README.TXT

The \Mssdk\Samples\DbMsg directory contains samples for the following
Platform SDK components:

* ADO
* OLE-DB
* Microsoft Exchange
* Microsoft SQL Server
* ODBC
* MAPI

The build environment (headers, libraries, and minimum build
tools) for the Platform SDK must be installed in order to build
any of the samples in the \Mssdk\Samples directory. The build
environment can be installed from the readme.htm or by running
setup.exe on the root of the Platform SDK compact disc. The most
recent version of the build environment is also available at
http://www.microsoft.com/msdn/sdk/platform.htm.

Some components may also require you to install runtimes, depending
on the platform on which you are trying to run the sample.
Redistributables are available in the \Mssdk\Redist subdirectory
on the Platform SDK compact disc.

Documentation for the components listed above can be installed
from the Platform SDK compact disc, or can be viewed online at
http://premium.microsoft.com/msdn/library/. This is a free service;
registration is necessary.

If you do not see any subdirectories in the \Mssdk\Samples\DbMsg
node, you'll need to install the COM and ActiveX components
from the Platform SDK compact disc or download and install them
from http://www.microsoft.com/msdn/sdk/platform.htm.


ADOASP.TXT

ADO Active Server Pages Sample
==============================
This sample demonstrates the use of ADO in Microsoft Active Server Pages. In this sample, data for PC components is stored in cmpsales.mdb. The sample application lets web clients browse through the components and obtain a price quote for their selected PC configuration.

Setup
-----
1. This sample requires Microsoft Internet Information Server 3.0 and Microsoft Active Server Pages. You can download these from http://www.microsoft.com/iis/.
2. Create an ODBC system DSN compsales that points to the sample data in cmpsales.mdb.
3. Copy the .asp, .jpg and .gif files of this sample to some directory on the web server machine with read and execute access.
4. Open cmpsales.asp in your web browser with the address : http://<machine name>/<virtual directory>/cmpsales.asp where <machine name> and <virtual directory> are your machine name and the virtual directory pointing to location of the sample files copied in Step 3.


CMPPRICE.ASP

<html>
<head><title>Chet's Damage Assessment</title><head>
<body background="Cldshalf.jpg" BGCOLOR=ffffcc TEXT="#000000">

<h1>Chet's Damage Assessment</h1>

<h3>System Configuration</h3>
<%
' Print system configuration
vSelections = Array("CPUs", "Memory", "HardDrives", "Monitors", "VideoBoards", "CDROMs", "SoundCards", "Printers", "Modems")
for i = 0 to ubound(vSelections)
response.write(request.form(vSelections(i)))
response.write("<br>")
next
%>

<%
' Figure out the cost
pricesql = "select sum(unitprice) from products where "
pricesql = pricesql & "productname = '" & request.form(vSelections(0)) & "'"
for i = 1 to ubound(vSelections)
pricesql = pricesql & " or productname = '" & request.form(vSelections(i)) & "'"
next

set price = server.createobject("adodb.recordset")
price.open pricesql, "dsn=compsales;uid=admin;pwd=;", 1
%>

<h3>Damage : <%=price(0)%>$ </h3>

<hr size=4>
<font face="MS SANS SERIF" size=1>
The data mentioned herein is totally fictitious and is in no way intended to represent any
real data, unless otherwise noted.
<p>
<img src="ie_anim.gif" width="88" height="31" align=right alt="ie">
</font>

</body>
</html>


CMPSALES.ASP

<html>
<head><title>Chet's Computer Stop</title><head>
<body background="Cldshalf.jpg" BGCOLOR=ffffcc TEXT="#000000">

<h1>Chet's Computer Stop</h1>

<form method=post action="cmpprice.asp">

<%
set compsales = server.createobject("adodb.connection")
compsales.open "compsales", "admin", ""

vQueries = Array("CPUs", "Memory", "HardDrives", "Monitors", "VideoBoards", "CDROMs", "SoundCards", "Printers", "Modems")

%>

<center>

<table width=100% border=0>

<%
set rs = server.createobject("adodb.recordset")

for i = 0 to UBound(vQueries)

rs.open "select * from " & vQueries(i), compsales, 1

response.write("<tr>")

value = rs(0)
response.write("<td>")
response.write(value)
response.write("</td>")

response.write("<td>")
response.write("<select name=""" & vQueries(i) & """>")
while not rs.eof
value = rs(0)
response.write("<option value = """)
response.write(value)
response.write(""" >")
response.write(value)
response.write("</option>")
rs.movenext
wend
response.write("</select>")
response.write("</td>")
response.write("</tr>")

rs.close

next
%>

</table>

</center>

<p><input type=submit value="Price Your System"><input type=reset value="Reset Selections">
</form>

<hr size=4>
<font face="MS SANS SERIF" size=1>
The data mentioned herein is totally fictitious and is in no way intended to represent any
real data, unless otherwise noted.
<p>

<img src="ie_anim.gif" width="88" height="31" align=right alt="ie">

</font>

</body>
</html>


ADOEMP.TXT

ADO Employee Sample
===================
This sample displays Employee data in a form. It allows users to scroll, insert, update and delete data. Note that existing employee records cannot be deleted from the table because of referential integrity constraints.


EMP.CPP

// Emp.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "Emp.h"
#include "EmpDlg.h"

#ifdef _DEBUG
//#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CEmpApp

BEGIN_MESSAGE_MAP(CEmpApp, CWinApp)
//{{AFX_MSG_MAP(CEmpApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEmpApp construction

CEmpApp::CEmpApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CEmpApp object

CEmpApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CEmpApp initialization

BOOL CEmpApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls();// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

CEmpDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
//  application, rather than start the application's message pump.
return FALSE;
}


EMP.H

// Emp.h : main header file for the EMP application
//

#include <objbase.h>
#include <afxdisp.h>
#include <tchar.h>// Unicode
#include <adoid.h>// ADO C++ header
#include <adoint.h>// ADO C++ header

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"// main symbols

/////////////////////////////////////////////////////////////////////////////
// CEmpApp:
// See Emp.cpp for the implementation of this class
//

class CEmpApp : public CWinApp
{
public:
CEmpApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEmpApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CEmpApp)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// Conversion macros/inline functions - Variant

inline CString VTOCSTR(VARIANT *v)
{
if(v->vt==VT_BSTR)
{
CString str((LPCWSTR)v->bstrVal);
return str;
}
else
{
return CString("");
}
}

#define VTOLONG(v)((v).vt==VT_I4 ? (LONG)(v).lVal:0L)
#define VTODATE(v)((v).vt==VT_DATE ? (CTime)(v).iVal:0L)

class CVar : public VARIANT
{
public:
CVar()
{
VariantInit(this);
}
CVar(VARTYPE vt, SCODE scode = 0)
{
VariantInit(this);
this->vt = vt;
this->scode = scode;
}
CVar(VARIANT var)
{
*this = var;
}
~CVar()
{
VariantClear(this);
}

// ASSIGNMENT OPS.
CVar & operator=(PCWSTR pcwstr)
{
VariantClear(this);
if (NULL == (this->bstrVal = SysAllocStringLen(pcwstr, wcslen(pcwstr))))
throw E_OUTOFMEMORY;
this->vt = VT_BSTR;
return *this;
}
CVar & operator=(VARIANT var)
{
HRESULT hr;

VariantClear(this);
if (FAILED(hr = VariantCopy(this, &var)))
throw hr;
return *this;
}

// CAST OPS.
// doesn't change type. only returns BSTR if variant is of type
// bstr. asserts otherwise.
operator BSTR() const
{
if(VT_BSTR == this->vt)
return this->bstrVal;
else
return NULL;
}

HRESULT Clear()
{
return VariantClear(this);
}
};


EMPBIZ.CPP

// EmpBiz.cpp : implementation file
//

#include "stdafx.h"
#include "Emp.h"
#include "EmpBiz.h"

#ifdef _DEBUG
//#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define THROW_ERR(exp)if (FAILED(hr = (exp))) throw hr

const LPCWSTR g_lpcwszSource = L"OLE_DB_NWind_Jet;";
const LPCWSTR g_lpcwszUser = L"Admin";
const LPCWSTR g_lpcwszPwd = L"";
const LPCWSTR g_lpcwszSQL = L"select EmployeeId, LastName, FirstName, Title, HomePhone from Employees";

/////////////////////////////////////////////////////////////////////////////
// CEmpBiz

CEmpBiz::CEmpBiz()
{
m_piConnection = NULL;
m_piEmpRecordSet = NULL;
m_fConnected = FALSE;
m_fRecordsetEmpty = TRUE;
}

CEmpBiz::~CEmpBiz()
{
if ( m_piConnection != NULL )
m_piConnection->Release();
if ( m_piEmpRecordSet != NULL )
m_piEmpRecordSet->Release();
m_varLastGoodRecord.Clear();
m_piConnection = NULL;
m_piEmpRecordSet = NULL;
}

long CEmpBiz::GetEmployeeId()
{
HRESULThr;
COleVariantvFldName, vID;

if (!m_fConnected)
return 0;

//Watch for empty recordset
if(m_fRecordsetEmpty)
return 0;

vFldName.bstrVal = CString(EMP_EMPLOYEE_ID).AllocSysString();
vFldName.vt = VT_BSTR;
THROW_ERR( m_piEmpRecordSet->get_Collect(vFldName, vID));

return VTOLONG(vID);
}

CString CEmpBiz::GetFirstName()
{
HRESULThr;
COleVariantvFldName, vFirstName;

 if (!m_fConnected)
return "";

//Watch for empty recordset
if(m_fRecordsetEmpty)
return "";

vFldName.bstrVal = CString(EMP_FIRST_NAME).AllocSysString();
vFldName.vt = VT_BSTR;
THROW_ERR(m_piEmpRecordSet->get_Collect(vFldName, vFirstName) );

return VTOCSTR(vFirstName);
}

CString CEmpBiz::GetLastName()
{
HRESULThr;
COleVariantvFldName, vLastName;

 if (!m_fConnected)
return "";

//Watch for empty recordset
if(m_fRecordsetEmpty)
return "";

vFldName.bstrVal = CString(EMP_LAST_NAME).AllocSysString();
vFldName.vt = VT_BSTR;
THROW_ERR(m_piEmpRecordSet->get_Collect(vFldName, vLastName) );

return VTOCSTR(vLastName);
}

CString CEmpBiz::GetHomePhone()
{
HRESULThr;
COleVariantvFldName, vHomePhone;

 if (!m_fConnected)
return "";

//Watch for empty recordset
if(m_fRecordsetEmpty)
return "";

vFldName.bstrVal = CString(EMP_HOME_PHONE).AllocSysString();
vFldName.vt = VT_BSTR;
THROW_ERR(m_piEmpRecordSet->get_Collect(vFldName, vHomePhone) );

return VTOCSTR(vHomePhone);
}

CString CEmpBiz::GetTitle()
{
HRESULThr;
COleVariantvFldName, vTitle;

 if (!m_fConnected)
return "";

//Watch for empty recordset
if(m_fRecordsetEmpty)
return "";

vFldName.bstrVal = CString(EMP_TITLE).AllocSysString();
vFldName.vt = VT_BSTR;
THROW_ERR(m_piEmpRecordSet->get_Collect(vFldName, vTitle) );

return VTOCSTR(vTitle);
}

BOOL CEmpBiz::IsAddMode()
{
HRESULThr;
 EditModeEnumlEditMode;

THROW_ERR(m_piEmpRecordSet->get_EditMode(&lEditMode) );

return lEditMode == adEditAdd ;
}

void CEmpBiz::AddRecord()
{
// TODO: Add your control notification handler code here
HRESULThr;
VARIANTrgvFields;
VARIANTrgvValues;

if (!m_fConnected)
return;

ClearFilter() ;

//Watch for empty recordset
if(!m_fRecordsetEmpty && !IsAddMode() )
{
//Remember where we were before adding in case the user
//cancels and we have to return
THROW_ERR(m_piEmpRecordSet->get_Bookmark(m_varLastGoodRecord) );
}

rgvFields.vt = VT_ERROR;
rgvFields.scode = DISP_E_PARAMNOTFOUND;

rgvValues.vt = VT_ERROR;
rgvValues.scode = DISP_E_PARAMNOTFOUND;

THROW_ERR(m_piEmpRecordSet->AddNew(rgvFields, rgvValues) );
//THROW_ERR( m_piEmpRecordSet->Update(rgvFields, rgvValues) );

m_fRecordsetEmpty = FALSE;
return ;
}

void CEmpBiz::DeleteRecord()
{
// TODO: Add your control notification handler code here
HRESULThr;
 EditModeEnumlEditMode;
VARIANT_BOOLvbEOF, vbBOF;

//Watch for empty recordset
if (!m_fConnected || m_fRecordsetEmpty)
return;

ClearFilter() ;

//Delete method depends on current mode
THROW_ERR(m_piEmpRecordSet->get_EditMode(&lEditMode) );

switch (lEditMode)
{
case adEditNone: // Just delete it
{
THROW_ERR( m_piEmpRecordSet->Delete(adAffectCurrent) );
THROW_ERR( m_piEmpRecordSet->MoveNext() );
//Watch for end of record set
THROW_ERR( m_piEmpRecordSet->get_EOF(&vbEOF) );
if(vbEOF)
{
THROW_ERR(m_piEmpRecordSet->MovePrevious() );
//Check for empty record set.
THROW_ERR( m_piEmpRecordSet->get_BOF(&vbBOF) );
if(vbBOF)
m_fRecordsetEmpty  = TRUE;
}
break;
}

case adEditInProgress: //Forget changes
{
THROW_ERR( m_piEmpRecordSet->CancelUpdate() );
THROW_ERR( m_piEmpRecordSet->Delete(adAffectCurrent) );
THROW_ERR( m_piEmpRecordSet->MoveFirst() );
break;
}

case adEditAdd: //If new record, go back to last known
{
THROW_ERR( m_piEmpRecordSet->CancelUpdate() );
THROW_ERR( m_piEmpRecordSet->put_Bookmark(m_varLastGoodRecord) );
}
}

return ;
}

BOOL CEmpBiz::MoveNext()
{
// TODO: Add your control notification handler code here
HRESULThr;
VARIANT_BOOL vbEOF;

if (!m_fConnected || m_fRecordsetEmpty)
return FALSE;

ClearFilter() ;

THROW_ERR(m_piEmpRecordSet->MoveNext() );

//Watch for end of record set
THROW_ERR(m_piEmpRecordSet->get_EOF(&vbEOF) );
if(vbEOF)
{
THROW_ERR(m_piEmpRecordSet->MovePrevious() );
return FALSE;
}
else
{
return TRUE;
}
}

BOOL CEmpBiz::MovePrevious()
{
// TODO: Add your control notification handler code here
HRESULThr;
VARIANT_BOOL vbBOF;

if (!m_fConnected || m_fRecordsetEmpty)
return FALSE;

ClearFilter() ;

THROW_ERR(m_piEmpRecordSet->MovePrevious() );

//Watch for beginning of recordset
THROW_ERR(m_piEmpRecordSet->get_BOF(&vbBOF) );
if(vbBOF)
{
THROW_ERR(m_piEmpRecordSet->MoveNext() );
return FALSE;
}
else
{
return TRUE;
}
}

BOOL CEmpBiz::MoveFirst()
{
// TODO: Add your control notification handler code here
HRESULThr;
VARIANT_BOOL vbBOF;

if (!m_fConnected || m_fRecordsetEmpty)
return FALSE;

ClearFilter() ;

THROW_ERR(m_piEmpRecordSet->MoveFirst() );

//Watch for beginning of recordset
THROW_ERR(m_piEmpRecordSet->get_BOF(&vbBOF) );
if(vbBOF)
{
return FALSE;
}
else
{
return TRUE;
}
}

BOOL CEmpBiz::MoveLast()
{
// TODO: Add your control notification handler code here
HRESULThr;
VARIANT_BOOL vbEOF;

if (!m_fConnected || m_fRecordsetEmpty)
return FALSE;

ClearFilter() ;

THROW_ERR(m_piEmpRecordSet->MoveLast() );

//Watch for beginning of recordset
THROW_ERR(m_piEmpRecordSet->get_BOF(&vbEOF) );
if(vbEOF)
{
return FALSE;
}
else
{
return TRUE;
}
}

BOOL CEmpBiz::FindForward(CString strCriteria)
{
HRESULThr;
VARIANT_BOOLvbEOF;
COleVariantv;

if (!m_fConnected || m_fRecordsetEmpty)
return FALSE;

THROW_ERR(m_piEmpRecordSet->get_Filter(&v) );
if ( v.vt != VT_BSTR || strCriteria !=v.bstrVal )
{
v.vt = VT_BSTR;
v.bstrVal = strCriteria.AllocSysString();
THROW_ERR(  m_piEmpRecordSet->put_Filter(v) );
}
else
{
THROW_ERR( m_piEmpRecordSet->MoveNext() );
}

//Watch for ending of recordset
THROW_ERR(m_piEmpRecordSet->get_EOF(&vbEOF));
if(vbEOF)
{
ClearFilter() ;
THROW_ERR( m_piEmpRecordSet->MoveLast() );

return FALSE;
}
else
{
return TRUE;
}
}

void CEmpBiz::ClearFilter()
{
HRESULThr;
VARIANTv;

if ( !m_fRecordsetEmpty)
{
v.vt = VT_I2;
v.iVal = adFilterNone;
THROW_ERR(m_piEmpRecordSet->put_Filter(v) );
}
}

//
//Update record in the database
//
void CEmpBiz::UpdateEmpRec(CString &strFirstName,
CString &strHomePhone, CString &strLastName,
CString &strTitle)
{
HRESULThr;
VARIANTvarFields;
VARIANTvarValues;
WCHAR*columnNames[4] = { L"firstName", L"Lastname", L"title", L"homePhone"};
ADOFields*pFields = NULL;
ADOField*pField = NULL;
CVarvarIndex(VT_BSTR);
COleVariantvarFieldVal;

if  (m_fRecordsetEmpty)
return;

varFields.vt = VT_ERROR;
varFields.scode = DISP_E_PARAMNOTFOUND;
varValues.vt = VT_ERROR;
varValues.scode = DISP_E_PARAMNOTFOUND;

try
{
// get the fields interface
THROW_ERR( m_piEmpRecordSet->get_Fields(&pFields) );

varIndex = SysAllocString(columnNames[0]) ;
THROW_ERR( pFields->get_Item(varIndex, &pField) );
varFieldVal.vt = VT_BSTR;
varFieldVal.bstrVal = strFirstName.AllocSysString();
THROW_ERR( pField->put_Value(varFieldVal) );
varFieldVal.Clear();

varIndex = SysAllocString(columnNames[1]) ;
THROW_ERR( pFields->get_Item(varIndex, &pField) );
varFieldVal.vt = VT_BSTR;
varFieldVal.bstrVal = strLastName.AllocSysString();
THROW_ERR( pField->put_Value(varFieldVal) );
varFieldVal.Clear();

varIndex = SysAllocString(columnNames[2]) ;
THROW_ERR( pFields->get_Item(varIndex, &pField) );
varFieldVal.vt = VT_BSTR;
varFieldVal.bstrVal = strTitle.AllocSysString();
THROW_ERR( pField->put_Value(varFieldVal) );
varFieldVal.Clear();

varIndex = SysAllocString(columnNames[3]) ;
THROW_ERR( pFields->get_Item(varIndex, &pField) );
varFieldVal.vt = VT_BSTR;
varFieldVal.bstrVal = strHomePhone.AllocSysString();
THROW_ERR( pField->put_Value(varFieldVal) );
varFieldVal.Clear();

//Commit the changes
THROW_ERR( m_piEmpRecordSet->Update(varFields, varValues) );

pField->Release();
pFields->Release();

//Return to the edited record
//CADOBookmark cBookmark = m_piEmpRecordSet->GetLastModified();
//m_piEmpRecordSet->SetBookmark(cBookmark);
}
catch (HRESULT hr)
{
if (pField)
pField->Release();
if (pFields)
pFields->Release();
throw hr;
}
return ;
}

/////////////////////////////////////////////////////////////////////////////
// CEmpBiz support

//When the document is created, connect to the database and open the
//Employee recordset.
BOOL CEmpBiz::ConnectToDatabase()
{

CVarvarDataSource, varUserId, varPwd, varSQL;
HRESULThr;
CVarvNull(VT_ERROR, DISP_E_PARAMNOTFOUND);
VARIANT_BOOLvbEOF, vbBOF;

varDataSource= g_lpcwszSource;
varUserId= g_lpcwszUser;
varPwd= g_lpcwszPwd;
varSQL= g_lpcwszSQL;

//Open the database and the recordset
try
{
if ( m_piConnection == NULL || m_piEmpRecordSet == NULL)
{
THROW_ERR(  CoInitialize(NULL) );
THROW_ERR(  CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID *)&m_piConnection) );
THROW_ERR(  m_piConnection->Open( varDataSource, varUserId, varPwd ) );

THROW_ERR(  CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *)&m_piEmpRecordSet) );
THROW_ERR(  m_piEmpRecordSet->putref_ActiveConnection(m_piConnection) );
THROW_ERR(  m_piEmpRecordSet->put_Source(varSQL) );

vNull.vt = VT_ERROR;
vNull.scode = DISP_E_PARAMNOTFOUND;
THROW_ERR(  m_piEmpRecordSet->Open(vNull, vNull, adOpenKeyset, adLockOptimistic, adCmdText) );

//Check for empty record set.
THROW_ERR( m_piEmpRecordSet->get_EOF(&vbEOF) );
THROW_ERR( m_piEmpRecordSet->get_BOF(&vbBOF) );
if(vbEOF && vbBOF)
m_fRecordsetEmpty  = TRUE;
else
m_fRecordsetEmpty  = FALSE;
}

return TRUE;
}
catch (HRESULT hr)
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
AfxMessageBox(szBuf);

return (FALSE);
}
}


EMPBIZ.H

// EmpBiz.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CEmpBiz dialog

class CEmpBiz
{
// Construction
public:
CEmpBiz();// standard constructor
~CEmpBiz();

// Attributes
public:
ADORecordset*m_piEmpRecordSet;
BOOLm_fConnected;
BOOLm_fRecordsetEmpty;

protected:
ADOConnection*m_piConnection;
COleVariantm_varLastGoodRecord;

// Operations
public:
BOOLConnectToDatabase(); //Opens a database
longGetEmployeeId();
CString GetFirstName();
CString GetLastName();
CString GetHomePhone() ;
CString GetTitle() ;
voidAddRecord() ;
voidDeleteRecord();
BOOLMoveNext() ;
BOOLMovePrevious();
BOOLMoveFirst() ;
BOOLMoveLast();
BOOLFindForward(CString strCriteria) ;
voidClearFilter();
BOOLIsAddMode();
voidUpdateEmpRec(CString &strFirstName,
CString &strHomePhone, CString &strLastName,
CString &strTitle);
};

#define EMP_EMPLOYEE_IDL"EmployeeID"
#defineEMP_LAST_NAMEL"LastName"
#defineEMP_FIRST_NAMEL"FirstName"
#define EMP_TITLEL"Title"
#define EMP_HOME_PHONEL"HomePhone"


EMPDLG.CPP

// EmpDlg.cpp : implementation file
//

#include "stdafx.h"
#include "Emp.h"
#include "EmpDlg.h"

#ifdef _DEBUG
//#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEmpDlg dialog

CEmpDlg::CEmpDlg(CWnd* pParent /*=NULL*/)
: CDialog(CEmpDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CEmpDlg)
m_nEmpNum = 0;
m_strFirstName = _T("");
m_strHomePhone = _T("");
m_strLastName = _T("");
m_strTitle = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

CEmpDlg::~CEmpDlg()
{
if (m_pBiz != NULL)
delete m_pBiz;
}

void CEmpDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CEmpDlg)
DDX_Text(pDX, IDC_EMPNUM, m_nEmpNum);
DDX_Text(pDX, IDC_FIRSTNAME, m_strFirstName);
DDX_Text(pDX, IDC_HOMEPHONE, m_strHomePhone);
DDX_Text(pDX, IDC_LASTNAME, m_strLastName);
DDX_Text(pDX, IDC_TITLE, m_strTitle);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CEmpDlg, CDialog)
//{{AFX_MSG_MAP(CEmpDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_EDIT_ADD, OnEditAdd)
ON_BN_CLICKED(ID_EDIT_DELETE, OnEditDelete)
ON_BN_CLICKED(ID_EDIT_NEXT, OnEditNext)
ON_BN_CLICKED(ID_EDIT_PREVIOUS, OnEditPrevious)
ON_BN_CLICKED(ID_EDIT_FIRST, OnEditFirst)
ON_BN_CLICKED(ID_EDIT_LAST, OnEditLast)
ON_BN_CLICKED(ID_EDIT_FINDF, OnEditFindF)
ON_BN_CLICKED(ID_APP_OPEN_RECORD, OnAppOpenRecord)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEmpDlg message handlers

BOOL CEmpDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}

// Set the icon for this dialog.  The framework does this automatically
//  when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);// Set big icon
SetIcon(m_hIcon, FALSE);// Set small icon

//Never enable the employee ID
(GetDlgItem(IDC_EMPNUM))->EnableWindow(FALSE);

// TODO: Add extra initialization here
m_pBiz = new CEmpBiz;

return TRUE;  // return TRUE  unless you set the focus to a control
}

void CEmpDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CEmpDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}

try
{
m_nEmpNum =  m_pBiz->GetEmployeeId();
m_strFirstName = m_pBiz->GetFirstName();
m_strLastName = m_pBiz->GetLastName();
m_strHomePhone = m_pBiz->GetHomePhone();
m_strTitle = m_pBiz->GetTitle();
}
catch (HRESULT hr)
{
TCHAR szBuf[256];
MessageBeep(0);
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
return ;
}

UpdateData(FALSE); //Invoke Data Exchange (copy member data to form controls)
return ;
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CEmpDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}

void CEmpDlg::OnEditAdd()
{
// TODO: Add your control notification handler code here
try
{
if(!CommitAlteredEmpRec())
return;

m_pBiz->AddRecord();

OnPaint();
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
MessageBeep(0);
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
return ;
}

void CEmpDlg::OnEditDelete()
{
// TODO: Add your control notification handler code here
try
{
//Delete method depends on current mode
m_pBiz->DeleteRecord();
OnPaint();
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
MessageBeep(0);
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
return ;
}

void CEmpDlg::OnEditNext()
{
// TODO: Add your control notification handler code here
if(!CommitAlteredEmpRec())
return;

try
{
if(m_pBiz->MoveNext())
OnPaint();
else
MessageBeep(0);
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
}

void CEmpDlg::OnEditPrevious()
{
// TODO: Add your control notification handler code here
if(!CommitAlteredEmpRec())
return;

try
{
if(m_pBiz->MovePrevious())
OnPaint();
else
MessageBeep(0);
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
}

void CEmpDlg::OnEditFirst()
{
// TODO: Add your control notification handler code here
if(!CommitAlteredEmpRec())
return;

try
{
if(m_pBiz->MoveFirst())
OnPaint();
else
MessageBeep(0);
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
}

void CEmpDlg::OnEditLast()
{
// TODO: Add your control notification handler code here
if(!CommitAlteredEmpRec())
return;

try
{
if(m_pBiz->MoveLast())
OnPaint();
else
MessageBeep(0);
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
}

void CEmpDlg::OnEditFindF()
{
try
{
if(m_pBiz->FindForward(PrepareCriteria()))
{
OnPaint();
}
else
{//not found.
MessageBeep(0);
//.. Should return to the last visited record.
}
}
catch( HRESULT hr )
{
TCHAR szBuf[256];
MessageBeep(0);
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
}
}

BOOL CEmpDlg::CommitAlteredEmpRec()
{
CStringstrOldFirstName, strOldHomePhone, strOldLastName, strOldTitle;
COleVariantvFirstName, vHomePhone, vLastName, vTitle, vID;

if (!m_pBiz->m_fConnected)
return FALSE;

//Get current record string values
try
{
strOldFirstName = m_pBiz->GetFirstName();
strOldLastName = m_pBiz->GetLastName();
strOldHomePhone = m_pBiz->GetHomePhone();
strOldTitle = m_pBiz->GetTitle();
}
catch(HRESULT  hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
return FALSE;
}

//Force DDX to update member values
if(!UpdateData())
return FALSE;

// Did any fields change value?
if(
(m_strFirstName== strOldFirstName) &&
(m_strHomePhone== strOldHomePhone) &&
(m_strLastName== strOldLastName) &&
(m_strTitle== strOldTitle) && !m_pBiz->IsAddMode() )
return TRUE;

//Save it, dump it or stay on it?
switch (AfxMessageBox(IDS_PROMPT_COMMIT_EMPREC, MB_YESNOCANCEL))
{
case IDYES:
{
try
{
m_pBiz->UpdateEmpRec(m_strFirstName,
m_strHomePhone,
m_strLastName,
m_strTitle);
}
catch (CMemoryException &memx)
{
TCHAR szBuf[256];
memx.GetErrorMessage(szBuf, sizeof(szBuf), NULL);
AfxMessageBox(szBuf);
return FALSE;
}
catch(HRESULT  hr )
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %X \n"), hr);
AfxMessageBox(szBuf);
return FALSE;
}

return TRUE;
}

case IDNO:
{
return TRUE;
}

default:
{
GetDlgItem(IDC_EMPNUM)->SetFocus();
return TRUE;
}
}
return TRUE;
}

CString CEmpDlg::PrepareCriteria()
{
CStringstrCriteria = "";

//Force DDX to update member values
if(!UpdateData())
return strCriteria;

if(m_strFirstName != "")
strCriteria = "FirstName = '" + m_strFirstName +"'";
if(m_strLastName != "")
strCriteria = strCriteria + ( strCriteria == "" ? "":" AND ") + "LastName = '" + m_strLastName +"'";
if(m_strHomePhone != "")
strCriteria = strCriteria + ( strCriteria == "" ? "":" AND ") + "HomePhone = '" + m_strHomePhone +"'";
if(m_strTitle != "")
strCriteria = strCriteria + ( strCriteria == "" ? "":" AND ") + "Title = '" + m_strTitle +"'";

return strCriteria;
}

void CEmpDlg::OnAppOpenRecord()
{
// TODO: Add your control notification handler code here

//Connect to the EMPLOYEE database
if(!m_pBiz->ConnectToDatabase())
{
m_pBiz->m_fConnected = FALSE;
return;
}

m_pBiz->m_fConnected = TRUE;

GetDlgItem(ID_EDIT_PREVIOUS)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_NEXT)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_DELETE)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_ADD)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_LAST)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_FIRST)->EnableWindow(TRUE);
GetDlgItem(ID_EDIT_FINDF)->EnableWindow(TRUE);

OnPaint();
}


EMPDLG.H

// EmpDlg.h : header file
//

#include "EmpBiz.h"

/////////////////////////////////////////////////////////////////////////////
// CEmpDlg dialog

class CEmpDlg : public CDialog
{
// Construction
public:
CEmpDlg(CWnd* pParent = NULL);// standard constructor
~CEmpDlg();

// Dialog Data
//{{AFX_DATA(CEmpDlg)
enum { IDD = IDD_EMP_DIALOG };
longm_nEmpNum;
CStringm_strFirstName;
CStringm_strHomePhone;
CStringm_strLastName;
CStringm_strTitle;
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEmpDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);
//}}AFX_VIRTUAL

// Operations
public:
BOOL CommitAlteredEmpRec();
void SetFieldStates() ;
CString PrepareCriteria();

// Implementation
protected:
HICONm_hIcon;
CEmpBiz*m_pBiz;

// Generated message map functions
//{{AFX_MSG(CEmpDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnEditAdd();
afx_msg void OnEditDelete();
afx_msg void OnEditNext();
afx_msg void OnEditPrevious();
afx_msg void OnEditFirst();
afx_msg void OnEditLast();
afx_msg void OnEditFindF();
afx_msg void OnAppOpenRecord();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

#define EMP_EMPLOYEE_IDL"EmployeeID"
#defineEMP_LAST_NAMEL"LastName"
#defineEMP_FIRST_NAMEL"FirstName"
#define EMP_TITLEL"Title"
#define EMP_HOME_PHONEL"HomePhone"


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Emp.rc
//
#define ID_EDIT_PREVIOUS                3
#define ID_EDIT_NEXT                    4
#define ID_EDIT_DELETE                  5
#define ID_EDIT_ADD                     6
#define ID_EDIT_LAST                    7
#define ID_EDIT_FIRST                   8
#define ID_EDIT_FINDF                   10
#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDS_ABOUTBOX                    101
#define IDD_EMP_DIALOG                  102
#define IDR_MAINFRAME                   128
#define IDC_EMPNUM                      1000
#define IDC_LASTNAME                    1001
#define IDC_FIRSTNAME                   1003
#define IDC_TITLE                       1004
#define IDC_HOMEPHONE                   1005
#define ID_APP_OPEN_RECORD              57666
#define IDS_PROMPT_COMMIT_EMPREC        61204

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        129
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//Emp.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"


STDAFX.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @modulestdafx.h
//
// @devnote None
//--------------------------------------------------------------------
// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <MMSYSTEM.H>// For system time functions

#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT


GETROWS.TXT

GetRows Sample
==============
This C/C++ sample demonstrates the use of the Recordset object's GetRows method to retrieve all the rows of a rowset into a 2-dimensional array of variants.


GETRDLG.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc GetRows Sample
//
// @modulegetrdlg.h
//
// @devnote None
//--------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////
// CGetRowsDlg dialog

class CGetRowsDlg : public CDialog
{
// Construction
public:
CGetRowsDlg(CWnd* pParent = NULL);// standard constructor
~CGetRowsDlg();

//Performs the standard DAO GetRows
HRESULT DoGetRows();

// Dialog Data
//{{AFX_DATA(CGetRowsDlg)
enum { IDD = IDD_MAINFORM };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGetRowsDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
HICON m_hIcon;
ADOConnection*m_piConnection;
ADORecordset*m_piEmpRecordSet;
CStringm_strSource;
CStringm_strUser;
CStringm_strPassword;
CStringm_strSQL;

void OnExecute();
// Generated message map functions
//{{AFX_MSG(CGetRowsDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

// Field numbers for employee table
#defineEMP_ID0L // Must use LONG or compiler gets confused with NULL
#defineEMP_LNAME1
#defineEMP_FNAME2
#define MAX_EMP_REC10 // Arbitrary maximum


GETROWS.CPP

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc GetRows Sample
//
// @modulegetrows.cpp
//
// @devnote None
//--------------------------------------------------------------------

#include "stdafx.h"
#include "GetRows.h"
#include "GetRDlg.h"

/////////////////////////////////////////////////////////////////////////////
// CGetRowsApp

BEGIN_MESSAGE_MAP(CGetRowsApp, CWinApp)
//{{AFX_MSG_MAP(CGetRowsApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGetRowsApp construction

CGetRowsApp::CGetRowsApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CGetRowsApp object

CGetRowsApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CGetRowsApp initialization

BOOL CGetRowsApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls();// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

CGetRowsDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
//  application, rather than start the application's message pump.
return FALSE;
}


GETROWS.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc GetRows Sample
//
// @modulegetrows.h
//
// @devnote None
//--------------------------------------------------------------------

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include <objbase.h>
#include <afxdisp.h>
#include <tchar.h>// Unicode
#include <adoid.h>// ADO C++ header
#include <adoint.h>// ADO C++ header

#include "resource.h"// main symbols

/////////////////////////////////////////////////////////////////////////////
// CGetRowsApp:
// See GetRows.cpp for the implementation of this class
//

class CGetRowsApp : public CWinApp
{
public:
CGetRowsApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGetRowsApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CGetRowsApp)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


GETRDLG.CPP

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc GetRows Sample
//
// @modulegetrdlg.cpp
//
// @devnote None
//--------------------------------------------------------------------

#include "stdafx.h"
#include "GetRows.h"
#include "GetRDlg.h"

const LPCTSTR x_lpcszSource = _T("OLE_DB_NWind_Jet");
const LPCTSTR x_lpcszUser = _T("Admin");
const LPCTSTR x_lpcszPassword = _T("");
const LPCTSTR x_lpcszSQL = _T("select EmployeeId, LastName, FirstName from Employees");

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
 //{{AFX_MSG_MAP(CAboutDlg)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGetRowsDlg dialog

CGetRowsDlg::CGetRowsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGetRowsDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CGetRowsDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_piConnection = NULL;
m_piEmpRecordSet = NULL;
m_strSource = x_lpcszSource;
m_strUser = x_lpcszUser;
m_strPassword = x_lpcszPassword;
m_strSQL = x_lpcszSQL;
}

CGetRowsDlg::~CGetRowsDlg()
{
if ( m_piConnection != NULL )
m_piConnection->Release();
if ( m_piEmpRecordSet != NULL )
m_piEmpRecordSet->Release();
m_piConnection = NULL;
m_piEmpRecordSet = NULL;
}

void CGetRowsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CGetRowsDlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CGetRowsDlg, CDialog)
//{{AFX_MSG_MAP(CGetRowsDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDD_EXECUTE, OnExecute)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGetRowsDlg message handlers

BOOL CGetRowsDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}

// Set the icon for this dialog.  The framework does this automatically
//  when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);// Set big icon
SetIcon(m_hIcon, FALSE);// Set small icon

return TRUE;  // return TRUE  unless you set the focus to a control
}

void CGetRowsDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CGetRowsDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CGetRowsDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}

void CGetRowsDlg::OnExecute()
{
BSTRbstrSource = NULL;
BSTRbstrUser = NULL;
BSTRbstrPassword = NULL;
BSTRbstrSQL = NULL;
HRESULThr;
COleVariantvNull;

//Open the database and the recordset
if ( m_piConnection == NULL || m_piEmpRecordSet == NULL)
{
hr = CoInitialize(NULL);
if (FAILED(hr))goto ErrorExit;

hr = CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID *)&m_piConnection);
if (FAILED(hr))goto ErrorExit;

bstrSource = m_strSource.AllocSysString();
bstrUser = m_strUser.AllocSysString();
bstrPassword = m_strPassword.AllocSysString();
bstrSQL = m_strSQL.AllocSysString();
if ( bstrSource == NULL || bstrUser == NULL || bstrSQL == NULL )
goto ErrorExit;

hr = m_piConnection->Open( bstrSource, bstrUser, bstrPassword );
 if (FAILED(hr))goto ErrorExit;

  hr = CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *)&m_piEmpRecordSet);
if (FAILED(hr))goto ErrorExit;

hr = m_piEmpRecordSet->put_Source(bstrSQL);
if (FAILED(hr))goto ErrorExit;

hr = m_piEmpRecordSet->putref_ActiveConnection(m_piConnection);
if (FAILED(hr))goto ErrorExit;

vNull.vt = VT_ERROR;
vNull.scode = DISP_E_PARAMNOTFOUND;
hr = m_piEmpRecordSet->Open(vNull, vNull, adOpenKeyset, adLockOptimistic, adCmdUnknown);
if (FAILED(hr))goto ErrorExit;
}

// Perform the two GetRows functions
hr = DoGetRows();
 if (FAILED(hr))goto ErrorExit;

// Reset the recordset
m_piEmpRecordSet->MoveFirst();

SysFreeString(bstrSource);
SysFreeString(bstrUser);
SysFreeString(bstrPassword);
SysFreeString(bstrSQL);

return ;

ErrorExit:
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
AfxMessageBox(szBuf);

SysFreeString(bstrSource);
SysFreeString(bstrUser);
SysFreeString(bstrPassword);
SysFreeString(bstrSQL);
return ;
}

// Perform standard GetRows against the Employee table
HRESULT CGetRowsDlg::DoGetRows()
{
HRESULThr;
COleVariantvBookmark, rgvFields;
COleVariantcRows;
COleVariantvarField, varNewField;
CStringstrLBRow;
LONGlNumOfCol, lNumRecords;
LONGlIndex[2];
CListBox*pListBox = (CListBox *)GetDlgItem(IDD_GETROWSLIST);

//Perform GetRows on Employee table

//Start from the current place
vBookmark.vt = VT_ERROR;
vBookmark.scode = DISP_E_PARAMNOTFOUND;

//Get all columns.
rgvFields.vt = VT_ERROR;
rgvFields.scode = DISP_E_PARAMNOTFOUND;

if (!m_piEmpRecordSet)
return E_NOINTERFACE;

hr = m_piEmpRecordSet->GetRows(adGetRowsRest, //MAX_EMP_REC,// non-optional,
vBookmark,
rgvFields,
&cRows
);
if (FAILED(hr)) goto ErrorExit;

//Find out how many records were actually retrieved
//(SafeArrays are 1-based)
lNumOfCol = 2;
SafeArrayGetUBound(cRows.parray, 2, &lNumRecords);

//Clear the listbox
pListBox->ResetContent();

for (lIndex[1] = 0; lIndex[1] <= lNumRecords; lIndex[1]++)
{
strLBRow.Empty();//Clear the string

for (lIndex[0] = 0; lIndex[0] <= lNumOfCol; lIndex[0]++)   // get 3 columns
{
SafeArrayGetElement(cRows.parray, &lIndex[0], &varField);

hr = VariantChangeType(&varNewField, &varField, 0, VT_BSTR);

if(hr == S_OK)
{
strLBRow += (LPCWSTR)varNewField.bstrVal;
strLBRow += _T(", ");
}
varField.Clear();
varNewField.Clear();
}
pListBox->AddString(strLBRow);
}

return hr;

ErrorExit:
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
AfxMessageBox(szBuf);

return hr;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by GetRows.rc
//
#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDS_ABOUTBOX                    101
#define IDD_GETROWS_DIALOG              102
#define IDD_MAINFORM                    102
#define IDD_MAINDLG                     102
#define IDR_MAINFRAME                   128
#define IDC_LIST1                       1000
#define IDC_LIST2                       1001
#define IDC_GROUP                       1002
#define IDC_EXPLAIN                     1003
#define IDC_EXECUTE                     1004
#define IDD_GETROWSEXLIST               1006
#define IDD_GETROWSLISTEX               1006
#define IDD_EXECUTE                     1007
#define IDD_GETROWSLIST                 2000

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         2000
#define _APS_NEXT_SYMED_VALUE           201
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//GetRows.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"


STDAFX.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc GetRows Sample
//
// @modulestdafx.h
//
// @devnote None
//--------------------------------------------------------------------

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT


ADOISAPI.TXT

ADOISAPI Sample
===============
This is a C/C++ ISAPI extension sample that works with Microsoft Internet Information Server. This sample establishes a connection to a database, fetches data, formats the data as an HTML table and returns the data back to the client's browser. The connection to the database in this sample is cached for better performance.

Setup
-----
1. Copy adoisapi.dll to some directory on the web server with execute access with /scripts as the directory's alias.
2. Copy adoisapi.htm to some directory on the web server with read access.
3. Make sure OLE_DB_NWind_Jet has been set up as an ODBC system DSN on the web server machine. To verify this, bring up ODBC Administrator from the control panel, select the System DSN tab and make sure that OLE_DB_NWind_Jet is listed as a system data source. If not, you need to create a System DSN called OLE_DB_NWind_Jet and point it to the OLE DB SDK sample database.


ADOISAPI.CPP

//--------------------------------------------------------------------
// Microsoft ADO
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @moduleadoisapi.cpp | ADO ISAPI sample application
//
// @devnote None
//--------------------------------------------------------------------

#include <afxdisp.h>
#include <crtdbg.h>
#include <ole2.h>
#include <initguid.h>
#include <stdio.h>

#include "adoid.h"
#include "adoint.h"
#include "httpext.h"

VOIDPatchQuery(char *szQuery, char **ppszPatchedQuery);
HRESULTFetchData(EXTENSION_CONTROL_BLOCK *pECB);
HRESULTOutputErrors(EXTENSION_CONTROL_BLOCK *pECB, HRESULT hrError);
voidOutputString(EXTENSION_CONTROL_BLOCK *pECB, LPCSTR szBuffer);

const LPCWSTRx_lpcszSource = L"OLE_DB_NWind_Jet";
const LPCWSTRx_lpcszUser = L"Admin";
const LPCWSTRx_lpcszPassword = L"";

#defineMAX_VALUE_SIZE128
ADOConnection*g_piConnection = NULL;

// Called by the system when processes and threads are initialized and terminated,
// or upon calls to the LoadLibrary and FreeLibrary functions.
BOOL WINAPIDllEntryPoint(
    HINSTANCEhinstDLL,
    DWORDfdwReason,
    LPVOIDlpvReserved)
{
if (DLL_PROCESS_DETACH == fdwReason)
{
if (g_piConnection)
 g_piConnection->Release();
CoUninitialize();
}

return TRUE;
}

BOOL WINAPIGetExtensionVersion(
HSE_VERSION_INFO *pVer)
{
pVer->dwExtensionVersion = MAKELONG( HSE_VERSION_MINOR, HSE_VERSION_MAJOR );

lstrcpynA( pVer->lpszExtensionDesc,
"Sample ADO ISAPI Application",
HSE_MAX_EXT_DLL_NAME_LEN );

return TRUE;
} // GetExtensionVersion()

DWORD WINAPIHttpExtensionProc(
EXTENSION_CONTROL_BLOCK *pECB )
{
DWORDdwWritten;
charszContent[] = "Content-type: text/html\r\n\r\n";

dwWritten = sizeof(szContent);
    pECB->ServerSupportFunction (pECB->ConnID,
                                 HSE_REQ_SEND_RESPONSE_HEADER,
                                 NULL,
                                 &dwWritten,
                                 (LPDWORD)szContent);

if (FAILED(FetchData(pECB)))
return HSE_STATUS_ERROR;
else
return HSE_STATUS_SUCCESS;
}

charConvertHexToDec(char cHex)
{
if ((toupper(cHex) >= 'A') && (toupper(cHex) <= 'F'))
return cHex - 'A' + 10;
else
return cHex - '0';
}

// Hack to clean up the query string. This code will work
// for most, but not all cases. For instance when the query
// string has embedded '+', this code will not work.
VOID PatchQuery(
char *szQuery,
char **ppszPatchedQuery)
{
char *p = szQuery + 1;

while (*p++)
{
if (*p == '+') //replace '+' by ' '
*p = ' ';

if (*p == '%') //a number begins
{
char ch;

if (*++p)
{
ch = ConvertHexToDec(*p) << 4; //convert the first digit

if (*++p)
{
ch |= ConvertHexToDec(*p);  //convert the 2nd digit

if (!*(p + 1))
{
*(p-2) = ch;
*(p-1) = '\0';
break;
}
else
{
*(p-2) = ' ';
*(p-1) = ' ';
*p = ch;
}
}
}
}
}

*ppszPatchedQuery = szQuery + 1;
  return;
}

VOID OutputString(
EXTENSION_CONTROL_BLOCK *pECB,
LPCSTR szBuffer)
{
DWORD dwBuffer = strlen(szBuffer);
pECB->WriteClient(pECB->ConnID, (PVOID) szBuffer, &dwBuffer, 0);
}

HRESULTOutputErrors(
EXTENSION_CONTROL_BLOCK *pECB,
HRESULT hrError)
{
ADOErrors*piErrors = NULL;
ADOError*piError = NULL;
HRESULThr;
BSTRbstrError;
charszBuffer[128];
LONGlErrorCount;

// output hresult
sprintf(szBuffer, "<P><H1>Error Fetching Data</H1>"
"<P><H3>HRESULT</H3> %ld", hrError);
OutputString(pECB, szBuffer);

// output error description if available
if (g_piConnection != NULL)
{
hr = g_piConnection->get_Errors(&piErrors);
 if (FAILED(hr))goto ErrorExit;
hr = piErrors->get_Count(&lErrorCount);
 if (FAILED(hr))goto ErrorExit;
if (lErrorCount > 0)
{
OutputString(pECB, "<p><H3>Error Description</H3>");

for (short i = 0; i < lErrorCount; i++)
{
hr = piErrors->get_Item(COleVariant(i), &piError);
if (FAILED(hr))goto ErrorExit;
hr = piError->get_Description(&bstrError);
if (FAILED(hr))goto ErrorExit;
OutputString(pECB, CString((LPCWSTR)bstrError));
OutputString(pECB, "<p>");

SysFreeString(bstrError);
piError->Release();
piError = NULL;
}
}
}

ErrorExit:
if (piError)
piError->Release();
if (piErrors)
piErrors->Release();

return hr;

}

HRESULTFetchData(
EXTENSION_CONTROL_BLOCK *pECB)
{
HRESULThr;
ADOConnection*piConnection = NULL;
ADORecordset*piRecordSet= NULL;
ADOFields*piFields = NULL;
ADOField*piField = NULL;
longlFieldCount = 0;
shorti;
COleVariantvarFieldVal;
char*pszPatchedQuery;
BSTRbstrPatchedQuery = NULL;
BSTRbstrName = NULL;
BSTRbstrSource = NULL;
BSTRbstrUser = NULL;
BSTRbstrPassword = NULL;
VARIANTvNull;
VARIANT_BOOLfEOF;

OutputString(pECB, "<HEAD><TITLE>Query Results"
"</TITLE></HEAD>\r\n<BODY>\r\n");

PatchQuery(pECB->lpszQueryString, &pszPatchedQuery);

OutputString(pECB, "<P><H1>Query String</H1>");
OutputString(pECB, pszPatchedQuery);

if ( g_piConnection == NULL )
{
bstrSource = SysAllocString( x_lpcszSource );
bstrUser = SysAllocString( x_lpcszUser );
bstrPassword = SysAllocString( x_lpcszPassword );
if ( bstrSource == NULL || bstrUser == NULL || bstrPassword == NULL ) goto errNoUninit;

hr = CoInitialize(NULL);
if (FAILED(hr))goto errNoUninit;

hr = CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID *)&piConnection);
if (FAILED(hr))
{
CoUninitialize();
goto errNoUninit;
}

hr = piConnection->Open( bstrSource, bstrUser, bstrPassword );
 if (FAILED(hr))
{
piConnection->Release();
CoUninitialize();
goto errNoUninit;
}

g_piConnection = piConnection;
}

hr = CoInitialize(NULL);
if (FAILED(hr))goto errNoUninit;

hr = CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *)&piRecordSet);
if (FAILED(hr))goto errFreeStuff;

// Allocate BSTR.
bstrPatchedQuery = SysAllocStringLen(NULL, strlen(pszPatchedQuery) );

//Check returned BSTR.
if (bstrPatchedQuery == NULL )goto errFreeStuff;

// Convert the multibyte string to a wide-character string.
if ( MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszPatchedQuery, strlen(pszPatchedQuery), bstrPatchedQuery, strlen(pszPatchedQuery)) == 0 )
goto errFreeStuff;

hr = piRecordSet->put_Source(bstrPatchedQuery);
if (FAILED(hr))goto errFreeStuff;

hr = piRecordSet->putref_ActiveConnection(g_piConnection);
if (FAILED(hr))goto errFreeStuff;

vNull.vt = VT_ERROR;
vNull.scode = DISP_E_PARAMNOTFOUND;
hr = piRecordSet->Open(vNull, vNull, adOpenKeyset, adLockOptimistic, adCmdText);
if (FAILED(hr))goto errFreeStuff;

hr = piRecordSet->get_Fields(&piFields);
if (FAILED(hr))goto errFreeStuff;
hr = piFields->get_Count(&lFieldCount);
if (FAILED(hr))goto errFreeStuff;

OutputString(pECB, "<P><H1>Query Results</H1>");
OutputString(pECB, "<P><TABLE BORDER=1 CELLSPACING=4>");

//
// print column names
//
OutputString(pECB, "<TR>");

for ( i = 0; i < lFieldCount; i++ )
{
OutputString(pECB, "<TH>");

hr = piFields->get_Item(COleVariant(i), &piField);
if (FAILED(hr))goto errFreeStuff;
hr = piField->get_Name(&bstrName);
if (FAILED(hr))goto errFreeStuff;

OutputString(pECB, CString(bstrName));  //convert bstr to CString

SysFreeString(bstrName);
piField->Release();
piField = NULL;
}

//
// print data
//
hr = piRecordSet->get_EOF(&fEOF);
if (FAILED(hr))goto errFreeStuff;
while (!fEOF)
{
 OutputString(pECB, "<TR>");

for (i = 0; i < lFieldCount; i++)
{
 OutputString(pECB, "<TD>");

hr = piFields->get_Item(COleVariant(i), &piField);
if (FAILED(hr))goto errFreeStuff;
hr = piField->get_Value(&varFieldVal);
if (FAILED(hr))goto errFreeStuff;

if (FAILED(VariantChangeType(&varFieldVal, &varFieldVal, 0, VT_BSTR)))
OutputString(pECB, "N/A");
else
OutputString(pECB, CString(varFieldVal.bstrVal));

varFieldVal.Clear();
piField->Release();
piField = NULL;
}

 OutputString(pECB, "</TR>");

hr = piRecordSet->MoveNext();
if (FAILED(hr))goto errFreeStuff;

hr = piRecordSet->get_EOF(&fEOF);
if (FAILED(hr))goto errFreeStuff;
}

OutputString(pECB, "</TABLE>");
OutputString(pECB, "</BODY>\r\n");

errFreeStuff:
if (piField)
piField->Release();
if (piFields)
piFields->Release();
if (piRecordSet)
piRecordSet->Release();
CoUninitialize();

errNoUninit:
SysFreeString(bstrSource);
SysFreeString(bstrUser);
SysFreeString(bstrPassword);
SysFreeString(bstrPatchedQuery);

if (FAILED(hr))
{
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
OutputErrors(pECB, hr);
}
OutputString(pECB, "</BODY>\r\n");

return hr;
}


ADOJAVA.TXT

ADO Java Sample
===============
This sample demonstrates the use of ADO from Java and Java/COM integration. This sample can be executed as an applet in a browser or as a stand-alone application as well.

Setup
-----
1. Open the project file, adosample.mak, in Microsoft Visual J++.
2. Open the Java Type Library Wizard (Tools/Java Type Library Wizard). Select 'Microsoft OLE DB ActiveX Data Objects 1.0 Library' to generate the Java .CLASS files for the ADO classes and interfaces.
3. Build and execute the project as a browser (applet) or stand-alone application. For the Class to debug/execute, use simpleado. The sample can be run as a stand-alone application in two ways :
- In the VJ++ environment : In the Project settings dialog, select the Debug tab, select General in the Category list and for the Class for Debugging Session, enter simpleado. In Debug/Execute project under, select Stand-alone interpreter. Now execute this project under the VJ++ environment.
- Outside the VJ++ environment : After compiling the project, type
jview simpleado
in the directory where simpleado.class was generated on compilation.


ADOREAD.TXT

ADORead
=======
This C/C++ sample generates timings for fetching data using different cursor types and cache sizes.


ADORDDLG.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @moduleADOrddlg.h
//
// @devnote None
//--------------------------------------------------------------------
// ADOReadDlg.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CADOReadDlg dialog

class CADOReadDlg : public CDialog
{
// Construction
public:
CADOReadDlg(CWnd* pParent = NULL);// standard constructor
~CADOReadDlg();

//Display results of query
void CADOReadDlg::DisplayQueryTime(int iControlID, int iUnitsID, DWORD dwDuration);
void CADOReadDlg::AddFieldToList(LONG lRecordNum, COleVariant *pdbVar);
void CADOReadDlg::Execute(CursorTypeEnum cursorType, int iControlID, int iUnitsID);
// Dialog Data
//{{AFX_DATA(CADOReadDlg)
enum { IDD = IDD_DAOREAD_DIALOG };
CComboBoxm_comboBoxMode;
CComboBoxm_comboBoxOption;
longm_lCacheSize;
CStringm_strTableQuery;
longm_lNumRows;
BOOLm_bDynamic;
BOOLm_bKeyset;
BOOLm_bStatic;
BOOLm_bForwardOnly;
CStringm_strSource;
CStringm_strUser;
CStringm_strPassword;
CStringm_strProvider;
longm_lConnectOption;
longm_lConnectMode;
//BOOLm_bReadOnly;
//}}AFX_DATA

//CADODefaultWorkspace m_cDefaultWorkspace;
ADOConnection *m_piConnection;

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CADOReadDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:

HICON m_hIcon;
CComboBox m_ComboBoxOption;

// Generated message map functions
//{{AFX_MSG(CADOReadDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnDatabase();
afx_msg void OnExecute();
afx_msg void OnSelchangeConnectMode();
afx_msg void OnSelchangeConnectOption();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


ADOREAD.CPP

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @moduleADOread.cpp
//
// @devnote None
//--------------------------------------------------------------------
// ADORead.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "ADORead.h"
#include "ADORdDlg.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CADOReadApp

BEGIN_MESSAGE_MAP(CADOReadApp, CWinApp)
//{{AFX_MSG_MAP(CADOReadApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CADOReadApp construction

CADOReadApp::CADOReadApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CADOReadApp object

CADOReadApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CADOReadApp initialization

BOOL CADOReadApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

//Enable3dControls();
AfxEnableWin40Compatibility();

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

CADOReadDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
//  application, rather than start the application's message pump.
return FALSE;
}


ADOREAD.H

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @moduleADOread.h
//
// @devnote None
//--------------------------------------------------------------------
// ADORead.h : main header file for the ADOREAD application
//

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"// main symbols
#include <objbase.h>
#include <afxdisp.h>
#include <tchar.h>// Unicode
//#include <ado.h>// ADO C++ header
#include <adoint.h>// ADO C++ header
#include <adoid.h>// ADO C++ header

/////////////////////////////////////////////////////////////////////////////
// CADOReadApp:
// See ADORead.cpp for the implementation of this class
//

class CADOReadApp : public CWinApp
{
public:
CADOReadApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CADOReadApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CADOReadApp)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


ADORDDLG.CPP

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @moduleADOrddlg.cpp
//
// @devnote None
//--------------------------------------------------------------------

// ADOReadDlg.cpp : implementation file
//

#include "stdafx.h"
#include "ADORead.h"
#include "ADORdDlg.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

const LPCTSTR x_lpcszSource = _T("OLE_DB_NWind_Jet;");
const LPCTSTR x_lpcszUser = _T("Admin");

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// Implementation
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CADOReadDlg dialog

CADOReadDlg::CADOReadDlg(CWnd* pParent /*=NULL*/)
: CDialog(CADOReadDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CADOReadDlg)
m_lCacheSize = 100;
m_strTableQuery = _T("SELECT EmployeeId, LastName, FirstName FROM Employees");
m_lNumRows = 10;
m_bDynamic = TRUE;
m_bKeyset = TRUE;
m_bStatic = TRUE;
m_bForwardOnly = TRUE;
m_strSource = x_lpcszSource; //.. D:\\samples\\Employee.mdb
m_strProvider = _T("msdasql");
m_strUser = x_lpcszUser;
m_lConnectMode  = adModeRead;
m_lConnectOption  = adPromptAlways;
//m_bReadOnly = FALSE;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_comboBoxOption.Create(CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_GROUP | WS_TABSTOP| WS_CHILD | WS_VISIBLE,
CRect(261,7,94,12), this, IDC_ConnectOption);
m_piConnection = NULL;
}

CADOReadDlg::~CADOReadDlg()
{
if ( m_piConnection != NULL )
m_piConnection->Release();
m_piConnection = NULL;
}

void CADOReadDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CADOReadDlg)
DDX_Control(pDX, IDC_ConnectMode, m_comboBoxMode);
DDX_Control(pDX, IDC_ConnectOption, m_comboBoxOption);
DDX_Text(pDX, IDC_CACHESIZE, m_lCacheSize);
DDV_MinMaxLong(pDX, m_lCacheSize, 0, 1200);
DDX_Text(pDX, IDC_TABLEQUERYEDIT, m_strTableQuery);
DDX_Text(pDX, IDC_NUMROWS, m_lNumRows);
DDV_MinMaxLong(pDX, m_lNumRows, 1, 32000);
DDX_Check(pDX, IDC_Dynamic, m_bDynamic);
DDX_Check(pDX, IDC_Keyset, m_bKeyset);
DDX_Check(pDX, IDC_StaticCursor, m_bStatic);
DDX_Check(pDX, IDC_FORWARDONLY, m_bForwardOnly);
DDX_Text(pDX, IDC_Source, m_strSource);
DDV_MaxChars(pDX, m_strSource, 256);
DDX_Text(pDX, IDC_PROVIDER, m_strProvider);
DDV_MaxChars(pDX, m_strProvider, 256);
DDX_Text(pDX, IDC_User, m_strUser);
DDV_MaxChars(pDX, m_strUser, 256);
//}}AFX_DATA_MAP

//Do the manual checks

//Check the database name parameter
if(pDX->m_bSaveAndValidate)
{
pDX->PrepareEditCtrl(IDC_Source);
if (m_strSource.IsEmpty())
{
MessageBox(_T("Missing Jet database name."), _T("ADO Test App Error"), MB_ICONEXCLAMATION );
pDX->Fail();
}
}

//Check the Table/Query parameter
if(pDX->m_bSaveAndValidate)
{
pDX->PrepareEditCtrl(IDC_TABLEQUERYEDIT);
if (m_strTableQuery.IsEmpty())
{
MessageBox(_T("Missing table,query or SQL."), _T("ADO Test App Error"), MB_ICONEXCLAMATION );
pDX->Fail();
}
}
}

BEGIN_MESSAGE_MAP(CADOReadDlg, CDialog)
//{{AFX_MSG_MAP(CADOReadDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_DATABASEBUTTON, OnDatabase)
ON_BN_CLICKED(ID_EXECUTE, OnExecute)
ON_CBN_SELCHANGE(IDC_ConnectMode, OnSelchangeConnectMode)
ON_CBN_SELCHANGE(IDC_ConnectOption, OnSelchangeConnectOption)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CADOReadDlg message handlers

BOOL CADOReadDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}

//Set default selections
SetDlgItemText(IDC_CACHESIZE, _T("100"));
SetDlgItemText(IDC_NUMROWS, _T("10"));

return TRUE;  // return TRUE  unless you set the focus to a control
}

void CADOReadDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CADOReadDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CADOReadDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}

void CADOReadDlg::OnDatabase()
{
CFileDialog*pcBrowse;//Need a file open dialog

pcBrowse = (CFileDialog*)new CFileDialog(TRUE,_T("mdb"),NULL,NULL,_T("Jet Database Files | *.mdb ||"), NULL);

pcBrowse->DoModal();

(GetDlgItem(IDC_Source))->SetWindowText(pcBrowse->GetPathName());

delete pcBrowse;
}

void CADOReadDlg::Execute(
CursorTypeEnumcursorType,
intiControlID,
intiUnitsID
)
{
ADORecordset*piRecordSet;
DWORDdwStart, dwEnd;
DWORDdwDuration;
LONGlIndex;
COleVariantdbVar;
COleVariantvNull;
HRESULThr;
BSTRbstrTableQuery;
VARIANT_BOOLvbEOF;

dwStart = timeGetTime();

//Open recordset
hr = CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, IID_IADORecordset, (LPVOID *)&piRecordSet);
if (FAILED(hr))goto ErrorExit;

//Check that recordset was created
if (piRecordSet != NULL)
{
bstrTableQuery = m_strTableQuery.AllocSysString();
hr = piRecordSet->put_Source(bstrTableQuery);// convert to bstr
if (FAILED(hr))goto ErrorExit;

hr = piRecordSet->putref_ActiveConnection(m_piConnection);
if (FAILED(hr))goto ErrorExit;

//Set the cache size to value supplied by user.
hr = piRecordSet->put_CacheSize(m_lCacheSize);
if (FAILED(hr))goto ErrorExit;

vNull.vt = VT_ERROR;
vNull.scode = DISP_E_PARAMNOTFOUND;
hr = piRecordSet->Open(vNull, vNull, cursorType, adLockOptimistic, adCmdUnknown);
if (FAILED(hr))goto ErrorExit;

//Watch for end of record set
piRecordSet->get_EOF(&vbEOF);
if (FAILED(hr))goto ErrorExit;

for (lIndex = 0L; !vbEOF && lIndex < m_lNumRows; lIndex++)
{
//Get the first returned column
hr = piRecordSet->get_Collect(COleVariant(0L, VT_I4), &dbVar);
if (FAILED(hr))goto ErrorExit;

AddFieldToList(lIndex, &dbVar);
hr = piRecordSet->MoveNext();
if (FAILED(hr))goto ErrorExit;
piRecordSet->get_EOF(&vbEOF);
if (FAILED(hr))goto ErrorExit;
//dbVar.Clear();// release memory
}

//Report the estimated execute time (in milliseconds)
dwEnd = timeGetTime();
dwDuration = dwEnd - dwStart;
DisplayQueryTime(iControlID, iUnitsID, dwDuration);
}
else
{
(GetDlgItem(IDC_DynamicTIME))->SetWindowText(_T("ERROR"));
}

piRecordSet->Release();
SysFreeString(bstrTableQuery);
return ;

 ErrorExit:
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
AfxMessageBox(szBuf);

piRecordSet->Release();
SysFreeString(bstrTableQuery);
return ;
}

//When the Execute button is pressed, run through the selected methods and perform each
//reporting an approximate execution time and listing the first field returned (only if
//it's a string or numeric)

void CADOReadDlg::OnExecute()
{
CListBox*pList = ((CListBox *)(GetDlgItem(IDC_ROWSLIST)));
HRESULThr;
BSTRbstrSource;
BSTRbstrUser;
BSTRbstrProvider;
CStringstrSource, strUser,strPassword, strProvider;
static BOOLEANfIsFirstTime = TRUE;

//Update the parameters
if (!UpdateData(TRUE))
{
return;
}

// This could take a while,
CWaitCursor wait;

//Clear the list
pList->ResetContent();

// Following is a series of database table retrieves using variations
// on the Recordset object. This isn't a great programming structure,
// but it shows each method clearly.

//Execute each the requested read methods, recording a time for each
//Open the database
if ( m_piConnection == NULL )
{
hr = CoInitialize(NULL);
if (FAILED(hr))goto ErrorExit;
hr = CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID *)&m_piConnection);
if (FAILED(hr))goto ErrorExit;
}

(GetDlgItem(IDC_Source))->GetWindowText(strSource);
(GetDlgItem(IDC_User))->GetWindowText(strUser);
(GetDlgItem(IDC_Password))->GetWindowText(strPassword);
(GetDlgItem(IDC_PROVIDER))->GetWindowText(strProvider);

if ( fIsFirstTime || m_strSource != strSource  || m_strUser != strUser||
m_strPassword != strPassword || m_strProvider != strProvider )
{
fIsFirstTime = FALSE;
m_strSource = strSource ;
m_strUser = strUser;
m_strPassword = strPassword ;
m_strProvider = strProvider;

bstrSource = m_strSource.AllocSysString();
bstrUser = m_strUser.AllocSysString();
bstrProvider = m_strProvider.AllocSysString();
if ( bstrProvider == NULL || bstrSource == NULL || bstrUser == NULL)goto ErrorExit;

if (FAILED(hr = m_piConnection->put_Provider(bstrProvider)))
goto ErrorExit;

hr = m_piConnection->Open( bstrSource, bstrUser, NULL );  //toaddpassword
 if (FAILED(hr))goto ErrorExit;

SysFreeString(bstrProvider);
SysFreeString(bstrSource);
SysFreeString(bstrUser);
}

//Open recordset on as a Dynamic
if(m_bDynamic)
{
pList->AddString(_T("Dynamic Cursor"));
pList->AddString(_T("=========================="));
Execute( adOpenDynamic, IDC_DynamicTIME, IDC_UNITDynamic );
}
//Open recordset on as a Keyset
if(m_bKeyset)
{
pList->AddString(_T("Keyset Cursor"));
pList->AddString(_T("=========================="));
Execute( adOpenKeyset, IDC_KeysetTIME, IDC_UNITDYNA );
}
//Open recordset on as a Static
if(m_bStatic)
{
pList->AddString(_T("Static Cursor"));
pList->AddString(_T("=========================="));
Execute( adOpenStatic, IDC_StaticTIME, IDC_UNITSNAP );
}
//Open recordset on as ForwardOnly
if(m_bForwardOnly)
{
pList->AddString(_T("ForwardOnly Cursor"));
pList->AddString(_T("=========================="));
Execute( adOpenForwardOnly, IDC_FORWARDONLYTIME, IDC_UNITFORWARDONLY );
}

return ;

 ErrorExit:
TCHAR szBuf[256];
wsprintf(szBuf, _T("Error: %d \n"), hr);
AfxMessageBox(szBuf);

SysFreeString(bstrProvider);
SysFreeString(bstrSource);
SysFreeString(bstrUser);
return ;

}

/////////////////////////////////////////////////////////////////////////////
// Other functions

void CADOReadDlg::DisplayQueryTime(int iControlID, int iUnitsID, DWORD dwDuration)
{
CString strDisp;
CString strUnits(_T("ms"));
doublefDuration = dwDuration;

//Did it go into minutes?
if(fDuration >= 60000.0)
{
fDuration = fDuration/60000.0;
strUnits = _T("M");
}
//Did it go to seconds?
else if(fDuration >= 1000.0)
{
fDuration = fDuration/1000.0;
strUnits = _T("S");
}

strDisp.Format(_T("%.2f"), fDuration);

(GetDlgItem(iUnitsID))->SetWindowText(strUnits);
(GetDlgItem(iControlID))->SetWindowText(strDisp);

}

void CADOReadDlg::AddFieldToList(LONG lRecordNum, COleVariant *pdbVar)
{
CStringstrDisp;
CStringstrScratch;
CListBox*pList = ((CListBox *)(GetDlgItem(IDC_ROWSLIST)));

//Add record number
strDisp.Format(_T("%d - "), lRecordNum);

//NOTE: This only works for strings/numerics

switch(pdbVar->vt)
{
case VT_BSTR:
{
strDisp += (LPCTSTR)pdbVar->bstrVal;
break;
}

case VT_I4:
{
strScratch.Format(_T("%d"), pdbVar->lVal);
strDisp += strScratch;
break;
}

case VT_UI1:
{
strScratch.Format(_T("%u"), pdbVar->bVal);
strDisp += strScratch;
break;
}

case VT_I2:
{
strScratch.Format(_T("%d"), pdbVar->iVal);
strDisp += strScratch;
break;
}

case VT_R4:
{
strScratch.Format(_T("%f"), pdbVar->fltVal);
strDisp += strScratch;
break;
}

case VT_R8 :
{
strScratch.Format(_T("%f"), pdbVar->dblVal);
strDisp += strScratch;
break;
}

default: //
{
strDisp += _T("Field type not supported by sample code");
}

}
pList->AddString(strDisp);

}

ConnectPromptEnum rgConnectPrompts[] = {adPromptAlways,
adPromptComplete,
adPromptCompleteRequired,
adPromptNever};

void CADOReadDlg::OnSelchangeConnectOption()
{
m_lConnectOption = rgConnectPrompts[m_comboBoxOption.GetCurSel()];
}

ConnectModeEnum rgConnectModes[] = {adModeRead,
adModeWrite,
adModeReadWrite,
adModeShareDenyRead,
adModeShareDenyWrite,
adModeShareExclusive,
adModeShareDenyNone};

void CADOReadDlg::OnSelchangeConnectMode()
{

m_lConnectMode = rgConnectModes[m_comboBoxMode.GetCurSel()];
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by adoread.rc
//
#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDS_ABOUTBOX                    101
#define IDD_DAOREAD_DIALOG              102
#define IDR_MAINFRAME                   128
#define IDC_EDIT2                       1002
#define IDC_Source                      1002
#define IDC_DATABASEBUTTON              1003
#define IDC_User                        1004
#define IDC_TABLEQUERYEDIT              1005
#define IDC_Dynamic                     1007
#define IDC_PROVIDER                    1008
#define IDC_Keyset                      1009
#define IDC_EXCLUSIVE                   1011
#define IDC_READONLY                    1012
#define IDC_DynamicTIME                 1013
#define IDC_KeysetTIME                  1014
#define IDC_CACHETIME                   1015
#define IDC_StaticTIME                  1016
#define IDC_KeysetCACHE                 1017
#define IDC_CACHESIZE                   1018
#define IDC_FORWARDONLYTIME             1019
#define IDC_FORWARDONLY                 1020
#define IDC_NUMROWS                     1021
#define IDC_ROWSLIST                    1022
#define ID_EXECUTE                      1023
#define IDC_UNITDynamic                 1025
#define IDC_UNITDYNA                    1026
#define IDC_UNITCACHE                   1027
#define IDC_UNITFORWARDONLY             1028
#define IDC_UNITGETROWS                 1029
#define IDC_UNITSNAP                    1030
#define IDC_Password                    1035
#define IDC_ConnectMode                 1038
#define IDC_ConnectOption               1039
#define IDC_StaticCursor                1041

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        131
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1042
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

//--------------------------------------------------------------------
// Microsoft ADO Samples
//
// (c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc ADORead Sample
//
// @modulestdafx.cpp
//
// @devnote None
//--------------------------------------------------------------------
// stdafx.cpp : source file that includes just the standard includes
//ADORead.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"


ADOVBS.TXT

ADOVBS Sample
=============
This sample demonstrates the use of ADO from VBScript and HTML and how ADO, VBScript and HTML on the client can be used to build a 2-tier weblication for the Intranet. In this sample, some of the VBScript and HTML code is generated at run-time to in order to generate fields based on the schema information in the Recordset.


ACLEDIT.CPP

//--acledit.cpp----------------------------------------------------------------
//
// Folder ACLs editing utility.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

//
// Begin program documentation
//

#if0

Command line arguments: See Usage(), or invoke acledit.exe with no command
line arguments to get a usage note.

#endif

//
// End program documentation
//

#include "edk.h"
#include "errno.h"
#include "acledit.h"

//
// Enumeration, structure, and other type definitions
//

// ACLs editing commands:

enum EDITCMD// ec
{
EDITCMD_LIST,
EDITCMD_INSERT,
EDITCMD_DELETE,
EDITCMD_MODIFY,
EDITCMD_HELP,
EDITCMD_INVALID
};

structRIGHTSINFO// ri
{
LPSTRpszKeyWord;
ULONGcchKeyWord;
LONGlRights;
};

//
// Forward function declarations.
//

static
BOOL
bDoDelete(VOID);

static
BOOL
bDoInsert(VOID);

static
BOOL
bDoList(VOID);

static
BOOL
bDoModify(VOID);

static
BOOL
bParseCmdLine(
    IN  INT     argc,
    IN  CHAR *  argv[]
    );

static
BOOL
bParseRightsLst(VOID);

static
VOID
DoHelp(VOID);

static
VOID
EventLogHexErrorMsg(
INDWORDdwEvent,
INHRESULThr
);

static
VOID
PrintRightsString(
INLONGlRights
);

static
VOID
PrintErr(
INCHAR *Format
...
);

static
VOID
Usage(VOID);

//
// Static storage
//

// Rights information array.  This is layed out in a specific manner, with
// all single bit rights first.  The order should not be changed.  The
// manifest constant NONEINDX is used to limit lookup of keywords to the
// range of single bit values, since the other keywords are special cases.

staticRIGHTSINFOria[] =
{
"ReadAny", sizeof("ReadAny") - 1, frightsReadAny,
"Create", sizeof("Create") - 1, frightsCreate,
"EditOwned", sizeof("EditOwned") - 1, frightsEditOwned,
"DeleteOwned", sizeof("DeleteOwned") - 1, frightsDeleteOwned,
"EditAny", sizeof("EditAny") - 1, frightsEditAny,
"DeleteAny", sizeof("DeleteAny") - 1, frightsDeleteAny,
"CreateSubfolder", sizeof("CreateSubfolder") - 1, frightsCreateSubfolder,
"Owner", sizeof("Owner") - 1, frightsOwner,
"Contact", sizeof("Contact") - 1, frightsContact,
"None", sizeof("None") - 1, rightsNone,
"ReadOnly", sizeof("ReadOnly") - 1, rightsReadOnly,
"ReadWrite", sizeof("ReadWrite") - 1, rightsReadWrite,
"All", sizeof("All") - 1, rightsAll
};

#defineNONEINDX9

staticLPSTRpszProfile =NULL;
staticLPSTRpszStore =NULL;
staticLPSTRpszFolder =NULL;
staticLPSTRpszRights =NULL;
staticLPSTRpszUser =NULL;

staticBOOLfInboxArgFnd =FALSE;

staticEDITCMDEditCmd =EDITCMD_INVALID;

staticBOOLfPositionFnd =FALSE;
staticLONGlCursor =   0;

staticLONGlRights =0;

staticLPMAPISESSIONlpSession =NULL;

staticLPMDBlpStore =NULL;

staticULONGcbEIDStore  =   0;

staticLPENTRYIDlpEIDStore =NULL;

staticULONGcbEIDFolder =   0;

staticLPENTRYIDlpEIDFolder =NULL;

staticLPFOLDERACLSlpFolderACLs =NULL;

// Command line argument flags, and array used by
// _HrExpandCommandLineArgument():

staticCHARszProfileArg[] ="profile";

staticCHARszStoreArg[] ="store";
staticCHARszFolderArg[] ="folder";
staticCHARszInboxArg[] ="inbox";

staticCHARszListArg[] ="list";
staticCHARszInsertArg[] ="insert";
staticCHARszModifyArg[] ="modify";
staticCHARszDeleteArg[] ="delete";

staticCHARszPositionArg[] ="position";

staticCHARszRightsArg[] ="rights";

staticCHARszUserArg[] ="user";

staticCHARszHelpArg[] ="help";

staticCHARszQuestionArg[] ="?";

staticCHAR *rgpszArgArray[] ={
szProfileArg,
szStoreArg,
szFolderArg,
szInboxArg,
szListArg,
szInsertArg,
szModifyArg,
szDeleteArg,
szPositionArg,
szRightsArg,
szUserArg,
szHelpArg,
szQuestionArg
};

staticULONGcErrs =0;

//
// Functions
//

// $--bDoDelete----------------------------------------------------------------
//
// DESCRIPTION:Execute the DELETE command.
//
// INPUT:None.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//-----------------------------------------------------------------------------

BOOL
bDoDelete(VOID)  // RETURNS: BOOL
{
HRESULThr =NOERROR;
BOOLbrc =FALSE;
LONGlCurCursor =0;

    DEBUGPRIVATE("bDoDelete()\n");

    hr = lpFolderACLs->HrSeek(lCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFolderACLs->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor == ACL_PAST_END)
{
PrintErr("ACL at position END does not exist");
goto cleanup;
}

    hr = lpFolderACLs->HrDelete();

    if (FAILED(hr))
    {
PrintErr("Deletion failed. If the ACL specified is the default ACL or\n"
 "is the ACL for the current user on a public folder, "
 "it may not be deleted");
cErrs--;
EventLogHexErrorMsg(ACLEDIT_DELETE_FAILED, hr);
        goto cleanup;
    }

brc = TRUE;

cleanup:

return brc;
}

// $--bDoInsert----------------------------------------------------------------
//
// DESCRIPTION:Execute the INSERT command.
//
// INPUT:None.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//-----------------------------------------------------------------------------

BOOL
bDoInsert(VOID)  // RETURNS: BOOL
{
LPSPropValueaPropVals   =   NULL;
BOOLbrc =FALSE;
ULONGcbEIDUser =0;
HRESULThr =NOERROR;
ULONGi =0;
LONGlCurCursor =0;
LONGlNewRights =0;
LPADRBOOKlpAdrBook =NULL;
LPADRENTRYlpAdrEntry  =   NULL;
LPADRLISTlpAdrList =NULL;
LPENTRYIDlpEIDUser =NULL;
LPSTRlpszDisplayName =NULL;

    DEBUGPRIVATE("bDoInsert()\n");

    hr = lpFolderACLs->HrSeek(lCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFolderACLs->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

// Open the address book and get the user entryid.

hr = lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &lpAdrBook);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_OPENADDRESSBOOK_FAILED, hr);
goto cleanup;
}

hr = MAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID FAR *)&lpAdrList);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_ALLOCATION_FAILED, hr);
goto cleanup;
}

lpAdrEntry  =   lpAdrList->aEntries;

lpAdrList->cEntries = 1;

lpAdrEntry->ulReserved1 =0;
lpAdrEntry->cValues =1;
lpAdrEntry->rgPropVals =NULL;

hr = MAPIAllocateBuffer(sizeof(SPropValue), (LPVOID FAR *)&aPropVals);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_ALLOCATION_FAILED, hr);
goto cleanup;
}

lpAdrEntry->rgPropVals =aPropVals;

aPropVals[0].ulPropTag =PR_DISPLAY_NAME;
aPropVals[0].Value.lpszA =NULL;

hr = MAPIAllocateMore(strlen(pszUser) + 1,
  lpAdrEntry->rgPropVals,
  (LPVOID FAR *)&lpAdrEntry->rgPropVals[0].Value.lpszA);
if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_ALLOCATION_FAILED, hr);
goto cleanup;
}

strcpy(lpAdrEntry->rgPropVals[0].Value.lpszA, pszUser);

hr = lpAdrBook->ResolveName(0, 0, NULL, lpAdrList);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_RESOLVENAME_FAILED, hr);
goto cleanup;
}

// Pick up ptr values again, assuming there was reallocation.

lpAdrEntry  =   lpAdrList->aEntries;
aPropVals =lpAdrEntry->rgPropVals;

for (i = 0; i < lpAdrEntry->cValues; i++)
{
if (aPropVals[i].ulPropTag == PR_ENTRYID)
{
cbEIDUser = aPropVals[i].Value.bin.cb;
lpEIDUser = (LPENTRYID)aPropVals[i].Value.bin.lpb;

break;
}
}

for (i = 0; i < lpAdrEntry->cValues; i++)
{
if (aPropVals[i].ulPropTag == PR_DISPLAY_NAME)
{
lpszDisplayName = aPropVals[i].Value.lpszA;
break;
}
}

if (lpEIDUser == NULL || lpszDisplayName == NULL)
{
hr = MAPI_E_NOT_FOUND;
EventLogHexErrorMsg(ACLEDIT_RESOLVENAME_FAILED, hr);
goto cleanup;

}

// Finally do the insert.

hr = lpFolderACLs->HrInsert(lRights,
lpszDisplayName,
cbEIDUser,
lpEIDUser,
&lNewRights);
if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_INSERT_FAILED, hr);

if (hr == E_FAIL)
{
PrintErr("Insertion failed. "
     "If ACL for user already exists, use modify");
cErrs--;
}
else
{
PrintErr("Insertion failed");
cErrs--;
}

goto cleanup;
}

// If the rights obtained are not exactly what is expected (which due to
// certain Exchange vagaries may be the case), the modified rights
// will be printed out.

if (lRights != lNewRights)
{
printf("Rights actually set are '");

PrintRightsString(lNewRights);

printf("'\n\n");
}

brc = TRUE;

cleanup:

FREEPADRLIST(lpAdrList);
ULRELEASE(lpAdrBook);

return brc;
}

// $--bDoList------------------------------------------------------------------
//
// DESCRIPTION:Execute the LIST command.
//
// INPUT:None.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//---------------------------------------------------------------------------

BOOL
bDoList(VOID)    // RETURNS: BOOL
{
HRESULThr =NOERROR;
BOOLbrc =FALSE;
HANDLEhConsole =GetStdHandle(STD_OUTPUT_HANDLE);
LONGlCurCursor =0;

    DEBUGPRIVATE("bDoList()\n");

if (!fPositionFnd)
lCursor = 0;

    hr = lpFolderACLs->HrSeek(lCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// Check for empty table or invalid cursor position.

    hr = lpFolderACLs->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (pszStore == NULL)
pszStore = "default";

if (!fPositionFnd)
{
printf("Listing of ACL table in folder \\%s in %s store:\n\n",
    pszFolder, pszStore);
}
else if (lCurCursor != ACL_PAST_END)
{
printf("Listing of ACL table record %d in folder \\%s in %s store:\n\n",
   lCurCursor, pszFolder, pszStore);
}

if (lCurCursor == ACL_PAST_END)
{
if (fPositionFnd)
{
PrintErr("ACL at position %d does not exist", lCursor);
}
else
{
printf("ACL table is empty.\n");
brc = TRUE;
}
goto cleanup;
}

while (TRUE)
{
ULONGcbentryid;
LONGlRights;
    LPSTRlpszDisplayName =NULL;
    LPENTRYIDlpentryid =NULL;

hr = lpFolderACLs->HrGet(&lRights,
 &lpszDisplayName,
 &cbentryid,
 &lpentryid);
if (FAILED(hr))
{
HRESULThrSaved = hr;

    hr = lpFolderACLs->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor != ACL_PAST_END || hrSaved != E_FAIL)
{
EventLogHexErrorMsg(ACLEDIT_GET_FAILED, hrSaved);
        goto cleanup;
}
break;
}
else
{
// Print listing:

printf("For record at cursor position %d:\n\n", lCurCursor);

printf("    User Name is '%s'\n", lpszDisplayName);

printf("    Rights are '");

PrintRightsString(lRights);

printf("'\n\n");

lCurCursor++;
}

// Clean up allocations:

MAPIFREEBUFFER(lpszDisplayName);
MAPIFREEBUFFER(lpentryid);

// Only dump one record if cursor position was specified.

if (fPositionFnd)
break;
}

brc = TRUE;

cleanup:

return brc;
}

// $--bDoModify----------------------------------------------------------------
//
// DESCRIPTION:Execute the MODIFY command.
//
// INPUT:None.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//-----------------------------------------------------------------------------

BOOL
bDoModify(VOID)  // RETURNS: BOOL
{
HRESULThr =NOERROR;
BOOLbrc =FALSE;
LONGlCurCursor =0;
LONGlNewRights =0;

    DEBUGPRIVATE("bDoModify()\n");

    hr = lpFolderACLs->HrSeek(lCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFolderACLs->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
EventLogHexErrorMsg(ACLEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor == ACL_PAST_END)
{
PrintErr("ACL at position END does not exist");
goto cleanup;
}

    hr = lpFolderACLs->HrModify(lRights, &lNewRights);

    if (FAILED(hr))
    {
PrintErr("Modification failed");
cErrs--;
EventLogHexErrorMsg(ACLEDIT_MODIFY_FAILED, hr);
        goto cleanup;
    }

// If the rights obtained are not exactly what is expected (which due to
// certain Exchange vagaries may be the case), the modified rights
// will be printed out.  For instance the Owner right is always retained
// for the current user ACL on a public folder, and setting EditAny will
// also cause EditOwned to be set.

if (lRights != lNewRights)
{
printf("Rights actually set are '");

PrintRightsString(lNewRights);

printf("'\n\n");
}

brc = TRUE;

cleanup:

return brc;
}

// $--bParseCmdLine------------------------------------------------------------
//
// DESCRIPTION:    Parse the command line.
//
// INPUT:
//
//[argc]-- Count of arguments from command line.
//[argv]-- Array of arguments from command line.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//-----------------------------------------------------------------------------

BOOL
bParseCmdLine(// RETURNS: BOOL
    IN  INT     argc,           // argument count
    IN  CHAR *  argv[]          // array of arguments
    )
{
    BOOL        bRetVal =   FALSE;
HRESULThr =NOERROR;
CHAR *pszArgData =NULL;
CHAR *pszFlagName =NULL;

    DEBUGPRIVATE("bParseCmdLine()\n");

argc--;// Get rid of command argument.
argv++;

if (argc == 0)
    goto cleanup;

while (argc > 0)
{
hr =_HrExpandCommandLineArgument(
*argv,
rgpszArgArray,
sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]),
NULL,
&pszFlagName,
&pszArgData
);

if (FAILED(hr))
{
PrintErr("Invalid command line argument");
goto cleanup;
}

if (pszFlagName == szProfileArg)
{
if (pszProfile != NULL)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

pszProfile = pszArgData;
}
else if (pszFlagName == szStoreArg)
{
if (pszStore != NULL)
{
PrintErr("Duplicate command line arguments");
                goto cleanup;
}

pszStore = pszArgData;
}
else if (pszFlagName == szFolderArg)
{
if (pszFolder != NULL)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

pszFolder = pszArgData;
}
else if (pszFlagName == szInboxArg)
{
// Note that you can put "/inbox" on the cmd line as many times
// as you like without creating a problem! :-)

if (pszStore != NULL || pszFolder != NULL)
{
PrintErr("Conflicting command line arguments");
goto cleanup;
}

if (pszArgData != NULL)
{
PrintErr("The /%s argument does not take a value", szInboxArg);
goto cleanup;
}

fInboxArgFnd = TRUE;
}
else if (pszFlagName == szListArg)
{
if (EditCmd != EDITCMD_INVALID)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData != NULL)
{
PrintErr("The /%s argument does not take a value", szListArg);
goto cleanup;
}

EditCmd = EDITCMD_LIST;
}
else if (pszFlagName == szInsertArg)
{
if (EditCmd != EDITCMD_INVALID)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData != NULL)
{
PrintErr("The /%s argument does not take a value",szInsertArg);
goto cleanup;
}

EditCmd = EDITCMD_INSERT;
}
else if (pszFlagName == szModifyArg)
{
if (EditCmd != EDITCMD_INVALID)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData != NULL)
{
PrintErr("The /%s argument does not take a value",szInsertArg);
goto cleanup;
}

EditCmd = EDITCMD_MODIFY;
}
else if (pszFlagName == szDeleteArg)
{
if (EditCmd != EDITCMD_INVALID)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData != NULL)
{
PrintErr("The /%s argument does not take a value",szDeleteArg);
goto cleanup;
}

EditCmd = EDITCMD_DELETE;
}
else if (pszFlagName == szPositionArg)
{
LPSTRpszValue = pszArgData;

if (fPositionFnd)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszValue == NULL || *pszValue == '\0')
{
PrintErr("The /%s argument requires a value", szPositionArg);
goto cleanup;
}

if (!_stricmp(pszValue, "END"))
{
lCursor = RULE_PAST_END;
}
else
{
errno = 0;

lCursor = strtol(pszValue, (CHAR **)NULL, 10);

if ((lCursor == LONG_MAX || lCursor == LONG_MIN) &&
errno == ERANGE)
{
PrintErr("Command line argument '%s' is invalid", *argv);
goto cleanup;
}
}

fPositionFnd = TRUE;
}
else if (pszFlagName == szRightsArg)
{
if (pszRights != NULL)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

pszRights = pszArgData;

if (pszRights == NULL || *pszRights == '\0')
{
PrintErr("The /%s argument requires a value", szRightsArg);
goto cleanup;
}

if (!bParseRightsLst())
goto cleanup;
}
else if (pszFlagName == szUserArg)
{
if (pszUser != NULL)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

pszUser = pszArgData;

if (pszUser == NULL || *pszUser == '\0')
{
PrintErr("The /%s argument requires a value", szRightsArg);
goto cleanup;
}
}
else if (pszFlagName == szHelpArg||
 pszFlagName == szQuestionArg)
{
EditCmd = EDITCMD_HELP;

// If we encounter /help or /?, we pretty much ignore everything
// else and produce a help msg.

bRetVal = TRUE;
goto cleanup;
}
else
{
PrintErr("Command line argument '%s' is invalid", *argv);
goto cleanup;
}

argc--;
argv++;
}

// Check that all args needed have been provided.

if (pszProfile == NULL)
{
PrintErr("Command line must specify %s argument", szProfileArg);
goto cleanup;
}

if (!fInboxArgFnd &&
(pszStore == NULL || *pszStore == '\0'||
 pszFolder == NULL || *pszFolder == '\0'))
{
PrintErr("Command line must specify %s or %s and %s arguments",
 szInboxArg, szStoreArg, szFolderArg);

goto cleanup;
}

if (EditCmd == EDITCMD_INVALID)
{
PrintErr("Command line must specify a command argument");
goto cleanup;
}
else if (EditCmd != EDITCMD_LIST && EditCmd != EDITCMD_INSERT)
{
if (!fPositionFnd)
{
PrintErr("Command argument specified requires %s argument",
 szPositionArg);

goto cleanup;
}
}

if (EditCmd == EDITCMD_INSERT)
{
if (!fPositionFnd)
{
// Defaults to end of list.

fPositionFnd =TRUE;
lCursor =ACL_PAST_END;
}

if (pszRights == NULL)
{
PrintErr("Insert command requires %s argument", szRightsArg);
goto cleanup;
}
else if (pszUser == NULL)
{
PrintErr("Insert command requires %s argument", szUserArg);
goto cleanup;
}
}
else if (EditCmd == EDITCMD_MODIFY)
{
if (pszRights == NULL)
{
PrintErr("Modify command requires %s argument", szRightsArg);
goto cleanup;
}
}
else
{
if (pszRights != NULL)
{
PrintErr("A %s argument is only used with the insert or modify "
 "command", szRightsArg);

goto cleanup;
}
else if (pszUser != NULL)
{
PrintErr("A %s argument is only used with the insert command",
 szUserArg);

goto cleanup;
}
}

bRetVal = TRUE;

cleanup:

    return bRetVal;
}

// $--bParseRightsLst----------------------------------------------------------
//
// DESCRIPTION:Parse the rights list, storing the result in the global lRights.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
// Notes:The rights keywords are case insensitive, but otherwise must
//match exactly.  There is no checking for keyword redundancy or
//conflict.
//-----------------------------------------------------------------------------

BOOL
bParseRightsLst(VOID)
{
BOOLbrc =FALSE;
ULONGcch =0;
ULONGi =0;
CHAR *pchBegin =pszRights;
CHAR *pchEnd =NULL;

while (*pchBegin != '\0')
{
while (isspace(*pchBegin))
pchBegin++;

pchEnd = pchBegin;

do
{
pchEnd++;
}while (!isspace(*pchEnd) && *pchEnd != '\0');

cch = pchEnd - pchBegin;

brc = FALSE;

for (i = 0; i < sizeof(ria)/sizeof(ria[0]); i++)
{
if (cch == ria[i].cchKeyWord&&
!memicmp(pchBegin, ria[i].pszKeyWord, cch))
{
lRights |= ria[i].lRights;
brc = TRUE;
break;
}
}

if (!brc)
{
PrintErr("Invalid keyword '%.*s' in the rights list",
 cch, pchBegin);

goto cleanup;
}

pchBegin = pchEnd;
}

cleanup:

return brc;
}

// $--DoHelp------------------------------------------------------------------
//
// DESCRIPTION:Execute the HELP command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoHelp(VOID)  // RETURNS: VOID
{
printf(
"\nUSAGE: ACLEDIT /Profile=<ProfileName> <Folder> <Command>\n\n"
"<Folder>   ::= /Store=<NameOfStore> /Folder=<FolderPath>                     ||\n"
"               /Inbox\n\n"
"<Command>  ::= /List [/Position=<Pos>]                                       ||\n"
"               /Insert /Rights=<RightLst> /User=<UserName> [/Position=<Pos>] ||\n"
"               /Modify /Position=<Pos> /Rights=<RightLst>                    ||\n"
"               /Delete /Position=<Pos>                                       ||\n"
"<Position> ::= <ACL Number>                                                  ||\n"
"               END\n\n"
"<RightLst> ::= <Right>...\n"
"<Right>    ::= ReadAny                                                       ||\n"
"               Create                                                        ||\n"
"               EditOwned                                                     ||\n"
"               DeleteOwned                                                   ||\n"
"               EditAny                                                       ||\n"
"               DeleteAny                                                     ||\n"
"               CreateSubfolder                                               ||\n"
"               Owner                                                         ||\n"
"               Contact                                                       ||\n"
"               None                                                          ||\n"
"               ReadOnly                                                      ||\n"
"               ReadWrite                                                     ||\n"
"               All                                                           ||\n"
"<UserName> ::= Display name of user against which ACL should be applied\n"
);
}

//$--EventLogHexErrorMsg-------------------------------------------------------
//
// DESCRIPTION: Write a message to the event log that contains a hex error code.
//This function is used for simple error messages that have the
//HRESULT (or other 4 byte error code) as the only insert.
//
// INPUT:
//
//[dwEvent]-- Event code for message string.
//[hr]-- HRESULT that will be printed in hex as the insert in the
//   message string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
EventLogHexErrorMsg(
INDWORDdwEvent,// event code for message string
INHRESULThr// HRESULT insert in the message string
)
{
CHARszErrorCode[11];

sprintf(szErrorCode, "%#.8x", hr);

EventLogMsg(dwEvent, 1, szErrorCode, 0);
}

//$--PrintErr------------------------------------------------------------------
//
// DESCRIPTION:Print an error message to stdout in a standard format.
//
// INPUT:
//
//[Format]-- Printf-style format string.
//[...]-- Variable argument parameters for format string.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
PrintErr(        // RETURNS: VOID
INCHAR *Format      // error message format
...
)
{
va_listargs;

    DEBUGPRIVATE("PrintErr()\n");

cErrs++;

printf("ERROR: ");

va_start(args, Format);

vprintf(Format, args);

va_end(args);

printf(".\n\n");

    return;
}

// $--PrintRightsString--------------------------------------------------------
//
// DESCRIPTION:Print a readable representation of the rights.
//
// INPUT:
//
//[lRights]-- Rights to be printed.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

VOID
PrintRightsString(
INLONGlRights
)
{
BOOLbSpaceNeeded =FALSE;
ULONGi =0;

// We must plow through the special cases and then hit everything
// else.  We express 'ReadWrite' as 'ReadAny EditAny' since they are
// equivalent, and 'ReadOnly' as 'ReadAny' since they are equivalent.

if (lRights == rightsAll)
{
printf("All");
}
else if (lRights == rightsNone)
{
printf("None");
}
else
{
for (i = 0; i < NONEINDX; i++)
{
if (lRights & ria[i].lRights)
{
if (bSpaceNeeded)
printf(" %s", ria[i].pszKeyWord);
else
printf("%s", ria[i].pszKeyWord);

bSpaceNeeded = TRUE;
}
}
}
}

// $--Usage--------------------------------------------------------------------
//
// DESCRIPTION:Print a usage message to the console.
//
// INPUT:None.
//
// RETURNS:VOID
//-----------------------------------------------------------------------------

VOID
Usage(VOID)  // RETURNS: VOID
{
    DEBUGPRIVATE("Usage()\n");
printf(
"\nUSAGE: ACLEDIT /Profile=<ProfileName> <Folder> <Command>\n\n"
"Enter ACLEDIT /? for details.\n"
);
}

// $--main---------------------------------------------------------------------
//
// DESCRIPTION:    acledit.exe main() routine.
//
// INPUT:   Standard argc and argv.
//
// RETURNS:     NOERROR on success;
//              E_INVALIDARG if bad input,
//              E_OUTOFMEMORY if memory problems.
//              E_FAIL on any execution error.
//
//-----------------------------------------------------------------------------

HRESULT
main(               // RETURNS: HRESULT

IN  INT     argc,       // argument count
    IN  CHAR *  argv[]      // array of command line arguments
    )
{
HRESULThr =NOERROR;
BOOLbMAPIInitialized =FALSE;
BOOLbPrintCompletion =TRUE;
BOOLbEventLoggingEnabled =FALSE;

    DEBUGPUBLIC("main()\n");

// Initialize event logging.

hr = HrEventOpenLog("EDKAclEdit", NULL, NULL, NULL, NULL, NULL);

if (FAILED(hr))
{
PrintErr("Event logging could not be enabled; error code=%#x\n",hr);
goto cleanup;
}
else
{
bEventLoggingEnabled = TRUE;
}

    // NOTE:  Must initialize MAPI before parsing command line,
    // as the parsing of the command line requires MAPI memory
    // allocation.

if (argc == 1)
{
Usage();

bPrintCompletion = FALSE;

goto cleanup;
}

hr = MAPIInitialize(0);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_MAPIINITIALIZE_FAILED, hr);
goto cleanup;
}

bMAPIInitialized = TRUE;

if (!bParseCmdLine(argc, argv))
{
printf("       Enter ACLEDIT /? for help.\n");

hr = HR_LOG(E_INVALIDARG);

goto cleanup;
}

if (EditCmd == EDITCMD_HELP)
{
DoHelp();

hr = NOERROR;

bPrintCompletion = FALSE;

goto cleanup;
}

hr = MAPILogonEx(0,
     pszProfile,
     NULL,
     MAPI_EXTENDED | MAPI_NEW_SESSION,
     &lpSession);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_MAPILOGON_FAILED, hr);
        goto cleanup;
}

// Get the entry id of the specified store and open it.

if (fInboxArgFnd)
{
hr = HrMAPIFindDefaultMsgStore(lpSession, &cbEIDStore, &lpEIDStore);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_FINDDEFAULTMSGSTORE_FAILED, hr);
goto cleanup;
}

pszFolder = "Top of Information Store\\Inbox";
}
else
{
hr = HrMAPIFindStore(lpSession, pszStore, &cbEIDStore, &lpEIDStore);

if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_FINDSTORE_FAILED, hr);
goto cleanup;
}
}

hr = lpSession->OpenMsgStore(0,
 cbEIDStore,
 lpEIDStore,
 NULL,
 MAPI_BEST_ACCESS|
 MAPI_DEFERRED_ERRORS,
 &lpStore);
if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_OPENMSGSTORE_FAILED, hr);
goto cleanup;
}

// Get the entry id of the specified folder and open it.

if (fInboxArgFnd)
{
LPSTRlpszExplicitClass = "";

hr = lpStore->GetReceiveFolder(NULL,
   0,
   &cbEIDFolder,
   &lpEIDFolder,
   &lpszExplicitClass);
if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_GETRECEIVEFOLDER_FAILED, hr);
goto cleanup;
}

pszFolder = "Inbox";
}
else
{
hr = HrMAPIFindFolderEx(lpStore,
TEXT('\\'),
pszFolder,
&cbEIDFolder,
&lpEIDFolder);
if (FAILED(hr))
{
EventLogHexErrorMsg(ACLEDIT_FINDFOLDER_FAILED, hr);
goto cleanup;
}
}

// Get a folder ACL interface ptr for the specified folder.

    hr = HrFolderACLsOpen(lpSession, lpStore, cbEIDFolder, lpEIDFolder, &lpFolderACLs);

    if (FAILED(hr))
    {
if (hr == E_NOINTERFACE)
EventLogMsg(ACLEDIT_NOACLTABLE, 0, 0);
else
EventLogHexErrorMsg(ACLEDIT_FOLDERACLSOPEN_FAILED, hr);
        goto cleanup;
    }

switch (EditCmd)
{
case EDITCMD_LIST:
if (!bDoList())
{
hr = E_FAIL;
goto cleanup;
}
break;

case EDITCMD_INSERT:
if (!bDoInsert())
{
hr = E_FAIL;
goto cleanup;
}
break;

case EDITCMD_MODIFY:
if (!bDoModify())
{
hr = E_FAIL;
goto cleanup;
}
break;

case EDITCMD_DELETE:
if (!bDoDelete())
{
hr = E_FAIL;
goto cleanup;
}
break;
}

cleanup:

MAPIFREEBUFFER(lpEIDFolder);
MAPIFREEBUFFER(lpEIDStore);

ULRELEASE(lpFolderACLs);
ULRELEASE(lpStore);

if (lpSession)
{
lpSession->Logoff(0, 0, 0);
ULRELEASE(lpSession);
}

if (bMAPIInitialized)
(VOID)MAPIUninitialize();

if (bPrintCompletion)
{
EDKEVENTCOUNTEvents = {0,0,0};

HrEventGetCounts(&Events);

cErrs += Events.cError;

if (cErrs == 0)
{
printf("Execution completed with no errors.\n");
}
else
{
if (cErrs == 1)
{
printf("1 error encountered during execution.\n");
}
else
{
printf("%u errors encountered during execution.\n", cErrs);
}

if (Events.cError == 1)
{
printf("1 error written to NT Event Log.\n");
}
else if (Events.cError > 1)
{
printf("%u errors written to NT Event Log.\n", Events.cError);
}
}
}

if (bEventLoggingEnabled)
HrEventCloseLog();

    return _nEcFromHr(hr);
}


BACKTEST.C

// --backtest.c-----------------------------------------------------------------
//
//  Backup sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------
//kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib edbbcli.lib
#include "edk.h"
#include <edbback.h>
#include <edbbcli.h>
#include <time.h>
#include "backtest.h"

#define MIN_READ_HINT_SIZE 8193

DWORD
g_cbReadHintSize= 65536;

BOOL
g_fRandomReads= fFalse;

SZ
SzFromHr(
HRESULT hr
)
{
SZ szReturn = NULL;
HMODULE hmoduleEdb = NULL;

hmoduleEdb = LoadLibrary(TEXT("edbbcli.dll"));

if (hmoduleEdb == NULL)
{
return "";
}

if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
hmoduleEdb,
hr,
GetUserDefaultLangID(),
(CHAR *)&szReturn,
0,
NULL) == 0)
{
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
hr,
GetUserDefaultLangID(),
(CHAR *)&szReturn,
0,
NULL) == 0)
return "Unknown";
}

return szReturn;
}

unsigned long
msfrompc(
LARGE_INTEGER lipcDuration
)
{
LARGE_INTEGER lipcFrequency = {0};// Ticks/second.

if (!QueryPerformanceFrequency(&lipcFrequency)) {
return(0);
}

lipcFrequency.QuadPart /= 1000;// Convert to ticks/millisecond

return(unsigned long)(lipcDuration.QuadPart / lipcFrequency.QuadPart);
}

EC
EcBackupFile(
HBC hbcBackupContext,
SZ szFile,
HANDLE hBackupFile
)
{
LARGE_INTEGER liFileSize = {0};
LARGE_INTEGER liBytesRead = {0};
LARGE_INTEGER lipcStart = {0};
LARGE_INTEGER lipcEnd = {0};
LARGE_INTEGER lipcDuration = {0};
CB cbRead = 0;
HRESULT hrStatus = NOERROR;
BackupFile bf = {0};
CB cbWritten = 0;
BFT bft = 0;

VOID *pvBuffer = malloc(g_cbReadHintSize);

if (pvBuffer == NULL)
{
return(hrError);
}

printf("Back up %s\n", szFile);

liBytesRead.QuadPart = 0;

//
//Open the remote database.
//

if (!QueryPerformanceCounter(&lipcStart))
{
printf("Unable to query performance counter: %d\n", GetLastError());
}

//
//Open the remote database.
//

bft = *szFile++;

hrStatus = HrBackupOpenFile(hbcBackupContext, szFile,
g_cbReadHintSize, &liFileSize);

if (hrStatus != ERROR_SUCCESS) {
printf("Open failed:%s %d\n", szFile, hrStatus);
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return hrStatus;
}

bf.ulFileSignature = FILE_SIGNATURE;
bf.liFileSize = liFileSize;
bf.cbFileNameLength = strlen(szFile)+1;
bf.bft = bft;

if (hBackupFile)
{
if (!WriteFile(hBackupFile, &bf, sizeof(bf), &cbWritten, NULL))
{
hrStatus = GetLastError();
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != sizeof(bf))
{
hrStatus = ERROR_HANDLE_DISK_FULL;
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}

if (!WriteFile(hBackupFile, szFile, bf.cbFileNameLength, &cbWritten, NULL))
{
hrStatus = GetLastError();
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != bf.cbFileNameLength)
{
hrStatus = ERROR_HANDLE_DISK_FULL;
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}
}

if (!QueryPerformanceCounter(&lipcEnd))
{
printf("Unable to query performance counter: %d\n", GetLastError());
}

lipcDuration.QuadPart = lipcEnd.QuadPart - lipcStart.QuadPart;

printf("EcBackupOpenFile took %d milliseconds\n", msfrompc(lipcDuration));

if (!QueryPerformanceCounter(&lipcStart))
{
printf("Unable to query performance counter: %d\n", GetLastError());
}

while ((hrStatus == ERROR_SUCCESS) &&
   (liBytesRead.QuadPart < liFileSize.QuadPart)) {
CB cbToRead = g_cbReadHintSize;

if (g_fRandomReads)
{
cbToRead = rand() % g_cbReadHintSize;
            if (cbToRead < MIN_READ_HINT_SIZE)
            {
                cbToRead = MIN_READ_HINT_SIZE;
            }
}

//
//  Read the next chunk from the database.
//
hrStatus = HrBackupRead(hbcBackupContext,
pvBuffer,
cbToRead,
&cbRead);

if (hrStatus == ERROR_SUCCESS) {
liBytesRead.QuadPart += cbRead;
        }
else
{
printf("Read failed:%s %d\n", szFile, hrStatus);
break;
}

if (hBackupFile)
{
if (!WriteFile(hBackupFile, pvBuffer, cbRead, &cbWritten, NULL))
{
hrStatus = GetLastError();
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != cbRead)
{
hrStatus = ERROR_HANDLE_DISK_FULL;
free(pvBuffer);
HrBackupClose(hbcBackupContext);
return(hrStatus);
}
}
}

if (!QueryPerformanceCounter(&lipcEnd))
{
printf("Unable to query performance counter: %d\n", GetLastError());
}

lipcDuration.QuadPart = lipcEnd.QuadPart - lipcStart.QuadPart;
if (hrStatus == ERROR_HANDLE_EOF) {
hrStatus = ERROR_SUCCESS;
}

if (hrStatus == ERROR_SUCCESS) {
printf("Read %I64d bytes from the file, %3.3fs %f kb/second.\n", liBytesRead.QuadPart, ((double)msfrompc(lipcDuration) / 1000.0), (msfrompc(lipcDuration) ? ((double)liBytesRead.QuadPart / (double)(msfrompc(lipcDuration))) : 0));
}

hrStatus = HrBackupClose(hbcBackupContext);

if (hrStatus != hrNone)
{
printf("Close failed:%s %d\n", szFile, hrStatus);
}

free(pvBuffer);
return(hrStatus);
}

EC
EcDoBackup(
BOOL fIncremental,
SZ szBackupServer,
SZ szBackupAnnotation,
SZ szBackupFile
)
{
HBC hbcBackupContext = NULL;
SZ szAttachmentInformation = NULL;
CB cbSize = 0;
EC hrStatus =  0;
SZ szAttachment = 0;
SZ szBackupLogFile = NULL;
SZ szBackupLogName = NULL;
HANDLE hBackupFile = NULL;
BackupHeader bh = {0};
LogHeader lh = {0};
C cAttachment = 0;
C cLogs = 0;
CB cbWritten = 0;

//
//Contact the component on the server to be backed up - the
//  szBackupAnnotation field indicates which component on the
//  server is to be contacted.
//

if (fIncremental)
{
hrStatus = HrBackupPrepare(szBackupServer,
szBackupAnnotation,
1,
BACKUP_TYPE_LOGS_ONLY,
&hbcBackupContext);
}
else
{
hrStatus = HrBackupPrepare(szBackupServer,
szBackupAnnotation,
0,
BACKUP_TYPE_FULL,
&hbcBackupContext);
}

if (hrStatus != ERROR_SUCCESS)
{
return hrStatus;
    }

if (szBackupFile)
{
//
//Open up the backup file for write access.
//
hBackupFile = CreateFile(szBackupFile,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
);
if (hBackupFile == INVALID_HANDLE_VALUE)
{
return(GetLastError());
}
}

cAttachment = 0;
if (!fIncremental)
{
//
//Get the list of attached databases to perform the backup
//
hrStatus = HrBackupGetDatabaseNames(hbcBackupContext,
&szAttachmentInformation,
&cbSize);
if (hrStatus != ERROR_SUCCESS) {
HrBackupEnd(hbcBackupContext);
if (hBackupFile)
{
CloseHandle(hBackupFile);
}
return hrStatus;
}

szAttachment = szAttachmentInformation;

while (*szAttachment != '\0')
{
cAttachment  += 1;
szAttachment += strlen(szAttachment)+1;
}
}

if (hBackupFile)
{
//
//Write the header for the backup into the file.
//
bh.ulSignature = BACKUP_SIGNATURE;
bh.ulVersion = BACKUP_VERSION;
bh.cbDatabaseNameLength = strlen(szBackupAnnotation)+1;
bh.cBackupFiles = cAttachment;

if (!WriteFile(hBackupFile, &bh, sizeof(bh), &cbWritten, NULL))
{
hrStatus = GetLastError();
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != sizeof(bh))
{
hrStatus = ERROR_HANDLE_DISK_FULL;
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}

if (!WriteFile(hBackupFile, szBackupAnnotation, bh.cbDatabaseNameLength, &cbWritten, NULL))
{
hrStatus = GetLastError();
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != bh.cbDatabaseNameLength)
{
hrStatus = ERROR_HANDLE_DISK_FULL;
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}
}

if (!fIncremental)
{
szAttachment = szAttachmentInformation;
while (*szAttachment != '\0')
{
hrStatus = EcBackupFile(hbcBackupContext, szAttachment, hBackupFile);

if (hrStatus != hrNone)
{
BackupFree(szAttachmentInformation);
HrBackupEnd(hbcBackupContext);
if (hBackupFile)
{
CloseHandle(hBackupFile);
}
return(hrStatus);
}
szAttachment += strlen(szAttachment)+1;
}

BackupFree(szAttachmentInformation);
}

//
//We've successfully backed up the system, now clear
    //  out the logs by truncating them.
//

hrStatus = HrBackupGetBackupLogs(hbcBackupContext,
&szBackupLogFile,
&cbSize);

if (hrStatus != ERROR_SUCCESS) {
HrBackupEnd(hbcBackupContext);
if (hBackupFile)
{
CloseHandle(hBackupFile);
}
return hrStatus;
}

cLogs = 0;

szBackupLogName = szBackupLogFile;

while (*szBackupLogName != '\0')
{
cLogs += 1;
szBackupLogName += strlen(szBackupLogName)+1;
}

if (hBackupFile)
{
//
//Write the header for the log portion of the backup into the file.
//

lh.ulLogSignature = LOG_SIGNATURE;
lh.cLogFiles = cLogs;

if (!WriteFile(hBackupFile, &lh, sizeof(lh), &cbWritten, NULL))
{
hrStatus = GetLastError();
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}

if (cbWritten != sizeof(lh))
{
hrStatus = ERROR_HANDLE_DISK_FULL;
HrBackupEnd(hbcBackupContext);
return(hrStatus);
}
}

szBackupLogName = szBackupLogFile;

while (*szBackupLogName != '\0') {
hrStatus = EcBackupFile(hbcBackupContext, szBackupLogName, hBackupFile);
if (hrStatus != hrNone)
{
BackupFree(szBackupLogFile);
HrBackupEnd(hbcBackupContext);
if (hBackupFile)
{
CloseHandle(hBackupFile);
}
return(hrStatus);
}
szBackupLogName += strlen(szBackupLogName)+1;
}

BackupFree(szBackupLogFile);

printf("All logs and files backed up.\n");

if (!fIncremental)
{
hrStatus = HrBackupTruncateLogs(hbcBackupContext);

if (hrStatus != hrNone)
{
printf("Unable to truncate log: %d (%x).\n", hrStatus, hrStatus);
if (hBackupFile)
{
CloseHandle(hBackupFile);
}

return(hrStatus);
}

}

hrStatus = HrBackupEnd(hbcBackupContext);

if (hBackupFile)
{
CloseHandle(hBackupFile);
}

return(hrStatus);
}

void
usage(void)
{
    printf("\nUsage: BackTest\t -S:<Server>"
        "\n\t\t -B:<Backup File>"
        "\n\t\t -T:<MDB|DSA>"
        "\n\t\t[-R:<Read Buffer Size>]"
        "\n\t\t[-R:R]  (generate random buffer sizes on successive reads)"
"\n\t\t[-I]    (incremental backup)\n" );
}

int
_cdecl
main (int cArg, char *rgszArgv[])
{
EC ecResult = 0;
I irgsz = 0;
BOOL fDsa = fFalse;
BOOL fMdb = fFalse;
SZ szServerName = NULL;
SZ szBackupType = NULL;
SZ szBackupFile = NULL;
char rgComputerName[ MAX_COMPUTERNAME_LENGTH ] = {0};
ERR err = 0;
BOOL fIncremental = fFalse;

    if (cArg <= 1)
    {
        usage();
        exit(1);
    }

for (irgsz = 1; irgsz < cArg ; irgsz += 1)
{
if (rgszArgv[irgsz][0] == '-' || rgszArgv[irgsz][0]=='/' )
{
if (toupper(rgszArgv[irgsz][1]) == 'T')
{
szBackupType = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
}
else if (toupper(rgszArgv[irgsz][1]) == 'S')
{
szServerName = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
}
else if (toupper(rgszArgv[irgsz][1]) == 'B')
{
szBackupFile = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
}
else if (toupper(rgszArgv[irgsz][1]) == 'R')
{
SZ szReadHint = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
if (isdigit(*szReadHint))
{
g_cbReadHintSize = atoi(szReadHint);
                    if (g_cbReadHintSize < MIN_READ_HINT_SIZE)
                    {
                        g_cbReadHintSize = MIN_READ_HINT_SIZE;
                    }
}
else
{
switch (toupper(*szReadHint))
{
case 'R':
case 'r':
g_fRandomReads = fTrue;
srand(time(NULL));
break;
default:
usage();
exit(1);
}
}
}
else if (toupper(rgszArgv[irgsz][1]) == 'I')
{
fIncremental = fTrue;
}
else
{
usage();
exit(1);
}
}
}

if (szServerName == NULL)
{
CB cbComputerName = sizeof(rgComputerName);
if (!GetComputerName(rgComputerName, &cbComputerName)) {
printf("Unable to query local computer name %d", GetLastError());
return(GetLastError());
}
szServerName = rgComputerName;
}

if (szBackupType == NULL)
{
usage();
printf("Must specify a backup type\n");
return(hrError);
}
else if (_stricmp(szBackupType, "MDB") == 0)
{
fMdb = fTrue;
}
else if (_stricmp(szBackupType, "DSA") == 0)
{
fDsa = fTrue;
}

if (!fMdb && !fDsa)
{
usage();
printf("Must specify either MDB or DSA as the backup type");
return(hrError);
}

ecResult = EcDoBackup(fIncremental, szServerName, ( fMdb ? EDBBACK_MDB_SERVER : (fDsa ? EDBBACK_DS_SERVER : NULL)), szBackupFile);

printf("ecResult: %s (%d/0x%x)\n", SzFromHr(ecResult), ecResult, ecResult);

return(ecResult);
}


BACKTEST.H

// --backtest.c-----------------------------------------------------------------
//
//  Backup sample header file.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef_BACKTEST_
#define_BACKTEST_

#defineDEF_BACKUP_FILE"EXCHANGE.BAK"

#defineBACKUP_SIGNATURE(0xFF + ('B' << 8) + ('A' << 16) + ('K' << 24))
#defineLOG_SIGNATURE(0xFF + ('L' << 8) + ('O' << 16) + ('G' << 24))
#defineFILE_SIGNATURE(0xFF + ('F' << 8) + ('I' << 16) + ('L' << 24))

#defineBACKUP_VERSION0x00000001

#define fFalse  FALSE
#define fTrue   TRUE

/* Count, index types */
typedef LONGC;
typedef LONGI;

/* Other Hungarian */
typedef char *SZ;
typedef WCHAR *WSZ;
typedef longEC;
typedef void *PV;
typedef CCB;
typedef IIB;
typedef BYTE *PB;
typedef CCCH;
typedefchar *PCH;

typedef struct tagBackupHeader {
ULONGulSignature;
ULONGulVersion;
CcBackupFiles;
CBcbDatabaseNameLength;
//charrgDatabaseName[cbDatabaseNameLength]
} BackupHeader, *PBackupHeader;

typedef struct tagLogHeader {
ULONGulLogSignature;
CcLogFiles;
} LogHeader, *PLogHeader;

typedef struct tagBackupFile {
ULONGulFileSignature;
LARGE_INTEGERliFileSize;
BFTbft;
CBcbFileNameLength;// Includes null.
//charrgFileName[cbFileNameLength];
//charrgFileData[liFileSize];
} BackupFile, *PBackupFile;

#endif// _BACKTEST_


RESTTEST.CPP

// --resttest.cpp---------------------------------------------------------------
//
//  Backup restore sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include <edbback.h>
#include <edbbcli.h>
#include <time.h>
#include "backtest.h"

DWORD
cbReadSize = 0x10000;

CHAR
rgchComputerName[ MAX_COMPUTERNAME_LENGTH ];

ERR
ErrRestoreFile(
HANDLE hBackup,
EDB_RSTMAP *rgrstmap,
I *irgRestMap,
SZ szDatabaseLocationList,
SZ *pszBackupLogDirectory = NULL,
    PBOOL pfSetGen = NULL,
PDWORD gen = NULL
);

ERR
ErrReadStructure(
HANDLE hFile,
VOID *pvStruct,
CB cbStruct
)
{
CB cbRead = 0;

if (!ReadFile(hFile, pvStruct, cbStruct, (PDWORD)&cbRead, NULL))
{
printf("Unable to read log headers from backup file %d\n", GetLastError());
return(GetLastError());
}

if (cbRead != cbStruct)
{
printf("Unable to read all of log header from backup file\n");
return(1);
}
return(ERROR_SUCCESS);
}

ERR
ErrReadHeader(
HANDLE hFile,
PBackupHeader bh
)
{
ERR err = 0;
if (err = ErrReadStructure(hFile, bh, sizeof(BackupHeader)))
{
printf("Unable to read log headers from backup file: %d\n", err);
return(err);
}

if (bh->ulSignature != BACKUP_SIGNATURE)
{
printf("Log signature does not match, backup is probably corrupt\n");
return(1);
}
return(ERROR_SUCCESS);
}

ERR
ErrReadHeader(
HANDLE hFile,
PBackupFile bf
)
{
ERR err = 0;
if (err = ErrReadStructure(hFile, bf, sizeof(BackupFile)))
{
printf("Unable to read log headers from backup file: %d\n", err);
return(err);
}

if (bf->ulFileSignature != FILE_SIGNATURE)
{
printf("Log signature does not match, backup is probably corrupt\n");
return(1);
}
return(ERROR_SUCCESS);
}

ERR
ErrReadHeader(
HANDLE hFile,
PLogHeader lh
)
{
ERR err = 0;
if (err = ErrReadStructure(hFile, lh, sizeof(LogHeader)))
{
printf("Unable to read log headers from backup file: %d\n", err);
return(err);
}

if (lh->ulLogSignature != LOG_SIGNATURE)
{
printf("Log signature does not match, backup is probably corrupt\n");
return(1);
}
return(ERROR_SUCCESS);
}

SZ
SzFindBFT(
SZ szDatabaseLocationList,
BFT bft
)
{
SZszDatabaseLocation = szDatabaseLocationList;

while (*szDatabaseLocation)
{
BFT bftLocation = *szDatabaseLocation++;

if (bftLocation == bft)
{
return szDatabaseLocation;
break;
}

szDatabaseLocation += strlen(szDatabaseLocation)+1;
}

return NULL;
}

ERR
ErrRestoreFile(
HANDLE hBackup,
EDB_RSTMAP *prstmap,
I *pirgrstmap,
SZ szDatabaseLocationList,
SZ *pszBackupLogDirectory,
    PBOOL pfSetGen,
PDWORD gen
)
{
ERR err = 0;
BackupFile bf = {0};
SZ szNewFileName = NULL;
HANDLE hRestoreFile = NULL;
CB cbRead = 0;
BFT bftFile = 0;
SZ szFileName = NULL;
PVOID pvBuffer = NULL;
char rgchDrive[_MAX_DRIVE] = {0};
char rgchDir[_MAX_DIR] = {0};
char rgchFileName[_MAX_FNAME] = {0};
char rgchExtension[_MAX_EXT] = {0};
LARGE_INTEGER liBytesRemaining = {0};
DWORD cbToRead = 0;

//
//Read the backup header for the file.
//

    if (pfSetGen)
        *pfSetGen = fFalse;

err = ErrReadHeader(hBackup, &bf);

szFileName = new char[bf.cbFileNameLength];

if (szFileName == NULL)
{
printf("Error allocating space for database name\n");
err = ERROR_NOT_ENOUGH_MEMORY;
        goto cleanup;
}

if (!ReadFile(hBackup, szFileName, bf.cbFileNameLength, (PDWORD)&cbRead, NULL))
{
printf("Unable to read file name from backup file: %d\n", GetLastError());
err = GetLastError();
        goto cleanup;
}

//
//Save away the backup file type of this file.
//

bftFile = bf.bft;

//
//Crack the incoming database name to determine the file name.
//

_splitpath(szFileName, rgchDrive, rgchDir, rgchFileName, rgchExtension);

if (gen != NULL)
{
//
//We want to find out the generation of this file if it's a log file.
//

if (strnicmp(rgchFileName, "EDB", 3) == 0)
{
if (stricmp(rgchExtension, ".LOG") == 0)
{
//
//log file generations are hex digits.
//

*gen = strtoul(&rgchFileName[3], NULL, 16);
                if (pfSetGen)
                    *pfSetGen = fTrue;

//
//If we want to find out the log directory, return it.
//

if (pszBackupLogDirectory != NULL)
{
if (rgchDir[strlen(rgchDir)-1] == '\\')
{
rgchDir[strlen(rgchDir)-1] = '\0';
}

//
//Save away the backup log directory if needed.
//

*pszBackupLogDirectory = strdup(rgchDir);
}

}
}
}

//
//Ok, now find out where we are goingto put this file.
//

//
//First scan for an exact match on BFT.
//

szNewFileName = SzFindBFT(szDatabaseLocationList, bftFile);

if (szNewFileName == NULL)
{
//
//Ok, we didn't find it exactly, let's try to find it's directory.
//

if (bftFile & BFT_LOG_DIRECTORY)
{
SZ szLogDir = NULL;

//
//This guy goes into the log directory.  Find out where that is.
//

szLogDir = SzFindBFT(szDatabaseLocationList, BFT_LOG_DIR);

if (szLogDir == NULL)
{
printf("Could not find log directory");
err = ERROR_FILE_NOT_FOUND;
                goto cleanup;
}

szNewFileName = new char[strlen(szLogDir)+1+strlen(rgchFileName)+strlen(rgchExtension)+2];
strcpy(szNewFileName, szLogDir);
strcat(szNewFileName, "\\");
strcat(szNewFileName, rgchFileName);
strcat(szNewFileName, rgchExtension);
}
else if (bftFile & BFT_DATABASE_DIRECTORY)
{
printf("Don't support inexact match in datbase directory yet");
err = ERROR_FILE_NOT_FOUND;
}
}

//
//If this goes into the database directory, it needs to go into the RSTMAP.
//

if (bftFile & BFT_DATABASE_DIRECTORY)
{
prstmap[*pirgrstmap].szDatabaseName = szFileName;
prstmap[*pirgrstmap].szNewDatabaseName = strdup(szNewFileName);
*pirgrstmap+=1;
}

//
//If the munged name in the database is on our current machine, then unmunge the name for efficency.
//

if (szNewFileName[0] == '\\' && szNewFileName[1] == '\\')
{
//
//Ok, this is a munged file name.
//
SZ szBackslash = strchr(&szNewFileName[2], '\\');

if (szBackslash != NULL)
{
if (_strnicmp(&szNewFileName[2], rgchComputerName, szBackslash-&szNewFileName[2]) == 0)
{
//
//Ok, the current computer name matches the munged name, we want to munge the
//  destination file specified to match the local name.
//

szNewFileName = _strdup(szBackslash+1);

if (szNewFileName[1] == '$')
{
szNewFileName[1] = ':';
}
}
}
}

hRestoreFile = CreateFile(szNewFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);

if (hRestoreFile == INVALID_HANDLE_VALUE)
{
err = GetLastError();

if (ERROR_SHARING_VIOLATION==err)
{
printf("Unable to open %s. Make sure the Store service has been "
"stopped before restoring.\n", szNewFileName, GetLastError());
}
else
{
printf("Unable to open file %s for restore: %d\n", szNewFileName, err);
}
goto cleanup;
}

liBytesRemaining = bf.liFileSize;

pvBuffer = (void *)new char[cbReadSize];

if (pvBuffer == NULL)
{
printf("Unable to allocate buffer for file restore.\n");
err = 1;
        goto cleanup;
}

cbToRead = cbReadSize;

while (liBytesRemaining.QuadPart != 0)
{
DWORD cbRead = 0;
DWORD cbWritten = 0;

if (liBytesRemaining.QuadPart < cbReadSize)
{
cbToRead = liBytesRemaining.LowPart;
}

if (!ReadFile(hBackup, pvBuffer, cbToRead, &cbRead, NULL))
{
printf("Unable to read from restore file: %d\n", GetLastError());
err = GetLastError();
            goto cleanup;
}

if (cbRead != cbToRead)
{
printf("Unable to read from restore file: %d\n", GetLastError());
err = GetLastError();
            goto cleanup;
}

if (!WriteFile(hRestoreFile, pvBuffer, cbRead, &cbWritten, NULL))
{
printf("Unable to write to restored file: %d\n", GetLastError());
err = GetLastError();
            goto cleanup;
}

if (cbRead != cbWritten)
{
printf("Unable to write to restored file: %d\n", GetLastError());
err = GetLastError();
            goto cleanup;
}

liBytesRemaining.QuadPart -= cbRead;
}

CloseHandle(hRestoreFile);

err = ERROR_SUCCESS;

cleanup:
    if (pvBuffer)
    {
    delete pvBuffer;
    }

    return(err);
}

int
_cdecl
main (int cArg, char *rgszArgv[])
{
I irgsz = 0;
BOOL fDsa = fFalse;
BOOL fMdb = fFalse;

SZ szBackupFile = DEF_BACKUP_FILE;
SZ szBackupServer = NULL;
HANDLE hBackupFile = NULL;
DWORD cbComputerName = sizeof(rgchComputerName);
SZ szDatabaseLocationList = NULL;
DWORD cbLocationList = 0;

    BOOL fInvalidArg = fFalse;

if (!GetComputerName(rgchComputerName, &cbComputerName))
{
printf("Unable to query computer name: %d\n", GetLastError());
return(GetLastError());
}

for (irgsz = 1; irgsz < cArg ; irgsz += 1)
{
if (rgszArgv[irgsz][0] == '-' || rgszArgv[irgsz][0]=='/' )
{
if (toupper(rgszArgv[irgsz][1]) == 'B')
{
szBackupFile = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
}
else if (toupper(rgszArgv[irgsz][1]) == 'S')
{
szBackupServer = (rgszArgv[irgsz][2] == ':' ? &rgszArgv[irgsz][3] : rgszArgv[++irgsz]);
}
else
{
                fInvalidArg = fTrue;
                break;
}
}
}

    if (fInvalidArg || cArg <= 1)
{
printf("\nUsage: RestTest\t -S:<Server>\n\t\t -B:<Backup File>\n");
        return(0);
}

if (szBackupServer == NULL)
{
szBackupServer = rgchComputerName;
}

//
//Open up the backup file for write access.
//

hBackupFile = CreateFile(szBackupFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
);
if (hBackupFile == INVALID_HANDLE_VALUE)
{
printf("Unable to open backup file %s: %d\n", szBackupFile, GetLastError());
return(GetLastError());
}

BackupHeader bh = {0};
CB cbRead = 0;

ERR err = 0;

err = ErrReadHeader(hBackupFile, &bh);

if (err != ERROR_SUCCESS)
{
return(err);
}

SZ szRestoreAnnotation = NULL;
szRestoreAnnotation = new char[bh.cbDatabaseNameLength];

if (szRestoreAnnotation == NULL)
{
printf("Out of memory error\n");
return(1);
}
if (!ReadFile(hBackupFile, szRestoreAnnotation, bh.cbDatabaseNameLength, (PDWORD)&cbRead, NULL))
{
printf("Unable to read from backup file %s: %d\n", szBackupFile, GetLastError());
return(GetLastError());
}

if (cbRead != bh.cbDatabaseNameLength)
{
printf("Unable to read all of database name from backup file %s\n", szBackupFile);
return(GetLastError());
}

if (strlen(szRestoreAnnotation) != (ULONG)bh.cbDatabaseNameLength-1)
{
printf("Database name %s corrupted in backup file %s\n", szRestoreAnnotation, szBackupFile);
return(GetLastError());
}

printf("Now performing component specific restoration\n");

HBC hbc = NULL;

    err = HrRestorePrepare(szBackupServer, szRestoreAnnotation, &hbc);

if (err != ERROR_SUCCESS)
{
printf("Unable to connect to restore provider on server %s\n", szBackupServer);
return(err);
}

err = HrRestoreGetDatabaseLocations(
hbc,
&szDatabaseLocationList,
&cbLocationList);

if (err != ERROR_SUCCESS)
{
printf("Unable to query database locations on %s\n", szBackupServer);
return(err);
}

C cRemainingBackupFiles = bh.cBackupFiles;

//
//Skip past the files to the log headers.
//

while (cRemainingBackupFiles--)
{
BackupFile bf = {0};

err = ErrReadHeader(hBackupFile, &bf);

if (err != ERROR_SUCCESS)
{
return(err);
}
LARGE_INTEGER liMoveDelta = {0};

liMoveDelta = bf.liFileSize;

liMoveDelta.QuadPart += bf.cbFileNameLength;

if (!SetFilePointer(hBackupFile, liMoveDelta.LowPart, &liMoveDelta.HighPart, FILE_CURRENT)) {
printf("Unable to set file pointer past file.\n");
return(1);
}
}

LogHeader lh = {0};

err = ErrReadHeader(hBackupFile, &lh);

if (err != ERROR_SUCCESS)
{
return(err);
}
//
//We now know the total number of logs to back up.
//

C cBackupFiles = lh.cLogFiles + bh.cBackupFiles;

printf("Restoring %s database\n", szRestoreAnnotation);

if (!SetFilePointer(hBackupFile, sizeof(BackupHeader)+bh.cbDatabaseNameLength, NULL, FILE_BEGIN)) {
printf("Unable to reset database pointer to start of database.\n");
return(1);
}

I irgRestMap = 0;
EDB_RSTMAP *rgRestMap=NULL;

//
//If cRemainingBackupFiles == 0 then we are doing an
//- incremental restore
//
cRemainingBackupFiles = bh.cBackupFiles;
if (cRemainingBackupFiles)
{
rgRestMap = new EDB_RSTMAP[cBackupFiles];
if (rgRestMap == NULL)
{
printf("Error allocating memory for restore map\n");
return(1);
}
}

//
//Now copy the restore database.
//

while (cRemainingBackupFiles--)
{
ERR err = ErrRestoreFile(hBackupFile, rgRestMap, &irgRestMap, szDatabaseLocationList);
if (err != ERROR_SUCCESS)
{
printf("Unable to restore file.\n");
return(err);
}
}

//
//Now read the log header again.
//
err = ErrReadHeader(hBackupFile, &lh);

cRemainingBackupFiles = lh.cLogFiles;

DWORD genLow = 0xffffffffL;
DWORD genHigh = 0;

SZ szBackupLogPath = NULL;

while (cRemainingBackupFiles--)
{
DWORD gen = 0;
        BOOL fSetGen = fFalse;

ERR err = ErrRestoreFile(
            hBackupFile,
            rgRestMap,
            &irgRestMap,
            szDatabaseLocationList,
            &szBackupLogPath,
            &fSetGen,
            &gen);
if (err != ERROR_SUCCESS)
{
printf("Unable to restore file.\n");
return(err);
}
        if (fSetGen)
        {
    if (gen < genLow)
    {
    genLow = gen;
    }

    if (gen > genHigh)
    {
    genHigh = gen;
    }
        }
}

printf("Databases successfully restored\n");

SZ szLogPath = NULL;

SZ szCheckpointDir = NULL;

//
//Now find out where the database locations go.
//

SZ szDatabaseLocation = szDatabaseLocationList;

while (*szDatabaseLocation)
{
BFT bftLocation = *szDatabaseLocation++;
if (bftLocation == BFT_LOG_DIR)
{
szLogPath = szDatabaseLocation;
}
else if (bftLocation == BFT_CHECKPOINT_DIR)
{
szCheckpointDir = szDatabaseLocation;
}
else

szDatabaseLocation += strlen(szDatabaseLocation)+1;
}

err = HrRestoreRegister(hbc, szCheckpointDir, szLogPath, rgRestMap, irgRestMap, szBackupLogPath, genLow, genHigh);

if (err != ERROR_SUCCESS)
{
    (void) HrRestoreRegisterComplete(hbc, err);
    (void) HrRestoreEnd(hbc);
printf("Unable to perform restore %d\n", err);
return(err);
}

(void) HrRestoreRegisterComplete(hbc, hrNone);

err = HrRestoreEnd(hbc);

if (err != ERROR_SUCCESS)
{
printf("Unable to complete restore: %d\n", err);
return(err);
}

return(0);

}


CFGCODE.C

// --cfgcode.c------------------------------------------------------------------
//
//  Program to take a .INI file containing property values as input, and create
//  a file containing packed configuration data as output.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "cfgcode.chk"
#include "cfgcodem.h"
#include <io.h>
#include <sys/stat.h>

//
// Command Line Argument Names
//

#define ARG_PROGRAM_NAME        "CFGCODE"
#define ARG_INPUT_FILE          "InFile"
#define ARG_OUTPUT_FILE         "OutFile"
#define ARG_SECTION             "SECTION"
#define ARG_CONFIG_NAME         "NAME"
#define ARG_HELP1               "?"
#define ARG_HELP2               "HELP"

static char * rgpszArgArray[] =
{
ARG_PROGRAM_NAME,
ARG_INPUT_FILE,
ARG_OUTPUT_FILE,
ARG_SECTION,
ARG_CONFIG_NAME,
ARG_HELP1,
ARG_HELP2,
};

//
// Command Line Argument Variables
//

static TCHAR    szInputFile[MAX_PATH+1]  = {0};
static TCHAR    szOutputFile[MAX_PATH+1] = {0};
static TCHAR    szSection[MAX_PATH+1]    = {0};
static TCHAR    szConfigName[MAX_PATH+1] = {0};
static BOOL     fPrintHelp               = FALSE;
static BOOL     fPrintUsage              = FALSE;

//
// Function Declarations
//

static VOID PrintUsage(void);

static VOID PrintHelp(void);

static HRESULT HrParseCommandLine(
IN      int                     argc,
IN      char *                  argv[]);

static HRESULT HrReadPropsFromINIFile(
IN      LPTSTR                  pszInputFile,
IN      LPTSTR                  pszSection,
OUT     ULONG *                 pcProps,
OUT     LPSPropValue *  ppProps);

static HRESULT HrParsePropValue(
    IN      LPTSTR                  pszFullLine,
IN      ULONG                   ulPropTag,
IN      LPTSTR                  pszValueText,
IN      LPVOID                  pBaseObject,
OUT     LPSPropValue            pProp);

static HRESULT HrCreateBinaryDataFile(
IN      LPTSTR                  pszOutputFile,
IN      ULONG                   cbData,
IN      LPBYTE                  pbData);

//
// Functions
//

//$--main-----------------------------------------------------------------------
//  Main function that performs file conversion.
// -----------------------------------------------------------------------------
int main(                               // RETURNS: exit code
IN      int                 argc,   // number of arguments on command line
IN      char *              argv[]) // array of command line arguments
{
HRESULT                 hr                      = NOERROR;
BOOL                    fMAPIInitialized        = FALSE;
MAPIINIT_0              MapiInit                = { 0 };
BOOL                    fPrintResult            = FALSE;
ULONG                   cProps                  = 0;
LPSPropValue            pProps                  = NULL;
ULONG                   cbConfigData            = 0;
LPBYTE                  pbConfigData            = NULL;
BOOL                    fEventLogOpen           = FALSE;

DEBUGPUBLIC("main()\n");

hr = CHK_main(argc, argv);
if (FAILED(hr))
return _nEcFromHr(EC_EDK_E_FAIL);

    printf( "\n" );

// Parse the command line arguments.

hr = HrParseCommandLine(argc, argv);
if (FAILED(hr))
goto cleanup;

// If they requested a usage message (i.e. no arguments) then
// print a usage message and exit.

if (fPrintUsage)
{
PrintUsage();
goto cleanup;
}

// If they requested help then print a help message and exit.

if (fPrintHelp)
{
PrintHelp();
goto cleanup;
}

// Open the event log.

hr = HrEventOpenLog(
    TEXT("EDK CFGCODE"), NULL, NULL, NULL, NULL, NULL);

if (FAILED(hr))
{
fprintf(stderr, "ERROR: Can't open event log.\n");
goto cleanup;
}
fEventLogOpen = TRUE;

// After this point we print the result of the operation and number
// of events logged.

fPrintResult = TRUE;

// Initialize MAPI.

    MapiInit.ulVersion = MAPI_INIT_VERSION;

    hr = MAPIInitialize(&MapiInit);
    if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_CANNOT_INIT_MAPI,
0,
0);
goto cleanup;
}

fMAPIInitialized = TRUE;

// If the input file name is not a full path then add the current
// directory to the front of the name.

if (strchr(szInputFile,':') == NULL && strchr(szInputFile,'\\') == NULL)
{
TCHAR szCurrentDir[MAX_PATH+1] = {0};
ULONG cchCurrentDir = 0;

cchCurrentDir = GetCurrentDirectory(MAX_PATH, szCurrentDir);
if (cchCurrentDir == 0)
{
EventLogMsg(
CFGCODE_CANNOT_GET_CURRENT_DIR,
0,
1, GetLastError());
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (
szCurrentDir[cchCurrentDir - 1] != ':' &&
szCurrentDir[cchCurrentDir - 1] != '\\')
{
strcat(szCurrentDir, "\\");
}
strcat(szCurrentDir, szInputFile);
strcpy(szInputFile, szCurrentDir);
}

// Do the operation.

hr = HrReadPropsFromINIFile(szInputFile, szSection, &cProps, &pProps);
if (FAILED(hr))
{
goto cleanup;
}

hr = HrCfgPackData(
szConfigName, cProps, pProps, &cbConfigData, &pbConfigData);
if (FAILED(hr))
{
EventLogMsg(
CFGCODE_CANNOT_PACK_CONFIG_INFO,
0,
0);
goto cleanup;
}

hr = HrCreateBinaryDataFile(szOutputFile, cbConfigData, pbConfigData);
if (FAILED(hr))
{
goto cleanup;
}

cleanup:
// Print the results of the operation if we got that far.

if (fPrintResult)
{
if (SUCCEEDED(hr))
{
printf("File created.\n");
}
else
{
HRESULT hrT = NOERROR;
EDKEVENTCOUNT sEventCount = {0};

hrT = HrEventGetCounts(&sEventCount);
if (FAILED(hrT) ||
(sEventCount.cError == 0 && sEventCount.cWarning == 0))
{
fprintf(stderr, "ERROR: operation failed.\n");
}

// Print the number of errors logged.

if (sEventCount.cError == 1)
{
fprintf(stderr,
"ERROR: 1 error written to NT event log.\n");
}
else if (sEventCount.cError > 1)
{
fprintf(stderr,
"ERROR: %d errors written to NT event log.\n");
}

// Print the number of warnings logged.

if (sEventCount.cWarning == 1)
{
fprintf(stderr,
"WARNING: 1 warning written to NT event log.\n");
}
else if (sEventCount.cWarning > 1)
{
fprintf(stderr,
"WARNING: %d warnings written to NT event log.\n");
}
}
}

// Free up everything.

if (fMAPIInitialized)
{
MAPIFREEBUFFER(pProps);
MAPIFREEBUFFER(pbConfigData);
MAPIUninitialize();
}

// Close event logging.

if (fEventLogOpen)
{
(void)HrEventCloseLog();
fEventLogOpen = FALSE;
}

    // return exit code
    return _nEcFromHr(hr);

}

//$--PrintUsage-----------------------------------------------------------------
//  Print brief message about how to use this program.
// -----------------------------------------------------------------------------
static VOID PrintUsage(void)                    // RETURNS: nothing
{
DEBUGPRIVATE("PrintUsage()\n");

printf("USAGE: CFGCODE InFile OutFile [Flags]\n");
printf("\n");
printf("  [Flags]        Enter CFGCODE /? for details\n");
}

//$--PrintHelp------------------------------------------------------------------
//  Print more detailed message about how to use this program.
// -----------------------------------------------------------------------------
static VOID PrintHelp(void)                             // RETURNS: nothing
{
DEBUGPRIVATE("PrintHelp()\n");

printf("Utility to create a packed configuration data file from a .INI "
"file.\n");
printf("\n");
printf("USAGE: CFGCODE InFile OutFile [Flags]\n");
printf("\n");
printf("  InFile         Input .INI file containing MAPI properties\n");
printf("  OutFile        Output file to write packed configuration data\n");
printf("  /SECTION=      Name of the [Section] in the .INI file\n");
printf("  /NAME=         Configuration name for packed data\n");
printf("  /HELP or /?    Display help screen\n");
}

//$--HrParseCommandLine---------------------------------------------------------
//  Parse the command line arguments and put their values in the appropriate
//  variables.
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine(              // RETURNS: HRESULT
IN      int                             argc,                   // number of arguments on command line
IN      char *                  argv[])                 // array of command line arguments
{
HRESULT         hr                              = NOERROR;
HRESULT         hrT                             = NOERROR;
int                     i                               = 0;
char *          pszArgument             = NULL;
char *          pszValue                = NULL;
int                     cNonFlagArgs    = 0;

DEBUGPRIVATE("HrParseCommandLine()\n");

hr = CHK_main(argc, argv);
if (FAILED(hr))
RETURN(hr);

// If there are no flags on the command line then just print usage message.

if (argc < 2)
{
fPrintUsage = TRUE;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, don't do any other
// parsing.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);

if (SUCCEEDED(hrT) && pszArgument &&
(!_stricmp(pszArgument,ARG_HELP1) ||
!_stricmp(pszArgument,ARG_HELP2)))
{
fPrintHelp = TRUE;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
 hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);
if (FAILED(hr))
{
fprintf(stderr, "ERROR: unable to parse command line flags.\n");
goto cleanup;
}

// Parse non-flag arguments.

if (pszArgument == NULL && pszValue != NULL)
{
switch (++cNonFlagArgs)
{
case 1:         // Input File
STRNCPY(szInputFile, pszValue);
break;

case 2:         // Output File
STRNCPY(szOutputFile, pszValue);
break;

default:        // Too many arguments!!!
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{
if (!_stricmp(pszArgument,ARG_SECTION))
{
STRNCPY(szSection, pszValue);
}
else
if (!_stricmp(pszArgument,ARG_CONFIG_NAME))
{
STRNCPY(szConfigName, pszValue);
}
else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown flags.

else
{
fprintf(stderr, "ERROR: unknown flag %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Now make sure that all the flags are valid.

if (*szInputFile == 0)
{
fprintf(stderr, "ERROR: please specify %s\n", ARG_INPUT_FILE);
hr = HR_LOG(E_FAIL);
}

if (*szOutputFile == 0)
{
fprintf(stderr, "ERROR: please specify %s\n", ARG_OUTPUT_FILE);
hr = HR_LOG(E_FAIL);
}

if (*szSection == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SECTION);
hr = HR_LOG(E_FAIL);
}

if (*szConfigName == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_CONFIG_NAME);
hr = HR_LOG(E_FAIL);
}

cleanup:

RETURN(hr);
}

//$--HrReadPropsFromINIFile-----------------------------------------------------
//  Read a set of MAPI properties from a .INI file.
//
//  Properties are stored with the property tags as hex values, as follows:
//
//  [Section_Name]
//  <property_tag>=<value>
//  <property_tag>=<value>
//  <property_tag>=<value>
// -----------------------------------------------------------------------------
static HRESULT HrReadPropsFromINIFile(  // RETURNS: HRESULT
IN      LPTSTR                  pszInputFile,   // name of file to read properties from
IN      LPTSTR                  pszSection,             // name of section in file
OUT     ULONG *                 pcProps,                // number of properties read from file
OUT     LPSPropValue *  ppProps)                // array of properties read from file
{
HRESULT                 hr                                      = NOERROR;
LPTSTR                  pszSectionBuffer        = NULL;
ULONG                   cchSectionBuffer        = 2048;
ULONG                   cchActualSize           = 0;
LPTSTR                  psz                                     = NULL;
LPTSTR                  pszFullLine                     = NULL;
ULONG                   iProp                           = 0;
ULONG                   cProps                          = 0;
LPSPropValue    pProps                          = NULL;
ULONG                   ulPropTag                       = 0;
ULONG                   ulError                         = ERROR_SUCCESS;

DEBUGPRIVATE("HrReadPropsFromINIFile()\n");

hr = CHK_HrReadPropsFromINIFile(
pszInputFile,
pszSection,
pcProps,
ppProps);
if (FAILED(hr))
RETURN(hr);

// Read the entire section from the .INI file into the buffer.
// If the buffer is too small then double its size and keep trying.

while (TRUE)
{
// Get the memory for the buffer to read in the section.

hr = MAPIAllocateBuffer(cchSectionBuffer, &pszSectionBuffer);
if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
ZeroMemory(pszSectionBuffer, cchSectionBuffer);

// Read in the section data.

SetLastError(ERROR_SUCCESS);
cchActualSize = GetPrivateProfileSection(
pszSection,
pszSectionBuffer,
cchSectionBuffer,
pszInputFile);
ulError = GetLastError();
if (ulError != ERROR_SUCCESS)
{
    if (ulError == ERROR_FILE_NOT_FOUND)
    {
EventLogMsg(
CFGCODE_CANNOT_READ_PROFILE_SECTION_FNF,
2, pszSection, pszInputFile,
0);
    }
    else
    {
EventLogMsg(
CFGCODE_CANNOT_READ_PROFILE_SECTION,
2, pszSection, pszInputFile,
1, GetLastError());
    }
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// If the buffer was big enough then we're done.  If the buffer is
// not big enough then the return value is the buffer size minus
// two (see GetPrivateProfileSection() documentation).

if (cchActualSize < (cchSectionBuffer - 2))
break;

// Otherwise, add 2K to the buffer size and try again.

MAPIFREEBUFFER(pszSectionBuffer);

cchSectionBuffer += 2048;
}

// Count the number of properties we've read.

for (psz = pszSectionBuffer; *psz; psz += (lstrlen(psz) + 1))
{
cProps++;
}

// Allocate an array of SPropValue's big enough to hold all the properties.

hr = MAPIAllocateBuffer(cProps * sizeof(SPropValue), &pProps);
if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}

// Read in the data and store it in the allocated array.  We may also need
// to allocate other pieces of memory to put the values of data items.

for (
psz = pszSectionBuffer, iProp = 0;
*psz;
psz += (lstrlen(psz) + 1), iProp++)
{
ASSERTERROR(iProp < cProps, "iProp >= cProps: array overflow!");

// Save pointer to original string in case we have to log an error.

pszFullLine = psz;

// Get the property tag.

ulPropTag = strtol(psz, &psz, 16);
if (psz == pszFullLine)
{
EventLogMsg(
CFGCODE_NO_PROP_TAG,
1, pszFullLine,
0);
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Make sure there's an equal sign.

if (*psz++ != '=')
{
EventLogMsg(
CFGCODE_PROP_TAG_WITHOUT_EQUALS,
1, pszFullLine,
0);
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Get the value based on the property type.

hr = HrParsePropValue(pszFullLine, ulPropTag, psz, pProps, &pProps[iProp]);
if (FAILED(hr))
goto cleanup;

} // end for

// Set the output parameters.

*pcProps = cProps;
*ppProps = pProps;

cleanup:
MAPIFREEBUFFER(pszSectionBuffer);
if (FAILED(hr))
{
MAPIFREEBUFFER(pProps);
}

RETURN(hr);
}

//$--HrParsePropValue-----------------------------------------------------------
//  Takes a text version of a property value and fills in an SPropValue
//  structure.  If memory needs to be allocated, it is allocated using
//  MAPIAllocateMore() based on the object passed in pBaseObject.
//
//  Valid property value formats for property types are as follows (and do
//  not include the quotes):
//
//  PT_BINARY   a series of two digit hex numbers separated by spaces
//              (e.g. "01 fe 2a 00 1c")
//
//  PT_BOOLEAN  the value 1 or 0 (1=True, 0=False)
//
//  PT_LONG     a decimal or hex constant (e.g. "127" or "0x0fff0003")
//
//  PT_STRING8  any string of characters (e.g. "This is fun!")
//
//  PT_SYSTIME  a date in the format "yyyy/mm/dd hh:mm:ss"
//              (e.g. "1995/12/31 23:59:59")
// -----------------------------------------------------------------------------
static HRESULT HrParsePropValue(                // RETURNS: HRESULT
    IN  LPTSTR          pszFullLine,    // the full line (for error logging)
IN      ULONG                   ulPropTag,              // property tag of this property
IN      LPTSTR                  pszValueText,   // text of property value to parse
IN      LPVOID                  pBaseObject,    // base object for MAPIAllocateMore()
OUT     LPSPropValue    pProp)                  // structure to write results
{
HRESULT                 hr                              = NOERROR;
LPTSTR          psz             = NULL;
LPTSTR          pszPrev         = NULL;

DEBUGPRIVATE("HrParsePropValue()\n");

hr = CHK_HrParsePropValue(
pszFullLine,
ulPropTag,
pszValueText,
pBaseObject,
pProp);
if (FAILED(hr))
RETURN(hr);

switch (PROP_TYPE(ulPropTag))
{
case PT_BINARY:
{
ULONG cb = 0;
ULONG ib = 0;
LPBYTE pb = NULL;
LONG lNumber = 0;

// Count the bytes and check that all the bytes are valid.

psz = pszValueText;
while (*psz)
{
if (isspace(*psz))
psz++;
else
{
pszPrev = psz;
lNumber = strtol(psz, &psz, 16);
if (psz == pszPrev || lNumber > 255 || lNumber < 0)
    {
hr = HR_LOG(E_FAIL);
EventLogMsg(
CFGCODE_ILLEGAL_BINARY_VALUE,
1, pszFullLine,
0);
goto cleanup;
    }
cb++;
}
}

// Allocate a buffer big enough for the number of bytes.

hr = MAPIAllocateMore(cb, pBaseObject, &pProp->Value.bin.lpb);
if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
pProp->Value.bin.cb = cb;

// Write the bytes into the buffer.

psz = pszValueText;
for (ib = 0, pb = pProp->Value.bin.lpb; ib < cb; ib++, pb++)
{
*pb = (BYTE)strtol(psz, &psz, 16);
}

break;
}

case PT_BOOLEAN:
    psz = pszValueText;

    while (isspace(*psz))
psz++;

    pszPrev = psz;
pProp->Value.l = strtol(psz, &psz, 0);

    while (isspace(*psz))
psz++;

    if (psz == pszPrev || *psz ||
(pProp->Value.l != 0 && pProp->Value.l != 1))
    {
hr = HR_LOG(E_FAIL);
EventLogMsg(
CFGCODE_ILLEGAL_BOOLEAN_VALUE,
1, pszFullLine,
0);
goto cleanup;
    }
break;

case PT_LONG:
    psz = pszValueText;

    while (isspace(*psz))
psz++;

    pszPrev = psz;
pProp->Value.l = strtol(psz, &psz, 0);

    while (isspace(*psz))
psz++;

    if (psz == pszPrev || *psz)
    {
hr = HR_LOG(E_FAIL);
EventLogMsg(
CFGCODE_ILLEGAL_LONG_VALUE,
1, pszFullLine,
0);
goto cleanup;
    }
break;

case PT_STRING8:
{
ULONG cb = strlen(pszValueText) + 1;

hr = MAPIAllocateMore(cb, pBaseObject, &pProp->Value.lpszA);
if (FAILED(hr))
{
hr = HR_LOG(E_FAIL);
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
MoveMemory(pProp->Value.lpszA, pszValueText, cb);
break;
}

case PT_SYSTIME:
{
SYSTEMTIME st = {0};
    CHAR chBogus = 0;
int lReturn = 0;
BOOL fItWorked = TRUE;

lReturn = sscanf(
pszValueText,
" %hd/%hd/%hd %hd:%hd:%hd %c",
&st.wYear,
&st.wMonth,
&st.wDay,
&st.wHour,
&st.wMinute,
&st.wSecond,
&chBogus);
if (lReturn == 0 || lReturn == EOF || chBogus)
{
hr = HR_LOG(E_FAIL);
EventLogMsg(
CFGCODE_ILLEGAL_SYSTIME_VALUE,
1, pszFullLine,
0);
goto cleanup;
}

fItWorked = SystemTimeToFileTime(&st, &pProp->Value.ft);
if (!fItWorked)
{
hr = HR_LOG(E_FAIL);
EventLogMsg(
CFGCODE_CANNOT_CONVERT_TIME,
1, pszFullLine,
1, GetLastError());
goto cleanup;
}

break;
}

default:
{
TCHAR szPropType[9] = {0};

wsprintf(szPropType, TEXT("%08lx"), PROP_TYPE(ulPropTag));

hr = HR_LOG(E_NOTIMPL);
EventLogMsg(
CFGCODE_UNSUPPORTED_PROPERTY_TYPE,
1, pszFullLine,
0);
goto cleanup;
}
}

pProp->ulPropTag = ulPropTag;

cleanup:
RETURN(hr);
}

//$--HrCreateBinaryDataFile-----------------------------------------------------
//  Create a file and write binary data to it.
// -----------------------------------------------------------------------------
static HRESULT HrCreateBinaryDataFile(  // RETURNS: HRESULT
IN      LPTSTR                  pszOutputFile,  // name of file to create
IN      ULONG                   cbData,                 // number of bytes to write to file
IN      LPBYTE                  pbData)                 // binary data to write to file
{
HRESULT                 hr                              = NOERROR;
HANDLE                  hOutputFile             = INVALID_HANDLE_VALUE;
BOOL                    fItWorked               = TRUE;
DWORD                   cbBytesWritten  = 0;

DEBUGPRIVATE("HrCreateBinaryDataFile()\n");

hr = CHK_HrCreateBinaryDataFile(
pszOutputFile,
cbData,
pbData);
if (FAILED(hr))
RETURN(hr);

hOutputFile = CreateFile(
pszOutputFile,                                  // file name
GENERIC_WRITE,                                  // open for write
0,                                                              // no sharing
NULL,                                                   // default security
CREATE_ALWAYS,                                  // overwrite existing file
FILE_ATTRIBUTE_NORMAL,                  // normal file
NULL);                                                  // no template file
if (hOutputFile == INVALID_HANDLE_VALUE)
{
EventLogMsg(
CFGCODE_COULD_NOT_CREATE_FILE,
1, pszOutputFile,
1, GetLastError());
hr = HR_LOG(E_FAIL);
goto cleanup;
}

fItWorked = WriteFile(
hOutputFile,                                    // file handle
pbData,                                                 // buffer to write
cbData,                                                 // number of bytes to write
&cbBytesWritten,                                // number of bytes actually written
NULL);                                                  // no overlapped
if (!fItWorked || cbBytesWritten != cbData)
{
EventLogMsg(
CFGCODE_COULD_NOT_WRITE_FILE,
1, pszOutputFile,
1, GetLastError());
hr = HR_LOG(E_FAIL);
goto cleanup;
}

fItWorked = CloseHandle(hOutputFile);
if (!fItWorked)
{
EventLogMsg(
CFGCODE_COULD_NOT_CLOSE_FILE,
1, pszOutputFile,
1, GetLastError());
hr = HR_LOG(E_FAIL);
goto cleanup;
}
hOutputFile = INVALID_HANDLE_VALUE;

cleanup:
if (FAILED(hr) && hOutputFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hOutputFile);
DeleteFile(pszOutputFile);
}

RETURN(hr);
}


CRARUN.CPP

//--crarun.cpp----------------------------------------------------------------
//
// implementation of the CExchRuleExt class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "windows.h"

#include <stdio.h>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>

#include "initguid.h"
#include "exchcli.h"

#include "crarun.h"

CExchRuleExt::CExchRuleExt()
{
}

CExchRuleExt::~CExchRuleExt()
{
}

STDMETHODIMP CExchRuleExt::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    HRESULT hResult = S_OK;

    *ppvObj = NULL;

    if (( IID_IUnknown == riid) || ( IID_IExchangeRuleExt == riid) )
    {
        *ppvObj = (LPUNKNOWN)this;
        AddRef();
    }
    else
        hResult = E_NOINTERFACE;

    return hResult;
}

STDMETHODIMP_(ULONG) CExchRuleExt::AddRef()
{
    ++m_cRef;
    return m_cRef;
}

STDMETHODIMP_(ULONG) CExchRuleExt::Release()
{
// Use stack parameter, in case we delete ourselves
    ULONG ulCount = --m_cRef;

if (!ulCount)
{
delete this;
}

return ulCount;
}

//$--Install------------------------------------------------------------------
// Do we want to run in this context.
// ---------------------------------------------------------------------------
STDMETHODIMP CExchRuleExt::Install (
    LPEXCHANGERULEEXTCALLBACK percb,    // exchange rule callback
    ULONG context)                      // ERCONTEXT_INFO or ERCONTEXT_EXEC
{
    return S_OK;
}

//$--QueryRelease-------------------------------------------------------------
// This method is reserved for future use.
// ---------------------------------------------------------------------------
STDMETHODIMP CExchRuleExt::QueryRelease()
{
return S_OK;
}

//$--GetCommand---------------------------------------------------------------
// Display UI and fill out command and display buffers.
// ---------------------------------------------------------------------------
STDMETHODIMP CExchRuleExt::GetCommand (
LPEXCHANGERULEEXTCALLBACK percb,    // exchange rule callback
LPTSTR pszCommand,                  // an IN/OUT buffer in which to return
                                        // the encoded command, the buffer may
                                        // be pre-initialized with a previously
                                        // existing command for this provider.
    ULONG cchCommand,                   // length of the buffer given
LPTSTR pszDisplayName,              // IN/OUT, as the previous buffer,
                                        // but for the display string
    ULONG cchDisplayName)               // length of the buffer given
{
    HRESULT hr;
    HWND hWnd;
    DWORD dwError;
    TCHAR szFileName[_MAX_PATH];
    TCHAR szInitialFileName[_MAX_PATH] = {""};

    if ((NULL == pszCommand) ||
        (NULL == pszDisplayName))
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    sscanf (pszCommand, "launch %s", szInitialFileName);

    hr = percb->GetWindow (&hWnd);

    if (FAILED(hr))
        return hr;

    OPENFILENAME ofn;

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = hWnd;
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.lpstrFilter = "Programs\0*.exe;*.bat;*.com\0";
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szInitialFileName;
    ofn.nMaxFile = _MAX_PATH;
    ofn.lpstrFileTitle = szFileName;
    ofn.nMaxFileTitle = _MAX_PATH;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = "Program to Launch";
    ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "exe";
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    if (!GetOpenFileName (&ofn))
    {
        dwError = CommDlgExtendedError();

        if (dwError)
            return S_FALSE;

        // else hit Cancel
    }
    else
    {
        if (strlen (ofn.lpstrFile) + sizeof ("launch ") > cchCommand)
        {
            return TYPE_E_BUFFERTOOSMALL;
        }

        if (strlen (ofn.lpstrFile) > cchDisplayName)
        {
            return TYPE_E_BUFFERTOOSMALL;
        }

        sprintf (pszCommand, "launch %s", ofn.lpstrFile);
        strcpy (pszDisplayName, ofn.lpstrFile);
    }

    return S_OK;
}

//$--QueryCommandDisplayName--------------------------------------------------
//  What should the client display in it's display box.
// ---------------------------------------------------------------------------
STDMETHODIMP CExchRuleExt::QueryCommandDisplayName(
LPEXCHANGERULEEXTCALLBACK percb,    // exchange rule callback
LPCTSTR pszCommand,                 // command buffer
LPTSTR pszDisplayName,              // OUT buffer for the display name
    ULONG cchDisplayName)               // size of buffer
{
TCHAR szFileName[_MAX_PATH];

    if ((NULL == pszCommand) ||
        (NULL == pszDisplayName))
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    sscanf (pszCommand, "launch %s", szFileName);

    if (strlen (szFileName) > cchDisplayName)
    {
        return TYPE_E_BUFFERTOOSMALL;
    }

    strcpy (pszDisplayName, szFileName);

return S_OK;
}

//$--Command------------------------------------------------------------------
//  Run the command.
// ---------------------------------------------------------------------------
STDMETHODIMP CExchRuleExt::Command(
LPEXCHANGERULEEXTCALLBACK percb,    // exchange rule callback
LPCTSTR pszCommand,                 // IN command to run
ULONG cb, LPENTRYID peid)           // EntryID of the message
{
HRESULT hr;
    HWND hWnd;
    TCHAR szFileName[_MAX_PATH];

    if ((NULL == pszCommand) ||
        (NULL == peid))
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    sscanf (pszCommand, "launch %s", szFileName);

    hr = percb->GetWindow (&hWnd);

    if (FAILED(hr))
        return hr;

    if ((HINSTANCE)32 < ShellExecute (hWnd, NULL, szFileName, NULL, NULL, SW_SHOWNORMAL))
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}


CRARUN.H

//--crarun.h------------------------------------------------------------------
//
// declaration of the CExchRuleExt class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

class CExchRuleExt : public IExchangeRuleExt
{
public:
    CExchRuleExt();
    ~CExchRuleExt();

    STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    STDMETHODIMP Install (LPEXCHANGERULEEXTCALLBACK percb, ULONG context);

STDMETHODIMP QueryRelease();

STDMETHODIMP GetCommand (
LPEXCHANGERULEEXTCALLBACK percb,
LPTSTR pszCommand, ULONG cchCommand,
LPTSTR pszDisplayName, ULONG cchDisplayName);

STDMETHODIMP QueryCommandDisplayName(
LPEXCHANGERULEEXTCALLBACK percb,
LPCTSTR pszCommand,
LPTSTR pszDisplayName, ULONG cchDisplayName);

STDMETHODIMP Command(
LPEXCHANGERULEEXTCALLBACK percb,
LPCTSTR pszCommand,
ULONG cb, LPENTRYID peid);
private:
    ULONG m_cRef;
};


ENTRY.CPP

//--entry.cpp-----------------------------------------------------------------
//
// Exchange entry function
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "windows.h"

#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>

#include "exchcli.h"

#include "crarun.h"

//$--ExchEntryPoint-----------------------------------------------------------
//
//Parameters - none
//
//Purpose
//The entry point called by Exchange.
//
//Return Value
//Pointer to Exchange Extension (IExchRuleExt) interface
//
//Comments
//Exchange Client calls this for each context entry.
// ---------------------------------------------------------------------------
extern "C"
LPEXCHANGERULEEXT CALLBACK ExchEntryPoint(void)
{
return new CExchRuleExt;
};


DIRSYNC.C

// --dirsync.c------------------------------------------------------------------
//
//  This file contains sample code showing how to do directory synchronization
//  using DAPI.  It implements both the INBOUND and OUTBOUND direction.
//  I.E. importing an external mail system or other software system to the
//  Exchange directory, and exporting the Exchange directory to an external
//  mail system or other software system.
//
//  It compiles to an executable file which is run from the command line.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dirsync.h"
#include "dsynmsg.h"
#include "dirsync.chk"

//
// Bit Values and Defined Constants
//

// Direction of data flow

#define DIRECTION_EXPORT        0x00000001
#define DIRECTION_IMPORT        0x00000002

// Source of settings

#define SOURCE_COMMAND_LINE 0   // from command line only (no registry involved)
#define SOURCE_INSTANCE     1   // from named instance using <Instance>
#define SOURCE_KEY          2   // from registry key using /KEY=<Key>

// Number of elements by which to grow the imported object list
// when it runs out of space.

#define IMPORTED_OBJECT_LIST_GROWTH_SIZE 1000

//
// Names of Command Line Arguments
//

#define ARG_PROGRAM_NAME        "DIRSYNC"
#define ARG_EXPORT              "EXPORT"
#define ARG_IMPORT              "IMPORT"
#define ARG_BOTH                "BOTH"
#define ARG_DSA                 "DSA"
#define ARG_BASEPOINT           "BASEPOINT"
#define ARG_CONTAINER           "CONTAINER"
#define ARG_SERVER              "SERVER"
#define ARG_HOMESERVER          "HOMESERVER"
#define ARG_EXPLIST             "EXPLIST"
#define ARG_EXPFILE             "EXPFILE"
#define ARG_IMPFILE             "IMPFILE"
#define ARG_OBJCLASS            "OBJCLASS"
#define ARG_MVAPPEND            "MVAPPEND"
#define ARG_NOMVAPPEND          "NOMVAPPEND"
#define ARG_EXPFULL             "EXPFULL"
#define ARG_NOEXPFULL           "NOEXPFULL"
#define ARG_IMPFULL             "IMPFULL"
#define ARG_NOIMPFULL           "NOIMPFULL"
#define ARG_PROGRESS            "PROGRESS"
#define ARG_NOPROGRESS          "NOPROGRESS"
#define ARG_SUBS                "SUBS"
#define ARG_NOSUBS              "NOSUBS"
#define ARG_NTCREATE            "NTCREATE"
#define ARG_NONTCREATE          "NONTCREATE"
#define ARG_NTDELETE            "NTDELETE"
#define ARG_NONTDELETE          "NONTDELETE"
#define ARG_USN                 "USN"
#define ARG_HELP1               "?"
#define ARG_HELP2               "HELP"
#define ARG_SET                 "SET"
#define ARG_DISPLAY             "DISPLAY"
#define ARG_KEY                 "KEY"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
    ARG_EXPORT,
    ARG_IMPORT,
    ARG_BOTH,
    ARG_DSA,
    ARG_BASEPOINT,
    ARG_CONTAINER,
    ARG_SERVER,
    ARG_HOMESERVER,
    ARG_EXPLIST,
    ARG_EXPFILE,
    ARG_IMPFILE,
    ARG_OBJCLASS,
    ARG_MVAPPEND,
    ARG_NOMVAPPEND,
    ARG_EXPFULL,
    ARG_NOEXPFULL,
    ARG_IMPFULL,
    ARG_NOIMPFULL,
    ARG_PROGRESS,
    ARG_NOPROGRESS,
    ARG_SUBS,
    ARG_NOSUBS,
    ARG_NTCREATE,
    ARG_NONTCREATE,
    ARG_NTDELETE,
    ARG_NONTDELETE,
    ARG_USN,
    ARG_HELP1,
    ARG_HELP2,
    ARG_SET,
    ARG_DISPLAY,
    ARG_KEY,
};

static char * rgpszObjClassValues[] = {
    OBJECT_CLASS_REMOTE,
    OBJECT_CLASS_MAILBOX,
};

//
// Command Line Parameters Also Stored In The Registry
//

// (Note: BOOL's in this category have been changed to DWORD's so
// they can be more easily read from or written to the registry.)

static DWORD    dwDirection                         = 0;
static TCHAR    szDSA           [MAX_PATH+1]        = {0};
static TCHAR    szBasePoint     [MAX_PATH+1]        = {0};
static TCHAR    szContainer     [MAX_PATH+1]        = {0};
static TCHAR    szServer        [MAX_PATH+1]        = {0};
static TCHAR    szHomeServer    [MAX_PATH+1]        = {0};
static TCHAR    szExportList    [2048]              = {0};
static TCHAR    szExportFile    [MAX_PATH+1]        = {0};
static TCHAR    szImportFile    [MAX_PATH+1]        = {0};
static TCHAR    szObjClass      [MAX_PATH+1]        = OBJECT_CLASS_REMOTE;
static DWORD    fFullExport                         = FALSE;
static DWORD    fFullImport                         = FALSE;
static DWORD    fProgressDialog                     = FALSE;
static DWORD    fSubs                               = FALSE;
static DWORD    fNTCreate                           = FALSE;
static DWORD    fNTDelete                           = FALSE;
static DWORD    fMVAppend                           = FALSE;
static DWORD    dwLastUSN                           = 0;

//
// Command Line Parameters Not Stored In The Registry
//

static BOOL     fHelp                               = FALSE;
static BOOL     fSet                                = FALSE;
static BOOL     fDisplay                            = FALSE;
static TCHAR    szKey           [MAX_PATH+1]        = {0};
static TCHAR    szKeyState      [MAX_PATH+1]        = {0};
static TCHAR    szInstanceName  [MAX_PATH+1]        = {0};

static LPDWORD  rgdwImportedObjectList              = NULL;
static DWORD    cdwImportedObjectList               = 0;
static DWORD    cdwMaxImportedObjectList            = 0;

//
// Other Variables Local to the File
//

static BOOL     fRespondedToUser                    = FALSE;

static DWORD    dwSettingsSource                    = SOURCE_COMMAND_LINE;
static TCHAR    szSourceStamp   [MAX_PATH+1]        = {0};
static HANDLE   hKey                                = NULL;
static HANDLE   hKeyState                           = NULL;

static LONG     iUSNCreatedAttribute                = -1;
static LONG     iUSNChangedAttribute                = -1;
static LONG     iIsDeletedAttribute                 = -1;
static LONG     iObjDistNameAttribute               = -1;
static LONG     iImportedFromAttribute              = -1;
static DIR_SYNC_ATTRIBUTE_INFO * rgdsAttributes     = NULL;
static DWORD    cdsAttributes                       = 0;

static DWORD    dwStartingUSN                       = 0;

static BOOL     fErrorInCallback                    = FALSE;
static DWORD    cDAPILoggedErrors                   = 0;

static DWORD    cObjectsImported                    = 0;
static DWORD    cObjectsExported                    = 0;

//
// Structures Used For Reading/Writing Registry Data
//

REG_VARIABLE rgrvInstanceData[] = {
    TEXT("Type"),               REG_DWORD,  sizeof(dwDirection),        &dwDirection,
    TEXT("DSA"),                REG_SZ,     sizeof(szDSA),              szDSA,
    TEXT("Basepoint"),          REG_SZ,     sizeof(szBasePoint),        szBasePoint,
    TEXT("Container"),          REG_SZ,     sizeof(szContainer),        szContainer,
    TEXT("Server"),             REG_SZ,     sizeof(szServer),           szServer,
    TEXT("Home Server"),        REG_SZ,     sizeof(szHomeServer),       szHomeServer,
    TEXT("Export List"),        REG_SZ,     sizeof(szExportList),       szExportList,
    TEXT("Export File"),        REG_SZ,     sizeof(szExportFile),       szExportFile,
    TEXT("Import File"),        REG_SZ,     sizeof(szImportFile),       szImportFile,
    TEXT("Object Class"),       REG_SZ,     sizeof(szObjClass),         szObjClass,
    TEXT("Full Export"),        REG_DWORD,  sizeof(fFullExport),        &fFullExport,
    TEXT("Full Import"),        REG_DWORD,  sizeof(fFullImport),        &fFullImport,
    TEXT("Progress Dialog"),    REG_DWORD,  sizeof(fProgressDialog),    &fProgressDialog,
    TEXT("Sub-containers"),     REG_DWORD,  sizeof(fSubs),              &fSubs,
    TEXT("NT Create"),          REG_DWORD,  sizeof(fNTCreate),          &fNTCreate,
    TEXT("NT Delete"),          REG_DWORD,  sizeof(fNTDelete),          &fNTDelete,
    TEXT("MV Append"),          REG_DWORD,  sizeof(fMVAppend),          &fMVAppend,
};

REG_VARIABLE rgrvStateData[] = {
    // Note: Imported Object List must be the first element in this array.
    TEXT("Imported Object List"),   REG_BINARY,     0,                  NULL,
    TEXT("Last USN"),           REG_DWORD,  sizeof(dwLastUSN),          &dwLastUSN,
};

//
// Function Declarations
//

static HRESULT HrPreParseCommandLine(
    IN int argc,
    IN char *argv[]);

static VOID PrintUsage(void);

static VOID PrintHelp(void);

static VOID PrintSettings(void);

static HRESULT HrOpenRegistryKeys(void);

static BOOL fAskCreateInstance(void);

static HRESULT HrReadRegistryData(void);

static HRESULT HrParseCommandLine(
    IN int argc,
    IN char *argv[]);

static HRESULT HrWriteInstanceData(void);

static HRESULT HrWriteStateData(void);

static HRESULT HrCheckInfoCompleteness(void);

static HRESULT HrDoExport(void);

static DAPI_FNExportEntry fExportCallback;

static HRESULT HrProcessExportHeader(
    IN PDAPI_ENTRY pEntry);

static HRESULT HrProcessExportEntry(
    IN PDAPI_ENTRY pEntry);

static int __cdecl nDWORDCompare(
    const void * pdw1,
    const void * pdw2);

static BOOL fFindAttributeInList(
    IN LPTSTR pszAttributeName,
    IN LPTSTR pszAttributeList,
    OUT LPTSTR * ppszMatch);

static VOID CleanupAttributeList(
    IN OUT LPTSTR pszAttributeList);

static HRESULT HrDoImport(void);

static HRESULT HrReadRegistryVariables(
    IN      HANDLE          hRegistryKey,
    IN      LPTSTR          pszRegistryKey,
    IN      DWORD           cVariables,
    IN OUT  LPREG_VARIABLE  rgrvVariables);

static HRESULT HrWriteRegistryVariables(
    IN      HANDLE          hRegistryKey,
    IN      LPTSTR          pszRegistryKey,
    IN      DWORD           cVariables,
    IN OUT  LPREG_VARIABLE  rgrvVariables);

static VOID PrintResults(
    IN  HRESULT     hr,
    IN  BOOL        fEventLogOpen);

//
// Functions
//

//$--main-----------------------------------------------------------------------
//  Main function that performs directory synchronization.
// -----------------------------------------------------------------------------
int main(                                   // RETURNS: exit code
    IN int argc,                            // number of args on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    BOOL                fEventLogOpen       = FALSE;
    BOOL                fMAPIInitialized    = FALSE;
    MAPIINIT_0          MapiInit            = {0};

    DEBUGPUBLIC("main()\n");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        return _nEcFromHr(hr);

    printf( "\n" );

    // If there are no command line arguments then print the usage message
    // and exit.

    if (argc < 2)
    {
        PrintUsage();
        goto cleanup;
    }

    // Do preliminary parsing of the command line.

    hr = HrPreParseCommandLine(argc, argv);
    if (FAILED(hr))
        goto cleanup;

    // If they requested help, then print a help message and exit.

    if (fHelp)
    {
        PrintHelp();
        goto cleanup;
    }

    // Initialize event logging.

    hr = HrEventOpenLog(
        TEXT("EDK DirSync"), NULL, NULL, NULL, NULL, NULL);

    if (SUCCEEDED(hr))
    {
        fEventLogOpen = TRUE;
    }
    hr = NOERROR;

    // Initialize MAPI (for memory allocation).

    MapiInit.ulVersion = MAPI_INIT_VERSION;

    hr = MAPIInitialize(&MapiInit);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_CANNOT_INIT_MAPI,
            0,
            0);
        goto cleanup;
    }
    fMAPIInitialized = TRUE;

    // If we are using the registry, then open or create (depending on the
    // /SET flag) the registry keys where the parameters and state information
    // are kept.  Then, read the parameters and state information from the
    // registry.

    if (dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        hr = HrOpenRegistryKeys();
        if (FAILED(hr))
            goto cleanup;

        hr = HrReadRegistryData();
        if (FAILED(hr))
            goto cleanup;
    }

    // Parse the command line arguments.

    hr = HrParseCommandLine(argc, argv);
    if (FAILED(hr))
        goto cleanup;

    // If they specified the /SET flag or requested creation of a new instance
    // or key, then write the (possibly changed) values back to the registry
    // but don't do any actual directory synchronization.

    if (fSet)
    {
        hr = HrWriteInstanceData();
        if (FAILED(hr))
            goto cleanup;

        hr = HrWriteStateData();
        if (FAILED(hr))
            goto cleanup;
    }

    // If they requested to display current settings then do it and then quit.

    if (fDisplay)
    {
        PrintSettings();
        goto cleanup;
    }

    // If we are not creating a new instance or key then do the actual
    // directory synchronization.

    if (!fSet)
    {
        // Make sure we have all the information we need to do directory
        // synchronization.

        hr = HrCheckInfoCompleteness();
        if (FAILED(hr))
            goto cleanup;

        // Create the source stamp.

        STRNCPY(szSourceStamp, TEXT("EDKDirSync:"));
        STRNCAT(szSourceStamp, szInstanceName);

        // Do import

        if (dwDirection & DIRECTION_IMPORT)
        {
            hr = HrDoImport();
            if (FAILED(hr))
                goto cleanup;

            if (cObjectsImported == 1)
            {
                fprintf(stderr, "1 object imported to server %s.\n",
                    szDSA);
                fRespondedToUser = TRUE;
            }
            else
            {
                fprintf(stderr, "%d objects imported to server %s.\n",
                    cObjectsImported, szDSA);
                fRespondedToUser = TRUE;
            }
        }

        // Do export

        if (dwDirection & DIRECTION_EXPORT)
        {
            hr = HrDoExport();
            if (FAILED(hr))
                goto cleanup;

            if (cObjectsExported == 1)
            {
                fprintf(stderr, "1 object exported from server %s.\n",
                    szDSA);
                fRespondedToUser = TRUE;
            }
            else
            {
                fprintf(stderr, "%d objects exported from server %s.\n",
                    cObjectsExported, szDSA);
                fRespondedToUser = TRUE;
            }
        }
    }

cleanup:
    // Print the results of the operation.

    PrintResults(hr, fEventLogOpen);

    // Close the registry.

    if (hKeyState)
    {
        RegFlushKey(hKeyState);
        CloseHandle(hKeyState);
    }

    if (hKey)
    {
        RegFlushKey(hKey);
        CloseHandle(hKey);
    }

    // Close event logging.

    if (fEventLogOpen)
    {
        (void) HrEventCloseLog();
    }

    // De-Initialize MAPI.

    if (fMAPIInitialized)
    {
        MAPIUninitialize();
        fMAPIInitialized = FALSE;
    }

    return(_nEcFromHr(hr));
}

//$--HrPreParseCommandLine------------------------------------------------------
//  Does preliminary parsing of the command line that is needed to decide how
//  (and whether) to read data from the registry key before parsing the command
//  line for real.  It looks for the following:
//
//  /? (Help)               If present, just print a help message and no other
//                          processing takes place.
//
//  /DISPLAY                If present then display the current values of
//                          data in the registry.
//
//  <Instance Name>         Used to construct a registry key name where
//                          parameters should be stored.
//
//  /KEY=<Registry Key>     Used to specify an absolute registry key location
//                          instead of using <Instance Name>.
//
//  /SET                    Indicates that we should create a new instance in
//                          the registry but NOT do any actual directory
//                          synchronization.
//
//  Note: if neither /KEY= nor <Instance Name> is specified, then we do not
//  use the registry and all data must come from the command line.
// -----------------------------------------------------------------------------
static HRESULT HrPreParseCommandLine(       // RETURNS: HRESULT
    IN int argc,                            // number of args on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    HRESULT             hrT                 = NOERROR;
    int                 i                   = 0;
    char *              pszArgument         = NULL;
    char *              pszValue            = NULL;

    DEBUGPRIVATE("HrPreParseCommandLine()\n");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        RETURN(hr);

    // Do an initial check for /? or /HELP or /DISPLAY.
    // If found, don't do any other parsing.

    for (i = 1; i < argc; i++)
    {
        hrT = _HrExpandCommandLineArgument(
            argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
            &pszArgument, &pszValue);

        if (SUCCEEDED(hrT) && pszArgument &&
            (!_stricmp(pszArgument,ARG_HELP1) ||
             !_stricmp(pszArgument,ARG_HELP2)))
        {
            fHelp = TRUE;
            goto cleanup;
        }
    }

    // Search the command line arguments for /KEY=<Registry Key> or
    // <Instance Name>.  Use whichever we find first to build a registry
    // key name.

    for (i = 1; i < argc; i++)
    {
        hrT = _HrExpandCommandLineArgument(
            argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
            &pszArgument, &pszValue);

        if (FAILED(hrT))
        {
            hr = hrT;
            if (hr == EDK_E_NOT_FOUND)
            {
                fprintf(stderr, "ERROR: unknown command line flag: %s\n",
                    argv[i]);
                fRespondedToUser = TRUE;
                continue;
            }
            else
            {
                fprintf(stderr, "ERROR: ambiguous command line flag: %s\n",
                    argv[i]);
                fRespondedToUser = TRUE;
                goto cleanup;
            }
        }

        // Check for <Instance Name>

        if (pszArgument == NULL && pszValue != NULL)
        {
            // Make sure this isn't a duplicate parameter.

            if (dwSettingsSource != SOURCE_COMMAND_LINE)
            {
                fprintf(stderr, "ERROR: duplicate instance name or /%s: %s\n",
                    ARG_KEY, argv[i]);
                fRespondedToUser = TRUE;
                hr = HR_LOG(E_FAIL);
            }

            // Create registry key name and state key name.

            STRNCPY(szInstanceName, argv[i]);

            STRNCPY(szKey,
                "HKEY_LOCAL_MACHINE\\SOFTWARE\\MS Exchange SDK Samples\\DirSync\\");
            STRNCAT(szKey, szInstanceName);

            STRNCPY(szKeyState, szKey);
            STRNCAT(szKeyState, TEXT("\\State"));

            dwSettingsSource = SOURCE_INSTANCE;
        }

        // Check for /KEY=<Registry Key>

        else if (pszArgument && !_stricmp(pszArgument,ARG_KEY))
        {
            // Make sure they gave a value.

            if (pszValue == NULL)
            {
                fprintf(stderr, "ERROR: flag /%s requires a value\n",
                    pszArgument);
                fRespondedToUser = TRUE;
                hr = HR_LOG(E_FAIL);
            }

            // Make sure this isn't a duplicate parameter.

            if (dwSettingsSource != SOURCE_COMMAND_LINE)
            {
                fprintf(stderr, "ERROR: duplicate instance name or /%s: %s\n",
                    ARG_KEY, argv[i]);
                fRespondedToUser = TRUE;
                hr = HR_LOG(E_FAIL);
            }

            // Get registry key name.

            if (SUCCEEDED(hr))
            {
                STRNCPY(szKey, pszValue);

                STRNCPY(szKeyState, szKey);
                STRNCAT(szKeyState, TEXT("\\State"));

                dwSettingsSource = SOURCE_KEY;
            }
        }

        // Check for /SET (create a new instance)

        else if (pszArgument &&
            (!_stricmp(pszArgument,ARG_SET)))
        {
            fSet = TRUE;
        }

        // Check for /DISPLAY (display settings)

        else if (pszArgument && !_stricmp(pszArgument,ARG_DISPLAY))
        {
            fDisplay = TRUE;
        }
    }

    // If they use /SET then they must specify an instance name or use /KEY.
    // Otherwise, it's an error.

    if (SUCCEEDED(hr) && fSet && dwSettingsSource == SOURCE_COMMAND_LINE)
    {
        fprintf(stderr, "ERROR: /%s requires instance name or /%s\n",
            ARG_SET, ARG_KEY);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);
    }

cleanup:
    RETURN(hr);
}

//$--PrintUsage-----------------------------------------------------------------
//  Print information about how to use this program.
// -----------------------------------------------------------------------------
static VOID PrintUsage(void)                // RETURNS: nothing
{
    DEBUGPRIVATE("PrintUsage()\n");

    printf("USAGE: %s Instance [Flags]\n\n", ARG_PROGRAM_NAME);
    printf("  Instance       %s instance name\n", ARG_PROGRAM_NAME);
    printf("  [Flags]        Enter %s /%s for details\n", ARG_PROGRAM_NAME,
        ARG_HELP1);
    fRespondedToUser = TRUE;
}

//$--PrintHelp------------------------------------------------------------------
//  Print information about the flags.
// -----------------------------------------------------------------------------
static VOID PrintHelp(void)                 // RETURNS: nothing
{
    DEBUGPRIVATE("PrintHelp()\n");

    printf("Directory Synchronization sample.\n\n");
    printf("USAGE: %s Instance [Flags]\n\n", ARG_PROGRAM_NAME);
    printf("  Instance       %s instance name\n", ARG_PROGRAM_NAME);
    printf("  General Flags\n");
    printf("  /BASEPOINT=    DN of Directory basepoint object\n");
    printf("  /BOTH          Perform both import and export operations\n");
    printf("  /CONTAINER=    RDN of Directory container beneath BASEPOINT\n");
    printf("  /DISPLAY       Display current settings\n");
    printf("  /DSA=          Directory Service Agent name\n");
    printf("  /HELP or /?    Display help screen\n");
    printf("  /KEY=          Override default registry key\n");
    printf("  /SET           Create or update DirSync instance and registry value\n");
    printf("  /[NO]PROGRESS  Display progress dialog\n");
    printf("  /USN=          Universal Sequence Number filter\n");
    printf("  Export Flags\n");
    printf("  /EXPLIST=      Attribute list for Export\n");
    printf("  /EXPFILE=      Export data file\n");
    printf("  /[NO]EXPFULL   Perform full export (no USN filter)\n");
    printf("  /EXPORT        Perform export operation\n");
    printf("  /SERVER=       Exchange server name\n");
    printf("  /[NO]SUBS      Export contents of sub-containers\n");
    printf("  Import Flags\n");
    printf("  /IMPFILE=      Import data file\n");
//  printf("  /[NO]IMPFULL   Perform full import (replace entire container)\n");
    printf("  /IMPORT        Perform import operation\n");
    printf("  /[NO]NTCREATE  Create NT accounts for added/modified mailboxes\n");
    printf("  /[NO]NTDELETE  Delete NT accounts for deleted mailboxes\n");
    printf("  /OBJCLASS=     Type of imported objects to create (%s or %s)\n",
        OBJECT_CLASS_MAILBOX, OBJECT_CLASS_REMOTE);
    printf("  /HOMESERVER=   Default home server for created mailboxes\n");
    printf("  /[NO]MVAPPEND  Append multivalued properties\n\n");
    printf("%s uses the NT registry key HKEY_LOCAL_MACHINE\\SOFTWARE\\\n",
        ARG_PROGRAM_NAME);
    printf("MS Exchange SDK Samples\\DirSync\\<Instance> for configuration "
        "information.\n");
    fRespondedToUser = TRUE;
}

//$--PrintSettings--------------------------------------------------------------
//  Print the current settings.
// -----------------------------------------------------------------------------
static VOID PrintSettings(void)             // RETURNS: nothing
{
    DWORD iObjectUSN = 0;

    DEBUGPRIVATE("PrintSettings()\n");

    switch (dwSettingsSource)
    {
        case SOURCE_COMMAND_LINE:
            printf("Current settings from command line:\n\n");
            break;

        case SOURCE_INSTANCE:
            printf("Current settings for instance \"%s\":\n\n", szInstanceName);
            break;

        case SOURCE_KEY:
            printf("Current settings for registry key \"%s\":\n\n", szKey);
            break;

        default:
            printf("Current settings:\n\n");
            break;
    }

    printf("  Type                 = ");
    switch (dwDirection)
    {
        case DIRECTION_IMPORT:
            printf("Import\n");
            break;

        case DIRECTION_EXPORT:
            printf("Export\n");
            break;

        case DIRECTION_IMPORT | DIRECTION_EXPORT:
            printf("Import & Export\n");
            break;

        default:
            printf("Unknown\n");
            break;
    }

    printf("  DSA                  = %s\n", szDSA);
    printf("  Base Point           = %s\n", szBasePoint);
    printf("  Object Class         = %s\n", szObjClass);
    printf("  Container            = %s\n", szContainer);
    printf("  Server               = %s\n", szServer);
    printf("  Home Server          = %s\n", szHomeServer);
    printf("  Export List          = %s\n", szExportList);
    printf("  Export File          = %s\n", szExportFile);
    printf("  Import File          = %s\n", szImportFile);
    printf("  Full Export          = %s\n", fFullExport ? "Yes" : "No");
//  printf("  Full Import          = %s\n", fFullImport ? "Yes" : "No");
    printf("  Progress Dialog      = %s\n", fProgressDialog ? "Yes" : "No");
    printf("  NT Create            = %s\n", fNTCreate ? "Yes" : "No");
    printf("  NT Delete            = %s\n", fNTDelete ? "Yes" : "No");
    printf("  Sub-containers       = %s\n", fSubs ? "Yes" : "No");
    printf("  Multi-Value Append   = %s\n", fMVAppend ? "Yes" : "No");
    printf("  Last USN             = %d\n", dwLastUSN);

    printf("  Imported Object List = ");
    for (iObjectUSN = 0; iObjectUSN < cdwImportedObjectList; iObjectUSN++)
    {
        printf("%d", rgdwImportedObjectList[iObjectUSN]);
        if (iObjectUSN < (cdwImportedObjectList - 1))
            printf(", ");
    }
    printf("\n");

    fRespondedToUser = TRUE;
}

//$--HrOpenRegistryKeys---------------------------------------------------------
//  Open or create (depending on the flag fSet) two registry keys.
//  One key is where the default parameters for a given instance are stored.
//  The other is where the state information for the instance is stored that
//  tells what information was retrieved during the last dir sync, and what to
//  get next.  The state key is always a subkey of the parameter key called
//  "State".
// -----------------------------------------------------------------------------
static HRESULT HrOpenRegistryKeys(void)     // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    DWORD               dwStatus            = ERROR_SUCCESS;
    DWORD               dwDisposition       = 0;
    HKEY                hBaseKey            = NULL;
    char *              pszSubKey           = NULL;

    DEBUGPRIVATE("HrOpenRegistryKeys()\n");

    // First, get the base key handle and sub key name from the string passed
    // in /KEY or built from the instance name.

    hr = HrIdentifyRegistryRootKey(szKey, &hBaseKey, &pszSubKey);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_INVALID_REGISTRY_KEY_NAME,
            1, szKey,
            0);
        goto cleanup;
    }

    // Try to open the parameter key.

    dwStatus = RegOpenKeyEx(
        hBaseKey,
        pszSubKey,
        0L,
        KEY_ALL_ACCESS,
        (struct HKEY__ **) &hKey);

    // If the key doesn't exist, then we need to create it...

    if (dwStatus == ERROR_FILE_NOT_FOUND)
    {
        // If the user didn't already ask to create a new instance with the

// /SET flag, then ask the user whether to create a new instance.

        if (!fSet)
        {
            if (fAskCreateInstance() == FALSE)
            {
                // User said "no", so terminate.

                switch (dwSettingsSource)
                {
                    case SOURCE_KEY:
                        fprintf(stderr, "NT registry key not created.\n");
                        fRespondedToUser = TRUE;
                        break;

                    default:
                    case SOURCE_INSTANCE:
                        fprintf(stderr, "Instance not created.\n");
                        fRespondedToUser = TRUE;
                        break;
                }
                hr = E_FAIL;
                goto cleanup;
            }
            else
            {
                // User said "yes", so set the flag the same as if it had been
                // set using /SET.

                fSet = TRUE;
            }
        }
    }

    // ...otherwise, if we got some other error trying to open the parameter
    // key then log it and return...

    else if (dwStatus != ERROR_SUCCESS)
    {
        if (fSet)
        {
            EventLogMsg(
                DIRSYNC_CANNOT_CREATE_KEY,
                1, szKey,
                1, dwStatus);
        }
        else
        {
            EventLogMsg(
                DIRSYNC_CANNOT_OPEN_KEY,
                1, szKey,
                1, dwStatus);
        }
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // ...otherwise the parameter key was opened successfully, so open
    // the state key.

    else
    {
        dwStatus = RegOpenKeyEx(
            hKey,
            TEXT("State"),
            0L,
            KEY_ALL_ACCESS,
            (struct HKEY__ **) &hKeyState);
        if (dwStatus != ERROR_SUCCESS)
        {
            EventLogMsg(
                DIRSYNC_CANNOT_OPEN_KEY,
                1, szKeyState,
                1, dwStatus);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // If we've gotten to here and the keys have not been opened yet,
    // then the user must have requested that we create them.

    if (hKey == NULL || hKeyState == NULL)
    {
        // Create a new parameter key.

        if (hKey == NULL)
        {
            dwStatus = RegCreateKeyEx(
                hBaseKey,
                pszSubKey,
                0L,
                NULL,
                REG_OPTION_NON_VOLATILE,
                KEY_ALL_ACCESS,
                NULL,
                (struct HKEY__ **) &hKey,
                &dwDisposition);
            if (dwStatus != ERROR_SUCCESS)
            {
                EventLogMsg(
                    DIRSYNC_CANNOT_CREATE_KEY,
                    1, szKey,
                    1, dwStatus);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        // Create a new state key.

        if (hKeyState == NULL)
        {
            dwStatus = RegCreateKeyEx(
                hKey,
                TEXT("State"),
                0L,
                NULL,
                REG_OPTION_NON_VOLATILE,
                KEY_ALL_ACCESS,
                NULL,
                (struct HKEY__ **) &hKeyState,
                &dwDisposition);
            if (dwStatus != ERROR_SUCCESS)
            {
                EventLogMsg(
                    DIRSYNC_CANNOT_CREATE_KEY,
                    1, szKey,
                    1, dwStatus);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        // Display a message that we created the keys.

        switch (dwSettingsSource)
        {
            case SOURCE_KEY:
                fprintf(stderr, "Created new NT registry key:\n%s\n", szKey);
                fRespondedToUser = TRUE;
                break;

            default:
            case SOURCE_INSTANCE:
                fprintf(stderr,
                    "Created new NT registry key for instance %s:\n%s\n",
                    szInstanceName, szKey);
                fRespondedToUser = TRUE;
                break;
        }
    }

cleanup:
    if (FAILED(hr))
    {
        CLOSEHANDLE(hKey);
        CLOSEHANDLE(hKeyState);
    }

    RETURN(hr);
}

//$--fAskCreateInstance---------------------------------------------------------
//  Ask the user whether or not to create a new key or instance.
// -----------------------------------------------------------------------------
static BOOL fAskCreateInstance(void)        // RETURNS: TRUE=create, FALSE=don't
{
    BOOL                fReturn             = TRUE;
    TCHAR               chAnswer            = 0;

    DEBUGPRIVATE("fAskCreateInstance()\n");

    // Ask the user whether to create a new key or instance.  Keep asking until
    // we get a yes or no.

    do
    {
        if (*szInstanceName)
        {
            printf(
                "The instance %s is unknown.  "
                "Create a new instance?  (Y/N) ", szInstanceName);
        }
        else
        {
            printf(
                "The NT registry key\n"
                "%s does not exist.\n"
                "Create a new key? (Y/N) ", szKey);
        }

        chAnswer = toupper(_getche());
        printf("\n");
    }
    while (chAnswer != 'Y' && chAnswer != 'N');

    // Give a confirmation message telling whether we are creating a new
    // key/instance or not.

    if (chAnswer == 'Y')
    {
        fReturn = TRUE;
    }
    else
    {
        fReturn = FALSE;
    }

    return(fReturn);
}

//$--HrReadRegistryData---------------------------------------------------------
//  Reads the default parameters and state information from the registry.
// -----------------------------------------------------------------------------
static HRESULT HrReadRegistryData(void)     // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    DWORD               dwType              = 0;
    DWORD               cbImportedObjectList = 0;

    DEBUGPRIVATE("HrReadRegistryData()\n");

    hr = HrReadRegistryVariables(
        hKey,
        szKey,
        ARRAY_CNT(rgrvInstanceData),
        rgrvInstanceData);
    if (FAILED(hr))
        goto cleanup;

    CleanupAttributeList(szExportList);

    // Find the length of the imported object list (if the value doesn't exist
    // then assume it has zero length).

    hr = RegQueryValueEx(
        hKeyState,
        TEXT("Imported Object List"),
        NULL,
        NULL,
        NULL,
        &cbImportedObjectList);

    if (hr == ERROR_FILE_NOT_FOUND)
    {
        cbImportedObjectList = 0;
        hr = NOERROR;
    }
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_CANNOT_READ_REGISTRY_VALUE,
            2, TEXT("Imported Object List"), szKeyState,
            1, hr);
        goto cleanup;
    }

    // Set the current size of the imported object list.

    cdwImportedObjectList = cbImportedObjectList / sizeof(DWORD);

    // Allocate a buffer for the imported object list with room to grow.

    cbImportedObjectList += (IMPORTED_OBJECT_LIST_GROWTH_SIZE * sizeof(DWORD));

    hr = MAPIAllocateBuffer(
        cbImportedObjectList,
        &rgdwImportedObjectList);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }

    cdwMaxImportedObjectList = cbImportedObjectList / sizeof(DWORD);

    // Set the buffer address and size in the REG_VARIABLE structure.

    rgrvStateData[0].cbData = cbImportedObjectList;
    rgrvStateData[0].pbData = rgdwImportedObjectList;

    // Read the state data from the registry.

    hr = HrReadRegistryVariables(
        hKeyState,
        szKeyState,
        ARRAY_CNT(rgrvStateData),
        rgrvStateData);
    if (FAILED(hr))
        goto cleanup;

    // If the binary data was not an integral number of DWORD's
    // then it's an error.

    if (rgrvStateData[0].cbData % sizeof(DWORD))
    {
        CHAR szDWORDSize[20] = {0};

        _itoa(sizeof(DWORD), szDWORDSize, 10);

        EventLogMsg(
            DIRSYNC_INVALID_REGISTRY_VALUE_SIZE,
            3, TEXT("Imported Object List"), szKeyState, szDWORDSize,
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    RETURN(hr);
}

//$--HrParseCommandLine---------------------------------------------------------
//  Parse the command line arguments and put their values in the appropriate
//  variables.
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine(          // RETURNS: HRESULT
    IN int argc,                            // number of arguments on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    int                 i                   = 0;
    char *              pszArgument         = NULL;
    char *              pszValue            = NULL;

    DEBUGPRIVATE("HrParseCommandLine()\n");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        RETURN(hr);

    for (i = 1; i < argc; i++)
    {
        hr = _HrExpandCommandLineArgument(
            argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
            &pszArgument, &pszValue);

        if (FAILED(hr))
        {
            fprintf(stderr, "ERROR: ambiguous command line flag: %s\n",
            argv[i]);
            fRespondedToUser = TRUE;
            goto cleanup;
        }

        if (pszArgument)
        {
            // Put all the flags that do not take a value here.

            if (!_stricmp(pszArgument,ARG_EXPORT))
            {
                dwDirection = DIRECTION_EXPORT;
            }
            else if (!_stricmp(pszArgument,ARG_IMPORT))
            {
                dwDirection = DIRECTION_IMPORT;
            }
            else if (!_stricmp(pszArgument,ARG_BOTH))
            {
                dwDirection = DIRECTION_IMPORT | DIRECTION_EXPORT;
            }
            else if (!_stricmp(pszArgument,ARG_EXPFULL))
            {
                fFullExport = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOEXPFULL))
            {
                fFullExport = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_IMPFULL))
            {
                fFullImport = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOIMPFULL))
            {
                fFullImport = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_PROGRESS))
            {
                fProgressDialog = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOPROGRESS))
            {
                fProgressDialog = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_SUBS))
            {
                fSubs = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOSUBS))
            {
                fSubs = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_NTCREATE))
            {
                fNTCreate = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NONTCREATE))
            {
                fNTCreate = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_NTDELETE))
            {
                fNTDelete = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NONTDELETE))
            {
                fNTDelete = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_MVAPPEND))
            {
                fMVAppend = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOMVAPPEND))
            {
                fMVAppend = FALSE;
            }
            else if (
                !_stricmp(pszArgument,ARG_SET) ||
                !_stricmp(pszArgument,ARG_DISPLAY))
            {
                // We already parsed these, but keep this if statement as
                // a placeholder so it doesn't think /SET or /DISPLAY needs
                // a value.
            }

            // If we got this far we know it's a flag that requires a value,
            // so if they didn't give one then it's an error.  We know it
            // is a valid flag because pszArgument != NULL, and that means
            // the flag is in the list.

            else if (pszValue == NULL)
            {
                fprintf(stderr, "ERROR: flag /%s requires a value\n",
                    pszArgument);
                fRespondedToUser = TRUE;
                hr = HR_LOG(E_FAIL);
            }

            // Put all the flags that take a value here.

            else if (!_stricmp(pszArgument,ARG_DSA))
            {
                STRNCPY(szDSA, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_BASEPOINT))
            {
                STRNCPY(szBasePoint, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_CONTAINER))
            {
                STRNCPY(szContainer, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_SERVER))
            {
                STRNCPY(szServer, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_HOMESERVER))
            {
                STRNCPY(szHomeServer, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_EXPLIST))
            {
                STRNCPY(szExportList, pszValue);
                CleanupAttributeList(szExportList);
            }
            else if (!_stricmp(pszArgument,ARG_EXPFILE))
            {
                STRNCPY(szExportFile, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_IMPFILE))
            {
                STRNCPY(szImportFile, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_USN))
            {
                dwLastUSN = atol(pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_OBJCLASS))
            {
                ULONG iObjClass = 0;

                hr = _HrFindArrayValue(
                    pszValue,
                    rgpszObjClassValues,
                    ARRAY_CNT(rgpszObjClassValues),
                    &iObjClass);
                if (hr == EDK_E_NOT_FOUND || *pszValue == 0)
                {
                    fprintf(
                        stderr,
                        "ERROR: \"%s\" is an invalid value for flag /%s.\n",
                        pszValue,
                        ARG_OBJCLASS);
                    fRespondedToUser = TRUE;
                    hr = HR_LOG(E_FAIL);
                }
                else if (FAILED(hr))
                {
                    fprintf(stderr, "ERROR: unable to parse argument %s.\n", argv[i]);
                    fRespondedToUser = TRUE;
                    hr = HR_LOG(E_FAIL);
                }
                else
                {
                    STRNCPY(szObjClass, rgpszObjClassValues[iObjClass]);
                }
            }
        }
    }

cleanup:
    RETURN(hr);
}

//$--HrWriteInstanceData--------------------------------------------------------
//  Write the instance variables to the registry.
// -----------------------------------------------------------------------------
static HRESULT HrWriteInstanceData(void)    // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrWriteInstanceData()\n");

    hr = HrWriteRegistryVariables(
        hKey,
        szKey,
        ARRAY_CNT(rgrvInstanceData),
        rgrvInstanceData);
    if (FAILED(hr))
        goto cleanup;

cleanup:
    RETURN(hr);
}

//$--HrWriteStateData-----------------------------------------------------------
//  Write the state variables to the registry.
// -----------------------------------------------------------------------------
static HRESULT HrWriteStateData(void)       // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrWriteStateData()\n");

    rgrvStateData[0].pbData = rgdwImportedObjectList;
    rgrvStateData[0].cbData = cdwImportedObjectList * sizeof(DWORD);

    hr = HrWriteRegistryVariables(
        hKeyState,
        szKeyState,
        ARRAY_CNT(rgrvStateData),
        rgrvStateData);
    if (FAILED(hr))
        goto cleanup;

cleanup:
    RETURN(hr);
}

//$--HrCheckInfoCompleteness----------------------------------------------------
//  Make sure that we have all the information to do directory synchronization.
//  This information can come from the command line and/or the registry.
// -----------------------------------------------------------------------------
static HRESULT HrCheckInfoCompleteness(void) // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrCheckInfoCompleteness()\n");

    // If we are doing an import, then make sure the Object Class is valid.

    if (dwDirection & DIRECTION_IMPORT)
    {
        DWORD iObjClass = 0;
        BOOL fObjClassIsValid = FALSE;

        for (iObjClass = 0; iObjClass < ARRAY_CNT(rgpszObjClassValues); iObjClass++)
        {
            if (!_stricmp(szObjClass, rgpszObjClassValues[iObjClass]))
            {
                fObjClassIsValid = TRUE;
                break;
            }
        }
        if (!fObjClassIsValid)
        {
            fprintf(
                stderr,
                "ERROR: Invalid Object Class value \"%s\" in registry\n",
                szObjClass);
            hr = HR_LOG(E_FAIL);
        }
    }

    if (*szKey == 0 && dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        fprintf(stderr, "ERROR: please specify instance name or /%s\n",
            ARG_KEY);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if (dwDirection == 0)
    {
        fprintf(stderr, "ERROR: no action specified; "
            "please specify /%s, /%s or /%s.\n",
            ARG_IMPORT, ARG_EXPORT, ARG_BOTH);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);

        // Don't give any more messages if they didn't specify an action.

        goto cleanup;
    }

    if (*szDSA == 0)
    {
        fprintf(stderr, "ERROR: please specify /%s\n", ARG_DSA);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);
    }

    if (*szBasePoint == 0)
    {
        fprintf(stderr, "ERROR: please specify /%s\n", ARG_BASEPOINT);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);
    }

    if (*szContainer == 0)
    {
        fprintf(stderr, "ERROR: please specify /%s\n", ARG_CONTAINER);
        fRespondedToUser = TRUE;
        hr = HR_LOG(E_FAIL);
    }

    if (dwDirection & DIRECTION_EXPORT)
    {
        if (*szExportFile == 0)
        {
            fprintf(stderr, "ERROR: please specify /%s\n", ARG_EXPFILE);
            fRespondedToUser = TRUE;
            hr = HR_LOG(E_FAIL);
        }
    }

    if (dwDirection & DIRECTION_IMPORT)
    {
        if (*szImportFile == 0)
        {
            fprintf(stderr, "ERROR: please specify /%s\n", ARG_IMPFILE);
            fRespondedToUser = TRUE;
            hr = HR_LOG(E_FAIL);
        }
    }

cleanup:
    RETURN(hr);
}

//$--HrDoExport-----------------------------------------------------------------
//  Export to a file all objects in the DIT with USN > dwLastUSN.  After each
//  exported object, update dwLastUSN and write the updated copy to the
//  registry.
// -----------------------------------------------------------------------------
static HRESULT HrDoExport(void)             // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    HRESULT             hrT                 = NOERROR;
    BEXPORT_PARMS       BExportParms        = {0};
    DAPI_ENTRY          AttributesEntry     = {0};
    ATT_VALUE           AttributesValue     = {0};
    LPBEXPORT_PARMS     bp                  = &BExportParms;
    LPTSTR              pszExportListCopy   = NULL;
    DWORD               i                   = 0;
    LPTSTR              pszObjClass         = NULL;
    BOOL                fExportOpened       = FALSE;

    DEBUGPRIVATE("HrDoExport()\n");

    // Allocate a copy of the export list, and add the attributes we need if
    // the user didn't include them.  We are allowing 200 bytes to add the
    // extra attributes.

    hr = MAPIAllocateBuffer(strlen(szExportList) + 200, &pszExportListCopy);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }

    // DAPI requires that there be exactly one "Obj-Class", and that it
    // be the first attribute in the list.  So, we must add it to the front
    // of the string and then remove it from anywhere else.

    // Begin the copy string with "Obj-Class," + the user's export list.

    strcpy(pszExportListCopy, OBJ_CLASS ",");
    strcat(pszExportListCopy, szExportList);

    // Search the resulting string for any instances of "Obj-Class" besides
    // the one we just added to the front.  If we find any, remove them by
    // overwriting them with spaces.  The spaces and extra commas will be
    // cleaned up later when we call CleanupAttributeList().

    pszObjClass = pszExportListCopy + 1;
    while (fFindAttributeInList(OBJ_CLASS, pszObjClass, &pszObjClass))
    {
        _strnset(pszObjClass, ' ', strlen(OBJ_CLASS));
    }

    // Now add USN-Created, USN-Changed, Is-Deleted, Obj-Dist-Name
    // and Imported-From to the string if they aren't already there.

    if (!fFindAttributeInList(USN_CREATED, pszExportListCopy, NULL))
    {
        strcat(pszExportListCopy, "," USN_CREATED);
    }

    if (!fFindAttributeInList(USN_CHANGED, pszExportListCopy, NULL))
    {
        strcat(pszExportListCopy, "," USN_CHANGED);
    }

    if (!fFindAttributeInList(IS_DELETED, pszExportListCopy, NULL))
    {
        strcat(pszExportListCopy, "," IS_DELETED);
    }

    if (!fFindAttributeInList(OBJ_DIST_NAME, pszExportListCopy, NULL))
    {
        strcat(pszExportListCopy, "," OBJ_DIST_NAME);
    }

    if (!fFindAttributeInList(IMPORTED_FROM, pszExportListCopy, NULL))
    {
        strcat(pszExportListCopy, "," IMPORTED_FROM);
    }

    // Finally, get rid of extra spaces and commas in the string.

    CleanupAttributeList(pszExportListCopy);

    // Set up the structure that defines how to do the export.

    bp->dwDAPISignature = DAPI_SIGNATURE;
    bp->dwFlags =
        DAPI_SUPPRESS_COMPLETION |
        DAPI_EVENT_ALL |
        DAPI_EXPORT_MAILBOX |
        DAPI_EXPORT_HIDDEN;
    if (!fProgressDialog)
    {
        bp->dwFlags |= DAPI_SUPPRESS_PROGRESS;
    }
    if (fSubs)
    {
        bp->dwFlags |= DAPI_EXPORT_SUBTREE;
    }

    // If they set the full export flag then get all USN's.

    if (fFullExport)
    {
        dwLastUSN = 0;
    }

    // If they didn't explicitly set the full export flag but the
    // starting USN is 0, then set the full export flag.  This lets
    // us treat the first running of an incremental export the same
    // as a full export, so we won't get Delete records exported.

    else if (dwLastUSN == 0)
    {
        fFullExport = TRUE;
    }

    // Only get USN's greater than what we got last time.

    if (dwLastUSN)
    {
        bp->ulUSNBase = dwLastUSN + 1;
    }

    // Store the starting USN value so we use it to distinguish
    // adds from modifies and other things like that.

    dwStartingUSN = bp->ulUSNBase;

    // Fill in the rest of the structure.

    bp->pszHomeServer = szServer;
    bp->pszDSAName = szDSA;
    bp->pszBasePoint = szBasePoint;
    bp->pszContainer = szContainer;
    bp->chColSep = DAPI_DEFAULT_DELIM;
    bp->chQuote = DAPI_DEFAULT_QUOTE;
    bp->chMVSep = DAPI_DEFAULT_MV_SEP;
    bp->ExportCallback.pfnExportEntry = fExportCallback;
    bp->ExportCallback.ulEvalTag = TEXT_VALUE_ARRAY;
    bp->pszExportFile = szExportFile;
    bp->pAttributes = &AttributesEntry;

    AttributesEntry.unAttributes = 1;
    AttributesEntry.ulEvalTag = TEXT_LINE;
    AttributesEntry.rgEntryValues = &AttributesValue;

    AttributesValue.DapiType = DAPI_TEXT;
    AttributesValue.Value.pszValue = pszExportListCopy;
    AttributesValue.size = lstrlen(pszExportListCopy);

    // Call foreign system open export routine.

    hr = HrOpenExport(szExportFile);
    if (FAILED(hr))
    {
        goto cleanup;
    }
    fExportOpened = TRUE;

    // Export the records (call fExportCallback for each one).

    fErrorInCallback = FALSE;
    cDAPILoggedErrors += BatchExport(bp);

    if (cDAPILoggedErrors || fErrorInCallback)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Call foreign system close export routine.

    fExportOpened = FALSE;
    hr = HrCloseExport();
    if (FAILED(hr))
    {
        goto cleanup;
    }

    // If we are doing an incremental export then clear the imported
    // objects list.

    if (!fFullExport)
    {
        cdwImportedObjectList = 0;
    }

    // Update the state data in the registry (if we are using the registry).

    if (dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        hr = HrWriteStateData();
        if (FAILED(hr))
            goto cleanup;
    }

    // Log success event.

    switch (dwSettingsSource)
    {
        case SOURCE_INSTANCE:
            EventLogMsg(
                DIRSYNC_EXPORT_SUCCEEDED_INSTANCE,
                1, szInstanceName,
                0);
            break;

        case SOURCE_KEY:
            EventLogMsg(
                DIRSYNC_EXPORT_SUCCEEDED_KEY,
                1, szKey,
                0);
            break;

        default:
        case SOURCE_COMMAND_LINE:
            EventLogMsg(
                DIRSYNC_EXPORT_SUCCEEDED,
                0,
                0);
            break;
    }

cleanup:
    // Call foreign system close export routine if it's open.

    if (fExportOpened)
    {
        (void) HrCloseExport();
    }

    // Free the copy of the attribute list.

    MAPIFREEBUFFER(pszExportListCopy);

    // Free the allocated memory for the attribute names.

    MAPIFREEBUFFER(rgdsAttributes);

    RETURN(hr);
}

//$--fExportCallback------------------------------------------------------------
//  Callback function called by DAPI first to give the list of attributes,
//  then once for each record being exported.
// -----------------------------------------------------------------------------
static BOOL PASCAL fExportCallback(         // RETURNS: TRUE=continue, FALSE=stop
    IN EXP_TYPE ExportDataType,             // tells whether header or data
    IN LPVOID lpvAppDefined,                // the user's context
    IN PDAPI_ENTRY pDAPIEntry)              // array of attribute data
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("fExportCallback()\n");

    // Check parameters.

    hr = CHK_fExportCallback(
        ExportDataType,
        lpvAppDefined,
        pDAPIEntry);
    if (FAILED(hr))
        return(FALSE);

    switch (ExportDataType)
    {
        // Called at start of export operation

        case EXPORT_HEADER:
            hr = HrProcessExportHeader(pDAPIEntry);
            if (FAILED(hr))
                goto cleanup;
            break;

        // Called for each exported record.

        case EXPORT_ENTRY:
            hr = HrProcessExportEntry(pDAPIEntry);
            if (FAILED(hr))
                goto cleanup;
            break;

        // Unknown ExportDataType.

        default:
            EventLogMsg(
                DIRSYNC_INTERNAL_ERROR,
                0,
                0);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
    }

cleanup:
    if (FAILED(hr))
    {
        fErrorInCallback = TRUE;
    }

    return(SUCCEEDED(hr));

}

//$--HrProcessExportHeader------------------------------------------------------
//  Function that takes the export header and uses it to save away an allocated
//  array of attribute names.  It also locates certain attributes needed for
//  processing of the records.
// -----------------------------------------------------------------------------
static HRESULT HrProcessExportHeader(       // RETURNS: HRESULT
    IN PDAPI_ENTRY pEntry)                  // record to export
{
    HRESULT             hr                  = NOERROR;
    DWORD               i                   = 0;

    DEBUGPRIVATE("fProcessExportHeader()\n");

    // Check parameters.

    hr = CHK_HrProcessExportHeader(pEntry);
    if (FAILED(hr))
        RETURN(hr);

    // Get the number of attribute name strings.

    cdsAttributes = pEntry->unAttributes;

    // Allocate space for an array of structures representing the attributes
    // that are being sent to us via DAPI callback.

    hr = MAPIAllocateBuffer(
        cdsAttributes * sizeof(*rgdsAttributes),
        &rgdsAttributes);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }

    ZeroMemory(rgdsAttributes, cdsAttributes * sizeof(*rgdsAttributes));

    // Loop to make allocated copies of each attribute name and store them
    // in the array of structures.  Look up each attribute name to see if
    // the user requested it on the command line, and if so, set the flag.
    // Also, search for the attributes USN-Created, USN-Changed, Is-Deleted
    // and Obj-Dist-Name and store away their offsets in the array.

    iUSNCreatedAttribute = -1;
    iUSNChangedAttribute = -1;
    iIsDeletedAttribute = -1;
    iObjDistNameAttribute = -1;
    iImportedFromAttribute = -1;

    for  (i = 0; i < cdsAttributes; i++)
    {
        // Allocate a copy of the attribute name string.

        hr = MAPIAllocateMore(
            strlen(pEntry->rgEntryValues[i].Value.pszValue) + 1,
            rgdsAttributes,
            &rgdsAttributes[i].pszName);
        if (FAILED(hr))
        {
            EventLogMsg(
                DIRSYNC_ERROR,
                0,
                1, ERROR_OUTOFMEMORY);
            goto cleanup;
        }

        strcpy(
            rgdsAttributes[i].pszName,
            pEntry->rgEntryValues[i].Value.pszValue);

        // See if it's one of the special ones we're looking for.

        if (!_stricmp(rgdsAttributes[i].pszName, USN_CREATED))
        {
            iUSNCreatedAttribute = i;
        }
        else if (!_stricmp(rgdsAttributes[i].pszName, USN_CHANGED))
        {
            iUSNChangedAttribute = i;
        }
        else if (!_stricmp(rgdsAttributes[i].pszName, IS_DELETED))
        {
            iIsDeletedAttribute = i;
        }
        else if (!_stricmp(rgdsAttributes[i].pszName, OBJ_DIST_NAME))
        {
            iObjDistNameAttribute = i;
        }
        else if (!_stricmp(rgdsAttributes[i].pszName, IMPORTED_FROM))
        {
            iImportedFromAttribute = i;
        }

        // See if it was in the user's original request.

        if (fFindAttributeInList(rgdsAttributes[i].pszName, szExportList, NULL))
        {
            rgdsAttributes[i].fRequested = TRUE;
        }
    }

    // If we didn't find all the attributes we were looking for then
    // that's an error and we can't continue.

    if (iUSNCreatedAttribute == -1 ||
        iUSNChangedAttribute == -1 ||
        iIsDeletedAttribute == -1 ||
        iObjDistNameAttribute == -1 ||
        iImportedFromAttribute == -1)
    {
        EventLogMsg(
            DIRSYNC_INTERNAL_ERROR,
            0,
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    // In case of error, free all the allocated memory.

    if (FAILED(hr))
    {
        MAPIFREEBUFFER(rgdsAttributes);
    }

    RETURN(hr);
}

//$--HrProcessExportEntry-------------------------------------------------------
//  Function to process an export entry.  This routine decides whether or not
//  to write the entry, and whether it should be written as an Add, Modify, or
//  Delete.
// -----------------------------------------------------------------------------
static HRESULT HrProcessExportEntry(        // RETURNS: HRESULT
    IN PDAPI_ENTRY pDAPIEntry)              // array of attribute data
{
    HRESULT             hr                  = NOERROR;

    PATT_VALUE          pav                 = NULL;

    DWORD               dwUSNCreated        = 0;
    DWORD               dwUSNChanged        = 0;
    BOOL                fIsDeleted          = FALSE;

    BOOL                fExport             = FALSE;
    DIR_SYNC_OPERATION  eOperation          = OPERATION_ADD;

    DEBUGPRIVATE("HrProcessExportEntry()\n");

    // Check parameters.

    hr = CHK_HrProcessExportEntry(pDAPIEntry);
    if (FAILED(hr))
        RETURN(hr);

    // Get USN-Created, USN-Changed, and Is-Deleted for this object.

    pav = &pDAPIEntry->rgEntryValues[iUSNCreatedAttribute];
    if (pav->DapiType != DAPI_NO_VALUE)
    {
        dwUSNCreated = atol(pav->Value.pszValue);
    }

    pav = &pDAPIEntry->rgEntryValues[iUSNChangedAttribute];
    if (pav->DapiType != DAPI_NO_VALUE)
    {
        dwUSNChanged = atol(pav->Value.pszValue);
    }

    pav = &pDAPIEntry->rgEntryValues[iIsDeletedAttribute];
    if (pav->DapiType != DAPI_NO_VALUE && atol(pav->Value.pszValue))
    {
        fIsDeleted = TRUE;
    }

    // Decide whether to export this record, and whether the operation
    // is Add, Modify or Delete.

    if (dwUSNChanged >= dwStartingUSN)
    {
        if (fIsDeleted)
        {
            if (dwUSNCreated < dwStartingUSN && !fFullExport)
            {
                fExport = TRUE;
                eOperation = OPERATION_DELETE;
            }
        }
        else
        {
            if (dwUSNCreated < dwStartingUSN)
            {
                fExport = TRUE;
                eOperation = OPERATION_MODIFY;
            }
            else
            {
                fExport = TRUE;
                eOperation = OPERATION_ADD;
            }
        }
    }

    // If this item was imported from the foreign system then
    // don't re-export it.

    if (fExport && cdwImportedObjectList && bsearch(
        &dwUSNChanged,
        rgdwImportedObjectList,
        cdwImportedObjectList,
        sizeof(DWORD),
        nDWORDCompare))
    {
        fExport = FALSE;
    }

    // Export the record.

    if (fExport)
    {
        hr = HrWriteExport(
            pDAPIEntry,
            rgdsAttributes,
            pDAPIEntry->rgEntryValues[iObjDistNameAttribute].Value.pszValue,
            eOperation);
        if (FAILED(hr))
            goto cleanup;

        cObjectsExported++;
    }

    // If the USN of this record is greater than the highest USN
    // so far, then update the highest USN.

    if (dwSettingsSource != SOURCE_COMMAND_LINE &&
        dwUSNChanged > dwLastUSN)
    {
        dwLastUSN = dwUSNChanged;
    }

cleanup:
    RETURN(hr);
}

//$--nDWORDCompare--------------------------------------------------------------
//  Compares two DWORDS and returns n>0, n==0, n<0 depending on which is
//  larger.  This is required for bsearch.
// -----------------------------------------------------------------------------
static int __cdecl nDWORDCompare(           // RETURNS: int
    const void * pdw1,                      // first element to compare
    const void * pdw2)                      // second element to compare
{
    return( (*((int *)pdw1)) - (*((int *)pdw2)) );
}

//$--fFindAttributeInList-------------------------------------------------------
//  Sets *ppszMatch to the address of the first occurance of the attribute
//  name in the attribute list.  This routine can distinguish between a true
//  match and an attribute name that is only a subset of another attribute name.
//  Returns TRUE if a match was found, or FALSE if not.
//  If ppszMatch is null, the address of the attribute is not returned, but
//  the return value tells whether or not the attribute is present.
// -----------------------------------------------------------------------------
static BOOL fFindAttributeInList(           // RETURNS: TRUE if attribute found
    IN LPTSTR pszAttributeName,             // name of attribute to search for
    IN LPTSTR pszAttributeList,             // list of attributes
    OUT LPTSTR * ppszMatch)                 // address of match found (or NULL)
{
    HRESULT             hr                  = NOERROR;
    BOOL                fReturn             = FALSE;
    LPTSTR              psz                 = NULL;
    LPTSTR              pszFound            = NULL;
    DWORD               cchNameLength       = strlen(pszAttributeName);

    DEBUGPRIVATE("fFindAttributeInList()\n");

    // Check parameters.

    hr = CHK_fFindAttributeInList(
        pszAttributeName,
        pszAttributeList,
        ppszMatch);
    if (FAILED(hr))
        return(FALSE);

    // Find each occurance of the attribute name in the list.

    for (psz = pszAttributeList; *psz; psz++)
    {
        // If we found the substring,
        // and it is immediately preceeded by either the beginning of the list
        // or a comma,
        // and it is immediately followed by either the end of the list
        // or a comma,
        // the we have a true match.

        if (!_strnicmp(psz, pszAttributeName, cchNameLength) &&
            (psz == pszAttributeList || *(psz - 1) == ',') &&
            (psz[cchNameLength] == 0 || psz[cchNameLength] == ','))
        {
            pszFound = psz;
            break;
        }
    }

    if (ppszMatch != NULL)
    {
        *ppszMatch = pszFound;
    }

    if (pszFound)
    {
        fReturn = TRUE;
    }

    return(fReturn);
}

//$--CleanupAttributeList-------------------------------------------------------
//  Remove white space and extra commas from an attribute list.  Editing is
//  done in place.
// -----------------------------------------------------------------------------
static VOID CleanupAttributeList(           // RETURNS: nothing
    IN OUT LPTSTR pszAttributeList)         // attribute list to be cleaned up
{
    HRESULT             hr                  = NOERROR;
    TCHAR *             pSrc                = pszAttributeList;
    TCHAR *             pDst                = pszAttributeList;

    DEBUGPRIVATE("CleanupAttributeList()\n");

    // Check parameters.

    hr = CHK_CleanupAttributeList(pszAttributeList);
    if (FAILED(hr))
    {
        HR_LOG(hr);
        return;
    }

    // Go through the string and strip out bogus characters.

    while (*pSrc)
    {
        // If this char is white space, or a comma at the beginning of the
        // string, or a double comma then strip it out.

        if (isspace(*pSrc) || (*pSrc == ',' && pDst == pszAttributeList) ||
            (*pSrc == ',' && pDst > pszAttributeList && *(pDst - 1) == ','))
        {
            pSrc++;
        }

        // Otherwise copy it over.

        else
        {
            *pDst++ = *pSrc++;
        }
    }

    // If there's an extra comma at the end then get rid of it.

    if (pDst > pszAttributeList && *(pDst - 1) == ',')
    {
        --pDst;
    }

    // Null terminate the destination string.

    *pDst = 0;
}

//$--HrDoImport-----------------------------------------------------------------
//  Import objects from a file.
// -----------------------------------------------------------------------------
static HRESULT HrDoImport(void)             // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DAPI_PARMS          sDAPIParms          = {0};
    DAPI_HANDLE         hDAPISession        = NULL;
    PDAPI_EVENT         pDAPIEvent          = NULL;
    PDAPI_EVENT         pde                 = NULL;

    PDAPI_ENTRY         pAttributes         = NULL;
    PDAPI_ENTRY         pValues             = NULL;

    DIR_SYNC_OPERATION  eOperation          = 0;
    DWORD               dwWriteFlags        = 0;
    ULONG               ulNewUSN            = 0;

    BOOL                fImportOpened       = FALSE;
    BOOL                fDAPIInitialized    = FALSE;
    HANDLE              hDAPIEventSource    = NULL;

    CHAR                szTemp1[20]         = {0};

    DEBUGPRIVATE("rcDoImport()\n");

    // Log that we are beginning the import operation.

    switch (dwSettingsSource)
    {
        case SOURCE_INSTANCE:
            EventLogMsg(
                DIRSYNC_BEGIN_IMPORT_INSTANCE,
                2, szDSA, szInstanceName,
                0);
            break;

        case SOURCE_KEY:
            EventLogMsg(
                DIRSYNC_BEGIN_IMPORT_KEY,
                2, szDSA, szKey,
                0);
            break;

        default:
        case SOURCE_COMMAND_LINE:
            EventLogMsg(
                DIRSYNC_BEGIN_IMPORT,
                1, szDSA,
                0);
            break;
    }

    // Reset to count of objects imported.

    cObjectsImported = 0;

    // Get handle to report DAPI events.

    hDAPIEventSource = RegisterEventSource(NULL, TEXT("Directory Import"));
    if (hDAPIEventSource == NULL)
    {
        // Log a warning.

        EventLogMsg(
            DIRSYNC_CANNOT_REGISTER_DAPI_EVENT_SOURCE,
            0,
            1, GetLastError());
    }

    // Initialize DAPI.

    sDAPIParms.dwDAPISignature = DAPI_SIGNATURE;
    sDAPIParms.pszDSAName = szDSA;
    if (fNTCreate)
    {
        sDAPIParms.dwFlags |= DAPI_CREATE_NT_ACCOUNT;
    }
    if (fNTDelete)
    {
        sDAPIParms.dwFlags |= DAPI_DELETE_NT_ACCOUNT;
    }

    pDAPIEvent = DAPIStart(
        &hDAPISession,
        &sDAPIParms);
    if (pDAPIEvent)
    {
        if (hDAPIEventSource)
        {
            for (pde = pDAPIEvent; pde; pde = pde->pNextEvent)
            {
                (void) ReportEvent(
                    hDAPIEventSource,
                    (WORD)EVENTLOG_ERROR_TYPE,
                    0,
                    pde->dwDAPIError,
                    NULL,
                    (WORD)pde->unSubst,
                    0,
                    pde->rgpszSubst,
                    NULL);
                ++cDAPILoggedErrors;
            }
        }
        DAPIFreeMemory(pDAPIEvent);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    fDAPIInitialized = TRUE;

    // Call foreign system open import routine.

    hr = HrOpenImport(
        szImportFile,
        szObjClass,
        szSourceStamp,
        szBasePoint,
        szContainer,
        szHomeServer);
    if (FAILED(hr))
        goto cleanup;
    fImportOpened = TRUE;

    // Loop for each record in the file.

    while (TRUE)
    {
        // Import a record.

        hr = HrReadImport(
            &eOperation,
            &pAttributes,
            &pValues);
        if (FAILED(hr))
        {
            // If end of file then we're done (no error).

            if (hr == EDK_E_END_OF_FILE)
            {
                hr = NOERROR;
                break;
            }
            goto cleanup;
        }

        // Reset write flags for this loop iteration.

        dwWriteFlags = 0;

        // Set the flags based on the type of operation.

        switch (eOperation)
        {
            case OPERATION_ADD:
                dwWriteFlags |= DAPI_WRITE_CREATE;
                break;

            case OPERATION_MODIFY:
                dwWriteFlags |= DAPI_WRITE_UPDATE;
                if (!fMVAppend)
                {
                    dwWriteFlags |= DAPI_MODIFY_REPLACE_PROPERTIES;
                }
                break;

            case OPERATION_DELETE:
                dwWriteFlags |= DAPI_WRITE_DELETE;
                break;

            default:
                EventLogMsg(
                    DIRSYNC_INTERNAL_ERROR,
                    0,
                    0);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
        }

        // Write the record to the DSA.
        // (NOTE: DAPIWrite may return events even if it was successful.
        // The true indicator of a successful write is that a new USN
        // is returned.)

        ulNewUSN = 0;
        pDAPIEvent = DAPIWrite(
            hDAPISession,
            dwWriteFlags,
            pAttributes,
            pValues,
            &ulNewUSN,
            NULL,
            NULL);

        // Log any events returned by DAPIWrite.

        if (pDAPIEvent)
        {
            if (hDAPIEventSource)
            {
                for (pde = pDAPIEvent; pde; pde = pde->pNextEvent)
                {
                    (void) ReportEvent(
                        hDAPIEventSource,
                        (WORD)EVENTLOG_ERROR_TYPE,
                        0,
                        pde->dwDAPIError,
                        NULL,
                        (WORD)pde->unSubst,
                        0,
                        pde->rgpszSubst,
                        NULL);
                    ++cDAPILoggedErrors;
                }
            }
            DAPIFreeMemory(pDAPIEvent);
        }

        // If DAPIWrite succeeded...

        if (ulNewUSN != 0)
        {
            // Count this object as succesfully imported.

            cObjectsImported++;

            // Expand the imported object list list if it's full.

            if (cdwImportedObjectList >= cdwMaxImportedObjectList)
            {
                LPDWORD rgdwNew = NULL;
                DWORD cdwNew =
                cdwMaxImportedObjectList + IMPORTED_OBJECT_LIST_GROWTH_SIZE;

                // Allocate the new memory.

                hr = MAPIAllocateBuffer(
                    cdwNew * sizeof(DWORD),
                    &rgdwNew);
                if (FAILED(hr))
                {
                    EventLogMsg(
                        DIRSYNC_ERROR,
                        0,
                        1, ERROR_OUTOFMEMORY);
                    goto cleanup;
                }

                // Copy the data from the old memory to the new.

                if (cdwImportedObjectList && rgdwImportedObjectList)
                {
                    CopyMemory(
                        rgdwNew,
                        rgdwImportedObjectList,
                        cdwImportedObjectList * sizeof(DWORD));
                }

                // Free the old memory.

                MAPIFREEBUFFER(rgdwImportedObjectList);

                // Replace the old memory pointer with the new.

                rgdwImportedObjectList = rgdwNew;
                cdwMaxImportedObjectList = cdwNew;
            }

            // Add the USN of the record we wrote to the imported object list.

            rgdwImportedObjectList[cdwImportedObjectList++] = ulNewUSN;
        }

        // Free the structures that were returned from HrReadImport().

        MAPIFREEBUFFER(pAttributes);
        MAPIFREEBUFFER(pValues);
    }

    // Update the state data in the registry.

    if (dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        hr = HrWriteStateData();
        if (FAILED(hr))
            goto cleanup;
    }

    // Call foreign system close import routine.

    fImportOpened = FALSE;
    hr = HrCloseImport();
    if (FAILED(hr))
        goto cleanup;

cleanup:
    // Log the number of objects imported.

    switch (dwSettingsSource)
    {
        case SOURCE_INSTANCE:
            EventLogMsg(
                DIRSYNC_END_IMPORT_INSTANCE,
                3, _itoa(cObjectsImported,szTemp1,10), szDSA, szInstanceName,
                0);
            break;

        case SOURCE_KEY:
            EventLogMsg(
                DIRSYNC_END_IMPORT_KEY,
                3, _itoa(cObjectsImported,szTemp1,10), szDSA, szKey,
                0);
            break;

        default:
        case SOURCE_COMMAND_LINE:
            EventLogMsg(
                DIRSYNC_END_IMPORT,
                2, _itoa(cObjectsImported,szTemp1,10), szDSA,
                0);
            break;
    }

    // Free any leftover memory structures.

    MAPIFREEBUFFER(pAttributes);
    MAPIFREEBUFFER(pValues);

    // Call foreign system close import routine if it's open.

    if (fImportOpened)
    {
        (void) HrCloseImport();
    }

    // De-Initialize DAPI.

    if (fDAPIInitialized)
    {
        DAPIEnd(&hDAPISession);
    }

    // Close DAPI event logging.

    CLOSEHANDLE(hDAPIEventSource);

    RETURN(hr);
}

//$--HrReadRegistryVariables----------------------------------------------------
//  Read a group of registry values into variables as defined by the array of
//  REG_VARIABLE structures.
// -----------------------------------------------------------------------------
static HRESULT HrReadRegistryVariables(     // RETURNS: HRESULT
    IN      HANDLE          hRegistryKey,   // handle of reg key to read from
    IN      LPTSTR          pszRegistryKey, // name of registry key to read from
    IN      DWORD           cVariables,     // number of values to read
    IN OUT  LPREG_VARIABLE  rgrvVariables)  // array of structures that map
                                            // values to variables
{
    HRESULT             hr                  = NOERROR;
    DWORD               iVariable           = 0;
    DWORD               dwType              = 0;

    DEBUGPRIVATE("HrReadRegistryVariables()\n");

    hr = CHK_HrReadWriteRegistryVariables(
        hRegistryKey,
        pszRegistryKey,
        cVariables,
        rgrvVariables);
    if (FAILED(hr))
        RETURN(hr);

    if (dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        for (iVariable = 0; iVariable < cVariables; iVariable++)
        {
            hr = RegQueryValueEx(
                hRegistryKey,
                rgrvVariables[iVariable].pszValueName,
                NULL,
                &dwType,
                rgrvVariables[iVariable].pbData,
                &rgrvVariables[iVariable].cbData);
            if (hr == ERROR_FILE_NOT_FOUND)
            {
                hr = NOERROR;
            }
            else if (FAILED(hr) || rgrvVariables[iVariable].dwType != dwType)
            {
                EventLogMsg(
                    DIRSYNC_CANNOT_READ_REGISTRY_VALUE,
                    2, rgrvVariables[iVariable].pszValueName, pszRegistryKey,
                    1, hr);
                goto cleanup;
            }
            else if (dwType == REG_SZ)
            {
                LPTSTR psz = rgrvVariables[iVariable].pbData;
                DWORD cch = rgrvVariables[iVariable].cbData / sizeof(TCHAR);
                psz[cch] = 0;
            }
        }
    }

cleanup:
    RETURN(hr);
}

//$--HrWriteRegistryVariables---------------------------------------------------
//  Write a group of variables to a registry key, as defined by the array of
//  REG_VARIABLE structures.
// -----------------------------------------------------------------------------
static HRESULT HrWriteRegistryVariables(    // RETURNS: HRESULT
    IN      HANDLE          hRegistryKey,   // handle of reg key to write to
    IN      LPTSTR          pszRegistryKey, // name of registry key to write to
    IN      DWORD           cVariables,     // number of values to write
    IN OUT  LPREG_VARIABLE  rgrvVariables)  // array of structures that map
                                            // values to variables
{
    HRESULT             hr                  = NOERROR;
    DWORD               iVariable           = 0;
    DWORD               cbData              = 0;

    DEBUGPRIVATE("HrWriteRegistryVariables()\n");

    hr = CHK_HrReadWriteRegistryVariables(
        hRegistryKey,
        pszRegistryKey,
        cVariables,
        rgrvVariables);
    if (FAILED(hr))
        RETURN(hr);

    if (dwSettingsSource != SOURCE_COMMAND_LINE)
    {
        for (iVariable = 0; iVariable < cVariables; iVariable++)
        {
            switch (rgrvVariables[iVariable].dwType)
            {
                case REG_SZ:
                    cbData = strlen(rgrvVariables[iVariable].pbData);
                    break;

                default:
                    cbData = rgrvVariables[iVariable].cbData;
                    break;
            }

            hr = RegSetValueEx(
                hRegistryKey,
                rgrvVariables[iVariable].pszValueName,
                0,
                rgrvVariables[iVariable].dwType,
                rgrvVariables[iVariable].pbData,
                cbData);
            if (FAILED(hr))
            {
                EventLogMsg(
                    DIRSYNC_CANNOT_WRITE_REGISTRY_VALUE,
                    2, rgrvVariables[iVariable].pszValueName, pszRegistryKey,
                    1, hr);
                goto cleanup;
            }
        }
    }

cleanup:
    RETURN(hr);
}

//$--PrintResults---------------------------------------------------------------
//  Print the results of the directory synchronization operation.
//  This routine prints the number of errors and warnings logged,
//  and whether the operation succeeded or failed.
// -----------------------------------------------------------------------------
static VOID PrintResults(                   // RETURNS: nothing
    IN  HRESULT         hr,                 // result code from operation
    IN  BOOL            fEventLogOpen)      // TRUE if EDK event logging is open
{
    HRESULT             hrT                 = NOERROR;
    EDKEVENTCOUNT       sEventCount         = {0};

    DEBUGPRIVATE("PrintResults()\n");

    if (fEventLogOpen)
    {
        HRESULT hrT = NOERROR;

        hrT = HrEventGetCounts(&sEventCount);
        if (FAILED(hrT))
        {
            fprintf(stderr,
                "WARNING: unable to get number of errors logged.\n");
        }
        else
        {
            // Add in any errors logged by DAPI.

            sEventCount.cError += cDAPILoggedErrors;

            // Print the number of errors logged.

            if (sEventCount.cError == 1)
            {
                fprintf(stderr,
                    "ERROR: 1 error written to NT event log.\n");
                fRespondedToUser = TRUE;
            }
            else if (sEventCount.cError > 1)
            {
                fprintf(stderr,
                    "ERROR: %d errors written to NT event log.\n",
                    sEventCount.cError);
                fRespondedToUser = TRUE;
            }

            // Print the number of warnings logged.

            if (sEventCount.cWarning == 1)
            {
                fprintf(stderr,
                    "WARNING: 1 warning written to NT event log.\n");
            }
            else if (sEventCount.cWarning > 1)
            {
                fprintf(stderr,
                    "WARNING: %d warnings written to NT event log.\n",
                    sEventCount.cWarning);
            }
        }
    }

    if (!fRespondedToUser)
    {
        if (SUCCEEDED(hr))
        {
            fprintf(stderr, "Done.\n");
        }
        else
        {
            fprintf(stderr, "Operation failed.\n");
        }
    }
}


DIRSYNC.H

// --dirsync.h------------------------------------------------------------------
//
// Header file containing definitions that are used by multiple files of the
// directory synchronization sample code.
//
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------
#if !defined(_DIRSYNC_H)
#define _DIRSYNC_H

#include "dapi.h"

//
// Names of DAPI Attributes
//

#define OBJ_CLASS                           "Obj-Class"
#define USN_CHANGED                         "USN-Changed"
#define USN_CREATED                         "USN-Created"
#define IS_DELETED                          "Is-Deleted"
#define OBJ_DIST_NAME                       "Obj-Dist-Name"
#define COMMON_NAME                         "Common-Name"
#define IMPORTED_FROM                       "Imported-From"
#define HOME_SERVER                         "Home-Server"

#define OBJECT_CLASS_REMOTE                 "Remote"
#define OBJECT_CLASS_MAILBOX                "Mailbox"

//
// Defined Types
//

//$--DIR_SYNC_OPERATION---------------------------------------------------------
//  Type of operation to be performed on directory.
// -----------------------------------------------------------------------------
typedef enum _DirSyncOperation {            // (hungarian = e)
    OPERATION_ADD = 0,                      // add a user to the directory
    OPERATION_DELETE,                       // delete a user from the directory
    OPERATION_MODIFY,                       // modify a user in the directory
    OPERATION_LAST                          // all operations are less than this
} DIR_SYNC_OPERATION, *LPDIR_SYNC_OPERATION;

//
// Structures
//

//$--DIR_SYNC_ATTRIBUTE_INFO----------------------------------------------------
//  Holds information about an attribute being passed back from BIMPORT.
//  An array of these structures is passed the the user's fWriteExport()
//  routine.  Each element of this array corresponds to the element of the
//  pEntry array element with the same index.
//
//  Note: the boolean variable is stored as a DWORD for alignment purposes.
// -----------------------------------------------------------------------------
typedef struct _DirSyncAttributeInfo {      // (hungarian = ds)
    DWORD   fRequested;                     // TRUE=user requested this attribute
    LPTSTR  pszName;                        // the name of this attribute
} DIR_SYNC_ATTRIBUTE_INFO, *LPDIR_SYNC_ATTRIBUTE_INFO;

//$--REG_VARIABLE---------------------------------------------------------------
//  Structure used to map variables in memory to registry values.
//  This is used by routines which read and write data to the registry.
// -----------------------------------------------------------------------------
typedef struct _RevVariable {               // (hungarian = rv)
    LPTSTR  pszValueName;                   // name of registry value
    DWORD   dwType;                         // type of value (e.g. REG_DWORD)
    DWORD   cbData;                         // in: size of buffer, out: size of data
    LPVOID  pbData;                         // pointer to data buffer
} REG_VARIABLE, *LPREG_VARIABLE;

//
// Function Declarations
//

//$--HrOpenExport---------------------------------------------------------------
//  Function to do whatever initialization needs to be done before exporting
//  records for directory synchronization.  It should return NOERROR if
//  the initialization was successful and an error otherwise.  If this function
//  returns an error then HrWriteExport() and HrCloseExport() will not be
//  called.
// -----------------------------------------------------------------------------
HRESULT HrOpenExport(                       // RETURNS: HRESULT
    IN LPTSTR pszExportFile);               // file name from /EXPFILE

//$--HrWriteExport--------------------------------------------------------------
//  Function to export a single record for directory synchronization.  It is
//  called repeatedly: once for each record to be exported.  If this function
//  returns an error it will not be called again, and HrCloseExport() will be
//  called once to clean everything up.
// -----------------------------------------------------------------------------
HRESULT HrWriteExport(                      // RETURNS: HRESULT
    IN PDAPI_ENTRY pEntry,                  // record to export
    IN LPDIR_SYNC_ATTRIBUTE_INFO rgdsAttributeInfo,
                                            // additional information about pEntry
    IN LPTSTR pszDN,                        // distinguished name of this object
    IN DIR_SYNC_OPERATION eOperation);      // type of operation to do with object

//$--HrCloseExport--------------------------------------------------------------
//  Function to do whatever cleanup needs to be done after exporting records
//  for directory synchronization.
// -----------------------------------------------------------------------------
HRESULT HrCloseExport(void);                // RETURNS: HRESULT

//$--HrOpenImport---------------------------------------------------------------
//  Function to do whatever initialization needs to be done before importing
//  records for directory synchronization.  It should return NOERROR if
//  the initialization was successful and an error otherwise.  If this function
//  returns an error then HrReadImport() and HrCloseImport() will not be
//  called.
// -----------------------------------------------------------------------------
HRESULT HrOpenImport(                       // RETURNS: HRESULT
    IN  LPTSTR pszImportFile,               // name of file to import
    IN  LPTSTR pszObjectClass,              // default object class
    IN  LPTSTR pszSourceStamp,              // string for Imported-From attr
    IN  LPTSTR pszBasePoint,                // default base point
    IN  LPTSTR pszContainer,                // default container
    IN  LPTSTR pszHomeServer);              // default home server for new mailbox

//$--HrReadImport---------------------------------------------------------------
//  Function to import a single record for directory synchronization.  It is
//  called repeatedly: once for each record to be imported.  If this function
//  returns an error it will not be called again, and HrCloseImport() will be
//  called once to clean everything up.
//
//  When there are no more records to import, HrReadImport should return
//  EDK_E_END_OF_FILE.
// -----------------------------------------------------------------------------
HRESULT HrReadImport(                       // RETURNS: HRESULT
    OUT LPDIR_SYNC_OPERATION peOperation,   // type of operation
    OUT PDAPI_ENTRY * ppAttributes,         // list of attribute names
    OUT PDAPI_ENTRY * ppValues);            // list of attribute values2365

//$--HrCloseImport--------------------------------------------------------------
//  Function to do whatever cleanup needs to be done after importing records
//  for directory synchronization.
// -----------------------------------------------------------------------------
HRESULT HrCloseImport(void);                // RETURNS: HRESULT

#endif


DSYNCOMM.C

// --dsyncomm.c-----------------------------------------------------------------
//
//  This file contains routines that are called by the code in dirsync.c.
//  The routines in this file do the actual communication with the foreign
//  system.
//
//  The routines in this file do the directory synchronization by reading and
//  writing to a file in a format that can be used for directory exchange.
//  However, they were designed in such a way that developers can easily
//  replace them with their own routines.  So for instance, they could be
//  rewritten to communicate directly with a foreign mail system's API, or
//  to make database read/write calls.
//
//
//  This file is linked with dirsync.c, and compiles to a seperate executable
//  which is run from the command line.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dirsync.h"
#include "dsynmsg.h"
#include "dsyncomm.chk"

//
// Defined Constants
//

// Longest line that can be read from the import file.

#define MAX_IMPORT_LINE                         512

// Equates for size of array of DAPI_ENTRY's created on import,
// initial allocation and growth amount.

#define DAPI_ARRAY_ALLOCATION_INITIAL           30
#define DAPI_ARRAY_ALLOCATION_GROWTH            20

// Equates for different ways of handling duplicate attributes
// in HrAddAttributeToList().

#define DUPLICATE_ALLOW         0       // don't check for duplicates (faster)
#define DUPLICATE_DISCARD       1       // discard the duplicate
#define DUPLICATE_REPLACE       2       // replace orig w/ dup in same location
#define DUPLICATE_MULTI_VALUED  3       // make dup multi-valued part of orig
#define DUPLICATE_FAIL          4       // return error on duplicate

//
// Variables Local to the File
//

static FILE *   hExportFile                     = NULL;
static TCHAR    szExportFile[MAX_PATH+1]        = {0};
static BOOL     fDeleteExportFile               = FALSE;

static ULONG    cDAPIArrayElements              = DAPI_ARRAY_ALLOCATION_INITIAL;

static FILE *   hImportFile                     = NULL;
static TCHAR    szImportFile[MAX_PATH+1]        = {0};
static TCHAR    szObjectClass[MAX_PATH+1]       = {0};
static TCHAR    szSourceStamp[MAX_PATH+1]       = {0};
static TCHAR    szBasePoint[MAX_PATH+1]         = {0};
static TCHAR    szContainer[MAX_PATH+1]         = {0};
static TCHAR    szHomeServer[MAX_PATH+1]        = {0};

static CHAR     szImportLine[MAX_IMPORT_LINE]   = {0};
static ULONG    cchImportLine                   = 0;
static BOOL     fImportLineSaved                = FALSE;
static ULONG    nImportLineNumber               = 0;
static CHAR     szTemp1[20]                     = {0};
static CHAR     szRawDN[MAX_IMPORT_LINE]        = {0};

//
// Declarations for Local Routines
//

static HRESULT HrCreateDAPIEntry(
    OUT PDAPI_ENTRY * ppDAPIEntry);

static HRESULT HrReadImportRecordHeader(
    IN      LPTSTR pszBasePoint,
    IN      LPTSTR pszContainer,
    IN OUT  PDAPI_ENTRY pAttributes,
    IN OUT  PDAPI_ENTRY pValues,
    OUT     LPDIR_SYNC_OPERATION peOperation);

static HRESULT HrReadImportDataLines(
    IN OUT  PDAPI_ENTRY pAttributes,
    IN OUT  PDAPI_ENTRY pValues);

static HRESULT HrStripWhiteSpace(
    IN OUT  LPTSTR pszString);

static HRESULT HrSkipToNextRecordHeader(void);

static HRESULT HrReadImportLine(void);

static HRESULT HrAddAttributeToList(
    IN      LPSTR       pszAttribute,
    IN      LPSTR       pszValue,
    IN      ULONG       ulDuplicateAction,
    IN OUT  PDAPI_ENTRY pAttributes,
    IN OUT  PDAPI_ENTRY pValues);

static HRESULT HrExpandDAPIEntry(
    IN OUT  PDAPI_ENTRY pDAPIEntry);

//
// User-Specified Routines
//

//$--HrOpenExport---------------------------------------------------------------
//  Function to do whatever initialization needs to be done before exporting
//  records for directory synchronization.  It should return NOERROR if
//  the initialization was successful and an error otherwise.  If this function
//  returns an error then HrWriteExport() and HrCloseExport() will not be
//  called.
// -----------------------------------------------------------------------------
HRESULT HrOpenExport(                       // RETURNS: HRESULT
    IN LPTSTR pszExportFile)                // file name from /EXPFILE
{
    HRESULT             hr                  = NOERROR;

    DEBUGPUBLIC("HrOpenExport()\n");

    hr = CHK_HrOpenExport(pszExportFile);
    if (FAILED(hr))
        RETURN(hr);

    lstrcpy(szExportFile, pszExportFile);
    fDeleteExportFile = FALSE;

    hExportFile = fopen(szExportFile, "w");
    if (hExportFile == NULL)
    {
        EventLogMsg(
            DIRSYNC_CANNOT_CREATE_EXPORT_FILE,
            1, szExportFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrWriteExport--------------------------------------------------------------
//  Function to export a single record for directory synchronization.  It is
//  called repeatedly: once for each record to be exported.  If this function
//  returns an error it will not be called again, and HrCloseExport() will be
//  called once to clean everything up.
// -----------------------------------------------------------------------------
HRESULT HrWriteExport(                      // RETURNS: HRESULT
    IN PDAPI_ENTRY pEntry,                  // record to export
    IN LPDIR_SYNC_ATTRIBUTE_INFO rgdsAttributeInfo,
                                            // additional information about pEntry
    IN LPTSTR pszDN,                        // distinguished name of this object
    IN DIR_SYNC_OPERATION eOperation)       // type of operation to do with object
{
    HRESULT             hr                  = NOERROR;
    int                 cBytesWritten       = 0;
    DWORD               i                   = 0;
    DWORD               iValueNumber        = 0;
    DWORD               chOperation         = 0;

    DEBUGPUBLIC("HrWriteExport()\n");

    hr = CHK_HrWriteExport(
        pEntry,
        rgdsAttributeInfo,
        pszDN,
        eOperation);
    if (FAILED(hr))
        RETURN(hr);

    // Determine the letter for the operation.

    switch (eOperation)
    {
        case OPERATION_ADD:
            chOperation = 'A';
            break;

        case OPERATION_DELETE:
            chOperation = 'D';
            break;

        case OPERATION_MODIFY:
            chOperation = 'M';
            break;

        default:
            EventLogMsg(
                DIRSYNC_INTERNAL_ERROR,
                0,
                0);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
    }

    // Output the first line of the record.

    cBytesWritten = fprintf(hExportFile, "%c %s\n", chOperation, pszDN);
    if (cBytesWritten < 0)
    {
        EventLogMsg(
            DIRSYNC_CANNOT_WRITE_EXPORT_FILE,
            1, szExportFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // If the operation is not delete, then output each attribute.

    if (eOperation != OPERATION_DELETE)
    {
        for (i = 0; i < pEntry->unAttributes; i++)
        {
            // Output this attribute only if the user requested it.

            if (rgdsAttributeInfo[i].fRequested)
            {
                ATT_VALUE * pValue = &pEntry->rgEntryValues[i];

                if (pValue->pNextValue == NULL)
                {
                    // Only one instance of this value.

                    cBytesWritten = fprintf(
                        hExportFile, "- %s:%s\n",
                        rgdsAttributeInfo[i].pszName,
                        pValue->Value.pszValue ? pValue->Value.pszValue : "");
                    if (cBytesWritten < 0)
                    {
                        hr = HR_LOG(E_FAIL);
                        EventLogMsg(
                            DIRSYNC_CANNOT_WRITE_EXPORT_FILE,
                            1, szExportFile,
                            1, _doserrno);
                        goto cleanup;
                    }
                }
                else
                {
                    // Multiple instances of this value.

                    for (
                        iValueNumber = 0;
                        pValue != NULL;
                        iValueNumber++, pValue = pValue->pNextValue)
                    {
                        cBytesWritten = fprintf(
                            hExportFile,
                            "- %s[%ld]:%s\n",
                            rgdsAttributeInfo[i].pszName,
                            iValueNumber,
                            pValue->Value.pszValue ? pValue->Value.pszValue : "");
                        if (cBytesWritten < 0)
                        {
                            hr = HR_LOG(E_FAIL);
                            EventLogMsg(
                                DIRSYNC_CANNOT_WRITE_EXPORT_FILE,
                                1, szExportFile,
                                1, _doserrno);
                            goto cleanup;
                        }
                    } // endfor
                } // endif (pValue->pNextValue == NULL)
            } // endif (rgdsAttributeInfo[i].fRequested)
        } // endfor
    } // endif (eOperation != OPERATION_DELETE)

    // Put a blank line between records.

    cBytesWritten = fprintf(hExportFile, "\n");
    if (cBytesWritten < 0)
    {
        hr = HR_LOG(E_FAIL);
    }

cleanup:
    // If there was an error then set the flag to tell HrCloseExport()
    // to delete the file.

    if (FAILED(hr))
    {
        fDeleteExportFile = TRUE;
    }

    RETURN(hr);
}

//$--HrCloseExport--------------------------------------------------------------
//  Function to do whatever cleanup needs to be done after exporting records
//  for directory synchronization.
// -----------------------------------------------------------------------------
HRESULT HrCloseExport(void)                 // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    DWORD               i                   = 0;

    DEBUGPUBLIC("HrCloseExport()\n");

    // Close the export file.

    if (fclose(hExportFile) == EOF)
    {
        EventLogMsg(
            DIRSYNC_CANNOT_CLOSE_EXPORT_FILE,
            1, szExportFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
    }

    // If there was an error either on the write or on the close, then delete
    // the file.

    if (FAILED(hr) || fDeleteExportFile)
    {
        (void) remove(szExportFile);
    }

    RETURN(hr);
}

//$--HrOpenImport---------------------------------------------------------------
//  Function to do whatever initialization needs to be done before importing
//  records for directory synchronization.  It should return NOERROR if
//  the initialization was successful and an error otherwise.  If this function
//  returns an error then HrReadImport() and HrCloseImport() will not be
//  called.
// -----------------------------------------------------------------------------
HRESULT HrOpenImport(                       // RETURNS: HRESULT
    IN  LPTSTR pszImportFile,               // name of file to import
    IN  LPTSTR pszObjectClass,              // default object class
    IN  LPTSTR pszSourceStamp,              // string for Imported-From attr
    IN  LPTSTR pszBasePoint,                // default base point
    IN  LPTSTR pszContainer,                // default container
    IN  LPTSTR pszHomeServer)               // default home server for new mailbox
{
    HRESULT             hr                  = NOERROR;

    DEBUGPUBLIC("HrOpenImport()\n");

    hr = CHK_HrOpenImport(
        pszImportFile,
        pszObjectClass,
        pszSourceStamp,
        pszBasePoint,
        pszContainer,
        pszHomeServer);
    if (FAILED(hr))
        RETURN(hr);

    lstrcpy(szImportFile, pszImportFile);
    lstrcpy(szObjectClass, pszObjectClass);
    lstrcpy(szSourceStamp, pszSourceStamp);
    lstrcpy(szBasePoint, pszBasePoint);
    lstrcpy(szContainer, pszContainer);
    lstrcpy(szHomeServer, pszHomeServer);
    strcpy(szRawDN, "");

    hImportFile = fopen(szImportFile, "r");
    if (hImportFile == NULL)
    {
        EventLogMsg(
            DIRSYNC_CANNOT_OPEN_IMPORT_FILE,
            1, szImportFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    fImportLineSaved = FALSE;
    nImportLineNumber = 0;

cleanup:
    RETURN(hr);
}

//$--HrReadImport---------------------------------------------------------------
//  Function to import a single record for directory synchronization.  It is
//  called repeatedly: once for each record to be imported.  If this function
//  returns an error it will not be called again, and HrCloseImport() will be
//  called once to clean everything up.
//
//  When there are no more records to import, HrReadImport should return
//  EDK_E_END_OF_FILE.
// -----------------------------------------------------------------------------
HRESULT HrReadImport(                       // RETURNS: HRESULT
    OUT LPDIR_SYNC_OPERATION peOperation,   // type of operation
    OUT PDAPI_ENTRY *   ppAttributes,       // list of attribute names
    OUT PDAPI_ENTRY *   ppValues)           // list of attribute values
{
    HRESULT             hr                  = NOERROR;
    PDAPI_ENTRY         pAttributes         = NULL;
    PDAPI_ENTRY         pValues             = NULL;
    DIR_SYNC_OPERATION  eOperation          = 0;

    DEBUGPUBLIC("HrReadImport()\n");

    hr = CHK_HrReadImport(
        peOperation,
        ppAttributes,
        ppValues);
    if (FAILED(hr))
        RETURN(hr);

    // Loop until we create a valid record or a fatal error.

    while (TRUE)
    {
        // Free any old attribute name and value structures that
        // may be around from a previous failed attempt.

        MAPIFREEBUFFER(pAttributes);
        MAPIFREEBUFFER(pValues);

        // Create new attribute name and value structures.

        hr = HrCreateDAPIEntry(&pAttributes);
        if (FAILED(hr))
            goto cleanup;

        hr = HrCreateDAPIEntry(&pValues);
        if (FAILED(hr))
            goto cleanup;

        // Add the Obj-Class attribute to the list (this must be first!).

        hr = HrAddAttributeToList(
            OBJ_CLASS,
            szObjectClass,
            DUPLICATE_ALLOW,
            pAttributes,
            pValues);
        if (FAILED(hr))
            goto cleanup;

        // Read the first line of the record.

        hr = HrReadImportRecordHeader(
            szBasePoint,
            szContainer,
            pAttributes,
            pValues,
            &eOperation);
        if (FAILED(hr))
        {
            // If it was just a parsing error (i.e. an invalid line)
            // then skip to the next record header and try again.

            if (hr == EDK_E_PARSE)
            {
                hr = HrSkipToNextRecordHeader();
                if (FAILED(hr))
                    goto cleanup;
                continue;
            }

            goto cleanup;
        }

        // Read the other lines in this record.

        hr = HrReadImportDataLines(pAttributes, pValues);
        if (FAILED(hr))
        {
            // If it was just a parsing error (i.e. an invalid line)
            // then skip to the next record header and try again.

            if (hr == EDK_E_PARSE)
            {
                hr = HrSkipToNextRecordHeader();
                if (FAILED(hr))
                    goto cleanup;
                continue;
            }

            goto cleanup;
        }

        // Add the Imported-From source stamp.

        hr = HrAddAttributeToList(
            IMPORTED_FROM,
            szSourceStamp,
            DUPLICATE_REPLACE,
            pAttributes,
            pValues);
        if (FAILED(hr))
            goto cleanup;

        // Succeeded, so exit the loop.

        break;
    }

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(pAttributes);
        MAPIFREEBUFFER(pValues);
    }
    else
    {
        *peOperation = eOperation;
        *ppAttributes = pAttributes;
        *ppValues = pValues;
    }

    RETURN(hr);
}

//$--HrCloseImport--------------------------------------------------------------
//  Function to do whatever cleanup needs to be done after importing records
//  for directory synchronization.
// -----------------------------------------------------------------------------
HRESULT HrCloseImport(void)                 // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    DWORD               i                   = 0;

    DEBUGPUBLIC("HrCloseImport()\n");

    // Close the import file.

    if (fclose(hImportFile) == EOF)
    {
        EventLogMsg(
            DIRSYNC_CANNOT_CLOSE_IMPORT_FILE,
            1, szImportFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    RETURN(hr);
}

//
// Local Routines
//

//$--HrCreateDAPIEntry----------------------------------------------------------
//  Create a DAPI_ENTRY structure with an allocated array of ATT_VALUE
//  structures attached to it.  The number of ATT_VALUE's is determined
//  by the static variable cDAPIArrayElements.  Initially, the unAttributes
//  member of the DAPI_ENTRY structure is 0, and this will be incremented
//  each time one of the elements of the array of ATT_VALUE's is actually
//  used.
// -----------------------------------------------------------------------------
static HRESULT HrCreateDAPIEntry(           // RETURNS: HRESULT
    OUT PDAPI_ENTRY * ppDAPIEntry)          // DAPI_ENTRY structure created
{
    HRESULT             hr                  = NOERROR;
    PDAPI_ENTRY         pDAPIEntry          = NULL;

    DEBUGPRIVATE("HrCreateDAPIEntry()\n");

    hr = CHK_HrCreateDAPIEntry(
        ppDAPIEntry);
    if (FAILED(hr))
        RETURN(hr);

    hr = MAPIAllocateBuffer(sizeof(DAPI_ENTRY), &pDAPIEntry);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }
    ZeroMemory(pDAPIEntry, sizeof(DAPI_ENTRY));

    pDAPIEntry->unAttributes = 0;
    pDAPIEntry->ulEvalTag = TEXT_VALUE_ARRAY;

    hr = MAPIAllocateMore(
        cDAPIArrayElements * sizeof(ATT_VALUE),
        pDAPIEntry,
        &pDAPIEntry->rgEntryValues);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }
    ZeroMemory(
        pDAPIEntry->rgEntryValues,
        cDAPIArrayElements * sizeof(ATT_VALUE));

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(pDAPIEntry);
    }
    else
    {
        *ppDAPIEntry = pDAPIEntry;
    }

    RETURN(hr);
}

//$--HrReadImportRecordHeader---------------------------------------------------
//  Reads the first line of a record from the import file and adds its data
//  to the attribute name and attribute value arrays.  Also updates the global
//  variable szRawDN for use in error event logging if there is a parsing error.
//
//  RETURN:         NOERROR =       operation was successful
//                  EDK_E_PARSE =   error but continue parsing at next record
//                  other error =   fatal error that makes it impossible to
//                                  continue parsing the file
// -----------------------------------------------------------------------------
static HRESULT HrReadImportRecordHeader(    // RETURNS: HRESULT
    IN      LPTSTR pszBasePoint,            // default base point
    IN      LPTSTR pszContainer,            // default container
    IN OUT  PDAPI_ENTRY pAttributes,        // attribute name structure
    IN OUT  PDAPI_ENTRY pValues,            // attribute value structure
    OUT     LPDIR_SYNC_OPERATION peOperation) // operation type
{
    HRESULT             hr                  = NOERROR;
    char                chOperation         = 0;
    int                 cMatch              = 0;
    CHAR                szNormalizedDN[MAX_IMPORT_LINE] = {0};
    DIR_SYNC_OPERATION  eOperation          = 0;

    hr = CHK_HrReadImportRecordHeader(
        pszBasePoint,
        pszContainer,
        pAttributes,
        pValues,
        peOperation);
    if (FAILED(hr))
        RETURN(hr);

    DEBUGPRIVATE("HrReadImportRecordHeader()\n");

    // Clear the raw DN.

    szRawDN[0] = 0;

    // Get the next line (should be the first line of a record).

    hr = HrReadImportLine();
    if (FAILED(hr))
        goto cleanup;

    // Parse it to get the operation type and the distinguished name.

    cMatch = sscanf(szImportLine, " %c %[^\n]", &chOperation, szRawDN);
    if (cMatch != 2)
    {
        szRawDN[0] = 0;
        EventLogMsg(
            DIRSYNC_INVALID_IMPORT_LINE,
            4, szImportFile, _itoa(nImportLineNumber,szTemp1,10), szRawDN,
                szImportLine,
            0);
        hr = HR_LOG(EDK_E_PARSE);
        goto cleanup;
    }

    switch (toupper(chOperation))
    {
        case 'A':
        case 'a':
            eOperation = OPERATION_ADD;
            break;

        case 'M':
        case 'm':
            eOperation = OPERATION_MODIFY;
            break;

        case 'D':
        case 'd':
            eOperation = OPERATION_DELETE;
            break;

        default:
            szRawDN[0] = 0;
            EventLogMsg(
                DIRSYNC_INVALID_IMPORT_LINE,
                4, szImportFile, _itoa(nImportLineNumber,szTemp1,10), szRawDN,
                    szImportLine,
                0);
            hr = HR_LOG(EDK_E_PARSE);
            goto cleanup;
    }

    if (*szRawDN == 0)
    {
        EventLogMsg(
            DIRSYNC_INVALID_IMPORT_LINE,
            4, szImportFile, _itoa(nImportLineNumber,szTemp1,10), szRawDN,
                szImportLine,
            0);
        hr = HR_LOG(EDK_E_PARSE);
        goto cleanup;
    }

    // If the DN starts with "/o" then don't change it.

    if (!_strnicmp(szRawDN, "/o", 2))
    {
        strcpy(szNormalizedDN, szRawDN);
    }

    // Otherwise, normalize the DN (make sure it has /o= /ou= /cn=).

    else
    {
        // Start with "/o=<enterprise>/ou=<site>"...

        strcpy(szNormalizedDN, pszBasePoint);

        // ...[add "/cn=<container>"]...

        if (*pszContainer)
        {
            if (*pszContainer != '/')
            {
                strcat(szNormalizedDN, "/cn=");
            }
            strcat(szNormalizedDN, pszContainer);
        }

        // ...add the DN.

        if (*szRawDN != '/')
        {
            strcat(szNormalizedDN, "/cn=");
        }
        strcat(szNormalizedDN, szRawDN);
    }

    // Add the distinguished name to the list as Common-Name.

    hr = HrAddAttributeToList(
        COMMON_NAME,
        szNormalizedDN,
        DUPLICATE_ALLOW,
        pAttributes,
        pValues);
    if (FAILED(hr))
        goto cleanup;

    *peOperation = eOperation;

cleanup:
    RETURN(hr);
}

//$--HrReadImportDataLines------------------------------------------------------
//  Read the remaining data lines following the header into the attribute
//  name and attribute value arrays.
//
//  RETURN:         NOERROR =       operation was successful
//                  EDK_E_PARSE =   error but continue parsing at next record
//                  other error =   fatal error that makes it impossible to
//                                  continue parsing the file
// -----------------------------------------------------------------------------
static HRESULT HrReadImportDataLines(       // RETURNS: HRESULT
    IN OUT  PDAPI_ENTRY pAttributes,        // list of attribute names
    IN OUT  PDAPI_ENTRY pValues)            // list of attribute values
{
    HRESULT             hr                  = NOERROR;
    int                 cMatch              = 0;
    char                ch                  = 0;
    ULONG               ulDuplicateAction   = DUPLICATE_ALLOW;

    CHAR                szAttributeName[MAX_IMPORT_LINE] = {0};
    CHAR                szAttributeValue[MAX_IMPORT_LINE] = {0};
    CHAR                szCurrentObjectClass[MAX_IMPORT_LINE] = {0};

    DEBUGPRIVATE("HrReadImportDataLines()\n");

    hr = CHK_HrReadImportDataLines(
        pAttributes,
        pValues);
    if (FAILED(hr))
        RETURN(hr);

    // Make a copy of the current object class.

    strcpy(szCurrentObjectClass, szObjectClass);

    // Loop until end of file or a line that doesn't start with '-'.

    while (TRUE)
    {
        // Reset the action to perform on duplicate attribute names
        // (default is not to check for duplicates).

        ulDuplicateAction = DUPLICATE_ALLOW;

        // Get a line from the file.

        hr = HrReadImportLine();
        if (hr == EDK_E_END_OF_FILE)
        {
            hr = NOERROR;
            break;
        }
        if (FAILED(hr))
            goto cleanup;

        // If the line doesn't start with '-' then "unget" the line
        // and we're done.

        cMatch = sscanf(szImportLine, " %c", &ch);
        if (cMatch != 1 || ch != '-')
        {
            fImportLineSaved = TRUE;
            break;
        }

        // Try to match the line to a normal property template.

        cMatch = sscanf(
            szImportLine,
            " - %[-a-zA-Z] :%[^\n]",
            szAttributeName,
            szAttributeValue);

        // If there was only 1 match, then check for the special case
        // where the attribute value is an empty string.

        if (cMatch == 1)
        {
            cMatch = sscanf(
                szImportLine,
                " - %[-a-zA-Z] %[^\n]",
                szAttributeName,
                szAttributeValue);

            if (cMatch == 2 && !strcmp(szAttributeValue, ":"))
                szAttributeValue[0] = 0;
            else
                cMatch = 1;
        }

        // If that didn't work, try to match it to a multi-valued
        // property template.

        if (cMatch != 2)
        {
            cMatch = sscanf(
                szImportLine,
                " - %[-a-zA-Z] [ %*d ] :%[^\n]",
                szAttributeName,
                szAttributeValue);

            // If there was only 1 match, then check for the special case
            // where the attribute value is an empty string.

            if (cMatch == 1)
            {
                cMatch = sscanf(
                    szImportLine,
                    " - %[-a-zA-Z] [ %*d ] %[^\n]",
                    szAttributeName,
                    szAttributeValue);

                if (cMatch == 2 && !strcmp(szAttributeValue, ":"))
                    szAttributeValue[0] = 0;
                else
                    cMatch = 1;
            }

            // If we couldn't match it as either a normal property
            // template or a multi-valued property template, then
            // it's an error.

            if (cMatch != 2)
            {
                EventLogMsg(
                    DIRSYNC_INVALID_IMPORT_LINE,
                    4, szImportFile, _itoa(nImportLineNumber,szTemp1,10),
                        szRawDN, szImportLine,
                    0);
                hr = HR_LOG(EDK_E_PARSE);
                goto cleanup;
            }

            ulDuplicateAction = DUPLICATE_MULTI_VALUED;
        }

        // If it's "Obj-Class", then replace the original one and store
        // away the new value.

        if (!_stricmp(szAttributeName, OBJ_CLASS))
        {
            ulDuplicateAction = DUPLICATE_REPLACE;

            strcpy(szCurrentObjectClass, szAttributeValue);
            hr = HrStripWhiteSpace(szCurrentObjectClass);
            if (FAILED(hr))
                goto cleanup;
        }

        // Otherwise, if it's "Common-Name", then that's an error because they
        // already specified "Common-Name" in the first line of the record.

        else if (!_stricmp(szAttributeName, COMMON_NAME))

{
            EventLogMsg(
                DIRSYNC_CANNOT_IMPORT_COMMON_NAME,
                3, szImportFile, _itoa(nImportLineNumber,szTemp1,10),
                    szImportLine,
                0);
            hr = HR_LOG(EDK_E_PARSE);
            goto cleanup;
        }

        // Add the attribute name and value to the list.

        hr = HrAddAttributeToList(
            szAttributeName,
            szAttributeValue,
            ulDuplicateAction,
            pAttributes,
            pValues);
        if (FAILED(hr))
            goto cleanup;
    }

    // If the object class is "Mailbox" then add the "Home-Server" attribute,
    // unless the user already specified one.

    if (*szHomeServer &&
        !_stricmp(szCurrentObjectClass, OBJECT_CLASS_MAILBOX))
    {
        hr = HrAddAttributeToList(
            HOME_SERVER,
            szHomeServer,
            DUPLICATE_DISCARD,
            pAttributes,
            pValues);
        if (FAILED(hr))
            goto cleanup;
    }

cleanup:
    RETURN(hr);
}

//$--HrStripWhiteSpace----------------------------------------------------------
//  Strip white space from a string.
// -----------------------------------------------------------------------------
static HRESULT HrStripWhiteSpace(           // RETURNS: HRESULT
    IN OUT  LPTSTR pszString)               // string to strip white space from
{
    HRESULT             hr                  = NOERROR;
    LPTSTR              pszSrc              = pszString;
    LPTSTR              pszDst              = pszString;

    DEBUGPRIVATE("HrStripWhiteSpace()\n");

    hr = CHK_HrStripWhiteSpace(
        pszString);
    if (FAILED(hr))
        RETURN(hr);

    while (*pszSrc)
    {
        if (isspace(*pszSrc))
            pszSrc++;
        else
            *pszDst++ = *pszSrc++;
    }
    *pszDst = 0;

    RETURN(hr);
}

//$--HrSkipToNextRecordHeader---------------------------------------------------
//  Skip forward in the file to the next line that looks like a record
//  header (i.e. begins with A|M|D).  After this routine is called, the
//  next call to HrReadImportLine will return the record header.
//  This is used to resynchronize the input following a parsing error.
// -----------------------------------------------------------------------------
static HRESULT HrSkipToNextRecordHeader(void) // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    int                 ch                  = 0;
    int                 cMatch              = 0;

    DEBUGPRIVATE("HrSkipToNextRecordHeader()\n");

    // Loop to skip lines that aren't record headers.

    do
    {
        hr = HrReadImportLine();
        if (FAILED(hr))
            goto cleanup;

        cMatch = sscanf(szImportLine, " %c", &ch);
        ch = toupper(ch);
    }
    while (cMatch != 1 || (ch != 'A' && ch != 'M' && ch != 'D'));

    // "Unget" the line.

    fImportLineSaved = TRUE;

cleanup:
    RETURN(hr);
}

//$--HrReadImportLine-----------------------------------------------------------
//  Read a line from the import file into the static variables
//  szImportLine and cchImportLine.  Setting fImportLineSaved == TRUE
//  acts as an "unget", and causes the current contents of szImportLine
//  to be used the next time this routine is called.
//
//  HrReadImportLine automatically skips empty lines, or those that begin
//  with '#', signifying a comment.
//
//  Returns:    NOERROR             = success
//              EDK_E_END_OF_FILE   = end of file
//              E_FAIL              = error
// -----------------------------------------------------------------------------
static HRESULT HrReadImportLine(void)       // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    int                 ch                  = 0;
    int                 cMatch              = 0;
    char *              pszResult           = NULL;

    DEBUGPRIVATE("HrReadImportLine()\n");

    // Loop until we get a line we can use.

    do
    {
        // If we have a line saved from last time then just use that one.

        if (fImportLineSaved)
            fImportLineSaved = FALSE;

        // Otherwise read another line from the file.

        else
        {
            pszResult =
                fgets(szImportLine, sizeof(szImportLine) - 1, hImportFile);
            if (pszResult == NULL)
            {
                if (ferror(hImportFile))
                {
                    EventLogMsg(
                        DIRSYNC_CANNOT_READ_IMPORT_FILE,
                        1, szImportFile,
                        1, _doserrno);
                    hr = HR_LOG(E_FAIL);
                }
                else
                {
                    hr = EDK_E_END_OF_FILE;
                }

                goto cleanup;
            }

            // Increment the line number.

            nImportLineNumber++;

            // Strip newline from end of line.

            cchImportLine = strlen(szImportLine);
            if (cchImportLine && szImportLine[cchImportLine - 1] == '\n')
                szImportLine[cchImportLine - 1] = 0;
            else
            {
                EventLogMsg(
                    DIRSYNC_IMPORT_LINE_TOO_LONG,
                    3, szImportFile, _itoa(nImportLineNumber,szTemp1,10),
                        szImportLine,
                    0);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        // Get the first non-whitespace character in the line so we
        // can make sure it exists and isn't a comment character ('#')

        cMatch = sscanf(szImportLine, " %c", &ch);
    }
    while (cMatch != 1 || ch == '#');

cleanup:
    RETURN(hr);
}

//$-HrAddAttributeToList--------------------------------------------------------
//  Adds an attribute name and value to the attribute name and attribute
//  value arrays.
// -----------------------------------------------------------------------------
static HRESULT HrAddAttributeToList(        // RETURNS: HRESULT
    IN      LPSTR       pszAttribute,       // name of attribute to add
    IN      LPSTR       pszValue,           // value of attribute to add
    IN      ULONG       ulDuplicateAction,  // action if property already in list
    IN OUT  PDAPI_ENTRY pAttributes,        // list of attribute names
    IN OUT  PDAPI_ENTRY pValues)            // list of attribute values
{
    HRESULT             hr                  = NOERROR;
    BOOL                fIsDuplicate        = FALSE;
    int                 iDuplicate          = 0;
    PATT_VALUE          pavAttribute        = NULL;
    PATT_VALUE          pavValue            = NULL;

    DEBUGPRIVATE("HrAddAttributeToList()\n");

    hr = CHK_HrAddAttributeToList(
        pszAttribute,
        pszValue,
        ulDuplicateAction,
        pAttributes,
        pValues);
    if (FAILED(hr))
        RETURN(hr);

    // Check for duplicate attributes unless they are allowed.

    if (ulDuplicateAction != DUPLICATE_ALLOW)
    {
        // Search backwards because duplicate names for multi-valued
        // attributes are likely to be grouped together in the file.

        for (
            iDuplicate = pAttributes->unAttributes - 1;
            iDuplicate >= 0;
            iDuplicate--)
        {
            if (!_stricmp(
                pAttributes->rgEntryValues[iDuplicate].Value.pszA,
                pszAttribute))
            {
                fIsDuplicate = TRUE;
                break;
            }
        }
    }

    // Handle duplicates here.

    if (fIsDuplicate)
    {
        switch (ulDuplicateAction)
        {
            // Discard duplicate -- don't change anything.

            case DUPLICATE_DISCARD:
                pavAttribute = NULL;
                pavValue = NULL;
                break;

            // Replace duplicate -- no need to re-create the name but
            // overwrite the present value in the array.

            case DUPLICATE_REPLACE:
                pavAttribute = NULL;
                pavValue = &pValues->rgEntryValues[iDuplicate];
                break;

            // Make duplicate multi-valued part of original -- no need
            // to re-create the name but allocate a new value structure
            // and link it into the original.

            case DUPLICATE_MULTI_VALUED:
                pavAttribute = NULL;
                hr = MAPIAllocateMore(
                    sizeof(ATT_VALUE),
                    pValues,
                    &pavValue);
                if (FAILED(hr))
                {
                    EventLogMsg(
                        DIRSYNC_ERROR,
                        0,
                        1, ERROR_OUTOFMEMORY);
                    goto cleanup;
                }

                pavValue->pNextValue =
                    pValues->rgEntryValues[iDuplicate].pNextValue;
                pValues->rgEntryValues[iDuplicate].pNextValue = pavValue;
                break;

            // Fail on duplicates -- error.

            case DUPLICATE_FAIL:
                EventLogMsg(
                    DIRSYNC_DUPLICATE_IMPORT_ATTRIBUTE,
                    3, pszAttribute, szImportFile,
                        _itoa(nImportLineNumber,szTemp1,10), szImportLine,
                    0);
                hr = HR_LOG(E_FAIL);
                goto cleanup;

            // Invalid parameter.

            default:
                EventLogMsg(
                    DIRSYNC_INTERNAL_ERROR,
                    0,
                    0);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
        }
    }

    // Handle non-duplicates here.

    else
    {
        // Not a duplicate -- add both name and value to the end of
        // their respective arrays.

        // If we've run out of space in the arrays then expand them.

        if (pAttributes->unAttributes >= cDAPIArrayElements)
        {
            cDAPIArrayElements += DAPI_ARRAY_ALLOCATION_GROWTH;

            hr = HrExpandDAPIEntry(pAttributes);
            if (FAILED(hr))
                goto cleanup;

            hr = HrExpandDAPIEntry(pValues);
            if (FAILED(hr))
                goto cleanup;
        }

        pavAttribute = &pAttributes->rgEntryValues[pAttributes->unAttributes++];
        pavValue = &pValues->rgEntryValues[pValues->unAttributes++];
    }

    // Fill in the attribute name structure.

    if (pavAttribute)
    {
        ZeroMemory(pavAttribute, sizeof(ATT_VALUE));
        pavAttribute->DapiType = DAPI_STRING8;
        pavAttribute->size = strlen(pszAttribute);
        hr = MAPIAllocateMore(
            pavAttribute->size + 1,
            pAttributes,
            &pavAttribute->Value.pszA);
        if (FAILED(hr))
        {
            EventLogMsg(
                DIRSYNC_ERROR,
                0,
                1, ERROR_OUTOFMEMORY);
            goto cleanup;
        }
        strcpy(pavAttribute->Value.pszA, pszAttribute);
    }

    // Fill in the attribute value structure.

    if (pavValue)
    {
        ZeroMemory(pavValue, sizeof(ATT_VALUE));
        pavValue->DapiType = DAPI_STRING8;
        pavValue->size = strlen(pszValue);
        hr = MAPIAllocateMore(
            pavValue->size + 1,
            pValues,
            &pavValue->Value.pszA);
        if (FAILED(hr))
        {
            EventLogMsg(
                DIRSYNC_ERROR,
                0,
                1, ERROR_OUTOFMEMORY);
            goto cleanup;
        }
        strcpy(pavValue->Value.pszA, pszValue);
    }

cleanup:
    RETURN(hr);
}

//$--HrExpandDAPIEntry----------------------------------------------------------
//  Re-allocates a DAPI_ENTRY structure with a larger array.
// -----------------------------------------------------------------------------
static HRESULT HrExpandDAPIEntry(           // RETURNS: HRESULT
    IN OUT  PDAPI_ENTRY pDAPIEntry)         // DAPI_ENTRY structure
{
    HRESULT             hr                  = NOERROR;
    PATT_VALUE          pavNewBuffer        = NULL;

    DEBUGPRIVATE("HrExpandDAPIEntry()\n");

    hr = CHK_HrExpandDAPIEntry(
        pDAPIEntry);
    if (FAILED(hr))
        RETURN(hr);

    // Allocate the new (bigger) buffer.

    hr = MAPIAllocateMore(
        cDAPIArrayElements * sizeof(ATT_VALUE),
        pDAPIEntry,
        &pavNewBuffer);
    if (FAILED(hr))
    {
        EventLogMsg(
            DIRSYNC_ERROR,
            0,
            1, ERROR_OUTOFMEMORY);
        goto cleanup;
    }
    ZeroMemory(
        pavNewBuffer,
        cDAPIArrayElements * sizeof(ATT_VALUE));

    // Copy the old buffer to the new one.

    CopyMemory(
        pavNewBuffer,
        pDAPIEntry->rgEntryValues,
        pDAPIEntry->unAttributes * sizeof(ATT_VALUE));

    // Point the header to the new buffer.  (It's OK to leave the
    // old one dangling--it was allocated with MAPIAllocateMore,
    // and will be freed along with the header).

    pDAPIEntry->rgEntryValues = pavNewBuffer;

cleanup:
    RETURN(hr);
}


DSEXPORT.C

// --dsexport.c-----------------------------------------------------------------
//
//  Directory service export sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_FILE"FILE"
#define ARG_BASEPOINT"BASEPOINT"
#define ARG_CONTAINER"CONTAINER"
#define ARG_CLASSES "CLASSES"
#define ARG_DSA"DSA"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"
#define ARG_SERVER"SERVER"
#define ARG_ALL_RECIPIENTS"ALL_RECIPIENTS"
#define ARG_ONLY_BASEPOINT"ONLY_BASEPOINT"
#define ARG_DIST_LIST"DIST_LIST"
#define ARG_MAILBOX"MAILBOX"
#define ARG_REMOTE_ADDRESS"REMOTE_ADDRESS"
#define ARG_ALL_CLASSES"ALL_CLASSES"
#define ARG_HIDDEN"HIDDEN"
#define ARG_SUBTREE             "SUBTREE"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_FILE,
ARG_BASEPOINT,
ARG_CONTAINER,
    ARG_CLASSES,
ARG_DSA,
ARG_HELP1,
ARG_HELP2,
ARG_SERVER,
ARG_ALL_RECIPIENTS,
ARG_ONLY_BASEPOINT,
ARG_DIST_LIST,
ARG_MAILBOX,
ARG_REMOTE_ADDRESS,
    ARG_ALL_CLASSES,
    ARG_HIDDEN,
    ARG_SUBTREE,
};

//
// Variables For Command Line Arguments
//

char    szExportFile[MAX_PATH+1]= {0};
char    szDsaName[MAX_PATH+1]= {0};
char    szServerName[MAX_PATH+1]= {0};
char    szBasePoint[MAX_PATH+1]    = {0};
char    szParentContainer[MAX_PATH+1]= {0};
DWORD   ControlFlags= 0;
ULONG   cClasses                        = 0;
LPSTR*  lppszClasses                    = NULL;

//
// Other Variables
//

BOOL fDisplayedHelp    = FALSE;
DWORD cLoggedErrors    = 0;

//
// Function Declarations
//

static HRESULT HrParseCommandLine(
    IN int argc,
    IN char *argv[]);

static VOID ShowUsage(
void);

static VOID ShowHelp(
void);

static HRESULT HrDoBatchExport(
void);

//
// Functions
//

//$--main-----------------------------------------------------------------------
//  Main function that performs directory export.
// -----------------------------------------------------------------------------
int main(// RETURNS: exit code
IN int argc,// number of arguments on command line
IN char *argv[])// array of command line arguments
{
    HRESULT hr    = NOERROR;
    BOOL    IsMAPIInit  = FALSE;

DEBUGPUBLIC("main()\n");

    printf( "\n" );

    hr = MAPIInitialize(NULL);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    IsMAPIInit = TRUE;

    // Get export parameters from the command line.

    hr = HrParseCommandLine(argc, argv);
    if (FAILED(hr))
    {
        goto cleanup;
    }

    // Do batch export

    hr = HrDoBatchExport();
    if (FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if(IsMAPIInit == TRUE)
    {
        MAPIUninitialize();
    }

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
fprintf(stderr, "Export operation completed successfully.\n");
}

// Error completion.

else if (cLoggedErrors == 1)
{
fprintf(stderr, "ERROR: 1 error written to NT Event Log.\n");
}
else if (cLoggedErrors > 1)
{
fprintf(stderr, "ERROR: %d errors written to NT Event Log.\n",
cLoggedErrors);
}

    MAPIFREEBUFFER(lppszClasses);

return _nEcFromHr(hr);
}

//$--HrParseCommandLine---------------------------------------------------------
//  Read export configuration from command line.
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine(// RETURNS: HRESULT
    IN int argc,// number of arguments on command line
    IN char *argv[])// array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;
    LPTSTR      lpszSep         = "%\0";

DEBUGPRIVATE("HrParseCommandLine()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!_stricmp(pszArgument,ARG_HELP1) ||
!_stricmp(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;
if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

if (pszArgument != NULL && pszValue == NULL)
{
// ALL_RECIPIENTS

if (!_stricmp(pszArgument,ARG_ALL_RECIPIENTS))
{
ControlFlags |= DAPI_EXPORT_RECIPIENTS;
}

// ONLY_BASEPOINT

else if (!_stricmp(pszArgument,ARG_ONLY_BASEPOINT))
{
ControlFlags |= DAPI_EXPORT_BASEPOINT_ONLY;
}

// DIST_LIST

else if (!_stricmp(pszArgument,ARG_DIST_LIST))
{
ControlFlags |= DAPI_EXPORT_DIST_LIST;
}

// MAILBOX

else if (!_stricmp(pszArgument,ARG_MAILBOX))
{
ControlFlags |= DAPI_EXPORT_MAILBOX;
}

// REMOTE_ADDRESS

else if (!_stricmp(pszArgument,ARG_REMOTE_ADDRESS))
{
ControlFlags |= DAPI_EXPORT_CUSTOM;
}

// ALL_CLASSES

else if (!_stricmp(pszArgument,ARG_ALL_CLASSES))
{
ControlFlags |= DAPI_EXPORT_ALL_CLASSES;
}

// HIDDEN

else if (!_stricmp(pszArgument,ARG_HIDDEN))
{
ControlFlags |= DAPI_EXPORT_HIDDEN;
}

// SUBTREE

else if (!_stricmp(pszArgument,ARG_SUBTREE))
{
ControlFlags |= DAPI_EXPORT_SUBTREE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{
if (!_stricmp(pszArgument,ARG_DSA))
{
strncpy(szDsaName, pszValue, MAX_PATH);
                szDsaName[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_SERVER))
{
strncpy(szServerName, pszValue, MAX_PATH);
                szServerName[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_FILE))
{
strncpy(szExportFile, pszValue, MAX_PATH);
                szExportFile[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_BASEPOINT))
{
strncpy(szBasePoint, pszValue, MAX_PATH);
                szBasePoint[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_CONTAINER))
{
strncpy(szParentContainer, pszValue, MAX_PATH);
                szParentContainer[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_CLASSES))
{

                hr = HrStrTokAll(
                    pszValue,
                    lpszSep,
                    &cClasses,
                    &lppszClasses);

                if(FAILED(hr))
                {
    fprintf(stderr, "ERROR: syntax error in /%s\n",
    pszArgument);
                    goto cleanup;
                }
            }

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
if (*szDsaName == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_DSA);
hr = HR_LOG(E_FAIL);
}
if (*szExportFile == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_FILE);
hr = HR_LOG(E_FAIL);
}
}

cleanup:
    RETURN(hr);
}

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: DSEXPORT [Flags]\n\n");
printf("  [Flags]        Enter DSEXPORT /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Directory Service Export sample.\n\n");
printf("USAGE: DSEXPORT [Flags]\n\n");
printf("  /FILE=               Name of export file\n");
printf("  /SERVER=             Exchange server name\n");
printf("  /DSA=                Directory Service Agent name\n");
printf("  /BASEPOINT=          DN of Directory basepoint object\n");
printf("  /CONTAINER=          RDN of Directory container beneath "
"BASEPOINT\n");
printf("  /CLASSES=            Object classes (separated by '%%')\n");
printf("  /HELP or /?          Display help screen\n");
printf("  /ONLY_BASEPOINT      Export only the BASEPOINT object\n");
printf("  /ALL_RECIPIENTS      Export all recipients\n");
printf("  /DIST_LIST           Export distribution list recipients\n");
printf("  /MAILBOX             Export mailbox recipients\n");
printf("  /REMOTE_ADDRESS      Export remote address recipients\n");
printf("  /ALL_CLASSES         Export all classes\n");
printf("  /HIDDEN              Export hidden objects of the selected "
    "classes\n");
printf("  /SUBTREE             Export subtree\n");

fDisplayedHelp = TRUE;
}

//$--DoBatchExport--------------------------------------------------------------
//  Do batch export of directory objects.
// -----------------------------------------------------------------------------
static HRESULT HrDoBatchExport(void)// RETURNS: HRESULT
{
HRESULT         hr              = NOERROR;
BEXPORT_PARMS BexportParms    = {0};
LPBEXPORT_PARMS lpBexportParms  = {0};

DEBUGPRIVATE("HrDoBatchExport()\n");

lpBexportParms = &BexportParms;
lpBexportParms->dwDAPISignature = DAPI_SIGNATURE;
lpBexportParms->dwFlags = ControlFlags | DAPI_EVENT_ALL;
lpBexportParms->pszExportFile = szExportFile;
lpBexportParms->pszBasePoint = szBasePoint;

    if (*szParentContainer)
    {
    lpBexportParms->pszContainer = szParentContainer;
    }

    if (*szDsaName)
    {
    lpBexportParms->pszDSAName = szDsaName;
    }

    if (*szServerName)
    {
    lpBexportParms->pszHomeServer = szServerName;
    }

    if(lppszClasses != NULL)
    {
    lpBexportParms->rgpszClasses = lppszClasses;
    }

lpBexportParms->chColSep = DAPI_DEFAULT_DELIM;
lpBexportParms->chQuote = DAPI_DEFAULT_QUOTE;
lpBexportParms->chMVSep = DAPI_DEFAULT_MV_SEP;

cLoggedErrors = BatchExport(lpBexportParms);
if (cLoggedErrors)
{
hr = HR_LOG(E_FAIL);
}

    RETURN(hr);
}


DSIMPORT.C

// --dsimport.c-----------------------------------------------------------------
//
//  Directory service import sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_FILE"FILE"
#define ARG_BASEPOINT"BASEPOINT"
#define ARG_CONTAINER"CONTAINER"
#define ARG_DSA"DSA"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_FILE,
ARG_BASEPOINT,
ARG_CONTAINER,
ARG_DSA,
ARG_HELP1,
ARG_HELP2,
};

//
// Variables For Command Line Arguments
//

char szDsaName[MAX_PATH+1]= {0};
char szBasePoint[MAX_PATH+1]= {0};
char szParentContainer[MAX_PATH+1]= {0};
char szImportFile[MAX_PATH+1]= {0};

//
// Other Variables
//

BOOL fDisplayedHelp= FALSE;
DWORD cLoggedErrors= 0;

//
// Function Declarations
//

static HRESULT HrParseCommandLine(
    int argc,
    char *argv[]);

static VOID ShowUsage(
void);

static VOID ShowHelp(
void);

static HRESULT HrDoBatchImport(
void);

//
// Functions
//

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(// RETURNS: exit code
int argc,// number of arguments on command line
char *argv[])// array of command line arguments
{
    HRESULThr= NOERROR;

DEBUGPUBLIC("main()\n");

    printf( "\n" );

    // Get import parameters from the command line.

    hr = HrParseCommandLine(argc, argv);
    if (FAILED(hr))
    {
        goto cleanup;
    }

    // Do batch import

    hr = HrDoBatchImport();
    if (FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
fprintf(stderr, "Import operation completed successfully.\n");
}

// Error completion.

else if (cLoggedErrors == 1)
{
fprintf(stderr, "ERROR: 1 error written to NT Event Log.\n");
}
else if (cLoggedErrors > 1)
{
fprintf(stderr, "ERROR: %d errors written to NT Event Log.\n",
cLoggedErrors);
}

return _nEcFromHr(hr);
}

//$--HrParseCommandLine---------------------------------------------------------
//  Read import configuration from command line.
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine(// RETURNS: HRESULT
    int argc,// number of arguments on command line
    char *argv[])// array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrParseCommandLine()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!_stricmp(pszArgument,ARG_HELP1) ||
!_stricmp(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
&pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;
if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{
if (!_stricmp(pszArgument,ARG_DSA))
{
strncpy(szDsaName, pszValue, MAX_PATH);
                szDsaName[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_FILE))
{
strncpy(szImportFile, pszValue, MAX_PATH);
                szImportFile[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_BASEPOINT))
{
strncpy(szBasePoint, pszValue, MAX_PATH);
                szBasePoint[MAX_PATH] = 0;
}
else if (!_stricmp(pszArgument,ARG_CONTAINER))
{
strncpy(szParentContainer, pszValue, MAX_PATH);
                szParentContainer[MAX_PATH] = 0;
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
if (*szDsaName == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_DSA);
hr = HR_LOG(E_FAIL);
}
if (*szImportFile == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_FILE);
hr = HR_LOG(E_FAIL);
}
}

cleanup:
    RETURN(hr);
}

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: DSIMPORT [Flags]\n\n");
printf("  [Flags]        Enter DSIMPORT /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Directory Service Import sample.\n\n");
printf("USAGE: DSIMPORT [Flags]\n\n");
printf("  /FILE=               Name of import file\n");
printf("  /DSA=                Directory Service Agent name\n");
printf("  /BASEPOINT=          DN of Directory basepoint object\n");
printf("  /CONTAINER=          RDN of Directory container beneath "
"BASEPOINT\n");
printf("  /HELP or /?          Display help screen\n");

fDisplayedHelp = TRUE;
}

//$--HrDoBatchImport------------------------------------------------------------
//  Do batch import of directory objects.
// -----------------------------------------------------------------------------
static HRESULT HrDoBatchImport(void)// RETURNS: HRESULT
{
HRESULT         hr              = NOERROR;
BIMPORT_PARMS BimportParms    = {0};
LPBIMPORT_PARMS lpBimportParms  = &BimportParms;

DEBUGPRIVATE("HrDoBatchImport()\n");

lpBimportParms->dwDAPISignature = DAPI_SIGNATURE;
lpBimportParms->dwFlags            =
        DAPI_RESTRICT_ACCESS |
DAPI_EVENT_ALL;

lpBimportParms->pszImportFile = szImportFile;
lpBimportParms->pszBasePoint = szBasePoint;
lpBimportParms->pszContainer     = szParentContainer;
lpBimportParms->pszNTDomain= NULL;

    if(*szDsaName)
    {
    lpBimportParms->pszDSAName = szDsaName;
    }

lpBimportParms->chColSep = DAPI_DEFAULT_DELIM;
lpBimportParms->chQuote = DAPI_DEFAULT_QUOTE;
lpBimportParms->chMVSep = DAPI_DEFAULT_MV_SEP;

cLoggedErrors = BatchImport(lpBimportParms);
if (cLoggedErrors)
{
hr = HR_LOG(E_FAIL);
}

    RETURN(hr);
}


ADDRINST.C

// --addrinst.c-----------------------------------------------------------------
//
//  Address Type installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_SITEDN"SITEDN"
#define ARG_NAME"NAME"
#define ARG_MACHINE         "MACHINE"
#define ARG_TYPE"TYPE"
#define ARG_SERVER"SERVER"
#define ARG_PROXYDLL        "PROXYDLL"
#define ARG_GWPROXY         "GWPROXY"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
ARG_NAME,
ARG_MACHINE,
ARG_TYPE,
ARG_SERVER,
ARG_PROXYDLL,
    ARG_GWPROXY,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char   szSiteDN[MAX_PATH+1]             = {0};
char   szDisplayName[MAX_PATH+1]        = {0};
char   szMachineType[MAX_PATH+1]        = {0};
char   szAddressType[MAX_PATH+1]        = {0};
char   szProxygenDll[MAX_PATH+1]        = {0};
char   szFileName[MAX_PATH+1]           = {0};
char   szServer[MAX_PATH+1]= {0};
char   szGatewayProxy[MAX_PATH+1]       = {0};
BOOL   fDisplayedHelp= FALSE;
BOOL   fDeleteAddrType                  = FALSE;
LPVOID lpVersionInfo                    = NULL;
DWORD  dwFileVersionMS                  = 0;
DWORD  dwFileVersionLS                  = 0;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: ADDRINST [Flags]\n\n");
printf("  [Flags]        Enter ADDRINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Address Type Installation sample.\n\n");
printf("USAGE: ADDRINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Address type site distinguished name\n");
printf("  /NAME=               Address type display name\n");
printf("  /MACHINE=            Proxy generator DLL machine type\n");
printf("  /TYPE=               Address type\n");
printf("  /PROXYDLL=           Proxy generator DLL (include full path)\n");
printf("  /SERVER=             Server computer name\n");
printf("  /GWPROXY=            Gateway site proxy\n");
printf("\n");
printf("  Optional Flags\n");
    printf("  /DELETE              Delete address type\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULT          hr                 = NOERROR;
    HRESULT          hrT                = NOERROR;
char *           pszArgument        = NULL;
char *           pszValue    = NULL;
    int              i    = 0;
    UINT             dwBytes            = 0;
    LPVOID           lpBuffer           = 0;
    VS_FIXEDFILEINFO *lpvsFixedFileInfo = 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteAddrType = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_MACHINE))
{
strncpy(szMachineType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_TYPE))
{
strncpy(szAddressType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_PROXYDLL))
{
strncpy(szProxygenDll, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_GWPROXY))
{
strncpy(szGatewayProxy, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD dwService      = 0;
            DWORD dwCurrentState = 0;

            hr = HrGetExchangeStatus(
                szServer,
                &dwService,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(stderr, "ERROR: please start Exchange on %s\n",szServer);
    hr = HR_LOG(E_FAIL);
            }
        }

    if (szSiteDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
hr = HR_LOG(E_FAIL);
}

    if (szAddressType[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_TYPE);
hr = HR_LOG(E_FAIL);
}

    if (szMachineType[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_MACHINE);
hr = HR_LOG(E_FAIL);
}

    if (szGatewayProxy[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_GWPROXY);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteAddrType == FALSE)
        {
        if (szDisplayName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_NAME);
    hr = HR_LOG(E_FAIL);
    }

        if (szProxygenDll[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_PROXYDLL);
    hr = HR_LOG(E_FAIL);
    }
            else
            {
                DWORD dwAttrib = 0;

                dwAttrib = GetFileAttributes(szProxygenDll);

                if(dwAttrib == 0xFFFFFFFF)
                {
        fprintf(stderr,
        "ERROR: please specify an existing file for /%s\n",
        ARG_PROXYDLL);

        hr = HR_LOG(E_FAIL);
                }
            }
        }
}

    if(SUCCEEDED(hr))
    {
        //
        // Get proxy generation DLL file name
        //

    strncpy(szFileName,
        _GetFileNameFromFullPath((LPTSTR)szProxygenDll), MAX_PATH);

        //
        // Get proxy generation DLL file version
        //

        hr = HrGetFileVersionInfo(
            szProxygenDll,
            &lpVersionInfo);

        if(FAILED(hr))
    {
    fprintf(stderr, "ERROR: version unavailable [%08lx] in /%s\n",
                hr,
        ARG_PROXYDLL);

    hr = HR_LOG(E_FAIL);
    }
        else
        {
            BOOL fRet = FALSE;

            fRet = VerQueryValue(lpVersionInfo,
                          TEXT("\\"),
                          &lpBuffer,
                          &dwBytes);

            if(fRet == FALSE)
            {
        fprintf(stderr, "ERROR: version resource is invalid in /%s\n",
            ARG_PROXYDLL);

        hr = HR_LOG(E_FAIL);
            }
            else
            {
                lpvsFixedFileInfo = (VS_FIXEDFILEINFO *)lpBuffer;

                dwFileVersionMS = lpvsFixedFileInfo->dwFileVersionMS;
                dwFileVersionLS = lpvsFixedFileInfo->dwFileVersionLS;
            }
        }
    }

    hrT = HrAdminProgramExists();

    if(FAILED(hrT))
    {
fprintf(stderr, "ERROR: install the Exchange administrator program\n");
hr = HR_LOG(E_FAIL);
    }

cleanup:

    GLOBALFREE(lpVersionInfo);

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr                 = NOERROR;
    FILE    *fp                = NULL;
    BOOL    fErrMsg            = TRUE;
    DWORD   dwOldFileVersionMS = 0;
    DWORD   dwOldFileVersionLS = 0;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteAddrType == FALSE)
    {
        hr = HrAddressTypeExists(
            szServer,
            szSiteDN,
            szAddressType,
            szMachineType);

        if(SUCCEEDED(hr))
        {
            hr = HrGetAddressTypeVersion(
                szServer,
                szSiteDN,
                szAddressType,
                szMachineType,
                &dwOldFileVersionMS,
                &dwOldFileVersionLS);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: file version is missing in %s address type\n",
            szAddressType);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if((dwOldFileVersionMS >  dwFileVersionMS) ||
              ((dwOldFileVersionMS == dwFileVersionMS) &&
               (dwOldFileVersionLS >  dwFileVersionLS)))
            {
        fprintf(stderr,
            "WARNING: installing older version of %s address type\n",
            szAddressType);
            }

            hr = HrRemoveAddressType(
                szServer,
                szSiteDN,
                szAddressType,
                szMachineType);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: unable to remove %s address type\n",
            szAddressType);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        hr = HrInstallGatewayProxy(
            szServer,
            szSiteDN,
            szGatewayProxy);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "WARNING: unable to add site proxy for %s address type\n",
        szAddressType);
        }

        hr = HrInstallAddressType(
            szServer,
            szSiteDN,
            szDisplayName,
            szAddressType,
            dwFileVersionMS,
            dwFileVersionLS,
            szFileName,
            szMachineType);
    }
    else
    {
        hr = HrAddressTypeExists(
            szServer,
            szSiteDN,
            szAddressType,
            szMachineType);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "ERROR: the %s address type does not exist\n",
        szAddressType);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrRemoveGatewayProxy(
            szServer,
            szSiteDN,
            szGatewayProxy);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "WARNING: unable to remove site proxy for %s address type\n",
        szAddressType);
        }

        hr = HrRemoveAddressType(
            szServer,
            szSiteDN,
            szAddressType,
            szMachineType);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteAddrType == FALSE)
        {
    fprintf(stderr, "Address type installed successfully.\n");
        }
        else
        {
    fprintf(stderr, "Address type deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: error(s) written to NT Event Log.\n");
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


EXTNINST.C

// --extninst.c-----------------------------------------------------------------
//
//  Admin extension installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_SITEDN"SITEDN"
#define ARG_DISPLAY_NAME"DISPLAY_NAME"
#define ARG_MACHINE         "MACHINE"
#define ARG_NAME            "NAME"
#define ARG_SERVER"SERVER"
#define ARG_EXTDLL          "EXTDLL"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
ARG_DISPLAY_NAME,
ARG_MACHINE,
ARG_NAME,
ARG_SERVER,
ARG_EXTDLL,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char   szSiteDN[MAX_PATH+1]             = {0};
char   szDisplayName[MAX_PATH+1]        = {0};
char   szMachineType[MAX_PATH+1]        = {0};
char   szExtensionName[MAX_PATH+1]      = {0};
char   szExtensionDll[MAX_PATH+1]       = {0};
char   szFileName[MAX_PATH+1]           = {0};
char   szServer[MAX_PATH+1]= {0};
BOOL   fDisplayedHelp= FALSE;
BOOL   fDeleteExtension                 = FALSE;
LPVOID lpVersionInfo                    = NULL;
DWORD  dwFileVersionMS                  = 0;
DWORD  dwFileVersionLS                  = 0;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: EXTNINST [Flags]\n\n");
printf("  [Flags]        Enter EXTNINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Admin Extension Installation sample.\n\n");
printf("USAGE: EXTNINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Admin extension site distinguished name\n");
printf("  /DISPLAY_NAME=       Admin extension display name\n");
printf("  /MACHINE=            Admin extension DLL machine type\n");
printf("  /NAME=               Admin extension name\n");
printf("  /EXTDLL=             Admin extension DLL (include full path)\n");
printf("  /SERVER=             Server computer name\n");
printf("\n");
printf("  Optional Flags\n");
    printf("  /DELETE              Delete admin extension\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULT          hr                 = NOERROR;
    HRESULT          hrT                = NOERROR;
char *           pszArgument        = NULL;
char *           pszValue    = NULL;
    int              i    = 0;
    UINT             dwBytes            = 0;
    LPVOID           lpBuffer           = 0;
    VS_FIXEDFILEINFO *lpvsFixedFileInfo = {0};

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteExtension = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DISPLAY_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_MACHINE))
{
strncpy(szMachineType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_NAME))
{
strncpy(szExtensionName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXTDLL))
{
strncpy(szExtensionDll, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD dwService      = 0;
            DWORD dwCurrentState = 0;

            hr = HrGetExchangeStatus(
                szServer,
                &dwService,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(stderr, "ERROR: please start Exchange on %s\n",szServer);
    hr = HR_LOG(E_FAIL);
            }
        }

    if (szSiteDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
hr = HR_LOG(E_FAIL);
}

    if (szExtensionName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_NAME);
hr = HR_LOG(E_FAIL);
}

    if (szMachineType[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_MACHINE);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteExtension == FALSE)
        {
        if (szDisplayName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_DISPLAY_NAME);
    hr = HR_LOG(E_FAIL);
    }

        if (szExtensionDll[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_EXTDLL);
    hr = HR_LOG(E_FAIL);
    }
            else
            {
                DWORD dwAttrib = 0;

                dwAttrib = GetFileAttributes(szExtensionDll);

                if(dwAttrib == 0xFFFFFFFF)
                {
        fprintf(stderr,
        "ERROR: please specify an existing file for /%s\n",
        ARG_EXTDLL);

        hr = HR_LOG(E_FAIL);
                }
            }
        }
}

    if(SUCCEEDED(hr) && (fDeleteExtension == FALSE))
    {
        //
        // Get extension DLL file name
        //

    strncpy(szFileName,
        _GetFileNameFromFullPath((LPTSTR)szExtensionDll), MAX_PATH);

        //
        // Get extension DLL file version
        //

        hr = HrGetFileVersionInfo(
            szExtensionDll,
            &lpVersionInfo);

        if(FAILED(hr))
    {
    fprintf(stderr, "ERROR: version unavailable [%08lx] in /%s\n",
                hr,
        ARG_EXTDLL);

    hr = HR_LOG(E_FAIL);
    }
        else
        {
            BOOL fRet = FALSE;

            fRet = VerQueryValue(lpVersionInfo,
                          TEXT("\\"),
                          &lpBuffer,
                          &dwBytes);

            if(fRet == FALSE)
            {
        fprintf(stderr, "ERROR: version resource is invalid in /%s\n",
            ARG_EXTDLL);

        hr = HR_LOG(E_FAIL);
            }
            else
            {
                lpvsFixedFileInfo = (VS_FIXEDFILEINFO *)lpBuffer;

                dwFileVersionMS = lpvsFixedFileInfo->dwFileVersionMS;
                dwFileVersionLS = lpvsFixedFileInfo->dwFileVersionLS;
            }
        }
    }

    hrT = HrAdminProgramExists();

    if(FAILED(hrT))
    {
fprintf(stderr, "ERROR: install the Exchange administrator program\n");
hr = HR_LOG(E_FAIL);
    }

cleanup:

    GLOBALFREE(lpVersionInfo);

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr                 = NOERROR;
    FILE    *fp                = NULL;
    BOOL    fErrMsg            = TRUE;
    DWORD   dwOldFileVersionMS = 0;
    DWORD   dwOldFileVersionLS = 0;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteExtension == FALSE)
    {
        hr = HrAdminExtensionExists(
            szServer,
            szSiteDN,
            szExtensionName,
            szMachineType);

        if(SUCCEEDED(hr))
        {
            hr = HrGetAdminExtensionVersion(
                szServer,
                szSiteDN,
                szExtensionName,
                szMachineType,
                &dwOldFileVersionMS,
                &dwOldFileVersionLS);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: file version is missing in %s admin extension\n",
            szExtensionName);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if((dwOldFileVersionMS >  dwFileVersionMS) ||
              ((dwOldFileVersionMS == dwFileVersionMS) &&
               (dwOldFileVersionLS >  dwFileVersionLS)))
            {
        fprintf(stderr,
            "WARNING: installing older version of %s admin extension\n",
            szExtensionName);
            }

            hr = HrRemoveAdminExtension(
                szServer,
                szSiteDN,
                szExtensionName,
                szMachineType);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: unable to remove %s admin extension\n",
            szExtensionName);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        hr = HrInstallAdminExtension(
            szServer,
            szSiteDN,
            szDisplayName,
            szExtensionName,
            dwFileVersionMS,
            dwFileVersionLS,
            szFileName,
            szMachineType);
    }
    else
    {
        hr = HrAdminExtensionExists(
            szServer,
            szSiteDN,
            szExtensionName,
            szMachineType);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "ERROR: the %s admin extension does not exist\n",
        szExtensionName);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrRemoveAdminExtension(
            szServer,
            szSiteDN,
            szExtensionName,
            szMachineType);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteExtension == FALSE)
        {
    fprintf(stderr, "Admin extension installed successfully.\n");
        }
        else
        {
    fprintf(stderr, "Admin extension deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: error(s) written to NT Event Log.\n");
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


GWINST.C

// --gwinst.c-------------------------------------------------------------------
//
//  Gateway installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"
#include "sadapi.h"

//
// Names of Command Line Arguments
//

#define ARG_ENTERPRISE"ENTERPRISE"
#define ARG_SITEDN          "SITEDN"
#define ARG_DISPLAY_NAME"DISPLAY_NAME"
#define ARG_NAME            "NAME"
#define ARG_ADDRTYPE"ADDRTYPE"
#define ARG_EXTNAME         "EXTNAME"
#define ARG_EXTDATA         "EXTDATA"
#define ARG_SERVER"SERVER"
#define ARG_ACCOUNT"ACCOUNT_NAME"
#define ARG_PRESERVE_DNS    "PRESERVE_DNS"
#define ARG_DELETE"DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
    ARG_DISPLAY_NAME,
ARG_NAME,
ARG_ADDRTYPE,
ARG_EXTNAME,
ARG_EXTDATA,
ARG_SERVER,
ARG_ACCOUNT,
    ARG_PRESERVE_DNS,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szSiteDN[MAX_PATH+1]               = {0};
char szDisplayName[MAX_PATH+1]          = {0};
char szGatewayName[MAX_PATH+1]          = {0};
char szExtensionName[MAX_PATH+1]        = {0};
char szExtensionData[MAX_PATH+1]        = {0};
char szServer[MAX_PATH+1]               = {0};
char szAccountName[MAX_PATH+1]          = {0};
char szAddressType[MAX_PATH+1]          = {0};
BOOL fCanPreserveDNs                    = FALSE;
BOOL fDisplayedHelp                     = FALSE;
BOOL fDeleteGateway                     = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: GWINST [Flags]\n\n");
printf("  [Flags]        Enter GWINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Gateway Installation sample.\n\n");
printf("USAGE: GWINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Gateway site distinguished name\n");
printf("  /DISPLAY_NAME=       Gateway display name\n");
printf("  /NAME=               Gateway name\n");
printf("  /ADDRTYPE=           Address type\n");
printf("  /SERVER=             Server computer name\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /EXTNAME=            Extension name\n");
printf("  /EXTDATA=            Extension data absolute path & file name\n");
printf("                       (Use CFGCODE to create this file.)\n");
printf("  /ACCOUNT=            Account name\n");
    printf("  /PRESERVE_DNS=       Can this gateway preserve DNs? (yes/no)\n");
    printf("  /DELETE              Delete gateway\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteGateway = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DISPLAY_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_NAME))
{
strncpy(szGatewayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_ADDRTYPE))
{
strncpy(szAddressType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXTNAME))
{
strncpy(szExtensionName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXTDATA))
{
strncpy(szExtensionData, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_ACCOUNT))
{
strncpy(szAccountName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_PRESERVE_DNS))
{
    if (!lstrcmpi(pszValue,TEXT("YES")))
                {
                    fCanPreserveDNs = TRUE;
                }
                else if (!lstrcmpi(pszValue,TEXT("NO")))
                {
                    fCanPreserveDNs = FALSE;
                }
                else
                {
    fprintf(stderr, "ERROR: enter \"YES\" or \"NO\" for flag /%s\n",
    pszArgument);
    hr = HR_LOG(E_FAIL);
                }
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD dwService      = 0;
            DWORD dwCurrentState = 0;

            hr = HrGetExchangeStatus(
                szServer,
                &dwService,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(stderr, "ERROR: please start Exchange on %s\n",szServer);
    hr = HR_LOG(E_FAIL);
            }
        }

    if (szSiteDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
hr = HR_LOG(E_FAIL);
}

    if (szGatewayName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_NAME);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteGateway == FALSE)
        {
        if (szDisplayName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_DISPLAY_NAME);
    hr = HR_LOG(E_FAIL);
    }

            if (szAddressType[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_ADDRTYPE);
    hr = HR_LOG(E_FAIL);
    }
        }
}

    hrT = HrAdminProgramExists();

    if(FAILED(hrT))
    {
fprintf(stderr, "ERROR: install the Exchange administrator program\n");
hr = HR_LOG(E_FAIL);
    }

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    FILE    *fp     = NULL;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteGateway == FALSE)
    {
        //
        // Set gateway bit
        //

        hr = HrSetGatewayBit(
            szServer,
            szSiteDN,
            szServer);

        if(FAILED(hr))
        {
        fprintf(stderr,
        "ERROR: unable to set gateway bit\n");
        hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrGatewayExists(
            szServer,
            szSiteDN,
            szGatewayName);

        if(SUCCEEDED(hr))
        {
            hr = HrRemoveGateway(
                szServer,
                szSiteDN,
                szGatewayName);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: unable to remove gateway %s\n",
            szGatewayName);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        hr = HrInstallGateway(
            szServer,
            szSiteDN,
            szDisplayName,
            szGatewayName,
            szExtensionName,
            szExtensionData,
            szAddressType,
            szAccountName,
            fCanPreserveDNs);

//
// Rebuild gateway address routing table (GWART).
//

        if(SUCCEEDED(hr))
        {
    (void)SAD_ScRunRID(szServer);
        }
    }
    else
    {
        hr = HrGatewayExists(
            szServer,
            szSiteDN,
            szGatewayName);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "ERROR: the gateway %s does not exist\n",
        szGatewayName);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrRemoveGateway(
            szServer,
            szSiteDN,
            szGatewayName);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteGateway == FALSE)
        {
    fprintf(stderr, "Gateway installed successfully.\n");
        }
        else
        {
    fprintf(stderr, "Gateway deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: error(s) written to NT Event Log.\n");
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


MBINST.C

// --mbinst.c-------------------------------------------------------------------
//
//  Mailbox agent installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_SITEDN          "SITEDN"
#define ARG_DISPLAY_NAME    "DISPLAY_NAME"
#define ARG_RELATIVE_DN"RELATIVE_DN"
#define ARG_EXTNAME         "EXTNAME"
#define ARG_EXTDATA         "EXTDATA"
#define ARG_SERVER"SERVER"
#define ARG_ACCOUNT"ACCOUNT_NAME"
#define ARG_DELETE"DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
    ARG_DISPLAY_NAME,
ARG_RELATIVE_DN,
ARG_EXTNAME,
ARG_EXTDATA,
ARG_SERVER,
ARG_ACCOUNT,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szSiteDN[MAX_PATH+1]    = {0};
char szDisplayName[MAX_PATH+1]              = {0};
char szRelativeDN[MAX_PATH+1]               = {0};
char szExtensionName[MAX_PATH+1]    = {0};
char szExtensionData[MAX_PATH+1]    = {0};
char szServer[MAX_PATH+1]    = {0};
char szAccountName[MAX_PATH+1]    = {0};
BOOL fDisplayedHelp    = FALSE;
BOOL fDeleteMailboxAgent                    = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: MBINST [Flags]\n\n");
printf("  [Flags]        Enter MBINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Mailbox Agent Installation sample.\n\n");
printf("USAGE: MBINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Mailbox agent site distinguished name\n");
printf("  /DISPLAY_NAME=       Mailbox agent display name\n");
printf("  /RELATIVE_DN=        Mailbox agent relative distinguished name\n");
printf("  /SERVER=             Server computer name\n");
printf("  /EXTNAME=            Extension name\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /EXTDATA=            Extension data absolute path & file name\n");
printf("                       (Use CFGCODE to create this file.)\n");
printf("  /ACCOUNT=            Account name\n");
    printf("  /DELETE              Delete mailbox agent\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteMailboxAgent = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DISPLAY_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_RELATIVE_DN))
{
strncpy(szRelativeDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXTNAME))
{
strncpy(szExtensionName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXTDATA))
{
strncpy(szExtensionData, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_ACCOUNT))
{
strncpy(szAccountName, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD   dwService      = 0;
            DWORD   dwCurrentState = 0;

            hr = HrGetExchangeStatus(
                szServer,
                &dwService,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(stderr, "ERROR: please start Exchange on %s\n",szServer);
    hr = HR_LOG(E_FAIL);
            }
        }

    if (szSiteDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
hr = HR_LOG(E_FAIL);
}

    if (szDisplayName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_DISPLAY_NAME);
hr = HR_LOG(E_FAIL);
}

    if (szRelativeDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_RELATIVE_DN);
hr = HR_LOG(E_FAIL);
}
        else
        {
            if(_strnicmp(szRelativeDN, "/cn=", strlen("/cn=")))
            {
    fprintf(
        stderr,
        "ERROR: /%s value must start with '/cn='\n",
        ARG_RELATIVE_DN);
    hr = HR_LOG(E_FAIL);
            }
        }

        if(fDeleteMailboxAgent == FALSE)
        {
        if (szExtensionName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_EXTNAME);
    hr = HR_LOG(E_FAIL);
    }
        }
}

    hrT = HrAdminProgramExists();

    if(FAILED(hrT))
    {
fprintf(stderr, "ERROR: install the Exchange administrator program\n");
hr = HR_LOG(E_FAIL);
    }

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    FILE    *fp     = NULL;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteMailboxAgent == FALSE)
    {
        hr = HrMailboxAgentExists(
            szServer,
            szSiteDN,
            szRelativeDN);

        if(SUCCEEDED(hr))
        {
            hr = HrRemoveMailboxAgent(
                szServer,
                szSiteDN,
                szRelativeDN);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: unable to remove mailbox agent %s\n",
            szRelativeDN);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
        else if(hr != E_FAIL)
        {
            fErrMsg = FALSE;
    fprintf(stderr,
        "ERROR: invalid value for /%s\n",
        ARG_RELATIVE_DN);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrInstallMailboxAgent(
            szServer,
            szSiteDN,
            szDisplayName,
            szRelativeDN,
            szExtensionName,
            szExtensionData,
            szAccountName);
    }
    else
    {
        hr = HrMailboxAgentExists(
            szServer,
            szSiteDN,
            szRelativeDN);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "ERROR: the mailbox agent %s does not exist\n",
        szRelativeDN);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrRemoveMailboxAgent(
            szServer,
            szSiteDN,
            szRelativeDN);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteMailboxAgent == FALSE)
        {
    fprintf(stderr, "Mailbox agent installed successfully.\n");
        }
        else
        {
    fprintf(stderr, "Mailbox agent deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: error(s) written to NT Event Log.\n");
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


PERFINST.C

// --perfinst.c-----------------------------------------------------------------
//
//  Performance monitoring installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

//
// Names of Command Line Arguments
//

#define ARG_SERVICE         "SERVICE"
#define ARG_CLASS           "CLASS"
#define ARG_DLL             "DLL"
#define ARG_OPEN            "OPEN"
#define ARG_CLOSE           "CLOSE"
#define ARG_COLLECT         "COLLECT"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SERVICE,
ARG_CLASS,
ARG_DLL,
    ARG_OPEN,
    ARG_CLOSE,
    ARG_COLLECT,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szServiceName[MAX_PATH+1]          = {0};
char szServiceClass[MAX_PATH+1]         = {0};
char szPerfDllName[MAX_PATH+1]          = {0};
char szOpenFunc[MAX_PATH+1]             = {0};
char szCollectFunc[MAX_PATH+1]          = {0};
char szCloseFunc[MAX_PATH+1]            = {0};
BOOL fDisplayedHelp= FALSE;
BOOL fDeletePerform                     = FALSE;
BOOL fUnloadCounters                    = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: PERFINST [Flags]\n\n");
printf("  [Flags]        Enter PERFINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Performance Monitoring Installation sample.\n\n");
printf("USAGE: PERFINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SERVICE=            Service name\n");
printf("  /CLASS=              Service class\n");
printf("  /DLL=                Performance DLL name\n");
    printf("  /OPEN=               Initializes performance monitoring\n");
    printf("  /COLLECT=            Reports performance data when requested\n");
    printf("  /CLOSE=              Closes performance monitoring\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /DELETE              Delete performance monitoring\n");
printf("  /HELP or /?          Display help screen\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeletePerform = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SERVICE))
{
strncpy(szServiceName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_CLASS))
{
strncpy(szServiceClass, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DLL))
{
strncpy(szPerfDllName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_OPEN))
{
strncpy(szOpenFunc, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_CLOSE))
{
strncpy(szCloseFunc, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_COLLECT))
{
strncpy(szCollectFunc, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServiceName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVICE);
hr = HR_LOG(E_FAIL);
}

    if (szServiceClass[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_CLASS);
hr = HR_LOG(E_FAIL);
}

        if(fDeletePerform == FALSE)
        {
        if (szPerfDllName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_DLL);
    hr = HR_LOG(E_FAIL);
    }

        if (szOpenFunc[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_OPEN);
    hr = HR_LOG(E_FAIL);
    }

        if (szCollectFunc[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_COLLECT);
    hr = HR_LOG(E_FAIL);
    }

        if (szCloseFunc[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_CLOSE);
    hr = HR_LOG(E_FAIL);
    }
        }
}

cleanup:
    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeletePerform == FALSE)
    {
        hr = HrInstallServicePerfMon(
            szServiceName,
            szServiceClass,
            szPerfDllName,
            szOpenFunc,
            szCollectFunc,
            szCloseFunc);
    }
    else
    {
        hr = HrRemoveServicePerfMon(
            szServiceName,
            szServiceClass,
            &fUnloadCounters);

        if(fUnloadCounters == TRUE)
        {
    fprintf(
        stderr,
        "Performance counters can now be unloaded!\n");
        }
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeletePerform == FALSE)
        {
    fprintf(
        stderr,
        "Performance monitoring installed successfully.\n");
        }
        else
        {
    fprintf(
        stderr,
        "Performance monitoring deleted successfully.\n");
        }
hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: an error occurred [%08lx].\n",hr);
        }

hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


PROFINST.C

// --profinst.c-----------------------------------------------------------------
//
//  MAPI profile creation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

//
// Names of Command Line Arguments
//

#define ARG_SERVICE         "SERVICE"
#define ARG_NAME"NAME"
#define ARG_TYPE            "TYPE"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SERVICE,
ARG_NAME,
    ARG_TYPE,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szServiceName[MAX_PATH+1]          = {0};
char szProfileName[MAX_PATH+1]          = {0};
char szProfileType[MAX_PATH+1]          = {0};
BOOL fDisplayedHelp                     = FALSE;
BOOL fDeleteProfile                     = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: PROFINST [Flags]\n\n");
printf("  [Flags]        Enter PROFINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("MAPI Profile Installation sample.\n\n");
printf("USAGE: PROFINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SERVICE=            Service name\n");
printf("  /NAME=               Profile name\n");
printf("  /TYPE=               Profile type (GATEWAY or AGENT)\n");
printf("\n");
printf("  Optional Flags\n");
    printf("  /DELETE              Delete profile\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteProfile = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SERVICE))
{
strncpy(szServiceName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_NAME))
{
strncpy(szProfileName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_TYPE))
{
strncpy(szProfileType, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if ((szServiceName[0] == 0) && (fDeleteProfile == FALSE))
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVICE);
hr = HR_LOG(E_FAIL);
}

    if (szProfileName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_NAME);
hr = HR_LOG(E_FAIL);
}

    if(szProfileType[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_TYPE);
hr = HR_LOG(E_FAIL);
}
        else
        {
            if(lstrcmpi(szProfileType, "GATEWAY") &&
               lstrcmpi(szProfileType, "AGENT"))
            {
    fprintf(stderr, "ERROR: please specify a valid /%s\n", ARG_TYPE);
    hr = HR_LOG(E_FAIL);
            }
        }
}

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT    hr         = NOERROR;
MAPIINIT_0 MapiInit   = { 0 };
    BOOL       IsInitMAPI = FALSE;
    BOOL       fErrMsg    = TRUE;

    printf( "\n" );

    MapiInit.ulVersion = MAPI_INIT_VERSION;

    hr = MAPIInitialize(&MapiInit);

    if(FAILED(hr))
    {
    fprintf(stderr, "ERROR: MAPI failed to initialize\n");
        fErrMsg = FALSE;
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    IsInitMAPI = TRUE;

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteProfile == FALSE)
    {
        if(!lstrcmpi(szProfileType, "GATEWAY"))
        {
            hr = HrCreateGatewayProfile(szServiceName, szProfileName);
        }
        else
        {
            hr = HrCreateMailboxAgentProfile(szServiceName, szProfileName);
        }
    }
    else
    {
        hr = HrRemoveProfile(szProfileName);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteProfile == FALSE)
        {
    fprintf(stderr, "Profile created successfully.\n");
    hr = NOERROR;
        }
        else
        {
    fprintf(stderr, "Profile deleted successfully.\n");
    hr = NOERROR;
        }
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: an error occurred [%08lx].\n",hr);
        }
hr = HR_LOG(E_FAIL);
}

    if(IsInitMAPI == TRUE)
    {
        MAPIUninitialize();
    }

    // return exit code
    return _nEcFromHr(hr);

}


SERVINST.C

// --servinst.c-----------------------------------------------------------------
//
//  Service installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

//
// Run GUIDGEN.EXE to create a GUID to use when installing your company's
// products. This will make it possible for you to find services that you
// have installed.
//

#define PRODUCT_GUID_SAMPLE  TEXT("F90E4AE0-E588-11ce-A2CA-00AA0040E865")

//
// Names of Command Line Arguments
//

#define ARG_SITEDN"SITEDN"
#define ARG_OBJNAME         "OBJNAME"
#define ARG_OBJTYPE         "OBJTYPE"
#define ARG_SERVICE         "SERVICE"
#define ARG_DISPLAY_NAME    "DISPLAY_NAME"
#define ARG_DEPENDENCIES    "DEPENDENCIES"
#define ARG_EXE             "EXE"
#define ARG_SERVER"SERVER"
#define ARG_ACCOUNT"ACCOUNT"
#define ARG_PASSWORD        "PASSWORD"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
ARG_OBJNAME,
    ARG_OBJTYPE,
ARG_SERVICE,
    ARG_DISPLAY_NAME,
    ARG_DEPENDENCIES,
ARG_EXE,
ARG_SERVER,
ARG_ACCOUNT,
ARG_PASSWORD,
ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szSiteDN[MAX_PATH+1]               = {0};
char szObjectName[MAX_PATH+1]           = {0};
char szObjectType[MAX_PATH+1]           = {0};
char szServiceName[MAX_PATH+1]          = {0};
char szDisplayName[MAX_PATH+1]          = {0};
char szExeName[MAX_PATH+1]              = {0};
char szServer[MAX_PATH+1]               = {0};
char szAccountName[MAX_PATH+1]          = {0};
char szPassword[MAX_PATH+1]             = {0};
char szObjectGuid[MAX_PATH+1]           = {0};
char szDependencies[MAX_PATH+1]         = {0};
BOOL fDisplayedHelp                     = FALSE;
BOOL fDeleteService                     = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: SERVINST [Flags]\n\n");
printf("  [Flags]        Enter SERVINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Service Installation sample.\n\n");
printf("USAGE: SERVINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Site distinguished name\n");
printf("  /OBJNAME=            Object name\n");
printf("  /OBJTYPE=            Object type (GATEWAY or AGENT)\n");
printf("  /SERVICE=            Service name\n");
printf("  /DISPLAY_NAME=       Service display name\n");
    printf("  /DEPENDENCIES=       Service dependencies (separated by '%%')\n");
printf("  /EXE=                Executable name\n");
printf("  /SERVER=             Server computer name\n");
printf("  /ACCOUNT=            Account name\n");
printf("  /PASSWORD=           Account password\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /DELETE              Delete service\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteService = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_OBJNAME))
{
strncpy(szObjectName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_OBJTYPE))
{
strncpy(szObjectType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVICE))
{
strncpy(szServiceName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DISPLAY_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DEPENDENCIES))
{
strncpy(szDependencies, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_EXE))
{
strncpy(szExeName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_ACCOUNT))
{
strncpy(szAccountName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_PASSWORD))
{
strncpy(szPassword, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServiceName[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVICE);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteService == FALSE)
        {
        if (szSiteDN[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
    hr = HR_LOG(E_FAIL);
    }

        if (szDisplayName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_DISPLAY_NAME);
    hr = HR_LOG(E_FAIL);
    }

        if (szObjectName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_OBJNAME);
    hr = HR_LOG(E_FAIL);
    }

        if(szObjectType[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_OBJTYPE);
    hr = HR_LOG(E_FAIL);
    }
            else
            {
                if(!lstrcmpi(szObjectType, "GATEWAY"))
                {
                    strcpy(szObjectGuid, OBJECT_GUID_MAIL_GATEWAY);
                }
                else if(!lstrcmpi(szObjectType, "AGENT"))
                {
                    strcpy(szObjectGuid, OBJECT_GUID_MAILBOX_AGENT);
                }
                else
                {
        fprintf(stderr,
            "ERROR: please specify a valid /%s\n", ARG_OBJTYPE);
        hr = HR_LOG(E_FAIL);
                }
            }

        if (szExeName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_EXE);
    hr = HR_LOG(E_FAIL);
    }

        if (szServer[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
    hr = HR_LOG(E_FAIL);
    }

        if (szAccountName[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_ACCOUNT);
    hr = HR_LOG(E_FAIL);
    }

        if (szPassword[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_PASSWORD);
    hr = HR_LOG(E_FAIL);
    }
        }
}

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    TCHAR   *pch    = NULL;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    ZeroMemory(szDependencies, sizeof(szDependencies));

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    //
    // Convert message classes parameter to REG_MULTI_SZ format.
    //

    szDependencies[sizeof(szDependencies)-1] = 0;
    szDependencies[sizeof(szDependencies)-2] = 0;

    pch = szDependencies;

    while(*pch)
    {
        if(*pch == TEXT('%'))
        {
            *pch = 0;
        }

        pch++;
    }

    if(fDeleteService == FALSE)
    {
        hr = HrInstallService(
            szServer,
            szSiteDN,
            szDisplayName,
            szServiceName,
            szObjectName,
            szObjectGuid,
            PRODUCT_GUID_SAMPLE,
            szExeName,
            szDependencies,
            szAccountName,
            szPassword);
    }
    else
    {
        hr = HrRemoveService(szServiceName);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteService == FALSE)
        {
    fprintf(stderr, "Service installed successfully.\n");
    hr = NOERROR;
        }
        else
        {
    fprintf(stderr, "Service deleted successfully.\n");
    hr = NOERROR;
        }
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: an error occurred [%08lx].\n",hr);
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


TMPLINST.C

// --tmplinst.c-----------------------------------------------------------------
//
//  Address Template installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

//
// Names of Command Line Arguments
//

#define ARG_SITEDN"SITEDN"
#define ARG_DISPLAY_NAME    "DISPLAY_NAME"
#define ARG_NAME            "NAME"
#define ARG_TYPE            "TYPE"
#define ARG_SYNTAX          "SYNTAX"
#define ARG_SERVER"SERVER"
#define ARG_MESSAGE_DT      "MESSAGE_DT"
#define ARG_RECIPIENT_DT    "RECIPIENT_DT"
#define ARG_ADDRESS_DT      "ADDRESS_DT"
#define ARG_LANGUAGE        "LANGUAGE"
#define ARG_DATA16          "DATA16"
#define ARG_DATA32          "DATA32"
#define ARG_FILE            "FILE"
#define ARG_DELETE          "DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SITEDN,
    ARG_DISPLAY_NAME,
ARG_NAME,
ARG_TYPE,
ARG_SYNTAX,
ARG_SERVER,
ARG_MESSAGE_DT,
ARG_RECIPIENT_DT,
ARG_ADDRESS_DT,
ARG_LANGUAGE,
    ARG_DATA16,
    ARG_DATA32,
    ARG_FILE,
ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szSiteDN[MAX_PATH+1]                = {0};
char szDisplayName[MAX_PATH+1]           = {0};
char szTemplateName[MAX_PATH+1] = {0};
char szAddressType[MAX_PATH+1]           = {0};
char szAddressSyntax[MAX_PATH+1]         = {0};
char szServer[MAX_PATH+1] = {0};
char szMessageDisplayTable[MAX_PATH+1]   = {0};
char szRecipientDisplayTable[MAX_PATH+1] = {0};
char szAddressDisplayTable[MAX_PATH+1]   = {0};
char szLanguage[MAX_PATH+1]              = {0};
char szHelpData16[MAX_PATH+1]            = {0};
char szHelpData32[MAX_PATH+1]            = {0};
char szHelpFileName[MAX_PATH+1]          = {0};
BOOL fDisplayedHelp         = FALSE;
BOOL fDeleteTemplate                     = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: TMPLINST [Flags]\n\n");
printf("  [Flags]        Enter TMPLINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Address Template Installation sample.\n\n");
printf("USAGE: TMPLINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SITEDN=             Site distinguished name\n");
    printf("  /DISPLAY_NAME        Template display name\n");
printf("  /NAME=               Template name\n");
printf("  /TYPE=               Address type\n");
printf("  /SYNTAX=             Address syntax\n");
printf("  /SERVER=             Server computer name\n");
printf("  /ADDRESS_DT=         Address Entry Display Table\n");
printf("  /LANGUAGE=           Language (e.g. 409) \n");
    printf("  /DATA16=             Help data for 16-bit clients\n");
    printf("  /DATA32=             Help data for 32-bit clients\n");
    printf("  /FILE=               Help file name used by clients\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /MESSAGE_DT=         Per-Message Display Table\n");
printf("  /RECIPIENT_DT=       Per-Recipient Display Table\n");
printf("  /DELETE              Delete template\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteTemplate = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_SITEDN))
{
strncpy(szSiteDN, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DISPLAY_NAME))
{
strncpy(szDisplayName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_NAME))
{
strncpy(szTemplateName, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_TYPE))
{
strncpy(szAddressType, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_SYNTAX))
{
strncpy(szAddressSyntax, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_MESSAGE_DT))
{
strncpy(szMessageDisplayTable, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_RECIPIENT_DT))
{
strncpy(szRecipientDisplayTable, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_ADDRESS_DT))
{
strncpy(szAddressDisplayTable, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_LANGUAGE))
{
strncpy(szLanguage, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DATA16))
{
strncpy(szHelpData16, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_DATA32))
{
strncpy(szHelpData32, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_FILE))
{
strncpy(szHelpFileName, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD dwService      = 0;
            DWORD dwCurrentState = 0;

            hr = HrGetExchangeStatus(
                szServer,
                &dwService,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(stderr, "ERROR: please start Exchange on %s\n",szServer);
    hr = HR_LOG(E_FAIL);
            }
        }

        if (szTemplateName[0] == 0)
        {
            fprintf(stderr, "ERROR: please specify /%s\n", ARG_NAME);
            hr = HR_LOG(E_FAIL);
        }

    if (szSiteDN[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SITEDN);
hr = HR_LOG(E_FAIL);
}

    if (szAddressType[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_TYPE);
hr = HR_LOG(E_FAIL);
}

    if (szLanguage[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_LANGUAGE);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteTemplate == FALSE)
        {
        if (szAddressSyntax[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_SYNTAX);
    hr = HR_LOG(E_FAIL);
    }

        if (szAddressDisplayTable[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_ADDRESS_DT);
    hr = HR_LOG(E_FAIL);
    }
        }
}

    hrT = HrAdminProgramExists();

    if(FAILED(hrT))
    {
fprintf(stderr, "ERROR: install the Exchange administrator program\n");
hr = HR_LOG(E_FAIL);
    }

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    if(fDeleteTemplate == FALSE)
    {
        hr = HrAddressTemplateExists(
            szServer,
            szSiteDN,
            szTemplateName,
            szAddressType,
            szLanguage);

        if(SUCCEEDED(hr))
        {
            hr = HrRemoveAddressTemplate(
                szServer,
                szSiteDN,
                szTemplateName,
                szAddressType,
                szLanguage);

            if(FAILED(hr))
            {
        fprintf(stderr,
            "ERROR: unable to remove address template %s\n",
            szTemplateName);
        hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        hr = HrInstallAddressTemplate(
            szServer,
            szSiteDN,
            szDisplayName,
            szTemplateName,
            szAddressSyntax,
            szAddressType,
            szMessageDisplayTable,
            szRecipientDisplayTable,
            szAddressDisplayTable,
            szLanguage,
            szHelpData16,
            szHelpData32,
            szHelpFileName);
    }
    else
    {
        hr = HrAddressTemplateExists(
            szServer,
            szSiteDN,
            szTemplateName,
            szAddressType,
            szLanguage);

        if(FAILED(hr))
        {
    fprintf(stderr,
        "ERROR: the address template %s does not exist\n",
        szTemplateName);
    hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrRemoveAddressTemplate(
            szServer,
            szSiteDN,
            szTemplateName,
            szAddressType,
            szLanguage);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteTemplate == FALSE)
        {
    fprintf(stderr, "Address template created successfully.\n");
        }
        else
        {
    fprintf(stderr, "Address template deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: error(s) written to NT Event Log.\n");
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


XLATINST.C

// --xlatinst.c-----------------------------------------------------------------
//
//  Message converter installation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

//
// Names of Command Line Arguments
//

#define ARG_DLL"DLL"
#define ARG_CLASSES         "CLASSES"
#define ARG_OPTIONS         "OPTIONS"
#define ARG_POINT           "POINT"
#define ARG_DELETE"DELETE"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_DLL,
ARG_CLASSES,
ARG_OPTIONS,
ARG_POINT,
    ARG_DELETE,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szConversionDll[MAX_PATH+1]        = {0};
char szMessageClasses[MAX_PATH+1]       = {0};
char szOptions[MAX_PATH+1]= {0};
char szPoint[MAX_PATH+1]                = {0};
BOOL fDisplayedHelp= FALSE;
BOOL fDeleteConverter                   = FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: XLATINST [Flags]\n\n");
printf("  [Flags]        Enter XLATINST /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Message Converter Installation sample.\n\n");
printf("USAGE: XLATINST [Flags]\n\n");
printf("  Required Flags\n");
printf("  /DLL=                Message conversion DLL name (no path)\n");
printf("  /CLASSES=            Message classes (separated by '%%')\n");
printf("  /OPTIONS=            Options string\n");
printf("  /POINT=              Point string\n");
printf("\n");
printf("  Optional Flags\n");
    printf("  /DELETE              Delete conversion DLL\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!lstrcmpi(pszArgument,ARG_HELP1) ||
!lstrcmpi(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
if (!lstrcmpi(pszArgument,ARG_DELETE))
{
fDeleteConverter = TRUE;
}

// Other flag (must take a value).

else
{
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{

if (!lstrcmpi(pszArgument,ARG_DLL))
{
strncpy(szConversionDll, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_CLASSES))
{
strncpy(szMessageClasses, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_OPTIONS))
{
strncpy(szOptions, pszValue, MAX_PATH);
}

else if (!lstrcmpi(pszArgument,ARG_POINT))
{
strncpy(szPoint, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szConversionDll[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_DLL);
hr = HR_LOG(E_FAIL);
}

        if(fDeleteConverter == FALSE)
        {
        if (szMessageClasses[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_CLASSES);
    hr = HR_LOG(E_FAIL);
    }

        if (szPoint[0] == 0)
    {
    fprintf(stderr, "ERROR: please specify /%s\n", ARG_POINT);
    hr = HR_LOG(E_FAIL);
    }
        }
}

cleanup:

    RETURN(hr);
}

//$--main-----------------------------------------------------------------------
//  Main function that performs directory import.
// -----------------------------------------------------------------------------
int main(
    IN int argc,
    IN char *argv[])
{
    HRESULT hr      = NOERROR;
    TCHAR   *pch    = NULL;
    BOOL    fErrMsg = TRUE;

    printf( "\n" );

    //
    // Get parameters from the command-line
    //

    ZeroMemory(szMessageClasses, sizeof(szMessageClasses));

    hr = HrConfig(argc, argv);

    if (FAILED(hr))
    {
        fErrMsg = FALSE;
        goto cleanup;
    }

    //
    // Convert message classes parameter to REG_MULTI_SZ format.
    //

    szMessageClasses[sizeof(szMessageClasses)-1] = 0;
    szMessageClasses[sizeof(szMessageClasses)-2] = 0;

    pch = szMessageClasses;

    while(*pch)
    {
        if(*pch == TEXT('%'))
        {
            *pch = 0;
        }

        pch++;
    }

    if(fDeleteConverter == FALSE)
    {
        hr = HrInstallMessageConverter(
            szConversionDll,
            szConversionDll,
            szMessageClasses,
            szOptions,
            szPoint);
    }
    else
    {
        hr = HrRemoveMessageConverter(
            szConversionDll);
    }

cleanup:

// Successful completion.

if (fDisplayedHelp)
{
hr = NOERROR;
}
else if (SUCCEEDED(hr))
{
        if(fDeleteConverter == FALSE)
        {
    fprintf(stderr, "Message converter installed successfully.\n");
        }
        else
        {
    fprintf(stderr, "Message converter deleted successfully.\n");
        }

hr = NOERROR;
}

// Error completion.

else
{
        if(fErrMsg == TRUE)
        {
    fprintf(stderr, "ERROR: an error occurred [%08lx].\n",hr);
        }
hr = HR_LOG(E_FAIL);
}

    // return exit code
    return _nEcFromHr(hr);

}


ADMIN.C

// Admin.C ---------------------------------------------------------------------
// Functions to support Exchange Admin Configuration Extension Dialogs for C
// programs.  This does the majority of the handshaking necessary.
// (See ExAdmin.CPP for C++ support)
//
// This module is intended to be a generic MAIN source file for creating an
// Exchange Administration DLL.  You could take this file and include it in your
// project as starting place for your DLL.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "retcode.h"
#include "admin.h"
#include "admin.chk"

HINSTANCE hDLLInst  = NULL;
BOOL      bReadOnly = FALSE;     // The mode is read only if TRUE.

ADMIN_AdministratorFunction* pAdminFunctions;  // Admin functions available to you.
ADMIN_ObjectInfo*            pAdminObjectInfo; // Admin server name and other useful DNs.

extern BOOL __stdcall _CRT_INIT( HINSTANCE, DWORD, LPVOID);

//$--LibMain--------------------------------------------------------------------
// Eported Function. Called when DLL is first loaded.
// -----------------------------------------------------------------------------

BOOL APIENTRY LibMain(        // Returns TRUE if DLL has been initialized.
    IN HINSTANCE   hinstDLL,  // Handle to this DLL instance
    IN DWORD       dwReason,  // Reason DLL is being initialized.
    IN LPVOID      lpContext) // Context (not used)
{
    // Set global instance variable.
    hDLLInst = hinstDLL;

    // Initialize the C Run Time DLL.
    if( dwReason == DLL_PROCESS_ATTACH || dwReason == DLL_THREAD_ATTACH )
    {
        if( _CRT_INIT( hinstDLL, dwReason, lpContext) == FALSE)
        {
            HR_LOG( E_FAIL);
            return( FALSE);
        }
    }

    return( TRUE);
}

//$--bInitSheet()---------------------------------------------------------------
// This function initializes the property sheet info and returns it to admin.
//
// Since this piece of code is written to be a generic MAIN source file for
// creating an Exchange Administration DLL we use global data defined outside
// of this module to contain the information about the property sheet dialogs.
// You will need to make sure your code contains the following two globals:
//
//  1) ADMIN_SheetInfo rgSheetInfo[]                Array of info about your
//                                                  property sheets.
//
//  2) int cSheetInfo = ARRAY_CNT( rgSheetInfo);    The count of property sheets
//                                                  in the array.
// -----------------------------------------------------------------------------

BOOL PASCAL bInitSheet(                 // RETURNS: TRUE if initialization OK
    IN  ADMIN_ObjectInfo* poi,          // Computer name & DN.
    IN  ULONG             fFlags,       // Tells us if it is read only or not.
    OUT ADMIN_SheetInfo** ppSheetInfo,  // Property sheet description array
    OUT UINT*             pcsi,         // Number of property sheets
    OUT VOID**            ppNotUsed)    // Local data
{
    int              iSheet     = 0;

    DEBUGPUBLIC( "bInitSheet()\n");
    if( FAILED( CHK_bInitSheet( poi, fFlags, ppSheetInfo, pcsi, ppNotUsed)))
        return( FALSE);

    // Remember these for later usage.
    pAdminObjectInfo = poi;
    bReadOnly = (fFlags & fxfReadOnly) > 0;

    // Initialize Admin property sheet info array.
    for( iSheet = 0; iSheet < cSheetInfo; iSheet++)
    {   // Update each array item with info we know about.
        rgSheetInfo[ iSheet].hInstance = hDLLInst;
        rgSheetInfo[ iSheet].langid = MAKELANGID(LANG_ENGLISH,SUBLANG_NEUTRAL);
    }

    // Set return values.
    *pcsi = cSheetInfo;
    *ppSheetInfo = rgSheetInfo;
    *ppNotUsed = NULL;

    return( TRUE);
}

//$--DeinitSheet()--------------------------------------------------------------
// Admin calls this when the property sheet dialog box is about to be removed.
// -----------------------------------------------------------------------------

void PASCAL DeinitSheet(
    IN VOID* pNotUsed)           // Extension data.
{
    DeinitSheet1();
}

//$--bInstallExtension()--------------------------------------------------------
// -----------------------------------------------------------------------------

BOOL PASCAL bInstallExtension(ADMIN_ObjectInfo * poi)
{
    return(TRUE);
}

//$--bDeinstallExtension()------------------------------------------------------
// -----------------------------------------------------------------------------

BOOL PASCAL bDeinstallExtension(ADMIN_ObjectInfo * poi)
{
    return(TRUE);
}

//$--bShowPage()----------------------------------------------------------------
// This is called once per standard property sheet.  Not at all for our property
// sheets.  If you want to disable some or all of the standard property sheets
// then return FALSE for those default property sheets.
// -----------------------------------------------------------------------------

BOOL PASCAL bShowPage( UINT iddAdminPage)
{
    BOOL bRet = TRUE;   // Show the standard property sheet.
    return( bRet);
}

//$--iStartPage()---------------------------------------------------------------
// This function is called once durring initialization.  Return -1 to cause the
// first standard property sheet to be displayed.  Or return the index of one of
// our property sheets to have it come up first.
// -----------------------------------------------------------------------------

INT PASCAL iStartPage( void)
{
    int i = -1; // Default to the first standard property sheet.
    return( i);
}

//$--ADMIN_Initialize()---------------------------------------------------------
// Exported function. This is the first function called by admin after the DLL
// has been initialized.
// -----------------------------------------------------------------------------

VOID PASCAL ADMIN_Initialize(
    IN  ADMIN_AdministratorConnections* pAdminConnections,  // Global Administrator Connections
    IN  ADMIN_AdministratorFunction*    pAdminFuncs,        // Global admin function
    OUT ADMIN_ExtensionFunction**       ppExtensionFunction)// Global Extension function
{
    DEBUGPUBLIC( "ADMIN_Initialize()\n");
    if( FAILED( CHK_ADMIN_Initialize( pAdminConnections, pAdminFuncs, ppExtensionFunction)))
        return;

    // We need to keep a global copy of admin's function pointers.
    pAdminFunctions = pAdminFuncs;

    // Tell admin where it can find the rest of our functions that it needs.
    *ppExtensionFunction = &extensionFunctions;
}

// -----------------------------------------------------------------------------

// =============================================================================
// Miscelaneous support functions.  The above functions are required by admin.,
// the functions listed below are not.
// =============================================================================

//$--HrLoadExtData()------------------------------------------------------------
// Loads the extension data from Exchange Admin into an array of property values.
// -----------------------------------------------------------------------------

HRESULT HrLoadExtData(
    IN  HWND            hDlg,            // Handle of property sheet dialog.
    IN  LPTSTR          lpszExtDataName, // Name of extension data to retrieve.
    OUT ULONG*          lpcExtProps,     // Ptr to receive count of ext data properties.
    OUT LPSPropValue*   lppExtProps)     // Ptr to ptr to extension data properties.
{
    HRESULT     hr               = NOERROR;
    RC          rc               = RC_SUCCESS;
    UINT        cTemp            = 0;
    ULONG       cRawExtData      = 0;    // Count of raw extension data blob.
    LPBYTE      lpbRawExtData    = NULL; // Ptr to raw extension data blob.
    LPWSTR      lpwszExtDataName = NULL; // Ptr to mapi allocated WIDE extension data name.

    //hr = CHK_HrLoadExtData( lpszExtDataName, lpcExtProps, lppExtProps);
    //if( FAILED( hr))
        //RETURN( hr);

    // Make sure the property count starts at zero.
    *lpcExtProps = 0;

    // Convert extension data name string to a wide string
    // which is required by Exchange Admin.
    hr = HrStrToStrW( lpszExtDataName, &lpwszExtDataName);
    if( FAILED( hr))
        goto cleanup;

    // Ask Exchange Admin the size of the buffer needed to hold the data.
    rc = pAdminFunctions->pfnGetObjectDataSize( hDlg, lpwszExtDataName, &cTemp);
    if( RC_FAILED( rc))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Convert UINT to a ULONG.
    cRawExtData = cTemp;

    // Allocate a buffer to hold the extension data returned from Exchange Admin.
    hr = MAPIAllocateBuffer( cRawExtData, (LPVOID*) &lpbRawExtData);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Get the extension data from Exchange Admin.
    rc = pAdminFunctions->pfnGetObjectData( hDlg, lpwszExtDataName, lpbRawExtData, cRawExtData);
    if( RC_FAILED(rc))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Unpack the Exchange Admin data into a MAPI style property value array.
    hr = HrCfgUnpackData( cRawExtData, lpbRawExtData, NULL, lpcExtProps, lppExtProps);
    if( FAILED( hr))
        goto cleanup;

cleanup:
    MAPIFREEBUFFER( lpwszExtDataName);
    MAPIFREEBUFFER( lpbRawExtData);

    RETURN( hr);
}

// -----------------------------------------------------------------------------
// Saves the extension data into an Exchange Admin buffer.  Admin will not
// commit this data until the user presses the Apply or OK button.  However the
// changed data is then available to other property sheets.
// -----------------------------------------------------------------------------

HRESULT HrSaveExtData(
    IN  HWND            hDlg,            // Handle of property sheet dialog.
    IN  LPTSTR          lpszExtDataName, // Name of extension data to save.
    IN  ULONG           cExtProps,       // Count of ext data properties.
    IN  LPSPropValue    lpExtProps,      // Ptr to extension data properties.
    IN  BOOL            bNew)            // Indicates the blob is new.
{
    HRESULT hr          = NOERROR;
    RC      rc          = RC_SUCCESS;
    ULONG   cExtData    = 0;
    LPBYTE  lpbExtData  = NULL;

    // Validate the data to save.
    if( cExtProps != 0 && !TEST_READ_PTR( lpExtProps, sizeof( SPropValue) * cExtProps))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Pack the configuration data from a MAPI style property value array
    // into a format that Exchange Admin will be able to save.
    hr = HrCfgPackData( lpszExtDataName, cExtProps, lpExtProps, &cExtData, &lpbExtData);
    if( FAILED( hr))
        goto cleanup;

    // Set the extension data using Exchange Admin.
    rc = pAdminFunctions->pfnSetObjectData( hDlg, lpbExtData, cExtData, bNew);
    if( RC_FAILED(rc))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER( lpbExtData);
    RETURN( hr);
}

//$--HrLoadDialogTemplate()-----------------------------------------------------
// Use this function to create child dialog box templates.  It will ensure that
// the correct language and fonts are used.
//
// To display the dialog use DialogBoxIndirect() which is a Windows SDK
// function.
// -----------------------------------------------------------------------------

HRESULT HrLoadDialogTemplate(
    IN  UINT     iddDialog,     // Resource ID of dialog.
    OUT HGLOBAL* phDlgTemplate) // Ptr to var to store handle in.
{
    HRESULT hr = NOERROR;

    if( !pAdminFunctions->pfnLoadDialogResource( hDLLInst, iddDialog,
            LANGIDFROMLCID(pAdminObjectInfo->lcid), (LPBYTE*) phDlgTemplate))
        hr = HR_LOG( E_FAIL);

    RETURN( hr);
}

// -----------------------------------------------------------------------------


ADMIN.H

// Admin.h ---------------------------------------------------------------------
// Functions to support Exchange Admin Configuration Extension Dialogs for C
// programs.  This does the majority of the handshaking necessary.
// (See ExAdmin.H for C++ support)
//
// This module is intended to be a generic MAIN source file for creating an
// Exchange Administration DLL.  You could take this file and include it in your
// project as starting place for your DLL.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef __ADMIN_H__
#define __ADMIN_H__

#include "adminext.h"

// -----------------------------------------------------------------------------
// Functions that are required or used by Exchange Admin.
// -----------------------------------------------------------------------------

// This function initializes the property sheet info and returns it to admin.
//
// Since this piece of code is written to be a generic MAIN source file for
// creating an Exchange Administration DLL we use global data defined outside
// of this module to contain the information about the property sheet dialogs.
// You will need to make sure your code contains the following two globals:
//
//  1) ADMIN_SheetInfo rgSheetInfo[]                Array of info about your
//                                                  property sheets.
//
//  2) int cSheetInfo = ARRAY_CNT( rgSheetInfo);    The count of property sheets
//                                                  in the array.

BOOL PASCAL bInitSheet(                 // RETURNS: TRUE if initialization OK
    IN  ADMIN_ObjectInfo* poi,          // Computer name & DN.
    IN  ULONG             fFlags,       // Tells us if it is read only or not.
    OUT ADMIN_SheetInfo** ppSheetInfo,  // Property sheet description array
    OUT UINT*             pcsi,         // Number of property sheets
    OUT VOID**            ppNotUsed);   // Local data

// Admin calls this when the property sheet dialog box is about to be removed.
void PASCAL DeinitSheet(
    IN VOID* pNotUsed);          // Extension data.

BOOL PASCAL bInstallExtension(ADMIN_ObjectInfo * poi);
BOOL PASCAL bDeinstallExtension(ADMIN_ObjectInfo * poi);

// This is called once per standard property sheet.  Not at all for our property
// sheets.  If you want to disable some or all of the standard property sheets
// then return FALSE for those default property sheets.
BOOL PASCAL bShowPage( UINT iddAdminPage);

// This function is called once durring initialization.  Return -1 to cause the
// first standard property sheet to be displayed.  Or return the index of one of
// our property sheets to have it come up first.
INT PASCAL iStartPage( void);

// Exported function. This is the first function called by admin after the DLL
// has been initialized.
VOID PASCAL ADMIN_Initialize(
    IN  ADMIN_AdministratorConnections* pAdminConnections,  // Global Administrator Connections
    IN  ADMIN_AdministratorFunction*    pAdminFuncs,        // Global admin function
    OUT ADMIN_ExtensionFunction**       ppExtensionFunction);// Global Extension function

// -----------------------------------------------------------------------------
// Miscelaneous support functions that are not required by Exchange Admin.
// -----------------------------------------------------------------------------

// Loads the extension data from Exchange Admin into an array of property values.
HRESULT HrLoadExtData(
    IN  HWND            hDlg,            // Handle of property sheet dialog.
    IN  LPTSTR          lpszExtDataName, // Name of extension data to retrieve.
    OUT ULONG*          lpcExtProps,     // Ptr to receive count of ext data properties.
    OUT LPSPropValue*   lppExtProps);    // Ptr to ptr to extension data properties.

// Saves the extension data into an Exchange Admin buffer.  Admin will not
// commit this data until the user presses the Apply or OK button.  However the
// changed data is then available to other property sheets.
HRESULT HrSaveExtData(
    IN  HWND            hDlg,            // Handle of property sheet dialog.
    IN  LPTSTR          lpszExtDataName, // Name of extension data to save.
    IN  ULONG           cExtProps,       // Count of ext data properties.
    IN  LPSPropValue    lpExtProps,      // Ptr to extension data properties.
    IN  BOOL            bNew);           // Indicates the blob is new.

// Use this function to create child dialog box templates.  It will ensure that
// the correct language and fonts are used.
//
// To display the dialog use DialogBoxIndirect() which is a Windows SDK
// function.
HRESULT HrLoadDialogTemplate(
    IN  UINT     iddDialog,     // Resource ID of dialog.
    OUT HGLOBAL* phDlgTemplate);// Ptr to var to store handle in.

// -----------------------------------------------------------------------------
// Global data declared in Admin.C
// -----------------------------------------------------------------------------

extern HINSTANCE hDLLInst;
extern BOOL      bReadOnly;     // The mode is read only if TRUE.

extern ADMIN_AdministratorFunction* pAdminFunctions;  // Admin functions available to you.
extern ADMIN_ObjectInfo*            pAdminObjectInfo; // Admin server name and other useful DNs.

// -----------------------------------------------------------------------------
// Globals and external functions that must be defined in the dialog code that
// you write.  (See ExtDatVw.C as an example.)
// -----------------------------------------------------------------------------

// This is a list of functions that Exchange Admin will call.  You will need to
// fill this list with the address of your functions as well as some of the
// functions contained in Admin.C.
extern ADMIN_ExtensionFunction extensionFunctions;

// You will need to provide the resource ID of your dialogs, the address of your
// dialog processing functions, and the resource ID of the string to place on
// the property sheet tag.
extern ADMIN_SheetInfo rgSheetInfo[];
extern int cSheetInfo;  // Set to ARRAY_CNT( rgSheetInfo)

// External function to deinitialize first property sheet.  It should call the deinit
// for the second property sheet if there is one, and so on.
//
// You will need to implement this function in your code!
void DeinitSheet1();

// -----------------------------------------------------------------------------
#endif // __ADMIN_H__


EDITDLG.C

// EditDlg.c -------------------------------------------------------------------
// Functions to support a dialog that lets the user select a property type and
// enter a data value for it.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "resource.h"

// -----------------------------------------------------------------------------

#define MAX_DATA_VALUE      600     // Maximum data value we can edit.

// -----------------------------------------------------------------------------
// Global handels to controls used in this module.
// -----------------------------------------------------------------------------

HWND hCboType       = NULL;
HWND hEditDataValue = NULL;

// -----------------------------------------------------------------------------
// Defined in ExtDatVw.C
// -----------------------------------------------------------------------------

extern LPSPropValue lpEditPropVal;  // Pointer to property to edit.
extern HWND         hAdminWnd;      // Handle to parent window.
extern TCHAR        szAdminTitle[]; // Title of parent window.

HRESULT HrFormatPrValue(
    IN  LPSPropValue lpPropVal, // Ptr to property to format.
    OUT LPTSTR*      lppszBuf); // Ptr to ptr to allocated output buffer.

// -----------------------------------------------------------------------------
// Table to contain property types that we support the editing of.
// -----------------------------------------------------------------------------

typedef struct
{
    LPTSTR  lpsz;
    ULONG   ul;
} PropType;

#define PROP_TBL_ENTRY( PropName)      {TEXT( #PropName), PropName},

// Ordered by frequency of use.
PropType PropTypeArray[] =
{
    PROP_TBL_ENTRY( PT_STRING8)
    PROP_TBL_ENTRY( PT_LONG)
    PROP_TBL_ENTRY( PT_BOOLEAN)
    PROP_TBL_ENTRY( PT_I2)
    PROP_TBL_ENTRY( PT_R4)
    PROP_TBL_ENTRY( PT_DOUBLE)
};

//$--OnInitDialog()-------------------------------------------------------------
// Initialize the dialog.
// -----------------------------------------------------------------------------

static VOID OnInitDialog(
    IN HWND    hDlg)    // Dialog's window handle.
{
    HRESULT hr          = NOERROR;
    LPTSTR  lpszProp    = NULL;
    LPTSTR  lpszValue   = NULL;
    ULONG   iType       = 0;

    // Get the handle of the list box view control.
    hCboType = GetDlgItem( hDlg, IDC_CBO_TYPE);
    if( hCboType == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Get the handle of the list box view control.
    hEditDataValue = GetDlgItem( hDlg, IDC_EDIT_DATA_VALUE);
    if( hEditDataValue == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Initialize the combo box with data types that we allow entry on.
    for( iType = 0; iType < ARRAY_CNT( PropTypeArray); iType ++)
        SendMessage( hCboType, CB_ADDSTRING, 0, (LPARAM) PropTypeArray[ iType].lpsz);

    // Do we have initial data?
    if( !lpEditPropVal)
        // No initial data so default to first combo box item.
        SendMessage( hCboType, CB_SETCURSEL, 0, 0);
    else
    {   // YES, we have initial data so initialize the controls.

        // Find the property type in the PropTypeArray and set combo box index
        // to the same index found in the array.
        // The combo box list is NOT sorted so there will be a one to one
        // correlation between the PropTypeArray and the items in the list.
        for( iType = 0; iType < ARRAY_CNT( PropTypeArray); iType ++)
        {
            if( PROP_TYPE( lpEditPropVal->ulPropTag) == PropTypeArray[ iType].ul)
                SendMessage( hCboType, CB_SETCURSEL, (WPARAM) iType, 0);
        }

        // Format the property value into a string that the user can edit.
        hr = HrFormatPrValue( lpEditPropVal, &lpszProp);
        if( FAILED( hr))
            goto cleanup;

        lpszValue = _tcschr( lpszProp, TEXT( '\t')) + 1;
        if( lpszValue == (LPTSTR) 1)
        {   // This should have found the tab character in the formated string.
            hr = HR_LOG( E_FAIL);
            goto cleanup;
        }

        // Set the text of the edit control to the formated value.
        SetDlgItemText( hDlg, IDC_EDIT_DATA_VALUE, lpszValue);

        // This pointer currently points to unfreeable memory.  We will allocate
        // data and set this to point to it later.  Until then we don't want anyone
        // freeing data that should not be freed so we MUST set it to NULL to avoid
        // potential problems later.
        lpEditPropVal = NULL;

        MAPIFREEBUFFER( lpszProp);
    }

cleanup:
    MAPIFREEBUFFER( lpszProp);
}

//$--OnCmdOk()------------------------------------------------------------------
// When the OK button is pressed we allocate a property value structure and save
// the users data in it.
// -----------------------------------------------------------------------------

static VOID OnCmdOk(
    IN HWND    hDlg)    // Dialog's window handle.
{
    HRESULT hr                           = 0;
    TCHAR   szDataValue[ MAX_DATA_VALUE] = {TEXT( '\0')};// Buffer for intermediate data.
    int     iType                        = 0;
    ULONG   ulPropType                   = 0;
    LPBYTE  lpbData                      = NULL;

    // Validate that the combo field has a selection.
    iType = SendMessage( hCboType, CB_GETCURSEL, 0, 0);
    if( iType == CB_ERR)
    {
        MessageBox( hAdminWnd, "You must select a data type.", szAdminTitle, MB_OK);
        SetFocus( hCboType);
        goto cleanup;
    }

    // Set the true MAPI property type from combo box selection.
    ulPropType = PropTypeArray[ iType].ul;

    // Get the data value entered by the user.
    GetDlgItemText( hDlg, IDC_EDIT_DATA_VALUE, szDataValue, sizeof( szDataValue));

    // Allocate a buffer to handle the biggest property we will need to deal with.
    hr = MAPIAllocateBuffer( sizeof( SPropValue) + MAX_DATA_VALUE, &lpEditPropVal);
    if( FAILED( hr))
        goto cleanup;

    // Set pointer to buffer of extra data.
    lpbData = ((LPBYTE) lpEditPropVal) + sizeof( SPropValue);

    // Set the property tag.  Using 1 for the ID keeps it from being
    // considered invalid by some of the MAPI routines.
    lpEditPropVal->ulPropTag = PROP_TAG( ulPropType, 1);

    switch( ulPropType)
    {
        case PT_I2:
            lpEditPropVal->Value.i = (int) _ttol( szDataValue);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_LONG:
            lpEditPropVal->Value.l = _ttol( szDataValue);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_R4:
            lpEditPropVal->Value.flt = (float) atof( szDataValue);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_DOUBLE:
            lpEditPropVal->Value.dbl = atof( szDataValue);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_BOOLEAN:
            if( _tcsicmp( szDataValue, TEXT( "TRUE")) == 0)
            {
                lpEditPropVal->Value.b = TRUE;
                break;
            }
            else if( _tcsicmp( szDataValue, TEXT( "FALSE")) == 0)
            {
                lpEditPropVal->Value.b = FALSE;
                break;
            }

            MessageBox( hAdminWnd, "You must enter either TRUE or FALSE.", szAdminTitle, MB_OK);
            SetFocus( hEditDataValue);
            goto cleanup;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_STRING8:
            lpEditPropVal->Value.LPSZ = lpbData;
            lstrcpy( lpbData, szDataValue);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        default:
            MessageBox( hAdminWnd, "Unknown property type.", szAdminTitle, MB_OK);
            SetFocus( hEditDataValue);
            goto cleanup;
    }

    // Normal termination.
    EndDialog( hDlg, IDOK);
    return;

cleanup:
    MAPIFREEBUFFER( lpEditPropVal);
}

//$--OnWmCommand()--------------------------------------------------------------
// Processes a command sent to the dialog box. In this case, commands are sent
// to dialog box by the controls when the control values have changed or when
// a button is pressed.
// -----------------------------------------------------------------------------

static VOID OnWmCommand(
    IN HWND    hDlg,    // Dialog's window handle.
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    WORD wNotifyCode = HIWORD( wParam);     // Notification code
    WORD wID         = LOWORD( wParam);     // Id of control

    // Has an edit control changed or a button been clicked?
    if( wNotifyCode == EN_CHANGE || wNotifyCode == BN_CLICKED)
    {   // YES, so process the change or click.
        switch( wID)
        {
            case IDOK:
                OnCmdOk( hDlg);
                return;

            case IDCANCEL:
                EndDialog( hDlg, IDCANCEL);
                return;

            default:
                return;
        }
    }
}

//$--EditDialogProc()-----------------------------------------------------------
// Dialog procedure for this dialog.
// -----------------------------------------------------------------------------

BOOL CALLBACK EditDialogProc( // RETURNS: TRUE if msg was processed.
    IN HWND    hDlg,    // Dialog's window handle.
    IN UINT    wMsg,    // Window message to be processed
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    WORD wNotifyCode = HIWORD( wParam);     // Notification code
    WORD wID         = LOWORD( wParam);     // Id of control

    switch( wMsg)
    {
        case WM_INITDIALOG:
            OnInitDialog( hDlg);
            return( TRUE);

        case WM_COMMAND:
            OnWmCommand( hDlg, wParam, lParam);
            return( TRUE);

        default:
            return( FALSE);
    }
}

// -----------------------------------------------------------------------------


EXTDATVW.C

// ExtDatVw.C ------------------------------------------------------------------
// Functions to process the main extension data viewer property sheet.
// See Admin.H and Admin.C for main handshaking with Exchange Admin.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "retcode.h"
#include "admin.h"
#include "resource.h"
#include "PrValAry.h"

//----------------------------------------------------------------------------
// Some of the global data needed by this module.  See end of file for more.
//----------------------------------------------------------------------------

HWND    hAdminWnd           = NULL; // Handle to parent window.
HWND    hEditExtDataName    = NULL;
HWND    hLBView             = NULL;
HGLOBAL hEditDlgTemplate    = NULL;

BOOL    bExtDataHasChanged  = FALSE;

TCHAR   szAdminTitle[100]   = TEXT( "");    // Title of parent window.
TCHAR   szExtDataName[100]  = TEXT( "");
TCHAR   szSaveChangesMsg[]  = TEXT( "The extension data has been changed but not saved.  "
                                    "Do you wish to save your changes first?\n"
                                    "\n"
                                    "(press 'Yes' button followed by 'Apply' button to save changes)");
WNDPROC EditCtlWndProc      = NULL;

BOOL    bNewExtData         = TRUE;

#define PVA_EXPAND_AMT          10      // Amount to expand the array as it grows.
PropValArray    pvaExtProps     = {0};  // See PrValAry.H for this data structure.
LPSPropValue    lpEditPropVal   = NULL; // Pointer to property to edit.

//----------------------------------------------------------------------------
// Dialog procedure for the edit property dialog implemented in EditDlg.C.
//----------------------------------------------------------------------------

BOOL CALLBACK EditDialogProc( // RETURNS: TRUE if msg was processed.
    IN HWND    hDlg,    // Dialog's window handle.
    IN UINT    wMsg,    // Window message to be processed
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam);  // Window message parameter2

//----------------------------------------------------------------------------
// These integers control the size of the buffer we allocate for the
// _vsnprintf() function call.  It grows when _vsnprintf() returns an
// error.  The effect is that we remember the largest buffer needed for
// string formating and always start off by allocating that size of buffer.
// This provides a safe string formating method.
//----------------------------------------------------------------------------

static int nLargestBuf = 256;
static int nGrowSize = 128;

//----------------------------------------------------------------------------
// This performs a safe format with no memory overwrites.
//
// Takes arguments almost like sprintf().  The exception is that the first
// parameter is a pointer to a pointer to a buffer.  We dynamicaly allocate
// the buffer to the largest size we have encountered so far and increase it
// if needed.
//----------------------------------------------------------------------------

HRESULT HrFormat(
    OUT LPTSTR* lppszBuf, // Ptr to ptr to allocated output buffer.
    IN  LPTSTR  pFmt,     // Ptr to format string.
    ...)                  // Any other parameters needed.
{
    HRESULT hr    = NOERROR;
    va_list pVArg = {0};

    va_start( pVArg, pFmt);

    for( ;;)
    {   // Take a shot at formating the string.
        hr = MAPIAllocateBuffer( nLargestBuf, lppszBuf);
        if( FAILED( hr))
            goto cleanup;

        if( _vsntprintf( *lppszBuf, nLargestBuf, pFmt, pVArg) >= 0)
           break;   // Format was successful.

        // Buffer was not big enough so enlarge it.
        nLargestBuf += nGrowSize;
        nGrowSize = (int) (nGrowSize * 3 / 2);

        // Free what we are not using so we can reallocate it.
        MAPIFREEBUFFER( *lppszBuf);
    }

cleanup:
    RETURN( hr);
}

// -----------------------------------------------------------------------------
// This formats the property value into a string buffer that you will have to
// free using MAPIFreeBuffer.
//
// NOTE: A number of the possible property types don't have a conversion at
//       this time.
// -----------------------------------------------------------------------------

HRESULT HrFormatPrValue(
    IN  LPSPropValue lpPropVal, // Ptr to property to format.
    OUT LPTSTR*      lppszBuf)  // Ptr to ptr to allocated output buffer.
{
    HRESULT     hr      = NOERROR;
    int         ii      = 0;
    unsigned    ui      = 0;
    double      dbl     = 0;
    LPTSTR      lpszBuf = NULL;
    LPBYTE      pByte   = NULL;
    SYSTEMTIME  st      = {0};
    __UPV       upv     = lpPropVal->Value;

    switch( PROP_TYPE( lpPropVal->ulPropTag))
    {
        case PT_I2:
            ii = upv.i; // Convert short unsigned to regular unsigned.
            hr = HrFormat( lppszBuf, "PT_I2\t%d", ii);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_LONG:
            hr = HrFormat( lppszBuf, "PT_LONG\t%ld", upv.ul);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_R4:
            dbl = upv.flt;
            hr = HrFormat( lppszBuf, "PT_R4\t%g", dbl);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_DOUBLE:
            hr = HrFormat( lppszBuf, "PT_DOUBLE\t%g", upv.dbl);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_BOOLEAN:
            if( upv.b)
                hr = HrFormat( lppszBuf, "PT_BOOLEAN\tTRUE");
            else
                hr = HrFormat( lppszBuf, "PT_BOOLEAN\tFALSE");
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_SYSTIME:
            if( FileTimeToSystemTime( &upv.ft, &st))
                hr = HrFormat( lppszBuf, "PT_SYSTIME\t%.2d:%.2d:%.2d %.2d/%.2d/%.2d",
                    st.wHour, st.wMinute, st.wSecond,
                    st.wMonth, st.wDay, st.wYear);
            else
                hr = HrFormat( lppszBuf, "PT_SYSTIME\tError %lu converting file time to system time.", GetLastError());
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_STRING8:
            hr = HrFormat( lppszBuf, "PT_STRING8\t%s", upv.lpszA);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_BINARY:
            // Format the count and a place holder for up to 400 hex digits.
            hr = HrFormat( lppszBuf, "PT_BINARY\tCB:%d 0x%-400s", upv.bin.cb, TEXT( "PlaceHolder"));

            // Make sure we don't format more than 200 bytes (400 hex digits).
            if( upv.bin.cb > 200)
                ii = 200;
            else
                ii = upv.bin.cb;

            // Find "PlaceHolder" to set the starting point for formating.
            lpszBuf = _tcsstr( *lppszBuf, TEXT("PlaceHolder"));

            // Ptr to first byte in binary data to format.
            pByte = upv.bin.lpb;

            // Loop to format into hex digits.
            while( ii)
            {   // Format a single byte into a hex value and add to sRetVal.
                ui = *pByte;
                sprintf( lpszBuf, "%.2x", ui);

                // Next byte.
                lpszBuf += 2;
                pByte ++;
                ii --;
            }
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_ERROR:
        {
            hr = HrFormat( lppszBuf, "PT_ERROR\tError %.8lx", upv.err);
            break;
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        default:
            hr = HrFormat( lppszBuf, "PT_%d\thas no conversion at this time.", PROP_TYPE( lpPropVal->ulPropTag));
            break;
    }

    RETURN( hr);
}

//$--OnRefreshBtnClick()--------------------------------------------------------
// This is called when the refresh button is clicked or the extension data name
// edit control loses focus.  We need to verify that the user has saved changes
// before we refresh.
// -----------------------------------------------------------------------------

static VOID OnRefreshBtnClick(
    IN HWND    hDlg)    // Dialog's window handle.
{
    HRESULT         hr          = NOERROR;
    ULONG           cExtProps   = 0;      // Count of ext data properties.
    ULONG           iProp       = 0;      // Index to a single property.
    int             iSaveChanges= 0;
    LPTSTR          lpszProp    = NULL;   // Formated property type and value for display.
    LPSPropValue    lpExtProps  = NULL;   // Ptr to extension data properties.

    // If extension data has changed make sure user wants to loose their changes.
    if( bExtDataHasChanged)
    {
        iSaveChanges = MessageBox( hDlg, szSaveChangesMsg, szAdminTitle, MB_YESNO);
        if( iSaveChanges == IDYES)
            return; // Cancel the REFRESH so user can press the apply button.
    }

    // At this point we know that data has been saved if it had been altered.

    // Get the extension data name from the edit control.
    GetDlgItemText( hDlg, IDC_EDIT_EXT_DATA_NAME, szExtDataName, sizeof( szExtDataName));

    // Return if extension data name has not been supplied.
    if( szExtDataName[0] == TEXT( '\0'))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Free old property value array.
    PVA_Destroy( &pvaExtProps);

    // Clear the contents of the listbox.
    SendMessage( hLBView, LB_RESETCONTENT, 0, 0);

    // Apply could be enabled so use this flag to tell us
    // not to save anything unless it really changes later.
    bExtDataHasChanged = FALSE;

    // Load the extension data as a property value array.  Ignore any
    // errors since they are expected.
    hr = HrLoadExtData( hDlg, szExtDataName, &cExtProps, &lpExtProps);
    bNewExtData = FAILED( hr); // We expect this to fail if creating new extension data.

    // Initialize the property value array structure.
    hr = PVA_HrInit( &pvaExtProps, PVA_EXPAND_AMT, cExtProps, lpExtProps);
    if( FAILED( hr))
        goto cleanup;

    // Loop to display the property values.
    for( iProp = 0; iProp < cExtProps; iProp++)
    {
        hr = HrFormatPrValue( lpExtProps + iProp, &lpszProp);
        if( FAILED( hr))
            goto cleanup;

        SendMessage( hLBView, LB_ADDSTRING, 0, (LPARAM) lpszProp);
        MAPIFREEBUFFER( lpszProp);
    }

    // Leave a line at end of list for inserting at end.
    SendMessage( hLBView, LB_ADDSTRING, 0, (LPARAM) TEXT( "*** end ***"));

cleanup:
    if( FAILED( hr))
        SendMessage( hLBView, LB_RESETCONTENT, 0, 0); // Clear the contents of the listbox.

    return;
}

//$--OnWmKillFocus()------------------------------------------------------------
// Handles the kill focus for the extension data name edit control.  We need
// to refresh the list box if the user changed the extension data name.
// -----------------------------------------------------------------------------

static void OnWmKillFocus(
    IN HWND    hWnd,    // Window handle of edit control.
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    HWND hDlg = NULL;

    // If the extension data name has not change there is no need to refresh.
    if( szExtDataName[0] != TEXT('\0'))
        return;

    // Get the parent dialog handle.
    hDlg = GetParent( hWnd);

    // Refresh the list box.
    OnRefreshBtnClick( hDlg);
}

//$--EditWindowProc()-----------------------------------------------------------
// Window procedure for the edit control.
// -----------------------------------------------------------------------------

BOOL CALLBACK EditWindowProc( // RETURNS: TRUE if msg was processed.
    IN HWND    hWnd,    // Window handle.
    IN UINT    wMsg,    // Window message to be processed
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    switch( wMsg)
    {
        case WM_KILLFOCUS:
            OnWmKillFocus( hWnd, wParam, lParam);
            // Intentionaly drop through to also do the default processing.

        default:
            return( CallWindowProc( EditCtlWndProc, hWnd, wMsg, wParam, lParam));
    }
}

// -----------------------------------------------------------------------------
// Initialize the dialog.
// -----------------------------------------------------------------------------

static VOID OnInitDialog(
    IN HWND    hDlg)    // Dialog's window handle.
{
    int nTabStops[] = {60};    // Array of tab stops.

    // Get the parent window handle.
    hAdminWnd = GetParent( hDlg);

    // Get the parent window title so we can use it in message boxes.
    GetWindowText( hAdminWnd, szAdminTitle, sizeof(szAdminTitle));

    // Tell admin where to find our ICON and TITLE controls so it can display
    // the standard picture and text.
    pAdminFunctions->pfnSetIcon( hDlg, IDC_ADMINICON);
    pAdminFunctions->pfnSetTitle( hDlg, IDC_TITLE);

    // Get the handle of the extension data name edit control.
    hEditExtDataName = GetDlgItem( hDlg, IDC_EDIT_EXT_DATA_NAME);
    if( hEditExtDataName == NULL)
    {
        HR_LOG( E_FAIL);
        return;
    }

    // Subclass the window procedure to capture the kill focus message.
    EditCtlWndProc = (WNDPROC) SetWindowLong( hEditExtDataName, GWL_WNDPROC, (LONG) EditWindowProc);

    // Get the handle of the list box view control.
    hLBView = GetDlgItem( hDlg, IDC_LB_VIEW);
    if( hLBView == NULL)
    {
        HR_LOG( E_FAIL);
        return; // Can't use goto since handle is invalid.
    }

    // Gives us an arbitrarly large amount of room to scroll horizontaly.
    SendMessage( hLBView, LB_SETHORIZONTALEXTENT, 4000, 0);

    // Set the tab stops.
    SendMessage( hLBView, LB_SETTABSTOPS, ARRAY_CNT( nTabStops), (LPARAM) ((int*) nTabStops));
}

//$--OnExtDataNameChange()------------------------------------------------------
// When the extension data name is changed this function is called.  We need to
// make sure the user has saved changes that may have been made to previous data.
// -----------------------------------------------------------------------------

static VOID OnExtDataNameChange(
    IN HWND    hDlg)    // Dialog's window handle.
{
    static BOOL bRecurse = FALSE;   // Used to avoid recursion
    int iSaveChanges = 0;

    if( !bExtDataHasChanged)
        goto cleanup;

    if( bRecurse)
        goto cleanup;
    bRecurse = TRUE;

    iSaveChanges = MessageBox( hDlg, szSaveChangesMsg, szAdminTitle, MB_YESNO);
    if( iSaveChanges == IDYES)
    {   // This causes recursion.
        SetDlgItemText( hDlg, IDC_EDIT_EXT_DATA_NAME, szExtDataName);
        goto cleanup;
    }

    // Indicates the extension data to view has changed.
    szExtDataName[0] = TEXT( '\0');

    // Indicates the extension data has not changed.
    bExtDataHasChanged = FALSE;

cleanup:
    bRecurse = FALSE;
}

//$--HrEditValue()--------------------------------------------------------------
// Supports editing of a property value when either the New or Modify button is
// pressed.
// -----------------------------------------------------------------------------

static HRESULT HrEditValue(
    IN HWND hDlg,  // Dialog's window handle.
    IN BOOL bAdd)  // TRUE if adding a new one.
{
    HRESULT hr       = NOERROR;
    int     iRet     = 0;
    ULONG   ulCurSel = SendMessage( hLBView, LB_GETCURSEL, 0, 0);
    LPTSTR  lpszProp = NULL;   // Formated property type and value for display.

    if( ulCurSel == LB_ERR)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Don't let them modify the last item in the list which is only a place holder.
    if( !bAdd && ulCurSel == pvaExtProps.cTotal)
        goto cleanup;

    // Is this the first time we tried to edit data?
    if( !hEditDlgTemplate)
    {   // YES, so load and initialize the dialog template.
        hr = HrLoadDialogTemplate( IDD_EDIT, &hEditDlgTemplate);
        if( FAILED( hr))
            goto cleanup;
    }

    // Make data to edit available.
    if( bAdd)
        lpEditPropVal = NULL;
    else
        lpEditPropVal = pvaExtProps.lpPropVal + ulCurSel;

    // Bring up the dialog to create the data.
    iRet = DialogBoxIndirect( hDLLInst, hEditDlgTemplate, hDlg, EditDialogProc);
    if( iRet == IDOK)
    {   // The user has wants to save his/her changes.
        if( !lpEditPropVal)
        {
            hr = HR_LOG( E_FAIL);
            goto cleanup;
        }

        // Insert or modify the array.
        if( bAdd)
            hr = PVA_HrInsert( &pvaExtProps, ulCurSel, lpEditPropVal);
        else
            hr = PVA_HrModify( &pvaExtProps, ulCurSel, lpEditPropVal);
        if( FAILED( hr))
            goto cleanup;

        // Format the edited data for display in the list box.
        hr = HrFormatPrValue( lpEditPropVal, &lpszProp);
        if( FAILED( hr))
            goto cleanup;

        // If modifying an existing one delete it and then reinsert it.
        if( !bAdd)
            SendMessage( hLBView, LB_DELETESTRING, ulCurSel, 0);

        // Insert modified data and set the selection to it.
        SendMessage( hLBView, LB_INSERTSTRING, ulCurSel, (LPARAM) lpszProp);
        if( bAdd)
            ulCurSel ++;
        SendMessage( hLBView, LB_SETCURSEL, ulCurSel, 0);

        // Free the formated data string.
        MAPIFREEBUFFER( lpszProp);

        pAdminFunctions->pfnInfoHasChanged( hDlg);
        bExtDataHasChanged = TRUE;
    }
    else if( iRet != IDCANCEL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

cleanup:
    RETURN( hr);
}

//$--OnDeleteBtnClick()---------------------------------------------------------
// Delete a single property value from the array and list box.
// -----------------------------------------------------------------------------

static VOID OnDeleteBtnClick(
    IN HWND    hDlg)    // Dialog's window handle.
{
    HRESULT hr;
    ULONG   ulCurSel = SendMessage( hLBView, LB_GETCURSEL, 0, 0);

    if( ulCurSel == LB_ERR)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Don't let them delete the last item in the list which is only a place holder.
    if( ulCurSel == pvaExtProps.cTotal)
        goto cleanup;

    // Delete the selected item from the array.
    hr = PVA_HrDelete( &pvaExtProps, ulCurSel);
    if( FAILED( hr))
        goto cleanup;

    // Delete the item for the list box and set the selection to the item below it.
    SendMessage( hLBView, LB_DELETESTRING, ulCurSel, 0);
    SendMessage( hLBView, LB_SETCURSEL, ulCurSel, 0);

    pAdminFunctions->pfnInfoHasChanged( hDlg);
    bExtDataHasChanged = TRUE;

cleanup:;
}

//$--OnWmCommand()--------------------------------------------------------------
// Processes a command sent to the dialog box. In this case, commands are sent
// to a dialog box by the controls when the control values have changed or when
// a button is pressed.
// -----------------------------------------------------------------------------

static BOOL OnWmCommand(
    IN HWND    hDlg,    // Dialog's window handle.
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    HRESULT hr       = NOERROR;
    WORD wNotifyCode = HIWORD( wParam);     // Notification code
    WORD wID         = LOWORD( wParam);     // Id of control

    // Has an edit control changed or a button been clicked?
    if( wNotifyCode == EN_CHANGE || wNotifyCode == BN_CLICKED)
    {   // YES, so process the change or click.
        switch( wID)
        {
            case IDC_EDIT_EXT_DATA_NAME:
                OnExtDataNameChange( hDlg);
                return( TRUE);

            case IDC_BTN_REFRESH:
                OnRefreshBtnClick( hDlg);
                return( TRUE);

            case IDC_BTN_ADD:
                hr = HrEditValue( hDlg, TRUE);
                if( FAILED( hr));
                return( TRUE);

            case IDC_BTN_MODIFY:
                hr = HrEditValue( hDlg, FALSE);
                if( FAILED( hr));
                return( TRUE);

            case IDC_BTN_DELETE:
                OnDeleteBtnClick( hDlg);
                return( TRUE);

            default:
                return( FALSE);
        }
    }
}

//$--DialogProc() --------------------------------------------------------------
// Dialog procedure for the property sheet.
// -----------------------------------------------------------------------------

BOOL CALLBACK DialogProc( // RETURNS: TRUE if msg was processed.
    IN HWND    hDlg,    // Dialog's window handle.
    IN UINT    wMsg,    // Window message to be processed
    IN WPARAM  wParam,  // Window message parameter1
    IN LPARAM  lParam)  // Window message parameter2
{
    switch( wMsg)
    {
        case WM_INITDIALOG:
            OnInitDialog( hDlg);
            return( TRUE);

        case WM_COMMAND:
            return( OnWmCommand( hDlg, wParam, lParam));

        case WM_CHILDACTIVATE:
            if( !bNewExtData)
                OnRefreshBtnClick( hDlg);
            return( TRUE);

        default:
            return( FALSE);
    }
}

//$--bSaveData()----------------------------------------------------------------
// Called by Exchange Admin when the property sheet page loses focus.  This is
// where all data needs to be saved so that other property sheets can have
// access to it.  Exchange Admin will not commit the data to permanant media
// until the OK or APPLY button has been pressed.
// -----------------------------------------------------------------------------

BOOL PASCAL bSaveData(   // Returns: TRUE if successful.
    IN VOID* pNotUsed,       // Extension data for all sheets
    IN HWND  hDlg)           // Window handle of the property sheet.
{
    HRESULT hr   = NOERROR;
    BOOL    bRet = TRUE;

    DEBUGPUBLIC( "bSaveData()\n");
    //if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        //return( FALSE);

    // If our internal flag tells us that the data has not changed then don't save it.
    if( !bExtDataHasChanged)
        goto cleanup;

    // Packs the property value array and passes it to Exchange Admin for storage.
    hr = HrSaveExtData( hDlg, szExtDataName, pvaExtProps.cTotal,
                        pvaExtProps.lpPropVal, bNewExtData);
    if( FAILED( hr))
    {
        bRet = FALSE;
        goto cleanup;
    }

    // The data is not new anymore.
    bNewExtData = FALSE;

    // Indicates the extension data has not changed.
    bExtDataHasChanged = FALSE;

    // Clear the extension data name (in memory) so it will refresh
    // when we come back to form.  The name will still be on the form
    // so we can read it again later.
    szExtDataName[0] = TEXT( '\0');

cleanup:
    return( bRet);
}

//$--bCommitData()--------------------------------------------------------------
// This function is called by Exchange Admin when the user presses either the
// OK or the APPLY buttons.  We do not save data here.  We do that in the
// bSaveData() function.  Exchange Admin will commit the data to the data store
// if we return TRUE.
//
// Possible uses of this function are to allow you to display a message telling
// the user to restart an application or to save data to another location
// besides the Exchange Admin managed data store.
// -----------------------------------------------------------------------------

BOOL PASCAL bCommitData(            // Returns:  TRUE if successful.
    IN VOID* pNotUsed,       // Extension data.
    IN HWND  hDlg)                  // Handle of the property sheet to be committed.
{
    DEBUGPUBLIC( "bCommitData()\n");
    //if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        //return( FALSE);

    return( TRUE);
}

//$--bHasHelp()-----------------------------------------------------------------
// Called by Exchange Admin to find out if we support help.
// -----------------------------------------------------------------------------

BOOL PASCAL bHasHelp(               // RETURNS: true if help is available.
    IN VOID* pNotUsed,       // Ext dll managed data area.
    IN HWND  hDlg)                  // Window handle of the property sheet.
{
    DEBUGPUBLIC( "bHasHelp()\n");
    //if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        //return( FALSE);

    return( FALSE);
}

//$--DoHelp()-------------------------------------------------------------------
// Called by Exchange Admin to displays help for the user.
// -----------------------------------------------------------------------------

VOID PASCAL DoHelp(
    IN VOID* pNotUsed,       // Ext dll managed data area.
    IN HWND  hDlg)           // Window handle of the property sheet.
{
    DEBUGPUBLIC( "DoHelp()\n");
    //if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        //return;
}

//$--DeinitSheet1()-------------------------------------------------------------
// This function will be called by the DeinitSheet() function in Admin.C.  If
// you were to implement more than one property sheet this function would need
// to call a DeinitSheet2() function for the next sheet and so on for each
// additional sheet.
// -----------------------------------------------------------------------------

VOID DeinitSheet1()
{
    // Destroy the property value array.
    PVA_Destroy( &pvaExtProps);

    // If we loaded a dialog template we need to free it.
    if( hEditDlgTemplate)
        pAdminFunctions->pfnFreeDialogResource( (LPBYTE*) &hEditDlgTemplate);

    // Call other dialog's DeinitSheet functions here.
}

// =============================================================================
// Globals needed by Admin.C to perform the handshaking with Exchange Admin.
// =============================================================================

// -----------------------------------------------------------------------------
// These are pointers to the functions that Exchange Admin will want to access.
// -----------------------------------------------------------------------------

ADMIN_ExtensionFunction extensionFunctions =
{
    ADMIN_ExtensionAPIVersion,
    bInstallExtension,      // Found in Admin.C
    bDeinstallExtension,    // Found in Admin.C
    bInitSheet,             // Found in Admin.C
    bShowPage,              // Found in Admin.C
    iStartPage,             // Found in Admin.C
    bHasHelp,
    DoHelp,
    bSaveData,
    bCommitData,
    DeinitSheet             // Found in Admin.C
};

// -----------------------------------------------------------------------------
// This array contains a list of info for one property sheet.  If you were
// writing a DLL that had more than one sheet you would need to provide a list
// of information for each sheet.
// -----------------------------------------------------------------------------

ADMIN_SheetInfo rgSheetInfo[] =
{
    {   // List of info for a single property sheet.
        0,                   // Place holder for instance handle.
        IDD_EXT_DATA_VIEWER, // Resource ID of Dialog
        &DialogProc,         // DLGPROC      lpfnDlgProc;
        IDS_EXT_DATA_VIEWER, // Resource ID of name to place on tag.
        0,                   // LPARAM that will be passed to DlgProc on WM_INITDIALOG.
        0,                   // Place holder for language ID of dialog to be loaded.
    },
    // {More property sheet info could be added here.}
};

// Count of property sheets that we implement. (needed by admin.c)
int cSheetInfo = ARRAY_CNT( rgSheetInfo);

// -----------------------------------------------------------------------------


PRVALARY.C

// PrValAry.C ------------------------------------------------------------------
// Implementation of a set of functions that maintain a MAPI
// allocated array of property values.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "PrValAry.h"

// -----------------------------------------------------------------------------

HRESULT PVA_HrInit(
    THIS LPPropValArray pva,        // Uninitialized structure to be initialized.
    IN   ULONG          cExpandAmt, // Amount to expand array by when it gets full.
    IN   ULONG          cProps,     // Count of properties in array.
    IN   LPSPropValue   lpPropVal)  // A MAPI allocated array of properties, or NULL.
{
    HRESULT hr = NOERROR;
    ULONG   cb = 0;

    // This will validate the initial property array.
    if( cProps)
    {
        hr = ScCountProps( cProps, lpPropVal, &cb);
        if( FAILED( hr))
            goto cleanup;
    }

    pva->cExpandAmt = cExpandAmt;
    pva->cTotal = cProps;
    pva->cMax = cProps;
    pva->lpPVOrig = lpPropVal;
    pva->lpPropVal = lpPropVal;

cleanup:
    if( FAILED( hr))
        memset( pva, 0, sizeof( PropValArray));

    RETURN( hr);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

void PVA_Destroy(
    THIS LPPropValArray pva)        // Structure to be destroyed.
{
    if( pva->lpPropVal != pva->lpPVOrig)
        MAPIFREEBUFFER( pva->lpPropVal);
    MAPIFREEBUFFER( pva->lpPVOrig);
    memset( pva, 0, sizeof( PropValArray));
}

// -----------------------------------------------------------------------------
// This function prepares a property for placement in the property value array.
//
// If the property is a simple type that does not point to data in another
// location we simply return.
//
// If the propery points to data contained outside of the SPropValue structure
// we make a copy of it using ScCopyProps().  This simplifies our code since
// there are many different data types and ScCopyProps() handles them all.  We
// will waste the sizeof( SPropValue) bytes but it is a small price to pay for
// simple code.
// -----------------------------------------------------------------------------

static HRESULT PVA_HrPrepare(
    THIS    LPPropValArray  pva,        // Array to manipulate
    IN OUT  LPSPropValue*   lppPropVal) // Prop val to prepare for placement in array.
{
    HRESULT         hr           = NOERROR;
    ULONG           cb           = 0;
    ULONG           cBytesCopied = 0;
    LPSPropValue    lpNewPropVal = NULL;

    // Count the bytes needed for the new property.
    hr = ScCountProps( 1, *lppPropVal, &cb);
    if( FAILED( hr))
        goto cleanup;

    // Does this entry require extra data space?
    if( cb > sizeof( SPropValue))
    {   // YES, so prepare it.

        // Allocate a new buffer, linked to the original, to hold the copied properties.
        hr = MAPIAllocateMore( cb, pva->lpPVOrig, &lpNewPropVal);
        if( FAILED( hr))
            goto cleanup;
        memset( lpNewPropVal, 0, cb);

        // Copy the subscriber properties to the new buffer.
        hr = ScCopyProps( 1, *lppPropVal, lpNewPropVal, &cBytesCopied);
        if( FAILED( hr))
            goto cleanup;
        ASSERTERROR( cb == cBytesCopied, "ScCountProps & ScCopyProps are inconsistent!");

        // Replace the caller's buffer since we have a copy of the data.
        *lppPropVal = lpNewPropVal;
    }

cleanup:
    RETURN( hr);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

HRESULT PVA_HrInsert(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp,        // Index of insertion point.
    IN   LPSPropValue   lpPropVal)    // Property value to insert.
{
    HRESULT         hr           = NOERROR;
    ULONG           cb           = 0;
    ULONG           cBytesCopied = 0;
    LPSPropValue    lpPVSlot     = NULL;
    LPSPropValue    lpNewPropVal = NULL;

    ASSERTERROR( iProp <= pva->cTotal, "Index is too large.");

    // Is the array full?
    if( pva->cTotal == pva->cMax)
    {   // YES, the array is full.

        // Increase the maximum size of the array.
        pva->cMax += pva->cExpandAmt;

        // Allocate a new and expanded array.
        cb = pva->cMax * sizeof( SPropValue);
        hr = MAPIAllocateBuffer( cb, &lpNewPropVal);
        if( FAILED( hr))
            goto cleanup;

        // Zero newly allocated memory.
        memset( lpNewPropVal, 0, cb);

        // Copy full array into it, if there is something to copy.
        if( pva->lpPropVal)
            MoveMemory( lpNewPropVal, pva->lpPropVal, pva->cTotal * sizeof( SPropValue));

        // If the full array is NOT the original MAPIAllocation free it.  By retaining
        // the original allocation we keep the extra data associated with it but waste
        // the space occupied by the original SPropValue array.  But it is worth this
        // small cost to keep the code simple.
        if( pva->lpPropVal != pva->lpPVOrig)
            MAPIFREEBUFFER( pva->lpPropVal);

        // Make the new array allocation the current array.
        pva->lpPropVal = lpNewPropVal;
        if( !pva->lpPVOrig)
            pva->lpPVOrig = lpNewPropVal;
    }

    // Prepare property value to be placed in array.
    hr = PVA_HrPrepare( pva, &lpPropVal);
    if( FAILED( hr))
        goto cleanup;

    // Slot to insert new property into.
    lpPVSlot = pva->lpPropVal + iProp;

    // Open up a new slot for the property to insert (if not at end).
    if( iProp < pva->cTotal)
        MoveMemory( lpPVSlot + 1, lpPVSlot, (pva->cTotal - iProp) * sizeof( SPropValue));

    // Copy the new property into the array.
    MoveMemory( lpPVSlot, lpPropVal, sizeof( SPropValue));

    // Increment the total array elements used.
    pva->cTotal ++;

cleanup:
    RETURN( hr);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

HRESULT PVA_HrModify(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp,        // Index of insertion point.
    IN   LPSPropValue   lpPropVal)    // Property value to insert.
{
    HRESULT         hr           = NOERROR;

    // Prepare property value to be placed in array.
    hr = PVA_HrPrepare( pva, &lpPropVal);
    if( FAILED( hr))
        goto cleanup;

    // Copy the new property into the array.
    MoveMemory( pva->lpPropVal + iProp, lpPropVal, sizeof( SPropValue));

cleanup:
    RETURN( hr);
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

HRESULT PVA_HrDelete(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp)        // Index of property to delete.
{
    LPSPropValue    lpPVSlot     = NULL;

    // Decrement the total array elements used.
    pva->cTotal --;

    // Pointer to the element to be deleted.
    lpPVSlot = pva->lpPropVal + iProp;

    // Delete the property in the slot.
    if( iProp < pva->cTotal)
        MoveMemory( lpPVSlot, lpPVSlot + 1, (pva->cTotal - iProp) * sizeof( SPropValue));

    // Clear the last property in the array.
    memset( pva->lpPropVal + pva->cTotal, 0, sizeof( SPropValue));

    return( NOERROR);
}

// -----------------------------------------------------------------------------


PRVALARY.H

// PrValAry.H ------------------------------------------------------------------
// Declaration of a structure and set of functions that maintain a MAPI
// allocated array of property values.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef __PRVALARY_H__
#define __PRVALARY_H__

// -----------------------------------------------------------------------------

#ifdef THIS
#undef THIS
#define THIS    // Documents a parameter type.
#endif

// -----------------------------------------------------------------------------

typedef struct
{
    ULONG           cExpandAmt; // Expand array by this ammount when full.
    ULONG           cTotal;     // Current array total.
    ULONG           cMax;       // Current maximum array will hold.
    LPSPropValue    lpPVOrig;   // Original MAPI allocated array.
    LPSPropValue    lpPropVal;  // Actual array without data unless it equals lpPVOrig.
} PropValArray, *LPPropValArray;

// -----------------------------------------------------------------------------

HRESULT PVA_HrInit(
    THIS LPPropValArray pva,          // Uninitialized structure to be initialized.
    IN   ULONG          cExpandAmt,   // Amount to expand array by when it gets full.
    IN   ULONG          cProps,       // Count of properties in array.
    IN   LPSPropValue   lpPropVal);   // A MAPI allocated array of properties, or NULL.

void PVA_Destroy(
    THIS LPPropValArray pva);         // Structure to be destroyed

HRESULT PVA_HrInsert(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp,        // Index of insertion point.
    IN   LPSPropValue   lppPropVal);  // Property value to insert.

HRESULT PVA_HrModify(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp,        // Index of insertion point.
    IN   LPSPropValue   lpPropVal);   // Property value to insert.

HRESULT PVA_HrDelete(
    THIS LPPropValArray pva,          // Array to manipulate
    IN   ULONG          iProp);       // Index of property to delete.

// -----------------------------------------------------------------------------

#endif // __PRVALARY_H__


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by extdatvw.rc
//
#define IDS_EXT_DATA_VIEWER             1
#define IDD_EXT_DATA_VIEWER             102
#define IDD_EDIT                        103
#define IDC_ADMINICON                   1000
#define IDC_TITLE                       1001
#define IDC_LB_VIEW                     1002
#define IDC_EDIT_EXT_DATA_NAME          1003
#define IDC_BTN_DELETE                  1004
#define IDC_BTN_MODIFY                  1005
#define IDC_BTN_ADD                     1006
#define IDC_BTN_REFRESH                 1007
#define IDC_CBO_TYPE                    1008
#define IDC_EDIT_DATA_VALUE             1009

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1010
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


FIMPORT2.TXT

Here is an attachment file.


FIMPORT.C

// --fimport.c------------------------------------------------------------------
//
//  The Exchange SDK Folder Import sample allows bulk import of data
//  into an Exchange folder.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "attrname.h"
#include "fimportm.h"
#include "fimport.chk"

//
// Names of Command Line Arguments
//

#define ARG_PROGRAM_NAME        "FIMPORT"
#define ARG_HELP1               "?"
#define ARG_HELP2               "HELP"
#define ARG_APPEND              "APPEND"
#define ARG_UPDATE              "UPDATE"
#define ARG_REPLACE             "REPLACE"
#define ARG_FILE                "FILE"
#define ARG_FOLDERPATH          "FOLDERPATH"
#define ARG_CLASS               "CLASS"
#define ARG_PROFILE             "PROFILE"
#define ARG_SETREAD             "SETREAD"
#define ARG_NOSETREAD           "NOSETREAD"
#define ARG_EFD                 "EFD"
#define ARG_NOEFD               "NOEFD"
#define ARG_PROGRESS            "PROGRESS"
#define ARG_NOPROGRESS          "NOPROGRESS"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
    ARG_HELP1,
    ARG_HELP2,
    ARG_APPEND,
    ARG_UPDATE,
    ARG_REPLACE,
    ARG_FILE,
    ARG_FOLDERPATH,
    ARG_CLASS,
    ARG_PROFILE,
    ARG_SETREAD,
    ARG_NOSETREAD,
    ARG_EFD,
    ARG_NOEFD,
    ARG_PROGRESS,
    ARG_NOPROGRESS,
};

//
// Table of Commands for First Line of .CSV File.
//

static char * rgpszFileCommandArray[] = {
    ARG_APPEND,
    ARG_UPDATE,
    ARG_REPLACE,
    ARG_FOLDERPATH,
    ARG_CLASS,
    ARG_SETREAD,
    ARG_NOSETREAD,
    ARG_EFD,
    ARG_NOEFD,
};

// These equates MUST equal the offset of the
// corresponding entry in rgpszFileCommandArray.

typedef enum _FileCommand        // fc
{
    FILECOMMAND_APPEND          = 0,
    FILECOMMAND_UPDATE          = 1,
    FILECOMMAND_REPLACE         = 2,
    FILECOMMAND_FOLDERPATH      = 3,
    FILECOMMAND_CLASS           = 4,
    FILECOMMAND_SETREAD         = 5,
    FILECOMMAND_NOSETREAD       = 6,
    FILECOMMAND_EFD             = 7,
    FILECOMMAND_NOEFD           = 8,
}
FILECOMMAND, *LPFILECOMMAND;

//
// Enumerations, Structures and Type Definitions
//

typedef enum _Operation         // op
{
    OP_APPEND,
    OP_UPDATE,
    OP_REPLACE,
}
OPERATION, *LPOPERATION;

typedef enum _PropTypeCode      // ptc
{
    PROPTYPECODE_LONG           = 'L',
    PROPTYPECODE_STRING         = 'S',
    PROPTYPECODE_SYSTIME        = 'T',
    PROPTYPECODE_UNICODE        = 'U',
    PROPTYPECODE_BOOLEAN        = 'B',
    PROPTYPECODE_BINARY         = 'X',
    PROPTYPECODE_GUID           = 'G',
    PROPTYPECODE_MULTI          = 'M',
}
PROPTYPECODE, *LPPROPTYPECODE;

//
// Variables to store command line parameters
//

static BOOL             fHelp                           = FALSE;
static OPERATION        opOperation                     = OP_APPEND;
static CHAR             szFile          [MAX_PATH+1]    = {0};
static CHAR             szFolderPath    [MAX_PATH+1]    = {0};
static CHAR             szClass         [MAX_PATH+1]    = {0};
static CHAR             szProfile       [MAX_PATH+1]    = {0};
static BOOL             fSetRead                        = FALSE;
static BOOL             fEfd                            = FALSE;
static BOOL             fProgress                       = TRUE;

//
// Other Variables
//

static LPMAPISESSION    lpSession                       = NULL;
static LPMAPIFOLDER     lpFolder                        = NULL;
static LPSPropValue     rgMessageProps                  = NULL;
static ULONG            cCsvFileMessageProps            = 0;
static ULONG            cAllMessageProps                = 0;
static BOOL             fImportingAttachments           = FALSE;

#define                 MAX_CSV_FILE_LINE               1000
static FILE *           fpCsvFile                       = NULL;
static ULONG            iCsvLine                        = 0;
static BOOL             fCsvLineSaved                   = FALSE;
static CHAR             szCsvLine[MAX_CSV_FILE_LINE]    = {0};

// If cMessagesImported == DONT_REPORT_MSGS_IMPORTED then don't print
// the number of messages imported.

#define                 DONT_REPORT_MSGS_IMPORTED       -1
static LONG             cMessagesImported               = DONT_REPORT_MSGS_IMPORTED;

//
// Function Declarations
//

static VOID PrintUsage(void);

static HRESULT HrPreParseCommandLine(
    IN int argc,
    IN char *argv[]);

static VOID PrintHelp(void);

static HRESULT HrParseCommandLine(
    IN int argc,
    IN char *argv[]);

static HRESULT HrCheckInfoCompleteness(void);

static HRESULT HrGetFolderFromPath(
    IN LPMAPISESSION lpSession,
    IN LPSTR pszStoreFolderPath,
    OUT LPMAPIFOLDER * lppFolder);

static HRESULT HrDoImport(void);

static HRESULT HrReadColumnHeaders(void);

static HRESULT HrProcessMessages(void);

static HRESULT HrParseMultiPropValue(
IN ULONG            ulPropTag,
IN LPTSTR           pszValueText,
IN LPVOID           pBaseObject,
OUT LPSPropValue    pProp);

static HRESULT HrParsePropValue(
IN ULONG            ulPropTag,
IN LPTSTR           pszValueText,
IN LPVOID           pBaseObject,
OUT LPSPropValue    pProp);

HRESULT HrCreateMessageAttachment(
INLPMESSAGElpMsg,
INLPSTRlpszAttachPath);

static HRESULT HrStrAToStrWEx(
    IN  LPCSTR          lpszSource,
    IN  LPVOID          lpvBaseObject,
    OUT LPWSTR *        lppwszDest);

static HRESULT HrOpenCsvFile();

static HRESULT HrReadCsvSettings(void);

static HRESULT HrGetCsvLine(void);

static HRESULT HrUngetCsvLine(void);

static HRESULT HrCloseCsvFile(void);

//
// Inline Helper Functions
//

//$--SZ_BASE10------------------------------------------------------------------
//  Converts an integer to its decimal string equivalent.
//  For use as a parameter to EventLogMsg.
// -----------------------------------------------------------------------------
__inline static LPCTSTR SZ_BASE10(
    IN const INT iNumber)
{
    static TCHAR szTemp[16] = {0};
    return( (LPCTSTR)_itot( iNumber, szTemp, 10));
}

//$--SZ_BASE16------------------------------------------------------------------
//  Converts an integer to its hexidecimal string equivalent.
//  For use as a parameter to EventLogMsg.
// -----------------------------------------------------------------------------
__inline static LPCTSTR SZ_BASE16(
    IN const ULONG ulNumber)
{
    static TCHAR szTemp[16] = {0};
    return( (LPCTSTR)_itot( ulNumber, szTemp, 16));
}

//$--TRIM_WHITE_SPACE-----------------------------------------------------------
//  Trims white space from the front and back of a string.  Edits the string
//  in place in the buffer.
// -----------------------------------------------------------------------------
__inline static VOID TRIM_WHITE_SPACE(
    IN OUT LPSTR psz)
{
    LPSTR pszSrc = psz;
    LPSTR pszDst = psz;
    LPSTR pszPlaceToNullTerminate = NULL;

    while (isspace(*pszSrc))
        pszSrc++;

    while (*pszSrc)
    {
        if (!isspace(*pszSrc))
            pszPlaceToNullTerminate = pszDst + 1;
        *pszDst++ = *pszSrc++;
    }

    if (pszPlaceToNullTerminate)
        *pszPlaceToNullTerminate = 0;
    else
        *pszDst = 0;
}

//
// Functions
//

//$--main-----------------------------------------------------------------------
//  Main function that performs folder import.
// -----------------------------------------------------------------------------
int main(                                   // RETURNS: exit code
    IN int argc,                            // number of args on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    BOOL                fEventLogOpen       = FALSE;
    BOOL                fMAPIInitialized    = FALSE;
    BOOL                fCsvFileIsOpen      = FALSE;
    MAPIINIT_0          MapiInit            = {0};

    DEBUGPUBLIC("main()");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        return _nEcFromHr(hr);

    printf( "\n" );

    // If there are no command line arguments then print the usage message
    // and exit.

    if (argc < 2)
    {
        PrintUsage();
        goto cleanup;
    }

    // Do preliminary parsing of the command line (prints a message on failure).

    hr = HrPreParseCommandLine(argc, argv);
    if (FAILED(hr))
        goto cleanup;

    // If they requested help, then print a help message and exit.

    if (fHelp)
    {
        PrintHelp();
        goto cleanup;
    }

    // Parse the command line arguments (prints a message on failure).

    hr = HrParseCommandLine(argc, argv);
    if (FAILED(hr))
        goto cleanup;

    // Start reporting the number of messages imported after here.

    cMessagesImported = 0;

    // Initialize event logging.

    hr = HrEventOpenLog(
        TEXT("EDK FImport"), NULL, NULL, NULL, NULL, NULL);

    if (FAILED(hr))
    {
        fprintf(stderr,
            "WARNING: unable to initialize event logging.\n");
        hr = NOERROR;
    }
    else
    {
        fEventLogOpen = TRUE;
    }

    // Open the .CSV file.

    hr = HrOpenCsvFile(szFile);
    if (FAILED(hr))
        goto cleanup;
    fCsvFileIsOpen = TRUE;

    // Initialize MAPI.

    MapiInit.ulVersion = MAPI_INIT_VERSION;

    hr = MAPIInitialize(&MapiInit);
    if (FAILED(hr))
    {
        EventLogMsg(
            FIMPORT_CANNOT_INIT_MAPI,
            0,
            0);
        goto cleanup;
    }
    fMAPIInitialized = TRUE;

    // Logon to MAPI.

    if (szProfile[0])
    {
    // If a profile was given on the command line then
        // log on to the specified profile.

    hr = MAPILogonEx(
            0,
            szProfile,
            NULL,
            MAPI_NEW_SESSION | MAPI_EXPLICIT_PROFILE , //| MAPI_NO_MAIL,
            &lpSession);

    if (FAILED(hr))
    {
    EventLogMsg(
    FIMPORT_PROFILE_LOGON_ERROR,
    2, szProfile, SZ_BASE16(hr),
                0);

            hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }
    else
    {
    // Otherwise, log onto the existing session.

    hr = MAPILogonEx(
            0,
            szProfile,
            NULL,
            MAPI_ALLOW_OTHERS , //| MAPI_NO_MAIL,
            &lpSession);

    if (FAILED(hr))
    {
    EventLogMsg(
    FIMPORT_LOGON_ERROR,
    1, SZ_BASE16(hr),
                0);

            hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }

    // Read the first line from the .CSV file, which contains
    // arguments like those on the command line.

    hr = HrReadCsvSettings();
    if (FAILED(hr))
        goto cleanup;

    // Parse the command line arguments again, so that command
    // line arguments will override the first line of the file.

    hr = HrParseCommandLine(argc, argv);
    if (FAILED(hr))
    {
        // Don't report number of messages imported.
        cMessagesImported = DONT_REPORT_MSGS_IMPORTED;
        goto cleanup;
    }

    // Make sure we got all the information we need from the
    // command line/.CSV file (prints a message on failure).

    hr = HrCheckInfoCompleteness();
    if (FAILED(hr))
    {
        // Don't report number of messages imported.
        cMessagesImported = DONT_REPORT_MSGS_IMPORTED;
        goto cleanup;
    }

    // Get a pointer to the folder.

    hr = HrGetFolderFromPath(lpSession, szFolderPath, &lpFolder);
    if (FAILED(hr))
        goto cleanup;

    // Import from the .CSV file to the folder.

    hr = HrDoImport();
    if (FAILED(hr))
        goto cleanup;

cleanup:

    // Close .CSV file.

    if (fCsvFileIsOpen)
    {
        (void) HrCloseCsvFile();
        fCsvFileIsOpen = FALSE;
    }

    // Print the number of events logged, then close event logging.

    if (fEventLogOpen)
    {
        HRESULT hrT = NOERROR;
        EDKEVENTCOUNT sEventCount = {0};

        hrT = HrEventGetCounts(&sEventCount);
        if (SUCCEEDED(hrT))
        {
            if (sEventCount.cError == 1)
            {
                fprintf(stderr,
                    "ERROR: 1 error written to the event log.\n");
            }
            else if (sEventCount.cError)
            {
                fprintf(stderr,
                    "ERROR: %d errors written to the event log.\n",
                    sEventCount.cError);
            }

            if (sEventCount.cWarning == 1)
            {
                fprintf(stderr,
                    "WARNING: 1 warning written to the event log.\n");
            }
            else if (sEventCount.cWarning)
            {
                fprintf(stderr,
                    "WARNING: %d warnings written to the event log.\n",
                    sEventCount.cWarning);
            }
        }

        (void) HrEventCloseLog();
        fEventLogOpen = FALSE;
    }

    // Print the number of messages imported.

    if (cMessagesImported != DONT_REPORT_MSGS_IMPORTED)
    {
        if (cMessagesImported == 1)
        {
            fprintf(stderr, "There was 1 message imported.\n");
        }
        else
        {
            fprintf(stderr,
                "There were %d message(s) imported.\n", cMessagesImported);
        }
    }

    // Release MAPI objects and de-initialize MAPI.

    if (fMAPIInitialized)
    {
        ULRELEASE(lpFolder);
        ULRELEASE(lpSession);

        MAPIUninitialize();
        fMAPIInitialized = FALSE;
    }

    return(_nEcFromHr(hr));
}

//$--PrintUsage-----------------------------------------------------------------
//  Print information about how to use this program.
// -----------------------------------------------------------------------------
static VOID PrintUsage(void)                // RETURNS: nothing
{
    DEBUGPRIVATE("PrintUsage()");

    printf("USAGE: %s [Flags]\n\n", ARG_PROGRAM_NAME);
    printf("  [Flags]        Enter %s /%s for details\n", ARG_PROGRAM_NAME,
        ARG_HELP1);
}

//$--HrPreParseCommandLine------------------------------------------------------
//  Does preliminary parsing of the command line that is needed to decide
//  if /HELP was included.
// -----------------------------------------------------------------------------
static HRESULT HrPreParseCommandLine(       // RETURNS: HRESULT
    IN int argc,                            // number of args on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    int                 i                   = 0;
    char *              pszArgument         = NULL;
    char *              pszValue            = NULL;

    DEBUGPRIVATE("HrPreParseCommandLine()");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        RETURN(hr);

    // Check for /? or /HELP.

    for (i = 1; i < argc; i++)
    {
        hr = _HrExpandCommandLineArgument(
            argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
            &pszArgument, &pszValue);

        if (SUCCEEDED(hr) && pszArgument &&
            (!_stricmp(pszArgument,ARG_HELP1) ||
             !_stricmp(pszArgument,ARG_HELP2)))
        {
            fHelp = TRUE;
            break;
        }
    }

    RETURN(NOERROR);
}

//$--PrintHelp------------------------------------------------------------------
//  Print information about the flags.
// -----------------------------------------------------------------------------
static VOID PrintHelp(void)                 // RETURNS: nothing
{
    DEBUGPRIVATE("PrintHelp()");

    printf("Folder Import sample.\n\n");
    printf("USAGE: %s [Flags]\n\n", ARG_PROGRAM_NAME);
    printf("  Flags\n");
    printf("  /FILE=         Filename of the .CSV file to import\n");
    printf("  /APPEND        The content of the .CSV file is appended to the folder\n");
    printf("  /UPDATE        The columns in the .CSV file replace the associated\n");
    printf("                 properties in the messages in the folder.  The replacement\n");
    printf("                 is carried out starting with the first message in the folder.\n");
    printf("  /REPLACE       The existing messages in the folder are replaced with the\n");
    printf("                 content of the .CSV file\n");
    printf("  /FOLDERPATH=   Path of the folder where messages will be placed\n");
    printf("  /CLASS=        Message class of the messages to place in the folder\n");
    printf("  /PROFILE=      Profile to log into message store\n");
    printf("  /[NO]SETREAD   Set the read flag on each new message\n");
    printf("  /[NO]EFD       Set the property MS_EXCHANGE_01=1 on each new message\n");
    printf("  /[NO]PROGRESS  Display progress dialog\n");
    printf("  /HELP or /?    Display help screen\n");
}

//$--HrParseCommandLine---------------------------------------------------------
//  Parse the command line arguments and put their values in the appropriate
//  variables.
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine(          // RETURNS: HRESULT
    IN int argc,                            // number of arguments on command line
    IN char *argv[])                        // array of command line arguments
{
    HRESULT             hr                  = NOERROR;
    HRESULT             hrT                 = NOERROR;
    int                 i                   = 0;
    char *              pszArgument         = NULL;
    char *              pszValue            = NULL;

    DEBUGPRIVATE("HrParseCommandLine()");

    // Check parameters.

    hr = CHK_main(argc, argv);
    if (FAILED(hr))
        RETURN(hr);

    for (i = 1; i < argc; i++)
    {
        hrT = _HrExpandCommandLineArgument(
            argv[i], rgpszArgArray, ARRAY_CNT(rgpszArgArray), NULL,
            &pszArgument, &pszValue);

        if (hrT == EDK_E_NOT_FOUND)
        {
            fprintf(stderr,
                "ERROR: unknown command line flag: %s\n",
                argv[i]);
            hr = hrT;

            // Keep parsing to show all invalid flags.
        }

        else if (hrT == EDK_E_AMBIGUOUS)
        {
            fprintf(stderr,
                "ERROR: ambiguous command line flag: %s\n",
                argv[i]);
            hr = hrT;

            // Keep parsing to show all invalid flags.
        }

        else if (FAILED(hrT))
        {
            fprintf(stderr,
                "ERROR: general error while parsing command line flag: %s\n",
                argv[i]);
            hr = hrT;
            goto cleanup;
        }

        if (pszArgument)
        {
            // Put all the flags that do not take a value here.

            if (!_stricmp(pszArgument,ARG_APPEND))
            {
                opOperation = OP_APPEND;
            }
            else if (!_stricmp(pszArgument,ARG_UPDATE))
            {
                opOperation = OP_UPDATE;
            }
            else if (!_stricmp(pszArgument,ARG_REPLACE))
            {
                opOperation = OP_REPLACE;
            }
            else if (!_stricmp(pszArgument,ARG_SETREAD))
            {
                fSetRead = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOSETREAD))
            {
                fSetRead = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_EFD))
            {
                fEfd = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOEFD))
            {
                fEfd = FALSE;
            }
            else if (!_stricmp(pszArgument,ARG_PROGRESS))
            {
                fProgress = TRUE;
            }
            else if (!_stricmp(pszArgument,ARG_NOPROGRESS))
            {
                fProgress = FALSE;
            }

            // If we got this far we know it's a flag that requires a value,
            // so if they didn't give one then it's an error.  We know it
            // is a valid flag because pszArgument != NULL, and that means
            // the flag is in the list.

            else if (pszValue == NULL)
            {
                fprintf(stderr, "ERROR: flag /%s requires a value\n",
                    pszArgument);
                hr = HR_LOG(E_FAIL);
            }

            // Put all the flags that take a value here.

            else if (!_stricmp(pszArgument,ARG_FILE))
            {
                STRNCPY(szFile, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_FOLDERPATH))
            {
                STRNCPY(szFolderPath, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_CLASS))
            {
                STRNCPY(szClass, pszValue);
            }
            else if (!_stricmp(pszArgument,ARG_PROFILE))
            {
                STRNCPY(szProfile, pszValue);
            }
        }
    }

    // If any of the parsing failed then stop here.

    if (FAILED(hr))
        goto cleanup;

    // Make sure all required information has been given.

    if (szFile[0] == 0)
    {
        fprintf(stderr, "ERROR: please specify /%s\n", ARG_FILE);
        hr = HR_LOG(E_FAIL);
    }

cleanup:
    RETURN(hr);
}

//$--HrCheckInfoCompleteness----------------------------------------------------
//  Make sure that all required information has been given either on the
//  command line or in the first line of the file.  If not, print an error
//  message describing what is missing and return an error.
// -----------------------------------------------------------------------------
static HRESULT HrCheckInfoCompleteness(void) // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrCheckInfoCompleteness()");

    if (szFolderPath[0] == 0)
    {
        fprintf(stderr,
            "ERROR: Folder Path must be specified on the command "
            "line or in the file.\n");
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrGetFolderFromPath--------------------------------------------------------
//  Given a path string made up of the store name and folder, return a
//  pointer to a MAPI folder object.
// -----------------------------------------------------------------------------
static HRESULT HrGetFolderFromPath(         // RETURNS: HRESULT
    IN  LPMAPISESSION   lpSession,          // current MAPI session
    IN  LPSTR           pszStoreFolderPath, // "storename\folderpath"
    OUT LPMAPIFOLDER *  lppFolder)          // folder pointer
{
    HRESULT             hr                  = NOERROR;
    LPSTR               pszStore            = NULL;
    ULONG               cchStore            = 0;
    LPSTR               pszFolderPath       = NULL;
    ULONG               cbEntryId           = 0;
    LPENTRYID           pEntryId            = NULL;
    LPMDB               pMdb                = NULL;
    ULONG               ulObjType           = 0;

    DEBUGPRIVATE("HrGetFolderFromPath()");

    // Check parameters.

    hr = CHK_HrGetFolderFromPath(
        lpSession,
        pszStoreFolderPath,
        lppFolder);

    if (FAILED(hr))
        RETURN(hr);

    // Find the first '\' in the folder path.  This separates the name
    // of the store from the folder only path.  If there is no '\' then
    // the path must be invalid.

    pszFolderPath = strchr(pszStoreFolderPath, '\\');

    if (pszFolderPath == NULL)
    {
EventLogMsg(
FIMPORT_INVALID_FOLDER_PATH,
1, pszStoreFolderPath,
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Allocate a separate piece of memory and copy the store name into it.

    cchStore = pszFolderPath - pszStoreFolderPath;

    hr = MAPIAllocateBuffer(
        cchStore + 1,
        &pszStore);
    if (FAILED(hr))
    {
EventLogMsg(
FIMPORT_ERROR,
0,
            1, ERROR_NOT_ENOUGH_MEMORY);
        goto cleanup;
    }

    strncpy(pszStore, pszStoreFolderPath, cchStore);
    pszStore[cchStore] = 0;

    TRIM_WHITE_SPACE(pszStore);

    // Set pszFolderPath to point to the folder path.

    pszFolderPath++;

    // Find the requested store, or the default store if
    // they use the name "@DEFAULT".

    if (!stricmp(pszStore, "@DEFAULT"))
    {
        hr = HrMAPIFindDefaultMsgStore(
            lpSession,
            &cbEntryId,
            &pEntryId);
        if (FAILED(hr))
        {
    EventLogMsg(
    FIMPORT_CANNOT_FIND_DEFAULT_STORE,
    1, SZ_BASE16(hr),
                0);
            goto cleanup;
        }
    }
    else
    {
        hr = HrMAPIFindStoreA(
            lpSession,
            pszStore,
            &cbEntryId,
            &pEntryId);
        if (FAILED(hr))
        {
    EventLogMsg(
    FIMPORT_CANNOT_FIND_STORE,
    2, pszStore, SZ_BASE16(hr),
                0);
            goto cleanup;
        }
    }

    // Open the requested store.

    hr = MAPICALL(lpSession)->OpenEntry(
        lpSession,
        cbEntryId,
        pEntryId,
        NULL,
        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN *)&pMdb);
    if (FAILED(hr))
    {
EventLogMsg(
FIMPORT_CANNOT_OPEN_STORE,
2, pszStore, SZ_BASE16(hr),
            0);
        goto cleanup;
    }
    if (ulObjType != MAPI_STORE)
    {
EventLogMsg(
FIMPORT_WRONG_STORE_TYPE,
2, pszStore, SZ_BASE16(ulObjType),
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open the folder indicated by the folder path.

    MAPIFREEBUFFER(pEntryId);
    cbEntryId = 0;

    hr = HrMAPIFindFolderExA(
        pMdb,
        '\\',
        pszFolderPath,
        &cbEntryId,
        &pEntryId);
    if (FAILED(hr))
    {
EventLogMsg(
FIMPORT_CANNOT_FIND_FOLDER,
3, pszFolderPath, pszStore, SZ_BASE16(hr),
            0);
        goto cleanup;
    }

    hr = MAPICALL(lpSession)->OpenEntry(
        lpSession,
        cbEntryId,
        pEntryId,
        NULL,
        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN *)lppFolder);
    if (FAILED(hr))
    {
EventLogMsg(
FIMPORT_CANNOT_OPEN_FOLDER,
3, pszFolderPath, pszStore, SZ_BASE16(hr),
            0);
        goto cleanup;
    }
    if (ulObjType != MAPI_FOLDER)
    {
EventLogMsg(
FIMPORT_WRONG_FOLDER_TYPE,
3, pszFolderPath, pszStore, SZ_BASE16(ulObjType),
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER(pEntryId);
    MAPIFREEBUFFER(pszStore);
    ULRELEASE(pMdb);

    RETURN(hr);
}

//$--HrDoImport-----------------------------------------------------------------
//  Import the messages from the .CSV file into the folder.
// -----------------------------------------------------------------------------
static HRESULT HrDoImport(void)             // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrDoImport()");

    // Read the column headers line from the .CSV file and use it
    // to build an SPropValue array.

    hr = HrReadColumnHeaders();
    if (FAILED(hr))
        goto cleanup;

    // Read messages from the .CSV file and write them to the folder.

    hr = HrProcessMessages();
    if (FAILED(hr))
        goto cleanup;

cleanup:
    MAPIFREEBUFFER(rgMessageProps);
    RETURN(hr);
}

//$--HrReadColumnHeaders--------------------------------------------------------
//  Read the list of column headers from the next line of the .CSV file,
//  and place the resulting properties in rgMessageProps.  Use PR_NULL
//  for a column that says "ATTTACH".
// -----------------------------------------------------------------------------
static HRESULT HrReadColumnHeaders(void)    // RETURNS: HRESULT
{

HRESULT             hr                  = NOERROR;
    ULONG               iToken              = 0;
    ULONG               cToken              = 0;
    LPSTR *             rgpszToken          = NULL;
    LPSTR               pszToken            = NULL;
    LPWSTR              pwszToken           = NULL;
    ULONG               ulNamedPropType     = 0;
MAPINAMEID    sNameID    = {0};
LPMAPINAMEID    rglpNameID[1]    = {&sNameID};
LPSPropTagArray    lpPropTags    = NULL;
    ULONG               iGeneratedProp      = 0;

    DEBUGPRIVATE("HrReadColumnHeaders()");

    // Read a line from the file.

    hr = HrGetCsvLine();
    if (FAILED(hr))
    {
    EventLogMsg(
    FIMPORT_UNEXPECTED_END_OF_FILE,
    2, szFile, SZ_BASE10(iCsvLine),
    0);
    goto cleanup;
    }

    // Break the line up into tokens separated by commas.  At the same time,
    // count how many properties are in the line.

    hr = HrStrTokAll(
        szCsvLine,
        ",",
        &cToken,
        &rgpszToken);
    if (FAILED(hr))
    {
        EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "HrStrTokAll",
            0);
        goto cleanup;
    }

    // Calculate the number of properties, both the number coming from the
    // .CSV file, as well as the number being generated in other ways
    // (i.e. from /EFD and /CLASS).

    cCsvFileMessageProps = cToken;
    cAllMessageProps = cToken;
    iGeneratedProp = cToken;

    if (fEfd)
        cAllMessageProps++;

    if (szClass[0] != 0)
        cAllMessageProps++;

    // Allocate an SPropValue array big enough to hold all the properties.

    hr = MAPIAllocateBuffer(
        cAllMessageProps * sizeof(SPropValue),
        &rgMessageProps);
    if (FAILED(hr))
    {
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
    }

    ZeroMemory(
        rgMessageProps,
        cAllMessageProps * sizeof(SPropValue));

    // If they specified /EFD then we need to set the MS_EXCHANGE_01
    // property on each message, so get/create the property tag
    // for that property and add it at the end of the SPropValue array.

    if (fEfd)
    {
    // Construct the MAPINAMEID.
    sNameID.lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
    sNameID.ulKind = MNID_STRING;
    sNameID.Kind.lpwstrName = L"MS_EXCHANGE_01";

    // Get the MS_EXCHANGE_01 property tag.

    hr = MAPICALL(lpFolder)->GetIDsFromNames(
    lpFolder,
    1,
    rglpNameID,
    MAPI_CREATE,
    &lpPropTags);
    if (FAILED(hr))
    goto cleanup;

        // Add the MS_EXCHANGE_01 property tag to the end of
        // the SPropValue array.

        rgMessageProps[iGeneratedProp].ulPropTag =
            CHANGE_PROP_TYPE(lpPropTags->aulPropTag[0], PT_LONG);
        rgMessageProps[iGeneratedProp].Value.ul = 1;

    MAPIFREEBUFFER(lpPropTags);

        iGeneratedProp++;
    }

    // If the specified /CLASS then we need to set the PR_MESSAGE_CLASS
    // property on each message, so add it at the end of the SPropValue array.

    if (szClass[0] != 0)
    {
        rgMessageProps[iGeneratedProp].ulPropTag = PR_MESSAGE_CLASS;
        rgMessageProps[iGeneratedProp].Value.lpszA = szClass;

        iGeneratedProp++;
    }

    // Loop to translate each column heading into a property id.

    for (iToken = 0; iToken < cToken; iToken++)
    {
        pszToken = rgpszToken[iToken];

        TRIM_WHITE_SPACE(pszToken);

        // If it's "ATTACH" then it must be an attachment, so use
        // PR_NULL as a placeholder to indicate an attachment.

        if (!stricmp(pszToken, "ATTACH"))
        {
            rgMessageProps[iToken].ulPropTag = PR_NULL;
            fImportingAttachments = TRUE;
            continue;       // skip validity check for this property
        }

        // If it has a type on it like [M]<type_char>:<property_name>
        // then it must be a named property.

        else if (
            (pszToken[0] != 0 && pszToken[1] == ':')
                ||
            (pszToken[0] != 0 && pszToken[1] != 0 && pszToken[2] == ':')
            )
        {
            // Get the property type from the prefix.

            ULONG   ulMVFlag = 0L;

            if( toupper(*pszToken) == PROPTYPECODE_MULTI)
            {
                ulMVFlag = MV_FLAG;
                pszToken++;
            }

            switch (toupper(*pszToken))
            {
            case PROPTYPECODE_LONG:
                ulNamedPropType = PT_LONG | ulMVFlag;
                break;

            case PROPTYPECODE_STRING:
                ulNamedPropType = PT_STRING8 | ulMVFlag;
                break;

            case PROPTYPECODE_SYSTIME:
                ulNamedPropType = PT_SYSTIME | ulMVFlag;
                break;

            case PROPTYPECODE_UNICODE:
                ulNamedPropType = PT_UNICODE | ulMVFlag;
                break;

            case PROPTYPECODE_BOOLEAN:
                ulNamedPropType = PT_BOOLEAN | ulMVFlag;
                break;

            case PROPTYPECODE_BINARY:
                ulNamedPropType = PT_BINARY | ulMVFlag;
                break;

            case PROPTYPECODE_GUID:
                ulNamedPropType = PT_CLSID | ulMVFlag;
                break;

            default:
                {
                    CHAR szTypePrefix[2] = {pszToken[0], 0};

                    EventLogMsg(
                        FIMPORT_INVALID_TYPE_PREFIX,
                        4, szFile, SZ_BASE10(iCsvLine), szCsvLine, szTypePrefix,
                        0);
                    goto cleanup;
                }
            }

            // Skip past explicit property type code and colon.

            pszToken += 2;

            // Convert the string to UNICODE so we can look up its property tag.

            hr = HrStrAToStrWEx(pszToken, rgpszToken, &pwszToken);
            if (FAILED(hr))
            {
                EventLogMsg(
                    FIMPORT_INTERNAL_ERROR,
                    1, "HrStrAToStrWEx",
                    0);
                goto cleanup;
            }

        // Construct the MAPINAMEID.

        sNameID.lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
        sNameID.ulKind = MNID_STRING;
        sNameID.Kind.lpwstrName = pwszToken;

        // Get the property tag.

        hr = MAPICALL(lpFolder)->GetIDsFromNames(
        lpFolder,
        1,
        rglpNameID,
        MAPI_CREATE,
        &lpPropTags);
        if (FAILED(hr))
        goto cleanup;

            // Add the property type to the property tag we got and
            // store it in the array.

            rgMessageProps[iToken].ulPropTag = CHANGE_PROP_TYPE(
                lpPropTags->aulPropTag[0],
                ulNamedPropType);

        MAPIFREEBUFFER(lpPropTags);
        }

        // Otherwise it must be a normal property, so look up its
        // property tag id in the table.

        else
        {
            // Get the property tag id from the name.

            hr = HrGetPropTagValue(
                pszToken,
                &rgMessageProps[iToken].ulPropTag);
            if (FAILED(hr))
            {
                EventLogMsg(
                    FIMPORT_UNKNOWN_PROPERTY,
                    4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszToken,
                    0);
                goto cleanup;
            }

        }

        // Make sure the property type is one of the ones we support.

        switch (PROP_TYPE(rgMessageProps[iToken].ulPropTag))
        {
        case PT_BINARY:
        case PT_MV_BINARY:
        case PT_BOOLEAN:
        case PT_LONG:
        case PT_MV_LONG:
        case PT_STRING8:
        case PT_MV_STRING8:
        case PT_SYSTIME:
        case PT_UNICODE:
        case PT_MV_UNICODE:
        case PT_CLSID:
            // OK.
            break;

        default:
EventLogMsg(
FIMPORT_UNSUPPORTED_PROPERTY_TYPE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine,
                    SZ_BASE16(rgMessageProps[iToken].ulPropTag),
0);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(rgMessageProps);
    }
    MAPIFREEBUFFER(rgpszToken);
MAPIFREEBUFFER(lpPropTags);
    RETURN(hr);
}

//$--HrProcessMessages----------------------------------------------------------
//  Read messages from the .CSV file and write them to the folder.
// -----------------------------------------------------------------------------
static HRESULT HrProcessMessages(void)      // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    ULONG               iToken              = 0;
    ULONG               cToken              = 0;
    LPSTR *             rgpszToken          = NULL;
    LPMESSAGE           pMsg                = NULL;
    LPSPropProblemArray pProblems           = NULL;
    LPMAPITABLE         lpContentsTable     = NULL;
    SizedSPropTagArray(1, sEntryIdOnly)     = {1, PR_ENTRYID};
    LPSRowSet           pFolderRow          = NULL;
    ULONG               ulObjType           = 0;
    BOOL                fUpdateMessages     = FALSE;
    LPMAPITABLE         pAttachmentTable    = NULL;
    LPSRowSet           pAttachments        = NULL;
    ULONG               iAttachment         = 0;

    DEBUGPRIVATE("HrProcessMessages()");

    // If they said /REPLACE then delete all the messages in the folder.

    if (opOperation == OP_REPLACE)
    {
        hr = MAPICALL(lpFolder)->EmptyFolder(
            lpFolder,
            0,
            NULL,
            0);
        if (FAILED(hr))
        {
EventLogMsg(
FIMPORT_INTERNAL_ERROR,
1, "EmptyFolder",
0);
            goto cleanup;
        }
    }

    // If they said /UPDATE then open the folder's contents table.

    if (opOperation == OP_UPDATE)
    {
        // Open the table and set to get only the entry ids.

        hr = MAPICALL(lpFolder)->GetContentsTable(
            lpFolder,
            MAPI_DEFERRED_ERRORS,
            &lpContentsTable);
        if (FAILED(hr))
        {
EventLogMsg(
FIMPORT_INTERNAL_ERROR,
1, "GetContentsTable",
0);
            goto cleanup;
        }

        hr = MAPICALL(lpContentsTable)->SetColumns(
            lpContentsTable,
            (LPSPropTagArray) &sEntryIdOnly,
            0);
        if (FAILED(hr))
        {
EventLogMsg(
FIMPORT_INTERNAL_ERROR,
1, "SetColumns",
0);
            goto cleanup;
        }

        // Set the flag that shows we are updating messages.
        // (If we run out of messages to update, this flag will
        // be set to false and we will then start creating new
        // messages).

        fUpdateMessages = TRUE;
    }

    // Loop to import messages.

    while (TRUE)
    {
        // Read a line from the file.

        hr = HrGetCsvLine();

        // If we got an end of file then we're done.

        if (hr == EDK_E_END_OF_FILE)
        {
            hr = NOERROR;
            break;
        }

        if (FAILED(hr))
            goto cleanup;

        // Display a progress indicator.

        if (fProgress)
        {
            printf("\rImporting message %d...", cMessagesImported + 1);
        }

        // Break the line up into tokens separated by commas.

        hr = HrStrTokAll(
            szCsvLine,
            ",",
            &cToken,
            &rgpszToken);
        if (FAILED(hr))
        {
            EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "HrStrTokAll",
                0);
            goto cleanup;
        }

        // Make sure the number of properties in this line matches
        // the number in the header line.

        if (cToken != cCsvFileMessageProps)
        {
            EventLogMsg(
                FIMPORT_WRONG_NUMBER_OF_PROPERTY_VALUES,
                3, szFile, SZ_BASE10(iCsvLine), szCsvLine,
                0);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // If we are updating messages then open the next message in the folder.

        if (fUpdateMessages)
        {
            // Get the next message entry id from the contents table.

            hr = MAPICALL(lpContentsTable)->QueryRows(
                lpContentsTable,
                1,
                0,
                &pFolderRow);
            if (FAILED(hr))
            {
                EventLogMsg(
                    FIMPORT_INTERNAL_ERROR,
                    1, "QueryRows",
                    0);
                goto cleanup;
            }

            // If we got a message then open it.

            if (pFolderRow->cRows)
            {
                // Open the message.

                hr = MAPICALL(lpFolder)->OpenEntry(
                    lpFolder,
                    pFolderRow->aRow[0].lpProps[0].Value.bin.cb,
                    (LPENTRYID)pFolderRow->aRow[0].lpProps[0].Value.bin.lpb,
                    NULL,
                    MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                    &ulObjType,
                    (LPUNKNOWN *) &pMsg);
                if (FAILED(hr))
                {
            EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "OpenEntry",
                        0);
                    goto cleanup;
                }
                if (ulObjType != MAPI_MESSAGE)
                {
            EventLogMsg(
            FIMPORT_WRONG_MESSAGE_TYPE,
            1, SZ_BASE16(ulObjType),
                        0);
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                // If we are importing attachments then clear
                // out any old attachments in the message.

                if (fImportingAttachments)
                {
                    SizedSPropTagArray(1, sGetAttachNums) = {1, PR_ATTACH_NUM};

                    // Get the attachment numbers of all the attachments.

                    hr = MAPICALL(pMsg)->GetAttachmentTable(
                        pMsg,
                        0,
                        &pAttachmentTable);
                    if (FAILED(hr))
                    {
                EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "GetAttachmentTable",
                            0);
                        goto cleanup;
                    }

                    hr = HrQueryAllRows(
                        pAttachmentTable,
                        (LPSPropTagArray) &sGetAttachNums,
                        NULL,
                        NULL,
                        0,
                        &pAttachments);
                    if (FAILED(hr))
                    {
                EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "HrQueryAllRows",
                            0);
                        goto cleanup;
                    }

                    // Delete them all.

                    for (
                        iAttachment = 0;
                        iAttachment < pAttachments->cRows;
                        iAttachment++)
                    {
                        hr = MAPICALL(pMsg)->DeleteAttach(
                            pMsg,
                            pAttachments->aRow[iAttachment].lpProps[0].Value.ul,
                            0,
                            NULL,
                            0);
                        if (FAILED(hr))
                        {
                    EventLogMsg(
                    FIMPORT_INTERNAL_ERROR,
                    1, "DeleteAttach",
                                0);
                            goto cleanup;
                        }
                    }

                    ULRELEASE(pAttachmentTable);
                    MAPIFREEBUFFER(pAttachments);
                }
            }

            // If we ran out of messages then clear the flag
            // and we will start creating them instead.

            else
                fUpdateMessages = FALSE;

            MAPIFREEBUFFER(pFolderRow);
        }

        // If we don't have a message yet then create one.

        if (pMsg == NULL)
        {
            hr = MAPICALL(lpFolder)->CreateMessage(
                lpFolder,
                NULL,
                MAPI_DEFERRED_ERRORS,
                &pMsg);
            if (FAILED(hr))
            {
        EventLogMsg(
        FIMPORT_INTERNAL_ERROR,
        1, "CreateMessage",
                    0);
                goto cleanup;
            }
        }

        // Loop to fill in values for each property.

        for (iToken = 0; iToken < cToken; iToken++)
        {
            // Clear the Value part of the SPropValue structure
            // so we are sure there isn't any garbage left
            // there from the previous pass through this loop.

            ZeroMemory(
                &(rgMessageProps[iToken].Value),
                sizeof(rgMessageProps[iToken].Value));

            // If this "property" is really an attachment then add it
            // to the message.

            if (rgMessageProps[iToken].ulPropTag == PR_NULL)
            {
                TRIM_WHITE_SPACE(rgpszToken[iToken]);

                hr = HrCreateMessageAttachment(
                    pMsg,
                    rgpszToken[iToken]);
                if (FAILED(hr))
                    goto cleanup;
            }

            // Otherwise add the property to the SPropValue array.

            else
            {
                if( rgMessageProps[iToken].ulPropTag & MV_FLAG)
                    hr = HrParseMultiPropValue(
                        rgMessageProps[iToken].ulPropTag,
                        rgpszToken[iToken],
                        rgpszToken,
                        &rgMessageProps[iToken]);
                else
                    hr = HrParsePropValue(
                        rgMessageProps[iToken].ulPropTag,
                        rgpszToken[iToken],
                        rgpszToken,
                        &rgMessageProps[iToken]);
                if (FAILED(hr))
                    goto cleanup;
            }
        }

        // Set the properties on the message.

        hr = MAPICALL(pMsg)->SetProps(
            pMsg,
            cAllMessageProps,
            rgMessageProps,
            &pProblems);
        if (FAILED(hr))
        {
    EventLogMsg(
    FIMPORT_INTERNAL_ERROR,
    1, "SetProps",
                0);
            goto cleanup;
        }
if (pProblems != NULL)
{
    EventLogMsg(
    FIMPORT_PROBLEMS_SETTING_MESSAGE_PROPERTIES,
    0,
                0);

MAPIFREEBUFFER(pProblems);

            hr = HR_LOG(E_FAIL);
goto cleanup;
}

        // Set or clear the read flag according to /SETREAD.

        hr = MAPICALL(pMsg)->SetReadFlag(
            pMsg,
            fSetRead ? SUPPRESS_RECEIPT : CLEAR_READ_FLAG);
        if (FAILED(hr))
        {
    EventLogMsg(
    FIMPORT_INTERNAL_ERROR,
    1, "SetReadFlag",
                0);
            goto cleanup;
        }

        // Save the changes to the message and release it.

        hr = MAPICALL(pMsg)->SaveChanges(pMsg, 0);
        if (FAILED(hr))
        {
    EventLogMsg(
    FIMPORT_INTERNAL_ERROR,
    1, "SaveChanges",
                0);
            goto cleanup;
        }

        ULRELEASE(pMsg);

        // Increment the imported messages count.

        cMessagesImported++;

        // Free the allocated list of tokens.

        MAPIFREEBUFFER(rgpszToken);
    }

cleanup:
    // Clean up the progress indicator.

    if (fProgress && cMessagesImported)
    {
        printf("\r                                          \r");
    }

    // Free resources.
    ULRELEASE(pAttachmentTable);
    ULRELEASE(lpContentsTable);
    ULRELEASE(pMsg);
    MAPIFREEBUFFER(pAttachments);
    MAPIFREEBUFFER(pFolderRow);
    MAPIFREEBUFFER(rgpszToken);
    MAPIFREEBUFFER(pProblems);
    RETURN(hr);
}

//$--HrParseMultiPropValue-------------------------------------------------------
//  Takes a text version of a multi-valued property and fills in an SPropValue
//  structure.  If memory needs to be allocated, it is allocated using
//  MAPIAllocateMore() based on the object passed in pBaseObject.
// -----------------------------------------------------------------------------
static HRESULT HrParseMultiPropValue(       // RETURNS: HRESULT
IN ULONG            ulPropTag,          // property tag of this property
IN LPTSTR           pszValueText,       // text of property value to parse
IN LPVOID           pBaseObject,        // base object for MAPIAllocateMore()
OUT LPSPropValue    pProp)              // structure to write results
{
HRESULT             hr                  = NOERROR;
    ULONG               cToken              = 0L;
    LPTSTR*             rgpszToken          = NULL;
    SPropValue          pvaSingleProp       = {0};
    ULONG               i;  // loop

DEBUGPRIVATE("HrParseMultiPropValue()");

hr = CHK_HrParseMultiPropValue(
ulPropTag,
pszValueText,
pBaseObject,
pProp);
if (FAILED(hr))
RETURN(hr);

    hr = HrStrTokAll(
        pszValueText,
        "%",
        &cToken,
        &rgpszToken);
    if (FAILED(hr))
    {
        EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "HrStrTokAll",
            0);
        goto cleanup;
    }

    for( i=0; i<cToken; i++)
    {
        hr = HrParsePropValue(
            ulPropTag & ~MV_FLAG,
            rgpszToken[i],
            pBaseObject,
            (LPSPropValue)&pvaSingleProp);
        if( FAILED (hr))
        {
            goto cleanup;
        }

        switch (PROP_TYPE(ulPropTag))
    {
            // PT_MV_BINARY

    case PT_MV_BINARY:
    {
                if( !pProp->Value.MVbin.lpbin)
                {
                    hr = MAPIAllocateMore( cToken * sizeof(SBinary), pBaseObject, &pProp->Value.MVbin.lpbin);
        if (FAILED(hr))
        {
        EventLogMsg(
        EDKEVENT_ERROR,
        0,
        1, ERROR_OUTOFMEMORY);
        goto cleanup;
        }

                    pProp->Value.MVbin.cValues = cToken;
                }

                // Relocate single value to MV property array

                pProp->Value.MVbin.lpbin[i].cb  = pvaSingleProp.Value.bin.cb;
                pProp->Value.MVbin.lpbin[i].lpb = pvaSingleProp.Value.bin.lpb;

    break;
    }

            // PT_MV_LONG

    case PT_MV_LONG:
            {
                if( !pProp->Value.MVl.lpl)
                {
                    hr = MAPIAllocateMore( cToken * sizeof(LONG), pBaseObject, &pProp->Value.MVl.lpl);
        if (FAILED(hr))
        {
        EventLogMsg(
        EDKEVENT_ERROR,
        0,
        1, ERROR_OUTOFMEMORY);
        goto cleanup;
        }

                    pProp->Value.MVl.cValues = cToken;
                }

                // Relocate single value to MV property array

                pProp->Value.MVl.lpl[i] = pvaSingleProp.Value.l;

    break;
            }

            // PT_MV_STRING8

    case PT_MV_STRING8:
    {
                if( !pProp->Value.MVszA.lppszA)
                {
                    hr = MAPIAllocateMore( cToken * sizeof(LPSTR), pBaseObject, (PVOID*)&pProp->Value.MVszA.lppszA);
        if (FAILED(hr))
        {
        EventLogMsg(
        EDKEVENT_ERROR,
        0,
        1, ERROR_OUTOFMEMORY);
        goto cleanup;
        }

                    pProp->Value.MVszA.cValues = cToken;
                }

                // Relocate single value to MV property array

                pProp->Value.MVszA.lppszA[i] = pvaSingleProp.Value.lpszA;

    break;
    }

            // PT_MV_UNICODE

    case PT_MV_UNICODE:
    {
                if( !pProp->Value.MVszW.lppszW)
                {
                    hr = MAPIAllocateMore( cToken * sizeof(LPWSTR), pBaseObject, (PVOID*)&pProp->Value.MVszW.lppszW);
        if (FAILED(hr))
        {
        EventLogMsg(
        EDKEVENT_ERROR,
        0,
        1, ERROR_OUTOFMEMORY);
        goto cleanup;
        }

                    pProp->Value.MVszW.cValues = cToken;
                }

                // Relocate single value to MV property array

                pProp->Value.MVszW.lppszW[i] = pvaSingleProp.Value.lpszW;

    break;
    }

            // Non-Supported Property Types

    default:
    {
    hr = HR_LOG(E_NOTIMPL);
    EventLogMsg(
    FIMPORT_UNSUPPORTED_PROPERTY_TYPE,
    4, szFile, SZ_BASE10(iCsvLine), szCsvLine, SZ_BASE16(PROP_TYPE(ulPropTag)),
    0);
    goto cleanup;
    }
    } // switch
    }   // for

pProp->ulPropTag = ulPropTag;

cleanup:
RETURN(hr);
}

//$--HrParsePropValue-----------------------------------------------------------
//  Takes a text version of a property value and fills in an SPropValue
//  structure.  If memory needs to be allocated, it is allocated using
//  MAPIAllocateMore() based on the object passed in pBaseObject.
//
//  Valid property value formats for property types are as follows (and do
//  not include the quotes):
//
//  PT_BINARY   a series of two digit hex numbers separated by spaces
//              (e.g. "01 fe 2a 00 1c")
//
//  PT_BOOLEAN  the value 1 or 0 (1=True, 0=False)
//
//  PT_LONG     a decimal or hex constant (e.g. "127" or "0x0fff0003")
//
//  PT_STRING8  any string of characters (e.g. "Exchange is fun!")
//
//  PT_SYSTIME  a date in the format "yyyy/mm/dd hh:mm:ss"
//              (e.g. "1995/12/31 23:59:59")
//
//  PT_CLSID    fully formatted GUID (e.g. "{########-####-####-####-############}"
//              where '#' is any hex digit)
//
// -----------------------------------------------------------------------------
static HRESULT HrParsePropValue(            // RETURNS: HRESULT
IN ULONG            ulPropTag,          // property tag of this property
IN LPTSTR           pszValueText,       // text of property value to parse
IN LPVOID           pBaseObject,        // base object for MAPIAllocateMore()
OUT LPSPropValue    pProp)              // structure to write results
{
HRESULT             hr                  = NOERROR;
LPTSTR              psz                 = NULL;
LPTSTR              pszPrev             = NULL;

DEBUGPRIVATE("HrParsePropValue()");

hr = CHK_HrParsePropValue(
ulPropTag,
pszValueText,
pBaseObject,
pProp);
if (FAILED(hr))
RETURN(hr);

switch (PROP_TYPE(ulPropTag))
{
        // PT_BINARY

case PT_BINARY:
{
ULONG cb = 0;
ULONG ib = 0;
LPBYTE pb = NULL;
LONG lNumber = 0;

// Count the bytes and check that all the bytes are valid.

psz = pszValueText;
while (*psz)
{
if (isspace(*psz))
psz++;
else
{
pszPrev = psz;
lNumber = strtol(psz, &psz, 16);
if (psz == pszPrev || lNumber > UCHAR_MAX || lNumber < 0)
            {
    hr = HR_LOG(E_FAIL);
    EventLogMsg(
    FIMPORT_ILLEGAL_BINARY_VALUE,
    4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszValueText,
    0);
    goto cleanup;
            }
cb++;
}
}

// Allocate a buffer big enough for the number of bytes.

hr = MAPIAllocateMore(cb, pBaseObject, &pProp->Value.bin.lpb);
if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
pProp->Value.bin.cb = cb;

// Write the bytes into the buffer.

psz = pszValueText;
for (ib = 0, pb = pProp->Value.bin.lpb; ib < cb; ib++, pb++)
{
*pb = (BYTE)strtol(psz, &psz, 16);
}

break;
}

        // PT_CLSID

case PT_CLSID:
{
            CLSID     clsidTemp;
            LPOLESTR  pwszValueText;

            hr = HrStrToStrW( pszValueText, &pwszValueText);
            if( FAILED( hr))
                goto cleanup;

            hr = CLSIDFromString( pwszValueText, &clsidTemp);

            MAPIFREEBUFFER( pwszValueText);

            if( FAILED( hr))
            {
EventLogMsg(
FIMPORT_ILLEGAL_GUID_VALUE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszValueText,
0);
goto cleanup;
}

// Allocate a buffer big enough for a GUID.

hr = MAPIAllocateMore(sizeof(GUID), pBaseObject, &pProp->Value.lpguid);
if (FAILED(hr))
{
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}

// Write the bytes into the buffer.

CopyMemory( pProp->Value.lpguid, &clsidTemp, sizeof( GUID));

break;
}

        // PT_BOOLEAN

case PT_BOOLEAN:
        {
        psz = pszValueText;

        while (isspace(*psz))
        psz++;

        pszPrev = psz;
pProp->Value.l = strtol(psz, &psz, 0);

        while (isspace(*psz))
        psz++;

        if (psz == pszPrev || *psz ||
        (pProp->Value.l != 0 && pProp->Value.l != 1))
        {
    hr = HR_LOG(E_FAIL);
    EventLogMsg(
    FIMPORT_ILLEGAL_BOOLEAN_VALUE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszValueText,
    0);
    goto cleanup;
        }
    break;
        }

        // PT_LONG

case PT_LONG:
        {
        psz = pszValueText;

        while (isspace(*psz))
        psz++;

        pszPrev = psz;
pProp->Value.l = strtol(psz, &psz, 0);

        while (isspace(*psz))
        psz++;

        if (psz == pszPrev || *psz)
        {
    hr = HR_LOG(E_FAIL);
    EventLogMsg(
    FIMPORT_ILLEGAL_LONG_VALUE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszValueText,
    0);
    goto cleanup;
        }
break;
        }

        // PT_STRING8

case PT_STRING8:
{
ULONG cb = cbStrLenA(pszValueText);

hr = MAPIAllocateMore(cb, pBaseObject, &pProp->Value.lpszA);
if (FAILED(hr))
{
    hr = HR_LOG(E_FAIL);
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
CopyMemory(pProp->Value.lpszA, pszValueText, cb);
break;
}

        // PT_UNICODE

case PT_UNICODE:
{
            hr = HrStrAToStrWEx(
                pszValueText,
                pBaseObject,
                &pProp->Value.lpszW);
if (FAILED(hr))
{
    hr = HR_LOG(E_FAIL);
EventLogMsg(
EDKEVENT_ERROR,
0,
1, ERROR_OUTOFMEMORY);
goto cleanup;
}
break;
}

        // PT_SYSTIME

case PT_SYSTIME:
{
SYSTEMTIME st = {0};
        CHAR chBogus = 0;
int lReturn = 0;
BOOL fItWorked = TRUE;

lReturn = sscanf(
pszValueText,
" %hd/%hd/%hd %hd:%hd:%hd %c",
&st.wYear,
&st.wMonth,
&st.wDay,
&st.wHour,
&st.wMinute,
&st.wSecond,
&chBogus);
if (lReturn == 0 || lReturn == EOF || chBogus)
{
hr = HR_LOG(E_FAIL);
EventLogMsg(
FIMPORT_ILLEGAL_SYSTIME_VALUE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszValueText,
0);
goto cleanup;
}

fItWorked = SystemTimeToFileTime(&st, &pProp->Value.ft);
if (!fItWorked)
{
hr = HR_LOG(E_FAIL);
EventLogMsg(
FIMPORT_INTERNAL_ERROR,
1, "SystemTimeToFileTime",
0);
goto cleanup;
}

break;
}

        // Non-Supported Property Types

default:
{
hr = HR_LOG(E_NOTIMPL);
EventLogMsg(
FIMPORT_UNSUPPORTED_PROPERTY_TYPE,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, SZ_BASE16(ulPropTag),
0);
goto cleanup;
}
}

pProp->ulPropTag = ulPropTag;

cleanup:
RETURN(hr);
}

//$--HrCreateMessageAttachment-----------------------------------------------
//
// DESCRIPTION: Create a message attachment and write the contents of the
//specified file to it.  Also create various required properties
//for the message attachment.
//
// INPUT:
//
//[lpMsg]-- Message for which attachment is to be created.
//[lpszAttachPath]-- Path of file containing attachment text.
//
// RETURNS: HRESULT --  NOERROR if O.K.,
//                      E_INVALIDARG if invalid parameter,
//                      E_OUTOFMEMORY if memory problems,
//                      E_FAIL if call fails
//
//---------------------------------------------------------------------------

HRESULT HrCreateMessageAttachment(          // RETURNS: HRESULT
INLPMESSAGElpMsg,              // MAPI message pointer
INLPSTRlpszAttachPath      // list of attachment file names
)
{
    HRESULT         hr              =   NOERROR;    // return code
ULONGcb              =   0;
LPATTACHlpAttach        =NULL;
ULONGulAttachmentNum =   0;
    ULONG           nAttachments    =   0;      // # of attachments
    ULONG           iAttach         =   0;      // attachment index
    LPSTR           lpszCurrent     =   NULL;   // current attachment
LPSTR *lppszAttachTok=NULL;// array of tokenized attachments

    // MAPI property creation flags.
    const ULONG     ulFlags     =   MAPI_CREATE | MAPI_MODIFY |
                                    MAPI_DEFERRED_ERRORS;   // reduces RPCs

    // # of attachment properties
    #define CVALUES 3

    // property value array
    SPropValue      aPropVals[CVALUES]  =   {0};

    DEBUGPRIVATE("HrCreateMessageAttachment()");

    // Check input parameters
    hr = CHK_HrCreateMessageAttachment(lpMsg, lpszAttachPath);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Determine number of attachments by breaking into tokens
hr = HrStrTokAll(
lpszAttachPath,// file name list
";",                // separator character
&nAttachments,// # of file names
&lppszAttachTok);// array of strings pointer
    if (FAILED(hr))
    {
        EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "HrStrTokAll",
            0);
        goto cleanup;
    }

    ASSERTERROR((nAttachments != 0), "Bad nAttachments");
ASSERT_READ_PTR(lppszAttachTok, sizeof(LPSTR) * nAttachments,
"Bad lppszAttachTok");

    // Process each attachment.
    for ( iAttach = 0; iAttach < nAttachments; iAttach++ )
    {
    // Create required additional properties.
LPSPropProblemArraylpProblems =NULL;
LPSTRlpszFileName    =   NULL;

        // Release MAPI objects
    ULRELEASE(lpAttach);

// Retrieve next attachment
lpszCurrent = lppszAttachTok[iAttach];

ASSERT_STRINGA_PTR(lpszCurrent, "Bad lpszCurrent");

    // Create the attachment in the message.
    hr = MAPICALL(lpMsg)->CreateAttach(lpMsg,
                             NULL,
 MAPI_DEFERRED_ERRORS,  // reduces RPCs
 &ulAttachmentNum,
 &lpAttach);
        if (FAILED(hr))
        {
            EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "IMessage::CreateAttach()",
                0);
            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR(lpAttach, "Bad lpAttach");

aPropVals[0].ulPropTag =PR_ATTACH_METHOD;
aPropVals[0].Value.l =ATTACH_BY_VALUE;

lpszFileName = strrchr(lpszCurrent, '\\');

if (!lpszFileName)
lpszFileName = lpszCurrent;
else
lpszFileName++;// Advance past the backslash.

// CAVEAT - We don't do anything here to check for
        // invalid FAT 8.3 names.
aPropVals[1].ulPropTag =PR_ATTACH_FILENAME;
aPropVals[1].Value.lpszA =lpszFileName;

aPropVals[2].ulPropTag =PR_ATTACH_LONG_FILENAME;
aPropVals[2].Value.lpszA =lpszFileName;

hr = MAPICALL(lpAttach)->SetProps(lpAttach,
            CVALUES,
            aPropVals,
            &lpProblems);
        if (FAILED(hr))
        {
            EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "IMAPIProp::SetProps()",
                0);
            goto cleanup;
        }

if (lpProblems != NULL)
{
    EventLogMsg(
    FIMPORT_PROBLEMS_SETTING_MESSAGE_PROPERTIES,
    0,
                0);

MAPIFREEBUFFER(lpProblems);

            hr = HR_LOG(E_FAIL);
goto cleanup;
}

        // Create the PR_ATTACH_DATA_BIN property from the attachment
        // file text.
        hr = HrMAPISetPropFromFile(
            (LPMAPIPROP) lpAttach,  // pointer to attachment object
            PR_ATTACH_DATA_BIN,     // property tag
            lpszCurrent,            // fully-pathed file name from which to read data
            &cb);                   // # bytes read in
        if (FAILED(hr))
        {
            EventLogMsg(
                FIMPORT_CANNOT_ADD_ATTACHMENT,
4, szFile, SZ_BASE10(iCsvLine), szCsvLine, lpszCurrent,
                0);
            goto cleanup;
        }

    // Commit the attachment changes.
    hr = MAPICALL(lpAttach)->SaveChanges(lpAttach, 0);
        if (FAILED(hr))
        {
            EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "IMAPIProp::SaveChanges()",
                0);
            goto cleanup;
        }
    }   // end for each attachment

    // we are done

cleanup:

    // Release MAPI and OLE objects
ULRELEASE(lpAttach);

// Free MAPI buffers
MAPIFREEBUFFER(lppszAttachTok);

RETURN(hr);

}

//$--HrStrAToStrWEx-------------------------------------------------------------
//  Convert a byte string to a word string.  The resulting string is placed in
//  a buffer allocated using MAPIAllocateBuffer if lpvBaseObject == NULL,
//  or MAPIAllocateMore if lpvBaseObject != NULL.
// -----------------------------------------------------------------------------
static HRESULT HrStrAToStrWEx(
    IN  LPCSTR          lpszSource,     // source string
    IN  LPVOID          lpvBaseObject,  // base object for MAPIAllocateMore
    OUT LPWSTR *        lppwszDest)     // destination string
{
    HRESULT             hr              = NOERROR;
    LONG                cchDest         = 0;
    LPWSTR              lpwszDest       = NULL;

    DEBUGPUBLIC("HrStrAToStrWEx()");

    hr = CHK_HrStrAToStrWEx(lpszSource, lpvBaseObject, lppwszDest);
    if (FAILED(hr))
        RETURN(hr);

    // Compute the number of word characters needed for the destination buffer.

    if (*lpszSource)
    {
        cchDest = MultiByteToWideChar(
            CP_ACP,
            MB_PRECOMPOSED,
            lpszSource,
            strlen(lpszSource),
            NULL,
            0);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // Allocate the destination string buffer.

    if (lpvBaseObject == NULL)
    {
        hr = MAPIAllocateBuffer(
            (cchDest + 1) * sizeof(WCHAR),
            &lpwszDest);
    }
    else
    {
        hr = MAPIAllocateMore(
            (cchDest + 1) * sizeof(WCHAR),
            lpvBaseObject,
            &lpwszDest);
    }

    if (FAILED(hr))
        goto cleanup;

    // Convert from byte string to word string.

    if (*lpszSource)
    {
        cchDest = MultiByteToWideChar(
            CP_ACP,
            MB_PRECOMPOSED,
            lpszSource,
            strlen(lpszSource),
            lpwszDest,
            cchDest);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    lpwszDest[cchDest] = 0;

    // Store in output parameter.

    *lppwszDest = lpwszDest;

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpwszDest);
    }

    RETURN(hr);
}

//
// File Access Routines
//

// The import source may be changed from a file to some other
// source by replacing the following routines.

//$--HrOpenCsvFile--------------------------------------------------------------
//  Open the .CSV file and prepare to read lines from it.
// -----------------------------------------------------------------------------
static HRESULT HrOpenCsvFile(void)          // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrOpenCsvFile()");

    // Open the file.

    fpCsvFile = fopen(szFile, "r");
    if (fpCsvFile == NULL)
    {
        EventLogMsg(
            FIMPORT_CANNOT_OPEN_IMPORT_FILE,
            1, szFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Reset the line number counter.

    iCsvLine = 0;
    fCsvLineSaved = FALSE;

cleanup:
    RETURN(hr);
}

//$--HrReadCsvSettings----------------------------------------------------------
//  Read the settings information from the .CSV file.
// -----------------------------------------------------------------------------
static HRESULT HrReadCsvSettings(void)      // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    ULONG               iToken              = 0;
    ULONG               cToken              = 0;
    LPSTR *             rgpszToken          = NULL;
    LPSTR               pszKeyword          = NULL;
    LPSTR               pszValue            = NULL;
    FILECOMMAND         fcCommand           = 0;

    DEBUGPRIVATE("HrReadCsvSettings()");

    // Read a line from the file.

    hr = HrGetCsvLine();
    if (FAILED(hr))
        goto cleanup;

    // Break the line up into tokens separated by commas.

    hr = HrStrTokAll(
        szCsvLine,
        ",",
        &cToken,
        &rgpszToken);
    if (FAILED(hr))
    {
        EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "HrStrTokAll",
            0);
        goto cleanup;
    }

    // If the first token is not "COMMAND" then the file does
    // not contain a settings line.

    TRIM_WHITE_SPACE(rgpszToken[0]);
    if (cToken == 0 || stricmp(rgpszToken[0], "COMMAND"))
    {
        hr = HrUngetCsvLine();
        goto cleanup;
    }

    // Parse each token and store the resulting setting into
    // the appropriate variable.

    for (iToken = 1; iToken < cToken; iToken++)
    {
        // Break each token into keyword and value, separated
        // by an equals sign.

        pszKeyword = rgpszToken[iToken];
        pszValue = strchr(pszKeyword, '=');
        if (pszValue)
            *pszValue++ = 0;
        TRIM_WHITE_SPACE(pszKeyword);

        // Try to match the keyword to a valid command.

        hr = _HrFindArrayValue(
            pszKeyword,
            rgpszFileCommandArray,
            ARRAY_CNT(rgpszFileCommandArray),
            &fcCommand);

        if (FAILED(hr))
        {
            if (hr == EDK_E_NOT_FOUND)
            {
                EventLogMsg(
                    FIMPORT_INVALID_KEYWORD,
                    4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszKeyword,
                    0);
            }
            else if (hr == EDK_E_AMBIGUOUS)
            {
                EventLogMsg(
                    FIMPORT_AMBIGUOUS_KEYWORD,
                    4, szFile, SZ_BASE10(iCsvLine), szCsvLine, pszKeyword,
                    0);
            }
            else
            {
                EventLogMsg(
                    FIMPORT_INTERNAL_ERROR,
                    1, "_HrFindArrayValue",
                    0);
            }

            goto cleanup;
        }

        // Set the setting variable for this keyword.

        switch (fcCommand)
        {
        case FILECOMMAND_APPEND:
            opOperation = OP_APPEND;
            break;

        case FILECOMMAND_UPDATE:
            opOperation = OP_UPDATE;
            break;

        case FILECOMMAND_REPLACE:
            opOperation = OP_REPLACE;
            break;

        case FILECOMMAND_FOLDERPATH:
            STRNCPY(szFolderPath, pszValue);
            break;

        case FILECOMMAND_CLASS:
            STRNCPY(szClass, pszValue);
            break;

        case FILECOMMAND_SETREAD:
            fSetRead = TRUE;
            break;

        case FILECOMMAND_NOSETREAD:
            fSetRead = FALSE;
            break;

        case FILECOMMAND_EFD:
            fEfd = TRUE;
            break;

        case FILECOMMAND_NOEFD:
            fEfd = FALSE;
            break;

        default:
            EventLogMsg(
                FIMPORT_INTERNAL_ERROR,
                1, "switch (fcCommand)",
                0);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    } // end for

cleanup:
    MAPIFREEBUFFER(rgpszToken);

    RETURN(hr);
}

//$--HrGetCsvLine---------------------------------------------------------------
//  Read a line from the .CSV file into szCsvLine.  Setting fCsvLineSaved
//  == TRUE acts as an "unget", and causes the current contents of szCsvLine
//  to be used the next time this routine is called.
//
//  HrGetCsvLine automatically skips empty lines.
//
//  Returns:    NOERROR             = success
//              EDK_E_END_OF_FILE   = end of file
//              E_FAIL              = error
// -----------------------------------------------------------------------------
static HRESULT HrGetCsvLine(void)           // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    int                 ch                  = 0;
    int                 cMatch              = 0;
    char *              pszResult           = NULL;
    int                 cchCsvLine          = 0;

    DEBUGPRIVATE("HrGetCsvLine()");

    // Loop until we get a line we can use.

    do
    {
        // If we have a line saved from last time then just use that one.

        if (fCsvLineSaved)
            fCsvLineSaved = FALSE;

        // Otherwise read another line from the file.

        else
        {
            pszResult =
                fgets(szCsvLine, sizeof(szCsvLine) - 1, fpCsvFile);
            if (pszResult == NULL)
            {
                if (ferror(fpCsvFile))
                {
                    EventLogMsg(
                        FIMPORT_CANNOT_READ_IMPORT_FILE,
                        1, szFile,
                        1, _doserrno);
                    hr = HR_LOG(E_FAIL);
                }
                else
                {
                    hr = EDK_E_END_OF_FILE;
                }

                goto cleanup;
            }

            // Increment the line number.

            iCsvLine++;

            // Strip newline from end of line.

            cchCsvLine = strlen(szCsvLine);
            if (cchCsvLine && szCsvLine[cchCsvLine - 1] == '\n')
                szCsvLine[cchCsvLine - 1] = 0;
            else
            {
                EventLogMsg(
                    FIMPORT_IMPORT_LINE_TOO_LONG,
                    3, szFile, SZ_BASE10(iCsvLine),
                        szCsvLine,
                    0);
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        // Get the first non-whitespace character in the line so we
        // can make sure it isn't a blank line.

        cMatch = sscanf(szCsvLine, " %c", &ch);
    }
    while (cMatch != 1);

cleanup:
    RETURN(hr);
}

//$--HrUngetCsvLine-------------------------------------------------------------
//  Push the current line back on the input stream so that it will be
//  returned by the next call to HrGetCsvLine.
// -----------------------------------------------------------------------------
static HRESULT HrUngetCsvLine(void)         // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;

    DEBUGPRIVATE("HrUngetCsvLine()");

    // Make sure that there isn't already another saved line and
    // that we have read at least one line from the file.

    if (fCsvLineSaved || iCsvLine == 0)
    {
        EventLogMsg(
            FIMPORT_INTERNAL_ERROR,
            1, "HrUngetCsvLine",
            0);
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Set the flag to "unget" the current line.

    fCsvLineSaved = TRUE;

cleanup:
    RETURN(hr);
}

//$--HrCloseCsvFile-------------------------------------------------------------
//  Close the .CSV file.
// -----------------------------------------------------------------------------
static HRESULT HrCloseCsvFile(void)         // RETURNS: HRESULT
{
    HRESULT             hr                  = NOERROR;
    int                 nResult             = 0;

    DEBUGPRIVATE("HrCloseCsvFile()");

    // Close the file.

    nResult = fclose(fpCsvFile);

    if (nResult)
    {
        EventLogMsg(
            FIMPORT_CANNOT_CLOSE_IMPORT_FILE,
            1, szFile,
            1, _doserrno);
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}


GWCLEAN.CPP

// --gwclean.cpp--------------------------------------------------------------
//
// Implements the GWCLEAN gateway utility which
// moves all messages in a gateway's folder before a particular
// date and time to a PST (personal information store).
//
// Syntax:
//
// GWCLEAN GatewayProfileName [flags]
//
// where the flags are:
//
//  -DAYS=<#DaysPriorToToday>    --  # of days prior to today
//                                   defaults to yesterday
//  -FILE=<PSTFileName>          --  PST file name to move messages to
//                                   defaults to GWCLEAN.PST in current
//                                   directory
//  -FOLDER=<GatewayFolderName>  --  folder to move gateway message from
//                                   defaults to MTS-OUT
//
// Copyright (C) Microsoft Corporation 1986-1996.  All Rights Reserved
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "mspst.h"

#include "gwstring.h"
#include "gwobjcls.h"
#include "gwevents.h"// compiled from gwevents.mc
#include "gwclean.chk"

// Globals
CGWClean * lpCGWClean  =   NULL;    // global GWCLEAN class object pointer

// external function declarations
extern HRESULT HrParseCommandLine(
                    IN INT argc,    // argument count
                    IN CHAR * argv[],   // list of arguments
                    OUT LPTSTR lpProfile,      // GW profile name
                    OUT LPTSTR lpPSTName,      // PST name
                    OUT LPTSTR lpFolder,       // GW folder name
                    OUT LPFILETIME lpBeforeTime); // date/time before structure pointer

extern VOID DisplayUserMsg(         // RETURNS: VOID
                    IN UINT uResID,            // resource identifier
                    ...);           // variable arguments

extern HRESULT HrComputeDate(           // RETURNS: HRESULT
                    IN UINT,            // number of days before today
                    OUT LPFILETIME);    // file time structure pointer

extern HRESULT HrCheckFileName(     // RETURNS: HRESULT
            IN LPTSTR lpFileName);  // file name to check

// useful constants
LPTSTRpszAppName=TEXT("GWCLEAN");

//$--INTERNAL_ERROR-----------------------------------------------------------
//  Helper function for EventLogMsg
// ---------------------------------------------------------------------------
extern inline VOID INTERNAL_ERROR(
    IN LPCTSTR str,
    IN const HRESULT hr)
{
    TCHAR szErrorCode[16] = {0};

    EventLogMsg(
        GWCLEAN_INTERNALERR,
        2, str, _itot( hr, szErrorCode, 16),
        0);
}

//$--main---------------------------------------------------------------------
//
// DESCRIPTION: Implement GWCLEAN gateway utility described above.
//
// INPUT:       INT   --  number of command line arguments
//              TCHAR[] *    --  command line arguments
//
// RETURNS:     int              --  0 if successful,
//                                  non-zero otherwise:
//                                  (See edkcode.h for more specifics)
//
// ----------------------------------------------------------------------------

INT main(                   // RETURNS: INT
        INT argc,           // number of command line arguments
        TCHAR * argv[])     // command line arguments
{
    HRESULT     hr          =   NOERROR;
HRESULThrT=NOERROR;// temporary return code
    FILETIME    sBeforeTime =   {0};    // before date/time structure
    TCHAR       lpProfile[MAX_PATH+1]   =   ""; // GW profile name
    TCHAR       lpPSTName[MAX_PATH+1]   =   ""; // PST file name
    TCHAR       lpFolder[MAX_PATH+1]    =   ""; // GW folder name
HANDLEhEvent=NULL;// event logging handle
EDKEVENTCOUNTsEventCount={0};// event log count structure
BOOLfEventLogOpened=FALSE;// TRUE if event log is open
    TCHAR           szErrorCode[16]         =   {0};

    // Special base class which guarantees that MAPI is
    // unitialized when it goes out of scope.
    BOOL        fInitialized    =   FALSE;  // TRUE if MAPI is initialized

    DEBUGPUBLIC("main()\n");

    // check command line arguments
    hr = CHK_main(argc, argv);

    if ( FAILED(hr) )
    {
        // This should never happen
MODULE_ERROR("Arguments to main() invalid");
        return _nEcFromHr(hr);
    }

// Open the NT event log for this application.
 hr = HrEventOpenLog(
pszAppName,// application name
NULL,// executable name (computed)
    NULL,// event message file (computed)
NULL,// parameter message file
NULL,// category message file
&hEvent);// event logging handle

if ( FAILED(hr) )
{
DisplayUserMsg(
IDS_EVENT_LOG,
hr);

goto cleanup;
}

ASSERTERROR((hEvent != NULL), "Bad hEvent");

fEventLogOpened = TRUE;

    // Instantiate a GWCLEAN object.
    lpCGWClean = new CGWClean();

    if ( !lpCGWClean )
    {
EventLogMsg(
GWCLEAN_OUTOFMEMORY,
0,// # of string replacements
0);// # of Win32 error arguments

        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    // Parse command line arguments.  The syntax is:
    //
    // GWCLEAN <GWProfileName> -FILE=<PSTName> -DAYS=<#days> -FOLDER=<folder>
    //
    // where PSTName is a fully-pathed file name, #days
    // is a positive integer representing the number of days before
    // today for which to move all gateway <folder> messages which were
    // sent on before that date, and GWProfileName is the name of an
    // gateway profile.
    //
    hr = HrParseCommandLine(argc, argv,
            lpProfile,      // GW profile name
            lpPSTName,      // PST file name
            lpFolder,       // GW folder name
            &sBeforeTime);  // before time pointer

    if ( FAILED(hr) )
    {
        // User errors are displayed in HrParseCommandLine()
        goto cleanup;
    }

    // Initialize use of extended MAPI.
    hr = MAPIInitialize(NULL);

    if ( FAILED(hr) )
    {
EventLogMsg(
GWCLEAN_MAPIINIT,
1, _itot( hr, szErrorCode, 16),
0);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    fInitialized = TRUE; // MAPI is initialized

    // Initialize our CGWClean class instance.  This
    // function takes care of creating the new PST, logging
    // on to the gateway and temporary profiles, etc...
    hr = lpCGWClean->HrInitialize(
    lpProfile,
        lpPSTName,
        lpFolder,
        &sBeforeTime);

    if ( FAILED(hr) )
    {
        // User messages are taken care of in HrInitialize().
        goto cleanup;
    }

    // Move messages in GW MTS-OUT folder before date/time to
    // new PST folder.
    hr = lpCGWClean->HrMoveMsgs();

    if ( FAILED(hr) )
    {
        // user messages displayed in HrMoveMsgs
        goto cleanup;
    }

cleanup:

// If doing event logging
if ( fEventLogOpened == TRUE )
{
// Retrieve number of errors and warnings written to the event log.
ZeroMemory(&sEventCount, sizeof(sEventCount));
hrT = HrEventGetCounts(
&sEventCount);// structure to hold event logging statistics

if ( FAILED(hrT) )
{
            INTERNAL_ERROR( TEXT("HrEventGetCounts()"), hrT);
sEventCount.cError = 1;// have at least one error
}

if ( sEventCount.cError == 1 )
{
// print one error message
DisplayUserMsg(
IDS_1ERROR);
}

else if ( sEventCount.cError > 1 )
{
// print multiple error message
DisplayUserMsg(
IDS_ERRORS);
}

if ( sEventCount.cWarning == 1 )
{
// print out one warning message
DisplayUserMsg(
IDS_1WARNING);
}

else if ( sEventCount.cWarning > 1 )
{
// print out multiple warnings message
DisplayUserMsg(
IDS_WARNINGS);
}

// close event log
(VOID)HrEventCloseLog();
hEvent = NULL;
fEventLogOpened = FALSE;

}// end if doing event logging

    if ( lpCGWClean )
    {
        // delete gwclean object.
        // A side effect of this is that all MAPI objects are freed
        // and all sessions are logged off.
        delete lpCGWClean;

    }   // end if lpCGWClean

    if ( fInitialized )
    {
        // Unitialize MAPI
        (VOID)MAPIUninitialize();
    }

    // We are done.
    // return the appropriate exit code
    return _nEcFromHr(hr);

}   // end main()

//$--HrParseCommandLine---------------------------------------
//
// DESCRIPTION: Parse GWCLEAN command line
//
// INPUT:   argc    --  argument count
//          argv    --  list of arguments
//
// OUTPUT:  lpProfile   --  GW profile name pointer (MAX_PATH + 1 character buffer)
//          lpPSTName   --  PST file name (MAX_PATH + 1 character buffer)
//          lpFolder    --  GW folder name (MAX_PATH + 1 character buffer)
//          pnDays      --  #days before pointer
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise
//
// ------------------------------------------------------------
HRESULT HrParseCommandLine(                     // RETURNS: HRESULT
                    IN INT argc,                // argument count
                    IN TCHAR * argv[],          // list of arguments
                    OUT LPTSTR lpProfile,      // GW profile name
                    OUT LPTSTR lpPSTName,      // PST name
                    OUT LPTSTR lpFolder,       // GW folder name
                    OUT LPFILETIME lpBeforeTime)// before time pointer
{
    HRESULT     hr          =   NOERROR;
    LPTSTR      lpData      =   NULL;       // pointer to flag data
    LPTSTR      lpFlag      =   NULL;       // pointer to flag name
    ULONG       iFlag       =   0;          // flag index
    INT         iArg        =   0;          // argument index
    UINT        nDays       =   0;          // number of days before today
    BOOL        fDaysFound  =   FALSE;      // TRUE if user specifies DAYS flag
    LPTSTR      lpTemp      =   0;          // temporary string pointer
    UINT        cchExeName  =   0;          // # characters in executable name
    DWORD       dwRetVal    =   0;          // Win32 API return code

    // fully-pathed module file name
    TCHAR       lpszExeName[MAX_PATH + 1]   = TEXT("");

    // default values
    TCHAR       lpszPath[MAX_PATH + 1]      = TEXT(""); // default path
    TCHAR       lpszPSTName[MAX_PATH + 1]   = TEXT(""); // default PST name

    const LPTSTR    lpszFolder  =   TEXT("MTS-OUT");        // gw folder
    const UINT      nDefDays    =   1;                      // #days

    const TCHAR     chBackSlash =   TEXT('\\');             // back slash
    const LPTSTR    lpszPSTExt  =   TEXT("PST");             // PST extension
    const LPTSTR    lpszEXEExt  =   TEXT("EXE");            // EXE extension

    // argument indices into the argument flag array
    const UINT      iHelp1      =   0;                      // HELP index
    const UINT      iHelp2      =   1;                      // HELP index
    const UINT      iDays       =   2;                      // DAYS index
    const UINT      iFile       =   3;                      // FILE index
    const UINT      iFolder     =   4;                      // FOLDER index

    // # of command line flags
    const ULONG     nFlags      =   5;

    // Array of known flags for this program.
    const LPCTSTR rgpszFlags[nFlags]  =
    {
        TEXT("HELP"),   // help flag
        TEXT("?"),      // help flag
        TEXT("DAYS"),   // # days before today flag
        TEXT("FILE"),   // PST file name flag
        TEXT("FOLDER")  // gateway folder name flag
    };

    DEBUGPRIVATE("HrParseCommandLine()\n");

    hr = CHK_HrParseCommandLine(argc, argv,
                    lpProfile, lpPSTName, lpFolder, lpBeforeTime);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // If no arguments are passed in, print the syntax
    // message
    if ( argc < 2 )
    {
        DisplayUserMsg(
                IDS_USAGE);
        DisplayUserMsg(
                IDS_BLANK);
        DisplayUserMsg(
                IDS_USAGEPROF);
        DisplayUserMsg(
                IDS_USAGEDAYS);
        DisplayUserMsg(
                IDS_USAGEPST);
        DisplayUserMsg(
                IDS_USAGEFOLDER);
        DisplayUserMsg(
                IDS_USAGEHELP);

        hr = HR_LOG(E_INVALIDARG);

        goto cleanup;
    }

    // Build the default default PST file
    // name from the fully-pathed executable name.
    dwRetVal = GetModuleFileName(
                NULL,      // module handle (default is current application)
                lpszExeName,    // string buffer
                MAX_PATH);      // maximum length

    if ( dwRetVal == 0 )
    {
        INTERNAL_ERROR( TEXT("GetModuleFileName()"), dwRetVal);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cchExeName = lstrlen(lpszExeName);  // # characters in executable name
    lstrcpy(lpszPSTName, lpszExeName);

    // Replace the EXE extension with the PST extension.
    lpTemp = lpszPSTName + (cchExeName - lstrlen(lpszEXEExt));
    lstrcpy(lpTemp,
            lpszPSTExt);

    // Retrieve the default path name
    lstrcpy(lpszPath, lpszExeName);

    // Strip off the executable name.
    lpTemp = lpszPath + (cchExeName - 1);
    while ( *lpTemp != chBackSlash )
    {
        if ( lpTemp == lpszPath )
        {
            // back at the beginning.  This is an error.
            INTERNAL_ERROR( TEXT("HrParseCommandLine()"), E_FAIL);

            hr = HR_LOG(E_FAIL);
            goto cleanup;

        }   // if can't find back slash

        lpTemp--;

    }   // end while

    // When we get to here, lpTemp is pointing to the back slash.
    *(lpTemp + 1) = 0;  // null terminate path

    // loop throught the command line arguments
    // Start with the first user-supplied argument
    for ( iArg = 1; iArg < argc; iArg++ )
    {
        hr = _HrExpandCommandLineArgument(
            argv[iArg],     // argument user entered
            (LPTSTR *) rgpszFlags,     // array of recognized switches
            nFlags,         // number of known flags
            &iFlag,         // pointer to flag index into array
            &lpFlag,        // pointer to flag name
            &lpData);       // pointer to flag data

        if ( FAILED(hr) )
        {
            if ( hr == EDK_E_NOT_FOUND )
            {
                // Bad command line argument.
                // Display error message.
                DisplayUserMsg(
                    IDS_PARAMERR);

                hr = HR_LOG(E_INVALIDARG);

                goto cleanup;
            }

            else
            {
                INTERNAL_ERROR( TEXT("_HrExpandCommandLineArgument()"), hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        if ( (lpFlag == NULL) && (*lpData != 0) )
        {
            // This should be the gateway profile name
            // (The profile name doesn't have a flag.)
            lstrcpyn(lpProfile, lpData, MAX_PATH);

            continue;   // go on to next argument
        }

        // We have a valid flag.  Copy the data
        // to the appropriate parameter and test it.
        switch ( iFlag )
        {
            case iHelp1:
            case iHelp2:
                // user wants help message.
                // Print it out.
                DisplayUserMsg(
                        IDS_HELPDESCR);
                DisplayUserMsg(
                        IDS_BLANK);
                DisplayUserMsg(
                        IDS_USAGE);
                DisplayUserMsg(
                        IDS_BLANK);
                DisplayUserMsg(
                        IDS_USAGEPROF);
                DisplayUserMsg(
                        IDS_HELPDAYS1);
                DisplayUserMsg(
                        IDS_HELPDAYS2);
                DisplayUserMsg(
                        IDS_HELPDAYS3);
                DisplayUserMsg(
                        IDS_HELPDAYS4);
                DisplayUserMsg(
                        IDS_HELPDAYS5);
                DisplayUserMsg(
                        IDS_USAGEPST);
                DisplayUserMsg(
                        IDS_HELPPST);
                DisplayUserMsg(
                        IDS_USAGEFOLDER);
                DisplayUserMsg(
                        IDS_HELPFOLDER);
                DisplayUserMsg(
                        IDS_HELPHELP);

                hr = HR_LOG(E_INVALIDARG);

                goto cleanup;

            case iDays:
                // Check number of days variable
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    // Bad command line argument.
                    // Display error message.
                    DisplayUserMsg(
                        IDS_BADDAYS);

                    hr = HR_LOG(E_INVALIDARG);

                    goto cleanup;
                }

                // Make sure number of days is a valid decimal
                // number less than or equal to 9999.
                if ( lstrlen(lpData) > 4 )
                {
                    // number of days is greater than 9999.
                    DisplayUserMsg(
                            IDS_BADDAYS);

                    hr = HR_LOG(E_INVALIDARG);

                    goto cleanup;
                }

                // check individual characters of days value
                lpTemp = lpData;
                while ( *lpTemp )
                {
                    if ( ! isdigit(*lpTemp) )
                    {
                        // number of days is not a non-negative
                        // integer!
                        DisplayUserMsg(
                            IDS_BADDAYS);

                        hr = HR_LOG(E_INVALIDARG);

                        goto cleanup;
                    }

                    lpTemp++;

                }   // end while

                // Try to convert #days to an integer.
                nDays = atoi(lpData);

                fDaysFound = TRUE;  // user specified DAYS value

                break;

            case iFile:
                // check PST output file name
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    // invalid file name.
                    DisplayUserMsg(
                            IDS_NOFILE);

                    hr = HR_LOG(E_INVALIDARG);

                    goto cleanup;
                }

                // Save PST file name
                if ( strchr(lpData, chBackSlash) == NULL )
                {
                    lstrcpyn(lpPSTName, lpszPath, MAX_PATH);
                    strncat(lpPSTName, lpData, MAX_PATH);
                }

                else
                {
                    // already have full path
                    lstrcpyn(lpPSTName, lpData, MAX_PATH);
                }

                break;

            case iFolder:
                // check gateway folder name
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    // invalid folder name.
                    DisplayUserMsg(
                            IDS_NOFOLDER);

                    hr = HR_LOG(E_INVALIDARG);

                    goto cleanup;
                }

                // save folder name
                lstrcpyn(lpFolder, lpData, MAX_PATH);

                // let open folder call determine if its a valid
                // folder name.

                break;

            default:
                // shouldn't happen!
                INTERNAL_ERROR( TEXT("HrParseCommandLine()"), E_FAIL);

                hr = HR_LOG(E_FAIL);
                goto cleanup;

        }   // end switch
    }   // end for

    // Check required parameters
    if ( *lpProfile == 0 )
    {
        DisplayUserMsg(
                   IDS_NOPROFILE);

        hr = HR_LOG(E_INVALIDARG);

        goto cleanup;
    }

    // Handle default values.
    if ( !fDaysFound )
    {
        // use default days value (yesterday)
        nDays = nDefDays;
    }

    // Convert # days to a data and time.
    hr = HrComputeDate(nDays, lpBeforeTime);

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrComputeDate()"), hr);

        goto cleanup;
    }

    if ( *lpFolder == 0 )
    {
        // use default folder.
        lstrcpyn(lpFolder, lpszFolder, MAX_PATH);
    }

    if ( *lpPSTName == 0 )
    {
        // use default PST name
        lstrcpyn(lpPSTName, lpszPSTName, MAX_PATH);
    }

    // Check to see if this file is valid
    // and can be created or overwritten
    hr = HrCheckFileName(lpPSTName);

    if ( FAILED(hr) )
    {
        // HrCheckFileName prints out error
        // messages.
        goto cleanup;
    }

    // We are done.

cleanup:

    RETURN(hr);

}

//$--HrCheckFileName----------------------------------------------
//
// DESCRIPTION: Checks to see if file exists.  If it does
//              prompts user as to whether or not to overwrite
//              it.  If file is valid file name and can be over-written,
//              returns NOERROR.  Otherwise, returns E_FAIL.
//
// INPUT:   lpFileName  --  file name to test for existence
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
// ---------------------------------------------------------------
HRESULT HrCheckFileName(           // RETURNS: HRESULT
            IN LPTSTR lpFileName)   // file name to check
{
    HRESULT hr                  =   NOERROR;
    HANDLE  hFile               =   NULL;   // file handle
    DWORD   dwLastError         =   0;      // last Win32 error
    TCHAR   szLine[MAX_PATH]    =   {0};    // large input buffer

    DEBUGPRIVATE("HrCheckFileName()\n");

    // check input parameters
    hr = CHK_HrCheckFileName(lpFileName);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    hFile = CreateFile(lpFileName,
                GENERIC_READ | GENERIC_WRITE,
                0,
                0,
                CREATE_NEW,
                FILE_ATTRIBUTE_NORMAL,
                0);

    if ( hFile == INVALID_HANDLE_VALUE )
    {
        dwLastError = GetLastError();   // Get windows error code
        if ( dwLastError == ERROR_FILE_EXISTS )
        {
            // File already exists.  Ask user is he/she wants to
            // overwrite it.
            while ( hFile == INVALID_HANDLE_VALUE )
            {
                DisplayUserMsg(
                        IDS_OVERWRITEFILE,
                        lpFileName);

                if ( gets(szLine) == NULL )
                {
                    INTERNAL_ERROR( TEXT("gets()"), GetLastError());

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                switch ( szLine[0] )
                {
                    case TEXT('Y'):
                    case TEXT('y'):
                        // overwrite the file.
                        hFile = CreateFile(lpFileName,
                                           GENERIC_READ | GENERIC_WRITE,
                                           0,
                                           0,
                                           TRUNCATE_EXISTING,
                                           FILE_ATTRIBUTE_NORMAL,
                                           0);

                        if ( hFile == INVALID_HANDLE_VALUE )
                        {
                            DisplayUserMsg(
                            IDS_OUTPUTERR,
                                lpFileName);

                            hr = HR_LOG(E_FAIL);

                            goto cleanup;
                        }

                        break;

                    case TEXT('n'):
                    case TEXT('N'):
                        // Give up!
                        DisplayUserMsg(
                            IDS_USERCANCEL);

                        // This is not really an error.  The user
                        // just wishes to terminate the program early.
                        hr = HR_LOG(E_FAIL);

                        goto cleanup;

                    default:
                        // ask again!
                        continue;

                }   // end switch
            }   // end while
        }   // end if file already exits.

        else
        {
            // some other file path or permission-type error
            DisplayUserMsg(
                    IDS_OUTPUTERR,
                    lpFileName);

            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

    }   // end if can't create new file

    // O.K. to overwrite file.
    // Delete the file just created.
    if ( hFile != INVALID_HANDLE_VALUE )
    {
        CLOSEHANDLE(hFile);

        (VOID)DeleteFile(lpFileName);
    }

cleanup:

    // close the opened file.
    CLOSEHANDLE(hFile);

    RETURN(hr);

}

//$--DisplayUserMsg----------------------------------------------------------
//
// DESCRIPTION: Utility to display message to command line user.
//
// INPUT: UINT        --  Resource string identifier
//
// NOTES:       This function can handle up to nine variable string arguments.
//
// RETURNS:     nothing
//
// ----------------------------------------------------------------------------

VOID DisplayUserMsg(            // RETURNS: VOID
            IN UINT nResID,     // resource string identifier
            ...)                // additional arguments
{
    INT     nRetCode= 0;          // return value
    va_list vArgList=   {0};        // variable argument list
static HMODULEhInst=NULL;// application handle
staticBOOLfInit=FALSE;// TRUE if hInst is initialized

    const   INT cbMsgLength=   256;        // maximum length of printed messageso

// LoadString message buffer
    TCHAR   szMessage[cbMsgLength]=   TEXT("");

    DEBUGPRIVATE("DisplayUserMsg()\n");

    // No CHK_DisplayUserMsg() call.
    // We check arguments as we go, as we are already in an error state.

    // Check the instance handle passed in.
    if ( fInit == FALSE )
    {
// Retrieve this applications module handle.
hInst = GetModuleHandle(NULL);

fInit = TRUE;

if ( hInst == NULL )
{
        // Print out default error string.
        fprintf(stderr, "ERROR: Can't dipslay resource strings.\n");

        HR_LOG(E_FAIL);

        goto cleanup;
    }
}

if ( hInst == NULL )
{
// nothing to do
goto cleanup;
}

    nRetCode = LoadString(hInst, nResID, szMessage, cbMsgLength);

    if ( nRetCode == 0 )
    {
        // Print out default error string.
        fprintf(stderr, "ERROR: Can't load resource string %d.\n", nRetCode);

        HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Get an optional argument list pointer
    va_start(vArgList, nResID);

    // Print the message to standard out
    _vtprintf(szMessage,        // format string
            vArgList);          // variable argument list

    va_end(vArgList);

cleanup:

    // Return
    return;

}   // end DisplayUserMsg()

//$--HrComputeDate-----------------------------------------------------------
//
// DESCRIPTION: Computes the date of today minus the number of days passed
//              in and fills in the file time structure buffer passed in.
//
// OUTPUT:      lpSysFileTime   --  pointer to file time buffer
//
// RETURNS:     HRESULT --  NOERROR if O.K.,
//                          E_INVALIDARG if bad input.
//
// ---------------------------------------------------------------------------

static HRESULT HrComputeDate(                      // RETURNS: HRESULT
            IN  UINT uDaysBack,             // number of days before today
            OUT LPFILETIME lpSysFileTime)   // pointer to system file time
{
    HRESULT     hr              =   NOERROR;    // return code
    INT         iCounter        =   0;          // loop counter
    SYSTEMTIME  sLocalSysTime   =   {0};        // Local system time structure

FILETIME    sLocalFileTime  =   {0};        // Local file time structure
    DWORDLONG   dwlFileTime     =   0;          // file time converted to DWORDLONG

    const DWORDLONG dwlNum100nsPerDay = 864000000000; // number of 100 nanoseconds in one day!

    DEBUGPRIVATE("HrComputeDate\n");

    hr = CHK_HrComputeDate(uDaysBack, lpSysFileTime);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Compute desired system time of the before date/time variable
    // before which all gateway MTS-OUT messages are to be moved.
    // Algorithm:
    //  1) Today's date and time.
    //  2) Modify
    // from # days before today parameter passed in by user.
    // First, get the current system time and adjust it to the end
    // of today and convert that to a FILETIME value.  Then, convert the
    // FILETIME value to a DWORDLONG (64 bit) value.
    GetLocalTime(&sLocalSysTime);    // Get local time
    sLocalSysTime.wHour = 23;
    sLocalSysTime.wMinute = 59;
    sLocalSysTime.wSecond = 59;
    sLocalSysTime.wMilliseconds = 999;
    if ( sLocalSysTime.wDayOfWeek )
    {
        sLocalSysTime.wDayOfWeek--;
    }
    else
    {
        sLocalSysTime.wDayOfWeek = 6;    // Sunday to Saturday
    }
    SystemTimeToFileTime(&sLocalSysTime, &sLocalFileTime);   // local time to local file time
    LocalFileTimeToFileTime(&sLocalFileTime, lpSysFileTime);   // local file time to system file time
    dwlFileTime = ((DWORDLONG)((DWORDLONG) lpSysFileTime->dwLowDateTime) |
        (DWORDLONG)(((DWORDLONG)(((DWORDLONG)lpSysFileTime->dwHighDateTime) << 32)) & 0xFFFFFFFF00000000));

    // Subtract off the desired number of 100 nanoseconds from this mornings
    // time.
    // (The FILETIME structure measures time in 100 nanoseconds intervals.)
    // Then, convert this value back to a FILETIME value.
    dwlFileTime -= (DWORDLONG)(((DWORDLONG) uDaysBack) * dwlNum100nsPerDay);
    lpSysFileTime->dwLowDateTime = (DWORD) dwlFileTime;
    lpSysFileTime->dwHighDateTime =
        (DWORD) (((DWORDLONG) (dwlFileTime >> 32)) & 0xFFFFFFFF);

    RETURN(hr);

}   // end HrComputeDate()

/*
 * EOF
 */


GWOBJCLS.CPP

// --gwobjcls.cpp-------------------------------------------------------------
//
// Class source file for the GWCLEAN class object.
//
// Copyright (C) Microsoft Corporation 1986-1996,  All Rights Reserved
//
// ----------------------------------------------------------------------------

#include "edk.h"
#include "mspst.h"

#include "gwstring.h"
#include "gwobjcls.h"
#include "gwevents.h"// compiled from gwevents.mc
#include "gwobjcls.chk"

// external function declarations
extern VOID DisplayUserMsg(// RETURNS: VOID
IN UINT uResID,// resource string identifier
 ...);// additional arguments

extern inline VOID INTERNAL_ERROR(
    IN LPCTSTR str,
    IN const HRESULT hr);

//$--CGWClean::CGWClean----------------------------------------
//
// DESCRIPTION: constructor for CGWClean
//
// INPUT:   none
//
// RETURNS: nothing
//
// -------------------------------------------------------------
CGWClean::CGWClean()        // returns nothing
{
    DEBUGPRIVATE("CGWClean::CGWClean()\n");

    // Initialize data members.
    m_fNewPST           =   FALSE;
    m_fNoMessages       =   FALSE;
    m_lpProfileName     =   NULL;
    m_lpPSTName         =   NULL;
    m_lpFolderName      =   NULL;
    m_lpBeforeDateTime  =   NULL;
    m_lpTempProfilePW   =   NULL;
    m_lpTempProfName    =   NULL;
    m_lpSessGW          =   NULL;
    m_lpSessNew         =   NULL;
    m_lpMDBGateway      =   NULL;
    m_lpMDBNew          =   NULL;
    m_lpFolderFrom      =   NULL;
    m_lpFolderTo        =   NULL;

}

//$--CGWClean::~CGWClean-----------------------------------------------------
//
// DESCRIPTION: CGWClean Destructor
//
// ---------------------------------------------------------------------------

CGWClean::~CGWClean()
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("CGWClean::~CGWClean()\n");

    if ( m_lpMDBGateway )
    {
        // Set the PR_TRANSFER_ENABLED property so that message
        // transfer activity resumes on the gateway.
        hr = HrMAPISetPropBoolean(
                m_lpMDBGateway,         // MAPI object pointer
                PR_TRANSFER_ENABLED,    // property tag
                TRUE);                  // boolean value

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("HrMAPISetPropBoolean()"), hr);
        }
    }

    // clean up after PST creation.  (Removes temporary profile)
    if ( m_fNewPST )
    {
        (VOID)HrCleanupPSTGlobals(m_lpTempProfName);

        // If there were no messages to move, delete the
        // PST created.
        if ( m_fNoMessages )
        {
            (VOID)DeleteFile(m_lpPSTName);
        }
    }

    // Free MAPI buffers
    MAPIFREEBUFFER(m_lpTempProfilePW);
    MAPIFREEBUFFER(m_lpTempProfName);

    // Release MAPI store and folder objects
    ULRELEASE(m_lpMDBGateway);
    ULRELEASE(m_lpMDBNew);
    ULRELEASE(m_lpFolderFrom);
    ULRELEASE(m_lpFolderTo);

    // Logoff any MAPI sessions to which we are logged on.
    if ( m_lpSessGW )
    {
        (VOID)m_lpSessGW->Logoff(0, 0, 0);
    }

    if ( m_lpSessNew )
    {
        (VOID)m_lpSessNew->Logoff(0, 0, 0);
    }

    // Release MAPI session objects if they have not already been released.
    ULRELEASE(m_lpSessGW);
    ULRELEASE(m_lpSessNew);

    // Delete m_lpBeforeDateTime buffer.
    if ( m_lpBeforeDateTime )
    {
        delete m_lpBeforeDateTime;
    }

}   // end CGWClean::~CGWClean();

//$--CGWClean::HrInitialize-------------------------------------------------------
//
// DESCRIPTION: CGWClean initializer.  Creates profile,
//              establishes sessions, opens default stores,
//              opens from and to folders.
//
// INPUT:   lpProfileName   --  gateway profile name
//          lpPSTName  --  new PST name
//          lpFolder    --  gateway folder name
//          lpBeforeDate  --  date/time before which to move messages
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_OUTOFMEMORY if memory problems,
//                      E_FAIL otherwise
//
// ---------------------------------------------------------------------------

HRESULT CGWClean::HrInitialize(
             IN LPTSTR lpProfileName,       // gateway profile name
             IN LPTSTR lpPSTName,           // New PST name
             IN LPTSTR lpFolderName,        // gateway folder name
             IN LPFILETIME lpBeforeTime)    // date/time before which to move messages
{
    HRESULT hr      =   NOERROR;
    ULONG   cbEid   =   0;          // number of bytes in entry identifier
    TCHAR   szErrorCode[16] = {0};

    // Special MAPI buffer base class which frees itself
    // when it goes out of scope.
    LPENTRYID   lpEid   =   NULL;   // entry identifier pointer

    // MAPI session flags
    const ULONG ulFlags =   MAPI_EXPLICIT_PROFILE |
                            MAPI_NEW_SESSION |
                            MAPI_NO_MAIL;

    // new PST PR_DISPLAY_NAME value
    const LPTSTR   lpszDisplayName =   TEXT("Mailbox - GWCLEAN");

    // Separator in folder name.
    const TCHAR     cSeparator  =   TEXT('\\');

    // message database store flags
    const ULONG     ulMDBflags  =   MDB_NO_DIALOG | MDB_WRITE;

    DEBUGPRIVATE("CGWClean::HrInitialize()\n");

    hr = CHK_CGWClean_HrInitialize(lpProfileName,
                                 lpPSTName, lpFolderName, lpBeforeTime);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize class members
    m_lpProfileName = lpProfileName;
    m_lpPSTName = lpPSTName;
    m_lpFolderName = lpFolderName;

    m_lpBeforeDateTime = new FILETIME;

    if( !TEST_WRITE_PTR( m_lpBeforeDateTime, sizeof(FILETIME)) )
    {
        EventLogMsg(
        GWCLEAN_OUTOFMEMORY,
        0,
        0);

        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    m_lpBeforeDateTime->dwHighDateTime = lpBeforeTime->dwHighDateTime;
    m_lpBeforeDateTime->dwLowDateTime = lpBeforeTime->dwLowDateTime;

    // Open session to gateway.
    hr = MAPILogonEx(0,   // UI flags,
                m_lpProfileName,    // profile name
                NULL,               // password
                ulFlags,            // MAPI flags
                &m_lpSessGW);

    if ( FAILED(hr) )
    {
        EventLogMsg(
                GWCLEAN_PROFILEERR,
2, m_lpProfileName, _itot( hr, szErrorCode, 16),
                0);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the new PST and temporary profile.
    hr = HrCreatePersonalStore(
                        m_lpPSTName,            // fully-pathed PST name
                        PSTF_NO_ENCRYPTION,     // type of encryption
                        (LPTSTR) lpszDisplayName,        // PST PR_DISPLAY_NAME value
                        TEXT(""),               // PST password
                        &m_lpTempProfName,      // temporary profile name
                        &m_lpTempProfilePW);    // temporary profile password

    if ( FAILED(hr) )
    {
        EventLogMsg(
        GWCLEAN_NEWPSTERR,
2, m_lpPSTName, _itot( hr, szErrorCode, 16),
            0);

        goto cleanup;
    }

    // Have created a new PST.
    m_fNewPST = TRUE;

    // Log onto the PST's temporary profile
    hr = MAPILogonEx(0,       // UI flags
                m_lpTempProfName,   // temporary profile name
                m_lpTempProfilePW,  // temporary profile password
                ulFlags,            // MAPI flags,
                &m_lpSessNew);

    if ( FAILED(hr) )
    {
        EventLogMsg(
                GWCLEAN_PROFILEERR,
2, m_lpTempProfName, _itot( hr, szErrorCode, 16),
                0);

        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Open gateway's default message store.
    // Find the default MDB message store in the gateway profile.
    hr = HrMAPIFindDefaultMsgStore(m_lpSessGW,    // session pointer
                                 &cbEid,        // count of bytes in entry ID
                                 &lpEid);       // entry id pointer for default store

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrMAPIFindDefaultMsgStore()"), hr);

        goto cleanup;
    }

    // Open the gateway's default message store.
    hr = m_lpSessGW->OpenMsgStore(0,        // window handle
                                  cbEid,    // # of bytes in entry ID
                                  lpEid,    // pointer to entry ID
                                  NULL,     // interface ID pointer
                                  ulMDBflags, // flags
                                  &m_lpMDBGateway);   // output ptr to store

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPISession::OpenMsgStore()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Set the PR_TRANSFER_ENABLED property so that we don't
    // get any activity on the gateway.
    hr = HrMAPISetPropBoolean(
            m_lpMDBGateway,         // MAPI object pointer
            PR_TRANSFER_ENABLED,    // property tag
            FALSE);                 // boolean value

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrMAPISetPropBoolean()"), hr);

        goto cleanup;
    }

    // Free gateway message store entry ID buffer
    MAPIFREEBUFFER(lpEid);

    // Open PST temporary profile's default message store.
    // Find the default MDB message store in the gateway profile
    // first
    hr = HrMAPIFindDefaultMsgStore(m_lpSessNew,   // session pointer
                                 &cbEid,        // count of bytes in entry ID
                                 &lpEid);       // entry id pointer for default store

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrMAPIFindDefaultMsgStore()"), hr);

        goto cleanup;
    }

    // Open the gateway's default message store.
    hr = m_lpSessNew->OpenMsgStore(0,       // window handle
                                  cbEid,    // # of bytes in entry ID
                                  lpEid,    // pointer to entry ID
                                  NULL,     // interface ID pointer
                                  ulMDBflags, // flags
                                  &m_lpMDBNew);   // output ptr to store

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPISession::OpenMsgStore()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open desired folder in gateway.
    hr = HrMAPIOpenFolderEx(
            m_lpMDBGateway,     // message store pointer
            cSeparator,         // folder path separator
            m_lpFolderName,     // folder path
            &m_lpFolderFrom);

    if ( FAILED(hr) )
    {
        EventLogMsg(
        GWCLEAN_BADFOLDER,
3,
            m_lpFolderName,
            m_lpProfileName,
            _itot( hr, szErrorCode, 16),
            0);

        goto cleanup;
    }

    // Create GWCLEAN folder in PST.
    hr = HrCreatePSTFolder();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // We are now ready to move messages from the open
    // gateway folder to the open PST folder.
    // We are done with our initialization.

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpEid);

    RETURN(hr);

}   // end CGWClean::HrInitialize()

//$--CGWClean::HrCreatePSTFolder-----------------------------------------------
//
// DESCRIPTION: Create a new folder in the new PST and
//              open it.
//
// RETURNS:     HRESULT   --  NOERROR if successful
//                            E_FAIL otherwise.
//
// ---------------------------------------------------------------------------

HRESULT CGWClean::HrCreatePSTFolder()         // RETURNS: HRESULT
{
    HRESULT hr          =   NOERROR;    // MAPI return code
    ULONG   ulObjType   =   0;          // object type

#ifdef USE_MAPIWRAP
    // Special MAPI object base classes which release
    // object when they goe out of scope.
    CMAPIInterface<LPMAPIFOLDER>    lpRootFolder;   // PST root folder
    CMAPIInterface<LPMAPIFOLDER>    lpTopOfStore;   // top of personal folders in PST
#else
    LPMAPIFOLDER    lpRootFolder    =   NULL;   // PST root folder pointer
    LPMAPIFOLDER    lpTopOfStore    =   NULL;   // top of PST personal folders
#endif

    // top of store folder name for PST.
    const LPTSTR   lpszTopOfStoreName  =   TEXT("Top of Personal Folders");

    // Folder to which gateway messages are copied
    const LPTSTR   lpszGWCLEAN =   TEXT("GWCLEAN");

    DEBUGPRIVATE("CGWClean::HrCreatePSTFolder()\n");

    // consistency checks
    ASSERT_IUNKNOWN_PTR( m_lpMDBNew, "Bad m_lpMDBNew");

    // Find the new PST's root folder.
    hr = m_lpMDBNew->OpenEntry(0,     // # of bytes in entry ID
                                  NULL,  // ptr to entry ID
                                  NULL,  // interface ID pointer
                                  MAPI_MODIFY | MAPI_DEFERRED_ERRORS, // flags
                                  &ulObjType,       // object type pointer
                (LPUNKNOWN *) &lpRootFolder);       // output ptr to folder

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMsgStore::OpenEntry()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( ulObjType == MAPI_FOLDER, "Bad ulObjType");

    // Create the new PST's Top of Personal Folders folder.
    hr = lpRootFolder->CreateFolder(
                FOLDER_GENERIC,    // folder type
                (LPTSTR) lpszTopOfStoreName,  // folder name
                NULL,        // folder comment
                NULL,        // interface ID pointer
                OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,  // flags
                &lpTopOfStore); // new folder pointer

    if ( FAILED(hr) )
    {
        // Check for insufficient disk space.
        if ( hr == MAPI_E_NOT_ENOUGH_DISK )
        {
            EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

hr = HR_LOG(E_FAIL);

goto cleanup;
        }

        else
        {
            INTERNAL_ERROR( TEXT("IMAPIFolder::CreateFolder()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
        }
    }// end if creation of folder failed

    // Create the new PST's GWCLEAN folder (under the Top of Personal Folders folder).
    // All gateway messages will be moved into this folder
    hr = lpTopOfStore->CreateFolder(
                FOLDER_GENERIC,  // folder type
                (LPTSTR) lpszGWCLEAN, // folder name
                NULL,        // folder comment
                NULL,        // interface ID pointer
                OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,  // flags
                &m_lpFolderTo); // new folder pointer

    if ( FAILED(hr) )
    {
        // Check for insufficient disk space.
        if ( hr == MAPI_E_NOT_ENOUGH_DISK )
        {
            EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

hr = HR_LOG(E_FAIL);

goto cleanup;
        }

        else
        {
            INTERNAL_ERROR( TEXT("IMAPIFolder::CreateFolder()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
        }
    }// end if creation of folder fails

cleanup:

    ULRELEASE(lpTopOfStore);
    ULRELEASE(lpRootFolder);

    // Base classes take care of releasing MAPI objects
    // and freeing MAPI memory.

    RETURN(hr);

}

//$--CGWClean::HrMoveMsgs------------------------------------------------------
//
// DESCRIPTION: Move messages from gateways MTS-OUT folder to PSTs MTS-OUT folder.
//
// RESULT:      HRESULT   --  NOERROR if successful,
//                            EDK_E_NOT_FOUND if no messages to move,
//                            E_FAIL otherwise.
//
// ---------------------------------------------------------------------------

HRESULT CGWClean::HrMoveMsgs()         // RETURNS: HRESULT
{
    HRESULT hr  =   NOERROR;        // MAPI return code
    LPMAPITABLE lpContentsTable =   NULL;   // contents table for gateway MTS-OUT folder
    LPENTRYLIST lpEidList   =   NULL;       // list of entry identifiers to move
    LPENTRYLIST lpSearchKeyList =   NULL;   // list of search key identifiers for messages copied
    SizedSPropTagArray(2, SPropEntryIDs);   // property tag array containing only PR_ENTRY_ID and PR_SEARCH_KEY
    SRestriction    sRestriction    =   {0};// Property restriction structure
    SPropValue  sPropValue  =   {0};        // Property value structure
    LPSRowSet   lpMsgRows   =   NULL;       // Row pointer corresponding to a message
    ULONG       ulMsgCount  =   0;          // count of messages moved
    SYSTEMTIME  sSysTime    =   {0};        // local system time structure
    FILETIME    sLocalFileTime  =   {0};    // local file time structure
    TCHAR   szDateTimeString[80]    =   {0};// before date time string
    ULONG   ulCount =   0;                  // loop counter
    ULONG   ulNumBytes  =   0;              // size of entry identifier
    LPBYTE  lpEid   =   NULL;               // entry ID pointer for message
    TCHAR   szErrorCode[16] = {0};          // buffer for error code strings

    DEBUGPRIVATE("CGWCLEAN::HrMoveMsgs()\n");

    // Consistency checking
    ASSERT_IUNKNOWN_PTR( m_lpFolderFrom, "Bad m_lpFolderFrom");
    ASSERT_IUNKNOWN_PTR( m_lpFolderTo, "Bad m_lpFolderTo");

    // Get the contents table for the gateway's MTS-OUT folder.
    hr = m_lpFolderFrom->GetContentsTable(
                MAPI_DEFERRED_ERRORS,  // flags
                &lpContentsTable);  // Contents table pointer

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPIFolder::GetContentsTable()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR((lpContentsTable != NULL), "Bad lpContentsTable");

    // Get a list of all desired message IDs from the contents table.
    // First, call SetColumns to chose the PR_ENTRY_ID column and
    // PR_SEARCH_KEY columns.  (PR_SEARCH_KEY is the transmittable,
    // unique identifier of the message.)
    SPropEntryIDs.cValues = 2;
    SPropEntryIDs.aulPropTag[0] = PR_ENTRYID;
    SPropEntryIDs.aulPropTag[1] = PR_SEARCH_KEY;

    hr = lpContentsTable->SetColumns((LPSPropTagArray) (&SPropEntryIDs),    // Properties desired
                                     0);            // flags

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPITable::SetColumns()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Make a call here to Restrict to restrict the rows in the table returned
    // to be those where the PR_CREATION_TIME property is less than
    // the desired "before" date.

    ASSERTERROR((m_lpBeforeDateTime != NULL), "Bad m_lpBeforeDateTime");

    sPropValue.ulPropTag    =   PR_CREATION_TIME;
    sPropValue.Value.ft.dwLowDateTime = m_lpBeforeDateTime->dwLowDateTime;
    sPropValue.Value.ft.dwHighDateTime = m_lpBeforeDateTime->dwHighDateTime;

    sRestriction.rt                         = RES_PROPERTY;
    sRestriction.res.resProperty.relop      = RELOP_LT;
    sRestriction.res.resProperty.ulPropTag  = PR_CREATION_TIME;
    sRestriction.res.resProperty.lpProp     = &sPropValue;

    hr = lpContentsTable->Restrict(&sRestriction, 0);

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPITable::Restrict()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Let user know that we are starting the copy.
    // (Convert the FILETIME value to a string for easy
    // display to the user.)
    FileTimeToLocalFileTime(m_lpBeforeDateTime, &sLocalFileTime); // system file time to local file time
    FileTimeToSystemTime(&sLocalFileTime, &sSysTime);  // local file time to local system time
    wsprintf(szDateTimeString, TEXT("%d:%d:%d.%d %d/%d/%d"), sSysTime.wHour,
            sSysTime.wMinute, sSysTime.wSecond, sSysTime.wMilliseconds,
            sSysTime.wMonth, sSysTime.wDay, sSysTime.wYear);

    ASSERT_STRING_PTR( szDateTimeString, "Bad szDateTimeString");

    DisplayUserMsg(
    IDS_STARTMOVE,
        szDateTimeString,
        m_lpProfileName,
        m_lpPSTName);

    // Build a list of entry identifiers for the messages which match
    // our criteria.
    while ( TRUE )
    {
        // Free the MAPI structures (SRowSet is special!)
        FREEPROWS(lpMsgRows);

        // Free EDK structures.
        if ( lpEidList )
        {
            HrMAPIDestroyEntryList(&lpEidList);
            lpEidList = NULL;
        }

        if ( lpSearchKeyList )
        {
            HrMAPIDestroyEntryList(&lpSearchKeyList);
            lpSearchKeyList = NULL;
        }

        // Retrieve the row (messages) in the table which meet our
        // selection criteria in EDK_MAX_QUERY_ROWS chunks (1024 at a time).
        hr = lpContentsTable->QueryRows(EDK_MAX_QUERY_ROWS, // count to return
                                        0,                  // flags
                                        &lpMsgRows);        // returned row structure pointer

        switch (hr)
        {
            case MAPI_W_POSITION_CHANGED:
                // Location in table changed by somebody else.
                // Warn the user.
                EventLogMsg(
                GWCLEAN_POSCHANGED,
                0,
                0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;

            default:
                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPITable::QueryRows()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;

        }   // end switch

        ASSERTERROR((lpMsgRows != NULL), "Bad lpMsgRows");

        // Check to see if any messages to process.
        if ( !lpMsgRows->cRows )
        {
            // If no messages found at all, let user know.
            if ( !ulMsgCount )
            {
                EventLogMsg(
                GWCLEAN_NOMSGS,
1,
                    szDateTimeString,
                    0);

                hr = HR_LOG(EDK_E_NOT_FOUND);

                // Set no messages flag
                m_fNoMessages = TRUE;

                goto cleanup;
            }

            // Otherwise, we are done!
            break;
        }

        // Add message identifiers for messages returned to our list of
        // entry IDs.
        ulMsgCount += lpMsgRows->cRows;  // increment message count

        for ( ulCount = 0; ulCount < lpMsgRows->cRows; ulCount++ )
        {
            ASSERTERROR(lpMsgRows->aRow[ulCount].cValues == 2,
                "Bad lpMsgRows->aRow[].cValues");
            ASSERTERROR(lpMsgRows->aRow[ulCount].lpProps[0].ulPropTag == PR_ENTRYID,
                "Bad lpMsgRows->aRows[].lpProps[0].ulPropTag");
            ASSERTERROR(lpMsgRows->aRow[ulCount].lpProps[1].ulPropTag == PR_SEARCH_KEY,
                "Bad lpMsgRows->aRows[].lpProps[1].ulPropTag");

            ulNumBytes = lpMsgRows->aRow[ulCount].lpProps[0].Value.bin.cb;
            lpEid = lpMsgRows->aRow[ulCount].lpProps[0].Value.bin.lpb;

            // Add entry identifier to the entry ID list.
            if ( !lpEidList )
            {
                // Create new entry identifier list.
                hr = HrMAPICreateEntryList(ulNumBytes, (LPENTRYID) lpEid, &lpEidList);

            if ( FAILED(hr) )
            {
                    INTERNAL_ERROR( TEXT("HrMAPICreateEntryList()"), hr);

                goto cleanup;
            }
            }
            else
            {
                hr = HrMAPIAppendEntryList(ulNumBytes, (LPENTRYID) lpEid, lpEidList);

            if ( FAILED(hr) )
            {
                    INTERNAL_ERROR( TEXT("HrMAPIAppendEntryList()"), hr);

                goto cleanup;
            }
            }

            ASSERTERROR((lpEidList != NULL), "Bad lpEidList");

            ulNumBytes = lpMsgRows->aRow[ulCount].lpProps[1].Value.bin.cb;
            lpEid = lpMsgRows->aRow[ulCount].lpProps[1].Value.bin.lpb;

            // Add seach key identifier for search key list.
            if ( !lpSearchKeyList )
            {
                // Create new search key list.
                hr = HrMAPICreateEntryList(ulNumBytes, (LPENTRYID) lpEid, &lpSearchKeyList);

            if ( FAILED(hr) )
            {
                    INTERNAL_ERROR( TEXT("HrMAPICreateEntryList()"), hr);

                goto cleanup;
            }
            }
            else
            {
                hr = HrMAPIAppendEntryList(ulNumBytes, (LPENTRYID) lpEid, lpSearchKeyList);

            if ( FAILED(hr) )
            {
                    INTERNAL_ERROR( TEXT("HrMAPIAppendEntryList()"), hr);

                goto cleanup;
            }
            }

            ASSERTERROR((lpSearchKeyList != NULL), "Bad lpSearchKeyList");

        }   // end for

        ASSERTERROR((lpEidList != NULL), "Bad lpEidList");  // just in case!
        ASSERTERROR((lpSearchKeyList != NULL), "Bad lpSearchKeyList");

        // Copy messages from gateway MTS-OUT folder to new PST's MTS-OUT
        // folder.
        hr = m_lpFolderFrom->CopyMessages(lpEidList,  // list of message IDs
                                        &IID_IMAPIFolder,   // interface ID of destination folder
                                        m_lpFolderTo,  // destination folder ptr
                                        0,  // window handle
                                        0,  // progress dialogue handle
                                        MAPI_DECLINE_OK );   // flags

        switch (hr)
        {
            case MAPI_E_DECLINE_COPY:
                // Provider doesn't support CopyMessages function.
                // Warn the user.
                EventLogMsg(
                GWCLEAN_NOCOPYMSGS,
                0,
                0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;

            case MAPI_E_NOT_ENOUGH_DISK:
                // insufficient disk space.
                EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;

            case MAPI_W_PARTIAL_COMPLETION:
                // Couldn't copy all messages.
                // Warn the user.
                EventLogMsg(
                GWCLEAN_PARTIALCOPY,
                1, _itot( hr, szErrorCode, 16),
                0);

                hr = HR_LOG(E_FAIL);
                goto cleanup;

            default:
                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPIFolder::CopyMessages()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;

        }   // end switch

        // Copy the extra gateway message properties not copied via CopyMessages
        // from the old messages to the new messages.
        hr = HrCopyGWMessageProps(lpEidList, lpSearchKeyList);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Now, delete messages moved from gateway MTS-OUT folder to the
        // new PST MTS-OUT folder.
        // Delete copied messages from gateway's MTS-OUT folder.
        hr = m_lpFolderFrom->DeleteMessages(lpEidList,  // entry identifier list
                                              0,          // window handle
                                              0,          // progress dialog
                                              0);         // flags

        switch (hr)
        {
            case MAPI_W_PARTIAL_COMPLETION:

                EventLogMsg(
                GWCLEAN_PARTIALDEL,
                1, _itot( hr, szErrorCode, 16),
                0);

                hr = HR_LOG(E_FAIL);
                goto cleanup;

            case MAPI_E_HAS_FOLDERS:
            case MAPI_E_SUBMITTED:

                EventLogMsg(
                GWCLEAN_PARTIALDEL,
                1, _itot( hr, szErrorCode, 16),
                0);

                hr = HR_LOG(E_FAIL);
                goto cleanup;

            default:
                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPIFolder::DeleteMessages()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;

        }   // end switch
    }   // end while messages to copy

    // We have done it!
    // Display a successful completion message and log event.
    EventLogMsg(
    GWCLEAN_SUCCESS,
1, _itot( ulMsgCount, szErrorCode, 10),
0);

DisplayUserMsg(
IDS_SUCCESS,
ulMsgCount);

cleanup:

    // Free EDK structures.
    if ( lpEidList )
    {
        HrMAPIDestroyEntryList(&lpEidList);
    }

    if ( lpSearchKeyList )
    {
        HrMAPIDestroyEntryList(&lpSearchKeyList);
    }

    // Free MAPI structures (SRowSet is special!)
    FREEPROWS(lpMsgRows);

    // Release MAPI objects.
    ULRELEASE(lpContentsTable);

    RETURN(hr);

}   // end CGWClean::MoveMsgs()

//$--CGWClean::HrCopyGWMessageProps--------------------------------------------
//
// DESCRIPTION: Copy the gateway message (envelope) properties not copied by CopyMessages
//              from the gateway messages to the new PST messages.
//              The additional properties copied are determined
//              in HrCreateNewProps().
//
// INPUT:       LPENTRYLIST --  list of entry identifiers of messages copied.
//              LPENTRYLIST --  list of search key identifiers of messages copied.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------

HRESULT CGWClean::HrCopyGWMessageProps(                   // RETURNS: HRESULT
                        IN LPENTRYLIST lpEidList,       // entry identifiers
                        IN LPENTRYLIST lpSearchKeyList) // search keys
{
    HRESULT hr  =   NOERROR;        // return code
    LPMESSAGE   lpMessage   =   NULL;       // original message pointer (envelope)
    LPMESSAGE   lpNewMessage    =   NULL;   // copied message pointer   (envelope)
    ULONG       ulObjType   =   0;          // MAPI object type
    LPMAPITABLE lpContentsTable =   NULL;   // contents table pointer for new MTS-OUT folder
    SRestriction    sRestriction    =   {0};// Property restriction structure
    SPropValue  sPropValue  =   {0};        // Property value structure
    LPSRowSet   lpMsgRows   =   NULL;       // Row pointer corresponding to a message
    SizedSPropTagArray(2, SPropEntryIDs);   // property tag array containing only PR_ENTRY_ID and PR_SEARCH_KEY
    ULONG   ulLoopCounter   =   0;              // loop counter
    ULONG   ulDupEidSize    =   0;              // duplicate message entry ID size
    LPBYTE  lpDupEid    =   NULL;               // duplicate message's search key
    ULONG   ulTempIndex =   0;                  // temporary index
    BOOL    bDupDone    =   FALSE;              // Duplicate done flag
    LPENTRYLIST lpDoneList  =   NULL;       // duplicates done list

    DEBUGPRIVATE("CGWClean::HrCopyGWMessageProps()\n");

    hr = CHK_CGWClean_HrCopyGWMessageProps(lpEidList, lpSearchKeyList);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // consistency checking
    ASSERT_IUNKNOWN_PTR( m_lpFolderTo, "Bad m_lpFolderTo");
    ASSERT_IUNKNOWN_PTR( m_lpFolderFrom, "Bad m_lpFolderFrom");

    // Get the contents table for the new MTS-OUT folder.
    hr = m_lpFolderTo->GetContentsTable(0,  // flags
                                             &lpContentsTable);  // Contents table pointer

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPFolder::GetContentsTable()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Call SetColumns to chose the PR_ENTRY_ID column and
    // PR_SEARCH_KEY columns for the new table.  (PR_SEARCH_KEY is the transmittable,
    // unique identifier of the message.)
    SPropEntryIDs.cValues = 2;
    SPropEntryIDs.aulPropTag[0] = PR_ENTRYID;
    SPropEntryIDs.aulPropTag[1] = PR_SEARCH_KEY;

    hr = lpContentsTable->SetColumns((LPSPropTagArray) (&SPropEntryIDs),    // Properties desired
                                     0);            // flags

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPITable::SetColumns()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Handle each message in the list
    for ( ulLoopCounter = 0; ulLoopCounter < lpEidList->cValues; ulLoopCounter++ )
    {
        // Release the MAPI objects.
        ULRELEASE(lpMessage);

        // Free the MAPI structures (SRowSet is special!)
        FREEPROWS(lpMsgRows);

        // Print out a . for every message to be copied.
        _puttchar(TEXT('.'));

        // Open original message envelope.
        hr = m_lpFolderFrom->OpenEntry(
                            lpEidList->lpbin[ulLoopCounter].cb,  // # of bytes in entry ID
                            (LPENTRYID) (lpEidList->lpbin[ulLoopCounter].lpb),// entry ID pointer
                            &IID_IMessage,  // interface ID pointer
                            0,              // flags
                            &ulObjType,     // object type
                            (LPUNKNOWN FAR *) &lpMessage);    // output pointer to message

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("IMAPIFolder::OpenEntry()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR( lpMessage, "Bad lpMessage");
        ASSERTERROR(ulObjType == MAPI_MESSAGE, "Bad ulObjType");

        // Go about finding the corresponding new message

        // Make a call to find the row in the new folder's table
        // where the PR_SEARCH_KEY property is equal to the
        // PR_SEARCH_KEY property of the original message.
        sPropValue.ulPropTag    =   PR_SEARCH_KEY;
        sPropValue.Value.bin.cb =   lpSearchKeyList->lpbin[ulLoopCounter].cb;
        sPropValue.Value.bin.lpb =  lpSearchKeyList->lpbin[ulLoopCounter].lpb;

        sRestriction.rt                         = RES_PROPERTY;
        sRestriction.res.resProperty.relop      = RELOP_EQ;
        sRestriction.res.resProperty.ulPropTag  = PR_SEARCH_KEY;
        sRestriction.res.resProperty.lpProp     = &sPropValue;

        hr = lpContentsTable->Restrict(&sRestriction, 0);

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("IMAPITable::Restrict()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Retrieve the row(s) from the new table which meet the
        // selection criteria.
        hr = lpContentsTable->QueryRows(lpEidList->cValues, // maximum count to return
                                        0,                  // flags
                                        &lpMsgRows);        // returned row structure pointer

        switch (hr)
        {
            case MAPI_W_POSITION_CHANGED:
                // Location in table changed by somebody else.
                // Warn the user.
                EventLogMsg(
                GWCLEAN_POSCHANGED,
                0,
                0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;

            default:
                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPITable::QueryRows()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;

        }   // end switch

        ASSERT_READ_PTR( lpMsgRows, sizeof(SRowSet), "Bad lpMsgRows");

        ASSERTERROR( lpMsgRows->cRows,
            "Bad lpMsgRows->cRows"); // should be at least one match
        ASSERTERROR(lpMsgRows->aRow[0].cValues == 2,
            "Bad lpMsgRows->aRow[0].cValues");
        ASSERTERROR(lpMsgRows->aRow[0].lpProps[0].ulPropTag == PR_ENTRYID,
            "Bad lpMsgRows->aRow[0].lpProps[0].ulPropTag");
        ASSERTERROR(lpMsgRows->aRow[0].lpProps[1].ulPropTag == PR_SEARCH_KEY,
            "Bad lpMsgRows->aRow[0].lpProps[1].ulPropTag");

        // If this message has duplicates, see if we have already
        // handled them before.  If so, continue on to next message
        // in list.
        if ( lpMsgRows->cRows > 1 )
        {
            ulDupEidSize = lpMsgRows->aRow[0].lpProps[1].Value.bin.cb;
            lpDupEid = lpMsgRows->aRow[0].lpProps[1].Value.bin.lpb;

            // if this search key is in the done list, go to the
            // next message.  Otherwise, add it to the done list and
            // handle its duplicates.
            if ( !lpDoneList )
            {
                // Create done list and
                // record this search key in the done list.
                hr = HrMAPICreateEntryList(ulDupEidSize,
                                          (LPENTRYID) lpDupEid,
                                          &lpDoneList);

                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("HrMAPICreateEntryList()"), hr);

                    goto cleanup;
                }
            }

            else
            {
                // look for search key in done list.
                for ( ulTempIndex = 0, bDupDone = FALSE;
                      ulTempIndex < lpDoneList->cValues; ulTempIndex++ )
                {
                    if ( memcmp(lpDupEid, lpDoneList->lpbin[ulTempIndex].lpb, ulDupEidSize) == 0 )
                    {
                        // This duplicate has already been done.
                        bDupDone = TRUE;
                        break;
                    }

                }   // end for

                // If message's duplicates already handled, continue
                if ( bDupDone )
                {
                    continue;   // go do next message
                }

                // Otherwise, add this message's search key to the done list.
                hr = HrMAPIAppendEntryList(ulDupEidSize,
                                          (LPENTRYID) lpDupEid,
                                          lpDoneList);

                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("HrMAPIAppendEntryList()"), hr);

                    goto cleanup;
                }

            }   // end if
        }   // end if message has duplicates

        // Handle extra properties for message's duplicates
        ULONG ulDupCount;   // duplicate count
        for (ulDupCount = 0; ulDupCount < lpMsgRows->cRows; ulDupCount++ )
        {
            // Open corresponding new message envelope.
            hr = m_lpFolderTo->OpenEntry(
                        lpMsgRows->aRow[ulDupCount].lpProps[0].Value.bin.cb,    // # of bytes in entry ID
                        (LPENTRYID) lpMsgRows->aRow[ulDupCount].lpProps[0].Value.bin.lpb,   // entry ID pointer
                        &IID_IMessage,  // interface ID pointer
                        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,    // flags
                        &ulObjType,     // object type
                        (LPUNKNOWN FAR *) &lpNewMessage);    // output pointer to message

            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("IMAPIFolder::OpenEntry()"), hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            ASSERT_IUNKNOWN_PTR( lpNewMessage, "Bad lpNewMessage");
            ASSERTERROR(ulObjType == MAPI_MESSAGE, "Bad ulObjType");

            // If not all properties were copied to the new message
            // by IMAPIFolder::CopyMessages(),
            // create those properties in the new message now.
            hr = HrCreateNewProps(lpMessage, lpNewMessage);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Copy over the properties from the original message (envelope
            // attachment) to the new message (envelope attachment).
            hr = HrCopyGWAttachProps(lpMessage, lpNewMessage);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Save changes to new message.
            hr = lpNewMessage->SaveChanges(KEEP_OPEN_READWRITE);

            if ( FAILED(hr) )
            {
                // check for insufficient disk space
                if ( hr == MAPI_E_NOT_ENOUGH_DISK )
                {
                    EventLogMsg(
                    GWCLEAN_OUTOFDISK,
                    0,
                    0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;
                }

else
{
                    INTERNAL_ERROR( TEXT("IMAPIProp::SaveChanges()"), hr);

hr = HR_LOG(E_FAIL);
goto cleanup;
}
            }// end if save changes failure

            // Release new message MAPI object.
            ULRELEASE(lpNewMessage);

        }   // end for all duplicates
    }   // end for

    // We are done copying the extra gateway message properties.

cleanup:

    // Release MAPI objects.
    ULRELEASE(lpContentsTable);
    ULRELEASE(lpMessage);
    ULRELEASE(lpNewMessage);

    // SRowSet is special!
    FREEPROWS(lpMsgRows);

    // Free done list
    if ( lpDoneList )
    {
        HrMAPIDestroyEntryList(&lpDoneList);
    }

    RETURN(hr);

}   // end CGWClean::CopyGWMessageProps()

//$--CGWClean::HrCopyGWMessageProps----------------------------------------------------
//
// DESCRIPTION: Recursively copy the gateway message & attachment (message)
//              properties not copied by CopyMessages
//              from the gateway messages to the new PST messages.
//              The additional properties copied are
//              determined in HrCreateNewProps().
//
// INPUT:       LPMESSAGE --  Original message pointer
//              LPMESSAGE --  New message pointer
//
// RESULT:      HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------

HRESULT CGWClean::HrCopyGWAttachProps(              // RETURNS: HRESULT
                        IN LPMESSAGE lpParent,      // original message pointer
                        IN LPMESSAGE lpNewParent)   // new message pointer
{
    HRESULT hr  =   NOERROR;    // return code
    LPMAPITABLE lpAttachTable   =   NULL;   // Original message's attachment table
    LPMAPITABLE lpNewAttachTable    =   NULL;   // new message's attachment table
    LPMESSAGE   lpMessage   =   NULL;       // Original message (envelope attachment)
    LPMESSAGE   lpNewMessage    =   NULL;   // New message (envelope attachment)
    ULONG   ulCount =   0;                  // count
    LPSRowSet   lpRows  =   NULL;           // old message QueryRows return
    LPSRowSet   lpNewRows   =   NULL;       // new message QueryRows return
    ULONG   ulAttachNum =   0;              // old message attachment index
    ULONG   ulNewAttachNum  =   0;          // new message attachment index
    LPATTACH    lpAttachment    =   NULL;   // Original attachment
    LPATTACH    lpNewAttachment =   NULL;   // new attachment
    ULONG       ulLoopCounter   =   0;      // loop counter
    LPBYTE  lpSearchKey =   NULL;           // old search key
    LPBYTE  lpSearchKeyNew  =   NULL;       // new search key
    ULONG   ulCountNew  =   0;
    BOOL    bFound  =   FALSE;              // True if find corresponding attachment
    ULONG   ulLoopCount2    =   0;          // inner loop counter

    SizedSPropTagArray(1, sPropAttachNum)   =   // PR_ATTACH_NUM property tag array
    {
        1,  // number of properties
        {
            PR_ATTACH_NUM   // property tag
        }
    };

    DEBUGPRIVATE("CGWClean::HrCopyGWAttachProps()\n");

    hr = CHK_CGWClean_HrCopyGWAttachProps(lpParent, lpNewParent);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Open the message's attachments table.
    hr = lpParent->GetAttachmentTable(0, &lpAttachTable);

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMessage::GetAttachmentTable()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = lpNewParent->GetAttachmentTable(0, &lpNewAttachTable);

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMessage::GetAttachmentTable()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR( lpAttachTable, "Bad lpAttachTable");
    ASSERT_IUNKNOWN_PTR( lpNewAttachTable, "Bad lpNewAttachTable");

    // Retrieve all the attachments for this message.  This function is now
    // called recursively to handle all message attachments.
    hr = HrQueryAllRows(
            lpAttachTable,  // original attachment table
            (LPSPropTagArray) &sPropAttachNum,  // only want PR_ATTACH_NUM columns
            NULL,           // restriction pointer
            NULL,           // sort order set pointer
            0,              // maximum rows--defaults to all
            &lpRows);

    switch (hr)
    {
        case MAPI_W_POSITION_CHANGED:
            // Location in table changed by somebody else.
            // Warn the user.
            EventLogMsg(
            GWCLEAN_POSCHANGED,
            0,
            0);

            hr = HR_LOG(E_FAIL);

            goto cleanup;

        default:
            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("HrQueryAllRows()"), hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            break;

    }   // end switch()

    ASSERT_READ_PTR( lpRows, sizeof(SRowSet), "Bad lpRows");

    // If there are no attachments for this message,
    // we are done.  Return!
    if ( !(lpRows->cRows) )
    {
        goto cleanup;
    }

    // Get all the attachments for the new envelope.
    hr = HrQueryAllRows(
            lpNewAttachTable,   // new message attachment table
            (LPSPropTagArray) &sPropAttachNum,  // only want PR_ATTACH_NUM columns
            NULL,               // restriction pointer
            NULL,               // sort order set pointer
            0,                  // maximum rows--defaults to all
            &lpNewRows);

    switch (hr)
    {
        case MAPI_W_POSITION_CHANGED:
            // Location in table changed by somebody else.
            // Warn the user.
            EventLogMsg(
                GWCLEAN_POSCHANGED,
            0,
            0);

            hr = HR_LOG(E_FAIL);

            goto cleanup;

        default:
            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("HrQueryAllRows()"), hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

        break;

    }   // end switch()

    ASSERT_READ_PTR( lpNewRows, sizeof(SRowSet), "Bad lpNewRows");
    ASSERTERROR(lpNewRows->cRows == lpRows->cRows, "Bad lpNewRows count");

    // Handle each attachment
    for ( ulLoopCounter = 0; ulLoopCounter < lpRows->cRows; ulLoopCounter++ )
    {
        // Release MAPI objects
        ULRELEASE(lpMessage);
        ULRELEASE(lpAttachment);

        // Get entry identifier information for attachment.
        ulAttachNum = lpRows->aRow[ulLoopCounter].lpProps[0].Value.ul;

        // Open the attachment specified by the entry identifier.
        hr = lpParent->OpenAttach(
                                    ulAttachNum,
                                    0,
                                    MAPI_DEFERRED_ERRORS,   // reduces RPCs
                                    &lpAttachment);

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("IMessage::OpenAttach()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR( lpAttachment, "Bad lpAttachment");

        // Try to open the attachment as a message.
        hr = lpAttachment->OpenProperty(PR_ATTACH_DATA_OBJ,
                                    &IID_IMessage,  // interface identifier
                                    0,
                                    MAPI_DEFERRED_ERRORS,  // reduces RPCs
                                    (LPUNKNOWN FAR *) &lpMessage);

        switch (hr)
        {
            case MAPI_E_INTERFACE_NOT_SUPPORTED:
            case MAPI_E_NOT_FOUND:
                // The attachment is not a message.
                // Try the next attachment.
                hr = HR_LOG(NOERROR);   // This isn't a failure!

                continue;   // go to next attachment

            default:

                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPIProp::OpenProperty()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;
        }   // end switch

        ASSERT_IUNKNOWN_PTR( lpMessage, "Bad lpMessage");

        // Find the new attachment corresponding to the old attachment.
        for ( ulLoopCount2 = 0; ulLoopCount2 < lpRows->cRows; ulLoopCount2++ )
        {
            // Free MAPI structures
            MAPIFREEBUFFER(lpSearchKey);
            MAPIFREEBUFFER(lpSearchKeyNew);

            // Release MAPI objects
            ULRELEASE(lpNewAttachment);
            ULRELEASE(lpNewMessage);

            // Get entry identifier information for new message attachment.
            ulNewAttachNum = lpNewRows->aRow[ulLoopCount2].lpProps[0].Value.ul;

            // Open the attachment specified by the new entry identifier.
            hr = lpNewParent->OpenAttach(
                                    ulNewAttachNum,
                                    0,
                                    MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                                    &lpNewAttachment);

            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("IMessage::OpenAttach()"),  hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            ASSERT_IUNKNOWN_PTR( lpNewAttachment, "Bad lpNewAttachment");

            // Open the new attachment as a message
            hr = lpNewAttachment->OpenProperty(PR_ATTACH_DATA_OBJ,
                                       &IID_IMessage,
                                       0,
                                       MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                                       (LPUNKNOWN FAR *) &lpNewMessage);

            switch (hr)
            {
                case MAPI_E_INTERFACE_NOT_SUPPORTED:
                case MAPI_E_NOT_FOUND:
                    // Not this attachment! (not an error)
                    hr = HR_LOG(NOERROR);

                    // Try the next attachment
                    continue;

                default:

                    if ( FAILED(hr) )
                    {
                        INTERNAL_ERROR( TEXT("IMAPIProp::OpenProperty()"), hr);

                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }

                    break;

            }   // end switch

            ASSERT_IUNKNOWN_PTR( lpNewMessage, "Bad lpNewMessage");

            // Compare search key of new message to search key of
            // old message.  If not the same, try next new attachment
            // in table.
            hr = HrMAPIGetPropBinary(
                            (LPMAPIPROP) lpNewMessage,
                            PR_SEARCH_KEY,
                            &ulCountNew,
                            (LPVOID FAR *) &lpSearchKeyNew);

            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("HrMAPIGetPropBinary()"), hr);

                goto cleanup;
            }

            hr = HrMAPIGetPropBinary(
                            (LPMAPIPROP) lpMessage,
                            PR_SEARCH_KEY,
                            &ulCount,
                            (LPVOID FAR *) &lpSearchKey);

            if ( FAILED(hr) )
            {
                INTERNAL_ERROR( TEXT("HrMAPIGetPropBinary()"), hr);

                goto cleanup;
            }

            ASSERTERROR(ulCount == ulCountNew, "Bad ulCount");

            // manually compare search keys. (PR_SEARCH_KEY is comparable)
            bFound = FALSE;
            if ( memcmp(lpSearchKey, lpSearchKeyNew, ulCount) == 0 )
            {
                // Found correct new attachment.
                bFound = TRUE;
                break;
            }

        }   // end for

        // Make sure we found the corresponding attachment.
        if ( !bFound )
        {
            INTERNAL_ERROR( TEXT("HrCopyGWAttachProps()"),  E_FAIL);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Create and copy all properties in new attachment which aren't
        // in the old attachment.
        hr = HrCreateNewProps(lpMessage, lpNewMessage);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Call this function recursively to handle all attached messages.
        hr = HrCopyGWAttachProps(lpMessage, lpNewMessage);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Save changes to the new message.
        hr = lpNewMessage->SaveChanges(KEEP_OPEN_READWRITE);

        if ( FAILED(hr) )
        {
            // check for insufficient disk space
            if ( hr == MAPI_E_NOT_ENOUGH_DISK )
            {
                EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

hr = HR_LOG(E_FAIL);

goto cleanup;
            }

else
{
                INTERNAL_ERROR( TEXT("IMAPIProp::SaveChanges()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
}
        }// end if save changes failure

        // Save changes to the new attachment
        hr = lpNewAttachment->SaveChanges(KEEP_OPEN_READWRITE);

        if ( FAILED(hr) )
        {
            // check for insufficient disk space
            if ( hr == MAPI_E_NOT_ENOUGH_DISK )
            {
                EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

hr = HR_LOG(E_FAIL);

goto cleanup;
            }

else
{
                INTERNAL_ERROR( TEXT("IMAPIProp::SaveChanges()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
}
        }
    }   // end for

cleanup:

    // Release the MAPI objects
    ULRELEASE(lpAttachTable);
    ULRELEASE(lpNewAttachTable);
    ULRELEASE(lpMessage);
    ULRELEASE(lpNewMessage);
    ULRELEASE(lpAttachment);
    ULRELEASE(lpNewAttachment);

    // Free  MAPI structures.  (SRowSet is special!)
    FREEPROWS(lpRows);
    FREEPROWS(lpNewRows);

    MAPIFREEBUFFER(lpSearchKey);
    MAPIFREEBUFFER(lpSearchKeyNew);

    RETURN(hr);

}   // end CGWClean::CopyGWAttachProps()

//$--CGWClean::HrCreateNewProps----------------------------------------------------
//
// DESCRIPTION: Determine which properties didn't get copied
//              to the new message by CopyMessages(), and then
//              create and set those properties on the new message.
//
// INPUT:       LPMESSAGE --  Original message pointer
//              LPMESSAGE --  New message pointer
//
// RESULT:      HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------

HRESULT CGWClean::HrCreateNewProps(   // RETURNS: HRESULT
    IN LPMESSAGE lpMessage,         // source message
    IN LPMESSAGE lpNewMessage)                // destination message
{
    const   INT iMaxSize    =   124;    // maximum number of properties to create
    HRESULT hr  =   NOERROR;    // return code
    ULONG   ulCount =   0;      // # props for original message
    ULONG   ulNewCount  =   0;  // # props for new message
    LPSPropValue    lpsPropValues   =   NULL;   // original message properties and values
    LPSPropValue    lpsPropNewValues    =   NULL;   // new message properties and values
    ULONG   ulLoopCount =   0;  // loop counter
    ULONG   ulLoopCount2    =   0;  // second loop counter
    ULONG   ulOrigProp  =   0;  // original message property tag
    BOOL    bFound  =   FALSE;  // TRUE if property is found in new message
    MAPINAMEID  rgNamedMsgProps[iMaxSize] =   {0}; // array of named property structures for "extra" message properties
    LPMAPINAMEID    lpNamedMsgProps[iMaxSize] =   {0};    // array of pointers to MAPINAMEID structures
    ULONG   ulCreated   =   0;  // number of named properties created in new message

SPropValue  lpsCreatedPropValues[iMaxSize]   =   {0};    // values of created properties
    LPSPropTagArray lpsNewPropTags  =   NULL;   // new named property tags
    WCHAR   lpPropNames[iMaxSize][80]  =   {0};    // named property array
    LPCWSTR lpBaseName  =   L"GWMSGPROP";   // base name for named property
    LPSPropProblemArray    lpsPropProblems =   NULL;   // for SetProps call
    WCHAR   lpTempString[10]    =   {0};        // temporary string for _itow

    DEBUGPRIVATE("CGWClean::HrCreateNewProps()\n");

    // check input parameters
    hr = CHK_CGWClean_HrCreateNewProps(lpMessage, lpNewMessage);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Get all properties for the original message
    hr = lpMessage->GetProps(NULL,  // get all properties
                                 0,     // flags
                                 &ulCount,  // number of properties
                                 &lpsPropValues);   // property value array pointer

    if ( hr == MAPI_W_ERRORS_RETURNED )
    {
        // May not have enough memory to get PR_BODY, PR_RTF_COMPRESSED
        // and PR_ATTACH_DATA_BIN properties if they are very large.
        // This is not really an error.
        // CopyMessages will copy over all properties that it can.
        // Then, we are only interested in copying over properties which
        // exist in old object but not in the new.
        hr = HR_LOG(NOERROR);
    }

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPIProp::GetProps()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_READ_PTR( lpsPropValues, sizeof(SPropValue), "Bad lpsPropValues");

   // Get all properties from the new messsage.
   hr = lpNewMessage->GetProps(NULL,  // get all properties
                                 0,     // flags
                                 &ulNewCount,  // number of properties
                                 &lpsPropNewValues);   // property value array pointer

    if ( hr == MAPI_W_ERRORS_RETURNED )
    {
        // May not have enough memory to get PR_BODY, PR_RTF_COMPRESSED
        // and PR_ATTACH_DATA_BIN properties if they are very large.
        // This is not really an error.
        // CopyMessages will copy over all properties that it can.
        // Then, we are only interested in copying over properties which
        // exist in old object but not in the new.
        hr = HR_LOG(NOERROR);
    }

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("IMAPIProp::GetProps()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_READ_PTR( lpsPropNewValues, sizeof(SPropValue), "Bad lpsPropNewValues");

    // If the number of properties in both messages is the same,
    // then we are done.  Otherwise, we need to create all properties
    // in the new message which are in the original message, but which
    // are not currently in the new message.
    if ( ulCount != ulNewCount )
    {
        for ( ulLoopCount = 0; ulLoopCount < ulCount; ulLoopCount++ )
        {
            // Get next original message property
            ulOrigProp = lpsPropValues[ulLoopCount].ulPropTag;

            bFound = FALSE; // initialize found flag

            // Search for original property in the new message.
            for ( ulLoopCount2 = 0; ulLoopCount2 < ulNewCount; ulLoopCount2++ )
            {
                if ( PROP_ID(lpsPropNewValues[ulLoopCount2].ulPropTag) ==
                     PROP_ID(ulOrigProp) )
                {
                    // We found it.  Done with this property check
                    bFound = TRUE;
                    break;
                }
            }   // end for

            // If we didn't find the property in the new message,
            // create it now.
            if ( !bFound )
            {
                // Create a named property in the new message for the
                // original property in the old message
                // (Set up structures here.  Create when all found.)
                ASSERTERROR(ulCreated < iMaxSize, "Bad ulCreated");

                rgNamedMsgProps[ulCreated].lpguid = (LPGUID) &PS_PUBLIC_STRINGS;
                rgNamedMsgProps[ulCreated].ulKind = MNID_STRING;

                // Named property will be GWMSGPROP# where # is an integer
                // starting at 0.
                lstrcpyW(lpPropNames[ulCreated], lpBaseName);
                lstrcatW(lpPropNames[ulCreated], _itow(ulCreated, lpTempString, 10));
                rgNamedMsgProps[ulCreated].Kind.lpwstrName = lpPropNames[ulCreated];

                lpNamedMsgProps[ulCreated] = &(rgNamedMsgProps[ulCreated]);

                // Set up the value for the created property and the property tag type.
                lpsCreatedPropValues[ulCreated].Value = lpsPropValues[ulLoopCount].Value;
                lpsCreatedPropValues[ulCreated].ulPropTag = lpsPropValues[ulLoopCount].ulPropTag;

                // increment count of named message properties created
                ulCreated++;

            }   // end if not found
        }   // end for

        // Create the needed named properties
        hr = lpNewMessage->GetIDsFromNames(ulCreated,    // # of names
                                           (LPMAPINAMEID FAR *) lpNamedMsgProps,// array of names
                                           MAPI_CREATE,     // flags
                                           &lpsNewPropTags);// address of property tags

        switch ( hr )
        {
            case MAPI_W_ERRORS_RETURNED:
                INTERNAL_ERROR( TEXT("IMAPIProp::GetIDsFromNames()"), hr);

                hr = HR_LOG(E_FAIL);
                goto cleanup;

            case MAPI_E_NOT_ENOUGH_DISK:
                // insufficient disk space
                EventLogMsg(
                GWCLEAN_OUTOFDISK,
                0,
                0);

                hr = HR_LOG(E_FAIL);

                goto cleanup;

            default:
                if ( FAILED(hr) )
                {
                    INTERNAL_ERROR( TEXT("IMAPIProp::GetIDsFromNames()"), hr);

                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                break;

        }   // end switch

        // Keep the original property type, but replace the
        // proptery ID with the new named property ID.
        for ( ulLoopCount2 = 0; ulLoopCount2 < ulCreated; ulLoopCount2++ )
        {
            lpsCreatedPropValues[ulLoopCount2].ulPropTag = PROP_TAG(
               PROP_TYPE(lpsCreatedPropValues[ulLoopCount2].ulPropTag),
               PROP_ID(lpsNewPropTags->aulPropTag[ulLoopCount2]));

        }   // end for

        // Set the values of the new named properties
        hr = lpNewMessage->SetProps(ulCreated, // number of properties
                                    lpsCreatedPropValues,  // Property tags and values
                                    &lpsPropProblems);    // problem structure

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("IMAPIProp::SetProps()"), hr);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }   // end if some properties not already copied over

cleanup:

    // free MAPI buffers
    MAPIFREEBUFFER(lpsPropValues);
    MAPIFREEBUFFER(lpsPropNewValues);
    MAPIFREEBUFFER(lpsNewPropTags);
    MAPIFREEBUFFER(lpsPropProblems);

    // Release MAPI and OLE objects

    RETURN(hr);

}

/*
 * EOF
 */


GWOBJCLS.H

// --gwobjcls.h---------------------------------------------------------------
//
// Class header file for the GWCLEAN class object.
//
// Copyright (C) Microsoft Corporation 1986-1996,  All Rights Reserved

#ifndef _GWOBJCLS_H
#define _GWOBJCLS_H

// $--CGWClean---------------------------------------------------------------
//
// GWCLEAN class defintion.  Main purpose is to avoid lots of global variables
// and to make memory de-allocation at program termination simple.
//
// ---------------------------------------------------------------------------

class CGWClean
{
public:
    // class methods
    CGWClean();                     // constructor
    ~CGWClean();                    // destructor

    // initializes class members
    HRESULT CGWClean::HrInitialize(
             IN LPTSTR lpProfileName,       // gateway profile name
             IN LPTSTR lpPSTName,           // New PST name
             IN LPTSTR lpFolderName,        // gateway folder name
             IN LPFILETIME lpBeforeTime);   // date/time before which to move messages

    HRESULT HrMoveMsgs();               // Moves messages before date/time from GW out folder to new PST out folder

private:
    // Additional, helper functions
    HRESULT HrCreatePSTFolder();        // Creates new folder in new PST
    HRESULT HrCopyGWMessageProps(IN LPENTRYLIST, IN LPENTRYLIST);   // Copies gateway message properties not copied by CopyMessages
    HRESULT HrCopyGWAttachProps(IN LPMESSAGE, IN LPMESSAGE);    // Copies gateway attachment properties not copied by CopyMessages
    HRESULT HrCreateNewProps(IN LPMESSAGE, IN LPMESSAGE);   // created uncopied properties on new message

    // class members
    LPTSTR     m_lpPSTName;            // PST to copy to
    LPTSTR     m_lpProfileName;        // gateway profile to copy from
    LPFILETIME  m_lpBeforeDateTime;     // Remove messages before date and time
    LPTSTR     m_lpFolderName;         // Gateway folder to move messages from

    // MAPI session variables
    LPMAPISESSION m_lpSessGW;           // Session for gateway profile
    LPMAPISESSION m_lpSessNew;          // Session for temporary profile
    LPMDB         m_lpMDBGateway;       // Gateway message store pointer
    LPMDB         m_lpMDBNew;           // PST message store pointer
    LPMAPIFOLDER  m_lpFolderFrom;       // Gateway folder
    LPMAPIFOLDER  m_lpFolderTo;         // PST folder

    // variables for temporary profile created by CreateNewPST()
    LPTSTR   m_lpTempProfilePW;         // temporary profile's password
    LPTSTR   m_lpTempProfName;          // temporary profile's name

    // state variables
    BOOL    m_fNewPST;                  // TRUE if PST is created
    BOOL    m_fNoMessages;              // TRUE if there were no messages to move

};

#endif


GWSTRING.H

// --gwstring.h---------------------------------------------------------------
//
// String identifiers for GWCLEAN.
//
// Copyright (C) Microsoft Corporation 1986-1996,  All Rights Reserved
//
// ---------------------------------------------------------------------------

#ifndef _GWSTRING_H
#define _GWSTRING_H

// Message types:
#define TYPE_INFO   0   // informational message
#define TYPE_ERROR  1   // fatal error message

// String identifiers for GWCLEAN.

#define IDS_USAGEFOLDER 100
#define IDS_SUCCESS     101
#define IDS_PARAMERR    102
#define IDS_USAGE       103
#define IDS_BLANK       104
#define IDS_USAGEPST    105
#define IDS_USAGEDAYS   106
#define IDS_USAGEPROF   107
#define IDS_USAGEHELP   108
#define IDS_HELPDESCR   109
#define IDS_HELPDAYS1   110
#define IDS_HELPDAYS2   111
#define IDS_HELPDAYS3   112
#define IDS_HELPDAYS4   113
#define IDS_STARTMOVE   114
#define IDS_OVERWRITEFILE   115
#define IDS_USERCANCEL  116
#define IDS_HELPHELP    117
#define IDS_NOPROFILE   118
#define IDS_BADDAYS     119
#define IDS_NOFILE      120
#define IDS_NOFOLDER    121
#define IDS_HELPPST     122
#define IDS_HELPFOLDER  123
#define IDS_HELPDAYS5   124
#define IDS_EVENT_LOG125
#define IDS_OUTPUTERR126
#defineIDS_1ERROR127
#define IDS_ERRORS128
#define IDS_1WARNING129
#define IDS_WARNINGS130

#endif


ADDRESS.C

// --address.c------------------------------------------------------------------
//
//  Find an address given a proxy address.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "_entryid.h"
#include "gwmain.h"
#include "gwsample.h"

//$--HrCreateSearchKey-----------------------------------------------------------
// Create a search key given an address type and an address.
// -----------------------------------------------------------------------------
HRESULT HrCreateSearchKey(              // RETURNS: return code
    IN LPVOID lpObject,                 // pointer to object
    IN LPTSTR lpszAddressType,          // pointer to address type
    IN LPTSTR lpszAddress,              // pointer to address
    OUT LPTSTR *lppszSearchKey)         // pointer to search key
{
    HRESULT hr     = NOERROR;
    SCODE   sc     = 0;
    ULONG   cBytes = 0;

    DEBUGPUBLIC("HrCreateSearchKey()\n");

    *lppszSearchKey = NULL;

    cBytes =
        (lstrlen(lpszAddressType) +
         lstrlen(lpszAddress) + 2) * sizeof(TCHAR);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszSearchKey);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(*lppszSearchKey, lpszAddressType);
    lstrcat(*lppszSearchKey, TEXT(":"));
    lstrcat(*lppszSearchKey, lpszAddress);
    CharUpperBuff(*lppszSearchKey, cBytes/sizeof(TCHAR));

cleanup:

    return(hr);
}

//$--HrCreateProxyAddress-------------------------------------------------------
// Create proxy address.
// -----------------------------------------------------------------------------
HRESULT HrCreateProxyAddress(               // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPVOID     lpObject,                 // pointer to object
    IN BOOL       fMapiRecip,               // MAPI recipient
    IN LPTSTR     lpszDisplayName,          // pointer to display name
    IN LPTSTR     lpszAddressType,          // pointer to address type
    IN LPTSTR     lpszAddress,              // pointer to address
    OUT LPTSTR    *lppszProxyDisplayName,   // proxy display name
    OUT LPTSTR    *lppszProxyAddressType,   // proxy address type
    OUT LPTSTR    *lppszProxyAddress,       // proxy address
    OUT LPTSTR    *lppszProxySearchKey,     // proxy search key
    OUT ULONG     *lpcbProxyEid,            // count of bytes in proxy entry ID
    OUT LPENTRYID *lppProxyEid)             // pointer to proxy entry ID
{
    HRESULT   hr      = NOERROR;
    HRESULT   hrT     = NOERROR;
    SCODE     sc      = 0;

    ULONG     cBytes  = 0;

    ULONG     cbeidT  = 0;
    LPENTRYID lpeidT  = NULL;

    ULONG     ulFlags = 0;

    DEBUGPUBLIC("HrCreateProxyAddress()\n");

    if(lpszDisplayName == NULL)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    if(lpszAddressType == NULL)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    if(lpszAddress == NULL)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    *lppszProxyDisplayName = NULL;
    *lppszProxyAddressType = NULL;
    *lppszProxyAddress     = NULL;
    *lppszProxySearchKey   = NULL;
    *lpcbProxyEid          = 0;
    *lppProxyEid           = NULL;

    //
    // Create proxy address
    //

    cBytes = cbStrLen(lpszAddress);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszProxyAddress);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszProxyAddress, lpszAddress, cBytes);

    //
    // Create proxy display name
    //

    cBytes = cbStrLen(lpszDisplayName);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszProxyDisplayName);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszProxyDisplayName, lpszDisplayName, cBytes);

    //
    // Create proxy address type
    //

    cBytes = cbStrLen(lpszAddressType);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszProxyAddressType);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszProxyAddressType, lpszAddressType, cBytes);

    if(fMapiRecip == TRUE)
    {
        ulFlags = fMapiUnicode;
    }
    else
    {
        ulFlags = fMapiUnicode|MAPI_SEND_NO_RICH_INFO;
    }

    //
    // Create proxy entry ID
    //

    hrT = MAPICALL(lpAdrBook)->CreateOneOff(
        lpAdrBook,
        *lppszProxyDisplayName,
        *lppszProxyAddressType,
        *lppszProxyAddress,
        ulFlags,
        &cbeidT,
        &lpeidT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    sc = MAPIAllocateMore(cbeidT, lpObject, (void **)lppProxyEid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    *lpcbProxyEid = cbeidT;

    CopyMemory(*lppProxyEid, lpeidT, cbeidT);

    //
    // Create proxy search key
    //

    hr = HrCreateSearchKey(
        lpObject,
        *lppszProxyAddressType,
        *lppszProxyAddress,
        lppszProxySearchKey);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpeidT);

    if(FAILED(hr))
    {
        *lppszProxyDisplayName = NULL;
        *lppszProxyAddressType = NULL;
        *lppszProxyAddress     = NULL;
        *lppszProxySearchKey   = NULL;
        *lpcbProxyEid          = 0;
        *lppProxyEid           = NULL;
    }

    RETURN(hr);
}

//$--HrCreateEmailAddress-------------------------------------------------------
// Create email address.
// -----------------------------------------------------------------------------
HRESULT HrCreateEmailAddress(               // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPVOID     lpObject,                 // pointer to object
    IN LPTSTR     lpszDisplayName,          // pointer to display name
    IN LPTSTR     lpszAddressType,          // pointer to address type
    IN LPTSTR     lpszAddress,              // pointer to address
    IN ULONG      cbeid,                    // count of bytes in entry ID
    IN LPENTRYID  lpeid,                    // pointer to entry ID
    OUT LPTSTR    *lppszEmailDisplayName,   // email display name
    OUT LPTSTR    *lppszEmailAddressType,   // email address type
    OUT LPTSTR    *lppszEmailAddress,       // email address
    OUT LPTSTR    *lppszEmailSearchKey,     // email search key
    OUT ULONG     *lpcbEmailEid,            // count of bytes in email entry ID
    OUT LPENTRYID *lppEmailEid)             // pointer to email entry ID
{
    HRESULT   hr     = NOERROR;
    SCODE     sc     = 0;
    ULONG     cBytes = 0;

    DEBUGPUBLIC("HrCreateEmailAddress()\n");

    *lppszEmailDisplayName = NULL;
    *lppszEmailAddressType = NULL;
    *lppszEmailAddress     = NULL;
    *lppszEmailSearchKey   = NULL;
    *lpcbEmailEid          = 0;
    *lppEmailEid           = NULL;

    //
    // Create email address
    //

    cBytes = cbStrLen(lpszAddress);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszEmailAddress);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszEmailAddress, lpszAddress, cBytes);

    //
    // Create email display name
    //

    cBytes = cbStrLen(lpszDisplayName);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszEmailDisplayName);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszEmailDisplayName, lpszDisplayName, cBytes);

    //
    // Create email address type
    //

    cBytes = cbStrLen(lpszAddressType);

    sc = MAPIAllocateMore(cBytes, lpObject, (void **)lppszEmailAddressType);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppszEmailAddressType, lpszAddressType, cBytes);

    //
    // Create email entry ID
    //

    sc = MAPIAllocateMore(cbeid, lpObject, (void **)lppEmailEid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    *lpcbEmailEid = cbeid;

    CopyMemory(*lppEmailEid, lpeid, cbeid);

    //
    // Create email search key
    //

    hr = HrCreateSearchKey(
        lpObject,
        *lppszEmailAddressType,
        *lppszEmailAddress,
        lppszEmailSearchKey);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        *lppszEmailDisplayName = NULL;
        *lppszEmailAddressType = NULL;
        *lppszEmailAddress     = NULL;
        *lppszEmailSearchKey   = NULL;
        *lpcbEmailEid          = 0;
        *lppEmailEid           = NULL;
    }

    return(hr);
}

//$--HrDeleteAddressProps--------------------------------------------------------
// Delete address properties from the message.
// -----------------------------------------------------------------------------
HRESULT HrDeleteAddressProps(           // RETURNS: return code
    IN LPMESSAGE  lpMessage,            // pointer to message
    IN ULONG ulDisplayName,             // display name property tag
    IN ULONG ulAddressType,             // address type property tag
    IN ULONG ulAddress,                 // address property tag
    IN ULONG ulSearchKey,               // search key property tag
    IN ULONG ulEntryId)                 // entry ID property tag
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;
    ULONG   i   = 0;

    SizedSPropTagArray(5, rgPropTags) =
    {
        5,
        {
            PR_NULL,
            PR_NULL,
            PR_NULL,
            PR_NULL,
            PR_NULL,
        }
    };

    DEBUGPUBLIC("HrDeleteAddressProps()\n");

    if(FBadPropTag(ulDisplayName) ||
      (PROP_TYPE(ulDisplayName) == PT_NULL) ||
      (PROP_TYPE(ulDisplayName) == PT_ERROR))
    {
        MODULE_WARNING1("Invalid property type for property tag [%08lx]",ulDisplayName);
    }
    else
    {
        rgPropTags.aulPropTag[i++] = ulDisplayName;
    }

    if(FBadPropTag(ulAddressType) ||
      (PROP_TYPE(ulAddressType) == PT_NULL) ||
      (PROP_TYPE(ulAddressType) == PT_ERROR))
    {
        MODULE_WARNING1("Invalid property type for property tag [%08lx]",ulAddressType);
    }
    else
    {
        rgPropTags.aulPropTag[i++] = ulAddressType;
    }

    if(FBadPropTag(ulAddress) ||
      (PROP_TYPE(ulAddress) == PT_NULL) ||
      (PROP_TYPE(ulAddress) == PT_ERROR))
    {
        MODULE_WARNING1("Invalid property type for property tag [%08lx]",ulAddress);
    }
    else
    {
        rgPropTags.aulPropTag[i++] = ulAddress;
    }

    if(FBadPropTag(ulSearchKey) ||
      (PROP_TYPE(ulSearchKey) == PT_NULL) ||
      (PROP_TYPE(ulSearchKey) == PT_ERROR))
    {
        MODULE_WARNING1("Invalid property type for property tag [%08lx]",ulSearchKey);
    }
    else
    {
        rgPropTags.aulPropTag[i++] = ulSearchKey;
    }

    if(FBadPropTag(ulEntryId) ||
      (PROP_TYPE(ulEntryId) == PT_NULL) ||
      (PROP_TYPE(ulEntryId) == PT_ERROR))
    {
        MODULE_WARNING1("Invalid property type for property tag [%08lx]",ulEntryId);
    }
    else
    {
        rgPropTags.aulPropTag[i++] = ulEntryId;
    }

    //
    // Check if there are any properties left to delete.
    //

    if(i == 0)
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
    }

    rgPropTags.cValues = i;

    hrT = MAPICALL(lpMessage)->DeleteProps(
        lpMessage,
        (SPropTagArray *)&rgPropTags,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    return(hr);
}

//$--HrDeleteAddressListProperty------------------------------------------------
// Delete a property from an address list given its property tag.
// -----------------------------------------------------------------------------
HRESULT HrDeleteAddressListProperty(    // RETURNS: return code
    IN ULONG ulPropTag,                 // property tag
    IN OUT LPADRLIST lpAdrList)         // pointer to address list
{
    HRESULT      hr      = NOERROR;
    ULONG        i       = 0;
    ULONG        cValues = 0;
    LPSPropValue lpProps = NULL;
    LPSPropValue lpPropT = NULL;

    DEBUGPUBLIC("HrDeleteAddressListProperty()\n");

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        cValues = lpAdrList->aEntries[i].cValues;
        lpProps = lpAdrList->aEntries[i].rgPropVals;

        lpPropT = LpValFindProp(ulPropTag, cValues, lpProps);

        if(lpPropT != NULL)
        {
            lpPropT->ulPropTag = PR_NULL;
        }
    }

    RETURN(hr);
}

//$--HrSetUlongPropertyValue----------------------------------------------------
// Set a ULONG property value.
// -----------------------------------------------------------------------------
HRESULT HrSetUlongPropertyValue(        // RETURNS: return code
    IN BOOL  fOverwrite,                // overwrite value if it exists
    IN ULONG ulPropTag,                 // property tag
    IN ULONG ulValue,                   // property value
    IN ULONG cValues,                   // count of property values
    IN LPSPropValue lpProps,            // pointer to property values
    OUT ULONG* lpcValues,               // new count of property values
    OUT LPSPropValue* lppProps)         // new pointer to property values
{
    HRESULT      hr           = NOERROR;
    LPSPropValue lpPropT      = NULL;
    SPropValue   PropValue[1] = {0};

    DEBUGPUBLIC("HrSetUlongPropertyValue()\n");

    if(PROP_TYPE(ulPropTag) != PT_LONG)
    {
        RETURN(HR_LOG(E_INVALIDARG));
    }

    *lpcValues = 0;
    *lppProps  = NULL;

    lpPropT = LpValFindProp(ulPropTag, cValues, lpProps);

    if(lpPropT == NULL)
    {
        PropValue[0].ulPropTag = ulPropTag;
        PropValue[0].Value.ul  = ulValue;

        hr = HrMAPIAppendSPropValues(
            cValues,
            lpProps,
            1,
            PropValue,
            lpcValues,
            lppProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        SCODE sc = 0;

        sc = ScDupPropset(
            cValues,
            lpProps,
    MAPIAllocateBuffer,
    lppProps);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        *lpcValues = cValues;

        if(fOverwrite == TRUE)
        {
            lpPropT = LpValFindProp(ulPropTag, *lpcValues, *lppProps);

            if(lpPropT != NULL)
            {
                lpPropT->ulPropTag = ulPropTag;
                lpPropT->Value.ul  = ulValue;
            }
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrExpandMessageAddressList-------------------------------------------------
// Expand a list of addresses on the message.
// -----------------------------------------------------------------------------
HRESULT HrExpandMessageAddressList(     // RETURNS: return code
    IN LPMESSAGE  lpMessage,            // pointer to message
    OUT ULONG*    lpcMesgAddr,          // count of message addresses
    OUT ULONG*    lpcReplyAddr,         // count of reply recipient addresses
    OUT ULONG*    lpcRecipAddr,         // count of recipient addresses
    OUT LPADRLIST *lppRecipList,        // pointer to recipient address list
    OUT LPADRLIST *lppAdrList)          // pointer to address list
{
    HRESULT      hr                   = NOERROR;
    HRESULT      hrT                  = NOERROR;
    ULONG        i                    = 0;
    ULONG        cValues              = 0;
    LPSPropValue lpProps              = NULL;
    LPSPropValue lpPropT              = NULL;
    LPSRowSet    lpRows               = 0;

    LPMAPITABLE  lpTable              = NULL;

    SPropValue   rgProps[3]           = {0};

    ULONG        cEntries             = 0;

    ULONG        cAddr                = 0;

    ULONG        cMesgAddr            = 0;
    ULONG        cRecipAddr           = 0;
    ULONG        cReplyAddr           = 0;

#define IREPORTDEST 10

#define CADDREID    14

    SizedSPropTagArray(CADDREID, rgPropTags) =
    {
        CADDREID,
        {
            PR_SENDER_ENTRYID,
            PR_SENT_REPRESENTING_ENTRYID,
            PR_ORIGINAL_SENDER_ENTRYID,
            PR_ORIGINAL_SENT_REPRESENTING_ENTRYID,
            PR_RECEIVED_BY_ENTRYID,
            PR_RCVD_REPRESENTING_ENTRYID,
            PR_ORIGINAL_AUTHOR_ENTRYID,
            PR_REPORT_ENTRYID,
            PR_READ_RECEIPT_ENTRYID,
            PR_ORIGINATOR_ENTRYID,
            PR_REPORT_DESTINATION_ENTRYID,
            PR_CREATOR_ENTRYID,
            PR_LAST_MODIFIER_ENTRYID,
            //PR_ORIGINALLY_INTENDED_RECIP_ENTRYID,
            PR_REPLY_RECIPIENT_ENTRIES,
        }
    };

    DEBUGPUBLIC("HrExpandMessageAddressList()\n");

    *lpcMesgAddr  = 0;
    *lpcReplyAddr = 0;
    *lpcRecipAddr = 0;
    *lppRecipList = NULL;
    *lppAdrList   = NULL;

    //
    // Get message addresses
    //

    hrT = MAPICALL(lpMessage)->GetProps(
        lpMessage,
        (SPropTagArray *)&rgPropTags,
        fMapiUnicode,
        &cValues,
        &lpProps);

    if(FAILED(hrT))
    {
        lpProps = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Get the recipient table for the message
    //

    hrT = MAPICALL(lpMessage)->GetRecipientTable(
        lpMessage,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//
//  Query all the rows
//

hr = HrQueryAllRows(
    lpTable,
NULL,
NULL,
NULL,
0,
&lpRows);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Calculate the total number of addresses
    //

    if(lpProps[CADDREID-1].ulPropTag == PR_REPLY_RECIPIENT_ENTRIES)
    {
        cReplyAddr = lpProps[CADDREID-1].Value.MVbin.cValues;
    }
    else
    {
        cReplyAddr = 0;
    }

    cEntries = cValues - 1 + lpRows->cRows + cReplyAddr;

    for(i = 0; i < lpRows->cRows; i++)
    {
        lpPropT = LpValFindProp(
            PR_ORIGINAL_ENTRYID,
            lpRows->aRow[i].cValues,
            lpRows->aRow[i].lpProps);

        if((lpPropT != NULL) && (lpPropT->ulPropTag == PR_ORIGINAL_ENTRYID))
        {
            cEntries++;
        }
    }

    //
    // Create an ADRLIST
    //

    hr = HrMAPICreateSizedAddressList(
        cEntries,
        lppAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cAddr = 0;

    //
    // Add message addresses to the address list
    //

    cMesgAddr = --cValues;

    for(i = 0; i < cMesgAddr; i++, cAddr++)
    {
        if(PROP_TYPE(lpProps[i].ulPropTag) == PT_ERROR)
        {
            rgProps[0].ulPropTag     = PR_NULL;
            rgProps[0].Value.err     = MAPI_E_NOT_FOUND;
        }
        else
        {
            rgProps[0].ulPropTag     = PR_ENTRYID;
            rgProps[0].Value.bin.cb  = lpProps[i].Value.bin.cb;
            rgProps[0].Value.bin.lpb = lpProps[i].Value.bin.lpb;
        }

        rgProps[1].ulPropTag     = PR_RECIPIENT_TYPE;
        rgProps[1].Value.ul      = MAPI_TO;

        hr = HrMAPISetAddressList(
            cAddr,
            2,
            rgProps,
            *lppAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    //
    // Add reply recipient addresses to the address list
    //

    for(i = 0; i < cReplyAddr; i++, cAddr++)
    {
        rgProps[0].ulPropTag     = PR_ENTRYID;
        rgProps[0].Value.bin.cb  = lpProps[CADDREID-1].Value.MVbin.lpbin[i].cb;
        rgProps[0].Value.bin.lpb = lpProps[CADDREID-1].Value.MVbin.lpbin[i].lpb;
        rgProps[1].ulPropTag     = PR_RECIPIENT_TYPE;
        rgProps[1].Value.ul      = MAPI_TO;

        hr = HrMAPISetAddressList(
            cAddr,
            2,
            rgProps,
            *lppAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    //
    // Add recipient addresses to the address list
    //

    cRecipAddr = lpRows->cRows;

    for(i = 0; i < cRecipAddr; i++, cAddr++)
    {
        hr = HrMAPISetAddressList(
            cAddr,
            lpRows->aRow[i].cValues,
            lpRows->aRow[i].lpProps,
            *lppAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    for(i = 0; i < cRecipAddr; i++)
    {
        lpPropT = LpValFindProp(
            PR_ORIGINAL_ENTRYID,
            lpRows->aRow[i].cValues,
            lpRows->aRow[i].lpProps);

        if((lpPropT != NULL) && (lpPropT->ulPropTag == PR_ORIGINAL_ENTRYID))
        {
            rgProps[0].ulPropTag     = PR_ENTRYID;
            rgProps[0].Value.bin.cb  = lpPropT->Value.bin.cb;
            rgProps[0].Value.bin.lpb = lpPropT->Value.bin.lpb;
            rgProps[1].ulPropTag     = PR_RECIPIENT_NUMBER;
            rgProps[1].Value.ul      = i;
            rgProps[2].ulPropTag     = PR_RECIPIENT_TYPE;
            rgProps[2].Value.ul      = MAPI_TO;

            hr = HrMAPISetAddressList(
                cAddr,
                3,
                rgProps,
                *lppAdrList);

            cAddr++;

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
    }

    *lpcMesgAddr  = cMesgAddr;
    *lpcReplyAddr = cReplyAddr;
    *lpcRecipAddr = cRecipAddr;

    *lppRecipList = (LPADRLIST)lpRows;

cleanup:

    if(FAILED(hr))
    {
        *lpcMesgAddr  = 0;
        *lpcReplyAddr = 0;
        *lpcRecipAddr = 0;

        FREEPADRLIST(*lppRecipList);
    }

    // Free MAPI buffers
    MAPIFREEBUFFER(lpProps);

    // Release MAPI objects
    ULRELEASE(lpTable);

    RETURN(hr);

#undef IREPORTDEST
}

//$--HrCheckForType-------------------------------------------------------------
//  Check if the passed in string has the requested address type. If so, return
//  the address value, otherwise fail.
// -----------------------------------------------------------------------------
static HRESULT HrCheckForType(  // RETURNS: return code
    IN  LPUNKNOWN lpObject,     // pointer to object
    IN  LPTSTR lpszAddrType,    // pointer to address type
    IN  LPTSTR lpszProxy,       // pointer to proxy address
    OUT LPTSTR * lppszAddress)  // pointer to address pointer
{
    HRESULT hr              = EDK_E_NOT_FOUND;
    int     rc              = 0;
    LPTSTR  lpszProxyAddr   = NULL;
    ULONG   cbAddress       = 0;
    SCODE   sc              = 0;
    ULONG   cchProxy        = 0;
    ULONG   cchProxyType    = 0;

    DEBUGPUBLIC("HrCheckForType()\n");

    //
// Initialize output parameter
    //

*lppszAddress = NULL;

    //
    // Find the ':' separator.
    //

    cchProxy = lstrlen(lpszProxy);

    for(
        cchProxyType = 0;
        (lpszProxy[cchProxyType] != 0) && (lpszProxy[cchProxyType] != TEXT(':'));
        cchProxyType++);

    if((cchProxyType == 0) || (cchProxyType >= cchProxy))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Does the address type match?
    //

    rc = CompareString(
        GetSystemDefaultLCID(),
        NORM_IGNORECASE|NORM_IGNORENONSPACE|SORT_STRINGSORT,
        lpszAddrType, -1,
        lpszProxy, cchProxyType);

    if(rc == 2)
    {
        //
        // Specified address type found
        //

        lpszProxyAddr = lpszProxy + cchProxyType + 1;

        cbAddress = cbStrLen(lpszProxyAddr);

        //
        // Make a buffer to hold it.
        //

        sc = MAPIAllocateMore(cbAddress, lpObject, (void **)lppszAddress);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }
        else
        {
            CopyMemory(*lppszAddress, lpszProxyAddr, cbAddress);

            hr = NOERROR;
        }
    }
    else if(rc == 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    else
    {
        hr = EDK_E_NOT_FOUND;
    }

cleanup:

    RETURN(hr);
}

//$--HrMapAddrToProxy-----------------------------------------------------------
// Map all email addresses to proxy addresses in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapAddrToProxy(               // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList)         // pointer to address list
{
    HRESULT      hr                    = NOERROR;
    SCODE        sc                    = 0;
    ULONG        i                     = 0;
    ULONG        j                     = 0;
    ULONG        cbAddress             = 0;
    ULONG        cProxy                = 0;
    LPSPropValue lpProps               = NULL;
    LPSPropValue lpPropT               = NULL;

    LPTSTR       lpszProxyDisplayName  = NULL;
    LPTSTR       lpszProxyAddressType  = NULL;
    LPTSTR       lpszProxyAddress      = NULL;

    LPTSTR       lpszEmailDisplayName  = NULL;
    LPTSTR       lpszEmailAddressType  = NULL;
    LPTSTR       lpszEmailAddress      = NULL;
    LPTSTR       lpszEmailSearchKey    = NULL;
    ULONG        cbEmailEid            = 0;
    LPENTRYID    lpEmailEid            = NULL;

    SPropValue   prop[MIN_CRECIPPROPS] = {0};

    SizedSPropTagArray(MIN_CRECIPPROPS, rgPropTags) =
    {
        MIN_CRECIPPROPS,
        {
            PR_DISPLAY_NAME,
            PR_ADDRTYPE,
            PR_EMAIL_ADDRESS,
            PR_SEARCH_KEY,
            PR_ENTRYID,
            PR_SEND_RICH_INFO,
            PR_RESPONSIBILITY,
            PR_RECIPIENT_NUMBER,
            PR_ORIGINAL_DISPLAY_NAME,
            PR_ORIGINAL_ENTRYID,
            PR_EMS_AB_PROXY_ADDRESSES
        }
    };

    DEBUGPUBLIC("HrMapAddrToProxy()\n");

    //
    // NOTE: PrepareRecips() will not create a value for PR_SEARCH_KEY. This is
    // why the following code has to check for PR_ADDRTYPE and PR_EMAIL_ADDRESS
    // and create a search key where possible.
    //

    hr = MAPICALL(lpAdrBook)->PrepareRecips(
        lpAdrBook,
        0,
        (LPSPropTagArray)&rgPropTags,
        lpAdrList);

    if(HR_FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        lpProps = lpAdrList->aEntries[i].rgPropVals;

        lpPropT = LpValFindProp(
            PR_ENTRYID,
            lpAdrList->aEntries[i].cValues,
            lpProps);

        //
        // Check if this is a valid recipient entry.
        //

        if((lpAdrList->aEntries[i].cValues < MIN_CRECIPPROPS) ||
           (lpPropT == NULL) ||
           (PROP_TYPE(lpPropT->ulPropTag) == PT_ERROR))
        {
            //
            // Add an empty recipient entry.
            //

            MODULE_WARNING1("**** Adding an empty recipient entry [%lu] ****", i);

            prop[0].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_DISPLAY_NAME));
            prop[0].Value.err = MAPI_E_NOT_FOUND;
            prop[1].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ADDRTYPE));
            prop[1].Value.err = MAPI_E_NOT_FOUND;
            prop[2].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_EMAIL_ADDRESS));
            prop[2].Value.err = MAPI_E_NOT_FOUND;
            prop[3].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_SEARCH_KEY));

prop[3].Value.err = MAPI_E_NOT_FOUND;
            prop[4].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_SEND_RICH_INFO));
            prop[4].Value.err = MAPI_E_NOT_FOUND;
            prop[5].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ENTRYID));
            prop[5].Value.err = MAPI_E_NOT_FOUND;
            prop[6].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_RESPONSIBILITY));
            prop[6].Value.err = MAPI_E_NOT_FOUND;
            prop[7].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_RECIPIENT_NUMBER));
            prop[7].Value.err = MAPI_E_NOT_FOUND;
            prop[8].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ORIGINAL_DISPLAY_NAME));
            prop[8].Value.err = MAPI_E_NOT_FOUND;
            prop[9].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ORIGINAL_ENTRYID));
            prop[9].Value.err = MAPI_E_NOT_FOUND;
            prop[10].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_EMS_AB_PROXY_ADDRESSES));
            prop[10].Value.err = MAPI_E_NOT_FOUND;

            hr = HrMAPISetAddressList(
                i,
                MIN_CRECIPPROPS,
                prop,
                lpAdrList);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            lpProps = lpAdrList->aEntries[i].rgPropVals;
        }

        // If the PR_RECIPIENT_NUMBER property is not set, default the value
        // to 1

        if((PROP_TYPE(lpProps[IRECIPNUM].ulPropTag) == PT_ERROR) &&
           (PROP_ID(lpProps[IRECIPNUM].ulPropTag) == PROP_ID(PR_RECIPIENT_NUMBER)))
        {
            lpProps[IRECIPNUM].ulPropTag = PR_RECIPIENT_NUMBER;
            lpProps[IRECIPNUM].Value.ul  = 1;
        }

        // If the PR_SEND_RICH_INFO property is not set, default the value
        // to FALSE

        if((PROP_TYPE(lpProps[IMAPIRECIP].ulPropTag) == PT_ERROR) &&
           (PROP_ID(lpProps[IMAPIRECIP].ulPropTag) == PROP_ID(PR_SEND_RICH_INFO)))
        {
            lpProps[IMAPIRECIP].ulPropTag = PR_SEND_RICH_INFO;
            lpProps[IMAPIRECIP].Value.b   = FALSE;
        }

        // If the PR_RESPONSIBILITY property is not set, default the value
        // to FALSE

        if((PROP_TYPE(lpProps[IRESP].ulPropTag) == PT_ERROR) &&
           (PROP_ID(lpProps[IRESP].ulPropTag) == PROP_ID(PR_RESPONSIBILITY)))
        {
            lpProps[IRESP].ulPropTag = PR_RESPONSIBILITY;
            lpProps[IRESP].Value.b   = FALSE;
        }

        lpszProxyDisplayName = NULL;
        lpszProxyAddressType = NULL;
        lpszProxyAddress     = NULL;

        // If the given address type matches the PR_ADDRTYPE value,
        // return the PR_EMAIL_ADDRESS value

        if((lpProps[IADDRTYPE].ulPropTag == PR_ADDRTYPE) &&
           (lpProps[IEMAILADDR].ulPropTag == PR_EMAIL_ADDRESS) &&
           (lstrcmpi(lpProps[IADDRTYPE].Value.LPSZ, lpszAddrType) == 0))
        {
            lpszProxyAddressType = lpProps[IADDRTYPE].Value.LPSZ;
            lpszProxyAddress = lpProps[IEMAILADDR].Value.LPSZ;
        }

        //
        // Search for a PR_EMS_AB_PROXY_ADDRESSES of the given type if present.
        //

        else if(lpProps[IPROXYADDR].ulPropTag == PR_EMS_AB_PROXY_ADDRESSES)
        {
            BOOL IsFound = FALSE;

            //
            // Count of proxy addresses
            //

            cProxy = lpAdrList->aEntries[i].rgPropVals[IPROXYADDR].Value.MVSZ.cValues;

            IsFound = FALSE;

            for(j = 0; j < cProxy; j++)
            {
                hr = HrCheckForType(
                    (LPUNKNOWN)lpProps,
                    lpszAddrType,
                    lpProps[IPROXYADDR].Value.MVSZ.LPPSZ[j],
                    &lpszProxyAddress);

                if(hr == EDK_E_NOT_FOUND)
                {
                    continue;
                }
                else if(FAILED(hr))
                {
                    goto cleanup;
                }
                else
                {
                    IsFound = TRUE;
                    break;
                }
            }

            if(IsFound == FALSE)
            {
                MODULE_WARNING("A proxy address was not found!");

                if(lpProps[IADDRTYPE].ulPropTag == PR_ADDRTYPE)
                {
                    lpszProxyAddressType = lpProps[IADDRTYPE].Value.LPSZ;
                }

                if(lpProps[IEMAILADDR].ulPropTag == PR_EMAIL_ADDRESS)
                {
                    lpszProxyAddress = lpProps[IEMAILADDR].Value.LPSZ;
                }
            }
            else
            {
                lpszProxyAddressType = lpszAddrType;
            }
        }
        else
        {
            if(lpProps[IADDRTYPE].ulPropTag == PR_ADDRTYPE)
            {
                lpszProxyAddressType = lpProps[IADDRTYPE].Value.LPSZ;
            }

            if(lpProps[IEMAILADDR].ulPropTag == PR_EMAIL_ADDRESS)
            {
                lpszProxyAddress = lpProps[IEMAILADDR].Value.LPSZ;
            }
        }

        //
        // Update proxy display name
        //

        if((lpProps[IDISPNAME].ulPropTag != PR_DISPLAY_NAME) ||
           (lpProps[IDISPNAME].Value.LPSZ == NULL) ||
           (lstrlen(lpProps[IDISPNAME].Value.LPSZ) == 0))
        {
            if(lpszProxyAddress != NULL)
            {
                lpszProxyDisplayName = lpszProxyAddress;
            }
            else
            {
                lpszProxyDisplayName = NULL;
            }
        }
        else
        {
            lpszProxyDisplayName = lpProps[IDISPNAME].Value.LPSZ;
        }

        //
        // Create a proxy address
        //

        if((lpszProxyDisplayName != NULL) &&
           (lpszProxyAddressType != NULL) &&
           (lpszProxyAddress != NULL))
        {
            lpszEmailDisplayName = NULL;
            lpszEmailAddressType = NULL;
            lpszEmailAddress     = NULL;
            lpszEmailSearchKey   = NULL;
            cbEmailEid           = 0;
            lpEmailEid           = NULL;

            hr = HrCreateProxyAddress(
                lpAdrBook,
                lpProps,
                lpProps[IMAPIRECIP].Value.b,
                lpszProxyDisplayName,
                lpszProxyAddressType,
                lpszProxyAddress,
                &lpszEmailDisplayName,
                &lpszEmailAddressType,
                &lpszEmailAddress,
                &lpszEmailSearchKey,
                &cbEmailEid,
                &lpEmailEid);

            if(FAILED(hr))
            {
                goto cleanup;
            }

            lpProps[IDISPNAME].ulPropTag    = PR_DISPLAY_NAME;
            lpProps[IDISPNAME].Value.LPSZ   = lpszEmailDisplayName;

            lpProps[IADDRTYPE].ulPropTag    = PR_ADDRTYPE;
            lpProps[IADDRTYPE].Value.LPSZ   = lpszEmailAddressType;

            lpProps[IEMAILADDR].ulPropTag   = PR_EMAIL_ADDRESS;
            lpProps[IEMAILADDR].Value.LPSZ  = lpszEmailAddress;

            lpProps[ISEARCHKEY].ulPropTag   = PR_SEARCH_KEY;

            if(lpszEmailSearchKey == NULL)
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = 0;
                lpProps[ISEARCHKEY].Value.bin.lpb = NULL;
            }
            else
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = cbStrLen(lpszEmailSearchKey);
                lpProps[ISEARCHKEY].Value.bin.lpb = lpszEmailSearchKey;
            }

            lpProps[IENTRYID].ulPropTag     = PR_ENTRYID;
            lpProps[IENTRYID].Value.bin.cb  = cbEmailEid;
            lpProps[IENTRYID].Value.bin.lpb = (LPBYTE)lpEmailEid;
        }
        else
        {
            MODULE_WARNING("Unable to create a proxy address!");
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrCollapseMessageAddressList-----------------------------------------------
// Collapse a list of addresses on the message.
// -----------------------------------------------------------------------------
HRESULT HrCollapseMessageAddressList(   // RETURNS: return code
    IN LPMESSAGE     lpMessage,         // pointer to message
    IN ULONG         cMesgAddr,         // count of message addresses
    IN ULONG         cReplyAddr,        // count of reply recipient addresses
    IN ULONG         cRecipAddr,        // count of recipient addresses
    IN LPADRLIST     lpAdrList,         // message address list
    OUT ULONG        *lpcValues,        // count of message address properties
    OUT LPSPropValue *lppProps,         // message address properties
    OUT LPADRLIST    *lppAdrList)       // recipient address list
{
    HRESULT      hr            = NOERROR;
    SCODE        sc            = 0;
    LPSPropValue lpMesgProps   = NULL;
    LPTSTR       *lppDispNames = NULL;
    SBinary      *lppEntryIDs  = NULL;
    ULONG        cBytes        = 0;
    ULONG        i             = 0;
    ULONG        j             = 0;
    ULONG        cOrigAddr     = 0;
    LPADRLIST    lpRecipList   = 0;

    ULONG        cValues       = 0;
    LPSPropValue lpProps       = NULL;

#define CMESGPROPS 70

    SizedSPropTagArray(CMESGPROPS, rgPropTags) =
    {
        CMESGPROPS,
        {
            PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_SEARCH_KEY,
            PR_SENDER_ENTRYID,
            PR_SENT_REPRESENTING_NAME,
            PR_SENT_REPRESENTING_ADDRTYPE,
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_SENT_REPRESENTING_SEARCH_KEY,
            PR_SENT_REPRESENTING_ENTRYID,
            PR_ORIGINAL_SENDER_NAME,
            PR_ORIGINAL_SENDER_ADDRTYPE,
            PR_ORIGINAL_SENDER_EMAIL_ADDRESS,
            PR_ORIGINAL_SENDER_SEARCH_KEY,
            PR_ORIGINAL_SENDER_ENTRYID,
            PR_ORIGINAL_SENT_REPRESENTING_NAME,
            PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE,
            PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY,
            PR_ORIGINAL_SENT_REPRESENTING_ENTRYID,
            PR_RECEIVED_BY_NAME,
            PR_NULL,//PR_RECEIVED_BY_ADDRTYPE,
            PR_NULL,//PR_RECEIVED_BY_EMAIL_ADDRESS,
            PR_RECEIVED_BY_SEARCH_KEY,
            PR_RECEIVED_BY_ENTRYID,
            PR_RCVD_REPRESENTING_NAME,
            PR_NULL,//PR_RCVD_REPRESENTING_ADDRTYPE,
            PR_NULL,//PR_RCVD_REPRESENTING_EMAIL_ADDRESS,
            PR_RCVD_REPRESENTING_SEARCH_KEY,
            PR_RCVD_REPRESENTING_ENTRYID,
            PR_ORIGINAL_AUTHOR_NAME,
            PR_ORIGINAL_AUTHOR_ADDRTYPE,
            PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS,
            PR_ORIGINAL_AUTHOR_SEARCH_KEY,
            PR_ORIGINAL_AUTHOR_ENTRYID,
            PR_REPORT_NAME,
            PR_NULL,//PR_REPORT_ADDRTYPE,
            PR_NULL,//PR_REPORT_EMAIL_ADDRESS,
            PR_REPORT_SEARCH_KEY,
            PR_REPORT_ENTRYID,
            PR_NULL,//PR_READ_RECEIPT_NAME,
            PR_NULL,//PR_READ_RECEIPT_ADDRTYPE,
            PR_NULL,//PR_READ_RECEIPT_EMAIL_ADDRESS,
            PR_READ_RECEIPT_SEARCH_KEY,
            PR_READ_RECEIPT_ENTRYID,
            PR_ORIGINATOR_NAME,
            PR_ORIGINATOR_ADDRTYPE,
            PR_ORIGINATOR_ADDR,//PR_ORIGINATOR_EMAIL_ADDRESS,
            PR_NULL,//PR_ORIGINATOR_SEARCH_KEY,
            PR_ORIGINATOR_ENTRYID,
            PR_REPORT_DESTINATION_NAME,
            PR_NULL,//PR_REPORT_DESTINATION_ADDRTYPE,
            PR_NULL,//PR_REPORT_DESTINATION_EMAIL_ADDRESS,
            PR_NULL,//PR_REPORT_DESTINATION_SEARCH_KEY,
            PR_REPORT_DESTINATION_ENTRYID,
            PR_CREATOR_NAME,
            PR_NULL,//PR_CREATOR_ADDRTYPE,
            PR_NULL,//PR_CREATOR_EMAIL_ADDRESS,
            PR_NULL,//PR_CREATOR_SEARCH_KEY,
            PR_CREATOR_ENTRYID,
            PR_LAST_MODIFIER_NAME,
            PR_NULL,//PR_LAST_MODIFIER_ADDRTYPE,
            PR_NULL,//PR_LAST_MODIFIER_EMAIL_ADDRESS,
            PR_NULL,//PR_LAST_MODIFIER_SEARCH_KEY,
            PR_LAST_MODIFIER_ENTRYID,
            //PR_ORIGINALLY_INTENDED_RECIPIENT_NAME,
            //PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE,
            //PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS,
            //PR_NULL,//PR_ORIGINALLY_INTENDED_RECIP_SEARCH_KEY,
            //PR_ORIGINALLY_INTENDED_RECIP_ENTRYID,
            PR_REPLY_RECIPIENT_NAMES,
            PR_NULL,//PR_REPLY_RECIPIENT_ADDRTYPE,
            PR_NULL,//PR_REPLY_RECIPIENT_EMAIL_ADDRESS,
            PR_NULL,//PR_REPLY_RECIPIENT_SEARCH_KEY,
            PR_REPLY_RECIPIENT_ENTRIES,
        }
    };

    DEBUGPUBLIC("HrCollapseMessageAddressList()\n");

    if(cMesgAddr != (CMESGPROPS/5-1))
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    if((cMesgAddr + cReplyAddr + cRecipAddr) > lpAdrList->cEntries)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    cOrigAddr = lpAdrList->cEntries - (cMesgAddr + cReplyAddr + cRecipAddr);

    cBytes = CbNewSPropValue(CMESGPROPS);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpMesgProps);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    ZeroMemory(lpMesgProps, cBytes);

    //
    // Copy message address properties
    //

    for(i = 0; i < (CMESGPROPS/5-1); i++)
    {
        if(PROP_ID(lpAdrList->aEntries[i].rgPropVals[IDISPNAME].ulPropTag) !=
           PROP_ID(PR_DISPLAY_NAME))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(rgPropTags.aulPropTag[i*5+IDISPNAME] != PR_NULL)
        {
            lpMesgProps[i*5+IDISPNAME].ulPropTag =
                PROP_TAG(
                    PROP_TYPE(lpAdrList->aEntries[i].rgPropVals[IDISPNAME].ulPropTag),
                    PROP_ID(rgPropTags.aulPropTag[i*5+IDISPNAME]));
        }
        else
        {
            lpMesgProps[i*5+IDISPNAME].ulPropTag = PR_NULL;
        }

        lpMesgProps[i*5+IDISPNAME].Value.LPSZ =
            lpAdrList->aEntries[i].rgPropVals[IDISPNAME].Value.LPSZ;

        if(rgPropTags.aulPropTag[i*5+IADDRTYPE] != PR_NULL)
        {
            lpMesgProps[i*5+IADDRTYPE].ulPropTag =
                PROP_TAG(
                    PROP_TYPE(lpAdrList->aEntries[i].rgPropVals[IADDRTYPE].ulPropTag),
                    PROP_ID(rgPropTags.aulPropTag[i*5+IADDRTYPE]));
        }
        else
        {
            lpMesgProps[i*5+IADDRTYPE].ulPropTag = PR_NULL;
        }

        lpMesgProps[i*5+IADDRTYPE].Value.LPSZ =
            lpAdrList->aEntries[i].rgPropVals[IADDRTYPE].Value.LPSZ;

        if(rgPropTags.aulPropTag[i*5+IEMAILADDR] != PR_NULL)
        {
            lpMesgProps[i*5+IEMAILADDR].ulPropTag =
                PROP_TAG(
                    PROP_TYPE(lpAdrList->aEntries[i].rgPropVals[IEMAILADDR].ulPropTag),
                    PROP_ID(rgPropTags.aulPropTag[i*5+IEMAILADDR]));
        }
        else
        {
            lpMesgProps[i*5+IEMAILADDR].ulPropTag = PR_NULL;
        }

        lpMesgProps[i*5+IEMAILADDR].Value.LPSZ =
            lpAdrList->aEntries[i].rgPropVals[IEMAILADDR].Value.LPSZ;

        if(rgPropTags.aulPropTag[i*5+ISEARCHKEY] != PR_NULL)
        {
            lpMesgProps[i*5+ISEARCHKEY].ulPropTag =
                PROP_TAG(
                    PROP_TYPE(lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].ulPropTag),
                    PROP_ID(rgPropTags.aulPropTag[i*5+ISEARCHKEY]));
        }
        else
        {
            lpMesgProps[i*5+ISEARCHKEY].ulPropTag = PR_NULL;
        }

        lpMesgProps[i*5+ISEARCHKEY].Value.bin.cb =
            lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.cb;
        lpMesgProps[i*5+ISEARCHKEY].Value.bin.lpb =
            lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.lpb;

        if(rgPropTags.aulPropTag[i*5+IENTRYID] != PR_NULL)
        {
            lpMesgProps[i*5+IENTRYID].ulPropTag =
                PROP_TAG(
                    PROP_TYPE(lpAdrList->aEntries[i].rgPropVals[IENTRYID].ulPropTag),
                    PROP_ID(rgPropTags.aulPropTag[i*5+IENTRYID]));
        }
        else
        {
            lpMesgProps[i*5+IENTRYID].ulPropTag = PR_NULL;
        }

        lpMesgProps[i*5+IENTRYID].Value.bin.cb =
            lpAdrList->aEntries[i].rgPropVals[IENTRYID].Value.bin.cb;
        lpMesgProps[i*5+IENTRYID].Value.bin.lpb =
            lpAdrList->aEntries[i].rgPropVals[IENTRYID].Value.bin.lpb;
    }

    //
    // Copy reply recipient properties
    //

    if(cReplyAddr > 0)
    {
        //
        // Copy reply recipient names
        //

        cBytes = cReplyAddr*(sizeof(LPTSTR)+MAX_ALIGN);

        sc = MAPIAllocateBuffer(cBytes, (void **)&lppDispNames);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        ZeroMemory(lppDispNames, cBytes);

        for(i = 0, j = cMesgAddr; i < cReplyAddr; i++, j++)
        {
            lppDispNames[i] =
                lpAdrList->aEntries[j].rgPropVals[IDISPNAME].Value.LPSZ;
        }

        //
        // Copy reply recipient entry IDs
        //

        cBytes = cReplyAddr*(sizeof(SBinary)+MAX_ALIGN);

        sc = MAPIAllocateBuffer(cBytes, (void **)&lppEntryIDs);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        ZeroMemory(lppDispNames, cBytes);

        for(i = 0, j = cMesgAddr; i < cReplyAddr; i++, j++)
        {
            lppEntryIDs[i].cb =
                lpAdrList->aEntries[j].rgPropVals[IENTRYID].Value.bin.cb;

            lppEntryIDs[i].lpb =
                lpAdrList->aEntries[j].rgPropVals[IENTRYID].Value.bin.lpb;
        }

        lpMesgProps[CMESGPROPS-5].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-5];
        lpMesgProps[CMESGPROPS-5].Value.MVSZ.cValues = cReplyAddr;
        lpMesgProps[CMESGPROPS-5].Value.MVSZ.LPPSZ   = lppDispNames;

        lpMesgProps[CMESGPROPS-4].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-4];
        lpMesgProps[CMESGPROPS-3].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-3];
        lpMesgProps[CMESGPROPS-2].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-2];

        lpMesgProps[CMESGPROPS-1].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-1];
        lpMesgProps[CMESGPROPS-1].Value.MVbin.cValues = cReplyAddr;
        lpMesgProps[CMESGPROPS-1].Value.MVbin.lpbin   = lppEntryIDs;
    }
    else
    {
        lpMesgProps[CMESGPROPS-5].ulPropTag =
            PROP_TAG(PT_ERROR,PROP_ID(rgPropTags.aulPropTag[CMESGPROPS-5]));
        lpMesgProps[CMESGPROPS-5].Value.err = MAPI_E_NOT_FOUND;

        lpMesgProps[CMESGPROPS-4].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-4];
        lpMesgProps[CMESGPROPS-3].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-3];
        lpMesgProps[CMESGPROPS-2].ulPropTag = rgPropTags.aulPropTag[CMESGPROPS-2];

        lpMesgProps[CMESGPROPS-1].ulPropTag =
            PROP_TAG(PT_ERROR,PROP_ID(rgPropTags.aulPropTag[CMESGPROPS-1]));
        lpMesgProps[CMESGPROPS-1].Value.err = MAPI_E_NOT_FOUND;
    }

    //
    // Duplicate message properties
    //

    cValues = CMESGPROPS;
    lpProps = NULL;

    sc = ScDupPropset(
        cValues,
        lpMesgProps,
MAPIAllocateBuffer,
&lpProps);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Copy recipient address list
    //

    hr = HrMAPICreateSizedAddressList(
        cRecipAddr,
        &lpRecipList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0, j = (cMesgAddr + cReplyAddr); i < cRecipAddr; i++, j++)
    {
        lpRecipList->aEntries[i] = lpAdrList->aEntries[j];
    }

    //
    // Copy original recipient information
    //

    for(i = (cMesgAddr + cReplyAddr + cRecipAddr); i < lpAdrList->cEntries; i++)
    {
        j = lpAdrList->aEntries[i].rgPropVals[IRECIPNUM].Value.ul;

        lpRecipList->aEntries[j].rgPropVals[IORIGDISP] =
            lpAdrList->aEntries[i].rgPropVals[IDISPNAME];
        lpRecipList->aEntries[j].rgPropVals[IORIGDISP].ulPropTag  =
            PR_ORIGINAL_DISPLAY_NAME;

        lpRecipList->aEntries[j].rgPropVals[IORIGENTRY] =
            lpAdrList->aEntries[i].rgPropVals[IENTRYID];
        lpRecipList->aEntries[j].rgPropVals[IORIGENTRY].ulPropTag =
            PR_ORIGINAL_ENTRYID;
    }

    //
    // Duplicate updated address list
    //

    hr = HrMAPICreateSizedAddressList(
        lpRecipList->cEntries,
        lppAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < lpRecipList->cEntries; i++)
    {
        if(lpRecipList->aEntries[i].cValues < MIN_CRECIPPROPS)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Unregistered named properties cannot be set on the recipient list.
        //

        lpRecipList->aEntries[i].rgPropVals[IPROXYADDR].ulPropTag = PR_NULL;
        lpRecipList->aEntries[i].rgPropVals[IPROXYADDR].Value.ul  = 0;

        hr = HrMAPISetAddressList(
            i,
            lpRecipList->aEntries[i].cValues,
            lpRecipList->aEntries[i].rgPropVals,
            *lppAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    *lpcValues  = cValues;
    *lppProps   = lpProps;

cleanup:

    if(FAILED(hr))
    {
        *lpcValues = 0;

        if(lppProps != NULL)
        {
            MAPIFREEBUFFER(*lppProps);
        }

        if(lppAdrList != NULL)
        {
            FREEPADRLIST(*lppAdrList);
        }
    }

    MAPIFREEBUFFER(lpMesgProps);

    MAPIFREEBUFFER(lppDispNames);

    MAPIFREEBUFFER(lppEntryIDs);

    MAPIFREEBUFFER(lpRecipList);

    RETURN(hr);
}

//$--HrMapProxyToAddr-----------------------------------------------------------
// Map all proxy addresses to email addresses in a given address list. Always
// call HrMapAddrToProxy() before calling this function.
// -----------------------------------------------------------------------------
HRESULT HrMapProxyToAddr(               // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPABCONT     lpGalABCont,       // pointer to GAL container
    IN OUT LPADRLIST lpAdrList)         // pointer to address list
{
    HRESULT         hr                   = NOERROR;
    HRESULT         hrT                  = NOERROR;
    SCODE           sc                   = 0;
    ULONG           i                    = 0;

    SPropValue      prop[2]              = {0};
    LPFlagList      lpFlagList           = NULL;
    LPADRLIST       lpRecipList          = NULL;

    LPTSTR          lpszEmailDisplayName = NULL;
    LPTSTR          lpszEmailAddressType = NULL;
    LPTSTR          lpszEmailAddress     = NULL;
    LPTSTR          lpszEmailSearchKey   = NULL;
    ULONG           cbEmailEid           = 0;
    LPENTRYID       lpEmailEid           = NULL;

    SizedSPropTagArray(MIN_CRECIPPROPS, rgPropTags) =
    {
        MIN_CRECIPPROPS,
        {
            PR_DISPLAY_NAME,
            PR_ADDRTYPE,
            PR_EMAIL_ADDRESS,
            PR_SEARCH_KEY,
            PR_ENTRYID,
            PR_SEND_RICH_INFO,
            PR_RESPONSIBILITY,
            PR_RECIPIENT_NUMBER,
            PR_ORIGINAL_DISPLAY_NAME,
            PR_ORIGINAL_ENTRYID,
            PR_RECIPSTAT
        }
    };

    DEBUGPUBLIC("HrMapProxyToAddr()\n");

    sc = MAPIAllocateBuffer( CbNewFlagList(lpAdrList->cEntries), (LPVOID*)&lpFlagList);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lpFlagList->cFlags = lpAdrList->cEntries;

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        lpFlagList->ulFlag[i] = MAPI_UNRESOLVED;
    }

    hr = HrMAPICreateSizedAddressList(lpAdrList->cEntries, &lpRecipList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        if((lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].ulPropTag != PR_SEARCH_KEY) ||
           (lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.cb == 0) ||
           (lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.lpb == NULL))
        {
            prop[0].ulPropTag  = PR_DISPLAY_NAME;
            prop[0].Value.LPSZ = TEXT("UNKNOWN:UNKNOWN");
        }
        else
        {
            prop[0].ulPropTag  = PR_DISPLAY_NAME;
            prop[0].Value.LPSZ =
                (LPTSTR)lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.lpb;
        }

        prop[1].ulPropTag = PR_RECIPIENT_TYPE;
        prop[1].Value.ul = MAPI_TO;

        hr = HrMAPISetAddressList(
            i,
            2,
            prop,
            lpRecipList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = MAPICALL(lpGalABCont)->ResolveNames(
        lpGalABCont,
        (LPSPropTagArray)&rgPropTags,
        EMS_AB_ADDRESS_LOOKUP,
        lpRecipList,
        lpFlagList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    for(i = 0; i < lpRecipList->cEntries; i++)
    {
        if(lpFlagList->ulFlag[i] != MAPI_RESOLVED)
        {
            if(lpFlagList->ulFlag[i] == MAPI_AMBIGUOUS)
            {
                hrT = MAPI_E_AMBIGUOUS_RECIP;
            }
            else
            {
                hrT = NOERROR; // MAPI_E_NOT_FOUND
            }
        }
        else
        {
            //
            // Update recipient entry only if an email address was found
            //

            lpszEmailDisplayName = NULL;
            lpszEmailAddressType = NULL;
            lpszEmailAddress     = NULL;
            lpszEmailSearchKey   = NULL;
            cbEmailEid           = 0;
            lpEmailEid           = NULL;

            hr = HrCreateEmailAddress(
                lpAdrBook,
                lpAdrList->aEntries[i].rgPropVals,
                lpRecipList->aEntries[i].rgPropVals[IDISPNAME].Value.LPSZ,
                lpRecipList->aEntries[i].rgPropVals[IADDRTYPE].Value.LPSZ,
                lpRecipList->aEntries[i].rgPropVals[IEMAILADDR].Value.LPSZ,
                lpRecipList->aEntries[i].rgPropVals[IENTRYID].Value.bin.cb,
                (LPENTRYID)lpRecipList->aEntries[i].rgPropVals[IENTRYID].Value.bin.lpb,
                &lpszEmailDisplayName,
                &lpszEmailAddressType,
                &lpszEmailAddress,
                &lpszEmailSearchKey,
                &cbEmailEid,
                &lpEmailEid);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            lpAdrList->aEntries[i].rgPropVals[IDISPNAME].Value.LPSZ  =
                lpszEmailDisplayName;
            lpAdrList->aEntries[i].rgPropVals[IADDRTYPE].Value.LPSZ  =
                lpszEmailAddressType;
            lpAdrList->aEntries[i].rgPropVals[IEMAILADDR].Value.LPSZ =
                lpszEmailAddress;

            if(lpszEmailSearchKey == NULL)
            {
                lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.cb = 0;
            }
            else
            {
                lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.cb =
                    cbStrLen(lpszEmailSearchKey);
            }

            lpAdrList->aEntries[i].rgPropVals[ISEARCHKEY].Value.bin.lpb =
                (LPBYTE)lpszEmailSearchKey;
            lpAdrList->aEntries[i].rgPropVals[IENTRYID].Value.bin.cb  =
                cbEmailEid;
            lpAdrList->aEntries[i].rgPropVals[IENTRYID].Value.bin.lpb =
                (LPBYTE)lpEmailEid;

            hrT = NOERROR;
        }

        //
        // Add recipient status to recipient entry in address list
        //

        lpAdrList->aEntries[i].rgPropVals[IRECIPSTAT].ulPropTag =
            PR_RECIPSTAT;
        lpAdrList->aEntries[i].rgPropVals[IRECIPSTAT].Value.ul = hrT;
    }

cleanup:

    MAPIFREEBUFFER(lpFlagList);

    FREEPADRLIST(lpRecipList);

    RETURN(hr);
}

//$--HrNonDeliverRecipient------------------------------------------------------
// Non-deliver a recipient.
// -----------------------------------------------------------------------------
HRESULT HrNonDeliverRecipient(          // RETURNS: return code
    IN ULONG ulReasonCode,              // NDR reason code
    IN ULONG ulDiagnosticCode,          // NDR diagnostic code
    IN LPFILETIME lpftReportTime,       // report time
    IN ULONG ulRecipientNumber,         // recipient number
    IN ULONG ulOffset,                  // offset in NDR address list
    IN ULONG cValues,                   // count of property values
    IN LPSPropValue lpProps,            // pointer to property values
    IN OUT LPADRLIST lpAdrList)         // pointer to NDR address list
{
    HRESULT      hr              = NOERROR;
    ULONG        i               = 0;
    ULONG        j               = 0;
    ULONG        ulExtraProps[4] = {0};
    LPSPropValue lpExtraProps[4] = {NULL};
    SPropValue   ExtraProps[5]   = {0};
    ULONG        cNewValues      = 0;
    LPSPropValue lpNewProps      = NULL;
    LPSPropValue lpPropT         = NULL;

    DEBUGPUBLIC("HrNonDeliverRecipient()\n");

    lpPropT = LpValFindProp(PR_NDR_REASON_CODE, cValues, lpProps);

    if(lpPropT != NULL)
    {
        ulExtraProps[j  ]  = lpPropT->ulPropTag;
        lpExtraProps[j++]  = lpPropT;
        lpPropT->ulPropTag = PR_NULL;
    }

    ExtraProps[i].ulPropTag = PR_NDR_REASON_CODE;
    ExtraProps[i].Value.ul  = ulReasonCode;
    i++;

    lpPropT = LpValFindProp(PR_NDR_DIAG_CODE, cValues, lpProps);

    if(lpPropT != NULL)
    {
        ulExtraProps[j  ]  = lpPropT->ulPropTag;
        lpExtraProps[j++]  = lpPropT;
        lpPropT->ulPropTag = PR_NULL;
    }

    ExtraProps[i].ulPropTag = PR_NDR_DIAG_CODE;
    ExtraProps[i].Value.ul  = ulDiagnosticCode;
    i++;

    lpPropT = LpValFindProp(PR_REPORT_TIME, cValues, lpProps);

    if(lpPropT != NULL)
    {
        ulExtraProps[j  ]  = lpPropT->ulPropTag;
        lpExtraProps[j++]  = lpPropT;
        lpPropT->ulPropTag = PR_NULL;
    }

    ExtraProps[i].ulPropTag = PR_REPORT_TIME;
    ExtraProps[i].Value.ft  = *lpftReportTime;
    i++;

    lpPropT = LpValFindProp(PR_REPORT_TEXT, cValues, lpProps);

    if(lpPropT != NULL)
    {
        ulExtraProps[j  ]  = lpPropT->ulPropTag;
        lpExtraProps[j++]  = lpPropT;
        lpPropT->ulPropTag = PR_NULL;
    }

    ExtraProps[i].ulPropTag  = PR_REPORT_TEXT;
    ExtraProps[i].Value.LPSZ = TEXT("");
    i++;

    lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

    if(lpPropT == NULL)
    {
        ExtraProps[i].ulPropTag = PR_RECIPIENT_NUMBER;
        ExtraProps[i].Value.ul  = ulRecipientNumber;
        i++;
    }

    hr = HrMAPIAppendSPropValues(
        cValues,
        lpProps,
        i,
        ExtraProps,
        &cNewValues,
        &lpNewProps);

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpNewProps);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetAddressList(
        ulOffset,
        cNewValues,
        lpNewProps,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpNewProps);

    for(i = 0; i < j; i++)
    {
        lpPropT = lpExtraProps[i];
        lpPropT->ulPropTag = ulExtraProps[i];
    }

    RETURN(hr);
}


GWSAMPLE.C

// --gwsample.c-----------------------------------------------------------------
//
//  Sample gateway application.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwmain.h"
#include "gwsample.h"
#include "attrname.h"
#include "convcwrp.h"
#include "ipmconv.h"
#include "sgwdata.h"

TCHAR szAppName[]       = TEXT("GWSAMPLE");
TCHAR szWindowTitle[]   = TEXT("Sample Gateway");

// Output/Input file name prefix.
TCHAR szFilePrefix[]    = TEXT("SGW");

#define C_EQU   TEXT("C=")
#define A_EQU   TEXT("A=")
#define P_EQU   TEXT("P=")

static TCHAR szServiceName[MAX_SERVICE_NAME_LENGTH+1] = {0};

static LPTSTR           lpszLocalGDI    = NULL;
static CRITICAL_SECTION csGatewaySeqNo  = {0};
static ULONG            ulGatewaySeqNo  = 0;

static LPMAPISESSION    lpSession       = NULL;
static LPMDB            lpStore         = NULL;
static LPMAPIFOLDER     lpRootFolder    = NULL;
static LPMAPIFOLDER     lpMtsInFolder   = NULL;
static LPMAPIFOLDER     lpMtsOutFolder  = NULL;
static ULONG            cbGalEid        = 0;
static LPENTRYID        lpGalEid        = NULL;

//performance code

LONG    cInThreads           = 0;
ULONG   cInMessages          = 0;

LPTSTR  lpszAddressType      = NULL;

LPTSTR  lpszSysAdminName     = NULL;
LPTSTR  lpszSysAdminAddr     = NULL;

LPTSTR  lpszInDirectory      = NULL;
LPTSTR  lpszInConverter      = NULL;
LPWSTR  lpszInConverterW     = NULL;

BOOL    fInEnabled           = FALSE;

ULONG   cbBufferSize         = 0;
LPBYTE  lpbBuffer            = NULL;
ULONG   cBufferArraySize     = 0;
LPTSTR  *lppszBufferArray    = NULL;

DWORD   dwInSleepInterval    = 0;
DWORD   dwInPollInterval     = 0;

LONG    cOutThreads          = 0;
ULONG   cOutMessages         = 0;

LPTSTR  lpszOutDirectory     = NULL;
LPTSTR  lpszOutConverter     = NULL;
LPWSTR  lpszOutConverterW    = NULL;

BOOL    fOutEnabled          = FALSE;

DWORD   dwOutSleepInterval   = 0;
DWORD   dwOutPollInterval    = 0;

LPTSTR  lpszArchiveDirectory = NULL;

HANDLE  hEventLog            = NULL;

typedef struct _id_item
{
    CRITICAL_SECTION *lpcs;     // only used by list
    LPTSTR           lpszId;
    struct _id_item  *next;
} IDITEM, *LPIDITEM;

static CRITICAL_SECTION csInboundIdList  = {0};
static CRITICAL_SECTION csOutboundIdList = {0};

static IDITEM InboundIdList  = {0};
static IDITEM OutboundIdList = {0};

// Size is because country 2-3, ADMD 0-16, PRMD is 1-16 and LocalId1-32 plus
// size for "/c=/p=/a=/l=". So 3+16+16+32+12+1 = 80.
#define cbMTSIDMax (80)

//$--HrCreateIdItem-------------------------------------------------------------
//  Creates an ID item.
// -----------------------------------------------------------------------------
HRESULT HrCreateIdItem(                 // RETURNS: return code
    IN  LPSTR lpszId,                   // identifier
    OUT LPIDITEM *lppIdItem)            // identifier item
{
HRESULT  hr       = NOERROR;
    LPIDITEM lpIdItem = NULL;
    ULONG    cBytes   = 0;

    *lppIdItem = NULL;

    cBytes = sizeof(IDITEM);

    hr = MAPIAllocateBuffer(cBytes, (void **)&lpIdItem);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    ZeroMemory(lpIdItem, cBytes);

    cBytes  = cbStrLen(lpszId);

    hr = MAPIAllocateMore(cBytes, lpIdItem, (void **)&lpIdItem->lpszId);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lstrcpy(lpIdItem->lpszId, lpszId);

    *lppIdItem = lpIdItem;

cleanup:

    RETURN(hr);
}

//$--HrDestroyIdItem------------------------------------------------------------
//  Destroys an ID item.
// -----------------------------------------------------------------------------
HRESULT HrDestroyIdItem(                // RETURNS: return code
    IN OUT LPIDITEM *lppIdItem)         // identifier item
{
HRESULT hr = NOERROR;

    MAPIFREEBUFFER(*lppIdItem);

    RETURN(hr);
}

//$--HrLookupIdItem-------------------------------------------------------------
//  Looks up an ID item.
// -----------------------------------------------------------------------------
HRESULT HrLookupIdItem(                 // RETURNS: return code
    IN LPIDITEM lpIdList,               // identifier item list
    IN LPTSTR lpszId,                   // identifier
    IN OUT LPIDITEM *lppIdItem)         // identifier item
{
HRESULT  hr       = EDK_E_NOT_FOUND;
    HRESULT  hrT      = NOERROR;
    LPIDITEM lpIdItem = NULL;

    __try
    {
        *lppIdItem = NULL;

        lpIdItem = lpIdList->next;

        while(lpIdItem != NULL)
        {
            if(!lstrcmpi(lpIdItem->lpszId, lpszId))
            {
                //
                // Found identifier in ID list
                //

                *lppIdItem = lpIdItem;

                hr = NOERROR;
                goto cleanup;
            }

            lpIdItem = lpIdItem->next;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

cleanup:

    RETURN(hr);
}

//$--HrAddIdItem----------------------------------------------------------------
//  Adds an ID item to a list.
// -----------------------------------------------------------------------------
HRESULT HrAddIdItem(                    // RETURNS: return code
    IN LPIDITEM lpIdList,               // identifier item list
    IN LPTSTR lpszId,                   // identifier
    IN OUT LPIDITEM *lppIdItem)         // identifier item
{
HRESULT  hr       = EDK_E_NOT_FOUND;
    HRESULT  hrT      = NOERROR;
    LPIDITEM lpIdItem = NULL;

    EnterCriticalSection(lpIdList->lpcs);

    hr = HrLookupIdItem(lpIdList, lpszId, &lpIdItem);

    if(hr != EDK_E_NOT_FOUND)
    {
        hr = HR_LOG(EDK_E_ALREADY_EXISTS);
        goto cleanup;
    }

    __try
    {
        *lppIdItem = NULL;

        hr = HrCreateIdItem(
            lpszId,
            &lpIdItem);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        lpIdItem->next = lpIdList->next;

        lpIdList->next = lpIdItem;

        *lppIdItem = lpIdItem;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

cleanup:

    LeaveCriticalSection(lpIdList->lpcs);

    RETURN(hr);
}

//$--HrRemoveIdItem-------------------------------------------------------------
//  Removes an ID item from a list.
// -----------------------------------------------------------------------------
HRESULT HrRemoveIdItem(                 // RETURNS: return code
    IN LPIDITEM lpIdList,               // identifier item list
    IN LPIDITEM lpIdItem)               // identifier item
{
HRESULT  hr           = EDK_E_NOT_FOUND;
    HRESULT  hrT          = NOERROR;
    LPIDITEM lpPrevIdItem = NULL;
    LPIDITEM lpCurrIdItem = NULL;

    EnterCriticalSection(lpIdList->lpcs);

    __try
    {
        lpCurrIdItem = lpIdList->next;

        while(lpCurrIdItem != NULL)
        {
            if(lpCurrIdItem == lpIdItem)
            {
                //
                // Found identifier item in ID list
                //

                if(lpIdList->next == lpIdItem)
                {
                    // item found at head of list
                    lpIdList->next = lpIdItem->next;
                }
                else
                {
                    lpPrevIdItem->next = lpIdItem->next;
                }

                hr = NOERROR;
                goto cleanup;
            }

            lpPrevIdItem = lpCurrIdItem;
            lpCurrIdItem = lpCurrIdItem->next;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

cleanup:

    hrT = HrDestroyIdItem(&lpIdItem);

    if(FAILED(hr))
    {
        hrT = HR_LOG(E_FAIL);
    }

    LeaveCriticalSection(lpIdList->lpcs);

    RETURN(hr);
}

//$--HrEmptyIdList--------------------------------------------------------------
//  Empty an ID list.
// -----------------------------------------------------------------------------
HRESULT HrEmptyIdList(                  // RETURNS: return code
    IN LPIDITEM lpIdList)               // identifier item list
{
HRESULT  hr       = NOERROR;
    HRESULT  hrT      = NOERROR;
    LPIDITEM lpIdItem = NULL;

    EnterCriticalSection(lpIdList->lpcs);

    __try
    {
        lpIdItem = lpIdList->next;

        while(lpIdItem != NULL)
        {
            lpIdList->next = lpIdItem->next;

            MAPIFREEBUFFER(lpIdItem);

            lpIdItem = lpIdList->next;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    LeaveCriticalSection(lpIdList->lpcs);

    RETURN(hr);
}

//$--HrCreateMtsId--------------------------------------------------------------
//  Returns the newly created stringized MTS identifier. It takes in a site
//  MTS identifier string that contains the country, ADMD and PRMD code and
//  creates the local identifier to create a new MTS identifier.
// -----------------------------------------------------------------------------
HRESULT HrCreateMtsId(                  // RETURNS: return code
    IN  LPSTR lpszSiteMtsId,            // site MTS identifier
    IN  LPSTR lpszGatewayName,          // gateway name
    IN  ULONG ulSeqNo,                  // sequence number
    OUT LPSTR lpszMTSID)                // MTS identifier
{
HRESULT    hr                     = NOERROR;
    SYSTEMTIME st                     = {0};
CHAR       rgchUTC[cbMTSIDMax]    = {0};
ULONG      cbMTName               = 0;
LPSTR      lpszMTName[cbMTSIDMax] = {0};
ULONG      cbSeqNo                = 0;
CHAR       rgchSeqNo[9]           = {0};

// Get the current time and make it a UTCTime string
    GetSystemTime(&st);
    sprintf(rgchUTC, "%04u%02u%02u%02u%02u%02uZ",
        st.wYear,
        st.wMonth,
        st.wDay,
        st.wHour,
        st.wMinute,
        st.wSecond);

// format the ulSeqNo into a string and get's length
sprintf((LPSTR)rgchSeqNo, "%08lx", ulSeqNo);
cbSeqNo = strlen(rgchSeqNo);

// find out how much of the gateway name can be put into the local identifer
cbMTName = 32 - cbSeqNo - strlen(rgchUTC) - 2;

lstrcpy((LPSTR)lpszMTName, lpszGatewayName);

if(cbMTName < (ULONG)lstrlen((LPSTR)lpszMTName))
    {
lpszMTName[cbMTName] = 0;
}

    //
// Format of local identifier is:
//     "Gateway Name-stringized UTC Time-stringized ulSeqNo"
// NOTE: DO NOT CHANGE Delimiter to "/".
    //

sprintf(
    lpszMTSID,
    "%s;L=%s-%s-%s",
    lpszSiteMtsId,
    lpszMTName,
    rgchUTC,
    rgchSeqNo);

RETURN(hr);
}

//$--DumpPropProblems-----------------------------------------------------------
//  Dumps property problems to the debug log.
//------------------------------------------------------------------------------
VOID DumpPropProblems(                  // RETURNS: nothing
    IN LPSTR lpszFile,                  // source file name
    IN ULONG ulLine,                    // source line number
    IN LPSPropProblemArray lpProblems)  // property problems
{
    ULONG i = 0;

    DEBUGPUBLIC("DumpPropProblems()\n");

    if(lpProblems == NULL)
    {
        goto cleanup;
    }

#ifdef DEBUG
    for(i = 0; i < lpProblems->cProblem; i++)
    {
        _Assert("PROBLEM", lpszFile, ulLine, D_WARNING, 0, "%lu\t%08lx\t%08lx\n",
            lpProblems->aProblem[i].ulIndex,
            lpProblems->aProblem[i].ulPropTag,
            lpProblems->aProblem[i].scode);
    }
#endif

cleanup:

    return;
}

#ifdef DEBUG
#define DUMPPROPPROBLEMS(_prob) DumpPropProblems(__FILE__,__LINE__,(_prob))
#else
#define DUMPPROPPROBLEMS(_prob) ((void)0)
#endif

//$--HrCreateEnterpriseGDI------------------------------------------------------
//  Create the enterprise GDI from the input string.
// -----------------------------------------------------------------------------
HRESULT HrCreateEnterpriseGDI(          // RETURNS: return code
    IN LPTSTR lpsz,                     // pointer to input string
    IN LPTSTR lpszEOT,                  // pointer to separator character(s)
    OUT LPTSTR *lppszGDI)               // pointer to enterprise GDI
{
    HRESULT hr          = NOERROR;
    SCODE   sc          = 0;
    ULONG   cBytes      = 0;
    ULONG   cArraySize  = 0;
    LPTSTR  *lppszArray = NULL;
    LPTSTR  lpszCountry = NULL;
    LPTSTR  lpszAdmd    = NULL;
    LPTSTR  lpszPrmd    = NULL;
    ULONG   i           = 0;

    DEBUGPUBLIC("HrCreateEnterpriseGDI()\n");

    *lppszGDI = NULL;

    hr = HrStrTokAll(
        lpsz,
        lpszEOT,
        &cArraySize,
        &lppszArray);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < cArraySize; i++)
    {
        if(lppszArray[i] == NULL)
        {
            ;
        }
        else if(!_tcsnicmp(lppszArray[i], C_EQU, lstrlen(C_EQU)))
        {
            lpszCountry = lppszArray[i];
        }
        else if(!_tcsnicmp(lppszArray[i], A_EQU, lstrlen(A_EQU)))
        {
            lpszAdmd = lppszArray[i];
        }
        else if(!_tcsnicmp(lppszArray[i], P_EQU, lstrlen(P_EQU)))
        {
            lpszPrmd = lppszArray[i];
        }
    }

    if(lpszCountry == NULL)
    {
        lpszCountry = TEXT("C=;");
    }

    if(lpszAdmd == NULL)
    {
        lpszAdmd = TEXT("A=;");
    }

    if(lpszPrmd == NULL)
    {
        lpszPrmd = TEXT("P=;");
    }

    cBytes = (lstrlen(TEXT(";;;")) +
              lstrlen(lpszCountry) +
              lstrlen(lpszAdmd) +
              lstrlen(lpszPrmd) + 1) * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(cBytes, (void **)lppszGDI);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(*lppszGDI, lpszCountry);
    lstrcat(*lppszGDI, TEXT(";"));
    lstrcat(*lppszGDI, lpszAdmd);
    lstrcat(*lppszGDI, TEXT(";"));
    lstrcat(*lppszGDI, lpszPrmd);
    lstrcat(*lppszGDI, TEXT(";"));

    CharUpperBuff(*lppszGDI, cBytes/sizeof(TCHAR));

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszGDI);
    }

    MAPIFREEBUFFER(lppszArray);

    RETURN(hr);
}

//$--HrGetMtsIdEnterpriseGDI----------------------------------------------------
//  Get the enterprise GDI from the MTS-ID (or SUBJECT-MTS-ID).
// -----------------------------------------------------------------------------
HRESULT HrGetMtsIdEnterpriseGDI(        // RETURNS: return code
    IN LPMESSAGE lpMessage,             // pointer to message
    IN ULONG ulPropTag,                 // MTS-ID property tag
    OUT LPTSTR *lppszGDI)               // pointer to enterprise GDI
{
    HRESULT hr          = NOERROR;
    ULONG   cBytes      = 0;
    LPTSTR  lpszMtsId   = NULL;

    DEBUGPUBLIC("HrGetMtsIdEnterpriseGDI()\n");

    *lppszGDI = NULL;

    hr = HrMAPIGetPropBinary(
        (LPMAPIPROP)lpMessage,
        ulPropTag,
        &cBytes,
        (LPVOID *)&lpszMtsId);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = HrCreateEnterpriseGDI(
        lpszMtsId,
        TEXT("/;"),
        lppszGDI);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszGDI);
    }

    MAPIFREEBUFFER(lpszMtsId);

    RETURN(hr);
}

//$--HrGetTraceEnterpriseGDI----------------------------------------------------
//  Get the enterprise GDI from the external trace information.
// -----------------------------------------------------------------------------
HRESULT HrGetTraceEnterpriseGDI(        // RETURNS: return code
    IN LPMESSAGE lpMessage,             // pointer to message
    IN ULONG ulPropTag,                 // external trace info property tag
    OUT LPTSTR *lppszGDI)               // pointer to enterprise GDI
{
    HRESULT      hr           = NOERROR;
    SCODE        sc           = 0;
    ULONG        cBytes       = 0;
    LPTRACEINFO  lpTraceInfo  = NULL;
    LPTRACEENTRY lpTraceEntry = NULL;
    FILETIME     ft           = {0};
    LPTSTR       lpszT        = NULL;
    LPTSTR       lpszCountry  = NULL;
    LPTSTR       lpszAdmd     = NULL;
    LPTSTR       lpszPrmd     = NULL;
    ULONG        i            = 0;

    DEBUGPUBLIC("HrGetTraceEnterpriseGDI()\n");

    *lppszGDI = NULL;

    hr = HrMAPIGetPropBinary(
        (LPMAPIPROP)lpMessage,
        ulPropTag,
        &cBytes,
        (LPVOID *)&lpTraceInfo);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = HrTraceGotoFirstEntry(
        lpTraceInfo,
        &i,
        &lpTraceEntry);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrTraceOpenEntry(
        lpTraceEntry,
        &i,
        &ft,
        &ft,
        &lpszAdmd,
        &lpszCountry,
        &lpszPrmd,
        &lpszT,
        &lpszT,
        &lpszT);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(lpszCountry == NULL)
    {
        lpszCountry = TEXT("C=;");
    }

    if(lpszAdmd == NULL)
    {
        lpszAdmd = TEXT("A=;");
    }

    if(lpszPrmd == NULL)
    {
        lpszPrmd = TEXT("P=;");
    }

    cBytes = (lstrlen(TEXT("C=;A=;P=;")) +
              lstrlen(lpszCountry) +
              lstrlen(lpszAdmd) +
              lstrlen(lpszPrmd) + 1) * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(cBytes, (void **)lppszGDI);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(*lppszGDI, C_EQU);
    lstrcat(*lppszGDI, lpszCountry);
    lstrcat(*lppszGDI, TEXT(";A="));
    lstrcat(*lppszGDI, lpszAdmd);
    lstrcat(*lppszGDI, TEXT(";P="));
    lstrcat(*lppszGDI, lpszPrmd);
    lstrcat(*lppszGDI, TEXT(";"));

    CharUpperBuff(*lppszGDI, cBytes/sizeof(TCHAR));

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszGDI);
    }

    MAPIFREEBUFFER(lpTraceInfo);

    RETURN(hr);
}

//$--HrFixUpRecipientNumbers----------------------------------------------------
//  Fix up recipient numbers.
// -----------------------------------------------------------------------------
static HRESULT HrFixUpRecipientNumbers( // RETURNS: return code
    IN OUT LPADRLIST lpEnvelopeAdrList, // pointer to envelope address list
    IN OUT LPADRLIST lpMessageAdrList)  // pointer to message address list
{
    HRESULT      hr                = NOERROR;
    HRESULT      hrT               = NOERROR;
    SCODE        sc                = 0;
    ULONG        cb                = 0;

    ULONG*       rgulOrigRecipNums = NULL;

    LPSPropValue lpPropT           = NULL;

    ULONG        i                 = 0;
    ULONG        j                 = 0;

    ULONG        cValues           = 0;
    LPSPropValue lpProps           = NULL;

    DEBUGPRIVATE("HrFixUpRecipientNumbers()\n");

    if( (!((lpMessageAdrList == NULL) || (lpMessageAdrList->cEntries == 0))) &&
        (!((lpEnvelopeAdrList == NULL) || (lpEnvelopeAdrList->cEntries == 0))))
    {
        cb = lpMessageAdrList->cEntries * (sizeof(ULONG) + MAX_ALIGN);

        sc = MAPIAllocateBuffer(cb, (void **)&rgulOrigRecipNums);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        for(i = 0; i < lpMessageAdrList->cEntries; i++)
        {
            rgulOrigRecipNums[i] = 0;

            cValues = lpMessageAdrList->aEntries[i].cValues;
            lpProps = lpMessageAdrList->aEntries[i].rgPropVals;

            if(lpProps != NULL)
            {
                lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

                if(lpPropT == NULL)
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                rgulOrigRecipNums[i] = lpPropT->Value.ul;

                lpPropT->ulPropTag =
                    PROP_TAG(PT_ERROR, PROP_ID(lpPropT->ulPropTag));
            }
        }

        for(i = 0; i < lpEnvelopeAdrList->cEntries; i++)
        {
            cValues = lpEnvelopeAdrList->aEntries[i].cValues;
            lpProps = lpEnvelopeAdrList->aEntries[i].rgPropVals;

            if(lpProps != NULL)
            {
                lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

                if(lpPropT == NULL)
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                for(j = 0; j < lpMessageAdrList->cEntries; j++)
                {
                    if(rgulOrigRecipNums[j] == lpPropT->Value.ul)
                    {
                        lpPropT->Value.ul = j+1;
                    }
                }
            }
        }

        //
        // Clear recipient numbers for message address list.
        //

        hr = HrDeleteAddressListProperty(
            PR_RECIPIENT_NUMBER,
            (LPADRLIST)lpMessageAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    //
    // Clear row IDs for envelope address list.
    //

    if(!((lpEnvelopeAdrList == NULL) || (lpEnvelopeAdrList->cEntries == 0)))
    {
        hr = HrDeleteAddressListProperty(
            PR_ROWID,
            (LPADRLIST)lpEnvelopeAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    //
    // Clear row IDs for message address list.
    //

    if(!((lpMessageAdrList == NULL) || (lpMessageAdrList->cEntries == 0)))
    {
        hr = HrDeleteAddressListProperty(
            PR_ROWID,
            (LPADRLIST)lpMessageAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

cleanup:

    MAPIFREEBUFFER(rgulOrigRecipNums);

    RETURN(hr);
}

//$--HrDeleteSenderProps--------------------------------------------------------
// Delete sender properties from the message.
// -----------------------------------------------------------------------------
HRESULT HrDeleteSenderProps(            // RETURNS: return code
    IN LPMESSAGE  lpMessage)            // pointer to message
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    LPSPropProblemArray lpProblems = NULL;

    SizedSPropTagArray(10, rgPropTags) =
    {
        10,
        {
            PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_SEARCH_KEY,
            PR_SENDER_ENTRYID,
            PR_SENT_REPRESENTING_NAME,
            PR_SENT_REPRESENTING_ADDRTYPE,
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_SENT_REPRESENTING_SEARCH_KEY,
            PR_SENT_REPRESENTING_ENTRYID,
        }
    };

    DEBUGPUBLIC("HrDeleteSenderProps()\n");

    hrT = MAPICALL(lpMessage)->DeleteProps(
        lpMessage,
        (SPropTagArray *)&rgPropTags,
        &lpProblems);

    if(FAILED(hrT))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);

cleanup:

    MAPIFREEBUFFER(lpProblems);

    RETURN(hr);
}

//$--HrDeleteMtsIdProps---------------------------------------------------------
// Delete MTS-ID properties from the message.
// -----------------------------------------------------------------------------
HRESULT HrDeleteMtsIdProps(             // RETURNS: return code
    IN LPMESSAGE  lpMessage)            // pointer to message
{
    HRESULT hr = NOERROR;

    LPSPropProblemArray lpProblems = NULL;

    SizedSPropTagArray(1, rgPropTags) =
    {
        1,
        {
            PR_MTS_ID,
        }
    };

    DEBUGPUBLIC("HrDeleteMtsIdProps()\n");

    hr = MAPICALL(lpMessage)->DeleteProps(
        lpMessage,
        (SPropTagArray *)&rgPropTags,
        &lpProblems);

    if(FAILED(hr))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);

cleanup:

    MAPIFREEBUFFER(lpProblems);

    RETURN(hr);
}

//$--HrDeleteTraceProps---------------------------------------------------------
// Delete trace properties from the message.
// -----------------------------------------------------------------------------
HRESULT HrDeleteTraceProps(                  // RETURNS: return code
    IN LPMESSAGE  lpMessage)            // pointer to message
{
    HRESULT hr  = NOERROR;

    LPSPropProblemArray lpProblems = NULL;

    SizedSPropTagArray(1, rgPropTags) =
    {
        1,
        {
            PR_TRACE_INFO,
        }
    };

    DEBUGPUBLIC("HrDeleteTraceProps()\n");

    hr = MAPICALL(lpMessage)->DeleteProps(
        lpMessage,
        (SPropTagArray *)&rgPropTags,
        &lpProblems);

    if(FAILED(hr))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);

cleanup:

    MAPIFREEBUFFER(lpProblems);

    RETURN(hr);
}

//$--HrNonDeliverAllRecipList---------------------------------------------------
//  Create a recipient list to non-deliver all recipients.
// -----------------------------------------------------------------------------
HRESULT HrNonDeliverAllRecipList(       // RETURNS: return code
    IN LPMESSAGE lpMessage,             // message
    IN LPADRLIST *lppAdrList)           // address list
{
    HRESULT      hr            = NOERROR;
    HRESULT      hrT           = NOERROR;
    LPSRowSet    lpRows        = NULL;
    LPADRLIST    lpAdrList     = NULL;
    LPADRLIST    lpAllAdrList  = NULL;
    LPMAPITABLE  lpTable       = NULL;

    LPSPropValue lpPropT       = NULL;

    ULONG        cFailedValues = 0;
    LPSPropValue lpFailedProps = NULL;

    ULONG        i             = 0;

    DEBUGPUBLIC("HrNonDeliverAllRecipList()");

    *lppAdrList = NULL;

    //
    // Get the recipient table for the message
    //

    hr = MAPICALL(lpMessage)->GetRecipientTable(
        lpMessage,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Query all the rows
    //

    hr = HrQueryAllRows(
    lpTable,
    NULL,
    NULL,
    NULL,
    0,
    &lpRows);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    lpAdrList = (LPADRLIST)lpRows;

    hr = HrNDRCreateFailedRecipList(
        &lpAllAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        hr = HrNDRGetFailedRecipList(
            &lpAdrList->aEntries[i],
            0,
            NULL,
            &cFailedValues,
            &lpFailedProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cFailedValues, lpFailedProps);

if(lpPropT != NULL)
        {
            lpPropT->Value.ul = (i+1);
        }

        hr = HrNDRAddToFailedRecipList(
            cFailedValues,
            lpFailedProps,
            &lpAllAdrList);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        MAPIFREEBUFFER(lpFailedProps);
    }   // end for

    *lppAdrList  = lpAllAdrList;
    lpAllAdrList = NULL;

cleanup:

    MAPIFREEBUFFER(lpFailedProps);

    FREEPADRLIST(lpAdrList);        // also frees lpRows

    FREEPADRLIST(lpAllAdrList);

    ULRELEASE(lpTable);

    if(FAILED(hr))
    {
        FREEPADRLIST(lpAllAdrList);
        *lppAdrList = NULL;
    }

    RETURN(hr);
}

//$--HrCreateNonDeliveryReport--------------------------------------------------
//  Create a non-delivery report.
// -----------------------------------------------------------------------------
HRESULT HrCreateNonDeliveryReport(          // RETURNS: return code
    IN BOOL fCreateMtsId,                   // create MTS identifier
    IN LPADRBOOK lpAdrBook,                 // pointer to address book
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN LPADRLIST lpAdrList,                 // pointer to address list
    OUT LPMESSAGE *lppReport)               // pointer to report
{
    HRESULT      hr                   = NOERROR;
    LPMESSAGE    lpReport             = NULL;
    ULONG        cBytes               = 0;
    ULONG        cValues              = 0;
    LPSPropValue lpProps              = NULL;

    LPTSTR       lpszEmailDisplayName = NULL;
    LPTSTR       lpszEmailAddressType = NULL;
    LPTSTR       lpszEmailAddress     = NULL;
    LPTSTR       lpszEmailSearchKey   = NULL;
    ULONG        cbEmailEid           = 0;
    LPENTRYID    lpEmailEid           = NULL;

LPSPropProblemArray lpProblems    = NULL;

    CHAR         szGatewayName[MAX_SERVICE_NAME_LENGTH+1] = {0};

    ULONG        ulMtsIdSeqNo          = 0;
    CHAR         rgchMTSID[cbMTSIDMax] = {0};

    DEBUGPUBLIC("HrCreateNonDeliveryReport()\n");

    *lppReport = NULL;

    if(fCreateMtsId == TRUE)
    {
        EnterCriticalSection(&csGatewaySeqNo);
        ulMtsIdSeqNo = ++ulGatewaySeqNo;
        LeaveCriticalSection(&csGatewaySeqNo);

        //
        // Get gateway service name.
        //

        hr = HrServiceGetName(
            szGatewayName);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        hr = HrCreateMtsId(
            lpszLocalGDI,
            szGatewayName,
            ulMtsIdSeqNo,
            rgchMTSID);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    hr = HrNDRGetFailedProps(
        lpMessage,
        &cValues,
        &lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrNDRCreate(
        lpAdrBook,
        lpFolder,
        lpMessage,
        lpAdrList,
        cValues,
        lpProps,
        &lpReport);

    MAPIFREEBUFFER(lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cBytes = CbNewSPropValue(6);

    hr = MAPIAllocateBuffer(cBytes, (void **)&lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    ZeroMemory(lpProps, cBytes);

    hr = HrCreateProxyAddress(
        lpAdrBook,
        lpProps,
        TRUE,
        SYSTEM_ADMINISTRATOR,
        SYSTEM,
        SYSTEM_ADMINISTRATOR,
        &lpszEmailDisplayName,
        &lpszEmailAddressType,
        &lpszEmailAddress,
        &lpszEmailSearchKey,
        &cbEmailEid,
        &lpEmailEid);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    lpProps[0].ulPropTag         = PR_SENDER_NAME;
    lpProps[0].Value.LPSZ        = lpszEmailDisplayName;

    lpProps[1].ulPropTag         = PR_SENDER_ADDRTYPE;
    lpProps[1].Value.LPSZ        = lpszEmailAddressType;

    lpProps[2].ulPropTag         = PR_SENDER_EMAIL_ADDRESS;
    lpProps[2].Value.LPSZ        = lpszEmailAddress;

    lpProps[3].ulPropTag         = PR_SENDER_SEARCH_KEY;

    if(lpszEmailSearchKey == NULL)
    {
        lpProps[3].Value.bin.cb  = 0;
        lpProps[3].Value.bin.lpb = NULL;
    }
    else
    {
        lpProps[3].Value.bin.cb  = cbStrLen(lpszEmailSearchKey);
        lpProps[3].Value.bin.lpb = lpszEmailSearchKey;
    }

    lpProps[4].ulPropTag         = PR_SENDER_ENTRYID;
    lpProps[4].Value.bin.cb      = cbEmailEid;
    lpProps[4].Value.bin.lpb     = (LPBYTE)lpEmailEid;

    lpProps[5].ulPropTag         = PR_MTS_ID;
    lpProps[5].Value.bin.cb      = cbStrLen(rgchMTSID);
    lpProps[5].Value.bin.lpb     = (LPBYTE)rgchMTSID;

    if(fCreateMtsId == TRUE)
    {
        cValues = 6;
    }
    else
    {
        cValues = 5;
    }

    hr = MAPICALL(lpReport)->SetProps(
        lpReport,
        cValues,
        lpProps,
        &lpProblems);

    if(FAILED(hr))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);
    MAPIFREEBUFFER(lpProblems);

    // Save changes just made to report envelope
    hr = MAPICALL(lpReport)->SaveChanges(
        lpReport,
        KEEP_OPEN_READWRITE);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    *lppReport = lpReport;

cleanup:

    MAPIFREEBUFFER(lpProps);

    MAPIFREEBUFFER(lpProblems);

    RETURN(hr);
}

//$--HrImportMessage------------------------------------------------------------
//  Process an IPM/IPN in MTS-IN.
// -----------------------------------------------------------------------------
HRESULT HrImportMessage(                // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN LPABCONT lpGalABCont,            // pointer to GAL
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN BOOL fSendNDR,                   // TRUE if NDRs should be generated
    IN DWORD dwStartProcessingTime,     // start processing time
    IN OUT LPMESSAGE lpEnvelope)        // pointer to envelope
{
    HRESULT             hr                     = NOERROR;
    HRESULT             hrT                    = NOERROR;

    LPMESSAGE           lpMessage              = NULL;
    LPATTACH            lpAttach               = NULL;

    ULONG               cEnvelopeValues        = 0;
    LPSPropValue        lpEnvelopeProps        = NULL;
    ULONG               cMessageValues         = 0;
    LPSPropValue        lpMessageProps         = NULL;

    LPADRLIST           lpOriginalEnvelopeAdrList = NULL;
    LPADRLIST           lpOriginalMessageAdrList  = NULL;

    LPADRLIST           lpEnvelopeAdrList      = NULL;
    LPADRLIST           lpMessageAdrList       = NULL;

    LPADRLIST           lpEnvelope_NDR_AdrList = NULL;
    LPADRLIST           lpMessage_NDR_AdrList  = NULL;

    DWORD               dwEndProcessingTime    = 0;
    DWORD               dwTotalProcessingTime  = 0;
    DWORD               dwSleepProcessingTime  = 0;

    ULONG               cEnvelopeMesgAddr         = 0;
    ULONG               cEnvelopeReplyAddr        = 0;
    ULONG               cEnvelopeRecipAddr        = 0;
    LPADRLIST           lpEnvelopeAllAdrList      = NULL;
    LPADRLIST           lpMappedEnvelopeAdrList   = NULL;

    ULONG               cMessageMesgAddr          = 0;
    ULONG               cMessageReplyAddr         = 0;
    ULONG               cMessageRecipAddr         = 0;
    LPADRLIST           lpMessageAllAdrList       = NULL;
    LPADRLIST           lpMappedMessageAdrList    = NULL;

    BOOL                HasContent                = TRUE;

LPSPropProblemArray lpProblems                = NULL;

    DEBUGPUBLIC("HrImportMessage()\n");

    // Get the attachment in the envelope
    hrT = MAPICALL(lpEnvelope)->OpenAttach(
        lpEnvelope,
        0,
        NULL,
        MAPI_MODIFY,
        &lpAttach);

    if((FAILED(hrT)) && (hrT != MAPI_E_NOT_FOUND))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(hrT == MAPI_E_NOT_FOUND)
    {
        //
        // Message does not have an attachment.
        //

        HasContent = FALSE;
        lpAttach   = NULL;
    }

    if(HasContent == TRUE)
    {
        // Open a message interface on the attachment
        hrT = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    //
    // Map envelope addresses
    //

    hr = HrExpandMessageAddressList(
        lpEnvelope,
        &cEnvelopeMesgAddr,
        &cEnvelopeReplyAddr,
        &cEnvelopeRecipAddr,
        &lpOriginalEnvelopeAdrList,
        &lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMapAddrToProxy(
        lpAdrBook,
        lpszAddressType,
        lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMapGatewayProxyToAddr(
        lpAdrBook,
        lpszAddressType,
        lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMapProxyToAddr(
        lpAdrBook,
        lpGalABCont,
        lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrCollapseMessageAddressList(
        lpEnvelope,
        cEnvelopeMesgAddr,
        cEnvelopeReplyAddr,
        cEnvelopeRecipAddr,
        lpEnvelopeAllAdrList,
        &cEnvelopeValues,
        &lpEnvelopeProps,
        &lpMappedEnvelopeAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    FREEPADRLIST(lpEnvelopeAllAdrList);

    //
    // TNEF encode is setting the value of PR_RESPONSIBILITY to FALSE
    // for each recipient entry.
    //

    {
        ULONG        i       = 0;
        LPSPropValue lpPropT = NULL;

        for(i = 0; i < lpOriginalEnvelopeAdrList->cEntries; i++)
        {
            lpPropT = LpValFindProp(
                PR_RESPONSIBILITY,
                lpOriginalEnvelopeAdrList->aEntries[i].cValues,
                lpOriginalEnvelopeAdrList->aEntries[i].rgPropVals);

            if(lpPropT != NULL)
            {
                lpPropT->Value.b = TRUE;
            }
        }

        for(i = 0; i < lpMappedEnvelopeAdrList->cEntries; i++)
        {
            lpPropT = LpValFindProp(
                PR_RESPONSIBILITY,
                lpMappedEnvelopeAdrList->aEntries[i].cValues,
                lpMappedEnvelopeAdrList->aEntries[i].rgPropVals);

            if(lpPropT != NULL)
            {
                lpPropT->Value.b = TRUE;
            }
        }
    }

    if(HasContent == TRUE)
    {
        //
        // Map message addresses
        //

        hr = HrExpandMessageAddressList(
            lpMessage,
            &cMessageMesgAddr,
            &cMessageReplyAddr,
            &cMessageRecipAddr,
            &lpOriginalMessageAdrList,
            &lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMapAddrToProxy(
            lpAdrBook,
            lpszAddressType,
            lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMapGatewayProxyToAddr(
            lpAdrBook,
            lpszAddressType,
            lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMapProxyToAddr(
            lpAdrBook,
            lpGalABCont,
            lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrCollapseMessageAddressList(
            lpMessage,
            cMessageMesgAddr,
            cMessageReplyAddr,
            cMessageRecipAddr,
            lpMessageAllAdrList,
            &cMessageValues,
            &lpMessageProps,
            &lpMappedMessageAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        FREEPADRLIST(lpMessageAllAdrList);
    }

//------------------------------------------------------------------------------

    hr = HrMapRecipProxyToAddr(
        lpAdrBook,
        TRUE,
        fIgnoreRespValue,
        lpGalABCont,
        lpszAddressType,
        lpOriginalEnvelopeAdrList,
        lpMappedEnvelopeAdrList,
        NULL,
        NULL,
        lpEnvelope,
        &lpEnvelopeAdrList,
        &lpEnvelope_NDR_AdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        hr = HrMapRecipProxyToAddr(
            lpAdrBook,
            FALSE,
            fIgnoreRespValue,
            lpGalABCont,
            lpszAddressType,
            lpOriginalMessageAdrList,
            lpMappedMessageAdrList,
            lpEnvelopeAdrList,
            lpEnvelope_NDR_AdrList,
            lpMessage,
            &lpMessageAdrList,
            &lpMessage_NDR_AdrList);

        FREEPADRLIST(lpOriginalMessageAdrList);

        FREEPADRLIST(lpMessage_NDR_AdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // Non-deliver any recipients that could not be mapped.

    if((lpEnvelope_NDR_AdrList != NULL) &&
       (lpEnvelope_NDR_AdrList->cEntries != 0) &&
       (fSendNDR == TRUE))
    {
        LPMESSAGE lpReportEnvelope = NULL;

        //
        // Clear row IDs for envelope NDR address list.
        //

        hr = HrDeleteAddressListProperty(
            PR_ROWID,
            (LPADRLIST)lpEnvelope_NDR_AdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Create a non delivery report in MTS-OUT
        //

        hr = HrCreateNonDeliveryReport(
            TRUE,
            lpAdrBook,
            lpMtsOutFolder,
            lpMessage,
            lpEnvelope_NDR_AdrList,
            &lpReportEnvelope);

        if(FAILED(hr))
        {
            ;
        }
        else
        {
            HANDLE hNewMailEvent = GetGWNewMailEvent();

            if((hNewMailEvent != NULL) && (!SetEvent(hNewMailEvent)))
            {
                hr = HR_LOG(E_FAIL);
            }
        }

        ULRELEASE(lpReportEnvelope);
    }

    FREEPADRLIST(lpEnvelope_NDR_AdrList);

    if((lpEnvelopeAdrList == NULL) || (lpEnvelopeAdrList->cEntries == 0))
    {
        FREEPADRLIST(lpEnvelopeAdrList);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        if((lpMessageAdrList == NULL) || (lpMessageAdrList->cEntries == 0))
        {
            FREEPADRLIST(lpMessageAdrList);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    // Fix up recipient numbers
    hr = HrFixUpRecipientNumbers(lpEnvelopeAdrList, lpMessageAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//------------------------------------------------------------------------------

    hrT = MAPICALL(lpEnvelope)->SetProps(
        lpEnvelope,
        cEnvelopeValues,
        lpEnvelopeProps,
        &lpProblems);

    if(FAILED(hrT))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);
    MAPIFREEBUFFER(lpProblems);

    hr = HrMAPISetPropBoolean(
        (LPMAPIPROP)lpEnvelope,
        PR_DELETE_AFTER_SUBMIT,
        TRUE);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        hrT = MAPICALL(lpMessage)->SetProps(
            lpMessage,
            cMessageValues,
            lpMessageProps,
            &lpProblems);

        if(FAILED(hrT))
        {
            lpProblems = NULL;

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        DUMPPROPPROBLEMS(lpProblems);
        MAPIFREEBUFFER(lpProblems);
    }

//------------------------------------------------------------------------------

    // Set envelope recipients
    hrT = MAPICALL(lpEnvelope)->ModifyRecipients(
        lpEnvelope,
        0,
        lpEnvelopeAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        // Set message recipients
        hrT = MAPICALL(lpMessage)->ModifyRecipients(
            lpMessage,
            0,
            lpMessageAdrList);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    if(HasContent == TRUE)
    {
        // Save changes to the message
        hrT = MAPICALL(lpMessage)->SaveChanges(
            lpMessage,
            MAPI_DEFERRED_ERRORS|KEEP_OPEN_READWRITE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Save changes to attachment object
        hrT = MAPICALL(lpAttach)->SaveChanges(
            lpAttach,
            MAPI_DEFERRED_ERRORS|KEEP_OPEN_READWRITE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        ULRELEASE(lpMessage);

        ULRELEASE(lpAttach);
    }

    //----------------------------------------------------------------------
    dwEndProcessingTime     = GetTickCount();

    dwTotalProcessingTime   = dwEndProcessingTime - dwStartProcessingTime;

    dwSleepProcessingTime   =
        ((dwTotalProcessingTime < dwInSleepInterval) ?
         (dwInSleepInterval - dwTotalProcessingTime) : 0);

    if(dwSleepProcessingTime > 0)
    {
        Sleep(dwSleepProcessingTime);
    }
    //----------------------------------------------------------------------

    // Submit message
    hrT = MAPICALL(lpEnvelope)->SubmitMessage(lpEnvelope, FORCE_SUBMIT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpProblems);

    FREEPADRLIST(lpEnvelopeAdrList);

    FREEPADRLIST(lpMessageAdrList);

    FREEPADRLIST(lpEnvelope_NDR_AdrList);

    FREEPADRLIST(lpMessage_NDR_AdrList);

    FREEPADRLIST(lpOriginalEnvelopeAdrList);
    FREEPADRLIST(lpOriginalMessageAdrList);

    FREEPADRLIST(lpEnvelopeAllAdrList);
    FREEPADRLIST(lpMessageAllAdrList);

    FREEPADRLIST(lpMappedEnvelopeAdrList);
    FREEPADRLIST(lpMappedMessageAdrList);

    MAPIFREEBUFFER(lpEnvelopeProps);
    MAPIFREEBUFFER(lpMessageProps);

    ULRELEASE(lpMessage);

    ULRELEASE(lpAttach);

    RETURN(hr);
}

//$--HrCheckExternalTraceInformation--------------------------------------------
// Process messages in MTS-IN.
// -----------------------------------------------------------------------------
HRESULT HrCheckExternalTraceInformation(    // RETURNS: return code
    IN LPMESSAGE lpEnvelope)                // pointer to envelope
{
    HRESULT hr           = NOERROR;
    LPTSTR  lpszMtsIdGDI = NULL;
    LPTSTR  lpszTraceGDI = NULL;

    DEBUGPUBLIC("HrCheckExternalTraceInformation()\n");

    hr = HrGetMtsIdEnterpriseGDI(
        lpEnvelope,
        PR_MTS_ID,
        &lpszMtsIdGDI);

    if((FAILED(hr)) && (hr != MAPI_E_NOT_FOUND))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetTraceEnterpriseGDI(
        lpEnvelope,
        PR_TRACE_INFO,
        &lpszTraceGDI);

    if((FAILED(hr)) && (hr != MAPI_E_NOT_FOUND))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if((lpszMtsIdGDI == NULL) && (lpszTraceGDI == NULL))
    {
        // consistent
    }
    else if((lpszMtsIdGDI == NULL) || (lpszTraceGDI == NULL))
    {
        // warning
        MODULE_ERROR("**** Inconsistent external trace ****");

        if((lpszTraceGDI != NULL) &&
           (lstrcmpi(lpszTraceGDI, lpszLocalGDI) == 0))
    {
            // delete MTS-ID
            hr = HrDeleteMtsIdProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
    }
        else if((lpszMtsIdGDI != NULL) &&
                (lstrcmpi(lpszMtsIdGDI, lpszLocalGDI) == 0))
        {
            // delete trace
            hr = HrDeleteTraceProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
    }
    else
    {
            // delete trace
            hr = HrDeleteTraceProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            // delete MTS-ID
            hr = HrDeleteMtsIdProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
    }
    }
    else
    {
        if(lstrcmpi(lpszMtsIdGDI, lpszTraceGDI) != 0)
    {
            // warning
            MODULE_ERROR("**** Inconsistent external trace ****");

            if(lstrcmpi(lpszTraceGDI, lpszLocalGDI) == 0)
    {
                // delete MTS-ID
                hr = HrDeleteMtsIdProps(
                    lpEnvelope);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
    }
            else if(lstrcmpi(lpszMtsIdGDI, lpszLocalGDI) == 0)
    {
                // delete trace
                hr = HrDeleteTraceProps(
                    lpEnvelope);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
    }
    else
    {
                // delete trace
                hr = HrDeleteTraceProps(
                    lpEnvelope);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                // delete MTS-ID
                hr = HrDeleteMtsIdProps(
                    lpEnvelope);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
    }
    }
    else
    {
            // consistent
    }
    }

cleanup:

    hr = NOERROR;

    MAPIFREEBUFFER(lpszMtsIdGDI);

    MAPIFREEBUFFER(lpszTraceGDI);

    RETURN(hr);
}

//$--HrNonDeliverMtsInMessage---------------------------------------------------
//  Non-deliver a message in MTS-IN.
// -----------------------------------------------------------------------------
HRESULT HrNonDeliverMtsInMessage(       // RETURNS: zero
    IN LPTSTR lpszMessageClass,         // message class
    IN LPMESSAGE lpEnvelope)            // envelope
{
    HRESULT   hr               = NOERROR;
    LPADRBOOK lpAdrBook        = NULL;
    LPMESSAGE lpMessage        = NULL;
    LPMESSAGE lpSubjectMessage = NULL;
    LPMESSAGE lpReportEnvelope = NULL;
    LPADRLIST lpReportAdrList  = NULL;
    LPATTACH  lpAttach         = NULL;
    BOOL      HasContent       = TRUE;

    DEBUGPUBLIC("HrNonDeliverMtsInMessage()\n");

    lpAdrBook   = GetAdrBookPtr();

    MODULE_ERROR1(
        "**** [IN ] Unknown message class [%s] ****",
        ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

    hr = HrNonDeliverAllRecipList(
        lpEnvelope,
        &lpReportAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Get the attachment in the envelope
    hr = MAPICALL(lpEnvelope)->OpenAttach(
        lpEnvelope,
        0,
        NULL,
        MAPI_MODIFY,
        &lpAttach);

    if((FAILED(hr)) && (hr != MAPI_E_NOT_FOUND))
    {
        goto cleanup;
    }

    if(hr == MAPI_E_NOT_FOUND)
    {
        //
        // Message does not have an attachment.
        //

        HasContent = FALSE;
        lpAttach   = NULL;
    }

    lpSubjectMessage = NULL;

    if(HasContent == TRUE)
    {
        // Open a message interface on the attachment
        hr = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        lpSubjectMessage = lpMessage;
    }
    else
    {
        lpSubjectMessage = lpEnvelope;
    }

    //
    // Create a non delivery report in MTS-OUT
    //

    if(lpSubjectMessage != NULL)
    {
        hr = HrCreateNonDeliveryReport(
            TRUE,
            lpAdrBook,
            lpMtsOutFolder,
            lpSubjectMessage,
            lpReportAdrList,
            &lpReportEnvelope);

        if(FAILED(hr))
        {
            ;
        }
        else
        {
            HANDLE hNewMailEvent = GetGWNewMailEvent();

            if((hNewMailEvent != NULL) && (!SetEvent(hNewMailEvent)))
            {
                hr = HR_LOG(E_FAIL);
            }
        }
    }

cleanup:

    ULRELEASE(lpReportEnvelope);

    ULRELEASE(lpMessage);

    FREEPADRLIST(lpReportAdrList);

    RETURN(hr);
}

//$--ProcessMtsIn---------------------------------------------------------------
//  Process messages in MTS-IN.
// -----------------------------------------------------------------------------
DWORD ProcessMtsIn(                     // RETURNS: zero
    IN LPVOID lpParameter)              // parameter
{
    HRESULT             hr                       = NOERROR;
    HRESULT             hrT                      = NOERROR;
    ULONG               i                        = 0;
    ULONG               cRows                    = 0;

    LPMESSAGE           lpEnvelope               = NULL;

    ULONG               cbMessageClass           = 0;
    LPTSTR              lpszMessageClass         = NULL;

    LPSTREAM            lpStream                 = NULL;
    TCHAR               rgchFileName[MAX_PATH+1] = {0};

    DWORD               dwStartProcessingTime    = 0;

    LPADRBOOK           lpAdrBook                = NULL;
    LPABCONT            lpGalABCont              = NULL;
    ULONG               ulObjType                = 0;

SPropTagArray       sptExcludeNone           = {0};
LPSTnefProblemArray lptpa                    = NULL;

LPWIN32_FIND_DATA   rgFindData               = NULL;
LPWIN32_FIND_DATA   lpFindData               = NULL;

    EDKCNVRES           crRes                    = GCR_OK;

    //
    //  Declare an environment for the conversion.
    //

    EDKCNVENV envTmp =
    {
        1,

lpszInConverterW,
        (LHANDLE)NULL,
        NULL,
        NULL,
        NULL
    };

    CONV_OPTIONS sConvOptions =
    {
        FALSE,              // attach TNEF encapsulation
        NULL                // address type
    };

    const LPCWSTR pszMessageClassW = L"ENVELOPE.IPM";

    PVOID           pvConvInstance              = NULL;

    LPIDITEM        lpIdItem                    = NULL;

    DEBUGPUBLIC("ProcessMtsIn()\n");

    //performance code

    // Initialize conversion engine instance data.
    hr = HrConvInitInstance(hEventLog, &pvConvInstance);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cRows       = GetGWFindDataSize(lpParameter);

    rgFindData  = GetGWFindData(lpParameter);

    lpAdrBook   = GetAdrBookPtr();

    lpGalABCont = GetGWGAL();

    for(i = 0; i < cRows; i++)
    {
        dwStartProcessingTime = GetTickCount();

//------------------------------------------------------------------------------
    // Build file name of incoming message

        lpFindData = &rgFindData[i];

    lstrcpy(rgchFileName, lpszInDirectory);
    lstrcat(rgchFileName, lpFindData->cFileName);

    //------------------------------------------------------------------------------

        // Adding file to the "being processed" list!
        // Prevents another thread from processing this file.
        hr = HrAddIdItem(&InboundIdList, rgchFileName, &lpIdItem);

        if(hr == EDK_E_ALREADY_EXISTS)
        {
            //
            // Don't process this message. It is already being processed
            // by another thread.
            //

            continue;
        }

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

        // Open a buffered stream on the input file.
    hrT = OpenStreamOnFile(
        MAPIAllocateBuffer,
        MAPIFreeBuffer,
    STGM_READ | STGM_SHARE_EXCLUSIVE,// stream interface flags
    rgchFileName,
    NULL,
    &lpStream);

    if(FAILED(hrT))
    {
            // Temporarily can't access this file.
            // We MUST remove it from the being processed list, since
            // this thread added it, but now can't continue to process it.
  goto SKIP;
    }

        lpEnvelope = NULL;

        hrT = MAPICALL(lpMtsInFolder)->CreateMessage(
            lpMtsInFolder,
            NULL,
            MAPI_DEFERRED_ERRORS,
            &lpEnvelope);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

//------------------------------------------------------------------------------

        // Initialize the conversion environment structure

        sConvOptions.lpszAddressType = lpszAddressType;

        envTmp.lphSession      = (LHANDLE)lpSession;
        envTmp.lpAB            = lpAdrBook;
        envTmp.pGatewayDefined = &sConvOptions;

        //
        //  Perform the conversion.
        //

        hr = HrConvConvert(
            pvConvInstance,         // conversion instance pointer
            &envTmp,                // environment structure pointer
            pszMessageClassW,       // dummy message class
            lpStream,               // stream to read from
            lpEnvelope,             // message to convert to
            &crRes);                // result pointer

        if(FAILED(hr) || crRes != GCR_OK)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

//------------------------------------------------------------------------------

        // Get the message class

        MAPIFREEBUFFER(lpszMessageClass);

        cbMessageClass = 0;
        lpszMessageClass = NULL;

        hr = HrMAPIGetPropString(
            (LPMAPIPROP)lpEnvelope,
            PR_MESSAGE_CLASS,
            &cbMessageClass,
            (void **)&lpszMessageClass);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MODULE_WARNING1(
            "**** [IN ] Processing message of class [%s] ****",
            ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

        //
        // Check for inconsistencies in MTS-ID & external trace information.
        //

        hr = HrCheckExternalTraceInformation(lpEnvelope);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Process the message

        if(_tcsstr(lpszMessageClass, TEXT("ENVELOPE.IPM")) != NULL)
        {
            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_NON_RECEIPT_NOTIFICATION_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrImportMessage(
                lpAdrBook,
                lpGalABCont,
                FALSE,
                TRUE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".NDR")) != NULL))
        {
            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrDeleteSenderProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrImportMessage(
                lpAdrBook,
                lpGalABCont,
                TRUE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".DR")) != NULL))
        {
            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrDeleteSenderProps(
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrImportMessage(
                lpAdrBook,
                lpGalABCont,
                TRUE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".IPNNRN")) != NULL))
        {
            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_NON_RECEIPT_NOTIFICATION_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrImportMessage(
                lpAdrBook,
                lpGalABCont,
                FALSE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".IPNRN")) != NULL))
        {
            hr = HrMAPISetPropBoolean(
                (LPMAPIPROP)lpEnvelope,
                PR_READ_RECEIPT_REQUESTED,
                TRUE);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrImportMessage(
                lpAdrBook,
                lpGalABCont,
                FALSE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else
        {
            hr = HrNonDeliverMtsInMessage(
                lpszMessageClass,
                lpEnvelope);

            if(FAILED(hr))
            {
                ; // failed to non-deliver message
            }
        }

        if(FAILED(hr))
        {
            MODULE_ERROR1(
                "**** [IN ] Error while processing message of class [%s] ****",
                ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

            //
            // This message will be moved to the archive directory.
            //

            ULRELEASE(lpStream);
            MAPIFREEBUFFER(lpszMessageClass);
            ULRELEASE(lpEnvelope);
            continue;
        }

        ULRELEASE(lpStream);

        MAPIFREEBUFFER(lpszMessageClass);

        ULRELEASE(lpEnvelope);

        //performance code

        if(!DeleteFile(rgchFileName))
        {
            hr = HR_LOG(E_FAIL);
        }

    //------------------------------------------------------------------------------

SKIP:   // can't process this file now, so remove it from the "being processed" list.

        hr = HrRemoveIdItem(&InboundIdList, lpIdItem);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------
    }

cleanup:

    ULRELEASE(lpStream);

    MAPIFREEBUFFER(lpszMessageClass);

    ULRELEASE(lpEnvelope);

    if(pvConvInstance != NULL)
    {
        // De-initialize conversion engine instance data.
        (VOID)HrConvUninitInstance(pvConvInstance);
        pvConvInstance = NULL;
    }

    return(0);
}

//$--HrExportMessage------------------------------------------------------------
// Process an IPM/IPN in MTS-OUT.
// -----------------------------------------------------------------------------
HRESULT HrExportMessage(                // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN BOOL fSendNDR,                   // TRUE if NDRs should be generated
    IN DWORD dwStartProcessingTime,     // start processing time
    IN OUT LPMESSAGE lpEnvelope)        // pointer to envelope
{
    HRESULT             hr                        = NOERROR;
    HRESULT             hrT                       = NOERROR;

    LPMESSAGE           lpMessage                 = NULL;
    LPATTACH            lpAttach                  = NULL;

    ULONG               cEnvelopeValues           = 0;
    LPSPropValue        lpEnvelopeProps           = NULL;
    ULONG               cMessageValues            = 0;
    LPSPropValue        lpMessageProps            = NULL;

    LPADRLIST           lpOriginalEnvelopeAdrList = NULL;
    LPADRLIST           lpOriginalMessageAdrList  = NULL;

    LPADRLIST           lpEnvelopeAdrList         = NULL;
    LPADRLIST           lpMessageAdrList          = NULL;

    LPADRLIST           lpEnvelope_NDR_AdrList    = NULL;
    LPADRLIST           lpMessage_NDR_AdrList     = NULL;

    DWORD               dwEndProcessingTime       = 0;
    DWORD               dwTotalProcessingTime     = 0;
    DWORD               dwSleepProcessingTime     = 0;

    ULONG               cbeid                     = 0;
    LPENTRYID           lpeid                     = NULL;

    ULONG               cEnvelopeMesgAddr         = 0;
    ULONG               cEnvelopeReplyAddr        = 0;
    ULONG               cEnvelopeRecipAddr        = 0;
    LPADRLIST           lpEnvelopeAllAdrList      = NULL;
    LPADRLIST           lpMappedEnvelopeAdrList   = NULL;

    ULONG               cMessageMesgAddr          = 0;
    ULONG               cMessageReplyAddr         = 0;
    ULONG               cMessageRecipAddr         = 0;
    LPADRLIST           lpMessageAllAdrList       = NULL;
    LPADRLIST           lpMappedMessageAdrList    = NULL;

    BOOL                HasContent                = TRUE;

    LPSPropProblemArray lpProblems                = NULL;

    DEBUGPUBLIC("HrExportMessage()\n");

    // Get the attachment in the envelope
    hrT = MAPICALL(lpEnvelope)->OpenAttach(
        lpEnvelope,
        0,
        NULL,
        MAPI_MODIFY,
        &lpAttach);

    if((FAILED(hrT)) && (hrT != MAPI_E_NOT_FOUND))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(hrT == MAPI_E_NOT_FOUND)
    {
        //
        // Message does not have an attachment.
        //

        HasContent = FALSE;
        lpAttach   = NULL;
    }

    if(HasContent == TRUE)
    {
        // Open a message interface on the attachment
        hrT = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    //
    // Map envelope addresses
    //

    hr = HrExpandMessageAddressList(
        lpEnvelope,
        &cEnvelopeMesgAddr,
        &cEnvelopeReplyAddr,
        &cEnvelopeRecipAddr,
        &lpOriginalEnvelopeAdrList,
        &lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMapAddrToProxy(
        lpAdrBook,
        lpszAddressType,
        lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMapGatewayAddrToProxy(
        lpAdrBook,
        lpszAddressType,
        lpEnvelopeAllAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrCollapseMessageAddressList(
        lpEnvelope,
        cEnvelopeMesgAddr,
        cEnvelopeReplyAddr,
        cEnvelopeRecipAddr,
        lpEnvelopeAllAdrList,
        &cEnvelopeValues,
        &lpEnvelopeProps,
        &lpMappedEnvelopeAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    FREEPADRLIST(lpEnvelopeAllAdrList);

    if(HasContent == TRUE)
    {
        //
        // Map message addresses
        //

        hr = HrExpandMessageAddressList(
            lpMessage,
            &cMessageMesgAddr,
            &cMessageReplyAddr,
            &cMessageRecipAddr,
            &lpOriginalMessageAdrList,
            &lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMapAddrToProxy(
            lpAdrBook,
            lpszAddressType,
            lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMapGatewayAddrToProxy(
            lpAdrBook,
            lpszAddressType,
            lpMessageAllAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrCollapseMessageAddressList(
            lpMessage,
            cMessageMesgAddr,
            cMessageReplyAddr,
            cMessageRecipAddr,
            lpMessageAllAdrList,
            &cMessageValues,
            &lpMessageProps,
            &lpMappedMessageAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        FREEPADRLIST(lpMessageAllAdrList);
    }

//------------------------------------------------------------------------------

    hr = HrMapRecipAddrToProxy(
        lpAdrBook,
        TRUE,
        fIgnoreRespValue,
        lpszAddressType,
        NULL,
        lpOriginalEnvelopeAdrList,
        lpMappedEnvelopeAdrList,
        NULL,
        NULL,
        lpEnvelope,
        &lpEnvelopeAdrList,
        &lpEnvelope_NDR_AdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        hr = HrMapRecipAddrToProxy(
            lpAdrBook,
            FALSE,
            fIgnoreRespValue,
            lpszAddressType,
            lpOriginalEnvelopeAdrList,
            lpOriginalMessageAdrList,
            lpMappedMessageAdrList,
            lpEnvelopeAdrList,
            lpEnvelope_NDR_AdrList,
            lpMessage,
            &lpMessageAdrList,
            &lpMessage_NDR_AdrList);

        FREEPADRLIST(lpOriginalEnvelopeAdrList);

        FREEPADRLIST(lpOriginalMessageAdrList);

        FREEPADRLIST(lpMessage_NDR_AdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // Non-deliver any recipients that could not be mapped.

    if((lpEnvelope_NDR_AdrList != NULL) &&
       (lpEnvelope_NDR_AdrList->cEntries != 0) &&
       (fSendNDR == TRUE))
    {
        LPMESSAGE lpReportEnvelope = NULL;

        //
        // Clear row IDs for envelope NDR address list.
        //

        hr = HrDeleteAddressListProperty(
            PR_ROWID,
            (LPADRLIST)lpEnvelope_NDR_AdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Create a non delivery report in MTS-IN
        //

        hr = HrCreateNonDeliveryReport(
            FALSE,
            lpAdrBook,
            lpMtsInFolder,
            lpMessage,
            lpEnvelope_NDR_AdrList,
            &lpReportEnvelope);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        hr = HrMAPISetPropBoolean(
            (LPMAPIPROP)lpReportEnvelope,
            PR_DELETE_AFTER_SUBMIT,
            TRUE);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);

            ULRELEASE(lpReportEnvelope);

            goto cleanup;
        }

        // Submit message
        hrT = MAPICALL(lpReportEnvelope)->SubmitMessage(lpReportEnvelope, FORCE_SUBMIT);

        if(FAILED(hrT))
        {
            hrT = HR_LOG(hrT);
        }

        ULRELEASE(lpReportEnvelope);
    }

    FREEPADRLIST(lpEnvelope_NDR_AdrList);

    if((lpEnvelopeAdrList == NULL) || (lpEnvelopeAdrList->cEntries == 0))
    {
        FREEPADRLIST(lpEnvelopeAdrList);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        if((lpMessageAdrList == NULL) || (lpMessageAdrList->cEntries == 0))
        {
            FREEPADRLIST(lpMessageAdrList);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    // Fix up recipient numbers
    hr = HrFixUpRecipientNumbers(lpEnvelopeAdrList, lpMessageAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//------------------------------------------------------------------------------

    hrT = MAPICALL(lpEnvelope)->SetProps(
        lpEnvelope,
        cEnvelopeValues,
        lpEnvelopeProps,
        &lpProblems);

    if(FAILED(hrT))
    {
        lpProblems = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    DUMPPROPPROBLEMS(lpProblems);
    MAPIFREEBUFFER(lpProblems);

    if(HasContent == TRUE)
    {
        hrT = MAPICALL(lpMessage)->SetProps(
            lpMessage,
            cMessageValues,
            lpMessageProps,
            &lpProblems);

        if(FAILED(hrT))
        {
            lpProblems = NULL;

            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        DUMPPROPPROBLEMS(lpProblems);
        MAPIFREEBUFFER(lpProblems);
    }

//------------------------------------------------------------------------------

    // Set envelope recipients
    hrT = MAPICALL(lpEnvelope)->ModifyRecipients(
        lpEnvelope,
        0,
        lpEnvelopeAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(HasContent == TRUE)
    {
        // Set message recipients
        hrT = MAPICALL(lpMessage)->ModifyRecipients(
            lpMessage,
            0,
            lpMessageAdrList);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

//------------------------------------------------------------------------------

    if(HasContent == TRUE)
    {
        // Save changes to the message
        hrT = MAPICALL(lpMessage)->SaveChanges(
            lpMessage,
            MAPI_DEFERRED_ERRORS|FORCE_SAVE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Save changes to attachment object
        hrT = MAPICALL(lpAttach)->SaveChanges(
            lpAttach,
            MAPI_DEFERRED_ERRORS|FORCE_SAVE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        ULRELEASE(lpMessage);

        ULRELEASE(lpAttach);
    }

    // Save changes to the envelope
    hrT = MAPICALL(lpEnvelope)->SaveChanges(
        lpEnvelope,
        MAPI_DEFERRED_ERRORS|KEEP_OPEN_READWRITE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //----------------------------------------------------------------------
    dwEndProcessingTime     = GetTickCount();

    dwTotalProcessingTime   = dwEndProcessingTime - dwStartProcessingTime;

    dwSleepProcessingTime   =
        ((dwTotalProcessingTime < dwOutSleepInterval) ?
         (dwOutSleepInterval - dwTotalProcessingTime) : 0);

    if(dwSleepProcessingTime > 0)
    {
        Sleep(dwSleepProcessingTime);
    }
    //----------------------------------------------------------------------

cleanup:

    MAPIFREEBUFFER(lpProblems);

    MAPIFREEBUFFER(lpeid);

    FREEPADRLIST(lpOriginalEnvelopeAdrList);

    FREEPADRLIST(lpOriginalMessageAdrList);

    FREEPADRLIST(lpEnvelopeAdrList);

    FREEPADRLIST(lpMessageAdrList);

    FREEPADRLIST(lpEnvelope_NDR_AdrList);

    FREEPADRLIST(lpMessage_NDR_AdrList);

    FREEPADRLIST(lpEnvelopeAllAdrList);

    FREEPADRLIST(lpMessageAllAdrList);

    FREEPADRLIST(lpMappedEnvelopeAdrList);
    FREEPADRLIST(lpMappedMessageAdrList);

    MAPIFREEBUFFER(lpEnvelopeProps);
    MAPIFREEBUFFER(lpMessageProps);

    ULRELEASE(lpMessage);

    ULRELEASE(lpAttach);

    RETURN(hr);
}

//$--HrConvertMtsOut------------------------------------------------------------
//  Convert a message in MTS-OUT.
// -----------------------------------------------------------------------------
HRESULT HrConvertMtsOut(                // RETURNS: return code
    IN PVOID     pvConvInstance,        // converter instance
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN LPTSTR    lpszMessageClass,      // message class
    IN LPMESSAGE lpMessage)             // message
{
    HRESULT      hr                          = NOERROR;
    HRESULT      hrT                         = NOERROR;
    LPSRowSet    lpRows                      = NULL;
    LPADRLIST    lpAdrList                   = NULL;
    LPMAPITABLE  lpTable                     = NULL;
    BOOL         fFileCreated                = FALSE;   // TRUE if output file created

    ULONG        cValues                     = 0;
    LPSPropValue lpProps                     = NULL;
    LPSPropValue lpPropT                     = NULL;

    BOOL         IsMapiRecip                 = 0;
    LPTSTR       lpszAddress                 = NULL;

    LPADRLIST    lpMapiAdrList               = NULL;
    LPADRLIST    lpNonMapiAdrList            = NULL;

    ULONG        i                           = 0;

    LPSTREAM     lpStream                    = NULL;
    TCHAR        rgchOutFileName[MAX_PATH+1] = {0};

    EDKCNVRES    crRes                       = GCR_OK;

    //
    //  Declare an environment for the conversion.
    //

    EDKCNVENV envTmp =
    {
        1,
        lpszOutConverterW,
        (LHANDLE)NULL,
        NULL,
        NULL,
        NULL
    };

    CONV_OPTIONS sConvOptions =
    {
        FALSE,              // attach TNEF encapsulation
        NULL                // address type
    };

    ULONG  cbMessageClass                    = 0;
    LPWSTR lpszMessageClassW                 = NULL;

    ULONG  iMapi                             = 0;
    ULONG  iNonMapi                          = 0;

    SizedSPropTagArray(1, rgPropTags) =
    {
        1,
        {
            PR_REPORT_DESTINATION_ENTRYID,
        }
    };

// stream interface flags
const ULONGulFlags=STGM_CREATE | STGM_READWRITE |
  SOF_UNIQUEFILENAME | STGM_SHARE_EXCLUSIVE;

    DEBUGPUBLIC("HrConvertMtsOut()\n");

    if(_tcsstr(lpszMessageClass, TEXT("ENVELOPE.IPM")) != NULL)
    {
        //
        // Get the recipient table for the message
        //

        hrT = MAPICALL(lpMessage)->GetRecipientTable(
            lpMessage,
            MAPI_DEFERRED_ERRORS,
            &lpTable);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //
    //  Query all the rows
    //

    hr = HrQueryAllRows(
        lpTable,
    NULL,
    NULL,
    NULL,
    0,
    &lpRows);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Divide recipient list into MAPI and non-MAPI recipients
        //

        lpAdrList = (LPADRLIST)lpRows;

        hr = HrMAPICreateSizedAddressList(
            lpAdrList->cEntries,
            &lpMapiAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMAPICreateSizedAddressList(
            lpAdrList->cEntries,
            &lpNonMapiAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        for(i = 0; i < lpAdrList->cEntries; i++)
        {
            cValues = lpAdrList->aEntries[i].cValues;
            lpProps = lpAdrList->aEntries[i].rgPropVals;

            lpPropT = LpValFindProp(PR_SEND_RICH_INFO, cValues, lpProps);

            if(lpPropT != NULL)
            {
                IsMapiRecip = lpPropT->Value.b;
            }
            else
            {
                IsMapiRecip = FALSE;
            }

            if(IsMapiRecip == TRUE)
            {
                hr = HrMAPISetAddressList(
                    iMapi,
                    cValues,
                    lpProps,
                    lpMapiAdrList);

                iMapi++;

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
            }
            else
            {
                hr = HrMAPISetAddressList(
                    iNonMapi,
                    cValues,
                    lpProps,
                    lpNonMapiAdrList);

                iNonMapi++;

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
            }
        }   // end for

        //
        // Convert message for MAPI recipients
        //

        if(iMapi > 0)
        {
            // Set envelope recipients
            hrT = MAPICALL(lpMessage)->ModifyRecipients(
                lpMessage,
                0,
                lpMapiAdrList);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hrT = MAPICALL(lpMessage)->SaveChanges(
                lpMessage,
                KEEP_OPEN_READWRITE);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

    //------------------------------------------------------------------------------

        lstrcpy(rgchOutFileName, lpszOutDirectory);

            // Open a buffered stream on our SGW*.TMP output file.
        hrT = OpenStreamOnFile(
            MAPIAllocateBuffer,
            MAPIFreeBuffer,
        ulFlags,// stream interface flags
        rgchOutFileName,
        szFilePrefix,
        &lpStream);

        if(FAILED(hrT))
        {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
        }

            fFileCreated = TRUE;    // output file has been created

        //------------------------------------------------------------------------------

            // Get the message class

            MAPIFREEBUFFER(lpszMessageClassW);

            cbMessageClass = 0;
            lpszMessageClassW = NULL;

            hr = HrMAPIGetPropString(
                (LPMAPIPROP)lpMessage,
                PR_MESSAGE_CLASS_W,
                &cbMessageClass,
                (void **)&lpszMessageClassW);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            // Initialize the conversion environment structure

            sConvOptions.fTnefEncode     = TRUE;
            sConvOptions.lpszAddressType = lpszAddressType;

            envTmp.lphSession      = (LHANDLE)lpSession;
            envTmp.lpAB            = lpAdrBook;
            envTmp.pGatewayDefined = &sConvOptions;

            //
            //  Perform the conversion.
            //

            hr = HrConvConvert(
                pvConvInstance,         // conversion instance pointer
                &envTmp,                // environment structure pointer
                lpszMessageClassW,      // message class
                lpMessage,              // message to convert from
                lpStream,               // stream to read to
                &crRes);                // result pointer

            if(FAILED(hr) || crRes != GCR_OK)
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

        //------------------------------------------------------------------------------

            ULRELEASE(lpStream);

            //
            // Finished processing message
            //

            {
                BOOL fT = FALSE;

                fT = SetFileAttributes(rgchOutFileName, FILE_ATTRIBUTE_NORMAL);

                if(fT == FALSE)
                {
                    hrT = HR_LOG(E_FAIL);
                }
            }
        }   // end if MAPI recipients

        //
        // Convert message for non-MAPI recipients
        //

        if(iNonMapi > 0)
        {
            // Set envelope recipients
            hrT = MAPICALL(lpMessage)->ModifyRecipients(
                lpMessage,
                0,
                lpNonMapiAdrList);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hrT = MAPICALL(lpMessage)->SaveChanges(
                lpMessage,
                KEEP_OPEN_READWRITE);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

    //------------------------------------------------------------------------------

        lstrcpy(rgchOutFileName, lpszOutDirectory);

        hrT = OpenStreamOnFile(
            MAPIAllocateBuffer,
            MAPIFreeBuffer,
        STGM_CREATE | STGM_READWRITE | SOF_UNIQUEFILENAME,
        rgchOutFileName,
        szFilePrefix,
        &lpStream);

        if(FAILED(hrT))
        {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
        }

            fFileCreated = TRUE;    // output file has been created

        //------------------------------------------------------------------------------

            // Get the message class

            MAPIFREEBUFFER(lpszMessageClassW);

            cbMessageClass = 0;
            lpszMessageClassW = NULL;

            hr = HrMAPIGetPropString(
                (LPMAPIPROP)lpMessage,
                PR_MESSAGE_CLASS_W,
                &cbMessageClass,
                (void **)&lpszMessageClassW);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            // Initialize the conversion environment structure

            sConvOptions.fTnefEncode     = FALSE;
            sConvOptions.lpszAddressType = lpszAddressType;

            envTmp.lphSession      = (LHANDLE)lpSession;
            envTmp.lpAB            = lpAdrBook;
            envTmp.pGatewayDefined = &sConvOptions;

            //
            //  Perform the conversion.
            //

            hr = HrConvConvert(
                pvConvInstance,         // conversion instance pointer
                &envTmp,                // environment structure pointer
                lpszMessageClassW,      // message class
                lpMessage,              // message to convert from
                lpStream,               // stream to read to
                &crRes);                // result pointer

            if(FAILED(hr) || crRes != GCR_OK)
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

        //------------------------------------------------------------------------------

            ULRELEASE(lpStream);

            //
            // Finished processing message
            //

            {
                BOOL fT = FALSE;

                fT = SetFileAttributes(rgchOutFileName, FILE_ATTRIBUTE_NORMAL);

                if(fT == FALSE)
                {
                    hrT = HR_LOG(E_FAIL);
                }
            }
        }   // end if non-MAPI recipients
    }   // end if Interpersonal message

    else if(_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL)
    {
        //
        // Determine if the report/notification recipient is a MAPI
        // or a non-MAPI recipient.
        //

        if((_tcsstr(lpszMessageClass, TEXT(".NDR")) != NULL) ||
           (_tcsstr(lpszMessageClass, TEXT(".DR")) != NULL))
        {
            //
            // Get destination of non-delivery/delivery report.
            //

            hr = MAPICALL(lpMessage)->GetProps(
                lpMessage,
                (SPropTagArray *)&rgPropTags,
                fMapiUnicode,
                &cValues,
                &lpProps);

            if(FAILED(hr))
            {
                lpProps = NULL;

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            hr = HrGWResolveProxy(
                lpAdrBook,
                lpProps->Value.bin.cb,
                (LPENTRYID)lpProps->Value.bin.lpb,
                lpszAddressType,
                &IsMapiRecip,
                &lpszAddress);

            MAPIFREEBUFFER(lpProps);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
        else if((_tcsstr(lpszMessageClass, TEXT(".IPNNRN")) != NULL) ||
                (_tcsstr(lpszMessageClass, TEXT(".IPNRN")) != NULL))
        {
            //
            // Get the recipient table for the non-read/read notification.
            //

            hrT = MAPICALL(lpMessage)->GetRecipientTable(
                lpMessage,
                MAPI_DEFERRED_ERRORS,
                &lpTable);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

        //
        //  Query all the rows
        //

        hr = HrQueryAllRows(
            lpTable,
        NULL,
        NULL,
        NULL,
        0,
        &lpRows);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            lpAdrList = (LPADRLIST)lpRows;
            cValues   = lpAdrList->aEntries[i].cValues;
            lpProps   = lpAdrList->aEntries[i].rgPropVals;

            lpPropT = LpValFindProp(PR_SEND_RICH_INFO, cValues, lpProps);

            if(lpPropT != NULL)
            {
                IsMapiRecip = lpPropT->Value.b;
            }
            else
            {
                IsMapiRecip = FALSE;
            }

            FREEPADRLIST(lpAdrList);
        }
        else
        {
            IsMapiRecip = FALSE;
        }

    //------------------------------------------------------------------------------

    lstrcpy(rgchOutFileName, lpszOutDirectory);

    hrT = OpenStreamOnFile(
        MAPIAllocateBuffer,
        MAPIFreeBuffer,
    STGM_CREATE | STGM_READWRITE | SOF_UNIQUEFILENAME,
    rgchOutFileName,
    szFilePrefix,
    &lpStream);

    if(FAILED(hrT))
    {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
    }

        fFileCreated = TRUE;    // output file has been created

    //------------------------------------------------------------------------------

        // Get the message class

        MAPIFREEBUFFER(lpszMessageClassW);

        cbMessageClass = 0;
        lpszMessageClassW = NULL;

        hr = HrMAPIGetPropString(
            (LPMAPIPROP)lpMessage,
            PR_MESSAGE_CLASS_W,
            &cbMessageClass,
            (void **)&lpszMessageClassW);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Initialize the conversion environment structure

        sConvOptions.fTnefEncode     = IsMapiRecip;
        sConvOptions.lpszAddressType = lpszAddressType;

        envTmp.lphSession            = (LHANDLE)lpSession;
        envTmp.lpAB                  = lpAdrBook;
        envTmp.pGatewayDefined       = &sConvOptions;

        //
        //  Perform the conversion.
        //

        hr = HrConvConvert(
            pvConvInstance,         // conversion instance pointer
            &envTmp,                // environment structure pointer
            lpszMessageClassW,      // message class
            lpMessage,              // message to convert from
            lpStream,               // stream to read to
            &crRes);                // result pointer

        if(FAILED(hr) || crRes != GCR_OK)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

        ULRELEASE(lpStream);

        //
        // Finished processing message
        //

        {
            BOOL fT = FALSE;

            fT = SetFileAttributes(rgchOutFileName, FILE_ATTRIBUTE_NORMAL);

            if(fT == FALSE)
            {
                hrT = HR_LOG(E_FAIL);
            }
        }
    }   // end if report

    else
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if ( FAILED(hr) )
    {
        // delete the output file created, if any.
        if ( fFileCreated == TRUE )
        {
            // Must release the stream first!
            ULRELEASE(lpStream);

            (VOID)DeleteFile(rgchOutFileName);
        }
    }

    MAPIFREEBUFFER(lpszAddress);
    MAPIFREEBUFFER(lpszMessageClassW);

    FREEPADRLIST(lpAdrList);        // also frees lpRows

    FREEPADRLIST(lpMapiAdrList);

    FREEPADRLIST(lpNonMapiAdrList);

    ULRELEASE(lpStream);

    ULRELEASE(lpTable);

    RETURN(hr);
}

//$--HrNonDeliverMtsOutMessage--------------------------------------------------
//  Non-deliver a message in MTS-OUT.
// -----------------------------------------------------------------------------
HRESULT HrNonDeliverMtsOutMessage(      // RETURNS: zero
    IN LPTSTR lpszMessageClass,         // message class
    IN LPMESSAGE lpEnvelope)            // envelope
{
    HRESULT   hr               = NOERROR;
    LPADRBOOK lpAdrBook        = NULL;
    LPMESSAGE lpMessage        = NULL;
    LPMESSAGE lpSubjectMessage = NULL;
    LPMESSAGE lpReportEnvelope = NULL;
    LPADRLIST lpReportAdrList  = NULL;
    LPATTACH  lpAttach         = NULL;
    BOOL      HasContent       = TRUE;

    DEBUGPUBLIC("HrNonDeliverMtsOutMessage()\n");

    lpAdrBook   = GetAdrBookPtr();

    MODULE_ERROR1(
        "**** [IN ] Unknown message class [%s] ****",
        ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

    hr = HrNonDeliverAllRecipList(
        lpEnvelope,
        &lpReportAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Get the attachment in the envelope
    hr = MAPICALL(lpEnvelope)->OpenAttach(
        lpEnvelope,
        0,
        NULL,
        MAPI_MODIFY,
        &lpAttach);

    if((FAILED(hr)) && (hr != MAPI_E_NOT_FOUND))
    {
        goto cleanup;
    }

    if(hr == MAPI_E_NOT_FOUND)
    {
        //
        // Message does not have an attachment.
        //

        HasContent = FALSE;
        lpAttach   = NULL;
    }

    lpSubjectMessage = NULL;

    if(HasContent == TRUE)
    {
        // Open a message interface on the attachment
        hr = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        lpSubjectMessage = lpMessage;
    }
    else
    {
        lpSubjectMessage = lpEnvelope;
    }

    //
    // Create a non delivery report in MTS-IN
    //

    if(lpSubjectMessage != NULL)
    {
        hr = HrCreateNonDeliveryReport(
            FALSE,
            lpAdrBook,
            lpMtsInFolder,
            lpSubjectMessage,
            lpReportAdrList,
            &lpReportEnvelope);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        hr = HrMAPISetPropBoolean(
            (LPMAPIPROP)lpReportEnvelope,
            PR_DELETE_AFTER_SUBMIT,
            TRUE);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        // Submit message
        hr = MAPICALL(lpReportEnvelope)->SubmitMessage(lpReportEnvelope, FORCE_SUBMIT);

        if(FAILED(hr))
        {
            ;
        }
    }

cleanup:

    ULRELEASE(lpReportEnvelope);

    ULRELEASE(lpMessage);

    FREEPADRLIST(lpReportAdrList);

    RETURN(hr);
}

//$--ProcessMtsOut--------------------------------------------------------------
//  Process messages in MTS-OUT.
// -----------------------------------------------------------------------------
DWORD ProcessMtsOut(                    // RETURNS: zero
    IN LPVOID lpParameter)              // parameter
{
    HRESULT         hr                          = NOERROR;
    HRESULT         hrT                         = NOERROR;
    SCODE           sc                          = 0;
    ULONG           cbeid                       = 0;
    LPENTRYID       lpeid                       = NULL;
    ULONG           ulObjType                   = 0;
    ULONG           ulPropTag                   = 0;
    ULONG           cValues                     = 0;
    ULONG           i                           = 0;
    ULONG           j                           = 0;
    ULONG           cRows                       = 0;

    LPMESSAGE       lpEnvelope                  = NULL;

    ULONG           cbMessageClass              = 0;
    LPTSTR          lpszMessageClass            = NULL;

    LPSPropValue    lpProps                     = NULL;

LPSPropTagArray lpPropTagArray              = NULL;

    ULONG           cValuesT                    = 0;
    LPSPropValue    lpPropsT                    = NULL;

    DWORD           dwStartProcessingTime       = 0;

    LPADRBOOK       lpAdrBook                   = NULL;

    ULONG           cbMsgEid                    = 0;
    LPENTRYID       lpMsgEid                    = NULL;

    SizedSPropTagArray(1, rgRecipPropTag) =
    {
        1,
        {
            PR_MESSAGE_RECIPIENTS
        }
    };

    SizedSPropTagArray(1, rgRecordKeyPropTag) =
    {
        1,
        {
            PR_RECORD_KEY
        }
    };

    ULONG           cBytes                      = 0;
    LPTSTR          lpszRecordKey               = 0;

    PVOID           pvConvInstance              = NULL;

    LPIDITEM        lpIdItem                    = NULL;

    DEBUGPUBLIC("ProcessMtsOut()\n");

    //performance code

    // Initialize conversion engine instance data.
    hr = HrConvInitInstance(hEventLog, &pvConvInstance);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cRows       = GetGWSRowSet(lpParameter)->cRows;

    lpAdrBook   = GetAdrBookPtr();

    for(i = 0; i < cRows; i++)
    {
        dwStartProcessingTime = GetTickCount();

        cValues = GetGWSRowSet(lpParameter)->aRow[i].cValues;

        lpProps = GetGWSRowSet(lpParameter)->aRow[i].lpProps;

        cbeid = lpProps[0].Value.bin.cb;
        lpeid = (LPENTRYID)lpProps[0].Value.bin.lpb;

        cbMsgEid = cbeid;
        lpMsgEid = lpeid;

        cValues = 0;
        lpProps = NULL;

        // Get the message in the MTS-OUT folder
        hrT = MAPICALL(lpMtsOutFolder)->OpenEntry(
            lpMtsOutFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            &ulObjType,
            (LPUNKNOWN FAR *)&lpEnvelope);

        cbeid = 0;
        lpeid = NULL;

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(ulObjType != MAPI_MESSAGE)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------
        // Get the message ID

        MAPIFREEBUFFER(lpPropsT);
        MAPIFREEBUFFER(lpszRecordKey);

        cValuesT = 0;
        lpPropsT = NULL;

        hr = MAPICALL(lpEnvelope)->GetProps(
            lpEnvelope,
            (LPSPropTagArray)&rgRecordKeyPropTag,
    fMapiUnicode,
    &cValuesT,
    &lpPropsT);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(lpPropsT[0].ulPropTag != PR_RECORD_KEY)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(lpPropsT[0].Value.bin.cb == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(lpPropsT[0].Value.bin.lpb == NULL)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        cBytes = (lpPropsT[0].Value.bin.cb + sizeof(TCHAR)) * (2 * sizeof(TCHAR));

        hr = MAPIAllocateBuffer(cBytes, (void **)&lpszRecordKey);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        HexFromBin(lpPropsT[0].Value.bin.lpb, lpPropsT[0].Value.bin.cb, lpszRecordKey);

        MAPIFREEBUFFER(lpPropsT);

        hr = HrAddIdItem(
            &OutboundIdList,
            lpszRecordKey,
            &lpIdItem);

        if(hr == EDK_E_ALREADY_EXISTS)
        {
            //
            // Skip this message. It is already being processed
            // by another thread.
            //

            MAPIFREEBUFFER(lpPropsT);
            MAPIFREEBUFFER(lpszRecordKey);
            ULRELEASE(lpEnvelope);
            continue;
        }

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------
        // Get the message class

        MAPIFREEBUFFER(lpszMessageClass);

        cbMessageClass = 0;
        lpszMessageClass = NULL;

        hr = HrMAPIGetPropString(
            (LPMAPIPROP)lpEnvelope,
            PR_MESSAGE_CLASS,
            &cbMessageClass,
            (void **)&lpszMessageClass);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MODULE_WARNING1(
            "**** [OUT] Processing message of class [%s] ****",
            ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

        if(_tcsstr(lpszMessageClass, TEXT("ENVELOPE.IPM")) != NULL)
        {
            hr = HrExportMessage(
                lpAdrBook,
                FALSE,
                TRUE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".NDR")) != NULL))
        {
            hr = HrExportMessage(
                lpAdrBook,
                TRUE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".DR")) != NULL))
        {
            hr = HrExportMessage(
                lpAdrBook,
                TRUE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".IPNNRN")) != NULL))
        {
            hr = HrExportMessage(
                lpAdrBook,
                FALSE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else if((_tcsstr(lpszMessageClass, TEXT("REPORT.IPM")) != NULL) &&
                (_tcsstr(lpszMessageClass, TEXT(".IPNRN")) != NULL))
        {
            hr = HrExportMessage(
                lpAdrBook,
                FALSE,
                FALSE,
                dwStartProcessingTime,
                lpEnvelope);
        }
        else
        {
            hr = HrNonDeliverMtsOutMessage(
                lpszMessageClass,
                lpEnvelope);

            if(FAILED(hr))
            {
                ; // failed to non-deliver message
            }
        }

        if(FAILED(hr))
        {
            MODULE_ERROR1(
                "**** [OUT] Error while processing message of class [%s] ****",
                ((lpszMessageClass == NULL) ? "(null)" : lpszMessageClass));

            //
            // This message will be moved to the archive folder.
            //

            MAPIFREEBUFFER(lpszMessageClass);
            MAPIFREEBUFFER(lpPropsT);
            MAPIFREEBUFFER(lpszRecordKey);
            ULRELEASE(lpEnvelope);
            continue;
        }

    //------------------------------------------------------------------------------
        else
        {
            hr = HrConvertMtsOut(
                pvConvInstance,
                lpAdrBook,
                lpszMessageClass,
                lpEnvelope);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
    //------------------------------------------------------------------------------

        ULRELEASE(lpEnvelope);

        //performance code

        hr = HrMAPIDeleteMessage(
            lpMtsOutFolder,
            cbMsgEid,
            lpMsgEid);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

        hr = HrRemoveIdItem(&OutboundIdList, lpIdItem);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

    }

cleanup:

    MAPIFREEBUFFER(lpPropsT);

    MAPIFREEBUFFER(lpszRecordKey);

    MAPIFREEBUFFER(lpszMessageClass);

    ULRELEASE(lpEnvelope);

    if(pvConvInstance != NULL)
    {
        // De-initialize conversion engine instance data.
        (VOID)HrConvUninitInstance(pvConvInstance);
        pvConvInstance = NULL;
    }

    return(0);
}

// $--AppendSlash----------------------------------------------------------
//  This function will append a backslash to a pathname if one does not
//  already exist.
// ------------------------------------------------------------------------
void AppendSlash (                  // RETURNS:  Nothing
    IN OUT LPTSTR sz1)              // pathname
{
    LPTSTR sz2 = sz1;

    while (*sz2)                    //  Loop through each char until '\0'
    {
        sz1 = sz2;
        sz2 = CharNext(sz1);
    }

    //  Check there were no CharNext errors, then add a '\' if not there.
    if (sz2 != sz1 + 1 || *sz1 != TEXT('\\'))
    {
        *sz2++ = TEXT('\\');
        *sz2 = TEXT('\0');
    }
}

//$--GWMain---------------------------------------------------------------------
//  Start threads.
// -----------------------------------------------------------------------------
void GWMain(
    void)
{
    HRESULT      hrT                 = NOERROR;
    HRESULT      hr                  = NOERROR;

    EDK_SERVICE_CONTROL_T sc = 0;

    LPMAPIPROP   lpCurrentObject     = NULL;
    ULONG        ulExtensionDataTag  = 0;
    ULONG        cValues             = 0;
    LPSPropValue lpProps             = NULL;
    LPSBinary    lpBlobArray         = NULL;
    ULONG        cBlobArray          = 0;
    ULONG        i                   = 0;
    LPBYTE       lpbBlob             = NULL;
    ULONG        cbBlob              = 0;
    LPSPropValue lpUnpackedProps     = NULL;
    ULONG        cUnpackedProps      = 0;
    LPTSTR       lpszUnpackedName    = NULL;
    LPTSTR       lpszDir             = NULL;
    ULONG        cBytes              = 0;
    LPTSTR       lpsz                = NULL;

SizedSPropTagArray(1, rgPropTags) =
{
1,
{
0,
        }
    };

    DEBUGPUBLIC("GWMain()\n");

    //--------------------------------------------------------------------------

    //
    // Get a pointer to the current object
    //

hr = HrOpenSessionObject(
    lpSession,
        &lpCurrentObject);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Convert our MAPI Name ID to a property tag
    //
    hr = HrCfgPropertyTagFromNameId(
        lpCurrentObject,
        NM_EXTENSION_DATA,
        &ulExtensionDataTag);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Now get the properties we want from the site object
    //

    rgPropTags.aulPropTag[0] =
        PROP_TAG(MV_FLAG|PT_BINARY, PROP_ID(ulExtensionDataTag));

    hr = MAPICALL(lpCurrentObject)->GetProps(
        lpCurrentObject,
        (LPSPropTagArray)&rgPropTags,
fMapiUnicode,
&cValues,
&lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(PROP_TYPE(lpProps[0].ulPropTag) == PT_ERROR)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpBlobArray = lpProps->Value.MVbin.lpbin;
    cBlobArray  = lpProps->Value.MVbin.cValues;

    for(i = 0; i < cBlobArray; i++)

{
        if (!lstrcmpW(SGWBLOBNAMEW, (LPWSTR)lpBlobArray[i].lpb))
        {
            lpbBlob = lpBlobArray[i].lpb;
            cbBlob  = lpBlobArray[i].cb;
        }
    }

    hr = HrCfgUnpackData(
        cbBlob,
        lpbBlob,
        &lpszUnpackedName,
        &cUnpackedProps,
        &lpUnpackedProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //--------------------------------------------------------------------------

    if(cUnpackedProps < 17)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;

    }

    lpszAddressType     = lpUnpackedProps[IDX_ADDR_TYPE].Value.LPSZ;

    lpszSysAdminName    = lpUnpackedProps[IDX_DISP_NAME].Value.LPSZ;

    lpszSysAdminAddr    = lpUnpackedProps[IDX_EMAIL_ADDR].Value.LPSZ;

    cInThreads          = lpUnpackedProps[IDX_EDIT_THREADS_IN].Value.ul;

    cInMessages         = lpUnpackedProps[IDX_EDIT_MSGS_IN].Value.ul;

    dwInSleepInterval   = lpUnpackedProps[IDX_EDIT_SLEEP_IN].Value.ul;

    lpszInDirectory     = lpUnpackedProps[IDX_DIR_IN].Value.LPSZ;

    dwInPollInterval    = lpUnpackedProps[IDX_EDIT_POLLING_IN].Value.ul;

    lpszInConverter     = lpUnpackedProps[IDX_CONVERTER_IN].Value.LPSZ;

    fInEnabled          = lpUnpackedProps[IDX_CHECK_IN_BOUND].Value.b;

    cOutThreads         = lpUnpackedProps[IDX_EDIT_THREADS_OUT].Value.ul;

    cOutMessages        = lpUnpackedProps[IDX_EDIT_MSGS_OUT].Value.ul;

    dwOutSleepInterval  = lpUnpackedProps[IDX_EDIT_SLEEP_OUT].Value.ul;

    lpszOutDirectory    = lpUnpackedProps[IDX_DIR_OUT].Value.LPSZ;

    dwOutPollInterval   = lpUnpackedProps[IDX_EDIT_POLLING_OUT].Value.ul;

    lpszOutConverter    = lpUnpackedProps[IDX_CONVERTER_OUT].Value.LPSZ;

    fOutEnabled         = lpUnpackedProps[IDX_CHECK_OUT_BOUND].Value.b;

    hr = HrStrToStrW(lpszInConverter, &lpszInConverterW);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrStrToStrW(lpszOutConverter, &lpszOutConverterW);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Add trailing slash to inbound directory.
    //

    cBytes  = cbStrLen(lpszInDirectory) + sizeof(TCHAR);
    lpszDir = NULL;

    hr = MAPIAllocateMore(cBytes, lpUnpackedProps, (void **)&lpszDir);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lstrcpy(lpszDir, lpszInDirectory);
    AppendSlash(lpszDir);
    lpszInDirectory = lpszDir;

    //
    // Add trailing slash to outbound directory.
    //

    cBytes  = cbStrLen(lpszOutDirectory) + sizeof(TCHAR);
    lpszDir = NULL;

    hr = MAPIAllocateMore(cBytes, lpUnpackedProps, (void **)&lpszDir);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lstrcpy(lpszDir, lpszOutDirectory);
    AppendSlash(lpszDir);
    lpszOutDirectory = lpszDir;

    //
    // Add trailing slash to archive directory.
    //

    cBytes  = cbStrLen(lpszOutDirectory) +
              cbStrLen(TEXT("\\ARCHIVE")) +
              sizeof(TCHAR);

    lpszDir = NULL;

    hr = MAPIAllocateBuffer(cBytes, (void **)&lpszDir);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lstrcpy(lpszDir, lpszOutDirectory);
    AppendSlash(lpszDir);
    lpszArchiveDirectory = lpszDir;

    lpsz =  lpszArchiveDirectory;
    lpsz += lstrlen(lpszArchiveDirectory);
    lpsz --;
    *lpsz = 0;

    while(lpsz != lpszArchiveDirectory)
    {
        if((*lpsz == TEXT('\\')) || (*lpsz == TEXT(':')))
        {
            if(*lpsz == TEXT(':'))
                lpsz++;
            lstrcpy(lpsz, "\\ARCHIVE\\");
            break;
        }

        lpsz--;
    }

    //--------------------------------------------------------------------------

    if(fInEnabled == TRUE)
    {
        hr = HrGWStartNewFileHandler(
            dwInSleepInterval,
            dwInPollInterval,
            cInMessages,
            cInThreads,
            lpszAddressType,
            TEXT("SGW*.TMP"),
            lpszInDirectory,
            (LPTHREAD_START_ROUTINE)ProcessMtsIn);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        MODULE_WARNING("**** [IN] Processing not enabled ****");
    }

    //--------------------------------------------------------------------------

    if(fOutEnabled == TRUE)
    {
        hr = HrGWStartNewMailHandler(
            dwOutSleepInterval,
            dwOutPollInterval,
            cOutMessages,
            cOutThreads,
            lpszAddressType,
            lpszOutDirectory,
            (LPTHREAD_START_ROUTINE)ProcessMtsOut);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        MODULE_WARNING("**** [OUT] Processing not enabled ****");
    }

cleanup:

    if(FAILED(hr))
    {
        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    MAPIFREEBUFFER(lpProps);

    MAPIFREEBUFFER(lpszUnpackedName);

    ULRELEASE(lpCurrentObject);

    //
    // Wait for a request for the gateway to stop.
    //

    hr = HR_LOG(HrServiceWaitForStop(INFINITE, &sc));

    MAPIFREEBUFFER(lpszInConverterW);

    MAPIFREEBUFFER(lpszOutConverterW);

    MAPIFREEBUFFER(lpUnpackedProps);

    //
    // IMPORTANT: Return from this function only if the gateway is ready to stop.
    //

    return;
}

//$--HrGWLogon------------------------------------------------------------------
//  Logon to the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogon(                      // RETURNS: return code
    void)                               // no arguments
{
    HRESULT      hr                  = NOERROR;
    SCODE        sc                  = 0;
    BOOL         fStatus             = TRUE;
    LPTSTR       lpszT               = NULL;
    LPMAPIPROP   lpSiteAddressing    = NULL;
    ULONG        ulGatewayProxyTag   = 0;
    ULONG        cValues             = 0;
    LPSPropValue lpProps             = NULL;
    ULONG        i                   = 0;

SizedSPropTagArray(1, rgPropTags) =
{
1,
{
0,
        }
    };

    DEBUGPUBLIC("HrGWLogon()\n");

    InitializeCriticalSection(&csGatewaySeqNo);
    InitializeCriticalSection(&csInboundIdList);
    InitializeCriticalSection(&csOutboundIdList);

    InboundIdList.lpcs  = &csInboundIdList;
    OutboundIdList.lpcs = &csOutboundIdList;

    lpSession       = GetGWSession();
    lpStore         = GetGWDefaultStore();
    lpRootFolder    = GetGWRootFolder();
    lpMtsInFolder   = GetGWMtsInFolder();
    lpMtsOutFolder  = GetGWMtsOutFolder();
    cbGalEid        = GetGWGALEntryIdSize();
    lpGalEid        = GetGWGALEntryId();

    //
    // Get a pointer to the site addressing object

hr = HrOpenSiteContainerAddressing(
    lpSession,
        &lpSiteAddressing);

    if(FAILED(hr))
    {
        fStatus = FALSE;
        goto cleanup;
    }

    //
    // Convert our MAPI Name ID to a property tag
    //
    hr = HrCfgPropertyTagFromNameId(
        lpSiteAddressing,
        NM_GATEWAY_PROXY,
        &ulGatewayProxyTag);

    if(FAILED(hr))
    {
        fStatus = FALSE;
        goto cleanup;
    }

    //
    // Now get the properties we want from the site object
    //

    rgPropTags.aulPropTag[0] = ulGatewayProxyTag;

    hr = MAPICALL(lpSiteAddressing)->GetProps(
        lpSiteAddressing,
        (LPSPropTagArray)&rgPropTags,
fMapiUnicode,
&cValues,
&lpProps);

    if(FAILED(hr))
    {
        fStatus = FALSE;
        goto cleanup;
    }

    if(PROP_TYPE(lpProps[0].ulPropTag) == PT_ERROR)
    {
        hr = HR_LOG(E_FAIL);

        fStatus = FALSE;
        goto cleanup;
    }

    if(lpProps[0].Value.MVSZ.cValues == 0)
    {
        hr = HR_LOG(E_FAIL);

        fStatus = FALSE;
        goto cleanup;
    }

    for(i = 0; i < (lpProps[0].Value.MVSZ.cValues); i++)
    {
        if(!_tcsnicmp(
            lpProps[0].Value.MVSZ.LPPSZ[i],
            TEXT("X400:"),
            lstrlen(TEXT("X400:"))))
        {
            break;
        }
    }

    sc = MAPIAllocateBuffer(
        cbStrLen(lpProps[0].Value.MVSZ.LPPSZ[i]),
        (void **)&lpszT);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(lpszT, lpProps[0].Value.MVSZ.LPPSZ[i]);

    //
    // Create the local enterprise GDI.
    //

    hr = HrCreateEnterpriseGDI(
        lpszT,
        TEXT(":;"),
        &lpszLocalGDI);

    if(FAILED(hr))
    {
        fStatus = FALSE;
        goto cleanup;
    }

    // Open an event log for this application.
    hr = HrEventOpenLog(
        szAppName,          // application name
        NULL,               // executable name (will be computed)
        TEXT("EDKMSG.DLL"), // event message file
        NULL,               // parameter message file (will be computed)
        NULL,               // category message file (will be computed)
        &hEventLog);        // event source handle

    if ( FAILED(hr) || (hEventLog == NULL) )
    {
        goto cleanup;
    }

    // Initialize global conversion engine data.
    hr = HrConvInitGlobals();

    if ( FAILED(hr) )
    {
        fStatus = FALSE;
        goto cleanup;
    }

cleanup:

    ULRELEASE(lpSiteAddressing);

    MAPIFREEBUFFER(lpProps);

    MAPIFREEBUFFER(lpszT);

    if(fStatus == FALSE)
    {
        //
        // Gateway logon failed.
        //

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    RETURN(hr);
}

//$--HrArchiveMtsOut------------------------------------------------------------
//  Archive messages in MTS-OUT which could not be delivered.
// -----------------------------------------------------------------------------
HRESULT HrArchiveMtsOut(                // RETURNS: zero
    VOID)                               // parameter
{
    HRESULT         hr                          = NOERROR;
    ULONG           cbeid                       = 0;
    LPENTRYID       lpeid                       = NULL;
    ULONG           ulObjType                   = 0;
    ULONG           ulPropTag                   = 0;
    ULONG           cValues                     = 0;
    ULONG           i                           = 0;
    ULONG           cRows                       = 0;

    LPMAPIFOLDER    lpArchiveFolder             = NULL;
    LPMAPITABLE     lpTable                     = NULL;

    LPMESSAGE       lpEnvelope                  = NULL;

    LPSPropValue    lpProps                     = NULL;

LPSPropTagArray lpPropTagArray              = NULL;

    ULONG           cValuesT                    = 0;
    LPSPropValue    lpPropsT                    = NULL;

    ULONG           cbMsgEid                    = 0;
    LPENTRYID       lpMsgEid                    = NULL;

    LPSRowSet       lpRows                      = NULL;

    SizedSPropTagArray(1, rgRecordKeyPropTag) =
    {
        1,
        {
            PR_RECORD_KEY
        }
    };

    ULONG           cBytes                      = 0;
    LPTSTR          lpszRecordKey               = 0;

    LPIDITEM        lpIdItem                    = NULL;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            PR_ENTRYID
        }
    };

    DEBUGPUBLIC("HrArchiveMtsOut()\n");

    //
    // Find and open the MTS-OUT archive folder.
    //

    hr = HrMAPIFindFolder(
        GetGWRootFolder(),
        TEXT("ARCHIVE"),
        &cbeid,
        &lpeid);

    if(FAILED(hr))
    {
        hr = MAPICALL(lpRootFolder)->CreateFolder(
            lpRootFolder,
            FOLDER_GENERIC,
            TEXT("ARCHIVE"),
            NULL,
            NULL,
            MAPI_DEFERRED_ERRORS,
            &lpArchiveFolder);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }
    else
    {
        // Open the archive folder in the root folder
        hr = MAPICALL(lpRootFolder)->OpenEntry(
            lpRootFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            &ulObjType,
            (LPUNKNOWN FAR *) &lpArchiveFolder);

        MAPIFREEBUFFER(lpeid);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(ulObjType != MAPI_FOLDER)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    //
    // Get the contents table for the MTS-OUT
    //

    hr = MAPICALL(lpMtsOutFolder)->GetContentsTable(
        lpMtsOutFolder,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = MAPICALL(lpTable)->SetColumns(
        lpTable,
        (LPSPropTagArray)&rgPropTag,
        TBL_BATCH);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Query all the rows
    //

    hr = HrQueryAllRows(
    lpTable,
    NULL,
    NULL,
    NULL,
    0,
    &lpRows);

    ULRELEASE(lpTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    cRows = lpRows->cRows;

    for(i = 0; i < cRows; i++)
    {
        cValues = lpRows->aRow[i].cValues;

        lpProps = lpRows->aRow[i].lpProps;

        cbeid = lpProps[0].Value.bin.cb;
        lpeid = (LPENTRYID)lpProps[0].Value.bin.lpb;

        cbMsgEid = cbeid;
        lpMsgEid = lpeid;

        cValues = 0;
        lpProps = NULL;

        // Get the message in the MTS-OUT folder
        hr = MAPICALL(lpMtsOutFolder)->OpenEntry(
            lpMtsOutFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
            &ulObjType,
            (LPUNKNOWN FAR *)&lpEnvelope);

        cbeid = 0;
        lpeid = NULL;

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(ulObjType != MAPI_MESSAGE)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

        // Get the message ID

        MAPIFREEBUFFER(lpPropsT);
        MAPIFREEBUFFER(lpszRecordKey);

        cValuesT = 0;
        lpPropsT = NULL;

        hr = MAPICALL(lpEnvelope)->GetProps(
            lpEnvelope,
            (LPSPropTagArray)&rgRecordKeyPropTag,
    fMapiUnicode,
    &cValuesT,
    &lpPropsT);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(lpPropsT[0].ulPropTag != PR_RECORD_KEY)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(lpPropsT[0].Value.bin.cb == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(lpPropsT[0].Value.bin.lpb == NULL)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        cBytes = (lpPropsT[0].Value.bin.cb + sizeof(TCHAR)) * (2 * sizeof(TCHAR));

        hr = MAPIAllocateBuffer(cBytes, (void **)&lpszRecordKey);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        HexFromBin(lpPropsT[0].Value.bin.lpb, lpPropsT[0].Value.bin.cb, lpszRecordKey);

        MAPIFREEBUFFER(lpPropsT);

        hr = HrLookupIdItem(
            &OutboundIdList,
            lpszRecordKey,
            &lpIdItem);

        if(hr == EDK_E_NOT_FOUND)
        {
            //
            // Skip this message. It is a new message.
            //

            MAPIFREEBUFFER(lpszRecordKey);
            MAPIFREEBUFFER(lpPropsT);
            ULRELEASE(lpEnvelope);
            continue;
        }

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    //------------------------------------------------------------------------------

        MAPIFREEBUFFER(lpszRecordKey);
        MAPIFREEBUFFER(lpPropsT);
        ULRELEASE(lpEnvelope);

    //------------------------------------------------------------------------------

        hr = HrMAPIMoveMessage(
            lpMtsOutFolder,
            lpArchiveFolder,
            cbMsgEid,
            lpMsgEid);

        if(FAILED(hr))
        {
            ; // create an entry in the debug log
        }
    }

cleanup:

    FREEPROWS(lpRows);

    MAPIFREEBUFFER(lpPropsT);

    MAPIFREEBUFFER(lpszRecordKey);

    ULRELEASE(lpEnvelope);

    ULRELEASE(lpArchiveFolder);

    ULRELEASE(lpTable);

    RETURN(hr);
}

//$--HrArchiveMtsIn-------------------------------------------------------------
//  Archive messages in MTS-IN which could not be delivered.
// -----------------------------------------------------------------------------
HRESULT HrArchiveMtsIn(                 // RETURNS: return code
    VOID)                               // no arguments
{
HRESULT  hr                       = NOERROR;
    HRESULT  hrT                      = NOERROR;
    LPIDITEM lpIdItem                 = NULL;
    BOOL     fRet                     = FALSE;
    TCHAR    rgchFileName[MAX_PATH+1] = {0};
    TCHAR    rgchPathName[MAX_PATH+1] = {0};
    LPTSTR   lpszPath                 = NULL;
    LPIDITEM lpIdList                 = NULL;

    //
    // Find and open the MTS-IN archive directory.
    //

    lstrcpy(rgchPathName, lpszArchiveDirectory);

    fRet = CreateDirectory(rgchPathName, NULL);

    if(fRet == FALSE)
    {
        hr = HR_LOG(E_FAIL);
    }

    lpszPath = rgchPathName;

    lpIdList = &InboundIdList;

    EnterCriticalSection(lpIdList->lpcs);

    __try
    {
        lpIdItem = lpIdList->next;

        while(lpIdItem != NULL)
        {
            lpIdList->next = lpIdItem->next;

            rgchFileName[0] = 0;

        fRet = GetTempFileName32(lpszPath, TEXT("BAK"), 0, (LPTSTR)rgchFileName);

            if(fRet == FALSE)
        {
        hr = HR_LOG(E_FAIL);
        }
            else
            {
                fRet = MoveFileEx(
                    lpIdItem->lpszId,
                    (LPTSTR)rgchFileName,
                    MOVEFILE_COPY_ALLOWED|MOVEFILE_REPLACE_EXISTING);

                if(fRet == FALSE)
                {
                    hr = HR_LOG(E_FAIL);
                }
            }

            lpIdItem = lpIdList->next;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    LeaveCriticalSection(lpIdList->lpcs);

    RETURN(hr);
}

//$--HrGWLogoff-----------------------------------------------------------------
//  Logoff of the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogoff(                     // RETURNS: return code
    void)                               // no arguments
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrGWLogoff()\n");

    // De-initialize conversion engine global data.
    ConvUninitGlobals();

    // Close the event log.
    (VOID)HrEventCloseLog();
    hEventLog = NULL;

    MAPIFREEBUFFER(lpbBuffer);

    MAPIFREEBUFFER(lppszBufferArray);

    MAPIFREEBUFFER(lpszLocalGDI);

    //
    // Archive messages which could not be delivered.
    //

    hrT = HR_LOG(HrArchiveMtsIn());

    hrT = HR_LOG(HrArchiveMtsOut());

    hrT = HR_LOG(HrEmptyIdList(&InboundIdList));

    hrT = HR_LOG(HrEmptyIdList(&OutboundIdList));

    DeleteCriticalSection(&csGatewaySeqNo);
    DeleteCriticalSection(&csInboundIdList);
    DeleteCriticalSection(&csOutboundIdList);

    MAPIFREEBUFFER(lpszArchiveDirectory);

    RETURN(hr);
}


GWSAMPLE.H

// --gwsample.h-----------------------------------------------------------------
//
// Sample gateway application.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _GWSAMPLE_H
#define _GWSAMPLE_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

#define SYSTEM_ADMINISTRATOR    TEXT("System Administrator")
#define SYSTEM                  TEXT("SYSTEM")

extern LPTSTR  lpszSysAdminName;
extern LPTSTR  lpszSysAdminAddr;

#define IDISPNAME  0
#define IADDRTYPE  1
#define IEMAILADDR 2
#define ISEARCHKEY 3
#define IENTRYID   4
#define IMAPIRECIP 5
#define IRESP      6
#define IRECIPNUM  7
#define IORIGDISP  8
#define IORIGENTRY 9
#define IPROXYADDR 10

#define IRECIPSTAT 10

#define MIN_CRECIPPROPS 11

#define PR_RECIPSTAT PROP_TAG(PT_LONG, 0x65FF)

//$--HrMapRecipProxyToAddr------------------------------------------------------
// Map recipient proxy addresses to addresses.
// -----------------------------------------------------------------------------
HRESULT HrMapRecipProxyToAddr(          // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN BOOL fDefaultRespValue,          // default responsibility value
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN LPABCONT lpGalABCont,            // pointer to GAL container
    IN LPTSTR lpszAddrType,             // pointer to address type
    IN LPADRLIST lpOriginalAdrList,     // pointer to unmodified address list
    IN LPADRLIST lpMappedAdrList,       // pointer to unmodified address list
    IN OUT LPADRLIST lpEnvelopeAdrList, // pointer to envelope address list
    IN OUT LPADRLIST lpEnvelopeNDRAdrList,// pointer to envelope NDR address list
    IN OUT LPMESSAGE lpMessage,         // pointer to message
    OUT LPADRLIST *lppAdrList,          // pointer to address list
    OUT LPADRLIST *lppNDRAdrList);      // pointer to NDR address list

//$--HrCreateEmailAddress-------------------------------------------------------
// Create email address.
// -----------------------------------------------------------------------------
HRESULT HrCreateEmailAddress(               // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPVOID     lpObject,                 // pointer to object
    IN LPTSTR     lpszDisplayName,          // pointer to display name
    IN LPTSTR     lpszAddressType,          // pointer to address type
    IN LPTSTR     lpszAddress,              // pointer to address
    IN ULONG      cbeid,                    // count of bytes in entry ID
    IN LPENTRYID  lpeid,                    // pointer to entry ID
    OUT LPTSTR    *lppszEmailDisplayName,   // email display name
    OUT LPTSTR    *lppszEmailAddressType,   // email address type
    OUT LPTSTR    *lppszEmailAddress,       // email address
    OUT LPTSTR    *lppszEmailSearchKey,     // email search key
    OUT ULONG     *lpcbEmailEid,            // count of bytes in email entry ID
    OUT LPENTRYID *lppEmailEid);            // pointer to email entry ID

//$--HrDeleteAddressListProperty------------------------------------------------
// Delete a property from an address list given its property tag.
// -----------------------------------------------------------------------------
HRESULT HrDeleteAddressListProperty(    // RETURNS: return code
    IN ULONG ulPropTag,                 // property tag
    IN OUT LPADRLIST lpAdrList);        // pointer to address list

//$--HrMapRecipAddrToProxy------------------------------------------------------
// Map recipient addresses to proxy addresses.
// -----------------------------------------------------------------------------
HRESULT HrMapRecipAddrToProxy(          // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN BOOL fDefaultRespValue,          // default responsibility value
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN LPTSTR lpszAddrType,             // pointer to address type
    IN LPADRLIST lpOriginalEnvelopeAdrList,// pointer to unmodified address list
    IN LPADRLIST lpOriginalAdrList,     // pointer to unmodified address list
    IN LPADRLIST lpMappedAdrList,       // pointer to unmodified address list
    IN OUT LPADRLIST lpEnvelopeAdrList, // pointer to envelope address list
    IN OUT LPADRLIST lpEnvelopeNDRAdrList,// pointer to envelope NDR address list
    IN OUT LPMESSAGE lpMessage,         // pointer to message
    OUT LPADRLIST *lppAdrList,          // pointer to address list
    OUT LPADRLIST *lppNDRAdrList);      // pointer to NDR address list

//$--HrDeleteAddressProps--------------------------------------------------------
// Delete address properties from the message.
// -----------------------------------------------------------------------------
HRESULT HrDeleteAddressProps(           // RETURNS: return code
    IN LPMESSAGE  lpMessage,            // pointer to message
    IN ULONG ulDisplayName,             // display name property tag
    IN ULONG ulAddressType,             // address type property tag
    IN ULONG ulAddress,                 // address property tag
    IN ULONG ulSearchKey,               // search key property tag
    IN ULONG ulEntryId);                // entry ID property tag

//$--HrCreateProxyAddress-------------------------------------------------------
// Create proxy address.
// -----------------------------------------------------------------------------
HRESULT HrCreateProxyAddress(               // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPVOID     lpObject,                 // pointer to object
    IN BOOL       fMapiRecip,               // MAPI recipient
    IN LPTSTR     lpszDisplayName,          // pointer to display name
    IN LPTSTR     lpszAddressType,          // pointer to address type
    IN LPTSTR     lpszAddress,              // pointer to address
    OUT LPTSTR    *lppszProxyDisplayName,   // proxy display name
    OUT LPTSTR    *lppszProxyAddressType,   // proxy address type
    OUT LPTSTR    *lppszProxyAddress,       // proxy address
    OUT LPTSTR    *lppszProxySearchKey,     // proxy search key
    OUT ULONG     *lpcbProxyEid,            // count of bytes in proxy entry ID
    OUT LPENTRYID *lppProxyEid);            // pointer to proxy entry ID

//$--HrSetUlongPropertyValue----------------------------------------------------
// Set a ULONG property value.
// -----------------------------------------------------------------------------
HRESULT HrSetUlongPropertyValue(        // RETURNS: return code
    IN BOOL  fOverwrite,                // overwrite value if it exists
    IN ULONG ulPropTag,                 // property tag
    IN ULONG ulValue,                   // property value
    IN ULONG cValues,                   // count of property values
    IN LPSPropValue lpProps,            // pointer to property values
    OUT ULONG* lpcValues,               // new count of property values
    OUT LPSPropValue* lppProps);        // new pointer to property values

//$--HrNonDeliverRecipient------------------------------------------------------
// Non-deliver a recipient.
// -----------------------------------------------------------------------------
HRESULT HrNonDeliverRecipient(          // RETURNS: return code
    IN ULONG ulReasonCode,              // NDR reason code
    IN ULONG ulDiagnosticCode,          // NDR diagnostic code
    IN LPFILETIME lpftReportTime,       // report time
    IN ULONG ulRecipientNumber,         // recipient number
    IN ULONG ulOffset,                  // offset in NDR address list
    IN ULONG cValues,                   // count of property values
    IN LPSPropValue lpProps,            // pointer to property values
    IN OUT LPADRLIST lpAdrList);        // pointer to NDR address list

//$--HrExpandMessageAddressList-------------------------------------------------
// Expand a list of addresses on the message.
// -----------------------------------------------------------------------------
HRESULT HrExpandMessageAddressList(     // RETURNS: return code
    IN LPMESSAGE  lpMessage,            // pointer to message
    OUT ULONG*    lpcMesgAddr,          // count of message addresses
    OUT ULONG*    lpcReplyAddr,         // count of reply recipient addresses
    OUT ULONG*    lpcRecipAddr,         // count of recipient addresses
    OUT LPADRLIST *lppRecipList,        // pointer to recipient address list
    OUT LPADRLIST *lppAdrList);         // pointer to address list

//$--HrMapAddrToProxy-----------------------------------------------------------
// Map all email addresses to proxy addresses in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapAddrToProxy(               // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList);        // pointer to address list

//$--HrCollapseMessageAddressList-----------------------------------------------
// Collapse a list of addresses on the message.
// -----------------------------------------------------------------------------
HRESULT HrCollapseMessageAddressList(   // RETURNS: return code
    IN LPMESSAGE     lpMessage,         // pointer to message
    IN ULONG         cMesgAddr,         // count of message addresses
    IN ULONG         cReplyAddr,        // count of reply recipient addresses
    IN ULONG         cRecipAddr,        // count of recipient addresses
    IN LPADRLIST     lpAdrList,         // message address list
    OUT ULONG        *lpcValues,        // count of message address properties
    OUT LPSPropValue *lppProps,         // message address properties
    OUT LPADRLIST    *lppAdrList);      // recipient address list

//$--HrMapProxyToAddr-----------------------------------------------------------
// Map all proxy addresses to email addresses in a given address list. Always
// call HrMapAddrToProxy() before calling this function.
// -----------------------------------------------------------------------------
HRESULT HrMapProxyToAddr(               // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPABCONT     lpGalABCont,       // pointer to GAL container
    IN OUT LPADRLIST lpAdrList);        // pointer to address list

//$--HrMapGatewayAddrToProxy----------------------------------------------------
// Map all gateway specific email addresses to proxy addresses
// in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapGatewayAddrToProxy(        // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList);        // pointer to address list

//$--HrMapGatewayProxyToAddr----------------------------------------------------
// Map all gateway specific email addresses to proxy addresses
// in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapGatewayProxyToAddr(        // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList);        // pointer to address list

#ifdef __cplusplus
}
#endif // __cplusplus

#endif


IADDR.C

// --iaddr.c--------------------------------------------------------------------
//
//  Find an address given a proxy address.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwsample.h"

//$--HrMapGatewayProxyToAddr----------------------------------------------------
// Map all gateway specific email addresses to proxy addresses
// in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapGatewayProxyToAddr(        // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList)         // pointer to address list
{
    HRESULT         hr                   = NOERROR;
    SCODE           sc                   = 0;
    ULONG           i                    = 0;
    ULONG           j                    = 0;
    ULONG           cbAddress            = 0;
    ULONG           cProxy               = 0;
    LPSPropValue    lpProps              = NULL;
    LPTSTR          lpszProxyAddress     = NULL;

    LPTSTR          lpszEmailDisplayName = NULL;
    LPTSTR          lpszEmailAddressType = NULL;
    LPTSTR          lpszEmailAddress     = NULL;
    LPTSTR          lpszEmailSearchKey   = NULL;
    ULONG           cbEmailEid           = 0;
    LPENTRYID       lpEmailEid           = NULL;

    DEBUGPUBLIC("HrMapGatewayProxyToAddr()\n");

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        lpProps = lpAdrList->aEntries[i].rgPropVals;

        if((lpProps[IDISPNAME].ulPropTag == PR_DISPLAY_NAME) &&
           ((lstrcmpi(lpszSysAdminName, lpProps[IDISPNAME].Value.LPSZ) == 0) ||
            (lstrcmpi(TEXT("."), lpProps[IDISPNAME].Value.LPSZ) == 0) ||
            (lstrcmpi(lpszSysAdminAddr, lpProps[IDISPNAME].Value.LPSZ) == 0)))

        {
            lpszEmailDisplayName = NULL;
            lpszEmailAddressType = NULL;
            lpszEmailAddress     = NULL;
            lpszEmailSearchKey   = NULL;
            cbEmailEid           = 0;
            lpEmailEid           = NULL;

            hr = HrCreateProxyAddress(
                lpAdrBook,
                lpProps,
                TRUE,
                SYSTEM_ADMINISTRATOR,
                SYSTEM,
                SYSTEM_ADMINISTRATOR,
                &lpszEmailDisplayName,
                &lpszEmailAddressType,
                &lpszEmailAddress,
                &lpszEmailSearchKey,
                &cbEmailEid,
                &lpEmailEid);

            if(FAILED(hr))
            {
                goto cleanup;
            }

            lpProps[IDISPNAME].ulPropTag      = PR_DISPLAY_NAME;
            lpProps[IDISPNAME].Value.LPSZ     = lpszEmailDisplayName;

            lpProps[IADDRTYPE].ulPropTag      = PR_ADDRTYPE;
            lpProps[IADDRTYPE].Value.LPSZ     = lpszEmailAddressType;

            lpProps[IEMAILADDR].ulPropTag     = PR_EMAIL_ADDRESS;
            lpProps[IEMAILADDR].Value.LPSZ    = lpszEmailAddress;

            lpProps[ISEARCHKEY].ulPropTag = PR_SEARCH_KEY;

            if(lpszEmailSearchKey == NULL)
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = 0;
                lpProps[ISEARCHKEY].Value.bin.lpb = NULL;
            }
            else
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = cbStrLen(lpszEmailSearchKey);
                lpProps[ISEARCHKEY].Value.bin.lpb = lpszEmailSearchKey;
            }

            lpProps[IENTRYID].ulPropTag       = PR_ENTRYID;
            lpProps[IENTRYID].Value.bin.cb    = cbEmailEid;
            lpProps[IENTRYID].Value.bin.lpb   = (LPBYTE)lpEmailEid;
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrMapRecipProxyToAddr------------------------------------------------------
// Map recipient proxy addresses to addresses.
// -----------------------------------------------------------------------------
HRESULT HrMapRecipProxyToAddr(          // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN BOOL fDefaultRespValue,          // default responsibility value
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN LPABCONT lpGalABCont,            // pointer to GAL container
    IN LPTSTR lpszAddrType,             // pointer to address type
    IN LPADRLIST lpOriginalAdrList,     // pointer to unmodified address list
    IN LPADRLIST lpMappedAdrList,       // pointer to unmodified address list
    IN OUT LPADRLIST lpEnvelopeAdrList, // pointer to envelope address list
    IN OUT LPADRLIST lpEnvelopeNDRAdrList,// pointer to envelope NDR address list
    IN OUT LPMESSAGE lpMessage,         // pointer to message
    OUT LPADRLIST *lppAdrList,          // pointer to address list
    OUT LPADRLIST *lppNDRAdrList)       // pointer to NDR address list
{
    HRESULT         hrT                  = NOERROR;
    HRESULT         hr                   = NOERROR;
    SCODE           sc                   = 0;
    LPMAPITABLE     lpTable              = NULL;

    ULONG           cbeid                = 0;
    LPENTRYID       lpeid                = NULL;

    ULONG           cRows                = 0;
    LPSRowSet       lpRows               = 0;

    LPSPropTagArray lpPropTagArray       = NULL;

    ULONG           nName                = 0;
    ULONG           nAddrType            = 0;
    ULONG           nAddress             = 0;
    ULONG           nEntryId             = 0;
    ULONG           nSearchKey           = 0;
    ULONG           nRecipStat           = 0;

    ULONG           i                    = 0;
    ULONG           iAck                 = 0;
    ULONG           iNak                 = 0;

    ULONG           cValues              = 0;
    LPSPropValue    lpProps              = NULL;

    LPSPropValue    lpEnvelopeProps      = NULL;
    LPSPropValue    lpMessageProps       = NULL;

    ULONG           cbSearchKey          = 0;
    LPTSTR          lpszSearchKey        = NULL;

    BOOL            fRespTrue            = FALSE;

    FILETIME        ftReportTime         = {0};

    LPSPropValue    lpPropT              = NULL;

    ULONG           cNewProps            = 0;
    LPSPropValue    lpNewProps           = NULL;

    ULONG           cMapValues           = 0;
    LPSPropValue    lpMapProps           = NULL;

    BOOL            fEnv                 = FALSE;

    DEBUGPUBLIC("HrMapRecipProxyToAddr()\n");

    *lppAdrList    = NULL;
    *lppNDRAdrList = NULL;

    //
    // Get the current time.
    //

    GetSystemTimeAsFileTime( &ftReportTime);

    nName      = IDISPNAME;
    nAddrType  = IADDRTYPE;
    nAddress   = IEMAILADDR;
    nEntryId   = IENTRYID;
    nSearchKey = ISEARCHKEY;
    nRecipStat = IRECIPSTAT;

    cRows  = lpOriginalAdrList->cEntries;
    lpRows = (LPSRowSet)lpOriginalAdrList;

    if(lpRows == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(lpEnvelopeAdrList != NULL)
    {
        //
        // Clear recipient numbers if this is not an envelope address list.
        //

        hr = HrDeleteAddressListProperty(
            PR_RECIPIENT_NUMBER,
            (LPADRLIST)lpRows);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = HrMAPICreateSizedAddressList(
        cRows,
        lppAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPICreateSizedAddressList(
        cRows,
        lppNDRAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < cRows; i++)
    {
        //
        // Preserve original recipient number for original recipient entry
        //

        cValues = lpRows->aRow[i].cValues;
        lpProps = lpRows->aRow[i].lpProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            FALSE,
            PR_RECIPIENT_NUMBER,
            (i+1),
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpRows->aRow[i].cValues = cNewProps;
        lpRows->aRow[i].lpProps = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original recipient number for mapped recipient entry
        //

        cValues = lpMappedAdrList->aEntries[i].cValues;
        lpProps = lpMappedAdrList->aEntries[i].rgPropVals;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            FALSE,
            PR_RECIPIENT_NUMBER,
            (i+1),
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpMappedAdrList->aEntries[i].cValues = cNewProps;
        lpMappedAdrList->aEntries[i].rgPropVals = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original row number for original recipient entry
        //

        cValues = lpRows->aRow[i].cValues;
        lpProps = lpRows->aRow[i].lpProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            TRUE,
            PR_ROWID,
            i,
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpRows->aRow[i].cValues = cNewProps;
        lpRows->aRow[i].lpProps = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original row number for mapped recipient entry
        //

        cValues = lpMappedAdrList->aEntries[i].cValues;
        lpProps = lpMappedAdrList->aEntries[i].rgPropVals;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            TRUE,
            PR_ROWID,
            i,
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpMappedAdrList->aEntries[i].cValues = cNewProps;
        lpMappedAdrList->aEntries[i].rgPropVals = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Mapped recipient entry (read-only)
        //

        cMapValues = lpMappedAdrList->aEntries[i].cValues;
        lpMapProps = lpMappedAdrList->aEntries[i].rgPropVals;

        //
        // Find the email address associated with a proxy address,
        //

        //
        // Get the recipient responsibility
        //

        lpPropT = LpValFindProp(PR_RESPONSIBILITY, cMapValues, lpMapProps);

        if(lpPropT == NULL)
        {
            fRespTrue = fDefaultRespValue;
        }
        else
        {
            fRespTrue = lpPropT->Value.b;
        }

        if(fIgnoreRespValue == TRUE)
        {
            fRespTrue = FALSE;
        }

        //
        // NOTE: If the recipient address type is the same as the email
        // address type then the address was mapped.
        //

        if((lpMapProps[IADDRTYPE].ulPropTag != PR_ADDRTYPE) ||
           (lpMapProps[IADDRTYPE].Value.LPSZ == NULL))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        else
        {
            if( (lstrcmpi(lpMapProps[IADDRTYPE].Value.LPSZ,
                         TEXT(EXCHANGE_ADDRTYPE)) != 0) &&
                (fRespTrue == TRUE) )
            {
                MODULE_WARNING("email address was not found");

                if(lpMapProps[IRECIPSTAT].Value.ul != NOERROR)
                {
                    //
                    // Non-deliver recipient
                    //

                    hr = HrNonDeliverRecipient(
                        MAPI_REASON_TRANSFER_IMPOSSIBLE,
                        (ULONG)MAPI_DIAG_OR_NAME_AMBIGUOUS,
                        &ftReportTime,
                        (ULONG)-1,
                        iNak,
                        cMapValues,
                        lpMapProps,
                        *lppNDRAdrList);

                    if(FAILED(hr))
                    {
                        goto cleanup;
                    }

                    iNak++;
                }
                else
                {
                    goto skip;
                }
            }
            else
            {
skip:
                hr = HrMAPISetAddressList(
                    iAck,
                    cMapValues,
                    lpMapProps,
                    *lppAdrList);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                iAck++;
            }
        }
    }

    (*lppAdrList)->cEntries    = iAck;
    (*lppNDRAdrList)->cEntries = iNak;

cleanup:

    if(FAILED(hr))
    {
        if(*lppAdrList != NULL)
        {
            FREEPADRLIST(*lppAdrList);
        }

        if(*lppNDRAdrList != NULL)
        {
            FREEPADRLIST(*lppNDRAdrList);
        }

    }

    MAPIFREEBUFFER(lpNewProps);

    RETURN(hr);
}


OADDR.C

// --oaddr.c---------------------------------------------------------------------
//
//  Find an address given a proxy address.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "_entryid.h"
#include "gwmain.h"
#include "gwsample.h"

//$--HrMapGatewayAddrToProxy----------------------------------------------------
// Map all gateway specific email addresses to proxy addresses
// in a given address list.
// -----------------------------------------------------------------------------
HRESULT HrMapGatewayAddrToProxy(        // RETURNS: return code
    IN  LPADRBOOK    lpAdrBook,         // pointer to address book
    IN  LPTSTR       lpszAddrType,      // pointer to the address type
    IN OUT LPADRLIST lpAdrList)         // pointer to address list
{
    HRESULT         hr                   = NOERROR;
    SCODE           sc                   = 0;
    ULONG           i                    = 0;
    ULONG           j                    = 0;
    ULONG           cbAddress            = 0;
    ULONG           cProxy               = 0;
    LPSPropValue    lpProps              = NULL;
    LPTSTR          lpszProxyAddress     = NULL;

    LPTSTR          lpszEmailDisplayName = NULL;
    LPTSTR          lpszEmailAddressType = NULL;
    LPTSTR          lpszEmailAddress     = NULL;
    LPTSTR          lpszEmailSearchKey   = NULL;
    ULONG           cbEmailEid           = 0;
    LPENTRYID       lpEmailEid           = NULL;

    DEBUGPUBLIC("HrMapGatewayAddrToProxy()\n");

    for(i = 0; i < lpAdrList->cEntries; i++)
    {
        lpProps = lpAdrList->aEntries[i].rgPropVals;

        if((lpProps[IDISPNAME].ulPropTag == PR_DISPLAY_NAME) &&
           (lstrcmpi(SYSTEM_ADMINISTRATOR, lpProps[IDISPNAME].Value.LPSZ) == 0))
        {
            lpszEmailDisplayName = NULL;
            lpszEmailAddressType = NULL;
            lpszEmailAddress     = NULL;
            lpszEmailSearchKey   = NULL;
            cbEmailEid           = 0;
            lpEmailEid           = NULL;

            hr = HrCreateProxyAddress(
                lpAdrBook,
                lpProps,
                TRUE,
                lpszSysAdminName,
                lpszAddrType,
                lpszSysAdminAddr,
                &lpszEmailDisplayName,
                &lpszEmailAddressType,
                &lpszEmailAddress,
                &lpszEmailSearchKey,
                &cbEmailEid,
                &lpEmailEid);

            if(FAILED(hr))
            {
                goto cleanup;
            }

            lpProps[IDISPNAME].ulPropTag      = PR_DISPLAY_NAME;
            lpProps[IDISPNAME].Value.LPSZ     = lpszEmailDisplayName;

            lpProps[IADDRTYPE].ulPropTag      = PR_ADDRTYPE;
            lpProps[IADDRTYPE].Value.LPSZ     = lpszEmailAddressType;

            lpProps[IEMAILADDR].ulPropTag     = PR_EMAIL_ADDRESS;
            lpProps[IEMAILADDR].Value.LPSZ    = lpszEmailAddress;

            lpProps[ISEARCHKEY].ulPropTag = PR_SEARCH_KEY;

            if(lpszEmailSearchKey == NULL)
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = 0;
                lpProps[ISEARCHKEY].Value.bin.lpb = NULL;
            }
            else
            {
                lpProps[ISEARCHKEY].Value.bin.cb  = cbStrLen(lpszEmailSearchKey);
                lpProps[ISEARCHKEY].Value.bin.lpb = lpszEmailSearchKey;
            }

            lpProps[IENTRYID].ulPropTag       = PR_ENTRYID;
            lpProps[IENTRYID].Value.bin.cb    = cbEmailEid;
            lpProps[IENTRYID].Value.bin.lpb   = (LPBYTE)lpEmailEid;
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrMapRecipAddrToProxy------------------------------------------------------
// Map recipient addresses to proxy addresses.
// -----------------------------------------------------------------------------
HRESULT HrMapRecipAddrToProxy(          // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN BOOL fDefaultRespValue,          // default responsibility value
    IN BOOL fIgnoreRespValue,           // ignore  responsibility value
    IN LPTSTR lpszAddrType,             // pointer to address type
    IN LPADRLIST lpOriginalEnvelopeAdrList,// pointer to unmodified address list
    IN LPADRLIST lpOriginalAdrList,     // pointer to unmodified address list
    IN LPADRLIST lpMappedAdrList,       // pointer to unmodified address list
    IN OUT LPADRLIST lpEnvelopeAdrList, // pointer to envelope address list
    IN OUT LPADRLIST lpEnvelopeNDRAdrList,// pointer to envelope NDR address list
    IN OUT LPMESSAGE lpMessage,         // pointer to message
    OUT LPADRLIST *lppAdrList,          // pointer to address list
    OUT LPADRLIST *lppNDRAdrList)       // pointer to NDR address list
{
    HRESULT         hrT                  = NOERROR;
    HRESULT         hr                   = NOERROR;
    SCODE           sc                   = 0;

    ULONG           cbeid                = 0;
    LPENTRYID       lpeid                = NULL;

    ULONG           cRows                = 0;
    LPSRowSet       lpRows               = 0;

    ULONG           nName                = 0;
    ULONG           nAddrType            = 0;
    ULONG           nAddress             = 0;
    ULONG           nEntryId             = 0;
    ULONG           nSearchKey           = 0;

    ULONG           i                    = 0;
    ULONG           iAck                 = 0;
    ULONG           iNak                 = 0;

    ULONG           cValues              = 0;
    LPSPropValue    lpProps              = NULL;

    LPSPropValue    lpEnvelopeProps      = NULL;
    LPSPropValue    lpMessageProps       = NULL;

    BOOL            fRespTrue            = FALSE;

    FILETIME        ftReportTime         = {0};

    LPSPropValue    lpPropT              = NULL;

    ULONG           cNewProps            = 0;
    LPSPropValue    lpNewProps           = NULL;

    ULONG           cMapValues           = 0;
    LPSPropValue    lpMapProps           = NULL;

    BOOL            fEnv                 = FALSE;

    DEBUGPUBLIC("HrMapRecipAddrToProxy()\n");

    if((lpEnvelopeAdrList != NULL) && (lpEnvelopeNDRAdrList == NULL))
    {
        RETURN(HR_LOG(E_INVALIDARG));
    }

    *lppAdrList    = NULL;
    *lppNDRAdrList = NULL;

    //
    // Get the current time.
    //

    GetSystemTimeAsFileTime( &ftReportTime);

    nName      = IDISPNAME;
    nAddrType  = IADDRTYPE;
    nAddress   = IEMAILADDR;
    nEntryId   = IENTRYID;
    nSearchKey = ISEARCHKEY;

    cRows  = lpOriginalAdrList->cEntries;
    lpRows = (LPSRowSet)lpOriginalAdrList;

    if(lpRows == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(lpEnvelopeAdrList != NULL)
    {
        //
        // Clear recipient numbers if this is not an envelope address list.
        //

        hr = HrDeleteAddressListProperty(
            PR_RECIPIENT_NUMBER,
            (LPADRLIST)lpRows);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = HrMAPICreateSizedAddressList(
        cRows,
        lppAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPICreateSizedAddressList(
        cRows,
        lppNDRAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < cRows; i++)
    {
        //
        // Preserve original recipient number for original recipient entry
        //

        cValues = lpRows->aRow[i].cValues;
        lpProps = lpRows->aRow[i].lpProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            FALSE,
            PR_RECIPIENT_NUMBER,
            (i+1),
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpRows->aRow[i].cValues = cNewProps;
        lpRows->aRow[i].lpProps = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original recipient number for mapped recipient entry
        //

        cValues = lpMappedAdrList->aEntries[i].cValues;
        lpProps = lpMappedAdrList->aEntries[i].rgPropVals;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            FALSE,
            PR_RECIPIENT_NUMBER,
            (i+1),
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpMappedAdrList->aEntries[i].cValues = cNewProps;
        lpMappedAdrList->aEntries[i].rgPropVals = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original row number for original recipient entry
        //

        cValues = lpRows->aRow[i].cValues;
        lpProps = lpRows->aRow[i].lpProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            TRUE,
            PR_ROWID,
            i,
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpRows->aRow[i].cValues = cNewProps;
        lpRows->aRow[i].lpProps = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Preserve original row number for mapped recipient entry
        //

        cValues = lpMappedAdrList->aEntries[i].cValues;
        lpProps = lpMappedAdrList->aEntries[i].rgPropVals;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrSetUlongPropertyValue(
            TRUE,
            PR_ROWID,
            i,
            cValues,
            lpProps,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpProps);

        lpMappedAdrList->aEntries[i].cValues = cNewProps;
        lpMappedAdrList->aEntries[i].rgPropVals = lpNewProps;

        cNewProps  = 0;
        lpNewProps = NULL;

        cValues = 0;
        lpProps = NULL;

        //
        // Mapped recipient entry (read-only)
        //

        cMapValues = lpMappedAdrList->aEntries[i].cValues;
        lpMapProps = lpMappedAdrList->aEntries[i].rgPropVals;

        //
        // Get the recipient responsibility
        //

        lpPropT = LpValFindProp(PR_RESPONSIBILITY, cMapValues, lpMapProps);

        if(lpPropT == NULL)
        {
            fRespTrue = fDefaultRespValue;
        }
        else
        {
            fRespTrue = lpPropT->Value.b;
        }

        if(fIgnoreRespValue == TRUE)
        {
            fRespTrue = FALSE;
        }

        //
        // NOTE: If email address type is the same as the proxy address type
        // then the address was mapped.
        //

        if((lpMapProps[IADDRTYPE].ulPropTag != PR_ADDRTYPE) ||
           (lpMapProps[IADDRTYPE].Value.LPSZ == NULL))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        else if((lstrcmpi(lpMapProps[IADDRTYPE].Value.LPSZ,lpszAddrType) != 0) &&
           (fRespTrue == TRUE))
        {
            MODULE_WARNING("proxy address was not found");

            //
            // If recipient is used by the envelope then NDR envelope recipients
            // associated with this recipient.
            //

            fEnv = FALSE;

            if(lpEnvelopeAdrList != NULL)
            {
                ULONG j        = 0;
                ULONG iNakT    = 0;
                ULONG cEntries = 0;

                iNakT = lpEnvelopeNDRAdrList->cEntries;

                for(j = 0; j < lpEnvelopeAdrList->cEntries; j++)
                {
                    cValues = lpEnvelopeAdrList->aEntries[j].cValues;
                    lpProps = lpEnvelopeAdrList->aEntries[j].rgPropVals;

                    lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

                    if(lpPropT == NULL)
                    {
                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }

                    if(lpPropT->Value.ul == (i+1))
                    {
                        ULONG ulRowId = 0;

                        fEnv = TRUE;

                        cEntries = lpEnvelopeAdrList->cEntries;
                        if(cEntries > 0) cEntries--;
                        lpEnvelopeAdrList->cEntries = cEntries;

                        lpEnvelopeAdrList->aEntries[j].cValues =
                            lpEnvelopeAdrList->aEntries[cEntries].cValues;
                        lpEnvelopeAdrList->aEntries[j].rgPropVals =
                            lpEnvelopeAdrList->aEntries[cEntries].rgPropVals;

                        cEntries = lpEnvelopeNDRAdrList->cEntries;
                        cEntries++;
                        lpEnvelopeNDRAdrList->cEntries = cEntries;

                        lpEnvelopeAdrList->aEntries[cEntries].cValues    = 0;
                        lpEnvelopeAdrList->aEntries[cEntries].rgPropVals = NULL;

                        //
                        // Use unmapped recipient entry
                        //

                        lpPropT = LpValFindProp(PR_ROWID, cValues, lpProps);

                        if(lpPropT == NULL)
                        {
                            hr = HR_LOG(E_FAIL);
                            goto cleanup;
                        }

                        ulRowId = lpPropT->Value.ul;

                        cValues = lpOriginalEnvelopeAdrList->aEntries[ulRowId].cValues;
                        lpProps = lpOriginalEnvelopeAdrList->aEntries[ulRowId].rgPropVals;

                        //
                        // Non-deliver recipient
                        //

                        hr = HrNonDeliverRecipient(
                            MAPI_REASON_TRANSFER_IMPOSSIBLE,
                            (ULONG)MAPI_DIAG_NO_DIAGNOSTIC,
                            &ftReportTime,
                            (i+1),
                            iNakT,
                            cValues,
                            lpProps,
                            lpEnvelopeNDRAdrList);

                        if(FAILED(hr))
                        {
                            hr = HR_LOG(E_FAIL);
                            goto cleanup;
                        }

                        iNakT++;
                    }
                }

                iNakT = lpEnvelopeNDRAdrList->cEntries;
            }

            if(fEnv == FALSE)
            {
                //
                // Non-deliver recipient
                //

                hr = HrNonDeliverRecipient(
                    MAPI_REASON_TRANSFER_IMPOSSIBLE,
                    (ULONG)MAPI_DIAG_NO_DIAGNOSTIC,
                    &ftReportTime,
                    (i+1),
                    iNak,
                    cMapValues,
                    lpMapProps,
                    *lppNDRAdrList);

                if(FAILED(hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                iNak++;
            }
        }
        else
        {
            //
            // Proxy address was found
            //

            hr = HrMAPISetAddressList(
                iAck,
                cMapValues,
                lpMapProps,
                *lppAdrList);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            iAck++;
        }
    }

    (*lppAdrList)->cEntries    = iAck;
    (*lppNDRAdrList)->cEntries = iNak;

cleanup:

    if(FAILED(hr))
    {
        if(*lppAdrList != NULL)
        {
            FREEPADRLIST(*lppAdrList);
        }

        if(*lppNDRAdrList != NULL)
        {
            FREEPADRLIST(*lppNDRAdrList);
        }
    }

    MAPIFREEBUFFER(lpNewProps);

    RETURN(hr);
}


CHKRFORM.CFG

[Description]
MessageClass=IPM.Checkers
CLSID={86174010-5030-0076-9912-00aa0038901b}
Category=Games
Subcategory=Board Games
DisplayName=Checkers Sample Form
Comment=Sample Checkers Form
LargeIcon=wcheck.ico
SmallIcon=wchecks.ico
Version=1.0
Locale=enu
Hidden=0
Owner=Exchange Server SDK
ComposeCommand=New Checkers Game ...

[Platforms]
Platform.2=NTx86

[Platform.NTx86]
CPU=ix86
OSVersion=WinNT3.5
file1=wcheck32.exe
registry1=LocalServer32 = %d\wcheck32.exe /f
registry4=InProcHandler32 = ole32.dll

[Properties]
Property1=BoardData
Property2=Turn
Property3=MoveNumber
Property4=BoardScore

[Property.BoardData]
Type=258
NmidInteger=26624
DisplayName=Board Data
SpecialType=0

[Property.Turn]
Type=3
NmidInteger=26625
DisplayName=Turn
SpecialType=0

[Property.MoveNumber]
Type=3
NmidInteger=26626
DisplayName=Move Number
SpecialType=0

[Property.BoardScore]
Type=3
NmidInteger=26627
DisplayName=Scoring Function Output
SpecialType=0

[Extensions]
WMS_Extension=WMS_Extension

[Extension.WMS_Extension]
Type=30
NmidPropset={00020D35-0000-0000-C000-000000000046}
NmidInteger=1
Value=11222000
;
; ----- position
; 0
; 1 read
; 2 reply
; 3 reply all
; 4 forward
; 5 print
; 6 save
; 7 on delivery
; ----- meaning
; 0==default
; 1==DoVerb
; 2==Disabled

[Verbs]
Verb1=1

[Verb.1]
DisplayName=bogus
Code=0
Flags=0
Attribs=0


INSTFORM.CPP

// --InstForm.cpp---------------------------------------------------------------
// Command line utility to install or remove Exchange forms.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "exchcli.h"

class CUserParams;// must be declared before "instform.chk"

#include "iformmsg.h"// created from instform.mc by the message compiler
#include "instform.chk"

// Define a warning to exit if user asks for help.
const HRESULT EXIT_WARNING = MAPI_W_ERRORS_RETURNED;

LPTSTRlpszAppName = TEXT("InstForm");

//$--INTERNAL_ERROR-------------------------------------------------------------
//  Helper function for EventLogMsg
// -----------------------------------------------------------------------------
static inline VOID INTERNAL_ERROR(
    IN LPCTSTR str,
    IN const HRESULT hr)
{
    TCHAR   szErrorCode[16] = {0};

    EventLogMsg(
        INSTFORM_INTERNAL_ERROR,
        2, str, _itot( hr, szErrorCode, 16),
        0);
}

//$--fFileExists()--------------------------------------------------------------
// RETURNS: TRUE if the file exists and FALSE if not.
// -----------------------------------------------------------------------------

BOOL fFileExists(
    IN LPTSTR lpszFileSpec)   // File to validate exists.
{
    struct _tfinddata_t FileInfo = {0};
    long lhRet = _tfindfirst( lpszFileSpec, &FileInfo);

    if( lhRet == -1)
        return( FALSE);
    _findclose( lhRet);

    return( TRUE);
}

//$--CUserParams----------------------------------------------------------------
// Container class for user's parameters and parsing the command line.
// -----------------------------------------------------------------------------

class CUserParams
{
public:
    CUserParams();

    HRESULT HrParseCommandLine(
        IN int      argc,       // number of arguments on command line
        IN char*    argv[]);    // array of command line arguments

friend HRESULTHrSetupAndDoIt( CUserParams&);

    // These members will contain the arguments
    // and values specified by the user.
    BOOL    m_fInstall;         // TRUE when installing a form.

protected:
    BOOL    m_fRemove;          // TRUE when removing a form.
    HFRMREG m_hFrmReg;          // The registry to install form in.
    char*   m_pszCfgFile;       // The configuration file name for installing a form.
    char*   m_pszMsgClass;      // The message class for removing a form.
    char*   m_pszFolder;        // The path of the folder for installing in a folder registry.
    char*   m_pszProfile;       // The profile to logon to.
    char*   m_pszPassword;      // The password for logon.
    char*   m_pszMsgStore;      // The Display Name of the message store.

protected:
    void PrintUsage( IN BOOL fHelp = FALSE);
    static char*    m_pszFlagArray[];
    static char*    m_pszRegArray[];

    // These MUST correspond to the m_pszFlagArray[].
    enum eFlagIndex
    {
        NON_FLAG = MAX_ULONG,
        HELP1 = 0,
        HELP2,
        INSTALL,
        REMOVE,
        VALUE_REQUIRED, // Place flags that require a value below.
        REGISTRY = VALUE_REQUIRED,
        FOLDER,
        PASSWORD,
        EPROFILE,
        MSGSTORE,
    };
};

// -----------------------------------------------------------------------------
// Arrays that belong to CUserParams for parsing command line.
// -----------------------------------------------------------------------------

char* CUserParams::m_pszFlagArray[] =
{
    "?",
    "HELP",
    "INSTALL",
    "REMOVE",
    // Place flags that require a value below.
    "REGISTRY",
    "FOLDER",
    "PASSWORD",
    "PROFILE",
    "MSGSTORE",
};

// This array is defined to correspond to MAPI defined constants listed in
// the m_hFrmRegArray defined below, so the order is important.
char* CUserParams::m_pszRegArray[] =
{
    "LOCAL",
    "PERSONAL",
    "FOLDER",
    "ENTERPRISE",
};

// This array is defined to correspond to the strings
// in the array m_pszRegArray defined above.
static HFRMREG m_hFrmRegArray[] =
{
    HFRMREG_LOCAL,
    HFRMREG_PERSONAL,
    HFRMREG_FOLDER,
    HFRMREG_ENTERPRISE,
};

// $--CUserParams::PrintUsage()-------------------------------------------------
// Print the usage message for the command line parameters.
// -----------------------------------------------------------------------------

void CUserParams::PrintUsage(
    IN BOOL fHelp) // True if complete help is wanted.
{
    printf(
        "USAGE:  INSTFORM /INSTALL cfg_file [flags]\n"
        "   OR:  INSTFORM /REMOVE msg_class [flags]\n");

    if( !fHelp)
        return;

    printf(
        "\n"
        "   /HELP or /? Show help information.\n"
        "\n"
        "   /INSTALL    To install the form specified in the cfg_file\n"
        "\n"
        "   /REMOVE     To remove the form specified by the msg_class.\n"
        "\n"
        "   /REGISTRY=  Flag to specify which registry to install form in.\n"
        "               FOLDER the /FOLDER option must be specified.\n"
        "               PERSONAL\n"
        "               LOCAL\n"
        "               ENTERPRISE\n"
        "\n"
        "   /FOLDER=    Specify the folder path of the registry to install in. Example:\n"
        "                   /FOLDER=\"top of information store\\inbox\"\n"
        "\n"
        "               When this parameter is specified the /REGISTRY=FOLDER is\n"
        "               assumed and need not be specified.  Any other /REGISTRY=\n"
        "               option is an error.\n"
        "\n"
        "   /PROFILE=   Specify the profile for logon.  Optional for local registry.\n"
        "\n"
        "   /PASSWORD=  Specify the password if needed.\n"
        "\n"
        "   /MSGSTORE=  Specify the display name of the message store that contains\n"
        "               the folder specified.  This is only valid with folders.  If\n"
        "               not specified the default message store will be used.\n"
        "\n"
        "   cfg_file    The name of the configuration file of the form to install.\n"
        "\n"
        "   msg_class   The message class of the form to be removed from the\n"
        "               forms registry.\n"
    );
}

// $--CUserParams::CUserParams()------------------------------------------------
// CONSTRUCTOR to initialize the user parameter container.
// -----------------------------------------------------------------------------

CUserParams::CUserParams()
{
    m_fInstall      = FALSE;
    m_fRemove       = FALSE;
    m_hFrmReg       = MAX_ULONG;
    m_pszMsgClass   = NULL;
    m_pszCfgFile    = NULL;
    m_pszFolder     = NULL;
    m_pszProfile    = NULL;
    m_pszPassword   = NULL;
    m_pszMsgStore   = NULL;
}

// $--CUserParams::HrParseCommandLine()-----------------------------------------
// Parse the command line arguments and place their values in the appropriate
// member variables.
// -----------------------------------------------------------------------------

HRESULT CUserParams::HrParseCommandLine(
    IN int      argc,           // number of arguments on command line
    IN char*    argv[])         // array of command line arguments
{
    HRESULT     hr              = NOERROR;
    int         iArg            = 0;
    int         cNonFlagArgs    = 0;
    eFlagIndex  nFlagIndex      = NON_FLAG;
    char*       pszValue        = NULL;
    char*       pszFirstArg     = NULL;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    DEBUGPRIVATE( "CUserParams::HrParseCommandLine()");

    // If there are no flags on the command line then just print a usage message.
    if( argc < 2)
    {
        PrintUsage();
        return( EXIT_WARNING);
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Do an initial check for /? or /HELP.  If found, don't do any other parsing.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    for (iArg = 1; iArg < argc; iArg++)
    {   // Ignore return value while we look for help flags.
        hr = _HrExpandCommandLineArgument( argv[iArg], m_pszFlagArray,
            ARRAY_CNT( m_pszFlagArray), (ULONG*) &nFlagIndex, NULL, &pszValue);

        if( SUCCEEDED( hr) && nFlagIndex == HELP1 || nFlagIndex == HELP2)
        {
            PrintUsage( TRUE);
            return( EXIT_WARNING);
        }
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Loop through and parse all the command line arguments.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    for( iArg = 1; iArg < argc; iArg++)
    {
        hr = _HrExpandCommandLineArgument( argv[iArg], m_pszFlagArray,
            ARRAY_CNT( m_pszFlagArray), (ULONG*) &nFlagIndex, NULL, &pszValue);
        if( FAILED(hr))
        {
            fprintf(stderr, "ERROR: unknown command line parameter: %s.\n", argv[ iArg]);
            goto cleanup;
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Parse non-flag arguments.
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        if( nFlagIndex == NON_FLAG && pszValue != NULL)
        {
            switch( cNonFlagArgs ++)
            {
                case 0:     // Either cfg_file or msg_class
                    pszFirstArg = pszValue;
                    break;

                default:    // Too many arguments!!!
                    fprintf( stderr, "ERROR: too many arguments: %s\n", argv[iArg]);
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
            }
        }
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        else // Parse the flag arguments.
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        {
            if( nFlagIndex < VALUE_REQUIRED)
            {
                if( pszValue)
                {   // No value needed here.
                    fprintf( stderr, "ERROR: flag /%s does not take a value.\n", m_pszFlagArray[ nFlagIndex]);
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
            }
            else // nFlagIndex >= VALUE_REQUIRED
            {
                if( !pszValue)
                {   // Make sure user supplied required values.
                    fprintf( stderr, "ERROR: flag /%s requires a value.\n", m_pszFlagArray[ nFlagIndex]);
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }
            }

            switch( nFlagIndex)
            {
                case INSTALL:
                    m_fInstall = TRUE;
                    break;

                case REMOVE:
                    m_fRemove = TRUE;
                    break;

                case REGISTRY:
                    // Look up the registry value entered by the user and convert
                    // to a value that MAPI will understand.
                    hr = _HrFindArrayValue( pszValue, m_pszRegArray, ARRAY_CNT( m_pszRegArray), &m_hFrmReg);
                    if (FAILED(hr))
                    {
                        fprintf(stderr, "ERROR: invalid registry value: %s\n", pszValue);
                        goto cleanup;
                    }
                    m_hFrmReg = m_hFrmRegArray[ m_hFrmReg];

                    // If they specified the /FOLDER= flag then the only registry
                    // flag they can specify is FOLDER.
                    if( m_hFrmReg != HFRMREG_FOLDER && m_pszFolder != NULL)
                    {
                        fprintf( stderr, "ERROR: When specifying \"/FOLDER=\" omit the \"/REGISTRY=\" flag.\n");
                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }
                    break;

                case FOLDER:
                    if( m_hFrmReg != HFRMREG_FOLDER && m_hFrmReg != MAX_ULONG)
                    {
                        fprintf( stderr, "ERROR: When specifying \"/FOLDER=\" omit the \"/REGISTRY=\" flag.\n");
                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }
                    m_hFrmReg = HFRMREG_FOLDER;
                    m_pszFolder = pszValue;
                    break;

                case PASSWORD:
                    m_pszPassword = pszValue;
                    break;

                case EPROFILE:
                    m_pszProfile = pszValue;
                    break;

                case MSGSTORE:
                    m_pszMsgStore = pszValue;
                    break;

                default:
                    // Unexpected value in m_pszFlagArray and the user specified it.
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
            }
        }
    }   // End For Loop

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Final parameter validation.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Validate that either INSTALL or REMOVE was specifed.
    if( m_fInstall)
    {   // They specified /INSTALL
        if( m_fRemove)
        {
            fprintf( stderr, "ERROR: Conflicting command line arguments.\n"
                             "       Both /INSTALL and /REMOVE can not be specified.\n");
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        if( !pszFirstArg)
        {
            fprintf( stderr, "ERROR: please specify the cfg_file name.\n");
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        if( !fFileExists( pszFirstArg))
        {
            fprintf( stderr, "ERROR: can not find file %s\n", pszFirstArg);
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        m_pszCfgFile = pszFirstArg;
    }
    else if( m_fRemove)
    {   // They specified /REMOVE
        if( !pszFirstArg)
        {
            fprintf( stderr, "ERROR: please specify the msg_class name.\n");
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        m_pszMsgClass = pszFirstArg;
    }
    else
    {
        fprintf( stderr, "ERROR: please specify either /INSTALL or /REMOVE.\n");
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // It is an error to specify the folder registry but no folder name.
    if( m_hFrmReg == HFRMREG_FOLDER && !m_pszFolder)
    {
        fprintf( stderr, "ERROR: You must specify \"/FOLDER=\" with the \"/REGISTRY=FOLDER\" flag.\n");
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // It is an error to specify message store without specifiying the folder registry.
    if( m_pszMsgStore && m_hFrmReg != HFRMREG_FOLDER)
    {
        fprintf( stderr, "ERROR: \"/MSGSTORE=\" is only valid when \"/FOLDER=\" has been specified.\n");
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // The profile must be specified unless using the local registry.
    if( m_hFrmReg != HFRMREG_LOCAL && !m_pszProfile)
    {
        fprintf( stderr, "ERROR: You must specify the profile name.\n");
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // If user did not specify the registry then use the default one.
    if( m_hFrmReg == MAX_ULONG)
        m_hFrmReg = HFRMREG_DEFAULT;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup:
    RETURN( hr);
}

//$--HrOpenMsgStoreFolderByName()-----------------------------------------------
// Returns pointer to open folder resource.  If MsgStore is NULL, default message
// store is assumed.
// -----------------------------------------------------------------------------

HRESULT
HrOpenMsgStoreFolderByName(
INLPMAPISESSIONlpMAPISession,  // Active session handle
INLPTSTRlpszMsgStore,   // Name of message store, or NULL for default
INLPTSTRlpszFolder,     // Folder path name to open
OUTLPMAPIFOLDER*lppFolder)     // RETURN: open folder interface
{
HRESULThr = NOERROR;
ULONGcbeid  = 0L;
LPENTRYIDlpeid = NULL;
LPMDBlpMDB = NULL;
LPMAPIFOLDERlpFolder = NULL;

DEBUGPUBLIC( "HrOpenMsgStoreFolderByName()");

hr = CHK_HrOpenMsgStoreFolderByName(
lpMAPISession, lpszMsgStore, lpszFolder,
lppFolder);
if( FAILED( hr))
RETURN( hr);

// Get the entry ID of the specified or default message store.
if( lpszMsgStore)
    hr = HrMAPIFindStore( lpMAPISession, lpszMsgStore, &cbeid, &lpeid);
else
    hr = HrMAPIFindDefaultMsgStore( lpMAPISession, &cbeid, &lpeid);
if( FAILED( hr))
goto cleanup;

ASSERTERROR( cbeid != 0, "Entry ID count should not be zero.");
ASSERTERROR( lpeid != NULL, "NULL lpeid pointer");

// Open the message store.
hr = lpMAPISession->OpenMsgStore(
0, cbeid, lpeid, NULL,
MDB_WRITE | MAPI_DEFERRED_ERRORS,
&lpMDB);
if( FAILED(hr))
    goto cleanup;

ASSERT_IUNKNOWN_PTR( lpMDB, "Invalid store pointer");

    hr = HrMAPIOpenFolderEx( lpMDB, TEXT('\\'), lpszFolder, &lpFolder);
    if( FAILED(hr))
    goto cleanup;

*lppFolder = lpFolder;

cleanup:
MAPIFREEBUFFER( lpeid);

ULRELEASE( lpMDB);

RETURN( hr);
}

//$--HrOpenFormContainer-----------------------------------------------------
// Returns open form container interface.  NULLs may be passed for unnecessary
// string values.  For all but HFRMREG_LOCAL, a MAPI session handle is also
// returned.  The user assumes responsibility of logging off and releasing
// this session.
//
// There are two container types for forms.  The first involves logging
// on to a MAPI session to open the form container that the form will be
// installed on.  The second is simpler and does not require logging on to a
// MAPI session, but only works for installing local forms.  Both techniques
// are demonstrated below.
//----------------------------------------------------------------------------

HRESULT
HrOpenFormContainer(
INHFRMREGhFrmReg,            // Desired form registry container
INLPTSTRlpszProfile,        // Session profile, if needed
INLPTSTRlpszPassword,       // Session password, if needed
INLPTSTRlpszMsgStore,       // Message store of folder, if needed
INLPTSTRlpszFolder,         // Folder name, if needed
OUTLPMAPISESSION*lppMAPISession,    // RETURN: active session
OUTLPMAPIFORMCONTAINER*lppFormContainer)  // RETURN: open folder container
{
HRESULThr= NOERROR;
LPMAPIFOLDERlpFolder= NULL;
    LPMAPIFORMMGRlpFormMgr= NULL;
LPMAPISESSIONlpMAPISession= NULL;
LPMAPIFORMCONTAINERlpFormContainer= NULL;

hr = CHK_HrOpenFormContainer(
hFrmReg, lpszProfile, lpszPassword, lpszMsgStore, lpszFolder,
lppMAPISession, lppFormContainer);
if( FAILED( hr))
RETURN( hr);

    // Initialize return values
    if( lppMAPISession)
        *lppMAPISession = NULL;

    *lppFormContainer = NULL;

    if( hFrmReg == HFRMREG_LOCAL)
    {
    hr = MAPIOpenLocalFormContainer( &lpFormContainer);
        if( FAILED( hr))
        {
            INTERNAL_ERROR( TEXT("MAPIOpenLocalFormContainer()"), hr);
            goto cleanup;
        }
    }
    else
    {
        ASSERTERROR( lppMAPISession != NULL, "NULL lppMAPISession passed");
    ASSERTERROR( lpszProfile != NULL, "NULL Profile name");

        hr = MAPILogonEx(
                NULL, lpszProfile, lpszPassword,
                MAPI_LOGON_UI | MAPI_NO_MAIL | MAPI_NEW_SESSION,
                &lpMAPISession);
        if( FAILED( hr))
        {
            INTERNAL_ERROR( TEXT("MAPILogonEx()"), hr);
            goto cleanup;
        }

        if( hFrmReg == HFRMREG_FOLDER)
        {
        ASSERTERROR( lpszFolder != NULL, "NULL Folder name");

        hr = HrOpenMsgStoreFolderByName( lpMAPISession,
        lpszMsgStore, lpszFolder, &lpFolder);
        if( hr == EDK_E_NOT_FOUND)
        {
                TCHAR    szErrorCode[16] = {0};

        EventLogMsg( INSTFORM_NOTFOUND_ERROR,
        1, _itot( hr, szErrorCode, 16),
        0);

        goto cleanup;
        }
        if( FAILED( hr)) {
                INTERNAL_ERROR( TEXT("HrOpenMsgStoreFolderByName()"), hr);
        goto cleanup;
        }
        }

        // Open form manager interface.
        hr = MAPIOpenFormMgr( lpMAPISession, &lpFormMgr);
        if( FAILED( hr))
        {
            INTERNAL_ERROR( TEXT("MAPIOpenFormMgr()"), hr);
            goto cleanup;
        }

        // Open appropriate form container.
        hr = lpFormMgr->OpenFormContainer( hFrmReg, lpFolder, &lpFormContainer);
        if( FAILED( hr))
        {
            INTERNAL_ERROR( TEXT("IMAPIFormMgr::OpenFormContainer()"), hr);
            goto cleanup;
        }

        // Success!  Set return interface pointers.
        if( lppMAPISession)
            *lppMAPISession = lpMAPISession;
    }

*lppFormContainer = lpFormContainer;

cleanup:
ULRELEASE( lpFolder);
ULRELEASE( lpFormMgr);

if( FAILED( hr) && lpMAPISession)
{
lpMAPISession->Logoff( 0L, 0L, 0L);
ULRELEASE( lpMAPISession);
}

RETURN( hr);
}

//$--HrSetupAndDoIt()-----------------------------------------------------------
// Open form container interface, and install or remove.
// -----------------------------------------------------------------------------

static HRESULT HrSetupAndDoIt(
    IN CUserParams& UserParams)     // Parsed parameters from the user.
{
    HRESULThr = NOERROR;
LPMAPISESSIONlpMAPISession= NULL;
LPMAPIFORMCONTAINERlpFormContainer= NULL;

    DEBUGPRIVATE( "HrSetupAndDoIt()");

    // Open appropriate form container interface.
    hr = HrOpenFormContainer(
UserParams.m_hFrmReg,
UserParams.m_pszProfile, UserParams.m_pszPassword,
UserParams.m_pszMsgStore, UserParams.m_pszFolder,
&lpMAPISession, &lpFormContainer);
if( FAILED( hr))
goto cleanup;

    if( UserParams.m_fInstall)
    {
ULONGulUIFlags = MAPI_DIALOG;

// Shouldn't use MAPI_DIALOG flag if installing to local forms registry.
if( UserParams.m_hFrmReg == HFRMREG_LOCAL)
ulUIFlags = 0L;

        hr = lpFormContainer->InstallForm( NULL, ulUIFlags, UserParams.m_pszCfgFile);
        if( FAILED( hr))
        {
            if( hr == MAPI_E_USER_CANCEL)
{
                EventLogMsg( INSTFORM_USER_CANCEL_ERROR,
                0,
                0);
            }
            else
            {
                LPMAPIERROR lpMAPIError = NULL;
                HRESULT     hrT = NOERROR;

                hrT = lpFormContainer->GetLastError (hr, 0L, &lpMAPIError);

                if (SUCCEEDED(hrT) && lpMAPIError)
                {
EventLogMsg( INSTFORM_EXTENDED_ERROR,
1, lpMAPIError->lpszError,
0);
                    MAPIFREEBUFFER (lpMAPIError);
                }

                INTERNAL_ERROR( TEXT("IMAPIFormContainer::InstallForm()"), hr);

                hr = HR_LOG( E_FAIL);
}
        }
    }
    else
    {
        hr = lpFormContainer->RemoveForm( UserParams.m_pszMsgClass);
        if( FAILED( hr) || hr == MAPI_W_PARTIAL_COMPLETION)
        {
            if( hr == MAPI_E_NOT_FOUND)
{
                EventLogMsg( INSTFORM_FORM_NOT_FOUND_ERROR,
                1, UserParams.m_pszMsgClass,
                0);
            }
            else
{
                LPMAPIERROR lpMAPIError = NULL;
                HRESULT     hrT = NOERROR;

                hrT = lpFormContainer->GetLastError (hr, 0L, &lpMAPIError);

                if (SUCCEEDED(hrT) && lpMAPIError)
                {
EventLogMsg( INSTFORM_EXTENDED_ERROR,
1, lpMAPIError->lpszError,
0);
                    MAPIFREEBUFFER (lpMAPIError);
                }

                INTERNAL_ERROR( TEXT("IMAPIFormContainer::RemoveForm()"), hr);

                hr = HR_LOG( E_FAIL);
}
        }
    }

cleanup:
ULRELEASE( lpFormContainer);

if( lpMAPISession)
{
lpMAPISession->Logoff( 0L, 0L, 0L);
ULRELEASE( lpMAPISession);
}

    RETURN( hr);
}

//$--main()---------------------------------------------------------------------
// See CUserParams::PrintUsage() for user parameters.
// -----------------------------------------------------------------------------

int main( int argc, char *argv[])
{
    HRESULThr = NOERROR;
BOOLfMAPIInitialized = FALSE;
BOOLfEventLogOpen = FALSE;

    CUserParams UserParams;

    DEBUGPUBLIC( "main()");

// Parse the user's parameters.
    hr = UserParams.HrParseCommandLine( argc, argv);
    if( hr == EXIT_WARNING || FAILED( hr))
        goto cleanup;

    // Initialize MAPI.
    hr = MAPIInitialize( NULL);
    if( FAILED( hr))
        goto cleanup;

    fMAPIInitialized = TRUE;

    // Open an event logging handle for this application.
    hr = HrEventOpenLog(
        lpszAppName,// application name
        NULL,// executable name (computed)
        NULL,// event message file (computed)
        NULL,// parameter message file (this)
        NULL,// category message file (this)
        NULL);// event logging handle
    if( FAILED(hr))
    {
        fprintf( stderr, "ERROR: unable to open event log.\n");
        goto cleanup;
}

fEventLogOpen = TRUE;

    hr = HrSetupAndDoIt( UserParams);
    if( FAILED( hr))
    {
        printf( "FAILURE! Could not %s form.\n", UserParams.m_fInstall ? "install" : "remove");
        goto cleanup;
    }

    printf( "Form %s successfully.\n", UserParams.m_fInstall ? "installed" : "removed");

cleanup:

if( fEventLogOpen)
{
    EDKEVENTCOUNT   sEventCount     = {0};
        HRESULThrT= NOERROR;

        hrT = HrEventGetCounts(&sEventCount);
        if( SUCCEEDED( hrT))
        {
            // Print the number of errors logged.
            if (sEventCount.cError == 1)
                fprintf(stderr, "ERROR: 1 error written to NT event log.\n");
            else if (sEventCount.cError > 1)
                fprintf(stderr, "ERROR: %d errors written to NT event log.\n",
                    sEventCount.cError);

            // Print the number of warnings logged.
            if (sEventCount.cWarning == 1)
                fprintf(stderr, "WARNING: 1 warning written to NT event log.\n");
            else if (sEventCount.cWarning > 1)
                fprintf(stderr, "WARNING: %d warnings written to NT event log.\n",
                    sEventCount.cWarning);
        }
else
{
            fprintf(stderr, "WARNING: unable to get number of errors logged.\n");
        }

(void)HrEventCloseLog();
    }

if( fMAPIInitialized)
MAPIUninitialize();

    return( _nEcFromHr(hr));
}

// -----------------------------------------------------------------------------


CONVCLS.CPP

// --convcls.cpp--------------------------------------------------------------
//
// IPM to 822 conversion class source file.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "msgemit.h"
#include "tagnames.h"
#include "convcls.h"
#include "convcls.chk"

//$--CIPMConvert::CIPMConvert------------------------------------------------
//
// DESCRIPTION: CIPMConvert conversion class constructor
//
// INPUT:   none
//
// RETURNS: nothing
//
// ---------------------------------------------------------------------------
CIPMConvert::CIPMConvert()
{
    DEBUGPRIVATE("CIPMConvert::CIPMConvert()\n");

    // Initialize data members
    m_lpwszMsgClass =   NULL;
    m_lpStream = NULL;
    m_lpEnvelope = NULL;
    m_lpContent = NULL;
    m_lpAttach = NULL;
    m_fTNEFEncode = FALSE;
    m_lpszAddrType = NULL;
    m_lpAB = NULL;
    m_MsgType = mtNone;

    ZeroMemory(m_rgszData, sizeof(m_rgszData));

    m_lpRcptToList = NULL;
    m_lpContentRecips = NULL;
    m_lpReportToList = NULL;

    m_lpTraceInfo = NULL;
    m_lpIntTraceInfo = NULL;

ZeroMemory( &m_sOffsetHeaders, sizeof( ULARGE_INTEGER));
}

//$--CIPMConvert::~CIPMConvert------------------------------------------------
//
// DESCRIPTION: CIPMConvert conversion class destructor
//
// INPUT:   none
//
// RETURNS: nothing
//
// ---------------------------------------------------------------------------
CIPMConvert::~CIPMConvert()
{
    DEBUGPRIVATE("CIPMConvert::~CIPMConvert()\n");

    // Free up all memory allocated for this object
    Reset();
}

//$--CIPMConvert::HrInitialize-----------------------------------
//
// DESCRIPTION: Initializes conversion class instance
//
// INPUT:   lpwszMsgClass   --  message class
//          fTNEFEncode --  TNEF encode attachments flag
//          lpszAddrType--  address type
//          lpAddrBook  --  address book pointer
//          lpEnvelope  --  message envelope pointer
//          lpStream    --  stream pointer
//
// NOTE:    The strings passed in as inputs are NOT duplicated.
//          Thus, they must stay around for the life of the
//          conversion.
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      EDK_E_ALREADY_EXISTS if already in use
//
// ----------------------------------------------------------------
HRESULT CIPMConvert::HrInitialize(      // RETURNS: HRESULT
    IN LPCWSTR lpwszMsgClass,           // message class
    IN BOOL fTNEFEncode,                // TNEF encode attachments flag
    IN LPCTSTR lpszAddrType,            // address type
    IN LPADRBOOK lpAddrBook,            // address book pointer
    IN LPMESSAGE lpEnvelope,            // message envelope pointer
    IN LPSTREAM lpStream)               // stream to write output to
{
    HRESULT hr      =   NOERROR;    // return code
    BOOL    fInUse  =   FALSE;      // TRUE if instance is in use

    DEBUGPRIVATE("CIPMConvert::HrInitialize()\n");

    // check input parameters
    hr = CHK_CIPMConvert_HrInitialize(lpwszMsgClass, fTNEFEncode,
                                      lpszAddrType, lpAddrBook,
                                      lpEnvelope, lpStream);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // make sure that this instance isn't already in use
    fInUse = fCheckInit();

    if ( fInUse )
    {
        hr = HR_LOG(EDK_E_ALREADY_EXISTS);

        goto cleanup;
    }

    // Set mesage class, envelope, TNEF, environment
    // and stream data members
    m_lpwszMsgClass = lpwszMsgClass;
    m_fTNEFEncode = fTNEFEncode;
    m_lpszAddrType = lpszAddrType;
    m_lpAB = lpAddrBook;
    m_lpEnvelope = lpEnvelope;
    m_lpStream = lpStream;

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::HrConvert------------------------------------
//
// DESCRIPTION: Converts an "822-style" header to
//              a MAPI IPM message
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_OUTOFMEMORY if memory problems
//                          E_FAIL if API problems
//                          E_NOTIMPL if conversion not supported
//
// ----------------------------------------------------------------
HRESULT CIPMConvert::HrConvert()    // RETURNS: HRESULT
{
    HRESULT hr              =   NOERROR;
    BOOL    fInitialized    =   FALSE;  // TRUE if conversion instance has been initialized
    ULONG   nRecipients     =   0;      // # of [non]delivery recipients

    DEBUGPRIVATE("CIPMConvert::HrConvert()\n");

    // Check to make sure that we have been initialized
    fInitialized = fCheckInit();

    if ( !fInitialized )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Parse message envelope and content common to
    // all message types and save the data.
    hr = HrParseEnvelopeAndContent();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Do processing which is specific to each message type.
    switch ( m_MsgType )
    {
        case mtIPM:     // interpersonal message

            // Create the message content container
            // This must be called before HrParseIPMSpecific().
            hr = HrCreateContent();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Parse message body.
            hr = HrParseIPMSpecific();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Parse file attachments.
            hr = HrParseAttachments();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // set content properties
            hr = HrSetContentProps();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // If we are doing TNEF decoding, let TNEF decode the
            // content properties.
            if ( m_fTNEFEncode == TRUE )
            {
                hr = HrTnefToContent();

                if ( FAILED(hr) )
                {
                    goto cleanup;
                }

            }   // end if doing TNEF decoding

            // Set envelope properties
            hr = HrSetEnvelopeProps();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // done with IPM
            break;

        case mtNDR:     // non-delivery report
        case mtDR:      // delivery report

            // Parse [non]recipient information
            hr = HrParseReportSpecific(
                        &nRecipients);  // # of [non]delivery recipients

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // A [non]delivery report must have at least one
            // recipient.
            if ( nRecipients < 1 )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // If we are doing TNEF decoding, let TNEF decode the
            // content properties.
            if ( m_fTNEFEncode == TRUE )
            {
                hr = HrTnefToContent();

                if ( FAILED(hr) )
                {
                    goto cleanup;
                }

            }   // end if doing TNEF decoding

            // Set report properties.
            hr = HrSetReportProps();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // done with NDR or DR

            break;

        case mtNRN:
        case mtRN:
            // read/non-read notifications

            // Parse notification-specific data
            hr = HrParseNotificationSpecific();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Create the notification content container
            hr = HrCreateContent();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // If we are doing TNEF decoding, let TNEF decode the
            // content properties.
            if ( m_fTNEFEncode == TRUE )
            {
                hr = HrTnefToContent();

                if ( FAILED(hr) )
                {
                    goto cleanup;
                }

            }   // end if doing TNEF decoding

            // Set envelope properties
            hr = HrSetEnvelopeProps();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Set notification's content properties.
            hr = HrSetNotifyCntProps();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Done with [non]read notification.

            break;

        default:
            // shouldn't happen
            hr = HR_LOG(E_FAIL);

            goto cleanup;

    }   // end switch

    // save the changes to the output message.
    hr = m_lpEnvelope->SaveChanges(
                KEEP_OPEN_READWRITE);       // we don't own envelope pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    // Release and free MAPI objects used for conversion now.
    // Don't keep them in memory any longer than necessary.
    // (Also allows for re-use of thread.)
    Reset();

    RETURN(hr);
}

//$--CIPMConvert::HrParseAttachments------------------------------------------
//
// DESCRIPTION: Handle parsing & creation of binary file attachments
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseAttachments()
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("CIPMConvert::HrParseAttachments()\n");

    // consistency checking
    ASSERTERROR(m_MsgType == mtIPM, "Bad m_MsgType");

    // Parse a binary file attachment at a time (if any)
    //  & create the attachment & its properties
    while ( TRUE )
    {
        hr = HrCreateNextAttachment();

        // Check to see if we have no more attachments.
        // This happens when we reach the end of the file
        // or when we hit the beginning of TNEFed data.
        if ( (hr == EDK_E_END_OF_FILE) ||
             ( (hr == EDK_E_NOT_FOUND) && (m_fTNEFEncode == TRUE) ) )
        {
            hr = NOERROR;       // not really an error

            break;              // get out of this loop
        }

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // parse next attachment

    }   // end while

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::Reset----------------------------------------
//
// DESCRIPTION: resets all of the conversion class' data members
//              and frees all MAPI memory allocated by the class.
//              This allow the thread's class instance to be re-used
//              for another conversion (if desired).
//
// INPUT:       none
//
// RETURNS:     VOID
//
// ----------------------------------------------------------------
VOID CIPMConvert::Reset()        // RETURNS: VOID
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CIPMConvert::Reset()\n");

    // Reset all of the conversion class' data members.
    // This allows the class instance to be re-used by this
    // thread (Also frees MAPI memory).
    m_lpwszMsgClass = NULL;
    m_lpEnvelope = NULL;
    m_lpStream = NULL;
    m_fTNEFEncode = FALSE;
    m_lpszAddrType = NULL;
    m_lpAB = NULL;
    m_MsgType = mtNone;

ZeroMemory( &m_sOffsetHeaders, sizeof(ULARGE_INTEGER));

    ULRELEASE(m_lpContent);
    ULRELEASE(m_lpAttach);

    // Free the data string member array
    (VOID)HrFreeData();

    // Free the address lists
    FREEPADRLIST(m_lpRcptToList);
    FREEPADRLIST(m_lpContentRecips);
    FREEPADRLIST(m_lpReportToList);

    // Free the trace entry list
    // Note: Trace information list is allocated as one big
    // block, so it can be deallocated with MAPIFREEBUFFER.
    MAPIFREEBUFFER(m_lpTraceInfo);

    // Free the internal trace entry list
    // Note: Internal trace information list is allocated as one big
    // block, so it can be deallocated with MAPIFREEBUFFER.
    MAPIFREEBUFFER(m_lpIntTraceInfo);

    return;

}

//$--CIPMConvert::FreeData----------------------------------------------------
//
// DESCRIPTION: Frees the memory used by the data string array member
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise
//
// ----------------------------------------------------------------------------
VOID CIPMConvert::HrFreeData()      // RETURNS: VOID
{
    UINT    iEntry  =   0;          // data entry index

    DEBUGPRIVATE("CIPMConvert::HrFreeData()\n");

    // Free all allocated data string entries
    for ( iEntry = 0; iEntry < nDataEntries; iEntry++ )
    {
        MAPIFREEBUFFER(m_rgszData[iEntry]);

    }   // end for

    return;

}

//$--CIPMConvert::HrParseEnvelopeAndContent-----------------------------------
//
// DESCRIPTION: Read 822-style envelope and common content text
//              from stream and saves data read
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_OUTOFMEMORY if memory problems
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseEnvelopeAndContent()
{
    HRESULT     hr      =   NOERROR;
    LPTSTR      lpTag   =   NULL;       // pointer to tag string
    LPTSTR      lpData  =   NULL;       // pointer to data string
    BOOL        fFound  =   FALSE;      // TRUE if tag is found
    ULONG       nRecips =   0;          // number of recipients
    ULONG       nTraces =   0;          // number of trace entries
    ULONG       nIntTraces = 0;         // number of internal trace entries
    ULONG       cbRead  =   0;          // number bytes read

    // temporary string buffer
    TCHAR       szTempBuffer[ulMaxOutStringLen] =   TEXT("");

    DEBUGPRIVATE("CIPMConvert::HrParseEnvelopeAndContent()\n");

    // consistency checks
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");

    // Read the first tag and data line from the stream.
    // It should the the "X-Tnef-Attach" tag and data line.
    hr = HrParseTagAndData(
        m_lpStream,     // stream pointer
        &cbRead,        // # bytes read from stream
        &lpTag,         // pointer to tag string
        &lpData);       // poitner to data string

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check the tag read against the tag expected
    if ( lstrcmp(lpTag, lpszTagTnefAttach) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERT_STRING_PTR(lpData, "Bad lpData.");

    // Don't trust the TNEF flag passed in by the caller for inbound
    // conversions.  They may not know whether or not the inbound
    // file contains TNEF data.  (If there is TNEF data in the file,
    // then the X-Tnef-Attach line will have non-null data.  Otherwise,
    // the file doesn't contain TNEF data.)
    if ( *lpData == 0 ) // empty string
    {
        // Since no TNEF data file name, must not be doing
        // TNEF decoding.
        m_fTNEFEncode = FALSE;
    }

    else    // TNEF data file name specified
    {
        // Since TNEF data file name, must be doing TNEF
        // encoding.
        m_fTNEFEncode = TRUE;

        ASSERTERROR(lstrcmp(lpData, lpszTagTnefHdr) == 0, "Bad lpData.");
    }

    // Free tag and data strings
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    // Read the "MAIL FROM" data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // pointer to tag string pointer
                &(m_rgszData[iMailFrom]));       // pointer to data string pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expected.
    if ( lstrcmp(lpTag, lpszTagMailFrom) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag string
    MAPIFREEBUFFER(lpTag);

    // Read the "RCPT TO" data lines from the stream.
    hr = HrParseRecipients(
                lpszTagRcptTo,      // tag
                MAPI_TO,            // MAPI recipient type
                &m_lpRcptToList,    // target address list
                &nRecips);          // # recipients of type found

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check the number of "RCPT TO" recipients
    if ( nRecips == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Read the "DATA:" marker from the stream.
    hr = m_lpStream->Read(
                szTempBuffer,           // data buffer
                lstrlen(lpszTagData) * sizeof(TCHAR),  // length (no terminator)
                &cbRead);               // # characters read.

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Check that we have the "DATA" tag
    if ( lstrcmp(szTempBuffer, lpszTagData) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Read the "X-Message-Class" data line from the stream.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iClass]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagMsgClass) != 0 )
    {
        // bad tag
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // Set the message type
    hr = HrSetMessageType(m_rgszData[iClass]);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERTERROR(m_MsgType != mtNone, "Bad m_MsgType");

    // Read the "Date" tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iDate]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagDate) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // Read the "X-Message-ID" tag and data line
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iXID]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagMsgID) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // If we have a report, read the "X-Message-Subject-ID"
    // tag and data line.
    if ( m_MsgType != mtIPM )
    {
        hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iSubjectXID]));       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Check that the tag is what we expect
        if ( lstrcmp(lpTag, lpszTagSubjectID) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Free the tag
        MAPIFREEBUFFER(lpTag);

    }   // end if report

    // Read trace data (if any).
    hr = HrParseExternalTraceData(&nTraces);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Read internal trace data (if any).
    hr = HrParseInternalTraceData(&nIntTraces);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Read the FROM tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iFrom]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagFrom) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // Parse the "To" recipients and store the addresses.
    hr = HrParseRecipients(
                lpszTagTo,              // tag
                MAPI_TO,                // MAPI recipient type
                &m_lpContentRecips,      // target address list
                &nRecips);              // # recipients of type found

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Note:
    // An IPM message content is allowed to have no "To" recipients.
    if ( (m_MsgType != mtIPM) && (nRecips < 1) )
    {
        // Notifications should have a "To" recipient
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Parse the "CC" recipients and store the addresses.
    hr = HrParseRecipients(
                lpszTagCC,              // tag
                MAPI_CC,                // MAPI recipient type
                &m_lpContentRecips,     // target address list
                &nRecips);              // # recipients of type found

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Parse the "BCC" recipients and store the addresses.
    hr = HrParseRecipients(
                lpszTagBCC,             // tag
                MAPI_BCC,               // MAPI recipient type
                &m_lpContentRecips,     // target address list
                &nRecips);              // # recipients of type found

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Read in the blank line which separates the recipients from
    // the subject.
    // First, zero out the temporary array
    ZeroMemory(
                szTempBuffer,       // buffer
                ulMaxOutStringLen);

    hr = m_lpStream->Read(
                szTempBuffer,       // temporary buffer
                lstrlen(lpszNewLine) * sizeof(TCHAR),  // length (no terminator)
                &cbRead);           // number of bytes read

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Check that we read in a new line.
    if ( lstrcmp(szTempBuffer, lpszNewLine) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Parse the "Subject" tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iSubject]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagSubject) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // Parse the "Priority" tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iPriority]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagPriority) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

    // Parse the "Importance" tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &(m_rgszData[iImportance]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that the tag is what we expect
    if ( lstrcmp(lpTag, lpszTagImportance) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free the tag
    MAPIFREEBUFFER(lpTag);

// this location marks the end of the headers
{
LARGE_INTEGERsOffsetZero = {0};
hr = m_lpStream->Seek(
sOffsetZero,        // offset
STREAM_SEEK_CUR,    // from current
&m_sOffsetHeaders);

if ( FAILED(hr) )
{
hr = HR_LOG(E_FAIL);

goto cleanup;
}
}

    // we are done.

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpData);
    MAPIFREEBUFFER(lpTag);

    RETURN(hr);

}

//$--CIPMConvert::HrSetMessageType--------------------------------------------
//
// DESCRIPTION: Sets a message's type, based on the message class
//              property.
//
// INPUT:   lpMessageClass  --  message class.
//
// RETURNS: HRESULT --  NOERROR if successful & is a supported message
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetMessageType(
        IN LPTSTR lpszMessageClass)        // message class
{
    HRESULT         hr                  =   NOERROR;

    // upper-case message class
    TCHAR           pszClassCaps[ulMaxOutStringLen] =   TEXT("");

    // Message class supported
    const LPTSTR    lpszIPMClass    =   TEXT("IPM");
    const LPTSTR    lpszReport      =   TEXT("REPORT.");

    // Report types supported.
    const LPTSTR    lpszNDR         =   TEXT(".NDR");
    const LPTSTR    lpszDR          =   TEXT(".DR");
    const LPTSTR    lpszNRN         =   TEXT(".IPNNRN");
    const LPTSTR    lpszRN          =   TEXT(".IPNRN");

    DEBUGPRIVATE("CIPMConvert::HrSetMessageType()\n");

    // Check input parameters.
    hr = CHK_CIPMConvert_HrSetMessageType(lpszMessageClass);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Convert the class passed in to upper case.
    lstrcpyn(pszClassCaps, lpszMessageClass, ulMaxOutStringLen);
    (VOID)CharUpper(pszClassCaps);

    if ( !TEST_STRING_PTR(pszClassCaps) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Make sure that this is an IPM message of some sort.
    if ( _tcsstr(pszClassCaps, lpszIPMClass) == NULL )
    {
        // Message is not of type IPM.
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // See if this is a report.
    if ( _tcsstr(pszClassCaps, lpszReport) != NULL )
    {
        // We have a report.  See what kind it is.
        if ( _tcsstr(pszClassCaps, lpszNDR) != NULL )
        {
            // we have an NDR.
            m_MsgType = mtNDR;

            goto cleanup;
        }

        if ( _tcsstr(pszClassCaps, lpszDR) != NULL )

{
            // we have a DR.
            m_MsgType = mtDR;

            goto cleanup;
        }

        if ( _tcsstr(pszClassCaps, lpszNRN) != NULL )
        {
            // we have a NRN.
            m_MsgType = mtNRN;

            goto cleanup;
        }

        if ( _tcsstr(pszClassCaps, lpszRN) != NULL )
        {
            // we have an RN.
            m_MsgType = mtRN;

            goto cleanup;
        }

        // Otherwise, we have an unknown report type
        hr = HR_LOG(E_FAIL);

        goto cleanup;

    }   // end if have an IPM REPORT.

    // Otherwise, we have an IPM message.
    m_MsgType = mtIPM;

    // We are done.

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::HrParseIPMSpecific----------------------------------------
//
// DESCRIPTION: Parses IPM specific tag and data lines.
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_OUTOFMEMORY if memory problems,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseIPMSpecific()
{
    HRESULT         hr          =   NOERROR;
    LPTSTR          lpTag       =   NULL;   // tag string
    LPTSTR          lpData      =   NULL;   // data string
    ULONG           cb          =   0;      // byte count
    ULONG           cbRead      =   0;      // number of bytes read
    LARGE_INTEGER   sAdvance    =   {0};    // byte offset

    // temporary buffer
    TCHAR   szTempBuffer[ulMaxOutStringLen] =   TEXT("");

    // MAPI property creation flags
    const ULONG ulFlags     =   MAPI_CREATE | MAPI_MODIFY |
                                MAPI_DEFERRED_ERRORS;

    DEBUGPRIVATE("CIPMConvert::HrParseIPMSpecific()\n");

    // consistency checks
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");
    ASSERTERROR(m_MsgType == mtIPM, "Bad m_MsgType");

    // Parse the "----beginbody" tag and data line.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // number bytes read from stream
                &lpTag,         // tag pointer
                &lpData);       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check that we have the expected tag
    if ( lstrcmp(lpTag, lpszTagBodyHdr) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Determine number of bytes in body.
    cb = atol(lpData);

    if ( cb )
    {
        if ( m_fTNEFEncode == TRUE )
        {
            // don't create the PR_RTF_COMPRESSED property
            // on the content.  Let the TNEF decoding function
            // do this for us.
            // Merely skip over the message body text in the
            // input stream.
            sAdvance.LowPart = cb;  // # of bytes to advance
            hr = m_lpStream->Seek(
                        sAdvance,   // # bytes to advance
                        STREAM_SEEK_CUR,    // from current position
                        NULL);      // don't care

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

        }   // end if doing TNEF encoding

        else    // not doing TNEF encoding
        {
            // Consistency checking
            ASSERTERROR(!FBadUnknown(m_lpContent), "Bad m_lpContent");

            // Create the RTF compressed text data in the message
            // from the ANSI body text.
            hr = HrTextToRTFCompressed(
                cb/sizeof(CHAR),// # characters of ANSI text
                m_lpStream,     // ANSI text stream pointer
0,// don't save attachment rendering positions
NULL,// don't save attachment rendering positions
                m_lpContent,    // MAPI message pointer
0);             // use current code page

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if not doing TNEF encoding
    }   // end if body text

    // Free the tag and data
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    // Read in the "\n----endbody\n" marker
    hr = m_lpStream->Read(
                szTempBuffer,   // marker
                lstrlen(lpszTagBodyEnd) * sizeof(TCHAR), // length (no terminator)
                &cbRead);       // number of bytes read.

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Check that the tag is what we expect.
    if ( lstrcmp(szTempBuffer, lpszTagBodyEnd) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done (attachments are handled by HrCreateNextAttachment().

cleanup:

    // Free tag and data
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    RETURN(hr);

}

//$--CIPMConvert::HrParseRecipients-------------------------------------------
//
// DESCRIPTION: Parses recipient tag and data lines & saves addresses
//
// INPUT:   lpszTag --  expected tag
//
// INPUT/OUTPUT:    lppAdrList   --  target address list pointer
//
// OUTPUT:  pnRecips    --  number of recipients of type found
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_OUTOFMEMORY if memory problems,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseRecipients(
        IN LPCTSTR lpszTag,     // expected tag
        IN ULONG ulType,        // MAPI recipient type
        IN OUT LPADRLIST * lppAdrList, // target address list
        OUT ULONG * pnRecips)   // # recipients found of type
{
    HRESULT         hr              =   NOERROR;
    LPTSTR          lpTag           =   NULL;       // tag string
    LPTSTR          lpData          =   NULL;       // data string
    ULONG           cbRead          =   0;          // # bytes read
    LARGE_INTEGER   sOffsetBack     =   {0};        // # bytes to back up by
LONGLONGcbBack=0;// # bytes to back up by
    LPSPropValue    lpsRecipProps   =   NULL;       // property value array pointer
    ULONG           cbEid           =   0;          // number of bytes in entry identifier
    LPENTRYID       lpEid           =   NULL;       // entry identifier
    ULONG           cbName          =   0;          // # bytes in email address
    DWORD           cbSize          =   0;          // sizeof of property value array

    // recipient property array indices
    const UINT      iEmailAddr      =   0;          // email address
    const UINT      iRecipType      =   1;          // recipient type
    const UINT      iName           =   2;          // display name
    const UINT      iEID            =   3;          // recipient entry identifier
    const UINT      iAddrType       =   4;          // address type
    const UINT      iRecipNumber    =   5;          // recipient number

    // # of recipient properties
    const ULONG     nRecipProps     =   6;

    DEBUGPRIVATE("CIPMConvert::HrParseRecipients()\n");

    // consistency checking

    // check input paramters
    hr = CHK_CIPMConvert_HrParseRecipients(lpszTag, ulType,
                                           lppAdrList, pnRecips);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize input parameters.
    *pnRecips = 0;

    while ( TRUE )
    {
        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);
        MAPIFREEBUFFER(lpEid);

        MAPIFREEBUFFER(lpsRecipProps);

        // Parse one recipient tag and data line at a time.
        hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // #bytes read
                &lpTag,         // tag pointer
                &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Check that tag is of expected type.
        if ( lstrcmp(lpTag, lpszTag) != 0 )
        {
            // Have run out of recipients of desired type.
            // This is not an error.
            // Back up to beginning of line just read.
            cbBack = ((LONGLONG) (-1)) * cbRead;

sOffsetBack.LowPart = LOWLONG(cbBack);
sOffsetBack.HighPart = HILONG(cbBack);

            hr = m_lpStream->Seek(
                sOffsetBack,        // number of bytes to back up by
                STREAM_SEEK_CUR,    // from current position
                NULL);

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            goto cleanup;

        }   // end if look ahead data not what we want

        // Have found a recipient.
        (*pnRecips)++;    // increment count

        // Allocate memory for this recipients properties.
        // (Must do dynamic allocation, as IMessage::ModifyRecipients
        // may reallocate property array buffer.).
        cbSize = sizeof(SPropValue) * nRecipProps;      // sizeof property value array
        hr = MAPIAllocateBuffer(
                    cbSize,   // # bytes to allocate
                    (VOID **) &lpsRecipProps);                    // pointer to array pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        // Initialize property value array
        ZeroMemory(
                    lpsRecipProps,      // buffer
                    cbSize);            // size of buffer

        // Allocate additional memory for the e-mail address.
        cbName = cbStrLen(lpData);
        hr = MAPIAllocateMore(
                    cbName,                                 // # additional bytes
                    lpsRecipProps,                          // original buffer
                    (VOID **) &(lpsRecipProps[iEmailAddr].Value.LPSZ));        // email address buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        // Set up properties for this recipient.
        lpsRecipProps[iEmailAddr].ulPropTag = PR_EMAIL_ADDRESS;   // e-mail address
        lstrcpy(
                lpsRecipProps[iEmailAddr].Value.LPSZ,
                lpData);

        lpsRecipProps[iRecipType].ulPropTag = PR_RECIPIENT_TYPE;  // recipient type
        lpsRecipProps[iRecipType].Value.l = ulType;

        // Allocate more memory for display name
        hr = MAPIAllocateMore(
                    cbName,
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iName].Value.LPSZ));     // display name buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iName].ulPropTag = PR_DISPLAY_NAME;   // display name
        lstrcpy(
                lpsRecipProps[iName].Value.LPSZ,
                lpData);

        ASSERTERROR(!FBadUnknown(m_lpAB), "Bad m_lpAB");
        ASSERTERROR(!IsBadStringPtr(m_lpszAddrType, INFINITE),
                    "Bad lpszAddrType");

        // Create an entry identifier for this "one off" address
        hr = m_lpAB->CreateOneOff(
                    lpData,         // PR_DISPLAY_NAME
                    (LPTSTR) m_lpszAddrType,   // PR_ADDRTYPE
                    lpData,         // PR_EMAIL_ADDRESS
                    0,              // flags
                    &cbEid,         // #bytes in entry ID
                    &lpEid);        // entry identifier

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Allocate more memory for entry identifier
        hr = MAPIAllocateMore(
                    cbEid,              // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iEID].Value.bin.lpb));         // entry identifier buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iEID].ulPropTag = PR_ENTRYID;        // entry identifier
        lpsRecipProps[iEID].Value.bin.cb = cbEid;
        CopyMemory(
               lpsRecipProps[iEID].Value.bin.lpb,   // destination buffer
               lpEid,                               // source buffer
               cbEid);                              // count bytes

        // Allocate more memory for address type
        hr = MAPIAllocateMore(
                    cbStrLen(m_lpszAddrType),   // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iAddrType].Value.LPSZ));    // address type buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iAddrType].ulPropTag = PR_ADDRTYPE;
        lstrcpy(
                lpsRecipProps[iAddrType].Value.LPSZ,
                m_lpszAddrType);

        lpsRecipProps[iRecipNumber].ulPropTag = PR_RECIPIENT_NUMBER;
        lpsRecipProps[iRecipNumber].Value.l = -1;

        if ( *lppAdrList == NULL )
        {
            // Create a new address list.
            hr = HrMAPICreateAddressList(
                nRecipProps,    // number of properties for address
                lpsRecipProps,  // recipient properties
                lppAdrList);    // target address list pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }

        else
        {
            // Append recipient to address list.
            hr = HrMAPIAppendAddressList(
                nRecipProps,    // number of properties for address
                lpsRecipProps,  // recipient properties
                lppAdrList);    // target address list pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if already have address list
    }   // end while

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);
    MAPIFREEBUFFER(lpEid);

    // HrMAPICreateAddressList makes a duplicate of the
    // property value array passed into it.  Thus, free
    // property value array here.
    MAPIFREEBUFFER(lpsRecipProps);

    // caller "owns" address list.

    RETURN(hr);

}

//$--CIPMConvert::HrParseExternalTraceData--------------------------------------
//
// DESCRIPTION: Parse trace tag and data lines and store data.
//
// OUTPUT:  pnEntries   --  # of trace entries found
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseExternalTraceData(
        OUT ULONG * pnEntries)   // # trace entries found
{
    HRESULT         hr          =   NOERROR;
    LPTSTR          lpTag       =   NULL;       // tag string
    LPTSTR          lpData      =   NULL;       // data string
    ULONG           cbRead      =   0;          // # bytes read from stream
    LARGE_INTEGER   sOffsetBack =   {0};        // number of bytes to seek
LONGLONGcbBack=0;// number of bytes to seek

    LONG        lAction     =   0;          // trace action
    FILETIME    ftArrival   =   {0};        // arrival time structure
    FILETIME    ftDeferred  =   {0};        // deferred time structure

    TCHAR       pszADMD[MAX_ADMD_NAME_SIZ]         = TEXT(""); // ADMD name
    TCHAR       pszCountry[MAX_COUNTRY_NAME_SIZ]   = TEXT(""); // country name
    TCHAR       pszPRMDId[MAX_PRMD_NAME_SIZ]       = TEXT(""); // PRMD ID
    TCHAR       pszAttADMD[MAX_ADMD_NAME_SIZ]      = TEXT(""); // attempted ADMD name
    TCHAR       pszAttCountry[MAX_COUNTRY_NAME_SIZ]= TEXT(""); // attempted country name
    TCHAR       pszAttPRMDId[MAX_PRMD_NAME_SIZ]    = TEXT(""); // attempted PRMD ID

    DEBUGPRIVATE("CIPMConvert::HrParseExternalTraceData()\n");

    // check input parameters
    hr = CHK_CIPMConvert_HrParseExternalTraceData(pnEntries);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pnEntries = 0;     // initialize output

    while ( TRUE )
    {
        // Parse "External-Received-By" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagExternalRcvdBy) != 0 )
        {
            // There are no more trace entries.
            // This isn't an error.
            // Rewind back to before this string.
            cbBack = ((LONGLONG) (-1)) * cbRead;

sOffsetBack.LowPart = LOWLONG(cbBack);
sOffsetBack.HighPart = HILONG(cbBack);

            hr = m_lpStream->Seek(
                    sOffsetBack,        // offset
                    STREAM_SEEK_CUR,    // from current position
                    NULL);

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            goto cleanup;
        }

        // Parse the external trace data into its individual components.
        hr = HrParseExternalTraceString(
                lpData,     // trace data string
                &lAction,   // trace action
                pszCountry, // country name
                pszADMD,    // ADMD name
                pszPRMDId); // PRMD identifier

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffer
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "External-Received At" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagExternalRcvdAt) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Convert the date/time string to a file time
        // structure.
        hr = HrParseDateTimeString(
                lpData,         // date time string
                &ftArrival);    // file time structure

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "External-Attempted-By" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check the tag
        if ( lstrcmp(lpTag, lpszTagExternalAttmBy) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Parse the data into its individual components
        hr = HrParseExternalTraceString(
                lpData,         // trace data string
                &lAction,       // trace action
                pszAttCountry,  // attempted country name
                pszAttADMD,     // attempted ADMD name
                pszAttPRMDId);  // attempted PRMD identifier

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "External-Deferred-At" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagExternalDefdAt) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Convert the date/time string to a file time
        // structure.
        hr = HrParseDateTimeString(
                lpData,         // date time string
                &ftDeferred);   // file time structure

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Append this trace entry information to the
        // trace information list.
        if ( m_lpTraceInfo == NULL )
        {
            // create list with entry.
            hr = HrTraceCreateEntryList(
                    lAction,        // trace action
                    ftArrival,      // arrival time
                    ftDeferred,     // deferred time
                    pszADMD,        // ADMD name
                    pszCountry,     // country name
                    pszPRMDId,      // PRMD identifier
                    pszAttADMD,     // attempted ADMD
                    pszAttCountry,  // attempted country
                    pszAttPRMDId,   // attempted PRMD ID
                    &m_lpTraceInfo);    // pointer to trace information pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }

        else
        {
            // append entry to list
            hr = HrTraceAppendEntryList(
                    lAction,        // trace action
                    ftArrival,      // arrival time
                    ftDeferred,     // deferred time
                    pszADMD,        // ADMD name
                    pszCountry,     // country name
                    pszPRMDId,      // PRMD identifier
                    pszAttADMD,     // attempted ADMD
                    pszAttCountry,  // attempted country
                    pszAttPRMDId,   // attempted PRMD ID
                    &m_lpTraceInfo);    // pointer to trace information pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if trace information list exists

        (*pnEntries)++;   // increment # of entries found

    }   // end while

    // we are done.

cleanup:

    // free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    RETURN(hr);

}

//$--CIPMConvert::HrParseInternalTraceData--------------------------------------
//
// DESCRIPTION: Parse trace tag and data lines and store data.
//
// OUTPUT:  pnEntries   --  # of trace entries found
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseInternalTraceData(
        OUT ULONG * pnEntries)   // # trace entries found
{
    HRESULT         hr          =   NOERROR;
    LPTSTR          lpTag       =   NULL;       // tag string
    LPTSTR          lpData      =   NULL;       // data string
    ULONG           cbRead      =   0;          // # bytes read from stream
    LARGE_INTEGER   sOffsetBack =   {0};        // number of bytes to seek
LONGLONGcbBack=0;// number of bytes to seek

    LONG        lAction     =   0;          // trace action
    FILETIME    ftArrival   =   {0};        // arrival time structure
    FILETIME    ftDeferred  =   {0};        // deferred time structure

    TCHAR       pszADMD[MAX_ADMD_NAME_SIZ]         = TEXT(""); // ADMD name
    TCHAR       pszCountry[MAX_COUNTRY_NAME_SIZ]   = TEXT(""); // country name
    TCHAR       pszPRMDId[MAX_PRMD_NAME_SIZ]       = TEXT(""); // PRMD ID
    TCHAR       pszMTA[MAX_ADMD_NAME_SIZ]          = TEXT(""); // MTA name
    TCHAR       pszAttADMD[MAX_ADMD_NAME_SIZ]      = TEXT(""); // attempted ADMD name
    TCHAR       pszAttCountry[MAX_COUNTRY_NAME_SIZ]= TEXT(""); // attempted country name
    TCHAR       pszAttPRMDId[MAX_PRMD_NAME_SIZ]    = TEXT(""); // attempted PRMD ID
    TCHAR       pszAttMTA[MAX_MTA_NAME_SIZ]        = TEXT(""); // attempted MTA name

    DEBUGPRIVATE("CIPMConvert::HrParseInternalTraceData()\n");

    // check input parameters
    hr = CHK_CIPMConvert_HrParseInternalTraceData(pnEntries);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pnEntries = 0;     // initialize output

    while ( TRUE )
    {
        // Parse "Internal-Received-By" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagInternalRcvdBy) != 0 )
        {
            // There are no more trace entries.
            // This isn't an error.
            // Rewind back to before this string.
            cbBack = ((LONGLONG) (-1)) * cbRead;

sOffsetBack.LowPart = LOWLONG(cbBack);
sOffsetBack.HighPart = HILONG(cbBack);

            hr = m_lpStream->Seek(
                    sOffsetBack,        // offset
                    STREAM_SEEK_CUR,    // from current position
                    NULL);

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            goto cleanup;
        }

        // Parse the internal trace data into its individual components.
        hr = HrParseInternalTraceString(
                lpData,     // trace data string
                &lAction,   // trace action
                pszCountry, // country name
                pszADMD,    // ADMD name
                pszPRMDId,  // PRMD identifier
                pszMTA);    // MTA name

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffer
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "Internal-Received At" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagInternalRcvdAt) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Convert the date/time string to a file time
        // structure.
        hr = HrParseDateTimeString(
                lpData,         // date time string
                &ftArrival);    // file time structure

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "Internal-Attempted-By" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check the tag
        if ( lstrcmp(lpTag, lpszTagInternalAttmBy) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Parse the data into its individual components
        hr = HrParseInternalTraceString(
                lpData,         // trace data string
                &lAction,       // trace action
                pszAttCountry,  // attempted country name
                pszAttADMD,     // attempted ADMD name

pszAttPRMDId,   // attempted PRMD identifier
                pszAttMTA);     // attempted MTA name

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Parse "Internal-Deferred-At" tag and data line.
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag.
        if ( lstrcmp(lpTag, lpszTagInternalDefdAt) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Convert the date/time string to a file time
        // structure.
        hr = HrParseDateTimeString(
                lpData,         // date time string
                &ftDeferred);   // file time structure

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Append this trace entry information to the
        // trace information list.
        if ( m_lpIntTraceInfo == NULL )
        {
            // create list with entry.
            hr = HrInternalTraceCreateEntryList(
                    lAction,        // trace action
                    ftArrival,      // arrival time
                    ftDeferred,     // deferred time
                    pszADMD,        // ADMD name
                    pszCountry,     // country name
                    pszPRMDId,      // PRMD identifier
                    pszMTA,         // MTA name
                    pszAttADMD,     // attempted ADMD
                    pszAttCountry,  // attempted country
                    pszAttPRMDId,   // attempted PRMD ID
                    pszAttMTA,      // attempted MTA
                    &m_lpIntTraceInfo);    // pointer to trace information pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }

        else
        {
            // append entry to list
            hr = HrInternalTraceAppendEntryList(
                    lAction,        // trace action
                    ftArrival,      // arrival time
                    ftDeferred,     // deferred time
                    pszADMD,        // ADMD name
                    pszCountry,     // country name
                    pszPRMDId,      // PRMD identifier
                    pszMTA,         // MTA name
                    pszAttADMD,     // attempted ADMD
                    pszAttCountry,  // attempted country
                    pszAttPRMDId,   // attempted PRMD ID
                    pszAttMTA,      // attempted MTA
                    &m_lpIntTraceInfo);    // pointer to trace information pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if trace information list exists

        (*pnEntries)++;   // increment # of entries found

    }   // end while

    // we are done.

cleanup:

    // free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    RETURN(hr);

}

//$--CIPMConvert::HrCreateContent---------------------------------------------
//
// DESCRIPTION: Create an envelope content message.
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrCreateContent()
{
    HRESULT             hr              =   NOERROR;
    ULONG               ulAttachNum     =   0;      // attachment number
    LPSPropProblemArray lpsProblems     =   NULL;   // problem array pointer

    // MAPI flags
    const ULONG ulFlags                 =
        MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS;

    // property array indices
    const UINT  iMethod                 =   0;
    const UINT  iPosition               =   1;

    const UINT  nProps                  =   2;      // # of properties

    SPropValue  rgAttachProps[nProps]   =   {0};    // array of properties

    DEBUGPRIVATE("CIPMConvert::HrCreateContent()\n");

    // consistency checks
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");
    ASSERTERROR((m_MsgType == mtIPM || m_MsgType == mtRN || m_MsgType == mtNRN),
                "Bad m_MsgType");

    // If we are doing TNEF decoding, let TNEF create
    // the envelope attachment & content.
    if ( m_fTNEFEncode == TRUE )
    {
        goto cleanup;
    }

    hr = m_lpEnvelope->CreateAttach(
                NULL,           // interface ID pointer
                MAPI_DEFERRED_ERRORS,   // MAPI flags (reduces RPC calls)
                &ulAttachNum,   // attachment number
                &m_lpAttach);   // pointer to attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(m_lpAttach), "Bad m_lpAttach");

    // Set the appropriate attachment properties for a
    // content message.
    rgAttachProps[iMethod].ulPropTag = PR_ATTACH_METHOD;  // property tag
    rgAttachProps[iMethod].Value.l = ATTACH_EMBEDDED_MSG; // value

    rgAttachProps[iPosition].ulPropTag = PR_RENDERING_POSITION;
    rgAttachProps[iPosition].Value.l = INFINITE;  // default

    // Set attachment properties
    hr = m_lpAttach->SetProps(
                nProps,         // # of properties
                rgAttachProps,  // array of properties
                &lpsProblems);  // problem array pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        lpsProblems = NULL;     // so we can safely release it

        goto cleanup;
    }

    // check problem array
    if ( lpsProblems )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Create a message attachment.
    hr = m_lpAttach->OpenProperty(
                PR_ATTACH_DATA_OBJ,     // for a message object
                &IID_IMessage,          // MAPI message interface
                0,                      // interface flags
                ulFlags,                // MAPI property creation flags
                (LPUNKNOWN *) &m_lpContent);          // envelope content pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(m_lpContent), "Bad m_lpContent");

    // we are done.

cleanup:

    // FREE MAPI buffers
    MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}

//$--CIPMConvert::HrCreateNextAttachment--------------------------------------
//
// DESCRIPTION: Parse and create next envelope content attachment.
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      EDK_E_NOT_FOUND if no more attachments
//                      E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrCreateNextAttachment()
{
    HRESULT         hr              =   NOERROR;
    LPTSTR          lpTag           =   NULL;   // tag string
    LPTSTR          lpData          =   NULL;   // data string
    ULONG           cb              =   0;      // # of bytes in attachment
    LPTSTR          lpszFileName    =   NULL;   // file name string
    ULONG           cbRead          =   0;      // # bytes read from stream
    LPATTACH        lpAttach        =   NULL;   // attachment pointer
    ULONG           ulAttachNum     =   0;      // attachment number
    PVIRTUALSTREAMONPROPERTYlpAttachStream  =   NULL;   // buffered stream
    LARGE_INTEGER   sOffset         =   {0};    // # bytes to retreat
LONGLONGcbOffset=0;// # bytes to retreat
    ULARGE_INTEGER  sSize           =   {0};    // # bytes in attachment

    // temporary string buffer
    TCHAR       szTempBuffer[ulMaxOutStringLen] =   TEXT("");

   // MAPI property creation flags
    const ULONG ulFlags     =   MAPI_CREATE | MAPI_MODIFY |
                                MAPI_DEFERRED_ERRORS;   // reduces RPCs

    const UINT  cProps      =   5;      // number of properties in array
    const UINT  iFileName   =   0;      // file name property index
    const UINT  iLongName   =   1;      // long file name property index
    const UINT  iMethod     =   2;      // attachment method property index
    const UINT  iPosition   =   3;      // renderning position property index
    const UINT  iName       =   4;      // display name property index

    // Property tag array for an attachment
    SPropValue  sAttachProps[cProps]    =   {0};

    // property problem array pointer
    LPSPropProblemArray lpProblems      =   NULL;

    DEBUGPRIVATE("CIPMConvert::HrCreateNextAttachment()\n");

    // Parse "----beginattach" tag and data
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // # bytes read
                &lpTag,         // tag pointer
                &lpData);       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check the tag.
    if ( lstrcmp(lpTag, lpszTagAttachHdr) != 0 )
    {
        // We have read ahead to far.
        // (TNEF data may follow)
        // Back up to where we were before.
        cbOffset = ((LONGLONG) (-1)) * cbRead; // negative offset

sOffset.LowPart = LOWLONG(cbOffset);
sOffset.HighPart = HILONG(cbOffset);

        hr = m_lpStream->Seek(
                    sOffset,    // # bytes to retreat
                    STREAM_SEEK_CUR,    // from current position
                    NULL);      // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Let caller know that there are no more attachments
        hr = HR_LOG(EDK_E_NOT_FOUND);

        goto cleanup;
    }

    // Parse attachment header data into file name
    // and file size (in bytes) components.
    hr = HrParseAttachmentHeader(
                lpData,     // attachment header data
                &lpszFileName,// file name
                &cb);       // number of bytes in attachment

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERTERROR(lpszFileName != NULL, "Bad lpszFileName");

    // check to make sure that attachment file name isn't empty.
    if ( *lpszFileName == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Note: Only support binary attachments at this time.

    // free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);

    if ( m_fTNEFEncode == FALSE )   // If not doing TNEF decoding
    {
        // consistency check
        ASSERT_IUNKNOWN_PTR(m_lpContent, "Bad m_lpContent");

        // Create attachment
        hr = m_lpContent->CreateAttach(
                NULL,           // interface ID pointer
                MAPI_DEFERRED_ERRORS,   // flags (reduces RPCs)
                &ulAttachNum,   // attachment number
                &lpAttach);     // pointer to attachment pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR(lpAttach, "Bad lpAttach");

        // Create a buffered stream on the attachment's binary data and
        // read the attachment data into it.
hr = HrOpenVirtualStreamOnProperty(
lpAttach,// MAPI property object pointer
            PR_ATTACH_DATA_BIN, // property tag
            ulFlags,            // MAPI property creation flags
            &lpAttachStream); // buffered stream pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR(lpAttachStream, "Bad lpAttachStream");

        // Read the attachment data into the attachment stream.
        sSize.LowPart = cb;         // # bytes in attachment
        hr = m_lpStream->CopyTo(    // source stream
            (LPSTREAM) lpAttachStream,// destination stream
            sSize,                  // # bytes to copy
            NULL,
            NULL);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

// Commit changes to the attachment data stream.
// We must do this, since the stream is buffered.
hr = lpAttachStream->Commit(
0);// not going to do anything else with it

if ( FAILED(hr) )
{
hr = HR_LOG(E_FAIL);

goto cleanup;
}
    }   // end if not doing TNEF decoding

    else    // are doing TNEF decoding
    {
        // Let TNEF extract the attachments.
        // Merely skip over the attachment data for now.
        sOffset.LowPart = cb;    // # of bytes
        hr = m_lpStream->Seek(
                    sOffset,    // # bytes to advance
                    STREAM_SEEK_CUR,    // from current position,
                    NULL);      // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

    }   // end if doing TNEF decoding

    // Read in "----endattach" marker
    hr = m_lpStream->Read(
            szTempBuffer,   // data buffer
            lstrlen(lpszTagAttachEnd) * sizeof(TCHAR), // length (no terminator)
            &cbRead);       // number of bytes read

    // test the marker
    if ( lstrcmp(szTempBuffer, lpszTagAttachEnd) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    if ( m_fTNEFEncode == FALSE )
    {
        // Set attachment properties.
        sAttachProps[iFileName].ulPropTag = PR_ATTACH_FILENAME;     // property tag
        sAttachProps[iFileName].Value.LPSZ = lpszFileName;            // file name

        sAttachProps[iLongName].ulPropTag = PR_ATTACH_LONG_FILENAME;// property tag
        sAttachProps[iLongName].Value.LPSZ = lpszFileName;            // file name

        sAttachProps[iMethod].ulPropTag = PR_ATTACH_METHOD; // property tag
        sAttachProps[iMethod].Value.l = ATTACH_BY_VALUE;    // value

        sAttachProps[iPosition].ulPropTag = PR_RENDERING_POSITION;
        sAttachProps[iPosition].Value.l = INFINITE;         // default

        sAttachProps[iName].ulPropTag = PR_DISPLAY_NAME;    // property tag
        sAttachProps[iName].Value.LPSZ = lpszFileName;        // file name

        // Set properties.
        hr = lpAttach->SetProps(
            cProps,                 // # property values
            (LPSPropValue) &sAttachProps,   // property value array
            &lpProblems);           // problem array pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            lpProblems = NULL;

            goto cleanup;
        }

        // Fail if not all properties were set.
        if ( lpProblems )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Save changes to the attachment
        hr = lpAttach->SaveChanges(
                MAPI_DEFERRED_ERRORS);      // save's RPCs

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }
    }   // end if not doing TNEF decoding

    // we are done with this attachment

cleanup:

    // Release MAPI objects
    ULRELEASE(lpAttach);
    ULRELEASE(lpAttachStream);

    // free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);
    MAPIFREEBUFFER(lpszFileName);
    MAPIFREEBUFFER(lpProblems);

    RETURN(hr);

}

//$--HrSetEnvelopeProps-------------------------------------------------------
//
// DESCRIPTION: Sets IPM envelope properties
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetEnvelopeProps()
{
    HRESULT             hr                  =   NOERROR;
    LPSPropProblemArray lpsProblems         =   NULL;   // property problem array
    ULONG               cbEid               =   0;      // # bytes in entry ID
    LPENTRYID           lpEid               =   NULL;   // entry identifier
    LPTSTR              lpszSubjectPrefix   =   NULL;   // subject prefix
    LPTSTR              lpszNormSubject     =   NULL;   // normalized subject
    FILETIME            ftArrival           =   {0};    // arrival time
    LPTSTR              lpMailFromAddr      =   NULL;   // Mail from address
    ULONG               nEnvProps           =   0;      // # of envelope properties
    BOOL                fDRRequested        =   FALSE;  // TRUE if DRs requeted for IPM

    // message envelope class
    TCHAR               pszMsgClass[ulMaxOutStringLen * 2]  =   TEXT("");

    // Property value array indices
    const UINT  iClassVal               =   0;
    const UINT  iFromAddrVal            =   1;
    const UINT  iFromNameVal            =   2;
    const UINT  iFromEidVal             =   3;
    const UINT  iFromAddrTypeVal        =   4;
    const UINT  iXIDVal                 =   5;
    const UINT  iSubjectVal             =   6;
    const UINT  iDeleteVal              =   7;
    const UINT  iArrivalTimeVal         =   8;

    // properties specific to IPMs
    const UINT  iDRRequested            =   9;

    // minimum number of IPM envelope properties
    const ULONG nIPMEnvProps            =   10;

    // properties specific to notifications
    const UINT  iNotifyReqVal           =   9;

    // minimum number of notification properties
    const ULONG nNotifyEnvProps         =   10;

    // property value array
    // NOTE:  The PR_TRACE_INFO and the PR_INTERNAL_TRACE_INFO properties
    // are optional. Thus, the maximum number of envelope properties
    // is two more than the minimum number of notify properties.
    SPropValue  rgEnvProps[nNotifyEnvProps + 2] =   {0};

    const LPCTSTR   lpszEnvClass        =   TEXT("ENVELOPE.");

    DEBUGPRIVATE("CIPMConvert::HrSetEnvelopeProps()\n");

    // consistency checking
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");
    ASSERTERROR((m_MsgType == mtIPM || m_MsgType == mtNRN || m_MsgType == mtRN),
                 "Bad m_MsgType");

    // have an IPM envelope
    lstrcpy(pszMsgClass, lpszEnvClass);

    // Append message class to envelope class.
    lstrcat(pszMsgClass, m_rgszData[iClass]);

    // Set properties on envelope.
    rgEnvProps[iClassVal].ulPropTag = PR_MESSAGE_CLASS;
    rgEnvProps[iClassVal].Value.LPSZ = pszMsgClass;

    lpMailFromAddr = m_rgszData[iMailFrom]; // mail from address
    rgEnvProps[iFromAddrVal].ulPropTag = PR_ORIGINATOR_ADDR;
    rgEnvProps[iFromAddrVal].Value.LPSZ = lpMailFromAddr;

    rgEnvProps[iFromNameVal].ulPropTag = PR_ORIGINATOR_NAME;
    rgEnvProps[iFromNameVal].Value.LPSZ = lpMailFromAddr;

    // Create an entry identifier for this "one off" address
    hr = m_lpAB->CreateOneOff(
                    lpMailFromAddr,     // name
                    (LPTSTR) m_lpszAddrType,   // address type
                    lpMailFromAddr,     // address
                    0,              // flags
                    &cbEid,         // #bytes in entry ID
                    &lpEid);        // entry identifier

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgEnvProps[iFromEidVal].ulPropTag = PR_ORIGINATOR_ENTRYID;
    rgEnvProps[iFromEidVal].Value.bin.cb = cbEid;
    rgEnvProps[iFromEidVal].Value.bin.lpb = (BYTE *) lpEid;

    rgEnvProps[iFromAddrTypeVal].ulPropTag = PR_ORIGINATOR_ADDRTYPE;
    rgEnvProps[iFromAddrTypeVal].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    rgEnvProps[iXIDVal].ulPropTag = PR_MESSAGE_SUBMISSION_ID;
    rgEnvProps[iXIDVal].Value.bin.cb =
        cbStrLen(m_rgszData[iXID]);
    rgEnvProps[iXIDVal].Value.bin.lpb = (BYTE *) m_rgszData[iXID];

    rgEnvProps[iSubjectVal].ulPropTag = PR_SUBJECT;       // same as for content
    rgEnvProps[iSubjectVal].Value.LPSZ = m_rgszData[iSubject];

    rgEnvProps[iDeleteVal].ulPropTag = PR_DELETE_AFTER_SUBMIT;
    rgEnvProps[iDeleteVal].Value.b = TRUE;

    // Determine the arrival time.  This is the current time!

    GetSystemTimeAsFileTime( &ftArrival);

    rgEnvProps[iArrivalTimeVal].ulPropTag = PR_ARRIVAL_TIME;
    rgEnvProps[iArrivalTimeVal].Value.ft = ftArrival;

    // Set the properties unique to notifications
    // and set the number of properties to set
    if ( m_MsgType == mtIPM )
    {
        // Set the PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED property,
        // when available, so that delivery reports will be generated.
        if ( m_fTNEFEncode == TRUE )
        {
            // Value of PR_ORIGINATORY_DELIVERY_REPORT_REQUESTED is
            // in the TNEF-decoded message content.
            // First, open the content.
            hr = m_lpEnvelope->OpenAttach(
                0,  // attachment #
                NULL,   // interface (whatever is appropriate)
                MAPI_DEFERRED_ERRORS,   // reduces RPCs
                &m_lpAttach);         // attachment pointer

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            ASSERT_IUNKNOWN_PTR(m_lpAttach, "Bad m_lpAttach");

            // Open the attachment as a message.
            hr = m_lpAttach->OpenProperty(
                PR_ATTACH_DATA_OBJ, // property tag
                &IID_IMessage,  // interface identifier
                0,              // interface flags
                MAPI_DEFERRED_ERRORS,   // reduces RPCs
                (LPUNKNOWN *) &m_lpContent);

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            ASSERT_IUNKNOWN_PTR(m_lpContent, "Bad m_lpContent.");

            // Retrive the content's delivery report request value.
            hr = HrMAPIGetPropBoolean(
                m_lpContent,        // message content poitner
                PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,    // property tag
                &fDRRequested);     // property value

            // If the PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED property
            // doesn't exist in the content, just set leave it at to the default
            // value of false.
            if ( hr == MAPI_E_NOT_FOUND )   // does NOT return EDK_E_NOT_FOUND!
            {
                // Not an error.  Just set delivery report
                // requested value to FALSE.
                fDRRequested = FALSE;

                hr = HR_LOG(NOERROR);
            }

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

        }   // end if doing TNEF decoding

        // Set the delivery report requested value.
        rgEnvProps[iDRRequested].ulPropTag =
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
        rgEnvProps[iDRRequested].Value.b = fDRRequested;

        // set number of envelope properties
        nEnvProps = nIPMEnvProps;
    }

    else    // have a notification
    {
        // Set the appropriate notification requested property.
        if ( m_MsgType == mtNRN )   // have a non-read notification
        {
            rgEnvProps[iNotifyReqVal].ulPropTag =
                PR_NON_RECEIPT_NOTIFICATION_REQUESTED;
        }

        else    // have a read notification
        {
            rgEnvProps[iNotifyReqVal].ulPropTag =
                PR_READ_RECEIPT_REQUESTED;
        }

        rgEnvProps[iNotifyReqVal].Value.b = TRUE;

        // set number of envelope properties
        nEnvProps = nNotifyEnvProps;

    }   // end if notification

    // Set the trace information, if any.
    if ( m_lpTraceInfo != NULL )
    {
        rgEnvProps[nEnvProps].ulPropTag = PR_TRACE_INFO;
        rgEnvProps[nEnvProps].Value.bin.cb = CbTRACEINFO(m_lpTraceInfo);
        rgEnvProps[nEnvProps].Value.bin.lpb = (BYTE *) m_lpTraceInfo;

        // increment the property count
        nEnvProps++;

    }   // end if trace information

    // Set the internal trace information, if any.
    if ( m_lpIntTraceInfo != NULL )
    {
        rgEnvProps[nEnvProps].ulPropTag = PR_INTERNAL_TRACE_INFO;
        rgEnvProps[nEnvProps].Value.bin.cb = CbINTTRACEINFO(m_lpIntTraceInfo);
        rgEnvProps[nEnvProps].Value.bin.lpb = (BYTE *) m_lpIntTraceInfo;

        // increment the property count
        nEnvProps++;

    }   // end if trace information

    // Set the PR_TRANSPORT_MESSAGE_HEADER property
    hr = HrSetTransHdr();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    hr = m_lpEnvelope->SetProps(
                nEnvProps,      // # of properties values
                rgEnvProps,     // property value array
                &lpsProblems);  // problem array

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        lpsProblems = NULL;

        goto cleanup;
    }

    if ( lpsProblems != NULL )
    {
        // problems with property values
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Set properties on envelope's recipient table.
    hr = m_lpEnvelope->ModifyRecipients(
                MODRECIP_ADD,       // flags
                m_lpRcptToList);    // address list

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpEid);
    MAPIFREEBUFFER(lpszSubjectPrefix);
    MAPIFREEBUFFER(lpszNormSubject);
    MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}

//$--CIPMConvert::HrSetContentProps-------------------------------------------
//
// DESCRIPTION: Sets IPM content properties
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetContentProps()
{
    HRESULT             hr                      =   NOERROR;
    LPSPropProblemArray lpsProblems             =   NULL;   // problems array pointer
    ULONG               ulPriority              =   0;      // priority value
    ULONG               ulImportance            =   0;      // importance value
    FILETIME            ftClientSubmit          =   {0};    // file time structure
    ULONG               cbEid                   =   0;      // # bytes in entry identifier
    LPENTRYID           lpEid                   =   NULL;   // entry identifier

    // Property value array indices
    const UINT          iClassVal               =   0;
    const UINT          iFromAddrVal            =   1;
    const UINT          iFromNameVal            =   2;
    const UINT          iFromEidVal             =   3;
    const UINT          iFromAddrTypeVal        =   4;
    const UINT          iSubmitTimeVal          =   5;
    const UINT          iImportanceVal          =   6;
    const UINT          iPriorityVal            =   7;
    const UINT          iSubjectVal             =   8;
const UINTiFromAddrVal2=9;
const UINTiFromNameVal2=10;
const UINTiFromEidVal2=11;
const UINTiFromAddrTypeVal2=12;

    // # of property values
    const ULONG         nCntProps               =   13;

    // property value arrray
    SPropValue          rgCntProps[nCntProps]   =   {0};

    DEBUGPRIVATE("CIPMConvert::HrSetContentProps()\n");

    // consistency checking
    ASSERTERROR(m_MsgType == mtIPM,
                "Bad m_MsgType");

    // If TNEF decoding is desired, let TNEF extract the content
    // properties
    if ( m_fTNEFEncode == TRUE )
    {
        goto cleanup;
    }

    // Set up the content properties.
    rgCntProps[iClassVal].ulPropTag = PR_MESSAGE_CLASS;
    rgCntProps[iClassVal].Value.LPSZ = m_rgszData[iClass];

// PR_SENDER_* and PR_SENT_REPRESENTING_* properties
    rgCntProps[iFromAddrVal].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
    rgCntProps[iFromAddrVal].Value.LPSZ = m_rgszData[iFrom];

    rgCntProps[iFromNameVal].ulPropTag = PR_SENDER_NAME;
    rgCntProps[iFromNameVal].Value.LPSZ = m_rgszData[iFrom];

    // Create an entry identifier for this "one off" address
    hr = m_lpAB->CreateOneOff(
                    m_rgszData[iFrom],          // name

(LPTSTR) m_lpszAddrType,    // address type
                    m_rgszData[iFrom],          // address
                    0,              // flags
                    &cbEid,         // #bytes in entry ID
                    &lpEid);        // entry identifier

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgCntProps[iFromEidVal].ulPropTag = PR_SENDER_ENTRYID;
    rgCntProps[iFromEidVal].Value.bin.cb = cbEid;
    rgCntProps[iFromEidVal].Value.bin.lpb = (BYTE *) lpEid;

    rgCntProps[iFromAddrTypeVal].ulPropTag = PR_SENDER_ADDRTYPE;
    rgCntProps[iFromAddrTypeVal].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    rgCntProps[iFromAddrVal2].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
    rgCntProps[iFromAddrVal2].Value.LPSZ = m_rgszData[iFrom];

    rgCntProps[iFromNameVal2].ulPropTag = PR_SENT_REPRESENTING_NAME;
    rgCntProps[iFromNameVal2].Value.LPSZ = m_rgszData[iFrom];

    rgCntProps[iFromEidVal2].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
    rgCntProps[iFromEidVal2].Value.bin.cb = cbEid;
    rgCntProps[iFromEidVal2].Value.bin.lpb = (BYTE *) lpEid;

    rgCntProps[iFromAddrTypeVal2].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
    rgCntProps[iFromAddrTypeVal2].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    // Determine the client submit file time
    hr = HrParseDateTimeString(
                m_rgszData[iDate],        // date time string
                &ftClientSubmit);       // file time structure pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgCntProps[iSubmitTimeVal].ulPropTag = PR_CLIENT_SUBMIT_TIME;
    rgCntProps[iSubmitTimeVal].Value.ft = ftClientSubmit;

    // Determine importance
    hr = HrParseImportanceString(
                m_rgszData[iImportance],      // importance string
                &ulImportance);             // importance value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgCntProps[iImportanceVal].ulPropTag = PR_IMPORTANCE;
    rgCntProps[iImportanceVal].Value.l = ulImportance;

    // Determine priority
    hr = HrParsePriorityString(
                m_rgszData[iPriority],        // priority string
                &ulPriority);               // priority value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgCntProps[iPriorityVal].ulPropTag = PR_PRIORITY;
    rgCntProps[iPriorityVal].Value.l = ulPriority;

    rgCntProps[iSubjectVal].ulPropTag = PR_SUBJECT;
    rgCntProps[iSubjectVal].Value.LPSZ = m_rgszData[iSubject];

    // more consistency checks
    ASSERTERROR(!FBadUnknown(m_lpContent), "Bad m_lpContent");

    // Set properties on the content.
    hr = m_lpContent->SetProps(
                nCntProps,      // # properties
                rgCntProps,     // property value array
                &lpsProblems);  // problems array pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        lpsProblems = NULL;

        goto cleanup;
    }

    if ( lpsProblems != NULL )
    {
        // problems with property values
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Modify the content's recipient table.
    hr = m_lpContent->ModifyRecipients(
                MODRECIP_ADD,       // flags
                m_lpContentRecips); // address list

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // save the changes to the message content
    hr = m_lpContent->SaveChanges(
                MAPI_DEFERRED_ERRORS);      // saves RPCs

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // save changes to the envelope attachment
    hr = m_lpAttach->SaveChanges(
                0);                     // we are done with the envelope attachment

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpEid);
    MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}

//$--CIPMConvert::HrSetTransHdr------------------------------------------------
//
// DESCRIPTION: Set the PR_TRANSPORT_MESSAGE_HEADER property
//              on the envelope.
//
// ASSUMPTIONS: The entire input stream has been read when this
//              function is called.  (i.e.  This function will only
//              set the PR_TRANSPORT_MESSAGE_HEADERS property equal
//              to the contents of the beginning of the stream to its
//              current position.)
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetTransHdr()
{
    HRESULT         hr              =   NOERROR;
    PVIRTUALSTREAMONPROPERTYlpTransHdrStrm  =   NULL;   // buffered stream pointer
    LARGE_INTEGER   sOffsetBegin    =   {0};    // begin position

    // MAPI property creation flags
    const ULONG ulFlags         =   MAPI_CREATE | MAPI_MODIFY |
                                    MAPI_DEFERRED_ERRORS;

    DEBUGPRIVATE("CIPMConvert::HrSetTransHdr()\n");

    // consistency checking
    ASSERT_IUNKNOWN_PTR(m_lpEnvelope, "Bad m_lpEnvelope");
ASSERTERROR( m_sOffsetHeaders.LowPart != 0, "No headers detected");

    // The PR_TRANSPORT_MESSAGE_HEADERS envelope property is
    // merely a copy of the input "header" read by the inbound
    // conversion DLL.
    // Create this property now via a virtual (e.g. buffered) stream
hr = HrOpenVirtualStreamOnProperty(
m_lpEnvelope,// MAPI object pointer
PR_TRANSPORT_MESSAGE_HEADERS,// property tag
ulFlags,// MAPI property flags
&lpTransHdrStrm);// virtual (buffered) stream pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(lpTransHdrStrm, "Bad lpTransHdrStrm");

    // Seek to begining of input stream
    hr = m_lpStream->Seek(
        sOffsetBegin,   // offset
        STREAM_SEEK_SET,// from beginning
        NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Copy input stream to transport header stream
    hr = m_lpStream->CopyTo(
                (LPSTREAM) lpTransHdrStrm,     // destination stream
                m_sOffsetHeaders,   // should be size of headers
                NULL,               // don't care
                NULL);              // don't care

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // save changes to the PR_TRANSPORT_MESSAGE_HEADER data stream.
    hr = lpTransHdrStrm->Commit(
            0);     // not going to do anything else but free it

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    // release the PR_TRANSPORT_MESSAGE_HEADER data strem
    ULRELEASE(lpTransHdrStrm);

    RETURN(hr);

}


CONVCLS.H

// --convcls.h--------------------------------------------------------------
//
// IPM to 822-style header conversion class header file.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#ifndef _CONVCLS_H
#define _CONVCLS_H

// indices into the tag data member array
const UINT  iMailFrom   =   0;      // "Mail From" data
const UINT  iClass      =   1;      // "X-Message-Class" data
const UINT  iDate       =   2;      // "Date" data
const UINT  iXID        =   3;      // "X-Message-ID" data
const UINT  iSubjectXID =   4;      // "X-Message-Subject-ID" data
const UINT  iFrom       =   5;      // "From" data
const UINT  iSubject    =   6;      // "Subject" data
const UINT  iPriority   =   7;      // "Priority" data
const UINT  iImportance =   8;      // "Importance" data

// read/not read notification data member indices
const UINT  iReadBy     =   9;      // read/not read by address data
const UINT  iNotifyTime =   10;     // read/not read notification time
const UINT  iDiscardCode=   11;     // not read discard code data
const UINT  iNonRcptCode=   12;     // not read non-receipt code data

const UINT  nDataEntries    =   13; // number of entries in data member array

//$--MSG_TYPE-----------------------------------------------
//
// Message type enumeration (based on message class)
//
// -------------------------------------------------------------
enum MSG_TYPE
{
    mtNone,        // no type
    mtIPM,         // interpersonal message
    mtNDR,         // non-delivery report
    mtDR,          // delivery report
    mtNRN,         // non-read notification
    mtRN           // read notification
};

//$--CIPMConvert------------------------------------------------------------
//
// 822-style header to IPM message stream converter class
//
// ---------------------------------------------------------------------------

class CIPMConvert
{
public:

    CIPMConvert();  // constructor
    ~CIPMConvert(); // destructor

    // Initializes conversion class instance
    HRESULT HrInitialize(
        IN LPCWSTR lpwszMsgClass,   // message class
        IN BOOL fTNEFEncode,        // TNEF encoding flag
        IN LPCTSTR lpszAddrType,    // address type
        IN LPADRBOOK lpAddrBook,    // address book pointer
        IN LPMESSAGE lpMessage,     // message pointer
        IN LPSTREAM lpStream);      // stream pointer

    // 822-style header to IPM message or report converter
    HRESULT HrConvert();

private:

    // methods

    // Set message type based on message class.
    HRESULT CIPMConvert::HrSetMessageType(
        IN LPTSTR lpszMessageClass);        // message class

    // Parses envelope and content header, saving data.
    HRESULT HrParseEnvelopeAndContent();

    // Parses IPM-specific tag and data lines
    HRESULT HrParseIPMSpecific();

    // Parses and creates binary file attachments
    HRESULT HrParseAttachments();

    // Parses [non]delivery report-specific tag and data lines
    HRESULT HrParseReportSpecific(
                ULONG * pnRecipients);  // # of [non]delivery recipients

    // sets delivery and non-delivery report properties
    HRESULT HrSetReportProps();

    // Parses read/non-read notification-specific tag and data lines
    HRESULT HrParseNotificationSpecific();

    // sets [non]read notification content properties.
    HRESULT HrSetNotifyCntProps();

    // Parses recipient tag and data lines & saves addresses
    HRESULT HrParseRecipients(
        IN LPCTSTR lpszTag,     // expected tag
        IN ULONG ulType,        // MAPI recipient type
        IN OUT LPADRLIST * lpAdrList, // target address list
        OUT ULONG * pnRecips);  // # recipients found of type

    // Creates content
    HRESULT HrCreateContent();

    // Sets envelope properties
    HRESULT HrSetEnvelopeProps();

    // Sets content properties
    HRESULT HrSetContentProps();

    // Parses and creates next attachment.
    HRESULT HrCreateNextAttachment();

    // Checks to see if conversion class has been initialized
    inline BOOL fCheckInit()        // RETURNS: BOOL
    {
        return(m_lpwszMsgClass && m_lpEnvelope && m_lpStream && m_lpAB && m_lpszAddrType);
    };

    // "Resets" the class after a conversion is completed.
    VOID Reset();                   // RETURNS: VOID

    // Frees the data string member array memory
    VOID HrFreeData();              // RETURNS: VOID

    // Parses and stores the external trace information.
    HRESULT CIPMConvert::HrParseExternalTraceData(
        OUT ULONG * pnEntries);         // number of trace entries found

    // Parses and stores the internal trace information.
    HRESULT CIPMConvert::HrParseInternalTraceData(
        OUT ULONG * pnEntries);         // number of trace entries found

    // Sets the envelope's PR_TRANSPORT_MESSAGE_HEADER property
    HRESULT HrSetTransHdr();

    // Decodes the TNEFed data into the envelope's content.
    HRESULT HrTnefToContent();

    // data members
    LPCWSTR     m_lpwszMsgClass;      // mesage class
    LPMESSAGE   m_lpEnvelope;         // message's envelope
    LPMESSAGE   m_lpContent;          // message's content (the original message)
    LPSTREAM    m_lpStream;           // stream currently writing to
    LPATTACH    m_lpAttach;           // envelope attachment pointer
    BOOL        m_fTNEFEncode;        // TNEF encoding flag
    LPCTSTR     m_lpszAddrType;       // address type
    LPADRBOOK   m_lpAB;               // address book

    MSG_TYPE    m_MsgType;            // message type (based on message class)

    // Array of non-recipient tag data for the envelope and its content.
    LPTSTR      m_rgszData[nDataEntries];   // array of data strings for envelope & content

    // Recipient address list for IPM & [N]RN envelopes
    LPADRLIST   m_lpRcptToList;     // "RCPT TO" addresses

    // Recipient address list for IPM & [N]RN contents
    LPADRLIST   m_lpContentRecips;         // "To", "CC" and "BCC" addresses

    // [non]recipient list for [non]delivery reports
    LPADRLIST   m_lpReportToList;   // [N]DR recipient and non-recipient list

    // Trace entry list
    LPTRACEINFO m_lpTraceInfo;      // Trace entry list

    // Internal Trace entry list
    PINTTRACEINFO m_lpIntTraceInfo; // Trace entry list

// Stream marker for end of headers
ULARGE_INTEGER m_sOffsetHeaders;

};

#endif


CONVREPT.CPP

// --convrept.cpp------------------------------------------------
//
// CIPMConvert source code for IPM reports.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
// --------------------------------------------------------------

#include "edk.h"
#include "tagnames.h"
#include "msgemit.h"
#include "convcls.h"
#include "convrept.chk"

//$--CIPMConvert::HrParseReportSpecific------------------------------------------
//
// DESCRIPTION: Parses [non]delivery report-specific tag and data lines
//              and constructs RCPT TO recipient list from them
//
// INPUT:   none
//
// OUTPUT:  pnRecipients    --  number of [non]recipients
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_OUTOFMEMORY if memory problems
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseReportSpecific(
            OUT ULONG * pnRecipients)   // # of [non]recipients
{
    HRESULT hr  =   NOERROR;
    LPTSTR          lpTag           =   NULL;       // tag string
    LPTSTR          lpData          =   NULL;       // data string
    ULONG           cbRead          =   0;          // # bytes read
    LPSPropValue    lpsRecipProps   =   NULL;       // property value array pointer
    ULONG           nRecipProps     =   0;          // number of property values in array
    ULONG           cbEid           =   0;          // number of bytes in entry identifier
    LPENTRYID       lpEid           =   NULL;       // entry identifier
    ULONG           cbName          =   0;          // # bytes in email address
    DWORD           cbSize          =   0;          // sizeof of property value array
    LARGE_INTEGER   sRetreat        =   {0};        // # of bytes to back up
LONGLONGcbRetreat=0;// # of bytes to back up

    // report RCPT TO recipient list property indices
    // These properties are common to both delivery reports
    // and non-delivery reports.
    const UINT      iEmailAddr      =   0;          // email address
    const UINT      iRecipType      =   1;          // recipient type
    const UINT      iName           =   2;          // display name
    const UINT      iEID            =   3;          // recipient entry identifier
    const UINT      iAddrType       =   4;          // address type
    const UINT      iRecipNumber    =   5;          // recipient number
    const UINT      iReportTime     =   6;          // time report was generated

    // NDR-specific non-recipient properties
    const UINT      iReportText     =   7;          // report text
    const UINT      iDiagCode       =   8;          // NDR diagnostic code
    const UINT      iReasonCode     =   9;          // NDR reason code

    // # of NDR non-recipient properties
    const ULONG     nNDRRecipProps  =   10;

    // DR-specific recipient properties
    const UINT      iDeliverTime    =   7;          // DR delivery time

    // # of DR recipient properties
    const ULONG     nDRRecipProps   =   8;

    DEBUGPRIVATE("CIPMConvert::HrParseReportSpecific()\n");

    // consistency checking
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");
    ASSERTERROR((m_MsgType == mtNDR || m_MsgType == mtDR),
                "Bad m_MsgType");
    ASSERTERROR(!FBadUnknown(m_lpAB), "Bad m_lpAB");
    ASSERTERROR(!IsBadStringPtr(m_lpszAddrType, INFINITE),
                "Bad lpszAddrType");

    // Check the input parameters.
    hr = CHK_CIPMConvert_HrParseReportSpecific(pnRecipients);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize the output parameter
    *pnRecipients = 0;

    // Initialize number of recipient properties
    if ( m_MsgType == mtNDR )   // non-delivery report
    {
        nRecipProps = nNDRRecipProps;
    }

    else    // delivery report
    {
        nRecipProps = nDRRecipProps;
    }

    // Handle recipient data per [non]recipient.
    while ( TRUE )
    {
        // Free MAPI buffers
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);
        MAPIFREEBUFFER(lpEid);

        MAPIFREEBUFFER(lpsRecipProps);

        // Parse the "Your message was not delivered to" tag and data
        // or the "Your message was successfully delivered to" tag and data
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag pointer
                    &lpData);       // data pointer

        // Check for end of file condition
        if ( hr == EDK_E_END_OF_FILE )
        {
            // Have reached the end of the file,  This
            // is not an error.
            hr = NOERROR;

            break;
        }

        if ( FAILED(hr) )
        {
            // general failure
            goto cleanup;
        }

        // Check the tag read against the tag expected
        if ( ( (m_MsgType == mtNDR) &&
               (lstrcmp(lpTag, lpszTagNotSentTo) != 0) ) ||
             ( (m_MsgType == mtDR) &&
                  (lstrcmp(lpTag, lpszTagSentTo) != 0) ) )
        {
            // doing TNEF decoding, we have read ahead too far.
            // Back up to where we were before.
            if ( m_fTNEFEncode == TRUE )
            {
                cbRetreat = ((LONGLONG) (-1)) * cbRead;

sRetreat.LowPart = LOWLONG(cbRetreat);
sRetreat.HighPart = HILONG(cbRetreat);

                hr = m_lpStream->Seek(
                            sRetreat,   // negative offset
                            STREAM_SEEK_CUR,    // from current position
                            NULL);      // don't care

                if ( FAILED(hr) )
                {
                    hr = HR_LOG(E_FAIL);

                    goto cleanup;
                }

                // This is not an error.
                hr = HR_LOG(NOERROR);

                break;  // we are done

            }   // end if doing TNEF decoding

            else    // not doing TNEF decoding
            {
                // general error
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

        }   // end if tag is not what we expect

        // Allocate memory for this recipient's properties.
        // (Must do dynamic allocation, as IMessage::ModifyRecipients
        // may reallocate property array buffer.).
        cbSize = sizeof(SPropValue) * nRecipProps;      // sizeof property value array
        hr = MAPIAllocateBuffer(
                    cbSize,   // # bytes to allocate
                    (VOID **) &lpsRecipProps);                    // pointer to array pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        // Initialize property value array
        ZeroMemory(
                    lpsRecipProps,      // buffer
                    cbSize);            // size of buffer

        // save data for this non-recipient in the RCPT TO recipient property array.

        // Allocate additional memory for the e-mail address.
        cbName = cbStrLen(lpData);
        hr = MAPIAllocateMore(
                    cbName,                                 // # additional bytes
                    lpsRecipProps,                          // original buffer
                    (VOID **) &(lpsRecipProps[iEmailAddr].Value.LPSZ));        // email address buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        // Set up properties for this recipient.

        // Set up the e-mail address
        lpsRecipProps[iEmailAddr].ulPropTag = PR_EMAIL_ADDRESS;   // e-mail address
        lstrcpy(
                lpsRecipProps[iEmailAddr].Value.LPSZ,
                lpData);

        // Set up the recipient type
        lpsRecipProps[iRecipType].ulPropTag = PR_RECIPIENT_TYPE;  // recipient type
        lpsRecipProps[iRecipType].Value.l = MAPI_TO;

        // Allocate more memory for display name
        hr = MAPIAllocateMore(
                    cbName,
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iName].Value.LPSZ));     // display name buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iName].ulPropTag = PR_DISPLAY_NAME;   // display name
        lstrcpy(
                lpsRecipProps[iName].Value.LPSZ,
                lpData);

        // Create an entry identifier for this "one off" address
        hr = m_lpAB->CreateOneOff(
                    lpData,         // PR_DISPLAY_NAME
                    (LPTSTR) m_lpszAddrType,   // PR_ADDRTYPE
                    lpData,         // PR_EMAIL_ADDRESS
                    0,              // flags
                    &cbEid,         // #bytes in entry ID
                    &lpEid);        // entry identifier

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Allocate more memory for entry identifier
        hr = MAPIAllocateMore(
                    cbEid,              // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iEID].Value.bin.lpb));         // entry identifier buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iEID].ulPropTag = PR_ENTRYID;        // entry identifier
        lpsRecipProps[iEID].Value.bin.cb = cbEid;
        CopyMemory(
               lpsRecipProps[iEID].Value.bin.lpb,   // destination buffer
               lpEid,                               // source buffer
               cbEid);                              // count bytes

        // Allocate more memory for address type
        hr = MAPIAllocateMore(
                    cbStrLen(m_lpszAddrType),   // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iAddrType].Value.LPSZ));    // address type buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        lpsRecipProps[iAddrType].ulPropTag = PR_ADDRTYPE;
        lstrcpy(
                lpsRecipProps[iAddrType].Value.LPSZ,
                m_lpszAddrType);

        lpsRecipProps[iRecipNumber].ulPropTag = PR_RECIPIENT_NUMBER;
        lpsRecipProps[iRecipNumber].Value.l = -1;

        // Allocate additional memory for the report time.
        hr = MAPIAllocateMore(
                    sizeof(FILETIME),  // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iReportTime].Value.ft));    // address type buffer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

            goto cleanup;
        }

        // Report time
        lpsRecipProps[iReportTime].ulPropTag = PR_REPORT_TIME;

        // Determine and fill in the report time
        hr = HrParseDateTimeString(
                m_rgszData[iDate],        // date time string
                &(lpsRecipProps[iReportTime].Value.ft));   // file time pointer

        if ( FAILED(hr) )
        {
            // Use a default value.
            GetSystemTimeAsFileTime( &(lpsRecipProps[iReportTime].Value.ft));
        }

        // Free tag and data string
        MAPIFREEBUFFER(lpTag);
        MAPIFREEBUFFER(lpData);

        // Handle NDR-specific properties
        if ( m_MsgType == mtNDR )
        {
            // Parse "for the following reason" tag and data line.
            hr = HrParseTagAndData(
                        m_lpStream,     // stream pointer
                        &cbRead,        // # bytes read
                        &lpTag,         // tag pointer
                        &lpData);       // data pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Check the tag read against the one expected
            if ( lstrcmp(lpTag, lpszTagReason) != 0 )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // Allocate more memory for the report text
            hr = MAPIAllocateMore(
                    cbStrLen(lpData),   // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iReportText].Value.LPSZ));    // address type buffer

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_OUTOFMEMORY);

                goto cleanup;
            }

            lpsRecipProps[iReportText].ulPropTag = PR_REPORT_TEXT;
            lstrcpy(
                lpsRecipProps[iReportText].Value.LPSZ,  // buffer
                lpData);                                // value

            // Free tag and data strings
            MAPIFREEBUFFER(lpTag);
            MAPIFREEBUFFER(lpData);

            // Parse the "Diagnostic code" tag and data line
            hr = HrParseTagAndData(
                        m_lpStream,     // stream pointer
                        &cbRead,        // # bytes read
                        &lpTag,         // tag pointer
                        &lpData);       // data pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Check the tag read against the one expected
            if ( lstrcmp(lpTag, lpszTagDiagCode) != 0 )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // save data in the RCPT TO recipient property array.
            lpsRecipProps[iDiagCode].ulPropTag = PR_NDR_DIAG_CODE;
            lpsRecipProps[iDiagCode].Value.l = atol(lpData);

            // Free tag and data strings
            MAPIFREEBUFFER(lpTag);
            MAPIFREEBUFFER(lpData);

            // Parse the "NDR code" tag and data line.
            hr = HrParseTagAndData(
                        m_lpStream,     // stream pointer
                        &cbRead,        // # bytes read
                        &lpTag,         // tag pointer
                        &lpData);       // data pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Check tag read against one expected
            if ( lstrcmp(lpTag, lpszTagNDRCode) != 0 )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // save data in the RCPT TO recipient property array.
            lpsRecipProps[iReasonCode].ulPropTag = PR_NDR_REASON_CODE;
            lpsRecipProps[iReasonCode].Value.l = atol(lpData);

            // Free tag and data string
            MAPIFREEBUFFER(lpTag);
            MAPIFREEBUFFER(lpData);

        }   // end if have an NDR

        // Handle DR specific properties
        else
        {
            // Read in the "at" tag and data
            hr = HrParseTagAndData(
                        m_lpStream,     // stream pointer
                        &cbRead,        // # bytes read
                        &lpTag,         // tag pointer
                        &lpData);       // data pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Check the tag read against the one expected
            if ( lstrcmp(lpTag, lpszTagAtTime) != 0 )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // Allocate additional memory for the delivery time.
            hr = MAPIAllocateMore(
                    sizeof(FILETIME),  // # bytes to add
                    lpsRecipProps,
                    (VOID **) &(lpsRecipProps[iDeliverTime].Value.ft));

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_OUTOFMEMORY);

                goto cleanup;
            }

            // Determine and set the delivery time.
            lpsRecipProps[iDeliverTime].ulPropTag = PR_DELIVER_TIME;

            hr = HrParseDateTimeString(
                lpData,        // date time string
                &(lpsRecipProps[iDeliverTime].Value.ft));   // file time pointer

            if ( FAILED(hr) )
            {
                // Use a default value.
                GetSystemTimeAsFileTime( &(lpsRecipProps[iDeliverTime].Value.ft));
            }

            // Free tag and data string
            MAPIFREEBUFFER(lpTag);
            MAPIFREEBUFFER(lpData);

        }   // end if have a DR

        // Add the property array to the RCPT TO recipient list.
        if ( m_lpReportToList == NULL )
        {
            // Create the recipient list
            hr = HrMAPICreateAddressList(
                nRecipProps,        // # of properties for address
                lpsRecipProps,      // recipient properties
                &m_lpReportToList);  // target address list pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if recipient list doesn't exist

        else
        {
            // Append to the recipient list
            hr = HrMAPIAppendAddressList(
                nRecipProps,        // # of properties for address
                lpsRecipProps,      // recipient properties
                &m_lpReportToList);  // target address list pointer

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if recipient list does exist

        // increment [non]recipient count
        (*pnRecipients)++;

        // Process next [non]recipient

    }   // end while

    // we are done.

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpTag);
    MAPIFREEBUFFER(lpData);
    MAPIFREEBUFFER(lpEid);

    // HrMAPICreateAddressList makes a duplicate of the
    // property value array passed into it.  Thus, free
    // property value array here.
    MAPIFREEBUFFER(lpsRecipProps);

    RETURN(hr);

}

//$--$CIPMConvert::HrSetReportProps-------------------------------------------
//
// DESCRIPTION: Sets delivery and non-delivery report properties.
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetReportProps()
{
    HRESULT             hr                  =   NOERROR;
    ULONG               ulImportance        =   0;      // importance value
    ULONG               ulPriority          =   0;      // priority value
    LPSPropProblemArray lpsProblems         =   NULL;   // problem array pointer
    ULONG               nReportProps        =   0;      // # of report properties
    ULONG               cbDestEid           =   0;      // # dest entry ID bytes
    LPENTRYID           lpDestEid           =   NULL;   // destination entry ID
    LPTSTR              lpszSubjectPrefix   =   NULL;   // subject prefix
    LPTSTR              lpszNormSubject     =   NULL;   // normalized subject
    SYSTEMTIME          st                  =   {0};    // current system time
    FILETIME            ftArrival           =   {0};    // arrival time
    FILETIME            ftSubmit            =   {0};    // submission time
    LPTSTR              lpDestAddr          =   NULL;   // destination address

    // property value array indices
    const UINT  iClassVal       =   0;
    const UINT  iImportanceVal  =   1;
    const UINT  iPriorityVal    =   2;
    const UINT  iSubjectVal     =   3;
    const UINT  iDestNameVal    =   4;
    const UINT  iDestEidVal     =   5;
    const UINT  iSubjectXIDVal  =   6;
    const UINT  iSubPrefixVal   =   7;
    const UINT  iNormSubjectVal =   8;
    const UINT  iDeleteVal      =   9;
    const UINT  iArrivalTimeVal =   10;
    const UINT  iOrigSubmitVal  =   11;
    const UINT  iReportReqVal   =   12;
const UINTiContentRetVal=13;

    // minimum number of report properties
    const ULONG nMinRptProps    =   14;

    // maximum # of report properties.
    // Note: There may not be any trace information or any X message
    // identifier information.  Thus, the maximum number of
    // properties is three more than the minimum number of properties.
    const ULONG nMaxRptProps    =   nMinRptProps + 3;

    // Property value array
    SPropValue  rgReportProps[nMaxRptProps] =   {0};

    // index of email address in the envelope recipient address
    // list property value array.
    const UINT      iEmailAddr  =   0;          // email address

    DEBUGPRIVATE("CIPMConvert::HrSetReportProps()\n");

    // consistency checking
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");
    ASSERTERROR((m_MsgType == mtNDR || m_MsgType == mtDR),
                "Bad m_MsgType");
    ASSERTERROR((!IsBadReadPtr(m_lpRcptToList, sizeof(ADRLIST))),
                "Bad m_lpRcptToList");

    // Initialize number of report properties to be the minimum
    // number of report properties.
    nReportProps = nMinRptProps;

    // Fill in the properties to be set.
    rgReportProps[iClassVal].ulPropTag = PR_MESSAGE_CLASS;
    rgReportProps[iClassVal].Value.LPSZ = m_rgszData[iClass];

    // Retrieve importance value.
    hr = HrParseImportanceString(
                m_rgszData[iImportance],    // importance string
                &ulImportance);             // importance value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgReportProps[iImportanceVal].ulPropTag = PR_IMPORTANCE;
    rgReportProps[iImportanceVal].Value.l = ulImportance;

    // Retrieve priority value
    hr = HrParsePriorityString(
                m_rgszData[iPriority],      // priority string
                &ulPriority);               // priority value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgReportProps[iPriorityVal].ulPropTag = PR_PRIORITY;
    rgReportProps[iPriorityVal].Value.l = ulPriority;

    rgReportProps[iSubjectVal].ulPropTag = PR_SUBJECT;
    rgReportProps[iSubjectVal].Value.LPSZ = m_rgszData[iSubject];

    // Retrieve the report destination name.
    // The report destination name is actually in the
    // "RCPT TO" line, which was stored in the m_lpRcptToList.
    lpDestAddr = m_lpRcptToList->aEntries[0].rgPropVals[iEmailAddr].Value.LPSZ;
    rgReportProps[iDestNameVal].ulPropTag = PR_REPORT_DESTINATION_NAME;
    rgReportProps[iDestNameVal].Value.LPSZ = lpDestAddr;

    // Create an entry identifier for this "one off" address
    hr = m_lpAB->CreateOneOff(
                    lpDestAddr,     // name
                    (LPTSTR) m_lpszAddrType,    // address type
                    lpDestAddr,     // address
                    0,              // flags
                    &cbDestEid,     // #bytes in entry ID
                    &lpDestEid);    // entry identifier

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgReportProps[iDestEidVal].ulPropTag = PR_REPORT_DESTINATION_ENTRYID;
    rgReportProps[iDestEidVal].Value.bin.cb = cbDestEid;
    rgReportProps[iDestEidVal].Value.bin.lpb = (BYTE *) lpDestEid;

    rgReportProps[iSubjectXIDVal].ulPropTag = PR_MTS_SUBJECT_ID;
    rgReportProps[iSubjectXIDVal].Value.bin.cb =
        cbStrLen(m_rgszData[iSubjectXID]);
    rgReportProps[iSubjectXIDVal].Value.bin.lpb =
        (BYTE *) m_rgszData[iSubjectXID];

    // Contruct subject prefix & normalized subject
    // (Prefix is everything up to and including first colon.
    // Normalized subject is everything else, except first space).
    hr = HrEDKParseSubjectPrefix(
                m_rgszData[iSubject],   // subject text
                &lpszSubjectPrefix,     // prefix
                &lpszNormSubject);      // normalized subject

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgReportProps[iSubPrefixVal].ulPropTag = PR_SUBJECT_PREFIX;
    rgReportProps[iSubPrefixVal].Value.LPSZ = lpszSubjectPrefix;

    rgReportProps[iNormSubjectVal].ulPropTag = PR_NORMALIZED_SUBJECT;
    rgReportProps[iNormSubjectVal].Value.LPSZ = lpszNormSubject;

    rgReportProps[iDeleteVal].ulPropTag = PR_DELETE_AFTER_SUBMIT;
    rgReportProps[iDeleteVal].Value.b = TRUE;

    // Determine the arrival time.  This is the current time!
    GetSystemTimeAsFileTime( &ftArrival);

    rgReportProps[iArrivalTimeVal].ulPropTag = PR_ARRIVAL_TIME;
    rgReportProps[iArrivalTimeVal].Value.ft = ftArrival;

    // Determine the original submission time.
    hr = HrParseDateTimeString(
                m_rgszData[iDate],      // date time string
                &ftSubmit);             // file time structure

    if ( FAILED(hr) )
    {
        // Use a default value.
        GetSystemTimeAsFileTime( &ftSubmit);
    }

    rgReportProps[iOrigSubmitVal].ulPropTag = PR_CLIENT_SUBMIT_TIME;
    rgReportProps[iOrigSubmitVal].Value.ft = ftSubmit;

    // Set the appropriate report requested property
    if ( m_MsgType == mtNDR )   // non-delivery report
    {
        rgReportProps[iReportReqVal].ulPropTag =
            PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED;
    }

    else    // delivery report
    {
        rgReportProps[iReportReqVal].ulPropTag =
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
    }

    rgReportProps[iReportReqVal].Value.b = TRUE;

// Set the content return requested flag.  In general, we should
// try to propagate the original message content with all delivery
// and non-delivery reports.
rgReportProps[iContentRetVal].ulPropTag = PR_CONTENT_RETURN_REQUESTED;
// Evaluates to TRUE if TNEFed content data is available and FALSE otherwise.
rgReportProps[iContentRetVal].Value.b = m_fTNEFEncode;

    // Only fill in PR_MESSAGE_SUBMISSION_ID if it is not null.
    if ( *(m_rgszData[iXID]) != 0 )
    {
        // Fill in X-Message-ID
        rgReportProps[nReportProps].ulPropTag = PR_MESSAGE_SUBMISSION_ID;
        rgReportProps[nReportProps].Value.bin.cb =
            cbStrLen(m_rgszData[iXID]);
        rgReportProps[nReportProps].Value.bin.lpb = (BYTE *) m_rgszData[iXID];

        // increment number of properties
        nReportProps++;
    }

    // Set the trace information, if any.
    if ( m_lpTraceInfo != NULL )
    {
        rgReportProps[nReportProps].ulPropTag = PR_TRACE_INFO;
        rgReportProps[nReportProps].Value.bin.cb = CbTRACEINFO(m_lpTraceInfo);
        rgReportProps[nReportProps].Value.bin.lpb = (BYTE *) m_lpTraceInfo;

        // increment the property count
        nReportProps++;

    }   // end if trace information

    // Set the internal trace information, if any.
    if ( m_lpIntTraceInfo != NULL )
    {
        rgReportProps[nReportProps].ulPropTag = PR_INTERNAL_TRACE_INFO;
        rgReportProps[nReportProps].Value.bin.cb = CbINTTRACEINFO(m_lpIntTraceInfo);
        rgReportProps[nReportProps].Value.bin.lpb = (BYTE *) m_lpIntTraceInfo;

        // increment the property count
        nReportProps++;

    }   // end if internal trace information

    // Set the PR_TRANSPORT_MESSAGE_HEADER property.
    hr = HrSetTransHdr();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Set the report properties.
    hr = m_lpEnvelope->SetProps(
                nReportProps,       // # of properties
                rgReportProps,      // property value array
                &lpsProblems);      // property problem array poniter

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        lpsProblems = NULL;     // so can safely release

        goto cleanup;
    }

    if ( lpsProblems != NULL )
    {
        // some properties weren't accepted
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Set properties on report's recipient table.
    hr = m_lpEnvelope->ModifyRecipients(
                0,                    // flags
                m_lpReportToList);    // address list

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpDestEid);
    MAPIFREEBUFFER(lpszSubjectPrefix);
    MAPIFREEBUFFER(lpszNormSubject);
    MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}

//$--CIPMConvert::HrParseNotificationSpecific------------------------------------------
//
// DESCRIPTION: Parses read/non read notification-specific tag and data lines.

//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrParseNotificationSpecific()
{
    HRESULT hr  =   NOERROR;
    LPTSTR          lpTag           =   NULL;       // tag string
    ULONG           cbRead          =   0;          // # bytes read

    DEBUGPRIVATE("CIPMConvert::HrParseNotificaionSpecific()\n");

    // consistency checking
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");
    ASSERTERROR((m_MsgType == mtNRN || m_MsgType == mtRN),
                "Bad m_MsgType");

    // Parse the "Your message was [not] read by" tag and data.
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // # bytes read
                &lpTag,         // tag pointer
                &(m_rgszData[iReadBy]));       // data pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check the tag read against the one expected.
    if ( (m_MsgType == mtNRN) &&
         (lstrcmp(lpTag, lpszTagNotReadBy) != 0) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    else if ( (m_MsgType == mtRN) &&
              (lstrcmp(lpTag, lpszTagReadBy) != 0) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free tag string
    MAPIFREEBUFFER(lpTag);

    // Parse the "at" tag and data line.
    hr = HrParseTagAndData(
            m_lpStream,     // stream pointer
            &cbRead,        // #bytes read
            &lpTag,         // tag string
            &(m_rgszData[iNotifyTime]));    // data buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Check tag read against tag expected.
    if ( lstrcmp(lpTag, lpszTagAtTime) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Free tag string
    MAPIFREEBUFFER(lpTag);

    // Handle non-read receipt-specific information.
    if ( m_MsgType == mtNRN )
    {
        // Parse "Discard Code" tag and data
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag string
                    &(m_rgszData[iDiscardCode]));   // data buffer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag read against one expected.
        if ( lstrcmp(lpTag, lpszTagDiscCode) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // free tag string
        MAPIFREEBUFFER(lpTag);

        // Parse "Non-receipt code" tag and data
        hr = HrParseTagAndData(
                    m_lpStream,     // stream pointer
                    &cbRead,        // # bytes read
                    &lpTag,         // tag string
                    &(m_rgszData[iNonRcptCode]));   // data buffer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // check tag read against tag expected
        if ( lstrcmp(lpTag, lpszTagNRNCode) != 0 )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Free tag string
        MAPIFREEBUFFER(lpTag);

    }   // end if NRN

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpTag);

    RETURN(hr);

}

//$--CIPMConvert::HrSetNotifyCntProps-----------------------------------------
//
// DESCRIPTION: Set content properties for read and non-read notifications
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrSetNotifyCntProps()
{
    HRESULT             hr                  =   NOERROR;
    LPSPropProblemArray lpsProblems         =   NULL;   // property problems
    FILETIME            ftSubmit            =   {0};    // submission time struct
    FILETIME            ftReport            =   {0};    // report time struct
    ULONG               ulImportance        =   0;      // importance value
    ULONG               ulPriority          =   0;      // priority value
    ULONG               nNotifyProps        =   0;      // # of properties
    ULONG               cbEid               =   0;      // # bytes in entry ID
    LPENTRYID           lpEid               =   NULL;   // entry identifier
    LPTSTR              lpszNormSubject     =   NULL;   // normalized subject
    LPTSTR              lpszSubjectPrefix   =   NULL;   // subject prefix
    LPTSTR              lpszFromAddr        =   NULL;   // from address
    ULONG               cbParentKey         =   0;      // # bytes in binary parent key

    // byte array for the binary parent key (PR_PARENT_KEY) property
    BYTE                rgbParentKey[ulMaxOutStringLen + 1] =   {0};

    // indices into the notification content's property value array
    const UINT  iClassVal       =   0;
    const UINT  iFromAddrVal    =   1;
    const UINT  iFromNameVal    =   2;
    const UINT  iFromEidVal     =   3;
    const UINT  iFromAddrTypeVal=   4;
    const UINT  iSubmitTimeVal  =   5;
    const UINT  iImportanceVal  =   6;
    const UINT  iPriorityVal    =   7;
    const UINT  iSubjectVal     =   8;
    const UINT  iReportTimeVal  =   9;
    const UINT  iSubPrefixVal   =   10;
    const UINT  iNormSubjectVal =   11;
    const UINT  iOrigSubjectVal =   12;
    const UINT  iOrigSubmitVal  =   13;
    const UINT  iOrigDestNamVal =   14;
    const UINT  iOrigDestEidVal =   15;
    const UINT  iOrigDestAddrVal=   16;
    const UINT  iOrigDestATypVal=   17;
    const UINT  iSubjectXIDVal  =   18;
    const UINT  iFromAddrVal2   =   19;
    const UINT  iFromNameVal2   =   20;
    const UINT  iFromEidVal2    =   21;
    const UINT  iFromAddrTypeVal2=  22;

    // Properties unique to non-read notifications
    const UINT  iDiscardCodeVal =   23;
    const UINT  iNonRcptCodeVal =   24;

    // The X-Message-Subject-ID property is not always
    // present for read and non-read notifications.
    // It's index will have to be computed.

    // number of read notification content properties
    const ULONG nRNNotifyProps  =   23;

    // number of non-read notification content properties
    const ULONG nNRNNotifyProps =   25;

    // Property value array
    SPropValue  rgNotifyProps[nNRNNotifyProps]  =   {0};

    DEBUGPRIVATE("CIPMConvert::HrSetNotifyCntProps()\n");

    // consistency checking
    ASSERTERROR((m_MsgType == mtNRN || m_MsgType == mtRN),
                "Bad m_MsgType");

    // If we are doing TNEF decoding, let TNEF
    // extract the content properties.
    if ( m_fTNEFEncode == TRUE )
    {
        goto cleanup;
    }

    // Initialize number of properties
    if ( m_MsgType == mtNRN )
    {
        // non-read notification
        nNotifyProps = nNRNNotifyProps;
    }

    else
    {
        // read notification
        nNotifyProps = nRNNotifyProps;
    }

    // Set up notification content properties
    rgNotifyProps[iClassVal].ulPropTag = PR_MESSAGE_CLASS;
    rgNotifyProps[iClassVal].Value.LPSZ = m_rgszData[iClass];

// PR_SENDER_* and PR_SENT_REPRESENTING_* properties
    lpszFromAddr = m_rgszData[iFrom];
    rgNotifyProps[iFromAddrVal].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
    rgNotifyProps[iFromAddrVal].Value.LPSZ = lpszFromAddr;

    rgNotifyProps[iFromNameVal].ulPropTag = PR_SENDER_NAME;
    rgNotifyProps[iFromNameVal].Value.LPSZ = lpszFromAddr;

    // Create an entry identifier for this "one off" address
    hr = m_lpAB->CreateOneOff(
                    lpszFromAddr,   // name
                    (LPTSTR) m_lpszAddrType,    // address type
                    lpszFromAddr,   // address
                    0,              // flags
                    &cbEid,         // #bytes in entry ID
                    &lpEid);        // entry identifier

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgNotifyProps[iFromEidVal].ulPropTag = PR_SENDER_ENTRYID;
    rgNotifyProps[iFromEidVal].Value.bin.cb = cbEid;
    rgNotifyProps[iFromEidVal].Value.bin.lpb = (BYTE *) lpEid;

    rgNotifyProps[iFromAddrTypeVal].ulPropTag = PR_SENDER_ADDRTYPE;
    rgNotifyProps[iFromAddrTypeVal].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    rgNotifyProps[iFromAddrVal2].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
    rgNotifyProps[iFromAddrVal2].Value.LPSZ = lpszFromAddr;

    rgNotifyProps[iFromNameVal2].ulPropTag = PR_SENT_REPRESENTING_NAME;
    rgNotifyProps[iFromNameVal2].Value.LPSZ = lpszFromAddr;

    rgNotifyProps[iFromEidVal2].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
    rgNotifyProps[iFromEidVal2].Value.bin.cb = cbEid;
    rgNotifyProps[iFromEidVal2].Value.bin.lpb = (BYTE *) lpEid;

    rgNotifyProps[iFromAddrTypeVal2].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
    rgNotifyProps[iFromAddrTypeVal2].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    // Parse client submit time.
    hr = HrParseDateTimeString(
                m_rgszData[iDate],      // date time string
                &ftSubmit);             // file time structure

    if ( FAILED(hr) )
    {
        // Use a default value.
        GetSystemTimeAsFileTime( &ftSubmit);
    }

    rgNotifyProps[iSubmitTimeVal].ulPropTag =
        PR_CLIENT_SUBMIT_TIME;
    rgNotifyProps[iSubmitTimeVal].Value.ft = ftSubmit;

    // Determine importance value.
    hr = HrParseImportanceString(
                m_rgszData[iImportance],    // importance string
                &ulImportance);             // importance value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgNotifyProps[iImportanceVal].ulPropTag = PR_IMPORTANCE;
    rgNotifyProps[iImportanceVal].Value.l = ulImportance;

    // Determine priority value.
    hr = HrParsePriorityString(
                m_rgszData[iPriority],  // priority string
                &ulPriority);           // priority value

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgNotifyProps[iPriorityVal].ulPropTag = PR_PRIORITY;
    rgNotifyProps[iPriorityVal].Value.l = ulPriority;

    rgNotifyProps[iSubjectVal].ulPropTag = PR_SUBJECT;
    rgNotifyProps[iSubjectVal].Value.LPSZ = m_rgszData[iSubject];

    // Parse report time.
    hr = HrParseDateTimeString(
                m_rgszData[iNotifyTime],    // date time string
                &ftReport);                 // file time structure

    if ( FAILED(hr) )
    {
        // Use a default value.
        GetSystemTimeAsFileTime( &ftReport);
    }

    rgNotifyProps[iReportTimeVal].ulPropTag = PR_REPORT_TIME;
    rgNotifyProps[iReportTimeVal].Value.ft = ftReport;

    // Construct subject prefix
    // The subject prefix is everything up until the first colon
    // of the subject (and the next space, if any).  The normalized
    // subject is everything else.
    hr = HrEDKParseSubjectPrefix(
                m_rgszData[iSubject],   // subject text
                &lpszSubjectPrefix,     // prefix
                &lpszNormSubject);      // normalized subject

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    rgNotifyProps[iSubPrefixVal].ulPropTag = PR_SUBJECT_PREFIX;
    rgNotifyProps[iSubPrefixVal].Value.LPSZ = lpszSubjectPrefix;

    rgNotifyProps[iNormSubjectVal].ulPropTag = PR_NORMALIZED_SUBJECT;
    rgNotifyProps[iNormSubjectVal].Value.LPSZ = lpszNormSubject;

    // Must set original subject after have computed normalized
    // subject.
    rgNotifyProps[iOrigSubjectVal].ulPropTag = PR_ORIGINAL_SUBJECT;
    rgNotifyProps[iOrigSubjectVal].Value.LPSZ = lpszNormSubject;

    rgNotifyProps[iOrigSubmitVal].ulPropTag = PR_ORIGINAL_SUBMIT_TIME;
    rgNotifyProps[iOrigSubmitVal].Value.ft = ftSubmit;

    // Note:  PR_ORIGINALLY_INTENDED_RECIPIENT_NAME is an
    // entry identifier, NOT a string!
    // Must be filled in after original destination EID is determined
    rgNotifyProps[iOrigDestNamVal].ulPropTag =
            PR_ORIGINALLY_INTENDED_RECIPIENT_NAME;
    rgNotifyProps[iOrigDestNamVal].Value.bin.cb = cbEid;
    rgNotifyProps[iOrigDestNamVal].Value.bin.lpb = (BYTE *) lpEid;

    rgNotifyProps[iOrigDestEidVal].ulPropTag =
            PR_ORIGINALLY_INTENDED_RECIP_ENTRYID;
    rgNotifyProps[iOrigDestEidVal].Value.bin.cb = cbEid;
    rgNotifyProps[iOrigDestEidVal].Value.bin.lpb = (BYTE *) lpEid;

    rgNotifyProps[iOrigDestAddrVal].ulPropTag =
            PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS;
    rgNotifyProps[iOrigDestAddrVal].Value.LPSZ = lpszFromAddr;

    rgNotifyProps[iOrigDestATypVal].ulPropTag =
            PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE;
    rgNotifyProps[iOrigDestATypVal].Value.LPSZ = (LPTSTR) m_lpszAddrType;

    // Handle non-read notification specific properties.
    if ( m_MsgType == mtNRN )
    {
        rgNotifyProps[iDiscardCodeVal].ulPropTag = PR_DISCARD_REASON;
        rgNotifyProps[iDiscardCodeVal].Value.l = atol(m_rgszData[iDiscardCode]);

        rgNotifyProps[iNonRcptCodeVal].ulPropTag = PR_NON_RECEIPT_REASON;
        rgNotifyProps[iNonRcptCodeVal].Value.l = atol(m_rgszData[iNonRcptCode]);
    }

    // Read notifications use the PR_PARENT_KEY property to
    // hold the IPM subject identifier.
    // Determine the PR_PARENT_KEY.
    ASSERTERROR(lstrlen(m_rgszData[iSubjectXID]) <= ulMaxOutStringLen,
                "Bad m_rgszData[iSubjectXID]");

    // compute # bytes of binary data expected
    cbParentKey = lstrlen(m_rgszData[iSubjectXID])/2;

    hr = ScBinFromHexBounded(
            m_rgszData[iSubjectXID],    // ASCII representation of binary key
            rgbParentKey,               // Binary parent key pointer
            cbParentKey);               // # bytes of binary data

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgNotifyProps[iSubjectXIDVal].ulPropTag = PR_PARENT_KEY;
    rgNotifyProps[iSubjectXIDVal].Value.bin.cb = cbParentKey;   // # bytes
    rgNotifyProps[iSubjectXIDVal].Value.bin.lpb = rgbParentKey; // binary value

    ASSERTERROR(!FBadUnknown(m_lpContent), "Bad m_lpContent");

    // Set notification content properties.
    hr = m_lpContent->SetProps(
                nNotifyProps,       // # of properties
                rgNotifyProps,      // array of property values
                &lpsProblems);      // problem array pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        lpsProblems = NULL;

        goto cleanup;
    }

    // Check for property problems
    if ( lpsProblems )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Modify content's recipient table
    hr = m_lpContent->ModifyRecipients(
                MODRECIP_ADD,       // flags
                m_lpContentRecips); // address list

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // save the changes to the message content
    hr = m_lpContent->SaveChanges(
                0);                     // we are done with the message content

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // save changes to the envelope attachment
    hr = m_lpAttach->SaveChanges(
                0);                     // we are done with the envelope attachment

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpEid);
    MAPIFREEBUFFER(lpszSubjectPrefix);
    MAPIFREEBUFFER(lpszNormSubject);
    MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}


CONVTNEF.CPP

// --convtnef.cpp--------------------------------------------------------------
//
// Code for messages whose content is to be TNEFed.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All Rights Reserved.
// All rights reserved.
//
// ----------------------------------------------------------------------------

#include "edk.h"
#include "convcls.h"
#include "tagnames.h"
#include "msgemit.h"
#include "ipmcdefs.h"

//$--CIPMConvert::HrTnefToContent---------------------------------------------------------------
//
// DESCRIPTION: TNEF decodes TNEFed data from the input stream.
// to the envelope content.
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrTnefToContent()
{
    HRESULT             hr              =   NOERROR;
    LPTSTR              lpszTag         =   NULL;   // tag string pointer
    LPTSTR              lpszData        =   NULL;   // data string pointer
    LPITNEF             lpTnef          =   NULL;   // TNEF interface pointer
    LPSTnefProblemArray lpsProblems     =   NULL;   // property problem array
    ULONG               cbRead          =   0;      // # bytes read
    SPropTagArray       sNoPropTags     =   {0};    // property tag array

    DEBUGPRIVATE("CIPMConvert::HrTnefToContent()\n");

    // consistency checks
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");
    ASSERTERROR(!FBadUnknown(m_lpAB), "Bad m_lpAB");
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");

    // TNEF decode the saved TNEFed data.
    // This data contains the properties for the
    // envelope's content and all of its attachments.

    // First, read in the "MAPIMAIL.DAT" TNEF tag and data
    // from the input stream. (The data will be null).
    hr = HrParseTagAndData(
                m_lpStream,     // stream pointer
                &cbRead,        // # bytes read
                &lpszTag,       // tag pointer
                &lpszData);     // data pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Check the tag read
    if ( lstrcmp(lpszTag, lpszTagTnefHdr) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Next, open a TNEF interface on the input stream
    // which we shall decode to the envelope content.
  hr = OpenTnefStreamEx(
            NULL,
        m_lpStream,         // input stream pointer
        (LPTSTR) szTnefFileName,    // file name associated with TNEFed data
    TNEF_DECODE,        // decode the message properties
    m_lpEnvelope,       // destination message pointer
    wTnefKey,           // "unique" TNEF key
    m_lpAB,             // address book pointer
    &lpTnef);           // TNEF interface object pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(lpTnef), "Bad lpTnef");

    // Decode all properties to the envelope content
    hr = lpTnef->ExtractProps(
            TNEF_PROP_EXCLUDE,      // exclude
            (LPSPropTagArray) &sNoPropTags, // no properties to exclude
            &lpsProblems);          // property problem array pointer

    if ( FAILED(hr) )       // Don't care about MAPI_W_ERRORS_RETURNED in this case
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done!

cleanup:

    // Release all OLE and MAPI objects
    ULRELEASE(lpTnef);

    // Free MAPI buffers
    MAPIFREEBUFFER(lpsProblems);
    MAPIFREEBUFFER(lpszTag);
    MAPIFREEBUFFER(lpszData);

    RETURN(hr);

}


IPM_IN.CPP

// --ipm_in.cpp--------------------------------------------------------------
//
// API entry points for the ipm_in dynamic link library.
// Converts an 822-style message header to a MAPI message.
// This is the "Inbound IPM Converter".
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "ipmconv.h"
#include "convcls.h"
#include "ipm_in.chk"

// GLOBAL VARIABLES

// Thread local storage slot index.
// This storage will be used to store the conversion class instance pointer
// for each thread.
static DWORD   dwTlsIndex   =   0xFFFFFFFF;

// external function declarations

// The DLL entry point extern "C" function declaration and
// "C" run-time library initialization extern "C" function declarations.
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE hInstDll, DWORD fdwReason,
                                     LPVOID lpvReserved);
extern "C" BOOL WINAPI _CRT_INIT(HINSTANCE hInstDll, DWORD fdwReason,
                                 LPVOID lpvReserved);

//$--HrCnvQueryCapability---------------------------------------------------------
//
// DESCRIPTION: Determines whether or not a particular conversion can be
//              done.  The only conversion allowed is from
//              an 822 style header to a MAPI message of classes
//              containing ENVELOPE.IPM or
//              REPORT.IPM
//
// INPUT:       pszOptions  --  (not used)
//              pszClass    --  class of message to convert
//                          (just needs to be a valid class)
//              pContent    --  822-style header stream pointer
//              pEnv    --  conversion environment pointer
//
// OUTPUT:      pfAmCandidate   --  TRUE if can do conversion, FALSE otherwise
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//
// ---------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
        IN LPCWSTR pszOptions,  // conversion options
        IN LPCWSTR pszClass,    // message class to convert
        IN PVOID pContent,      // 822-style header stream pointer (candidate)
        IN PEDKCNVENV pEnv,     // conversion environment pointer
        OUT BOOL * pfAmCandidate)   // TRUE if can convert, FALSE otherwise
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("HrCnvQueryCapability()\n");

    // check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass,
                                  pContent, pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pfAmCandidate = FALSE;     // initialize output variable

    // Check target message class
    // We can only convert classes which contain ENVELOPE.IPM or
    // REPORT.IPM.
    if ( (wcsstr(pszClass, IPMENVELOPECLASS) == NULL) &&
         (wcsstr(pszClass, IPMREPORTCLASS) == NULL) )
    {
        goto cleanup;
    }

    // We assume that we can parse the input stream.
    *pfAmCandidate = TRUE;

cleanup:

    RETURN(hr);

}

//$--HrAsciitoIPMFormat--------------------------------------------------------
//
// DESCRIPTION: Converts an "822-style" stream input to a MAPI IPM message.
//
// NOTES:   Only one conversion may be performed at
//          a time (per thread). In order to do concurrent conversions,
//          create multiple threads.
//
// INPUT:       lpStream    --  Stream to read from
//              lpwszClass  --  message class
//              fTNEFEncode --  TNEF encoding flag
//              lpAddrType  --  address type
//              lpAddrBook  --  Address book pointer
//              lpEnvelope  --  message to write to
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_FAIL if API function call failure,
//                              E_NOTIMPL if requesting a feature not implemented
//                              or supported,
//                              EDK_E_ALREADY_EXISTS if a conversion is already
//                              in progress,
//
// ---------------------------------------------------------------------------

static HRESULT HrAsciitoIPMFormat(
                               IN LPCWSTR lpwszClass,     // message class
                               IN BOOL fTNEFEncode,       // TNEF encoding flag
                               IN LPCTSTR lpAddrType,     // address type
                               IN LPADRBOOK lpAddrBook,   // address book pointer
                               IN LPSTREAM lpStream,      // pointer to stream
                               IN LPMESSAGE lpMessage)    // pointer to MAPI message envelope
{
    HRESULT         hr              =   NOERROR;
    CIPMConvert *   lpCIPMConvert   =   NULL;   // thread-safe conversion class instance pointer
    BOOL            fInitialized    =   FALSE;  // TRUE if conversion instance already in use

    DEBUGPRIVATE("HrAsciitoIPMFormat()\n");

    // Check input parameters.
    hr = CHK_HrAsciitoIPMFormat(lpwszClass, fTNEFEncode,
                                lpAddrType, lpAddrBook, lpStream,
                                lpMessage);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ASSERTERROR(dwTlsIndex != 0xFFFFFFFF, "Bad thread storage index");

    // Retrieve the conversion class instance pointer for this class.
    lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

    if ( !lpCIPMConvert )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Initialize conversion instance
    hr = lpCIPMConvert->HrInitialize(lpwszClass, fTNEFEncode,
                                     lpAddrType, lpAddrBook, lpMessage,
                                     lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Go ahead and do conversion.
    hr = lpCIPMConvert->HrConvert();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Note: The thread-safe conversion class instance is de-initialized
    // at thread or process termination.

cleanup:

    RETURN(hr);

}

//$--HrCnvConvert---------------------------------------------------------
//
// DESCRIPTION: Converts an 822-style header to a MAPI
//              ENVELOP.IPM or REPORT.IPM message.
//              This function is designed to be called on a per-thread
//              basis.  Each thread can handle one conversion at a time.
//
// INPUT:       pszOptions  --  (not used)
//              pszClass    --  class of message to convert
//                              (just needs to be a valid class)
//              pContent    --  Stream to read from
//              pContentOut --  MAPI message envelope pointer
//
// OUTPUT:      pcr --  pointer to conversion result enumeration
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if API call failure or syntax error.
//                          E_OUTOFMEMORY if memory problems
//                          E_NOTIMPL if conversion not supported,
//                          EDK_E_ALREADY_EXISTS if busy,
//                          EDK_E_END_OF_FILE if unexpected end-of-file encountered.
//
// ---------------------------------------------------------------------------
static HRESULT HrCnvConvert(
        IN LPCWSTR pszOptions,      // conversion options
        IN LPCWSTR pszClass,        // message class to convert
        IN PVOID pContent,          // stream to convert (candidate)
        IN PVOID pContentOut,       // MAPI message envelope pointer
        IN PEDKCNVENV pEnv,         // conversion environment pointer
        OUT EDKCNVRES * pcr)        // result of conversion
{
    HRESULT         hr          =   NOERROR;
    CONV_OPTIONS *  pConvOpts   =   NULL;   // conversion options structure

    DEBUGPRIVATE("HrCnvConvert()\n");

    // check input parameters
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize output parameter
    *pcr = GCR_CONVERSION_FAILED;

    // We can only convert classes which contain ENVELOPE.IPM or
    // REPORT.IPM
    if ( (wcsstr(pszClass, IPMENVELOPECLASS) == NULL) &&
         (wcsstr(pszClass, IPMREPORTCLASS) == NULL) )
    {
        *pcr = GCR_CANNOT_CONVERT;

        goto cleanup;
    }

    // The pGatewayDefined field of the conversion envrironment
    // structure pointer points to a CONV_OPTIONS structure.
    // The CONV_OPTIONS structure contains the TNEF encoding
    // flag and the address book pointer.
    // Note, the CONV_OPTIONS structure was checked for validity
    // in the CHK function.
    pConvOpts = (CONV_OPTIONS *) (pEnv->pGatewayDefined);

    // Let our helper function do the work!
    hr = HrAsciitoIPMFormat(
              pszClass,                     // message class
              pConvOpts->fTnefEncode,       // TNEF encoding flag
              pConvOpts->lpszAddressType,   // address type
              pEnv->lpAB,                   // addres book
              (LPSTREAM) pContent,          // stream to read from
              (LPMESSAGE) pContentOut);     // MAPI message to write to

    switch (hr)
    {
        case E_NOTIMPL:
            // don't support this conversion type
            *pcr = GCR_CANNOT_CONVERT;

            goto cleanup;

        default:
            if ( FAILED(hr) )
            {
                // conversion failed.
                *pcr = GCR_CONVERSION_FAILED;

                goto cleanup;
            }
    }   // end switch

    // if we get to here, then the conversion succeeded.
    *pcr = GCR_OK;

cleanup:

    RETURN(hr);

}

// structure containing pointers to exported functions
static CONVDLLVECT sExportedFuncs =
{
    nDesiredConvDllVersion,  // version number 1
    HrCnvQueryCapability,
    HrCnvConvert
};

//$--HrGetExportVector--------------------------------------------------------
//
// DESCRIPTION: Gets pointer to exported functions structure.
//
// INPUT:       dwVersion   --  version requested
//
// OUTPUT:      ppExportVectors --  pointer to export vector structure pointer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//
// ---------------------------------------------------------------------------

HRESULT HrGetExportVector(     // RETURNS: HRESULT
        IN DWORD dwVersion,         // version requested
        OUT PCONVDLLVECT * ppExportVectors)  // pointer to exported functions structure
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrGetExportVector()\n");

    // check input parameters
    hr = CHK_HrGetExportVector(dwVersion, ppExportVectors);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // return pointer to exported vectors structure
    *ppExportVectors = &sExportedFuncs;

    RETURN(hr);

}

//$--DllEntryPoint------------------------------------------------------------
//
// DESCRIPTION: 32-bit Windows DLL entry point procedure.  Called on process
//              and thread initialization and deinitialization.
//              Takes care of calling _CRT_INIT to initialize the "C" run-time
//              library.
//
// INPUT:       hInstDll    --  DLL instance handle
//              fdwReason   --  Reason being called flag (PROCESS_ATTACH ...)
//              lpvReserved --  reserved
//
// RETURNS:     TRUE if successful, FALSE otherwise
//
// ---------------------------------------------------------------------------
BOOL WINAPI DllEntryPoint(                // RETURNS: BOOL
            IN HINSTANCE hInstDll,  // DLL instance handle
            IN DWORD fdwReason,     // reason this function is being called
            IN LPVOID lpvReserved)  // reserved (has static and dynamic call values)
{
    BOOL            fResult         =   TRUE;   // FALSE if can't initialize
    CIPMConvert *   lpCIPMConvert   =   NULL;   // Per-thread conversion class instance pointer

    // check to see why we were called.
    switch ( fdwReason )
    {
        case DLL_PROCESS_ATTACH:
            // Initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            if ( !fResult )
            {
                goto cleanup;
            }

            // Get the thread local storage index.
            dwTlsIndex = TlsAlloc();

            if ( dwTlsIndex == 0xFFFFFFFF )
            {
                // general failure
                fResult = FALSE;

                goto cleanup;
            }

            // Initialize a new conversion class.
            lpCIPMConvert = new CIPMConvert();

            if ( !lpCIPMConvert )
            {
                fResult = FALSE;

                goto cleanup;
            }

            // Store the new thread-safe conversion class in the
            // thread local storage slot.
            fResult = TlsSetValue(dwTlsIndex, (LPVOID) lpCIPMConvert);

            break;

        case DLL_THREAD_ATTACH:
            // Initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            if ( !fResult )
            {
                goto cleanup;
            }

            // Initialize a new conversion class.
            lpCIPMConvert = new CIPMConvert();

            if ( !lpCIPMConvert )
            {
                fResult = FALSE;

                goto cleanup;
            }

            // Store the new thread-safe conversion class in the
            // thread local storage slot.
            fResult = TlsSetValue(dwTlsIndex, (LPVOID) lpCIPMConvert);

            break;

        case DLL_THREAD_DETACH:
            // Get our thread-local conversion class instance pointer
            lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

            if ( lpCIPMConvert )
            {
                // free this conversion class instance.
                delete lpCIPMConvert;
            }

            // de-initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            break;

        case DLL_PROCESS_DETACH:
            // Get our thread-local conversion class instance pointer
            lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

            if ( lpCIPMConvert )
            {
                // free this conversion class instance.
                delete lpCIPMConvert;
            }

            // Free our thread-local storage index
            (VOID)TlsFree(dwTlsIndex);

            // de-initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            break;

        default:
            // shouldn't happen
            ASSERTERROR(FALSE, "Bad fdwReason");

            fResult = FALSE;

            break;

    }   // end switch

cleanup:

    return fResult;

}


CONVCLS.CPP

// --convcls.cpp--------------------------------------------------------------
//
// IPM to 822 conversion class source file.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "ipmconv.h"
#include "msgemit.h"
#include "convcls.h"
#include "tagnames.h"
#include "convcls.chk"

//$--CIPMConvert::CIPMConvert------------------------------------------------
//
// DESCRIPTION: CIPMConvert conversion class constructor
//
// INPUT:   none
//
// RETURNS: nothing
//
// ---------------------------------------------------------------------------
CIPMConvert::CIPMConvert()
{
    DEBUGPRIVATE("CIPMConvert::CIPMConvert()\n");

    // Initialize data members
    m_lpwszMsgClass = NULL;
    m_lpStream = NULL;
    m_fTNEFEncode = FALSE;
    m_lpAB = NULL;
    m_lpEnvelope = NULL;
    m_lpContent = NULL;
    m_lpEnvProps = NULL;
    m_lpCntProps = NULL;
    m_MsgType = mtNone;
    m_lpAttach = NULL;
}

//$--CIPMConvert::~CIPMConvert------------------------------------------------
//
// DESCRIPTION: CIPMConvert conversion class destructor
//
// INPUT:   none
//
// RETURNS: nothing
//
// ---------------------------------------------------------------------------
CIPMConvert::~CIPMConvert()
{
    DEBUGPRIVATE("CIPMConvert::~CIPMConvert()\n");

    // Free all MAPI buffers and release all MAPI objects
    Reset();

}

//$--CIPMConvert::fCheckInit--------------------------------------------------
//
// DESCRIPTION: Inline function to check to see if conversion class instance has
//              been initialized via HrInitialize().
//
// INPUT:       none
//
// RETURNS:     BOOL    --  TRUE if initialized, FALSE otherwise.
//
// ----------------------------------------------------------------------------
inline BOOL CIPMConvert::fCheckInit()   // RETURNS: BOOL
{
    return (m_lpwszMsgClass && m_lpEnvelope && m_lpStream && m_lpAB);
};

// $--CIPMConvert::HrInitialize-----------------------------------
//
// DESCRIPTION: Initializes conversion class instance
//
// INPUT:   lpwszMsgClass   --  message class
//          lpAddrBook  --  address book pointer
//          lpEnvelope  --  message envelope pointer
//          fTNEFEncode --  TNEF encode attachments flag
//          lpStream    --  Stream to write output to
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      EDK_E_ALREADY_EXISTS if already in use
//                      E_NOTIMPL if not yet implemented
//
// ----------------------------------------------------------------
HRESULT CIPMConvert::HrInitialize(
    IN LPCWSTR lpwszMsgClass,           // message class
    IN BOOL fTNEFEncode,                // TNEF encode attachments flag
    IN LPADRBOOK lpAddrBook,            // address book pointer
    IN LPMESSAGE lpEnvelope,            // message envelope pointer
    IN LPSTREAM lpStream)               // stream to write output to
{
    HRESULT hr      =   NOERROR;    // return code
    BOOL    fInUse  =   FALSE;      // TRUE if instance is in use

    DEBUGPRIVATE("CIPMConvert::HrInitialize()\n");

    // check input parameters
    hr = CHK_CIPMConvert_HrInitialize(lpwszMsgClass, fTNEFEncode,
                                      lpAddrBook, lpEnvelope,
                                      lpStream);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // make sure that this instance isn't already in use
    fInUse = fCheckInit();

    if ( fInUse )
    {
        hr = HR_LOG(EDK_E_ALREADY_EXISTS);

        goto cleanup;
    }

    // Set mesage class, envelope, TNEF, address book
    // and stream data members
    m_lpwszMsgClass = lpwszMsgClass;
    m_fTNEFEncode = fTNEFEncode;
    m_lpAB = lpAddrBook;
    m_lpEnvelope = lpEnvelope;
    m_lpStream = lpStream;

    // Determine the report type of the message, if any
    hr = HrSetMsgType(lpwszMsgClass);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::HrSetMsgType-------------------------------
//
// DESCRIPTION: Sets a message's type.
//
// INPUT:   lpwszReportClass    --  report class
//
// RETURNS: HRESULT --  NOERROR if successful & is a report.
//                      E_INVALIDARG if bad input
//
// -------------------------------------------------------------
HRESULT CIPMConvert::HrSetMsgType(       // RETURNS: HRESULT
        IN LPCWSTR lpwszMsgClass)        // message class
{
    HRESULT         hr                  =   NOERROR;
    LPWSTR          lpwClassCaps        =   NULL;       // upper-case version of class

    // Report types supported.
    const LPWSTR    lpwszNDRClass   =   L".NDR";
    const LPWSTR    lpwszDRClass    =   L".DR";
    const LPWSTR    lpwszNRNClass   =   L".IPNNRN";
    const LPWSTR    lpwszRNClass    =   L".IPNRN";

    DEBUGPRIVATE("CIPMConvert::HrSetMsgType()\n");

    // Check input parameters.
    hr = CHK_CIPMConvert_HrSetMsgType(lpwszMsgClass);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Convert the class passed in to upper case.
    lpwClassCaps = CharUpperW((LPWSTR) lpwszMsgClass);

    if ( (lpwClassCaps == NULL) ||
         IsBadStringPtrW(lpwClassCaps, INFINITE) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // See if this is an IPM message.
    if ( wcsstr(lpwClassCaps, IPMENVELOPECLASS) != NULL )
    {
        m_MsgType = mtIPM;  // interpersonal message

        goto cleanup;
    }

    // Check to make sure that we have a report
    if ( wcsstr(lpwClassCaps, IPMREPORTCLASS) == NULL )
    {
        // Not a report or an IPM!
        m_MsgType = mtNone;

        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Have a report.
    // Determine report type based on message class
    if ( wcsstr(lpwClassCaps, lpwszNDRClass) != NULL )
    {
        // non-delivery report
        m_MsgType = mtNDR;

        goto cleanup;
    }

    if ( wcsstr(lpwClassCaps, lpwszDRClass) != NULL )
    {
        // delivery report
        m_MsgType = mtDR;

        goto cleanup;
    }

    if ( wcsstr(lpwClassCaps, lpwszNRNClass) != NULL )
    {
        // non-read notification
        m_MsgType = mtNRN;

        goto cleanup;
    }

    if ( wcsstr(lpwClassCaps, lpwszRNClass) != NULL )
    {
        // read notification
        m_MsgType = mtRN;

        goto cleanup;
    }

    // If we get to here, we have an unrecognized report.
    m_MsgType = mtNone;

    hr = HR_LOG(E_FAIL);

    goto cleanup;

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::HrConvert------------------------------------
//
// DESCRIPTION: Converts a MAPI REPORT.IPM message to a "822-style"
//              stream output.
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_OUTOFMEMORY if memory problems
//                          E_FAIL if API problems
//                          E_NOTIMPL if conversion not supported
//
// ----------------------------------------------------------------
HRESULT CIPMConvert::HrConvert()    // RETURNS: HRESULT
{
    HRESULT hr              =   NOERROR;
    BOOL    fInitialized    =   FALSE;  // TRUE if conversion instance has been initialized

    DEBUGPRIVATE("CIPMConvert::HrConvert()\n");

    // Check to make sure that we have been initialized
    fInitialized = fCheckInit();

    if ( !fInitialized )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Call the appropriate conversion, based on the message
    // class.
    if ( m_MsgType == mtIPM )
    {
        // have an ENVELOPE.IPM.* message
        hr = HrIPMTo822Format();

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

    else if ( m_MsgType != mtNone )
    {
        // have some kind of supported REPORT.IPM.* message
        hr = HrReportTo822Format();

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

    else
    {
        // we have something we don't support
        hr = HR_LOG(E_NOTIMPL);

        goto cleanup;
    }

    // Append the TNEFed envelope content and attachments to the
    // stream if this is desired.
    if ( m_fTNEFEncode == TRUE )
    {
        // want to TNEF encode everthing attached to the envelope.
        hr = HrContentToTnef();

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

    }   // end if TNEF encoding desired

    // commit the changes to the output stream.
    hr = m_lpStream->Commit(STGC_DEFAULT);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    // Release and free MAPI objects used for conversion now.
    // Don't keep them in memory any longer than necessary.
    // (Also allows for re-use of thread.)
    Reset();

    RETURN(hr);
}

//$--CIPMConvert::HrIPMto822Format--------------------------------------------------------
//
// DESCRIPTION: Converts a MAPI ENVELOPE.IPM message to a "822-style" stream output
//
// INPUT:       none
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL if API function call failure,
//
// ---------------------------------------------------------------------------

HRESULT CIPMConvert::HrIPMTo822Format()   //  RETURNS:    HRESULT
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CIPMConvert::HrIPMTo822Format()\n");

    // Convert the message envelope.
    hr = HrConvertEnvelope();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Convert the message.
    hr = HrConvertContent();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Write out each attachment.
    hr = HrConvertAttachments();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);
}

//$--CIPMConvert::HrConvertEnvelope--------------------------------------------------------
//
// DESCRIPTION: Handles the MAPI IPM or notification envelope conversion.
//
// INPUT:       none
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL if API function call failure,
//
// ---------------------------------------------------------------------------

HRESULT CIPMConvert::HrConvertEnvelope()   //  RETURNS:    HRESULT
{
    HRESULT     hr              =   NOERROR;        // return code
    ULONG       ulNumRecips     =   0;              // number of recipients
    ULONG       ulCount         =   0;              // loop counter
    LPSRowSet   lpRecipRows     =   NULL;           // envelope's recipient table rows pointer
    ULONG       iProp           =   0;              // property index

    // indices into the envelope property tag array
    const UINT  iClass       =    0;  // index of message class value
    const UINT  iFromAddr    =    1;  // index of sender address
    const UINT  iXID         =    2;  // index of X-message identifier
    const UINT  iTrace       =    3;  // index of external trace information
    const UINT  iIntTrace    =    4;  // index of internal trace information

    const ULONG nEnvProps    =    5;  // number of envelope properties to retrieve

    // property values needed for the envelope
    SizedSPropTagArray(nEnvProps, sPropTagArray) =
    {
        nEnvProps,  // number of properties
        {
            PR_MESSAGE_CLASS,       // envelope message class, index iClass
            PR_ORIGINATOR_ADDR,     // envelope sender's email address, index iFromAddr
            PR_MESSAGE_SUBMISSION_ID,// content's X-Message-ID
            PR_TRACE_INFO,          // external trace information
PR_INTERNAL_TRACE_INFO, // internal trace information
        }
    };

    // indices into the recipient addresses property values array
    const UINT  iRecipient  =   0;  // PR_EMAIL_ADDRESS
    const UINT  iType       =   1;

    const ULONG nRecipProps =   2;  // number of recipient properties

    // property columns needed from the envelope's recipient table
    SizedSPropTagArray(nRecipProps, sRecipProps) =
    {
        nRecipProps,  // number of columns (properties)
        {
            PR_EMAIL_ADDRESS,       // recipient Email address
            PR_RECIPIENT_TYPE       // recipient address type
        }
    };

    DEBUGPRIVATE("HrConvertEnvelope()\n");

    // Retrieve all of the properties which we need for the
    // envelope now (currently, there are two.)
    hr = HrRetrieveProps(m_lpEnvelope,
                         (LPSPropTagArray) &sPropTagArray,
                         &m_lpEnvProps);

    // It's O.K. if we cannot retrieve the trace information property.
    if ( hr == EDK_E_NOT_FOUND )
    {
        for ( iProp = 0; iProp < nEnvProps; iProp++ )
        {
            if ( PROP_TYPE(m_lpEnvProps[iProp].ulPropTag) == PT_ERROR )
            {
                if ( iProp == iTrace )
                {
                    // Not finding external trace information is not fatal.
                    hr = HR_LOG(NOERROR);
                }

                else if ( iProp == iIntTrace )
                {
                    // Not finding internal trace information is not fatal.
                    hr = HR_LOG(NOERROR);
                }

                else
                {
                    // some other, unexpected error.
                    hr = HR_LOG(E_FAIL);

                    goto cleanup;
                }

            }   // end if found a property problem
        }   // end for each property tag
    }   // end if one or more properties not found

    // check for other failures
    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the name of the TNEF data file, if any.
    // (Note: If we are doing TNEF encoding, the name of the attached
    // TNEF data is always MAPIMAIL.DAT.)
    hr = HrEmitTagDataLine(
        lpszTagTnefAttach,      // tag
        (LPTSTR) (m_fTNEFEncode ? lpszTagTnefHdr : lpszNullData),  // data
        m_lpStream);            // stream pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // build and emit the "MAIL FROM" line.
    // from the sender's address string.
    hr = HrEmitTagDataLine(
                lpszTagMailFrom,        // tag
                m_lpEnvProps[iFromAddr].Value.LPSZ,  // sender's Email address
                m_lpStream);              // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build and emit the "RCPT TO" line for each recipient.
    // First, Retrieve the envelope's recipient table rows.
    hr = HrGetRecipientList(
                m_lpEnvelope,       // MAPI message envelope pointer
                (LPSPropTagArray) &sRecipProps, // array of property columns to retrieve
                &lpRecipRows);      // pointer to recipient array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Find and print out each MAPI_TO recipient
    ulNumRecips = 0;    // initialize number of "TO" recipients.
    for ( ulCount = 0; ulCount < lpRecipRows->cRows; ulCount++ )
    {
        if ( lpRecipRows->aRow[ulCount].lpProps[iType].Value.ul ==
             MAPI_TO )
        {
            // found a "RCPT TO" recipient
            ulNumRecips++;  // increment count

            hr = HrEmitTagDataLine(
                lpszTagRcptTo,      // tag
                lpRecipRows->aRow[ulCount].lpProps[iRecipient].Value.LPSZ,  // data
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if found a to recipient
    }   // end for MAPI_TO recipients

    // There had better be at least one "RCPT TO" recipient!
    if ( ulNumRecips == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Emit the "DATA:" marker
    hr = m_lpStream->Write(
                lpszTagData,        // data
                lstrlen(lpszTagData) * sizeof(TCHAR), // length (no terminator)
                NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // We are done.

cleanup:

    // Free structures
    FREEPROWS(lpRecipRows);

    RETURN(hr);

}

//$--CIPMConvert::HrConvertContent--------------------------------------------------------
//
// DESCRIPTION: Handles the MAPI IPM envelope content (original mesage) conversion.
//
// INPUT:       none
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL if API function call failure,
//
// ---------------------------------------------------------------------------

HRESULT CIPMConvert::HrConvertContent()   //  RETURNS:    HRESULT
{
    HRESULT         hr              =   NOERROR;        // return code
    LPTSTR          lpTempString    =   NULL;   // data string
    ULONG           ulCount         =   0;      // temporary count variable
    ULONG           ulNumRecips     =   0;      // number of recipients
    LPSRowSet       lpRecipRows     =   NULL;   // content's recipient table rows pointer
    LPTRACEINFO     lpTraceInfo     =   NULL;   // external trace information
    LPTRACEENTRY    lpTraceEntry    =   NULL;   // external trace entry
    PINTTRACEINFO   lpIntTraceInfo  =   NULL;   // internal trace information
    PINTTRACEENTRY  lpIntTraceEntry =   NULL;   // internal trace entry
    ULONG           cbBodyText      =   0;      // # bytes in body text
    LPSTREAM        lpTextBody      =   NULL;   // ANSI text body stream
    BOOL            fBody           =   TRUE;   // FALSE if no body text
    ULARGE_INTEGER  scbStream       =   {0};    // # bytes to copy
    LARGE_INTEGER   sOffsetZero     =   {0};    // zero offset
    ULONG           iProp           =   0;      // index into property value array
    BOOL            fPriority       =   TRUE;   // FALSE if message has no priority

    // temporary file name buffer
    TCHAR           szFileName[MAX_PATH + 1]    =   TEXT(".\\");

    // temporary string buffer
    TCHAR           szTempBuf[ulMaxOutStringLen + 1]   =   TEXT("");

    // temporary file name prefix
    const LPTSTR    szPrefix    =   TEXT("CNV");

    // indices into content properties
    const UINT  iClass       =    0;  // index of message class value
    const UINT  iFromAddr    =    1;  // index of sender address
    const UINT  iXID         =    2;  // index of X-message identifier
    const UINT  iSentTime    =    3;  // index of sent time value
    const UINT  iImportance  =    4;  // index of importance value
    const UINT  iPriority    =    5;  // index of priority value
    const UINT  iSubject     =    6;  // index of the subject value

    const ULONG nCntProps    =    7;  // # of message content properties

    // property values needed for the message content
    SizedSPropTagArray(nCntProps, sPropTagArray) =
    {
        nCntProps,  // number of properties
        {
            PR_MESSAGE_CLASS,       // content's message class, index iClass
            PR_SENDER_EMAIL_ADDRESS,// content's sender's email address, index iFromAddr
            PR_SEARCH_KEY,          // dummy--acutally use PR_MESSAGE_SUBMISSION_ID on from
                                    // the envelope
            PR_CLIENT_SUBMIT_TIME,  // content's sent time, index iSentTime
            PR_IMPORTANCE,          // content's importance value, index iImportance
            PR_PRIORITY,            // content's priority value, index iPriority
            PR_SUBJECT              // content's subject, index iSubject
        }
    };

    // indices needed from envelope properties array
    const UINT  iTrace       =    3;  // index of external trace information
    const UINT  iIntTrace    =    4;  // index of internal trace information

    // indices into the recipient addresses property values array
    const UINT  iRecipient  =   0;  // PR_EMAIL_ADDRESS
    const UINT  iType       =   1;  // PR_RECIPIENT_TYPE

    const ULONG nRecipProps =   2;  // # of recipient properties

    // property columns needed from the content's recipient table
    SizedSPropTagArray(nRecipProps, sRecipProps) =
    {
        nRecipProps,  // number of columns (properties)
        {
            PR_EMAIL_ADDRESS,       // recipient Email address
            PR_RECIPIENT_TYPE       // recipient address type
        }
    };

    // "Spread" between importance values and their corresponding
    // priority values
    const ULONG nPriorityImportanceSpread   =   1;

    DEBUGPRIVATE("CIPMConvert::HrConvertContent()\n");

    // Open the envelope's content
    hr = HrOpenContent();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Retrieve and store the content properties needed.
    hr = HrRetrieveProps(m_lpContent,
                         (LPSPropTagArray) &sPropTagArray,
                         &m_lpCntProps);

    // The PR_PRIORITY property may not be available for Schedule+
    // "ENVELOPE.IPM.Schedule.Meeting.Request" messages.
    if ( hr == EDK_E_NOT_FOUND )
    {
        for ( iProp == 0; iProp < sPropTagArray.cValues; iProp++ )
        {
            if ( PROP_TYPE(m_lpCntProps[iProp].ulPropTag) == PT_ERROR )
            {
                if ( iProp == iPriority )   // no PR_PRIORITY property
                {
                    // This isn't an error for some types of IPM
                    // messages.  Merely compute the priority from
                    // the importance.
                    fPriority = FALSE;  // message has no priority

                    hr = HR_LOG(NOERROR);
                }

                else    // some other error
                {
                    hr = HR_LOG(E_FAIL);

                    goto cleanup;
                }
            }   // end if found bad property tag
        }   // end for
    }   // end if property not found

    if ( FAILED(hr) )
    {
        // general error case
        goto cleanup;
    }

    // Print out the "X-Message-Class:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                    lpszTagMsgClass,
                    m_lpCntProps[iClass].Value.LPSZ,
                    m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the date and time string
    hr = HrCreateDateTimeString(
                  &(m_lpCntProps[iSentTime].Value.ft),  // PR_CLIENT_SUBMIT_TIME property value
                  &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Date:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                    lpszTagDate,
                    lpTempString,
                    m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Print out the "X-Message-ID:" tag and data line to the stream
    // The X message ID is already null-terminated.
    hr = HrEmitTagDataLine(
                lpszTagMsgID,
                (LPTSTR) m_lpEnvProps[iXID].Value.bin.lpb,  // really a null-terminated string
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the trace information string, if any.
    if ( PROP_TYPE(m_lpEnvProps[iTrace].ulPropTag) != PT_ERROR )
    {
        // have valid trace information
        lpTraceInfo = (LPTRACEINFO) (m_lpEnvProps[iTrace].Value.bin.lpb);

        // Print out trace information data
        // Note:  It's O.K. for there to be no trace information
        // on NDR's just created by the gateway.
        for ( ulCount = 0; ulCount < lpTraceInfo->cEntries; ulCount++ )
        {
            // retrieve trace entry.
            lpTraceEntry = &(lpTraceInfo->rgtraceentry[ulCount]);

            // Create "External-Received-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,          // trace action
                lpTraceEntry->rgchCountryName,  // country name
                lpTraceEntry->rgchADMDName,     // ADMD name
                lpTraceEntry->rgchPRMDId,       // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Received-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "External-Attempted-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,              // trace action
                lpTraceEntry->rgchAttCountryName,   // country name
                lpTraceEntry->rgchAttADMDName,      // ADMD name
                lpTraceEntry->rgchAttPRMDId,        // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for trace entries
    }   // end if any trace information

    // Build the internal trace information string, if any.
    if ( PROP_TYPE(m_lpEnvProps[iIntTrace].ulPropTag) != PT_ERROR )
    {
        // have valid trace information
        lpIntTraceInfo = (PINTTRACEINFO) (m_lpEnvProps[iIntTrace].Value.bin.lpb);

        // Print out trace information data
        // Note:  It's O.K. for there to be no trace information
        // on NDR's just created by the gateway.
        for ( ulCount = 0; ulCount < lpIntTraceInfo->cEntries; ulCount++ )
        {
            // retrieve trace entry.
            lpIntTraceEntry = &(lpIntTraceInfo->rgIntTraceEntry[ulCount]);

            // Create "Internal-Received-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,          // trace action
                lpIntTraceEntry->rgchCountryName,  // country name
                lpIntTraceEntry->rgchADMDName,     // ADMD name
                lpIntTraceEntry->rgchPRMDId,       // PRMD identifier
                lpIntTraceEntry->rgchMTAName,      // MTA name

&lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Received-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "Internal-Attempted-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,              // trace action
                lpIntTraceEntry->rgchAttCountryName,   // country name
                lpIntTraceEntry->rgchAttADMDName,      // ADMD name
                lpIntTraceEntry->rgchAttPRMDId,        // PRMD identifier
                lpIntTraceEntry->rgchMTAName,      // MTA name
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for trace entries
    }   // end if any trace information

    // Print out the "From:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
        lpszTagFrom,     // tag
        m_lpCntProps[iFromAddr].Value.LPSZ,    // PR_SENDER_EMAIL_ADDRESS property value
        m_lpStream);                // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Get the recipients from the recipient table's rows.
    hr = HrGetRecipientList(
                m_lpContent,        // message content pointer
                (LPSPropTagArray) &sRecipProps, // columns/properties desired
                &lpRecipRows);      // pointer to recipient table row pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Find and print out the MAPI_TO recipients.
    ulNumRecips = 0;    // initialize count of to recipients
    for ( ulCount = 0; ulCount < lpRecipRows->cRows; ulCount++ )
    {
        if ( lpRecipRows->aRow[ulCount].lpProps[iType].Value.ul ==
             MAPI_TO )
        {
            // found a "TO" recipient
            ulNumRecips++;  // increment count

            // Print out "TO" tag and data line
            hr = HrEmitTagDataLine(
                lpszTagTo,          // tag
                lpRecipRows->aRow[ulCount].lpProps[iRecipient].Value.LPSZ, // data
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if found a to recipient
    }   // end for

    // It is O.K. for an IPM message content to have no "To" recipients.
    // (The envelope, however, must have "RCPT TO" recipients

    // Find and print out the MAPI_CC recipients.
    for ( ulCount = 0; ulCount < lpRecipRows->cRows; ulCount++ )
    {
        if ( lpRecipRows->aRow[ulCount].lpProps[iType].Value.ul ==
             MAPI_CC )
        {
            // found a "CC" recipient
            // Print out "CC" tag and data line
            hr = HrEmitTagDataLine(
                lpszTagCC,          // tag
                lpRecipRows->aRow[ulCount].lpProps[iRecipient].Value.LPSZ, // data
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if found a cc recipient
    }   // end for

    // find and print out BCC recipients
    for ( ulCount = 0; ulCount < lpRecipRows->cRows; ulCount++ )
    {
        if ( lpRecipRows->aRow[ulCount].lpProps[iType].Value.ul ==
             MAPI_BCC )
        {
            // found a "BCC" recipient
            // Print out "BCC" tag and data line
            hr = HrEmitTagDataLine(
                lpszTagBCC,          // tag
                lpRecipRows->aRow[ulCount].lpProps[iRecipient].Value.LPSZ, // data
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
        }   // end if found a bcc recipient
    }   // end for

    // Print out a blank line
    hr = m_lpStream->Write(
            lpszNewLine,        // data
            lstrlen(lpszNewLine) * sizeof(TCHAR),   // length (no terminator)
            NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Print out the "Subject:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagSubject,
            m_lpCntProps[iSubject].Value.LPSZ,  // PR_SUBJECT property value
            m_lpStream);                    // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build priority string.
    // First, check to see if a priority was assigned.
    if ( fPriority == FALSE )
    {
        // Set the priority based on the message's importance.
        m_lpCntProps[iPriority].Value.ul =
            m_lpCntProps[iImportance].Value.ul - nPriorityImportanceSpread;

    }   // end if no priority

    hr = HrCreatePriorityString(
                m_lpCntProps[iPriority].Value.ul,  // PR_PRIORITY property value
                &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Priority:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagPriority,
            lpTempString,
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Build the importance string
    hr = HrCreateImportanceString(
                m_lpCntProps[iImportance].Value.ul,    // PR_IMPORTANCE property value
                &lpTempString);                             // buffer pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Importance:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                lpszTagImportance,
                lpTempString,
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Write the ANSI body text header and data information
    // to the stream.
    // This means that we must first convert the compressed
    // rich text format body to ANSI.

    // Create a temporary, buffered stream to hold the ANSI text.
    hr = OpenStreamOnFile(
        MAPIAllocateBuffer,     // allocation routine
        MAPIFreeBuffer,         // deallocation routine
        STGM_READWRITE | STGM_CREATE | SOF_UNIQUEFILENAME |
        STGM_DELETEONRELEASE | STGM_SHARE_EXCLUSIVE,   // interface flags
        szFileName,             // temporary file name
        szPrefix,               // file prefix
        &lpTextBody);           // stream pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(lpTextBody), "Bad lpTextBody");

    // Convert the RTF compressed stream data to ANSI text
    // data.
    hr = HrRTFCompressedToText(
                    m_lpContent,        // MAPI message pointer
                    lpTextBody,         // stream to copy ANSI text to
0,// use current code page
                    &cbBodyText);       // # bytes copied to stream

    // It is O.K. for there to be no RTF body text
// (Note:  The MAPI error code MAPI_E_NOT_FOUND is returned in this
// case.)
    if ( hr == MAPI_E_NOT_FOUND )
    {
        // no message body
        fBody = FALSE;

        // not an error
        hr = HR_LOG(NOERROR);
    }

    // Test for other errors
    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(lpTextBody), "Bad lpTextBody.");

    // Format the number of bytes in the message body data.
    wsprintf(
            szTempBuf,      // buffer
            TEXT("%ld"),    // format string
            cbBodyText);    // data length (in bytes)

    // Emit the body text "----beginbody" tag and data.
    hr = HrEmitTagDataLine(
            lpszTagBodyHdr,     // tag
            szTempBuf,          // data
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    if ( fBody == TRUE )    // There is body text
    {
        // Go back to the begging of the ANSI body text
        // source stream.
        hr = lpTextBody->Seek(
                    sOffsetZero,        // 0 offset
                    STREAM_SEEK_SET,    // from beginning
                    NULL);              // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Copy all the ANSI body text to the output stream
        scbStream.LowPart = cbBodyText;  // # bytes to copy
        hr = lpTextBody->CopyTo(
                    m_lpStream, // output stream pointer
                    scbStream,  // # bytes to copy
                    NULL,       // don't care
                    NULL);      // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

    }   // end if body

    // Emit the body text footer.
    hr = m_lpStream->Write(
            lpszTagBodyEnd,         // data
            lstrlen(lpszTagBodyEnd) * sizeof(TCHAR), // data length (no terminator)
            NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // We are done.

cleanup:

    // Release MAPI and OLE objects
    ULRELEASE(lpTextBody);      // Deletes temporary file as a side-effect

    // Free buffers
    MAPIFREEBUFFER(lpTempString);

    FREEPROWS(lpRecipRows);

    RETURN(hr);

}

//$--CIPMConvert::HrConvertAttachments--------------------------------------------------------
//
// DESCRIPTION: Handles the MAPI IPM original message attachments conversion.
//
// INPUT:       none
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL if API function call failure,
//
// ---------------------------------------------------------------------------

HRESULT CIPMConvert::HrConvertAttachments()   //  RETURNS:    HRESULT
{
    HRESULT         hr              =   NOERROR;
    LPMAPITABLE     lpTable         =   NULL;   // attachments table pointer
    LPSRowSet       lpRows          =   NULL;   // table rows
    ULONG           iLoopCounter    =   0;      // loop counter index
    LPATTACH        lpAttachment    =   NULL;   // message attachment pointer
    ULONG           nProps          =   0;      // # of properties
    LPSPropValue    lpPropVals      =   NULL;   // property value array pointer
    LPTSTR          lpFileName      =   NULL;   // file name pointer
    ULONG           cbAttachSize    =   0;      // # bytes in attachment
    PVIRTUALSTREAMONPROPERTYlpStreamAtData  =   NULL;   // buffered attachment data stream
STATSTGsStatStg={0};// stream statistics

    // temporary string buffer
    TCHAR           szTempBuffer[ulMaxOutStringLen + 1] =   TEXT("");
    // Attachment header buffer
    TCHAR           szAttachHdr[ulMaxOutStringLen + 1]  =   TEXT("");

    // property tag array for attachment number
    SizedSPropTagArray(1, sPropAttachNum)   =
    {
        1,  // one property
        {
            PR_ATTACH_NUM   // property tag
        }
    };

    // indices into the property value array
    const UINT  iAttachMethod   =   0;
    const UINT  iFileName       =   1;

    const ULONG nAttachProps    =   2;  // # of attachment properties

    // properties to retrieve per attachment
    SizedSPropTagArray(nAttachProps, sAttachPropTags) =
    {
        nAttachProps,      // number of properties to retrieve
        {
            PR_ATTACH_METHOD,       // attachment "type"
            PR_ATTACH_FILENAME      // attachment 8.3 file name
        }
    };

    DEBUGPRIVATE("CIPMConvert::HrConvertAttachments()\n");

    // Handle the attachment for the message contents.
    // First open the attachments table.
    hr = m_lpContent->GetAttachmentTable(MAPI_DEFERRED_ERRORS, &lpTable);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(lpTable, "Bad lpTable");

    // Retrieve the desired rows.
    // (HrQueryAllRows does a SetColumns for us)
    hr = HrQueryAllRows(lpTable,        // MAPI table pointer
                        (LPSPropTagArray) &sPropAttachNum,  // columns to retrieve
                        NULL,           // restriction pointer
                        NULL,           // order set pointer
                        0,              // max rows, defaults to all
                        &lpRows);       // pointer to row array pointer

    if ( FAILED(hr) || (hr == MAPI_W_POSITION_CHANGED) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;

    }

    ASSERT_READ_PTR(lpRows, sizeof(SRowSet), "Bad lpRows");

    // handle each attachment
    for ( iLoopCounter = 0; iLoopCounter < lpRows->cRows; iLoopCounter++ )
    {
        // Release objects from previous iteration
        ULRELEASE(lpAttachment);
        ULRELEASE(lpStreamAtData);

        // Free MAPI memory from previous iteration.
        MAPIFREEBUFFER(lpPropVals);

        // Open the attachment
        hr = m_lpContent->OpenAttach(
            lpRows->aRow[iLoopCounter].lpProps[0].Value.ul,  // attachment #
            &IID_IAttachment,    // interface
            MAPI_DEFERRED_ERRORS, // reduces RPCs
            &lpAttachment);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Get the desired properties for this attachment.

        // Notes:
        // If the attachment is not a binary attachment, this function may fail,
        // because the PR_ATTACH_FILENAME property won't be there.
        hr = lpAttachment->GetProps(
                            (LPSPropTagArray) &sAttachPropTags,   // property tags
                            fMapiUnicode,   // flags
                            &nProps,        // number of properties
                            &lpPropVals);   // pointer to property value arrray

        ASSERTERROR(nProps == sAttachPropTags.cValues,
                    "Bad nProps");
        ASSERTERROR(!IsBadReadPtr(lpPropVals, sizeof(SPropValue) * nProps),
                    "Bad lpPropVals");

        if ( hr == MAPI_W_ERRORS_RETURNED )
        {
            // Not an error if the PR_ATTACH_FILENAME property
            // doesn't exist.  (This just isn't a binary attachment!)
            if ( (PROP_TYPE(lpPropVals[iFileName].ulPropTag) == PT_ERROR) &&
                 (lpPropVals[iFileName].Value.l == MAPI_E_NOT_FOUND) )
            {
                // We have a non-binary attachment.
                // This is not really an error.
                // We just drop this attachment and
                // continue with the next.
                hr = HR_LOG(NOERROR);

                continue;
            }
        }   // end if problems getting binary attachment properties

        if ( FAILED(hr) || ( hr == MAPI_W_ERRORS_RETURNED) )
        {
            // If things are O.K., hr should be NOERROR now.
            // general failure
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Determine if this attachment is a binary attachment
        // (i.e. if it has binary data in the PR_ATTACH_DATA_BIN
        // property).  The only types of attachments which we
        // handle are binary attachments (PR_ATTACH_METHOD == ATTACH_BY_VALUE).
        // All unresolved,
        // OLE and embedded message attachments will be dropped.
        if ( lpPropVals[iAttachMethod].Value.l != ATTACH_BY_VALUE )
        {
            // We don't have a resolved, binary attachment.
            // Just drop this attachment and continue.
            continue;

        }   // end if not a resolved, binary attachment

        // Open a buffered stream on the binary attachment data property.
hr = HrOpenVirtualStreamOnProperty(
lpAttachment,// MAPI property object pointer
            PR_ATTACH_DATA_BIN, // binary attachment
            MAPI_DEFERRED_ERRORS,   // flags (reduces RPCs)
            &lpStreamAtData); // buffered stream pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERT_IUNKNOWN_PTR(lpStreamAtData, "Bad lpStreamAtData");

        // Determine the number of bytes of binary attachment
        // data.
        hr = lpStreamAtData->Stat(
&sStatStg,// stream statistics structure pointer
STATFLAG_NONAME);// statistics flags

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // See if data is too long for us to handle
        if ( sStatStg.cbSize.HighPart != 0 )
        {
            hr = HR_LOG(E_NOTIMPL);

            goto cleanup;
        }

        cbAttachSize = sStatStg.cbSize.LowPart;    // # bytes of data

        // Make sure that the file name isn't NULL.
        lpFileName = lpPropVals[iFileName].Value.LPSZ;
        if ( (lpFileName == NULL) || (*lpFileName == 0) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Create the attachment header from the attachment
        // file name and the # bytes in the attachment.
        //
        // Format:   FileName:#bytes
        //
        // e.g.      FILE1.TMP:54
        lstrcpy(szAttachHdr, lpFileName);
        AddColon(szAttachHdr);
        lstrcat(szAttachHdr, _ultoa(cbAttachSize, szTempBuffer, 10));

        // Emit the attachments "----beginattach" tag and data line
        hr = HrEmitTagDataLine(
                    lpszTagAttachHdr,
                    szAttachHdr,
                    m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Copy the binary attachment data from the input
        // stream to the output stream.
        hr = lpStreamAtData->CopyTo(
            m_lpStream,     // destination stream
            sStatStg.cbSize,    // number of bytes to copy
            NULL,           // don't care
            NULL);          // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Emit the attachment's footer "----endattach" text.
        hr = m_lpStream->Write(
                lpszTagAttachEnd,           // data
                lstrlen(lpszTagAttachEnd) * sizeof(TCHAR),// length (no terminator)
                NULL);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // We are done with this attachment.  Do the next

    }   // end for

cleanup:

    // Free all MAPI structures
    FREEPROWS(lpRows);
    MAPIFREEBUFFER(lpPropVals);

    // Release all MAPI  object
    ULRELEASE(lpTable);
    ULRELEASE(lpAttachment);
    ULRELEASE(lpStreamAtData);

    RETURN(hr);

}

//$--CIPMConvert::HrRetrieveProps--------------------------------
//
// DESCRIPTION: Retrieves and stores properties desired on specified object
//
// INPUT:       lpObject    --  pointer to message object
//              lpPropTags  --  property tag array pointer
//
//
// OUTPUT:      lppPropVals --  pointer to property value array pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          EDK_E_NOT_FOUND if one or more
//                          properties not found,
//                          E_FAIL otherwise
//
// -------------------------------------------------------------
HRESULT CIPMConvert::HrRetrieveProps(       // RETURNS: HRESULT
    IN LPMAPIPROP lpObj,                    // MAPI object pointer
    IN LPSPropTagArray lpPropTags,          // counted property tag array pointer
    OUT LPSPropValue * lppPropVals)     // pointer to property value array pointer
{
    HRESULT hr      =   NOERROR;
    ULONG   ulCount =   0;      // count of properties retrieved

    DEBUGPRIVATE("CIPMConvert::HrRetrieveProps()\n");

    // check input parameters
    hr = CHK_CIPMConvert_HrRetrieveProps(lpObj, lpPropTags,
                                         lppPropVals);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    hr = lpObj->GetProps(lpPropTags,    // properties to retrieve
                         fMapiUnicode,  // flags
                         &ulCount,      // number of properties retrieved
                         lppPropVals);  // array of property values

    switch ( hr )
    {
        case MAPI_W_ERRORS_RETURNED:

            hr = HR_LOG(EDK_E_NOT_FOUND);

            goto cleanup;

        default:

            if ( FAILED(hr) || (ulCount != lpPropTags->cValues) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }
    }   // end switch

    // more consistency checks
    ASSERTERROR(!IsBadReadPtr(lppPropVals, sizeof(LPSPropValue)),
                "Bad lppPropVals");
    ASSERTERROR(!IsBadReadPtr(*lppPropVals, sizeof(SPropValue) * ulCount),
                "Bad *lppPropVals");

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::Reset----------------------------------------
//
// DESCRIPTION: resets all of the conversion class' data members
//              and frees all MAPI memory allocated by the class.
//              This allow the thread's class instance to be re-used
//              for another conversion (if desired).
//
// INPUT:       none
//
// RETURNS:     VOID
//
// ----------------------------------------------------------------
VOID CIPMConvert::Reset()        // RETURNS: VOID
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CIPMConvert::Reset()\n");

    // Reset all of the conversion class' data members.
    // This allows the class instance to be re-used by this
    // thread (Also frees MAPI memory).
    m_lpwszMsgClass = NULL;
    m_fTNEFEncode = FALSE;
    m_lpAB = NULL;
    m_lpEnvelope = NULL;
    m_lpStream = NULL;
    m_MsgType = mtNone;

    // Release OLE objects
    ULRELEASE(m_lpContent);
    ULRELEASE(m_lpAttach);

    MAPIFREEBUFFER(m_lpEnvProps);
    MAPIFREEBUFFER(m_lpCntProps);

    return;

}


CONVCLS.H

// --convcls.h--------------------------------------------------------------
//
// IPM to 822 conversion class header file.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#ifndef _CONVCLS_H
#define _CONVCLS_H

// $--MESSAGE_TYPE-----------------------------------------------
//
// Report type enumeration
//
// -------------------------------------------------------------
enum MESSAGE_TYPE
{
    mtNone,        // no type
    mtIPM,         // IPM message
    mtNDR,         // non-delivery report
    mtDR,          // delivery report
    mtNRN,         // non-read notification
    mtRN           // read notification
};

// $--CIPMConvert------------------------------------------------------------
//
// IPM message to 822-style stream converter class
//
// ---------------------------------------------------------------------------

class CIPMConvert
{
public:

    CIPMConvert();  // constructor
    ~CIPMConvert(); // destructor

    // Initializes conversion class instance
    HRESULT HrInitialize(
        IN LPCWSTR lpwszMsgClass,   // message class
        IN BOOL bTNEFEnc,           // TNEF encode attachments flag
        IN LPADRBOOK lpAddrBook,    // address book pointer
        IN LPMESSAGE lpMessage,     // message to convert
        IN LPSTREAM lpStream);      // stream to write output to

    // IPM message or report to 822-style converter
    HRESULT HrConvert();

private:

    // methods

    // TNEF encodes everything below the envelope.  Only
    // converts the envelope to the "822-style" header.
    HRESULT HrContentToTnef();

    // converts an ENVELOPE.IPM message
    HRESULT HrIPMTo822Format();

    // converts a REPORT.IPM message
    HRESULT HrReportTo822Format();

    // Converts the message's or notification's envelope
    // IPM.NOTE or REPORT.IPM.IPN[N]RN
    HRESULT HrConvertEnvelope();

    // Retrieves and stores envelope's properties.
    HRESULT HrEnvelopeGetProps();

    // Converts the message's  content
    HRESULT HrConvertContent();

    // Converts the message's attachments
    HRESULT HrConvertAttachments();

    // Converts REPORT.[N]DR delivery/non-delivery report envelope
    HRESULT HrConvertReport();

    // Opens the envelope's content.
    HRESULT HrOpenContent();

    // Retrieves and stores properties requested
    HRESULT HrRetrieveProps(
        IN LPMAPIPROP lpObj,            // MAPI object to retrieve properties from
        IN LPSPropTagArray lpPropTags,  // pointer to property tags
        OUT LPSPropValue * lppPropVals);// pointer to property value array

    // Determines and sets the report type
    HRESULT HrSetMsgType(
        IN LPCWSTR lpwszMessageClass);  // message class

    // Handles NDR non-recipient table
    HRESULT HrConvertNonRecipients();

    // Handle DR recipient table
    HRESULT HrConvertRecipients();

    // Handle RN & NRN report content
    HRESULT HrConvertNotifyContent();

    // Checks to see if conversion class has been initialized
    BOOL fCheckInit();               // RETURNS: BOOL

    // "Resets" the class after a conversion is completed.
    VOID Reset();                   // RETURNS: VOID

    // data members
    // Currently, converter only supports one conversion per thread.
    LPCWSTR     m_lpwszMsgClass;      // mesage class
    LPMESSAGE   m_lpEnvelope;         // message's envelope
    LPMESSAGE   m_lpContent;          // message's content (the original message)
    LPSTREAM    m_lpStream;           // stream currently writing to
    BOOL        m_fTNEFEncode;        // TNEF encoding flag
    LPADRBOOK   m_lpAB;               // address book pointer
    LPATTACH    m_lpAttach;           // envelope attachment pointer

    LPSPropValue    m_lpEnvProps;     // message envelope properties pointer
    LPSPropValue    m_lpCntProps;     // message content properties pointer

    MESSAGE_TYPE     m_MsgType;       // message type
};

#endif


CONVREPT.CPP

// --convrept.cpp------------------------------------------------
//
// CIPMConvert source code for IPM reports.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
// --------------------------------------------------------------

#include "edk.h"
#include "ipmconv.h"
#include "msgemit.h"
#include "convcls.h"
#include "tagnames.h"
#include "convrept.chk"

//$--CIPMConvert::HrReportTo822Format---------------------------
//
// DESCRIPTION: Handle convertion of IPM reports to 822-style header
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_OUTOFMEMORY if memory problems
//                      E_NOTIMPL if unknown report type is received
//                      E_FAIL otherwise
//
// -------------------------------------------------------------
HRESULT CIPMConvert::HrReportTo822Format()  // RETURNS: HRESULT
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("CIPMConvert::HrReportTo822Format()\n");

    // switch off of report's type
    switch ( m_MsgType )
    {
        case mtNDR:
            // non-delivery report

            // Handle the report's envelope.
            hr = HrConvertReport();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Handle non-recipient table.
            hr = HrConvertNonRecipients();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            break;

        case mtDR:
            // delivery report

            // Handle the report's envelope.
            hr = HrConvertReport();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Handle recipient table
            hr = HrConvertRecipients();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            break;

        case mtNRN:
        case mtRN:
            // non-read notification &
            // read notification

            // Handle notification envelope
            // (appears to be essentially the same
            // as a regular IPM envelope.
            hr = HrConvertEnvelope();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Handle read/non-read notification content.
            hr = HrConvertNotifyContent();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            break;

        default:
            // shouldn't happen
            hr = HR_LOG(E_FAIL);

            goto cleanup;

    }   // end switch

    // we are done

cleanup:

    RETURN(hr);

}

//$--CIPMConvert::HrConvertReport--------------------------------
//
// DESCRIPTION: Handle conversion of REPORT.[N]DR delivery/non-delivery
//              report envelope
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_OUTOFMEMORY if memory problems,
//                      E_NOTIMPL if don't support report type
//                      E_FAIL otherwise.
//
//----------------------------------------------------------------
HRESULT CIPMConvert::HrConvertReport()   // RETURNS: HRESULT
{
    HRESULT         hr              =   NOERROR;
    LPTSTR          lpTempString    =   NULL;       // temporary string pointer
    BOOL            fReportXID      =   TRUE;       // FALSE if report does not have XID property
    LPTRACEINFO     lpTraceInfo     =   NULL;       // trace information structure pointer
    ULONG           iLoop           =   0;          // loop index
    LPTRACEENTRY    lpTraceEntry    =   NULL;       // trace entry pointer
    BOOL            fTraceInfo      =   TRUE;       // FALSE if no trace information
    ULONG           ulObjType       =   0;          // MAPI object type
    LPMAILUSER      lpReportToUser  =   NULL;       // report to recipient pointer
    LPSPropValue    lpRecipProps    =   NULL;       // recipient property value array pointer

    PINTTRACEINFO   lpIntTraceInfo  =   NULL;       // trace information structure pointer
    PINTTRACEENTRY  lpIntTraceEntry =   NULL;       // trace entry pointer
    BOOL            fIntTraceInfo   =   TRUE;       // FALSE if no trace information

    // Report property indices
    const UINT  iClass       =    0;  // index of message class value
    const UINT  iFromAddr    =    1;  // index of sender address
    const UINT  iXID         =    2;  // index of X-message identifier
    const UINT  iTrace       =    3;  // index of trace information
    const UINT  iSentTime    =    4;  // index of sent time value
    const UINT  iImportance  =    5;  // index of importance value
    const UINT  iPriority    =    6;  // index of priority value
    const UINT  iSubject     =    7;  // index of the subject value
    const UINT  iReportToEID =    8;  // index of report destination entry ID
    const UINT  iSubjectXID  =    9;  // index of report subject X-message ID value
    const UINT  iIntTrace    =   10;  // index of trace information

    // number of report properties
    const ULONG     nProps          =   11;

    // Properties to retrieve for the report envelope
    SizedSPropTagArray(nProps, sPropTagArray) =
    {
        nProps,  // number of properties
        {
            PR_MESSAGE_CLASS,   // report class property ID
            PR_SENDER_EMAIL_ADDRESS,    // report from address
            PR_MESSAGE_SUBMISSION_ID,   // report X-identifier
            PR_TRACE_INFO,      // report trace information
            PR_CREATION_TIME,   // report creation time
            PR_IMPORTANCE,      // report importance
            PR_PRIORITY,        // report priority
            PR_SUBJECT,         // report subject
            PR_REPORT_DESTINATION_ENTRYID,  // report destination entry identifier
            PR_MTS_SUBJECT_ID,  // report's subject X-identifier
            PR_INTERNAL_TRACE_INFO,  // report internal trace information
        }
    };

    // indices into the recipient property array.
    const UINT  iEmailAddr  =   0;      // recipient's email address

    const ULONG nRecipProps =   1;      // # of recipient properties

    // Array of recipient properties to retrieve
    SizedSPropTagArray(nRecipProps, sRecipProps) =
    {
        nRecipProps,      // # recipient properties to retrieve
        {
            PR_EMAIL_ADDRESS    // recipient's email address
        }
    };

    DEBUGPRIVATE("CIPMConvert::HrConvertReport()\n");

    // consistency checks
    ASSERT_IUNKNOWN_PTR(m_lpAB, "Bad m_lpAB");

    // Retieve the properties needed on the report envelope.
    hr = HrRetrieveProps(m_lpEnvelope,
                          (LPSPropTagArray) &sPropTagArray,
                          &m_lpEnvProps);

    // Delivery reports may not have the PR_MESSAGE_SUBMISSION_ID
    // nor the PR_MTS_SUBJECT_ID property.  This is a bug.  For the time
    // being, however, we shall not fail because of this.
    if ( hr == EDK_E_NOT_FOUND )
    {
        UINT    iPropIndex  =   0;
        for ( iPropIndex = 0; iPropIndex < sPropTagArray.cValues;
              iPropIndex++ )
        {
            if ( PROP_TYPE(m_lpEnvProps[iPropIndex].ulPropTag) ==
                 PT_ERROR )
            {
                if ( iPropIndex == iXID )
                {
                    fReportXID = FALSE;

                    hr = HR_LOG(NOERROR);   // not treated as an error
                }

                // It's also O.K. for there to be no trace
                // information.
                else if ( iPropIndex == iTrace )
                {
                    // no trace information
                    fTraceInfo = FALSE;

                    hr = HR_LOG(NOERROR);

                }   // end if no PR_TRACE_INFO

                else if ( iPropIndex == iIntTrace )
                {
                    // no internal trace information
                    fIntTraceInfo = FALSE;

                    hr = HR_LOG(NOERROR);

                }   // end if no PR_INTERNAL_TRACE_INFO

                else
                {
                    hr = HR_LOG(E_FAIL);    // another property isn't set

                    goto cleanup;
                }
            }
        }   // end for
    }

    // Test for other errors
    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build and emit the "X-Tnef-Attach" tag and data line.
    // The TNEF data file name is always "MAPIMAIL.DAT" if
    // we are doing TNEF encoding and null otherwise.
    hr = HrEmitTagDataLine(
        lpszTagTnefAttach,      // tag
        (LPTSTR) (m_fTNEFEncode ? lpszTagTnefHdr : lpszNullData),  // data
        m_lpStream);            // stream pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // build and emit the "MAIL FROM" line.
    // from the sender's address string.
    hr = HrEmitTagDataLine(
            lpszTagMailFrom,        // tag
            m_lpEnvProps[iFromAddr].Value.LPSZ,  // sender's Email address
            m_lpStream);              // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Emit the "RCPT TO" tag and data line.
    // First, we must open the address book entry associated with the
    // destination entry identifier and retrieve the entry's email
    // address.
    hr = m_lpAB->OpenEntry(
        m_lpEnvProps[iReportToEID].Value.bin.cb,    // # bytes in entry ID
        (LPENTRYID) m_lpEnvProps[iReportToEID].Value.bin.lpb,   // entry identifier
        NULL,                                       // interface identifer
        MAPI_DEFERRED_ERRORS,                       // reduces RPCs
        &ulObjType,                                 // MAPI object type
        (LPUNKNOWN *) &lpReportToUser);             // report to recipient pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(ulObjType == MAPI_MAILUSER, "Bad ulObjType");
    ASSERT_IUNKNOWN_PTR(lpReportToUser, "Bad lpReportToUser.");

    // Retrieve the email address of the report recipient.
    hr = HrRetrieveProps(
        lpReportToUser,     // MAPI recipient pointer
        (LPSPropTagArray) &sRecipProps,   // property tag array
        &lpRecipProps);     // property value array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the recipient's address for the "RCPT_TO" tag
    hr = HrEmitTagDataLine(
            lpszTagRcptTo,          // tag
            lpRecipProps[iEmailAddr].Value.LPSZ, // report destination
            m_lpStream);          // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Emit the "DATA" marker
    hr = m_lpStream->Write(
            lpszTagData,        // data
            lstrlen(lpszTagData) * sizeof(TCHAR),   // length (no terminator)
            NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Print out the "X-Message-Class:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagMsgClass,        // tag
            m_lpEnvProps[iClass].Value.LPSZ,  // report class
            m_lpStream);          // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the date and time string
    hr = HrCreateDateTimeString(
                  &(m_lpEnvProps[iSentTime].Value.ft),  // PR_CLIENT_SUBMIT_TIME property value
                  &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Date:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                lpszTagDate,
                lpTempString,
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Print out the "X-Message-ID:" tag and data line to the stream
    // The X-Message ID string is null terminated.
    if ( fReportXID )
    {
        hr = HrEmitTagDataLine(
            lpszTagMsgID,
            (LPTSTR) m_lpEnvProps[iXID].Value.bin.lpb,   // really a null-terminated string
            m_lpStream);
    }

    else
    {
        // Delivery reports may not have a PR_MESSAGE_SUBMISSION_ID
        // nor a PR_MTS_SUBJECT_ID.
        // We will print out a "null" XID in this case.
        hr = HrEmitTagDataLine(
            lpszTagMsgID,            // tag
            (LPTSTR) lpszNullData,   // blank data
            m_lpStream);
    }

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "X-Message-Subject-ID:" tag and data line to the stream
    // The X-Message ID string is already null terminated.
    hr = HrEmitTagDataLine(
        lpszTagSubjectID,
        (LPTSTR) m_lpEnvProps[iSubjectXID].Value.bin.lpb,    // really a null-terminated string
        m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the trace information string, if any
    if ( fTraceInfo == TRUE )
    {
        // have trace information
        lpTraceInfo = (LPTRACEINFO) (m_lpEnvProps[iTrace].Value.bin.lpb);

        // Print out trace entry data
        for ( iLoop = 0; iLoop < lpTraceInfo->cEntries; iLoop++ )
        {
            // retrieve trace entry.
            lpTraceEntry = &(lpTraceInfo->rgtraceentry[iLoop]);

            // Create "External-Received-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,          // trace action
                lpTraceEntry->rgchCountryName,  // country name
                lpTraceEntry->rgchADMDName,     // ADMD name
                lpTraceEntry->rgchPRMDId,       // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Received-At date and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "External-Attempted-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,              // trace action
                lpTraceEntry->rgchAttCountryName,   // country name
                lpTraceEntry->rgchAttADMDName,      // ADMD name
                lpTraceEntry->rgchAttPRMDId,        // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for each trace entry
    }   // end if any trace information

    // Build the internal trace information string, if any
    if ( fIntTraceInfo == TRUE )
    {
        // have trace information
        lpIntTraceInfo = (PINTTRACEINFO) (m_lpEnvProps[iIntTrace].Value.bin.lpb);

        // Print out trace entry data
        for ( iLoop = 0; iLoop < lpIntTraceInfo->cEntries; iLoop++ )
        {
            // retrieve trace entry.
            lpIntTraceEntry = &(lpIntTraceInfo->rgIntTraceEntry[iLoop]);

            // Create "Internal-Received-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,          // trace action
                lpIntTraceEntry->rgchCountryName,  // country name
                lpIntTraceEntry->rgchADMDName,     // ADMD name
                lpIntTraceEntry->rgchPRMDId,       // PRMD identifier
                lpIntTraceEntry->rgchMTAName,      // ADMD name
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Received-At date and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "Internal-Attempted-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,              // trace action
                lpIntTraceEntry->rgchAttCountryName,   // country name
                lpIntTraceEntry->rgchAttADMDName,      // ADMD name
                lpIntTraceEntry->rgchAttPRMDId,        // PRMD identifier
                lpIntTraceEntry->rgchAttMTAName,       // MTA name
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for each internal trace entry
    }   // end if any internal trace information

    // Print out the "From:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagFrom,     // tag
            m_lpEnvProps[iFromAddr].Value.LPSZ,    // report creator
            m_lpStream);                // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "TO:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagTo,     // tag
            lpRecipProps[iEmailAddr].Value.LPSZ,    // report destination
            m_lpStream);                // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out a blank line
    hr = m_lpStream->Write(
            lpszNewLine,            // data
            lstrlen(lpszNewLine) * sizeof(TCHAR),   // length (no terminator)
            NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Print out the "Subject:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagSubject,                // tag
            m_lpEnvProps[iSubject].Value.LPSZ,  // PR_SUBJECT property value
            m_lpStream);                    // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build priority string.
    hr = HrCreatePriorityString(
                m_lpEnvProps[iPriority].Value.ul,  // PR_PRIORITY property value
                &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Priority:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagPriority,
            lpTempString,
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Build the importance string
    hr = HrCreateImportanceString(
                m_lpEnvProps[iImportance].Value.ul,    // PR_IMPORTANCE property value
                &lpTempString);                             // buffer pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Importance:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                lpszTagImportance,
                lpTempString,
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

cleanup:

    // Release MAPI objects
    ULRELEASE(lpReportToUser);

    // Release MAPI buffers
    MAPIFREEBUFFER(lpTempString);
    MAPIFREEBUFFER(lpRecipProps);

    RETURN(hr);

}

//$--CIPMConvert::HrConvertNonRecipients--------------------------
//
// DESCRIPTION: Handle NDR report's non-recipient table
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ------------------------------------------------------------
HRESULT CIPMConvert::HrConvertNonRecipients()    // RETURNS: HRESULT
{
    HRESULT     hr                  =   NOERROR;
    LPSRowSet   lpRows              =   NULL;       // recipient table rows pointer
    ULONG       iRowCount           =   0;          // row counter

    // temporary string buffer
    TCHAR       szTempBuffer[ulMaxOutStringLen + 1]    =   TEXT("");

    // Columns needed from the report's recipient table
    SizedSPropTagArray(4, sRecipProps)  =
    {
        4,      // number of properties (columns) desired
        {
            PR_EMAIL_ADDRESS,   // non-recipient Email address
            PR_REPORT_TEXT,     // report text
            PR_NDR_DIAG_CODE,   // diagnostic failure code
            PR_NDR_REASON_CODE  // reason failure code
        }
    };

    // indices into the property values array
    const UINT  iRecipient      =   0;  // non-recipient email address
    const UINT  iText           =   1;  // report text
    const UINT  iDiagCode       =   2;  // diagnostic reason code
    const UINT  iNDRCode        =   3;  // non-delivery reason code

    DEBUGPRIVATE("CIPMConvert::HrConvertNonRecipients()\n");

    // Retrieve the rows from the NDR's recipient table
    hr = HrGetRecipientList(
                m_lpEnvelope,   // report pointer
                (LPSPropTagArray) &sRecipProps, // columns to retrieve
                &lpRows);       // pointer to rows array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Make sure that we have at least one non-recipient
    if ( lpRows->cRows == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Process each non-recipient
    for ( iRowCount = 0; iRowCount < lpRows->cRows;
          iRowCount++ )
    {
        // Emit the "Your message was not delivered to" tag and
        // data line.
        hr = HrEmitTagDataLine(
                lpszTagNotSentTo,   // tag
                lpRows->aRow[iRowCount].lpProps[iRecipient].Value.LPSZ, // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Emit the "for the following reason" tag and data line.
        hr = HrEmitTagDataLine(
                lpszTagReason,      // tag
                lpRows->aRow[iRowCount].lpProps[iText].Value.LPSZ, // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Format the diagnostic code
        wsprintf(
            szTempBuffer,
            TEXT("%ld"),
            lpRows->aRow[iRowCount].lpProps[iDiagCode].Value.ul);

        // Emit the "Diagnostic code" tag and data line.
        hr = HrEmitTagDataLine(
            lpszTagDiagCode,        // tag
            szTempBuffer,           // data
            m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Format the NDR reason code
        wsprintf(
            szTempBuffer,
            TEXT("%ld"),
            lpRows->aRow[iRowCount].lpProps[iNDRCode].Value.ul);

        // Emit the "Diagnostic reason" tag and data line.
        hr = HrEmitTagDataLine(
            lpszTagNDRCode,         // tag
            szTempBuffer,           // data
            m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // done with this recipient

    }   // end for

cleanup:

    // Free MAPI buffers
    FREEPROWS(lpRows);

    RETURN(hr);

}

//$--CIPMConvert::HrConvertRecipients--------------------------
//
// DESCRIPTION: Handle DR report's recipient table
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ------------------------------------------------------------
HRESULT CIPMConvert::HrConvertRecipients()    // RETURNS: HRESULT
{
    HRESULT     hr                  =   NOERROR;
    LPSRowSet   lpRows              =   NULL;       // recipient table rows pointer
    ULONG       iRowCount           =   0;          // row counter
    LPTSTR      lpTempString        =   NULL;       // temporary string pointer

    // Columns needed from the report's recipient table
    SizedSPropTagArray(2, sRecipProps)  =
    {
        2,      // number of properties (columns) desired
        {
            PR_EMAIL_ADDRESS,   // non-recipient Email address
            PR_DELIVER_TIME     // delivery time
        }
    };

    // indices into the property values array
    const UINT  iRecipient      =   0;  // recipient email address
    const UINT  iTime           =   1;  // delivery time

    DEBUGPRIVATE("CIPMConvert::HrConvertRecipients()\n");

    // Retrieve the rows from the DR's recipient table
    hr = HrGetRecipientList(
                m_lpEnvelope,   // report pointer
                (LPSPropTagArray) &sRecipProps, // columns to retrieve
                &lpRows);       // pointer to rows array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Make sure that we have at least one recipient
    if ( lpRows->cRows == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;

}

    for ( iRowCount = 0; iRowCount < lpRows->cRows;
          iRowCount++ )
    {
        // Emit the "Your message was successfully delivered to"
        // tag and data line.
        hr = HrEmitTagDataLine(
                    lpszTagSentTo,      // tag
                    lpRows->aRow[iRowCount].lpProps[iRecipient].Value.LPSZ, // data
                    m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Create the delivery time string
        hr = HrCreateDateTimeString(
                    &(lpRows->aRow[iRowCount].lpProps[iTime].Value.ft),   // delivery time
                    &lpTempString);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Emit the "at" time tag and data line.
        hr = HrEmitTagDataLine(
                    lpszTagAtTime,      // tag
                    lpTempString,       // data
                    m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Free temporary buffer
        MAPIFREEBUFFER(lpTempString);

        // We are done with this recipient

    }   // end for

cleanup:

    // Free MAPI buffers
    FREEPROWS(lpRows);

    MAPIFREEBUFFER(lpTempString);

    RETURN(hr);

}

//$--CIPMConvert::HrConvertNotifyContent--------------------------
//
// DESCRIPTION: Handles read and non-read notification report's content
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ------------------------------------------------------------
HRESULT CIPMConvert::HrConvertNotifyContent()    // RETURNS: HRESULT
{
    HRESULT         hr              =   NOERROR;
    LPTSTR          lpTempString    =   NULL;   // temporary string pointer
    LPSRowSet       lpRecipRows     =   NULL;   // recipient table rows pointer
    LPTRACEINFO     lpTraceInfo     =   NULL;   // trace information structure pointer
    ULONG           iLoop           =   0;      // loop index
    LPTRACEENTRY    lpTraceEntry    =   NULL;   // trace entry pointer

    PINTTRACEINFO   lpIntTraceInfo  =   NULL;   // trace information structure pointer
    PINTTRACEENTRY  lpIntTraceEntry =   NULL;   // trace entry pointer

    // temporary string buffer
    TCHAR           szTempBuf[ulMaxOutStringLen + 1]   =   TEXT("");

    // common notification property indices
    const UINT  iClass       =    0;  // index of message class value
    const UINT  iFromAddr    =    1;  // index of sender address
    const UINT  iXID         =    2;  // index of X-message identifier
    const UINT  iSentTime    =    3;  // index of sent time value
    const UINT  iImportance  =    4;  // index of importance value
    const UINT  iPriority    =    5;  // index of priority value
    const UINT  iSubject     =    6;  // index of the subject value
    const UINT  iSubjectXID  =    7;  // index of X-message subject ID
    const UINT  iReportTime  =    8;  // index of report time

    // Number of read-notification properties
    const ULONG nRNProps     =    9;

    // properties unique to non read notifications
    const UINT  iDiscardCode =    9;  // PR_DISCARD_REASON
    const UINT  iNonRcptCode =    10; // PR_NON_RECEIPT_REASON

    // number of non-read notification content properties
    const ULONG     nNRNProps    =   11;

    // Properties needed from the NRN report's content
    SizedSPropTagArray(nNRNProps, sPropsNotify)  =
    {
        nNRNProps,   // number of properties (columns) desired
        {
            PR_MESSAGE_CLASS,       // report class property ID
            PR_SENDER_EMAIL_ADDRESS,// content's sender's email address, index iFromAddr
            PR_SEARCH_KEY,          // dummy--acutally use PR_MESSAGE_SUBMISSION_ID on from
                                    // the envelope
            PR_CLIENT_SUBMIT_TIME,  // content's sent time, index iSentTime
            PR_IMPORTANCE,          // report importance
            PR_PRIORITY,            // report priority
            PR_SUBJECT,             // report subject
            // Notifications use PR_PARENT_KEY instread of
            // PR_MTS_SUBJECT_ID for the X-message subject identifier
            PR_PARENT_KEY,          // X-message-subject identifier
            PR_REPORT_TIME,         // report time
            PR_DISCARD_REASON,      // reason why message was discarded
            PR_NON_RECEIPT_REASON   // reson why message wansn't read
        }
    };

    // indices used into envelope property array
    const UINT  iTrace       =    3;  // PR_TRACE_INFO
    const UINT  iIntTrace    =    4;  // PR_INTERNAL_TRACE_INFO

    // Columns needed from the report content's recipient table
    SizedSPropTagArray(1, sRecipProps)  =
    {
        1,      // number of properties (columns) desired
        {
            PR_EMAIL_ADDRESS   // notification "TO" Email address
        }
    };

    // indices into the recipient table property values
    const UINT  iRecipient  =   0;  // PR_EMAIL_ADDRESS

    DEBUGPRIVATE("CIPMConvert::HrConvertNotifyContent()\n");

    // consistency checking
    ASSERTERROR((m_MsgType == mtNRN || m_MsgType == mtRN),
                "Bad m_MsgType");

    // Open the envelope's content.
    hr = HrOpenContent();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    if ( m_MsgType == mtRN )
    {
        // Adjust number of properties to retrieve for read notifications.
        sPropsNotify.cValues = nRNProps;
    }

    // Retrieve the desired properties from the NRN or RN's
    // content
    hr = HrRetrieveProps(
                m_lpContent,    // report pointer
                (LPSPropTagArray) &sPropsNotify,  // Properties to retrieve
                &m_lpCntProps); // pointer to property array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "X-Message-Class:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagMsgClass,        // tag
            m_lpCntProps[iClass].Value.LPSZ,  // report class
            m_lpStream);          // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the date and time string
    hr = HrCreateDateTimeString(
                  &(m_lpCntProps[iSentTime].Value.ft),  // PR_CLIENT_SUBMIT_TIME property value
                  &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Date:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                lpszTagDate,
                lpTempString,
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Print out the "X-Message-ID:" tag and data line to the stream
    // The X-Message ID string is null terminated.
    // Note:  The PR_MESSAGE_SUBMISSION_ID is really on the
    // envelope.
    hr = HrEmitTagDataLine(
            lpszTagMsgID,
            (LPTSTR) m_lpEnvProps[iXID].Value.bin.lpb,   // really a null-terminated string
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "X-Message-Subject-ID:" tag and data line to the stream
    // The X-Message ID string is already null terminated.
    // This is the binary PR_PARENT_KEY property for notifications,
    // so we shall need to convert it to ASCII.

    // Check that we have enough room in our output buffer.
    ASSERTERROR((m_lpCntProps[iSubjectXID].Value.bin.cb * 2 <= ulMaxOutStringLen),
                "Bad cbParentKey");
    HexFromBin(
            m_lpCntProps[iSubjectXID].Value.bin.lpb,    // binary data
            m_lpCntProps[iSubjectXID].Value.bin.cb,     // # bytes of binary data
            szTempBuf);                                 // string buffer

    ASSERTERROR(!IsBadStringPtr(szTempBuf, INFINITE), "Bad szTempBuf");

    hr = HrEmitTagDataLine(
            lpszTagSubjectID,       // tag
            szTempBuf,              // data
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the trace information, if any.
    if ( PROP_TYPE(m_lpEnvProps[iTrace].ulPropTag) != PT_ERROR )
    {
        // We have valid trace information
        lpTraceInfo = (LPTRACEINFO) (m_lpEnvProps[iTrace].Value.bin.lpb);

        // Print out trace entries
        for ( iLoop = 0; iLoop < lpTraceInfo->cEntries; iLoop++ )
        {
            // retrieve trace entry.
            lpTraceEntry = &(lpTraceInfo->rgtraceentry[iLoop]);

            // Create "External-Received-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,          // trace action
                lpTraceEntry->rgchCountryName,  // country name
                lpTraceEntry->rgchADMDName,     // ADMD name
                lpTraceEntry->rgchPRMDId,       // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Received-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "External-Attempted-By" string
            hr = HrCreateExternalTraceString(
                lpTraceEntry->lAction,              // trace action
                lpTraceEntry->rgchAttCountryName,   // country name
                lpTraceEntry->rgchAttADMDName,      // ADMD name
                lpTraceEntry->rgchAttPRMDId,        // PRMD identifier
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "External-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagExternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "External-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out External-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagExternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for each trace entry
    }   // end if trace information

    // Print out the internal trace information, if any.
    if ( PROP_TYPE(m_lpEnvProps[iIntTrace].ulPropTag) != PT_ERROR )
    {
        // We have valid trace information
        lpIntTraceInfo = (PINTTRACEINFO) (m_lpEnvProps[iIntTrace].Value.bin.lpb);

        // Print out trace entries
        for ( iLoop = 0; iLoop < lpIntTraceInfo->cEntries; iLoop++ )
        {
            // retrieve trace entry.
            lpIntTraceEntry = &(lpIntTraceInfo->rgIntTraceEntry[iLoop]);

            // Create "Internal-Received-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,          // trace action
                lpIntTraceEntry->rgchCountryName,  // country name
                lpIntTraceEntry->rgchADMDName,     // ADMD name
                lpIntTraceEntry->rgchPRMDId,       // PRMD identifier
                lpIntTraceEntry->rgchMTAName,      // MTA name
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Received-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Received-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftArrivalTime), // trace arrival time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Received-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalRcvdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Create "Internal-Attempted-By" string
            hr = HrCreateInternalTraceString(
                lpIntTraceEntry->lAction,              // trace action
                lpIntTraceEntry->rgchAttCountryName,   // country name
                lpIntTraceEntry->rgchAttADMDName,      // ADMD name
                lpIntTraceEntry->rgchAttPRMDId,        // PRMD identifier
                lpIntTraceEntry->rgchAttMTAName,       // MTA name
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out the "Internal-Attempted-By:" tag and data line to the stream.
            hr = HrEmitTagDataLine(
                lpszTagInternalAttmBy,
                lpTempString,
                m_lpStream);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

            // Build "Internal-Deferred-At" date and time string
            hr = HrCreateDateTimeString(
                &(lpIntTraceEntry->ftDeferredTime), // trace deferal time
                &lpTempString);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            // Print out Internal-Deferred-At data and time string
            hr = HrEmitTagDataLine(
                lpszTagInternalDefdAt,  // tag
                lpTempString,       // data
                m_lpStream);        // stream

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

            MAPIFREEBUFFER(lpTempString);

        }   // end for each internal trace entry
    }   // end if internal trace information

    // Print out the "From:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagFrom,     // tag
            m_lpCntProps[iFromAddr].Value.LPSZ,    // report creator
            m_lpStream);                // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build the "TO:" tag and data line from the
    // content's recipient table.
    hr = HrGetRecipientList(
            m_lpContent,        // MAPI message content pointer
            (LPSPropTagArray) &sRecipProps,        // property columns desired array
            &lpRecipRows);      // Rows retrieved from recipient table

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // There should only be one "TO" for a read or non-read
    // notification, since an IPM.NOTE message can have only one
    // sender.
    if ( lpRecipRows->cRows != 1 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Print out the "TO:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
            lpszTagTo,     // tag
            lpRecipRows->aRow[0].lpProps[iRecipient].Value.LPSZ,    // report destination
            m_lpStream);                // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out a blank line
    hr = m_lpStream->Write(
            lpszNewLine,            // data
            lstrlen(lpszNewLine) * sizeof(TCHAR),   // length (no terminator)
            NULL);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Print out the "Subject:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagSubject,                // tag
            m_lpCntProps[iSubject].Value.LPSZ,  // PR_SUBJECT property value
            m_lpStream);                    // stream

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Build priority string.
    hr = HrCreatePriorityString(
                m_lpCntProps[iPriority].Value.ul,  // PR_PRIORITY property value
                &lpTempString);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Priority:" tag and data line to the stream
    hr = HrEmitTagDataLine(
            lpszTagPriority,
            lpTempString,
            m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // Build the importance string
    hr = HrCreateImportanceString(
                m_lpCntProps[iImportance].Value.ul,    // PR_IMPORTANCE property value
                &lpTempString);                             // buffer pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Print out the "Importance:" tag and data line to the stream.
    hr = HrEmitTagDataLine(
                lpszTagImportance,
                lpTempString,
                m_lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTempString);

    // The output for the two notifications diverges
    // here.
    if ( m_MsgType == mtNRN )    // non-read notification
    {
        // Emit the "Your message was not read by" tag
        // and data line
        hr = HrEmitTagDataLine(
                lpszTagNotReadBy,   // tag
                m_lpCntProps[iFromAddr].Value.LPSZ,    // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Create the report time string.
        hr = HrCreateDateTimeString(
            &(m_lpCntProps[iReportTime].Value.ft),    // time
            &lpTempString);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Emit the "at" tag and data line
        hr = HrEmitTagDataLine(
                lpszTagAtTime,      // tag
                lpTempString,       // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // free temporary string buffer
        MAPIFREEBUFFER(lpTempString);

        // Format the discard code data
        wsprintf(
                szTempBuf,      // buffer
                TEXT("%ld"),    // format string
                m_lpCntProps[iDiscardCode].Value.ul);   // discard code

        // Emit the "Discard code" tag and data line.
        hr = HrEmitTagDataLine(
                lpszTagDiscCode,    // tag
                szTempBuf,          // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Format the non-receipt code data
        wsprintf(
                szTempBuf,      // buffer
                TEXT("%ld"),    // format string
                m_lpCntProps[iNonRcptCode].Value.ul);   // code

        // Emit the "Non-receipt code" tag and data line
        hr = HrEmitTagDataLine(
                lpszTagNRNCode,     // tag
                szTempBuf,          // buffer
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

    }   // end if NRN

    else if ( m_MsgType == mtRN )    // read notification
    {
        // Emit the "Your message was read by" tag and
        // data line.
        hr = HrEmitTagDataLine(
                lpszTagReadBy,          // tag
                m_lpCntProps[iFromAddr].Value.LPSZ,    // data
                m_lpStream);

        // Create the receipt time string.
        hr = HrCreateDateTimeString(
            &(m_lpCntProps[iReportTime].Value.ft),    // time
            &lpTempString);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Emit the "at" tag and data line
        hr = HrEmitTagDataLine(
                lpszTagAtTime,      // tag
                lpTempString,       // data
                m_lpStream);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // free temporary string buffer
        MAPIFREEBUFFER(lpTempString);
    }

    else
    {
        // shouldn't happen
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done.

cleanup:

    // Free MAPI buffers
    FREEPROWS(lpRecipRows);

    MAPIFREEBUFFER(lpTempString);

    RETURN(hr);

}


CONVTNEF.CPP

// --convtnef.cpp--------------------------------------------------------------
//
// Code for messages whose content is to be TNEFed.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All Rights Reserved.
// All rights reserved.
//
// ----------------------------------------------------------------------------

#include "edk.h"
#include "convcls.h"
#include "tagnames.h"
#include "msgemit.h"
#include "ipmcdefs.h"

//$--CIPMConvert::HrContentToTnef---------------------------------------------------------------
//
// DESCRIPTION: TNEF encodes everything below the envelope.
// (e.g. the content and its attachments)
//
// INPUT:       none
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrContentToTnef()
{
    HRESULT             hr          =   NOERROR;
    LPITNEF             lpTnef      =   NULL;   // TNEF interface pointer
    WORD                wKey        =   0;      // attachments key
    LPSPropTagArray     lpsPropTags =   NULL;   // property tag array pointer
    ULONG               ulPropTag   =   0;      // property tag
    ULONG               iPropTag    =   0;      // index into the property tag array
    ULONG               cExclude    =   0;      // count of properties to exclude
    LPSTnefProblemArray lpsProblems =   NULL;   // problem array pointer

    // Want to include the recipient properites in the TNEF data.
    SizedSPropTagArray(1, sRecipPropTag)    =
    {
        1,
        {
            PR_MESSAGE_RECIPIENTS
        }
    };

    DEBUGPRIVATE("CIPMConvert::HrContentToTnef()\n");

    // consistency checks
    ASSERTERROR(!FBadUnknown(m_lpStream), "Bad m_lpStream");
    ASSERTERROR(!FBadUnknown(m_lpAB), "Bad m_lpAB");
    ASSERTERROR(!FBadUnknown(m_lpEnvelope), "Bad m_lpEnvelope");

    // TNEF the envelope's content and all of its attachments.

    // First, write out the "MAPIMAIL.DAT" TNEF section header
    // to the output stream.
    hr = HrEmitTagDataLine(
                lpszTagTnefHdr,     // tag
                (LPTSTR) lpszNullData,  // data
                m_lpStream);        // stream pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Next, open a TNEF interface on the envelope which writes its
    // output to the output stream.
  hr = OpenTnefStreamEx(
            NULL,
        m_lpStream,         // output stream pointer
        (LPTSTR) szTnefFileName,    // file name associated with TNEFed data
    TNEF_ENCODE,        // encode the message properties
    m_lpEnvelope,       // source message pointer
    wTnefKey,           // "unique" TNEF key
    m_lpAB,             // address book pointer
    &lpTnef);           // TNEF interface object pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(lpTnef), "Bad lpTnef");

    // Encode all transmittable properties for the envelope content and
    // its attachments to the TNEFed stream.
    // First, get a list of all properties on the source message.
    hr = m_lpEnvelope->GetPropList(
            fMapiUnicode,       // MAPI flags
            &lpsPropTags);

    if ( FAILED(hr) )   // don't care about MAPI_W_ERRORS_RETURNED in this case
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!IsBadReadPtr(lpsPropTags, sizeof(LPSPropTagArray)),
                "Bad lpsPropTags.");

    // Now, build a new list of non-transmittable properties
    // which we wish to exclude from the TNEF data.
    for ( iPropTag = 0; iPropTag < lpsPropTags->cValues; iPropTag++ )
    {
        ulPropTag = lpsPropTags->aulPropTag[iPropTag];

        // See if we have a property which is transmittable, but
        // we still wish to exclude (gateway can't set some
        // transmittable properties)
  if ((ulPropTag == PR_SENT_REPRESENTING_NAME) ||
   (ulPropTag == PR_SENT_REPRESENTING_ADDRTYPE) ||
   (ulPropTag == PR_SENT_REPRESENTING_EMAIL_ADDRESS) ||
   (ulPropTag == PR_SENT_REPRESENTING_SEARCH_KEY) ||
   (ulPropTag == PR_SENT_REPRESENTING_ENTRYID))
        {
            lpsPropTags->aulPropTag[cExclude] = ulPropTag;
            cExclude++;

        }   // end property transmittable but not wanted

        // See if we have a non-transmittable property which we
        // really wish to exclude
   else if ((!FIsTransmittable(ulPropTag)) &&
    (ulPropTag != PR_TRACE_INFO) &&
    (ulPropTag != PR_MTS_SUBJECT_ID) &&
    (ulPropTag != PR_ORIGINATOR_NAME) &&
    (ulPropTag != PR_ORIGINATOR_ADDRTYPE) &&
    (ulPropTag != PR_ORIGINATOR_ADDR) &&
    (ulPropTag != PR_ORIGINATOR_ENTRYID) &&
    (ulPropTag != PR_REPORT_DESTINATION_NAME) &&
    (ulPropTag != PR_REPORT_DESTINATION_ENTRYID) &&
    (ulPropTag != PR_MESSAGE_RECIPIENTS) &&
    (ulPropTag != PR_MESSAGE_ATTACHMENTS))
        {
            lpsPropTags->aulPropTag[cExclude] = ulPropTag;
            cExclude++;

        }   // end if property is non-transmittable & not wanted
    }   // end for

    // Exclude the list of properties just built from the TNEFed
    // data.
    lpsPropTags->cValues = cExclude;    // # of properties to exclude
    hr = lpTnef->AddProps(
            TNEF_PROP_EXCLUDE | // wish to exclude these properties
            TNEF_PROP_ATTACHMENTS_ONLY, // only want to encode the attachments
            0,              // attachemnt ID (not used)
            NULL,           // no special attachment data
            lpsPropTags);   // property tag array pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Include the message recipient properties.
    hr = lpTnef->AddProps(
            TNEF_PROP_INCLUDE | // wish to include these properties
            TNEF_PROP_ATTACHMENTS_ONLY, // only want to encode the attachments
            0,              // attacment ID (not used)
            NULL,           // special attachment data
            (LPSPropTagArray) &sRecipPropTag);  // property tag array pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Save the TNEFed information to the output stream.
    hr = lpTnef->Finish(
                0,      // flags
                &wKey,  // attachments key
                &lpsProblems);  // problem array pointer

    if ( FAILED(hr) )   // don't care about MAPI_W_ERRORS_RETURNED in this case
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    // Release all OLE and MAPI objects
    ULRELEASE(lpTnef);

    // Free MAPI buffers
    MAPIFREEBUFFER(lpsProblems);
    MAPIFREEBUFFER(lpsPropTags);

    RETURN(hr);

}

//$--CIPMConvert::HrOpenContent------------------------------------------------
//
// DESCRIPTION: Utility function to open the envelope's content.
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CIPMConvert::HrOpenContent()
{
    HRESULT         hr              =   NOERROR;

    DEBUGPRIVATE("CIPMConvert::HrOpenContent()\n");

    // consistency checks
    ASSERT_IUNKNOWN_PTR(m_lpEnvelope, "Bad m_lpEnvelope");
    ASSERTERROR(m_lpAttach == NULL, "MEMORY LEAK of m_lpAttach");
    ASSERTERROR(m_lpContent == NULL, "MEMORY LEAK of m_lpContent");

    // Open the message content which is attached to the envelope.
    // The content's attachment number is always 0.
    hr = m_lpEnvelope->OpenAttach(
        0,      // attachment #
        NULL,   // interface (whatever is appropriate)
        MAPI_DEFERRED_ERRORS,   // reduces RPCs
        &m_lpAttach);         // attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(m_lpAttach, "Bad m_lpAttach");

    // Open the attachment as a message.
    hr = m_lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ,
                                    &IID_IMessage,  // interface identifier
                                    0,              // interface flags
                                    MAPI_DEFERRED_ERRORS,   // reduces RPCs
                                    (LPUNKNOWN *) &m_lpContent);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(m_lpContent, "Bad m_lpContent");

cleanup:

    // m_lpContent & m_lpAttachment are released by Reset().

    RETURN(hr);

}


IPM_OUT.CPP

// --ipm_out.cpp--------------------------------------------------------------
//
// API entry points for the ipm_out dynamic link library.
// Converts MAPI messages to an 822-style header.
// This is the "Outbound IPM Converter" refered to in "Sample Gateway
// Vision, Requirements, Architecture and File Formats".
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "ipmconv.h"
#include "msgemit.h"
#include "convcls.h"
#include "ipm_out.chk"

// GLOBAL VARIABLES

// Thread local storage slot index.
// This storage will be used to store the conversion class instance pointer
// for each thread.
static DWORD   dwTlsIndex   =   0xFFFFFFFF;

// external function declarations

// The DLL entry point extern "C" function declaration and
// "C" run-time library initialization extern "C" function declarations.
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE hInstDll, DWORD fdwReason,
                                     LPVOID lpvReserved);
extern "C" BOOL WINAPI _CRT_INIT(HINSTANCE hInstDll, DWORD fdwReason,
                                 LPVOID lpvReserved);

//$--HrCnvQueryCapability---------------------------------------------------------
//
// DESCRIPTION: Determines whether or not a particular conversion can be
//              done.  The only conversion allowed is from
//              a MAPI message of class ENVELOPE.IPM.NOTE or
//              REPORT.IPM.NOTE to a 822-style header
//              which is written an output stream.
//
// INPUT:       pszOptions  --  (not used)
//              pszClass    --  class of message to convert
//              pContent    --  MAPI message to convert
//              pEnv    --  conversion environment pointer
//
// OUTPUT:      pfAmCandidate   --  TRUE if can do conversion, FALSE otherwise
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//
// ---------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
        IN LPCWSTR pszOptions,  // conversion options
        IN LPCWSTR pszClass,    // message class to convert
        IN PVOID pContent,      // message to convert (candidate)
        IN PEDKCNVENV pEnv,     // conversion environment pointer
        OUT BOOL * pfAmCandidate)   // TRUE if can convert, FALSE otherwise
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("HrCnvQueryCapability()\n");

    // check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass,
                                  pContent, pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize output variable.
    *pfAmCandidate = FALSE;

    // We can only convert message classes which contain ENVELOPE.IPM
    // or REPORT.IPM
    if ( (wcsstr(pszClass, IPMENVELOPECLASS) == NULL) &&
         (wcsstr(pszClass, IPMREPORTCLASS) == NULL) )
    {
        goto cleanup;
    }

    // If we get to here, we can convert the message
    *pfAmCandidate = TRUE;

cleanup:

    RETURN(hr);

}

//$--HrIPMto822Format--------------------------------------------------------
//
// DESCRIPTION: Converts a MAPI IPM message to a "822-style" stream output
//
// NOTES:   Only one conversion may be performed at
//          a time (per thread). In order to do concurrent conversions,
//          create multiple threads.
//
// INPUT:       lpwszClass  --  message class
//              lpAddrBook  --  address book pointer
//              lpMessage   --  Pointer to MAPI message.
//              bTNEFEncode --  Do TNEF encoding of message attachment flag
//              lpStream    --  Stream to write to
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_FAIL if API function call failure,
//                              E_NOTIMPL if requesting a feature not implemented
//                              or supported,
//                              EDK_E_ALREADY_EXISTS if a conversion is already
//                              in progress,
//
// ---------------------------------------------------------------------------

static HRESULT HrIPMto822Format(
                               IN LPCWSTR lpwszClass,     // message class
                               IN BOOL fTNEFEncode,       // TRUE is should encode attachments
                               IN LPADRBOOK lpAddrBook,   // address book pointer
                               IN LPMESSAGE lpMessage,    // pointer to MAPI message
                               IN LPSTREAM lpStream)      // pointer to stream
{
    HRESULT         hr              =   NOERROR;
    CIPMConvert *   lpCIPMConvert   =   NULL;   // thread-safe conversion class instance pointer
    BOOL            fInitialized    =   FALSE;  // TRUE if conversion instance already in use

    DEBUGPRIVATE("HrIPMto822Format()\n");

    // Check input parameters.
    hr = CHK_HrIPMto822Format(lpwszClass, fTNEFEncode,
                              lpAddrBook, lpMessage, lpStream);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ASSERTERROR(dwTlsIndex != 0xFFFFFFFF, "Bad thread storage index");

    // Retrieve the conversion class instance pointer for this class.
    lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

    if ( !lpCIPMConvert )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Initialize conversion instance
    hr = lpCIPMConvert->HrInitialize(lpwszClass, fTNEFEncode,
                                     lpAddrBook,
                                     lpMessage, lpStream);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Go ahead and do conversion.
    hr = lpCIPMConvert->HrConvert();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Note: The thread-safe conversion class instance is de-initialized
    // at thread or process termination.

cleanup:

    RETURN(hr);

}   // end HrIPMto822Format()

//$--HrCnvConvert---------------------------------------------------------
//
// DESCRIPTION: Converts MAPI ENVELOPOE.IPM.* or REPORT.IPM.*
//              to an 822-style header and writes
//              the header to the stream passed in.
//              This function is designed to be called on a per-thread
//              basis.  Each thread can handle one conversion at a time.
//
// INPUT:       pszOptions  --  (not used)
//              pszClass    --  class of message to convert
//                              (must contain ENVELOPE.IPM or
//                              REPORT.IPM)
//              pContent    --  MAPI message to convert
//              pContentOut --  Stream to write header to
//              pEnv    --  conversion environment pointer
//
// OUTPUT:      pcr --  pointer to conversion result enumeration
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if API call failure,
//                          E_OUTOFMEMORY if memory problems
//                          E_NOTIMPL if conversion not supported,
//                          EDK_E_ALREADY_EXISTS if busy
//
// ---------------------------------------------------------------------------
static HRESULT HrCnvConvert(
        IN LPCWSTR pszOptions,  // conversion options
        IN LPCWSTR pszClass,    // message class to convert
        IN PVOID pContent,      // message to convert (candidate)
        IN PVOID pContentOut,   // stream to write 822-style header to
        IN PEDKCNVENV pEnv,     // conversion environment pointer
        OUT EDKCNVRES * pcr)    // result of conversion
{
    HRESULT     hr          =   NOERROR;

    DEBUGPRIVATE("HrCnvConvert()\n");

    // check input parameters
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize output parameter
    *pcr = GCR_CONVERSION_FAILED;

    // We can only convert classes which contain ENVELOPE.IPM or
    // REPORT.IPM.
    if ( (wcsstr(pszClass, IPMENVELOPECLASS) == NULL) &&
         (wcsstr(pszClass, IPMREPORTCLASS) == NULL) )
    {
        *pcr = GCR_CANNOT_CONVERT;

        goto cleanup;
    }

    // Let our helper function do the work!
    hr = HrIPMto822Format(
        pszClass,                   // message class
        ((CONV_OPTIONS *) (pEnv->pGatewayDefined))->fTnefEncode,  // TNEF flag
        pEnv->lpAB,                 // address book pointer
        (LPMESSAGE) pContent,       // message to convert
        (LPSTREAM) pContentOut);    // stream to write to

    switch (hr)
    {
        case E_NOTIMPL:
            // don't support this conversion type
            *pcr = GCR_CANNOT_CONVERT;

            goto cleanup;

        default:
            if ( FAILED(hr) )
            {
                // conversion failed.
                *pcr = GCR_CONVERSION_FAILED;

                goto cleanup;
            }
    }   // end switch

    // if we get to here, then the conversion succeeded.
    *pcr = GCR_OK;

cleanup:

    RETURN(hr);

}

// structure containing pointers to exported functions
static CONVDLLVECT sExportedFuncs =
{
    nDesiredConvDllVersion,  // version number 1
    HrCnvQueryCapability,
    HrCnvConvert
};

//$--HrGetExportVector--------------------------------------------------------
//
// DESCRIPTION: Gets pointer to exported functions structure.
//
// INPUT:       dwVersion   --  version requested
//
// OUTPUT:      ppExportVectors --  pointer to export vector structure pointer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//
// ---------------------------------------------------------------------------

HRESULT HrGetExportVector(
        IN DWORD dwVersion,         // version requested
        OUT PCONVDLLVECT * ppExportVectors)  // pointer to exported functions structure
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrGetExportVector()\n");

    // check input parameters
    hr = CHK_HrGetExportVector(dwVersion, ppExportVectors);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // return pointer to exported vectors structure
    *ppExportVectors = &sExportedFuncs;

    RETURN(hr);

}

//$--DllEntryPoint------------------------------------------------------------
//
// DESCRIPTION: 32-bit Windows DLL entry point procedure.  Called on process
//              and thread initialization and deinitialization.
//              Takes care of calling _CRT_INIT to initialize the "C" run-time
//              library.
//
// INPUT:       hInstDll    --  DLL instance handle
//              fdwReason   --  Reason being called flag (PROCESS_ATTACH ...)
//              lpvReserved --  reserved
//
// RETURNS:     TRUE if successful, FALSE otherwise
//
// ---------------------------------------------------------------------------
BOOL WINAPI DllEntryPoint(                // RETURNS: BOOL
            IN HINSTANCE hInstDll,  // DLL instance handle
            IN DWORD fdwReason,     // reason this function is being called
            IN LPVOID lpvReserved)  // reserved (has static and dynamic call values)
{
    BOOL            fResult         =   TRUE;   // FALSE if can't initialize
    CIPMConvert *   lpCIPMConvert   =   NULL;   // Per-thread conversion class instance pointer

    // check to see why we were called.
    switch ( fdwReason )
    {
        case DLL_PROCESS_ATTACH:
            // Initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            if ( !fResult )
            {
                goto cleanup;
            }

            // Get the thread local storage index.
            dwTlsIndex = TlsAlloc();

            if ( dwTlsIndex == 0xFFFFFFFF )
            {
                // general failure
                fResult = FALSE;

                goto cleanup;
            }

            // Initialize a new conversion class.
            lpCIPMConvert = new CIPMConvert();

            if ( !lpCIPMConvert )
            {
                fResult = FALSE;

                goto cleanup;
            }

            // Store the new thread-safe conversion class in the
            // thread local storage slot.
            fResult = TlsSetValue(dwTlsIndex, (LPVOID) lpCIPMConvert);

            break;

        case DLL_THREAD_ATTACH:
            // Initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            if ( !fResult )
            {
                goto cleanup;
            }

            // Initialize a new conversion class.
            lpCIPMConvert = new CIPMConvert();

            if ( !lpCIPMConvert )
            {
                fResult = FALSE;

                goto cleanup;
            }

            // Store the new thread-safe conversion class in the
            // thread local storage slot.
            fResult = TlsSetValue(dwTlsIndex, (LPVOID) lpCIPMConvert);

            break;

        case DLL_THREAD_DETACH:
            // Get our thread-local conversion class instance pointer
            lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

            if ( lpCIPMConvert )
            {
                // free this conversion class instance.
                delete lpCIPMConvert;
            }

            // de-initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            break;

        case DLL_PROCESS_DETACH:
            // Get our thread-local conversion class instance pointer
            lpCIPMConvert = (CIPMConvert *) TlsGetValue(dwTlsIndex);

            if ( lpCIPMConvert )
            {
                // free this conversion class instance.
                delete lpCIPMConvert;
            }

            // Free our thread-local storage index
            (VOID)TlsFree(dwTlsIndex);

            // de-initialize the "C" run-time libarary.
            fResult = _CRT_INIT(hInstDll, fdwReason, lpvReserved);

            break;

    }   // end switch

cleanup:

    return fResult;

}


ACLCLS.CPP

// --aclcls.cpp-------------------------------------------------------------
//
//  API entry points into the ACL Class Library (aclcls).
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"
#include "aclclsf.h"

#include "aclcls.chk"

//
// Functions
//

// $--HrFolderACLsOpen-----------------------------------------------------
//
// DESCRIPTION:Get a ptr to an object which implements the IExchangeFolderACLs
//interface defined in aclcls.h.
//
// INPUT:
//
//  [lpSession]         -- Pointer to MAPI session.
//  [lpMDB]-- Ptr to message store containing folder.
//  [cbentryid]-- Number of bytes in folder's entry identifier.
//  [lpentryid]-- Folder's entry identifier.
//
// OUTPUT:
//
//  [lppFolderACLs]-- Ptr to object which supports interface;
//   NULL if none.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif not enough memory;
//E_NOINTERFACEif acl table does not exist on folder;
//              E_FAILotherwise.
//
//-----------------------------------------------------------------------------

STDAPI
HrFolderACLsOpen(// RETURNS: HRESULT
    IN      LPMAPISESSION       lpSession,      // MAPI session pointer
    INLPMDBlpMDB,// MAPI MDB store ptr
INULONGcbentryid,// # bytes in entry ID
INLPENTRYIDlpentryid,// entry ID ptr
OUTLPFOLDERACLS FAR *lppFolderACLs// IExchangeFolderACLs ptr ptr
)
{
    HRESULThr =NOERROR;
    CFolderACLs FAR *pFolderACLs =NULL;

    DEBUGPUBLIC("HrFolderACLsOpen().\n");

    hr = CHK_HrFolderACLsOpen(cbentryid, lpentryid, lppFolderACLs);

    if (FAILED(hr))
        RETURN(hr);

    *lppFolderACLs = NULL;

    // Initialize controlling class object.

    pFolderACLs = new CFolderACLs();

if (pFolderACLs == NULL)
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

// Open the folder in the controlling class object.

hr = pFolderACLs->HrOpen(lpSession, lpMDB, cbentryid, lpentryid);

if (FAILED(hr))
goto cleanup;

    // Give the user access to the programmer interface (CIExchangeFolderACLs)
    // on the controlling CFolderACLs object.

    hr = pFolderACLs->QueryInterface(IID_IExchangeFolderACLs,
 (LPVOID FAR *)lppFolderACLs);
    // Ref count now 2.

    if (FAILED(hr))
        goto cleanup;

    ASSERT_READ_PTR_OR_NULL(*lppFolderACLs, sizeof(LPFOLDERACLS),
                             "Bad lppFolderACLs.");

cleanup:

    // Release our copy of the CFolderACLs object.

    ULRELEASE(pFolderACLs);// Ref count now 1.

    RETURN(hr);
}


ACLCLSF.CPP

// --aclclsf.cpp---------------------------------------------------------------
//
//  Implementation file for the CFolderACLs control class and its
//class factory.
//
//  The CFolderACLs class is a controlling class.  It controls the
//  CIExchangeFolderACLs class (which implements the IExchangeFolderACLs
//  programatic interface defined in aclcls.h).  When we add automation
//  capabilities, it will also control the standard OLE dispatch interface
//  for this class.
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"
#include "aclclsf.h"
#include "aclclsf.chk"

// $--CFolderACLs::CFolderACLs-------------------------------------------------
//
// DESCRIPTION:CFolderACLs controlling class class constructor.
//
// INPUT:None.
//
// RETURNS:     Ptr to new CFolderACLs instance.
//
//-----------------------------------------------------------------------------

CFolderACLs::CFolderACLs()
{
    DEBUGPRIVATE("CFolderACLs::CFolderACLs().\n");

    m_hr =NOERROR;    // no error so far
    m_refs =1;
m_lpMapiTbl =NULL;
m_lpExchTbl =NULL;
m_lpFolder =NULL;

    m_prog_interface = new CIExchangeFolderACLs;

    if (m_prog_interface == NULL)
    {
        m_hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Set programming interface ptr to this object.

    m_prog_interface->m_pFA = this;

    m_lpSession = NULL;

cleanup:

    ;   // no operation
}

// $--CFolderACLs::~CFolderACLs------------------------------------------------
//
// DESCRIPTION:CFolderACLs controlling class class destructor.
//
// INPUT:None.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

CFolderACLs::~CFolderACLs()
{
    DEBUGPRIVATE("CFolderACLs::~CFolderACLs().\n");

delete m_prog_interface;

    ULRELEASE(m_lpSession);
ULRELEASE(m_lpMapiTbl);
ULRELEASE(m_lpExchTbl);
ULRELEASE(m_lpFolder);
}

// $--CFolderACLs::Create------------------------------------------------------
//
// DESCRIPTION:Create a new instance of the CFolderACLs object.
//Also take care of setting up the standard OLE dispatch
//object ptr for the new instance.
//
// INPUT:None.
//
// RETURNS:     Ptr to new CFolderACLs object on success; NULL otherwise.
//
//-----------------------------------------------------------------------------

CFolderACLs FAR *
CFolderACLs::Create()// RETURNS CFolderACLs ptr
{
    CFolderACLs FAR *pFolderACLs =NULL;

    DEBUGPRIVATE("CFolderACLs::Create().\n");

    // Create an instance of CFolderACLs.

    pFolderACLs = new FAR CFolderACLs();

    if (pFolderACLs == NULL)
    {
        HR_LOG(E_FAIL);
        pFolderACLs = NULL;
        goto cleanup;
    }

    if (FAILED(pFolderACLs->m_hr))
    {
        HR_LOG(pFolderACLs->m_hr);
        ULRELEASE(pFolderACLs);
        goto cleanup;
    }

cleanup:

    return pFolderACLs;
}

// $--CFolderACLs::HrGetTableEntry---------------------------------------------
//
// DESCRIPTION:Get a IExchangeModifyTable ACL table entry using the entryid.
//
// INPUT:
//
//   [cbentryid]--  Count of bytes in member entry ID.
//   [lpentryid]--  Ptr to member entry ID.
//
// OUTPUT:
//
//   [lppszDisplayName]--  Member display name buffer (set to NULL if not
//wanted).
//   [lplRights]--  ACL rights bits buffer.
//
// RETURNS:     NOERRORon success;
//EDK_E_NOT_FOUNDif not found;
//              E_FAILotherwise.
//
// Notes:We use this method to examine the table entries to see if a
//requested action was actually completed.  IExchangeModifyTable
//code may reset rights, readd entries, etc., so we have to check.
//-----------------------------------------------------------------------------

HRESULT
CFolderACLs::HrGetTableEntry(// RETURNS: HRESULT
INULONGcbentryid,// # of bytes in entry ID
INLPENTRYIDlpentryid,// entry ID ptr
OUTLPSTR FAR *lppszDisplayName,// display name ptr
OUTLPLONGlplRights// rights ptr
)
{
HRESULThr =NOERROR;
ULONGi =0;
LPMAPITABLElpMapiTbl =NULL;
LPSRowlpRow =NULL;
LPSRowSetlpRows =NULL;
    LPADRBOOK       lpAdrBook =     NULL;
    ULONG           ulResult =      FALSE;

// NOTE: The following order of properties is assumed by lots
//       of other code (CFolderACLs, CIExchangeFolderACLs).

    SizedSPropTagArray(C_ACLPROPS, rgPropTag) =
    {
    C_ACLPROPS,
    {
    PR_MEMBER_ENTRYID,
    PR_MEMBER_RIGHTS,
    PR_MEMBER_NAME
    }
    };

    DEBUGPRIVATE("CFolderACLs::HrGetTableEntry()\n");

if (lppszDisplayName != NULL)
*lppszDisplayName = NULL;

    hr = m_lpSession->OpenAddressBook(
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if (FAILED(hr))
    {
        goto cleanup;
    }

    // Open a MAPI table on the ACL table property.  This table can be
// read to determine what the ACLs table looks like.

    hr = m_lpExchTbl->GetTable(0, &lpMapiTbl);

    if (FAILED(hr))
    {
        goto cleanup;
    }

// Select columns needed.

hr = lpMapiTbl->SetColumns((LPSPropTagArray)&rgPropTag, 0);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Get all the rows in the table up to EDK_MAX_QUERY_ROWS.  If there
// are more rows than this, fail with MAPI_E_TABLE_TOO_BIG.

hr = lpMapiTbl->QueryRows(EDK_MAX_QUERY_ROWS, 0, &lpRows);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Check to be sure that table is not bigger than EDK_MAX_QUERY_ROWS.
// We do this by checking whether or not the cursor is positioned at
// the end of the table.

if (lpRows->cRows == EDK_MAX_QUERY_ROWS)
{
ULONGulRow =0;
ULONGulNumerator =0;
ULONGulDenominator =0;

hr = lpMapiTbl->QueryPosition(&ulRow, &ulNumerator, &ulDenominator);

if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
goto cleanup;
        }

// SeekRowApprox() doc states that if ulNumerator == ulDenominator, you
// are at the end of the table.

if (ulNumerator != ulDenominator)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Search for the entry and return values.

for (i = 0; i < lpRows->cRows; i++)
{
lpRow = &lpRows->aRow[i];

if (lpRow->lpProps[I_MEMBER_ENTRYID].ulPropTag == PR_MEMBER_ENTRYID&&
lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.cb == cbentryid)
{
// When we pass in an entryid to IExchangeModifyTable, it upcases
// any embedded distinguisheed names.  We therefore have to use
// a case insensitive compare.  Can't guarantee it won't
// misidentify something, but the odds seem pretty long, and this
// is the only way we can get around this IExchangeModifyTable
// feature (since they also don't guarantee that table position
// will be maintained).

            hr = lpAdrBook->CompareEntryIDs(
                lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.cb,
                (LPENTRYID)(lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.lpb),
                cbentryid,
                lpentryid,
                0,
                &ulResult);

            if(SUCCEEDED(hr) && (ulResult == TRUE))
{
// Found the match! Make sure you have values available, and
// then return them.

if (lpRow->cValues < C_ACLPROPS||
lpRow->lpProps[I_MEMBER_RIGHTS].ulPropTag !=
PR_MEMBER_RIGHTS||
lpRow->lpProps[I_MEMBER_NAME].ulPropTag !=
PR_MEMBER_NAME)
{
hr = HR_LOG(E_UNEXPECTED); // Data from mdb is invalid!
goto cleanup;
}

*lplRights = lpRow->lpProps[I_MEMBER_RIGHTS].Value.l;

if (lppszDisplayName != NULL)
{
if (lpRow->lpProps[I_MEMBER_NAME].Value.lpszA != NULL)
{
ULONG cb =
strlen(lpRow->lpProps[I_MEMBER_NAME].Value.lpszA) +
 sizeof(char);

hr = MAPIAllocateBuffer(strlen(lpRow->
lpProps[I_MEMBER_NAME].Value.lpszA) +
sizeof(char),
(LPVOID FAR *)lppszDisplayName);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

strcpy(*lppszDisplayName,
   lpRow->lpProps[I_MEMBER_NAME].Value.lpszA);
}
}

goto cleanup;// Success!
}
}
}

hr = HR_LOG(EDK_E_NOT_FOUND); // Not found!

cleanup:

if (FAILED(hr))
{
if (lppszDisplayName != NULL)
MAPIFREEBUFFER(*lppszDisplayName);
}

FREEPROWS(lpRows);
ULRELEASE(lpMapiTbl);
    ULRELEASE(lpAdrBook);

    RETURN(hr);
}

// $--CFolderACLs::HrOpen------------------------------------------------------
//
// DESCRIPTION:Open the object on a ACLs folder.
//
// INPUT:
//
//  [lpMDB]-- Ptr to MDB object containing the ACLs folder.
//  [cbentryid]-- Number of bytes in folder's entry identifier.
//  [lpentryid]-- Folder's entry identifier.
//
// RETURNS:     NOERROR on success;
//              E_INVALIDARG if bad input;
//E_NOINTERFACE if acl table does not exist on folder;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

HRESULT
CFolderACLs::HrOpen(// RETURNS: HRESULT
    IN      LPMAPISESSION   lpSession,  // MAPI session pointer
INLPMDBlpMDB,// MDB store ptr
INULONGcbentryid,  // # bytes in entry ID
INLPENTRYIDlpentryid   // entry ID ptr
)
{
HRESULThr =NOERROR;
LPSRowSetlpRows =NULL;
ULONGulObjType =0;

// NOTE: The following order of properties is assumed by lots
//       of other code (CFolderACLs, CIExchangeFolderACLs).  We place the
// member name last so we can drop it when we write the ACL's
// back (in ModifyTable()).

    SizedSPropTagArray(C_ACLPROPS, rgPropTag) =
    {
    C_ACLPROPS,
    {
    PR_MEMBER_ENTRYID,
    PR_MEMBER_RIGHTS,
    PR_MEMBER_NAME
    }
    };

    DEBUGPRIVATE("CFolderACLs::HrOpen()\n");

    hr = CHK_CFolderACLs_HrOpen(lpMDB, cbentryid, lpentryid);

    if (FAILED(hr))
        RETURN(hr);

// Don't allow multiple opens on one object.

if (m_lpFolder != NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    // AddRef the MAPI session pointer

    lpSession->AddRef();

    m_lpSession = lpSession;

// Open the folder.

hr = lpMDB->OpenEntry(cbentryid,
  lpentryid,
  NULL,
  MAPI_BEST_ACCESS|
  MAPI_DEFERRED_ERRORS,
  &ulObjType,
  (LPUNKNOWN FAR *)&m_lpFolder);

if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

if (ulObjType != MAPI_FOLDER)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    // Open the ACL table property on the folder.  The returned table
// ptr may be used to get a MAPI table for reading and it can
// also be used to modify the ACLs table.  If there is no acl table,
// this call will fail with E_NOINTERFACE.

    hr = m_lpFolder->OpenProperty(PR_ACL_TABLE,
  (LPGUID)&IID_IExchangeModifyTable,
  0,
  MAPI_DEFERRED_ERRORS,
  (LPUNKNOWN FAR *)&m_lpExchTbl);

    if (FAILED(hr))
goto cleanup;

    // Open a MAPI table on the ACL table property.  This table can be
// read to determine what the ACLs table looks like.

    hr = m_lpExchTbl->GetTable(0, &m_lpMapiTbl);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Select columns needed.

hr = m_lpMapiTbl->SetColumns((LPSPropTagArray)&rgPropTag, 0);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Get all the rows in the table up to EDK_MAX_QUERY_ROWS.  If there
// are more rows than this, fail with MAPI_E_TABLE_TOO_BIG.

hr = m_lpMapiTbl->QueryRows(EDK_MAX_QUERY_ROWS, 0, &lpRows);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Check to be sure that table is not bigger than EDK_MAX_QUERY_ROWS.
// We do this by checking whether or not the cursor is positioned at
// the end of the table.

if (lpRows->cRows == EDK_MAX_QUERY_ROWS)
{
ULONGulRow =0;
ULONGulNumerator =0;
ULONGulDenominator =0;

hr = m_lpMapiTbl->QueryPosition(&ulRow, &ulNumerator, &ulDenominator);

if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
goto cleanup;
        }

// SeekRowApprox() doc states that if ulNumerator == ulDenominator, you
// are at the end of the table.

if (ulNumerator != ulDenominator)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Store the table in a sorted and more manageable format.

hr = m_SRowLst.HrInitialize(lpRows);

    if (FAILED(hr))
        goto cleanup;

// NOTE - lpRows gets deallocated in SROWLST::Initialize().  We NULL it
//  out here as a safeguard against future use.

lpRows = NULL;

cleanup:

if (FAILED(hr))
{
        ULRELEASE(m_lpSession);
ULRELEASE(m_lpMapiTbl);
ULRELEASE(m_lpExchTbl);
ULRELEASE(m_lpFolder);
}

    RETURN(hr);
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

// $--CFolderACLs::QueryInterface------------------------------------------
//
// DESCRIPTION:Return ptr to object which implements the desired
//interface, if this object supports the interface.
//
// INPUT:
//
//  [riid]-- Reference to interface identifier of desired interface.
//
//  [ppv]-- Ptr to object which supports interface.  NULL if none.
//
// RETURNS:     NOERROR if successful;
//E_INVALIDARG if bad input.
//E_NOINTERFACE if interface isn't supported.
//
//  Interfaces supported:
//
//      IUnknown
//      IDispatch   (eventually)
//      IExchangeFolderACLs
//      DFolderACLs (eventually)
//
//---------------------------------------------------------------------------

STDMETHODIMP
CFolderACLs::QueryInterface(// RETURNS: HRESULT
INREFIIDriid,   // interface ID reference
OUTLPVOID FAR *ppv     // interface ptr ptr
)
{
    HRESULT hr =NOERROR;

    DEBUGPRIVATE("CFolderACLs::QueryInterface().\n");

    hr = CHK_CFolderACLs_QueryInterface(riid, ppv);

    if (FAILED(hr))
        RETURN(hr);

    *ppv = NULL;

    // See if we support the requested interface.

    if (IsEqualIID(riid, IID_IUnknown))   // IUnknown interface is ourself
{
    *ppv = this;    // Return ourself
}
    else if (IsEqualIID(riid, IID_IExchangeFolderACLs))
    {
        // User wants our programatic interface.

    *ppv = m_prog_interface;
    }
    else
    {
        // We don't support the requested interface.

      *ppv = NULL;
        hr = HR_LOG(E_NOINTERFACE);
        goto cleanup;
    }

    // If we reach this point, no error occurred.  Increment reference count.

    AddRef();

cleanup:

    RETURN(hr);
}

// $--CFolderACLs::AddRef--------------------------------------------------
//
// DESCRIPTION:Increment the reference count on this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CFolderACLs::AddRef()// RETURNS: ULONG
{
    DEBUGPRIVATE("CFolderACLs::AddRef().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs++;

    return m_refs;
}

// $--CFolderACLs::Release-------------------------------------------------
//
// DESCRIPTION:Decrement the reference count on this object.  If the
//reference count reaches 0, destroy this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CFolderACLs::Release()// RETURNS: ULONG
{
    ULONG   ulRefCount =0;

    DEBUGPRIVATE("CFolderACLs::Release().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs--;

    if (!m_refs)
    {
        delete this;
        ulRefCount = 0;
    }
    else
    {
        ulRefCount = m_refs;
    }

    return ulRefCount;
}


ACLCLSF.H

// --aclclsf.h---------------------------------------------------------------
//
// Header file for the CFolderACLs class and its class factory.
//
// The CFolderACLs class is a controlling class.  It controls the
// CIExchangeFolderACLs class (which implements the IExchangeFolderACLs
// programatic interface defined in aclcls.h).
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#if !defined(_ACLCLSF_H)
#define _ACLCLSF_H

// $$--CFolderACLs--------------------------------------------------------
//
// The CFolderACLs class.
//
// ---------------------------------------------------------------------------

class FAR CFolderACLs : public IUnknown
{
friend class CIExchangeFolderACLs;

public:

    CFolderACLs();

~CFolderACLs();

// Standard OLE stuff.

STDMETHOD(QueryInterface)(
INREFIIDriid,
OUTVOID FAR * FAR *ppv
);

STDMETHOD_(ULONG, AddRef)(VOID);

STDMETHOD_(ULONG, Release)(VOID);

static
CFolderACLs FAR *
Create();

HRESULT
HrGetTableEntry(
INULONGcbentryid,
INLPENTRYIDlpentryid,
OUTLPSTR FAR *lppszDisplayName,
OUTLPLONGlplRights
);

HRESULT
HrOpen(
            IN      LPMAPISESSION lpSession,
INLPMDBlpMDB,
INULONGcbentryid,
INLPENTRYIDlpentryid
);

protected:

        HRESULT                 m_hr;       // constructor "return" code

LPEXCHANGEMODIFYTABLEm_lpExchTbl;

LPMAPIFOLDERm_lpFolder;// Ptr to MAPI folder object.

LPMAPITABLEm_lpMapiTbl;

CSROWLSTm_SRowLst;// Table info stored here.

ULONGm_refs;// Reference count.

// This is a ptr to the CIExchangeFolderACLs class, which implements
// the IFolderACLs interface exported to the user.

class FAR CIExchangeFolderACLs FAR *m_prog_interface;

        LPMAPISESSION           m_lpSession;
};

// $$--CIExchangeFolderACLs---------------------------------------------------
//
// Class CIExchangeFolderACLs.  This class implements the IExchangeFolderACLs
// interface which is exposed to the use via the aclcls.h header file.
//
// ---------------------------------------------------------------------------

class FAR CIExchangeFolderACLs : public IExchangeFolderACLs
{
public:

// Standard OLE stuff.

STDMETHOD(QueryInterface)(
THIS_
REFIIDriid,
LPVOID FAR *ppvObj
);

STDMETHOD_(ULONG, AddRef)(
THIS
);

STDMETHOD_(ULONG, Release)(
THIS
);

// Methods unique to the IExchangeFolderACLs interface.

STDMETHOD(HrDelete)(
THIS
);

STDMETHOD(HrGet)(
THIS_
LPLONGlplRights,
LPSTR FAR *lppszDisplayName,
ULONG FAR *lpcbentryid,
LPENTRYID FAR *lppentryid
);

STDMETHOD(HrInsert)(
THIS_
LONGlRights,
LPSTRlpszDisplayName,
ULONGcbentryid,
LPENTRYIDlpentryid,
LPLONGlplRights
);

STDMETHOD(HrModify)(
THIS_
LONGlRights,
LPLONGlplRights
);

STDMETHOD(HrSeek)(
THIS_
LONGlPos
);

STDMETHOD(HrTell)(
THIS_
LPLONGlplPos
);

    private:

        friend CFolderACLs; // so can access m_pFA

// Ptr to the controlling CFolderACLs object (shouldn't be
        // available to the user).

CFolderACLs FAR *m_pFA;
};

#endif


ACLCLSI.CPP

// --aclclsi.cpp-------------------------------------------------------------
//
//  Implementation file for the CIExchangeFolderACLs programmer interface class.
//
//  The CFolderACLs class is the controlling class.  It controls the
//  CIExchangeFolderACLs class (which implements the IExchangeFolderACLs
//programatic interface defined in aclcls.h and which this file implements).
//
//  Copyright (C) Microsoft Corp. 1986-1996, All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"
#include "aclclsf.h"
#include "aclclsi.chk"

// IUnknown Methods --
// Let the controlling parent object handle all of these!

// $--CIExchangeFolderACLs::QueryInterface-------------------------------------
//
// DESCRIPTION:Return ptr to object which implements the desired
//interface, if this object supports the interface.
//
//Implemented via the parent object's (CFolderACLs')
//QueryInterface.
// INPUT:
//
// [riid]-- Reference to interface identifier of desired interface.
//
// OUTPUT:
//
// [ppvObj]-- Ptr to object which supports interface.  NULL if none.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//E_NOINTERFACEif interface not supported.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::QueryInterface(// RETURNS: HRESULT
INREFIIDriid,       // interface ID reference
OUTLPVOID FAR *ppvObj      // interface ptr ptr
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderACLs::QueryInterface().\n");

    hr = CHK_CIExchangeFolderACLs_QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA");

    // Let parent controlling object handle this.  (Let it do all
    // parameter checking also!)

    hr = m_pFA->QueryInterface(riid, ppvObj);

    RETURN(hr);
}

// $--CIExchangeFolderACLs::AddRef---------------------------------------------
//
// DESCRIPTION:Increment this object's reference count.
//
//Implemented via the parent object's (CFolderACLs') AddRef().
//
// INPUT:None.
//
// RETURNS:New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CIExchangeFolderACLs::AddRef()// RETURNS: ULONG
{
    ULONGulRefCount =0;

    DEBUGPUBLIC("CIExchangeFolderACLs::AddRef().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA");

    // Let parent controlling object handle this.

    ulRefCount = m_pFA->AddRef();

    return ulRefCount;
}

// $--CIExchangeFolderACLs::Release--------------------------------------------
//
// DESCRIPTION:Decrement this object's reference count.
//
//Implemented via the parent object's (CFolderACLs') Release().
//
// INPUT:None.
//
// RETURNS:New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CIExchangeFolderACLs::Release()    // RETURNS: ULONG
{
    ULONGulRefCount =0;

    DEBUGPUBLIC("CIExchangeFolderACLs::Release().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA");

    // Let parent controlling object handle this.

    ulRefCount = m_pFA->Release();

    return ulRefCount;
}

//
// Methods unique to the IExchangeFolderACLs interface.
//

// $--CIExchangeFolderACLs::HrDelete-------------------------------------------
//
// DESCRIPTION:Delete the current record.
//
// INPUT:None.
//
// RETURNS:NOERRORif successful;
//              E_OUTOFMEMORYif insufficient memory;
//              E_FAILif cursor at ACL_PAST_END, or if the
//current record is the default ACL or is
//the ACL for the current user.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrDelete(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
LONGlPosDeletion =ACL_PAST_END;
LONGlNewRights =0;
LPSRowlpRow =NULL;
CSROWNODE *pDeletedNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderACLs::HrDelete().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

lPosDeletion = m_pFA->m_SRowLst.GetCursor();

hr = m_pFA->m_SRowLst.HrRemoveFromLst(&pDeletedNode);

if (FAILED(hr))
goto cleanup;

lpRow = &pDeletedNode->m_SRow;

if (lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.cb == 0)
{
hr = HR_LOG(E_FAIL);// Cannot delete default ACL!
}
else
{
hr = m_pFA->m_SRowLst.HrWriteToTable(m_pFA->m_lpExchTbl);
}

if (FAILED(hr))
{
HRESULThrTmp = m_pFA->m_SRowLst.HrInsert(lpRow);

if (FAILED(hrTmp))
hr = HR_LOG(E_UNEXPECTED);
else
lpRow->lpProps = NULL;

goto cleanup;
}

// Check that the deletion actually worked.  IExchangeModifyTable will
// actually add back the current user acl if you attempt to delete it,
// albeit with different rights.  If we detect that this has happened,
// we modify back to the original rights and fail the call.

hr = m_pFA->HrGetTableEntry(lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.cb,
(LPENTRYID)
lpRow->lpProps[I_MEMBER_ENTRYID].Value.bin.lpb,
NULL,
&lNewRights);

if (hr == EDK_E_NOT_FOUND)
{
hr = NOERROR;
}
else
{
if (FAILED(hr))
{
// This is not expected to happen.  If it does, we need to fix
// the problem so that we can insure that the table and list are
// in sync.

hr = HR_LOG(E_UNEXPECTED);
goto cleanup;
}
else
{
// The entry is still in the table, so reinsert it into the list
// and rewrite the table to insure that the rights are in sync.

hr = HR_LOG(E_FAIL);

HRESULThrTmp = m_pFA->m_SRowLst.HrInsert(lpRow);

if (FAILED(hrTmp))
{
hr = HR_LOG(E_UNEXPECTED);
goto cleanup;
}
else
{
lpRow->lpProps = NULL;
}

hrTmp = m_pFA->m_SRowLst.HrWriteToTable(m_pFA->m_lpExchTbl);

if (FAILED(hrTmp))
hr = HR_LOG(E_UNEXPECTED);

goto cleanup;
}
}

cleanup:

delete pDeletedNode;

    RETURN(hr);
}

// $--CIExchangeFolderACLs::HrGet----------------------------------------------
//
// DESCRIPTION:Get the current record and advance the cursor.
//
// OUTPUT:
//
//   [lplRights]--  ACL rights bits buffer.
//   [lppszDisplayName]--  Member display name buffer.
//   [lpcbentryid]--  Count of bytes in member entry ID.
//   [lppentryid]--  Member entry ID buffer.
//
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_FAILif cursor at ACL_PAST_END;
//              E_OUTOFMEMORYif insufficient memory.
//
// Notes:Space for *lplRights and *lpcbentryid is allocated by the user.
//Space for the other parameters is allocated by this method.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrGet(// RETURNS: HRESULT
OUTLPLONGlplRights,// rights ptr
OUTLPSTR FAR *lppszDisplayName,   // display name ptr
OUTULONG FAR *lpcbentryid,// # of bytes in entry ID ptr
OUTLPENTRYID FAR *lppentryid// entry ID ptr ptr
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderACLs::HrGet().\n");

    hr = CHK_CIExchangeFolderACLs_HrGet(lplRights,
      lppszDisplayName,
      lpcbentryid,
      lppentryid);
    if (FAILED(hr))
        RETURN(hr);

// Null the ptrs so we can free them on failure.

*lppszDisplayName =NULL;
*lppentryid =NULL;

*lpcbentryid =0;// Safeguard against misuse.

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

pNode = m_pFA->m_SRowLst.GetCurrentNode();

if (pNode == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (pNode->m_SRow.cValues < C_ACLPROPS||
pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].ulPropTag !=
PR_MEMBER_ENTRYID||
pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].ulPropTag !=
PR_MEMBER_RIGHTS||
pNode->m_SRow.lpProps[I_MEMBER_NAME].ulPropTag !=
PR_MEMBER_NAME)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

*lpcbentryid = pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].Value.bin.cb;

if (*lpcbentryid != 0&&
pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].Value.bin.lpb != NULL)
{
hr = MAPIAllocateBuffer(*lpcbentryid, (LPVOID FAR *)lppentryid);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

memcpy(*lppentryid,
   pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].Value.bin.lpb,
   *lpcbentryid);
}

*lplRights = pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l;

if (pNode->m_SRow.lpProps[I_MEMBER_NAME].Value.lpszA != NULL)
{
ULONGcb = strlen(pNode->m_SRow.lpProps[I_MEMBER_NAME].Value.lpszA) +
 sizeof(char);

hr = MAPIAllocateBuffer(cb, (LPVOID FAR *)lppszDisplayName);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

strcpy(*lppszDisplayName,
   pNode->m_SRow.lpProps[I_MEMBER_NAME].Value.lpszA);
}

// Advance the cursor.

m_pFA->m_SRowLst.SetCursor(m_pFA->m_SRowLst.GetCursor() + 1);

cleanup:

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppszDisplayName);
MAPIFREEBUFFER(*lppentryid);
}

    RETURN(hr);
}

// $--CIExchangeFolderACLs::HrInsert-------------------------------------------
//
// DESCRIPTION:Insert record before the current record and advance the cursor.
//
// INPUT:
//
//  [lRights]-- Member's ACL Rights bits.
//  [lpszDisplayName]-- Member's Display Name.
//  [cbentryid]-- Count of bytes in member's entry identifier.
//[lpentryid]-- Ptr to member's entry identifier.
//
// OUTPUT:
//
//[lplRights]-- Ptr to rights bits actually set on completion; if
//   this info is not wanted, specify NULL.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_OUTOFMEMORYif memory problems;
//              E_FAILotherwise (attempting to insert an acl for
//another existing user, etc.).
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrInsert(// RETURNS: HRESULT
INLONGlRights,         // rights bits
INLPSTRlpszDisplayName,    // display name
INULONGcbentryid,// # bytes in entry ID
INLPENTRYIDlpentryid,// entry ID ptr
OUTLPLONGlplRights// ptr to output rights bits
)
{
    HRESULThr =NOERROR;
LONGlNewRights =0;
LPSTRlpszDisplayNameCopy =NULL;
LPENTRYIDlpentryidCopy =NULL;
LONGlPosInsertion =ACL_PAST_END;
SRowsr = {0, C_ACLPROPS, NULL};

    DEBUGPUBLIC("CIExchangeFolderACLs::HrInsert().\n");

    hr = CHK_CIExchangeFolderACLs_HrInsert(lRights,
     lpszDisplayName,
     cbentryid,
     lpentryid,
   lplRights);
    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

lPosInsertion = m_pFA->m_SRowLst.GetCursor();

if (lPosInsertion == ACL_PAST_END)
lPosInsertion = m_pFA->m_SRowLst.GetNodeCount();

hr = MAPIAllocateBuffer(sizeof(SPropValue) * C_ACLPROPS,
(LPVOID FAR *)&sr.lpProps);
if (FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
    }

memset(sr.lpProps, 0, sizeof(SPropValue) * C_ACLPROPS);

hr = MAPIAllocateMore(cbentryid,
  sr.lpProps,
    (LPVOID FAR *)&lpentryidCopy);
if (FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
    }

memcpy(lpentryidCopy, lpentryid, cbentryid);

hr = MAPIAllocateMore(strlen(lpszDisplayName) + sizeof(char),
  sr.lpProps,
    (LPVOID FAR *)&lpszDisplayNameCopy);
if (FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
    }

strcpy(lpszDisplayNameCopy, lpszDisplayName);

sr.lpProps[I_MEMBER_ENTRYID].ulPropTag =PR_MEMBER_ENTRYID;
sr.lpProps[I_MEMBER_ENTRYID].Value.bin.cb =cbentryid;
sr.lpProps[I_MEMBER_ENTRYID].Value.bin.lpb =(LPBYTE)lpentryidCopy;

sr.lpProps[I_MEMBER_RIGHTS].ulPropTag =PR_MEMBER_RIGHTS;
sr.lpProps[I_MEMBER_RIGHTS].Value.l =lRights;

sr.lpProps[I_MEMBER_NAME].ulPropTag =PR_MEMBER_NAME;
sr.lpProps[I_MEMBER_NAME].Value.lpszA =lpszDisplayNameCopy;

hr = m_pFA->m_SRowLst.HrInsert(&sr);

if (FAILED(hr))
goto cleanup;

hr = m_pFA->m_SRowLst.HrWriteToTable(m_pFA->m_lpExchTbl);

if (FAILED(hr))
{
HRESULThrTmp = NOERROR;

// Delete the node from the list, since the table write failed.

m_pFA->m_SRowLst.SetCursor(lPosInsertion);

hrTmp = m_pFA->m_SRowLst.HrDelete();

if (FAILED(hrTmp))
hr = HR_LOG(E_UNEXPECTED);

// The memory allocated by this method was cleaned up when the
// node was deleted.

sr.lpProps = NULL;
goto cleanup;
}

// Determine what rights bits are actually set.

hr = m_pFA->HrGetTableEntry(cbentryid,
lpentryid,
NULL,
&lNewRights);

if (FAILED(hr))
{
hr = HR_LOG(E_UNEXPECTED);

// The memory allocated by this method is still associated with
// the list.

sr.lpProps = NULL;
goto cleanup;
}

if (lNewRights != lRights)
{
LONGlPos =m_pFA->m_SRowLst.GetCursor();
CSROWNODE *pNode = NULL;

m_pFA->m_SRowLst.SetCursor(lPosInsertion);

pNode = m_pFA->m_SRowLst.GetCurrentNode();

pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l = lNewRights;

m_pFA->m_SRowLst.SetCursor(lPos);
}

if (lplRights != NULL)
*lplRights = lNewRights;

cleanup:

if (FAILED(hr))
MAPIFREEBUFFER(sr.lpProps);

    RETURN(hr);
}

// $--CIExchangeFolderACLs::HrModify-------------------------------------------
//
// DESCRIPTION:Modify rights for the current record.
//
// INPUT:
//
//[lRights]-- Rights bits requested to be set.
//
// OUTPUT:
//
//[lplRights]-- Ptr to rights bits actually set on completion; if
//   this info is not wanted, specify NULL.
//
// RETURNS:NOERRORif successful;
//              E_FAILotherwise.
//
// Notes:We need to return *lplRights because the Exchange MDB may set
//extra bits, depending on the user and other circumstances.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrModify(// RETURNS: HRESULT
INLONGlRights,         // input rights bits
OUTLPLONGlplRights// ptr to output rights bits
)
{
    HRESULThr =NOERROR;
LONGlNewRights =0;
LONGlPrvRights =0;
CSROWNODE *pNode = NULL;

    DEBUGPUBLIC("CIExchangeFolderACLs::HrModify().\n");

    hr = CHK_CIExchangeFolderACLs_HrModify(lplRights);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

pNode = m_pFA->m_SRowLst.GetCurrentNode();

if (pNode == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

lPrvRights = pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l;

pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l = lRights;

hr = m_pFA->m_SRowLst.HrWriteToTable(m_pFA->m_lpExchTbl);

if (FAILED(hr))
{
pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l = lPrvRights;
goto cleanup;
}

// Determine what rights bits are actually set.

hr = m_pFA->HrGetTableEntry(
pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].Value.bin.cb,
(LPENTRYID)pNode->m_SRow.lpProps[I_MEMBER_ENTRYID].Value.bin.lpb,
NULL,
&lNewRights);

if (FAILED(hr))
goto cleanup;

pNode->m_SRow.lpProps[I_MEMBER_RIGHTS].Value.l = lNewRights;

if (lplRights != NULL)
*lplRights = lNewRights;

cleanup:

    RETURN(hr);
}

// $--CIExchangeFolderACLs::HrSeek---------------------------------------------
//
// DESCRIPTION:Set the current cursor position.
//
// INPUT:
//
// [lPos]-- New cursor position.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrSeek(// RETURNS: HRESULT
INLONGlPos// cursor position
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderACLs::HrSeek().\n");

    hr = CHK_CIExchangeFolderACLs_HrSeek(lPos);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

m_pFA->m_SRowLst.SetCursor(lPos);

    RETURN(hr);
}

// $--CIExchangeFolderACLs::HrTell---------------------------------------------
//
// DESCRIPTION:Return the current cursor position.
//
// OUTPUT:
//
// [lplPos]-- Ptr for returning current cursor position.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderACLs::HrTell(// RETURNS: HRESULT
OUTLPLONGlplPos// cursor position ptr
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderACLs::HrTell().\n");

    hr = CHK_CIExchangeFolderACLs_HrTell(lplPos);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFA, sizeof(CFolderACLs FAR *), "Bad m_pFA.");

*lplPos = m_pFA->m_SRowLst.GetCursor();

    RETURN(hr);
}


LLMACRO.H

//--llmacro.h------------------------------------------------------------------
//
// Linked list macros.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#ifndef_LLMACRO_H
#define _LLMACRO_H

//
// Single linked list manipulation macros.
//

//
// Linked lists maintained by these macros are assumed to look like this:
//
//  ________      ________      ________
//      |        |    |        |    |        |
//      |        |    |        |    |        |
// list head ptr --->|  head  |--->| middle |--->|  tail  |---> NULL
//      |        |    |  ...   |    |        |
//      |        |    |        |    |        |<----
//      |________|    |________|    |________|    /
//                                                             /
// list tail ptr -----------------------------------------------
//
// The macro parameters used are:
//
//  np -node ptr for node being added, removed, etc.
//  hd -list head ptr, as shown above
//  tl -list tail ptr, as shown above
//

#define AddToSLLHead(np, hd, tl)\
{\
  if (!(hd))\
    (tl)= (np);\
  (np)->m_pNxt= (hd);\
  (hd)= (np);\
}

#define AddToSLLTail(np, hd, tl)\
{\
  if (hd)\
  {\
    (tl)->m_pNxt= (np);\
    (tl)= (np);\
  }\
  else\
    (hd) = (tl) = (np);\
}

#define RmFromSLLHead(np, hd, tl)\
{\
  (np)= (hd);\
  (hd)= (hd)->m_pNxt;\
  if (!(hd))\
    (tl) = NULL;\
}

//
// Double linked list manipulation macros.
//

//
// Linked lists maintained by these macros are assumed to look like this:
//
//                    ________      ________      ________
//     |        |    |        |    |        |
// list head ptr --->|        |--->|        |--->|        |---> NULL
//                   |  head  |    | middle |    |  tail  |
//  ---|        |<---|  ...   |<---|        |<---
//  /  |________|    |________|    |________|   /
//                /                                           /
//                ---------------------------------------------
//
// The macro parameters used are:
//
//  np -node ptr for node being added, removed, etc.
//  lp -list head ptr, as shown above.
//  pn -previous node ptr, points at node after which an insertion
//is to be done
//
// NOTE that care must be taken in using these macros in a conditional statement
// (if ... else) due to the potential impact of the braces in the macros on
// the logic of the conditional statement.
//
//

#defineAddToDLLHead(np, lp)\
{\
  if ((lp))\
  {\
    (np)->m_pPrv= (lp)->m_pPrv;\
    (lp)->m_pPrv= (np);\
  }\
  else\
    (np)->m_pPrv= (np);\
  (np)->m_pNxt = (lp);\
  (lp)= (np);\
}

#defineAddToDLLTail(np, lp)\
{\
  (np)->m_pNxt= NULL;\
  if ((lp))\
  {\
    (np)->m_pPrv= (lp)->m_pPrv;\
    (lp)->m_pPrv->m_pNxt= (np);\
    (lp)->m_pPrv= (np);\
  }\
  else\
  {\
    (np)->m_pPrv= (np);\
    (lp)= (np);\
  }\
}

//
// InsertIntoDLL() assumes:
//
//lp != NULL
//pn != NULL
//pn is a node in lp
//

#defineInsertIntoDLL(np, lp, pn)\
{\
  if ((pn)->m_pNxt)\
  {\
    (pn)->m_pNxt->m_pPrv= (np);\
    (np)->m_pNxt= (pn)->m_pNxt;\
  }\
  else\
  {\
    (np)->m_pNxt= NULL;\
    (lp)->m_pPrv= (np);\
  }\
  (pn)->m_pNxt= (np);\
  (np)->m_pPrv= (pn);\
}

#defineRmFromDLL(np, lp)\
{\
  if ((np)->m_pNxt) \
  {\
    (np)->m_pNxt->m_pPrv= (np)->m_pPrv;\
    if ((np) == (lp))\
      (lp)= (np)->m_pNxt;\
    else\
      (np)->m_pPrv->m_pNxt= (np)->m_pNxt;\
  }\
  else\
  {\
    if ((np) == (lp))\
      (lp)= NULL;\
    else\
    {\
      (np)->m_pPrv->m_pNxt= NULL;\
      (lp)->m_pPrv= (np)->m_pPrv;\
    }\
  }\
}

#defineRmFromDLLHead(np, lp)\
{\
  (np) = (lp);\
  if ((lp)->m_pNxt)\
  {\
    (lp)->m_pNxt->m_pPrv= (lp)->m_pPrv;\
    (lp)= (lp)->m_pNxt;\
  }\
  else\
    (lp)= NULL;\
}

#endif// _LLMACRO_H


SROWLST.CPP

// --srowlst.cpp---------------------------------------------------------------
//
// Implementation of classes CSROWNODE and CSROWLST.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"

// ROWLIST allocation helper macro for IExchangeModifyTable interface.

#defineCbNewROWLIST(_centries)(offsetof(ROWLIST,aEntries) + \
(_centries)*sizeof(ROWENTRY))

// $--CSROWNODE::CSROWNODE-----------------------------------------------------
//
// DESCRIPTION:CSROWNODE constructor.
//
// INPUT:
//
//[lpSRow]-- Ptr to SRow for the CSROWNODE to be constructed.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWNODE::CSROWNODE(
INLPSRowlpSRow  // row set ptr
    )
{
    DEBUGPRIVATE("CSROWNODE::CSROWNODE()\n");

    m_pNxt =NULL;
    m_pPrv =NULL;
m_SRow =*lpSRow;
}

// $--CSROWNODE::~CSROWNODE----------------------------------------------------
//
// DESCRIPTION:CSROWNODE destructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWNODE::~CSROWNODE()
{
    DEBUGPRIVATE("CSROWNODE::~CSROWNODE()\n");

MAPIFREEBUFFER(m_SRow.lpProps);
}

// $--CSROWLST::CSROWLST-------------------------------------------------------
//
// DESCRIPTION:CSROWLST constructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWLST::CSROWLST()
{
    DEBUGPRIVATE("CSROWLST::CSROWLST()\n");

m_cNodes =0;
m_lPos =ACL_PAST_END;
    m_pCurNode =NULL;
    m_pLstHd =NULL;
}

// $--CSROWLST::~CSROWLST------------------------------------------------------
//
// DESCRIPTION:CSROWLST destructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWLST::~CSROWLST()
{
    CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::~CSROWLST()\n");

    while (m_pLstHd != NULL)
    {
        pNode = m_pLstHd;
        m_pLstHd = m_pLstHd->m_pNxt;
delete pNode;
    }
}

// $--CSROWLST::HrInitialize---------------------------------------------------
//
// DESCRIPTION:Initialize an CSROWLST.  An SRowSet is stored in the CSROWLST.
//The SROWNODES are ordered according to the order they are
//found in the table. Upon completion of this method, either
//successful or unsuccessful, all storage associated with lpRows
//has either been reassigned to the CSROWLST, or has been
//deallocated.
//
// INPUT:
//
//[lpRows]-- Ptr to SRowSet to use in initializing the CSROWLST.
//
// RETURNS:NOERROR on success;
//              E_INVALIDARG if bad input,
//              E_FAIL otherwise
//
// Warning:lpRows is NO LONGER VALID after calling this function and
//should NOT be used or deallocated!
//
//---------------------------------------------------------------------------

HRESULT
CSROWLST::HrInitialize(// RETURNS: HRESULT
INLPSRowSetlpRows      // row set ptr
)
{
HRESULThr =NOERROR;
ULONGi =0;
    CSROWNODE *pCurNode =NULL;
    CSROWNODE *pNewNode =NULL;
ULONGulCurSeqNo =0;
ULONGulNewSeqNo =0;

    DEBUGPRIVATE("CSROWLST::HrInitialize()\n");

// Verify that all rows have the proper number of properties.

for (i = 0; i < lpRows->cRows; i++)
{
if (lpRows->aRow[i].cValues < C_ACLPROPS)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Now add the SRow's in the SRowSet to the row list.

for (i = 0; i < lpRows->cRows; i++)
{
pNewNode = new CSROWNODE(&lpRows->aRow[i]);

if (pNewNode == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

// The lpProps storage no longer belongs to the SRowSet, so:

lpRows->aRow[i].cValues = 0;
lpRows->aRow[i].lpProps = NULL;

AddToDLLTail(pNewNode, m_pLstHd);

m_cNodes++;
}

if (m_cNodes > 0)
{
m_lPos = 0;
m_pCurNode = m_pLstHd;
}

cleanup:

FREEPROWS(lpRows);

RETURN(hr);
}

// $--CSROWLST::HrDelete----------------------------------------------------
//
// DESCRIPTION:Delete the current record.
//
// INPUT:None.
//
// RETURNS:NOERROR on success;
//E_FAIL if current cursor value is ACL_PAST_END.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrDelete(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrDelete()\n");

hr = HrRemoveFromLst(&pNode);

if (FAILED(hr))
goto cleanup;

delete pNode;

cleanup:

    RETURN(hr);
}

// $--CSROWLST::HrInsert----------------------------------------------------
//
// DESCRIPTION:Insert a new CSROWNODE before the current record and advance
//the cursor.
//
// INPUT:
//
//[lpSRow]-- Ptr to SRow for the CSROWNODE to be inserted.
//
// RETURNS:NOERROR on success;
//              E_INVALIDARG if bad input;
//              E_FAIL otherwise;
//
// Notes:The cursor is advanced only if it is not at ACL_PAST_END.
//E_FAIL is returned if there is already an entry for the
//user.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrInsert(// RETURNS: HRESULT
INLPSRowlpSRow      // row set ptr
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNewNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrInsert()\n");

if (m_cNodes == EDK_MAX_QUERY_ROWS)
    {
hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

{
CSROWNODE *pNode =m_pLstHd;

while (pNode != NULL)
{
if (!stricmp(lpSRow->lpProps[I_MEMBER_NAME].Value.lpszA,
 pNode->m_SRow.lpProps[I_MEMBER_NAME].Value.lpszA))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

pNode = pNode->m_pNxt;
}
}

pNewNode = new CSROWNODE(lpSRow);

if (pNewNode == NULL)
    {
    hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

if (m_pCurNode == m_pLstHd)// Covers empty list and insertion at head.
{
AddToDLLHead(pNewNode, m_pLstHd);
}
else if (m_pCurNode == NULL)// Covers insertion at tail.
{
AddToDLLTail(pNewNode, m_pLstHd);
}
else// Covers all other cases.
{
// It is important to store the previous node ptr in a separate
// location because m_pCurNode->m_pPrv gets stomped on in
// InsertIntoDLL().

CSROWNODE *pPrvNode = m_pCurNode->m_pPrv;

InsertIntoDLL(pNewNode, m_pLstHd, pPrvNode);
}

m_cNodes++;

if (m_lPos != ACL_PAST_END)
m_lPos++;

// Note that m_pCurNode does not change regardless of the insertion point.

cleanup:

    RETURN(hr);
}

// $--CSROWLST::HrRemoveFromLst------------------------------------------------
//
// DESCRIPTION:Remove the current record from the list and make it available
//to the caller.  The caller is responsible for deleting the
//record when he no longer needs it.
//
// OUTPUT:
//
//[ppRemovedNode]-- Ptr that will be set to removed node on successful
//   return.
//
// RETURNS:NOERROR on success;
//E_FAIL if current cursor value is ACL_PAST_END.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrRemoveFromLst(
OUTCSROWNODE * *ppRemovedNode
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrRemoveFromLst()\n");

*ppRemovedNode = NULL;

if (m_lPos == ACL_PAST_END)
    {
hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

pNode = m_pCurNode;

m_pCurNode = m_pCurNode->m_pNxt;

if (m_pCurNode == NULL)
m_lPos = ACL_PAST_END;

RmFromDLL(pNode, m_pLstHd);

m_cNodes--;

*ppRemovedNode = pNode;

cleanup:

    RETURN(hr);
}

// $--CSROWLST::SetCursor------------------------------------------------------
//
// DESCRIPTION:Standard C++ set member function which sets the current
//cursor position.
//
// INPUT:
//
//[lPos]-- New cursor position.  If < 0 or past end of list, then the
//   new cursor positon will be set to ACL_PAST_END.
//
// RETURNS:The new cursor position.
//
//---------------------------------------------------------------------------

LONG
CSROWLST::SetCursor(// RETURNS: LONG
INLONGlPos// cursor position
)
{
    DEBUGPRIVATE("CSROWLST::SetCursor()\n");

if (lPos >= 0 && lPos < (LONG) m_cNodes)
{
if (m_lPos == ACL_PAST_END || m_lPos > lPos)
{
m_pCurNode = m_pLstHd;
m_lPos = 0;
}

while (m_lPos < lPos)
{
m_pCurNode = m_pCurNode->m_pNxt;
m_lPos++;
}
}
else
{
m_lPos =ACL_PAST_END;
m_pCurNode =NULL;
}

    return m_lPos;
}

// $--CSROWLST::HrWriteToTable-------------------------------------------------
//
// DESCRIPTION:Write the contents of the CSROWLST to an EXCHANGEMODIFYTABLE.
//
// INPUT:
//
//[lpExchTbl]-- Ptr to the EXCHANGEMODIFYTABLE that will be rewritten based
//   on the contents of the CSROWLST.
//
// RETURNS:NOERROR on success;
//              E_INVALIDARG if bad input;
//              E_FAIL otherwise.
//
//---------------------------------------------------------------------------

HRESULT
CSROWLST::HrWriteToTable(// RETURNS: HRESULT
INLPEXCHANGEMODIFYTABLElpExchTbl   // Exchange modify tbl i/f ptr
)
{
HRESULThr =NOERROR;
LPROWENTRYlpRowEntry =NULL;
LPROWLISTlpRowList =NULL;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrWriteToTable()\n");

// Make a ROWLIST for use with the IExchangeModifyTable interface.  We
// explicitly call MAPIAllocateBuffer() here due to the size vagaries
// of MAPI structures.  In writing to the table, we have to drop off
// the PR_MEMBER_NAME property, which we have conveniently placed last
// in the SPropValue array.

hr = MAPIAllocateBuffer(CbNewROWLIST(m_cNodes), (LPVOID FAR *)&lpRowList);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

lpRowList->cEntries = m_cNodes;

pNode = m_pLstHd;

lpRowEntry = lpRowList->aEntries;

while (pNode != NULL)
{
lpRowEntry->ulRowFlags =ROW_ADD;
lpRowEntry->cValues =pNode->m_SRow.cValues - 1;//drop member name
lpRowEntry->rgPropVals =pNode->m_SRow.lpProps;

lpRowEntry++;

pNode = pNode->m_pNxt;
}

// Modify the entire table.

hr = lpExchTbl->ModifyTable(ROWLIST_REPLACE, lpRowList);

if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:

// Deallocate the ROWLIST (not including the SPropValue ptrs, since they
// still actually belong to the CSROWLST).

MAPIFREEBUFFER(lpRowList);

RETURN(hr);
}


SROWLST.H

// --srowlst.h-----------------------------------------------------------------
//
// Header file for classes CSROWNODE and CSROWLST.
//
// Copyright (C) Microsoft Corp,, 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#ifndef_SROWLST_H
#define _SROWLST_H

#include "llmacro.h"

// Count of properties supported by the ACL object.

#defineC_ACLPROPS3

// Indices of ACL properties in the SRow associated with a rule object.

#defineI_MEMBER_ENTRYID0
#defineI_MEMBER_RIGHTS1
#defineI_MEMBER_NAME2

class CSROWNODE// srn
{
friend class CIExchangeFolderACLs;
friend class CSROWLST;

    public:

        // constructor & destructor

CSROWNODE(
INLPSRowlpSRow
    );

~CSROWNODE();

protected:

CSROWNODE *m_pNxt;
CSROWNODE *m_pPrv;
SRowm_SRow;
};

class CSROWLST// srl
{
    public:

CSROWLST();     // constructor

~CSROWLST();    // destructor

        // standard C++ Get and Set member functions

CSROWNODE * GetCurrentNode(VOID);

LONG GetCursor(VOID) const;

LONG GetNodeCount(VOID) const;

LONG SetCursor(
INLONGlPos
);

HRESULT HrInitialize(
INLPSRowSetlpRows
);

HRESULT HrDelete(VOID);

HRESULT HrInsert(
INLPSRowlpSRow
);

HRESULT HrRemoveFromLst(
OUTCSROWNODE * *ppRemovedNode
);

HRESULT HrWriteToTable(
INLPEXCHANGEMODIFYTABLElpExchTbl
);

private:

ULONGm_cNodes;
LONGm_lPos;
CSROWNODE *m_pCurNode;
CSROWNODE *m_pLstHd;
};

// $--CSROWNODE::GetCurrentNode------------------------------------------------
//
// DESCRIPTION:Return a ptr to the current node.
//
// INPUT:   None.
//
// RETURNS:A ptr to the current node.
//
//-----------------------------------------------------------------------------

inline CSROWNODE * CSROWLST::GetCurrentNode(VOID)
{
    return m_pCurNode;
}

// $--CSROWLST::GetCursor------------------------------------------------------
//
// DESCRIPTION:Return the current cursor position.
//
// INPUT:   None.
//
// RETURNS:The current cursor position.
//
//-----------------------------------------------------------------------------

inline LONG CSROWLST::GetCursor() const
{
    return m_lPos;
}

// $--CSROWLST::GetNodeCount---------------------------------------------------
//
// DESCRIPTION:Return the count of nodes in the list.
//
// INPUT:   None.
//
// RETURNS:The count of nodes in the list.
//
//-----------------------------------------------------------------------------

inline LONG CSROWLST::GetNodeCount(VOID) const
{
    return m_cNodes;
}

#endif // _SROWLSTH_


ADDRLKUP.C

// --addrlkup.c-----------------------------------------------------------------
//
//  Functions used in address mapping.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "addrlkup.chk"

//$--HrFindExchangeGlobalAddressList-------------------------------------------------
// Returns the entry ID of the global address list container in the address
// book.
// -----------------------------------------------------------------------------
HRESULT HrFindExchangeGlobalAddressList( // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,        // address book pointer
    OUT ULONG *lpcbeid,             // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)          // pointer to entry ID pointer
{
    HRESULT         hr                  = NOERROR;
    ULONG           ulObjType           = 0;
    ULONG           i                   = 0;
    LPMAPIPROP      lpRootContainer     = NULL;
    LPMAPIPROP      lpContainer         = NULL;
    LPMAPITABLE     lpContainerTable    = NULL;
    LPSRowSet       lpRows              = NULL;
    ULONG           cbContainerEntryId  = 0;
    LPENTRYID       lpContainerEntryId  = NULL;
    LPSPropValue    lpCurrProp          = NULL;
    SRestriction    SRestrictAnd[2]     = {0};
    SRestriction    SRestrictGAL        = {0};
    SPropValue      SPropID             = {0};
    SPropValue      SPropProvider       = {0};
    BYTE            muid[]              = MUIDEMSAB;

    SizedSPropTagArray(1, rgPropTags) =
    {
        1,
        {
            PR_ENTRYID,
        }
    };

    DEBUGPUBLIC("HrFindExchangeGlobalAddressList()");

    hr = CHK_HrFindExchangeGlobalAddressList(
        lpAdrBook,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Open the root container of the address book
    hr = MAPICALL(lpAdrBook)->OpenEntry(
        lpAdrBook,
        0,
        NULL,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *)&lpRootContainer);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    if(ulObjType != MAPI_ABCONT)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the hierarchy table of the root container
    hr = MAPICALL(((LPABCONT)lpRootContainer))->GetHierarchyTable(
        (LPABCONT)lpRootContainer,
        MAPI_DEFERRED_ERRORS|CONVENIENT_DEPTH,
        &lpContainerTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Restrict the table to the global address list (GAL)
    // ---------------------------------------------------

    // Initialize provider restriction to only Exchange providers

    SRestrictAnd[0].rt                          = RES_PROPERTY;
    SRestrictAnd[0].res.resProperty.relop       = RELOP_EQ;
    SRestrictAnd[0].res.resProperty.ulPropTag   = PR_AB_PROVIDER_ID;
    SPropProvider.ulPropTag                     = PR_AB_PROVIDER_ID;

    SPropProvider.Value.bin.cb                  = 16;
    SPropProvider.Value.bin.lpb                 = (LPBYTE)muid;
    SRestrictAnd[0].res.resProperty.lpProp      = &SPropProvider;

    // Initialize container ID restriction to only GAL container

    SRestrictAnd[1].rt                          = RES_PROPERTY;
    SRestrictAnd[1].res.resProperty.relop       = RELOP_EQ;
    SRestrictAnd[1].res.resProperty.ulPropTag   = PR_EMS_AB_CONTAINERID;
    SPropID.ulPropTag                           = PR_EMS_AB_CONTAINERID;
    SPropID.Value.l                             = 0;
    SRestrictAnd[1].res.resProperty.lpProp      = &SPropID;

    // Initialize AND restriction

    SRestrictGAL.rt                             = RES_AND;
    SRestrictGAL.res.resAnd.cRes                = 2;
    SRestrictGAL.res.resAnd.lpRes               = &SRestrictAnd[0];

    // Restrict the table to the GAL - only a single row should remain

    // Get the row corresponding to the GAL

//
//  Query all the rows
//

hr = HrQueryAllRows(
    lpContainerTable,
(LPSPropTagArray)&rgPropTags,
&SRestrictGAL,
NULL,
0,
&lpRows);

    if(FAILED(hr) || (lpRows == NULL) || (lpRows->cRows != 1))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the entry ID for the GAL

    lpCurrProp = &(lpRows->aRow[0].lpProps[0]);

    if(lpCurrProp->ulPropTag == PR_ENTRYID)
    {
        cbContainerEntryId = lpCurrProp->Value.bin.cb;
        lpContainerEntryId = (LPENTRYID)lpCurrProp->Value.bin.lpb;
    }
    else
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
    }

    hr = MAPIAllocateBuffer(cbContainerEntryId, (LPVOID *)lppeid);

    if(FAILED(hr))
    {
        *lpcbeid = 0;
        *lppeid = NULL;
    }
    else
    {
        CopyMemory(
            *lppeid,
            lpContainerEntryId,
            cbContainerEntryId);

        *lpcbeid = cbContainerEntryId;
    }

cleanup:

    ULRELEASE(lpRootContainer);
    ULRELEASE(lpContainerTable);
    ULRELEASE(lpContainer);

    FREEPROWS(lpRows);

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppeid);

        *lpcbeid = 0;
        *lppeid = NULL;
    }

    RETURN(hr);
}

//$--HrGWResolveAddress@-------------------------------------------------------
// Returns the entry ID for the recipient identified by a given address.
// -----------------------------------------------------------------------------
HRESULT HrGWResolveAddressW(
    IN LPABCONT lpGalABCont,        // pointer to GAL container
    IN LPCWSTR lpszAddress,         // pointer to proxy address
    OUT BOOL *lpfMapiRecip,         // MAPI recipient
    OUT ULONG *lpcbEntryID,         // count of bytes in entry ID
    OUT LPENTRYID *lppEntryID)      // pointer to entry ID
{
    HRESULT     hr           = NOERROR;
    LPSTR       lpszAddressA = NULL;

    DEBUGPUBLIC( "HrGWResolveAddressW()");

    hr = CHK_HrGWResolveAddressW(
        lpGalABCont, lpszAddress, lpfMapiRecip,
        lpcbEntryID, lppEntryID);
    if( FAILED( hr))
        RETURN( hr);

    // MAPI doesn't officially support resolution of UNICODE
    // addresses.  Therefore, the address is converted to ANSI...

    hr = HrStrWToStrA( lpszAddress, &lpszAddressA);
    if( FAILED( hr))
        goto cleanup;

    hr = HrGWResolveAddressA(
        lpGalABCont, lpszAddressA, lpfMapiRecip,
        lpcbEntryID, lppEntryID);

cleanup:
    MAPIFREEBUFFER( lpszAddressA);

    RETURN(hr);
}

HRESULT HrGWResolveAddressA(
    IN LPABCONT lpGalABCont,        // pointer to GAL container
    IN LPCSTR lpszAddress,          // pointer to proxy address
    OUT BOOL *lpfMapiRecip,         // MAPI recipient
    OUT ULONG *lpcbEntryID,         // count of bytes in entry ID
    OUT LPENTRYID *lppEntryID)      // pointer to entry ID
{
    HRESULT     hr          = NOERROR;
    HRESULT     hrT         = 0;
    SCODE       sc          = 0;
    LPADRLIST   lpAdrList   = NULL;
    LPFlagList  lpFlagList  = NULL;
    SPropValue  prop[2]     = {0};
    ULONG       cbEntryID   = 0;
    LPENTRYID   lpEntryID   = NULL;

    static const SizedSPropTagArray(2, rgPropTags) =
    { 2,
        {
            PR_ENTRYID,
            PR_SEND_RICH_INFO
        }
    };

    DEBUGPUBLIC("HrGWResolveAddressA()");

    hr = CHK_HrGWResolveAddressA(
        lpGalABCont,
        lpszAddress,
        lpfMapiRecip,
        lpcbEntryID,
        lppEntryID);

    if(FAILED(hr))
        RETURN(hr);

    *lpfMapiRecip = FALSE;
    *lpcbEntryID  = 0;
    *lppEntryID   = NULL;

    sc = MAPIAllocateBuffer( CbNewFlagList(1), (LPVOID*)&lpFlagList);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lpFlagList->cFlags    = 1;
    lpFlagList->ulFlag[0] = MAPI_UNRESOLVED;

    hr = HrMAPICreateSizedAddressList(1, &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    prop[0].ulPropTag = PR_DISPLAY_NAME_A;
    prop[0].Value.lpszA = (LPSTR)lpszAddress;
    prop[1].ulPropTag = PR_RECIPIENT_TYPE;
    prop[1].Value.ul = MAPI_TO;

    hr = HrMAPISetAddressList(
        0,
        2,
        prop,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpGalABCont)->ResolveNames(
        lpGalABCont,
        (LPSPropTagArray)&rgPropTags,
        EMS_AB_ADDRESS_LOOKUP,
        lpAdrList,
        lpFlagList);

    if(lpFlagList->ulFlag[0] != MAPI_RESOLVED)
    {
        if(lpFlagList->ulFlag[0] == MAPI_AMBIGUOUS)
        {
            hrT = MAPI_E_AMBIGUOUS_RECIP;
        }
        else
        {
            hrT = MAPI_E_NOT_FOUND;
        }
    }

    if(FAILED(hrT))
    {
        if(hrT == MAPI_E_NOT_FOUND)
        {
            hr = HR_LOG(EDK_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        };

        goto cleanup;
    }

    cbEntryID = lpAdrList->aEntries[0].rgPropVals[0].Value.bin.cb;
    lpEntryID = (LPENTRYID)lpAdrList->aEntries[0].rgPropVals[0].Value.bin.lpb;

    sc = MAPIAllocateBuffer( cbEntryID, (LPVOID*)lppEntryID);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(*lppEntryID, lpEntryID, cbEntryID);
    *lpcbEntryID  = cbEntryID;
    *lpfMapiRecip = lpAdrList->aEntries[0].rgPropVals[1].Value.b;

cleanup:

    MAPIFREEBUFFER(lpFlagList);

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}

//$--HrCheckForType@------------------------------------------------------------
//  Check if the passed in string has the requested address type. If so, return
//  the address value, otherwise fail.
// -----------------------------------------------------------------------------
static HRESULT HrCheckForTypeW(  // RETURNS: return code
    IN  LPCWSTR lpszAddrType,    // pointer to address type
    IN  LPCWSTR lpszProxy,       // pointer to proxy address
    OUT LPWSTR * lppszAddress)   // pointer to address pointer
{
    HRESULT hr              = EDK_E_NOT_FOUND;
    LPCWSTR lpszProxyAddr   = NULL;
    ULONG   cbAddress       = 0;
    SCODE   sc              = 0;
    ULONG   cchProxy        = 0;
    ULONG   cchProxyType    = 0;

    DEBUGPRIVATE("HrCheckForTypeW()");

    hr = CHK_HrCheckForTypeW(
        lpszAddrType,
        lpszProxy,
        lppszAddress);

    if(FAILED(hr))
        RETURN(hr);

// Initialize output parameter

*lppszAddress = NULL;

    // find the ':' separator.

    cchProxy     = wcslen(lpszProxy);
    cchProxyType = wcscspn(lpszProxy, L":");

    if((cchProxyType == 0) || (cchProxyType >= cchProxy))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = EDK_E_NOT_FOUND;

    // does the address type match?
    if((cchProxyType == wcslen(lpszAddrType)) &&
       (_wcsnicmp(lpszProxy, lpszAddrType, cchProxyType) == 0))
    {
        // specified address type found
        lpszProxyAddr = lpszProxy + cchProxyType + 1;

        cbAddress = cbStrLenW(lpszProxyAddr);

        // make a buffer to hold it.
        sc = MAPIAllocateBuffer(cbAddress, (void **)lppszAddress);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
        }
        else
        {
            CopyMemory(*lppszAddress, lpszProxyAddr, cbAddress);

            hr = NOERROR;
        }
    }

cleanup:

    RETURN(hr);
}

static HRESULT HrCheckForTypeA( // RETURNS: return code
    IN  LPCSTR lpszAddrType,    // pointer to address type
    IN  LPCSTR lpszProxy,       // pointer to proxy address
    OUT LPSTR * lppszAddress)   // pointer to address pointer
{
    HRESULT hr              = EDK_E_NOT_FOUND;
    LPCSTR  lpszProxyAddr   = NULL;
    ULONG   cbAddress       = 0;
    SCODE   sc              = 0;
    ULONG   cchProxy        = 0;
    ULONG   cchProxyType    = 0;

    DEBUGPRIVATE("HrCheckForTypeA()");

    hr = CHK_HrCheckForTypeA(
        lpszAddrType,
        lpszProxy,
        lppszAddress);

    if(FAILED(hr))
        RETURN(hr);

// Initialize output parameter

*lppszAddress = NULL;

    // find the ':' separator.

    cchProxy     = lstrlenA(lpszProxy);
    cchProxyType = strcspn(lpszProxy, ":");

    if((cchProxyType == 0) || (cchProxyType >= cchProxy))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = EDK_E_NOT_FOUND;

    // does the address type match?
    if((cchProxyType == (ULONG)lstrlenA(lpszAddrType)) &&
       (_strnicmp(lpszProxy, lpszAddrType, cchProxyType) == 0))
    {
        // specified address type found
        lpszProxyAddr = lpszProxy + cchProxyType + 1;

        cbAddress = cbStrLenA(lpszProxyAddr);

        // make a buffer to hold it.
        sc = MAPIAllocateBuffer(cbAddress, (void **)lppszAddress);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
        }
        else
        {
            CopyMemory(*lppszAddress, lpszProxyAddr, cbAddress);

            hr = NOERROR;
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrGWResolveProxy@---------------------------------------------------------
// Returns the address of a specified type for the recipient identified by
// a given entry ID.
// -----------------------------------------------------------------------------
#define IADDRTYPE  0
#define IEMAILADDR 1
#define IMAPIRECIP 2
#define IPROXYADDR 3

HRESULT HrGWResolveProxyW(         // RETURNS: return code
    IN  LPADRBOOK   lpAdrBook,      // pointer to address book
    IN  ULONG       cbeid,          // count of bytes in the entry ID
    IN  LPENTRYID   lpeid,          // pointer to the entry ID
    IN  LPCWSTR     lpszAddrType,   // pointer to the address type
    OUT BOOL *lpfMapiRecip,         // MAPI recipient
    OUT LPWSTR *    lppszAddress)   // pointer to the address pointer
{
    HRESULT         hr              = EDK_E_NOT_FOUND;
    HRESULT         hrT             = 0;
    SCODE           sc              = 0;
    ULONG           i               = 0;
    ULONG           cbAddress       = 0;
    ULONG           cProxy          = 0;
    LPSPropValue    lpProps         = NULL;
    LPADRLIST       lpAdrList       = NULL;
    SPropValue      prop[2]         = {0};

    SizedSPropTagArray(4, rgPropTags) =
    {
        4,
        {
            PR_ADDRTYPE_W,
            PR_EMAIL_ADDRESS_W,
            PR_SEND_RICH_INFO,
            PR_EMS_AB_PROXY_ADDRESSES_W
        }
    };

    DEBUGPUBLIC("HrGWResolveProxyW()");

    hr = CHK_HrGWResolveProxyW(
        lpAdrBook,
        cbeid,
        lpeid,
        lpszAddrType,
        lpfMapiRecip,
        lppszAddress);

    if(FAILED(hr))
        RETURN(hr);

// Initialize output parameters

    *lpfMapiRecip = FALSE;
*lppszAddress = NULL;

    hr = HrMAPICreateSizedAddressList(1, &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    prop[0].ulPropTag       = PR_ENTRYID;
    prop[0].Value.bin.cb    = cbeid;
    prop[0].Value.bin.lpb   = (LPBYTE)lpeid;
    prop[1].ulPropTag       = PR_RECIPIENT_TYPE;
    prop[1].Value.ul        = MAPI_TO;

    hr = HrMAPISetAddressList(
        0,
        2,
        prop,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpAdrBook)->PrepareRecips( lpAdrBook,
        0,
        (LPSPropTagArray)&rgPropTags,
        lpAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpProps = lpAdrList->aEntries[0].rgPropVals;

    //
    //  Hack:  detect the case where prepare recips doesn't work correctly.
    //      This happens when trying to look up a recipient that is in
    //      a replicated directory but not in the local directory.
    //
    if (lpAdrList->aEntries[0].cValues == 3)
    {
        MODULE_ERROR("PrepareRecips() failed but did not declare.");

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // If the given address type matches the PR_ADDRTYPE value,
    // return the PR_EMAIL_ADDRESS value

    if((PROP_TYPE(lpProps[IADDRTYPE].ulPropTag) != PT_ERROR) &&
       (PROP_TYPE(lpProps[IEMAILADDR].ulPropTag) != PT_ERROR) &&
       (_wcsicmp(lpProps[IADDRTYPE].Value.lpszW, lpszAddrType) == 0))
    {
        cbAddress = cbStrLenW(lpProps[IEMAILADDR].Value.lpszW);

        sc = MAPIAllocateBuffer(cbAddress, (void **)lppszAddress);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
        }
        else
        {
            CopyMemory(*lppszAddress, lpProps[IEMAILADDR].Value.lpszW, cbAddress);
            hr = NOERROR;
        }

        goto cleanup;
    }

    // Search for a PR_EMS_AB_PROXY_ADDRESSES of the given type if present.

    else if(PROP_TYPE(lpProps[IPROXYADDR].ulPropTag) != PT_ERROR)
    {
        // count of proxy addresses
        cProxy = lpAdrList->aEntries[0].rgPropVals[IPROXYADDR].Value.MVszW.cValues;

        for(i = 0; i < cProxy; i++)
        {
            hr = HrCheckForTypeW(
                lpszAddrType,
                lpProps[IPROXYADDR].Value.MVszW.lppszW[i],
                lppszAddress);

            if(hr == EDK_E_NOT_FOUND)
            {
                continue;
            }
            else if(FAILED(hr))
            {
                goto cleanup;
            }
            else
            {
                //
                // Found a matching proxy address.
                //

                goto cleanup;
            }
        }
    }
    else
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
    }

cleanup:

    if(SUCCEEDED(hr))
    {
        *lpfMapiRecip = lpAdrList->aEntries[0].rgPropVals[IMAPIRECIP].Value.b;
    }

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}

HRESULT HrGWResolveProxyA(         // RETURNS: return code
    IN  LPADRBOOK   lpAdrBook,      // pointer to address book
    IN  ULONG       cbeid,          // count of bytes in the entry ID
    IN  LPENTRYID   lpeid,          // pointer to the entry ID
    IN  LPCSTR      lpszAddrType,   // pointer to the address type
    OUT BOOL *lpfMapiRecip,         // MAPI recipient
    OUT LPSTR *     lppszAddress)   // pointer to the address pointer
{
    HRESULT         hr              = EDK_E_NOT_FOUND;
    HRESULT         hrT             = 0;
    SCODE           sc              = 0;
    ULONG           i               = 0;
    ULONG           cbAddress       = 0;
    ULONG           cProxy          = 0;
    LPSPropValue    lpProps         = NULL;
    LPADRLIST       lpAdrList       = NULL;
    SPropValue      prop[2]         = {0};

    SizedSPropTagArray(4, rgPropTags) =
    {
        4,
        {
            PR_ADDRTYPE_A,
            PR_EMAIL_ADDRESS_A,
            PR_SEND_RICH_INFO,
            PR_EMS_AB_PROXY_ADDRESSES_A
        }
    };

    DEBUGPUBLIC("HrGWResolveProxyA()");

    hr = CHK_HrGWResolveProxyA(
        lpAdrBook,
        cbeid,
        lpeid,
        lpszAddrType,
        lpfMapiRecip,
        lppszAddress);

    if(FAILED(hr))
        RETURN(hr);

// Initialize output parameters

    *lpfMapiRecip = FALSE;
*lppszAddress = NULL;

    hr = HrMAPICreateSizedAddressList(1, &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    prop[0].ulPropTag       = PR_ENTRYID;
    prop[0].Value.bin.cb    = cbeid;
    prop[0].Value.bin.lpb   = (LPBYTE)lpeid;
    prop[1].ulPropTag       = PR_RECIPIENT_TYPE;
    prop[1].Value.ul        = MAPI_TO;

    hr = HrMAPISetAddressList(
        0,
        2,
        prop,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpAdrBook)->PrepareRecips( lpAdrBook,
        0,
        (LPSPropTagArray)&rgPropTags,
        lpAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpProps = lpAdrList->aEntries[0].rgPropVals;

    //
    //  Hack:  detect the case where prepare recips doesn't work correctly.
    //      This happens when trying to look up a recipient that is in
    //      a replicated directory but not in the local directory.
    //
    if (lpAdrList->aEntries[0].cValues == 3)
    {
        MODULE_ERROR("PrepareRecips() failed but did not declare.");

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // If the given address type matches the PR_ADDRTYPE value,
    // return the PR_EMAIL_ADDRESS value

    if((PROP_TYPE(lpProps[IADDRTYPE].ulPropTag) != PT_ERROR) &&
       (PROP_TYPE(lpProps[IEMAILADDR].ulPropTag) != PT_ERROR) &&
       (_strcmpi(lpProps[IADDRTYPE].Value.lpszA, lpszAddrType) == 0))
    {
        cbAddress = cbStrLenA(lpProps[IEMAILADDR].Value.lpszA);

        sc = MAPIAllocateBuffer(cbAddress, (void **)lppszAddress);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
        }
        else
        {
            CopyMemory(*lppszAddress, lpProps[IEMAILADDR].Value.lpszW, cbAddress);
            hr = NOERROR;
        }

        goto cleanup;
    }

    // Search for a PR_EMS_AB_PROXY_ADDRESSES of the given type if present.

    else if(PROP_TYPE(lpProps[IPROXYADDR].ulPropTag) != PT_ERROR)
    {
        // count of proxy addresses
        cProxy = lpAdrList->aEntries[0].rgPropVals[IPROXYADDR].Value.MVszA.cValues;

        for(i = 0; i < cProxy; i++)
        {
            hr = HrCheckForTypeA(
                lpszAddrType,
                lpProps[IPROXYADDR].Value.MVszA.lppszA[i],
                lppszAddress);

            if(hr == EDK_E_NOT_FOUND)
            {
                continue;
            }
            else if(FAILED(hr))
            {
                goto cleanup;
            }
            else
            {
                //
                // Found a matching proxy address.
                //

                goto cleanup;
            }
        }
    }
    else
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
    }

cleanup:

    if(SUCCEEDED(hr))
    {
        *lpfMapiRecip = lpAdrList->aEntries[0].rgPropVals[IMAPIRECIP].Value.b;
    }

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}


CFGNOTIF.C

// --cfgnotif.c-----------------------------------------------------------------
//
//  Routines to provide notification when a given extension data section has
//  changed, and read the new data.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "attrname.h"
#include "cfgnotif.chk"

//
// Internal Function Declarations
//

static DWORD WINAPI PollThread(
    IN LPVOID lpvThreadParam);

static HRESULT HrUpdateAdviseNotify(
    IN      LPSPropValue    lpNewData,
    IN OUT  LPADVISENOTIFY  lpNotify);

static HRESULT HrDestroyAdviseNotify(
    IN OUT  LPADVISENOTIFY * lppNotify);

//
// Configuration API Routines
//

//$--HrCfgCreateAdviseObj-------------------------------------------------------
//  Begin monitoring of a MAPI session for changes to extension data.
//  This routine should be called once to create an advise object for a MAPI
//  session that is being monitored.  Then, HrCfgAdvise() should be called
//  once for each extension data section that is being monitored.
//  If nPollInterval == 0 then no monitoring thread is created, and the user
//  must do polling manually by calling HrCfgPollAdviseObj().
// -----------------------------------------------------------------------------
HRESULT HrCfgCreateAdviseObj(               // RETURNS: HRESULT
    IN  LPMAPISESSION       lpSession,      // MAPI session to monitor
    IN  ULONG               nPollInterval,  // milliseconds between polling
    OUT LPADVISEOBJ *       lppAdviseObj)   // created advise object
{
    HRESULT                 hr              = NOERROR;
    LPADVISEOBJ             lpAdviseObj     = NULL;
SECURITY_ATTRIBUTES     saEvent         = {0, NULL, TRUE};
    BOOL                    fCriticalSectionInitialized     = FALSE;

    hr = CHK_HrCfgCreateAdviseObj(
        lpSession,
        nPollInterval,
        lppAdviseObj);
    if (FAILED(hr))
        RETURN(hr);

    DEBUGPUBLIC("HrCfgCreateAdviseObj()\n");

    // Get memory for the advise object.

    hr = MAPIAllocateBuffer(sizeof(*lpAdviseObj), &lpAdviseObj);
    if (FAILED(hr))
        goto cleanup;

    ZeroMemory(lpAdviseObj, sizeof(*lpAdviseObj));

    // Store parameters in advise object.

    lpAdviseObj->lpSession = lpSession;
    lpAdviseObj->nPollInterval = nPollInterval;

    // Get an IMAPIProp interface on the current object.

    hr = HrOpenSessionObject(lpSession, &lpAdviseObj->lpCurrentObject);
    if (FAILED(hr))
        goto cleanup;

    // Get the property tag for USN-Changed.

    hr = HrCfgPropertyTagFromNameId(
        lpAdviseObj->lpCurrentObject,
        NM_USN_CHANGED,
        &lpAdviseObj->ulUSNChangedPropTag);
    if (FAILED(hr))
        goto cleanup;

    // If it came back without the property type then set it to PT_LONG.

    if (PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag) == 0)
    {
        lpAdviseObj->ulUSNChangedPropTag =
            CHANGE_PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag,
            PT_LONG);
    }

    // Get the property tag for Extension-Data.

    hr = HrCfgPropertyTagFromNameId(
        lpAdviseObj->lpCurrentObject,
        NM_EXTENSION_DATA,
        &lpAdviseObj->ulExtensionDataPropTag);
    if (FAILED(hr))
        goto cleanup;

    // If it came back without the property type then set it to PT_MV_BINARY.

    if (PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag) == 0)
    {
        lpAdviseObj->ulExtensionDataPropTag =
            CHANGE_PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag,
            PT_MV_BINARY);
    }

    // Create a critical section to synchronize access to the advise object.

    InitializeCriticalSection(&lpAdviseObj->csCriticalSection);
    fCriticalSectionInitialized = TRUE;

    // "Poll" the object to get the initial values of the extension data blobs.

    hr = HrCfgPollAdviseObj(lpAdviseObj);
    if (FAILED(hr))
        goto cleanup;

    // Only create the thread if nPollInterval != 0.

    if (nPollInterval != 0)
    {
        // Create an event to stop the polling thread.

        lpAdviseObj->hPollThreadStop = CreateEvent(&saEvent, FALSE, FALSE, NULL);
    if (lpAdviseObj->hPollThreadStop == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    // Create the polling thread.

    lpAdviseObj->hPollThread = CreateThread(
        NULL,
        0,
        PollThread,
        lpAdviseObj,
        0,
    &lpAdviseObj->ulPollThreadID);
    if (lpAdviseObj->hPollThread == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }

    // Set the return variable.

    *lppAdviseObj = lpAdviseObj;

cleanup:
    if (FAILED(hr) && lpAdviseObj)
    {
        CLOSEHANDLE(lpAdviseObj->hPollThreadStop);

        if (fCriticalSectionInitialized)
            DeleteCriticalSection(&lpAdviseObj->csCriticalSection);

        ULRELEASE(lpAdviseObj->lpCurrentObject);

        MAPIFREEBUFFER(lpAdviseObj);
    }

    RETURN(hr);
}

//$--HrCfgPollAdviseObj---------------------------------------------------------
//  Checks an advise object against what is stored in the DSA.  Calls the
//  notification routines if there are any changes.
// -----------------------------------------------------------------------------
HRESULT HrCfgPollAdviseObj(                 // RETURNS: HRESULT
    IN  LPADVISEOBJ     lpAdviseObj)        // advise object to poll
{
    HRESULT             hr                  = NOERROR;
    ULONG               ulNewUSNChanged     = 0;
    LPSPropValue        lpNewExtensionData  = NULL;
    ULONG               cValues             = 0;
    LPADVISENOTIFY      lpAdviseNotify      = NULL;

    hr = CHK_HrCfgPollAdviseObj(
        lpAdviseObj);
    if (FAILED(hr))
        RETURN(hr);

    DEBUGPUBLIC("HrCfgPollAdviseObj()\n");

    // Lock the object against access by other threads.

    EnterCriticalSection(&lpAdviseObj->csCriticalSection);

    // Get the new USN-Changed from the DSA.  If the new USN-Changed is
    // different from the one we have stored then something in the object
    // has changed (possibly the extension data).  Note: the first time these
    // statements are executed, USN-Changed will always have "changed" since
    // the one we have stored starts out at 0.

    hr = HrMAPIGetPropLong(
        lpAdviseObj->lpCurrentObject,
        lpAdviseObj->ulUSNChangedPropTag,
        &ulNewUSNChanged);
    if (FAILED(hr))
        goto cleanup;

    // If USN-Changed is different then do the update.

    if (ulNewUSNChanged != lpAdviseObj->ulUSNChanged)
    {
        SizedSPropTagArray(1, ExtDataTag) =
            {1, {lpAdviseObj->ulExtensionDataPropTag}};

        // Read the new extension data.

        hr = MAPICALL(lpAdviseObj->lpCurrentObject)->GetProps(
            lpAdviseObj->lpCurrentObject,
            (LPSPropTagArray) &ExtDataTag,
            0,
            &cValues,
            &lpNewExtensionData);

        // If the property was not found then allocate an empty multivalued
        // binary property and store it as if it had been read from the
        // extension data property.

        if (hr == MAPI_W_ERRORS_RETURNED &&
            cValues == 1 &&
            PROP_TYPE(lpNewExtensionData->ulPropTag) == PT_ERROR &&
            lpNewExtensionData->Value.err == MAPI_E_NOT_FOUND)
        {
            lpNewExtensionData->ulPropTag = lpAdviseObj->ulExtensionDataPropTag;
            lpNewExtensionData->Value.MVbin.cValues = 0;

            hr = MAPIAllocateMore(
                0,
                lpNewExtensionData,
                &lpNewExtensionData->Value.MVbin.lpbin);
            if (FAILED(hr))
                goto cleanup;

            hr = NOERROR;
        }

        // If there was any other error then return.

        if (FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
            goto cleanup;

        // Loop for each ADVISENOTIFY and check to see if it's data has changed.
        // If it has, update it and call the callback routine.  After this
        // loop is done, the blob pointers in the ADVISENOTIFY structures will
        // point into the new extension data property, not the old one.  Then,
        // the old one can be freed and replaced in the ADVISEOBJ structure
        // by the new one.

        for (
            lpAdviseNotify = lpAdviseObj->lpNotifyList;
            lpAdviseNotify;
            lpAdviseNotify = lpAdviseNotify->lpNext)
        {
            hr = HrUpdateAdviseNotify(lpNewExtensionData, lpAdviseNotify);
            if (FAILED(hr))
                goto cleanup;
        }

        // Replace the old extension data property with the new one.

        MAPIFREEBUFFER(lpAdviseObj->lpExtensionData);
        lpAdviseObj->lpExtensionData = lpNewExtensionData;
        lpNewExtensionData = NULL;

        // Replace the old USN-Changed value with the new one.

        lpAdviseObj->ulUSNChanged = ulNewUSNChanged;
    }

cleanup:
    MAPIFREEBUFFER(lpNewExtensionData);

    // Unlock the object to allow access by other threads.

    LeaveCriticalSection(&lpAdviseObj->csCriticalSection);

    RETURN(hr);
}

//$--HrCfgDestroyAdviseObj------------------------------------------------------
//  End monitoring of a MAPI session.  This routine calls HrCfgUnadvise() for
//  any extension data sections that are actively being monitored.
// -----------------------------------------------------------------------------
HRESULT HrCfgDestroyAdviseObj(              // RETURNS: HRESULT
    IN  LPADVISEOBJ         lpAdviseObj)    // advise object to destroy
{
    HRESULT             hr                  = NOERROR;
    HRESULT             hrT                 = NOERROR;
    BOOL                fItWorked           = TRUE;
    DWORD               dwStatus            = 0;

    DEBUGPUBLIC("HrCfgDestroyAdviseObj()\n");

    hr = CHK_HrCfgDestroyAdviseObj(
        lpAdviseObj);
    if (FAILED(hr))
        RETURN(hr);

    if (lpAdviseObj)
    {
        // Signal the thread to stop and then release the signal event.

        if (lpAdviseObj->hPollThreadStop)
        {
            fItWorked = SetEvent(lpAdviseObj->hPollThreadStop);
            if (!fItWorked)
            {
                hr = HR_LOG(E_FAIL);
            }
            CLOSEHANDLE(lpAdviseObj->hPollThreadStop);
        }

        // Wait for thread to terminate and then release it's handle.

        if (lpAdviseObj->hPollThread)
        {
            dwStatus = WaitForSingleObject(lpAdviseObj->hPollThread, INFINITE);
            if (dwStatus != WAIT_OBJECT_0)
            {
                hr = HR_LOG(E_FAIL);
            }
            CLOSEHANDLE(lpAdviseObj->hPollThread);
        }

        // Free the notification structures.

        while (lpAdviseObj->lpNotifyList)
        {
            hrT = HrDestroyAdviseNotify(&lpAdviseObj->lpNotifyList);
            if (FAILED(hrT))
            {
                hr = hrT;
            }
        }

        // Delete the critical section.

        DeleteCriticalSection(&lpAdviseObj->csCriticalSection);

        // Release the IMAPIProp interface.

        ULRELEASE(lpAdviseObj->lpCurrentObject);

        // Free the extension data property.

        MAPIFREEBUFFER(lpAdviseObj->lpExtensionData);

        // Free the advise object itself.

        MAPIFREEBUFFER(lpAdviseObj);
    }

    RETURN(hr);
}

//$--HrCfgAdvise----------------------------------------------------------------
//  Begin monitoring of an extension data section.  When the extension data
//  changes, the specified callback routine is called.  Note: The callback
//  routine will be called once from hrCfgAdvise() to set the initial extension
//  data values.
// -----------------------------------------------------------------------------
HRESULT HrCfgAdvise(                        // RETURNS: HRESULT
    IN  LPADVISEOBJ         lpAdviseObj,    // advise object
    IN  LPSTR              lpszSection,    // name of extension data section
    IN  LPADVISECALLBACK    lpfnCallback,   // function to call on changes
    IN  LPVOID              lpvUserContext) // user-defined context
{
    HRESULT                 hr              = NOERROR;
    LPADVISENOTIFY          lpNotify        = NULL;
    ULONG                   cchSection      = 0;
    ULONG                   cchActual       = 0;

    DEBUGPUBLIC("HrCfgAdvise()\n");

    hr = CHK_HrCfgAdvise(
        lpAdviseObj,
        lpszSection,
        lpfnCallback,
        lpvUserContext);
    if (FAILED(hr))
        RETURN(hr);

    // Allocate memory for the ADVISENOTIFY structure.

    hr = MAPIAllocateBuffer(
        sizeof(*lpNotify),
        &lpNotify);
    if (FAILED(hr))
        goto cleanup;
    ZeroMemory(lpNotify, sizeof(*lpNotify));

    // Get the number of characters in the section name.

    cchSection = lstrlen(lpszSection);

    // Allocate memory for the LPSTR section name and copy the section
    // name into it.

    hr = MAPIAllocateMore(
        cchSection + 1,
        lpNotify,
        &lpNotify->lpszOwnerTag);
    if (FAILED(hr))
        goto cleanup;

    lstrcpy(lpNotify->lpszOwnerTag, lpszSection);

    // Allocate memory for the UNICODE section name, and copy the section
    // name into it, converting to UNICODE.

    hr = MAPIAllocateMore(
        (cchSection + 1) * sizeof(WCHAR),
        lpNotify,
        &lpNotify->lpwszOwnerTag);
    if (FAILED(hr))
        goto cleanup;

SetLastError(ERROR_SUCCESS);

    cchActual = wsprintfW(lpNotify->lpwszOwnerTag, L"%hs", lpszSection);
if (GetLastError() != ERROR_SUCCESS || cchActual != cchSection)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    // Fill in the structure members.

    lpNotify->lpfnCallback = lpfnCallback;
    lpNotify->lpvUserContext = lpvUserContext;

    // Lock the object against access by other threads.

    EnterCriticalSection(&lpAdviseObj->csCriticalSection);

    // Cause the callback routine to be called for the first time.

    lpNotify->fFirstTime = TRUE;
    hr = HrUpdateAdviseNotify(lpAdviseObj->lpExtensionData, lpNotify);

    // NOTE: Don't check the return value for failure until after
    // we leave the critical section.

    // Link the structure into the object's list.

    if (SUCCEEDED(hr))
    {
        lpNotify->lpNext = lpAdviseObj->lpNotifyList;
        lpAdviseObj->lpNotifyList = lpNotify;
    }

    // Unlock the object to allow access by other threads.

    LeaveCriticalSection(&lpAdviseObj->csCriticalSection);

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpNotify);
    }

    RETURN(hr);
}

//$--HrCfgUnadvise--------------------------------------------------------------
//  End monitoring of an extension data section.
// -----------------------------------------------------------------------------
HRESULT HrCfgUnadvise(                      // RETURNS: HRESULT
    IN  LPADVISEOBJ         lpAdviseObj,    // advise object
    IN  LPSTR              lpszSection)    // name of extension data section
{
    HRESULT                 hr              = NOERROR;
    LPADVISENOTIFY *        lppNotify       = NULL;
    BOOL                    fFound          = FALSE;

    DEBUGPUBLIC("HrCfgAdvise()\n");

    hr = CHK_HrCfgUnadvise(
        lpAdviseObj,
        lpszSection);
    if (FAILED(hr))
        RETURN(hr);

    // Lock the object against access by other threads.

    EnterCriticalSection(&lpAdviseObj->csCriticalSection);

    // Search the list of ADVISENOTIFY structures for the section name we want.

    for (
        lppNotify = &lpAdviseObj->lpNotifyList;
        *lppNotify;
        lppNotify = &((*lppNotify)->lpNext))
    {
        if (!lstrcmp(lpszSection, (*lppNotify)->lpszOwnerTag))
        {
            // Found it, so destroy it.

            hr = HrDestroyAdviseNotify(lppNotify);

            fFound = TRUE;
            break;
        }
    }

    if (!fFound)
    {
        hr = HR_LOG(E_FAIL);
    }

    // Unlock the object to allow access by other threads.

    LeaveCriticalSection(&lpAdviseObj->csCriticalSection);

    RETURN(hr);
}

//
// Local Helper Routines
//

//$--PollThread-----------------------------------------------------------------
//  Function that is called as a seperate thread.  It wakes up periodically
//  and polls the extension data sections.  If any have changed, it calls
//  the routine registered for that section.
// -----------------------------------------------------------------------------
static DWORD WINAPI PollThread(             // RETURNS: error code
    IN  LPVOID          lpvThreadParam)     // pointer to advise object
{
    HRESULT             hr                  = NOERROR;
LPADVISEOBJ         lpAdviseObj         = lpvThreadParam;
    BOOL                fDone               = FALSE;
    DWORD               dwStatus            = 0;

    DEBUGPRIVATE("PollThread()\n");

    hr = CHK_PollThread(
        lpvThreadParam);
    if (FAILED(hr))
        RETURN(hr);

    // Main loop: sleep, poll object.

    while (!fDone)
    {
        // Sleep and wait for the timer.

        dwStatus = WaitForSingleObject(
            lpAdviseObj->hPollThreadStop,
            lpAdviseObj->nPollInterval);

switch (dwStatus)
{
case WAIT_TIMEOUT:
                // Poll the object.
                hr = HrCfgPollAdviseObj(lpAdviseObj);
                if (FAILED(hr))
                    goto cleanup;
                break;

case WAIT_OBJECT_0:
                // Thread terminate request.
fDone = TRUE;
break;

default:
// Some kind of error.
                hr = HR_LOG(E_FAIL);
                goto cleanup;
}
    }

cleanup:
    return(ERROR_SUCCESS);
}

//$--HrUpdateAdviseNotify-------------------------------------------------------
//  Compare new extension data property with the old one for changes to the
//  blob defined by the ADVISENOTIFY structure.  If it has changed, then call
//  the callback routine.  In any case, point the ADVISENOTIFY structure at the
//  new extension data blob, so that the old one may be freed.
// -----------------------------------------------------------------------------
static HRESULT HrUpdateAdviseNotify(        // RETURNS: HRESULT
    IN      LPSPropValue    lpNewData,      // new extension data
    IN OUT  LPADVISENOTIFY  lpNotify)       // notification structure
{
    HRESULT             hr                  = NOERROR;
    LPSBinary           lpNewBlobArray      = NULL;
    ULONG               cNewBlobArray       = 0;
    ULONG               iBlob               = 0;
    LPBYTE              lpbNewBlob          = NULL;
    ULONG               cbNewBlob           = 0;
    LPSPropValue        lpUnpackedProps     = NULL;
    ULONG               cUnpackedProps      = 0;
    LPSTR              lpszUnpackedName    = NULL;

    DEBUGPRIVATE("HrUpdateAdviseNotify()\n");

    hr = CHK_HrUpdateAdviseNotify(
        lpNewData,
        lpNotify);
    if (FAILED(hr))
        RETURN(hr);

    // Find the owner tag in the new extension data property array.

    if (lpNewData)
    {
        lpNewBlobArray = lpNewData->Value.MVbin.lpbin;
        cNewBlobArray = lpNewData->Value.MVbin.cValues;

        for (iBlob = 0; iBlob < cNewBlobArray; iBlob++)
        {
            if (!wcscmp(
                lpNotify->lpwszOwnerTag,
                (LPWSTR) lpNewBlobArray[iBlob].lpb))
            {
                lpbNewBlob = lpNewBlobArray[iBlob].lpb;
                cbNewBlob = lpNewBlobArray[iBlob].cb;
            }
        }
    }

    // If the blob has changed then unpack the properties and call the
    // callback routine.

    if (lpNotify->fFirstTime ||
        lpNotify->cbBlob != cbNewBlob ||
        (lpNotify->lpbBlob &&
        lpbNewBlob &&
        memcmp(lpNotify->lpbBlob, lpbNewBlob, cbNewBlob)))
    {
        // If the new blob exists then unpack the properties.
        // Otherwise, use a NULL pointer for a non-existant blob.

        if (lpbNewBlob)
        {
            hr = HrCfgUnpackData(
                cbNewBlob,
                lpbNewBlob,
                &lpszUnpackedName,
                &cUnpackedProps,
                &lpUnpackedProps);
            if (FAILED(hr))
                goto cleanup;
        }

        // Call the callback routine.

        hr = (*lpNotify->lpfnCallback)(
            lpNotify->lpvUserContext,
            lpNotify->lpwszOwnerTag,
            cUnpackedProps,
            lpUnpackedProps);
        if (FAILED(hr))
            goto cleanup;

        // If the callback succeeded then zero out the unpacked property
        // variables so we don't free them (that's the user's responsiblity).

        cUnpackedProps = 0;
        lpUnpackedProps = NULL;

        // Callback has now been called at least once.

        lpNotify->fFirstTime = FALSE;
    }

    // Replace the old blob with the new one in the ADVISENOTIFY structure.

    lpNotify->lpbBlob = lpbNewBlob;
    lpNotify->cbBlob = cbNewBlob;

cleanup:
    MAPIFREEBUFFER(lpszUnpackedName);
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpUnpackedProps);
    }

    RETURN(hr);
}

//$--HrDestroyAdviseNotify------------------------------------------------------
//  Destroy an ADVISENOTIFY structure and remove it from the linked list.
//  This routine takes as its argument the address of a pointer to the
//  structure to be destroyed and returns with that pointer pointing to
//  the next structure in the linked list.
// -----------------------------------------------------------------------------
static HRESULT HrDestroyAdviseNotify(       // RETURNS: HRESULT
    IN OUT  LPADVISENOTIFY * lppNotify)     // address of ptr to ADVISENOTIFY
{
    HRESULT             hr                  = NOERROR;
    LPADVISENOTIFY      lpNotify            = NULL;

    DEBUGPRIVATE("HrDestroyAdviseNotify()\n");

    hr = CHK_HrDestroyAdviseNotify(
        lppNotify);
    if (FAILED(hr))
        RETURN(hr);

    // Save pointer to the structure.

    lpNotify = *lppNotify;

    // Take structure out of list, by making whatever pointed to it point
    // to the next thing on the list.

    *lppNotify = lpNotify->lpNext;

    // Free the structure.

    MAPIFREEBUFFER(lpNotify);

    RETURN(hr);
}


EDKCFG.C

// --edkcfg.c-------------------------------------------------------------------
//
//  Routines to aid in saving and retrieving configuration information for
//  gateways and other objects to and from the DIT.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "attrname.h"
#include "edkcfg.chk"

static HRESULT HrCfgCountProps(
    IN  int cprop,
    IN  LPSPropValue rgprop,
    OUT ULONG FAR *pcb);

static HRESULT HrCfgCopyProps(
    IN  int cprop,
    IN  LPSPropValue rgprop,
    OUT LPVOID pvDst,
    OUT ULONG FAR *pcb);

#define Align(_cb)(((_cb) + 7) & ~7)

//Pointer manipulation macros for use in the Reloc functions
#ifdef WIN16
#define SEG(_fp)HIWORD((DWORD)_fp)
#define OFF(_fp)LOWORD((DWORD)_fp)

#define PvRelocPv(_p,_baseOld,_baseNew) \
((LPVOID)MAKELONG(OFF(_p) - OFF(_baseOld) + OFF(_baseNew), SEG(_baseNew)))
#else
#define PvRelocPv(_p,_baseOld,_baseNew)\
((LPVOID)((LPBYTE)(_p) - (LPBYTE)(_baseOld) + (LPBYTE)(_baseNew)))
#endif

#define SITE_ADDRESSING_RDN "/cn=Configuration/cn=Site-Addressing"

// Magic number to indicate that this is a valid configuration data structure.
// The value 0x10101010 was chosen because older code used the byte value 0x10
// to mean "end of record", so any older code reading this structure will
// ignore the data.
#define EDKCFG_MAGIC_NUMBER0x10101010

// Version number that determines the format of the data that comes after it
// in the structure.
#define EDKCFG_VERSION_NUMBER1

// The routines in this file pack and unpack a set of MAPI properties into a
// binary data structure which can be stored in a file or the directory
// information tree.  The binary data structure has the following format:
//
// +---------------------------------------+
// | UNICODE Config Name (null-terminated) |
// +---------------------------------------+
// | DWORD EDKCFG_MAGIC_NUMBER             |
// +---------------------------------------+
// | DWORD EDKCFG_VERSION_NUMBER           |
// +---------------------------------------+
// | DWORD Number of properties            |
// +---------------------------------------+
// | Packed binary array of properties     |
// +---------------------------------------+

//$--HrCfgPackData--------------------------------------------------------------
//  Take a configuration name and a set of MAPI properties and pack them into
//  a binary buffer suitable to be saved in the directory information tree for
//  the gateway or other object.
// -----------------------------------------------------------------------------
HRESULT HrCfgPackDataA(                     // RETURN: HRESULT
INLPSTRlpszOwnerTag,// name of configuration (or NULL)
INULONGcProps,// number of MAPI properties
INLPSPropValuelpProps,// MAPI properties for this config.
OUTULONG *lpcbConfigData,// size of resulting binary struct.
OUTLPBYTE *lppbConfigData)// resulting binary structure
{
HRESULThr= NOERROR;
    LPWSTR      lpwszOwnerTag       = NULL;

DEBUGPUBLIC("HrCfgPackDataA()\n");

hr = CHK_HrCfgPackDataA(
lpszOwnerTag,
cProps,
lpProps,
lpcbConfigData,
lppbConfigData);
if (FAILED(hr))
RETURN(hr);

    // Convert config name to word string.

    if (lpszOwnerTag)
    {
        hr = HrStrAToStrW(lpszOwnerTag, &lpwszOwnerTag);
        if (FAILED(hr))
            goto cleanup;
    }

    // Do the packing operation.

    hr = HrCfgPackDataW(
        lpwszOwnerTag,
        cProps,
        lpProps,
        lpcbConfigData,
        lppbConfigData);
    if (FAILED(hr))
        goto cleanup;

cleanup:
    MAPIFREEBUFFER(lpwszOwnerTag);

    RETURN(hr);
}

HRESULT HrCfgPackDataW(                     // RETURN: HRESULT
INLPWSTRlpwszOwnerTag,    // name of configuration (or NULL)
INULONGcProps,// number of MAPI properties
INLPSPropValuelpProps,// MAPI properties for this config.
OUTULONG *lpcbConfigData,// size of resulting binary struct.
OUTLPBYTE *lppbConfigData)// resulting binary structure
{
HRESULThr= NOERROR;

ULONGcbConfigData= 0;
LPBYTEpbConfigData= NULL;

ULONGib= 0;
ULONGibOwnerTag    = 0;
ULONGcchOwnerTag    = 0;
ULONGibMagicNumber= 0;
ULONGibVersionNumber= 0;
ULONGibPropCount= 0;
ULONGibPackedProps= 0;
ULONGcbPackedProps= 0;

DEBUGPUBLIC("HrCfgPackDataW()\n");

hr = CHK_HrCfgPackDataW(
lpwszOwnerTag,
cProps,
lpProps,
lpcbConfigData,
lppbConfigData);
if (FAILED(hr))
RETURN(hr);

    // If they passed NULL for configuration name then use an empty string.

    if (lpwszOwnerTag == NULL)
    {
        lpwszOwnerTag = L"";
    }

// Figure out how much memory to allocate, and the offsets of the various
// fields within the allocated memory.  Also figure out the size of the
// packed property structure.

ib = 0;

ibOwnerTag = ib;
cchOwnerTag = wcslen(lpwszOwnerTag);
ib += ((cchOwnerTag + 1) * sizeof(WCHAR));

ibMagicNumber = ib;
ib += sizeof(DWORD);

ibVersionNumber = ib;
ib += sizeof(DWORD);

ibPropCount = ib;
ib += sizeof(DWORD);

if (cProps)
{
ibPackedProps = ib;
hr = HrCfgCountProps(cProps, lpProps, &cbPackedProps);
if (FAILED(hr))
goto cleanup;
ib += cbPackedProps;
}

cbConfigData = ib;

// Allocate the memory.

hr = MAPIAllocateBuffer(cbConfigData, &pbConfigData);
if (FAILED(hr))
goto cleanup;

// Write the properties (if any) to the buffer.  This must be done first.

if (cProps)
{
ULONG cbSizeRequired = 0;

// Convert the properties to a packed binary structure AT THE FRONT
// of the memory we just allocated.  It will be moved to the back
// later.  This makes sure the conversion output buffer is aligned
// on an eight-byte boundry.

hr = HrCfgCopyProps(cProps, lpProps, pbConfigData, &cbSizeRequired);
if (FAILED(hr))
goto cleanup;
if (cbSizeRequired > cbPackedProps)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Make all pointers relative to the start of the property array.

hr = ScRelocProps(cProps, (LPSPropValue)pbConfigData, pbConfigData, 0,
&cbSizeRequired);
if (FAILED(hr))
goto cleanup;
if (cbSizeRequired > cbPackedProps)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Now copy the packed property array to the back of the buffer.

MoveMemory(pbConfigData + ibPackedProps, pbConfigData, cbPackedProps);
}

// Write the configuration name to the buffer.

{
        wcscpy((LPWSTR)(pbConfigData + ibOwnerTag), lpwszOwnerTag);
}

// Write magic number, version number, and number of properties
// to the buffer.

{
DWORDdwMagicNumber= EDKCFG_MAGIC_NUMBER;
DWORDdwVersionNumber= EDKCFG_VERSION_NUMBER;
DWORDdwPropCount= cProps;

MoveMemory(pbConfigData + ibMagicNumber, &dwMagicNumber,
sizeof(dwMagicNumber));
MoveMemory(pbConfigData + ibVersionNumber, &dwVersionNumber,
sizeof(dwVersionNumber));
MoveMemory(pbConfigData + ibPropCount, &dwPropCount,
sizeof(dwPropCount));
}

// Return the address and size of the buffer to the caller.

*lpcbConfigData = cbConfigData;
*lppbConfigData = pbConfigData;

cleanup:
if (FAILED(hr))
{
MAPIFREEBUFFER(pbConfigData);
}

RETURN(hr);
}

//$--HrCfgUnpackData------------------------------------------------------------
//  Take a packed binary buffer and unpack it to yield a configuration name,
//  number of properties, and an array of MAPI properties.  If the number of
//  properties is zero then NULL is returned for the array of properties.
// -----------------------------------------------------------------------------
HRESULT HrCfgUnpackDataA(// RETURNS: HRESULT
INULONGcbConfigData,// size of of binary structure
INLPBYTElpbConfigData,// pointer to binary structure
OUTLPSTR *    lppszOwnerTag,    // name of configuration (or NULL)
OUTULONG *lpcProps,// number of MAPI properties
OUTLPSPropValue *lppProps)// MAPI properties for this config.
{
HRESULThr= NOERROR;
    LPWSTR          pwszOwnerTag    = NULL;
    LPSTR           pszOwnerTag     = NULL;

DEBUGPUBLIC("HrCfgUnpackDataA()\n");

hr = CHK_HrCfgUnpackDataA(
cbConfigData,
lpbConfigData,
lppszOwnerTag,
lpcProps,
lppProps);
if (FAILED(hr))
RETURN(hr);

    // If they want the name returned then call the UNICODE version
    // and then convert the name to a byte string.

    if (lppszOwnerTag)
    {
        hr = HrCfgUnpackDataW(
            cbConfigData,
            lpbConfigData,
            &pwszOwnerTag,
            lpcProps,
            lppProps);
        if (FAILED(hr))
            goto cleanup;

        hr = HrStrWToStrA(pwszOwnerTag, &pszOwnerTag);
        if (FAILED(hr))
            RETURN(hr);

        *lppszOwnerTag = pszOwnerTag;
    }

    // If they don't care about the name, then just call the UNICODE
    // version and return.

    else
    {
        hr = HrCfgUnpackDataW(
            cbConfigData,
            lpbConfigData,
            NULL,
            lpcProps,
            lppProps);
        if (FAILED(hr))
            goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER(pwszOwnerTag);

    RETURN(hr);
}

HRESULT HrCfgUnpackDataW(// RETURNS: HRESULT
INULONGcbConfigData,// size of of binary structure
INLPBYTElpbConfigData,// pointer to binary structure
OUTLPWSTR *lppwszOwnerTag,    // name of configuration (or NULL)
OUTULONG *lpcProps,// number of MAPI properties
OUTLPSPropValue *lppProps)// MAPI properties for this config.
{
HRESULThr= NOERROR;
LPBYTEpb= lpbConfigData;
ULONGcb= cbConfigData;
LPWSTRpwszOwnerTag= NULL;
ULONGcProps= 0;
LPSPropValuepProps= NULL;
    ULONG           cchOwnerTag     = 0;

DEBUGPUBLIC("HrCfgUnpackDataW()\n");

hr = CHK_HrCfgUnpackDataW(
cbConfigData,
lpbConfigData,
lppwszOwnerTag,
lpcProps,
lppProps);
if (FAILED(hr))
RETURN(hr);

// If they requested the configuration name then get it.

if (lppwszOwnerTag)
{

        hr = HrStrWToStrW((LPWSTR)pb, &pwszOwnerTag);
if (FAILED(hr))
goto cleanup;
}

    cchOwnerTag = wcslen((LPWSTR)pb);
pb += ((cchOwnerTag + 1) * sizeof(WCHAR));
cb -= ((cchOwnerTag + 1) * sizeof(WCHAR));

// Make sure the magic number is correct.

{
DWORD dwMagicNumber = 0;

MoveMemory(&dwMagicNumber, pb, sizeof(dwMagicNumber));
pb += sizeof(dwMagicNumber);
cb -= sizeof(dwMagicNumber);

if (dwMagicNumber != EDKCFG_MAGIC_NUMBER)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Make sure the version number is correct.

{
DWORD dwVersionNumber = 0;

MoveMemory(&dwVersionNumber, pb, sizeof(dwVersionNumber));
pb += sizeof(dwVersionNumber);
cb -= sizeof(dwVersionNumber);

if (dwVersionNumber != EDKCFG_VERSION_NUMBER)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Get the number of properties.

{
DWORD dwPropCount = 0;

MoveMemory(&dwPropCount, pb, sizeof(dwPropCount));
pb += sizeof(dwPropCount);
cb -= sizeof(dwPropCount);
cProps = dwPropCount;
}

// Get and unpack the property structure if there are any properties
    // (otherwise we return NULL for the pointer to the property array).

    if (cProps)
{
ULONG cbSizeRequired = 0;

hr = MAPIAllocateBuffer(cb, &pProps);
if (FAILED(hr))
goto cleanup;

if (cProps)
{
MoveMemory(pProps, pb, cb);
hr = ScRelocProps(cProps, pProps, 0, pProps, &cbSizeRequired);
if (FAILED(hr))
goto cleanup;
            if (cbSizeRequired > cb)
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
}
}

// Fill in output parameters.

if (lppwszOwnerTag)
{
*lppwszOwnerTag = pwszOwnerTag;
}
*lpcProps = cProps;
*lppProps = pProps;

cleanup:
if (FAILED(hr))
{
MAPIFREEBUFFER(pwszOwnerTag);
MAPIFREEBUFFER(pProps);
}

RETURN(hr);
}

//$--HrOpenSiteContainer-----------------------------------------------------------------
//  Takes a pointer to a MAPI session and returns an IMAPIProp interface for
//  the current site object.
// -----------------------------------------------------------------------------
HRESULT HrOpenSiteContainer(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,// MAPI session
OUTLPMAPIPROP *lppSite)// current site
{
HRESULThr= NOERROR;
LPMAPIPROPpCurrentObjectInterface= NULL;
LPSPropValuepEmailAddressProps= NULL;
LPSTRpszDN= NULL;
ULONGcbSiteEntryID= 0;
LPENTRYIDpSiteEntryID= NULL;
LPMAPIPROPpSiteInterface= NULL;

DEBUGPUBLIC("HrOpenSiteContainer()\n");

hr = CHK_HrOpenSiteContainer(
lpSession,
lppSite);
if (FAILED(hr))
RETURN(hr);

// Get an IMAPIProp interface for the current object.

hr = HrOpenSessionObject(lpSession, &pCurrentObjectInterface);
if (FAILED(hr))
goto cleanup;

// Get the PR_EMAIL_ADDRESS property.

{
SPropTagArray GetEmailAddress = {1, {PR_EMAIL_ADDRESS_A}};
ULONG cProps = 0;

hr = MAPICALL(pCurrentObjectInterface)->GetProps(
pCurrentObjectInterface,
&GetEmailAddress,
0L,
&cProps,
&pEmailAddressProps);
if (cProps != 1)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
if (hr == MAPI_W_ERRORS_RETURNED)
{
hr = HR_LOG(pEmailAddressProps->Value.err);
goto cleanup;
}
if (FAILED(hr))
goto cleanup;
}

// Create the DN from the PR_EMAIL_ADDRESS property.

{
LPSTR pszSrc = pEmailAddressProps->Value.lpszA;
LPSTR pszSrcLim = pszSrc;
ULONG cbDN = 0;

// Find the first '/' after the "/o=" and "/ou=", to extract the
// DN from the e-mail address.

while (*pszSrcLim && (
*pszSrcLim != '/' ||
!_strnicmp(pszSrcLim,"/o=",3) ||
!_strnicmp(pszSrcLim,"/ou=",4)))
{
pszSrcLim++;
}
cbDN = pszSrcLim - pszSrc;

// Copy the string into an allocated buffer.

hr = MAPIAllocateBuffer(cbDN + 1, &pszDN);
if (FAILED(hr))
goto cleanup;

MoveMemory(pszDN, pszSrc, cbDN);
pszDN[cbDN] = 0;
}

// Create a directory entry ID for this site using the DN.

{
hr = HrCreateDirEntryId(pszDN, &cbSiteEntryID, &pSiteEntryID);
if (FAILED(hr))
goto cleanup;
}

// Open the site entry ID and get an IMAPIProp interface.

{
ULONG ulObjType = 0;

hr = MAPICALL(lpSession)->OpenEntry(
lpSession,
cbSiteEntryID,
pSiteEntryID,
&IID_IMAPIProp,
MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *)&pSiteInterface);
if (FAILED(hr))
goto cleanup;
}

// Everything was successful, so return the site interface to the caller.

*lppSite = pSiteInterface;

cleanup:

ULRELEASE(pCurrentObjectInterface);

MAPIFREEBUFFER(pEmailAddressProps);
MAPIFREEBUFFER(pszDN);
MAPIFREEBUFFER(pSiteEntryID);

if (FAILED(hr))
{
ULRELEASE(pSiteInterface);
*lppSite = NULL;
}

RETURN(hr);
}

//$--HrOpenSiteContainerAddressing-------------------------------------------------------
//  Takes a pointer to a MAPI session and returns an IMAPIProp interface for
//  the current site addressing object.
// -----------------------------------------------------------------------------
HRESULT HrOpenSiteContainerAddressing(               // RETURNS: HRESULT
INLPMAPISESSIONlpSession,          // MAPI session
OUTLPMAPIPROP *lppSiteAddressing)  // current site addressing
{
HRESULThr= NOERROR;
LPMAPIPROPpCurrentObjectInterface= NULL;
LPSPropValuepEmailAddressProps= NULL;
LPSTRpszDN= NULL;
ULONGcbSiteAddrEntryID       = 0;
LPENTRYIDpSiteAddrEntryID        = NULL;
LPMAPIPROPpSiteAddrInterface      = NULL;

DEBUGPUBLIC("HrOpenSiteContainerAddressing()\n");

hr = CHK_HrOpenSiteContainerAddressing(
lpSession,
lppSiteAddressing);
if (FAILED(hr))
RETURN(hr);

// Get an IMAPIProp interface for the current object.

hr = HrOpenSessionObject(lpSession, &pCurrentObjectInterface);
if (FAILED(hr))
goto cleanup;

// Get the PR_EMAIL_ADDRESS property.

{
SPropTagArray GetEmailAddress = {1, {PR_EMAIL_ADDRESS_A}};
ULONG cProps = 0;

hr = MAPICALL(pCurrentObjectInterface)->GetProps(
pCurrentObjectInterface,
&GetEmailAddress,
0L,
&cProps,
&pEmailAddressProps);
if (cProps != 1)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
if (hr == MAPI_W_ERRORS_RETURNED)
{
hr = HR_LOG(pEmailAddressProps->Value.err);
goto cleanup;
}
if (FAILED(hr))
goto cleanup;
}

// Create the DN from the PR_EMAIL_ADDRESS property.

{
LPSTR pszSrc    = pEmailAddressProps->Value.lpszA;
LPSTR pszSrcLim = pszSrc;
ULONG cbDN      = 0;
ULONG cbRDN     = 0;

// Find the first '/' after the "/o=" and "/ou=", to extract the
// DN from the e-mail address.

while (*pszSrcLim && (
*pszSrcLim != '/' ||
!_strnicmp(pszSrcLim,"/o=",3) ||
!_strnicmp(pszSrcLim,"/ou=",4)))
{
pszSrcLim++;
}

cbDN  = pszSrcLim - pszSrc;

        cbRDN = strlen(SITE_ADDRESSING_RDN);

// Copy the string into an allocated buffer.

hr = MAPIAllocateBuffer(cbDN + cbRDN + 1, &pszDN);
if (FAILED(hr))
goto cleanup;

MoveMemory(pszDN, pszSrc, cbDN);
pszDN[cbDN] = 0;
        strcat(pszDN,SITE_ADDRESSING_RDN);
}

// Create a directory entry ID for this site addressing object using the DN.

{
hr = HrCreateDirEntryId(
    pszDN,
    &cbSiteAddrEntryID,
    &pSiteAddrEntryID);

if (FAILED(hr))
goto cleanup;
}

// Open the site addressing entry ID and get an IMAPIProp interface.

{
ULONG ulObjType = 0;

hr = MAPICALL(lpSession)->OpenEntry(
lpSession,
cbSiteAddrEntryID,
pSiteAddrEntryID,
&IID_IMAPIProp,
MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *)&pSiteAddrInterface);
if (FAILED(hr))
goto cleanup;
}

// Everything was successful, so return the site addressing interface
// to the caller.

*lppSiteAddressing = pSiteAddrInterface;

cleanup:

ULRELEASE(pCurrentObjectInterface);

MAPIFREEBUFFER(pEmailAddressProps);
MAPIFREEBUFFER(pszDN);
MAPIFREEBUFFER(pSiteAddrEntryID);

if (FAILED(hr))
{
ULRELEASE(pSiteAddrInterface);
*lppSiteAddressing = NULL;
}

RETURN(hr);
}

//$--HrOpenSessionObject--------------------------------------------------------
//  Takes a pointer to a MAPI session and returns an IMAPIProp interface for
//  the current object that the session is logged in as.
// -----------------------------------------------------------------------------
HRESULT HrOpenSessionObject(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,// MAPI session
OUTLPMAPIPROP *lppCurrentObject)// current object
{
HRESULThr= NOERROR;
ULONGcbSessionEntryID= 0;
LPENTRYIDpSessionEntryID= NULL;
LPMAPIPROPpCurrentObjectInterface= NULL;

DEBUGPUBLIC("HrOpenSessionObject()\n");

hr = CHK_HrOpenSessionObject(
lpSession,
lppCurrentObject);
if (FAILED(hr))
RETURN(hr);

// Get the entry ID for the session.

{
hr = MAPICALL(lpSession)->QueryIdentity(
lpSession,
&cbSessionEntryID,
&pSessionEntryID);
if (FAILED(hr))
goto cleanup;
}

// Open the entry ID and get an IMAPIProp interface.

{
ULONG ulObjType = 0;

hr = MAPICALL(lpSession)->OpenEntry(
lpSession,
cbSessionEntryID,
pSessionEntryID,
&IID_IMAPIProp,
MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *)&pCurrentObjectInterface);
if (FAILED(hr))
goto cleanup;
}

// Everything was successful, so return the site interface to the caller.

*lppCurrentObject = pCurrentObjectInterface;

cleanup:
    if (FAILED(hr))
    {
        ULRELEASE(pCurrentObjectInterface);
        *lppCurrentObject = NULL;
    }
MAPIFREEBUFFER(pSessionEntryID);
RETURN(hr);
}

//$--HrCfgPropertyTagFromNameId----------------------------------------------------
//  Takes a pointer to an IMAPIProp interface and a name ID, and returns the
//  MAPI property tag that represents the given name ID for that interface.
// -----------------------------------------------------------------------------
HRESULT HrCfgPropertyTagFromNameId(// RETURNS: HRESULT
INLPMAPIPROPlpMAPIProp,// IMAPIProp interface
    IN  LONG            lNameID,            // Name ID
OUTULONG *lpulPropTag)// MAPI property tag of name ID
{
HRESULThr= NOERROR;
MAPINAMEIDsNameID= {0};
LPMAPINAMEIDrglpNameID[1]= {&sNameID};
LPSPropTagArraylpPropTags= NULL;

DEBUGPUBLIC("HrCfgPropertyTagFromNameId()\n");

hr = CHK_HrCfgPropertyTagFromNameId(
lpMAPIProp,
lNameID,
lpulPropTag);
if (FAILED(hr))
RETURN(hr);

// Construct the MAPINAMEID.

sNameID.lpguid = (LPGUID)PS_EXCHANGE_DIRECTORY;
sNameID.ulKind = MNID_ID;
sNameID.Kind.lID = lNameID;

// Get the property tag.

hr = MAPICALL(lpMAPIProp)->GetIDsFromNames(
lpMAPIProp,
1,
rglpNameID,
0,
&lpPropTags);
if (FAILED(hr))
goto cleanup;

// Extract the property tag from the array.

*lpulPropTag = lpPropTags->aulPropTag[0];

cleanup:
MAPIFREEBUFFER(lpPropTags);
RETURN(hr);
}

//$--HrCfgCountProps------------------------------------------------------------
//  Counts the size of the memory needed to hold a property array.
//  This works like the MAPI routine ScCountProps but always uses
//  RISC-like 8-byte alignment.
// -----------------------------------------------------------------------------
static HRESULT HrCfgCountProps(             // RETURNS: HRESULT
    IN  int cprop,                          // number of properties
    IN  LPSPropValue rgprop,                // array of properties
    OUT ULONG FAR *pcb)                     // number of bytes needed
{
    HRESULT             hr                  = NOERROR;
LPSPropValue    pprop               = NULL;
ULONG    cb                  = 0;
ULONG    cbMV                = 0;
int    iValue              = 0;

    DEBUGPRIVATE("HrCfgCountProps()");

for (pprop = rgprop; cprop--; ++pprop)
{
ULONGulID = PROP_ID(pprop->ulPropTag);
ULONGulType = PROP_TYPE(pprop->ulPropTag);

//Check for valid PROP_ID.

if (   (ulID == PROP_ID_INVALID)
|| ((ulType == PT_NULL) && (ulID != PROP_ID_NULL))
|| ((ulType != PT_NULL) && (ulID == PROP_ID_NULL)))
        {
            hr = E_INVALIDARG;
            goto cleanup;
        }

//Check for valid PROP_TYPE and count memory consumed.

cb += sizeof(SPropValue);
switch ( PROP_TYPE(pprop->ulPropTag) )
{
case PT_UNSPECIFIED:
default:
                hr = E_INVALIDARG;
                goto cleanup;

case PT_I2:
case PT_LONG:
case PT_R4:
case PT_APPTIME:
case PT_DOUBLE:
case PT_BOOLEAN:
case PT_CURRENCY:
case PT_SYSTIME:
case PT_I8:
case PT_ERROR:
case PT_OBJECT:
case PT_NULL:
break;

case PT_CLSID:
if (IsBadReadPtr(pprop->Value.lpguid, sizeof(GUID)))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align(sizeof(GUID));
break;

case PT_BINARY:
if (pprop->Value.bin.cb && IsBadReadPtr( pprop->Value.bin.lpb
, (UINT) (pprop->Value.bin.cb)))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align(pprop->Value.bin.cb);
break;

case PT_STRING8:
if (IsBadStringPtrA(pprop->Value.lpszA, INFINITE))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align((lstrlenA( pprop->Value.lpszA ) + 1) * sizeof(CHAR));
break;

case PT_UNICODE:
if (IsBadStringPtrW(pprop->Value.lpszW, INFINITE))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align((lstrlenW( pprop->Value.lpszW ) + 1) * sizeof(WCHAR));
break;

case PT_MV_I2:
if (   !(cbMV = pprop->Value.MVi.cValues * sizeof(short int))
|| IsBadReadPtr(pprop->Value.MVi.lpi, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align(cbMV);
break;

case PT_MV_LONG:
if (   !(cbMV = pprop->Value.MVl.cValues * sizeof(LONG))
|| IsBadReadPtr(pprop->Value.MVl.lpl, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align(cbMV);
break;

case PT_MV_R4:
if (   !(cbMV = pprop->Value.MVflt.cValues * sizeof(float))
|| IsBadReadPtr(pprop->Value.MVflt.lpflt, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += Align(cbMV);
break;

case PT_MV_APPTIME:
if (   !(cbMV = pprop->Value.MVat.cValues * sizeof(double))
|| IsBadReadPtr(pprop->Value.MVat.lpat, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_DOUBLE:
if (   !(cbMV = pprop->Value.MVdbl.cValues * sizeof(double))
|| IsBadReadPtr(pprop->Value.MVdbl.lpdbl, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_CURRENCY:
if (   !(cbMV = pprop->Value.MVcur.cValues * sizeof(CURRENCY))
|| IsBadReadPtr(pprop->Value.MVcur.lpcur, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_SYSTIME:
if (   !(cbMV = pprop->Value.MVft.cValues * sizeof(FILETIME))
|| IsBadReadPtr(pprop->Value.MVft.lpft, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_CLSID:
if (   !(cbMV = pprop->Value.MVguid.cValues * sizeof(GUID))
|| IsBadReadPtr(pprop->Value.MVguid.lpguid, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_I8:
if (   !(cbMV = pprop->Value.MVli.cValues * sizeof(LARGE_INTEGER))
|| IsBadReadPtr(pprop->Value.MVli.lpli, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
cb += cbMV;
break;

case PT_MV_BINARY:
if (   !(cbMV = pprop->Value.MVbin.cValues * sizeof(SBinary))
|| IsBadReadPtr(pprop->Value.MVbin.lpbin, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }

ASSERTERROR(Align(cbMV) == cbMV, "Align(cbMV) != cbMV");
cb += cbMV;

for ( iValue = 0;
  (ULONG)iValue < pprop->Value.MVbin.cValues;
  iValue++ )
{
if (IsBadReadPtr(pprop->Value.MVbin.lpbin[iValue].lpb,
(UINT)pprop->Value.MVbin.lpbin[iValue].cb))
                    {
                        hr = E_INVALIDARG;
                        goto cleanup;
                    }
cb += Align(pprop->Value.MVbin.lpbin[iValue].cb);
}
break;

case PT_MV_STRING8:
if (   !(cbMV = pprop->Value.MVszA.cValues * sizeof(LPVOID))
|| IsBadReadPtr(pprop->Value.MVszA.lppszA, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }

cb += cbMV;

for ( iValue = 0;
  (ULONG)iValue < pprop->Value.MVszA.cValues;
  iValue++ )
{
if (IsBadStringPtrA(pprop->Value.MVszA.lppszA[iValue],
INFINITE))
                    {
                        hr = E_INVALIDARG;
                        goto cleanup;

}
cb += lstrlenA(pprop->Value.MVszA.lppszA[iValue]) + 1;
}

cb = Align(cb);

break;

case PT_MV_UNICODE:
if (   !(cbMV = pprop->Value.MVszW.cValues * sizeof(LPVOID))
|| IsBadReadPtr(pprop->Value.MVszW.lppszW, (UINT) cbMV))
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }

cb += cbMV;

for ( iValue = 0;
  (ULONG)iValue < pprop->Value.MVszW.cValues;
  iValue++ )
{
if (IsBadStringPtrW(pprop->Value.MVszW.lppszW[iValue], INFINITE))
                    {
                        hr = E_INVALIDARG;
                        goto cleanup;
                    }
cb += (lstrlenW(pprop->Value.MVszW.lppszW[iValue]) + 1)
  * sizeof(WCHAR);
}

cb = Align(cb);

break;
}
}

if (pcb)
{
*pcb = cb;
}

cleanup:
RETURN(hr);
}

//$--HrCfgCopyProps-------------------------------------------------------------
//  Copies a prop table into a single block of memory.
//  This works like the MAPI routine ScCopyProps but always uses
//  RISC-like 8-byte alignment.
// -----------------------------------------------------------------------------
static HRESULT HrCfgCopyProps(              // RETURNS: HRESULT
    IN  int cprop,                          // number of properties
    IN  LPSPropValue rgprop,                // array of properties
    OUT LPVOID pvDst,                       // pointer to destination memory
    OUT ULONG FAR *pcb)                     // number of bytes needed
{
    HRESULT             hr                  = NOERROR;
LPSPropValue    pprop               = NULL;
LPSPropValue    ppropDst            = NULL;
ULONG    cb                  = 0;
ULONG    cbMV                = 0;
LPBYTE    pb                  = NULL;
UINT    cbT                 = 0;
int    iValue              = 0;

    DEBUGPRIVATE("HrCfgCopyProps()");

cb = cprop * sizeof(SPropValue);
CopyMemory(pvDst, rgprop, (UINT)cb);
pb = (LPBYTE)pvDst + cb;

for (pprop = rgprop, ppropDst = pvDst; cprop--; ++pprop, ++ppropDst)
{
//Tricky: common code after the switch increments pb and cb
//by the amount copied. If no increment is necessary, the case
//uses 'continue' rather than 'break' to exit the switch, thus
//skipping the increment -- AND any other code which may be
//added after the switch.

switch ( PROP_TYPE(pprop->ulPropTag) )
{
default:
                hr = E_INVALIDARG;
                goto cleanup;

case PT_I2:
case PT_LONG:
case PT_R4:
case PT_APPTIME:
case PT_DOUBLE:
case PT_BOOLEAN:
case PT_CURRENCY:
case PT_SYSTIME:
case PT_I8:
case PT_ERROR:
case PT_OBJECT:
case PT_NULL:
continue;//nothing to add

case PT_CLSID:
ppropDst->Value.lpguid = (LPGUID) pb;
cbT = sizeof(GUID);
CopyMemory(pb, (LPBYTE) pprop->Value.lpguid, cbT);
break;

case PT_BINARY:
ppropDst->Value.bin.lpb = pb;
cbT = (UINT)pprop->Value.bin.cb;
CopyMemory(pb, pprop->Value.bin.lpb, cbT);
break;

case PT_STRING8:
ppropDst->Value.lpszA = (LPSTR)pb;
cbT = lstrlenA( pprop->Value.lpszA ) + 1;
CopyMemory(pb, pprop->Value.lpszA, cbT);
break;

case PT_UNICODE:
ppropDst->Value.lpszW = (LPWSTR)pb;
cbT = (lstrlenW( pprop->Value.lpszW ) + 1) * sizeof(WCHAR);
CopyMemory(pb, pprop->Value.lpszW, cbT);
break;

case PT_MV_I2:
ppropDst->Value.MVi.lpi = (short int FAR *)pb;
cbT = (UINT)pprop->Value.MVi.cValues * sizeof(short int);
CopyMemory(pb, pprop->Value.MVi.lpi, cbT);
break;

case PT_MV_LONG:
ppropDst->Value.MVl.lpl = (LONG FAR *)pb;
cbT = (UINT)pprop->Value.MVl.cValues * sizeof(LONG);
CopyMemory(pb, pprop->Value.MVl.lpl, cbT);
break;

case PT_MV_R4:
ppropDst->Value.MVflt.lpflt = (float FAR *)pb;
cbT = (UINT)pprop->Value.MVflt.cValues * sizeof(float);
CopyMemory(pb, pprop->Value.MVflt.lpflt, cbT);
break;

case PT_MV_APPTIME:
ppropDst->Value.MVat.lpat = (double FAR *)pb;
cbT = (UINT)pprop->Value.MVat.cValues * sizeof(double);
CopyMemory(pb, pprop->Value.MVat.lpat, cbT);
break;

case PT_MV_DOUBLE:
ppropDst->Value.MVdbl.lpdbl = (double FAR *)pb;
cbT = (UINT)pprop->Value.MVdbl.cValues * sizeof(double);
CopyMemory(pb, pprop->Value.MVdbl.lpdbl, cbT);
break;

case PT_MV_CURRENCY:
ppropDst->Value.MVcur.lpcur = (CURRENCY FAR *)pb;
cbT = (UINT)pprop->Value.MVcur.cValues * sizeof(CURRENCY);
CopyMemory(pb, pprop->Value.MVcur.lpcur, cbT);
break;

case PT_MV_SYSTIME:
ppropDst->Value.MVft.lpft = (FILETIME FAR *)pb;
cbT = (UINT)pprop->Value.MVft.cValues * sizeof(FILETIME);
CopyMemory(pb, pprop->Value.MVft.lpft, cbT);
break;

case PT_MV_CLSID:
ppropDst->Value.MVguid.lpguid = (GUID FAR *)pb;
cbT = (UINT)pprop->Value.MVguid.cValues * sizeof(GUID);
CopyMemory(pb, pprop->Value.MVguid.lpguid, cbT);
break;

case PT_MV_I8:
ppropDst->Value.MVli.lpli = (LARGE_INTEGER FAR *)pb;
cbT = (UINT)pprop->Value.MVli.cValues * sizeof(LARGE_INTEGER);
CopyMemory(pb, pprop->Value.MVli.lpli, cbT);
break;

case PT_MV_BINARY:
ppropDst->Value.MVbin.lpbin = (SBinary *) pb;
cbMV = pprop->Value.MVbin.cValues * sizeof(SBinary);
pb += cbMV;
cb += cbMV;
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVbin.cValues;
  iValue++)
{
ppropDst->Value.MVbin.lpbin[iValue].lpb = pb;
cbT = (UINT)pprop->Value.MVbin.lpbin[iValue].cb;
ppropDst->Value.MVbin.lpbin[iValue].cb = (ULONG)cbT;
CopyMemory(pb, pprop->Value.MVbin.lpbin[iValue].lpb, cbT);
cbT = Align(cbT);
cb += cbT;
pb += cbT;
}
continue;//already updated, don't do it again

case PT_MV_STRING8:
ppropDst->Value.MVszA.lppszA = (LPSTR *) pb;
cbMV = pprop->Value.MVszA.cValues * sizeof(LPSTR);
pb += cbMV;
cb += cbMV;
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVszA.cValues;
  iValue++)
{
ppropDst->Value.MVszA.lppszA[iValue] = (LPSTR)pb;
cbT = lstrlenA(pprop->Value.MVszA.lppszA[iValue]) + 1;
CopyMemory(pb, pprop->Value.MVszA.lppszA[iValue], cbT);
pb += cbT;
cb += cbT;
}
cbT = (UINT)Align(cb);
pb += cbT - cb;
cb  = cbT;
continue;//already updated, don't do it again

case PT_MV_UNICODE:
ppropDst->Value.MVszW.lppszW = (LPWSTR *) pb;
cbMV = pprop->Value.MVszW.cValues * sizeof(LPWSTR);
pb += cbMV;
cb += cbMV;
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVszW.cValues;
  iValue++)
{
ppropDst->Value.MVszW.lppszW[iValue] = (LPWSTR)pb;
cbT = (lstrlenW(pprop->Value.MVszW.lppszW[iValue]) + 1)
* sizeof(WCHAR);
CopyMemory(pb, pprop->Value.MVszW.lppszW[iValue], cbT);
pb += cbT;
cb += cbT;
}
cbT = (UINT)Align(cb);
pb += cbT - cb;
cb  = cbT;
continue;//already updated, don't do it again
}

//Advance pointer and total count by the amount copied
cbT = Align(cbT);
pb += cbT;
cb += cbT;
}

if (pcb)
{
*pcb = cb;
}

cleanup:
RETURN(hr);
}


_EXCHDBG.H

// --_exchdbg.h-----------------------------------------------------------------
//
//  Header file for internal DEBUG functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _EXCHDBG_H
#define _EXCHDBG_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

#ifdef DEBUG
#define REPORT_ERROR(x) \
    { \
        OutputDebugString((x)); \
        _Assert("ASSERT", lpszFile, ulLine, D_ERROR, FALSE, "%s", (x)); \
    }

#define REPORT_STATUS(x) \
    { \
        OutputDebugString((x)); \
        _Assert("DEBUG", lpszFile, ulLine, D_STATUS, FALSE, "%s", (x)); \
    }

#else
#define REPORT_ERROR(x) ((void)0)
#define REPORT_STATUS(x) ((void)0)
#endif

//$--_DebugMemoryStatus---------------------------------------------------------
//  Writes an entry in the debug log for memory status.
// -----------------------------------------------------------------------------
void _DebugMemoryStatus(                // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN ULONG cBytes);                   // count of bytes

#ifdef __cplusplus
}
#endif // __cplusplus

#endif


EDKDEBUG.C

// --edkdebug.c-----------------------------------------------------------------
//
//  Functions to log debugging information in DEBUG builds.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _PRIVATE_EDKDEBUG_H
#define _PRIVATE_MEMORY_H
#include "edk.h"

#define REPORT_ERROR(x) \
    _WriteDebugString("error: %s, %lu, %s\n", __FILE__,__LINE__,(x))

static LPSTR szNullA = "";

//$--GetComponentFileName-------------------------------------------------------
//  Return a text representation of a component file name.
// -----------------------------------------------------------------------------
static LPSTR GetComponentFileName(      // RETURNS: component file name
    IN LPSTR lpszFile)                  // pointer to file name
{
    LPSTR pszT = NULL;
    LPSTR psz  = NULL;

    if(IsBadStringPtrA(lpszFile, INFINITE))
    {
        return(szNullA);
    }

    psz  = lpszFile;

    pszT = lpszFile + lstrlen(lpszFile);

    while(pszT >= lpszFile)
    {
        if(*pszT == '\\')
        {
            psz = pszT;
        }
        else if((*pszT == ':'))
        {
            psz = pszT;
            break;
        }

        pszT--;
    }

    if((*psz == '\\') || (*psz == ':'))
    {
        psz++;
    }

    return(psz);
}

//$--BoolName-------------------------------------------------------------------
//  Return a text representation of a boolean value.
// -----------------------------------------------------------------------------
static LPSTR BoolName(                  // RETURNS: textized boolean value
    IN BOOL fStatus)                    // boolean value
{
    switch(fStatus)
    {
        case TRUE:
            return("TRUE");
        case FALSE:
            return("FALSE");
        default:
            return("UNKNOWN");
    }
}

//$--DebugLevelName-------------------------------------------------------------
//  Return a text representation of a debug level.
// -----------------------------------------------------------------------------
static LPSTR DebugLevelName(            // RETURNS: textized debug level
    IN DEBUGLEVEL eLevel)               // debug level
{
    switch(eLevel)
    {
        case D_PUBLIC:
            return("D_PUBLIC");
        case D_PRIVATE:
            return("D_PRIVATE");
        case D_ERROR:
            return("D_ERROR");
        case D_WARNING:
            return("D_WARNING");
        case D_STATUS:
            return("D_STATUS");
        case D_ACTION:
            return("D_ACTION");
        case D_OTHER:
            return("D_OTHER");
        default:
            return("UNKNOWN");
    }
}

//$--_ShowAssert----------------------------------------------------------------
//  Prompts user for what action to take on an assertion failure.
// -----------------------------------------------------------------------------
static void _ShowAssert(                // RETURNS: nothing
    IN LPSTR lpszFile,                  // source file name
    IN ULONG ulLine)                    // source line number
{
    int  n              = 0;
    char lpsz[BUFSIZ+1] = {0};

    LPSTR  lpszPrompt
        =  "%s - line %lu\n"
                "\n"
                "Click on ABORT  to exit the application.\n"
                "Click on RETRY  to debug the application.\n"
                "Click on IGNORE to continue.";

    __try
    {
        n = _snprintf(
            lpsz,
            BUFSIZ,
            lpszPrompt,
            GetComponentFileName(lpszFile),
            ulLine);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        REPORT_ERROR(_exception_code());
        n = -1;
    }

    if(n < 0)
    {
        goto cleanup;
    }

    n = MessageBox(NULL,
        lpsz,
        "Assertion Failure",
        MB_ABORTRETRYIGNORE | MB_ICONSTOP | MB_TASKMODAL);

    switch(n)
    {
        case IDABORT:
            // User chose to terminate program
            ExitProcess(0);
            break;
        case IDRETRY:
            // User chose to trap into the debugger
            DebugBreak();
            break;
        case IDIGNORE:
            // User chose to continue execution
            break;
        default:
            break;
    }

cleanup:

    return;
}

//$--_Assert--------------------------------------------------------------------
//
//  Write to the debug log file and/or evaluate assertion.
//
// -----------------------------------------------------------------------------
void _Assert(                   // RETURNS: nothing
    IN LPSTR lpszTag,           // tag name
    IN LPSTR lpszFile,          // source file name
    IN ULONG ulLine,            // source line number
    IN DEBUGLEVEL Level,        // assertion level
    IN BOOL fValue,             // assertion value
    IN LPSTR lpszFormat,        // format string
    ...)                        // arguments
{
    char       lpsz[BUFSIZ+1] = {0};
    va_list    va_alist       = {0};
    SYSTEMTIME st             = {0};
    FILETIME   ft             = {0};
    BOOL       fRet           = FALSE;

    _InitDebugFile();

    if(_StatusDebugLevel(Level) == FALSE)
    {
        goto cleanup;
    }

    if((lpszTag == NULL) || IsBadStringPtrA(lpszTag, INFINITE))
    {
        REPORT_ERROR(ERROR_INVALID_PARAMETER);
        goto cleanup;
    }

    if((lpszFile == NULL) || IsBadStringPtrA(lpszFile, INFINITE))
    {
        REPORT_ERROR(ERROR_INVALID_PARAMETER);
        goto cleanup;
    }

    if((lpszFormat == NULL) || IsBadStringPtrA(lpszFormat, INFINITE))
    {
        REPORT_ERROR(ERROR_INVALID_PARAMETER);
        goto cleanup;
    }

    _LockDebugFile();

    if(((Level != D_ERROR) && (Level != D_WARNING)) ||
       (((Level == D_ERROR) || (Level == D_WARNING)) && (fValue == FALSE)))
    {
        __try
        {
            GetSystemTime(&st);

            fRet = SystemTimeToFileTime(&st, &ft);

            if(fRet != TRUE)
            {
                ft.dwLowDateTime  = 0;
                ft.dwHighDateTime = 0;
            }

            _WriteDebugFile(
                "%08lX%08lX %s - process %lu, thread %lu, level %s, file %s, line %lu: ",
                ft.dwHighDateTime,
                ft.dwLowDateTime,
                lpszTag,
                GetCurrentProcessId(),
                GetCurrentThreadId(),
                DebugLevelName(Level),
                lpszFile,
                ulLine);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            REPORT_ERROR(_exception_code());
        }

        __try
        {
            va_start( va_alist, lpszFormat );

            _vsnprintf( lpsz, BUFSIZ, lpszFormat, va_alist );

            va_end( va_alist );

            _WriteDebugFile("%s", lpsz);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            REPORT_ERROR(_exception_code());
        }
    }

    _UnlockDebugFile();

cleanup:

    if(_StatusShowAssert() && (fValue == FALSE) && (Level == D_ERROR))
    {
        _ShowAssert(lpszFile, ulLine);
    }

    return;
}


EDKLOG.C

// --edklog.c-------------------------------------------------------------------
//
//  Functions to log debugging information in DEBUG builds.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _PRIVATE_EDKDEBUG_H
#define _PRIVATE_MEMORY_H
#include "edk.h"

#define REPORT_ERROR(x) \
    _WriteDebugString( \
        "***** error: %s, %lu, [%08lX] *****\n", __FILE__,__LINE__,(x))

#define DECL_FENCE(_name)   ULONG _dbg_##_name = 0xAAAAAAAA

#define TEST_FENCE(_name)   \
{ \
    if(_dbg_##_name != 0xAAAAAAAA) \
    { \
        _WriteDebugString( \
            "***** error: %s, %lu, memory overwrite at tag '%s'! *****\n", \
            __FILE__,__LINE__,#_name); \
    } \
}

DECL_FENCE(EDKDEBUG1);

static BOOL fDebugLevel[D_LAST] =
    {
        TRUE,
        TRUE,
        TRUE,
        TRUE,
        TRUE,
        TRUE,
        TRUE
    };

DECL_FENCE(EDKDEBUG2);

static BOOL             IsAssertOff = TRUE;

DECL_FENCE(EDKDEBUG3);

static BOOL             IsDebugOff  = FALSE;

DECL_FENCE(EDKDEBUG4);

static CHAR szFileName[MAX_PATH+1]  = {0};

DECL_FENCE(EDKDEBUG5);

static FILE*            fp          = NULL;

DECL_FENCE(EDKDEBUG6);

static CRITICAL_SECTION cs          = {0};

DECL_FENCE(EDKDEBUG7);

#define TEST_ALL_FENCES \
{ \
    TEST_FENCE(EDKDEBUG1); \
    TEST_FENCE(EDKDEBUG2); \
    TEST_FENCE(EDKDEBUG3); \
    TEST_FENCE(EDKDEBUG4); \
    TEST_FENCE(EDKDEBUG5); \
    TEST_FENCE(EDKDEBUG6); \
    TEST_FENCE(EDKDEBUG7); \
}

//$--_WriteDebugString----------------------------------------------------------
//
//  Write a string to the debugger output window.
//
// -----------------------------------------------------------------------------
void _WriteDebugString(                 // RETURNS: nothing
    IN LPSTR lpszFormat,                // format string
    ...)                                // arguments
{
    char lpsz[BUFSIZ+1] = {0};
    va_list va_alist    = {0};

    SetLastError(ERROR_SUCCESS);

    __try
    {
        va_start( va_alist, lpszFormat );

        _vsnprintf( lpsz, BUFSIZ, lpszFormat, va_alist );

        va_end( va_alist );

        OutputDebugString(lpsz);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());
    }
}

//$--_StatusShowAssert----------------------------------------------------------
//
//  Status of the assert prompt.
//
// -----------------------------------------------------------------------------
BOOL _StatusShowAssert(             // RETURNS: TRUE if assert prompt
    void)                           // no arguments
{
    TEST_ALL_FENCES // Check for memory overwrites in global variables

    return(!IsAssertOff);
}

//$--_StatusDebugFile-----------------------------------------------------------
//
//  Status of the debug log file.
//
// -----------------------------------------------------------------------------
BOOL _StatusDebugFile(              // RETURNS: TRUE if debug log available
    void)                           // no arguments
{
    BOOL fStatus = TRUE;

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    if((fp == NULL) || (IsDebugOff == TRUE))
    {
        fStatus = FALSE;
    }

    return(fStatus);
}

//$--_StatusDebugLevel----------------------------------------------------------
//
//  Status of the debug level.
//
// -----------------------------------------------------------------------------
BOOL _StatusDebugLevel(             // RETURNS: nothing
    IN DEBUGLEVEL level)            // debug level
{
    BOOL fStatus = TRUE;

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    if( (_StatusDebugFile() == FALSE) ||
        (level >= D_LAST) ||
        (fDebugLevel[(int)level] == FALSE))
    {

        fStatus = FALSE;
    }

    return(fStatus);
}

//$--_LockDebugFile-------------------------------------------------------------
//
//  Lock the debug log file.
//
// -----------------------------------------------------------------------------
void _LockDebugFile(                // RETURNS: nothing
    void)                           // no arguments
{
    CHAR sz[BUFSIZ+1] = {0};

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    __try
    {
        EnterCriticalSection(&cs);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        REPORT_ERROR(_exception_code());
    }
}

//$--_UnlockDebugFile-----------------------------------------------------------
//
//  Unlock the debug log file.
//
// -----------------------------------------------------------------------------
void _UnlockDebugFile(              // RETURNS: nothing
    void)                           // no arguments
{
    CHAR sz[BUFSIZ+1] = {0};

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    __try
    {
        LeaveCriticalSection(&cs);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        REPORT_ERROR(_exception_code());
    }
}

//$--_WriteDebugFile------------------------------------------------------------
//
//  Write to the debug log file.
//
// -----------------------------------------------------------------------------
void _WriteDebugFile(               // RETURNS: nothing
    IN LPSTR lpszFormat,            // format string
    ...)                            // arguments
{
    va_list va_alist   = {0};

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    if(fp == NULL)
    {
        REPORT_ERROR(ERROR_INVALID_PARAMETER);
        goto cleanup;
    }

    __try
    {
        EnterCriticalSection(&cs);

        __try
        {
            va_start( va_alist, lpszFormat );

            vfprintf( fp, lpszFormat, va_alist );

            va_end( va_alist );

            fflush(fp);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            REPORT_ERROR(_exception_code());
        }

        LeaveCriticalSection(&cs);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        REPORT_ERROR(_exception_code());
    }

cleanup:

    return;
}

//$--SetDebugLevel--------------------------------------------------------------
//  Set debug levels to log.
// -----------------------------------------------------------------------------
static void SetDebugLevel(              // RETURNS: nothing
    IN LPSTR lpszLevel)                 // textized debug level
{
    int   level         = 0;
    char* lpszToken     = NULL;
    char  buf[BUFSIZ+1] = {0};

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    if(lpszLevel == NULL)
    {
        goto cleanup;
    }

    if(IsBadStringPtrA(lpszLevel, INFINITE))
    {
        REPORT_ERROR(ERROR_INVALID_PARAMETER);
        goto cleanup;
    }

    IsDebugOff = TRUE;

    for(level = 0; level < D_LAST; level++)
    {
        // Turn the debug level off
        fDebugLevel[level] = FALSE;
    }

    strncpy(buf, lpszLevel, BUFSIZ);
    buf[BUFSIZ] = 0;

    lpszToken = strtok(buf,", ");

    while(lpszToken != NULL)
    {
        if(!_stricmp("D_PUBLIC", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_PUBLIC] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_PRIVATE", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_PRIVATE] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_ERROR", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_ERROR] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_WARNING", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_WARNING] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_STATUS", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_STATUS] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_ACTION", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_ACTION] = TRUE; IsDebugOff = FALSE;
        }
        else if(!_stricmp("D_OTHER", lpszToken))
        {
            // Turn the debug level on
            fDebugLevel[(int)D_OTHER] = TRUE; IsDebugOff = FALSE;
        }

        lpszToken = strtok(NULL, ", ");
    }

cleanup:

    return;
}

//$--_InitDebugFile-------------------------------------------------------------
//
//  Initialize the debug log file.
//
// -----------------------------------------------------------------------------
void _InitDebugFile(                // RETURNS: nothing
    void)                           // no arguments
{
    HANDLE hT                      = NULL;
    DWORD  dwT                     = 0;
    CHAR   szMutexName[MAX_PATH+1] = {0};
CHAR   szTempPath[MAX_PATH+1]  = {0};    // temporary file name path

    TEST_ALL_FENCES // Check for memory overwrites in global variables

    if((fp == NULL) && (IsDebugOff == FALSE))
    {
        _snprintf(szMutexName, MAX_PATH, "EDK%08lX", GetCurrentProcessId());

        hT = CreateMutex(NULL, FALSE, szMutexName);

        if(hT == NULL)
        {
            REPORT_ERROR(GetLastError());
            return;
        }

        if((hT != NULL) && (GetLastError() == ERROR_ALREADY_EXISTS))
        {
            __try
            {
                dwT = WaitForSingleObject(hT, INFINITE);

                if((dwT == WAIT_OBJECT_0) || (dwT == WAIT_ABANDONED))
                {
                    ReleaseMutex(hT);
                }

                CloseHandle(hT);
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                REPORT_ERROR(_exception_code());
            }

            return;
        }

        dwT = WaitForSingleObject(hT, INFINITE);

        if((dwT == WAIT_OBJECT_0) || (dwT == WAIT_ABANDONED))
        {
            LPSTR lpsz = NULL;

            InitializeCriticalSection(&cs);

            SetDebugLevel(getenv("EDK_DEBUG"));

            if(IsDebugOff == FALSE)
            {
                lpsz = getenv("EDK_DEBUG_FILE");

                if(lpsz == NULL)
                {
// Create a file in the temporary file directory
// with name equal to "EDKxxx.log".
dwT = GetTempPath(
MAX_PATH,// length of buffer
szTempPath);// string buffer

if ( dwT != 0 )
{
                        // If TEMP has been removed, we'll create it
                        (VOID)CreateDirectory(
                            szTempPath,
                            NULL);

dwT = GetTempFileName(
szTempPath,// temporary file path
EDK_LOG_FILE_PREFIX,// log file prefix
0,// creates unique file name
szFileName);// file name buffer

if ( dwT != 0 )
{
                            UINT    iFileSuffix = 0;

                            // Delete the edk*.tmp file created as a side
                            // effect of GetTempFileName().
                            (VOID)DeleteFile(szFileName);

                            // Replace .TMP suffix with .LOG suffix.
                            iFileSuffix = cbStrLenA(szFileName) - sizeof("TMP");
                            lstrcpyA( &szFileName[iFileSuffix], EDK_LOG_FILE_SUFFIX);
}
                        else
                        {
REPORT_ERROR(GetLastError());
                        }
}
else
{
REPORT_ERROR(GetLastError());
}
                }
                else
                {
                    strncpy((char *)szFileName, lpsz, MAX_PATH);
                }

                szFileName[MAX_PATH] = 0;

                if((fp = fopen(szFileName, "w")) == NULL)
                {
                    REPORT_ERROR(ERROR_INVALID_PARAMETER);
                }
            }

            lpsz = getenv("EDK_ASSERT");

            if((lpsz != NULL) && (lstrcmpiA(lpsz, "TRUE") == 0))
            {
                IsAssertOff = FALSE;
            }

            ReleaseMutex(hT);
        }
        else
        {
            REPORT_ERROR(GetLastError());
        }

        CloseHandle(hT);
    }
}


ERROR.C

// --error.c--------------------------------------------------------------------
//
//  Function error code wrapping functions for DEBUG builds.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _PRIVATE_EDKDEBUG_H
#define _PRIVATE_MEMORY_H
#include "edk.h"

//$--EDKDBG_HrLog---------------------------------------------------------------
//  Log an HRESULT (and the last Win32 error) to the debug log.
// -----------------------------------------------------------------------------
HRESULT EDKDBG_HrLog(                   // RETURNS: HRESULT
    IN LPSTR lpszFile,                  // file name
    IN ULONG ulLine,                    // line number
    IN HRESULT hr,                      // function return code
    IN DWORD dwLastError)               // last Win32 error
{
    _Assert(
        "ASSERT",
        lpszFile,
        ulLine,
        D_ERROR,
        SUCCEEDED(hr),
        "A function/method returned [%08lX] [%08lX]\n",
        hr,
        dwLastError);

    SetLastError(dwLastError);

    return(hr);
}

//$--EDKDBG_HrFailed------------------------------------------------------------
//  Log an HRESULT (and the last Win32 error) to the debug log.
// -----------------------------------------------------------------------------
BOOL EDKDBG_HrFailed(                   // RETURNS: HRESULT
    IN LPSTR lpszFile,                  // file name
    IN ULONG ulLine,                    // line number
    IN HRESULT hr,                      // function return code
    IN DWORD dwLastError)               // last Win32 error
{
    _Assert(
        "ASSERT",
        lpszFile,
        ulLine,
        D_ERROR,
        SUCCEEDED(hr),
        "A function/method returned [%08lX] [%08lX]\n",
        hr,
        dwLastError);

    SetLastError(dwLastError);

    return(FAILED(hr));
}


MAPIMEM.C

// --mapimem.c-------------------------------------------------------------------
//
//  Memory wrapping functions for DEBUG builds.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _PRIVATE_EDKDEBUG_H
#define _PRIVATE_MEMORY_H

#include "edk.h"
#include "_exchdbg.h"

#include <stddef.h>
#include <malloc.h>

//$--EDKDBG_MAPIAllocateBuffer--------------------------------------------------
//  Wrapper for MAPIAllocateBuffer().
// -----------------------------------------------------------------------------
SCODE EDKDBG_MAPIAllocateBuffer(        // RETURNS: status code
    IN  ULONG ulLine,                   // line number
    IN  LPSTR lpszFile,                 // file name
    IN  ULONG ulSize,                   // size of memory block
    OUT LPVOID *lppv)                   // pointer to memory block address
                                        // variable
{
    SCODE sc            = 0;
    CHAR  buf[BUFSIZ+1] = {0};

    if(IsBadWritePtr(lppv, sizeof(LPVOID)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid pointer [%08lx]\n", lppv);

        REPORT_ERROR(buf);

        sc = E_INVALIDARG;

        return(sc);
    }

    sc = MAPIAllocateBuffer(
        ulSize,
        lppv);

    if(FAILED(sc))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",ulSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,ulSize);
    }

    return(sc);
}

//$--EDKDBG_MAPIAllocateMore----------------------------------------------------
//  Wrapper for MAPIAllocateMore().
// -----------------------------------------------------------------------------
SCODE EDKDBG_MAPIAllocateMore(          // RETURNS: status code
    IN  ULONG ulLine,                   // line number
    IN  LPSTR lpszFile,                 // file name
    IN  ULONG ulSize,                   // size of memory block
    IN  LPVOID lpvOriginal,             // pointer to original memory block
    OUT LPVOID *lppvData)               // pointer to memory block address
                                        // variable
{
    SCODE sc            = 0;
    CHAR  buf[BUFSIZ+1] = {0};

    if(IsBadReadPtr(lpvOriginal, 1))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid original pointer [%08lx]\n", lpvOriginal);

        REPORT_ERROR(buf);

        sc = E_INVALIDARG;

        return(sc);
    }

    if(IsBadWritePtr(lppvData, sizeof(LPVOID)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid pointer [%08lx]\n", lppvData);

        REPORT_ERROR(buf);

        sc = E_INVALIDARG;

        return(sc);
    }

    sc = MAPIAllocateMore(
        ulSize,
        lpvOriginal,
        lppvData);

    if(FAILED(sc))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",ulSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,ulSize);
    }

    return(sc);
}

//$--EDKDBG_MAPIFreeBuffer------------------------------------------------------
//  Wrapper for MAPIFreeBuffer().
// -----------------------------------------------------------------------------
SCODE EDKDBG_MAPIFreeBuffer(            // RETURNS: status code
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN LPVOID lpv)                      // pointer to memory block
{
    SCODE sc            = 0;
    CHAR  buf[BUFSIZ+1] = {0};

    if((lpv != NULL) && IsBadWritePtr(lpv, 1))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid pointer [%08lx]\n", lpv);

        REPORT_ERROR(buf);

        sc = E_INVALIDARG;

        return(sc);
    }

    sc = MAPIFreeBuffer(lpv);

    if(FAILED(sc))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: deallocation failed\n");

        REPORT_ERROR(buf);
    }

    return(sc);
}


MEMORY.C

// --memory.c-------------------------------------------------------------------
//
//  Memory wrapping functions for DEBUG builds.
//
// Copyright 1986 - 1998 Microsoft Corporation.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _PRIVATE_EDKDEBUG_H
#define _PRIVATE_MEMORY_H

#include <malloc.h>

#define _INC_MALLOC

#include "edk.h"
#include "_exchdbg.h"

#ifdef _NTSDK
#undef _NTSDK
#endif

#include <stddef.h>

#undef _INC_MALLOC

//$--_DebugMemoryStatus---------------------------------------------------------
//  Writes an entry in the debug log for memory status.
// -----------------------------------------------------------------------------
void _DebugMemoryStatus(                // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN ULONG cBytes)                    // count of bytes
{
    CHAR         buf[BUFSIZ+1] = {0};
    MEMORYSTATUS ms            = {0};

    ZeroMemory(&ms, sizeof(MEMORYSTATUS));

    ms.dwLength = sizeof(MEMORYSTATUS);

    GlobalMemoryStatus(&ms);

_snprintf(
    buf,
        BUFSIZ,
    "\n\tMEMORY:\tAllocBytes: %lu\n"
    "\t\tMemoryLoad: %lu\n"
    "\t\tTotalPhys: %lu ; AvailPhys: %lu\n"
    "\t\tTotalPageFile: %lu ; AvailPageFile: %lu\n"
    "\t\tTotalVirtual: %lu ; AvailVirtual: %lu\n",
        cBytes,
    ms.dwMemoryLoad,
    ms.dwTotalPhys,
    ms.dwAvailPhys,
    ms.dwTotalPageFile,
    ms.dwAvailPageFile,
    ms.dwTotalVirtual,
    ms.dwAvailVirtual);

    REPORT_STATUS(buf);
}

//$--_DebugHeapStatus-----------------------------------------------------------
//  Writes an entry in the debug log.
// -----------------------------------------------------------------------------
static void _DebugHeapStatus(           // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN int heapstatus)                  // heap status
{
    CHAR buf[BUFSIZ+1] = {0};

    switch (heapstatus)
{
    case _FREEENTRY:
    _snprintf(buf,BUFSIZ,"HEAP: free entry in heap\n");
    break;
    case _HEAPOK:
    break;
    case _HEAPEMPTY:
    _snprintf(buf,BUFSIZ,"HEAP: heap is empty\n");
        break;
    case _HEAPEND:
    _snprintf(buf,BUFSIZ,"HEAP: end of heap\n");
    break;
    case _HEAPBADPTR:
    _snprintf(buf,BUFSIZ,"HEAP: bad pointer to heap\n");
    break;
    case _HEAPBADBEGIN:
    _snprintf(buf,BUFSIZ,"HEAP: bad start of heap\n");
    break;
    case _HEAPBADNODE:
    _snprintf(buf,BUFSIZ,"HEAP: bad node in heap\n");
    break;
    default:
    _snprintf(buf,BUFSIZ,"HEAP: _heapchk returned [%u]\n", heapstatus);
    }

    if(heapstatus != _HEAPOK)
    {
        REPORT_ERROR(buf);
    }
}

//$--_DebugHeapCheck------------------------------------------------------------
//  Writes an entry in the debug log. This function is only called in
//  DEBUG builds through the DebugHeapCheck() macro.
// -----------------------------------------------------------------------------
void _DebugHeapCheck(                   // RETURNS: return code
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile)                  // file name
{
    int heapstatus = _HEAPOK;

    heapstatus = _heapchk();

    _DebugHeapStatus(ulLine, lpszFile, heapstatus);
}

//$--_DebugDumpHeap-------------------------------------------------------------
//  Writes an entry in the debug log. This function is only called in
//  DEBUG builds through the DebugDumpHeap() macro.
// -----------------------------------------------------------------------------
void _DebugDumpHeap(                    // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile)                  // file name
{
    _HEAPINFO hi            = {0};
    int       heapstatus    = _HEAPOK;
    CHAR      buf[BUFSIZ+1] = {0};

    ZeroMemory(&hi, sizeof(_HEAPINFO));

    while((heapstatus = _heapwalk (&hi)) == _HEAPOK)
    {
        if (hi._useflag == _USEDENTRY)
        {
            _snprintf(buf, BUFSIZ, "HEAP: used block at %08lx of size %u\n",
                hi._pentry,
                hi._size );

            REPORT_STATUS(buf);
        }
    }

    if(heapstatus != _HEAPOK)
    {
        _DebugHeapStatus(ulLine, lpszFile, heapstatus);
    }
}

//$--EDKDBG_malloc--------------------------------------------------------------
//  Wrapper for malloc().
// -----------------------------------------------------------------------------
LPVOID EDKDBG_malloc(                   // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN ULONG ulSize)                    // size of memory block
{
    LPVOID lpv           = NULL;
    CHAR   buf[BUFSIZ+1] = {0};

    _DebugHeapCheck(ulLine, lpszFile);

    lpv = malloc(ulSize);

    if(lpv != NULL)
    {
        if(ulSize > 0)
        {
            FillMemory(lpv, ulSize, 0xCC);
        }
    }
    else
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",ulSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,ulSize);
    }

    return(lpv);
}

//$--EDKDBG_calloc--------------------------------------------------------------
//  Wrapper for calloc().
// -----------------------------------------------------------------------------
LPVOID EDKDBG_calloc(                   // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN ULONG ulNum,                     // number of elements
    IN ULONG ulSize)                    // size of element
{
    LPVOID lpv           = NULL;
    CHAR   buf[BUFSIZ+1] = {0};

    _DebugHeapCheck(ulLine, lpszFile);

    lpv = calloc(ulNum, ulSize);

    // Calloc cannot fill with 0xCC since that changes the known behaviour of calloc

    if(lpv == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",ulSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,ulSize);
    }

    return(lpv);
}

//$--EDKDBG_realloc-------------------------------------------------------------
//  Wrapper for realloc().
// -----------------------------------------------------------------------------
LPVOID EDKDBG_realloc(                  // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN LPVOID lpvBlock,                 // pointer to memory block
    IN ULONG ulSize)                    // new size of memory block
{
    LPVOID lpv           = NULL;
    CHAR   buf[BUFSIZ+1] = {0};

    _DebugHeapCheck(ulLine, lpszFile);

    lpv = realloc(lpvBlock, ulSize);

    if(lpv == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",ulSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,ulSize);
    }

    return(lpv);
}

//$--EDKDBG_strdup--------------------------------------------------------------
//  Wrapper for strdup().
// -----------------------------------------------------------------------------
char* EDKDBG_strdup(                    // RETURNS: pointer to allocated string
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN const char *lpsz)                // pointer to string
{
    LPVOID lpv           = NULL;
    CHAR   buf[BUFSIZ+1] = {0};

    if(IsBadStringPtrA(lpsz, INFINITE))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid pointer [%08lx]\n", lpsz);

        REPORT_ERROR(buf);

        return(lpv);
    }

    _DebugHeapCheck(ulLine, lpszFile);

    lpv = _strdup(lpsz);

    if(lpv == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",0,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    return((char *)lpv);
}

//$--EDKDBG_wcsdup--------------------------------------------------------------
//  Wrapper for _wcsdup().
// -----------------------------------------------------------------------------
wchar_t* EDKDBG_wcsdup(                 // RETURNS: pointer to allocated string
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN const wchar_t *lpsz)             // pointer to string
{
    LPVOID lpv           = NULL;
    CHAR   buf[BUFSIZ+1] = {0};

    if(IsBadStringPtrW(lpsz, INFINITE))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid pointer [%08lx]\n", lpsz);

        REPORT_ERROR(buf);

        return(lpv);
    }

    _DebugHeapCheck(ulLine, lpszFile);

    lpv = _wcsdup(lpsz);

    if(lpv == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",0,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    return((wchar_t *)lpv);
}

//$--EDKDBG_free----------------------------------------------------------------
//  Wrapper for free().
// -----------------------------------------------------------------------------
void EDKDBG_free(                       // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN LPVOID lpv)                      // pointer to memory block
{
    _DebugHeapCheck(ulLine, lpszFile);

    free(lpv);

    return;
}

//$--EDKDBG_GlobalAlloc---------------------------------------------------------
//  Wrapper for GlobalAlloc().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_GlobalAlloc(             // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN UINT  fuFlags,                   // allocation flags
    IN DWORD dwSize)                    // size of memory block
{
    LPVOID  lpv           = NULL;
    HGLOBAL hglb          = NULL;
    DWORD   cBytes        = 0;
    CHAR    buf[BUFSIZ+1] = {0};

    _DebugHeapCheck(ulLine, lpszFile);

    if((fuFlags != 0) && (! (fuFlags & GMEM_VALID_FLAGS)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid allocation flags [%08lx]\n", fuFlags);

        REPORT_ERROR(buf);
    }

    hglb = GlobalAlloc(fuFlags, dwSize);

    if(hglb == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",dwSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,dwSize);
    }

    return(hglb);
}

//$--EDKDBG_GlobalReAlloc-------------------------------------------------------
//  Wrapper for GlobalReAlloc().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_GlobalReAlloc(           // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN HGLOBAL hglb,                    // pointer to memory block
    IN DWORD cbBytes,                   // new size of memory block
    IN UINT  fuFlags)                   // allocation flags
{
    LPVOID  lpv           = NULL;
    CHAR    buf[BUFSIZ+1] = {0};
    UINT    gflags        = 0;
    HGLOBAL hglbMem       = 0;

    _DebugHeapCheck(ulLine, lpszFile);

    if((fuFlags != 0) && (! (fuFlags & GMEM_VALID_FLAGS)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid allocation flags [%08lx]\n", fuFlags);

        REPORT_ERROR(buf);
    }

    gflags = GlobalFlags(hglb);

    if(gflags == GMEM_INVALID_HANDLE)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        return(hglbMem);
    }

    hglbMem = GlobalReAlloc(hglb, cbBytes, fuFlags);

    if(hglbMem == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",cbBytes,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,cbBytes);
    }

    return(hglbMem);
}

//$--EDKDBG_GlobalFree----------------------------------------------------------
//  Wrapper for GlobalFree().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_GlobalFree(              // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN HGLOBAL hglb)                    // pointer to memory block
{
    LPVOID  lpv           = NULL;
    CHAR    buf[BUFSIZ+1] = {0};
    UINT    gflags        = 0;
    HGLOBAL hglbMem       = 0;

    _DebugHeapCheck(ulLine, lpszFile);

    gflags = GlobalFlags(hglb);

    if(gflags == GMEM_INVALID_HANDLE)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        SetLastError(ERROR_INVALID_HANDLE);

        return(hglb);
    }

    if(hglb == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    hglbMem = GlobalFree(hglb);

    if(hglbMem != NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: deallocation failed [%08lx]\n", GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    return(hglbMem);
}

//$--EDKDBG_LocalAlloc----------------------------------------------------------
//  Wrapper for LocalAlloc().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_LocalAlloc(              // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN UINT  fuFlags,                   // allocation flags
    IN DWORD dwSize)                    // size of memory block
{
    LPVOID  lpv           = NULL;
    HGLOBAL hglb          = NULL;
    CHAR    buf[BUFSIZ+1] = {0};

    _DebugHeapCheck(ulLine, lpszFile);

    if((fuFlags != 0) && (! (fuFlags & LMEM_VALID_FLAGS)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid allocation flags [%08lx]\n", fuFlags);

        REPORT_ERROR(buf);
    }

    hglb = LocalAlloc(fuFlags, dwSize);

    if(hglb == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",dwSize,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,dwSize);
    }

    return(hglb);
}

//$--EDKDBG_LocalReAlloc--------------------------------------------------------
//  Wrapper for LocalReAlloc().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_LocalReAlloc(            // RETURNS: pointer to memory block
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN HGLOBAL hglb,                    // pointer to memory block
    IN DWORD cbBytes,                   // new size of memory block
    IN UINT  fuFlags)                   // allocation flags
{
    LPVOID  lpv           = NULL;
    CHAR    buf[BUFSIZ+1] = {0};
    UINT    gflags        = 0;
    HGLOBAL hglbMem       = 0;

    _DebugHeapCheck(ulLine, lpszFile);

    if((fuFlags != 0) && (! (fuFlags & LMEM_VALID_FLAGS)))
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid allocation flags [%08lx]\n", fuFlags);

        REPORT_ERROR(buf);
    }

    gflags = LocalFlags(hglb);

    if(gflags == LMEM_INVALID_HANDLE)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        return(hglbMem);
    }

    hglbMem = LocalReAlloc(hglb, cbBytes, fuFlags);

    if(hglbMem == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: allocation of [%lu] bytes failed [%08lx]\n",cbBytes,GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,cbBytes);
    }

    return(hglbMem);
}

//$--EDKDBG_LocalFree-----------------------------------------------------------
//  Wrapper for LocalFree().
// -----------------------------------------------------------------------------
HGLOBAL EDKDBG_LocalFree(               // RETURNS: nothing
    IN ULONG ulLine,                    // line number
    IN LPSTR lpszFile,                  // file name
    IN HGLOBAL hglb)                    // pointer to memory block
{
    LPVOID  lpv           = NULL;
    CHAR    buf[BUFSIZ+1] = {0};
    UINT    gflags        = 0;
    HGLOBAL hglbMem       = 0;

    _DebugHeapCheck(ulLine, lpszFile);

    gflags = LocalFlags(hglb);

    if(gflags == LMEM_INVALID_HANDLE)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        SetLastError(ERROR_INVALID_HANDLE);

        return(hglb);
    }

    if(hglb == NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: invalid handle [%08lx]\n", hglb);

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    hglbMem = LocalFree(hglb);

    if(hglbMem != NULL)
    {
    _snprintf(buf,BUFSIZ,"MEMORY: deallocation failed [%08lx]\n", GetLastError());

        REPORT_ERROR(buf);

        _DebugMemoryStatus(ulLine,lpszFile,0);
    }

    return(hglbMem);
}


EDKEVENT.C

// --edkevent.c-----------------------------------------------------------------
//
//  Routines to do event logging for EDK.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include <stdarg.h>
#include "edkevent.chk"

static EDKEVENTCOUNTsEventCount= {0};
static HANDLEhEventSource= NULL;
static BOOLfPreviouslyOpenedHandle= FALSE;

static VOID LogEventCommon(
IN BOOL fIsUnicode,
IN DWORD dwEvent,
IN DWORD cStrings,
IN va_list pArgList);

//$--HrEventOpenLog----------------------------------------------------------
//  Initialize event logging for the EDK.
// -----------------------------------------------------------------------------
HRESULT HrEventOpenLog(// RETURNS: HRESULT
IN LPSTR pszApplicationName,// name of this application
IN LPSTR pszExecutableName,// name of executable
    IN LPSTR pszEventMessageFile,      // name of event message file
    IN LPSTR pszParameterMessageFile,  // name of parameter message file
    IN LPSTR pszCategoryMessageFile,   // name of category message file
OUT LPHANDLE phEventSourceOut)// [returns event logging handle]
{
    HRESULT     hr              = NOERROR;
DWORDdwError= ERROR_SUCCESS;
CHARszKeyName[MAX_PATH+1] = {0};
HKEYhKey= NULL;
DWORDdwDisposition= 0;
HMODULEhinstModule= NULL;
CHARszExecutableBuffer[MAX_PATH+1] = {0};
DWORDcch= 0;

    LPSTR      pszMessageFile      = NULL;

DEBUGPUBLIC("HrEventOpenLog()\n");

// Check the parameters.

hr = CHK_HrEventOpenLog(
pszApplicationName,
pszExecutableName,
        pszEventMessageFile,
        pszParameterMessageFile,
        pszCategoryMessageFile,
phEventSourceOut);

if(FAILED(hr))
RETURN(hr);

// Make sure the event log isn't already open.

if (hEventSource != NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Flag that this is not a previously opened handle, and should be closed
// when HrEventCloseLog() is called.

fPreviouslyOpenedHandle = FALSE;

// If they didn't pass in an executable name then figure out the name
// of the executable currently executing and use that.

if (pszExecutableName == NULL)
{
hinstModule = GetModuleHandle(NULL);
if (hinstModule == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

cch = GetModuleFileName(
hinstModule,
szExecutableBuffer,
sizeof(szExecutableBuffer) - 1);
if (cch == 0)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

pszExecutableName = szExecutableBuffer;
}

// Create the name of the registry key for this application.

lstrcpy(szKeyName,
"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\");
lstrcat(szKeyName, pszApplicationName);

// Open or create the registry key.  This registry key contains/will
// contain values that are required by the Windows NT function
// ReportEvent().

dwError = RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&hKey,
&dwDisposition);
if (dwError != ERROR_SUCCESS)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Add the message file names to the key.

    if(pszEventMessageFile == NULL)
    {
        pszMessageFile = pszExecutableName;
    }
    else
    {
        pszMessageFile = pszEventMessageFile;
    }

hr = _HrWriteRegistrySZ(
    hKey,
    "EventMessageFile",
pszMessageFile);

if (FAILED(hr))
    {
goto cleanup;
    }

    if(pszParameterMessageFile == NULL)
    {
        pszMessageFile = "%SystemRoot%\\System32\\kernel32.dll";
    }
    else
    {
        pszMessageFile = pszParameterMessageFile;
    }

hr = _HrWriteRegistrySZ(
    hKey,
    "ParameterMessageFile",
pszMessageFile);

if(FAILED(hr))
    {
goto cleanup;
    }

    if(pszCategoryMessageFile == NULL)
    {
        pszMessageFile = pszExecutableName;
    }
    else
    {
        pszMessageFile = pszCategoryMessageFile;
    }

hr = _HrWriteRegistrySZ(
    hKey,
    "CategoryMessageFile",
pszMessageFile);

if(FAILED(hr))
    {
goto cleanup;
    }

hr = _HrWriteRegistryDWORD(
        hKey,
        "TypesSupported",
EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE);

if(FAILED(hr))
    {
goto cleanup;
    }

// Register the event source.

hEventSource = RegisterEventSource(NULL, pszApplicationName);

if (hEventSource == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Reset the event type counters.

sEventCount.cError = 0;
sEventCount.cWarning = 0;
sEventCount.cInformation = 0;

// Return the handle if they requested it.

if (phEventSourceOut)
{
*phEventSourceOut = hEventSource;
}

cleanup:
if (hKey != NULL)
{
dwError = RegCloseKey(hKey);

if (dwError != ERROR_SUCCESS)
{
hr = HR_LOG(E_FAIL);
}

hKey = NULL;
}

RETURN(hr);
}

//$--HrEventUseExisting---------------------------------------------------
//  Initialize event logging for the EDK by connecting to an already open
//  event log handle.  This allows EventLogMsg() to log events to a handle
//  that was opened elsewhere.  Calling HrEventCloseLog() after calling
//  this routine will do internal cleanup but will not close the event log
//  handle.  One example of where this routine is useful is within a DLL
//  that is called by EDK code in which event logging has already been
//  initialized.
// -----------------------------------------------------------------------------
HRESULT HrEventUseExisting(// RETURNS: HRESULT
IN HANDLE hExistingEventSource)// previously opened event log handle
{
HRESULT hr = NOERROR;

DEBUGPUBLIC("HrEventUseExisting()\n");

// Check the parameters.

hr = CHK_HrEventUseExisting(
hExistingEventSource);
if (FAILED(hr))
RETURN(hr);

// Make sure the event log isn't already open.

if (hEventSource != NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Use the existing event log handle.

hEventSource = hExistingEventSource;

cleanup:
RETURN(hr);
}

//$--EventLogMsg----------------------------------------------------------------
//
//  EventLogMsgA -- byte string version
//  EventLogMsgW -- word string version
//
//  Log an event to the event log, and optionally, log the original error(s)
//  that caused the event.  It has the following parameters:
//
//   DWORD    dwEvent
//   DWORD    cStrings
//  [LPSTR   pszString1]
//  [LPSTR   pszString2]
//  [...................]
//  [LPSTR   pszStringN]
//   DWORD    cErrorCodes
//  [DWORD    dwErrorCode1]
//  [DWORD    dwErrorCode2]
//  [.....................]
//  [DWORD    dwErrorCodeN]
//
//  Each of the above strings and error codes are used as parameters to the
//  message in the order they appear.  This means that in event messages,
//  all of the  error message replacement parameters must have higher numbers
//  than all of the string replacement parameters.  For example:
//
//      EventLogMsg(
//          MYAPP_CANNOT_COPY_FILE,
//          2, pszSourceFile, pszDestFile,
//          1, dwError);
//
//  And the message would be defined as:
//
//      MessageId=
//      Severity=Error
//      Facility=Application
//      SymbolicName=MYAPP_CANNOT_COPY_FILE
//      Language=English
//      Cannot copy file from %1 to %2 due to the following error:%n%3.
//      .
//
//  Note: This routine preserves the last error value returned by
//        GetLastError().
//
// -----------------------------------------------------------------------------

//$--EventLogMsgA---------------------------------------------------------------
//  Byte string version of EventLogMsg().
//
//  IMPORTANT!!! The error code count [and error code list] is REQUIRED after
//  the text string count [and text string list].  Failure to include the
//  error code argument(s) may cause unexpected results.
// -----------------------------------------------------------------------------
VOID EventLogMsgA(// RETURNS: nothing
IN DWORD dwEvent,// error code of event to log
IN DWORD cStrings,// number of text string parameters
IN ...// text string parameters
//IN DWORD cErrors,// number of error code parameters
//IN ...// error code parameters
)
{
DWORD   dwSaveLastError= 0;
va_listpArgList        = {0};

dwSaveLastError = GetLastError();

va_start(pArgList, cStrings);
LogEventCommon(FALSE, dwEvent, cStrings, pArgList);
va_end(pArgList);

SetLastError(dwSaveLastError);
}

//$--EventLogMsgW---------------------------------------------------------------
//  Word string version of EventLogMsg().
//
//  IMPORTANT!!! The error code count [and error code list] is REQUIRED after
//  the text string count [and text string list].  Failure to include the
//  error code argument(s) may cause unexpected results.
// -----------------------------------------------------------------------------
VOID EventLogMsgW(// RETURNS: nothing
IN DWORD dwEvent,// error code of event to log
IN DWORD cStrings,// number of text string parameters
IN ...// text string parameters
//IN DWORD cErrors,// number of error code parameters
//IN ...// error code parameters
)
{
DWORDdwSaveLastError= 0;
va_listpArgList        = {0};

dwSaveLastError = GetLastError();

va_start(pArgList, cStrings);
LogEventCommon(TRUE, dwEvent, cStrings, pArgList);
va_end(pArgList);

SetLastError(dwSaveLastError);
}

// Maximum total number of string and error parameters allowed by EventLogMsg().
#define_MAX_BUILD_STRINGS20

// Size of buffer to write error number string into (must be big enough to hold
// a string of the form: %%<decimal#> in either multi-byte or UNICODE.
#define_MAX_ERROR_STRING_BYTES40

//$--LogEventCommon-------------------------------------------------------------
//  Common event logging code for both multi-byte and UNICODE strings.
// -----------------------------------------------------------------------------
static VOID LogEventCommon(// RETURNS: nothing
IN BOOL fIsUnicode,// flag set if text strings are UNICODE
IN DWORD dwEvent,// error code of event to log
IN DWORD cStrings,// number of text strings that follow
IN va_list pArgList)// argument strings/error codes
{
HRESULThr= NOERROR;
BOOLfItWorked= TRUE;
DWORDiArg= 0;
DWORDcBuildStrings= 0;
WORDwEventType= 0;

// This array can contain either all LPSTR's or all LPWSTR's.
LPVOIDrgpvBuildStrings[_MAX_BUILD_STRINGS] = {NULL};

// This array will be used to build either WCHAR strings or CHAR strings.
BYTErgrgbBuildErrorStrings[_MAX_BUILD_STRINGS]
[_MAX_ERROR_STRING_BYTES] = {0};

DEBUGPRIVATE("LogEventCommon()\n");

// Make sure event log is open.

if (hEventSource == NULL)
{
OutputDebugString("Event log is not open in LogEventCommon()\n");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Get the number of string arguments and make sure it is within limits.

cBuildStrings = cStrings;

if (cBuildStrings > _MAX_BUILD_STRINGS)
{
OutputDebugString("Too many string parameters in LogEventCommon()\n");
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

// Get the string arguments and make sure they are valid string pointers.

for (iArg = 0; iArg < cBuildStrings; iArg++)
{
BOOL fIsValidString = TRUE;

if (fIsUnicode)
{
rgpvBuildStrings[iArg] = va_arg(pArgList, LPWSTR);
fIsValidString = TEST_STRINGW_PTR(rgpvBuildStrings[iArg]);
}
else
{
rgpvBuildStrings[iArg] = va_arg(pArgList, LPSTR);
fIsValidString = TEST_STRINGA_PTR(rgpvBuildStrings[iArg]);
}

if (!fIsValidString)
{
OutputDebugString("Invalid string parameter in LogEventCommon()\n");
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
}

// Get the number of error code arguments and make sure it is within limits.

cBuildStrings += va_arg(pArgList, DWORD);

if (cBuildStrings > _MAX_BUILD_STRINGS)
{
OutputDebugString("Too many error code parameters in "
"LogEventCommon()\n");
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

// Get the error code arguments and convert them to strings of the form:
// %%<Number>.

for ( ; iArg < cBuildStrings; iArg++)
{
rgpvBuildStrings[iArg] = rgrgbBuildErrorStrings[iArg];

if (fIsUnicode)
{
swprintf(rgpvBuildStrings[iArg], L"%%%%%ld",
va_arg(pArgList, DWORD));
}
else
{
sprintf(rgpvBuildStrings[iArg], "%%%%%ld",
va_arg(pArgList, DWORD));
}
}

// Compute the event type for this event.

switch (dwEvent & 0xC0000000)
{
case 0xC0000000:
wEventType = EVENTLOG_ERROR_TYPE;
break;
case 0x80000000:
wEventType = EVENTLOG_WARNING_TYPE;
break;
default:
wEventType = EVENTLOG_INFORMATION_TYPE;
break;
}

// Log the event.

if (fIsUnicode)
{
fItWorked = ReportEventW(
hEventSource,
wEventType,
0,
dwEvent,
NULL,
(WORD)cBuildStrings,
0,
(LPWSTR *)rgpvBuildStrings,
NULL);
}
else
{
fItWorked = ReportEventA(
hEventSource,
wEventType,
0,
dwEvent,
NULL,
(WORD)cBuildStrings,
0,
(LPSTR *)rgpvBuildStrings,
NULL);
}

if (!fItWorked)
{
OutputDebugString("ReportEvent() failed in LogEventCommon()\n");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Bump up the counter for the right type of event.

switch (wEventType)
{
case EVENTLOG_ERROR_TYPE:
sEventCount.cError++;
break;
case EVENTLOG_WARNING_TYPE:
sEventCount.cWarning++;
break;
case EVENTLOG_INFORMATION_TYPE:
sEventCount.cInformation++;
break;
default:
OutputDebugString(
"Cannot increment event type counter for "
"unknown event type.\n");
break;
}

cleanup:
// If we couldn't log it to the event log, then output a message to the
// error log and debug log.

if (FAILED(hr))
{
CHAR szDescription[100] = {0};
sprintf(szDescription, "Unable to log event 0x%08lx\n", dwEvent);
OutputDebugString(szDescription);
}
}

//$--HrEventGetCounts--------------------------------------------------------
//  Returns the number of Error, Warning, and Information events logged (by the
//  current executable).
// -----------------------------------------------------------------------------
HRESULT HrEventGetCounts(// RETURNS: HRESULT
OUT LPEDKEVENTCOUNT lpsEventCount)// structure to return event counts
{
HRESULThr= NOERROR;

DEBUGPUBLIC("HrEventGetCounts(lpsEventCount)\n");

hr = CHK_HrEventGetCounts(
lpsEventCount);
if (FAILED(hr))
RETURN(hr);

// Return the values.

MoveMemory(lpsEventCount, &sEventCount, sizeof(sEventCount));

RETURN(hr);
}

//$--HrEventCloseLog---------------------------------------------------------
//  Shut down event logging for the EDK.
// -----------------------------------------------------------------------------
HRESULT HrEventCloseLog()// RETURNS: HRESULT
{
HRESULThr= NOERROR;
BOOLfItWorked= FALSE;

DEBUGPUBLIC("HrEventCloseLog()\n");

if (hEventSource && !fPreviouslyOpenedHandle)
{
fItWorked = CloseEventLog(hEventSource);
if (!fItWorked)
{
hr = HR_LOG(E_FAIL);
}
}

hEventSource = NULL;

RETURN(hr);
}

// $--HrEventGetHandle------------------------------------------------------
//
// DESCRIPTION: Retrieve event handle for this executable.
//
// OUTPUT:  phEventLog   --  event log handle pointer
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
// Notes:
//
// 1) The event handle returned will be NULL if there is
// no open event log.
//
// 2) DLLs may not call this function to retrieve the event handle
// which their parent executable set.  If the parent executable sets
// an event handle, then it must pass the event handle to the DLL.
//
// ----------------------------------------------------------------------------
HRESULT HrEventGetHandle(
        IN HANDLE * phEventLog)       // event log handle pointer
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("HrEventGetHandle");

    // check input parameters
    hr = CHK_HrEventGetHandle(phEventLog);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Return current event log handle.  Note that it
    // may be NULL.
    *phEventLog = hEventSource;

    RETURN(hr);

}


EDKGUID.C

// --EDKGUID.C------------------------------------------------------------------
// To be used to create the precompiled header.
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define EDK_USES_IID
#include "edk.h"


EDKMAPI.C

// --edkmapi.c------------------------------------------------------------------
//
//  Module containing MAPI utility functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "edkmapi.chk"

//$--HrMAPIGetFirstSRowSet-------------------------------------------------------
//  Gets the first SRowSet from a table
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetFirstSRowSet(           // RETURNS: return code
    IN LPMAPITABLE  lpTable,            // pointer to table address variable
    IN ULONG cRows,                     // count of number of rows in SRowSet
    IN LPSPropTagArray rgPropTags,      // array of property tags
    OUT LPSRowSet FAR *lppRows)         // pointer to address variable for
                                        // SRowSet
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrMAPIGetFirstSRowSet()\n");

    hr = CHK_HrMAPIGetFirstSRowSet(
        lpTable,
        cRows,
        rgPropTags,
        lppRows);

    if(FAILED(hr))
        RETURN(hr);

    *lppRows = NULL;

    // Set the columns to return
    hrT = MAPICALL(lpTable)->SetColumns(lpTable, rgPropTags, (ULONG)0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Go to the beginning of the table
    hrT = MAPICALL(lpTable)->SeekRow(lpTable, BOOKMARK_BEGINNING, 0, NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the next SRowSet
    hrT = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cRows,
        (ULONG)0,
        lppRows);

    if(SUCCEEDED(hrT) && ((*lppRows) != NULL) && ((*lppRows)->cRows == 0))
    {
        FREEPROWS(*lppRows);

        hrT = EDK_E_NOT_FOUND;
    }

    if(FAILED(hrT) || ((*lppRows) == NULL))
    {
        if (hrT != EDK_E_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            hr = HR_LOG(EDK_E_END_OF_FILE);
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrMAPIGetNextSRowSet--------------------------------------------------------
//  Gets the next SRowSet from a table
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetNextSRowSet(            // RETURNS: return code
    IN LPMAPITABLE lpTable,             // pointer to table
    IN ULONG cRows,                     // count of number of rows in SRowSet
    IN LPSPropTagArray rgPropTags,      // array of property tags
    OUT LPSRowSet FAR *lppRows)         // pointer to address variable for
                                        // SRowSet
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrMAPIGetNextSRowSet()\n");

    hr = CHK_HrMAPIGetNextSRowSet(
        lpTable,
        cRows,
        rgPropTags,
        lppRows);

    if(FAILED(hr))
        RETURN(hr);

    *lppRows = NULL;

    // Set the columns to return
    hrT = MAPICALL(lpTable)->SetColumns(lpTable, rgPropTags, (ULONG)0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the next SRowSet
    hrT = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cRows,
        (ULONG)0,
        lppRows);

    if(SUCCEEDED(hrT) && ((*lppRows) != NULL) && ((*lppRows)->cRows == 0))
    {
        FREEPROWS(*lppRows);

        hrT = EDK_E_NOT_FOUND;
    }

    if(FAILED(hrT) || ((*lppRows) == NULL))
    {
        if (hrT != EDK_E_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            hr = HR_LOG(EDK_E_END_OF_FILE);
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrMAPICreateEntryList-------------------------------------------------------
//  Creates an ENTRYLIST.
// -----------------------------------------------------------------------------
HRESULT HrMAPICreateEntryList(           // RETURNS: return code
    IN ULONG cbeid,                     // count of bytes in Entry ID
    IN LPENTRYID lpeid,                 // pointer to Entry ID
    OUT LPENTRYLIST FAR *lppEntryList)  // pointer to address variable of Entry
                                        // list
{
    HRESULT hr              = NOERROR;
    SCODE   sc              = 0;
    LPVOID  lpvSBinaryArray = NULL;
    LPVOID  lpvSBinary      = NULL;
    LPVOID  lpv             = NULL;

    DEBUGPUBLIC("HrMAPICreateEntryList()\n");

    hr = CHK_HrMAPICreateEntryList(
        cbeid,
        lpeid,
        lppEntryList);

    if(FAILED(hr))
        RETURN(hr);

    *lppEntryList = NULL;

    sc = MAPIAllocateBuffer(cbeid, &lpv);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy entry ID
    CopyMemory(lpv, lpeid, cbeid);

    sc = MAPIAllocateBuffer(sizeof(SBinary), &lpvSBinary);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize SBinary structure
    ZeroMemory(lpvSBinary, sizeof(SBinary));

    ((LPSBinary)lpvSBinary)->cb = cbeid;
    ((LPSBinary)lpvSBinary)->lpb = (LPBYTE)lpv;

    sc = MAPIAllocateBuffer(sizeof(SBinaryArray), &lpvSBinaryArray);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize SBinaryArray structure
    ZeroMemory(lpvSBinaryArray, sizeof(SBinaryArray));

    ((SBinaryArray *)lpvSBinaryArray)->cValues = 1;
    ((SBinaryArray *)lpvSBinaryArray)->lpbin = (LPSBinary)lpvSBinary;

    *lppEntryList = (LPENTRYLIST)lpvSBinaryArray;

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpv);

        MAPIFREEBUFFER(lpvSBinary);

        MAPIFREEBUFFER(lpvSBinaryArray);
    }

    RETURN(hr);
}

//$--HrMAPIAppendEntryList-------------------------------------------------------
//  Appends to an ENTRYLIST.
// -----------------------------------------------------------------------------
HRESULT HrMAPIAppendEntryList(           // RETURNS: return code
    IN ULONG cbeid,                     // count of bytes in Entry ID
    IN LPENTRYID lpeid,                 // pointer to Entry ID
    OUT LPENTRYLIST FAR lpEntryList)    // pointer to address variable of Entry
                                        // list
{
    HRESULT hr          = NOERROR;
    SCODE   sc          = 0;
    LPVOID  lpvSBinary  = NULL;
    LPVOID  lpv         = NULL;
    ULONG   cValues     = 0;
    ULONG   cSBinary    = 0;
    ULONG   i           = 0;

    DEBUGPUBLIC("HrMAPIAppendEntryList()\n");

    hr = CHK_HrMAPIAppendEntryList(
        cbeid,
        lpeid,
        lpEntryList);

    if(FAILED(hr))
        RETURN(hr);

    sc = MAPIAllocateBuffer(cbeid, &lpv);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy Entry ID
    CopyMemory(lpv, lpeid, cbeid);

    cValues = lpEntryList->cValues;

    cSBinary = sizeof(SBinary) * (cValues + 1);

    sc = MAPIAllocateBuffer(cSBinary, &lpvSBinary);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize SBinary structure
    ZeroMemory(lpvSBinary, cSBinary);

#define BINLIST ((LPSBinary)lpvSBinary)

    // Copy values from old SBinary list to new SBinary list
    for(i = 0; i < cValues; i++)
    {
        BINLIST[i].cb  = lpEntryList->lpbin[i].cb;
        BINLIST[i].lpb = lpEntryList->lpbin[i].lpb;
    }

    // Add new entry to the end of the list
    BINLIST[cValues].cb  = cbeid;
    BINLIST[cValues].lpb = (LPBYTE)lpv;

    // Free old SBinary list
    MAPIFREEBUFFER(lpEntryList->lpbin);

    // Increment number of cValues
    cValues++;

    // Update Entry list
    lpEntryList->cValues = cValues;
    lpEntryList->lpbin = BINLIST;

#undef BINLIST

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpv);

        MAPIFREEBUFFER(lpvSBinary);
    }

    RETURN(hr);
}

//$--HrMAPIDestroyEntryList---------------------------------------------------------
//  Frees an ENTRYLIST.
// -----------------------------------------------------------------------------
HRESULT HrMAPIDestroyEntryList(              // RETURNS: return code
    IN OUT LPENTRYLIST FAR *lppEntryList)// pointer to address variable of Entry
                                         // list
{
    HRESULT     hr          = NOERROR;
    ULONG       i           = 0;
    LPENTRYLIST lpEntryList = NULL;

    DEBUGPUBLIC("HrMAPIDestroyEntryList()\n");

    hr = CHK_HrMAPIDestroyEntryList(
        lppEntryList);

    if(FAILED(hr))
        RETURN(hr);

    if(lppEntryList != NULL)
    {
        lpEntryList = *lppEntryList;

        if(lpEntryList != NULL)
        {
            if(lpEntryList->lpbin != NULL)
            {
                // Free values in SBinary list
                for(i = 0; i < lpEntryList->cValues; i++)
                {
                    MAPIFREEBUFFER(lpEntryList->lpbin[i].lpb);
                }

                // Free SBinary list
                MAPIFREEBUFFER(lpEntryList->lpbin);
            }

            lpEntryList->cValues = 0;

            // Free SBinary array
            MAPIFREEBUFFER(lpEntryList);

            *lppEntryList = NULL;
        }
    }

    RETURN(hr);
}

//$--HrMAPIWriteStreamToFile----------------------------------------------------
//  Write stream to a file given a file handle.
// -----------------------------------------------------------------------------
HRESULT HrMAPIWriteStreamToFile(        // RETURNS: return code
    IN LPSTREAM lpStream,               // Pointer to stream
    OUT HANDLE hFile)                   // Handle to file
{
    HRESULT hr              = NOERROR;
    HRESULT hrT             = NOERROR;
    ULONG   cBytesRead      = 0;
    DWORD   cBytesWritten   = 0;
    BYTE    byteBuffer[128] = {0};
    BOOL    fWriteOk        = FALSE;

    DEBUGPUBLIC("HrMAPIWriteStreamToFile()\n");

    hr = CHK_HrMAPIWriteStreamToFile(
        lpStream,
        hFile);

    if(FAILED(hr))
        RETURN(hr);

    for(;;)
    {
        hrT = OLECALL(lpStream)->Read(
            lpStream,
            byteBuffer,
            sizeof(byteBuffer),
            &cBytesRead);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            break;
        }

        if (cBytesRead == 0)
        {
            hr = NOERROR;
            break ;
        }

        fWriteOk = WriteFile(
            hFile,
            byteBuffer,
            cBytesRead,
            &cBytesWritten,
            NULL);

        if(fWriteOk == FALSE)
        {
            hr = HR_LOG(E_FAIL);
            break;
        }

        if(cBytesWritten != cBytesRead)
        {
            hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
            break;
        }
    }

    RETURN(hr);
}

//$--HrMAPIWriteFileToStream----------------------------------------------------
//  Write file to a stream given a stream pointer.
// -----------------------------------------------------------------------------
HRESULT HrMAPIWriteFileToStream(        // RETURNS: return code
    IN HANDLE hFile,                    // Handle to file
    OUT LPSTREAM lpStream)              // Pointer to stream
{
    HRESULT hr              = NOERROR;
    HRESULT hrT             = NOERROR;
    DWORD   cBytesRead      = 0;
    ULONG   cBytesWritten   = 0;
    BYTE    byteBuffer[128] = {0};
    BOOL    fReadOk         = FALSE;

    DEBUGPUBLIC("HrMAPIWriteFileToStream()\n");

    hr = CHK_HrMAPIWriteFileToStream(
        hFile,
        lpStream);

    if(FAILED(hr))
        RETURN(hr);

    for(;;)
    {
        fReadOk = ReadFile(
            hFile,
            byteBuffer,
            sizeof(byteBuffer),
            &cBytesRead,
            NULL);

        if(fReadOk == FALSE)
        {
            hr = HR_LOG(E_FAIL);
            break;
        }

        if(cBytesRead == 0)
        {
            hr = NOERROR;
            break;
        }

        hrT = OLECALL(lpStream)->Write(
            lpStream,
            byteBuffer,
            cBytesRead,
            &cBytesWritten);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            break;
        }

        if(cBytesWritten != cBytesRead)
        {
            hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
            break;
        }
    }

    RETURN(hr);
}

//$--HrMAPIWriteAttachmentToFile-----------------------------------------------
//  Write the identified message attachment to a file.
// -----------------------------------------------------------------------------
HRESULT HrMAPIWriteAttachmentToFile(   // RETURNS: return code
    IN LPMESSAGE lpMessage,             // message containing the attachments
    IN ULONG iAttach,                   // attachment identifier
    OUT HANDLE hFile)                   // handle to file
{
    HRESULT  hr          = NOERROR;
    HRESULT  hrT         = NOERROR;
    LPATTACH lpAttach    = NULL;
    LPSTREAM lpAttStream = NULL;

    DEBUGPUBLIC("HrMAPIWriteAttachmentToFile()\n");

    hr = CHK_HrMAPIWriteAttachmentToFile(
        lpMessage,
        iAttach,
        hFile);

    if(FAILED(hr))
        RETURN(hr);

    //
    //  Open the attachment.
    //

    hrT = MAPICALL(lpMessage)->OpenAttach(
        lpMessage,
        iAttach,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &lpAttach);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(lpAttach != NULL, "NULL lpAttach variable");

    //
    //  Get a stream interface on the attachment
    //

    hrT = MAPICALL(lpAttach)->OpenProperty(
        lpAttach,
        PR_ATTACH_DATA_BIN,
        (LPIID)&IID_IStream,
        STGM_READ | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
        MAPI_DEFERRED_ERRORS,
        (LPUNKNOWN *)&lpAttStream);

    if(FAILED(hrT))
    {
        if(hrT == MAPI_E_NOT_FOUND)
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }

        goto cleanup;
    }

    //
    //  Copy the attachment into a file.
    //

    hrT = HrMAPIWriteStreamToFile(lpAttStream, hFile);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(lpAttStream != NULL)
        OLECALL(lpAttStream)->Release(lpAttStream);

    if(lpAttach != NULL)
        MAPICALL(lpAttach)->Release(lpAttach);

    RETURN(hr);
}

//$--HrMAPIGotoSRow--------------------------------------------------------------
//  Goto the specified SRow in an SRowSet.
//------------------------------------------------------------------------------
HRESULT HrMAPIGotoSRow(                  // RETURNS: return code
    IN LPSRowSet FAR lpRows,            // pointer to SRowSet
    IN ULONG ulRow,                     // index of SRow in SRowSet
    OUT LPSRow *lppRow)                 // pointer to SRow
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrMAPIGotoSRow()\n");

    hr = CHK_HrMAPIGotoSRow(
        lpRows,
        ulRow,
        lppRow);

    if(FAILED(hr))
        RETURN(hr);

    *lppRow = NULL;

    if(ulRow < lpRows->cRows)
    {
        *lppRow = &(lpRows->aRow[ulRow]);
    }
    else
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
    }

    RETURN(hr);
}

//$--HrMAPIGotoFirstSRow---------------------------------------------------------
//  Goto the first SRow in an SRowSet.
//------------------------------------------------------------------------------
HRESULT HrMAPIGotoFirstSRow(             // RETURNS: return code
    IN LPSRowSet FAR lpRows,            // pointer to SRowSet
    OUT ULONG *lpulRow,                 // index of SRow in SRowSet
    OUT LPSRow *lppRow)                 // pointer to SRow
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrMAPIGotoFirstSRow()\n");

    hr = CHK_HrMAPIGotoFirstSRow(
        lpRows,
        lpulRow,
        lppRow);

    if(FAILED(hr))
        RETURN(hr);

    *lpulRow = 0;

    hr = HrMAPIGotoSRow(
        lpRows,
        0,
        lppRow);

    RETURN(hr);
}

//$--HrMAPIGotoNextSRow----------------------------------------------------------
//  Goto the next SRow in an SRowSet.
//------------------------------------------------------------------------------
HRESULT HrMAPIGotoNextSRow(              // RETURNS: return code
    IN LPSRowSet FAR lpRows,            // pointer to SRowSet
    IN OUT ULONG *lpulRow,              // index of SRow in SRowSet
    OUT LPSRow *lppRow)                 // pointer to SRow
{
    HRESULT hr    = NOERROR;
    ULONG   ulRow = 0;

    DEBUGPUBLIC("HrMAPIGotoNextSRow()\n");

    hr = CHK_HrMAPIGotoNextSRow(
        lpRows,
        lpulRow,
        lppRow);

    if(FAILED(hr))
        RETURN(hr);

    ulRow = *lpulRow;

    ulRow++;

    *lpulRow = ulRow;

    hr = HrMAPIGotoSRow(
        lpRows,
        ulRow,
        lppRow);

    RETURN(hr);
}

//$--HrMAPIWriteStreamToMemory----------------------------------------------------
//  Reads a given number of bytes from a stream to a block of memory.
// -----------------------------------------------------------------------------
HRESULT HrMAPIWriteStreamToMemory(        // RETURNS: return code
    IN LPSTREAM lpStream,               // pointer to stream
    IN ULONG cBytes,                    // count of bytes in memory
    IN LPBYTE lpbBytes,                 // pointer to memory
    OUT ULONG *lpcBytesRead)            // count of bytes read from stream
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrMAPIWriteStreamToMemory()\n");

    hr = CHK_HrMAPIWriteStreamToMemory(
        lpStream,
        cBytes,
        lpbBytes,
        lpcBytesRead);

    if(FAILED(hr))
        RETURN(hr);

    *lpcBytesRead = 0;

    // Read from the stream
    hrT = OLECALL(lpStream)->Read(
        lpStream,
        lpbBytes,
        cBytes,
        lpcBytesRead);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrMAPIWriteMemoryToStream---------------------------------------------------
//  Writes a given number of bytes from a block of memory to a stream
// -----------------------------------------------------------------------------
HRESULT HrMAPIWriteMemoryToStream(       // RETURNS: return code
    IN LPSTREAM lpStream,               // pointer to stream
    IN ULONG cBytes,                    // count of bytes in memory
    IN LPBYTE lpbBytes,                 // pointer to memory
    OUT ULONG *lpcBytesWritten)         // count of bytes written from stream
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrMAPIWriteMemoryToStream()\n");

    hr = CHK_HrMAPIWriteMemoryToStream(
        lpStream,
        cBytes,
        lpbBytes,
        lpcBytesWritten);

    if(FAILED(hr))
        RETURN(hr);

    *lpcBytesWritten = 0;

    // Write a block to the stream
    hrT = OLECALL(lpStream)->Write(
        lpStream,
        lpbBytes,
        cBytes,
        lpcBytesWritten);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if((*lpcBytesWritten) < cBytes)
    {
        hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrMAPISetStreamSize---------------------------------------------------------
//  Sets the size of the given stream.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetStreamSize(             // RETURNS: return code
    IN LPSTREAM lpStream,               // pointer to stream
    IN ULONG cBytes)                    // count of bytes in stream
{
    HRESULT        hr  = NOERROR;
    HRESULT        hrT = NOERROR;
    ULARGE_INTEGER ll  = {0};

    DEBUGPUBLIC("HrMAPISetStreamSize()\n");

    hr = CHK_HrMAPISetStreamSize(
        lpStream,
        cBytes);

    if(FAILED(hr))
        RETURN(hr);

    ll.LowPart  = cBytes;
    ll.HighPart = 0;

    hrT = OLECALL(lpStream)->SetSize(lpStream, ll);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}


IADDRESS.C

// --iaddress.c-----------------------------------------------------------------
//
//  Module containing MAPI utility functions for addressing.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "iaddress.chk"

//$--HrMAPICreateAddressList-----------------------------------------------------
//  Create an address list.
// -----------------------------------------------------------------------------
HRESULT HrMAPICreateAddressList(             // RETURNS: return code
    IN ULONG cProps,                        // count of values in address list
                                            // entry
    IN LPSPropValue lpPropValues,           // pointer to address list entry
    OUT LPADRLIST *lppAdrList)              // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    SCODE           sc              = 0;
    LPSPropValue    lpNewPropValues = NULL;
    ULONG           cBytes          = 0;

    DEBUGPUBLIC("HrMAPICreateAddressList()\n");

    hr = CHK_HrMAPICreateAddressList(
        cProps,
        lpPropValues,
        lppAdrList);

    if(FAILED(hr))
        RETURN(hr);

    *lppAdrList = NULL;

    sc = ScDupPropset(
        cProps,
        lpPropValues,
MAPIAllocateBuffer,
&lpNewPropValues);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cBytes = CbNewADRLIST(1);

    sc = MAPIAllocateBuffer(cBytes, lppAdrList);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize ADRLIST structure
    ZeroMemory(*lppAdrList, cBytes);

    (*lppAdrList)->cEntries = 1;
    (*lppAdrList)->aEntries[0].cValues = cProps;
    (*lppAdrList)->aEntries[0].rgPropVals = lpNewPropValues;

cleanup:

    if(FAILED(hr))
    {
        if(lppAdrList != NULL)
        {
            MAPIFREEBUFFER(*lppAdrList);
            *lppAdrList = NULL;
        }
        MAPIFREEBUFFER(lpNewPropValues);
    }

    RETURN(hr);
}

//$--HrMAPIAppendAddressList-----------------------------------------------------
//  Append to an address list.
// -----------------------------------------------------------------------------
HRESULT HrMAPIAppendAddressList(             // RETURNS: return code
    IN ULONG cProps,                        // count of values in address list
                                            // entry
    IN LPSPropValue lpPropValues,           // pointer to address list entry
    IN OUT LPADRLIST *lppAdrList)           // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    SCODE           sc              = 0;
    LPADRLIST       lpAdrList       = NULL;
    LPADRENTRY      lpAdrEntry      = NULL;
    LPSPropValue    lpNewPropValues = NULL;
    ULONG           i               = 0;
    ULONG           cBytes          = 0;
    ULONG           cEntries        = 0;

    DEBUGPUBLIC("HrMAPIAppendAddressList()\n");

    hr = CHK_HrMAPIAppendAddressList(
        cProps,
        lpPropValues,
        lppAdrList);

    if(FAILED(hr))
        RETURN(hr);

    sc = ScDupPropset(
        cProps,
        lpPropValues,
MAPIAllocateBuffer,
&lpNewPropValues);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cEntries = ((*lppAdrList)->cEntries + 1);

    cBytes = CbNewADRLIST(cEntries);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpAdrList);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize ADRLIST
    ZeroMemory(lpAdrList, cBytes);

    lpAdrEntry = lpAdrList->aEntries;

    // Copy old ADRENTRY values to array
    for(i = 0; i < (*lppAdrList)->cEntries; i++)
    {
        lpAdrEntry[i].cValues =
            (*lppAdrList)->aEntries[i].cValues;
        lpAdrEntry[i].rgPropVals =
            (*lppAdrList)->aEntries[i].rgPropVals;
    }

    // Copy new ADRENTRY values to array
    lpAdrEntry[i].cValues = cProps;
    lpAdrEntry[i].rgPropVals = lpNewPropValues;

    lpAdrList->cEntries = (*lppAdrList)->cEntries + 1;

    MAPIFREEBUFFER(*lppAdrList);

    *lppAdrList = lpAdrList;

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpNewPropValues);
    }

    RETURN(hr);
}

//$--HrMAPICreateSizedAddressList------------------------------------------------
//  Create a sized address list.
// -----------------------------------------------------------------------------
HRESULT HrMAPICreateSizedAddressList(        // RETURNS: return code
    IN ULONG cEntries,                      // count of entries in address list
    OUT LPADRLIST *lppAdrList)              // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    SCODE           sc              = 0;
    ULONG           cBytes          = 0;

    DEBUGPUBLIC("HrMAPICreateAddressList()\n");

    hr = CHK_HrMAPICreateSizedAddressList(
        cEntries,
        lppAdrList);

    if(FAILED(hr))
        RETURN(hr);

    *lppAdrList = NULL;

    cBytes = CbNewADRLIST(cEntries);

    sc = MAPIAllocateBuffer(cBytes, lppAdrList);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize ADRLIST structure
    ZeroMemory(*lppAdrList, cBytes);

    (*lppAdrList)->cEntries = cEntries;

cleanup:

    RETURN(hr);
}

//$--HrMAPISetAddressList--------------------------------------------------------
//  Set an address list.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetAddressList(                // RETURNS: return code
    IN ULONG iEntry,                        // index of address list entry
    IN ULONG cProps,                        // count of values in address list
                                            // entry
    IN LPSPropValue lpPropValues,           // pointer to address list entry
    IN OUT LPADRLIST lpAdrList)             // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    SCODE           sc              = 0;
    LPSPropValue    lpNewPropValues = NULL;
    ULONG           cBytes          = 0;

    DEBUGPUBLIC("HrMAPISetAddressList()\n");

    hr = CHK_HrMAPISetAddressList(
        iEntry,
        cProps,
        lpPropValues,
        lpAdrList);

    if(FAILED(hr))
        RETURN(hr);

    if(iEntry >= lpAdrList->cEntries)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    sc = ScDupPropset(
        cProps,
        lpPropValues,
MAPIAllocateBuffer,
&lpNewPropValues);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(lpAdrList->aEntries[iEntry].rgPropVals != NULL)
    {
        MAPIFREEBUFFER(lpAdrList->aEntries[iEntry].rgPropVals);
    }

    lpAdrList->aEntries[iEntry].cValues = cProps;
    lpAdrList->aEntries[iEntry].rgPropVals = lpNewPropValues;

cleanup:

    RETURN(hr);
}


IFIND.C

// --ifind.c------------------------------------------------------------------
//
//  Module containing FIND functions for various folders.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "ifind.chk"

//$--HrMAPIFindInbox----------------------------------------------------------
//  Find IPM inbox folder.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindInbox(              // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // Entry ID of IPM inbox
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;
    SCODE   sc  = 0;

    DEBUGPUBLIC("HrMAPIFindInbox()");

    hr = CHK_HrMAPIFindInbox(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the entry ID of the Inbox from the message store
    hrT = MAPICALL(lpMdb)->GetReceiveFolder(
        lpMdb,
        TEXT("IPM"),
        fMapiUnicode,
        lpcbeid,
        lppeid,
        NULL);

    if(FAILED(hrT))
    {
        if(hrT == MAPI_E_NOT_FOUND)
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    ASSERTERROR(*lpcbeid != 0, "ZERO length entry ID");

    ASSERTERROR(*lppeid != NULL, "NULL entry ID");

    RETURN(hr);
}

//$--HrMAPIFindOutbox---------------------------------------------------------
//  Find IPM outbox folder.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindOutbox(             // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // Entry ID of IPM outbox
{
    HRESULT       hr          = NOERROR;
    HRESULT       hrT         = NOERROR;
    SCODE         sc          = 0;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;
    SPropTagArray rgPropTag   = { 1, { PR_IPM_OUTBOX_ENTRYID } };

    DEBUGPUBLIC("HrMAPIFindOutbox()");

    hr = CHK_HrMAPIFindOutbox(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the outbox entry ID property.
    hrT = MAPICALL(lpMdb)->GetProps(
        lpMdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_IPM_OUTBOX_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbeid = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbeid, (void **)lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy outbox Entry ID
    CopyMemory(
        *lppeid,
        lpPropValue->Value.bin.lpb,
        cbeid);

    *lpcbeid = cbeid;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPIFindIPMSubtree--------------------------------------------------------
//  Find IPM subtree folder.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindIPMSubtree(            // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // entry ID of IPM subtree
{
    HRESULT       hr          = NOERROR;
    HRESULT       hrT         = NOERROR;
    SCODE         sc          = 0;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;
    SPropTagArray rgPropTag   = { 1, { PR_IPM_SUBTREE_ENTRYID } };

    DEBUGPUBLIC("HrMAPIFindIPMSubtree()");

    hr = CHK_HrMAPIFindIPMSubtree(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the subtree entry ID property.
    hrT = MAPICALL(lpMdb)->GetProps(
        lpMdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_IPM_SUBTREE_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbeid = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbeid, (void **)lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy subtree entry ID
    CopyMemory(
        *lppeid,
        lpPropValue->Value.bin.lpb,
        cbeid);

    *lpcbeid = cbeid;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrGWFindMtsOutFolder------------------------------------------------------------
//  Find MTS-OUT folder.
// -----------------------------------------------------------------------------
HRESULT HrGWFindMtsOutFolder(                // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // entry ID of MTS-OUT
{
    HRESULT       hr          = NOERROR;
    HRESULT       hrT         = NOERROR;
    SCODE         sc          = 0;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;
    SPropTagArray rgPropTag   = { 1, { PR_GW_MTSOUT_ENTRYID } };

    DEBUGPUBLIC("HrGWFindMtsOutFolder()");

    hr = CHK_HrGWFindMtsOutFolder(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the entry ID property.
    hrT = MAPICALL(lpMdb)->GetProps(
        lpMdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_GW_MTSOUT_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbeid = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbeid, (void **)lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy entry ID
    CopyMemory(
        *lppeid,
        lpPropValue->Value.bin.lpb,
        cbeid);

    *lpcbeid = cbeid;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrGWFindMtsInFolder-------------------------------------------------------------
//  Find MTS-IN folder.
// -----------------------------------------------------------------------------
HRESULT HrGWFindMtsInFolder(                 // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // entry ID of MTS-IN
{
    HRESULT       hr          = NOERROR;
    HRESULT       hrT         = NOERROR;
    SCODE         sc          = 0;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;
    SPropTagArray rgPropTag   = { 1, { PR_GW_MTSIN_ENTRYID } };

    DEBUGPUBLIC("HrGWFindMtsInFolder()");

    hr = CHK_HrGWFindMtsInFolder(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the entry ID property.
    hrT = MAPICALL(lpMdb)->GetProps(
        lpMdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_GW_MTSIN_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbeid = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbeid, (void **)lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy entry ID
    CopyMemory(
        *lppeid,
        lpPropValue->Value.bin.lpb,
        cbeid);

    *lpcbeid = cbeid;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrFindExchangePublicStore-------------------------------------------------------
//  Find public store root folder.
// -----------------------------------------------------------------------------
HRESULT HrFindExchangePublicStore(           // RETURNS: return code
    IN LPMDB lpMdb,                     // pointer to message store
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // entry ID of public store
{
    HRESULT       hr          = NOERROR;
    HRESULT       hrT         = NOERROR;
    SCODE         sc          = 0;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;
    SPropTagArray rgPropTag   = { 1, { PR_NON_IPM_SUBTREE_ENTRYID } };

    DEBUGPUBLIC("HrFindExchangePublicStore()");

    hr = CHK_HrFindExchangePublicStore(
        lpMdb,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    // Get the entry ID property.
    hrT = MAPICALL(lpMdb)->GetProps(
        lpMdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_NON_IPM_SUBTREE_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbeid = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbeid, (void **)lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy entry ID
    CopyMemory(
        *lppeid,
        lpPropValue->Value.bin.lpb,
        cbeid);

    *lpcbeid = cbeid;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}


IFOLDER.C

// --ifolder.c------------------------------------------------------------------
//
//  Module containing MAPI utility functions for folders.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "ifolder.chk"

typedef struct {
    ULONG   cbRootFolderA;
    LPSTR   lpszRootFolderA;
    ULONG   cbRootFolderW;
    LPWSTR  lpszRootFolderW;
    ULONG   ulPropTag;
} ROOT_LOOKUP;

#define RLENTRY(str,ul) {sizeof(str), str, sizeof(L##str), L##str, ul}

static const ROOT_LOOKUP rgRootLookup[] = {
    RLENTRY( "@PR_IPM_SUBTREE_ENTRYID",              PR_IPM_SUBTREE_ENTRYID),
    RLENTRY( "@PR_IPM_OUTBOX_ENTRYID",               PR_IPM_OUTBOX_ENTRYID),
    RLENTRY( "@PR_IPM_WASTEBASKET_ENTRYID",          PR_IPM_WASTEBASKET_ENTRYID),
    RLENTRY( "@PR_IPM_SENTMAIL_ENTRYID",             PR_IPM_SENTMAIL_ENTRYID),
    RLENTRY( "@PR_IPM_PUBLIC_FOLDERS_ENTRYID",       PR_IPM_PUBLIC_FOLDERS_ENTRYID),
    RLENTRY( "@PR_IPM_FAVORITES_ENTRYID",            PR_IPM_FAVORITES_ENTRYID),
    RLENTRY( "@PR_NON_IPM_SUBTREE_ENTRYID",          PR_NON_IPM_SUBTREE_ENTRYID),
    RLENTRY( "@PR_GW_MTSIN_ENTRYID",                 PR_GW_MTSIN_ENTRYID),
    RLENTRY( "@PR_GW_MTSOUT_ENTRYID",                PR_GW_MTSOUT_ENTRYID),
    RLENTRY( "@PR_VIEWS_ENTRYID",                    PR_VIEWS_ENTRYID),
    RLENTRY( "@PR_DEFAULT_VIEW_ENTRYID",             PR_DEFAULT_VIEW_ENTRYID),
    RLENTRY( "@PR_COMMON_VIEWS_ENTRYID",             PR_COMMON_VIEWS_ENTRYID),
    RLENTRY( "@PR_FINDER_ENTRYID",                   PR_FINDER_ENTRYID),
    RLENTRY( "@PR_HEADER_FOLDER_ENTRYID",            PR_HEADER_FOLDER_ENTRYID),
    RLENTRY( "@PR_SCHEDULE_FOLDER_ENTRYID",          PR_SCHEDULE_FOLDER_ENTRYID),
    RLENTRY( "@PR_IPM_DAF_ENTRYID",                  PR_IPM_DAF_ENTRYID),
    RLENTRY( "@PR_EFORMS_REGISTRY_ENTRYID",          PR_EFORMS_REGISTRY_ENTRYID),
    RLENTRY( "@PR_SPLUS_FREE_BUSY_ENTRYID",          PR_SPLUS_FREE_BUSY_ENTRYID),
    RLENTRY( "@PR_OFFLINE_ADDRBOOK_ENTRYID",         PR_OFFLINE_ADDRBOOK_ENTRYID),
    RLENTRY( "@PR_EFORMS_FOR_LOCALE_ENTRYID",        PR_EFORMS_FOR_LOCALE_ENTRYID),
    RLENTRY( "@PR_FREE_BUSY_FOR_LOCAL_SITE_ENTRYID", PR_FREE_BUSY_FOR_LOCAL_SITE_ENTRYID),
    RLENTRY( "@PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID",  PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID),
};

static const ULONG ulRootLookupCount = ARRAY_CNT(rgRootLookup);

//$--_HrLookupRootFolder@------------------------------------------------------
//  Compare folder name to known root folder ENTRYID strings.  Return ENTRYID,
//  if matched.
// -----------------------------------------------------------------------------
static HRESULT _HrLookupRootFolderW(
    IN  LPMDB lpMdb,                // pointer to open message store
    IN  LPCWSTR lpszRootFolder,     // root folder name only (no separators)
    OUT ULONG *lpcbeid,             // size of entryid
    OUT LPENTRYID *lppeid)          // pointer to entryid
{
    HRESULT      hr           = NOERROR;

    DEBUGPRIVATE( "HrMAPIFindRootFolderW()");

    hr = CHK_HrMAPIFindRootFolderW( lpMdb, lpszRootFolder, lpcbeid, lppeid);
    if( FAILED( hr))
        RETURN( hr);

    *lpcbeid = 0L;
    *lppeid  = NULL;

    // Since there are no ENTRYID properties for "Inbox"...
    if( _wcsicmp( lpszRootFolder, L"@PR_IPM_INBOX_ENTRYID") == 0)
    {
        hr = MAPICALL( lpMdb)->GetReceiveFolder( lpMdb,
            NULL,       // default message class ("IPM")
            MAPI_UNICODE,
            lpcbeid,
            lppeid,
            NULL);
        if( FAILED( hr))
        {
            if( hr == MAPI_E_NOT_FOUND)
                hr = HR_LOG( EDK_E_NOT_FOUND);
            else
                hr = HR_LOG( E_FAIL);

            goto cleanup;
        }
    }
    else
    {
        ULONG cbRootFolder = 0L;
        ULONG i            = 0L;

        // Calculate length of folder name to speed up search somewhat
        cbRootFolder = cbStrLenW( lpszRootFolder);

        for( i = 0L; i < ulRootLookupCount; i++)
        {
            if( cbRootFolder == rgRootLookup[i].cbRootFolderW &&
                _wcsicmp( rgRootLookup[i].lpszRootFolderW, lpszRootFolder) == 0 )
            {
                SPropTagArray rgPropTag    = { 1, { rgRootLookup[i].ulPropTag}};
                LPSPropValue  lpPropValue  = NULL;
                ULONG         cValues      = 0L;

                // Get the outbox entry ID property.
                hr = MAPICALL(lpMdb)->GetProps( lpMdb,
                    &rgPropTag,
                    MAPI_UNICODE,
                    &cValues,
                    &lpPropValue);
                if( FAILED( hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                if( hr == MAPI_W_ERRORS_RETURNED)
                {
                    if( lpPropValue && lpPropValue->Value.ul == MAPI_E_NOT_FOUND)
                        hr = HR_LOG( MAPI_E_NOT_FOUND);
                    else
                        hr = HR_LOG( E_FAIL);

                    goto cleanup;
                }

                ASSERTERROR(cValues != 0, "ZERO properties returned");
                ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

                // Check to make sure we got the right property.
                if (lpPropValue->ulPropTag == rgRootLookup[i].ulPropTag)
                {
                    *lpcbeid = lpPropValue->Value.bin.cb;

                    hr = MAPIAllocateBuffer( *lpcbeid, (PVOID*)lppeid);
                    if( SUCCEEDED( hr))
                    {
                        CopyMemory( *lppeid, lpPropValue->Value.bin.lpb, *lpcbeid);
                    }
                    else
                    {
                        hr = HR_LOG( E_OUTOFMEMORY);
                    }
                }
                else
                {
                    hr = HR_LOG(E_FAIL);
                }

                MAPIFREEBUFFER( lpPropValue);
                goto cleanup;
            }
        }

        // Not a recognized root folder
        hr = HR_LOG( EDK_E_NOT_FOUND);
    }

cleanup:
    RETURN( hr);
}

static HRESULT _HrLookupRootFolderA(
    IN  LPMDB lpMdb,                // pointer to open message store
    IN  LPCSTR lpszRootFolder,      // root folder name only (no separators)
    OUT ULONG *lpcbeid,             // size of entryid
    OUT LPENTRYID *lppeid)          // pointer to entryid
{
    HRESULT      hr           = NOERROR;

    DEBUGPUBLIC( "HrMAPIFindRootFolderA()");

    hr = CHK_HrMAPIFindRootFolderA( lpMdb, lpszRootFolder, lpcbeid, lppeid);
    if( FAILED( hr))
        RETURN( hr);

    *lpcbeid = 0L;
    *lppeid  = NULL;

    // Since there are no ENTRYID properties for "Inbox"...
    if( _strcmpi( lpszRootFolder, "@PR_IPM_INBOX_ENTRYID") == 0)
    {
        hr = MAPICALL( lpMdb)->GetReceiveFolder( lpMdb,
            NULL,       // default message class ("IPM")
            0L,
            lpcbeid,
            lppeid,
            NULL);
        if( FAILED( hr))
        {
            if( hr == MAPI_E_NOT_FOUND)
                hr = HR_LOG( EDK_E_NOT_FOUND);
            else
                hr = HR_LOG( E_FAIL);

            goto cleanup;
        }
    }
    else
    {
        ULONG cbRootFolder = 0L;
        ULONG i            = 0L;

        // Calculate length of folder name to speed up search somewhat
        cbRootFolder = cbStrLenA( lpszRootFolder);

        for( i = 0L; i < ulRootLookupCount; i++)
        {
            if( cbRootFolder == rgRootLookup[i].cbRootFolderA &&
                _strcmpi( rgRootLookup[i].lpszRootFolderA, lpszRootFolder) == 0 )
            {
                SPropTagArray rgPropTag    = { 1, { rgRootLookup[i].ulPropTag}};
                LPSPropValue  lpPropValue  = NULL;
                ULONG         cValues      = 0L;

                // Get the outbox entry ID property.
                hr = MAPICALL(lpMdb)->GetProps( lpMdb,
                    &rgPropTag,
                    0L,
                    &cValues,
                    &lpPropValue);
                if( FAILED( hr))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                if( hr == MAPI_W_ERRORS_RETURNED)
                {
                    if( lpPropValue && lpPropValue->Value.ul == MAPI_E_NOT_FOUND)
                        hr = HR_LOG( MAPI_E_NOT_FOUND);
                    else
                        hr = HR_LOG( E_FAIL);

                    goto cleanup;
                }

                ASSERTERROR(cValues != 0, "ZERO properties returned");
                ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

                // Check to make sure we got the right property.
                if (lpPropValue->ulPropTag == rgRootLookup[i].ulPropTag)
                {
                    *lpcbeid = lpPropValue->Value.bin.cb;

                    hr = MAPIAllocateBuffer( *lpcbeid, (PVOID*)lppeid);
                    if( SUCCEEDED( hr))
                    {
                        CopyMemory( *lppeid, lpPropValue->Value.bin.lpb, *lpcbeid);
                    }
                    else
                    {
                        hr = HR_LOG( E_OUTOFMEMORY);
                    }
                }
                else
                {
                    hr = HR_LOG(E_FAIL);
                }

                MAPIFREEBUFFER( lpPropValue);
                goto cleanup;
            }
        }

        // Not a recognized root folder
        hr = HR_LOG( EDK_E_NOT_FOUND);
    }

cleanup:
    RETURN( hr);
}

//$--HrMAPIFindFolder@-----------------------------------------------------
//  Search folder for entry ID of child folder by name.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindFolderW(         // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,           // pointer to folder
    IN LPCWSTR lpszName,                // name of folder to find
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULThr           = NOERROR;
LPMAPITABLElpTable      = NULL;
LPSRowSetlpRow        = NULL;
    LPSPropValue    lpRowProp    = NULL;
    ULONG           i            = 0L;

    static const enum { IDISPNAME, IENTRYID };
    static const SizedSPropTagArray( 2, rgColProps) = { 2, { PR_DISPLAY_NAME_W, PR_ENTRYID}};

DEBUGPUBLIC( "HrMAPIFindFolderW()");

    hr = CHK_HrMAPIFindFolderW( lpFolder, lpszName, lpcbeid, lppeid);
    if (FAILED(hr))
        RETURN(hr);

    // Avoid dereferencing a NULL in retail builds
    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lpcbeid = 0;
    *lppeid  = NULL;

    if( !lpFolder)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

hr = MAPICALL( lpFolder)->GetHierarchyTable( lpFolder,
        MAPI_UNICODE | MAPI_DEFERRED_ERRORS, &lpTable);
if (FAILED(hr))
    {
        hr = HR_LOG( E_FAIL);
goto cleanup;
    }

    hr = HrQueryAllRows( lpTable, (LPSPropTagArray)&rgColProps, NULL, NULL, 0L, &lpRow);
if( FAILED( hr))
    {
        if( hr == MAPI_E_NOT_FOUND)
            hr = HR_LOG( EDK_E_NOT_FOUND);
        else
            hr = HR_LOG( E_FAIL);

        goto cleanup;
    }

    ASSERTERROR( lpRow != NULL, "NULL lpRow returned from QueryRows!");

    // Search rows for the folder in question
    for(i = 0; i < lpRow->cRows; i++)
    {
        ASSERTERROR(
            lpRow->aRow[i].cValues > 0,
            "Row is empty of properties.");

        lpRowProp = lpRow->aRow[i].lpProps;

        ASSERTERROR(
            lpRowProp[IENTRYID].ulPropTag == PR_ENTRYID,
            "Row does not contain entry id");
        ASSERTERROR(
            lpRowProp[IDISPNAME].ulPropTag == PR_DISPLAY_NAME_A,
            "Row does not contain display name");

        if(_wcsicmp(lpRowProp[IDISPNAME].Value.lpszW, lpszName) == 0)
        {
            *lpcbeid = lpRowProp[IENTRYID].Value.bin.cb;

            hr = MAPIAllocateBuffer(*lpcbeid, (PVOID*)lppeid);
            if(FAILED(hr))
            {
                hr = HR_LOG(E_OUTOFMEMORY);
                goto cleanup;
            }

            // Copy entry ID
            CopyMemory( *lppeid, lpRowProp[IENTRYID].Value.bin.lpb, *lpcbeid);
            goto cleanup;
        }

    }  // for

    hr = HR_LOG( EDK_E_NOT_FOUND);

cleanup:
FREEPROWS(lpRow);
ULRELEASE(lpTable);

    RETURN(hr);
}

HRESULT HrMAPIFindFolderA(         // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,           // pointer to folder
    IN LPCSTR  lpszName,                // name of folder to find
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULThr           = NOERROR;
LPMAPITABLElpTable      = NULL;
LPSRowSetlpRow        = NULL;
    LPSPropValue    lpRowProp    = NULL;
    ULONG           i            = 0L;

    static const enum { IDISPNAME, IENTRYID };
    static SizedSPropTagArray( 2, rgColProps) = { 2, { PR_DISPLAY_NAME_A, PR_ENTRYID}};

DEBUGPUBLIC( "HrMAPIFindFolderA()");

    hr = CHK_HrMAPIFindFolderA( lpFolder, lpszName, lpcbeid, lppeid);
    if (FAILED(hr))
        RETURN(hr);

    // Avoid dereferencing a NULL in retail builds
    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lpcbeid = 0;
    *lppeid  = NULL;

    if( !lpFolder)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

hr = MAPICALL( lpFolder)->GetHierarchyTable( lpFolder,
        MAPI_DEFERRED_ERRORS, &lpTable);
if (FAILED(hr))
    {
        hr = HR_LOG( E_FAIL);
goto cleanup;
    }

    hr = HrQueryAllRows( lpTable, (LPSPropTagArray)&rgColProps, NULL, NULL, 0L, &lpRow);
if( FAILED( hr))
    {
        if( hr == MAPI_E_NOT_FOUND)
            hr = HR_LOG( EDK_E_NOT_FOUND);
        else
            hr = HR_LOG( E_FAIL);

        goto cleanup;
    }

    ASSERTERROR( lpRow != NULL, "NULL lpRow returned from QueryRows!");

    // Search rows for the folder in question
    for(i = 0; i < lpRow->cRows; i++)
    {
        ASSERTERROR(
            lpRow->aRow[i].cValues > 0,
            "Row is empty of properties.");

        lpRowProp = lpRow->aRow[i].lpProps;

        ASSERTERROR(
            lpRowProp[IENTRYID].ulPropTag == PR_ENTRYID,
            "Row does not contain entry id");
        ASSERTERROR(
            lpRowProp[IDISPNAME].ulPropTag == PR_DISPLAY_NAME_A,
            "Row does not contain display name");

        if(_strcmpi(lpRowProp[IDISPNAME].Value.lpszA, lpszName) == 0)
        {
            *lpcbeid = lpRowProp[IENTRYID].Value.bin.cb;

            hr = MAPIAllocateBuffer(*lpcbeid, (PVOID*)lppeid);
            if(FAILED(hr))
            {
                hr = HR_LOG(E_OUTOFMEMORY);
                goto cleanup;
            }

            // Copy entry ID
            CopyMemory( *lppeid, lpRowProp[IENTRYID].Value.bin.lpb, *lpcbeid);
            goto cleanup;
        }

    }  // for

    hr = HR_LOG( EDK_E_NOT_FOUND);

cleanup:
FREEPROWS(lpRow);
ULRELEASE(lpTable);

    RETURN(hr);
}

//$--HrMAPIFindSubfolderEx@---------------------------------------------------------
//  Finds an arbitrarily nested folder in the indicated folder given it's
//  path name.
//------------------------------------------------------------------------------
HRESULT HrMAPIFindSubfolderExW(
    IN LPMAPIFOLDER lpRootFolder,       // open root folder
    IN WCHAR chSep,                     // folder path separator character
    IN LPCWSTR lpszFolderPath,          // folder path
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULT         hr              = NOERROR;
    LPMAPIFOLDER    lpParentFolder  = lpRootFolder;
    LPMAPIFOLDER    lpChildFolder   = NULL;
ULONG           cbeid           = 0L;
    LPENTRYID       lpeid           = NULL;
    LPWSTR         *lppszFolderList = NULL;
    WCHAR           rgchSep[2]      = L"\\";
    ULONG           ulFolderCount   = 0L;
ULONGulObjType    = 0L;
    ULONG           i               = 0L;

    DEBUGPUBLIC( "HrMAPIFindSubfolderExW()");

    hr = CHK_HrMAPIFindSubfolderExW(
        lpRootFolder, chSep, lpszFolderPath, lpcbeid, lppeid);
    if( FAILED( hr))
        RETURN( hr);

    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    // Set the user defined separator character
    rgchSep[0] = chSep;

    hr = HrStrTokAllW( lpszFolderPath, rgchSep, &ulFolderCount, &lppszFolderList);
    if( FAILED( hr))
        goto cleanup;

    for(i = 0L; i < ulFolderCount; i++)
    {
        // Free entryid before re-use.
        MAPIFREEBUFFER( lpeid);

        hr = HrMAPIFindFolderW( lpParentFolder, lppszFolderList[i], &cbeid, &lpeid);
        if( FAILED( hr))
            goto cleanup;

        // Only OpenEntry if needed for next tier of folder path.
        if( lppszFolderList[i+1] != NULL)
        {
            hr = MAPICALL( lpParentFolder)->OpenEntry( lpParentFolder,
                cbeid,
                lpeid,
                NULL,
                MAPI_DEFERRED_ERRORS,
                &ulObjType,
                (LPUNKNOWN*)&lpChildFolder);
            if( FAILED( hr) || ulObjType != MAPI_FOLDER)
            {
                MAPIFREEBUFFER( lpeid);

                hr = HR_LOG( E_FAIL);
                goto cleanup;
            }
        }

        // No longer need the parent folder
        // (Don't release the folder that was passed!)
        if( i > 0L)
            ULRELEASE( lpParentFolder);

        lpParentFolder = lpChildFolder;
        lpChildFolder  = NULL;
    }

    // Success!
    *lpcbeid = cbeid;
    *lppeid  = lpeid;

cleanup:
    MAPIFREEBUFFER( lppszFolderList);

    RETURN( hr);
}

HRESULT HrMAPIFindSubfolderExA(
    IN LPMAPIFOLDER lpRootFolder,       // open root folder
    IN CHAR chSep,                      // folder path separator character
    IN LPCSTR lpszFolderPath,           // folder path
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULT         hr              = NOERROR;
    LPMAPIFOLDER    lpParentFolder  = lpRootFolder;
    LPMAPIFOLDER    lpChildFolder   = NULL;
ULONG           cbeid           = 0L;
    LPENTRYID       lpeid           = NULL;
    LPSTR          *lppszFolderList = NULL;
    CHAR            rgchSep[2]      = "\\";
    ULONG           ulFolderCount   = 0L;
ULONGulObjType    = 0L;
    ULONG           i               = 0L;

    DEBUGPUBLIC( "HrMAPIFindSubfolderExA()");

    hr = CHK_HrMAPIFindSubfolderExA(
        lpRootFolder, chSep, lpszFolderPath, lpcbeid, lppeid);
    if( FAILED( hr))
        RETURN( hr);

    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    // Set the user defined separator character
    rgchSep[0] = chSep;

    hr = HrStrTokAllA( lpszFolderPath, rgchSep, &ulFolderCount, &lppszFolderList);
    if( FAILED( hr))
        goto cleanup;

    for(i = 0L; i < ulFolderCount; i++)
    {
        // Free entryid before re-use.
        MAPIFREEBUFFER( lpeid);

        hr = HrMAPIFindFolderA( lpParentFolder, lppszFolderList[i], &cbeid, &lpeid);
        if( FAILED( hr))
            goto cleanup;

        // Only OpenEntry if needed for next tier of folder path.
        if( lppszFolderList[i+1] != NULL)
        {
            hr = MAPICALL( lpParentFolder)->OpenEntry( lpParentFolder,
                cbeid,
                lpeid,
                NULL,
                MAPI_DEFERRED_ERRORS,
                &ulObjType,
                (LPUNKNOWN*)&lpChildFolder);
            if( FAILED( hr) || ulObjType != MAPI_FOLDER)
            {
                MAPIFREEBUFFER( lpeid);

                hr = HR_LOG( E_FAIL);
                goto cleanup;
            }
        }

        // No longer need the parent folder
        // (Don't release the folder that was passed!)
        if( i > 0L)
            ULRELEASE( lpParentFolder);

        lpParentFolder = lpChildFolder;
        lpChildFolder  = NULL;
    }

    // Success!
    *lpcbeid = cbeid;
    *lppeid  = lpeid;

cleanup:
    MAPIFREEBUFFER( lppszFolderList);

    RETURN( hr);
}

//$--HrMAPIFindFolderEx@-----------------------------------------------
//  Finds an arbitrarily nested folder in the indicated store given it's
//  path name.
//------------------------------------------------------------------------------
HRESULT HrMAPIFindFolderExW(
    IN LPMDB lpMdb,                     // Open message store
    IN WCHAR  chSep,                    // folder path separator character
    IN LPCWSTR lpszFolderPath,          // folder path
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULT      hr              = NOERROR;
    LPMAPIFOLDER lpRootFolder    = NULL;
    const WCHAR *cpPath          = lpszFolderPath;
    ULONG        cbeid           = 0L;
    LPENTRYID    lpeid           = NULL;

    DEBUGPUBLIC("HrMAPIFindFolderExW()");

    hr = CHK_HrMAPIFindFolderExW(
        lpMdb, chSep, lpszFolderPath, lpcbeid, lppeid);
    if( FAILED(hr))
        RETURN(hr);

    // Avoid dereferencing a NULL in retail builds
    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lpcbeid = 0L;
    *lppeid  = NULL;

    if( !lpMdb)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Move forward past any initial path separators
    while( *cpPath && *cpPath == chSep) cpPath++;

    // Check for literal property name
    if( *cpPath == L'@')
    {
        WCHAR  szRootFolder[MAX_PATH + 1] = {0};
        WCHAR *cpFolder        = szRootFolder;

        // Copy root folder to string buffer
        while( *cpPath && *cpPath != chSep) *cpFolder++ = *cpPath++;

        *cpFolder = 0;

        hr = _HrLookupRootFolderW( lpMdb, szRootFolder, &cbeid, &lpeid);
        if( hr == EDK_E_NOT_FOUND)
        {   // Folder was unrecognized!  Try opening the REAL root folder,
            // and pass the FULL path to HrMAPIFindSubfolderEx().

            cpPath = lpszFolderPath;
            cbeid  = 0L;      // force REAL root folder
            lpeid  = NULL;
        }
        else if( FAILED( hr))
            goto cleanup;
        else
        {   // Folder found! If path continues, open the returned ENTRYID
            // and pass the REMAINING path to HrMAPIFindSubfolderEx().

            // Skip past next separators, if necessary
            while( *cpPath && *cpPath == chSep) cpPath++;
        }
    }

    if( *cpPath)
    {   // The path continues!

        ULONG ulObjType = 0;

        hr = MAPICALL( lpMdb)->OpenEntry( lpMdb,
            cbeid,
            lpeid,
            NULL,
            MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
            &ulObjType,
            (LPUNKNOWN*)&lpRootFolder);
        if( FAILED( hr))
            goto cleanup;

        if( ulObjType != MAPI_FOLDER)
        {
            hr = HR_LOG( E_FAIL);
            goto cleanup;
        }

        // Free before re-use
        MAPIFREEBUFFER( lpeid);

        // Find the subfolder in question
        hr = HrMAPIFindSubfolderExW(
            lpRootFolder, chSep, cpPath, &cbeid, &lpeid);
        if(FAILED(hr))
            goto cleanup;
    }

    // Success!
    *lpcbeid = cbeid;
    *lppeid  = lpeid;

cleanup:
    ULRELEASE(lpRootFolder);

    RETURN(hr);
}

HRESULT HrMAPIFindFolderExA(
    IN LPMDB lpMdb,                     // Open message store
    IN CHAR  chSep,                     // folder path separator character
    IN LPCSTR lpszFolderPath,           // folder path
    OUT ULONG *lpcbeid,                 // pointer to count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID pointer
{
    HRESULT      hr              = NOERROR;
    LPMAPIFOLDER lpRootFolder    = NULL;
    const CHAR  *cpPath          = lpszFolderPath;
    ULONG        cbeid           = 0L;
    LPENTRYID    lpeid           = NULL;

    DEBUGPUBLIC("HrMAPIFindFolderExA()");

    hr = CHK_HrMAPIFindFolderExA(
        lpMdb, chSep, lpszFolderPath, lpcbeid, lppeid);
    if( FAILED(hr))
        RETURN(hr);

    // Avoid dereferencing a NULL in retail builds
    if( !lpcbeid || !lppeid)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lpcbeid = 0L;
    *lppeid  = NULL;

    if( !lpMdb)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Move forward past any initial path separators
    while( *cpPath && *cpPath == chSep) cpPath++;

    // Check for literal property name
    if( *cpPath == '@')
    {
        CHAR  szRootFolder[MAX_PATH + 1] = {0};
        CHAR *cpFolder        = szRootFolder;

        // Copy root folder to string buffer
        while( *cpPath && *cpPath != chSep) *cpFolder++ = *cpPath++;

        *cpFolder = 0;

        hr = _HrLookupRootFolderA( lpMdb, szRootFolder, &cbeid, &lpeid);
        if( hr == EDK_E_NOT_FOUND)
        {   // Folder was unrecognized!  Try opening the REAL root folder,
            // and pass the FULL path to HrMAPIFindSubfolderEx().

            cpPath = lpszFolderPath;
            cbeid  = 0L;      // force REAL root folder
            lpeid  = NULL;
        }
        else if( FAILED( hr))
            goto cleanup;
        else
        {   // Folder found! If path continues, open the returned ENTRYID
            // and pass the REMAINING path to HrMAPIFindSubfolderEx().

            // Skip past next separators, if necessary
            while( *cpPath && *cpPath == chSep) cpPath++;
        }
    }

    if( *cpPath)
    {   // The path continues!

        ULONG ulObjType = 0;

        hr = MAPICALL( lpMdb)->OpenEntry( lpMdb,
            cbeid,
            lpeid,
            NULL,
            MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
            &ulObjType,
            (LPUNKNOWN*)&lpRootFolder);
        if( FAILED( hr))
            goto cleanup;

        if( ulObjType != MAPI_FOLDER)
        {
            hr = HR_LOG( E_FAIL);
            goto cleanup;
        }

        // Free before re-use
        MAPIFREEBUFFER( lpeid);

        // Find the subfolder in question
        hr = HrMAPIFindSubfolderExA(
            lpRootFolder, chSep, cpPath, &cbeid, &lpeid);
        if(FAILED(hr))
            goto cleanup;
    }

    // Success!
    *lpcbeid = cbeid;
    *lppeid  = lpeid;

cleanup:
    ULRELEASE(lpRootFolder);

    RETURN(hr);
}

//$--HrMAPIOpenFolderEx@-----------------------------------------------
//  Opens an arbitrarily nested folder in the indicated store given it's
//  path name.
//------------------------------------------------------------------------------
HRESULT HrMAPIOpenFolderExW(
    IN LPMDB lpMdb,                     // Open message store
    IN WCHAR chSep,                     // folder path separator character
    IN LPCWSTR lpszFolderPath,          // folder path
    OUT LPMAPIFOLDER * lppFolder)       // pointer to folder opened
{
    HRESULT   hr        = NOERROR;
    LPENTRYID lpeid     = NULL;
    ULONG     cbeid     = 0;
    ULONG     ulObjType = 0;

    DEBUGPUBLIC( "HrMAPIOpenFolderExW()");

    hr = CHK_HrMAPIOpenFolderExW(
        lpMdb, chSep, lpszFolderPath, lppFolder);
    if(FAILED(hr))
        RETURN(hr);

    if( !lppFolder)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lppFolder = NULL;

    hr = HrMAPIFindFolderExW(
        lpMdb, chSep, lpszFolderPath,
        &cbeid, &lpeid);
    if(FAILED(hr))
        goto cleanup;

    hr = MAPICALL( lpMdb)->OpenEntry( lpMdb,
        cbeid,
        lpeid,
        NULL,

MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)lppFolder);
    if( FAILED( hr) || ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR( *lppFolder, "INVALID *lppFolder pointer");

cleanup:
    MAPIFREEBUFFER(lpeid);

    RETURN(hr);
}

HRESULT HrMAPIOpenFolderExA(
    IN LPMDB lpMdb,                     // Open message store
    IN CHAR chSep,                      // folder path separator character
    IN LPCSTR lpszFolderPath,           // folder path
    OUT LPMAPIFOLDER * lppFolder)       // pointer to folder opened
{
    HRESULT   hr        = NOERROR;
    LPENTRYID lpeid     = NULL;
    ULONG     cbeid     = 0;
    ULONG     ulObjType = 0;

    DEBUGPUBLIC( "HrMAPIOpenFolderExA()");

    hr = CHK_HrMAPIOpenFolderExA(
        lpMdb, chSep, lpszFolderPath, lppFolder);
    if(FAILED(hr))
        RETURN(hr);

    if( !lppFolder)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lppFolder = NULL;

    hr = HrMAPIFindFolderExA(
        lpMdb, chSep, lpszFolderPath,
        &cbeid, &lpeid);
    if(FAILED(hr))
        goto cleanup;

    hr = MAPICALL(lpMdb)->OpenEntry( lpMdb,
        cbeid,
        lpeid,
        NULL,
        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)lppFolder);
    if( FAILED( hr) || ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(*lppFolder, "INVALID *lppFolder pointer");

cleanup:
    MAPIFREEBUFFER(lpeid);

    RETURN(hr);
}

//$--HrMAPIOpenSubfolderEx@---------------------------------------------------
//  Opens an aribtrarily nested folder in the indicated folder given
//  its path name.
//------------------------------------------------------------------------
HRESULT HrMAPIOpenSubfolderExW(
    IN LPMAPIFOLDER lpRootFolder,       // open root folder
    IN WCHAR chSep,                     // character used as path separator
    IN LPCWSTR lpszFolderPath,          // folder path
    OUT LPMAPIFOLDER *lppFolder)        // pointer to open child folder
{
    HRESULT   hr        = NOERROR;
    LPENTRYID lpeid     = NULL;
    ULONG     cbeid     = 0;
    ULONG     ulObjType = 0;

    DEBUGPUBLIC( "HrMAPIOpenSubfolderExW()");

    hr = CHK_HrMAPIOpenSubfolderExW(
        lpRootFolder, chSep, lpszFolderPath, lppFolder);
    if(FAILED(hr))
        RETURN(hr);

    if( !lppFolder)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lppFolder = NULL;

    hr = HrMAPIFindSubfolderExW(
        lpRootFolder, chSep, lpszFolderPath,
        &cbeid, &lpeid);
    if(FAILED(hr))
        goto cleanup;

    hr = MAPICALL( lpRootFolder)->OpenEntry( lpRootFolder,
        cbeid,
        lpeid,
        NULL,
        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)lppFolder);
    if( FAILED( hr) || ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR( *lppFolder, "INVALID Folder!");

cleanup:
    MAPIFREEBUFFER(lpeid);

    RETURN(hr);
}

HRESULT HrMAPIOpenSubfolderExA(
    IN LPMAPIFOLDER lpRootFolder,       // open root folder
    IN CHAR chSep,                      // character used as path separator
    IN LPCSTR lpszFolderPath,           // folder path
    OUT LPMAPIFOLDER *lppFolder)        // pointer to open child folder
{
    HRESULT   hr        = NOERROR;
    LPENTRYID lpeid     = NULL;
    ULONG     cbeid     = 0;
    ULONG     ulObjType = 0;

    DEBUGPUBLIC( "HrMAPIOpenSubfolderExA()");

    hr = CHK_HrMAPIOpenSubfolderExA(
        lpRootFolder, chSep, lpszFolderPath, lppFolder);
    if(FAILED(hr))
        RETURN(hr);

    if( !lppFolder)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    *lppFolder = NULL;

    hr = HrMAPIFindSubfolderExA(
        lpRootFolder, chSep, lpszFolderPath,
        &cbeid, &lpeid);
    if(FAILED(hr))
        goto cleanup;

    hr = MAPICALL( lpRootFolder)->OpenEntry( lpRootFolder,
        cbeid,
        lpeid,
        NULL,
        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)lppFolder);
    if( FAILED( hr) || ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR( *lppFolder, "INVALID Folder!");

cleanup:
    MAPIFREEBUFFER(lpeid);

    RETURN(hr);
}

//$--HrOpenExchangePublicFolders-----------------------------------------------
//  Opens the root of the public folder heirarchy.
//------------------------------------------------------------------------
HRESULT HrOpenExchangePublicFolders(
    IN LPMDB lpPubStore,                // pointer to public message store
    OUT LPMAPIFOLDER *lppRootFolder)    // return ptr to public folder root
{
    HRESULT       hr           = NOERROR;
    ULONG         cValues      = 0L;
    LPSPropValue  lpPropValue  = NULL;
    SPropTagArray rgPropTag    = { 1, { PR_IPM_PUBLIC_FOLDERS_ENTRYID } };
    ULONG         ulObjType    = 0L;
    LPMAPIFOLDER  lpRootFolder = NULL;

    DEBUGPUBLIC( "HrOpenExchangePublicFolders()");

    hr = CHK_HrOpenExchangePublicFolders( lpPubStore, lppRootFolder);
    if( FAILED( hr))
        RETURN( hr);

    // Avoid dereferencing a NULL in retail builds
    if( !lpPubStore)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    // Get the outbox entry ID property.
    hr = MAPICALL(lpPubStore)->GetProps( lpPubStore,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);
    if( FAILED( hr))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if( hr == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_IPM_PUBLIC_FOLDERS_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = MAPICALL( lpPubStore)->OpenEntry( lpPubStore,
        lpPropValue->Value.bin.cb,
        (LPENTRYID)lpPropValue->Value.bin.lpb,
        NULL,
        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)&lpRootFolder);
    if( FAILED( hr) || ulObjType != MAPI_FOLDER)
        goto cleanup;

    *lppRootFolder = lpRootFolder;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}


IMESSAGE.C

// --imessage.c-----------------------------------------------------------------
//
//  Module containing MAPI utility functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "imessage.chk"

//$--HrEDKSearchTableByProp-----------------------------------------------------
//  Find the entry IDs matching the restriction.
// -----------------------------------------------------------------------------
static HRESULT HrEDKSearchTableByProp(  // RETURNS: return code
    IN LPMAPITABLE lpTable,             // pointer to table
    IN LPSPropValue lpSPropValue,       // property value
    OUT LPENTRYLIST *lppMsgList)        // list of matching entry IDs
{
    HRESULT       hr         = NOERROR;
    HRESULT       hrT        = NOERROR;
    LPSRowSet     lpRows     = NULL;
    LPENTRYLIST   lpMsgList  = NULL;
    ULONG         i          = 0;
    ULONG         cbeid      = 0;
    LPENTRYID     lpeid      = NULL;
    ULONG         cRows      = 0;
    SPropTagArray rgPropTags = { 1, { PR_ENTRYID } };
    SRestriction  sres       = { 0 };
    LPSPropValue  lpProp     = NULL;

    DEBUGPRIVATE("HrEDKSearchTableByProp()");

    hr = CHK_HrEDKSearchTableByProp(
        lpTable,
        lpSPropValue,
        lppMsgList);

    if(FAILED(hr))
        RETURN(hr);

    *lppMsgList = NULL;

    // Set the columns to return
    hrT = MAPICALL(lpTable)->SetColumns(lpTable, &rgPropTags, TBL_BATCH);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Restrict rows to those that match criteria

    sres.rt                         = RES_PROPERTY;
    sres.res.resProperty.relop      = RELOP_EQ;
    sres.res.resProperty.ulPropTag  = lpSPropValue->ulPropTag;
    sres.res.resProperty.lpProp     = lpSPropValue;

    hrT = MAPICALL(lpTable)->Restrict(lpTable, &sres, 0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Search the rows of the table, EDK_MAX_QUERY_ROWS at a time

    for(;;)
    {

        // Get a row set

        hrT = MAPICALL(lpTable)->QueryRows(
            lpTable,
            EDK_MAX_QUERY_ROWS,
            0,
            &lpRows);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        cRows = lpRows->cRows;

        // If table is empty then no message(s)

        if((cRows == 0) && (lpMsgList == NULL))
        {
            hr = HR_LOG(EDK_E_NOT_FOUND);
            goto cleanup;
        }

        // Create list of entry IDs

        for(i = 0; i < cRows; i++)
        {
            lpProp = lpRows->aRow[i].lpProps;

            if((lpProp[0].ulPropTag == PR_ENTRYID) && (lpRows->aRow[i].cValues > 0))
            {
                cbeid = lpProp[0].Value.bin.cb;

                lpeid = (LPENTRYID)lpProp[0].Value.bin.lpb;

                if(lpMsgList == NULL)
                {
                    hrT = HrMAPICreateEntryList(cbeid,lpeid,&lpMsgList);

                    if(FAILED(hrT))
                    {
                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }
                }
                else
                {
                    hrT = HrMAPIAppendEntryList(cbeid,lpeid,lpMsgList);

                    if(FAILED(hrT))
                    {
                        hr = HR_LOG(E_FAIL);
                        goto cleanup;
                    }
                }
            }
            else
            {
                ASSERTERROR(
                    (lpProp[0].ulPropTag == PR_ENTRYID),"INVALID property tag");

                ASSERTERROR((lpRows->aRow[i].cValues > 0),"INVALID row entry");
            }
        }

        FREEPROWS(lpRows);
    }

cleanup:

    FREEPROWS(lpRows);

    if(FAILED(hr))
    {
        hrT = HrMAPIDestroyEntryList(&lpMsgList);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
        }

        *lppMsgList = NULL;
    }
    else
    {
        *lppMsgList = lpMsgList;
    }

    RETURN(hr);
}

//$--HrMAPIFindMsgByProp---------------------------------------------------------
//  Find the entry ID of a message given a property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindMsgByProp(         // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,       // pointer to folder
    IN LPSPropValue lpSPropValue,   // property value
    OUT LPENTRYLIST *lppMsgList)    // list of matching messages
{
    HRESULT       hr         = NOERROR;
    HRESULT       hrT        = NOERROR;
    LPMAPITABLE   lpTable    = NULL;

    DEBUGPUBLIC("HrMAPIFindMsgByProp()");

    hr = CHK_HrMAPIFindMsgByProp(
        lpFolder,
        lpSPropValue,
        lppMsgList);

    if(FAILED(hr))
        RETURN(hr);

    // Get the contents table for the folder
    hrT = MAPICALL(lpFolder)->GetContentsTable(
        lpFolder,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrEDKSearchTableByProp(
        lpTable,
        lpSPropValue,
        lppMsgList);

cleanup:

    ULRELEASE(lpTable);

    RETURN(hr);
}

//$--HrMAPIMoveMessage--------------------------------------------------------
//  Move one message from one folder to another.
// -----------------------------------------------------------------------------
HRESULT HrMAPIMoveMessage(            // RETURNS: return code
    IN LPMAPIFOLDER lpSrcFolder,        // pointer to source folder
    IN LPMAPIFOLDER lpDstFolder,        // pointer to destination folder
    IN ULONG cbeid,                     // count of bytes in entry ID
    IN LPENTRYID lpeid)                 // pointer to entry ID
{
    HRESULT     hr           = NOERROR;
    HRESULT     hrT          = NOERROR;
    LPENTRYLIST lpMsgList    = NULL;

    DEBUGPUBLIC("HrMAPIMoveMessage()");

    hr = CHK_HrMAPIMoveMessage(
        lpSrcFolder,
        lpDstFolder,
        cbeid,
        lpeid);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPICreateEntryList(
        cbeid,
        lpeid,
        &lpMsgList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Move the message

    hrT = MAPICALL(lpSrcFolder)->CopyMessages(
        lpSrcFolder,
        lpMsgList,
        NULL,
        lpDstFolder,
        (ULONG)0,
        NULL,
        MESSAGE_MOVE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    hrT = HrMAPIDestroyEntryList(
        &lpMsgList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPICopyMessage--------------------------------------------------------
//  Copy one message from one folder to another.
// -----------------------------------------------------------------------------
HRESULT HrMAPICopyMessage(            // RETURNS: return code
    IN LPMAPIFOLDER lpSrcFolder,        // pointer to source folder
    IN LPMAPIFOLDER lpDstFolder,        // pointer to destination folder
    IN ULONG cbeid,                     // count of bytes in entry ID
    IN LPENTRYID lpeid)                 // pointer to entry ID
{
    HRESULT     hr           = NOERROR;
    HRESULT     hrT          = NOERROR;
    LPENTRYLIST lpMsgList    = NULL;

    DEBUGPUBLIC("HrMAPICopyMessage()");

    hr = CHK_HrMAPICopyMessage(
        lpSrcFolder,
        lpDstFolder,
        cbeid,
        lpeid);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPICreateEntryList(
        cbeid,
        lpeid,
        &lpMsgList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Copy the message

    hrT = MAPICALL(lpSrcFolder)->CopyMessages(
        lpSrcFolder,
        lpMsgList,
        NULL,
        lpDstFolder,
        (ULONG)0,
        NULL,
        0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    hrT = HrMAPIDestroyEntryList(
        &lpMsgList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIDeleteMessage------------------------------------------------------
//  Delete one message from one folder to another.
// -----------------------------------------------------------------------------
HRESULT HrMAPIDeleteMessage(          // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,           // pointer to folder
    IN ULONG cbeid,                     // count of bytes in entry ID
    IN LPENTRYID lpeid)                 // pointer to entry ID
{
    HRESULT     hr           = NOERROR;
    HRESULT     hrT          = NOERROR;
    LPENTRYLIST lpMsgList    = NULL;

    DEBUGPUBLIC("HrMAPIDeleteMessage()");

    hr = CHK_HrMAPIDeleteMessage(
        lpFolder,
        cbeid,
        lpeid);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPICreateEntryList(
        cbeid,
        lpeid,
        &lpMsgList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Remove the message

    hrT = MAPICALL(lpFolder)->DeleteMessages(
        lpFolder,
        lpMsgList,
        (ULONG)0,
        NULL,
        (ULONG)0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    hrT = HrMAPIDestroyEntryList(
        &lpMsgList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}


IPROP.C

// --iprop.c--------------------------------------------------------------------
//
//  Module containing MAPI utility functions for properties.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "iprop.chk"

//$--HrMAPIOpenCachedProp--------------------------------------------------------
//
//  DESCRIPTION: Create a new (local) IPropData object in which the original object
//  properties are cached.  The local cached can be created for
//  reading (for use with GetProp calls) for for writing (for use with
//  SetProp calls).  The purpose of this function and HrMAPICloseCachedProp
//  is to reduce the number of remote procedure calls made by code
//  which performs many GetProp or SetProp calls on an object.
//
//  INPUT:  lpObj   --  property object to cache
//          lpPropList  --  list of properties to cache (for reading)
//                          defaults to all properties if NULL.
//          ulFlags --  read OR write access flag (EDK_CACHE_READ
//                      or EDK_CACHE_WRITE)
//
//  OUTPUT: lppCachedObj    --  cached property object
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
//  NOTE:   This function creates a cached object for reading only
//          or for writing only.  It does not support and object
//          for both reading and writing.
//
// -----------------------------------------------------------------------------
HRESULT HrMAPIOpenCachedProp(            // RETURNS: return code
    IN LPMAPIPROP lpObj,                // source object
    IN LPSPropTagArray lpPropList,      // list of properties to cache
    IN ULONG ulFlags,                   // open for reading only or for writing only
OUT LPPROPDATA FAR * lppCachedObj)  // cached version of source object
{
HRESULT             hr         = NOERROR;
LPSPropProblemArray lpProblems = NULL;
    LPSPropValue        lpPropVals = NULL;
    ULONG               ulPropCount= 0;     // number of properties

    DEBUGPUBLIC("HrMAPIOpenCachedProp()\n");

    hr = CHK_HrMAPIOpenCachedProp(
        lpObj,
        lpPropList,
        ulFlags,
    lppCachedObj);

    if(FAILED(hr))
        RETURN(hr);

    *lppCachedObj = NULL;

    //
// Call CreateIProp() to create a new IPropData object.
// Since IPropData inherits from IMAPIProp, we can use it as our
// IMAPIProp cache pointer.
    //

hr = CreateIProp(
    &IID_IMAPIPropData, // interface type
MAPIAllocateBuffer,// allocation routine
    MAPIAllocateMore,   // allocate more routine
    MAPIFreeBuffer,     // deallocation routine
    0,                  // reserved
    lppCachedObj);      // address of pointer to new IPropData object

   if(FAILED(hr))
   {
hr = HR_LOG(E_FAIL);

        goto cleanup;
}

    // If we are creating the cache for reading, then
    // we must populate the new object with properties.
    // Otherwise, we pass an "empty" object back to the user
    // for property writing.
    if ( ulFlags == EDK_CACHE_READ )
    {
        // Get properties from remote object and set these same properties
        // on the local object.
        hr = MAPICALL(lpObj)->GetProps(
lpObj,                  // for C to C++ vtbl resolution
            lpPropList,             // Get all properties
            fMapiUnicode,           // flags
            &ulPropCount,           // number of properties retrieved
            &lpPropVals);           // property values structure pointer

        // handle errors
        // If there is an error, release the cached object
        if ( FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        hr = MAPICALL(*lppCachedObj)->SetProps(*lppCachedObj,
                                               ulPropCount,
                                               lpPropVals,
                                               &lpProblems);

        // handle errors
        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Check to see if there were any problems setting the
        // properties
        if ( lpProblems != NULL )
        {
            // We have an error
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }
    }   // end if creating cache for reading

    // We have been successful.

cleanup:

    // Handle error case
    if ( FAILED(hr) )
    {
        ULRELEASE(*lppCachedObj);
    }

    // Free MAPI buffers
    MAPIFREEBUFFER(lpProblems);
    MAPIFREEBUFFER(lpPropVals);

    RETURN(hr);
}

//$--HrMAPICloseCachedProp-------------------------------------------------------
//
//  DESCRIPTION: If object was created as a write cache,
//               copy properties in local cached object
//               back to original remote object.
//
//  INPUT:  lpCachedObj --  cached property object
//          lpOriginalObj   --  original property object
//          ulFlags --  read cache or write cache flag (EDK_CACHE_READ
//                      or EDK_CACHE_WRITE)
//
//  OUTPUT: lppProblems --  set to the property problem array returned
//          by if there were problems setting properties on the original
//          object
//
//  NOTES:  lppProblems:  It may be set, even though overall call
//          is successful.  This is because all of the SetProps have been "deferred" on the
//          original object until this call, the user will need to evaluate
//          the contents of the lppProblems buffer pointer based on which
//          properties he/or she actually tried to set.
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise
//
//                          lppProblems will only be valid if return code
//                          is NOERROR.
//
// -----------------------------------------------------------------------------
HRESULT HrMAPICloseCachedProp(           // RETURNS: return code
    IN LPPROPDATA lpCachedObj,          // cached property object
    IN LPMAPIPROP lpOriginalObj,        // original object
    IN ULONG ulFlags,                   // cache type (EDK_CACHE_READ or EDK_CACHE_WRITE)
    OUT LPSPropProblemArray FAR * lppProblems) // pointer to property problems array if problems setting properties
{
HRESULT             hr          = NOERROR;
    ULONG               ulPropCount = 0;        // number of properties
    LPSPropValue        lpPropVals  = NULL;     // property values

    DEBUGPUBLIC("HrMAPICloseCachedProp()\n");

    hr = CHK_HrMAPICloseCachedProp(
        lpCachedObj,
        lpOriginalObj,
        ulFlags,
        lppProblems);

    if(FAILED(hr))
        RETURN(hr);

    *lppProblems = NULL;    // initialize output

    // If the cache was opened for writing, copy all of the
    // properties from the cached object back to the original
    // object.
    if ( ulFlags == EDK_CACHE_WRITE )
    {
        // Get properties from local object and set these same properties
        // on the remote object.  CopyTo() is not sufficient, because it
        // won't copy read-only properties.
        hr = MAPICALL(lpCachedObj)->GetProps(
lpCachedObj,            // for C to C++ vtbl resolution
            NULL,                   // get all properties
            fMapiUnicode,           // flags
            &ulPropCount,           // number of properties retrieved
            &lpPropVals);           // property values structure pointer

        // handle errors
        if ( FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Set all properties retrieved in the remote object
        hr = MAPICALL(lpOriginalObj)->SetProps(lpOriginalObj,
                                               ulPropCount,
                                               lpPropVals,
                                               lppProblems);

        // handle errors
        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;

        }
    }   // end if cache created for writing

    // The user will need to evaluate why the set property
    // call has failed (if *lppProblems has been set
    // by the SetProps call) based on what properties (if any)
    // they have set in the cached object.

    // Overall, we have been successful.

cleanup:

    // Free MAPI buffers
    MAPIFREEBUFFER(lpPropVals);

    RETURN(hr);
}

//$--HrMAPIGetPropString---------------------------------------------------------
//  Get a string property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropString(             // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    OUT ULONG *lpcbProp,                // count of bytes in property
    OUT LPVOID *lppvProp)               // pointer to property address variable
{
    HRESULT      hr          = NOERROR;
    HRESULT      hrT         = NOERROR;
    ULONG        cValues     = 0;
    LPSPropValue lpPropValue = NULL;
    ULONG        cbProp      = 0;
    SCODE        sc          = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            0
        }
    };

    DEBUGPUBLIC("HrMAPIGetPropString()\n");

    hr = CHK_HrMAPIGetPropString(
        lpObj,
        ulPropTag,
        lpcbProp,
        lppvProp);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbProp = 0;
    *lppvProp = NULL;

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulPropTag;

    hrT = MAPICALL(lpObj)->GetProps(
         lpObj,
         (LPSPropTagArray)&rgPropTag,
         fMapiUnicode,
         &cValues,
         &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    if(PROP_TYPE(ulPropTag) == PT_STRING8)
    {
        cbProp = ((lpPropValue->Value.lpszA == NULL) ?
            sizeof(CHAR) :
            cbStrLenA(lpPropValue->Value.lpszA));
    }
    else
    {
        cbProp = ((lpPropValue->Value.lpszW == NULL) ?
            sizeof(WCHAR) :
            cbStrLenW(lpPropValue->Value.lpszW));
    }

    sc = MAPIAllocateBuffer(cbProp, lppvProp);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    ASSERTERROR(*lppvProp != NULL, "NULL *lppvProp pointer");

    if(PROP_TYPE(ulPropTag) == PT_STRING8)
    {
        memcpy(*lppvProp, lpPropValue->Value.lpszA, cbProp);
    }
    else
    {
        memcpy(*lppvProp, lpPropValue->Value.lpszW, cbProp);
    }

    *lpcbProp = cbProp;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPISetPropString---------------------------------------------------------
//  Set a string property.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropString(             // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN LPVOID lpvProp)                  // pointer to property
{
    HRESULT    hr        = NOERROR;
    HRESULT    hrT       = NOERROR;
    ULONG      cValues   = 1;
    SPropValue PropValue = {0};

    DEBUGPUBLIC("HrMAPISetPropString()\n");

    hr = CHK_HrMAPISetPropString(
        lpObj,
        ulPropTag,
        lpvProp);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize SPropValue structure
    memset(&PropValue, 0, sizeof(PropValue));

    PropValue.ulPropTag     = ulPropTag;

    if(PROP_TYPE(ulPropTag) == PT_STRING8)
    {
        PropValue.Value.lpszA = (LPSTR)lpvProp;
    }
    else
    {
        PropValue.Value.lpszW = (LPWSTR)lpvProp;
    }

    hrT = MAPICALL(lpObj)->SetProps(
        lpObj,
        cValues,
        &PropValue,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIGetPropBinary---------------------------------------------------------
//  Get a binary property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropBinary(             // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    OUT ULONG *lpcbProp,                // count of bytes in property
    OUT LPVOID *lppvProp)               // pointer to property address variable
{
    HRESULT      hr          = NOERROR;
    HRESULT      hrT         = NOERROR;
    ULONG        cValues     = 0;
    LPSPropValue lpPropValue = NULL;
    ULONG        cbProp      = 0;
    SCODE        sc          = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            0
        }
    };

    DEBUGPUBLIC("HrMAPIGetPropBinary()\n");

    hr = CHK_HrMAPIGetPropBinary(
        lpObj,
        ulPropTag,
        lpcbProp,
        lppvProp);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbProp = 0L;
    *lppvProp = NULL;

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulPropTag;

    hrT = MAPICALL(lpObj)->GetProps(
         lpObj,
         (LPSPropTagArray)&rgPropTag,
         fMapiUnicode,
         &cValues,
         &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    cbProp = lpPropValue->Value.bin.cb;

    sc = MAPIAllocateBuffer(cbProp, lppvProp);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    ASSERTERROR(*lppvProp != NULL, "NULL *lppvProp pointer");

    // Copy property value
    memcpy(*lppvProp, lpPropValue->Value.bin.lpb, cbProp);

    *lpcbProp = cbProp;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPISetPropBinary---------------------------------------------------------
//  Set a binary property.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropBinary(             // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN ULONG cbProp,                    // count of bytes in property
    IN LPVOID lpvProp)                  // pointer to property
{
    HRESULT    hr        = NOERROR;
    HRESULT    hrT       = NOERROR;
    ULONG      cValues   = 1;
    SPropValue PropValue = {0};

    DEBUGPUBLIC("HrMAPISetPropBinary()\n");

    hr = CHK_HrMAPISetPropBinary(
        lpObj,
        ulPropTag,
        cbProp,
        lpvProp);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize SPropValue structure
    memset(&PropValue, 0, sizeof(PropValue));

    PropValue.ulPropTag     = ulPropTag;
    PropValue.Value.bin.cb  = cbProp;
    PropValue.Value.bin.lpb = (LPBYTE)lpvProp;

    hrT = MAPICALL(lpObj)->SetProps(
        lpObj,
        cValues,
        &PropValue,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIGetPropBoolean--------------------------------------------------------
//  Get a boolean property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropBoolean(            // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    OUT BOOL *lpfProp)                  // pointer to property variable
{
    HRESULT      hr          = NOERROR;
    HRESULT      hrT         = NOERROR;
    ULONG        cValues     = 0;
    LPSPropValue lpPropValue = NULL;
    SCODE        sc          = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            0
        }
    };

    DEBUGPUBLIC("HrMAPIGetPropBoolean()\n");

    hr = CHK_HrMAPIGetPropBoolean(
        lpObj,
        ulPropTag,
        lpfProp);

    if(FAILED(hr))
        RETURN(hr);

    *lpfProp = FALSE;

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulPropTag;

    hrT = MAPICALL(lpObj)->GetProps(
         lpObj,
         (LPSPropTagArray)&rgPropTag,
         fMapiUnicode,
         &cValues,
         &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    *lpfProp = (BOOL)(lpPropValue->Value.b);

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPISetPropBoolean--------------------------------------------------------
//  Set a boolean property.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropBoolean(            // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN BOOL fProp)                      // property
{
    HRESULT    hr        = NOERROR;
    HRESULT    hrT       = NOERROR;
    ULONG      cValues   = 1;
    SPropValue PropValue = {0};

    DEBUGPUBLIC("HrMAPISetPropBoolean()\n");

    hr = CHK_HrMAPISetPropBoolean(
        lpObj,
        ulPropTag,
        fProp);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize SPropValue structure
    ZeroMemory(&PropValue, sizeof(PropValue));

    PropValue.ulPropTag = ulPropTag;
    PropValue.Value.b   = fProp;

    hrT = MAPICALL(lpObj)->SetProps(
        lpObj,
        cValues,
        &PropValue,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIGetPropLong-----------------------------------------------------------
//  Get a long property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropLong(               // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    OUT ULONG *lpulProp)                // pointer to property variable
{
    HRESULT      hr          = NOERROR;
    HRESULT      hrT         = NOERROR;
    ULONG        cValues     = 0;
    LPSPropValue lpPropValue = NULL;
    SCODE        sc          = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            0
        }
    };

    DEBUGPUBLIC("HrMAPIGetPropLong()\n");

    hr = CHK_HrMAPIGetPropLong(
        lpObj,
        ulPropTag,
        lpulProp);

    if(FAILED(hr))
        RETURN(hr);

    *lpulProp = 0;

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulPropTag;

    hrT = MAPICALL(lpObj)->GetProps(
         lpObj,
         (LPSPropTagArray)&rgPropTag,
         fMapiUnicode,
         &cValues,
         &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    *lpulProp = lpPropValue->Value.ul;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPISetPropLong-----------------------------------------------------------
//  Set a long property.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropLong(               // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN ULONG ulProp)                    // property
{
    HRESULT    hr        = NOERROR;
    HRESULT    hrT       = NOERROR;
    ULONG      cValues   = 1;
    SPropValue PropValue = {0};

    DEBUGPUBLIC("HrMAPISetPropLong()\n");

    hr = CHK_HrMAPISetPropLong(
        lpObj,
        ulPropTag,
        ulProp);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize SPropValue structure
    ZeroMemory(&PropValue, sizeof(PropValue));

    PropValue.ulPropTag = ulPropTag;
    PropValue.Value.ul  = ulProp;

    hrT = MAPICALL(lpObj)->SetProps(
        lpObj,
        cValues,
        &PropValue,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIGetPropSystime--------------------------------------------------------
//  Get a systime property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropSystime(            // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    OUT LPFILETIME lpSystime)           // pointer to property variable
{
    HRESULT      hr          = NOERROR;
    HRESULT      hrT         = NOERROR;
    ULONG        cValues     = 0;
    LPSPropValue lpPropValue = NULL;
    SCODE        sc          = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            0
        }
    };

    DEBUGPUBLIC("HrMAPIGetPropSystime()\n");

    hr = CHK_HrMAPIGetPropSystime(
        lpObj,
        ulPropTag,
        lpSystime);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize FILETIME structure
    ZeroMemory(lpSystime, sizeof(FILETIME));

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulPropTag;

    hrT = MAPICALL(lpObj)->GetProps(
         lpObj,
         (LPSPropTagArray)&rgPropTag,
         fMapiUnicode,
         &cValues,
         &lpPropValue);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        if((lpPropValue != NULL) && (lpPropValue->Value.ul == MAPI_E_NOT_FOUND))
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpPropValue = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    lpSystime->dwLowDateTime = lpPropValue->Value.ft.dwLowDateTime;
    lpSystime->dwHighDateTime = lpPropValue->Value.ft.dwHighDateTime;

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--HrMAPISetPropSystime--------------------------------------------------------
//  Set a systime property.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropSystime(            // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN LPFILETIME lpSystime)            // pointer to property
{
    HRESULT    hr        = NOERROR;
    HRESULT    hrT       = NOERROR;
    ULONG      cValues   = 1;
    SPropValue PropValue = {0};

    DEBUGPUBLIC("HrMAPISetPropSystime()\n");

    hr = CHK_HrMAPISetPropSystime(
        lpObj,
        ulPropTag,
        lpSystime);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize SPropValue structure
    ZeroMemory(&PropValue, sizeof(PropValue));

    PropValue.ulPropTag                 = ulPropTag;
    PropValue.Value.ft.dwLowDateTime    = lpSystime->dwLowDateTime;
    PropValue.Value.ft.dwHighDateTime   = lpSystime->dwHighDateTime;

    hrT = MAPICALL(lpObj)->SetProps(
        lpObj,
        cValues,
        &PropValue,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrMAPIGetPropToFile---------------------------------------------------------
//  Get a property and put in a given file.
// -----------------------------------------------------------------------------
HRESULT HrMAPIGetPropToFile(             // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN LPSTR lpszFilename,             // pointer to destination file name
    OUT ULONG *lpcbProp)                // pointer to count of bytes address
                                        // variable
{
    HRESULT  hr             = NOERROR;
    HRESULT  hrT            = NOERROR;
    SCODE    sc             = 0;
    LPSTREAM lpStream       = NULL;
    HANDLE   hFile          = NULL;
    ULONG    ulBytesRead    = 0;
    LPBYTE   lpbBlock       = NULL;
    DWORD    dwBytesWritten = 0;

    DEBUGPUBLIC("HrMAPIGetPropToFile()\n");

    hr = CHK_HrMAPIGetPropToFile(
        lpObj,
        ulPropTag,
        lpszFilename,
        lpcbProp);

    if(FAILED(hr))
        RETURN(hr);

    // Open a stream on the property
    hrT = MAPICALL(lpObj)->OpenProperty(
        lpObj,
        ulPropTag,
        (LPIID)&IID_IStream,
        STGM_READ,
        MAPI_DEFERRED_ERRORS,
        (LPUNKNOWN *)&lpStream);

    if(FAILED(hrT))
    {
        // Streams are not supported by provider
        if((hrT == MAPI_E_NO_SUPPORT) || (hrT == MAPI_E_INTERFACE_NOT_SUPPORTED))
        {
            ULONG PropType = 0;

            lpStream = NULL;

            MODULE_WARNING1("Streams are not supported by provider [%08lx]",hrT);

            PropType = PROP_TYPE(ulPropTag);

            // Read property into memory
            switch(PropType)
            {
            case PT_BINARY:
                hr = HrMAPIGetPropBinary(
                    lpObj,
                    ulPropTag,
                    &ulBytesRead,
                    (void **)&lpbBlock);
                break;
            default:
                hr = HrMAPIGetPropString(
                    lpObj,
                    ulPropTag,
                    &ulBytesRead,
                    (void **)&lpbBlock);
            }
        }
        else
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    hFile = CreateFile(
        lpszFilename,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
        NULL);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //  Copy propery value to the file
    if(lpStream != NULL)
    {
        sc = MAPIAllocateBuffer(EDK_CBTRANSFER, (void **)&lpbBlock);

        // An error occured allocating the block buffer
        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        for (;;)
        {
            // Read a block from the stream
            hrT = OLECALL(lpStream)->Read(
                lpStream,
                lpbBlock,
                EDK_CBTRANSFER,
                &ulBytesRead);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if(ulBytesRead == 0L)
                break;

            // Write the block to the file
            hr = _HrWriteFile(hFile, ulBytesRead, lpbBlock);

            if(FAILED(hr))
            {
                goto cleanup;
            }
        }
    }
    else
    {
        // Write the block to the file
        hr = _HrWriteFile(hFile, ulBytesRead, lpbBlock);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

cleanup:

    // Close the file
    if(hFile != NULL)
    {
        if(CloseHandle(hFile) == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    // Release the stream
    ULOLERELEASE(lpStream);

    MAPIFREEBUFFER(lpbBlock);

    RETURN(hr);
}

//$--HrMAPISetPropFromFile-------------------------------------------------------
//  Set a property from a given file.
// -----------------------------------------------------------------------------
HRESULT HrMAPISetPropFromFile(           // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN LPSTR lpszFilename,             // pointer to source file name
    OUT ULONG *lpcbProp)                // pointer to count of bytes address
                                        // variable
{
    HRESULT        hr             = NOERROR;
    HRESULT        hrT            = NOERROR;
    SCODE          sc             = 0;
    LPSTREAM       lpStream       = NULL;
    HFILE          hFile          = HFILE_ERROR;
    OFSTRUCT       ofStruct       = {0};
    DWORD          dwBytesRead    = 0;
    LPBYTE         lpbBlock       = NULL;
    ULONG          ulBytesWritten = 0;
    ULARGE_INTEGER ll             = {0,0};
    ULONG          ulFileSize     = 0;
    BYTE           bLastByte      = 0xFF;
    ULONG          cbProp         = 0;

    DEBUGPUBLIC("HrMAPISetPropFromFile()\n");

    hr = CHK_HrMAPISetPropFromFile(
        lpObj,
        ulPropTag,
        lpszFilename,
        lpcbProp);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbProp = 0;

    // Open a stream on the property
    hrT = MAPICALL(lpObj)->OpenProperty(
        lpObj,
        ulPropTag,
        (LPIID)&IID_IStream,
        STGM_DIRECT | STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
        MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
        (LPUNKNOWN *)&lpStream);

    if(FAILED(hrT))
    {
        // Streams are not supported by provider
        if((hrT == MAPI_E_NO_SUPPORT) || (hrT == MAPI_E_INTERFACE_NOT_SUPPORTED))
        {
            lpStream = NULL;
        }
        else
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hFile = OpenFile(
        lpszFilename,
        &ofStruct,
        OF_READ);

    if(hFile == HFILE_ERROR)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get file size
    if((ulFileSize = GetFileSize((HANDLE)hFile, NULL)) == (DWORD)HFILE_ERROR)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(PROP_TYPE(ulPropTag) == PT_UNICODE)
    {
        if((ulFileSize % sizeof(wchar_t)) != 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    cbProp = ulFileSize;

    //  Copy propery value to the file
    if(lpStream != NULL)
    {
        // Allocate memory for the block buffer
        sc = MAPIAllocateBuffer(EDK_CBTRANSFER, (void **)&lpbBlock);

        // An error occured allocating the block buffer
        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        ll.LowPart  = ulFileSize;
        ll.HighPart = 0L;

        hrT = OLECALL(lpStream)->SetSize(lpStream, ll);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        for (;;)
        {
            BOOL    fStatus;

            // Read a block from the file
            fStatus = ReadFile(
                (HANDLE)hFile,
                lpbBlock,
                EDK_CBTRANSFER,
                &dwBytesRead,
                NULL);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if(dwBytesRead == 0L)
                break;

            bLastByte = lpbBlock[dwBytesRead - 1L];

            // Write a block to the stream
            hrT = OLECALL(lpStream)->Write(
                lpStream,
                lpbBlock,
                dwBytesRead,
                &ulBytesWritten);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if(ulBytesWritten < dwBytesRead)
            {
                hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
                goto cleanup;
            }
        }

        if((PROP_TYPE(ulPropTag) == PT_STRING8) ||
           (PROP_TYPE(ulPropTag) == PT_UNICODE))
        {
            // NULL terminate if not already
            if(bLastByte != 0)
            {
                // Initialize with enough zeroes for a NULL character
                ZeroMemory(lpbBlock, sizeof(wchar_t));

                if(PROP_TYPE(ulPropTag) == PT_UNICODE)
                {
                    dwBytesRead = sizeof(wchar_t);
                }
                else
                {
                    dwBytesRead = 1L;
                }

                ulBytesWritten = 0L;

                // Write a block to the stream
                hrT = OLECALL(lpStream)->Write(
                    lpStream,
                    lpbBlock,
                    dwBytesRead,
                    &ulBytesWritten);

                if(FAILED(hrT))
                {
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
                }

                if(ulBytesWritten < dwBytesRead)
                {
                    hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
                    goto cleanup;
                }

                cbProp += ulBytesWritten;
            }
        }
    }
    else
    {
        BOOL  fStatus  = FALSE;
        ULONG PropType = 0;

        // Allocate the memory for the property value
        sc = MAPIAllocateBuffer(
            ulFileSize + 2 * sizeof(wchar_t),
            (void **)&lpbBlock);

        // An error occured allocating the block buffer
        if(FAILED(sc))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Read the property value into memory
        fStatus = ReadFile(
            (HANDLE)hFile,
            lpbBlock,
            ulFileSize,
            &dwBytesRead,
            NULL);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Check if the entire file was read
        if(dwBytesRead != ulFileSize)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if((PROP_TYPE(ulPropTag) == PT_STRING8) ||
           (PROP_TYPE(ulPropTag) == PT_UNICODE))
        {
            // NULL terminate if not already
            bLastByte = lpbBlock[dwBytesRead - 1L];

            if(bLastByte != 0)
            {
                if(PROP_TYPE(ulPropTag) == PT_UNICODE)
                {
                    ((wchar_t *)lpbBlock)[dwBytesRead/sizeof(wchar_t)] = '\0';
                    ulFileSize += sizeof(wchar_t);
                }
                else
                {
                    lpbBlock[dwBytesRead] = 0;
                    ulFileSize++;
                }

                cbProp = ulFileSize;
            }
        }

        PropType = PROP_TYPE(ulPropTag);

        // Set property
        switch(PropType)
        {
        case PT_BINARY:
            hr = HrMAPISetPropBinary(
                lpObj,
                ulPropTag,
                ulFileSize,
                &lpbBlock);
            break;
        default:
            hr = HrMAPISetPropString(
                lpObj,
                ulPropTag,
                &lpbBlock);
        }
    }

cleanup:

    // Close the file
    if(hFile != HFILE_ERROR)
    {
        if(CloseHandle((HANDLE)hFile) == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    // Release the stream
    ULOLERELEASE(lpStream);

    MAPIFREEBUFFER(lpbBlock);

    if(SUCCEEDED(hr))
    {
        *lpcbProp = cbProp;
    }

    RETURN(hr);
}

//$--HrMAPIOpenStreamOnProperty-------------------------------------------------
//  Open a stream on a given property.
// -----------------------------------------------------------------------------
HRESULT HrMAPIOpenStreamOnProperty(      // RETURNS: return code
    IN LPMAPIPROP lpObj,                // pointer to object
    IN ULONG ulPropTag,                 // property tag
    IN ULONG ulFlags,                   // flags (MAPI_CREATE and/or MAPI_MODIFY)
    OUT LPSTREAM *lppStream)            // pointer to stream address variable
{
    HRESULT  hr          = NOERROR;
    HRESULT  hrT         = NOERROR;
    LPSTREAM lpStream    = NULL;
    ULONG    ulStgmFlags = STGM_READ;

    DEBUGPUBLIC("HrMAPIOpenStreamOnProperty()\n");

    hr = CHK_HrMAPIOpenStreamOnProperty(
        lpObj,
        ulPropTag,
        ulFlags,
        lppStream);

    if(FAILED(hr))
        RETURN(hr);

    *lppStream = NULL;

    if(ulFlags & MAPI_CREATE)
    {
        ulStgmFlags |= STGM_CREATE;
    }

    if(ulFlags & MAPI_MODIFY)
    {
        ulStgmFlags |= STGM_WRITE;
    }

    hrT = MAPICALL(lpObj)->OpenProperty(
        lpObj,
        ulPropTag,
        (LPIID)&IID_IStream,
        STGM_DIRECT | STGM_SHARE_EXCLUSIVE | ulStgmFlags,
        MAPI_DEFERRED_ERRORS | ulFlags,
        (LPUNKNOWN *)&lpStream);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lppStream = lpStream;

cleanup:

    RETURN(hr);
}

//$--HrMAPIAppendSPropValues-----------------------------------------------------
//  Append one set of SPropValue's to another.
// -----------------------------------------------------------------------------
HRESULT HrMAPIAppendSPropValues(         // RETURNS: return code
    IN ULONG cHeadProps,                // count of property values in head
    IN LPSPropValue lpHeadProps,        // pointer to property values in
                                        // head
    IN ULONG cTailProps,                // count of property values in tail
    IN LPSPropValue lpTailProps,        // pointer to property values in
                                        // tail
    OUT ULONG *lpcNewProps,             // pointer to count of property
                                        // values
    OUT LPSPropValue *lppNewProps)      // pointer to property values
{
    HRESULT         hr                  = NOERROR;
    SCODE           sc                  = 0;
    ULONG           cNewProps           = 0;
    LPSPropValue    lpTmpProps          = NULL;
    LPSPropValue    lpNewProps          = NULL;
    ULONG           cBytes              = 0;
    ULONG           i                   = 0;
    ULONG           j                   = 0;

    DEBUGPUBLIC("HrMAPIAppendSPropValues()\n");

    hr = CHK_HrMAPIAppendSPropValues(
        cHeadProps,
        lpHeadProps,
        cTailProps,
        lpTailProps,
        lpcNewProps,
        lppNewProps);

    if(FAILED(hr))
        RETURN(hr);

    *lpcNewProps = 0;
    *lppNewProps = NULL;

    cNewProps = cHeadProps + cTailProps;

    cBytes = CbSPropValue(cNewProps);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpTmpProps);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Copy existing property values
    for(i = 0; i < cHeadProps; i++)
    {
        lpTmpProps[i] = lpHeadProps[i];
    }

    for(i = cHeadProps, j = 0; i < cNewProps; i++, j++)
    {
        lpTmpProps[i] = lpTailProps[j];
    }

    sc = ScDupPropset(
        cNewProps,
        lpTmpProps,
MAPIAllocateBuffer,
&lpNewProps);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lpcNewProps = cNewProps;
    *lppNewProps = lpNewProps;

cleanup:

    MAPIFREEBUFFER(lpTmpProps);

    RETURN(hr);
}

//$--HrMAPIMoveOneProp-----------------------------------------------------------
//  Move one property from a source object to a destination object.
// -----------------------------------------------------------------------------
HRESULT HrMAPIMoveOneProp(               // RETURNS: return code
    IN LPMAPIPROP lpSrcObj,             // pointer to source object
    IN ULONG ulSrcPropTag,              // source property tag
    IN ULONG ulDstPropTag,              // destination property tag
    IN BOOL IsMust,                     // TRUE if a required property
    IN BOOL IsReplace,                  // TRUE if existing destination
                                        // property can be replaced
    IN OUT LPMAPIPROP lpDstObj)         // pointer to destination object
{
    HRESULT         hr                  = NOERROR;
    HRESULT         hrT                 = NOERROR;
    SCODE           sc                  = 0;
    ULONG           cProps              = 0;
    LPSPropValue    lpProps             = NULL;
    SizedSPropTagArray(1, rgPropTag)    = { 1, 0 };

    DEBUGPUBLIC("HrMAPIMoveOneProp()\n");

    hr = CHK_HrMAPIMoveOneProp(
        lpSrcObj,
        ulSrcPropTag,
        ulDstPropTag,
        IsMust,
        IsReplace,
        lpDstObj);

    if(FAILED(hr))
        RETURN(hr);

    if(PROP_TYPE(ulSrcPropTag) != PROP_TYPE(ulDstPropTag))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if((IsReplace == FALSE) && FPropExists(lpDstObj, ulDstPropTag))
    {
        MODULE_WARNING("Destination property exists and not overwritten.");

        goto cleanup;
    }

    rgPropTag.cValues = 1;
    rgPropTag.aulPropTag[0] = ulSrcPropTag;

    hrT = MAPICALL(lpSrcObj)->GetProps(
        lpSrcObj,
        (LPSPropTagArray)&rgPropTag,
        fMapiUnicode,
        &cProps,
        &lpProps);

    if(hrT == MAPI_W_ERRORS_RETURNED)
    {
        hrT = lpProps->Value.ul;

        if(hrT != MAPI_E_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
        }
        else if(IsMust == TRUE)
        {
            hr = HR_LOG(MAPI_E_NOT_FOUND);
        }

        goto cleanup;
    }

    if(FAILED(hrT))
    {
        lpProps = NULL;

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cProps != 0, "ZERO cProps variable");

    ASSERTERROR(lpProps != NULL, "NULL lpProps variable");

    lpProps->ulPropTag = ulDstPropTag;

    hrT = MAPICALL(lpDstObj)->SetProps(
        lpDstObj,
        cProps,
        lpProps,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpProps);

    RETURN(hr);
}


ISTORE.C

// --istore.c-------------------------------------------------------------------
//
//  Module containing MAPI utility functions for message stores.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "istore.chk"

// $--HrMAPIFindStore@----------------------------------------------------------
//
// DESCRIPTION:Find an MDB store based on a string.
//
// INPUT:
//
//  [lpSession]-- MAPI session ptr.
//  [lpszStore]-- Store name (PR_DISPLAY_NAME).
//
// OUTPUT:
//
//  [lpcbentryid]-- Number of bytes in store's entry identifier.
//  [lppentryid]-- Ptr to store entry identifier.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif not enough memory;
//EDK_E_NOT_FOUNDif specified store not found;
//              E_FAILotherwise.
//
//---------------------------------------------------------------------------
HRESULT HrMAPIFindStoreW(
INLPMAPISESSIONlpSession,  // MAPI session ptr
INLPCWSTRlpszStore,  // store name
OUTULONG FAR *lpcbentryid,    // ptr to # byte in entry ID
OUTLPENTRYID FAR *lppentryid)// entry ID buffer ptr
{
    HRESULThr           = NOERROR;
    LPSTR           lpszStoreA   = NULL;

DEBUGPUBLIC( "HrMAPIFindStoreW()");

    hr = CHK_HrMAPIFindStoreW( lpSession, lpszStore, lpcbentryid, lppentryid);
    if (FAILED(hr))
        RETURN(hr);

// Because MAPI doesn't officially support UNICODE strings, we are forced
    // to do a string conversion and call the other function...
hr = HrStrWToStrA( lpszStore, &lpszStoreA);
    if( FAILED( hr))
        goto cleanup;

    hr = HrMAPIFindStoreA( lpSession, lpszStoreA, lpcbentryid, lppentryid);

cleanup:
MAPIFREEBUFFER( lpszStoreA);

    RETURN(hr);
}

HRESULT HrMAPIFindStoreA(
INLPMAPISESSIONlpSession,  // MAPI session ptr
INLPCSTRlpszStore,  // store name
OUTULONG FAR *lpcbentryid,    // ptr to # byte in entry ID
OUTLPENTRYID FAR *lppentryid)// entry ID buffer ptr
{
    HRESULThr           = NOERROR;
LPMAPITABLElpTable      = NULL;
LPSRowSetlpRow        = NULL;
    LPSPropValue    lpProp       = NULL;

    static SizedSPropTagArray( 1, EntryID) = { 1, { PR_ENTRYID}};
    static SPropValue RestrictProp = { PR_DISPLAY_NAME_A, 0L, { 0}};
    static SRestriction Restriction = { RES_PROPERTY, { RELOP_EQ, PR_DISPLAY_NAME_A, (ULONG)&RestrictProp}};

DEBUGPUBLIC( "HrMAPIFindStoreA()");

    hr = CHK_HrMAPIFindStoreA( lpSession, lpszStore, lpcbentryid, lppentryid);
    if (FAILED(hr))
        RETURN(hr);

    // Set name of the store name to find.
    RestrictProp.Value.lpszA = (LPSTR)lpszStore;

hr = MAPICALL( lpSession)->GetMsgStoresTable( lpSession,
        0L, &lpTable);
if (FAILED(hr))
    {
        hr = HR_LOG( E_FAIL);
goto cleanup;
    }

    hr = HrQueryAllRows(
        lpTable, (LPSPropTagArray)&EntryID,
        &Restriction, NULL, 0, &lpRow);
if( FAILED( hr))
    {
        if( hr == MAPI_E_NOT_FOUND)
            hr = HR_LOG( EDK_E_NOT_FOUND);
        else
            hr = HR_LOG( E_FAIL);

        goto cleanup;
    }

    if (lpRow == NULL || lpRow->cRows != 1)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    lpProp = &lpRow->aRow[0].lpProps[0];

hr = MAPIAllocateBuffer(lpProp->Value.bin.cb, (LPVOID FAR *)lppentryid);
if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

memcpy(*lppentryid, lpProp->Value.bin.lpb, lpProp->Value.bin.cb);
*lpcbentryid = lpProp->Value.bin.cb;

cleanup:
FREEPROWS(lpRow);
ULRELEASE(lpTable);

    RETURN(hr);
}

//$--HrMAPIFindDefaultMsgStore----------------------------------------------------
//  Get the entry ID of the default message store.
// -----------------------------------------------------------------------------
HRESULT HrMAPIFindDefaultMsgStore(    // RETURNS: return code
    IN LPMAPISESSION lplhSession,   // session pointer
    OUT ULONG *lpcbeid,             // count of bytes in entry ID
    OUT LPENTRYID *lppeid)          // entry ID of default store
{
    HRESULT     hr      = NOERROR;
    HRESULT     hrT     = NOERROR;
    SCODE       sc      = 0;
    LPMAPITABLE lpTable = NULL;
    LPSRowSet   lpRows  = NULL;
    LPENTRYID   lpeid   = NULL;
    ULONG       cbeid   = 0;
    ULONG       cRows   = 0;
    ULONG       i       = 0;

    SizedSPropTagArray(2, rgPropTagArray) =
    {
        2,
        {
            PR_DEFAULT_STORE,
            PR_ENTRYID
        }
    };

    DEBUGPUBLIC("HrMAPIFindDefaultMsgStore()\n");

    hr = CHK_HrMAPIFindDefaultMsgStore(
        lplhSession,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    // Get the list of available message stores from MAPI

    hrT = MAPICALL(lplhSession)->GetMsgStoresTable(lplhSession, 0, &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the row count for the message recipient table
    hrT = MAPICALL(lpTable)->GetRowCount(lpTable, 0, &cRows);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Set the columns to return
    hrT = MAPICALL(lpTable)->SetColumns(lpTable, (LPSPropTagArray)&rgPropTagArray, 0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Go to the beginning of the recipient table for the envelope
    hrT = MAPICALL(lpTable)->SeekRow(lpTable, BOOKMARK_BEGINNING, 0, NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Read all the rows of the table
    hrT = MAPICALL(lpTable)->QueryRows(lpTable, cRows, 0, &lpRows);

    if(SUCCEEDED(hrT) && (lpRows != NULL) && (lpRows->cRows == 0))
    {
        FREEPROWS(lpRows);

        hrT = EDK_E_NOT_FOUND;
    }

    if(FAILED(hrT) || (lpRows == NULL))
    {
        if(hrT != EDK_E_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            hr = HR_LOG(EDK_E_NOT_FOUND);
        }

        goto cleanup;
    }

    for(i = 0; i < cRows; i++)
    {
        if(lpRows->aRow[i].lpProps[0].Value.b == TRUE)
        {
            cbeid = lpRows->aRow[i].lpProps[1].Value.bin.cb;

            sc = MAPIAllocateBuffer(cbeid, (void **)&lpeid);

            if(FAILED(sc))
            {
                cbeid = 0;
                lpeid = NULL;

                hr = HR_LOG(E_OUTOFMEMORY);
                goto cleanup;
            }

            // Copy entry ID of message store
            CopyMemory(
                lpeid,
                lpRows->aRow[i].lpProps[1].Value.bin.lpb,
                cbeid);

            break;
        }
    }

    if(lpeid == NULL)
    {
        hr = HR_LOG(E_FAIL);
    }

    ASSERTERROR(cbeid != 0, "ZERO cbeid variable");

    ASSERT_READ_PTR(lpeid, cbeid, "INVALID lpeid variable");

cleanup:

    if(lpRows != NULL)
    {
        FreeProws(lpRows);
    }

    ULRELEASE(lpTable);

    *lpcbeid = cbeid;
    *lppeid = lpeid;

    RETURN(hr);
}

//$--FIsPublicStore------------------------------------------------------------
//  Returns TRUE if the MDB is a public store.
// -----------------------------------------------------------------------------
BOOL FIsPublicStore(
    IN LPMDB lpmdb)                     // pointer to message store

{
    BOOL          bIsPublic   = FALSE;
    HRESULT       hrT         = NOERROR;
    ULONG         cValues     = 0;
    LPSPropValue  lpPropValue = NULL;
    ULONG         cbeid       = 0;

    static SPropTagArray rgPropTag = { 1, { PR_MDB_PROVIDER } };

    DEBUGPUBLIC( "FIsPublicStore()");

    hrT = CHK_FIsPublicStore( lpmdb);
    if(FAILED(hrT))
        return(FALSE);

    // Get the property.
    hrT = MAPICALL(lpmdb)->GetProps( lpmdb,
        &rgPropTag,
        fMapiUnicode,
        &cValues,
        &lpPropValue);

    if(FAILED(hrT) || hrT == MAPI_W_ERRORS_RETURNED)
    {
        HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(cValues != 0, "ZERO cValues variable");

    ASSERTERROR(lpPropValue != NULL, "NULL lpPropValue variable");

    // Check to make sure we got the right property.
    if (lpPropValue->ulPropTag != PR_MDB_PROVIDER)
    {
        HR_LOG(E_FAIL);
        goto cleanup;
    }

    // See if PR_MDB_PROVIDER == pbExchangeProviderPublicGuid

    if ( (lpPropValue->Value.bin.cb == sizeof(GUID)) &&
         (memcmp(lpPropValue->Value.bin.lpb,
            pbExchangeProviderPublicGuid, sizeof(GUID)) == 0) )
    {
         bIsPublic = TRUE;
    }

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    return(bIsPublic);
}

//$--_HrOpenStoreFromGuid-------------------------------------------------------
//  Locates the store provider based on GUID and returns open interface pointer
//------------------------------------------------------------------------------
HRESULT _HrOpenStoreFromGuid (
    IN  LPMAPISESSION lphSession,
    IN  LPGUID        pbStoreProviderGuid,
    OUT LPMDB        *lppMDB)
{
    HRESULT       hr         = NOERROR;
    HRESULT       hrT        = NOERROR;
    LPMAPITABLE   lpTable    = NULL;
    LPSRowSet     lpRows     = NULL;

    static SPropTagArray aptEntryID = { 1, { PR_ENTRYID } };
    static SPropValue    SProp = { PR_MDB_PROVIDER, 0L, { 0}};
    static SRestriction  SRestrict = { RES_PROPERTY,
        { RELOP_EQ, PR_MDB_PROVIDER, (LONG)&SProp } };

    DEBUGPRIVATE("_HrOpenStoreFromGuid()");

    hr = CHK_HrOpenStoreFromGuid( lphSession, pbStoreProviderGuid, lppMDB);
    if(FAILED(hr))
        RETURN(hr);

    // Clear return value

    *lppMDB = NULL;

    // Get table of message stores

    hrT = MAPICALL(lphSession)->GetMsgStoresTable(lphSession, 0, &lpTable);
    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Restrict the table to the appropriate Exchange store
    SProp.Value.bin.cb  = sizeof(GUID);
    SProp.Value.bin.lpb = (LPBYTE)pbStoreProviderGuid;

    hrT = HrQueryAllRows( lpTable, &aptEntryID, &SRestrict, NULL, 0L, &lpRows);
    if(FAILED(hrT) || (lpRows->cRows == 0))
    {
        if((hrT == MAPI_E_NOT_FOUND) ||
           ((hrT == SUCCESS_SUCCESS) && (lpRows->cRows == 0)))
        {
            hr = HR_LOG(EDK_E_NOT_FOUND);
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }

        goto cleanup;
    }

    if((lpRows->aRow->cValues != 1) ||
        (lpRows->aRow->lpProps[0].ulPropTag != PR_ENTRYID))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open the message store

    hrT = MAPICALL(lphSession)->OpenMsgStore(
        lphSession,
        (ULONG)0,
        lpRows->aRow->lpProps[0].Value.bin.cb,
        (LPENTRYID)lpRows->aRow->lpProps[0].Value.bin.lpb,
        NULL,
        MAPI_BEST_ACCESS,
        lppMDB);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    FREEPROWS(lpRows);

    ULRELEASE(lpTable);

    RETURN(hr);
}


ITRACE.C

// --itrace.c-------------------------------------------------------------------
//
//  Module containing MAPI utility functions for message traces.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "itrace.chk"

//$--HrTraceGetEntryListSize--------------------------------------------------
//  Get the size of the hop trace information in bytes.
// -----------------------------------------------------------------------------
HRESULT HrTraceGetEntryListSize(      // RETURNS: return code
    IN LPTRACEINFO lpTraceInfo,         // Pointer to hop trace address variable
    OUT ULONG      *lpcbTraceInfo)      // Count of bytes in hop trace list
{
    HRESULT hr         = NOERROR;
    LPBYTE  lpbFirst   = NULL;
    LPBYTE  lpbLast    = NULL;
    ULONG   cBytes     = 0;
    ULONG   cbExpected = 0;
    ULONG   cEntries   = 0;

    DEBUGPUBLIC("HrTraceGetEntryListSize()");

    hr = CHK_HrTraceGetEntryListSize(
        lpTraceInfo,
        lpcbTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbTraceInfo = 0;

    lpbFirst = (LPBYTE)lpTraceInfo;

    cEntries = lpTraceInfo->cEntries;

    lpbLast  = (LPBYTE)&(lpTraceInfo->rgtraceentry[cEntries]) - 1;

    cBytes = ((lpbFirst <= lpbLast) ? (lpbLast-lpbFirst) : (lpbFirst-lpbLast));

    cBytes++;

    cbExpected = CbTRACEINFO(lpTraceInfo);

    if(cBytes != cbExpected)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lpcbTraceInfo = cBytes;

cleanup:

    RETURN(hr);
}

//$--HrTraceGetEntryList-----------------------------------------------------
//  Get the hop trace information for a given message.
// -----------------------------------------------------------------------------
HRESULT HrTraceGetEntryList(         // RETURNS: return code
    IN LPMESSAGE   lpMessage,           // Pointer to message.
    OUT LPTRACEINFO *lppTraceInfo)      // Pointer to hop trace address variable
{
    HRESULT hr         = NOERROR;
    ULONG   cBytes     = 0;
    ULONG   cbExpected = 0;

    DEBUGPUBLIC("HrTraceGetEntryList()");

    hr = CHK_HrTraceGetEntryList(
        lpMessage,
        lppTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    *lppTraceInfo = NULL;

    hr = HrMAPIGetPropBinary(
        (LPMAPIPROP)lpMessage,
        PR_TRACE_INFO,
        &cBytes,
        (LPVOID *)lppTraceInfo);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR((*lppTraceInfo)->cEntries != 0, "ZERO cEntries variable");

    cbExpected = CbTRACEINFO((*lppTraceInfo));

    if(cBytes != cbExpected)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrTraceSetEntryList-----------------------------------------------------
//  Set the hop trace information for a given message.
// -----------------------------------------------------------------------------
HRESULT HrTraceSetEntryList(         // RETURNS: return code
    IN LPMESSAGE   lpMessage,           // Pointer to message.
    IN LPTRACEINFO lpTraceInfo)         // Pointer to hop trace address variable
{
    HRESULT hr         = NOERROR;
    ULONG   cBytes     = 0;
    ULONG   cbExpected = 0;

    DEBUGPUBLIC("HrTraceSetEntryList()");

    hr = CHK_HrTraceSetEntryList(
        lpMessage,
        lpTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrTraceGetEntryListSize(lpTraceInfo, &cBytes);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbExpected = CbTRACEINFO(lpTraceInfo);

    if(cBytes != cbExpected)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropBinary(
        (LPMAPIPROP)lpMessage,
        PR_TRACE_INFO,
        cBytes,
        (LPVOID *)lpTraceInfo);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrTraceCopyEntry--------------------------------------------------------
//  Copy trace entry information to a trace entry structure.
// -----------------------------------------------------------------------------
HRESULT HrTraceCopyEntry(            // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    OUT LPTRACEENTRY lpTraceEntry)      // Pointer to trace entry address
                                        // variable.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrTraceCopyEntry()");

    hr = CHK_HrTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpTraceEntry);

    if(FAILED(hr))
        RETURN(hr);

    lpTraceEntry->lAction = lAction;

    lpTraceEntry->ftArrivalTime = ftArrivalTime;

    lpTraceEntry->ftDeferredTime = ftDeferredTime;

    if((lpszADMDName != NULL) && (strlen(lpszADMDName) > 0))
    {
        strncpy(lpTraceEntry->rgchADMDName, lpszADMDName, MAX_ADMD_NAME_SIZ);
        lpTraceEntry->rgchADMDName[MAX_ADMD_NAME_SIZ-1] = '\0';
    }

    if((lpszCountryName != NULL) && (strlen(lpszCountryName) > 0))
    {
        strncpy(lpTraceEntry->rgchCountryName, lpszCountryName, MAX_COUNTRY_NAME_SIZ);
        lpTraceEntry->rgchCountryName[MAX_COUNTRY_NAME_SIZ-1] = '\0';
    }

    if((lpszPRMDId != NULL) && (strlen(lpszPRMDId) > 0))
    {
        strncpy(lpTraceEntry->rgchPRMDId, lpszPRMDId, MAX_PRMD_NAME_SIZ);
        lpTraceEntry->rgchPRMDId[MAX_PRMD_NAME_SIZ-1] = '\0';
    }

    if((lpszAttADMDName != NULL) && (strlen(lpszAttADMDName) > 0))
    {
        strncpy(lpTraceEntry->rgchAttADMDName, lpszAttADMDName, MAX_ADMD_NAME_SIZ);
        lpTraceEntry->rgchAttADMDName[MAX_ADMD_NAME_SIZ-1] = '\0';
    }

    if((lpszAttCountryName != NULL) && (strlen(lpszAttCountryName) > 0))
    {
        strncpy(lpTraceEntry->rgchAttCountryName, lpszAttCountryName, MAX_COUNTRY_NAME_SIZ);
        lpTraceEntry->rgchAttCountryName[MAX_COUNTRY_NAME_SIZ-1] = '\0';
    }

    if((lpszAttPRMDId != NULL) && (strlen(lpszAttPRMDId) > 0))
    {
        strncpy(lpTraceEntry->rgchAttPRMDId, lpszAttPRMDId, MAX_PRMD_NAME_SIZ);
        lpTraceEntry->rgchAttPRMDId[MAX_PRMD_NAME_SIZ] = '\0';
    }

    RETURN(hr);
}

//$--HrTraceCreateEntryList--------------------------------------------------
//  Create a hop trace information list.
// -----------------------------------------------------------------------------
HRESULT HrTraceCreateEntryList(      // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    OUT LPTRACEINFO *lppTraceInfo)      // Pointer to hop trace address variable
{
    HRESULT     hr          = NOERROR;
    ULONG       cBytes      = 0;
    LPTRACEINFO lpTraceInfo = NULL;
    SCODE       sc          = 0;

    DEBUGPUBLIC("HrTraceCreateEntryList()");

    hr = CHK_HrTraceCreateEntryList(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lppTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    // Allocate a trace entry list

    cBytes = CbNewTRACEINFO(1);

    sc = MAPIAllocateBuffer(cBytes, (void **)lppTraceInfo);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize trace entry list
    ZeroMemory(*lppTraceInfo, cBytes);

    lpTraceInfo = *lppTraceInfo;

    lpTraceInfo->cEntries = 1;

    hr = HrTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        &(lpTraceInfo->rgtraceentry[0]));

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrTraceAppendEntryList--------------------------------------------------
//  Append to an existing hop trace information list.
// -----------------------------------------------------------------------------
HRESULT HrTraceAppendEntryList(      // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    IN OUT LPTRACEINFO *lppTraceInfo)   // Pointer to hop trace address variable
{
    HRESULT      hr             = NOERROR;
    ULONG        i              = 0;
    ULONG        cBytes         = 0;
    ULONG        cEntries       = 0;
    LPTRACEINFO  lpTraceInfo    = NULL;
    LPTRACEENTRY lpTraceEntry   = NULL;
    LPTRACEINFO  lpNewTraceInfo = NULL;
    SCODE        sc             = 0;

    DEBUGPUBLIC("HrTraceAppendEntryList()");

    hr = CHK_HrTraceAppendEntryList(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lppTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    // Allocate a trace entry list

    lpTraceInfo = *lppTraceInfo;

    cEntries = lpTraceInfo->cEntries + 1;

    cBytes = CbNewTRACEINFO(cEntries);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpNewTraceInfo);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize trace entry list
    ZeroMemory(lpNewTraceInfo, cBytes);

    lpNewTraceInfo->cEntries = cEntries;

    cEntries--;

    for(i = 0; i < cEntries; i++)
    {
        lpTraceEntry = &(lpTraceInfo->rgtraceentry[i]);

        hr = HrTraceCopyEntry(
            lpTraceEntry->lAction,
            lpTraceEntry->ftArrivalTime,
            lpTraceEntry->ftDeferredTime,
            lpTraceEntry->rgchADMDName,
            lpTraceEntry->rgchCountryName,
            lpTraceEntry->rgchPRMDId,
            lpTraceEntry->rgchAttADMDName,
            lpTraceEntry->rgchAttCountryName,
            lpTraceEntry->rgchAttPRMDId,
            &(lpNewTraceInfo->rgtraceentry[i]));

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = HrTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        &(lpNewTraceInfo->rgtraceentry[cEntries]));

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTraceInfo);

    *lppTraceInfo = lpNewTraceInfo;

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpNewTraceInfo);
    }

    RETURN(hr);
}

//$--HrTraceGotoEntry--------------------------------------------------------
//  Goto the specified TRACEENTRY in a TRACEINFO
// -----------------------------------------------------------------------------
HRESULT HrTraceGotoEntry(            // RETURNS: return code
    IN LPTRACEINFO lpTraceInfo,         // pointer to TRACEINFO
    IN ULONG ulIndex,                   // index of TRACEENTRY in TRACEINFO
    OUT LPTRACEENTRY *lppTraceEntry)    // pointer to TRACEENTRY
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrTraceGotoEntry()");

    hr = CHK_HrTraceGotoEntry(
        lpTraceInfo,
        ulIndex,
        lppTraceEntry);

    if(FAILED(hr))
        RETURN(hr);

    *lppTraceEntry = NULL;

    if(ulIndex < lpTraceInfo->cEntries)
    {
        *lppTraceEntry = &(lpTraceInfo->rgtraceentry[ulIndex]);
    }
    else
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
    }

    RETURN(hr);
}

//$--HrTraceGotoFirstEntry---------------------------------------------------
//  Goto the first TRACEENTRY in a TRACEINFO
// -----------------------------------------------------------------------------
HRESULT HrTraceGotoFirstEntry(       // RETURNS: return code
    IN LPTRACEINFO lpTraceInfo,         // pointer to TRACEINFO
    OUT ULONG *lpulIndex,               // index of TRACEENTRY in TRACEINFO
    OUT LPTRACEENTRY *lppTraceEntry)    // pointer to TRACEENTRY
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrTraceGotoFirstEntry()");

    hr = CHK_HrTraceGotoFirstEntry(
        lpTraceInfo,
        lpulIndex,
        lppTraceEntry);

    if(FAILED(hr))
        RETURN(hr);

    *lpulIndex = 0;

    hr = HrTraceGotoEntry(
        lpTraceInfo,
        0,
        lppTraceEntry);

    RETURN(hr);
}

//$--HrTraceGotoNextEntry----------------------------------------------------
//  Goto the next TRACEENTRY in a TRACEINFO
// -----------------------------------------------------------------------------
HRESULT HrTraceGotoNextEntry(        // RETURNS: return code
    IN LPTRACEINFO lpTraceInfo,         // pointer to TRACEINFO
    IN OUT ULONG *lpulIndex,            // index of TRACEENTRY in TRACEINFO
    OUT LPTRACEENTRY *lppTraceEntry)    // pointer to TRACEENTRY
{
    HRESULT hr      = NOERROR;
    ULONG   ulIndex = 0;

    DEBUGPUBLIC("HrTraceGotoNextEntry()");

    hr = CHK_HrTraceGotoNextEntry(
        lpTraceInfo,
        lpulIndex,
        lppTraceEntry);

    if(FAILED(hr))
        RETURN(hr);

    ulIndex = *lpulIndex;

    ulIndex++;

    *lpulIndex = ulIndex;

    hr = HrTraceGotoEntry(
        lpTraceInfo,
        ulIndex,
        lppTraceEntry);

    RETURN(hr);
}

//$--HrTraceOpenEntry--------------------------------------------------------
//  Open a TRACEENTRY.
// -----------------------------------------------------------------------------
HRESULT HrTraceOpenEntry(            // RETURNS: return code
    IN LPTRACEENTRY lpTraceEntry,       // pointer to TRACEENTRY
    OUT LONG     *plAction,             // The routing action the tracing site
                                        // took.
    OUT FILETIME *pftArrivalTime,       // The time at which the communique
                                        // entered the tracing site.
    OUT FILETIME *pftDeferredTime,      // The time are which the tracing site
                                        // released the message.
    OUT LPSTR    *lppszADMDName,        // ADMD Name
    OUT LPSTR    *lppszCountryName,     // Country Name
    OUT LPSTR    *lppszPRMDId,          // PRMD Identifier
    OUT LPSTR    *lppszAttADMDName,     // Attempted ADMD Name
    OUT LPSTR    *lppszAttCountryName,  // Attempted Country Name
    OUT LPSTR    *lppszAttPRMDId)       // Attempted PRMD Identifier
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrTraceOpenEntry()");

    hr = CHK_HrTraceOpenEntry(
        lpTraceEntry,
        plAction,
        pftArrivalTime,
        pftDeferredTime,
        lppszADMDName,
        lppszCountryName,
        lppszPRMDId,
        lppszAttADMDName,
        lppszAttCountryName,
        lppszAttPRMDId);

    if(FAILED(hr))
        RETURN(hr);

    *plAction               = lpTraceEntry->lAction;
    *pftArrivalTime         = lpTraceEntry->ftArrivalTime;
    *pftDeferredTime        = lpTraceEntry->ftDeferredTime;
    *lppszADMDName          = lpTraceEntry->rgchADMDName;
    *lppszCountryName       = lpTraceEntry->rgchCountryName;
    *lppszPRMDId            = lpTraceEntry->rgchPRMDId;
    *lppszAttADMDName       = lpTraceEntry->rgchAttADMDName;
    *lppszAttCountryName    = lpTraceEntry->rgchAttCountryName;
    *lppszAttPRMDId         = lpTraceEntry->rgchAttPRMDId;

    RETURN(hr);
}

//$--HrTraceSetInfo----------------------------------------------------------
//  Set the trace-info on a message.
// -----------------------------------------------------------------------------
HRESULT HrTraceSetInfo(              // RETURNS: return code
    IN LONG lAction,                    // pointer to action
    IN FILETIME *lpftArrivalTime,       // pointer to arrival time
    IN FILETIME *lpftDeferredTime,      // pointer to deferred time
    IN LPSTR lpszCountry,               // pointer to country
    IN LPSTR lpszADMD,                  // pointer to ADMD
    IN LPSTR lpszPRMD,                  // pointer to PRMD
    IN OUT LPMESSAGE lpMessage)         // pointer to message
{
    HRESULT     hr          = NOERROR;
    LPTRACEINFO lpTraceInfo = NULL;

    DEBUGPUBLIC("HrTraceSetInfo()");

    hr = CHK_HrTraceSetInfo(
        lAction,
        lpftArrivalTime,
        lpftDeferredTime,
        lpszCountry,
        lpszADMD,
        lpszPRMD,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrTraceCreateEntryList(
        lAction,
        *lpftArrivalTime,
        *lpftDeferredTime,
        lpszADMD,
        lpszCountry,
        lpszPRMD,
        NULL,
        NULL,
        NULL,
        &lpTraceInfo);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrTraceSetEntryList(
        lpMessage,
        lpTraceInfo);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpTraceInfo);

    RETURN(hr);
}

//$--HrTraceUpdateInfo-------------------------------------------------------
//  Update the trace-info on a message.
// -----------------------------------------------------------------------------
HRESULT HrTraceUpdateInfo(           // RETURNS: return code
    IN LONG lAction,                    // pointer to action
    IN FILETIME *lpftArrivalTime,       // pointer to arrival time
    IN FILETIME *lpftDeferredTime,      // pointer to deferred time
    IN LPSTR lpszCountry,               // pointer to country
    IN LPSTR lpszADMD,                  // pointer to ADMD
    IN LPSTR lpszPRMD,                  // pointer to PRMD
    IN OUT LPMESSAGE lpMessage)         // pointer to message
{
    HRESULT      hr                 = NOERROR;
    LPTRACEINFO  lpTraceInfo        = NULL;
    LPTRACEENTRY lpTraceEntry       = NULL;
    ULONG        index              = 0;
    LONG         lCurrAction        = 0;
    FILETIME     ftCurrArrivalTime  = {0};
    FILETIME     ftCurrDeferredTime = {0};
    LPSTR       lpszCurrCountry    = NULL;
    LPSTR       lpszCurrADMD       = NULL;
    LPSTR       lpszCurrPRMD       = NULL;
    LPSTR       lpszCurrAttCountry = NULL;
    LPSTR       lpszCurrAttADMD    = NULL;
    LPSTR       lpszCurrAttPRMD    = NULL;

    DEBUGPUBLIC("HrTraceUpdateInfo()");

    hr = CHK_HrTraceUpdateInfo(
        lAction,
        lpftArrivalTime,
        lpftDeferredTime,
        lpszCountry,
        lpszADMD,
        lpszPRMD,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrTraceGetEntryList(
        lpMessage,
        &lpTraceInfo);

    if(FAILED(hr))
    {
        if(hr != EDK_E_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    if(hr == EDK_E_NOT_FOUND)
    {
        hr = HrTraceCreateEntryList(
            lAction,
            *lpftArrivalTime,
            *lpftDeferredTime,
            lpszADMD,
            lpszCountry,
            lpszPRMD,
            NULL,
            NULL,
            NULL,
            &lpTraceInfo);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        hr = HrTraceGotoFirstEntry(lpTraceInfo, &index, &lpTraceEntry);

        while(SUCCEEDED(hr))
        {

            hr = HrTraceOpenEntry(
                lpTraceEntry,
                &lCurrAction,
                &ftCurrArrivalTime,
                &ftCurrDeferredTime,
                &lpszCurrADMD,
                &lpszCurrCountry,
                &lpszCurrPRMD,
                &lpszCurrAttADMD,
                &lpszCurrAttCountry,
                &lpszCurrAttPRMD);

            if(FAILED(hr))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            if( (lstrcmpi(lpszADMD, lpszCurrADMD) == 0) &&
                (lstrcmpi(lpszPRMD, lpszCurrPRMD) == 0) &&
                (lstrcmpi(lpszCountry, lpszCurrCountry) == 0))
            {
                hr = HR_LOG(EDK_E_ALREADY_EXISTS);
                goto cleanup;

            }

            hr = HrTraceGotoNextEntry(
                lpTraceInfo,
                &index,
                &lpTraceEntry);

            if((FAILED(hr)) && (hr != EDK_E_NOT_FOUND))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }

        hr = HrTraceAppendEntryList(
            lAction,
            *lpftArrivalTime,
            *lpftDeferredTime,
            lpszADMD,
            lpszCountry,
            lpszPRMD,
            NULL,
            NULL,
            NULL,
            &lpTraceInfo);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = HrTraceSetEntryList(
        lpMessage,
        lpTraceInfo);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpTraceInfo);

    RETURN(hr);
}

//------------------------------------------------------------------------------
//
// INTERNAL TRACE INFORMATION FUNCTIONS
//
//------------------------------------------------------------------------------

//$--HrInternalTraceCopyEntry---------------------------------------------------
//  Copy internal trace entry information to an internal trace entry structure.
// -----------------------------------------------------------------------------
HRESULT HrInternalTraceCopyEntry(       // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszMTAName,            // MTA Name
    IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    IN LPSTR    lpszAttMTAName,         // Attempted MTA Name
    OUT PINTTRACEENTRY lpTraceEntry)    // Pointer to trace entry address
                                        // variable.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrInternalTraceCopyEntry()");

    hr = CHK_HrInternalTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszMTAName,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpszAttMTAName,
        lpTraceEntry);

    if(FAILED(hr))
        RETURN(hr);

    lpTraceEntry->lAction = lAction;

    lpTraceEntry->ftArrivalTime = ftArrivalTime;

    lpTraceEntry->ftDeferredTime = ftDeferredTime;

    if((lpszADMDName != NULL) && (strlen(lpszADMDName) > 0))
    {
        strncpy(lpTraceEntry->rgchADMDName, lpszADMDName, MAX_ADMD_NAME_SIZ);
        lpTraceEntry->rgchADMDName[MAX_ADMD_NAME_SIZ-1] = '\0';
    }

    if((lpszCountryName != NULL) && (strlen(lpszCountryName) > 0))
    {
        strncpy(lpTraceEntry->rgchCountryName, lpszCountryName, MAX_COUNTRY_NAME_SIZ);
        lpTraceEntry->rgchCountryName[MAX_COUNTRY_NAME_SIZ-1] = '\0';
    }

    if((lpszPRMDId != NULL) && (strlen(lpszPRMDId) > 0))
    {
        strncpy(lpTraceEntry->rgchPRMDId, lpszPRMDId, MAX_PRMD_NAME_SIZ);
        lpTraceEntry->rgchPRMDId[MAX_PRMD_NAME_SIZ-1] = '\0';
    }

    if((lpszMTAName != NULL) && (strlen(lpszMTAName) > 0))
    {
        strncpy(lpTraceEntry->rgchMTAName, lpszMTAName, MAX_MTA_NAME_SIZ);
        lpTraceEntry->rgchMTAName[MAX_MTA_NAME_SIZ-1] = '\0';
    }

    if((lpszAttADMDName != NULL) && (strlen(lpszAttADMDName) > 0))
    {
        strncpy(lpTraceEntry->rgchAttADMDName, lpszAttADMDName, MAX_ADMD_NAME_SIZ);
        lpTraceEntry->rgchAttADMDName[MAX_ADMD_NAME_SIZ-1] = '\0';
    }

    if((lpszAttCountryName != NULL) && (strlen(lpszAttCountryName) > 0))
    {
        strncpy(lpTraceEntry->rgchAttCountryName, lpszAttCountryName, MAX_COUNTRY_NAME_SIZ);
        lpTraceEntry->rgchAttCountryName[MAX_COUNTRY_NAME_SIZ-1] = '\0';
    }

    if((lpszAttPRMDId != NULL) && (strlen(lpszAttPRMDId) > 0))
    {
        strncpy(lpTraceEntry->rgchAttPRMDId, lpszAttPRMDId, MAX_PRMD_NAME_SIZ);
        lpTraceEntry->rgchAttPRMDId[MAX_PRMD_NAME_SIZ] = '\0';
    }

    if((lpszAttMTAName != NULL) && (strlen(lpszAttMTAName) > 0))
    {
        strncpy(lpTraceEntry->rgchAttMTAName, lpszAttMTAName, MAX_MTA_NAME_SIZ);
        lpTraceEntry->rgchAttMTAName[MAX_MTA_NAME_SIZ-1] = '\0';
    }

    RETURN(hr);
}

//$--HrInternalTraceCreateEntryList---------------------------------------------
//  Create a internal hop trace information list.
// -----------------------------------------------------------------------------
HRESULT HrInternalTraceCreateEntryList( // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszMTAName,            // MTA Name

IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    IN LPSTR    lpszAttMTAName,         // Attempted MTA Name
    OUT PINTTRACEINFO *lppTraceInfo)    // Pointer to hop trace address variable
{
    HRESULT       hr          = NOERROR;
    ULONG         cBytes      = 0;
    PINTTRACEINFO lpTraceInfo = NULL;
    SCODE         sc          = 0;

    DEBUGPUBLIC("HrInternalTraceCreateEntryList()");

    hr = CHK_HrInternalTraceCreateEntryList(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszMTAName,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpszAttMTAName,
        lppTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    // Allocate a trace entry list

    cBytes = CbNewINTTRACEINFO(1);

    sc = MAPIAllocateBuffer(cBytes, (void **)lppTraceInfo);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize trace entry list
    ZeroMemory(*lppTraceInfo, cBytes);

    lpTraceInfo = *lppTraceInfo;

    lpTraceInfo->cEntries = 1;

    hr = HrInternalTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszMTAName,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpszAttMTAName,
        &(lpTraceInfo->rgIntTraceEntry[0]));

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrInternalTraceAppendEntryList---------------------------------------------
//  Append to an existing internal hop trace information list.
// -----------------------------------------------------------------------------
HRESULT HrInternalTraceAppendEntryList( // RETURNS: return code
    IN LONG     lAction,                // The routing action the tracing site
                                        // took.
    IN FILETIME ftArrivalTime,          // The time at which the communique
                                        // entered the tracing site.
    IN FILETIME ftDeferredTime,         // The time are which the tracing site
                                        // released the message.
    IN LPSTR    lpszADMDName,           // ADMD Name
    IN LPSTR    lpszCountryName,        // Country Name
    IN LPSTR    lpszPRMDId,             // PRMD Identifier
    IN LPSTR    lpszMTAName,            // MTA Name
    IN LPSTR    lpszAttADMDName,        // Attempted ADMD Name
    IN LPSTR    lpszAttCountryName,     // Attempted Country Name
    IN LPSTR    lpszAttPRMDId,          // Attempted PRMD Identifier
    IN LPSTR    lpszAttMTAName,         // Attempted MTA Name
    IN OUT PINTTRACEINFO *lppTraceInfo) // Pointer to hop trace address variable
{
    HRESULT        hr             = NOERROR;
    ULONG          i              = 0;
    ULONG          cBytes         = 0;
    ULONG          cEntries       = 0;
    PINTTRACEINFO  lpTraceInfo    = NULL;
    PINTTRACEENTRY lpTraceEntry   = NULL;
    PINTTRACEINFO  lpNewTraceInfo = NULL;
    SCODE          sc             = 0;

    DEBUGPUBLIC("HrInternalTraceAppendEntryList()");

    hr = CHK_HrInternalTraceAppendEntryList(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszMTAName,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpszAttMTAName,
        lppTraceInfo);

    if(FAILED(hr))
        RETURN(hr);

    // Allocate a trace entry list

    lpTraceInfo = *lppTraceInfo;

    cEntries = lpTraceInfo->cEntries + 1;

    cBytes = CbNewINTTRACEINFO(cEntries);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpNewTraceInfo);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize trace entry list
    ZeroMemory(lpNewTraceInfo, cBytes);

    lpNewTraceInfo->cEntries = cEntries;

    cEntries--;

    for(i = 0; i < cEntries; i++)
    {
        lpTraceEntry = &(lpTraceInfo->rgIntTraceEntry[i]);

        hr = HrInternalTraceCopyEntry(
            lpTraceEntry->lAction,
            lpTraceEntry->ftArrivalTime,
            lpTraceEntry->ftDeferredTime,
            lpTraceEntry->rgchADMDName,
            lpTraceEntry->rgchCountryName,
            lpTraceEntry->rgchPRMDId,
            lpTraceEntry->rgchMTAName,
            lpTraceEntry->rgchAttADMDName,
            lpTraceEntry->rgchAttCountryName,
            lpTraceEntry->rgchAttPRMDId,
            lpTraceEntry->rgchAttMTAName,
            &(lpNewTraceInfo->rgIntTraceEntry[i]));

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    hr = HrInternalTraceCopyEntry(
        lAction,
        ftArrivalTime,
        ftDeferredTime,
        lpszADMDName,
        lpszCountryName,
        lpszPRMDId,
        lpszMTAName,
        lpszAttADMDName,
        lpszAttCountryName,
        lpszAttPRMDId,
        lpszAttMTAName,
        &(lpNewTraceInfo->rgIntTraceEntry[cEntries]));

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    MAPIFREEBUFFER(lpTraceInfo);

    *lppTraceInfo = lpNewTraceInfo;

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpNewTraceInfo);
    }

    RETURN(hr);
}


EDKMSG.C

// --edkmsg.c-------------------------------------------------------------------
//
// EDK message DLL.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"


EDKTRACK.C

// --edktrack.c-----------------------------------------------------------------
//
//  Module containing gateway tracking functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "sadapi.h"

#include "edktrack.chk"

//$--HrTrackInitLog-------------------------------------------------------------
//  This function initializes the gateway event tracking module.
// -----------------------------------------------------------------------------
HRESULT HrTrackInitLog(             // RETURNS: return code
IN LPSTR lpszComputer,          // name of the remote computer
    OUT PVOID *ppvSession)          // pointer to gateway tracking session
{
    HRESULT      hr        = NOERROR;
    RPC_SC       sc        = ERROR_SUCCESS;
    PRPCBINDINFO lpSession = NULL;

    DEBUGPUBLIC("HrTrackInitLog()");

    hr = CHK_HrTrackInitLog( lpszComputer, ppvSession);
    if(FAILED(hr))
        RETURN(hr);

    *ppvSession = NULL;

    hr = MAPIAllocateBuffer( sizeof(RPCBINDINFO), &lpSession);
    if( FAILED( hr))
        goto cleanup;

    ZeroMemory( lpSession, sizeof(RPCBINDINFO));

    __try
    {
    sc = SAD_ScBindA(lpSession, lpszComputer);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());
        sc = RPC_E_UNEXPECTED;
    }

if(sc != ERROR_SUCCESS)
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

    *ppvSession = (PVOID)lpSession;

cleanup:
    if( FAILED( hr))
        MAPIFREEBUFFER( lpSession);

    RETURN( hr);
}

//$--HrTrackWriteLog@-------------------------------------------------------
//  This function logs a gateway tracking event.
// ----------------------------------------------------------------------------
HRESULT HrTrackWriteLogW(
    IN PVOID           pvSession,       // gateway tracking session
    IN UINT            nEventType,      // gateway tracking event type
    IN SYSTEMTIME      stEvent,         // time event took place
    IN LPCWSTR         lpszGatewayName, // gateway name
    IN LPCWSTR         lpszPartner,     // pointer to entity name string
    IN LPCWSTR         lpszMTSID,       // pointer to MTS-ID string
    IN LPCWSTR         lpszSubjectMTSID,// pointer to subject MTS-ID string
    IN LPCWSTR         lpszRemoteID,    // pointer to foreign ID string
    IN LPCWSTR         lpszOriginator,  // pointer to originator string
    IN INT             nPriority,       // message priority
    IN INT             nLength,         // message length
    IN INT             nSeconds,        // seconds to process this message
    IN INT             nCost,           // cost of processing this message
    IN INT             cRecipients,     // count of recipients
    IN LPCWSTR        *lppszRecipients) // pointer to recipient pointers
{
    HRESULT     hr                = NOERROR;
    RPC_SC      sc                = ERROR_SUCCESS;
    RPCBINDINFO *lpSession        = NULL;

    RPC_GATEWAY_TRACK_INFORMATION gti = {0};

    DEBUGPUBLIC("HrTrackWriteLogW()");

    hr = CHK_HrTrackWriteLogW(
        pvSession, nEventType, stEvent,
        lpszGatewayName, lpszPartner, lpszMTSID,
        lpszSubjectMTSID, lpszRemoteID, lpszOriginator,
        nPriority, nLength, nSeconds, nCost,
        cRecipients, lppszRecipients);
    if(FAILED(hr))
        RETURN(hr);

    lpSession = (PRPCBINDINFO)pvSession;

    gti.nEventType               = nEventType;

    gti.stEvent.rgwSystemTime[0] = stEvent.wYear;
    gti.stEvent.rgwSystemTime[1] = stEvent.wMonth;
    gti.stEvent.rgwSystemTime[2] = stEvent.wDayOfWeek;
    gti.stEvent.rgwSystemTime[3] = stEvent.wDay;
    gti.stEvent.rgwSystemTime[4] = stEvent.wHour;
    gti.stEvent.rgwSystemTime[5] = stEvent.wMinute;
    gti.stEvent.rgwSystemTime[6] = stEvent.wSecond;
    gti.stEvent.rgwSystemTime[7] = stEvent.wMilliseconds;

    // Strings already WCHAR
    gti.szGatewayName = (LPWSTR)lpszGatewayName;
    gti.szPartner     = (LPWSTR)lpszPartner;
    gti.szMTSID       = (LPWSTR)lpszMTSID;
    gti.szSubjectID   = (LPWSTR)lpszSubjectMTSID;
    gti.szRemoteID    = (LPWSTR)lpszRemoteID;
    gti.szOriginator  = (LPWSTR)lpszOriginator;

    gti.nPriority       = nPriority;
    gti.nLength         = nLength;
    gti.nSeconds        = nSeconds;
    gti.nCost           = nCost;

    __try
    {
        sc = SAD_ScSaveGatewayTrackingData(
            lpSession,
        &gti,
        cRecipients,
        (LPWSTR*)lppszRecipients);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());
        sc = RPC_E_UNEXPECTED;
    }

    if(sc != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:
    RETURN(hr);
}

HRESULT HrTrackWriteLogA(
    IN PVOID           pvSession,       // gateway tracking session
    IN UINT            nEventType,      // gateway tracking event type
    IN SYSTEMTIME      stEvent,         // time event took place
    IN LPCSTR          lpszGatewayName, // gateway name
    IN LPCSTR          lpszPartner,     // pointer to entity name string
    IN LPCSTR          lpszMTSID,       // pointer to MTS-ID string
    IN LPCSTR          lpszSubjectMTSID,// pointer to subject MTS-ID string
    IN LPCSTR          lpszRemoteID,    // pointer to foreign ID string
    IN LPCSTR          lpszOriginator,  // pointer to originator string
    IN INT             nPriority,       // message priority
    IN INT             nLength,         // message length
    IN INT             nSeconds,        // seconds to process this message
    IN INT             nCost,           // cost of processing this message
    IN INT             cRecipients,     // count of recipients
    IN LPCSTR         *lppszRecipients) // pointer to recipient pointers
{
    HRESULT     hr                = NOERROR;
    RPC_SC      sc                = ERROR_SUCCESS;
    RPCBINDINFO *lpSession        = NULL;
    LPWSTR      *lppszRecipientsW = NULL;

    RPC_GATEWAY_TRACK_INFORMATION gti = {0};

    DEBUGPUBLIC("HrTrackWriteLogA()");

    hr = CHK_HrTrackWriteLogA(
        pvSession, nEventType, stEvent,
        lpszGatewayName, lpszPartner, lpszMTSID,
        lpszSubjectMTSID, lpszRemoteID, lpszOriginator,
        nPriority, nLength, nSeconds, nCost,
        cRecipients, lppszRecipients);
    if(FAILED(hr))
        RETURN(hr);

    lpSession = (PRPCBINDINFO)pvSession;

    gti.nEventType               = nEventType;

    gti.stEvent.rgwSystemTime[0] = stEvent.wYear;
    gti.stEvent.rgwSystemTime[1] = stEvent.wMonth;
    gti.stEvent.rgwSystemTime[2] = stEvent.wDayOfWeek;
    gti.stEvent.rgwSystemTime[3] = stEvent.wDay;
    gti.stEvent.rgwSystemTime[4] = stEvent.wHour;
    gti.stEvent.rgwSystemTime[5] = stEvent.wMinute;
    gti.stEvent.rgwSystemTime[6] = stEvent.wSecond;
    gti.stEvent.rgwSystemTime[7] = stEvent.wMilliseconds;

    // Convert strings to WCHAR
    hr = HrStrAToStrW( lpszGatewayName, &gti.szGatewayName);
    if(FAILED(hr))
    goto cleanup;

    hr = HrStrAToStrW( lpszPartner, &gti.szPartner);
    if(FAILED(hr))
    goto cleanup;

    hr = HrStrAToStrW( lpszMTSID, &gti.szMTSID);
    if(FAILED(hr))
    goto cleanup;

    hr = HrStrAToStrW( lpszSubjectMTSID, &gti.szSubjectID);
    if(FAILED(hr))
    goto cleanup;

    hr = HrStrAToStrW( lpszRemoteID, &gti.szRemoteID);
    if(FAILED(hr))
    goto cleanup;

    hr = HrStrAToStrW( lpszOriginator, &gti.szOriginator);
    if(FAILED(hr))
    goto cleanup;

    gti.nPriority       = nPriority;
    gti.nLength         = nLength;
    gti.nSeconds        = nSeconds;
    gti.nCost           = nCost;

if(cRecipients != 0)
{
        ULONG   cb          = 0L;
        int     iRecipients = 0;

        cb = cRecipients * (sizeof(LPWSTR) + MAX_ALIGN);

    hr = MAPIAllocateBuffer(
            cb, (PVOID)&lppszRecipientsW);
        if( FAILED( hr))
            goto cleanup;

        ZeroMemory( lppszRecipientsW, cb);

for(iRecipients = 0; iRecipients < cRecipients; iRecipients++)
{
if(lppszRecipients[iRecipients] == NULL)
            {
                hr = HR_LOG(E_FAIL);
        goto cleanup;
            }

            hr = HrStrAToStrW(
                lppszRecipients[iRecipients],
                &lppszRecipientsW[iRecipients]);
            if(FAILED(hr))
        goto cleanup;
}
}

    __try
    {
        sc = SAD_ScSaveGatewayTrackingData(
            lpSession,
        &gti,
        cRecipients,
        lppszRecipientsW);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());
        sc = RPC_E_UNEXPECTED;
    }

    if(sc != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER(gti.szGatewayName);
    MAPIFREEBUFFER(gti.szPartner);
    MAPIFREEBUFFER(gti.szMTSID);
    MAPIFREEBUFFER(gti.szSubjectID);
    MAPIFREEBUFFER(gti.szRemoteID);
    MAPIFREEBUFFER(gti.szOriginator);

if(cRecipients != 0 && lppszRecipientsW != NULL)
{
        int iRecipients = 0;

for(iRecipients = 0; iRecipients < cRecipients; iRecipients ++)
            MAPIFREEBUFFER(lppszRecipientsW[iRecipients]);

        MAPIFREEBUFFER(lppszRecipientsW);
}

    RETURN(hr);
}

//$--HrTrackUninitLog----------------------------------------------------------
//  This function uninitializes the gateway event tracking module.
// ----------------------------------------------------------------------------
HRESULT HrTrackUninitLog(               // RETURNS: return code
    IN OUT PVOID *ppvSession)           // pointer to gateway tracking session
{
    HRESULT      hr        = NOERROR;
    PRPCBINDINFO lpSession = NULL;

    DEBUGPUBLIC("HrTrackUninitLog()");

    hr = CHK_HrTrackUninitLog(
        ppvSession);
    if(FAILED(hr))
        RETURN(hr);

    lpSession = (PRPCBINDINFO)*ppvSession;

    __try
    {
    SAD_Unbind(lpSession);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());
        hr = HR_LOG(E_FAIL);
    }

    MAPIFREEBUFFER( *ppvSession);

    RETURN(hr);
}


_VSOF.H

//--_vsof.h--------------------------------------------------------------------
//
// This file contains methods and #define's for implementing an OLE 2.0
// storage stream (as defineed in the OLE 2.0 specs) on top of OS file.
//
// Copyright 1986-1996.  Microsoft Corporation.  All Rights Reserved.
//
//-------------------------------------------------------------------------------

#ifndef __VSOF_H
#define __VSOF_H

#define BUFFER_SIZE 4096
#define VIRTUAL_BUFFER_SIZE 16384L
#define VBUFF_ENV_NAME "virtual_stream_on_file_buffer_size"
#define VSOF_DEFAULT_PREFIX "VSF"

#define SET_FILE_POINTER_ERROR 0xffffffff

/*IStream Interface ------------------------------------------------ */

//Warning:  This is not multithread safe with respect to clones.

#define MAPI_ISTREAM_METHODS(IPURE)\
MAPIMETHOD(Read)\
(THIS_OUT VOID HUGEP *lpv,\
IN ULONGcb,\
OUT ULONG FAR *lpcbRead) IPURE;\
MAPIMETHOD(Write)\
(THIS_IN VOID const HUGEP *lpv,\
IN ULONGcb,\
OUT ULONG FAR *lpcbWritten) IPURE;\
MAPIMETHOD(Seek)\
(THIS_IN LARGE_INTEGERlibMove,\
IN DWORDdwOrigin,\
OUT ULARGE_INTEGER FAR *lplibNewPosition) IPURE;\
MAPIMETHOD(SetSize)\
(THIS_IN ULARGE_INTEGERlibNewSize) IPURE;\
MAPIMETHOD(CopyTo)\
(THIS_IN IStream FAR *lpstm,\
IN ULARGE_INTEGERcb,\
OUT ULARGE_INTEGER FAR *lpcbRead,\
OUT ULARGE_INTEGER FAR *lpcbWritten) IPURE;\
MAPIMETHOD(Commit)\
(THIS_IN DWORDdwCommitFlags) IPURE;\
MAPIMETHOD(Revert)\
(THIS)IPURE;\
MAPIMETHOD(LockRegion)\
(THIS_IN ULARGE_INTEGERlibOffset,\
IN ULARGE_INTEGERcb,\
IN DWORDdwLockType) IPURE;\
MAPIMETHOD(UnlockRegion)\
(THIS_IN ULARGE_INTEGERulibOffset,\
IN ULARGE_INTEGERcb,\
IN DWORDdwLockType) IPURE;\
MAPIMETHOD(Stat)\
(THIS_IN STATSTG FAR *lpstatstg,\
IN DWORDdwStatFlag) IPURE;\
MAPIMETHOD(Clone)\
(THIS_OUT IStream FAR * FAR *lppstm) IPURE;\

/*VSOF Interface -------------------------------------------------- */

#undefINTERFACE
#defineINTERFACE struct _VSOF
#undefMAPIMETHOD_
#defineMAPIMETHOD_(type, method)MAPIMETHOD_DECLARE(type, method, VSOF_)
MAPI_IUNKNOWN_METHODS(IMPL)
MAPI_ISTREAM_METHODS(IMPL)
#undefMAPIMETHOD_
#defineMAPIMETHOD_(type, method)MAPIMETHOD_TYPEDEF(type, method, VSOF_)
MAPI_IUNKNOWN_METHODS(IMPL)
MAPI_ISTREAM_METHODS(IMPL)
#undefMAPIMETHOD_
#defineMAPIMETHOD_(type, method)STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(VSOF_)
{
BEGIN_INTERFACE
MAPI_IUNKNOWN_METHODS(IMPL)
MAPI_ISTREAM_METHODS(IMPL)
};

//$--_VSOF---------------------------------------------------------------------
//
// Virtual Stream On File internal structure.
//
//-----------------------------------------------------------------------------
typedef struct _VSOF
{
VSOF_Vtbl FAR *lpVtbl;
LONGlcInit;
ULONGulFlags;
CHARrgchFile[MAX_PATH];
HANDLEhf;         // == INVALID_HANDLE_VALUE if in virutal mode.
LPALLOCATEBUFFERlpfnAlloc;
LPFREEBUFFERlpfnFree;
    DWORD               dwVirtualBufferSize;
    ULONG               ulFileSize;     // for virtual mode.
    ULONG               ulFilePointer;  // for virtual mode.
    LPVOID              lpvVBuff;    //  Virtual buffer.  NULL if not in virtual mode.
    struct _VSOF *      lpVSOFOrig;  // Original.  we are a clone if this is non-NULL.
BOOL                fDirty;// stream dirty flag

} VSOF, FAR * LPVSOF;

typedef struct _VSOF *VSOF_HANDLE;

DWORD
VSOF_SetFilePointer(
    VSOF_HANDLE vhFile,
    LONG lDistanceToMove,
    PLONG lpDistanceToMoveHigh,
    DWORD dwMoveMethod
    );

BOOL
VSOF_SetEndOfFile(
    VSOF_HANDLE vhFile
    );

BOOL
VSOF_FlushFileBuffers(
    VSOF_HANDLE vhFile
    );

BOOL
VSOF_ReadFile(
    VSOF_HANDLE vhFile,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped
    );

BOOL
VSOF_WriteFile(
    VSOF_HANDLE vhFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
    );

DWORD
VSOF_GetFileSize(
    VSOF_HANDLE vhFile,
    LPDWORD lpFileSizeHigh
    );

BOOL
VSOF_CloseHandle(
    VSOF_HANDLE vhObject
    );

VSOF_HANDLE
VSOF_CreateFile(
    LPCSTR lpFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes,
    HANDLE hTemplateFile
    );

BOOL
VSOF_DeleteFile(
    VSOF_HANDLE vhFile
    );

STDMETHODIMP_(VOID) InvalidateVSOF (LPVSOF);

//$--VSOF_Vtbl-----------------------------------------------------------------
//
// Virtual function table for the Virtual Stream On File interface.
//
//-----------------------------------------------------------------------------
const VSOF_Vtbl VtblVSOF =
{
VSOF_QueryInterface,
VSOF_AddRef,
VSOF_Release,
VSOF_Read,
VSOF_Write,
VSOF_Seek,
VSOF_SetSize,
VSOF_CopyTo,
VSOF_Commit,
VSOF_Revert,
VSOF_LockRegion,
VSOF_UnlockRegion,
VSOF_Stat,
VSOF_Clone
};

#endif// __VSOF_H


_VSOP.H

//--_vsop.h--------------------------------------------------------------------
//
// CEDKVirtualStreamOnProperty class header file.
//
// Copyright Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#ifndef __VSOP_H
#define __VSOP_H

// maximum number of stream characters to buffer
const ULONGcbMaxBuffered=8192;// 8K data bytes

//--$CEDKVirtualStreamOnProperty---------------------------------------------------------
//
// Class which buffers stream access to a binary or string property.
//
//-----------------------------------------------------------------------------

class CEDKVirtualStreamOnProperty : public IUnknown
{
public:

// The HrOpenVirtualStreamOnProperty() function is CEDKVirtualStreamOnProperty
// friend.
friend HRESULT _HrOpenVirtualStreamOnProperty(
    IN LPMAPIPROP pPropObject,// MAPI property object pointer
    IN ULONG ulPropTag,     // property tag to open virtual stream on
IN ULONG ulFlags,// MAPI property flags
    OUT PVIRTUALSTREAMONPROPERTY * ppVirtualStreamOnProperty);  // pointer buffered stream

    // methods

    // Note:  Order IS important.  The CEDKVirtualStreamOnProperty interface
    // must have the same vtable order as the IStream interface.

    // IUnknown methods
STDMETHOD(QueryInterface)(
IN REFIID riid,         // interface identifier reference
OUT LPVOID * ppvObj);   // pointer to object pointer

STDMETHOD_(ULONG, AddRef)();

STDMETHOD_(ULONG, Release)();

    // IStream Methods.
    //
    // Note: Some methods are not currrently suppoerted.
    // These methods all return STG_E_INVALIDFUNCTION:
    // Seek, Clone, Revert, SetSize, UnlockRegion & LockRegion.
    //
    STDMETHOD(Read)(
        IN LPVOID pv,       // output buffer pointer
        IN ULONG cb,        // maximum # of bytes to read
        OUT ULONG * pcb);   // # of bytes read

    STDMETHOD(Write)(
        IN VOID const * pv, // data to write
        IN ULONG cb,        // # bytes to write
        OUT ULONG *pcb);    // # bytes written

    STDMETHOD(Seek)(
        IN LARGE_INTEGER cbOffset,      // byte offset
        IN DWORD dwOrigin,              // origin
        OUT ULARGE_INTEGER * pcbPos);   // new position

    STDMETHOD(SetSize)(
        IN ULARGE_INTEGER nSize);    // new size

    STDMETHOD(CopyTo)(
        IN LPSTREAM pStrm,              // destination stream pointer
        IN ULARGE_INTEGER cb,           // # bytes to copy
        OUT ULARGE_INTEGER * pcbRead,   // # bytes read
        OUT ULARGE_INTEGER * pcbWritten);// # bytes written

    STDMETHOD(Commit)(
        IN DWORD dwFlags);   // flags

    STDMETHOD(Revert)();

    STDMETHOD(LockRegion)(
        IN ULARGE_INTEGER cbOffset, // offset
        IN ULARGE_INTEGER cbLength, // length
        IN DWORD dwFlags);          // flags

    STDMETHOD(UnlockRegion)(
        IN ULARGE_INTEGER cbOffset, // offset
        IN ULARGE_INTEGER cbLength, // length
        IN DWORD dwFlags);          // flags

    STDMETHOD(Stat)(
        OUT STATSTG * pStatStg,     // stream statistic pointer
        IN DWORD dwFlags);          // flags

    STDMETHOD(Clone)(
        OUT LPSTREAM * ppStrm);      // pointer to new stream

protected:

    // constructor
    CEDKVirtualStreamOnProperty();

    // destructor
    ~CEDKVirtualStreamOnProperty();

    // Initialize virtual stream on property data
    HRESULT HrInitialize(
    IN LPMAPIPROP pPropObject,// MAPI property object pointer
    IN ULONG ulPropTag,     // property tag to open virtual stream on
IN ULONG ulFlags);// MAPI property flags

private:

// Utility functions

// Sets mode in the storage statistics structure
inline VOID SetMode(
IN ULONG ulMode)// stream mode
{
m_StatStg.grfMode = ulMode;
}

// returns TRUE if property opened for writing
inline BOOL fIsWriteProperty()
{
return ( ((m_ulFlags & MAPI_MODIFY) == MAPI_MODIFY) );
}

// returns TRUE if property to be created
inline BOOL fIsNewProperty()
{
return ( ((m_ulFlags & MAPI_CREATE) == MAPI_CREATE) );
}

// returns TRUE if property not opened for writing
inline BOOL fIsReadProperty()
{
return ( ((m_ulFlags & MAPI_MODIFY) == 0) &&
         ((m_ulFlags & MAPI_CREATE) == 0) );
}

// calculates current size of stream.
HRESULT HrComputeCurrentSize(
IN ULARGE_INTEGER * pcbSize);

// Initializes newly created data buffer to a particular size
inline VOID InitBufferSize(
IN ULONG cbSize)// new buffer size
{
ASSERT_READ_PTR(m_rgbBuffer, cbSize, "Bad m_rgbBuffer");

m_cbBuffer = cbSize;// new buffer size
m_pbBuffer = m_rgbBuffer;// current data pointer
m_fDataInitialized = TRUE;// have read data

// If buffer is a write buffer,
// then zero fill write buffer, including
// the extra space left for null-terminating
// exactly 8K long strings. (Read
// buffers will already be filled with data read.)
if ( fIsWriteProperty() == TRUE )
{
ASSERTERROR(cbSize == cbMaxBuffered, "Bad cbSize");

ZeroMemory(
m_rgbBuffer,
cbSize + sizeof(CHAR));
}
}

// Retrieves number of unread bytes in local data buffer
inline ULONG GetBytesUnread()
{
return (m_cbBuffer - (m_pbBuffer - m_rgbBuffer));
}

// Retrieves number of unwritten bytes in local data buffer
inline ULONG GetBytesUnwritten()
{
return (m_cbBuffer - (m_pbBuffer - m_rgbBuffer));
}

inline ULONG GetBytesWritten()
// Retrieves number of bytes written to local data buffer
{
return (m_pbBuffer - m_rgbBuffer);
}

// Increments number of bytes read in local data buffer
inline VOID IncrementBytesRead(
IN ULONG cbRead)// number of bytes read
{
m_pbBuffer += cbRead;
}

// Increments number of bytes written to local data buffer
inline VOID IncrementBytesWritten(
IN ULONG cbWritten)// number of bytes written
{
m_pbBuffer += cbWritten;
}

// Increments size of underlying stream
inline VOID IncrementStreamSize(
IN LARGE_INTEGER cbAdd)// number of bytes to add to stream size
{
DWORDLONGcbCurrent=0;// current size
DWORDLONGcbIncrement=0;// amount ot add

// Determine current stream size
cbCurrent = MAKEDWORDLONG(m_StatStg.cbSize.LowPart,
  m_StatStg.cbSize.HighPart);

// Determine amount to increase stream size by.
cbIncrement = MAKEDWORDLONG(cbAdd.LowPart,cbAdd.HighPart);

// Increment current stream size
cbCurrent += cbIncrement;

// Set size in storage statistics structure
m_StatStg.cbSize.LowPart = LOWDWORD(cbCurrent);
m_StatStg.cbSize.HighPart = HIDWORD(cbCurrent);;

}

// Retrieves value of data initialized flag
inline BOOL fDataInitialized()
{
return m_fDataInitialized;
}

// Opens/Creates an underlying property stream for reading or writing.
HRESULT HrOpenUnderlyingStream();

// Flushes the data in the write buffer to the underlying stream.
HRESULT HrFlushWriteBuffer(
IN BOOL fCleanWriteBuffer);// TRUE if should zero out write buffer also

// data members

LPMAPIPROPm_pPropObject;// MAPI property object pointer
ULONGm_ulFlags;// MAPI property flags
ULONGm_ulPropTag;// MAPI property tag (must be string or binary)

// underlying stream structures (if there is an underlying stream)
LPSTREAMm_pStream;// unbuffered stream pointer (if any)
    STATSTG m_StatStg;      // stream statistics

// buffered read or write data (exclusive OR)
BYTE *m_rgbBuffer;
BYTE *m_pbBuffer;// pointer into the data buffer
ULONGm_cbBuffer;// current size of data buffer
BOOLm_fDataInitialized;// TRUE if data buffer has been initialized

    ULONG   m_refs;         // reference count

};

#endif


EDKRTF.C

// --edkrtf.c------------------------------------------------------------------
//
// EDK utility functions to help with creating the PR_RTF_COMPRESSED
// message property.
//
// Copyright (C) Microsoft Corp. 1986-1996, All rights reserved.
//
// ----------------------------------------------------------------------------

#include "edk.h"
#include "edkrtf.chk"

//$--HrTextToRTFCompressed------------------------------------------------
//
// DESCRIPTION: Convert plain ANSI text to its RTF compressed
//              equivalent for a MAPI message.
//
// INPUT:       cchText --  number of text character to convert
//              lpText  --  plain ANSI text to convert
//                              cAttachments    --      # of message attachments
//                              rgiRendering    --      array of attachment rendering positions
//              lpMsg   --  MAPI message pointer for which to create
//                          the compressed rich text
//              cpid    --  code page of the text stream
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                                                      MAPI_E_* if IMAPIProp::OpenProperty() fails during RTF
//                                                              property creation,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT HrTextToRTFCompressed(
    IN ULONG cchText,       // # characters of text
    IN LPSTREAM lpText,     // plain text stream pointer
IN ULONG cAttachments,  // # of message attachments
IN ULONG * rgiRendering,// array of attachment rendering positions
    IN LPMESSAGE lpMsg,     // MAPI message pointer
IN ULONG cpid)
{
    HRESULT         hr                      =  NOERROR;
    LPSTREAM        lpText2                 =  NULL;// text stream
LPSTREAM        lpRTFText               =  NULL;// uncompressed RTF stream
    LPSTREAM        lpRTFComp               =  NULL;// RTF compressed stream
    LPSTREAM        lpRTFUnComp             =  NULL;// uncompressed RTF stream
ULONG           iAttach                 =  0;   // attachment index
ULARGE_INTEGER  cbBlockSize             =  {INT_MAX,0};
ULARGE_INTEGER  cbTextSize              =  {cchText,0};
    ULARGE_INTEGER  scbRead                 =  {0}; // # bytes read from stream
    ULARGE_INTEGER  scbWritten              =  {0}; // # bytes written to stream
    LARGE_INTEGER   cbTopOfFile             =  {0};
    // MAPI property creation flags
    const ULONG ulFlags = MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS;

    DEBUGPUBLIC("HrTextToRTFCompressed()\n");

// check input parameters
    hr = CHK_HrTextToRTFCompressed(cchText, lpText, cAttachments,
      rgiRendering, lpMsg, cpid);
if (FAILED(hr))
{
RETURN(hr);
}

    // Create another stream and copy only the bytes we want to it.
    // We do this since we don't know the number of RTF compressed
    // bytes to copy to the message.
    // 1.) Create lpTextStream2
    // 2.) Copy the correct number of bytes over

hr = CreateStreamOnHGlobal(
            NULL,   // it will allocate the memory
            TRUE,   // free memory on release
            &lpText2);

if (FAILED(hr))
    {
        goto cleanup;
    }

    hr = OLECALL(lpText)->CopyTo(lpText, lpText2, cbTextSize, &scbRead, &scbWritten);

    if (FAILED(hr))
    {
        goto cleanup;
    }

hr = OLECALL(lpText2)->Commit(lpText2, STGC_DEFAULT);

if (FAILED(hr))
{
goto cleanup;
}

    hr = OLECALL(lpText2)->Seek(lpText2,
cbTopOfFile,
STREAM_SEEK_SET,
NULL);

if (FAILED(hr))
goto cleanup;

    hr  = HrRTFFromTextStream(lpText2, &lpRTFText, cpid, cAttachments, rgiRendering);

if (FAILED(hr))
{
goto cleanup;
}

    ASSERT_IUNKNOWN_PTR(lpRTFText, "Bad lpRTFText");

    // Open a stream on the newly created PR_RTF_COMPRESSED
    // message property.
    hr = MAPICALL(lpMsg)->OpenProperty(
    lpMsg,                  // message pointer
    PR_RTF_COMPRESSED,      // property tag
    &IID_IStream,           // interface ID reference
    STGM_CREATE | STGM_WRITE,   // interface flags
    ulFlags,                // MAPI flags
    (LPUNKNOWN *) &lpRTFComp);  // stream pointer

if (FAILED(hr))
{
// Return MAPI error.

goto cleanup;
}

    ASSERT_IUNKNOWN_PTR(lpRTFComp, "Bad lpRTFComp");

hr = WrapCompressedRTFStream (lpRTFComp, MAPI_CREATE | MAPI_MODIFY, &lpRTFUnComp);

if (FAILED(hr))
{
goto cleanup;
}

hr = OLECALL(lpRTFText)->CopyTo(lpRTFText,
lpRTFUnComp,
cbBlockSize,
&scbRead,
&scbWritten);
if (FAILED(hr))
{
goto cleanup;
}

hr = OLECALL(lpRTFUnComp)->Commit(lpRTFUnComp, STGC_DEFAULT);

if (FAILED(hr))
{
goto cleanup;
}

cleanup:

    // Release MAPI & OLE objects
    ULRELEASE(lpText2);
    ULRELEASE(lpRTFText);
    ULRELEASE(lpRTFComp);
ULRELEASE(lpRTFUnComp);

    RETURN(hr);

}

//$--HrRTFCompressedToText--------------------------------------------------
//
// DESCRIPTION: Convert message's compressed RTF to its ANSI
//              equivalent.
//
// INPUT:       lpMsg   --  MAPI message pointer for which to retrieve
//                          the ANSI text.
//              pText   --  pointer to stream to copy ANSI text to.
//              cpid    --  code page for the text stream
//
// OUTPUT:      pcb     --  # bytes copied to stream
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          MAPI_E_* if IMAPIProp::OpenProperty() fails during RTF
//                                                              property open,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT HrRTFCompressedToText(
    IN LPMESSAGE lpMsg,         // MAPI message pointer
    IN LPSTREAM pText,          // stream to copy ANSI text to
IN ULONG cpid,                          // code page
    OUT ULONG * pcb)            // # bytes copied to stream
{
    HRESULT         hr          =   NOERROR;
    LPSTREAM        lpRTFComp   =   NULL;   // RTF compressed stream
    LPSTREAM        lpTextWrap  =   NULL;   // "wrapper" stream pointer
    RTFSYNCINFO     sRtfSync    =   {0};    // RTFSYNCINFO structure
    ULARGE_INTEGER  scbInfinite =   {0};    // "infinite" large integer
    ULARGE_INTEGER  scbWritten  =   {0};    // # bytes written to stream

    DEBUGPUBLIC("HrRTFCompressedToText()\n");

    // check input parameters
    hr = CHK_HrRTFCompressedToText(lpMsg, pText, cpid, pcb);

    if ( FAILED(hr) )
    {
RETURN(hr);
    }

    // initialize output parameters
    *pcb = 0;

    // Open a stream interface on the message's RFT compressed
    // message text property.
    hr = MAPICALL(lpMsg)->OpenProperty(
lpMsg,                  // message pointer
PR_RTF_COMPRESSED,      // property tag
&IID_IStream,           // interface ID reference
STGM_READ,              // interface flags
MAPI_DEFERRED_ERRORS,   // reduces RPCs
(LPUNKNOWN *) &lpRTFComp);  // stream pointer

    if ( FAILED(hr) )
    {
// Return MAPI error.

goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(lpRTFComp, "Bad lpRTFComp");

    // Convert RTF compressed stream data to ANSI "wrapper" stream.
    // The ANSI wrapper stream supports only the CopyTo and Read
    // methods.
    hr = HrTextFromCompressedRTFStreamEx(
lpRTFComp,  // RTF compressed stream pointer
&lpTextWrap,// pointer to wrapper stream pointer
&sRtfSync,  // RTFSYNCINFO pointer
cpid,       // code page (use default)
0,          // zero attachments
NULL);      // no attachment array

    if ( FAILED(hr) )
    {
goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(lpTextWrap, "Bad lpTextWrap");

    // Copy the contents of the "wrapper" stream (which only supports
    // the Read & CopyTo methods) to the stream provided.
    scbInfinite.LowPart = INFINITE; // want to copy all data
    hr = OLECALL(lpTextWrap)->CopyTo(
lpTextWrap,     // stream pointer
pText,          // destination stream
scbInfinite,    // # bytes to copy
NULL,           // don't care
&scbWritten);   // # bytes copied

    if ( FAILED(hr) )
    {
goto cleanup;
    }

    // check that number of bytes written is less than
    // what we can handle.
    if ( (scbWritten.LowPart == INFINITE) ||
 (scbWritten.HighPart != 0) )
    {
hr = HR_LOG(E_NOTIMPL);

goto cleanup;
    }

    // Fill in number of bytes copied
    *pcb = scbWritten.LowPart;

    // we are done

cleanup:

    // Release MAPI and OLE objects
    ULRELEASE(lpRTFComp);
    ULRELEASE(lpTextWrap);

    RETURN(hr);

}


EDKUTCPP.CPP

// EdkUtCpp.CPP-----------------------------------------------------------------
// Implements methods for a class that supplies a list of id's and strings
// which can be found by ID.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "EdkUtCpp.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------
// CONSTRUCTOR: Pass in a pointer to an array of IDList and it's number of items.
// Use ARRAY_CNT( IDListArray) to get this.
// -----------------------------------------------------------------------------

CIDList::CIDList(
    PIDList pIDList,    // Pointer to array of IDList.
    UINT nCnt)          // Number of items in the list.
{
    m_pIDList = pIDList;
    m_nCnt = nCnt;
}

// -----------------------------------------------------------------------------
// Find an ID in the pIDList and return it's string.
// -----------------------------------------------------------------------------

const LPSTR CIDList::Find(  // Returns the string found or a not found message string.
    ULONG ulID)             // ID to search for.
{
    if( m_pIDList)
    {
        PIDList pIDList = m_pIDList;
        UINT nCnt = m_nCnt;
        while( nCnt)
        {
            if( pIDList->ulID == ulID)
                return( pIDList->pszValue); // Found the item we were looking for.
            pIDList ++;
            nCnt --;
        }
    }

    // NOTE: If you change this format you must also change the declration
    // of m_chNotFound to reflect the maximum size.
    sprintf( m_chNotFound, "(ID: 0x%lX not found)", ulID);
    return( m_chNotFound);
}

// -----------------------------------------------------------------------------


EDKUTILS.C

//
//  EDK utility functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "edkutils.chk"

//$--_HrWriteFile---------------------------------------------------------------
//  Transfer contents from a given memory location & length to an open file.
// -----------------------------------------------------------------------------
HRESULT _HrWriteFile(
    IN HANDLE hFile,                    // file handle
    IN ULONG cbmem,                     // count of bytes of memory
    IN LPBYTE lpbmem)                   // pointer to memory
{
    HRESULT hr             = NOERROR;
    DWORD   dwBytesWritten = 0;
    BOOL    fStatus        = FALSE;

    DEBUGPUBLIC("_HrWriteFile()");

    hr = CHK__HrWriteFile(
        hFile,
        cbmem,
        lpbmem);

    if(FAILED(hr))
        RETURN(hr);

    fStatus = WriteFile(
        hFile,
        lpbmem,
        cbmem,
        &dwBytesWritten,
        NULL);

    if(fStatus == FALSE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Check if out of disk space
    if(dwBytesWritten != cbmem)
    {
        hr = HR_LOG(MAPI_E_NOT_ENOUGH_DISK);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrCreateDirEntryId------------------------------------------------------
//  Create a directory entry ID given the address of the object
//  in the directory.
// -----------------------------------------------------------------------------
HRESULT HrCreateDirEntryId(          // RETURNS: return code
    IN LPSTR lpszAddress,               // pointer to address
    OUT ULONG *lpcbeid,                 // count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID
{
    HRESULT hr       = NOERROR;
    SCODE   sc       = 0;
    LPBYTE  lpdireid = NULL;
    ULONG   cbdireid = 0;
    BYTE    muid[16] = MUIDEMSAB;
    ULONG   cbAddr   = 0;

    DEBUGPUBLIC("HrCreateDirEntryId()");

    hr = CHK_HrCreateDirEntryId(
        lpszAddress,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

    cbAddr = cbStrLen(lpszAddress);

    cbdireid = sizeof(DIR_ENTRYID) + cbAddr;

    sc = MAPIAllocateBuffer(cbdireid, (void **)&lpdireid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    else
    {
        ZeroMemory(lpdireid, cbdireid);

        ((LPDIR_ENTRYID)lpdireid)->ulVersion = EMS_VERSION;

        CopyMemory(
            (LPBYTE)&((LPDIR_ENTRYID)lpdireid)->muid,
            muid,
            sizeof(muid));

        CopyMemory(
            lpdireid + sizeof(DIR_ENTRYID),
            (LPBYTE)lpszAddress,
            cbAddr);
    }

    *lpcbeid = cbdireid;
    *lppeid  = (LPENTRYID)lpdireid;

cleanup:

    RETURN(hr);
}

//$--_HrMemoryToFile----------------------------------------------------------
//  Transfer contents from a given memory location & length to a given file.
// -----------------------------------------------------------------------------
HRESULT _HrMemoryToFile(              // RETURNS: return code
    IN ULONG cbmem,                     // count of bytes of memory
    IN LPBYTE lpbmem,                   // pointer to memory
    IN LPSTR lpszFilename)             // pointer to destination file name
{
    HRESULT hr             = NOERROR;
    HANDLE  hFile          = INVALID_HANDLE_VALUE;
    DWORD   dwBytesWritten = 0;

    DEBUGPUBLIC("_HrMemoryToFile()");

    hr = CHK__HrMemoryToFile(
        cbmem,
        lpbmem,
        lpszFilename);

    if(FAILED(hr))
        RETURN(hr);

    hFile = CreateFile(
        lpszFilename,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
        NULL);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = _HrWriteFile(hFile, cbmem, lpbmem);

cleanup:

    // Close the file
    if(hFile != INVALID_HANDLE_VALUE)
    {
        if(CloseHandle(hFile) == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    RETURN(hr);
}

//$--_HrFileToMemory----------------------------------------------------------
//  Transfer contents from a given file to memory.
// -----------------------------------------------------------------------------
HRESULT _HrFileToMemory(              // RETURNS: return code
    IN LPSTR lpszFilename,             // pointer to source file name
    OUT ULONG *lpcbmem,                 // pointer to count of bytes of memory
                                        // variable
    OUT LPBYTE *lppbmem)                // pointer to bytes of memory address
                                        // variable
{
    HRESULT        hr          = NOERROR;
    SCODE          sc          = 0;
    HFILE          hFile       = HFILE_ERROR;
    OFSTRUCT       ofStruct    = {0};
    DWORD          dwBytesRead = 0;
    ULARGE_INTEGER ll          = {0,0};
    ULONG          ulFileSize  = 0;
    ULONG          ulMemSize   = 0;
    BOOL           fStatus     = FALSE;

    DEBUGPUBLIC("_HrFileToMemory()");

    hr = CHK__HrFileToMemory(
        lpszFilename,
        lpcbmem,
        lppbmem);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbmem = 0;
    *lppbmem = NULL;

    hFile = OpenFile(
        lpszFilename,
        &ofStruct,
        OF_READ);

    if(hFile == HFILE_ERROR)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get file size
    if((ulFileSize = GetFileSize((HANDLE)hFile, NULL)) == (DWORD)HFILE_ERROR)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Allocate the memory for the file contents
    ulMemSize = ulFileSize + 2 * sizeof(wchar_t);

    sc = MAPIAllocateBuffer(ulMemSize, (void **)lppbmem);

    // An error occured allocating the block buffer
    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Initialize block of memory
    ZeroMemory(*lppbmem, ulMemSize);

    // Read the property value into memory
    fStatus = ReadFile(
        (HANDLE)hFile,
        *lppbmem,
        ulFileSize,
        &dwBytesRead,
        NULL);

    if(fStatus == FALSE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Check if the entire file was read
    if(dwBytesRead != ulFileSize)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lpcbmem = ulFileSize;

cleanup:

    // Close the file
    if(hFile != HFILE_ERROR)
    {
        if(CloseHandle((HANDLE)hFile) == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppbmem);

        *lpcbmem = 0;
    }

    RETURN(hr);
}

//$--HrGetServiceStatus------------------------------------------------------
//  Get the current state of a service on a given machine.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceStatus(          // RETURNS: return code
    IN LPSTR lpszMachineName,          // machine name
    IN LPSTR lpszServiceName,          // service name
    OUT DWORD *lpdwCurrentState)        // current state
{
    HRESULT         hr               = NOERROR;
    SC_HANDLE       schSCManager     = NULL;
    SC_HANDLE       schService       = NULL;
    DWORD           fdwDesiredAccess = 0;
    SERVICE_STATUS  ssServiceStatus  = {0};
    BOOL            fRet             = FALSE;

    hr = CHK_HrGetServiceStatus(
        lpszMachineName,
        lpszServiceName,
        lpdwCurrentState);

    if(FAILED(hr))
        RETURN(hr);

    *lpdwCurrentState = 0;

    fdwDesiredAccess = GENERIC_READ;

    schSCManager = OpenSCManager(
        lpszMachineName,    // address of machine name string
        NULL,               // address of database name string
        fdwDesiredAccess    // type of access
    );

    if(schSCManager == NULL)
    {
        hr = HR_LOG(GetLastError());
        goto cleanup;
    }

    schService = OpenService(
        schSCManager,       // handle of service control manager database
        lpszServiceName,    // address of name of service to start
        fdwDesiredAccess    // type of access to service
    );

    if(schService == NULL)
    {
        hr = HR_LOG(GetLastError());
        goto cleanup;
    }

    fRet = QueryServiceStatus(
        schService,         // handle of service
        &ssServiceStatus    // address of service status structure
    );

    if(fRet == FALSE)
    {
        hr = HR_LOG(GetLastError());
        goto cleanup;
    }

    *lpdwCurrentState = ssServiceStatus.dwCurrentState;

cleanup:

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);

    RETURN(hr);
}

//$--HrGetExchangeStatus-----------------------------------------------------
//  Get the current state of the Exchange server on a given machine.
// -----------------------------------------------------------------------------
HRESULT HrGetExchangeStatus(         // RETURNS: return code
    IN LPSTR lpszMachineName,          // machine name
    OUT DWORD *lpdwService,             // service
    OUT DWORD *lpdwCurrentState)        // current state
{
    HRESULT hr = NOERROR;

    hr = CHK_HrGetExchangeStatus(
        lpszMachineName,
        lpdwService,
        lpdwCurrentState);

    if(FAILED(hr))
        RETURN(hr);

    *lpdwService = 0;
    *lpdwCurrentState = 0;

    hr = HrGetServiceStatus(
        lpszMachineName,
        "MSExchangeSA",
        lpdwCurrentState);

    if(FAILED(hr) || (*lpdwCurrentState != SERVICE_RUNNING))
    {
        hr = HR_LOG(E_FAIL);

        *lpdwService = EXCHANGE_SA;
        goto cleanup;
    }

    hr = HrGetServiceStatus(
        lpszMachineName,
        "MSExchangeDS",
        lpdwCurrentState);

    if(FAILED(hr) || (*lpdwCurrentState != SERVICE_RUNNING))
    {
        hr = HR_LOG(E_FAIL);

        *lpdwService = EXCHANGE_DS;
        goto cleanup;
    }

    hr = HrGetServiceStatus(
        lpszMachineName,
        "MSExchangeMTA",
        lpdwCurrentState);

    if(FAILED(hr) || (*lpdwCurrentState != SERVICE_RUNNING))
    {
        hr = HR_LOG(E_FAIL);

        *lpdwService = EXCHANGE_MTA;
        goto cleanup;
    }

    hr = HrGetServiceStatus(
        lpszMachineName,
        "MSExchangeIS",
        lpdwCurrentState);

    if(FAILED(hr) || (*lpdwCurrentState != SERVICE_RUNNING))
    {
        hr = HR_LOG(E_FAIL);

        *lpdwService = EXCHANGE_IS;
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrGetExchangeServiceStatus----------------------------------------------
//  Get the current state of an Exchange service on a given machine.
// -----------------------------------------------------------------------------
HRESULT HrGetExchangeServiceStatus(  // RETURNS: return code
    IN LPSTR lpszMachineName,          // machine name
    IN  DWORD dwService,                // service
    OUT DWORD *lpdwCurrentState)        // current state
{
    HRESULT hr              = NOERROR;
    LPSTR  lpszServiceName = NULL;

    hr = CHK_HrGetExchangeServiceStatus(
        lpszMachineName,
        dwService,
        lpdwCurrentState);

    if(FAILED(hr))
        RETURN(hr);

    *lpdwCurrentState = 0;

    switch(dwService)
    {
    case EXCHANGE_DS:
        lpszServiceName = "MSExchangeDS";
        break;
    case EXCHANGE_IS:
        lpszServiceName = "MSExchangeIS";
        break;
    case EXCHANGE_MTA:
        lpszServiceName = "MSExchangeMTA";
        break;
    case EXCHANGE_SA:
        lpszServiceName = "MSExchangeSA";
        break;
    default:
        hr = HR_LOG(E_INVALIDARG);

        *lpdwCurrentState = SERVICE_STOPPED;
        goto cleanup;
    }

    hr = HrGetServiceStatus(
        lpszMachineName,
        lpszServiceName,
        lpdwCurrentState);

    if(FAILED(hr) || (*lpdwCurrentState != SERVICE_RUNNING))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--FMachineExists---------------------------------------------------------
//  Returns TRUE if the computer exists.
// -----------------------------------------------------------------------------
BOOL FMachineExists(                // RETURNS: TRUE/FALSE
    IN LPSTR  lpszComputerName)        // address of name of remote computer
{
    HRESULT hr       = NOERROR;
    BOOL    fRet     = TRUE;
    LONG    lRet     = 0;
    HKEY    hkResult = NULL;

    hr = CHK_HrEDKComputerExists(lpszComputerName);

    if(FAILED(hr))
        return(FALSE);

    lRet = RegConnectRegistry(
        lpszComputerName,
        HKEY_LOCAL_MACHINE,
        &hkResult);

    if(lRet != ERROR_SUCCESS)
    {
        fRet = FALSE;
        hr = HR_LOG(E_FAIL);
    }

    RegCloseKey(hkResult);

    return(fRet);
}

//$--_HrFindFile--------------------------------------------------------------
//  Find a file in a directory subtree.
// -----------------------------------------------------------------------------
HRESULT _HrFindFile(                      // RETURNS: return code
    IN LPSTR lpszInPathName,               // starting path name
    IN LPSTR lpszInFileName,               // file name
    OUT LPSTR lpszOutPathName)             // path name where file first found
{
    HRESULT         hr                     = NOERROR;
    HANDLE          hFile                  = INVALID_HANDLE_VALUE;
    HANDLE          hDir                   = INVALID_HANDLE_VALUE;
    CHAR           szPathName[MAX_PATH+1] = {0};
    CHAR           szFileName[MAX_PATH+1] = {0};
    WIN32_FIND_DATA wfdFile                = {0};
    WIN32_FIND_DATA wfdDir                 = {0};

    DEBUGPUBLIC("_HrFindFile()");

    wsprintf(
        szFileName, "%s\\%s", lpszInPathName, lpszInFileName);

    hFile = FindFirstFile(szFileName, &wfdFile);

    if(hFile != INVALID_HANDLE_VALUE)
    {
        lstrcpy(lpszOutPathName, lpszInPathName);
        goto cleanup;
    }

    wsprintf(
        szFileName, "%s\\%s", lpszInPathName, "*.*");

    hDir = FindFirstFile(szFileName, &wfdDir);

    if(hDir != INVALID_HANDLE_VALUE)
    {
        while(1)
        {
            if(!lstrcmp(wfdDir.cFileName, "."))
            {
                ; // skip current directory
            }
            else if(!lstrcmp(wfdDir.cFileName, ".."))
            {
                ; // skip parent directory
            }
            else if(wfdDir.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
            {
                wsprintf(
                    szPathName, "%s\\%s", lpszInPathName, wfdDir.cFileName);

                hr = _HrFindFile(
                    szPathName,
                    lpszInFileName,
                    lpszOutPathName);

                if(SUCCEEDED(hr))
                {
                    goto cleanup;
                }
            }

            if(! FindNextFile(hDir, &wfdDir))
            {
                hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
                break;
            }
        }
    }
    else
    {
        hr = HR_LOG(E_FAIL);
    }

cleanup:

    if(hFile != INVALID_HANDLE_VALUE)
    {
        FindClose(hFile);
    }

    if(hDir != INVALID_HANDLE_VALUE)
    {
        FindClose(hDir);
    }

    RETURN(hr);
}

//$--GetSystemEnvironmentVariable-----------------------------------------------
//  Gets a system environment variable.
// -----------------------------------------------------------------------------
DWORD GetSystemEnvironmentVariable( // RETURNS: size of value
    IN LPSTR  lpszName,           // address of environment variable name
    OUT LPSTR  lpszValue,          // address of buffer for variable value
    OUT DWORD  cchValue)            // size of buffer, in characters
{
    HRESULT hr         = NOERROR;
    BOOL    fRet       = TRUE;
    LONG    lRet       = 0;
    HKEY    hKey       = INVALID_HANDLE_VALUE;
    DWORD   cbData     = 0;
    DWORD   dwType     = 0;

    DEBUGPUBLIC("GetSystemEnvironmentVariable()");

    //
    //  Open the system Environment key.
    //

    lRet = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
        0,
        KEY_QUERY_VALUE,
        &hKey);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        fRet = FALSE;
        goto cleanup;
    }

    //
    //  Get size of value.
    //

    lRet = RegQueryValueEx(
        hKey,
        lpszName,
        NULL,
        &dwType,
        NULL,
        &cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        cbData = 0;
        goto cleanup;
    }

    if(cchValue < (cbData/sizeof(CHAR)))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Get the current value.
    //

    lRet = RegQueryValueEx(
        hKey,
        lpszName,
        NULL,
        &dwType,
        lpszValue,
        &cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        cbData = 0;
        goto cleanup;
    }

cleanup:

    if(hKey != INVALID_HANDLE_VALUE)
    {
        RegCloseKey(hKey);
    }

    return(cbData);
}

//$--SetSystemEnvironmentVariable-----------------------------------------------
//  Sets a system environment variable permanently.
// -----------------------------------------------------------------------------
BOOL SetSystemEnvironmentVariable(  // RETURNS: TRUE/FALSE
    IN LPCSTR  lpszName,           // address of environment variable name
    IN LPCSTR  lpszValue)          // address of new value for variable
{
    HRESULT hr         = NOERROR;
    BOOL    fRet       = TRUE;
    LONG    lRet       = 0;
    HKEY    hKey       = INVALID_HANDLE_VALUE;
    DWORD   cbData     = 0;

    DEBUGPUBLIC("SetSystemEnvironmentVariable()");

    //
    //  Open the system Environment key.
    //

    lRet = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
        0,
        KEY_SET_VALUE,
        &hKey);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        fRet = FALSE;
        goto cleanup;
    }

    //
    // Set the Environment value.
    //

    cbData = cbStrLen(lpszValue);

    lRet = RegSetValueEx(
        hKey,
        lpszName,
        0,
        REG_EXPAND_SZ,
        (CONST BYTE *)lpszValue,
        cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(hKey != INVALID_HANDLE_VALUE)
    {
        RegCloseKey(hKey);
    }

    return(fRet);
}

//$--GetUserEnvironmentVariable-------------------------------------------------
//  Gets a user environment variable.
// -----------------------------------------------------------------------------
DWORD GetUserEnvironmentVariable(   // RETURNS: size of value
    IN LPSTR  lpszName,           // address of environment variable name
    OUT LPSTR  lpszValue,          // address of buffer for variable value
    OUT DWORD  cchValue)            // size of buffer, in characters
{
    HRESULT hr         = NOERROR;
    BOOL    fRet       = TRUE;
    LONG    lRet       = 0;
    HKEY    hKey       = INVALID_HANDLE_VALUE;
    DWORD   cbData     = 0;
    DWORD   dwType     = 0;

    DEBUGPUBLIC("GetUserEnvironmentVariable()");

    //
    //  Open the user Environment key.
    //

    lRet = RegOpenKeyEx(
        HKEY_CURRENT_USER,
        "Environment",
        0,
        KEY_QUERY_VALUE,
        &hKey);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        fRet = FALSE;
        goto cleanup;
    }

    //
    //  Get size of value.
    //

    lRet = RegQueryValueEx(
        hKey,
        lpszName,
        NULL,
        &dwType,
        NULL,
        &cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        cbData = 0;
        goto cleanup;
    }

    if(cchValue < (cbData/sizeof(CHAR)))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Get the current value.
    //

    lRet = RegQueryValueEx(
        hKey,
        lpszName,
        NULL,
        &dwType,
        lpszValue,
        &cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        cbData = 0;
        goto cleanup;
    }

cleanup:

    if(hKey != INVALID_HANDLE_VALUE)
    {
        RegCloseKey(hKey);
    }

    return(cbData);
}

//$--SetUserEnvironmentVariable-------------------------------------------------
//  Sets a user environment variable permanently.
// -----------------------------------------------------------------------------
BOOL SetUserEnvironmentVariable(    // RETURNS: TRUE/FALSE
    IN LPCSTR  lpszName,           // address of environment variable name
    IN LPCSTR  lpszValue)          // address of new value for variable
{
    HRESULT hr         = NOERROR;
    BOOL    fRet       = TRUE;
    LONG    lRet       = 0;
    HKEY    hKey       = INVALID_HANDLE_VALUE;
    DWORD   cbData     = 0;

    DEBUGPUBLIC("SetUserEnvironmentVariable()");

    //
    //  Open the user Environment key.
    //

    lRet = RegOpenKeyEx(
        HKEY_CURRENT_USER,
        "Environment",
        0,
        KEY_SET_VALUE,
        &hKey);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        fRet = FALSE;
        goto cleanup;
    }

    //
    // Set the Environment value.
    //

    cbData = cbStrLen(lpszValue);

    lRet = RegSetValueEx(
        hKey,
        lpszName,
        0,
        REG_EXPAND_SZ,
        (CONST BYTE *)lpszValue,
        cbData);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(hKey != INVALID_HANDLE_VALUE)
    {
        RegCloseKey(hKey);
    }

    return(fRet);
}

//$--IsDelimiter@---------------------------------------------------------------
//  Returns TRUE if character is a delimiter.
//------------------------------------------------------------------------------
__inline static BOOL IsDelimiterW(
    IN LPCWSTR lpszDelimiters,      // delimiters
    IN WCHAR ch)                    // character
{
    LPCWSTR pch = lpszDelimiters;

    if(pch)
    {
        while(*pch)
        {
            if(*pch == ch)
                return(TRUE);

            pch++;
        }
    }

    return(FALSE);
}

__inline static BOOL IsDelimiterA(
    IN LPCSTR lpszDelimiters,       // delimiters
    IN CHAR ch)                     // character
{
    LPCSTR pch = lpszDelimiters;

    if(pch)
    {
        while(*pch)
        {
            if(*pch == ch)
                return(TRUE);

            pch++;
        }
    }

    return(FALSE);
}

//$--HrStrTokAll@---------------------------------------------------------------
//  Splits string lpsz at token separators and points elements of array
//  *lpppsz to string components.
//------------------------------------------------------------------------------
HRESULT HrStrTokAllW(               // RETURNS: return code
    IN LPCWSTR lpsz,                // separated string
    IN LPCWSTR lpszEOT,             // pointer to string containing separators
    OUT ULONG * lpcpsz,             // count of string pointers
    OUT LPWSTR ** lpppsz)           // pointer to list of strings
{
    HRESULT hr      = NOERROR;
    SCODE   sc      = 0;
    LPCWSTR pch     = NULL;
    LPBYTE  lpb     = NULL;
    LPWSTR *lppsz   = NULL;
    LPWSTR  pchNew  = NULL;
    ULONG   cToken  = 0;
    ULONG   cArrayBytes = 0;
    ULONG   cBytes  = 0;
    ULONG   csz     = 0;

    DEBUGPUBLIC("HrStrTokAllW()");

    hr = CHK_HrStrTokAllW(
        lpsz, lpszEOT, lpcpsz, lpppsz);
    if(FAILED(hr))
        RETURN(hr);

    *lpcpsz = 0;
    *lpppsz = NULL;

    // Skip to end if nothing to do
    if( *lpsz == 0)
        goto cleanup;

    // Count the number of tokens.
    pch = lpsz;
    while( *pch)
    {
        // Skip adjacent delimiter characters
        while( IsDelimiterW( lpszEOT, *pch))
            pch++;

        if( *pch == 0) break;

        cToken++;

        // Skip next token
        while( *pch && !IsDelimiterW( lpszEOT, *pch))
            pch++;
    }

    // Array will consist of pointers followed by the tokenized string
    cArrayBytes = ( (cToken + 1) * (sizeof(LPWSTR) + MAX_ALIGN));
    cBytes = cArrayBytes + cbStrLenW(lpsz);

    sc = MAPIAllocateBuffer( cBytes, (PVOID*) &lpb);
    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lppsz  = (LPWSTR*) lpb;
    pchNew = (LPWSTR) (lpb + cArrayBytes);

    lstrcpyW( pchNew, lpsz);

    while( *pchNew)
    {
        // remove and skip delimiter characters
        while( IsDelimiterW( lpszEOT, *pchNew))
            *pchNew++ = 0;

        if( *pchNew == 0) break;

        lppsz[csz++] = pchNew;

        // skip next token
        while( *pchNew && !IsDelimiterW( lpszEOT, *pchNew))
            pchNew++;
    }

    lppsz[csz] = NULL;

    ASSERTERROR( csz == cToken, "Error tokenizing string");

    *lpcpsz = csz;
    *lpppsz = lppsz;

cleanup:
    if(FAILED(hr))
        MAPIFREEBUFFER(lpb);

    RETURN(hr);
}

HRESULT HrStrTokAllA(               // RETURNS: return code
    IN LPCSTR lpsz,                 // separated string
    IN LPCSTR lpszEOT,              // pointer to string containing separators
    OUT ULONG * lpcpsz,             // count of string pointers
    OUT LPSTR ** lpppsz)            // pointer to list of strings
{
    HRESULT hr      = NOERROR;
    SCODE   sc      = 0;
    LPCSTR  pch     = NULL;
    LPBYTE  lpb     = NULL;
    LPSTR  *lppsz   = NULL;
    LPSTR   pchNew  = NULL;
    ULONG   cToken  = 0;
    ULONG   cArrayBytes = 0;
    ULONG   cBytes  = 0;
    ULONG   csz     = 0;

    DEBUGPUBLIC("HrStrTokAllA()");

    hr = CHK_HrStrTokAllA(
        lpsz, lpszEOT, lpcpsz, lpppsz);
    if(FAILED(hr))
        RETURN(hr);

    *lpcpsz = 0;
    *lpppsz = NULL;

    // Skip to end if nothing to do
    if( *lpsz == 0)
        goto cleanup;

    // Count the number of tokens.
    pch = lpsz;
    while( *pch)
    {
        // Skip adjacent delimiter characters
        while( IsDelimiterA( lpszEOT, *pch))
            pch++;

        if( *pch == 0) break;

        cToken++;

        // Skip next token
        while( *pch && !IsDelimiterA( lpszEOT, *pch))
            pch++;
    }

    // Array will consist of pointers followed by the tokenized string
    cArrayBytes = ( (cToken + 1) * (sizeof(LPSTR) + MAX_ALIGN));
    cBytes = cArrayBytes + cbStrLenA(lpsz);

    sc = MAPIAllocateBuffer( cBytes, (PVOID*) &lpb);
    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lppsz  = (LPSTR*) lpb;
    pchNew = (LPSTR) (lpb + cArrayBytes);

    lstrcpyA( pchNew, lpsz);

    while( *pchNew)
    {
        // remove and skip delimiter characters
        while( IsDelimiterA( lpszEOT, *pchNew))
            *pchNew++ = 0;

        if( *pchNew == 0) break;

        lppsz[csz++] = pchNew;

        // skip next token
        while( *pchNew && !IsDelimiterA( lpszEOT, *pchNew))
            pchNew++;
    }

    lppsz[csz] = NULL;

    ASSERTERROR( csz == cToken, "Error tokenizing string");

    *lpcpsz = csz;
    *lpppsz = lppsz;

cleanup:
    if(FAILED(hr))
        MAPIFREEBUFFER(lpb);

    RETURN(hr);
}

//$--HrCreateProfileName-----------------------------------------------------
//  Create a unique profile name.
// -----------------------------------------------------------------------------
HRESULT HrCreateProfileName(         // RETURNS: return code
    IN  LPSTR lpszPrefix,              // prefix of profile name
    IN  ULONG  cBufferSize,             // size of buffer in bytes
    IN OUT LPSTR lpszBuffer)           // buffer
{
    HRESULT    hr     = NOERROR;
    SYSTEMTIME st     = {0};
FILETIME   ft     = {0};
    INT        cBytes = 0;
    BOOL       fRet   = FALSE;

    DEBUGPUBLIC("HrCreateProfileName()");

    ZeroMemory(lpszBuffer, cBufferSize);

GetSystemTime(&st);

fRet = SystemTimeToFileTime(&st, &ft);

    if(fRet == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto cleanup;
    }

    cBytes = _snprintf(
        lpszBuffer,
        cBufferSize,
        "%s %08lx%08lx%08lx",
        lpszPrefix,
        GetCurrentProcessId(),
        ft.dwHighDateTime,
        ft.dwLowDateTime);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        ZeroMemory(lpszBuffer, cBufferSize);
    }

    RETURN(hr);
}

//$--HrGetFileVersionInfo----------------------------------------------------
//  Get the file version information.
// -----------------------------------------------------------------------------
HRESULT HrGetFileVersionInfo(            // RETURNS: return code
    IN  LPSTR lpszFileName,                // file name
    OUT LPVOID *lppVersionInfo)             // file version information
{
    HRESULT hr            = NOERROR;
    DWORD   dwVerInfoSize = 0;
    DWORD   dwVerHandle   = 0;
    BOOL    fRet          = 0;

    DEBUGPUBLIC("HrGetFileVersionInfo()");

    *lppVersionInfo = NULL;

    dwVerInfoSize = GetFileVersionInfoSize(lpszFileName, &dwVerHandle);

    if(dwVerInfoSize != 0)
    {
        *lppVersionInfo =
            (LPVOID)GlobalAlloc(GMEM_FIXED, dwVerInfoSize);

        if(*lppVersionInfo == NULL)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto cleanup;
        }

        fRet = GetFileVersionInfo(
            lpszFileName,
            0,
            dwVerInfoSize,
            (LPVOID)*lppVersionInfo);

        if(fRet == FALSE)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto cleanup;
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        if(lppVersionInfo != NULL)
        {
            GLOBALFREE(*lppVersionInfo);
        }
    }

    RETURN(hr);
}

//$--_GetFileNameFromFullPath--------------------------------------------------
//  Return a pointer to the file name.
// -----------------------------------------------------------------------------
LPSTR _GetFileNameFromFullPath(
    IN LPSTR lpszFullPath)                // full path name
{
    LPSTR pszT = NULL;
    LPSTR psz  = NULL;

    DEBUGPUBLIC("_GetFileNameFromFullPath()");

    psz  = lpszFullPath;

    pszT = lpszFullPath + lstrlen(lpszFullPath);

    while(pszT >= lpszFullPath)
    {
        if((*pszT == '\\') || (*pszT == ':'))
        {
            psz = pszT;
            break;
        }

        pszT--;
    }

    if((*psz == '\\') || (*psz == ':'))
    {
        psz++;
    }

    return(psz);
}

//$--HrSearchGAL----------------------------------------------------------------
//  Look for the entry ID by searching for the DN in the global address list.
//
//  RETURNS:hr == NOERROR           Found
//hr == EDK_E_NOT_FOUND   DN Not Found
//hr == (anything else)   Other Error
// -----------------------------------------------------------------------------
static HRESULT HrSearchGAL(// RETURNS: HRESULT
INLPADRBOOKlpAdrBook,// address book (directory) to look in
INLPSTRlpszDN,            // object distinguished name
OUTULONG *lpcbEntryID,// count of bytes in entry ID
OUTLPENTRYID * lppEntryID)    // pointer to entry ID
{
    HRESULT         hr                  = NOERROR;
    LPSTR           lpszAddress         = NULL;
    ULONG           cbGALEntryID        = 0;
    LPENTRYID       lpGALEntryID        = NULL;
    ULONG           ulGALObjectType     = 0;
    LPABCONT        lpGAL               = NULL;
    BOOL            fMapiRecip          = FALSE;

    DEBUGPRIVATE("HrSearchGAL()");

    hr = CHK_HrSearchGAL(
        lpAdrBook,
        lpszDN,
        lpcbEntryID,
        lppEntryID);
    if (FAILED(hr))
        RETURN(hr);

    // Initially zero out the return variables.

    *lpcbEntryID = 0;
    *lppEntryID = NULL;

    // Create an address string consisting of "EX:" followed by the DN.

    hr = MAPIAllocateBuffer(
        strlen(EXCHANGE_ADDRTYPE ":") + strlen(lpszDN) + 1,
        &lpszAddress);
    if (FAILED(hr))
        goto cleanup;

    strcpy(lpszAddress, EXCHANGE_ADDRTYPE ":");
    strcat(lpszAddress, lpszDN);

    // Open the global address list.

    hr = HrFindExchangeGlobalAddressList(
        lpAdrBook,
        &cbGALEntryID,
        &lpGALEntryID);
    if (FAILED(hr))
        goto cleanup;

    hr = MAPICALL(lpAdrBook)->OpenEntry(
        lpAdrBook,
        cbGALEntryID,
        lpGALEntryID,
        &IID_IMAPIContainer,
        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
        &ulGALObjectType,
        (LPUNKNOWN *) &lpGAL);
    if (FAILED(hr))
        goto cleanup;

    // Make sure it's the right object type.

    if (ulGALObjectType != MAPI_ABCONT)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Resolve the address (returns EDK_E_NOT_FOUND if not found).

    hr = HrGWResolveAddress(
        lpGAL,
        lpszAddress,
        &fMapiRecip,
        lpcbEntryID,
        lppEntryID);
    if (FAILED(hr))
        goto cleanup;

cleanup:
    ULRELEASE(lpGAL);
    MAPIFREEBUFFER(lpGALEntryID);
    MAPIFREEBUFFER(lpszAddress);
    RETURN(hr);
}

//$--HrSearchHierarchyTable-----------------------------------------------------
//  Look for the entry ID by searching for the DN in the hierarchy table.
//
//  RETURNS:hr == NOERROR           Found
//hr == EDK_E_NOT_FOUND   DN Not Found
//hr == (anything else)   Other Error
// -----------------------------------------------------------------------------
static HRESULT HrSearchHierarchyTable(  // RETURNS: HRESULT
INLPADRBOOKlpAdrBook,// address book (directory) to look in
INLPSTRlpszDN,            // object distinguished name
OUTULONG *lpcbEntryID,// count of bytes in entry ID
OUTLPENTRYID * lppEntryID)    // pointer to entry ID
{
    HRESULT         hr                  = NOERROR;
    ULONG           ulObjectType        = 0;
    LPMAPICONTAINER lpRootContainer     = NULL;
    LPMAPITABLE     lpHierarchyTable    = NULL;
    SizedSPropTagArray(1, sGetEntryID)  = {1, PR_ENTRYID};
    const ULONG     cMaxBatchRows       = 100;
    LPSRowSet       lpBatch             = NULL;
    ULONG           cbEntryID           = 0;
    LPENTRYID       lpEntryID           = NULL;
    LPSTR           lpszDNInsideEntryID = NULL;
    ULONG           iRow                = 0;
    MAPIUID         muidEMSAB           = MUIDEMSAB;
    LPMAPIUID       lpmuid              = NULL;

    DEBUGPRIVATE("HrSearchHierarchyTable()");

    hr = CHK_HrSearchHierarchyTable(
        lpAdrBook,
        lpszDN,
        lpcbEntryID,
        lppEntryID);
    if (FAILED(hr))
        RETURN(hr);

    // Initially zero out the return variables.

    *lpcbEntryID = 0;
    *lppEntryID = NULL;

    // Get the hierarchy table for the address book.

    hr = MAPICALL(lpAdrBook)->OpenEntry(
        lpAdrBook,
        0,
        NULL,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &ulObjectType,
        (LPUNKNOWN *) &lpRootContainer);
if (FAILED(hr))
goto cleanup;

    if (ulObjectType != MAPI_ABCONT)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = MAPICALL(lpRootContainer)->GetHierarchyTable(
        lpRootContainer,
        CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS,
        &lpHierarchyTable);
if (FAILED(hr))
goto cleanup;

    // We are only interested in the entry ID property.

    hr = MAPICALL(lpHierarchyTable)->SetColumns(
        lpHierarchyTable,
        (LPSPropTagArray) &sGetEntryID,
        0);
if (FAILED(hr))
goto cleanup;

    // Loop to get batches of entry IDs and search each batch for our DN.

    while (TRUE)
    {
        // Get a new batch of entry IDs.

        hr = MAPICALL(lpHierarchyTable)->QueryRows(
            lpHierarchyTable,
            cMaxBatchRows,
            0,
            &lpBatch);
    if (FAILED(hr))
    goto cleanup;

        // If we didn't get any rows then we are at the end of the table
        // so stop searching.

        if (lpBatch->cRows == 0)
            break;

        // Search through this batch for our DN.

        for (iRow = 0; iRow < lpBatch->cRows; iRow++)
        {
            // Get each row's entry ID and the MAPIUID contained inside of it.

            cbEntryID = lpBatch->aRow[iRow].lpProps[0].Value.bin.cb;
            lpEntryID = (LPENTRYID) lpBatch->aRow[iRow].lpProps[0].Value.bin.lpb;
            lpmuid = &(((LPDIR_ENTRYID)lpEntryID)->muid);

            // Make sure this entry ID is from the Exchange address book

            if (IsEqualMAPIUID(lpmuid, &muidEMSAB))
            {
                // Get the DN contained inside of the entry ID.

                lpszDNInsideEntryID = (LPSTR)
                    (lpBatch->aRow[iRow].lpProps[0].Value.bin.lpb
                    + sizeof(DIR_ENTRYID));

                // If the entry ID DN matches the one we're looking for then
                // we've found the entry ID we need to return.

                if (!_stricmp(lpszDNInsideEntryID, lpszDN))
                {
                    // Make a copy of the entry ID and return it
                    // in the return variables.

                    hr = MAPIAllocateBuffer(cbEntryID, lppEntryID);
                    if (FAILED(hr))
                        goto cleanup;
                    MoveMemory(*lppEntryID, lpEntryID,cbEntryID);
                    *lpcbEntryID = cbEntryID;

                    hr = NOERROR;
                    goto cleanup;
                }
            }

            // Free this row.

            MAPIFREEBUFFER(lpBatch->aRow[iRow].lpProps);
        }

        // Free the row set for the batch, so we can go  get another one.

        MAPIFREEBUFFER(lpBatch);
    }

    // If we got here then we didn't find the DN in the hierarchy table.

    hr = EDK_E_NOT_FOUND;

cleanup:
    if (lpBatch)
    {
        for (iRow = 0; iRow < lpBatch->cRows; iRow++)
            MAPIFREEBUFFER(lpBatch->aRow[iRow].lpProps);
        MAPIFREEBUFFER(lpBatch);
    }
    ULRELEASE(lpHierarchyTable);
    ULRELEASE(lpRootContainer);

    RETURN(hr);
}

//$--HrCreateDirEntryIdEx-------------------------------------------------------
//  Create a directory entry ID given the address of the object
//  in the directory.
// -----------------------------------------------------------------------------
HRESULT HrCreateDirEntryIdEx(// RETURNS: HRESULT
INLPADRBOOKlpAdrBook,// address book (directory) to look in
INLPSTRlpszAddress,// object distinguished name
OUTULONG *lpcbEntryID,// count of bytes in entry ID
OUTLPENTRYID * lppEntryID)    // pointer to entry ID
{
HRESULThr= NOERROR;
    ULONG           cbHierarchyEntryID  = 0;
    LPENTRYID       lpHierarchyEntryID  = NULL;
    ULONG           ulEntryIDType       = 0;

DEBUGPUBLIC("HrCreateDirEntryIdEx()");

    hr = CHK_HrCreateDirEntryIdEx(
        lpAdrBook,
        lpszAddress,
        lpcbEntryID,
        lppEntryID);
    if (FAILED(hr))
        RETURN(hr);

    // Initially zero out the return variables.

    *lpcbEntryID = 0;
    *lppEntryID = NULL;

// Look for the DN in the global address list.

hr = HrSearchGAL(
lpAdrBook,
lpszAddress,
        lpcbEntryID,
        lppEntryID);
if (FAILED(hr))
goto cleanup;

    // If the type was DT_AGENT or DT_ORGANIZATION, then we have to
    // do a further lookup in the hierarchy table to determine the
    // DN's real type.

    ulEntryIDType = ((LPDIR_ENTRYID) *lppEntryID)->ulType;

    if (ulEntryIDType == DT_AGENT ||
        ulEntryIDType == DT_ORGANIZATION)
    {
    // Look for the DN in the hierarchy table.

    hr = HrSearchHierarchyTable(
    lpAdrBook,
    lpszAddress,
    &cbHierarchyEntryID,
    &lpHierarchyEntryID);

        // If we didn't find it in the hierarchy table then
        // use the DT_AGENT entry ID.

        if (hr == EDK_E_NOT_FOUND)
        {
            hr = NOERROR;
            goto cleanup;
        }

        // Free the DT_AGENT entry ID.

        MAPIFREEBUFFER(*lppEntryID);
        *lpcbEntryID = 0;

        // If HrSearchHierarchyTable returned any other error
        // then return failure.

        if (FAILED(hr))
    goto cleanup;

        // Replace the DT_AGENT entry ID with the new one from
        // the hierarchy table.

        *lpcbEntryID = cbHierarchyEntryID;
        *lppEntryID = lpHierarchyEntryID;
    }

cleanup:
RETURN(hr);
}


UTL_CLI.C

// --UTL_CLI.C------------------------------------------------------------------
//
// Command line argument parsing helper functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"

#include <stdio.h>
#include <string.h>
#include "utl_cli.chk"

#define NO_MATCH    ((ULONG) ~0)
#define MAXARG      250     // Maximum length of a single command line argument.

//$--_HrFindArrayValue--------------------------------------------------------
// Scan through an array of string pointers searching for a value string that
// matches up to the length of the value string.  Case INSENSITIVE.
//
// OUTPUT:  pnIndex will contain the index into the array of the match.  It will
//          contain ~0 if no match was found.
//
// RETURNS: NOERROR
//          EDK_E_NOT_FOUND if match was not found.
//          EDK_E_AMBIGUOUS if more than one match was found.
//          E_INVALIDARG
// -----------------------------------------------------------------------------

HRESULT _HrFindArrayValue(
    IN  char*  pszValue,    // Ptr to string value to find.
    IN  char** rgpszArray,  // Array of strings containing known values.
    IN  ULONG  nArraySize,  // Number of known values.
    OUT ULONG* pnIndex)     // Ptr to index of known value, ~0 if no match found.
{
    HRESULT hr          = NOERROR;
    ULONG   ii          = 0;
    int     nCompLen    = 0;

    DEBUGPUBLIC( "_HrFindArrayValue()\n");

    // Check the input parameters.
    hr = CHK__HrFindArrayValue( pszValue, rgpszArray, nArraySize, pnIndex);
    if( FAILED( hr))
        RETURN( hr);

    // Get the length of the input string and verify that it is greater than zero.
    nCompLen = lstrlenA( pszValue);
    if( nCompLen <= 0)
        RETURN( E_INVALIDARG);

    // Initialize output parameter and return value.
    *pnIndex = NO_MATCH;
    hr = EDK_E_NOT_FOUND;

    // Search for the value.
    for( ii = 0; ii < nArraySize; ii ++)
    {
        // Ignore NULL's in the array.
        if( rgpszArray[ ii] == NULL)
            continue;

        // Compare value and entry in array.  The comparison is NOT case sensitive.
        if( _strnicmp( pszValue, rgpszArray[ ii], nCompLen) == 0)
        {   //  If no previous matches have been found, then remember
            //  the current array index.  If a match has already been
            //  found then abort.
            if( *pnIndex == NO_MATCH)
            {   // The first match has been found
                *pnIndex = ii;
                hr = NOERROR;
            }
            else
            {   // Second match, this is ambiguous.
                *pnIndex = NO_MATCH;

                // Return the appropriate error.
                hr = EDK_E_AMBIGUOUS;

                goto cleanup;
            }
        }
    }

cleanup:
    // Don't use RETURN() since it would log EDK_E_NOT_FOUND as an error
    // and we don't wish to log this here. Let the caller do it if need be.
    return( hr);
}

//$--_HrExpandCommandLineArgument-----------------------------------------------
//
// DESCRIPTION: Expands abbreviated command line flags of the form -FLAG=VALUE (or
//              /FLAG=VALUE) to their full text forms returning the index of the
//              matching flag and a pointer to the data part of the flag (ie the
//              part after the equals sign).  The caller passes an array of known
//              flag names, and the function tries to make an unambiguous match
//              with one of the names.  In this way users can be offered the
//              convenience of entering /V=Foo, instead of
//              /VERY-LONG-AND-CLUMSY-NAME=Foo (assuming no other flag begins with
//              V, otherwise the user might have to enter more letters).
//
//              The comparison is not case sensitive; the flag names /BLUTO and
//              -bluto are not distinguished, and /b might match on either of them.
//
//              To maintain greater compatibility with other Microsoft NT and DOS
//              command line applications, the ":" character may be substituted
//              for the "=".  So /FLAG:VALUE or -FLAG:VALUE are also valid.
//
//
//  INPUT:  pszArg          command line flag
//          rgpszArgArray   array of known flag names (without leading dashes)
//          nArraySize      number of elements in rgpszArgArray
//
//  OUTPUT: pnFlagIndex     pointer to index of matching flag name, ~0 if no match.
//          ppszFlagName    pointer to known flag name.
//                          Either of the above output parameters may be NULL.
//          ppszArgData     pointer to flag data. (NULL is NOT okay)
//
//  RETURNS:    HRESULT     NOERROR if no error (value without flag is OK),
//                          E_INVALIDARG if bad input,
//                          EDK_E_NOT_FOUND if unknown flag name
//                          EDK_E_AMBIGUOUS if more than one match found.
//                          E_FAIL if user argument is too long.
// -----------------------------------------------------------------------------

HRESULT _HrExpandCommandLineArgument(
    IN  char*  pszArg,          // flag user entered
    IN  char** rgpszArgArray,   // array of known flag names (w/o leading dashes)
    IN  ULONG  nArraySize,      // number of known flags
    OUT ULONG* pnFlagIndex,     // ptr to index of known flag, -1 if no match found.
OUT char** ppszFlagName,// pointer to known flag name
    OUT char** ppszArgData)     // ptr to user data for flag
{
    HRESULT hr                  = NOERROR;
    ULONG   nMatchIndex         = NO_MATCH;
    int     nCompLen            = 0;
    char*   pszSeparatorChar    = NULL;
    char    szArg[MAXARG + 1]  = "";

    DEBUGPUBLIC("_HrExpandCommandLineArgument()\n");

    // Check the input parameters
    hr = CHK__HrExpandCommandLineArgument( pszArg, rgpszArgArray, nArraySize,
                                            pnFlagIndex, ppszFlagName, ppszArgData);
    if( FAILED( hr))
        RETURN( hr);

    // Initialize output parameters.
    if( pnFlagIndex)
        *pnFlagIndex = NO_MATCH;
    if( ppszFlagName)
        *ppszFlagName = NULL;
    *ppszArgData = NULL;

    if (*pszArg != '-' && *pszArg != '/')
    {
        //
        //  If this is not a flag parameter, set the argument
        //  data pointer to point to the beginning of the
        //  argument.
        //

        *ppszArgData = pszArg;
        goto cleanup;
    }

    //
    //  Advance beyond the flag introducer.
    //

    ++pszArg;

    //
    //  Look for an separator character ('=' or ':'), marking the end of the
    //  flag text and the start of the flag data.  Only use characters before
    //  the separator character (if it is there) for matching.
    //

    pszSeparatorChar = pszArg + strcspn(pszArg,"=:");
    if (*pszSeparatorChar == 0)
        pszSeparatorChar = NULL;

    if (pszSeparatorChar == NULL)
        nCompLen = lstrlenA(pszArg);
    else
        nCompLen = (pszSeparatorChar - pszArg);

    //
    //  Abort if there is nothing to examine.
    //

    if (nCompLen <= 0)
    {
        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
    }

    // If argument is not too long copy it into a buffer.
    if( nCompLen > MAXARG)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }
    CopyMemory( szArg, pszArg, nCompLen);
    szArg[ nCompLen] = '\0';

    // Scan through the argument array looking for matches with the
    // command line argument.
    hr = _HrFindArrayValue( szArg, rgpszArgArray, nArraySize, &nMatchIndex);
    if( FAILED( hr))
        goto cleanup;

    // Found the argument so set the output parameters.
    if (pszSeparatorChar != NULL)
        *ppszArgData = pszSeparatorChar + 1;
    if( pnFlagIndex)
        *pnFlagIndex = nMatchIndex;
    if( ppszFlagName)
        *ppszFlagName = rgpszArgArray[ nMatchIndex];

cleanup:

    RETURN(hr);
}

//$--_nEcFromHr--------------------------------------------------------------
//
// DESCRIPTION:
//
// Convert an HRESULT to an exit code suitable for return
// from a console application.
//
// NOTE:
//
// EDK HRESULTS get converted to their "code" (current 1 - 6).
// Any successful HRESULT converts to zero.
// Any EDK "approved" Win32 or OLE HRESULT gets converted
// to its EDK exit code (currently 11 - 15).
// Any non-EDK approved HRESULT gets converted to EC_EDK_E_UNKNOWN
// (currently 20).  See edkcode.h for definitions.
//
// INPUT:       hr  --  HRESULT to convert
//
// RETURNS:     INT --  see note above
//
//-----------------------------------------------------------------------------
INT _nEcFromHr(      // returns INT
            IN HRESULT hr)      // HRESULT to convert to exit code
{
    INT ec  =   0;              // console application exit code

    DEBUGPUBLIC("_nEcFromHr()\n");

    // doesn't need a check function.

    if ( SCODE_FACILITY(hr) ==
         FACILITY_EDK )
    {
        // EDK HRESULT defined in edk.h
        // return HRESULT code.
        ec = SCODE_CODE(hr);
        goto cleanup;
    }

    // See if we have an "approved" EDK Win32 or OLE HRESULT
    switch ( hr )
    {
        case NOERROR:
            // Success!  Return standard "success" exit
            // code
            ec = 0;
            break;

        case E_FAIL:
            // return corresponding EDK exit code
            ec = EC_EDK_E_FAIL;
            break;

        case E_INVALIDARG:
            // return corresponding EDK exit code
            ec = EC_EDK_E_INVALIDARG;
            break;

        case E_NOTIMPL:
            // return corresponding EDK exit code
            ec = EC_EDK_E_NOTIMPL;
            break;

        case E_NOINTERFACE:
            // return corresponding EDK exit code
            ec = EC_EDK_E_NOINTERFACE;
            break;

        case E_OUTOFMEMORY:
            // return corresponding EDK exit code
            ec = EC_EDK_E_OUTOFMEMORY;
            break;

case E_ACCESSDENIED:
// return corresponding EDK exit code
ec = EC_EDK_E_ACCESSDENIED;
break;

        default:
            // Non-EDK approved HRESULT.
            // return "unknown" exit code.
            ec = EC_EDK_E_UNKNOWN;
            break;

    }   // end switch

cleanup:

    // we are done
    return ec;

}


UTL_DN.C

// --UTL_CLI.c------------------------------------------------------------------
//
// Utilities for retrieving distinguished names from Exchange server
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"
#include "utl_dn.chk"

//$--HrGetMailboxDN----------------------------------------------------------
//  Retrieves mailbox distinguished name from the current session object.
//  Example output: /O=Enterprise/OU=Site/CN=Recipients/CN=MailboxName
// -----------------------------------------------------------------------------
HRESULT HrGetMailboxDN(
    IN  LPMAPISESSION lphSession,       // current session
    OUT LPSTR *ppszMailboxDN)           // distinguished name of mailbox.
{
    HRESULT         hr              = NOERROR;
    LPMAPIPROP      lpSessionProps  = NULL;
    LPSPropValue    lpEmailAddress  = NULL;
    ULONG           cProps          = 0L;

    static SPropTagArray PropEmailAddress = { 1L, { PR_EMAIL_ADDRESS_A}};

    DEBUGPUBLIC( "HrGetMailboxDN()");

    hr = CHK_HrGetMailboxDN( lphSession, ppszMailboxDN);
    if( FAILED( hr))
        RETURN( hr);

    hr = HrOpenSessionObject( lphSession, &lpSessionProps);
    if( FAILED( hr))
        goto cleanup;

    hr = MAPICALL( lpSessionProps)->GetProps( lpSessionProps,
        &PropEmailAddress,
        0L,
        &cProps,
        &lpEmailAddress);
    if( FAILED( hr) || hr == MAPI_W_ERRORS_RETURNED)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( cProps == 1, "Unexpected number of props returned!");
    ASSERTERROR( lpEmailAddress != NULL, "Invalid EmailAddress prop");

    hr = MAPIAllocateBuffer( cbStrLenA( lpEmailAddress->Value.lpszA), (PVOID*)ppszMailboxDN);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpyA( *ppszMailboxDN, lpEmailAddress->Value.lpszA);

cleanup:
    MAPIFREEBUFFER( lpEmailAddress);

    ULRELEASE( lpSessionProps);

    RETURN( hr);
}

//$--HrGetServerDN----------------------------------------------------------
//  Retrieves server distinguished name from the current session object.
//  Example output: /O=Enterprise/OU=Site/CN=Servers/CN=ServerName/CN=NULL
// -----------------------------------------------------------------------------
HRESULT HrGetServerDN(
    IN  LPMAPISESSION lphSession,       // current session
    OUT LPSTR *ppszServerDN)            // distinguished name of server.
{
    HRESULT         hr              = NOERROR;
    LPMAPIPROP      lpSessionProps  = NULL;
    LPSPropValue    lpHomeMTA       = NULL;
    ULONG           cbDN            = 0L;
    ULONG           cProps          = 0L;
    LPSTR           lpLastCN        = NULL;

    static SPropTagArray PropHomeMTA = { 1L, { PR_EMS_AB_HOME_MTA_A}};

    DEBUGPUBLIC( "HrGetServerDN()");

    hr = CHK_HrGetServerDN( lphSession, ppszServerDN);
    if( FAILED( hr))
        RETURN( hr);

    hr = HrOpenSessionObject( lphSession, &lpSessionProps);
    if( FAILED( hr))
        goto cleanup;

    hr = MAPICALL( lpSessionProps)->GetProps( lpSessionProps,
        &PropHomeMTA,
        0L,
        &cProps,
        &lpHomeMTA);
    if( FAILED( hr) || hr == MAPI_W_ERRORS_RETURNED)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( cProps == 1, "Unexpected number of props returned!");
    ASSERTERROR( lpHomeMTA != NULL, "Invalid HomeMTA prop");

    // Search for last "/cn=" and ignore it when copying string
    lpLastCN = strrchr( lpHomeMTA->Value.lpszA, '/');
    if( lpLastCN == NULL ||
        _strnicmp( lpLastCN, "/cn=", 4) != 0)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    cbDN = lpLastCN - lpHomeMTA->Value.lpszA;

    hr = MAPIAllocateBuffer( cbDN + 1, (PVOID*)ppszServerDN);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }

    strncpy( *ppszServerDN, lpHomeMTA->Value.lpszA, cbDN);
    (*ppszServerDN)[cbDN] = '\0';

cleanup:
    MAPIFREEBUFFER( lpHomeMTA);

    ULRELEASE( lpSessionProps);

    RETURN( hr);
}


UTL_PAS.C

// --UTL_PAS.C------------------------------------------------------------------
//
// Get password from the user helper function
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "utl_pas.chk"

//$--_HrInputCommandLinePassword---------------------------------------------------------
//
//  DESCRIPTION: Input password and echo *'s.
//
//  INPUT:  dwFlags     --  reserved - must be zero
//          cbLength    --  length of buffer/password
//
//  OUTPUT: pszBuffer   --  buffer to write password into
//
//  RETURNS:    HRESULT --  NOERROR if no error,
//                          E_FAIL if user enters ctrl-c,
//                          E_INVALIDARG if bad input
//
// -----------------------------------------------------------------------------
HRESULT _HrInputCommandLinePassword(// RETURNS: HRESULT
IN DWORD dwFlags,// reserved--must be zero
IN DWORD cbLength,          // size of the buffer in bytes
OUT LPSTR pszBuffer)// buffer to write string into
{
    HRESULT     hr          = NOERROR;
    LPSTRpsz= pszBuffer;

DEBUGPUBLIC("_HrInputCommandLinePassword()\n");

    // check input parameters
    hr = CHK__HrInputCommandLinePassword(dwFlags, cbLength, pszBuffer);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    for(;;)
    {
        int ch = _getch();

// Handle Ctrl-C.

if (ch == '\003')
{
printf("^C");
hr = HR_LOG(E_FAIL);
*pszBuffer = 0;
break;
}

// Handle backspace.

if (ch == '\b' && psz > pszBuffer)
{
printf( "\b \b" );
psz--;
cbLength++;
}

// Handle normal printable character.

if (isprint(ch))
{
        printf ( "*" );
        *psz++ = ch;
        cbLength--;
}

// Handle input termination.

        if (ch == '\n' || ch == '\r' || cbLength < 2)
        {
            *psz = 0;
            break;
        }
    }

    printf ( "\n" );

RETURN(hr);

}


UTL_REG.C

// --UTL_REG.C------------------------------------------------------------------
//
// Registry key helper functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"
#include "utl_reg.chk"

//$--HrIdentifyRegistryRootKey------------------------------------------------------
//
//  DESCRIPTION:Checks that a fully qualified key name begins with one of the four
//predefined NT Registry keys: HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT,
//HKEY_CURRENT_USER, or HKEY_USERS.  The output from this function
//can be passed to registry functions like RegOpenKeyEx().
//
//INPUT:pszKey - pointer to fully qualified key name
//
//OUTPUT:phkKeyHandle - pointer to key handle
//ppszSubKey - pointer to subkey section of pszKey
//
//RETURNS:HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise,
//
//Notes:   Successful completion.  hkKey is the Registry key handle,
//pszSubKey points to the remainder of the subkey string.  Note
//that it is legitimate for pszSubKey to be NULL, meaning that
//the user is trying to access values in the root of one of the
//predefined registry keys.
//
// -----------------------------------------------------------------------------

#defineNOT_A_KEY_HANDLE(0)

HRESULT HrIdentifyRegistryRootKey(      // RETURNS: HRESULT
                                    IN  char *pszKey,   // pointer to fully qualified key name
OUT HKEY *phkKeyHandle, // pointer to key handle
OUT char **ppszSubKey)  // pointer to subkey section
{
    HRESULT     hr  =   NOERROR;
char *pszBackslashChar    =   NULL;
intnCompLen    =   0;

    DEBUGPUBLIC("HrIdentifyRegistryRootKey()");

    // check input parameters
    hr = CHK_HrIdentifyRegistryRootKey(pszKey, phkKeyHandle, ppszSubKey);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

//
//initialise
//outputs.
//

    *phkKeyHandle = NOT_A_KEY_HANDLE;
*ppszSubKey = NULL;

//
//Ignore leading backslash character.
//
//e.g. pszKey = "\HKEY_LOCAL_MACHINE\SubKey"
//

if (*pszKey == '\\')
++pszKey;

//
//Search for the next backslash in the key name
//

pszBackslashChar = strchr(pszKey,'\\');

//
//The subkey string begins with the first character
//after the backslash delimiter.  (In NT 3.5 the function
//RegCreateKeyEx() requires that subkey strings *not*
//begin with a backslash).
//

if (pszBackslashChar == NULL)
*ppszSubKey = NULL;
else
{
*ppszSubKey = pszBackslashChar + 1;

//
//Set pszSubKey to NULL if there are no characters after
//the backslash delimiter.
//
//e.g. pszKey = "HKEY_LOCAL_KEY\"
//

if (**ppszSubKey == '\0')
*ppszSubKey = NULL;
}

//
//Perform comparisons only on characters before
//the backslash delimiter.
//

if (pszBackslashChar == NULL)
nCompLen = strlen(pszKey);
else
nCompLen = (pszBackslashChar - pszKey);

//
//Exit if there is nothing to compare.
//

if (nCompLen <= 0)
{
        hr = HR_LOG(E_FAIL);

goto cleanup;
}

//
//Select on the number of characters to compare,
//then check for a string match.
//

ASSERTERROR(strlen("HKEY_LOCAL_MACHINE") == 18,"Invalid string length");
ASSERTERROR(strlen("HKEY_CLASSES_ROOT") == 17,"Invalid string length");
ASSERTERROR(strlen("HKEY_CURRENT_USER") == 17,"Invalid string length");
ASSERTERROR(strlen("HKEY_USERS") == 10,"Invalid string length");

switch(nCompLen)
{
case 18 :

if (strncmp(pszKey,"HKEY_LOCAL_MACHINE",nCompLen) == 0)
*phkKeyHandle = HKEY_LOCAL_MACHINE;
else
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

break;

case 17 :

if (strncmp(pszKey,"HKEY_CLASSES_ROOT",nCompLen) == 0)
*phkKeyHandle = HKEY_CLASSES_ROOT;
else
if (strncmp(pszKey,"HKEY_CURRENT_USER",nCompLen) == 0)
*phkKeyHandle = HKEY_CURRENT_USER;
else
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
}

break;

case 10 :

if (strncmp(pszKey,"HKEY_USERS",nCompLen) == 0)
*phkKeyHandle = HKEY_USERS;
else
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

break;

default :

            hr = HR_LOG(E_FAIL);

            goto cleanup;
}

cleanup:

if ( FAILED(hr) && (hr != E_INVALIDARG) )
{
*phkKeyHandle = NOT_A_KEY_HANDLE;
*ppszSubKey = NULL;
}

    RETURN(hr);

}

//$--_HrReadRegistrySZ--------------------------------------------------------
//
//  DESCRIPTION :Read a string from the registry.
//
//  INPUT:  hRegistryKey    --  registry key to read value from
//          lpszValueName   --  name of value to read
//
//  INPUT OUTPUT:   lpcbBufferSize  --  size of buffer for value and size of value
//
//  OUTPUT: lpszBuffer      -- buffer to read value into
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          EDK_E_NOT_FOUND if value does not exist
//                          E_FAIL if failure due to other error
//
// -----------------------------------------------------------------------------

HRESULT _HrReadRegistrySZ(// RETURNS: HRESULT
IN HKEY hRegistryKey,// registry key to read value from
IN LPSTR lpszValueName,// name of value to read
OUT LPSTR lpszBuffer,// buffer to read value into
IN OUT DWORD *lpcbBufferSize)// size of buffer to read value into
{
    HRESULT     hr          = NOERROR;
DWORDdwStatus= ERROR_SUCCESS;
DWORDdwType= 0;

DEBUGPUBLIC("_HrReadRegistrySZ()");

    // Check input parameters
    hr = CHK__HrReadRegistrySZ(hRegistryKey, lpszValueName, lpszBuffer, lpcbBufferSize);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

dwStatus = RegQueryValueEx(
hRegistryKey,
lpszValueName,
NULL,
&dwType,
(LPBYTE)lpszBuffer,
lpcbBufferSize);

if (dwStatus != ERROR_SUCCESS || dwType != REG_SZ)
{
        if(dwStatus != ERROR_SUCCESS)
        {
            hr = HR_LOG(HRESULT_FROM_WIN32(dwStatus));
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }

        goto cleanup;
}

cleanup:

    RETURN(hr);

}

//$--_HrReadRegistryDWORD-----------------------------------------------------
//
//  DESCRIPTION: Read a DWORD integer from the registry.
//
//  INPUT:  hRegistryKey    --  registry key to read value from
//          lpszValueName   --  name of value to read
//
//  OUTPUT: lpdwBuffer  --  DWORD buffer to read value into
//
//  RETURNS:    HRESULT --  NOERROR is success,
//                          E_INVALIDARG if bad input
//                          EDK_E_NOT_FOUND if value does not exist
//                          E_FAIL if failure due to other error
//
// -----------------------------------------------------------------------------
HRESULT _HrReadRegistryDWORD(// RETURNS: HRESULT
IN HKEY hRegistryKey,// registry key to read value from
IN LPSTR lpszValueName,// name of value to read
OUT LPDWORD lpdwBuffer)// address of DWORD to read value into
{
    HRESULT     hr              = NOERROR;
DWORDdwStatus= ERROR_SUCCESS;
DWORDdwType= 0;
DWORDcbBufferSize= sizeof(DWORD);

DEBUGPUBLIC("_HrReadRegistryDWORD()");

    // Check input parameters.
    hr = CHK__HrReadRegistryDWORD(hRegistryKey, lpszValueName, lpdwBuffer);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

dwStatus = RegQueryValueEx(
hRegistryKey,
lpszValueName,
NULL,
&dwType,
(LPBYTE)lpdwBuffer,
&cbBufferSize);

if (dwStatus != ERROR_SUCCESS || dwType != REG_DWORD)
{
        if(dwStatus != ERROR_SUCCESS)
        {
            hr = HR_LOG(HRESULT_FROM_WIN32(dwStatus));
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }

        goto cleanup;
}

cleanup:

    RETURN(hr);

}

//$--_HrWriteRegistrySZ-------------------------------------------------------
//
//  DESCRIPTION: Write a string to the registry.
//
//  INPUT:  hRegistryKey    --  registry key to write value to
//          lpszValueName   --  name of value to write
//          lpszValue       --  string value to write
//
//  RETURNS:    HRESULT --  NOERROR if success
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
HRESULT _HrWriteRegistrySZ(// RETURNS: HRESULT
IN HKEY hRegistryKey,// registry key to write value to
IN LPSTR lpszValueName,// name of value to write
IN LPCSTR lpszValue)// string value to write
{
    HRESULT     hr          = NOERROR;
DWORDdwStatus= ERROR_SUCCESS;

DEBUGPUBLIC("_HrWriteRegistrySZ()");

    // Check input parameters
    hr = CHK__HrWriteRegistrySZ(hRegistryKey, lpszValueName, lpszValue);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

dwStatus = RegSetValueEx(
hRegistryKey,
lpszValueName,
0L,
REG_SZ,
(LPBYTE)lpszValue,
(lstrlen(lpszValue) + 1) * sizeof(CHAR));

if (dwStatus != ERROR_SUCCESS)
{
        hr = HR_LOG(HRESULT_FROM_WIN32(dwStatus));

        goto cleanup;
}

cleanup:

    RETURN(hr);

}

//$--_HrWriteRegistryDWORD----------------------------------------------------
//
//  DESCRIPTION: Write a DWORD integer to the registry.
//
//  INPUT:  hRegistryKey    --  registry key to write value to
//          lpszValueName   --  name of value to write
//          dwValue         --  value to write
//
//  RETURNS:    HRESULT --  NOERROR is success,
//              E_INVALIDARG if bad input
//              E_FAIL otherwise
//
// -----------------------------------------------------------------------------

HRESULT _HrWriteRegistryDWORD(// RETURNS: HRESULT
IN HKEY hRegistryKey,// registry key to write value to
IN LPSTR lpszValueName,// name of value to write
IN DWORD dwValue)// DWORD value to write
{
    HRESULT     hr          = NOERROR;
DWORDdwStatus= ERROR_SUCCESS;

DEBUGPUBLIC("_HrWriteRegistryDWORD()");

    // Check input parameters
    hr = CHK__HrWriteRegistryDWORD(hRegistryKey, lpszValueName, dwValue);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

dwStatus = RegSetValueEx(
hRegistryKey,
lpszValueName,
0L,
REG_DWORD,
(LPBYTE)&dwValue,
sizeof(dwValue));

if (dwStatus != ERROR_SUCCESS)
{
        hr = HR_LOG(HRESULT_FROM_WIN32(dwStatus));

        goto cleanup;
}

cleanup:

    RETURN(hr);

}


UTL_RPC.C

// --edkmidl.c------------------------------------------------------------------
//
// MIDL memory allocation functions.
//
// Copyright (C) Microsoft Corp. 1986-1996, All rights reserved.
//
// ----------------------------------------------------------------------------

#include "edk.h"

void __RPC_FAR * __RPC_API midl_user_allocate(size_t cb)
{
void *pv;

pv = GlobalAlloc(GMEM_FIXED, cb);
if (!pv)
RpcRaiseException(RPC_S_OUT_OF_MEMORY);

return pv;
}

void __RPC_API midl_user_free(void __RPC_FAR * pv)
{
GlobalFree(pv);
}


UTL_STR.C

// --UTL_STR.C------------------------------------------------------------------
//
// String conversion helper functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"
#include "utl_str.chk"

//$--HrStrAToStrW---------------------------------------------------------------
//  Convert a byte string to a word string.  The resulting string is placed in
//  a buffer allocated using MAPIAllocateBuffer.
// -----------------------------------------------------------------------------
HRESULT HrStrAToStrW(
    IN  LPCSTR          lpszSource,     // source string
    OUT LPWSTR *        lppwszDest)     // destination string
{
    HRESULT             hr              = NOERROR;
    LONG                cchDest         = 0;
    LPWSTR              lpwszDest       = NULL;

    DEBUGPUBLIC("HrStrAToStrW()");

    hr = CHK_HrStrAToStrW(lpszSource, lppwszDest);
    if (FAILED(hr))
        RETURN(hr);

    // Compute the number of word characters needed for the destination buffer.

    if (*lpszSource)
    {
        cchDest = MultiByteToWideChar(
            CP_ACP,
            MB_PRECOMPOSED,
            lpszSource,
            strlen(lpszSource),
            NULL,
            0);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // Allocate the destination string buffer.

    hr = MAPIAllocateBuffer((cchDest + 1) * sizeof(WCHAR), &lpwszDest);
    if (FAILED(hr))
        goto cleanup;

    // Convert from byte string to word string.

    if (*lpszSource)
    {
        cchDest = MultiByteToWideChar(
            CP_ACP,
            MB_PRECOMPOSED,
            lpszSource,
            strlen(lpszSource),
            lpwszDest,
            cchDest);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    lpwszDest[cchDest] = 0;

    // Store in output parameter.

    *lppwszDest = lpwszDest;

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpwszDest);
    }

    RETURN(hr);
}

//$--HrStrWToStrA---------------------------------------------------------------
//  Convert a word string to a byte string.  The resulting string is placed in
//  a buffer allocated using MAPIAllocateBuffer.
// -----------------------------------------------------------------------------
HRESULT HrStrWToStrA(
    IN  LPCWSTR         lpwszSource,    // source string
    OUT LPSTR *         lppszDest)      // destination string
{
    HRESULT             hr              = NOERROR;
    LONG                cchDest         = 0;
    LPSTR               lpszDest        = NULL;

    DEBUGPUBLIC("HrStrWToStrA()");

    hr = CHK_HrStrWToStrA(lpwszSource, lppszDest);
    if (FAILED(hr))
        RETURN(hr);

    // Compute the number of byte characters needed for the destination buffer.

    if (*lpwszSource)
    {
        cchDest = WideCharToMultiByte(
            CP_ACP,
            WC_COMPOSITECHECK | WC_SEPCHARS,
            lpwszSource,
            wcslen(lpwszSource),
            NULL,
            0,
            NULL,
            NULL);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    // Allocate the destination string buffer.

    hr = MAPIAllocateBuffer(cchDest + 1, &lpszDest);
    if (FAILED(hr))
        goto cleanup;

    // Convert from word string to byte string.

    if (*lpwszSource)
    {
        cchDest = WideCharToMultiByte(
            CP_ACP,
            WC_COMPOSITECHECK | WC_SEPCHARS,
            lpwszSource,
            wcslen(lpwszSource),
            lpszDest,
            cchDest,
            NULL,
            NULL);
        if (cchDest == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    lpszDest[cchDest] = 0;

    // Store in output parameter.

    *lppszDest = lpszDest;

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpszDest);
    }

    RETURN(hr);
}

//$--HrStrAToStrA---------------------------------------------------------------
//  Create an allocated copy of a byte string using MAPIAllocateBuffer.
//  This is useful for creating macros involving CHAR strings.
// -----------------------------------------------------------------------------
HRESULT HrStrAToStrA(
    IN  LPCSTR          lpszSource,     // source string
    OUT LPSTR *         lppszDest)      // destination string
{
    HRESULT             hr              = NOERROR;
    LPSTR               lpszDest        = NULL;

    DEBUGPUBLIC("HrStrAToStrA()");

    hr = CHK_HrStrAToStrA(lpszSource, lppszDest);
    if (FAILED(hr))
        RETURN(hr);

    // Allocate the destination buffer and copy the string.

    hr = MAPIAllocateBuffer(strlen(lpszSource) + 1, &lpszDest);
    if (FAILED(hr))
        goto cleanup;

    strcpy(lpszDest, lpszSource);

    // Store in output parameter.

    *lppszDest = lpszDest;

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpszDest);
    }

    RETURN(hr);
}

//$--HrStrWToStrW---------------------------------------------------------------
//  Create an allocated copy of a word string using MAPIAllocateBuffer.
//  This is useful for creating macros involving CHAR strings.
// -----------------------------------------------------------------------------
HRESULT HrStrWToStrW(
    IN  LPCWSTR         lpwszSource,    // source string
    OUT LPWSTR *        lppwszDest)     // destination string
{
    HRESULT             hr              = NOERROR;
    LPWSTR              lpwszDest       = NULL;

    DEBUGPUBLIC("HrStrWToStrW()");

    hr = CHK_HrStrWToStrW(lpwszSource, lppwszDest);
    if (FAILED(hr))
        RETURN(hr);

    // Allocate the destination buffer and copy the string.

    hr = MAPIAllocateBuffer(
        (wcslen(lpwszSource) + 1) * sizeof(WCHAR), &lpwszDest);
    if (FAILED(hr))
        goto cleanup;

    wcscpy(lpwszDest, lpwszSource);

    // Store in output parameter.

    *lppwszDest = lpwszDest;

cleanup:
    if (FAILED(hr))
    {
        MAPIFREEBUFFER(lpwszDest);
    }

    RETURN(hr);
}


VSOF.C

//--vsof.c---------------------------------------------------------------------
//
// This file contains methods and code for implementing an OLE 2.0
// storage stream (as defined in the OLE 2.0 specs) on top of OS file.
//
// Copyright 1986-1996.  Microsoft Corporation.  All Rights Reserved.
//
// Notes:
//
// Specifying STGM_DELETEONRELEASE makes the file opened a temporary file (It
// will be deleted when the stream is released).
//
// Specifying STGM_SHARE_EXCLUSIVE makes the file writable by only one application
// at a time.
//
//-----------------------------------------------------------------------------
#include "edk.h"
#include "_vsof.h"
#include "vsof.chk"

//$--ComputeShareFlags---------------------------------------------------------------
//
// DESCRIPTION:computes the file share flags based on the stream share flags.
//
// INPUT:ulFlags--stream flags
//
// RETURNS:DWORD--file share flags
//
//-----------------------------------------------------------------------------
static __inline DWORD dwComputeShareFlags(
IN ULONG ulFlags)// stream flags
{
// By default, the file created is shareable for reading & writing
DWORDfdwShare=FILE_SHARE_READ | FILE_SHARE_WRITE;

// Determine if concurrent access to file is restricted
if ( ulFlags & STGM_SHARE_EXCLUSIVE )
{
fdwShare = 0;// file not to be shared at all
}

else
{
if ( ulFlags & STGM_SHARE_DENY_WRITE )
{
fdwShare &= ~FILE_SHARE_WRITE;// don't allow sharing on writes
}

if ( ulFlags & STGM_SHARE_DENY_READ )
{
fdwShare &= ~FILE_SHARE_READ;// don't allow sharing on reads
}
}// end if not exclusive access file

// We are done

return fdwShare;

}

//$--ScGetVSOFFilename---------------------------------------------------------------
//
// DESCRIPTION:Get virtual stream on file file name
//
// INPUT:lpszPath--file path
//lpszPrefix--file prefix
//lpszBuf--file name output buffer
//
// RETURNS:HRESULT--NOERROR if sucessful,
//E_INVALIDARG if bad input,
//E_FAIL otherwise
//
//-----------------------------------------------------------------------------
HRESULT ScGetVSOFFilename (
IN LPSTR lpszPath, // file path
IN LPSTR lpszPrefix, // file name prefix
IN LPSTR lpszBuf)// file name output buffer
{
HRESULThr=NOERROR;
CHARrgchPath[MAX_PATH]= {0};

DEBUGPRIVATE("ScGetVSOFFilename()\n");

// check input parameters
hr = CHK_ScGetVSOFFilename(lpszPath, lpszPrefix, lpszBuf);

if ( FAILED(hr) )
{
RETURN(hr);
}

if (lpszPrefix == NULL)
{
lpszPrefix = VSOF_DEFAULT_PREFIX;
}

if (!lpszPath)
{
if (!GetTempPath (MAX_PATH, rgchPath))
{
//Init the Path to the current directory (".")
//
rgchPath[0] = '.';
rgchPath[1] = 0;
}
lpszPath = rgchPath;
}

if (!GetTempFileName32 (lpszPath, lpszPrefix, 0, lpszBuf))
{
hr = HR_LOG(E_FAIL);

goto cleanup;
}

cleanup:

RETURN(hr);

}

//$--MakeReal------------------------------------------------------------------
//
// DESCRIPTION:Create a "real" temporary file.
//
// INPUT:vhFile--virtual file handle
//
// RETURNS:BOOL
//
// NOTES:This function is only called when it is necessary to make a in-buffer
// temporary file real.
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL MakeReal(// RETURNS: BOOL
IN VSOF_HANDLE vhFile)// virtual file handle
{
HRESULThr =NOERROR;
BOOLfMadeReal=FALSE;// TRUE if file made real
    charszPath[MAX_PATH]="";
    char *lpszPath=NULL;
    char   szPrefix[MAX_PATH]="";
    char *lpszPrefix=NULL;
    char *p=NULL;
    DWORD dwSizeWritten=0;
DWORDfdwShare=0;// share flags for created file

const DWORDfdwAttr=FILE_ATTRIBUTE_TEMPORARY |
FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN;;

DEBUGPRIVATE("MakeReal()\n");

    if (lstrlen(vhFile->rgchFile) >= MAX_PATH)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpy(szPath, vhFile->rgchFile);

    p = lstrlen(vhFile->rgchFile)+vhFile->rgchFile+1;
    if (lstrlen(p) >= MAX_PATH)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }
    lstrcpy(szPrefix, p);
    lpszPath = szPath;
    lpszPrefix = szPrefix;

    if (!lstrlen(szPath))
        lpszPath = NULL;
    if (!lstrlen(szPrefix))
        lpszPrefix = NULL;

    if (ScGetVSOFFilename (lpszPath, lpszPrefix, vhFile->rgchFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Determine whether this file will have concurrent access.
fdwShare = dwComputeShareFlags(vhFile->ulFlags);

// Create a temporary file for writing (this function is only
// called for temporary files!)
    vhFile->hf = CreateFile (vhFile->rgchFile,
                             GENERIC_READ | GENERIC_WRITE,
 fdwShare,
                             NULL,
                             CREATE_ALWAYS,
     fdwAttr,
                             NULL);

if (vhFile->hf == INVALID_HANDLE_VALUE)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    if (vhFile->ulFileSize)
    {
        if (!WriteFile(vhFile->hf, vhFile->lpvVBuff, vhFile->ulFileSize,
                       &dwSizeWritten, NULL))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
        else if (dwSizeWritten != vhFile->ulFileSize)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
    }

    if (SetFilePointer(vhFile->hf, vhFile->ulFilePointer, NULL, FILE_BEGIN) ==
        0xFFFFFFFF)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// The file is now real.
fMadeReal = TRUE;

// We are done.

cleanup:

    return fMadeReal;

}

//$--VSOF_SetFilePointer-------------------------------------------------------
//
// DESCRIPTION:performs seek
//
// INPUT:vhFile--virtual file handle
//lDistanceToMove--distance to move
//lpDistanceToMoveHigh-- pointer to 64-bit distance to move
//dwMoveMethod--origin
//
// RETURNS:DWORD-- new file position or error code
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
DWORD VSOF_SetFilePointer(// RETURNS: DWORD
IN VSOF_HANDLE vhFile,// file handle
    IN LONG lDistanceToMove,// distance to move
    IN PLONG lpDistanceToMoveHigh,// pointer to 64 bit distance to move
    IN DWORD dwMoveMethod// origin
    )
{
HRESULThr=NOERROR;
DWORDposNew=SET_FILE_POINTER_ERROR;// error code

DEBUGPRIVATE("VSOF_SetFilePointer()\n");

// check input parameters
hr = CHK_VSOF_SetFilePointer(vhFile, lDistanceToMove, lpDistanceToMoveHigh,
                             dwMoveMethod);

if ( FAILED(hr) )
{
SetLastError(ERROR_INVALID_PARAMETER);

return SET_FILE_POINTER_ERROR;
}

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

    if (lpDistanceToMoveHigh && *lpDistanceToMoveHigh)
    {
        if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
        else
{
            goto NonVirtual;
}
    }

    switch (dwMoveMethod)
    {
    case FILE_BEGIN:
        if (lDistanceToMove >= (long)vhFile->dwVirtualBufferSize)
        {
            if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
            else
{
                goto NonVirtual;
}
        }
        else if (lDistanceToMove < 0)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
hr = HR_LOG(E_FAIL);
goto cleanup;
        }
        vhFile->ulFilePointer = lDistanceToMove;
        break;
    case FILE_CURRENT:
        if ((lDistanceToMove+(long)vhFile->ulFilePointer) >= (long)vhFile->dwVirtualBufferSize)
        {
            if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
            else
{
                goto NonVirtual;
}
        }
        else if ((lDistanceToMove+(long)vhFile->ulFilePointer) < 0)
        {
hr = HR_LOG(E_FAIL);
            SetLastError(ERROR_INVALID_PARAMETER);
goto cleanup;
        }
        vhFile->ulFilePointer = lDistanceToMove+(long)vhFile->ulFilePointer;
        break;
    case FILE_END:
        if ((lDistanceToMove+(long)vhFile->ulFileSize) >= (long)vhFile->dwVirtualBufferSize)
        {
            if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
            else
{
                goto NonVirtual;
}
        }
        else if ((lDistanceToMove+(long)vhFile->ulFileSize) < 0)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
hr = HR_LOG(E_FAIL);
goto cleanup;
        }
        vhFile->ulFilePointer = lDistanceToMove + (long)vhFile->ulFileSize;
        break;
    default:
        SetLastError(ERROR_INVALID_PARAMETER);
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

// Return new file position.
posNew = vhFile->ulFilePointer;

goto cleanup;

NonVirtual:
// real file
    posNew = SetFilePointer(
    vhFile->hf,
        lDistanceToMove,
        lpDistanceToMoveHigh,
        dwMoveMethod);

goto cleanup;

cleanup:

return posNew;

}

//$--VSOF_SetEndOfFile---------------------------------------------------------
//
// DESCRIPTION:set end of file
//
// INPUT:vhFile--virtual file handle
//
// RETURNS:BOOL
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL VSOF_SetEndOfFile(// RETURNS BOOL
    IN VSOF_HANDLE vhFile// virtual file handle
    )
{
BOOLfSet=FALSE;// TRUE if successful

DEBUGPRIVATE("VSOF_SetEndOfFile()\n");

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

    vhFile->ulFileSize = vhFile->ulFilePointer;

fSet = TRUE;

    goto cleanup;

NonVirtual:
// real file
    fSet = SetEndOfFile(vhFile->hf);

goto cleanup;

cleanup:

return fSet;

}

//$--VSOF_FlushFileBuffers-----------------------------------------------------
//
// DESCRIPTION:Flushes buffered data to file.
//
// INPUT:vhFile--virtual file handle
//
// RETURNS:BOOL-- TRUE if successful, FALSE otherwise.
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL VSOF_FlushFileBuffers(// RETURNS: BOOL
    IN VSOF_HANDLE vhFile// virtual file handle
    )
{
HRESULThr=NOERROR;
BOOLfFlushed=FALSE;// TRUE if flushed

DEBUGPRIVATE("VSOF_FlushFileBuffers()\n");

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}
    if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);

goto cleanup;
}

// Fall through!

NonVirtual:
// real file
    fFlushed = FlushFileBuffers(vhFile->hf);

goto cleanup;

cleanup:

return fFlushed;

}

//$--VSOF_ReadFile-------------------------------------------------------------
//
// DESCRIPTION:Read from file/buffer
//
// INPUT:vhFile--virtual file handle
//lpBuffer--output buffer
//nNumberOfBytesToRead--# bytes to read
//lpNumberOfBytesRead--# bytes read
//lpOverlapped
//
// RETURNS:BOOL
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL VSOF_ReadFile(// RETURNS: BOOL
    IN VSOF_HANDLE vhFile,// virtual file handle
    IN LPVOID lpBuffer,// output buffer
    IN DWORD nNumberOfBytesToRead,// # bytes to read
    OUT LPDWORD lpNumberOfBytesRead,// # bytes read
    OUT LPOVERLAPPED lpOverlapped
    )
{
HRESULThr=NOERROR;
BOOLfRead=FALSE;// TRUE if successful

DEBUGPRIVATE("VSOF_ReadFile()");

hr = CHK_VSOF_ReadFile(vhFile, lpBuffer, nNumberOfBytesToRead,
                       lpNumberOfBytesRead, lpOverlapped);

if ( FAILED(hr) )
{
SetLastError(ERROR_INVALID_PARAMETER);

return FALSE;
}

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

// Initialize output parameters
*lpNumberOfBytesRead = 0;
if ( lpOverlapped != NULL )
{
ZeroMemory(lpOverlapped, sizeof(OVERLAPPED));
}

    if (!nNumberOfBytesToRead)
    {
        *lpNumberOfBytesRead = 0;
        fRead = TRUE;
goto cleanup;
    }

    if (vhFile->ulFilePointer >= vhFile->ulFileSize)
    {
        *lpNumberOfBytesRead = 0;
fRead = TRUE;// EOF for synchronous read
goto cleanup;
    }

    // Take which ever is less.
    *lpNumberOfBytesRead = nNumberOfBytesToRead <
        (vhFile->ulFileSize - vhFile->ulFilePointer) ?
        nNumberOfBytesToRead : (vhFile->ulFileSize - vhFile->ulFilePointer);

    CopyMemory(lpBuffer, ((unsigned char*)vhFile->lpvVBuff) +
               vhFile->ulFilePointer,
               *lpNumberOfBytesRead);

    vhFile->ulFilePointer += *lpNumberOfBytesRead;  // update file pointer.

fRead = TRUE;

goto cleanup;

NonVirtual:
// real file
    fRead = ReadFile(
    vhFile->hf,
        lpBuffer,
        nNumberOfBytesToRead,
        lpNumberOfBytesRead,
        lpOverlapped);

goto cleanup;

cleanup:

return fRead;

}

//$--VSOF_WriteFile------------------------------------------------------------
//
// DESCRIPTION:Writes buffer to file.
//
// INPUT:vhFile--file handle
//lpBuffer--input buffer
//nNumberOfBytesToWrite--# bytes to write
//lpNumberOfBytesWritten--# bytes written
//lpOverlapped--overlapped
//
// RETURNS:BOOL--TRUE if successful
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL VSOF_WriteFile(// RETURNS: BOOL
    IN VSOF_HANDLE vhFile,// file handle
    IN LPCVOID lpBuffer,// input buffer
    IN DWORD nNumberOfBytesToWrite,// # bytes to write
    OUT LPDWORD lpNumberOfBytesWritten,// # bytes written
    OUT LPOVERLAPPED lpOverlapped// overlapped
    )
{
HRESULThr=NOERROR;
BOOLfWritten=FALSE;// TRUE if successful

DEBUGPRIVATE("VSOF_WriteFile()\n");

// check input parameters
hr = CHK_VSOF_WriteFile(vhFile, lpBuffer, nNumberOfBytesToWrite,
                        lpNumberOfBytesWritten, lpOverlapped);

if ( FAILED(hr) )
{
SetLastError(ERROR_INVALID_PARAMETER);

return FALSE;
}

// Initialize output parameters
*lpNumberOfBytesWritten = 0;
if ( lpOverlapped != NULL )
{
ZeroMemory(lpOverlapped, sizeof(OVERLAPPED));
}

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

    if ((nNumberOfBytesToWrite+vhFile->ulFilePointer) >= vhFile->dwVirtualBufferSize)
    {
        if (!MakeReal(vhFile))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

        goto NonVirtual;
    }

    CopyMemory(((unsigned char *)vhFile->lpvVBuff)+vhFile->ulFilePointer, lpBuffer,
               nNumberOfBytesToWrite);

    *lpNumberOfBytesWritten = nNumberOfBytesToWrite;

    vhFile->ulFilePointer += nNumberOfBytesToWrite;  // Update file pointer.
    vhFile->ulFileSize = vhFile->ulFilePointer;      // Update file size.

fWritten = TRUE;

goto cleanup;

NonVirtual:
// real file
    fWritten = WriteFile(
    vhFile->hf,
        lpBuffer,
        nNumberOfBytesToWrite,
        lpNumberOfBytesWritten,
        lpOverlapped);

goto cleanup;

cleanup:

return fWritten;

}

//$--VSOF_GetFileSize----------------------------------------------------------
//
// DESCRIPTION:Gets the file's files
//
// INPUT:vhFile--file handle
//
// OUTPUT:lpFileSizeHigh--pointer to 64-bit file size
//
// RETURNS:DWORD--file size
//
//-----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
DWORD VSOF_GetFileSize(// RETURNS: DWORD
    IN VSOF_HANDLE vhFile,// file handle
    OUT LPDWORD lpFileSizeHigh// high file size
    )
{
HRESULThr=NOERROR;
DWORDdwFileSize=0;// file size

DEBUGPRIVATE("VSOF_GetFileSize()\n");

hr = CHK_VSOF_GetFileSize(vhFile, lpFileSizeHigh);

if ( FAILED(hr) )
{
SetLastError(ERROR_INVALID_PARAMETER);

return 0;
}

    if (vhFile->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

    if (lpFileSizeHigh)
{
        *lpFileSizeHigh = 0;
}

dwFileSize = vhFile->ulFileSize;

goto cleanup;

NonVirtual:
// real file
    dwFileSize = GetFileSize(
    vhFile->hf,
        lpFileSizeHigh);

goto cleanup;

cleanup:

return dwFileSize;

}

//$--VSOF_CloseHandle----------------------------------------------------------
//
// DESCRIPTION:Close file.
//
// INPUT:vhObject--file handle
//
// RETURNS:BOOL--TRUE if successful.
//
//----------------------------------------------------------------------------
#ifndef DEBUG
__inline
#endif
BOOL VSOF_CloseHandle(// RETURNS: BOOL
    VSOF_HANDLE vhObject// file handle
    )
{
BOOLfClosed=FALSE;// TRUE if successful

DEBUGPRIVATE("VSOF_CloseHandle()\n");

    if (vhObject->hf != INVALID_HANDLE_VALUE)
{
        goto NonVirtual;
}

    fClosed = TRUE;

goto cleanup;

NonVirtual:
// real file
    fClosed = CloseHandle(vhObject->hf);

goto cleanup;

cleanup:

return fClosed;

}

// Special virtual stream interface extensions for performance...
// There are certain situations where knowing if the stream has changed can be
// used to make desicions that will improve performance.

//$--VSOF_SetClean-------------------------------------------------------------
//
// DESCRIPTION:Unsets stream dirty flag
//
// INPUT:lpStream--stream pointer
//
// RETURNS:HRESULT--NOERROF if successful,
//E_INVALIDARG if bad input.
//
//-----------------------------------------------------------------------------
HRESULT VSOF_SetClean(
IN LPSTREAM lpStream)// stream pointer
{
HRESULThr=NOERROR;

// check input parameters
hr = CHK_VSOF_SetClean(lpStream);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Unset stream dirty flag
    if (((LPVSOF)lpStream)->lpVSOFOrig)
        ((LPVSOF)lpStream)->lpVSOFOrig->fDirty = FALSE;
    else
        ((LPVSOF)lpStream)->fDirty = FALSE;

// we are done

RETURN(hr);

}

//$--VSOF_IsDirty-------------------------------------------------------------
//
// DESCRIPTION:Returns stream dirty flag
//
// INPUT:lpStream--stream pointer
//
// RETURNS:HRESULT--NOERROF if successful,
//E_INVALIDARG if bad input.
//
//-----------------------------------------------------------------------------
HRESULT VSOF_IsDirty(// RETURNS:BOOL
IN LPSTREAM lpStream,// stream pointer
OUT BOOL * pfDirty)// dirty flag pointer
{
HRESULThr=NOERROR;

// check input parameters
hr = CHK_VSOF_IsDirty(lpStream, pfDirty);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Return dirty flag
    if (((LPVSOF)lpStream)->lpVSOFOrig)
{
        *pfDirty = ((LPVSOF)lpStream)->lpVSOFOrig->fDirty;
}
    else
{
        *pfDirty = ((LPVSOF)lpStream)->fDirty;
}

// we are done

RETURN(hr);

}

//$--VSOF_SetDirty-------------------------------------------------------------
//
// DESCRIPTION:Sets stream dirty flag macro
//
// INPUT:lpStream--stream pointer
//
// RETURNS:VOID
//
//-----------------------------------------------------------------------------
static __inline VOID VSOF_SetDirty(// RETURNS:VOID
IN LPVSOF lpVSOF)// Virtual Stream On File pointer
{
// consistency checking
ASSERT_WRITE_PTR(lpVSOF, sizeof(VSOF), "Bad lpVSOF");

// Set dirty flag
    if (lpVSOF->lpVSOFOrig)
        lpVSOF->lpVSOFOrig->fDirty = TRUE;
    else
        lpVSOF->fDirty = TRUE;
}

/* Unimplemented methods. */

//$--VSOF_Revert---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Revert()
//
// INPUT:Same as for IStream::Revert()
//
// RETURNS:HRESULT--STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Revert(
IN LPVSOF lpVSOF)// this
{
HRESULThr=NOERROR;

    DEBUGPUBLIC("VSOF_Revert()\n");

// Check input parameters
hr = CHK_VSOF_Revert(lpVSOF);

if ( FAILED(hr) )
{
RETURN(hr);
}

RETURN(STG_E_INVALIDFUNCTION);

}

//$--VSOF_LockRegion---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::LockRegion()
//
// INPUT:Same as for IStream::LockRegion()
//
// RETURNS:HRESULT--STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_LockRegion (
IN LPVSOF lpVSOF, // this
IN ULARGE_INTEGER uliOffset, // offset
IN ULARGE_INTEGER uliSize, // size
IN DWORD dwLockType)// lock type
{
HRESULThr=NOERROR;

    DEBUGPUBLIC("VSOF_LockRegion()\n");

hr = CHK_VSOF_LockRegion(lpVSOF, uliOffset, uliSize, dwLockType);

if ( FAILED(hr) )
{
RETURN(hr);
}

RETURN(STG_E_INVALIDFUNCTION);

}

//$--VSOF_UnlockRegion---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::UnlockRegion()
//
// INPUT:Same as for IStream::UnlockRegion()
//
// RETURNS:HRESULT--STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_UnlockRegion (
IN LPVSOF lpVSOF, // this
IN ULARGE_INTEGER uliOffset, // offset
IN ULARGE_INTEGER uliSize, // length
IN DWORD dwLockType)// lock type
{
HRESULThr=NOERROR;

    DEBUGPUBLIC("VSOF_UnlockRegion()\n");

hr = CHK_VSOF_UnlockRegion(lpVSOF, uliOffset, uliSize, dwLockType);

if ( FAILED(hr) )
{
RETURN(hr);
}

RETURN(STG_E_INVALIDFUNCTION);

}

// IUnknown methods:

//$--VSOF_QueryInterface---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::QueryInterface()
//
// INPUT:Same as for IStream::QueryInterface()
//
// RETURNS:HRESULT--NOERRORif VSOF object supports interface,
//E_NOINTERFACE if VSOF object doesn't support interface,
//E_INVALIDARG if bad input.
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_QueryInterface(
IN LPVSOF lpVSOF, // this
IN REFIID riid, // interface identifier reference
OUT LPVOID FAR * lppvObj)// interface object pointer
{
HRESULThr=NOERROR;

    DEBUGPUBLIC("VSOF_QueryInterface()\n");

// check input parameters
hr = CHK_VSOF_QueryInterface(lpVSOF, riid, lppvObj);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialize output parameters
*lppvObj = NULL;

if ( (IsEqualIID(riid, &IID_IUnknown) == FALSE) &&
 ( IsEqualIID(riid, &IID_IStream) == FALSE) )
{
hr = HR_LOG(E_NOINTERFACE);

goto cleanup;
}

lpVSOF->lcInit++;
*lppvObj = lpVSOF;

// we are done.

cleanup:

RETURN(hr);

}

//$--VSOF_AddRef---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::AddRef()
//
// INPUT:this
//
// RETURNS:ULONG--reference count
//
//-----------------------------------------------------------------------------
STDMETHODIMP_(ULONG) VSOF_AddRef(// RETURNS: ULONG
IN LPVSOF lpVSOF)// this
{
HRESULThr=NOERROR;

DEBUGPUBLIC("VSOF_AddRef()\n");

hr = CHK_VSOF_AddRef(lpVSOF);

if ( FAILED(hr) )
{
return 0;
}

return (++lpVSOF->lcInit);
}

//$--VSOF_Clone---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Clone()
//
// INPUT:this,
//
// OUTPUT:virtual stream pointer
//
// RETURNS:HRESULT--NOERROR if successful,
//E_INVALIDARG if bad input,
//E_ACCESSDENIED if no access,
//EDK_E_NOT_FOUND if file not found,
//E_FAIL otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Clone (
IN LPVSOF lpVSOF, // this
OUT LPSTREAM FAR *lppStm)// virtual stream pointer
{
    HRESULThr =NOERROR;
    LPVSOFlpVSOFClone=NULL;
DWORDfdwAccess=0;
DWORDfdwCreate=0;
    DWORDfdwShare=0;
    DWORDfdwAttr=0;
    LARGE_INTEGERli ={0};

DEBUGPUBLIC("VSOF_Clone()\n");

// check input parameters
hr = CHK_VSOF_Clone(lpVSOF, lppStm);

if ( FAILED(hr) )
{
RETURN(hr);
}

// initialize output parameters
    *lppStm = NULL;

    ASSERTERROR(lpVSOF->ulFlags & VSOF_CLONEABLE, "Bad lpVSOF->ulFlags");

    li.LowPart = VSOF_SetFilePointer(
    lpVSOF,
        0,
        &li.HighPart,
        FILE_CURRENT);

    if (li.LowPart == 0xFFFFFFFF)
    {
    // check for an error;
        hr = HRESULT_FROM_WIN32(GetLastError());
if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

hr = (*(lpVSOF->lpfnAlloc)) (sizeof(VSOF), &lpVSOFClone);
if (FAILED (hr))
{
goto cleanup;
    }

ZeroMemory(lpVSOFClone, sizeof(VSOF));
lpVSOFClone->lpVtbl = (VSOF_Vtbl FAR *)&VtblVSOF;
lpVSOFClone->lcInit = 1;
lpVSOFClone->ulFlags = lpVSOF->ulFlags;
lpVSOFClone->lpfnAlloc = lpVSOF->lpfnAlloc;
lpVSOFClone->lpfnFree = lpVSOF->lpfnFree;

lpVSOFClone->hf = INVALID_HANDLE_VALUE;

fdwCreate = OPEN_EXISTING;

    fdwAttr = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN;
    if (lpVSOF->ulFlags & STGM_DELETEONRELEASE)
        fdwAttr = FILE_ATTRIBUTE_TEMPORARY;  // keeps more data in memory.

    if (lpVSOF->ulFlags & STGM_READWRITE)
    {
fdwAccess = GENERIC_READ | GENERIC_WRITE;
    }
    else
    {
fdwAccess = GENERIC_READ;
    }

// Determine level of concurrent access allowed for file.
fdwShare = dwComputeShareFlags(lpVSOF->ulFlags);

    if (lpVSOF->lpVSOFOrig)
    {
        lpVSOFClone->lpVSOFOrig = lpVSOF->lpVSOFOrig;
        lpVSOFClone->hf = CreateFile (
        lpVSOF->lpVSOFOrig->rgchFile,
            fdwAccess,
            fdwShare,
            NULL,
            fdwCreate,
            fdwAttr,
            NULL);
    }
    else
    {
        lpVSOFClone->lpVSOFOrig = lpVSOF;
        lpVSOFClone->hf = CreateFile (
        lpVSOF->rgchFile,
            fdwAccess,
            fdwShare,
            NULL,
            fdwCreate,
            fdwAttr,
            NULL);
    }

if (lpVSOFClone->hf == INVALID_HANDLE_VALUE)
{
DWORD dwLastError = GetLastError();

switch(dwLastError)
{
case ERROR_ACCESS_DENIED:
hr = HR_LOG(E_ACCESSDENIED);
goto cleanup;

case ERROR_FILE_NOT_FOUND:
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;

default:
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

    // Set the file position to match that of the file we're cloning.
    li.LowPart = VSOF_SetFilePointer(
    lpVSOFClone,
        li.LowPart,
        &li.HighPart,
        FILE_BEGIN);

    if (li.LowPart == 0xFFFFFFFF)
    { // check for an error;
hr = HRESULT_FROM_WIN32(GetLastError());
if ( FAILED(hr) )
{
            goto cleanup;
        }
    }

    if (lpVSOF->lpVSOFOrig)
    {
        lpVSOF->lpVSOFOrig->lpVtbl->AddRef(lpVSOF);  // for ourself.
    }
    else
    {
        lpVSOF->lpVtbl->AddRef(lpVSOF);  // for ourself.
    }
    *lppStm = (LPSTREAM)lpVSOFClone;

cleanup:

    if ( FAILED(hr) )
    {
        if (lpVSOFClone)
        {
            if (lpVSOFClone->hf == INVALID_HANDLE_VALUE)
{
                VSOF_CloseHandle(lpVSOFClone);

}
            (*(lpVSOF->lpfnFree)) (lpVSOFClone);
        }
    }

RETURN(hr);

}

//$--VSOF_Release---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Release()
//
// INPUT:this,
//
// RETURNS:ULONG--new reference count.
//
//-----------------------------------------------------------------------------
STDMETHODIMP_(ULONG) VSOF_Release (// RETURNS:ULONG
IN LPVSOF lpVSOF)// this
{
HRESULThr=NOERROR;
ULONGnRefCount=0;// new reference count

DEBUGPUBLIC("VSOF_Release()\n");

// Check input parameters
hr = CHK_VSOF_Release(lpVSOF);

if ( FAILED(hr) )
{
RETURN(hr);
}

if ((--lpVSOF->lcInit) == 0)
{
// last reference to object
        if (lpVSOF->lpVSOFOrig)  // This is a clone..
        {
            lpVSOF->lpVSOFOrig->lpVtbl->Release(lpVSOF->lpVSOFOrig);

if (!VSOF_CloseHandle (lpVSOF))
{
                HR_LOG(E_FAIL);

lpVSOF->lcInit = 1;
nRefCount = 0xFFFFFFFF;

goto cleanup;
}
        }
        else if (lpVSOF->hf != INVALID_HANDLE_VALUE)
        {
if (!VSOF_CloseHandle (lpVSOF))
{
                HR_LOG(E_FAIL);

lpVSOF->lcInit = 1;
nRefCount = 0xFFFFFFFF;

goto cleanup;
}

if (lpVSOF->ulFlags & (STGM_DELETEONRELEASE | VSOF_CLONEABLE))
{
DeleteFile (lpVSOF->rgchFile);
}
}
        if (lpVSOF->lpvVBuff)
{
            (*lpVSOF->lpfnFree) (lpVSOF->lpvVBuff);
}
(*lpVSOF->lpfnFree) (lpVSOF);

// It's gone!
nRefCount = 0;

goto cleanup;
}

// Still has references
nRefCount = lpVSOF->lcInit;

cleanup:

return nRefCount;

}

//$--VSOF_Read---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Read()
//
// INPUT:lpVSOF--this,
//lpvData--read buffer
//cbSize--size of read buffer
//
// OUTPUT:lpcbRead--# bytes read
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_READFAULT otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Read (
IN LPVSOF lpVSOF,// this
IN VOID HUGEP * lpvData,// buffer to read data into
IN ULONG cbSize,// # bytes to read
OUT ULONG FAR * lpcbRead)// # bytes read
{
HRESULT hr = NOERROR;
ULONG cbRead=0;// # bytes read

    DEBUGPUBLIC("VSOF_Read()\n");

// check input parameters
hr = CHK_VSOF_Read(lpVSOF, lpvData, cbSize, lpcbRead);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialize output parameters
if ( lpcbRead != NULL )
{
*lpcbRead = 0;
}

ASSERTERROR ((lpVSOF->hf != INVALID_HANDLE_VALUE) || (lpVSOF->lpvVBuff),
    "Bad lpVSOF->hf or bad lpVSOF->lpvVBuff");
if (!VSOF_ReadFile (lpVSOF, lpvData, cbSize, &cbRead, NULL))
{
        hr = HR_LOG(STG_E_READFAULT);
// don't exit early!
}

// Set output parameters
if (lpcbRead != NULL)
{
*lpcbRead = cbRead;
}

RETURN(hr);
}

//$--VSOF_Write---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Write()
//
// INPUT:lpVSOF--this,
//lpvData--write buffer
//cbSize--size of write buffer
//
// OUTPUT:lpcbWritten--# bytes written
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_MEDIUMFULL otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Write(
IN LPVSOF lpVSOF,// this
IN VOID HUGEP * lpvData,// buffer to write data from
IN ULONG cbSize,// size of buffer
OUT ULONG FAR * lpcbWritten)// # bytes written
{
HRESULT hr = NOERROR;
ULONG cbWritten=0;// # bytes written

    DEBUGPUBLIC("VSOF_Write()\n");

// check input parameters
hr = CHK_VSOF_Write(lpVSOF, lpvData, cbSize, lpcbWritten);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialize output parameters
if ( lpcbWritten != NULL )
{
*lpcbWritten = 0;
}

// Set dirty flag on stream.
    VSOF_SetDirty(lpVSOF);

ASSERTERROR ((lpVSOF->hf != INVALID_HANDLE_VALUE) || (lpVSOF->lpvVBuff),
    "Bad lpVSOF->hf or bad lpVSOF->lpvVBuff");
if (!VSOF_WriteFile (lpVSOF, lpvData, cbSize, &cbWritten, NULL))
{
hr = HR_LOG(STG_E_MEDIUMFULL);
// don't exit early
}

// Set output parameters
if (lpcbWritten != NULL)
{
*lpcbWritten = cbWritten;
}

RETURN (hr);
}

//$--VSOF_Seek---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Seek()
//
// INPUT:lpVSOF--this,
//liMOve--offset
//dwMode--origin
//
// OUTPUT:lpliPos--new position
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_SEEKERROR otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Seek (
IN LPVSOF lpVSOF,// this
IN LARGE_INTEGER liMove,// offset
IN DWORD dwMode,// origin
OUT ULARGE_INTEGER FAR * lpliPos)// new position
{
HRESULThr=NOERROR;
ULONGibLow=0;
ULONGibHigh=0;

    DEBUGPUBLIC("VSOF_Seek()\n");

ASSERTERROR (STREAM_SEEK_SET == FILE_BEGIN, "Bad STREAM_SEEK_SET");
ASSERTERROR (STREAM_SEEK_CUR == FILE_CURRENT, "Bad STREAM_SEEK_CUR");
ASSERTERROR (STREAM_SEEK_END == FILE_END, "Bad STREAM_SEEK_END");

// Check input parameters
hr = CHK_VSOF_Seek(lpVSOF, liMove, dwMode, lpliPos);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialize output parameters
if ( lpliPos != NULL )
{
ZeroMemory(lpliPos, sizeof(ULARGE_INTEGER));
}

ASSERTERROR ((lpVSOF->hf != INVALID_HANDLE_VALUE) || (lpVSOF->lpvVBuff),
    "Bad lpVSOF->hf or bad lpVSOF->lpvVBuff");

#ifdef CHICAGO

//Windows 95 doesn't support large seeks
//
ibHigh = liMove.HighPart;
ibLow = VSOF_SetFilePointer (lpVSOF, liMove.LowPart, NULL, dwMode);
ibHigh = 0;

#else

ibHigh = liMove.HighPart;
ibLow = VSOF_SetFilePointer (lpVSOF, liMove.LowPart, &ibHigh, dwMode);

#endif

if ((ibLow == 0xFFFFFFFF) && GetLastError())
{
hr = HR_LOG(STG_E_SEEKERROR);

goto cleanup;
}

// Set output parameters
if (lpliPos)
{
lpliPos->LowPart = ibLow;
lpliPos->HighPart = ibHigh;
}

cleanup:

RETURN(hr);

}

//$--VSOF_SetSize---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::SetSize()
//
// INPUT:lpVSOF--this,
//liSize--new size
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_SEEKERROR otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_SetSize (
IN LPVSOF lpVSOF, // this
IN ULARGE_INTEGER liSize)// new size
{
HRESULThr=NOERROR;
ULONGibLow=0;
ULONG ibHigh=0;

    DEBUGPUBLIC("VSOF_SetSize()\n");

// check input parameters
hr = CHK_VSOF_SetSize(lpVSOF, liSize);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Set stream dirty flag.
VSOF_SetDirty(lpVSOF);

ibLow = VSOF_SetFilePointer (lpVSOF, 0, &ibHigh, FILE_CURRENT);
if ((ibLow == 0xFFFFFFFF) && GetLastError())
{
hr = HR_LOG(STG_E_SEEKERROR);
goto cleanup;
}

liSize.LowPart = VSOF_SetFilePointer (lpVSOF, liSize.LowPart, &liSize.HighPart, FILE_BEGIN);
if ((liSize.LowPart == 0xFFFFFFFF) && GetLastError())
{
hr = HR_LOG(STG_E_SEEKERROR);
goto cleanup;
}

if (!VSOF_SetEndOfFile (lpVSOF))
{
hr = HR_LOG(STG_E_WRITEFAULT);
goto cleanup;
}

cleanup:

ibLow = VSOF_SetFilePointer (lpVSOF, ibLow, &ibHigh, FILE_BEGIN);
if ((ibLow == 0xFFFFFFFF) && GetLastError())
{
hr = HR_LOG(STG_E_SEEKERROR);
goto cleanup;
}

RETURN(hr);

}

//$--VSOF_CopyTo---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::CopyTo()
//
// INPUT:lpVSOF--this,
//lpStrmDest--destination stream pointer
//cbCopy--# bytes to copy
//
// OUTPUT:lpcbRead--# bytes read from source stream
//lpcbWritten--# bytes written to destination stream
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_MEDIUMFULL if disk full,
//STG_E_READFAULT otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_CopyTo (
IN LPVSOF lpVSOF,// this
IN LPSTREAM lpStrmDst,// destination stream pointer
IN ULARGE_INTEGER cbCopy,// # bytes to copy
OUT ULARGE_INTEGER FAR * lpcbRead,// # bytes read
OUT ULARGE_INTEGER FAR * lpcbWritten)// # bytes written
{
HRESULT hr=NOERROR;
LPBYTE lpb = NULL;
ULONG cbr=0;
ULONG cbrTotal=0;
ULONG cbw=0;
ULONG cbwTotal = 0;

    DEBUGPUBLIC("VSOF_CopyTo()\n");

// Check input parameters
hr = CHK_VSOF_CopyTo(lpVSOF, lpStrmDst, cbCopy, lpcbRead, lpcbWritten);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialixe output paramters
if ( lpcbRead != NULL )
{
ZeroMemory(lpcbRead, sizeof(ULARGE_INTEGER));
}

if ( lpcbWritten != NULL )
{
ZeroMemory(lpcbWritten, sizeof(ULARGE_INTEGER));
}

//Since we are really not supporting excessively large
//streams, we are not gonna deal with the high long
//
hr = (*lpVSOF->lpfnAlloc) (BUFFER_SIZE, &lpb);
if (FAILED (hr))
{
goto cleanup;
}

while (cbCopy.LowPart)
{
ULONG cb = min (cbCopy.LowPart, BUFFER_SIZE);

if (!VSOF_ReadFile (lpVSOF, lpb, cb, &cbr, NULL))
{
hr = HR_LOG(STG_E_READFAULT);
goto cleanup;
}

hr = lpStrmDst->lpVtbl->Write (lpStrmDst, lpb, cbr, &cbw);
if (FAILED (hr))
{
goto cleanup;
}

if (cbr != cbw)
{
ASSERTERROR (FALSE, "VSOF_CopyTo(), bytes read != bytes written\n");
hr = HR_LOG(STG_E_MEDIUMFULL);
goto cleanup;
}

cbrTotal += cbr;
cbwTotal += cbw;

//They asked to copy more than available
//
if (cb != cbr)
{
break;
}

cbCopy.LowPart -= cb;
}

cleanup:

// Set output parameters
if (lpcbRead != NULL)
{
lpcbRead->LowPart = cbrTotal;
lpcbRead->HighPart = 0;
}

if (lpcbWritten != NULL)
{
lpcbWritten->LowPart = cbwTotal;
lpcbWritten->HighPart = 0;
}

if (lpb)
{
(*lpVSOF->lpfnFree) (lpb);
}

RETURN (hr);

}

//$--VSOF_Commit---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Commit()
//
// INPUT:lpVSOF--this,
//ulFlags--flags
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_WRITEFAULT otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Commit(
IN LPVSOF lpVSOF, // this
IN ULONG ulFlags)// flags
{
HRESULThr=NOERROR;

    DEBUGPUBLIC("VSOF_Commit()\n");

// check input parameters
hr = CHK_VSOF_Commit(lpVSOF, ulFlags);

if ( FAILED(hr) )
{
RETURN(hr);
}

//There is no 16bit equivilent here
//
if (!VSOF_FlushFileBuffers (lpVSOF))
{
hr = HR_LOG(STG_E_WRITEFAULT);

goto cleanup;
}

cleanup:

RETURN(hr);

}

//$--VSOF_Stat---------------------------------------------------------------
//
// DESCRIPTION:Same as for IStream::Stat()
//
// INPUT:lpVSOF--this,
//lpStg--storage statistics pointer
//ulFlags--flags
//
// RETURNS:HRESULT--NOERROR if sucessful,
//STG_E_INVALIDPARAM if bad input,
//STG_E_INVALIDHEADER otherwise
//
//-----------------------------------------------------------------------------
STDMETHODIMP VSOF_Stat(
IN LPVSOF lpVSOF,// this
IN STATSTG FAR * lpStg, // storage statistics pointer
IN DWORD dwFlags)// flags
{
HRESULThr=NOERROR;
ULONGcbLow=0;
ULONGcbHigh=0;

    DEBUGPUBLIC("VSOF_Stat)\n");

// check input parameters
hr = CHK_VSOF_Stat(lpVSOF, lpStg, dwFlags);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initlialize output parameter
ZeroMemory(lpStg, sizeof(STATSTG));

cbLow = VSOF_GetFileSize (lpVSOF, &cbHigh);
if ((cbLow == 0xFFFFFFFF) && GetLastError())
{
hr = HR_LOG(STG_E_INVALIDHEADER);
// don't return early!
}

lpStg->type = STGTY_STREAM;
lpStg->cbSize.LowPart = cbLow;
lpStg->cbSize.HighPart = cbHigh;
lpStg->grfMode = lpVSOF->ulFlags;

RETURN(hr);

}

//$--HrOpenVirtualStreamOnFile---------------------------------------------------------------
//
// DESCRIPTION:Open/create a virtual stream on the specified file.
//
// INPUT:lpfnAlloc--memory allocation routine
//lpfnFree--memory deallocation routine
//ulFlags--stream flags
//lpszPath--fully pathed file name
//lpszPrefix--file prefix
//
// OUTPUT:lppStream--virtual stream pointer
//
// RETURNS:HRESULT--NOERROR if sucessful,
//E_INVALIDARG if bad input,
//E_ACCESSDENIED if no access,
//EDK_E_NOT_FOUND if file not found,
//E_FAIL otherwise
//
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE HrOpenVirtualStreamOnFile(
IN LPALLOCATEBUFFER lpfnAlloc,// allocation routine
IN LPFREEBUFFER lpfnFree,// deallocation routine
IN ULONG ulFlags,// stream flags
IN LPSTR lpszFile,// fully-pathed file name
IN LPSTR lpszPrefix,// file prefix
OUT LPSTREAM FAR * lppStrm)// virtual stream pointer
{
HRESULT hr=NOERROR;
DWORD fdwAccess=0;
DWORD fdwCreate=0;
    DWORD fdwShare=0;
    DWORD fdwAttr=0;
    DWORD dwVirtualBufferSize=0;
    char *lpszVirtualBufferSize=0;
LPVSOF lpVSOF = NULL;

    DEBUGPUBLIC("HrOpenVirtualStreamOnFile()\n");

// Check input parameters
hr = CHK_HrOpenVirtualStreamOnFile(lpfnAlloc, lpfnFree, ulFlags, lpszFile,
 lpszPrefix, lppStrm);

if ( FAILED(hr) )
{
RETURN(hr);
}

    fdwAttr = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN;
    if (ulFlags & STGM_DELETEONRELEASE)
    {
        fdwAttr = FILE_ATTRIBUTE_TEMPORARY;  // keeps more data in memory.
        if (!(ulFlags & VSOF_CLONEABLE))
{
            fdwAttr |= FILE_FLAG_DELETE_ON_CLOSE;  // faster than DeleteFile()!
}
    }

if (ulFlags & STGM_READWRITE)
{
fdwAccess = GENERIC_READ | GENERIC_WRITE;

if (ulFlags & STGM_CREATE)
{
fdwCreate = CREATE_ALWAYS;
}
else
{
fdwCreate = TRUNCATE_EXISTING;
}
}
else
{
fdwCreate = OPEN_EXISTING;
fdwAccess = GENERIC_READ;
}

// Determine level of concurrent access allowed for file.
fdwShare = dwComputeShareFlags(ulFlags);

hr = (*lpfnAlloc) (sizeof(VSOF), &lpVSOF);
if (FAILED (hr))
{
goto cleanup;
}

ZeroMemory(lpVSOF, sizeof(VSOF));
lpVSOF->hf = INVALID_HANDLE_VALUE;
lpVSOF->lpVtbl = (VSOF_Vtbl FAR *)&VtblVSOF;
lpVSOF->lcInit = 1;
lpVSOF->ulFlags = ulFlags & ~VSOF_UNIQUEFILENAME;
lpVSOF->lpfnAlloc = lpfnAlloc;
lpVSOF->lpfnFree = lpfnFree;

    dwVirtualBufferSize = VIRTUAL_BUFFER_SIZE;
    if (lpszVirtualBufferSize = getenv(VBUFF_ENV_NAME))
{
        dwVirtualBufferSize = atol(lpszVirtualBufferSize);
}

// Algorithm:
// 1) Open "non-temporary" files for reading or writing.
// 2) Create a buffer for "temporary" file data.

if (ulFlags & VSOF_UNIQUEFILENAME)
{
        if ((ulFlags & STGM_DELETEONRELEASE) && !(ulFlags & VSOF_CLONEABLE) &&
            dwVirtualBufferSize)
        {
// User wants a temporary file.
            // We take the virtual fork in the road...
            if (!lpszFile)
{
                lpszFile = "";
}
            if (!lpszPrefix)
{
                lpszPrefix = "";
}

            hr = (*lpfnAlloc) (dwVirtualBufferSize, &lpVSOF->lpvVBuff);
            if ( FAILED(hr) )
{
                goto cleanup;
}

            if ((lstrlen(lpszFile) + lstrlen(lpszPrefix) + 2) >= MAX_PATH)
            {
                ASSERTERROR(FALSE,"lpszFile and lpszPrefix are too long!");

                hr = HR_LOG(E_FAIL);
goto cleanup;
            }

            // Copy the filename and prefix so we can create a temp file later.
            lstrcpy(lpVSOF->rgchFile, lpszFile);
            lstrcpy(lpVSOF->rgchFile + lstrlen(lpszFile) + 1, lpszPrefix);
            lpVSOF->dwVirtualBufferSize = dwVirtualBufferSize;

// We are done with setup for a temporary (virtual) file.
            goto cleanup;
        }

hr = ScGetVSOFFilename (lpszFile, lpszPrefix, lpVSOF->rgchFile);
if (FAILED (hr))
{
goto cleanup;
}
}
else
{
ASSERTERROR (lstrlen (lpszFile) <= MAX_PATH, "Bad lstrlen(lpszFile)");
lstrcpy (lpVSOF->rgchFile, lpszFile);
}

// Want a non-temporary (real) file.
lpVSOF->hf = CreateFile(
lpVSOF->rgchFile,
fdwAccess,
fdwShare,
NULL,
fdwCreate,
fdwAttr,
NULL);

if (lpVSOF->hf == INVALID_HANDLE_VALUE)
{
DWORD dwLastError = GetLastError();

switch(dwLastError)
{
case ERROR_ACCESS_DENIED:

hr = HR_LOG(E_ACCESSDENIED);
goto cleanup;

case ERROR_FILE_NOT_FOUND:

hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;

default:

hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

cleanup:

if (FAILED (hr))
{
if (lpVSOF)
{
if (lpVSOF->hf != INVALID_HANDLE_VALUE)
{
VSOF_CloseHandle (lpVSOF);
if (ulFlags & STGM_DELETEONRELEASE)
DeleteFile (lpVSOF->rgchFile);
}
(*lpfnFree) (lpVSOF);
lpVSOF = NULL;
}
}

*lppStrm = (LPSTREAM)lpVSOF;

RETURN(hr);
}


VSOP.CPP

//--vsop.cpp---------------------------------------------------------------
//
// Stream wrapper class for large MAPI properties.
//
// Copyright (C) Microsoft Corp, 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#include "edk.h"
#include "_vsop.h"
#include "vsop.chk"

//$--HrOpenVirtualStreamOnProperty------------------------------------------
//
// DESCRIPTION:
// API to open a buffered stream on a binary or string property
// Callable from "C" or "C++"
//
// INPUT:pPropObject--MAPI property object pointer
//ulPropTag--property tag to open stream on
//ulFlags--MAPI property flags (MAPI_MODIFY, MAPI_CREATE)
//
// OUTPUT:ppVirtualStreamOnProperty-- pointer to buffered stream on property
//
// RETURNS:HRESULT--NOERROR if successful,
//E_INVALIDARG if bad input,
//E_OUTOFMEMORY if memory problems,
//E_FAIL otherwise
//
//-----------------------------------------------------------------------------
STDAPI HrOpenVirtualStreamOnProperty(
    IN LPMAPIPROP pPropObject,// MAPI property object pointer
    IN ULONG ulPropTag,     // property tag to open virtual stream on
IN ULONG ulFlags,// MAPI property flags
    OUT PVIRTUALSTREAMONPROPERTY * ppVirtualStreamOnProperty)  // pointer buffered stream
{
// Implemented by local C++ function _HrOpenVirtualStreamOnProperty().
// Doing this allows us to declare _HrOpenVirtualStreamOnProperty() as
// a friend of the CEDKVitrualStreamOnProperty class.
RETURN(_HrOpenVirtualStreamOnProperty(pPropObject, ulPropTag, ulFlags, ppVirtualStreamOnProperty));
}

//$--_HrOpenVirtualStreamOnProperty---------------------------------------------
//
// DESCRIPTION:  same as for _HrOpenVirtualStreamOnProperty, but its a local
// C++ function instead of an exported "C" function.
//
// INPUT:ditto
//
// OUTPUT:ditto
//
// RETURNS:ditto
//
//-----------------------------------------------------------------------------
HRESULT _HrOpenVirtualStreamOnProperty(
    IN LPMAPIPROP pPropObject,// MAPI property object pointer
    IN ULONG ulPropTag,     // property tag to open virtual stream on
IN ULONG ulFlags,// MAPI property flags
    OUT PVIRTUALSTREAMONPROPERTY * ppVirtualStreamOnProperty)  // pointer buffered stream
{
    HRESULT             hr              =   NOERROR;
    CEDKVirtualStreamOnProperty * pVirtualStreamOnProperty    =   NULL;       // stream wrapper pointer

    DEBUGPUBLIC("HrOpenVirtualStreamOnProperty()");

    // check input parameters
    hr = CHK_HrOpenVirtualStreamOnProperty(pPropObject, ulPropTag, ulFlags,
      ppVirtualStreamOnProperty);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize output parameters.
    *ppVirtualStreamOnProperty = NULL;

    // Instantiate a new virtual stream on property object
    pVirtualStreamOnProperty = new CEDKVirtualStreamOnProperty();

    if ( pVirtualStreamOnProperty == NULL )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(pVirtualStreamOnProperty, "Bad pVirtualStreamOnProperty");

    // pVirtualStreamOnProperty reference count is now 1.

    // Initialize VSOP instance.
    hr = pVirtualStreamOnProperty->HrInitialize(
    pPropObject,// property object pointer
    ulPropTag,// property tag
    ulFlags);// MAPI property flags

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Retrieve an IEDKVirtualStreamOnProperty interface.
    hr = pVirtualStreamOnProperty->QueryInterface(
        IID_IEDKVirtualStreamOnProperty,          // interfac ID reference
        (LPVOID *) ppVirtualStreamOnProperty);      // IEDKVirtualStreamOnProperty interface pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERT_READ_PTR(ppVirtualStreamOnProperty, sizeof(PVIRTUALSTREAMONPROPERTY),
        "Bad ppVirtualStreamOnProperty");
    ASSERT_IUNKNOWN_PTR(*ppVirtualStreamOnProperty, "Bad *ppVirtualStreamOnProperty");

    // pVirtualStreamOnProperty reference count is now 2.

    // we are done

cleanup:

    // Release our copy of the pVirtualStreamOnProperty pointer.
    ULRELEASE(pVirtualStreamOnProperty);

    // pVirtualStreamOnProperty reference count is now 1 in success case.

    RETURN(hr);

}

//--$CEDKVirtualStreamOnProperty::CEDKVirtualStreamOnProperty---------------------------------------
//
// DESCRIPTION: Constructor for CEDKVirtualStreamOnProperty.
//
// INPUT: none
//
// RETURNS: nothing
//
//-----------------------------------------------------------------------------
CEDKVirtualStreamOnProperty::CEDKVirtualStreamOnProperty()
{
    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::CEDKVirtualStreamOnProperty()\n");

// initialize data members
m_pStream = NULL;// unbuffered stream pointer (if any)
m_pPropObject = NULL;
m_ulPropTag = 0;
m_ulFlags = 0;// MAPI property flags
m_rgbBuffer = NULL;
m_pbBuffer = NULL;// pointer into the character buffer
m_cbBuffer = 0;// current size of character buffer
m_fDataInitialized = FALSE;// TRUE if data buffer has been initialized

    ZeroMemory(
    &m_StatStg,
sizeof(m_StatStg));

// increment reference count
    m_refs = 1;

}

//--$CEDKVirtualStreamOnProperty::~CEDKVirtualStreamOnProperty---------------------------------------
//
// DESCRIPTION: Destructor for CEDKVirtualStreamOnProperty.
//
// INPUT: none
//
// RETURNS: nothing
//
//-----------------------------------------------------------------------------
CEDKVirtualStreamOnProperty::~CEDKVirtualStreamOnProperty()
{
    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::~CEDKVirtualStreamOnProperty()\n");

// consistency check
ASSERTERROR(m_refs == 0, "Bad m_refs.");

// Release "real" stream, if any
ULRELEASE(m_pStream);

m_ulFlags = 0;// MAPI property flags
m_pPropObject = NULL;
m_ulPropTag = 0;

// Free data buffer memory, if any.
MAPIFREEBUFFER(m_rgbBuffer);

m_pbBuffer = NULL;// pointer into the character buffer
m_cbBuffer = 0;// current size of character buffer
m_fDataInitialized = FALSE;

    ZeroMemory(
    &m_StatStg,
sizeof(m_StatStg));

    m_refs = 0;

}

//--$CEDKVirtualStreamOnProperty::HrInitialize-------------------------------------------
//
// DESCRIPTION:  Initialize CEDKVirtualStreamOnProperty instance.
//
// INPUT:   pPropObject--pointer to MAPI property object
//          ulPropTag--property tag
//ulFlags--MAPI property access flags
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//E_FAIL otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CEDKVirtualStreamOnProperty::HrInitialize(
IN LPMAPIPROP pPropObject,// pointer to MAPI property object
IN ULONG ulPropTag,// property tag
IN ULONG ulFlags)// MAPI property access flags
{
    HRESULT     hr          =   NOERROR;
    SYSTEMTIME  sSystemTime =   {0};        // System Time structure
    BOOL        fRetVal     =   FALSE;      // Win32 error code.

    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::HrInitialize()\n");

    // check input parameters
    hr = CHK_CEDKVirtualStreamOnProperty_HrInitialize(pPropObject, ulPropTag,
    ulFlags);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

// Save options
m_ulFlags = ulFlags;
m_pPropObject = pPropObject;
m_ulPropTag = ulPropTag;

// Zero out storage statistics function.
ZeroMemory(&m_StatStg, sizeof(STATSTG));

// Set storage type for streams.
    m_StatStg.type = STGTY_STREAM;

// Set mode in storage statistics structure to
// lowest common denominator (for reads).
    SetMode(STGM_READ | STGM_SHARE_EXCLUSIVE);

    // Don't care about or support other stream statistics.

    // we are done

    RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::HrOpenUnderlyingStream--------------------------------
//
// DESCRIPTION:Open or create an underlying stream for reading or writing.
//
// INPUT:none
//
// RETURNS:HRESULT--NOERROR if successful,
//E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CEDKVirtualStreamOnProperty::HrOpenUnderlyingStream()
{
HRESULThr=NOERROR;
ULONGulIIDFlags=STGM_SHARE_EXCLUSIVE;// default stream mode flags

DEBUGPRIVATE("CEDKVirtualStreamOnProperty::HrOpenUnderlyingStream()\n");

// consistency check
ASSERT_IUNKNOWN_PTR(m_pPropObject, "Bad m_pPropObject");

// Determine if need stream modification (writing)
if ( fIsWriteProperty() == TRUE )
{
ulIIDFlags |= STGM_WRITE;
}

// Determine if need stream creation.
if ( fIsNewProperty() == TRUE )
{
ulIIDFlags |= STGM_CREATE;
}

// Open/create a stream on the property
hr = m_pPropObject->OpenProperty(
m_ulPropTag,// property tag
&IID_IStream,// interface identifier reference
ulIIDFlags,// interface flags
m_ulFlags | MAPI_DEFERRED_ERRORS,// MAPI property creation flags
(LPUNKNOWN *) &m_pStream);// stream pointer

if ( FAILED(hr) )
{
// Return the MAPI error code

goto cleanup;
}

// Adjust mode in the storage statistics structure
SetMode(ulIIDFlags);

 // we are done.

cleanup:

RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::HrFlushWriteBuffer---------------------------
//
// DESCRIPTION:Flushes data in write buffer to the underlying stream.
//
// INPUT:fCleanOutWriteBuffer--TRUE if should zero out the write buffer
//
// RETURNS:HRESULT--NOERROR if successful,
//E_FAIL otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CEDKVirtualStreamOnProperty::HrFlushWriteBuffer(
IN BOOL fCleanWriteBuffer)// TRUE if should zero out the write buffer
{
HRESULThr=NOERROR;
ULONGcbWrite=0;// # bytes to flush
ULONGcbWritten=0;// # bytes written
LARGE_INTEGERcbWriteStream={0};// # bytes to write to underlying stream

DEBUGPRIVATE("CEDKVirtualStreamOnProperty::HrFlushWriteBuffer()\n");

// See if we have an underlying stream to write to.
if ( m_pStream == NULL )
{
// Open stream on property & write contents
// of buffer to it & readjust buffer data members.
hr = HrOpenUnderlyingStream();

if ( FAILED(hr) )
{
// Return any possible MAPI error code due to property access errors.

goto cleanup;
}
}// end if no underlying stream

// By the time we get here, we should have an underlying
// stream
ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

// Determine number of bytes which need to be flushed from buffer.
cbWrite = GetBytesWritten();

// Flush contents of buffer to the stream
hr = m_pStream->Write(
m_rgbBuffer,// Source buffer
cbWrite,// # bytes to write
&cbWritten);// # bytes written

if ( FAILED(hr) )
{
goto cleanup;
}

// Check # of bytes written
if ( cbWritten != cbWrite )
{
hr = HR_LOG(E_FAIL);

goto cleanup;
}

// Adjust size of stream in the storage statistics
// structure.
cbWriteStream.LowPart = cbWritten;
IncrementStreamSize(cbWriteStream);

// Clean out write buffer, if requested
if ( fCleanWriteBuffer == TRUE )
{
InitBufferSize(m_cbBuffer);
}

// We are done

cleanup:

RETURN(hr);

}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

// $--CEDKVirtualStreamOnProperty::QueryInterface------------------------------------------
//
// DESCRIPTION:Return pointer to object which implements the desired
//interface, if this object supports the interface.
//
// INPUT:
//
//  [riid]-- Reference to interface identifier of desired interface.
//
//  [ppv]-- Ptr to object which supports interface.  NULL if none.
//
// RETURNS:     NOERROR if successful;
//E_INVALIDARG if bad input.
//E_NOINTERFACE if interface isn't supported.
//
//  Interfaces supported:
//
//      IUnknown
//      IEDKVirtualStreamOnProperty
//
//---------------------------------------------------------------------------

STDMETHODIMP CEDKVirtualStreamOnProperty::QueryInterface(
IN REFIID riid,   // interface ID reference
OUTLPVOID * ppvObj)    // pointer to interface pointer
{
    HRESULT hr =NOERROR;

    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::QueryInterface().\n");

    hr = CHK_CEDKVirtualStreamOnProperty_QueryInterface(riid, ppvObj);

    if (FAILED(hr))
    {
        RETURN(hr);
    }

    // initialize output parameter
    *ppvObj = NULL;

    // See if we support the requested interface.

    if (IsEqualIID(riid, IID_IUnknown))   // IUnknown interface is ourself
{
    *ppvObj = this;    // Return ourself
}

    else if (IsEqualIID(riid, IID_IEDKVirtualStreamOnProperty))
    {
        // User wants our programatic interface, which is ourselves.
    *ppvObj = this;
    }
    else
    {
        // We don't support the requested interface.
        hr = HR_LOG(E_NOINTERFACE);

        goto cleanup;
    }

    // If we reach this point, no error occurred.  Increment reference count.
    AddRef();

cleanup:

    RETURN(hr);
}

//$--CEDKVirtualStreamOnProperty::AddRef--------------------------------------------------
//
// DESCRIPTION:Increment the reference count on this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CEDKVirtualStreamOnProperty::AddRef()// RETURNS: ULONG
{
    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::AddRef().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs++;

    return m_refs;
}

//$--CEDKVirtualStreamOnProperty::Release-------------------------------------------------
//
// DESCRIPTION:Decrement the reference count on this object.  If the
//reference count reaches 0, destroy this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//---------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CEDKVirtualStreamOnProperty::Release()// RETURNS: ULONG
{
    ULONG   ulRefCount =0;

    DEBUGPRIVATE("CEDKVirtualStreamOnProperty::Release().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs--;

    if ( m_refs == 0 )
    {
        delete this;
        ulRefCount = 0;
    }
    else
    {
        ulRefCount = m_refs;
    }

    return ulRefCount;
}

//$--CEDKVirtualStreamOnProperty::HrComputeCurrentSize-------------------------
//
// DESCRIPTION:Calculates current stream size
//
// IN:pcbSize--unsigned large integer structure pointer
//
// RETURNS:HRESULT--NOERROR if successful,
//E_INVALIDARG if bad input,
//E_FAIL otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CEDKVirtualStreamOnProperty::HrComputeCurrentSize(
IN ULARGE_INTEGER * pcbSize)// unsigned large integer pointer
{
HRESULThr=NOERROR;
DWORDLONGcbTotal=0;// total size of virtual stream
DWORDLONGcbStream=0;// size of underlying stream

DEBUGPRIVATE("CEDKVirtualStreamOnProperty()\n");

// check input parameters
hr = CHK_CEDKVirtualStreamOnProperty_HrComputeCurrentSize(pcbSize);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Intialize output parameter
ZeroMemory(pcbSize, sizeof(ULARGE_INTEGER));

// Check to see that data buffer is initialized
if ( fDataInitialized() == FALSE )
{
// First data buffer has not been initialized.
if ( fIsWriteProperty() == TRUE )
{
// Have a write property.
// Currently, virtual write stream is zero bytes.
// We are done.
goto cleanup;
}

else
{
// Have a read property.
// Need to set up underlying data.
// Do this by reading 0 bytes from the property.
hr = Read(
NULL,
0,
NULL);

if ( FAILED(hr) )
{
goto cleanup;
}

ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

}// end if read property
}// end if underlying data hasn't been initialized

// By the time we get to here, the underlying data has
// been initialize.
ASSERTERROR(fDataInitialized() == TRUE, "Bad state");

// See if there is an uderlying stream.
if ( m_pStream == NULL )
{
if ( fIsReadProperty() == TRUE )
{
// have a read property
// size of stream is size of property buffer.
cbTotal = m_cbBuffer;
}

else// have a write property
{
// size of stream is number of bytes written to write buffer.
cbTotal = GetBytesWritten();
}
}// end if no underlying stream

else// have an underlying stream
{
// Stream size if the size of the underlying stream
// (which is maintained in the stream statistics structure for
// write buffers and must be read directly from stream for
// read buffers)
// plus the size of the write data buffer.
cbStream = MAKEDWORDLONG(// Size of underlying stream
 m_StatStg.cbSize.LowPart,
m_StatStg.cbSize.HighPart);

// See if we have a read or a write stream
if ( (fIsWriteProperty() == TRUE) )
{
// have a write stream
cbTotal = cbStream + GetBytesWritten();
}

else// have a read stream
{
// See if we need to retrieve the stream size
if ( cbStream == 0 )
{
// need to retrieve read stream size.
hr = m_pStream->Stat(
&m_StatStg,// storage statistics structure pointer
STATFLAG_NONAME);// statistics flags

if ( FAILED(hr) )
{
goto cleanup;
}
}

 // Size of virtual read stream is just size of underlying stream.
cbTotal = MAKEDWORDLONG(
m_StatStg.cbSize.LowPart,
m_StatStg.cbSize.HighPart);

}// end if have a read stream
}// end if there is an underlying buffer

// Set output variables.
pcbSize->LowPart = LOWDWORD(cbTotal);
pcbSize->HighPart = HIDWORD(cbTotal);

// We are done

cleanup:

RETURN(hr);

}

// IStream methods

//$--CEDKVirtualStreamOnProperty::Read---------------------------------------------------
//
// DESCRIPTION: Read the number of bytes requested from our "stream" to the
//              output buffer.
//
// INPUT:       pv  --  pointer to output buffer
//              cb  --  maximum number of bytes to read
//
// OUTPUT:      pcb --  number of bytes actually read, may be NULL.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          STG_E_INVALIDPARAMETER if bad input,
//STG_E_ACCESSDENIED if not read stream,
//STG_E_INSUFFICIENTMEMORY if memory problems,
//MAPI_E_* if property access fails,
//STG_E_UNKNOWN if unexpected error,
//                          STG_E_READFAULT otherwise.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Read(
        IN LPVOID pv,       // output buffer pointer
        IN ULONG cb,        // maximum # of bytes to read
        OUT ULONG * pcb)    // # of bytes read
{
    HRESULT hr      =   NOERROR;
LPSPropValuelpsProp=NULL;// property array pointer
ULONGnProps=0;// # of properties
ULONGcbProp=0;// # bytes in property
BYTE *pbData=NULL;// data pointer
ULONGcbUnread=0;// # bytes unread in buffer
ULONGcbRead=0;// # bytes to read from buffer
BYTE *pbDest=NULL;// pointer into output buffer
ULONGcbTotalRead=0;// Total # bytes read

SizedSPropTagArray(1, sPropTag) =// property tag array
{
1,// number of properties
{
m_ulPropTag// property tag desired
}
};

    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Read()\n");

    // check input parameters
    hr = CHK_CEDKVirtualStreamOnProperty_Read(pv, cb, pcb);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize output variables
if ( pcb != NULL )
{
    *pcb = 0;
}

    ZeroMemory(pv, cb);

pbDest = (BYTE *) pv;// pointer into output buffer

// Read is only valid for properties opened for reading
if ( fIsReadProperty() == FALSE )
{
hr = HR_LOG(STG_E_ACCESSDENIED);

goto cleanup;
}

// Algorithm: (simplified for clarity).  These steps may correspond
// to helper methods yet to be written.
//
// 1) If data buffer is empty, try to retrieve entire property at once
// via the IMAPIProp::GetProps() method.
// If this succeeds, read cb bytes out of data buffer into
// output buffer and goto cleanup.
//
// 2) If data buffer is empty, then
// open a "real" stream on the property and copy 8K bytes from the
// stream to the data buffer.  Read cb bytes out of the data buffer
// into the output buffer and goto cleanup.
//
// 3) If data buffer is not empty and we have not reached the end of the data
// buffer, then read the next cb bytes out of the data buffer into the
// output buffer and goto cleanup.
//
// 4) If we have reached the end of the data buffer and there is no underlying
// stream, then read 0 bytes from the data buffer and goto cleanup.
//
// 5) Otherwise, we have reached the end of the data buffer and there is
// an underlying stream. Retrieve the next 8K bytes from the stream and
// copy these to the data buffer.  Read cb bytes from the data buffer to the
// output buffer and goto cleanup.
//

// Check to see if data buffer is empty because
// we haven't retrieved any data yet.
if ( fDataInitialized() == FALSE )
{
// data buffer is empty.
// Try to retrieve whole property into 8K buffer.
hr = m_pPropObject->GetProps(
(LPSPropTagArray) &sPropTag,// property tag array pointer
fMapiUnicode,// MAPI flags
&nProps,// number of properties retrieved
&lpsProp);// property array pointer

// Check to see if property is too big to fit in buffer
if ( FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED) )
{
// Check to see if property is to big to retrieve
// via GetProps()
if ( ( (hr == MAPI_W_ERRORS_RETURNED) &&
       (lpsProp[0].Value.l == MAPI_E_NOT_ENOUGH_MEMORY) ) ||
 (hr == MAPI_E_NOT_ENOUGH_MEMORY) )
{
// Open a stream on the property.
ASSERT_IUNKNOWN_PTR(m_pPropObject, "Bad m_pPropObject");

hr = m_pPropObject->OpenProperty(
m_ulPropTag,// property tag
&IID_IStream,// stream interface
STGM_READ | STGM_SHARE_EXCLUSIVE,// interface flags
MAPI_DEFERRED_ERRORS,// reduces RPCs
(LPUNKNOWN *) &m_pStream);// stream pointer

if ( FAILED(hr) )
{
// Return MAPI error code.

goto cleanup;
}

ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

// Allocate 8K buffer to hold some of the stream data.
hr = MAPIAllocateBuffer(
cbMaxBuffered,// # bytes to allocate
(LPVOID *) &m_rgbBuffer);// data buffer pointer

if ( FAILED(hr) )
{
hr = HR_LOG(STG_E_INSUFFICIENTMEMORY);

goto cleanup;
}

ASSERT_READ_PTR(m_rgbBuffer, cbMaxBuffered, "Bad m_rgbBuffer");

// Read 8K of stream data into the data buffer and
hr = m_pStream->Read(
m_rgbBuffer,// destination buffer pointer
cbMaxBuffered,// # bytes to read
&cbRead);// # bytes read

if ( FAILED(hr) )
{
goto cleanup;
}

// set data buffer members.
InitBufferSize(cbRead);

}// end if property too big

// Otherwise, we have a fatal error
else
{
// Return MAPI error code.

goto cleanup;

}// end if have other error
}// end if error on GetProps() call

else if ( SUCCEEDED(hr) )
{
// Property can be retrieved via GetProps().
// Do some checks
ASSERTERROR(nProps == 1, "Bad nProps");
ASSERT_READ_PTR(lpsProp, sizeof(SPropTagArray), "Bad lpsProp");

// Make sure that property will fit in our buffer.
if ( PROP_TYPE(m_ulPropTag) == PT_BINARY )
{
// have a binary property
cbProp = lpsProp[0].Value.bin.cb;
pbData = lpsProp[0].Value.bin.lpb;// data pointer
}

else if ( PROP_TYPE(m_ulPropTag) == PT_TSTRING )
{
// have a string property
cbProp = lstrlen(lpsProp[0].Value.LPSZ) * sizeof(CHAR);
pbData = (BYTE *) lpsProp[0].Value.LPSZ;// data pointer
}

else
{
// This shouldn't happen
ASSERTERROR(FALSE, "Bad m_ulPropTag");

hr = HR_LOG(STG_E_UNKNOWN);

goto cleanup;
}

// Set data buffer members
// First, copy property data to our local data buffer
hr = MAPIAllocateBuffer(
cbProp,// number of bytes to allocate
(LPVOID *) &m_rgbBuffer);// data buffer pointer

if ( FAILED(hr) )
{
hr = HR_LOG(STG_E_INSUFFICIENTMEMORY);

goto cleanup;
}

ASSERT_READ_PTR(m_rgbBuffer, cbProp, "Bad m_rgbBuffer");

CopyMemory(
m_rgbBuffer,// destination buffer
pbData,// source buffer
cbProp);// number of bytes

// Set data buffer members
InitBufferSize(cbProp);

}// end if property can fit in our data buffer

// Test for real failures
else if ( FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED) )
{
// Return MAPI error code.

goto cleanup;
}
}// end if haven't gotten any data yet.

ASSERT_READ_PTR(m_rgbBuffer, m_cbBuffer, "Bad m_rgbBuffer");

// When we get to here, we will have data in the buffer.
// Try to read the desired number of bytes from the buffer.
while ( cbTotalRead < cb )
{
// First, determine the number of unread bytes in the buffer.
cbUnread = GetBytesUnread();

// Check to see if we need to refill the data buffer
if ( cbUnread == 0 )
{
// We need to refill the data buffer.
// See if there is an underlying stream to read more
// data from.  If not, then we are done.
if ( m_pStream == NULL )
{
// No more data.
// We are done.
break;
}

ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

// Read more data from underlying stream
hr = m_pStream->Read(
m_rgbBuffer,// data buffer pointer
cbMaxBuffered,// # bytes to read
&cbRead);// # bytes read

if ( FAILED(hr) )
{
goto cleanup;
}

// Set up data buffer members
InitBufferSize(cbRead);

// Re-calculate the number of unread data bytes
cbUnread = GetBytesUnread();

// Re-test for end of data condition
if ( cbUnread == 0 )
{
// we are done.
break;
}

}// end if need to refill data buffer

// Determine number of bytes to read from buffer.
cbRead = min(cbUnread, cb);

// Read data from data buffer to output buffer
CopyMemory(
pbDest,// destination buffer pointer
m_pbBuffer,// source buffer
cbRead);// # bytes to copy

// Adjust data buffer pointers
IncrementBytesRead(cbRead);
pbDest += cbRead;

// increment number of bytes read
cbTotalRead += cbRead;

}// end while

    // we are done

cleanup:

// Set output variables (handles case of partial reads)
if ( pcb != NULL )
{
(*pcb) = cbTotalRead;
}

// Free MAPI buffers
MAPIFREEBUFFER(lpsProp);

    RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::Write---------------------------------------------------
//
// DESCRIPTION: Write the number of bytes requested from the buffer to the
//              "stream".
//
// INPUT:       pv  --  pointer to buffer
//              cb  --  number of bytes to write
//
// OUTPUT:      pcb --  number of bytes written -- may be NULL
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          STG_E_INVALIDPARAMETER if bad input,
//STG_E_ACCESSDENIED if not write stream,
//STG_E_INSUFFICIENTMEMORY if memory problems,
//MAPI_E_* if property access fails,
//                          STG_E_WRITEFAULT otherwise.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Write(
        IN VOID const * pv, // data to write
        IN ULONG cb,        // # bytes to write
        OUT ULONG *pcb)     // # bytes written
{
HRESULT hr=NOERROR;
BYTE *pbSource=NULL;// pointer into source buffer
ULONGcbUnwritten=0;// # of unwritten bytes in buffer

ULONGcbWrite=0;// # of bytes to write
ULONGcbTotalWrite=0;// total # bytes written

    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Write()\n");

// Check input parameters
hr = CHK_CEDKVirtualStreamOnProperty_Write(pv, cb, pcb);

if ( FAILED(hr) )
{
RETURN(hr);
}

// Initialize output paramters.
if ( pcb != NULL )
{
*pcb = 0;
}

pbSource = (BYTE *) pv;// pointer into source buffer

// If we don't have a write buffer, then we fail.
if ( fIsWriteProperty() == FALSE )
{
hr = HR_LOG(STG_E_ACCESSDENIED);

goto cleanup;
}

// Algorithm:
//
// 1) If data buffer isn't full, copy cb bytes from input buffer
// to the data buffer.  If data buffer becomes full during this process,
// goto step 2).  Otherwise, goto cleanup.
//
// 2) If data buffer is full or has become full in step and there is
// no underlying stream, then open or create
// the underlying stream on the property, as appropriate (based on m_ulFlags).
//
// 3) If the data buffer is full, write the contents of the data buffer
// to the underlying stream.  Then, if there is still any additional data
// to be written, copy it to the now empty data buffer.  Goto cleanup.

// If no current data buffer, make one.
if ( fDataInitialized() == FALSE )
{
// Create a data buffer to write to.
// Buffer includes "extra" room for null-termination of exactly 8K
// string.
hr = MAPIAllocateBuffer(
cbMaxBuffered + sizeof(CHAR),// # bytes to allocate
(LPVOID *) &m_rgbBuffer);// data buffer pointer

if ( FAILED(hr) )
{
hr = HR_LOG(STG_E_INSUFFICIENTMEMORY);

goto cleanup;
}

// Set data buffer members
InitBufferSize(cbMaxBuffered);

}// end if no data buffer

ASSERT_READ_PTR(m_rgbBuffer, m_cbBuffer, "Bad m_rgbBuffer");

// Copy data to data buffer,
while ( cbTotalWrite < cb )
{
// Determine number of unwritten bytes left in buffer.
cbUnwritten = GetBytesUnwritten();

// See if we need to flush out the write buffer.
if ( cbUnwritten == 0 )
{
// Flush data written to the underlying stream,
// making sure to reset the write buffer.
hr = HrFlushWriteBuffer(TRUE);

if ( FAILED(hr) )
{
// Return possbile MAPI error code due to property access failure

goto cleanup;
}

// continue on to handle writing of pv input data
continue;

}// end if need to flush write buffer

ASSERTERROR(cbUnwritten != 0, "Bad cbUnwritten");

// Compute number of byte to write to data buffer
cbWrite = min(cb, cbUnwritten);

// Write the desired number of bytes
CopyMemory(
m_pbBuffer,// destination buffer pointer
pbSource,// source buffer
cbWrite);// # bytes to copy

// Update the number of bytes written
IncrementBytesWritten(cbWrite);
pbSource += cbWrite;

// increment total number of bytes written
cbTotalWrite += cbWrite;

}// end while

// We are done.

cleanup:

// Set output variables (handles case of partial writes)
if ( pcb != NULL )
{
(*pcb) = cbTotalWrite;
}

    RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::CopyTo---------------------------------------------------
//
// DESCRIPTION: Copy the number of bytes requested from the "source" stream to the
//              destination stream.
//
// INPUT:       pStrm  --  destination stream pointer
//              cb  --  number of bytes to copy
//
// OUTPUT:      pcbRead --  number of bytes read -- may be NULL
//              pcbWritten  --  number of bytes written -- may be NULL
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          STG_E_INVALIDPARAMETER if bad input,
//error codes from Read() and Write(),
//                          E_FAIL otherwise.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::CopyTo(
        IN LPSTREAM pStrm,              // destination stream pointer
        IN ULARGE_INTEGER cb,           // # bytes to copy
        OUT ULARGE_INTEGER * pcbRead,   // # bytes read
        OUT ULARGE_INTEGER * pcbWritten)// # bytes written
{
    HRESULT hr          =   NOERROR;
    ULONG   cbRead      =   0;          // # bytes read from source
    ULONG   cbWritten   =   0;          // # bytes written to destination
ULONGcbDesired=0;// # bytes desired
DWORDLONGcbTemp1=0;// temporary byte count
DWORDLONGcbTotalRead=0; // # bytes read so far
DWORDLONGcbTotalWrite=0;// # bytes written so far
DWORDLONGcbTotalReq=0;// # total bytes desired

CHARrgchBuffer[cbMaxBuffered]={0};// data buffer

    DEBUGPUBLIC("CEDKStreamWrappr::CopyTo()\n");

    // check input parameters
    hr = CHK_CEDKVirtualStreamOnProperty_CopyTo(pStrm, cb, pcbRead, pcbWritten);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize output variables
if ( pcbRead != NULL )
{
ZeroMemory(pcbRead, sizeof(ULARGE_INTEGER));
}

if ( pcbWritten != NULL )
{
ZeroMemory(pcbWritten, sizeof(ULARGE_INTEGER));
}

// _int64s are easier to do arithmetic on than LARGE_INTEGER structures
cbTotalReq = MAKEDWORDLONG(cb.LowPart, cb.HighPart);

while ( cbTotalRead < cbTotalReq )
{
    // Read up to 8K bytes from the source stream (using IStream::Read() method)

// First, determine # bytes to read.
cbTemp1 = cbTotalReq - cbTotalRead;

if ( HIDWORD(cbTemp1) != 0 )
{
cbDesired = cbMaxBuffered;// read 8K bytes
}

else
{
cbDesired = min(cbMaxBuffered, LOWDWORD(cbTemp1));
}

hr = Read(
rgchBuffer,// data buffer
cbDesired,// # bytes to read
&cbRead);// # bytes read

if ( FAILED(hr) )
{
goto cleanup;
}

// compute current total number of bytes read.
cbTotalRead += cbRead;

// Check for end of stream
if ( cbRead == 0 )
{
// we are done
break;
}

ASSERTERROR(cbRead == cbDesired, "Bad cbRead");

    // Write cb bytes to the destination stream (using IStream::Write() method)
hr = pStrm->Write(
rgchBuffer,// data buffer
cbRead,// # bytes just read
&cbWritten);// # bytes written

if ( FAILED(hr) )
{
goto cleanup;
}

// Compute current total number of bytes written
cbTotalWrite += cbWritten;

ASSERTERROR(cbWritten == cbRead, "Bad cbWritten");

}// end while

    // We are done

cleanup:

// Set output paremeters (handles case of partial read or write)
if ( pcbRead != NULL )
{
pcbRead->LowPart = LOWDWORD(cbTotalRead);
pcbRead->HighPart = HIDWORD(cbTotalRead);
}

if ( pcbWritten != NULL )
{
pcbWritten->LowPart = LOWDWORD(cbTotalWrite);
pcbWritten->HighPart = HIDWORD(cbTotalWrite);
}

    RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::Commit---------------------------------------------------
//
// DESCRIPTION: Save changes to the stream.
//
// INPUT:       dwFlags --  flags
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  NOERROR if successful,
//STG_E_WRITEFAULT if problems writting,
//MAPI_E_* if property access fails,
//STG_E_UNKNOWN if unexpected problem.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Commit(
        IN DWORD dwFlags)   // flags
{
HRESULThr=NOERROR;
SPropValuesPropValue={0};// property value
LPSPropProblemArraylpsProblems=NULL;// property problem array pointer
ULONGcbWrite=0;// # bytes to write to stream
ULONGiProp=0;// property problem index

    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Commit()");

// Check to see if are only doing reads on the property/
if ( fIsReadProperty() == TRUE )
{
// nothing to do.
MODULE_WARNING("Commit() does nothing when called on a read stream.");

goto cleanup;
}

// Algorithm:
//
// 1) If there is no underlying stream and there is data in the buffer,
// then try to write the data to the property via the IMAPIPROP::SetProps() method.
// If this succeeds, then save the changes to the property object and
// go to cleanup.
//
// 2) If the SetProps() in step 1) failed, then open or create a stream
// on the underlying property.
//
// 3) Write any data in the buffer to the underlying stream.  Commit the
// changes to the underlying stream and save the changes to the
// property object.  Go to cleanup.

// See if there is any unflushed data in the data buffer.
cbWrite = GetBytesWritten();

if ( cbWrite > 0 )
{
// If we have no underlying stream, try a SetProps()
// first.
if ( m_pStream == NULL )
{
ASSERT_IUNKNOWN_PTR(m_pPropObject, "Bad m_pPropObject");

// Set up property value structure.
sPropValue.ulPropTag = m_ulPropTag;// property tag

if ( PROP_TYPE(m_ulPropTag) == PT_BINARY )
{
// have a binary property
sPropValue.Value.bin.cb = cbWrite;// property size
sPropValue.Value.bin.lpb = m_rgbBuffer;// property value
}

else if ( PROP_TYPE(m_ulPropTag) == PT_TSTRING )
{
// have a string property
sPropValue.Value.LPSZ = (LPSTR) m_rgbBuffer;// property value
}

else
{
// This shouldn't happen
ASSERTERROR(FALSE, "Bad property tag type");

hr = HR_LOG(STG_E_UNKNOWN);

goto cleanup;
}

hr = m_pPropObject->SetProps(
1,// # of properties
&sPropValue,// property value pointer
&lpsProblems);// property problem array pointer

if ( SUCCEEDED(hr) && (lpsProblems == NULL) )
{
// We are done!
goto cleanup;
}

// Check for errors other than not enough memory
if ( FAILED(hr) && (hr != MAPI_E_NOT_ENOUGH_MEMORY) )
{
// fatal error
// Return MAPI error code

goto cleanup;
}

// Check property problem array
if ( lpsProblems != NULL )
{
ASSERT_READ_PTR(lpsProblems, sizeof(SPropProblemArray),
"Bad lpsProblems");

for ( iProp = 0; iProp < lpsProblems->cProblem; iProp++ )
{
if ( lpsProblems->aProblem[iProp].scode !=
     MAPI_E_NOT_ENOUGH_MEMORY )
{
// fatal error
// Return MAPI error code
hr = HR_LOG(lpsProblems->aProblem[iProp].scode);

goto cleanup;
}
}// end for each problem
}

// Otherwise, there is too much
// data to use SetProps.
// Open a stream on the property.
hr = HrOpenUnderlyingStream();

if ( FAILED(hr) )
{
// Return possible MAPI error code due to property access problem.

goto cleanup;
}
}// end if no underlying stream

// If we get to here, we
// have an underlying stream.
ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

// Flush buffered data to the underlying stream.
// making sure that we do NOT reset the write buffer.
hr = HrFlushWriteBuffer(FALSE);

if ( FAILED(hr) )
{
// Return possible MAPI error code due to property access failure

goto cleanup;
}
}// end if unflushed data in the buffer

// If there is an underlying stream, commit the changes to it now.
if ( m_pStream != NULL )
{
ASSERT_IUNKNOWN_PTR(m_pStream, "Bad m_pStream");

hr = m_pStream->Commit(dwFlags);

if ( FAILED(hr) )
{
goto cleanup;
}

}// end if underlying stream

// We are done.

cleanup:

// Free MAPI buffers
MAPIFREEBUFFER(lpsProblems);

    RETURN(hr);

}

//$--CEDKVirtualStreamOnProperty::Stat---------------------------------------------------
//
// DESCRIPTION: Return "stream" statistics
//
// INPUT:
//              dwFlags --  flags
//
// OUTPUT:      pStatStg    --  statistics buffer pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Stat(
        OUT STATSTG * pStatStg,     // stream statistic pointer
        IN DWORD dwFlags)           // flags
{
    HRESULT hr  =   NOERROR;
ULARGE_INTEGERcbSize={0};// virtual stream size

    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Stat()\n");

    // Check parameters
    hr = CHK_CEDKVirtualStreamOnProperty_Stat(pStatStg, dwFlags);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize output buffer
    ZeroMemory(
        pStatStg,
        sizeof(STATSTG));

    // Get the current size of the stream
hr = HrComputeCurrentSize(
&cbSize);// large integer structure pointer

if ( FAILED(hr) )
{
goto cleanup;
}

    // Note:  last modification and access time are not supported.

    // copy of the default stream statistics to the output buffer
    CopyMemory(
        pStatStg,           // destination buffer
        &m_StatStg,         // source buffer
        sizeof(m_StatStg)); // # bytes to copy

// Set the current size of the virtual stream
pStatStg->cbSize.LowPart = cbSize.LowPart;
pStatStg->cbSize.HighPart = cbSize.HighPart;

    // we are done

cleanup:

    RETURN(hr);
}

// Methods not currently supported for our stream wrapper class:
//
// Seek, Clone, Revert, SetSize, UnlockRegion, LockRegion.
//
// These methods all return STG_E_INVALIDFUNCTION.

//$--CEDKVirtualStreamOnProperty::Seek---------------------------------------------------
//
// DESCRIPTION: Go to the specified position in the "stream".
//
// INPUT:       cbOffset    --  offset
//              dwOrigin    --  origin
//
// OUTPUT:      pcbPos  --  new position pointer -- may be NULL
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Seek(
        IN LARGE_INTEGER cbOffset,      // byte offset
        IN DWORD dwOrigin,              // origin
        OUT ULARGE_INTEGER * pcbPos)    // new position
{
    HRESULT         hr      =   NOERROR;

DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Seek()\n");

// check input parameters
hr = CHK_CEDKVirtualStreamOnProperty_Seek(cbOffset, dwOrigin, pcbPos);

if ( FAILED(hr) )
{
RETURN(hr);
}

// not implemented.
    RETURN(STG_E_INVALIDFUNCTION);

}

//$--CEDKVirtualStreamOnProperty::Clone---------------------------------------------------
//
// DESCRIPTION: Duplicate "stream"
//
// INPUT:       ppStrm  --  duplicate stream pointer
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Clone(
        OUT LPSTREAM * ppStrm)      // pointer to new stream
{
    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Clone()\n");

    RETURN(STG_E_INVALIDFUNCTION);
}

//$--CEDKVirtualStreamOnProperty::Revert---------------------------------------------------
//
// DESCRIPTION: Undo changes to stream
//
// INPUT:       None
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::Revert()
{
    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::Revert()\n");

    RETURN(STG_E_INVALIDFUNCTION);
}

//$--CEDKVirtualStreamOnProperty::SetSize---------------------------------------------------
//
// DESCRIPTION: Grow or shrink "stream"
//
// INPUT:       nSize   --  new size
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::SetSize(
        IN ULARGE_INTEGER nSize)    // new size
{
    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::SetSize()\n");

    RETURN(STG_E_INVALIDFUNCTION);
}

//$--CEDKVirtualStreamOnProperty::UnlockRegion---------------------------------------------------
//
// DESCRIPTION: Unlock section of "stream"
//
// INPUT:       cbOffset    --  offset
//              cbLength    --  length
//              dwFlags     --  flags
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::UnlockRegion(
        IN ULARGE_INTEGER cbOffset, // offset
        IN ULARGE_INTEGER cbLength, // length
        IN DWORD dwFlags)           // flags
{
    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::UnlockRegion()\n");

    RETURN(STG_E_INVALIDFUNCTION);
}

//$--CEDKVirtualStreamOnProperty::LockRegion---------------------------------------------------
//
// DESCRIPTION: lock section of "stream"
//
// INPUT:       cbOffset    --  offset
//              cbLength    --  length
//              dwFlags     --  flags
//
// OUTPUT:      None
//
// RETURNS:     HRESULT --  STG_E_INVALIDFUNCTION
//
//-----------------------------------------------------------------------------
STDMETHODIMP CEDKVirtualStreamOnProperty::LockRegion(
        IN ULARGE_INTEGER cbOffset, // offset
        IN ULARGE_INTEGER cbLength, // length
        IN DWORD dwFlags)           // flags
{
    DEBUGPUBLIC("CEDKVirtualStreamOnProperty::LockRegion()\n");

    RETURN(STG_E_INVALIDFUNCTION);
}


EXADMIN.CPP

// -----------------------------------------------------------------------------
// ExAdmin.CPP: Implements methods which support Exchange Admin Configuration
//              Extension Dialogs.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#if defined(UNICODE) || defined(_UNICODE)
#error EXADMIN cannot be compiled in UNICODE.
#endif

#include "edkafx.h"
#include "ExAdmin.h"
#include "helpers.h"
#include "ErrCpp.H"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------
// Following is used to determine the languages we support

struct EnumLangParams
{
WORD langRequested;
BOOL fFound;
};
typedef EnumLangParams*PEnumLangParams;

BOOL CALLBACK  EnumResTypesCallback(HANDLE hModule,LPTSTR lpszType,LONG lParam);
BOOL CALLBACK  EnumResNamesCallback(HANDLE hModule, LPCTSTR lpszType,
   LPTSTR lpszName, LONG lParam);
BOOL CALLBACK  fEnumLangProc(HANDLE hMod, LPCTSTR ptzType, LPCTSTR ptzName, WORD wLang, LONG lParam);

// -----------------------------------------------------------------------------

__inline HRESULT CHK_HrGeneral(
    IN VOID* pvExtensionData,       // Extension data for all sheets
    IN HWND  hDlg)                  // Window handle of the property sheet.
{
    if( !hDlg)
        RETURN( E_INVALIDARG);

    return( NOERROR);
}

// -----------------------------------------------------------------------------

__inline HRESULT CHK_bInitSheet(
    IN  ADMIN_ObjectInfo* poi,              // Server name & other DNs.
    IN  ULONG             fFlags,
    OUT ADMIN_SheetInfo** ppSheetInfo,      // Property sheet description array
    OUT UINT*             pcsi,             // Number of property sheets
    OUT VOID**            ppvNotUsed) // Local data (NOT USED)
{
    if( !TEST_READ_PTR( poi, sizeof( ADMIN_ObjectInfo)))
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( ppSheetInfo, sizeof( VOID *)))
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( pcsi, sizeof( UINT)))
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( ppvNotUsed, sizeof( VOID *)))
        RETURN( E_INVALIDARG);

    return( NOERROR);
}

// -----------------------------------------------------------------------------

__inline HRESULT CHK_ADMIN_Initialize(
    IN  ADMIN_AdministratorConnections* pAdminConnections,  // Global Administrator Connections
    IN  ADMIN_AdministratorFunction*    pAdminFunctions,    // Global admin function
    OUT ADMIN_ExtensionFunction**       ppExtensionFunction)// Global Extension function
{
    if( !TEST_READ_PTR( pAdminConnections, sizeof( ADMIN_AdministratorConnections)))
        RETURN( E_INVALIDARG);

    if( !TEST_READ_PTR( pAdminFunctions, sizeof( ADMIN_AdministratorFunction)))
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( ppExtensionFunction, sizeof( VOID *)))
        RETURN( E_INVALIDARG);

    if( !TEST_FUNCTION_PTR( (FARPROC) pAdminFunctions->pfnGetObjectData))
        RETURN( E_INVALIDARG);

    return( NOERROR);
}

// -----------------------------------------------------------------------------

__inline HRESULT CHK_LoadStringA(
UINT wID,// ID of resource
LPSTR szBuf,// Buffer to store resource
int cchBuf)// Size of buffer in chars
{
    if( wID == 0)
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( szBuf, cchBuf))
        RETURN( E_INVALIDARG);

    return( NOERROR);
}

__inline HRESULT CHK_LoadStringW(
UINT wID,// ID of resource
LPWSTR wzBuf,// Buffer to store resource
int cchBuf)// Size of buffer in chars
{
    if( wID == 0)
        RETURN( E_INVALIDARG);

    if( !TEST_WRITE_PTR( wzBuf, sizeof( WCHAR ) * cchBuf))
        RETURN( E_INVALIDARG);

    return( NOERROR);
}

//$--CInitDLL::InitInstance()---------------------------------------------------
// MFC initialization for the DLL.  No multi-threaded code allowed.
// -----------------------------------------------------------------------------

BOOL CInitDLL::InitInstance()
{
    // Any DLL initialization goes here.
    TRACE0("InitDLL.DLL initializing\n");
    SetDialogBkColor();     // Grey dialogs in the DLL as well.
    return TRUE;
}

//$--CInitDLL::ExitInstance()---------------------------------------------------
// Exit MFC for the DLL gracefully.  No multi-threaded code allowed.
// -----------------------------------------------------------------------------

int CInitDLL::ExitInstance()
{
    // Any DLL termination goes here (WEP-like code).
    return CWinApp::ExitInstance();
}

//$--CInitDLL::~CInitDLL()------------------------------------------------------
// Detach from property sheet window of Admin program then delete the CWnd we
// created in AdminDlgProc().  The reason we attach to the property sheet window
// of admin is so that MFC will give us Modal dialog boxes.
// -----------------------------------------------------------------------------

CInitDLL::~CInitDLL()
{
    if( m_pMainWnd)
    {
        m_pMainWnd->Detach();
        delete m_pMainWnd;
        m_pMainWnd = NULL;
    }
}

//$--CAdmin::HrSubclassWindow()-------------------------------------------------
// Make subclassing windows to controls with error checking a little easier.
// -----------------------------------------------------------------------------

HRESULT CADialog::HrSubclassWindow(
    int   nID,  // Id of a control in this dialog.
    CWnd& Wnd)  // Reference to MFC CWnd object to connect to Windows control.
{
    CWnd* pWnd = GetDlgItem( nID);
    if( !pWnd)
    {   // Could not find id (%d) of control in dialog.
        RETURN( E_FAIL);
    }

    // Connect the windows control to a MFC CWnd derived object.
    Wnd.SubclassWindow( pWnd->GetSafeHwnd());
    return( NOERROR);
}

//$--AdminDlgProc()-------------------------------------------------------------
// Subclasses the dialog the first time in and always calls the MFC dialog proc
// to handle the message. When message == WM_INITDIALOG then lParam is a pointer
// to the CAdminDialog.
// -----------------------------------------------------------------------------

LRESULT CALLBACK AdminDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    if( message == WM_INITDIALOG)
    {   // First time using this dialog so subclass its window so that MFC
        // code will work as expected.
        CAdminDialog* pAdminDlg = (CAdminDialog*) lParam;
        pAdminDlg->SubclassWindow( hDlg);

        // We attach to the property sheet window of admin so that MFC will give us
        // modal dialog boxes.  We don't want to subclass this window because we
        // would start receiving it's messages and we don't want that.
        if( !AfxGetApp()->m_pMainWnd)
        {   // We only need to do this once.
            AfxGetApp()->m_pMainWnd = new CWnd;
            AfxGetApp()->m_pMainWnd->Attach( GetParent( hDlg));
        }
    }

    // Prepare to call MFC dialog proc to handle the message.
    MSG msg;
    msg.hwnd = hDlg;
    msg.message = message;
    msg.wParam = wParam;
    msg.lParam = lParam;

    return( AfxDlgProc( hDlg, message, wParam, lParam));
}

// -----------------------------------------------------------------------------
// Message map for CAdminDialog.
// -----------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CAdminDialog, CDialog)
    //{{AFX_MSG_MAP(CAdminDialog)
    ON_MESSAGE( WM_CHILDACTIVATE, OnChildActivate)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

//$--CAdminDialog::CAdminDialog()-----------------------------------------------
// CONSTRUCTOR -- Records static information to be used later.
// -----------------------------------------------------------------------------

CAdminDialog::CAdminDialog(
    UINT iddDialog,     // The resource ID of your dialog.
    UINT idsName,       // The resource ID of the string containing the name
                        // used by admin for the tab of your property sheet.
    LPSTR lpszBlobName)// Name of extension data blob.
    : CADialog()        // Construct our ancestor.
{
    // Remember the caller's dialog, property sheet names, and extension blob name.
    m_iddDialog = iddDialog;
    m_idsName = idsName;

    // Remember the blob name as a wide string.  The pointer will
    // be NULL if the derived class does not have extension data.
    if( !lpszBlobName)
        m_lpwszBlobName = NULL;
    else
    {
        CHRESULT hr = HrStrAToStrW( lpszBlobName, &m_lpwszBlobName);
        if( FAILED( hr));
    }

    // Initialize other member variables.
    m_cExtData = 0;
    m_lpbExtData = NULL;
    m_cExtProps = 0;
    m_lpExtProps = NULL;

    // Add the "this" pointer of the dialog to a static list.  We need to know
    // about all dialogs so we can tell admin about them later.
    m_DlgList.AddTail( (void*) this);
}

// -----------------------------------------------------------------------------
// DESTRUCTOR
// -----------------------------------------------------------------------------

CAdminDialog::~CAdminDialog()
{
    MAPIFREEBUFFER( m_lpbExtData);
    MAPIFREEBUFFER( m_lpExtProps);
    MAPIFREEBUFFER( m_lpwszBlobName);
}

// -----------------------------------------------------------------------------
// If the caller does not pass in the lpszCaption then use the title of the
// parent window for the default title of our message box.
// -----------------------------------------------------------------------------

int CAdminDialog::MessageBox( LPCSTR lpszText, LPCSTR lpszCaption, UINT nType)
{
    if( lpszCaption == NULL)
        lpszCaption = m_sMsgBoxCaption;
    return( CWnd::MessageBox( lpszText, lpszCaption, nType));
}

int CAdminDialog::MessageBox2( int IDText, int IDCaption, UINT nType)
{
    CString MBText;
    CString MBCaption;
int nSize = -1;
int nLen;

// try buffer size of 256, then larger size until entire string is retrieved
do
{
nSize += 256;
nLen = LoadStringA(IDText, MBText.GetBuffer(nSize), nSize+1);
} while (nLen == nSize);
MBText.ReleaseBuffer();

if( IDCaption == 0)
        MBCaption = m_sMsgBoxCaption;
else
{
nSize = -1;
do
{
nSize += 256;
nLen = LoadStringA( IDCaption, MBCaption.GetBuffer(nSize), nSize+1);
} while (nLen == nSize);
MBCaption.ReleaseBuffer();
}

    return( CWnd::MessageBox( MBText, MBCaption, nType));
}

//$--CAdminDialog::LoadDialogTemplate()-----------------------------------------
// Use this function to create child dialog box templates.  It will ensure that
// the correct language and fonts are used.
//
// To create the dialog use the default constructor (no parameters) and then use
// the CDialog::InitModalIndirect() function to initialize the dialog.  Then you
// can call CDialog::DoModal() to process the dialog.
// -----------------------------------------------------------------------------

const HGLOBAL CAdminDialog::LoadDialogTemplate( UINT iddDialog)
{
    HGLOBAL hDlgTemplate = NULL;

    if( m_pAdminFunctions->pfnLoadDialogResource( AfxGetInstanceHandle(),
                          iddDialog, GetLanguageId(), (LPBYTE*) &hDlgTemplate))
        return( hDlgTemplate);
    else
        return( NULL);
}

//$--CAdminDialog::LoadString()------------------------------------------------
// Loads a string resource by id and language
// and (A version) converts to the current ANSI code page.
// Note that strings are stored in blocks of 16, and the ID gives
// us the number of the block, and the offset into the block.
// -----------------------------------------------------------------------------
int CAdminDialog::LoadStringA(UINT wID, LPSTR szBuf, int cchBuf)
{
    UINT    block, num;
    int     len = 0;
    HRSRC   hRC = NULL;
    HGLOBAL hgl = NULL;;
    LPWSTR  str = NULL;
    register UINT i;

    DEBUGPUBLIC( "CAdminDialog::LoadStringA()\n");
CHRESULT hr = CHK_LoadStringA( wID, szBuf, cchBuf);
    if( FAILED( hr))
        return( 0 );

szBuf[0] = '\0';

    block = (wID >> 4) + 1;// compute block number
    num = wID & 0xf;// compute offset into block

    hRC = FindResourceEx(AfxGetInstanceHandle(), RT_STRING, MAKEINTRESOURCE(block), GetLanguageId());
    if (!hRC)
goto Error;

hgl = LoadResource(AfxGetInstanceHandle(), hRC);
if (!hgl)
goto Error;

    str = (LPWSTR)LockResource(hgl);
    if (!str)
goto Error;

// Move up block to string we want
for (i = 0; i < num; i++)
    str += *str + 1;

// convert the string to current code page
len = WideCharToMultiByte(CP_ACP,
  WC_COMPOSITECHECK,
  str + 1, *str,
  szBuf, cchBuf - 1,
  NULL, NULL);

szBuf[min(cchBuf-1, *str)] = '\0';

Error:

if (hgl)
{
UnlockResource(hgl);// maybe not needed
    FreeResource(hgl);    // maybe not needed
}

    return len;
}

int CAdminDialog::LoadStringW(UINT wID, LPWSTR wzBuf, int cchBuf)
{

    UINT    block, num;
    int     len = 0;
    HRSRC   hRC = NULL;
    HGLOBAL hgl = NULL;
    LPWSTR  str = NULL;
    register UINT i;

    DEBUGPUBLIC( "CAdminDialog::LoadStringW()\n");
CHRESULT hr = CHK_LoadStringW( wID, wzBuf, cchBuf);
    if( FAILED( hr))
        return( 0 );

wzBuf[0] = TEXT('\0');

    block = (wID >> 4) + 1;// compute block number
    num = wID & 0xf;// compute offset into block

    hRC = FindResourceEx(AfxGetInstanceHandle(), RT_STRING, MAKEINTRESOURCE(block), GetLanguageId());
    if (!hRC)
goto Error;

hgl = LoadResource(AfxGetInstanceHandle(), hRC);
if (!hgl)
goto Error;

    str = (LPWSTR)LockResource(hgl);
    if (str)
    {
for (i = 0; i < num; i++)
    str += *str + 1;
wcsncpy(wzBuf, str + 1, min(cchBuf - 1, *str));
    }

wzBuf[min(cchBuf-1, *str) ] = '\0';

len = *str + 1;

Error:

if (hgl)
{
UnlockResource(hgl);// maybe not needed
    FreeResource(hgl);    // maybe not needed
}

    return len;
}

//$--CAdminDialog::GetExtBin()--------------------------------------------------
// Use this to get a binary extension data property.
// -----------------------------------------------------------------------------

LPSBinary CAdminDialog::GetExtBinary(
    ULONG iProp)        // Index of property.
{
    DEBUGPUBLIC( "CAdminDialog::GetExtBin()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_BINARY);
    if( FAILED( hr))
        return( NULL);

    return( &m_lpExtProps[iProp].Value.bin);
}

//$--CAdminDialog::GetExtString()-----------------------------------------------
// Use this to get a string extension data property.
// -----------------------------------------------------------------------------

LPSTR CAdminDialog::GetExtString(
    ULONG iProp)        // Index of property.
{
    DEBUGPUBLIC( "CAdminDialog::GetExtString()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_STRING8);
    if( FAILED( hr))
        return( NULL);

    return( m_lpExtProps[iProp].Value.lpszA);
}

//$--CAdminDialog::GetExtLong()-------------------------------------------------
// Use this to get a long extension data property.
// -----------------------------------------------------------------------------

LONG CAdminDialog::GetExtLong(
    ULONG iProp)        // Index of property.
{
    DEBUGPUBLIC( "CAdminDialog::GetExtLong()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_LONG);
    if( FAILED( hr))
        return( -1);

    return( m_lpExtProps[iProp].Value.l);
}

// -----------------------------------------------------------------------------
// Use this to get a boolean extension data property.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::GetExtBool(
    ULONG iProp)        // Index of property.
{
    DEBUGPUBLIC( "CAdminDialog::GetExtBool()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_BOOLEAN);
    if( FAILED( hr))
        return( FALSE);

    return( m_lpExtProps[iProp].Value.b);
}

//$--CAdminDialog::GetExtSysTime()----------------------------------------------
// Use this to get a system time extension data property.
// -----------------------------------------------------------------------------

FILETIME CAdminDialog::GetExtSysTime(
    ULONG iProp)        // Index of property.
{
    FILETIME ft = {0};

    DEBUGPUBLIC( "CAdminDialog::GetExtSysTime()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_SYSTIME);
    if( FAILED( hr))
        return( ft);

    return( m_lpExtProps[iProp].Value.ft);
}

//$--CAdminDialog::HrModExtBin()------------------------------------------------
// Use this to modify a binary extension data property.  It will allocate more
// memory only if the new buffer is larger.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrModExtBinary(
    IN ULONG  iProp,    // Index of property
    IN ULONG  cb,       // Count of new data bytes.
    IN const LPBYTE lpNew)    // New data bytes.
{
    DEBUGPUBLIC( "CAdminDialog::HrModExtBin()\n");
    CHRESULT hr = CHK_HrModExtBin( iProp, cb, lpNew);
    if( FAILED( hr))
        RETURN( hr);

    // New binary data will be copied to this address.
    LPBYTE lpb = m_lpExtProps[ iProp].Value.bin.lpb;

    if( cb > m_lpExtProps[ iProp].Value.bin.cb)
    {   // New binary data is too big so alloacate a new buffer to stick it in.
        CHRESULT hr = MAPIAllocateMore( cb, m_lpExtProps, (LPVOID*) &lpb);
        if( FAILED( hr))
            RETURN( hr);

        // Replace old buffer ptr to new one.
        m_lpExtProps[ iProp].Value.bin.lpb = lpb;
    }

    // Copy new data to MAPI buffer.
    memmove( lpb, lpNew, cb);
    m_lpExtProps[ iProp].Value.bin.cb = cb;
    return( NOERROR);
}

//$--CAdminDialog::HrModExtString()---------------------------------------------
// Use this to modify a string extension data property.  It will allocate more
// memory only if the new string is larger.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrModExtString(
    IN ULONG   iProp,    // Index of property
    IN LPCSTR lpszNew)  // New data string.
{
    DEBUGPUBLIC( "CAdminDialog::HrModExtString()\n");
    CHRESULT hr = CHK_HrModExtString( iProp, lpszNew);
    if( FAILED( hr))
        RETURN( hr);

    // Size of original string.
    ULONG cb = cbStrLenA( m_lpExtProps[ iProp].Value.lpszA);

    // Size of new string.
    ULONG cbNew = cbStrLenA( lpszNew);

    // New binary data will be copied to this address.
    LPSTR lpsz = m_lpExtProps[ iProp].Value.lpszA;

    if( cbNew > cb)
    {   // New binary data is too big so alloacate a new buffer to stick it in.
        CHRESULT hr = MAPIAllocateMore( cbNew, m_lpExtProps, (LPVOID*) &lpsz);
        if( FAILED( hr))
            RETURN( hr);

        // Replace old buffer ptr to new one.
        m_lpExtProps[ iProp].Value.lpszA = lpsz;
    }

    // Copy new data to MAPI buffer.
    memmove( lpsz, lpszNew, cbNew);
    return( NOERROR);
}

//$--CAdminDialog::HrModExtLong()-----------------------------------------------
// Use this to modify a long extension data property.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrModExtLong(
    IN ULONG  iProp,    // Index of property
    IN LONG   lNew)     // New long data value.
{
    DEBUGPUBLIC( "CAdminDialog::HrModExtLong()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_LONG);
    if( FAILED( hr))
        RETURN( hr);

    // Copy new data to MAPI buffer.
    m_lpExtProps[ iProp].Value.l = lNew;
    return( NOERROR);
}

//$--CAdminDialog::HrModExtBool()-----------------------------------------------
// Use this to modify a boolean extension data property.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrModExtBool(
    IN ULONG  iProp,    // Index of property
    IN BOOL   bNew)     // New boolean data value.
{
    DEBUGPUBLIC( "CAdminDialog::HrModExtBool()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_BOOLEAN);
    if( FAILED( hr))
        RETURN( hr);

    // Copy new data to MAPI buffer.
    m_lpExtProps[ iProp].Value.b = bNew;
    return( NOERROR);
}

//$--CAdminDialog::HrModExtSysTime()-----------------------------------------------
// Use this to modify a SysTime extension data property.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrModExtSysTime(
    IN ULONG    iProp,    // Index of property
    IN FILETIME ftNew)    // New boolean data value.
{
    DEBUGPUBLIC( "CAdminDialog::HrModExtSysTime()\n");
    CHRESULT hr = CHK_HrExtData( iProp, PT_SYSTIME);
    if( FAILED( hr))
        RETURN( hr);

    // Copy new data to MAPI buffer.
    m_lpExtProps[ iProp].Value.ft = ftNew;
    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Set the extension data properties to an existing property value array.  This
// can be used to create a blob for the first time.  To do this initialized the
// lpExtProps array with just property types and no real data.  Then use the
// HrMod...() functions to set the values.
//
// NOTE: Since this MUST be a MAPI allocated buffer, and since it is not as easy
//       to initialize a MAPI allocated buffer as a static one, we copy the
//       buffer the user passes in.  Therefore the user will need to free their
//       buffer if it is not a static one.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrSetExtProps(
    ULONG        cExtProps,     // Count of extension data properties.
    LPSPropValue lpExtProps)    // Array of properties to set extension data to.
{
    DEBUGPUBLIC( "CAdminDialog::HrSetExtProps()\n");
    CHRESULT hr = CHK_HrSetExtProps( cExtProps, lpExtProps);
    if( FAILED( hr))
        RETURN( hr);

    // Free old extension properties and set to the new ones.  We do not
    // free the old blob pointer here because its existence signifies that
    // we are not creating a new blob.
    MAPIFREEBUFFER( m_lpExtProps);
    m_cExtProps = 0;

    // Make sure that all property tags have a non zero property id. This is done
    // because ScCountProps() does a validation to make sure all properties have a
    // non zero id.  This should be fixed in some future version of MAPI.
    ULONG iProp = 0;
    for( iProp = 0; iProp < cExtProps; iProp++)
    {
        if( PROP_ID( lpExtProps[ iProp].ulPropTag) == 0)
            lpExtProps[ iProp].ulPropTag = PROP_TAG( PROP_TYPE( lpExtProps[ iProp].ulPropTag), 1);
    }

    // Count the bytes needed for the new property array.
    ULONG cBytes = 0;
    hr = ScCountProps( cExtProps, lpExtProps, &cBytes);
    if( FAILED( hr))
        RETURN( hr);

    // Allocate a new buffer to hold the copied properties.
    hr = MAPIAllocateBuffer( cBytes, (LPVOID*) &m_lpExtProps);
    if( FAILED( hr))
        RETURN( hr);
    memset( m_lpExtProps, 0, cBytes);

    // Copy the subscriber properties to the new buffer.
    ULONG cBytesCopied = 0;
    hr = ScCopyProps( cExtProps, lpExtProps, m_lpExtProps, &cBytesCopied);
    if( FAILED( hr))
        RETURN( hr);
    ASSERTERROR( cBytes == cBytesCopied, "ScCountProps & ScCopyProps are inconsistent!");

    // We completed this successfuly so set the count.
    m_cExtProps = cExtProps;

    DataHasChanged();

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Loads the extension data into a buffer that is contained in this object.  Use
// GetExtCount() and GetExtData() to get individual items. Use the HrMod...()
// functions to modify a property value.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrLoadExtData()
{
    MAPIFREEBUFFER( m_lpbExtData);
    m_cExtData = 0;

    MAPIFREEBUFFER( m_lpExtProps);
    m_cExtProps = 0;

    if( m_lpwszBlobName == NULL)
        return( NOERROR);

    // Determine the size of the buffer needed to hold the data.
    UINT cTemp;
    RC rc = m_pAdminFunctions->pfnGetObjectDataSize( GetSafeHwnd(), m_lpwszBlobName, &cTemp);
    if(RC_FAILED(rc))
        RETURN( E_FAIL);

    // Convert UINT to a ULONG.
    m_cExtData = cTemp;

    // Allocate a buffer to hold the extension data.
    CHRESULT hr = MAPIAllocateBuffer( m_cExtData, (LPVOID*) &m_lpbExtData);
    if( FAILED( hr))
        RETURN( hr);

    // Get the extension data.
    rc = m_pAdminFunctions->pfnGetObjectData( GetSafeHwnd(), m_lpwszBlobName, m_lpbExtData, m_cExtData);
    if( RC_FAILED(rc))
        RETURN( E_FAIL);

    // Unpack the data into a MAPI style property value array.
    hr = HrCfgUnpackData( m_cExtData, m_lpbExtData, NULL, &m_cExtProps, &m_lpExtProps);
    if( FAILED( hr))
        RETURN( hr);

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Saves the extension data that is held in a buffer contained in this object.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrSaveExtData()
{
    if( m_lpwszBlobName == NULL)
        return( NOERROR);

    if( m_cExtProps == 0 || !TEST_READ_PTR( m_lpExtProps, sizeof( SPropValue) * m_cExtProps))
        return( E_FAIL);

    // If we did not have extension blob data then we are creating a new blob.
    BOOL bNew = (m_lpbExtData == NULL);
    MAPIFREEBUFFER( m_lpbExtData);
    m_cExtData = 0;

    // Pack the configuration data from a MAPI style property value array.
    CHRESULT hr = HrCfgPackDataW( m_lpwszBlobName, m_cExtProps, m_lpExtProps, &m_cExtData, &m_lpbExtData);
    if( FAILED( hr))
        RETURN( hr);

    // Set the extension data.
    RC rc = m_pAdminFunctions->pfnSetObjectData( GetSafeHwnd(), m_lpbExtData, m_cExtData, bNew);
    if( RC_FAILED(rc))
        RETURN( E_FAIL);

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Add service to be tracked by Server Monitor.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrAddService(
    IN LPSTR lpszServiceName)      // SHORT name of the service.
{
    // Not UNICODE, so convert to wide string.
    CMAPIBuffer< LPWSTR> lpwszServiceName;
    CHRESULT hr = HrStrAToStrW( lpszServiceName, &lpwszServiceName);
    if( FAILED( hr))
        return( hr);

    if( !m_pAdminFunctions->pfnAddService( GetSafeHwnd(), lpwszServiceName))
        RETURN( E_FAIL);

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Remove service to be tracked by Server Monitor.
// -----------------------------------------------------------------------------

HRESULT CAdminDialog::HrRemoveService(
    IN LPSTR lpszServiceName)      // SHORT name of the service.
{
    // Not UNICODE, so convert to wide string.
    CMAPIBuffer< LPWSTR> lpwszServiceName;
    CHRESULT hr = HrStrAToStrW( lpszServiceName, &lpwszServiceName);
    if( FAILED( hr))
        return( hr);

    if( !m_pAdminFunctions->pfnRemoveService( GetSafeHwnd(), lpwszServiceName))
        RETURN( E_FAIL);

    return( NOERROR);
}

//$--CAdminDialog::GetNameList()------------------------------------------------
// You MUST release the array with FreeNameList.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::GetNameList(
    OUT int*     lpcNameList,       // Ptr to number of WIDE strings in the array.
    OUT LPWSTR** lppwszNameList)    // Ptr to a ptr of WIDE string array.
{
    BOOL fRes = m_pAdminFunctions->pfnGetNameList( GetSafeHwnd(), lpcNameList, lppwszNameList);
    if( !fRes)
        RETURN( E_FAIL);

    return( NOERROR);
}

//$--CAdminDialog::SetNameList()------------------------------------------------
// Set the name list.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::SetNameList(
    IN int      cNameList,        // Number of WIDE strings in the array.
    IN LPWSTR*  lpwszNameList)    // Ptr to a WIDE string array.
{
    BOOL fRes = m_pAdminFunctions->pfnSetNameList( GetSafeHwnd(), cNameList, lpwszNameList);
    if( !fRes )
        RETURN( E_FAIL);

    return( NOERROR);
}

//$--CAdminDialog::fSetLcid()---------------------------------------------------
// Set the LCID we will use.

// Note that Exchange Admin could ask for any locale, but this dll typically only
// supports a subset.  This function checks to see if the dll has resources of the
// requested locale, and sets a default if not.
// -----------------------------------------------------------------------------
void CAdminDialog::SetLcid(LCID lcid)
{
EnumLangParams ELP;

// We're just concerned with the primary language
ELP.langRequested = MAKELANGID(PRIMARYLANGID( LANGIDFROMLCID(lcid) ), SUBLANG_NEUTRAL);
ELP.fFound = FALSE;

    // Check that we support this language, use default if not
    // This check works by walking the resource tree. It stops as soon as one resource with
    // a matching language is found.
if (!EnumResourceTypes(AfxGetInstanceHandle(), (ENUMRESTYPEPROC) EnumResTypesCallback, (LONG) &ELP))
{
// Note: EnumResourceTypes returns false if one of the callbacks returns false
// (indicating that the search can be stopped)
}

// Default to English
if (ELP.fFound)
m_lcid = ELP.langRequested;
else
m_lcid = MAKELCID( MAKELANGID( LANG_ENGLISH, SUBLANG_NEUTRAL),  SORT_DEFAULT);

return;  // We always succeed
}

// $--CAdminDialog::bInitSheet()------------------------------------------------
// This function initializes the property sheet info and returns it to ADMIN.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::bInitSheet(              // RETURNS: TRUE if initialization OK
    IN  ADMIN_ObjectInfo* poi,              // Server name & other DNs.
    IN  ULONG             fFlags,
    OUT ADMIN_SheetInfo** ppSheetInfo,      // Property sheet description array
    OUT UINT*             pcsi,             // Number of property sheets
    OUT VOID**            ppNotUsed)        // Local data (NOT USED)
{
    ASSERT_READ_PTR( poi, sizeof( ADMIN_ObjectInfo), "Admin sent us a bad pointer.");

    // Remember these for later usage.
    m_pAdminObjectInfo = poi;
    m_bReadOnly = (fFlags & fxfReadOnly) > 0;

// Choose the language we will display
SetLcid( poi->lcid );

// Allocate memory for the property sheet info array.
    *pcsi = m_DlgList.GetCount();
    m_pSheetInfo = new ADMIN_SheetInfo[ *pcsi];
    if( !m_pSheetInfo)
    {   // Memory allocation error!
        return( FALSE);
    }

    // Initialize Admin property sheet info array.
    POSITION pos = m_DlgList.GetHeadPosition();
    ADMIN_SheetInfo* pSheetInfo = m_pSheetInfo;
    while( pos)
    {   // Tell admin about all of the CAdminDialog derived objects that
        // have been instantiated.
        CAdminDialog* pAdminDlg = (CAdminDialog*) m_DlgList.GetNext( pos);

        pSheetInfo->hInstance = AfxGetInstanceHandle();
        pSheetInfo->iddDialog = pAdminDlg->m_iddDialog;
        pSheetInfo->lpfnDlgProc = (DLGPROC) AdminDlgProc;
        pSheetInfo->idsName = pAdminDlg->m_idsName;
        pSheetInfo->lParam = (LPARAM) pAdminDlg;
        pSheetInfo->langid = GetLanguageId();

        pSheetInfo ++;
    }

    // Set return values.
    *ppSheetInfo = m_pSheetInfo;
    *ppNotUsed = NULL;

    return( TRUE);  // TRUE indicates success.
}

// $--CAdminDialog::DeinitSheet()-----------------------------------------------
// Admin calls this when the property sheet dialog box is about to be removed
// and after the OnDestroy() functions of the individual property page dialogs
// have been called.
// -----------------------------------------------------------------------------

void CAdminDialog::DeinitSheet(         // Returns nothing
    IN VOID* pNotUsed)           // Extension data.
{
    delete m_pSheetInfo;
    m_pSheetInfo = NULL;
}

// $--CAdminDialog::FindDlg()---------------------------------------------------
// Finds and returns a pointer to a CAdminDialog object using its dialog handle.
// -----------------------------------------------------------------------------

CAdminDialog* CAdminDialog::FindDlg( HWND hDlg)
{
    POSITION pos = m_DlgList.GetHeadPosition();
    while( pos)
    {   // Get pointer to next CAdminDialog object and see if its window
        // handle matches the one we are looking for.
        CAdminDialog* pAdminDlg = (CAdminDialog*) m_DlgList.GetNext( pos);
        if( pAdminDlg->m_hWnd == hDlg)
            return( pAdminDlg);             // Found it!
    }
    return( NULL);      // Not found.
}

// $--CAdminDialog::ADMIN_Initialize()------------------------------------------
// Helper function used to access private and protected members.
// -----------------------------------------------------------------------------

VOID CAdminDialog::ADMIN_Initialize(                        // Returns nothing.
    IN  ADMIN_AdministratorConnections* pAdminConnections,  // Global Administrator Connections
    IN  ADMIN_AdministratorFunction*    pAdminFunctions)    // Global admin function
{
    if( m_pMAPISession != pAdminConnections->psesMapi)
    {   // Add a reference to the admin's MAPI session.
        m_pMAPISession = pAdminConnections->psesMapi;
        m_pAddrBook = pAdminConnections->pab;
        m_pABContainer = pAdminConnections->pabContainer;
    }

    m_pAdminFunctions = pAdminFunctions;
}

// $--CAdminDialog::OnInitDialog()----------------------------------------------
// Calls the admin function to set the icon and title for the property sheet.
// Make sure your derived classes OnInitDialog() calls this function.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Get the title of the parent window to use as the default
    // caption of our message boxes.
    GetParent()->GetWindowText( m_sMsgBoxCaption);

    // Set the icon and title for this dialog.
    m_pAdminFunctions->pfnSetIcon( m_hWnd, IDC_ADMINICON);
    m_pAdminFunctions->pfnSetTitle( m_hWnd, IDC_TITLE);

    // Load the extension data if there is any.
    CHRESULT hr = HrLoadExtData();
    if( FAILED( hr));

    return TRUE;  // Return TRUE unless you set the focus to a control.
}

//$--CAdminDialog::OnChildActivate()--------------------------------------------
// This gets called when we receive a WM_CHILDACTIVATE message.  This indicates
// that the property sheet has just been brought into focus.
// -----------------------------------------------------------------------------

LONG CAdminDialog::OnChildActivate( UINT, LONG )
{
    // Load the extension data if there is any.
    CHRESULT hr = HrLoadExtData();
    if( FAILED( hr));

    // Call the user's function to refresh their dialog.
    Refresh();

    return( hr);
}

//$--CAdminDialog::Refresh()----------------------------------------------------
// A do nothing function so the user is not required to implement this function
// if they really don't need it.
// -----------------------------------------------------------------------------

void CAdminDialog::Refresh()
{
}

//$--CAdminDialog::InvalidEntry()-----------------------------------------------
// Displays a message box with a resource string for invalid entries.  After
// the user presses OK the appropriate control gets focus.
// -----------------------------------------------------------------------------

void CAdminDialog::InvalidEntry( int nResourceStrID, CWnd& wndCtrl)
{
    CString sResourceString;
    if( sResourceString.LoadString( nResourceStrID))
        MessageBox2( nResourceStrID);
    else
    {   // The nResourceStrID must be bad, display a general
        // purpose message and log the error.
        HR_LOG( E_FAIL);
        MessageBoxA( "Invalid entry.");
    }
    wndCtrl.SetFocus();
}

// $--CAdminDialog::bSaveData()-------------------------------------------------
// Override this virtual function if you need to validate data when the property
// sheet changes, or when the Ok or Apply button is pressed.  This is the appropriate
// place to check edit control values, list box selections etc.
// Default behavior is to do nothing but continue.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::bSaveData()
{
    // Save the extension data if there is any.
    CHRESULT hr = HrSaveExtData();
    if( FAILED( hr))
        return( FALSE);

    return( TRUE);
}

// $--CAdminDialog::bCommitData()-----------------------------------------------
// Override this virtual function if you need to save data to permanent storage
// when the Ok or Apply button is pressed. (You can assume the data is good, because
// you should have checked it in the bSaveData() call.)
// Default behavior is to do nothing but continue.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::bCommitData()
{
    return( TRUE);
}

// $--CAdminDialog::bHasHelp()--------------------------------------------------
// Override this virtual function if you supply help.
// Default behavior is NO help.
// -----------------------------------------------------------------------------

BOOL CAdminDialog::bHasHelp()
{
    MessageBox( "Help is not available for this property sheet.");
    return( FALSE);
}

// $--CAdminDialog::DoHelp()----------------------------------------------------
// Override this virtual function if you supply help.
// Default behavior is to display a message.
// -----------------------------------------------------------------------------

VOID CAdminDialog::DoHelp()
{
    MessageBox( "Help is not available for this property sheet.");
}

// $--bInitSheet()--------------------------------------------------------------
// This function initializes the property sheet info and returns it to admin.
// It uses the static member function since this one can NOT access the static
// member data.
// -----------------------------------------------------------------------------

BOOL PASCAL bInitSheet(                 // RETURNS: TRUE if initialization OK
    IN  ADMIN_ObjectInfo* poi,              // Computer name & DN.
    IN  ULONG             fFlags,
    OUT ADMIN_SheetInfo** ppSheetInfo,      // Property sheet description array
    OUT UINT*             pcsi,             // Number of property sheets
    OUT VOID**            ppNotUsed) // Local data
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    DEBUGPUBLIC( "bInitSheet()\n");
    if( FAILED( CHK_bInitSheet( poi, fFlags, ppSheetInfo, pcsi, ppNotUsed)))
        return( FALSE);

    return( CAdminDialog::bInitSheet( poi, fFlags, ppSheetInfo, pcsi, ppNotUsed));
}

// $--DeinitSheet()-------------------------------------------------------------
// Admin calls this when the property sheet dialog box is about to be removed.
// -----------------------------------------------------------------------------

void PASCAL DeinitSheet(                // Returns nothing
    IN VOID* pNotUsed)           // Extension data.
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    CAdminDialog::DeinitSheet( pNotUsed);
}

// $--bInstallExtension()-------------------------------------------------------
// -----------------------------------------------------------------------------

BOOL PASCAL bInstallExtension(ADMIN_ObjectInfo * poi)
{
    return(TRUE);
}

// $--bDeinstallExtension()-----------------------------------------------------
// -----------------------------------------------------------------------------

BOOL PASCAL bDeinstallExtension(ADMIN_ObjectInfo * poi)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    return(TRUE);
}

// $--bSaveData()-----------------------------------------------------------
// Called by admin when the property sheet page loses focus.
// -----------------------------------------------------------------------------

BOOL PASCAL bSaveData(   // Returns: TRUE if successful.
    IN VOID* pNotUsed,       // Extension data for all sheets
    IN HWND  hDlg)           // Window handle of the property sheet.
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    DEBUGPUBLIC( "bSaveData()\n");
    if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        return( FALSE);

    CAdminDialog* pAdminDlg = CAdminDialog::FindDlg( hDlg);
    if( !pAdminDlg)
    {
        HR_LOG( E_FAIL);
        return( FALSE);
    }
    return( pAdminDlg->bSaveData());
}

// $--bCommitData()-------------------------------------------------------------
// This function is called by admin when the user presses either the OK or the
// APPLY buttons.
// -----------------------------------------------------------------------------

BOOL PASCAL bCommitData(            // Returns:  TRUE if successful.
    IN VOID* pNotUsed,       // Extension data.
    IN HWND  hDlg)                  // Handle of the property sheet to be committed.
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    DEBUGPUBLIC( "bCommitData()\n");
    if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        return( FALSE);

    CAdminDialog* pAdminDlg = CAdminDialog::FindDlg( hDlg);
    if( !pAdminDlg)
    {
        HR_LOG( E_FAIL);
        return( FALSE);
    }
    return( pAdminDlg->bCommitData());
}

// $--bHasHelp()----------------------------------------------------------------
// Called by admin to find out if we support help.
// -----------------------------------------------------------------------------

BOOL PASCAL bHasHelp(               // RETURNS: true if help is available.
    IN VOID* pNotUsed,       // Ext dll managed data area.
    IN HWND  hDlg)                  // Window handle of the property sheet.
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    DEBUGPUBLIC( "bHasHelp()\n");
    if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        return( FALSE);

    CAdminDialog* pAdminDlg = CAdminDialog::FindDlg( hDlg);
    if( !pAdminDlg)
    {
        HR_LOG( E_FAIL);
        return( FALSE);
    }
    return( pAdminDlg->bHasHelp());
}

// $--DoHelp()------------------------------------------------------------------
// Called by admin to displays help for the user.
// -----------------------------------------------------------------------------

VOID PASCAL DoHelp(                 // RETURNS: Nothing
    IN VOID* pNotUsed,       // Ext dll managed data area.
    IN HWND  hDlg)                  // Window handle of the property sheet.
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    DEBUGPUBLIC( "DoHelp()\n");
    if( FAILED( CHK_HrGeneral( pNotUsed, hDlg)))
        return;

    CAdminDialog* pAdminDlg = CAdminDialog::FindDlg( hDlg);
    if( !pAdminDlg)
    {
        HR_LOG( E_FAIL);
        return;
    }
    pAdminDlg->DoHelp();
}

// -----------------------------------------------------------------------------
// NOTE: The admin program will keep a pointer to this.  These are pointers to
//       the functions that admin will want to access.
// -----------------------------------------------------------------------------

ADMIN_ExtensionFunction extensionFunctions =
{
    ADMIN_ExtensionAPIVersion,
    bInstallExtension,
    bDeinstallExtension,
    bInitSheet,
    bShowPage,  // This one can be defined in your dll code, otherwise it will come from ShowPage.CPP.
    iStartPage, // This one can be defined in your dll code, otherwise it will come from StrtPage.CPP.
    bHasHelp,
    DoHelp,
    bSaveData,
    bCommitData,
    DeinitSheet
};

// $--ADMIN_Initialize()--------------------------------------------------------
// Exported function. This is the first function called by admin after the DLL
// has been initialized.
// -----------------------------------------------------------------------------

extern "C" VOID PASCAL ADMIN_Initialize(                    // Returns nothing.
    IN  ADMIN_AdministratorConnections* pAdminConnections,  // Global Administrator Connections
    IN  ADMIN_AdministratorFunction*    pAdminFunctions,    // Global admin function
    OUT ADMIN_ExtensionFunction**       ppExtensionFunction)// Global Extension function
{
    DEBUGPUBLIC( "ADMIN_Initialize()\n");
    if( FAILED( CHK_ADMIN_Initialize( pAdminConnections, pAdminFunctions, ppExtensionFunction)))
        return;

    CAdminDialog::ADMIN_Initialize( pAdminConnections, pAdminFunctions);

    // Tell admin where it can find the rest of our functions that it needs.
    *ppExtensionFunction = &extensionFunctions;
}

// -----------------------------------------------------------------------------
// Detemine what languages this dll supports

BOOL CALLBACK EnumResTypesCallback(HANDLE hModule,LPTSTR lpszType,LONG lParam)
{
PEnumLangParams pELP = (PEnumLangParams) lParam;

    EnumResourceNames((HINSTANCE) hModule, lpszType, (ENUMRESNAMEPROC) EnumResNamesCallback, lParam);

if (pELP->fFound)
return FALSE;// terminates search
else
return TRUE;
}

BOOL CALLBACK EnumResNamesCallback(HANDLE hModule, LPCTSTR lpszType,
   LPTSTR lpszName, LONG lParam)
{
PEnumLangParams pELP = (PEnumLangParams) lParam;

    EnumResourceLanguages((HINSTANCE)hModule, lpszType, lpszName, (ENUMRESLANGPROC) fEnumLangProc,
  lParam);

if (pELP->fFound)
return FALSE;// terminates search
else
return TRUE;
}

BOOL CALLBACK fEnumLangProc(HANDLE hMod, LPCTSTR ptzType, LPCTSTR ptzName, WORD wLang, LONG lParam)
{
PEnumLangParams pELP = (PEnumLangParams) lParam;

if (wLang == pELP->langRequested)
pELP->fFound = TRUE;

if (pELP->fFound)
return FALSE;// terminates search
else
return TRUE;
}


HELPERS.H

// -----------------------------------------------------------------------------
// Helpers.h: Template Class that wraps MAPI buffers or interfaces so that you
//            don't have to concern yourself with freeing or releasing them.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _HELPERS_H_
#define _HELPERS_H_

// -----------------------------------------------------------------------------
// Use this class to wrap a pointer to a mapi buffer.  It will automaticly get
// freed when it goes out of scope.  Use the instance of this just as you would
// its base type.  Example:
//     CMAPIBuffer< LPENTRYID>  lpeid;
//     HRESULT hr = HrMAPIFindDefaultMsgStore( m_pMAPISession, &cbeid, &lpeid);
//     hr = m_pMAPISession->OpenMsgStore( 0, cbeid, lpeid, ...
//
// The intended use of this class is for declaring mapi buffer pointers on the
// stack or inside of another class or structure.  There is NO value in creating
// an instance of this class on the heap with new().  It's sole purpose in life
// is to keep you from having to remember to free the buffer.
//
// If you need to reuse the pointer be sure to call MAPIFREEBUFFER before
// reusing it.  Example:
//     CMAPIBuffer< LPENTRYID>  lpeid;
//     HRESULT hr = HrMAPIFindDefaultMsgStore( m_pMAPISession, &cbeid, &lpeid);
//     ... // Do some other work...
//     MAPIFREEBUFFER( lpeid);
//     hr = HrGetSomeOtherEID( &cbeid, &lpeid);
// -----------------------------------------------------------------------------

template< class TYPE>
class CMAPIBuffer
{
public:
    CMAPIBuffer( TYPE ptr = NULL)   { m_ptr = ptr;}
    ~CMAPIBuffer()                  { MAPIFREEBUFFER( m_ptr);}

    // Returns a pointer to the TYPE by just specifying the object.
    operator TYPE() {return( m_ptr);}

    // Returns the address of the object correct for the base type.
    TYPE* operator &() {return( &m_ptr);}

    void operator =(LPVOID lpv)  {m_ptr = (TYPE) lpv;}

protected:
    TYPE m_ptr;
};

// -----------------------------------------------------------------------------
// Use this class to wrap a pointer to a mapi interface.  It is nearly identical
// to the above class except that it releases the interface pointer when the
// instance of the object goes out of scope.
//
// The intended use of this class is for declaring mapi interface pointers on the
// stack or inside of another class or structure.  There is NO value in creating
// an instance of this class on the heap with new().  It's sole purpose in life
// is to keep you from having to remember to release the interface.
// -----------------------------------------------------------------------------

template< class TYPE>
class CMAPIInterface
{
public:
    CMAPIInterface( TYPE ptr = NULL)   { m_ptr = ptr;}
    ~CMAPIInterface()                  { ULRELEASE( m_ptr);}

    // Returns a pointer to the TYPE by just specifying the object.
    operator TYPE()         {return( m_ptr);}

    // Returns the address of the object correct for the base type.
    TYPE* operator &() {return( &m_ptr);}

    // Returns a pointer to the TYPE for -> operations.
    TYPE operator ->()      {return( m_ptr);}

    void operator =(LPVOID lpv)  {m_ptr = (TYPE) lpv;}

protected:
    TYPE m_ptr;
};

// $--CMAPIIsInitialized--------------------------------------------------------
// Initialize MAPI using MAPIInitialize().  If it is successful then create an
// instance of this object which will uninitialize MAPI at destruction.
// -----------------------------------------------------------------------------

class CMAPIIsInitialized
{
public:
    ~CMAPIIsInitialized()     {MAPIUninitialize();}
};

// $--CMAPISession--------------------------------------------------------------
// Works just like CMAPIInterface but it also Logs off at destruction.
// -----------------------------------------------------------------------------

class CMAPISession : public CMAPIInterface< LPMAPISESSION>
{
public:
    // DESTRUCTOR logs off of the MAPI session and releases the session handle.
    ~CMAPISession()
    {
        if( m_ptr)
            m_ptr->Logoff( 0L, 0L, 0L);
    }
};

// -----------------------------------------------------------------------------

#endif // _HELPERS_H_


SHOWPAGE.CPP

// -----------------------------------------------------------------------------
// ShowPage.CPP: Implements a default bShowPage helper function for the ExAdmin
//               lib.  There is only one function in this file because it is
//               intended that the user can override this function by simply
//               defining their own.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"

// $--bShowPage()---------------------------------------------------------------
// This is called once per standard property sheet.  Not at all for our property
// sheets.  If you want to disable some or all of the standard property sheets
// Then define this function in your code.  Your function will need to return
// FALSE to disable a default property sheet.
// -----------------------------------------------------------------------------

BOOL PASCAL bShowPage( UINT iddAdminPage)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    BOOL bRet = TRUE;   // Show the standard property sheet.
    return( bRet);
}

// -----------------------------------------------------------------------------


STRTPAGE.CPP

// -----------------------------------------------------------------------------
// ShowPage.CPP: Implements a default bStartPage helper function for the ExAdmin
//               lib.  There is only one function in this file because it is
//               intended that the user can override this function by simply
//               defining their own.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"

// $--iStartPage()--------------------------------------------------------------
// This function is called once durring initialization.  Return -1 to cause the
// first standard property sheet to be displayed.  Or return the index of one of
// our property sheets to have it come up first.  The user can override this
// function by simply defining their own function in their code.
// -----------------------------------------------------------------------------

INT PASCAL iStartPage( void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    int i = -1; // Default to the first standard property sheet.
    return( i);
}

// -----------------------------------------------------------------------------


EXCHINST.C

// --exchinst.c-----------------------------------------------------------------
//
// Functions for installing Exchange objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "dapi.h"

#include "exchinst.chk"

//
// Miscellaneous Defines
//

#define MAX_CSV_LINE_SIZ                2048
#define MAX_WORD                        0xFFFF
#define FILE_PREFIX                     "EXCH"
#define NEW_LINE                        "\r\n"

#define EXCHINST_DELIM                  '\t'
#define EXCHINST_QUOTE                  '"'
#define EXCHINST_MV_SEP                 '%'

#define SZ_EXCHINST_DELIM               "\t"
#define SZ_EXCHINST_QUOTE               "\""
#define SZ_EXCHINST_MV_SEP              "%"

#define BEGIN_CSV_LINE(a,b)  lstrcpy(a, b)

#define APPEND_CSV_LINE(a,b)           \
    {                                  \
lstrcat(a, SZ_EXCHINST_DELIM); \
lstrcat(a, b);                 \
    }

#define DELETEFILE(_file)                       \
{                                               \
    if((_file) != NULL && (_file)[0] != 0)      \
    {                                           \
if(! DeleteFile ((_file)))              \
{                                       \
    HRESULT _hr = HR_LOG(E_FAIL);       \
}                                       \
    }                                           \
    (_file)[0] = 0;                             \
}

//
// Attribute Defines
//

#define OBJ_CLASS                       "Obj-Class"
#define MODE                            "Mode"
#define ADDR_SYNTAX                     "Address-Syntax"
#define ADDR_ENTRY_DT                   "Address-Entry-Display-Table"
#define ADDR_TYPE                       "Address-Type"
#define ADMIN_DISPLAY_NAME              "Admin-Display-Name"
#define DISPLAY_NAME                    "Display-Name"
#define COMMON_NAME                     "Common-Name"
#define DELIVERY_MECHANISM              "Delivery-Mechanism"
#define DELIV_EXT_CONT_TYPES            "Deliv-Ext-Cont-Types"
#define EXTENSION_DATA                  "Extension-Data"
#define EXTENSION_NAME                  "Extension-Name"
#define HELP_FILE_NAME                  "Help-File-Name"
#define COMPUTER_NAME                   "Computer-Name"
#define GATEWAY_PROXY                   "Gateway-Proxy"
#define HOME_SERVER                     "Home-Server"
#define FILE_VERSION                    "File-Version"
#define PER_MSG_DDT                     "Per-Msg-Dialog-Display-Table"
#define PER_RECIP_DDT                   "Per-Recip-Dialog-Display-Table"
#define PROXY_GENERATOR_DLL             "Proxy-Generator-DLL"
#define ROUTING_LIST                    "Routing-List"
#define OBJ_DIST_NAME                   "Obj-Dist-Name"
#define ORGANIZATION                    "Organization"
#define ORGANIZATIONAL_UNIT             "Organizational-Unit"
#define CONTAINER                       "Container"
#define HELP_DATA16                     "Help-Data16"
#define HELP_DATA32                     "Help-Data32"
#define OBJ_ADMIN                       "Obj-Admins"
#define SITE_ADDRESSING                 "Site-Addressing"
#define ADMIN_EXTENSION_DLL             "Admin-Extension-Dll"
#define CAN_PRESERVE_DNS                "Can-Preserve-DNs"
#define HEURISTICS                      "Heuristics"
#define CONTAINER_INFO                  "Container-Info"

//
// Attribute Value Defines
//

#define OBJ_CLASS_GW                    "Mail-Gateway"
#define OBJ_CLASS_MB                    "Mailbox-Agent"
#define OBJ_CLASS_SITE                  "Site-Addressing"
#define OBJ_CLASS_ADDR_TYPE             "Addr-Type"
#define OBJ_CLASS_ADDR_TEMPLATE         "Address-Template"
#define OBJ_CLASS_ADMIN_EXTENSION       "Admin-Extension"
#define OBJ_CLASS_COMPUTER              "Computer"
#define OBJ_CLASS_CONTAINER             "Container"

//
// Container Information Defines
//

#define ADDRESS_TEMPLATE_CONTAINER_INFO "256"

//
// Import Mode Defines
//

#define MODE_CREATE                                             "Create"
#define MODE_MODIFY                                             "Modify"
#define MODE_DELETE                                             "Delete"

#define DELIVERY_MECHANISM_GW                   "2"
#define DELIVERY_MECHANISM_MB                   "0"

#define CONTAINER_CONFIGURATION         "/cn=Configuration"
#define CONTAINER_GW                    "/cn=Configuration/cn=Connections"
#define CONTAINER_ADDR_TYPE             "/cn=Configuration/cn=Addressing/cn=Address-Types"
#define CONTAINER_ADDR_TEMPLATE         "/cn=Configuration/cn=Addressing/cn=Address-Templates"
#define CONTAINER_SERVERS               "/cn=Configuration/cn=Servers"
#define CONTAINER_SITE_ADDR             "/cn=Configuration/cn=Site-Addressing"
#define CONTAINER_ADD_INS               "/cn=Configuration/cn=Add-Ins"

#define ACCOUNT_NAME                    "Obj-Users"

//
//  Common macros.
//

#define CREATEKEY(hkParent, pszName, hkOut, dwDisposition) \
    RegCreateKeyEx(hkParent, pszName, 0, "", REG_OPTION_NON_VOLATILE, \
KEY_ALL_ACCESS, NULL, &hkOut, &dwDisposition)

#define SETSZVALUE(hk, pszName, pszValue) \
    RegSetValueEx(hk, pszName, 0, REG_SZ, pszValue, lstrlen(pszValue)+1)

#define SETMULTISZVALUE(hk, pszName, pszValue) \
    RegSetValueEx(hk, pszName, 0, REG_MULTI_SZ, pszValue, \
CbMultiSz(pszValue)+sizeof(CHAR))

#define FREEHKEY(hk) \
    if(hk != INVALID_HANDLE_VALUE) \
RegCloseKey(hk);

static CHAR szExport[]          = "Export";

static CHAR szNull[]            = "(null)";

static CHAR szNullDisplayName[] = "No Display Name!";

//$--CbMultiSz------------------------------------------------------------------
//  Count of bytes in a REG_MULTI_SZ string (not including terminating NULL).
// -----------------------------------------------------------------------------
static DWORD CbMultiSz(                 // RETURNS: count of bytes
    IN LPSTR lpszRegMultiSz)           // REG_MULTI_SZ string
{
    HRESULT hr   = NOERROR;
    DWORD   cch  = 0;
    DWORD   cb   = 0;
    LPSTR  lpsz = NULL;

    DEBUGPRIVATE("CbMultiSz()");

    hr = CHK_HrMultiSz(lpszRegMultiSz);
    if(FAILED(hr))
return(0);

    if(lpszRegMultiSz == NULL)
    {
goto cleanup;
    }

    lpsz = lpszRegMultiSz;

    while(*lpsz)
    {
cch = lstrlen(lpsz);

cch++;

cb  += cch * sizeof(CHAR);

lpsz += cch;
    }

cleanup:

    return(cb);
}

//$--HrGetRegistryValue---------------------------------------------------------
//  Get a registry value - allocating memory to hold it.
// -----------------------------------------------------------------------------
static HRESULT HrGetRegistryValue(  // RETURNS: return code
    IN  HKEY hk,                    // the key.
    IN  LPSTR lpszValue,           // value name in key.
    OUT DWORD * lpType,             // where to put type info.
    OUT DWORD * lpcb,               // where to put byte count info.
    OUT LPVOID * lppData)           // where to put the data.
{
    HRESULT hr   = E_FAIL;
    LONG    lRet = 0;

    DEBUGPRIVATE("HrGetRegistryValue()\n");

    hr = CHK_HrGetRegistryValue(
hk,
lpszValue,
lpType,
lpcb,
lppData);
    if(FAILED(hr))
RETURN(hr);

    *lppData = NULL;

    //
    //  Get its size
    //

    lRet = RegQueryValueEx(
hk,
lpszValue,
NULL,
lpType,
NULL,
lpcb);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Allocate memory for it
    //

    *lppData = (LPVOID)GlobalAlloc(GMEM_FIXED, *lpcb);

    if(*lppData == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    // Get the current value
    //

    lRet = RegQueryValueEx(hk, lpszValue, NULL, lpType, *lppData, lpcb);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    hr = NOERROR;

cleanup:

    if(FAILED(hr))
    {
if(lppData != NULL)
{
    GLOBALFREE(*lppData);
}
    }

    RETURN(hr);
}

//$--FCsvGetField---------------------------------------------------------------
// Given a record and a field separator and a field number, this routine
// will extract the field requested.
//------------------------------------------------------------------------------
static BOOL FCsvGetField(       // RETURNS: TRUE/FALSE
    IN  WORD wLen,              // maximum length of the field to extract
    IN  WORD wFieldNum,         // field number we want from the record
    IN  CHAR cFieldSeparator,  // character to use as a field separator
    IN  CHAR *lpszRecord,      // record to extract the field from
    OUT CHAR *lpszField)       // field we have extracted
{
    HRESULT hr              = NOERROR;
    BOOL    fRet            = FALSE;
    CHAR   *lpszBeginField = lpszField;

    DEBUGPRIVATE("FCsvGetField()");

    hr = CHK_HrCsvGetField(
wLen,
wFieldNum,
cFieldSeparator,
lpszRecord,
lpszField);
    if(FAILED(hr))
return(FALSE);

    while((wFieldNum > 0) && (*lpszRecord != 0))
    {
// If we found a field separator, increment current field
if(*lpszRecord == cFieldSeparator)
{
    wFieldNum--;
}
// If we are at the desired field, copy the current character into it
else if(wFieldNum == 1 && wLen > 1)
{
    *lpszField = *lpszRecord;
    lpszField++;
    wLen--;
}

lpszRecord++;
    }

    *lpszField = 0;

    // If the requested field # existed, return True,
    // otherwise we ran out of fields - return False

    if(wFieldNum <= 1)
    {
fRet = TRUE;
    }
    else
    {
fRet = FALSE;
    }

    return(fRet);
}

//$--FCsvGetRecord--------------------------------------------------------------
// Given a buffer, the buffer's length and a file handle, this
// function fills the buffer with a single line read from the file.
// The NL & CR are NOT put into the buffer. No unprintable characters are
// placed in the buffer
// -----------------------------------------------------------------------------
BOOL FCsvGetRecord(                 // RETURNS: TRUE/FALSE
    IN  WORD wBufferLen,            // length of the record buffer
    IN  HANDLE hFile,               // file handle to read from
    OUT CHAR *lpszBuffer)          // record we have retrieved
{
    HRESULT hr          = NOERROR;
    DWORD   dwBytesRead = 0;
    BOOL    fReadData   = FALSE;

    DEBUGPRIVATE("FCsvGetRecord()");

    hr = CHK_HrCsvGetRecord(
wBufferLen,
hFile,
lpszBuffer);
    if(FAILED(hr))
return(FALSE);

    while((ReadFile(hFile, (LPVOID)lpszBuffer, 1, &dwBytesRead, NULL) == TRUE) &&
  (wBufferLen > 1) && (*lpszBuffer != '\n') && (dwBytesRead > 0))
    {
fReadData = TRUE;

// Only store character in buffer if it is printable!

if((isprint(*lpszBuffer)) || (*lpszBuffer == EXCHINST_DELIM))
{
    lpszBuffer++;
    wBufferLen--;
}
    }

    // If a given record is too long it is a problem!!!

    if(wBufferLen <= 0)
    {
fReadData = FALSE;
    }

    *lpszBuffer = 0;

    return(fReadData);
}

//$--HrEDKExportObject----------------------------------------------------------
// This function will export an object from an Exchange server.
// -----------------------------------------------------------------------------
static HRESULT HrEDKExportObject(       // RETURNS: return code
    IN  LPSTR lpszServer,              // server name
    IN  LPSTR lpszBasePoint,           // base point
    IN  DWORD dwControlFlags,           // control flags
    IN  LPSTR *rgpszClasses,           // classes
    IN  LPSTR lpszObjectAttributes,    // list of attributes to export
    OUT LPSTR lpszTempName)            // temporary file name
{
    HRESULT       hr                     = E_FAIL;
    ULONG         cErrors                = 0;
    HANDLE        hTempFile              = INVALID_HANDLE_VALUE;
    CHAR          szTempPath[MAX_PATH+1] = {0};
    DWORD         dwNumberOfBytesWritten = 0;
    BEXPORT_PARMS BExportParms           = {0};
    BOOL          fRet                   = FALSE;

    DEBUGPRIVATE("HrEDKExportObject()");

    hr = CHK_HrEDKExportObject(
lpszServer,
lpszBasePoint,
dwControlFlags,
rgpszClasses,
lpszObjectAttributes,
lpszTempName);
    if(FAILED(hr))
RETURN(hr);

    // Get temporary directory path

    if(!GetTempPath(MAX_PATH, szTempPath))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Get temporary file name

    if(!GetTempFileName(szTempPath, FILE_PREFIX, 0, lpszTempName))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Create the temporary file
    hTempFile = CreateFile(lpszTempName,
GENERIC_WRITE,
0,
(LPSECURITY_ATTRIBUTES)NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE)NULL);

    // Check to see if temporary file was created...

    if(hTempFile == INVALID_HANDLE_VALUE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Write data to the temporary file & close it

    fRet = WriteFile(
hTempFile,
lpszObjectAttributes,
lstrlen(lpszObjectAttributes)*sizeof(CHAR),
&dwNumberOfBytesWritten,
NULL);

    if(fRet == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    fRet = WriteFile(
hTempFile,
NEW_LINE,
lstrlen(NEW_LINE)*sizeof(CHAR),
&dwNumberOfBytesWritten,
NULL);

    if(fRet == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    CLOSEHANDLE(hTempFile);

    //
    // Batch Export
    //

    BExportParms.dwDAPISignature = DAPI_SIGNATURE;
    BExportParms.dwFlags         = dwControlFlags |
   DAPI_MODIFY_REPLACE_PROPERTIES |
   DAPI_SUPPRESS_PROGRESS |
   DAPI_SUPPRESS_COMPLETION |
                   DAPI_SUPPRESS_ARCHIVES |
                   DAPI_IMPORT_NO_ERR_FILE;
    BExportParms.pszExportFile   = lpszTempName;
    BExportParms.pszBasePoint    = lpszBasePoint;
    BExportParms.pszDSAName      = lpszServer;
    BExportParms.rgpszClasses    = rgpszClasses;
    BExportParms.chColSep        = EXCHINST_DELIM;
    BExportParms.chQuote         = EXCHINST_QUOTE;
    BExportParms.chMVSep         = EXCHINST_MV_SEP;

    cErrors = BatchExport(&BExportParms);

    if(cErrors == 0)
    {
hr = NOERROR;
    }
    else
    {
hr = HR_LOG(E_FAIL);
    }

cleanup:

    CLOSEHANDLE(hTempFile);

    RETURN(hr);
}

//$--HrEDKImportObject----------------------------------------------------------
// This function will import an object into an Exchange server.
// -----------------------------------------------------------------------------
static HRESULT HrEDKImportObject (      // RETURNS: return code
    IN  LPSTR lpszServer,              // server name
    IN  LPSTR lpszBasePoint,           // base point
    IN  LPSTR lpszObjectAttributes,    // list of attributes to import
    IN  LPSTR lpszAttributeValues)     // list of attributes values to import
{
    HRESULT       hr                     = E_FAIL;
    ULONG         cErrors                = 0;
    HANDLE        hTempFile              = INVALID_HANDLE_VALUE;
    CHAR         szTempName[MAX_PATH+1] = {0};
    CHAR         szTempPath[MAX_PATH+1] = {0};
    DWORD         cBytes                 = 0;
    BIMPORT_PARMS BImportParms           = {0};
    BOOL          fRet                   = FALSE;

    DEBUGPRIVATE("HrEDKImportObject()");

    hr = CHK_HrEDKImportObject(
lpszServer,
lpszBasePoint,
lpszObjectAttributes,
lpszAttributeValues);
    if(FAILED(hr))
RETURN(hr);

    // Get temporary directory path

    if(!GetTempPath(MAX_PATH, szTempPath))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Get temporary file name

    if(!GetTempFileName(szTempPath, FILE_PREFIX, 0, szTempName))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Create the temporary file

    hTempFile = CreateFile(szTempName,
GENERIC_WRITE,
0,
(LPSECURITY_ATTRIBUTES)NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE)NULL);

    // Check to see if temporary file was created

    if(hTempFile == INVALID_HANDLE_VALUE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Write data to the temporary file & close it

    fRet = WriteFile(
hTempFile,
lpszObjectAttributes,
lstrlen(lpszObjectAttributes)*sizeof(CHAR),
&cBytes,
NULL);

    if(fRet == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    fRet = WriteFile(
hTempFile,
NEW_LINE,
lstrlen(NEW_LINE)*sizeof(CHAR),
&cBytes,
NULL);

    if(fRet == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    fRet = WriteFile(
hTempFile,
lpszAttributeValues,
lstrlen(lpszAttributeValues)*sizeof(CHAR),
&cBytes,
NULL);

    if(fRet == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    CLOSEHANDLE(hTempFile);

    //
    // Batch Import
    //

    BImportParms.dwDAPISignature = DAPI_SIGNATURE;
    BImportParms.dwFlags         = DAPI_RESTRICT_ACCESS |
   DAPI_SUPPRESS_PROGRESS |
                   DAPI_MODIFY_REPLACE_PROPERTIES |
   DAPI_YES_TO_ALL |
   DAPI_EVENT_ALL |
   DAPI_SUPPRESS_COMPLETION |
                   DAPI_SUPPRESS_ARCHIVES |
                   DAPI_IMPORT_NO_ERR_FILE;
    BImportParms.pszImportFile   = szTempName;
    BImportParms.pszBasePoint    = lpszBasePoint;
    BImportParms.pszDSAName      = lpszServer;
    BImportParms.chColSep        = EXCHINST_DELIM;
    BImportParms.chQuote         = EXCHINST_QUOTE;
    BImportParms.chMVSep         = EXCHINST_MV_SEP;

    cErrors = BatchImport(&BImportParms);

    if(cErrors == 0)
    {
hr = NOERROR;
    }
    else
    {
hr = HR_LOG(E_FAIL);
    }

cleanup:

    CLOSEHANDLE(hTempFile);
    DELETEFILE(szTempName);

    RETURN(hr);
}

//$--HrDeleteObject----------------------------------------------------------
//  Deletes an object from an Exchange server
// -----------------------------------------------------------------------------
static HRESULT HrDeleteObject(   // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszBasepoint,        // base point
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszObjClass)         // object class
{
    HRESULT hr                                   = NOERROR;
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues [MAX_CSV_LINE_SIZ] = {0};

    DEBUGPRIVATE("HrDeleteObject()");

    hr = CHK_HrDeleteObject(
lpszServer,
lpszBasepoint,
lpszCommonName,
lpszObjClass);
    if(FAILED(hr))
RETURN(hr);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , lpszObjClass);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_DELETE);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
lpszBasepoint,
szObjectAttributes,
szAttributeValues));

    RETURN(hr);
}

//$--HrEDKObjectExists----------------------------------------------------------
//  Checks if an object exists in the directory.
// -----------------------------------------------------------------------------
static HRESULT HrEDKObjectExists(   // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszBasePoint,        // base point
    IN LPSTR lpszObjClass,         // object class
    IN LPSTR lpszCommonName)       // common name
{
    HRESULT hr                                   = NOERROR;
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues [MAX_CSV_LINE_SIZ] = {0};

    DEBUGPRIVATE("HrEDKObjectExists()");

    hr = CHK_HrEDKObjectExists(
lpszServer,
lpszBasePoint,
lpszCommonName);
    if(FAILED(hr))
RETURN(hr);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , lpszObjClass);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_MODIFY);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
lpszBasePoint,
szObjectAttributes,
szAttributeValues));

    RETURN(hr);
}

//$--HrEDKEnumAttrib------------------------------------------------------------
//  Enumerates the given attribute.
// -----------------------------------------------------------------------------
static HRESULT HrEDKEnumAttrib(          // RETURNS: return code
    IN  LPSTR lpszRootDN,               // distinguished name of DIT root
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR *rgpszClasses,            // classes
    IN  LPSTR lpszAttribName,           // attribute name
    OUT LPSTR *lppszAttribValues)       // attribute values
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCurRecord[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szCurField[MAX_PATH+1]               = {0};
    CHAR   szTempName[MAX_PATH+1]               = {0};
    WORD    wAttribField                         = MAX_WORD;
    WORD    wCurrField                           = 0;
    LPSTR  lpsz                                 = NULL;
    LPSTR  lpszCurrAttrib                       = NULL;

    DEBUGPRIVATE("HrEDKEnumAttrib()");

    hr = CHK_HrEDKEnumAttrib(
lpszRootDN,
lpszServer,
rgpszClasses,
lpszAttribName,
lppszAttribValues);
    if(FAILED(hr))
RETURN(hr);

    *lppszAttribValues = NULL;

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, lpszAttribName);

    hr = HrEDKExportObject(
lpszServer,
lpszRootDN,
DAPI_EXPORT_BASEPOINT_ONLY,
rgpszClasses,
szObjectAttributes,
szTempName);

    if(SUCCEEDED(hr))
    {
// Open the temporary file
hTempFile = CreateFile (
    szTempName,
    GENERIC_READ,
    0,
    (LPSECURITY_ATTRIBUTES)NULL,
    OPEN_EXISTING,
    FILE_FLAG_DELETE_ON_CLOSE,
    (HANDLE)NULL);

if(hTempFile == INVALID_HANDLE_VALUE)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

//
// The first line contains the list of fields - find which field has
// the attribute we are looking for.
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

for(
    wCurrField = 1;

    FCsvGetField(
MAX_PATH,
wCurrField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    wCurrField++)
{
    if(strcmp(szCurField, lpszAttribName) == 0)
    {
wAttribField = wCurrField;
break;
    }
}

// Was the field exported & found above?

if(wAttribField == MAX_WORD)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

while( FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine))
{
    FCsvGetField(
MAX_CSV_LINE_SIZ,
wAttribField,
EXCHINST_DELIM,
szCurLine,
szCurRecord);

    if( *szCurRecord)
    {
lpsz = (LPSTR)GlobalAlloc(
    GMEM_FIXED,
    cbStrLen(szCurRecord) + sizeof(CHAR));

if(lpsz == NULL)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}

lpszCurrAttrib = lpsz;

for(
    wCurrField = 1;

    FCsvGetField(
MAX_PATH,
wCurrField,
EXCHINST_MV_SEP,
szCurRecord,
szCurField);

    wCurrField++)
{
    lstrcpy(lpszCurrAttrib, szCurField);

    lpszCurrAttrib += cbStrLen(lpszCurrAttrib);
}

*lpszCurrAttrib = 0;
break;  // single record only
    }
}
    }

    *lppszAttribValues = lpsz;

cleanup:

    CLOSEHANDLE(hTempFile);

    if(FAILED(hr))
    {
GLOBALFREE(*lppszAttribValues);
    }

    RETURN(hr);
}

//$--HrEDKEnumDNs---------------------------------------------------------------
//  Enumerates the distinguished name(s).
// -----------------------------------------------------------------------------
static HRESULT HrEDKEnumDNs(             // RETURNS: return code
    IN  LPSTR lpszRootDN,               // distinguished name of DIT root
    IN  LPSTR lpszServer,               // server name
    IN  DWORD  dwControlFlags,           // control flags
    IN  LPSTR *rgpszClasses,            // classes
    OUT LPSTR *lppszDNs)                // distinguished names
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCurRecord[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szCurField[MAX_PATH+1]               = {0};
    CHAR   szTempName[MAX_PATH+1]               = {0};
    WORD    wAttribField                         = MAX_WORD;
    WORD    wCurrField                           = 0;
    LPSTR  lpsz                                 = NULL;
    ULONG   ulCurrOffset                         = 0;

    DEBUGPRIVATE("HrEDKEnumDNs()");

    hr = CHK_HrEDKEnumDNs(
lpszRootDN,
lpszServer,
dwControlFlags,
rgpszClasses,
lppszDNs);
    if(FAILED(hr))
RETURN(hr);

    *lppszDNs = NULL;

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, OBJ_DIST_NAME);

    hr = HrEDKExportObject(
lpszServer,
lpszRootDN,
dwControlFlags,
rgpszClasses,
szObjectAttributes,
szTempName);

    if(SUCCEEDED(hr))
    {
// Open the temporary file
hTempFile = CreateFile (
    szTempName,
    GENERIC_READ,
    0,
    (LPSECURITY_ATTRIBUTES)NULL,
    OPEN_EXISTING,
    FILE_FLAG_DELETE_ON_CLOSE,
    (HANDLE)NULL);

if(hTempFile == INVALID_HANDLE_VALUE)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

//
// The first line contains the list of fields - find which field has
// the attribute we are looking for.
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

for(
    wCurrField = 1;

    FCsvGetField(
MAX_PATH,
wCurrField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    wCurrField++)
{
    if(strcmp(szCurField, OBJ_DIST_NAME) == 0)
    {
wAttribField = wCurrField;
break;
    }
}

// Was the field exported & found above?

if(wAttribField == MAX_WORD)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

ulCurrOffset = 0;

while(FCsvGetRecord (MAX_CSV_LINE_SIZ, hTempFile, szCurLine))
{
    FCsvGetField(
MAX_PATH,
wAttribField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    if( *szCurField)
    {
if(lpsz == NULL)
{
    lpsz = (LPSTR)GlobalAlloc(
GMEM_FIXED,
cbStrLen(szCurField) + sizeof(CHAR));
}
else
{
    lpsz = (LPSTR)GlobalReAlloc(
lpsz,
GlobalSize(lpsz) + cbStrLen(szCurField),
GMEM_MOVEABLE);
}

if(lpsz == NULL)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}

lstrcpy(&lpsz[ulCurrOffset], szCurField);

ulCurrOffset += cbStrLen(szCurField);

lpsz[ulCurrOffset] = 0;
    }
}
    }

    *lppszDNs = lpsz;

cleanup:

    CLOSEHANDLE(hTempFile);

    if(FAILED(hr))
    {
GLOBALFREE(*lppszDNs);
    }

    RETURN(hr);
}

//$--HrEnumOrganizations-----------------------------------------------------
//  Enumerates the organization name(s).
// -----------------------------------------------------------------------------
HRESULT HrEnumOrganizations(          // RETURNS: return code
    IN  LPSTR lpszRootDN,               // distinguished name of DIT root
    IN  LPSTR lpszServer,               // server name
    OUT LPSTR *lppszOrganizations)      // organizations

{
    HRESULT hr              = NOERROR;
    LPSTR  rgpszClasses[2] = {0};

    DEBUGPUBLIC("HrEnumOrganizations()");

    hr = CHK_HrEnumOrganizations(
lpszRootDN,
lpszServer,
lppszOrganizations);
    if(FAILED(hr))
RETURN(hr);

    rgpszClasses[0] = ORGANIZATION;
    rgpszClasses[1] = NULL;

    hr = HrEDKEnumDNs(
lpszRootDN,
lpszServer,
DAPI_EXPORT_SUBTREE,
rgpszClasses,
lppszOrganizations);

    RETURN(hr);
}

//$--HrEnumSites-------------------------------------------------------------
//  Enumerates the site name(s).
// -----------------------------------------------------------------------------
HRESULT HrEnumSites(                  // RETURNS: return code
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR lpszOrganizationDN,       // distinguished name of organization
    OUT LPSTR *lppszSites)              // sites
{
    HRESULT hr              = NOERROR;
    LPSTR  rgpszClasses[2] = {0};

    DEBUGPUBLIC("HrEnumSites()");

    hr = CHK_HrEnumSites(
lpszServer,
lpszOrganizationDN,
lppszSites);
    if(FAILED(hr))
RETURN(hr);

    rgpszClasses[0] = ORGANIZATIONAL_UNIT;
    rgpszClasses[1] = NULL;

    hr = HrEDKEnumDNs(
lpszOrganizationDN,
lpszServer,
0,
rgpszClasses,
lppszSites);

    RETURN(hr);
}

//$--HrEnumContainers--------------------------------------------------------
//  Enumerates the container name(s).
// -----------------------------------------------------------------------------
HRESULT HrEnumContainers(             // RETURNS: return code
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR lpszSiteDN,               // distinguished name of site
    IN  BOOL   fSubtree,                 // sub-tree?
    OUT LPSTR *lppszContainers)         // containers
{
    HRESULT hr              = NOERROR;
    LPSTR  rgpszClasses[2] = {0};
    DWORD   dwControlFlags  = 0;

    DEBUGPUBLIC("HrEnumContainers()");

    hr = CHK_HrEnumContainers(
lpszServer,
lpszSiteDN,
fSubtree,
lppszContainers);
    if(FAILED(hr))
RETURN(hr);

    rgpszClasses[0] = CONTAINER;
    rgpszClasses[1] = NULL;

    if(fSubtree == TRUE)
    {
dwControlFlags = DAPI_EXPORT_SUBTREE;
    }

    hr = HrEDKEnumDNs(
lpszSiteDN,
lpszServer,
dwControlFlags,
rgpszClasses,
lppszContainers);

    RETURN(hr);
}

//$--HrEnumSiteAdmins--------------------------------------------------------
//  Enumerates the administrators for a given site.
// -----------------------------------------------------------------------------
HRESULT HrEnumSiteAdmins(             // RETURNS: return code
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR lpszSiteDN,               // distinguished name of site
    OUT LPSTR *lppszAdmins)             // administrator accounts
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrEnumSiteAdmins()");

    hr = CHK_HrEnumSiteAdmins(
lpszServer,
lpszSiteDN,
lppszAdmins);
    if(FAILED(hr))
RETURN(hr);

    hr = HrEDKEnumAttrib(
lpszSiteDN,
lpszServer,
NULL,
OBJ_ADMIN,
lppszAdmins);

    RETURN(hr);
}

//$--HrEnumProductServices---------------------------------------------------
//  Enumerates the services for a product.
// -----------------------------------------------------------------------------
HRESULT HrEnumProductServices(        // RETURNS: return code
    IN  LPSTR lpszProductGuid,          // product GUID
    OUT LPSTR *lppszServices,           // service names
    OUT LPSTR *lppszDisplayNames)       // service display names
{
    HRESULT   hr                        = NOERROR;
    LONG      lRet                      = 0;
    HKEY      hkServices                = INVALID_HANDLE_VALUE;
    HKEY      hKey                      = INVALID_HANDLE_VALUE;
    SC_HANDLE schSCManager              = NULL;
    SC_HANDLE schService                = NULL;
    DWORD     dwType                    = 0;
    DWORD     cbGuid                    = 0;
    LPSTR    lpszGuid                  = NULL;
    DWORD     cbName                    = 0;
    LPSTR    lpszName                  = NULL;

    CHAR     szServiceName[MAX_PATH+1] = {0};
    LPSTR    lpszServices              = NULL;
    LPSTR    lpszDisplayNames          = NULL;
    FILETIME  ftLastWrite               = {0};
    DWORD     dwBufferSize              = 0;
    DWORD     iSubKey                   = 0;
    ULONG     ulServOffset              = 0;
    ULONG     ulDispOffset              = 0;
    CHAR     szSubKey[MAX_PATH+1]      = {0};
    LONG      cBytes                    = 0;

    DEBUGPUBLIC("HrEnumProductServices()");

    hr = CHK_HrEnumProductServices(
lpszProductGuid,
lppszServices,
lppszDisplayNames);
    if(FAILED(hr))
RETURN(hr);

    *lppszServices = NULL;
    *lppszDisplayNames = NULL;

    //
    //  Connect to service control manager.
    //

    schSCManager = OpenSCManager(
NULL,                       // machine (NULL == local)
NULL,                       // database (NULL == default)
SC_MANAGER_ALL_ACCESS);     // access required

    if(schSCManager == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    //  Open the services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_ALL_ACCESS,
&hkServices);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    ulServOffset = 0;
    ulDispOffset = 0;

    for(;;)
    {
if(lpszGuid != szNull)
{
    GLOBALFREE(lpszGuid);
}

if(lpszName != szNullDisplayName)
{
    GLOBALFREE(lpszName);
}

dwBufferSize = MAX_PATH+1;

lRet = RegEnumKeyEx(
    hkServices,
    iSubKey,
    szServiceName,
    &dwBufferSize,
    NULL,
    NULL,
    NULL,
    &ftLastWrite);

iSubKey++;

if(lRet == ERROR_NO_MORE_ITEMS)
{
    break;
}

//
//  Open the service "Parameters" key.
//

cBytes = _snprintf(
    szSubKey,
    MAX_PATH+1,
    "%s\\Parameters",
    szServiceName);

if(cBytes < 0)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

lRet = RegOpenKeyEx(
    hkServices,
    szSubKey,
    0,
    KEY_ALL_ACCESS,
    &hKey);

if(lRet != ERROR_SUCCESS)
{
    continue;
}

//
//  Get the current value.
//

hr = HrGetRegistryValue(
    hKey,
    "ProductGUID",
    &dwType,
    &cbGuid,
    &lpszGuid);

FREEHKEY(hKey);

if(FAILED(hr) || dwType != REG_SZ)
{
    continue;
}

if(lpszGuid == NULL)
{
    lpszGuid = szNull;
}

//
//  Open the service key.
//

cBytes = _snprintf(
    szSubKey,
    MAX_PATH+1,
    "%s",
    szServiceName);

if(cBytes < 0)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

lRet = RegOpenKeyEx(
    hkServices,
    szSubKey,
    0,
    KEY_ALL_ACCESS,
    &hKey);

if(lRet != ERROR_SUCCESS)
{
    continue;
}

//
//  Get the current value.
//

hr = HrGetRegistryValue(
    hKey,
    "DisplayName",
    &dwType,
    &cbName,
    &lpszName);

FREEHKEY(hKey);

if(FAILED(hr) || dwType != REG_SZ)
{
    lpszName = szNullDisplayName;
}

if(lpszName == NULL)
{
    lpszName = szNullDisplayName;
}

if(!lstrcmpi(lpszProductGuid, lpszGuid))
{
    //
    // Add the service name to the list.
    //

    if(lpszServices == NULL)
    {
cBytes = cbStrLen(szServiceName) + sizeof(CHAR);

lpszServices = (LPSTR)GlobalAlloc(
    GMEM_FIXED,
    cBytes);
    }
    else
    {
LPSTR lpszT = NULL;

cBytes = GlobalSize(lpszServices) +
 cbStrLen(szServiceName) +
 sizeof(CHAR);

lpszT = (LPSTR)GlobalReAlloc(
    lpszServices,
    cBytes,
    GMEM_MOVEABLE);

if(lpszT != NULL)
{
    lpszServices = lpszT;
}
    }

    if(lpszServices == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    lstrcpy(&lpszServices[ulServOffset], szServiceName);

    ulServOffset += lstrlen(szServiceName);
    lpszServices[ulServOffset] = 0;

    ulServOffset ++;
    lpszServices[ulServOffset] = 0;

    //
    // Add the service display name to the list.
    //

    if(lpszDisplayNames == NULL)
    {
cBytes = cbStrLen(lpszName) + sizeof(CHAR);

lpszDisplayNames = (LPSTR)GlobalAlloc(
    GMEM_FIXED,
    cBytes);
    }
    else
    {
LPSTR lpszT = NULL;

cBytes = GlobalSize(lpszDisplayNames) +
 cbStrLen(lpszName) +
 sizeof(CHAR);

lpszT = (LPSTR)GlobalReAlloc(
    lpszDisplayNames,
    cBytes,
    GMEM_MOVEABLE);

if(lpszT != NULL)
{
    lpszDisplayNames = lpszT;
}
    }

    if(lpszDisplayNames == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    lstrcpy(&lpszDisplayNames[ulDispOffset], lpszName);

    ulDispOffset += lstrlen(lpszName);
    lpszDisplayNames[ulDispOffset] = 0;

    ulDispOffset ++;
    lpszDisplayNames[ulDispOffset] = 0;
}

if(lpszGuid != szNull)
{
    GLOBALFREE(lpszGuid);
}

if(lpszName != szNullDisplayName)
{
    GLOBALFREE(lpszName);
}
    }

    *lppszServices = lpszServices;
    *lppszDisplayNames = lpszDisplayNames;

    hr = NOERROR;

cleanup:

    FREEHSCM(schService);
    FREEHSCM(schSCManager);

    FREEHKEY(hkServices);
    FREEHKEY(hKey);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    if(lpszGuid != szNull)
    {
GLOBALFREE(lpszGuid);
    }

    if(lpszName != szNull)
    {
GLOBALFREE(lpszName);
    }

    if(FAILED(hr))
    {
GLOBALFREE(*lppszServices);
GLOBALFREE(*lppszDisplayNames);
    }

    RETURN(hr);
}

//$--HrInstallService--------------------------------------------------------
//  Installs a service associated with an Exchange object.
// -----------------------------------------------------------------------------
HRESULT HrInstallService(            // RETURNS: return code
    IN LPSTR  lpszServer,              // server name
    IN LPSTR  lpszSiteDN,              // distinguished name of site
    IN LPSTR  lpszServiceDisplayName,  // service display name
    IN LPSTR  lpszServiceName,         // service name
    IN LPSTR  lpszCommonName,          // object relative distinguished name
    IN LPSTR  lpszObjectGuid,          // object GUID
    IN LPSTR  lpszProductGuid,         // product GUID
    IN LPSTR  lpszExeName,             // executable name
    IN LPCSTR lpszDependencies,        // dependencies
    IN LPSTR  lpszAccount,             // account
    IN LPSTR  lpszPassword)            // password
{
    HRESULT   hr                 = NOERROR;
    LONG      lRet               = 0;
    HKEY      hkServices         = INVALID_HANDLE_VALUE;
    HKEY      hkObject           = INVALID_HANDLE_VALUE;
    HKEY      hkTmp              = INVALID_HANDLE_VALUE;
    DWORD     dwDisposition      = 0;
    BOOL      bObjectKeyCreated  = FALSE;
    SC_HANDLE schSCManager       = NULL;
    SC_HANDLE schService         = NULL;

    DEBUGPUBLIC("HrInstallService()");

    hr = CHK_HrInstallService(
lpszServer,
lpszSiteDN,
lpszServiceDisplayName,
lpszServiceName,
lpszCommonName,
lpszObjectGuid,
lpszProductGuid,
lpszExeName,
lpszDependencies,
lpszAccount,
lpszPassword);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Connect to service control manager.
    //

    schSCManager = OpenSCManager(
NULL,                       // machine (NULL == local)
NULL,                       // database (NULL == default)
SC_MANAGER_ALL_ACCESS);     // access required

    if(schSCManager == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    //  Create the service.
    //

    schService = CreateService(
schSCManager,               // SCManager database
lpszServiceName,            // service name
lpszServiceDisplayName,     // service display name
SERVICE_ALL_ACCESS,         // desired access
SERVICE_WIN32_OWN_PROCESS,  // service type
SERVICE_DEMAND_START,       // start type
SERVICE_ERROR_NORMAL,       // error control type
lpszExeName,                // service's binary
NULL,                       // no load ordering group
NULL,                       // no tag identifier
lpszDependencies,           // dependencies
lpszAccount,                // account
lpszPassword);              // password

    if(schService == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    //  Open the services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_ALL_ACCESS,
&hkServices);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Create the object key.
    //

    lRet = CREATEKEY(hkServices, lpszServiceName, hkObject, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    bObjectKeyCreated = TRUE;

    //
    // Create Parameters key.
    //

    lRet = CREATEKEY(hkObject, "Parameters", hkTmp, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    // Create new values under Parameters key

    lRet = SETSZVALUE(hkTmp, "HomeDSA", lpszServer);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkTmp, "SiteDN", lpszSiteDN);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkTmp, "CommonName", lpszCommonName);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkTmp, "ObjectGUID", lpszObjectGuid);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkTmp, "ProductGUID", lpszProductGuid);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

cleanup:

    FREEHSCM(schService);
    FREEHSCM(schSCManager);

    FREEHKEY(hkServices);
    FREEHKEY(hkObject);
    FREEHKEY(hkTmp);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrGetRegistryString-----------------------------------------------------
//  Get a string value from the registry.
// -----------------------------------------------------------------------------
static HRESULT HrGetRegistryString(  // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    IN  LPSTR  lpszServicePath,        // service path
    IN  LPSTR  lpszValueName,          // registry value name
    IN  DWORD   dwExpectedType,         // expected registry data type
    OUT LPSTR  *lppszString)           // string
{
    HRESULT hr                           = NOERROR;
    LONG    lRet                         = 0;
DWORD   dwType                       = 0;
DWORD   cbOrig                       = 0;
CHAR    szKey[MAX_PATH+1]            = {0};
HKEY    hKey                         = INVALID_HANDLE_VALUE;

    DEBUGPUBLIC("HrGetRegistryString()");

    hr = CHK_HrGetRegistryString(
lpszServiceName,
lpszServicePath,
lpszValueName,
dwExpectedType,
lppszString);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Open the key.
    //

lRet = _snprintf(
    szKey,
MAX_PATH+1,
    lpszServicePath,
    lpszServiceName);

    if(lRet < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKey,
0,
KEY_ALL_ACCESS,
&hKey);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    hr = HrGetRegistryValue(
hKey, lpszValueName, &dwType, &cbOrig, (LPVOID)lppszString);

    if(FAILED(hr) || dwType != dwExpectedType)
    {
if(dwType != dwExpectedType)
    hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:

FREEHKEY(hKey);

    if(FAILED(hr))
    {
GLOBALFREE(*lppszString);
    }

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrGetServiceServerName--------------------------------------------------
//  Get the Exchange server name associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceServerName(      // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszServerName)       // server name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceServerName()");

    hr = CHK_HrGetServiceServerName(
lpszServiceName,
lppszServerName);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
"HomeDSA",
REG_SZ,
lppszServerName);

    RETURN(hr);
}

//$--HrGetServiceSiteDN------------------------------------------------------
//  Get the site DN of the Exchange object associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceSiteDN(          // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszSiteDN)           // site distinguished name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceSiteDN()");

    hr = CHK_HrGetServiceSiteDN(
lpszServiceName,
lppszSiteDN);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
"SiteDN",
REG_SZ,
lppszSiteDN);

    RETURN(hr);
}

//$--HrGetServiceCommonName--------------------------------------------------
//  Get the common name of the Exchange object associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceCommonName(      // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszCommonName)       // object relative distinguished name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceCommonName()");

    hr = CHK_HrGetServiceCommonName(
lpszServiceName,
lppszCommonName);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
"CommonName",
REG_SZ,
lppszCommonName);

    RETURN(hr);
}

//$--HrGetServiceObjectGUID--------------------------------------------------
//  Get the object GUID associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceObjectGUID(      // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszObjectGUID)       // object GUID
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceObjectGUID()");

    hr = CHK_HrGetServiceObjectGUID(
lpszServiceName,
lppszObjectGUID);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
"ObjectGUID",
REG_SZ,
lppszObjectGUID);

    RETURN(hr);
}

//$--HrGetServiceProductGUID-------------------------------------------------
//  Get the product GUID associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceProductGUID(     // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszProductGUID)      // product GUID
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceProductGUID()");

    hr = CHK_HrGetServiceProductGUID(
lpszServiceName,
lppszProductGUID);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
"ProductGUID",
REG_SZ,
lppszProductGUID);

    RETURN(hr);
}

//$--HrGetServiceDisplayName-------------------------------------------------
//  Get the display name associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceDisplayName(     // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszDisplayName)      // display name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceDisplayName()");

    hr = CHK_HrGetServiceDisplayName(
lpszServiceName,
lppszDisplayName);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s",
"DisplayName",
REG_SZ,
lppszDisplayName);

    RETURN(hr);
}

//$--HrGetServiceExecutableName----------------------------------------------
//  Get the executable name associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceExecutableName(  // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszExecutableName)   // executable name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceExecutableName()");

    hr = CHK_HrGetServiceExecutableName(
lpszServiceName,
lppszExecutableName);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s",
"ImagePath",
REG_EXPAND_SZ,
lppszExecutableName);

    RETURN(hr);
}

//$--HrGetServiceAccountName-------------------------------------------------
//  Get the account name associated with the service.
// -----------------------------------------------------------------------------
HRESULT HrGetServiceAccountName(     // RETURNS: return code
    IN  LPSTR  lpszServiceName,        // service name
    OUT LPSTR  *lppszAccountName)      // account name
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrGetServiceAccountName()");

    hr = CHK_HrGetServiceAccountName(
lpszServiceName,
lppszAccountName);
    if(FAILED(hr))
RETURN(hr);

    hr = HrGetRegistryString(
lpszServiceName,
"SYSTEM\\CurrentControlSet\\Services\\%s",
"ObjectName",
REG_SZ,
lppszAccountName);

    RETURN(hr);
}

//$--HrRemoveService---------------------------------------------------------
//  Removes a service.
// -----------------------------------------------------------------------------
HRESULT HrRemoveService(             // RETURNS: return code
    IN LPCSTR lpszServiceName)         // service name
{
    HRESULT   hr           = NOERROR;
    SC_HANDLE schService   = NULL;
    SC_HANDLE schSCManager = NULL;
    BOOL      fStatus      = FALSE;

    DEBUGPUBLIC("HrRemoveService()\n");

    hr = CHK_HrRemoveService(
lpszServiceName);
    if(FAILED(hr))
RETURN(hr);

    schSCManager = OpenSCManager(
NULL,
NULL,
SC_MANAGER_ALL_ACCESS);

    if(schSCManager == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    schService = OpenService(schSCManager, lpszServiceName, SERVICE_ALL_ACCESS);

    if(schService == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    fStatus = DeleteService(schService);

    if(fStatus == FALSE)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

cleanup:

    FREEHSCM(schService);
    FREEHSCM(schSCManager);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrNameContained------------------------------------------------------------
// Determine if the name is in the list.
// -----------------------------------------------------------------------------
static HRESULT HrNameContained( // RETURNS: return code
    IN LPSTR lpszName,         // name to find
    IN LPSTR lpszList,         // list to search
    IN DWORD cbListLen)         // max length to search
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrNameContained()\n");

    hr = CHK_HrNameContained(
lpszName,
lpszList,
cbListLen);
    if(FAILED(hr))
RETURN(hr);

    hr = EDK_E_NOT_FOUND;

    while(*lpszList)
    {
// does it match?
if(!lstrcmpi(lpszName, lpszList))
{
    hr = NOERROR;
    break;
}

// go to next element
lpszList += sizeof(CHAR)*(1+lstrlen(lpszList));
    }

    RETURN(hr);
}

//$--HrEDKAddServiceToLinkage---------------------------------------------------
//  Add service name to the export value in the key.
// -----------------------------------------------------------------------------
static HRESULT HrEDKAddServiceToLinkage(// RETURNS: return code
    IN HKEY hkLinkage,                  // key containing export list.
    IN LPSTR lpszServiceName)          // service name.
{
    HRESULT hr            = E_FAIL;
    HRESULT hrT           = NOERROR;
    LONG    lRet          = 0;
    DWORD   dwType        = 0;
    DWORD   cbOrig        = 0;
    DWORD   cbServiceName = 0;
    LPBYTE  lpOrig        = NULL;
    LPBYTE  lpRes         = NULL;

    DEBUGPRIVATE("HrEDKAddServiceToLinkage()\n");

    hr = CHK_HrEDKAddServiceToLinkage(
hkLinkage,
lpszServiceName);
    if(FAILED(hr))
RETURN(hr);

    //
    // How big is the current value?
    //

    hrT = HrGetRegistryValue(hkLinkage, szExport, &dwType, &cbOrig, &lpOrig);

    if(SUCCEEDED(hrT))
    {
ASSERTERROR(lpOrig != NULL, "NULL lpOrig variable");

// If the name is already there, don't add it again.

// has value, make sure it is the right type.
if(dwType != REG_MULTI_SZ)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

hrT = HrNameContained(lpszServiceName, (LPSTR)lpOrig, cbOrig);

if(SUCCEEDED(hrT))
{
    hr = NOERROR;
    goto cleanup;
}
    }
    else
    {
// Assume that there is no initial value, so leave space
// for a double-null terminator.
cbOrig = 1;
    }

    // Allocate space for the result.

    cbServiceName = cbStrLen(lpszServiceName);

    lpRes = GlobalAlloc(GMEM_FIXED, cbServiceName + cbOrig);

    if(lpRes == NULL)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // put out new name in and put in extra null in case orig value was empty
    lstrcpy(lpRes, lpszServiceName);

    // put in the rest of the name if it exists
    if(lpOrig != NULL)
    {
CopyMemory(((LPSTR)lpRes)+cbServiceName, lpOrig, cbOrig);
    }
    else
    {
*((LPSTR)(lpRes+cbServiceName)) = 0;
    }

    // Now, write the result.
    lRet = RegSetValueEx(
hkLinkage,
szExport,
0,
REG_MULTI_SZ,
lpRes,
cbServiceName + cbOrig);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = NOERROR;

cleanup:

    GLOBALFREE(lpOrig);
    GLOBALFREE(lpRes);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrInstallServicePerfMon-------------------------------------------------
//  Installs service performance monitoring.
// -----------------------------------------------------------------------------
HRESULT HrInstallServicePerfMon(         // RETURNS: return code
    IN LPSTR lpszServiceName,              // service name
    IN LPSTR lpszClassName,                // class name
    IN LPSTR lpszLibraryName,              // library name
    IN LPSTR lpszLibraryOpenFunction,      // open function
    IN LPSTR lpszLibraryCollectFunction,   // collect function
    IN LPSTR lpszLibraryCloseFunction)     // close function
{
    HRESULT hr             = E_FAIL;
    LONG    lRet           = 0;

    HKEY    hkServices     = INVALID_HANDLE_VALUE;

    HKEY    hkServiceClass = INVALID_HANDLE_VALUE;

HKEY    hkLinkage      = INVALID_HANDLE_VALUE;
    HKEY    hkPerformance  = INVALID_HANDLE_VALUE;

    HKEY    hkServiceName  = INVALID_HANDLE_VALUE;
    HKEY    hkLinkage2     = INVALID_HANDLE_VALUE;
    HKEY    hkParameters   = INVALID_HANDLE_VALUE;

    DWORD   dwDisposition  = 0;

    DEBUGPUBLIC("HrInstallServicePerfMon()\n");

    hr = CHK_HrInstallServicePerfMon(
lpszServiceName,
lpszClassName,
lpszLibraryName,
lpszLibraryOpenFunction,
lpszLibraryCollectFunction,
lpszLibraryCloseFunction);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Open the general services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_ALL_ACCESS,
&hkServices);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the class key.
    //
    lRet = CREATEKEY(hkServices, lpszClassName, hkServiceClass, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the linkage key
    //
    lRet = CREATEKEY(hkServiceClass, "Linkage", hkLinkage, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the performance key
    //
    lRet = CREATEKEY(hkServiceClass, "Performance", hkPerformance, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    // setup performance data.
    //
    lRet = SETSZVALUE(hkPerformance, "Library", lpszLibraryName);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkPerformance, "Open", lpszLibraryOpenFunction);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkPerformance, "Collect", lpszLibraryCollectFunction);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    lRet = SETSZVALUE(hkPerformance, "Close", lpszLibraryCloseFunction);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    // Link the service name into the Export list.
    //

    hr = HrEDKAddServiceToLinkage(hkLinkage, lpszServiceName);

    if(FAILED(hr))
    {
goto cleanup;
    }

    //
    //  Open or create the service key.
    //

    lRet = CREATEKEY(hkServices, lpszServiceName, hkServiceName, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the linkage key
    //

    lRet = CREATEKEY(hkServiceName, "Linkage", hkLinkage2, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the performance key
    //

    lRet = CREATEKEY(hkServiceName, "Parameters", hkParameters, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    // Setup parameters data
    //

    lRet = SETSZVALUE(hkParameters, "ObjectClass", lpszClassName);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

cleanup:

    FREEHKEY(hkLinkage);
    FREEHKEY(hkPerformance);
    FREEHKEY(hkServiceClass);

    FREEHKEY(hkLinkage2);
    FREEHKEY(hkParameters);
    FREEHKEY(hkServiceName);

    FREEHKEY(hkServices);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

// $--HrDeleteContainedKeysInternal---------------------------------------------
//  recursive delete keys.  Is passed in lpszwName for storage.
// -----------------------------------------------------------------------------
static HRESULT HrDeleteContainedKeysInternal(   // RETURNS: return code
    IN HKEY hKey,                               // registry key handle
    IN LPWSTR lpszwName)                        // temporary storage
{
#define cchName 255
    HRESULT hr           = NOERROR;
    DWORD   dwEnumResult = !ERROR_NO_MORE_ITEMS;
    HKEY    hSubKey      = NULL;

    DEBUGPRIVATE("HrDeleteContainedKeysInternal()\n");

    hr = CHK_HrDeleteContainedKeysInternal(
hKey,
lpszwName);
    if(FAILED(hr))
RETURN(hr);

    //
    // Enumerate each sub key and delete it recursively. Temporary memory
    // for the sub-key name is allocated and freed before and after the
    // recursive call but not during to prevent a recursive storage
    // build up.
    //

    do
    {
DWORD    dwResult = 0;
DWORD    cbSubKey = cchName;
FILETIME ftDummy  = {0};

//
// Get the name of the next remaining sub-key.
//

dwEnumResult = RegEnumKeyExW(
    hKey, 0, lpszwName, &cbSubKey, NULL, NULL, NULL, &ftDummy);

if(dwEnumResult == ERROR_SUCCESS)
{
    //
    // Convert the name of the sub-key into an open handle.
    // Dispose of the name.
    //

    dwResult = RegOpenKeyExW(
hKey, lpszwName, 0, KEY_ALL_ACCESS, &hSubKey);

    if(dwResult == ERROR_SUCCESS)
    {
//
// Recursively call delete with this sub-key handle.
//

hr = HrDeleteContainedKeysInternal(hSubKey, lpszwName);

if(FAILED(hr))
{
    goto cleanup;
}

FREEHKEY(hSubKey);

//
// Get the name of the sub-key again
//

cbSubKey = cchName;

dwEnumResult = RegEnumKeyExW(
    hKey, 0, lpszwName, &cbSubKey, NULL, NULL, NULL, &ftDummy);

if(dwEnumResult != ERROR_SUCCESS)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(dwEnumResult));
    goto cleanup;
}

dwResult = RegDeleteKeyW(hKey, lpszwName);

if(dwResult != ERROR_SUCCESS)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(dwResult));
    goto cleanup;
}
    }
    else
    {
hr = HR_LOG(HRESULT_FROM_WIN32(dwResult));
goto cleanup;
    }

    FREEHKEY(hSubKey);
}
else if(dwEnumResult != ERROR_NO_MORE_ITEMS)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(dwEnumResult));
    goto cleanup;
}
    } while (dwEnumResult != ERROR_NO_MORE_ITEMS); // do

cleanup:

    FREEHKEY(hSubKey);

    RETURN(hr);
}

//$--HrDeleteContainedKeys------------------------------------------------------
//  The recursive component of delete key. Each invocation of this function
//  will enumerate the subkeys of a key and delete them one by one
//  recursively.
// -----------------------------------------------------------------------------
static HRESULT HrDeleteContainedKeys(       // RETURNS: return code
    IN HKEY hKey)                           // registry key handle
{
    HRESULT hr                = NOERROR;
    WCHAR   rgchName[cchName] = {0};

    DEBUGPRIVATE("HrDeleteContainedKeys()");

    hr = CHK_HrDeleteContainedKeys(
hKey);
    if(FAILED(hr))
RETURN(hr);

    hr = HrDeleteContainedKeysInternal(hKey, rgchName);

    RETURN(hr);

#undef  cchName
}

//$--HrRemoveRegistry-----------------------------------------------------------
//  Remove the registry for the service.
// -----------------------------------------------------------------------------
HRESULT HrRemoveRegistry(              // RETURNS: nothing
    IN LPCSTR lpszServiceName)         // service name
{
    HRESULT hr         = NOERROR;
    LONG    lRet       = 0;
    HKEY    hkServices = INVALID_HANDLE_VALUE;
    HKEY    hkSvc      = INVALID_HANDLE_VALUE;

    DEBUGPUBLIC("HrRemoveRegistry()");

    hr = CHK_HrEDKRemoveRegistry(
lpszServiceName);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Open the services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_ALL_ACCESS,
&hkServices);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open the service key.
    //

    lRet = RegOpenKeyEx(
hkServices,
lpszServiceName,
0,
KEY_ALL_ACCESS,
&hkSvc);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    hr = HrDeleteContainedKeys(hkSvc);

    if(FAILED(hr))
    {
goto cleanup;
    }

    lRet = RegDeleteKey(hkServices, lpszServiceName);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

cleanup:

    FREEHKEY(hkSvc);
    FREEHKEY(hkServices);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrRemoveName---------------------------------------------------------------
//  Remove the service name from the export list and signal if we're now empty
// -----------------------------------------------------------------------------
static HRESULT HrRemoveName(        // RETURNS: return code
    IN HKEY hkLinkage,              // key containing export
    IN LPSTR lpszServiceName,      // service name
    OUT BOOL * pfEmpty)             // flag to indicate its empty.
{
    LONG    lRet     = 0;
    HRESULT hr       = E_FAIL;
    HRESULT hrT      = NOERROR;
    BOOL    fChanged = FALSE;
    BOOL    fEmpty   = TRUE;
    DWORD   dwType   = 0;
    DWORD   cbOrig   = 0;
    LPVOID  pOrig    = NULL;
    LPVOID  pNew     = NULL;
    DWORD   cbNew    = 0;
    LPSTR  lpszOrig  = NULL;
    LPSTR  lpszNew   = NULL;

    DEBUGPRIVATE("HrRemoveName()\n");

    hr = CHK_HrRemoveName(
hkLinkage,
lpszServiceName,
pfEmpty);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Get the current value.
    //

    hrT = HrGetRegistryValue(hkLinkage, szExport, &dwType, &cbOrig, &pOrig);

    if(FAILED(hrT) || dwType != REG_MULTI_SZ)
    {
if(dwType != REG_MULTI_SZ)
    hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    //
    //  Allocate a buffer big enough to hold a copy.
    //

    pNew = GlobalAlloc(GMEM_FIXED, cbOrig);

    if(pNew == NULL)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    lpszOrig = (LPSTR)pOrig;
    lpszNew = (LPSTR)pNew;

    //
    //  Loop through the source.
    //

    while(*lpszOrig)
    {
int cchNameLen = strlen(lpszOrig) + 1;

if(!_strcmpi(lpszOrig, lpszServiceName))
{
    // found an instance.
    fChanged = TRUE;
}
else
{
    lstrcpy(lpszNew, lpszOrig);

    lpszNew += cchNameLen;
    cbNew += sizeof(CHAR) * cchNameLen;

    fEmpty = FALSE;
}

lpszOrig += cchNameLen;
    }

    //
    // Add final NULL
    //

    *lpszNew = 0;
    cbNew += sizeof(CHAR);

    if(fChanged)
    {
//
//  If it has changed, write it back.
//

lRet = RegSetValueEx(hkLinkage, szExport, 0, REG_MULTI_SZ, pNew, cbNew);

if(lRet != ERROR_SUCCESS)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}
    }

    hr = NOERROR;

    *pfEmpty = fEmpty;

cleanup:

    GLOBALFREE(pOrig);
    GLOBALFREE(pNew);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrRemoveServicePerfMon--------------------------------------------------
//  Removes service performance monitoring.
// -----------------------------------------------------------------------------
HRESULT HrRemoveServicePerfMon(          // RETURNS: return code
    IN  LPSTR lpszServiceName,             // service name
    IN  LPSTR lpszClassName,               // class name
    OUT BOOL   *lpfUnloadCounters)          // unload counters?
{
    HRESULT hr             = E_FAIL;
    LONG    lRet           = 0;
    BOOL    bEmpty         = FALSE;
    HKEY    hkServices     = INVALID_HANDLE_VALUE;
    HKEY    hkServiceClass = INVALID_HANDLE_VALUE;
    HKEY    hkLinkage      = INVALID_HANDLE_VALUE;
    DWORD   dwDisposition  = 0;

    DEBUGPUBLIC("HrRemoveServicePerfMon()\n");

    hr = CHK_HrRemoveServicePerfMon(
lpszServiceName,
lpszClassName,
lpfUnloadCounters);
    if(FAILED(hr))
RETURN(hr);

    *lpfUnloadCounters = FALSE;

    //
    //  Open the general services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_ALL_ACCESS,
&hkServices);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the class key.
    //

    lRet = CREATEKEY(hkServices, lpszClassName, hkServiceClass, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open or create the linkage key
    //

    lRet = CREATEKEY(hkServiceClass, "Linkage", hkLinkage, dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Remove the name.
    //

    hr = HrRemoveName(hkLinkage, lpszServiceName, &bEmpty);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    FREEHKEY(hkLinkage);

    if(SUCCEEDED(hr) && bEmpty)
    {
*lpfUnloadCounters = TRUE;
    }

    FREEHKEY(hkServiceClass);
    FREEHKEY(hkServices);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrInstallGateway--------------------------------------------------------
// Installs a gateway into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallGateway(        // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszDisplayName,      // display name
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszExtensionName,    // admin extension name
    IN LPSTR lpszExtensionData,    // extension data file
    IN LPSTR lpszAddressType,      // address type handled by this gateway
    IN LPSTR lpszAccountName,      // account name
    IN BOOL   fCanPreserveDNs)      // can this gateway preserve DNs?
{
    HRESULT hr                                          = NOERROR;
    CHAR   szTmpBuf[MAX_CSV_LINE_SIZ]                  = {0};
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]               = {0};
    CHAR   szHomeServer[MAX_CSV_LINE_SIZ]              = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]         = {0};
    CHAR   lpszComputerName[MAX_COMPUTERNAME_LENGTH+1] = {0};
    DWORD   cbComputerName                              = MAX_COMPUTERNAME_LENGTH+1;
    INT     cBytes                                      = 0;

    DEBUGPUBLIC("HrInstallGateway()");

    hr = CHK_HrInstallGateway(
lpszServer,
lpszSiteDN,
lpszDisplayName,
lpszCommonName,
lpszExtensionName,
lpszExtensionData,
lpszAddressType,
lpszAccountName,
fCanPreserveDNs);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_GW);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szHomeServer,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s",
lpszSiteDN,
CONTAINER_SERVERS,
lpszServer);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_GW);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, DELIVERY_MECHANISM);
    APPEND_CSV_LINE (szAttributeValues , DELIVERY_MECHANISM_GW);

    APPEND_CSV_LINE (szObjectAttributes, DELIV_EXT_CONT_TYPES);
    APPEND_CSV_LINE (szAttributeValues , "2A864886F7140501");

    if((lpszExtensionData != NULL) && (lstrlen(lpszExtensionData) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, EXTENSION_DATA);
sprintf(szTmpBuf, "=%s", lpszExtensionData);
APPEND_CSV_LINE (szAttributeValues , szTmpBuf);
    }

    if((lpszExtensionName != NULL) && (lstrlen(lpszExtensionName) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, EXTENSION_NAME);
APPEND_CSV_LINE (szAttributeValues , lpszExtensionName);
    }

    APPEND_CSV_LINE (szObjectAttributes, COMPUTER_NAME);
    GetComputerName((LPSTR)lpszComputerName, &cbComputerName);
    APPEND_CSV_LINE (szAttributeValues , lpszComputerName);

    APPEND_CSV_LINE (szObjectAttributes, HOME_SERVER);

    APPEND_CSV_LINE (szAttributeValues , szHomeServer);

    APPEND_CSV_LINE (szObjectAttributes, ROUTING_LIST);
    sprintf(szTmpBuf, "%s:*;1", lpszAddressType);
    APPEND_CSV_LINE (szAttributeValues , szTmpBuf);

    if((lpszAccountName != NULL) && (lstrlen(lpszAccountName) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, ACCOUNT_NAME);
APPEND_CSV_LINE (szAttributeValues , lpszAccountName);
    }

    if(fCanPreserveDNs == TRUE)
    {
APPEND_CSV_LINE (szObjectAttributes, CAN_PRESERVE_DNS);
sprintf(szTmpBuf, "1");
APPEND_CSV_LINE (szAttributeValues , szTmpBuf);
    }
    else
    {
APPEND_CSV_LINE (szObjectAttributes, CAN_PRESERVE_DNS);
sprintf(szTmpBuf, "0");
APPEND_CSV_LINE (szAttributeValues , szTmpBuf);
    }

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
szBasePoint,
szObjectAttributes,
szAttributeValues));

cleanup:

    RETURN(hr);
}

//$--HrRemoveGateway---------------------------------------------------------
//  Removes a gateway from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveGateway(         // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName)       // common name
{
    HRESULT hr                            = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                        = 0;

    DEBUGPUBLIC("HrRemoveGateway()");

    hr = CHK_HrRemoveGateway(
lpszServer,
lpszSiteDN,
lpszCommonName);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_GW);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HR_LOG(HrDeleteObject(
lpszServer,
szBasePoint,
lpszCommonName,
OBJ_CLASS_GW));

cleanup:

    RETURN(hr);
}

//$--HrGatewayExists---------------------------------------------------------
//  Checks if a gateway exists on a given site.
// -----------------------------------------------------------------------------
HRESULT HrGatewayExists(         // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName)       // common name
{
    HRESULT hr                            = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                        = 0;

    DEBUGPUBLIC("HrGatewayExists()");

    hr = CHK_HrGatewayExists(
lpszServer,
lpszSiteDN,
lpszCommonName);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_GW);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKObjectExists(
lpszServer,
szBasePoint,
OBJ_CLASS_GW,
lpszCommonName);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrInstallMailboxAgent---------------------------------------------------
//  Installs a mailbox agent into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallMailboxAgent(   // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszDisplayName,      // display name
    IN LPSTR lpszRDN,              // relative distinguished name
    IN LPSTR lpszExtensionName,    // admin extension name
    IN LPSTR lpszExtensionData,    // extension data file
    IN LPSTR lpszAccountName)      // account name
{
    HRESULT hr                                           = NOERROR;
    CHAR   szTempBuffer[MAX_CSV_LINE_SIZ]               = {0};
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]                = {0};
    CHAR   szHomeServer[MAX_CSV_LINE_SIZ]               = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ]         = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ]               = {0};
    CHAR   lpszComputerName[MAX_COMPUTERNAME_LENGTH+1]  = {0};
    DWORD   cbComputerName                               = MAX_COMPUTERNAME_LENGTH+1;
    INT     cBytes                                       = 0;
    LPSTR  lpsz                                         = NULL;
    LPSTR  lpszT                                        = NULL;
    LPSTR  lpszCommonName                               = NULL;
    BOOL    IsFound                                      = FALSE;

    DEBUGPUBLIC("HrInstallMailboxAgent()");

    hr = CHK_HrInstallMailboxAgent(
lpszServer,
lpszSiteDN,
lpszDisplayName,
lpszRDN,
lpszExtensionName,
lpszExtensionData,
lpszAccountName);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szHomeServer,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s",
lpszSiteDN,
CONTAINER_SERVERS,
lpszServer);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    IsFound = FALSE;

    lpsz = lpszRDN + lstrlen(lpszRDN);

    while(lpsz != lpszRDN)
    {
lpsz--;

if(*lpsz == '/')
{
    IsFound = TRUE;
    break;
}
    }

    if(IsFound == FALSE)
    {
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
    }

    lpszCommonName = lpsz + sizeof("cn=");

    lstrcpy(szCommonName, lpszCommonName);

    lstrcpy(szBasePoint, lpszSiteDN);
    lstrcpyn(szBasePoint + lstrlen(szBasePoint), lpszRDN, lpsz - lpszRDN + 1);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_MB);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , szCommonName);

    APPEND_CSV_LINE (szObjectAttributes, DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, DELIVERY_MECHANISM);
    APPEND_CSV_LINE (szAttributeValues , DELIVERY_MECHANISM_MB);

    APPEND_CSV_LINE (szObjectAttributes, DELIV_EXT_CONT_TYPES);
    APPEND_CSV_LINE (szAttributeValues , "2A864886F7140501");

    if((lpszExtensionData != NULL) && (lstrlen(lpszExtensionData) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, EXTENSION_DATA);
sprintf(szTempBuffer, "=%s", lpszExtensionData);
APPEND_CSV_LINE (szAttributeValues , szTempBuffer);
    }

    if((lpszExtensionName != NULL) && (lstrlen(lpszExtensionName) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, EXTENSION_NAME);
APPEND_CSV_LINE (szAttributeValues , lpszExtensionName);
    }

    APPEND_CSV_LINE (szObjectAttributes, COMPUTER_NAME);
    GetComputerName((LPSTR)lpszComputerName, &cbComputerName);
    APPEND_CSV_LINE (szAttributeValues , lpszComputerName);

    if((lpszAccountName != NULL) && (lstrlen(lpszAccountName) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, ACCOUNT_NAME);
APPEND_CSV_LINE (szAttributeValues , lpszAccountName);
    }

    APPEND_CSV_LINE (szObjectAttributes, HOME_SERVER);

    APPEND_CSV_LINE (szAttributeValues , szHomeServer);

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
szBasePoint,
szObjectAttributes,
szAttributeValues));

cleanup:

    RETURN(hr);
}

//$--HrRemoveMailboxAgent----------------------------------------------------
// Removes a mailbox agent from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveMailboxAgent(    // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszRDN)              // relative distinguished name
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    LPSTR  lpsz                           = NULL;
    LPSTR  lpszT                          = NULL;
    LPSTR  lpszCommonName                 = NULL;
    BOOL    IsFound                        = FALSE;

    DEBUGPUBLIC("HrRemoveMailboxAgent()");

    hr = CHK_HrRemoveMailboxAgent(
lpszServer,
lpszSiteDN,
lpszRDN);
    if(FAILED(hr))
RETURN(hr);

    IsFound = FALSE;

    lpsz = lpszRDN + lstrlenA(lpszRDN);

    while(lpsz != lpszRDN)
    {
lpsz--;

if(*lpsz == '/')
{
    IsFound = TRUE;
    break;
}
    }

    if(IsFound == FALSE)
    {
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
    }

    lpszCommonName = lpsz + sizeof("cn=");

    lstrcpy(szCommonName, lpszCommonName);

    lstrcpy(szBasePoint, lpszSiteDN);
    lstrcpyn(szBasePoint + lstrlen(szBasePoint), lpszRDN, lpsz - lpszRDN + 1);

    hr = HR_LOG(HrDeleteObject(
lpszServer,
szBasePoint,
szCommonName,
OBJ_CLASS_MB));

cleanup:

    RETURN(hr);
}

//$--HrMailboxAgentExists----------------------------------------------------
//  Checks if a mailbox agent exists on a given site.
// -----------------------------------------------------------------------------
HRESULT HrMailboxAgentExists(    // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszRDN)              // relative distinguished name
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    LPSTR  lpsz                           = NULL;
    LPSTR  lpszT                          = NULL;
    LPSTR  lpszCommonName                 = NULL;
    BOOL    IsFound                        = FALSE;

    DEBUGPUBLIC("HrMailboxAgentExists()");

    hr = CHK_HrMailboxAgentExists(
lpszServer,
lpszSiteDN,
lpszRDN);
    if(FAILED(hr))
RETURN(hr);

    IsFound = FALSE;

    lpsz = lpszRDN + lstrlen(lpszRDN);

    while(lpsz != lpszRDN)
    {
lpsz--;

if(*lpsz == '/')
{
    IsFound = TRUE;
    break;
}
    }

    if(IsFound == FALSE)
    {
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
    }

    lpszCommonName = lpsz + sizeof("cn=");

    lstrcpy(szCommonName, lpszCommonName);

    lstrcpy(szBasePoint, lpszSiteDN);
    lstrcpyn(szBasePoint + lstrlen(szBasePoint), lpszRDN, lpsz - lpszRDN + 1);

    hr = HrEDKObjectExists(
lpszServer,
szBasePoint,
OBJ_CLASS_MB,
szCommonName);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrInstallAddressType----------------------------------------------------
//  Installs an address type object into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallAddressType(    // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszDisplayName,      // display name
    IN LPSTR lpszCommonName,       // common name
    IN DWORD  dwFileVersionMS,      // high-order 32 bits of file version number
    IN DWORD  dwFileVersionLS,      // low-order 32 bits of file version number
    IN LPSTR lpszProxyGenDLL,      // proxy generator DLL file name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")

{
    HRESULT hr                                   = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ]       = {0};
    CHAR   szFileVersion[MAX_CSV_LINE_SIZ]      = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues [MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                               = 0;
    WORD    wver[4]                              = {0};
    BYTE    bver[8]                              = {0};

    DEBUGPUBLIC("HrInstallAddressType()");

    hr = CHK_HrInstallAddressType(
lpszServer,
lpszSiteDN,
lpszDisplayName,
lpszCommonName,
dwFileVersionMS,
dwFileVersionLS,
lpszProxyGenDLL,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADDR_TYPE);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    wver[0] = LOWORD(dwFileVersionLS);
    wver[1] = HIWORD(dwFileVersionLS);
    wver[2] = LOWORD(dwFileVersionMS);
    wver[3] = HIWORD(dwFileVersionMS);

    bver[0] = LOBYTE(wver[0]);
    bver[1] = HIBYTE(wver[0]);
    bver[2] = LOBYTE(wver[1]);
    bver[3] = HIBYTE(wver[1]);
    bver[4] = LOBYTE(wver[2]);
    bver[5] = HIBYTE(wver[2]);
    bver[6] = LOBYTE(wver[3]);
    bver[7] = HIBYTE(wver[3]);

    cBytes = _snprintf(
szFileVersion,
MAX_CSV_LINE_SIZ,
"%02x%02x%02x%02x%02x%02x%02x%02x",
bver[0], bver[1], bver[2], bver[3],
bver[4], bver[5], bver[6], bver[7]);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_ADDR_TYPE);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, ADDR_TYPE);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);

    APPEND_CSV_LINE (szAttributeValues , szCommonName);

    APPEND_CSV_LINE (szObjectAttributes, PROXY_GENERATOR_DLL);
    APPEND_CSV_LINE (szAttributeValues , lpszProxyGenDLL);

    APPEND_CSV_LINE (szObjectAttributes, FILE_VERSION);
    APPEND_CSV_LINE (szAttributeValues , szFileVersion);

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
szBasePoint,
szObjectAttributes,
szAttributeValues));

cleanup:

    RETURN(hr);
}

//$--HrRemoveAddressType-----------------------------------------------------
//  Removes an address type from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveAddressType(     // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrRemoveAddressType()");

    hr = CHK_HrRemoveAddressType(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADDR_TYPE);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HR_LOG(HrDeleteObject(
lpszServer,
szBasePoint,
szCommonName,
OBJ_CLASS_ADDR_TYPE));

cleanup:

    RETURN(hr);
}

//$--HrAddressTypeExists-----------------------------------------------------
//  Checks if an address type exists on a given site.
// -----------------------------------------------------------------------------
HRESULT HrAddressTypeExists(     // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrAddressTypeExists()");

    hr = CHK_HrAddressTypeExists(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADDR_TYPE);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKObjectExists(
lpszServer,
szBasePoint,
OBJ_CLASS_ADDR_TYPE,
szCommonName);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrGetAddressTypeVersion-------------------------------------------------
//  Get the version of an address type object in a given site.
// -----------------------------------------------------------------------------
HRESULT HrGetAddressTypeVersion( // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszMachineType,      // machine type (e.g. "i386")
    OUT DWORD *lpdwFileVersionMS,   // high-order 32 bits of file version
    OUT DWORD *lpdwFileVersionLS)   // low-order 32 bits of file version
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    INT     cBytes                               = 0;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCurVersionRecord[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szFileVersionMS[2*sizeof(DWORD)+3]   = {0};
    CHAR   szFileVersionLS[2*sizeof(DWORD)+3]   = {0};
    CHAR   szCurField[MAX_PATH]                 = {0};
    CHAR   szTempName[MAX_PATH]                 = {0};
    WORD    wVersionField                        = MAX_WORD;
    WORD    wCurField                            = 0;
    LPSTR  endptr                               = NULL;

    DEBUGPUBLIC("HrGetAddressTypeVersion()");

    hr = CHK_HrGetAddressTypeVersion(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType,
lpdwFileVersionMS,
lpdwFileVersionLS);
    if(FAILED(hr))
RETURN(hr);

    *lpdwFileVersionMS = 0;
    *lpdwFileVersionLS = 0;

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, FILE_VERSION);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s:%s",
lpszSiteDN,
CONTAINER_ADDR_TYPE,
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKExportObject(
lpszServer,
szBasePoint,
DAPI_EXPORT_BASEPOINT_ONLY,
NULL,
szObjectAttributes,
szTempName);

    if(SUCCEEDED(hr))
    {
hTempFile = CreateFile(
    szTempName,
    GENERIC_READ,
    0,
    (LPSECURITY_ATTRIBUTES)NULL,
    OPEN_EXISTING,
    FILE_FLAG_DELETE_ON_CLOSE,
    (HANDLE)NULL);

if(hTempFile == INVALID_HANDLE_VALUE)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}

//
// The first line contains the list of fields - find which field has
// the file version.
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

for(
    wCurField = 1;

    FCsvGetField(
MAX_PATH,
wCurField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    wCurField++)
{
    if(lstrcmp(szCurField, FILE_VERSION) == 0)
    {
wVersionField = wCurField;
break;
    }
}

// Was the file version field exported & found above?

if(wVersionField == MAX_WORD)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

FCsvGetField(
    MAX_CSV_LINE_SIZ,
    wVersionField,
    EXCHINST_DELIM,
    szCurLine,
    szCurVersionRecord);

szFileVersionMS[ 0] = '0';
szFileVersionMS[ 1] = 'x';
szFileVersionMS[ 2] = szCurVersionRecord[16];
szFileVersionMS[ 3] = szCurVersionRecord[17];
szFileVersionMS[ 4] = szCurVersionRecord[14];
szFileVersionMS[ 5] = szCurVersionRecord[15];
szFileVersionMS[ 6] = szCurVersionRecord[12];
szFileVersionMS[ 7] = szCurVersionRecord[13];
szFileVersionMS[ 8] = szCurVersionRecord[10];
szFileVersionMS[ 9] = szCurVersionRecord[11];
szFileVersionMS[10] = 0;

szFileVersionLS[ 0] = '0';
szFileVersionLS[ 1] = 'x';
szFileVersionLS[ 2] = szCurVersionRecord[ 8];
szFileVersionLS[ 3] = szCurVersionRecord[ 9];
szFileVersionLS[ 4] = szCurVersionRecord[ 6];
szFileVersionLS[ 5] = szCurVersionRecord[ 7];
szFileVersionLS[ 6] = szCurVersionRecord[ 4];
szFileVersionLS[ 7] = szCurVersionRecord[ 5];
szFileVersionLS[ 8] = szCurVersionRecord[ 2];
szFileVersionLS[ 9] = szCurVersionRecord[ 3];
szFileVersionLS[10] = 0;

*lpdwFileVersionMS = _tcstoul(szFileVersionMS, &endptr, 16);
*lpdwFileVersionLS = _tcstoul(szFileVersionLS, &endptr, 16);
    }

cleanup:

    CLOSEHANDLE(hTempFile);

    RETURN(hr);
}

//$--HrInstallAddressTemplate------------------------------------------------
//  Installs an address template into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallAddressTemplate( // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszSiteDN,            // distinguished name of site
    IN LPSTR lpszDisplayName,       // display name
    IN LPSTR lpszCommonName,        // common name
    IN LPSTR lpszAddressSyntax,     // address syntax generator file
    IN LPSTR lpszAddressType,       // address type supported by this template
    IN LPSTR lpszPerMessageDDT,     // per-message dialog file
    IN LPSTR lpszPerRecipientDDT,   // per-recipient dialog file
    IN LPSTR lpszAddressEntryDDT,   // address entry dialog file
    IN LPSTR lpszLanguage,          // language supported by this template
    IN LPSTR lpszHelpData16,        // help data for 16-bit clients
    IN LPSTR lpszHelpData32,        // help data for 32-bit clients
    IN LPSTR lpszHelpFile)          // client help file name
{
    HRESULT hr                                   = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ]       = {0};
    CHAR   szTempBuffer[MAX_CSV_LINE_SIZ]       = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues [MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                               = 0;

    DEBUGPUBLIC("HrInstallAddressTemplate()");

    hr = CHK_HrInstallAddressTemplate(
lpszServer,
lpszSiteDN,
lpszDisplayName,
lpszCommonName,
lpszAddressSyntax,
lpszAddressType,
lpszPerMessageDDT,
lpszPerRecipientDDT,
lpszAddressEntryDDT,
lpszLanguage,
lpszHelpData16,
lpszHelpData32,
lpszHelpFile);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s",
lpszSiteDN,
CONTAINER_ADDR_TEMPLATE,
lpszLanguage);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_ADDR_TEMPLATE);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    APPEND_CSV_LINE (szObjectAttributes, ADDR_SYNTAX);
    sprintf(szTempBuffer, "=%s", lpszAddressSyntax);
    APPEND_CSV_LINE (szAttributeValues , szTempBuffer);

    APPEND_CSV_LINE (szObjectAttributes, ADDR_TYPE);
    APPEND_CSV_LINE (szAttributeValues , lpszAddressType);

    if((lpszPerMessageDDT != NULL) && (lstrlen(lpszPerMessageDDT) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, PER_MSG_DDT);
sprintf(szTempBuffer, "=%s", lpszPerMessageDDT);
APPEND_CSV_LINE (szAttributeValues , szTempBuffer);
    }

    if((lpszPerRecipientDDT != NULL) && (lstrlen(lpszPerRecipientDDT) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, PER_RECIP_DDT);
sprintf(szTempBuffer, "=%s", lpszPerRecipientDDT);
APPEND_CSV_LINE (szAttributeValues , szTempBuffer);
    }

    APPEND_CSV_LINE (szObjectAttributes, ADDR_ENTRY_DT);
    sprintf(szTempBuffer, "=%s", lpszAddressEntryDDT);
    APPEND_CSV_LINE (szAttributeValues , szTempBuffer);

    if((lpszHelpData16 != NULL) && (lstrlen(lpszHelpData16) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, HELP_DATA16);
sprintf(szTempBuffer, "=%s", lpszHelpData16);
APPEND_CSV_LINE (szAttributeValues , szTempBuffer);
    }

    if((lpszHelpData32 != NULL) && (lstrlen(lpszHelpData32) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, HELP_DATA32);
sprintf(szTempBuffer, "=%s", lpszHelpData32);
APPEND_CSV_LINE (szAttributeValues , szTempBuffer);
    }

    if((lpszHelpFile != NULL) && (lstrlen(lpszHelpFile) > 0))
    {
APPEND_CSV_LINE (szObjectAttributes, HELP_FILE_NAME);
APPEND_CSV_LINE (szAttributeValues , lpszHelpFile);
    }

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
szBasePoint,
szObjectAttributes,
szAttributeValues));

cleanup:

    RETURN(hr);
}

//$--HrRemoveAddressTemplate-------------------------------------------------
//  Removes an address template from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveAddressTemplate(  // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszSiteDN,            // distinguished name of site
    IN LPSTR lpszCommonName,        // common name
    IN LPSTR lpszAddressType,       // address type supported by this template
    IN LPSTR lpszLanguage)          // language supported by this template
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrRemoveAddressTemplate()");

    hr = CHK_HrRemoveAddressTemplate(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszAddressType,
lpszLanguage);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s",
lpszSiteDN,
CONTAINER_ADDR_TEMPLATE,
lpszLanguage);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HR_LOG(HrDeleteObject(
lpszServer,
szBasePoint,
lpszCommonName,
OBJ_CLASS_ADDR_TEMPLATE));

cleanup:

    RETURN(hr);
}

//$--HrAddressTemplateExists-------------------------------------------------
//  Checks if an address template exists on a given site.
// -----------------------------------------------------------------------------
HRESULT HrAddressTemplateExists(  // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszSiteDN,            // distinguished name of site
    IN LPSTR lpszCommonName,        // common name
    IN LPSTR lpszAddressType,       // address type supported by this template
    IN LPSTR lpszLanguage)          // language supported by this template
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrAddressTemplateExists()");

    hr = CHK_HrAddressTemplateExists(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszAddressType,
lpszLanguage);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s",
lpszSiteDN,
CONTAINER_ADDR_TEMPLATE,
lpszLanguage);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKObjectExists(
lpszServer,
szBasePoint,
OBJ_CLASS_ADDR_TEMPLATE,
lpszCommonName);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrInstallAdminExtension-------------------------------------------------
//  Installs an admin extension object into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallAdminExtension( // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszDisplayName,      // display name
    IN LPSTR lpszCommonName,       // common name
    IN DWORD  dwFileVersionMS,      // high-order 32 bits of file version number
    IN DWORD  dwFileVersionLS,      // low-order 32 bits of file version number
    IN LPSTR lpszExtensionDLL,     // admin extension DLL file name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")
{
    HRESULT hr                                   = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ]       = {0};
    CHAR   szFileVersion[MAX_CSV_LINE_SIZ]      = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues [MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                               = 0;
    WORD    wver[4]                              = {0};
    BYTE    bver[8]                              = {0};

    DEBUGPUBLIC("HrInstallAdminExtension()");

    hr = CHK_HrInstallAdminExtension(
lpszServer,
lpszSiteDN,
lpszDisplayName,
lpszCommonName,
dwFileVersionMS,
dwFileVersionLS,
lpszExtensionDLL,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADD_INS);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    wver[0] = LOWORD(dwFileVersionLS);
    wver[1] = HIWORD(dwFileVersionLS);
    wver[2] = LOWORD(dwFileVersionMS);
    wver[3] = HIWORD(dwFileVersionMS);

    bver[0] = LOBYTE(wver[0]);
    bver[1] = HIBYTE(wver[0]);
    bver[2] = LOBYTE(wver[1]);
    bver[3] = HIBYTE(wver[1]);
    bver[4] = LOBYTE(wver[2]);
    bver[5] = HIBYTE(wver[2]);
    bver[6] = LOBYTE(wver[3]);
    bver[7] = HIBYTE(wver[3]);

    cBytes = _snprintf(
szFileVersion,
MAX_CSV_LINE_SIZ,
"%02x%02x%02x%02x%02x%02x%02x%02x",
bver[0], bver[1], bver[2], bver[3],
bver[4], bver[5], bver[6], bver[7]);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_ADMIN_EXTENSION);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , szCommonName);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_EXTENSION_DLL);
    APPEND_CSV_LINE (szAttributeValues , lpszExtensionDLL);

    APPEND_CSV_LINE (szObjectAttributes, FILE_VERSION);
    APPEND_CSV_LINE (szAttributeValues , szFileVersion);

    hr = HR_LOG(HrEDKImportObject(
lpszServer,
szBasePoint,
szObjectAttributes,
szAttributeValues));

cleanup:

    RETURN(hr);
}

//$--HrRemoveAdminExtension--------------------------------------------------
//  Removes an admin extension from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveAdminExtension(  // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")
{
    HRESULT hr                             = 0;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrRemoveAdminExtension()");

    hr = CHK_HrRemoveAdminExtension(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADD_INS);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HR_LOG(HrDeleteObject(
lpszServer,
szBasePoint,
szCommonName,
OBJ_CLASS_ADMIN_EXTENSION));

cleanup:

    RETURN(hr);
}

//$--HrAdminExtensionExists--------------------------------------------------
//  Checks if an admin extension exists on a given site.
// -----------------------------------------------------------------------------
HRESULT HrAdminExtensionExists(  // RETURNS: return code
    IN LPSTR lpszServer,           // server name
    IN LPSTR lpszSiteDN,           // distinguished name of site
    IN LPSTR lpszCommonName,       // common name
    IN LPSTR lpszMachineType)      // machine type (e.g. "i386")
{
    HRESULT hr                             = NOERROR;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCommonName[MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrAdminExtensionExists()");

    hr = CHK_HrAdminExtensionExists(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType);
    if(FAILED(hr))
RETURN(hr);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_ADD_INS);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cBytes = _snprintf(
szCommonName,
MAX_CSV_LINE_SIZ,
"%s:%s",
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKObjectExists(
lpszServer,
szBasePoint,
OBJ_CLASS_ADMIN_EXTENSION,
szCommonName);

    if(FAILED(hr))
    {
goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrGetAdminExtensionVersion----------------------------------------------
//  Get the version of an admin extension object in a given site.
// -----------------------------------------------------------------------------
HRESULT HrGetAdminExtensionVersion(  // RETURNS: return code
    IN LPSTR lpszServer,               // server name
    IN LPSTR lpszSiteDN,               // distinguished name of site
    IN LPSTR lpszCommonName,           // common name
    IN LPSTR lpszMachineType,          // machine type (e.g. "i386")
    OUT DWORD *lpdwFileVersionMS,       // high-order 32 bits of file version
    OUT DWORD *lpdwFileVersionLS)       // low-order 32 bits of file version
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    INT     cBytes                               = 0;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCurVersionRecord[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szFileVersionMS[2*sizeof(DWORD)+3]   = {0};
    CHAR   szFileVersionLS[2*sizeof(DWORD)+3]   = {0};
    CHAR   szCurField[MAX_PATH]                 = {0};
    CHAR   szTempName[MAX_PATH]                 = {0};
    WORD    wVersionField                        = MAX_WORD;
    WORD    wCurField                            = 0;
    LPSTR  endptr                               = NULL;

    DEBUGPUBLIC("HrGetAdminExtensionVersion()");

    hr = CHK_HrGetAdminExtensionVersion(
lpszServer,
lpszSiteDN,
lpszCommonName,
lpszMachineType,
lpdwFileVersionMS,
lpdwFileVersionLS);
    if(FAILED(hr))
RETURN(hr);

    *lpdwFileVersionMS = 0;
    *lpdwFileVersionLS = 0;

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, FILE_VERSION);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s/cn=%s:%s",
lpszSiteDN,
CONTAINER_ADD_INS,
lpszCommonName,
lpszMachineType);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKExportObject(
lpszServer,
szBasePoint,
DAPI_EXPORT_BASEPOINT_ONLY,
NULL,
szObjectAttributes,
szTempName);

    if(SUCCEEDED(hr))
    {
hTempFile = CreateFile(
    szTempName,
    GENERIC_READ,
    0,
    (LPSECURITY_ATTRIBUTES)NULL,
    OPEN_EXISTING,
    FILE_FLAG_DELETE_ON_CLOSE,
    (HANDLE)NULL);

if(hTempFile == INVALID_HANDLE_VALUE)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}

//
// The first line contains the list of fields - find which field has
// the file version.
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

for(
    wCurField = 1;

    FCsvGetField(
MAX_PATH,
wCurField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    wCurField++)
{
    if(lstrcmp(szCurField, FILE_VERSION) == 0)
    {
wVersionField = wCurField;
break;
    }
}

// Was the file version field exported & found above?

if(wVersionField == MAX_WORD)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

FCsvGetField(
    MAX_CSV_LINE_SIZ,
    wVersionField,
    EXCHINST_DELIM,
    szCurLine,
    szCurVersionRecord);

szFileVersionMS[ 0] = '0';
szFileVersionMS[ 1] = 'x';
szFileVersionMS[ 2] = szCurVersionRecord[16];
szFileVersionMS[ 3] = szCurVersionRecord[17];
szFileVersionMS[ 4] = szCurVersionRecord[14];
szFileVersionMS[ 5] = szCurVersionRecord[15];
szFileVersionMS[ 6] = szCurVersionRecord[12];
szFileVersionMS[ 7] = szCurVersionRecord[13];
szFileVersionMS[ 8] = szCurVersionRecord[10];
szFileVersionMS[ 9] = szCurVersionRecord[11];
szFileVersionMS[10] = 0;

szFileVersionLS[ 0] = '0';
szFileVersionLS[ 1] = 'x';
szFileVersionLS[ 2] = szCurVersionRecord[ 8];
szFileVersionLS[ 3] = szCurVersionRecord[ 9];
szFileVersionLS[ 4] = szCurVersionRecord[ 6];
szFileVersionLS[ 5] = szCurVersionRecord[ 7];
szFileVersionLS[ 6] = szCurVersionRecord[ 4];
szFileVersionLS[ 7] = szCurVersionRecord[ 5];
szFileVersionLS[ 8] = szCurVersionRecord[ 2];
szFileVersionLS[ 9] = szCurVersionRecord[ 3];
szFileVersionLS[10] = 0;

*lpdwFileVersionMS = _tcstoul(szFileVersionMS, &endptr, 16);
*lpdwFileVersionLS = _tcstoul(szFileVersionLS, &endptr, 16);
    }

cleanup:

    CLOSEHANDLE(hTempFile);

    RETURN(hr);
}

//$--HrInstallContainer---------------------------------------------------------
//  Installs a container in the directory.
// -----------------------------------------------------------------------------
HRESULT HrInstallContainer(         // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszBasePoint,         // base point
    IN LPSTR lpszCommonName,        // common name

IN LPSTR lpszDisplayName,       // display name
    IN LPSTR lpszContainerInfo)     // container information
{
    HRESULT hr                                   = NOERROR;
    CHAR    szTempBuffer[MAX_CSV_LINE_SIZ]       = {0};
    CHAR    szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR    szAttributeValues [MAX_CSV_LINE_SIZ] = {0};
    INT     cBytes                               = 0;

    DEBUGPUBLIC("HrInstallContainer()");

    hr = CHK_HrInstallContainer(
        lpszServer,
        lpszBasePoint,
        lpszCommonName,
        lpszDisplayName,
        lpszContainerInfo);
    if(FAILED(hr))
        RETURN(hr);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_CONTAINER);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_CREATE);

    APPEND_CSV_LINE (szObjectAttributes, ADMIN_DISPLAY_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszDisplayName);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszCommonName);

    APPEND_CSV_LINE (szObjectAttributes, CONTAINER_INFO);
    APPEND_CSV_LINE (szAttributeValues , lpszContainerInfo);

    hr = HR_LOG(HrEDKImportObject(
        lpszServer,
        lpszBasePoint,
        szObjectAttributes,
        szAttributeValues));

    RETURN(hr);
}

//$--HrRemoveContainer----------------------------------------------------------
//  Removes a container from the directory.
// -----------------------------------------------------------------------------
HRESULT HrRemoveContainer(          // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszBasePoint,         // base point
    IN LPSTR lpszCommonName)        // common name
{
    HRESULT hr                             = NOERROR;
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrRemoveContainer()");

    hr = CHK_HrRemoveContainer(
        lpszServer,
        lpszBasePoint,
        lpszCommonName);
    if(FAILED(hr))
        RETURN(hr);

    hr = HR_LOG(HrDeleteObject(
        lpszServer,
        lpszBasePoint,
        lpszCommonName,
        OBJ_CLASS_CONTAINER));

    RETURN(hr);
}

//$--HrContainerExists----------------------------------------------------------
//  Checks if a container exists in the directory.
// -----------------------------------------------------------------------------
HRESULT HrContainerExists(          // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszBasePoint,         // base point
    IN LPSTR lpszCommonName)        // common name
{
    HRESULT hr                             = NOERROR;
    INT     cBytes                         = 0;

    DEBUGPUBLIC("HrContainerExists()");

    hr = CHK_HrContainerExists(
        lpszServer,
        lpszBasePoint,
        lpszCommonName);
    if(FAILED(hr))
        RETURN(hr);

    hr = HrEDKObjectExists(
        lpszServer,
        lpszBasePoint,
        OBJ_CLASS_CONTAINER,
        lpszCommonName);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrInstallAddressTemplateContainer------------------------------------------
//  Installs an address template container in the directory.
// -----------------------------------------------------------------------------
HRESULT HrInstallAddressTemplateContainer(  // RETURNS: return code
    IN LPSTR lpszServer,                    // server name
    IN LPSTR lpszSiteDN,                    // site distinguished name
    IN LPSTR lpszLanguage,                  // language supported by container
    IN LPSTR lpszDisplayName)               // display name
{
    HRESULT hr                             = NOERROR;
    INT     cBytes                         = 0;
    CHAR    szBasePoint[MAX_CSV_LINE_SIZ]  = {0};

    DEBUGPUBLIC("HrInstallAddressTemplateContainer()");

    hr = CHK_HrInstallAddressTemplateContainer(
        lpszServer,
        lpszSiteDN,
        lpszLanguage,
        lpszDisplayName);
    if(FAILED(hr))
        RETURN(hr);

    cBytes = _snprintf(
        szBasePoint,
        MAX_CSV_LINE_SIZ,
        "%s%s",
        lpszSiteDN,
        CONTAINER_ADDR_TEMPLATE);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrInstallContainer(
        lpszServer,
        szBasePoint,
        lpszLanguage,
        lpszDisplayName,
        ADDRESS_TEMPLATE_CONTAINER_INFO);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrRemoveAddressTemplateContainer-------------------------------------------
//  Removes an address template container from the directory.
// -----------------------------------------------------------------------------
HRESULT HrRemoveAddressTemplateContainer(   // RETURNS: return code
    IN LPSTR lpszServer,                    // server name
    IN LPSTR lpszSiteDN,                    // site distinguished name
    IN LPSTR lpszLanguage)                  // language supported by container
{
    HRESULT hr                             = NOERROR;
    INT     cBytes                         = 0;
    CHAR    szBasePoint[MAX_CSV_LINE_SIZ]  = {0};

    DEBUGPUBLIC("HrRemoveAddressTemplateContainer()");

    hr = CHK_HrRemoveAddressTemplateContainer(
        lpszServer,
        lpszSiteDN,
        lpszLanguage);
    if(FAILED(hr))
        RETURN(hr);

    cBytes = _snprintf(
        szBasePoint,
        MAX_CSV_LINE_SIZ,
        "%s%s",
        lpszSiteDN,
        CONTAINER_ADDR_TEMPLATE);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrRemoveContainer(
        lpszServer,
        szBasePoint,
        lpszLanguage);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrAddressTemplateContainerExists-------------------------------------------
//  Checks if an address template container exists in the directory.
// -----------------------------------------------------------------------------
HRESULT HrAddressTemplateContainerExists(   // RETURNS: return code
    IN LPSTR lpszServer,                    // server name
    IN LPSTR lpszSiteDN,                    // site distinguished name
    IN LPSTR lpszLanguage)                  // language supported by container
{
    HRESULT hr                             = NOERROR;
    INT     cBytes                         = 0;
    CHAR    szBasePoint[MAX_CSV_LINE_SIZ]  = {0};

    DEBUGPUBLIC("HrAddressTemplateContainerExists()");

    hr = CHK_HrAddressTemplateContainerExists(
        lpszServer,
        lpszSiteDN,
        lpszLanguage);
    if(FAILED(hr))
        RETURN(hr);

    cBytes = _snprintf(
        szBasePoint,
        MAX_CSV_LINE_SIZ,
        "%s%s",
        lpszSiteDN,
        CONTAINER_ADDR_TEMPLATE);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrContainerExists(
        lpszServer,
        szBasePoint,
        lpszLanguage);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrAddDelGatewayProxy-------------------------------------------------------
// This function will add/delete our gateway proxy to/from the given site.
// -----------------------------------------------------------------------------
static HRESULT HrAddDelGatewayProxy(    // RETURNS: return code
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR lpszSiteDN,               // site distinguished name
    IN  LPSTR lpszOurProxyString,       // proxy string for our gateway
    IN  BOOL  fAddProxy)                // If TRUE we will add proxy
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    INT     cBytes                               = 0;
    CHAR   szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR   szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR   szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR   szCurProxyRecord[MAX_CSV_LINE_SIZ]   = {0};
    CHAR   szNewProxyRecord[MAX_CSV_LINE_SIZ]   = {0};
    CHAR   szCurProxyString[MAX_CSV_LINE_SIZ]   = {0};
    CHAR   szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR   szCurField[MAX_PATH]                 = {0};
    CHAR   szTempName[MAX_PATH]                 = {0};
    CHAR   szTmpBuf[MAX_CSV_LINE_SIZ]           = {0};
    WORD    wGatewayProxyField                   = MAX_WORD;
    WORD    wCurField                            = 0;
    BOOL    fOurProxyExist                       = FALSE;
    DWORD   cchOurProxyString                    = 0;

    hr = CHK_HrAddDelGatewayProxy(
lpszServer,
lpszSiteDN,
lpszOurProxyString,
fAddProxy);
    if(FAILED(hr))
RETURN(hr);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, GATEWAY_PROXY);

    cBytes = _snprintf(
szBasePoint,
MAX_CSV_LINE_SIZ,
"%s%s",
lpszSiteDN,
CONTAINER_SITE_ADDR);

    if(cBytes < 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    hr = HrEDKExportObject(
lpszServer,
szBasePoint,
DAPI_EXPORT_BASEPOINT_ONLY,
NULL,
szObjectAttributes,
szTempName);

    if(SUCCEEDED(hr))
    {
hTempFile = CreateFile(
    szTempName,
    GENERIC_READ,
    0,
    (LPSECURITY_ATTRIBUTES)NULL,
    OPEN_EXISTING,
    FILE_FLAG_DELETE_ON_CLOSE,
    (HANDLE)NULL);

if(hTempFile == INVALID_HANDLE_VALUE)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}

//
// The first line contains the list of fields - find which field has
// the gateway proxy.
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

for(
    wCurField = 1;

    FCsvGetField(
MAX_PATH,
wCurField,
EXCHINST_DELIM,
szCurLine,
szCurField);

    wCurField++)
{
    if(strcmp(szCurField, GATEWAY_PROXY) == 0)
    {
wGatewayProxyField = wCurField;
break;
    }
}

// Was the gateway proxy field exported & found above?

if(wGatewayProxyField == MAX_WORD)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

cchOurProxyString = lstrlen(lpszOurProxyString);

//
// Get the current Proxy record and Add or Delete it as specified
// (Add only if it doesnt exist)
// (e.g. Proxy record: SMTP:@site.ent.com%MS:site/ent%EDK:@site.ent.com)
//

FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

FCsvGetField(
    MAX_CSV_LINE_SIZ,
    wGatewayProxyField,
    EXCHINST_DELIM,
    szCurLine,
    szCurProxyRecord);

for(
    wCurField = 1;

    FCsvGetField(
MAX_PATH,
wCurField,
EXCHINST_MV_SEP,
szCurProxyRecord,
szCurProxyString);

    wCurField++)
{
    if(_strnicmp(
szCurProxyString, lpszOurProxyString, cchOurProxyString) == 0)
    {
fOurProxyExist = TRUE;

if(fAddProxy)
{
    if(lstrlen(szNewProxyRecord) != 0)
lstrcat(szNewProxyRecord, SZ_EXCHINST_MV_SEP);
    lstrcat(szNewProxyRecord, szCurProxyString);
}
    }
    else
    {
if(lstrlen(szNewProxyRecord) != 0)
    lstrcat(szNewProxyRecord, SZ_EXCHINST_MV_SEP);
lstrcat(szNewProxyRecord, szCurProxyString);
    }
}

if(fOurProxyExist == FALSE)
{
    if(fAddProxy)
    {
if(lstrlen(szNewProxyRecord) != 0)
    lstrcat(szNewProxyRecord, SZ_EXCHINST_MV_SEP);
lstrcat(szNewProxyRecord, lpszOurProxyString);
    }
    else
    {
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
    }
}

// Build line to perform the modification.

BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_SITE);

APPEND_CSV_LINE (szObjectAttributes, MODE);
APPEND_CSV_LINE (szAttributeValues , MODE_MODIFY);

APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
APPEND_CSV_LINE (szAttributeValues , SITE_ADDRESSING);

APPEND_CSV_LINE (szObjectAttributes, GATEWAY_PROXY);
APPEND_CSV_LINE (szAttributeValues , szNewProxyRecord);

cBytes = _snprintf(
    szBasePoint,
    MAX_CSV_LINE_SIZ,
    "%s%s",
    lpszSiteDN,
    CONTAINER_CONFIGURATION);

if(cBytes < 0)
{
    hr = HR_LOG(E_FAIL);
    goto cleanup;
}

hr = HR_LOG(HrEDKImportObject(
    lpszServer,
    szBasePoint,
    szObjectAttributes,
    szAttributeValues));
    }

cleanup:

    CLOSEHANDLE(hTempFile);

    RETURN(hr);
}

//$--HrInstallGatewayProxy---------------------------------------------------
//  Installs a gateway proxy into a given site.
// -----------------------------------------------------------------------------
HRESULT HrInstallGatewayProxy(    // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszSiteDN,            // distinguished name of site
    IN LPSTR lpszProxy)             // gateway proxy
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrInstallGatewayProxy()");

    hr = CHK_HrInstallGatewayProxy(
lpszServer,
lpszSiteDN,
lpszProxy);
    if(FAILED(hr))
RETURN(hr);

    RETURN(HrAddDelGatewayProxy(
lpszServer,
lpszSiteDN,
lpszProxy,
TRUE));
}

//$--HrRemoveGatewayProxy----------------------------------------------------
//  Removes a gateway proxy from a given site.
// -----------------------------------------------------------------------------
HRESULT HrRemoveGatewayProxy(     // RETURNS: return code
    IN LPSTR lpszServer,            // server name
    IN LPSTR lpszSiteDN,            // distinguished name of site
    IN LPSTR lpszProxy)             // gateway proxy
{
    RETURN(HrAddDelGatewayProxy(
lpszServer,
lpszSiteDN,
lpszProxy,
FALSE));
}

//$--FHasAdminPrivs----------------------------------------------------------------
//  Returns TRUE if the current user is an administrator.
// -----------------------------------------------------------------------------
BOOL FHasAdminPrivs(                    // RETURNS: TRUE/FALSE
    void)                            // no arguments
{
    BOOL                     fRet               = TRUE;
    HANDLE                   hProcess           = INVALID_HANDLE_VALUE;
    HANDLE                   hAccessToken       = INVALID_HANDLE_VALUE;
    UCHAR                    InfoBuffer[1024]   = {0};
    PTOKEN_GROUPS            ptgGroups          = (PTOKEN_GROUPS)InfoBuffer;
    DWORD                    dwInfoBufferSize   = 0;
    PSID                     psidAdministrators = NULL;
    SID_IDENTIFIER_AUTHORITY siaNtAuthority     = SECURITY_NT_AUTHORITY;
    UINT                     x                  = 0;

    hProcess = GetCurrentProcess();

    if(!OpenProcessToken(hProcess,TOKEN_READ,&hAccessToken))
return(FALSE);

    if(!GetTokenInformation(
hAccessToken,TokenGroups, InfoBuffer, 1024, &dwInfoBufferSize))
return(FALSE);

    fRet = AllocateAndInitializeSid(
&siaNtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0,
0,
0,
0,
0,
0,
&psidAdministrators);

    if (!fRet)
    {
return(FALSE);
    }

    for(x = 0; x < ptgGroups->GroupCount; x++)
    {
if(EqualSid(psidAdministrators, ptgGroups->Groups[x].Sid))
{
    FreeSid(psidAdministrators);
    return(TRUE);
}
    }

    FreeSid(psidAdministrators);

    return(FALSE);
}

//$--HrInstallMessageConverter-----------------------------------------------
// Installs a message conversion DLL.
// -----------------------------------------------------------------------------
HRESULT HrInstallMessageConverter(   // RETURNS: return code
    IN LPSTR lpszConverter,            // converter name
    IN LPSTR lpszDllName,              // name of conversion DLL
    IN LPSTR lpszMessageClasses,       // message classes
    IN LPSTR lpszOptions,              // options string
    IN LPSTR lpszPoint)                // conversion point
{
    HRESULT hr                 = NOERROR;
    LONG    lRet               = 0;
    HKEY    hkClasses          = INVALID_HANDLE_VALUE;
    HKEY    hkObject           = INVALID_HANDLE_VALUE;
    HKEY    hkTmp              = INVALID_HANDLE_VALUE;
    DWORD   dwDisposition      = 0;

    DEBUGPUBLIC("HrInstallMessageConverter()\n");

    hr = CHK_HrInstallMessageConverter(
lpszConverter,
lpszDllName,
lpszMessageClasses,
lpszOptions,
lpszPoint);
    if(FAILED(hr))
RETURN(hr);

    lRet = RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
"Software\\Classes\\MAPI Conversions",
0,
"",
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&hkClasses,
&dwDisposition);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    //  Create the object key.
    //

    lRet = CREATEKEY(hkClasses, lpszDllName, hkObject, dwDisposition);

    if((lRet != ERROR_SUCCESS) && (lRet != ERROR_ALREADY_EXISTS))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    //
    // Create GetTrivialVector key.
    //

    lRet = CREATEKEY(hkObject, "GetTrivialVector", hkTmp, dwDisposition);

    if((lRet != ERROR_SUCCESS) && (lRet != ERROR_ALREADY_EXISTS))
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    // Create new values under GetTrivialVector key

    lRet = SETMULTISZVALUE(hkTmp, "Classes", lpszMessageClasses);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

    if(lpszOptions != NULL)
    {
lRet = SETSZVALUE(hkTmp, "Options", lpszOptions);

if(lRet != ERROR_SUCCESS)
{
    hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
    goto cleanup;
}
    }

    lRet = SETSZVALUE(hkTmp, "Point", lpszPoint);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

cleanup:

    FREEHKEY(hkClasses);
    FREEHKEY(hkObject);
    FREEHKEY(hkTmp);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrRemoveMessageConverter-----------------------------------------------
//  Delete a message conversion DLL.
// ----------------------------------------------------------------------------
HRESULT HrRemoveMessageConverter(    // RETURNS: return code
    IN LPCSTR lpszConverter)           // converter name
{
    HRESULT hr         = NOERROR;
    LONG    lRet       = 0;
    HKEY    hkClasses  = INVALID_HANDLE_VALUE;
    HKEY    hkObject   = INVALID_HANDLE_VALUE;

    DEBUGPUBLIC("HrRemoveMessageConverter()\n");

    hr = CHK_HrRemoveMessageConverter(
lpszConverter);
    if(FAILED(hr))
RETURN(hr);

    //
    //  Open the services key.
    //

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("Software\\Classes\\MAPI Conversions"),
0,
KEY_ALL_ACCESS,
&hkClasses);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    //
    //  Open the conversion key.
    //

    lRet = RegOpenKeyEx(
hkClasses,
lpszConverter,
0,
KEY_ALL_ACCESS,
&hkObject);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    (void)HrDeleteContainedKeys(hkObject);

    lRet = RegDeleteKey(hkClasses, lpszConverter);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

cleanup:

    FREEHKEY(hkObject);
    FREEHKEY(hkClasses);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrAdminProgramExists---------------------------------------------------
//  Check if the admin program has been installed on the local computer.
// ----------------------------------------------------------------------------
HRESULT HrAdminProgramExists(        // RETURNS: return code
    void)                               // nothing
{
    HRESULT hr                     = NOERROR;
    LONG    lRet                   = 0;
    HKEY    hKey                   = INVALID_HANDLE_VALUE;
    DWORD   dwType                 = 0;
    DWORD   cBytes                 = 0;
    LPSTR  lpszAdminDest          = NULL;
    CHAR   szFileName[MAX_PATH+1] = {0};
    ULONG   cch                    = 0;
    DWORD   dwAttrib               = 0;

    DEBUGPUBLIC("HrAdminProgramExists()\n");

    lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("Software\\Microsoft\\Exchange\\Setup"),
0,
KEY_ALL_ACCESS,
&hKey);

    if(lRet != ERROR_SUCCESS)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
goto cleanup;
    }

    hr = HrGetRegistryValue(
hKey, "AdminDest", &dwType, &cBytes, (LPVOID) &lpszAdminDest);

    if(FAILED(hr) || dwType != REG_SZ)
    {
if(dwType != REG_SZ)
    hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    if(lpszAdminDest == NULL)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    cch = lstrlen(lpszAdminDest);

    if(cch == 0)
    {
hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    lstrcpy(szFileName, lpszAdminDest);

    if(szFileName[cch-1] != '\\')
    {
lstrcatA(szFileName, "\\");
    }

    lstrcatA(szFileName, "ADMIN.EXE");

    dwAttrib = GetFileAttributes(szFileName);

    if(dwAttrib == 0xFFFFFFFF)
    {
hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
goto cleanup;
    }

cleanup:

GLOBALFREE(lpszAdminDest);
    FREEHKEY(hKey);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrSetGatewayBit------------------------------------------------------------
//  This function will set the gateway bit on a given server.
// -----------------------------------------------------------------------------
HRESULT HrSetGatewayBit(                // RETURNS: return code
    IN  LPSTR lpszServer,               // server name
    IN  LPSTR lpszSiteDN,               // site distinguished name
    IN  LPSTR lpszServerName)           // server name
{
    HRESULT hr                                   = NOERROR;
    HANDLE  hTempFile                            = INVALID_HANDLE_VALUE;
    INT     cBytes                               = 0;
    CHAR    szBasePoint[MAX_CSV_LINE_SIZ]        = {0};
    CHAR    szObjectAttributes[MAX_CSV_LINE_SIZ] = {0};
    CHAR    szAttributeValues[MAX_CSV_LINE_SIZ]  = {0};
    CHAR    szCurHeuristicRecord[MAX_CSV_LINE_SIZ] = {0};
    CHAR    szNewHeuristicRecord[MAX_CSV_LINE_SIZ] = {0};
    CHAR    szCurLine[MAX_CSV_LINE_SIZ]          = {0};
    CHAR    szCurField[MAX_PATH]                 = {0};
    CHAR    szTempName[MAX_PATH]                 = {0};
    CHAR    szTmpBuf[MAX_CSV_LINE_SIZ]           = {0};
    WORD    wHeuristicField                      = MAX_WORD;
    WORD    wCurField                            = 0;
    ULONG   ulHeuristic                          = 0;

    hr = CHK_HrSetGatewayBit(
    lpszServer,
    lpszSiteDN,
    lpszServerName);
    if(FAILED(hr))
    RETURN(hr);

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    APPEND_CSV_LINE (szObjectAttributes, HEURISTICS);

    cBytes = _snprintf(
    szBasePoint,
    MAX_CSV_LINE_SIZ,
    "%s%s/cn=%s",
    lpszSiteDN,
    CONTAINER_SERVERS,
        lpszServerName);

    if(cBytes < 0)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = HrEDKExportObject(
    lpszServer,
    szBasePoint,
    DAPI_EXPORT_BASEPOINT_ONLY,
    NULL,
    szObjectAttributes,
    szTempName);

    if(SUCCEEDED(hr))
    {
    hTempFile = CreateFile(
        szTempName,
        GENERIC_READ,
        0,
        (LPSECURITY_ATTRIBUTES)NULL,
        OPEN_EXISTING,
        FILE_FLAG_DELETE_ON_CLOSE,
        (HANDLE)NULL);

    if(hTempFile == INVALID_HANDLE_VALUE)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    //
    // The first line contains the list of fields - find which field has
    // the heuristic.
    //

    FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

    for(
        wCurField = 1;

        FCsvGetField(
    MAX_PATH,
    wCurField,
    EXCHINST_DELIM,
    szCurLine,
    szCurField);

        wCurField++)
    {
        if(strcmp(szCurField, HEURISTICS) == 0)
        {
        wHeuristicField = wCurField;
        break;
        }
    }

    // Was the heuristic field exported & found above?

    if(wHeuristicField == MAX_WORD)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Get the current value of the heuristic attribute.
    //

    FCsvGetRecord(MAX_CSV_LINE_SIZ, hTempFile, szCurLine);

    FCsvGetField(
        MAX_CSV_LINE_SIZ,
        wHeuristicField,
        EXCHINST_DELIM,
        szCurLine,
        szCurHeuristicRecord);

        //
        // Set the gateway bit.
        //

        ulHeuristic  = atol(szCurHeuristicRecord);

        ulHeuristic |= 2;

        cBytes = _snprintf(
        szNewHeuristicRecord,
        MAX_CSV_LINE_SIZ,
        "%lu",
            ulHeuristic);

        if(cBytes < 0)
        {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
        }

    // Build line to perform the modification.

    BEGIN_CSV_LINE  (szObjectAttributes, OBJ_CLASS);
    BEGIN_CSV_LINE  (szAttributeValues , OBJ_CLASS_COMPUTER);

    APPEND_CSV_LINE (szObjectAttributes, MODE);
    APPEND_CSV_LINE (szAttributeValues , MODE_MODIFY);

    APPEND_CSV_LINE (szObjectAttributes, COMMON_NAME);
    APPEND_CSV_LINE (szAttributeValues , lpszServerName);

    APPEND_CSV_LINE (szObjectAttributes, HEURISTICS);
    APPEND_CSV_LINE (szAttributeValues , szNewHeuristicRecord);

        cBytes = _snprintf(
        szBasePoint,
        MAX_CSV_LINE_SIZ,
        "%s%s",
        lpszSiteDN,
        CONTAINER_SERVERS);

        if(cBytes < 0)
        {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
        }

    hr = HR_LOG(HrEDKImportObject(
        lpszServer,
        szBasePoint,
        szObjectAttributes,
        szAttributeValues));
    }

cleanup:

    CLOSEHANDLE(hTempFile);

    RETURN(hr);
}

#include "exchprof.c"


EXCHPROF.C

// --exchprof.c-----------------------------------------------------------------
//
// Functions for installing Exchange objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#include "exchprof.chk"

#define lpszServerPlaceHolder           ((LPSTR)-1)
#define lpszObjectPlaceHolder           ((LPSTR)-2)
#define lpszDisplayNamePlaceHolder      ((LPSTR)-3)
#define lpszProfileVersionPlaceHolder   ((LPSTR)-4)

typedef struct
{
    LPSTR  lpszSection;
    LPSTR  lpszKey;
    ULONG   ulKey;
    LPSTR  lpszValue;
} SERVICESINIREC;

//$--aGwServicesIni-------------------------------------------------------------
//  Structure defines data to be written into MAPISVC.INF to create gateway
//  service.
// -----------------------------------------------------------------------------
static SERVICESINIREC aGwServicesIni[] =
{
    {"Services",                 "MSEMS_EdkGateway", 0L,                    "Microsoft Exchange Gateway Profile"},

    {"MSEMS_EdkGateway",         NULL,        PR_DISPLAY_NAME,              "Microsoft Exchange Gateway"},
    {"MSEMS_EdkGateway",         "Providers", 0L,                           "MSEMS_DSA_EdkGateway, MSEMS_MDB_EdkGateway, MSEMS_EdkGateway_Public"},
    {"MSEMS_EdkGateway",         "Sections",  0L,                           "MSEMS_EdkGateway_Section"},
    {"MSEMS_EdkGateway",         NULL,        PR_SERVICE_DLL_NAME,          "emsui.dll"},
    {"MSEMS_EdkGateway",         NULL,        PR_SERVICE_ENTRY_NAME,        "EMSCfg"},

    {"MSEMS_EdkGateway_Public",  NULL,        PR_DISPLAY_NAME,              "Public Folders"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_PROVIDER_DISPLAY,          "Microsoft Exchange Message Store"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_RESOURCE_TYPE,             "MAPI_STORE_PROVIDER"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_PROVIDER_DLL_NAME,         "EMSMDB.DLL"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_PROFILE_SERVER,            lpszServerPlaceHolder},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_RESOURCE_FLAGS,            "STATUS_NO_DEFAULT_STORE"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_PROFILE_OPEN_FLAGS,        "06000000"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_PROFILE_TYPE,              "03000000"},
    {"MSEMS_EdkGateway_Public",  NULL,        PR_MDB_PROVIDER,              "78b2fa70aff711cd9bc800aa002fc45a"},

    {"MSEMS_DSA_EdkGateway",     NULL,        PR_DISPLAY_NAME,              "Microsoft Exchange Directory Service"},
    {"MSEMS_DSA_EdkGateway",     NULL,        PR_PROVIDER_DISPLAY,          "Microsoft Exchange Directory Service"},
    {"MSEMS_DSA_EdkGateway",     NULL,        PR_PROVIDER_DLL_NAME,         "emsabp.dll"},
    {"MSEMS_DSA_EdkGateway",     NULL,        PR_RESOURCE_TYPE,             "23000000"},

    {"MSEMS_MDB_EdkGateway",     NULL,        PR_DISPLAY_NAME,              "Private Folders"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_PROVIDER_DISPLAY,          "Microsoft Exchange Message Store"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_PROVIDER_DLL_NAME,         "emsmdb.dll"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_RESOURCE_TYPE,             "21000000"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_RESOURCE_FLAGS,            "STATUS_PRIMARY_IDENTITY|STATUS_DEFAULT_STORE|STATUS_PRIMARY_STORE"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_PROFILE_SERVER,            lpszServerPlaceHolder},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_PROFILE_OPEN_FLAGS,        "00000000"},
    {"MSEMS_MDB_EdkGateway",     NULL,        PR_PROFILE_MAILBOX,           lpszObjectPlaceHolder},

    {"MSEMS_EdkGateway_Section", "UID",       0L,                           "13DBB0C8AA05101A9BB000AA002FC45A"}, // pbGlobalProfileSectionGuid
    {"MSEMS_EdkGateway_Section", NULL,        PR_PROFILE_VERSION,           lpszProfileVersionPlaceHolder},
    {"MSEMS_EdkGateway_Section", NULL,        PR_PROFILE_TRANSPORT_FLAGS,   "00000000"},
    {"MSEMS_EdkGateway_Section", NULL,        PR_PROFILE_CONNECT_FLAGS,     "01000000"},
    {"MSEMS_EdkGateway_Section", NULL,        PR_PROFILE_HOME_SERVER,       lpszServerPlaceHolder},
    {"MSEMS_EdkGateway_Section", NULL,        PR_PROFILE_USER,              lpszObjectPlaceHolder},

    {NULL, NULL, 0L, NULL}
};

static SERVICESINIREC aMbServicesIni[] =
{
{"Services",                 "MSEMS_EdkMbAgent", 0L, "Microsoft Exchange Mailbox Agent Profile"},

{"MSEMS_EdkMbAgent",         NULL,        PR_DISPLAY_NAME,            "Microsoft Exchange Mailbox Agent"},
{"MSEMS_EdkMbAgent",         "Providers", 0L,                         "MSEMS_EdkMbAgent_DSA, MSEMS_EdkMbAgent_MDB, MSEMS_EdkMbAgent_Public, MSEMS_EdkMbAgent_MSX"},
{"MSEMS_EdkMbAgent",         "Sections",  0L,                         "MSEMS_EdkMbAgent_Section"},
{"MSEMS_EdkMbAgent",         NULL,        PR_SERVICE_DLL_NAME,        "emsui.dll"},
{"MSEMS_EdkMbAgent",         NULL,        PR_SERVICE_SUPPORT_FILES,   "emsui.dll,emsabp.dll,emsmdb.dll,emsuix.dll"},
{"MSEMS_EdkMbAgent",         NULL,        PR_SERVICE_ENTRY_NAME,      "EMSCfg"},
{"MSEMS_EdkMbAgent",         NULL,        PR_RESOURCE_FLAGS,          "SERVICE_SINGLE_COPY"},

    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_DISPLAY_NAME,            "Microsoft Exchange Directory Service"},
    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_PROVIDER_DISPLAY,        "Microsoft Exchange Directory Service"},
    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_PROFILE_HOME_SERVER,     lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_PROFILE_SERVER,          lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_PROVIDER_DLL_NAME,       "emsabp.dll"},
    {"MSEMS_EdkMbAgent_DSA",     NULL,        PR_RESOURCE_TYPE,           "MAPI_AB_PROVIDER"},

{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_DISPLAY_NAME,            "Private Folders"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROVIDER_DISPLAY,        "Microsoft Exchange Message Store"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROVIDER_DLL_NAME,       "EMSMDB.DLL"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_RESOURCE_TYPE,           "MAPI_STORE_PROVIDER"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_RESOURCE_FLAGS,          "STATUS_PRIMARY_IDENTITY|STATUS_DEFAULT_STORE|STATUS_PRIMARY_STORE"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROFILE_OPEN_FLAGS,      "0C000000"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROFILE_TYPE,            "01000000"},
{"MSEMS_EdkMbAgent_MDB",     NULL,        PR_MDB_PROVIDER,            "5494A1C0297F101BA58708002B2A2517"},
    {"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROFILE_SERVER,          lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_MDB",     NULL,        PR_PROFILE_MAILBOX,         lpszObjectPlaceHolder},

    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_DISPLAY_NAME,            "Microsoft Exchange Transport"},
    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_PROVIDER_DISPLAY,        "Microsoft Exchange Transport"},
    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_PROVIDER_DLL_NAME,       "EMSMDB.DLL"},
    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_RESOURCE_TYPE,           "MAPI_TRANSPORT_PROVIDER"},
{"MSEMS_EdkMbAgent_MSX",     NULL,        PR_PROFILE_OPEN_FLAGS,      "00000000"},
    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_PROFILE_SERVER,          lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_MSX",     NULL,        PR_PROFILE_MAILBOX,         lpszObjectPlaceHolder},

    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_DISPLAY_NAME,            "Public Folders"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_PROVIDER_DISPLAY,        "Microsoft Exchange Message Store"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_RESOURCE_TYPE,           "MAPI_STORE_PROVIDER"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_PROVIDER_DLL_NAME,       "EMSMDB.DLL"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_PROFILE_SERVER,          lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_RESOURCE_FLAGS,          "STATUS_NO_DEFAULT_STORE"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_PROFILE_OPEN_FLAGS,      "06000000"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_PROFILE_TYPE,            "03000000"},
    {"MSEMS_EdkMbAgent_Public",  NULL,        PR_MDB_PROVIDER,            "78b2fa70aff711cd9bc800aa002fc45a"},

    {"MSEMS_EdkMbAgent_Section", "UID",       0L,                         "13DBB0C8AA05101A9BB000AA002FC45A"}, // pbGlobalProfileSectionGuid
    {"MSEMS_EdkMbAgent_Section", NULL,        PR_PROFILE_VERSION,         lpszProfileVersionPlaceHolder},
    {"MSEMS_EdkMbAgent_Section", NULL,        PR_PROFILE_TRANSPORT_FLAGS, "00000000"},
    {"MSEMS_EdkMbAgent_Section", NULL,        PR_PROFILE_CONNECT_FLAGS,   "03000000"},
    {"MSEMS_EdkMbAgent_Section", NULL,        PR_PROFILE_HOME_SERVER,     lpszServerPlaceHolder},
    {"MSEMS_EdkMbAgent_Section", NULL,        PR_PROFILE_USER,            lpszObjectPlaceHolder},

    {NULL, NULL, 0L, NULL}
};

//$--HrSetGatewayProfileParameters----------------------------------------------
//  Add values to MAPISVC.INF to create profile for the specified gateway.
// -----------------------------------------------------------------------------
static HRESULT HrSetGatewayProfileParameters(   // RETURNS: return code
    IN LPSTR lpszServerName,                   // server name
    IN LPSTR lpszSiteDN,                       // site distinguished name
    IN LPSTR lpszCommonName)                   // common name
{
    HRESULT hr                           = E_FAIL;
    BOOL    fRet                         = FALSE;
    UINT    uiRet                        = 0;
    LPSTR  lpszDN                       = NULL;
    CHAR   szSystemDir[MAX_PATH+1]      = {0};
    CHAR   szServicesIni[MAX_PATH+12]   = {0}; // 12 = space for "MAPISVC.INF"
    UINT    n                            = 0;
    CHAR   szPropNum[10]                = {0};
    LONG    cBytes                       = 0;
    LONG    lRet                         = 0;
    CHAR   szProfileVersion[MAX_PATH+1] = {0};
    WORD    wver[2]                      = {0};
    BYTE    bver[4]                      = {0};

    DEBUGPRIVATE("HrSetGatewayProfileParameters()\n");

    hr = CHK_HrSetGatewayProfileParameters(
        lpszServerName,
        lpszSiteDN,
        lpszCommonName);
    if(FAILED(hr))
        RETURN(hr);

    //
    // Compute profile version
    //

    wver[0] = LOWORD(PROFILE_VERSION);
    wver[1] = HIWORD(PROFILE_VERSION);

    bver[0] = LOBYTE(wver[0]);
    bver[1] = HIBYTE(wver[0]);
    bver[2] = LOBYTE(wver[1]);
    bver[3] = HIBYTE(wver[1]);

    cBytes = _snprintf(
        szProfileVersion,
        MAX_PATH+1,
        "%02x%02x%02x%02x",
        bver[0], bver[1], bver[2], bver[3]);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Compute the DN for use later.
    //

    cBytes =
        lstrlen(lpszSiteDN) +
        lstrlen(CONTAINER_GW) +
        4 +
        lstrlen(lpszCommonName)
        + 1;

    cBytes *= sizeof(CHAR);

    lpszDN = (LPSTR)GlobalAlloc(GMEM_FIXED, cBytes);

    if(lpszDN == NULL)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    lRet = _snprintf(
        lpszDN,
        cBytes,
        "%s%s/cn=%s",
        lpszSiteDN,
        CONTAINER_GW,
        lpszCommonName);

    if(lRet < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Compute the path to MAPISVC.INF
    //

    uiRet = GetSystemDirectory(szSystemDir, MAX_PATH);

    if(uiRet == 0)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    sprintf(szServicesIni, "%s\\%s", szSystemDir, "MAPISVC.INF");

    //
    //  Loop through and add items to MAPISVC.INF
    //

    n = 0;

    while(aGwServicesIni[n].lpszSection != NULL)
    {
        LPSTR lpszProp  = aGwServicesIni[n].lpszKey;
        LPSTR lpszValue = aGwServicesIni[n].lpszValue;

        // Switch the property if necessary

        if(lpszProp == NULL)
        {
            sprintf(szPropNum, "%lx", aGwServicesIni[n].ulKey);
            lpszProp = szPropNum;
        }

        // Switch the value if necessary

        if(lpszValue == lpszServerPlaceHolder)
        {
            lpszValue =  lpszServerName;
        }
        else if(lpszValue == lpszObjectPlaceHolder)
        {
            lpszValue = lpszDN;
        }
        else if(lpszValue == lpszDisplayNamePlaceHolder)
        {
            lpszValue = lpszCommonName;
        }
        else if(lpszValue == lpszProfileVersionPlaceHolder)
        {
            lpszValue = szProfileVersion;
        }

        //
        //  Write the item to MAPISVC.INF
        //

        fRet = WritePrivateProfileString(
            aGwServicesIni[n].lpszSection,
            lpszProp,
            lpszValue,
            szServicesIni);

        if(fRet == FALSE)
        {
            hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
            goto cleanup;
        }

        n++;
    }

    // Flush the information

    WritePrivateProfileString(NULL, NULL, NULL, szServicesIni);

    hr = NOERROR;

cleanup:

    GLOBALFREE(lpszDN);

    RETURN(hr);
}

//$--HrSetMailboxProfileParameters----------------------------------------------
//  Add values to MAPISVC.INF to create profile for the specified mailbox.
// -----------------------------------------------------------------------------
static HRESULT HrSetMailboxProfileParameters(   // RETURNS: return code
    IN LPSTR lpszServerName,                   // server name
    IN LPSTR lpszSiteDN,                       // site distinguished name
    IN LPSTR lpszCommonName)                   // common name
{
    HRESULT hr                           = E_FAIL;
    BOOL    fRet                         = FALSE;
    UINT    uiRet                        = 0;
    LPSTR  lpszDN                       = NULL;
    CHAR   szSystemDir[MAX_PATH+1]      = {0};
    CHAR   szServicesIni[MAX_PATH+12]   = {0}; // 12 = space for "MAPISVC.INF"
    UINT    n                            = 0;
    CHAR   szPropNum[10]                = {0};
    LONG    cBytes                       = 0;
    LONG    lRet                         = 0;
    CHAR   szProfileVersion[MAX_PATH+1] = {0};
    WORD    wver[2]                      = {0};
    BYTE    bver[4]                      = {0};

    DEBUGPRIVATE("HrSetMailboxProfileParameters()\n");

    hr = CHK_HrSetMailboxProfileParameters(
        lpszServerName,
        lpszSiteDN,
        lpszCommonName);
    if(FAILED(hr))
        RETURN(hr);

    //
    // Compute profile version
    //

    wver[0] = LOWORD(PROFILE_VERSION);
    wver[1] = HIWORD(PROFILE_VERSION);

    bver[0] = LOBYTE(wver[0]);
    bver[1] = HIBYTE(wver[0]);
    bver[2] = LOBYTE(wver[1]);
    bver[3] = HIBYTE(wver[1]);

    cBytes = _snprintf(
        szProfileVersion,
        MAX_PATH+1,
        "%02x%02x%02x%02x",
        bver[0], bver[1], bver[2], bver[3]);

    if(cBytes < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Compute the DN for use later.
    //

    cBytes =
        lstrlen(lpszSiteDN) +
        lstrlen(lpszCommonName)
        + 1;

    cBytes *= sizeof(CHAR);

    lpszDN = (LPSTR)GlobalAlloc(GMEM_FIXED, cBytes);

    if(lpszDN == NULL)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    lRet = _snprintf(
        lpszDN,
        cBytes,
        "%s%s",
        lpszSiteDN,
        lpszCommonName);

    if(lRet < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Compute the path to MAPISVC.INF
    //

    uiRet = GetSystemDirectory(szSystemDir, MAX_PATH);

    if(uiRet == 0)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    sprintf(szServicesIni, "%s\\%s", szSystemDir, "MAPISVC.INF");

    //
    //  Loop through and add items to MAPISVC.INF
    //

    n = 0;

    while(aMbServicesIni[n].lpszSection != NULL)
    {
        LPSTR lpszProp  = aMbServicesIni[n].lpszKey;
        LPSTR lpszValue = aMbServicesIni[n].lpszValue;

        // Switch the property if necessary

        if(lpszProp == NULL)
        {
            sprintf(szPropNum, "%lx", aMbServicesIni[n].ulKey);
            lpszProp = szPropNum;
        }

        // Switch the value if necessary

        if(lpszValue == lpszServerPlaceHolder)
        {
            lpszValue =  lpszServerName;
        }
        else if(lpszValue == lpszObjectPlaceHolder)
        {
            lpszValue = lpszDN;
        }
        else if(lpszValue == lpszDisplayNamePlaceHolder)
        {
            lpszValue = lpszCommonName;
        }
        else if(lpszValue == lpszProfileVersionPlaceHolder)
        {
            lpszValue = szProfileVersion;
        }

        //
        //  Write the item to MAPISVC.INF
        //

        fRet = WritePrivateProfileString(
            aMbServicesIni[n].lpszSection,
            lpszProp,
            lpszValue,
            szServicesIni);

        if(fRet == FALSE)
        {
            hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
            goto cleanup;
        }

        n++;
    }

    // Flush the information

    WritePrivateProfileString(NULL, NULL, NULL, szServicesIni);

    hr = NOERROR;

cleanup:

    GLOBALFREE(lpszDN);

    RETURN(hr);
}

//$--HrCreateGatewayProfile--------------------------------------------------
//  Creates a gateway profile.
// -----------------------------------------------------------------------------
HRESULT HrCreateGatewayProfile(          // RETURNS: return code
IN LPSTR lpszServiceName,              // service name
IN LPSTR lpszProfileName)              // profile name
{
    HRESULT        hr             = E_FAIL;
    LPPROFADMIN    lpProfAdmin    = NULL;
    LPSERVICEADMIN lpServiceAdmin = NULL;

    BOOL    fRet                         = FALSE;
    UINT    uRet                         = 0;
    CHAR   szSystemDir[MAX_PATH+1]      = {0};
    CHAR   szServicesIni[MAX_PATH+12+1] = {0}; // 12 = space for "MAPISVC.INF"

LONG    lRet                         = 0;
LPSTR  lpszServer                   = NULL;
LPSTR  lpszSiteDN                   = NULL;
LPSTR  lpszObjectGuid               = NULL;
    LPSTR  lpszCommonName               = NULL;
DWORD   dwType                       = 0;
DWORD   cbOrig                       = 0;
CHAR    szParametersKey[MAX_PATH+1]  = {0};
HKEY    hkParameters                 = INVALID_HANDLE_VALUE;

    DEBUGPUBLIC("HrCreateGatewayProfile()\n");

    hr = CHK_HrCreateGatewayProfile(
    lpszServiceName,
    lpszProfileName);
    if(FAILED(hr))
        RETURN(hr);

    //
    //  Open the parameters key.
    //

lRet = _snprintf(
    szParametersKey,
        MAX_PATH+1,
    "SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
    lpszServiceName);

    if(lRet < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lRet = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        szParametersKey,
        0,
        KEY_READ,
        &hkParameters);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
        goto cleanup;
    }

    //
    //  Get the profile parameters
    //

    hr = HrGetRegistryValue(
        hkParameters, "HomeDSA", &dwType, &cbOrig, (LPVOID) &lpszServer);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "SiteDN", &dwType, &cbOrig, (LPVOID) &lpszSiteDN);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "ObjectGUID", &dwType, &cbOrig, (LPVOID) &lpszObjectGuid);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if((lpszObjectGuid == NULL) ||
       (lstrcmpi(lpszObjectGuid, OBJECT_GUID_MAIL_GATEWAY)))
    {
        //
        // The object is the directory is not a gateway.
        //

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "CommonName", &dwType, &cbOrig, (LPVOID) &lpszCommonName);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Connect to Profile Admin interface.
    //

    hr = MAPIAdminProfiles(0, &lpProfAdmin);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR(lpProfAdmin != NULL, "NULL lpPropAdmin variable");

    //
    //  Create a profile for the gateway.
    //

    hr = MAPICALL(lpProfAdmin)->CreateProfile(
        lpProfAdmin,
        lpszProfileName,
        NULL,
        0,
        fMapiUnicode);

    if(FAILED(hr))
    {
        // Did it fail because a profile of this name already exists?

        hr = HrMAPIProfileExists(lpProfAdmin, lpszProfileName);

        if(hr != E_ACCESSDENIED)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            hr = HR_LOG(hr);
        }

        goto cleanup;
    }

    //
    // Fix MAPISVC.INF to contain the information we want to add to the profile
    //

    hr = HrSetGatewayProfileParameters(
        lpszServer,
        lpszSiteDN,
        lpszCommonName);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Add profile section from MAPISVC.INF
    //

    hr = MAPICALL(lpProfAdmin)->AdminServices(
        lpProfAdmin,
        lpszProfileName,
        "",
        0,
        0,
        &lpServiceAdmin);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR(lpServiceAdmin != NULL, "NULL lpServiceAdmin variable");

    hr = MAPICALL(lpServiceAdmin)->CreateMsgService(
        lpServiceAdmin,
        "MSEMS_EdkGateway",
        "Exchange Access for EDK Gateways",
        0,
        0);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Compute the path to MAPISVC.INF
    //

    uRet = GetSystemDirectory(szSystemDir, MAX_PATH);

    if(uRet == 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    sprintf(szServicesIni, "%s\\%s", szSystemDir, "MAPISVC.INF");

    //
    //  Remove service entry from MAPISVC.INF
    //

    fRet = WritePrivateProfileString(
        TEXT("Services"),
        TEXT("MSEMS_EdkGateway"),
        NULL,
        szServicesIni);

    if(fRet == FALSE)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    hr = NOERROR;

cleanup:

GLOBALFREE(lpszServer);
GLOBALFREE(lpszSiteDN);
GLOBALFREE(lpszObjectGuid);
    GLOBALFREE(lpszCommonName);

FREEHKEY(hkParameters);

    ULRELEASE(lpServiceAdmin);
    ULRELEASE(lpProfAdmin);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrCreateMailboxAgentProfile---------------------------------------------
//  Creates a mailbox agent profile.
// -----------------------------------------------------------------------------
HRESULT HrCreateMailboxAgentProfile(     // RETURNS: return code
IN LPSTR lpszServiceName,              // service name
IN LPSTR lpszProfileName)              // profile name
{
    HRESULT        hr             = E_FAIL;
    LPPROFADMIN    lpProfAdmin    = NULL;
    LPSERVICEADMIN lpServiceAdmin = NULL;

    BOOL    fRet                         = FALSE;
    UINT    uRet                         = 0;
    CHAR   szSystemDir[MAX_PATH+1]      = {0};
    CHAR   szServicesIni[MAX_PATH+12+1] = {0}; // 12 = space for "MAPISVC.INF"

LONG    lRet                         = 0;
LPSTR  lpszServer                   = NULL;
LPSTR  lpszSiteDN                   = NULL;
LPSTR  lpszObjectGuid               = NULL;
    LPSTR  lpszCommonName               = NULL;
DWORD   dwType                       = 0;
DWORD   cbOrig                       = 0;
CHAR    szParametersKey[MAX_PATH]    = {0};
HKEY    hkParameters                 = INVALID_HANDLE_VALUE;

    DEBUGPUBLIC("HrCreateMailboxAgentProfile()\n");

    hr = CHK_HrCreateMailboxAgentProfile(
    lpszServiceName,
    lpszProfileName);
    if(FAILED(hr))
        RETURN(hr);

    //
    //  Open the parameters key.
    //

lRet = _snprintf(
    szParametersKey,
        MAX_PATH+1,
    "SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
    lpszServiceName);

    if(lRet < 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lRet = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        szParametersKey,
        0,
        KEY_READ,
        &hkParameters);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(lRet));
        goto cleanup;
    }

    //
    //  Get the profile parameters
    //

    hr = HrGetRegistryValue(
        hkParameters, "HomeDSA", &dwType, &cbOrig, (LPVOID) &lpszServer);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "SiteDN", &dwType, &cbOrig, (LPVOID) &lpszSiteDN);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "ObjectGUID", &dwType, &cbOrig, (LPVOID) &lpszObjectGuid);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if((lpszObjectGuid == NULL) ||
       (lstrcmpi(lpszObjectGuid, OBJECT_GUID_MAILBOX_AGENT)))
    {
        //
        // The object is the directory is not a mailbox agent.
        //

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGetRegistryValue(
        hkParameters, "CommonName", &dwType, &cbOrig, (LPVOID) &lpszCommonName);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        if(dwType != REG_SZ)
            hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Connect to Profile Admin interface.
    //

    hr = MAPIAdminProfiles(0, &lpProfAdmin);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR(lpProfAdmin != NULL, "NULL lpPropAdmin variable");

    //
    //  Create a profile for the gateway.
    //

    hr = MAPICALL(lpProfAdmin)->CreateProfile(
        lpProfAdmin,
        lpszProfileName,
        NULL,
        0,
        fMapiUnicode);

    if(FAILED(hr))
    {
        // Did it fail because a profile of this name already exists?

        hr = HrMAPIProfileExists(lpProfAdmin, lpszProfileName);

        if(hr != E_ACCESSDENIED)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            hr = HR_LOG(hr);
        }

        goto cleanup;
    }

    //
    // Fix MAPISVC.INF to contain the stuff we want to add to the profile
    //

    hr = HrSetMailboxProfileParameters(
        lpszServer,
        lpszSiteDN,
        lpszCommonName);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Add profile section from MAPISVC.INF
    //

    hr = MAPICALL(lpProfAdmin)->AdminServices(
        lpProfAdmin,
        lpszProfileName,
        "",
        0,
        0,
        &lpServiceAdmin);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR(lpServiceAdmin != NULL, "NULL lpServiceAdmin variable");

    hr = MAPICALL(lpServiceAdmin)->CreateMsgService(
        lpServiceAdmin,
        "MSEMS_EdkMbAgent",
        "Exchange Access for EDK Mailbox Agents",
        0,
        0);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Compute the path to MAPISVC.INF
    //

    uRet = GetSystemDirectory(szSystemDir, MAX_PATH);

    if(uRet == 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    sprintf(szServicesIni, "%s\\%s", szSystemDir, "MAPISVC.INF");

    //
    //  Remove service entry from MAPISVC.INF
    //

    fRet = WritePrivateProfileString(
        TEXT("Services"),
        TEXT("MSEMS_EdkMbAgent"),
        NULL,
        szServicesIni);

    if(fRet == FALSE)
    {
        hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
        goto cleanup;
    }

    hr = NOERROR;

cleanup:

GLOBALFREE(lpszServer);
GLOBALFREE(lpszSiteDN);
GLOBALFREE(lpszObjectGuid);
    GLOBALFREE(lpszCommonName);

FREEHKEY(hkParameters);

    ULRELEASE(lpServiceAdmin);
    ULRELEASE(lpProfAdmin);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrRemoveProfile---------------------------------------------------------
//  Removes a profile.
//------------------------------------------------------------------------------
HRESULT HrRemoveProfile(                 // RETURNS: return code
    LPSTR lpszProfileName)                 // profile name
{
    HRESULT     hr          = NOERROR;
    LPPROFADMIN lpProfAdmin = NULL;

    DEBUGPUBLIC("HrRemoveProfile()\n");

    hr = CHK_HrRemoveProfile(
        lpszProfileName);
    if(FAILED(hr))
        RETURN(hr);

    //
    // Connect to Profile Admin interface.
    //

    hr = MAPIAdminProfiles(0, &lpProfAdmin);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    ASSERTERROR(lpProfAdmin != NULL, "NULL lpPropAdmin variable");

    //
    //  Delete a MAPI profile
    //

    hr = MAPICALL(lpProfAdmin)->DeleteProfile(lpProfAdmin, lpszProfileName, 0);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    ULRELEASE(lpProfAdmin);

    RegFlushKey(HKEY_LOCAL_MACHINE);
    RegFlushKey(HKEY_CURRENT_USER);

    RETURN(hr);
}

//$--HrMAPIProfileExists---------------------------------------------------------
//  Checks for an existing profile.
// -----------------------------------------------------------------------------
HRESULT HrMAPIProfileExists(             // RETURNS: return code
    IN LPPROFADMIN lpProfAdmin,         // profile admin object
    IN LPSTR      lpszProfileName)     // profile name
{
    HRESULT      hr      = NOERROR;
    HRESULT      hrT     = 0;
    LPMAPITABLE  lpTable = NULL;
    LPSRowSet    lpRows  = NULL;
    LPSPropValue lpProp  = NULL;
    ULONG        i       = 0;
    ULONG        cRows   = 0;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            PR_DISPLAY_NAME_A
        }
    };

    DEBUGPUBLIC("HrMAPIProfileExists()\n");

    hr = CHK_HrMAPIProfileExists(
        lpProfAdmin,
        lpszProfileName);
    if(FAILED(hr))
        RETURN(hr);

    // Get a table of existing profiles

    hr = MAPICALL(lpProfAdmin)->GetProfileTable(
        lpProfAdmin,
        0,
        &lpTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

hr = HrQueryAllRows(
    lpTable,
(LPSPropTagArray)&rgPropTag,
NULL,
NULL,
0,
&lpRows);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    cRows = lpRows->cRows;

    // If table is empty then profile doesn't exist

    if (cRows == 0)
    {
        hr = NOERROR;
        goto cleanup;
    }

    // Search rows for the folder in question

    for(i = 0; i < cRows; i++)
    {
        ASSERTERROR(
            lpRows->aRow[i].cValues > 0,
            "Profile row is empty of properties.");

        lpProp = lpRows->aRow[i].lpProps;

        ASSERTERROR(
            lpProp[0].ulPropTag == PR_DISPLAY_NAME_A,
            "Profile row does not contain display name");

        if(lstrcmpiA(lpProp[0].Value.lpszA, lpszProfileName) == 0)
        {
            hr = E_ACCESSDENIED;
            goto cleanup;
        }
    }

cleanup:

    FREEPROWS(lpRows);

    ULRELEASE(lpTable);

    RETURN(hr);
}


GWMAIN.C

// --gwmain.c-------------------------------------------------------------------
//
//  Logon/logoff a gateway.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define USES_IID_IMailUser
#include "edk.h"
#include "gwmain.h"

#define NOTIFY_TIMEOUT  60000

static ULONG         ulNewMailAdvise = 0xFFFFFFFFU;

static HANDLE        hNewMailEvent   = NULL;  // new mail event
static LPMAPISESSION lpSession       = NULL;  // session pointer
static LPMDB         lpStore         = NULL;  // message store pointer
static LPMAPIFOLDER  lpRootFolder    = NULL;  // root folder pointer
static LPMAPIFOLDER  lpMtsInFolder   = NULL;  // MTS-IN folder pointer
static LPMAPIFOLDER  lpMtsOutFolder  = NULL;  // MTS-OUT folder pointer
static LPADRBOOK     lpAdrBook       = NULL;  // pointer to address book
static ULONG         cbGalEid        = 0;     // count of bytes in GAL entry ID
static LPENTRYID     lpGalEid        = NULL;  // pointer to GAL entry ID
static LPABCONT      lpGalABCont     = NULL;  // pointer to GAL object

static BOOL          IsInitMAPI      = FALSE; // MAPI Initialized
static BOOL          IsMAPILogon     = FALSE; // MAPI Logon

CRITICAL_SECTION     csNewMailList   = {0};
CRITICAL_SECTION     csNewFileList   = {0};

static CHAR         szServerName[MAX_COMPUTERNAME_LENGTH+1] = {0};

HANDLE               hNewMailThread  = NULL;  // new mail thread
HANDLE               hNewFileThread  = NULL;  // new file thread

DWORD                dwNewMailTimeout = NOTIFY_TIMEOUT;
DWORD                dwNewFileTimeout = NOTIFY_TIMEOUT;

//$--GetGWExchangeServerName------------------------------------------------------
//  Get Exchange server name.
// -----------------------------------------------------------------------------
LPSTR GetGWExchangeServerName(void)
{
    DEBUGPUBLIC("GetGWExchangeServerName()\n");

    return(szServerName);
}

//$--GetGWSession-------------------------------------------------------------
//  Get MAPI session pointer.
// -----------------------------------------------------------------------------
LPMAPISESSION GetGWSession(void)
{
    DEBUGPUBLIC("GetGWSession()\n");

    return(lpSession);
}

//$--GetGWDefaultStore------------------------------------------------------------
//  Get default store pointer.
// -----------------------------------------------------------------------------
LPMDB GetGWDefaultStore(void)
{
    DEBUGPUBLIC("GetGWDefaultStore()\n");

    return(lpStore);
}

//$--GetGWRootFolder-------------------------------------------------------------
//  Get root folder pointer.
// -----------------------------------------------------------------------------
LPMAPIFOLDER GetGWRootFolder(void)
{
    DEBUGPUBLIC("GetGWRootFolder()\n");

    return(lpRootFolder);
}

//$--GetGWMtsInFolder-------------------------------------------------------------
//  Get MTS-IN folder pointer.
// -----------------------------------------------------------------------------
LPMAPIFOLDER GetGWMtsInFolder(void)
{
    DEBUGPUBLIC("GetGWMtsInFolder()\n");

    return(lpMtsInFolder);
}

//$--GetGWMtsOutFolder------------------------------------------------------------
//  Get MTS-OUT folder pointer.
// -----------------------------------------------------------------------------
LPMAPIFOLDER GetGWMtsOutFolder(void)
{
    DEBUGPUBLIC("GetGWMtsOutFolder()\n");

    return(lpMtsOutFolder);
}

//$--GetGWGALEntryIdSize--------------------------------------------------------------
//  Get count of bytes in GAL entry ID.
// -----------------------------------------------------------------------------
ULONG GetGWGALEntryIdSize(void)
{
    DEBUGPUBLIC("GetGWGALEntryIdSize()\n");

    return(cbGalEid);
}

//$--GetGWGALEntryId------------------------------------------------------------------
//  Get GAL entry ID pointer.
// -----------------------------------------------------------------------------
LPENTRYID GetGWGALEntryId(void)
{
    DEBUGPUBLIC("GetGWGALEntryId()\n");

    return(lpGalEid);
}

//$--GetAdrBookPtr--------------------------------------------------------------
//  Get address book pointer pointer.
// -----------------------------------------------------------------------------
LPADRBOOK GetAdrBookPtr(void)
{
    DEBUGPUBLIC("GetAdrBookPtr()\n");

    return(lpAdrBook);
}

//$--GetGWGAL------------------------------------------------------------------
//  Get GAL pointer.
// -----------------------------------------------------------------------------
LPABCONT GetGWGAL(void)
{
    DEBUGPUBLIC("GetGWGAL()\n");

    return(lpGalABCont);
}

//$--GetGWNewMailEvent------------------------------------------------------------
//  Get new mail event.
// -----------------------------------------------------------------------------
HANDLE GetGWNewMailEvent(void)
{
    DEBUGPUBLIC("GetGWNewMailEvent()\n");

    return(hNewMailEvent);
}

//$--GetGWNewMailStatus-----------------------------------------------------------
//  Get new mail status.
// -----------------------------------------------------------------------------
BOOL GetGWNewMailStatus(void)
{
    BOOL  IsNewMail = TRUE;
    DWORD dw        = 0;

    DEBUGPUBLIC("GetGWNewMailStatus()\n");

    if(hNewMailEvent == NULL)
    {
        IsNewMail = FALSE;
        goto cleanup;
    }

    dw = WaitForSingleObject(hNewMailEvent, 0);

    if((dw != WAIT_OBJECT_0) && (dw != WAIT_ABANDONED))
    {
        HRESULT hr = HR_LOG(E_FAIL);

        IsNewMail = FALSE;
    }

cleanup:

    return(IsNewMail);
}

//$--ScNewMailHandler-----------------------------------------------------------
//  Handle the arrival of new messages.
// -----------------------------------------------------------------------------
static SCODE STDAPICALLTYPE ScNewMailHandler(       // RETURNS: status code
    IN LPVOID lpvContext,                           // pointer to context
    IN ULONG cNotification,                         // count of notifications
    IN LPNOTIFICATION lpNotifications)              // pointer to notifications
{
    DEBUGPRIVATE("ScNewMailHandler()\n");

    if((hNewMailEvent != NULL) && (!SetEvent(hNewMailEvent)))
    {
        HRESULT hr = HR_LOG(E_FAIL);
    }

    return(0);
}

//$--HrGWWaitForStop----------------------------------------------------
//  Wait for the gateway to stop.
// -----------------------------------------------------------------------------
HRESULT HrGWWaitForStop(        // RETURNS: return code
    void)                               // no arguments
{
    HRESULT hr          = NOERROR;
    HANDLE  hObjects[2] = {0};
    DWORD   dwT         = WAIT_FAILED;
    ULONG   i           = 0;

    DEBUGPUBLIC("HrGWWaitForStop()\n");

    if(hNewMailThread != NULL)
    {
        hObjects[i++] = hNewMailThread;
    }

    if(hNewFileThread != NULL)
    {
        hObjects[i++] = hNewFileThread;
    }

    if(i > 0)
    {
        dwT = WaitForMultipleObjects(i, hObjects, TRUE, INFINITE);

        if(dwT == WAIT_FAILED)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    RETURN(hr);
}

//$--HrGetArbValue--------------------------------------------------------------
//  Get an arbitrary value - allocating memory to hold it.
// -----------------------------------------------------------------------------
static HRESULT HrGetArbValue(       // RETURNS: return code
    IN HKEY hk,                     // the key.
    IN LPSTR pszValue,              // value name in key.
    OUT DWORD * pType,              // where to put type info.
    OUT DWORD * pcb,                // where to put byte count info.
    OUT LPVOID * ppData)            // where to put the data.
{
    HRESULT hr   = E_FAIL;
    LONG    lRet = 0;

    DEBUGPRIVATE("HrGetArbValue()\n");

    *ppData = NULL;

    //
    //  Get its size.
    //
    lRet = RegQueryValueEx(
        hk,
        pszValue,
        NULL,
        pType,
        NULL,
        pcb);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Allocate memory for it.
    //

    *ppData = GlobalAlloc(GPTR, *pcb);

    if(*ppData == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // get the current value
    //
    lRet = RegQueryValueEx(hk, pszValue, NULL, pType, *ppData, pcb);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = NOERROR;

cleanup:

    if(FAILED(hr))
    {
        if(ppData != NULL)
        {
            GLOBALFREE(*ppData);
        }
    }

    RETURN(hr);
}

//$--HrCheckExchangeRunning-----------------------------------------------------
//  Check if the Exchange server is running.
// -----------------------------------------------------------------------------
static HRESULT HrCheckExchangeRunning(      // RETURNS: return code
    IN LPSTR lpszServiceName)
{
    HRESULT hr                          = NOERROR;
    LONG    lRet                        = 0;
    DWORD   dwService                   = 0;
    DWORD   dwCurrentState              = 0;
DWORD   dwType                      = 0;
DWORD   cbOrig                      = 0;
    LPSTR  lpszServerName              = NULL;
HKEY    hkParameters                = INVALID_HANDLE_VALUE;
CHAR   szParametersKey[MAX_PATH+1] = {0};

    //
    //  Open the parameters key.
    //

sprintf(
    szParametersKey,
    "SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters",
    lpszServiceName);

    lRet = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        szParametersKey,
        0,
        KEY_READ,
        &hkParameters);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    //  Get the Exchange server name.
    //

    hr = HrGetArbValue(
        hkParameters, "HomeDSA", &dwType, &cbOrig, (LPVOID) &lpszServerName);

    if(FAILED(hr) || dwType != REG_SZ)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Copy Exchange server name.
    //

    lstrcpyn(szServerName, lpszServerName, MAX_COMPUTERNAME_LENGTH);
    szServerName[MAX_COMPUTERNAME_LENGTH] = 0;

// TODO: remove "#ifndef" and "#endif"
#ifndef DEBUG
    hr = HrGetExchangeStatus(
        lpszServerName,
        &dwService,
        &dwCurrentState);

    if(FAILED(hr))
    {
MODULE_ERROR1(
    "The Exchange server on %s is not running!",
    lpszServerName);

    hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
#endif

cleanup:

    GLOBALFREE(lpszServerName);

    if(hkParameters != INVALID_HANDLE_VALUE)
    {
        HR_LOG(HRESULT_FROM_WIN32(RegCloseKey(hkParameters)));
    }

    RETURN(hr);
}

//$--HrInternalGatewayLogon-----------------------------------------------------
//  Logon to the gateway.
// -----------------------------------------------------------------------------
static HRESULT HrInternalGatewayLogon(void)  // RETURNS: return code
{
    HRESULT         hr               = NOERROR;

    ULONG           cValues          = 0;
    LPSPropValue    lpProp           = NULL;

    ULONG           ulObjType        = 0;

    ULONG           cbStoreEid       = 0;
    LPENTRYID       lpStoreEid       = NULL;

    SPropTagArray   rgMtsInEntryId   = { 1, { PR_GW_MTSIN_ENTRYID } };
    ULONG           cbMtsInEid       = 0;
    LPENTRYID       lpMtsInEid       = NULL;

    SPropTagArray   rgMtsOutEntryId  = { 1, { PR_GW_MTSOUT_ENTRYID } };
    ULONG           cbMtsOutEid      = 0;
    LPENTRYID       lpMtsOutEid      = NULL;

    CHAR           szGatewayName[MAX_SERVICE_NAME_LENGTH+1] = {0};

MAPIINIT_0MapiInit         = { 0 };

    ULONG           ulFlags          = 0;

    LPMAPIADVISESINK lpAdvise        = NULL;

    CHAR           szProfileName[MAX_PATH+1] = {0};

    BOOL            IsProfileCreated = FALSE;

    DEBUGPRIVATE("HrInternalGatewayLogon()\n");

    //
    // Initialize critical section for new mail list.
    //

    InitializeCriticalSection(&csNewMailList);

    //
    // Initialize critical section for new file list.
    //

    InitializeCriticalSection(&csNewFileList);

    //
    // Get gateway service name.
    //

    hr = HrServiceGetName(
        szGatewayName);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Check if Exchange is running.
    //

    hr = HrCheckExchangeRunning(
        szGatewayName);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the event object. The new mail event handler function signals
    // this event when it receives a new mail notification.

    hNewMailEvent = CreateEvent(
        NULL,    // no security attributes
        FALSE,   // auto-reset event
        FALSE,   // not-signalled
        NULL);   // no name

    if(hNewMailEvent == (HANDLE)NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    MapiInit.ulVersion = MAPI_INIT_VERSION;

    ulFlags = MAPI_NEW_SESSION|MAPI_EXTENDED|MAPI_NO_MAIL;

  if(FIsService())
{// Services need special initialization.
  MapiInit.ulFlags = MAPI_NT_SERVICE;
ulFlags |= MAPI_NT_SERVICE;
}

    hr = MAPIInitialize(&MapiInit);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    IsInitMAPI = TRUE;

    hr = HrCreateProfileName(
        szGatewayName,
        MAX_PATH+1,
        szProfileName);

    if(FAILED(hr))
    {
        goto cleanup;
    }

hr = HrCreateGatewayProfile(szGatewayName, szProfileName);

if(hr == E_ACCESSDENIED)
{
MODULE_WARNING(
"Could not create profile since previous profile with same name "
"is still logged on.");
        hr = NOERROR;
}
    else if(FAILED(hr))
    {
        goto cleanup;
    }
    else
    {
        IsProfileCreated = TRUE;
    }

    // Create a MAPI session
    hr = MAPILogonEx(
        0,
        szProfileName,
        NULL,
        ulFlags,
        &lpSession);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    IsMAPILogon = TRUE;

#define PSESSION ((LPMAPISESSION)lpSession)

    // Get entry ID of message store
    hr = HrMAPIFindDefaultMsgStore(lpSession, &cbStoreEid, &lpStoreEid);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = MAPICALL(PSESSION)->OpenMsgStore(
        PSESSION,
        (ULONG)0,
        cbStoreEid,
        lpStoreEid,
        NULL,
        MAPI_DEFERRED_ERRORS | MDB_NO_DIALOG | MDB_WRITE,
        &lpStore);

#undef PSESSION

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Get the root folder in the default message store
    hr = MAPICALL(lpStore)->OpenEntry(
        lpStore,
        (ULONG)0,
        NULL,
        NULL,
        MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpRootFolder);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Get the MTS-IN entry ID property.
    hr = MAPICALL(lpStore)->GetProps(
        lpStore,
        &rgMtsInEntryId,
        fMapiUnicode,
        &cValues,
        &lpProp);

    if(FAILED(hr))
    {
        lpProp = NULL;

        goto cleanup;
    }

    // Check to make sure we got the right property.
    if (lpProp->ulPropTag != PR_GW_MTSIN_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbMtsInEid = lpProp->Value.bin.cb;

    hr = MAPIAllocateBuffer(cbMtsInEid, (void **)&lpMtsInEid);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Copy MTS-IN Entry ID
    CopyMemory(lpMtsInEid,lpProp->Value.bin.lpb,cbMtsInEid);

    MAPIFREEBUFFER(lpProp);

    // Get the MTS-OUT entry ID property.
    hr = MAPICALL(lpStore)->GetProps(
        lpStore,
        &rgMtsOutEntryId,
        fMapiUnicode,
        &cValues,
        &lpProp);

    if(FAILED(hr))
    {
        lpProp = NULL;

        goto cleanup;
    }

    // Check to make sure we got the right property.
    if(lpProp->ulPropTag != PR_GW_MTSOUT_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbMtsOutEid = lpProp->Value.bin.cb;

    hr = MAPIAllocateBuffer(cbMtsOutEid, (void **)&lpMtsOutEid);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Copy MTS-OUT Entry ID
    CopyMemory(lpMtsOutEid,lpProp->Value.bin.lpb,cbMtsOutEid);

    MAPIFREEBUFFER(lpProp);

    // Open the MTS-IN folder in the root folder
    hr = MAPICALL(lpRootFolder)->OpenEntry(
        lpRootFolder,
        cbMtsInEid,
        lpMtsInEid,
        NULL,
        MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpMtsInFolder);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    if(ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open the MTS-OUT folder in the root folder
    hr = MAPICALL(lpRootFolder)->OpenEntry(
        lpRootFolder,
        cbMtsOutEid,
        lpMtsOutEid,
        NULL,
        MAPI_DEFERRED_ERRORS|MAPI_MODIFY,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpMtsOutFolder);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    if(ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open the Address Book
    hr = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = HrFindExchangeGlobalAddressList(
        lpAdrBook,
        &cbGalEid,
        &lpGalEid);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrAllocAdviseSink(ScNewMailHandler, NULL, &lpAdvise);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = MAPICALL(lpStore)->Advise(
        lpStore,
        cbMtsOutEid,
        lpMtsOutEid,
        fnevNewMail,
        lpAdvise,
        &ulNewMailAdvise);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Open the global recipient container
    hr = MAPICALL(lpAdrBook)->OpenEntry(
        lpAdrBook,
        cbGalEid,
        lpGalEid,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *)&lpGalABCont);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    if(ulObjType != MAPI_ABCONT)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(IsProfileCreated == TRUE)
    {
        // Mark profile for deletion
        HR_LOG(HrRemoveProfile(szProfileName));
    };

    ULRELEASE(lpAdvise);

    MAPIFREEBUFFER(lpProp);

    if(FAILED(hr))
    {
        if((ulNewMailAdvise != 0xFFFFFFFFL) && (lpStore != NULL))
        {
            MAPICALL(lpStore)->Unadvise(lpStore, ulNewMailAdvise);
        }

        if(hNewMailEvent != NULL)
        {
            if(!CloseHandle(hNewMailEvent))
            {
                hr = HR_LOG(E_FAIL);
            }
        }

        ULRELEASE(lpGalABCont);

        ULRELEASE(lpAdrBook);

        ULRELEASE(lpMtsInFolder);

        ULRELEASE(lpMtsOutFolder);

        ULRELEASE(lpRootFolder);

        ULRELEASE(lpStore);

        MAPIFREEBUFFER(lpGalEid);

        if(IsMAPILogon == TRUE)
        {
            // Logoff a MAPI session
            MAPICALL(lpSession)->Logoff(
                lpSession,
                (ULONG)0,
                (ULONG)0,
                (ULONG)0);

            MAPICALL(lpSession)->Release(
                lpSession);

            lpSession = NULL;
        }

        if(IsInitMAPI == TRUE)
        {
            MAPIUninitialize();

            IsInitMAPI = FALSE;
        }

        IsMAPILogon = FALSE;
    }

    MAPIFREEBUFFER(lpMtsInEid);

    MAPIFREEBUFFER(lpMtsOutEid);

    MAPIFREEBUFFER(lpStoreEid);

    RETURN(hr);
}

//$--HrInternalGatewayLogoff----------------------------------------------------
//  Logoff of the gateway.
// -----------------------------------------------------------------------------
static HRESULT HrInternalGatewayLogoff(void) // RETURNS: return code
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrInternalGatewayLogoff()\n");

    if((ulNewMailAdvise != 0xFFFFFFFFL) && (lpStore != NULL))
    {
        MAPICALL(lpStore)->Unadvise(lpStore, ulNewMailAdvise);
    }

    if(hNewMailEvent != NULL)
    {
        if(!CloseHandle(hNewMailEvent))
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    if(hNewMailThread != NULL)
    {
        if(!CloseHandle(hNewMailThread))
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    if(hNewFileThread != NULL)
    {
        if(!CloseHandle(hNewFileThread))
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    ULRELEASE(lpMtsInFolder);

    ULRELEASE(lpMtsOutFolder);

    ULRELEASE(lpRootFolder);

    ULRELEASE(lpStore);

    MAPIFREEBUFFER(lpGalEid);

    ULRELEASE(lpGalABCont);

    ULRELEASE(lpAdrBook);

    if(IsMAPILogon == TRUE)
    {
        // Logoff a MAPI session
        MAPICALL(lpSession)->Logoff(
            lpSession,
            (ULONG)0,
            (ULONG)0,
            (ULONG)0);

        MAPICALL(lpSession)->Release(
            lpSession);

        lpSession = NULL;
    }

    if(IsInitMAPI == TRUE)
    {
        MAPIUninitialize();

        IsInitMAPI = FALSE;
    }

    IsMAPILogon = FALSE;

    DeleteCriticalSection(&csNewMailList);

    DeleteCriticalSection(&csNewFileList);

    RETURN(hr);
}

//$--HrServiceStartup---------------------------------------------------------
//  This function is called at startup to initialize the gateway.
//------------------------------------------------------------------------------
HRESULT HrServiceStartup(         // RETURNS: return code
    IN HINSTANCE    hInstance,      // handle of current instance
    IN HINSTANCE    hPrevInstance,  // handle of previous instance
    IN HWND         hwndMainWindow, // handle to main window
    IN LPSTR        lpszCmdLine)    // pointer to command line
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrServiceStartup()\n");

    hr = HrInternalGatewayLogon();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = HrGWLogon();

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    RETURN(hr);
}

//$--ServiceMain--------------------------------------------------------------
//  This function is given its own thread to execute.
//------------------------------------------------------------------------------
void ServiceMain(                 // RETURNS: nothing
    IN HANDLE hEventShutdown)       // handle to shutdown event object
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("ServiceMain()\n");

    __try
    {
        GWMain();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();

    }

    //
    // Wait for the gateway to stop.
    //

    __try
    {
        hr = HrGWWaitForStop();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);
    }

    if(FAILED(hr))
    {
        hr = HR_LOG(hr);
    }

    //
    // Confirm that the gateway has stopped.
    //

    __try
    {
        hr = HrServiceConfirmStop();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);
    }

    if(FAILED(hr))
    {
        hr = HR_LOG(hr);
    }

    ExitThread(0);
}

//$--HrServiceShutdown--------------------------------------------------------
//  This function is called to shutdown the gateway.
//------------------------------------------------------------------------------
HRESULT HrServiceShutdown(            // RETURNS: exit code
    void)
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrServiceShutdown()\n");

    hr = HrGWLogoff();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = HrInternalGatewayLogoff();

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    RETURN(hr);
}


PROCESS.C

// --process.c------------------------------------------------------------------
//
//  Functions to start a thread to handle processing of new mail or files.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwmain.h"

#define SLEEP_TIME  3000

extern HANDLE hNewMailThread;
extern HANDLE hNewFileThread;

extern DWORD  dwNewMailTimeout;
extern DWORD  dwNewFileTimeout;

extern CRITICAL_SECTION csNewMailList;
extern CRITICAL_SECTION csNewFileList;

static HANDLE hFindData  = INVALID_HANDLE_VALUE; // change notification handle
static HANDLE PollHandle = INVALID_HANDLE_VALUE; // poll handle

//$--_GWThreadContext-----------------------------------------------------------
//  Type for gateway thread context information.
// -----------------------------------------------------------------------------
typedef struct _GWThreadContext
{
    BOOL                   fUseFindData;            // if TRUE use lpFindData

    CHAR                   szAddrType[MAX_PATH+1];  // address type

    DWORD                  dwSubmitInterval;        // submit interval
    DWORD                  dwPollingInterval;       // polling interval
    DWORD                  cMessages;               // #messages/thread
    DWORD                  cThreads;                // #threads

    ULONG                  ulThreadNumber;          // thread#

    CHAR                   szFindPath[MAX_PATH+1];  // path
    CHAR                   szFindMask[MAX_PATH+1];  // find mask
    HANDLE                 *lphFindData;            // change notifcation handle
    ULONG                  cFindData;               // find data count
LPWIN32_FIND_DATA      lpFindData;              // find data
    HANDLE                 *lpPollHandle;           // poll handle

    LPSRowSet              lpRows;                  // rows
    LPMAPITABLE            lpTable;                 // contents table

    LPTHREAD_START_ROUTINE lpStartAddress;          // thread function

} GWTHREADCONTEXT, *LPGWTHREADCONTEXT;

#include "process.chk"

//$--GetGWPath------------------------------------------------------------------
//  Get a gateway path pointer.
// -----------------------------------------------------------------------------
LPSTR GetGWPath(                       // RETURNS: gateway path
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("GetGWPath()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return(NULL);

    if(((LPGWTHREADCONTEXT)lpParameter)->fUseFindData == FALSE)
    {
        return(NULL);
    }

    return(((LPGWTHREADCONTEXT)lpParameter)->szFindPath);
}

//$--GetGWSRowSet-----------------------------------------------------------------
//  Get a SRowSet pointer.
// -----------------------------------------------------------------------------
LPSRowSet GetGWSRowSet(                   // RETURNS: SRowSet
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("GetGWSRowSet()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return(NULL);

    if(((LPGWTHREADCONTEXT)lpParameter)->fUseFindData == TRUE)
    {
        return(NULL);
    }

    return(((LPGWTHREADCONTEXT)lpParameter)->lpRows);
}

//$--GetGWFindData-----------------------------------------------------------
//  Get a WIN32_FIND_DATA pointer.
// -----------------------------------------------------------------------------
LPWIN32_FIND_DATA GetGWFindData(     // RETURNS: WIN32_FIND_DATA
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("GetGWFindData()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return(NULL);

    if(((LPGWTHREADCONTEXT)lpParameter)->fUseFindData == FALSE)
    {
        return(NULL);
    }

    return(((LPGWTHREADCONTEXT)lpParameter)->lpFindData);
}

//$--GetGWFindDataSize-------------------------------------------------------
//  Get a WIN32_FIND_DATA size.
// -----------------------------------------------------------------------------
ULONG GetGWFindDataSize(             // RETURNS: count of WIN32_FIND_DATA
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("GetGWFindDataSize()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return(0);

    if(((LPGWTHREADCONTEXT)lpParameter)->fUseFindData == FALSE)
    {
        return(0);
    }

    return(((LPGWTHREADCONTEXT)lpParameter)->cFindData);
}

//$--GetGWSubmitInterval----------------------------------------------------------
//  Get the submit interval.
// -----------------------------------------------------------------------------
DWORD GetGWSubmitInterval(                // RETURNS: submit interval
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("GetGWSubmitInterval()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return(0);

    return(((LPGWTHREADCONTEXT)lpParameter)->dwSubmitInterval);
}

//$--FreeThreadParameter--------------------------------------------------------
//  Free the thread parameter.
// -----------------------------------------------------------------------------
VOID FreeThreadParameter(               // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("FreeThreadParameter()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return;

    if(((LPGWTHREADCONTEXT)lpParameter)->lpRows != NULL)
    {
        FREEPROWS(((LPGWTHREADCONTEXT)lpParameter)->lpRows);
    }

    if(((LPGWTHREADCONTEXT)lpParameter)->lpFindData != NULL)
    {
        MAPIFREEBUFFER(((LPGWTHREADCONTEXT)lpParameter)->lpFindData);
    }

    MAPIFREEBUFFER(lpParameter);
}

//$--HrGetNewMailList-----------------------------------------------------------
//  Get list of new mail.
// -----------------------------------------------------------------------------
static HRESULT HrGetNewMailList(        // RETURNS: return code
    IN  LPMAPITABLE lpTable,            // contents table
    IN  ULONG cMessages,                // maximum number of messages to get
    IN  DWORD dwPollingInterval,        // polling interval
    OUT LPSRowSet *lppRows)             // new mail list
{
    HRESULT   hr     = NOERROR;
    LPSRowSet lpRows = NULL;

    DEBUGPRIVATE("HrGetNewMailList()");

    EnterCriticalSection(&csNewMailList);

START:

    //
    // Check for service control
    //

    hr = HrServiceProcessControl();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cMessages,
        0,
        &lpRows);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    if((lpRows == NULL) || (lpRows->cRows == 0))
    {
        FREEPROWS(lpRows);
    }
    else
    {
        goto cleanup;
    }

    if((dwPollingInterval == ((DWORD)-1)) && (GetGWNewMailEvent() != NULL))
    {
        DWORD   dw          = 0;
        HANDLE  hObjects[2] = {0};

        hObjects[0] = GetServiceStopEvent();
        hObjects[1] = GetGWNewMailEvent();

        dw = WaitForMultipleObjects(2, hObjects, FALSE, dwNewMailTimeout);

        switch(dw)
        {
        case WAIT_OBJECT_0:
        case (WAIT_ABANDONED_0):

            //
            // Service is stopping.
            //

            hr = HR_LOG(EDK_E_SHUTDOWN_SERVICE);

            goto cleanup;
            break;
        case (WAIT_OBJECT_0+1):
        case (WAIT_ABANDONED_0+1):
            break;
        case (WAIT_TIMEOUT):
            break;
        default:
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        if(dwPollingInterval == ((DWORD)-1))
        {
            dwPollingInterval = SLEEP_TIME;
        }

        Sleep(dwPollingInterval);
    }

    // Go to the beginning of the contents table for the folder
    hr = MAPICALL(lpTable)->SeekRow(lpTable, BOOKMARK_BEGINNING, 0, NULL);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    goto START;

cleanup:

    if(SUCCEEDED(hr))
    {
        *lppRows = lpRows;
    }
    else
    {
        FREEPROWS(lpRows);
    }

    LeaveCriticalSection(&csNewMailList);

    RETURN(hr);
}

//$--FnNewMailPool--------------------------------------------------------------
//  New mail thread pool function
// -----------------------------------------------------------------------------
static VOID FnNewMailPool(              // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("FnNewMailPool()");

    __try
    {
        for(;;)
        {
            hr = HrGetNewMailList(
                ((LPGWTHREADCONTEXT)lpParameter)->lpTable,
                ((LPGWTHREADCONTEXT)lpParameter)->cMessages,
                ((LPGWTHREADCONTEXT)lpParameter)->dwPollingInterval,
                &((LPGWTHREADCONTEXT)lpParameter)->lpRows);

            if(FAILED(hr))
            {
                break;
            }

            (*((LPGWTHREADCONTEXT)lpParameter)->lpStartAddress)(lpParameter);

            FREEPROWS(((LPGWTHREADCONTEXT)lpParameter)->lpRows);
        }

        FreeThreadParameter(lpParameter);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();

    }
}

//$--FnNewMailMain--------------------------------------------------------------
//  New mail handler main loop.
// -----------------------------------------------------------------------------
static VOID FnNewMailMain(              // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr                = NOERROR;
    SCODE   sc                = 0;
    DWORD   dw                = 0;
    ULONG   i                 = 0;
    BOOL    fStatus           = FALSE;

    LPMAPITABLE lpTable       = NULL;

    DWORD   dwThreadId        = 0;
    HANDLE  hThread           = NULL;

    LONG    NumStartThreads   = 0;
    LONG    NumStopThreads    = 0;

    LONG    cThreads          = 0;

    LONG    cRunningThreads   = 0;

    LPMAPIFOLDER     lpMtsOutFolder  = NULL;

    LPGWTHREADCONTEXT lpContext       = NULL;
    LPGWTHREADCONTEXT lpThreadContext = NULL;

    SizedSPropTagArray(1, rgPropTag) =
    {
        1,
        {
            PR_ENTRYID
        }
    };

    DEBUGPRIVATE("FnNewMailMain()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return;

    lpContext = (LPGWTHREADCONTEXT)lpParameter;

    cThreads = lpContext->cThreads;

    lpMtsOutFolder = GetGWMtsOutFolder();

    //--------------------------------------------------------------------------

    hr = MAPICALL(lpMtsOutFolder)->GetContentsTable(
        lpMtsOutFolder,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = MAPICALL(lpTable)->SetColumns(
        lpTable,
        (LPSPropTagArray)&rgPropTag,
        TBL_BATCH);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Check for service control
    //

    hr = HrServiceProcessControl();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    NumStartThreads = 0;
    NumStopThreads  = 0;

    cRunningThreads = 0;

    for(i = 0 ; i < ((ULONG)cThreads) ; i++)
    {
        lpThreadContext = NULL;

        sc = MAPIAllocateBuffer(sizeof(GWTHREADCONTEXT), (void **)&lpThreadContext);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        CopyMemory(lpThreadContext, lpContext, sizeof(GWTHREADCONTEXT));

        lpThreadContext->ulThreadNumber = i;
        lpThreadContext->lpTable        = lpTable;
        lpThreadContext->lpRows         = NULL;

        hThread = HServiceCreateThread(
            &NumStartThreads,
            &NumStopThreads,
            NULL,
            0,
            (LPTHREAD_START_ROUTINE)FnNewMailPool,
            (LPVOID)lpThreadContext,
            0,
            &dwThreadId);

        if(hThread == NULL)
        {
            hr = HR_LOG(E_FAIL);

            if(cRunningThreads == 0)
                goto cleanup;
            else
                goto WAIT;
        }

        CloseHandle(hThread);

        cRunningThreads = i;
        cRunningThreads++;
    }

WAIT:

    // Wait for all threads to start
    while(NumStartThreads != cRunningThreads)
    {
        Sleep(SLEEP_TIME);
    }

    // Wait for all threads to exit
    while(NumStopThreads != cRunningThreads)
    {
        Sleep(SLEEP_TIME);
    }

cleanup:

    ULRELEASE(lpTable);

    if(FAILED(hr))
    {
        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    return;
}

//$--FnNewMailThread------------------------------------------------------------
//  New mail thread function
// -----------------------------------------------------------------------------
static VOID FnNewMailThread(            // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr      = NOERROR;
    HGLOBAL hglobal = NULL;

    DEBUGPRIVATE("FnNewMailThread()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        ExitThread(0);

    __try
    {
        FnNewMailMain(lpParameter);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();

    }

    MAPIFREEBUFFER( lpParameter);

    ExitThread(0);
}

//$--HrGWStartNewMailHandler@--------------------------------------------------
//  Start a thread to handle processing of new mail.
// -----------------------------------------------------------------------------
HRESULT HrGWStartNewMailHandlerW(
    IN DWORD   dwSubmitInterval,                // submit interval
    IN DWORD   dwPollingInterval,               // polling interval
    IN DWORD   cMessages,                       // #messages/thread
    IN DWORD   cThreads,                        // #threads
    IN LPCWSTR lpszAddrType,                    // address type
    IN LPCWSTR lpszPath,                        // path
    IN LPTHREAD_START_ROUTINE lpStartAddress)   // thread function
{
    HRESULT hr            = NOERROR;
    HGLOBAL hglobal       = NULL;
    LPSTR   lpszAddrTypeA = NULL;
    LPSTR   lpszPathA     = NULL;
    DWORD   dwThreadId    = 0;

    LPGWTHREADCONTEXT lpContext = NULL;

    DEBUGPUBLIC("HrGWStartNewMailHandlerW()");

    hr = CHK_HrGWStartNewMailHandlerW(
        dwSubmitInterval,
        dwPollingInterval,
        cMessages,
        cThreads,
        lpszAddrType,
        lpszPath,
        lpStartAddress);
    if(FAILED(hr))
        RETURN(hr);

    hr = MAPIAllocateBuffer( sizeof(GWTHREADCONTEXT), (PVOID)&lpContext);
    if( FAILED( hr))
        goto cleanup;

    ZeroMemory(lpContext, sizeof(GWTHREADCONTEXT));

    lpContext->fUseFindData      = FALSE;
    lpContext->dwSubmitInterval  = dwSubmitInterval;
    lpContext->dwPollingInterval = dwPollingInterval;
    lpContext->lpStartAddress    = lpStartAddress;
    lpContext->cMessages         = cMessages;
    lpContext->cThreads          = cThreads;
    lpContext->lpStartAddress    = lpStartAddress;

    hr = HrStrWToStrA( lpszAddrType, &lpszAddrTypeA);
    if( FAILED( hr))
        goto cleanup;

    hr = HrStrWToStrA( lpszPath, &lpszPathA);
    if( FAILED( hr))
        goto cleanup;

    lstrcpyA(lpContext->szAddrType, lpszAddrTypeA);
    lstrcpyA(lpContext->szFindPath, lpszPathA);

    hNewMailThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)FnNewMailThread,
        (LPVOID)lpContext,
        0,
        &dwThreadId);

    if(hNewMailThread == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER( lpszAddrTypeA);
    MAPIFREEBUFFER( lpszPathA);

    if(FAILED(hr))
        MAPIFREEBUFFER( lpContext);

    RETURN(hr);
}

HRESULT HrGWStartNewMailHandlerA(
    IN DWORD   dwSubmitInterval,                // submit interval
    IN DWORD   dwPollingInterval,               // polling interval
    IN DWORD   cMessages,                       // #messages/thread
    IN DWORD   cThreads,                        // #threads
    IN LPCSTR  lpszAddrType,                    // address type
    IN LPCSTR  lpszPath,                        // path
    IN LPTHREAD_START_ROUTINE lpStartAddress)   // thread function
{
    HRESULT hr         = NOERROR;
    HGLOBAL hglobal    = NULL;
    DWORD   dwThreadId = 0;

    LPGWTHREADCONTEXT lpContext = NULL;

    DEBUGPUBLIC("HrGWStartNewMailHandlerA()");

    hr = CHK_HrGWStartNewMailHandlerA(
        dwSubmitInterval,
        dwPollingInterval,
        cMessages,
        cThreads,
        lpszAddrType,
        lpszPath,
        lpStartAddress);
    if(FAILED(hr))
        RETURN(hr);

    hr = MAPIAllocateBuffer( sizeof(GWTHREADCONTEXT), (PVOID)&lpContext);
    if( FAILED( hr))
        goto cleanup;

    ZeroMemory(lpContext, sizeof(GWTHREADCONTEXT));

    lpContext->fUseFindData      = FALSE;
    lpContext->dwSubmitInterval  = dwSubmitInterval;
    lpContext->dwPollingInterval = dwPollingInterval;
    lpContext->lpStartAddress    = lpStartAddress;
    lpContext->cMessages         = cMessages;
    lpContext->cThreads          = cThreads;
    lpContext->lpStartAddress    = lpStartAddress;

    lstrcpyA( lpContext->szAddrType, lpszAddrType);
    lstrcpyA( lpContext->szFindPath, lpszPath);

    hNewMailThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)FnNewMailThread,
        (LPVOID)lpContext,
        0,
        &dwThreadId);

    if(hNewMailThread == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    if(FAILED(hr))
        MAPIFREEBUFFER( lpContext);

    RETURN(hr);
}

//$--HrPollFile-----------------------------------------------------------------
//  Poll for a file.
// -----------------------------------------------------------------------------
static HRESULT HrPollFile(                  // RETURNS: return code
    IN LPSTR             lpszDirectory,    // pointer to directory
    IN LPSTR             lpszFindMask,     // pointer to find mask
    OUT HANDLE            *lpPollHandle,    // pointer to find data handle
OUT LPWIN32_FIND_DATA lpFindData)       // pointer to find data
{
    HRESULT hr              = NOERROR;
BOOL    fFound          = FALSE;
DWORD   dwIgnoreAttrs   =
                        FILE_ATTRIBUTE_READONLY |
                        FILE_ATTRIBUTE_SYSTEM |
                        FILE_ATTRIBUTE_DIRECTORY |
                        FILE_ATTRIBUTE_TEMPORARY;

    DEBUGPRIVATE("HrPollFile()");

    hr = CHK_HrPollFile(
        lpszDirectory,
        lpszFindMask,
        lpPollHandle,
    lpFindData);

    if(FAILED(hr))
        RETURN(hr);

//One of the following is now true:
    //
//A)We have a search already active, use FindNextFile.
//If it fails, we will want to restart the search
//(in case something appeared in the search behind
//us.
    //
//B)We don't have a search active, use FindFirstFile.

// Try the active search

if (*lpPollHandle != INVALID_HANDLE_VALUE)
{
fFound = FindNextFile(*lpPollHandle, lpFindData);

// If we fail, close the old search so we can start
// a new one below.

if (!fFound)
{
FindClose(*lpPollHandle);
*lpPollHandle = INVALID_HANDLE_VALUE;

            //
            // If we don't find a file, then return.
            //

            if(GetLastError() == ERROR_NO_MORE_FILES)
            {
                hr = HR_LOG(EDK_E_NOT_FOUND);
            }
            else
            {
                hr = HR_LOG(HRESULT_FROM_WIN32(GetLastError()));
            }

goto cleanup;
}
}

// If there was no search or if the old one was just closed above

if (*lpPollHandle == INVALID_HANDLE_VALUE)
{
HANDLE hFindT;
CHAR rgchFileName[MAX_PATH];

// Copy the directory name. Note that we trust the value in the
// profile to be correct, since the UI should have enforced a
// syntax that included a trailing : or \ in the spec.

lstrcpy(rgchFileName, lpszDirectory);

lstrcat(rgchFileName, lpszFindMask);

hFindT = FindFirstFile(rgchFileName, lpFindData);

// If nothing's found, we're done here.

if (hFindT == INVALID_HANDLE_VALUE)
        {
            hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
        }

// Found something, continue along.

fFound = TRUE;
*lpPollHandle = hFindT;
}

//Here on a match. Exclude unwanted files.
    //
//Any match with DIRECTORY, READONLY, SYSTEM or TEMPORARY attribute
//is ignored. Keep trying until we exhaust the current supply or we
//find a file without these attributes. Also, ignore files smaller
//than some arbitrary size, they're probably trash.

while (fFound)
{
//We found a file. Does it have any of the attributes we
//want to ignore? If not, get out. If so, try another.

#define MIN_USEFUL_FILESIZE ((DWORD) 64)

if ((!((lpFindData)->dwFileAttributes & dwIgnoreAttrs)) &&
((lpFindData->nFileSizeHigh != 0) ||
(lpFindData->nFileSizeLow >= MIN_USEFUL_FILESIZE)))
break;

fFound = FindNextFile(*lpPollHandle, lpFindData);
}

if(!fFound)
{
FindClose(*lpPollHandle);
*lpPollHandle = INVALID_HANDLE_VALUE;

        hr = HR_LOG(EDK_E_NOT_FOUND);
        goto cleanup;
}

cleanup:

RETURN(hr);
}

//$--HrGetNewFileList-----------------------------------------------------------
//  Get list of new files.
// -----------------------------------------------------------------------------
static HRESULT HrGetNewFileList(        // RETURNS: return code
    IN  ULONG cMessages,                // maximum number of messages to get
    IN  DWORD dwPollingInterval,        // polling interval
    IN  LPSTR lpszFindPath,            // find path
    IN  LPSTR lpszFindMask,            // find mask
    IN OUT HANDLE *lpPollHandle,        // poll handle
    IN OUT HANDLE *lphFindData,         // change notifcation handle
    OUT ULONG *lpcFindData,             // count of find data
    OUT LPWIN32_FIND_DATA *lppFindData) // new file list
{
    HRESULT           hr         = NOERROR;
LPWIN32_FIND_DATA lpFindData = NULL;
    ULONG             cBytes     = 0;
    SCODE             sc         = NOERROR;
    ULONG             j          = 0;
    BOOL              fStatus    = FALSE;

    DEBUGPRIVATE("HrGetNewFileList()");

    *lpcFindData = 0;
    *lppFindData = NULL;

    EnterCriticalSection(&csNewFileList);

START:

    //
    // Check for service control
    //

    hr = HrServiceProcessControl();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    lpFindData = NULL;

    cBytes = cMessages*(sizeof(WIN32_FIND_DATA) + MAX_ALIGN);

    sc = MAPIAllocateBuffer(cBytes, (void **)&lpFindData);

    ZeroMemory(lpFindData, cBytes);

    // An error occured allocating the messages buffer

    if(FAILED(sc))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(j = 0 ; j < ((ULONG)cMessages) ; j++)
    {
        hr = HrPollFile(
            lpszFindPath,
            lpszFindMask,
            lpPollHandle,
            &lpFindData[j]);

        if(hr == EDK_E_NOT_FOUND)
        {
            hr = NOERROR;

            //
            // Don't start any more threads after the current one is
            // started since there are no more files to process.
            //

            break;
        }

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    if(j == 0)
    {
        MAPIFREEBUFFER(lpFindData);
    }
    else
    {
        *lpcFindData = j;
        *lppFindData = lpFindData;
        goto cleanup;
    }

    if((dwPollingInterval == ((DWORD)-1)) && (*lphFindData != NULL))
    {
        DWORD   dw          = 0;
        HANDLE  hObjects[2] = {0};

        hObjects[0] = GetServiceStopEvent();
        hObjects[1] = *lphFindData;

        dw = WaitForMultipleObjects(2, hObjects, FALSE, dwNewFileTimeout);

        switch(dw)
        {
        case WAIT_OBJECT_0:
        case (WAIT_ABANDONED_0):

            //
            // Service is stopping.
            //

            hr = HR_LOG(EDK_E_SHUTDOWN_SERVICE);

            goto cleanup;
            break;
        case (WAIT_OBJECT_0+1):
        case (WAIT_ABANDONED_0+1):
            break;
        case (WAIT_TIMEOUT):
            goto START;
            break;
        default:
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        fStatus = FindNextChangeNotification(*lphFindData);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);

            FindCloseChangeNotification(*lphFindData);
            *lphFindData = NULL;
        }
    }
    else
    {
        if(dwPollingInterval == ((DWORD)-1))
        {
            dwPollingInterval = SLEEP_TIME;
        }

        Sleep(dwPollingInterval);
    }

    goto START;

cleanup:

    if(FAILED(hr))
    {
        *lpcFindData = 0;
        *lppFindData = NULL;

        MAPIFREEBUFFER(lpFindData);
    }

    LeaveCriticalSection(&csNewFileList);

    RETURN(hr);
}

//$--FnNewFilePool--------------------------------------------------------------
//  New file thread pool function
// -----------------------------------------------------------------------------
static VOID FnNewFilePool(              // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("FnNewFilePool()");

    __try
    {
        for(;;)
        {
            hr = HrGetNewFileList(
                    ((LPGWTHREADCONTEXT)lpParameter)->cMessages,
                    ((LPGWTHREADCONTEXT)lpParameter)->dwPollingInterval,
                    ((LPGWTHREADCONTEXT)lpParameter)->szFindPath,
                    ((LPGWTHREADCONTEXT)lpParameter)->szFindMask,
                    ((LPGWTHREADCONTEXT)lpParameter)->lpPollHandle,
                    ((LPGWTHREADCONTEXT)lpParameter)->lphFindData,
                    &((LPGWTHREADCONTEXT)lpParameter)->cFindData,
                    &((LPGWTHREADCONTEXT)lpParameter)->lpFindData);

            if(FAILED(hr))
            {
                break;
            }

            (*((LPGWTHREADCONTEXT)lpParameter)->lpStartAddress)(lpParameter);

            MAPIFREEBUFFER(((LPGWTHREADCONTEXT)lpParameter)->lpFindData);
        }

        FreeThreadParameter(lpParameter);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();

    }
}

//$--FnNewFileMain--------------------------------------------------------------
//  New file handler main loop.
// -----------------------------------------------------------------------------
static VOID FnNewFileMain(              // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr                = NOERROR;
    HRESULT hrT               = NOERROR;
    SCODE   sc                = 0;
    DWORD   dw                = 0;

BOOL    fStatus           = 0;
    ULONG   i                 = 0;

    ULONG   cBytes            = 0;

    DWORD   dwThreadId        = 0;
    HANDLE  hThread           = NULL;

    LONG    NumStartThreads   = 0;
    LONG    NumStopThreads    = 0;

    LONG    cThreads          = 0;
    ULONG   cMessages         = 0;

    LONG    cRunningThreads   = 0;

    DWORD   dwPollingInterval = 0;

    LPGWTHREADCONTEXT lpContext       = NULL;
    LPGWTHREADCONTEXT lpThreadContext = NULL;

    DEBUGPRIVATE("FnNewFileMain()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        return;

    lpContext = (LPGWTHREADCONTEXT)lpParameter;

    cThreads = lpContext->cThreads;

    //--------------------------------------------------------------------------

    //
    // Check for service control
    //

    hr = HrServiceProcessControl();

    if(FAILED(hr))
    {
        goto cleanup;
    }

    NumStartThreads = 0;
    NumStopThreads  = 0;

    cRunningThreads = 0;

    for(i = 0 ; i < ((ULONG)cThreads) ; i++)
    {
        lpThreadContext = NULL;

        sc = MAPIAllocateBuffer(sizeof(GWTHREADCONTEXT), (void **)&lpThreadContext);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        CopyMemory(lpThreadContext, lpContext, sizeof(GWTHREADCONTEXT));

        lpThreadContext->ulThreadNumber = i;
        lpThreadContext->cFindData      = 0;
        lpThreadContext->lpFindData     = NULL;

        hThread = HServiceCreateThread(
            &NumStartThreads,
            &NumStopThreads,
            NULL,
            0,
            (LPTHREAD_START_ROUTINE)FnNewFilePool,
            (LPVOID)lpThreadContext,
            0,
            &dwThreadId);

        if(hThread == NULL)
        {
            hr = HR_LOG(E_FAIL);

            if(cRunningThreads == 0)
                goto cleanup;
            else
                goto WAIT;
        }

        CloseHandle(hThread);

        cRunningThreads = i;
        cRunningThreads++;
    }

WAIT:

    // Wait for all threads to start
    while(NumStartThreads != cRunningThreads)
    {
        Sleep(SLEEP_TIME);
    }

    // Wait for all threads to exit
    while(NumStopThreads != cRunningThreads)
    {
        Sleep(SLEEP_TIME);
    }

cleanup:

    if(FAILED(hr))
    {
        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    return;
}

//$--FnNewFileThread------------------------------------------------------------
//  New file thread function
// -----------------------------------------------------------------------------
static VOID FnNewFileThread(            // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr      = NOERROR;
    BOOL    fStatus = FALSE;

    DEBUGPRIVATE("FnNewFileThread()");

    hr = CHK_HrCheckParameter(lpParameter);

    if(FAILED(hr))
        ExitThread(0);

    __try
    {
        FnNewFileMain(lpParameter);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);

        MODULE_WARNING("**** Stopping gateway ****");

        SetServiceExitCode( ERROR_INTERNAL_ERROR, NOERROR);

        ServiceStop();
    }

    __try
    {
        if(*((LPGWTHREADCONTEXT)lpParameter)->lphFindData != NULL)
        {
            fStatus = FindCloseChangeNotification(
                *((LPGWTHREADCONTEXT)lpParameter)->lphFindData);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);
    }

    MAPIFREEBUFFER( lpParameter);

    ExitThread(0);
}

//$--HrGWStartNewFileHandler@--------------------------------------------------
//  Start a thread to handle processing of new files.
// -----------------------------------------------------------------------------
HRESULT HrGWStartNewFileHandlerW(
    IN DWORD   dwSubmitInterval,                // submit interval
    IN DWORD   dwPollingInterval,               // polling interval
    IN DWORD   cMessages,                       // #messages/thread
    IN DWORD   cThreads,                        // #threads
    IN LPCWSTR lpszAddrType,                    // address type
    IN LPCWSTR lpszFileMask,                    // file mask
    IN LPCWSTR lpszPath,                        // path
    IN LPTHREAD_START_ROUTINE lpStartAddress)   // thread function
{
    HRESULT hr         = NOERROR;
    HANDLE  hThread    = NULL;
    LPSTR   lpszAddrTypeA = NULL;
    LPSTR   lpszFileMaskA = NULL;
    LPSTR   lpszPathA     = NULL;
    DWORD   dwThreadId = 0;

    LPGWTHREADCONTEXT lpContext = NULL;

    DEBUGPUBLIC("HrGWStartNewFileHandlerW()");

    hr = CHK_HrGWStartNewFileHandlerW(
        dwSubmitInterval,
        dwPollingInterval,
        cMessages,
        cThreads,
        lpszAddrType,
        lpszFileMask,
        lpszPath,
        lpStartAddress);
    if(FAILED(hr))
        RETURN(hr);

    hr = MAPIAllocateBuffer( sizeof(GWTHREADCONTEXT), (PVOID)&lpContext);
    if( FAILED( hr))
        goto cleanup;

    ZeroMemory(lpContext, sizeof(GWTHREADCONTEXT));

    lpContext->fUseFindData      = TRUE;
    lpContext->dwSubmitInterval  = dwSubmitInterval;
    lpContext->dwPollingInterval = dwPollingInterval;
    lpContext->lpStartAddress    = lpStartAddress;
    lpContext->cMessages         = cMessages;
    lpContext->cThreads          = cThreads;
    lpContext->lpStartAddress    = lpStartAddress;

    hr = HrStrWToStrA( lpszAddrType, &lpszAddrTypeA);
    if( FAILED( hr))
        goto cleanup;

    hr = HrStrWToStrA( lpszFileMask, &lpszFileMaskA);
    if( FAILED( hr))
        goto cleanup;

    hr = HrStrWToStrA( lpszPath, &lpszPathA);
    if( FAILED( hr))
        goto cleanup;

    lstrcpyA(lpContext->szAddrType, lpszAddrTypeA);
    lstrcpyA(lpContext->szFindMask, lpszFileMaskA);
    lstrcpyA(lpContext->szFindPath, lpszPathA);

    hFindData = FindFirstChangeNotification(
        lpContext->szFindPath,
        FALSE,
        FILE_NOTIFY_CHANGE_ATTRIBUTES);

    if(hFindData == INVALID_HANDLE_VALUE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpContext->lphFindData  = &hFindData;
    lpContext->lpPollHandle = &PollHandle;

    hNewFileThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)FnNewFileThread,
        (LPVOID)lpContext,
        0,
        &dwThreadId);

    if(hNewFileThread == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    MAPIFREEBUFFER( lpszAddrTypeA);
    MAPIFREEBUFFER( lpszFileMaskA);
    MAPIFREEBUFFER( lpszPathA);

    if(FAILED(hr))
        MAPIFREEBUFFER( lpContext);

    RETURN(hr);
}

HRESULT HrGWStartNewFileHandlerA(
    IN DWORD   dwSubmitInterval,                // submit interval
    IN DWORD   dwPollingInterval,               // polling interval
    IN DWORD   cMessages,                       // #messages/thread
    IN DWORD   cThreads,                        // #threads
    IN LPCSTR  lpszAddrType,                    // address type
    IN LPCSTR  lpszFileMask,                    // file mask
    IN LPCSTR  lpszPath,                        // path
    IN LPTHREAD_START_ROUTINE lpStartAddress)   // thread function
{
    HRESULT hr            = NOERROR;
    HANDLE  hThread       = NULL;
    DWORD   dwThreadId    = 0;

    LPGWTHREADCONTEXT lpContext = NULL;

    DEBUGPUBLIC("HrGWStartNewFileHandlerA()");

    hr = CHK_HrGWStartNewFileHandlerA(
        dwSubmitInterval,
        dwPollingInterval,
        cMessages,
        cThreads,
        lpszAddrType,
        lpszFileMask,
        lpszPath,
        lpStartAddress);
    if(FAILED(hr))
        RETURN(hr);

    hr = MAPIAllocateBuffer( sizeof(GWTHREADCONTEXT), (PVOID)&lpContext);
    if( FAILED( hr))
        goto cleanup;

    ZeroMemory(lpContext, sizeof(GWTHREADCONTEXT));

    lpContext->fUseFindData      = TRUE;
    lpContext->dwSubmitInterval  = dwSubmitInterval;
    lpContext->dwPollingInterval = dwPollingInterval;
    lpContext->lpStartAddress    = lpStartAddress;
    lpContext->cMessages         = cMessages;
    lpContext->cThreads          = cThreads;
    lpContext->lpStartAddress    = lpStartAddress;

    lstrcpy(lpContext->szAddrType, lpszAddrType);
    lstrcpy(lpContext->szFindMask, lpszFileMask);
    lstrcpy(lpContext->szFindPath, lpszPath);

    hFindData = FindFirstChangeNotification(
        lpContext->szFindPath,
        FALSE,
        FILE_NOTIFY_CHANGE_ATTRIBUTES);

    if(hFindData == INVALID_HANDLE_VALUE)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpContext->lphFindData  = &hFindData;
    lpContext->lpPollHandle = &PollHandle;

    hNewFileThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)FnNewFileThread,
        (LPVOID)lpContext,
        0,
        &dwThreadId);

    if(hNewFileThread == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
    if(FAILED(hr))
        MAPIFREEBUFFER( lpContext);

    RETURN(hr);
}


GWPERF.C

// --gwperf.c-------------------------------------------------------------------
//
//  This file contains the DLL side of the Exchange SDK performance
//  monitoring component.  When this DLL is called by the NT Performance
//  Monitor it reads the performance data from a shared memory section
//  created by the gateway side, and returns it to the Performance Monitor.
//  This DLL is also capable of reading the data from several different
//  gateway processes and combining them into one single Performance
//  Monitor object with multiple instances.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "monitor.h"
#include "monshare.h"
#include "gwperfm.h"
#include <winperf.h>

//
// Debugging Information
//

// Define VERBOSE_DEBUG here to write to the debug log with every call to
// CollectGatewayPerformanceData and the routines it calls.  Do this only
// if you need it, since this routine is called once every second and can
// quickly fill up your disk!!!

// #define VERBOSE_DEBUG

//
// Local Structure Declarations
//

//$--GINSTANCE------------------------------------------------------------------
//  Holds information about a single gateway instance.
// -----------------------------------------------------------------------------
typedef struct _GInstance        // (hungarian notation = gi)
{
    BOOL                fOpen;              // TRUE if this instance is open
HANDLE              hFileMapping;    // file mapping handle
    LONG                cLockReference;     // mutex lock reference count
HANDLE              hMutex;    // mutex handle
LPMONSHAREDMEMORY   lpmsmMemory;    // shared mem for gw communication
    LPWSTR              lpwszName;          // gateway instance name
} GINSTANCE, *LPGINSTANCE;

//$--GTYPE----------------------------------------------------------------------
//  Holds information about the gateway type that this DLL handles.
// -----------------------------------------------------------------------------
typedef struct _GType                       // (hungarian notation = gt)
{
    ULONG               cCollectFailures;   // number of times error on collect
    ULONG               cInstances;         // number of instances in array
    LPGINSTANCE         rggiInstances;      // alloc'd array of instance structs
    LONG                iObjectName;        // title index of obj we return
} GTYPE, *LPGTYPE;

//
// Public Function Declarations
//

PM_OPEN_PROC OpenGatewayPerformanceData;
PM_COLLECT_PROC CollectGatewayPerformanceData;
PM_CLOSE_PROC CloseGatewayPerformanceData;

//
// Local Function Declarations
//

static DWORD CreateGType(
    IN  LPWSTR          grwszInstanceNames,
    OUT LPGTYPE *       lppgt);

static DWORD DestroyGType(
    IN OUT  LPGTYPE     lpgt);

static DWORD OpenGInstance(
    IN      LPWSTR      lpwszInstanceName,
    IN OUT  LPGINSTANCE lpgi,
    OUT     LPLONG      lpiObjectName);

static DWORD CloseGInstance(
    IN OUT  LPGINSTANCE lpgi);

static DWORD CountMultiStrings(
    IN  LPWSTR          grwsz,
    OUT LPULONG         pcwsz);

static DWORD LockSharedMemory(
IN  LPGINSTANCE     lpgi);

static DWORD UnlockSharedMemory(
IN  LPGINSTANCE     lpgi);

static BOOL fOurDataRequested(
    IN  LPGTYPE         lpgt,
    IN  LPWSTR          lpwszRequest);

static VOID ReadSharedMemoryFromRegistry(
IN OUT  LPGINSTANCE lpgi);

//
// Local Constants
//

static const ULONG MAX_COLLECT_FAILURES = 10;

//
// Local Variables
//

static LPGTYPE lpgtContext = NULL;

static SECURITY_ATTRIBUTES sa;
static char rgbForSecurityDescriptor[SECURITY_DESCRIPTOR_MIN_LENGTH];

static SECURITY_ATTRIBUTES * psa;
static SECURITY_DESCRIPTOR * psd;

//
// DLL Hook Routines
//

//$--DllEntryPoint------------------------------------------------------------
//  Perform DLL initialization and shutdown.  Returns TRUE if successful,
//  and FALSE if unsuccessful.
// ---------------------------------------------------------------------------
BOOL WINAPI DllEntryPoint(                  // RETURNS: BOOL
    IN  HINSTANCE       hInstDll,           // DLL instance handle
    IN  DWORD           fdwReason,          // reason we're being called
    IN  LPVOID          lpvReserved)        // reserved
{
    BOOL                fResult             = TRUE;
    BOOL                fEventLogOpen       = FALSE;
    HRESULT             hr                  = NOERROR;
    CHAR                szDllPath[MAX_PATH+1] = {0};
    DWORD               cchDllPath          = 0;

    DEBUGPUBLIC("DllEntryPoint()\n");

    // Check to see why we were called.

    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:

            DEBUGACTION("DLL_PROCESS_ATTACH");

            // Get the path and file name of this DLL.

            cchDllPath = GetModuleFileName(
                GetInstanceModule(hInstDll),
                szDllPath,
                sizeof(szDllPath) - 1);
            if (cchDllPath == 0)
            {
                hr = HR_LOG(E_FAIL);
                MODULE_WARNING(
                    "GWPERF_DLL_ENTRY_MODULE_NAME: GetModuleFileName() failed.");
            }

            // If getting the name succeeded then initialize event logging.

            else
            {
                hr = HrEventOpenLog(
                    "EDK GWPerf",
                    szDllPath,
                    NULL,
                    NULL,
                    NULL,
                    NULL);
                if (FAILED(hr))
                {
                    MODULE_WARNING1(
                        "GWPERF_DLL_ENTRY_OPEN_LOG: HrEventOpenLog() failed for DLL %s.",
                        szDllPath);
                }
                else
                {
                    fEventLogOpen = TRUE;
                }
            }

            // Set up a security descriptor with a NULL DACL, to allow all
            // access to an object.  This is used if we need to create the
            // mutex and shared memory section.  Failure to do this is a
            // fatal error.

            psa = &sa;
            psd = (SECURITY_DESCRIPTOR *)rgbForSecurityDescriptor;

            if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
            {
                EventLogMsg(
                    GWPERF_ERROR,
                    0,
                    GetLastError());
                fResult = FALSE;
            }
            else if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL) NULL, FALSE))
            {
                EventLogMsg(
                    GWPERF_ERROR,
                    0,
                    GetLastError());
                fResult = FALSE;
            }
            else
            {
                psa->nLength = sizeof(*psa);
                psa->lpSecurityDescriptor = psd;
                psa->bInheritHandle = TRUE;
            }

            // If there was a fatal error on open, then close the event log.

            if (fResult == FALSE && fEventLogOpen == TRUE)
            {
                (void) HrEventCloseLog();
                fEventLogOpen = FALSE;
            }

            break;

        case DLL_THREAD_ATTACH:
            DEBUGACTION("DLL_THREAD_ATTACH");
            break;

        case DLL_THREAD_DETACH:
            DEBUGACTION("DLL_THREAD_DETACH");
            break;

        case DLL_PROCESS_DETACH:

            DEBUGACTION("DLL_PROCESS_DETACH");

            // Shut down event logging.

            hr = HrEventCloseLog();
            if (FAILED(hr))
            {
                MODULE_WARNING(
                    "GWPERF_DLL_ENTRY_CLOSE_LOG: HrEventCloseLog() failed for DLL %s.");
            }

            break;

        default:

            // Invalid reason.

            MODULE_ERROR("GWPERF_DLL_ENTRY_REASON: Invalid reason code.");

            fResult = FALSE;

            break;
    }

    ASSERTERROR(fResult, "GWPERF_DLL_ENTRY_FAILED: GWPerf.dll failed to load.");
    return fResult;
}

//$--OpenGatewayPerformanceData-------------------------------------------------
//  Function called by the NT Performance Monitor (via the registry) to begin
//  performance monitoring on a gateway.
//
//  NOTE: In this function, all strings are explicitly WCHAR UNICODE strings
//  because that is what is passed in as device names.
// -----------------------------------------------------------------------------
DWORD APIENTRY OpenGatewayPerformanceData(// RETURNS: Win32 error code
IN  LPWSTR          grwszInstanceNames)// strings from Linkage\Export
{
DWORDdwStatus      = ERROR_SUCCESS;

    DEBUGPUBLIC("OpenGatewayPerformanceData()\n");

    // If we're already open then the path to this DLL must exist in multiple
    // Performance keys, which is an error.

    if (lpgtContext)
    {
        dwStatus = ERROR_DLL_INIT_FAILED;
        EventLogMsg(
            GWPERF_OPEN_ALREADY_OPEN,
            0,
            0);
        goto cleanup;
    }

// If they didn't have a Linkage\Export value then we can't continue.

if (grwszInstanceNames == NULL)
{
        dwStatus = ERROR_DLL_INIT_FAILED;
        EventLogMsg(
            GWPERF_OPEN_NO_LINKAGE,
            0,
            0);
        goto cleanup;
}

    // Create the GTYPE structure and begin all the monitoring.

    dwStatus = CreateGType(grwszInstanceNames, &lpgtContext);
    if (dwStatus)
        goto cleanup;

cleanup:
    return dwStatus;
}

//$--CollectGatewayPerformanceData----------------------------------------------
//  Function called by the NT Performance Monitor to collect performance data
//  on a gateway.  Always returns either ERROR_SUCCESS or ERROR_MORE_DATA.
// -----------------------------------------------------------------------------
DWORD APIENTRY CollectGatewayPerformanceData( // RETURNS: Win32 error code
IN      LPWSTR      lpwszRequest,// type of data requested
IN OUT  LPVOID *    lppvData,// buffer where data should be put
IN OUT  LPDWORD     lpcbBytes,// size of data buffer
OUT     LPDWORD     lpcObjectTypes)// number of object types returned
{
DWORDdwStatus      = ERROR_SUCCESS;

LPBYTE*lppData= (LPBYTE *)lppvData;
DWORDigi= 0;
LPGINSTANCE        lpgi= NULL;
LPMONSHAREDMEMORYlpmsm= NULL;
BOOLfPOTCopied= FALSE;

LPBYTElpOutBuf= *lppData;
LPBYTElpOutBufPtr= *lppData;
DWORDcbOutBufSize= *lpcbBytes;
BOOLfFoundRunning    = FALSE;

PPERF_OBJECT_TYPElpOutPOT= NULL;

#ifdef VERBOSE_DEBUG
    DEBUGPUBLIC("CollectGatewayPerformanceData()\n");
#endif

// Set the return variables to return no data.

*lpcbBytes = 0;
*lpcObjectTypes = 0;
dwStatus = ERROR_SUCCESS;

    // If they didn't call open before calling here, then log an
    // error (but only once).

    if (lpgtContext == NULL)
    {
        static BOOL fAlreadyLogged = FALSE;

        if (!fAlreadyLogged)
        {
            EventLogMsg(
                GWPERF_COLLECT_NOT_OPEN,
                0,
                0);
            fAlreadyLogged = TRUE;
        }
        goto cleanup;
    }

    // If we've failed too many times then return with no data.

    if (lpgtContext->cCollectFailures > MAX_COLLECT_FAILURES)
    {
goto cleanup;
    }

    // If we know what our object number is, and the request string is not
    // asking for our object number, then return with no data.

    if (lpgtContext->iObjectName != -1 &&
        !fOurDataRequested(lpgtContext, lpwszRequest))
        goto cleanup;

// Loop to build the data structure for all instances by appending
// together the structures for each instance.  The new data structure
// is created in the buffer provided by the caller.

for (igi = 0; igi < lpgtContext->cInstances; igi++)
{
lpgi = &lpgtContext->rggiInstances[igi];
lpmsm = lpgi->lpmsmMemory;

// Initial check for valid data.  If not valid here then don't bother
        // locking the mutex.  This keeps us from degrading system performance
        // by locking and unlocking a mutex every time we are called when
        // our gateway isn't running.

if (!lpmsm->fDataIsValid)
continue;

        // Lock shared memory for gateway instance.  It will be unlocked
        // later.  Any exceptional conditions between now and "later" must
        // call UnlockSharedMemory.

dwStatus = LockSharedMemory(lpgi);
        if (dwStatus)
            goto cleanup;

// If this shared memory block doesn't contain valid data then skip it.

lpmsm = lpgi->lpmsmMemory;

if (!lpmsm->fDataIsValid)
{
dwStatus = UnlockSharedMemory(lpgi);
            if (dwStatus)
                goto cleanup;
continue;
}

        // If we don't already have our object number then get it from the
        // shared memory block.

        if (lpgtContext->iObjectName == -1)
        {
            PPERF_OBJECT_TYPE lpPOT = NULL;

            lpPOT = (PPERF_OBJECT_TYPE)
                (((LPBYTE)(lpgi->lpmsmMemory))
                + lpgi->lpmsmMemory->ibHeaderOffset);
        lpgtContext->iObjectName = lpPOT->ObjectNameTitleIndex;

            // Now that we finally know our number, check to make sure it is one
            // of the ones requested.

            if (!fOurDataRequested(lpgtContext, lpwszRequest))
            {
        dwStatus = UnlockSharedMemory(lpgi);
                goto cleanup;
            }
        }

        // Flag that we found a running gateway instance (i.e. one that has
        // the fDataIsValid flag set).

fFoundRunning = TRUE;

// Copy PERF_OBJECT_TYPE header structure from the first running
// gateway instance we find.

if (!fPOTCopied)
{
            // If the buffer isn't big enough then request a larger buffer
            // from the caller.

if (lpmsm->cbHeaderSize > cbOutBufSize)
{
    dwStatus = UnlockSharedMemory(lpgi);
                if (dwStatus)
                    goto cleanup;

dwStatus = ERROR_MORE_DATA;
goto cleanup;
}

            // Copy the header.

MoveMemory(
lpOutBufPtr,
((LPBYTE)(lpmsm)) + lpmsm->ibHeaderOffset,
lpmsm->cbHeaderSize);
lpOutPOT = (PPERF_OBJECT_TYPE)lpOutBufPtr;
lpOutBufPtr += lpmsm->cbHeaderSize;
cbOutBufSize -= lpmsm->cbHeaderSize;

// Start the header's instance count at 0 instances (it will
// be incremented each time we add an instance structure).

lpOutPOT->NumInstances = 0;

// Flag that we copied over the PERF_OBJECT_TYPE header structure.

fPOTCopied = TRUE;
}

        // If the buffer isn't big enough then request a larger buffer
        // from the caller.

if (lpmsm->cbInstanceSize > cbOutBufSize)
{
dwStatus = UnlockSharedMemory(lpgi);
            if (dwStatus)
                goto cleanup;

dwStatus = ERROR_MORE_DATA;
goto cleanup;
}

// Copy the instance data from this gateway instance.

MoveMemory(
lpOutBufPtr,
((LPBYTE)(lpmsm)) + lpmsm->ibInstanceOffset,
lpmsm->cbInstanceSize);
lpOutBufPtr += lpmsm->cbInstanceSize;
cbOutBufSize -= lpmsm->cbInstanceSize;

// Bump up variables in the PERF_OBJECT_TYPE header to reflect
// the addition of this instance.

lpOutPOT->TotalByteLength = lpOutBufPtr - lpOutBuf;
lpOutPOT->NumInstances++;

// Unlock the shared memory.

dwStatus = UnlockSharedMemory(lpgi);
        if (dwStatus)
            goto cleanup;
}

// If we found a running gateway instance then update the return variables.

if (fFoundRunning)
{
*lpcObjectTypes = 1;
*lppData = lpOutBufPtr;
*lpcbBytes = lpOutBufPtr - lpOutBuf;
}

cleanup:
    // If there was an error other than ERROR_MORE_DATA, then increment
    // the collect failure count.  When there gets to be too many errors,
    // we will start returning "no data" on every call to collect.

    if (dwStatus && dwStatus != ERROR_MORE_DATA)
    {
        ++lpgtContext->cCollectFailures;

        if (lpgtContext->cCollectFailures > MAX_COLLECT_FAILURES)
        {
            EventLogMsg(
                GWPERF_COLLECT_TOO_MANY_FAILURES,
                0,
                0);
        }

        dwStatus = ERROR_SUCCESS;
    }

return dwStatus;
}

//$--CloseGatewayPerformanceData------------------------------------------------
//  Function called by the NT Performance Monitor to end collection of
//  performance data.
// -----------------------------------------------------------------------------
DWORD APIENTRY CloseGatewayPerformanceData(// RETURNS: Win32 error code
void)
{
DWORDdwStatus      = ERROR_SUCCESS;

    DEBUGPUBLIC("CloseGatewayPerformanceData()\n");

    // If they didn't call "open" then log an error (but only once).

    if (lpgtContext == NULL)
    {
        static BOOL fAlreadyLogged = FALSE;

        if (!fAlreadyLogged)
        {
            EventLogMsg(
                GWPERF_CLOSE_NOT_OPEN,
                0,
                0);
            fAlreadyLogged = TRUE;
        }
        goto cleanup;
    }

    // Free the gateway type context.

    dwStatus = DestroyGType(lpgtContext);
    if (dwStatus)
        goto cleanup;
    lpgtContext = NULL;

cleanup:
return dwStatus;
}

//$--CreateGType----------------------------------------------------------------
//  Create a GTYPE structure.
// -----------------------------------------------------------------------------
static DWORD CreateGType(                   // RETURNS: Win32 error code
    IN  LPWSTR          grwszInstanceNames, // group of instance name strings
    OUT LPGTYPE *       lppgt)              // allocated GTYPE structure
{
DWORDdwStatus      = ERROR_SUCCESS;
    ULONG               cInstances          = 0;
LPGINSTANCE        lpgi= NULL;
ULONGigi= 0;
LPWSTRlpwszInstanceName= NULL;
    DWORD               iObjectName         = 0;
    LPGTYPE             lpgt                = NULL;

    DEBUGPRIVATE("CreateGType()\n");

    // Count the instance name strings passed to us.

dwStatus = CountMultiStrings(grwszInstanceNames, &cInstances);
    if (dwStatus)
        goto cleanup;

    // Allocate the GTYPE structure.

    lpgt = malloc(sizeof(*lpgt));
    if (lpgt == NULL)
    {
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
    }
ZeroMemory(lpgt, sizeof(*lpgt));

    // Fill in various fields in the GTYPE.

    lpgt->cInstances = cInstances;
    lpgt->iObjectName = -1;

// Allocate an array of GINSTANCE's, one for each instance name string.

lpgt->rggiInstances = calloc(cInstances, sizeof(GINSTANCE));
if (lpgt->rggiInstances == NULL)
    {
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
    }
ZeroMemory(lpgt->rggiInstances, cInstances * sizeof(GINSTANCE));

// Loop to open the GINSTANCE's.

    for (
        igi = 0, lpwszInstanceName = grwszInstanceNames;
        igi < lpgt->cInstances;
        igi++, lpwszInstanceName += (wcslen(lpwszInstanceName) + 1))
{
lpgi = &lpgt->rggiInstances[igi];

        // Open this gateway instance.

        dwStatus = OpenGInstance(lpwszInstanceName, lpgi, &iObjectName);
        if (dwStatus)
            goto cleanup;

        // If we haven't found the object name title index yet, and
        // one was returned from OpenGInstance, use it.

        if (lpgt->iObjectName == -1)
        {
            lpgt->iObjectName = iObjectName;
        }
}

    // Return the pointer to the GTYPE.

    *lppgt = lpgt;

cleanup:
if (dwStatus && lpgt)
    {
        (void) DestroyGType(lpgt);
    }

return dwStatus;
}

//$--DestroyGType---------------------------------------------------------------
//  Destroy a GTYPE structure (even a partially constructed one).
// -----------------------------------------------------------------------------
static DWORD DestroyGType(                  // RETURNS: Win32 error code
    IN OUT  LPGTYPE     lpgt)               // allocated GTYPE structure
{
DWORDdwStatus      = ERROR_SUCCESS;
DWORDdwStatusTemp  = ERROR_SUCCESS;
ULONGigi= 0;

    DEBUGPRIVATE("DestroyGType()\n");

    if (lpgt)
    {
        // Loop to close each GINSTANCE.

        if (lpgt->rggiInstances)
        {
            for (igi = 0; igi < lpgt->cInstances; igi++)
            {
                dwStatusTemp = CloseGInstance(&lpgt->rggiInstances[igi]);
                if (dwStatusTemp)
                {
                    dwStatus = dwStatusTemp;
                }
            }
            FREE(lpgt->rggiInstances);
        }

        // Free the GTYPE.

        FREE(lpgt);
    }

    return dwStatus;
}

//$--OpenGInstance--------------------------------------------------------------
//  Sets up a GINSTANCE structure and maps its shared memory.
// -----------------------------------------------------------------------------
static DWORD OpenGInstance(                 // RETURNS: Win32 error code
    IN      LPWSTR      lpwszInstanceName,  // name of gateway instance
    IN OUT  LPGINSTANCE lpgi,               // instance struct to fill in
    OUT     LPLONG      lpiObjectName)      // obj title name index for instance
                                            // (-1 = it doesn't have valid data)
{
DWORDdwStatus      = ERROR_SUCCESS;
LONG                cchOutSize          = 0;
    PPERF_OBJECT_TYPE   lpPOT               = NULL;
WCHARwszMappingName[MAX_PATH] = {0};
WCHARwszMutexName[MAX_PATH] = {0};
    LONG                iObjectName         = -1;

    DEBUGPRIVATE("OpenGInstance()\n");

    // Initially assume the instance is not locked.

    lpgi->cLockReference = 0;

    // Add an allocated copy of the instance name to the GINSTANCE structure.

    lpgi->lpwszName = _wcsdup(lpwszInstanceName);
    if (lpgi->lpwszName == NULL)
    {
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
    }

    // Create the name of the mutex.

cchOutSize = wsprintfW(
wszMutexName,
MON_MUTEX_NAME_TEMPLATE_W,
lpwszInstanceName);

    if ((cchOutSize + 1) * sizeof(CHAR) > sizeof(wszMutexName))
    {
        dwStatus = ERROR_BUFFER_OVERFLOW;
        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
    }

(void) CharUpperW(wszMutexName);

    // Create the name of the shared memory mapping.

cchOutSize = wsprintfW(
wszMappingName,
MON_MAPPING_NAME_TEMPLATE_W,
lpwszInstanceName);

    if ((cchOutSize + 1) * sizeof(CHAR) > sizeof(wszMappingName))
    {
        dwStatus = ERROR_BUFFER_OVERFLOW;
        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
    }

(void) CharUpperW(wszMappingName);

// First try to open the mutex and get ownership.

lpgi->hMutex = OpenMutexW(
    MUTEX_ALL_ACCESS,
    FALSE,
    wszMutexName);
if (lpgi->hMutex)
    {
        dwStatus = LockSharedMemory(lpgi);
        if (dwStatus)
            goto cleanup;
    }

    // If the mutex didn't exist then create it and get ownership.

    else
{
        dwStatus = GetLastError();
        if (dwStatus != ERROR_FILE_NOT_FOUND)
        {
            EventLogMsg(
                GWPERF_OPEN_ERROR,
                0,
                1, dwStatus);
            goto cleanup;
        }

        lpgi->hMutex = CreateMutexW(
            psa,
            TRUE,
            wszMutexName);
        if (lpgi->hMutex == NULL)
        {
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_OPEN_ERROR,
                0,
                1, dwStatus);
            goto cleanup;
        }
        lpgi->cLockReference++;
}

    // First try to open the shared memory mapping.

lpgi->hFileMapping = OpenFileMappingW(
    FILE_MAP_WRITE,
    FALSE,
    wszMappingName);

    // If the shared memory mapping didn't exist then create it.

if (lpgi->hFileMapping == NULL)
{
dwStatus = GetLastError();
        if (dwStatus != ERROR_FILE_NOT_FOUND)
        {
            EventLogMsg(
                GWPERF_OPEN_ERROR,
                0,
                1, dwStatus);
    goto cleanup;
        }

    lpgi->hFileMapping = CreateFileMappingW(
    (HANDLE)0xFFFFFFFF, // create in paging file
    psa, // no security
    PAGE_READWRITE, // read/write access
    0, // size (high)
    sizeof(MONSHAREDMEMORY), // size(low)
    wszMappingName);// mapping object name

    if (lpgi->hFileMapping == NULL)
    {
    dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_OPEN_ERROR,
                0,
                1, dwStatus);
    goto cleanup;
    }
}

// Map a view of the shared memory file.

lpgi->lpmsmMemory = MapViewOfFile(
lpgi->hFileMapping,
FILE_MAP_WRITE,
0, 0, 0);
if (lpgi->lpmsmMemory == NULL)
{
        dwStatus = GetLastError();

        EventLogMsg(
            GWPERF_OPEN_ERROR,
            0,
            1, dwStatus);
        goto cleanup;
}

    // If the signature isn't correct then we have a version mismatch.
    // (0 probably means it hasn't been fully created yet).

    if (lpgi->lpmsmMemory->dwSignature != MON_SHARED_MEMORY_SIGNATURE &&
        lpgi->lpmsmMemory->dwSignature != 0)
    {
        EventLogMsgW(
            GWPERF_VERSION_MISMATCH,
            1, lpwszInstanceName,
            0);
        dwStatus = ERROR_DLL_INIT_FAILED;
        goto cleanup;
    }

    // If there is no valid data in the shared memory section then try
    // to read it from the registry where the gateway saved it the last
    // time it was started.  If there was an error then the shared memory
    // fDataIsValid flag is returned as FALSE.

    if (!lpgi->lpmsmMemory->fDataIsValid)
    {
        ReadSharedMemoryFromRegistry(lpgi);

        // Check the signature again because we just read it in.

        if (lpgi->lpmsmMemory->dwSignature != MON_SHARED_MEMORY_SIGNATURE &&
            lpgi->lpmsmMemory->dwSignature != 0)
        {
            EventLogMsgW(
                GWPERF_VERSION_MISMATCH,
                1, lpwszInstanceName,
                0);
            dwStatus = ERROR_DLL_INIT_FAILED;
            goto cleanup;
        }
    }

    // If there is valid data in the shared memory section then get the
    // object name title index from it.

    if (lpgi->lpmsmMemory->fDataIsValid)
    {
        lpPOT = (PPERF_OBJECT_TYPE) (((LPBYTE)(lpgi->lpmsmMemory))
            + lpgi->lpmsmMemory->ibHeaderOffset);
    iObjectName = lpPOT->ObjectNameTitleIndex;
    }

    // Unlock the mutex.

    dwStatus = UnlockSharedMemory(lpgi);
    if (dwStatus)
        goto cleanup;

    // Flag that the instance is open.

lpgi->fOpen = TRUE;

    // Return the object name title index.

    *lpiObjectName = iObjectName;

cleanup:
    // If we had an error then close the GINSTANCE and return the error code.

    if (dwStatus)
    {
        (void) CloseGInstance(lpgi);
    }

    return dwStatus;
}

//$--CloseGInstance-------------------------------------------------------------
//  Closes a GINSTANCE structure, closes handles, unmaps shared memory, etc.
//  (even a partially opened one).
// -----------------------------------------------------------------------------
static DWORD CloseGInstance(                // RETURNS: Win32 error code
    IN OUT  LPGINSTANCE lpgi)               // instance struct to fill in
{
DWORDdwStatus      = ERROR_SUCCESS;
DWORDdwStatusTemp  = ERROR_SUCCESS;
    BOOL                fItWorked           = TRUE;

    DEBUGPRIVATE("CloseGInstance()\n");

    // Free the allocated instance name.

    FREE(lpgi->lpwszName);

    // Release and close the shared memory mutex.

if (lpgi->hMutex)
{
        // Release the mutex lock.

        while (lpgi->cLockReference > 0)
        {
            dwStatusTemp = UnlockSharedMemory(lpgi);
            if (dwStatusTemp)
            {
                dwStatus = dwStatusTemp;
                break;
            }
        }

        // Close the handle to the mutex.

fItWorked = CloseHandle(lpgi->hMutex);
if (!fItWorked)
{
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_ERROR,
                0,
                1, dwStatus);
}
lpgi->hMutex = NULL;
}

    // Unmap the view of the shared memory.

if (lpgi->lpmsmMemory)
{
fItWorked = UnmapViewOfFile(lpgi->lpmsmMemory);
if (!fItWorked)
{
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_ERROR,
                0,
                1, dwStatus);
}
lpgi->lpmsmMemory = NULL;
}

    // Close the file mapping handle.

if (lpgi->hFileMapping)
{
fItWorked = CloseHandle(lpgi->hFileMapping);
if (!fItWorked)
{
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_ERROR,
                0,
                1, dwStatus);
}
lpgi->hFileMapping = NULL;
}

    // Zero out the GINSTANCE structure (which will also clear the fOpen flag).

    ZeroMemory(lpgi, sizeof(*lpgi));

    return dwStatus;
}

//$--CountMultiStrings----------------------------------------------------------
//  Returns the number of null terminated strings in a group of word strings.
//  A group of word strings consists of zero or more null-terminated word
//  strings followed by a null word.
// -----------------------------------------------------------------------------
static DWORD CountMultiStrings(             // RETURNS: Win32 error code
    IN  LPWSTR          grwsz,              // group of strings to count
    OUT LPULONG         pcwsz)              // number of strings found
{
    DWORD               dwStatus            = ERROR_SUCCESS;
LPWSTR    pwsz        = NULL;

    DEBUGPRIVATE("CountMultiStrings()\n");

    *pcwsz = 0;
    for (pwsz = grwsz; *pwsz; pwsz += (wcslen(pwsz) + 1))
{
        (*pcwsz)++;
    }

    return dwStatus;
}

//$--LockSharedMemory-----------------------------------------------------------
//  Locks the mutex associated with a shared memory block.
// -----------------------------------------------------------------------------
static DWORD LockSharedMemory(        // RETURNS: Win32 error code
IN  LPGINSTANCE     lpgi)        // gateway instance to lock
{
    DWORD               dwStatus             = ERROR_SUCCESS;
    DWORD               dwWaitStatus         = 0;

#ifdef VERBOSE_DEBUG
    DEBUGPRIVATE("LockSharedMemory()\n");
#endif

    // Lock the mutex.

dwWaitStatus = WaitForSingleObject(lpgi->hMutex, MON_MUTEX_TIMEOUT);
    switch (dwWaitStatus)
    {
        // Failed--log and return error.

        case WAIT_FAILED:
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_ERROR,
                0,
                1, dwStatus);
            goto cleanup;

        // Timed out--log and return error.

        case WAIT_TIMEOUT:
            dwStatus = ERROR_DLL_INIT_FAILED;
            EventLogMsgW(
                GWPERF_MUTEX_TIMEOUT,
                1, lpgi->lpwszName,
                0);
            goto cleanup;

        // Success--increment the reference count.

        default:
            ++lpgi->cLockReference;
            break;
    }

cleanup:
    return dwStatus;
}

//$--UnlockSharedMemory---------------------------------------------------------
//  Unlocks the mutex associated with a shared memory block.
// -----------------------------------------------------------------------------
static DWORD UnlockSharedMemory(        // RETURNS: Win32 error code
IN  LPGINSTANCE     lpgi)    // gateway instance to unlock
{
    DWORD               dwStatus            = ERROR_SUCCESS;
BOOL         fItWorked     = FALSE;

#ifdef VERBOSE_DEBUG
    DEBUGPRIVATE("UnlockSharedMemory()\n");
#endif

    // Don't unlock more than we lock.

    if (lpgi->cLockReference > 0)
    {
        // Unlock the mutex.

    fItWorked = ReleaseMutex(lpgi->hMutex);
    if (!fItWorked)
        {
            dwStatus = GetLastError();
            EventLogMsg(
                GWPERF_ERROR,
                0,
                1, dwStatus);
            goto cleanup;
        }

        // Decrement the reference count.

        --lpgi->cLockReference;
    }

cleanup:
    return dwStatus;
}

//$--fOurDataRequested----------------------------------------------------------
//  Returns TRUE if the value request string is requesting our data.
// -----------------------------------------------------------------------------
static BOOL fOurDataRequested(    // RETURNS: TRUE=yes, FALSE=no
    IN  LPGTYPE         lpgt,               // gateway type structure
    IN  LPWSTR          lpwszRequest)// perfomance data request string
{
BOOL                fReturn             = FALSE;
LPWSTR        lpwszOldPtr        = NULL;
LONG        iObjectName        = 0;

    DEBUGPRIVATE("fOurDataRequested()\n");

// If they requested "Global", "Costly" or an empty string then
// they want our data.

    if (*lpwszRequest == 0 ||
    !wcscmp(lpwszRequest, L"Global") ||
    !wcscmp(lpwszRequest, L"Costly"))
{
fReturn = TRUE;
        goto cleanup;
}

    // If they requested "Foreign" then they don't want our data.

if (!wcscmp(lpwszRequest, L"Foreign"))
{
fReturn = FALSE;
        goto cleanup;
}

    // Search the list for our number.

    fReturn = FALSE;
while (lpwszRequest != lpwszOldPtr)
{
lpwszOldPtr = lpwszRequest;

iObjectName = wcstol(lpwszRequest, &lpwszRequest, 10);

if (iObjectName == lpgt->iObjectName &&
lpwszRequest != lpwszOldPtr)
{
fReturn = TRUE;
goto cleanup;
}
}

cleanup:
return fReturn;
}

//$--ReadSharedMemoryFromRegistry-----------------------------------------------
//  Read the default shared memory data from the registry, where it was stored
//  the last time the gateway started.    If there was an error then the shared
//  memory fDataIsValid flag is returned as FALSE.
// -----------------------------------------------------------------------------
static VOID ReadSharedMemoryFromRegistry(// RETURNS: nothing
IN OUT  LPGINSTANCE lpgi)    // gateway instance to unlock
{
    DWORD               dwStatus            = ERROR_SUCCESS;
BOOL         fItWorked     = FALSE;
CHAR        szKeyName[MAX_PATH] = {0};
HKEY        hKey    = NULL;
DWORD        cch    = 0;
DWORD        dwType              = 0;
    DWORD               cbData              = 0;

    DEBUGPRIVATE("ReadSharedMemoryFromRegistry()\n");

    // Zero out the shared memory section first.

    ZeroMemory(lpgi->lpmsmMemory,  sizeof(*(lpgi->lpmsmMemory)));

    // Create the registry key name from the gateway instance name.

cch = sprintf(
szKeyName,
"SYSTEM\\CurrentControlSet\\Services\\%S\\Parameters",
lpgi->lpwszName);
ASSERTERROR(
(cch * sizeof(*szKeyName)) < sizeof(szKeyName),
"wsprintf overflow: szKeyName");

// Open the registry key.

dwStatus = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0L,
KEY_READ,
&hKey);
if (dwStatus != ERROR_SUCCESS)
    {
        EventLogMsg(
            GWPERF_OPEN_DEFAULT_COUNTERS,
            1, szKeyName,
            1, dwStatus);
goto cleanup;
    }

    // Read the value into the shared memory section.

    cbData = sizeof(*(lpgi->lpmsmMemory));

    dwStatus = RegQueryValueEx(
        hKey,
        "ObjectDefaultPerformanceData",
        NULL,
        &dwType,
        (LPBYTE) lpgi->lpmsmMemory,
        &cbData);

    // If it is not found then log a message telling the user to run the
    // gateway once first.

    if (dwStatus == ERROR_FILE_NOT_FOUND)
    {
        EventLogMsgW(
            GWPERF_NO_DEFAULT_COUNTERS,
            1, lpgi->lpwszName,
            0);
goto cleanup;
    }

    if (dwStatus != ERROR_SUCCESS)
    {
        EventLogMsg(
            GWPERF_READ_DEFAULT_COUNTERS,
            1, szKeyName,
            1, dwStatus);
goto cleanup;
    }

    if (dwType != REG_BINARY)
    {
        EventLogMsg(
            GWPERF_DEFAULT_COUNTERS_WRONG_TYPE,
            1, szKeyName,
            0);
        dwStatus = ERROR_FILE_NOT_FOUND;
goto cleanup;
    }

cleanup:
// Close the registry key (if it's open).

if (hKey)
{
dwStatus = RegCloseKey(hKey);
        if (dwStatus != ERROR_SUCCESS)
        {
            EventLogMsg(
                GWPERF_CLOSE_DEFAULT_COUNTERS,
                1, szKeyName,
                1, dwStatus);

            // We can ignore an error here.

            dwStatus = ERROR_SUCCESS;
        }
}

    // If it didn't work, then clear out the shared memory, mark it
    // as having no valid data and continue on.

    if (dwStatus)
    {
        ZeroMemory(lpgi->lpmsmMemory,  sizeof(*(lpgi->lpmsmMemory)));

        lpgi->lpmsmMemory->fDataIsValid = FALSE;
    }
}


GWREPORT.C

// --gwreport.c-----------------------------------------------------------------
//
//  Gateway report functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwreport.h"

#define SZ_MTS_ID               "/c=XX/a=X/p=X/l=X"

#define CENTRIES_FIRST  4
#define CENTRIES_NEXT   32

//$--HrCreateEmptyRecipList-----------------------------------------------------
//
//  Create an empty list of recipients.
//
// -----------------------------------------------------------------------------
static HRESULT HrCreateEmptyRecipList(      // RETURNS: return code
    OUT    LPADRLIST*    lppAdrList)        // address list
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrCreateEmptyRecipList()\n");

    hr = HrMAPICreateSizedAddressList(
        CENTRIES_FIRST,
        lppAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }
    else
    {
        (*lppAdrList)->cEntries = 0;
    }

cleanup:

    RETURN(hr);
}

//$--HrNDRCreateFailedRecipList-------------------------------------------------
//
//  Create an empty list of failed recipients.
//
// -----------------------------------------------------------------------------
HRESULT HrNDRCreateFailedRecipList(         // RETURNS: return code
    OUT    LPADRLIST*    lppAdrList)        // address list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrNDRCreateFailedRecipList()\n");

    hr = HrCreateEmptyRecipList(lppAdrList);

    RETURN(hr);
}

//$--HrNDRGetFailedRecipList----------------------------------------------------
//
//  Create a failed recipient entry.
//
// -----------------------------------------------------------------------------
HRESULT HrNDRGetFailedRecipList(            // RETURNS: return code
    IN     LPADRENTRY    lpAdrEntry,        // address entry
    IN     ULONG         cExtraProps,       // count of extra properties
    IN     LPSPropValue  lpExtraProps,      // extra properties
    OUT    ULONG*        lpcFailedProps,    // count of failed properties
    OUT    LPSPropValue* lppFailedProps)    // failed properties
{
    HRESULT      hr           = NOERROR;
    FILETIME     ftReportTime = {0};
    ULONG        i            = 0;
    SPropValue   rgProps[9]   = {0};
    ULONG        cValues      = 0;
    LPSPropValue lpProps      = NULL;
    ULONG        cNewValues   = 0;
    LPSPropValue lpNewProps   = NULL;
    LPSPropValue lpPropT      = NULL;

    DEBUGPUBLIC("HrNDRGetFailedRecipList()\n");

    //
    // Get the current time
    //

    if((cExtraProps > 0) && (lpExtraProps != NULL))
    {
        hr = HrMAPIAppendSPropValues(
            lpAdrEntry->cValues,
            lpAdrEntry->rgPropVals,
            cExtraProps,
            lpExtraProps,
            &cValues,
            &lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }
    else
    {
        cValues = lpAdrEntry->cValues;

        hr = ScDupPropset(
            lpAdrEntry->cValues,
            lpAdrEntry->rgPropVals,
    MAPIAllocateBuffer,
            &lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    //
    // Default any missing properties.
    //

    lpPropT = LpValFindProp(PR_NDR_REASON_CODE, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_NDR_REASON_CODE))
    {
        rgProps[i  ].ulPropTag = PR_NDR_REASON_CODE;
        rgProps[i++].Value.l   = 0;
    }

    lpPropT = LpValFindProp(PR_NDR_DIAG_CODE, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_NDR_DIAG_CODE))
    {
        rgProps[i  ].ulPropTag = PR_NDR_DIAG_CODE;
        rgProps[i++].Value.l   = -1;
    }

    lpPropT = LpValFindProp(PR_REPORT_TIME, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_REPORT_TIME))
    {
        GetSystemTimeAsFileTime( &ftReportTime);

        rgProps[i  ].ulPropTag = PR_REPORT_TIME;
        rgProps[i++].Value.ft  = ftReportTime;
    }

    lpPropT = LpValFindProp(PR_REPORT_TEXT, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_REPORT_TEXT))
    {
        rgProps[i  ].ulPropTag  = PR_REPORT_TEXT;
        rgProps[i++].Value.LPSZ = TEXT(" ");
    }

    lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_RECIPIENT_NUMBER))
    {
        rgProps[i  ].ulPropTag = PR_RECIPIENT_NUMBER;
        rgProps[i++].Value.l   = 1;
    }

    lpPropT = LpValFindProp(
        PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED;
        rgProps[i++].Value.b   = TRUE;
    }

    lpPropT = LpValFindProp(
        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    lpPropT = LpValFindProp(
        PR_READ_RECEIPT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_READ_RECEIPT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_READ_RECEIPT_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    lpPropT = LpValFindProp(
        PR_NON_RECEIPT_NOTIFICATION_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_NON_RECEIPT_NOTIFICATION_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_NON_RECEIPT_NOTIFICATION_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    if(i > 0)
    {
        hr = HrMAPIAppendSPropValues(
            cValues,
            lpProps,
            i,
            rgProps,
            &cNewValues,
            &lpNewProps);

        MAPIFREEBUFFER(lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        *lpcFailedProps = cNewValues;
        *lppFailedProps = lpNewProps;
    }
    else
    {
        *lpcFailedProps = cValues;
        *lppFailedProps = lpProps;
    }

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpProps);
        MAPIFREEBUFFER(lpNewProps);

        *lpcFailedProps = 0;
        *lppFailedProps = NULL;
    }

    RETURN(hr);
}

//$--HrAddToRecipList-----------------------------------------------------------
//
//  Add a recipient entry to the list of recipients.
//
// -----------------------------------------------------------------------------
static HRESULT HrAddToRecipList(            // RETURNS: return code
    IN     ULONG         cProps,            // count of properties
    IN     LPSPropValue  lpProps,           // properties
    IN OUT LPADRLIST*    lppAdrList)        // address list
{
    HRESULT    hr          = NOERROR;
    SCODE      sc          = 0;
    ULONG      cOldEntries = 0;
    ULONG      cEntries    = 0;
    ULONG      cBytes      = 0;
    ULONG      i           = 0;
    LPADRENTRY lpAdrEntry  = NULL;
    LPADRLIST  lpAdrList   = NULL;

    DEBUGPRIVATE("HrAddToRecipList()\n");

    cEntries = (*lppAdrList)->cEntries;

    cOldEntries = cEntries;

    //
    // Grow the address list if necessary.
    //

    if((cEntries == CENTRIES_FIRST) ||
       ((cEntries > CENTRIES_FIRST) &&
       (((cEntries - CENTRIES_FIRST) % CENTRIES_NEXT) == 0)))
    {
        cEntries += CENTRIES_NEXT;

        cBytes = CbNewADRLIST(cEntries);

        sc = MAPIAllocateBuffer(cBytes, (void **)&lpAdrList);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        ZeroMemory(lpAdrList, cBytes);

        lpAdrEntry = lpAdrList->aEntries;

        //
        // Copy existing address entries to the new address list.
        //

        for(i = 0; i < (*lppAdrList)->cEntries; i++)
        {
            lpAdrEntry[i].cValues =
                (*lppAdrList)->aEntries[i].cValues;
            lpAdrEntry[i].rgPropVals =
                (*lppAdrList)->aEntries[i].rgPropVals;
        }

        MAPIFREEBUFFER(*lppAdrList);

        *lppAdrList = lpAdrList;
    }

    //
    // Add an address entry to the address list.
    //

    i = cOldEntries;

    (*lppAdrList)->cEntries = ++i;

    i--;

    hr = HrMAPISetAddressList(
        i,
        cProps,
        lpProps,
        *lppAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrNDRAddToFailedRecipList--------------------------------------------------
//
//  Add a recipient entry to the list of failed recipients.
//
// -----------------------------------------------------------------------------
HRESULT HrNDRAddToFailedRecipList(          // RETURNS: return code
    IN     ULONG         cFailedProps,      // count of failed properties
    IN     LPSPropValue  lpFailedProps,     // failed properties
    IN OUT LPADRLIST*    lppAdrList)        // address list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrNDRAddToFailedRecipList()\n");

    hr = HrAddToRecipList(cFailedProps, lpFailedProps, lppAdrList);

    RETURN(hr);
}

//$--HrExtractProps-------------------------------------------------------------
//
//  Get NDR/DR properties from the original message.
//
// -----------------------------------------------------------------------------
static HRESULT HrExtractProps(              // RETURNS: return code
    IN     BOOL          IsNdr,             // report is an NDR, otherwise a DR
    IN     LPMESSAGE     lpMessage,         // message
    OUT    ULONG*        lpcProps,          // count of properties
    OUT    LPSPropValue* lppProps)          // properties
{
    HRESULT      hr                     = NOERROR;
    SCODE        sc                     = 0;
    ULONG        cBytes                 = 0;
FILETIME     ftSubmitTime           = {0};
    ULONG        i                      = 0;
    ULONG        cValues                = 0;
    LPSPropValue lpProps                = NULL;
    LPSTR       lpszMessageClass       = NULL;
    LPSTR       lpszReportMessageClass = NULL;
    LPSTR       lpsz                   = NULL;
    ULONG        cch                    = 0;

SizedSPropTagArray(18, rgMessagePropTags) =
{
18,
{
PR_MESSAGE_CLASS,
            PR_SUBJECT,
            PR_NORMALIZED_SUBJECT,
PR_SUBJECT_PREFIX,
PR_CLIENT_SUBMIT_TIME,
            PR_MTS_ID,
            PR_SENDER_NAME,
            PR_SENDER_ENTRYID,
            PR_DELETE_AFTER_SUBMIT,
            PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED,
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,
            PR_READ_RECEIPT_REQUESTED,
            PR_NON_RECEIPT_NOTIFICATION_REQUESTED,
            PR_CONTENT_RETURN_REQUESTED,
            PR_PRIORITY,
            PR_IMPORTANCE,
            PR_REPORT_NAME,
            PR_REPORT_ENTRYID
        }
    };

    DEBUGPRIVATE("HrExtractProps()\n");

    //
    // Get report properties from the message.
    //

hr = MAPICALL(lpMessage)->GetProps(
    lpMessage,
(LPSPropTagArray)&rgMessagePropTags,
fMapiUnicode,
&cValues,
&lpProps);

if(FAILED(hr))
{
        lpProps = NULL;

goto cleanup;
}

    //
    // Create report message class
    //

    if(lpProps[0].ulPropTag == PR_MESSAGE_CLASS)
    {
        lpszMessageClass = lpProps[0].Value.LPSZ;
    }

    if(lpszMessageClass == NULL)
    {
        cch = 0;
    }
    else
    {
        cch = lstrlen(lpszMessageClass);
    }

    if(cch == 0)
    {
        lpszMessageClass = TEXT("IPM.NOTE");
        cch = lstrlen(lpszMessageClass);
    }

    cch += lstrlen(TEXT("REPORT."));

    if(IsNdr == TRUE)
    {
        cch += lstrlen(TEXT(".NDR"));
    }
    else
    {
        cch += lstrlen(TEXT(".DR"));
    }

    cch++; // terminating NULL character

    cBytes = cch * sizeof(CHAR);

    sc = MAPIAllocateMore(cBytes, lpProps, &lpszReportMessageClass);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(lpszReportMessageClass, TEXT("REPORT."));
    lstrcat(lpszReportMessageClass, lpszMessageClass);
    if(IsNdr == TRUE)
    {
        lstrcat(lpszReportMessageClass, TEXT(".NDR"));
    }
    else
    {
        lstrcat(lpszReportMessageClass, TEXT(".DR"));
    }

    //
    // PR_MESSAGE_CLASS
    //

lpProps[0].ulPropTag  = PR_MESSAGE_CLASS;
    lpProps[0].Value.LPSZ = lpszReportMessageClass;

    //
    // PR_SUBJECT
    //

if(lpProps[1].ulPropTag != PR_SUBJECT)
    {
        cBytes = cbStrLen("");

        sc = MAPIAllocateMore(cBytes, lpProps, &lpsz);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        lstrcpy(lpsz, TEXT(""));

    lpProps[1].ulPropTag  = PR_SUBJECT;
    lpProps[1].Value.LPSZ = lpsz;
    }

    //
    // PR_NORMALIZED_SUBJECT
    //

if(lpProps[2].ulPropTag != PR_NORMALIZED_SUBJECT)
    {
        cBytes = cbStrLen("");

        sc = MAPIAllocateMore(cBytes, lpProps, &lpsz);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        lstrcpy(lpsz, TEXT(""));

    lpProps[2].ulPropTag  = PR_NORMALIZED_SUBJECT;
    lpProps[2].Value.LPSZ = lpsz;
    }

    //
    // PR_SUBJECT_PREFIX
    //

if(lpProps[3].ulPropTag != PR_SUBJECT_PREFIX)
    {
        cBytes = cbStrLen("");

        sc = MAPIAllocateMore(cBytes, lpProps, &lpsz);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        lstrcpy(lpsz, TEXT(""));

    lpProps[3].ulPropTag  = PR_SUBJECT_PREFIX;
    lpProps[3].Value.LPSZ = lpsz;
    }

    //
    // PR_CLIENT_SUBMIT_TIME
    //

if(lpProps[4].ulPropTag != PR_CLIENT_SUBMIT_TIME)
    {
    GetSystemTimeAsFileTime( &ftSubmitTime);

    lpProps[4].ulPropTag = PR_CLIENT_SUBMIT_TIME;
    lpProps[4].Value.ft  = ftSubmitTime;
    }

    //
    // PR_MTS_SUBJECT_ID
    //

if(lpProps[5].ulPropTag != PR_MTS_ID)
    {
        cBytes = cbStrLen(SZ_MTS_ID);

        sc = MAPIAllocateMore(cBytes, lpProps, &lpsz);

        if(FAILED(sc))
        {
            hr = HR_LOG(E_OUTOFMEMORY);
            goto cleanup;
        }

        lstrcpy(lpsz, TEXT(SZ_MTS_ID));

    lpProps[5].ulPropTag     = PR_MTS_ID;
    lpProps[5].Value.bin.cb  = cBytes;
    lpProps[5].Value.bin.lpb = (LPBYTE) lpsz;
    }

    lpProps[5].ulPropTag         = PR_MTS_SUBJECT_ID;

    if((lpProps[16].ulPropTag == PR_REPORT_NAME) &&
       (lpProps[17].ulPropTag == PR_REPORT_ENTRYID))
    {
        lpProps[6].ulPropTag     = PR_SENDER_NAME;
        lpProps[6].Value.LPSZ    = lpProps[16].Value.LPSZ;

        lpProps[7].ulPropTag     = PR_SENDER_ENTRYID;
        lpProps[7].Value.bin.cb  = lpProps[17].Value.bin.cb;
        lpProps[7].Value.bin.lpb = lpProps[17].Value.bin.lpb;
    }

    //
    // PR_REPORT_DESTINATION_NAME
    //

if(lpProps[6].ulPropTag != PR_SENDER_NAME)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpProps[6].ulPropTag         = PR_REPORT_DESTINATION_NAME;

    //
    // PR_REPORT_DESTINATION_ENTRYID
    //

if(lpProps[7].ulPropTag != PR_SENDER_ENTRYID)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpProps[7].ulPropTag         = PR_REPORT_DESTINATION_ENTRYID;

    //
    // PR_DELETE_AFTER_SUBMIT
    //

    lpProps[8].ulPropTag         = PR_DELETE_AFTER_SUBMIT;
    lpProps[8].Value.b           = TRUE;

    //
    // PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED
    //

if(lpProps[9].ulPropTag != PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED)
    {
        lpProps[9].ulPropTag = PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED;
        lpProps[9].Value.b   = TRUE;
    }

    //
    // PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED
    //

if(lpProps[10].ulPropTag != PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED)
    {
        lpProps[10].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
        lpProps[10].Value.b   = FALSE;
    }

    //
    // PR_READ_RECEIPT_REQUESTED
    //

if(lpProps[11].ulPropTag != PR_READ_RECEIPT_REQUESTED)
    {
        lpProps[11].ulPropTag = PR_READ_RECEIPT_REQUESTED;
        lpProps[11].Value.b   = FALSE;
    }

    //
    // PR_NON_RECEIPT_NOTIFICATION_REQUESTED
    //

if(lpProps[12].ulPropTag != PR_NON_RECEIPT_NOTIFICATION_REQUESTED)
    {
        lpProps[12].ulPropTag = PR_NON_RECEIPT_NOTIFICATION_REQUESTED;
        lpProps[12].Value.b   = FALSE;
    }

    //
    // PR_CONTENT_RETURN_REQUESTED
    //

if(lpProps[13].ulPropTag != PR_CONTENT_RETURN_REQUESTED)
    {
        lpProps[13].ulPropTag = PR_CONTENT_RETURN_REQUESTED;
        lpProps[13].Value.b   = TRUE;
    }

    //
    // PR_PRIORITY
    //

if(lpProps[14].ulPropTag != PR_PRIORITY)
    {
        lpProps[14].ulPropTag = PR_PRIORITY;
        lpProps[14].Value.l   = PRIO_NORMAL;
    }

    //
    // PR_IMPORTANCE
    //

if(lpProps[15].ulPropTag != PR_IMPORTANCE)
    {
        lpProps[15].ulPropTag = PR_IMPORTANCE;
        lpProps[15].Value.l   = IMPORTANCE_NORMAL;
    }

    *lpcProps = cValues - 2;
    *lppProps = lpProps;

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpProps);

        *lpcProps = 0;
        *lppProps = NULL;
    }

    RETURN(hr);
}

//$--HrNDRGetFailedProps----------------------------------------------------------
//
//  Get NDR properties from the original message.
//
// -----------------------------------------------------------------------------
HRESULT HrNDRGetFailedProps(                  // RETURNS: return code
    IN     LPMESSAGE     lpMessage,         // message
    OUT    ULONG*        lpcProps,          // count of properties
    OUT    LPSPropValue* lppProps)          // properties
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrNDRGetFailedProps()\n");

    hr = HrExtractProps(TRUE, lpMessage, lpcProps, lppProps);

    RETURN(hr);
}

//$--HrXDRCreate----------------------------------------------------------------
//
//  Create a non-delivery or delivery report.
//
// -----------------------------------------------------------------------------
static HRESULT HrXDRCreate(                 // RETURNS: return code
    IN     LPADRBOOK     lpAdrBook,         // address book
    IN     LPMAPIFOLDER  lpFolder,          // folder
    IN     LPMESSAGE     lpMessage,         // message
    IN     LPADRLIST     lpAdrList,         // address list
    IN     ULONG         cProps,            // count of properties
    IN     LPSPropValue  lpProps,           // properties
    OUT    LPMESSAGE*    lppReport)         // report
{
    HRESULT      hr              = NOERROR;
    LPMESSAGE    lpReport        = NULL;
    ULONG        ulAttachmentNum = 0;
    LPATTACH     lpAttach        = NULL;
    LPMESSAGE    lpContent       = NULL;
    BOOL         fReturnContent  = FALSE;
    SPropValue   rgProps[1]      = {0};
    LPSPropValue lpPropT         = NULL;

    DEBUGPRIVATE("HrXDRCreate()\n");

    *lppReport = NULL;

    //
    // Create a message
    //

    hr = MAPICALL(lpFolder)->CreateMessage(
        lpFolder,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &lpReport);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Return content if available and requested.
    //

    lpPropT = LpValFindProp(
        PR_CONTENT_RETURN_REQUESTED, cProps, lpProps);

    if(lpPropT == NULL)
    {
        fReturnContent = TRUE;
    }
    else if(lpPropT->ulPropTag != PR_CONTENT_RETURN_REQUESTED)
    {
        lpPropT->ulPropTag = PR_CONTENT_RETURN_REQUESTED;
        lpPropT->Value.b   = TRUE;
    }
    else
    {
        fReturnContent = lpPropT->Value.b;
    }

    if((lpMessage != NULL) && (fReturnContent == TRUE))
    {
        //
        // Create an embedded message
        //

        hr = MAPICALL(lpReport)->CreateAttach(
            lpReport,
            NULL,
            (ULONG)0,
            &ulAttachmentNum,
            &lpAttach);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        rgProps[0].ulPropTag = PR_ATTACH_METHOD;
        rgProps[0].Value.ul  = ATTACH_EMBEDDED_MSG;

        hr = MAPICALL(lpAttach)->SetProps(
            lpAttach,
            1,
            rgProps,
            NULL);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        hr = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_CREATE|MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            (LPUNKNOWN *)&lpContent);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    if(lpAdrList != NULL)
    {
        hr = MAPICALL(lpReport)->ModifyRecipients(
            lpReport,
            0,
            lpAdrList);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    //
    // Set envelope properties
    //

    if((cProps > 0) && (lpProps != NULL))
    {
        hr = MAPICALL(lpReport)->SetProps(
            lpReport,
            cProps,
            lpProps,
            NULL);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    if((lpMessage != NULL) && (fReturnContent == TRUE))
    {
        hr = MAPICALL(lpMessage)->CopyTo(
            lpMessage,
            0,
            NULL,
            NULL,
            0,
            NULL,
            (LPIID)&IID_IMessage,
            lpContent,
            0,
            NULL);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(hr == MAPI_W_ERRORS_RETURNED)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = MAPICALL(lpContent)->SaveChanges(lpContent, KEEP_OPEN_READWRITE);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        hr = MAPICALL(lpAttach)->SaveChanges(lpAttach, KEEP_OPEN_READWRITE);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    hr = MAPICALL(lpReport)->SaveChanges(lpReport, KEEP_OPEN_READWRITE);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    *lppReport = lpReport;

cleanup:

    ULRELEASE(lpContent);

    ULRELEASE(lpAttach);

    if(FAILED(hr))
    {
        ULRELEASE(lpReport);

        *lppReport = NULL;
    }

    RETURN(hr);
}

//$--HrNDRCreate----------------------------------------------------------------
//
//  Create a non-delivery report.
//
// -----------------------------------------------------------------------------
HRESULT HrNDRCreate(                        // RETURNS: return code
    IN     LPADRBOOK     lpAdrBook,         // address book
    IN     LPMAPIFOLDER  lpFolder,          // folder
    IN     LPMESSAGE     lpMessage,         // message
    IN     LPADRLIST     lpAdrList,         // address list
    IN     ULONG         cProps,            // count of properties
    IN     LPSPropValue  lpProps,           // properties
    OUT    LPMESSAGE*    lppReport)         // report
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrNDRCreate()\n");

    hr = HrXDRCreate(
        lpAdrBook,
        lpFolder,
        lpMessage,
        lpAdrList,
        cProps,
        lpProps,
        lppReport);

    RETURN(hr);
}

//$--HrDRCreateDeliveredRecipList-----------------------------------------------
//
//  Create an empty list of delivered recipients.
//
// -----------------------------------------------------------------------------
HRESULT HrDRCreateDeliveredRecipList(       // RETURNS: return code
    OUT    LPADRLIST*    lppAdrList)        // address list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrDRCreateDeliveredRecipList()\n");

    hr = HrCreateEmptyRecipList(lppAdrList);

    RETURN(hr);
}

//$--HrDRGetDeliveredRecip--------------------------------------------------
//
//  Create a delivered recipient entry.
//
// -----------------------------------------------------------------------------
HRESULT HrDRGetDeliveredRecip(          // RETURNS: return code
    IN     LPADRENTRY    lpAdrEntry,        // address entry
    IN     ULONG         cExtraProps,       // count of extra properties
    IN     LPSPropValue  lpExtraProps,      // extra properties
    OUT    ULONG*        lpcDeliveredProps, // count of delivered properties
    OUT    LPSPropValue* lppDeliveredProps) // delivered properties
{
    HRESULT      hr            = NOERROR;
    FILETIME     ftReportTime  = {0};
    FILETIME     ftDeliverTime = {0};
    ULONG        i             = 0;
    SPropValue   rgProps[9]    = {0};
    ULONG        cValues       = 0;
    LPSPropValue lpProps       = NULL;
    ULONG        cNewValues    = 0;
    LPSPropValue lpNewProps    = NULL;
    LPSPropValue lpPropT       = NULL;

    DEBUGPUBLIC("HrDRGetDeliveredRecip()\n");

    if((cExtraProps > 0) && (lpExtraProps != NULL))
    {
        hr = HrMAPIAppendSPropValues(
            lpAdrEntry->cValues,
            lpAdrEntry->rgPropVals,
            cExtraProps,
            lpExtraProps,
            &cValues,
            &lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }
    else
    {
        cValues = lpAdrEntry->cValues;

        hr = ScDupPropset(
            lpAdrEntry->cValues,
            lpAdrEntry->rgPropVals,
    MAPIAllocateBuffer,
            &lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }
    }

    //
    // Default any missing properties.
    //

    lpPropT = LpValFindProp(PR_REPORT_TIME, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_REPORT_TIME))
    {
        GetSystemTimeAsFileTime( &ftReportTime);

        rgProps[i  ].ulPropTag = PR_REPORT_TIME;
        rgProps[i++].Value.ft  = ftReportTime;
    }

    lpPropT = LpValFindProp(PR_REPORT_TEXT, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_REPORT_TEXT))
    {
        rgProps[i  ].ulPropTag  = PR_REPORT_TEXT;
        rgProps[i++].Value.LPSZ = TEXT(" ");
    }

    lpPropT = LpValFindProp(PR_DELIVER_TIME, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_DELIVER_TIME))
    {
        GetSystemTimeAsFileTime( &ftDeliverTime);

        rgProps[i  ].ulPropTag = PR_DELIVER_TIME;
        rgProps[i++].Value.ft  = ftDeliverTime;
    }

    lpPropT = LpValFindProp(PR_REPORT_TEXT, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_REPORT_TEXT))
    {
        rgProps[i  ].ulPropTag  = PR_REPORT_TEXT;
        rgProps[i++].Value.LPSZ = TEXT(" ");
    }

    lpPropT = LpValFindProp(PR_RECIPIENT_NUMBER, cValues, lpProps);

    if((lpPropT == NULL) || (lpPropT->ulPropTag != PR_RECIPIENT_NUMBER))
    {
        rgProps[i  ].ulPropTag = PR_RECIPIENT_NUMBER;
        rgProps[i++].Value.l   = 1;
    }

    lpPropT = LpValFindProp(
        PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED;
        rgProps[i++].Value.b   = TRUE;
    }

    lpPropT = LpValFindProp(
        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    lpPropT = LpValFindProp(
        PR_READ_RECEIPT_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_READ_RECEIPT_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_READ_RECEIPT_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    lpPropT = LpValFindProp(
        PR_NON_RECEIPT_NOTIFICATION_REQUESTED, cValues, lpProps);

    if((lpPropT == NULL) ||
       (lpPropT->ulPropTag != PR_NON_RECEIPT_NOTIFICATION_REQUESTED))
    {
        rgProps[i  ].ulPropTag = PR_NON_RECEIPT_NOTIFICATION_REQUESTED;
        rgProps[i++].Value.b   = FALSE;
    }

    if(i > 0)
    {
        hr = HrMAPIAppendSPropValues(
            cValues,
            lpProps,
            i,
            rgProps,
            &cNewValues,
            &lpNewProps);

        MAPIFREEBUFFER(lpProps);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        *lpcDeliveredProps = cNewValues;
        *lppDeliveredProps = lpNewProps;
    }
    else
    {
        *lpcDeliveredProps = cValues;
        *lppDeliveredProps = lpProps;
    }

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpProps);
        MAPIFREEBUFFER(lpNewProps);

        *lpcDeliveredProps = 0;
        *lppDeliveredProps = NULL;
    }

    RETURN(hr);
}

//$--HrDRAddToDeliveredRecipList------------------------------------------------
//
//  Add a recipient entry to the list of delivered recipients.
//
// -----------------------------------------------------------------------------
HRESULT HrDRAddToDeliveredRecipList(        // RETURNS: return code
    IN     ULONG         cDeliveredProps,   // count of delivered properties
    IN     LPSPropValue  lpDeliveredProps,  // delivered properties
    IN OUT LPADRLIST*    lppAdrList)        // address list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrDRAddToDeliveredRecipList()\n");

    hr = HrAddToRecipList(cDeliveredProps, lpDeliveredProps, lppAdrList);

    RETURN(hr);
}

//$--HrDRGetProps-----------------------------------------------------------
//
//  Get DR properties from the original message.
//
// -----------------------------------------------------------------------------
HRESULT HrDRGetProps(                   // RETURNS: return code
    IN     LPMESSAGE     lpMessage,         // message
    OUT    ULONG*        lpcProps,          // count of properties
    OUT    LPSPropValue* lppProps)          // properties
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrDRGetProps()\n");

    hr = HrExtractProps(FALSE, lpMessage, lpcProps, lppProps);

    RETURN(hr);
}

//$--HrDRCreate-----------------------------------------------------------------
//
//  Create a delivery report.
//
// -----------------------------------------------------------------------------
HRESULT HrDRCreate(                         // RETURNS: return code
    IN     LPADRBOOK     lpAdrBook,         // address book
    IN     LPMAPIFOLDER  lpFolder,          // folder
    IN     LPMESSAGE     lpMessage,         // message
    IN     LPADRLIST     lpAdrList,         // address list
    IN     ULONG         cProps,            // count of properties
    IN     LPSPropValue  lpProps,           // properties
    OUT    LPMESSAGE*    lppReport)         // report
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrDRCreate()\n");

    hr = HrXDRCreate(
        lpAdrBook,
        lpFolder,
        lpMessage,
        lpAdrList,
        cProps,
        lpProps,
        lppReport);

    RETURN(hr);
}


MBLOGON.C

// --MBLOGON.c------------------------------------------------------------------
//
//  Privileged Access to logon and off of any message store.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "edkguid.h"
#include "mblogon.chk"

//$--HrMailboxLogon------------------------------------------------------
// Logon to a mailbox.  Before calling this function do the following:
//  1) Create a profile that has Exchange administrator privileges.
//  2) Logon to Exchange using this profile.
//  3) Open the mailbox using the Message Store DN and Mailbox DN.
//
// This version of the function needs the server and mailbox names to be
// in the form of distinguished names.  They would look something like this:
//       /O=<Organization>/OU=<Site>/CN=Servers/CN=<ServerName>/CN=NULL
//       /O=<Organization>/OU=<Site>/CN=<Container>/CN=<MailboxName>
// where items in <brackets> would need to be set to appropriate values
//
// Note1: The message store DN is nearly identical to the server DN, except
// for the addition of a trailing '/CN=' part.  This part is required although
// its actual value is ignored.
//
// Note2: A NULL lpszMailboxDN indicates the public store should be opened.
// -----------------------------------------------------------------------------

HRESULT HrMailboxLogon(
    IN  LPMAPISESSION   lplhSession,      // MAPI session handle
    IN  LPMDB           lpMDB,            // open message store
    IN  LPSTR           lpszMsgStoreDN,   // desired message store DN
    IN  LPSTR           lpszMailboxDN,    // desired mailbox DN or NULL
    OUT LPMDB           *lppMailboxMDB)   // ptr to mailbox message store ptr
{
    HRESULT                 hr              = NOERROR;
    LPEXCHANGEMANAGESTORE   lpXManageStore  = NULL;
    LPMDB                   lpMailboxMDB    = NULL;
    ULONG                   ulFlags         = 0L;
    ULONG                   cbeid           = 0L;     // count of bytes in entry ID
    LPENTRYID               lpeid           = NULL;   // Entry ID of default store

    DEBUGPUBLIC( "HrMailboxLogon()");

    hr = CHK_HrMailboxLogon(
        lplhSession, lpMDB, lpszMsgStoreDN, lpszMailboxDN,
        lppMailboxMDB);
    if( FAILED( hr))
        RETURN( hr);

    // Cheap firewall against API abuse
    if( !lplhSession || !lpMDB || !lpszMsgStoreDN)
    {
        hr = HR_LOG( E_INVALIDARG);
        goto cleanup;
    }

    hr = MAPICALL( lpMDB)->QueryInterface( lpMDB,
        (REFIID) &IID_IExchangeManageStore,
        (LPVOID*) &lpXManageStore);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( lpXManageStore != NULL, "NULL lpXManageStore pointer");

    // Use NULL MailboxDN for opening the public store
    if( lpszMailboxDN == NULL || !*lpszMailboxDN)
    {
        ulFlags = OPENSTORE_PUBLIC;
    }

    hr = MAPICALL( lpXManageStore)->CreateStoreEntryID( lpXManageStore,
        lpszMsgStoreDN, lpszMailboxDN,
        ulFlags | OPENSTORE_USE_ADMIN_PRIVILEGE | OPENSTORE_TAKE_OWNERSHIP,
        &cbeid, &lpeid);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( lpeid != NULL, "NULL lpeid pointer");

    hr = MAPICALL( lplhSession)->OpenMsgStore(
        lplhSession, 0, cbeid, lpeid, NULL,
    MDB_NO_DIALOG |
        MDB_NO_MAIL |       // spooler not notified of our presence
        MDB_TEMPORARY |     // message store not added to MAPI profile
        MAPI_BEST_ACCESS,   // normally WRITE, but allow access to RO store
    &lpMailboxMDB);
    if( FAILED(hr))
    {
        if( hr == MAPI_E_NOT_FOUND)
            hr = HR_LOG( EDK_E_NOT_FOUND);
        else
            hr = HR_LOG( E_FAIL);

        goto cleanup;
    }

    if( hr == MAPI_W_ERRORS_RETURNED)
    {
        ULRELEASE( lpMailboxMDB);

        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    if( lppMailboxMDB)
        *lppMailboxMDB = lpMailboxMDB;

cleanup:
    MAPIFREEBUFFER( lpeid);
    ULRELEASE( lpXManageStore);

    RETURN( hr);
}

//$--HrMailboxLogoff------------------------------------------------------------
//  Logoff of a mailbox.
// -----------------------------------------------------------------------------

HRESULT HrMailboxLogoff(
    IN OUT LPMDB *lppMailboxMDB)          // ptr to mailbox message store ptr
{
    HRESULT hr      = NOERROR;
    ULONG   ulFlags = LOGOFF_NO_WAIT;

    DEBUGPUBLIC( "HrMailboxLogoff()");

    hr = CHK_HrMailboxLogoff( lppMailboxMDB);
    if( FAILED( hr))
        RETURN( hr);

    // Cheap firewall against API abuse
    if( lppMailboxMDB && *lppMailboxMDB)
    {
        hr = MAPICALL( *lppMailboxMDB)->StoreLogoff( *lppMailboxMDB,
            &ulFlags);
        if( FAILED( hr))
            goto cleanup;

        ULRELEASE( *lppMailboxMDB);
    }

cleanup:

    RETURN( hr);
}


CONVCLSS.CPP

// --convclss.cpp---------------------------------------------------------------
//
// Class that represents conversion class name/list info.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convclss.chk"

//$--CClassName::CClassName-----------------------------------------------------
//
//  DESCRIPTION: Initialize the conversion registry object constructor.
//
//  INPUT:  none
//
//  RETURNS:    nothing
//
//------------------------------------------------------------------------------
CClassName::CClassName()
{
    DEBUGPRIVATE("CClassName::CClassName()\n");

    m_pszClassName = NULL;
    m_pEntryPoint = NULL;
    m_pcnNext = NULL;
}

//$--CClassName::~CClassName----------------------------------------------------
//
// DESCRIPTION: CClassName destructor
//
// INPUT:   none
//
// RETURNS: nothing
//
//------------------------------------------------------------------------------
CClassName::~CClassName()
{
    DEBUGPRIVATE("CClassName::~CClassName()\n");

    if(m_pEntryPoint != NULL)
    {
        m_pEntryPoint->Release();
    }

    MAPIFREEBUFFER(m_pszClassName);
}

//$--CClassName::HrEDKSet----------------------------------------------------------
//
//  DESCRIPTION: Initialize the conversion registry object.
//
//  INPUT:  pszClassName    --  class name
//          pEntryPoint --  entry point information
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CClassName::HrEDKSet(               // RETURNS: HRESULT
    IN LPCWSTR pszClassName,                // class name.      (Assigned)
    IN CDllEntryPoint * pEntryPoint )       // entry point info (Copied)
                                            //  reference count increased internally.
{
    HRESULT  hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CClassName::HrEDKSet()\n");

    // check input parameters
    hr = CHK_CClassName_HrEDKSet(pszClassName, pEntryPoint);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    m_pEntryPoint = pEntryPoint;

    pEntryPoint->AddRef();

    hr = HrStrWToStrW(pszClassName, &m_pszClassName);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERTERROR(m_pszClassName != NULL, "Bad m_pszClassName");

cleanup:

    RETURN(hr);
}

//$--CClassName::EDKDump-----------------------------------------------------------
//
// DESCRIPTION: dump the class contents.
//
// INPUT:   none
//
// RETURNS: void
//
//------------------------------------------------------------------------------
void CClassName::EDKDump()
{
    DEBUGPRIVATE("CClassName::EDKDump()\n");

    // _tprintf(TEXT(" Class: %ls\n"), m_pszClassName );

    m_pEntryPoint->EDKDump();
}


CONVCWRP.CPP

// --convcwrp.cpp---------------------------------------------------------------
//
// C callable code that wraps the conversion engine class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convcwrp.chk"

// Globals

// global conversion DLL registry information cache pointer
CEDKConvReg *  pCConvReg   =   NULL;

//$--HrConvInitGlobals-------------------------------------------------
//
// DESCRIPTION: Called once by the gateway to initalize common data areas.
//
// INPUT:   none
//
// RETURNS: HRESULT --  NOERROR if successful
//                      E_OUTOFMEMORY if memory problems
//
//---------------------------------------------------------------------
HRESULT HrConvInitGlobals()
{
    HRESULT hr  =   NOERROR;

    DEBUGPUBLIC("HrConvInitGlobals()\n");

    // Create a global conversion DLL registry information cache.
    pCConvReg = new CEDKConvReg();

    if ( pCConvReg == NULL )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    // Initialize the registry information cache.
    hr = pCConvReg->HrEDKInitialize();

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--ConvUninitGlobals-------------------------------------------------
//
// DESCRIPTION: Called once by the gateway to de-initalize common data areas.
//
// INPUT:   none
//
// RETURNS: VOID
//
//---------------------------------------------------------------------
VOID ConvUninitGlobals()
{
    DEBUGPUBLIC("ConvUninitGlobals()\n");

    // Delete the global registry information cache.
    if ( pCConvReg )
    {
        // First, delete its common members (the DLL cache
        // and the list of class names).
        pCConvReg->EDKFree();

        // Now, delete the registry informtion cache itself.
        delete pCConvReg;
    }

    pCConvReg = NULL;

    return;

}

//$--HrConvInitInstance-----------------------------------------------
//
//  DESCRIPTION: Called to create a new instance of the conversion engine.  Each
//  instance of the conversion engine is single-threaded.  Multiple
//  instances can be used in separate threads.
//
//  INPUT:  hEventSource    --  event source handle
//
//  OUTPUT:  ppvConvInst --  conversion engine instance
//
//  RETURNS:    HRESULT --  NOERROR if no error,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if failure
//
//---------------------------------------------------------------------
HRESULT HrConvInitInstance(
    IN HANDLE hEventSource,         // event source handle
    OUT PVOID * ppvConvInst)        // new conversion engine instance
{
    HRESULT hr = NOERROR;           // return code
    CEDKConvEng * pceTmp = NULL;    // Temporary conversion engine pointer

    DEBUGPUBLIC("HrConvInitInstance()\n");

    // check input parameters
    hr = CHK_HrConvInitInstance(hEventSource, ppvConvInst);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    //
    //  Make a new instance.
    //

    pceTmp = new CEDKConvEng;

    if(pceTmp == NULL)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  Pass back the new instance.
    //

    (*(CEDKConvEng**)ppvConvInst) = pceTmp;

    //
    //  Attempt to initialize
    //

    hr = pceTmp->HrEDKInitialize(hEventSource);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        if(pceTmp != NULL)
        {
            delete pceTmp;
            pceTmp = NULL;
        }
    }

    RETURN(hr);
}

//$--HrConvUninitInstance----------------------------------------------
//
// DESCRIPTION: Called to dispose of a previously allocated conversion engine.
//
// INPUT:   pvConvInst  --  conversion engine instance to free.
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise.
//
//---------------------------------------------------------------------
HRESULT HrConvUninitInstance(
    IN PVOID pvConvInst)                    // handle to instance to free.
{
    HRESULT hr  =   NOERROR;            // return code
    CEDKConvEng * pceTmp    =   NULL;   // temporary conversion engine pointer

    DEBUGPUBLIC("HrConvUninitInstance()\n");

    // check input parameters
    hr = CHK_HrConvUninitInstance(pvConvInst);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    pceTmp = (CEDKConvEng *) pvConvInst;

    delete pceTmp;

    RETURN(hr);
}

//$--HrConvConvert----------------------------------------------------
//
// DESCRIPTION: called to initiate a conversion.
//
// INPUT:   pvConvInst  --  conversion engine instance
//          pEnv    --  conversion environment
//          pszContentClass --  class of source
//          pContentIn  --  source to be converted
//          pContentOut --  converted object
//
// OUTPUT:
//          pcrResult   --  result
//
// RETURNS: HRESULT --  NOERROR if no error,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
//---------------------------------------------------------------------
HRESULT HrConvConvert(
    IN PVOID pvConvInst,            // handle to instance of engine.
    IN PEDKCNVENV pEnv,             // environment of the convrsion
    IN LPCWSTR pszContentClass,     // class of source to be converted.
    IN PVOID pContentIn,            // source to be converted.
    IN PVOID pContentOut,           // converted object
    OUT EDKCNVRES *pcrResult)       // result.
{
    HRESULT hr  =   NOERROR;    // return code
    CEDKConvEng * pceTmp    =   NULL;   // temporary conversion engine pointer

    DEBUGPUBLIC("HrConvConvert()\n");

    // check input parameters
    hr = CHK_HrConvConvert(pvConvInst, pEnv, pszContentClass, pContentIn,
                              pContentOut, pcrResult);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    pceTmp = (CEDKConvEng *) pvConvInst;

    hr = pceTmp->HrEDKConvert(pEnv, pszContentClass, pContentIn, pContentOut,
                              *pcrResult);

    RETURN(hr);

}


CONVDLL.CPP

// --convdll.cpp----------------------------------------------------------------
//
// Conversion DLL code.
// Handles loading, unloading and using conversion DLLs.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convdll.chk"

// external declarations
extern CEDKConvReg *  pCConvReg;        // global conversion registry information pointer

//$--CEDKConvDll::CEDKConvDll---------------------------------------------------
//
// DESCRIPTION: CEDKConvDLL class constructor
//
// INPUT:   pep --  CDllEntryPoint class instance
//                  hEventSource    --  event source handle
//
// RETURNS: nothing
//
//------------------------------------------------------------------------------
CEDKConvDll::CEDKConvDll(
    IN CDllEntryPoint * pep,
    IN HANDLE hEventSource)
{
    DEBUGPRIVATE("CEDKConvDll::CEDKConvDll()\n");

    // check input parameters
    CHK_CEDKConvDll_CEDKConvDll(pep, hEventSource);

    m_pepEntry = pep;
    m_hEventSource = hEventSource;

    // initialize other class data members
    m_hDll = NULL;
    m_pDllVector = NULL;

}

//$--CEDKConvDll::~CEDKConvDll--------------------------------------------------
//
// DESCRIPTION: CEDKConvDLL class destructor
//
// INPUT:   none
//
// RETURNS: nothing
//
//------------------------------------------------------------------------------
CEDKConvDll::~CEDKConvDll()
{
    DEBUGPRIVATE("CEDKConvDLL::~CEDKConvDLL()\n");

    m_pepEntry = NULL;
}

//$--CEDKConvDll::HrEDKLoad--------------------------------------------------------
//
//  DESCRIPTION: Load the specified DLL, find the entry point and invoke the
//  initialization method.  If this method fails, it frees the
//  DLL and entry point.  If this method succeeds, rcUnLoad must
//  be called to release the library.
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if no error,
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvDll::HrEDKLoad()        // RETURNS:  HRESULT
{
    HRESULT         hr      =   NOERROR;    // return code
    PCONVDLLENTRYFN pfnTmp  =   NULL;       // DLL entry point

    DEBUGPRIVATE("CEDKConvDLL::HrEDKLoad()\n");

    // consistency checking
    ASSERTERROR(m_pepEntry != NULL, "NULL m_pepEntry parameter.");
    ASSERTERROR(pCConvReg != NULL, "NULL pCConvReg");

    //
    //  Find the library in our cache
    //
    hr = pCConvReg->HrGetDllHandle(
        m_pepEntry->pszDllName(),   // DLL name
        &m_hDll);                   // DLL instance pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    ASSERTERROR(m_hDll != NULL, "Bad m_hDll");

    //
    //  Find the entry point.
    //
    pfnTmp = (PCONVDLLENTRYFN)GetProcAddress(
            m_hDll,
            m_pepEntry->pszEntryPoint());

    if(pfnTmp != NULL)
    {
        //
        //  Get the vector table.
        //
        hr = pfnTmp(nDesiredConvDllVersion, &m_pDllVector);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

    else
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    if( FAILED(hr) )
    {
        m_pDllVector = NULL;
        m_hDll = NULL;
    }

    RETURN(hr);
}

//$--CEDKConvDll::HrEDKUnLoad------------------------------------------------------
//
//  DESCRIPTION: Invoke the DLL cleanup method and release the library.
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise
//
//------------------------------------------------------------------------------
HRESULT CEDKConvDll::HrEDKUnLoad()
{
    HRESULT hr  =   NOERROR;        // return code

    DEBUGPRIVATE("CEDKConvDLL::HrEDKUnLoad()\n");

    // consistency checking
    ASSERTERROR(m_pDllVector != NULL,"NULL m_pDllVector parameter");

    ASSERTERROR(m_hDll != NULL,"NULL m_hDll parameter");

    //
    //  Free the library.
    //

    FreeLibrary(m_hDll);

    m_pDllVector = NULL;
    m_hDll = NULL;

    RETURN(hr);

}

//$--CEDKConvDll::HrEDKQueryCapability---------------------------------------------
//
//  DESCRIPTION: Invoke the query capability method of the dll.
//
//  INPUT:  pEnv    --  environment for conversion
//          pszContentClass --  class to be converted
//          pConent --  pointer to the content
//
//  OUTPUT: fAmCandidate    --  TRUE if can be converted, FALSE otherwise.
//
//  RETURNS:    HRESULT --  NOERROR if overall function call succeeds,
//                          E_INVALIDARG if bad input
//
//------------------------------------------------------------------------------
HRESULT CEDKConvDll::HrEDKQueryCapability(  // RETURN - HRESULT
    IN PEDKCNVENV pEnv,             // environment for conversion.
    IN LPCWSTR pszContentClass,     // class to be converted.
    IN PVOID pContent,              // pointer to the content.
    OUT BOOL &fAmCandidate)         // result of the conversion.
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CEDKConvDLL::HrEDKQueryCapability()\n");

    // check input parameters
    hr = CHK_CEDKConvDll_HrEDKQueryCapability(pEnv, pszContentClass, pContent,
                                  fAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // consistency checking
    ASSERTERROR(m_pDllVector != NULL,"NULL m_pDllVector parameter");

    ASSERTERROR(m_hDll != NULL,"NULL m_hDll parameter");

    hr = m_pDllVector->pfnCnvQueryCapability(
            m_pepEntry->pszOptions(),
            pszContentClass,
            pContent,
            pEnv,
            &fAmCandidate);

    RETURN(hr);

}

//$--CEDKConvDll::HrEDKConvert-----------------------------------------------------
//
//  DESCRIPTION: Invoke the converter.
//
//  INPUT:  pEnv    --  environment
//          lpszContentClass    --  class of content to convert
//          pContentIn  --  source
//          pContentOut --  target of conversion
//
//  OUTPUT:
//          crResult    --  result of conversion
//
//  RETURNS:    HRESULT --  NOERROR if overall call succeeds,
//                          E_INVALIDARG if bad input.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvDll::HrEDKConvert(  // RETURNS: HRESULT
    IN PEDKCNVENV pEnv,             // environment
    IN LPCWSTR lpszContentClass,    // class of content to convert.
    IN PVOID pContentIn,            // source.
    IN PVOID pContentOut,           // target of conversion
    OUT EDKCNVRES & crResult)       // result of conversion
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CEDKConvDLL::HrEDKConvert()\n");

    // check input parameters
    hr = CHK_CEDKConvDll_HrEDKConvert(pEnv, lpszContentClass, pContentIn, pContentOut,
                          crResult);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // consistency checking
    ASSERTERROR(m_pDllVector != NULL,"NULL m_pDllVector parameter");

    ASSERTERROR(m_hDll != NULL,"NULL m_hDll parameter");

    hr = m_pDllVector->pfnCnvConvert(
            m_pepEntry->pszOptions(),
            lpszContentClass,
            pContentIn,
            pContentOut,
            pEnv,
            &crResult);

    RETURN(hr);

}


CONVDLLC.CPP

// --convdllc.cpp--------------------------------------------------------------
//
// Conversion DLL cache source code.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
// ----------------------------------------------------------------------------

#include "convincl.h"
#include "convdllc.chk"

//$--CDllCache::CDllCache()----------------------------------------------------
//
// DESCRIPTION: constructor
//
// INPUT: none
//
// RETURNS; nothing
//
//-----------------------------------------------------------------------------
CDllCache::CDllCache()
{
    DEBUGPRIVATE("CDllCache::CDllCache()\n");

    ZeroMemory(m_rgSDllCache, sizeof(m_rgSDllCache));
}

//$--CDllCache::~CDllCache()----------------------------------------------------
//
// DESCRIPTION: destructor
//
// INPUT: none
//
// RETURNS; nothing
//
//-----------------------------------------------------------------------------
CDllCache::~CDllCache()
{
    UINT            iDll    =   0;      // index into DLL cache
    SDllCache *     pDll    =   NULL;   // current DLL pointer

    DEBUGPRIVATE("CDllCache::~CDllCache()\n");

    // Unload any loaded DLLs
    for ( iDll = 0; iDll < nCachedDlls; iDll++ )
    {
        // get current DLL pointer
        pDll = m_rgSDllCache[iDll];

        // See if its valid
        if ( pDll != NULL )
        {
            // unload the DLL
            ASSERTERROR(pDll->hInst != NULL, "Bad pDll->hInst");
            (VOID)FreeLibrary(pDll->hInst);

            // Free the DLL cache entry
            delete pDll;

            pDll = NULL;    // null out pointer
        }

    }   // end for each cached DLL

}

//$--CDllCache::HrAdd----------------------------------------------------------
//
// DESCRIPTION: Loads DLL and adds it to the cache.
//
// INPUT:       lpwszDllName  --  DLL name
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_OUTOFMEMORY if memory problems.
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CDllCache::HrAdd(
        IN LPWSTR lpwszDllName)     // DLL to add
{
    HRESULT         hr          =   NOERROR;
    SDllCache *     pDllCur     =   NULL;   // current cached DLL pointer
    UINT            iDll        =   0;      // index into DLL cache
    BOOL            fLoaded     =   FALSE;  // TRUE if DLL loaded

    DEBUGPRIVATE("CDllCache::HrAdd()\n");

    // check input parameters
    hr = CHK_CDllCache_HrAdd(lpwszDllName);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Find next empty space in the DLL cache
    for ( iDll = 0; iDll < nCachedDlls; iDll++ )
    {
        pDllCur = m_rgSDllCache[iDll];

        if ( pDllCur == NULL )
        {
            // found an place for this DLL.
            pDllCur = new SDllCache;    // allocate new cache entry

            if ( pDllCur == NULL )
            {
                hr = HR_LOG(E_OUTOFMEMORY);

                goto cleanup;
            }

            // fill in DLL entry information.
            lstrcpyW(pDllCur->lpwszName, lpwszDllName);  // copy DLL name

            // Load DLL
            pDllCur->hInst = LoadLibraryW(lpwszDllName);

            if ( pDllCur->hInst == NULL )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            // Save DLL entry information in the cache.
            m_rgSDllCache[iDll] = pDllCur;

            fLoaded = TRUE; // DLL is now loaded

            break;

        }   // end if found space

        // Otherwise, make sure that this DLL isn't already loaded.
        else if ( lstrcmpiW(pDllCur->lpwszName, lpwszDllName) == 0 )
        {
            // Dll already here.
            ASSERTERROR(pDllCur->hInst != NULL, "Bad pDllCur->hInst");

            fLoaded = TRUE;

            // We are done!
            break;
        }
    }   // end for

    // handle case where can't load DLL
    if ( fLoaded == FALSE )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--CDllCache::HrFind------------------------------------------------
//
// DESCRIPTION: Finds instance handle of DLL in cache.
//
// INPUT:       lpwszDllName  --  DLL name
//
// OUTPUT:      phInst  --  pointer to DLL instance handle
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT CDllCache::HrFind(
        IN LPWSTR lpwszDllName,     // DLL to find
        OUT HINSTANCE * phInst)     // pointer to DLL instance handle
{
    HRESULT         hr          =   NOERROR;
    SDllCache *     pDllCur     =   NULL;   // current cached DLL pointer
    UINT            iDll        =   0;      // index into DLL cache
    BOOL            fFound      =   FALSE;  // TRUE if DLL is currently cached

    DEBUGPRIVATE("CDllCache::HrFind()\n");

    // check input parameters
    hr = CHK_CDllCache_HrFind(lpwszDllName, phInst);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // intialize output parameter
    *phInst = NULL;

    // See if this DLL is already loaded and determine the index
    // of least used DLL in cache
    for ( iDll = 0; iDll < nCachedDlls; iDll++ )
    {
        // get pointer to current DLL.
        pDllCur = m_rgSDllCache[iDll];

        // See if it is a valid pointer
        if ( pDllCur == NULL )
        {
            // No more cached DLLs
            break;
        }

        // See if this DLL is already loaded
        if ( lstrcmpiW(lpwszDllName, pDllCur->lpwszName) == 0 )
        {
            // found DLL
            fFound = TRUE;

            // Succeeded.
            // Set our output variable
            *phInst = pDllCur->hInst;

            // done
            break;
        }
    }   // end for each DLL in cache

    // See if DLL was found
    if ( fFound == FALSE )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;

    }   // end if DLL not loaded.

cleanup:

    RETURN(hr);

}


CONVDLLE.CPP

// --convdlle.cpp---------------------------------------------------------------
//
// Defines an entry point to a conversion DLL.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convdlle.chk"

//$--CDllEntryPoint::CDllEntryPoint---------------------------------------------
//
//  DESCRIPTION: CDllEntryPoint class constructor
//
//  INPUT:  none
//
//  RETURNS:    nothing
//
//------------------------------------------------------------------------------
CDllEntryPoint::CDllEntryPoint()
{
    DEBUGPRIVATE("CDllEntryPoint::CDllEntryPoint()\n");

    m_nRefs = 1;

    m_pszDllName    = NULL;
    m_pszEntryPoint = NULL;
    m_pszGwPoint    = NULL;
    m_pszOptions    = NULL;
}

//$--CDllEntryPoint::~CDllEntryPoint--------------------------------------------
//
//  DESCRIPTION: CDllEntryPoint class destructor.
//
//  INPUT:  none
//
//  RETURNS:    nothing
//
//------------------------------------------------------------------------------
CDllEntryPoint::~CDllEntryPoint()
{
    DEBUGPRIVATE("CDllEntryPoint::~CDllEntryPoint()\n");

    // consistency checking
    ASSERTERROR(m_nRefs == 0,"ZERO m_nRefs variable");

    EDKFree();
}

//$--CDllEntryPoint::Release----------------------------------------------------
//
// DESCRIPTION: Decrements CDllEntryPoint instance reference count.  Destroys
//              object when reference count goes to zero.
//
// INPUT:   none
//
// RETURNS: nothing
//
//------------------------------------------------------------------------------
void CDllEntryPoint::Release()
{
    DEBUGPRIVATE("CDllEntryPoint::Release()");

    if(--m_nRefs <= 0)
        delete this;
}

//$--CDllEntryPoint::HrEDKSet------------------------------------------------------
//
//  DESCRIPTION: Initialize a conversion entry point values
//
//  INPUT:  pszDllName  --  Dll name
//          pszEntryPoint   --  entry point function name
//          pszGwPoint  --
//          pszOptions  --  options
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//
//------------------------------------------------------------------------------
HRESULT CDllEntryPoint::HrEDKSet(   // RETURNS: HRESULT
    IN LPCWSTR pszDllName,          // copied
    IN LPCWSTR pszEntryPoint,       // entry point function name
    IN LPCWSTR pszGwPoint,
    IN LPCWSTR pszOptions)          // options
{
    HRESULT hr  =   NOERROR;        // return code

    DEBUGPRIVATE("CDllEntryPoint::HrEDKSet()\n");

    // check input parameters
    hr = CHK_CDllEntryPoint_HrEDKSet(pszDllName, pszEntryPoint, pszGwPoint, pszOptions);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    hr = HrStrWToStrW(pszDllName, &m_pszDllName);

    if ( FAILED(hr) )
    {
        EDKFree();

        goto cleanup;
    }

    // Remember m_pszEntryPoint is ANSI string

    hr = HrStrWToStrA(pszEntryPoint, &m_pszEntryPoint);

    if ( FAILED(hr) )
    {
        EDKFree();

        goto cleanup;
    }

    hr = HrStrWToStrW(pszGwPoint, &m_pszGwPoint);

    if ( FAILED(hr) )
    {
        EDKFree();

        goto cleanup;
    }

    hr = HrStrWToStrW(pszOptions, &m_pszOptions);

    if ( FAILED(hr) )
    {
        EDKFree();

        goto cleanup;
    }

    ASSERTERROR(m_pszDllName != NULL, "Bad m_pszDllName");
    ASSERTERROR(m_pszEntryPoint != NULL, "Bad m_pszEntryPoint");
    ASSERTERROR(m_pszGwPoint != NULL, "Bad m_pszGwPoint");

cleanup:

    RETURN(hr);

}

//$--CDllEntryPoint::EDKFree-------------------------------------------------------
//
// DESCRIPTION: Common dynamic memory free.
//
// INPUT:   none
//
// RETURNS: void
//
//------------------------------------------------------------------------------
void CDllEntryPoint::EDKFree() // RETURNS: void
{
    DEBUGPRIVATE("CDllEntryPoint::EDKFree()\n");

    MAPIFREEBUFFER(m_pszDllName);
    MAPIFREEBUFFER(m_pszEntryPoint);
    MAPIFREEBUFFER(m_pszGwPoint);
    MAPIFREEBUFFER(m_pszOptions);
}

//$--CDllEntryPoint::EDKDump-------------------------------------------------------
//
// DESCRIPTION: dump class contents.
//
// INPUT:   none
//
// RETURNS: void
//
//------------------------------------------------------------------------------
void CDllEntryPoint::EDKDump() // RETURNS: void
{
    // _tprintf(TEXT("  Dll: %ls\n  Ept: %hs\n  Gtw: %ls\n  Opt: %ls\n"),
    //              m_pszDllName, m_pszEntryPoint, m_pszGwPoint, m_pszOptions );
}


CONVENGN.CPP

// --convengn.cpp---------------------------------------------------------------
//
// Conversion engine code.  This file defines the CEDKConvEng class which
// defines the top level behavior of the conversion engine.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convengn.chk"

//$--CEDKConvEng::CEDKConvEng----------------------------------------------------
//
//  DESCRIPTION: constructor for CEDKConvEng.  Required because CEDKConvEng has contained
//  objects with CTORs.
//
//  INPUT: none
//
//  RETURNS:    nothing
//
//------------------------------------------------------------------------------
CEDKConvEng::CEDKConvEng()
{
    DEBUGPRIVATE("CEDKConvEng::CEDKConvEng()\n");
}

//$--CEDKConvEng::HrEDKInitialize---------------------------------------------------
//
//  DESCRIPITON: Initialize the conversion engine.
//
//  INPUT:  hEventSource    --  event source handle
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvEng::HrEDKInitialize(   // RETURNS: HRESULT
        IN HANDLE hEventSource)            // event source handle
{
    HRESULT hr  =    NOERROR;

    DEBUGPRIVATE("CEDKConvEng::HrEDKInitialize()\n");

    // check input parameters
    hr = CHK_CEDKConvEng_HrEDKInitialize(hEventSource);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    m_hEventSource = hEventSource;

    hr = m_crRegistry.HrEDKInitialize();

    RETURN(hr);

}

//$--CEDKConvEng::HrEDKConvert------------------------------------------------------
//
//  DESCRIPTION: Search for a suitable conversion.  If a suitable conversion is
//  found, invoke it.
//
//  INPUT:  pEnv    --  environment of the conversion
//          pszContentClass --  class of source to be converted
//          pContentIn  --  source to be converted
//          pContentOut --  converted object
//
//  OUTPUT:
//          crResult    --  result
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvEng::HrEDKConvert(          // RETURNS:  HRESULT
    IN PEDKCNVENV pEnv,             // environment of the conversion
    IN LPCWSTR pszContentClass,     // class of source to be converted.
    IN PVOID pContentIn,            // source to be converted.
    IN PVOID pContentOut,           // converted object
    OUT EDKCNVRES & crResult)       // result.
{
    HRESULT hr  =   NOERROR;        // return code
    BOOL bFoundAtLeastOneCandidate = FALSE;
    LPCWSTR pszConversionPoint = NULL;  //  conversion point wide string

    DEBUGPRIVATE("CEDKConvEng::HrEDKConvert()\n");

    // check input parameters
    hr = CHK_CEDKConvEng_HrEDKConvert(pEnv, pszContentClass, pContentIn, pContentOut,
                          crResult);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    m_bFoundAtLeastOneAccept = FALSE;

    //
    //  Find all candidate converters
    //

    //
    //  start search.
    //

    crResult = GCR_NO_CANDIDATE;

    if(pEnv != NULL)
    {
        pszConversionPoint = pEnv->pszConversionPoint;
    }

    hr = m_crRegistry.HrEDKSearchOpen(pszConversionPoint, pszContentClass, m_pepConverter);

    if( FAILED(hr) )
    {
        goto cleanup;
    }

    while( TRUE )
    {
        // Find next candidate conversion DLL.
        hr = m_crRegistry.HrEDKSearchNext();

        if ( FAILED(hr) )
        {
            // no more candidate DLLs.
            // break;
            break;
        }

        crResult = GCR_CANNOT_CONVERT;

        bFoundAtLeastOneCandidate = TRUE;

        //
        //  Display some debug info.
        //
        #ifdef DEBUG
            // _tprintf( TEXT(" Attempting converter for class %ls\n"), pszContentClass );
            m_pepConverter->EDKDump();
        #endif

        //
        // Attempt the conversion.
        //

        hr = HrEDKAttemptConversion(
            pEnv,
            pszContentClass,
            pszConversionPoint,
            pContentIn,
            pContentOut,
            crResult);

        if(FAILED(hr))       // catastrophic error.
        {
            goto cleanup;
        }

        if(crResult == GCR_OK )    // conversion happened.
        {
            goto cleanup;
        }

    }   // end while

    //
    //  Detect and report the case when no candidates found.
    //

    if(!bFoundAtLeastOneCandidate)
    {
        EventLogMsgW(
            MESGXLAT_CNV_NO_CANDIDATE,       // event identfier
            2,                          // # of strings
            pszConversionPoint,         // string 1
            pszContentClass,
            0);                         // # of error codes
    }

    //
    //  Detect and report the case when candidates found but won't accept.
    //

    if(bFoundAtLeastOneCandidate && !m_bFoundAtLeastOneAccept)
    {
        WCHAR   szRcTmp[(sizeof(HRESULT)+1)*2] = {0};

        swprintf(szRcTmp, L"%08lx", hr);

        EventLogMsgW(
            MESGXLAT_CNV_NO_ACCEPT,  // event identifier
            4,                  // # of strings
            m_pepConverter->pszDllName(),   // string 1
            pszConversionPoint, // string 2
            pszContentClass,
            szRcTmp,
            0);                 // # of error codes
    }

cleanup:

    m_crRegistry.EDKSearchClose();

    if(hr == EDK_E_END_OF_FILE)       // just couldn't find a converter.  Only returned by Find/Next
    {
        crResult = GCR_NO_CANDIDATE;

        hr = HR_LOG(NOERROR);
    }

    RETURN(hr);

}

//$--CEDKConvEng::HrEDKAttemptConversion--------------------------------------------
//
//  DESCRIPTION: Attempt a particular conversion.
//
//  INPUT:  pEnv    --  environment of the conversion
//          pszContentClass --  class of source to be converted
//          pszConversionPoint  --  where the conversion happened
//          pContentIn  --  source to be converted
//          pContentOut --  converted object
//
//  OUTPUT:
//          crResult    --  result
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvEng::HrEDKAttemptConversion(
    IN PEDKCNVENV pEnv,             // environment of the convrsion
    IN LPCWSTR pszContentClass,     // class of source to be converted.
    IN LPCWSTR pszConversionPoint,  // where the conversion happened.
    IN PVOID pContentIn,            // source to be converted.
    IN PVOID pContentOut,           // converted object
    OUT EDKCNVRES & crResult)       // result.
{
    HRESULT hr  =   NOERROR;        // return code
    CEDKConvDll cDll(m_pepConverter, m_hEventSource);   // conversion DLL

    DEBUGPRIVATE("CEDKConvEng::HrEDKAttemptConversion()\n");

    // check input parameters
    hr = CHK_CEDKConvEng_HrEDKAttemptConversion(pEnv, pszContentClass, pszConversionPoint,
                                    pContentIn, pContentOut, crResult);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    //
    //  Make a connection to the DLL.
    //
    hr = cDll.HrEDKLoad();

    if(SUCCEEDED(hr))
    {
        BOOL bAmCandidate = FALSE;

        //
        //  Can this DLL handle the message class?
        //
        hr = cDll.HrEDKQueryCapability(pEnv, pszContentClass, pContentIn, bAmCandidate);

        if(SUCCEEDED(hr) && bAmCandidate)
        {
            m_bFoundAtLeastOneAccept = TRUE;

            //
            //  Yes, try to perform the conversion.
            //

            hr = cDll.HrEDKConvert(
                pEnv, pszContentClass, pContentIn, pContentOut, crResult);

            if(FAILED(hr))
            {
                crResult = GCR_CONVERSION_FAILED;

                //
                //  Report conversion failure
                //

                {
                    WCHAR   szRcTmp[(sizeof(HRESULT)+1)*2] = {0};

                    swprintf(szRcTmp, L"%08lx", hr);

                    EventLogMsgW(
                        MESGXLAT_CNV_CONVERT_FAIL,   // event identifier
                        4,                      // # of strings
                        m_pepConverter->pszDllName(),   // string 1
                        pszConversionPoint,     // string 2
                        pszContentClass,
                        szRcTmp,
                        0);                     // # of error codes
                }

            }
        }
        else
        {
            if(FAILED(hr))
            {
                //
                //  Report query failure
                //

                {
                    WCHAR   szRcTmp[(sizeof(HRESULT)+1)*2] = {0};

                    swprintf(szRcTmp, L"%08lx", hr);

                    EventLogMsgW(
                        MESGXLAT_CNV_QUERY_FAIL,         // event identifier
                        4,                          // # of strings
                        m_pepConverter->pszDllName(),   // string 1
                        pszConversionPoint,         // string 2
                        pszContentClass,
                        szRcTmp,
                        0);                         // # of error codes
                }

            }
        }
    }
    else
    {
        crResult = GCR_CANNOT_LOAD;
    }

    // Don't unload DLL.  The DLL cache takes care of this,
    // when appropriate.

    RETURN(hr);

}

//$--CEDKConvEng::HrEDKUninitialize-------------------------------------------------
//
//  DESCRIPTION: Uninitialize a conversion engine.
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT CEDKConvEng::HrEDKUninitialize()
{
    DEBUGPRIVATE("CEDKConvEng::HrEDKUninitialize()\n");

    RETURN(NOERROR);
}


CONVREG.CPP

// --convreg.cpp----------------------------------------------------------------
//
// Conversion registry code.
// Locates converters that are stored in DLLs from info stored in the
// registry.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convreg.chk"

//
//  String constants used to find registry keys.
//
static const LPWSTR pszMAPIConvKey = L"SOFTWARE\\Classes\\MAPI Conversions";

//
//  Conversion KEY values.
//
static const LPWSTR pszClassesKey =  L"Classes";
static const LPWSTR pszPointKey =    L"Point";
static const LPWSTR pszOptionsKey =  L"Options";

//
// limiting constants.
//
static const int cMaxKeyNameSize = 100;                    // maximum conversion key size.

// Static variables shared by all instances
HKEY CEDKConvReg::ms_hkConversions             = NULL;     // handle to conversion key.
CClassName * CEDKConvReg::ms_pcnClasses        = NULL;     // common cache of conversion info
CDllCache * CEDKConvReg::ms_pDllCache          = NULL;     // common DLL cache

//$--CEDKConvReg::CEDKConvReg------------------------------------------
//
//  DESCRIPTION:     CEDKConvReg class constructor
//
//  INPUT:  none
//
//  RETURNS:    nothing
//
//---------------------------------------------------------------------
CEDKConvReg::CEDKConvReg()
{
    DEBUGPRIVATE("CEDKConvReg::CEDKConvReg()\n");
}

//$--CEDKConvReg::~CEDKConvReg-----------------------------------------
//
//  DESCRIPTION: CEDKConvReg class destructor
//
//  INPUT:   none
//
//  RETURNS:    nothing
//
//---------------------------------------------------------------------
CEDKConvReg::~CEDKConvReg()
{
    DEBUGPRIVATE("CEDKConvReg::~CEDKConvReg()\n");
}

//$--CEDKConvReg::HrEDKInitialize-----------------------------------------
//
//  DESCRIPTION: Perform loading part of Conversion registry initialization.
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise
//
//---------------------------------------------------------------------
HRESULT CEDKConvReg::HrEDKInitialize()
{
    HRESULT hr  =   NOERROR;    //  return code
    LONG lRet = ERROR_SUCCESS;  // win32 return code

    DEBUGPRIVATE("CEDKConvReg::HrEDKInitialize()\n");

    //
    //  Skip initialization if it has already been done.
    //
    if(ms_pcnClasses != NULL)
    {
        goto cleanup;
    }

    // consistency checking
    ASSERTERROR(ms_hkConversions == NULL,"NULL ms_hkConversions variable");

    // Create the DLL cache.
    ms_pDllCache = new CDllCache;

    if ( ms_pDllCache == NULL )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    //
    //  Open key that contains all conversions.
    //
    lRet = RegOpenKeyExW(
        HKEY_LOCAL_MACHINE,
        pszMAPIConvKey,
        0,
        KEY_READ,
        &ms_hkConversions);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  Parse the contented keys and values.
    //
    hr = HrEDKParseConfiguration();

cleanup:

    RETURN(hr);

}

//$--CEDKConvReg::HrEDKParseConfiguration---------------------------------
//
//  DESCRIPTION: Parse the list of all conversions from the registry conversion keys
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if no error successful,
//                          EDK_E_END_OF_FILE if syntax error
//                          E_FAIL otherwise
//
//---------------------------------------------------------------------
HRESULT CEDKConvReg::HrEDKParseConfiguration()  // RETURNS: HRESULT
{
    HRESULT hr        = NOERROR;    // return code
    LONG lRet         = 0;
    HKEY hkDll        = NULL;
    HKEY hkEntryPoint = NULL;

    DEBUGPRIVATE("CEDKConvReg::HrEDKParseConfiguration()\n");

    // consistency checking
    ASSERTERROR(ms_hkConversions != NULL,"NULL ms_hkConversions parameter");

    ASSERTERROR(ms_pcnClasses == NULL,"NULL ms_pcnClasses parameter");

    m_bSyntaxError = FALSE;

    //
    // Enumerate all conversion DLLs.  These are sub-keys of hkConversions
    // At the same time, load each DLL found and add to the DLL cache.
    //

    UINT    iDll    =   0;  // DLL index

    for(iDll = 0;;iDll++)
    {
        FILETIME ftDummy                  = {0};
        WCHAR    szDll[cMaxKeyNameSize+1] = {0};
        DWORD    dwDummy                  = cMaxKeyNameSize;

        // DLL loop bounds
        ASSERTERROR(iDll >= 0 && iDll < 1024,"iDll out of bounds");

        //
        //  Get the next key
        //
        lRet = RegEnumKeyExW(
            ms_hkConversions, iDll, szDll, &dwDummy, NULL, NULL, NULL, &ftDummy);

        if(lRet == ERROR_NO_MORE_ITEMS)
            break;

        if(lRet != ERROR_SUCCESS)
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        //
        //  Open the conversion DLL key
        //
        lRet = RegOpenKeyExW(ms_hkConversions, szDll, 0L, KEY_READ, &hkDll);

        if(lRet != ERROR_SUCCESS)
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Add the conversion DLL to the global DLL cache.
        // (This also loads the DLL.)
        hr = ms_pDllCache->HrAdd(
            szDll);     // DLL name to load and add

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        //
        // enumerate all entry point keys contained in the DLL key.
        //
        int iEntryPoint = 0;

        for (iEntryPoint = 0;;iEntryPoint++)
        {
            WCHAR szEntryPoint[cMaxKeyNameSize+1] = {0};

            // Entry point loop bounds
            ASSERTERROR(
                iEntryPoint >= 0 && iEntryPoint < 1024,
                "iEntryPoint out of bounds");

            dwDummy = cMaxKeyNameSize;

            lRet = RegEnumKeyExW(
                hkDll, iEntryPoint, szEntryPoint, &dwDummy, NULL, NULL, NULL, &ftDummy);

            if(lRet == ERROR_NO_MORE_ITEMS)
                break;

            if(lRet != ERROR_SUCCESS)
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            //
            //  Open the entry point
            //
            lRet = RegOpenKeyExW(hkDll, szEntryPoint, 0L, KEY_READ, &hkEntryPoint);

            if(lRet != ERROR_SUCCESS)
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

            //
            //  Get values and put them in the in-memory structure.
            //
            hr = HrEDKRecordValues(hkEntryPoint, szDll, szEntryPoint);

            if(hr == EDK_E_END_OF_FILE)
            {
                // Error reported by HrEDKRecordValues
                hr = HR_LOG(NOERROR);   // not a critical error
            }

            if(FAILED(hr))
            {
                goto cleanup;
            }

            //
            //  Close entry point key.
            //
            REGCLOSEKEY(hkEntryPoint);
        }

        if(!iEntryPoint)
        {
            EDKSyntaxError(
                szDll,
                NULL,
                NULL,
                NULL,
                NULL);
        }

        //
        //  Close DLL key.
        //
        REGCLOSEKEY(hkDll);
    }

    if(!iDll)
    {
        EDKSyntaxError(
            NULL,
            NULL,
            NULL,
            NULL,
            NULL);
    }

cleanup:

    REGCLOSEKEY(hkDll);
    REGCLOSEKEY(hkEntryPoint);

    if(m_bSyntaxError)
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);
    }

    ASSERTERROR(hkDll == NULL,"NULL hkDll variable");

    ASSERTERROR(hkEntryPoint == NULL,"NULL hkEntryPoint variable");

    RETURN(hr);

}

//$--CEDKConvReg::HrEDKRecordValues---------------------------------------
//
//  DESCRIPTION: We have found an entry point.  Get its details and record them in
//  the in-memory list of classes/entry points.
//
//  INPUT:  hkEntryPoint    --  entry point key
//          pszDll  --  DLL name
//          pszEntryPoint   --  name of DLL's entry point function
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          EDK_E_END_OF_FILE if bad syntax,
//                          E_FAIL otherwise
//
//---------------------------------------------------------------------
HRESULT CEDKConvReg::HrEDKRecordValues(     // RETURN:  HRESULT
                                    //   if a required value is not present
                                    //   SyntaxError() is also called.
    IN HKEY const hkEntryPoint,     // Entry point key that contain details
    IN LPCWSTR pszDll,              // dll containing entry point.
    IN LPCWSTR pszEntryPoint)       // name of entry point in dll.
{
    HRESULT        hr             = NOERROR;    // return code
    PVOID          pClasses       = NULL;
    PVOID          pPoint         = NULL;
    PVOID          pOptions       = NULL;
    DWORD          cbSize         = 0;
    DWORD          nType          = 0;
    LPCWSTR        pszClassName   = NULL;
    CDllEntryPoint *pEntryPoint   = NULL;

    DEBUGPRIVATE("CEDKConvReg::HrEDKRecordValues()\n");

    // check input parameters
    hr = CHK_CEDKConvReg_HrEDKRecordValues(hkEntryPoint, pszDll,
                                           pszEntryPoint);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    //
    //  Get the key values.
    //

    //
    //  Supported classes.
    //
    hr = HrGetArbSizeValue(hkEntryPoint, pszClassesKey,
                              pClasses, cbSize, nType);

    if(hr == EDK_E_END_OF_FILE || nType != REG_MULTI_SZ)
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);
    }

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Conversion point.
    //
    hr = HrGetArbSizeValue(hkEntryPoint, pszPointKey,
                              pPoint, cbSize, nType);

    if(hr == EDK_E_END_OF_FILE || nType != REG_SZ)
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);
    }

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Options.
    //
    hr = HrGetArbSizeValue(hkEntryPoint, pszOptionsKey,
                              pOptions, cbSize, nType);

    // ignore absent value.
    if(SUCCEEDED(hr) && nType != REG_SZ)
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);

        goto cleanup;
    }
    else
    {
        hr = HR_LOG(NOERROR);
    }

    //
    //  Setup pointer to first class name.
    //
    pszClassName = (LPCWSTR)pClasses;

    ASSERTERROR(pszClassName != NULL, "Bad pszClassName");
    ASSERTERROR(pPoint != NULL, "Bad pPoint");

    if( (*pszClassName == 0) || (*((LPWSTR)pPoint) == 0) )
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);

        goto cleanup;
    }

    //
    //  Create a new entry point.
    //

    pEntryPoint = new CDllEntryPoint;

    if(pEntryPoint == NULL)
    {
        goto cleanup;
    }

    hr = pEntryPoint->HrEDKSet(
        pszDll, (LPCWSTR)pszEntryPoint, (LPCWSTR)pPoint, (LPWSTR)pOptions);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  For each class in pClasses
    //

    for(; *pszClassName; pszClassName += lstrlenW(pszClassName)+1)
    {
        //
        //  Make a new class structre
        //

        CClassName * pNewClass = new CClassName;

        if(pNewClass == NULL)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = pNewClass->HrEDKSet(pszClassName, pEntryPoint);

        if(FAILED(hr))
        {
            delete pNewClass;
            goto cleanup;
        }

        //
        // insert class in list of classes.
        //

        CClassName ** ppcnTmp = &ms_pcnClasses;

        for(;;)
        {
            ASSERTERROR(ppcnTmp != NULL,"NULL ppcnTmp variable");

            if(*ppcnTmp == NULL)
                break;

            ASSERTERROR(*ppcnTmp != NULL,"NULL *ppcnTmp variable");

            if(pNewClass->cNameLength() >= (*ppcnTmp)->cNameLength())
                break;

            ppcnTmp= &((*ppcnTmp)->m_pcnNext);
        }

        pNewClass->m_pcnNext = *ppcnTmp;

        *ppcnTmp = pNewClass;
    }

cleanup:

    if ( hr == EDK_E_END_OF_FILE )
    {
        // report syntax error to event log
        EDKSyntaxError(
                (LPWSTR) pszDll,
                (LPWSTR) pszEntryPoint,
                (LPWSTR) pClasses,
                (LPWSTR) pOptions,
                (LPWSTR) pPoint);
    }

    MAPIFREEBUFFER(pClasses);
    MAPIFREEBUFFER(pPoint);
    MAPIFREEBUFFER(pOptions);

    if(pEntryPoint != NULL)
    {
        pEntryPoint->Release();
        pEntryPoint = NULL;
    }

    RETURN(hr);
}

//$--CEDKConvReg::HrEDKSearchOpen---------------------------------------------
//
//  DESCRIPTION: Initiate the search for candidate converters.
//  Call HrEDKSearchNext to find subsequent converters.
//  Must call SearchClose after finished, even if errors
//  are returned.
//
//  INPUT:  pszConversionPoint  --  what conversion point
//          pszContentClass --  what class
//
//  OUTPUT: pep --  hold candidate conversion if found
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
//---------------------------------------------------------------------
HRESULT CEDKConvReg::HrEDKSearchOpen(       // returns HRESULT
    IN LPCWSTR pszConversionPoint,          // what conversion point.
    IN LPCWSTR pszContentClass,             // what class
    OUT CDllEntryPoint * &pep)              // holds candidate if found.
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("CEDKConvReg::HrEDKSearchOpen()\n");

    // check for bad input
    hr = CHK_CEDKConvReg_HrEDKSearchOpen(pszConversionPoint,
                                         pszContentClass, pep);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    m_ppep = &pep;
    m_pszConversionPoint = pszConversionPoint;
    m_pszContentClass = pszContentClass;
    m_pcnCurrentClass = ms_pcnClasses;

    RETURN(hr);

}

//$--CEDKConvReg::HrEDKSearchNext-----------------------------------------
//
//  DESCRIPTION: Find the next candidate conversion.  HrEDKSearchOpen must be called
//  first.  SearchClose must be called after.  EDK_E_END_OF_FILE indicates that
//  no candidates can be found.
//
//  INPUT:  none
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          EDK_E_END_OF_FILE is no candidates found,
//                          E_FAIL otherwise.
//
//---------------------------------------------------------------------
HRESULT CEDKConvReg::HrEDKSearchNext()      // RETURNS: HRESULT
{
    HRESULT             hr      =   NOERROR;    // return code
    CDllEntryPoint *    pdepTmp =   NULL;       // temporary conversion DLL
    HRESULT             hrT     =   NOERROR;    // temporary return code
    BOOL                fFound  =   FALSE;      // TRUE if found DLL

    DEBUGPRIVATE("CEDKConvReg::HrEDKSearchNext()\n");

    //
    //  Search remaining conversions.
    //
    while (m_pcnCurrentClass != NULL)
    {
        CClassName * pcnTmp = m_pcnCurrentClass;
        m_pcnCurrentClass = m_pcnCurrentClass->m_pcnNext;

        pdepTmp = pcnTmp->pEntryPoint();

        hrT = HrStrstriW(
                    pcnTmp->pszClassName(),     // substring
                    m_pszContentClass);         // message class

        if ( FAILED(hrT) )
        {
            // not a match,
            // continue.
            continue;
        }

        // Class names "match"
        // Check conversion pointer for a match.
        LPWSTR lpszCurPoint = pdepTmp->pszGwPoint();
        if ( lstrcmpiW(m_pszConversionPoint, lpszCurPoint) == 0 )
        {
            //
            // found a match, break;
            //
            fFound = TRUE;

            break;
        }
    }

    // Check to make sure that conversion was found.
    if ( (fFound == FALSE) || (pdepTmp == NULL) )
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);

        goto cleanup;
    }

    *m_ppep = pdepTmp;

cleanup:

    RETURN(hr);

}

//$--CEDKConvReg::EDKSearchClose------------------------------------------
//
//  DESCRIPTION: must be called after HrEDKSearchOpen.
//
//  INPUT:  none
//
//  RETURNS:    void
//
//---------------------------------------------------------------------
void CEDKConvReg::EDKSearchClose() // RETURNS: void
{
    DEBUGPRIVATE("CEDKConvReg::EDKSearchClose()\n");

}

//$--CEDKConvReg::EDKFree-------------------------------------------------
//
//  DESCRIPTION: internal routine to free conversion structure.
//
//  INPUT:  none
//
//  RETURNS:    void
//
//---------------------------------------------------------------------
void CEDKConvReg::EDKFree()    // RETURNS: void
{
    DEBUGPRIVATE("CEDKConvReg::EDKFree()\n");

    CClassName * pcnTmp = ms_pcnClasses;

    while (pcnTmp != NULL)
    {
        CClassName * pcnNext = pcnTmp->m_pcnNext;

        delete pcnTmp;

        pcnTmp = pcnNext;
    }
    ms_pcnClasses = NULL;

    REGCLOSEKEY(ms_hkConversions);

    // delete the DLL cache.  (This also unloads the DLLs previously
    // loaded.
    delete ms_pDllCache;

    ms_pDllCache = NULL;

}

//$--CEDKConvReg::EDKDumpMappings-----------------------------------------
//
//  DESCRIPTION: debug routine to display mappings.
//
//  INPUT:  none
//
//  RETURNS:    void
//
//---------------------------------------------------------------------
void CEDKConvReg::EDKDumpMappings()     // RETURNS void
{
    DEBUGPRIVATE("CEDKConvReg::EDKDumpMappings()\n");

    CClassName * pcnTmp = ms_pcnClasses;

    // incompatabile with WINWRAP _tprintf(TEXT("Dump of mappings...\n"));
    while (pcnTmp != NULL)
    {
        pcnTmp->EDKDump();

        pcnTmp = pcnTmp->m_pcnNext;
    }

}

//$--CEDKConvReg::EDKSyntaxError------------------------------------------
//
//  DESCRIPTION: debug routine called whenever a syntax error is detected.
//
//  INPUT:      pszDll  --  DLL key name
//              pszEntryPoint   -- entry point name
//              pszClass    --  message class key name
//              pszOptions  --  conversion options key name
//              pszPoint    --  conversion point key name
//
//  RETURNS:    VOID
//
//---------------------------------------------------------------------
VOID CEDKConvReg::EDKSyntaxError(         // RETURNS: VOID
        IN LPWSTR   pszDll,     // DLL name
        IN LPWSTR   pszEntryPoint,  // entry point name
        IN LPWSTR   pszClass,   // message class
        IN LPWSTR   pszOptions, // conversion options
        IN LPWSTR   pszPoint)   // conversion point
{
    LPWSTR  lpszBlank   =   L"";    // blank string

    LPWSTR  lpszDllName =   lpszBlank;   // conversion DLL name
    LPWSTR  lpszEntry   =   lpszBlank;   // entry point name
    LPWSTR  lpszPoint   =   lpszBlank;   // conversion point
    LPWSTR  lpszClass   =   lpszBlank;   // message class
    LPWSTR  lpszOptions =   lpszBlank;   // options

    DEBUGPRIVATE("CEDKConvReg::EDKSyntaxError()\n");

    // No check function for this routine.  It is an event
    // logging routine and all parameters may be NULL.

    m_bSyntaxError = TRUE;

    if ( pszDll != NULL )
    {
        lpszDllName = pszDll;
    }

    if ( pszEntryPoint != NULL )
    {
        lpszEntry = pszEntryPoint;
    }

    if ( pszPoint != NULL )
    {
        lpszPoint = pszPoint;
    }

    if ( pszClass != NULL )
    {
        lpszClass = pszClass;
    }

    if ( pszOptions != NULL )
    {
        lpszOptions = pszOptions;
    }

    // Log event to the event log.
    EventLogMsgW(
            MESGXLAT_CNV_BAD_SYNTAX,    // event identfier
            5,                          // # of strings
            lpszDllName,                // string 1
            lpszEntry,
            lpszClass,
            lpszOptions,
            lpszPoint,
            0);                         // # of error codes

}


CONVREGH.CPP

// --convregh.cpp---------------------------------------------------------------
//
// Class that represents conversion class name/list info.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "convincl.h"
#include "convregh.chk"

//$--HrGetArbSizeValue---------------------------------------------------------
//
//  DESCRIPTION: Allocate sufficient memory to read a value from the the registry
//  and read it.
//
//  INPUT:  hk  --  key that contains value
//          lpszValueName   --  name of value
//
//  OUTPUT: pValue  --  pointer to key's value
//          cbSize  --  size of value (including null)
//          nType   --  type of value
//
//  RETURNS:    HRESULT -   NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          EDK_E_END_OF_FILE if key not found,
//                          E_FAIL otherwise.
//
//------------------------------------------------------------------------------
HRESULT HrGetArbSizeValue(       // RETURNS: HRESULT
    IN HKEY hk,                 // key that contains the value
    IN LPWSTR lpszValueName,    // name of the value
    OUT PVOID & pValue,         // pointer to its value
    OUT DWORD & cbSize,         // how big is it (including trailing null(s)
    OUT DWORD & nType)          // what type
{
    HRESULT hr  =   NOERROR;    // return code
    LONG lRet = 0;

    DEBUGPRIVATE("HrGetArbSizeValue()\n");

    // check input parameters
    hr = CHK_HrGetArbSizeValue(hk, lpszValueName, pValue, cbSize, nType);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    pValue = NULL;

    lRet = RegQueryValueExW(hk, lpszValueName, NULL, &nType, NULL, &cbSize);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(EDK_E_END_OF_FILE);

        goto cleanup;
    }

    hr = MAPIAllocateBuffer(
                cbSize,     // # of bytes
                &pValue);   // output buffer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    lRet = RegQueryValueExW(hk, lpszValueName, NULL, &nType, (PBYTE)pValue, &cbSize);

    if(lRet != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    if( FAILED(hr) )
    {
        MAPIFREEBUFFER(pValue);
    }

    RETURN(hr);

}

//$--HrStrstriW-------------------------------------------------------
//
// DESCRIPTION: determines whether one string is a sub-string of another.
// Not case sensitive.
//
// INPUT:   pszSubString   --  the sub-string to check for
//          pszString   --  the string
//
// RETURNS: HRESULT --  NOERROR if string contains the sub-string.
//                      E_FAIL otherwise.
//
//---------------------------------------------------------------------
HRESULT HrStrstriW(
    IN LPCWSTR pszSubString,    // the candidate substring
    IN LPCWSTR pszString)       // the string.
{
    HRESULT hr          =   NOERROR;    // return code
    LPWSTR  pszStringT  =   NULL;       // string copy
    LPWSTR  pszSubStrT  =   NULL;       // substring copy

    DEBUGPRIVATE("HrStrstriW()\n");

    // DON'T check input parameters, as null strings are allowed.
    if((pszString == NULL) && (pszSubString == NULL))
    {
        goto cleanup;
    }

    if((pszString == NULL) && (pszSubString != NULL))
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    if((pszString != NULL) && (pszSubString == NULL))
    {
        goto cleanup;
    }

    // upper-case the strings so that our comparison will be
    // case-insensitive.  Duplicate the strings first, however.
    hr = HrStrWToStrW(
                pszString,          // input string
                &pszStringT);       // output string pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    hr = HrStrWToStrW(
                pszSubString,       // input string
                &pszSubStrT);       // output string pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    pszStringT = _wcsupr(pszStringT);
    pszSubStrT = _wcsupr(pszSubStrT);

    if ( (pszSubStrT == NULL) || (pszStringT == NULL) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    if ( wcsstr(pszStringT, pszSubStrT) == NULL )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // substring exists in string

cleanup:

    // free MAPI buffers
    MAPIFREEBUFFER(pszStringT);
    MAPIFREEBUFFER(pszSubStrT);

    RETURN(hr);

}


MESGXLAT.CPP

#include "edk.h"


MONCTX.H

// --monctx.h-------------------------------------------------------------------
//
//  File containing definitions for monitor context and related structures.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#if !defined(_MONCTX_H)
#define _MONCTX_H

#include <winperf.h>

//
// Macro definitions
//

// This checks to see that the value is a valid detail level as defined in
// WINPERF.H.  Detail level describes the level of knowledge needed to
// understand a given counter or object.
#define TEST_DETAIL_LEVEL(x) ( \
(x) == PERF_DETAIL_NOVICE || \
(x) == PERF_DETAIL_ADVANCED || \
(x) == PERF_DETAIL_EXPERT || \
(x) == PERF_DETAIL_WIZARD)

#define ASSERT_DETAIL_LEVEL(x, y) ASSERTERROR(TEST_DETAIL_LEVEL(x), y)

#define TEST_COUNTERDEF_ARRAY(_ccd, _lpcd) fIsValidCounterDefArray(_ccd, _lpcd)

// Casts a FILETIME or LARGE_INTEGER to an __int64
#define INT64(li) (*( \
(__int64 *) \
(ASSERTERROR(sizeof(li) == sizeof(__int64), "wrong size"), &(li)) \
))

// Casts an __int64 to a FILETIME
#define TO_FILETIME(i64) (*((FILETIME *)&(i64)))

// Constant value for one minute in system 8-byte time format.
#define ONE_MINUTE((__int64)60*10000000)

//
// Internal Structure Definitions
//

//$--MONCOUNTER-----------------------------------------------------------------
//  Internal structure representing a counter to be updated.
// -----------------------------------------------------------------------------
typedef struct _MonCounter// (hungarian notation = mc)
{
struct _MonCounter *lpmcNext;// ptr to next in current counter list
struct _MonCounter *lpmcNextJSW;// ptr to next in master JSW list
HJSWINDOW hjswJumpWindow;// handle of JSW for counter (or NULL)
LPCOUNTER lpcntCounter;// ptr to actual counter to update
    BOOL fZeroOnFree;                   // TRUE=zero this counter on gw shutdown
} MONCOUNTER, *LPMONCOUNTER;

//$--MONFOLDER------------------------------------------------------------------
//  Internal structure representing a folder to be monitored.
// -----------------------------------------------------------------------------
typedef struct _MonFolder// (hungarian notation = mf)
{
struct _MonFolder *lpmfNext;// pointer to next folder in chain
struct _MonContext *lpmcx;// pointer back to main context
LPMAPIFOLDER lpFolder;// folder this structure is linked to
LPMAPITABLE lpTable;// table of messages in folder
LPMAPIADVISESINK lpAdvise;// advise structure attached to table
ULONG ulConnection;// connection returned from Advise()

LPMONCOUNTER lpmcMessagesInFolder;// (lists of counters for this folder)
LPMONCOUNTER lpmcBytesInFolder;
LPMONCOUNTER lpmcMessagesEnteringFolder;
LPMONCOUNTER lpmcBytesEnteringFolder;
LPMONCOUNTER lpmcMessagesLeavingFolder;
LPMONCOUNTER lpmcBytesLeavingFolder;

CRITICAL_SECTION csCriticalSection;// critical section to synchronize
// access to the "update request"
// variables
BOOL fCriticalSectionInitialized;// flag that this critical section
// was initialized

// "Update request" variables.  The following four variables are set by
// the folder notification routine and are used by the folder update
// thread to update the folder counters.  This lets the folder notification
// routine do less work.

DWORD cMessagesEnteringFolder;// messages entering folder to process
DWORD cBytesEnteringFolder;// bytes entering folder to process
DWORD cMessagesLeavingFolder;// messages leaving folder to process
DWORD cBytesLeavingFolder;// bytes leaving folder to process

// The following two variables are kept track of by the folder update
// thread.  They keep a running total of the total number of bytes that
// have entered and left the folder AS REPORTED TO THE WINDOWS NT
// PERFORMANCE MONITOR SO FAR.  Together with the total bytes in the folder,
// they are used to determine when bytes are leaving the folder.  This
// is nescessary because the notification engine tells us that a message
// has left the folder, but not how many bytes it contained.

ULONG cTotalBytesEnteringFolder;// total bytes we said entered folder
ULONG cTotalBytesLeavingFolder;// total bytes we said left folder

BOOL fUpdateFolderSize;// "update request" to get new count
// of messages and bytes in folder
} MONFOLDER, *LPMONFOLDER;

//$--MONCONTEXT-----------------------------------------------------------------
//  Internal structure that holds all the variables needed for a performance
//  monitoring session.  Right now the performance monitoring API is written
//  to only allow one session, but it could easily be changed to allow multiple
//  sessions.
// -----------------------------------------------------------------------------
typedef struct _MonContext {// (hungarian notation = mcx)

// name of gateway type and gateway instance

LPSTR lpszObjectClass;// object class
LPSTR lpszObjectName;// object instance name

// shared memory to communicate with the DLL

DWORD cbSharedMemorySize;// size of shared memory block
HANDLE hSharedMemoryMapping;// handle of file mapping object
LPMONSHAREDMEMORY lpmsmSharedMemory; // pointer to shared memory block
LPCOUNTER lpcntCounter;// ptr to actual counters in shared mem
HANDLE hSharedMemoryMutex;// mutex to share memory

// lists of non-folder INCREMENTING counters being monitored

LPMONCOUNTER lpmcMessagesTransferredIn; // messages into Exchange
LPMONCOUNTER lpmcMessagesTransferredOut; // messages out of Exchange
LPMONCOUNTER lpmcBytesTransferredIn; // bytes into Exchange
LPMONCOUNTER lpmcBytesTransferredOut; // bytes out of Exchange
LPMONCOUNTER lpmcNDRsIn;// non-deliverables into Exchange
LPMONCOUNTER lpmcNDRsOut;// non-deliverables out of Exchange

// lists of non-folder ABSOLUTE counters being monitored

LPMONCOUNTER lpmcAssociations;// current number of associations

// list of counters allocated but not put onto another list yet
// (this is to avoid memory leaks in case of aborting startup)

LPMONCOUNTER lpmcFreeList;// list of counters not being used

// information about folders being monitored

LPMONFOLDER lpmfFolderList;// list of folders to monitor

// information needed by jumping sliding windows

LPMONCOUNTER lpmcJSWCounterList;// list of counters with JSW's
__int64 dwlCurrentTime;// 64 bit current system time

// information about the thread that updates the jumping sliding
// windows and folders

BOOL fUpdateThreadRunning;// set if the thread is running
HANDLE hUpdateThread;// handle to the thread process
DWORD dwUpdateThreadID;// thread ID
HANDLE hUpdateThreadWakeEvent;// handle to event to wake the thread
BOOL fUpdateThreadTerminate;// flag set to terminate thread

// First Counter and First Help values for this gateway.  These are taken
// from the SYSTEM\CurrentControlSet\Services\<lpszObjectClass>\Performance
// key in the registry.

DWORD dwFirstCounter;// "First Counter" from registry
DWORD dwFirstHelp;// "First Help" from registry
DWORD dwLastCounter;// "Last Counter" from registry
DWORD dwLastHelp;// "Last Help" from registry
DWORD iObjectTitleOffset;// offset of object name from
// First Counter

// Other things that need to be kept track of

DWORD dwObjectDetailLevel;// complexity of object (winperf.h)
DWORD dwDefaultCounter;// default counter on lists

} MONCONTEXT, *LPMONCONTEXT;

#endif


MONITOR.C

// --monitor.c------------------------------------------------------------------
//
//  This file implements the monitored object side of the EDK performance
//  monitoring component.
//
//  This component uses the stats.lib to generate a suite of statistics.  The
//  statistics are periodically copied to a memory area shared with the other
//  side of the performance monitoring component: a performance DLL which is
//  called by the NT Performance Monitor.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "monshare.h"
#include "monctx.h"
#include "monitor.chk"

//
// Internal Function Declarations
//

static HRESULT HrAllocateContext(
OUT LPMONCONTEXT *lppmcx);

static HRESULT HrGetCurrentTime(
OUT __int64 * lpdwlTime);

static HRESULT HrReadObjectClass(
IN LPMONCONTEXT lpmcx);

static HRESULT HrReadRegistryInfo(
IN LPMONCONTEXT lpmcx);

static HRESULT HrReadRegistryDWORD(
IN HKEY hKey,
IN LPSTR lpszValueName,
OUT LPDWORD lpdwValue);

static HRESULT HrCreateSharedMemory(
IN LPMONCONTEXT lpmcx);

static HRESULT HrFillInSharedMemory(
IN LPMONCONTEXT lpmcx,
IN DWORD ccdCounters,
IN LPCOUNTERDEF lpcdCounters);

static HRESULT HrBuildStructures(
IN LPMONCONTEXT lpmcx,
IN DWORD ccdCounters,
IN LPCOUNTERDEF lpcdCounters);

static HRESULT HrMonFindFolder(
IN  LPMONFOLDER   lpmfFolderList,
IN  LPMAPIFOLDER  lpFolder,
OUT LPMONFOLDER * lppmfFolder);

static HRESULT HrStartUpdateThread(
IN LPMONCONTEXT lpmcx);

static DWORD WINAPI UpdateThread(
IN LPVOID lpThreadParameter);

static HRESULT HrFreeEverything(
IN LPMONCONTEXT *lppmcx);

static HRESULT HrFreeCounterList(
IN LPMONCOUNTER *lppmcCounterList);

static HRESULT HrFreeFolderList(
IN LPMONFOLDER *lppmfFolderList);

static HRESULT HrAddToCounters(
IN LPMONCONTEXT lpmcx,
IN LPMONCOUNTER lpmcList,
IN LONG dwAmount);

static HRESULT HrSetCounters(
IN LPMONCOUNTER lpmcList,
IN DWORD dwAmount);

static HRESULT HrComputeCounterType(
IN  COUNTERTYPE ctStatistic,
IN  PERIODTYPE perPeriod,
IN  DWORD dwUserCounterType,
OUT DWORD *lpdwCounterType);

static HRESULT HrAlignPointer(
IN     DWORD cbAlignment,
IN OUT LPVOID * lppvPointer);

static ULONG STDAPICALLTYPE FolderNotification(
IN LPVOID lpvContext,
IN DWORD cNotification,
IN LPNOTIFICATION lpNotifications);

static HRESULT HrWakeUpdateThread(
IN LPMONCONTEXT lpmcx);

static HRESULT HrRegisterNotification(
IN LPMONCONTEXT lpmcx);

static HRESULT HrLinkCounterIntoList(
IN LPMONCONTEXT lpmcx,
IN LPMONFOLDER lpmf,
IN LPMONCOUNTER lpmc,
IN COUNTERTYPE ctCounterType);

static HRESULT HrVerifyLinkage(
IN LPMONCONTEXT lpmcx);

static HRESULT HrSaveSharedMemoryInRegistry(
    IN LPMONCONTEXT lpmcx);

//
// Local Variable Declarations
//

static LPMONCONTEXT lpmcxContext = NULL; // place to save allocated context

// Security descriptor to allow all access -- only need to initialize once!!!

static BOOL fSecurityDescriptorInitialized = FALSE;

static SECURITY_ATTRIBUTES sa;
static char rgbForSecurityDescriptor[SECURITY_DESCRIPTOR_MIN_LENGTH];

static SECURITY_ATTRIBUTES * psa = &sa;
static SECURITY_DESCRIPTOR * psd
                    = (SECURITY_DESCRIPTOR *)rgbForSecurityDescriptor;

//
// Monitor API Routines
//

//$--HrMonInit--------------------------------------------------------------
//  Begins performance monitoring of the current monitored object.
//
//  If lpszObjectClass != NULL, then use lpszObjectClass as the class of the
//  monitored object.
//  If lpszObjectClass == NULL, then read the object class from the
//  Parameters\ObjectClass value under the object's registry key, or if
//  it's not present, assume that the object class is the same as the object
//  name.
// -----------------------------------------------------------------------------
HRESULT HrMonInit( // RETURNS: HRESULT
IN DWORD dwFlags,// for future use--must be zero
IN LPSTR lpszObjectClass,// class of monitored object, or NULL
IN LPSTR lpszObjectName, // gateway instance name
IN DWORD dwObjectTitleOffset,// index number of object name in
// the registry database (offset from
// First Counter)
IN DWORD dwObjectDetailLevel,// complexity of object (see winperf.h)
IN LONG dwDefaultCounter,// zero-based number of default counter
// for this object
IN DWORD ccdNumberOfCounters,// number of counter structures
// being passed in
IN LPCOUNTERDEF lpcdCounters)// pointer to array of counter
// structures
{
    // This routine creates a "context" structure in allocated memory that
    // holds all the information needed for performance monitoring.  When fully
    // allocated, this context contains open handles to various objects, and
    // pointers to other allocated memory structures.  HrMonInit() calls
    // several other routines, each of which allocates some of the handles
    // and structures attached to the context.
    //
    // If one of these routines fails for any reason, it does NOT clean up what
    // it has created in the context.  Instead, it simply returns an error, and
    // then HrMonInit calls a "catch-all" cleanup routine called
    // HrFreeEverything().  HrFreeEverything frees/closes anything attached
    // to the context and then frees the context itself.  HrFreeEverything()
    // is also called by MonitorShutdown() to end performance monitoring.

HRESULThr = NOERROR;

DEBUGPUBLIC("HrMonInit()\n");

// Check the parameters.

hr = CHK_HrMonInit(
dwFlags,
lpszObjectClass,
lpszObjectName,
dwObjectTitleOffset,
dwObjectDetailLevel,
dwDefaultCounter,
ccdNumberOfCounters,
lpcdCounters);
if (FAILED(hr))
RETURN(hr);

    // Set up a security descriptor with a NULL DACL, to allow all
    // access to an object.  This is used if we need to create the
    // mutex and shared memory section.

    if (!fSecurityDescriptorInitialized)
    {
        if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
        {
    hr = HR_LOG(E_FAIL);
    goto cleanup_no_free;
        }

        if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL) NULL, FALSE))
        {
    hr = HR_LOG(E_FAIL);
    goto cleanup_no_free;
        }

        psa->nLength = sizeof(*psa);
        psa->lpSecurityDescriptor = psd;
        psa->bInheritHandle = TRUE;

        fSecurityDescriptorInitialized = TRUE;
    }

    // Make sure this hasn't already been called before.

if (lpmcxContext != NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup_no_free;
}

// Allocate and zero out performance monitoring context.

hr = HrAllocateContext(&lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// Store various parameters in the context.

lpmcxContext->iObjectTitleOffset = dwObjectTitleOffset;
lpmcxContext->dwObjectDetailLevel = dwObjectDetailLevel;
lpmcxContext->dwDefaultCounter = dwDefaultCounter;

// Get the current system time and put it in the context.

hr = HrGetCurrentTime(&lpmcxContext->dwlCurrentTime);
if (FAILED(hr))
{
goto cleanup;
}

// Add allocated copies of the gateway type name and instance name to
// the performance monitoring context.  If they passed NULL for the
    // gateway type then read it from the Parameters\ObjectClass value
    // under the gateway's service entry in the registry.

lpmcxContext->lpszObjectName = _strdup(lpszObjectName);
if (lpmcxContext->lpszObjectName == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

    if (lpszObjectClass)
    {
    lpmcxContext->lpszObjectClass = _strdup(lpszObjectClass);
    if (lpmcxContext->lpszObjectClass == NULL)
    {
    hr = HR_LOG(E_OUTOFMEMORY);
    goto cleanup;
    }
    }
    else
    {
        hr = HrReadObjectClass(lpmcxContext);
        if (FAILED(hr))
        {
            goto cleanup;
        }
    }

    // Verify that there is a back link from the gateway type to the
    // gateway name so the DLL can link to our shared memory section.

    hr = HrVerifyLinkage(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// Read the Counter and Help values from the registry.

hr = HrReadRegistryInfo(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// Create the shared memory section and mutex.  This leaves us as the
// owner of the mutex and we must release it before we return.

hr = HrCreateSharedMemory(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// Fill in the shared memory section with the structure that will be
// passed to the DLL, and from there to the Performance Monitor.

hr = HrFillInSharedMemory(
lpmcxContext, ccdNumberOfCounters, lpcdCounters);
if (FAILED(hr))
{
goto cleanup;
}

    // Save a copy of the shared memory block to the registry so that
    // GWPERF.DLL will be able to generate a blank set of counters when
    // the gateway isn't running.

    hr = HrSaveSharedMemoryInRegistry(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// Set up structures representing folders, and linked lists of counters
// hanging off the performance monitoring context.

hr = HrBuildStructures(
lpmcxContext, ccdNumberOfCounters, lpcdCounters);
if (FAILED(hr))
{
goto cleanup;
}

// Register the notification routines on all folders that need them.

hr = HrRegisterNotification(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}

// If there are any jumping sliding windows or folders, then start
// the thread that updates them.

if (lpmcxContext->lpmcJSWCounterList || lpmcxContext->lpmfFolderList)
{
hr = HrStartUpdateThread(lpmcxContext);
if (FAILED(hr))
{
goto cleanup;
}
}

// Set flag that the shared memory data structure is now valid.

lpmcxContext->lpmsmSharedMemory->fDataIsValid = TRUE;

// Release the mutex (it was "locked" when the mutex was created).

hr = HrMonUnlockCounters();
if (FAILED(hr))
{
goto cleanup;
}

cleanup:
if (FAILED(hr))
{
        HRESULT hrT = NOERROR;

hrT = HrFreeEverything(&lpmcxContext);
        if (FAILED(hrT))
            HR_LOG(hrT);
}

cleanup_no_free:
RETURN(hr);
}

//$--HrMonUninit----------------------------------------------------------
//  Ends performance monitoring of the current gateway.
// -----------------------------------------------------------------------------
HRESULT HrMonUninit(void)    // RETURNS: HRESULT
{
HRESULThr = NOERROR;

DEBUGPUBLIC("HrMonUninit()\n");

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");

// Shut down, and free the context and all memory attached to it.

hr = HrFreeEverything(&lpmcxContext);
    if (FAILED(hr))
        goto cleanup;

cleanup:
    RETURN(hr);
}

//$--HrMonCollectNDRStats----------------------------------------------------
//  Call this after processing an NDR.
// -----------------------------------------------------------------------------
HRESULT HrMonCollectNDRStats(// RETURNS: HRESULT
IN DWORD cNDRs,// number of NDR's processed
IN DIRECTIONTYPE dir)// direction of NDR's
{
HRESULThr = NOERROR;
HRESULThrT= NOERROR;

DEBUGPUBLIC("HrMonCollectNDRStats()\n");

// Check the parameters.

hr = CHK_HrMonCollectNDRStats(cNDRs, dir);
if (FAILED(hr))
RETURN(hr);

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");

    // Lock the shared memory against modification.

hr = HrMonLockCounters();
    if (FAILED(hr))
        goto cleanup;

    // Get current time for JSW's.

hr = HrGetCurrentTime(&lpmcxContext->dwlCurrentTime);
    if (FAILED(hr))
        goto cleanup;

    // Update the counters.

switch (dir)
{
case DIRECTIONTYPE_IN:
hr = HrAddToCounters(lpmcxContext, lpmcxContext->lpmcNDRsIn, cNDRs);
            if (FAILED(hr))
                goto cleanup;
break;

case DIRECTIONTYPE_OUT:
hr = HrAddToCounters(lpmcxContext, lpmcxContext->lpmcNDRsOut, cNDRs);
            if (FAILED(hr))
                goto cleanup;
break;
}

cleanup:
    // Unlock the shared memory.

    if (lpmcxContext)
    {
    hrT = HrMonUnlockCounters();
        if (FAILED(hrT))
        {
            hr = hrT;
        }
    }

    RETURN(hr);
}

//$--HrMonCollectMessageXferStats-------------------------------------
//  Call this after transferring a message.
// -----------------------------------------------------------------------------
HRESULT HrMonCollectMessageXferStats( // RETURNS: HRESULT
IN DWORD cMessages, // number of messages transferred
IN DWORD cBytes, // number of bytes transferred
IN DIRECTIONTYPE dir)// direction of message transfer
{
HRESULThr= NOERROR;
HRESULThrT= NOERROR;

DEBUGPUBLIC("HrMonCollectMessageXferStats()\n");

// Check the parameters.

hr = CHK_HrMonCollectMessageXferStats(cMessages, cBytes, dir);
if (FAILED(hr))
RETURN(hr);

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");

hr = HrMonLockCounters();
    if (FAILED(hr))
        goto cleanup;

hr = HrGetCurrentTime(&lpmcxContext->dwlCurrentTime);
    if (FAILED(hr))
        goto cleanup;

switch (dir)
{
case DIRECTIONTYPE_IN:

hr = HrAddToCounters(
lpmcxContext,
lpmcxContext->lpmcMessagesTransferredIn,
cMessages);
            if (FAILED(hr))
                goto cleanup;

hr = HrAddToCounters(
lpmcxContext,
lpmcxContext->lpmcBytesTransferredIn,
cBytes);
            if (FAILED(hr))
                goto cleanup;
break;

case DIRECTIONTYPE_OUT:

hr = HrAddToCounters(
lpmcxContext,
lpmcxContext->lpmcMessagesTransferredOut,
cMessages);
            if (FAILED(hr))
                goto cleanup;

hr = HrAddToCounters(
lpmcxContext,
lpmcxContext->lpmcBytesTransferredOut,
cBytes);
            if (FAILED(hr))
                goto cleanup;
break;
}

cleanup:
    if (lpmcxContext)
    {
    hrT = HrMonUnlockCounters();
        if (FAILED(hrT))
        {
            hr = hrT;
        }
    }

    RETURN(hr);
}

//$--HrMonCollectAssociationStats--------------------------------------------
//  Call this after making or breaking an association, or to set a new total
//  number of associations.
// -----------------------------------------------------------------------------
HRESULT HrMonCollectAssociationStats( // RETURNS: HRESULT
IN BOOL fSetNewTotal,// if TRUE, iAssociations becomes
// the new total of associations.
// if FALSE, iAssociations is added
// to the number of associations.

IN LONG cAssociations)// number of associations to add to
// total (can be negative), or new
// total if fSetNewTotal == TRUE
{
HRESULThr= NOERROR;
HRESULThrT= NOERROR;

DEBUGPUBLIC("HrMonCollectAssociationStats()\n");

// Check the parameters.

hr = CHK_HrMonCollectAssociationStats(fSetNewTotal, cAssociations);
if (FAILED(hr))
RETURN(hr);

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");

hr = HrMonLockCounters();
    if (FAILED(hr))
        goto cleanup;

hr = HrGetCurrentTime(&lpmcxContext->dwlCurrentTime);
    if (FAILED(hr))
        goto cleanup;

if (fSetNewTotal)
{
// Set all counters to the given value.

ASSERTERROR(cAssociations >= 0,
"cAssociations < 0 on set associations");

hr = HrSetCounters(
lpmcxContext->lpmcAssociations,
cAssociations);
        if (FAILED(hr))
            goto cleanup;
}
else
{
// Add given value to counters (value can be negative).

hr = HrAddToCounters(
lpmcxContext,
lpmcxContext->lpmcAssociations,
cAssociations);
        if (FAILED(hr))
            goto cleanup;
}

cleanup:
    if (lpmcxContext)
    {
    hrT = HrMonUnlockCounters();
        if (FAILED(hrT))
        {
            hr = hrT;
        }
    }

    RETURN(hr);
}

//$--HrMonLockCounters------------------------------------------------------
//  Locks the block of counters against access by other threads/processes.
//  This should be called before accessing a user defined counter.
// -----------------------------------------------------------------------------
HRESULT HrMonLockCounters(void)// RETURNS: HRESULT
{
    HRESULT         hr              = NOERROR;
DWORDdwReturnValue= 0;

DEBUGPUBLIC("HrMonLockCounters()\n");

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");
ASSERTERROR(lpmcxContext->hSharedMemoryMutex != NULL,
"lpmcxContext->hSharedMemoryMutex == NULL");

dwReturnValue = WaitForSingleObject(
lpmcxContext->hSharedMemoryMutex,
MON_MUTEX_TIMEOUT);
if (dwReturnValue == WAIT_FAILED || dwReturnValue == WAIT_TIMEOUT)
{
        hr = HR_LOG(E_FAIL);
goto cleanup;
}

ASSERTERROR(
dwReturnValue == WAIT_FAILED ||
dwReturnValue == WAIT_ABANDONED ||
dwReturnValue == WAIT_OBJECT_0 ||
dwReturnValue == WAIT_TIMEOUT,
"dwReturnValue: illegal value");

ASSERTERROR(dwReturnValue != WAIT_TIMEOUT,
"lpmcxContext->hSharedMemoryMutex: dwReturnValue == WAIT_TIMEOUT");

cleanup:
    RETURN(hr);
}

//$--HrMonUnlockCounters----------------------------------------------------
//  Unlocks the block of counters to allow access by other threads/processes.
//  This should be called after accessing a user defined counter.
// -----------------------------------------------------------------------------
HRESULT HrMonUnlockCounters(void)// RETURNS: HRESULT
{
    HRESULT         hr              = NOERROR;
BOOL fItWorked = FALSE;

DEBUGPUBLIC("HrMonUnlockCounters()\n");

    // Make sure there's a context.

    if (lpmcxContext == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

ASSERT_WRITE_PTR(lpmcxContext, sizeof(*lpmcxContext),
"lpmcxContext: bad write pointer");
ASSERTERROR(lpmcxContext->hSharedMemoryMutex != NULL,
"lpmcxContext->hSharedMemoryMutex == NULL");

fItWorked = ReleaseMutex(lpmcxContext->hSharedMemoryMutex);
if (!fItWorked)
{
        DWORD dwStatus = GetLastError();

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

cleanup:
    RETURN(hr);
}

//
// Internal Helper Functions
//

//$--HrAllocateContext----------------------------------------------------------
//  Helper function that allocates a new performance monitoring context and
//  zeros out its components.
// -----------------------------------------------------------------------------
static HRESULT HrAllocateContext(    // RETURNS: HRESULT
OUT LPMONCONTEXT *lppmcx)// pointer to address variable for
// performance monitor context ptr
{
LPMONCONTEXTlpmcxNew = NULL;
HRESULThr = NOERROR;

DEBUGPRIVATE("HrAllocateContext()\n");

// Check the parameters.

hr = CHK_HrAllocateContext(lppmcx);
if (FAILED(hr))
RETURN(hr);

lpmcxNew = malloc(sizeof(*lpmcxNew));
if (lpmcxNew == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}
ZeroMemory(lpmcxNew, sizeof(*lpmcxNew));
*lppmcx = lpmcxNew;

cleanup:
RETURN(hr);
}

//$--HrGetCurrentTime-----------------------------------------------------------
//  Helper function that returns the current system time in 8-byte format.
// -----------------------------------------------------------------------------
static HRESULT HrGetCurrentTime(// RETURNS: HRESULT
OUT __int64 * lpdwlTime)// current time
{
HRESULThr = NOERROR;
SYSTEMTIMEst= {0};
__int64dwlTime= 0;
BOOLfItWorked= TRUE;

DEBUGPRIVATE("HrGetCurrentTime()\n");

// Check the parameters.

hr = CHK_HrGetCurrentTime(lpdwlTime);
if (FAILED(hr))
RETURN(hr);

GetSystemTime(&st);
fItWorked = SystemTimeToFileTime(&st, &TO_FILETIME(dwlTime));
if (!fItWorked)
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

*lpdwlTime = dwlTime;

cleanup:
    RETURN(hr);
}

//$--HrReadRegistryInfo---------------------------------------------------------
//  Helper function that reads the First Counter and First Help values from the
//  SYSTEM\CurrentControlSet\Services\<lpszObjectClass>\Performance key.
//  Note that the context must already have lpszObjectClass defined.
// -----------------------------------------------------------------------------
static HRESULT HrReadRegistryInfo(    // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)// pointer to perf mon context
{
HRESULThr            = NOERROR;
DWORDdwStatus        = 0;
TCHARszKeyName[MAX_PATH]     = {0};
HKEYhKey        = NULL;
DWORDcch        = 0;

DEBUGPRIVATE("HrReadRegistryInfo()\n");

// Check the parameters.

hr = CHK_HrReadRegistryInfo(lpmcx);
if (FAILED(hr))
RETURN(hr);

ASSERT_STRING_PTR(lpmcx->lpszObjectClass,
"lpmcx->lpszObjectClass: bad string pointer");

// Create the registry key name from the gateway type.

cch = wsprintf(
szKeyName,
TEXT("SYSTEM\\CurrentControlSet\\Services\\%s\\Performance"),
lpmcx->lpszObjectClass);
ASSERTERROR(
(cch * sizeof(*szKeyName)) < sizeof(szKeyName),
"wsprintf overflow: szKeyName");

// Open the registry key.

dwStatus = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0L,
KEY_READ,
&hKey);
if (dwStatus != ERROR_SUCCESS)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Read the values: First Counter, First help, Last Counter, Last Help.

hr = HrReadRegistryDWORD(hKey, "First Counter",
&lpmcx->dwFirstCounter);
if (FAILED(hr))
{
goto cleanup;
}

hr = HrReadRegistryDWORD(hKey, "First Help",
&lpmcx->dwFirstHelp);
if (FAILED(hr))
{
goto cleanup;
}

hr = HrReadRegistryDWORD(hKey, "Last Counter",
&lpmcx->dwLastCounter);
if (FAILED(hr))
{
goto cleanup;
}

hr = HrReadRegistryDWORD(hKey, "Last Help",
&lpmcx->dwLastHelp);
if (FAILED(hr))
{
goto cleanup;
}

cleanup:

// Close the registry key (if it's open).

if (hKey)
{
dwStatus = RegCloseKey(hKey);
if (dwStatus != ERROR_SUCCESS)
{
            hr = HR_LOG(E_FAIL);
}
}

RETURN (hr);
}

//$--HrReadObjectClass----------------------------------------------------------
//  Helper function that reads the gateway type out of the registry.
// -----------------------------------------------------------------------------
static HRESULT HrReadObjectClass(       // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)              // pointer to perf mon context
{
HRESULThr        = NOERROR;
    DWORD       dwStatus                = ERROR_SUCCESS;
    BOOL        fValueNotFound          = FALSE;
TCHARszKeyName[MAX_PATH]     = {0};
HKEYhKey        = NULL;
DWORDcch        = 0;
    DWORD       dwType                  = 0;
    TCHAR       szObjectClass[MAX_PATH] = {0};
    DWORD       cbObjectClass           = sizeof(szObjectClass);

DEBUGPRIVATE("HrReadObjectClass()\n");

// Check the parameters.

hr = CHK_HrReadObjectClass(lpmcx);
if (FAILED(hr))
RETURN(hr);

// Create the registry key name from the gateway name.

cch = wsprintf(
szKeyName,
TEXT("SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters"),
lpmcx->lpszObjectName);
ASSERTERROR(
(cch * sizeof(*szKeyName)) < sizeof(szKeyName),
"wsprintf overflow: szKeyName");

// Open the registry key.

dwStatus = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0L,
KEY_READ,
&hKey);
if (dwStatus != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    // Read the value.

    dwStatus = RegQueryValueEx(
        hKey,
        TEXT("ObjectClass"),
        NULL,
        &dwType,
        szObjectClass,
        &cbObjectClass);
if (dwStatus != ERROR_SUCCESS || dwType != REG_SZ)
    {
        if (dwStatus == ERROR_FILE_NOT_FOUND)
            fValueNotFound = TRUE;
        else
            hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    // Add it to the context.

    lpmcx->lpszObjectClass = _strdup(szObjectClass);
    if (lpmcx->lpszObjectClass == NULL)
    {
        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
    }

cleanup:
    // If we couldn't read it then use the gateway name as the type.

    if (fValueNotFound)
    {
        lpmcx->lpszObjectClass = _strdup(lpmcx->lpszObjectClass);
        if (lpmcx->lpszObjectClass == NULL)
    hr = HR_LOG(E_FAIL);
    }

// Close the registry key (if it's open).

if (hKey)
{
dwStatus = RegCloseKey(hKey);
        if (dwStatus != ERROR_SUCCESS)
            hr = HR_LOG(E_FAIL);
}

    RETURN(hr);
}

//$--HrReadRegistryDWORD--------------------------------------------------------
//  Helper function that reads a DWORD value from the registry and makes sure
//  everything is as it should be.
// -----------------------------------------------------------------------------
static HRESULT HrReadRegistryDWORD(    // RETURNS: HRESULT
IN HKEY hKey,// open key handle
IN LPSTR lpszValueName,// name of value to get
OUT LPDWORD lpdwValue)// address of DWORD to return value
{
HRESULThr= NOERROR;
DWORDdwStatus= ERROR_SUCCESS;
DWORDdwValueType= 0;
DWORDdwValueSize= sizeof(*lpdwValue);

DEBUGPRIVATE("HrReadRegistryDWORD()\n");

// Check the parameters.

hr = CHK_HrReadRegistryDWORD(hKey, lpszValueName, lpdwValue);
if (FAILED(hr))
RETURN(hr);

dwStatus = RegQueryValueEx(
hKey,
lpszValueName,
0L,
&dwValueType,
(LPBYTE)lpdwValue,
&dwValueSize);
if (dwStatus != ERROR_SUCCESS)
{
hr = HR_LOG(E_FAIL);
}
else if (dwValueType != REG_DWORD)
{
hr = HR_LOG(E_FAIL);
}

RETURN(hr);
}

//$--HrCreateSharedMemory-------------------------------------------------------
//  Helper function that creates a shared memory section and a mutex.
//  NOTE: On successful return, the mutex will be owned by the current thread
//  and must be released.
// -----------------------------------------------------------------------------
static HRESULT HrCreateSharedMemory(// RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)// pointer to performance monitoring
// context
{
HRESULThr            = NOERROR;
CHARszMappingName[MAX_PATH]     = {0};
CHARszMutexName[MAX_PATH]       = {0};
BOOLfItWorked            = FALSE;
DWORDcch            = 0;

DEBUGPRIVATE("HrCreateSharedMemory()\n");

// Check the parameters.

hr = CHK_HrCreateSharedMemory(lpmcx);
if (FAILED(hr))
RETURN(hr);

ASSERTERROR(lpmcx->cbSharedMemorySize == 0,
"lpmcx->cbSharedMemorySize != 0");
ASSERTERROR(lpmcx->hSharedMemoryMapping == NULL,
"lpmcx->hSharedMemoryMapping != NULL");
ASSERTERROR(lpmcx->lpmsmSharedMemory == NULL,
"lpmcx->lpmsmSharedMemory != NULL");
ASSERTERROR(lpmcx->hSharedMemoryMutex == NULL,
"lpmcx->hSharedMemoryMutex != NULL");

// Create the name of the mutex that controls access to the shared memory.

cch = wsprintf(
szMutexName,
MON_MUTEX_NAME_TEMPLATE,
lpmcx->lpszObjectName);
ASSERTERROR(
(cch * sizeof(*szMutexName)) < sizeof(szMutexName),

"wsprintf overflow: szMutexName");
(void) CharUpper(szMutexName);

MODULE_STATUS1("mutex name = [%s]", szMutexName);

// First try to open the mutex and get ownership.

lpmcx->hSharedMemoryMutex = OpenMutex(
        MUTEX_ALL_ACCESS,
        FALSE,
        szMutexName);
    if (lpmcx->hSharedMemoryMutex)
    {
        hr = HrMonLockCounters();
        if (FAILED(hr))
            goto cleanup;
    }

    // If the mutex didn't exist then create it and get ownership.

    else
    {
        if (GetLastError() != ERROR_FILE_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    lpmcx->hSharedMemoryMutex = CreateMutex(
    psa,
    TRUE,
    szMutexName);
    if (lpmcx->hSharedMemoryMutex == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }

// Set the shared memory size.

lpmcx->cbSharedMemorySize = sizeof(MONSHAREDMEMORY);

// Create name of shared memory file mapping object.

cch = wsprintf(
szMappingName,
MON_MAPPING_NAME_TEMPLATE,
lpmcx->lpszObjectName);
ASSERTERROR(
(cch * sizeof(*szMappingName)) < sizeof(szMappingName),
"wsprintf overflow: szMappingName");
(void) CharUpper(szMappingName);

MODULE_STATUS1("mapping name = [%s]", szMappingName);

    // First try to open the shared memory file mapping object.

lpmcx->hSharedMemoryMapping = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,
        FALSE,
        szMappingName);

// If the file mapping object didn't exist then create it.

    if (lpmcx->hSharedMemoryMapping == NULL)
    {
        if (GetLastError() != ERROR_FILE_NOT_FOUND)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

    lpmcx->hSharedMemoryMapping = CreateFileMapping(
    (HANDLE)0xFFFFFFFF, // create in paging file
    psa, // no security
    PAGE_READWRITE, // read/write access
    0, // size (high)
    lpmcx->cbSharedMemorySize, // size(low)
    szMappingName);// mapping object name

    if (lpmcx->hSharedMemoryMapping == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }

// Map a view of the file mapping object.

lpmcx->lpmsmSharedMemory = MapViewOfFile(
lpmcx->hSharedMemoryMapping,
FILE_MAP_ALL_ACCESS,
0,
0,
lpmcx->cbSharedMemorySize);

if (lpmcx->lpmsmSharedMemory == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}
    ZeroMemory(lpmcx->lpmsmSharedMemory, lpmcx->cbSharedMemorySize);

// Set the flag that the shared memory is not valid yet.

lpmcx->lpmsmSharedMemory->fDataIsValid = FALSE;

    // Set the signature value.

    lpmcx->lpmsmSharedMemory->dwSignature = MON_SHARED_MEMORY_SIGNATURE;

cleanup:
RETURN(hr);
}

//$--HrFillInSharedMemory-------------------------------------------------------
//  Helper function that fills in the shared memory section with the structure
//  that is sent to the Windows NT Performance Monitor via the DLL.
//
//  This process is done in two passes.  The first pass sets up the various
//  pointers into the structure where data will be written, but does not write
//  any actual data.  After the first pass, the size of the resulting structure
//  is known, and a check is made to see that the resulting structure will fit
//  into the shared memory section.  The second pass then takes the pointers
//  that were set up by the first pass and fills in the actual data.
//
//  The structure that is created in the shared memory section has the
//  following format, which conforms to the specification in WINPERF.H for
//  the structure used to communicate with the Windows NT Performance Monitor:
//
//   /+===============================+
//  / |  _PERF_OBJECT_TYPE            |
// |  +===============================+
// |  |  _PERF_COUNTER_DEFINITION #1  |
// |  +-------------------------------+
//"Header ---->|  |  _PERF_COUNTER_DEFINITION #2  |
// Data" |  +-------------------------------+
// |  :  ...........................  :
// |  +-------------------------------+
//  \ |  _PERF_COUNTER_DEFINITION #N  |
//   >+===============================+
//  / |  _PERF_INSTANCE_DEFINITION    |
// |  +-------------------------------+
// |  |  Instance Name (UNICODE)      |
// |  +===============================+
// |  |  _PERF_COUNTER_BLOCK          |
// |  +-------------------------------+
//"Instance -->|  |  Counter #1 (COUNTER)         |
// Data" |  +-------------------------------+
// |  |  Counter #2 (COUNTER)         |
// |  +-------------------------------+
// |  |  ....................         :
// |  +-------------------------------+
//  \ |  Counter #N (COUNTER)         |
//   \+===============================+
//
//  In addition to the above structure, the shared memory section contains
//  some housekeeping information that is used to communicate with the
//  DLL, but is not sent to the Windows NT Performance Monitor.  This
//  information is described in the shared memory structure defined in
//  MONSHARE.H.  In this housekeeping information is information that tells
//  the DLL how to break the structure diagrammed above into two parts:
//  "Header Data" and "Instance Data".  This is done so that if there are two
//  instances of a gateway communicating with the same DLL, the DLL can easily
//  take the "Header Data" from one gateway and combine it with the
//  "Instance Data" sections from both gateways to form a single
//  structure to pass to the Windows NT Performance Monitor.  This allows
//  the user of Performance Monitor to see the gateways as a single object
//  with multiple instances, rather than two separate objects.
// -----------------------------------------------------------------------------
static HRESULT HrFillInSharedMemory(// RETURNS: HRESULT
IN LPMONCONTEXT lpmcx, // pointer to perf mon context
IN DWORD ccdCounters,// number of COUNTERDEF structures
IN LPCOUNTERDEF lpcdCounters)// array of COUNTERDEF structures
{
HRESULThr= NOERROR;

// Variables used to create _PERF_COUNTER_DEFINITION structures, and to
// create MONFOLDER and MONCOUNTER structures.

DWORDiCounter= 0;
LPCOUNTERDEFlpcdSrc= NULL;
PPERF_COUNTER_DEFINITIONlpDest= NULL;
LPCOUNTERDEFlpcdCounter= NULL;

// Pointers used to allocate chunks off of the
// MONSHAREDMEMORY.rgbDataBlock[] structure.

LPBYTElpbNextFreeByte= NULL;
LPBYTElpbNextFreeByteLim= NULL;

// Pointers to structures defined in winperf.h.

PPERF_OBJECT_TYPElpObjectType= NULL;
PPERF_COUNTER_DEFINITIONlpCounterDefinition= NULL;
PPERF_INSTANCE_DEFINITIONlpInstanceDefinition= NULL;
LPWSTRlpwszInstanceName= NULL;
DWORDcwInstanceNameSize= 0;
PPERF_COUNTER_BLOCKlpCounterBlock= NULL;
LPCOUNTERlpcntCounter= NULL;
    DWORD                       cLargeCounters          = 0;
    DWORD                       iCounterAllocated       = 0;
LPBYTElpbStructureEnd= NULL;

DEBUGPRIVATE("HrFillInSharedMemory()\n");

// Check the parameters.

hr = CHK_HrFillInSharedMemory(lpmcx, ccdCounters, lpcdCounters);
if (FAILED(hr))
RETURN(hr);

ASSERTERROR(lpmcx->lpcntCounter == NULL, "lpmcx->lpcntCounter != NULL");

    // Count the number of user-defined counters that are of size
    // PERF_SIZE_LARGE because we will have to allow double space for them.
    // While we're counting, if we find any counters of size PERF_SIZE_ZERO
    // or PERF_SIZE_VARIABLE_LEN then return an error, because we don't
    // support those types.

    for (iCounter = 0; iCounter < ccdCounters; iCounter++)
    {
        if (lpcdCounters[iCounter].ctStatistic == COUNTERTYPE_USER_DEFINED)
        {
            switch (lpcdCounters[iCounter].dwUserCounterType & 0x00000300)
            {
                case PERF_SIZE_LARGE:
                    cLargeCounters++;
                    break;

                case PERF_SIZE_DWORD:
                    break;

                default:
                    hr = HR_LOG(E_FAIL);
                    goto cleanup;
            }
        }
    }

    // Initially zero out the block in shared memory.

    ZeroMemory(lpmcx->lpmsmSharedMemory->rgbDataBlock, MON_DATA_BLOCK_SIZE);

// Set pointers into the build buffer area of shared memory.
// These pointers are used to get chunks of memory from the
// buffer and check for overflow.

lpbNextFreeByte = lpmcx->lpmsmSharedMemory->rgbDataBlock;
lpbNextFreeByteLim = lpbNextFreeByte + MON_DATA_BLOCK_SIZE;

// Set pointers into the build buffer for where the various structures
// will go (don't fill anything in just yet--we have to check for overflow
// first!).

hr = HrAlignPointer(4, &lpbNextFreeByte);
    if (FAILED(hr))
        goto cleanup;

lpObjectType = (PERF_OBJECT_TYPE *)lpbNextFreeByte;
lpbNextFreeByte += sizeof(*lpObjectType);

lpCounterDefinition = (PERF_COUNTER_DEFINITION *)lpbNextFreeByte;
lpbNextFreeByte += ccdCounters * sizeof(*lpCounterDefinition);

lpInstanceDefinition = (PERF_INSTANCE_DEFINITION *)lpbNextFreeByte;
lpbNextFreeByte += sizeof(*lpInstanceDefinition);

// NOTE: Instance name is always UNICODE!!!

lpwszInstanceName = (LPWSTR)lpbNextFreeByte;

// Calculate how many characters are needed for the conversion.
if (lstrlen(lpmcx->lpszObjectName) == 0)
{
cwInstanceNameSize = 0;
}
else
{
cwInstanceNameSize = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
lpmcx->lpszObjectName, -1, 0, 0);
if (cwInstanceNameSize == 0)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
cwInstanceNameSize--;
}

lpbNextFreeByte += (cwInstanceNameSize + 1) * sizeof(WCHAR);

hr = HrAlignPointer(4, &lpbNextFreeByte);
    if (FAILED(hr))
        goto cleanup;

lpCounterBlock = (PERF_COUNTER_BLOCK *)lpbNextFreeByte;
lpbNextFreeByte += sizeof(lpCounterBlock->ByteLength);

lpcntCounter = (LPCOUNTER)lpbNextFreeByte;
lpbNextFreeByte += (ccdCounters + cLargeCounters) * sizeof(COUNTER);

hr = HrAlignPointer(4, &lpbNextFreeByte);
    if (FAILED(hr))
        goto cleanup;

lpbStructureEnd = lpbNextFreeByte;

// Check for overflow.

if (lpbNextFreeByte > lpbNextFreeByteLim)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

// Fill in _PERF_OBJECT_TYPE structure.

lpObjectType->TotalByteLength = lpbStructureEnd - (LPBYTE)lpObjectType;
lpObjectType->DefinitionLength =
(LPBYTE)lpInstanceDefinition - (LPBYTE)lpObjectType;
lpObjectType->HeaderLength =
(LPBYTE)lpCounterDefinition - (LPBYTE)lpObjectType;
lpObjectType->ObjectNameTitleIndex =
lpmcx->dwFirstCounter + lpmcx->iObjectTitleOffset;
if (lpObjectType->ObjectNameTitleIndex > lpmcx->dwLastCounter)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
lpObjectType->ObjectNameTitle = NULL;
lpObjectType->ObjectHelpTitleIndex =
lpmcx->dwFirstHelp + lpmcx->iObjectTitleOffset;
if (lpObjectType->ObjectHelpTitleIndex > lpmcx->dwLastHelp)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
lpObjectType->ObjectHelpTitle = NULL;
lpObjectType->DetailLevel = lpmcx->dwObjectDetailLevel;
lpObjectType->NumCounters = ccdCounters;
lpObjectType->DefaultCounter = lpmcx->dwDefaultCounter;
lpObjectType->NumInstances = 1;
lpObjectType->CodePage = 0;
lpObjectType->PerfTime.LowPart = 0;
lpObjectType->PerfTime.HighPart = 0;
lpObjectType->PerfFreq.LowPart = 0;
lpObjectType->PerfFreq.HighPart = 0;

// Fill in _PERF_COUNTER_DEFINITION structures.

    // Set iCounterAllocated = 1 to skip a DWORD at the front for the
    // PERF_COUNTER_BLOCK header.

    iCounterAllocated = 1;

for (iCounter = 0; iCounter < ccdCounters; ++iCounter)
{
lpcdSrc = &lpcdCounters[iCounter];
lpDest = &lpCounterDefinition[iCounter];

ASSERT_DETAIL_LEVEL(lpcdSrc->dwDetailLevel,
"lpcdSrc->dwDetailLevel: invalid value");

lpDest->ByteLength = sizeof(*lpDest);
lpDest->CounterNameTitleIndex =
lpmcx->dwFirstCounter + lpcdSrc->iCounterTitleOffset;
if (lpDest->CounterNameTitleIndex > lpmcx->dwLastCounter)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
lpDest->CounterNameTitle = NULL;
lpDest->CounterHelpTitleIndex =
lpmcx->dwFirstHelp + lpcdSrc->iCounterTitleOffset;
if (lpDest->CounterHelpTitleIndex > lpmcx->dwLastHelp)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
lpDest->CounterNameTitle = NULL;
lpDest->DefaultScale = lpcdSrc->dwDefaultScale;
lpDest->DetailLevel = lpcdSrc->dwDetailLevel;
hr = HrComputeCounterType(
lpcdSrc->ctStatistic,
lpcdSrc->perPeriod,
lpcdSrc->dwUserCounterType,
&lpDest->CounterType);
        if (FAILED(hr))
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

        if ((lpcdSrc->dwUserCounterType & 0x00000300) == PERF_SIZE_LARGE)
        {
    lpDest->CounterSize = 2 * sizeof(COUNTER);
    lpDest->CounterOffset = iCounterAllocated * sizeof(COUNTER);
            iCounterAllocated += 2;
        }
        else
        {
    lpDest->CounterSize = sizeof(COUNTER);
    lpDest->CounterOffset = iCounterAllocated * sizeof(COUNTER);
            iCounterAllocated++;
        }
}

// Fill in _PERF_INSTANCE_DEFINITION structure.

lpInstanceDefinition->ByteLength =
(LPBYTE)lpCounterBlock - (LPBYTE)lpInstanceDefinition;
lpInstanceDefinition->ParentObjectTitleIndex = 0;
lpInstanceDefinition->ParentObjectInstance = 0;
lpInstanceDefinition->UniqueID = PERF_NO_UNIQUE_ID;
lpInstanceDefinition->NameOffset = sizeof(*lpInstanceDefinition);

// Fill in instance name.  NOTE: Instance name is always UNICODE!!!

lpInstanceDefinition->NameLength = (cwInstanceNameSize + 1) * sizeof(WCHAR);

if (lstrlen(lpmcx->lpszObjectName) == 0)
{
cwInstanceNameSize = 0;
}
else
{
if (
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpmcx->lpszObjectName,
-1, lpwszInstanceName, cwInstanceNameSize + 1) == 0)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}
lpwszInstanceName[cwInstanceNameSize] = 0;

// Fill in _PERF_COUNTER_BLOCK structure.

lpCounterBlock->ByteLength = lpbStructureEnd - (LPBYTE)lpCounterBlock;

// Fill in the shared memory header block (this is some information in the
// shared memory, before the _PERF blocks, which helps the DLL get easy
// access to the two halves of the _PERF structure group that it needs).

lpmcx->lpmsmSharedMemory->ibHeaderOffset =
(LPBYTE)lpObjectType - (LPBYTE)lpmcx->lpmsmSharedMemory;
lpmcx->lpmsmSharedMemory->cbHeaderSize =
(LPBYTE)lpInstanceDefinition - (LPBYTE)lpObjectType;
lpmcx->lpmsmSharedMemory->ibInstanceOffset =
(LPBYTE)lpInstanceDefinition - (LPBYTE)lpmcx->lpmsmSharedMemory;
lpmcx->lpmsmSharedMemory->cbInstanceSize =
lpbStructureEnd - (LPBYTE)lpInstanceDefinition;

// Save a pointer to the counters in the context (for use by
// HrMonBuildStructures).

lpmcx->lpcntCounter = lpcntCounter;

cleanup:
RETURN(hr);
}

//$--HrBuildStructures----------------------------------------------------------
//  Helper function that sets up structures representing folders and counters
//  in the performance monitoring context.
// -----------------------------------------------------------------------------
static HRESULT HrBuildStructures(    // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx,// pointer to perf mon context
IN DWORD ccdCounters,// number of COUNTERDEF structures
IN LPCOUNTERDEF lpcdCounters)// array of COUNTERDEF structures
{
HRESULThr= NOERROR;
HRESULThrT= NOERROR;

// Variables used to create MONFOLDER and MONCOUNTER structures.

LPMONCOUNTERlpmcCounter= NULL;
LPMONFOLDERlpmfFolder= NULL;

// Variables used to access COUNTERDEF structures.

DWORDiCounter= 0;
LPCOUNTERDEFlpcdCounter= NULL;

// Variables used to create jumping sliding windows (JSW's).

__int64dwlJSWStartTime= 0;

DEBUGPRIVATE("HrBuildStructures()\n");

// Check the parameters.

hr = CHK_HrBuildStructures(lpmcx, ccdCounters, lpcdCounters);
if (FAILED(hr))
RETURN(hr);

ASSERTERROR(lpmcx->lpmcMessagesTransferredIn == NULL,
"lpmcx->lpmcMessagesTransferredIn != NULL");
ASSERTERROR(lpmcx->lpmcMessagesTransferredOut == NULL,
"lpmcx->lpmcMessagesTransferredOut != NULL");
ASSERTERROR(lpmcx->lpmcBytesTransferredIn == NULL,
"lpmcx->lpmcBytesTransferredIn != NULL");
ASSERTERROR(lpmcx->lpmcBytesTransferredOut == NULL,
"lpmcx->lpmcBytesTransferredOut != NULL");
ASSERTERROR(lpmcx->lpmcNDRsIn == NULL,
"lpmcx->lpmcNDRsIn != NULL");
ASSERTERROR(lpmcx->lpmcNDRsOut == NULL,
"lpmcx->lpmcNDRsOut != NULL");
ASSERTERROR(lpmcx->lpmcAssociations == NULL,
"lpmcx->lpmcAssociations != NULL");
ASSERTERROR(lpmcx->lpmcFreeList == NULL,
"lpmcx->lpmcFreeList != NULL");
ASSERTERROR(lpmcx->lpmcJSWCounterList == NULL,
"lpmcx->lpmcJSWCounterList != NULL");
ASSERTERROR(lpmcx->lpmfFolderList == NULL,
"lpmcx->lpmfFolderList != NULL");

// Compute time to use as base for JSW's, which should be on an even
// minute boundry.

dwlJSWStartTime = (lpmcx->dwlCurrentTime / ONE_MINUTE) * ONE_MINUTE;

// Process each COUNTERDEF structure.  If it is for a user defined counter
// then return a pointer to it to the caller of HrMonInit().  If it is
// for any other type of counter, then add entries to the MONCOUNTER lists
// and the MONFOLDER list as appropriate and create a JSWindow structure
// if needed.

for (iCounter = 0; iCounter < ccdCounters; ++iCounter)
{
lpcdCounter = &lpcdCounters[iCounter];

// If this is a user defined counter then return a pointer to
// the actual counter in the place requested in the call to
// HrMonInit().

if (lpcdCounter->ctStatistic == COUNTERTYPE_USER_DEFINED)
{
ASSERTERROR(lpcdCounter->lppcntUserCounter != NULL,
"lpcdCounter->lppcntUserCounter == NULL");
ASSERTERROR(lpcdCounter->perPeriod == PERIODTYPE_NONE,
"lpcdCounter->perPeriod != PERIODTYPE_NONE");

*(lpcdCounter->lppcntUserCounter) =
&(lpmcx->lpcntCounter[iCounter]);

// Skip to the next COUNTERDEF.

continue;
}

// If we reach here then it's not a user defined counter.

ASSERTERROR(lpcdCounter->ctStatistic < COUNTERTYPE_LAST,
"lpcdCounter->ctStatistic: illegal value");
ASSERTERROR(lpcdCounter->perPeriod < PERIODTYPE_LAST,
"lpcdCounter->perPeriod: illegal value");
ASSERT_DETAIL_LEVEL(lpcdCounter->dwDetailLevel,
"lpcd->dwDetailLevel: illegal value");

// Allocate and fill in a new MONCOUNTER structure.  Put new
// MONCOUNTER structure on the free list so MonFreeEverything()
// will be able to find it and free it in case we have to abort.
// The free list is really just a temporary holding area that
// will never have more than one entry at a time.

lpmcCounter = malloc(sizeof(*lpmcCounter));
if (lpmcCounter == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}
ZeroMemory(lpmcCounter, sizeof(*lpmcCounter));

lpmcCounter->lpmcNext = lpmcx->lpmcFreeList;
lpmcx->lpmcFreeList = lpmcCounter;

        // If the period type is anything other than PERIODTYPE_CONTINUOUS
        // then we need to zero out the counter when HrMonUninit is called.
        // This makes sure all the counters go to zero for any PerfMon still
        // watching.  We shouldn't zero the "continuous" ones, because their
        // displayed value is based on their increase, and zeroing them may
        // result in a huge value being briefly displayed.  Of course, we
        // will see that huge value anyway if PerfMon is still watching when
        // the gateway restarts, but that seems like a better place to take
        // the hit.

        if (lpcdCounter->perPeriod != PERIODTYPE_CONTINUOUS)
            lpmcCounter->fZeroOnFree = TRUE;

// If there is a folder associated with this COUNTERDEF, then we need
// to find a MONFOLDER structure that matches it or create a new one if
// one does not already exist.  If there is no folder associated with
// this counter then we can skip this part.

lpmfFolder = NULL;

if (lpcdCounter->lpFolder != NULL)
{
// See if a MONFOLDER structure has already been defined for this
// folder.

hrT = HrMonFindFolder(lpmcx->lpmfFolderList, lpcdCounter->lpFolder,
&lpmfFolder);
if (FAILED(hrT))
{
            ULONGcValues= 0;
            LPSPropValuelpProps= NULL;
            SizedSPropTagArray(1, rgPropTags) =
                {1, {PR_MESSAGE_SIZE}};

// A MONFOLDER structure has not already been defined for
// this folder, so allocate one, fill it in and add it to
// the folder list.

lpmfFolder = malloc(sizeof(*lpmfFolder));
if (lpmfFolder == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}
ZeroMemory(lpmfFolder, sizeof(*lpmfFolder));

lpmfFolder->lpmcx = lpmcx;
lpmfFolder->lpFolder = lpcdCounter->lpFolder;

                // We need to do AddRef/Release because we are keeping
                // folder pointers around between HrMonInit and HrMonUninit.

                MAPICALL(lpmfFolder->lpFolder)->AddRef(lpmfFolder->lpFolder);

lpmfFolder->lpmfNext = lpmcx->lpmfFolderList;
lpmcx->lpmfFolderList = lpmfFolder;

                // Get the number of bytes in the folder (PR_MESSAGE_SIZE)
                // and store it in lpmfFolder->cTotalBytesEnteringFolder.
                // This will allow proper handling of the "bytes leaving
                // folder" counter in the case where messages are waiting
                // in the folder when monitoring starts.

hr = lpmfFolder->lpFolder->lpVtbl->GetProps(
lpmfFolder->lpFolder,
(LPSPropTagArray) &rgPropTags,
fMapiUnicode,
&cValues,
&lpProps);
                if (FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
                {
                    if (hr == MAPI_W_ERRORS_RETURNED)
                    {
                        MAPIFREEBUFFER(lpProps);
                        hr = HR_LOG(E_FAIL);
                    }
                    goto cleanup;
                }

lpmfFolder->cTotalBytesEnteringFolder = lpProps[0].Value.ul;

MAPIFREEBUFFER(lpProps);

                // Create a critical section for this folder.

InitializeCriticalSection(&lpmfFolder->csCriticalSection);
lpmfFolder->fCriticalSectionInitialized = TRUE;

// Set the folder to get it's initial size.

lpmfFolder->fUpdateFolderSize = TRUE;
}
}

// Take the MONCOUNTER structure off the free list in preparation
// for linking it into another list.

ASSERTERROR(lpmcx->lpmcFreeList != NULL, "free list empty");

lpmcCounter = lpmcx->lpmcFreeList;
lpmcx->lpmcFreeList = lpmcx->lpmcFreeList->lpmcNext;

// Link the MONCOUNTER structure into the proper list.  This could
// be a general list attached to the context structure, or a folder-
// specific list attached to a MONFOLDER structure.

hr = HrLinkCounterIntoList(
lpmcx,
lpmfFolder,
lpmcCounter,
lpcdCounter->ctStatistic);
if (FAILED(hr))
{
goto cleanup;
}

// Create the JSW attached to the counter structure if one is requested
// by the PERIODTYPE.

if (lpcdCounter->perPeriod == PERIODTYPE_LAST_N_MINUTES)
{
__int64 dwlJumpTime = ONE_MINUTE;

hr = HrJSOpen(
JSWINDOW_TOTAL_RATE,
&TO_FILETIME(dwlJSWStartTime),
&TO_FILETIME(dwlJumpTime),
lpcdCounter->cMinutes,
&lpmcCounter->hjswJumpWindow);

if (FAILED(hr))
{
lpmcCounter->hjswJumpWindow = NULL;// Just to make sure.
goto cleanup;
}

// Also add the counter structure to the list of counter structures
// that have JSWs.

lpmcCounter->lpmcNextJSW = lpmcx->lpmcJSWCounterList;
lpmcx->lpmcJSWCounterList = lpmcCounter;
}

// Add a pointer to the counter to the MONCOUNTER structure.

if (lpmcCounter)
{
lpmcCounter->lpcntCounter = &(lpmcx->lpcntCounter[iCounter]);
}

} // end for

cleanup:
RETURN(hr);
}

//$--HrMonFindFolder------------------------------------------------------------
//  Helper function that takes a MAPIFOLDER as input and searches a list of
//  folders to find a MONFOLDER structure for that folder.  If the folder is
//  found a pointer to it is placed in the memory pointed to by lppmfFolder and
//  NOERROR is returned.  Otherwise, and error is returned.
// -----------------------------------------------------------------------------
static HRESULT HrMonFindFolder(// RETURNS: HRESULT
IN  LPMONFOLDER   lpmfFolderList,// list of folder structs to search
IN  LPMAPIFOLDER  lpFolder,// folder to search for
OUT LPMONFOLDER * lppmfFolder)// folder found
{
HRESULThr= NOERROR;

DEBUGPRIVATE("HrMonFindFolder()\n");

// Check the parameters.

hr = CHK_HrMonFindFolder(lpmfFolderList, lpFolder, lppmfFolder);
if (FAILED(hr))
RETURN(hr);

while (lpmfFolderList && lpmfFolderList->lpFolder != lpFolder)
{
lpmfFolderList = lpmfFolderList->lpmfNext;

ASSERT_READ_PTR_OR_NULL(lpmfFolderList, sizeof(*lpmfFolderList),
"lpmfFolderList: bad read pointer");
}

if (lpmfFolderList == NULL)
{
hr = HR_LOG(EDK_E_NOT_FOUND);
}
else
{
*lppmfFolder = lpmfFolderList;
}

RETURN(hr);
}

//$--HrStartUpdateThread--------------------------------------------------------
//  Helper function that starts the thread that wakes up periodically to update
//  the jumping sliding windows and folders.
// -----------------------------------------------------------------------------
static HRESULT HrStartUpdateThread(     // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)              // pointer to perf mon context
{
HRESULThr= NOERROR;
SECURITY_ATTRIBUTESsaEvent= {0, NULL, TRUE};

DEBUGPRIVATE("HrStartUpdateThread()\n");

// Check the parameters.

hr = CHK_HrStartUpdateThread(lpmcx);
if (FAILED(hr))
RETURN(hr);

ASSERTERROR(!lpmcx->fUpdateThreadRunning, "thread already running");
ASSERTERROR(lpmcx->hUpdateThread == NULL, "lpmcx->hUpdateThread != NULL");
ASSERTERROR(lpmcx->hUpdateThreadWakeEvent == NULL,
"lpmcx->hUpdateThreadWakeEvent != NULL");
ASSERTERROR(lpmcx->dwUpdateThreadID == 0, "lpmcx->dwUpdateThreadID != 0");
ASSERTERROR(lpmcx->fUpdateThreadTerminate == FALSE,
"lpmcx->fUpdateThreadTerminate != FALSE");

// Create an unnamed, inheritable, auto-reset event.  This will be
// set by the gateway process to wake the thread, either to update
// or to terminate.  Start it signaled so the update thread wakes
// up to set the initial values of the folder counters.

lpmcx->hUpdateThreadWakeEvent = CreateEvent(&saEvent, FALSE, TRUE, NULL);
if (lpmcx->hUpdateThreadWakeEvent == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Create the thread.

lpmcx->hUpdateThread = CreateThread(NULL, 0, UpdateThread, lpmcx, 0,
&lpmcx->dwUpdateThreadID);
if (lpmcx->hUpdateThread == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

cleanup:
if (FAILED(hr))
{
CLOSEHANDLE(lpmcx->hUpdateThreadWakeEvent);
}
RETURN (hr);
}

//$--UpdateThread---------------------------------------------------------------
//  Function that is called as a seperate thread.  It wakes up periodically and
//  updates the jumping sliding windows and folders.
// -----------------------------------------------------------------------------
static DWORD WINAPI UpdateThread(    // RETURNS: error code
IN LPVOID lpThreadParameter)// pointer to perf mon context
{
static BOOLfGetPropsErrorLoggedOnce= FALSE;
static BOOLfJSWFailureLoggedOnce= FALSE;

LPMONCONTEXTlpmcx= lpThreadParameter;

HRESULThr= NOERROR;
DWORDdwStatus= ERROR_SUCCESS;

LPMONCOUNTERlpmc= NULL;
LPMONFOLDERlpmf= NULL;

DWORDdwMillisecondsToWait= 0;
DWORDdwWindowValue= 0;
DWORDdwFractionCompleteNum= 0;
DWORDdwFractionCompleteDen= 0;

DWORDcMessagesInFolder= 0;
DWORDcBytesInFolder= 0;
DWORDcMessagesEnteringFolder= 0;
DWORDcBytesEnteringFolder= 0;
DWORDcMessagesLeavingFolder= 0;
DWORDcBytesLeavingFolder= 0;

ULONGcValues= 0;
LPSPropValuelpProps= NULL;
SizedSPropTagArray(2, rgPropTags) =
    {2, {PR_CONTENT_COUNT, PR_MESSAGE_SIZE}};

    BOOL            fCountersLocked             = FALSE;

DEBUGPRIVATE("UpdateThread()\n");

// Check the parameters.

hr = CHK_UpdateThread(lpThreadParameter);
if (FAILED(hr))
RETURN(hr);

// Loop until flag is set to stop the thread.

while (!lpmcx->fUpdateThreadTerminate)
{
// If we have JSW's, then calculate how many milliseconds to wait
// until the next even minute mark.  Wait an extra second just to
// be sure we don't wake up too early.

if (lpmcx->lpmcJSWCounterList)
{
__int64 dwlCurrentTime = 0;

    hr = HrGetCurrentTime(&dwlCurrentTime);
            if (FAILED(hr))
                goto cleanup;

dwMillisecondsToWait = (DWORD)
(((ONE_MINUTE - (dwlCurrentTime % ONE_MINUTE))
+ 10000000) / 10000);
}

// Otherwise, wait until the other thread wakes us up.

else
{
dwMillisecondsToWait = INFINITE;
}

// Wait for specified time or wake event.

dwStatus = WaitForSingleObject(
lpmcx->hUpdateThreadWakeEvent,
dwMillisecondsToWait);
        if (dwStatus != WAIT_TIMEOUT && dwStatus != WAIT_OBJECT_0)
        {
            hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Check for terminate request.

if (lpmcx->fUpdateThreadTerminate)
goto cleanup;

// Update the folder counters from the "update request" variables.

if (lpmcx->lpmfFolderList)
{
for (
lpmf = lpmcx->lpmfFolderList;
lpmf;
lpmf = lpmf->lpmfNext)
{
cMessagesEnteringFolder = 0;
cBytesEnteringFolder = 0;
cMessagesLeavingFolder = 0;
cBytesLeavingFolder = 0;
cMessagesInFolder = 0;
cBytesInFolder = 0;

if (lpmf->fUpdateFolderSize)
{
// Check for terminate request.

if (lpmcx->fUpdateThreadTerminate)
goto cleanup;

// Get and reset the "update request" variables.

EnterCriticalSection(&lpmf->csCriticalSection);

cMessagesEnteringFolder = lpmf->cMessagesEnteringFolder;
lpmf->cMessagesEnteringFolder = 0;
cBytesEnteringFolder = lpmf->cBytesEnteringFolder;
lpmf->cBytesEnteringFolder = 0;
cMessagesLeavingFolder = lpmf->cMessagesLeavingFolder;
lpmf->cMessagesLeavingFolder = 0;
cBytesLeavingFolder = lpmf->cBytesLeavingFolder;
lpmf->cBytesLeavingFolder = 0;
lpmf->fUpdateFolderSize = FALSE;

LeaveCriticalSection(&lpmf->csCriticalSection);

// Check for terminate request.

if (lpmcx->fUpdateThreadTerminate)
goto cleanup;

// Get number of messages and bytes in folder.

if (lpmf->lpmcMessagesInFolder ||
    lpmf->lpmcBytesInFolder ||
    lpmf->lpmcBytesLeavingFolder)
{
// Get the PR_CONTENT_COUNT and PR_MESSAGE_SIZE props.

hr = lpmf->lpFolder->lpVtbl->GetProps(
lpmf->lpFolder,
(LPSPropTagArray) &rgPropTags,
fMapiUnicode,
&cValues,
&lpProps);
                        if (FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
                        {
                            if (hr == MAPI_W_ERRORS_RETURNED)
                            {
                                MAPIFREEBUFFER(lpProps);
                            }
                            goto cleanup;
                        }

cMessagesInFolder = lpProps[0].Value.ul;
cBytesInFolder = lpProps[1].Value.ul;

MAPIFREEBUFFER(lpProps);
}

// Check for terminate request.

if (lpmcx->fUpdateThreadTerminate)
goto cleanup;

// If they want the number of bytes leaving the folder
// then calculate it and keep track of it here.

if (lpmf->lpmcBytesLeavingFolder)
{
// Compute the total number of bytes entering folder
// so far.

lpmf->cTotalBytesEnteringFolder += cBytesEnteringFolder;

// Figure out how many bytes should be in the folder
// according to the running totals of bytes entering
// and bytes leaving that we have reported.  If the
// "should be" is higher than the actual, then report
// the difference as the number of bytes leaving the
// folder, and update the reported number leaving.

if (lpmf->cTotalBytesEnteringFolder -
lpmf->cTotalBytesLeavingFolder >
cBytesInFolder)
{
cBytesLeavingFolder =
lpmf->cTotalBytesEnteringFolder -
lpmf->cTotalBytesLeavingFolder -
cBytesInFolder;
lpmf->cTotalBytesLeavingFolder +=
cBytesLeavingFolder;
}
}

// Lock the counters.

hr = HrMonLockCounters();
                    if (FAILED(hr))
                        goto cleanup;
                    fCountersLocked = TRUE;

                    // Get the system time.

hr = HrGetCurrentTime(&lpmcx->dwlCurrentTime);
                    if (FAILED(hr))
                        goto cleanup;

                    // Update the counters.

hr = HrAddToCounters(
lpmcx,
lpmf->lpmcMessagesEnteringFolder,
cMessagesEnteringFolder);
                    if (FAILED(hr))
                        goto cleanup;

hr = HrAddToCounters(
lpmcx,
lpmf->lpmcBytesEnteringFolder,
cBytesEnteringFolder);
                    if (FAILED(hr))
                        goto cleanup;

hr = HrAddToCounters(
lpmcx,
lpmf->lpmcMessagesLeavingFolder,
cMessagesLeavingFolder);
                    if (FAILED(hr))
                        goto cleanup;

hr = HrAddToCounters(
lpmcx,
lpmf->lpmcBytesLeavingFolder,
cBytesLeavingFolder);
                    if (FAILED(hr))
                        goto cleanup;

hr = HrSetCounters(
lpmf->lpmcMessagesInFolder,
cMessagesInFolder);
                    if (FAILED(hr))
                        goto cleanup;

hr = HrSetCounters(
lpmf->lpmcBytesInFolder,
cBytesInFolder);
                    if (FAILED(hr))
                        goto cleanup;

                    // Unlock the counters.

                    fCountersLocked = FALSE;
hr = HrMonUnlockCounters();
                    if (FAILED(hr))
                        goto cleanup;
}
}
}

// Update the JSW counters.

if (lpmcx->lpmcJSWCounterList)
{
for (
lpmc = lpmcx->lpmcJSWCounterList;
lpmc;
lpmc = lpmc->lpmcNextJSW)
{
ASSERT_READ_PTR(lpmc, sizeof(*lpmc),
"lpmc: bad write pointer");
ASSERT_WRITE_PTR(lpmc->lpcntCounter,
sizeof(*lpmc->lpcntCounter),
"lpmc->lpcntCounter: bad write pointer");
ASSERTERROR(lpmc->hjswJumpWindow != NULL,
"lpmc->hjswJumpWindow == NULL");

// Check for terminate request.

if (lpmcx->fUpdateThreadTerminate)
goto cleanup;

// Lock the counters.

hr = HrMonLockCounters();
                if (FAILED(hr))
                    goto cleanup;
                fCountersLocked = TRUE;

// Get the value of the JSWindow (update system time).

hr = HrGetCurrentTime(&lpmcx->dwlCurrentTime);
                if (FAILED(hr))
                    goto cleanup;

hr = HrJSGetValue(
lpmc->hjswJumpWindow,
&TO_FILETIME(lpmcx->dwlCurrentTime),
&dwWindowValue,
&dwFractionCompleteNum,
&dwFractionCompleteDen);
if (FAILED(hr))
                    goto cleanup;

*lpmc->lpcntCounter = dwWindowValue;

                // Unlock the counters.

                fCountersLocked = FALSE;
hr = HrMonUnlockCounters();
                if (FAILED(hr))
                    goto cleanup;
} // end for

} // end if

} // end while (!lpmcx->fUpdateThreadTerminate)

cleanup:
    // Unlock the counters if they're locked.

    if (fCountersLocked)
    {
    hr = HrMonUnlockCounters();
        if (FAILED(hr))
            HR_LOG(hr);
    }

    // Always return success.

return(ERROR_SUCCESS);
}

//$--HrFreeEverything-----------------------------------------------------------
//  Helper function that shuts down the thread, monitoring, and frees memory.
// -----------------------------------------------------------------------------
static HRESULT HrFreeEverything(// RETURNS: HRESULT
IN LPMONCONTEXT *lppmcx)// the address of the pointer to the
// performance monitoring context (the
// pointer will be set to NULL)
{
BOOLfItWorked= FALSE;
HRESULThr= NOERROR;
HRESULThrT= NOERROR;
LPMONCONTEXTlpmcx= NULL;
LPMONFOLDERlpmf= NULL;
LPMONCOUNTERlpmc= NULL;

DEBUGPRIVATE("HrFreeEverything()\n");

// Check the parameters.

hr = CHK_HrFreeEverything(lppmcx);
if (FAILED(hr))
RETURN(hr);

// This pointer is easier to use.

lpmcx = *lppmcx;

// Free the context only if there is one allocated.

if (lpmcx)
{
// Tell the update thread to shut down and wait for it to do so.

if (lpmcx->hUpdateThread)
{
lpmcx->fUpdateThreadTerminate = TRUE;

hrT = HrWakeUpdateThread(lpmcx);
            if (FAILED(hrT))
                hr = hrT;
else
{
DWORD dw = WaitForSingleObject(lpmcx->hUpdateThread, INFINITE);
if (dw != WAIT_OBJECT_0)
{
                    hr = HR_LOG(E_FAIL);
}
}
CLOSEHANDLE(lpmcx->hUpdateThread);
lpmcx->dwUpdateThreadID = 0;
}

// Close the handle to the thread termination event.

CLOSEHANDLE(lpmcx->hUpdateThreadWakeEvent);

// Unhook monitoring routines and delete critical sections for folders.

for (
lpmf = lpmcx->lpmfFolderList;
lpmf;
lpmf = lpmf->lpmfNext)
{
if (lpmf->fCriticalSectionInitialized)
{
DeleteCriticalSection(&lpmf->csCriticalSection);
lpmf->fCriticalSectionInitialized = FALSE;
}

if (lpmf->lpTable)
{
hrT = MAPICALL(lpmf->lpTable)->
Unadvise(lpmf->lpTable, lpmf->ulConnection);
                if (FAILED(hrT))
                    hr = hrT;
lpmf->lpTable = NULL;

ULRELEASE(lpmf->lpTable);
lpmf->lpTable = NULL;
}
ULRELEASE(lpmf->lpAdvise);
lpmf->lpAdvise = NULL;
lpmf->ulConnection = 0;
}

// Free strings attached to the context.

ASSERT_STRING_PTR_OR_NULL(lpmcx->lpszObjectName,
"lpmcx->lpszObjectName: bad string pointer");
FREE(lpmcx->lpszObjectName);

ASSERT_STRING_PTR_OR_NULL(lpmcx->lpszObjectClass,
"lpmcx->lpszObjectClass: bad string pointer");
FREE(lpmcx->lpszObjectClass);

// Close all the JSW's.

for (lpmc = lpmcx->lpmcJSWCounterList; lpmc; lpmc = lpmc->lpmcNextJSW)
{
ASSERTERROR(lpmc->hjswJumpWindow != NULL,
"lpmc->hjswJumpWindow == NULL");

JSClose(lpmc->hjswJumpWindow);
lpmc->hjswJumpWindow = NULL;
}
lpmcx->lpmcJSWCounterList = NULL;

// Free all the MONCOUNTER structures attached directly to the context.

hrT = HrFreeCounterList(&lpmcx->lpmcMessagesTransferredIn);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcMessagesTransferredOut);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcBytesTransferredIn);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcBytesTransferredOut);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcNDRsIn);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcNDRsOut);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcAssociations);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmcx->lpmcFreeList);
        if (FAILED(hrT))
            hr = hrT;

// Free all the MONFOLDER structures, including the MONCOUNTER
// structures attached to the folders.

hrT = HrFreeFolderList(&lpmcx->lpmfFolderList);
        if (FAILED(hrT))
            hr = hrT;

// If we mapped the shared memory, unmap the view of the file.
        // NOTE: LEAVE THE fDateIsValid FLAG UNCHANGED SO ANYBODY STILL
        // WATCHING US WILL SEE VALID DATA!!!

if (lpmcx->lpmsmSharedMemory)
{
fItWorked = UnmapViewOfFile(lpmcx->lpmsmSharedMemory);
if (!fItWorked)
{
                hr = HR_LOG(E_FAIL);
}
lpmcx->lpmsmSharedMemory = NULL;
}

// Close file mapping object if it is open.

CLOSEHANDLE(lpmcx->hSharedMemoryMapping);

// Clear other shared memory variables.

lpmcx->cbSharedMemorySize = 0;

// Close mutex handle if it is open.

CLOSEHANDLE(lpmcx->hSharedMemoryMutex);

// Free the context itself.

FREE(*lppmcx);
}

    RETURN(hr);
}

//$--HrFreeCounterList----------------------------------------------------------
//  Helper function that frees a list of MONCOUNTER structures.
// -----------------------------------------------------------------------------
static HRESULT HrFreeCounterList(// RETURNS: HRESULT
IN LPMONCOUNTER *lppmcCounterList)// address of pointer to first element
// of counter list to be freed
{
HRESULThr= NOERROR;
LPMONCOUNTER lpmc= NULL;
LPMONCOUNTERlpmcNext= NULL;

DEBUGPRIVATE("HrFreeCounterList()\n");

// Check the parameters.

hr = CHK_HrFreeCounterList(lppmcCounterList);
if (FAILED(hr))
RETURN(hr);

lpmc = *lppmcCounterList;
while (lpmc)
{
ASSERT_WRITE_PTR(lpmc, sizeof(*lpmc), "lpmc: bad write pointer");
ASSERTWARNING(lpmc->hjswJumpWindow == NULL,
"lpmc->hjswJumpWindow != NULL");

        // Zero out the actual counter if requested.

        if (lpmc->fZeroOnFree && lpmc->lpcntCounter)
            *(lpmc->lpcntCounter) = 0;

        // Now free this node and move to the next one.

lpmcNext = lpmc->lpmcNext;
FREE(lpmc);
lpmc = lpmcNext;
}
*lppmcCounterList = NULL;

    RETURN(hr);
}

//$--HrFreeFolderList-----------------------------------------------------------
//  Helper function that frees a list of MONFOLDER structures and the
//  MONCOUNTER structures attached to them.
// -----------------------------------------------------------------------------
static HRESULT HrFreeFolderList(// RETURNS: HRESULT
IN LPMONFOLDER *lppmfFolderList)// address of pointer to first element
// of folder list to be freed
{
HRESULThr= NOERROR;
HRESULThrT= NOERROR;
LPMONFOLDER lpmf= NULL;
LPMONFOLDERlpmfNext= NULL;

DEBUGPRIVATE("HrFreeFolderList()\n");

// Check the parameters.

hr = CHK_HrFreeFolderList(lppmfFolderList);
if (FAILED(hr))
RETURN(hr);

lpmf = *lppmfFolderList;
while (lpmf)
{
        ULONG ulRefCount = 0;

ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");

        // We need to do AddRef/Release because we are keeping
        // folder pointers around between HrMonInit and HrMonUninit.

        ulRefCount = MAPICALL(lpmf->lpFolder)->Release(lpmf->lpFolder);
        ASSERTWARNING(ulRefCount <= 2, "ulRefCount > 2");

hrT = HrFreeCounterList(&lpmf->lpmcMessagesInFolder);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmf->lpmcBytesInFolder);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmf->lpmcMessagesEnteringFolder);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmf->lpmcBytesEnteringFolder);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmf->lpmcMessagesLeavingFolder);
        if (FAILED(hrT))
            hr = hrT;

hrT = HrFreeCounterList(&lpmf->lpmcBytesLeavingFolder);
        if (FAILED(hrT))
            hr = hrT;

lpmfNext = lpmf->lpmfNext;
FREE(lpmf);
lpmf = lpmfNext;
}
*lppmfFolderList = NULL;

    RETURN(hr);
}

//$--HrAddToCounters------------------------------------------------------------
//  Helper function that adds a value to each of the counters on the list.
// -----------------------------------------------------------------------------
static HRESULT HrAddToCounters(// RETURNS: HRESULT
IN LPMONCONTEXT lpmcx,// pointer to perf mon context
IN LPMONCOUNTER lpmcList,// pointer to list of counters
IN LONG dwAmount)// amount to add to each one
{
static BOOLfLoggedOnce= FALSE;
HRESULThr= NOERROR;
LPMONCOUNTERlpmc= NULL;

DEBUGPRIVATE("HrAddToCounters()\n");

// Check the parameters.

hr = CHK_HrAddToCounters(lpmcx, lpmcList, dwAmount);
if (FAILED(hr))
RETURN(hr);

for (lpmc = lpmcList; lpmc; lpmc = lpmc->lpmcNext)
{
ASSERT_READ_PTR(lpmc, sizeof(*lpmc), "lpmc: bad read pointer");

if (lpmc->hjswJumpWindow)
{
// Update jump window.

hr = HrJSCollectDataPoint(
lpmc->hjswJumpWindow,
&TO_FILETIME(lpmcx->dwlCurrentTime),
dwAmount);
if (FAILED(hr))
                goto cleanup;
}
else
{
// Update counter directly.

ASSERT_WRITE_PTR(
lpmc->lpcntCounter,
sizeof(*lpmc->lpcntCounter),
"lpmc->lpcntCounter: bad write pointer");

*lpmc->lpcntCounter += dwAmount;
}
}

cleanup:
    RETURN(hr);
}

//$--HrSetCounters--------------------------------------------------------------
//  Helper function that assigns a value to each of the counters on the list.
// -----------------------------------------------------------------------------
static HRESULT HrSetCounters(// RETURNS: HRESULT
IN LPMONCOUNTER lpmcList,// pointer to list of counters
IN DWORD dwAmount)// amount to set each one to
{
HRESULThr= NOERROR;
LPMONCOUNTERlpmc= NULL;

DEBUGPRIVATE("HrSetCounters()\n");

// Check the parameters.

hr = CHK_HrSetCounters(lpmcList, dwAmount);
if (FAILED(hr))
RETURN(hr);

for (lpmc = lpmcList; lpmc; lpmc = lpmc->lpmcNext)
{
ASSERT_READ_PTR(lpmc, sizeof(*lpmc), "lpmc: bad read pointer");
ASSERTERROR(lpmc->hjswJumpWindow == NULL,
"lpmc->hjswJumpWindow != NULL");
ASSERT_WRITE_PTR(lpmc->lpcntCounter, sizeof(*lpmc->lpcntCounter),
"lpmc->lpcntCounter: bad write pointer");

*lpmc->lpcntCounter = dwAmount;
}

    RETURN(hr);
}

//$--HrComputeCounterType-------------------------------------------------------
//  Helper function that computes the counter type value to use in the
//  _PERF_COUNTER_DEFINITION structure defined in winperf.h.
// -----------------------------------------------------------------------------
static HRESULT HrComputeCounterType(// RETURNS: HRESULT
IN  COUNTERTYPE ctStatistic,// the statistic this counter monitors
IN  PERIODTYPE perPeriod,// the period of this counter
IN  DWORD dwUserCounterType,// the CounterType (if ctStatistic is
// COUNTERTYPE_USER_DEFINED)
OUT DWORD * lpdwCounterType)// returned counter type
{
HRESULThr= NOERROR;
DWORDdwCounterType= PERF_COUNTER_RAWCOUNT;

DEBUGPRIVATE("HrComputeCounterType()\n");

// Check the parameters.

hr = CHK_HrComputeCounterType(
ctStatistic,
perPeriod,
dwUserCounterType,
lpdwCounterType);
if (FAILED(hr))
RETURN(hr);

switch (ctStatistic)
{
case COUNTERTYPE_MESSAGES_IN_FOLDER:
case COUNTERTYPE_BYTES_IN_FOLDER:
case COUNTERTYPE_ASSOCIATIONS:
ASSERTERROR(perPeriod == PERIODTYPE_NONE,
"perPeriod != PERIODTYPE_NONE");
dwCounterType = PERF_COUNTER_RAWCOUNT;
break;

case COUNTERTYPE_MESSAGES_ENTERING_FOLDER:
case COUNTERTYPE_BYTES_ENTERING_FOLDER:
case COUNTERTYPE_MESSAGES_LEAVING_FOLDER:
case COUNTERTYPE_BYTES_LEAVING_FOLDER:
case COUNTERTYPE_MESSAGES_TRANSFERRED_IN:
case COUNTERTYPE_BYTES_TRANSFERRED_IN:
case COUNTERTYPE_MESSAGES_TRANSFERRED_OUT:
case COUNTERTYPE_BYTES_TRANSFERRED_OUT:
case COUNTERTYPE_NDRS_IN:
case COUNTERTYPE_NDRS_OUT:
ASSERTERROR(perPeriod != PERIODTYPE_NONE,
"perPeriod == PERIODTYPE_NONE");
if (perPeriod == PERIODTYPE_CONTINUOUS)
{
dwCounterType = PERF_COUNTER_COUNTER;
}
else
{
dwCounterType = PERF_COUNTER_RAWCOUNT;
}
break;

case COUNTERTYPE_USER_DEFINED:
dwCounterType = dwUserCounterType;
break;

default:
            hr = HR_LOG(E_FAIL);
goto cleanup;
}

*lpdwCounterType = dwCounterType;

cleanup:
    RETURN(hr);
}

//$--HrAlignPointer-------------------------------------------------------------
//  Helper function that moves a pointer forward, if nescessary, so that it has
//  a given alignment.  NOTE: the alignment must be a power of 2.
// -----------------------------------------------------------------------------
static HRESULT HrAlignPointer(    // RETURNS: HRESULT
IN     DWORD cbAlignment,// the alignment desired
IN OUT LPVOID * lppvPointer)// the pointer to be aligned
{
HRESULThr= NOERROR;

DEBUGPRIVATE("HrAlignPointer()\n");

// Make sure the alignment is a power of 2.

// Check the parameters.

hr = CHK_HrAlignPointer(cbAlignment, lppvPointer);
if (FAILED(hr))
RETURN(hr);

*lppvPointer = ((LPVOID)
(((DWORD)(((LPBYTE)(*lppvPointer)) + cbAlignment - 1)) &
(~(cbAlignment - 1)))
);

    RETURN(hr);
}

//$--FolderNotification---------------------------------------------------------
//  Function called by the notification engine when a folder is changed.
//It sets the "update request" variables to tell the folder update thread
//to update the counters associated with this folder.  In order to make this
//routine run faster it does not update the counters directly.  Since the
//notification engine only allows one notification routine per process to
//run at any one time, this improves efficiency.
// -----------------------------------------------------------------------------
static ULONG STDAPICALLTYPE FolderNotification(   // RETURNS: S_OK (always)
IN LPVOID lpvContext,// pointer to MONFOLDER structure
IN DWORD cNotification,// number of elements in following array
IN LPNOTIFICATION lpNotifications)// array of notification structures
{
HRESULThr= NOERROR;
LPMONFOLDERlpmf= lpvContext;
ULONGcValues= 0;
LPSPropValuelpProps= NULL;

DWORDcBytesEnteringFolder= 0;
DWORDcMessagesEnteringFolder= 0;
DWORDcBytesLeavingFolder= 0;
DWORDcMessagesLeavingFolder= 0;
BOOLfUpdateFolderSize= FALSE;

DEBUGPRIVATE("FolderNotification()\n");

// Check the parameters.

hr = CHK_FolderNotification(lpvContext, cNotification, lpNotifications);
if (FAILED(hr))
return (S_OK);

// If cNotification == 0, just take a snapshot of the folder.

if (cNotification == 0)
{
fUpdateFolderSize = TRUE;
}

// Go through the list of notifications, counting up the number of bytes
// and messages that entered or left the folder.  Also figure out if the
// folder size changed so we know if we need to get the new size.

for (; cNotification; cNotification--, lpNotifications++)
{
// If it's not a table notification then ignore it.

if (lpNotifications->ulEventType == fnevTableModified)
{
// Handle different types of table notifications.

switch (lpNotifications->info.tab.ulTableEvent)
{
case TABLE_ROW_ADDED:
// Update counters for bytes and messages entering folder.

if (lpmf->lpmcBytesEnteringFolder)
{
for (
lpProps = lpNotifications->info.tab.row.lpProps,
cValues = lpNotifications->info.tab.row.cValues;
cValues;
cValues--, lpProps++)
{
if (lpProps->ulPropTag == PR_MESSAGE_SIZE)
{
cBytesEnteringFolder += lpProps->Value.ul;
break;
}
}
ASSERTWARNING(cValues,
"PR_MESSAGE_SIZE not found: 0 assumed");
}

cMessagesEnteringFolder++;

fUpdateFolderSize = TRUE;
break;

case TABLE_ROW_DELETED:
// Update counter for messages leaving folder.  The
// number of bytes leaving the folder will be calculated
// in the thread, since the notification engine doesn't
//  give us that information.

cMessagesLeavingFolder++;

fUpdateFolderSize = TRUE;
break;

case TABLE_SORT_DONE:
case TABLE_RESTRICT_DONE:
case TABLE_SETCOL_DONE:
// Nothing really changed in the folder.
break;

default:
// (fall through)
case TABLE_ROW_MODIFIED:
case TABLE_CHANGED:
case TABLE_ERROR:
fUpdateFolderSize = TRUE;
break;
} // end switch
} // end if
} // end for

// Now set the "update request" variables for any counters that need
// updating.

if (fUpdateFolderSize)
{
EnterCriticalSection(&lpmf->csCriticalSection);

lpmf->fUpdateFolderSize = TRUE;
if (cBytesEnteringFolder && lpmf->lpmcBytesEnteringFolder)
lpmf->cBytesEnteringFolder += cBytesEnteringFolder;
if (cMessagesEnteringFolder && lpmf->lpmcMessagesEnteringFolder)
lpmf->cMessagesEnteringFolder += cMessagesEnteringFolder;
if (cBytesLeavingFolder && lpmf->lpmcBytesLeavingFolder)
lpmf->cBytesLeavingFolder += cBytesLeavingFolder;
if (cMessagesLeavingFolder && lpmf->lpmcMessagesLeavingFolder)
lpmf->cMessagesLeavingFolder += cMessagesLeavingFolder;

LeaveCriticalSection(&lpmf->csCriticalSection);

(void) HrWakeUpdateThread(lpmf->lpmcx);
}

return (S_OK);
}

//$--HrWakeUpdateThread---------------------------------------------------------
//  Helper function that signals the update thread to wake up.
// -----------------------------------------------------------------------------
static HRESULT HrWakeUpdateThread(
IN LPMONCONTEXT lpmcx)
{
HRESULThr= NOERROR;
BOOLfItWorked= TRUE;

DEBUGPRIVATE("HrWakeUpdateThread()\n");

// Check the parameters.

hr = CHK_HrWakeUpdateThread(lpmcx);
if (FAILED(hr))
RETURN(hr);

fItWorked = SetEvent(lpmcx->hUpdateThreadWakeEvent);
if (!fItWorked)
{
        hr = HR_LOG(E_FAIL);
goto cleanup;
}

cleanup:
RETURN(hr);
}

//$--HrRegisterNotification-----------------------------------------------------
//  Helper function that registers notification on folders.
// -----------------------------------------------------------------------------
static HRESULT HrRegisterNotification(  // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)              // pointer to perf mon context
{
    HRESULT            hr             = NOERROR;
    HRESULT          hrT= SUCCESS_SUCCESS;
LPMONFOLDERlpmf= NULL;
SPropTagArrayrgPropTagBytesInMessage= {1, {PR_MESSAGE_SIZE}};
    ULONG               ulRowCount              = 0;

DEBUGPRIVATE("HrRegisterNotification()\n");

// Check the parameters.

hr = CHK_HrRegisterNotification(lpmcx);
if (FAILED(hr))
RETURN(hr);

// Start monitoring for each folder in the list.

for (lpmf = lpmcx->lpmfFolderList; lpmf; lpmf = lpmf->lpmfNext)
{
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
ASSERTERROR(lpmf->lpmcx == lpmcx, "lpmf->lpmcx != lpmcx");
ASSERTERROR(lpmf->lpFolder != NULL, "lpmf->lpFolder == NULL");
ASSERTERROR(lpmf->ulConnection == 0,
"lpmf: folder already has notification set");
ASSERTWARNING(
lpmf->lpmcMessagesInFolder ||
lpmf->lpmcBytesInFolder ||
lpmf->lpmcMessagesEnteringFolder ||
lpmf->lpmcBytesEnteringFolder ||
lpmf->lpmcMessagesLeavingFolder ||
lpmf->lpmcBytesLeavingFolder,
"lpmf: folder without counters");
ASSERTWARNING(lpmf->lpTable == NULL, "lpmf->lpTable != NULL");
ASSERTWARNING(lpmf->lpAdvise == NULL, "lpmf->lpAdvise != NULL");

// Set notification on this folder.

hrT = MAPICALL(lpmf->lpFolder)->GetContentsTable(lpmf->lpFolder, MAPI_DEFERRED_ERRORS,
&lpmf->lpTable);
    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

hrT = MAPICALL(lpmf->lpTable)->SetColumns(lpmf->lpTable,
&rgPropTagBytesInMessage, 0);
    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = HrAllocAdviseSink(FolderNotification, lpmf, &lpmf->lpAdvise);
    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpmf->lpTable)->Advise(
        lpmf->lpTable,
        fnevTableModified,
        lpmf->lpAdvise,
        &lpmf->ulConnection);
    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

        // Call GetRowCount to make Advise work correctly.

        hrT = MAPICALL(lpmf->lpTable)->GetRowCount(
            lpmf->lpTable,
            0,
            &ulRowCount);
    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

} // end for

cleanup:
    RETURN(hr);
}

//$--HrLinkCounterIntoList------------------------------------------------------

//  Helper function that takes a MONCOUNTER structure and links it into the
//  proper list in the MONCONTEXT or the specified MONFOLDER, depending on the
//  counter type.
// -----------------------------------------------------------------------------
static HRESULT HrLinkCounterIntoList(// RETURNS: return code
IN LPMONCONTEXT lpmcx,// pointer to perf mon context
IN LPMONFOLDER lpmf,// for folder-specific counters
IN LPMONCOUNTER lpmc,// counter structure to add to list
IN COUNTERTYPE ctCounterType)// type of counter this is
{
MONCOUNTER **lppmcPlaceToAdd= NULL;
HRESULThr= NOERROR;

DEBUGPRIVATE("HrLinkCounterIntoList()\n");

// Check the parameters.

hr = CHK_HrLinkCounterIntoList(lpmcx, lpmf, lpmc, ctCounterType);
if (FAILED(hr))
RETURN(hr);

switch (ctCounterType)
{
case COUNTERTYPE_MESSAGES_IN_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcMessagesInFolder;
break;
case COUNTERTYPE_BYTES_IN_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcBytesInFolder;
break;
case COUNTERTYPE_MESSAGES_ENTERING_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcMessagesEnteringFolder;
break;
case COUNTERTYPE_BYTES_ENTERING_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcBytesEnteringFolder;
break;
case COUNTERTYPE_MESSAGES_LEAVING_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcMessagesLeavingFolder;
break;
case COUNTERTYPE_BYTES_LEAVING_FOLDER:
ASSERT_WRITE_PTR(lpmf, sizeof(*lpmf), "lpmf: bad write pointer");
lppmcPlaceToAdd = &lpmf->lpmcBytesLeavingFolder;
break;
case COUNTERTYPE_MESSAGES_TRANSFERRED_IN:
lppmcPlaceToAdd = &lpmcx->lpmcMessagesTransferredIn;
break;
case COUNTERTYPE_BYTES_TRANSFERRED_IN:
lppmcPlaceToAdd = &lpmcx->lpmcBytesTransferredIn;
break;
case COUNTERTYPE_MESSAGES_TRANSFERRED_OUT:
lppmcPlaceToAdd = &lpmcx->lpmcMessagesTransferredOut;
break;
case COUNTERTYPE_BYTES_TRANSFERRED_OUT:
lppmcPlaceToAdd = &lpmcx->lpmcBytesTransferredOut;
break;
case COUNTERTYPE_NDRS_IN:
lppmcPlaceToAdd = &lpmcx->lpmcNDRsIn;
break;
case COUNTERTYPE_NDRS_OUT:
lppmcPlaceToAdd = &lpmcx->lpmcNDRsOut;
break;
case COUNTERTYPE_ASSOCIATIONS:
lppmcPlaceToAdd = &lpmcx->lpmcAssociations;
break;
default:
hr = HR_LOG(E_FAIL);
goto cleanup;
}

lpmc->lpmcNext = *lppmcPlaceToAdd;
*lppmcPlaceToAdd = lpmc;

cleanup:
RETURN(hr);
}

//$--HrVerifyLinkage------------------------------------------------------------
//  Verify that the Export value in the Linkage key for the gateway type
//  exists and contains the gateway name.
// -----------------------------------------------------------------------------
static HRESULT HrVerifyLinkage(         // RETURNS: HRESULT
IN LPMONCONTEXT lpmcx)              // pointer to perf mon context
{
HRESULThr        = NOERROR;
    DWORD       dwStatus                = ERROR_SUCCESS;
TCHARszKeyName[MAX_PATH]     = {0};
HKEYhKey        = NULL;
DWORDcch        = 0;
    DWORD       dwType                  = 0;
    TCHAR       szExport[1000]          = {0};
    DWORD       cbExport                = sizeof(szExport);
    BOOL        fFound                  = FALSE;
    LPTSTR      psz                     = NULL;

DEBUGPRIVATE("HrVerifyLinkage()\n");

// Check the parameters.

hr = CHK_HrVerifyLinkage(lpmcx);
if (FAILED(hr))
RETURN(hr);

// Create the registry key name from the gateway name.

cch = wsprintf(
szKeyName,
TEXT("SYSTEM\\CurrentControlSet\\Services\\%s\\Linkage"),
lpmcx->lpszObjectClass);
ASSERTERROR(
(cch * sizeof(*szKeyName)) < sizeof(szKeyName),
"wsprintf overflow: szKeyName");

// Open the registry key.

dwStatus = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0L,
KEY_READ,
&hKey);
if (dwStatus != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    // Read the value.

    dwStatus = RegQueryValueEx(
        hKey,
        TEXT("Export"),
        NULL,
        &dwType,
        szExport,
        &cbExport);
if (dwStatus != ERROR_SUCCESS || dwType != REG_MULTI_SZ)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

    // Look in the multi-valued value for the gateway name.

    fFound = FALSE;
    for (psz = szExport; *psz; psz += (strlen(psz) + 1))
    {
        if (!lstrcmpi(psz, lpmcx->lpszObjectName))
        {
            fFound = TRUE;
            break;
        }
    }

    // If we didn't find it then return an error.

    if (!fFound)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:
// Close the registry key (if it's open).

if (hKey)
{
dwStatus = RegCloseKey(hKey);
        if (dwStatus != ERROR_SUCCESS)
            hr = HR_LOG(E_FAIL);
}

    RETURN(hr);
}

//$--HrSaveSharedMemoryInRegistry-----------------------------------------------
//  Save a copy of the shared memory block to the registry so that
//  GWPERF.DLL will be able to generate a blank set of counters when
//  the gateway isn't running.
// -----------------------------------------------------------------------------
static HRESULT HrSaveSharedMemoryInRegistry(    // RETURNS: HRESULT
    IN LPMONCONTEXT lpmcx)                      // pointer to perf mon context
{
HRESULThr        = NOERROR;
    DWORD       dwStatus                = ERROR_SUCCESS;
TCHARszKeyName[MAX_PATH]     = {0};
HKEYhKey        = NULL;
DWORDcch        = 0;
    BOOL        fSave_DataIsValid       = FALSE;

DEBUGPRIVATE("HrSaveSharedMemoryInRegistry()\n");

// Check the parameters.

hr = CHK_HrSaveSharedMemoryInRegistry(lpmcx);
if (FAILED(hr))
RETURN(hr);

    // Save off the previous value of fDataIsValid.

    fSave_DataIsValid = lpmcx->lpmsmSharedMemory->fDataIsValid;

    // Create the registry key name from the gateway instance name.

cch = wsprintf(
szKeyName,
TEXT("SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters"),
lpmcx->lpszObjectName);
ASSERTERROR(
(cch * sizeof(*szKeyName)) < sizeof(szKeyName),
"wsprintf overflow: szKeyName");

// Open the registry key.

dwStatus = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
szKeyName,
0L,
KEY_SET_VALUE,
&hKey);
if (dwStatus != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

// Temporarily set the flag that the data in the shared memory
    // is valid, so that it will be stored as valid in the registry.

lpmcx->lpmsmSharedMemory->fDataIsValid = TRUE;

    // Write the value (calculate the size of the block that contains
    // real data by adding the offset of the instance data and its size).

    dwStatus = RegSetValueEx(
        hKey,
        TEXT("ObjectDefaultPerformanceData"),
        0,
        REG_BINARY,
        (LPBYTE) lpmcx->lpmsmSharedMemory,
        lpmcx->lpmsmSharedMemory->ibInstanceOffset +
            lpmcx->lpmsmSharedMemory->cbInstanceSize);
if (dwStatus != ERROR_SUCCESS)
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:
// Restore the previous value of fDataIsValid.

lpmcx->lpmsmSharedMemory->fDataIsValid = fSave_DataIsValid;

// Close the registry key (if it's open).

if (hKey)
{
dwStatus = RegCloseKey(hKey);
        if (dwStatus != ERROR_SUCCESS)
            hr = HR_LOG(E_FAIL);
}

    RETURN(hr);
}


NEWPST.CPP

// --newpst.cpp---------------------------------------------------------------
//
// Implementation of the newpst library
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#include "edk.h"
#include "mspst.h"
#include "newpst.chk"

//
// Static storage
//

static BOOLfProfileCreated =FALSE;

static LPPROFADMINlpProfAdmin     =   NULL;
static LPSERVICEADMINlpSvcAdmin      =NULL;

static MAPIUIDMsgStoreUID     =   {0};

//
// Functions
//

// $--HrCreatePersonalStore----------------------------------------------------------
//
// DESCRIPTION: Create a New PST with properties determined by the input
//parameters in a temporary profile.  The name and password
//of the temporary PST are returned to the user.
//
// INPUT:
//
//[lpszPSTPath]-- Value for PR_PST_PATH (new PST path).
//[dwEncryption]-- Value for PR_PST_ENCRYPTION (TBS).
//[lpszPSTDisplayName]-- Value for PR_DISPLAY_NAME (new PST display name).
//[lpszPSTPassword]-- Value for PR_PST_PW_SZ_NEW (new PST password,
//   NULL not accepted).
//
// OUTPUT:
//
//lppszProfileName-- Ptr that will be set to point at temporary
//   profile name on return; this may be used to
//   establish a MAPI session.
//lppszProfilePassword-- Ptr that will be set to point at temporary
//   profile password on return; this may be used to
//   establish a MAPI session.
//
// RETURNS:     NOERROR on success,
//              E_INVALIDARG if bad input,
//              E_ACCESSDENIED if can access profile
//  `           E_OUTOFMEMORY if memory problems
//              E_FAIL on general failure.
//
// Notes:Successive calls to CreateNewPST() should not be made without
//intervening calls to NewPSTCleanup().  Due to use of internal
//static storage for objects and profile strings, only one new
//PST may be created and used at a time.
//
//---------------------------------------------------------------------------

HRESULT HrCreatePersonalStore(                 // RETURNS HRESULT
INLPSTRlpszPSTPath,            // new PST fully-pathed name
INDWORDdwEncryption,           // encryption level
INLPSTRlpszPSTDisplayName,     // PR_DISPLAY_NAME value
INLPSTRlpszPSTPassword,        // new PST password
OUTLPSTR *lppszProfileName,       // temporary profile name
OUTLPSTR *lppszProfilePassword    // temporary profile password
)
{
HRESULThr          =           NOERROR;
LPSPropValueaPropVals   =NULL;
LPSRowSetlpRows      =NULL;
LPMAPITABLElpTable     =NULL;
    const UINT      cbProfile   =           14;     // # chars in temporary profile name
    const UINT      cbPassword  =           1;      // # char in temporary password

    DEBUGPUBLIC("HrCreatePersonalStore()\n");

    // Check input values
    hr = CHK_HrCreatePersonalStore(lpszPSTPath, dwEncryption, lpszPSTDisplayName,
                            lpszPSTPassword, lppszProfileName,
                            lppszProfilePassword);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

// Make sure we have not previously called CreateNewPST() without
// an intervening NewPSTCleanup() call.  When this occurs, we just
// return E_FAIL and do no cleanup, thus leaving the
// previous profile and PST in place.

if (lpProfAdmin != NULL)
{
hr = HR_LOG(E_FAIL);

        goto cleanup;
}

// Get a profile administration object.

hr = MAPIAdminProfiles(0, &lpProfAdmin);

if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);

goto cleanup;
    }

    // Allocate memory for temporary profile and password.
// Create the temporary profile.
    hr = MAPIAllocateBuffer(cbPassword * sizeof(CHAR),   // one character password
                            (PVOID *) lppszProfilePassword);  // pointer to string pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    hr = MAPIAllocateBuffer(cbProfile * sizeof(CHAR),
                            (PVOID *) lppszProfileName);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(lppszProfileName, sizeof(LPSTR)),
                "Bad lppszProfileName");
    ASSERTERROR(!IsBadWritePtr(lppszProfilePassword, sizeof(LPSTR)),
                "Bad lppszProfilePassword");

{
inti;

**lppszProfilePassword = '\0';  // no password

for (i = 0; (i < 1000) && (!fProfileCreated); i++)
{
sprintf(*lppszProfileName, "PstLoadTmp%03.3u", i);

hr = lpProfAdmin->CreateProfile(*lppszProfileName,
*lppszProfilePassword,
0,
0);

            switch (hr)
            {
               case MAPI_E_NO_ACCESS:   // name collision

                hr = HR_LOG(E_ACCESSDENIED);

                break;

               default:
                if ( FAILED(hr) )
                {
                    hr = HR_LOG(E_FAIL);

                    goto cleanup;
                }

                fProfileCreated = TRUE;

            }   // end switch
}   // end for

if (i == 1000)
        {
goto cleanup;// MAPI_E_NO_ACCESS will be returned.
        }
}

// Get a service administration object.

hr = lpProfAdmin->AdminServices(*lppszProfileName,
*lppszProfilePassword,
0,
0,
&lpSvcAdmin);
if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);

goto cleanup;
    }

// Add the MS Personal Information Store service to the temporary profile.
// This will also happen to be set as the default store.

hr = lpSvcAdmin->CreateMsgService("MSPST MS",
  "", // UI not used, so not needed.
  0,
  0);

if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);

goto cleanup;
    }

// Configure the MS Personal Information Store per NewPST entries.

{
ULONGcValues =   0;
LPSPropValuelpProp  =   NULL;

    hr = lpSvcAdmin->GetMsgServiceTable(0, &lpTable);

if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);

goto cleanup;
        }

// We don't need to worry about checking all the columns; there should
// only be one since we just created the profile and added one service.

    hr = lpTable->QueryRows(1, 0, &lpRows);

    if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);

goto cleanup;
        }

    if (lpRows == NULL)
{
hr = HR_LOG(E_FAIL);// highly unlikely.

goto cleanup;
}

    cValues =lpRows->aRow[0].cValues;
    lpProp =lpRows->aRow[0].lpProps;

    while (cValues)
    {
        if (lpProp->ulPropTag == PR_SERVICE_UID)
{
MsgStoreUID = *((LPMAPIUID)lpProp->Value.bin.lpb);
break;
}

        lpProp++;
cValues--;
    }

if (cValues == 0)
{
hr = HR_LOG(E_FAIL);// highly unlikely.

goto cleanup;
}

cValues = 6;

hr = MAPIAllocateBuffer(cValues * sizeof(SPropValue),
(LPVOID *)&aPropVals);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_OUTOFMEMORY);

goto cleanup;
}

aPropVals[0].ulPropTag =PR_DISPLAY_NAME;
aPropVals[0].Value.lpszA =lpszPSTDisplayName;

aPropVals[1].ulPropTag =PR_PST_PATH;
aPropVals[1].Value.lpszA =lpszPSTPath;;

aPropVals[2].ulPropTag =PR_PST_REMEMBER_PW;
aPropVals[2].Value.b =TRUE;

aPropVals[3].ulPropTag =PR_PST_ENCRYPTION;
aPropVals[3].Value.l =dwEncryption;

aPropVals[4].ulPropTag =PR_PST_PW_SZ_NEW;
aPropVals[4].Value.lpszA =lpszPSTPassword;

aPropVals[5].ulPropTag =PR_PST_PW_SZ_OLD;
aPropVals[5].Value.lpszA =*lppszProfilePassword;

hr = lpSvcAdmin->ConfigureMsgService(&MsgStoreUID,
 0,
 0,
 cValues,
 aPropVals);
if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);

goto cleanup;
        }
}

cleanup:

    FREEPROWS(lpRows);

ULRELEASE(lpTable);

    MAPIFREEBUFFER(aPropVals);

// This makes it an "all or none" call.

if ( FAILED(hr) )
    {
        if ( lppszProfileName )
        {
            // remove temporary profile and
            // release administration service.
    HrCleanupPSTGlobals(*lppszProfileName);

            MAPIFREEBUFFER(*lppszProfileName);
        }

        if ( lppszProfilePassword )
        {
            MAPIFREEBUFFER(*lppszProfilePassword);
        }
    }   // end if failure

RETURN(hr);
}

// $--HrCleanupPSTGlobals-----------------------------------------------------------
//
// DESCRIPTION: Release objects involved in creation of a new PST.
//
// INPUT:       temporary profile name
//
// RETURNS:     NOERROR on success,
//              E_FAIL if error.
//
// Notes:This should be called after Logoff() and Release() are called
//for the LPSESSION and before MAPIUninitialize() is called.
//---------------------------------------------------------------------------

HRESULT HrCleanupPSTGlobals(         // RETURNS: HRESULT
        IN LPSTR lpszProfileName)   // temporary profile name
{
HRESULThr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrCleanupPSTGlobals()\n");

    // check input parameters
    hr = CHK_HrCleanupPSTGlobals(lpszProfileName);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ULRELEASE(lpSvcAdmin);

if (fProfileCreated)
    {
hr = lpProfAdmin->DeleteProfile(lpszProfileName, 0);
    }

ULRELEASE(lpProfAdmin);

lpSvcAdmin =NULL;
fProfileCreated =FALSE;
lpProfAdmin =NULL;

RETURN(hr);
}


RULECLS.CPP

// --rulecls.cpp-------------------------------------------------------------
//
//  API entry points into the Rule Class Library (rulecls).
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include <errno.h>
#include "srowlst.h"
#include "ruleclsf.h"
#include "proptag.h"// temporary location for GetPropTag* functions.
#include "toktypes.h"

#include "rulecls.chk"

//
// Manifest constants.
//

#defineRES_UNINITIALIZED((ULONG) 0xffffffff)

//
// Macros
//

// ISNONTERMINALRESTRICTION() returns TRUE if lpRes is a nonterminal restriction

#define ISNONTERMINALRESTRICTION(lpRes)(((lpRes)->rt == RES_AND)||\
 ((lpRes)->rt == RES_OR)||\
 ((lpRes)->rt == RES_NOT))

// Forward function declarations.

static
HRESULT
HrAdrListDisplayNamesToString(
INLPADRLISTlpAdrList,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrAndRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrContentRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrCopyProps(
INintcprop,
INLPSPropValuergprop,
OUTLPVOIDpvDst,
OUTULONG FAR * pcb
);

static
HRESULT
HrCopyRestrictionArray(
INLPSRestrictionlpResSrc,
INLPVOIDlpObject,
INULONGcRes,
OUTLPSRestrictionlpResDest
);

static
HRESULT
HrCountProps(
INint cprop,
INLPSPropValuergprop,
OUTULONG FAR *pcb
);

static
HRESULT
HrCreateReplyMsg(
INLPMAPIFOLDERlpFolder,
INLPSTRlpszReplyText,
INLPVOIDlpObject,
OUTULONG FAR *lpcbentryid,
OUTLPENTRYID FAR *lppentryid
);

static
HRESULT
HrDupPropset(
INintcprop,
INLPSPropValuergprop,
INLPVOIDlpObject,
OUTLPSPropValue FAR *prgprop
);

static
HRESULT
HrGetExplicitTag(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrGetExpressionToken(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrGetFolderPath(
INLPMDBlpMDB,
INULONGcbentryid,
INLPENTRYIDlpentryid,
OUTLPSTR FAR *lppszFolderPath
);

static
HRESULT
HrGetMoveCopyArgs(
INLPMAPISESSIONlpSession,
INLPSTRpszFolder,
INLPVOIDlpObject,
IN OUTLPACTIONlpAction
);

static
HRESULT
HrGetNamedTag(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrGetNumericLiteral(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrGetStoreName(
INLPMDBlpMDB,
OUTLPSTR FAR *lppszName
);

static
HRESULT
HrGetStringLiteral(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrGetTimeLiteral(
IN OUTTOKENINFO *pti
);

static
HRESULT
HrNotRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrOrRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrParseAction(
INLPMAPISESSIONlpSession,
INLPMAPIFOLDERlpFolder,
INLPVOIDlpObject,
INCHAR *pch,
IN OUTLPACTIONlpAction
);

static
HRESULT
HrParseCondition(
INLPVOIDlpObject,
IN OUTCHAR **ppch,
IN OUTLPSRestrictionlpRes
);

static
HRESULT
HrPrintToString(
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer,
INLPSTRFormat
...
);

static
HRESULT
HrPrivateRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrPropertyRestrictionToString(
INLPSRestrictionlpRes,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

static
HRESULT
HrPropValueToString(
INLPSPropValuelpProp,
IN OUTLPSTR FAR *lppszString,
IN OUTLPULONGlpcbString,
IN OUTLPULONGlpcbBuffer
);

inline
CHAR *
lpszSkipNonWhiteSpace(
INCHAR *pch
);

inline
CHAR *
lpszSkipWhiteSpace(
INCHAR *pch
);

//
// Public functions
//

// $--HrCopyActions------------------------------------------------------------
//
// DESCRIPTION:Make a copy of an ACTIONS structure and all its subelements.
//
// INPUT:
//
//[lpActsSrc]-- Ptr to ACTIONS structure to be copied.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(), or NULL if the returned structure
//   is to be allocated using MAPIAllocateBuffer().
// OUTPUT:
//
//[lppActsDest]-- Ptr to be set to copy of ACTIONS structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

STDAPI
HrCopyActions(// RETURNS: HRESULT
INLPACTIONSlpActsSrc,  // source action ptr
INLPVOIDlpObject,// ptr to existing MAPI buffer
OUTLPACTIONS FAR *lppActsDest // ptr to destination ACTIONS buffer
)
{
BOOLfNullObject =(lpObject == NULL);
HRESULThr =NOERROR;
ULONGi =0;
LPACTIONlpActDest =NULL;
LPACTIONlpActSrc =NULL;
LPACTIONSlpActsDest =NULL;

    DEBUGPUBLIC("HrCopyActions()\n");

hr = CHK_HrCopyActions(lpActsSrc, lpObject, lppActsDest);

    if (FAILED(hr))
        RETURN(hr);

*lppActsDest = NULL;

if (lpActsSrc->cActions <= 0 || lpActsSrc->lpAction == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (lpObject != NULL)
{
hr = MAPIAllocateMore(sizeof(ACTIONS),
  lpObject,
  (LPVOID FAR *)lppActsDest);
}
else
{
hr = MAPIAllocateBuffer(sizeof(ACTIONS),
(LPVOID FAR *)lppActsDest);

lpObject = *lppActsDest;
}

if (FAILED(hr))
goto cleanup;

lpActsDest = *lppActsDest;

*lpActsDest = *lpActsSrc;

lpActsDest->lpAction = NULL;

hr = MAPIAllocateMore(sizeof(ACTION) * lpActsDest->cActions,
  lpObject,
  (LPVOID FAR *)&(lpActsDest->lpAction));
if (FAILED(hr))
goto cleanup;

// Initialize acttype values for all members of the array to a value
// that will not cause deallocation errors should the copy fail.

for (i = 0; i < lpActsDest->cActions; i++)
lpActsDest->lpAction[i].acttype = OP_BOUNCE;

// Now actually copy all the members of the array.   The number of
// actions should be small, so we don't worry about a more efficient
// loop implementation (getting rid of i and just using ptrs).

for (i = 0; i < lpActsDest->cActions; i++)
{
lpActDest =&(lpActsDest->lpAction[i]);
lpActSrc =&(lpActsSrc->lpAction[i]);

*lpActDest = *lpActSrc;

// Note -Any 0 source allocations (ie. cb==0 or ptr==NULL) cause
//the function to fail.  If there are instances in which
//such input is valid, we should evaluate them case by
//case.

switch (lpActSrc->acttype)
{
    case OP_MOVE:// actMoveCopy
    case OP_COPY:
    {
    if (lpActDest->actMoveCopy.cbStoreEntryId == 0||
lpActDest->actMoveCopy.lpStoreEntryId == NULL||
lpActDest->actMoveCopy.cbFldEntryId == 0||
lpActDest->actMoveCopy.lpFldEntryId == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(lpActDest->actMoveCopy.cbStoreEntryId,
  lpObject,
  (LPVOID FAR *)
  &(lpActDest->actMoveCopy.lpStoreEntryId));
    if (FAILED(hr))
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    memcpy(lpActDest->actMoveCopy.lpStoreEntryId,
       lpActSrc->actMoveCopy.lpStoreEntryId,
       lpActSrc->actMoveCopy.cbStoreEntryId);

    hr = MAPIAllocateMore(lpActDest->actMoveCopy.cbFldEntryId,
  lpObject,
  (LPVOID FAR *)
  &(lpActDest->actMoveCopy.lpFldEntryId));
    if (FAILED(hr))
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    memcpy(lpActDest->actMoveCopy.lpFldEntryId,
       lpActSrc->actMoveCopy.lpFldEntryId,
       lpActSrc->actMoveCopy.cbFldEntryId);

    break;
    }

    case OP_REPLY:// actReply
    case OP_OOF_REPLY:
    {
    if (lpActDest->actReply.cbEntryId == 0||
lpActDest->actReply.lpEntryId == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(lpActDest->actReply.cbEntryId,
  lpObject,
  (LPVOID FAR *)
  &(lpActDest->actReply.lpEntryId));
    if (FAILED(hr))
    goto cleanup;

    memcpy(lpActDest->actReply.lpEntryId,
   lpActSrc->actReply.lpEntryId,
   lpActSrc->actReply.cbEntryId);

    break;
    }

    case OP_DEFER_ACTION:// actDeferAction
    {
    if (lpActSrc->actDeferAction.pbData == NULL||
lpActSrc->actDeferAction.cbData == 0)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

hr = MAPIAllocateMore(lpActDest->actDeferAction.cbData,
  lpObject,
  (LPVOID FAR *)
  &(lpActDest->actDeferAction.pbData));
if (FAILED(hr))
goto cleanup;

memcpy(lpActDest->actDeferAction.pbData,
   lpActSrc->actDeferAction.pbData,
   lpActDest->actDeferAction.cbData);

    break;
    }

    case OP_BOUNCE:// scBounceCode
    {
    // Nothing to do!

    break;
    }

    case OP_FORWARD:// lpadrlist
    case OP_DELEGATE:
    {
    ULONGcEntries =0;
    ULONGi =0;
    LPADRLISTlpAdrListSrc =lpActSrc->lpadrlist;
    LPADRLISTlpAdrListDest =NULL;

    lpActDest->lpadrlist = NULL;

    if (lpAdrListSrc == NULL || lpAdrListSrc->cEntries == 0)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(CbADRLIST(lpAdrListSrc),
  lpObject,
 (LPVOID FAR *)&(lpActDest->lpadrlist));
    if (FAILED(hr))
    goto cleanup;

    cEntries = lpAdrListSrc->cEntries;

    lpAdrListDest = lpActDest->lpadrlist;

lpAdrListDest->cEntries = cEntries;

    // Initialize the new ADRENTRY's and validate cValues.

    for (i = 0; i < cEntries; i++)
    {
    lpAdrListDest->aEntries[i] = lpAdrListSrc->aEntries[i];

    lpAdrListDest->aEntries[i].rgPropVals = NULL;

    if (lpAdrListDest->aEntries[i].cValues == 0)
    hr = HR_LOG(E_FAIL);
    }

    if (FAILED(hr))
    goto cleanup;

    // Copy the rgPropVals.

    for (i = 0; i < cEntries; i++)
    {
    hr = HrDupPropset(lpAdrListDest->aEntries[i].cValues,
    lpAdrListSrc->aEntries[i].rgPropVals,
  lpObject,
    &lpAdrListDest->aEntries[i].rgPropVals);

    if (FAILED(hr))
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }

    break;
    }

    case OP_TAG:// propTag
{
hr = PropCopyMore(&lpActDest->propTag,
  &lpActSrc->propTag,
  MAPIAllocateMore,
  lpObject);

if (FAILED(hr))
{
if (hr != MAPI_E_NOT_ENOUGH_MEMORY)
hr = HR_LOG(E_FAIL);

goto cleanup;
}

break;
}

    case OP_DELETE:// union not used
    case OP_MARK_AS_READ:
    {
    // Nothing to do!

    break;
    }

    default:// error!
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
}
}

cleanup:

if (FAILED(hr))
{
if (fNullObject)
    MAPIFREEBUFFER(*lppActsDest);
}

RETURN(hr);
}

// $--HrCopyRestriction--------------------------------------------------------
//
// DESCRIPTION:Make a copy of an SRestriction structure and all its
//subelements.
//
// INPUT:
//
//[lpResSrc]-- Ptr to restriction to be copied.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(), or NULL if the returned structure
//   is to be allocated using MAPIAllocateBuffer().
//
// OUTPUT:
//
//[lppResDest]-- Ptr to be set to copy of restriction.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

STDAPI
HrCopyRestriction(// RETURNS: HRESULT
INLPSRestrictionlpResSrc,   // source restriction ptr
INLPVOIDlpObject,// ptr to existing MAPI buffer
OUTLPSRestriction FAR *lppResDest  // dest restriction buffer ptr
)
{
BOOLfNullObject =(lpObject == NULL);
HRESULThr =NOERROR;

    DEBUGPUBLIC("HrCopyRestriction()\n");

hr = CHK_HrCopyRestriction(lpResSrc, lpObject, lppResDest);

    if (FAILED(hr))
        RETURN(hr);

*lppResDest = NULL;

if (lpObject != NULL)
{
hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)lppResDest);
}
else
{
hr = MAPIAllocateBuffer(sizeof(SRestriction),
(LPVOID FAR *)lppResDest);

lpObject = *lppResDest;
}

if (FAILED(hr))
goto cleanup;

hr = HrCopyRestrictionArray(lpResSrc, lpObject, 1, *lppResDest);

if (FAILED(hr))
{
if (fNullObject)
MAPIFREEBUFFER(*lppResDest);
}

cleanup:

RETURN(hr);
}

// $--HrFolderRulesGetProviders------------------------------------------------
//
// DESCRIPTION:Get an array of rules provider names for a given folder.
//
// INPUT:
//
//[lpMDB]-- Pointer to message store containing folder.
//  [cbentryid]-- Number of bytes in folder's entry identifier.
//  [lpentryid]-- Folder's entry identifier.
//
// OUTPUT:
//
//  [lpcProviders]-- Pointer to ulong that will be set to count of
//   providers on successful return.
//[lpppszProviders]-- Pointer to array of string pointers that will be set
//   to point at an array of provider name string pointers
//   on successful return.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif not enough memory;
//E_NOINTERFACEif rules table does not exist on folder;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

STDAPI
HrFolderRulesGetProviders(// RETURNS: HRESULT
INLPMDBlpMDB,// MAPI MDB store ptr
INULONGcbentryid,// # bytes in entry ID
INLPENTRYIDlpentryid,// entry ID ptr
OUTLPULONGlpcProviders,// count of providers
OUTLPSTR FAR * FAR *lpppszProviders// ptr to array of providers
)
{
    HRESULThr =NOERROR;
    CFolderRules FAR *pFolderRules =NULL;

    DEBUGPUBLIC("HrFolderRulesGetProviders()\n");

    hr = CHK_HrFolderRulesGetProviders(cbentryid,
   lpentryid,
   lpcProviders,
   lpppszProviders);
    if (FAILED(hr))
        RETURN(hr);

    // Initialize controlling class object.

    pFolderRules = new CFolderRules();

if (pFolderRules == NULL)
{
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

// Open the folder in the controlling class object.

hr = pFolderRules->HrOpen(lpMDB, cbentryid, lpentryid, NULL);

if (FAILED(hr))
goto cleanup;

hr = pFolderRules->HrGetProviders(lpcProviders, lpppszProviders);

if (FAILED(hr))
goto cleanup;

cleanup:

    // Release our copy of the CFolderRules object.

    ULRELEASE(pFolderRules);// Ref count now 1.

    RETURN(hr);
}

// $--HrFolderRulesOpen--------------------------------------------------------
//
// DESCRIPTION:Get a pointer to an object which implements the
//IExchangeFolderRules interface defined in rulecls.h.
//
// INPUT:
//
//[lpMDB]-- Pointer to message store containing folder.
//  [cbentryid]-- Number of bytes in folder's entry identifier.
//  [lpentryid]-- Folder's entry identifier.
//  [lpszProvider]-- Provider for rules.  Multiple providers may have
//   rules on a folder. The IExchangeFolderRules interface
//   provides access to the rules associated with a
//   single specified provider.
//
// OUTPUT:
//
//  [lppFolderRules]-- Pointer to object which supports interface.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif not enough memory;
//E_NOINTERFACEif rules table does not exist on folder;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

STDAPI
HrFolderRulesOpen(// RETURNS: HRESULT
INLPMDBlpMDB,// MAPI MDB store ptr
INULONGcbentryid,// # bytes in entry ID
INLPENTRYIDlpentryid,// entry ID ptr
INLPSTRlpszProvider,// provider name
OUTLPFOLDERRULES FAR *lppFolderRules  // ptr to folder rules buffer
)
{
    HRESULThr =NOERROR;
    CFolderRules FAR *pFolderRules =NULL;

    DEBUGPUBLIC("HrFolderRulesOpen()\n");

    hr = CHK_HrFolderRulesOpen(cbentryid,
   lpentryid,
   lpszProvider,
   lppFolderRules);
    if (FAILED(hr))
        RETURN(hr);

    *lppFolderRules = NULL;

    // Initialize controlling class object.

    pFolderRules = new CFolderRules();

if (pFolderRules == NULL)
{
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

// Open the folder in the controlling class object.

hr = pFolderRules->HrOpen(lpMDB, cbentryid, lpentryid, lpszProvider);

if (FAILED(hr))
goto cleanup;

    // Give the user access to the programmer interface (CIExchangeFolderRules)
    // on the controlling CFolderRules object.

    hr = pFolderRules->QueryInterface(IID_IExchangeFolderRules,
 (LPVOID FAR *)lppFolderRules);
    // Ref count now 2.

    if (FAILED(hr))
        goto cleanup;

    ASSERT_READ_PTR_OR_NULL(*lppFolderRules, sizeof(LPFOLDERRULES),
                             "Bad lppFolderRules.");

cleanup:

    // Release our copy of the CFolderRules object.

    ULRELEASE(pFolderRules);// Ref count now 1.

    RETURN(hr);
}

// $--HrActionToString---------------------------------------------------------
//
// DESCRIPTION:Get a string representation of an ACTION.
//
// INPUT:
//
//  [lpSession]-- MAPI session ptr.
//[lpAction]-- Ptr to ACTION.
//
// OUTPUT:
//
//[lppszString]-- Ptr to ptr that will be set to point at generated
//   string representation on successful return.
//
// RETURNS:     NOERROR             if successful;
//              E_INVALIDARG    if bad input;
//              E_OUTOFMEMORYif not enough memory;
//              E_FAILotherwise.
//
//-----------------------------------------------------------------------------

STDAPI
HrActionToString(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,  // MAPI session ptr
INLPACTIONlpAction,// action ptr
OUTLPSTR FAR *lppszString// string ptr ptr
)
{
ULONGcbBuffer =0;
ULONGcbString =0;
HRESULThr =NOERROR;
LPSTRlpszActType =NULL;

    DEBUGPUBLIC("HrActionToString()\n");

hr = CHK_HrActionToString(lpSession, lpAction, lppszString);

    if (FAILED(hr))
        RETURN(hr);

*lppszString = NULL;

switch (lpAction->acttype)
{
case OP_MOVE:
lpszActType = "Move to ";
break;

case OP_COPY:
lpszActType = "Copy to ";
break;

case OP_REPLY:
lpszActType = "Reply";
break;

case OP_OOF_REPLY:
lpszActType = "Out-of-Office Reply";
break;

case OP_DEFER_ACTION:
lpszActType = "Provider-specific deferred action";
break;

case OP_BOUNCE:
lpszActType = "Bounce with error code ";
break;

case OP_FORWARD:
lpszActType = "Forward to ";
break;

case OP_DELEGATE:
lpszActType = "Delegate to ";
break;

case OP_TAG:
lpszActType = "Tag ";
break;

case OP_DELETE:
lpszActType = "Delete";
break;

case OP_MARK_AS_READ:
lpszActType = "Mark as Read";
break;

default:
lpszActType = "Invalid action type";
break;
}

hr = HrPrintToString(lppszString, &cbString, &cbBuffer, lpszActType);

    if (FAILED(hr))
        goto cleanup;

switch (lpAction->acttype)
{
case OP_MOVE:
case OP_COPY:
{
LPMDBlpMDB =NULL;
LPSTRlpszFolderPath =NULL;
LPSTRlpszStoreName = NULL;

// Open the message store.

hr = lpSession->OpenMsgStore(0,
 lpAction->actMoveCopy.cbStoreEntryId,
 lpAction->actMoveCopy.lpStoreEntryId,
  NULL,
  MAPI_DEFERRED_ERRORS,
  &lpMDB);
if (FAILED(hr))
goto cleanup;

hr = HrGetStoreName(lpMDB, &lpszStoreName);

    if (FAILED(hr))
{
ULRELEASE(lpMDB);
        goto cleanup;
}

hr = HrGetFolderPath(lpMDB,
 lpAction->actMoveCopy.cbFldEntryId,
 lpAction->actMoveCopy.lpFldEntryId,
 &lpszFolderPath);

    if (FAILED(hr))
{
MAPIFREEBUFFER(lpszStoreName);
ULRELEASE(lpMDB);
        goto cleanup;
}

hr = HrPrintToString(lppszString,
 &cbString,
 &cbBuffer,
 "%s\\%s", lpszStoreName, lpszFolderPath);

MAPIFREEBUFFER(lpszFolderPath);
MAPIFREEBUFFER(lpszStoreName);
ULRELEASE(lpMDB);

    if (FAILED(hr))
        goto cleanup;

break;
}

case OP_REPLY:
case OP_OOF_REPLY:
{
;// Nothing more to do.
break;
}

case OP_DEFER_ACTION:
{
;// Nothing more to do.
break;
}

case OP_BOUNCE:
{
LPSTRlpszBounceCode = NULL;

if (lpAction->scBounceCode == BOUNCE_MESSAGE_SIZE_TOO_LARGE)
lpszBounceCode = "BOUNCE_MESSAGE_SIZE_TOO_LARGE";
else if (lpAction->scBounceCode == BOUNCE_FORMS_MISMATCH)
lpszBounceCode = "BOUNCE_FORMS_MISMATCH";
else if (lpAction->scBounceCode == BOUNCE_ACCESS_DENIED)
lpszBounceCode = "BOUNCE_ACCESS_DENIED";

if (lpszBounceCode != NULL)
{
hr = HrPrintToString(lppszString,
 &cbString,
 &cbBuffer,
 lpszBounceCode);
}
else
{
hr = HrPrintToString(lppszString,
 &cbString,
 &cbBuffer,
 "%#x (invalid bounce code)",
lpAction->scBounceCode);
}

    if (FAILED(hr))
        goto cleanup;

break;
}

case OP_FORWARD:
case OP_DELEGATE:
{
hr = HrAdrListDisplayNamesToString(lpAction->lpadrlist,
   lppszString,
   &cbString,
   &cbBuffer);
    if (FAILED(hr))
        goto cleanup;

break;
}

case OP_TAG:
{
LPSTRlpszPropTag =NULL;
LPSTRlpszPropValue =NULL;

hr = HrGetPropTagName(lpAction->propTag.ulPropTag,
   &lpszPropTag);

if (FAILED(hr))
goto cleanup;

{
ULONGcbString =0;
ULONGcbBuffer =0;

hr = HrPropValueToString(&lpAction->propTag,
  &lpszPropValue,
  &cbString,
  &cbBuffer);
}

    if (FAILED(hr))
{
MAPIFREEBUFFER(lpszPropTag);
        goto cleanup;
}

hr = HrPrintToString(lppszString,
 &cbString,
 &cbBuffer,
 "%s = %s", lpszPropTag, lpszPropValue);

MAPIFREEBUFFER(lpszPropTag);
MAPIFREEBUFFER(lpszPropValue);

    if (FAILED(hr))
        goto cleanup;

break;
}

case OP_DELETE:
{
;// Nothing more to do.
break;
}

case OP_MARK_AS_READ:
{
;// Nothing more to do.
break;
}

default:
{
;// Nothing more to do.
break;
}
}

cleanup:

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppszString);
}
else
{
// If buffer is longer than necessary, reallocate.

if (cbBuffer > cbString)
{
LPSTRlpszString = NULL;

hr = MAPIAllocateBuffer(cbString, (LPVOID FAR *)&lpszString);

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppszString);
}
else
{
memcpy(lpszString, *lppszString, cbString);

MAPIFREEBUFFER(*lppszString);

*lppszString = lpszString;
}
}
}

    RETURN(hr);
}

// $--HrRestrictionToString----------------------------------------------------
//
// DESCRIPTION:Get a string representation of an SRestriction.
//
// INPUT:
//
//[lpRestriction]-- Ptr to restriction.
//
// OUTPUT:
//
//[lppszString]-- Ptr to ptr that will be set to point at generated
//   string representation on successful return.
//
// RETURNS:     NOERROR             if successful;
//              E_INVALIDARG    if bad input;
//              E_OUTOFMEMORYif not enough memory;
//              E_FAILotherwise.
//
//-----------------------------------------------------------------------------

STDAPI
HrRestrictionToString(// RETURNS: HRESULT
INLPSRestrictionlpRestriction,// restriction ptr
OUTLPSTR FAR *lppszString// string ptr ptr
)
{
ULONGcbBuffer =0;
ULONGcbString =0;
HRESULThr =NOERROR;

    DEBUGPUBLIC("HrRestrictionToString()\n");

hr = CHK_HrRestrictionToString(lpRestriction, lppszString);

    if (FAILED(hr))
        RETURN(hr);

*lppszString = NULL;

hr = HrPrivateRestrictionToString(lpRestriction,
  lppszString,
  &cbString,
  &cbBuffer);
    if (FAILED(hr))
        goto cleanup;

cleanup:

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppszString);
}
else
{
// If buffer is longer than necessary, reallocate.

if (cbBuffer > cbString)
{
LPSTRlpszString = NULL;

hr = MAPIAllocateBuffer(cbString, (LPVOID FAR *)&lpszString);

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppszString);
}
else
{
memcpy(lpszString, *lppszString, cbString);

MAPIFREEBUFFER(*lppszString);

*lppszString = lpszString;
}
}
}

    RETURN(hr);
};

// $--HrStringToAction---------------------------------------------------------
//
// DESCRIPTION:Generate an ACTION structure that describes the action
//provided in *lpszString.
//
// INPUT:
//
//[lpSession]-- Ptr to MAPI session.
//[lpFolder]-- Ptr to MAPI folder action applies to.  MUST have been
//   opened with MAPI_MODIFY access.
//[lpszString]-- Action string specifying ACTION to be generated.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer().  NULL is not permitted. Ideally,
//   this is a ptr to the parent ACTIONS structure.
//
// INPUT/OUTPUT:
//
//[lpAction]-- Ptr to ACTION structure that will be filled in.  This
//   is allocated by the caller.  Ideally, this is a ptr to
//   an ACTION array element that was allocated by the caller
//   using a MAPIAllocateMore() that referenced the parent
//   ACTIONS structure.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

STDAPI
HrStringToAction(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,// ptr to MAPI session
INLPMAPIFOLDERlpFolder,// ptr to MAPI folder
INLPSTRlpszString,// string ptr
INLPVOIDlpObject,// ptr to existing MAPI buf
IN OUTLPACTIONlpAction  // ACTION ptr
)
{
HRESULThr =NOERROR;
LPSTRlpszStringCopy =NULL;

    DEBUGPUBLIC("HrStringToAction()\n");

    hr = CHK_HrStringToAction(lpSession,
  lpFolder,
  lpszString,
  lpObject,
  lpAction);
    if (FAILED(hr))

RETURN(hr);

// Make copy of lpszString so you can write to it safely.

hr = MAPIAllocateBuffer(strlen(lpszString) + 1,
(LPVOID FAR *)&lpszStringCopy);
if (FAILED(hr))
goto cleanup;

strcpy(lpszStringCopy, lpszString);

hr = HrParseAction(lpSession, lpFolder, lpObject, lpszStringCopy, lpAction);

if (FAILED(hr))
goto cleanup;

cleanup:

MAPIFREEBUFFER(lpszStringCopy);

    RETURN(hr);
}

// $--HrStringToRestriction----------------------------------------------------
//
// DESCRIPTION:Generate an SRestriction structure that describes the
//restriction condition provided in *lpszString.  The
//grammar for *lpszString is given in the header file
//rulecls.h.
//
// INPUT:
//
//[lpszString]-- Condition string specifying restriction to be generated.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(), or NULL if the returned structure
//   is to be allocated using MAPIAllocateBuffer().
//
// OUTPUT:
//
//[lppRestriction]-- Ptr to ptr that will be set to point at generated
//   restriction on successful return.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

STDAPI
HrStringToRestriction(// RETURNS: HRESULT
INLPSTRlpszString,// string
INLPVOIDlpObject,// ptr to existing MAPI buf
OUTLPSRestriction FAR *lppRestriction  // restriction buffer ptr
)
{
BOOLfNullObject =(lpObject == NULL);
HRESULThr =NOERROR;
LPSTRlpszStringCopy =NULL;
CHAR *pch =NULL;

    DEBUGPUBLIC("HrStringToRestriction()\n");

    hr = CHK_HrStringToRestriction(lpszString, lpObject, lppRestriction);

    if (FAILED(hr))
        RETURN(hr);

// Make copy of lpszString so you can write to it safely.

hr = MAPIAllocateBuffer(strlen(lpszString) + 1,
(LPVOID FAR *)&lpszStringCopy);
if (FAILED(hr))
goto cleanup;

strcpy(lpszStringCopy, lpszString);

if (lpObject != NULL)
{
hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)lppRestriction);
}
else
{
hr = MAPIAllocateBuffer(sizeof(SRestriction),
(LPVOID FAR *)lppRestriction);

lpObject = *lppRestriction;
}

if (FAILED(hr))
goto cleanup;

(*lppRestriction)->rt = RES_UNINITIALIZED;

pch = lpszStringCopy;

hr = HrParseCondition(lpObject, &pch, *lppRestriction);

if (*pch != '\0')
hr = HR_LOG(E_INVALIDARG);

if (FAILED(hr))
{
if (fNullObject)
MAPIFREEBUFFER(*lppRestriction);

goto cleanup;
}

cleanup:

MAPIFREEBUFFER(lpszStringCopy);

    RETURN(hr);
}

//
// Private functions
//

// $--HrAdrListDisplayNamesToString--------------------------------------------
//
// DESCRIPTION:Get a string representation of the display names in an ADRLIST.
//
// INPUT:
//
//[lpAdrList]-- Ptr to ADRLIST, the display names of which are to be
//   represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrAdrListDisplayNamesToString(          // RETURNS: HRESULT
INLPADRLISTlpAdrList,// address list ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr =NOERROR;
ULONGi =0;
ULONGj =0;
LPADRENTRYlpAdrEntry =NULL;
LPSTRlpszFormat ="\"%s\"";

    DEBUGPRIVATE("HrAdrListDisplayNamesToString()\n");

for (i = 0; i < lpAdrList->cEntries; i++)
{
lpAdrEntry = &lpAdrList->aEntries[i];

for (j = 0; j < lpAdrEntry->cValues; j++)
{
if (lpAdrEntry->rgPropVals[j].ulPropTag == PR_DISPLAY_NAME)
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 lpszFormat,
 lpAdrEntry->rgPropVals[j].Value.lpszA);
if (FAILED(hr))
goto cleanup;

lpszFormat = ", \"%s\"";

break;
}
}
}

cleanup:

RETURN(hr);
}

// $--HrAndRestrictionToString-------------------------------------------------
//
// DESCRIPTION:Get a string representation of an SAndRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to and restriction to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrAndRestrictionToString(     // RETURNS: HRESULT
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr =NOERROR;
ULONGi =0;
SAndRestriction *presAnd=NULL;

    DEBUGPRIVATE("HrAndRestrictionToString()\n");

    presAnd = &lpRes->res.resAnd;

if (presAnd->cRes < 2)
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Invalid SAndRestriction>");
    if (FAILED(hr))
        goto cleanup;
}
else
{
hr = HrPrivateRestrictionToString(presAnd->lpRes,
    lppszString,
    lpcbString,
    lpcbBuffer);
if (FAILED(hr))
goto cleanup;

for (i = 1; i < presAnd->cRes; i++)
{
hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, " & ");

    if (FAILED(hr))
        goto cleanup;

if (ISNONTERMINALRESTRICTION(presAnd->lpRes + i))
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "(");

    if (FAILED(hr))
        goto cleanup;

hr = HrPrivateRestrictionToString(presAnd->lpRes + i,
    lppszString,
    lpcbString,
    lpcbBuffer);
if (FAILED(hr))
goto cleanup;

hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 ")");
if (FAILED(hr))
goto cleanup;
}
else
{
hr = HrPrivateRestrictionToString(presAnd->lpRes + i,
    lppszString,
    lpcbString,
    lpcbBuffer);
if (FAILED(hr))
goto cleanup;
}
}
}

cleanup:

RETURN(hr);
}

// $--HrContentRestrictionToString---------------------------------------------
//
// DESCRIPTION:Get a string representation of an SContentRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to content restriction to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrContentRestrictionToString(     // RETURNS: HRESULT
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr = NOERROR;
LPSTRlpszPropName =NULL;

    DEBUGPRIVATE("HrContentRestrictionToString()\n");

hr = HrGetPropTagName(lpRes->res.resContent.ulPropTag,
   &lpszPropName);
if (FAILED(hr))
goto cleanup;

hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%s } ",
 lpszPropName);
if (FAILED(hr))
goto cleanup;

hr = HrPropValueToString(lpRes->res.resProperty.lpProp,
 lppszString,
   lpcbString,
   lpcbBuffer);
if (FAILED(hr))
goto cleanup;

cleanup:

MAPIFREEBUFFER(lpszPropName);

RETURN(hr);
}

// $--HrCopyRestrictionArray---------------------------------------------------
//
// DESCRIPTION:    Copy an SRestriction array.
//
// INPUT:
//
//[lpResSrc]-- Ptr to restriction array to be copied.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(). NULL is not permitted!
//[cRes]-- Count of array elements.
//
// OUTPUT:
//
//[lpResDest]-- Ptr to destination restriction array (allocated by caller).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrCopyRestrictionArray(// RETURNS: HRESULT
INLPSRestrictionlpResSrc,       // source restriction
INLPVOIDlpObject,// ptr to existing MAPI buffer
INULONGcRes,           // # elements in array
OUTLPSRestrictionlpResDest       // destination restriction
)
{
HRESULThr =NOERROR;
ULONGi;

    DEBUGPRIVATE("HrCopyRestrictionArray()\n");

for (i = 0; i < cRes; i++)
{
lpResDest[i] = lpResSrc[i];

switch (lpResSrc[i].rt)
{
    case RES_AND:
    {
    ULONGcRes =lpResSrc[i].res.resAnd.cRes;
    LPSRestrictionlpRes =lpResSrc[i].res.resAnd.lpRes;

    if (cRes == 0 || lpRes == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(sizeof(SRestriction) * cRes,
  lpObject,
  (LPVOID FAR *)
  &lpResDest[i].res.resAnd.lpRes);
    if (FAILED(hr))
    goto cleanup;

    hr = HrCopyRestrictionArray(lpRes,
   lpObject,
   cRes,
     lpResDest[i].res.resAnd.lpRes);
    if (FAILED(hr))
    goto cleanup;

    break;
    }

    case RES_OR:
    {
    ULONGcRes =lpResSrc[i].res.resOr.cRes;
    LPSRestrictionlpRes =lpResSrc[i].res.resOr.lpRes;

    if (cRes == 0 || lpRes == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(sizeof(SRestriction) * cRes,
  lpObject,
  (LPVOID FAR *)
  &lpResDest[i].res.resOr.lpRes);
    if (FAILED(hr))
                    goto cleanup;

    hr = HrCopyRestrictionArray(lpRes,
   lpObject,
   cRes,
     lpResDest[i].res.resOr.lpRes);
    if (FAILED(hr))
        goto cleanup;

    break;
    }

    case RES_NOT:
    {
    LPSRestrictionlpRes = lpResSrc[i].res.resNot.lpRes;

    if (lpRes == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)
  &lpResDest[i].res.resNot.lpRes);
    if (FAILED(hr))
    goto cleanup;

    hr = HrCopyRestrictionArray(lpRes,
   lpObject,
   1,
   lpResDest[i].res.resNot.lpRes);
    if (FAILED(hr))
    goto cleanup;

    break;
    }

    case RES_CONTENT:
    {
    if (lpResSrc[i].res.resContent.lpProp == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = HrDupPropset(1,
  lpResSrc[i].res.resContent.lpProp,
  lpObject,
  &lpResDest[i].res.resContent.lpProp);
    if (FAILED(hr))
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    break;
    }

    case RES_PROPERTY:
    {
    if (lpResSrc[i].res.resProperty.lpProp == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = HrDupPropset(1,
    lpResSrc[i].res.resProperty.lpProp,
  lpObject,
    &lpResDest[i].res.resProperty.lpProp);
    if (FAILED(hr))
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    break;
    }

    case RES_COMPAREPROPS:
    case RES_BITMASK:
    case RES_SIZE:
    case RES_EXIST:
    {
    // Nothing to do.

    break;
    }

    case RES_SUBRESTRICTION:
    {
    LPSRestrictionlpRes = lpResSrc[i].res.resSub.lpRes;

    if (lpRes == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)
  &lpResDest[i].res.resSub.lpRes);
    if (FAILED(hr))
    goto cleanup;

    hr = HrCopyRestrictionArray(lpRes,
   lpObject,
   1,
   lpResDest[i].res.resSub.lpRes);
    if (FAILED(hr))
    goto cleanup;

    break;
    }

    case RES_COMMENT:
    {
    LPSRestrictionlpRes = lpResSrc[i].res.resComment.lpRes;

    if (lpRes == NULL)
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

    hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)
  &lpResDest[i].res.resComment.lpRes);
    if (FAILED(hr))
    goto cleanup;

    hr = HrCopyRestrictionArray(lpRes,
lpObject,
1,
lpResDest[i].res.resComment.lpRes);
    if (FAILED(hr))
    goto cleanup;

    break;
    }

    default:
    {
    hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
}
}

cleanup:

RETURN(hr);
}

// $--HrCreateReplyMsg---------------------------------------------------------
//
// DESCRIPTION:Create a reply message for the reply action.
//
// INPUT:
//
//[lpFolder]-- Ptr to MAPI folder action applies to.
//[lpszReplyText]-- Ptr to message text.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//      MAPIAllocateBuffer().  NULL is not permitted.
//
// OUTPUT:
//
//  [lpcbentryid]-- Number of bytes in message's entry identifier.
//  [lppentryid]-- Ptr to message entry identifier.
//
//
// RETURNS:     NOERRORif successful;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrCreateReplyMsg(// RETURNS:HRESULT
INLPMAPIFOLDERlpFolder,// ptr to MAPI folder
INLPSTRlpszReplyText,// ptr to message text
INLPVOIDlpObject,// ptr to existing MAPI buf
OUTULONG FAR *lpcbentryid,    // ptr to message entry ID size
OUTLPENTRYID FAR *lppentryid// message entry ID buffer ptr
)
{
static
SizedSPropTagArray(1, PropTagArray) ={1, {PR_ENTRYID}};

    SPropValueaPropVals[5] ={0};
ULONGcValues =   0;
HRESULThr =NOERROR;
LPMESSAGElpMsg =NULL;
LPSPropProblemArraylpProblems =NULL;
LPSPropValuergPropVals =NULL;
ULONGulOldStatus =0;

    DEBUGPRIVATE("HrCreateReplyMsg()\n");

hr = lpFolder->CreateMessage(NULL, MAPI_ASSOCIATED, &lpMsg);

if (FAILED(hr))
goto cleanup;

    aPropVals[0].ulPropTag =PR_MESSAGE_CLASS;
    aPropVals[0].Value.lpszA ="IPM.Note.Rules.ReplyTemplate.Microsoft";
    aPropVals[1].ulPropTag =PR_DELETE_AFTER_SUBMIT;
    aPropVals[1].Value.b =FALSE;
    aPropVals[2].ulPropTag =PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
    aPropVals[2].Value.b =FALSE;
    aPropVals[3].ulPropTag =PR_MESSAGE_FLAGS;
    aPropVals[3].Value.l =MSGFLAG_READ | MSGFLAG_UNSENT;
    aPropVals[4].ulPropTag =PR_BODY;
    aPropVals[4].Value.lpszA =lpszReplyText;

hr = lpMsg->SetProps(5, aPropVals, &lpProblems);

if (FAILED(hr))
goto cleanup;

if (lpProblems != NULL)
{
hr = HR_LOG(E_FAIL);
MAPIFREEBUFFER(lpProblems);
goto cleanup;
}

hr = lpMsg->SaveChanges(KEEP_OPEN_READWRITE);

if (FAILED(hr))
goto cleanup;

// Now get the entryid for the newly created msg.

hr = lpMsg->GetProps((LPSPropTagArray)&PropTagArray,
 0,
 &cValues,
 &rgPropVals);
if (FAILED(hr))
goto cleanup;

if (cValues != 1||
rgPropVals[0].ulPropTag != PR_ENTRYID)
{
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
}

// Allocate space for entryid and store it.

*lpcbentryid = rgPropVals[0].Value.bin.cb;

hr = MAPIAllocateMore(*lpcbentryid,
  lpObject,
  (LPVOID FAR *)lppentryid);
if (FAILED(hr))
goto cleanup;

memcpy(*lppentryid, rgPropVals[0].Value.bin.lpb, *lpcbentryid);

cleanup:

ULRELEASE(lpMsg);

RETURN(hr);
}

// $--HrGetExplicitTag---------------------------------------------------------
//
// DESCRIPTION:Parse an explicit property tag token, returning results in
//*pti.  A value for ulPropTag is returned in pti->dwValue.
//
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetExplicitTag(     // RETURNS: HRESULT
IN OUTTOKENINFO *pti// ptr to token information
)
{
    HRESULT hr =NOERROR;
CHARchtmp =0;
CHAR *pchDigit =NULL;
CHAR *pch =NULL;
ULONGulPropTag =0;

    DEBUGPRIVATE("HrGetExplicitTag()\n");

    pch = pti->pch + 1;

if (*pch != 'B' && *pch != 'b'&&
*pch != 'N' && *pch != 'n'&&
*pch != 'S' && *pch != 's'&&
*pch != 'T' && *pch != 't')
    {
hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

pch++;

pch = lpszSkipWhiteSpace(pch);

pchDigit = pch;

if (!isxdigit(*pch))
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

while (isxdigit(*pch))
    {
pch++;
    }

// We temporarily terminate the hex number string, just to be absolutely
// certain that the following character is not used.

chtmp =*pch;
*pch ='\0';

    errno = 0;  // initialize it

ulPropTag = strtoul(pchDigit, (CHAR **)NULL, 16);

*pch = chtmp;

if (ulPropTag == ULONG_MAX && errno == ERANGE)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

pch = lpszSkipWhiteSpace(pch);

if (*pch != ']')
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

if (*(pti->pch + 1) == 'B' || *(pti->pch + 1) == 'b')
{
if (PROP_TYPE(ulPropTag) != PT_BOOLEAN)
{
pti->tt = InvalidTag;
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
}

pti->tt = BooleanTag;
}
else if (*(pti->pch + 1) == 'N' || *(pti->pch + 1) == 'n')
{
if (PROP_TYPE(ulPropTag) != PT_LONG)
{
pti->tt = InvalidTag;
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
}

pti->tt = NumericTag;
}
else if (*(pti->pch + 1) == 'S' || *(pti->pch + 1) == 's')
{
if (PROP_TYPE(ulPropTag) != PT_STRING8&&
PROP_TYPE(ulPropTag) != PT_UNICODE)
{
pti->tt = InvalidTag;
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
}

pti->tt = StringTag;
}
else if (*(pti->pch + 1) == 'T' || *(pti->pch + 1) == 't')
{
if (PROP_TYPE(ulPropTag) != PT_SYSTIME)
{
pti->tt = InvalidTag;
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
}

pti->tt = TimeTag;
}
else
{
// We should not have gotten past the preliminary input check, but
// we include this to prevent maintenance headaches.

hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
}

pti->cch =pch - pti->pch + 1;
pti->dwValue =ulPropTag;

cleanup:

    RETURN(hr);
}

// $--HrGetExpressionToken-----------------------------------------------------
//
// DESCRIPTION:Parse a token in a condition expression, returning results in
//*pti.  The types of token that may be parsed are defined by the
//TOKENTYPE enumeration.
//
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_*otherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetExpressionToken(       // RETURNS: HRESULT
IN OUTTOKENINFO *pti     // token information ptr
)
{
HRESULThr =NOERROR;
CHAR *pch =NULL;

    DEBUGPRIVATE("HrGetExpressionToken()\n");

    pch = lpszSkipWhiteSpace(pti->pch);

pti->pch =pch;
pti->cch =0;
pti->tt =NotRecognized;
pti->dwValue =0;// Only valid if otherwise assigned below.

switch (*pch)
{
    case '&':
    pti->cch =1;
    pti->tt =AndOp;
    break;

    case '}':
    pti->cch =1;
    pti->tt =ContainsOp;
    break;

    case '\0':
    pti->cch =1;
    pti->tt =EndOfString;
    break;

    case '=':
    pti->cch =1;
    pti->tt =EqualOp;
    break;

    case '>':
    pti->cch =1;
    pti->tt =GreaterThanOp;
    break;

    case '(':
    pti->cch =1;
    pti->tt =LeftParen;
    break;

    case '<':
    pti->cch =1;
    pti->tt =LessThanOp;
    break;

    case '#':
    pti->cch =1;
    pti->tt =NotEqualOp;
    break;

    case '!':
    pti->cch =1;
    pti->tt =NotOp;
    break;

    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
// We try to match a time first; if this fails, we then try to
// just match a number.  This order is important; a correct
// time format will always match a number format.

    hr = HrGetTimeLiteral(pti);

if (FAILED(hr))
hr = HrGetNumericLiteral(pti);

    break;

    case '-':
    case '+':
    hr = HrGetNumericLiteral(pti);
    break;

    case 'P':
    case 'p':
    hr = HrGetNamedTag(pti);
    break;

    case '[':
    hr = HrGetExplicitTag(pti);
    break;

    case '|':
    pti->cch =1;
    pti->tt =OrOp;
    break;

    case ')':
    pti->cch =1;
    pti->tt =RightParen;
    break;

    case '"':
    hr = HrGetStringLiteral(pti);
    break;

    default:
            hr = HR_LOG(E_INVALIDARG);
    break;
}

RETURN(hr);
}

// $--HrGetFolderPath----------------------------------------------------------
//
// DESCRIPTION:Given the ENTRYID for a folder and a ptr to its store,
//get its path name.
//
// INPUT:
//
//  [lpMDB]-- MAPI store ptr.
//  [cbentryid]-- Count of bytes in folder's entry identifier.
//  [lpentryid]-- Ptr to folder entry identifier.
//
// OUTPUT:
//
//[lppszFolderPath]-- Ptr to ptr that will be set to point at folder path
//   on successful return.
//
// RETURNS:     NOERROR if successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//EDK_E_NOT_FOUNDif specified store not found;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetFolderPath(         // RETURNS: HRESULT
INLPMDBlpMDB,// MAPI store ptr
INULONGcbentryid,// count of bytes in folder entry ID
INLPENTRYIDlpentryid,// folder entry ID ptr
OUTLPSTR FAR *lppszFolderPath // folder path ptr ptr
)
{
static
SizedSPropTagArray(3, PropTagArray) ={3, {PR_DISPLAY_NAME,
 PR_PARENT_ENTRYID,
 PR_FOLDER_TYPE}};
ULONGcValues =   0;
HRESULThr =NOERROR;
LPMAPIFOLDERlpFolder =NULL;
LPSTRlpszParentFolderPath =NULL;
LPSPropValuergPropVals =NULL;
ULONGulObjType =0;

    DEBUGPRIVATE("HrGetFolderPath()\n");

// Get the folder object;

hr = lpMDB->OpenEntry(cbentryid,
  lpentryid,
  NULL,
  MAPI_DEFERRED_ERRORS,
  &ulObjType,
  (LPUNKNOWN FAR *)&lpFolder);
if (FAILED(hr))
goto cleanup;

if (ulObjType != MAPI_FOLDER)
{
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
}

// Get the needed properties.

hr = lpFolder->GetProps((LPSPropTagArray)&PropTagArray,
 0,
 &cValues,
 &rgPropVals);
if (FAILED(hr))
goto cleanup;

if (cValues != 3||
rgPropVals[0].ulPropTag != PR_DISPLAY_NAME||
rgPropVals[1].ulPropTag != PR_PARENT_ENTRYID||
rgPropVals[2].ulPropTag != PR_FOLDER_TYPE)
{
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
}

if (rgPropVals[2].Value.l == FOLDER_ROOT)
{
// We protect ourselves from various possible implementations of the
// root folder name by covering all possibilities.

if (rgPropVals[0].Value.lpszA != NULL)
{
hr = MAPIAllocateBuffer(strlen(rgPropVals[0].Value.lpszA) + 1,
(LPVOID FAR *)lppszFolderPath);
if (FAILED(hr))
goto cleanup;

strcpy(*lppszFolderPath, rgPropVals[0].Value.lpszA);
}
else
{
hr = MAPIAllocateBuffer(1, (LPVOID FAR *)lppszFolderPath);

if (FAILED(hr))
goto cleanup;

*lppszFolderPath[0] = '\0';
}
}
else
{
hr = HrGetFolderPath(lpMDB,
 rgPropVals[1].Value.bin.cb,
 (LPENTRYID)rgPropVals[1].Value.bin.lpb,
 &lpszParentFolderPath);
if (FAILED(hr))
goto cleanup;

hr = MAPIAllocateBuffer(strlen(lpszParentFolderPath) +
strlen(rgPropVals[0].Value.lpszA) + 2,
    (LPVOID FAR *)lppszFolderPath);
if (FAILED(hr))
goto cleanup;

strcpy(*lppszFolderPath, lpszParentFolderPath);

if (*lppszFolderPath[0] != '\0')
strcat(*lppszFolderPath, "\\");

strcat(*lppszFolderPath, rgPropVals[0].Value.lpszA);
}

cleanup:

MAPIFREEBUFFER(lpszParentFolderPath);
MAPIFREEBUFFER(rgPropVals);
ULRELEASE(lpFolder);

    RETURN(hr);
}

//$--HrGetMoveCopyArgs---------------------------------------------------------
//
// DESCRIPTION:Parse the parenthetical expression following a move or copy
//action argument.  The results are stored in the ACTION struct
//passed in as an argument.
//
// INPUT:
//
//[lpSession]-- Ptr to MAPI session.
//[pszFolder]-- Target folder path for move or copy.
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(). NULL is not permitted!
//
// INPUT/OUTPUT:
//
//[lpAction]-- Action structure to set up.
//
// RETURNS:     NOERRORif successful;
//E_INVALIDARGif bad input;
//E_*otherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetMoveCopyArgs(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,// ptr to MAPI session
INLPSTRpszFolder,// target folder path
INLPVOIDlpObject,// ptr to existing MAPI buffer
IN OUTLPACTIONlpAction// ACTION structure to set up
)
{
ULONGcbEIDActsFolder =0;
ULONGcbEIDActsStore =0;
    HRESULT hr =NOERROR;
LPMDBlpActsStore =NULL;
LPENTRYIDlpEIDActsFolder =NULL;
LPENTRYIDlpEIDActsStore =NULL;
LPENTRYIDlpEIDTmp =NULL;
CHAR *pch =NULL;
CHARszActsFolder[MAX_PATH + 1];
CHARszActsStore[MAX_PATH + 1];

    DEBUGPRIVATE("HrGetMoveCopyArgs()\n");

pch = strchr(pszFolder, '\\');

if (pch == NULL||
pch - pszFolder == 0||
pch - pszFolder + 1 > sizeof(szActsStore))
    {
hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

memcpy(szActsStore, pszFolder, pch - pszFolder);

szActsStore[pch - pszFolder] = '\0';

pch++;

if (*pch == '\0'||
strlen(pch) + 1 > sizeof(szActsFolder))
    {
hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

strcpy(szActsFolder, pch);

hr = HrMAPIFindStore(lpSession, szActsStore, &cbEIDActsStore, &lpEIDTmp);

if (FAILED(hr))
goto cleanup;

hr = MAPIAllocateMore(cbEIDActsStore,
  lpObject,
  (LPVOID FAR *)&lpEIDActsStore);
if (FAILED(hr))
goto cleanup;

memcpy(lpEIDActsStore, lpEIDTmp, cbEIDActsStore);

MAPIFREEBUFFER(lpEIDTmp);

hr = lpSession->OpenMsgStore(0,
 cbEIDActsStore,
 lpEIDActsStore,
 NULL,
 MAPI_DEFERRED_ERRORS,
 &lpActsStore);
if (FAILED(hr))
goto cleanup;

hr = HrMAPIFindFolderEx(lpActsStore,
'\\',
szActsFolder,
&cbEIDActsFolder,
&lpEIDTmp);
if (FAILED(hr))
goto cleanup;

hr = MAPIAllocateMore(cbEIDActsFolder,
  lpObject,
  (LPVOID FAR *)&lpEIDActsFolder);
if (FAILED(hr))
goto cleanup;

memcpy(lpEIDActsFolder, lpEIDTmp, cbEIDActsFolder);

MAPIFREEBUFFER(lpEIDTmp);

lpAction->actMoveCopy.cbStoreEntryId =cbEIDActsStore;
lpAction->actMoveCopy.lpStoreEntryId =lpEIDActsStore;
lpAction->actMoveCopy.cbFldEntryId =cbEIDActsFolder;
lpAction->actMoveCopy.lpFldEntryId =lpEIDActsFolder;

cleanup:

ULRELEASE(lpActsStore);

    RETURN(hr);
}

// $--HrGetNamedTag------------------------------------------------------------
//
// DESCRIPTION:Parse a named property tag token (PR_*), returning results in
//*pti.  A value for ulPropTag is returned in pti->dwValue.
//
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetNamedTag(        // RETURNS: HRESULT
IN OUTTOKENINFO *pti     // ptr to token information
)
{
HRESULThr =NOERROR;
CHARchtmp =0;
CHAR *pch =NULL;
ULONGulPropTag =0;

    DEBUGPRIVATE("HrGetNamedTag()\n");

    pch = pti->pch + 1;

while (isalnum(*pch) || *pch == '_')
pch++;

// Terminate the putative PR_ string so we can look it up in the PropTbl.

chtmp =*pch;
*pch ='\0';

hr = HrGetPropTagValue(pti->pch, &ulPropTag);

*pch = chtmp;

if (FAILED(hr))
goto cleanup;

if (PROP_TYPE(ulPropTag) == PT_BOOLEAN)
{
pti->tt = BooleanTag;
}
else if (PROP_TYPE(ulPropTag) == PT_LONG)
{
pti->tt = NumericTag;
}
else if (PROP_TYPE(ulPropTag) == PT_STRING8||
 PROP_TYPE(ulPropTag) == PT_UNICODE)
{
pti->tt = StringTag;
}
else if (PROP_TYPE(ulPropTag) == PT_SYSTIME)
{
pti->tt = TimeTag;
}
else
{
pti->tt = InvalidTag;

hr = HR_LOG(E_INVALIDARG);
}

pti->cch =pch - pti->pch;
pti->dwValue =ulPropTag;

cleanup:

    RETURN(hr);
}

// $--HrGetNumericLiteral------------------------------------------------------
//
// DESCRIPTION:Parse a numeric literal token, returning results in
//*pti.  A value for the numeric literal is returned in
//pti->dwValue.
//
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetNumericLiteral(      // RETURNS: HRESULT
IN OUTTOKENINFO *pti// ptr to token info structure
)
{
    HRESULThr =   NOERROR;
CHARchtmp =0;
LONGlValue =0;
CHAR *pch =NULL;
ULONGulValue =   0;

    DEBUGPRIVATE("HrGetNumericLiteral()\n");

    pch = pti->pch + 1;

while (isdigit(*pch))
pch++;

// If there is only 1 CHAR, it better be a digit rather than a sign.

if (pch == pti->pch + 1&& !isdigit(*(pti->pch)))
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

// We temporarily terminate the number string, just to be absolutely
// certain that the following character is not used.

chtmp =*pch;
*pch ='\0';

errno = 0;  // initialize it

// Use strtol() if the number is negative; otherwise use strtoul().

if (*(pti->pch) == '-')
{
lValue = strtol(pti->pch, (CHAR **)NULL, 10);

*pch = chtmp;

if ((lValue == LONG_MAX || lValue == LONG_MIN) && errno == ERANGE)
        {
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
        }

pti->dwValue = lValue;
}
else
{
ulValue = strtoul(pti->pch, (CHAR **)NULL, 10);

*pch = chtmp;

if (ulValue == ULONG_MAX && errno == ERANGE)
        {
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
        }

pti->dwValue =ulValue;
}

pti->cch =pch - pti->pch;
pti->tt =NumericLiteral;

cleanup:

    RETURN(hr);
}

// $--HrGetStoreName-----------------------------------------------------------
//
// DESCRIPTION:Given a ptr to a store, get its display name.
//
// INPUT:
//
//  [lpMDB]-- MAPI store ptr.
//
// OUTPUT:
//
//[lppszName]-- Ptr to ptr that will be set to point at store display
//   name on successful return.
//
// RETURNS:     NOERROR if successful;
//E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//EDK_E_NOT_FOUNDif specified store not found;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetStoreName(         // RETURNS: HRESULT
INLPMDBlpMDB,// MAPI store ptr
OUTLPSTR FAR *lppszName  // store name ptr ptr
)
{
static
SizedSPropTagArray(1, PropTagArray) ={1, {PR_DISPLAY_NAME}};

ULONGcValues =   0;
HRESULThr =NOERROR;
LPSPropValuelpProp  =   NULL;

    DEBUGPRIVATE("HrGetStoreName()\n");

// Get the display name property.

hr = lpMDB->GetProps((LPSPropTagArray)&PropTagArray, 0, &cValues, &lpProp);

if (FAILED(hr))
goto cleanup;

if (cValues != 1 || lpProp->ulPropTag != PR_DISPLAY_NAME)
{
hr = HR_LOG(EDK_E_NOT_FOUND);
goto cleanup;
}

hr = MAPIAllocateBuffer(strlen(lpProp->Value.lpszA) + 1,
(LPVOID FAR *)lppszName);
if (FAILED(hr))
goto cleanup;

strcpy(*lppszName, lpProp->Value.lpszA);

cleanup:

MAPIFREEBUFFER(lpProp);

    RETURN(hr);
}

// $--HrGetStringLiteral-------------------------------------------------------
//
// DESCRIPTION:Parse a string literal token, returning results in
//*pti.  The user should allocate separate storage to save the
//string literal if it is to be converted to a MAPI property
//value.
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//
// Notes:pti->pch is left pointing at the opening quote and the value
//returned for pti->cch includes the leading and trailing quotes
//(ie. pti->cch >= 2).
//-----------------------------------------------------------------------------

HRESULT
HrGetStringLiteral(// RETURNS: HRESULT
IN OUTTOKENINFO *pti         // ptr to token information
)
{
    HRESULT hr =NOERROR;
CHAR *pch =   NULL;

    DEBUGPRIVATE("HrGetStringLiteral()\n");

    pch = pti->pch + 1;

while (*pch != '"' && *pch != '\0')
pch++;

if (*pch != '"')
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

pti->cch =pch - pti->pch + 1;
pti->tt =StringLiteral;

cleanup:

    RETURN(hr);
}

// $--HrGetTimeLiteral---------------------------------------------------------
//
// DESCRIPTION:Parse a time literal token, returning results in *pti.
//A value for the time literal is returned in pti->ftValue.
//
// INPUT/OUTPUT:
//
//[pti]-- Ptr to token information structure.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrGetTimeLiteral(      // RETURNS: HRESULT
IN OUTTOKENINFO *pti// ptr to token info structure
)
{
    static CHARDateTemplate[] ="dddd-dd-dd";

CHARchtmp =0;
FILETIMEftLocal ={0};
    LONGHour =0;
    HRESULThr =   NOERROR;
    INTi =0;
    LONGMinute =0;
    LONGSecond =0;
CHAR *pch =pti->pch;
SYSTEMTIMEst ={0};

    DEBUGPRIVATE("HrGetTimeLiteral()\n");

    if (!isdigit(*pch)||
        !isdigit(*(pch + 1))||
        *(pch + 2) != ':')
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    Hour = atol(pch);

    if (Hour > 23)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

st.wHour = (WORD)Hour;

    pch += 3;

    if (!isdigit(*pch)||
        !isdigit(*(pch + 1))||
        (*(pch + 2) != ':' && !isspace(*(pch + 2))))
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    Minute = atol(pch);

    if (Minute > 59)
    {
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

st.wMinute = (WORD)Minute;

    if (*(pch + 2) == ':')
    {
        pch += 3;

        if (!isdigit(*pch)||
            !isdigit(*(pch + 1))||
            !isspace(*(pch + 2)))
        {
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
        }

        Second = atol(pch);

        if (Second > 59)
        {
            hr = HR_LOG(E_INVALIDARG);
            goto cleanup;
        }

st.wSecond = (WORD)Second;
    }

pch += 2;

pch = lpszSkipWhiteSpace(pch);

    for (i = 0; i < sizeof(DateTemplate) - 1; i++)
    {
        if (DateTemplate[i] == 'd')
        {
            if (!isdigit(*(pch + i)))
            {
                hr = HR_LOG(E_INVALIDARG);
                goto cleanup;
            }
        }
        else
        {
            if (DateTemplate[i] != *(pch + i))
            {
                hr = HR_LOG(E_INVALIDARG);
                goto cleanup;
            }
        }
    }

// We let SystemTimeToFileTime() worry about range checking the date values.

st.wYear =(WORD)atol(pch);
st.wMonth =(WORD)atol(pch + 5);

// We terminate the day value just in case (insures correct day
// interpretation even if the rest of the expression is not well formed).

chtmp = *(pch + sizeof(DateTemplate) - 1);
*(pch + sizeof(DateTemplate) - 1) = '\0';

st.wDay =(WORD)atol(pch + 8);

*(pch + sizeof(DateTemplate) - 1) = chtmp;

pch += (sizeof(DateTemplate) - 1);

// Convert SYSTEMTIME to FILETIME.

if (!SystemTimeToFileTime(&st, &ftLocal))
{
        hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

// We now presumably have a local FILETIME.  Convert to UTC.

if (!LocalFileTimeToFileTime(&ftLocal, &pti->ftValue))
{
        hr = HR_LOG(E_FAIL);
goto cleanup;
}

pti->cch =pch - pti->pch;
pti->tt =TimeLiteral;

cleanup:

    RETURN(hr);
}

// $--HrNotRestrictionToString-------------------------------------------------
//
// DESCRIPTION:Get a string representation of an SNotRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to not restriction to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrNotRestrictionToString(// RETURNS: HRESULT
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr =NOERROR;
SNotRestriction *presNot=NULL;

    DEBUGPRIVATE("HrNotRestrictionToString()\n");

    presNot=&lpRes->res.resNot;

hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, "!");

if (FAILED(hr))
goto cleanup;

if (ISNONTERMINALRESTRICTION(presNot->lpRes))
{
hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, "(");

if (FAILED(hr))
goto cleanup;

hr = HrPrivateRestrictionToString(presNot->lpRes,
  lppszString,
  lpcbString,
  lpcbBuffer);
if (FAILED(hr))
goto cleanup;

hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, ")");

if (FAILED(hr))
goto cleanup;
}
else
{
hr = HrPrivateRestrictionToString(presNot->lpRes,
  lppszString,
  lpcbString,
  lpcbBuffer);
if (FAILED(hr))
goto cleanup;
}

cleanup:

RETURN(hr);
}

// $--HrOrRestrictionToString--------------------------------------------------
//
// DESCRIPTION:Get a string representation of an SOrRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to or restriction to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrOrRestrictionToString(// RETURNS: HRESULT
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr = NOERROR;
ULONGi =0;
SOrRestriction *presOr =NULL;

    DEBUGPRIVATE("HrOrRestrictionToString()\n");

    presOr = &lpRes->res.resOr;

if (presOr->cRes < 2)
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Invalid SOrRestriction>");
if (FAILED(hr))
goto cleanup;
}
else
{
hr = HrPrivateRestrictionToString(presOr->lpRes,
  lppszString,
  lpcbString,
  lpcbBuffer);
if (FAILED(hr))
goto cleanup;

for (i = 1; i < presOr->cRes; i++)
{
hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, " | ");

    if (FAILED(hr))
        goto cleanup;

if (ISNONTERMINALRESTRICTION(presOr->lpRes + i))
{
hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, "(");

    if (FAILED(hr))
        goto cleanup;

hr = HrPrivateRestrictionToString(presOr->lpRes + i,
  lppszString,
  lpcbString,
  lpcbBuffer);
    if (FAILED(hr))
        goto cleanup;

hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, ")");

    if (FAILED(hr))
        goto cleanup;
}
else
{
hr = HrPrivateRestrictionToString(presOr->lpRes + i,
  lppszString,
  lpcbString,
  lpcbBuffer);
if (FAILED(hr))
goto cleanup;
}
}
}

cleanup:

RETURN(hr);
}

// $--HrParseAction------------------------------------------------------------
//
// DESCRIPTION:Parse an action string.
//
// INPUT:
//
//[lpSession]-- Ptr to MAPI session.
//
//[lpFolder]-- Ptr to MAPI folder action applies to.
//
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(). NULL is not permitted!
//
//[pch]-- Ptr to string to be parsed.
//
// INPUT/OUTPUT:
//
//[lpAction]-- Ptr to uninitialized ACTION structure on input;
//   It will be appropriately initialized on output.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_OUTOFMEMORYif out of memory;
//E_UNEXPECTEDif there is an internal error;
//              E_*otherwise.
//-----------------------------------------------------------------------------

HRESULT
HrParseAction(// RETURNS: HRESULT
INLPMAPISESSIONlpSession,// ptr to MAPI session
INLPMAPIFOLDERlpFolder,// ptr to MAPI folder
INLPVOIDlpObject,// ptr to existing MAPI buffer
INCHAR *pch,   // string ptr
IN OUTLPACTIONlpAction   // action ptr
)
{
static CHARszBounceKeyword[] ="bounce";
static CHARszCopyKeyword[] ="copy";
static CHARszDelegateKeyword[] ="delegate";
static CHARszDeleteKeyword[] ="delete";
static CHARszForwardKeyword[] ="forward";
static CHARszMarkReadKeyword[] ="markread";
static CHARszMoveKeyword[] ="move";
static CHARszMsgIdKeyword[] ="msgid";
static CHARszReplyKeyword[] ="reply";
static CHARszTagKeyword[] ="tag";

CHARchtmp =0;
HRESULThr =NOERROR;
LPADRBOOKlpAdrBook =NULL;
LPADRLISTlpAdrList =NULL;
CHAR *pchEnd =pch;

// Set all fields in ACTION struct to 0.

memset(lpAction, 0, sizeof(*lpAction));

// Find the start of the action keyword.

pch = lpszSkipWhiteSpace(pch);

pchEnd = lpszSkipNonWhiteSpace(pch);

// Terminate the action keyword so we can parse it.

chtmp =*pchEnd;
*pchEnd ='\0';

// Identify the action keyword.

if (!stricmp(pch, szBounceKeyword))
{
lpAction->acttype = OP_BOUNCE;
}
else if (!stricmp(pch, szCopyKeyword))
{
lpAction->acttype = OP_COPY;
}
else if (!stricmp(pch, szDelegateKeyword))
{
lpAction->acttype = OP_DELEGATE;
}
else if (!stricmp(pch, szDeleteKeyword))
{
lpAction->acttype = OP_DELETE;
}
else if (!stricmp(pch, szForwardKeyword))
{
lpAction->acttype = OP_FORWARD;
}
else if (!stricmp(pch, szMarkReadKeyword))
{
lpAction->acttype = OP_MARK_AS_READ;
}
else if (!stricmp(pch, szMoveKeyword))
{
lpAction->acttype = OP_MOVE;
}
else if (!stricmp(pch, szReplyKeyword))
{
lpAction->acttype = OP_REPLY;
}
else if (!stricmp(pch, szTagKeyword))
{
lpAction->acttype = OP_TAG;
}
else
{
*pchEnd = chtmp;
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

*pchEnd = chtmp;

pch = lpszSkipWhiteSpace(pchEnd);

switch (lpAction->acttype)
{
case OP_BOUNCE:
{
pchEnd = lpszSkipNonWhiteSpace(pch);

// Terminate the bounce code so we can parse it.

chtmp =*pchEnd;
*pchEnd ='\0';

if (!strcmp(pch, "BOUNCE_MESSAGE_SIZE_TOO_LARGE"))
{
lpAction->scBounceCode = BOUNCE_MESSAGE_SIZE_TOO_LARGE;
}
else if (!strcmp(pch, "BOUNCE_FORMS_MISMATCH"))
{
lpAction->scBounceCode = BOUNCE_FORMS_MISMATCH;
}
else if (!strcmp(pch, "BOUNCE_ACCESS_DENIED"))
{
lpAction->scBounceCode = BOUNCE_ACCESS_DENIED;
}
else
{
*pchEnd = chtmp;
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

*pchEnd = chtmp;

pch = lpszSkipWhiteSpace(pchEnd);

if (*pch != '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

break;
}

case OP_COPY:
case OP_MOVE:
{
hr = HrGetMoveCopyArgs(lpSession, pch, lpObject, lpAction);

if (FAILED(hr))
goto cleanup;

break;
}

case OP_DELEGATE:
case OP_FORWARD:
{
ULONGcRecips =0;
ULONGcQuotes =0;
CHAR *pQuote =pch;

// Count quotation mark pairs to arrive at a count of recipients.
// This is more reliable than counting comma's since a comma may
// be embedded in a recipient name.

while ((pQuote = strchr(pQuote, '"')) != NULL)
{
cQuotes++;
pQuote++;
}

if (cQuotes == 0 || (cQuotes & 1) != 0)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

cRecips = cQuotes >> 1;

hr = MAPIAllocateBuffer(CbNewADRLIST(cRecips), (LPVOID *)&lpAdrList);

if (FAILED(hr))
goto cleanup;

{
ULONGi           =   0;
LPADRENTRYlpAdrEntry  =   NULL;
LPSTRlpszName    =   NULL;

lpAdrList->cEntries = cRecips;

// Init the ADRENTRY's prior to actually allocating the SPropValue
// arrays.  This makes cleanup possible if there is an error.

for (i = 0; i < cRecips; i++)
{
lpAdrList->aEntries[i].ulReserved1 =0;
lpAdrList->aEntries[i].cValues =1;
lpAdrList->aEntries[i].rgPropVals =NULL;
}

// Now allocate and initialize the SPropValue arrays.

for (i = 0; i < cRecips; i++)
{
hr = MAPIAllocateBuffer(sizeof(SPropValue),
 (LPVOID *)&lpAdrList->aEntries[i].rgPropVals);

if (FAILED(hr))
goto cleanup;

lpAdrList->aEntries[i].rgPropVals->ulPropTag = PR_DISPLAY_NAME;
lpAdrList->aEntries[i].rgPropVals->Value.lpszA = NULL;
}

lpAdrEntry = lpAdrList->aEntries;

// Parse the recipient names.

// Note that the index used below is 1-based (ie., don't use it
// for a 0-based array!).

for (i = 1; i <= cRecips; i++)
{
if (*pch != '"')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

pch++;

lpszName = pch;

while (*pch != '"' && *pch != '\0')
pch++;

if (*pch == '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

*pch = '\0';

hr = MAPIAllocateMore(strlen(lpszName) + 1,
  lpAdrEntry->rgPropVals,
  (LPVOID *)&lpAdrEntry->
  rgPropVals[0].Value.lpszA);
if (FAILED(hr))
goto cleanup;

strcpy(lpAdrEntry->rgPropVals[0].Value.lpszA, lpszName);

lpAdrEntry++;

*pch = '"';

pch++;

pch = lpszSkipWhiteSpace(pch);

if (i != cRecips)
{
if (*pch != ',')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

pch++;

pch = lpszSkipWhiteSpace(pch);
}
else
{
if (*pch != '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
}
}

hr = lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &lpAdrBook);

if (FAILED(hr))
goto cleanup;

hr = lpAdrBook->ResolveName(0, 0, NULL, lpAdrList);

if (FAILED(hr))
goto cleanup;

// Now look for problems reported in the returned ADRLIST.

for (i = 0; i < cRecips; i++)
{
ULONGj;

lpAdrEntry = &lpAdrList->aEntries[i];

for (j = 0; j < lpAdrEntry->cValues; j++)
{
LPSPropValuepPropVal;

pPropVal = &lpAdrEntry->rgPropVals[j];

if (PROP_TYPE(pPropVal->ulPropTag == PT_ERROR))
{
hr = pPropVal->Value.err;

break;
}
}

if (FAILED(hr))
goto cleanup;
}

// Now reallocate the ADRLIST using linked allocation; we did
// not do this at first to avoid problems in ResolveName().

lpAction->lpadrlist = NULL;

hr = MAPIAllocateMore(CbNewADRLIST(cRecips),
  lpObject,
  (LPVOID *)&lpAction->lpadrlist);
if (FAILED(hr))
goto cleanup;

memcpy(lpAction->lpadrlist, lpAdrList, CbNewADRLIST(cRecips));

for (i = 0; i < cRecips; i++)
{
hr = HrDupPropset(lpAdrList->aEntries[i].cValues,
  lpAdrList->aEntries[i].rgPropVals,
  lpObject,
  &lpAction->lpadrlist->aEntries[i].rgPropVals);

if (FAILED(hr))
goto cleanup;
}
}

break;
}

case OP_DELETE:
case OP_MARK_AS_READ:
{
if (*pch != '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

break;
}

case OP_REPLY:
{
if (*pch == '"')
{
pch++;

pchEnd = pch;

while (*pchEnd != '"' && *pchEnd != '\0')
pchEnd++;

if (*pchEnd == '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

*pchEnd = '\0';

// Make a message from *pch.

hr = HrCreateReplyMsg(lpFolder,
  pch,
  lpObject,
  &lpAction->actReply.cbEntryId,
  &lpAction->actReply.lpEntryId);
if (FAILED(hr))
goto cleanup;

*pchEnd = '"';

pchEnd++;
}
else
{
ULONGcNibbles =0;

pchEnd = lpszSkipNonWhiteSpace(pch);

// Terminate the keyword so we can parse it.

chtmp =*pchEnd;
*pchEnd ='\0';

if (stricmp(pch, szMsgIdKeyword))
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

*pchEnd = chtmp;

pch = lpszSkipWhiteSpace(pchEnd);

if (*pch == '0' && (*(pch + 1) == 'x' || *(pch + 1) == 'X'))
pch += 2;

pchEnd = pch;

while (isxdigit(*pchEnd))
pchEnd++;

cNibbles = pchEnd - pch;

if (cNibbles == 0 || (cNibbles & 1) != 0)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

lpAction->actReply.cbEntryId = cNibbles >> 1;

// Allocate space for entryid.

hr = MAPIAllocateMore(lpAction->actReply.cbEntryId,
  lpObject,
  (LPVOID FAR *)&lpAction->actReply.lpEntryId);
if (FAILED(hr))
goto cleanup;

// Determine it's value and store it.

BYTEloNibble =0;
BYTEhiNibble =0;
BYTE *pByte =(BYTE *)lpAction->actReply.lpEntryId;

while (pch < pchEnd)
{
if (*pch >= '0' && *pch <= '9')
hiNibble = *pch - '0';
else if (*pch >= 'a' && *pch <= 'f')
hiNibble = *pch - ('a' - 0xa);
else
hiNibble = *pch - ('A' - 0xa);

pch++;

if (*pch >= '0' && *pch <= '9')
loNibble = *pch - '0';
else if (*pch >= 'a' && *pch <= 'f')
loNibble = *pch - ('a' - 0xa);
else
loNibble = *pch - ('A' - 0xa);

pch++;

*pByte = (hiNibble << 4) + loNibble;

pByte++;
}
}

// Finish parsing line.

pch = lpszSkipWhiteSpace(pchEnd);

if (*pch != '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

break;
}

case OP_TAG:
{
TOKENINFOti ={pch, 0, NotRecognized, 0};
ULONGpt =PT_UNSPECIFIED;

if (*pch == 'P' || *pch == 'p')
hr = HrGetNamedTag(&ti);
else if (*pch == '[')
hr = HrGetExplicitTag(&ti);
else
hr = HR_LOG(E_INVALIDARG);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

pt = PROP_TYPE(ti.dwValue);

pch = lpszSkipWhiteSpace(pch);

if (*pch != ',')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

pch++;// Skip over the comma.

pch = lpszSkipWhiteSpace(pch);

lpAction->propTag.ulPropTag = ti.dwValue;

ti.pch = pch;
ti.cch = 0;
ti.tt =NotRecognized;
ti.dwValue =0;

if (pt == PT_BOOLEAN)
{
hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

// A boolean literal is essentially a numeric literal with
// a restricted range, so...

if (ti.tt != NumericLiteral|| ti.dwValue > 1)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

lpAction->propTag.Value.b = (USHORT)ti.dwValue;
}
else if (pt == PT_LONG)
{
hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

if (ti.tt != NumericLiteral)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

lpAction->propTag.Value.l = ti.dwValue;
}
else if (pt == PT_STRING8)
{
hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

if (ti.tt != StringLiteral)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

hr = MAPIAllocateMore(ti.cch + 1 - 2, lpObject,
  (LPVOID FAR *)&lpAction->propTag.Value.lpszA);

if (FAILED(hr))
goto cleanup;

memcpy(lpAction->propTag.Value.lpszA, ti.pch + 1, ti.cch - 2);

*(lpAction->propTag.Value.lpszA + ti.cch - 2) = '\0';
}
else if (pt == PT_SYSTIME)
{
hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

if (ti.tt != TimeLiteral)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

lpAction->propTag.Value.ft = ti.ftValue;
}
else if (pt == PT_UNICODE)
{
INTcch = 0;

hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;

if (ti.tt != StringLiteral)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

if (ti.cch > 2)
{
cch = MultiByteToWideChar(CP_ACP,
  0,
  ti.pch + 1,
  ti.cch - 2,
  NULL,
  0);

if (cch == 0)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
}

hr = MAPIAllocateMore((cch + 1) * sizeof(WCHAR), lpObject,
  (LPVOID FAR *)&lpAction->propTag.Value.lpszW);

if (FAILED(hr))
goto cleanup;

if (ti.cch > 2)
{
if (!MultiByteToWideChar(CP_ACP,
 0,
 ti.pch + 1,
 ti.cch - 2,
 lpAction->propTag.Value.lpszW,
 cch))
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
}

*(lpAction->propTag.Value.lpszW + cch) = 0;
}
else
{
hr = HR_LOG(E_UNEXPECTED);// There is an internal error.
goto cleanup;
}

pch = lpszSkipWhiteSpace(pch);

if (*pch != '\0')
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

break;
}

default:
{
hr = HR_LOG(E_UNEXPECTED);// There is an internal error.
goto cleanup;

break;
}
}

cleanup:

ULRELEASE(lpAdrBook);
FREEPADRLIST(lpAdrList);

    return hr;
}

// $--HrParseCondition---------------------------------------------------------
//
// DESCRIPTION:Parse a restriction condition expression or parenthetical
//subexpression.
//
// INPUT:
//
//[lpObject]-- Ptr to an existing MAPI buffer allocated by
//   MAPIAllocateBuffer(). NULL is not permitted!
//
// INPUT/OUTPUT:
//
//[ppch]-- Ptr to ptr to string to be parsed on input;
//   ptr to ptr to first unparsed CHAR on output.
//
//[lpRes]-- Ptr to uninitialized SRestriction structure on input;
//   It will be appropriately initialized and may include
//   other allocated SRestrictions on output.
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//E_OUTOFMEMORYif out of memory;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrParseCondition(// RETURNS: HRESULT
INLPVOIDlpObject,// ptr to existing MAPI buffer
IN OUTCHAR **ppch,   // string
IN OUTLPSRestrictionlpRes   // restriction ptr
)
{
HRESULThr =NOERROR;
LPSRestrictionlpNxtRes =lpRes;
LPSPropValuelpProp =   NULL;
CHAR *pch =NULL;
TOKENINFOti ={0};

    DEBUGPRIVATE("HrParseCondition()\n");

    pch = *ppch;

while (TRUE)
{
ti.pch = pch;

hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;// Now pch points to next token.

switch (ti.tt)
{
    case LeftParen:
    {
    hr = HrParseCondition(lpObject, &pch, lpNxtRes);

    if (FAILED(hr))
    goto cleanup;

    ti.pch = pch;

    hr = HrGetExpressionToken(&ti);

    if (FAILED(hr))
    goto cleanup;

    pch = ti.pch + ti.cch;

    if (ti.tt != RightParen)
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
                }

    break;
    }

    case NotOp:
    {
    lpNxtRes->rt = RES_NOT;

    lpNxtRes->res.resNot.ulReserved =0;
    lpNxtRes->res.resNot.lpRes =NULL;

    hr = MAPIAllocateMore(sizeof(SRestriction),
  lpObject,
  (LPVOID FAR *)
  &lpNxtRes->res.resNot.lpRes);

    if (FAILED(hr))
    goto cleanup;

    lpNxtRes->res.resNot.lpRes->rt = RES_UNINITIALIZED;

    lpNxtRes = lpNxtRes->res.resNot.lpRes;

    continue;
    }

    case BooleanTag:
    {
    TOKENINFOtiBooleanTag =ti;
    TOKENINFOtiBooleanOp;
    TOKENINFOtiBooleanLiteral;

    tiBooleanOp.pch = pch;

    hr = HrGetExpressionToken(&tiBooleanOp);

    if (FAILED(hr))
    goto cleanup;

    pch = tiBooleanOp.pch + tiBooleanOp.cch;

    tiBooleanLiteral.pch = pch;

    hr = HrGetExpressionToken(&tiBooleanLiteral);

    if (FAILED(hr))
    goto cleanup;

    pch = tiBooleanLiteral.pch + tiBooleanLiteral.cch;

// A boolean literal is essentially a numeric literal with
// a restricted range, so...

    if (tiBooleanLiteral.tt != NumericLiteral||
tiBooleanLiteral.dwValue > 1)
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

if (tiBooleanOp.tt != EqualOp)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}

    lpNxtRes->rt = RES_PROPERTY;

    lpNxtRes->res.resProperty.relop =RELOP_EQ;
    lpNxtRes->res.resProperty.ulPropTag =tiBooleanTag.dwValue;
    lpNxtRes->res.resProperty.lpProp =NULL;

    hr = MAPIAllocateMore(sizeof(SPropValue),
  lpObject,
 (LPVOID FAR *)&lpProp);

    if (FAILED(hr))
    goto cleanup;

    lpProp->ulPropTag =tiBooleanTag.dwValue;
    lpProp->Value.b =(USHORT)tiBooleanLiteral.dwValue;

    lpNxtRes->res.resProperty.lpProp = lpProp;

    break;
    }

    case NumericTag:
    {
    ULONGrelop;
    TOKENINFOtiNumericTag =ti;
    TOKENINFOtiNumericOp;
    TOKENINFOtiNumericLiteral;

    tiNumericOp.pch = pch;

    hr = HrGetExpressionToken(&tiNumericOp);

    if (FAILED(hr))
    goto cleanup;

    pch = tiNumericOp.pch + tiNumericOp.cch;

    tiNumericLiteral.pch = pch;

    hr = HrGetExpressionToken(&tiNumericLiteral);

    if (FAILED(hr))
    goto cleanup;

    pch = tiNumericLiteral.pch + tiNumericLiteral.cch;

    if (tiNumericLiteral.tt != NumericLiteral)
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

    switch (tiNumericOp.tt)
    {
        case NotEqualOp:
        relop = RELOP_NE;
        break;

        case EqualOp:
        relop = RELOP_EQ;
        break;

        case GreaterThanOp:
        relop = RELOP_GT;
        break;

        case LessThanOp:
        relop = RELOP_LT;
        break;

        default:
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;

    }

    lpNxtRes->rt = RES_PROPERTY;

    lpNxtRes->res.resProperty.relop =relop;
    lpNxtRes->res.resProperty.ulPropTag =tiNumericTag.dwValue;
    lpNxtRes->res.resProperty.lpProp =NULL;

    hr = MAPIAllocateMore(sizeof(SPropValue),
  lpObject,
 (LPVOID FAR *)&lpProp);

    if (FAILED(hr))
    goto cleanup;

    lpProp->ulPropTag = tiNumericTag.dwValue;
    lpProp->Value.l =tiNumericLiteral.dwValue;

    lpNxtRes->res.resProperty.lpProp = lpProp;

    break;
    }

    case StringTag:
    {
    ULONGrelop;
    ULONGrt;
    TOKENINFOtiStringTag =ti;
    TOKENINFOtiStringOp;
    TOKENINFOtiStringLiteral;

    tiStringOp.pch = pch;

    hr = HrGetExpressionToken(&tiStringOp);

    if (FAILED(hr))
    goto cleanup;

    pch = tiStringOp.pch + tiStringOp.cch;

    tiStringLiteral.pch = pch;

    hr = HrGetExpressionToken(&tiStringLiteral);

    if (FAILED(hr))
    goto cleanup;

// Note that the string literal returned includes the
// opening and closing quotes (hence all the strange mods
// to pch and cch).

    pch = tiStringLiteral.pch + tiStringLiteral.cch;

    if (tiStringLiteral.tt != StringLiteral)
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

    switch (tiStringOp.tt)
    {
        case NotEqualOp:
        relop = RELOP_NE;
        rt =RES_PROPERTY;
        break;

        case EqualOp:
        relop = RELOP_EQ;
        rt =RES_PROPERTY;
        break;

        case ContainsOp:
        rt =RES_CONTENT;
        break;

        default:
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
    }

    lpNxtRes->rt = rt;

    if (rt == RES_PROPERTY)
    {
    lpNxtRes->res.resProperty.relop =  relop;
    lpNxtRes->res.resProperty.ulPropTag = tiStringTag.dwValue;
    lpNxtRes->res.resProperty.lpProp =  NULL;
    }
    else// rt == RES_CONTENT
    {
    lpNxtRes->res.resContent.ulFuzzyLevel =FL_SUBSTRING|
FL_IGNORECASE;

    lpNxtRes->res.resContent.ulPropTag = tiStringTag.dwValue;
    lpNxtRes->res.resContent.lpProp =    NULL;
    }

    hr = MAPIAllocateMore(sizeof(SPropValue),
  lpObject,
  (LPVOID FAR *)&lpProp);

    if (FAILED(hr))
    goto cleanup;

    if (rt == RES_PROPERTY)
    {
    lpNxtRes->res.resProperty.lpProp =lpProp;
    }
    else// rt == RES_CONTENT
    {
    lpNxtRes->res.resContent.lpProp =lpProp;
    }

    lpProp->ulPropTag =tiStringTag.dwValue;

if (PROP_TYPE(lpProp->ulPropTag) == PT_STRING8)
{
    lpProp->Value.lpszA =NULL;

hr = MAPIAllocateMore(tiStringLiteral.cch + 1 - 2, lpObject,
  (LPVOID FAR *)&lpProp->Value.lpszA);
if (FAILED(hr))
goto cleanup;

memcpy(lpProp->Value.lpszA,
   tiStringLiteral.pch + 1,
   tiStringLiteral.cch - 2);

*(lpProp->Value.lpszA + tiStringLiteral.cch - 2) = '\0';
}
else// it is UNICODE
{
INTcch = 0;

    lpProp->Value.lpszW =NULL;

if (tiStringLiteral.cch > 2)
{
cch = MultiByteToWideChar(CP_ACP,
  0,
  tiStringLiteral.pch + 1,
  tiStringLiteral.cch - 2,
  NULL,
  0);
if (cch == 0)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

hr = MAPIAllocateMore((cch + 1) * sizeof(WCHAR), lpObject,
  (LPVOID FAR *)&lpProp->Value.lpszW);

if (FAILED(hr))
goto cleanup;

if (tiStringLiteral.cch > 2)
{
if (!MultiByteToWideChar(CP_ACP,
 0,
 tiStringLiteral.pch + 1,
 tiStringLiteral.cch - 2,
 lpProp->Value.lpszW,
 cch))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

*(lpProp->Value.lpszW + cch) = 0;
}

    break;
    }

    case TimeTag:
    {
    ULONGrelop;
    TOKENINFOtiTimeTag =ti;
    TOKENINFOtiTimeOp;
    TOKENINFOtiTimeLiteral;

    tiTimeOp.pch = pch;

    hr = HrGetExpressionToken(&tiTimeOp);

    if (FAILED(hr))
    goto cleanup;

    pch = tiTimeOp.pch + tiTimeOp.cch;

    tiTimeLiteral.pch = pch;

    hr = HrGetExpressionToken(&tiTimeLiteral);

    if (FAILED(hr))
    goto cleanup;

    pch = tiTimeLiteral.pch + tiTimeLiteral.cch;

    if (tiTimeLiteral.tt != TimeLiteral)
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }

    switch (tiTimeOp.tt)
    {
        case NotEqualOp:
        relop = RELOP_NE;
        break;

        case EqualOp:
        relop = RELOP_EQ;
        break;

        case GreaterThanOp:
        relop = RELOP_GT;
        break;

        case LessThanOp:
        relop = RELOP_LT;
        break;

        default:
        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;

    }

    lpNxtRes->rt = RES_PROPERTY;

    lpNxtRes->res.resProperty.relop =relop;
    lpNxtRes->res.resProperty.ulPropTag =tiTimeTag.dwValue;
    lpNxtRes->res.resProperty.lpProp =NULL;

    hr = MAPIAllocateMore(sizeof(SPropValue),
  lpObject,
 (LPVOID FAR *)&lpProp);

    if (FAILED(hr))
    goto cleanup;

    lpProp->ulPropTag = tiTimeTag.dwValue;
    lpProp->Value.ft =tiTimeLiteral.ftValue;

    lpNxtRes->res.resProperty.lpProp = lpProp;

    break;
}

    default:
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }
}

ti.pch = pch;

hr = HrGetExpressionToken(&ti);

if (FAILED(hr))
goto cleanup;

pch = ti.pch + ti.cch;// Now pch points to next token.

switch (ti.tt)
{
    case AndOp:
    {
    LPSRestrictionlpResArray =NULL;

    hr = MAPIAllocateMore(sizeof(SRestriction) * 2,
  lpObject,
  (LPVOID FAR *)&lpResArray);

    if (FAILED(hr))
    goto cleanup;

    lpResArray[0] = *lpRes;

    lpResArray[1].rt = RES_UNINITIALIZED;

    lpRes->rt = RES_AND;

    lpRes->res.resAnd.cRes =2;
    lpRes->res.resAnd.lpRes =lpResArray;

    lpNxtRes = &lpResArray[1];

    break;
    }

    case OrOp:
    {
    LPSRestrictionlpResArray =NULL;

    hr = MAPIAllocateMore(sizeof(SRestriction) * 2,
  lpObject,
  (LPVOID FAR *)&lpResArray);

    if (FAILED(hr))
    goto cleanup;

    lpResArray[0] = *lpRes;

    lpResArray[1].rt = RES_UNINITIALIZED;

    lpRes->rt = RES_OR;

    lpRes->res.resOr.cRes =2;
    lpRes->res.resOr.lpRes =lpResArray;

    lpNxtRes = &lpResArray[1];

    break;
    }

    case EndOfString:
    case RightParen:
    {
    pch = ti.pch;// Push this token back and return to caller.

    if (lpRes->rt == RES_UNINITIALIZED)
    hr = HR_LOG(E_INVALIDARG);

        goto cleanup;// Done!
    }

    default:
    {
    hr = HR_LOG(E_INVALIDARG);
    goto cleanup;
    }
}
}

cleanup:

*ppch = pch;

RETURN(hr);
}

// $--HrPrintToString----------------------------------------------------------
//
// DESCRIPTION:Write the formatted string to the end of the supplied
//output string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// INPUT:
//
//[Format]-- A printf style format string.
//[...]-- Format arguments.
//
// RETURNS:     NOERRORif successful;
//E_OUTOFMEMORYif out of memory;
//              E_*otherwise.
//
// Notes:This routine appends the formatted string to the end of an
//existing string.  If the existing string is NULL, (ie.
//*lppszString == NULL) then this routine will do the initial
//allocation.  Note that *lppszString may be reallocated as
//necessary to grow the buffer, and the old copy will be released.
//The routine uses MAPIAllocateBuffer() and MAPIFreeBuffer().
//
//-----------------------------------------------------------------------------

#defineHRPRINTTOSTRING_BUFINCR1024

HRESULT
HrPrintToString(// RETURNS: HRESULT
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer,// ptr to count of bytes in buffer
INLPSTRFormat  // message format
...                 // variable arguments
)
{
va_listargs;
LONGcb =0;
HRESULThr =NOERROR;
LPSTRlpszStringCopy =NULL;

    DEBUGPRIVATE("HrPrintToString()\n");

if (*lppszString == NULL)
{
hr = MAPIAllocateBuffer(HRPRINTTOSTRING_BUFINCR,
(LPVOID FAR *)lppszString);
if (FAILED(hr))
goto cleanup;

*lpcbBuffer =HRPRINTTOSTRING_BUFINCR;
*lpcbString =1;
*lppszString[0] ='\0';
}

va_start(args, Format);

while (TRUE)
{
cb = _vsnprintf(*lppszString + *lpcbString - 1,
*lpcbBuffer - *lpcbString + 1,
Format,
args);

if (cb >= 0)
{
*lpcbString += cb;
break;
}

hr = MAPIAllocateBuffer(*lpcbBuffer + HRPRINTTOSTRING_BUFINCR,
(LPVOID FAR *)&lpszStringCopy);
if (FAILED(hr))
goto cleanup;

*lpcbBuffer += HRPRINTTOSTRING_BUFINCR;

memcpy(lpszStringCopy, *lppszString, *lpcbString);

MAPIFREEBUFFER(*lppszString);

*lppszString = lpszStringCopy;
}

cleanup:

va_end(args);

RETURN(hr);
}

// $--HrPrivateRestrictionToString---------------------------------------------
//
// DESCRIPTION:Get a string representation of an SRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to restriction to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrPrivateRestrictionToString(
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr =NOERROR;

    DEBUGPRIVATE("HrPrivateRestrictionToString()\n");

if (lpRes == NULL)
{
hr = HrPrintToString(lppszString, lpcbString, lpcbBuffer, "<NULL>");

    if (FAILED(hr))
        goto cleanup;
}
else
{
switch (lpRes->rt)
{
    case RES_AND:
    hr = HrAndRestrictionToString(lpRes,
  lppszString,
  lpcbString,
  lpcbBuffer);
    break;

    case RES_OR:
    hr = HrOrRestrictionToString(lpRes,
  lppszString,
  lpcbString,
  lpcbBuffer);
    break;

    case RES_NOT:
    hr = HrNotRestrictionToString(lpRes,
    lppszString,
    lpcbString,
    lpcbBuffer);
    break;

    case RES_CONTENT:
    hr = HrContentRestrictionToString(lpRes,
    lppszString,
    lpcbString,
    lpcbBuffer);
    break;

    case RES_PROPERTY:
    hr = HrPropertyRestrictionToString(lpRes,
      lppszString,
      lpcbString,
      lpcbBuffer);
    break;

    case RES_COMPAREPROPS:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SComparePropsRestriction>");
    break;

    case RES_BITMASK:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SBitMaskRestriction>");
    break;

    case RES_SIZE:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SSizeRestriction>");
    break;

    case RES_EXIST:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SExistRestriction>");
    break;

    case RES_SUBRESTRICTION:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SSubRestriction>");
    break;

    case RES_COMMENT:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<SCommentRestriction>");
    break;

    case RES_UNINITIALIZED:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Uninitialized SRestriction>");
    break;

    default:
    hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Invalid SRestriction>");
    break;
}

    if (FAILED(hr))
        goto cleanup;
}

cleanup:

RETURN(hr);
}

// $-HrPropertyRestrictionToString---------------------------------------------
//
// DESCRIPTION:Get a string representation of an SPropertyRestriction.
//
// INPUT:
//
//[lpRes]-- Ptr to property restriction to be represented as a
//   string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrPropertyRestrictionToString(// RETURNS: HRESULT
INLPSRestrictionlpRes,// restriction ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr = NOERROR;
LPSTRlpszPropName =NULL;
LPSTRlpszRelop   =   NULL;

    DEBUGPRIVATE("HrPropertyRestrictionToString()\n");

switch (lpRes->res.resProperty.relop)
{
    case RELOP_LT:
    lpszRelop = " < ";
    break;

    case RELOP_LE:
    lpszRelop = " <= ";
    break;

    case RELOP_GT:
    lpszRelop = " > ";
    break;

    case RELOP_GE:
    lpszRelop = " >= ";
    break;

    case RELOP_EQ:
    lpszRelop = " = ";
    break;

    case RELOP_NE:
    lpszRelop = " # ";
    break;

    case RELOP_RE:
    lpszRelop = " <RELOP_RE Operator> ";
    break;

    default:
    lpszRelop = " <Invalid Operator> ";
    break;
}

hr = HrGetPropTagName(lpRes->res.resProperty.ulPropTag,
   &lpszPropName);
if (FAILED(hr))
goto cleanup;

hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%s%s",
 lpszPropName,
 lpszRelop);
if (FAILED(hr))
goto cleanup;

hr = HrPropValueToString(lpRes->res.resProperty.lpProp,
 lppszString,
 lpcbString,
 lpcbBuffer);
if (FAILED(hr))
goto cleanup;

cleanup:

MAPIFREEBUFFER(lpszPropName);

RETURN(hr);
}

// $--HrPropValueToString------------------------------------------------------
//
// DESCRIPTION:Get a string representation of an SPropValue..
//
// INPUT:
//
//[lpProp]-- Ptr to property value to be represented as a string.
//
// INPUT/OUTPUT:
//
//[lppszString]-- Output string ptr ptr.  If *lppszString == NULL, then
//   a string will be allocated by this routine.
//[lpcbString]-- Ptr to count of bytes in input string on input (including
//   terminating '\0'); count of bytes in string after
//   appending formatted string on output.
//[lpcbBuffer]-- Ptr to count of bytes in input string buffer on input;
//   count of bytes in string buffer after appending formatted
//   string on output (buffer size may increase if allocation
//   was necessary).
//
// RETURNS:     NOERRORif successful;
//              E_INVALIDARGif bad input;
//              E_FAILotherwise.
//-----------------------------------------------------------------------------

HRESULT
HrPropValueToString(          // RETURNS: VOID
INLPSPropValuelpProp,// property value ptr
IN OUTLPSTR FAR *lppszString,// output string ptr ptr
IN OUTLPULONGlpcbString,// ptr to count of bytes in string
IN OUTLPULONGlpcbBuffer// ptr to count of bytes in buffer
)
{
HRESULThr =NOERROR;

    DEBUGPRIVATE("HrPropValueToString()\n");

if (lpProp == NULL)
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Undefined SPropValue>");
    if (FAILED(hr))
        goto cleanup;
}
else
{
ULONGulPropTag =0;

ulPropTag = lpProp->ulPropTag;

switch (PROP_TYPE(ulPropTag))
{
case PT_I2:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%#hx",
 lpProp->Value.i);
break;
}

case PT_LONG:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%#x",
 lpProp->Value.ul);
break;
}

case PT_R4:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_R4 value dump not supported>");
break;
}

case PT_DOUBLE:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_DOUBLE value dump not supported>");
break;
}

case PT_BOOLEAN:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 lpProp->Value.b ? "TRUE" : "FALSE");
break;
}

case PT_CURRENCY:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_CURRENCY value dump not supported>");
break;
}

case PT_APPTIME:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_APPTIME value dump not supported>");
break;
}

case PT_SYSTIME:
{
SYSTEMTIMEst ={0};
FILETIMEftLocal ={0};

// Convert UTC FILETIME to Local FILETIME.

if (!FileTimeToLocalFileTime(&lpProp->Value.ft, &ftLocal))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Convert Local FILETIME to SYSTEMTIME.

if (!FileTimeToSystemTime(&ftLocal, &st))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%02hu:%02hu:%02hu %04hu-%02hu-%02hu",
 st.wHour,
 st.wMinute,
 st.wSecond,
 st.wYear,
 st.wMonth,
 st.wDay);
break;
}

case PT_STRING8:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "\"%s\"",
 lpProp->Value.lpszA);
break;
}

case PT_BINARY:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_BINARY value dump not supported>");
break;
}

case PT_UNICODE:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "\"%S\"",
 lpProp->Value.lpszW);
break;
}

case PT_CLSID:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_CLSID value dump not supported>");
break;
}

case PT_I8:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_I8 value dump not supported>");
break;
}

case PT_MV_I2:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_I2 value dump not supported>");
break;
}

case PT_MV_LONG:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_LONG value dump not supported>");
break;
}

case PT_MV_R4:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_R4 value dump not supported>");
break;
}

case PT_MV_DOUBLE:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_DOUBLE value dump not supported>");
break;
}

case PT_MV_CURRENCY:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_CURRENCY value dump not supported>");
break;
}

case PT_MV_APPTIME:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_APPTIME value dump not supported>");
break;
}

case PT_MV_SYSTIME:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_SYSTIME value dump not supported>");
break;
}

case PT_MV_BINARY:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_BINARY value dump not supported>");
break;
}

case PT_MV_STRING8:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_STRING8 value dump not supported>");
break;
}

case PT_MV_UNICODE:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_UNICODE value dump not supported>");
break;
}

case PT_MV_CLSID:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_CLSID value dump not supported>");
break;
}

case PT_MV_I8:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_MV_I8 value dump not supported>");
break;
}

case PT_ERROR:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "%x",
 lpProp->Value.err);
break;
}

case PT_NULL:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_NULL value dump not supported>");
break;
}

case PT_OBJECT:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<PT_OBJECT value dump not supported>");
break;
}

default:
{
hr = HrPrintToString(lppszString,
 lpcbString,
 lpcbBuffer,
 "<Undefined property type value!>");
}
}

    if (FAILED(hr))
        goto cleanup;
}

cleanup:

RETURN(hr);
}

// $--lpszSkipNonWhiteSpace----------------------------------------------------
//
// DESCRIPTION:    Find first whitespace CHAR or '\0' in *pch.
//
// INPUT:
//
//[pch]-- Ptr to string to be examined for whitespace CHAR.
//
// RETURNS:     Ptr to first whitespace CHAR or '\0' in *pch.
//
//-----------------------------------------------------------------------------

inline
CHAR *
lpszSkipNonWhiteSpace(// RETURNS CHAR *
INCHAR *pch     // input string
)
{
while (!isspace(*pch) && *pch != '\0')
pch++;

return pch;
}

// $--lpszSkipWhiteSpace-------------------------------------------------------
//
// DESCRIPTION:    Find first nonwhitespace CHAR in *pch.
//
// INPUT:
//
//[pch]-- Ptr to string to be examined for nonwhitespace CHAR.
//
// RETURNS:     Ptr to first nonwhitespace CHAR in *pch.
//
//-----------------------------------------------------------------------------

inline
CHAR *
lpszSkipWhiteSpace(// RETURNS CHAR *
INCHAR *pch     // input string
)
{
while (isspace(*pch))
pch++;

return pch;
}

// NOTE NOTE NOTE - Code below here does not yet conform to coding standards
//because we do not yet know if it will be left in the
//product.  It will be appropriately munged when and if
//necessary.

HRESULT
HrCountProps(
INint cprop,
INLPSPropValuergprop,
OUTULONG FAR *pcb
)
{
LPSPropValuepprop;
ULONGcb = 0;
ULONGcbMV;
intiValue;

if ((rgprop && !cprop) || IsBadReadPtr(rgprop, cprop*sizeof(SPropValue)))
return E_INVALIDARG;

for (pprop = rgprop; cprop--; ++pprop)
{
ULONGulType = PROP_TYPE(pprop->ulPropTag);

//Check for valid PROP_TYPE and count memory consumed.

cb += sizeof(SPropValue);

switch (PROP_TYPE(pprop->ulPropTag))
{
case PT_UNSPECIFIED:
case PT_OBJECT:
default:
{
return E_INVALIDARG;
}

case PT_I2:
case PT_LONG:
case PT_R4:
case PT_APPTIME:
case PT_DOUBLE:
case PT_BOOLEAN:
case PT_CURRENCY:
case PT_SYSTIME:
case PT_CLSID:
case PT_I8:
case PT_ERROR:
case PT_NULL:
{
break;
}

case PT_BINARY:
{
cb += pprop->Value.bin.cb;

if (pprop->Value.bin.cb&&
IsBadReadPtr(pprop->Value.bin.lpb,
 (UINT)(pprop->Value.bin.cb)))
{
goto badProp;
}

break;
}

case PT_STRING8:
{
if (IsBadStringPtrA(pprop->Value.lpszA, INFINITE))
goto badProp;

cb += (lstrlenA( pprop->Value.lpszA ) + 1) * sizeof(CHAR);

break;
}

case PT_UNICODE:
{
if (IsBadStringPtrW(pprop->Value.lpszW, INFINITE))
goto badProp;

cb += (lstrlenW(pprop->Value.lpszW) + 1) * sizeof(WCHAR);

break;
}

            //Note!MVxxx.cValues may NOT be zero (DCR 2789).

case PT_MV_I2:
{
if (!(cbMV = pprop->Value.MVi.cValues * sizeof(short int)) ||
IsBadReadPtr(pprop->Value.MVi.lpi, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_LONG:
{
if (!(cbMV = pprop->Value.MVl.cValues * sizeof(LONG)) ||
IsBadReadPtr(pprop->Value.MVl.lpl, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_R4:
{
if (!(cbMV = pprop->Value.MVflt.cValues * sizeof(float)) ||
IsBadReadPtr(pprop->Value.MVflt.lpflt, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_APPTIME:
{
if (!(cbMV = pprop->Value.MVat.cValues * sizeof(double)) ||
IsBadReadPtr(pprop->Value.MVat.lpat, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_DOUBLE:
{
if (!(cbMV = pprop->Value.MVdbl.cValues * sizeof(double)) ||
IsBadReadPtr(pprop->Value.MVdbl.lpdbl, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_CURRENCY:
{
if (!(cbMV = pprop->Value.MVcur.cValues * sizeof(CURRENCY)) ||
IsBadReadPtr(pprop->Value.MVcur.lpcur, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_SYSTIME:
{
if (!(cbMV = pprop->Value.MVft.cValues * sizeof(FILETIME)) ||
IsBadReadPtr(pprop->Value.MVft.lpft, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_CLSID:
{
if (!(cbMV = pprop->Value.MVguid.cValues * sizeof(GUID)) ||
IsBadReadPtr(pprop->Value.MVguid.lpguid, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_I8:
{
if (!(cbMV =
  pprop->Value.MVli.cValues * sizeof(LARGE_INTEGER)) ||
IsBadReadPtr(pprop->Value.MVli.lpli, (UINT)cbMV))
{
goto badProp;
}

cb += cbMV;

break;
}

case PT_MV_BINARY:
{
if (!(cbMV = pprop->Value.MVbin.cValues * sizeof(SBinary)) ||
IsBadReadPtr(pprop->Value.MVbin.lpbin, (UINT)cbMV))
{
goto badProp;
}

for (iValue = 0;
 (ULONG)iValue < pprop->Value.MVbin.cValues;
 iValue++)
{
if (IsBadReadPtr(pprop->Value.MVbin.lpbin[iValue].lpb,
 (UINT)pprop->Value.MVbin.lpbin[iValue].cb))
{
goto badProp;
}

cb += pprop->Value.MVbin.lpbin[iValue].cb;
}

cb += cbMV;

break;
}

case PT_MV_STRING8:
{
if (!(cbMV = pprop->Value.MVszA.cValues * sizeof(LPVOID)) ||
IsBadReadPtr(pprop->Value.MVszA.lppszA, (UINT)cbMV))
{
goto badProp;
}

for (iValue = 0;
   (ULONG)iValue < pprop->Value.MVszA.cValues;
 iValue++)
{
if (IsBadStringPtrA(pprop->Value.MVszA.lppszA[iValue],
INFINITE))
{
goto badProp;
}

cb += lstrlenA(pprop->Value.MVszA.lppszA[iValue]) + 1;
}

cb += cbMV;

break;
}

case PT_MV_UNICODE:
{
if (!(cbMV = pprop->Value.MVszW.cValues * sizeof(LPVOID)) ||
IsBadReadPtr(pprop->Value.MVszW.lppszW, (UINT)cbMV))
{
goto badProp;
}

for (iValue = 0;
   (ULONG)iValue < pprop->Value.MVszW.cValues;
 iValue++)
{
if (IsBadStringPtrW(pprop->Value.MVszW.lppszW[iValue],
INFINITE))
{
goto badProp;
}

cb +=(lstrlenW(pprop->Value.MVszW.lppszW[iValue]) + 1) *
sizeof(WCHAR);
}

cb += cbMV;

break;
}
}
}

if (pcb)
*pcb = cb;

return NOERROR;

badProp:

return E_INVALIDARG;
}

HRESULT
HrCopyProps(
INintcprop,
INLPSPropValuergprop,
OUTLPVOIDpvDst,
OUTULONG FAR * pcb
)
{
LPSPropValuepprop;
LPSPropValueppropDst;
ULONGcb;
LPBYTEpb;
UINTcbT;
intiValue;

cb = cprop * sizeof(SPropValue);

memcpy(pvDst, rgprop, (UINT)cb);

pb = (LPBYTE)pvDst + cb;

for (pprop = rgprop, ppropDst = (LPSPropValue)pvDst;
 cprop--;
 ++pprop, ++ppropDst)
{
//Tricky: common code after the switch increments pb and cb
//by the amount copied. If no increment is necessary, the case
//uses 'continue' rather than 'break' to exit the switch, thus
//skipping the increment -- AND any other code which may be
//added after the switch.

switch (PROP_TYPE(pprop->ulPropTag))
{
default:
return E_INVALIDARG;

case PT_I2:
case PT_LONG:
case PT_R4:
case PT_APPTIME:
case PT_DOUBLE:
case PT_BOOLEAN:
case PT_CURRENCY:
case PT_SYSTIME:
case PT_CLSID:
case PT_I8:
case PT_ERROR:
case PT_NULL:
continue;//nothing to add

case PT_BINARY:
ppropDst->Value.bin.lpb = pb;
cbT = (UINT)pprop->Value.bin.cb;
memcpy(pb, pprop->Value.bin.lpb, cbT);
break;

case PT_STRING8:
ppropDst->Value.lpszA = (LPSTR)pb;
cbT = lstrlenA( pprop->Value.lpszA ) + 1;
memcpy(pb, pprop->Value.lpszA, cbT);
break;

case PT_UNICODE:
ppropDst->Value.lpszW = (LPWSTR)pb;
cbT = (lstrlenW( pprop->Value.lpszW ) + 1) * sizeof(WCHAR);
memcpy(pb, pprop->Value.lpszW, cbT);
break;

case PT_MV_I2:
ppropDst->Value.MVi.lpi = (short int FAR *)pb;
cbT = (UINT)pprop->Value.MVi.cValues * sizeof(short int);
memcpy(pb, pprop->Value.MVi.lpi, cbT);
break;

case PT_MV_LONG:
ppropDst->Value.MVl.lpl = (LONG FAR *)pb;
cbT = (UINT)pprop->Value.MVl.cValues * sizeof(LONG);
memcpy(pb, pprop->Value.MVl.lpl, cbT);
break;

case PT_MV_R4:
ppropDst->Value.MVflt.lpflt = (float FAR *)pb;
cbT = (UINT)pprop->Value.MVflt.cValues * sizeof(float);
memcpy(pb, pprop->Value.MVflt.lpflt, cbT);
break;

case PT_MV_APPTIME:
ppropDst->Value.MVat.lpat = (double FAR *)pb;
cbT = (UINT)pprop->Value.MVat.cValues * sizeof(double);
memcpy(pb, pprop->Value.MVat.lpat, cbT);
break;

case PT_MV_DOUBLE:
ppropDst->Value.MVdbl.lpdbl = (double FAR *)pb;
cbT = (UINT)pprop->Value.MVdbl.cValues * sizeof(double);
memcpy(pb, pprop->Value.MVdbl.lpdbl, cbT);
break;

case PT_MV_CURRENCY:
ppropDst->Value.MVcur.lpcur = (CURRENCY FAR *)pb;
cbT = (UINT)pprop->Value.MVcur.cValues * sizeof(CURRENCY);
memcpy(pb, pprop->Value.MVcur.lpcur, cbT);
break;

case PT_MV_SYSTIME:
ppropDst->Value.MVft.lpft = (FILETIME FAR *)pb;
cbT = (UINT)pprop->Value.MVft.cValues * sizeof(FILETIME);
memcpy(pb, pprop->Value.MVft.lpft, cbT);
break;

case PT_MV_CLSID:
ppropDst->Value.MVguid.lpguid = (GUID FAR *)pb;
cbT = (UINT)pprop->Value.MVguid.cValues * sizeof(GUID);
memcpy(pb, pprop->Value.MVguid.lpguid, cbT);
break;

case PT_MV_I8:
ppropDst->Value.MVli.lpli = (LARGE_INTEGER FAR *)pb;
cbT = (UINT)pprop->Value.MVli.cValues * sizeof(LARGE_INTEGER);
memcpy(pb, pprop->Value.MVli.lpli, cbT);
break;

case PT_MV_BINARY:
ppropDst->Value.MVbin.lpbin = (SBinary *) pb;
pb += pprop->Value.MVbin.cValues * sizeof(SBinary);
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVbin.cValues;
  iValue++)
{
ppropDst->Value.MVbin.lpbin[iValue].lpb = pb;
cbT = (UINT)pprop->Value.MVbin.lpbin[iValue].cb;
memcpy(pb, pprop->Value.MVbin.lpbin[iValue].lpb, cbT);
cb += cbT;
pb += cbT;
}
continue;//already updated, don't do it again

case PT_MV_STRING8:
ppropDst->Value.MVszA.lppszA = (LPSTR *) pb;
pb += pprop->Value.MVszA.cValues * sizeof(LPSTR);
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVszA.cValues;
  iValue++)
{
ppropDst->Value.MVszA.lppszA[iValue] = (LPSTR)pb;
cbT = lstrlenA(pprop->Value.MVszA.lppszA[iValue]) + 1;
memcpy(pb, pprop->Value.MVszA.lppszA[iValue], cbT);
pb += cbT;
cb += cbT;
}
continue;//already updated, don't do it again

case PT_MV_UNICODE:
ppropDst->Value.MVszW.lppszW = (LPWSTR *) pb;
pb += pprop->Value.MVszW.cValues * sizeof(LPWSTR);
for (iValue = 0;
  (ULONG)iValue < pprop->Value.MVszW.cValues;
  iValue++)
{
ppropDst->Value.MVszW.lppszW[iValue] = (LPWSTR)pb;
cbT = (lstrlenW(pprop->Value.MVszW.lppszW[iValue]) + 1)
* sizeof(WCHAR);
memcpy(pb, pprop->Value.MVszW.lppszW[iValue], cbT);
pb += cbT;
cb += cbT;
}
continue;//already updated, don't do it again
}

//Advance ptr and total count by the amount copied
pb += cbT;
cb += cbT;
}

if (pcb)
*pcb = cb;

return NOERROR;
}

/*
 *Wrapper function to just duplicate a property value array
 *into a single block of MAPI memory.
 */

HRESULT
HrDupPropset(
INint cprop,
INLPSPropValuergprop,
INLPVOIDlpObject,
OUTLPSPropValue FAR *prgprop
)
{
ULONGcb;
HRESULThr;

//Find out how much memory we need

if (hr = HrCountProps(cprop, rgprop, &cb))
goto ret;

//Obtain memory

if (lpObject != NULL)
hr = MAPIAllocateMore(cb, lpObject, (LPVOID FAR *)prgprop);
else
hr = MAPIAllocateBuffer(cb, (LPVOID FAR *)prgprop);

if (hr)
goto ret;

//Copy the properties

if (hr = HrCopyProps(cprop, rgprop, *prgprop, &cb))
goto ret;

ret:
return hr;
}


RULECLSF.CPP

// --ruleclsf.cpp-------------------------------------------------------------
//
//  Implementation file for the CFolderRules control class and its
//class factory.
//
//  The CFolderRules class is a controlling class.  It controls the
//  CIExchangeFolderRules class (which implements the IExchangeFolderRules
//  programatic interface defined in rulecls.h).  When we add automation
//  capabilities, it will also control the standard OLE dispatch interface
//  for this class.
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"
#include "ruleclsf.h"
#include "ruleclsf.chk"

// $--CFolderRules::CFolderRules-----------------------------------------------
//
// DESCRIPTION:CFolderRules controlling class class constructor.
//
// INPUT:None.
//
// RETURNS:     Ptr to new CFolderRules instance.
//
//-----------------------------------------------------------------------------

CFolderRules::CFolderRules()
{
    DEBUGPRIVATE("CFolderRules::CFolderRules().\n");

    m_hr =NOERROR;    // no error so far
    m_refs =1;
m_lpMapiTbl =NULL;
m_lpExchTbl =NULL;
m_lpFolder =NULL;

    m_prog_interface = new CIExchangeFolderRules;

    if ( m_prog_interface == NULL )
    {
        m_hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Set programming interface ptr to this object.

    m_prog_interface->m_pFR = this;

cleanup:

    ;   // no operation
}

// $--CFolderRules::~CFolderRules----------------------------------------------
//
// DESCRIPTION:CFolderRules controlling class class destructor.
//
// INPUT:None.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

CFolderRules::~CFolderRules()
{
    DEBUGPRIVATE("CFolderRules::~CFolderRules().\n");

delete m_prog_interface;

ULRELEASE(m_lpMapiTbl);
ULRELEASE(m_lpExchTbl);
ULRELEASE(m_lpFolder);
}

// $--CFolderRules::Create-----------------------------------------------------
//
// DESCRIPTION:Create a new instance of the CFolderRules object.
//Also take care of setting up the standard OLE dispatch
//object pointer for the new instance.
//
// INPUT:None.
//
// RETURNS:     Ptr to new CFolderRules object on success; NULL otherwise.
//
//-----------------------------------------------------------------------------

CFolderRules FAR *
CFolderRules::Create()// RETURNS CFolderRules pointer
{
    CFolderRules FAR *pFolderRules =NULL;

    DEBUGPRIVATE("CFolderRules::Create().\n");

    // Create an instance of CFolderRules.

    pFolderRules = new FAR CFolderRules();

    if (pFolderRules == NULL)
    {
        HR_LOG(E_FAIL);
        pFolderRules = NULL;
        goto cleanup;
    }

    if (FAILED(pFolderRules->m_hr))
    {
        HR_LOG(pFolderRules->m_hr);
        ULRELEASE(pFolderRules);
        goto cleanup;
    }

cleanup:

    return pFolderRules;
}

// $--CFolderRules::HrGetProviders---------------------------------------------
//
// DESCRIPTION:Get an array of rules provider names.
//
// OUTPUT:
//
//  [lpcProviders]-- Pointer to ulong that will be set to count of
//   providers on successful return.
//[lpppszProviders]-- Pointer to array of string pointers that will be set
//   to point at an array of provider name string pointers
//   on successful return.
//
// RETURNS:     NOERRORif successful;
//E_OUTOFMEMORYif not enough memory;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

HRESULT
CFolderRules::HrGetProviders(// RETURNS: HRESULT
OUTLPULONGlpcProviders,// count of providers
OUTLPSTR FAR * FAR *lpppszProviders// ptr to array of providers
)
{
HRESULThr =NOERROR;

    DEBUGPRIVATE("CFolderRules::HrGetProviders()\n");

if (m_lpFolder == NULL)
hr = E_FAIL;
else
hr = m_SRowLst.HrGetProviders(lpcProviders, lpppszProviders);

RETURN(hr);
}

// $--CFolderRules::HrOpen-----------------------------------------------------
//
// DESCRIPTION:Open the object on a rules folder.
//
// INPUT:
//
//  [lpMDB]-- Ptr to MDB object containing the rules folder.
//  [cbentryid]-- Number of bytes in folder's entry identifier.
//  [lpentryid]-- Folder's entry identifier.
//  [lpszProvider]-- Provider for rules.  Multiple providers may have
//   rules on a folder. The IExchangeFolderRules interface
//   provides access to the rules associated with a
//   single specified provider.  May be NULL, in which case
//   a provider list can be obtained, but no other operations
//   are possible.
//
// RETURNS:     NOERROR on success;
//              E_INVALIDARG if bad input;
//E_NOINTERFACE if the rules table does not exist on the folder;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

HRESULT
CFolderRules::HrOpen(// RETURNS: HRESULT
INLPMDBlpMDB,// MDB store pointer
INULONGcbentryid,  // # bytes in entry ID
INLPENTRYIDlpentryid,// entry ID pointer
INLPSTRlpszProvider// provider name
)
{
HRESULThr =NOERROR;
LPSRowSetlpRows =NULL;
ULONGulObjType =0;

// NOTE: The following order of properties is assumed by lots
//       of other code (CFolderRules, CIExchangeFolderRules).

    SizedSPropTagArray(C_RULEPROPS, rgPropTag) =
    {
    C_RULEPROPS,
    {
    PR_RULE_SEQUENCE,
    PR_RULE_STATE,
    PR_RULE_CONDITION,
    PR_RULE_ACTIONS,
    PR_RULE_PROVIDER,
PR_RULE_LEVEL,
PR_RULE_NAME
    }
    };

    DEBUGPRIVATE("CFolderRules::HrOpen()\n");

    hr = CHK_CFolderRules_HrOpen(lpMDB, cbentryid, lpentryid, lpszProvider);

    if (FAILED(hr))
        RETURN(hr);

// Don't allow multiple opens on one object.

if (m_lpFolder != NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Open the folder.

hr = lpMDB->OpenEntry(cbentryid,
  lpentryid,
  NULL,
  MAPI_BEST_ACCESS|
  MAPI_DEFERRED_ERRORS,
  &ulObjType,
  (LPUNKNOWN FAR *)&m_lpFolder);
if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

if (ulObjType != MAPI_FOLDER)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    // Open the RULE table property on the folder.  The returned table
// pointer may be used to get a MAPI table for reading and it can
// also be used to modify the rules table.  If there is no rules table,
// this call will fail with E_NOINTERFACE.

    hr = m_lpFolder->OpenProperty(PR_RULES_TABLE,
  (LPGUID)&IID_IExchangeModifyTable,
  0,
  MAPI_DEFERRED_ERRORS,
  (LPUNKNOWN FAR *)&m_lpExchTbl);
    if (FAILED(hr))
        goto cleanup;

    // Open a MAPI table on the RULE table property.  This table can be
// read to determine what the rules table looks like.

    hr = m_lpExchTbl->GetTable(0, &m_lpMapiTbl);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Select columns needed.

hr = m_lpMapiTbl->SetColumns((LPSPropTagArray)&rgPropTag, 0);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Get all the rows in the table up to EDK_MAX_QUERY_ROWS.  If there
// are more rows than this, fail with MAPI_E_TABLE_TOO_BIG.

hr = m_lpMapiTbl->QueryRows(EDK_MAX_QUERY_ROWS, 0, &lpRows);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

// Check to be sure that table is not bigger than EDK_MAX_QUERY_ROWS.
// We do this by checking whether or not the cursor is positioned at
// the end of the table.

if (lpRows->cRows == EDK_MAX_QUERY_ROWS)
{
ULONGulRow =0;
ULONGulNumerator =0;
ULONGulDenominator =0;

hr = m_lpMapiTbl->QueryPosition(&ulRow, &ulNumerator, &ulDenominator);

if (FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
goto cleanup;
        }

// SeekRowApprox() doc states that if ulNumerator == ulDenominator, you
// are at the end of the table.

if (ulNumerator != ulDenominator)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

// Store the table in a sorted and more manageable format.

hr = m_SRowLst.HrInitialize(lpszProvider, lpRows);

    if (FAILED(hr))
        goto cleanup;

// NOTE - lpRows gets deallocated in SROWLST::Initialize().  We NULL it
//  out here as a safeguard against future use.

lpRows = NULL;

cleanup:

if (FAILED(hr))
{
ULRELEASE(m_lpMapiTbl);
ULRELEASE(m_lpExchTbl);
ULRELEASE(m_lpFolder);
}

    RETURN(hr);
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

// $--CFolderRules::QueryInterface---------------------------------------------
//
// DESCRIPTION:Return pointer to object which implements the desired
//interface, if this object supports the interface.
//
// INPUT:
//
//  [riid]-- Reference to interface identifier of desired interface.
//
//  [ppv]-- Ptr to object which supports interface.  NULL if none.
//
// RETURNS:     NOERROR if successful;
//E_INVALIDARG if bad input.
//E_NOINTERFACE if interface isn't supported.
//
//  Interfaces supported:
//
//      IUnknown
//      IDispatch   (eventually)
//      IExchangeFolderRules
//      DFolderRules (eventually)
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CFolderRules::QueryInterface(// RETURNS: HRESULT
INREFIIDriid,   // interface ID reference
OUTLPVOID FAR *ppv     // pointer to interface pointer
)
{
    HRESULT hr =NOERROR;

    DEBUGPRIVATE("CFolderRules::QueryInterface().\n");

    hr = CHK_CFolderRules_QueryInterface(riid, ppv);

    if (FAILED(hr))
        RETURN(hr);

    *ppv = NULL;

    // See if we support the requested interface.

    if (IsEqualIID(riid, IID_IUnknown))   // IUnknown interface is ourself
{
    *ppv = this;    // Return ourself
}
    else if (IsEqualIID(riid, IID_IExchangeFolderRules))
    {
        // User wants our programatic interface.

    *ppv = m_prog_interface;
    }
    else
    {
        // We don't support the requested interface.

      *ppv = NULL;
        hr = HR_LOG(E_NOINTERFACE);
        goto cleanup;
    }

    // If we reach this point, no error occurred.  Increment reference count.

    AddRef();

cleanup:

    RETURN(hr);
}

// $--CFolderRules::AddRef-----------------------------------------------------
//
// DESCRIPTION:Increment the reference count on this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CFolderRules::AddRef()// RETURNS: ULONG
{
    DEBUGPRIVATE("CFolderRules::AddRef().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs++;

    return m_refs;
}

// $--CFolderRules::Release----------------------------------------------------
//
// DESCRIPTION:Decrement the reference count on this object.  If the
//reference count reaches 0, destroy this object.
//
// INPUT:None.
//
// RETURNS:     New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CFolderRules::Release()// RETURNS: ULONG
{
    ULONG   ulRefCount =0;

    DEBUGPRIVATE("CFolderRules::Release().\n");

    ASSERTERROR(m_refs, "Bad m_refs.");

    m_refs--;

    if (!m_refs)
    {
        delete this;
        ulRefCount = 0;
    }
    else
    {
        ulRefCount = m_refs;
    }

    return ulRefCount;
}


RULECLSF.H

// --ruleclsf.h---------------------------------------------------------------
//
// Header file for the CFolderRules class and its class factory.
//
// The CFolderRules class is a controlling class.  It controls the
// CIExchangeFolderRules class (which implements the IExchangeFolderRules
// programatic interface defined in rulecls.h).  When we add automation
// capabilities, it will also control the standard OLE dispatch interface
// for this class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#if !defined(_RULECLSF_H)
#define _RULECLSF_H

// $$--CFolderRules-----------------------------------------------------------
//
// The CFolderRules class.
//
// ---------------------------------------------------------------------------

class FAR CFolderRules : public IUnknown
{
friend class CIExchangeFolderRules;

public:

    CFolderRules();

~CFolderRules();

// Standard OLE stuff.

STDMETHOD(QueryInterface)(
INREFIIDriid,
OUTVOID FAR * FAR *ppv
);

STDMETHOD_(ULONG, AddRef)(VOID);

STDMETHOD_(ULONG, Release)(VOID);

static
CFolderRules FAR *
Create();

HRESULT
HrGetProviders(
OUTLPULONGlpcProviders,
OUTLPSTR FAR * FAR *lpppszProviders
);

HRESULT
HrOpen(
INLPMDBlpMDB,
INULONGcbentryid,
INLPENTRYIDlpentryid,
INLPSTRlpszProvider
);

protected:

        HRESULT                 m_hr;       // constructor "return" code

LPEXCHANGEMODIFYTABLEm_lpExchTbl;

LPMAPIFOLDERm_lpFolder;// Ptr to MAPI folder object.

LPMAPITABLEm_lpMapiTbl;

CSROWLSTm_SRowLst;// Table info stored here.

ULONGm_refs;// Reference count.

// This is a pointer to the CIExchangeFolderRules class, which implements
// the IExchangeFolderRules interface exported to the user.

class FAR CIExchangeFolderRules FAR *m_prog_interface;
};

// $$--CIExchangeFolderRules---------------------------------------------------
//
// Class CIExchangeFolderRules.  This class implements the IExchangeFolderRules
// interface which is exposed to the use via the rulecls.h header file.
//
// ----------------------------------------------------------------------------

class FAR CIExchangeFolderRules : public IExchangeFolderRules
{
public:

// Standard OLE stuff.

STDMETHOD(QueryInterface)(
THIS_
REFIIDriid,
LPVOID FAR *ppvObj
);

STDMETHOD_(ULONG, AddRef)(
THIS
);

STDMETHOD_(ULONG, Release)(
THIS
);

// Methods unique to the IExchangeFolderRules interface.

STDMETHOD(HrDelete)(
THIS
);

STDMETHOD(HrDisable)(
THIS
);

STDMETHOD(HrEnable)(
THIS
);

STDMETHOD(HrGet)(
THIS_
LPLONGplState,
LPSRestriction FAR *ppRestriction,
LPACTIONS FAR *ppActions,
LPLONGplLevel,
LPSTR FAR *ppszName
);

STDMETHOD(HrInsert)(
THIS_
LONGlState,
LPSRestrictionpRestriction,
LPACTIONSpActions,
LONGlLevel,
LPSTRpszName
);

STDMETHOD(HrSeek)(
THIS_
LONGlPos
);

STDMETHOD(HrTell)(
THIS_
LPLONGplPos
);

    private:

        friend CFolderRules; // so can access m_pFR

// Pointer to the controlling CFolderRules object (shouldn't be
        // available to the user).

CFolderRules FAR *m_pFR;
};

#endif


RULECLSI.CPP

// --ruleclsi.cpp-------------------------------------------------------------
//
//  Implementation file for the CIExchangeFolderRules programmer interface
//class.
//
//  The CFolderRules class is the controlling class.  It controls the
//  CIExchangeFolderRules class (which implements the IExchangeFolderRules
//  programatic interface defined in rulecls.h and which this file
//  implements).
//
//  Copyright (C) Microsoft Corp. 1986-1996, All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"
#include "ruleclsf.h"
#include "ruleclsi.chk"

// IUnknown Methods --
// Let the controlling parent object handle all of these!

// $--CIExchangeFolderRules::QueryInterface------------------------------------
//
// DESCRIPTION:Return ptr to object which implements the desired
//interface, if this object supports the interface.
//
//Implemented via the parent object's (CFolderRules')
//QueryInterface.
//
// INPUT:
//
// [riid]-- Reference to interface identifier of desired interface.
//
// OUTPUT:
//
// [ppvObj]-- Pointer to object which supports interface.  NULL if none.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//E_NOINTERFACEif interface not supported.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::QueryInterface(// RETURNS: HRESULT
INREFIIDriid,       // interface ID reference
OUTLPVOID FAR *ppvObj      // ptr to interface ptr
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderRules::QueryInterface().\n");

    hr = CHK_CIExchangeFolderRules_QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR");

    // Let parent controlling object handle this.  (Let it do all
    // parameter checking also!)

    hr = m_pFR->QueryInterface(riid, ppvObj);

    RETURN(hr);
}

// $--CIExchangeFolderRules::AddRef--------------------------------------------
//
// DESCRIPTION:Increment this object's reference count.
//
//Implemented via the parent object's (CFolderRules') AddRef().
//
// INPUT:None.
//
// RETURNS:New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CIExchangeFolderRules::AddRef()// RETURNS: ULONG
{
    ULONGulRefCount =0;

    DEBUGPUBLIC("CIExchangeFolderRules::AddRef().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR");

    // Let parent controlling object handle this.

    ulRefCount = m_pFR->AddRef();

    return ulRefCount;
}

// $--CIExchangeFolderRules::Release-------------------------------------------
//
// DESCRIPTION:Decrement this object's reference count.
//
//Implemented via the parent object's (CFolderRules')
//Release().
//
// INPUT:None.
//
// RETURNS:New reference count.
//
//-----------------------------------------------------------------------------

STDMETHODIMP_(ULONG)
CIExchangeFolderRules::Release()    // RETURNS: ULONG
{
    ULONGulRefCount =0;

    DEBUGPUBLIC("CIExchangeFolderRules::Release().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR");

    // Let parent controlling object handle this.

    ulRefCount = m_pFR->Release();

    return ulRefCount;
}

//
// Methods unique to the IExchangeFolderRules interface.
//

// $--CIExchangeFolderRules::HrDelete------------------------------------------
//
// DESCRIPTION:Delete the current record.
//
// INPUT:None.
//
// RETURNS:NOERRORif successful;
//              E_FAILif cursor at RULE_PAST_END;
//              E_OUTOFMEMORYif insufficient memory.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrDelete(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
LPMAPITABLElpMapiTbl =NULL;
LPROWLISTlpRowList =NULL;
LPSRowSetlpRows =NULL;
CSROWNODE *pDeletedNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderRules::HrDelete().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *), "Bad m_pFR.");

hr = m_pFR->m_SRowLst.HrRemoveFromLst(&pDeletedNode);

if (FAILED(hr))
goto cleanup;

if (m_pFR->m_SRowLst.GetNodeCount() +
m_pFR->m_SRowLst.GetOtherProvNodeCount() > 0)
{
hr = m_pFR->m_SRowLst.HrWriteToTable(m_pFR->m_lpExchTbl);
}
else
{
// Workaround BUGFIX for bug 2435.  This code works around a problem
// in IExchangeModifyTable::ModifyTable() whereby a ROWLIST_REPLACE
// operation fails if the replacement table is empty.  We should be
// able to remove this code once this problem is fixed in
// IExchangeModifyTable.

SizedSPropTagArray(1, rgPropTag) =
{
1,
{
PR_RULE_ID
}
};

// Get the PR_RULE_ID for the one remaining rule in the table.

hr = m_pFR->m_lpExchTbl->GetTable(0, &lpMapiTbl);

if (FAILED(hr))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Select columns needed.

hr = lpMapiTbl->SetColumns((LPSPropTagArray)&rgPropTag, 0);

if (FAILED(hr))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

hr = lpMapiTbl->QueryRows(1, 0, &lpRows);

if (FAILED(hr) || lpRows->cRows != 1)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Delete the rule.

hr = MAPIAllocateBuffer(CbNewROWLIST(1), (LPVOID FAR *)&lpRowList);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

lpRowList->cEntries = 1;

lpRowList->aEntries[0].ulRowFlags =ROW_REMOVE;
lpRowList->aEntries[0].cValues =lpRows->aRow[0].cValues;
lpRowList->aEntries[0].rgPropVals =lpRows->aRow[0].lpProps;

// Remove the entry.

hr = m_pFR->m_lpExchTbl->ModifyTable(0, lpRowList);

if (FAILED(hr))
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}

cleanup:

if (FAILED(hr) && pDeletedNode != NULL)
{
HRESULThrTmp = m_pFR->m_SRowLst.HrInsert(&pDeletedNode->m_SRow);

if (FAILED(hrTmp))
hr = HR_LOG(E_UNEXPECTED);
else
pDeletedNode->m_SRow.lpProps = NULL;
}

delete pDeletedNode;

MAPIFREEBUFFER(lpRowList);
FREEPROWS(lpRows);
ULRELEASE(lpMapiTbl);

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrDisable-----------------------------------------
//
// DESCRIPTION:Disable the current rule.
//
// INPUT:None.
//
// RETURNS:NOERRORif successful;
//              E_FAILif cursor at RULE_PAST_END or corrupt data;
//              E_OUTOFMEMORYif insufficient memory.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrDisable(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
LONGlPrevState =0;
CSROWNODE *pNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderRules::HrDisable().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR.");

pNode = m_pFR->m_SRowLst.GetCurrentNode();

if (pNode == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (pNode->m_SRow.cValues < C_RULEPROPS ||
pNode->m_SRow.lpProps[I_RULE_STATE].ulPropTag != PR_RULE_STATE)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Save the previous state in case the table write fails.

lPrevState = pNode->m_SRow.lpProps[I_RULE_STATE].Value.l;

// Note - Given the actual values for ST_ENABLED and ST_DISABLED, the
// following clearing AND setting of bits is really not necessary; however
// we do it this way in case the bits ever get redefined (currently
// ST_DISABLED == 0x0000).

pNode->m_SRow.lpProps[I_RULE_STATE].Value.l &= ~ST_ENABLED;
pNode->m_SRow.lpProps[I_RULE_STATE].Value.l |= ST_DISABLED;

hr = m_pFR->m_SRowLst.HrWriteToTable(m_pFR->m_lpExchTbl);

if (FAILED(hr))
{
// The table write failed, so reset the list to the previous value.

pNode->m_SRow.lpProps[I_RULE_STATE].Value.l = lPrevState;
goto cleanup;
}

cleanup:

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrEnable------------------------------------------
//
// DESCRIPTION:Enable the current rule.
//
// INPUT:None.
//
// RETURNS:NOERRORif successful;
//              E_FAILif cursor at RULE_PAST_END or corrupt data;
//              E_OUTOFMEMORYif insufficient memory.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrEnable(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
LONGlPrevState =0;
CSROWNODE *pNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderRules::HrEnable().\n");

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR.");

pNode = m_pFR->m_SRowLst.GetCurrentNode();

if (pNode == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (pNode->m_SRow.cValues < C_RULEPROPS ||
pNode->m_SRow.lpProps[I_RULE_STATE].ulPropTag != PR_RULE_STATE)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Save the previous state in case the table write fails.

lPrevState = pNode->m_SRow.lpProps[I_RULE_STATE].Value.l;

// Note - Given the actual values for ST_ENABLED and ST_DISABLED, the
// following clearing AND setting of bits is really not necessary; however
// we do it this way in case the bits ever get redefined (currently
// ST_DISABLED == 0x0000).

pNode->m_SRow.lpProps[I_RULE_STATE].Value.l &= ~ST_DISABLED;
pNode->m_SRow.lpProps[I_RULE_STATE].Value.l |= ST_ENABLED;

hr = m_pFR->m_SRowLst.HrWriteToTable(m_pFR->m_lpExchTbl);

if (FAILED(hr))
{
// The table write failed, so reset the list to the previous value.

pNode->m_SRow.lpProps[I_RULE_STATE].Value.l = lPrevState;
goto cleanup;
}

cleanup:

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrGet---------------------------------------------
//
// DESCRIPTION:Get the current record and advance the cursor.
//
// OUTPUT:
//
//   [lplState]--  Rule state buffer.
//   [lppRestriction]--  Restriction buffer.
//   [lppActions]--  Actions buffer.
//   [lplLevel]--  Rule level buffer.
//   [lppszName]--  Rule name buffer.
//
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_FAILif cursor at RULE_PAST_END;
//              E_OUTOFMEMORYif insufficient memory.
//
// Notes:Space for *lplState and *lplLevel is allocated by the user.
//Space for the other parameters is allocated by this method.
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrGet(// RETURNS: HRESULT
OUTLPLONGlplState,// state ptr
OUTLPSRestriction FAR *lppRestriction, // restriction buffer ptr
OUTLPACTIONS FAR *lppActions,// actions buffer ptr
OUTLPLONGlplLevel,// level ptr
OUTLPSTR FAR *lppszName   // rule name ptr
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPUBLIC("CIExchangeFolderRules::HrGet().\n");

    hr = CHK_CIExchangeFolderRules_HrGet(lplState,
        lppRestriction,
        lppActions,
     lplLevel,
     lppszName);
    if (FAILED(hr))
        RETURN(hr);

// Null the ptrs so we can free them on failure.

*lppRestriction =NULL;
*lppActions =NULL;
*lppszName =NULL;

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *), "Bad m_pFR.");

pNode = m_pFR->m_SRowLst.GetCurrentNode();

if (pNode == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (pNode->m_SRow.cValues < C_RULEPROPS||
pNode->m_SRow.lpProps[I_RULE_SEQUENCE].ulPropTag !=
PR_RULE_SEQUENCE||
pNode->m_SRow.lpProps[I_RULE_STATE].ulPropTag !=
PR_RULE_STATE||
pNode->m_SRow.lpProps[I_RULE_CONDITION].ulPropTag !=
PR_RULE_CONDITION||
pNode->m_SRow.lpProps[I_RULE_ACTIONS].ulPropTag !=
PR_RULE_ACTIONS||
pNode->m_SRow.lpProps[I_RULE_LEVEL].ulPropTag !=
PR_RULE_LEVEL||
pNode->m_SRow.lpProps[I_RULE_NAME].ulPropTag !=
PR_RULE_NAME)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}

*lplState = pNode->m_SRow.lpProps[I_RULE_STATE].Value.l;

if (pNode->m_SRow.lpProps[I_RULE_CONDITION].Value.x != NULL)
{
hr = HrCopyRestriction((LPSRestriction)pNode->
   m_SRow.lpProps[I_RULE_CONDITION].Value.x,
   NULL,
       lppRestriction);
if (FAILED(hr))
goto cleanup;
}

hr = HrCopyActions(
(LPACTIONS)pNode->m_SRow.lpProps[I_RULE_ACTIONS].Value.x,
NULL,
lppActions);

if (FAILED(hr))
goto cleanup;

*lplLevel = pNode->m_SRow.lpProps[I_RULE_LEVEL].Value.l;

if (pNode->m_SRow.lpProps[I_RULE_NAME].Value.lpszA != NULL)
{
ULONGcb = strlen(pNode->m_SRow.lpProps[I_RULE_NAME].Value.lpszA)
 + sizeof(char);

hr = MAPIAllocateBuffer(cb, (LPVOID FAR *)lppszName);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

strcpy(*lppszName,
   pNode->m_SRow.lpProps[I_RULE_NAME].Value.lpszA);
}

// Advance the cursor.

m_pFR->m_SRowLst.SetCursor(m_pFR->m_SRowLst.GetCursor() + 1);

cleanup:

if (FAILED(hr))
{
MAPIFREEBUFFER(*lppRestriction);
MAPIFREEBUFFER(*lppActions);
MAPIFREEBUFFER(*lppszName);
}

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrInsert------------------------------------------
//
// DESCRIPTION:Insert record before the current record and advance the cursor.
//
// INPUT:
//
//   [lState]-- Rule state.
//   [lpRestriction]-- Restriction.
//   [lpActions]-- Actions.
//   [lLevel]-- Rule level.
//   [lpszRule]-- Rule name.
//
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input;
//              E_OUTOFMEMORYif memory problems;
//              E_FAILotherwise.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrInsert(// RETURNS: HRESULT
INLONGlState,         // rule state
INLPSRestrictionlpRestriction,  // restriction ptr
INLPACTIONSlpActions,      // actions ptr
INLONGlLevel,// rule level
INLPSTRlpszName// rule name
)
{
    HRESULThr =NOERROR;
LPACTIONSlpActionsCopy =NULL;
LPSRestrictionlpRestrictionCopy =NULL;
LPSTRlpszNameCopy =NULL;
LONGlPosInsertion =RULE_PAST_END;
SRowsr = {0, C_RULEPROPS, NULL};

    DEBUGPUBLIC("CIExchangeFolderRules::HrInsert().\n");

    hr = CHK_CIExchangeFolderRules_HrInsert(lState,
  lpRestriction,
  lpActions,
  lLevel,
  lpszName);
    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *),
                            "Bad m_pFR.");

hr = MAPIAllocateBuffer(sizeof(SPropValue) * C_RULEPROPS,
(LPVOID FAR *)&sr.lpProps);
if (FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);

goto cleanup;
    }

memset(sr.lpProps, 0, sizeof(SPropValue) * C_RULEPROPS);

hr = HrCopyActions(lpActions, sr.lpProps, &lpActionsCopy);

if (FAILED(hr))
goto cleanup;

hr = HrCopyRestriction(lpRestriction, sr.lpProps, &lpRestrictionCopy);

if (FAILED(hr))
goto cleanup;

hr = MAPIAllocateMore(strlen(lpszName) + sizeof(char),
  sr.lpProps,
    (LPVOID FAR *)&lpszNameCopy);
if (FAILED(hr))
    {
        hr = HR_LOG(E_OUTOFMEMORY);

goto cleanup;
    }

strcpy(lpszNameCopy, lpszName);

sr.lpProps[I_RULE_SEQUENCE].ulPropTag = PR_RULE_SEQUENCE;
sr.lpProps[I_RULE_SEQUENCE].Value.ul =0;
// SROWLST::Insert() resets value as appropriate.

sr.lpProps[I_RULE_STATE].ulPropTag =PR_RULE_STATE;
sr.lpProps[I_RULE_STATE].Value.l =lState;

sr.lpProps[I_RULE_CONDITION].ulPropTag =PR_RULE_CONDITION;
sr.lpProps[I_RULE_CONDITION].Value.ul =(ULONG)lpRestrictionCopy;

sr.lpProps[I_RULE_ACTIONS].ulPropTag =PR_RULE_ACTIONS;
sr.lpProps[I_RULE_ACTIONS].Value.ul =(ULONG)lpActionsCopy;

sr.lpProps[I_RULE_PROVIDER].ulPropTag =PR_RULE_PROVIDER;
sr.lpProps[I_RULE_PROVIDER].Value.lpszA =NULL;
// SROWLST::Insert() sets value as appropriate.

sr.lpProps[I_RULE_LEVEL].ulPropTag =PR_RULE_LEVEL;
sr.lpProps[I_RULE_LEVEL].Value.l =lLevel;

sr.lpProps[I_RULE_NAME].ulPropTag =PR_RULE_NAME;
sr.lpProps[I_RULE_NAME].Value.lpszA =lpszNameCopy;

// The following code determines the cursor value the newly inserted
// record will have.  This is done so we can easily delete the
// record if the table write fails.

lPosInsertion = m_pFR->m_SRowLst.GetCursor();

if (lPosInsertion == RULE_PAST_END)
lPosInsertion = m_pFR->m_SRowLst.GetNodeCount();

hr = m_pFR->m_SRowLst.HrInsert(&sr);

if (FAILED(hr))
goto cleanup;

hr = m_pFR->m_SRowLst.HrWriteToTable(m_pFR->m_lpExchTbl);

if (FAILED(hr))
{
HRESULThrTmp =NOERROR;

// Delete the node from the list, since the table write failed.

m_pFR->m_SRowLst.SetCursor(lPosInsertion);

hrTmp = m_pFR->m_SRowLst.HrDelete();

if (FAILED(hrTmp))
hr = HR_LOG(E_UNEXPECTED);

// The memory allocated by this method was cleaned up when the
// node was deleted.

sr.lpProps = NULL;
goto cleanup;
}

cleanup:

if (FAILED(hr))
MAPIFREEBUFFER(sr.lpProps);

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrSeek--------------------------------------------
//
// DESCRIPTION:Set the current cursor position.
//
// INPUT:
//
// [lPos]-- New cursor position.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrSeek(// RETURNS: HRESULT
INLONGlPos// cursor position
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderRules::HrSeek().\n");

    hr = CHK_CIExchangeFolderRules_HrSeek(lPos);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *), "Bad m_pFR.");

m_pFR->m_SRowLst.SetCursor(lPos);

    RETURN(hr);
}

// $--CIExchangeFolderRules::HrTell--------------------------------------------
//
// DESCRIPTION:Return the current cursor position.
//
// OUTPUT:
//
// [plPos]-- Pointer for returning current cursor position.
//
// RETURNS:NOERRORif successful;
//E_INVALIDARGif bad input.
//
//-----------------------------------------------------------------------------

STDMETHODIMP
CIExchangeFolderRules::HrTell(// RETURNS: HRESULT
OUTLPLONGlplPos// cursor position ptr
)
{
    HRESULThr =NOERROR;

    DEBUGPUBLIC("CIExchangeFolderRules::HrTell().\n");

    hr = CHK_CIExchangeFolderRules_HrTell(lplPos);

    if (FAILED(hr))
        RETURN(hr);

    ASSERT_READ_PTR_OR_NULL(m_pFR, sizeof(CFolderRules FAR *), "Bad m_pFR.");

*lplPos = m_pFR->m_SRowLst.GetCursor();

    RETURN(hr);
}


SROWLST.CPP

// --srowlst.cpp---------------------------------------------------------------
//
// Implementation of classes CSROWNODE and CSROWLST.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#include "edk.h"
#include "srowlst.h"

// $--CSROWNODE::CSROWNODE-----------------------------------------------------
//
// DESCRIPTION:CSROWNODE constructor.
//
// INPUT:
//
//[lpSRow]-- Ptr to SRow for the CSROWNODE to be constructed.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWNODE::CSROWNODE(
INLPSRowlpSRow  // row set ptr
    )
{
    DEBUGPRIVATE("CSROWNODE::CSROWNODE()\n");

    m_pNxt =NULL;
    m_pPrv =NULL;
m_SRow =*lpSRow;
}

// $--CSROWNODE::~CSROWNODE----------------------------------------------------
//
// DESCRIPTION:CSROWNODE destructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWNODE::~CSROWNODE()
{
    DEBUGPRIVATE("CSROWNODE::~CSROWNODE()\n");

MAPIFREEBUFFER(m_SRow.lpProps);
}

// $--CSROWLST::CSROWLST-------------------------------------------------------
//
// DESCRIPTION:CSROWLST constructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWLST::CSROWLST()
{
    DEBUGPRIVATE("CSROWLST::CSROWLST()\n");

m_cNodes =0;
m_cOtherProvNodes =0;
m_lPos =RULE_PAST_END;
m_lpszProvider =NULL;
    m_pCurNode =NULL;
    m_pLstHd =NULL;
m_pOtherProvLstHd =NULL;
}

// $--CSROWLST::~CSROWLST------------------------------------------------------
//
// DESCRIPTION:CSROWLST destructor.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

CSROWLST::~CSROWLST()
{
    CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::~CSROWLST()\n");

MAPIFREEBUFFER(m_lpszProvider);

    while (m_pLstHd != NULL)
    {
        pNode = m_pLstHd;
        m_pLstHd = m_pLstHd->m_pNxt;
delete pNode;
    }

    while (m_pOtherProvLstHd != NULL)
    {
        pNode = m_pOtherProvLstHd;
        m_pOtherProvLstHd = m_pOtherProvLstHd->m_pNxt;
delete pNode;
    }
}

// $--CSROWLST::HrInitialize---------------------------------------------------
//
// DESCRIPTION:Initialize an CSROWLST.  An SRowSet is stored in the CSROWLST.
//The SROWNODES are sorted in ascending order based on the
//value of PR_RULE_SEQUENCE in the SRow's.  Upon completion of
//this method, either successful or unsuccessful, all storage
//associated with lpRows has either been reassigned to the
//CSROWLST, or has been deallocated.  Only rules associated
//with lpszProvider are visible to users.  Other rules are
//stored in the m_pOtherProvLstHd list so they may be written back
//out to the rules table when necessary.
//
// INPUT:
//
//  [lpszProvider]-- Provider for rules.  Multiple providers may have
//   rules on a folder. The IExchangeFolderRules interface
//   provides access to the rules associated with a
//   single specified provider.  May be NULL, in which case
//   a provider list can be obtained, but no other operations
//   are possible.
//
//INPUT/OUTPUT:
//
//[lpRows]-- Ptr to SRowSet to use in initializing the CSROWLST.
//
// RETURNS:NOERROR on success;
//              E_INVALIDARGif bad input;
//E_OUTOFMEMORYif insufficient memory;
//              E_FAILotherwise.
//
// Warning:lpRows is NO LONGER VALID after calling this function and
//should NOT be used or deallocated!
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrInitialize(// RETURNS: HRESULT
INLPSTRlpszProvider,// provider name
IN OUTLPSRowSetlpRows      // row set ptr
)
{
HRESULThr =NOERROR;
ULONGi =0;
    CSROWNODE *pCurNode =NULL;
    CSROWNODE *pNewNode =NULL;
ULONGulCurSeqNo =0;
ULONGulNewSeqNo =0;

    DEBUGPRIVATE("CSROWLST::HrInitialize()\n");

// Verify that all rows have the necessary properties.  We are somewhat
// draconian in insisting that everything is right; given a multiplicity
// of providers, however, it is a good safeguard against the unexpected.

for (i = 0; i < lpRows->cRows; i++)
{
if (lpRows->aRow[i].cValues < C_RULEPROPS||
lpRows->aRow[i].lpProps[I_RULE_SEQUENCE].ulPropTag !=
PR_RULE_SEQUENCE||
lpRows->aRow[i].lpProps[I_RULE_STATE].ulPropTag !=
PR_RULE_STATE||
lpRows->aRow[i].lpProps[I_RULE_CONDITION].ulPropTag !=
PR_RULE_CONDITION||
lpRows->aRow[i].lpProps[I_RULE_ACTIONS].ulPropTag !=
PR_RULE_ACTIONS||
lpRows->aRow[i].lpProps[I_RULE_PROVIDER].ulPropTag !=
PR_RULE_PROVIDER||
lpRows->aRow[i].lpProps[I_RULE_PROVIDER].Value.lpszA == NULL||
lpRows->aRow[i].lpProps[I_RULE_LEVEL].ulPropTag !=
PR_RULE_LEVEL||
lpRows->aRow[i].lpProps[I_RULE_NAME].ulPropTag !=
PR_RULE_NAME)
{
hr = HR_LOG(E_INVALIDARG);
goto cleanup;
}
}

if (lpszProvider == NULL)
{
for (i = 0; i < lpRows->cRows; i++)
{
pNewNode = new CSROWNODE(&lpRows->aRow[i]);

if (pNewNode == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

// The lpProps storage no longer belongs to the SRowSet, so:

lpRows->aRow[i].cValues = 0;
lpRows->aRow[i].lpProps = NULL;

AddToDLLTail(pNewNode, m_pOtherProvLstHd);

m_cOtherProvNodes++;
}
}
else
{
// Add the SRow's in the SRowSet to the row list.  If there are
// two identical sequence numbers associated with one provider, abort
// with an error.  Note that SRow's associated with the specified
// provider are added to the m_pLstHd list, and all other SRow's are
// added to the m_pOtherProvLstHd list.

hr = MAPIAllocateBuffer(strlen(lpszProvider) + 1,
(LPVOID FAR *)&m_lpszProvider);

if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

strcpy(m_lpszProvider, lpszProvider);

for (i = 0; i < lpRows->cRows; i++)
{
pNewNode = new CSROWNODE(&lpRows->aRow[i]);

if (pNewNode == NULL)
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

// The lpProps storage no longer belongs to the SRowSet, so:

lpRows->aRow[i].cValues = 0;
lpRows->aRow[i].lpProps = NULL;

if (stricmp(lpszProvider,
pNewNode->m_SRow.lpProps[I_RULE_PROVIDER].Value.lpszA))
{
AddToDLLTail(pNewNode, m_pOtherProvLstHd);

m_cOtherProvNodes++;
}
else
{
if (m_pLstHd == NULL)
{
AddToDLLHead(pNewNode, m_pLstHd);
m_cNodes++;
}
else
{
ulNewSeqNo =
pNewNode->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul;

// We walk through the list backwards to insert the new
// node, in order to get an O(n) sort for a previously
// sorted table (which is what we always write, but we have
// to do this because there are no guarantees regarding
// what others write).  See llmacro.h to understand the
// double linked list structure and macro calls.

pCurNode = m_pLstHd->m_pPrv;

ulCurSeqNo =
pCurNode->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul;

while (TRUE)
{
if (ulCurSeqNo < ulNewSeqNo)
{
InsertIntoDLL(pNewNode, m_pLstHd, pCurNode);
m_cNodes++;
break;
}
else if (ulCurSeqNo == ulNewSeqNo)
{
delete pNewNode;

hr = HR_LOG(E_FAIL);
goto cleanup;
}
else
{
if (pCurNode == m_pLstHd)
{
AddToDLLHead(pNewNode, m_pLstHd);
m_cNodes++;
break;
}
else
{
pCurNode = pCurNode->m_pPrv;

ulCurSeqNo =
pCurNode->
m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul;
}
}
}
}
}
}
}

if (m_cNodes > 0)
{
m_lPos = 0;
m_pCurNode = m_pLstHd;
}

cleanup:

FREEPROWS(lpRows);

RETURN(hr);
}

// $--CSROWLST::HrDelete-------------------------------------------------------
//
// DESCRIPTION:Delete the current record.
//
// INPUT:None.
//
// RETURNS:NOERROR on success;
//E_FAIL if current cursor value is RULE_PAST_END.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrDelete(VOID)// RETURNS: HRESULT
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrDelete()\n");

hr = HrRemoveFromLst(&pNode);

if (FAILED(hr))
goto cleanup;

delete pNode;

cleanup:

    RETURN(hr);
}

// $--HrGetProviders-----------------------------------------------------------
//
// DESCRIPTION:Get an array of rules provider names.
//
// OUTPUT:
//
//  [lpcProviders]-- Pointer to ulong that will be set to count of
//   providers on successful return.
//[lpppszProviders]-- Pointer to array of string pointers that will be set
//   to point at an array of provider name string pointers
//   on successful return.
//
// RETURNS:     NOERRORif successful;
//E_OUTOFMEMORYif not enough memory;
//              E_FAIL otherwise.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrGetProviders(// RETURNS: HRESULT
OUTLPULONGlpcProviders,// count of providers
OUTLPSTR FAR * FAR *lpppszProviders// ptr to array of providers
)
{
ULONGcProviders =0;
ULONGcProvidersMax =(m_cNodes > 0 ? 1 : 0) + m_cOtherProvNodes;
    HRESULThr =NOERROR;
ULONGi =0;
LPSTRlpszProvider=NULL;
LPSTR *lppszProviders =NULL;
CSROWNODE *pNode =m_pOtherProvLstHd;

    DEBUGPRIVATE("HrGetProviders()\n");

*lpppszProviders = NULL;

hr = MAPIAllocateBuffer(cProvidersMax * sizeof(LPSTR),
(LPVOID FAR *)&lppszProviders);
if (FAILED(hr))
goto cleanup;

// Get an array of ptrs to all the providers.

while (pNode != NULL)
{
lpszProvider = pNode->m_SRow.lpProps[I_RULE_PROVIDER].Value.lpszA;

for (i = 0; i < cProviders; i++)
if (!stricmp(lpszProvider, lppszProviders[i]))
break;

if (i == cProviders)
{
lppszProviders[cProviders] = lpszProvider;
cProviders++;
}

pNode = pNode->m_pNxt;
}

if (m_lpszProvider != NULL)
{
lppszProviders[cProviders] = m_lpszProvider;
cProviders++;
}

// Now allocate a copy for the user.

{
ULONGcch =cProviders * sizeof(LPSTR);
LPSTR *lppsz =NULL;
CHAR *pch = NULL;

for (i = 0; i < cProviders; i++)
cch += (strlen(lppszProviders[i]) + 1);

hr = MAPIAllocateBuffer(cch, (LPVOID FAR *)lpppszProviders);

if (FAILED(hr))
goto cleanup;

lppsz = *lpppszProviders;

pch = ((CHAR *)lppsz) + (cProviders * sizeof(LPSTR));

for (i = 0; i < cProviders; i++)
{
strcpy(pch, lppszProviders[i]);

lppsz[i] = pch;

pch += (strlen(lppszProviders[i]) + 1);
}
}

*lpcProviders = cProviders;

cleanup:

MAPIFREEBUFFER(lppszProviders);

    RETURN(hr);
}

// $--CSROWLST::HrInsert-------------------------------------------------------
//
// DESCRIPTION:Insert a new CSROWNODE before the current record and advance
//the cursor.  This also adjusts PR_RULE_SEQUENCE values as
//appropriate, and provides a value for PR_RULE_PROVIDER (ie.,
//the caller should leave this value NULL).
//
// INPUT:
//
//[lpSRow]-- Ptr to SRow for the CSROWNODE to be inserted.
//
// RETURNS:NOERRORon success;
//E_OUTOFMEMORYif insufficient memory;
//              E_FAILotherwise.
//
// Notes:The cursor is advanced only if it is not at RULE_PAST_END.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrInsert(// RETURNS: HRESULT
INLPSRowlpSRow      // row set ptr
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNewNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrInsert()\n");

if (m_cNodes == EDK_MAX_QUERY_ROWS)
    {
hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

if (m_lpszProvider == NULL)
    {
hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

pNewNode = new CSROWNODE(lpSRow);

if (pNewNode == NULL)
    {
    hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

pNewNode->m_SRow.lpProps[I_RULE_PROVIDER].Value.lpszA = m_lpszProvider;

if (m_pCurNode == m_pLstHd)// Covers empty list and insertion at head.
{
AddToDLLHead(pNewNode, m_pLstHd);
}
else if (m_pCurNode == NULL)// Covers insertion at tail.
{
AddToDLLTail(pNewNode, m_pLstHd);
}
else// Covers all other cases.
{
// It is important to store the previous node ptr in a separate
// location because m_pCurNode->m_pPrv gets stomped on in
// InsertIntoDLL().

CSROWNODE *pPrvNode = m_pCurNode->m_pPrv;

InsertIntoDLL(pNewNode, m_pLstHd, pPrvNode);
}

m_cNodes++;

if (m_lPos != RULE_PAST_END)
m_lPos++;

ReSequence();

// Note that m_pCurNode does not change regardless of the insertion point.

cleanup:

    RETURN(hr);
}

// $--CSROWLST::HrRemoveFromLst------------------------------------------------
//
// DESCRIPTION:Remove the current record from the list and make it available
//to the caller.  The caller is responsible for deleting the
//record when he no longer needs it.
//
// OUTPUT:
//
//[ppRemovedNode]-- Ptr that will be set to removed node on successful
//   return.
//
// RETURNS:NOERROR on success;
//E_FAIL if current cursor value is RULE_PAST_END.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrRemoveFromLst(
OUTCSROWNODE * *ppRemovedNode
)
{
    HRESULThr =NOERROR;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrRemoveFromLst()\n");

*ppRemovedNode = NULL;

if (m_lPos == RULE_PAST_END)
    {
hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

pNode = m_pCurNode;

m_pCurNode = m_pCurNode->m_pNxt;

if (m_pCurNode == NULL)
m_lPos = RULE_PAST_END;

RmFromDLL(pNode, m_pLstHd);

m_cNodes--;

*ppRemovedNode = pNode;

cleanup:

    RETURN(hr);
}

// $--CSROWLST::SetCursor------------------------------------------------------
//
// DESCRIPTION:Standard C++ set member function which sets the current
//cursor position.
//
// INPUT:
//
//[lPos]-- New cursor position.  If < 0 or past end of list, then the
//   new cursor positon will be set to RULE_PAST_END.
//
// RETURNS:The new cursor position.
//
//-----------------------------------------------------------------------------

LONG
CSROWLST::SetCursor(// RETURNS: LONG
INLONGlPos// cursor position
)
{
    DEBUGPRIVATE("CSROWLST::SetCursor()\n");

if (lPos >= 0 && lPos < (LONG) m_cNodes)
{
if (m_lPos == RULE_PAST_END || m_lPos > lPos)
{
m_pCurNode = m_pLstHd;
m_lPos = 0;
}

while (m_lPos < lPos)
{
m_pCurNode = m_pCurNode->m_pNxt;
m_lPos++;
}
}
else
{
m_lPos = RULE_PAST_END;
m_pCurNode =NULL;
}

    return m_lPos;
}

// $--CSROWLST::ReSequence-----------------------------------------------------
//
// DESCRIPTION:Resequence the PR_RULE_SEQUENCE values so they are valid.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
CSROWLST::ReSequence(VOID)      // RETURNS: VOID
{
CSROWNODE *pNode = NULL;
ULONGulLastSeqNo =0;

    DEBUGPRIVATE("CSROWLST::ReSequence()\n");

// The following algorithm is designed to not change sequence numbers
// unless it is necessary.  This is done to protect providers that may
// assign special significance to some sequence values (not a great idea,
// but it is rumored to have occurred).  There is no protection against
// rollover of values, but that is hopefully not a very likely occurrence.

if (m_pLstHd == NULL)
goto cleanup;

ulLastSeqNo = m_pLstHd->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul;

pNode = m_pLstHd->m_pNxt;

while (pNode != NULL)
{
if (pNode->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul <= ulLastSeqNo)
pNode->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul = ulLastSeqNo + 1;

ulLastSeqNo = pNode->m_SRow.lpProps[I_RULE_SEQUENCE].Value.ul;

pNode = pNode->m_pNxt;
}

cleanup:

    return;
}

// $--CSROWLST::HrWriteToTable-------------------------------------------------
//
// DESCRIPTION:Write the contents of the CSROWLST to an EXCHANGEMODIFYTABLE.
//
// INPUT:
//
//[lpExchTbl]-- Ptr to the EXCHANGEMODIFYTABLE that will be rewritten based
//   on the contents of the CSROWLST.
//
// RETURNS:NOERRORon success;
//              E_INVALIDARGif bad input;
//E_OUTOFMEMORYif insufficient memory;
//              E_FAILotherwise.
//
//-----------------------------------------------------------------------------

HRESULT
CSROWLST::HrWriteToTable(// RETURNS: HRESULT
INLPEXCHANGEMODIFYTABLElpExchTbl   // Exchange modify tbl i/f ptr
)
{
HRESULThr =NOERROR;
LPROWENTRYlpRowEntry =NULL;
LPROWLISTlpRowList =NULL;
CSROWNODE *pNode =NULL;

    DEBUGPRIVATE("CSROWLST::HrWriteToTable()\n");

// Make a ROWLIST for use with the IExchangeModifyTable interface.  We
// explicitly call MAPIAllocateBuffer() here due to the size vagaries
// of MAPI structures.

hr = MAPIAllocateBuffer(CbNewROWLIST(m_cNodes + m_cOtherProvNodes),
(LPVOID FAR *)&lpRowList);
if (FAILED(hr))
{
hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

lpRowList->cEntries = m_cNodes + m_cOtherProvNodes;

pNode = m_pLstHd;

lpRowEntry = lpRowList->aEntries;

while (pNode != NULL)
{
lpRowEntry->ulRowFlags =ROW_ADD;
lpRowEntry->cValues =pNode->m_SRow.cValues;
lpRowEntry->rgPropVals =pNode->m_SRow.lpProps;

lpRowEntry++;

pNode = pNode->m_pNxt;
}

pNode = m_pOtherProvLstHd;

while (pNode != NULL)
{
lpRowEntry->ulRowFlags =ROW_ADD;
lpRowEntry->cValues =pNode->m_SRow.cValues;
lpRowEntry->rgPropVals =pNode->m_SRow.lpProps;

lpRowEntry++;

pNode = pNode->m_pNxt;
}

// Modify the entire table.

hr = lpExchTbl->ModifyTable(ROWLIST_REPLACE, lpRowList);

if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
goto cleanup;
    }

cleanup:

// Deallocate the ROWLIST (not including the SPropValue ptrs, since they
// still actually belong to the CSROWLST).

MAPIFREEBUFFER(lpRowList);

RETURN(hr);
}


SROWLST.H

// --srowlst.h-----------------------------------------------------------------
//
// Header file for classes CSROWNODE and CSROWLST.
//
// Copyright (C) Microsoft Corp, 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#ifndef_SROWLST_H
#define _SROWLST_H

#include "llmacro.h"

// ROWLIST allocation helper macro for IExchangeModifyTable interface.

#defineCbNewROWLIST(_centries)(offsetof(ROWLIST,aEntries) + \
(_centries)*sizeof(ROWENTRY))

// Count of properties supported by rule object.

#defineC_RULEPROPS7

// Indices of rule properties in the SRow associated with a rule object.

#defineI_RULE_SEQUENCE0
#defineI_RULE_STATE1
#defineI_RULE_CONDITION2
#defineI_RULE_ACTIONS3
#defineI_RULE_PROVIDER4
#defineI_RULE_LEVEL5
#defineI_RULE_NAME6

class CSROWNODE// srn
{
friend class CIExchangeFolderRules;
friend class CSROWLST;

    public:

        // constructor & destructor

CSROWNODE(
INLPSRowlpSRow
    );

~CSROWNODE();

protected:

CSROWNODE *m_pNxt;
CSROWNODE *m_pPrv;
SRowm_SRow;
};

class CSROWLST// srl
{
    public:

CSROWLST();     // constructor

~CSROWLST();    // destructor

        // standard C++ Get and Set member functions

CSROWNODE * GetCurrentNode(VOID);

LONG GetCursor(VOID) const;

LONG GetNodeCount(VOID) const;

LONG GetOtherProvNodeCount(VOID) const;

LONG SetCursor(
INLONGlPos
);

HRESULT HrInitialize(
INLPSTRlpszProvider,
IN OUTLPSRowSetlpRows
);

HRESULT HrDelete(VOID);

HRESULTHrGetProviders(
OUTLPULONGlpcProviders,
OUTLPSTR FAR * FAR *lpppszProviders
);

HRESULT HrInsert(
INLPSRowlpSRow
);

HRESULT HrRemoveFromLst(
OUTCSROWNODE * *ppRemovedNode
);

HRESULT HrWriteToTable(
INLPEXCHANGEMODIFYTABLElpExchTbl
);

private:

VOID ReSequence(VOID);

ULONGm_cNodes;
ULONGm_cOtherProvNodes;
LONGm_lPos;
LPSTRm_lpszProvider;
CSROWNODE *m_pCurNode;
CSROWNODE *m_pLstHd;
CSROWNODE *m_pOtherProvLstHd;
};

// $--CSROWNODE::GetCurrentNode------------------------------------------------
//
// DESCRIPTION:Return a pointer to the current node.
//
// INPUT:   None.
//
// RETURNS:A pointer to the current node.
//
//-----------------------------------------------------------------------------

inline CSROWNODE * CSROWLST::GetCurrentNode(VOID)
{
    return m_pCurNode;
}

// $--CSROWLST::GetCursor------------------------------------------------------
//
// DESCRIPTION:Return the current cursor position.
//
// INPUT:   None.
//
// RETURNS:The current cursor position.
//
//-----------------------------------------------------------------------------

inline LONG CSROWLST::GetCursor(VOID) const
{
    return m_lPos;
}

// $--CSROWLST::GetNodeCount---------------------------------------------------
//
// DESCRIPTION:Return the count of nodes in the list.
//
// INPUT:   None.
//
// RETURNS:The count of nodes in the list.
//
//-----------------------------------------------------------------------------

inline LONG CSROWLST::GetNodeCount(VOID) const
{
    return m_cNodes;
}

// $--CSROWLST::GetOtherProvNodeCount------------------------------------------
//
// DESCRIPTION:Return the count of nodes in the other provider list.
//
// INPUT:   None.
//
// RETURNS:The count of nodes in the other provider list.
//
//-----------------------------------------------------------------------------

inline LONG CSROWLST::GetOtherProvNodeCount(VOID) const
{
    return m_cOtherProvNodes;
}

#endif // _SROWLSTH_


TOKTYPES.H

// --toktypes.h------------------------------------------------
//
// Token type definitions used by rulecls.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
// ------------------------------------------------------------

#ifndef _TOKTYPES_H
#define _TOKTYES_H

//
// Enumeration, structure, and other type definitions used in parsing tokens.
//

enum TOKENTYPE// tt
{
AndOp,
BooleanTag,
ContainsOp,
EndOfString,
EqualOp,
GreaterThanOp,
InvalidTag,// Property tag not PT_BOOLEAN, PT_LONG, PT_STRING8,
// PT_UNICODE or PT_SYSTIME.
LeftParen,
LessThanOp,
NotRecognized,
NotEqualOp,
NotOp,
NumericLiteral,
NumericTag,
OrOp,
RightParen,
StringLiteral,
StringTag,
TimeLiteral,
TimeTag
};

// $$--TOKENINFO-----------------------------------------------
//
// Token information structure type definition.
//
// ------------------------------------------------------------

typedef struct _TOKENINFO// ti
{
CHAR *pch;
ULONGcch;
TOKENTYPEtt;
union
{
DWORDdwValue;// Used to store tags and numeric literals.
FILETIMEftValue;// Used to store time literals.

};
} TOKENINFO;

#endif


FIFO.CPP

// --fifo.cpp-------------------------------------------------------------------
//
//  Implements a FIFO buffer.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "fifo.h"
#include "fifo.chk"

//$--CFIFO::CFIFO---------------------------------------------------------------
//  Constructor
// -----------------------------------------------------------------------------
CFIFO::CFIFO()
{
    DEBUGPRIVATE("CFIFO::CFIFIO()\n");

    m_nElements       = 0;
    m_maxElements     = 0;
    m_iCurrentElement = 0;
    m_padwFIFOBuffer  = NULL;
}

//$--CFIFO::~CFIFO--------------------------------------------------------------
//  Destructor
// -----------------------------------------------------------------------------
CFIFO::~CFIFO()
{
    DEBUGPRIVATE("CFIFO::~CFIFIO()\n");

    FREE(m_padwFIFOBuffer);
}

//$--CFIFO::HrReset-------------------------------------------------------------
//  Resets the FIFO buffer. Should be called after object is constructed
//  and before it is used.
// -----------------------------------------------------------------------------
HRESULT CFIFO::HrReset(                 // RETURNS: NOERROR if reset successful.
    IN DWORD maxElements)               // Maximum number of elements in buffer.
{
    HRESULT hr  = NOERROR;
    DWORD cb    = sizeof(DWORD) * maxElements;

    DEBUGPRIVATE("CFIFO::HrReset()\n");

hr = CHK_CFIFO_HrReset(maxElements);
if (FAILED(hr))
RETURN(hr);

    FREE(m_padwFIFOBuffer);

    m_padwFIFOBuffer = (DWORD *) malloc( cb );
    if (m_padwFIFOBuffer == NULL)
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    FillMemory(m_padwFIFOBuffer,cb,0);

    m_maxElements     = maxElements;
    m_nElements       = 0;
    m_iCurrentElement = 0;

cleanup:
    RETURN(hr);

}

//$--CFIFO::PushElement---------------------------------------------------------
//  Pushes a new element into the FIFO buffer. Returns the value pushed out
//  the other side.
// -----------------------------------------------------------------------------
VOID CFIFO::PushElement(
    IN DWORD dwValue,           // New Value to Push into buffer.
    OUT DWORD &dwPushedOut)     // Recieves old value pushed out of buffer.
{
    HRESULT hr  = NOERROR;

    DEBUGPRIVATE("CFIFO::PushElement()\n");

hr = CHK_CFIFO_PushElement(dwValue, dwPushedOut);
if (FAILED(hr))
return;

    // Return whatever is at curent element.
    // -------------------------------------
    dwPushedOut = m_padwFIFOBuffer[m_iCurrentElement];
    m_padwFIFOBuffer[m_iCurrentElement] = dwValue;

    if (m_nElements < m_maxElements)
    {
        m_nElements++;
    }

    // Update current element pointer
    //-------------------------------
    m_iCurrentElement++;
    if (m_iCurrentElement >= m_maxElements)
    {
        m_iCurrentElement = 0;
    }

    return;
}

//$--CFIFO::PushNNulls----------------------------------------------------------
//  Push a number of 0 Values into the FIFO buffer.  Returns a summation
//  of the values pushed out the other side.
// -----------------------------------------------------------------------------
VOID CFIFO::PushNNulls(
    IN DWORD cNulls,            // Number of NULLS to push.
    OUT DWORD &dwPushedOut)     // Receives total values pushed out.
{
    HRESULT hr  = NOERROR;
    DWORD i = 0;
    DWORD cCurrentToEnd = 0;

    DEBUGPRIVATE("CFIFO::PushNNulls()\n");

hr = CHK_CFIFO_PushNNulls(cNulls, dwPushedOut);
if (FAILED(hr))
return;

    dwPushedOut = 0;

    if (cNulls == 0)
        goto cleanup;

    // case: cNulls is larger than the max number of buffer elements.
    // Action: Clear the entire buffer and reset the current index.
    // ------------------------------------------------------------
    if (cNulls >=  m_maxElements)
    {
        for (i=0; i<m_maxElements; i++)
        {
            dwPushedOut += m_padwFIFOBuffer[i];
            m_padwFIFOBuffer[i] = 0 ;
        }

        m_iCurrentElement = 0;
        goto cleanup;
    }

    // case: cNulls will advance current index past end of buffer
    // action:  1) clear the elements from current index+1 to end of buffer.
    //          2) clear elements from start of buffer to new index.
    // ---------------------------------------------------------------------
    cCurrentToEnd = (m_maxElements-1) - m_iCurrentElement;
    if (cNulls > cCurrentToEnd)
    {
        for (i=m_iCurrentElement; i<m_maxElements; i++)
        {
            dwPushedOut += m_padwFIFOBuffer[i];
            m_padwFIFOBuffer[i] = 0;
        }

        m_iCurrentElement = cNulls - cCurrentToEnd - 1;
        for (i=0; i< m_iCurrentElement; i++)
        {
            dwPushedOut += m_padwFIFOBuffer[i];
            m_padwFIFOBuffer[i] = 0;
        }
        goto cleanup;
    }

    // case: cbDelta will not advance current pointer past end of buffer
    // action: clear the elements between the old current index and the
    //         new current index.
    // -----------------------------------------------------------------
    for (i=0; i<cNulls; i++)
    {
        dwPushedOut += m_padwFIFOBuffer[m_iCurrentElement+i];
        m_padwFIFOBuffer[m_iCurrentElement+i] = 0;
    }
    m_iCurrentElement += cNulls;

cleanup:
    m_nElements += cNulls;
    if (m_nElements > m_maxElements)
    {
        m_nElements = m_maxElements;
    }

    return;
}

//$--CFIFO::Min-----------------------------------------------------------------
// Returns the minimum value in the buffer.
// -----------------------------------------------------------------------------
VOID CFIFO::Min(
    OUT DWORD &dwMin)  // receives the minimum of all values in the buffer.
{
    HRESULT hr  = NOERROR;
    DWORD i = 0;

    DEBUGPRIVATE("CFIFO::Min()\n");

hr = CHK_CFIFO_Min(dwMin);
if (FAILED(hr))
return;

    dwMin = MAXWORD;

    if (m_nElements == 0)
    {
        dwMin = 0;
        goto cleanup;
    }

    for (i=0; i<m_iCurrentElement; i++)
    {
        if (m_padwFIFOBuffer[i] < dwMin )
            dwMin = m_padwFIFOBuffer[i];
    }

    for (i=m_iCurrentElement; i<m_nElements; i++)
    {
        if (m_padwFIFOBuffer[i] < dwMin )
            dwMin = m_padwFIFOBuffer[i];
    }

cleanup:
    return;
}

//$--CFIFO::Max-----------------------------------------------------------------
// Returns the maximum value in a buffer.
// -----------------------------------------------------------------------------
VOID CFIFO::Max(
    OUT DWORD &dwMax)  // receives the maximum value in the buffer.
{
    HRESULT hr  = NOERROR;
    DWORD i = 0;

    DEBUGPRIVATE("CFIFO::Max()\n");

hr = CHK_CFIFO_Max(dwMax);
if (FAILED(hr))
return;

    dwMax = 0;

    for (i=0; i<m_iCurrentElement; i++)
    {
        if (m_padwFIFOBuffer[i] > dwMax )
           dwMax = m_padwFIFOBuffer[i];
    }

    for (i=m_iCurrentElement; i<m_nElements; i++)
    {
        if (m_padwFIFOBuffer[i] > dwMax )
            dwMax = m_padwFIFOBuffer[i];
    }

    return;
}

//$--CFIFO::Average-------------------------------------------------------------
// Returns the average value in the buffer.
// -----------------------------------------------------------------------------
VOID CFIFO::Average(
    OUT DWORD &dwAverage)  // receives buffer average
{
    HRESULT hr  = NOERROR;
    DWORD i = 0;
    DWORD dwSum = 0;

    DEBUGPRIVATE("CFIFO::Average()\n");

hr = CHK_CFIFO_Average(dwAverage);
if (FAILED(hr))
return;

    dwAverage = 0;

    for (i=0; i<m_iCurrentElement; i++)
    {
        dwSum += m_padwFIFOBuffer[i];
    }

    for (i=m_iCurrentElement; i<m_nElements; i++)
    {
        dwSum += m_padwFIFOBuffer[i];
    }

    if (m_nElements != 0)
        dwAverage = dwSum / m_nElements;
    return;
}


FIFO.H

// --fifo.h---------------------------------------------------------------------
//
//  Defines the fifo object
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef __FIFO_H
#define __FIFO_H

#include <windows.h>

//$--CFIFO----------------------------------------------------------------------
//  Defines the CFIFO object, a First In First Out Buffer.
// -----------------------------------------------------------------------------
class CFIFO
{
public:
    CFIFO(void);
    virtual ~CFIFO(void);
    HRESULT HrReset(IN DWORD maxElements);
    VOID PushElement(IN DWORD dwValue, OUT DWORD &dwPushedOut);
    VOID PushNNulls(IN DWORD nNulls, OUT DWORD &dwPushedOut);
    DWORD dwNumberElements(VOID)  { return m_nElements; };
    DWORD dwMaxNumberElements(VOID) { return m_maxElements; };
    VOID Min(OUT DWORD &);
    VOID Max(OUT DWORD &);
    VOID Average(OUT DWORD &);

private:
    DWORD m_nElements;
    DWORD m_maxElements;
    DWORD m_iCurrentElement;
    DWORD *m_padwFIFOBuffer;
};

#endif


FLDMTRC.CPP

// --fldmtrc.cpp----------------------------------------------------------------
//
//  Implements the folder metrics code.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "fldmtrc.h"
#include "fldmtrc.chk"

// Class constants
// ---------------
SPropTagArray CFolderMetrics::m_sFolderContentCount     = { 1L, {PR_CONTENT_COUNT } };
SSortOrderSet CFolderMetrics::m_sSortOrderSet           = { 1L, 0, 0, {PR_MESSAGE_DELIVERY_TIME , TABLE_SORT_ASCEND} };
SPropTagArray CFolderMetrics::m_sInFolderSince          = { 1L, {PR_MESSAGE_DELIVERY_TIME } };

//$--CFolderMetrics::CFolderMetrics---------------------------------------------
//  Constructor for Folder Metrics object.
// -----------------------------------------------------------------------------
CFolderMetrics::CFolderMetrics()
{

    DEBUGPUBLIC("CFolderMetrics::CFolderMetrics()\n");

    m_lpFolder = NULL;
    m_lpTable = NULL;
}

//$--CFolderMetrics::~CFolderMetrics--------------------------------------------
//  Destructor for folder Metrics object.
// -----------------------------------------------------------------------------
CFolderMetrics::~CFolderMetrics()
{
    DEBUGPUBLIC("CFolderMetrics::~CFolderMetrics()\n");

    // m_lpFolder isn't freed because we didn't create it.
    // ---------------------------------------------------

    ULRELEASE(m_lpTable);
}

//$--CFolderMetrics::HrReset----------------------------------------------------
//  Resets and assigns the folder metrics object to a particular folder.
// -----------------------------------------------------------------------------
HRESULT CFolderMetrics::HrReset(        // HRESULT
    IN LPMAPIFOLDER lpFolder)           // Object is assigned to this folder.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("CFolderMetrics::HrReset()\n");

hr = CHK_CFolderMetrics_HrReset(lpFolder);
if (FAILED(hr))
RETURN(hr);

    if (lpFolder == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ULRELEASE(m_lpTable);

    // Open content table for lpFolder and set columns for PR_IN_FOLDER_SINCE
    // ----------------------------------------------------------------------
    hr = lpFolder->GetContentsTable(MAPI_DEFERRED_ERRORS, &m_lpTable);

    if (FAILED(hr))
    {
        goto cleanup;
    }

    hr = m_lpTable->SetColumns(&m_sInFolderSince, (ULONG)0);

    if (FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    m_lpFolder = lpFolder;

cleanup:
    // m_lpFolder isn't freed because we didn't create it.
    // ---------------------------------------------------

    RETURN(hr);
}

//$--CFolderMetrics::HrGetFolderMetrics-----------------------------------------
//  Determine and return the folder metrics values.
// -----------------------------------------------------------------------------
HRESULT CFolderMetrics::HrGetFolderMetrics( // RETURNS: HRESULT
    IN __int64 liNow,                       // Current time expressed as a file time.
    IN DWORD fMetric,                       // Flag to determine the metric
                                            //  to query. May be an or'ed
                                            //  combination of:
                                            //  FM_CMESSAGES, FM_LONGEST_WAIT
                                            //  FM_TOTAL_WAIT.
    OUT DWORD &cMessages,                   // Receives number of mesages
                                            //  in folder if FM_CMESSAGES
                                            //  is specified. = 0 otherwise.
    OUT __int64 &liLongestWait,             // Receives longest wait
                                            //  expressed as a file time if
                                            //  FM_LONGEST_WAIT specified.
                                            //  = 0 otherwise.
    OUT __int64 &liTotalWait)               // Recieves total wait
                                            //  expresses as a file time if
                                            //  FM_TOTAL_WAIT specified.
                                            // = 0 otherwise.
{
    HRESULT hr       = NOERROR;

    DEBUGPRIVATE("CFolderMetrics::HrGetFolderMetrics()\n");

    // pre-clear all return values
    // ---------------------------
    cMessages = 0;
    liLongestWait = 0;
    liTotalWait   = 0;

hr = CHK_CFolderMetrics_HrGetFolderMetrics(
    liNow,
fMetric,
cMessages,
liLongestWait,
liTotalWait);
if (FAILED(hr))
RETURN(hr);

    // The message count can be generated as a side effect of
    // calculating the FM_TOTAL_WAIT or queried separately as a folder
    // property.  If FM_TOTAL_WAIT is not requested then
    // then query the message count as a less expensive folder property.
    // ----------------------------------------------------------
    if (!(fMetric & FM_TOTAL_WAIT))
    {
        hr = HrGetcMessages(cMessages);

        if (FAILED(hr))
        {
            goto cleanup;
        }
    }

    // Re-sort contents table and go to the start of the table.
    // --------------------------------------------------------
    hr = m_lpTable->SortTable(&m_sSortOrderSet, 0);

    if (FAILED(hr))
    {
        goto cleanup;
    }

    hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);

    if (FAILED(hr))
    {
        goto cleanup;
    }

    // If longest wait metric requested, then get longest wait.
    // --------------------------------------------------------
    if (fMetric & FM_LONGEST_WAIT)
    {
        hr = HrGetLongestWait(liNow, liLongestWait);

        if (FAILED(hr))
        {
            goto cleanup;
        }
    }

    // If total wait metric requested, then get total wait.
    // ----------------------------------------------------
    if (fMetric & FM_TOTAL_WAIT)
    {
        hr = HrGetTotalWait(liNow, cMessages, liTotalWait);

        if (FAILED(hr))
        {
            goto cleanup;
        }
    }

cleanup:

    // if they didn't ask for it then don't give it to them.
    // -----------------------------------------------------
    if (!(fMetric & FM_CMESSAGES))
    {
        cMessages     = 0;
    }

    if (!(fMetric & FM_LONGEST_WAIT))
    {
        liLongestWait = 0;
    }

    if (!(fMetric & FM_TOTAL_WAIT))
    {
        liTotalWait = 0;
    }

    RETURN(hr);
}

// -----------------------------------------------------------------------------
//  Private member functions
// -----------------------------------------------------------------------------

//$--CFolderMetrics::HrGetcMessages---------------------------------------------
//  Returns the number of messages in the object folder.
// -----------------------------------------------------------------------------
HRESULT CFolderMetrics::HrGetcMessages( // RETURNS: HRESULT
    OUT DWORD &cMessage)                // Receives number of messages in folder.
{
    HRESULT         hr          = NOERROR;
    DWORD           cValues     = 0;
    LPSPropValue    lpPropValue = NULL;

    DEBUGPRIVATE("CFolderMetrics::HrGetMessages()\n");

hr = CHK_CFolderMetrics_HrGetcMessages(cMessage);
if (FAILED(hr))
RETURN(hr);

    cMessage = 0;

    hr = m_lpFolder->GetProps(
        &m_sFolderContentCount, fMapiUnicode, &cValues, &lpPropValue);

    if (FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if ( cValues > 0 )
    {
        cMessage = (DWORD) lpPropValue->Value.l;
    }

cleanup:

    MAPIFREEBUFFER(lpPropValue);

    RETURN(hr);
}

//$--CFolderMetrics::HrGetLongestWait-------------------------------------------
//  Retrieves duration in the folder of the oldest message.
// -----------------------------------------------------------------------------
HRESULT CFolderMetrics::HrGetLongestWait(   // RETURNS: HRESULT
    IN __int64 liNow,                       // Current Time expressed as a
                                            //   file time.
    OUT __int64 &liLongestWait)             // Receives the longest time that
                                            //   a messages has been in
                                            //   the folder expressed as a
                                            //   file time.
{
    HRESULT hr                  = NOERROR;
    LONG    lRes                = 0;

    LPSRowSet lpRows            = NULL;
    LPSPropValue lpProps        = NULL;
    ULONG ulType                = 0;
    __int64  liInFolderSince= 0;

    DEBUGPRIVATE("CFolderMetrics::HrGetLongestWait()\n");

hr = CHK_CFolderMetrics_HrGetLongestWait(liNow, liLongestWait);
if (FAILED(hr))
RETURN(hr);

    liLongestWait = 0;

    hr = m_lpTable->QueryRows(1, TBL_NOADVANCE, &lpRows);

    if (FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // If no entries, then quit.
    // -------------------------
    if (lpRows->cRows == 0)
    {
        goto cleanup;
    }

    // If number values differs from 1 then quit.
    // ------------------------------------------
    if (lpRows->aRow[0].cValues != 1)
    {
        ASSERTERROR(FALSE,"CFolderMetrics::rcGetLongestWait() Content "
        "table contains other than 1 Value type.");
        goto cleanup;
    }

    lpProps = lpRows->aRow[0].lpProps;

    ulType = PROP_TYPE(lpProps->ulPropTag);

    if (ulType!= PT_SYSTIME)
    {
        // OK, fake it
        liLongestWait = 0 ;
        goto cleanup ;
    }

    // If time is more recent than now then ignore it.
    // -----------------------------------------------
    liInFolderSince = INT64_FROM_FILETIME(lpProps->Value.ft);

    if (liInFolderSince < liNow)
    {
       liLongestWait = liNow - liInFolderSince;
    }

cleanup:

    FreeProws(lpRows);

    RETURN(hr);
}

//$--HrGetTotalWait-------------------------------------------------------------
//  Returns the sum of all of the waiting periods of each message in the folder.
// -----------------------------------------------------------------------------
HRESULT CFolderMetrics::HrGetTotalWait( // RETURNS: HRESULT
    IN __int64 liNow,                   // Current time expressed as a file time.
    OUT DWORD &cMessages,               // Receives number of messages in file.
    OUT __int64 &liTotalWait)           // Receives total wait of messages in file.
{
    HRESULT hr          = NOERROR;
    LONG  lRes          = 0;

    __int64  liInFolderSince = 0;

    LPSRowSet lpRows = NULL;
    LPSPropValue lpProps = NULL;

    DWORD i             = 0;
    DWORD cRows         = 0;

    DEBUGPRIVATE("CFolderMetrics::HrGetTotalWait()\n");

hr = CHK_CFolderMetrics_HrGetTotalWait(liNow, cMessages, liTotalWait);
if (FAILED(hr))
RETURN(hr);

    liInFolderSince = 0;
    liTotalWait = 0;
    cMessages = 0;

    // Read entries 100 at a time and calculate the total wait
    // from the in folder since time.
    // ---------------------------------------------------------
    do
    {
        hr = m_lpTable->QueryRows(100, 0, &lpRows);

        if (FAILED(hr) || (hr == MAPI_W_ERRORS_RETURNED))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        cMessages += lpRows->cRows;
        cRows = lpRows->cRows;

        for (i=0; i<cRows; i++)
        {
            // Get the property value from the row.
            // ------------------------------------
            if (lpRows->aRow[i].cValues != 1)
            {
                ASSERTERROR(FALSE,"Content table contains other than 1 "
                "Value type.");
                goto cleanup;
            }

            lpProps = lpRows->aRow[i].lpProps;
            if (PROP_TYPE(lpProps->ulPropTag) != PT_SYSTIME)
            {
                // ok, fake it.
                continue ;
            }

            // If folder submit time is more recent than
            // now, then don't include this item in average.
            // ----------------------------------------------
            liInFolderSince =  INT64_FROM_FILETIME(lpProps->Value.ft);
            if (liInFolderSince < liNow)
            {
                liTotalWait = liTotalWait + (liNow - liInFolderSince);
            }
        }

        FreeProws(lpRows);
        lpRows = NULL;
    }
    while (cRows != 0); // end of DO {  } WHILE () .

cleanup:

    FreeProws(lpRows);

    RETURN(hr);
}


FLDMTRC.H

// --fldmtrc.h------------------------------------------------------------------
//
//  Defines the folder metrics object.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef __fldmtrc_h
#define __fldmtrc_h

#include <windows.h>
#include <mapix.h>

// Flags for metric to calculate.
// ------------------------------
#define FM_CMESSAGES        0x00000001
#define FM_LONGEST_WAIT     0x00000002
#define FM_TOTAL_WAIT       0x00000004

//$--CFolderMetrics-------------------------------------------------------------
// Defines the folder metrics class; An object that is attached to a folder
// and can be queried for certain folder properties like size, length of
// wait of messages etc.
// -----------------------------------------------------------------------------
class CFolderMetrics
{
public:
    CFolderMetrics();
    virtual ~CFolderMetrics();
    HRESULT HrReset(IN LPMAPIFOLDER lpFolder);
    HRESULT HrGetFolderMetrics(IN __int64 liNow, IN DWORD fMetric, OUT DWORD &cMessages, OUT __int64 &liLongestWait, OUT __int64 &liTotalWait);

private:
    HRESULT HrGetcMessages(OUT DWORD &cMessages);
    HRESULT HrGetLongestWait(IN __int64 liNow, OUT __int64 &liLongestWait);
    HRESULT HrGetTotalWait(IN __int64 liNow, OUT DWORD &cMessages, OUT __int64 &liTotalWait);

    LPMAPIFOLDER m_lpFolder;
    LPMAPITABLE m_lpTable;

    static SPropTagArray m_sFolderContentCount;
    static SPropTagArray m_sInFolderSince;
    static SSortOrderSet m_sSortOrderSet;
};

#endif __fldmtrc_h


JSWINDOW.CPP

// --jswindow.cpp---------------------------------------------------------------
//
//  Implements a Jump Sliding window (jswindow) object.  The window collects
//  events and can return either an estimate or actual number of events
//  collected during the most recent window period.
//
//  The window contains a current register and a FIFO buffer.
//
//
//    |___|    |___|___|___|___|___|___| ... |___|___|
//   Current     1   2   3   4   5   6        i-1  i
//                      FIFO Buffer
//
//  The current register will collect events until the current interval has
//  passed. The this interval defines the resolution of the JSWindow.
//  A typical interval resolution is 10 seconds.
//
//  When the current register's interval has elapsed, it dumps its contents
//  into the FIFO buffer. In doing so it pushes the oldest contents out of
//  the end of the buffer.  The length of the FIFO buffer is called the
//  "duration" of the window and is expressed as the number of intervals.
//
//  A running total of all of the events contained within the FIFO buffer
//  is kept. Whenever new events are added to the buffer, they are also
//  added to the running total. When the old events are pushed out of the
//  end of the buffer, they are subtracted from the running total.
//
//  The running total is the number of events recorded in the last
//  "duration" of time.  ie.  If the duration time is 60 resolution
//  units, each resolution unit being 60 seconds long, then the
//  running total is the number of events per hour (60 * 60 = 3600 seconds).
//
//  You can query the window value using the GetValue() member function.
//  If the interval hasn't had time to fill (ie. if the interval is 1 hour and
//  you query it after 1 minute) then the ratio of dwFractionCompleteNum /
//  dwFractionCompleteDen will represent the fraction of the window
//  period that was filled when the GetValue request was made.
//
//  This code is NOT multithread safe.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "jswindow.h"
#include "jswindow.chk"

//$--CJSWindow::CJSWindow-------------------------------------------------------
//  Constructor
// -----------------------------------------------------------------------------
CJSWindow::CJSWindow()
{
    DEBUGPRIVATE("CJSWindow::CJSWindow\n");

}

//$--CJSWindow::~CJSWindow------------------------------------------------------
//  Destructor
// -----------------------------------------------------------------------------
CJSWindow::~CJSWindow()
{
    DEBUGPRIVATE("CJSWindow::~CJSWindow\n");

}

//$--CJSWindow::HrReset---------------------------------------------------------
//  Sets up the parameters of the JSWindow. This function must be called
//  after creating and before using the interval.
// -----------------------------------------------------------------------------
HRESULT CJSWindow::HrReset(        // RETURNS: HRESULT
    IN __int64 liNow,              // The current time in arbitrary units.
    IN __int64 liResolution,       // The resolution of the interval in arbitrary units.
    IN DWORD dwWindowDuration)     // The interval duration expressed in multiples of
                                   // resolution.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("CJSWindow::rcReset\n");

    hr = m_FIFO.HrReset(dwWindowDuration);

    m_liCurrentIntervalTimeStart = liNow;
    m_liResolution = liResolution;
    m_dwIntervalValue = 0;

    RETURN(hr);
}

//$--CJSWindow::AdvanceToNow----------------------------------------------------
//  Advances the JSWindow so that liNow is within the current active interval.
// -----------------------------------------------------------------------------
VOID CJSWindow::AdvanceToNow(       // RETURNS: nothing
    IN __int64 liNow,               // Current time.
    OUT DWORD &dwFIFOOut,           // Data pushed out of the FIFO buffer
OUT BOOL &fAdvanced)            // returns TRUE if a jump happened
{

    HRESULT hr = NOERROR;
    DWORD dwFIFOOut1    = 0;
    DWORD dwFIFOOut2    = 0;
    DWORD iDelta        = 0;
    __int64 liDeltaTime = 0;

    DEBUGPRIVATE("CJSWindow::AdvanceToNow\n");

fAdvanced = FALSE;

hr = CHK_CJSWindow_AdvanceToNow(liNow, dwFIFOOut, fAdvanced);
if (FAILED(hr))
return;

    ASSERTERROR(m_liResolution!=0,"CJSWindow::AdvanceToNow() member m_liResolution == 0");

    // Calculate the number of intervals that have passed since
    // the window was last updated and liNow.
    //
    // Find the difference between liNow and the last interval and divide
    // by the resolution.
    //
    // -----------------------------------------------------------------------------

    liDeltaTime = liNow - m_liCurrentIntervalTimeStart;
    iDelta = (DWORD)(liDeltaTime / m_liResolution);

    if (iDelta > 0)
    {
        fAdvanced = TRUE;
        m_FIFO.PushElement(m_dwIntervalValue, dwFIFOOut1);
        m_dwIntervalValue = 0;

        m_FIFO.PushNNulls(iDelta-1, dwFIFOOut2);
        m_liCurrentIntervalTimeStart = (m_liResolution * iDelta) + m_liCurrentIntervalTimeStart;
    }

    dwFIFOOut = dwFIFOOut1 + dwFIFOOut2;

    return;
}

//$--CJSWindowTotal::HrReset----------------------------------------------------
//  Sets up the parameters of the JSWindow. This function must be called
//  after creating and before using the interval.
// -----------------------------------------------------------------------------
HRESULT CJSWindowTotal::HrReset(   // RETURNS: HRESULT
    IN __int64 liNow,              // The current time in arbitrary units.
    IN __int64 liResolution,       // The resolution of the interval in arbitrary units.
    IN DWORD dwWindowDuration)     // The interval duration expressed in multiples of
                                   // resolution.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("CJSWindowTotal::HrReset()\n");

hr = CHK_CJSWindowTotal_HrReset(liNow, liResolution, dwWindowDuration);
if (FAILED(hr))
RETURN(hr);

    hr = CJSWindow::HrReset(liNow,liResolution,dwWindowDuration);

    m_dwWindowTotal = 0;

    RETURN(hr);
}

//$--CJSWindowTotal::DataPoint--------------------------------------------------
//  Used to add a count of events to the total window.
// -----------------------------------------------------------------------------
VOID CJSWindowTotal::DataPoint(
    IN __int64 liNow,           // Event time in arbitrary units.
    IN DWORD dwValue)           // Number of events to add for this time.
{
    HRESULT hr = NOERROR;
    DWORD dwFIFOIn  = 0;
    DWORD dwFIFOOut  = 0;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowTotal::DataPoint()\n");

hr = CHK_CJSWindowTotal_DataPoint(liNow, dwValue);
if (FAILED(hr))
return;

    dwFIFOIn = m_dwIntervalValue;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);
    if (fAdvanced)
    {
        m_dwWindowTotal += dwFIFOIn;
        m_dwWindowTotal -= dwFIFOOut;
    }

    m_dwIntervalValue += dwValue;
}

//$--CJSWindowTotal::HrGetValue-------------------------------------------------
//  Returns the total number of events in the window period. Also returns
//  the number of intervals that have been entered.
// -----------------------------------------------------------------------------
HRESULT CJSWindowTotal::HrGetValue(
    IN __int64 liNow,                   // Current time expressed as FILETIME.
    OUT DWORD &dwValue,                 // Receives window value.
    OUT DWORD &dwFractionCompleteNum,   // numerator
    OUT DWORD &dwFractionCompleteDen)   // denominator
{
    HRESULT hr          = NOERROR;
    DWORD dwFIFOOut     = 0;
    DWORD dwFIFOIn      = 0;
    BOOL fAdvanced      = FALSE;

    DEBUGPRIVATE("CJSWindowTotal::HrGetValue()\n");

hr = CHK_CJSWindowTotal_HrGetValue(
liNow,
dwValue,
dwFractionCompleteNum,
dwFractionCompleteDen);
if (FAILED(hr))
RETURN(hr);

    dwValue = 0;
    dwFractionCompleteNum = 0;
    dwFractionCompleteDen = 1;

    dwFIFOIn = m_dwIntervalValue;
    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);
    if (fAdvanced)
    {
        m_dwWindowTotal += dwFIFOIn;
        m_dwWindowTotal -= dwFIFOOut;
    }

    if (m_FIFO.dwNumberElements() != 0 )
    {
        dwValue =  m_dwWindowTotal;
        dwFractionCompleteNum = m_FIFO.dwNumberElements();
        dwFractionCompleteDen = m_FIFO.dwMaxNumberElements();
    }

    RETURN(hr);
}

//$--CJSWindowMin::DataPoint----------------------------------------------------
//  Used to add a count of events to the total window.
// -----------------------------------------------------------------------------
VOID CJSWindowMin::DataPoint(
    IN __int64 liNow,           // Event time in arbirary units.
    IN DWORD dwValue)           // Number of events to add for this time.
{
    HRESULT hr = NOERROR;
    DWORD dwFIFOOut;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindow::DataPoint()\n");

hr = CHK_CJSWindowMin_DataPoint(liNow, dwValue);
if (FAILED(hr))
return;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);

    if (m_dwIntervalValue > dwValue)
        m_dwIntervalValue = dwValue;
}

//$--CJSWindowMin::HrGetValue---------------------------------------------------
//  Returns the total number of events in the window period. Also returns
//  the number of intervals that have been entered.
// -----------------------------------------------------------------------------
HRESULT CJSWindowMin::HrGetValue(
    IN __int64 liNow,                   // Current time expressed as FILETIME.
    OUT DWORD &dwValue,                 // Receives window value.
    OUT DWORD &dwFractionCompleteNum,   // numerator
    OUT DWORD &dwFractionCompleteDen)   // denominator
{
    HRESULT hr = NOERROR;
    DWORD i = 0;
    DWORD dwFIFOOut =0;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowMin::HrGetValue()\n");

hr = CHK_CJSWindowMin_HrGetValue(
liNow,
dwValue,
dwFractionCompleteNum,
dwFractionCompleteDen);
if (FAILED(hr))
RETURN(hr);

    dwValue = 0;
    dwFractionCompleteNum = 0;
    dwFractionCompleteDen = 1;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);
    m_FIFO.Min(dwValue);

    dwFractionCompleteNum = m_FIFO.dwNumberElements();
    dwFractionCompleteDen = m_FIFO.dwMaxNumberElements();

    RETURN(hr);
}

//$--CJSWindowMax::DataPoint----------------------------------------------------
//  Used to add a count of events to the total window.
// -----------------------------------------------------------------------------
VOID CJSWindowMax::DataPoint(
    IN __int64 liNow,           // Event time in arbirary units.
    IN DWORD dwValue)           // Number of events to add for this time.
{
    HRESULT hr = NOERROR;
    DWORD dwFIFOOut = 0;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowMax::DataPoint()\n");

hr = CHK_CJSWindowMax_DataPoint(liNow, dwValue);
if (FAILED(hr))
return;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);

    if (m_dwIntervalValue < dwValue)
        m_dwIntervalValue = dwValue;
}

//$--CJSWindowMax::HrGetValue---------------------------------------------------
//  Returns the total number of events in the window period. Also returns
//  the number of intervals that have been entered.
// -----------------------------------------------------------------------------
HRESULT CJSWindowMax::HrGetValue(
    IN __int64 liNow,                  // Current time expressed as FILETIME.
    OUT DWORD &dwValue,                // Receives window value.
    OUT DWORD &dwFractionCompleteNum,  // Numerator of fraction completed.
    OUT DWORD &dwFractionCompleteDen)  // Denominator of fraction completed.
{
    HRESULT hr = NOERROR;
    DWORD i = 0;
    DWORD dwFIFOOut = 0;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowMax::HrGetValue()\n");

hr = CHK_CJSWindowMax_HrGetValue(
liNow,
dwValue,
dwFractionCompleteNum,
dwFractionCompleteDen);
if (FAILED(hr))
RETURN(hr);

    dwValue = 0;
    dwFractionCompleteNum = 0;
    dwFractionCompleteDen = 1;

    AdvanceToNow(liNow,dwFIFOOut, fAdvanced);

    m_FIFO.Max(dwValue);
    dwFractionCompleteNum = m_FIFO.dwNumberElements();
    dwFractionCompleteDen = m_FIFO.dwMaxNumberElements();

    RETURN(hr);
}

//$--CJSWindowAverage::HrReset----------------------------------------------------
//  Sets up the parameters of the JSWindow. This function must be called
//  after creating and before using the interval.
// -----------------------------------------------------------------------------
HRESULT CJSWindowAverage::HrReset( // RETURNS: HRESULT
    IN __int64 liNow,              // The current time in arbitrary units.
    IN __int64 liResolution,       // The resolution of the interval in arbitrary units.
    IN DWORD dwWindowDuration)     // The interval duration expressed in multiples of
                                   // resolution.
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("CJSWindowAverage::HrReset\n");

hr = CHK_CJSWindowAverage_HrReset(liNow, liResolution, dwWindowDuration);
if (FAILED(hr))
RETURN(hr);

    m_cIntervalValues = 0;
    m_dwIntervalTotal = 0;

    hr = CJSWindow::HrReset(liNow, liResolution, dwWindowDuration);
    RETURN(hr);
}

//$--CJSWindowAverage::DataPoint----------------------------------------------------
//  Used to add a count of events to the total window.
// -----------------------------------------------------------------------------
VOID CJSWindowAverage::DataPoint(
    IN __int64 liNow,             // Event time in arbirary units.
    IN DWORD dwValue)             // Number of events to add for this time.
{
    HRESULT hr = NOERROR;
    DWORD dwFIFOOut = 0;
    BOOL  fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowAverage::DataPoint()\n");

hr = CHK_CJSWindowAverage_DataPoint(liNow, dwValue);
if (FAILED(hr))
return;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);

    if (fAdvanced)
    {
        m_dwIntervalTotal = 0;
        m_cIntervalValues = 0;
    }

    m_dwIntervalTotal += dwValue;
    m_cIntervalValues ++;
    if (m_cIntervalValues > 0)
        m_dwIntervalValue = m_dwIntervalTotal / m_cIntervalValues;
    else
        m_dwIntervalValue = 0;

}

//$--CJSWindowAverage::HrGetValue-----------------------------------------------
//  Returns the total number of events in the window period. Also returns
//  the number of intervals that have been entered.
// -----------------------------------------------------------------------------
HRESULT CJSWindowAverage::HrGetValue(
    IN __int64 liNow,                   // Current time expressed as FILETIME.
    OUT DWORD &dwValue,                 // Receives window value.
    OUT DWORD &dwFractionCompleteNum,   // numerator
    OUT DWORD &dwFractionCompleteDen)   // denominator
{
    HRESULT hr = NOERROR;
    DWORD dwSum = 0;
    DWORD i = 0;
    DWORD dwFIFOOut = 0;
    BOOL fAdvanced = FALSE;

    DEBUGPRIVATE("CJSWindowAverage::HrGetValue()\n");

hr = CHK_CJSWindowAverage_HrGetValue(
liNow,
dwValue,
dwFractionCompleteNum,
dwFractionCompleteDen);
if (FAILED(hr))
RETURN(hr);

    dwValue = 0;
    dwFractionCompleteNum = 0;
    dwFractionCompleteDen = 1;

    AdvanceToNow(liNow, dwFIFOOut, fAdvanced);

    m_FIFO.Average(dwValue);
    dwFractionCompleteNum = m_FIFO.dwNumberElements();
    dwFractionCompleteDen = m_FIFO.dwMaxNumberElements();

    RETURN(hr);
}


JSWINDOW.H

// --jswindow.h-----------------------------------------------------------------
//
//  Defines Jump Sliding Window object.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef __jswindow_h
#define __jswindow_h

#include <windows.h>
#include "fifo.h"

//$--CJSWindow------------------------------------------------------------------
// Defines the Jump Slide base class. Other kinds of Jump Slide windows
// are derived from this class.
// -----------------------------------------------------------------------------
class CJSWindow
{
public:
    CJSWindow ();
    virtual ~CJSWindow ();
    virtual HRESULT HrReset(IN __int64 liNow, IN __int64 liResolution, IN DWORD dwWindowDuration);
    virtual VOID DataPoint(IN __int64 liNow, IN DWORD dwValue) = 0;
    virtual HRESULT HrGetValue(IN __int64 liNow, OUT DWORD &dwValue, DWORD &dwFractionCompleteNum, DWORD &dwFractionCompleteDen) = 0;

    VOID AdvanceToNow(IN __int64 liNow, OUT DWORD &dwFIFOOut, OUT BOOL &fAdvanced);

    __int64 m_liCurrentIntervalTimeStart;
    __int64 m_liResolution;
    CFIFO m_FIFO;
    DWORD m_dwIntervalValue;
};

//$--CJSWindowMin---------------------------------------------------------------
// Defines the Jump Slide window that determines the minimum value for a period.
// Derived from the CJSWindow Base Class.
// -----------------------------------------------------------------------------
class CJSWindowMin : public CJSWindow
{
public:
    VOID DataPoint(IN __int64 liNow, IN DWORD dwValue);
    HRESULT HrGetValue(IN __int64 liNow, OUT DWORD &dwValue, DWORD &dwFractionCompleteNum, OUT DWORD &dwFractionCompleteDen);
};

//$--CJSWindowMax---------------------------------------------------------------
// Defines the Jump Slide window that determines the maximum value for a period.
// Derived from the CJSWindow Base Class.
// -----------------------------------------------------------------------------
class CJSWindowMax : public CJSWindow
{
public:
    VOID DataPoint(IN __int64 liNow, IN DWORD dwValue);
    HRESULT HrGetValue(IN __int64 liNow, OUT DWORD &dwValue, DWORD &dwFractionCompleteNum, DWORD &dwFractionCompleteDen);
};

//$--CJSWindowAverage------------------------------------------------------------
// Defines the Jump Slide window that determines the average value for a period.
// Derived from the CJSWindow Base Class.
// -----------------------------------------------------------------------------
class CJSWindowAverage : public CJSWindow
{
public:
    HRESULT HrReset(IN __int64 liNow, IN __int64 liResolution, IN DWORD dwWindowDuration);
    VOID DataPoint(IN __int64 liNow, IN DWORD dwValue);
    HRESULT HrGetValue(IN __int64 liNow, OUT DWORD &dwValue, DWORD &dwFractionCompleteNum, DWORD &dwFractionCompleteDen);

private:
    DWORD m_dwIntervalTotal;
    DWORD m_cIntervalValues;
};

//$--CJSWindowTotal--------------------------------------------------------------
// Defines the Jump Slide window that determines the total value for a period.
// Derived from the CJSWindow Base Class.
// -----------------------------------------------------------------------------
class CJSWindowTotal : public CJSWindow
{
public:
    HRESULT HrReset(IN __int64 liNow, IN __int64 liResolution, IN DWORD dwWindowDuration);
    VOID DataPoint(IN __int64 liNow, IN DWORD dwValue);
    HRESULT HrGetValue(IN __int64 liNow, OUT DWORD &dwValue, OUT DWORD &dwFractionCompleteNum, OUT DWORD &dwFractionCompleteDen);

private:
    DWORD m_dwWindowTotal;
};

#endif


STATS.CPP

// --stats.cpp------------------------------------------------------------------
//
//  C wrappers for the C++ stats library.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "fldmtrc.h"
#include "jswindow.h"
#include "stats.chk"

extern "C" {

#define INT64_FROM_FILETIME(li) (*((__int64 *)&(li)))
#define FILETIME_FROM_INT64(li) (*((FILETIME *)&(li)))

//$--HrOpenFolderMetrics--------------------------------------------------------
//  Prepares a folder for retreiving metrics.
// -----------------------------------------------------------------------------
HRESULT HrOpenFolderMetrics(            // RETURNS: HRESULT
    IN LPMAPIFOLDER lpFolder,           // pointer to target mapi folder
    OUT HFLDMTRC *phFolderMetrics)      // receives handle to folder metric
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrOpenFolderMetrics()\n");

hr = CHK_HrOpenFolderMetrics(lpFolder, phFolderMetrics);
if (FAILED(hr))
RETURN(hr);

    if (phFolderMetrics == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *phFolderMetrics = (HFLDMTRC *)new CFolderMetrics();

    if (*phFolderMetrics == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = ((CFolderMetrics *)*phFolderMetrics)->HrReset(lpFolder);

    if (FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if ((FAILED(hr)) && (*phFolderMetrics != NULL))
    {
        delete (CFolderMetrics *)*phFolderMetrics;

        *phFolderMetrics = NULL;
    }

    RETURN(hr);
}

//$--HrGetFolderMetrics---------------------------------------------------------
//  Retrieves the metrics (size, longest wait, total wait) from a folder.
// -----------------------------------------------------------------------------
HRESULT HrGetFolderMetrics(             // RETURNS: HRESULT
    IN HFLDMTRC hFolderMetrics,         // handle created from rcOpenFolderMetrics
    IN FILETIME *pftNow,                // Current time expressed as FILETIME
    IN DWORD fMetric,                   // Flags for metric requested. May
                                        //  be or'ed combination of:
                                        //     FM_CMESSAGES
                                        //     FM_LONGEST_WAIT
                                        //     FM_TOTAL_WAIT
    OUT DWORD *pcMessages,              // If requested, recieves the number
                                        // of messages in folder. 0 otherwise
    OUT FILETIME *pftLongestWait,       // If requested, receives the longest
                                        // wait of messages currently in folder.
    OUT FILETIME *pftTotalWait)         // If requested, receives the total
                                        // wait of all messages in folder.
{

    HRESULT hr              = NOERROR;
    __int64 liNow           = 0;

    __int64 liLongestWait   = 0;
    __int64 liTotalWait     = 0;

    DEBUGPRIVATE("HrGetFolderMetrics()\n");

hr = CHK_HrGetFolderMetrics(
hFolderMetrics,
pftNow,
fMetric,
pcMessages,
pftLongestWait,
pftTotalWait);
if (FAILED(hr))
RETURN(hr);

    liNow = INT64_FROM_FILETIME(*pftNow);

    hr = ((CFolderMetrics *)hFolderMetrics)->HrGetFolderMetrics(
    liNow, fMetric, *pcMessages, liLongestWait, liTotalWait);

if (SUCCEEDED(hr))
{
*pftLongestWait = FILETIME_FROM_INT64(liLongestWait);
*pftTotalWait = FILETIME_FROM_INT64(liTotalWait);
}

    RETURN(hr);
}

//$--CloseFolderMetrics---------------------------------------------------------
//  Releases resources allocated in OpenFolderMetric.
// -----------------------------------------------------------------------------
VOID CloseFolderMetrics(                // RETURNS: nothing
    IN HFLDMTRC hFolderMetrics)         // handle generated by OpenFolderMetric
{
    HRESULT hr              = NOERROR;

    DEBUGPRIVATE("CloseFolderMetrics()\n");

hr = CHK_CloseFolderMetrics(hFolderMetrics);
if (FAILED(hr))
return;

    delete (CFolderMetrics *) hFolderMetrics;
}

//$--HrJSOpen-------------------------------------------------------------
//  Opens a JumpSlide windows.
// -----------------------------------------------------------------------------
HRESULT HrJSOpen(         // RETURNS: HRESULT
    IN JSWINDOWTYPE enType,     // The type of window to open
    IN FILETIME *pftNow,        // The current time expressed as a FILETIME
    IN FILETIME *pftResolution, // The resolution of the window. The
                                // window will jump in intervals of this
                                // size.
    IN DWORD dwWindowDuration,  // Duration of window expressed in resoution
                                // units.
    OUT HJSWINDOW *phJSWindow)  // Receives handle to newly opened window.
{
    HRESULT hr              = NOERROR;
    __int64 liNow           = 0;
    __int64 liResolution    = 0;

    DEBUGPUBLIC("HrJSOpen()\n");

hr = CHK_HrJSOpen(
enType,
pftNow,
pftResolution,
dwWindowDuration,
phJSWindow);
if (FAILED(hr))
RETURN(hr);

    switch (enType)
    {
        case (JSWINDOW_MIN) :
            *phJSWindow = (HJSWINDOW) new CJSWindowMin();
            break;
        case (JSWINDOW_MAX) :
            *phJSWindow = (HJSWINDOW) new CJSWindowMax();
            break;
        case (JSWINDOW_AVERAGE) :
            *phJSWindow = (HJSWINDOW) new CJSWindowAverage();
            break;
        case (JSWINDOW_TOTAL_RATE) :
            *phJSWindow = (HJSWINDOW) new CJSWindowTotal();
            break;
        default:
            *phJSWindow = NULL;
    }

    if (*phJSWindow == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    liNow = INT64_FROM_FILETIME(*pftNow);
    liResolution = INT64_FROM_FILETIME(*pftResolution);

    hr = ((CJSWindow *)(*phJSWindow))->HrReset(
    liNow, liResolution, dwWindowDuration);

    if (FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    if ((FAILED(hr)) && (*phJSWindow!=NULL))
    {
        delete (CJSWindow *) *phJSWindow;
        *phJSWindow = NULL;
    }

    RETURN(hr);
}

//$--HrJSCollectDataPoint-------------------------------------------------
//  Places a new data point into the window.
// -----------------------------------------------------------------------------
HRESULT HrJSCollectDataPoint(     // RETURNS: HRESULT
    IN HJSWINDOW hJSWindow,             // Handle to TARGET JSWindow
    IN FILETIME *pftNow,                // Current time expressed as FILETIME
    IN DWORD dwValue)                   // Value of data point to add
{
    HRESULT hr      = NOERROR;
    __int64 liNow   = 0;

    DEBUGPRIVATE("HrJSCollectDataPoint()\n");

hr = CHK_HrJSCollectDataPoint(hJSWindow, pftNow, dwValue);
if (FAILED(hr))
RETURN(hr);

    liNow = INT64_FROM_FILETIME(*pftNow);

    ((CJSWindow *)hJSWindow)->DataPoint(liNow, dwValue);

    RETURN(hr);
}

//$--HrJSGetValue---------------------------------------------------------
//  Retrieves and interval value from a window.
//
//  If the window has not had sufficient time to fill, an estimate of a
//  filled window can be made by normalizing the return value. The normalizing
//  fraction is returned in the parameters pdwFractionCompleteNum,
//  and pdwFractionCompleteDen.
//
//  Querying the window value after it has had time to fill will always
//  return a fraction complete of 1.
//
//  (ie. *pdwFractionCompleteNum/*pdwFractionCompleteDen == 1)
//
//  Example:
//  A JSWindow of TOTAL type with a duration of 1 hour.
//  The window has only been gathering Data points for 30 minutes.
//  Calling JSWindowGetValue() will return the total of the data points
//  in the last 30 minutes and return a fraction complete of 1/2.
//
//  The calling program can turn the returned value into an estimate of
//  total of data points over the last period if it normalizes the value
//  by the fraction complete.  In the above example multiplying the returned
//  value by 2 will generate the hourly estimate.
//
// -----------------------------------------------------------------------------
HRESULT HrJSGetValue(             // RETURNS: HRESULT
    IN HJSWINDOW hJSWindow,             // Hand of window.
    IN FILETIME *pftNow,                // Current time expressed as FILETIME
    OUT DWORD *pdwWindowValue,          // Window value
    OUT DWORD *pdwFractionCompleteNum,  // Fraction of window that was filled
                                        // numerator.
    OUT DWORD *pdwFractionCompleteDen)  // Fraction of window that was filled
                                        // denominator.
{
    HRESULT hr      = NOERROR;
    __int64 liNow   = 0;

    DEBUGPUBLIC("HrJSGetValue()\n");

hr = CHK_HrJSGetValue(
hJSWindow,
pftNow,
pdwWindowValue,
pdwFractionCompleteNum,
pdwFractionCompleteDen);
if (FAILED(hr))
RETURN(hr);

    liNow = INT64_FROM_FILETIME(*pftNow);

    hr = ((CJSWindow *)hJSWindow)->HrGetValue(liNow, *pdwWindowValue,
    *pdwFractionCompleteNum, *pdwFractionCompleteDen);

    if (FAILED(hr))
    {
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--JSClose--------------------------------------------------------------
//  Closes and destroys a Jump Slide Window.
// -----------------------------------------------------------------------------
VOID JSClose(                     // RETURNS: nothing
    IN HJSWINDOW hJSWindow)             // handle of window to close
{
    HRESULT hr      = NOERROR;

    DEBUGPRIVATE("JSClose()\n");

hr = CHK_JSClose(hJSWindow);
if (FAILED(hr))
return;

    delete (CJSWindow *)hJSWindow;
}

//$--HrFileTimeToSeconds--------------------------------------------------------
//  Converts a FILETIME to the number of seconds.  The FILETIME may
//  represent the number of seconds larger than can be represenred in
//  a DWORD.  In that case, this function will return E_FAIL.
// -----------------------------------------------------------------------------
HRESULT HrFileTimeToSeconds(            // RETURNS: HRESULT
    IN FILETIME *pft,                   // file time to convert
    OUT DWORD *pdwSec)                  // receives time in seconds (0 if error)
{
    HRESULT hr      = NOERROR;
    __int64 lift    = 0;
    __int64 liSec   = 0;

    DEBUGPUBLIC("HrFileTimeToSeconds()\n");

hr = CHK_HrFileTimeToSeconds(pft, pdwSec);
if (FAILED(hr))
RETURN(hr);

    *pdwSec = 0;

    lift = INT64_FROM_FILETIME(*pft);

    liSec = lift / (DWORD) 10000000L;

    if (FILETIME_FROM_INT64(liSec).dwHighDateTime != 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *pdwSec = (DWORD)liSec;

cleanup:

    RETURN(hr);
}

}  // extern "C"


WINWRAP.C

// --winwrap.c------------------------------------------------------------------
//
//  Windows NT service (Windows application) shell for EDK applications.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include <stdlib.h>

static struct _tparam
{
    LPLONG                 lplStartThreads;
    LPLONG                 lplStopThreads;
    LPTHREAD_START_ROUTINE lpStartAddress;
    LPVOID                 lpParameter;
};

#include "winwrap.chk"

#define DEFAULT_WAIT_HINT   30000

#define SERVICE_NAME        "WINWRAP"

#define WAIT_DESTROY        120000

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

static BOOL                    fIsService                = TRUE;

static DWORD                   dwStartWaitHint           = DEFAULT_WAIT_HINT;
static DWORD                   dwStartCheckPoint         = 0;

static DWORD                   dwPauseWaitHint           = DEFAULT_WAIT_HINT;
static DWORD                   dwPauseCheckPoint         = 0;

static DWORD                   dwContinueWaitHint        = DEFAULT_WAIT_HINT;
static DWORD                   dwContinueCheckPoint      = 0;

static DWORD                   dwStopWaitHint            = DEFAULT_WAIT_HINT;
static DWORD                   dwStopCheckPoint          = 0;

static HINSTANCE               hInst                     = NULL;

static HANDLE                  hServiceStopEvent         = NULL;
static HANDLE                  hServicePauseEvent        = NULL;
static HANDLE                  hServiceContinueEvent     = NULL;

static HANDLE                  hStopConfirmEvent         = NULL;

static SERVICE_STATUS          ssStatus                  = {0};

static SERVICE_STATUS_HANDLE   sshStatusHandle           = 0;

static HWND                    hAppWnd                   = NULL;

static DWORD                   dwWorkerTID               = 0;
static HANDLE                  hWorkerThread             = NULL;

static DWORD                   dwShutdownTID             = 0;
static HANDLE                  hShutdownThread           = NULL;

static DWORD                   dwServiceTID              = 0;
static HANDLE                  hServiceThread            = NULL;

// -----------------------------------------------------------------------------

static DWORD                   dwServiceArgc             = 0;
static LPSTR                  *lpszServiceArgv          = NULL;

static CHAR                   szServiceName[MAX_SERVICE_NAME_LENGTH+1] = {0};

static DWORD                   dwSvcWin32ExitCode           = 0;
static DWORD                   dwSvcServiceSpecificExitCode = 0;

static struct _WinMainContext
{
    HINSTANCE   hInstance;
    HINSTANCE   hPrevInstance;
    LPSTR       lpszCmdLine;
    int         nCmdShow;
}   WinMainContext;

//$--FIsService-----------------------------------------------------------------
//  Returns TRUE if the application is running as an NT service.
// -----------------------------------------------------------------------------
BOOL FIsService(                        // RETURNS: TRUE if service
    VOID)                               // no arguments
{
    return(fIsService);
}

//$--GetServiceStopEvent-------------------------------------------------------
//  Returns the handle for the service stop event.
// -----------------------------------------------------------------------------
HANDLE GetServiceStopEvent(            // RETURNS: handle to stop event
    VOID)                               // no arguments
{
    return(hServiceStopEvent);
}

//$--GetServiceInstance--------------------------------------------------------
//  Returns the handle for the service instance.
// -----------------------------------------------------------------------------
HANDLE GetServiceInstance(             // RETURNS: handle to service instance
    VOID)                               // no arguments
{
    return(hInst);
}

//$--FnCreateThread-------------------------------------------------------------
//  Wrapper for CreateThread() function.
// -----------------------------------------------------------------------------
static VOID FnCreateThread(             // RETURNS: nothing
    IN LPVOID lpParameter)              // parameter
{
    HRESULT hr      = NOERROR;
    HGLOBAL hglobal = NULL;

    struct _tparam *lpContext = NULL;

    DEBUGPRIVATE("FnCreateThread()\n");

    hr = CHK_HrFnCreateThread(
        lpParameter);

    if(FAILED(hr))
        return;

    lpContext = (struct _tparam *)lpParameter;

    InterlockedIncrement(lpContext->lplStartThreads);

    __try
    {
        (*(lpContext->lpStartAddress))(lpContext->lpParameter);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);
    }

    InterlockedIncrement(lpContext->lplStopThreads);

    __try
    {
        hglobal = GlobalFree(lpContext);

        if(hglobal != NULL)
        {
            hr = HR_LOG(E_FAIL);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(_exception_code());

        hr = HR_LOG(E_FAIL);
    }
}

//$--HServiceCreateThread-----------------------------------------------------------
//  Create a wrapped thread.
// -----------------------------------------------------------------------------
HANDLE HServiceCreateThread(                            // RETURNS: handle
    IN  LPLONG lplStartThreads,                     // number of running threads
    IN  LPLONG lplStopThreads,                      // number of stopped threads
    IN  LPSECURITY_ATTRIBUTES lpThreadAttributes,   // thread attributes
    IN  DWORD dwStackSize,                          // stack size
    IN  LPTHREAD_START_ROUTINE lpStartAddress,      // start address
    IN  LPVOID lpParameter,                         // parameter
    IN  DWORD dwCreationFlags,                      // creation flags
    OUT LPDWORD lpThreadId)                         // thread ID
{
    HRESULT        hr         = NOERROR;
    HGLOBAL        hglobal    = NULL;
    HANDLE         handle     = NULL;
    struct _tparam *lpContext = NULL;

    DEBUGPUBLIC("HServiceCreateThread()\n");

    SetLastError(ERROR_SUCCESS);

    hr = CHK_HrEDKCreateThread(
        lplStartThreads,
        lplStopThreads,
        lpThreadAttributes,
        dwStackSize,
        lpStartAddress,
        lpParameter,
        dwCreationFlags,
        lpThreadId);

    if(FAILED(hr))
        return(NULL);

    lpContext = (struct _tparam *)GlobalAlloc(GPTR, sizeof(struct _tparam));

    if(lpContext == NULL)
    {
        hr = HR_LOG(E_FAIL);

        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        goto cleanup;
    }

    lpContext->lplStartThreads = lplStartThreads;
    lpContext->lplStopThreads  = lplStopThreads;
    lpContext->lpStartAddress  = lpStartAddress;
    lpContext->lpParameter     = lpParameter;

    handle = CreateThread(
        lpThreadAttributes,
        dwStackSize,
        (LPTHREAD_START_ROUTINE)FnCreateThread,
        (LPVOID)lpContext,
        dwCreationFlags,
        lpThreadId);

cleanup:

    if((handle == NULL) && (lpContext != NULL))
    {
        hr = HR_LOG(E_FAIL);

        __try
        {
            hglobal = GlobalFree(lpContext);

            if(hglobal != NULL)
            {
                hr = HR_LOG(E_FAIL);
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            SetLastError(_exception_code());

            hr = HR_LOG(E_FAIL);
        }
    }

    return(handle);
}

//$--HrServiceGetName--------------------------------------------------------
//  The HrServiceGetName function can be used by any thread to get the
//  service name after the service has been started. lpszName must point to a
//  block of memory at least MAX_SERVICE_NAME_LENGTH+1 bytes in length.
// -----------------------------------------------------------------------------
HRESULT HrServiceGetName(            // RETURNS: return code
    OUT LPSTR lpszName)                // pointer to service name buffer
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrServiceGetName()\n");

    hr = CHK_HrServiceGetName(
        lpszName);

    if(FAILED(hr))
        RETURN(hr);

    lstrcpyn(lpszName, szServiceName, MAX_SERVICE_NAME_LENGTH+1);

    lpszName[MAX_SERVICE_NAME_LENGTH] = 0;

    RETURN(hr);
}

//$--HrServiceGetArgv--------------------------------------------------------
//  The HrServiceGetArgv function can be used by any thread to get the
//  service argv[] after the service has been started.
// -----------------------------------------------------------------------------
HRESULT HrServiceGetArgv(            // RETURNS: return code
    OUT DWORD  *lpdwArgc,               // pointer to argc address variable
    OUT LPSTR **lppszArgv)             // pointer to argv[] address variable
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrServiceGetArgv()\n");

    hr = CHK_HrServiceGetArgv(
        lpdwArgc,
        lppszArgv);

    if(FAILED(hr))
        RETURN(hr);

    *lppszArgv = lpszServiceArgv;
    *lpdwArgc  = dwServiceArgc;

    RETURN(hr);
}

//$--WaitShutdown---------------------------------------------------------------
//  Thread that will send WM_DESTROY once hShutdownEvent is signalled.
//------------------------------------------------------------------------------
void WaitShutdown(                  // RETURNS: nothing
    IN HANDLE hShutdownEvent)       // Handle to Shutdown event object
{
    HRESULT hr = NOERROR;
    DWORD   dw = 0;

    DEBUGPUBLIC("WaitShutdown()\n");

    hr = CHK_HrWaitShutdown(
        hShutdownEvent);

    if(FAILED(hr))
        return;

    if(hAppWnd == NULL)
    {
        hr = HR_LOG(E_FAIL);
        return;
    }

    dw = WaitForSingleObject(hShutdownEvent, INFINITE);

    if((dw != WAIT_OBJECT_0) && (dw != WAIT_ABANDONED))
    {
        hr = HR_LOG(E_FAIL);
    }

    if(hAppWnd != NULL)
    {
        PostMessage(hAppWnd, WM_DESTROY, 0, 0);
    }

    ExitThread(0);

    return;
}

//$--fInitApplication-----------------------------------------------------------
//  Initializes window data and registers window class
// -----------------------------------------------------------------------------
BOOL fInitApplication(              // RETURNS: TRUE if successful
    IN HINSTANCE hInstance)         // Handle to instance of application
{
    WNDCLASS wc = {0};

    DEBUGPUBLIC("fInitApplication()\n");

    // Fill in window class structure with parameters that describe the
    // main window.

    wc.style         = CS_HREDRAW | CS_VREDRAW; // Class style(s).
    wc.lpfnWndProc   = (WNDPROC)WndProc;        // Window Procedure
    wc.cbClsExtra    = 0;                       // No per-class extra data.
    wc.cbWndExtra    = 0;                       // No per-window extra data.
    wc.hInstance     = hInstance;               // Owner of this class
    wc.hIcon         = LoadIcon(hInstance, IDI_APPLICATION);   // Icon
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);            // Cursor
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);// Default color
    wc.lpszMenuName  = NULL;                    // Menu name
    wc.lpszClassName = szAppName;               // Name to register as

    if(wc.hIcon == NULL)
    {
        wc.hIcon     = LoadIcon(NULL, IDI_APPLICATION);        // Icon
    }

    // Register the window class and return success/failure code.

    return(RegisterClass(&wc));
}

//$--fInitInstance--------------------------------------------------------------
//  Saves instance handle and creates main window
// -----------------------------------------------------------------------------
BOOL fInitInstance(                 // RETURNS: TRUE if successful
    IN HINSTANCE hInstance,         // handle to instance of application
    IN int       nCmdShow)          // argument for ShowWindow()
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("fInitInstance()\n");

    // Save the instance handle in static variable, which will be used in
    // many subsequence calls from this application to Windows.

    hInst = hInstance; // Store instance handle in our global variable

    // Create a main window for this application instance.

    hAppWnd = CreateWindow(
        szAppName,                  // See RegisterClass() call.
        szWindowTitle,              // Text for window title bar.
        WS_MINIMIZE |
        WS_POPUP |
        WS_SYSMENU |
        WS_VISIBLE,                 // Window style.
        CW_USEDEFAULT, 0,           // Use default positioning
        CW_USEDEFAULT, 0,           // Use default positioning
        NULL,                       // Overlapped windows have no parent.
        NULL,                       // Use the window class menu.
        hInstance,                  // This instance owns this window.
        NULL                        // We don't use any data in our WM_CREATE
    );

    //
    // If window could not be created, return "failure"
    //

    if(hAppWnd == NULL)
    {
        hr = HR_LOG(E_FAIL);

        return(FALSE);
    }

    if(fIsService == FALSE)
    {
        //
        // Make the window visible; update its client area; and return "success"
        //

        ShowWindow(hAppWnd, nCmdShow);  // Show the window
        UpdateWindow(hAppWnd);          // Sends WM_PAINT message
    }

    return(TRUE);
}

//$--WorkerThread---------------------------------------------------------------
//  This function does the actual nuts and bolts work that
//  the service requires.  It will also Pause or Stop when
//  asked by the ServiceCtrlProc function.
// -----------------------------------------------------------------------------
VOID WorkerThread(                  // RETURNS: nothing
    IN HANDLE hShutdownEvent)       // service shutdown event handle
{
    HRESULT   hr            = NOERROR;
    HINSTANCE hInstance     = NULL;
    HINSTANCE hPrevInstance = NULL;
    LPSTR     lpszCmdLine   = NULL;
    int       nCmdShow      = 0;
    MSG       msg           = {0};
    BOOL      fT            = FALSE;

    DEBUGPUBLIC("WorkerThread()\n");

    hInstance     = WinMainContext.hInstance;
    hPrevInstance = WinMainContext.hPrevInstance;
    lpszCmdLine   = WinMainContext.lpszCmdLine;
    nCmdShow      = WinMainContext.nCmdShow;

    hr = CHK_HrWorkerThread(
        hShutdownEvent);

    if(FAILED(hr))
        return;

    // Are there other instances of the app running?
    if(hPrevInstance == NULL)
    {// YES, so initialize shared things.
        if(fInitApplication(hInstance) == FALSE)
        {
            hr = HR_LOG(E_FAIL);

            ServiceStop();

            goto cleanup;
        }
    }

    // Perform initializations that apply to a specific instance
    if(fInitInstance(hInstance, nCmdShow) == FALSE)
    {
        hr = HR_LOG(E_FAIL);

        ServiceStop();

        goto cleanup;
    }

    hr = HrServiceStartup(hInstance, hPrevInstance, hAppWnd, lpszCmdLine);

    if(FAILED(hr))
    {
        ServiceStop();

        goto cleanup;
    }

    //
    // Create a thread that waits for the shut down event to trigger.
    //

    hShutdownThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)WaitShutdown,
        (LPVOID)hShutdownEvent,
        0,
        &dwShutdownTID);

    if(hShutdownThread == NULL)
    {
        hr = HR_LOG(E_FAIL);

        ServiceStop();

        //
        // The service thread has not started yet so just shut down.
        //

        (void)HrServiceShutdown();

        goto cleanup;
    }

    // Start a thread that executes the ServiceMain function.
    hServiceThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)ServiceMain,
        (LPVOID)hShutdownEvent,
        0,
        &dwServiceTID);

    if(hServiceThread == NULL)
    {
        hr = HR_LOG(E_FAIL);

        ServiceStop();
    }

    // Acquire and dispatch messages until a WM_QUIT message is received.
    while (GetMessage(
        &msg,   // message structure
        NULL,   // handle of window receiving the message
        0,      // lowest message to examine
        0))     // highest message to examine
    {
        TranslateMessage(&msg);// Translates virtual key codes
        DispatchMessage(&msg); // Dispatches message to window
    }

    hr = NOERROR;

cleanup:

    if(FAILED(hr))
        SetServiceExitCode( ERROR_SERVICE_LOGON_FAILED, NOERROR);

    CLOSEHANDLE(hShutdownThread);

    ExitThread(0);

    return;
}

//$--SzMakeServiceName----------------------------------------------------------
//  Make a service name from the EXE name.
// -----------------------------------------------------------------------------
static LPSTR SzMakeServiceName(        // RETURNS: name of service
    IN LPSTR lpszExe)                  // executable name
{
    LPSTR lpszCurr    = NULL;
    LPSTR lpszName    = NULL;

    if(lpszExe == NULL)
    {
        return(NULL);
    }

    lpszName = lpszExe + lstrlen(lpszExe);

    while(lpszName != lpszExe)
    {
        //
        // Check if character is a delimiter
        //

        lpszCurr = ":\\";

        while(*lpszCurr)
        {
            if(*lpszName == *lpszCurr)
            {
                //
                // Found a delimiter
                //

                goto cleanup;
            }

            lpszCurr++;
        }

        //
        // Remove file extension
        //

        if(*lpszName == '.')
        {
            *lpszName = 0;
        }

        lpszName--;
    }

cleanup:

    if(lpszName != lpszExe)
    {
        //
        // Advance past delimiter
        //

        lpszName++;
    }

    return(lpszName);
}

//$--dwDoServiceCtrlPause-------------------------------------------------------
//  This function is called by ServiceCtrl whenever
//  someone calls ControlService to pause our service.
// -----------------------------------------------------------------------------
static DWORD dwDoServiceCtrlPause(         // RETURNS: service state
    VOID)                           // no arguments
{
    HRESULT hr       = NOERROR;
    DWORD   dwState  = SERVICE_RUNNING;
    DWORD   dwStatus = 0;
    BOOL    fStatus  = TRUE;

    DEBUGPUBLIC("dwDoServiceCtrlPause()\n");

    dwState = ssStatus.dwCurrentState;

    if(ssStatus.dwCurrentState == SERVICE_RUNNING)
    {
        // Report the status, specifying the checkpoint and wait-hint,
        // before setting the continue event.

        dwState = SERVICE_PAUSED;

        dwPauseCheckPoint++;

        FServiceReportStatus(
            dwState,              // current state
            NOERROR,              // exit code
            NOERROR,              // exit code
            dwPauseCheckPoint,    // checkpoint
            dwPauseWaitHint);     // wait hint

        if((hServicePauseEvent != NULL) && (hServiceContinueEvent != NULL))
        {
            fStatus = ResetEvent(hServiceContinueEvent);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
            }

            fStatus = SetEvent(hServicePauseEvent);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
            }
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    return(dwState);
}

//$--dwDoServiceCtrlContinue----------------------------------------------------
//  This function is called by ServiceCtrl whenever
//  someone calls ControlService to continue our service.
// -----------------------------------------------------------------------------
static DWORD dwDoServiceCtrlContinue(      // RETURNS: service state
    VOID)                           // no arguments
{
    HRESULT hr       = NOERROR;
    DWORD   dwState  = SERVICE_RUNNING;
    DWORD   dwStatus = 0;
    BOOL    fStatus  = TRUE;

    DEBUGPUBLIC("dwDoServiceCtrlContinue()\n");

    dwState = ssStatus.dwCurrentState;

    if(ssStatus.dwCurrentState == SERVICE_PAUSED)
    {
        // Report the status, specifying the checkpoint and wait-hint,
        // before setting the continue event.

        dwState = SERVICE_RUNNING;

        dwContinueCheckPoint++;

        FServiceReportStatus(
            dwState,              // current state
            NOERROR,              // exit code
            NOERROR,              // exit code
            dwContinueCheckPoint, // checkpoint
            dwContinueWaitHint);  // wait hint

        if((hServicePauseEvent != NULL) && (hServiceContinueEvent != NULL))
        {
            fStatus = ResetEvent(hServicePauseEvent);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
            }

            fStatus = SetEvent(hServiceContinueEvent);

            if(fStatus == FALSE)
            {
                hr = HR_LOG(E_FAIL);
            }
        }
        else
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    return(dwState);
}

//$--dwDoServiceCtrlStop--------------------------------------------------------
//  This function is called by ServiceCtrl whenever
//  someone calls ControlService to stop our service.
// -----------------------------------------------------------------------------
static DWORD dwDoServiceCtrlStop(          // RETURNS: service state
    VOID)                           // no arguments
{
    HRESULT hr       = NOERROR;
    DWORD   dwState  = SERVICE_RUNNING;
    DWORD   dwStatus = 0;
    BOOL    fStatus  = TRUE;

    DEBUGPUBLIC("dwDoServiceCtrlStop()\n");

    dwState = SERVICE_STOP_PENDING;

    // Report the status, specifying the checkpoint and wait-hint,
    // before setting the termination event.

    dwStopCheckPoint++;

    FServiceReportStatus(
        SERVICE_STOP_PENDING, // current state
        NOERROR,              // exit code
        NOERROR,              // exit code
        dwStopCheckPoint,     // checkpoint
        dwStopWaitHint);      // wait hint

    if(hServiceStopEvent != NULL)
    {
        fStatus = SetEvent(hServiceStopEvent);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    return(dwState);
}

//$--ServiceCtrlProc------------------------------------------------------------
//  This function is called by the Service Controller whenever
//  someone calls ControlService in reference to our service.
// -----------------------------------------------------------------------------
static VOID WINAPI ServiceCtrlProc(         // RETURNS: nothing
    IN DWORD dwCtrlCode)            // control code
{
    HRESULT hr       = NOERROR;
    DWORD   dwState  = SERVICE_RUNNING;
    DWORD   dwStatus = 0;
    BOOL    fStatus  = FALSE;

    DEBUGPUBLIC("ServiceCtrlProc()\n");

    //
    // Handle the requested control code.
    //

    switch(dwCtrlCode)
    {
        // Pause the service if it is running.
        case SERVICE_CONTROL_PAUSE:
            dwState = dwDoServiceCtrlPause();
            break;

        // Resume the paused service.
        case SERVICE_CONTROL_CONTINUE:
            dwState = dwDoServiceCtrlContinue();
            break;

        // Stop the service.
        case SERVICE_CONTROL_STOP:
            dwState = dwDoServiceCtrlStop();
            return;

        // Update the service status.
        case SERVICE_CONTROL_INTERROGATE:
            break;

        // Invalid control code
        default:
            break;
    }

    //
    // Send a status response.
    //

    fStatus = FServiceReportStatus(dwState, NOERROR, NOERROR, 0, 0);

    if(fStatus == FALSE)
    {
        hr = HR_LOG(E_FAIL);
    }
}

//$--_ServiceMain---------------------------------------------------------------
//  This function takes care of actually starting the service,
//  informing the service controller at each step along the way.
//  After launching the worker thread, it waits on the event
//  that the worker thread will signal at its termination.
// -----------------------------------------------------------------------------
VOID _ServiceMain(                   // RETURNS: nothing
    IN DWORD dwArgc,                // count of arguments
    IN LPSTR *lpszArgv)            // pointer to arguments
{
    HRESULT hr         = NOERROR;
    DWORD   dw         = 0;
    DWORD   dwThreadID = 0;
    BOOL    fStatus    = FALSE;

    DEBUGPUBLIC("_ServiceMain()");

    hr = CHK__ServiceMain(
        dwArgc,
        lpszArgv);

    if(FAILED(hr))
        return;

    // Register our service control handler:

    lstrcpyn(
        szServiceName,
        SzMakeServiceName(lpszArgv[0]),
        MAX_SERVICE_NAME_LENGTH);

    szServiceName[MAX_SERVICE_NAME_LENGTH] = 0;

    dwServiceArgc   = dwArgc;
    lpszServiceArgv = lpszArgv;

    if(fIsService == TRUE)
    {
        sshStatusHandle = RegisterServiceCtrlHandler(
            szServiceName,
            ServiceCtrlProc);

        if(sshStatusHandle == 0)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ssStatus.dwServiceSpecificExitCode = 0;

    // Report the status to Service Control Manager.

    dwStartCheckPoint++;

    if(FServiceReportStatus(
        SERVICE_START_PENDING,      // service state
        NOERROR,                    // exit code
        NOERROR,                    // exit code
        dwStartCheckPoint,          // checkpoint
        dwStartWaitHint) == FALSE)  // wait hint
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the event object. The control handler function signals
    // this event when it receives the "stop" control code.

    hServiceStopEvent = CreateEvent(
        NULL,    // no security attributes
        TRUE,    // manual reset event
        FALSE,   // not-signalled
        NULL);   // no name

    if(hServiceStopEvent == (HANDLE)NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the event object. The control handler function signals
    // this event when it receives the "pause" control code.

    hServicePauseEvent = CreateEvent(
        NULL,    // no security attributes
        TRUE,    // manual reset event
        FALSE,   // not-signalled
        NULL);   // no name

    if(hServicePauseEvent == (HANDLE)NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the event object. The control handler function signals
    // this event when it receives the "continue" control code.

    hServiceContinueEvent = CreateEvent(
        NULL,    // no security attributes
        TRUE,    // manual reset event
        FALSE,   // not-signalled
        NULL);   // no name

    if(hServiceContinueEvent == (HANDLE)NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Create the event object. The application signals
    // this event when it is ready to "stop".

    hStopConfirmEvent = CreateEvent(
        NULL,    // no security attributes
        TRUE,    // manual reset event
        FALSE,   // not-signalled
        NULL);   // no name

    if(hStopConfirmEvent == (HANDLE)NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Report the status to the service control manager.

    dwStartCheckPoint++;

    if(FServiceReportStatus(
        SERVICE_START_PENDING,      // service state
        NOERROR,                    // exit code
        NOERROR,                    // exit code
        dwStartCheckPoint,          // checkpoint
        dwStartWaitHint) == FALSE)  // wait hint
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Start the thread that performs the work of the service.
    hWorkerThread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)WorkerThread,
        (LPVOID)hServiceStopEvent,
        0,
        &dwWorkerTID);

    if(hWorkerThread == NULL)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Report the status to the service control manager.
    if(FServiceReportStatus(
        SERVICE_RUNNING, // service state
        NOERROR,         // exit code
        NOERROR,         // exit code
        0,               // checkpoint
        0) == FALSE)     // wait hint
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Wait indefinitely until worker thread exits.
    //

    dw = WaitForSingleObject(hWorkerThread, INFINITE);

    if((dw != WAIT_OBJECT_0) && (dw != WAIT_ABANDONED))
    {
        hr = HR_LOG(E_FAIL);
    }

cleanup:

    CLOSEHANDLE(hWorkerThread);

    CLOSEHANDLE(hServiceStopEvent);

    CLOSEHANDLE(hServicePauseEvent);

    CLOSEHANDLE(hServiceContinueEvent);

    CLOSEHANDLE(hStopConfirmEvent);

    //
    // Try to report the stopped status to the service control manager.
    //

    if(sshStatusHandle != 0)
    {
        fStatus = FServiceReportStatus(
            SERVICE_STOPPED,
            dwSvcWin32ExitCode,
            dwSvcServiceSpecificExitCode,
            0,
            0);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }

    // When SERVICE MAIN FUNCTION returns in a single service
    // process, the StartServiceCtrlDispatcher function in
    // the main thread returns, terminating the process.

    return;
}

//$--HrWaitServiceStopConfirm---------------------------------------------------
//  This function is called to wait for the application to stop.
// -----------------------------------------------------------------------------
HRESULT HrWaitServiceStopConfirm(   // RETURNS: return code
    VOID)                           // no arguments
{
    HRESULT hr            = NOERROR;
    DWORD   dwT           = 0;
    DWORD   dwStopTimeOut = dwStopWaitHint/2;

    DEBUGPUBLIC("HrWaitServiceStopConfirm()\n");

    for(;;)
    {
        if(fIsService == TRUE)
        {
            // Report the status, specifying the checkpoint and wait-hint.

            dwStopCheckPoint++;

            FServiceReportStatus(
                SERVICE_STOP_PENDING, // current state
                NOERROR,              // exit code
                NOERROR,              // exit code
                dwStopCheckPoint,     // checkpoint
                dwStopWaitHint);      // wait hint
        }

        //
        // Wait until application has stopped or a timeout occurs.
        //

        dwT = WaitForSingleObject(hStopConfirmEvent, dwStopTimeOut);

        switch(dwT)
        {
        case (WAIT_OBJECT_0):
        case (WAIT_ABANDONED_0):
            hr = NOERROR;
            goto cleanup;
            break;
        case WAIT_TIMEOUT:
            break;
        default:
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

cleanup:

    RETURN(hr);
}

//$--SetServiceExitCode---------------------------------------------------------
//  This function is called by the application to set the service exit code.
//
//  dwWin32ExitCode
//
//      - specifies a Win32 error code that the service uses to
//        report an error that occurs when it is starting or
//        stopping. To return an error code specific to the service,
//        the service must set this value to
//        ERROR_SERVICE_SPECIFIC_ERROR to indicate that the
//        dwServiceSpecificExitCode member contains the error code.
//        The service should set this value to NO_ERROR when it is
//        running and on normal termination.
//
//  dwServiceSpecificExitCode
//
//      - specifies a service specific error code that the
//        service returns when an error occurs while the service is
//        starting or stopping. This value is ignored unless the
//        dwWin32ExitCode member is set to ERROR_SERVICE_SPECIFIC_ERROR.
//
// -----------------------------------------------------------------------------
VOID SetServiceExitCode(
    IN DWORD dwWin32ExitCode,           // Win32 exit code
    IN DWORD dwServiceSpecificExitCode) // service specific exit code
{
    DEBUGPUBLIC("SetServiceExitCode()");

    dwSvcWin32ExitCode           = dwWin32ExitCode;
    dwSvcServiceSpecificExitCode = dwServiceSpecificExitCode;
}

//$--HrServiceConfirmStop----------------------------------------------------
//  This function is called by the application to indicate that it has stopped.
// -----------------------------------------------------------------------------
HRESULT HrServiceConfirmStop(    // RETURNS: return code
    VOID)                           // no arguments
{
    HRESULT hr      = NOERROR;
    BOOL    fStatus = TRUE;

    DEBUGPUBLIC("HrServiceConfirmStop()\n");

    if(hStopConfirmEvent != NULL)
    {
        fStatus = SetEvent(hStopConfirmEvent);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }
    else
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//------------------------------------------------------------------------------
// Utility functions...
//------------------------------------------------------------------------------

//$--FServiceReportStatus-------------------------------------------------------
//  This function is called by the private _ServiceMain() and
//  ServCtrlHandler() functions to update the service's status
//  to the service control manager.
// -----------------------------------------------------------------------------
BOOL FServiceReportStatus(              // RETURNS: TRUE if successful
    IN DWORD dwCurrentState,            // current state of service
    IN DWORD dwWin32ExitCode,           // service Win32 exit code
    IN DWORD dwServiceSpecificExitCode, // service specific exit code
    IN DWORD dwCheckPoint,              // check point number
    IN DWORD dwWaitHint)                // time to wait
{
    BOOL fResult = TRUE;

    DEBUGPUBLIC("FServiceReportStatus()\n");

    // Disable control requests until the service is started.

    if(dwCurrentState == SERVICE_START_PENDING)
    {
        ssStatus.dwControlsAccepted = 0;
    }
    else
    {
        ssStatus.dwControlsAccepted =
            SERVICE_ACCEPT_STOP |
            SERVICE_ACCEPT_PAUSE_CONTINUE;
    }

    // These SERVICE_STATUS members are set from parameters.

    ssStatus.dwCurrentState            = dwCurrentState;
    ssStatus.dwWin32ExitCode           = dwWin32ExitCode;
    ssStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
    ssStatus.dwCheckPoint              = dwCheckPoint;
    ssStatus.dwWaitHint                = dwWaitHint;

    // Report the status of the service to the service control manager.

    if(fIsService == TRUE)
    {
        fResult = SetServiceStatus(
            sshStatusHandle,    // service reference handle
            &ssStatus);         // SERVICE_STATUS structure

        if(fResult == FALSE)
        {
            // If an error occurs, stop the service.

            ServiceStop();
        }
    }

    return(fResult);
}

//$--ServiceStop-------------------------------------------------------------
//  This function can be used by any thread to stop the service.
// -----------------------------------------------------------------------------
VOID ServiceStop(                // RETURNS: nothing
    VOID)                           // no argument
{
    HRESULT hr      = NOERROR;
    BOOL    fStatus = TRUE;

    DEBUGPUBLIC("ServiceStop()\n");

    // Set a termination event to stop SERVICE MAIN FUNCTION.

    if(hServiceStopEvent != NULL)
    {
        fStatus = SetEvent(hServiceStopEvent);

        if(fStatus == FALSE)
        {
            hr = HR_LOG(E_FAIL);
        }
    }
}

//$--HrServiceWaitForControl----------------------------------------------------
//  The HrServiceWaitForControl function can be used by any thread to get or
//  wait for service control after the service has been started.
// -----------------------------------------------------------------------------
HRESULT HrServiceWaitForControl(        // RETURNS: return code
    IN DWORD dwTimeout,                 // time-out interval in milliseconds
    OUT EDK_SERVICE_CONTROL_T *pedksc)  // pointer to service name buffer
{
    HRESULT hr          = NOERROR;
    DWORD   cObjects    = 3;
    HANDLE  hObjects[3] = {0};
    DWORD   dwResult    = WAIT_FAILED;

    DEBUGPUBLIC("HrServiceWaitForControl()\n");

    hr = CHK_HrServiceWaitForControl(
        dwTimeout,
        pedksc);

    if(FAILED(hr))
        RETURN(hr);

    hObjects[0] = hServiceStopEvent;
    hObjects[1] = hServicePauseEvent;
    hObjects[2] = hServiceContinueEvent;

    dwResult = WaitForMultipleObjects(cObjects, hObjects, FALSE, dwTimeout);

    *pedksc = EDK_SC_NONE;

    switch(dwResult)
    {
    case (WAIT_OBJECT_0):
        *pedksc = EDK_SC_STOP;
        break;
    case (WAIT_OBJECT_0+1):
        *pedksc = EDK_SC_PAUSE;
        break;
    case (WAIT_OBJECT_0+2):
        *pedksc = EDK_SC_CONTINUE;
        break;
    case (WAIT_ABANDONED_0):
        break;
    case (WAIT_ABANDONED_0+1):
        break;
    case (WAIT_ABANDONED_0+2):
        break;
    case WAIT_TIMEOUT:
        break;
    default:
        *pedksc = EDK_SC_STOP;

        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrServiceWaitForContinue---------------------------------------------------
//  The HrServiceWaitForContinue function can be used by any thread to get or
//  wait for service control after the service has been started.
// -----------------------------------------------------------------------------
HRESULT HrServiceWaitForContinue(       // RETURNS: return code
    IN DWORD dwTimeout,                 // time-out interval in milliseconds
    OUT EDK_SERVICE_CONTROL_T *pedksc)  // pointer to service name buffer
{
    HRESULT hr          = NOERROR;
    DWORD   cObjects    = 2;
    HANDLE  hObjects[2] = {0};
    DWORD   dwResult    = WAIT_FAILED;

    DEBUGPUBLIC("HrServiceWaitForContinue()\n");

    hr = CHK_HrServiceWaitForContinue(
        dwTimeout,
        pedksc);

    if(FAILED(hr))
        RETURN(hr);

    hObjects[0] = hServiceStopEvent;
    hObjects[1] = hServiceContinueEvent;

    dwResult = WaitForMultipleObjects(cObjects, hObjects, FALSE, dwTimeout);

    *pedksc = EDK_SC_NONE;

    switch(dwResult)
    {
    case (WAIT_OBJECT_0):
        *pedksc = EDK_SC_STOP;
        break;
    case (WAIT_OBJECT_0+1):
        *pedksc = EDK_SC_CONTINUE;
        break;
    case (WAIT_ABANDONED_0):
        break;
    case (WAIT_ABANDONED_0+1):
        break;
    case WAIT_TIMEOUT:
        break;
    default:
        *pedksc = EDK_SC_STOP;

        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrServiceWaitForStop-------------------------------------------------------
//  The HrServiceWaitForStop function can be used by any thread to get or
//  wait for service control after the service has been started.
// -----------------------------------------------------------------------------
HRESULT HrServiceWaitForStop(           // RETURNS: return code
    IN DWORD dwTimeout,                 // time-out interval in milliseconds
    OUT EDK_SERVICE_CONTROL_T *pedksc)  // pointer to service name buffer
{
    HRESULT hr          = NOERROR;
    DWORD   cObjects    = 1;
    HANDLE  hObjects[1] = {0};
    DWORD   dwResult    = WAIT_FAILED;

    DEBUGPUBLIC("HrServiceWaitForStop()\n");

    hr = CHK_HrServiceWaitForStop(
        dwTimeout,
        pedksc);

    if(FAILED(hr))
        RETURN(hr);

    hObjects[0] = hServiceStopEvent;

    dwResult = WaitForMultipleObjects(cObjects, hObjects, FALSE, dwTimeout);

    *pedksc = EDK_SC_NONE;

    switch(dwResult)
    {
    case (WAIT_OBJECT_0):
        *pedksc = EDK_SC_STOP;
        break;
    case (WAIT_ABANDONED_0):
        break;
    case WAIT_TIMEOUT:
        break;
    default:
        *pedksc = EDK_SC_STOP;

        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);
}

//$--HrServiceProcessControl---------------------------------------------------
//  Check for service control
// -----------------------------------------------------------------------------
HRESULT HrServiceProcessControl(       // RETURNS: return code
    VOID)                               // no arguments
{
    HRESULT               hr    = NOERROR;
    HRESULT               hrT   = NOERROR;
    EDK_SERVICE_CONTROL_T edksc = EDK_SC_NONE;

    DEBUGPUBLIC("HrServiceProcessControl()\n");

    for(;;)
    {
        switch(edksc)
        {
        case EDK_SC_PAUSE:
            // Wait for service to continue or stop
            hrT = HrServiceWaitForContinue(INFINITE, &edksc);
            break;
        default:
            // Wait for service to pause, continue or stop
            hrT = HrServiceWaitForControl(0, &edksc);
        }

        if(SUCCEEDED(hrT))
        {
            switch(edksc)
            {
            case EDK_SC_NONE:
                goto cleanup;
                break;
            case EDK_SC_PAUSE:
                break;
            case EDK_SC_CONTINUE:
                goto cleanup;
                break;
            case EDK_SC_STOP:
                hr = HR_LOG(EDK_E_SHUTDOWN_SERVICE);
                goto cleanup;
                break;
            default:
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
        else
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

cleanup:

    RETURN(hr);
}

//$--HrFreeArgvW----------------------------------------------------------------
//  Free a wide character copy of argv[].
// -----------------------------------------------------------------------------
static HRESULT HrFreeArgvW(         // RETURNS: exit code
    IN     DWORD  dwArgc,           // Number of arguments
    IN OUT LPWSTR **lppszArgvW)     // pointer to wide character command line
{
    HRESULT hr     = NOERROR;
    DWORD   i      = 0;
    LPWSTR  *lpwsz = NULL;

    DEBUGPRIVATE("HrFreeArgvW()\n");

    hr = CHK_HrFreeArgvW(
        dwArgc,
        lppszArgvW);

    if(FAILED(hr))
        RETURN(hr);

    lpwsz = *lppszArgvW;

    if(lpwsz != NULL)
    {
        for(i = 0 ; i < dwArgc ; i++)
        {
            if(lpwsz[i] != NULL)
            {
                free(lpwsz[i]);
                lpwsz[i] = NULL;
            }
        }

        free(lpwsz);
        lpwsz = NULL;
    }

    *lppszArgvW = NULL;

    RETURN(hr);
}

//$--ControlHandler-------------------------------------------------------------
//  Handle console control events.
// -----------------------------------------------------------------------------
static BOOL WINAPI ControlHandler(          // RETURNS: TRUE if successful
    IN DWORD dwCtrlType)                    // control event type
{
    switch( dwCtrlType )
    {
    case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
    case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in debug mode

        ServiceStop();

        return(TRUE);
        break;
    default:
        ;
    }

    return(FALSE);
}

//$--WinMain--------------------------------------------------------------------
//  This function starts the service.
// -----------------------------------------------------------------------------
int APIENTRY WinMain(               // RETURNS: exit code
    IN  HINSTANCE hInstance,        // Handle to instance of application
    IN  HINSTANCE hPrevInstance,    // Handle to previous instance
    IN  LPSTR lpszCmdLine,          // Pointer to command line
    IN  int nCmdShow)               // Argument for ShowWindow()
{
    HRESULT hr       = NOERROR;
    DWORD   dwArgc   = 0;
    LPSTR* lpszArgv = NULL;

    SERVICE_TABLE_ENTRY dispatchTable[] =
    {
        { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)_ServiceMain },
        { NULL, NULL }
    };

    DEBUGPUBLIC("WinMain()\n");

    //
    // Parse command line
    //

    dwArgc   = (DWORD) __argc;
    lpszArgv = __argv;

    //
    // Check if running as a service
    //

    if((dwArgc > 1) && (lstrcmpi(lpszArgv[1], "NOTSERV") == 0))
    {
        fIsService = FALSE;
    }

    WinMainContext.hInstance     = hInstance;
    WinMainContext.hPrevInstance = hPrevInstance;
    WinMainContext.lpszCmdLine   = lpszCmdLine;
    WinMainContext.nCmdShow      = nCmdShow;

    if(fIsService == TRUE)
    {
        //
        // Run as a service
        //

        if(StartServiceCtrlDispatcher(dispatchTable) == FALSE)
        {
            hr = HR_LOG(E_FAIL);

            ServiceStop();
        }
    }
    else
    {
        SetConsoleCtrlHandler(ControlHandler, TRUE);

        if(dwArgc == 2)
        {
            hr = HR_LOG(E_FAIL);
        }
        else
        {
            _ServiceMain(dwArgc-2, lpszArgv+2);  // NOTE:  if we're running by command line,
                                                // need to pass arg after 'NOTSERV' as service
                                                // name rather than exe name.
        }
    }

    return(0);
}

//$--WndProc--------------------------------------------------------------------
//  Processes messages
// -----------------------------------------------------------------------------
LRESULT CALLBACK WndProc(           // RETURNS: LRESULT
    IN HWND hWnd,                   // window handle
    IN UINT message,                // type of message
    IN WPARAM uParam,               // additional information
    IN LPARAM lParam)               // additional information
{
    HRESULT hr        = NOERROR;
    HRESULT hrT       = NOERROR;
    LRESULT lRetVal   = 0;
    BOOL    fStatus   = TRUE;
    DWORD   dw        = 0;

    DEBUGPUBLIC("WndProc()\n");

    switch( message)
    {
    //  Closing the window will signal the shutdown event object and cause
    //  WaitShutdown to send a WM_DESTROY message and end its thread.
    case WM_CLOSE:
ServiceStop();
        break;

    //  Before destroying the window, call NTServiceShutdown.
    case WM_DESTROY:

        //
        // Wait for application to stop.
        //

        hrT = HR_LOG(HrWaitServiceStopConfirm());

        //
        // Wait for service thread to exit.
        //

        if(hServiceThread != NULL)
        {
            dw = WaitForSingleObject(hServiceThread, WAIT_DESTROY);

            if((dw != WAIT_OBJECT_0) && (dw != WAIT_ABANDONED_0))
            {
                hr = HR_LOG(E_FAIL);
            }

            CLOSEHANDLE(hServiceThread);
        }

        hrT = HR_LOG(HrServiceShutdown());

        //
        // Cause message loop in WorkerThread() to terminate.
        //

        PostQuitMessage(0);
        break;

    //  Paint the icon if its background is erased.
    case WM_ICONERASEBKGND:

        DefWindowProc(hWnd, WM_ICONERASEBKGND, uParam, lParam);
        PostMessage(hWnd, WM_PAINTICON, 0, 0);
        break;

    //  Users are not allowed to "Switch To" this application via Task Manager
    //  since it has no main window.  According to the API, if the icon can not
    //  be opened, this message should return FALSE.
    case WM_QUERYOPEN:

        lRetVal = (LRESULT)FALSE;
        break;

    //  Do the default action for any other message.
    default:

        lRetVal = DefWindowProc(hWnd, message, uParam, lParam);
        break;
    }

    return(lRetVal);
}


LOOKUP.C

// --lookup.c-------------------------------------------------------------------
//
//  A simple test driver for HrGWResolveAddress() & HrGWResolveProxy()
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

LPMAPISESSION lpSession        = NULL;
LPADRBOOK     lpAdrBook        = NULL;   // Pointer to address book
LPABCONT      lpGalABCont      = NULL;
ULONG         cbGalEid         = 0;      // count of bytes in GAL entry ID
LPENTRYID     lpGalEid         = NULL;   // pointer to GAL entry ID

static BOOL   fIsInit          = FALSE;

static BOOL   fInitializedMAPI = FALSE; // MAPI Initialized

#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

static char * rgpszArgArray[] = {
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//$--InitLookup-----------------------------------------------------------------
//  Initialize for address lookup
// -----------------------------------------------------------------------------
HRESULT HrInitLookup(                       // RETURNS: return code
    IN LPSTR lpszProfileName)               // MAPI profile name
{
    HRESULT hr        = NOERROR;
    HRESULT hrT       = NOERROR;
    SCODE   sc        = 0;
    ULONG   ulObjType = 0;

    DEBUGPUBLIC( "HrInitLookup()\n" );

    DEBUGACTION("Initializing MAPI\n");

    hrT = MAPIInitialize(NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    fInitializedMAPI = TRUE;

    hrT = MAPILogonEx(
        (ULONG) 0,
        lpszProfileName,
        NULL,
        MAPI_NEW_SESSION | MAPI_LOGON_UI | MAPI_EXTENDED | MAPI_NO_MAIL,
        &lpSession);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(lpSession != NULL, "NULL lpSession variable");

    hrT = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERTERROR(lpAdrBook != NULL, "NULL lpAdrBook variable");

    hr = HrFindExchangeGlobalAddressList(
        lpAdrBook,
        &cbGalEid,
        &lpGalEid);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open the global recipient container
    hrT = MAPICALL(lpAdrBook)->OpenEntry(
        lpAdrBook,
        cbGalEid,
        lpGalEid,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *)&lpGalABCont);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(ulObjType != MAPI_ABCONT)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(SUCCEEDED(hr))
    {
        fIsInit = TRUE;
    }
    else
    {
        if(fInitializedMAPI == TRUE)
        {
            MAPIFREEBUFFER(lpGalEid);

            ULRELEASE(lpGalABCont);

            ULRELEASE(lpAdrBook);

            ULRELEASE(lpSession);

            MAPIUninitialize();

            fInitializedMAPI = FALSE;
        }
    }

    RETURN(hr);
}

//$--HrUninitLookup------------------------------------------------------------
//  Free all global variables
// ----------------------------------------------------------------------------
HRESULT HrUninitLookup( void )      // RETURNS: return code
{
    HRESULT  hr = NOERROR;

    if(fIsInit == FALSE )
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    MAPIFREEBUFFER(lpGalEid);

    ULRELEASE(lpGalABCont);

    ULRELEASE(lpAdrBook);

    ULRELEASE(lpSession);

    fIsInit = FALSE;

cleanup:

    if(fInitializedMAPI == TRUE)
    {
        MAPIUninitialize();

        fInitializedMAPI = FALSE;
    }

    RETURN(hr);
}

//$--HrPrintAddress----------------------------------------------------------
// Prints the ADDRTYPE, DISPLAY_NAME & EMAIL_ADDRESS of a Recipient
// identified by a given Recipient ENTRYID
// --------------------------------------------------------------------------
HRESULT HrPrintAddress(         // RETURNS: return code
    IN BOOL      fMapiRecip,    // MAPI recipient
    IN ULONG     cbEntryID,     // count of bytes in entry ID
    IN LPENTRYID lpEntryID)     // pointer to entry ID
{
    HRESULT       hr        = NOERROR;
    HRESULT       hrT       = NOERROR;
    ULONG         ulObjType = 0;
    ULONG         cValues   = 0;
    LPSPropValue  lpProps   = {0};
    LPMAPIPROP    lpRecip   = NULL;

    SizedSPropTagArray(5, sPropTags) =
    {
        5,
        {
            PR_ENTRYID,
            PR_ADDRTYPE,
            PR_DISPLAY_NAME,
            PR_EMAIL_ADDRESS,
            PR_SEND_RICH_INFO
        }
    };

    DEBUGPUBLIC( "HrPrintAddress()\n" );

    // Open the Recipient object
    hrT = MAPICALL(lpSession)->OpenEntry(
        lpSession,
        cbEntryID,
        lpEntryID,
        NULL,
        MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *)&lpRecip);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the required properties of the user
    hrT = MAPICALL(lpRecip)->GetProps(
        lpRecip,
        (SPropTagArray *)&sPropTags,
        fMapiUnicode,
        &cValues,
        &lpProps);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    printf( " MAPI Recipient = %s\n", (fMapiRecip ? "TRUE" : "FALSE"));
    printf( " Address Type   = \"%s\"\n",   lpProps[1].Value.LPSZ);
    printf( " Display Name   = \"%s\"\n",   lpProps[2].Value.LPSZ);
    printf( " Email Address  = \"%s\"\n\n", lpProps[3].Value.LPSZ);

    ASSERTERROR(
        lpProps[4].Value.b == fMapiRecip,
        "PR_SEND_RICH_INFO not equal to entry ID flag");

cleanup:

    MAPIFREEBUFFER(lpProps);

    ULRELEASE(lpRecip);

    RETURN(hr);
}

//$--main----------------------------------------------------------------------
//  Test driver main entry point.
//
//  This program loops, until terminated by the user, prompting alternately
//  for input values to HrGWResolveAddress() and HrGWResolveProxy()
//  and prints the resolved results to a default output stream.
// ----------------------------------------------------------------------------

#define    MAX_INPUT_SZ        1024

int main(int argc, char *argv[])
{
    HRESULT    hr                       = NOERROR;
    SCODE      sc                       = 0;
    CHAR       szInput[MAX_INPUT_SZ]    = "";
    LPSTR      lpszProfileName          = NULL;
    ULONG      cbEntryID                = 0;
    LPENTRYID  lpEntryID                = NULL;
    LPSTR      lpszProxyAddr            = NULL;
int        i                        = 0;
LPSTR      lpszArgument             = NULL;
LPSTR      lpszValue                = NULL;
BOOL       fFinishedCommandLine     = FALSE;
    BOOL       fMapiRecip               = FALSE;

    printf( "\n" );

// If they didn't give command line args then print a usage message.

if (argc < 2)
{
    printf("USAGE: LOOKUP Profile\n\n");
printf("  [Flags]        Enter LOOKUP /? for details\n");
goto cleanup;
}

// If they used /? or /HELP (the only defined flags with labels) then
// print a help message.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &lpszArgument, &lpszValue);

if (SUCCEEDED(hr) && lpszArgument &&
(!_stricmp(lpszArgument,ARG_HELP1) ||
!_stricmp(lpszArgument,ARG_HELP2)))
{
printf("Profile Lookup sample.\n\n");
printf("USAGE: LOOKUP Profile\n\n");
printf("  Profile        Name of profile to look up\n");
printf("  /HELP or /?    Display help screen\n");
goto cleanup;
}
}

    // Get MAPI profile name from command line

if (argc > 2)
{
fprintf(stderr, "ERROR: too many command line arguments\n");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

hr = _HrExpandCommandLineArgument(
argv[1], rgpszArgArray, ARGARRAYSIZE, NULL, &lpszArgument, &lpszProfileName);

if (FAILED(hr) || lpszArgument)
{
fprintf(stderr, "ERROR: unknown command line argument %s\n", argv[1]);
hr = HR_LOG(E_FAIL);
goto cleanup;
}

fFinishedCommandLine = TRUE;

// Do initialization

    hr = HrInitLookup(lpszProfileName);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    //  Loop until terminated by user.
    //
    for(;;)
    {
        int iTmp = 0;

        printf( "\n\n"
                 "Enter address to be resolved in <type>:<value> format\n"
                 "or EXIT to terminate.\n"
                 "NOTE:  The address that you type must match exactly,\n"
                 "       including case.\n"
                 ": ");

        //
        //  Get next line of number.  EOF = terminate.
        //
        if (_fgetts( szInput, MAX_INPUT_SZ, stdin ) == NULL)
            break;

        //
        // trim the trailing '\n'
        //
        iTmp = strlen(szInput);

        if (iTmp > 0 && szInput[iTmp-1] == '\n')
            szInput[iTmp-1] = 0;

        //
        //  Check for exit.
        //
        if (_stricmp( szInput, TEXT("EXIT")) == 0)
            break ;

        fMapiRecip = FALSE;

        //
        //  Resolve the address.
        //

        hr = HrGWResolveAddress(
            lpGalABCont,
            szInput,
            &fMapiRecip,
            &cbEntryID,
            &lpEntryID);

        if(SUCCEEDED(hr))
        {
            printf("\n") ;

            hr = HrPrintAddress(fMapiRecip, cbEntryID, lpEntryID);

            if(FAILED(hr))
            {
                printf("ERROR: could not print address [%08lX]\n", hr);
                continue;
            }

            printf(" Enter address type to be resolved\n"
                     " :");

            if(gets(szInput) != NULL)
            {
                printf("\n") ;

                fMapiRecip = FALSE;

                //
                //  Check for exit.
                //
                if (_stricmp( szInput, TEXT("EXIT")) == 0)
                    break;

                hr = HrGWResolveProxy(
                    lpAdrBook,
                    cbEntryID,
                    lpEntryID,
                    szInput,
                    &fMapiRecip,
                    &lpszProxyAddr);

                printf("\n");

                if(FAILED(hr))
                {
            fprintf(stderr, "ERROR: could not resolve proxy [%08lX]\n", hr);
                }
                else
                {
                    printf( " MAPI Recipient = %s\n", (fMapiRecip ? "TRUE" : "FALSE"));
                    printf( " Resolved Proxy = %-30s\n", lpszProxyAddr);

                    MAPIFREEBUFFER(lpszProxyAddr);
                }
            }
        }
        else
        {
    fprintf(stderr, "ERROR: could not resolve address [%08lX]\n", hr);
        }

        MAPIFREEBUFFER(lpEntryID);
    }

cleanup:

    MAPIFREEBUFFER(lpEntryID);

    // Terminate the MAPI session properly

    if(fIsInit == TRUE)
    {
        hr = HR_LOG(HrUninitLookup());
    }

// Display results of operation.

if (fFinishedCommandLine)
{
if (SUCCEEDED(hr))
{
fprintf(stderr, "Done.\n");
}
else
{
fprintf(stderr, "Lookup not successful.\n");
}
}

    // return exit code
    return _nEcFromHr(hr);

}


MBLSAMPL.C

// --MBLSampl.c-----------------------------------------------------------------
//
//  Privileged Access to logon and off of any message store.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "edkguid.h"
#include "mblsampl.chk"

#define TPRINTF(x)          _tprintf(TEXT(x))
#define TPRINTF1(x,y)       _tprintf(TEXT(x),(y))

#define MAX_INPUT_SZ        300  //Max. Terminal Input
#define MAX_MESSAGE_HDRS    50   //Max. messages per query

// Arguments from command line
BOOL    fHelpRequested      = FALSE;
LPTSTR  lpszProfileName     = NULL;
LPTSTR  lpszPassword        = NULL;

// This gets set to a string as it moves through the code.  If we fail before
// interacting with the user then this will contain the reason.
LPTSTR  lpszReasonForFailure = TEXT( "ERROR: Unknown\n");

//$--HrInitialize---------------------------------------------------------------
//  Initialize the application by initializing MAPI, Logging on, and opening the
//  Exchange message store.
// -----------------------------------------------------------------------------
static HRESULT HrInitialize(                    // RETURNS: return code
    IN  LPTSTR lpszProfileName,     // MAPI profile name
    IN  LPTSTR lpszPassword,        // Profile password
    OUT LPMAPISESSION *lpplhSession,// ptr to MAPI session handle ptr.
    OUT LPMDB  *lppMDB)             // ptr to message store ptr.
{
    HRESULT     hr      = E_FAIL;
    ULONG       cbeid   = 0;    // count of bytes in entry ID
    LPENTRYID   lpeid   = NULL; // Entry ID of Exchange store

    DEBUGPRIVATE( "HrInitialize()");

    lpszReasonForFailure = TEXT( "ERROR: HrInitialize parameters are bad.\n");
    hr = CHK_HrInitialize( lpszProfileName, lpszPassword, lpplhSession, lppMDB);
    if( FAILED( hr))
        RETURN( hr);

    lpszReasonForFailure = TEXT( "ERROR: Could not initialize MAPI.\n");
    DEBUGACTION( "Initializing MAPI");
    hr = MAPIInitialize( NULL);
    if( FAILED( hr))
        goto cleanup;

    lpszReasonForFailure = TEXT( "ERROR: Could not logon using the profile that was given.\n");
    DEBUGACTION( "Logon to MAPI");
    *lpplhSession = NULL;
    hr = (HRESULT) MAPILogonEx(
            0,
            lpszProfileName,
            lpszPassword,
            MAPI_NO_MAIL | MAPI_NEW_SESSION | MAPI_LOGON_UI,
            lpplhSession);

    if( FAILED( hr))
        goto cleanup;
    ASSERTERROR( *lpplhSession != NULL, "Null lphSession!" );

    // Open the Exchange message store.
    lpszReasonForFailure = TEXT( "ERROR: Could not open the Exchange message store.\n");
    DEBUGACTION( "Opening Exchange Message Store");
    hr = HrOpenExchangePrivateStore( *lpplhSession, lppMDB);
    if( FAILED(hr))
        goto cleanup;

    ASSERTERROR( *lppMDB != NULL, "NULL lppMDB pointer");

lpszReasonForFailure = TEXT( "ERROR: Unknown\n");
    hr = NOERROR;

cleanup:
    MAPIFREEBUFFER( lpeid);

    if( FAILED( hr))
    {
        if( *lpplhSession)
        {   // Logoff a MAPI session
            MAPICALL((*lpplhSession))->Logoff( *lpplhSession, 0, 0, 0);
            ULRELEASE(*lpplhSession);
        }
        MAPIUninitialize();
    }

    RETURN( hr);
}

//$--HrShutDown-----------------------------------------------------------------
//  Shutdown the application by closing the Exchange message store, logging off,
//  and uninitializing MAPI.
// -----------------------------------------------------------------------------
static HRESULT HrShutDown(                     // RETURNS: return code
    IN OUT LPMAPISESSION *lpplhSession, // ptr to MAPI session handle ptr.
    IN OUT LPMDB  *lppMDB)              // ptr to message store ptr.
{
    HRESULT hr;

    DEBUGPRIVATE( "HrShutDown()");

    hr = CHK_HrShutDown( lpplhSession, lppMDB);
    if( FAILED( hr))
        RETURN( hr);

    ULRELEASE( *lppMDB);

    // Logoff a MAPI session
    MAPICALL((*lpplhSession))->Logoff( *lpplhSession, 0, 0, 0);
    ULRELEASE(*lpplhSession);

    MAPIUninitialize();

    RETURN( NOERROR);
}

//$--HrGetMsgList-----------------------------------------------------------
//  Returns the next message list (<=cMaxNRows) in a given message table;
// -----------------------------------------------------------------------------
static HRESULT HrGetMsgList(
    IN  LPMAPITABLE lpTable,    // ptr.to the contents table
    IN  ULONG       cMaxNRows,  // Max.# of rows to return
    OUT LPSRowSet   *lppRows )  // ptr.to address variable for SRowSet
{
    HRESULT      hr  = NOERROR;

    DEBUGPRIVATE( "HrGetMsgList()" );

    hr = CHK_HrGetMsgList( lpTable, cMaxNRows, lppRows);
    if( FAILED( hr))
        RETURN( hr);

    // Get the next list of messages in the folder
    hr = MAPICALL(lpTable)->QueryRows( lpTable, cMaxNRows, 0L, lppRows );
    if( SUCCEEDED( hr) && (!(*lppRows) || (*lppRows)->cRows == 0))
    {
        FREEPROWS(*lppRows);
        hr = HR_LOG( EDK_E_END_OF_FILE);
    }

    RETURN(hr);
}

//$--HrReadMailboxHeaders-------------------------------------------------------
//  Reads and displays the subject of the mail messages found in the MDB.
// -----------------------------------------------------------------------------
static HRESULT HrDispMailboxSubjects(
    IN LPMDB  lpMDB)                // Message store ptr.
{
    HRESULT         hr                  = NOERROR;
    unsigned        iMsgIndx            = 0;
    unsigned        nCnt                = 0;
    ULONG           cbeid               = 0L;
    ULONG           ulObjType           = 0L;
    LPENTRYID       lpeid               = NULL;
    LPMAPIFOLDER    lpInboxFolder       = NULL;     // ptr to Inbox folder
    LPMAPITABLE     lpContentsTbl       = NULL;     // ptr to Contents folder table
    LPSRowSet       lpInRowSet          = NULL;     // ptr to Inbox rows
    LPSPropValue    lpProps             = NULL;     // Ptr to property array.

    static const SizedSSortOrderSet(1L,sSortPrioSet) =
        { 1L, 0L, 0L, { PR_CLIENT_SUBMIT_TIME, TABLE_SORT_ASCEND}};

    static const SizedSPropTagArray(2L,sPropColumns) =
        { 2L, {PR_SUBJECT, PR_ENTRYID}};

    DEBUGPRIVATE( "HrDispMailboxSubjects()");

    hr = CHK_HrDispMailboxSubjects( lpMDB);
    if( FAILED( hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize an inbox folder object.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Find default (inbox) folder.
    hr = HrMAPIFindInbox( lpMDB, &cbeid, &lpeid);
    if( FAILED(hr))
        goto cleanup;
    ASSERTERROR ( lpeid != NULL, "NULL lpeid");

    // Open the default (inbox) folder.
    hr = MAPICALL( lpMDB)->OpenEntry( lpMDB, cbeid, lpeid, NULL,
        MAPI_MODIFY|MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN*)&lpInboxFolder);
    if( FAILED( hr))
        goto cleanup;
    if( ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Now that we have a folder we can initialize a contents table
// to look through.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the contents table for the folder.
    hr = MAPICALL( lpInboxFolder)->GetContentsTable(
        lpInboxFolder, MAPI_DEFERRED_ERRORS, &lpContentsTbl);
    if( FAILED( hr))
        goto cleanup;

    // Set the columns to return.
    hr = MAPICALL( lpContentsTbl)->SetColumns( lpContentsTbl, (LPSPropTagArray) &sPropColumns, 0L);
    if(FAILED(hr))
        goto cleanup;

    // Sort the table.
    hr = MAPICALL( lpContentsTbl)->SortTable( lpContentsTbl, (LPSSortOrderSet) &sSortPrioSet, 0L);
    if(FAILED(hr))
        goto cleanup;

    // Position to the beginning of the contents table.
    hr = MAPICALL( lpContentsTbl)->SeekRow( lpContentsTbl, BOOKMARK_BEGINNING, 0, NULL);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Contents table has been initialize so lets display it.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Display INBOX subjects until end of table reached.
    while( TRUE)
    {   // Read a group of message headers from the contents table.
        hr = HrGetMsgList( lpContentsTbl, MAX_MESSAGE_HDRS, &lpInRowSet);
        if( hr == EDK_E_END_OF_FILE)
            break;
        if( FAILED( hr))
            goto cleanup;;

        ASSERTERROR( lpInRowSet != NULL, "NULL lpInRowSet!");

        // Process all messages in the current table.
        for( iMsgIndx = 0; iMsgIndx < lpInRowSet->cRows; ++iMsgIndx )
        {   // Display the subject name.
            lpProps = lpInRowSet->aRow[iMsgIndx].lpProps;

            ASSERTERROR( lpProps->ulPropTag != PR_SUBJECT, "Invalid property returned!");

            if(lpProps->Value.LPSZ == NULL || *(lpProps->Value.LPSZ) == 0)
                printf( "  SUBJECT: (empty).\n");
            else
                printf( "  SUBJECT: %s\n", lpProps->Value.LPSZ);
            nCnt ++;
        }

        // Free the current message list structure.
        FREEPROWS( lpInRowSet);
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// All done displaying the subjects.  Time to clean up.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = NOERROR;

cleanup:
    if( hr == MAPI_E_NO_ACCESS)
        printf( "  Insufficient access rights.\n");
    else if( nCnt == 0)
        printf( "  No messages found in this mailbox.\n");

    ULRELEASE( lpContentsTbl);
    ULRELEASE( lpInboxFolder);
    MAPIFREEBUFFER( lpeid);

    RETURN( hr);
}

//$--PrintUsage-----------------------------------------------------------------
// Print usage text
// -----------------------------------------------------------------------------
static VOID PrintUsage (
    INLPTSTR  lpszAppName )
{
    LPTSTR  baseName = NULL;

    DEBUGPRIVATE ("PrintUsage()");

    baseName = strrchr (lpszAppName, '\\');

    if (baseName)
        baseName++;
    else
        baseName = lpszAppName;

    printf ("Privileged Access to Message Store Example\n" \
            "\n" \
            "USAGE: %s [/?] profile [password]\n",
            baseName);

}

//$--PrintHelp------------------------------------------------------------------
// Print help text
// -----------------------------------------------------------------------------
static VOID PrintHelp (
    INLPTSTR  lpszAppName )
{
    DEBUGPRIVATE ("PrintHelp()");

    PrintUsage (lpszAppName);

    printf ("\n" \
            "   /?,/HELP    show help information\n" \
            "   profile     the MAPI profile name for logon\n" \
            "   password    the password associated with the profile\n");
}

//$--HrParseCommandLine---------------------------------------------------------
// Simple command line parsing
// -----------------------------------------------------------------------------
static HRESULT HrParseCommandLine (
    IN  int     argc,
    IN  char    **argv )
{
    HRESULT     hr = NOERROR;
    LPTSTR      pszArgument = NULL;
    int         i = 0;

    DEBUGPRIVATE ("HrParseCommandLine()");

    hr = CHK_main (argc, argv);
    if (FAILED(hr))
        RETURN (hr);

    if (argc < 2) {
        PrintUsage(argv[0]);
        printf ("\nUse /? for help details\n");
        fHelpRequested = TRUE;
        goto cleanup;
    }

    // Scan first for /? or /HELP
    for (i = 1; i < argc; i++)
    {
        pszArgument = argv[i];
        if (*pszArgument == '/' || *pszArgument == '-')
        {
            pszArgument++;
            if (*pszArgument && (*pszArgument == '?' ||
                _tcsnicmp (pszArgument, TEXT("HELP"), _tcslen (pszArgument)) == 0))
            {
                PrintHelp (argv[0]);
                fHelpRequested = TRUE;
                goto cleanup;
            }

            // We only recognize /? or /HELP
            printf ("ERROR: Unknown command flag '/%s'\n", pszArgument);
            hr = HR_LOG (E_FAIL);
            goto cleanup;
        }
    }

    if (argc > 3)
    {
        printf ("ERROR: Too many arguments on command line\n");
        hr = HR_LOG (E_FAIL);
        goto cleanup;
    }

    lpszProfileName = argv[1];

    if (argc == 3)
        lpszPassword = argv[2];

cleanup:
    RETURN (hr);
}

//$--main-----------------------------------------------------------------------
// Initialize the application, loop on asking user for mailboxes and display
// their subject properties.
// -----------------------------------------------------------------------------
int main( int argc, char *argv[])
{
    HRESULT     hr                      = NOERROR;
    TCHAR       szMailbox[MAX_INPUT_SZ] = TEXT("");
    TCHAR       szServer[MAX_INPUT_SZ]  = TEXT("");
    LPENTRYID   lpEntryID               = NULL;
    LPMDB       lpMDB                   = NULL;     // Message store ptr.
    LPMAPISESSION lplhSession           = NULL;     // MAPI session handle ptr.
    LPMDB       lpMailboxMDB            = NULL;     // Mailbox message store ptr.
    ULONG       cStrLen                 = 0L;

    hr = HrParseCommandLine (argc, argv);
    if (FAILED(hr) || fHelpRequested)
        goto cleanup;

    hr = HrInitialize( lpszProfileName, lpszPassword, &lplhSession, &lpMDB);
    if( FAILED( hr))
    {
        printf( lpszReasonForFailure);
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Loop until terminated by user.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    while( TRUE)
    {
        TCHAR *pNewLine;

        printf( "\n---------------------------------------------------------------------------\n"
                "Enter the name of the SERVER that contains the mail box\n"
                "or EXIT to terminate.\n"
                "EXAMPLE: /O=<Organization>/OU=<Site>/CN=Configuration/CN=Servers/CN=<ServerName>\n"
                ": ");

        // Get next address.  EOF = terminate.
        if( _fgetts( szServer, MAX_INPUT_SZ, stdin) == NULL)
            break;

        // Trim the trailing newline ('\n').
        pNewLine = _tcschr( szServer, '\n');
        if( pNewLine != NULL)
            *pNewLine = 0;

        //  Check for exit.
        if( _tcsicmp( szServer, TEXT( "EXIT")) == 0)
            break;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        printf( "\n\n"
                "Enter the name of the mail box to access\n"
                "or EXIT to terminate.\n"
                "EXAMPLE: /O=<Organization>/OU=<Site>/CN=Recipients/CN=<MailboxName>\n"
                ": ");

        // Get next address.  EOF = terminate.
        if( _fgetts( szMailbox, MAX_INPUT_SZ, stdin) == NULL)
            break;

        // Trim the trailing newline ('\n').
        pNewLine = _tcschr( szMailbox, '\n');
        if( pNewLine != NULL)
            *pNewLine = 0;

        //  Check for exit.
        if( _tcsicmp( szMailbox, TEXT( "EXIT")) == 0)
            break;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Change Server DN to Message Store DN by adding next container in heirarchy.
        // Don't need to do this if '/cn=' has already been supplied.

        cStrLen = _tcslen( szServer);

        if( cStrLen >= _tcslen(TEXT("/cn=NULL")) &&
            ( _tcsicmp( &szServer[cStrLen - _tcslen(TEXT("/cn="))], TEXT("/cn=")) != 0 &&
              _tcsicmp( &szServer[cStrLen - _tcslen(TEXT("/cn=NULL"))], TEXT("/cn=NULL")) != 0))
        {
            _tcsncat( szServer, TEXT("/cn=Microsoft Private MDB"), MAX_INPUT_SZ - cStrLen);
        }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Logon to the mailbox specified by the user.
        hr = HrMailboxLogon( lplhSession, lpMDB, szServer, szMailbox, &lpMailboxMDB);
        if( FAILED( hr))
        {
            switch( hr)
            {
                case EDK_E_NOT_FOUND:
                    printf( "\nYou do not have permission to access this server\n"
                            "or the expected registry key does not exist.\n");
                    break;

                default:
                    printf( "\nFailed to logon to the mailbox:\n"
                            "  Server:  %s\n"
                            "  Mailbox: %s\n", szServer, szMailbox);
            }
        }
        else
        {   // Display the subject property of the messages found in the mailbox.
            hr = HrDispMailboxSubjects( lpMailboxMDB);
            if( FAILED( hr));

            // Logoff the mailbox.
            hr = HrMailboxLogoff( &lpMailboxMDB);
            if( FAILED( hr));
        }
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Clean up
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Terminate the MAPI session properly.
    hr = HrShutDown( &lplhSession, &lpMDB);
    if( FAILED( hr));

cleanup:
    // return exit code
    return _nEcFromHr(hr) ;
}

// -----------------------------------------------------------------------------


GLOBAL.CPP

//--global.cpp----------------------------------------------------------------
//
// Contains global object declarations.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//---------------------------------------------------------------------------

#include "edkafx.h"
#define _GLOBAL_OBJECTS_

// ---------------------------------------------------------------------------
// The purpose of this C++ file is to instantiate global objects in an order
// that can be depended upon.
//
// In C++, if you have a global object whose constructor depends upon another
// object having been constructed, you can run into problems with the
// randomness which the many object modules get linked.
//
// By instantiating all dependant objects in a single source module,
// construction will occur in the order defined.
//
// Declarations are surrounded by #ifdef _GLOBAL_OBJECTS_ and
// #endif //_GLOBAL_OBJECTS_.  This will cause them to be defined only once
// in this module (global.cpp)
// ---------------------------------------------------------------------------

#include "exadmin.h"    // This header file MUST be listed here.
#include "mbxadmin.h"
#include "errcpp.h"     // This MUST be included so that CHRESULT works.


MBXADMIN.CPP

//--mbxadmin.cpp--------------------------------------------------------------
//
// Implements an Exchange Administration property sheet dialog.
//
// This file is the class that display's the property sheet in Exchange's
// Admin program.  It also modify's the extension-data for a mailbox.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "edkafx.h"

#include "exadmin.h"
#include "resource.h"
#include "mbxdata.h"
#include "mbxadmin.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

CMbxAdminDlg::CMbxAdminDlg()
    : CAdminDialog( IDD_MBXADMIN, IDS_MBXADMIN, MBXBLOBNAME)
{
    m_strPassword  = _T("");
    m_strPassword2 = _T("");
}

BEGIN_MESSAGE_MAP(CMbxAdminDlg, CAdminDialog)
//{{AFX_MSG_MAP(CMbxAdminDlg)
    ON_EN_CHANGE(IDC_PASSWORD, OnChange)
    ON_EN_CHANGE(IDC_PASSWORD2, OnChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMbxAdminDlg::DoDataExchange(CDataExchange* pDX)
{
CAdminDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMbxAdminDlg)
DDX_Text(pDX, IDC_PASSWORD, m_strPassword);
    DDX_Text(pDX, IDC_PASSWORD2, m_strPassword2);
//}}AFX_DATA_MAP
}

//$--CMbxAdminDlg::OnInitDialog()---------------------------------------------
// Initialize the dialog with extension data.
// Create extension data if necessary.
// ---------------------------------------------------------------------------

BOOL CMbxAdminDlg::OnInitDialog()
{
    CAdminDialog::OnInitDialog();

    m_strPassword = m_strPassword2 = GetExtString(IDX_PASSWORD);

    UpdateData(FALSE);  // Send to the window

    // If there are no properties in our blob...
    if( GetExtCount() == 0)
    {   // NO, extension data is not available so set the memory copy of this
        // to the defaults.

        // The following text string pointers are cast as LONGLONG here
        // because  a bug in the MIPS C++ compiler causes problems if we try
        // to do the cast inside the ExtensionProps structure.

        static const LONGLONG llszPassword    = (LONGLONG) TEXT("");

        static SInitPropValue ExtensionProps[] =
        {
            { PT_STRING8, 0, llszPassword },
        };

        // Initialize the property value array used to create the extension
        // data blob.
        if( FAILED( HrSetExtProps( ARRAY_CNT( ExtensionProps), ExtensionProps)))
            return( TRUE);

        // Save it in Admin's memory buffer as well.
        CAdminDialog::bSaveData();
    }

    return( TRUE);  // return TRUE unless you set the focus to a control
}

//$--CMbxAdminDlg::bSaveData()------------------------------------------------
// Called when a different property sheet has been selected or when either the
// OK or APPLY NOW button is pressed.  Returns TRUE if data has been validated
// and saved.
// ---------------------------------------------------------------------------

BOOL CMbxAdminDlg::bSaveData()
{
    HRESULT hr;
    UpdateData (TRUE);

if (m_strPassword != m_strPassword2)
return (FALSE);

    hr = HrModExtString( IDX_PASSWORD, m_strPassword);
    if( FAILED( hr))
        return( FALSE);

    return( CAdminDialog::bSaveData());
}

//$--CMbxAdminDlg::OnChange()-------------------------------------------------
// Called when either edit box changes
// ---------------------------------------------------------------------------

void CMbxAdminDlg::OnChange()
{
    DataHasChanged();
}


MBXADMIN.H

//--mbxadmin.h----------------------------------------------------------------
//
// Declaration for the CMbxAdminDlg class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

class CMbxAdminDlg : public CAdminDialog
{
public:
    CMbxAdminDlg();

protected:
    virtual BOOL bSaveData();

public:
//{{AFX_DATA(CMbxAdminDlg)
CString m_strPassword;
        CString m_strPassword2;
//}}AFX_DATA

protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMbxAdminDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//}}AFX_VIRTUAL

// Generated message map functions
//{{AFX_MSG(CMbxAdminDlg)
virtual BOOL OnInitDialog();
    afx_msg void OnChange();
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_

// Declare any instances of a CAdminDialog derived class.
CMbxAdminDlg dlg;

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by mbxadmin.rc
//
#define IDC_ADMINICON                   1000
#define IDC_TITLE                       1001
#define IDC_PASSWORD                    1035
#define IDC_PASSWORD2                   1036
#define IDS_WARNING_NO_DATA_FOUND       2007
#define IDS_WARNING_ACL_CHANGE          2008
#define IDS_MESSAGE_BOX_TITLE           2009
#define IDS_MESSAGE_BOX_WARNING         2010
#define IDS_ERROR_CONFIG_UNAVAILABLE    2012
#define IDS_ERROR_CONFIG_CORRUPT        2014
#define IDS_MBXADMIN                    2017
#define IDS_INVALID_STRING              2019
#define IDS_INVALID_POLLING_FREQUENCY   2020
#define IDD_MBXADMIN                    2020

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        2021
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1037
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


DABOUT.CPP

//--dabout.cpp----------------------------------------------------------------
//
// implementation of the CAboutDlg class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "mbxserv.h"

#include "dabout.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


DABOUT.H

//--dabout.h------------------------------------------------------------------
//
// declaration of the CAboutDlg class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


DCHKMAIL.CPP

//--dchkmail.cpp--------------------------------------------------------------
//
// implementation of the CCheckMailDlg class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "mbxserv.h"

#include "oditem.h"// COwnerDrawItem
#include "odlist.h"// COwnerDrawListBox
#include "dchkmail.h"
#include "dabout.h"// CAboutDlg

#include "mbxdata.h"
#include "emsabtag.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define MAX_MESSAGE_HDRS    50   //Max. messages per query

/////////////////////////////////////////////////////////////////////////////
// CCheckMailDlg dialog

CCheckMailDlg::CCheckMailDlg(CWnd* pParent /*=NULL*/)
: CDialog(CCheckMailDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CCheckMailDlg)
m_strAlias = _T("");
m_strPassword = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_lpGAL = NULL;
m_lpSession = NULL;
m_lpMDB = NULL;
}

void CCheckMailDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCheckMailDlg)
DDX_Control(pDX, IDC_MAIL, m_lbMail);
DDX_Text(pDX, IDC_ALIAS, m_strAlias);
DDX_Text(pDX, IDC_PASSWORD, m_strPassword);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CCheckMailDlg, CDialog)
//{{AFX_MSG_MAP(CCheckMailDlg)
ON_WM_SYSCOMMAND()
ON_BN_CLICKED(IDOK, OnGetMail)
ON_EN_CHANGE(IDC_ALIAS, OnChange)
ON_EN_CHANGE(IDC_PASSWORD, OnChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCheckMailDlg message handlers

BOOL CCheckMailDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERTERROR((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX,
            "IDM_ABOUTBOX is out of range");
    ASSERTERROR(IDM_ABOUTBOX < 0xF000,"IDM_ABOUTBOX is out of range");

CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}

// Set the icon for this dialog.  The framework does this automatically
//  when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);// Set big icon
SetIcon(m_hIcon, FALSE);// Set small icon

    SetClassLong (m_hWnd, GCL_HICON, (LONG)m_hIcon);

OnChange();

return TRUE;  // return TRUE  unless you set the focus to a control
}

void CCheckMailDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

void CCheckMailDlg::OnGetMail()
{
HRESULT hr;
ULONGulLoop;
LPSTRlpszMsgStoreDN= NULL;
LPSTRlpszMailboxDN= NULL;
SBinaryArray *lpbaExtData= NULL;

LPMAPITABLElpGALTable= NULL;
LPMDBlpMailboxMDB= NULL;
LPSRowSetlpRowSet= NULL;

SRestrictionresNickname;
SPropValueprop;

m_lbMail.ResetContent();// Clear listbox

    UpdateData (TRUE);

    ASSERTERROR (!m_strAlias.IsEmpty(), "Can't get mail with out an alias");
ASSERTERROR (!m_strPassword.IsEmpty(), "Can't get mail with out a password");

    ASSERTERROR (m_lpGAL != NULL, "No pointer to the global address list");

static const SizedSPropTagArray(3, StoreAttributes) =
{
3,
{
PR_EMAIL_ADDRESS,// lpszMsgStoreDN
PR_EMS_AB_HOME_MDB,// lpszMailboxDN
PR_EMS_AB_EXTENSION_DATA,     // extension data
}
};

    hr = m_lpGAL->GetContentsTable (MAPI_DEFERRED_ERRORS, &lpGALTable);

    if (FAILED(hr))
goto cleanup;

resNickname.rt = RES_CONTENT;
resNickname.res.resContent.ulFuzzyLevel = FL_PREFIX;// FL_FULLSTRING == MAPI_E_TOO_COMPLEX
resNickname.res.resContent.ulPropTag = PR_ACCOUNT;
resNickname.res.resContent.lpProp = &prop;
prop.ulPropTag = PR_ACCOUNT;
prop.Value.lpszA = (LPSTR)(LPCSTR)m_strAlias;

    hr = HrQueryAllRows (
            lpGALTable,
            (LPSPropTagArray)&StoreAttributes,
            &resNickname,
            NULL,
            2,
            &lpRowSet);

    if (FAILED(hr))
goto cleanup;

if (lpRowSet->cRows != 1)
{
AfxMessageBox ("Name not found or name is ambiguous.");
goto cleanup;
}

ASSERTERROR (lpRowSet->cRows == 1, "Using ambiguous name");

// go find the values we need
for (ulLoop = 0;ulLoop < lpRowSet->aRow[0].cValues; ulLoop++)
{
switch (lpRowSet->aRow[0].lpProps[ulLoop].ulPropTag)
{
case PR_EMS_AB_EXTENSION_DATA:
lpbaExtData = &lpRowSet->aRow[0].lpProps[ulLoop].Value.MVbin;
break;
case PR_EMS_AB_HOME_MDB:
lpszMsgStoreDN = lpRowSet->aRow[0].lpProps[ulLoop].Value.lpszA;
break;
case PR_EMAIL_ADDRESS:
lpszMailboxDN = lpRowSet->aRow[0].lpProps[ulLoop].Value.lpszA;
break;
}
}

if (lpbaExtData == NULL)
{
AfxMessageBox ("Mailbox has not been configured");
goto cleanup;
}

if ((lpszMsgStoreDN == NULL) || (lpszMailboxDN == NULL))
{
AfxMessageBox ("Could not find message store information");
goto cleanup;
}

hr = HrCheckPassword (lpbaExtData, m_strPassword);

    if (FAILED(hr))
goto cleanup;

    hr = HrMailboxLogon (m_lpSession, m_lpMDB, lpszMsgStoreDN, lpszMailboxDN,
            &lpMailboxMDB);

    if (FAILED(hr))
goto cleanup;

    hr = HrDispMailboxSubjects (lpMailboxMDB);

    if (FAILED(hr))
        goto cleanup;

cleanup:
    if (lpMailboxMDB)
        hr = HrMailboxLogoff (&lpMailboxMDB);

    ULRELEASE(lpGALTable);
    ULRELEASE(lpMailboxMDB);
MAPIFREEBUFFER(lpRowSet);
}

void CCheckMailDlg::OnChange()
{
// Enable Get Mail button only when we have an alias and password

UpdateData (TRUE);

CWnd * pWnd = GetDlgItem (IDOK);
ASSERTERROR (pWnd != NULL, "Could not find OK button");

if (m_strAlias.IsEmpty() || m_strPassword.IsEmpty())
{
pWnd->EnableWindow (FALSE);
}
else
{
pWnd->EnableWindow (TRUE);
}
}

HRESULT CCheckMailDlg::HrDispMailboxSubjects(LPMDB  lpMDB)
{
    HRESULT         hr                  = NOERROR;
    unsigned        iMsgIndx            = 0;
    unsigned        nCnt                = 0;
    ULONG           cbeid               = 0L;
    ULONG           ulObjType           = 0L;
    LPENTRYID       lpeid               = NULL;
    LPMAPIFOLDER    lpInboxFolder       = NULL;     // ptr to Inbox folder
    LPMAPITABLE     lpContentsTbl       = NULL;     // ptr to Contents folder table
    LPSRowSet       lpInRowSet          = NULL;     // ptr to Inbox rows
    LPSPropValue    lpProps             = NULL;     // Ptr to property array.

    static const SizedSSortOrderSet(1L,sSortPrioSet) =
        { 1L, 0L, 0L, { PR_CLIENT_SUBMIT_TIME, TABLE_SORT_ASCEND}};

    static const SizedSPropTagArray(3L,sPropColumns) =
        { 3L, {PR_SUBJECT, PR_MESSAGE_FLAGS, PR_ENTRYID}};

    DEBUGPRIVATE( "HrDispMailboxSubjects()");

    if (lpMDB == NULL)
        return E_INVALIDARG;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize an inbox folder object.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Find default (inbox) folder.
    hr = HrMAPIFindInbox( lpMDB, &cbeid, &lpeid);
    if( FAILED(hr))
        goto cleanup;
    ASSERTERROR ( lpeid != NULL, "NULL lpeid");

    // Open the default (inbox) folder.
    hr = lpMDB->OpenEntry(cbeid, lpeid, NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN*)&lpInboxFolder);
    if( FAILED( hr))
        goto cleanup;
    if( ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Now that we have a folder we can initialize a contents table
// to look through.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the contents table for the folder.
    hr = lpInboxFolder->GetContentsTable(
MAPI_DEFERRED_ERRORS, &lpContentsTbl);
    if( FAILED( hr))
        goto cleanup;

    // Set the columns to return.
    hr = lpContentsTbl->SetColumns((LPSPropTagArray) &sPropColumns, 0L);
    if(FAILED(hr))
        goto cleanup;

    // Sort the table.
    hr = lpContentsTbl->SortTable((LPSSortOrderSet) &sSortPrioSet, 0L);
    if(FAILED(hr))
        goto cleanup;

    // Position to the beginning of the contents table.
    hr = lpContentsTbl->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Contents table has been initialize so lets display it.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Display INBOX subjects until end of table reached.
    while (TRUE)
    {   // Read a group of message headers from the contents table.
        hr = HrGetMsgList( lpContentsTbl, MAX_MESSAGE_HDRS, &lpInRowSet);
        if (hr == MAPI_E_NOT_FOUND)
            break;

        if (FAILED( hr))
            goto cleanup;;

        ASSERTERROR( lpInRowSet != NULL, "NULL lpInRowSet!");

        // Process all messages in the current table.
        for (iMsgIndx = 0; iMsgIndx < lpInRowSet->cRows; ++iMsgIndx )
        {   // Display the subject name.
            lpProps = lpInRowSet->aRow[iMsgIndx].lpProps;

            ASSERTERROR(PROP_ID(lpProps->ulPropTag) == PROP_ID(PR_SUBJECT),
                        "Invalid property returned!");

CString strSubject;
if (lpProps->Value.LPSZ == NULL || *(lpProps->Value.LPSZ) == 0)
{
strSubject = "  SUBJECT: (empty).";
}
            else
{
strSubject = lpProps->Value.LPSZ;
}

COwnerDrawItem * pODI = new COwnerDrawItem();
if (lpProps[1].Value.l & MSGFLAG_READ)
{
pODI->Create (NULL, strSubject);
}
else
{
pODI->Create (NULL, strSubject,ODI_BOLDTEXT);
}

m_lbMail.AddODI(pODI);
nCnt ++;
        }

// Free the current message list structure.
        FREEPROWS( lpInRowSet);
    }

if (nCnt == 0)
{
COwnerDrawItem * pODI = new COwnerDrawItem();
pODI->Create (NULL, "No messages found");
m_lbMail.AddODI(pODI);
}

cleanup:
return S_OK;
}

HRESULT CCheckMailDlg::HrGetMsgList(
    LPMAPITABLE lpTable,    // ptr.to the contents table
    LONG       cMaxNRows,  // Max.# of rows to return
    LPSRowSet   *lppRows )  // ptr.to address variable for SRowSet
{
    HRESULT      hr  = NOERROR;

    if ((lpTable == NULL) ||
        (lppRows == NULL))
    {
        return E_INVALIDARG;
    }

    DEBUGPRIVATE( "HrGetMsgList()" );

    // Get the next list of messages in the folder
    hr = lpTable->QueryRows(cMaxNRows, 0L, lppRows );
    if( SUCCEEDED(hr) && ((*lppRows) != NULL) && ((*lppRows)->cRows == 0))
    {
        FREEPROWS(*lppRows);
        hr = MAPI_E_NOT_FOUND;
    }

    return hr;
}

HRESULT CCheckMailDlg::HrCheckPassword (SBinaryArray * lpbaExtData, LPCTSTR szPassword)
{
HRESULT hr = E_FAIL;

    if ((lpbaExtData == NULL) ||
        (szPassword == NULL))
    {
        return E_INVALIDARG;
    }
// For each blob returned
for (ULONG ulLoop = 0; ulLoop < lpbaExtData->cValues; ulLoop++)
{
// Find our blob (the name is the first thing; UNICODE)
if (wcsicmp (MBXBLOBNAMEW, (LPWSTR)lpbaExtData->lpbin[ulLoop].lpb) == 0)
{
LPSPropValue pvData = NULL;
ULONG ulData;

hr = HrCfgUnpackData (
lpbaExtData->lpbin[ulLoop].cb,
lpbaExtData->lpbin[ulLoop].lpb, NULL, &ulData, &pvData);

if (FAILED (hr))
return hr;

ASSERTERROR (ulData == 1, "Extension data in unknown format");

if (lstrcmp (szPassword, pvData[0].Value.lpszA) == 0)
{
hr = S_OK;
}
else
{
AfxMessageBox ("Invalid Password");
hr = E_FAIL;
}
MAPIFREEBUFFER (pvData);
}
}
return hr;
}


DCHKMAIL.H

//--dchkmail.h----------------------------------------------------------------
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////
// CCheckMailDlg dialog

class CCheckMailDlg : public CDialog
{
// Construction
public:
CCheckMailDlg(CWnd* pParent = NULL);// standard constructor

// Dialog Data
//{{AFX_DATA(CCheckMailDlg)
enum { IDD = IDD_VOXSERV_DIALOG };
CStringm_strAlias;
CStringm_strPassword;
//}}AFX_DATA
COwnerDrawListBoxm_lbMail;

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCheckMailDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//}}AFX_VIRTUAL
public:
LPABCONT m_lpGAL;// Global address book
LPMAPISESSION m_lpSession;
LPMDBm_lpMDB;

// Implementation
protected:
HICON m_hIcon;

HRESULT HrDispMailboxSubjects(LPMDB  lpMDB);
HRESULT HrGetMsgList(LPMAPITABLE lpTable, LONG cMaxNRows, LPSRowSet *lppRows);
HRESULT HrCheckPassword (SBinaryArray * lpbaExtData, LPCTSTR szPassword);

// Generated message map functions
//{{AFX_MSG(CCheckMailDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnGetMail();
afx_msg void OnChange();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


MBXSERV.CPP

//--mbxserv.cpp---------------------------------------------------------------
//
// implementation of the CMbxServApp class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "mbxserv.h"
#include "oditem.h"
#include "odlist.h"
#include "dchkmail.h"

#include "initguid.h"
#include "mapiguid.h"
#include "edkguid.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define SERVER_STORE_MASK   0x04

/////////////////////////////////////////////////////////////////////////////
// CMbxServApp

BEGIN_MESSAGE_MAP(CMbxServApp, CWinApp)
//{{AFX_MSG_MAP(CMbxServApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMbxServApp construction

CMbxServApp::CMbxServApp()
{
m_lpSession = NULL;
m_lpMDB= NULL;
m_lpAddrBook= NULL;
m_lpGAL= NULL;
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CMbxServApp object

CMbxServApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CMbxServApp initialization

BOOL CMbxServApp::InitInstance()
{
char szMsg[255];

// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls();// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

    HRESULT hr;
ULONG           ulObjType;
    ULONG           cbeid;
    LPENTRYID       lpeid = NULL;

hr = MAPIInitialize (NULL);

    if (FAILED(hr))
    {
        AfxMessageBox ("MAPIInitialize() failed");
        return FALSE;
    }

hr = (HRESULT) MAPILogonEx(
0,
NULL,           // lpszProfileName; let the user pick
NULL,
MAPI_NO_MAIL | MAPI_NEW_SESSION | MAPI_LOGON_UI,
&m_lpSession);

    if (FAILED(hr))
    {
        AfxMessageBox ("Could not logon to this profile.");
        return FALSE;
    }

    // Get the entry ID of the Exchange message store. We can't use default store,
    // as it may be a PST.
ASSERT_IUNKNOWN_PTR (m_lpSession, "Invalid m_lpSession");

    hr = HrOpenExchangePrivateStore(m_lpSession, &m_lpMDB);

if (FAILED(hr))
{
sprintf (szMsg, "Could not open exchange message store (0x%x)", hr);
AfxMessageBox (szMsg);
return FALSE;
}

    ASSERT_IUNKNOWN_PTR (m_lpMDB, "Invalid m_lpMDB");

hr = m_lpSession->OpenAddressBook (0, NULL, AB_NO_DIALOG, &m_lpAddrBook);

    if (FAILED(hr))
    {
        AfxMessageBox ("Could not open address book.");
        return FALSE;
    }

hr = HrFindExchangeGlobalAddressList (m_lpAddrBook, &cbeid, &lpeid);

    if (FAILED(hr))
    {
        AfxMessageBox ("Could not find exchange global address list.");
        return FALSE;
    }

hr = m_lpSession->OpenEntry (cbeid, lpeid, NULL, MAPI_BEST_ACCESS, &ulObjType, (LPUNKNOWN FAR *)&m_lpGAL);

    if (FAILED(hr))
    {
        AfxMessageBox ("Could not open exchange global address list.");
        return FALSE;
    }

    ASSERT_IUNKNOWN_PTR (m_lpGAL, "Invalid m_lpGAL");

CCheckMailDlg dlg;
m_pMainWnd = &dlg;
dlg.m_lpGAL = m_lpGAL;
dlg.m_lpSession = m_lpSession;
dlg.m_lpMDB = m_lpMDB;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
//  application, rather than start the application's message pump.
return FALSE;
}

int CMbxServApp::ExitInstance()
{
if (m_lpSession)
{
m_lpSession->Logoff(0,0,0);
ULRELEASE(m_lpSession);
}

ULRELEASE(m_lpMDB);
ULRELEASE(m_lpAddrBook);
ULRELEASE(m_lpGAL);

MAPIUninitialize();

return CWinApp::ExitInstance();
}


MBXSERV.H

//--mbxserv.h-----------------------------------------------------------------
//
// Main header file for the MBXSERV application
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"// main symbols

/////////////////////////////////////////////////////////////////////////////
// CMbxServApp:
// See voxserv.cpp for the implementation of this class
//

class CMbxServApp : public CWinApp
{
public:
CMbxServApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMbxServApp)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL

// Implementation
LPMAPISESSIONm_lpSession;
LPMDBm_lpMDB;
LPADRBOOKm_lpAddrBook;
LPABCONTm_lpGAL;

//{{AFX_MSG(CMbxServApp)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


MISCGDI.CPP

//--miscdgi.cpp---------------------------------------------------------------
//
// implementation of the DrawItem
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "miscgdi.h"
#include "oditem.h"

#define EXTRA_X 5

void DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
    CDC* pDC = CDC::FromHandle(lpDIS->hDC);
    COwnerDrawItem *pODI = (COwnerDrawItem *)lpDIS->itemData;

    if ((pODI == NULL) || (pODI == (COwnerDrawItem*)0xFFFFFFFF))
    {
        TRACE ("COwnerDrawListBox::DrawItem - Got invalid data\n");
        return;
    }

    COLORREF crOldTextColor;
    COLORREF crOldBkColor;

    CString strText (pODI->m_strDescription);

    CFont fnBold;
CFont * pOldFont = NULL;
if (pODI->m_dwFlags & ODI_BOLDTEXT)
{
TEXTMETRIC tm;
LOGFONT lf;
CString strFont;

memset (&lf, 0, sizeof (LOGFONT));

pDC->GetTextMetrics (&tm);
lf.lfHeight = tm.tmHeight;
lf.lfWeight = 700;

pDC->GetTextFace(strFont);
strncpy (lf.lfFaceName, strFont, 32);
fnBold.CreateFontIndirect(&lf);

pOldFont = pDC->SelectObject (&fnBold);
}

// rcSelection is to move the selection rect over to the right
CRect rcSelection (lpDIS->rcItem);

    if ( (lpDIS->itemAction & ODA_DRAWENTIRE) || (lpDIS->itemAction & ODA_SELECT) )
    {
        if (lpDIS->itemState & ODS_SELECTED)
        {
            // item is selected
            COLORREF crHighlight= GetSysColor (COLOR_HIGHLIGHT);
COLORREF crHighlightText= GetSysColor (COLOR_HIGHLIGHTTEXT);
            crOldTextColor = pDC->SetTextColor (crHighlightText);
            crOldBkColor = pDC->SetBkColor (crHighlight);   // For DrawText below
            CBrush br(crHighlight);
            CBrush bk(GetSysColor (COLOR_WINDOW));
            pDC->FillRect(&lpDIS->rcItem, &bk);
pDC->FillRect(rcSelection, &br);
        }

        if (!lpDIS->itemState & ODS_SELECTED)
        {
            // Item has been de-selected -- remove frame
            crOldTextColor = pDC->SetTextColor (pODI->m_crText);
            CBrush br(GetSysColor (COLOR_WINDOW));
            pDC->FillRect(&lpDIS->rcItem, &br);
        }

        CRect rcText (lpDIS->rcItem);

rcText.left += EXTRA_X;
        pDC->DrawText (strText, -1, rcText, DT_LEFT);
    }

    if ((lpDIS->itemState & ODS_FOCUS) &&
        (lpDIS->itemAction & (ODA_FOCUS | ODA_DRAWENTIRE)))
    {
        // Item has gotten the focus
        // DrawFocusRect (lpDIS->hDC, &lpDIS->rcItem);
DrawFocusRect (lpDIS->hDC, rcSelection);
    }

    if (!(lpDIS->itemState & ODS_FOCUS) &&
        (lpDIS->itemAction & ODA_FOCUS))
    {
        // Item has lost focus
        // DrawFocusRect (lpDIS->hDC, &lpDIS->rcItem);
DrawFocusRect (lpDIS->hDC, rcSelection);
    }

// Cleanup
if (pODI->m_dwFlags & ODI_BOLDTEXT)
{
pDC->SelectObject (pOldFont);
fnBold.DeleteObject();
}

    if ( (lpDIS->itemAction & ODA_DRAWENTIRE) || (lpDIS->itemAction & ODA_SELECT) )
    {
        if (lpDIS->itemState & ODS_SELECTED)
        {
            pDC -> SetBkColor(crOldBkColor);
            pDC -> SetTextColor(crOldTextColor);
        }
        else
        {
            // Item has been de-selected -- remove frame
            pDC -> SetTextColor(crOldTextColor);
        }
    }
}


MISCGDI.H

//--mbxserv.h-----------------------------------------------------------------
//
// DrawItem function
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

void DrawItem(LPDRAWITEMSTRUCT lpDIS);


ODITEM.CPP

//--dabout.cpp----------------------------------------------------------------
//
// implementation of the COwnerDrawItem class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "oditem.h"
#include "miscgdi.h"

#define new DEBUG_NEW
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// COwnerDrawItem

IMPLEMENT_DYNAMIC(COwnerDrawItem, CObject)

COwnerDrawItem::COwnerDrawItem()
 : m_crText (GetSysColor (COLOR_WINDOWTEXT))
{
}

COwnerDrawItem::~COwnerDrawItem()
{
}

void COwnerDrawItem::Create (CObject *pObject, const char *szDescription,
                             DWORD dwFlags)
{
    m_pObject           = pObject;
    m_strDescription    = szDescription;
    m_dwFlags           = dwFlags;
}

COwnerDrawItem& COwnerDrawItem::operator= (const COwnerDrawItem &rhs)
{
    if (this == &rhs)
        return *this;

    m_strDescription = rhs.m_strDescription;
    m_pObject        = rhs.m_pObject;
    m_dwFlags        = rhs.m_dwFlags;
    m_crText         = rhs.m_crText;
    m_crBkText       = rhs.m_crBkText;

    return *this;
}
/////////////////////////////////////////////////////////////////////////////
// COwnerDrawItem diagnostics

#ifdef _DEBUG
void COwnerDrawItem::AssertValid() const
{
    CObject::AssertValid();
}

void COwnerDrawItem::Dump(CDumpContext& dc) const
{
    CObject::Dump(dc);
}
#endif


ODITEM.H

//--oditem.h------------------------------------------------------------------
//
// interface of the COwnerDrawItem class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#define ODI_DELOBJECT   0x10    // delete data object associated with the ODI when ODI deleted
#define ODI_GOTDATA     0x20    // have data object filled in
#define ODI_DISABLED    0x40    // disabled item (greyed out)
#define ODI_BOLDTEXT    0x100   // draw bold text

class COwnerDrawItem : public CObject
{
public:
    COwnerDrawItem ();
    virtual ~COwnerDrawItem ();
    void Create (CObject* pObject, const char *szDescription, DWORD dwFlags = 0);

    COwnerDrawItem& operator= (const COwnerDrawItem &rhs);

    // Data members
    CString         m_strDescription;
    CObject        *m_pObject;          // User data
    DWORD           m_dwFlags;          // ODI_???
    COLORREF        m_crText;           // TextColor
    COLORREF        m_crBkText;         // Background Color

#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:
    DECLARE_DYNAMIC(COwnerDrawItem)
};


ODLIST.CPP

//--odlist.cpp----------------------------------------------------------------
//
// implementation of the COwnerDrawListBox class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"
#include "oditem.h"
#include "odlist.h"
#include "miscgdi.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// COwnerDrawListBox

IMPLEMENT_DYNCREATE(COwnerDrawListBox, CListBox)

BEGIN_MESSAGE_MAP(COwnerDrawListBox, CListBox)
    //{{AFX_MSG_MAP(COwnerDrawListBox)
    ON_WM_GETDLGCODE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

COwnerDrawListBox::COwnerDrawListBox ()
{
    m_nCurSel = -1;
}

COwnerDrawListBox::~COwnerDrawListBox ()
{
}

int COwnerDrawListBox::SetCurSel (int index)
{
    if ((index != LB_ERR) && (index < GetCount()))
    {
        COwnerDrawItem *pODI = GetODI (index);
        ASSERTERROR (pODI != NULL, "No owner draw item object found");

        if (pODI->m_dwFlags & ODI_DISABLED) // can not select disabled item
        {
            MessageBeep (0);
            // check if previous selection is not disabled and if so - find undesabled one
            COwnerDrawItem *pODIPrev = GetODI (m_nCurSel);
            COwnerDrawItem *pODINext;
            int             nCount   = GetCount ();
            int             i;

            // select first enabled
            if (pODIPrev->m_dwFlags & ODI_DISABLED)
            {
                m_nCurSel = -1;
                for (i = 0; i < nCount; i++)
                {
                    pODINext = GetODI (i);
                    ASSERTERROR (pODINext != NULL,"No owner draw item found");
                    if (!(pODINext->m_dwFlags & ODI_DISABLED))
                    {
                        m_nCurSel = i;
                        break;
                    }
                }
            }
        }
        else
        {
            m_nCurSel = index;
        }
    }
    else
    {
        m_nCurSel = index;
    }

    return CListBox::SetCurSel (m_nCurSel);
}

void COwnerDrawListBox::EnableString (int index, BOOL bEnable)
{
    ASSERTERROR (index != LB_ERR, "Invalid index used");

    COwnerDrawItem *pODI = GetODI (index);
    ASSERTERROR (pODI != NULL, "No owner draw item found");

    if (bEnable)
    {
        pODI->m_dwFlags &= ~(DWORD) ODI_DISABLED;
        pODI->m_crText   = RGB (0, 0, 0);
    }
    else
    {
        pODI->m_dwFlags |= ODI_DISABLED;
        pODI->m_crText   = GetSysColor (COLOR_GRAYTEXT);
    }
}

/////////////////////////////////////////////////////////////////////////////
// COwnerDrawListBox diagnostics

#ifdef _DEBUG
void COwnerDrawListBox::AssertValid() const
{
    CListBox::AssertValid();
}

void COwnerDrawListBox::Dump(CDumpContext& dc) const
{
    CListBox::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// COwnerDrawListBox commands

/////////////////////////////////////////////////////////////////////////////
// OWNER DRAW stuff

void COwnerDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
    // all items are of fixed size
    // must use LBS_OWNERDRAWVARIABLE for this to work
    lpMIS->itemHeight = 20;
}

void COwnerDrawListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
    ::DrawItem (lpDIS);
}

void COwnerDrawListBox::DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct)
{
    COwnerDrawItem *pItem = (COwnerDrawItem *)lpDeleteItemStruct->itemData;
    ASSERT_VALID (pItem);

    if (pItem->m_dwFlags & ODI_DELOBJECT)
        delete pItem->m_pObject;

    delete pItem;
    SendMessage (LB_SETITEMDATA, lpDeleteItemStruct->itemID, NULL);
}

UINT COwnerDrawListBox::OnGetDlgCode()
{
    return (DLGC_WANTMESSAGE | CListBox::OnGetDlgCode());
}

#ifdef _DEBUG
#define _MSX_INLINE
#include "odlist.inl"
#endif


ODLIST.H

//--odlist.h------------------------------------------------------------------
//
// interface of the COwnerDrawListBox class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

// Forward Reference
class COwnerDrawItem;

class COwnerDrawListBox : public CListBox
{
public:
    COwnerDrawListBox();
    virtual ~COwnerDrawListBox();

#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

    int m_nCurSel;
    // overrides for standard get and set functions to accomodate COwnerDrawItem
    // as a data wi save pODI, which has a pointer to the "real" data
    // all this is transperent to the user
    void*           GetItemDataPtr     (int index);
    DWORD           GetItemData        (int index);
    CObject*        GetItemDataObject  (int index);
    COwnerDrawItem* GetODI             (int index);
    int             SetItemDataPtr     (int index, void    *pData);
    int             SetItemData        (int index, DWORD   dwData);
    int             SetItemDataObject  (int index, CObject *pObject);
    int             SetODI             (int index, COwnerDrawItem *pODI);

    // override for standard Add and Insert functions
    // lpStr is a data obect that is stored as item data pointer
    // Default owner draw item is created for this version
    // if you need to create specific ODI, use the other overload
    int      AddString          (LPCSTR lpStr);
    int      AddODI             (COwnerDrawItem *pODI);
    int      InsertString       (int index, LPCSTR lpStr);
    int      InsertODI          (int index, COwnerDrawItem *pODI);

    // override for standard function to prevent selection of disabled item
    int      SetCurSel          (int index);

    // disable or enables item depending on bEnable
    void     EnableString       (int index, BOOL bEnable);

protected:
    DECLARE_DYNCREATE(COwnerDrawListBox)

protected:
// Overridables (must override draw, measure and compare for owner draw)
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
    virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
    virtual void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);

// Generated message map functions
protected:
    //{{AFX_MSG(COwnerDrawListBox)
    afx_msg UINT OnGetDlgCode();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

// Add inlines
#ifndef _DEBUG
#define _MSX_INLINE inline
#include "odlist.inl"
#endif


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by mbxserv.rc
//
#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDS_ABOUTBOX                    101
#define IDD_VOXSERV_DIALOG              102
#define IDR_MAINFRAME                   128
#define IDC_ALIAS                       1000
#define IDC_PASSWORD                    1001
#define IDC_MAIL                        1002

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        130
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1003
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

//--stdafx.cpp----------------------------------------------------------------
//
// source file that includes just the standard includes
//mbxserv.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "stdafx.h"


STDAFX.H

//--stdafx.h------------------------------------------------------------------
//
// include file for standard system include files, or project specific include
// files that are used frequently, but are changed infrequently
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#define VC_EXTRALEAN// Exclude rarely-used stuff from Windows headers

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT

#include "edk.h"


MSGEMIT.CPP

// --msgemit.cpp--------------------------------------------------------------
//
// API entry points for the msgemit static link library.
// Contains helper functions for converting MAPI messages to
// 822-style headers.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "msgemit.h"
#include "tagnames.h"
#include "msgemit.chk"

// Value to string lookup tables.
// These tables are referrend by offset for a speedy conversion

// Day of week lookup table
static const STRINGVALUEPAIRW rgDayW[nDays]  =
{
    {0, lpszSundayW      },
    {1, lpszMondayW      },
    {2, lpszTuesdayW     },
    {3, lpszWednesdayW   },
    {4, lpszThursdayW    },
    {5, lpszFridayW      },
    {6, lpszSaturdayW    }
};

static const STRINGVALUEPAIRA rgDayA[nDays]  =
{
    {0, lpszSundayA      },
    {1, lpszMondayA      },
    {2, lpszTuesdayA     },
    {3, lpszWednesdayA   },
    {4, lpszThursdayA    },
    {5, lpszFridayA      },
    {6, lpszSaturdayA    }
};

// Month of year lookup table
static const STRINGVALUEPAIRW rgMonthW[nMonths]  =
{
    {1,  lpszJanuaryW    },
    {2,  lpszFebruaryW   },
    {3,  lpszMarchW      },
    {4,  lpszAprilW      },
    {5,  lpszMayW        },
    {6,  lpszJuneW       },
    {7,  lpszJulyW       },
    {8,  lpszAugustW     },
    {9,  lpszSeptemberW  },
    {10, lpszOctoberW    },
    {11, lpszNovemberW   },
    {12, lpszDecemberW   }
};

static const STRINGVALUEPAIRA rgMonthA[nMonths]  =
{
    {1,  lpszJanuaryA    },
    {2,  lpszFebruaryA   },
    {3,  lpszMarchA      },
    {4,  lpszAprilA      },
    {5,  lpszMayA        },
    {6,  lpszJuneA       },
    {7,  lpszJulyA       },
    {8,  lpszAugustA     },
    {9,  lpszSeptemberA  },
    {10, lpszOctoberA    },
    {11, lpszNovemberA   },
    {12, lpszDecemberA   }
};

// Trace action lookup table
static const STRINGVALUEPAIR rgAction[nActions]    =
{
    { MD_AC_EXPANDED,    lpszExpanded   },  // MD_AC_EXPANDED = -2
    { MD_AC_REDIRECTED,  lpszRedirected },  // MD_AC_REDIRECTED = -1
    { MD_AC_RELAYED,     lpszRelayed    },  // MD_AC_RELAYED = 0
    { MD_AC_REROUTED,    lpszRerouted   }   // MD_AC_REROUTED = 1
};

// Priority string lookup table
static const STRINGVALUEPAIR rgPriority[nPriorities]   =
{
    { PRIO_NONURGENT,   lpszNonUrgent       },  // PRIO_NONURGENT = -1
    { PRIO_NORMAL,      lpszNormalUrgency   },  // PRIO_NORMAL = 0
    { PRIO_URGENT,      lpszUrgent          }   // PRIO_URGENT = 1
};

// Importance values lookup table
static const STRINGVALUEPAIR rgImportance[nImportances]    =
{
    { IMPORTANCE_LOW,       lpszLowImp      },  // IMPORTANCE_LOW = 0
    { IMPORTANCE_NORMAL,    lpszNormalImp   },  // IMPORTANCE_NORAML = 1
    { IMPORTANCE_HIGH,      lpszHighImp     }   // IMPORTANCE_HIGH = 2
};

// inline constant encoding "macro" used
// by HrEDKEncodeBinaryStreamDataToStream and
// HrEDKEncodeBinaryDataToStream.
static inline VOID EDKEncodeByte(       // RETURNS: VOID
    IN BYTE b,                          // byte to encode
    OUT CHAR szOutBuffer[])            // static output buffer
{
    wsprintf(
        szOutBuffer,            // output buffer
        "%2.2X ",               // format string
        b);                     // byte to encode
}

//$--HrCreateDateTimeString@-------------------------------------------------
//
// DESCRIPTION: Builds a date & time created string for a MAPI message file time
//
// INPUT:       lpFileTime  --  File Time structure pointer
//
// OUTPUT:      lppTimeString   --  Pointer to time string output buffer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if bad input,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL otherwise
//
// ---------------------------------------------------------------------------
HRESULT HrCreateDateTimeStringW(
    IN LPFILETIME lpFileTime,       // message file time pointer
    OUT LPWSTR * lppTimeString)     // pointer to time string buffer
{
    HRESULT     hr              =   NOERROR;    // return code
    SYSTEMTIME  sSystemTime     =   {0};        // system time structure
    WCHAR       szTempString[ulMaxOutStringLen] =   {0};        // for integer to ASCII conversions
    BOOL        fSucceeded      =   FALSE;      // Windows API return code

    DEBUGPUBLIC( "HrCreateDateTimeStringW()");

    // Check input parameters.
    hr = CHK_HrCreateDateTimeStringW(lpFileTime, lppTimeString);
    if ( FAILED(hr) )
        RETURN(hr);

    // Allocate memory for output string.
    hr = MAPIAllocateBuffer(
        ulMaxOutStringLen * sizeof(WCHAR),
        (PVOID *)lppTimeString);
    if ( FAILED(hr) )
        goto cleanup;

    **lppTimeString = L'\0';    // Set output string to empty string

    // If input time is all zeros, then we are done.  The
    // output string is the empty string.
    if ( (lpFileTime->dwLowDateTime == 0) &&
         (lpFileTime->dwHighDateTime == 0) )
    {
        goto cleanup;
    }

    // Convert FILETIME structure to a SYSTEMTIME structure
    fSucceeded = FileTimeToSystemTime(lpFileTime, &sSystemTime);
    if ( !fSucceeded )
    {
        GetSystemTime(&sSystemTime);
    }

    // Build new date time string.  New date time string format is:
    //
    // "DAY, DT MON YEAR HH:MM:SS"
    //
    // (Day, date month year hours:minutes:seconds)

    ASSERTERROR(sSystemTime.wDayOfWeek < nDays, "Bad sSystemTime.wDayOfWeek");

    // Set day of week (0 - 6)  (offset is 0)
    lstrcpyW( *lppTimeString,
            rgDayW[sSystemTime.wDayOfWeek].lpszString);

    // Set date (1-31).  Space pad it to two characters.
    wsprintfW(szTempString, L"%2.2d ", sSystemTime.wDay);
    lstrcatW(*lppTimeString, szTempString);

    ASSERTERROR(sSystemTime.wMonth <= nMonths, "Bad sSystemTime.wMonth");
    ASSERTERROR(sSystemTime.wMonth > 0, "Bad sSystemTime.wMonth");

    // Set month (1-12) (e.g. offset is 1)
    lstrcatW(*lppTimeString,
            rgMonthW[sSystemTime.wMonth - 1].lpszString);

    // Set year, hour, minute and second.
    wsprintfW(szTempString,
             L"%u %2.2u:%2.2u:%2.2u UT",   // format string
             sSystemTime.wYear,
             sSystemTime.wHour,
             sSystemTime.wMinute,
             sSystemTime.wSecond);
    lstrcatW(*lppTimeString, szTempString);

cleanup:
    if( FAILED(hr))
        MAPIFREEBUFFER(*lppTimeString);

    RETURN(hr);
}

HRESULT HrCreateDateTimeStringA(
    IN LPFILETIME lpFileTime,       // message file time pointer
    OUT LPSTR   * lppTimeString)    // pointer to time string buffer
{
    HRESULT     hr              =   NOERROR;    // return code
    SYSTEMTIME  sSystemTime     =   {0};        // system time structure
    CHAR        szTempString[ulMaxOutStringLen] =   {0};        // for integer to ASCII conversions
    BOOL        fSucceeded      =   FALSE;      // Windows API return code

    DEBUGPUBLIC( "HrCreateDateTimeStringA()");

    // Check input parameters.
    hr = CHK_HrCreateDateTimeStringA(lpFileTime, lppTimeString);
    if ( FAILED(hr) )
        RETURN(hr);

    // Allocate memory for output string.
    hr = MAPIAllocateBuffer(
        ulMaxOutStringLen * sizeof(CHAR),
        (PVOID *)lppTimeString);
    if (FAILED( hr))
        goto cleanup;

    **lppTimeString = '\0';    // Set output string to empty string

    // If input time is all zeros, then we are done.  The
    // output string is the empty string.
    if ( (lpFileTime->dwLowDateTime == 0) &&
         (lpFileTime->dwHighDateTime == 0) )
    {
        goto cleanup;
    }

    // Convert FILETIME structure to a SYSTEMTIME structure
    fSucceeded = FileTimeToSystemTime(lpFileTime, &sSystemTime);
    if ( !fSucceeded )
    {
        GetSystemTime(&sSystemTime);
    }

    // Build new date time string.  New date time string format is:
    //
    // "DAY, DT MON YEAR HH:MM:SS"
    //
    // (Day, date month year hours:minutes:seconds)

    ASSERTERROR(sSystemTime.wDayOfWeek < nDays, "Bad sSystemTime.wDayOfWeek");

    // Set day of week (0 - 6)  (offset is 0)
    lstrcpyA( *lppTimeString,
            rgDayA[sSystemTime.wDayOfWeek].lpszString);

    // Set date (1-31).  Space pad it to two characters.
    wsprintfA(szTempString, "%2.2d ", sSystemTime.wDay);
    lstrcatA(*lppTimeString, szTempString);

    ASSERTERROR(sSystemTime.wMonth <= nMonths, "Bad sSystemTime.wMonth");
    ASSERTERROR(sSystemTime.wMonth > 0, "Bad sSystemTime.wMonth");

    // Set month (1-12) (e.g. offset is 1)
    lstrcatA(*lppTimeString,
            rgMonthA[sSystemTime.wMonth - 1].lpszString);

    // Set year, hour, minute and second.
    wsprintfA(szTempString,
             "%u %2.2u:%2.2u:%2.2u UT",   // format string
             sSystemTime.wYear,
             sSystemTime.wHour,
             sSystemTime.wMinute,
             sSystemTime.wSecond);
    lstrcatA(*lppTimeString, szTempString);

cleanup:
    if( FAILED(hr))
        MAPIFREEBUFFER(*lppTimeString);

    RETURN(hr);
}

//$--HrCreateImportanceString------------------------------------------------
//
// DESCRIPTION: Creates an message importance string from a MAPI message importance value
//
// INPUT:       ulImportance    --  message's PR_IMPORTANCE value
//
// OUTPUT:      lppImportance --  Pointer to importance string buffer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems,
//                              E_FAIL otherwise.
//
// ---------------------------------------------------------------------------

HRESULT HrCreateImportanceString(                   //  RETURNS:    HRESULT
                    IN ULONG ulImportance,             // message's PR_IMPORTANCE value
                    OUT LPSTR * lppImportance)  // pointer to importance string buffer
{
    HRESULT hr      =   NOERROR;    // return code

    DEBUGPRIVATE("HrCreateImportanceString()");

    // Check input parameters.
    hr = CHK_HrCreateImportanceString(ulImportance, lppImportance);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Allocate memory for output string
    hr = MAPIAllocateBuffer(ulMaxOutStringLen * sizeof(CHAR),
                            (LPVOID *) lppImportance);

    if ( FAILED(hr) )
    {
        hr = E_OUTOFMEMORY;

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(*lppImportance, sizeof(LPSTR)),
                "Bad *lppImportance");

    **lppImportance = 0;    // set output string to empty string

    // Create importance string (offset is 0)
    lstrcpy(*lppImportance,
            rgImportance[ulImportance].lpszString);

cleanup:

    if ( FAILED(hr) )
    {
        // free the output buffer
        MAPIFREEBUFFER(*lppImportance);
    }

    RETURN(hr);

}   // end HrCreateImportanceString()

//$--HrEmitTagDataLine-------------------------------------------------------
//
// DESCRIPTION: Emits a line constructed from a static tag and dynamic
//              data to the specified stream.  If the data is the empty
//              string, writes the NULL byte (0x00) out to the stream.
//
// INPUT:       lpszTag --  tag portion of line
//              lpData      --  data portion of line (may be "")
//              lpStream    --  stream to write line to
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,d
//                              E_FAIL otherwise
//
// ---------------------------------------------------------------------------

HRESULT HrEmitTagDataLine(
        IN LPCSTR lpszTag,     // static portion of line
        IN LPSTR lpData,       // dynamic portion of line
        IN LPSTREAM lpStream)   // stream to write line to
{
    HRESULT hr              =   NOERROR;
    ULONG   cbWritten       =   0;      // # bytes written

    DEBUGPRIVATE("HrEmitTagDataLine()");

    // Check input parameters
    hr = CHK_HrEmitTagDataLine(lpszTag, lpData,
                               lpStream);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Write tag to stream.
    hr = lpStream->Write(lpszTag,
                         cbStrBytes(lpszTag),
                         &cbWritten);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR((cbWritten == cbStrBytes(lpszTag)), "Bad cbWritten");

    // Write colon to stream
    hr = lpStream->Write(lpszColon,
                         cbStrBytes(lpszColon),
                         &cbWritten);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR((cbWritten == cbStrBytes(lpszColon)), "Bad cbWritten");

    // Write data to stream
    hr = lpStream->Write(
                lpData,
                cbStrBytes(lpData),
                &cbWritten);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR((cbWritten == cbStrBytes(lpData)), "Bad cbWritten");

    // Write new line to stream (indicates end of line)
    hr = lpStream->Write(lpszNewLine,
                         cbStrBytes(lpszNewLine),
                         &cbWritten);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR((cbWritten == cbStrBytes(lpszNewLine)), "Bad cbWritten");

    // we are done.

cleanup:

    RETURN(hr);

}

//$--HrCreatePriorityString-------------------------------------------------------
//
// DESCRIPTION: Creates message priority data string from a MAPI priority value
//
// INPUT:       ulPriority  --  message's PR_PRIORITY value
//
// OUTPUT:      lppPriorityString --  message priority data string pointer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems
//                              E_FAIL otherwise
//
// ---------------------------------------------------------------------------

HRESULT HrCreatePriorityString(
        IN ULONG ulPriority,         // message's PR_PRIORITY value
        OUT LPSTR * lppPriorityString)   // message priority data string pointer
{
    HRESULT hr      =   NOERROR;

    DEBUGPRIVATE("HrCreatePriorityString()");

    // check input parameters
    hr = CHK_HrCreatePriorityString(ulPriority, lppPriorityString);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Allocate memory for output string
    hr = MAPIAllocateBuffer(ulMaxOutStringLen * sizeof(CHAR),
                            (LPVOID *) lppPriorityString);

    if ( FAILED(hr) )
    {
        hr = E_OUTOFMEMORY;

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(*lppPriorityString, sizeof(LPSTR)),
                "Bad *lppPriorityString");

    **lppPriorityString = 0;    // set output string to empty string

    // Assign appropriate string (offset is -1)
    lstrcpy(*lppPriorityString,
            rgPriority[ulPriority + nPrioOffset].lpszString);

    // we are done

cleanup:

    if ( FAILED(hr) )
    {
        // free output buffer
        MAPIFREEBUFFER(*lppPriorityString);
    }

    RETURN(hr);

}

//$--HrEDKEncodeBinaryStreamDataToStream-----------------------------------------------
//
// DESCRIPTION: Copies binary data from an input stream or
//              specified output stream,
//              encoding each byte as a two-character
//              hexadecimal ASCII representation of the byte.
//              E.g. 255 becomes 'FF'.
//
// INPUT:
//              lpStreamIn  --  input stream pointer
//              lpStreamOut --  output stream pointer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_FAIL otherwise
//
// ---------------------------------------------------------------------------
HRESULT HrEDKEncodeBinaryStreamDataToStream(
    IN LPSTREAM lpStreamIn,     // input stream pointer
    IN LPSTREAM lpStreamOut)    // output stream pointer
{
    HRESULT     hr                  =   NOERROR;    // return code
    ULONG       cbRead              =   0;          // # bytes read
    ULONG       cbWrite             =   0;          // # bytes to be written
    BYTE *      lpByteRead          =   NULL;       // pointer into bytes read buffer
    CHAR *     lpCharWrite         =   NULL;       // pointer into characters to write buffer
    ULONG       iLoopCount          =   0;          // loop counter

    BYTE       szBinaryData[nBytesEncodePerLine] = "";        // byte buffer to read data into
    CHAR       szOutputLine[ulMaxOutStringLen] =   {0};       // Converted ASCII text
    CHAR       szTempBuffer[ulMaxOutStringLen] =   {0};       // temporary buffer

    DEBUGPRIVATE("HrEDKEncodeBinaryStreamDataToStream()");

    // Check input parameters.
    hr = CHK_HrEDKEncodeBinaryStreamDataToStream(
                            lpStreamIn,
                            lpStreamOut);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Read data from input stream and
    // write encoded version to output stream.
    while ( TRUE )
    {
            // Read 24 bytes of text from input stream.
        hr = lpStreamIn->Read(
                    szBinaryData,   // buffer
                    nBytesEncodePerLine,      // # bytes
                    &cbRead);       // # bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // See if there are no more bytes to process (last line of
        // file has exactly 24 bytes)
        if ( cbRead == 0 )
        {
            // we are done!
            break;
        }

        // Convert each byte to a two-digit hexadecimal representation
        // of it.
        lpByteRead = szBinaryData;      // initialize pointer into input buffer
        lpCharWrite = szOutputLine;     // initialize pointer into write buffer
        *lpCharWrite = 0;               // null terminate output string
        for ( iLoopCount = 0; iLoopCount < cbRead;
              iLoopCount++ )
        {
            // Encode byte
            EDKEncodeByte(
                    *lpByteRead,        // byte to encode
                    szTempBuffer);      // output buffer

            // Append encoded by to output string
            lstrcpy(lpCharWrite, szTempBuffer);
            lpCharWrite += lstrlen(szTempBuffer);   // increment write pointer
            lpByteRead++;       // increment read pointer by one byte

        }   // end for

        // replace the last space with a new line character.
        lpCharWrite--;
        lstrcpy(lpCharWrite, lpszNewLine);

        cbWrite = lstrlen(szOutputLine) * sizeof(CHAR);

        // Write the converted attachment text from body stream to the output stream.
        // Decision:  Don't try to recover from a partial write.
        hr = lpStreamOut->Write(
                    szOutputLine,   // buffer
                    cbWrite,        // # bytes
                    NULL);          // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        if ( cbRead != nBytesEncodePerLine )
        {
            break;  // done reading from body stream
        }

        // do next set of input bytes

    }   // end while

    // we are done

cleanup:

    RETURN(hr);

}   // end HrEDKEncodeBinaryStreamDataToStream()

//$--HrCreateExternalTraceString------------------------------------------------
//
// DESCRIPTION: Creates a string from a message's external trace information.
//
// INPUT:       lAction --  trace action
//              lpCountry   --  country
//              lpADMDName  --  ADMD name
//              lpPRMDId    --  PRMD identifier
//
// OUTPUT:      lppTraceString  --  pointer to trace information string buffer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems
//                              E_FAIL otherwise,
//
// -----------------------------------------------------------------------------

HRESULT HrCreateExternalTraceString(
    IN LONG lAction,             // trace action
    IN LPSTR lpCountry,         // country name
    IN LPSTR lpADMDName,        // ADMD name
    IN LPSTR lpPRMDId,          // PRMD identifier
    OUT LPSTR * lppTraceString)    // trace information string pointer
{
    HRESULT         hr              =   NOERROR;

    DEBUGPRIVATE("HrCreateExternalTraceString()");

    hr = CHK_HrCreateExternalTraceString(
lAction,
lpCountry,
lpADMDName,
        lpPRMDId,
lppTraceString);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Allocate memory for output string.
    hr = MAPIAllocateBuffer(ulMaxOutStringLen * sizeof(CHAR),
                            (LPVOID *) lppTraceString);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(*lppTraceString, sizeof(LPSTR)),
                "Bad *lppTraceString");

    **lppTraceString = 0;    // Set output string to empty string

    // Check to see if the PRMD, ADMD and country strings are
    // empty.  If they are, then we are done (The trace string
    // to output is the empty string).
    if ( (*lpCountry == 0) && (*lpADMDName == 0) && (*lpPRMDId == 0) )
    {
        goto cleanup;
    }

    // Format of trace string is:
    // by /P=<Prmd> /A=<Admd> /C=<Country>/;<Action>;
    // NOTE:  The trace arrival date and time is printed out
    // separately.

    // Copy in "/P=" string
    lstrcpy(*lppTraceString, lpszPRMDId);

    // Add the PRMD data
    lstrcat(*lppTraceString, lpPRMDId);

    // Copy in the /A= string
    lstrcat(*lppTraceString, lpszADMDName);

    // Add the ADMD data
    lstrcat(*lppTraceString, lpADMDName);

    // Copy in the /C= string
    lstrcat(*lppTraceString, lpszCountry);

    // Add the country data
    lstrcat(*lppTraceString, lpCountry);

    // Copy in the /; string
    lstrcat(*lppTraceString, lpszCountryEnd);

    // Add the action data (offset is -2)
    lstrcat(*lppTraceString,
            rgAction[lAction + nActOffset].lpszString);

    // We are done.

cleanup:

    if ( FAILED(hr) )
    {
        // free output buffer
        MAPIFREEBUFFER(*lppTraceString);
    }

    RETURN(hr);

}

//$--HrCreateInternalTraceString------------------------------------------------
//
// DESCRIPTION: Creates a string from a message's internal trace information.
//
// INPUT:       lAction--  trace action
//              lpCountry   --  country
//              lpADMDName  --  ADMD name
//              lpPRMDId    --  PRMD identifier
//              lpMTAName   --  MTA name
//
// OUTPUT:      lppTraceString  --  pointer to trace information string buffer
//
// RETURNS:     HRESULT     --  NOERROR if successful,
//                              E_INVALIDARG if invalid parameter,
//                              E_OUTOFMEMORY if memory problems
//                              E_FAIL otherwise,
//
// -----------------------------------------------------------------------------
HRESULT HrCreateInternalTraceString(
    IN LONG lAction,             // trace action
    IN LPTSTR lpCountry,         // country name
    IN LPTSTR lpADMDName,        // ADMD name
    IN LPTSTR lpPRMDId,          // PRMD identifier
IN LPTSTR lpMTAName,         // MTA name
    OUT LPTSTR * lppTraceString)    // trace information string pointer
{
    HRESULT         hr              =   NOERROR;

    DEBUGPRIVATE("HrCreateInternalTraceString()");

    hr = CHK_HrCreateInternalTraceString(
lAction,
lpCountry,
lpADMDName,
        lpPRMDId,
lpMTAName,
lppTraceString);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Allocate memory for output string.
    hr = MAPIAllocateBuffer(ulMaxOutStringLen * sizeof(TCHAR),
                            (LPVOID *) lppTraceString);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(*lppTraceString, sizeof(LPTSTR)),
                "Bad *lppTraceString");

    **lppTraceString = 0;    // Set output string to empty string

    // Check to see if the PRMD, ADMD and country strings are
    // empty.  If they are, then we are done (The trace string
    // to output is the empty string).
    if ( (*lpCountry == 0) && (*lpADMDName == 0) &&
 (*lpPRMDId == 0) &&  (*lpMTAName == 0) )
    {
        goto cleanup;
    }

    // Format of trace string is:
    // by /M=<Mta> /P=<Prmd> /A=<Admd> /C=<Country>/;<Action>;
    // NOTE:  The trace arrival date and time is printed out
    // separately.

    // Copy in the /M= string
    lstrcpy(*lppTraceString, lpszMTAName);

    // Add the MTA data
    lstrcat(*lppTraceString, lpMTAName);

    // Copy in "/P=" string
    lstrcat(*lppTraceString, lpszPRMDId);

    // Add the PRMD data
    lstrcat(*lppTraceString, lpPRMDId);

    // Copy in the /A= string
    lstrcat(*lppTraceString, lpszADMDName);

    // Add the ADMD data
    lstrcat(*lppTraceString, lpADMDName);

    // Copy in the /C= string
    lstrcat(*lppTraceString, lpszCountry);

    // Add the country data
    lstrcat(*lppTraceString, lpCountry);

    // Copy in the /; string
    lstrcat(*lppTraceString, lpszCountryEnd);

    // Add the action data (offset is -2)
    lstrcat(*lppTraceString,
            rgAction[lAction + nActOffset].lpszString);

    // We are done.

cleanup:

    if ( FAILED(hr) )
    {
        // free output buffer
        MAPIFREEBUFFER(*lppTraceString);
    }

    RETURN(hr);

}

//$--HrGetRecipientList------------------------------------------
//
// DESCRIPTION: Utility function which retrieves columns
// desired from a MAPI recipient table.
//
// INPUT:   lpMessage   --  pointer to MAPI message
//          lpPropTags  --  list of columns (properties) to retrieve
//
// OUTPUT:  lppRows --  pointer to array of rows returned pointer.
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise
//
// -------------------------------------------------------------
HRESULT HrGetRecipientList(
        IN LPMESSAGE lpMessage,         // MAPI message  pointer
        IN LPSPropTagArray lpPropTags,  // properties (columns) desired
        OUT LPSRowSet * lppRows)        // pointer to rows returned
{
    HRESULT     hr      =   NOERROR;
    LPMAPITABLE lpTable =   NULL;       // MAPI table pointer

    DEBUGPRIVATE("HrGetRecipientList()");

    // check input parameters
    hr = CHK_HrGetRecipientList(lpMessage, lpPropTags,
                                  lppRows);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Open the report envelope's recipient table
    hr = lpMessage->GetRecipientTable(
        MAPI_DEFERRED_ERRORS | fMapiUnicode,    // flags
        &lpTable);     // MAPI table pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(lpTable),
                "Bad lpTable");

    // Retrieve all of the rows in the table.
    // (HrQueryAllRows does a SetColumns for us.)
    hr = HrQueryAllRows(lpTable,    // MAPI table pointer
                        lpPropTags, // columns to return
                        NULL,       // restriction pointer
                        NULL,       // order set pointer
                        0,          // maximum # rows (defaults to all)
                        lppRows);   // pointer to property value array pointer

    if ( FAILED(hr) || (hr == MAPI_W_POSITION_CHANGED) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(lppRows != NULL, "Bad lppRows");
    ASSERTERROR(!IsBadReadPtr(*lppRows, sizeof(SRowSet)),
                "Bad *lppRows");

cleanup:

    if ( FAILED(hr) )
    {
        FREEPROWS(*lppRows);
    }

    // Release MAPI objects
    ULRELEASE(lpTable);

    RETURN(hr);
}


MSGPARSE.CPP

// --msgparse.cpp-------------------------------------------------------------
//
// API entry points for the msgemit static link library.
// Contains helper functions for converting Ascii
// 822-style headers to MAPI messages.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include "edk.h"
#include "msgemit.h"
#include "tagnames.h"
#include "msgparse.chk"

// Value to string lookup tables.

// Day of week lookup table
static const STRINGVALUEPAIRA rgSortedDay[nDays]  =
{
    {5, lpszFridayA      },
    {1, lpszMondayA      },
    {6, lpszSaturdayA    },
    {0, lpszSundayA      },
    {4, lpszThursdayA    },
    {2, lpszTuesdayA     },
    {3, lpszWednesdayA   }
};

// Month of year lookup table
static const STRINGVALUEPAIRA rgSortedMonth[nMonths]  =
{
    {4,  lpszAprilA      },
    {8,  lpszAugustA     },
    {12, lpszDecemberA   },
    {2,  lpszFebruaryA   },
    {1,  lpszJanuaryA    },
    {7,  lpszJulyA       },
    {6,  lpszJuneA       },
    {3,  lpszMarchA      },
    {5,  lpszMayA        },
    {11, lpszNovemberA   },
    {10, lpszOctoberA    },
    {9,  lpszSeptemberA  }
};

// Trace action lookup table
static const STRINGVALUEPAIR rgAction[nActions]    =
{
    { MD_AC_EXPANDED,    lpszExpanded   },  // MD_AC_EXPANDED = -2
    { MD_AC_REDIRECTED,  lpszRedirected },  // MD_AC_REDIRECTED = -1
    { MD_AC_RELAYED,     lpszRelayed    },  // MD_AC_RELAYED = 0
    { MD_AC_REROUTED,    lpszRerouted   }   // MD_AC_REROUTED = 1
};

// Priority string lookup table
static const STRINGVALUEPAIR rgPriority[nPriorities]   =
{
    { PRIO_NONURGENT,   lpszNonUrgent       },  // PRIO_NONURGENT = -1
    { PRIO_NORMAL,      lpszNormalUrgency   },  // PRIO_NORMAL = 0
    { PRIO_URGENT,      lpszUrgent          }   // PRIO_URGENT = 1
};

// Importance values lookup table
static const STRINGVALUEPAIR rgImportance[nImportances]    =
{
    { IMPORTANCE_HIGH,      lpszHighImp     },  // IMPORTANCE_HIGH = 2
    { IMPORTANCE_LOW,       lpszLowImp      },  // IMPORTANCE_LOW = 0
    { IMPORTANCE_NORMAL,    lpszNormalImp   }   // IMPORTANCE_NORAML = 1
};

// static inline "macro" function for speed of execution.

//$--nAsciiHexToInt------------------------------------------------------------
// DESCRIPTION:
//
// Static inline function which converts a hexadecimal
// "character" to its equivalent integer value (e.g. 'F' to 15).
// This function is a static inline function with
// no parameter checking for speed!
//
// INPUT:   chHex   --  hexadecimal character
//
// RETURNS: UINT
//
// ----------------------------------------------------------------------------
static inline UINT nAsciiHexToInt(
            IN CHAR chHex)         // hexadecimal character
{
    // Covert hex character to decimal
    if ( isdigit(chHex) )
    {
        return(chHex - '0');
    }

    else
    {
        return((chHex - 'A') + 10);
    }
}

//$----HrFindStringValue--------------------------------------------------------
//
// DESCRIPTION: Convert a string to its associated
//              integer value.
//
// INPUT:       lpString  --  string
//              nValues --  number of values in array
//              rgValue --  string value pair array
//
// OUTPUT:      pulValue --  pointer to value
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
static HRESULT HrFindStringValue(
    IN LPSTR lpString,         // string
    IN UINT nValues,            // number values in array
    IN const STRINGVALUEPAIR rgValue[],   // string value pair array
    OUT ULONG * pulValue)       // value pointer
{
    HRESULT hr          =   NOERROR;
    UINT    iValue      =   0;      // current index into string value array
    INT     nRetVal     =   0;      // lstrcmp return value
    BOOL    fFound      =   FALSE;  // TRUE if value found

    DEBUGPRIVATE("HrFindStringValue()\n");

    // check input parameters
    hr = CHK_HrFindStringValue(lpString, nValues, rgValue, pulValue);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pulValue = 0;   // initialize

    // Find matching string
    // These arrays are small enough that a linear search is
    // acceptable.
    for ( iValue = 0; iValue < nValues; iValue++ )
    {
        nRetVal = lstrcmp(
                lpString,
                rgValue[iValue].lpszString);

        if ( nRetVal == 0 )     // strings match
        {
            // found it.  Assign value.
            *pulValue = rgValue[iValue].lValue;

            fFound = TRUE;

            break;
        }

        // continue with next entry

    }   // end for

    // Test to see if value found
    if ( fFound == FALSE )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$----HrReadNextString---------------------------------------------------------
//
// DESCRIPTION: Read string up to next separator character.
//
// INPUT:   lpString    --  input string
//          chSeparator --  separator character
//          cbBuffer    --  # bytes in output buffer
//
// OUTPUT:  pszOutput  --  output string
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise
//
// ----------------------------------------------------------------------------
static HRESULT HrReadNextString(
    IN LPSTR lpString,     // input string
    IN CHAR chSeparator,   // separator character
    IN UINT cbBuffer,       // # bytes in buffer
    OUT CHAR * pszOutput)  // output buffer
{
    HRESULT hr      =   NOERROR;
    LPSTR  lpTemp  =   NULL;       // temporary string pointer
    LPSTR  lpOut   =   NULL;       // pointer intot output string
    UINT    cbRead  =   0;          // count of bytes read into output buffer
    CHAR   cTemp   =   0;          // current character

    DEBUGPRIVATE("HrReadNextString()\n");

    // check input parameters
    hr = CHK_HrReadNextString(lpString, chSeparator, cbBuffer,
                              pszOutput);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pszOutput = 0;     // initialize output buffer

    lpOut = pszOutput;  // output string pointer
    lpTemp = lpString;  // input string pointer

    while ( cbRead < cbBuffer )
    {
        cTemp = *lpTemp;    // get current character

        // Test the current character
        if ( cTemp == chSeparator )
        {
            // We are done!
            goto cleanup;
        }

        if ( cTemp == 0 )
        {
            // we have reached the end of the string.
            // and haven't seen the separator.
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Otherwise, copy the character read into the
        // output buffer.
        *lpOut = cTemp;

        cbRead += sizeof(CHAR);    // increment # of characters read

        lpTemp++;   // increment pointer into input string
        lpOut++;    // increment pointer into output string

    }   // end while

    // we are done

cleanup:

    *lpOut = 0;

    RETURN(hr);

}

//$----HrParseTagAndData-------------------------------------------------------
//
// DESCRIPTION: Parse ASCII 822-style header line into
//              tag and data components.
//
// INPUT:       lpStream    --  stream pointer to read from
//
// OUTPUT:      pcbRead --  number of bytes read from stream
//              ppTag   --  pointer to tag buffer
//              ppData  --  pointer to data buffer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_OUTOFMEMORY if memory problem
//                          EDK_E_END_OF_FILE if end of stream
//                          E_FAIL otherwise
//
// ---------------------------------------------------------------------------
HRESULT HrParseTagAndData(
        IN LPSTREAM lpStream,   // stream pointer
        OUT ULONG * pcbRead,    // # of bytes read
        OUT LPSTR * lppTag,    // pointer to tag buffer
        OUT LPSTR * lppData)   // pointer to data buffer
{
    HRESULT         hr          =   NOERROR;
    CHAR           ch          =   0;          // character read
    ULONG           cb          =   0;          // number of bytes read
    ULONG           cbTag       =   0;          // number of bytes in tag
    ULONG           cbData      =   0;          // number of bytes in data
    LONGLONG   cbOffsetBack=   0;        // offset for Seek
LARGE_INTEGERsOffsetBack={0};// offset for Seek
    BOOL            fBlankLine  =   FALSE;      // TRUE if reading a blank line

    DEBUGPRIVATE("HrParseTagAndData()\n");

    // check input parameters
    hr = CHK_HrParseTagAndData(lpStream, pcbRead,
                               lppTag, lppData);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize output variables.
    *lppTag = 0;
    *lppData = 0;
    *pcbRead = 0;

    // Read in one character at a time until we read the
    // whole tag or encounter an error.
    while ( TRUE )
    {
        hr = lpStream->Read(
                &ch,            // buffer to read character into
                sizeof(CHAR),  // number of bytes to read
                &cb);           // number of bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // See if we have reached the end of the file.
        if ( cb == 0 )
        {
            // Early terminate of tag string.
            // This is an error.
            hr = HR_LOG(EDK_E_END_OF_FILE);

            goto cleanup;

        }   // end if end of file

        ASSERTERROR(cb == sizeof(CHAR), "Bad cb");

        // increment # bytes read
        *pcbRead += cb;
        cbTag += cb;    // increment # bytes in the tag

        // Test the character read.
        if ( ch == chColon )
        {
            // have read whole tag.
            // Go on to find the data.
            break;

        }   // end if colon character

        if ( ch == chNewLine )
        {
            // Read in a new line.
            // We have not found a tag.
            // This is an error if this is not a blank line
            // (i.e. \r\n).
            if ( *pcbRead == 2 )
            {
                // have a blank line.
                // This is not an error.
                fBlankLine = TRUE;

                // Pass back an empty tag and data
                cbTag = sizeof(CHAR);
                cbData = sizeof(CHAR);

                break;
            }

            // Otherwise, have a general error
            hr = HR_LOG(E_FAIL);

            goto cleanup;

        }   // end if new line character
    }   // end while looking for tag

    // Read in one character at a time until we read the
    // whole data string or encounter an error.
    // Don't read any more if we have read in a blank line.
    while ( TRUE && (fBlankLine == FALSE) )
    {
        hr = lpStream->Read(
                &ch,            // buffer to read character into
                sizeof(CHAR),  // number of bytes to read
                &cb);           // number of bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERTERROR(cb == sizeof(CHAR), "Bad cb");

        // See if we have reached the end of the file.
        if ( cb == 0 )
        {
            // Reached end of file before new line.
            // This is an error.
            hr = HR_LOG(EDK_E_END_OF_FILE);

            goto cleanup;

        }   // end if end of file

        // increment # bytes read
        *pcbRead += cb;

        // Don't count the carriage return character
        if ( ch != chReturn )
        {
            cbData += cb;           // increment number of data bytes
        }

        if ( ch == chNewLine )
        {
            // Found the end of this line.
            // We are done reading in the data.
            break;

        }   // end if new line character
    }   // end while looking for data

    // Allocate memory for the tag
    // (have room for null terminator, since
    // counted the colon which is not part of the tag.
    hr = MAPIAllocateBuffer(
        cbTag,      // # bytes
        (VOID **) lppTag);    // tag buffer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERT_WRITE_PTR(*lppTag, cbTag, "Bad *lppTag");

    // Initialize tag string
    **lppTag = 0;

    // Allocate memory for the data string.
    // We have room for the null, since we
    // counted the newline, which is not part
    // of the data string.
    hr = MAPIAllocateBuffer(
        cbData,     // # of bytes
        (VOID **) lppData);   // data string buffer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERT_WRITE_PTR(*lppData, cbData, "Bad *lppData");

    // Initialize data string
    **lppData = 0;

    // Read in tag and data if haven't already determined
    // that they are empty.
    if ( fBlankLine == FALSE )
    {
        // Seek back to where we were in the stream and copy in
        // the tag and data.
        cbOffsetBack = (*pcbRead) * ((LONGLONG) (-1));// # bytes read

sOffsetBack.LowPart = LOWLONG(cbOffsetBack);
sOffsetBack.HighPart = HILONG(cbOffsetBack);

        hr = lpStream->Seek(
            sOffsetBack,        // negative offset
            STREAM_SEEK_CUR,    // from current position
            NULL);              // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Read in the tag
        hr = lpStream->Read(
            *lppTag,    // destination buffer.
            cbTag,      // # bytes to read
            &cb);       // # bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERTERROR(cb == cbTag, "Bad cb");

        // Null terminate tag by replacing colon with a NULL character.
        *(*lppTag + (cbTag/sizeof(CHAR) - 1)) = 0;

        // Read in the data.
        hr = lpStream->Read(
            *lppData,   // destination buffer.
            cbData,     // # bytes to read
            &cb);       // # bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERTERROR(cb == cbData, "Bad cb");

        // Null terminate data by replacing carriage
        // return with a NULL character.
        ASSERTERROR(*(*lppData + (cbData/sizeof(CHAR) - 1)) == chReturn,
            "Bad *lppData last character");

        *(*lppData + (cbData/sizeof(CHAR) - 1)) = 0;

        // Read in the new line character.
        hr = lpStream->Read(
            &ch,            // buffer
            sizeof(CHAR),  // # bytes
            &cb);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERTERROR(cb == sizeof(CHAR), "Bad cb");
        ASSERTERROR(ch == chNewLine, "Bad ch");

    }   // end if not returning blank tag and data

    // we are done

cleanup:

    if ( FAILED(hr) )
    {
        MAPIFREEBUFFER(*lppTag);
        MAPIFREEBUFFER(*lppData);
    }

    RETURN(hr);

}

//$----HrParseDateTimeString----------------------------------------------------
//
// DESCRIPTION: Parses a date and time string into a file time
//              structure.
//
// INPUT:       lpDateTimeString    --  date and time string
//
// OUTPUT:      pFileTime   --  file time pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT HrParseDateTimeString(
    IN LPSTR lpDateTimeString,     // date and time string
    OUT FILETIME * pFileTime)       // file time pointer
{
    HRESULT     hr                              = NOERROR;
    CHAR       szTempBuffer[cbDateTimeString]  = {0};// temporary buffer
    LPSTR      lpTemp                          = NULL;    // string pointer
    SYSTEMTIME  sSystemTime                     = {0};     // system time structure
    ULONG       ulValue                         = 0;       // value pointer
    BOOL        fSucceeded                      = FALSE;   // Win32 API return code

    // constants
    const UINT  ncDay       =   5;      // length of day string
    const UINT  ncMonth     =   4;      // length of month string
    const UINT  ncDuoDigit  =   2;      // length of two-digit integer
    const UINT  ncYear      =   4;      // length of year

    DEBUGPRIVATE("HrParseDateTimeString()\n");

    // check input parameters
    hr = CHK_HrParseDateTimeString(lpDateTimeString, pFileTime);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ZeroMemory(pFileTime, sizeof(FILETIME));      // initialize output

    // If the date/time string is empty, we are done.
    // (This is a valid case.)
    if ( lstrcmp(lpDateTimeString, lpszBlank) == 0 )
    {
        goto cleanup;
    }

    lpTemp = lpDateTimeString;  // intialize string pointer

    // Read in day string
    lstrcpyn(szTempBuffer, lpTemp, ncDay + 1);

    // Find value associated with day of week.
    hr = HrFindStringValue(
            szTempBuffer,       // string
            nDays,              // number values
            rgSortedDay,        // string value pair array
            &ulValue);          // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    sSystemTime.wDayOfWeek = (WORD) ulValue;    // set day of week

    lpTemp += ncDay;  // increment string pointer

    // Read in day of month (2 digits)
    lstrcpyn(szTempBuffer, lpTemp, ncDuoDigit + 1);
    sSystemTime.wDay = atoi(szTempBuffer);

    // Check day of month
    if ( (sSystemTime.wDay == 0) || (sSystemTime.wDay > 31) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += ncDuoDigit;   // increment string pointer

    // Next character should be a space
    if ( *lpTemp != chSpace )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp ++;              // skip over space

    // Read in month string
    lstrcpyn(szTempBuffer, lpTemp, ncMonth + 1);

    // Find value associated with month.
    hr = HrFindStringValue(
            szTempBuffer,       // string
            nMonths,            // number values
            rgSortedMonth,      // string value pair array
            &ulValue);          // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    sSystemTime.wMonth = (WORD) ulValue;    // set month value

    lpTemp += ncMonth;  // increment string pointer

    // Read in year
    lstrcpyn(szTempBuffer, lpTemp, ncYear + 1);
    sSystemTime.wYear = atoi(szTempBuffer);

    // Check year
    if ( sSystemTime.wYear == 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += ncYear;   // increment string pointer

    // Next character should be a space
    if ( *lpTemp != chSpace )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp++;              // skip over space

    // read in hours
    lstrcpyn(szTempBuffer, lpTemp, ncDuoDigit + 1);
    sSystemTime.wHour = atoi(szTempBuffer);

    // check hour
    if ( sSystemTime.wHour > 23 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += ncDuoDigit;   // increment pointer

    // next character should be a colon
    if ( *lpTemp != chColon )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp++;   // skip over colon

    // read in minutes
    lstrcpyn(szTempBuffer, lpTemp, ncDuoDigit + 1);
    sSystemTime.wMinute = atoi(szTempBuffer);

    // check minutes
    if ( sSystemTime.wMinute > 59 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += ncDuoDigit;   // increment pointer

    // next character should be a colon
    if ( *lpTemp != chColon )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp++;   // skip over colon

    // read in seconds
    lstrcpyn(szTempBuffer, lpTemp, ncDuoDigit + 1);
    sSystemTime.wSecond = atoi(szTempBuffer);

    // check seconds
    if ( sSystemTime.wSecond > 59 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += ncDuoDigit;   // increment pointer

    // read in UT string
    lstrcpyn(szTempBuffer, lpTemp, lstrlen(lpszUniversalTime) + 1);

    // check universal time string
    if ( lstrcmp(szTempBuffer, lpszUniversalTime) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Convert the system time structure to a file time
    // structure.
    fSucceeded = SystemTimeToFileTime(
                &sSystemTime,       // system time pointer
                pFileTime);         // file time pointer

    if ( fSucceeded == FALSE )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done

cleanup:

    RETURN(hr);

}

//$----HrParsePriorityString----------------------------------------------------
//
// DESCRIPTION: Convert a priority string to its associated
//              integer value.
//
// INPUT:       lpPriority  --  priority string
//
// OUTPUT:      pulPriority --  pointer to priority value
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT HrParsePriorityString(
    IN LPSTR lpPriority,       // Priority string
    OUT ULONG * pulPriority)    // priority value pointer
{
    HRESULT hr          =   NOERROR;
    UINT    iPriority   =   0;      // index into priority array

    DEBUGPRIVATE("HrParsePriorityString()\n");

    // check input parameters
    hr = CHK_HrParsePriorityString(lpPriority, pulPriority);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pulPriority = 0;   // initialize

    // Find matching priority string
    hr = HrFindStringValue(
                lpPriority,     // string
                nPriorities,    // number of values
                rgPriority,   // string value pair array
                pulPriority);   // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$----HrParseImportanceString----------------------------------------------------
//
// DESCRIPTION: Convert an importance string to its associated
//              integer value.
//
// INPUT:       lpImportance  --  importance string
//
// OUTPUT:      pulImportance --  pointer to importance value
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ----------------------------------------------------------------------------
HRESULT HrParseImportanceString(
    IN LPSTR lpImportance,     // Importance string
    OUT ULONG * pulImportance)  // importance value pointer
{
    HRESULT hr          =   NOERROR;
    UINT    iImportance =   0;      // index into importance array

    DEBUGPRIVATE("HrParseImportanceString()\n");

    // check input parameters
    hr = CHK_HrParseImportanceString(lpImportance, pulImportance);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pulImportance = 0;   // initialize

    // Find matching priority string
    hr = HrFindStringValue(
                lpImportance,       // string
                nImportances,       // number of values
                rgImportance,     // String value pair array
                pulImportance);     // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$--HrParseExternalTraceString-------------------------------------------------
//
// DESCRIPTION: Parse External-Received-By or
//              External-Attempted-By trace string into
//              its separate components.
//
// INPUT:       lpTraceString   --  External-Received/Attempted-By trace string
//
// OUTPUT:      plAction   --  trace action
//              pCountry  --  country name
//              pADMDName --  ADMD name
//              pPRMDId   --  PRMD identifier
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise
//
// -----------------------------------------------------------------------------
HRESULT HrParseExternalTraceString(
    IN LPSTR lpTraceString,      // trace information string
    OUT LONG * plAction,          // trace action pointer
    OUT CHAR * pCountry,         // country name
    OUT CHAR * pADMDName,        // ADMD name
    OUT CHAR * pPRMDId)          // PRMD identifier
{
    HRESULT hr      =   NOERROR;
    UINT    nChars  =   0;          // length of trace string
    LPSTR  lpTemp  =   NULL;       // temporary string pointer
    INT     nUnread =   0;          // number of characters not read so far.
    UINT    nLength =   0;          // length of current string

    // temporary string buffer
    CHAR   szTempBuffer[ulMaxOutStringLen] =   {0};

    DEBUGPRIVATE("HrParseExternalTraceString()\n");

    // check input parameters
    hr = CHK_HrParseExternalTraceString(
lpTraceString,
plAction,
pCountry,
        pADMDName,
pPRMDId);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize parameters
    *plAction = 0;
    *pCountry = 0;
    *pADMDName = 0;
    *pPRMDId = 0;

    // If we have no trace data, then we are done.
    // (This is a valid case.)
    if ( lstrcmp(lpTraceString, lpszBlank) == 0 )
    {
        goto cleanup;
    }

    nChars = lstrlen(lpTraceString);    // length of input string
    nUnread = nChars;                   // # characters not read yet
    lpTemp = lpTraceString;             // string pointer

    // Read in PRMD identifier prefix.
    nLength = lstrlen(lpszPRMDId);  // length of PRMD ID prefix

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszPRMDId) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in PRMD identifier.
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // terminating/separating character
            MAX_PRMD_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pPRMDId);     // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine PRMD identifier length
    nLength = lstrlen(pPRMDId);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in ADMD name prefix.
    nLength = lstrlen(lpszADMDName);  // length of ADMD name prefix

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszADMDName) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in ADMD name
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // teminating/separating character
            MAX_ADMD_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pADMDName);       // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine ADMD name length
    nLength = lstrlen(pADMDName);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in country prefix
    nLength = lstrlen(lpszCountry);  // length of country

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszCountry) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in country name
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // teminating/separating character
            MAX_COUNTRY_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pCountry);       // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine country name length
    nLength = lstrlen(pCountry);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in country postfix
    nLength = lstrlen(lpszCountryEnd);  // length of country

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszCountryEnd) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Action is currently pointed to by lpTemp.
    // Convert the action string to an integer value
    hr = HrFindStringValue(
                lpTemp,         // string
                nActions,       // number of values
                rgAction,       // String value pair array
                (ULONG *) plAction);      // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$--HrParseInternalTraceString-------------------------------------------------
//
// DESCRIPTION: Parse Internal-Received-By or
//              Internal-Attempted-By trace string into
//              its separate components.
//
// INPUT:       lpTraceString   --  Internal-Received/Attempted-By trace string
//
// OUTPUT:      plAction  --  trace action
//              pCountry  --  country name
//              pADMDName --  ADMD name
//              pPRMDId   --  PRMD identifier
//              pMTAName  --  MTA name
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise
//
// -----------------------------------------------------------------------------
HRESULT HrParseInternalTraceString(
    IN LPTSTR lpTraceString,      // trace information string
    OUT LONG * plAction,          // trace action pointer
    OUT CHAR * pCountry,          // country name
    OUT CHAR * pADMDName,         // ADMD name
    OUT CHAR * pPRMDId,           // PRMD identifier
    OUT CHAR * pMTAName)          // MTA name
{
    HRESULT hr      =   NOERROR;
    UINT    nChars  =   0;          // length of trace string
    LPTSTR  lpTemp  =   NULL;       // temporary string pointer
    INT     nUnread =   0;          // number of characters not read so far.
    UINT    nLength =   0;          // length of current string

    // temporary string buffer
    CHAR    szTempBuffer[ulMaxOutStringLen] =   TEXT("");

    DEBUGPRIVATE("HrParseInternalTraceString()\n");

    // check input parameters
    hr = CHK_HrParseInternalTraceString(
lpTraceString,
plAction,
pCountry,
        pADMDName,
pPRMDId,
pMTAName);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Initialize parameters
    *plAction = 0;
    *pCountry = 0;
    *pADMDName = 0;
    *pPRMDId = 0;
    *pMTAName = 0;

    // If we have no trace data, then we are done.
    // (This is a valid case.)
    if ( lstrcmp(lpTraceString, lpszBlank) == 0 )
    {
        goto cleanup;
    }

    nChars = lstrlen(lpTraceString);    // length of input string
    nUnread = nChars;                   // # characters not read yet
    lpTemp = lpTraceString;             // string pointer

    // Read in MTA name prefix.
    nLength = lstrlen(lpszMTAName);  // length of MTA name prefix

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszMTAName) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in MTA name
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // terminating/separating character
            MAX_MTA_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pMTAName);       // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine MTA name length
    nLength = lstrlen(pMTAName);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in PRMD identifier prefix.
    nLength = lstrlen(lpszPRMDId);  // length of PRMD ID prefix

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszPRMDId) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in PRMD identifier.
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // terminating/separating character
            MAX_PRMD_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pPRMDId);     // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine PRMD identifier length
    nLength = lstrlen(pPRMDId);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in ADMD name prefix.
    nLength = lstrlen(lpszADMDName);  // length of ADMD name prefix

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszADMDName) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in ADMD name
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // teminating/separating character
            MAX_ADMD_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pADMDName);       // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine ADMD name length
    nLength = lstrlen(pADMDName);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in country prefix
    nLength = lstrlen(lpszCountry);  // length of country

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszCountry) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Read in country name
    hr = HrReadNextString(
            lpTemp,         // input string
            chForwardSlash,    // teminating/separating character
            MAX_COUNTRY_NAME_SIZ * sizeof(CHAR),  // size of output buffer
            pCountry);       // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Determine country name length
    nLength = lstrlen(pCountry);
    nUnread -= nLength;             // update # character unread

    lpTemp += nLength;      // increment string pointer

    // Read in country postfix
    nLength = lstrlen(lpszCountryEnd);  // length of country

    // Check number of characters read.
    nUnread -= nLength;

    if ( nUnread < 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lstrcpyn(szTempBuffer, lpTemp, nLength + 1);

    // Check prefix
    if ( lstrcmp(szTempBuffer, lpszCountryEnd) != 0 )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    lpTemp += nLength;  // increment pointer

    // Action is currently pointed to by lpTemp.
    // Convert the action string to an integer value
    hr = HrFindStringValue(
                lpTemp,         // string
                nActions,       // number of values
                rgAction,       // String value pair array
                (ULONG *) plAction);      // value pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$----HrParseAttachmentHeader--------------------------------------------------
//
// DESCRIPTION: Parse attachment header data into its file name
//              and file size components.
//
// INPUT:   lpHeader    --  attachment header data string
//
// OUTPUT:  lppFileName --  attachment file name
//          pcb         --  pointer to # of bytes in attachment
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input,
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT HrParseAttachmentHeader(
    IN LPSTR lpHeader,         // attachment header data string
    OUT LPSTR * lppFileName,   // attachment file name
    OUT ULONG * pcb)            // pointer to number of bytes in file name
{
    HRESULT hr      =   NOERROR;
    LPSTR  lpTemp  =   NULL;       // temporary string pointer
    ULONG   cbSize  =   0;          // size of file name buffer

    DEBUGPRIVATE("HrParseAttachmentHeader()\n");

    // check input parameters
    hr = CHK_HrParseAttachmentHeader(lpHeader, lppFileName, pcb);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Allocate file name buffer
    cbSize = ulMaxOutStringLen * sizeof(CHAR); // size of file name buffer
    hr = MAPIAllocateBuffer(cbSize,     // # bytes to allocate
                            (LPVOID *) lppFileName);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(!IsBadWritePtr(*lppFileName, sizeof(LPSTR)),
                "Bad *lppFileName");

    ZeroMemory(*lppFileName, cbSize); // intialize file name
    *pcb = 0;             // set number of file bytes to 0.

    lpTemp = lpHeader;    // initialize string pointer

    // Read in the file name.
    hr = HrReadNextString(
            lpTemp,             // input string
            chColon,            // teminating/separating character
            cbSize,             // size of output buffer
            *lppFileName);      // output buffer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Skip over file name and colon character
    lpTemp += lstrlen(*lppFileName) + 1;    // increment pointer

    // Read in the file size.
    // File size is currently pointed to by lpTemp.
    *pcb = atol(lpTemp);

    // We are done.

cleanup:

    if ( FAILED(hr) )
    {
        MAPIFREEBUFFER(*lppFileName);
    }

    RETURN(hr);

}

//$----HrEDKDecodeBinaryStreamDataToStream-----------------------------------------------
//
// DESCRIPTION: Read attachment hexadecimal encoding from input
//              stream to attachment.
//
// INPUT:       cb  --  number of bytes in original binary attachment.
//              lpStreamIn  --  input stream pointer
//              lpStreamOut --  Output stream pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------

HRESULT HrEDKDecodeBinaryStreamDataToStream(
    IN LPSTREAM lpStreamIn, // input stream
    IN ULONG cb,            // number of bytes in original binary data
    IN LPSTREAM lpStreamOut)// output stream
{
    HRESULT     hr          =   NOERROR;
    ULONG       iByte       =   0;          // byte index
    UINT        nOutput     =   0;          // integer value to write to attachment
    UINT        nHigh       =   0;          // 16's place
    UINT        nLow        =   0;          // 1's place
    ULONG       cbRead      =   0;          // number of bytes read from stream

    // temporary buffer size
    const UINT  cbTempBuffer    =   nCharsPerEncodedByte * sizeof(CHAR);

    // indices of characters in temporary buffer
    const UINT  iHigh       =   0;          // high-order hex digit
    const UINT  iLow        =   1;          // low-order hex digit
    const UINT  iReturn     =   2;          // return or space character

    // temporary string buffer
    CHAR       szTempBuffer[cbTempBuffer]  =   {0};

    DEBUGPRIVATE("HrEDKDecodeBinaryStreamDataToStream()\n");

    // check input parameters
    hr = CHK_HrEDKDecodeBinaryStreamDataToStream(lpStreamIn, cb, lpStreamOut);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Read and convert one logical byte encoding from the
    // input stream to the output stream.
    for ( iByte = 0; iByte < cb; iByte++ )
    {
        // read in three bytes at a time from the input stream.
        // The first two will be an ASCII representation
        // of a hexadecimal number (e.g. FF).
        // The third character will either be a space or
        // a return character.
        hr = lpStreamIn->Read(
                    szTempBuffer,       // data buffer
                    cbTempBuffer,       // # bytes in data buffer
                    &cbRead);           // # bytes read.

        if ( FAILED(hr) || (cbRead != cbTempBuffer) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Convert two-character ASCII portion of buffer to
        // a character to write to the output stream.
        nHigh = nAsciiHexToInt(
                szTempBuffer[iHigh]);// high-order hexadecimal character

        nLow = nAsciiHexToInt(
                szTempBuffer[iLow]);// low-order hexadecimal character

        // compute output digit
        nOutput = (16 * nHigh) + nLow;

        // Write the byte to the output stream
        hr = lpStreamOut->Write(
                    (CHAR *) &(nOutput),      // data buffer
                    sizeof(CHAR),          // # bytes in buffer
                    NULL);

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Check for the last pair on the line
        if ( szTempBuffer[iReturn] == chReturn)
        {
            // We have just read the last pair on the line,
            // since the third character read in is the
            // return character.  Since the return character
            // is followed by a newline character, we should read
            // that in now.
            hr = lpStreamIn->Read(
                        szTempBuffer,       // input buffer
                        sizeof(chNewLine),  // # bytes to read in
                        &cbRead);

            if ( FAILED(hr) )
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }

        }   // end if
    }   // end for

    // commit changes to output stream
    // Do we need to do this?
    hr = lpStreamOut->Commit(STGC_DEFAULT);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // we are done.

cleanup:

    RETURN(hr);

}

//$--HrEDKParseSubjectPrefix---------------------------------------------------
//
// DESCRIPTION: Break a subject into its prefix component & its
//              original subject component.  (The prefix is everything
//              up to and including the first colon and the first space
//              after the colon, if any.
//
// INPUT:       pszSubject --  subject text
//
// OUTPUT       ppszPrefix  --  prefix string pointer
//              ppszOrigSub --  original subject pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_OUTOFMEMORY if memory propblems,
//                          E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
HRESULT HrEDKParseSubjectPrefix(
            IN LPSTR pszSubject,       // subject text
            OUT LPSTR * ppszPrefix,    // subject prefix
            OUT LPSTR * ppszOrigSub)   // original subject text
{
    HRESULT hr          =   NOERROR;
    ULONG   cbSubject   =   0;          // # bytes in the subject
    CHAR   chCur       =   NULL;       // current character
    BOOL    fPrefix     =   FALSE;      // TRUE if a prefix is found
    LPSTR  pszPrefix   =   NULL;       // pointer into prefix string
    LPSTR  pszOrigSub  =   NULL;       // pointer into original subject
    LPSTR  pszCur      =   NULL;       // pointer into subject text

    DEBUGPRIVATE("HrEDKParseSubjectPrefix()\n");

    // check input parameters
    hr = CHK_HrEDKParseSubjectPrefix(pszSubject, ppszPrefix,
                                     ppszOrigSub);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initialize pointer
    *ppszPrefix = NULL;
    *ppszOrigSub = NULL;

    // allocate memory for output variables
    cbSubject = cbStrLen(pszSubject);
    hr = MAPIAllocateBuffer(
                cbSubject,      // # bytes
                (VOID **) ppszPrefix);    // buffer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(ppszPrefix != NULL, "Bad ppszPrefix");
    ASSERTERROR(*ppszPrefix != NULL, "Bad **ppszPrefix");

    hr = MAPIAllocateBuffer(
                cbSubject,      // # bytes
                (VOID **) ppszOrigSub);   // buffer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_OUTOFMEMORY);

        goto cleanup;
    }

    ASSERTERROR(ppszOrigSub != NULL, "Bad ppszOrigSub");
    ASSERTERROR(*ppszOrigSub != NULL, "Bad *ppszOrigSub");

    // null-terminate strings.
    **ppszPrefix = 0;
    **ppszOrigSub = 0;

    // look for a colon in the subject text
    pszCur = pszSubject;
    chCur = *pszCur;
    pszPrefix = *ppszPrefix;
    pszOrigSub = *ppszOrigSub;
    while ( chCur != 0 )
    {
        if ( chCur == chColon )
        {
            // there is a prefix
            fPrefix = TRUE;

            // copy colon into candidate prefix
            *pszPrefix = chCur;
            pszPrefix++;        // increment prefix pointer

            // Test next character for a space
            if ( *(pszCur + 1) == chSpace )
            {
                // first space after the colon is part of the prefix
                pszCur++;           // increment current pointer
                *pszPrefix = chSpace;
                pszPrefix++;
            }
        }

        else if ( fPrefix == FALSE )
        {
            // copy character into candidate prefix
            *pszPrefix = chCur;
            pszPrefix++;
        }

        else // Have already found prefix
        {
            // copy character into the original subject
            *pszOrigSub = chCur;
            pszOrigSub++;
        }

        // advance to next character
        pszCur++;
        chCur = *pszCur;

    }   // end while

    // NULL terminate prefix and original subject found
    *pszPrefix = 0;
    *pszOrigSub = 0;

    // handle case where no prefix found
    if ( fPrefix == FALSE )
    {
        // null out prefix
        **ppszPrefix = 0;

        // Copy subject into original subject
        lstrcpy(*ppszOrigSub, pszSubject);

    }   // end if no prefix found

cleanup:

    if ( FAILED(hr) )
    {
        // deallocate memory
        MAPIFREEBUFFER(*ppszOrigSub);
        MAPIFREEBUFFER(*ppszPrefix);
    }

    RETURN(hr);

}


PERFTEST.C

// --perftest.c-----------------------------------------------------------------
//
//  Performance monitoring sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwmain.h"
#include "prfcount.h"
#include "monitor.h"
#include "perftest.chk"

//  Defined for NT service shell
TCHAR szAppName[]       = TEXT("PERFTEST");
TCHAR szWindowTitle[]   = TEXT("Performance Monitoring Sample");

static LPMAPISESSION    lpSession       = NULL;
static LPMDB            lpStore         = NULL;
static LPMAPIFOLDER     lpRootFolder    = NULL;
static LPMAPIFOLDER     lpMtsInFolder   = NULL;
static LPMAPIFOLDER     lpMtsOutFolder  = NULL;
static ULONG            cbGalEid        = 0;
static LPENTRYID        lpGalEid        = NULL;

#define REPLY_PREFIX    "RE: "

TCHAR                   szServiceName[MAX_SERVICE_NAME_LENGTH+1] = {0};

static BOOL             fMonitorRunning= FALSE;

LPCOUNTER lpcntUserDefinedCounter       = NULL;

// The array rgcdCounters describes the counters published to the performance
// monitor by PerfTest.  Defined constants named PLACEHOLDER_* must be replaced
// by pointers to MAPI folder objects before calling HrMonInit.

#define PLACEHOLDER_MTS_OUT     ((LPMAPIFOLDER) 1)

COUNTERDEF rgcdCounters[] =
{
{
COUNTER_MESSAGES_IN,
COUNTERTYPE_MESSAGES_TRANSFERRED_IN,
NULL,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_BYTES_IN,
COUNTERTYPE_BYTES_TRANSFERRED_IN,
NULL,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_MESSAGES_OUT,
COUNTERTYPE_MESSAGES_TRANSFERRED_OUT,
NULL,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_BYTES_OUT,
COUNTERTYPE_BYTES_TRANSFERRED_OUT,
NULL,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_MESSAGES_IN_OUT_QUEUE,
COUNTERTYPE_MESSAGES_IN_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_NONE,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_BYTES_IN_OUT_QUEUE,
COUNTERTYPE_BYTES_IN_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_NONE,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_MESSAGES_ENTERING_OUT_QUEUE,
COUNTERTYPE_MESSAGES_ENTERING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_BYTES_ENTERING_OUT_QUEUE,
COUNTERTYPE_BYTES_ENTERING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_MESSAGES_LEAVING_OUT_QUEUE,
COUNTERTYPE_MESSAGES_LEAVING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_BYTES_LEAVING_OUT_QUEUE,
COUNTERTYPE_BYTES_LEAVING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_CONTINUOUS,
0,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_HOUR_MESSAGES_ENTERING_OUT_QUEUE,
COUNTERTYPE_MESSAGES_ENTERING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_LAST_N_MINUTES,
60,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_HOUR_BYTES_ENTERING_OUT_QUEUE,
COUNTERTYPE_BYTES_ENTERING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_LAST_N_MINUTES,
60,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_HOUR_MESSAGES_LEAVING_OUT_QUEUE,
COUNTERTYPE_MESSAGES_LEAVING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_LAST_N_MINUTES,
60,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_HOUR_BYTES_LEAVING_OUT_QUEUE,
COUNTERTYPE_BYTES_LEAVING_FOLDER,
PLACEHOLDER_MTS_OUT,
PERIODTYPE_LAST_N_MINUTES,
60,
NULL,
0,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_USER_1,
COUNTERTYPE_USER_DEFINED,
NULL,
PERIODTYPE_NONE,
0,
&lpcntUserDefinedCounter,
PERF_COUNTER_RAWCOUNT,
PERF_DETAIL_NOVICE,
0
},
{
COUNTER_USER_2,
COUNTERTYPE_USER_DEFINED,
NULL,
PERIODTYPE_NONE,
0,
&lpcntUserDefinedCounter,
PERF_COUNTER_LARGE_RAWCOUNT,
PERF_DETAIL_NOVICE,
0
},
};

//$--HrCreateReplySubject-------------------------------------------------------
//  Create reply subject.
// -----------------------------------------------------------------------------
HRESULT HrCreateReplySubject(       // RETURNS: HRESULT
    IN OUT LPMESSAGE lpMessage)     // pointer to message
{
    HRESULT hr               = NOERROR;
    SCODE   sc               = 0;
    ULONG   cbSubject        = 0;
    LPTSTR  lpszSubject      = NULL;
    LPTSTR  lpszReplySubject = NULL;

DEBUGPUBLIC("HrCreateReplySubject()\n");

    hr = CHK_HrCreateReplySubject(lpMessage);
if (FAILED(hr))
RETURN(hr);

    hr = HrMAPIGetPropString(
        (LPMAPIPROP)lpMessage,
        PR_SUBJECT,
        &cbSubject,
        (void **)&lpszSubject);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbSubject =
        (lstrlen(lpszSubject) + lstrlen(REPLY_PREFIX) + 1) * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(
        cbSubject,
        (void **)&lpszReplySubject);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    lstrcpy(lpszReplySubject, REPLY_PREFIX);
    lstrcat(lpszReplySubject, lpszSubject);

    hr = HrMAPISetPropString(
        (LPMAPIPROP)lpMessage,
        PR_SUBJECT,
        lpszReplySubject);

    MAPIFREEBUFFER(lpszReplySubject);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrCreateReplyRecipients----------------------------------------------------
//  Create reply recipients
// -----------------------------------------------------------------------------
HRESULT HrCreateReplyRecipients(    // RETURNS: HRESULT
    IN MSG_T msgType,               // message type
    IN OUT LPMESSAGE lpMessage)     // pointer to message
{
    HRESULT         hr         = NOERROR;
    HRESULT         hrT        = NOERROR;
    LPADRLIST       lpAdrList  = NULL;
    ULONG           cValues    = 0;
    LPSPropValue    lpProps    = NULL;
    LPSPropTagArray lpPropTags = NULL;

SizedSPropTagArray(6, rgEnvPropTags) =
{
6,
{
PR_ORIGINATOR_NAME,
            PR_ORIGINATOR_ADDRTYPE,
            PR_ORIGINATOR_ADDR,
PR_ORIGINATOR_ENTRYID,

            // The next two properties won't exist on the envelope, but
            // are place holders so that we can add them to the resulting
            // structure without having to re-allocate memory.

PR_RECIPIENT_TYPE,
            PR_RECIPIENT_NUMBER,
        }
    };

SizedSPropTagArray(5, rgMsgPropTags) =
{
5,
{
PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_EMAIL_ADDRESS,
PR_SENDER_ENTRYID,

            // The next property won't exist on the message, but
            // is a place holder so that we can add it to the resulting
            // structure without having to re-allocate memory.

 PR_RECIPIENT_TYPE
       }
    };

DEBUGPUBLIC("HrCreateReplyRecipients()\n");

    hr = CHK_HrCreateReplyRecipients(msgType, lpMessage);

if(FAILED(hr))
RETURN(hr);

    switch(msgType)
    {
    case MSG_ENVELOPE:
        lpPropTags = (LPSPropTagArray)&rgEnvPropTags;
        break;
    case MSG_CONTENT:
        lpPropTags = (LPSPropTagArray)&rgMsgPropTags;
        break;
    default:
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

hrT = MAPICALL(lpMessage)->GetProps(
    lpMessage,
lpPropTags,
fMapiUnicode,
&cValues,
&lpProps);

    // If as expected, there were errors returned make sure they aren't
    // in the first four properties.  The last one(s) are just placeholders.

    if (hrT == MAPI_W_ERRORS_RETURNED)
    {
        int i = 0;

        for (i = 0; i < 4; i++)
        {
            if (PROP_TYPE(lpProps[i].ulPropTag) == PT_ERROR)
            {
                MAPIFREEBUFFER(lpProps);

                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
        }
        hrT = NOERROR;
    }

if (FAILED(hrT))
{
        lpProps = NULL;

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

    lpProps[0].ulPropTag = PR_DISPLAY_NAME;
    lpProps[1].ulPropTag = PR_ADDRTYPE;
    lpProps[2].ulPropTag = PR_EMAIL_ADDRESS;
    lpProps[3].ulPropTag = PR_ENTRYID;
    lpProps[4].ulPropTag = PR_RECIPIENT_TYPE;
    lpProps[4].Value.l = MAPI_TO;

    if(cValues == 6)
    {
        lpProps[5].ulPropTag = PR_RECIPIENT_NUMBER;
        lpProps[5].Value.l = 1;
    }

    hr = HrMAPICreateSizedAddressList(
        1,
        &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetAddressList(
        0,
        cValues,
        lpProps,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpMessage)->ModifyRecipients(lpMessage, (ULONG)0, lpAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpProps);

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}

//$--HrSetDeleteAfterSubmit-----------------------------------------------------
//  Set PR_DELETE_AFTER_SUBMIT to TRUE.
// -----------------------------------------------------------------------------
HRESULT HrSetDeleteAfterSubmit(          // RETURNS: HRESULT
    IN OUT LPMESSAGE lpMessage)     // pointer to message
{
    HRESULT hr = NOERROR;

DEBUGPUBLIC("HrSetDeleteAfterSubmit()\n");

    hr = CHK_HrSetDeleteAfterSubmit(lpMessage);
if (FAILED(hr))
RETURN(hr);

    hr = HrMAPISetPropBoolean(
        (LPMAPIPROP)lpMessage,
        PR_DELETE_AFTER_SUBMIT,
        TRUE);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--Process--------------------------------------------------------------------
//  Process messages arriving at MTS-OUT.
// -----------------------------------------------------------------------------
DWORD Process(
    LPVOID lpParameter)
{
    HRESULT      hr                 = NOERROR;
    HRESULT      hrT                = 0;
    ULONG        i                  = 0;
    ULONG        cRows              = 0;
    ULONG        cValues            = 0;
    ULONG        ulObjType          = 0;

    LPADRBOOK    lpAdrBook          = NULL;

    LPMESSAGE    lpMessage          = NULL;
    LPMESSAGE    lpEnvelope         = NULL;
    LPATTACH     lpAttach           = NULL;

    ULONG        cbeid              = 0;
    LPENTRYID    lpeid              = NULL;

    LPSPropValue lpProps            = NULL;

    ULONG        ulSize             = 0;

    cRows = GetGWSRowSet(lpParameter)->cRows;

DEBUGPUBLIC("Process()\n");

    // Open the Address Book
    hrT = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < cRows; i++)
    {
        cValues = GetGWSRowSet(lpParameter)->aRow[i].cValues;

        lpProps = GetGWSRowSet(lpParameter)->aRow[i].lpProps;

        cbeid = lpProps[0].Value.bin.cb;
        lpeid = (LPENTRYID)lpProps[0].Value.bin.lpb;

        // Get the message in the MTS-OUT folder
        hrT = MAPICALL(lpMtsOutFolder)->OpenEntry(
            lpMtsOutFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            &ulObjType,
            (LPUNKNOWN FAR *)&lpEnvelope);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(ulObjType != MAPI_MESSAGE)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Get the attachment in the envelope
        hrT = MAPICALL(lpEnvelope)->OpenAttach(
            lpEnvelope,
            0,
            NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            &lpAttach);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Open a message interface on the attachment
        hrT = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------

        //
        // Collect size data to sumbit to performance monitor after message
        // has been successfully sent.
        //

        hr = HrMAPIGetPropLong(
            (LPMAPIPROP)lpMessage,
            PR_MESSAGE_SIZE,
            &ulSize);

        if(hr == MAPI_E_NOT_FOUND)
        {
            ulSize = 0;
        }
        else if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Modify the subject.
        //

        hr = HrCreateReplySubject(
            lpEnvelope);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrCreateReplySubject(
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Address the message to the originator.
        //

        hr = HrCreateReplyRecipients(
            MSG_ENVELOPE,
            lpEnvelope);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrCreateReplyRecipients(
            MSG_CONTENT,
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Delete the message after it has been submitted.
        //

        hr = HrSetDeleteAfterSubmit(
            lpEnvelope);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrSetDeleteAfterSubmit(
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------

        //
        // Save changes to the message.
        //

        hrT = MAPICALL(lpMessage)->SaveChanges(lpMessage, KEEP_OPEN_READWRITE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Save changes to attachment object.
        //

        hrT = MAPICALL(lpAttach)->SaveChanges(lpAttach, KEEP_OPEN_READWRITE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        //
        // Save changes to the envelope.
        //

        hrT = MAPICALL(lpEnvelope)->SaveChanges(lpEnvelope, KEEP_OPEN_READWRITE);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------

        //
        // Submit message
        //

        hrT = MAPICALL(lpEnvelope)->SubmitMessage(lpEnvelope, FORCE_SUBMIT);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------

        //
        // Submit size data to performance monitor
        //

    if(fMonitorRunning)
    {
            // Report the message as transferred out.

    hrT = HrMonCollectMessageXferStats(
        1,
        ulSize,
        DIRECTIONTYPE_OUT);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }

            // Now report the same message as transferred in, since this
            // "gateway" just bounces the message back to the sender.

    hrT = HrMonCollectMessageXferStats(
        1,
        ulSize,
        DIRECTIONTYPE_IN);

            if(FAILED(hrT))
            {
                hr = HR_LOG(E_FAIL);
                goto cleanup;
            }
    }

        ULRELEASE(lpMessage);

        ULRELEASE(lpAttach);

        ULRELEASE(lpEnvelope);

        // ---------------------------------------------------------------------
    }

cleanup:

    ULRELEASE(lpAttach);

    ULRELEASE(lpMessage);

    ULRELEASE(lpEnvelope);

    ULRELEASE(lpAdrBook);

    return(0);
}

//$--GWMain----------------------------------------------------------------
//  Start threads.
// -----------------------------------------------------------------------------
void GWMain(
    void)
{
    HRESULT               hr = NOERROR;
    EDK_SERVICE_CONTROL_T sc = 0;
    ULONG                 icd = 0;

DEBUGPUBLIC("GWMain()\n");

    hr = HrServiceGetName(
        szServiceName);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
// Start performance monitoring
    //

    // Replace any placeholders in rgcdCounters with a pointer
    // to the actual MAPI folder.

    for (icd = 0; icd < ARRAY_CNT(rgcdCounters); icd++)
    {
        if (rgcdCounters[icd].lpFolder == PLACEHOLDER_MTS_OUT)
        {
            rgcdCounters[icd].lpFolder = lpMtsOutFolder;
        }
    }

    // Initialize event logging.

hr = HrMonInit(
0,
NULL,
szServiceName,
OBJECT_EDK_PERFTEST,
PERF_DETAIL_NOVICE,
COUNTER_MESSAGES_OUT,
ARRAY_CNT(rgcdCounters),
rgcdCounters);

    if(FAILED(hr))
    {
fMonitorRunning = FALSE;
    }
else
{
fMonitorRunning = TRUE;
}

    hr = HrGWStartNewMailHandler(
        1000,
        (ULONG)-1,
        1,
        1,
        EXCHANGE_ADDRTYPE,// Currently "EX"
        getenv("TEMP"),
        (LPTHREAD_START_ROUTINE)Process);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        ServiceStop();
    }

    //
    // Wait for a request for the service to stop.
    //

    hr = HR_LOG(HrServiceWaitForStop(INFINITE, &sc));

    return;
}

//$--HrGWLogon-------------------------------------------------------------
//  Logon to the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogon(void)
{
    HRESULT   hr       = NOERROR;

DEBUGPUBLIC("HrGWLogon()\n");

    lpSession      = GetGWSession();
    lpStore        = GetGWDefaultStore();
    lpRootFolder   = GetGWRootFolder();
    lpMtsInFolder  = GetGWMtsInFolder();
    lpMtsOutFolder = GetGWMtsOutFolder();
    cbGalEid       = GetGWGALEntryIdSize();
    lpGalEid       = GetGWGALEntryId();

    RETURN(hr);
}

//$--HrGWLogoff------------------------------------------------------------
//  Logoff of the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogoff(void)
{
    HRESULT     hr        = NOERROR;

DEBUGPUBLIC("HrGWLogoff()\n");

if(fMonitorRunning == TRUE)
{
     hr = HrMonUninit();

fMonitorRunning = FALSE;
}

    RETURN(hr);
}


PRFCOUNT.H

// --PrfCount.h-----------------------------------------------------------------
//
//  This file contains defined constants for the performance counters
//  that the PerfTest gateway exports to the Windows NT Performance Monitor.
//
//  This file, along with PerfCnt.ini, is used to install the PerfTest
//  performance counters on the server that is running the PerfTest gateway.
//  Any changes to this file must be reflected in PrfCount.ini and in the
//  array of COUNTERDEF structures that is passed to HrMonInit in
//  PerfTest.c.
//
//  For information on how to instlall/uninstall counters, see PrfCount.ini.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------
#if !defined(_PRFCOUNT_H)
#define _PRFCOUNT_H

#define OBJECT_EDK_PERFTEST 0

#define COUNTER_MESSAGES_IN     2
#define COUNTER_MESSAGES_OUT 4
#define COUNTER_BYTES_IN     6
#define COUNTER_BYTES_OUT     8

#define COUNTER_MESSAGES_IN_OUT_QUEUE10
#define COUNTER_BYTES_IN_OUT_QUEUE12
#define COUNTER_MESSAGES_ENTERING_OUT_QUEUE14
#define COUNTER_BYTES_ENTERING_OUT_QUEUE16
#define COUNTER_MESSAGES_LEAVING_OUT_QUEUE18
#define COUNTER_BYTES_LEAVING_OUT_QUEUE20

#define COUNTER_HOUR_MESSAGES_ENTERING_OUT_QUEUE22
#define COUNTER_HOUR_BYTES_ENTERING_OUT_QUEUE24
#define COUNTER_HOUR_MESSAGES_LEAVING_OUT_QUEUE26
#define COUNTER_HOUR_BYTES_LEAVING_OUT_QUEUE28

#define COUNTER_USER_1        30
#define COUNTER_USER_2        32

#endif


BODY.TXT

This is a test message for the POPPST program.


POPPST.CPP

// --poppst.cpp---------------------------------------------------------------
//
// Implementation of mail storage file populator program.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

//
// Begin program documentation
//

/*

Poppst is a command line program that uses input from an ini file to create
messages in either a new PST or an existing PST.  If a new PST is desired, it
will be created in a temporary profile that is deleted when PST creation is
done.  If it is desired to create the messages in an existing PST, the profile
name is given for the PST.

Command line arguments:

One command line argument, the name of the ini file, is required.

Format of the ini file:

The following notation is used in describing the ini file grammar:

- Angle brackets (ie. <>) are used to indicate a token, or syntactic element.
- Braces (ie. {}) are used to indicate an optional syntactic element.
- Ellipsis (ie. ...) are used to indicate that the preceding syntactic element
  may be repeated.
- // indicates embedded comments (ie., not part of the file format).
- | indicates alternation (ie., a | b implies a or b is acceptable).

File format generalities:

The format used is basically consistent with the standard Microsoft ini file
format.  Thus, there are section header lines and section entry lines.  The
section header lines are distinguished from section entry lines by enclosing
the section keyword in square brackets.  Section entry lines have an entry
keyword that is immediately followed by an '='.  The '=' is then immediately
followed by a value for the section entry.  All characters following the '='
up to the newline are used for the value (ie., if there is whitespace, it will
be included in the value).  Leading whitespace is permitted in all lines, and
is not significant.  Whitespace everywhere else IS significant.  Comments may
be included by using ';' as the first nonwhitespace character on a line.
Blank lines may also be included in the ini file and are ignored.  Either a
newpst section or a profile section is required, and must be the first section
in the ini file.  Note that both a newpst section and a profile section
should NOT be used in one ini file.  One or more message sections should then
follow the newpst or profile section.  Unless otherwise noted, all section
entry lines are required, and must occur exactly once in a section.  The order
of section entries within a section is not important.The poppst program is
not case sensitive with regard to keywords.  The maximum line length permitted
is 512 chars (cbMaxIniLine) including the newline.

!!!!!IMPORTANT INFORMATION!!!!!

The [newpst] and [profile] sections are mutually exclusive.

If you specify the [newpst] section, a temporary profile will be used.
This means that no name resolution will be possible.

If you specify the [profile] section, the message create
will be placed in the specified folder in the default message store.
If you want the message to be placed into your PST, then you must
FIRST set the PST "Personal Folders" store to be your default store
for the specified profile (Select Options | Delivery in the Exchange
client).

!!!!!!!!END IMPORTANT INFORMATION!!!!!!!!!!!!!!!

Newpst section format (use to create a new pst and add msgs to it):\

[newpst]
NAME=<pst display name string>
ENCRYPTION=<encryption state>
PASSWORD=<pst password string>
PATH=<file path>

Profile section format (use to add msgs to existing pst in existing profile):

[profile]
NAME=<profile name string>
PASSWORD=<profile password string>

Message section format:

[message<whitespace><number>]
FROM=<user name>{;<user name>}...
TO=<user name>{;<user name>}...
CC=<user name>{;<user name>}...// Optional.
BCC=<user name>{;<user name>}...// Optional.
FOLDER=<folder path>
SUBJECT=<string>
XTEXT=<file path>                   // Optional
DATE=<date>
TIME=<time>
PRIORITY=<priority>
XATTACH=<file path>{;<file path>}...// Optional.

// The <number> value in the message section header should be unique.  Poppst
// doesn't acutally use the number.  It is there only for Windows API compatibility.

BNF for syntactic elements used above:

//This is an incomplete BNF, in that in some instances, we resolve an element
//to <string> or <nonnull string>, and then add a comment indicating that
//the string must be acceptable input for a given parameter of a given api
//or a valid value for a given property.

<pst display name string> ::= <string>// Must be valid PR_DISPLAY_NAME
// value.
<pst password string> ::= <string>// Must be valid PR_PST_PW_SZ_NEW
// value.

<profile name string> ::= <nonnull string>// Must be valid lpszProfileName
// param of MAPILogonEx().

<profile password string> ::= <string>// Must be valid lpszPassword
// param of MAPILogonEx().

<whitespace> ::= <char>// Where isspace(<char>) == TRUE.

<number> ::= <digit>...

<user name> ::= <user display name>{[<address type>:<email address>]}

<user display name> ::= <nonnull string>// Must be valid PR_DISPLAY_NAME
// value.

<address type> ::= <nonnull string>// Must be valid PR_ADDRTYPE value.

<email address> ::= <nonnull string>// Must be valid PR_EMAIL_ADDRESS
// value.

<folder path> ::= {<folder namestring>{\<folder namestring>}...}// See note
// below.
<string> ::= {<char>}...

<file path> ::= <nonnull string>// Must be valid filename param of
// fopen() api.

<date> ::= YYYY-MM-DD// ie., year-month-day, as digits.

<time> ::= HH:MM{:SS}// ie., hour:min{sec} as digits.

<encryption state> ::= ON | OFF

<priority> ::= HIGH | NORMAL | LOW

<nonnull string> ::= <char>...

<char> ::= Any char other than '\n'.

<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

<folder namestring> ::= <nonnull string>// Must be valid PR_DISPLAY_NAME
// value for a mailstore folder.

// Additional note on <folder path>:

A null folder path is used to specify the root folder of a mail store.  If you
want to add mail to the user inbox, the correct folder path is:
"Microsoft Personal Information Store\Inbox".

// Notes on message properties:

The properties that are created for each entry line in a message section are
documented below.  In general, we have used the properties that will result
in the information being displayed by the Exchange client frontend.

FROM:PR_SENDER_NAME = <user name>
PR_SENT_REPRESENTING_NAME = <user name>

// The following three lines are used to set up the recipient table in
// a message.  If the address book is available and <user name> can be
// resolved, then PR_DISPLAY_NAME will be used to derive PR_ADDRTYPE,
// PR_EMAIL_ADDRESS, and PR_ENTRYID.  If the address book is available,
// PR_ADDRTYPE and PR_EMAIL_ADDRESS may also be provided explicitly by using
// a <user name> of form <user display name>[<address type>:<email address>].
// All the <user display name>'s provided will also be used to set
// PR_DISPLAY_TO, PR_DISPLAY_CC, and PR_DISPLAY_BCC.

TO:PR_RECIPIENT_TYPE = MAPI_TO;
PR_DISPLAY_NAME = <user name> for each <user name>;

CC:PR_RECIPIENT_TYPE = MAPI_CC;
PR_DISPLAY_NAME = <user name> for each <user name>;

BCC:PR_RECIPIENT_TYPE = MAPI_BCC;
PR_DISPLAY_NAME = <user name> for each <user name>;

FOLDER:Used to find folder message should be placed in.  If it does not
exist, it will be created.

SUBJECT:Used to set PR_SUBJECT.

XTEXT:Contents of <file path> used to set PR_BODY & PR_RTF_COMPRESSED

DATE:Used to set PR_MESSAGE_DELIVERY_TIME.

TIME:Used to set PR_MESSAGE_DELIVERY_TIME.

PRIORITY:Used to set PR_PRIORITY & PR_IMPORTANCE

XATTACH:Contents of <file path> used to set PR_ATTACH_DATA_BIN.
PR_ATTACH_METHOD = ATTACH_BY_VALUE.
PR_ATTACH_FILENAME = <file path>.
PR_ATTACH_LONG_FILENAME = <file path>.

*/

//
// End program documentation
//

#include "edk.h"
#include "mspst.h"
#include "popstrs.h"
#include "poppstmc.h"// generated by the message compiler from poppstmc.mc
#include "poppst.chk"

//
// Manifest constants
//

const ULONG ulMapiLogonFlags    =
(MAPI_NEW_SESSION | MAPI_EXPLICIT_PROFILE | MAPI_NO_MAIL);

const UINT  cbMaxIniLine    =   512;

//
// Macros
//

// No macros defined.

//
// Enumeration, structure, and other type definitions
//

enum INIFILESECTIONTYPE// ifst
{
    IFST_MESSAGE,
IFST_NEWPST,
    IFST_PROFILE,
    IFST_UNKNOWN
};

enum LINEREADSTATUS// lrs
{
    LRS_OKAY,           // Line was read and is available.
    LRS_EOF,            // We are at end of file; no more lines.
    LRS_IOERROR,        // There was an I/O error in the last read.
    LRS_LINETOOLONG     // Last line too long to return.
};

struct INIFILELINEVALUE// iflv
{
BOOLfValid;
    CHAR            achValue[cbMaxIniLine];
};

struct INIFILEPARSEINFO// ifpi
{
    CHAR *          pszKeyword;
    HRESULT            (*HrParseFunction)(INCHAR *pch);
INIFILELINEVALUE *piflv;
};

//
// Forward function declarations
//

static
HRESULT
HrCreateMessageAttachment(
INLPMESSAGElpMsg,
INLPSTRlpszAttachPath
);

static
HRESULT
HrGetNextLine(OUT LINEREADSTATUS * lpReadStatus);

static
VOID
InitSectionData(
INBOOLfCommented);// TRUE if section data is commented

static
HRESULT
HrIsSectionLine(
    IN      CHAR *  pch,
    OUT     BOOL * bIsSectionLine
    );

static
HRESULT
HrOpenFolder(OUT LPMAPIFOLDER * lppFolder);

static
HRESULT
HrParseDateValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrParseEncryptionValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrParseEntryLine(
    IN      CHAR *  pch
    );

static
HRESULT
HrParseLine();

static
HRESULT
HrParseNonNullStringValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrParsePriorityValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrParseSectionLine(
    IN      CHAR *  pch,// pointer to section keyword
INBOOLfCommented// TRUE is section is commented out
    );

static
HRESULT
HrParseStringValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrParseTimeValue(
    IN      CHAR *  pch
    );

static
HRESULT
HrProcessMessageSection(VOID);

static
HRESULT
HrProcessNewPSTSection(VOID);

static
HRESULT
HrProcessProfileSection(VOID);

static
HRESULT
HrResolveRecipientNames(
OUTLPADRLIST *lppAdrList
);

static
HRESULT
HrResolveSenderName(
    IN      LPSTR pszName,
OUTLPADRLIST *ppAdrList
);

static
HRESULT
HrSetMessageProperties(
INLPMESSAGElpMsg
);

static
HRESULT
HrWriteMessageText(
INLPMESSAGElpMsg,
INLPSTRlpszFilePath
);

//
// Static storage
//

// Temporary profile name
static LPSTRlpszProfileName =   NULL;

// Temporary profile password
static LPSTRlpszProfilePassword =   NULL;

// Storage used to input and parse ini file lines and store values.

// Ini file pointer:

static FILE *           IniFile =NULL;

// Current input line:

static CHAR             achIniLine[cbMaxIniLine]  =   {0};

// Parser state variables:

static BOOLfTmpProf =FALSE;
static INIFILESECTIONTYPEifstCurSection  =   IFST_UNKNOWN;

// The following variable is TRUE if the current section has been
// commented out (e.g. ;[newpst])
static BOOLfCurSectionComment=FALSE;

// Parsed value storage (uses ~0.5KB per value):

static INIFILELINEVALUEiflvBCC =   {0};// For Message
static INIFILELINEVALUEiflvCC  =   {0};// For Message
static INIFILELINEVALUEiflvDATE    =   {0};// For Message
static INIFILELINEVALUEiflvENCRYPTION  =   {0};// For NewPST
static INIFILELINEVALUEiflvFOLDER  =   {0};// For Message
static INIFILELINEVALUEiflvFROM    =   {0};// For Message
static INIFILELINEVALUEiflvPROFILENAME =   {0};// For Profile
static INIFILELINEVALUEiflvPSTNAME =   {0};// For NewPST
static INIFILELINEVALUEiflvPROFILEPASSWORD =   {0};// For Profile
static INIFILELINEVALUEiflvPSTPASSWORD =   {0};// For NewPST
static INIFILELINEVALUEiflvPATH    =   {0};// For NewPST
static INIFILELINEVALUEiflvPRIORITY    =   {0};// For Message
static INIFILELINEVALUEiflvSUBJECT =   {0};// For Message
static INIFILELINEVALUEiflvTIME    =   {0};// For Message
static INIFILELINEVALUEiflvTO  =   {0};// For Message
static INIFILELINEVALUEiflvXATTACH =   {0};// For Message
static INIFILELINEVALUEiflvXTEXT   =   {0};// For Message

// We translate some values and store them below during parsing:

static BOOLbEncryptionNewPST   =   0;
static FILETIMEftCurMsg            =   {0};
static LONGlPriorityCurMsg     =   0;
static LONG             lImportanceCurMsg   =   0;
static SYSTEMTIMEstCurMsg ={0, 0, 0, 0, 0, 0, 0, 0};

// Parser tables for entries:

static
INIFILEPARSEINFO    aifpiMessageEntry[] =   {
                                            "BCC", HrParseNonNullStringValue,
&iflvBCC,
                                            "CC", HrParseNonNullStringValue,
&iflvCC,
                                            "DATE", HrParseDateValue,
&iflvDATE,
                                            "FOLDER", HrParseStringValue,
&iflvFOLDER,
                                            "FROM", HrParseNonNullStringValue,
&iflvFROM,
                                            "PRIORITY", HrParsePriorityValue,
&iflvPRIORITY,
                                            "SUBJECT", HrParseStringValue,
&iflvSUBJECT,
                                            "TIME", HrParseTimeValue,
&iflvTIME,
                                            "TO", HrParseNonNullStringValue,
&iflvTO,
                                            "XATTACH", HrParseNonNullStringValue,
&iflvXATTACH,
                                            "XTEXT", HrParseNonNullStringValue,
&iflvXTEXT
                                            };
static
INIFILEPARSEINFO  aifpiNewPSTEntry[] =     {
                                            "NAME", HrParseStringValue,
&iflvPSTNAME,
                                            "PASSWORD", HrParseStringValue,
&iflvPSTPASSWORD,
"ENCRYPTION", HrParseEncryptionValue,
&iflvENCRYPTION,
"PATH", HrParseNonNullStringValue,
&iflvPATH
                                            };
static
INIFILEPARSEINFO  aifpiProfileEntry[] =     {
                                            "NAME", HrParseNonNullStringValue,
&iflvPROFILENAME,
                                            "PASSWORD", HrParseStringValue,
&iflvPROFILEPASSWORD
                                            };
// Error handling variables:

static ULONGcMsgs =0;
static DWORDiLine =1;
static DWORDiSection =1;

// MAPI-related storage:

static LPADRBOOKlpAdrBook =NULL;
static LPMDBlpMdb =NULL;
static LPMAPIFOLDERlpRootFolder =NULL;
static LPMAPISESSIONlpSession =NULL;

// character constants
const CHARcSemiColon=';';// delimiter character or comment
const CHARcBeginSectLine='[';// begins section line
const CHAR cEndSectLine=']';// ends section line
const CHARcEquals='=';// keyword equals value character
const CHARcNewLine='\n';// new line character
const CHAR cColon=':';// colon character
const CHARcBackSlash='\\';// backslash character
const CHARcDate='d';// date template character

// string constants
LPCSTRpszSeparator=";";// delimiter character

// Application name
static CHARszAppName[]="POPPST";

// Static inline "macros"
// Used instead of #define macros, since they can do type checking

// in-line static for determining if should process section
static inline BOOL fSectionReady(// RETURNS: BOOL
IN INIFILESECTIONTYPE ifstDesiredSection)// section want to process
{
 // If current section is desired section and current section
// is not commented out, we should try to process it.
return ( ( (ifstCurSection == ifstDesiredSection) &&
  (fCurSectionComment == FALSE) ) ? TRUE : FALSE );

}

//
// Functions
//

//$--INTERNAL_ERROR----------------------------------------------------------
//  Helper functions for EventLogMsg
// --------------------------------------------------------------------------
static inline VOID INTERNAL_ERROR(
    IN LPCTSTR str,
    IN const HRESULT hr)
{
    TCHAR szErrorCode[16] = {0};

    EventLogMsg(
        POPPST_INTERNAL_ERROR,
        2, str, _itot( hr, szErrorCode, 16),
        0);
}

static inline LPCTSTR SZ_BASE10(
    IN const INT iNumber)
{
    static TCHAR szTemp[16] = {0};
    return( (LPCTSTR)_itot( iNumber, szTemp, 10));
}

static inline LPCTSTR SZ_BASE16(
    IN const ULONG ulNumber)
{
    static TCHAR szTemp[16] = {0};
    return( (LPCTSTR)_itot( ulNumber, szTemp, 16));
}

//$--DisplayUserMsg----------------------------------------------------------
//
// DESCRIPTION: Utility to display message to command line user.
//
// INPUT:       hInst   --  program instance handle
//              nResID  --  Resource string identifier
//
// RETURNS:     VOID
//
// ----------------------------------------------------------------------------
VOID DisplayUserMsg(            // RETURNS: nothing
            IN UINT nResID,     // resource string identifier
            ...)                // additional arguments
{
    INT     nRetCode    = 0;// return value
    va_list vArgList    =   {0};        // variable argument list
static HMODULEhModule=NULL;// application instance handle
static BOOLfInit=FALSE;// TRUE if module handle has is initialized
DWORDnLastErr=0;// last Win32 API error

    const INTnMsgLength=   256;        // maximum length of printed messages

// LoadString message buffer
    CHAR  szMessage[nMsgLength]  =   "";

    DEBUGPRIVATE("DisplayUserMsg()\n");

    // No CHK_DisplayUserMsg() call.
    // We check arguments as we go, as we may be in an error state.

    // Check the instance handle
    if ( fInit == FALSE )
    {
// Retrieve this programs instance handle.
hModule = GetModuleHandle(NULL);

fInit = TRUE;// have retrieved instance handle

// Print out error if have failed.
if ( hModule == NULL )
{
nLastErr = GetLastError();

fprintf(stderr, "ERROR: Unable to display resource messages to console: "
"error code 0x%lx,\n", nLastErr);

HR_LOG(HRESULT_FROM_WIN32(nLastErr));
}
    }

// Check validity of module handle
if ( hModule == NULL )
{
// can't do anything
goto cleanup;
}

// retrieve resource string
    nRetCode = LoadString(
    hModule,
    nResID,
    szMessage,
    nMsgLength);

    if ( nRetCode == 0 )
    {
        // Print out default error string.
nLastErr = GetLastError();

fprintf(stderr, "ERROR: Can't load resource string %ld: "
"error code 0x%lx,\n", nResID, nLastErr);

        HR_LOG(HRESULT_FROM_WIN32(nLastErr));

        goto cleanup;
    }

    // Get an optional argument list pointer
    va_start(vArgList, nResID);

    // Print the message to standard out
    vprintf(szMessage,          // format string
            vArgList);          // variable argument list

    va_end(vArgList);

// we are done.

cleanup:

    // Return
    return;

}   // end DisplayUserMsg()

//$--main--------------------------------------------------------------------
//
// DESCRIPTION: poppst.exe main() routine.
//
// INPUT:       argc    --  argument count
//              argv    --  command line arguments
//
// RETURNS:     INT --     0 on success;
//                         E_INVALIDARG if invalid parameter,
//                         E_OUTOFMEMORY if memory problems,
//                         E_FAIL if internal error.
//
//---------------------------------------------------------------------------

INT main(               // RETURNS: INT
    IN  INT     argc,       // # arguments on command line
    IN  CHAR *  argv[]      // command line arguments array
    )
{
    HRESULT hr  =   NOERROR;
INT            iCounter        =0;// argument index
BOOLbMAPIInitialized =FALSE;
    LINEREADSTATUS      lrs =   LRS_OKAY;
    BOOL    bIsSectionLine  =   FALSE;  // TRUE if the line is a section line
HANDLEhEvent=NULL;// event logging handle
EDKEVENTCOUNTsEventCount={0};// event log count structure
BOOLfEventLogOpened=FALSE;// TRUE if event log is open

    DEBUGPUBLIC("main()\n");

    // Check input parameters.
hr = CHK_main(argc, argv);
if ( FAILED(hr) )
goto cleanup;

// parse comand line
    if (argc == 1)
    {
// Display usage message
DisplayUserMsg(
IDS_USAGE);
DisplayUserMsg(
IDS_NEWLINE);
DisplayUserMsg(
IDS_USAGEFILE);
DisplayUserMsg(
IDS_USAGEHELP);

        hr = HR_LOG(E_INVALIDARG);

        goto cleanup;
    }

    // Check to see if user wants help (/? or /HELP flag)
    for ( iCounter = 0; iCounter < argc; iCounter++ )
    {
        ASSERT_STRING_PTR(argv[iCounter], "Bad argv[iCounter]");

        if ( argv[iCounter][0] == '/' || argv[iCounter][0] == '-')
        {
            if ( (argv[iCounter][1] == '?') ||
                 (argv[iCounter][1] == 'H') ||
                 (argv[iCounter][1] == 'h') )
            {
                // display help message
DisplayUserMsg(
IDS_HELP);
DisplayUserMsg(
IDS_NEWLINE);
DisplayUserMsg(
IDS_USAGE);
DisplayUserMsg(
IDS_NEWLINE);
DisplayUserMsg(
IDS_USAGEFILE);
DisplayUserMsg(
IDS_HELPDOC);

                hr = HR_LOG(E_INVALIDARG);

                goto cleanup;
            }
        }
    }   // end for

    // Check number of parameters.  (Should be 2, including the
    // program name).
    if ( argc != 2 )
    {
DisplayUserMsg(
IDS_PARAM_NUMBER);

        hr = HR_LOG(E_INVALIDARG);

        goto cleanup;
    }

    if ( FAILED(hr) )
        goto cleanup;

// Open an event logging handle for this application.
 hr = HrEventOpenLog(
szAppName,// application name
NULL,// executable name (computed)
    NULL,// event message file (computed)
NULL,// parameter message file
NULL,// category message file
&hEvent);// event logging handle

if ( FAILED(hr) )
{
DisplayUserMsg(
IDS_EVENT_LOG,
hr);

goto cleanup;
}

ASSERTERROR((hEvent != NULL), "Bad hEvent");

fEventLogOpened = TRUE;

// Let user know that we are doing something.
DisplayUserMsg(
IDS_PROCESSING,
argv[1]);

    // open the INI file.
    IniFile = fopen(argv[1], "r");

if ( IniFile == NULL )
{
EventLogMsg(
POPPST_OPEN_FILE_ERROR,
1,// # of string replacements
argv[1],
0);// # of Win32 error codes

        hr = HR_LOG(E_INVALIDARG);
        goto cleanup;
}

hr = MAPIInitialize(0);

if (FAILED(hr))
{
EventLogMsg(
POPPST_MAPIINIT_ERROR,
1, SZ_BASE16(hr),
0);

        hr = HR_LOG(E_FAIL);

goto cleanup;
}

bMAPIInitialized = TRUE;

InitSectionData(FALSE);

    while (TRUE)
    {
hr = HrGetNextLine(&lrs);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

if (lrs == LRS_OKAY)
{
            hr = HrIsSectionLine(achIniLine, &bIsSectionLine);

            if ( FAILED(hr) )
            {
                goto cleanup;
            }

if ( bIsSectionLine )
{
if ( fSectionReady(IFST_MESSAGE) == TRUE )
{
    hr = HrProcessMessageSection();

                    if ( FAILED(hr) )
                    {
                        goto cleanup;
                    }
}
else if ( fSectionReady(IFST_NEWPST) == TRUE )
{
                    hr = HrProcessNewPSTSection();

if ( FAILED(hr) )
                    {
goto cleanup;
                    }
}
else if ( fSectionReady(IFST_PROFILE) == TRUE )
{
                    hr = HrProcessProfileSection();

if ( FAILED(hr) )
                    {
goto cleanup;
                    }
}

// If ifstCurSection == IFST_UNKNOWN, we don't have a valid
// section at present, and do nothing.
}

            hr = HrParseLine();

        if ( FAILED(hr) )
        {
EventLogMsg(
POPPST_SYNTAX_ERROR,
1, SZ_BASE10(iLine),
0);
            }
}
else if (lrs == LRS_LINETOOLONG)
{
EventLogMsg(
POPPST_LENGTH_ERROR,
1, SZ_BASE10(iLine),
0);
}
else
{
break;
}

        iLine++;
    }

    if (lrs == LRS_EOF)
    {
if ( fSectionReady(IFST_MESSAGE) == TRUE )
{
hr = HrProcessMessageSection();

            if ( FAILED(hr) )
            {
                goto cleanup;
            }
}
else if ( fSectionReady(IFST_NEWPST) == TRUE )
{
            hr = HrProcessNewPSTSection();

if ( FAILED(hr) )
            {
goto cleanup;
            }
}
else if ( fSectionReady(IFST_PROFILE) == TRUE )
{
            hr = HrProcessProfileSection();

if ( FAILED(hr) )
            {
goto cleanup;
            }
}

    }// end if have reached end of file

// Dertermine number of errors, warnings and informationsl
// messages logged to the event log.
hr = HrEventGetCounts(
&sEventCount);// structure to hold event logging statistics

if ( FAILED(hr) )
{
        INTERNAL_ERROR( TEXT("HrEventGetCounts()"), hr);

sEventCount.cError = 1;// have at least one error
}

// if no errors, check for proper program termination state
if ( sEventCount.cError == 0 )
{
if ( (lrs == LRS_EOF) && (ifstCurSection == IFST_UNKNOWN) )
{
// Have an empty INI file.
// Print out error to the user.
hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_EMPTY_ERROR,
1,
argv[1],
0);

goto cleanup;

}// end if have an empty file

    else if (lrs == LRS_IOERROR)
    {
EventLogMsg(
POPPST_IO_ERROR,
1, SZ_BASE10(iLine),
0);

goto cleanup;
    }

// Otherwise, we are successful
   else
    {
// Log this fact.
EventLogMsg(
POPPST_POPULATE_SUCCEEDED,
0,
0);
    }

}// end if no error message printed out so far

// We are done.

cleanup:

// Print out a completion message to the user.
// First, determine the number of errors, warnings and informationsl
// messages logged to the event log.
if ( fEventLogOpened == TRUE )
{
ZeroMemory(&sEventCount, sizeof(sEventCount));
hr = HrEventGetCounts(
&sEventCount);// structure to hold event logging statistics

if ( FAILED(hr) )
{
            INTERNAL_ERROR( TEXT("HrEventGetCounts()"), hr);

sEventCount.cError = 1;// have at least one error
}

if ( sEventCount.cError == 0 )
{
// print success message
DisplayUserMsg(
IDS_SUCCESS);
}

else if ( sEventCount.cError == 1 )
{
// print one error message
DisplayUserMsg(
IDS_1ERROR);
}

else
{
// print multiple error message
DisplayUserMsg(
IDS_ERRORS);
}

if ( sEventCount.cWarning == 1 )
{
// print out one warning message
DisplayUserMsg(
IDS_1WARNING);
}

else if ( sEventCount.cWarning > 1 )
{
// print out mutliple warnings message
DisplayUserMsg(
IDS_WARNINGS);
}

// close event log
(VOID)HrEventCloseLog();
hEvent = NULL;
fEventLogOpened = FALSE;

}// end if have event log handle

// Release MAPI and OLE objects
ULRELEASE(lpAdrBook);
ULRELEASE(lpRootFolder);
ULRELEASE(lpMdb);

if (lpSession)
{
    lpSession->Logoff(0, 0, 0);
ULRELEASE(lpSession);
}

if (fTmpProf)
{
HrCleanupPSTGlobals(lpszProfileName);

        MAPIFREEBUFFER(lpszProfileName);

MAPIFREEBUFFER(lpszProfilePassword);
}

if (bMAPIInitialized)
    {
    MAPIUninitialize();
    }

    if (IniFile)
    {
        fclose(IniFile);
    }

    // return the appropriate exit code, based on our HRESULT.
    return _nEcFromHr(hr);
}

//$--HrCreateMessageAttachment-----------------------------------------------
//
// DESCRIPTION: Create a message attachment and write the contents of the
//specified file to it.  Also create various required properties
//for the message attachment.
//
// INPUT:
//
//[lpMsg]-- Message for which attachment is to be created.
//[lpszAttachPath]-- Path of file containing attachment text.
//
// RETURNS: HRESULT --  NOERROR if O.K.,
//                      E_INVALIDARG if invalid parameter,
//                      E_OUTOFMEMORY if memory problems,
//                      E_FAIL if call fails
//
//---------------------------------------------------------------------------

HRESULT HrCreateMessageAttachment(          // RETURNS: HRESULT
INLPMESSAGElpMsg,              // MAPI message pointer
INLPSTRlpszAttachPath      // list of attachment file names
)
{
    HRESULT         hr              =   NOERROR;    // return code
ULONGcb              =   0;
LPATTACHlpAttach        =NULL;
ULONGulAttachmentNum =   0;
    ULONG           nAttachments    =   0;      // # of attachments
    ULONG           iAttach         =   0;      // attachment index
    LPSTR           lpszCurrent     =   NULL;   // current attachment
LPSTR *lppszAttachTok=NULL;// array of tokenized attachments

    // MAPI property creation flags.
    const ULONG     ulFlags     =   MAPI_CREATE | MAPI_MODIFY |
                                    MAPI_DEFERRED_ERRORS;   // reduces RPCs

    // Buffer read/write size
    const UINT      cbBufSize           =   4096;

    // read/write data buffer
BYTEab[cbBufSize]       =   {0};

    // # of attachment properties
    const ULONGcValues             =3;

    // property value array
    SPropValue      aPropVals[cValues]  =   {0};

    DEBUGPRIVATE("HrCreateMessageAttachment()\n");

    // Check input parameters
    hr = CHK_HrCreateMessageAttachment(lpMsg, lpszAttachPath);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Determine number of attachments by breaking into tokens
hr = HrStrTokAll(
lpszAttachPath,// file name list
(LPSTR) pszSeparator,// separator character
&nAttachments,// # of file names
&lppszAttachTok);// array of strings pointer

if ( FAILED(hr) )
{
        INTERNAL_ERROR( TEXT("HrStrTokAll()"), hr);

goto cleanup;
}

    ASSERTERROR((nAttachments != 0), "Bad nAttachments");
ASSERT_READ_PTR(lppszAttachTok, sizeof(LPSTR) * nAttachments,
"Bad lppszAttachTok");

    // Process each attachment.
    for ( iAttach = 0; iAttach < nAttachments; iAttach++ )
    {
        // Release MAPI objects
    ULRELEASE(lpAttach);

// Retrieve next attachment
lpszCurrent = lppszAttachTok[iAttach];

ASSERT_STRINGA_PTR(lpszCurrent, "Bad lpszCurrent");

    // Create the attachment in the message.
    hr = lpMsg->CreateAttach(NULL,
 MAPI_DEFERRED_ERRORS,  // reduces RPCs
 &ulAttachmentNum,
 &lpAttach);

    if ( FAILED(hr) )
    {
            INTERNAL_ERROR( TEXT("IMessage::CreateAttach()"), hr);

            hr = HR_LOG(E_FAIL);

    goto cleanup;
    }

        ASSERT_IUNKNOWN_PTR(lpAttach, "Bad lpAttach");

    // Create required additional properties.
LPSPropProblemArraylpProblems =NULL;
LPSTRlpszFileName    =   NULL;

aPropVals[0].ulPropTag =PR_ATTACH_METHOD;
aPropVals[0].Value.l =ATTACH_BY_VALUE;

lpszFileName = strrchr(lpszCurrent, cBackSlash);

if (!lpszFileName)
lpszFileName = lpszCurrent;
else
lpszFileName++;// Advance past the backslash.

// CAVEAT - We don't do anything here to check for invalid FAT 8.3
//    names.  This may be a good idea.
aPropVals[1].ulPropTag =PR_ATTACH_FILENAME;
aPropVals[1].Value.lpszA =lpszFileName;

aPropVals[2].ulPropTag =PR_ATTACH_LONG_FILENAME;
aPropVals[2].Value.lpszA =lpszFileName;

hr = lpAttach->SetProps(cValues, aPropVals, &lpProblems);

if (FAILED(hr))
{
            INTERNAL_ERROR( TEXT("IMAPIProp::SetProps()"), hr);

            hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (lpProblems != NULL)
{
ULONGi   =   0;
for (i = 0; i < lpProblems->cProblem; i++)
{
                INTERNAL_ERROR( TEXT("IMAPIProp::SetProps()"), lpProblems->aProblem[i].scode);
}

MAPIFREEBUFFER(lpProblems);

            hr = HR_LOG(E_FAIL);
goto cleanup;
}

        // Create the PR_ATTACH_DATA_BIN property from the attachment
        // file text.
        hr = HrMAPISetPropFromFile(
            lpAttach,               // pointer to attachment object
            PR_ATTACH_DATA_BIN,     // property tag
            lpszCurrent,            // fully-pathed file name from which to read data
            &cb);                   // # bytes read in

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("HrMAPISetPropFromFile()"), hr);

            goto cleanup;
        }

    // Commit the attachment changes.
    hr = lpAttach->SaveChanges(0);

    if (FAILED(hr))
    {
            INTERNAL_ERROR( TEXT("IMAPIProp::SaveChanges()"), hr);

            hr = HR_LOG(E_FAIL);
    goto cleanup;
    }
    }   // end for each attachment

    // we are done

cleanup:

    // Release MAPI and OLE objects
ULRELEASE(lpAttach);

// Free MAPI buffers
MAPIFREEBUFFER(lppszAttachTok);

RETURN(hr);

}

//$--HrGetNextLine-----------------------------------------------------------
//
// DESCRIPTION: Get the next input line from the ini file and deposit it in
//the input buffer (achIniLine).
//
// OUTPUT:  LINEREADSTATUS *    --  pointer to line read status
//
//  LRS_OKAY if line was read and is available.
//  LRS_EOF if we are at end of file; no more lines.
//  LRS_IOERROR if there was an I/O error in the last read.
//  LRS_LINETOOLONG if last line was too long to fit in the buffer.
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad parameter,
//
//---------------------------------------------------------------------------

HRESULT HrGetNextLine(                  // RETURNS: HRESULT
    OUT LINEREADSTATUS * lpReadStatus)  // pointer to line read status
{
    HRESULT         hr  =   NOERROR;    // return code
    INT             ch  =   0;
    CHAR *          pch =       &achIniLine[0];
    CHAR *          pchInv =    &achIniLine[cbMaxIniLine];

    DEBUGPRIVATE("HrGetNextLine()\n");

    // Check input parameters.
    hr = CHK_HrGetNextLine(lpReadStatus);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *lpReadStatus = LRS_OKAY;

    while (TRUE)
    {
        if (pch == pchInv)
        {
            // Line is too long
            *lpReadStatus =           LRS_LINETOOLONG;
            achIniLine[0] = 0;
            while ((ch = getc(IniFile)) != EOF && (ch != cNewLine));

            break;
        }

        ch = getc(IniFile);

        if (ch == cNewLine)
        {
            // Just read end of this line
            *lpReadStatus =   LRS_OKAY;
            *pch =  0;

            break;
        }

        if (ch == EOF)
        {
            // Just read last line in file
            // We want to process the last line, if any.
            *pch = 0;

            if ( pch == &achIniLine[0] )
            {
                // we have passed the last line in the file.
                *lpReadStatus = feof(IniFile) ? LRS_EOF : LRS_IOERROR;
            }

            else
            {
                // we just read last line.  We want to process
                // its data.
                *lpReadStatus = feof(IniFile) ? LRS_OKAY : LRS_IOERROR;
            }

            break;

        }   // end if end of file reached

        *pch = ch;
        pch++;
    }

    RETURN(hr);

}

//$--InitSectionData---------------------------------------------------------
//
// DESCRIPTION: Initialize static variables that will be used in parsing an
//ini file section to default values.
//
// INPUT:       None.
//
// RETURNS:     Nothing.
//
//---------------------------------------------------------------------------

VOID InitSectionData(           // RETURNS: VOID
IN BOOL fCommented)// TRUE if section is commented out
{
    DEBUGPRIVATE("InitSectionData()\n");

// Record whether or not section is commented out
fCurSectionComment = fCommented;

ifstCurSection =IFST_UNKNOWN;

iflvBCC.fValid =FALSE;
iflvCC.fValid =FALSE;
iflvDATE.fValid =FALSE;
iflvENCRYPTION.fValid =FALSE;
iflvFOLDER.fValid =FALSE;
iflvFROM.fValid =FALSE;
iflvPATH.fValid =FALSE;
iflvPRIORITY.fValid =FALSE;
iflvPROFILENAME.fValid =FALSE;
iflvPROFILEPASSWORD.fValid =FALSE;
iflvPSTNAME.fValid =FALSE;
iflvPSTPASSWORD.fValid =FALSE;
iflvSUBJECT.fValid =FALSE;
iflvTIME.fValid =FALSE;
iflvTO.fValid =FALSE;
iflvXATTACH.fValid =FALSE;
iflvXTEXT.fValid =FALSE;
}

//$--HrIsSectionLine-----------------------------------------------------------
//
// DESCRIPTION: Determine if an input line is a section header (ie., first
//non-whitespace char is '[').
//
// INPUT:       [pch]-- Ptr to input line.
//
// OUTPUT:      lpbIsSectionLine    --  BOOLEAN pointer.  TRUE if is section line.
//
// RETURNS:     HRESULT --  NOERROR if successful
//                          E_INVALIDARG if bad parameter
//---------------------------------------------------------------------------

HRESULT HrIsSectionLine(                    // RETURNS: HRESULT
    IN      CHAR *  pch,                    // input line
    OUT     BOOL * lpbIsSectionLine     // boolean pointer
    )
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("HrIsSectionLine()\n");

    // Check input parameters.
    hr = CHK_HrIsSectionLine(pch, lpbIsSectionLine);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

// skip leading white space and comment characters
    while ( (isspace(*pch) > 0) || (*pch == cSemiColon) )
{
        pch++;
}

*lpbIsSectionLine = (*pch == cBeginSectLine);// All sections must start with an '['

    RETURN(hr);

}

//$--HrOpenFolder------------------------------------------------------------
//
// DESCRIPTION: Open the folder specified by the iflvFOLDER static variable.
//
// INPUT:       None.
//
// OUTPUT:      lppFolder   --  folder pointer pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//              E_INVALIDARG if invalid parameters,
//              E_FAIL otherwise
//
//---------------------------------------------------------------------------

HRESULT HrOpenFolder(                   // RETURNS: HRESULT
    OUT LPMAPIFOLDER * lppFolder)   // output folder pointer pointer
{
    HRESULT         hr          =   NOERROR;
    UINT            cchFolder   =   0;      // # characters in folder

    const CHAR      chSep    =   cBackSlash;      // separator character

    DEBUGPRIVATE("HrOpenFolder()\n");

    // Check input parameters
    hr = CHK_HrOpenFolder(lppFolder);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *lppFolder = NULL;  // initialize

    // Strip the trailing '\', if any.
    cchFolder = lstrlen(iflvFOLDER.achValue);

    ASSERTERROR((cchFolder > 0), "Bad cchFolder");

    if ( iflvFOLDER.achValue[cchFolder - 1] == cBackSlash )
    {
        iflvFOLDER.achValue[cchFolder - 1] = 0;
    }

    // Determine if getting root folder
if (iflvFOLDER.achValue[0] == 0)
    {
*lppFolder = lpRootFolder;

        goto cleanup;
    }

    // Otherwise, opent the pathed folder.
    hr = HrMAPIOpenFolderEx(
        lpMdb,                      // message store pointer
        chSep,                      // folder path separator character
        iflvFOLDER.achValue,        // folder path
        lppFolder);                 // pointer to folder opened

    if ( FAILED(hr) )
    {
EventLogMsg(
POPPST_FOLDER_ERROR,
2, iflvFOLDER.achValue, SZ_BASE16(hr),
            0);

        goto cleanup;
    }

cleanup:

RETURN(hr);

}

//$--HrParseDateValue--------------------------------------------------------
//
// DESCRIPTION: Parse a DATE= entry for the value.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     HRESULT --  NOERROR if success,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if call fails.
//
//---------------------------------------------------------------------------

HRESULT HrParseDateValue(           // RETURNS: HRESULT
    IN      CHAR *  pch             // date string
    )
{
    HRESULT hr  =   NOERROR;    // return value
    static CHAR    DateTemplate[] = "dddd-dd-dd";
    INT     i = 0;

    DEBUGPRIVATE("HrParseDateValue()\n");

    // Check input parameters
    hr = CHK_HrParseDateValue(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    for (i = 0; i < sizeof(DateTemplate); i++)
    {
        if (DateTemplate[i] == cDate)
        {
            if (!isdigit(*(pch + i)))
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }
        }
        else
        {
            if (DateTemplate[i] != *(pch + i))
            {
                hr = HR_LOG(E_FAIL);

                goto cleanup;
            }
        }
    }

// Given that we can't REALLY do range checking without knowing about
// leap years, we rely on SystemTimeToFileTime() to later do complete range
// checking on the year/month/day values.  We can cast the values to words
// without a problem, given we know there are a most 4 decimal digits.

stCurMsg.wYear =(WORD)atol(pch);
stCurMsg.wMonth =(WORD)atol(pch + 5);
stCurMsg.wDay =(WORD)atol(pch + 8);

cleanup:

    RETURN(hr);

}

//$--HrParseEncryptionValue----------------------------------------------------
//
// DESCRIPTION: Parse an ENCRYPTION= entry for the value.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if call fails
//
//---------------------------------------------------------------------------
HRESULT HrParseEncryptionValue(         // RETURNS: HRESULT
    IN      CHAR *  pch                 // input encryption string
    )
{
    HRESULT hr  =   NOERROR;    // return value

    DEBUGPRIVATE("HrParseEncryptionValue()\n");

    // check input parameters
    hr = CHK_HrParseEncryptionValue(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

if (!_stricmp(pch, "ON"))
bEncryptionNewPST = TRUE;
else if (!_stricmp(pch, "OFF"))
bEncryptionNewPST = FALSE;
else
    {
    hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--HrParseEntryLine--------------------------------------------------------
//
// DESCRIPTION: Parse an ini file section entry line to determine what kind of
//entry it is, and store the value.  Section entry lines are the
//lines that follow the section header.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if call fails.
//
//---------------------------------------------------------------------------

HRESULT HrParseEntryLine(       // RETURNS: HRESULT
    IN      CHAR *  pch         // input line
    )
{
    HRESULT             hr  =                   NOERROR;
    INIFILEPARSEINFO *  aifpiEntry  =           NULL;
    CHAR                chTmp   =               0;
    INT                 cKeywords   =           0;
    INT                 i   =                   0;
    CHAR *              pchKeyword  =           NULL;

    DEBUGPRIVATE("HrParseEntryLine()\n");

    // Check input parameters
    hr = CHK_HrParseEntryLine(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    if (ifstCurSection == IFST_MESSAGE)
    {
        aifpiEntry = aifpiMessageEntry;
        cKeywords = sizeof(aifpiMessageEntry)/sizeof(aifpiMessageEntry[0]);
    }
    else if (ifstCurSection == IFST_NEWPST)
    {
        aifpiEntry = aifpiNewPSTEntry;
        cKeywords = sizeof(aifpiNewPSTEntry)/sizeof(aifpiNewPSTEntry[0]);
    }
    else if (ifstCurSection == IFST_PROFILE)
    {
        aifpiEntry = aifpiProfileEntry;
        cKeywords = sizeof(aifpiProfileEntry)/sizeof(aifpiProfileEntry[0]);
    }
    else
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Save ptr to keyword.

    pchKeyword = pch;

    // Scan for end of keyword and delimit keyword, saving replaced char.

    while (*pch != cEquals && *pch != 0)
        pch++;

    if (*pch == 0)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    chTmp = *pch;
    *pch  = 0;

    // Compare putative keyword against valid section keywords.

    for (i = 0; i < cKeywords; i++)
        if (!_stricmp(pchKeyword, aifpiEntry[i].pszKeyword))
            break;

    *pch = chTmp;

    if (i == cKeywords)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    hr = aifpiEntry[i].HrParseFunction(pch + 1);

    // Store result if success.

    if ( SUCCEEDED(hr) )
    {
// Check for duplicate entries in a section:

if (aifpiEntry[i].piflv->fValid)
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

aifpiEntry[i].piflv->fValid = TRUE;
        strcpy(aifpiEntry[i].piflv->achValue, pch + 1);
    }

cleanup:

    RETURN(hr);

}

//$--HrParseLine---------------------------------------------------------------
//
// DESCRIPTION: Parse an ini file line, and store the value if it is a section
//entry line.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
//---------------------------------------------------------------------------

HRESULT HrParseLine(VOID)       // RETURNS: HRESULT
{
    HRESULT     hr  =       NOERROR;
    CHAR *      pch =       &achIniLine[0];
BOOLfCommentedSection=FALSE;// TRUE if see commented section

    DEBUGPRIVATE("HrParseLine()\n");

    while ( isspace(*pch) > 0 )// Skip leading line whitespace.
        pch++;

    if (*pch == 0)               // It is a blank line.
    {
        goto cleanup;
    }

    if (*pch == cSemiColon)           // It is a comment.
    {
// Skip intervening white spaces and comments
while ( (isspace(*pch) > 0) || (*pch == cSemiColon) )
{
pch++;
}

// Check to see if the commented out line is a section header.
// If if is, then the effect is that the entire section is
// commented out.
if ( *pch == cBeginSectLine )
{
// have a commented-out section
fCommentedSection = TRUE;

// continue on to next if statement
}

else
{
// commented out line, ignore it.
        goto cleanup;
}
    }

    if ( *pch == cBeginSectLine )  // It is a section, or is invalid
    {
iSection = iLine;
        hr = HrParseSectionLine(
        pch + 1, // pointer to section keyword
        fCommentedSection);// TRUE if have a commented-out section

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }
    else                            // It is a section entry, or is invalid.
    {
        hr = HrParseEntryLine(pch);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

cleanup:

    RETURN(hr);

}

//$--HrParseNonNullStringValue-----------------------------------------------
//
// DESCRIPTION: Parse a non-null string value for an entry line (ie., the
//null string is not a permissible value).
//
// INPUT:       [pch]-- Ptr to input string.
//
// HRESULT:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if invalid parameter,
//                          E_FAIL if call fails
//---------------------------------------------------------------------------

HRESULT HrParseNonNullStringValue(          // RETURNS: HRESULT
    IN      CHAR *  pch                     // input line
    )
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("HrParseNonNullStringValue()\n");

    // Check input parameters.
    hr = CHK_HrParseNonNullStringValue(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    if ( *pch == 0 )
    {
        hr = HR_LOG(E_FAIL);
    }

    RETURN(hr);

}

//$--HrParsePriorityValue----------------------------------------------------
//
// DESCRIPTION: Parse a PRIORITY= section entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// HRESULT:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if call fails
//
//---------------------------------------------------------------------------

HRESULT HrParsePriorityValue(           // RETURNS: HRESULT
    IN      CHAR *  pch                 // input line
    )
{
    HRESULT hr  =   NOERROR;    // return value

    DEBUGPRIVATE("HrParsePriorityValue()\n");

    // Check input parameters.
    hr = CHK_HrParsePriorityValue(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

if (!_stricmp(pch, "NORMAL"))
    {
lPriorityCurMsg = PRIO_NORMAL;
        lImportanceCurMsg = IMPORTANCE_NORMAL;
    }
else if (!_stricmp(pch, "HIGH"))
    {
lPriorityCurMsg = PRIO_URGENT;
        lImportanceCurMsg = IMPORTANCE_HIGH;
    }
else if (!_stricmp(pch, "LOW"))
    {
lPriorityCurMsg = PRIO_NONURGENT;
        lImportanceCurMsg = IMPORTANCE_LOW;
    }
else
    {
        hr = HR_LOG(E_FAIL);
    }

RETURN(hr);

}

//$--HrParseSectionLine------------------------------------------------------
//
// DESCRIPTION: Parse an ini file section header line.
//
// INPUT:       [pch]-- Ptr to section keyword
//fComment--TRUE if section is commented-out
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if invalid input,
//                          E_FAIL if call fails.
//
//---------------------------------------------------------------------------

HRESULT HrParseSectionLine(
    IN      CHAR *  pch,            // input line
INBOOL fComment// TRUE if section is commented
    )
{
    HRESULT     hr  =   NOERROR;
    CHAR        chTmp   =   0;
    CHAR *      pchKeyword  =   NULL;

    DEBUGPRIVATE("HrParseSectionLine()\n");

    // Check input parameters
    hr = CHK_HrParseSectionLine(pch, fComment);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

InitSectionData(
fComment);// TRUE if section is commented

    // Save ptr to keyword.

    pchKeyword = pch;

    // Scan for end of keyword and delimit keyword, saving replaced char.

    while (!isspace(*pch) && *pch != cEndSectLine && *pch != 0)
        pch++;

    if (*pch == 0)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    chTmp = *pch;
    *pch  = 0;

    // Compare putative keyword against valid section keywords.

    if (!_stricmp(pchKeyword, "message"))
    {
// Numeric value may follow "message" keyword.  We don't use it,
// but we do a rough check that what we are seeing is consistent
// with what we expect.

        *pch = chTmp;

        while ( isspace(*pch) > 0 )
            pch++;

        if (!isdigit(*pch))
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

pch++;

        while (isdigit(*pch))
            pch++;

        // Check for proper section entry termination.

        if (*pch == cEndSectLine && *(pch + 1) == 0)
        {
            ifstCurSection =IFST_MESSAGE;
        }
    }
    else if (!_stricmp(pchKeyword, "newpst"))
    {
        // Check for proper section entry termination.
        // NewPST sections don't have a value.

        *pch = chTmp;

        if (*pch == cEndSectLine && *(pch + 1) == 0)
        {
            ifstCurSection =IFST_NEWPST;
        }
    }
    else if (!_stricmp(pchKeyword, "profile"))
    {
        // Check for proper section entry termination.
        // Profile sections don't have a value.

        *pch = chTmp;

        if (*pch == cEndSectLine && *(pch + 1) == 0)
        {
            ifstCurSection =IFST_PROFILE;
        }
    }
    else
    {
        hr = HR_LOG(E_FAIL);
        *pch = chTmp;   // Invalid section keyword.

        goto cleanup;

    }

cleanup:

    RETURN(hr);

}

//$--HrParseStringValue--------------------------------------------------------
//
// DESCRIPTION: Parse a string value for an entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// HRESULT:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input.
//
//---------------------------------------------------------------------------

HRESULT HrParseStringValue(     // RETURNS: HRESULT
    IN      CHAR *  pch         // input line
    )
{
    HRESULT hr  =   NOERROR;

    DEBUGPRIVATE("HrParseStringValue()\n");

    // Check input parameters.
    hr = CHK_HrParseStringValue(pch);

    // Anything is a valid string!

    RETURN(hr);

}

//$--HrParseTimeValue--------------------------------------------------------
//
// DESCRIPTION: Parse a TIME= section entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL if call fails.
//
//---------------------------------------------------------------------------

HRESULT HrParseTimeValue(           // RETURNS: HRESULT
    IN      CHAR *  pch             // input line
    )
{
    HRESULT hr  =   NOERROR;
    long    Hour    =   0;
    long    Minute  =   0;
    long    Second = 0;

    DEBUGPRIVATE("HrParseTimeValue()\n");

    // Check input parameters.
    hr = CHK_HrParseTimeValue(pch);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    if (!isdigit(*pch)          ||
        !isdigit(*(pch + 1))    ||
        *(pch + 2) != cColon)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    Hour = atol(pch);

    if (Hour > 23)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

stCurMsg.wHour = (WORD)Hour;

    pch += 3;

    if (!isdigit(*pch)                              ||
        !isdigit(*(pch + 1))                        ||
        (*(pch + 2) != cColon && *(pch + 2) != 0))
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    Minute = atol(pch);

    if (Minute > 59)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

stCurMsg.wMinute = (WORD)Minute;

    if (*(pch + 2) == cColon)
    {
        pch += 3;

        if (!isdigit(*pch)                              ||
            !isdigit(*(pch + 1))                        ||
            *(pch + 2) != 0)
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        Second = atol(pch);

        if (Second > 59)
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }
    }

stCurMsg.wSecond = (WORD)Second;

    if (Hour * 60L * 60L + Minute * 60L + Second >= 24L * 60L * 60L)
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--HrProcessMessageSection---------------------------------------------------

//
// DESCRIPTION: Process the information parsed in a message section, creating
//a message in a pst.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
//---------------------------------------------------------------------------

HRESULT HrProcessMessageSection(VOID)        // RETURNS: HRESULT
{
HRESULThr  =   NOERROR;
LPADRLISTlpRecips =NULL;
LPMAPIFOLDERlpFolder =NULL;
LPMESSAGElpMsg =NULL;

    DEBUGPRIVATE("HrProcessMessageSection()\n");

// Confirm that a NewPST or Profile section has been parsed and a MAPI
// session has been established.
if (!lpSession)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_ORDER_ERROR,
1, SZ_BASE10(iSection),
0);

goto cleanup;
}

// Confirm that the Message section has the required section entries.

if (!iflvDATE.fValid||
!iflvFOLDER.fValid||
!iflvFROM.fValid||
!iflvPRIORITY.fValid||
!iflvSUBJECT.fValid||
!iflvTIME.fValid||
!iflvTO.fValid)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_MISSING_ERROR,
2, TEXT("Message"), SZ_BASE10(iSection),
0);

goto cleanup;
}

// Convert message date/time to FILETIME.

if (!SystemTimeToFileTime(&stCurMsg, &ftCurMsg))
{
        INTERNAL_ERROR( TEXT("SystemTimeToFileTime()"), GetLastError());

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Open the specified folder, or if it does not exist, create it.
// If the root folder was specified, the value of pRootFolder will
// be returned.
    hr = HrOpenFolder(&lpFolder);

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

// Create the message.
hr = lpFolder->CreateMessage(NULL, MAPI_DEFERRED_ERRORS, &lpMsg);

if (FAILED(hr))
{
        INTERNAL_ERROR( TEXT("IMAPIFolder::CreateMessage()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Set the sender, subject, date/time, and priority properties.
    hr = HrSetMessageProperties(lpMsg);

    if ( FAILED(hr) )
    {
goto cleanup;
    }

// Write the message text, if any
hr = HrWriteMessageText(lpMsg, iflvXTEXT.achValue);

    if ( FAILED(hr) )
    {
goto cleanup;
    }

// Resolve recipient name information.
hr = HrResolveRecipientNames(&lpRecips);

    if ( FAILED(hr) )
    {
goto cleanup;
    }

// Set recipient info.
hr = lpMsg->ModifyRecipients(MODRECIP_ADD, lpRecips);

if (FAILED(hr))
{
        INTERNAL_ERROR( TEXT("IMessage::ModifyRecipients()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

// If there is an attachment, create it.
if (iflvXATTACH.fValid)
{
hr = HrCreateMessageAttachment(lpMsg, iflvXATTACH.achValue);

        if ( FAILED(hr) )
        {
goto cleanup;
        }
}

    // save the message!
hr = lpMsg->SaveChanges(0);

if (FAILED(hr))
{
        INTERNAL_ERROR( TEXT("IMAPIProp::SaveChanges()"), hr);

        hr = HR_LOG(E_FAIL);
    goto cleanup;
    }

// Done!
cMsgs++;

cleanup:

if (lpRecips)
{
// Have to release the SPropVal allocations first.
ULONGi;

for (i = 0; i < lpRecips->cEntries; i++)
MAPIFREEBUFFER(lpRecips->aEntries[i].rgPropVals);

MAPIFREEBUFFER(lpRecips);
}

ULRELEASE(lpMsg);

if (lpFolder && lpFolder != lpRootFolder)
ULRELEASE(lpFolder);

    RETURN(hr);

}

//$--HrProcessNewPSTSection----------------------------------------------------
//
// DESCRIPTION: Process the information parsed in a newpst section, creating
//a temporary profile and new pst and logging onto a MAPI session
//for the temporary profile.  Also open the new pst and its
//root folder.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
// Notes:If this function fails, the error should be treated as a
//fatal error.
//---------------------------------------------------------------------------

HRESULT HrProcessNewPSTSection(VOID)        // RETURNS: HRESULT
{
HRESULThr  =   NOERROR;

    DEBUGPRIVATE("HrProcessNewPSTSection()\n");

// Confirm that this is the first NewPST section we have encountered,
// and there was not a Profile section.
if (lpSession)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_MUTUAL_EXCLUSIVENESS_ERROR,
1, SZ_BASE10(iSection),
0);

goto cleanup;
}

// Confirm that it has the required section entries.

if (!iflvENCRYPTION.fValid||
!iflvPATH.fValid||
!iflvPSTNAME.fValid||
!iflvPSTPASSWORD.fValid)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_MISSING_ERROR,
2, TEXT("NewPST"), SZ_BASE10(iSection),
            0);

goto cleanup;
}

fTmpProf = TRUE; // Flag used to indicate we are using a temporary profile.

// Create the requested PST.  This also creates a new profile, and we must
// get a session on the new profile in order to further manipulate the PST.
hr = HrCreatePersonalStore(iflvPATH.achValue,
  bEncryptionNewPST ?
  PSTF_COMPRESSABLE_ENCRYPTION :
  PSTF_NO_ENCRYPTION,
  iflvPSTNAME.achValue,
  iflvPSTPASSWORD.achValue,
  &lpszProfileName,
  &lpszProfilePassword);

if (FAILED(hr))
{
EventLogMsg(
POPPST_NEWPST_ERROR,
2, iflvPSTNAME.achValue, SZ_BASE16(hr),
0);

goto cleanup;
}

// Save profile values (they will be used later by HrProcessProfileSection()).

iflvPROFILENAME.fValid = TRUE;

strcpy(iflvPROFILENAME.achValue, lpszProfileName);

iflvPROFILEPASSWORD.fValid = TRUE;

strcpy(iflvPROFILEPASSWORD.achValue, lpszProfilePassword);

// Log onto a session.

hr = HrProcessProfileSection();

    if ( FAILED(hr) )
    {
goto cleanup;
    }

    // warn the user that a temporary profile is being used
EventLogMsg(
POPPST_TEMPORARY_PROFILE_WARNING,
0,
0);

cleanup:

RETURN(hr);

}

//$--HrProcessProfileSection-------------------------------------------------
//
// DESCRIPTION: Process the information parsed in a profile section, logging
//onto a MAPI profile session and opening the message store, its
//root folder, and the pab if one is available.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_FAIL otherwise.
//
// Notes:If this function fails, the error should be treated as a
//fatal error.  On successful completion, lpSession, lpMdb, and
//lpRootFolder are valid.  These are all opened here and left
//open because they need to be open during message processing.
//
//---------------------------------------------------------------------------

HRESULT HrProcessProfileSection(VOID)       // RETURNS: HRESULT
{
HRESULThr          =   NOERROR;
LPSRowSetlpRows      =NULL;
    ULONG           cbEid       =   0;      // # bytes in entry identifier
    LPENTRYID       lpEid       =   NULL;   // entry identifier
    ULONG    ulObjType   =   0;      // MAPI object type

    DEBUGPRIVATE("HrProcessProfileSection()\n");

// Confirm that this is the first Profile section we have encountered,
// and there was not a NewPST section.
if (lpSession)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_MUTUAL_EXCLUSIVENESS_ERROR,
1, SZ_BASE10(iSection),
0);

goto cleanup;
}

// Confirm that it has the required section entries.

if (!iflvPROFILEPASSWORD.fValid||
!iflvPROFILENAME.fValid)
{
        hr = HR_LOG(E_FAIL);

EventLogMsg(
POPPST_MISSING_ERROR,
2, TEXT("Profile"), SZ_BASE10(iSection),
            0);

goto cleanup;
}

// Log on to the specified profile.

hr = MAPILogonEx(0,
   iflvPROFILENAME.achValue,
   iflvPROFILEPASSWORD.achValue,
   ulMapiLogonFlags,
   &lpSession);

if (FAILED(hr))
{
EventLogMsg(
POPPST_LOGON_ERROR,
2, iflvPROFILENAME.achValue, SZ_BASE16(hr),
            0);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

    ASSERTERROR(!FBadUnknown(lpSession), "Bad lpSession");

// Find the default message store in the message stores table.
    hr = HrMAPIFindDefaultMsgStore(
                lpSession,      // MAPI session pointer
                &cbEid,         // entry identifier byte count
                &lpEid);        // entry identifier

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrMAPIFindDefaultMsgStore"), hr);

        goto cleanup;
    }

// Open the default message store.
hr = lpSession->OpenMsgStore(
                0,
    cbEid,  // entry id byte count
lpEid,  // entry id
NULL,
MDB_WRITE | MAPI_DEFERRED_ERRORS,  // want write access
&lpMdb);    // MDB pointer

if (FAILED(hr))
{
EventLogMsg(
POPPST_OPEN_STORE_ERROR,
2, iflvPROFILENAME.achValue, SZ_BASE16(hr),
            0);

        hr = HR_LOG(E_FAIL);
    goto cleanup;
}

    ASSERT_IUNKNOWN_PTR(lpMdb, "Bad lpMdb");

// Open the root folder in the default message store.  OpenEntry() opens
// the root folder if lpEntryID is NULL.
hr = lpMdb->OpenEntry(0,
  NULL,
  NULL,
  MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
  &ulObjType,
  (LPUNKNOWN *)&lpRootFolder);

if (FAILED(hr))
{
        INTERNAL_ERROR( TEXT("IMsgStore::OpenEntry()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (ulObjType != MAPI_FOLDER)
{
        INTERNAL_ERROR( TEXT("IMsgStore::OpenEntry()"), MAPI_E_UNEXPECTED_TYPE);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

    ASSERTERROR(!FBadUnknown(lpRootFolder), "Bad lpRootFolder");

    // open address book if not using a temporary profile
    if ( fTmpProf == FALSE )
    {
        // open the address book
    hr = lpSession->OpenAddressBook(
            0,              // window handle
            NULL,           // interface pointer
            AB_NO_DIALOG,   // don't want dialogue box
            &lpAdrBook);    // address book pointer

    if (FAILED(hr))
    {
EventLogMsg(
POPPST_AB_ERROR,
2, iflvPROFILENAME.achValue, SZ_BASE16(hr),
0);

            hr = HR_LOG(E_FAIL);
            goto cleanup;
    }

        ASSERTERROR(!FBadUnknown(lpAdrBook), "Bad lpAdrBook");

    }   // end if not using temporary profile

cleanup:

    // free MAPI buffers and release MAPI objects
    MAPIFREEBUFFER(lpEid);

FREEPROWS(lpRows);

RETURN(hr);

}

//$--HrResolveSenderName------------------------------------------------------
//
// DESCRIPTION: Resolve the sender's name, if possible.
//              Also checks number of sender names.
//
// INPUT:       pszName --  sender's display name
//
// OUTPUT:      ppAdrList   --  pointer to address list pointer
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// ---------------------------------------------------------------------------
HRESULT HrResolveSenderName(
        IN LPSTR pszName,   // sender's display name
        OUT LPADRLIST * ppAdrList)  // pointer to address list pointer
{
    HRESULT hr      =   NOERROR;
    ULONG   cNames  =   0;          // # of sender names
LPSTR *lppszTemp=NULL;// array of string pointers

    const ULONG cProps  =   2;      // # of property values
const LPSTRpszSep=";";// separator character

    // property value array
    SPropValue  rgPropVals[cProps]  =   {0};

    DEBUGPRIVATE("HrResolveSenderName()\n");

    hr = CHK_HrResolveSenderName(pszName, ppAdrList);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Check number of sender names.  There should only be one.
hr = HrStrTokAll(
pszName,// sender names
(LPSTR) pszSeparator,// separator character
&cNames,// number of sender names
&lppszTemp);// array of string pointers

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrStrTokAll()"), hr);

        goto cleanup;
    }

    if ( cNames != 1 )
    {
EventLogMsg(
POPPST_FROM_ERROR,
1, SZ_BASE10(iSection),
0);

        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

ASSERT_READ_PTR(lppszTemp, sizeof(LPSTR) * cNames, "Bad lppszTemp");

    // Only resolve sender name if we are not using a temporary
    // profile!
    if ( fTmpProf == FALSE )
    {
        // Set up property value with know values.
        rgPropVals[0].ulPropTag = PR_DISPLAY_NAME;
        rgPropVals[0].Value.lpszA = pszName;

        rgPropVals[1].ulPropTag = PR_RECIPIENT_TYPE;
        rgPropVals[1].Value.l = MAPI_TO;

        // Create an address list for our sender name.
        hr = HrMAPICreateAddressList(
                cProps,         // # of property values
                rgPropVals,     // property value array
                ppAdrList);     // address list pointer

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("HrMAPICreateAddressList()"), hr);

            goto cleanup;
        }

        // Resolve the name
    hr = lpAdrBook->ResolveName(
            0,              // window handle
            0,
            NULL,
            *ppAdrList);    // address list pointer

        // Handle case where sender name can't be resolved.
        if ( FAILED(hr) )
        {
            // some fatal error
EventLogMsg(
POPPST_SENDER_ERROR,
3, pszName, SZ_BASE10(iSection), SZ_BASE16(hr),
0);

            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ASSERTERROR(!IsBadReadPtr(ppAdrList, sizeof(LPADRLIST)),
                    "Bad ppAdrList");
        ASSERTERROR(!IsBadReadPtr(*ppAdrList, sizeof(ADRLIST)),
                    "Bad *ppAdrList");

    }   // end if have a "real" address book

cleanup:

// Free MAPI buffers
MAPIFREEBUFFER(lppszTemp);

    RETURN(hr);

}

//$--HrResolveRecipientNames-------------------------------------------------
//
// DESCRIPTION: Get the properties needed for the recipients using the input
//information and resolves the recipient names, if possible.
//
// OUTPUT       [lppAdrList]-- Ptr to output address list with resolved
//   names.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input parameter,
//                          E_OUTOFMEMORY if memory problems
//                          E_FAIL if call fails.
//
//---------------------------------------------------------------------------

HRESULT HrResolveRecipientNames(            // RETURNS: HRESULT
OUTLPADRLIST *lppAdrList      // address list pointer
)
{
#defineCRECIPLST3// # of recipient lists

    HRESULT         hr  =   NOERROR;
ULONGcRecips =   0;// total # of recipients
ULONGcToRecips=0;// # of TO recipients
ULONGcCCRecips=0;// # of CC recipients
ULONGcBCCRecips=0;// # of BCC recipients
LPADRLISTlpAdrList =NULL;
LPSTR *ppch =   0;
LPSTR *lppszToRecips=NULL;// array of tokenized TO recipients
LPSTR *lppszCCRecips=NULL;// array of tokenized CC recipients
LPSTR *lppszBCCRecips=NULL;// array of tokenized BCC recipients

// Array of recipient INI file lines
INIFILELINEVALUE *apiflv[CRECIPLST]=
{
&iflvTO,
&iflvCC,
&iflvBCC
};

// Array of recipient types
LONGaRecipTypes[CRECIPLST]=
{
MAPI_TO,
MAPI_CC,
MAPI_BCC
};

// Array of tokenized recipient names
LPSTR **rgRecipTokens[CRECIPLST]=
{
&lppszToRecips,
&lppszCCRecips,
&lppszBCCRecips
};

// Array of recipient counts
ULONG *rgcRecips[CRECIPLST]=
{
&cToRecips,
&cCCRecips,
&cBCCRecips
};

    DEBUGPRIVATE("HrResolveRecipientNames()\n");

    // Check input parameters.
    hr = CHK_HrResolveRecipientNames(lppAdrList);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *lppAdrList = NULL; // initialize

if ( iflvTO.fValid )
{
// Count the number of recipients to be resolved by scanning the strings
// for semicolons.
hr = HrStrTokAll(
iflvTO.achValue,// MAPI TO recipients
(LPSTR) pszSeparator,// separator character
&cToRecips,// # of TO recipients
&lppszToRecips);// array of tokenized recipients

    if ( FAILED(hr) )
    {
            INTERNAL_ERROR( TEXT("HrStrTokAll()"), hr);

        goto cleanup;
    }

// increment total # of recipients
cRecips += cToRecips;

ASSERT_READ_PTR(lppszToRecips, sizeof(LPSTR) * cToRecips,
"Bad lppszToRecips");
}

if (iflvCC.fValid)
    {
hr = HrStrTokAll(
iflvCC.achValue,// MAPI CC recipients
(LPSTR) pszSeparator,// separator character
&cCCRecips,// # of CC recipients
&lppszCCRecips);// array of tokenized recipients

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("HrStrTokAll()"), hr);

            goto cleanup;
        }

        cRecips += cCCRecips;

ASSERT_READ_PTR(lppszCCRecips, sizeof(LPSTR) * cCCRecips,
"Bad lppszCCRecips");
    }

if (iflvBCC.fValid)
    {
hr = HrStrTokAll(
iflvBCC.achValue,// MAPI BCC recipients
(LPSTR) pszSeparator,// separator character
&cBCCRecips,// # of BCC recipients
&lppszBCCRecips);// array of tokenized recipients

        if ( FAILED(hr) )
        {
            INTERNAL_ERROR( TEXT("HrStrTokAll()"), hr);

            goto cleanup;
        }

        cRecips += cBCCRecips;

ASSERT_READ_PTR(lppszBCCRecips, sizeof(LPSTR) * cBCCRecips,
"Bad lppszBCCRecips");
    }

// Check that there is at least one recipient.
if ( cRecips <= 0 )
{
EventLogMsg(
POPPST_NEED_RECIPIENT_ERROR,
1, SZ_BASE10(iSection),
0);

hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Set up an ADRLIST to be returned to caller.  THREE levels of allocation
// are required!
hr = MAPIAllocateBuffer(CbNewADRLIST(cRecips), (LPVOID *)&lpAdrList);

if ( FAILED(hr) )
{
        INTERNAL_ERROR( TEXT("MAPIAllocateBuffer()"), hr);

        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

*lppAdrList = lpAdrList;

{
LPSPropValueaPropVals   =   NULL;
        ULONG           cPropVals   =   2;
ULONGi           =   0;
ULONGj           =   0;
LPADRENTRYlpAdrEntry  =   NULL;
LPSTRlpszName    =   NULL;

lpAdrList->cEntries = cRecips;

// Initialize the ADRENTRY's prior to actually allocating the
// SPropValue arrays.  This makes cleanup possible if there is an error.

for (i = 0; i < cRecips; i++)
{
lpAdrList->aEntries[i].ulReserved1 =0;
lpAdrList->aEntries[i].cValues =cPropVals;
lpAdrList->aEntries[i].rgPropVals =NULL;
}

// Now allocate and initialize the SPropValue arrays.
for (i = 0; i < cRecips; i++)
{
hr = MAPIAllocateBuffer(cPropVals * sizeof(SPropValue),
  (LPVOID *)&(aPropVals));
if ( FAILED(hr) )
{
                INTERNAL_ERROR( TEXT("MAPIAllocateBuffer()"), hr);

                hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

lpAdrList->aEntries[i].rgPropVals = aPropVals;

aPropVals[0].ulPropTag =PR_RECIPIENT_TYPE;
aPropVals[0].Value.l =0;

aPropVals[1].ulPropTag =PR_DISPLAY_NAME;
aPropVals[1].Value.lpszA =NULL;
}

// Now fill in the property values.  Note that we can count on the
// recipient lists to be non-null strings, because we checked for
// this during parsing.

{
lpAdrEntry = lpAdrList->aEntries;

for (i = 0; i < CRECIPLST; i++)
{
if (!apiflv[i]->fValid)
continue;

// Retrieve current recipient list of type X.
ppch =*(rgRecipTokens[i]);

ASSERTERROR(ppch != NULL, "Bad ppch");

for ( j = 0; j < *(rgcRecips[i]); j++ )
{
// Retrieve current recipient
lpszName = ppch[j];

ASSERT_STRINGA_PTR(lpszName, "Bad lpszName");

lpAdrEntry->rgPropVals[0].Value.l = aRecipTypes[i];

hr = MAPIAllocateMore(
cbStrLenA(lpszName),
lpAdrEntry->rgPropVals,
(LPVOID *) &lpAdrEntry->
rgPropVals[1].Value.lpszA);

if ( FAILED(hr) )
{
                        INTERNAL_ERROR( TEXT("MAPIAllocateMore()"), hr);

                        hr = HR_LOG(E_OUTOFMEMORY);
goto cleanup;
}

lstrcpyA(lpAdrEntry->rgPropVals[1].Value.lpszA, lpszName);

lpAdrEntry++;

}
}
}

        // if not using a temporary profile, try to resolve the
        // recipient names
        if ( fTmpProf == FALSE )
        {
    // If we have an address book available, use it to resolve any names
    // that you can.  If we don't, then whatever got entered as the user
    // name remains as the display name.

    // CAVEAT - It seems that MAPI has some sort of bug whereby the first
    //attempt to resolve a given name fails.  A subsequent
    //identical request will succeed, so the first of two
    //messages referencing a recipient won't be resolved, but
    //the second will.
        hr = lpAdrBook->ResolveName(0, 0, NULL, lpAdrList);

            if ( (hr == MAPI_E_NOT_FOUND) ||
                 (hr == MAPI_E_AMBIGUOUS_RECIP) )
            {
                // non-fatal errors.  Just can't resolve
                // this recipient name.
EventLogMsg(
POPPST_RESOLVE_WARNING,
2, SZ_BASE10(iSection), SZ_BASE16(hr),
                    0);

                hr = HR_LOG(NOERROR);

                // continue on.
            }

    if ( FAILED(hr) )
    {
                // Some un-recoverable error.
EventLogMsg(
POPPST_RESOLVE_ERROR,
2, SZ_BASE10(iSection), SZ_BASE16(hr),
0);

                hr = HR_LOG(E_FAIL);

    goto cleanup;
    }

    // Now look for problems reported in the returned ADRLIST.
    for (i = 0; i < lpAdrList->cEntries; i++)
    {
                HRESULT         hrT =   NOERROR;

    lpAdrEntry = &lpAdrList->aEntries[i];

    for (j = 0; j < lpAdrEntry->cValues; j++)
    {
    LPSPropValuepPropVal;

    pPropVal = &lpAdrEntry->rgPropVals[j];

    if (PROP_TYPE(pPropVal->ulPropTag == PT_ERROR))
{
    hrT = pPropVal->Value.err;

break;
}
                }   // end for this recipient

                if ( (hrT == MAPI_E_NOT_FOUND) ||
                     (hrT == MAPI_E_AMBIGUOUS_RECIP) )
                {
                    // not a fatal error.  Just can't
                    // resolve a particular recipient.
EventLogMsg(
POPPST_RESOLVE_WARNING,
2, SZ_BASE10(iSection), SZ_BASE16(hrT),
                        0);

                    hrT = HR_LOG(NOERROR);

                    // continue on.
                }

                // Print out error for this recipient, if any
    if ( FAILED(hrT) )
    {
                    // Some non-recoverable error.
EventLogMsg(
POPPST_RESOLVE_ERROR,
2, SZ_BASE10(iSection), SZ_BASE16(hrT),
                        0);

                    hr = HR_LOG(hrT);
                    goto cleanup;
    }

                // continue with next recipient

    }   // end for
        }   // end if not using temporary PST
}

cleanup:

// Free MAPI buffers
MAPIFREEBUFFER(lppszToRecips);
MAPIFREEBUFFER(lppszCCRecips);
MAPIFREEBUFFER(lppszBCCRecips);

// Actual cleanup of *lppAdrList and its substructures must be done by
// caller!

    RETURN(hr);

}

//$--HrSetMessageProperties--------------------------------------------------
//
// DESCRIPTION: Set the sender, subject, date/time, and priority properties
//for the input message.
//
// INPUT/OUTPUT:lpMsg -- Message for which properties are to be set.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_OUTOFMEMORY if memory problems.
//                          E_FAIL if call fails
//
//---------------------------------------------------------------------------

HRESULT HrSetMessageProperties(     // RETURNS: HRESULT
IN OUT LPMESSAGElpMsg       // message pointer
)
{
HRESULThr  =           NOERROR;
LPSPropProblemArraylpProblems =NULL;
    LPADRLIST           lpAdrList =     NULL;   // used to resolve sender's address
    ULONG               iProp   =       0;      // property value index
    LPSPropValue        lpEntry =       NULL;   // address list entry pointer
    ULONG               nValues =       NULL;   // # of property values

    // String buffers for resolved sender name values.
    CHAR                szFromAddr[cbMaxIniLine]  =   "";
    CHAR                szFromAdrTyp[cbMaxIniLine]=   "";

    // property value indices
    const UINT          iMsgClass   =   0;
    const UINT          iPriority   =   1;
    const UINT          iImportance =   2;
    const UINT          iftDeliver  =   3;
    const UINT          iftSubmit   =   4;
    const UINT          iSubject    =   5;
    const UINT          iFromName   =   6;
    const UINT          iRepFromName=   7;
    const UINT          iDelete     =   8;

    const ULONG         cMinValues  =   9;

    // properties which are only set if using a real profile
    const UINT          iFromAddr   =   9;
    const UINT          iFromAdrTyp =   10;
    const UINT          iFromEID    =   11;
    const UINT          iFromKey    =   12;
    const UINT          iRepFromAddr=   13;
    const UINT          iRepFromAdrTyp= 14;
    const UINT          iRepFromEID =   15;
    const UINT          iRepFromKey =   16;

    // maximum number of property values
const ULONG    cMaxValues =17;

    // array of property values
    SPropValue          aPropVals[cMaxValues]  =   {0};

    const UINT          cbEidMax            =   256;    // max EID size

    // entry identifier buffer
    SizedENTRYID(cbEidMax, eidFrom)         =   {0};

    DEBUGPRIVATE("HrSetMessageProperties()\n");

    // Check input parameters
    hr = CHK_HrSetMessageProperties(lpMsg);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // initiliaze # of property values
    nValues = cMinValues;

aPropVals[iFromName].ulPropTag = PR_SENDER_NAME;
aPropVals[iFromName].Value.lpszA = iflvFROM.achValue;

aPropVals[iRepFromName].ulPropTag =PR_SENT_REPRESENTING_NAME;
aPropVals[iRepFromName].Value.lpszA =iflvFROM.achValue;

aPropVals[iSubject].ulPropTag =PR_SUBJECT;
aPropVals[iSubject].Value.lpszA = iflvSUBJECT.achValue;

    // PR_DELIVER_TIME is not transmittable.
    aPropVals[iftDeliver].ulPropTag = PR_DELIVER_TIME;
    aPropVals[iftDeliver].Value.ft = ftCurMsg;

    // PR_CLIENT_SUBMIT_TIME is transmittable
aPropVals[iftSubmit].ulPropTag = PR_CLIENT_SUBMIT_TIME;
aPropVals[iftSubmit].Value.ft =ftCurMsg;

    aPropVals[iMsgClass].ulPropTag = PR_MESSAGE_CLASS;
    aPropVals[iMsgClass].Value.lpszA = "IPM.Note";

    aPropVals[iPriority].ulPropTag = PR_PRIORITY;
    aPropVals[iPriority].Value.l = lPriorityCurMsg;

    aPropVals[iImportance].ulPropTag = PR_IMPORTANCE;
    aPropVals[iImportance].Value.l = lImportanceCurMsg;

    aPropVals[iDelete].ulPropTag = PR_DELETE_AFTER_SUBMIT;
    aPropVals[iDelete].Value.b = TRUE;

    // Resolve the sender's address if we are using a real
    // profile
    if ( fTmpProf == FALSE )
    {
        hr = HrResolveSenderName(
                iflvFROM.achValue,  // sender's display name
                &lpAdrList);        // address list pointer

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        // Set the EMAIL_ADDRESS, ADDR_TYPE and ENTRYID properties
        // based on what HrResolveSenderName returned.
        lpEntry = lpAdrList->aEntries[0].rgPropVals;   // intialize entry pointer
        for ( iProp = 0; iProp < lpAdrList->aEntries[0].cValues; iProp++ )
        {
            switch ( lpEntry[iProp].ulPropTag )
            {
                case PR_DISPLAY_NAME:
                    // update sender's name with resolved name
                    lstrcpyn(iflvFROM.achValue,
                    lpEntry[iProp].Value.lpszA, cbMaxIniLine);

break;

                case PR_ADDRTYPE:
                    // fill in address type
                    lstrcpyn(
                        szFromAdrTyp,
                        lpEntry[iProp].Value.lpszA,
                        cbMaxIniLine);

                    aPropVals[iFromAdrTyp].ulPropTag = PR_SENDER_ADDRTYPE;
                    aPropVals[iFromAdrTyp].Value.lpszA =
                        szFromAdrTyp;

                    aPropVals[iRepFromAdrTyp].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
                    aPropVals[iRepFromAdrTyp].Value.lpszA =
                        szFromAdrTyp;

                    break;

                case PR_EMAIL_ADDRESS:
                    // Fill in email address
                    lstrcpyn(
                        szFromAddr,
                        lpEntry[iProp].Value.lpszA,
                        cbMaxIniLine);

                    aPropVals[iFromAddr].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
                    aPropVals[iFromAddr].Value.lpszA =
                        szFromAddr;

                    aPropVals[iRepFromAddr].ulPropTag =
                        PR_SENT_REPRESENTING_EMAIL_ADDRESS;
                    aPropVals[iRepFromAddr].Value.lpszA =
                        szFromAddr;

                    break;

                case PR_ENTRYID:
                    // fill in entry identifier
                    CopyMemory(
                        &eidFrom.ab,
                        lpEntry[iProp].Value.bin.lpb,
                        lpEntry[iProp].Value.bin.cb);

                    aPropVals[iFromEID].ulPropTag = PR_SENDER_ENTRYID;
                    aPropVals[iFromEID].Value.bin.cb =
                        lpEntry[iProp].Value.bin.cb;
                    aPropVals[iFromEID].Value.bin.lpb =
                        (BYTE *) &eidFrom.ab;

                    aPropVals[iRepFromEID].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
                    aPropVals[iRepFromEID].Value.bin.cb =
                        lpEntry[iProp].Value.bin.cb;
                    aPropVals[iRepFromEID].Value.bin.lpb =
                        (BYTE *) &eidFrom.ab;

                    break;

                case PR_SEARCH_KEY:
                    // fill in sender's seach key
                    aPropVals[iFromKey].ulPropTag = PR_SENDER_SEARCH_KEY;
                    aPropVals[iFromKey].Value.bin.cb =
                        lpEntry[iProp].Value.bin.cb;
                    aPropVals[iFromKey].Value.bin.lpb =
                        lpEntry[iProp].Value.bin.lpb;

                    aPropVals[iRepFromKey].ulPropTag =
                        PR_SENT_REPRESENTING_SEARCH_KEY;
                    aPropVals[iRepFromKey].Value.bin.cb =
                        lpEntry[iProp].Value.bin.cb;
                    aPropVals[iRepFromKey].Value.bin.lpb =
                        lpEntry[iProp].Value.bin.lpb;

                    break;

                default:
                    // not concerned with this property
                    break;

            }   // end switch
        }   // end for

        // Re-set number of property values
        nValues = cMaxValues;

    }   // end if has real profile

    ASSERTERROR((nValues != 0), "Bad nValues");

hr = lpMsg->SetProps(nValues, aPropVals, &lpProblems);

if (FAILED(hr))
{
        INTERNAL_ERROR( TEXT("IMAPIProp::SetProps()"), hr);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (lpProblems != NULL)
{
ULONGi;

for (i = 0; i < lpProblems->cProblem; i++)
            INTERNAL_ERROR( TEXT("IMAPIProp::SetProps()"), lpProblems->aProblem[1].scode);
MAPIFREEBUFFER(lpProblems);

        hr = HR_LOG(E_FAIL);
goto cleanup;
}

cleanup:

    // Free MAPI buffers
    FREEPADRLIST(lpAdrList);

RETURN(hr);

}

//$--HrWriteMessageText------------------------------------------------------
//
// DESCRIPTION:    Write the message text (PR_BODY & PR_RTF_COMPRESSED)
//                 to the message, if any
//
// INPUT:
//
//[lpMsg]-- Message for which message text is to be written.
//[lpszFilePath]-- Path of file containing message text (may be null)
//
// RETURNS:     NOERROR if successful,
//              E_INVALIDARGS if bad input,
//              E_FAIL if call fails.
//
// Notes:We use stream methods to do this, since the SetProps() has a
//4KB limit for the size of a property you can set.
//
//---------------------------------------------------------------------------

HRESULT HrWriteMessageText(             // RETURNS: HRESULT
INLPMESSAGElpMsg,          // message pointer
INLPSTRlpszFilePath    // message text path, may be null
)
{
    HRESULT         hr          =   NOERROR;
LPSTREAM    lpStrm      =NULL;
    ULONG           cbText      =   0;      // # bytes in message text
    ULONG           cbBody      =   0;      // # bytes in PR_BODY
    STATSTG         sStatStg    =   {0};    // stream status structure

    DEBUGPRIVATE("HrWriteMessageText()\n");

    // Check input parameters
    hr = CHK_HrWriteMessageText(lpMsg, lpszFilePath);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // If no message text, then we are done.
    if ( *lpszFilePath == 0 )
    {
        goto cleanup;
    }

    // Create the PR_BODY property (needed by the PST, which is not an
    // rich-text aware store) from the message body file data.
    hr = HrMAPISetPropFromFile(
        lpMsg,              // pointer to message object
        PR_BODY,            // property tag
        lpszFilePath,       // pointer to source file name
        &cbBody);           // # bytes in message body

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrMAPISetPropFromFile()"), hr);

        goto cleanup;
    }

    // Build the RTF compressed data for the message (needed
    // for RTF-aware stores, such as the Exchange MDB).
    // First, open a virtual (bufferred) stream on the input text file.
    hr = HrOpenVirtualStreamOnFile(
        MAPIAllocateBuffer,     // allocation routine
        MAPIFreeBuffer,         // deallocation routine
        STGM_READ | STGM_SHARE_EXCLUSIVE,// interface flags
        lpszFilePath,           // file name
        NULL,                   // file prefix
        &lpStrm);               // stream pointer

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrOpenVirtualStreamOnFile()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    ASSERT_IUNKNOWN_PTR(lpStrm, "Bad lpStrm");

    // Determine size of stream (not necessarily size of PR_BODY!)
    hr = lpStrm->Stat(
        &sStatStg,          // status structure pointer
        0);                 // flags

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("VSOF::Stat()"), hr);

        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cbText = sStatStg.cbSize.LowPart;

    // See if body if too big for us to handle
    if ( sStatStg.cbSize.HighPart > 0 )
    {
EventLogMsg(
POPPST_BODY_ERROR,
1, SZ_BASE10(iSection),
            0);

        hr = HR_LOG(E_NOTIMPL);
        goto cleanup;
    }

    // Create RTF compressed property from the ASCII text
    hr = HrTextToRTFCompressed(
            cbText, // # of bytes of text
            lpStrm, // text stream pointer
0,// # of attachment w/ rendering positions
NULL,// array of attachment rendering positions
            lpMsg,  // message pointer
0);// use current code page

    if ( FAILED(hr) )
    {
        INTERNAL_ERROR( TEXT("HrTextToRTFCompressed()"), hr);

        goto cleanup;
    }

    // we are done.

cleanup:

ULRELEASE(lpStrm);

RETURN(hr);
}


POPSTRS.H

//--popstrs.h------------------------------------------------------------------
//
// Resource string identifiers for POPPST application.
//
// Copyright (C) Microsoft Corporation, 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

#ifndef _POPSTRS_H
#define _POPSTRS_H

#define IDS_PROCESSING100
#define IDS_USAGE101
#define IDS_HELP102
#define IDS_SUCCESS103
#defineIDS_1ERROR104
#define IDS_1WARNING105
#define IDS_ERRORS106
#define IDS_WARNINGS107
#define IDS_EVENT_LOG108
#define IDS_NEWLINE109
#define IDS_USAGEFILE110
#define IDS_USAGEHELP111
#define IDS_HELPDOC112
#define IDS_PARAM_NUMBER113
#define IDS_BAD_ARGV114

#endif


FLDPICK.CPP

//=========================================================================
// FldPick.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//    Implements the folder picker dialog.

//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"
#include "PostData.h"
#include "FldPick.h"

#include <initguid.h>
#define USES_IID_IMAPIFolder
#define USES_IID_IMsgStore
#include <mapiguid.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

SizedSPropTagArray(3, sptaFolderBrowse) = { 3, { PR_OBJECT_TYPE,
                                                    PR_ENTRYID,
                                                    PR_DISPLAY_NAME } };

/////////////////////////////////////////////////////////////////////////////
// CFolderPicker dialog

CFolderPicker::CFolderPicker(CWnd* pParent /*=NULL*/)
: CDialog(CFolderPicker::IDD, pParent)
{
//{{AFX_DATA_INIT(CFolderPicker)
//}}AFX_DATA_INIT
}

void CFolderPicker::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CFolderPicker)
DDX_Control(pDX, IDOK, m_ok);
DDX_Control(pDX, IDCANCEL, m_cancel);
DDX_Control(pDX, IDC_TREEVIEW, m_TreeCtrl);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CFolderPicker, CDialog)
//{{AFX_MSG_MAP(CFolderPicker)
ON_NOTIFY(TVN_ITEMEXPANDING, IDC_TREEVIEW, OnItemexpandingTreeview)
ON_NOTIFY(TVN_GETDISPINFO, IDC_TREEVIEW, OnGetdispinfoTreeview)
ON_NOTIFY(TVN_SELCHANGED, IDC_TREEVIEW, OnSelchangedTreeview)
ON_NOTIFY(TVN_DELETEITEM, IDC_TREEVIEW, OnDeleteitemTreeview)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFolderPicker message handlers

void CFolderPicker::OnOK()
{
// Add extra validation here
   GetSelectedFolder(&PostData.m_lpTempFld);
   CDialog::OnOK();
}

void CFolderPicker::OnCancel()
{
// Add extra cleanup here
CDialog::OnCancel();
}

BOOL CFolderPicker::OnInitDialog()
{
CDialog::OnInitDialog();

// Add extra initialization here
m_TreeCtrl.SetImageList(&PostData.m_obImageListExchange, TVSIL_NORMAL);

AddTreeViewItems();

return TRUE;  // return TRUE unless you set the focus to a control
              // EXCEPTION: OCX Property Pages should return FALSE
}

BOOL CFolderPicker::AddTreeViewItems()
{
HTREEITEM hTRoot = NULL;
SMailID *pMailID = NULL;
SCODEsc       = S_OK;

sc = CreateMailID(MAPI_SESSION, 0, NULL, "Microsoft Exchange\0", &pMailID);
if (sc == S_OK)
{

// First add the root item
hTRoot = AddOneItem ((HTREEITEM)TVI_ROOT, pMailID->lpszDisplayName,
(HTREEITEM)NULL, iimlExchange, (DWORD)pMailID);

m_TreeCtrl.Expand(hTRoot, TVE_EXPAND);
m_TreeCtrl.Select(hTRoot, TVGN_CARET);
}

return TRUE;
}

// This function fills out the TV_ITEM and TV_INSERTSTRUCT structures
// and adds the item to the tree view control.

HTREEITEM CFolderPicker::AddOneItem (HTREEITEM hParent, LPSTR lpszText,
   HTREEITEM hInsAfter, int iImage, DWORD lparam)
{

TV_INSERTSTRUCT   tvis;

tvis.hParent             = hParent;
tvis.hInsertAfter        = hInsAfter;
tvis.item.mask           = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_PARAM | TVIF_STATE;
tvis.item.pszText        = LPSTR_TEXTCALLBACK;
tvis.item.cchTextMax     = 0;
tvis.item.iImage         = I_IMAGECALLBACK;
tvis.item.iSelectedImage = I_IMAGECALLBACK;
tvis.item.cChildren      = I_CHILDRENCALLBACK;
tvis.item.lParam         = (LPARAM)lparam;
tvis.item.state          = 0;
tvis.item.stateMask      = 0;

   return m_TreeCtrl.InsertItem(&tvis);
}

//-------------------------------------------------------------------------
// Purpose:
//    Returns a mapi folder on the selected item in the tree view.
//
STDMETHODIMP CFolderPicker::GetSelectedFolder
   (
   LPMAPIFOLDER*  ppfld
   )
{
   HRESULT  hr= NOERROR;
   SMailID* pMailID = NULL;

   // Get the current tree view selection's mail id
   pMailID = (SMailID *)m_TreeCtrl.GetItemData(m_TreeCtrl.GetSelectedItem());
   if( pMailID)
   {
hr = OpenFolder(pMailID, ppfld);
if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("OpenFolder() failed for ");
afxDump << TEXT(pMailID->lpszDisplayName);
#endif
}
   }

   return hr;
}

//-------------------------------------------------------------------------
// Purpose:
//    Add rows from a mapi table to the tree view.
//
STDMETHODIMP CFolderPicker::AddRows
   (
   HTREEITEM   hitemParent,
   LPMAPITABLE pmt
   )
{
   HRESULT     hr  = NOERROR;
   BOOL        fDone          = FALSE;
   HTREEITEM   hitem          = TVI_FIRST;
   LPSRowSet   prws           = NULL;
   ULONG       cRowsTotal     = 0;
   CONST UINT  cRowsBatch     = 256;
   CONST UINT  cWanted        = 3;
   UINT        cFound         = 0;
   ULONG       ulType         = 0;
   ULONG       cbeid          = 0;
   LPENTRYID   peid           = NULL;
   LPTSTR      pszDisplayName = NULL;
   SMailID*    pMailID        = NULL;
   SMailID*    pParentMailID  = NULL;

   // Set the columns to those needed for folder browsing
   if (SUCCEEDED(hr = pmt->SetColumns((LPSPropTagArray)&sptaFolderBrowse, 0)))
   {
      // Get all of the table's rows
      while (!fDone &&
             SUCCEEDED(hr = pmt->QueryRows(cRowsBatch, 0, &prws)) &&
             prws)
      {
         if (prws->cRows > 0)
         {
            cRowsTotal += prws->cRows;
            // Add them to the tree
            for (ULONG iRow = 0; iRow < prws->cRows; iRow++)
            {
               cFound = 0;
               // Get the appropriate values from the columns
               for (ULONG iCol = 0; iCol < prws->aRow[iRow].cValues; iCol++)
               {
                  if (PR_OBJECT_TYPE == prws->aRow[iRow].lpProps[iCol].ulPropTag)
                  {
                     ulType = (ULONG)prws->aRow[iRow].lpProps[iCol].Value.l;
                     cFound++;
                  }
                  else if (PR_ENTRYID == prws->aRow[iRow].lpProps[iCol].ulPropTag)
                  {
                     cbeid = prws->aRow[iRow].lpProps[iCol].Value.bin.cb;
                     peid  = (LPENTRYID)prws->aRow[iRow].lpProps[iCol].Value.bin.lpb;
                     cFound++;
                  }
                  else if (PR_DISPLAY_NAME == prws->aRow[iRow].lpProps[iCol].ulPropTag)
                  {
                     pszDisplayName = prws->aRow[iRow].lpProps[iCol].Value.LPSZ;
                     cFound++;
                  }
               }

               // if we found all of them...
               if (cFound == cWanted)
               {
                  // Create a mail id and add it to the tree
                  if (SUCCEEDED(CreateMailID(ulType, cbeid, peid, pszDisplayName, &pMailID)))
                  {
                     hitem = AddOneItem(hitemParent, pszDisplayName, hitem, iimlExchange, (DWORD)pMailID);
ASSERT(hitem);

                  }
               } // if (cFound == cWanted)

            } // for (iRow
         }
         else
         {
            fDone = TRUE;
         }

         FreeProws(prws);
      } // while

         // Set the children count on the parent item now that we know it
         //m_TreeCtrl.SetItemChildCount(hitemParent, cRowsTotal);
TV_ITEM  tvi;
tvi.mask      = TVIF_HANDLE | TVIF_CHILDREN;
tvi.hItem     = hitemParent;
tvi.cChildren = cRowsTotal;
m_TreeCtrl.SetItem(&tvi);
   }

   return hr;
}

//-------------------------------------------------------------------------
// Purpose:
//    Fill the tree view from the "root" of exchange.
//
STDMETHODIMP CFolderPicker::TvmtFromRoot
   (
   HTREEITEM   hitemParent
   )
{
   HRESULT     hr  = NOERROR;
   LPMAPITABLE pmt = NULL;

   if (SUCCEEDED(hr = PostData.m_lpSession->GetMsgStoresTable(0, &pmt)))
   {
      hr = AddRows(hitemParent, pmt);
      pmt->Release();
   }

   return hr;
}

//-------------------------------------------------------------------------
// Purpose:
//    Fill the tree view from a folder.
//
STDMETHODIMP CFolderPicker::TvmtFromFolder
   (
   HTREEITEM      hitemParent,
   LPMAPIFOLDER   pfld
   )
{
   HRESULT        hr   = NOERROR;
   LPMAPITABLE    pmt  = NULL;

   // Get the folder hierarchy table
   if (SUCCEEDED(hr = pfld->GetHierarchyTable(MAPI_DEFERRED_ERRORS, &pmt)))
   {
      hr = AddRows(hitemParent, pmt);
      pmt->Release();
   }

   return hr;
}

//-------------------------------------------------------------------------
// Purpose:
//    Expand a tree view item.
//
STDMETHODIMP CFolderPicker::FirstTimeExpand
   (
   HTREEITEM  hitem,
   SMailID*   pMailID
   )
{
   HRESULT  hr= NOERROR;

   if (MAPI_SESSION == pMailID->ulType)
   {
      hr = TvmtFromRoot(hitem);
   }
   else
   {
      LPMAPIFOLDER   pfld;

      if (SUCCEEDED(hr = OpenFolder(pMailID, &pfld)))
      {
         hr = TvmtFromFolder(hitem, pfld);
         pfld->Release();
      }
    }

   return hr;
}
//-------------------------------------------------------------------------
// Purpose:
//    Creates a *simple* pidl.
//
// static
STDMETHODIMP CFolderPicker::CreateMailID
   (
   ULONG          ulType,           // IN:  one of MAPI_SESSION, MAPI_STORE, or MAPI_FOLDER
   ULONG          cbEntryID,        // IN:  count of bytes in entry ID
   LPENTRYID      lpEntryID,        // IN:  entry ID
   LPCTSTR        lpszDisplayName,  // IN:  display name of item
   SMailID**      ppob              // OUT: pointer to resulting object
   )
{
  SCODE sc= S_OK;

// Allocate space for the
sc = MAPIAllocateBuffer( sizeof(SMailID), (LPVOID *)ppob);
    if (FAILED(sc))
{
#ifdef _DEBUG
afxDump << TEXT("CreateMailID(): MAPIAllocateBuffer failed");
#endif
goto CleanUp;   // Allocate space for the
}

(*ppob)->cbEntryID = cbEntryID;
(*ppob)->ulType   = ulType;
(*ppob)->lpEntryID  = NULL;
(*ppob)->lpszDisplayName   = NULL;

sc = MAPIAllocateMore(cbEntryID, (LPVOID)*ppob, (LPVOID*)&((*ppob)->lpEntryID));
if (FAILED(sc))
{
#ifdef _DEBUG
afxDump << TEXT("CreateMailID(): MAPIAllocateMore failed");
#endif
goto CleanUp;   // Allocate space for the
}

CopyMemory((*ppob)->lpEntryID, lpEntryID, cbEntryID);

sc = MAPIAllocateMore(lstrlen(lpszDisplayName)+1, (LPVOID)*ppob, (LPVOID*)&((*ppob)->lpszDisplayName));
if (FAILED(sc))
{
#ifdef _DEBUG
afxDump << TEXT("CreateMailID(): MAPIAllocateMore failed");
#endif
goto CleanUp;   // Allocate space for the
}

lstrcpy((*ppob)->lpszDisplayName, lpszDisplayName);

CleanUp:
return(sc);

}

STDMETHODIMP CFolderPicker::OpenFolder(SMailID *pMailID, LPMAPIFOLDER *ppfld)
{

   HRESULT        hr= NOERROR;
   LPSPropValue   pval= NULL;
   ULONG          ulObjType = 0;

   if (MAPI_STORE == pMailID->ulType)
   {
      // Open the store. (only if not already open)
  LPMDB lpMDB = NULL;
      if (SUCCEEDED(hr = PostData.OpenMsgStore(pMailID->cbEntryID, pMailID->lpEntryID, &lpMDB)))
      {
         // Open the IPM subtree as a folder.
         if (SUCCEEDED(hr = HrGetOneProp(lpMDB, PR_IPM_SUBTREE_ENTRYID, &pval)))
         {
            hr = PostData.m_lpSession->OpenEntry(pval->Value.bin.cb,
                                          (LPENTRYID)pval->Value.bin.lpb,
                                          &IID_IMAPIFolder,
                                          MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                                          &ulObjType,
                                          (LPUNKNOWN*)ppfld);

            MAPIFreeBuffer(pval);
         }

       }
   }
   else if (MAPI_FOLDER == pMailID->ulType)
   {
             // Open the folder.
            hr = PostData.m_lpSession->OpenEntry(pMailID->cbEntryID,
                                          pMailID->lpEntryID,
                                          &IID_IMAPIFolder,
                                          MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                                          &ulObjType,
                                          (LPUNKNOWN*)ppfld);
   }

   return hr;
}

void CFolderPicker::OnItemexpandingTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR;
// Add your control notification handler code here
// Provide display info

   // An item is expanding,
      SMailID*       pMailID  = (SMailID*)pnmtv->itemNew.lParam;

if ((TVE_COLLAPSE == pnmtv->action) || (TVE_COLLAPSERESET == pnmtv->action))
    {
if (pMailID->lpEntryID == NULL)
        {
            // prevent tree view from collapsing the root
            pnmtv->itemNew.mask  = TVIF_HANDLE | TVIF_STATE;
            pnmtv->itemNew.state = TVIS_EXPANDED;
            pnmtv->itemNew.stateMask = TVIS_EXPANDED;
         }
}
else if ((TVE_EXPAND == pnmtv->action) && !(pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
{
         if (FAILED(FirstTimeExpand(pnmtv->itemNew.hItem, pMailID)))
 {
#ifdef _DEBUG
afxDump << TEXT("FirstTimeExpand() failed for ");
afxDump << TEXT(pMailID->lpszDisplayName);
#endif
 }
}

*pResult = 0;
}

void CFolderPicker::OnGetdispinfoTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pDispInfo = (TV_DISPINFO*)pNMHDR;
// Add your control notification handler code here
// Provide display info

     SMailID*       pMailID = (SMailID*)pDispInfo->item.lParam;

      if (pMailID)
      {
         // Set up the display text
         if (pDispInfo->item.mask & TVIF_TEXT)
         {
            pDispInfo->item.pszText    = (LPTSTR)pMailID->lpszDisplayName;
            pDispInfo->item.cchTextMax = lstrlen(pDispInfo->item.pszText);
         }

         // Set up the images to use
         if (pDispInfo->item.mask & TVIF_IMAGE)
         {
            INT   iImage;
            INT   iSelectedImage;

            iImage = MapTypeToExchangeImageListIndex(pMailID->ulType, &iSelectedImage);

            if ((pDispInfo->item.mask & TVIF_STATE) && (pDispInfo->item.state & TVIS_EXPANDED))
            {
               pDispInfo->item.iImage = iSelectedImage;
            }
            else
            {
               pDispInfo->item.iImage = iImage;
            }
            pDispInfo->item.iSelectedImage = iSelectedImage;
         }

         // Set up the count of children
         if (pDispInfo->item.mask & TVIF_CHILDREN)
         {
            pDispInfo->item.cChildren = I_CHILDRENCALLBACK;
         }
      }

  *pResult = 0;
}

void CFolderPicker::OnSelchangedTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR;
// Add your control notification handler code here
// Enable or disable buttons as appropriate due to the selection

SMailID*       pMailID  = (SMailID*)pnmtv->itemNew.lParam;

    if (MAPI_SESSION != pMailID->ulType)
    {
        m_ok.EnableWindow(TRUE);
    }
    else
    {
        m_ok.EnableWindow(FALSE);
    }

*pResult = 0;
}

//-------------------------------------------------------------------------
// Purpose:
//    Get the image list index corresponding to a MAPI object type.
//
INT CFolderPicker::MapTypeToExchangeImageListIndex
   (
   ULONG ulType,
   INT*  piSelectedImage
   )
{
   INT iImage = iimlFolder;

   if (MAPI_SESSION == ulType)
   {
iImage           = iimlExchange;
*piSelectedImage = iimlExchangeOpen;
   }
   else if (MAPI_STORE == ulType)
   {
iImage           = iimlMdb;
*piSelectedImage = iimlMdbOpen;
   }
   else if (MAPI_FOLDER == ulType)
   {
iImage           = iimlFolder;
*piSelectedImage = iimlFolderOpen;
   }
   else
   {
#ifdef _DEBUG
    afxDump << TEXT("MapTypeToExchangeImageListIndex: Incorrect ulType");
#endif
iImage = iimlFolder;
*piSelectedImage = iimlFolder;
}

   return iImage;
}

void CFolderPicker::OnDeleteitemTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR;
// Add your control notification handler code here
// Delete our user data
    SMailID*       pMailID = (SMailID *)pnmtv->itemOld.lParam;

MAPIFreeBuffer((LPVOID)pMailID);

*pResult = 0;
}


FLDPICK.H

//=========================================================================
// FldPick.H
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    Definition of CFolderPicker class
//=========================================================================

extern void WINAPI CreateAndPostMessage();

/////////////////////////////////////////////////////////////////////////////
// CFolderPicker dialog

class CFolderPicker : public CDialog
{
// Construction
public:
CFolderPicker(CWnd* pParent = NULL);   // standard constructor

// Dialog Data
//{{AFX_DATA(CFolderPicker)
enum { IDD = IDD_FOLDERPICKER };
CButtonm_ok;
CButtonm_cancel;
CTreeCtrlm_TreeCtrl;
//}}AFX_DATA

BOOL AddTreeViewItems();
HTREEITEM AddOneItem (HTREEITEM hParent, LPSTR szText,HTREEITEM hInsAfter, int iImage, DWORD lparam);
STDMETHODIMP TvmtFromRoot(HTREEITEM   hitemParent);
STDMETHODIMP TvmtFromFolder(HTREEITEM      hitemParent, LPMAPIFOLDER   pfld);
STDMETHODIMP FirstTimeExpand(HTREEITEM  hitem, SMailID*   pMailID);
STDMETHODIMP CreateMailID(ULONG ulType, ULONG cbEntryID,LPENTRYID pEntryID,
LPCTSTR lpszDisplayName, SMailID**  ppob );
STDMETHODIMP OpenFolder(SMailID *pMailID, LPMAPIFOLDER *lppFld);

STDMETHODIMP GetSelectedFolder(LPMAPIFOLDER*  ppfld);
STDMETHODIMP AddRows(HTREEITEM   hitemParent, LPMAPITABLE pmt);
INT MapTypeToExchangeImageListIndex(ULONG ulType, INT*  piSelectedImage);

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFolderPicker)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:

// Generated message map functions
//{{AFX_MSG(CFolderPicker)
virtual void OnOK();
virtual void OnCancel();
virtual BOOL OnInitDialog();
afx_msg void OnItemexpandingTreeview(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnGetdispinfoTreeview(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnSelchangedTreeview(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDeleteitemTreeview(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


FREEDOC.CPP

//=========================================================================
// FREEDOC.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    Provides free-floating document routines.
//
//=========================================================================

#include "stdafx.h"
#include <windowsx.h>
#include <winnls.h>
#include <mapinls.h>

#include "PostSmpl.h"
#include "freedoc.h"
#include "PostData.h"
#include "edk.h"

#define cbReadMax         8192
#define cchPathNameMax    320

enum {
   ivalAttAttachMethod,
   ivalAttRenderingPosition,
   ivalAttAttachRendering,
   ivalAttAttachFilename,
   ivalAttAttachLongFilename,
   ivalAttCreationTime,
   ivalAttLastModificationTime,
   cvalAttMax
};

/*
 * CREATEFREEDOC declarations
 */
enum {
   ivalMsgSubject,
   ivalMsgSenderName,
   ivalMsgSentRepresentingName,
   ivalMsgClientSubmitTime,
   ivalMsgMessageDeliveryTime,
   ivalMsgMessageClass,
   ivalMsgMessageFlags,
   ivalMsgSentRepresentingEntryid,
   ivalMsgSenderEntryid,
   ivalMsgSentRepresentingSearchKey,
   ivalMsgSenderSearchKey,
   ivalMsgIcon,
   ivalMsgImportance,
   ivalMsgPriority,
   ivalMsgSensitivity,
   ivalMsgLastModificationTime,
   cvalMsgMax
};

typedef struct
{
   LPTSTR         lpszPathName;
   LPTSTR         lpszFileName;
   LPMESSAGE      pmsg;
   LPATTACH       patt;
   SPropValue     rgvalAtt[cvalAttMax];
   SPropValue     rgvalMsg[cvalMsgMax];

} CREATEFREEDOC;

// Added to PROPSET.CPP
STDAPI PromoteSummaryInfo
   (
   LPMESSAGE   pmsg,
   LPSTR     lpszTitle,
   LPSTR     lpszSubject,
   LPSTR     lpszAuthor,
   LPSTR     lpszKeywords,
   LPSTR     lpszComments
   );

// Commonly used LARGE_INTEGER
static LARGE_INTEGER liZero = { 0 };
// Message classes: note class and IPM root class
CONST CHAR szIPMDocument[] = TEXT("IPM.Document");

static SCODE ScLoadAttachDataFromFile(LPSTORAGE pstg, LPTSTR lpszFileName, LPATTACH patt);
static BOOL FGetFileTimes(LPTSTR lpszPathName, FILETIME * pftCreate, FILETIME * pftModify);
static HRESULT HrInitAttachment(
      LPMESSAGE pmsg,
      LPATTACH * ppatt,
      LPSPropValue rgvalAtt,
      LPTSTR lpszPathName,
      LPTSTR lpszFileName);

static HGLOBAL HmfpGetIconForFile(LPTSTR lpszPathName, LPTSTR lpszFileName);
static SCODE   ScGetRenderBits(HGLOBAL hmfp, ULONG * pcb, LPBYTE * ppb);
static void    OleUIMetafilePictIconFree(HGLOBAL);

/*
 * HrInitAttachment
 *
 * Purpose:
 *    Initialize a message with a file attachment
 *
 * Arguments:
 *    pmsg           Message to add the attachment to
 *    ppatt          Where to put the attachment
 *    rgvalAtt       Properties for the attachment
 *    lpszFileName        Name of the file

 *
 * Returns:
 *    SCODE       the status code;
 */
HRESULT HrInitAttachment(
      LPMESSAGE pmsg,
      LPATTACH * ppatt,
      LPSPropValue rgvalAtt,
      LPTSTR lpszPathName,
      LPTSTR lpszFileName)

{
   HRESULT        hr;
   ULONG       ulAttachNum;
   SYSTEMTIME     st;
   SCODE       sc;
   HGLOBAL        hmfp = NULL;

   rgvalAtt[ivalAttAttachRendering].Value.bin.lpb = NULL;

   // Copy the file as an attachment
   if (FAILED(hr = pmsg->CreateAttach(
         NULL,
         MAPI_DEFERRED_ERRORS,
         &ulAttachNum,
         ppatt)))
   {
      #ifdef _DEBUG
  afxDump << TEXT("HrInitAttachment():CreateAttach() failed");
  #endif

      goto CleanUp;
   }

   GetSystemTime(&st);
   SystemTimeToFileTime(&st, &rgvalAtt[ivalAttCreationTime].Value.ft);

   // Make assumptions about the file time properties
   rgvalAtt[ivalAttCreationTime].ulPropTag = PR_CREATION_TIME;
   rgvalAtt[ivalAttLastModificationTime].ulPropTag = PR_LAST_MODIFICATION_TIME;
   rgvalAtt[ivalAttLastModificationTime].Value.ft =
         rgvalAtt[ivalAttCreationTime].Value.ft;

   // Save various properties
   rgvalAtt[ivalAttAttachMethod].ulPropTag = PR_ATTACH_METHOD;
   rgvalAtt[ivalAttAttachMethod].Value.l = ATTACH_BY_VALUE;
   rgvalAtt[ivalAttRenderingPosition].ulPropTag = PR_RENDERING_POSITION;
   rgvalAtt[ivalAttRenderingPosition].Value.l = -1;
   rgvalAtt[ivalAttAttachFilename].ulPropTag = PR_ATTACH_FILENAME;
   rgvalAtt[ivalAttAttachFilename].Value.LPSZ = lpszFileName;
   rgvalAtt[ivalAttAttachLongFilename].ulPropTag = PR_ATTACH_LONG_FILENAME;
   rgvalAtt[ivalAttAttachLongFilename].Value.LPSZ = lpszFileName;

   // Also need to set icon for the attachment
   // Determine the icon to use with this file
   rgvalAtt[ivalAttAttachRendering].ulPropTag = PR_ATTACH_RENDERING;
   hmfp = HmfpGetIconForFile(lpszPathName, lpszFileName);
   if (!hmfp)
   {
      hr = ResultFromScode(E_OUTOFMEMORY);
      goto CleanUp;
   }
   if (sc = ScGetRenderBits(hmfp,
                     &rgvalAtt[ivalAttAttachRendering].Value.bin.cb,
                     &rgvalAtt[ivalAttAttachRendering].Value.bin.lpb))
   {
      hr = ResultFromScode(sc);
      goto CleanUp;
   }

CleanUp:

   OleUIMetafilePictIconFree(hmfp);

   return hr;
}

/*
 * HrInitCFD
 *
 * Purpose:
 *    Initialize the contents of CFD.
 *
 * Arguments:
 *    pcfd        pointer to a CFD
 *
 * Returns:
 *    HRESULT        Any error that has occured
 */

HRESULT HrInitCFD(LPMAPIFOLDER pfld, CREATEFREEDOC* pcfd)
{
   HRESULT        hr;
   LPSPropValue   rgvalMsg = pcfd->rgvalMsg;
   LPSPropValue   rgvalAtt = pcfd->rgvalAtt;

   // Create a new message for the docfile
   if (FAILED(hr = pfld->CreateMessage(
         NULL,
         MAPI_DEFERRED_ERRORS,
         &pcfd->pmsg)))
   {
      #ifdef _DEBUG
  afxDump << TEXT("HrInitCFD():CreateMessage() failed");
  #endif

      goto Cleanup;
   }

   // Get the initalize attachment settings
   if (FAILED(hr = HrInitAttachment(
         pcfd->pmsg,
         &pcfd->patt,
         rgvalAtt,
         pcfd->lpszPathName,
         pcfd->lpszFileName
         )))
   {
      goto Cleanup;
   }

   // Set up the delivery time
   rgvalMsg[ivalMsgMessageDeliveryTime].Value.ft =
      rgvalAtt[ivalAttCreationTime].Value.ft;

Cleanup:
   return hr;
}

/*
 * ReleaseCFD
 *
 * Purpose:
 *    Clean up the contents of a CFD
 *
 * Arguments:
 *    pcfd        pointer to a CFD
 *
 * Returns:
 *    None.
 */
VOID ReleaseCFD(CREATEFREEDOC* pcfd)
{
   ULRELEASE(pcfd->pmsg);
   ULRELEASE(pcfd->patt);
   MAPIFREEBUFFER(pcfd->rgvalAtt[ivalAttAttachRendering].Value.bin.lpb);

}

/*
 * ScCreateFreeDocFinal
 *
 * Purpose:
 *    Common code that takes care of setting message properties
 *
 * Arguments:
 *    pfld        the folder where to create the FreeDoc
 *    lpszFileName     the filename
 *
 * Returns:
 *    SCODE       the status code
 */
SCODE ScCreateFreeDocFinal
   (
   CREATEFREEDOC* pcfd,
   LPSTR        lpszTitle,
   LPSTR        lpszSubject,
   LPSTR        lpszAuthor,
   LPSTR        lpszKeywords,
   LPSTR        lpszComments
   )
{
   HRESULT        hr;
   SCODE  sc = S_OK;
   LPSPropValue   rgvalMsg= pcfd->rgvalMsg;
   LPSPropValue   rgvalAtt= pcfd->rgvalAtt;
   LPSPropValue   pvalName= NULL;
   LPSPropValue   pvalKey= NULL;
   LPTSTR         lpszProgId= NULL;
   LPTSTR         lpszClass = NULL;
   ULONG  cbEid= 0;
   LPENTRYID      peid= NULL;
   ULONG  ulObjType = 0;
   LPMAPIPROP     pmp= NULL;
   LPMAPISESSION  pses= NULL;
   ULONG  cvalMsg= cvalMsgMax - 1;
   LPBYTE         lpbDest= NULL;
   SHFILEINFO     sfi= {0};

   static CHAR   szScCreateFreeDocFinal1[] = TEXT("%s.%s");

   // Set the properties
   if (sc = ((LPMAPIPROP)pcfd->patt)->SetProps(cvalAttMax, rgvalAtt, NULL))
      goto CleanUp;

   // Default the times
   rgvalMsg[ivalMsgClientSubmitTime].Value.ft =
                        rgvalAtt[ivalAttCreationTime].Value.ft;
   rgvalMsg[ivalMsgLastModificationTime].Value.ft =
                        rgvalAtt[ivalAttLastModificationTime].Value.ft;

   // Find out the app name
   if (lpszProgId)
   {
  sc = MAPIAllocateBuffer((lstrlen(szIPMDocument) + lstrlen(lpszProgId) + 2) * sizeof(CHAR), (LPVOID *)&lpszClass);
  if (FAILED(sc))
      {
         sc = E_OUTOFMEMORY;
         goto CleanUp;
      }
      wsprintf(lpszClass, szScCreateFreeDocFinal1, szIPMDocument, lpszProgId);
      rgvalMsg[ivalMsgMessageClass].Value.LPSZ = lpszClass;
   }
   else
      rgvalMsg[ivalMsgMessageClass].Value.LPSZ = (LPTSTR) szIPMDocument;

   // Save the changes to the attachment
   if (sc = pcfd->patt->SaveChanges(0))
   {
      #ifdef _DEBUG
  afxDump << TEXT("ScCreateFreeDocFinal:SaveChanges() failed");
  #endif

      goto CleanUp;
   }

   //Shared folder doesn't like us keeping the attachment open
   pcfd->patt->Release();
   pcfd->patt = NULL;

   // Find out the user's name
   // AddRefObj(pses = CENT(pses));
   pses = PostData.m_lpSession;
   pses->AddRef();

   if (pses)
   {
      // Determine our identity
      if (FAILED(hr = pses->QueryIdentity(&cbEid, &peid)))
      {
 #ifdef _DEBUG
 afxDump << TEXT("ScCreateFreeDocFinal:QueryIdentity() failed");
 #endif

         goto CleanUp;
      }

      // Get the object referring to our identity
      if (hr = pses->OpenEntry(cbEid, peid, NULL, MAPI_DEFERRED_ERRORS,
                               &ulObjType, (LPUNKNOWN *) &pmp))
      {
         #ifdef _DEBUG
 afxDump << TEXT("ScCreateFreeDocFinal:OpenEntry() failed");
 #endif

         goto CleanUp;
      }

      // Get the display name
      if (sc = HrGetOneProp(pmp, PR_DISPLAY_NAME, &pvalName))
         goto CleanUp;
      if (sc = HrGetOneProp(pmp, PR_SEARCH_KEY, &pvalKey))
         goto CleanUp;
   }

//If subject is not specified then use file's displayname as default subject
if (!lpszSubject)
{
if (SHGetFileInfo(
 pcfd->lpszPathName,
0,
&sfi,
sizeof(SHFILEINFO),
SHGFI_DISPLAYNAME))
{
lpszSubject = sfi.szDisplayName;
}
else
{
lpszSubject = pcfd->lpszFileName;
}
}

   // Set up the propvals
   rgvalMsg[ivalMsgSubject].ulPropTag = PR_SUBJECT;
   rgvalMsg[ivalMsgSubject].Value.LPSZ = lpszSubject;
   rgvalMsg[ivalMsgSenderName].ulPropTag = PR_SENDER_NAME;
   rgvalMsg[ivalMsgSentRepresentingName].ulPropTag = PR_SENT_REPRESENTING_NAME;
   rgvalMsg[ivalMsgSenderName].Value.LPSZ =
   rgvalMsg[ivalMsgSentRepresentingName].Value.LPSZ =
                        pvalName ? pvalName->Value.LPSZ : (LPTSTR) "";
   rgvalMsg[ivalMsgClientSubmitTime].ulPropTag = PR_CLIENT_SUBMIT_TIME;
   rgvalMsg[ivalMsgMessageDeliveryTime].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
   rgvalMsg[ivalMsgMessageClass].ulPropTag = PR_MESSAGE_CLASS;
   rgvalMsg[ivalMsgMessageFlags].ulPropTag = PR_MESSAGE_FLAGS;
   rgvalMsg[ivalMsgMessageFlags].Value.l = 0;
   rgvalMsg[ivalMsgSentRepresentingEntryid].ulPropTag =
                        PR_SENT_REPRESENTING_ENTRYID;
   rgvalMsg[ivalMsgSenderEntryid].ulPropTag = PR_SENDER_ENTRYID;
   rgvalMsg[ivalMsgSentRepresentingEntryid].Value.bin.cb =
   rgvalMsg[ivalMsgSenderEntryid].Value.bin.cb = cbEid;
   rgvalMsg[ivalMsgSentRepresentingEntryid].Value.bin.lpb =
   rgvalMsg[ivalMsgSenderEntryid].Value.bin.lpb = (LPBYTE) peid;
   rgvalMsg[ivalMsgSentRepresentingSearchKey].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
   rgvalMsg[ivalMsgSenderSearchKey].ulPropTag = PR_SENDER_SEARCH_KEY;
   rgvalMsg[ivalMsgSentRepresentingSearchKey].Value.bin.cb =
   rgvalMsg[ivalMsgSenderSearchKey].Value.bin.cb = pvalKey ? pvalKey->Value.bin.cb : 0;
   rgvalMsg[ivalMsgSentRepresentingSearchKey].Value.bin.lpb =
   rgvalMsg[ivalMsgSenderSearchKey].Value.bin.lpb = pvalKey ? pvalKey->Value.bin.lpb : NULL;
   rgvalMsg[ivalMsgIcon].ulPropTag = PR_ICON;
   rgvalMsg[ivalMsgIcon].Value = rgvalAtt[ivalAttAttachRendering].Value;
   rgvalMsg[ivalMsgImportance].ulPropTag = PR_IMPORTANCE;
   rgvalMsg[ivalMsgImportance].Value.l = IMPORTANCE_NORMAL;
   rgvalMsg[ivalMsgPriority].ulPropTag = PR_PRIORITY;
   rgvalMsg[ivalMsgPriority].Value.l = PRIO_NORMAL;
   rgvalMsg[ivalMsgSensitivity].ulPropTag = PR_SENSITIVITY;
   rgvalMsg[ivalMsgSensitivity].Value.l = SENSITIVITY_NONE;
   rgvalMsg[ivalMsgLastModificationTime].ulPropTag = PR_LAST_MODIFICATION_TIME;

   // Set the message properties
   if (sc = ((LPMAPIPROP)pcfd->pmsg)->SetProps(cvalMsg, pcfd->rgvalMsg, NULL))
      goto CleanUp;

   // Set the summary info properties if they have been specified
   if (lpszTitle
         || lpszSubject
         || lpszAuthor
         || lpszKeywords
         || lpszComments)
      {

PromoteSummaryInfo(pcfd->pmsg, lpszTitle, lpszSubject, lpszAuthor, lpszKeywords, lpszComments);

      }

   sc = pcfd->pmsg->SaveChanges(0);

CleanUp:

   MAPIFREEBUFFER(lpbDest);
   MAPIFREEBUFFER(peid);
   MAPIFREEBUFFER(pvalName);
   MAPIFREEBUFFER(pvalKey);
   MAPIFREEBUFFER(lpszProgId);
   MAPIFREEBUFFER(lpszClass);
   ULRELEASE(pmp);
   ULRELEASE(pses);
   return sc;
}

/*
 * ScCreateFreeDoc
 *
 * Purpose:
 *    Creates a FreeDoc out of a file
 *
 * Arguments:
 *    pfld        the folder where to create the FreeDoc
 *    lpszFileName     the filename
 *
 * Returns:
 *    SCODE       the status code
 */
SCODE ScCreateFreeDoc
   (
   LPSTORAGE  pstg,
   LPMAPIFOLDER   pfld,
   LPSTR          lpszPathName,
   LPSTR  lpszFileName,
   LPSTR          lpszTitle,
   LPSTR          lpszSubject,
   LPSTR          lpszAuthor,
   LPSTR          lpszKeywords,
   LPSTR          lpszComments
   )
{
   CREATEFREEDOC  cfd;
   SCODE       sc = S_OK;

   ZeroMemory(&cfd, sizeof(CREATEFREEDOC));

   // Make sure we aren't trying to attach a directory
   if (GetFileAttributes(lpszPathName) & FILE_ATTRIBUTE_DIRECTORY)
   {
     #ifdef _DEBUG
 afxDump << TEXT("ScCreateFreeDoc:Trying to attach a directory");
 #endif
 goto CleanUp;
   }

   cfd.lpszPathName = lpszPathName;
   cfd.lpszFileName = lpszFileName;

   if (sc = GetScode(HrInitCFD(pfld, &cfd)))
      goto CleanUp;

   // Load the file into the attachment
   if (FAILED(sc = ScLoadAttachDataFromFile(pstg, lpszPathName, cfd.patt)))
   {
      goto CleanUp;
   }

   // Get the file time properties
   FGetFileTimes(lpszPathName, &cfd.rgvalAtt[ivalAttCreationTime].Value.ft,
               &cfd.rgvalAtt[ivalAttLastModificationTime].Value.ft);

   if (SUCCEEDED(sc))
   {
      sc = ScCreateFreeDocFinal(&cfd,
                                lpszTitle,
                                lpszSubject,
                                lpszAuthor,
                                lpszKeywords,
                                lpszComments);
   }

CleanUp:
   ReleaseCFD(&cfd);
   return sc;
}

/*
 * ScLoadAttachDataFromFile
 *
 * Purpose:
 *    Reads in a file into the attachment's data stream
 *
 * Arguments:
 *    lpszFileName     the file to be read in
 *    patt        the attachment
 *
 * Returns:
 *    SCODE       S_OK or an error code
 */
SCODE ScLoadAttachDataFromFile(LPSTORAGE pstg, LPTSTR lpszPathName, LPATTACH patt)
{
   SCODE       sc= E_FAIL; // MAIL_E_STREAMINFILE;
   HRESULT     hr= NOERROR;
   LPSTREAM    pstm= NULL;
   UINT        cbRead= 0;
   ULONG       cbWritten= 0;

   // Get a stream interface from the attachment data
   if (hr = patt->OpenProperty(PR_ATTACH_DATA_BIN,
         (LPIID) &IID_IStream, STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
         MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
         (LPUNKNOWN *) &pstm))
   {
      #ifdef _DEBUG
  afxDump << TEXT(" ScLoadAttachDataFromFile:OpenProperty() failed");
  #endif
  goto CleanUp;
   }

   // Reset the destination stream to the start
   if (hr = pstm->Seek(liZero, STREAM_SEEK_SET, NULL))
   {
      #ifdef _DEBUG
  afxDump << TEXT(" ScLoadAttachDataFromFile:pstm->Seek failed");
  #endif
      goto CleanUp;
   }

   cbRead = sizeof(CHAR)*(lstrlen(theApp.m_lpBuffer) + 1);
   if (hr = pstm->Write(theApp.m_lpBuffer, cbRead, &cbWritten))
   {
        #ifdef _DEBUG
afxDump << TEXT(" ScLoadAttachDataFromFile:pstm->Write() failed");
#endif
goto CleanUp;
}

   sc = S_OK;
   goto CleanUp;

CleanUp:
   ULRELEASE(pstm);
   return sc;

}

/*
 * FGetFileTimes
 *
 * Purpose:
 *    Gets a file's create and modify times
 *
 * Arguments:
 *    szPathName        The file name
 *    pftCreate         The creation time
 *    pftModify         The modify time
 *
 * Returns:
 *    BOOL           TRUE on success
 */
BOOL FGetFileTimes(LPTSTR lpszPathName, FILETIME * pftCreate,
               FILETIME * pftModify)
{
   BOOL     fSuccess = FALSE;
   FILETIME ftBlank = { 0 };

   HANDLE   hFile;

   if (pftCreate)
      *pftCreate = ftBlank;
   if (pftModify)
      *pftModify = ftBlank;
   hFile = CreateFile(lpszPathName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                  OPEN_EXISTING, 0, NULL);
   if (hFile)
      fSuccess = GetFileTime(hFile, pftCreate, NULL, pftModify);

   CloseHandle(hFile);

   // Try putting in last modify if can't get create time
   if (pftCreate &&
      pftModify &&
      !pftCreate->dwLowDateTime &&
      !pftCreate->dwHighDateTime)
      *pftCreate = *pftModify;
   return fSuccess;
}

/*
 * HmfpGetIconForFile
 *
 * Purpose:
 *    A centralized place to create a metafile picture of an icon and label
 *
 * Arguments:
 *    lpszPathName     The path to the file we need an icon for
 *    lpszFileName     The filename which becomes the label
 *
 * Returns:
 *    HGLOBAL        Handle to METAFILEPICT metafile with icon and label
 */

HGLOBAL HmfpGetIconForFile(LPTSTR lpszPathName, LPTSTR lpszFileName)
{
   HGLOBAL  hmfp;
   HICON hicon = NULL;
   CHAR szDllName[cchPathNameMax];
   UINT  iIcon = 0;
   UsesMakeOLESTR;

   // Use filename if no pathname supplied
   if (!lpszPathName)
      lpszPathName = lpszFileName;

   szDllName[0] = 0;

   hmfp = OleGetIconOfFile(MakeOLESTR(lpszPathName), FALSE);
   return hmfp;
}

/*
 * ScGetRenderBits
 *
 * Purpose:
 *    Prepares a transportable metafile picture
 *
 * Arguments:
 *    hmfp        The metafile picture
 *    pcb            The size of the transportable data
 *    ppb            Pointer to the data
 *
 * Returns:
 *    SCODE       The status code
 */
SCODE ScGetRenderBits(HGLOBAL hmfp, ULONG * pcb, LPBYTE * ppb)
{
   SCODE          sc = S_OK;
   LPMETAFILEPICT    pmfp = NULL;
   HMETAFILE         hmf = NULL;
   UINT           cbMF = 0;
   WORD           wCheck = 0;

   // Clear out our out parameters
   *pcb = 0;
   *ppb = NULL;

   // Lock down our data
   pmfp = (LPMETAFILEPICT)GlobalLock(hmfp);
   if (!pmfp)
      goto MemErr;

   // How much room do we need
   hmf = pmfp->hMF;

   cbMF = GetMetaFileBitsEx(hmf, 0, NULL);
   if (!cbMF)
   {
      sc = E_FAIL;
      goto CleanUp;
   }

   *pcb = cbMF;

   // Allocate that space
    sc = MAPIAllocateBuffer(*pcb, (LPVOID *)ppb);
if (FAILED(sc))
goto MemErr;

   // Put together the header

   // Get the data for real
#ifdef DEBUG
   cbMF =
#endif
   GetMetaFileBitsEx(hmf, cbMF, *ppb);

   goto CleanUp;

MemErr:
   sc = E_OUTOFMEMORY;

CleanUp:
   if (pmfp)
      GlobalUnlock(hmfp);
   return sc;
}

/*
 * OleUIMetafilePictIconFree
 *
 * Purpose:
 *  Deletes the metafile contained in a METAFILEPICT structure and
 *  frees the memory for the structure itself.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL metafilepict structure created in
 *                  OleMetafilePictFromIconAndLabel
 *
 * Return Value:
 *  None
 */

void OleUIMetafilePictIconFree(HGLOBAL hMetaPict)
   {

   LPMETAFILEPICT      pMF;

   if (NULL==hMetaPict)
      return;

   pMF=(LPMETAFILEPICT)GlobalLock(hMetaPict);

   if (NULL!=pMF)
      {
      if (NULL!=pMF->hMF)
         DeleteMetaFile(pMF->hMF);
      }

   GlobalUnlock(hMetaPict);
   GlobalFree(hMetaPict);

   return;
   }


FREEDOC.H

//=========================================================================
// FREEDOC.H
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    Defines and prototypes for free-floating document routines.
//=========================================================================

#ifndef _FREEDOC_H_
#define _FREEDOC_H_

// Helper to convert from A to W #if !defined(RC_INVOKED) && !defined(MakeOLESTR)
#if !defined(RC_INVOKED) && !defined(MakeOLESTR)
#     define CchSzAToSzW(_szA, _szW, _cbSzW) \
         MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (_szA), -1, (_szW),   \
                        (_cbSzW) / sizeof(WCHAR))

#     define CchSzWToSzA(_szW, _szA, _cbSzA) \
         WideCharToMultiByte(CP_ACP, 0, (_szW), -1, (_szA), (_cbSzA),   \
                        NULL, NULL)

#     define UsesMakeOLESTRX(_cchMax)  OLECHAR szWT[_cchMax]
#     define UsesMakeOLESTR         UsesMakeOLESTRX(MAX_PATH)
#     define MakeOLESTR(_szA)       \
         (CchSzAToSzW((_szA), szWT, sizeof(szWT)) ? szWT : NULL)

#     define UsesMakeANSIX(_cchMax)    CHAR szAT[_cchMax * 2]
#     define UsesMakeANSI           UsesMakeANSIX(MAX_PATH)
#     define MakeANSI(_szW)         \
         (CchSzWToSzA((_szW), szAT, sizeof(szAT)) ? szAT : NULL)
#endif

extern SCODE ScCreateFreeDoc(LPSTORAGEpstg,
 LPMAPIFOLDER   pfld,
 LPSTRszPathName,
 LPSTRlpszFileName,
 LPSTRlpszTitle,
 LPSTRlpszSubject,
 LPSTRlpszAuthor,
 LPSTRlpszKeywords,
 LPSTRlpszComments);

STDAPI GetSummaryInfo
   (
   LPSTORAGE  pstg,
   CString*   lpsTitle,
   CString*   lpsSubject,
   CString*   lpsAuthor,
   CString*   lpsKeywords,
   CString*   lpsComments
   ) ;

STDAPI SetSummaryInfo
   (
   LPSTORAGE  pstg,
   LPCSTR   psTitle,
   LPCSTR   psSubject,
   LPCSTR   psAuthor,
   LPCSTR   psKeywords,
   LPCSTR   psComments
   ) ;

#endif // _FREEDOC_H_


MAINFRM.CPP

//=========================================================================
// MainFrm.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//    implementation of the CMainFrame class
//
//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"

#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

static UINT indicators[] =
{
ID_SEPARATOR,           // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
//  add member initialization code here

}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1;      // fail to create
}

if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
  sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1;      // fail to create
}

//  Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

//  Delete these three lines if you don't want the toolbar to
//  be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

   return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
//  Modify the Window class or styles here by modifying
//  the CREATESTRUCT cs

return CFrameWnd::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers


MAINFRM.H

//=========================================================================
// MainFrm.H
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    interface of the CMainFrame class
//=========================================================================

class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
CStatusBar  m_wndStatusBar;
CToolBar    m_wndToolBar;
//HWNDm_hWndEdit;
// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


POSTDATA.CPP

//=========================================================================
// PostData.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//    implementation of the CPostData class
//
//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"
#include "PostData.h"
#include "fldpick.h"
#include "freedoc.h"

CPostDataPostData;

const CHARcBackSlash='\\';// backslash character

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

 //-------------------------------------------------------------------------
// Purpose:
//    Open a message store if we don't already have it open.
//
//static
HRESULT CPostData::OpenMsgStore
   (
   ULONG      cbeid,
   LPENTRYID  peid,
   LPMDB  *lppMDB
   )
{
   HRESULT  hr             = NOERROR;
   ULONG    fulIsSameStore = FALSE;
   INTnStore   = 0;
   INTii   = 0;

   for (ii = 0; ii < nMaxStores; ii++)
   {
   if (m_pcbeidStore[ii])
{
// If we have a store already open, then simply return
hr = m_lpSession->CompareEntryIDs(cbeid,
                       peid,
                   m_pcbeidStore[ii],
               m_lppeidStore[ii],
           0,
       &fulIsSameStore);  // fulIsSameStore == TRUE if match
if (SUCCEEDED(hr) && fulIsSameStore)
{
*lppMDB = m_lppMDB[ii];
return hr;
}
}
   }

    //Find next available slot
   BOOL bFound = FALSE;
    for (ii = 0; ii < nMaxStores & !bFound; ii++)
{
if (!m_pcbeidStore[ii])
{
nStore = ii;
bFound = TRUE;
}
}

if (nStore == nMaxStores)
{
hr = E_FAIL;
return hr;
}

   // Open the new one.
   hr = m_lpSession->OpenMsgStore(NULL,
                             cbeid,
                             peid,
                             &IID_IMsgStore,
                             MDB_WRITE | MAPI_DEFERRED_ERRORS,
                             &m_lppMDB[nStore]);
    if (SUCCEEDED(hr))
{
  // Save the entry of the one we just opened in case we get called later.
  if (SUCCEEDED(hr = MAPIAllocateBuffer(cbeid, (LPVOID*)&m_lppeidStore[nStore])))
  {
CopyMemory(m_lppeidStore[nStore], peid, cbeid);
m_pcbeidStore[nStore] = cbeid;

  }
}

   *lppMDB = m_lppMDB[nStore];
   return hr;
}

//-------------------------------------------------------------------------
// Purpose:
//    Logoff of the open message store and release it.
//
//static
void CPostData::LogoffMessageStore(VOID)
{
for( INT ii = 0; ii < nMaxStores; ii++)
{
HRESULT hr = NOERROR;
if (m_lppMDB[ii])
{
ULONG ulFlags = LOGOFF_ORDERLY;
if (FAILED(hr = m_lppMDB[ii]->StoreLogoff(&ulFlags)))
{
#ifdef _DEBUG
afxDump << TEXT("LogoffMessageStore:StoreLogoff failed");
#endif
}

m_lppMDB[ii]->Release();
m_lppMDB[ii]       = NULL;
m_pcbeidStore[ii]  = 0;
MAPIFreeBuffer(m_lppeidStore[ii]);
m_lppeidStore[ii]  = NULL;
}
}
}

HRESULT CPostData::HrInitialize()
{
HRESULT hr   = NOERROR;
LPMAPITABLE pTable = NULL;
    LPSRowSetpRows  = NULL;
INTii   = 0;

// Initialize MAPI.
    hr = MAPIInitialize( NULL);
    if( FAILED( hr))
        goto cleanup;

    fMAPIInitialized = TRUE;

// Open session
    hr = MAPILogonEx(NULL,// UI flags,
                NULL,    // profile name
                NULL,               // password
                MAPI_NEW_SESSION | MAPI_LOGON_UI | MAPI_ALLOW_OTHERS,            // MAPI flags
                &m_lpSession);

if FAILED(hr)
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:MAPILogonEx failed");
#endif
goto cleanup;
}

// Open the address book and get the user entryid.
hr = m_lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &m_lpAdrBook);

if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:OpenAddressBook failed");
#endif
goto cleanup;
}

//Get the number of stores in available session
if(SUCCEEDED( hr = m_lpSession->GetMsgStoresTable (0, &pTable)))
{
        hr = HrQueryAllRows (pTable,
                             NULL,
                             NULL,
                             NULL,
                             0,
                             &pRows);

nMaxStores = pRows->cRows;
}

// Allocate all the store handles and entryids
hr = MAPIAllocateBuffer(pRows->cRows * sizeof(LPMDB), (LPVOID *)&m_lppMDB);
if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:MAPIAllocateBuffer failed");
#endif
goto cleanup;
}

hr = MAPIAllocateBuffer(pRows->cRows * sizeof(ULONG), (LPVOID *)&m_pcbeidStore);
if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:MAPIAllocateBuffer failed");
#endif
goto cleanup;
}

hr = MAPIAllocateBuffer(pRows->cRows * sizeof(LPENTRYID), (LPVOID *)&m_lppeidStore);
if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:MAPIAllocateBuffer failed");
#endif
goto cleanup;
}

for (ii = 0; ii < nMaxStores; ii++)
{
m_lppMDB[ii]  = NULL;
m_pcbeidStore[ii] = 0;
m_lppeidStore[ii] = NULL;
}

if ( !CreateImageListExchange() )
{
#ifdef _DEBUG
afxDump << TEXT("HrInitialize:CreateImageListExchange failed");
#endif
goto cleanup;
}

cleanup:
if (FAILED(hr))
HrUninitialize();

return hr;

}

HRESULT CPostData::HrUninitialize()
{
HRESULT hr = NOERROR;

if (m_lpAdrBook)
m_lpAdrBook->Release();

if (m_lpSession)
{
   LogoffMessageStore();
hr = m_lpSession->Logoff(0, 0, 0);
m_lpSession->Release();
}

if( fMAPIInitialized )
MAPIUninitialize();

m_obImageListExchange.DeleteImageList();

return hr;
}

/////////////////////////////////////////////////////////////////////////////
// CPostData

CPostData::CPostData()
{
m_lpSession= NULL;
m_lpAdrBook= NULL;
fMAPIInitialized= FALSE;
m_lpTempFld= NULL;

nMaxStores= 5;
m_pcbeidStore= NULL;
m_lppeidStore= NULL;
m_lppMDB= NULL;

szFile= TEXT("");
szFileTitle = TEXT("");
m_psTitle= TEXT("");
m_psAuthor= TEXT("");
m_psComments= TEXT("");
m_psKeywords= TEXT("");
m_psSubject= TEXT("");
m_psTitle= TEXT("");

}

CPostData::~CPostData()
{
szFile.Empty();
szFileTitle.Empty();
m_psAuthor.Empty();
m_psComments.Empty();
m_psKeywords.Empty();
m_psSubject.Empty();
m_psTitle.Empty();
}

//-------------------------------------------------------------------------
// Purpose:
//    Create our global image list used to draw mail items.
//
//
BOOL CPostData::CreateImageListExchange(VOID)
{
   BOOL  fRet = FALSE;

   static struct SImageListInfo
  {
UINT  nIdIcon;
      } aImageListInfo[cimlExchange] = { ICO_Exchange,
 ICO_Exchange,
 ICO_Mdb,
 ICO_MdbOpen,
 ICO_Folder,
 ICO_FolderOpen,
 ICO_DefaultMdb,
 ICO_DefaultMdbOpen,
   };

   if (m_obImageListExchange.Create(cxSmallIcon, cySmallIcon, TRUE, cimlExchange, 1))
   {
UINT ii= 0;
HICON hicon = NULL;

for (ii = 0; ii < cimlExchange; ii++)
{
         hicon = theApp.LoadIcon(MAKEINTRESOURCE(aImageListInfo[ii].nIdIcon));
if (hicon)
{
if (-1 == m_obImageListExchange.Add(hicon))
            break;
            }
        }

fRet = (cimlExchange == ii);
      }

   return fRet;
}

BOOL CPostData::FilePost(LPSTORAGE pstg)
{
BOOL fRet  = FALSE;
HRESULT hr = NOERROR;
CFolderPicker  FilePostDlg;
CString szTitle;
CString szSubject;
CString szAuthor;
CString szKeywords;
CString szComments;

fRet = (FilePostDlg.DoModal() == IDOK);

if (!fRet)
goto cleanup;

if (m_lpTempFld == NULL)
{
fRet = FALSE;
goto cleanup;
}

GetSummaryInfo(pstg, &szTitle, &szSubject, &szAuthor, &szKeywords, &szComments);

// Create the free doc
hr = ScCreateFreeDoc( pstg,
m_lpTempFld,
                           (LPSTR)(LPCSTR)szFile,
   (LPSTR)(LPCSTR)szFileTitle,
                           (LPSTR)(LPCSTR)szTitle,
                   (LPSTR)(LPCSTR)szSubject,
                           (LPSTR)(LPCSTR)szAuthor,
                           (LPSTR)(LPCSTR)szKeywords,
                           (LPSTR)(LPCSTR)szComments);
if (FAILED(hr))
{
#ifdef _DEBUG
afxDump << TEXT("Post Doc failed");
#endif
fRet = FALSE;
goto cleanup;
}

cleanup:
if (m_lpTempFld)
{
m_lpTempFld->Release();
m_lpTempFld = NULL;
}
  return fRet;
}

void CPostData::CreateAndPostMessage()
{
HRESULThResult= NOERROR;
SCODEsCode= S_OK;
LPMESSAGEpMessage= NULL;
FLAGSfCreateMessageFlags= 0L;
LPSPropValuepMessagePropValues= NULL;
ULONGcbSenderEntryIDSize= 0;
LPENTRYIDpSenderEntryID= NULL;
ULONGuObjectType= 0;
ULONGuCount= 0;
LPMAPIPROPpSender= NULL;
static SizedSPropTagArray(1, aSenderProps) = {1L, PR_DISPLAY_NAME};
LPSPropValuepSenderProps= NULL;
BOOLfRet= FALSE;
UINTc= 0;
UINTuSize= 20;

CFolderPicker  FilePostDlg;
    fRet = (FilePostDlg.DoModal() == IDOK);

if (!fRet)
goto cleanup;

   if (m_lpTempFld == NULL)
goto cleanup;

//Recover sender information
hResult = m_lpSession->QueryIdentity(&cbSenderEntryIDSize,&pSenderEntryID);

if (HR_FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): QueryIdentiry failed");
#endif
goto cleanup;
}

hResult = m_lpSession->OpenEntry(cbSenderEntryIDSize,
pSenderEntryID,
NULL,
MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
&uObjectType,
(LPUNKNOWN FAR *)&pSender);

   if (HR_FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): OpenEntry failed");
#endif
goto cleanup;
}

hResult = pSender->GetProps((LPSPropTagArray)&aSenderProps,
0,
&uCount,
&pSenderProps);

if (FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): GetProps failed");
#endif
goto cleanup;
}

_ASSERT(pSenderProps[0].ulPropTag == PR_DISPLAY_NAME);

//Create a message in the folder.
hResult = m_lpTempFld->CreateMessage(NULL,fCreateMessageFlags,&pMessage);

if (HR_FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): CreateMessage failed");
#endif
goto cleanup;
}

//Grab memory for a message property array
sCode = MAPIAllocateBuffer(uSize * sizeof(SPropValue),(void **)&pMessagePropValues);

if (FAILED(sCode))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): MAPIAllocateBuffer failed");
#endif
goto cleanup;
}

//Set some properties on the message.
pMessagePropValues[c  ].ulPropTag = PR_MESSAGE_CLASS;
pMessagePropValues[c++].Value.lpszA = "IPM.POST";

pMessagePropValues[c  ].ulPropTag = PR_SUBJECT;
pMessagePropValues[c++].Value.lpszA = "";

////////////////////////////////////////////////////////////////
// This property PR_BODY needs to be set with approriate text.
// PR_BODY property holds the message body text.
// Here it is modified with edit box text.
/////////////////////////////////////////////////////////////////

pMessagePropValues[c  ].ulPropTag = PR_BODY;
pMessagePropValues[c++].Value.lpszA = (LPSTR)(LPCSTR)theApp.m_lpBuffer;

pMessagePropValues[c  ].ulPropTag = PR_SENDER_ENTRYID;
pMessagePropValues[c  ].Value.bin.cb = cbSenderEntryIDSize;
pMessagePropValues[c++].Value.bin.lpb = (LPBYTE)pSenderEntryID;

pMessagePropValues[c  ].ulPropTag = PR_SENDER_NAME;
pMessagePropValues[c++].Value.lpszA = pSenderProps[0].Value.lpszA;

pMessagePropValues[c  ].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
pMessagePropValues[c  ].Value.bin.cb = cbSenderEntryIDSize;
pMessagePropValues[c++].Value.bin.lpb = (LPBYTE)pSenderEntryID;

pMessagePropValues[c  ].ulPropTag = PR_SENT_REPRESENTING_NAME;
pMessagePropValues[c++].Value.lpszA = pSenderProps[0].Value.lpszA;

pMessagePropValues[c  ].ulPropTag = PR_DELETE_AFTER_SUBMIT;
pMessagePropValues[c++].Value.b = 0;

pMessagePropValues[c  ].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
pMessagePropValues[c++].Value.b = 0;

pMessagePropValues[c  ].ulPropTag = PR_READ_RECEIPT_REQUESTED;
pMessagePropValues[c++].Value.b = 0;

pMessagePropValues[c  ].ulPropTag = PR_PRIORITY;
pMessagePropValues[c++].Value.ul = PRIO_NORMAL;

//Test that the property array has not been overrun
_ASSERT(c <= uSize);

//Write message properties.
hResult = pMessage->SetProps(c,pMessagePropValues,NULL);
if (HR_FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): SetProps failed");
#endif
goto cleanup;
}

//Clear the read flag.  This operation requires a
//separate MAPI call.
pMessage->SetReadFlag(CLEAR_READ_FLAG);

//Save changes made to the message
hResult = pMessage->SaveChanges(FORCE_SAVE);
if (HR_FAILED(hResult))
{
#ifdef _DEBUG
afxDump << TEXT("CreateAndPostMessage(): SaveChanges failed");
#endif

goto cleanup;
}

cleanup:
//Release MAPI objects
if (pMessage)
pMessage->Release();
if (pSender)
pSender->Release();

//Free memory allocations
if (m_lpTempFld)
m_lpTempFld->Release();

MAPIFreeBuffer(pMessagePropValues);
MAPIFreeBuffer(pSenderEntryID);
MAPIFreeBuffer(pSenderProps);
}

BEGIN_MESSAGE_MAP(CPostData, CWnd)
//{{AFX_MSG_MAP(CPostData)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPostData message handlers


POSTDATA.H

//=========================================================================
// PostData.H
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    interface of the CPostData class
//=========================================================================

/////////////////////////////////////////////////////////////////////////////
// CPostData window

struct MailID
{
ULONG       ulType ;
ULONG       cbEntryID  ;
LPENTRYID   lpEntryID   ;
LPTSTR      lpszDisplayName;
};

typedef struct MailID SMailID;

CONST UINT  cxSmallIcon = 16;
CONST UINT  cySmallIcon = 16;
// Exchange image list indicies
enum
   {
   iimlExchange,
   iimlExchangeOpen,
   iimlMdb,
   iimlMdbOpen,
   iimlFolder,
   iimlFolderOpen,
   iimlDefaultMdb,
   iimlDefaultMdbOpen,
   cimlExchange
   };

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CPostData window

class CPostData : public CWnd
{
// Construction
public:
CPostData();

// Attributes
public:
 LPADRBOOK m_lpAdrBook;
LPMAPISESSION m_lpSession;
BOOL fMAPIInitialized;

LPMAPIFOLDER m_lpTempFld;
CImageList m_obImageListExchange;
LPMDB*m_lppMDB;
ULONG*m_pcbeidStore;
LPENTRYID*m_lppeidStore;
ULONGnMaxStores;

// Summary information
CStringszFile;
CStringszFileTitle;
CString m_psTitle ;
CString m_psAuthor;
CString m_psComments;
CString m_psKeywords;
CString m_psSubject;

// Operations
public:
 HRESULT HrInitialize();
HRESULT HrUninitialize();
HRESULT OpenMsgStore(ULONG cbeid, LPENTRYID  peid, LPMDB *lppMDB);
void LogoffMessageStore(VOID);
BOOL CreateImageListExchange();
BOOL FilePost(LPSTORAGE pstg);
void CreateAndPostMessage();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPostData)
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CPostData();

// Generated message map functions
protected:
//{{AFX_MSG(CPostData)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

extern CPostDataPostData;

/////////////////////////////////////////////////////////////////////////////


POSTDOC.CPP

//=========================================================================
// PostDoc.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//    implementation of the CPostSmplDoc class
//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"
#include "PostDoc.h"
#include "PostData.h"
#include "Freedoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPostSmplDoc

IMPLEMENT_DYNCREATE(CPostSmplDoc, COleDocument)

BEGIN_MESSAGE_MAP(CPostSmplDoc, COleDocument)
//{{AFX_MSG_MAP(CPostSmplDoc)
ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs)
ON_COMMAND(ID_FILE_SAVE, OnFileSave)
//ON_COMMAND(ID_FILE_POSTDOC, OnFilePostdoc)
//}}AFX_MSG_MAP
ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPostSmplDoc construction/destruction

CPostSmplDoc::CPostSmplDoc()
{
//  add one-time construction code here
EnableCompoundFile(TRUE);

}

CPostSmplDoc::~CPostSmplDoc()
{
}

BOOL CPostSmplDoc::OnNewDocument()
{
if (!COleDocument::OnNewDocument())
return FALSE;

//  add reinitialization code here
// (SDI documents will reuse this document)

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplDoc serialization

void CPostSmplDoc::Serialize(CArchive& ar)
{

if (ar.IsStoring())
{
// Save document to disk
ar.Write((LPCSTR)theApp.m_lpBuffer,
theApp.m_lpBuffer.GetLength());

//Save the summary property set
SetSummaryInfo( m_lpRootStg,
(LPCSTR)PostData.m_psTitle,
(LPCSTR)PostData.m_psSubject,
(LPCSTR)PostData.m_psAuthor,
(LPCSTR)PostData.m_psKeywords,
(LPCSTR)PostData.m_psComments);
}
else
{
// Load the document data
CONST nRequested = 100;
INT nReturned = 100;
char lpszBuf[nRequested] = "";
theApp.m_lpBuffer = TEXT("");

while (nReturned == nRequested)
{
nReturned = ar.Read(lpszBuf,
nRequested);
theApp.m_lpBuffer += lpszBuf;
}

}

}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplDoc diagnostics

#ifdef _DEBUG
void CPostSmplDoc::AssertValid() const
{
COleDocument::AssertValid();
}

void CPostSmplDoc::Dump(CDumpContext& dc) const
{
COleDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CPostSmplDoc commands

void CPostSmplDoc::OnFileSave()
{
//  Add your command handler code here
COleDocument::OnFileSave();

}
void CPostSmplDoc::OnFileSaveAs()
{
//  Add your command handler code here
COleDocument::OnFileSaveAs();

}


POSTDOC.H

//=========================================================================
// PostSmplDoc.h
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//   interface of the CPostSmplDoc class
//=========================================================================

class CPostSmplDoc : public COleDocument
{
protected: // create from serialization only
CPostSmplDoc();
DECLARE_DYNCREATE(CPostSmplDoc)

// Attributes
public:

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPostSmplDoc)
public:
virtual void Serialize(CArchive& ar);
virtual BOOL OnNewDocument();
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CPostSmplDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

afx_msg void OnFileSaveAs();
afx_msg void OnFileSave();
LPSTORAGE GetStorage(){ return m_lpRootStg;};

protected:

// Generated message map functions
protected:
//{{AFX_MSG(CPostSmplDoc)

//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


POSTSMPL.CPP

//=========================================================================
// PostSmpl.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//     Defines the class behaviors for the application.
//
//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"

#include "MainFrm.h"
#include "PostDoc.h"
#include "PostView.h"
#include "PostData.h"
#include "SummInfo.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// {5BE4EF30-71F7-11CF-A352-00AA006C41F8}
static const CLSID clsid =
{ 0x5be4ef30, 0x71f7, 0x11cf, { 0xa3, 0x52, 0x0, 0xaa, 0x0, 0x6c, 0x41, 0xf8 } };

/////////////////////////////////////////////////////////////////////////////
// CPostSmplApp

BEGIN_MESSAGE_MAP(CPostSmplApp, CWinApp)
//{{AFX_MSG_MAP(CPostSmplApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_FILE_POSTMSG, OnFilePostmsg)
ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
ON_COMMAND(ID_FILE_SUM_INFO, OnFileSumInfo)
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPostSmplApp construction

CPostSmplApp::CPostSmplApp()
{
//  add construction code here,
// Place all significant initialization in InitInstance
m_bEmbedded = FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CPostSmplApp object

CPostSmplApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CPostSmplApp initialization

BOOL CPostSmplApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls();// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

// Register the application's document templates.  Document templates
//  serve as the connection between documents, frame windows and views.

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CPostSmplDoc),
RUNTIME_CLASS(CMainFrame),       // main SDI frame window
RUNTIME_CLASS(CPostSmplView));
AddDocTemplate(pDocTemplate);

// enable file manager drag/drop and DDE Execute open
   EnableShellOpen();
   RegisterShellFileTypes();

   if (FAILED(PostData.HrInitialize()))
{
#ifdef _DEBUG
afxDump << "Logon failure\n" ;
#endif
return FALSE;
}

// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;

//If some filename was passed in command line, then display its contents in view
if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen && cmdInfo.m_strFileName)
{
if (cmdInfo.m_bRunEmbedded)
m_bEmbedded = TRUE;

//Update all views
POSITION nPos = pDocTemplate->GetFirstDocPosition();
CDocument *pDoc = pDocTemplate->GetNextDoc(nPos);
pDoc->UpdateAllViews(NULL);
}

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CPostSmplApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplApp commands

int CPostSmplApp::ExitInstance()
{
//  Add your specialized code here and/or call the base class
PostData.HrUninitialize();
m_lpBuffer.Empty();
return CWinApp::ExitInstance();
}

void CPostSmplApp::OnFilePostmsg()
{
//  Add your command handler code here
PostData.CreateAndPostMessage();
}

void CPostSmplApp::OnFileOpen()
{
//  Add your command handler code here
CWinApp::OnFileOpen();

}

void CPostSmplApp::OnFileNew()
{
//  Add your command handler code here
m_lpBuffer= TEXT("");

//clear up the variables related to document.
PostData.szFile = TEXT("");
PostData.szFileTitle = TEXT("");
PostData.m_psTitle = TEXT("");
PostData.m_psAuthor = TEXT("");
PostData.m_psComments = TEXT("");
PostData.m_psKeywords = TEXT("");
PostData.m_psSubject = TEXT("");
PostData.m_psTitle = TEXT("");

CWinApp::OnFileNew();
}

void CPostSmplApp::OnFileSumInfo()
{
// TODO: Add your command handler code here
CSummInfo sumDlg;

sumDlg.m_pstitle    =  PostData.m_psTitle;
sumDlg.m_psauthor   =  PostData.m_psAuthor;
sumDlg.m_pscomments =  PostData.m_psComments;
sumDlg.m_psdirectory =  PostData.szFile;
sumDlg.m_psfilename =  PostData.szFileTitle;
sumDlg.m_pskeywords =  PostData.m_psKeywords;
sumDlg.m_pssubject  =  PostData.m_psSubject;
sumDlg.m_pstitle    =  PostData.m_psTitle;

if (IDOK == sumDlg.DoModal())
{
PostData.m_psTitle       = sumDlg.m_pstitle;
PostData.m_psAuthor      = sumDlg.m_psauthor;
PostData.m_psComments    = sumDlg.m_pscomments;
PostData.m_psKeywords    = sumDlg.m_pskeywords;
PostData.m_psSubject     = sumDlg.m_pssubject;
PostData.m_psTitle       = sumDlg.m_pstitle;
}
}


POSTSMPL.H

//=========================================================================
// PostSmpl.h
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    main header file for the POSTSMPL application
//=========================================================================

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols
#include <mapiutil.h>
#include <mapix.h>

/////////////////////////////////////////////////////////////////////////////
// CPostSmplApp:
// See PostSmpl.cpp for the implementation of this class
//

class CPostSmplApp : public CWinApp
{
public:
CPostSmplApp();
CString m_lpBuffer;
BOOL    m_bEmbedded;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPostSmplApp)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CPostSmplApp)
afx_msg void OnAppAbout();
afx_msg void OnFilePostmsg();
afx_msg void OnFileOpen();
afx_msg void OnFileNew();
afx_msg void OnFileSumInfo();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

extern CPostSmplApp theApp;
/////////////////////////////////////////////////////////////////////////////


POSTVIEW.CPP

//=========================================================================
//  PostView.cpp
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//    implementation of the CPostSmplView class
//=========================================================================

#include "stdafx.h"
#include "PostSmpl.h"

#include "PostDoc.h"
#include "PostView.h"
#include "PostData.h"
#include "freedoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView

IMPLEMENT_DYNCREATE(CPostSmplView, CEditView)

BEGIN_MESSAGE_MAP(CPostSmplView, CEditView)
//{{AFX_MSG_MAP(CPostSmplView)
ON_COMMAND(ID_FILE_SAVE, OnFileSave)
ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs)
ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
ON_COMMAND(ID_FILE_POSTDOC, OnFilePostdoc)
ON_UPDATE_COMMAND_UI(ID_FILE_POSTDOC, OnUpdateFilePostdoc)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CEditView::OnFilePrintPreview)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView construction/destruction

CPostSmplView::CPostSmplView()
{
//  add construction code here

}

CPostSmplView::~CPostSmplView()
{
}

BOOL CPostSmplView::PreCreateWindow(CREATESTRUCT& cs)
{
//  Modify the Window class or styles here by modifying
//  the CREATESTRUCT cs

return CEditView::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView drawing

void CPostSmplView::OnDraw(CDC* pDC)
{
CPostSmplDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

//  add draw code for native data here
}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView printing

BOOL CPostSmplView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}

void CPostSmplView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
//  add extra initialization before printing
}

void CPostSmplView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
//  add cleanup after printing
}

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView diagnostics

#ifdef _DEBUG
void CPostSmplView::AssertValid() const
{
CEditView::AssertValid();
}

void CPostSmplView::Dump(CDumpContext& dc) const
{
CEditView::Dump(dc);
}

CPostSmplDoc* CPostSmplView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CPostSmplDoc)));
return (CPostSmplDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CPostSmplView message handlers

void CPostSmplView::OnFileSave()
{
CPostSmplDoc* pDoc = GetDocument();

//Get the text body from edit box
GetDataFromEdit();

if (pDoc)
{
//Save text to the file
pDoc->OnFileSave();

//Get the name of the saved file
PostData.szFile = pDoc->GetPathName();
PostData.szFileTitle = pDoc->GetTitle();
}
}

void CPostSmplView::OnFileSaveAs()
{
CPostSmplDoc* pDoc = GetDocument();

//Get the text body from edit box
GetDataFromEdit();

if (pDoc)
{
//Save text to the file
pDoc->OnFileSaveAs();

//Get the name of the saved file
PostData.szFile = pDoc->GetPathName();
PostData.szFileTitle = pDoc->GetTitle();
}
}

void CPostSmplView::OnFileOpen()
{
CPostSmplDoc* pDoc = GetDocument();

//Open the document file specified by user
theApp.OnFileOpen();

if (pDoc)
{
//Get the filename
PostData.szFile = pDoc->GetPathName();
PostData.szFileTitle = pDoc->GetTitle();

//Get the summary property set
GetSummaryInfo( pDoc->GetStorage(),
&PostData.m_psTitle,
&PostData.m_psSubject,
&PostData.m_psAuthor,
&PostData.m_psKeywords,
&PostData.m_psComments);
}

//Update the editbox with document text
PutDataToEdit();
}

void CPostSmplView::OnFileNew()
{

//Create a new document
theApp.OnFileNew();

//clear the editbox
PutDataToEdit();
}

void CPostSmplView::GetDataFromEdit()
{
//Get data from edit box window
CEdit &EdtCtrl = GetEditCtrl();
UINT cLen = GetBufferLength();
EdtCtrl.GetWindowText(theApp.m_lpBuffer);

}

void CPostSmplView::PutDataToEdit()
{
//Put data to edit box window
CEdit &EdtCtrl = GetEditCtrl();
EdtCtrl.SetWindowText(theApp.m_lpBuffer);
}

void CPostSmplView::OnFilePostdoc()
{
CPostSmplDoc* pDoc = GetDocument();

if (pDoc)
//Post the document
PostData.FilePost(pDoc->GetStorage());
}

void CPostSmplView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
CPostSmplDoc* pDoc = GetDocument();
if (pDoc)
{
//Get the filename
PostData.szFile = pDoc->GetPathName();
PostData.szFileTitle = pDoc->GetTitle();

//Update the editbox with document text
PutDataToEdit();

//Get the summary property set
GetSummaryInfo( pDoc->GetStorage(),
&PostData.m_psTitle,
&PostData.m_psSubject,
&PostData.m_psAuthor,
&PostData.m_psKeywords,
&PostData.m_psComments);

}
CView::OnUpdate(pSender, lHint, pHint);
}

void CPostSmplView::OnUpdateFilePostdoc(CCmdUI* pCmdUI)
{
if (theApp.m_bEmbedded)
pCmdUI->Enable( FALSE );
}


POSTVIEW.H

//=========================================================================
// PostView.h
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    interface of the CPostSmplView class
//=========================================================================

class CPostSmplView : public CEditView
{
protected: // create from serialization only
CPostSmplView();
DECLARE_DYNCREATE(CPostSmplView)

// Attributes
public:
CPostSmplDoc* GetDocument();
void PutDataToEdit();
void GetDataFromEdit();

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPostSmplView)
public:
virtual void OnDraw(CDC* pDC);  // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CPostSmplView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
//{{AFX_MSG(CPostSmplView)
afx_msg void OnFileSave();
afx_msg void OnFileSaveAs();
afx_msg void OnFileOpen();
afx_msg void OnFileNew();
afx_msg void OnFilePostdoc();
afx_msg void OnUpdateFilePostdoc(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in PostSmplView.cpp
inline CPostSmplDoc* CPostSmplView::GetDocument()
   { return (CPostSmplDoc*)m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////


PROPSET.CPP

//=========================================================================
// PROPSET.CPP
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    Provides OLE and MAPI property set routines.
//=========================================================================

#include "stdafx.h"
#include <windowsx.h>
#include <winnls.h>
#include <mapinls.h>
#include "edk.h"
#include "PostSmpl.h"
#include <initguid.h>
#include "freedoc.h"

/*
 * Data Strcuctures
 */

typedef GUID   FMTID;
typedef struct _fmtidoffset
{
   FMTID fmtid;               // FMTID for a section
   DWORD dwOffset;            // offset of section from start of stream
} FMTIDOFFSET;

typedef struct _propsethdr
{
   WORD  wByteOrder;    // Always 0xFFFE
   WORD  wFormat;    // Always 0
   DWORD dwOSVer;    // Hiword: 0=Win16; 1=Mac; 2=Win32
                     // Loword: GetVersion()
   CLSID clsid;         // App CLSID
   DWORD cSections;     // Number of sections
} PROPSETHDR;

typedef DWORD  PID;
typedef struct _pidoffset
{
   PID      pid;              // Property ID
   DWORD dwOffset;            // offset of property from start of section
} PIDOFFSET;

typedef struct _propsectionhdr
{
   DWORD    cbSection;        // Size of this section
   DWORD    cProps;           // Number of properties in this section
} PROPSECTIONHDR;

/*
 * Define some of the guids we use
 */
DEFINE_GUID(FMTID_SumInfo, 0xF29F85E0L, 0x4FF9, 0x1068, 0xAB, 0x91, 0x08, 0x00, 0x2B, 0x27, 0xB3, 0xD9);

typedef struct _mppidmapiprop
{
   WORD     wMapiType;        // MAPI Type
   LPWSTR      szW;           // MAPI Name
} MPPIDMAPIPROP;

enum pidSumInfo {
   PID_TITLE = 2,
   PID_SUBJECT,
   PID_AUTHOR,
   PID_KEYWORDS,
   PID_COMMENTS,
   cpidSumInfoMax
};

typedef struct _propsetstminfo
{
   LPOLESTR       szName;              // stream name
   FMTID const   *pfmtid;              // FMTID of section to promote
   INT            cmppidmapipropMax;      // Mapping for property names
   MPPIDMAPIPROP *mppidmapiprop;
} PROPSETSTMINFO;

// Lego workaround - define the symbols for the array

WCHAR szTitle[]      = L"Title";
WCHAR szSubject[]    = L"Subject";
WCHAR szAuthor[]     = L"Author";
WCHAR szKeywords[]   = L"Keywords";
WCHAR szComments[]   = L"Comments";

MPPIDMAPIPROP mppidmapipropSumInfo[] =
{
   {  PT_NULL, NULL        }, // Dictionary
   {  PT_NULL, NULL        }, // Code page
   {  PT_STRING8,    szTitle     },
   {  PT_STRING8,    szSubject   },
   {  PT_STRING8,    szAuthor    },
   {  PT_MV_STRING8, szKeywords  }, // Special case VT_LPSTR -> MV
   {  PT_STRING8,    szComments  }
};

OLECHAR szSumInfo[] = OLESTR("\005SummaryInformation");

PROPSETSTMINFO rgpropsetstminfo[] =
{
   {szSumInfo, &FMTID_SumInfo, cpidSumInfoMax, mppidmapipropSumInfo},
   {NULL, NULL, 0, NULL}
};

/*
 * SzDupSz
 *
 * Purpose:
 *    This function allocates a new buffer and copies the
 *    supplied string into it.
 *
 * Parameters:
 *    sz    the string to duplicate
 *
 * Returns:
 *    a pointer to the new string (or NULL on failure)
 */
HRESULT SzDupSz(LPCTSTR sz, LPSTR *lppstr)
{
   INT         cb = 0;
   HRESULT   hr = NOERROR;

   if (!sz)
      return hr;

   cb = (lstrlen(sz) + 1) * sizeof(TCHAR);
   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cb, (LPVOID *)lppstr)))
         return hr;

   if (!lstrcpy(*lppstr, sz))
      return hr;

   return hr;
}

/*
 * ScParseKeywords
 *
 * Purpose:
 *    Parses a string of keywords listed by separators in a pval, into a
 *    multi valued property.
 *
 * Arguments:
 *    pval     Contains the keywords list string in pval->Value.lpszA
 *    szDefSep Default list separator
 *
 * Returns:
 *    SCODE    The status code
 */
SCODE ScParseKeywords(LPSPropValue pval, TCHAR * szDefSep)
{
   SCODE    sc= S_OK;
   LPSTR    szKeywords= pval->Value.lpszA;
   CHAR *   pch= szKeywords;
   CHAR *   pchPrev= NULL;
   LPSTR *  rgsz= NULL;
   LONG     cKeywords= 0;
   LONG     isz= 0;
   TCHAR    szSep[3];
   BOOL     fInWord= FALSE;
   BOOL     fFakeOneWord= FALSE;
   HRESULT  hr= NOERROR;

   static TCHAR   szScParseKeywords1[] = TEXT("intl");
   static TCHAR   szScParseKeywords2[] = TEXT("sList");
   static TCHAR   szScParseKeywords3[] = TEXT(",");

   // Get the list separator character
   GetProfileString(szScParseKeywords1, szScParseKeywords2,
         szScParseKeywords3, szSep, sizeof(szSep) / sizeof(TCHAR));

   // Default list separator is the space character
   if (!szDefSep)
      szDefSep = TEXT(" ");

   // Loop through the string zero filling non-keywords
   while (*pch)
   {
#ifdef DBCS
      if (!FGLeadByte(*pch) && (*pch == szDefSep[0] || *pch == szSep[0]))
#else
      if (*pch == szDefSep[0] || *pch == szSep[0])
#endif
      {
         fInWord = FALSE;
      }
      else
      {
         // If we aren't in a word, we are now
         if (!fInWord)
            cKeywords++;
         fInWord = TRUE;
      }

      // Remember this position
      pchPrev = pch;

      // Move forward
#ifdef DBCS
      pch = SzGNext(pch);
#else
      pch = AnsiNext(pch);
#endif

      // If we aren't in a word, zap area from our previous position
      if (!fInWord)
         ZeroMemory(pchPrev, pch - pchPrev);
   }

   // Remove leading and trailing spaces from keywords

   pch = szKeywords;
   isz = cKeywords;
   while (isz)
   {
      isz--;

      // Skip consecutive separators
      while (!*pch)
         pch++;

      if (*pch)
      {
         // Zero out leading spaces
         pchPrev = pch;
         while (isspace(*pch))
            pch = AnsiNext(pch);
         ZeroMemory(pchPrev, pch - pchPrev);

         // Blank keyword
         if (!*pch)
         {
            cKeywords--;
            continue;
         }

         // Go to end of string
         pch = &pch[lstrlen(pch)];

         // Zero out trailing spaces
         pchPrev = AnsiPrev(szKeywords, pch);
         while (isspace(*pchPrev))
            pchPrev = AnsiPrev(szKeywords, pchPrev);
         pchPrev = AnsiNext(pchPrev);
         ZeroMemory(pchPrev, pch - pchPrev);
      }
   }

   // Handle denegerate case where we have no keywords. MAPI requires that
   // there be a value, though, so we fake up a single empty string
   if (!cKeywords)
   {
      cKeywords = 1;
      fFakeOneWord = TRUE;
   }

   // Now that we know how many keywords there are, it's time to allocate
   // space
   pval->Value.MVszA.cValues = cKeywords;

   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cKeywords * sizeof(LPSTR), (LPVOID *)&pval->Value.MVszA.lppszA)))
   {
       sc = E_OUTOFMEMORY;
   goto CleanUp;
   }

   rgsz = pval->Value.MVszA.lppszA;

   if (fFakeOneWord)
   {
      if (!SUCCEEDED(hr = SzDupSz((LPSTR) TEXT(""), &rgsz[0])))
  sc = E_OUTOFMEMORY;
      goto CleanUp;
   }

   // Find the strings we had left over
   pch = szKeywords;
   while (isz < cKeywords)
   {
   if (*pch)
   {
// Remember the start of the string and then zoom to the end
   if ( !SUCCEEDED(hr = SzDupSz(pch, &rgsz[isz++])))
   {
   sc = E_OUTOFMEMORY;
   goto CleanUp;
   }

   while (*pch)
#ifdef DBCS
            pch = SzGNext(pch);
#else
            ++pch;
#endif
   }
      ++pch;
   }

CleanUp:
   return sc;
}

/*
 * ScSaveStr
 *
 * Purpose:
 *    Saves a string
 *
 * Arguments:
 *    pstm              The stream to load from
 *    psz               String pointer
 *
 * Returns:
 *    SCODE             The status code
 */
SCODE ScSaveStr(LPSTREAM pstm, LPCSTR  lpstr)
{
   SCODEsc= S_OK;
   HRESULT  hr= NOERROR;
   DWORDdwSize= sizeof(CHAR) * (lstrlen(lpstr) + 1);
   ULONGcb= 0;

   // Save string size
   if (hr = pstm->Write(&dwSize, sizeof(DWORD), &cb))
      goto CleanUp;

   // Save the string
   if (dwSize &&
      (hr = pstm->Write(lpstr, dwSize, &cb)))
      goto CleanUp;

CleanUp:
   sc = GetScode(hr);
   return sc;
}

/*
 * ScLoadStr
 *
 * Purpose:
 *    Load a string
 *
 * Arguments:
 *    pstm              The stream to load from
 *    psz               String pointer
 *
 * Returns:
 *    SCODE             The status code
 */
SCODE ScLoadStr(LPSTREAM pstm, CString *lpcstring)
{
   SCODEsc= S_OK;
   HRESULT  hr= NOERROR;
   LPSTRlpstr= NULL;
   DWORD    dwSize= 0;

     // Save string size
   if (hr = pstm->Read(&dwSize, sizeof(DWORD), NULL))
      goto CleanUp;

   // Allocate space for string and then load the string
   if (dwSize)
      hr = MAPIAllocateBuffer(dwSize, (LPVOID *)&lpstr);
   else
      hr = SzDupSz((LPTSTR)TEXT(""), &lpstr);

   if (!SUCCEEDED(hr))
   {
      sc = E_OUTOFMEMORY;
      goto CleanUp;
   }

   if (dwSize && (hr = pstm->Read(lpstr, dwSize, NULL)))
   {
      sc = GetScode(hr);
      goto CleanUp;
   }

   *lpcstring = lpstr;

CleanUp:

   MAPIFREEBUFFER(lpstr);

   return sc;
}

/*
 * SetSummaryInfo
 *
 */
STDAPI SetSummaryInfo
   (
   LPSTORAGE pstg,
   LPCSTRlpsTitle,
   LPCSTR   lpsSubject,
   LPCSTR   lpsAuthor,
   LPCSTR   lpsKeywords,
   LPCSTR   lpsComments
   )
{
   SCODE             sc= S_OK;
   HRESULT           hr= NOERROR;
   PROPSETHDR        propsethdr;
   PROPSECTIONHDR    propsectionhdr;
   FMTIDOFFSET*      rgfmtidoffset= NULL;
   FMTIDOFFSET*      pfmtidoffset= NULL;
   PIDOFFSET*        rgpidoffset= NULL;
   PIDOFFSET*        ppidoffset= NULL;
   PROPSETSTMINFO*   ppropsetstminfo= rgpropsetstminfo;    // point to summary info part
   ULONG             cb= 0;
   BOOL              fFound= FALSE;
   BOOL              fSumInfoStm= FALSE;
   LARGE_INTEGER     li= {0};
   DWORD             dwType= 0;
   LPSTREAM          pstm= NULL;

    // Open the summay info stream
   sc = pstg->OpenStream(ppropsetstminfo->szName,
                         NULL,
                         STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                         0,
                         &pstm);

//If doesn't exist, create a new summary info stream
if (sc == STG_E_FILENOTFOUND)
sc = pstg->CreateStream(ppropsetstminfo->szName,
                              STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
  0,
  0,
  &pstm);

   if (FAILED(sc))
   goto CleanUp;

    // Write the PropSet header
propsethdr.cSections= 1;
propsethdr.wByteOrder= 0xFFFE ;
propsethdr.wFormat=  0;
propsethdr.dwOSVer= MAKELONG(LOWORD(GetVersion()),2);
//propsethdr.clsid=;

if (hr = pstm->Write(&propsethdr, sizeof(propsethdr), &cb))
      goto WriteError;

   // Write the FMTID/OFFSET pair
   cb = propsethdr.cSections * sizeof(FMTIDOFFSET);

   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cb, (LPVOID *)&rgfmtidoffset)))
   {
      sc = E_OUTOFMEMORY;
  goto CleanUp;
   }

   pfmtidoffset = rgfmtidoffset;
   pfmtidoffset->fmtid = FMTID_SumInfo;
   pfmtidoffset->dwOffset = sizeof(PROPSETHDR) + sizeof(FMTIDOFFSET);

   if (hr = pstm->Write(rgfmtidoffset, cb, &cb))
      goto WriteError;

   // Seek to that location in the stream
   LISet32(li, pfmtidoffset->dwOffset);
   if (hr = pstm->Seek(li, STREAM_SEEK_SET, NULL))
      goto CleanUp;

   // Write the section header
   propsectionhdr.cProps = 5;
   propsectionhdr.cbSection = 2 * sizeof(DWORD) +
  propsectionhdr.cProps * 4 * sizeof(DWORD)+
  sizeof(CHAR) * (lstrlen(lpsSubject) + 1)+
  sizeof(CHAR) * (lstrlen(lpsTitle) +1)+
  sizeof(CHAR) * (lstrlen(lpsComments) + 1)+
  sizeof(CHAR) * (lstrlen(lpsAuthor) +  1)+
  sizeof(CHAR) * (lstrlen(lpsKeywords) + 1) ;

   if (hr = pstm->Write(&propsectionhdr, sizeof(propsectionhdr), NULL))
      goto WriteError;

   // Write the PID/OFFSET pairs
   cb = propsectionhdr.cProps * sizeof(PIDOFFSET);

   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cb, (LPVOID *)&rgpidoffset)))
   {
      sc = E_OUTOFMEMORY;
  goto CleanUp;
   }
   ppidoffset = rgpidoffset;

   ppidoffset->pid = PID_TITLE;
   ppidoffset->dwOffset = 12 * sizeof(DWORD);
   ppidoffset++;
   ppidoffset->pid = PID_SUBJECT;
   ppidoffset->dwOffset = (ppidoffset - 1)->dwOffset +
sizeof(CHAR) * (lstrlen(lpsTitle) + 1) + sizeof(DWORD)*2;
   ppidoffset++;
   ppidoffset->pid = PID_AUTHOR;
   ppidoffset->dwOffset = (ppidoffset - 1)->dwOffset +
sizeof(CHAR) * (lstrlen(lpsSubject) + 1) + sizeof(DWORD)*2;
   ppidoffset++;
   ppidoffset->pid = PID_COMMENTS;
   ppidoffset->dwOffset = (ppidoffset - 1)->dwOffset +
sizeof(CHAR) * (lstrlen(lpsAuthor) + 1) + sizeof(DWORD)*2;
   ppidoffset++;
   ppidoffset->pid = PID_KEYWORDS;
   ppidoffset->dwOffset = (ppidoffset - 1)->dwOffset +
sizeof(CHAR) * (lstrlen(lpsComments) + 1) + sizeof(DWORD)*2;
   ppidoffset = rgpidoffset;

   if (hr = pstm->Write(rgpidoffset, cb, NULL))
      goto WriteError;

   // Go through all the properties
   for (; propsectionhdr.cProps--; ++ppidoffset)
   {
      // Seek to the data
      LISet32(li, ppidoffset->dwOffset + pfmtidoffset->dwOffset);
      if (hr = pstm->Seek(li, STREAM_SEEK_SET, NULL))
         goto CleanUp;

      // Write the type and property value
      hr = NOERROR;
      switch (ppidoffset->pid)
      {
      case PID_TITLE:

  dwType = PT_STRING8;
  if (hr = pstm->Write(&dwType, sizeof(DWORD), &cb))
goto WriteError;
         sc = ScSaveStr(pstm, lpsTitle);
         break;

      case PID_SUBJECT:

  dwType = PT_STRING8;
  if (hr = pstm->Write(&dwType, sizeof(DWORD), &cb))
goto WriteError;

          sc = ScSaveStr(pstm, lpsSubject);
          break;

      case PID_AUTHOR:

  dwType = PT_STRING8;
  if (hr = pstm->Write(&dwType, sizeof(DWORD), &cb))
goto WriteError;

  sc = ScSaveStr(pstm, lpsAuthor);
  break;

      case PID_KEYWORDS:

  dwType = PT_MV_STRING8;
  if (hr = pstm->Write(&dwType, sizeof(DWORD), &cb))
goto WriteError;

  sc = ScSaveStr(pstm, lpsKeywords);
  break;

      case PID_COMMENTS:

  dwType = PT_STRING8;
  if (hr = pstm->Write(&dwType, sizeof(DWORD), &cb))
  goto WriteError;

  sc = ScSaveStr(pstm, lpsComments);
          break;

      default:
         continue;
         break;
      }
      if (hr) goto WriteError;
   }

   pstm->Commit(STGC_DEFAULT);

   goto CleanUp;

WriteError:
   sc = GetScode(hr);

CleanUp:
   MAPIFREEBUFFER(rgfmtidoffset);
   MAPIFREEBUFFER(rgpidoffset);
   ULRELEASE(pstm);
   return sc;
}

/*
 * GetSummaryInfo
 *
 */
STDAPI GetSummaryInfo
   (
   LPSTORAGE  pstg,
   CString*  lpsTitle,
   CString*   lpsSubject,
   CString*   lpsAuthor,
   CString*   lpsKeywords,
   CString*   lpsComments
   )
{
   SCODE             sc= S_OK;
   HRESULT           hr= NOERROR;
   PROPSETHDR        propsethdr;
   PROPSECTIONHDR    propsectionhdr;
   FMTIDOFFSET*      rgfmtidoffset= NULL;
   FMTIDOFFSET*      pfmtidoffset= NULL;
   PIDOFFSET*        rgpidoffset= NULL;
   PIDOFFSET*        ppidoffset= NULL;
   PROPSETSTMINFO*   ppropsetstminfo = rgpropsetstminfo;    // point to summary info part
   ULONG             cb= 0;
   BOOL              fFound= FALSE;
   BOOL              fSumInfoStm= FALSE;
   LARGE_INTEGER     li={0};
   DWORD             dwType= 0;
   LPSTREAM          pstm= NULL;

   *lpsTitle   = TEXT("");
   *lpsSubject = TEXT("");
   *lpsAuthor    = TEXT("");
   *lpsKeywords= TEXT("");
   *lpsComments= TEXT("");

   // Open the summay info stream
   sc = pstg->OpenStream(ppropsetstminfo->szName,
                         NULL,
                         STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                         0,
                         &pstm);
   if (FAILED(sc))
      {
      goto CleanUp;
      }

   // Read the header to find out how many sections we need to search
   if (hr = pstm->Read(&propsethdr, sizeof(propsethdr), NULL))
      goto ReadError;
   if (propsethdr.cSections == 0)
      goto CleanUp;

   // Allocate space for the sections list and read it in
   cb = propsethdr.cSections * sizeof(FMTIDOFFSET);

   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cb, (LPVOID *)&rgfmtidoffset)))
   {
      sc = E_OUTOFMEMORY;
  goto CleanUp;
   }
   pfmtidoffset = rgfmtidoffset;

   if (hr = pstm->Read(rgfmtidoffset, cb, NULL))
      goto ReadError;

   // Look for the section with the matching FMTID
   for (fFound = FALSE; propsethdr.cSections--; ++pfmtidoffset)
   {
      if (IsEqualGUID(*ppropsetstminfo->pfmtid, pfmtidoffset->fmtid))
      {
         fFound = TRUE;
         break;
      }
   }
   if (!fFound)
      goto CleanUp;

   // Are we in the SumInfo stream
   fSumInfoStm = IsEqualGUID(*ppropsetstminfo->pfmtid, FMTID_SumInfo);

   // Seek to that location in the stream
   LISet32(li, pfmtidoffset->dwOffset);
   if (hr = pstm->Seek(li, STREAM_SEEK_SET, NULL))
      goto ReadError;

   // Read the section header to find out how many props we need to read
   if (hr = pstm->Read(&propsectionhdr, sizeof(propsectionhdr), NULL))
      goto ReadError;
   if (propsectionhdr.cProps == 0)
      goto CleanUp;

   // Allocate space for the props list and read it in
   cb = propsectionhdr.cProps * sizeof(PIDOFFSET);

   if (!SUCCEEDED(hr = MAPIAllocateBuffer(cb, (LPVOID *)&rgpidoffset)))
   {
      sc = E_OUTOFMEMORY;
  goto CleanUp;
   }

   ppidoffset = rgpidoffset;

   if (hr = pstm->Read(rgpidoffset, cb, NULL))
      goto ReadError;

   // Go through all the properties
   for (; propsectionhdr.cProps--; ++ppidoffset)
   {
      // Seek to the data
      LISet32(li, ppidoffset->dwOffset + pfmtidoffset->dwOffset);
      if (hr = pstm->Seek(li, STREAM_SEEK_SET, NULL))
         goto ReadError;

      // Read the type
      if (hr = pstm->Read(&dwType, sizeof(DWORD), NULL))
         goto ReadError;

  // Load the data
      hr = NOERROR;
      switch (ppidoffset->pid)
      {
      case PID_TITLE:
         sc = ScLoadStr(pstm, lpsTitle);
         break;

      case PID_SUBJECT:
         sc = ScLoadStr(pstm, lpsSubject);
         break;

      case PID_AUTHOR:
         sc = ScLoadStr(pstm, lpsAuthor);
         break;

      case PID_KEYWORDS:
 sc = ScLoadStr(pstm, lpsKeywords);
         break;

      case PID_COMMENTS:
         sc = ScLoadStr(pstm, lpsComments);
         break;

      default:
         continue;
         break;
      }
      if (hr) goto ReadError;
   }

   goto CleanUp;

ReadError:
   sc = GetScode(hr);

CleanUp:
   MAPIFREEBUFFER(rgfmtidoffset);
   MAPIFREEBUFFER(rgpidoffset);
   ULRELEASE(pstm);
   return sc;
}

/*
 * PromoteSummaryInfo
 *
 */
 STDAPI PromoteSummaryInfo
   (
   LPMESSAGE   pmsg,
   LPSTR     lpszTitle,
   LPSTR     lpszSubject,
   LPSTR     lpszAuthor,
   LPSTR     lpszKeywords,
   LPSTR     lpszComments
   )
{
   CONST UINT        cProps = 5;
   HRESULT           hr= E_OUTOFMEMORY;
   UINT              i= 0;
   MPPIDMAPIPROP*    pmppidmapipropSumInfo = &mppidmapipropSumInfo[2];  // start at szTitle
   LPMAPINAMEID*     rgpmnid = NULL;
   LPMAPINAMEID      rgmnid = NULL;
   LPSPropTagArray   ptaga = NULL;
   SPropValue        rgvalSumInfo[cProps];

   if (SUCCEEDED(hr = MAPIAllocateBuffer( cProps * sizeof(LPMAPINAMEID), (LPVOID *)&rgpmnid)))
   {
      if (SUCCEEDED(hr = MAPIAllocateBuffer(cProps * sizeof(MAPINAMEID), (LPVOID*)&rgmnid)))
      {
         // Initialize the name id structure and the array of pointers to the name id structurs
         for (i = 0; i < cProps; i++)
         {
            rgmnid[i].lpguid          = (LPGUID)&PS_PUBLIC_STRINGS;
            rgmnid[i].ulKind          = MNID_STRING;
            rgmnid[i].Kind.lpwstrName = pmppidmapipropSumInfo[i].szW;
            rgpmnid[i] = &rgmnid[i];
         }

         if (SUCCEEDED(hr = pmsg->GetIDsFromNames(cProps, rgpmnid, MAPI_CREATE, &ptaga)))
         {
            if (cProps == ptaga->cValues)
            {
               // Plug the IDs into the values
               for (i = 0; i < cProps && SUCCEEDED(hr);  i++)
               {
                  rgvalSumInfo[i].ulPropTag = PROP_TAG(pmppidmapipropSumInfo[i].wMapiType,
                                                       PROP_ID(ptaga->aulPropTag[i]));

                  switch (i)
                  {
                     case 0:
                        rgvalSumInfo[i].Value.lpszA = (LPTSTR)lpszTitle;
                        break;
                     case 1:
                        rgvalSumInfo[i].Value.lpszA = (LPTSTR)lpszSubject;
                        break;
                     case 2:
                        rgvalSumInfo[i].Value.lpszA = (LPTSTR)lpszAuthor;
                        break;
                     case 3:
                     {
                        rgvalSumInfo[i].Value.lpszA = (LPTSTR)lpszKeywords;

                        hr = ScParseKeywords(&rgvalSumInfo[i], TEXT(";"));
                     }
                        break;
                     case 4:
                        rgvalSumInfo[i].Value.lpszA = (LPTSTR)lpszComments;
                        break;
                  }

               }

               if (SUCCEEDED(hr))
               {
                  // Set the values
                  hr = pmsg->SetProps(cProps, rgvalSumInfo, NULL);
               }

               // Free our prop tag array
               MAPIFREEBUFFER(ptaga);
            }
         }
         MAPIFREEBUFFER(rgmnid);
      }
      MAPIFREEBUFFER(rgpmnid);
   }

   return hr;
}


RESOURCE.H

//=========================================================================
// Resource.H
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//    Generated by Microsoft Developer Studio
//=========================================================================

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by postsmpl.rc
//
#define IDD_ABOUTBOX                    100
#define IDR_MAINFRAME                   128
#define IDR_POSTSMTYPE                  129
#define ICO_File                        137
#define IDD_FOLDERPICKER                140
#define ICO_MdbOpen                     146
#define ICO_Mdb                         147
#define ICO_FolderOpen                  149
#define ICO_Folder                      150
#define ICO_Exchange                    152
#define ICO_DefaultMdbOpen              153
#define ICO_DefaultMdb                  154
#define IDC_TREEVIEW                    1056
#define IDD_SUMMARYINFO                 1057
#define IDC_FileName                    1058
#define IDC_Directory                   1059
#define IDC_Title                       1060
#define IDC_Subject                     1061
#define IDC_Author                      1062
#define IDC_Keywords                    1063
#define IDC_Comments                    1064
#define ID_FILE_POSTDOC                 32771
#define ID_FILE_POSTMSG                 32774
#define ID_FILE_SUM_INFO                32775

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        138
#define _APS_NEXT_COMMAND_VALUE         32776
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

//=========================================================================
// stdafx.cpp
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
//
// Purpose:
//   source file that includes just the standard includes
//PostSmpl.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

//=========================================================================

#include "stdafx.h"


STDAFX.H

//=========================================================================
// stdafx.h
//
// Copyright (C) 1986-1996.  Microsoft Corp.  All Rights Reserved.
//
// Purpose:
//     include file for standard system include files
//=========================================================================

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#define VC_EXTRALEAN// Exclude rarely-used stuff from Windows headers

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxole.h>         // MFC OLE classes
#include <afxodlgs.h>       // MFC OLE dialog classes
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT


SUMMINFO.CPP

// SummInfo.cpp : implementation file
//

#include "stdafx.h"
#include "postsmpl.h"
#include "SummInfo.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CSummInfo dialog

CSummInfo::CSummInfo(CWnd* pParent /*=NULL*/)
: CDialog(CSummInfo::IDD, pParent)
{
//{{AFX_DATA_INIT(CSummInfo)
m_psauthor = _T("");
m_pscomments = _T("");
m_psdirectory = _T("");
m_psfilename = _T("");
m_pskeywords = _T("");
m_pssubject = _T("");
m_pstitle = _T("");
//}}AFX_DATA_INIT
}

void CSummInfo::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSummInfo)
DDX_Text(pDX, IDC_Author, m_psauthor);
DDX_Text(pDX, IDC_Comments, m_pscomments);
DDX_Text(pDX, IDC_Directory, m_psdirectory);
DDX_Text(pDX, IDC_FileName, m_psfilename);
DDX_Text(pDX, IDC_Keywords, m_pskeywords);
DDX_Text(pDX, IDC_Subject, m_pssubject);
DDX_Text(pDX, IDC_Title, m_pstitle);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CSummInfo, CDialog)
//{{AFX_MSG_MAP(CSummInfo)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSummInfo message handlers


SUMMINFO.H

// SummInfo.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CSummInfo dialog

class CSummInfo : public CDialog
{
// Construction
public:
CSummInfo(CWnd* pParent = NULL);   // standard constructor

// Dialog Data
//{{AFX_DATA(CSummInfo)
enum { IDD = IDD_SUMMARYINFO };
CStringm_psauthor;
CStringm_pscomments;
CStringm_psdirectory;
CStringm_psfilename;
CStringm_pskeywords;
CStringm_pssubject;
CStringm_pstitle;
//}}AFX_DATA

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSummInfo)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:

// Generated message map functions
//{{AFX_MSG(CSummInfo)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


PROXYGEN.C

//$--proxygen.c-----------------------------------------------------------------
//
//  This file implements a proxy address generating dll for SMTP style
//  address@domain addresses (although can be used for any address type that
//  matches these conventions).
//
//  Proxies are generated from some combination of mailbox name, display name,
//  given name and surname (with spaces and other illegal characters replaced
//  or deleted) using the template specified in the site level proxy generated
//  by your setup program or the Microsoft Exchange Administrator program.
//  See the function header for rcGenerateProxy for details.
//
//  All functions will attempt to write an event log entry in the case of any
//  failure other than RC_MEMORY.
//
// Copyright Microsoft Corporation 1986-1996,  All Rights Reserved
// -----------------------------------------------------------------------------

//
// Include Files
// --------------

#include "proxygen.h"
#include "proxyinf.h"
#include "proxymsg.h"

//
// Defines
// -------

#define FREE(x) { if((x) != NULL) { free((void *)(x)); (x) = NULL; } }

#define MAX(a,b) a>b?a:b

typedefDWORDIES;

// RcInitProxies parses 3 data components out of the site proxy as follows:
//   ADDRESS-TYPE ":" FORMAT "@" DOMAIN
// For example, if the site proxy were "SMTP:%m@microsoft.com" then
//   ADDRESS TYPE is "SMTP"
//   FORMAT is "%m"
//   DOMAIN is "microsoft.com"
// Once these components are parsed out they are stored in the PROXYSESSION
// structure and accessed using the macros below.

typedef struct _proxysession
{
    LPWSTR      pszServer;
    LPWSTR      pszFormat;
    LPWSTR      pszType;
    ULONG       cchType;
    LPWSTR      pszDomain;
    ULONG       cchDomain;
} PROXYSESSION, *PPROXYSESSION;

#define GET_SITE_FORMAT(x)     ((PPROXYSESSION)(x))->pszFormat
#define GET_SITE_TYPE(x)       ((PPROXYSESSION)(x))->pszType
#define GET_SITE_TYPE_CCH(x)   ((PPROXYSESSION)(x))->cchType
#define GET_SITE_DOMAIN(x)     ((PPROXYSESSION)(x))->pszDomain
#define GET_SITE_DOMAIN_CCH(x) ((PPROXYSESSION)(x))->cchDomain
#define GET_SERVER(x)          ((PPROXYSESSION)(x))->pszServer

// The translation table from unicode to SMTP legal (subset of 7-bit ASCII)
// characters is indexed from 0 to MAX_TRANSLATE_TABLE.  Characters outside
// the range of the translation table are deleted.  Do not change this
// constant without changing the translation table defined locally to
// RcGenerateUserProxy().
#define MAX_TRANSLATE_TABLE     255

//
// Globals
// -------

const PWSTR pszEventSource         = L"EDKProxy";
const WCHAR chAddressTypeDelimiter  = (WCHAR)':';
const WCHAR chDomainDelimiter       = (WCHAR)'@';
const PWSTR pszDefaultFormat        = L"%m";
const ULONG cchDefaultFormat        = 2;

//  Local Function Prototypes
//  -------------------------

RC rcScanAndAppend(IN char *        sAnsitoSMTPMap,         // char replacement table
                   IN LPWSTR        pszSource,              // source buffer
                   IN OUT LPWSTR    pszDest,                // dest buffer
                   IN OUT ULONG *   piDest,                 // current index in dest buffer
                   IN ULONG         iMaxDest);              // max dest buffer index

BOOL fIsWhiteSpaceW(WCHAR wc);

VOID TrimW(IN OUT PWSTR pwstrSrc);

VOID LogError(LPWSTR pszServer, IES ies, WORD wEventType, LPWSTR rgwsz[], WORD cwsz);

RC RcGenerateUserProxy(                 // RETURNS: RC_SUCCESS RC_MEMORY RC_ERROR
    IN LPWSTR pwstrUserFormat,          // Format string for user part of proxy
    IN LPWSTR pwstrServer,              // Server name
    IN PRECIPIENTINFO pRecipientInfo,   // Recipient specific details.
    INT nRetries,                       // Number of times this function
                                        //   has previously been called with
                                        //   this value for pRecipientInfo.
    IN OUT LPWSTR pwstrUserProxy,       // user part of SMTP proxy address
    OUT ULONG * pcchUserProxy);         // count of num chars in pwstrUserProxy

//$--RcInitProxies--------------------------------------------------------------
//  Parses and makes the site specific information accessible to the
//  RcGenerateProxy, RcUpdateProxy, RcValidateProxy, RcValidateSiteProxy and
//  CloseProxies functions.
//
//  The pwstrSiteProxy parameter is required as it cannot be defaulted for this
//  type of address.
// -----------------------------------------------------------------------------
RC RcInitProxies(               // RETURNS:  RC_MEMORY RC_ERROR RC_SUCCESS
    IN LPWSTR pwstrSiteProxy,   // The site proxy for this address type.
    IN LPWSTR pszServer,        // Exchange Server name
    OUT HANDLE *phProxySession) // Handle to the proxy session.
{
    RC     rc           = RC_ERROR;
    LPWSTR pszType      = NULL;
    ULONG  cchType      = 0;
    LPWSTR pszFormat    = NULL;
    ULONG  cchFormat    = 0;
    LPWSTR pszDomain    = NULL;
    ULONG  cchDomain    = 0;
    LPWSTR pszAt        = NULL;
    LPWSTR pszColon     = NULL;

    // Verify Input Parameters

    if (pszServer == NULL)
    {
        // invalid arguments - internal error
        // can't write event log since no server name
        rc = RC_ERROR;
        goto error;
    }

    // The domain proxy must be available so check this first

    if (pwstrSiteProxy == NULL)
    {
LogError(
    pszServer,
    iesNoSiteProxy,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto error;
    }

    // Initialize output parameter

    *phProxySession = NULL;

    // Allocate a proxy session structure

    *phProxySession = (HANDLE) malloc(sizeof(PROXYSESSION));
    if (*phProxySession == NULL)
    {
        rc = RC_MEMORY;
        goto error;
    }

    // Initialize elements of *phProxySession

    GET_SERVER(*phProxySession) = NULL;
    GET_SITE_FORMAT(*phProxySession) = NULL;
    GET_SITE_DOMAIN(*phProxySession) = NULL;
    GET_SITE_DOMAIN_CCH(*phProxySession) = 0;
    GET_SITE_TYPE(*phProxySession) = NULL;
    GET_SITE_TYPE_CCH(*phProxySession) = 0;

    // Trim blanks from pwstrSiteProxy

    TrimW(pwstrSiteProxy);

    // Validate pwstrSiteProxy, must be "TYPE:x@y" where x is 0 or more chars
    // up to the first colon and y is at least 1 char

    cchDomain = lstrlenW(pwstrSiteProxy);
    pszAt = wcschr(pwstrSiteProxy, chDomainDelimiter);
    pszColon = wcschr(pwstrSiteProxy, chAddressTypeDelimiter);

    if ((cchDomain < 4) ||  // min size = T:@y
        (pszColon == NULL) ||       // no colon
        (*pszColon != chAddressTypeDelimiter) ||
        (pwstrSiteProxy[0] == chAddressTypeDelimiter)) // 1st char is :, no addr type
    {
        // pwstrSiteProxy is invalid

   LPWSTR rgwsz[] =
{
pwstrSiteProxy
};

LogError(
    pszServer,
    iesBadSiteProxy,
    EVENTLOG_ERROR_TYPE,
    rgwsz,
    1);

        rc = RC_ERROR;
        goto error;
    }

    // Split site proxy into it's component parts
    // ------------------------------------------

    // replace : with 0 temporarily

    *pszColon = 0;

    // get address type

    cchType = lstrlenW(pwstrSiteProxy);

    pszType = malloc((cchType + 1) * sizeof(WCHAR));

    if (pszType == NULL)
    {
        rc = RC_MEMORY;
        goto error;
    }

    lstrcpyW(pszType, pwstrSiteProxy);

    // Restore ':'

    *pszColon = chAddressTypeDelimiter;

    // Check site proxy again for more validation

    if ((pwstrSiteProxy[cchType] != chAddressTypeDelimiter) ||
        (pszAt == NULL) ||
        (*pszAt != chDomainDelimiter))
    {
        // pwstrSiteProxy is invalid

   LPWSTR rgwsz[] =
{
pwstrSiteProxy
};

LogError(
    pszServer,
    iesBadSiteProxy,
    EVENTLOG_ERROR_TYPE,
    rgwsz,
    1);

        rc = RC_ERROR;
        goto error;
    }

    // replace @ with 0 temporarily

    *pszAt = 0;

    // get format part

    cchFormat = lstrlenW(&(pwstrSiteProxy[cchType+1]));

    if (cchFormat == 0)
    {
        // use default format

        pszFormat = malloc((lstrlenW(pszDefaultFormat) + 1) * sizeof(WCHAR));

        if (pszFormat == NULL)
        {
            rc = RC_MEMORY;
            goto error;
        }

        lstrcpyW(pszFormat, pszDefaultFormat);
    }
    else
    {
        // pull format out of site proxy

        pszFormat = malloc((cchFormat+1)*sizeof(WCHAR));

        if (pszFormat == NULL)
        {
            rc = RC_MEMORY;
            goto error;
        }

        lstrcpyW(pszFormat, &(pwstrSiteProxy[cchType])+1);
    }

    // get domain part

    cchDomain = lstrlenW(pszAt+1);
    pszDomain = malloc((cchDomain+1)*sizeof(WCHAR));

    if (pszDomain == NULL)
    {
        rc = RC_MEMORY;
        goto error;
    }

    lstrcpyW(pszDomain, (pszAt+1));

    // Restore '@'

    *pszAt = chDomainDelimiter;

    // Set structure members

    GET_SERVER(*phProxySession) = pszServer;
    GET_SITE_FORMAT(*phProxySession) = pszFormat;
    GET_SITE_DOMAIN(*phProxySession) = pszDomain;
    GET_SITE_DOMAIN_CCH(*phProxySession) = cchDomain;
    GET_SITE_TYPE(*phProxySession) = pszType;
    GET_SITE_TYPE_CCH(*phProxySession) = cchType;

    rc = RC_SUCCESS;
    goto cleanup;

error:

    FREE(*phProxySession);
    FREE(pszType);
    FREE(pszFormat);
    FREE(pszDomain);

cleanup:

    return rc;
}

//$--RcGenerateUserProxy--------------------------------------------------------
//  Private helper function: Generates the user part of the proxy (before the
//  '@') given the format portion of the site proxy and the recipient info.
// -----------------------------------------------------------------------------
RC RcGenerateUserProxy(                 // RETURNS: RC_SUCCESS RC_MEMORY RC_ERROR
    IN LPWSTR pwstrUserFormat,          // Format string for user part of proxy
    IN LPWSTR pwstrServer,              // Server name
    IN PRECIPIENTINFO pRecipientInfo,   // Recipient specific details.
    INT nRetries,                       // Number of times this function
                                        //   has previously been called with
                                        //   this value for pRecipientInfo.
    IN OUT LPWSTR pwstrUserProxy,       // user part of SMTP proxy address
    OUT ULONG * pcchUserProxy)          // count of num chars in pwstrUserProxy
{
    RC      rc                          = RC_ERROR;
    LPWSTR  pszLocalFormat              = NULL;
    BOOL    fFreeLocalFormat            = FALSE;
    ULONG   iProxyBuffer                = 0,
            iFormat                     = 0,
            cchFormat                   = 0;
    BOOL    fEscape                     = FALSE;
    char sAnsitoSMTPMap[] =
    {   // SMTP        ANSI        Byte
            0,      // null         00
            0,      // (control)    01
            0,      // (control)    02
            0,      // (control)    03
            0,      // (control)    04
            0,      // (control)    05
            0,      // (control)    06
            0,      // (control)    07
            0,      // (control)    08
            0,      // (control)    09
            0,      // (control)    10
            0,      // (control)    11
            0,      // (control)    12
            0,      // (control)    13
            0,      // (control)    14
            0,      // (control)    15
            0,      // (control)    16
            0,      // (control)    17
            0,      // (control)    18
            0,      // (control)    19
            0,      // (control)    20
            0,      // (control)    21
            0,      // (control)    22
            0,      // (control)    23
            0,      // (control)    24
            0,      // (control)    25
            0,      // (control)    26
            0,      // (control)    27
            0,      // (control)    28
            0,      // (control)    29
            0,      // (control)    30
            0,      // (control)    31
            0,      // space        32
            '!',    // !            33
            0,      // "            34 ** special **
            '#',    // #            35
            '$',    // $            36
            '%',    // %            37
            '&',    // &            38
            39,     // '            39
            0,      // (            40 ** special **
            0,      // )            41 ** special **
            '*',    // *            42
            '+',    // +            43
            0,      // ,            44 ** special **
            '-',    // -            45
            '.',    // .            46 ** This is a special but is ok here **
            '/',    // /            47
            '0',    // 0            48
            '1',    // 1            49
            '2',    // 2            50
            '3',    // 3            51
            '4',    // 4            52
            '5',    // 5            53
            '6',    // 6            54
            '7',    // 7            55
            '8',    // 8            56
            '9',    // 9            57
            0,      // :            58 ** special **
            0,      // ;            59 ** special **
            0,      // <            60 ** special **
            '=',    // =            61
            0,      // >            62 ** special **
            '?',    // ?            63
            0,      // @            64 ** special **
            'A',    // A            65
            'B',    // B            66
            'C',    // C            67
            'D',    // D            68
            'E',    // E            69
            'F',    // F            70
            'G',    // G            71
            'H',    // H            72
            'I',    // I            73
            'J',    // J            74
            'K',    // K            75
            'L',    // L            76
            'M',    // M            77
            'N',    // N            78
            'O',    // O            79
            'P',    // P            80
            'Q',    // Q            81
            'R',    // R            82
            'S',    // S            83
            'T',    // T            84
            'U',    // U            85
            'V',    // V            86
            'W',    // W            87
            'X',    // X            88
            'Y',    // Y            89
            'Z',    // Z            90
            0,      // [            91 ** special **
            '^',    // ^            92
            0,      // ]            93 ** special **
            0,      // ,            94 ?? - doesn't translate
            '_',    // _            95
            '`',    // `            96
            'a',    // a            97
            'b',    // b            98
            'c',    // c            99
            'd',    // d            100
            'e',    // e            101
            'f',    // f            102
            'g',    // g            103
            'h',    // h            104
            'i',    // i            105
            'j',    // j            106
            'k',    // k            107
            'l',    // l            108
            'm',    // m            109
            'n',    // n            110
            'o',    // o            111
            'p',    // p            112
            'q',    // q            113
            'r',    // r            114
            's',    // s            115
            't',    // t            116
            'u',    // u            117
            'v',    // v            118
            'w',    // w            119
            'x',    // x            120
            'y',    // y            121
            'z',    // z            122
            '{',    // {            123
            '|',    // |            124
            '}',    // }            125
            '~',    // ~            126
            0,      // del          127
            0,      // (control)    128
            0,      // (control)    129
            0,      // (control)    130
            0,      // (control)    131
            0,      // (control)    132
            0,      // (control)    133
            0,      // (control)    134
            0,      // (control)    135
            0,      // (control)    136
            0,      // (control)    137
            0,      // (control)    138
            0,      // (control)    139
            0,      // (control)    140
            0,      // (control)    141
            0,      // (control)    142
            0,      // (control)    143
            0,      // (control)    144
            0,      // (control)    145
            0,      // (control)    146
            0,      // (control)    147
            0,      // (control)    148
            0,      // (control)    149
            0,      // (control)    150
            0,      // (control)    151
            0,      // (control)    152
            0,      // (control)    153
            0,      // (control)    154
            0,      // (control)    155
            0,      // (control)    156
            0,      // (control)    157
            0,      // (control)    158
            0,      // (control)    159
            0,      // space        160
            0,      // 1st exclamn  161
            'C',    // cents        162
            'L',    // pounds       163
            'P',    // phi          164
            'Y',    // yen          165
            'I',    // pipe         166
            'S',    // section      167
            0,      // "            168
            'C',    // copyright    169
            'A',    // a underscore 170
            0,      // <<           171
            0,      // not          172
            '-',    // -            173
            'R',    // registered   174
            0,      // bar          175
            0,      // degree       176
            0,      // plus/minus   177
            '2',    // supers 2     178
            '3',    // supers 3     179
            0,      // '            180
            'M',    // mu           181
            'P',    // paragraph    182
            0,      // "            183
            0,      // ,            184
            '1',    // supers 1     185
            'O',    // o underscore 186
            0,      // >>           187
            0,      // 1/4          188
            0,      // 1/2          189
            0,      // 3/4          190
            0,      // 1st question 191
            'A',    // A grave      192
            'A',    // A acute      193
            'A',    // A circumflex 194
            'A',    // A tilde      195
            'A',    // A umlaut     196
            'A',    // A dot        197
            'A',    // AE ligature  198
            'C',    // C cedilla    199
            'E',    // E grave      200
            'E',    // E acute      201
            'E',    // E circumflex 202
            'E',    // E umlaut     203
            'I',    // I grave      204
            'I',    // I acute      205
            'I',    // I circumflex 206
            'I',    // I umlaut     207
            'D',    // D dash       208
            'N',    // N tilde      209
            'O',    // O grave      210
            'O',    // O acute      211
            'O',    // O circumflex 212
            'O',    // O tilde      213
            'O',    // O umlaut     214
            'X',    // multiplicn   215
            '0',    // 0 slash      216
            'U',    // U grave      217
            'U',    // U acute      218
            'U',    // U circumflex 219
            'U',    // U umlaut     220
            'Y',    // Y acute      221
            'T',    // iapta        222
            'B',    // beta         223
            'a',    // a grave      224
            'a',    // a acute      225
            'a',    // a circumflex 226
            'a',    // a tilde      227
            'a',    // a umlaut     228
            'a',    // a dot        229
            'a',    // ae ligature  230
            'c',    // c cedilla    231
            'e',    // e grave      232
            'e',    // e acute      233
            'e',    // e circumflex 234
            'e',    // e umlaut     235
            'i',    // i grave      236
            'i',    // i acute      237
            'i',    // i circumflex 238
            'i',    // i umlaut     239
            'd',    // d dash       240
            'n',    // n tilde      241
            'o',    // o grave      242
            'o',    // o acute      243
            'o',    // o circumflex 244
            'o',    // o tilde      245
            'o',    // o umlaut     246
            0,      // divide       247
            '0',    // o slash      248
            'u',    // u grave      249
            'u',    // u acute      250
            'u',    // u circumflex 251
            'u',    // u umlaut     252
            'y',    // y acute      253
            't',    // iapta        254
            'y'     // y umlaut     255
    };

    // Verify Input Parameters

    if  (pwstrServer == NULL)
    {
        // invalid arguments - internal error
        // can't write event log since no server name
        rc = RC_ERROR;
        goto cleanup;
    }

    if ((pwstrUserFormat == NULL) ||
        (pwstrServer == NULL) ||
        (pRecipientInfo == NULL) ||
        (pwstrUserProxy == NULL))
    {
        // invalid arguments - internal error

LogError(
    pwstrServer,
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    // Initialize output parameters

    *pwstrUserProxy = 0;
    *pcchUserProxy = 0;

    // Figure out what format to use based on nRetries
    // -----------------------------------------------

    if (nRetries == 0)
    {
        pszLocalFormat = pwstrUserFormat;
    }
    else if (nRetries == 1)
    {
        pszLocalFormat = pszDefaultFormat;
    }
    else if (nRetries > 1)
    {
        WCHAR pszRetries[20]; // temp buffer used to convert long to string

        _itow(nRetries, pszRetries, 10); // 10 is base

        pszLocalFormat = malloc(
            (cchDefaultFormat + lstrlenW(pszRetries) + 1) * sizeof(WCHAR));

        if (pszLocalFormat == NULL)
        {
            rc = RC_MEMORY;
            goto cleanup;
        }

        fFreeLocalFormat = TRUE;
        wsprintfW(pszLocalFormat, L"%s%s", pszDefaultFormat, pszRetries);
    }
    else
    {
        // nRetries is negative - nonsensical or nRetries is so big its negative

   LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

LogError(
    pwstrServer,
    iesUnableToCreateUniqueProxy,
    EVENTLOG_ERROR_TYPE,
    rgwsz,
    1);

        rc = RC_ERROR;
        goto cleanup;
    }

    cchFormat = lstrlenW(pszLocalFormat);

    // Parse the format string
    // -----------------------

    fEscape = FALSE;

    for (iFormat = 0; iFormat < cchFormat; iFormat++)
    {
        if (fEscape)
        {
            // next character indicates reason for escape

            switch (pszLocalFormat[iFormat])
            {

            case ('%'):
                if (iProxyBuffer+1 < cchMaxProxyValue)
                {
                    pwstrUserProxy[iProxyBuffer++] = '%';
                    pwstrUserProxy[iProxyBuffer] = 0;
                }
                else
                {
                    // Can't fit character in buffer

     LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

          LogError(
            pwstrServer,
        iesProxyToBig,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

            case ('s'): // surname
                rc = rcScanAndAppend(
                    sAnsitoSMTPMap,                 // char replacement table
                    pRecipientInfo->pszLastName,    // source buffer
                    pwstrUserProxy,                 // dest buffer
                    &iProxyBuffer,                  // current index in dest buffer
                    cchMaxProxyValue-1);            // dest buffer max index

                if (RC_FAILED(rc))
                {
                    // Can't fit last name in buffer

     LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

          LogError(
            pwstrServer,
        iesProxyToBig,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

            case ('g'): // given name
                rc = rcScanAndAppend(
                    sAnsitoSMTPMap,                 // char replacement table
                    pRecipientInfo->pszFirstName,   // source buffer
                    pwstrUserProxy,                 // dest buffer
                    &iProxyBuffer,                  // current index in dest buffer
                    cchMaxProxyValue-1);            // dest buffer size

                if (RC_FAILED(rc))
                {
                    // Can't fit given name in buffer

     LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

          LogError(
            pwstrServer,
        iesProxyToBig,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

            case ('d'): // display name
                rc = rcScanAndAppend(
                    sAnsitoSMTPMap,                 // char replacement table
                    pRecipientInfo->pszDisplayName, // source buffer
                    pwstrUserProxy,                 // dest buffer
                    &iProxyBuffer,                  // current index in dest buffer
                    cchMaxProxyValue-1);            // dest buffer size

                if (RC_FAILED(rc))
                {
                    // Can't fit display name in buffer

     LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

          LogError(
            pwstrServer,
        iesProxyToBig,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

            case ('m'): // mailbox name
                rc = rcScanAndAppend(
                    sAnsitoSMTPMap,                 // char replacement table
                    pRecipientInfo->pszCommonName,  // source buffer
                    pwstrUserProxy,                 // dest buffer
                    &iProxyBuffer,                  // current index in dest buffer
                    cchMaxProxyValue-1);            // dest buffer size

                if (RC_FAILED(rc))
                {
                    // Can't fit mailbox name in buffer

     LPWSTR rgwsz[] =
{
pRecipientInfo->pszDisplayName
};

          LogError(
            pwstrServer,
        iesProxyToBig,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

            case ('r'): // char replacement change
                if (iFormat+2 < cchFormat)
                {
                    char chSrc, chDest;

                    chSrc = (char)pszLocalFormat[iFormat+1];
                    chDest = (char)pszLocalFormat[iFormat+2];

                    if (chSrc == chDest)
                    {
                        sAnsitoSMTPMap[chSrc] = 0;
                    }
                    else
                    {
                        sAnsitoSMTPMap[chSrc] = chDest;
                    }

                    iFormat += 2;
                }
                else
                {
                    // format string is invalid

     LPWSTR rgwsz[] =
{
pszLocalFormat

};

          LogError(
            pwstrServer,
        iesInvalidFormatString,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
                break;

             default:
                // format specifier is unrecognized
                {
      LPWSTR rgwsz[] =
        {
    pszLocalFormat
    };

          LogError(
                pwstrServer,
        iesInvalidFormatString,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);
                }

                rc = RC_ERROR;
                goto cleanup;

            } // switch

            fEscape = FALSE;

        } // if fEscape
        else
        {
            if (pszLocalFormat[iFormat] == '%')
            {
                fEscape = TRUE;
            } // if escape char
            else
            {
                // literal character; just insert it as long as it is legal
                if ((pszLocalFormat[iFormat] <= MAX_TRANSLATE_TABLE) &&
                    (sAnsitoSMTPMap[pszLocalFormat[iFormat]] != 0) &&
                    (iProxyBuffer < cchMaxProxyValue-1))
                {
                    pwstrUserProxy[iProxyBuffer++] = pszLocalFormat[iFormat];
                    pwstrUserProxy[iProxyBuffer] = 0;
                }
                else
                {
                    // invalid literal character in format string

     LPWSTR rgwsz[] =
{
pszLocalFormat
};

          LogError(
            pwstrServer,
        iesInvalidFormatString,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                    rc = RC_ERROR;
                    goto cleanup;
                }
            } // else literal
        } // else !fEscape

    } // for iFormat

    *pcchUserProxy = iProxyBuffer;

    rc = RC_SUCCESS;

cleanup:

    if (fFreeLocalFormat)
    {
        FREE(pszLocalFormat);
    }

    return rc;

}

//$--RcGenerateProxy------------------------------------------------------------
//  Returns a single proxy address as a unicode string.
//
//  When nRetries == 0
//  ==================
//
//  The format of this proxy is based upon the site proxy string which has the
//  following format:
//      address-type ":" [format] "@" domain
//
//  The address-type and domain are uninterpreted and are written exactly as is
//
//  Format is optional, if specified it must contain only valid address
//  characters plus the following special symbols:
//      "%s"        substitute the Surname of the user
//      "%g"        substitute the Given name of the user
//      "%d"        substitute the Display name of the user
//      "%m"        substitute the mailbox name of the user
//      "%%"        substitute a single '%'
//  In all the substitutions above, spaces and illegal characters will be
//  removed from the field before the substitution is made. To specify a
//  different substitution to be used, use %r:
//      "%r" char1 char2
//  All instances of char1 will be replaced by char2 in the substitutions
//  which occur after this in the format string. If char1 == char2 the
//  character will be deleted.
//
//  If the format field is not present, this is equivalent to:
//      address-type ":" "%m" "@" domain
//
//  Examples:
//
//  These examples are based on:
//      mailbox = "dougdo"
//      display name = "Doug M. Dosdall"
//      surname = "Dosdall"
//      given name = "Doug"
//
//  Site Proxy                          Generated User Proxy
//  ----------------------------------- ----------------------------------------
//  SMTP:%r _%d@wspu.microsoft.com      SMTP:Doug_M._Dosdall@wspu.microsoft.com
//  SAMPLE:%r..%d@wspu.microsoft.com    SAMPLE:DougMDosdall@wspu.microsoft.com
//  EDK:%g.%s@wspu.microsoft.com        EDK:Doug.Dosdall@wspu.microsoft.com
//  SMTP:@wspu.microsoft.com            SMTP:dougdo@wspu.microsoft.com
//
//
//  When nRetries == 1
//  ==================
//
//  The user proxy is generated as per above except the format portion is
//  ignored, i.e. assumed to be %m only.
//
//
//  When nRetries > 1
//  =================
//
//  The user proxy is generated using the following format:
//      address-type ":" "%m" nRetries "@" domain
//
//  For example, when nRetries == 2, the generated user proxy for the example
//  above would be:
//
//  SMTP:dougdo2@wspu.microsoft.com
//
// -----------------------------------------------------------------------------
RC RcGenerateProxy(                     // RETURNS: RC_SUCCESS RC_MEMORY RC_ERROR
    IN HANDLE hProxySession,            // Handle to proxy sesssion.
    IN PRECIPIENTINFO pRecipientInfo,   // Recipient specific details.
    INT nRetries,                       // Number of times this function
                                        //   has previously been called with
                                        //   this value for pRecipientInfo.
    OUT LPWSTR *ppwstrProxyAddr)        // generated proxy address.
{
    RC      rc                          = RC_SUCCESS;
    WCHAR   pwstrProxyBuffer[cchMaxProxyValue] = {L'\0'};
    ULONG   cchProxyBuffer              = 0;
    ULONG   cb                          = 0;
    ULONG   i                           = 0;

    // Verify Input Parameters

if ((hProxySession == NULL) ||
        (GET_SERVER(hProxySession) == NULL))
    {
        // invalid arguments - internal error
        // can't write event log since no server name
        rc = RC_ERROR;
        goto cleanup;
    }

    if ((GET_SITE_FORMAT(hProxySession) == NULL) ||
        (GET_SITE_DOMAIN(hProxySession) == NULL) ||
        (pRecipientInfo == NULL) ||
        (pRecipientInfo->pszCommonName == NULL))
    {
        // invalid arguments - internal error; RcGenerateProxy should never
        // be called unless RcInitProxies was succesfully called first and
        // RcInitProxies must always set the site format and site domain.

LogError(
    GET_SERVER(hProxySession),
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    // Initialize output parameter

    *ppwstrProxyAddr = NULL;

    // Get the expanded user part of the proxy
    // ---------------------------------------

    rc = RcGenerateUserProxy(
        GET_SITE_FORMAT(hProxySession),
        GET_SERVER(hProxySession),
        pRecipientInfo,
        nRetries,
        pwstrProxyBuffer,
        &cchProxyBuffer);

    if (RC_FAILED(rc))
    {
        // RcGenerateUserProxy generates own log entry
        // just pass error through
        goto cleanup;
    }

    // Figure out the size of the final proxy and alloc buffer
    // -------------------------------------------------------

    cb = (GET_SITE_TYPE_CCH(hProxySession) +    // e.g. "SMTP"
          1 +                                   // ":"
          cchProxyBuffer +                      // user proxy
          1 +                                   // @
          GET_SITE_DOMAIN_CCH(hProxySession) +  // domain
          1) * sizeof(WCHAR);                   // terminating null

    *ppwstrProxyAddr = malloc(cb);

    if ((*ppwstrProxyAddr) == NULL)
    {
        rc = RC_MEMORY;
        goto cleanup;
    }

    // Put all the pieces together in *ppwstrProxyAddr
    // -----------------------------------------------

    lstrcpyW(*ppwstrProxyAddr, GET_SITE_TYPE(hProxySession));
    i = GET_SITE_TYPE_CCH(hProxySession);
    (*ppwstrProxyAddr)[i++] = chAddressTypeDelimiter;
    lstrcpyW(&((*ppwstrProxyAddr)[i]), pwstrProxyBuffer);
    i += cchProxyBuffer;
    (*ppwstrProxyAddr)[i++] = chDomainDelimiter;
    lstrcpyW(&((*ppwstrProxyAddr)[i]), GET_SITE_DOMAIN(hProxySession));

cleanup:

    return rc;

}

//$--RcUpdateProxy--------------------------------------------------------------
//  Updates a given proxy based on given the old/new site proxy.
//
//  Separately decides to update the user part and the domain part of the
//  proxy depending on which part of the site proxy has changed - the format
//  part or the domain part or both. If the proxy is to be changed then it
//  returns fDomainMatch == TRUE and the updated proxy. If the proxy should not
//  be updated then it returns fDomainMatch = FALSE and the pwstrProxy parameter
//  should be ignored.
//
//  This allows for selective updating of proxies; it's main goal is to attempt
//  to only update proxies which have been previously automatically generated
//  and not to lose manual edits previously made by an administrator.
//
//  Example 1: Domain has changed.
//  Old Site Proxy: %g@acme.com
//  New Site Proxy: %g@acmetool.com
//  User Proxies: Alice@acme.com, Bob@acme.com, Carol@otherco.com
//  In this example, RcUpdateProxy would be called 3 times, once for each user.
//  For this case, we want to update Alice and Bob's proxies but not Carol's so
//  we return for each of the three calls:
//     fDomainMatch     pwstrProxy
//  1. TRUE             Alice@acmetool.com
//  2. TRUE             Bob@acmetool.com
//  3. FALSE            Carol@otherco.com
//
//  Example 2: Format string changed; acme is now too big to use only first names
//  Old Site Proxy: %g@acme.com
//  New Site Proxy: %g.%s@acme.com
//  User Proxies: alice@acme.com, bob@acme.com, TJones@acme.com
//  For this case, we again want to update Alice and Bob's proxies since the
//  user part of their current proxies still match what would have been
//  generated with the old site proxy. However, the address for TJones must
//  have been manually entered and we do not want to change it to the new
//  format. So the returns are:
//     fDomainMatch     pwstrProxy
//  1. TRUE             AliceN@acme.com
//  2. TRUE             BobB@acme.com
//  3. FALSE            TJones@acme.com
//
// If both the format part and domain part of the site proxy have changed then
// both cases are checked and evaluated separately and fDomainMatch is set to
// TRUE if any part of the proxy has changed.
// -----------------------------------------------------------------------------
RC RcUpdateProxy(                   // RETURNS: RC_SUCCESS RC_MEMORY RC_ERROR RC_NOTFOUND
    IN HANDLE hProxySession,            // Handle to proxy sesssion.
    IN PRECIPIENTINFO pRecipientInfo,
    IN LPWSTR pwstrOldSiteProxy,        // Old site proxy
    IN LPWSTR pwstrNewSiteProxy,        // New site proxy
    IN OUT LPWSTR pwstrProxy,      // Proxy to modify, buffer is of
                                        //   cchMaxProxyValue size
IN VOID * pUnused)// Unused
{
    RC      rc = RC_SUCCESS;
    WCHAR   pwstrBuf[cchMaxProxyValue] = {L'\0'};   // working buffer for final proxy
    ULONG   iBuf            = 0;
    WCHAR   pwstrUserBuf[cchMaxProxyValue];
    ULONG   cchUserBuf;
    LPWSTR  pwstrTypeOSP    = NULL; // address type part of pwstrOldSiteProxy
    LPWSTR  pwstrTypeP      = NULL; //                      pwstrProxy
    LPWSTR  pwstrTypeNSP    = NULL; //                      pwstrNewSiteProxy
    LPWSTR  pwstrUserOSP    = NULL; // user/format part of pwstrOldSiteProxy
    LPWSTR  pwstrUserP      = NULL; //                     pwstrProxy
    LPWSTR  pwstrUserNSP    = NULL; //                     pwstrNewSiteProxy
    LPWSTR  pwstrDomainOSP  = NULL; // domain part of pwstrOldSiteProxy
    LPWSTR  pwstrDomainP    = NULL; //                pwstrProxy
    LPWSTR  pwstrDomainNSP  = NULL; //                pwstrNewSiteProxy

    // separate out the parts we need of the input parameters
    // ------------------------------------------------------

    // separate pwstrOldSiteProxy into type, user format and domain by temp
    // replacing : and @ with \0

    pwstrTypeOSP = pwstrOldSiteProxy;

    pwstrUserOSP = wcschr(pwstrOldSiteProxy, chAddressTypeDelimiter);

    if (pwstrUserOSP == NULL)
    {
        // internal error - we are getting a site proxy string which does not start
        // ADDRTYPE: (this should never happen)
LogError(
    GET_SERVER(hProxySession),
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    *pwstrUserOSP = 0;
    pwstrUserOSP++;

    pwstrDomainOSP = wcschr(pwstrUserOSP, chDomainDelimiter);

    if (pwstrDomainOSP)
    {
        *pwstrDomainOSP = 0;
        pwstrDomainOSP++;
    }

    // separate pwstrNewSiteProxy into type, user format and domain by temp
    // replacing : and @ with \0

    pwstrTypeNSP = pwstrNewSiteProxy;

    pwstrUserNSP = wcschr(pwstrNewSiteProxy, chAddressTypeDelimiter);

    if (pwstrUserNSP == NULL)
    {
        // internal error - we are getting a site proxy string which does not start
        // ADDRTYPE: (this should never happen)
LogError(
    GET_SERVER(hProxySession),
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    *pwstrUserNSP = 0;
    pwstrUserNSP++;

    pwstrDomainNSP = wcschr(pwstrUserNSP, chDomainDelimiter);

    if (pwstrDomainNSP)
    {
        *pwstrDomainNSP = 0;
        pwstrDomainNSP++;
    }

    // separate pwstrProxy into type, user and domain by temporarily replacing
    // @ and : with \0

    pwstrTypeP = pwstrProxy;

    pwstrUserP = wcschr(pwstrProxy, chAddressTypeDelimiter);

    if (pwstrUserP == NULL)
    {
        // internal error - we are getting a site proxy string which does not start
        // ADDRTYPE: (this should never happen)
LogError(
    GET_SERVER(hProxySession),
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    *pwstrUserP = 0;
    pwstrUserP++;

    pwstrDomainP = wcschr(pwstrUserP, chDomainDelimiter);

    if (pwstrDomainP)
    {
        *pwstrDomainP = 0;
        pwstrDomainP++;
    }

    // Do the address types match?

    if ((lstrcmpiW(pwstrTypeOSP, pwstrTypeP)) || (lstrcmpiW(pwstrTypeOSP, pwstrTypeNSP)))
    {
        // internal error - the address types do not match; again, this should
        // never occur.
LogError(
    GET_SERVER(hProxySession),
    iesProxyInternalFailure,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        rc = RC_ERROR;
        goto cleanup;
    }

    // Start building the new proxy; however leave *pfNoDomainMatch == TRUE
    // until we know we have made a significate change
    // --------------------------------------------------------------------

    // Copy in the address type and ":"

    lstrcpyW(pwstrBuf, pwstrTypeP);
    iBuf = lstrlenW(pwstrTypeP);
    pwstrBuf[iBuf++] = chAddressTypeDelimiter;

    // if the user part of the proxy that we would generate from pRecipientInfo
    // and pwstrOldSiteProxy == the user part of pwstrProxy (if its != then it
    // must have been manually edited for this user and we don't want to touch it)
    // then generate a new user part using pwstrNewSiteProxy and pRecipientInfo.

    // generate the user part of the proxy from the user part of the old domain
    // proxy

    if (pwstrUserOSP[0] == 0)
    {
        rc = RcGenerateUserProxy(
            pszDefaultFormat,
            GET_SERVER(hProxySession),
            pRecipientInfo,
            0,
            pwstrUserBuf,
            &cchUserBuf);
    }
    else
    {
        rc = RcGenerateUserProxy(
            pwstrUserOSP,
            GET_SERVER(hProxySession),
            pRecipientInfo,
            0,
            pwstrUserBuf,
            &cchUserBuf);
    }

    if (RC_FAILED(rc))
    {
        // RcGenerateUserProxy generates own log entry
        // just pass error through
        goto cleanup;
    }

    // See how this compares to actual user part of current proxy

    if (lstrcmpiW(pwstrUserBuf, pwstrUserP) == 0)
    {
        // go ahead and get the updated the user part

        if (pwstrUserNSP[0] == 0)
        {
            rc = RcGenerateUserProxy(
                pszDefaultFormat,
                GET_SERVER(hProxySession),
                pRecipientInfo,
                0,
                pwstrUserBuf,
                &cchUserBuf);
        }
        else
        {
            rc = RcGenerateUserProxy(
                pwstrUserNSP,
                GET_SERVER(hProxySession),
                pRecipientInfo,
                0,
                pwstrUserBuf,
                &cchUserBuf);
        }

        if (RC_FAILED(rc))
        {
            goto cleanup;
        }

        // copy the new user part onto the final result

        lstrcpyW(&(pwstrBuf[iBuf]), pwstrUserBuf);
        iBuf += cchUserBuf;
    }
    else
    {
        // don't change the user part, just recopy the original
        // user part

        lstrcpyW(&(pwstrBuf[iBuf]), pwstrUserP);
        iBuf += lstrlenW(pwstrUserP);
    }

    // Always replace the domain part of the site proxy.  Add the domain
    // part of the site proxy the proxy if there is anything to add
    // -----------------------------------------------------------------

    if (pwstrDomainNSP)
    {
        pwstrBuf[iBuf++] = chDomainDelimiter;
        lstrcpyW(&(pwstrBuf[iBuf]), pwstrDomainNSP);
    }

    // Always copy the final result to the output buffer
    // -------------------------------------------------

    lstrcpyW(pwstrProxy, pwstrBuf);

cleanup:

    // Restore the : and @ characters in the munged strings

    if (pwstrUserOSP)
    {
        *(pwstrUserOSP-1) = chAddressTypeDelimiter;
    }
    if (pwstrDomainOSP)
    {
        *(pwstrDomainOSP-1) = chDomainDelimiter;
    }

    if (pwstrUserNSP)
    {
        *(pwstrUserNSP-1) = chAddressTypeDelimiter;
    }
    if (pwstrDomainNSP)
    {
        *(pwstrDomainNSP-1) = chDomainDelimiter;
    }

    return rc;
}

//$--RcValidateProxy------------------------------------------------------------
//  Verifies that a manually entered proxy is at least minimally valid.
// -----------------------------------------------------------------------------
RC RcValidateProxy(             // RETURNS: RC_SUCCESS  RC_MEMORY RC_ERROR
                                //          RC_IMPLEMENTATION
    IN HANDLE hProxySession,    // Handle to the proxy session previously
                                // returned by rcProxuInit.
    IN LPWSTR pwstrProxyAddr,   // The foreign format proxy address to be
                                // validated.
    OUT BOOL *pfValid)          // Recieves TRUE if valid, FALSE otherwise.
                                // Undefined if other than RC_SUCCESS returned
                                // by function.
{
    UINT    i,
            cch = 0;

    // assume address is valid until proven otherwise

    *pfValid = TRUE;

    // The only proxy validation done here is to look for characters < 33 or
    // > 127. Obviously more should be done as more characters are illegal.

    // Could also do some sort of content checking to verify whether the content
    // (i.e. the domain part) is not only legal but makes sense (this is harder).

    cch = lstrlenW(pwstrProxyAddr);

    for (i = 0; i < cch; i++)
    {
        if ((pwstrProxyAddr[i] < 33) || (pwstrProxyAddr[i] > 127))
        {

            // Log error so user knows why proxy address was rejected

       LPWSTR rgwsz[] =
    {
    pwstrProxyAddr
    };

        LogError(
            GET_SERVER(hProxySession),
            iesInvalidCharacter,
            EVENTLOG_ERROR_TYPE,
            rgwsz,
            1);

            *pfValid = FALSE;
            break;
        }
    }

    return RC_SUCCESS;
}

//$--RcValidateSiteProxy--------------------------------------------------------
// Given a Unicode string, it validates that this string is a valid site address.
// This is called to verify that manually generated site proxies are legal.
// For this implementation, it is assumed the site proxy is legal if:
// - We can split it into it's component parts
// - The format and domain parts contain only chars in the 33-127 range
// -----------------------------------------------------------------------------
RC RcValidateSiteProxy(         // RETURNS: RC_SUCCESS  RC_MEMORY RC_ERROR
                                //          RC_IMPLEMENTATION
    IN HANDLE hProxySession,    // Handle to the proxy session previously
                                // returned by rcProxuInit.
    IN LPWSTR pwstrSiteProxy,   // The foreign format site proxy address to be
                                // validated.
    OUT BOOL *pfValid)        // Recieves TRUE if valid, FALSE otherwise.
                                // Undefined if other than RC_SUCCESS returned
                                // by function.
{
    RC      rc          = RC_SUCCESS;
LPWSTR  pszServer   = GET_SERVER(hProxySession);
    LPWSTR pszType      = NULL;
    ULONG  cchType      = 0;
    LPWSTR pszFormat    = NULL;
    ULONG  cchFormat    = 0;
    LPWSTR pszDomain    = NULL;
    ULONG  cchDomain    = 0;
    LPWSTR pszAt        = NULL;
    LPWSTR pszColon     = NULL;
    BOOL   fEscape      = FALSE;
    ULONG  i;

    // Initialize output parameter

    *pfValid = TRUE;

    // The site proxy must be available so check this first

    if (pwstrSiteProxy == NULL)
    {
LogError(
    pszServer,
    iesNoSiteProxy,
    EVENTLOG_ERROR_TYPE,
    NULL,
    0);
        *pfValid = FALSE;
        goto exit;
    }

    // Trim blanks from pwstrSiteProxy

    TrimW(pwstrSiteProxy);

    // Validate pwstrSiteProxy, must be "TYPE:x@y" where x is 0 or more chars
    // up to the first colon and y is at least 1 char

    cchDomain = lstrlenW(pwstrSiteProxy);
    pszAt = wcschr(pwstrSiteProxy, chDomainDelimiter);
    pszColon = wcschr(pwstrSiteProxy, chAddressTypeDelimiter);

    if ((cchDomain < 4) ||  // min size = T:@y
        (pszColon == NULL) ||       // no colon
        (*pszColon != chAddressTypeDelimiter) ||
        (pwstrSiteProxy[0] == chAddressTypeDelimiter))
    {
        // pwstrSiteProxy is invalid

   LPWSTR rgwsz[] =
{
pwstrSiteProxy
};

LogError(
    pszServer,
    iesBadSiteProxy,
    EVENTLOG_ERROR_TYPE,
    rgwsz,
    1);

        *pfValid = FALSE;
        goto exit;
    }

    // Split site proxy into it's component parts
    // ------------------------------------------

    // replace : with 0 temporarily

    *pszColon = 0;

    // get address type

    cchType = lstrlenW(pwstrSiteProxy);

    pszType = malloc((cchType + 1) * sizeof(WCHAR));

    if (pszType == NULL)
    {
        rc = RC_MEMORY;
        goto exit;
    }

    lstrcpyW(pszType, pwstrSiteProxy);

    // Restore ':'

    *pszColon = chAddressTypeDelimiter;

    // Check site proxy again for more validation

    if ((pwstrSiteProxy[cchType] != chAddressTypeDelimiter) ||
        (pszAt == NULL) ||
        (*pszAt != chDomainDelimiter))
    {
        // pwstrSiteProxy is invalid

   LPWSTR rgwsz[] =
{
pwstrSiteProxy
};

LogError(
    pszServer,
    iesBadSiteProxy,
    EVENTLOG_ERROR_TYPE,
    rgwsz,
    1);

        *pfValid = FALSE;
        goto exit;
    }

    // replace @ with 0 temporarily

    *pszAt = 0;

    // get format part

    cchFormat = lstrlenW(&(pwstrSiteProxy[cchType+1]));

    if (cchFormat == 0)
    {
        // use default format

        pszFormat = malloc((lstrlenW(pszDefaultFormat) + 1) * sizeof(WCHAR));

        if (pszFormat == NULL)
        {
            rc = RC_MEMORY;
            goto exit;
        }

        lstrcpyW(pszFormat, pszDefaultFormat);
    }
    else
    {
        // pull format out of site proxy

        pszFormat = malloc((cchFormat+1)*sizeof(WCHAR));

        if (pszFormat == NULL)
        {
            rc = RC_MEMORY;
            goto exit;
        }

        lstrcpyW(pszFormat, &(pwstrSiteProxy[cchType])+1);
    }

    // get domain part

    cchDomain = lstrlenW(pszAt+1);
    pszDomain = malloc((cchDomain+1)*sizeof(WCHAR));

    if (pszDomain == NULL)
    {
        rc = RC_MEMORY;
        goto exit;
    }

    lstrcpyW(pszDomain, (pszAt+1));

    // Restore '@'

    *pszAt = chDomainDelimiter;

    // We've now split up the site proxy which is the minumum needed to be
    // able to call Generate, etc. Now validate the pieces

    // validate address type
    // nothing to do, this is not user editable so we won't bother to check it

    // validate format string
    // Just check that char's after % are valid and that rest of chars
    // are in 33 to 127 range.  More could be done but GenerateProxy will
    // take care of that.

    for (i = 0; i < cchFormat; i++)
    {

        if (pszFormat[i] == '%')
        {
            i++;
            if ((i >= cchFormat) || (wcschr(L"sgdmr%", pszFormat[i]) == NULL))
            {
           LPWSTR rgwsz[] =
        {
        pszFormat
        };

        LogError(
            pszServer,
        iesInvalidFormatString,
        EVENTLOG_ERROR_TYPE,
        rgwsz,
        1);

                *pfValid = FALSE;
                goto exit;
            }
        }
        else if ((pszFormat[i] < 33) || (pszFormat[i] > 127))
        {
       LPWSTR rgwsz[] =
    {
    pszFormat
    };

        LogError(
            pszServer,
            iesInvalidFormatString,
            EVENTLOG_ERROR_TYPE,
            rgwsz,
            1);

            *pfValid = FALSE;
            goto exit;
        }

    } // for

    // validate domain part
    // just check for characters < 33 or > 127

    for (i = 0; i < cchDomain; i++)
    {

        if ((pszDomain[i] < 33) || (pszDomain[i] > 127))
        {
       LPWSTR rgwsz[] =
    {
    pwstrSiteProxy
    };

        LogError(
            pszServer,
            iesBadSiteProxy,
            EVENTLOG_ERROR_TYPE,
            rgwsz,
            1);

            *pfValid = FALSE;
            goto exit;
        }

    } // for

exit:

    FREE(pszType);
    FREE(pszFormat);
    FREE(pszDomain);

    return rc;
}

//$--FreeProxy------------------------------------------------------------------
//  Frees the memory previously allocated by GenerateProxy for a proxy address
//  string.
//------------------------------------------------------------------------------
VOID FreeProxy(
    IN LPWSTR pszProxy)   // the proxy string to free  May be NULL.
{
    FREE(pszProxy);
}

//$--CloseProxies---------------------------------------------------------------
//  Closes a proxy generation session and frees any associated data.  The DLL
//  can now be unloaded.
// -----------------------------------------------------------------------------
VOID CloseProxies(
    IN HANDLE hProxySession)    //  Handle to proxy session.
{
    if (hProxySession)
    {
        FREE(GET_SITE_FORMAT(hProxySession));
        FREE(GET_SITE_DOMAIN(hProxySession));
        GET_SITE_DOMAIN_CCH(hProxySession) = 0;
        FREE(hProxySession);
    }

    return;
}

//$--rcScanAndAppend------------------------------------------------------------
//  For each character in pszSource, it maps it to a (possibly) new character
//  using sAnsiToSMTPMap (chars > MAX_TRANSLATE_TABLE or which map to 0 are
//  deleted) then appends it to pszDest at position iDest.  pszDest and iDest
//  are updated. If the location to be written is >= iMaxDest, then RC_ERROR is
//  returned.
// -----------------------------------------------------------------------------
RC rcScanAndAppend(IN char *        sAnsitoSMTPMap, // char replacement table
                   IN LPWSTR        pszSource,      // source buffer
                   IN OUT LPWSTR    pszDest,        // dest buffer
                   IN OUT ULONG *   piDest,         // current index in dest buffer
                   IN ULONG         iMaxDest)       // max dest buffer index
{
    RC      rc          = RC_SUCCESS; // only error paths set

ULONG   cchSource;
    ULONG   i;

    // How big is source string?

    cchSource = lstrlenW(pszSource);

    if ((cchSource <= 0) || (pszSource == NULL))
    {
        // nothing to append; just return success
        goto cleanup;
    }

    // Iterate through source string and add it

    for (i = 0; i < cchSource; i++)
    {
        if ((pszSource[i] >= 0) &&
            (pszSource[i] <= MAX_TRANSLATE_TABLE) &&
            (sAnsitoSMTPMap[pszSource[i]] != 0))
        {
            // add translated character

            if (*piDest < iMaxDest)
            {
                pszDest[(*piDest)++] = sAnsitoSMTPMap[pszSource[i]];
            }
            else
            {
                // won't fit; return error
                rc = RC_ERROR;
                goto cleanup;
            }

        }
        // else skip it

     } // for

     // append final null

     pszDest[*piDest] = 0;

cleanup:
    return rc;
}

//$--TrimW----------------------------------------------------------------------
// Trims leading blanks from the begining and end of the string.
// -----------------------------------------------------------------------------
static VOID TrimW(              // RETURNS nothing (can't fail)
    IN OUT PWSTR pwstrSrc)      // String to be trimmed
{
    PWSTR pwstrDest = NULL;
    INT i = 0;

    if (pwstrSrc == NULL)
        goto cleanup;

    // trim leading

    pwstrDest = pwstrSrc;
    while ((pwstrSrc!=L'\0') && fIsWhiteSpaceW(*pwstrSrc))
    {
        pwstrSrc++;
    }

    // copy rest

    while (*pwstrSrc!=L'\0')
    {
        *pwstrDest=*pwstrSrc;
        pwstrDest++;
        pwstrSrc++;
        i++;
    }
    *pwstrDest = L'\0';

    // trim trailing

    if (i>0)
    {
        pwstrDest--;
        i--;

        while ((i>0) && (fIsWhiteSpaceW(*pwstrDest)))
        {
            *pwstrDest=L'\0';
            i--;
        }
    }

cleanup:
    return;
}

//$--fIsWhiteSpaceW-------------------------------------------------------------
// Determines if character is a white space (space or tab).
// -----------------------------------------------------------------------------
static BOOL fIsWhiteSpaceW(     // RETURNS:  TRUE if whitespace, FALSE o/w
    WCHAR wc)                   // character to test.
{
    BOOL bRes = TRUE;

    if (wc == L' ')
        goto cleanup;

    if (wc == L'\t')
        goto cleanup;

    bRes = FALSE;

cleanup:
    return bRes;
}

//$--LogError-------------------------------------------------------------------
// Logs an error to the application log in case of a proxy generation failure
// -----------------------------------------------------------------------------
VOID LogError(LPWSTR pszServer, IES ies, WORD wEventType, LPWSTR rgwsz[], WORD cwsz)
{
HANDLE h;

h = RegisterEventSourceW(pszServer, pszEventSource);
if (h != NULL)
{
ReportEventW(
h,
wEventType,
0,
ies,
NULL,
cwsz,
0,
rgwsz,
NULL);
DeregisterEventSource(h);
}
}


BINARRAY.CPP

// -----------------------------------------------------------------------------
// BinArray.cpp: Implements methods that wraps the MAPI SBinaryArray structure.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "BinArray.h"
#include "BinArray.chk"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// $--CBinArray::CBinArray()----------------------------------------------------
// CONSTRUCTOR: Start off with an empty array.
// -----------------------------------------------------------------------------

CBinArray::CBinArray( ULONG AllocUnits)
{
    m_SBA.cValues = 0;
    m_SBA.lpbin = NULL;

    m_Size = 0;
    m_AllocUnits = AllocUnits;
    m_pCurBin = NULL;
}

// $--CBinArray::~CBinArray()---------------------------------------------------
// DESTRUCTOR: Free all allocated memory.
// -----------------------------------------------------------------------------

CBinArray::~CBinArray()
{
    ULONG cValues = m_SBA.cValues;
    LPSBinary pBin = m_SBA.lpbin;

    if( cValues)
        ASSERTERROR( pBin != NULL, "m_SBA.lpbin should point to something.");

    while( cValues)
    {   // Delete each byte array pointer.

        if( pBin->lpb)
            delete [] pBin->lpb;

        // Move pointer to next one.
        cValues --;
        pBin ++;
    }

    // Clear this just incase someone else was hanging on to a pointer to it.
    m_SBA.cValues = 0;
    m_SBA.lpbin = NULL;
}

// $--CBinArray::HrAdd()--------------------------------------------------------
// Add binary data to the array.  Returns TRUE if data was added.
// -----------------------------------------------------------------------------

BOOL CBinArray::bAdd(
    ULONG cb,       // Count of byte array passed in.
    LPBYTE pBytes)  // Ptr to array of bytes.
{
    DEBUGPUBLIC( "CBinArray::bAdd()\n");
    if( FAILED( CHK_CBinArray_bAdd( cb, pBytes)))
        return( FALSE);

    if( m_SBA.cValues > m_Size)
    {
        HR_LOG( E_FAIL); // Corruption of m_SBA.cValues, it's too large.
        return( FALSE);
    }

    // Have we filled the existing array?
    if( m_SBA.cValues == m_Size)
    {   // YES, so expand it.
        if( !bExpand())
            return( FALSE);
    }

    // Allocate memory to hold binary data.
    m_pCurBin->lpb = new BYTE[ cb];
    if( !m_pCurBin->lpb)
        return( FALSE);

    // Make a copy of binary data.
    m_pCurBin->cb = cb;
    memcpy( m_pCurBin->lpb, pBytes, cb);

    // Increment the count and the current pointer.
    m_SBA.cValues ++;
    m_pCurBin ++;

    return( TRUE);
}

// -----------------------------------------------------------------------------
// Expand the array by m_AllocUnits.
// -----------------------------------------------------------------------------

BOOL CBinArray::bExpand()
{
    ULONG OldSize = m_Size;

    if( !CDynamicArray< SBinary>::bExpand( m_AllocUnits))
        return( FALSE);

    // Adjust working pointers.
    m_SBA.lpbin = m_ptr;
    m_pCurBin = m_ptr + OldSize;

    return( TRUE);
}

// -----------------------------------------------------------------------------


BINARRAY.H

// -----------------------------------------------------------------------------
// BinArray.h: Wrapper class for a mapi SBinaryArray.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _BINARRAY_H_
#define _BINARRAY_H_

#include "DynArray.h"

// -----------------------------------------------------------------------------
// Wraps a MAPI SBinaryArray structure and provides methods for creating it,
// filling it, and cleaning up when it goes out of scope.
// -----------------------------------------------------------------------------

class CBinArray : CDynamicArray< SBinary>
{
public:
    // A MAPI SBinaryArray.  Don't do anything with this except pass it to MAPI
    // functions.  This class will do the maintenance on it.
    SBinaryArray    m_SBA;

    // CONSTRUCTOR: Start off with an empty array.
    CBinArray( ULONG AllocUnits = 10);

    // DESTRUCTOR: Free all allocated memory.
    ~CBinArray();

    // Add binary data to the array.
    BOOL bAdd(
        ULONG cb,       // Count of byte array passed in.
        LPBYTE pBytes); // Ptr to array of bytes.

    // Returns a pointer to the SBinaryArray by just specifying the object.
    operator SBinaryArray*() {return( &m_SBA);}

protected:
    ULONG           m_AllocUnits;   // Number of units for each array expansion.
    LPSBinary       m_pCurBin;      // Ptr to next SBinary to fill up with data.

    // Expand the array by m_AllocUnits.
    BOOL bExpand();
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif // _BINARRAY_H_


DELMSGDL.CPP

// delmsgdl.cpp : implementation file
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.

#include "edkafx.h"
// add additional includes here
#include "delmsgdl.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CDelMsgDlg dialog

CDelMsgDlg::CDelMsgDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDelMsgDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CDelMsgDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}

void CDelMsgDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDelMsgDlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CDelMsgDlg, CDialog)
//{{AFX_MSG_MAP(CDelMsgDlg)
ON_BN_CLICKED(IDC_CMD_DELETE_BY_GATEWAY, OnCmdDeleteByGateway)
ON_BN_CLICKED(IDC_CMD_DELETE_NOW, OnCmdDeleteNow)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDelMsgDlg message handlers

void CDelMsgDlg::OnCmdDeleteByGateway()
{
    EndDialog( IDC_CMD_DELETE_BY_GATEWAY);
}

void CDelMsgDlg::OnCmdDeleteNow()
{
EndDialog( IDC_CMD_DELETE_NOW);
}


DELMSGDL.H

// delmsgdl.h : header file
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.

/////////////////////////////////////////////////////////////////////////////
// CDelMsgDlg dialog
#include "resource.h"

class CDelMsgDlg : public CDialog
{
// Construction
public:
CDelMsgDlg(CWnd* pParent = NULL);   // standard constructor

// Dialog Data
//{{AFX_DATA(CDelMsgDlg)
enum { IDD = IDD_DELETE_MESSAGE };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDelMsgDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:

// Generated message map functions
//{{AFX_MSG(CDelMsgDlg)
afx_msg void OnCmdDeleteByGateway();
afx_msg void OnCmdDeleteNow();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


DYNARRAY.H

// -----------------------------------------------------------------------------
// DynArray.h: Template Class that allows you to make a dynamic array of almost
//             any type or class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _DYNARRAY_H_
#define _DYNARRAY_H_

// -----------------------------------------------------------------------------
// Use this class to create an array of almost any type or class.  The exception
// is with classes that have construction that does not initialize all data to
// zero, classes that need destructors, or classes that can not be binary copied.
//
// Once the object is created usage of it is nearly identical to using a regular
// array of that type or class.  See example of usage at the end of this file.
//
// NOTE: When passing an object of CDynamicArray< type> as a parameter for a
//       variable argument list you must type cast it.  This is different usage
//       from when the parameter is fixed and the compiler knows the type.
//       See and example of this at the very end of this file.
//
// -----------------------------------------------------------------------------

template< class TYPE>
class CDynamicArray
{
public:
    // CONSTRUCTOR: Dynamicly creates an array.
    CDynamicArray( ULONG nCnt = 0);

    // DESTRUCTOR: Frees the memory that was allocated.
    ~CDynamicArray() {Delete();}

    // Returns a pointer to the TYPE by just specifying the object.
    operator TYPE*() {return( m_ptr);}

    // Frees the memory that was allocated.
    void Delete();

    // Frees the memory that was allocated and then allocates a new block.
    void New( ULONG nCnt);

    // Expand the array by nCnt.  Return TRUE if it was expanded.
    BOOL bExpand( ULONG nCnt);

protected:
    TYPE*   m_ptr;  // Pointer to the array you ordered.
    ULONG   m_Size; // Number of items allocated.
};

// $--CDynamicArray< TYPE>::CDynamicArray()-------------------------------------
// CONSTRUCTOR: Dynamicly creates an array.
// -----------------------------------------------------------------------------

template< class TYPE>
CDynamicArray< TYPE>::CDynamicArray(
    ULONG nCnt)     // Initial size of array.  It is okay to pass zero.
{
    m_ptr = NULL;
    m_Size = 0;
    New( nCnt);
}

// $--CDynamicArray< TYPE>::Delete()--------------------------------------------
// Frees the memory that was allocated.
// -----------------------------------------------------------------------------

template< class TYPE>
void CDynamicArray< TYPE>::Delete()
{
    if( m_ptr)
    {
        delete [] m_ptr;
        m_ptr = NULL;
        m_Size = 0;
    }
}

// $--CDynamicArray< TYPE>::New()-----------------------------------------------
// Frees the memory that was allocated and then allocates a new block.
// NOTE: If you already had copy of a pointer to this array you must reload it.
// -----------------------------------------------------------------------------

template< class TYPE>
void CDynamicArray< TYPE>::New(
    ULONG nCnt)     // Initial size of array.  It is okay to pass zero.
{
    Delete();   // If there was data delete it.
    bExpand( nCnt);
}

// $--CDynamicArray< TYPE>::bExpand()-------------------------------------------
// Expand the array by nCnt.  Return TRUE if it was expanded.
// NOTE: If you already had copy of a pointer to this array you must reload it.
// -----------------------------------------------------------------------------

template< class TYPE>
BOOL CDynamicArray< TYPE>::bExpand( // RETURNS: TRUE if successful.
    ULONG nCnt)                     // Amount to expand array by.
{
    if( !nCnt)
        return( TRUE);  // It is okay to pass zero.

    // Allocate the additonal memory.
    TYPE* pNew = new TYPE[ m_Size + nCnt];
    if( !pNew)
    {   // Could not allocate that much memory.
        HR_LOG( E_OUTOFMEMORY);
        return( FALSE);
    }

    // Is this the first allocation?
    if( m_ptr)
    {   // NO, so copy previous allocation into this block.
        memcpy( pNew, m_ptr, m_Size * sizeof( TYPE));
        delete [] m_ptr;
    }

    // Initialize new block of data to zero.
    memset( pNew + m_Size, 0, nCnt * sizeof( TYPE));

    // Adjust working pointers.
    m_ptr = pNew;
    m_Size += nCnt;

    return( TRUE);
}

// -----------------------------------------------------------------------------

#ifdef _EXAMPLES_
void TestCDynamicArray()
{
    CDynamicArray< char> Str( 20);      // Construct an array of 20 characters.
    CDynamicArray< int> IntArray( 3);   // Construct an array of 3 integers.

    if( !IntArray || !Str)
        return; // Could not allocate memory.

    strcpy( Str, "This is a dynamic test.");

    IntArray[0] = 1;
    IntArray[1] = 2;
    IntArray[2] = 3;

    IntArray.New( 6);       // Free old memory and reallocate 6 integers.
    if( !IntArray)
        return; // Could not allocate memory.

    IntArray[0] = 11;
    IntArray[1] = 12;
    IntArray[2] = 13;
    IntArray[3] = 14;
    IntArray[4] = 15;
    IntArray[5] = 16;

    // Expand array by 4 and bring it to a total size of 10.
    if( !IntArray.bExpand( 4))
        return; // Could not allocate memory.

    IntArray[6] = 17;
    IntArray[7] = 18;
    IntArray[8] = 19;
    IntArray[9] = 20;

    CDynamicArray< char> Buf( 100);     // Construct an array of 100 characters.
    if( !Buf)
        return; // Could not allocate memory.

    // NOTE: When passing an object of CDynamicArray< type> as a parameter for a
    //       variable argument list you must either type cast it.  This is different
    //       usage from when the parameter is fixed and the compiler knows the type.
    //
    //       Notice the difference in the way Buf is used and the way Str is used. The
    //       compiler knows the first parameter is a char* so it can use the opperator
    //       defined in our class to place m_ptr on the stack.  But in the case of Str
    //       The compiler does not know what the expected parameter is supposed to be
    //       so by default it will place the entire class on the stack.  In this case
    //       it would be m_ptr and m_Size.
    sprintf( Buf, "Look at some of the data. %s %d %d", (char*) Str, IntArray[3], IntArray[9]);
}

#endif _EXAMPLES_

// -----------------------------------------------------------------------------
#endif // _DYNARRAY_H_


FAILDMSG.CPP

// -----------------------------------------------------------------------------
// FaildMsg.cpp : Implements a class that keeps a failed status and displays
//                a message when the destructor is called and it has failed.
//
//                This is useful to functions that have many exit points that
//                all wish to display the same message if the function fails.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "faildmsg.h"

// -----------------------------------------------------------------------------

CMsgOnFail::CMsgOnFail( CString sMsg, CString sTitle)
{
    m_bFailed = TRUE;
    m_sMsg = sMsg;
    m_sTitle = sTitle;
}

// -----------------------------------------------------------------------------

CMsgOnFail::~CMsgOnFail()
{
    if( m_bFailed)
        ::MessageBox( NULL, m_sMsg, m_sTitle, MB_TASKMODAL);
}

// -----------------------------------------------------------------------------


FAILDMSG.H

// -----------------------------------------------------------------------------
// Init.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _INIT_H_
#define _INIT_H_

// -----------------------------------------------------------------------------
// This class holds a status of whether something has failed or not.  If it has
// Failed when the destructor is called a message is displayed.
//
// This defaults to FAILED.
// -----------------------------------------------------------------------------

class CMsgOnFail
{
public:
    CMsgOnFail( CString sMsg, CString sTitle = "Failure");
    ~CMsgOnFail();

    void Failed()     { m_bFailed = TRUE;}
    void Succeeded()  { m_bFailed = FALSE;}
    BOOL bHasFailed() { return( m_bFailed);}

protected:
    BOOL    m_bFailed;
    CString m_sMsg;
    CString m_sTitle;
};

// -----------------------------------------------------------------------------

#endif // _INIT_H_


GLOBAL.CPP

// -----------------------------------------------------------------------------
// Global.CPP -- Contains global object declarations.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#define _GLOBAL_OBJECTS_

// -----------------------------------------------------------------------------
// The purpose of this C++ file is to instantiate global objects in an order that
// can be depended upon.
//
// In C++, if you have a global object whose constructor depends upon another
// object having been constructed, you can run into problems with the randomness
// which the many object modules get linked.
//
// By instantiating all dependant objects in a single source module, construction
// will occur in the order defined.
//
// One way to do this is to instantiate the objects in the header file which they
// are declared in.  Surround the declarations by #ifdef _GLOBAL_OBJECTS_
// and #endif //_GLOBAL_OBJECTS_.  This will cause them to be defined only once
// in this module (Global.CPP).  Then include those header files below.
//
// Another way would be to include the header file which declare the objects
// needed and to then place the definition directly below the header file.  This
// is not as clean and is not recommended.
// -----------------------------------------------------------------------------

#include "ExAdmin.h"    // This header file must be listed here.
#include "queview.h"
#include "ErrCpp.h"     // This MUST be included so that CHRESULT works.


MAPIMSG.CPP

// -----------------------------------------------------------------------------
// MAPIMsg.cpp: Implements a class that opens a mapi message and does the
//              appropriate error checking.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "MAPIMsg.h"
#include "mapimsg.chk"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// $--CMAPIMessage::CMAPIMessage()----------------------------------------------
// CONSTRUCTOR: Opens the mapi message using a mapi container and does the
// appropriate error checking.  Releases the object if it is not a MAPI_MESSAGE.
// -----------------------------------------------------------------------------

CMAPIMessage::CMAPIMessage(
    LPMAPICONTAINER pMAPIContainer, // Containter to use to open message.
    ULONG cb,                       // Count of bytes in Entry Id.
    LPENTRYID pEID,                 // Entry Id of message to open.
    ULONG ulFlags)                  // Defaults to 0L, MAPI_MODIFY is a likely one to pass in.
{
    // NOTE: Initialization of members MUST be initialized before CHK function is called.
    m_pMsg = NULL;

    DEBUGPUBLIC( "CMAPIMessage::CMAPIMessage()\n");
    m_hr = CHK_CMAPIMessage_CMAPIMessage( pMAPIContainer, cb, pEID, ulFlags);
    if( FAILED( m_hr))
        return;

    ULONG ulObjType = 0;
    m_hr = pMAPIContainer->OpenEntry(
        cb, pEID, NULL, ulFlags|MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN*) &m_pMsg);
    if( FAILED( m_hr))
        return;

    if( ulObjType != MAPI_MESSAGE)
    {
        m_hr = HR_LOG( E_FAIL);
        ULRELEASE( m_pMsg);
    }
}

// -----------------------------------------------------------------------------


MAPIMSG.H

// -----------------------------------------------------------------------------
// MAPIMsg.h: Wrapper class for a mapi message interface pointer.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _MAPIMSG_H_
#define _MAPIMSG_H_

#include "ErrCpp.h"

// -----------------------------------------------------------------------------
// A wrapper class to contain an interface to a mapi message.  This contains the
// error checking and automatic release of the pointer upon destruction.
//
// Construct by passing a pointer to a mapi container (could be a folder), a
// count of bytes of the entry id, and the entry id.
//
// Test FAILED( m_hr) for validity of the object, and use m_pMsg to access
// the mapi message interface pointer.
// -----------------------------------------------------------------------------

class CMAPIMessage
{
public:
    // CONSTRUCTOR: Opens the mapi message using a mapi container and does the
    // appropriate error checking.  Releases the object if it is not a MAPI_MESSAGE.
    CMAPIMessage(
        LPMAPICONTAINER pMAPIContainer, // Containter to use to open message.
        ULONG cb,                       // Count of bytes in Entry Id.
        LPENTRYID pEID,                 // Entry Id of message to open.
        ULONG ulFlags = 0L);            // MAPI_MODIFY is a likely one to pass in.

    // DESTRUCTOR: Releases MAPI's IMessage interface pointer.
    ~CMAPIMessage()     {ULRELEASE( m_pMsg);}

    LPMESSAGE   m_pMsg; // Pointer to MAPI's IMessage interface.
    CHRESULT    m_hr;   // Contains the actual HRESULT from the OpenEntry().

    // Returns a pointer to the MAPI Message.
    operator LPMESSAGE() {return( m_pMsg);}

    // Returns a pointer to the MAPI Message for -> operations.
    LPMESSAGE operator ->() {return( m_pMsg);}
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif // _MAPIMSG_H_


PROPDEF.H

// -----------------------------------------------------------------------------
// PropDef.h:  This defines an array of mapi property ids and types.
//             Include this file only once in your code for the entire project.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _PROPDEF_H_
#define _PROPDEF_H_

// -----------------------------------------------------------------------------

static IDList PropIDs[] =
{
    PROP_ID( PR_ARRIVAL_TIME),    "PR_ARRIVAL_TIME",
    PROP_ID( PR_GW_ADMIN_OPERATIONS), "PR_GW_ADMIN_OPERATIONS",
    PROP_ID( PR_IMPORTANCE),  "PR_IMPORTANCE",
    PROP_ID( PR_ORIGINATOR_ADDR), "PR_ORIGINATOR_ADDR",
    PROP_ID( PR_ORIGINATOR_ADDRTYPE), "PR_ORIGINATOR_ADDRTYPE",
    PROP_ID( PR_ORIGINATOR_NAME), "PR_ORIGINATOR_NAME",
    PROP_ID( PR_SENSITIVITY), "PR_SENSITIVITY",
    PROP_ID( PR_DISPLAY_TO),  "PR_DISPLAY_TO",
    PROP_ID( PR_HASATTACH),   "PR_HASATTACH",
    PROP_ID( PR_MESSAGE_CLASS),   "PR_MESSAGE_CLASS",
    PROP_ID( PR_MESSAGE_SIZE),    "PR_MESSAGE_SIZE",
    PROP_ID( PR_PRIORITY),    "PR_PRIORITY",
    PROP_ID( PR_SUBJECT), "PR_SUBJECT",
    PROP_ID( PR_ACKNOWLEDGEMENT_MODE),    "PR_ACKNOWLEDGEMENT_MODE",
    PROP_ID( PR_ALTERNATE_RECIPIENT_ALLOWED), "PR_ALTERNATE_RECIPIENT_ALLOWED",
    PROP_ID( PR_AUTHORIZING_USERS),   "PR_AUTHORIZING_USERS",
    PROP_ID( PR_AUTO_FORWARD_COMMENT),    "PR_AUTO_FORWARD_COMMENT",
    PROP_ID( PR_AUTO_FORWARDED),  "PR_AUTO_FORWARDED",
    PROP_ID( PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID),    "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID",
    PROP_ID( PR_CONTENT_CORRELATOR),  "PR_CONTENT_CORRELATOR",
    PROP_ID( PR_CONTENT_IDENTIFIER),  "PR_CONTENT_IDENTIFIER",
    PROP_ID( PR_CONTENT_LENGTH),  "PR_CONTENT_LENGTH",
    PROP_ID( PR_CONTENT_RETURN_REQUESTED),    "PR_CONTENT_RETURN_REQUESTED",
    PROP_ID( PR_CONVERSATION_KEY),    "PR_CONVERSATION_KEY",
    PROP_ID( PR_CONVERSION_EITS), "PR_CONVERSION_EITS",
    PROP_ID( PR_CONVERSION_WITH_LOSS_PROHIBITED), "PR_CONVERSION_WITH_LOSS_PROHIBITED",
    PROP_ID( PR_CONVERTED_EITS),  "PR_CONVERTED_EITS",
    PROP_ID( PR_DEFERRED_DELIVERY_TIME),  "PR_DEFERRED_DELIVERY_TIME",
    PROP_ID( PR_DELIVER_TIME),    "PR_DELIVER_TIME",
    PROP_ID( PR_DISCARD_REASON),  "PR_DISCARD_REASON",
    PROP_ID( PR_DISCLOSURE_OF_RECIPIENTS),    "PR_DISCLOSURE_OF_RECIPIENTS",
    PROP_ID( PR_DL_EXPANSION_HISTORY),    "PR_DL_EXPANSION_HISTORY",
    PROP_ID( PR_DL_EXPANSION_PROHIBITED), "PR_DL_EXPANSION_PROHIBITED",
    PROP_ID( PR_EXPIRY_TIME), "PR_EXPIRY_TIME",
    PROP_ID( PR_IMPLICIT_CONVERSION_PROHIBITED),  "PR_IMPLICIT_CONVERSION_PROHIBITED",
    PROP_ID( PR_IMPORTANCE),  "PR_IMPORTANCE",
    PROP_ID( PR_IPM_ID),  "PR_IPM_ID",
    PROP_ID( PR_LATEST_DELIVERY_TIME),    "PR_LATEST_DELIVERY_TIME",
    PROP_ID( PR_MESSAGE_DELIVERY_ID), "PR_MESSAGE_DELIVERY_ID",
    PROP_ID( PR_MESSAGE_SECURITY_LABEL),  "PR_MESSAGE_SECURITY_LABEL",
    PROP_ID( PR_OBSOLETED_IPMS),  "PR_OBSOLETED_IPMS",
    PROP_ID( PR_ORIGINALLY_INTENDED_RECIPIENT_NAME),  "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME",
    PROP_ID( PR_ORIGINAL_EITS),   "PR_ORIGINAL_EITS",
    PROP_ID( PR_ORIGINATOR_CERTIFICATE),  "PR_ORIGINATOR_CERTIFICATE",
    PROP_ID( PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED),    "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED",
    PROP_ID( PR_ORIGINATOR_RETURN_ADDRESS),   "PR_ORIGINATOR_RETURN_ADDRESS",
    PROP_ID( PR_PARENT_KEY),  "PR_PARENT_KEY",
    PROP_ID( PR_PROOF_OF_SUBMISSION_REQUESTED),   "PR_PROOF_OF_SUBMISSION_REQUESTED",
    PROP_ID( PR_READ_RECEIPT_REQUESTED),  "PR_READ_RECEIPT_REQUESTED",
    PROP_ID( PR_RECEIPT_TIME),    "PR_RECEIPT_TIME",
    PROP_ID( PR_RECIPIENT_REASSIGNMENT_PROHIBITED),   "PR_RECIPIENT_REASSIGNMENT_PROHIBITED",
    PROP_ID( PR_REDIRECTION_HISTORY), "PR_REDIRECTION_HISTORY",
    PROP_ID( PR_RELATED_IPMS),    "PR_RELATED_IPMS",
    PROP_ID( PR_LANGUAGES),   "PR_LANGUAGES",
    PROP_ID( PR_REPLY_TIME),  "PR_REPLY_TIME",
    PROP_ID( PR_REPORT_TAG),  "PR_REPORT_TAG",
    PROP_ID( PR_REPORT_TIME), "PR_REPORT_TIME",
    PROP_ID( PR_RETURNED_IPM),    "PR_RETURNED_IPM",
    PROP_ID( PR_SECURITY),    "PR_SECURITY",
    PROP_ID( PR_INCOMPLETE_COPY), "PR_INCOMPLETE_COPY",
    PROP_ID( PR_SENSITIVITY), "PR_SENSITIVITY",
    PROP_ID( PR_SUBJECT_IPM), "PR_SUBJECT_IPM",
    PROP_ID( PR_CLIENT_SUBMIT_TIME),  "PR_CLIENT_SUBMIT_TIME",
    PROP_ID( PR_REPORT_NAME), "PR_REPORT_NAME",
    PROP_ID( PR_SENT_REPRESENTING_SEARCH_KEY),    "PR_SENT_REPRESENTING_SEARCH_KEY",
    PROP_ID( PR_X400_CONTENT_TYPE),   "PR_X400_CONTENT_TYPE",
    PROP_ID( PR_NON_RECEIPT_REASON),  "PR_NON_RECEIPT_REASON",
    PROP_ID( PR_RECEIVED_BY_ENTRYID), "PR_RECEIVED_BY_ENTRYID",
    PROP_ID( PR_RECEIVED_BY_NAME),    "PR_RECEIVED_BY_NAME",
    PROP_ID( PR_SENT_REPRESENTING_ENTRYID),   "PR_SENT_REPRESENTING_ENTRYID",
    PROP_ID( PR_SENT_REPRESENTING_NAME),  "PR_SENT_REPRESENTING_NAME",
    PROP_ID( PR_RCVD_REPRESENTING_ENTRYID),   "PR_RCVD_REPRESENTING_ENTRYID",
    PROP_ID( PR_RCVD_REPRESENTING_NAME),  "PR_RCVD_REPRESENTING_NAME",
    PROP_ID( PR_REPORT_ENTRYID),  "PR_REPORT_ENTRYID",
    PROP_ID( PR_READ_RECEIPT_ENTRYID),    "PR_READ_RECEIPT_ENTRYID",
    PROP_ID( PR_MESSAGE_SUBMISSION_ID),   "PR_MESSAGE_SUBMISSION_ID",
    PROP_ID( PR_PROVIDER_SUBMIT_TIME),    "PR_PROVIDER_SUBMIT_TIME",
    PROP_ID( PR_ORIGINAL_SUBJECT),    "PR_ORIGINAL_SUBJECT",
    PROP_ID( PR_DISC_VAL),    "PR_DISC_VAL",
    PROP_ID( PR_ORIG_MESSAGE_CLASS),  "PR_ORIG_MESSAGE_CLASS",
    PROP_ID( PR_ORIGINAL_AUTHOR_ENTRYID), "PR_ORIGINAL_AUTHOR_ENTRYID",
    PROP_ID( PR_ORIGINAL_AUTHOR_NAME),    "PR_ORIGINAL_AUTHOR_NAME",
    PROP_ID( PR_ORIGINAL_SUBMIT_TIME),    "PR_ORIGINAL_SUBMIT_TIME",
    PROP_ID( PR_REPLY_RECIPIENT_ENTRIES), "PR_REPLY_RECIPIENT_ENTRIES",
    PROP_ID( PR_REPLY_RECIPIENT_NAMES),   "PR_REPLY_RECIPIENT_NAMES",
    PROP_ID( PR_RECEIVED_BY_SEARCH_KEY),  "PR_RECEIVED_BY_SEARCH_KEY",
    PROP_ID( PR_RCVD_REPRESENTING_SEARCH_KEY),    "PR_RCVD_REPRESENTING_SEARCH_KEY",
    PROP_ID( PR_READ_RECEIPT_SEARCH_KEY), "PR_READ_RECEIPT_SEARCH_KEY",
    PROP_ID( PR_REPORT_SEARCH_KEY),   "PR_REPORT_SEARCH_KEY",
    PROP_ID( PR_ORIGINAL_DELIVERY_TIME),  "PR_ORIGINAL_DELIVERY_TIME",
    PROP_ID( PR_ORIGINAL_AUTHOR_SEARCH_KEY),  "PR_ORIGINAL_AUTHOR_SEARCH_KEY",
    PROP_ID( PR_MESSAGE_TO_ME),   "PR_MESSAGE_TO_ME",
    PROP_ID( PR_MESSAGE_CC_ME),   "PR_MESSAGE_CC_ME",
    PROP_ID( PR_MESSAGE_RECIP_ME),    "PR_MESSAGE_RECIP_ME",
    PROP_ID( PR_ORIGINAL_SENDER_NAME),    "PR_ORIGINAL_SENDER_NAME",
    PROP_ID( PR_ORIGINAL_SENDER_ENTRYID), "PR_ORIGINAL_SENDER_ENTRYID",
    PROP_ID( PR_ORIGINAL_SENDER_SEARCH_KEY),  "PR_ORIGINAL_SENDER_SEARCH_KEY",
    PROP_ID( PR_ORIGINAL_SENT_REPRESENTING_NAME), "PR_ORIGINAL_SENT_REPRESENTING_NAME",
    PROP_ID( PR_ORIGINAL_SENT_REPRESENTING_ENTRYID),  "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID",
    PROP_ID( PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY),   "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY",
    PROP_ID( PR_START_DATE),  "PR_START_DATE",
    PROP_ID( PR_END_DATE),    "PR_END_DATE",
    PROP_ID( PR_OWNER_APPT_ID),   "PR_OWNER_APPT_ID",
    PROP_ID( PR_RESPONSE_REQUESTED),  "PR_RESPONSE_REQUESTED",
    PROP_ID( PR_SENT_REPRESENTING_ADDRTYPE),  "PR_SENT_REPRESENTING_ADDRTYPE",
    PROP_ID( PR_SENT_REPRESENTING_EMAIL_ADDRESS), "PR_SENT_REPRESENTING_EMAIL_ADDRESS",
    PROP_ID( PR_ORIGINAL_SENDER_ADDRTYPE),    "PR_ORIGINAL_SENDER_ADDRTYPE",
    PROP_ID( PR_ORIGINAL_SENDER_EMAIL_ADDRESS),   "PR_ORIGINAL_SENDER_EMAIL_ADDRESS",
    PROP_ID( PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE), "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE",
    PROP_ID( PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS),    "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS",
    PROP_ID( PR_CONVERSATION_TOPIC),  "PR_CONVERSATION_TOPIC",
    PROP_ID( PR_CONVERSATION_INDEX),  "PR_CONVERSATION_INDEX",
    PROP_ID( PR_BODY),    "PR_BODY",
    PROP_ID( PR_REPORT_TEXT), "PR_REPORT_TEXT",
    PROP_ID( PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY), "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY",
    PROP_ID( PR_REPORTING_DL_NAME),   "PR_REPORTING_DL_NAME",
    PROP_ID( PR_REPORTING_MTA_CERTIFICATE),   "PR_REPORTING_MTA_CERTIFICATE",
    PROP_ID( PR_RTF_SYNC_BODY_CRC),   "PR_RTF_SYNC_BODY_CRC",
    PROP_ID( PR_RTF_SYNC_BODY_COUNT), "PR_RTF_SYNC_BODY_COUNT",
    PROP_ID( PR_RTF_SYNC_BODY_TAG),   "PR_RTF_SYNC_BODY_TAG",
    PROP_ID( PR_RTF_COMPRESSED),  "PR_RTF_COMPRESSED",
    PROP_ID( PR_RTF_SYNC_PREFIX_COUNT),   "PR_RTF_SYNC_PREFIX_COUNT",
    PROP_ID( PR_RTF_SYNC_TRAILING_COUNT), "PR_RTF_SYNC_TRAILING_COUNT",
    PROP_ID( PR_ORIGINALLY_INTENDED_RECIP_ENTRYID),   "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID",
    PROP_ID( PR_PREPROCESS),  "PR_PREPROCESS",
    PROP_ID( PR_CONTENT_INTEGRITY_CHECK), "PR_CONTENT_INTEGRITY_CHECK",
    PROP_ID( PR_EXPLICIT_CONVERSION), "PR_EXPLICIT_CONVERSION",
    PROP_ID( PR_IPM_RETURN_REQUESTED),    "PR_IPM_RETURN_REQUESTED",
    PROP_ID( PR_MESSAGE_TOKEN),   "PR_MESSAGE_TOKEN",
    PROP_ID( PR_NDR_REASON_CODE), "PR_NDR_REASON_CODE",
    PROP_ID( PR_NDR_DIAG_CODE),   "PR_NDR_DIAG_CODE",
    PROP_ID( PR_NON_RECEIPT_NOTIFICATION_REQUESTED),  "PR_NON_RECEIPT_NOTIFICATION_REQUESTED",
    PROP_ID( PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED),    "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED",
    PROP_ID( PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT),    "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT",
    PROP_ID( PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY),   "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY",
    PROP_ID( PR_PHYSICAL_DELIVERY_MODE),  "PR_PHYSICAL_DELIVERY_MODE",
    PROP_ID( PR_PHYSICAL_DELIVERY_REPORT_REQUEST),    "PR_PHYSICAL_DELIVERY_REPORT_REQUEST",
    PROP_ID( PR_PHYSICAL_FORWARDING_ADDRESS), "PR_PHYSICAL_FORWARDING_ADDRESS",
    PROP_ID( PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED),   "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED",
    PROP_ID( PR_PHYSICAL_FORWARDING_PROHIBITED),  "PR_PHYSICAL_FORWARDING_PROHIBITED",
    PROP_ID( PR_PHYSICAL_RENDITION_ATTRIBUTES),   "PR_PHYSICAL_RENDITION_ATTRIBUTES",
    PROP_ID( PR_PROOF_OF_DELIVERY),   "PR_PROOF_OF_DELIVERY",
    PROP_ID( PR_PROOF_OF_DELIVERY_REQUESTED), "PR_PROOF_OF_DELIVERY_REQUESTED",
    PROP_ID( PR_RECIPIENT_CERTIFICATE),   "PR_RECIPIENT_CERTIFICATE",
    PROP_ID( PR_RECIPIENT_NUMBER_FOR_ADVICE), "PR_RECIPIENT_NUMBER_FOR_ADVICE",
    PROP_ID( PR_RECIPIENT_TYPE),  "PR_RECIPIENT_TYPE",
    PROP_ID( PR_REGISTERED_MAIL_TYPE),    "PR_REGISTERED_MAIL_TYPE",
    PROP_ID( PR_REPLY_REQUESTED), "PR_REPLY_REQUESTED",
    PROP_ID( PR_REQUESTED_DELIVERY_METHOD),   "PR_REQUESTED_DELIVERY_METHOD",
    PROP_ID( PR_SENDER_ENTRYID),  "PR_SENDER_ENTRYID",
    PROP_ID( PR_SENDER_NAME), "PR_SENDER_NAME",
    PROP_ID( PR_TYPE_OF_MTS_USER),    "PR_TYPE_OF_MTS_USER",
    PROP_ID( PR_SENDER_SEARCH_KEY),   "PR_SENDER_SEARCH_KEY",
    PROP_ID( PR_SENDER_ADDRTYPE), "PR_SENDER_ADDRTYPE",
    PROP_ID( PR_SENDER_EMAIL_ADDRESS),    "PR_SENDER_EMAIL_ADDRESS",
    PROP_ID( PR_CURRENT_VERSION), "PR_CURRENT_VERSION",
    PROP_ID( PR_DELETE_AFTER_SUBMIT), "PR_DELETE_AFTER_SUBMIT",
    PROP_ID( PR_DISPLAY_BCC), "PR_DISPLAY_BCC",
    PROP_ID( PR_DISPLAY_CC),  "PR_DISPLAY_CC",
    PROP_ID( PR_PARENT_DISPLAY),  "PR_PARENT_DISPLAY",
    PROP_ID( PR_MESSAGE_DELIVERY_TIME),   "PR_MESSAGE_DELIVERY_TIME",
    PROP_ID( PR_MESSAGE_FLAGS),   "PR_MESSAGE_FLAGS",
    PROP_ID( PR_PARENT_ENTRYID),  "PR_PARENT_ENTRYID",
    PROP_ID( PR_SENTMAIL_ENTRYID),    "PR_SENTMAIL_ENTRYID",
    PROP_ID( PR_CORRELATE),   "PR_CORRELATE",
    PROP_ID( PR_CORRELATE_MTSID), "PR_CORRELATE_MTSID",
    PROP_ID( PR_DISCRETE_VALUES), "PR_DISCRETE_VALUES",
    PROP_ID( PR_RESPONSIBILITY),  "PR_RESPONSIBILITY",
    PROP_ID( PR_SPOOLER_STATUS),  "PR_SPOOLER_STATUS",
    PROP_ID( PR_TRANSPORT_STATUS),    "PR_TRANSPORT_STATUS",
    PROP_ID( PR_MESSAGE_RECIPIENTS),  "PR_MESSAGE_RECIPIENTS",
    PROP_ID( PR_MESSAGE_ATTACHMENTS), "PR_MESSAGE_ATTACHMENTS",
    PROP_ID( PR_SUBMIT_FLAGS),    "PR_SUBMIT_FLAGS",
    PROP_ID( PR_TRANSPORT_KEY),   "PR_TRANSPORT_KEY",
    PROP_ID( PR_MSG_STATUS),  "PR_MSG_STATUS",
    PROP_ID( PR_MESSAGE_DOWNLOAD_TIME),   "PR_MESSAGE_DOWNLOAD_TIME",
    PROP_ID( PR_CREATION_VERSION),    "PR_CREATION_VERSION",
    PROP_ID( PR_MODIFY_VERSION),  "PR_MODIFY_VERSION",
    PROP_ID( PR_BODY_CRC),    "PR_BODY_CRC",
    PROP_ID( PR_NORMALIZED_SUBJECT),  "PR_NORMALIZED_SUBJECT",
    PROP_ID( PR_RTF_IN_SYNC), "PR_RTF_IN_SYNC",
    PROP_ID( PR_ATTACH_SIZE), "PR_ATTACH_SIZE",
    PROP_ID( PR_ATTACH_NUM),  "PR_ATTACH_NUM",
    PROP_ID( PR_ORIGINAL_DISPLAY_BCC),    "PR_ORIGINAL_DISPLAY_BCC",
    PROP_ID( PR_ORIGINAL_DISPLAY_CC), "PR_ORIGINAL_DISPLAY_CC",
    PROP_ID( PR_ORIGINAL_DISPLAY_TO), "PR_ORIGINAL_DISPLAY_TO",
    PROP_ID( PR_ENTRYID), "PR_ENTRYID",
    PROP_ID( PR_OBJECT_TYPE), "PR_OBJECT_TYPE",
    PROP_ID( PR_ICON),    "PR_ICON",
    PROP_ID( PR_MINI_ICON),   "PR_MINI_ICON",
    PROP_ID( PR_STORE_ENTRYID),   "PR_STORE_ENTRYID",
    PROP_ID( PR_STORE_RECORD_KEY),    "PR_STORE_RECORD_KEY",
    PROP_ID( PR_RECORD_KEY),  "PR_RECORD_KEY",
    PROP_ID( PR_MAPPING_SIGNATURE),   "PR_MAPPING_SIGNATURE",
    PROP_ID( PR_ACCESS_LEVEL),    "PR_ACCESS_LEVEL",
    PROP_ID( PR_INSTANCE_KEY),    "PR_INSTANCE_KEY",
    PROP_ID( PR_ROW_TYPE),    "PR_ROW_TYPE",
    PROP_ID( PR_ACCESS),  "PR_ACCESS",
    PROP_ID( PR_ROWID),   "PR_ROWID",
    PROP_ID( PR_DISPLAY_NAME),    "PR_DISPLAY_NAME",
    PROP_ID( PR_ADDRTYPE),    "PR_ADDRTYPE",
    PROP_ID( PR_EMAIL_ADDRESS),   "PR_EMAIL_ADDRESS",
    PROP_ID( PR_COMMENT), "PR_COMMENT",
    PROP_ID( PR_DEPTH),   "PR_DEPTH",
    PROP_ID( PR_PROVIDER_DISPLAY),    "PR_PROVIDER_DISPLAY",
    PROP_ID( PR_CREATION_TIME),   "PR_CREATION_TIME",
    PROP_ID( PR_LAST_MODIFICATION_TIME),  "PR_LAST_MODIFICATION_TIME",
    PROP_ID( PR_RESOURCE_FLAGS),  "PR_RESOURCE_FLAGS",
    PROP_ID( PR_PROVIDER_DLL_NAME),   "PR_PROVIDER_DLL_NAME",
    PROP_ID( PR_SEARCH_KEY),  "PR_SEARCH_KEY",
    PROP_ID( PR_PROVIDER_UID),    "PR_PROVIDER_UID",
    PROP_ID( PR_PROVIDER_ORDINAL),    "PR_PROVIDER_ORDINAL",
//    PR_FORM_VERSION,    "PR_FORM_VERSION",
//    PR_FORM_CLSID,  "PR_FORM_CLSID",
//    PR_FORM_CONTACT_NAME,   "PR_FORM_CONTACT_NAME",
//    PR_FORM_CATEGORY,   "PR_FORM_CATEGORY",
//    PR_FORM_CATEGORY_SUB,   "PR_FORM_CATEGORY_SUB",
//    PR_FORM_HOST_MAP,   "PR_FORM_HOST_MAP",
//    PR_FORM_HIDDEN, "PR_FORM_HIDDEN",
//    PR_FORM_DESIGNER_NAME,  "PR_FORM_DESIGNER_NAME",
//    PR_FORM_DESIGNER_GUID,  "PR_FORM_DESIGNER_GUID",
//    PR_FORM_MESSAGE_BEHAVIOR,   "PR_FORM_MESSAGE_BEHAVIOR",
    PROP_ID( PR_DEFAULT_STORE),   "PR_DEFAULT_STORE",
    PROP_ID( PR_STORE_SUPPORT_MASK),  "PR_STORE_SUPPORT_MASK",
    PROP_ID( PR_IPM_SUBTREE_SEARCH_KEY),  "PR_IPM_SUBTREE_SEARCH_KEY",
    PROP_ID( PR_IPM_OUTBOX_SEARCH_KEY),   "PR_IPM_OUTBOX_SEARCH_KEY",
    PROP_ID( PR_IPM_WASTEBASKET_SEARCH_KEY),  "PR_IPM_WASTEBASKET_SEARCH_KEY",
    PROP_ID( PR_IPM_SENTMAIL_SEARCH_KEY), "PR_IPM_SENTMAIL_SEARCH_KEY",
    PROP_ID( PR_MDB_PROVIDER),    "PR_MDB_PROVIDER",
    PROP_ID( PR_RECEIVE_FOLDER_SETTINGS), "PR_RECEIVE_FOLDER_SETTINGS",
    PROP_ID( PR_VALID_FOLDER_MASK),   "PR_VALID_FOLDER_MASK",
    PROP_ID( PR_IPM_SUBTREE_ENTRYID), "PR_IPM_SUBTREE_ENTRYID",
    PROP_ID( PR_IPM_OUTBOX_ENTRYID),  "PR_IPM_OUTBOX_ENTRYID",
    PROP_ID( PR_IPM_WASTEBASKET_ENTRYID), "PR_IPM_WASTEBASKET_ENTRYID",
    PROP_ID( PR_IPM_SENTMAIL_ENTRYID),    "PR_IPM_SENTMAIL_ENTRYID",
    PROP_ID( PR_VIEWS_ENTRYID),   "PR_VIEWS_ENTRYID",
    PROP_ID( PR_COMMON_VIEWS_ENTRYID),    "PR_COMMON_VIEWS_ENTRYID",
    PROP_ID( PR_FINDER_ENTRYID),  "PR_FINDER_ENTRYID",
    PROP_ID( PR_CONTAINER_FLAGS), "PR_CONTAINER_FLAGS",
    PROP_ID( PR_FOLDER_TYPE), "PR_FOLDER_TYPE",
    PROP_ID( PR_CONTENT_COUNT),   "PR_CONTENT_COUNT",
    PROP_ID( PR_CONTENT_UNREAD),  "PR_CONTENT_UNREAD",
    PROP_ID( PR_CREATE_TEMPLATES),    "PR_CREATE_TEMPLATES",
    PROP_ID( PR_DETAILS_TABLE),   "PR_DETAILS_TABLE",
    PROP_ID( PR_SEARCH),  "PR_SEARCH",
    PROP_ID( PR_SELECTABLE),  "PR_SELECTABLE",
    PROP_ID( PR_SUBFOLDERS),  "PR_SUBFOLDERS",
    PROP_ID( PR_STATUS),  "PR_STATUS",
    PROP_ID( PR_ANR), "PR_ANR",
    PROP_ID( PR_CONTENTS_SORT_ORDER), "PR_CONTENTS_SORT_ORDER",
    PROP_ID( PR_CONTAINER_HIERARCHY), "PR_CONTAINER_HIERARCHY",
    PROP_ID( PR_CONTAINER_CONTENTS),  "PR_CONTAINER_CONTENTS",
    PROP_ID( PR_FOLDER_ASSOCIATED_CONTENTS),  "PR_FOLDER_ASSOCIATED_CONTENTS",
    PROP_ID( PR_DEF_CREATE_DL),   "PR_DEF_CREATE_DL",
    PROP_ID( PR_DEF_CREATE_MAILUSER), "PR_DEF_CREATE_MAILUSER",
    PROP_ID( PR_CONTAINER_CLASS), "PR_CONTAINER_CLASS",
    PROP_ID( PR_CONTAINER_MODIFY_VERSION),    "PR_CONTAINER_MODIFY_VERSION",
    PROP_ID( PR_AB_PROVIDER_ID),  "PR_AB_PROVIDER_ID",
    PROP_ID( PR_ATTACHMENT_X400_PARAMETERS),  "PR_ATTACHMENT_X400_PARAMETERS",
    PROP_ID( PR_ATTACH_DATA_BIN), "PR_ATTACH_DATA_BIN",
    PROP_ID( PR_ATTACH_DATA_OBJ), "PR_ATTACH_DATA_OBJ",
    PROP_ID( PR_ATTACH_ENCODING), "PR_ATTACH_ENCODING",
    PROP_ID( PR_ATTACH_EXTENSION),    "PR_ATTACH_EXTENSION",
    PROP_ID( PR_ATTACH_FILENAME), "PR_ATTACH_FILENAME",
    PROP_ID( PR_ATTACH_METHOD),   "PR_ATTACH_METHOD",
    PROP_ID( PR_ATTACH_LONG_FILENAME),    "PR_ATTACH_LONG_FILENAME",
    PROP_ID( PR_ATTACH_PATHNAME), "PR_ATTACH_PATHNAME",
    PROP_ID( PR_ATTACH_RENDERING),    "PR_ATTACH_RENDERING",
    PROP_ID( PR_ATTACH_TAG),  "PR_ATTACH_TAG",
    PROP_ID( PR_RENDERING_POSITION),  "PR_RENDERING_POSITION",
    PROP_ID( PR_ATTACH_TRANSPORT_NAME),   "PR_ATTACH_TRANSPORT_NAME",
    PROP_ID( PR_DISPLAY_TYPE),    "PR_DISPLAY_TYPE",
    PROP_ID( PR_TEMPLATEID),  "PR_TEMPLATEID",
//    PROP_ID( PR_CAPABILITIES_TABLE),  "PR_CAPABILITIES_TABLE",
    PROP_ID( PR_PRIMARY_CAPABILITY),  "PR_PRIMARY_CAPABILITY",
    PROP_ID( PR_ACCOUNT), "PR_ACCOUNT",
    PROP_ID( PR_ALTERNATE_RECIPIENT), "PR_ALTERNATE_RECIPIENT",
    PROP_ID( PR_CALLBACK_TELEPHONE_NUMBER),   "PR_CALLBACK_TELEPHONE_NUMBER",
    PROP_ID( PR_CONVERSION_PROHIBITED),   "PR_CONVERSION_PROHIBITED",
    PROP_ID( PR_DISCLOSE_RECIPIENTS), "PR_DISCLOSE_RECIPIENTS",
    PROP_ID( PR_GENERATION),  "PR_GENERATION",
    PROP_ID( PR_GIVEN_NAME),  "PR_GIVEN_NAME",
    PROP_ID( PR_GOVERNMENT_ID_NUMBER),    "PR_GOVERNMENT_ID_NUMBER",
    PROP_ID( PR_OFFICE_TELEPHONE_NUMBER), "PR_OFFICE_TELEPHONE_NUMBER",
    PROP_ID( PR_HOME_TELEPHONE_NUMBER),   "PR_HOME_TELEPHONE_NUMBER",
    PROP_ID( PR_INITIALS),    "PR_INITIALS",
    PROP_ID( PR_KEYWORD), "PR_KEYWORD",
    PROP_ID( PR_LANGUAGE),    "PR_LANGUAGE",
    PROP_ID( PR_LOCATION),    "PR_LOCATION",
    PROP_ID( PR_MAIL_PERMISSION), "PR_MAIL_PERMISSION",
    PROP_ID( PR_MHS_COMMON_NAME), "PR_MHS_COMMON_NAME",
    PROP_ID( PR_ORGANIZATIONAL_ID_NUMBER),    "PR_ORGANIZATIONAL_ID_NUMBER",
    PROP_ID( PR_SURNAME), "PR_SURNAME",
    PROP_ID( PR_ORIGINAL_ENTRYID),    "PR_ORIGINAL_ENTRYID",
    PROP_ID( PR_ORIGINAL_DISPLAY_NAME),   "PR_ORIGINAL_DISPLAY_NAME",
    PROP_ID( PR_ORIGINAL_SEARCH_KEY), "PR_ORIGINAL_SEARCH_KEY",
    PROP_ID( PR_POSTAL_ADDRESS),  "PR_POSTAL_ADDRESS",
    PROP_ID( PR_COMPANY_NAME),    "PR_COMPANY_NAME",
    PROP_ID( PR_TITLE),   "PR_TITLE",
    PROP_ID( PR_DEPARTMENT_NAME), "PR_DEPARTMENT_NAME",
    PROP_ID( PR_OFFICE_LOCATION), "PR_OFFICE_LOCATION",
    PROP_ID( PR_PRIMARY_TELEPHONE_NUMBER),    "PR_PRIMARY_TELEPHONE_NUMBER",
    PROP_ID( PR_OFFICE2_TELEPHONE_NUMBER),    "PR_OFFICE2_TELEPHONE_NUMBER",
    PROP_ID( PR_CELLULAR_TELEPHONE_NUMBER),   "PR_CELLULAR_TELEPHONE_NUMBER",
    PROP_ID( PR_RADIO_TELEPHONE_NUMBER),  "PR_RADIO_TELEPHONE_NUMBER",
    PROP_ID( PR_CAR_TELEPHONE_NUMBER),    "PR_CAR_TELEPHONE_NUMBER",
    PROP_ID( PR_OTHER_TELEPHONE_NUMBER),  "PR_OTHER_TELEPHONE_NUMBER",
    PROP_ID( PR_TRANSMITABLE_DISPLAY_NAME),   "PR_TRANSMITABLE_DISPLAY_NAME",
    PROP_ID( PR_STORE_PROVIDERS), "PR_STORE_PROVIDERS",
    PROP_ID( PR_AB_PROVIDERS),    "PR_AB_PROVIDERS",
    PROP_ID( PR_TRANSPORT_PROVIDERS), "PR_TRANSPORT_PROVIDERS",
    PROP_ID( PR_DEFAULT_PROFILE), "PR_DEFAULT_PROFILE",
    PROP_ID( PR_AB_SEARCH_PATH),  "PR_AB_SEARCH_PATH",
    PROP_ID( PR_AB_DEFAULT_DIR),  "PR_AB_DEFAULT_DIR",
    PROP_ID( PR_AB_DEFAULT_PAB),  "PR_AB_DEFAULT_PAB",
    PROP_ID( PR_FILTERING_HOOKS), "PR_FILTERING_HOOKS",
    PROP_ID( PR_SERVICE_NAME),    "PR_SERVICE_NAME",
    PROP_ID( PR_SERVICE_DLL_NAME),    "PR_SERVICE_DLL_NAME",
    PROP_ID( PR_SERVICE_ENTRY_NAME),  "PR_SERVICE_ENTRY_NAME",
    PROP_ID( PR_SERVICE_UID), "PR_SERVICE_UID",
    PROP_ID( PR_SERVICE_EXTRA_UIDS),  "PR_SERVICE_EXTRA_UIDS",
    PROP_ID( PR_SERVICES),    "PR_SERVICES",
    PROP_ID( PR_SERVICE_SUPPORT_FILES),   "PR_SERVICE_SUPPORT_FILES",
    PROP_ID( PR_SERVICE_DELETE_FILES),    "PR_SERVICE_DELETE_FILES",
    PROP_ID( PR_IDENTITY_DISPLAY),    "PR_IDENTITY_DISPLAY",
    PROP_ID( PR_IDENTITY_ENTRYID),    "PR_IDENTITY_ENTRYID",
    PROP_ID( PR_RESOURCE_METHODS),    "PR_RESOURCE_METHODS",
    PROP_ID( PR_RESOURCE_TYPE),   "PR_RESOURCE_TYPE",
    PROP_ID( PR_STATUS_CODE), "PR_STATUS_CODE",
    PROP_ID( PR_IDENTITY_SEARCH_KEY), "PR_IDENTITY_SEARCH_KEY",
    PROP_ID( PR_OWN_STORE_ENTRYID),   "PR_OWN_STORE_ENTRYID",
    PROP_ID( PR_RESOURCE_PATH),   "PR_RESOURCE_PATH",
    PROP_ID( PR_STATUS_STRING),   "PR_STATUS_STRING",
    PROP_ID( PR_X400_DEFERRED_DELIVERY_CANCEL),   "PR_X400_DEFERRED_DELIVERY_CANCEL",
    PROP_ID( PR_HEADER_FOLDER_ENTRYID),   "PR_HEADER_FOLDER_ENTRYID",
    PROP_ID( PR_REMOTE_PROGRESS), "PR_REMOTE_PROGRESS",
    PROP_ID( PR_REMOTE_PROGRESS_TEXT),    "PR_REMOTE_PROGRESS_TEXT",
    PROP_ID( PR_CONTROL_FLAGS),   "PR_CONTROL_FLAGS",
    PROP_ID( PR_CONTROL_STRUCTURE),   "PR_CONTROL_STRUCTURE",
    PROP_ID( PR_CONTROL_TYPE),    "PR_CONTROL_TYPE",
    PROP_ID( PR_DELTAX),  "PR_DELTAX",
    PROP_ID( PR_DELTAY),  "PR_DELTAY",
    PROP_ID( PR_XPOS),    "PR_XPOS",
    PROP_ID( PR_YPOS),    "PR_YPOS",
    PROP_ID( PR_CONTROL_ID),  "PR_CONTROL_ID",
    PROP_ID( PR_INITIAL_DETAILS_PANE),    "PR_INITIAL_DETAILS_PANE"
};

static IDList PropTypes[] =
{
    PT_UNSPECIFIED, "PT_UNSPECIFIED",
    PT_NULL,    "PT_NULL",
    PT_I2,  "PT_I2",
    PT_LONG,    "PT_LONG",
    PT_R4,  "PT_R4",
    PT_DOUBLE,  "PT_DOUBLE",
    PT_CURRENCY,    "PT_CURRENCY",
    PT_APPTIME, "PT_APPTIME",
    PT_ERROR,   "PT_ERROR",
    PT_BOOLEAN, "PT_BOOLEAN",
    PT_OBJECT,  "PT_OBJECT",
    PT_I8,  "PT_I8",
    PT_SYSTIME, "PT_SYSTIME",
    PT_STRING8, "PT_STRING8",
    PT_UNICODE, "PT_UNICODE",
    PT_CLSID,   "PT_CLSID",
    PT_BINARY,  "PT_BINARY",
    PT_MV_I2,   "PT_MV_I2",
    PT_MV_LONG, "PT_MV_LONG",
    PT_MV_R4,   "PT_MV_R4",
    PT_MV_DOUBLE,   "PT_MV_DOUBLE",
    PT_MV_CURRENCY, "PT_MV_CURRENCY",
    PT_MV_APPTIME,  "PT_MV_APPTIME",
    PT_MV_SYSTIME,  "PT_MV_SYSTIME",
    PT_MV_STRING8,  "PT_MV_STRING8",
    PT_MV_BINARY,   "PT_MV_BINARY",
    PT_MV_UNICODE,  "PT_MV_UNICODE",
    PT_MV_CLSID,    "PT_MV_CLSID",
    PT_MV_I8,   "PT_MV_I8",
    PT_MV_TSTRING,  "PT_MV_TSTRING"
};

// -----------------------------------------------------------------------------

#endif  //_PROPDEF_H_


PROPDLG.CPP

// -----------------------------------------------------------------------------
// PropDlg.cpp: Implementation of a class that displays a dialog to show a
//              list of properties.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "PropDlg.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CPropDlg, CDialog)
//{{AFX_MSG_MAP(CPropDlg)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// -----------------------------------------------------------------------------
// CONSTRUCTOR: Takes an ptr to a CProperty interface object pointer.
// -----------------------------------------------------------------------------

CPropDlg::CPropDlg( CProperty* piProp) : CADialog(/*CPropDlg::IDD*/)
{
//{{AFX_DATA_INIT(CPropDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
    m_piProp = piProp;
}

// -----------------------------------------------------------------------------
// Initialize the dialog using the properties listed in the CProperty object
// that we got a pointer to in the constructor.
// -----------------------------------------------------------------------------

BOOL CPropDlg::OnInitDialog()
{
// Pay tribute to our ancestors.
CDialog::OnInitDialog();

    // Connect the CListBox object to the appropriate control.
    if( HrSubclassWindow( IDC_LIST_PROPS, m_lbProps) != NOERROR)
        return( TRUE);  // Could not find the list box window.

    // Set the tab stops for the list box.
    int nTabStops[] = { 130, 400};
    m_lbProps.SetTabStops( ARRAY_CNT( nTabStops), nTabStops);
    m_lbProps.SetHorizontalExtent( 1000);

    if( !m_piProp)
    {
        HR_LOG( E_FAIL);
        return( TRUE);
    }

    // Get the first property.
    m_piProp->First();

    // Fill list box with all properties.
    while( !m_piProp->bAtEnd())
    {   // Format string to add to list box.
        CString sBuf = m_piProp->szGetPrID();
        sBuf += "\t";
        sBuf += m_piProp->szGetPrValue();

        // Add formated string to list box.
        m_lbProps.AddString( sBuf);

        // Move on to the next property.
        m_piProp->Next();
    }

return TRUE;  // Return TRUE unless you set the focus to a control.
}

// -----------------------------------------------------------------------------


PROPDLG.H

// -----------------------------------------------------------------------------
// PropDlg.h : Declaration of a class that displays a dialog to show a
//             list of properties.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _PROPDLG_H_
#define _PROPDLG_H_

// -----------------------------------------------------------------------------

#include "resource.h"
#include "ExAdmin.h"
#include "Property.h"

// -----------------------------------------------------------------------------

class CPropDlg : public CADialog
{
public:
CPropDlg( CProperty* piProp);   // standard constructor

protected:
    CProperty*  m_piProp;   // Pointer to CProperty interface object.
    CListBox    m_lbProps;  // List box to show properties in.

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Class wizard stuff.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public:
//{{AFX_DATA(CPropDlg)
enum { IDD = IDD_PROPS};
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPropDlg)
//}}AFX_VIRTUAL

// Generated message map functions
//{{AFX_MSG(CPropDlg)
virtual BOOL OnInitDialog();
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

// -----------------------------------------------------------------------------

#endif //_PROPDLG_H_


PROPERTY.CPP

// -----------------------------------------------------------------------------
// Property.cpp: Implements methods for classes that works with IMAPIProp.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "property.h"
#include "propdef.h"    // Include only here!  Contains static declarations.
#include "ErrCpp.h"
#include "property.chk"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------
// CONSTRUCTOR
// -----------------------------------------------------------------------------

CProperty::CProperty() :
    m_idlPropIDs( PropIDs, ARRAY_CNT( PropIDs)),
    m_idlPropTypes( PropTypes, ARRAY_CNT( PropTypes))
{
    Init();
    m_bUnsigned = TRUE;     // Default treatment of int and long to unsigned.
    m_sDblFmt = "%10.2g";   // Default format for printing doubles.
}

// -----------------------------------------------------------------------------
// Set the default sign format for int and long.  Used by szGetPrValue() when
// caller does not provide a parameter.
// -----------------------------------------------------------------------------

BOOL CProperty::bSetUnsigned( BOOL bUnsigned)
{
    BOOL bOldVal = m_bUnsigned;
    m_bUnsigned = bUnsigned;
    return( bOldVal);
}

// -----------------------------------------------------------------------------
// Set the default format used for doubles and floats.  This would be in
// printf() format.
// -----------------------------------------------------------------------------

CString CProperty::SetDblFmt( LPSTR pszDblFmt)
{
    CString sRetVal = m_sDblFmt;
    m_sDblFmt = pszDblFmt;
    return( m_sDblFmt);
}

// -----------------------------------------------------------------------------
// Frees the property list array.
// -----------------------------------------------------------------------------

void CProperty::Free(
    ULONG cValues,          // Count of new properties.
    LPSPropValue pProps)    // Ptr to list of new properties.
{
    MAPIFREEBUFFER( m_pFirstProp);  // Free old list of properties.
    Init( cValues, pProps);         // Initialize list with new properties.
}

// -----------------------------------------------------------------------------
// Gets the properties using an IMAPIProp interface.
// -----------------------------------------------------------------------------

HRESULT CProperty::GetProps(
    IMAPIProp*      imapiProp,  // MAPI interface to access properties with.
    LPSPropTagArray pta,        // Ptr to property tag array.
    ULONG           ulFlags)    // Optional flags, default to zero.
{
    DEBUGPUBLIC( "CProperty::GetProps()\n");
    CHRESULT hr = CHK_CProperty_GetProps( imapiProp, pta, ulFlags);
    if( FAILED( hr))
        RETURN( hr);

    Free();

    // Get the properties using MAPI.
    hr = imapiProp->GetProps( pta, ulFlags, &m_cValues, &m_pFirstProp);
    if( FAILED( hr))
        RETURN( hr);

    // Setup to transverse through list of properties.
    m_pLastProp = m_pFirstProp + (m_cValues - 1);
    First();

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// Set current property to the next property.
// -----------------------------------------------------------------------------

void CProperty::Next()
{
    if( m_pCurProp == NULL)
        m_pCurProp = m_pFirstProp;
    else if( m_pCurProp == m_pLastProp)
        m_pCurProp = NULL;
    else
        m_pCurProp ++;
}

// -----------------------------------------------------------------------------
// Get the current property ID as a ULONG.
// -----------------------------------------------------------------------------

ULONG CProperty::ulGetPrID()
{
    ASSERTERROR( m_pCurProp != NULL, "m_pCurProp is NULL!");
    if( m_pCurProp == NULL)
        return( 0);

    return( PROP_ID(m_pCurProp->ulPropTag));
}

// -----------------------------------------------------------------------------
// Get the current property TYPE as a ULONG.
// -----------------------------------------------------------------------------

ULONG CProperty::ulGetPrType()
{
    ASSERTERROR( m_pCurProp != NULL, "m_pCurProp is NULL!");
    if( m_pCurProp == NULL)
        return( 0);

    return(PROP_TYPE( m_pCurProp->ulPropTag));
}

// -----------------------------------------------------------------------------
// Get the current property VALUE as a union _PV as defined in MAPI.
// -----------------------------------------------------------------------------

UPropVal CProperty::upvGetPrValue()
{
    UPropVal upv;
    upv.ul = 0;     // On error return this property value.

    ASSERTERROR( m_pCurProp != NULL, "m_pCurProp is NULL!");
    if( m_pCurProp == NULL)
        return( upv);

    return( m_pCurProp->Value);
}

// -----------------------------------------------------------------------------
// Get the current property VALUE as a formated CString.  Pass TRUE to treat
// ints or longs as unsigned, FALSE otherwise.  If no params passed in we
// default to a value that may be set through bSetUnsigned().
//
// NOTE: A number of the possible property types don't have a conversion at
//       this time.
// -----------------------------------------------------------------------------

CString CProperty::szGetPrValue( BOOL bUnsigned)
{
    int         ii;
    unsigned    ui;
    double      dbl;
    char        chBuf[4];
    LPBYTE      pByte;
    CString     sRetVal;
    FILETIME    LocalFT = {0};
    SYSTEMTIME  st = {0};
    UPropVal    upv = upvGetPrValue();

    if( bUnsigned == -1)
        bUnsigned = m_bUnsigned;

    switch( ulGetPrType())
    {
        case PT_I2:
            if( bUnsigned)
            {
                ui = (unsigned) upv.i; // Convert to short unsigned to regular unsigned.
                sRetVal.Format( "%u", ui);
            }
            else
            {
                ii = upv.i;             // Convert to short int to regular int.
                sRetVal.Format( "%d", ii);
            }
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_LONG:
            if( bUnsigned)
                sRetVal.Format( "%lu", upv.ul);
            else
                sRetVal.Format( "%ld", upv.l);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_R4:
            dbl = upv.flt;
            sRetVal.Format( m_sDblFmt, dbl);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_DOUBLE:
            sRetVal.Format( m_sDblFmt, upv.dbl);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_BOOLEAN:
            if( upv.b)
                sRetVal = "TRUE";
            else
                sRetVal = "FALSE";
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_SYSTIME:
            if( FileTimeToLocalFileTime( &upv.ft, &LocalFT))
            {
                if( FileTimeToSystemTime( &LocalFT, &st))
                {
                    sRetVal.Format( "%.2d:%.2d:%.2d %.2d/%.2d/%.2d",
                        st.wHour, st.wMinute, st.wSecond,
                        st.wMonth, st.wDay, st.wYear);
                    break;
                }
            }

            sRetVal.Format( "Error %lu converting file time to local or system time.", GetLastError());
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_STRING8:
            sRetVal.Format( "%s", upv.lpszA);
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_BINARY:
            sRetVal.Format( "CB:%d 0x", upv.bin.cb);  // Format the count.

            // Make sure we don't format more than 100 bytes.
            if( upv.bin.cb > 100)
                ii = 100;
            else
                ii = upv.bin.cb;

            // Loop to format into hex digits.
            pByte = upv.bin.lpb;
            while( ii)
            {   // Format a single byte into a hex value and add to sRetVal.
                ui = *pByte;
                sprintf( chBuf, "%.2x", ui);
                sRetVal += chBuf;

                // Next byte.
                pByte ++;
                ii --;
            }
            break;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        case PT_ERROR:
        {
            CHRESULT hr = upv.err;
            sRetVal.Format( "Error %s [%.8lx]", hr.Msg(), hr);
            break;
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        default:
            sRetVal = szGetPrType();
            sRetVal += " has no conversion at this time.";
            break;
    }
    return( sRetVal);
}

// $--CProperty::Init()---------------------------------------------------------
// Initializes property counters and pointers.
// -----------------------------------------------------------------------------

void CProperty::Init( ULONG cValues, LPSPropValue pProps)
{
    m_cValues = cValues;
    m_pFirstProp = pProps;
    m_pLastProp = m_pFirstProp + (m_cValues - 1);
    m_pCurProp = NULL;
}

// -----------------------------------------------------------------------------
// CONSTRUCTOR
// -----------------------------------------------------------------------------

CPropertyRows::CPropertyRows()
{
    m_pRowSet = NULL;                   // No property row set.
    m_ulCurRowIndex = PR_ROW_SET_END;   // No current row.
}

// -----------------------------------------------------------------------------
// Frees the rows of properties then initializes the base class values since we
// don't want it trying to free a single row of properties that no longer exists.
// -----------------------------------------------------------------------------

void CPropertyRows::Free()
{
    if( !m_pRowSet)
        return;

    FreeProws( m_pRowSet);
    m_pRowSet = NULL;
    m_ulCurRowIndex = PR_ROW_SET_END;   // No current row.

    Init( 0, NULL);
}

// -----------------------------------------------------------------------------
// Gets a row of properties from a MAPI table.
// -----------------------------------------------------------------------------

HRESULT CPropertyRows::HrGetTblRows( // RETURNS: return code
    LPMAPITABLE lpTable,        // Ptr to a mapi table.
    ULONG       cMaxNRows)      // Max.# of rows to return
{
    DEBUGPUBLIC( "CPropertyRows::HrGetTblRows()\n");
    CHRESULT hr = CHK_CPropertyRows_HrGetTblRows( lpTable, cMaxNRows);
    if( FAILED( hr))
        RETURN( hr);

    // Free last row set before trying to get another.
    Free();

    // Get the next list of messages in the folder
    hr = lpTable->QueryRows( cMaxNRows, 0L, &m_pRowSet);
    if( FAILED( hr))
        RETURN( hr);

    // Make sure we received a valid pointer.
    if( !m_pRowSet)
        RETURN( E_FAIL);

    // Have we reached the end of the row list?
    if( m_pRowSet->cRows == 0)
        RETURN( EDK_E_END_OF_FILE);    // YES, since no rows found return EDK_E_END_OF_FILE.

    FirstRow();

    return( NOERROR);
}

// $--CPropertyRows::SetCurRow()------------------------------------------------
// Set currency to either the first or the next row of properties.
// -----------------------------------------------------------------------------

void CPropertyRows::SetCurRow( BOOL bFirst) // TRUE sets to first row, FALSE sets to next row.
{
    if( !m_pRowSet)
        return; // There is no row set.

    // Were we requested to set to the first row, or were we at the end of the row list?
    if( bFirst || m_ulCurRowIndex == PR_ROW_SET_END)
        m_ulCurRowIndex = 0;    // YES, so set to first row.
    else
        m_ulCurRowIndex ++;     // NO, so set to the next row.

    if( bAtEndOfRows())
    {
        m_ulCurRowIndex = PR_ROW_SET_END;
        return;
    }

    // Initialize base class so that it's first and current property
    // point to the first one in this row.
    Init( m_pRowSet->aRow[ m_ulCurRowIndex].cValues, m_pRowSet->aRow[ m_ulCurRowIndex].lpProps);
    First();    // First property of this row.
}

// -----------------------------------------------------------------------------


PROPERTY.H

// -----------------------------------------------------------------------------
// Property.h : Define classes that deals with IMAPIProp properties.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _PROPERTY_H_
#define _PROPERTY_H_

#include "edkutcpp.h"

extern IDList PropTags[];
extern IDList PropTypes[];

typedef union _PV UPropVal;

// -----------------------------------------------------------------------------

#define PR_ROW_SET_END (ULONG) ~0

// -----------------------------------------------------------------------------
// This class lets the user get properties using an IMAPIProp interface (such as
// a mapi message interface).
//
// It lets you move through the properties using the First(), Next(), and
// bAtEnd() member functions.
//
// It lets you get the properties type, id, and value in either its native format
// of in a readable string.
// -----------------------------------------------------------------------------

class CProperty
{
public:
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Construction, destruction, and setting defaults.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CProperty();
    ~CProperty()  {Free();}

    // Set the default sign format for int and long.
    BOOL bSetUnsigned( BOOL bUnsigned = TRUE);

    // Set the default format used for doubles and floats.
    CString SetDblFmt( LPSTR pszDblFmt);

    // Frees the property list array.
    void Free( ULONG cValues = 0, LPSPropValue pProps = NULL);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Set up the count of values and pointer to them.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Gets the properties using an IMAPIProp interface.
    HRESULT GetProps( IMAPIProp* imapiProp, LPSPropTagArray pta, ULONG ulFlags = 0);

    // Set up the properties array to work with.
    void Set( ULONG cValues, LPSPropValue pProps)   {Free( cValues, pProps);}

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Moving through the property list array.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    void First()    {m_pCurProp = m_pFirstProp;}
    void Next();
    BOOL bAtEnd()   {return( m_pCurProp == NULL);}

    ULONG GetCount(){return( m_cValues);}

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Getting info about the current property.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the current property ID as a ULONG.
    ULONG       ulGetPrID();

    // Get the current property ID as a ptr to a char array string.
    const LPSTR szGetPrID()         {return( m_idlPropIDs.Find( ulGetPrID()));}

    // Get the current property TYPE as a ULONG.
    ULONG       ulGetPrType();

    // Get the current property TYPE as a ptr to a char array string.
    const LPSTR szGetPrType()       {return( m_idlPropTypes.Find( ulGetPrType()));}

    // Get the current property VALUE as a union _PV as defined in MAPI.
    UPropVal    upvGetPrValue();

    // Get the current property VALUE as a formated CString.  Pass TRUE to treat
    // ints or longs as unsigned, FALSE otherwise.  If no params passed in we
    // default to a value that may be set through bSetUnsigned().
    CString     szGetPrValue( BOOL bUnsigned = -1);

protected:
    // Initializes property counters and pointers.
    void Init( ULONG cValues = 0, LPSPropValue pProps = NULL);

    ULONG           m_cValues;          // Count of values pointed to by m_pFirstProp.
    LPSPropValue    m_pFirstProp;       // Ptr to first property in array.
    LPSPropValue    m_pLastProp;        // Ptr to last property.
    LPSPropValue    m_pCurProp;         // Ptr to current property.
    CIDList         m_idlPropIDs;       // ID list for valid property ids.
    CIDList         m_idlPropTypes;     // ID list for valid property types.
    BOOL            m_bUnsigned;        // Default signed format for int and long.
    CString         m_sDblFmt;          // Default format for doubles.
};

// -----------------------------------------------------------------------------
// This class gives you the functionality of the above CProperty class but for
// rows of properties.  Set up the row set by calling HrGetTblRows().
//
// Use FirstRow(), NextRow(), and bAtEndRows() to move through the rows of
// properties.  Then access the properties using the methods of the CProperty
// class.
// -----------------------------------------------------------------------------

class CPropertyRows : public CProperty
{
public:
    CPropertyRows();
    ~CPropertyRows() {Free();}

    // Frees the rows of properties.
    void Free();

    // Gets a row of properties from a MAPI table.
    HRESULT HrGetTblRows(                    // RETURNS: return code
        LPMAPITABLE lpTable,            // Ptr to a mapi table.
        ULONG       cMaxNRows = 10);    // Max.# of rows to return

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Moving through the rows of property list arrays.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    void FirstRow()     {SetCurRow(TRUE);}
    void NextRow()      {SetCurRow(FALSE);}
    BOOL bAtEndOfRows()
    {
        if( !m_pRowSet)
            return( TRUE);
        return( m_ulCurRowIndex >= m_pRowSet->cRows);
    }

    ULONG GetRowCount()
    {
        if( !m_pRowSet)
            return( 0);
        return( m_pRowSet->cRows);
    }

protected:
    void SetCurRow( BOOL bFirst);   // TRUE sets to first row, FALSE sets to next row.

    LPSRowSet   m_pRowSet;          // Ptr to rows of properties.
    ULONG       m_ulCurRowIndex;    // Index of current row.
};

// -----------------------------------------------------------------------------

#endif //_PROPERTY_H_


PRPLSTCB.CPP

// -----------------------------------------------------------------------------
// prpLstCb.cpp: Implements methods for the Queue Viewer's List box and Combo Box.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "prplstcb.h"
#include "helpers.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// $--CProps::CProps()----------------------------------------------------------
// CONSTRUCTOR
// -----------------------------------------------------------------------------

CProps::CProps( ULONG cbEID, BYTE* pEID, DWORD dwFlags)
{
    m_cbEID = cbEID;
    if( m_cbEID == 0)
        m_pEID = NULL;
    else
    {
        m_pEID = new BYTE[ m_cbEID];
        memcpy( m_pEID, pEID, m_cbEID);
    }
    m_dwFlags = dwFlags;
}

// $--CProps::~CProps()---------------------------------------------------------
// DESTRUCTOR
// -----------------------------------------------------------------------------

CProps::~CProps()
{
    if( m_pEID)
        delete [] m_pEID;
}

// $--CPropsListBox::CPropsListBox()------------------------------------------------
// CONSTRUCTOR
// -----------------------------------------------------------------------------

CPropsListBox::CPropsListBox()
{
    static SizedSPropTagArray(5L,sPropColumns) = { 5L, {PR_GW_ADMIN_OPERATIONS, PR_ENTRYID, PR_ORIGINATOR_NAME, PR_SUBJECT, PR_MESSAGE_SIZE}};
    static SizedSSortOrderSet(1L,sSortPrioSet) = { 1L, 0L, 0L, { PR_CLIENT_SUBMIT_TIME, TABLE_SORT_ASCEND}};
    m_psPropColumns = (LPSPropTagArray) &sPropColumns;
    m_psSortPrioSet = (LPSSortOrderSet) &sSortPrioSet;

}

// $--CPropsListBox::HrAddItem()--------------------------------------------------
// This virtual member function is called from HrFillBox().  It extracts the
// properties from pProps into useable objects, formats them, and then add them
// to the list box.
// -----------------------------------------------------------------------------

HRESULT CPropsListBox::HrAddItem(  // Returns HRESULT
     CProperty& Properties)  // Ptr to properties of EID, sender, subject, & size.
{
    CString sFlags;
    if( !m_pProps)
        sFlags = "\t\t";
    else
    {
        if( m_pProps->bIsDelete())
            sFlags += "d";
        if( m_pProps->bIsNDR())
            sFlags += "\tn\t";
        else
            sFlags += "\t\t";
    }

    // Take only the mailbox name of the sender distinguished name.
    CString sSenderName = Properties.szGetPrValue();
    sSenderName.MakeLower();
    CHAR szStrToFind[] = "/cn=recipients/cn=";
    int ii = sSenderName.Find( szStrToFind);
    sSenderName = Properties.szGetPrValue();
    if( ii > 0)
        // Take only the name.
        sSenderName = sSenderName.Mid( ii + sizeof( szStrToFind) - 1);

    // Subject.
    Properties.Next();
    CString sSubject = Properties.szGetPrValue();

    // Size.
    Properties.Next();
    CString sSize = Properties.szGetPrValue();

    // Display the originator, subject, and size by adding to list box.
    CString sBuffer;
    sBuffer.Format( "%s%-.12s\t%-.42s\t%.14s", sFlags.GetBuffer(0), sSenderName.GetBuffer(0),
                    sSubject.GetBuffer(0), sSize.GetBuffer(0));
    AddString( sBuffer);

    return( NOERROR);
}

// $--CPropsComboBox::CPropsComboBox()------------------------------------------
// CONSTRUCTOR
// -----------------------------------------------------------------------------

CPropsComboBox::CPropsComboBox() : m_sCurFolderName( TEXT("MTS-OUT"))
{
    static SizedSPropTagArray(4L,sPropColumns) = { 4L, {PR_GW_ADMIN_OPERATIONS, PR_ENTRYID, PR_DISPLAY_NAME, PR_CONTENT_COUNT}};
    static SizedSSortOrderSet(1L,sSortPrioSet) = { 1L, 0L, 0L, { PR_DISPLAY_NAME, TABLE_SORT_ASCEND}};
    m_psPropColumns = (LPSPropTagArray) &sPropColumns;
    m_psSortPrioSet = (LPSSortOrderSet) &sSortPrioSet;
    m_iFolder = -1;
    m_pFolder = NULL;
    m_pMDB = NULL;
}

// $--CPropsComboBox::HrInitialize()----------------------------------------------
// Find all folders and open default folder.
// -----------------------------------------------------------------------------

HRESULT CPropsComboBox::HrInitialize(
    LPMDB   pMDB)   // Ptr to MDB that contain gateway folders.
{
    DEBUGPUBLIC( "CPropsComboBox::HrInitialize()");
    CHRESULT hr = CHK_CPropsComboBox_HrInitialize( pMDB);
    if(FAILED(hr))
        RETURN( hr);

    // Make sure we have a valid pMDB, even in retail builds
    if( !pMDB)
        RETURN( E_FAIL);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the current selection and if there is one save the display
// name in m_sCurFolderName
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    int iCurSel = GetCurSel();
    if( iCurSel != CB_ERR)
    {   // Something is selected.
        GetLBText( iCurSel, m_sCurFolderName);
        int nLen = m_sCurFolderName.Find( TEXT(" -- "));
        m_sCurFolderName.GetBufferSetLength( nLen);
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remember the MDB ptr and open the root folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    m_pMDB = pMDB;

    ULONG ulObjType = 0;
    CMAPIInterface< LPMAPIFOLDER> pRootFolder;
    hr = m_pMDB->OpenEntry(
        0, NULL, NULL, MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN FAR *) &pRootFolder);
    if(FAILED(hr))
        RETURN( hr);

    ASSERTERROR(ulObjType == MAPI_FOLDER, "ulObjType is not a MAPI_FOLDER");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Using the root folder we can initialize a hierarchy table and use it to fill
// the combo box with a list of folders.  Also open the default folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the contents table for the folder.
    CMAPIInterface< LPMAPITABLE> pHierarchTbl;
    hr = pRootFolder->GetHierarchyTable( MAPI_DEFERRED_ERRORS, &pHierarchTbl);
    if( FAILED( hr))
        RETURN( hr);

    // Fill Combo Box with a list of folders.
    hr = HrFillBox( pHierarchTbl);
    if( FAILED( hr))
        RETURN( hr);

    // Select the first folder if non are selected yet.
    if( GetCurSel() == CB_ERR)
        SetCurSel( 0);

    // Open the default folder.
    hr = HrOpenFolder();
    if( FAILED( hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    return( NOERROR);
}

// -----------------------------------------------------------------------------
// This releases the old folder and opens the newly selected one.
// -----------------------------------------------------------------------------

BOOL CPropsComboBox::HrOpenFolder()
{
    DEBUGPUBLIC( "CPropsComboBox::HrOpenFolder()");

    int iFolder = GetCurSel();
    m_iFolder = -1; // In case we fail.

    // Release the old folder.
    ULRELEASE( m_pFolder);

    // Open the folder.
    ULONG ulObjType = 0;
    CHRESULT hr = m_pMDB->OpenEntry( GetByteCnt( iFolder), GetEID( iFolder),
        NULL, MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN FAR *) &m_pFolder);

    if( FAILED( hr))
        RETURN( hr);

    if( ulObjType != MAPI_FOLDER)
        RETURN( E_FAIL);

    m_iFolder = iFolder;
    return( NOERROR);
}

// $--CPropsComboBox::HrAddItem()-------------------------------------------------
// This virtual member function is called from HrFillBox().  It extracts the
// properties from pProps into useable objects, formats them, and then add them
// to the combo box.
// -----------------------------------------------------------------------------

HRESULT CPropsComboBox::HrAddItem( // Returns HRESULT
     CProperty& Properties)  // Ptr to properties of Entry Id and Folder name.
{
    BOOL bIsCurrent = FALSE;

    // Folder name.
    CString sFolderName = Properties.szGetPrValue();

    // Explicitly clear the DEFER flag of the MTS-IN folder.
    if( sFolderName == TEXT( "MTS-IN") && m_pProps)
        m_pProps->SetDefer( FALSE);

    // Is this the current one?
    if( sFolderName == m_sCurFolderName)
        bIsCurrent = TRUE;  // Yes

    // Count of messages in folder.
    Properties.Next();
    sFolderName += " -- " + Properties.szGetPrValue();

    // Add the folder name to the combo box.
    int ii = AddString( sFolderName);

    // If this is the current folder set the combo box selection.
    if( bIsCurrent)
        SetCurSel( ii);
    return( NOERROR);
}

// $--CPropsComboBox::CleanUp()---------------------------------------------------
// Called when the window has been destroyed.
// -----------------------------------------------------------------------------

void CPropsComboBox::Destruct()
{
    CPropsListComboBox<CComboBox>::Destruct();
    m_iFolder = -1;
    ULRELEASE( m_pFolder);
}

// -----------------------------------------------------------------------------


PRPLSTCB.H

// -----------------------------------------------------------------------------
// PrpLstCb.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _PRPLSTCB_H_
#define _PRPLSTCB_H_

#include "ErrCpp.h"
#include "Property.h"
#include "prplstcb.chk"

// -----------------------------------------------------------------------------

#define ADMINOP_HIDDEN  0x01L
#define ADMINOP_DEFER   0x02L
#define ADMINOP_DELETE  0x04L
#define ADMINOP_NDR     0x08L

// -----------------------------------------------------------------------------
// Used to contain properties such as entry id and flags.
// -----------------------------------------------------------------------------

class CProps
{
public:
    CProps( ULONG cbEID, BYTE* pEID, DWORD dwFlags);
    ~CProps();

    // Get the entry id and byte count for a specific index.
    ULONG GetByteCnt() {return( m_cbEID);}
    LPENTRYID GetEID() {return( (LPENTRYID) m_pEID);}

    // Functions to return the state of a flag.
    BOOL bTestFlags( DWORD dwBits) {return( (m_dwFlags & dwBits) == dwBits);}
    BOOL bIsHidden()    {return( bTestFlags(ADMINOP_HIDDEN));}
    BOOL bIsDefer()     {return( bTestFlags(ADMINOP_DEFER));}
    BOOL bIsDelete()    {return( bTestFlags(ADMINOP_DELETE));}
    BOOL bIsNDR()       {return( bTestFlags(ADMINOP_NDR));}
    DWORD dwGetFlags()  {return( m_dwFlags);}   // Returns all of the flags.

    // Set one of the flags (pass in TRUE or FALSE).
    void SetHidden( BOOL bState)    { m_dwFlags = (m_dwFlags & ~ADMINOP_HIDDEN) | bState;}
    void SetDefer( BOOL bState)     { m_dwFlags = (m_dwFlags & ~ADMINOP_DEFER ) | bState << 1;}
    void SetDelete( BOOL bState)    { m_dwFlags = (m_dwFlags & ~ADMINOP_DELETE) | bState << 2;}
    void SetNDR( BOOL bState)       { m_dwFlags = (m_dwFlags & ~ADMINOP_NDR   ) | bState << 3;}

protected:
    ULONG   m_cbEID;
    BYTE*   m_pEID;
    DWORD   m_dwFlags;
};

// Helper function.
inline BOOL bIsHidden( DWORD dwFlags) {return( (dwFlags & 0x01) == 0x01);}

// Used to replace a null CProps*.
extern CProps  NullCProps;

// -----------------------------------------------------------------------------
// Definition of a class that keeps a list of entry ids using struct _EID to
// contain them.  This is a template class which is to be derived from either
// CListBox or CComboBox.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
class CPropsListComboBox : public BASE_CLASS
{
public:
    CPropsListComboBox()    { m_pProps = NULL;}
    ~CPropsListComboBox()   { Destruct();}

    // Fills the list or combo box with data from the table, uses HrAddItem to prepare the data.
    HRESULT HrFillBox( LPMAPITABLE pTable);

    // Clears all data from the box.
    void ResetContent();

    // Use this to remove an item from the list.
    int DeleteString( int ii);

    // Get the entry id and byte count for a specific index.
    CProps* GetProps( int ii);  // Always returns valid pointer, GUARENTEED!
    ULONG GetByteCnt( int ii)   {return( GetProps( ii)->GetByteCnt());}
    LPENTRYID GetEID( int ii)   {return( GetProps( ii)->GetEID());}
    DWORD dwGetFlags( int ii)   {return( GetProps( ii)->dwGetFlags());}

protected:
    LPSPropTagArray     m_psPropColumns;    // Set this in your derived class.
    LPSSortOrderSet     m_psSortPrioSet;    // Set this in your derived class.
    CProps*             m_pProps;           // Used by AddString() and HrFillBox()

    // Pure virtual function called by HrFillBox.
    virtual HRESULT HrAddItem( CProperty& Properties) = 0;

    // Used to add a string and its properties.
    int AddString( const LPCTSTR lpszItem);

    // Use this because we can't use a message map in a template class.
    virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam);

    // Clean up when the window has been destroyed.
    virtual void Destruct();

    // These functions may not be used unless they are implemented properly.
    int InsertString( UINT nIndex, LPCTSTR lpszItem)
    {
        ASSERTERROR( FALSE, "NOT IMPLEMENTED");
        return( LB_ERR);
    }
};

// $--CPropsListComboBox< BASE_CLASS>::HrFillBox()------------------------------
// Fill the list or combo box with data from a table.  This function reads the
// data from the caller specified table and validates it.
//
// A pure virtual function named HrAddItem() supplies the method for formating
// and placing the data into the list or combo box.  HrAddItem() could also
// decide to throw it away or use it.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
HRESULT CPropsListComboBox< BASE_CLASS>::HrFillBox(
    LPMAPITABLE     pTable)
{
    DEBUGPUBLIC( "CPropsListComboBox< BASE_CLASS>::HrFillBox()\n");
    CHRESULT hr = CHK_CPropsListComboBox_HrFillBox( pTable);
    if(FAILED(hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Setup the table's columns and sort order.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Set the columns to return.
    hr = pTable->SetColumns( m_psPropColumns, 0L);
    if( FAILED( hr))
        RETURN( hr);

    // Sort the table.
    hr = pTable->SortTable( m_psSortPrioSet, 0L);
    if(FAILED(hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Contents table has been initialize so lets display it.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Clear the list/combo box of all entries.
    ResetContent();

    // Fill list/combo box with info from folder until end of table reached.
    while( TRUE)
    {   // Read the first 10 message headers from the contents table.
        CPropertyRows PropRowSet;
        CHRESULT hr = PropRowSet.HrGetTblRows( pTable);
        if( hr == EDK_E_END_OF_FILE )
            return( NOERROR);    // All done.

        if( FAILED( hr))
            RETURN( hr);

        // Process all messages in the current table.
        for( PropRowSet.FirstRow(); !PropRowSet.bAtEndOfRows(); PropRowSet.NextRow())
        {   // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            // Validate that the property array has the correct number of values
            // and that it has the correct properties in the expected order.
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            //#ifdef DEBUG
                PropRowSet.First();
                unsigned cProps = m_psPropColumns->cValues;
                if( PropRowSet.GetCount() < cProps)
                {
                    RETURN( E_FAIL);
                }

                if( PropRowSet.ulGetPrID() != PROP_ID( PR_GW_ADMIN_OPERATIONS))
                {
                    RETURN( E_FAIL);
                }

                PropRowSet.Next();
                if( PropRowSet.ulGetPrID() != PROP_ID( PR_ENTRYID))
                {
                    RETURN( E_FAIL);
                }

                unsigned ii;
                for( ii = 2; ii < cProps; ii ++)
                {
                    PropRowSet.Next();
                    if( PropRowSet.ulGetPrID() != PROP_ID( m_psPropColumns->aulPropTag[ ii]))
                    {
                        RETURN( E_FAIL);
                    }
                }
            //#endif

            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            // Extract the properties from pRowSet into useable objects.
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            PropRowSet.First();

            // PR_GW_ADMIN_OPERATIONS
            DWORD dwFlags;
            if( PropRowSet.ulGetPrType() != PT_LONG)
                dwFlags = 0;
            else
                dwFlags = PropRowSet.upvGetPrValue().ul;
            if( bIsHidden( dwFlags))
                continue;   // No need to go any further, this one is hidden.

            // PR_ENTRYID
            // Create a CProps object which will be added to list in AddString().
            PropRowSet.Next();
            if( m_pProps)
                delete m_pProps;
            if( PropRowSet.ulGetPrType() != PT_BINARY)
                m_pProps = new CProps( 0, NULL, dwFlags);
            else
                m_pProps = new CProps( PropRowSet.upvGetPrValue().bin.cb, PropRowSet.upvGetPrValue().bin.lpb, dwFlags);

            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            // Add the item to the list.
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            PropRowSet.Next();
            hr = HrAddItem( PropRowSet);
            if( FAILED( hr))
                RETURN( hr);
        } // End For Loop
    }
}

// $--CPropsListComboBox< BASE_CLASS>::ResetContent()---------------------------
// Frees all entry ids associated with the list box items and clears the list box.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
void CPropsListComboBox< BASE_CLASS>::ResetContent()
{
    // Are we still connected to the windows control?
    if( !::IsWindow(m_hWnd))
        return; // NO, we are not connected.

    // Free all property info objects.
    int ii = GetCount();
    while( ii)
    {
        ii --;
        CProps* pProps = (CProps*) GetItemDataPtr( ii);
        delete pProps;
    }
    BASE_CLASS::ResetContent();
}

// $--CPropsListComboBox< BASE_CLASS>::DeleteString()---------------------------
// Use this to remove an item from the list.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
int CPropsListComboBox< BASE_CLASS>::DeleteString( int ii) // Index of list item.
{
    // Are we still connected to the windows control?
    if( !::IsWindow(m_hWnd))
    {   // NO, we are not connected.
        HR_LOG( E_FAIL);
        return(0);
    }

    // NOTE: This parameter test MUST be done after the above test since
    //       the GetCount() function depends upon being connected to the
    //       windows control.
    if( ii < 0 || ii >= GetCount())
    {
        HR_LOG( E_FAIL);
        return( 0);
    }

    CProps* pProps = (CProps*) GetItemDataPtr( ii);
    delete pProps;
    return( BASE_CLASS::DeleteString( (UINT) ii));
}

// $--CPropsListComboBox< BASE_CLASS>::GetProps()-------------------------------
// Returns CProps object and GUARENTEES it to be valid.  This does error
// checking and if it can't get the pointer requested it returns a pointer to
// NullCProps which has NULL for all values and always exists.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
CProps* CPropsListComboBox< BASE_CLASS>::GetProps( int ii)   // Index of entry id.
{
    // Are we still connected to the windows control?
    if( !::IsWindow(m_hWnd))
    {
        HR_LOG( E_FAIL);
        return(0);  // NO we are not connected.
    }

    // NOTE: This parameter test MUST be done after the above test since
    //       the GetCount() function depends upon being connected to the
    //       windows control.
    if( ii < 0 || ii >= GetCount())
    {
        HR_LOG( E_FAIL);
        return( &NullCProps);
    }

    CProps* pProps = (CProps*) GetItemDataPtr( ii);
    if( !pProps || pProps == (CProps*) -1l)
    {
        HR_LOG( E_FAIL);
        return( &NullCProps);
    }

    return( pProps);
}

// $--CPropsListComboBox< BASE_CLASS>::AddString()------------------------------
// Add a string to the list box and an entry id to associate with it.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
int CPropsListComboBox< BASE_CLASS>::AddString( // Return index of list box item.
    const LPCTSTR lpszItem)                     // Ptr to string to add to list.
{
    DEBUGPUBLIC( "CPropsListComboBox< BASE_CLASS>::AddString()\n");
    if( FAILED( CHK_CPropsListComboBox_AddString( lpszItem)))
        return( LB_ERR);

    // Add the displayable row to the list box.
int ii = BASE_CLASS::AddString( lpszItem);
    if( ii == LB_ERR)
    {
        HR_LOG( E_FAIL);
        return( ii);
    }
    else if( ii == LB_ERRSPACE)
    {
        HR_LOG( E_FAIL);
        return( ii);
    }

// Add a pointer to the properties as additional data.
// Do we have properties to add?
if( m_pProps)
    {   // YES, so associate it with the box's row.
        SetItemDataPtr( ii, (void*) m_pProps);
        m_pProps = NULL;    // So it does not get deleted before it should.
    }

    return( ii);
}

// $--CPropsListComboBox< BASE_CLASS>::WindowProc()-----------------------------
// Clean up when list box is being destroyed.
// Use this because we can't use a message map in a template class therefore
// we can't capture ON_WM_DESTROY in the usual MFC manor.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
LRESULT CPropsListComboBox< BASE_CLASS>::WindowProc( UINT message, WPARAM wParam, LPARAM lParam)
{
    if( message == WM_DESTROY)
        Destruct();
    return( CWnd::WindowProc( message, wParam, lParam)); // Dispatch message anyway.
}

// $--CPropsListComboBox< BASE_CLASS>::Destruct()-------------------------------
// Clean up when the window has been destroyed.  This virtual function is
// supplied so that derived classes do not have to implement a message map or
// a WindowProc().  Plus we must delete all CProps objects.
// -----------------------------------------------------------------------------

template< class BASE_CLASS>
void CPropsListComboBox< BASE_CLASS>::Destruct()
{
    ResetContent();
    if( m_pProps)
    {
        delete m_pProps;
        m_pProps = NULL;
    }
}

// -----------------------------------------------------------------------------
// Derived class to support the list box in the QueViewer.
// -----------------------------------------------------------------------------

class CPropsListBox : public CPropsListComboBox< CListBox>
{
public:
    CPropsListBox();

protected:
    // Virtual function called by HrFillBox.
    virtual HRESULT HrAddItem( CProperty& Properties);
};

// -----------------------------------------------------------------------------
// Derived class to support the combo box in the QueViewer.
// -----------------------------------------------------------------------------

class CPropsComboBox : public CPropsListComboBox< CComboBox>
{
public:
    CPropsComboBox();

    // Find all folders, and open default folder.
    HRESULT HrInitialize(
        LPMDB   pMDB);  // Ptr to MDB that contain gateway folders.

    // Returns TRUE if a folder selection has changed.
    BOOL bSelectionChanged() { return( m_iFolder != GetCurSel());}

    // Returns the folder that is selected.
    LPMAPIFOLDER GetFolder() {return( m_pFolder);}
    BOOL bIsDefer()    { return( GetProps( m_iFolder)->bIsDefer());}
    BOOL bIsDelete()   { return( GetProps( m_iFolder)->bIsDelete());}
    BOOL bIsNDR()      { return( GetProps( m_iFolder)->bIsNDR());}

protected:
    int                     m_iFolder;  // Index of folder being viewed.
    LPMAPIFOLDER            m_pFolder;  // Ptr to folder being viewed.
    LPMDB                   m_pMDB;     // Ptr to MSD of gateway.
    CString                 m_sCurFolderName; // Contains name of selected folder.

    // Open the selected folder.
    BOOL HrOpenFolder();

    // Virtual function called by HrFillBox.
    virtual HRESULT HrAddItem( CProperty& Properties);

    // Clean up when the window has been destroyed.
    virtual void Destruct();
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

// Used to replace a null CProps*.
CProps  NullCProps( 0, NULL, 0);

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif //_PRPLSTCB_H_


QUEVIEW.CPP

// -----------------------------------------------------------------------------
// QueView.cpp : Implements an Exchange Administration property sheet to be used
//               for viewing the MTS-IN and MTS-OUT folders of a gateway.
//
// NOTE: To use the Delete and NDR buttons the PR_GW_ADMIN_OPERATIONS on each
//       folder you want this capability for must be set to 14.  Either do this
//       in your gateway application or use MDBVU32.EXE to do this.  At the time
//       this note was written PR_GW_ADMIN_OPERATIONS equates to 0x6658.  When
//       creating it create it as a ULONG.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
//#define _EXAMPLES_
#include "helpers.h"
#include "queview.h"
#include "property.h"
#include "PropDlg.h"
#include "MAPIMsg.h"
#include "BinArray.h"
#include "DynArray.h"
#include "delmsgdl.h"
#include "faildmsg.h"

/* #define INITGUID
#include <initguid.h>
#include <edkguid.h>
#define USES_IID_IMessage
#define USES_IID_IMAPIProp
#define USES_IID_IMAPIPropData
#define USES_IID_IMAPIFolder
#define USES_IID_IABContainer
#define USES_IID_IAddrBook
#define USES_IID_IMAPIFormInfo
#define USES_IID_IMailUser
#define USES_IID_IAttachment
#define USES_IID_IDistList
#define USES_IID_IMAPIStatus
#define USES_IID_IMAPISession
#define USES_IID_IMsgStore
#define USES_IID_IProfSect
#include <mapiguid.h>
*/

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------

const UINT iRefreshTimerId = 1; // The one and only timer we use.
UINT nRefreshTime = 1000;  // One second wait before refreshing combo box.

// -----------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CQueViewDlg, CAdminDialog)
//{{AFX_MSG_MAP(CQueViewDlg)
ON_BN_CLICKED(IDC_CMD_REFRESH, OnCmdRefresh)
ON_WM_DESTROY()
ON_BN_CLICKED(IDC_CMD_DETAILS, OnCmdDetails)
ON_CBN_SELCHANGE(IDC_COMBO_FOLDERS, OnSelChangeComboFolders)
ON_LBN_SELCHANGE(IDC_LIST_MSGS, OnSelChangeListMsgs)
ON_BN_CLICKED(IDC_CMD_DELETE, OnCmdDelete)
ON_BN_CLICKED(IDC_CMD_NDR, OnCmdNdr)
ON_WM_TIMER()
ON_LBN_DBLCLK(IDC_LIST_MSGS, OnCmdDetails)
ON_CBN_KILLFOCUS(IDC_COMBO_FOLDERS, OnKillFocusComboFolders)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//$--CQueViewDlg::CQueViewDlg()-------------------------------------------------
// CONSTRUCTOR:
// -----------------------------------------------------------------------------

CQueViewDlg::CQueViewDlg() : CAdminDialog( IDD_QUEUE_VIEW, IDS_QUEUE_VIEW)
{
    m_bInitilized = FALSE;
    m_bMTSIn = FALSE;
    m_pGWmdb = NULL;
    m_hPropDlgTemplate = NULL;
}

//$--CQueViewDlg::~CQueViewDlg()-------------------------------------------------
// DESTRUCTOR:
// -----------------------------------------------------------------------------

CQueViewDlg::~CQueViewDlg()
{
    if( m_hPropDlgTemplate)
        FreeDialogTemplate( &m_hPropDlgTemplate);
}

//$--CQueViewDlg::bHasHelp()----------------------------------------------------
// Called to determine if you supply help.  Return TRUE if you do.
// -----------------------------------------------------------------------------

BOOL CQueViewDlg::bHasHelp()
{
    return( TRUE);
}

//$--CQueViewDlg::DoHelp()------------------------------------------------------
// Called to start help.
// -----------------------------------------------------------------------------

VOID CQueViewDlg::DoHelp()
{
    ::WinHelp( GetSafeHwnd(), TEXT( "queview.hlp"), HELP_CONTENTS, 0);
}

//$--CQueViewDlg::Refresh()-----------------------------------------------------
// Called when the property sheet has been activated or reactivated.
// -----------------------------------------------------------------------------

void CQueViewDlg::Refresh()
{
    OnCmdRefresh();
}

//$--CQueViewDlg::OnCmdRefresh()------------------------------------------------
// Handles the Refresh button.  Uses the selected folder to fill the list box
// with Sender, Subject, and Size of each message in the folder.  Also refreshes
// the combo box with the current list of folders.
// -----------------------------------------------------------------------------

void CQueViewDlg::OnCmdRefresh()
{
    DEBUGPUBLIC( "CQueViewDlg::OnCmdRefresh()");
    if( !::IsWindow(m_hWnd))
        return; // Our list box is not connected to a window.

    // Initialize the folder combo box with the current list.
    CHRESULT hr = m_cbFolders.HrInitialize( m_pGWmdb);
    if( FAILED( hr))
        return;

    // Get the folder we have selected.
    LPMAPIFOLDER pFolder = m_cbFolders.GetFolder();
    if( !pFolder)
    {
        HR_LOG( E_FAIL);
        return;
    }

    // Get the contents table for the folder.
    CMAPIInterface< LPMAPITABLE> pContentsTbl;
    hr = pFolder->GetContentsTable( MAPI_DEFERRED_ERRORS, &pContentsTbl);
    if( FAILED( hr))
        return;

    // Fill the list box with the data from this table.
    hr = m_lbMsgs.HrFillBox( pContentsTbl);
    if( FAILED( hr))
        return;

    OnSelChangeListMsgs();
}

//$--CQueViewDlg::OnInitDialog()------------------------------------------------
// Initialize the dialog, connect class objects to controls, open MDB and folders.
// -----------------------------------------------------------------------------

BOOL CQueViewDlg::OnInitDialog()
{
    DEBUGPUBLIC( "CQueViewDlg::OnInitDialog()");

// Pay tribute to our ancestors.
CAdminDialog::OnInitDialog();

    // This object will validate that initialization completed when this
    // function exits.  If it has not then a message will be displayed.
    CMsgOnFail bInitStatus( "Could not logon to the information store.", "Queue Viewer");

    if( !GetMAPISession())
        return( TRUE);  // We don't have a session handle so we must bail out now.

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Code to open the default message store.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the entry ID of the default message store.
    ULONG cbeid = 0;              // count of bytes in entry ID
    CMAPIBuffer<LPENTRYID>  lpeid; // Entry ID of default store
    CHRESULT hr = HrMAPIFindDefaultMsgStore( GetMAPISession(), &cbeid, &lpeid);
    if( FAILED( hr))
        return( TRUE);
    ASSERTERROR( cbeid != 0, "Entry ID count should not be zero.");
    ASSERTERROR( lpeid != NULL, "NULL lpeid pointer");

    // Open the default message store.
    DEBUGACTION( "Opening Default Message Store");
    hr = GetMAPISession()->OpenMsgStore( 0, cbeid, lpeid, NULL, MDB_NO_DIALOG | MDB_WRITE, &m_pMDB);
    if( FAILED(hr))
        return( TRUE);
    ASSERTERROR( m_pMDB != NULL, "NULL lpMDB pointer");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Convert server name.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    int cb = WideCharToMultiByte( CP_ACP, 0, GetDNHomeMDB(), -1, NULL, 0, NULL, NULL);
    if( cb == 0)
    {
        HR_LOG( E_FAIL);
        return( TRUE);
    }

    CString sServer;
    char* pszServer = sServer.GetBuffer(cb);
    cb = WideCharToMultiByte( CP_ACP, 0, GetDNHomeMDB(), -1, pszServer, cb, NULL, NULL);
    if( cb == 0)
    {
        HR_LOG( E_FAIL);
        return( TRUE);
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Convert container distinguished name.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    cb = WideCharToMultiByte( CP_ACP, 0, GetDN(), -1, NULL, 0, NULL, NULL);
    if( cb == 0)
    {
        HR_LOG( E_FAIL);
        return( TRUE);
    }

    CString sDN;
    char* pszDN = sDN.GetBuffer(cb);
    cb = WideCharToMultiByte( CP_ACP, 0, GetDN(), -1, pszDN, cb, NULL, NULL);
    if( cb == 0)
    {
        HR_LOG( E_FAIL);
        return( TRUE);
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Now logon to the message database of this gateway.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrMailboxLogon(
        GetMAPISession(),    // Ptr to MAPI session handle.
        m_pMDB,
        pszServer,           // Ptr to server DN.
        pszDN,               // Ptr to mailbox DN.
        &m_pGWmdb);          // Ptr to gateway mailbox message store ptr.

    if( FAILED( hr))
        return( TRUE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the m_cbFolder object which will connect it to the
// IDC_COMBO_FOLDERS combo box window, and get list of folders.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( HrSubclassWindow( IDC_COMBO_FOLDERS, m_cbFolders) != NOERROR)
        return( TRUE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the m_lbMsgs object by connecting it to the IDC_LIST_MSGS list
// box window.  Also set the tab stops for the list box.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( HrSubclassWindow( IDC_LIST_MSGS, m_lbMsgs) != NOERROR)
        return( TRUE);

    int nTabStops[] = { 6, 12, 76, 215};
    m_lbMsgs.SetTabStops( ARRAY_CNT( nTabStops), nTabStops);
    m_lbMsgs.SetHorizontalExtent( 1000);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Connect the buttons to CButton objects.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( HrSubclassWindow( IDC_CMD_DELETE, m_btnDelete) != NOERROR)
        return( TRUE);

    if( HrSubclassWindow( IDC_CMD_NDR, m_btnNDR) != NOERROR)
        return( TRUE);

    if( HrSubclassWindow( IDC_CMD_REFRESH, m_btnRefresh) != NOERROR)
        return( TRUE);
    m_btnRefresh.EnableWindow( TRUE);

    if( HrSubclassWindow( IDC_CMD_DETAILS, m_btnDetails) != NOERROR)
        return( TRUE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // We succeed, do not display a message.
    bInitStatus.Succeeded();

return( TRUE);  // return TRUE unless you set the focus to a control
}

// -----------------------------------------------------------------------------
// Do the cleanup work for member data.
// MAPI Interfaces must be released here.  We can not wait until the DLL exits
// to release the object interfaces since the code goes single threaded at that
// time and causes deadlocks while waiting for MAPI threads to exit.
// -----------------------------------------------------------------------------

void CQueViewDlg::OnDestroy()
{
CAdminDialog::OnDestroy();
    ULRELEASE( m_pMDB); // Must be done here.  See note above.
    HRESULT hr = HrMailboxLogoff( &m_pGWmdb);
}

//$--CQueViewDlg::OnSelChangeComboFolders()-------------------------------------
// There are three functions that deal with changes in the folders combo box.
// They are: OnTimer, OnKillFocusComboFolders, and this one.  The reason we can
// not just refresh right here if the selection changes is that the refresh
// involves opening folders and reading the contents which takes a little bit
// of time.  If the user is scrolling through the list of folders using the
// arrow keys it gets slow and jerky.
//
// To deal with this we (re)set a timer each time the selection changes.  If
// the timer goes off the user has stopped scrolling through the list and we
// can refresh without it being jerky.  Also if the control looses focus we
// want to immediately refresh the folder and the message lists.
// -----------------------------------------------------------------------------

void CQueViewDlg::OnSelChangeComboFolders()
{
    // Kill the old timer that may be running so we can start it again.
    KillTimer( iRefreshTimerId);

    // Set the timer to delay opening the folder and reading its contents.
    SetTimer( iRefreshTimerId, nRefreshTime, NULL);
}

//$--CQueViewDlg::OnTimer()-----------------------------------------------------
// If the selection changed we refresh the combo box of folders, open the new
// folder, and then update the list box data.
//
// (See CQueViewDlg::OnSelChangeComboFolders() above for a full description.)
// -----------------------------------------------------------------------------

void CQueViewDlg::OnTimer( UINT nIDEvent)
{
    // We no longer need the timer.
    KillTimer( nIDEvent);

    // Refresh the dialog with the current data only
    // if the selection has truly changed.
    if( m_cbFolders.bSelectionChanged())
        OnCmdRefresh();
}

//$--CQueViewDlg::OnKillFocusComboFolders()-------------------------------------
// If the selection changed we refresh the combo box of folders, open the new
// folder, and then update the list box data.
//
// (See CQueViewDlg::OnSelChangeComboFolders() above for a full description.)
// -----------------------------------------------------------------------------

void CQueViewDlg::OnKillFocusComboFolders()
{
    // Has the selection has changed?
    if( m_cbFolders && m_cbFolders.bSelectionChanged())
    {   // YES, it has so kill the timer and refresh the
        // dialog with the current data.
        KillTimer( iRefreshTimerId);
        OnCmdRefresh();
    }
}

//$--CQueViewDlg::OnSelChangeListMsgs()-----------------------------------------
// Enable and disable the buttons depending upon the number of list box items
// that are selected.
// -----------------------------------------------------------------------------

void CQueViewDlg::OnSelChangeListMsgs()
{
int nCnt = m_lbMsgs.GetSelCount();

#if 1   // Set to 0 to force buttons on for testing purposes.
    // Button enabled when delete is available and at least one is selected.
    m_btnDelete.EnableWindow( m_cbFolders.bIsDelete() && nCnt != 0);

    // Button enabled when delete is available and at least one is selected.
    m_btnNDR.EnableWindow( m_cbFolders.bIsNDR() && m_cbFolders.bIsDefer() && nCnt != 0);
#else
    // Code to enable buttons when testing.
    m_btnDelete.EnableWindow( TRUE);
    m_btnNDR.EnableWindow( TRUE);
#endif

    // Button enabled when only one is selected.
    m_btnDetails.EnableWindow( nCnt == 1);
}

// -----------------------------------------------------------------------------
// Displays the details of a single message.  Should only get here when only
// one line is selected in the list box.
// -----------------------------------------------------------------------------

void CQueViewDlg::OnCmdDetails()
{
    DEBUGPUBLIC( "CQueViewDlg::OnCmdDetails()");
ASSERTERROR( m_lbMsgs.GetSelCount() == 1, "This button should be disabled when there are not 1 items selected.");
    ASSERTERROR( m_cbFolders.GetFolder() != NULL, "There should be a folder available.");

    // Get the index of the entry id for the selected message.
    int ii = -1;
    m_lbMsgs.GetSelItems( 1, &ii);
ASSERTERROR( ii >= 0 && ii < m_lbMsgs.GetCount(), "INVALID EID index.");

    // Open the selected message.
    ULONG        cb = m_lbMsgs.GetByteCnt( ii);
    LPENTRYID    pEID = m_lbMsgs.GetEID( ii);
    CMAPIMessage MAPIMsg( m_cbFolders.GetFolder(), cb, pEID);
    if( FAILED( MAPIMsg.m_hr))
    {   // Construction of MAPIMsg failed!  Probably messages are obsolete.
        MessageBox2( IDS_MESSAGE_GONE);
        OnCmdRefresh();
        return;
    }

    // List of properties that we are interested in.
    static SizedSPropTagArray( 13, sProps) =
    { 13,
        {
            PR_ARRIVAL_TIME,
            PR_DISPLAY_TO,
            PR_GW_ADMIN_OPERATIONS,
            PR_HASATTACH,
            PR_IMPORTANCE,
            PR_MESSAGE_CLASS,
            PR_MESSAGE_SIZE,
            PR_ORIGINATOR_ADDR,
            PR_ORIGINATOR_ADDRTYPE,
            PR_ORIGINATOR_NAME,
            PR_PRIORITY,
            PR_SENSITIVITY,
            PR_SUBJECT
        }
    };

    // Get the properties from the message using a CProperty interface object
    // to access and contain properties.
    CProperty iProp;
    CHRESULT hr = iProp.GetProps( MAPIMsg, (LPSPropTagArray) &sProps);
    if( FAILED( hr))
        return;

    // Load the dialog into memory as a template so the fonts
    // and language get set appropriatly.
    if( !m_hPropDlgTemplate)
    {
        m_hPropDlgTemplate = LoadDialogTemplate( IDD_PROPS);
        if( !m_hPropDlgTemplate)
        {
            hr = HR_LOG( E_FAIL);
            return;
        }
    }

    // Initialize the dialog listing the properties.
    CPropDlg PropDlg( &iProp);
    if( !PropDlg.InitModalIndirect( m_hPropDlgTemplate))
    {
        hr = HR_LOG( E_FAIL);
        return;
    }

    // Show the dialog listing the properties.
    PropDlg.DoModal();
}

// -----------------------------------------------------------------------------

void CQueViewDlg::OnCmdDelete()
{
    int iUserAnswer;
    if( m_cbFolders.bIsDefer())
    {
        CDelMsgDlg DelMsgDlg;
        iUserAnswer = DelMsgDlg.DoModal();
    }
    else
    {
        iUserAnswer = MessageBox2( IDS_DELETENOW, IDS_DELETEMSGTITLE,
                                  MB_YESNO | MB_ICONQUESTION);
    }

    switch( iUserAnswer)
    {
        case IDYES:
        case IDC_CMD_DELETE_NOW:
            DeleteMsgsNow();
            break;

        case IDC_CMD_DELETE_BY_GATEWAY:
            DeferMsgsAction( ADMINOP_DELETE);
            break;

        default:
            break;
    }
}

//$--CQueViewDlg::DeleteMsgsNow()-----------------------------------------------
// Delete the selected messages right now using MAPI.
// -----------------------------------------------------------------------------

void CQueViewDlg::DeleteMsgsNow()
{
    DEBUGPUBLIC( "CQueViewDlg::DeleteMsgsNow()");
    // Get a pointer to the folder that contain the messages we will be working on.
    LPMAPIFOLDER pFolder = m_cbFolders.GetFolder();
if( !pFolder)
    {
        return;
    }

    // Determine the count of messages to delete.
    int nSelCnt = m_lbMsgs.GetSelCount();
    if( nSelCnt < 1)
    {
        return;
    }

    // Allocate an array to hold the indexes of messages to delete.
    CDynamicArray< int> IndexArray( nSelCnt);
    if( !IndexArray)
    {
        HR_LOG( E_OUTOFMEMORY);
        return;
    }

    // Create the array needed to delete all selected messages at once.
    m_lbMsgs.GetSelItems( nSelCnt, IndexArray);
    CBinArray EIDList( nSelCnt);
    int* pIndex = IndexArray;
    while( nSelCnt)
    {
        if( !EIDList.bAdd( m_lbMsgs.GetByteCnt( *pIndex), (LPBYTE) m_lbMsgs.GetEID( *pIndex)))
        {
            HR_LOG( E_FAIL);
            return;
        }

        // Move on to next message.
        pIndex ++;
        nSelCnt--;
    }

    // Delete all selected messages now.
    CHRESULT hr = pFolder->DeleteMessages( EIDList, (ULONG) GetSafeHwnd(), NULL, MESSAGE_DIALOG);
    if( FAILED( hr))
        return;

    // Refresh the dialog with the messages that are now in the folder.
    OnCmdRefresh();
}

//$--CQueViewDlg::DeferMsgsAction()---------------------------------------------
// This function sets the specified action flags in the PR_GW_ADMIN_OPERATIONS
// property of each message that is selected.  The only two action flags that
// are appropriate for setting are ADMINOP_DELETE or ADMINOP_NDR.
//
// This affectivly causes defered deletions or NDRs of a messages.
// -----------------------------------------------------------------------------

void CQueViewDlg::DeferMsgsAction(
    ULONG ulAction)     // Should be either ADMINOP_DELETE or ADMINOP_NDR.
{
    // Get a pointer to the folder that contain the messages we will be working on.
    LPMAPIFOLDER pFolder = m_cbFolders.GetFolder();
if( !pFolder)
        return;

    // Determine the count of messages to modify.
    int nSelCnt = m_lbMsgs.GetSelCount();
    if( nSelCnt < 1)
        return;

    // Allocate an array to hold the indexes of messages to modify.
    CDynamicArray< int> IndexArray( nSelCnt);
    if( !IndexArray)
    {
        HR_LOG( E_OUTOFMEMORY);
        return;
    }

    // Mark all selected messages to be modified.
    m_lbMsgs.GetSelItems( nSelCnt, IndexArray);
    int* pIndex = IndexArray;
    for( ; nSelCnt; pIndex ++, nSelCnt--)
    {
        ULONG cb = m_lbMsgs.GetByteCnt( *pIndex);
        LPENTRYID pEID = m_lbMsgs.GetEID( *pIndex);

        // Open the message.
        CMAPIMessage MAPIMsg( pFolder, cb, pEID, 0);
        if( FAILED( MAPIMsg.m_hr))
        {   // Log error but continue to try and modify the other messages.
            continue;   // Move on to next message.
        }

        // We have an opened message.  Now set the properties.
        SPropValue PropVal;
        PropVal.ulPropTag = PR_GW_ADMIN_OPERATIONS;
        PropVal.Value.ul = m_lbMsgs.dwGetFlags( *pIndex) | ulAction;
        CHRESULT hr = MAPIMsg->SetProps( 1, &PropVal, NULL);
        if( FAILED( hr))
        {   // Log error but continue to try and modify the other messages.
            continue;   // Move on to next message.
        }

        // We modified the properties successfully.  Now save the changes.
        hr = MAPIMsg->SaveChanges(0);
        if( FAILED( hr))
        {   // Log error but continue to try and modify the other messages.
            continue;   // Move on to next message.
        }
    }
    OnCmdRefresh();
}

// -----------------------------------------------------------------------------

void CQueViewDlg::OnCmdNdr()
{
    DEBUGPUBLIC( "CQueViewDlg::OnCmdNdr()");
    int iUserAnswer = MessageBox2( IDS_NDRNOW, IDS_NDRMSGTITLE,
                              MB_YESNO | MB_ICONQUESTION);

    if( iUserAnswer == IDYES)
        DeferMsgsAction( ADMINOP_NDR);
}

// -----------------------------------------------------------------------------


QUEVIEW.H

// -----------------------------------------------------------------------------
// QueView.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _QUEVIEW_H_
#define _QUEVIEW_H_

// -----------------------------------------------------------------------------

#include "ExAdmin.h"
#include "resource.h"
#include "PrpLstCb.h"

// -----------------------------------------------------------------------------
// Definition of a class that contains the methods and objects necessary to
// handle the Queue Viewer property sheet as an extension dialog.
// -----------------------------------------------------------------------------

class CQueViewDlg : public CAdminDialog
{
public:
    // A default constructor is the only one we will need.
    CQueViewDlg();
    ~CQueViewDlg();

protected:
    BOOL                    m_bInitilized;     // Initialization succeeded if TRUE.
    BOOL                    m_bMTSIn;          // TRUE if MTS in is to be viewed.
    LPMDB                   m_pMDB;            // Ptr to MDB of admin session.
    LPMDB                   m_pGWmdb;          // Ptr to gateway's message store.
    HGLOBAL                 m_hPropDlgTemplate;// Template to initialize the property dialog.

    // Derived from CWnd.
    CPropsListBox           m_lbMsgs;          // List box containing entry ids of messages.
    CPropsComboBox          m_cbFolders;       // Combo box containing entry ids of folders.
    CButton                 m_btnDelete;       // Delete button object.
    CButton                 m_btnNDR;          // NDR button object.
    CButton                 m_btnRefresh;      // Refresh button object.
    CButton                 m_btnDetails;      // Details button object.

    void DeleteMsgsNow();
    void DeferMsgsAction(
        ULONG ulAction);     // Should be either ADMINOP_DELETE or ADMINOP_NDR.

    // Called to determine if you supply help.  Return TRUE if you do, defaults to FALSE.
    virtual BOOL bHasHelp();

    // Called to start help.  Default does nothing.
    virtual VOID DoHelp();

    // Called when the property sheet has been activated or reactivated.
    virtual void Refresh();

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Class wizard stuff.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public:
//{{AFX_DATA(CQueViewDlg)
enum { IDD = IDD_QUEUE_VIEW };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CQueViewDlg)
public:
protected:
//}}AFX_VIRTUAL

// Generated message map functions
//{{AFX_MSG(CQueViewDlg)
afx_msg void OnCmdRefresh();
virtual BOOL OnInitDialog();
afx_msg void OnDestroy();
afx_msg void OnCmdDetails();
afx_msg void OnSelChangeComboFolders();
afx_msg void OnSelChangeListMsgs();
afx_msg void OnCmdDelete();
afx_msg void OnCmdNdr();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnKillFocusComboFolders();
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

// Declare an instances of a CAdminDialog derived class.
CQueViewDlg QueViewDlg;

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif // _QUEVIEW_H_


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by queview.rc
//
#define IDS_YOUR                        1
#define IDS_QUEUE_VIEW                  2
#define IDS_MESSAGE_GONE                3
#define IDS_DELETENOW                   4
#define IDS_DELETEMSGTITLE              5
#define IDS_NDRNOW                      6
#define IDS_NDRMSGTITLE                 7
#define IDD_YOUR                        129
#define IDD_QUEUE_VIEW                  130
#define IDD_PROPS                       137
#define IDD_DELETE_MESSAGE              138
#define IDD_DIALOG1                     139
#define IDC_ADMINICON                   1000
#define IDC_TITLE                       1001
#define IDC_LIST_MSGS                   1003
#define IDC_CMD_DELETE                  1004
#define IDC_CMD_DETAILS                 1005
#define IDC_CMD_NDR                     1006
#define IDC_CMD_REFRESH                 1007
#define IDC_COMBO_FOLDERS               1014
#define IDC_LIST_PROPS                  1015
#define IDC_CMD_DELETE_NOW              1016
#define IDC_CMD_DELETE_BY_GATEWAY       1017
#define IDC_CMD_HELP                    1019
#define IDC_EDIT1                       1019
#define IDC_LIST1                       1020

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        141
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1028
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


REPORT.C

// --report.c-------------------------------------------------------------------
//
//  Module containing report functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

//$--REPORT_T-------------------------------------------------------------------
//  Report type.
// -----------------------------------------------------------------------------
typedef enum __REPORT_T
{
    NDR = 0,        // non-delivery report
    DR,             // delivery report
    RN,             // read notification report
    NRN,            // non-read notification report
    REPORT_LAST     // all report values are less than this
} REPORT_T;

#define VALID_REPORT_T(x) (((x) >= ((ULONG)0)) && \
                            ((x) <= ((ULONG)REPORT_LAST)))

#include "report.chk"

//$--HrEDKReportSubject---------------------------------------------------------
//  Return a report subject.
// -----------------------------------------------------------------------------
static HRESULT HrEDKReportSubject(          // RETURNS: return code
    IN REPORT_T ReportType,                 // report type
    IN LPTSTR lpszSubject,                  // pointer to subject
    OUT LPTSTR *lppszReportSubject)         // pointer to report subject
{
    HRESULT hr                  = NOERROR;
    ULONG   cch                 = 0;
    LPTSTR  lpszOrigSubject     = NULL;
    LPTSTR  lpszReportPrefix    = NULL;
    SCODE   sc                  = 0;
    ULONG   cBytes              = 0;

    DEBUGPRIVATE("HrEDKReportSubject()\n");

    hr = CHK_HrEDKReportSubject(
        ReportType,
        lpszSubject,
        lppszReportSubject);

    if(FAILED(hr))
        RETURN(hr);

    *lppszReportSubject = NULL;

    cch = 0;

    if(lpszSubject != NULL)
    {
        cch = lstrlen(lpszSubject);
    }

    if(cch == 0)
    {
        lpszOrigSubject = TEXT("");
    }
    else
    {
        lpszOrigSubject = lpszSubject;
    }

    switch(ReportType)
    {
    case NDR:
        lpszReportPrefix = TEXT("Undeliverable: ");
        break;
    case DR:
        lpszReportPrefix = TEXT("Delivered: ");
        break;
    case RN:
        lpszReportPrefix = TEXT("Read: ");
        break;
    case NRN:
        lpszReportPrefix = TEXT("Unread: ");
        break;
    default:
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cch += lstrlen(lpszReportPrefix);

    cch++;  // terminating NULL character

    cBytes = cch * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(cBytes, lppszReportSubject);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    _tcscpy(*lppszReportSubject, lpszReportPrefix);
    _tcscat(*lppszReportSubject, lpszOrigSubject);

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszReportSubject);
    }

    RETURN(hr);
}

//$--HrEDKReportMessageClass----------------------------------------------------
//  Return a report message class.
// -----------------------------------------------------------------------------
static HRESULT HrEDKReportMessageClass(     // RETURNS: return code
    IN REPORT_T ReportType,                 // report type
    IN LPTSTR lpszMessageClass,             // pointer to message class
    OUT LPTSTR *lppszReportMessageClass)    // pointer to report message class
{
    HRESULT hr                  = NOERROR;
    ULONG   cch                 = 0;
    LPTSTR  lpszMsgClass        = NULL;
    LPTSTR  lpszReportSuffix    = NULL;
    SCODE   sc                  = 0;
    ULONG   cBytes              = 0;

    DEBUGPRIVATE("HrEDKReportMessageClass()\n");

    hr = CHK_HrEDKReportMessageClass(
        ReportType,
        lpszMessageClass,
        lppszReportMessageClass);

    if(FAILED(hr))
        RETURN(hr);

    *lppszReportMessageClass = NULL;

    cch = lstrlen(lpszMessageClass);

    if(cch == 0)
    {
        lpszMsgClass = TEXT("IPM.NOTE");
        cch = lstrlen(lpszMsgClass);
    }
    else
    {
        lpszMsgClass = lpszMessageClass;
    }

    cch += lstrlen(TEXT("REPORT."));

    switch(ReportType)
    {
    case NDR:
        lpszReportSuffix = TEXT(".NDR");
        break;
    case DR:
        lpszReportSuffix = TEXT(".DR");
        break;
    case RN:
        lpszReportSuffix = TEXT(".IPNRN");
        break;
    case NRN:
        lpszReportSuffix = TEXT(".IPNNRN");
        break;
    default:
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    cch += lstrlen(lpszReportSuffix);

    cch++;  // terminating NULL character

    cBytes = cch * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(cBytes, lppszReportMessageClass);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    _tcscpy(*lppszReportMessageClass, TEXT("REPORT."));
    _tcscat(*lppszReportMessageClass, lpszMsgClass);
    _tcscat(*lppszReportMessageClass, lpszReportSuffix);

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszReportMessageClass);
    }

    RETURN(hr);
}

//$--HrEDKSetReportMessageClass-------------------------------------------------
//  Set the report message class.
// -----------------------------------------------------------------------------
static HRESULT HrEDKSetReportMessageClass(  // RETURNS: return code
    IN REPORT_T ReportType,                 // report type
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN OUT LPMESSAGE lpReport)              // pointer to report
{
    HRESULT hr                      = NOERROR;
    HRESULT hrT                     = 0;
    ULONG   cbMessageClass          = 0;
    LPTSTR  lpszMessageClass        = NULL;
    LPTSTR  lpszReportMessageClass  = NULL;

    DEBUGPRIVATE("HrEDKSetReportMessageClass()\n");

    hr = CHK_HrEDKSetReportMessageClass(
        ReportType,
        lpMessage,
        lpReport);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPIGetPropString(
        (LPMAPIPROP)lpMessage,
        PR_MESSAGE_CLASS,
        &cbMessageClass,
        (LPVOID *)&lpszMessageClass);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrEDKReportMessageClass(
        ReportType,
        lpszMessageClass,
        &lpszReportMessageClass);

    MAPIFREEBUFFER(lpszMessageClass);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropString(
        (LPMAPIPROP)lpReport,
        PR_MESSAGE_CLASS,
        (LPVOID)lpszReportMessageClass);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpszMessageClass);

    MAPIFREEBUFFER(lpszReportMessageClass);

    RETURN(hr);
}

//$--HrEDKEnvelopeMessageClass--------------------------------------------------
//  Return an envelope message class.
// -----------------------------------------------------------------------------
static HRESULT HrEDKEnvelopeMessageClass(   // RETURNS: return code
    IN LPTSTR lpszMessageClass,             // pointer to message class
    OUT LPTSTR *lppszEnvelopeMessageClass)  // pointer to envelope message class
{
    HRESULT hr                  = NOERROR;
    ULONG   cch                 = 0;
    LPTSTR  lpszMsgClass        = NULL;
    LPTSTR  lpszReportSuffix    = NULL;
    SCODE   sc                  = 0;
    ULONG   cBytes              = 0;

    DEBUGPRIVATE("HrEDKEnvelopeMessageClass()\n");

    hr = CHK_HrEDKEnvelopeMessageClass(
        lpszMessageClass,
        lppszEnvelopeMessageClass);

    if(FAILED(hr))
        RETURN(hr);

    *lppszEnvelopeMessageClass = NULL;

    cch = lstrlen(lpszMessageClass);

    if(cch == 0)
    {
        lpszMsgClass = "IPM.NOTE";
        cch = lstrlen(lpszMsgClass);
    }
    else
    {
        lpszMsgClass = lpszMessageClass;
    }

    cch += lstrlen(TEXT("ENVELOPE."));

    cch++;  // terminating NULL character

    cBytes = cch * sizeof(TCHAR);

    sc = MAPIAllocateBuffer(cBytes, lppszEnvelopeMessageClass);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    _tcscpy(*lppszEnvelopeMessageClass, TEXT("ENVELOPE."));
    _tcscat(*lppszEnvelopeMessageClass, lpszMsgClass);

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszEnvelopeMessageClass);
    }

    RETURN(hr);
}

//$--HrEDKCopyReportProps-------------------------------------------------------
//  Copy report properties.
// -----------------------------------------------------------------------------
static HRESULT HrEDKCopyReportProps(        // RETURNS: return code
    IN REPORT_T ReportType,                 // report type
    IN LPMESSAGE lpReport,                  // pointer to report
    IN ULONG cbSubjectMtsId,                // count of bytes in subject MTS-ID
    IN LPBYTE lpSubjectMtsId,               // pointer to subject MTS-ID
    IN LPTSTR lpszReportName,               // pointer to report name
    IN ULONG cbReportEntryId,               // count of bytes in report entry ID
    IN LPENTRYID lpReportEntryId,           // pointer to report entry ID
    IN OUT LPMESSAGE lpReportEnvelope)      // pointer to report envelope
{
    HRESULT hr = NOERROR;

    DEBUGPRIVATE("HrEDKCopyReportProps()\n");

    hr = CHK_HrEDKCopyReportProps(
        ReportType,
        lpReport,
        cbSubjectMtsId,
        lpSubjectMtsId,
        lpszReportName,
        cbReportEntryId,
        lpReportEntryId,
        lpReportEnvelope);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPIMoveOneProp(
        (LPMAPIPROP)lpReport,
        PR_SUBJECT,
        PR_SUBJECT,
        FALSE,
        TRUE,
        (LPMAPIPROP)lpReportEnvelope);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrEDKSetReportMessageClass(
        ReportType,
        lpReport,
        lpReportEnvelope);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropBinary(
        (LPMAPIPROP)lpReportEnvelope,
        PR_MTS_SUBJECT_ID,
        cbSubjectMtsId,
        (LPVOID)lpSubjectMtsId);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropString(
        (LPMAPIPROP)lpReportEnvelope,
        PR_REPORT_DESTINATION_NAME,
        (LPVOID)lpszReportName);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropBinary(
        (LPMAPIPROP)lpReportEnvelope,
        PR_REPORT_DESTINATION_ENTRYID,
        cbReportEntryId,
        (LPVOID)lpReportEntryId);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropBoolean(
        (LPMAPIPROP)lpReportEnvelope,
        PR_DELETE_AFTER_SUBMIT,
        TRUE);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

// These strings are explicitly not localized.

static const char lpszReport[] = "REPORT.";
static const char lpszIPNRN[] = ".IPNRN";
static const char lpszIPNNRN[] = ".IPNNRN";
static const UINT  cchReportClass = sizeof(lpszReport) + sizeof(lpszIPNNRN);

static const enum
{
IREPORT = 0, ICONVERSATION, ICONVERSATIONTOPIC, ICONVERSATIONIDX, ISEARCH,
IMSGCLASS, ISUBJECT, ISUBJECTPREFX, ISUBJECTNORM, ISENDERNAME, ISENDEREID,
ISENDADRTYPE, ISENDEMAIL, IREPORTNAME, IREPORTEID, IRECEIVENAME, IRECEIVEEID,
IPRIORITY, IIMPORTANCE, ISENTREPNAME, ISENTREPEID, IRCVDREPNAME, IRCVDREPEID,
IMSGDELTIME, ISUBMITTIME, IDISPTO, IDISPCC, IDISPBCC, ISENSITIVITY,
INDRREQUEST, IDRREQUEST, IRNREQUEST, RRN_PROP_MAX
};

static const enum
{
IRECIPTYPE = 0, IRECIPDISPNM, IRECIPEID, IRECIPADRTYPE, IRECIPEMAIL,
RRN_RECIP_PROP_MAX
};

//$--HrReadReceipt--------------------------------------------------------------
//  Create a read/non-read notification report.
// -----------------------------------------------------------------------------
static HRESULT HrReadReceipt(           // RETURNS: return code
    IN ULONG ulFlags,                   // flags
IN LPSTR lpszReportText,            // report text
IN LPSTR lpszSubjectPrefix,         // subject prefix
    IN LPMESSAGE lpMessage,             // message
    OUT LPMESSAGE lpReadReceipt)        // report
{
SCODEsc= S_OK;
HRESULThr= hrSuccess;
ADRLISTAdrList             = {0};
BOOLfSetError= FALSE;
BOOLfSubjectOpenProperty= FALSE;
CHARchNull= 0;
FILETIME ft                  = {0};
LPSPropValuelpOrigProps= NULL;
LPSPropValuelpRecipVals= NULL;
LPSTRlpszMessageClass= NULL;
LPSTRlpszReadSubject= NULL;
LPSTRlpszSubjOrig= NULL;
SPropValue rgMsgVals[ RRN_PROP_MAX ] = {0};
SYSTEMTIME st                  = {0};
UINTiNumProp            = 0;
ULONGulSize              = 0;
ULONGulT                 = 0;
#ifdef DEBUG
LPSPropProblemArraylpProblem= NULL;
#endif

static SizedSPropTagArray(RRN_PROP_MAX, ptagaOrigMsg) =
{
RRN_PROP_MAX,
{
PR_REPORT_TAG,
PR_CONVERSATION_KEY,
PR_CONVERSATION_TOPIC_A,
PR_CONVERSATION_INDEX,
PR_SEARCH_KEY,// must
PR_MESSAGE_CLASS_A,// must
PR_SUBJECT_A,
PR_SUBJECT_PREFIX_A,
PR_NORMALIZED_SUBJECT_A,
PR_SENDER_NAME_A,
PR_SENDER_ENTRYID,// must
PR_SENDER_ADDRTYPE,
PR_SENDER_EMAIL_ADDRESS,
PR_REPORT_NAME_A,
PR_REPORT_ENTRYID,
PR_RECEIVED_BY_NAME_A,
PR_RECEIVED_BY_ENTRYID,
PR_PRIORITY,
PR_IMPORTANCE,
PR_SENT_REPRESENTING_NAME_A,
PR_SENT_REPRESENTING_ENTRYID,
PR_RCVD_REPRESENTING_NAME_A,
PR_RCVD_REPRESENTING_ENTRYID,
PR_MESSAGE_DELIVERY_TIME,// must
PR_CLIENT_SUBMIT_TIME,
PR_DISPLAY_TO_A,
PR_DISPLAY_CC_A,
PR_DISPLAY_BCC_A,
PR_SENSITIVITY,

/*Properties from here out are just place
 *holders for values we set into a message
 *regardless of the value in the original
 *message
 */

PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED,
PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,
PR_READ_RECEIPT_REQUESTED
}
};

static enum
{
outIREPORTTEXT = 0, outIMESSAGECLASS, outIREPORTTIME, outIORIGAUTHOREID,
outIORIGDELIVERYTIME, outIPARENTKEY, outIDELETEAFTERSUBMIT,
outCMAXIDX
};

    hr = CHK_HrReadReceipt(
        ulFlags,
    lpszReportText,
    lpszSubjectPrefix,
        lpMessage,
        lpReadReceipt);

    if(FAILED(hr))
        RETURN(hr);

#ifdef DEBUG

// Determine if we should fill out the out the empty message.

hr = HrGetOneProp((LPMAPIPROP)lpMessage, PR_READ_RECEIPT_REQUESTED, &lpOrigProps);
if (FAILED(hr) && hr != ResultFromScode(MAPI_E_NOT_FOUND))
{
MODULE_ERROR("HrReadReceipt: Failure to get prop");
goto exit;
}

ASSERTERROR(!(GetScode(hr) == MAPI_E_NOT_FOUND || !lpOrigProps->Value.l),
"PR_READ_RECEIPT_REQUESTED Flag not found");

MAPIFreeBuffer(lpOrigProps);
lpOrigProps = NULL;
#endif

// get the props we need from the original message

hr = lpMessage->lpVtbl->GetProps(lpMessage,
(LPSPropTagArray)&ptagaOrigMsg, 0, // ansi
&ulT, &lpOrigProps);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure to get Original msg props");
fSetError = TRUE;
goto exit;
}

    // if message delivery time is not found then add it

if(lpOrigProps[IMSGDELTIME].ulPropTag != PR_MESSAGE_DELIVERY_TIME)
    {
    GetSystemTime(&st);
    SystemTimeToFileTime(&st, &ft);

    lpOrigProps[IMSGDELTIME].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
    lpOrigProps[IMSGDELTIME].Value.ft  = ft;
    }

// check to see if we have the "must" properties

if (lpOrigProps[ISEARCH].ulPropTag != PR_SEARCH_KEY
  || lpOrigProps[IMSGCLASS].ulPropTag != PR_MESSAGE_CLASS_A
  || lpOrigProps[ISENDEREID].ulPropTag != PR_SENDER_ENTRYID
  || lpOrigProps[IMSGDELTIME].ulPropTag != PR_MESSAGE_DELIVERY_TIME)
{
hr = HR_LOG(ResultFromScode(MAPI_E_NOT_FOUND));
MODULE_ERROR("HrReadReceipt: Required property(s) not found");
        goto exit;
}

// Get the original subject

if (lpOrigProps[ISUBJECTNORM].ulPropTag == PR_NORMALIZED_SUBJECT_A)
lpszSubjOrig = lpOrigProps[ISUBJECTNORM].Value.lpszA;
else if (lpOrigProps[ISUBJECT].ulPropTag == PR_SUBJECT_A)
lpszSubjOrig = lpOrigProps[ISUBJECT].Value.lpszA;
else if (lpOrigProps[ISUBJECT].Value.err == MAPI_E_NOT_ENOUGH_MEMORY)
{
//We've got a mondo subject.  Use OpenProperty...
//
LPSTREAM lpstreamSrc= NULL;
LPSTREAM lpstreamDst= NULL;
ULARGE_INTEGERuliRead = { 0 };
ULARGE_INTEGERuliWritten = { 0 };
ULARGE_INTEGERuliSize;
LARGE_INTEGERliMove;
ULONGcbWritten= 0;

// open the source Stream

hr = lpMessage->lpVtbl->OpenProperty(lpMessage, PR_SUBJECT_A,
&IID_IStream, 0, 0, (LPUNKNOWN *)&lpstreamSrc);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure calling OpenProperty on subject Src");
fSetError = TRUE;
goto openprop_cleanup;
}

// open the destinationStream

hr = lpReadReceipt->lpVtbl->OpenProperty(lpReadReceipt, PR_SUBJECT_A,
&IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY,
(LPUNKNOWN *)&lpstreamDst);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure calling OpenProperty on subject Dst");
fSetError = TRUE;
goto openprop_cleanup;
}

// Copy the Subject header to the destination

hr = lpstreamDst->lpVtbl->Write(lpstreamDst, lpszSubjectPrefix,
lstrlenA(lpszSubjectPrefix), &cbWritten);

if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: OLE IStream Write failed");
goto openprop_cleanup;
}

ASSERTERROR((LONG)cbWritten == lstrlenA(lpszSubjectPrefix), "Write failed");

// Tell OLE ISTREAM to copy all of it

uliSize.HighPart = (ULONG) -1;
uliSize.LowPart  = (ULONG) -1;

hr = lpstreamSrc->lpVtbl->CopyTo(lpstreamSrc, lpstreamDst, uliSize,
&uliRead, &uliWritten);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: OLE IStream CopyTo failed");
goto openprop_cleanup;
}

ASSERTERROR(uliWritten.LowPart == uliRead.LowPart, "Write failed");

hr = lpstreamDst->lpVtbl->Commit(lpstreamDst, 0);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: OLE IStream Commit failed");
goto openprop_cleanup;
}

// Handle PR_ORIGINAL_SUBJECT

UlRelease(lpstreamDst);
lpstreamDst = NULL;

hr = lpReadReceipt->lpVtbl->OpenProperty(lpReadReceipt,
PR_ORIGINAL_SUBJECT_A,  &IID_IStream, 0,
MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN *)&lpstreamDst);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure calling OpenProperty on Orig Subject Dst");
fSetError = TRUE;
goto openprop_cleanup;
}

// rewind the source

liMove.HighPart = 0;
liMove.LowPart  = 0;

hr = lpstreamSrc->lpVtbl->Seek(lpstreamSrc, liMove, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure calling Seek on subject Src stream");
goto openprop_cleanup;
}

// Tell OLE ISTREAM to copy all of it

uliSize.HighPart = (ULONG) -1;
uliSize.LowPart  = (ULONG) -1;

hr = lpstreamSrc->lpVtbl->CopyTo(lpstreamSrc, lpstreamDst, uliSize,
&uliRead, &uliWritten);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: OLE IStream CopyTo failed");
goto openprop_cleanup;
}

ASSERTERROR(uliWritten.LowPart == uliRead.LowPart, "Write failed");

hr = lpstreamDst->lpVtbl->Commit(lpstreamDst, 0);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: OLE IStream Commit failed");
goto openprop_cleanup;
}

fSubjectOpenProperty = TRUE;

openprop_cleanup:

UlRelease(lpstreamSrc);
UlRelease(lpstreamDst);

if (FAILED(hr))
{
goto exit;
}
}
else
lpszSubjOrig = &chNull;

if (!fSubjectOpenProperty)
{
ulSize = lstrlenA (lpszSubjOrig) + lstrlenA (lpszSubjectPrefix) + 1;
sc = MAPIAllocateBuffer(ulSize, (LPVOID *) &lpszReadSubject);
if (FAILED(sc))
{
hr = HR_LOG(ResultFromScode(sc));
MODULE_ERROR("HrReadReceipt: OOM");
goto exit;
}

wsprintfA (lpszReadSubject,
"%s%s",
lpszSubjectPrefix,
lpszSubjOrig);
}

// Generate the Report Message Class from the existing message class

ASSERTERROR(
    lpOrigProps[IMSGCLASS].ulPropTag == PR_MESSAGE_CLASS_A,
    "Message class is missing");

ulSize = cchReportClass + lstrlenA(lpOrigProps[IMSGCLASS].Value.lpszA) + 1;
sc = MAPIAllocateBuffer(ulSize, (LPVOID *)&lpszMessageClass);
if (FAILED(sc))
{
hr = HR_LOG(ResultFromScode(sc));
MODULE_ERROR("HrReadReceipt: OOM for RRN report message class");
        goto exit;
}

wsprintfA(lpszMessageClass,
"%s%s%s",
lpszReport,
lpOrigProps[IMSGCLASS].Value.lpszA,
(ulFlags & MAPI_NON_READ) ? (LPSTR) lpszIPNNRN : (LPSTR) lpszIPNRN);

//
// Setup the "must" prop vals
//

rgMsgVals[outIREPORTTEXT].ulPropTag  = PR_REPORT_TEXT_A;
rgMsgVals[outIREPORTTEXT].Value.lpszA = lpszReportText;

rgMsgVals[outIMESSAGECLASS].ulPropTag = PR_MESSAGE_CLASS_A;
rgMsgVals[outIMESSAGECLASS].Value.lpszA= lpszMessageClass;

GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);

rgMsgVals[outIREPORTTIME].ulPropTag= PR_REPORT_TIME;
rgMsgVals[outIREPORTTIME].Value.ft= ft;

// PR_ORIGINAL_AUTHOR_?? is set from PR_SENDER_??

rgMsgVals[outIORIGAUTHOREID].ulPropTag = PR_ORIGINAL_AUTHOR_ENTRYID;
rgMsgVals[outIORIGAUTHOREID].Value.bin= lpOrigProps[ISENDEREID].Value.bin;

rgMsgVals[outIORIGDELIVERYTIME].ulPropTag  = PR_ORIGINAL_DELIVERY_TIME;
rgMsgVals[outIORIGDELIVERYTIME].Value.ft = lpOrigProps[IMSGDELTIME].Value.ft;

// Set PR_PARENT_KEY with PR_MESSAGE_KEY of the original message.

rgMsgVals[outIPARENTKEY].ulPropTag = PR_PARENT_KEY;
rgMsgVals[outIPARENTKEY].Value.bin= lpOrigProps[ISEARCH].Value.bin;

rgMsgVals[outIDELETEAFTERSUBMIT].ulPropTag = PR_DELETE_AFTER_SUBMIT;
rgMsgVals[outIDELETEAFTERSUBMIT].Value.b = TRUE;

iNumProp = outCMAXIDX;

//
// Setup the variable Prop Vals
//

if (lpOrigProps[ISENDERNAME].ulPropTag == PR_SENDER_NAME_A)
{
rgMsgVals[iNumProp].ulPropTag = PR_ORIGINAL_AUTHOR_NAME_A;
rgMsgVals[iNumProp].Value.lpszA= lpOrigProps[ISENDERNAME].Value.lpszA;
iNumProp++;
}

// Check to see if we handled Subject props by OpenProperty...

if (!fSubjectOpenProperty)
{
rgMsgVals[iNumProp].ulPropTag = PR_SUBJECT_A;
rgMsgVals[iNumProp].Value.lpszA = lpszReadSubject;
iNumProp++;

if (lpOrigProps[ISUBJECT].ulPropTag == PR_SUBJECT_A)
{
rgMsgVals[iNumProp].ulPropTag= PR_ORIGINAL_SUBJECT_A;
rgMsgVals[iNumProp].Value.lpszA= lpOrigProps[ISUBJECT].Value.lpszA;
iNumProp++;
}
else
{
MODULE_ERROR("HrReadReceipt: PR_SUBJECT missing");
rgMsgVals[iNumProp].ulPropTag= PR_ORIGINAL_SUBJECT_A;
rgMsgVals[iNumProp].Value.lpszA= &chNull;
iNumProp++;
}
}

//The ordering here is slightly important.  PR_SUBJECT
//needs to be either first or last to aid in EMS processing
//
rgMsgVals[iNumProp].ulPropTag = PR_SUBJECT_PREFIX_A;
rgMsgVals[iNumProp].Value.lpszA = lpszSubjectPrefix;
iNumProp++;
rgMsgVals[iNumProp].ulPropTag = PR_NORMALIZED_SUBJECT_A;
rgMsgVals[iNumProp].Value.lpszA = lpszSubjOrig;
iNumProp++;

// If PR_SENT_REPRESENTING_?? exists copy to PR_RCVD_REPRESENTING_??

if (lpOrigProps[ISENTREPNAME].ulPropTag == PR_SENT_REPRESENTING_NAME_A)
{
rgMsgVals[iNumProp].ulPropTag = PR_RCVD_REPRESENTING_NAME_A;
rgMsgVals[iNumProp].Value.lpszA= lpOrigProps[ISENTREPNAME].Value.lpszA;
iNumProp++;
}

if (lpOrigProps[ISENTREPEID].ulPropTag == PR_SENT_REPRESENTING_ENTRYID)
{
rgMsgVals[iNumProp].ulPropTag = PR_RCVD_REPRESENTING_ENTRYID;
rgMsgVals[iNumProp].Value.bin= lpOrigProps[ISENTREPEID].Value.bin;
iNumProp++;
}

// If PR_RCVD_REPRESENTING_?? exists copy to PR_SENT_REPRESENTING_??

if (lpOrigProps[IRCVDREPNAME].ulPropTag == PR_RCVD_REPRESENTING_NAME_A)
{
rgMsgVals[iNumProp].ulPropTag  = PR_SENT_REPRESENTING_NAME_A;
rgMsgVals[iNumProp].Value.lpszA= lpOrigProps[IRCVDREPNAME].Value.lpszA;
iNumProp++;
}

if (lpOrigProps[IRCVDREPEID].ulPropTag == PR_RCVD_REPRESENTING_ENTRYID)
{
rgMsgVals[iNumProp].ulPropTag  = PR_SENT_REPRESENTING_ENTRYID;
rgMsgVals[iNumProp].Value.bin= lpOrigProps[IRCVDREPEID].Value.bin;
iNumProp++;
}

if (lpOrigProps[ISUBMITTIME].ulPropTag == PR_CLIENT_SUBMIT_TIME)
{
rgMsgVals[iNumProp].ulPropTag  = PR_ORIGINAL_SUBMIT_TIME;
rgMsgVals[iNumProp].Value.ft   = lpOrigProps[ISUBMITTIME].Value.ft;
iNumProp++;
}

if (lpOrigProps[IDISPTO].ulPropTag == PR_DISPLAY_TO_A)
{
rgMsgVals[iNumProp].ulPropTag= PR_ORIGINAL_DISPLAY_TO_A;
rgMsgVals[iNumProp].Value.lpszA = lpOrigProps[IDISPTO].Value.lpszA;
iNumProp++;
}

if (lpOrigProps[IDISPCC].ulPropTag == PR_DISPLAY_CC_A)
{
rgMsgVals[iNumProp].ulPropTag= PR_ORIGINAL_DISPLAY_CC_A;
rgMsgVals[iNumProp].Value.lpszA = lpOrigProps[IDISPCC].Value.lpszA;
iNumProp++;
}

if (lpOrigProps[IDISPBCC].ulPropTag == PR_DISPLAY_BCC_A)
{
rgMsgVals[iNumProp].ulPropTag= PR_ORIGINAL_DISPLAY_BCC_A;
rgMsgVals[iNumProp].Value.lpszA = lpOrigProps[IDISPBCC].Value.lpszA;
iNumProp++;
}

if (lpOrigProps[IPRIORITY].ulPropTag == PR_PRIORITY)
rgMsgVals[iNumProp++] = lpOrigProps[IPRIORITY];

if (lpOrigProps[IIMPORTANCE].ulPropTag == PR_IMPORTANCE)
rgMsgVals[iNumProp++] = lpOrigProps[IIMPORTANCE];

if (lpOrigProps[ISENSITIVITY].ulPropTag == PR_SENSITIVITY)
rgMsgVals[iNumProp++] = lpOrigProps[ISENSITIVITY];

if (lpOrigProps[ICONVERSATION].ulPropTag == PR_CONVERSATION_KEY)
rgMsgVals[iNumProp++] = lpOrigProps[ICONVERSATION];

if (lpOrigProps[ICONVERSATIONTOPIC].ulPropTag == PR_CONVERSATION_TOPIC_A)
rgMsgVals[iNumProp++] = lpOrigProps[ICONVERSATIONTOPIC];

if (lpOrigProps[ICONVERSATIONIDX].ulPropTag == PR_CONVERSATION_INDEX)
rgMsgVals[iNumProp++] = lpOrigProps[ICONVERSATIONIDX];

if (lpOrigProps[IREPORT].ulPropTag == PR_REPORT_TAG)
rgMsgVals[iNumProp++] = lpOrigProps[IREPORT];

//Turn off any and all report requests
//
rgMsgVals[iNumProp].ulPropTag = PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED;
rgMsgVals[iNumProp].Value.b = FALSE;
iNumProp++;
rgMsgVals[iNumProp].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
rgMsgVals[iNumProp].Value.b = FALSE;
iNumProp++;
rgMsgVals[iNumProp].ulPropTag = PR_READ_RECEIPT_REQUESTED;
rgMsgVals[iNumProp].Value.b = FALSE;
iNumProp++;

// Fill in recipient properties

    sc = MAPIAllocateBuffer(sizeof(SPropValue) * RRN_RECIP_PROP_MAX, (LPVOID *)&lpRecipVals);
if (FAILED(sc))
{
hr = HR_LOG(ResultFromScode(sc));
MODULE_ERROR("HrReadReceipt: OOM for recipient props");
        goto exit;
}

// If PR_REPORT_NAME & EID exists, use that as the MAPI_TO recipient
// else use PR_SENDER_NAME & EID.

lpRecipVals[IRECIPTYPE].ulPropTag = PR_RECIPIENT_TYPE;
lpRecipVals[IRECIPTYPE].Value.l = MAPI_TO;

lpRecipVals[IRECIPDISPNM].ulPropTag= PR_DISPLAY_NAME_A;
lpRecipVals[IRECIPEID].ulPropTag= PR_ENTRYID;

if (lpOrigProps[IREPORTNAME].ulPropTag == PR_REPORT_NAME_A
  && lpOrigProps[IREPORTEID].ulPropTag == PR_REPORT_ENTRYID)
  {
lpRecipVals[IRECIPDISPNM].Value.lpszA= lpOrigProps[IREPORTNAME].Value.lpszA;
lpRecipVals[IRECIPEID].Value.bin= lpOrigProps[IREPORTEID].Value.bin;
lpRecipVals[IRECIPADRTYPE].ulPropTag= PR_NULL;
lpRecipVals[IRECIPEMAIL].ulPropTag= PR_NULL;
}
else
{
lpRecipVals[IRECIPDISPNM].Value.lpszA= lpOrigProps[ISENDERNAME].Value.lpszA;
lpRecipVals[IRECIPEID].Value.bin= lpOrigProps[ISENDEREID].Value.bin;

lpRecipVals[IRECIPADRTYPE].ulPropTag= PR_ADDRTYPE_A;
lpRecipVals[IRECIPADRTYPE].Value.lpszA= lpOrigProps[ISENDADRTYPE].Value.lpszA;

lpRecipVals[IRECIPEMAIL].ulPropTag= PR_EMAIL_ADDRESS_A;
lpRecipVals[IRECIPEMAIL].Value.lpszA= lpOrigProps[ISENDEMAIL].Value.lpszA;
}

AdrList.cEntries = 1;

AdrList.aEntries[0].cValues = RRN_RECIP_PROP_MAX;
AdrList.aEntries[0].rgPropVals = lpRecipVals;

// SetProps and ModifyRecips.

#ifdef DEBUG
hr = lpReadReceipt->lpVtbl->SetProps(lpReadReceipt,
(ULONG)iNumProp, rgMsgVals, &lpProblem);
#else
hr = lpReadReceipt->lpVtbl->SetProps(lpReadReceipt,
(ULONG)iNumProp, rgMsgVals, NULL);
#endif
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure setting a slew of props");
fSetError = TRUE;
goto exit;
}

#ifdef DEBUG
if (lpProblem)
{
MAPIFreeBuffer(lpProblem);
}
#endif

hr = lpReadReceipt->lpVtbl->ModifyRecipients(
lpReadReceipt, 0, &AdrList);
if (FAILED(hr))
{
MODULE_ERROR("HrReadReceipt: Failure modifying recipients");
fSetError = TRUE;
goto exit;
}

exit:
MAPIFreeBuffer(lpOrigProps);
MAPIFreeBuffer(lpszMessageClass);
MAPIFreeBuffer(lpszReadSubject);
MAPIFreeBuffer(AdrList.aEntries[0].rgPropVals);

return(hr);
}

//$--HrEDKCreateReadReceipt-----------------------------------------------------
//  Create a RN/NRN report.
// -----------------------------------------------------------------------------
static HRESULT HrEDKCreateReadReceipt(      // RETURNS: return code
    IN ULONG ulFlags,                       // flags
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN LPTSTR lpszSubjectPrefix,            // pointer to subject prefix
    IN LPTSTR lpszReportText,               // pointer to report text
    OUT LPMESSAGE *lppReportEnvelope,       // pointer to report envelope
    OUT LPMESSAGE *lppReport)               // pointer to report
{
    HRESULT      hr                = NOERROR;
    LPTSTR       lpszMessageClass  = NULL;
    LPMESSAGE    lpReportEnvelope  = NULL;
    ULONG        ulAttachmentNum   = 0;
    LPATTACH     lpAttach          = NULL;
    LPMESSAGE    lpReport          = NULL;
    HRESULT      hrT               = 0;
    ULONG        cValues           = 0;
    LPSPropValue lpProps           = NULL;
    LPSPropValue lpPropT           = NULL;

    ULONG        cNewProps         = 0;
    LPSPropValue lpNewProps        = NULL;

    LPMAPITABLE  lpTable           = NULL;
    LPADRLIST    lpAdrList         = NULL;

    LPADRLIST    lpEnvelopeAdrList = NULL;

    ULONG        i                 = 0;
    ULONG        iRecip            = 0;

    SPropValue   RecipNumProp[1]   = {0};
    SPropValue   rgProps[1]        = {0};

SYSTEMTIME   st                = {0};
FILETIME     ft                = {0};

    SizedSPropTagArray(6,rgReportTags) =
    {
        6,
        {
            PR_SENT_REPRESENTING_NAME,
            PR_SENT_REPRESENTING_ENTRYID,
            PR_MESSAGE_CLASS,
            PR_SUBJECT,
            PR_PRIORITY,
            PR_DELETE_AFTER_SUBMIT
        }
    };

    DEBUGPRIVATE("HrEDKCreateReadReceipt()\n");

    hr = CHK_HrEDKCreateReadReceipt(
        ulFlags,
        lpFolder,
        lpMessage,
        lpszSubjectPrefix,
        lpszReportText,
        lppReportEnvelope,
        lppReport);

    if(FAILED(hr))
        RETURN(hr);

    if(ulFlags == MAPI_NON_READ)
    {
        hr = HrMAPISetPropBoolean(
            (LPMAPIPROP)lpMessage,
            PR_NON_RECEIPT_NOTIFICATION_REQUESTED,
            TRUE);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        hr = HrMAPISetPropBoolean(
            (LPMAPIPROP)lpMessage,
            PR_READ_RECEIPT_REQUESTED,
            TRUE);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }

    *lppReportEnvelope = NULL;
    *lppReport = NULL;

    //
    // Create a message
    //

    hr = MAPICALL(lpFolder)->CreateMessage(
        lpFolder,
        NULL,
        (ULONG)0,
        &lpReportEnvelope);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Create an embedded message
    //

    hr = MAPICALL(lpReportEnvelope)->CreateAttach(
        lpReportEnvelope,
        NULL,
        (ULONG)0,
        &ulAttachmentNum,
        &lpAttach);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    rgProps[0].ulPropTag = PR_ATTACH_METHOD;
    rgProps[0].Value.ul  = ATTACH_EMBEDDED_MSG;

    hr = MAPICALL(lpAttach)->SetProps(
        lpAttach,
        1,
        rgProps,
        NULL);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = MAPICALL(lpAttach)->OpenProperty(
        lpAttach,
        PR_ATTACH_DATA_OBJ,
        (LPIID)&IID_IMessage,
        0,
        MAPI_CREATE|MAPI_MODIFY,
        (LPUNKNOWN *)&lpReport);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //--------------------------------------------------------------------------

    hrT = HrReadReceipt(
        ulFlags,
    lpszReportText,
    lpszSubjectPrefix,
        lpMessage,
        lpReport);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);

rgProps[0].ulPropTag    = PR_CLIENT_SUBMIT_TIME;
rgProps[0].Value.ft     = ft;

    hrT = MAPICALL(lpReport)->SetProps(
        lpReport,
        1,
        rgProps,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpReport)->GetProps(
        lpReport,
        (SPropTagArray *)&rgReportTags,
        fMapiUnicode,
        &cValues,
        &lpProps);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

if((lpProps[0].ulPropTag != PR_SENT_REPRESENTING_NAME) ||
   (lpProps[1].ulPropTag != PR_SENT_REPRESENTING_ENTRYID) ||
   (lpProps[2].ulPropTag != PR_MESSAGE_CLASS))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

    lpProps[0].ulPropTag  = PR_ORIGINATOR_NAME;
    lpProps[1].ulPropTag  = PR_ORIGINATOR_ENTRYID;

    hr = HrEDKEnvelopeMessageClass(
        lpProps[2].Value.LPSZ,
        &lpszMessageClass);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpProps[2].Value.LPSZ = lpszMessageClass;

    hrT = MAPICALL(lpReportEnvelope)->SetProps(
        lpReportEnvelope,
        6,
        lpProps,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    //
    // Create recipient table for report envelope.
    //

    // -------------------------------------------------------------------------

    //
// Get the current recipient table from the report.
    //

hrT = MAPICALL(lpReport)->GetRecipientTable(lpReport, 0, &lpTable);

if(FAILED(hrT))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

//
// Query all the rows.
//

hrT = HrQueryAllRows(
    lpTable,
NULL,
NULL,
NULL,
0,
&(LPSRowSet)lpAdrList);

if(FAILED(hrT))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

    hr = HrMAPICreateSizedAddressList(
        lpAdrList->cEntries,
        &lpEnvelopeAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0, iRecip = 0; i < lpAdrList->cEntries; i++)
    {
        RecipNumProp[0].ulPropTag = PR_RECIPIENT_NUMBER;
        RecipNumProp[0].Value.ul  = iRecip+1;

        cNewProps  = 0;
        lpNewProps = NULL;

        hr = HrMAPIAppendSPropValues(
            lpAdrList->aEntries[i].cValues,
            lpAdrList->aEntries[i].rgPropVals,
            1,
            RecipNumProp,
            &cNewProps,
            &lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        hr = HrMAPISetAddressList(
            iRecip,
            cNewProps,
            lpNewProps,
            lpEnvelopeAdrList);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        MAPIFREEBUFFER(lpNewProps);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        iRecip++;
    }

    lpEnvelopeAdrList->cEntries = iRecip;

    if(iRecip == 0)
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpReportEnvelope)->ModifyRecipients(
        lpReportEnvelope,
        0,
        lpEnvelopeAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // -------------------------------------------------------------------------

    hrT = MAPICALL(lpReport)->SaveChanges(lpReport, KEEP_OPEN_READWRITE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpAttach)->SaveChanges(lpAttach, KEEP_OPEN_READWRITE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Save changes to the report envelope
    hrT = MAPICALL(lpReportEnvelope)->SaveChanges(lpReportEnvelope, KEEP_OPEN_READWRITE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lppReportEnvelope = lpReportEnvelope;
    *lppReport = lpReport;

cleanup:

    MAPIFREEBUFFER(lpNewProps);

    ULRELEASE(lpTable);

    FREEPADRLIST(lpAdrList);

    FREEPADRLIST(lpEnvelopeAdrList);

    MAPIFREEBUFFER(lpszMessageClass);

    ULRELEASE(lpAttach);

    RETURN(hr);
}

//$--HrEDKCreateReadNotificationReport------------------------------------------
//  Create a read notification report.
// -----------------------------------------------------------------------------
HRESULT HrEDKCreateReadNotificationReport(  // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN LPTSTR lpszReportText,               // pointer to report text
    OUT LPMESSAGE *lppReportEnvelope,       // pointer to report envelope
    OUT LPMESSAGE *lppReport)               // pointer to report
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrEDKCreateReadNotificationReport()\n");

    hr = CHK_HrEDKCreateReadNotificationReport(
        lpFolder,
        lpMessage,
        lpszReportText,
        lppReportEnvelope,
        lppReport);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrEDKCreateReadReceipt(
        0,
        lpFolder,
        lpMessage,
        TEXT("Read: "),
        lpszReportText,
        lppReportEnvelope,
        lppReport);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrEDKCreateNonReadNotificationReport---------------------------------------
//  Create a non-read notification report.
// -----------------------------------------------------------------------------
HRESULT HrEDKCreateNonReadNotificationReport(   // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,                   // pointer to folder
    IN LPMESSAGE lpMessage,                     // pointer to message
    IN LPTSTR lpszReportText,                   // pointer to report text
    IN ULONG ulNonReceiptReason,                // non-receipt reason
    IN ULONG ulDiscardReason,                   // discard reason
    OUT LPMESSAGE *lppReportEnvelope,           // pointer to report envelope
    OUT LPMESSAGE *lppReport)                   // pointer to report
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = 0;

    DEBUGPUBLIC("HrEDKCreateNonReadNotificationReport()\n");

    hr = CHK_HrEDKCreateNonReadNotificationReport(
        lpFolder,
        lpMessage,
        lpszReportText,
        ulNonReceiptReason,
        ulDiscardReason,
        lppReportEnvelope,
        lppReport);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrEDKCreateReadReceipt(
        MAPI_NON_READ,
        lpFolder,
        lpMessage,
        TEXT("Unread: "),
        lpszReportText,
        lppReportEnvelope,
        lppReport);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropLong(
        (LPMAPIPROP)*lppReport,
        PR_DISCARD_REASON,
        ulDiscardReason);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetPropLong(
        (LPMAPIPROP)*lppReport,
        PR_NON_RECEIPT_REASON,
        ulNonReceiptReason);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Save changes to the report
    hrT = MAPICALL(*lppReport)->SaveChanges(*lppReport, KEEP_OPEN_READWRITE);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}


REPORT.H

// --report.h-------------------------------------------------------------------
//
//  Header file for module containing report functions.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------
#if !defined(_REPORT_H)
#define _REPORT_H

#include <wchar.h>
#include <tchar.h>
#include "retcode.h"
#include "edkdebug.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

//$--HrEDKCreateDeliveryReport--------------------------------------------------
//  Create a delivery report.
// -----------------------------------------------------------------------------
HRESULT HrEDKCreateDeliveryReport(          // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpEnvelope,                // pointer to message envelope
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN LPADRLIST lpAdrList,                 // pointer to address list
    OUT LPMESSAGE *lppReportEnvelope,       // pointer to report envelope
    OUT LPMESSAGE *lppReport);              // pointer to report

//$--HrEDKCreateReadNotificationReport------------------------------------------
//  Create a read notification report.
// -----------------------------------------------------------------------------
HRESULT HrEDKCreateReadNotificationReport(  // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpMessage,                 // pointer to message
    IN LPTSTR lpszReportText,               // pointer to report text
    OUT LPMESSAGE *lppReportEnvelope,       // pointer to report envelope
    OUT LPMESSAGE *lppReport);              // pointer to report

//$--HrEDKCreateNonReadNotificationReport---------------------------------------
//  Create a non-read notification report.
// -----------------------------------------------------------------------------
HRESULT HrEDKCreateNonReadNotificationReport(   // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,                   // pointer to folder
    IN LPMESSAGE lpMessage,                     // pointer to message
    IN LPTSTR lpszReportText,                   // pointer to report text
    IN ULONG ulNonReceiptReason,                // non-receipt reason
    IN ULONG ulDiscardReason,                   // discard reason
    OUT LPMESSAGE *lppReportEnvelope,           // pointer to report envelope
    OUT LPMESSAGE *lppReport);                  // pointer to report

#ifdef __cplusplus
}
#endif // __cplusplus

#endif


RPTDRIV.C

// --rptdriv.c------------------------------------------------------------------
//
//  Report generation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "gwmain.h"
#include "report.h"

#include "rptdriv.chk"

//  Defined for NT service shell
TCHAR szAppName[]       = TEXT("RPTDRIV");
TCHAR szWindowTitle[]   = TEXT("Reports Sample");

static LPMAPISESSION    lpSession       = NULL;
static LPMDB            lpStore         = NULL;
static LPMAPIFOLDER     lpRootFolder    = NULL;
static LPMAPIFOLDER     lpMtsInFolder   = NULL;
static LPMAPIFOLDER     lpMtsOutFolder  = NULL;
static ULONG            cbGalEid        = 0;
static LPENTRYID        lpGalEid        = NULL;

#define NDR     0
#define DR      1
#define RN      2
#define NRN     3
#define UNKNOWN 4

//$--UlMakeReportType-----------------------------------------------------------
//  Make a report type from a string.
// -----------------------------------------------------------------------------
static ULONG UlMakeReportType(          // RETURNS: type of report
    IN LPTSTR lpszStr)                  // string
{
    ULONG  ulReportType = UNKNOWN;
    LPTSTR lpszCurr     = NULL;
    LPTSTR lpszName     = NULL;

    if(lpszStr == NULL)
    {
        ulReportType = UNKNOWN;
        goto cleanup;
    }

    lpszName = lpszStr;

    //
    // Skip leading white space.
    //

    while(*lpszName && ((*lpszName == ' ') || (*lpszName == '\t')))
    {
        lpszName++;
    }

    lpszCurr = lpszName;

    while(*lpszCurr && (*lpszCurr != ' ') && (*lpszCurr != '\t'))
    {
        lpszCurr++;
    }

    *lpszCurr = 0;

    if(lstrcmpi(lpszName, "NDR") == 0)
    {
        ulReportType = NDR;
    }
    else if(lstrcmpi(lpszName, "DR") == 0)
    {
        ulReportType = DR;
    }
    else if(lstrcmpi(lpszName, "RN") == 0)
    {
        ulReportType = RN;
    }
    else if(lstrcmpi(lpszName, "NRN") == 0)
    {
        ulReportType = NRN;
    }
    else
    {
        ulReportType = UNKNOWN;
    }

cleanup:

    return(ulReportType);
}

//$--HrBuildSampleNDRAdrList----------------------------------------------------
//  Build a sample non-delivery report address list.
// -----------------------------------------------------------------------------
HRESULT HrBuildSampleNDRAdrList(            // RETURNS: return code
    IN LPMESSAGE lpMessage,                 // pointer to message
    OUT LPADRLIST *lppAdrList)              // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    HRESULT         hrT             = NOERROR;
    LPADRLIST       lpAdrList       = NULL;
    LPADRLIST       lpNewAdrList    = NULL;
    SPropValue      rgProps[5]      = {0};
    ULONG           cHeadProps      = 0;
    LPSPropValue    lpHeadProps     = NULL;
    ULONG           cTailProps      = 0;
    LPSPropValue    lpTailProps     = NULL;
    ULONG           cNewProps       = 0;
    LPSPropValue    lpNewProps      = NULL;
    LPMAPITABLE     lpTable         = NULL;
    ULONG           cRows           = 0;

    DEBUGPUBLIC("HrBuildSampleNDRAdrList()\n");

    hr = CHK_HrBuildSampleNDRAdrList(
        lpMessage,
        lppAdrList);

    if(FAILED(hr))
        RETURN(hr);

//------------------------------------------------------------------------------

    hrT = MAPICALL(lpMessage)->GetRecipientTable(
        lpMessage,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->GetRowCount(lpTable, 0, &cRows);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cRows,
        0,
        (LPSRowSet *)&lpAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    else if((lpAdrList == NULL) || (lpAdrList->cEntries != cRows))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//------------------------------------------------------------------------------

    hr = HrNDRCreateFailedRecipList(
        &lpNewAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrNDRGetFailedRecipList(
        &lpAdrList->aEntries[0],
        0,
        NULL,
        &cNewProps,
        &lpNewProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrNDRAddToFailedRecipList(
        cNewProps,
        lpNewProps,
        &lpNewAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lppAdrList = lpNewAdrList;

cleanup:

    ULRELEASE(lpTable);

    MAPIFREEBUFFER(lpNewProps);

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}

//$--HrSendNDR------------------------------------------------------------------
//  Send a non-delivery report.
// -----------------------------------------------------------------------------
HRESULT HrSendNDR(                          // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpEnvelope,                // pointer to message envelope
    IN LPMESSAGE lpMessage)                 // pointer to message
{
    HRESULT      hr               = NOERROR;
    HRESULT      hrT              = NOERROR;
    LPMESSAGE    lpReport         = NULL;
    ULONG        cValues          = 0;
    LPSPropValue lpProps          = NULL;
    LPADRLIST    lpAdrList        = NULL;

    DEBUGPUBLIC("HrSendNDR()\n");

    hr = CHK_HrSendNDR(
        lpAdrBook,
        lpFolder,
        lpEnvelope,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrBuildSampleNDRAdrList(
        lpMessage,
        &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrNDRGetFailedProps(
        lpMessage,
        &cValues,
        &lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrNDRCreate(
        lpAdrBook,
        lpFolder,
        lpMessage,
        lpAdrList,
        cValues,
        lpProps,
        &lpReport);

    FREEPADRLIST(lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Submit report
    hrT = MAPICALL(lpReport)->SubmitMessage(lpReport, FORCE_SUBMIT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    FREEPADRLIST(lpAdrList);

    MAPIFREEBUFFER(lpProps);

    ULRELEASE(lpReport);

    RETURN(hr);
}

//$--HrBuildSampleDRAdrList-----------------------------------------------------
//  Build a sample delivery report address list.
// -----------------------------------------------------------------------------
HRESULT HrBuildSampleDRAdrList(             // RETURNS: return code
    IN LPMESSAGE lpMessage,                 // pointer to message
    OUT LPADRLIST *lppAdrList)              // pointer to address list pointer
{
    HRESULT         hr              = NOERROR;
    HRESULT         hrT             = NOERROR;
    LPADRLIST       lpAdrList       = NULL;
    LPADRLIST       lpNewAdrList    = NULL;
    SPropValue      rgProps[5]      = {0};
    ULONG           cHeadProps      = 0;
    LPSPropValue    lpHeadProps     = NULL;
    ULONG           cTailProps      = 0;
    LPSPropValue    lpTailProps     = NULL;
    ULONG           cNewProps       = 0;
    LPSPropValue    lpNewProps      = NULL;
    LPSPropValue    lpPropT         = NULL;
    LPMAPITABLE     lpTable         = NULL;
    ULONG           cRows           = 0;

    DEBUGPUBLIC("HrBuildSampleDRAdrList()\n");

    hr = CHK_HrBuildSampleDRAdrList(
        lpMessage,
        lppAdrList);

    if(FAILED(hr))
        RETURN(hr);

//------------------------------------------------------------------------------

    hrT = MAPICALL(lpMessage)->GetRecipientTable(
        lpMessage,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->GetRowCount(lpTable, 0, &cRows);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cRows,
        0,
        (LPSRowSet *)&lpAdrList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    else if((lpAdrList == NULL) || (lpAdrList->cEntries != cRows))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//------------------------------------------------------------------------------

    hr = HrDRCreateDeliveredRecipList(
        &lpNewAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrDRGetDeliveredRecip(
        &lpAdrList->aEntries[0],
        0,
        NULL,
        &cNewProps,
        &lpNewProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    lpPropT = LpValFindProp(
        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, cNewProps, lpNewProps);

    if(lpPropT != NULL)
    {
       lpPropT->ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
       lpPropT->Value.b   = TRUE;
    }

    hr = HrDRAddToDeliveredRecipList(
        cNewProps,
        lpNewProps,
        &lpNewAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lppAdrList = lpNewAdrList;

cleanup:

    ULRELEASE(lpTable);

    MAPIFREEBUFFER(lpNewProps);

    FREEPADRLIST(lpAdrList);

    RETURN(hr);
}

//$--HrSendDR-------------------------------------------------------------------
//  Send a delivery report.
// -----------------------------------------------------------------------------
HRESULT HrSendDR(                           // RETURNS: return code
    IN LPADRBOOK  lpAdrBook,                // pointer to address book
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpEnvelope,                // pointer to message envelope
    IN LPMESSAGE lpMessage)                 // pointer to message
{
    HRESULT      hr               = NOERROR;
    HRESULT      hrT              = NOERROR;
    LPMESSAGE    lpReport         = NULL;
    ULONG        cValues          = 0;
    LPSPropValue lpProps          = NULL;
    LPSPropValue lpPropT          = NULL;
    LPADRLIST    lpAdrList        = NULL;

    DEBUGPUBLIC("HrSendDR()\n");

    hr = CHK_HrSendDR(
        lpAdrBook,
        lpFolder,
        lpEnvelope,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrBuildSampleDRAdrList(
        lpMessage,
        &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrDRGetProps(
        lpMessage,
        &cValues,
        &lpProps);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrDRCreate(
        lpAdrBook,
        lpFolder,
        lpMessage,
        lpAdrList,
        cValues,
        lpProps,
        &lpReport);

    FREEPADRLIST(lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Submit report
    hrT = MAPICALL(lpReport)->SubmitMessage(lpReport, FORCE_SUBMIT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    FREEPADRLIST(lpAdrList);

    MAPIFREEBUFFER(lpProps);

    ULRELEASE(lpReport);

    RETURN(hr);
}

//$--HrBuildSampleRNSender------------------------------------------------------
//  Build a sample sender for a RN/NRN report.
// -----------------------------------------------------------------------------
HRESULT HrBuildSampleRNSender(          // RETURNS: return code
    IN LPMESSAGE lpMessage,             // pointer to message
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    OUT LPTSTR    *lppszDisplayName,    // pointer to display name
    OUT ULONG     *lpcbeid,             // pointer count of bytes in entry ID
    OUT LPENTRYID *lppeid)              // pointer to entry ID
{
    HRESULT      hr         = NOERROR;
    HRESULT      hrT        = NOERROR;
    SCODE        sc         = 0;
    ULONG        cBytes     = 0;
    LPADRLIST    lpAdrListT = NULL;
    LPADRLIST    lpAdrList  = NULL;
    ULONG        cValues    = 0;
    LPSPropValue lpProps    = NULL;
    LPSPropValue lpPropT    = NULL;
    LPMAPITABLE  lpTable    = NULL;
    ULONG        cRows      = 0;

    SPropValue   prop[2]    = {0};

    SizedSPropTagArray(2, rgPropTags) =
    {
        2,
        {
            PR_DISPLAY_NAME,
            PR_ENTRYID,
        }
    };

    DEBUGPUBLIC("HrBuildSampleRNSender()\n");

    hr = CHK_HrBuildSampleRNSender(
        lpMessage,
        lpAdrBook,
        lppszDisplayName,
        lpcbeid,
        lppeid);

    if(FAILED(hr))
        RETURN(hr);

    *lpcbeid = 0;
    *lppeid  = NULL;

//------------------------------------------------------------------------------

    //
    // Get a row from the recipient table
    //

    hrT = MAPICALL(lpMessage)->GetRecipientTable(
        lpMessage,
        MAPI_DEFERRED_ERRORS,
        &lpTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->GetRowCount(lpTable, 0, &cRows);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hrT = MAPICALL(lpTable)->QueryRows(
        lpTable,
        cRows,
        0,
        (LPSRowSet *)&lpAdrListT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }
    else if((lpAdrListT == NULL) || (lpAdrListT->cEntries != cRows))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

//------------------------------------------------------------------------------

    //
    // Get the entry ID
    //

    cValues = lpAdrListT->aEntries[0].cValues;
    lpProps = lpAdrListT->aEntries[0].rgPropVals;

    lpPropT = LpValFindProp(PR_ENTRYID, cValues, lpProps);

    if(lpPropT != NULL)
    {
        if((lpPropT->Value.bin.cb == 0) || (lpPropT->Value.bin.lpb == NULL))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
    }
    else
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    prop[0].ulPropTag       = PR_ENTRYID;
    prop[0].Value.bin.cb    = lpPropT->Value.bin.cb;
    prop[0].Value.bin.lpb   = (LPBYTE)lpPropT->Value.bin.lpb;
    prop[1].ulPropTag       = PR_RECIPIENT_TYPE;
    prop[1].Value.ul        = MAPI_TO;

    hr = HrMAPICreateSizedAddressList(1, &lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrMAPISetAddressList(
        0,
        2,
        prop,
        lpAdrList);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = MAPICALL(lpAdrBook)->PrepareRecips( lpAdrBook,
        0,
        (LPSPropTagArray)&rgPropTags,
        lpAdrList);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Copy the display name
    //

    cBytes = cbStrLen(lpAdrList->aEntries[0].rgPropVals[0].Value.LPSZ);

    sc = MAPIAllocateBuffer(cBytes, lppszDisplayName);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    CopyMemory(
        *lppszDisplayName,
        lpAdrList->aEntries[0].rgPropVals[0].Value.LPSZ,
        cBytes);

    //
    // Copy the entry ID
    //

    cBytes = lpAdrList->aEntries[0].rgPropVals[1].Value.bin.cb;

    sc = MAPIAllocateBuffer(cBytes, lppeid);

    if(FAILED(sc))
    {
        hr = HR_LOG(E_OUTOFMEMORY);
        goto cleanup;
    }

    *lpcbeid = cBytes;

    CopyMemory(
        *lppeid,
        lpAdrList->aEntries[0].rgPropVals[1].Value.bin.lpb,
        cBytes);

cleanup:

    ULRELEASE(lpTable);

    FREEPADRLIST(lpAdrListT);
    FREEPADRLIST(lpAdrList);

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppszDisplayName);

        *lpcbeid = 0;

        MAPIFREEBUFFER(*lppeid);
    }

    RETURN(hr);
}

//$--HrSendRN-------------------------------------------------------------------
//  Send a read notification report.
// -----------------------------------------------------------------------------
HRESULT HrSendRN(                           // RETURNS: return code
    IN LPADRBOOK lpAdrBook,                 // pointer to address book
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpEnvelope,                // pointer to envelope
    IN LPMESSAGE lpMessage)                 // pointer to message
{
    HRESULT     hr                  = NOERROR;
    HRESULT     hrT                 = NOERROR;
    LPMESSAGE   lpReportEnvelope    = NULL;
    LPMESSAGE   lpReport            = NULL;
    LPTSTR      lpszDisplayName     = NULL;
    ULONG       cbeid               = 0;
    LPENTRYID   lpeid               = NULL;
    FILETIME    ft                  = {0};
    SPropValue  rgProps[3]          = {0};

    DEBUGPUBLIC("HrSendRN()\n");

    hr = CHK_HrSendRN(
        lpAdrBook,
        lpFolder,
        lpEnvelope,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrBuildSampleRNSender(
        lpEnvelope,
        lpAdrBook,
        &lpszDisplayName,
        &cbeid,
        &lpeid);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    GetSystemTimeAsFileTime( &ft);

    rgProps[0].ulPropTag     = PR_RCVD_REPRESENTING_NAME;
    rgProps[0].Value.LPSZ    = lpszDisplayName;

    rgProps[1].ulPropTag     = PR_RCVD_REPRESENTING_ENTRYID;
    rgProps[1].Value.bin.cb  = cbeid;
    rgProps[1].Value.bin.lpb = (LPBYTE)lpeid;

    rgProps[2].ulPropTag     = PR_CLIENT_SUBMIT_TIME;
    rgProps[2].Value.ft      = ft;

     hrT = MAPICALL(lpMessage)->SetProps(
        lpMessage,
        3,
        rgProps,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrEDKCreateReadNotificationReport(
        lpFolder,
        lpMessage,
        TEXT("sample report text"),
        &lpReportEnvelope,
        &lpReport);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Submit report
    hrT = MAPICALL(lpReportEnvelope)->SubmitMessage(lpReportEnvelope, FORCE_SUBMIT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpszDisplayName);

    MAPIFREEBUFFER(lpeid);

    ULRELEASE(lpReport);

    ULRELEASE(lpReportEnvelope);

    RETURN(hr);
}

//$--HrSendNRN------------------------------------------------------------------
//  Send a non-read notification report.
// -----------------------------------------------------------------------------
HRESULT HrSendNRN(                          // RETURNS: return code
    IN LPADRBOOK lpAdrBook,                 // pointer to address book
    IN LPMAPIFOLDER lpFolder,               // pointer to folder
    IN LPMESSAGE lpEnvelope,                // pointer to envelope
    IN LPMESSAGE lpMessage)                 // pointer to message
{
    HRESULT     hr                  = NOERROR;
    HRESULT     hrT                 = NOERROR;
    LPMESSAGE   lpReportEnvelope    = NULL;
    LPMESSAGE   lpReport            = NULL;
    LPTSTR      lpszDisplayName     = NULL;
    ULONG       cbeid               = 0;
    LPENTRYID   lpeid               = NULL;
    FILETIME    ft                  = {0};
    SPropValue  rgProps[3]          = {0};

    DEBUGPUBLIC("HrSendNRN()\n");

    hr = CHK_HrSendNRN(
        lpAdrBook,
        lpFolder,
        lpEnvelope,
        lpMessage);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrBuildSampleRNSender(
        lpEnvelope,
        lpAdrBook,
        &lpszDisplayName,
        &cbeid,
        &lpeid);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    GetSystemTimeAsFileTime( &ft);

    rgProps[0].ulPropTag     = PR_RCVD_REPRESENTING_NAME;
    rgProps[0].Value.LPSZ    = lpszDisplayName;

    rgProps[1].ulPropTag     = PR_RCVD_REPRESENTING_ENTRYID;
    rgProps[1].Value.bin.cb  = cbeid;
    rgProps[1].Value.bin.lpb = (LPBYTE)lpeid;

    rgProps[2].ulPropTag     = PR_CLIENT_SUBMIT_TIME;
    rgProps[2].Value.ft      = ft;

     hrT = MAPICALL(lpMessage)->SetProps(
        lpMessage,
        3,
        rgProps,
        NULL);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrEDKCreateNonReadNotificationReport(
        lpFolder,
        lpMessage,
        TEXT("sample report text"),
        0,
        0,
        &lpReportEnvelope,
        &lpReport);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Submit report
    hrT = MAPICALL(lpReportEnvelope)->SubmitMessage(lpReportEnvelope, FORCE_SUBMIT);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    MAPIFREEBUFFER(lpszDisplayName);

    MAPIFREEBUFFER(lpeid);

    ULRELEASE(lpReport);

    ULRELEASE(lpReportEnvelope);

    RETURN(hr);
}

//$--HrSendReport---------------------------------------------------------------
//
//  Send a report to the originator of the message.
//
// -----------------------------------------------------------------------------
HRESULT HrSendReport(                   // RETURNS: return code
    IN LPADRBOOK lpAdrBook,             // pointer to address book
    IN LPMESSAGE lpMessage,             // pointer to message
    IN LPMESSAGE lpEnvelope)            // pointer to envelope
{
    HRESULT  hr              = NOERROR;
    ULONG    ulReportType    = 0;
    ULONG    cbSubject       = 0;
    LPTSTR   lpszSubject     = NULL;
    LPTSTR   lpszNullSubject = TEXT("NULL");

    hr = CHK_HrSendReport(
        lpAdrBook,
        lpMessage,
        lpEnvelope);

    if(FAILED(hr))
        RETURN(hr);

    hr = HrMAPIGetPropString(
        (LPMAPIPROP)lpMessage,
        PR_SUBJECT,
        &cbSubject,
        (void **)&lpszSubject);

    if(FAILED(hr) && (hr != MAPI_E_NOT_FOUND))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    if(lpszSubject == NULL)
    {
        lpszSubject = lpszNullSubject;
    }

    //
    // Determine which type of report to return to the originator.
    //

    ulReportType = UlMakeReportType(lpszSubject);

    //
    // Send the report.
    //

    switch(ulReportType)
    {
    case NDR:
        hr = HrSendNDR(
            lpAdrBook,
            lpMtsInFolder,
            lpEnvelope,
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        break;
    case DR:
        hr = HrSendDR(
            lpAdrBook,
            lpMtsInFolder,
            lpEnvelope,
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        break;
    case RN:
        hr = HrSendRN(
            lpAdrBook,
            lpMtsInFolder,
            lpEnvelope,
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        break;
    case NRN:
        hr = HrSendNRN(
            lpAdrBook,
            lpMtsInFolder,
            lpEnvelope,
            lpMessage);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }
        break;
    default:
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(lpszSubject != lpszNullSubject)
    {
        MAPIFREEBUFFER(lpszSubject);
    }

    RETURN(hr);
}

//$--Process--------------------------------------------------------------------
//  Process messages arriving at MTS-OUT.
// -----------------------------------------------------------------------------
DWORD Process(
    IN LPVOID lpParameter)
{
    HRESULT      hr                 = NOERROR;
    HRESULT      hrT                = 0;
    ULONG        i                  = 0;
    ULONG        cRows              = 0;
    ULONG        cValues            = 0;
    ULONG        ulObjType          = 0;

    LPADRBOOK    lpAdrBook          = NULL;

    LPMESSAGE    lpMessage          = NULL;
    LPMESSAGE    lpEnvelope         = NULL;
    LPATTACH     lpAttach           = NULL;

    ULONG        cbeid              = 0;
    LPENTRYID    lpeid              = NULL;

    LPTSTR       pszMessageClass    = NULL;
    ULONG        cbMessageClass     = 0;

    LPSPropValue lpProps            = NULL;

    cRows = GetGWSRowSet(lpParameter)->cRows;

    // Open the Address Book
    hrT = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    for(i = 0; i < cRows; i++)
    {
        cValues = GetGWSRowSet(lpParameter)->aRow[i].cValues;

        lpProps = GetGWSRowSet(lpParameter)->aRow[i].lpProps;

        cbeid = lpProps[0].Value.bin.cb;
        lpeid = (LPENTRYID)lpProps[0].Value.bin.lpb;

        // Get the message in the MTS-OUT folder
        hrT = MAPICALL(lpMtsOutFolder)->OpenEntry(
            lpMtsOutFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            &ulObjType,
            (LPUNKNOWN FAR *)&lpEnvelope);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if(ulObjType != MAPI_MESSAGE)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Get the attachment in the envelope
        hrT = MAPICALL(lpEnvelope)->OpenAttach(
            lpEnvelope,
            0,
            NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            &lpAttach);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Open a message interface on the attachment
        hrT = MAPICALL(lpAttach)->OpenProperty(
            lpAttach,
            PR_ATTACH_DATA_OBJ,
            (LPIID)&IID_IMessage,
            0,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            (LPUNKNOWN *)&lpMessage);

        if(FAILED(hrT))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------

        (void)HrSendReport(lpAdrBook, lpMessage, lpEnvelope);

        // ---------------------------------------------------------------------

        ULRELEASE(lpMessage);

        ULRELEASE(lpAttach);

        ULRELEASE(lpEnvelope);

        // ---------------------------------------------------------------------

        hr = HrMAPIDeleteMessage(

lpMtsOutFolder,
            cbeid,
            lpeid);

        if(FAILED(hr))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // ---------------------------------------------------------------------
    }

cleanup:

    ULRELEASE(lpAttach);

    ULRELEASE(lpMessage);

    ULRELEASE(lpEnvelope);

    MAPIFREEBUFFER(pszMessageClass);

    ULRELEASE(lpAdrBook);

    RETURN(0);
}

//$--GWMain----------------------------------------------------------------
//  Start threads.
// -----------------------------------------------------------------------------
void GWMain(
    void)
{
    HRESULT               hr                     = NOERROR;
    EDK_SERVICE_CONTROL_T sc                     = 0;
    DWORD                 dwRet                  = 0;
    CHAR                  szTempPath[MAX_PATH+1] = {0};

    dwRet = GetTempPath(MAX_PATH, szTempPath);

    if((dwRet == 0) || (dwRet > MAX_PATH))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    hr = HrGWStartNewMailHandler(
        1000,
        (ULONG)-1,
        1,
        1,
        EXCHANGE_ADDRTYPE,// Currently "EX"
        szTempPath,
        (LPTHREAD_START_ROUTINE)Process);

    if(FAILED(hr))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        ServiceStop();
    }

    //
    // Wait for a request for the service to stop.
    //

    hr = HR_LOG(HrServiceWaitForStop(INFINITE, &sc));

    return;
}

//$--HrGWLogon-------------------------------------------------------------
//  Logon to the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogon(void)
{
    HRESULT hr = NOERROR;

    lpSession      = GetGWSession();
    lpStore        = GetGWDefaultStore();
    lpRootFolder   = GetGWRootFolder();
    lpMtsInFolder  = GetGWMtsInFolder();
    lpMtsOutFolder = GetGWMtsOutFolder();
    cbGalEid       = GetGWGALEntryIdSize();
    lpGalEid       = GetGWGALEntryId();

    RETURN(hr);
}

//$--HrGWLogoff------------------------------------------------------------
//  Logoff of the gateway.
// -----------------------------------------------------------------------------
HRESULT HrGWLogoff(void)
{
    HRESULT hr = NOERROR;

    RETURN(hr);
}


RULEEDIT.CPP

//--ruleedit.cpp---------------------------------------------------------------
//
// Folder rules editing utility.
//
// Copyright (C) Microsoft Corp., 1986-1996.  All rights reserved.
//
//-----------------------------------------------------------------------------

//
// Begin program documentation
//

#if0

Command line arguments: See Usage(), or invoke ruleedit.exe with no command
line arguments to get a usage note.

#endif

//
// End program documentation
//

#include "edk.h"
#include "errno.h"
#include "ruleedit.h"

//
// Manifest constants
//

const UINT  cbMaxIniLine =1024;
const UINT  cMaxActions =16;

//
// Enumeration, structure, and other type definitions
//

enum EDITCMD// ec
{
EDITCMD_DELETE,
EDITCMD_DISABLE,
EDITCMD_ENABLE,
EDITCMD_HELP,
EDITCMD_INSERT,
EDITCMD_LIST,
EDITCMD_INVALID
};

struct INIFILEPARSEINFO// ifpi
{
    CHAR *          pszKeyword;
    VOID            (*ParseFunction)(INCHAR *pch);
};

enum INIFILESECTIONTYPE// ifst
{
    IFST_INITIALIZATION,
IFST_COMMAND,
    IFST_UNKNOWN
};

enum LINEREADSTATUS// lrs
{
    LRS_OKAY,           // Line was read and is available.
    LRS_EOF,            // We are at end of file; no more lines.
    LRS_IOERROR,        // There was an I/O error in the last read.
    LRS_LINETOOLONG     // Last line too long to return.
};

enum STORETYPE// st
{
ST_UNKNOWN,// Store type not determined.
ST_EXCHANGEMAILBOX,// Microsoft Exchange Mailbox Store.
ST_EXCHANGEPUBLIC,// Microsoft Exchange Public Store.
ST_NONEXCHANGE// Store not provided by Exchange.
};

//
// Forward function declarations.
//

static
BOOL
bParseCmdLine(
    IN  INT     argc,
    IN  CHAR *  argv[]
    );

static
VOID
DoDelete(VOID);

static
VOID
DoDisable(VOID);

static
VOID
DoEnable(VOID);

static
VOID
DoHelp(VOID);

static
VOID
DoInsert(VOID);

static
VOID
DoList(
INLPSTRlpszProvider
);

static
VOID
EventLogHexErrorMsg(
INDWORDdwEvent,
INHRESULThr
);

static
HRESULT
HrGetDefaultStoreName(
INLPMAPISESSIONlpSession,
OUTLPSTR FAR *lppDisplayName
    );

static
HRESULT
HrGetStoreType(
INLPMAPISESSIONlpSession,
INLPMDBlpMDB,
INLPSTRlpDisplayName,
OUTSTORETYPE *lpStoreType
    );

static
HRESULT
HrProcessCommandSection(VOID);

static
HRESULT
HrProcessInitializationSection(VOID);

static
BOOL
IsSectionLine(
    INCHAR *  pch
    );

static
LINEREADSTATUS
lrsGetNextLine(VOID);

static
VOID
ParseLine(VOID);

static
VOID
ParseSectionLine(
    IN      CHAR *  pch
    );

static
VOID
ParseEntryLine(
    IN      CHAR *  pch
    );

static
VOID
ParseActionValue(
    IN      CHAR *  pch
    );

static
VOID
ParseCommandValue(
    IN      CHAR *  pch
    );

static
VOID
ParseConditionValue(
    IN      CHAR *  pch
    );

static
VOID
ParseFlagsValue(
    IN      CHAR *  pch
    );

static
VOID
ParsePositionValue(
    IN      CHAR *  pch
    );

static
VOID
ParseFolderValue(
    IN      CHAR *  pch
    );

static
VOID
ParseProfileValue(
    IN      CHAR *  pch
    );

static
VOID
ParseProviderValue(
    IN      CHAR *  pch
    );

static
VOID
PrintActions(
INLPACTIONSlpActs
);

static
VOID
PrintErr(
INCHAR *Format
...
);

static
VOID
PrintIniFileErr(
INCHAR *Format
...
);

static
VOID
PrintStateFlags(
INLONGlStateFlags
);

static
VOID
Usage(VOID);

//
// Static storage
//

// All the keywords used by the program:

staticCHARszActionKeyword[] ="action";
staticCHARszCommandKeyword[] ="command";
staticCHARszConditionKeyword[] ="condition";
staticCHARszDeleteKeyword[] ="delete";
staticCHARszDisableKeyword[] ="disable";
staticCHARszDisabledKeyword[] ="disabled";
staticCHARszEnableKeyword[] ="enable";
staticCHARszEnabledKeyword[] ="enabled";
staticCHARszErrorKeyword[] ="error";
staticCHARszExitLevelKeyword[] ="exitlevel";
staticCHARszFileKeyword[] ="file";
staticCHARszFlagsKeyword[] ="flags";
staticCHARszFolderKeyword[] ="folder";
staticCHARszHelpKeyword[] ="help";
staticCHARszInboxKeyword[] ="inbox";
staticCHARszInitializationKeyword[] ="initialization";
staticCHARszInsertKeyword[] ="insert";
staticCHARszKeepOOFHistKeyword[] ="keepoofhist";
staticCHARszListKeyword[] ="list";
staticCHARszPositionKeyword[] ="position";
staticCHARszProfileKeyword[] ="profile";
staticCHARszProviderKeyword[] ="provider";
staticCHARszQuestionKeyword[] ="?";
staticCHARszUserOOFKeyword[] ="useroof";

// Command line arg flags, and array used by _HrExpandCommandLineArgument():

staticCHAR *rgpszArgArray[] ={
szFileKeyword,
szFolderKeyword,
szHelpKeyword,
szListKeyword,
szProfileKeyword,
szProviderKeyword,
szQuestionKeyword
};

// Storage used to input and parse ini file lines and store values:

// Ini file ptr:

static FILE *IniFile =NULL;

// Current input line:

static CHARachIniLine[cbMaxIniLine] ={0};

// Parser tables for entries:

static
INIFILEPARSEINFO    aifpiInitializationEntry[] =   {
                                            szProfileKeyword,
ParseProfileValue,
                                            szFolderKeyword,
ParseFolderValue,
szProviderKeyword,
ParseProviderValue
                                            };
static
INIFILEPARSEINFO    aifpiCommandEntry[] =   {
                                            szCommandKeyword,
ParseCommandValue,
                                            szPositionKeyword,
ParsePositionValue,
                                            szConditionKeyword,
ParseConditionValue,
                                            szActionKeyword,
ParseActionValue,
szFlagsKeyword,
ParseFlagsValue
                                            };

// Parser state variables:

static INIFILESECTIONTYPEifstCurSection =IFST_UNKNOWN;

staticCHARszProfile[MAX_PATH + 1] ={0};

staticBOOLfSkipProfileArg =FALSE;

staticLPMAPISESSIONlpSession =NULL;

staticCHARszStore[MAX_PATH + 1] ={0};

staticLPMDBlpStore =NULL;

staticULONGcbEIDStore  =   0;

staticLPENTRYIDlpEIDStore =NULL;

staticSTORETYPEstStoreType =ST_UNKNOWN;

staticCHARszFolder[MAX_PATH + 1] ={0};

staticLPMAPIFOLDERlpFolder =NULL;

staticULONGcbEIDFolder =0;

staticLPENTRYIDlpEIDFolder =NULL;

static
IExchangeFolderRules *lpFRules =NULL;

staticBOOLfInboxArgFnd =FALSE;

staticBOOLfSkipFolderArg =FALSE;

staticCHARszProvider[MAX_PATH + 1] ={0};

staticBOOLfSkipProviderArg =FALSE;

staticBOOLfProviderArgFnd =FALSE;

staticULONGcProviders =0;

staticLPSTR FAR *lppszProviders =NULL;

staticEDITCMDEditCmd =EDITCMD_INVALID;

staticLONGlCursor =0;

staticBOOLfPositionArgFnd =FALSE;

staticLPACTIONSlpActions =NULL;

staticLPSRestrictionlpRes =NULL;

staticLONGlStateFlags =ST_ENABLED;

staticBOOLfFlagsArgFnd =FALSE;

// Error handling variables:

staticULONGcErrs =0;
staticULONGcPriorErrs =0;// Used in determining if a section should
// be executed.

static DWORDiLine =1;
static DWORDiSection =1;

//
// Functions
//

//$--main----------------------------------------------------------------------
//
// DESCRIPTION:    ruleedit.exe main() routine.
//
// INPUT:Standard argc and argv.
//
// RETURNS:integer from edkcode.h
//
//-----------------------------------------------------------------------------

INT
main(                   // RETURNS: INT
    IN  INT     argc,       // argument count
    IN  CHAR *  argv[]      // array of command line arguments
)
{
BOOLbMAPIInitialized =FALSE;
BOOLbPrintCompletion =TRUE;
BOOLbEventLoggingEnabled =FALSE;
HRESULThr =NOERROR;
LINEREADSTATUSlrs =LRS_OKAY;

    DEBUGPUBLIC("main()\n");

// Initialize event logging.

hr = HrEventOpenLog("EDKRuleEdit", NULL, NULL, NULL, NULL, NULL);

if (FAILED(hr))
{
PrintErr("Event logging could not be enabled; error code=%#x", hr);
hr = HR_LOG(E_FAIL);

goto cleanup;
}
else
{
bEventLoggingEnabled = TRUE;
}

    // NOTE:  Must initialize MAPI before parsing command line,
    // as the parsing of the command line requires MAPI memory
    // allocation.

if (argc < 2)
{
Usage();
bPrintCompletion = FALSE;

goto cleanup;
}

hr = MAPIInitialize(0);

if (FAILED(hr))
{
PrintErr("MAPI initialization failed");
EventLogHexErrorMsg(RULEEDIT_MAPIINITIALIZE_FAILED, hr);
hr = HR_LOG(E_FAIL);

goto cleanup;
}

bMAPIInitialized = TRUE;

if (!bParseCmdLine(argc, argv))
{
Usage();
hr = HR_LOG(E_INVALIDARG);

goto cleanup;
}

if (EditCmd == EDITCMD_HELP)
{
DoHelp();
bPrintCompletion = FALSE;

goto cleanup;
}

ifstCurSection = IFST_UNKNOWN;

// /List can be specified on the cmd line instead of specifying an
// ini file to process.  If this is the case, we handle it here.

if (EditCmd == EDITCMD_LIST)
{
if (!fSkipProfileArg)
{
PrintErr("/PROFILE must be specified on command line with /LIST");
hr = HR_LOG(E_FAIL);
goto cleanup;
}
else if (!fSkipFolderArg)
{
PrintErr("/FOLDER must be specified on command line with /LIST");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

hr = HrProcessInitializationSection();

if (FAILED(hr))
goto cleanup;

if (fProviderArgFnd)
{
DoList(szProvider);
}
else
{
ULONGi = 0;

hr = HrFolderRulesGetProviders(lpStore,
   cbEIDFolder,
   lpEIDFolder,
   &cProviders,
   &lppszProviders);
if (FAILED(hr))
{
PrintErr("Folder providers could not be determined");
EventLogHexErrorMsg(RULEEDIT_FOLDERRULESGETPROVIDERS_FAILED,hr);

goto cleanup;
}

if (cProviders == 0)
printf("Rules table is empty.\n\n");
else
for (i = 0; i < cProviders; i++)
DoList(lppszProviders[i]);
}

// Note that execution always ends here.

goto cleanup;
}

while (TRUE)
{
lrs = lrsGetNextLine();

if (lrs == LRS_OKAY)
{
if (IsSectionLine(achIniLine))
{
if (ifstCurSection == IFST_COMMAND)
hr = HrProcessCommandSection();
else if (ifstCurSection == IFST_INITIALIZATION)
hr = HrProcessInitializationSection();

if (FAILED(hr))
goto cleanup;
}

ParseLine();
}
else if (lrs == LRS_LINETOOLONG)
{
PrintErr("Line %u too long in Ini File", iLine);
}
else
{
break;
}

iLine++;
}

if (lrs == LRS_EOF)
{
if (ifstCurSection == IFST_COMMAND)
hr = HrProcessCommandSection();
else if (ifstCurSection == IFST_INITIALIZATION)
hr = HrProcessInitializationSection();

if (FAILED(hr))
goto cleanup;
}

cleanup:

MAPIFREEBUFFER(lpActions);
MAPIFREEBUFFER(lpRes);
MAPIFREEBUFFER(lpEIDFolder);
MAPIFREEBUFFER(lpEIDStore);
MAPIFREEBUFFER(lppszProviders);

ULRELEASE(lpFRules);
ULRELEASE(lpFolder);
ULRELEASE(lpStore);

if (lpSession)
{
lpSession->Logoff(0, 0, 0);
ULRELEASE(lpSession);
}

if (bMAPIInitialized)
(VOID)MAPIUninitialize();

if (bPrintCompletion)
{
EDKEVENTCOUNTEvents = {0,0,0};

HrEventGetCounts(&Events);

if (cErrs == 0)
{
printf("Execution completed with no errors.\n");
}
else
{
if (cErrs == 1)
{
printf("1 error encountered during execution.\n");
}
else
{
printf("%u errors encountered during execution.\n", cErrs);
}

if (Events.cError == 1)
{
printf("1 error written to NT Event Log.\n");
}
else if (Events.cError > 1)
{
printf("%u errors written to NT Event Log.\n", Events.cError);
}
}
}

if (bEventLoggingEnabled)
HrEventCloseLog();

if (SUCCEEDED(hr) && cErrs > 0)
hr = HR_LOG(E_FAIL);

    return _nEcFromHr(hr);
}

//$--bParseCmdLine-------------------------------------------------------------
//
// DESCRIPTION:    Parse the command line.
//
// INPUT:
//
//[argc]-- Count of arguments from command line.
//[argv]-- Array of arguments from command line.
//
// RETURNS:TRUE on success; FALSE otherwise.
//
//-----------------------------------------------------------------------------

BOOL
bParseCmdLine(// RETURNS: BOOL
    IN  INT     argc,           // argument count
    IN  CHAR *  argv[]          // array of arguments
    )
{
    BOOL        brc =   FALSE;
HRESULThr =NOERROR;
CHAR *pszArgData =NULL;
CHAR *pszFlagName =NULL;

    DEBUGPRIVATE("bParseCmdLine()\n");

argc--;// Get rid of command argument.
argv++;

if (argc == 0)
    goto cleanup;

while (argc > 0)
{
hr =_HrExpandCommandLineArgument(
*argv,
rgpszArgArray,
sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]),
NULL,
&pszFlagName,
&pszArgData
);

if (FAILED(hr))
{
PrintErr("Invalid command line argument");
goto cleanup;
}

if (pszFlagName == szFileKeyword)
{
if (IniFile != NULL)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData == NULL || pszArgData[0] == '\0')
{
pszArgData = "ruleedit.ini";
}

if ((IniFile = fopen(pszArgData, "r")) == NULL)
{
CHARszTmpFileName[MAX_PATH + 1];

if (strchr(pszArgData, '.') != NULL||
strlen(pszArgData) + 4 >= sizeof(szTmpFileName))
{
PrintErr("Ini file %s could not be opened", pszArgData);
goto cleanup;
}

// Try appending ".ini".

strcpy(szTmpFileName, pszArgData);
strcat(szTmpFileName, ".ini");

if ((IniFile = fopen(szTmpFileName, "r")) == NULL)
{
PrintErr("Ini file %s could not be opened", szTmpFileName);
goto cleanup;
}
}
}
else if (pszFlagName == szFolderKeyword)
{
if (szFolder[0] != '\0' || fInboxArgFnd)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData == NULL || pszArgData[0] == '\0')
{
PrintErr("Command line %s argument requires a value",
 szFolderKeyword);
goto cleanup;
}

if (!stricmp(pszArgData, szInboxKeyword))
{
// It is the special name "inbox", which is interpreted as
// appropriate.

fInboxArgFnd = TRUE;
}
else
{
// Split the store and folder names and check their lengths.

CHAR *pch = strchr(pszArgData, '\\');

if (pch == NULL)
{
PrintErr("Command line %s argument invalid",
 szFolderKeyword);
goto cleanup;
}

*pch = '\0';
pch++;

if (*pszArgData == '\0')
{
PrintErr("Command line %s argument invalid",
 szFolderKeyword);
goto cleanup;
}

if (strlen(pszArgData) >= sizeof(szStore))
{
PrintErr("Command line %s argument too long",
 szFolderKeyword);
goto cleanup;
}

strcpy(szStore, pszArgData);

if (*pch == '\0')
{
PrintErr("Command line %s argument invalid",
 szFolderKeyword);
goto cleanup;
}

if (strlen(pch) >= sizeof(szFolder))
{
PrintErr("Command line %s argument too long",
 szFolderKeyword);
goto cleanup;
}

strcpy(szFolder, pch);
}

fSkipFolderArg = TRUE;
}
else if (pszFlagName == szHelpKeyword||
 pszFlagName == szQuestionKeyword)
{
EditCmd = EDITCMD_HELP;

// If we encounter /help or /?, we pretty much ignore everything
// else and produce a help msg.

brc = TRUE;
goto cleanup;
}
else if (pszFlagName == szListKeyword)
{
if (EditCmd == EDITCMD_LIST)
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

EditCmd = EDITCMD_LIST;
}
else if (pszFlagName == szProfileKeyword)
{
if (szProfile[0] != '\0')
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData == NULL || pszArgData[0] == '\0')
{
PrintErr("Command line %s argument requires a value",
 szProfileKeyword);
goto cleanup;
}

if (strlen(pszArgData) >= sizeof(szProfile))
{
PrintErr("Command line %s argument too long",
 szProfileKeyword);
goto cleanup;
}

strcpy(szProfile, pszArgData);

fSkipProfileArg = TRUE;// Ignore ini file value.
}
else if (pszFlagName == szProviderKeyword)
{
if (szProvider[0] != '\0')
{
PrintErr("Duplicate command line arguments");
goto cleanup;
}

if (pszArgData == NULL || pszArgData[0] == '\0')
{
PrintErr("Command line %s argument requires a value",
 szProviderKeyword);
goto cleanup;
}

if (strlen(pszArgData) >= sizeof(szProvider))
{
PrintErr("Command line %s argument too long",
 szProviderKeyword);
goto cleanup;
}

strcpy(szProvider, pszArgData);

fSkipProviderArg =TRUE;// Ignore ini file value.
fProviderArgFnd =TRUE;
}
else
{
PrintErr("Command line argument '%s' is invalid", *argv);
goto cleanup;
}

argc--;
argv++;
}

// Check for required arguments.

if (IniFile == NULL)
{
if (EditCmd != EDITCMD_LIST)
{
if ((IniFile = fopen("ruleedit.ini", "r")) == NULL)
{
PrintErr("Ini file ruleedit.ini could not be opened");
goto cleanup;
}
}
}
else
{
if (EditCmd == EDITCMD_LIST)
{
PrintErr("/list and /file parameters may not both be specified");
goto cleanup;
}
}

brc = TRUE;

cleanup:

    return brc;
}

//$--DoDelete------------------------------------------------------------------
//
// DESCRIPTION:Execute the DELETE command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoDelete(VOID)// RETURNS: VOID
{
HRESULThr =NOERROR;
LONGlCurCursor =0;

    DEBUGPRIVATE("DoDelete()\n");

    hr = lpFRules->HrSeek(lCursor);

    if (FAILED(hr))
    {
PrintErr("Delete command at line %u failed.\n\n", iSection);
EventLogHexErrorMsg(RULEEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFRules->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
PrintErr("Delete command at line %u failed.\n\n", iSection);
EventLogHexErrorMsg(RULEEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor == RULE_PAST_END)
{
PrintErr("Delete command at line %u failed; "
 "rule at position %d does not exist", iSection, lCursor);
goto cleanup;
}

    hr = lpFRules->HrDelete();

    if (FAILED(hr))
    {
PrintErr("Delete command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_DELETE_FAILED, hr);
        goto cleanup;
    }

printf("Delete command at line %u succeeded.\n\n", iSection);

cleanup:

return;
}

//$--DoDisable-----------------------------------------------------------------
//
// DESCRIPTION:Execute the DISABLE command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoDisable(VOID)// RETURNS:VOID
{
HRESULThr;
LONGlCurCursor;

    DEBUGPRIVATE("DoDisable()\n");

    hr = lpFRules->HrSeek(lCursor);

    if (FAILED(hr))
    {
PrintErr("Disable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFRules->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
PrintErr("Disable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor == RULE_PAST_END)
{
PrintErr("Disable command at line %u failed; "
 "rule at position %d does not exist", iSection, lCursor);
goto cleanup;
}

    hr = lpFRules->HrDisable();

    if (FAILED(hr))
    {
PrintErr("Disable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_DISABLE_FAILED, hr);
        goto cleanup;
    }

printf("Disable command at line %u succeeded.\n\n", iSection);

cleanup:

return;
}

//$--DoEnable------------------------------------------------------------------
//
// DESCRIPTION:Execute the ENABLE command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoEnable(VOID)// RETURNS: VOID
{
HRESULThr =NOERROR;
LONGlCurCursor =0;

    DEBUGPRIVATE("DoEnable()\n");

    hr = lpFRules->HrSeek(lCursor);

    if (FAILED(hr))
    {
PrintErr("Enable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// We do prior check of cursor location to be able to cleanly specify
// the error.

    hr = lpFRules->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
PrintErr("Enable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor == RULE_PAST_END)
{
PrintErr("Enable command at line %u failed; "
 "rule at position %d does not exist", iSection, lCursor);
goto cleanup;
}

    hr = lpFRules->HrEnable();

    if (FAILED(hr))
    {
PrintErr("Enable command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_ENABLE_FAILED, hr);
        goto cleanup;
    }

printf("Enable command at line %u succeeded.\n\n", iSection);

cleanup:

return;
}

//$--DoHelp--------------------------------------------------------------------
//
// DESCRIPTION:Execute the HELP command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoHelp(VOID)  // RETURNS: VOID
{
printf(
"\n"
"USAGE: RULEEDIT [/Profile=<ProfileName>]"
"\n"
"                [<Folder>]"
"\n"
"                [/List | /File=<IniFile>]"
"\n"
"                [/Provider=<ProviderName>]"
"\n"
"\n"
"<Folder> ::= /Folder=<StoreName>\\<FolderPath> | /Folder=Inbox"
"\n"
"\n"
);
}

//$--DoInsert------------------------------------------------------------------
//
// DESCRIPTION:Execute the INSERT command.
//
// INPUT:None.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoInsert(VOID)// RETURNS: VOID
{
HRESULThr =NOERROR;
ULONGi =0;

    DEBUGPRIVATE("DoInsert()\n");

    hr = lpFRules->HrSeek(lCursor);

    if (FAILED(hr))
    {
PrintErr("Insert command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

hr = lpFRules->HrInsert(lStateFlags, lpRes, lpActions, 0, "");

if (FAILED(hr))
{
PrintErr("Insert command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_INSERT_FAILED, hr);
goto cleanup;
}

printf("Insert command at line %u succeeded.\n\n", iSection);

cleanup:

return;
}

//$--DoList--------------------------------------------------------------------
//
// DESCRIPTION:Execute the LIST command.
//
// INPUT:
//
//[lpszProvider]-- Provider to list rules for.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
DoList(// RETURNS: VOID
INLPSTRlpszProvider// Provider name
)
{
HRESULThr =NOERROR;
LONGlCurCursor =0;
LPACTIONSlpActs =NULL;
LPSRestrictionlpRes =NULL;
LPSTRlpszRes =NULL;
   LPSTRlpszRuleName =NULL;
LPFOLDERRULESlpFRulesLst =NULL;

    DEBUGPRIVATE("DoList()\n");

if (!fPositionArgFnd)
lCursor = 0;

// If the provider being listed is not the provider that the global
// rules folder ptr has been opened for, we have to get a local rules
// folder ptr for that provider.

if (stricmp(lpszProvider, szProvider))
{
hr = HrFolderRulesOpen(lpStore,
   cbEIDFolder,
   lpEIDFolder,
   lpszProvider,
   &lpFRulesLst);

// An error is highly unexpected here, since this code should never
// execute without the global folder rules ptr having been opened.

if (FAILED(hr))
{
hr = HR_LOG(E_UNEXPECTED);
PrintErr("Unexpected internal error in ruleedit");
EventLogHexErrorMsg(RULEEDIT_INTERNAL_ERROR, hr);
goto cleanup;
}
}
else
{
lpFRulesLst = lpFRules;// AddRef() not necessary due to scope.
}

    hr = lpFRulesLst->HrSeek(lCursor);

    if (FAILED(hr))
    {
PrintErr("List command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_SEEK_FAILED, hr);
        goto cleanup;
    }

// Check for empty table or invalid cursor position.

    hr = lpFRulesLst->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
PrintErr("List command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

printf("Listing of rules for provider '%s'\n"
   "in folder '%s' in store '%s':\n\n",
   lpszProvider, szFolder, szStore);

if (lCurCursor == RULE_PAST_END)
{
if (fPositionArgFnd)
PrintErr("List command at line %u failed; "
 "Rule at specified position (%d) does not exist",
 iSection, lCursor);
else
printf("There are no rules for provider '%s'\n\n", lpszProvider);

goto cleanup;
}

while (TRUE)
{
LONGlStateFlags;
LONGlLevel;

hr = lpFRulesLst->HrGet(&lStateFlags,
 &lpRes,
 &lpActs,
 &lLevel,
 &lpszRuleName);
if (FAILED(hr))
{
HRESULThrSaved = hr;

    hr = lpFRulesLst->HrTell(&lCurCursor);

    if (FAILED(hr))
    {
PrintErr("List command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_TELL_FAILED, hr);
        goto cleanup;
    }

if (lCurCursor != RULE_PAST_END || hrSaved != E_FAIL)
{
PrintErr("List command at line %u failed", iSection);
EventLogHexErrorMsg(RULEEDIT_GET_FAILED, hrSaved);
        goto cleanup;
}
break;
}
else
{
// Print listing:

printf("Row number = %d\n", lCurCursor);

if (lpRes != NULL)
{
hr = HrRestrictionToString(lpRes, &lpszRes);

if (FAILED(hr))
{
printf("String representation of rule condition "
   "could not be determined.\n");
cErrs++;
EventLogHexErrorMsg(RULEEDIT_RESTRICTIONTOSTRING_FAILED, hr);
}
else
{
printf("Rule Condition = %s\n", lpszRes);
}
}
else
{
printf("Rule Condition is not defined (NULL)\n");
}

PrintActions(lpActs);

PrintStateFlags(lStateFlags);

printf("Rule Level = %u\n", lLevel);

printf("Rule Name = %s\n", lpszRuleName);

printf("\n");

lCurCursor++;
}

// Only print one record if cursor position was specified.

if (fPositionArgFnd)
break;

// Loop repeats; clean up allocations so ptrs can be reused.
// Note that MAPIFREEBUFFER() is a macro that nulls the ptr.

MAPIFREEBUFFER(lpRes);
MAPIFREEBUFFER(lpszRes);
MAPIFREEBUFFER(lpActs);
MAPIFREEBUFFER(lpszRuleName);
}

cleanup:

printf("\n");

// Clean up allocations:

MAPIFREEBUFFER(lpRes);
MAPIFREEBUFFER(lpszRes);
MAPIFREEBUFFER(lpActs);
MAPIFREEBUFFER(lpszRuleName);

if (lpFRulesLst != lpFRules)
ULRELEASE(lpFRulesLst);

return;
}

//$--EventLogHexErrorMsg-------------------------------------------------------
//
// DESCRIPTION: Write a message to the event log that contains a hex error code.
//This function is used for simple error messages that have the
//HRESULT (or other 4 byte error code) as the only insert.
//
// INPUT:
//
//[dwEvent]-- Event code for message string.
//[hr]-- HRESULT that will be printed in hex as the insert in the
//   message string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
EventLogHexErrorMsg(
INDWORDdwEvent,// event code for message string
INHRESULThr// HRESULT insert in the message string
)
{
CHARszErrorCode[11];

sprintf(szErrorCode, "%#.8x", hr);

EventLogMsg(dwEvent, 1, szErrorCode, 0);
}

//$--HrGetDefaultStoreName-----------------------------------------------------
//
// DESCRIPTION: Get the display name for the default store.
//
// INPUT:
//
//[lpSession]-- Ptr to MAPI session.
//
// OUTPUT:
//
//[lppDisplayName]-- Ptr to ptr that will be set to point at store
//   display name on successful return.
//
// RETURNS:     NOERRORif successful;
//E_*if call failed.
//-----------------------------------------------------------------------------

HRESULT
HrGetDefaultStoreName(// RETURNS:HRESULT
INLPMAPISESSIONlpSession,// MAPI session ptr
OUTLPSTR FAR *lppDisplayName// Display name buffer ptr
    )
{
static
SizedSPropTagArray(2, Columns) = {2,{PR_DEFAULT_STORE,PR_DISPLAY_NAME}};

ULONGcchDisplayName =0;
HRESULThr =NOERROR;
LPSTRlpDisplayName =NULL;
LPSRowSetlpRows =NULL;
LPMAPITABLElpTable =NULL;

    DEBUGPRIVATE("HrGetDefaultStoreName()\n");

hr = lpSession->GetMsgStoresTable(0, &lpTable);

if (FAILED(hr))
goto cleanup;

hr = lpTable->SetColumns((LPSPropTagArray)&Columns, 0);

if (FAILED(hr))
goto cleanup;

while (TRUE)
{
LPSPropValuelpProp;

hr = lpTable->QueryRows(1, 0, &lpRows);

if (FAILED(hr) || lpRows->cRows != 1)
break;

lpProp = &lpRows->aRow[0].lpProps[0];

if (lpProp->ulPropTag == PR_DEFAULT_STORE && lpProp->Value.b)
break;

FREEPROWS(lpRows);
}

if (FAILED(hr))
goto cleanup;

if (lpRows == NULL||
lpRows->cRows != 1||
lpRows->aRow[0].lpProps[1].ulPropTag != PR_DISPLAY_NAME||
lpRows->aRow[0].lpProps[1].Value.lpszA == NULL)
{
hr = HR_LOG(MAPI_E_NOT_FOUND);
goto cleanup;
}

// Allocate space for the display name to be returned and copy it out.

lpDisplayName = lpRows->aRow[0].lpProps[1].Value.lpszA;

cchDisplayName = strlen(lpDisplayName) + 1;

hr = MAPIAllocateBuffer(cchDisplayName, (LPVOID FAR *)lppDisplayName);

if (FAILED(hr))
goto cleanup;

strcpy(*lppDisplayName, lpDisplayName);

cleanup:

if (lpRows)
FREEPROWS(lpRows);

ULRELEASE(lpTable);

RETURN(hr);
}

//$--HrGetStoreType------------------------------------------------------------
//
// DESCRIPTION: Determine the store type of a given message store.
//
// INPUT:
//
//[lpSession]-- Ptr to MAPI session.
//[lpMDB]-- Ptr to MAPI store.
//[lpDisplayName]-- Ptr to display name of store to be evaluated.
//
// OUTPUT:
//
//[lpStoreType]-- Ptr to store type that will be set on successful return.
//
// RETURNS:     NOERRORif successful;
//E_*if call failed.
//-----------------------------------------------------------------------------

HRESULT
HrGetStoreType(// RETURNS:HRESULT
INLPMAPISESSIONlpSession,// MAPI session ptr
INLPMDBlpMDB,// store ptr
INLPSTRlpDisplayName,// store display name ptr
OUTSTORETYPE *lpStoreType// store type ptr
    )
{
// Note - There may be a problem with the following name changing.
//  Unfortunately, there is not a good other solution for
//  distinguishing between an exchange vs a non-exchange store
//  at present.

static CHARszExchangeName[] = "Microsoft Exchange Server";

static
SizedSPropTagArray(2, Columns) = {2,{PR_DISPLAY_NAME,PR_PROVIDER_DISPLAY}};

HRESULThr =NOERROR;
LPSRowSetlpRows =NULL;
LPMAPITABLElpTable =NULL;

    DEBUGPRIVATE("HrGetStoreType()\n");

hr = lpSession->GetMsgStoresTable(0, &lpTable);

if (FAILED(hr))
goto cleanup;

hr = lpTable->SetColumns((LPSPropTagArray)&Columns, 0);

if (FAILED(hr))
goto cleanup;

while (TRUE)
{
LPSPropValuelpProp;

hr = lpTable->QueryRows(1, 0, &lpRows);

if (FAILED(hr) || lpRows->cRows != 1)
break;

lpProp = &lpRows->aRow[0].lpProps[0];

if (lpProp->ulPropTag == PR_DISPLAY_NAME)
{
if (!stricmp(lpProp->Value.lpszA, lpDisplayName))
break;
}

FREEPROWS(lpRows);
}

if (FAILED(hr))
goto cleanup;

if (lpRows == NULL||
lpRows->cRows != 1||
lpRows->aRow[0].lpProps[1].ulPropTag != PR_PROVIDER_DISPLAY)
{
hr = HR_LOG(MAPI_E_NOT_FOUND);
goto cleanup;
}

if (!memcmp(lpRows->aRow[0].lpProps[1].Value.lpszA,
    szExchangeName, sizeof(szExchangeName) - 1))
{
if (FIsPublicStore(lpMDB))
*lpStoreType = ST_EXCHANGEPUBLIC;
else
*lpStoreType = ST_EXCHANGEMAILBOX;
}
else
{
*lpStoreType = ST_NONEXCHANGE;
}

cleanup:

if (lpRows)
FREEPROWS(lpRows);

ULRELEASE(lpTable);

RETURN(hr);
}

//$--HrProcessCommandSection---------------------------------------------------
//
// DESCRIPTION: Process the information parsed in a command section, and
//execute the command.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if execution should continue;
//                          E_FAIL if there is an initialization error;
//E_UNEXPECTED if there is an internal error.
//
// Notes:This function logs or prints errors associated with
//failure of an individual command, but does not return an
//error code.  If a more major error occurs that should
//stop execution, then an error code is returned.
//
//-----------------------------------------------------------------------------

HRESULT
HrProcessCommandSection(VOID)        // RETURNS: HRESULT
{
BOOLfSkipExec =FALSE;
HRESULThr =NOERROR;

    DEBUGPRIVATE("HrProcessCommandSection()\n");

// Confirm that initialization has been done.

if (lpFRules == NULL)
{
        hr = HR_LOG(E_FAIL);
PrintErr("Initialization section missing, or not at start of file");
fSkipExec = TRUE;
goto cleanup;
}

// Confirm that the command section has the required section entries.
// If anything past this point fails, we report or log the error, but
// return NOERROR and keep on going.

if (EditCmd == EDITCMD_INVALID)
{
PrintErr("Command must include a command argument");
fSkipExec = TRUE;
goto cleanup;
}
else if (EditCmd != EDITCMD_LIST)
{
if (!fPositionArgFnd)
{
if (EditCmd == EDITCMD_INSERT)
{
lCursor = RULE_PAST_END;
}
else
{
PrintErr("Command specified requires %s argument",
 szPositionKeyword);

fSkipExec = TRUE;
goto cleanup;
}
}

if (EditCmd == EDITCMD_INSERT)
{
if (lpRes == NULL)
{
PrintErr("Insert command requires %s argument",
 szConditionKeyword);

fSkipExec = TRUE;
goto cleanup;
}
else if (lpActions == NULL)
{
PrintErr("Insert command requires %s argument",
 szActionKeyword);

fSkipExec = TRUE;
goto cleanup;
}

{
LPACTIONlpAction =lpActions->lpAction;
LPACTIONlpActionInv =lpActions->lpAction +
lpActions->cActions;

while (lpAction < lpActionInv)
{
if (lpAction->acttype == OP_MOVE||
lpAction->acttype == OP_COPY)
{
if (stStoreType == ST_EXCHANGEPUBLIC)
{
PrintErr("Move and Copy actions are only supported"
  " by Exchange mailboxes");

fSkipExec = TRUE;
goto cleanup;
}

if (lpAction->actMoveCopy.cbStoreEntryId !=
cbEIDStore||
memcmp(lpAction->actMoveCopy.lpStoreEntryId,
   lpEIDStore,
   cbEIDStore))
{
PrintErr("Move and Copy actions are only supported"
  " to destinations in the same store");

fSkipExec = TRUE;
goto cleanup;
}
}
else if (lpAction->acttype == OP_BOUNCE)
{
if (stStoreType == ST_EXCHANGEMAILBOX)
{
PrintErr("Bounce actions are only supported"
  " by Exchange public stores");

fSkipExec = TRUE;
goto cleanup;
}
}
else if (lpAction->acttype == OP_DELEGATE)
{
if (stStoreType == ST_EXCHANGEPUBLIC)
{
PrintErr("Delegate actions are only supported"
  " by Exchange mailboxes");

fSkipExec = TRUE;
goto cleanup;
}
}

lpAction++;
}
}
}
}

// Only process the section if there were no errors in parsing it.

if (cErrs > cPriorErrs)
{
fSkipExec = TRUE;
goto cleanup;
}

switch (EditCmd)
{
case EDITCMD_LIST:
if (fProviderArgFnd)
{
DoList(szProvider);
}
else
{
ULONGi = 0;

// We have to redetermine the providers every time we do a list
// because RuleEdit may have inserted rules in the table.

MAPIFREEBUFFER(lppszProviders);

hr = HrFolderRulesGetProviders(lpStore,
   cbEIDFolder,
   lpEIDFolder,
   &cProviders,
   &lppszProviders);
if (FAILED(hr))
{
PrintErr("Folder providers could not be determined");
EventLogHexErrorMsg(RULEEDIT_FOLDERRULESGETPROVIDERS_FAILED,hr);

// We treat this as a killer error.

goto cleanup;
}

if (cProviders == 0)
printf("Rules table is empty.\n\n");
else
for (i = 0; i < cProviders; i++)
DoList(lppszProviders[i]);
}
break;

case EDITCMD_INSERT:
DoInsert();
break;

case EDITCMD_DELETE:
DoDelete();
break;

case EDITCMD_ENABLE:
DoEnable();
break;

case EDITCMD_DISABLE:
DoDisable();
break;

// Can only occur if the code gets screwed up in maintenance:

default:
hr = HR_LOG(E_UNEXPECTED);
PrintErr("Unexpected internal error in ruleedit");
EventLogHexErrorMsg(RULEEDIT_INTERNAL_ERROR, hr);
goto cleanup;
break;
}

cleanup:

if (fSkipExec)
{
printf("Execution of command at line %u not attempted "
   "due to errors.\n\n", iSection);
}

    RETURN(hr);
}

//$--HrProcessInitializationSection--------------------------------------------
//
// DESCRIPTION: Process the information parsed in an initialization section,
//logging onto a MAPI profile session and opening the message
//store, its folder, and rules table.  If profile or folder info
//was provided on the command line, that is processed instead.
//
// INPUT:       None.
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_* otherwise.
//
// Notes:If this function fails, the error should be treated as a
//fatal error.
//
//-----------------------------------------------------------------------------

HRESULT
HrProcessInitializationSection(VOID)       // RETURNS: HRESULT
{
HRESULThr =NOERROR;
LPSTRpszStore =NULL;
ULONGulObjType =0;

    DEBUGPRIVATE("HrProcessInitializationSection()\n");

// Confirm that this is the first Initialization section we have
// encountered.

if (lpFRules != NULL)
{
if (!fSkipProfileArg || !fSkipFolderArg)
{
PrintErr("Multiple Initialization sections in Ini File");
hr = HR_LOG(E_FAIL);
}

// It is okay to find an initialization section after initialization
// if all args were specified on the command line.

goto cleanup;
}

// Only process the section if there were no errors in parsing it.

if (cErrs > cPriorErrs)
goto cleanup;

if (szProfile[0] == '\0')
{
PrintErr("Profile not specified on command line or in Ini File");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

if (szFolder[0] == '\0' && !fInboxArgFnd)
{
PrintErr("Folder not specified on command line or in Ini File");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Use default provider value if one was not specified.

if (!fProviderArgFnd)
strcpy(szProvider, "MSFT:RuleEdit");

hr = MAPILogonEx(0,
     szProfile,
     NULL,
     MAPI_EXTENDED | MAPI_NEW_SESSION,
     &lpSession);

if (FAILED(hr))
{
PrintErr("MAPI Logon failed");
EventLogHexErrorMsg(RULEEDIT_MAPILOGON_FAILED, hr);
        goto cleanup;
}

// Get the entry id of the specified store and open it.

if (fInboxArgFnd)
{
hr = HrMAPIFindDefaultMsgStore(lpSession, &cbEIDStore, &lpEIDStore);

if (FAILED(hr))
{
PrintErr("Default message store could not be determined");
EventLogHexErrorMsg(RULEEDIT_FINDDEFAULTMSGSTORE_FAILED, hr);
goto cleanup;
}

hr = HrGetDefaultStoreName(lpSession, &pszStore);

if (FAILED(hr))
{
PrintErr("Default message store name could not be determined");
EventLogHexErrorMsg(RULEEDIT_GETDEFAULTSTORENAME_FAILED, hr);
goto cleanup;
}

if (strlen(pszStore) >= sizeof(szStore))
{
PrintErr("Default store name too long"); // Highly unexpected.
goto cleanup;
}

strcpy(szStore, pszStore);
}
else
{
hr = HrMAPIFindStore(lpSession, szStore, &cbEIDStore, &lpEIDStore);

if (FAILED(hr))
{
PrintErr("Message store not found");
EventLogHexErrorMsg(RULEEDIT_FINDSTORE_FAILED, hr);
goto cleanup;
}
}

hr = lpSession->OpenMsgStore(0,
 cbEIDStore,
 lpEIDStore,
 NULL,
 MAPI_DEFERRED_ERRORS | MAPI_BEST_ACCESS,
 &lpStore);
if (FAILED(hr))
{
PrintErr("Message store could not be opened");
EventLogHexErrorMsg(RULEEDIT_OPENMSGSTORE_FAILED, hr);
goto cleanup;
}

// Confirm that this is an Exchange store.  We stop processing if it
// is not.

hr = HrGetStoreType(lpSession, lpStore, szStore, &stStoreType);

if (FAILED(hr))
{
PrintErr("Message store provider type could not be determined");
EventLogHexErrorMsg(RULEEDIT_GETSTORETYPE_FAILED, hr);
goto cleanup;
}

if (stStoreType == ST_NONEXCHANGE)
{
PrintErr("Rules are only supported by Exchange Server stores");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

// Get the entry id of the specified folder and open it.

if (fInboxArgFnd)
{
LPSTRlpszExplicitClass = "";

hr = lpStore->GetReceiveFolder(NULL,
   0,
   &cbEIDFolder,
   &lpEIDFolder,
   &lpszExplicitClass);
if (FAILED(hr))
{
PrintErr("Inbox not found");
EventLogHexErrorMsg(RULEEDIT_GETRECEIVEFOLDER_FAILED, hr);
goto cleanup;
}

// BUGBUG - We may want to look this up instead of hardwiring it.

strcpy(szFolder, "Top of Information Store\\Inbox");
}
else
{
hr = HrMAPIFindFolderEx(lpStore,
'\\',
szFolder,
&cbEIDFolder,
&lpEIDFolder);
if (FAILED(hr))
{
PrintErr("Specified folder not found");
EventLogHexErrorMsg(RULEEDIT_FINDFOLDER_FAILED, hr);
goto cleanup;
}
}

// Get a folder pointer.  This is required for HrStringToAction().

hr = lpStore->OpenEntry(cbEIDFolder,
lpEIDFolder,
NULL,
MAPI_BEST_ACCESS |
MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *)&lpFolder);
if (FAILED(hr))
{
PrintErr("Specified folder could not be opened");
EventLogHexErrorMsg(RULEEDIT_OPENENTRY_FAILED, hr);
goto cleanup;
}

// Get a folder rules interface ptr for the specified folder.  This ptr
// will be used for all commands with the exception of the list command
// when no provider has been specified.  In that instance, the list
// command has to get a ptr for each provider.

    hr = HrFolderRulesOpen(lpStore,
   cbEIDFolder,
   lpEIDFolder,
   szProvider,
   &lpFRules);

    if (FAILED(hr))
    {
if (hr == E_NOINTERFACE)
{
PrintErr("Specified folder does not support rules");
EventLogMsg(RULEEDIT_NORULESTABLE, 0, 0);
}
else
{
PrintErr("Specified folder could not be opened");
EventLogHexErrorMsg(RULEEDIT_FOLDERRULESOPEN_FAILED, hr);
}
        goto cleanup;
    }

cleanup:

MAPIFREEBUFFER(pszStore);

RETURN(hr);
}

//$--IsSectionLine-------------------------------------------------------------
//
// DESCRIPTION: Determine if an input line is a section header (ie., first
//non-whitespace char is '[').
//
// INPUT:       [pch]-- Ptr to input line.
//
// RETURNS:     TRUE if input line is a section line; FALSE otherwise.
//-----------------------------------------------------------------------------

BOOL
IsSectionLine(// RETURNS: BOOL
    INCHAR *  pch// input line
    )
{
    DEBUGPRIVATE("IsSectionLine()\n");

while (isspace(*pch))// Skip leading line whitespace.
pch++;

return (*pch == '[');
}

//$--lrsGetNextLine------------------------------------------------------------
//
// DESCRIPTION: Get the next input line from the ini file and deposit it in
//the input buffer (achIniLine).
//
// INPUT:None.
//
// RETURNS:
//
//  LRS_OKAY if line was read and is available.
//  LRS_EOF if we are at end of file; no more lines.
//  LRS_IOERROR if there was an I/O error in the last read.
//  LRS_LINETOOLONG if last line was too long to fit in the buffer.
//-----------------------------------------------------------------------------

LINEREADSTATUS
lrsGetNextLine(VOID)// RETURNS: LINEREADSTATUS
{
    INT             ch =0;
LINEREADSTATUSlrs =LRS_OKAY;
    CHAR *          pch =       &achIniLine[0];
    CHAR *          pchInv =    &achIniLine[cbMaxIniLine];

    DEBUGPRIVATE("lrsGetNextLine()\n");

    while (TRUE)
    {
        if (pch == pchInv)
        {
            // Line is too long.

            lrs =           LRS_LINETOOLONG;
            achIniLine[0] = '\0';
            while ((ch = getc(IniFile)) != EOF && (ch != '\n'));

            break;
        }

        ch = getc(IniFile);

        if (ch == '\n')
        {
            // Just read end of this line.

            lrs =   LRS_OKAY;
            *pch =  '\0';

            break;
        }

        if (ch == EOF)
        {
            // We have read last line in file.
            // We want to process the last line, if any.

            *pch = '\0';

            if (pch == &achIniLine[0])
            {
                // The last line was properly terminated with a newline
// and we are truly at EOF.

                lrs = feof(IniFile) ? LRS_EOF : LRS_IOERROR;
            }
            else
            {
                // The last line was not properly terminated and has data.

                lrs = feof(IniFile) ? LRS_OKAY : LRS_IOERROR;
            }

            break;
        }

        *pch = ch;
        pch++;
    }

    return lrs;
}

//$--ParseLine-----------------------------------------------------------------
//
// DESCRIPTION: Parse an ini file line, and store the value if it is a section
//entry line.
//
// INPUT:       None.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

VOID
ParseLine(VOID)// RETURNS: VOID
{
    CHAR *      pch =&achIniLine[0];

    DEBUGPRIVATE("ParseLine()\n");

    while (isspace(*pch))           // Skip leading line whitespace.
        pch++;

    if (*pch == '\0')               // It is a blank line.
    {
;
    }
    else if (*pch == ';')           // It is a comment.
    {
;
    }
else
{
if ( *pch == '[' )  // It is a section, or is invalid
{
iSection = iLine;
cPriorErrs = cErrs;
ParseSectionLine(pch + 1);
}
else                            // It is a section entry, or is invalid.
{
ParseEntryLine(pch);
}
}
}

//$--ParseSectionLine----------------------------------------------------------
//
// DESCRIPTION: Parse an ini file section header line.
//
// INPUT:       [pch]-- Ptr to section keyword.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

VOID
ParseSectionLine(// RETURNS:VOID
    IN      CHAR *  pch// input line
    )
{
    CHAR        chTmp   =   '\0';
BOOLfBadSyntax =FALSE;
    CHAR *      pchKeyword  =   NULL;

    DEBUGPRIVATE("ParseSectionLine()\n");

    // Save ptr to keyword.

    pchKeyword = pch;

    // Scan for end of keyword and delimit keyword, saving replaced char.

    while (!isspace(*pch) && *pch != ']' && *pch != '\0')
        pch++;

    if (*pch == '\0')
{
fBadSyntax = TRUE;
goto cleanup;
}

    chTmp = *pch;
    *pch  = '\0';

    // Compare putative keyword against valid section keywords.

    if (!stricmp(pchKeyword, szCommandKeyword))
    {
// Numeric value may follow "command" keyword.  We don't use it,
// but we do a rough check that what we are seeing is consistent
// with what we expect.

        *pch = chTmp;

        while (isspace(*pch))
            pch++;

        if (!isdigit(*pch))
{
fBadSyntax = TRUE;
goto cleanup;
}

pch++;

        while (isdigit(*pch))
            pch++;

        // Check for proper section entry termination.

        if (*pch != ']' || *(pch + 1) != '\0')
{
fBadSyntax = TRUE;
goto cleanup;
}

ifstCurSection = IFST_COMMAND;

// Initialize command section parser state.

EditCmd =EDITCMD_INVALID;
fPositionArgFnd = FALSE;

MAPIFREEBUFFER(lpActions);
MAPIFREEBUFFER(lpRes);

lStateFlags = ST_ENABLED;
fFlagsArgFnd = FALSE;
    }
    else if (!stricmp(pchKeyword, szInitializationKeyword))
    {
        // Check for proper section entry termination.
        // Initialization sections don't have a value.

        *pch = chTmp;

        if (*pch != ']' || *(pch + 1) != '\0')
{
fBadSyntax = TRUE;
goto cleanup;
}

        ifstCurSection = IFST_INITIALIZATION;
    }
    else
    {
PrintIniFileErr("Invalid keyword");

ifstCurSection = IFST_UNKNOWN;// Invalid section keyword.
        *pch = chTmp;
goto cleanup;
    }

fBadSyntax = FALSE;

cleanup:

if (fBadSyntax)
PrintIniFileErr("Bad syntax");

return;
}

//$--ParseEntryLine------------------------------------------------------------
//
// DESCRIPTION: Parse an ini file section entry line to determine what kind of
//entry it is, and store the value.  Section entry lines are the
//lines that follow the section header.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//
//-----------------------------------------------------------------------------

VOID
ParseEntryLine(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    INIFILEPARSEINFO *  aifpiEntry =NULL;
    CHAR                chTmp ='\0';
    INT                 cKeywords =0;
    INT                 i =0;
    CHAR *              pchKeyword =NULL;

    DEBUGPRIVATE("ParseEntryLine()\n");

    if (ifstCurSection == IFST_COMMAND)
    {
        aifpiEntry = aifpiCommandEntry;
        cKeywords = sizeof(aifpiCommandEntry) /
sizeof(aifpiCommandEntry[0]);
    }
    else if (ifstCurSection == IFST_INITIALIZATION)
    {
        aifpiEntry = aifpiInitializationEntry;
        cKeywords = sizeof(aifpiInitializationEntry) /
sizeof(aifpiInitializationEntry[0]);
    }
    else
    {
PrintIniFileErr("Entry line not in a valid section");
        goto cleanup;
    }

    // Save ptr to keyword.

    pchKeyword = pch;

    // Scan for end of keyword and delimit keyword, saving replaced char.

    while (*pch != '=' &&  *pch != ':' &&  !isspace(*pch) && *pch != '\0')
        pch++;

    chTmp = *pch;

    *pch  = '\0';

    // Compare putative keyword against valid section keywords.

    for (i = 0; i < cKeywords; i++)
        if (!stricmp(pchKeyword, aifpiEntry[i].pszKeyword))
            break;

    *pch = chTmp;

    if (i == cKeywords)
{
PrintIniFileErr("Invalid keyword");
        goto cleanup;
}

while (isspace(*pch))
pch++;

    aifpiEntry[i].ParseFunction(*pch == '\0' ? NULL : pch + 1);

cleanup:

return;
}

//$--ParseActionValue----------------------------------------------------------
//
// DESCRIPTION: Parse an Action value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//
// Notes:In the case of an OP_MOVE or OP_COPY action, it is necessary
//to have a MAPI session to properly set up the action struct.
//If a session has not been established, we don't report an error
//even though the action could not have been completed, because
//the error will be caught as a fatal error in
//HrProcessCommandSection().
//-----------------------------------------------------------------------------

VOID
ParseActionValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
static CHARszCopyKeyword[] ="copy";
static CHARszMoveKeyword[] ="move";
static CHARszReplyKeyword[] ="reply";

HRESULThr =NOERROR;

    DEBUGPRIVATE("ParseActionValue()\n");

if (pch == NULL|| *pch == '\0')
{
PrintIniFileErr("Action entry must have a value");
goto cleanup;
}

if (lpActions == NULL)
{
hr = MAPIAllocateBuffer(sizeof(ACTIONS), (LPVOID FAR *)&lpActions);

if (FAILED(hr))
{
PrintErr("Memory allocation failed");
EventLogHexErrorMsg(RULEEDIT_ALLOCATION_FAILED, hr);
goto cleanup;
}

lpActions->ulVersion =EDK_RULES_VERSION;
lpActions->cActions =0;
lpActions->lpAction =NULL;

hr = MAPIAllocateMore(sizeof(ACTION) * cMaxActions,
  lpActions,
  (LPVOID FAR *)&lpActions->lpAction);
if (FAILED(hr))
{
PrintErr("Memory allocation failed");
EventLogHexErrorMsg(RULEEDIT_ALLOCATION_FAILED, hr);
goto cleanup;
}

memset(lpActions->lpAction, 0, sizeof(ACTION) * cMaxActions);
}
else
{
if (lpActions->cActions > cMaxActions)
{
PrintIniFileErr("Maximum count of rule actions exceeded");
goto cleanup;
}
}

// Check if the session ptr is NULL.  If so, further check to see if
// this is a move, copy or reply action.  In that case we know
// HrStringToAction() would fail, and simply return.  The fact that the
// session was not established will be reported by other code
// (see note above).

if (lpSession == NULL)
{
if (!memicmp(szCopyKeyword, pch, sizeof(szCopyKeyword) - 1)||
!memicmp(szMoveKeyword, pch, sizeof(szMoveKeyword) - 1)||
!memicmp(szReplyKeyword, pch, sizeof(szReplyKeyword) - 1))
goto cleanup;
}

hr = HrStringToAction(lpSession,
  lpFolder,
  pch,
  lpActions,
  &lpActions->lpAction[lpActions->cActions]);

if (FAILED(hr))
{
if (hr == E_INVALIDARG)
PrintIniFileErr("Bad syntax or value in action value");
else
PrintErr("Action creation failed for action at line %u of ini file;"

"Error code = %#x", iLine, hr);
goto cleanup;
}

lpActions->cActions++;

cleanup:

    return;
}

//$--ParseCommandValue---------------------------------------------------------
//
// DESCRIPTION: Parse a Command value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseCommandValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    DEBUGPRIVATE("ParseCommandValue()\n");

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Command entry must have a value");
goto cleanup;
}

if (EditCmd != EDITCMD_INVALID)
{
PrintIniFileErr("Multiple command entries");
goto cleanup;
}

if (!stricmp(pch, szDeleteKeyword))
{
EditCmd = EDITCMD_DELETE;
}
else if (!stricmp(pch, szDisableKeyword))
{
EditCmd = EDITCMD_DISABLE;
}
else if (!stricmp(pch, szEnableKeyword))
{
EditCmd = EDITCMD_ENABLE;
}
else if (!stricmp(pch, szInsertKeyword))
{
EditCmd = EDITCMD_INSERT;
}
else if (!stricmp(pch, szListKeyword))
{
EditCmd = EDITCMD_LIST;
}
else
{
PrintIniFileErr("Command '%s' is not valid", pch);
}

cleanup:

    return;
}

//$--ParseConditionValue-------------------------------------------------------
//
// DESCRIPTION: Parse a Condition value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseConditionValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
HRESULThr =NOERROR;

    DEBUGPRIVATE("ParseConditionValue()\n");

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Condition entry must have a value");
goto cleanup;
}

if (lpRes != NULL)
{
PrintIniFileErr("Multiple condition entries");
goto cleanup;
}

hr = HrStringToRestriction(pch, NULL, &lpRes);

if (FAILED(hr))
{
if (hr == E_INVALIDARG)
PrintIniFileErr("Bad syntax or value in condition expression");
else
PrintErr("Restriction creation failed for condition at line %u "
 "of ini file; Error code = %#x", iLine, hr);
goto cleanup;
}

cleanup:

    return;
}

//$--ParseFlagsValue-----------------------------------------------------------
//
// DESCRIPTION: Parse a Flags value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseFlagsValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
CHARchTmp ='\0';
BOOLfBadSyntax =FALSE;
BOOLfDisabledKeywordFnd =FALSE;
CHAR *pchEnd =NULL;

    DEBUGPRIVATE("ParseFlagsValue()\n");

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Flags entry must have a value");
goto cleanup;
}

if (fFlagsArgFnd)
{
PrintIniFileErr("Multiple Flags entries");
goto cleanup;
}

while (isspace(*pch))// Skip leading line whitespace.
pch++;

if (*pch == '\0')
{
fBadSyntax = TRUE;
goto cleanup;
}

// We override the default of ST_ENABLED since we have a flags= entry.

lStateFlags = ST_DISABLED;

while(TRUE)
{
pchEnd = pch;

while (isalpha(*pchEnd))
pchEnd++;

chTmp = *pchEnd;

*pchEnd = '\0';

if (!stricmp(pch, szEnabledKeyword))
{
lStateFlags |=ST_ENABLED;
}
else if (!stricmp(pch, szDisabledKeyword))
{
fDisabledKeywordFnd = TRUE;
}
else if (!stricmp(pch, szUserOOFKeyword))
{
lStateFlags |=ST_ONLY_WHEN_OOF;
}
else if (!stricmp(pch, szErrorKeyword))
{
lStateFlags |=ST_ERROR;
}
else if (!stricmp(pch, szKeepOOFHistKeyword))
{
lStateFlags |=ST_KEEP_OOF_HIST;
}
else if (!stricmp(pch, szExitLevelKeyword))
{
lStateFlags |=ST_EXIT_LEVEL;
}
else
{
*pchEnd = chTmp;
fBadSyntax = TRUE;
goto cleanup;
}

*pchEnd = chTmp;

pch = pchEnd;

while (isspace(*pch))
pch++;

if (*pch == '\0')
break;

if (*pch == ',')
{
pch++;
}
else
{
fBadSyntax = TRUE;
goto cleanup;
}

while (isspace(*pch))
pch++;
}

if ((lStateFlags & ST_ENABLED) && fDisabledKeywordFnd)
{
PrintIniFileErr("Conflicting flags in Flags entry");
goto cleanup;
}

fFlagsArgFnd = TRUE;

cleanup:

if (fBadSyntax)
{
PrintIniFileErr("Bad syntax in Flags entry");
goto cleanup;
}

    return;
}

//$--ParsePositionValue--------------------------------------------------------
//
// DESCRIPTION: Parse a position value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParsePositionValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    DEBUGPRIVATE("ParsePositionValue()\n");

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Position entry must have a value");
goto cleanup;
}

if (fPositionArgFnd)
{
PrintIniFileErr("Multiple position entries");
goto cleanup;
}

if (!stricmp(pch, "END"))
{
lCursor = RULE_PAST_END;
}
else
{
errno = 0;

lCursor = strtol(pch, (CHAR **)NULL, 10);

if ((lCursor == LONG_MAX || lCursor == LONG_MIN) &&
errno == ERANGE)
{
PrintIniFileErr("Position value invalid");
goto cleanup;
}
}

fPositionArgFnd = TRUE;

cleanup:

    return;
}

//$--ParseFolderValue----------------------------------------------------------
//
// DESCRIPTION: Parse a Folder value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseFolderValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    DEBUGPRIVATE("ParseFolderValue()\n");

if (fSkipFolderArg)
goto cleanup;

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Folder entry must have a value");
goto cleanup;
}

if (szFolder[0] != '\0' || fInboxArgFnd)
{
PrintIniFileErr("Multiple folder entries");
goto cleanup;
}

if (!stricmp(pch, szInboxKeyword))
{
// It is the special name "inbox", which is interpreted as
// appropriate.

fInboxArgFnd = TRUE;
}
else
{
// Split the store and folder names and check their lengths.

CHAR *pszFolder = strchr(pch, '\\');

if (pszFolder == NULL)
{
PrintIniFileErr("Invalid folder name");
goto cleanup;
}

*pszFolder = '\0';
pszFolder++;

if (*pch == '\0')
{
PrintIniFileErr("Invalid folder name");
goto cleanup;
}

if (strlen(pch) >= sizeof(szStore))
{
PrintIniFileErr("Folder name too long");
goto cleanup;
}

strcpy(szStore, pch);

if (*pszFolder == '\0')
{
PrintIniFileErr("Invalid folder name");
goto cleanup;
}

if (strlen(pszFolder) >= sizeof(szFolder))
{
PrintIniFileErr("Folder name too long");
goto cleanup;
}

strcpy(szFolder, pszFolder);
}

cleanup:

    return;
}

//$--ParseProfileValue---------------------------------------------------------
//
// DESCRIPTION: Parse a Profile value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseProfileValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    DEBUGPRIVATE("ParseProfileValue()\n");

if (fSkipProfileArg)
goto cleanup;

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Profile entry must have a value");
goto cleanup;
}

if (szProfile[0] != '\0')
{
PrintIniFileErr("Multiple profile entries");
goto cleanup;
}

if (strlen(pch) >= sizeof(szProfile))
{
PrintIniFileErr("Profile name too long");
goto cleanup;
}

strcpy(szProfile, pch);

cleanup:

    return;
}

//$--ParseProviderValue--------------------------------------------------------
//
// DESCRIPTION: Parse a Provider value entry line.
//
// INPUT:       [pch]-- Ptr to input string.
//
// RETURNS:     Nothing.
//-----------------------------------------------------------------------------

VOID
ParseProviderValue(// RETURNS: VOID
    IN      CHAR *  pch// input line
    )
{
    DEBUGPRIVATE("ParseProviderValue()\n");

if (fSkipProviderArg)
goto cleanup;

if (pch == NULL || *pch == '\0')
{
PrintIniFileErr("Provider entry must have a value");
goto cleanup;
}

if (szProvider[0] != '\0')
{
PrintIniFileErr("Multiple provider entries");
goto cleanup;
}

if (strlen(pch) >= sizeof(szProvider))
{
PrintIniFileErr("Provider name too long");
goto cleanup;
}

strcpy(szProvider, pch);

fProviderArgFnd = TRUE;

cleanup:

    return;
}

//$--PrintActions--------------------------------------------------------------
//
// DESCRIPTION:    Print the rule actions.
//
// INPUT:
//
//[lpActs]-- Rule actions to print.
//
// RETURNS:VOID
//
//-----------------------------------------------------------------------------

VOID
PrintActions(     // RETURNS: VOID
INLPACTIONSlpActs  // actions ptr
)
{
HRESULThr =NOERROR;
ULONGi;
LPACTIONlpAct =NULL;
LPSTRlpszAct =NULL;

    DEBUGPRIVATE("PrintActions()\n");

if (lpActs->cActions <= 0 || lpActs->lpAction == NULL)
{
printf("Rule Actions structure is invalid.\n");
        goto cleanup;
}

for (i = 0; i < lpActs->cActions; i++)
{
lpAct =&(lpActs->lpAction[i]);

hr = HrActionToString(lpSession, lpAct, &lpszAct);

if (FAILED(hr))
{
printf("String representation of rule action %u "
   "could not be determined.\n", i);
cErrs++;
EventLogHexErrorMsg(RULEEDIT_ACTIONTOSTRING_FAILED, hr);
}
else
{
printf("Rule Action %u = %s\n", i, lpszAct);
}

MAPIFREEBUFFER(lpszAct);
}

cleanup:

    return;
}

//$--PrintErr------------------------------------------------------------------
//
// DESCRIPTION:Print an error message to stdout in a standard format.
//
// INPUT:
//
//[Format]-- Printf-style format string.
//[...]-- Variable argument parameters for format string.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
PrintErr(        // RETURNS: VOID
INCHAR *Format      // error message format
...
)
{
va_listargs;

    DEBUGPRIVATE("PrintErr()\n");

cErrs++;

printf("ERROR: ");

va_start(args, Format);

vprintf(Format, args);

va_end(args);

printf(".\n\n");

    return;
}

//$--PrintIniFileErr-----------------------------------------------------------
//
// DESCRIPTION:Print an error message to stdout in a standard format.
//This is intended primarily for use in reporting ini file
//input errors.
//
// INPUT:
//
//[Format]-- Printf-style format string.
//[...]-- Variable argument parameters for format string.
//
// RETURNS:Nothing.
//
//-----------------------------------------------------------------------------

VOID
PrintIniFileErr(        // RETURNS: VOID
INCHAR *Format      // error message format
...
)
{
va_listargs;

    DEBUGPRIVATE("PrintIniFileErr()\n");

cErrs++;

printf("ERROR: ");

va_start(args, Format);

vprintf(Format, args);

va_end(args);

printf(" in line %u of Ini File.\n\n", iLine);

    return;
}

//$--PrintStateFlags-----------------------------------------------------------
//
// DESCRIPTION:    Print the rule state flags.
//
// INPUT:
//
//[lStateFlags]-- Rule state flags to print.
//
// RETURNS:VOID
//
//-----------------------------------------------------------------------------

VOID
PrintStateFlags(   // RETURNS: VOID
INLONGlStateFlags// state flags
)
{
    DEBUGPRIVATE("PrintStateFlags()\n");

printf("Rule state flags =");

if (lStateFlags & ST_ENABLED)
printf(" Enabled");
else
printf(" Disabled");

if (lStateFlags & ST_ERROR)
printf(", Error");

if (lStateFlags & ST_ONLY_WHEN_OOF)
printf(", UserOOF");

if (lStateFlags & ST_KEEP_OOF_HIST)
printf(", KeepOOFHist");

if (lStateFlags & ST_EXIT_LEVEL)
printf(", ExitLevel");

printf("\n");

    return;
}

//$--Usage---------------------------------------------------------------------
//
// DESCRIPTION:Print a usage message to the console.
//
// INPUT:None.
//
// RETURNS:Nothing.
//-----------------------------------------------------------------------------

VOID
Usage(VOID)  // RETURNS: VOID
{
    DEBUGPRIVATE("Usage()\n");

printf(
"\n"
"USAGE: RULEEDIT [/Profile=<ProfileName>]"
"\n"
"                [<Folder>]"
"\n"
"                [/List | /File=<IniFile>]"
"\n"
"                [/Provider=<ProviderName>]"
"\n"
"\n"
"<Folder> ::= /Folder=<StoreName>\\<FolderPath> | /Folder=Inbox"
"\n"
"\n"
"Enter RULEEDIT /? for more details."
"\n"
"\n"
);
}


APPT.BAS

Attribute VB_Name = "ApptObjectRoutines"
Option Explicit

'******************************
'
' Calls all scripts in this module
'
'******************************
Function CallAllApptScripts()

Set gobjBase = objSetBaseObject

If gobjBase.LoggedOn = False Then
    gobjBase.Logon profileName:="ryanw24"
End If

Set gobjSched = gobjBase.ScheduleSelected
gobjSched.Activate

'Create a simple task (desc. and notes)
'    bCreateSimpleTask (1)

'Create a task with all fields filled (except contact and role)
 '   bCreateTaskAllFields (1)

'Create an 8K Task
    bCreate8KAppt (1000)

'Create simple recurring task
'    bCreateSimpleRecurTask (1)
'Create recurring task with all fields filled
'Create an 8K task

Set gobjSched = Nothing
Set gobjBase = Nothing

End Function
Function bCreate8KAppt(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer
Dim szText As String

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Get the 8k of text
szText = szGetMuchText(8192)

'Create some simple tasks

Set objTable = objGetTable(SINGLE_APPOINTMENTS)

For i = 1 To nNumber
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreate8KAppt " & Str$(i) & " " & Now() & szText), Notes:="Notes", _
        BusyType:=CLng(1), _
        Start:=(DateAdd("h", i, Now())), End:=(DateAdd("h", i + 1, Now()))
    Set objItem = Nothing
Next i

Set objTable = Nothing

bCreate8KAppt = True

End Function


CORE.BAS

Attribute VB_Name = "CoreObjectRoutines"
Option Explicit

Public gobjBase As Object
Public gobjSched As Object

Global Const SINGLE_APPOINTMENTS = "SingleAppointments"
Global Const RECURRING_APPOINTMENTS = "RecurringAppointments"
Global Const SINGLE_EVENTS = "SingleEvents"
Global Const RECURRING_EVENTS = "RecurringEvents"
Global Const SINGLE_TASKS = "SingleTasks"
Global Const RECURRING_TASKS = "RecurringTasks"
Global Const CONTACTS = "Contacts"
Global Const PROJECTS = "Projects"
Global Const ATTENDEES = "Attendees"
Global Const ACCESS_CONTROLS = "AccessControls"

'Sacls for setting AccessActual and AccessEffective and AccessDefault
Global Const saclNone = (0)
Global Const saclReadMinimal = (1)
Global Const saclRead = (2)
Global Const saclCreate = (3)
Global Const saclWrite = (4)
Global Const saclOwner = (5)
Global Const saclMax = (6)

'RecurringType for recurring tasks and appointments
'/* Pattern = Every X days */
Global Const trecurDaily = (64)
'/* Pattern = Every X weeks on DayOfWeekMask days */
Global Const trecurWeekly = (48)
'/* Pattern = Day X of every Y months */
Global Const trecurMonthly1 = (12)
'/* Pattern = {1st | 2nd | 3rd | 4th | Last} Day of Week of every Y months */
Global Const trecurMonthly2 = (56)
'/* Pattern = On Month Day */
Global Const trecurYearly1 = (7)
'/* Pattern = {1st | 2nd | 3rd | 4th | Last} Day of Week of Month */
Global Const trecurYearly2 = (51)

Function objSetBaseObject() As Object

    Set objSetBaseObject = CreateObject("Schedule+.Application")

End Function

'*****************************************
'
' objGetTable - returns obj for Task
'
'   szTable - Constant passed in for table name (see General Declaractions
'
'*****************************************
Function objGetTable(szTable As String) As Object

Select Case szTable
    Case SINGLE_APPOINTMENTS
        Set objGetTable = gobjSched.SingleAppointments
    Case RECURRING_APPOINTMENTS
        Set objGetTable = gobjSched.RecurringAppointments
    Case SINGLE_EVENTS
        Set objGetTable = gobjSched.SingleEvents
    Case RECURRING_EVENTS
        Set objGetTable = gobjSched.RecurringEvents
    Case SINGLE_TASKS
        Set objGetTable = gobjSched.SingleTasks
    Case RECURRING_TASKS
        Set objGetTable = gobjSched.RecurringTasks
    Case CONTACTS
        Set objGetTable = gobjSched.CONTACTS
    Case PROJECTS
        Set objGetTable = gobjSched.PROJECTS
    Case ATTENDEES
        Set objGetTable = gobjSched.ATTENDEES
    Case ACCESS_CONTROLS
        Set objGetTable = gobjSched.AccessControls
    Case Else
        Debug.Print "objGetTaskTable: invalid table name passed"
End Select

End Function

'****************************************************************************************************
' szGetMuchText --   Generate a specified amount of text
'
'   STATE:  Not state dependent
'
'   INPUTS:
'       nCharacters - The number of characters to be generated from 1 to 65,500(?)
'       szReturnText - Variable the generated characters will be stored in
'
'****************************************************************************************************
Function szGetMuchText(nCharacters As Integer) As String

Dim a As String             ' repeated string
Dim a_len As Integer        ' length of "a" (short integer)
Dim GMTLoop As Long         ' number of times to loop
Dim GMTRemain As Long       ' messy remainder of division
Dim i As Long               ' counter
Dim szReturnText As String

' Initialize variables
    szReturnText = ""
    a = "This is " + Str$(nCharacters) + " characters of text.*"
    a_len = Len(a)

' Generate characters
    If a_len >= nCharacters Then
        szReturnText = Left$(a, nCharacters)
    Else
        GMTLoop = Int(nCharacters / a_len)
        GMTRemain = nCharacters Mod a_len
        For i = 1 To GMTLoop
            szReturnText = szReturnText + a
        Next
        szReturnText = szReturnText + Left$(a, GMTRemain)
    End If

szGetMuchText = szReturnText

End Function 'nGetMuchText

'****************************************************************************************************
' szGetIntlText --   Generate a specified amount of international text.
'
'   STATE:  Not dependent on Boomerang state
'
'   INPUTS:
'       nCharacters - The number of international characters to be generated from 1 to 65,500(?)
'       szReturnText - Variable the generated characters will be stored in
'
'   WRITTEN BY: ryanw
'
'****************************************************************************************************
Function szGetIntlText(nCharacters As Integer) As String

Dim a As String             ' repeated string
Dim a_len As Integer        ' length of "a" (short integer)
Dim GITLoop As Long         ' number of times to loop
Dim GITRemain As Long       ' messy remainder of division
Dim i As Long               ' counter

' Initialize variables
    szReturnText = ""
    a$ = ""

' Generate international string where chr() values are international characters
    For i = 128 To 168
        a$ = a$ + Chr$(i)
    Next
    For i = 173 To 175
        a$ = a$ + Chr$(i)
    Next

    ' Add to string the desired length of the string
    a$ = a$ + "-=<" + Str$(nCharacters) + " characters>=-"

    a_len = Len(a)

' Generate characters
    If a_len >= nCharacters Then
        szReturnText$ = Left$(a$, nCharacters)
    Else
        GITLoop = Int(nCharacters / a_len)
        GITRemain = nCharacters Mod a_len
        For i = 1 To GITLoop
            szReturnText$ = szReturnText$ + a$
        Next
        szReturnText$ = szReturnText + Left$(a$, GITRemain)
    End If

szGetIntlText = szReturnText

End Function 'nGetIntlText


DATETIME.BAS

Attribute VB_Name = "Module1"
Option Explicit

Function lConvertToJohnnyDate(dtDate As Date)

Dim lReturn As Long
lReturn = 0

lReturn = ((Year(dtDate)) * (2 ^ 9))
lReturn = lReturn Or ((Month(dtDate)) * (2 ^ 5))
lReturn = lReturn Or (Day(dtDate))

lConvertToJohnnyDate = lReturn

End Function
Function lConvertToJohnnyTime(dtTime As Date)

Dim lReturn As Long
lReturn = 0

lReturn = ((Hour(dtTime)) * (2 ^ 12))
lReturn = lReturn Or ((Minute(dtTime)) * (2 ^ 6))
lReturn = lReturn Or (Second(dtTime))

lConvertToJohnnyTime = lReturn

End Function


TASKS.BAS

Attribute VB_Name = "TaskObjectRoutines"
Option Explicit

Function bCreate8KRecurTask(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer
Dim dt As Date
Dim szText As String

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Get the 8k of text
szText = szGetMuchText(8192)

'Create some simple tasks

Set objTable = objGetTable(RECURRING_TASKS)

For i = 1 To nNumber
    dt = DateAdd("d", i, Now())
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreate8KRecurTask " & Str$(i) & " " & Now() & szText), Notes:="Notes", _
        Priority:=CLng((67 * 2 ^ 8) And (51)), _
        AccessActual:=saclOwner, _
        AlarmAmount:=CLng(10), AlarmTypeUnit:=CLng(0), BusyType:=CLng(1), BeforeEnd:=True, ring:=True, _
        AmountActualEffort:=CLng(10), TypeUnitActualEffort:=CLng(2), AmountEstimatedEffort:=CLng(5), TypeUnitEstimatedEffort:=CLng(2), _
        AmountStartWork:=CLng(2), TypeUnitStartWork:=CLng(2), _
        Billing:="Billing", Mileage:="Mileage", Role:="Role", _
        IsTaskAutoDone:=True, PercentCompleted:=CLng(55)
    objItem.SetProperties RecurringType:=trecurWeekly, WeekInterval:=CLng(1), DayOfWeekMask:=CLng(4), DayOfWeekStart:=CLng(0), _
        StartRecurringDate:=lConvertToJohnnyDate(dt), _
        StartRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 1, dt)), _
        EndRecurringDate:=lConvertToJohnnyDate(DateAdd("yyyy", 1, dt)), _
        EndRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 2, dt))

    Set objItem = Nothing
    DoEvents
Next i

Set objTable = Nothing

bCreate8KRecurTask = True

End Function

'****************************
'
' bCreateRecurTaskAllFields - Creates Recurring Task while setting all fields
'
'****************************
Function bCreateRecurTaskAllFields(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer
Dim dt As Date

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Create some simple tasks

Set objTable = objGetTable(RECURRING_TASKS)

For i = 1 To nNumber
    dt = DateAdd("d", i, Now())
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreateRecurTaskAllFields " & Str$(i) & " " & Now()), Notes:="Notes", _
        Priority:=CLng((67 * 2 ^ 8) And (51)), _
        AccessActual:=saclOwner, _
        AlarmAmount:=CLng(10), AlarmTypeUnit:=CLng(0), BusyType:=CLng(1), BeforeEnd:=True, ring:=True, _
        AmountActualEffort:=CLng(10), TypeUnitActualEffort:=CLng(2), AmountEstimatedEffort:=CLng(5), TypeUnitEstimatedEffort:=CLng(2), _
        AmountStartWork:=CLng(2), TypeUnitStartWork:=CLng(2), _
        Billing:="Billing", Mileage:="Mileage", Role:="Role", _
        IsTaskAutoDone:=True, PercentCompleted:=CLng(55)
    objItem.SetProperties RecurringType:=trecurWeekly, WeekInterval:=CLng(1), DayOfWeekMask:=CLng(4), DayOfWeekStart:=CLng(0), _
        StartRecurringDate:=lConvertToJohnnyDate(dt), _
        StartRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 1, dt)), _
        EndRecurringDate:=lConvertToJohnnyDate(DateAdd("yyyy", 1, dt)), _
        EndRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 2, dt))

    Set objItem = Nothing
    DoEvents
Next i

Set objTable = Nothing

bCreateRecurTaskAllFields = True

End Function

'*******************************************
'
'Creates a weekly recurring task with no end date
'
'*******************************************
Function bCreateSimpleRecurTask(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer
Dim dt As Date

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Create some simple tasks

Set objTable = objGetTable(RECURRING_TASKS)

For i = 1 To nNumber
    Set objItem = objTable.New
    dt = DateAdd("d", i, Now())
    objItem.SetProperties Text:=("OLE: bCreateSimpleRecurTask " & Str$(i) & " " & Now()), Notes:="Notes", _
        Start:=dt, End:=(DateAdd("h", 1, dt)), _
        RecurringType:=trecurWeekly, WeekInterval:=CLng(1), DayOfWeekMask:=CLng(4), DayOfWeekStart:=CLng(0), _
        StartRecurringDate:=lConvertToJohnnyDate(dt), _
        StartRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 1, dt)), _
        EndRecurringTime:=lConvertToJohnnyTime(DateAdd("h", 2, dt))

'        EndRecurringDate:=lConvertToJohnnyDate(DateAdd("yyyy", 1, dt)), _

    objItem.Flush
    Set objItem = Nothing
    DoEvents
Next i

bCreateSimpleRecurTask = True

End Function

'******************************
'
' Calls all scripts in this module
'
'******************************
Function CallAllTaskScripts()

Set gobjBase = objSetBaseObject

If gobjBase.LoggedOn = False Then
    gobjBase.Logon profileName:="ryanw24"
End If

Set gobjSched = gobjBase.ScheduleSelected
gobjSched.Activate

'Create a simple task (desc. and notes)
'    bCreateSimpleTask (1)

'Create a task with all fields filled (except contact and role)
'    bCreateTaskAllFields (1)

'Create an 8K Task
'    bCreate8KTask (1)

'Create simple recurring task
    bCreateSimpleRecurTask (1)

'Create recurring task with all fields filled
    bCreateRecurTaskAllFields (1)

'Create an 8K task
    bCreate8KRecurTask (1)

Set gobjSched = Nothing
Set gobjBase = Nothing

End Function

Function bCreateSimpleTask(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Create some simple tasks

Set objTable = objGetTable(SINGLE_TASKS)

For i = 1 To nNumber
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreateSimpleTask " & Str$(i) & " " & Now()), Notes:="Notes"
    Set objItem = Nothing
    DoEvents
Next i

bCreateSimpleTask = True

End Function

Function bCreate8KTask(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer
Dim szText As String

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Get the 8k of text
szText = szGetMuchText(8192)

'Create some simple tasks

Set objTable = objGetTable(SINGLE_TASKS)

For i = 1 To nNumber
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreate8KTask " & Str$(i) & " " & Now() & szText), Notes:="Notes", _
        Priority:=CLng((67 * 2 ^ 8) And (51)), _
        AlarmAmount:=CLng(10), AlarmTypeUnit:=CLng(0), BusyType:=CLng(1), BeforeEnd:=True, ring:=True, _
        Start:=(DateAdd("d", i, Now())), End:=(DateAdd("d", i + 1, Now())), _
        AmountActualEffort:=CLng(10), TypeUnitActualEffort:=CLng(2), _
        AmountEstimatedEffort:=CLng(5), TypeUnitEstimatedEffort:=CLng(2), _
        AmountStartWork:=CLng(2), TypeUnitStartWork:=CLng(2), _
        Billing:="Billing", Mileage:="Mileage", Role:="Role", _
        IsTaskAutoDone:=True, PercentCompleted:=CLng(55)

    Set objItem = Nothing
    DoEvents
Next i

Set objTable = Nothing

bCreate8KTask = True

End Function

Function bCreateTaskAllFields(nNumber As Integer) As Boolean
Dim objTable As Object
Dim objItem As Object
Dim i As Integer

'Check to make sure nNumber was set
nNumber = IIf(nNumber = 0, 1, nNumber)

'Create some simple tasks

Set objTable = objGetTable(SINGLE_TASKS)

For i = 1 To nNumber
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreateTaskAllFields " & Now()), Notes:="Notes", _
        Priority:=CLng((67 * 2 ^ 8) And (51)), _
        AccessActual:=saclOwner, _
        AlarmAmount:=CLng(10), AlarmTypeUnit:=CLng(0), BusyType:=CLng(1), BeforeEnd:=True, ring:=True, _
        Start:=DateAdd("d", i, Now()), End:=DateAdd("d", i + 1, Now()), _
        AmountActualEffort:=CLng(10), TypeUnitActualEffort:=CLng(2), _
        AmountEstimatedEffort:=CLng(5), TypeUnitEstimatedEffort:=CLng(2), _
        AmountStartWork:=CLng(2), TypeUnitStartWork:=CLng(2), _
        Billing:="Billing", Mileage:="Mileage", Role:="Role", _
        IsTaskAutoDone:=True, PercentCompleted:=CLng(55)

    Set objItem = Nothing
    DoEvents
Next i

Set objTable = Nothing

bCreateTaskAllFields = True

End Function

Function UETest() As Boolean
Dim objTable As Object
Dim objItem As Object

'Create a Sched+ object
    Set gobjBase = CreateObject("Schedule+.Application")

'Find out if we're logged on, if not use a profile
    If gobjBase.LoggedOn = False Then
        gobjBase.Logon profileName:="ryanw24"
    End If

'Get the handle for the selected schedule and activate it's window
    Set gobjSched = gobjBase.ScheduleSelected
    gobjSched.Activate

'Create a simple task

    'Get the tasks table
    Set objTable = gobjSched.SingleTasks

    'Create a new item
    Set objItem = objTable.New
    objItem.SetProperties Text:=("OLE: bCreateSimpleTask " & Now()), Notes:="Notes"
    Set objItem = Nothing

'Release Objects and log off
    Set objTable = Nothing
    Set gobjSched = Nothing
    gobjBase.Logoff
    Set gobjBase = Nothing
End Function


EDITEXT.CPP

// -----------------------------------------------------------------------------
// EditExt.cpp : Implements a data reading and conversion extension to the CEdit
//               class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "EditExt.h"

//$--CEditExt::GetString()------------------------------------------------------
// Returns the data from an edit control as a CString.
// -----------------------------------------------------------------------------

CString CEditExt::GetString( int nIndex)
{
    int Len = LineLength( nIndex) + sizeof(TCHAR);
    CString sBuf;
    GetLine( nIndex, sBuf.GetBuffer( Len), Len);
    sBuf.ReleaseBuffer();
    return( sBuf);
}

//$--CEditExt::GetLong()--------------------------------------------------------
// Returns the data from an edit control as a long.
// -----------------------------------------------------------------------------

LONG CEditExt::GetLong( int nIndex)
{
    return( _tcstol( GetString( nIndex), NULL, 0));
}

//$--CEditExt::GetULong()-------------------------------------------------------
// Returns the data from an edit control as a unsigned long.
// -----------------------------------------------------------------------------

ULONG CEditExt::GetULong( int nIndex)
{
    return( _tcstoul( GetString( nIndex), NULL, 0));
}

// -----------------------------------------------------------------------------


EDITEXT.H

// -----------------------------------------------------------------------------
// EditExt.h : Defines a data reading and conversion extension to the CEdit
//             class.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _EDITEXT_H_
#define _EDITEXT_H_

// -----------------------------------------------------------------------------

class CEditExt : public CEdit
{
public:
    CString GetString( int nIndex = 0);
    LONG GetLong( int nIndex = 0);
    ULONG GetULong( int nIndex = 0);
};

// -----------------------------------------------------------------------------

#endif // _EDITEXT_H_


FORMAT.CPP

// -----------------------------------------------------------------------------
// Format.cpp: Implements functions to format and return CStrings.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include "edkafx.h"
#include "exadmin.h"
#include "format.h"

const int MAX_RESOURCE_MSG = 500;

//----------------------------------------------------------------------------
// These integers control the size of the buffer we allocate for the
// _vsnprintf() function call.  It grows when _vsnprintf() returns an
// error.  The effect is that we remember the largest buffer needed for
// string formating and always start off by allocating that size of buffer.
// This provides a safe string formating method.
//----------------------------------------------------------------------------

static int nLargestBuf = 256;
static int nGrowSize = 128;

//----------------------------------------------------------------------------
// Takes arguments just like vprintf(), but returns the result in a CString.
// This performs a safe format with no overwrites.
//----------------------------------------------------------------------------

CString _Format( TCHAR *pFmt, va_list pVArg)
{
    CString sFmtBuf;

    for( ;;)
    {   // Take a shot at formating the string.
        TCHAR* pFmtBuf = sFmtBuf.GetBuffer( nLargestBuf);
        if( _vsntprintf( pFmtBuf, nLargestBuf, pFmt, pVArg) >= 0)
           break;   // Format was successful.

        // Buffer was not big enough so enlarge it.
        nLargestBuf += nGrowSize;
        nGrowSize = (int) (nGrowSize * 3 / 2);
    }
    sFmtBuf.ReleaseBuffer();
    return( sFmtBuf);
}

//----------------------------------------------------------------------------
// Takes arguments just like printf(), but returns the result in a CString.
//----------------------------------------------------------------------------

CString Format( TCHAR *pMsgFmt ...)
{
    va_list pVArg;
    va_start( pVArg, pMsgFmt);
    return( _Format( pMsgFmt, pVArg));
}

//----------------------------------------------------------------------------
// Find a format string in the resource and use it to format the CString.
//----------------------------------------------------------------------------

CString _FormatResource( int hResStr, va_list pVArg)
{
    TCHAR sMsgFmt[ MAX_RESOURCE_MSG];    // Arbitrary size that should be big enough.

    if( CAdminDialog::LoadStringA( hResStr, sMsgFmt, sizeof( sMsgFmt)) == 0)
        return( Format( "Unknown error %d m_bOccurred.", hResStr));
    else
        return( _Format( sMsgFmt, pVArg));
}

//----------------------------------------------------------------------------
// Find a format string in the resource and use it to format the CString.
//----------------------------------------------------------------------------

CString FormatResource( int hResStr ...)
{
    va_list pVArg;
    va_start( pVArg, hResStr);
    return( _FormatResource( hResStr, pVArg));
}

//----------------------------------------------------------------------------


FORMAT.H

// -----------------------------------------------------------------------------
// Format.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//----------------------------------------------------------------------------

#include <stdarg.h>

//----------------------------------------------------------------------------

// Pass a printf() style format string and variable argument list
// and we will return a CString with the results.
CString _Format( TCHAR *pFmt, va_list pVArg);

// Pass a printf() style format string and variable arguments
// and we will return a CString with the results.
CString Format( TCHAR *pFmt ...);

// Pass a string resource handle as the format string and list of
// variable arguments and we will return a CString with the results.
CString _FormatResource( int hResStr, va_list pVArg);

// Pass a string resource handle as the format string and variable
// arguments and we will return a CString with the results.
CString FormatResource( int hResStr ...);

//----------------------------------------------------------------------------


GLOBAL.CPP

// -----------------------------------------------------------------------------
// Global.CPP -- Contains global object declarations.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#define _GLOBAL_OBJECTS_

// -----------------------------------------------------------------------------
// The purpose of this C++ file is to instantiate global objects in an order that
// can be depended upon.
//
// In C++, if you have a global object whose constructor depends upon another
// object having been constructed, you can run into problems with the randomness
// which the many object modules get linked.
//
// By instantiating all dependant objects in a single source module, construction
// will occur in the order defined.
//
// One way to do this is to instantiate the objects in the header file which they
// are declared in.  Surround the declarations by #ifdef _GLOBAL_OBJECTS_
// and #endif //_GLOBAL_OBJECTS_.  This will cause them to be defined only once
// in this module (Global.CPP).  Then include those header files below.
//
// Another way would be to include the header file which declare the objects
// needed and to then place the definition directly below the header file.  This
// is not as clean and is not recommended.
// -----------------------------------------------------------------------------

#include "ExAdmin.h"    // This header file must be listed here.
#include "SGWAdmin.h"
#include "ErrCpp.h"     // This MUST be included so that CHRESULT works.


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by sgwadmin.rc
//
#define IDS_SGW                         1
#define IDS_INVALID_STRING              2
#define IDS_INVALID_MSGS_PER_THREAD     3
#define IDS_INVALID_SLEEP_BETWEEN_MSGS  4
#define IDS_INVALID_THREAD_CNT          5
#define IDS_RESTARTGATEWAY              6
#define IDD_SGW                         129
#define IDC_ADMINICON                   1000
#define IDC_TITLE                       1001
#define IDC_EDIT_DISP_NAME              1002
#define IDC_EDIT_EMAIL_ADDR             1003
#define IDC_EDIT_DIR_IN                 1004
#define IDC_EDIT_POLLING_IN             1005
#define IDC_EDIT_SLEEP_IN               1006
#define IDC_EDIT_MSGS_IN                1007
#define IDC_EDIT_THREADS_IN             1008
#define IDC_EDIT_THREADS_OUT            1009
#define IDC_EDIT_MSGS_OUT               1010
#define IDC_EDIT_SLEEP_OUT              1011
#define IDC_EDIT_POLLING_OUT            1012
#define IDC_EDIT_DIR_OUT                1013
#define IDC_RADIO_BOTH                  1014
#define IDC_EDIT_CONVERTER_IN           1014
#define IDC_RADIO_IN_BOUND              1015
#define IDC_EDIT_CONVERTER_OUT          1015
#define IDC_RADIO_OUT_BOUND             1016
#define IDC_CHECK_IN_BOUND              1017
#define IDC_CHECK_OUT_BOUND             1018
#define IDC_EDIT_ADDR_TYPE              1019

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        130
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1020
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SGWADMIN.CPP

// -----------------------------------------------------------------------------
// SGWAdmin.cpp : Implements an Exchange Administration property sheet dialog.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "SGWAdmin.h"
#include "errcpp.h"
#include "Format.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

// -----------------------------------------------------------------------------
// Declaration of static data used to initialize and hold cfg extension data.
// -----------------------------------------------------------------------------

static const CHAR szNOTIFICATION[]      = TEXT("NOTIFICATION");

// The following text string pointers are cast as LONGLONG here because
// a bug in the MIPS C++ compiler causes problems if we try to do the cast
// inside the ExtensionProps structure.

static const LONGLONG llszDfltAddrType  = (LONGLONG) TEXT("addrtype");
static const LONGLONG llszDfltDispName  = (LONGLONG) TEXT("System Administrator");
static const LONGLONG llszDfltEmailAddr = (LONGLONG) TEXT("sysadmin");
static const LONGLONG llszDfltDirIn     = (LONGLONG) TEXT("\\in");
static const LONGLONG llszDfltDirOut    = (LONGLONG) TEXT("\\out");
static const LONGLONG llszDfltCnvrtrIn  = (LONGLONG) TEXT("inbound");
static const LONGLONG llszDfltCnvrtrOut = (LONGLONG) TEXT("outbound");

// Setup default structure.
static SInitPropValue ExtensionProps[] =
{
    { PT_BOOLEAN, 0, TRUE},                         // IDX_CHECK_IN_BOUND
    { PT_BOOLEAN, 0, TRUE},                         // IDX_CHECK_OUT_BOUND
    { PT_LONG,    0, 4},                            // IDX_EDIT_THREADS_IN
    { PT_LONG,    0, 4},                            // IDX_EDIT_THREADS_OUT
    { PT_LONG,    0, 1},                            // IDX_EDIT_MSGS_IN
    { PT_LONG,    0, 1},                            // IDX_EDIT_MSGS_OUT
    { PT_LONG,    0, 0},                            // IDX_EDIT_SLEEP_IN
    { PT_LONG,    0, 0},                            // IDX_EDIT_SLEEP_OUT
    { PT_LONG,    0, INFINITE},                     // IDX_EDIT_POLLING_IN
    { PT_LONG,    0, INFINITE},                     // IDX_EDIT_POLLING_OUT
    { PT_STRING8, 0, llszDfltAddrType},             // IDX_ADDR_TYPE
    { PT_STRING8, 0, llszDfltDispName},             // IDX_DISP_NAME
    { PT_STRING8, 0, llszDfltEmailAddr},            // IDX_EMAIL_ADDR
    { PT_STRING8, 0, llszDfltDirIn},                // IDX_DIR_IN
    { PT_STRING8, 0, llszDfltDirOut},               // IDX_DIR_OUT
    { PT_STRING8, 0, llszDfltCnvrtrIn},             // IDX_CONVERTER_IN
    { PT_STRING8, 0, llszDfltCnvrtrOut},            // IDX_CONVERTER_OUT
};

// -----------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CSGWDlg, CAdminDialog)
//{{AFX_MSG_MAP(CSGWDlg)
ON_EN_CHANGE(IDC_EDIT_DIR_IN, OnEnChange)
ON_BN_CLICKED(IDC_CHECK_IN_BOUND, OnBnClicked)
ON_EN_CHANGE(IDC_EDIT_DIR_OUT, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_DISP_NAME, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_EMAIL_ADDR, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_MSGS_IN, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_MSGS_OUT, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_POLLING_IN, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_POLLING_OUT, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_SLEEP_IN, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_SLEEP_OUT, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_THREADS_IN, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_THREADS_OUT, OnEnChange)
ON_BN_CLICKED(IDC_CHECK_OUT_BOUND, OnBnClicked)
ON_EN_CHANGE(IDC_EDIT_ADDR_TYPE, OnEnChange)
    ON_EN_CHANGE(IDC_EDIT_CONVERTER_IN, OnEnChange)
ON_EN_CHANGE(IDC_EDIT_CONVERTER_OUT, OnEnChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//$--CSGWDlg::OnEnChange()------------------------------------------------------
// Called when an edit control is changed.
// -----------------------------------------------------------------------------

void CSGWDlg::OnEnChange()
{
    if( !m_bRefresh)
        DataHasChanged();
}

//$--CSGWDlg::OnBnClicked()-----------------------------------------------------
// Called when a check box is changed.
// -----------------------------------------------------------------------------

void CSGWDlg::OnBnClicked()
{
    if( !m_bRefresh)
        DataHasChanged();
}

//$--CSGWDlg::bHasHelp()--------------------------------------------------------
// Called to determine if you supply help.  Return TRUE if you do.
// -----------------------------------------------------------------------------

BOOL CSGWDlg::bHasHelp()
{
    return( TRUE);
}

//$--CSGWDlg::DoHelp()----------------------------------------------------------
// Called to start help.
// -----------------------------------------------------------------------------

VOID CSGWDlg::DoHelp()
{
    CHAR szDllPath[MAX_PATH+1] = {0};
    DWORD cchDllPath = 0;

    // Get the path and file name of this DLL.

    cchDllPath = GetModuleFileName(
        (HMODULE)(AfxGetInstanceHandle()),
        szDllPath,
        sizeof(szDllPath) - 1);

    // If we got back something that ends in ".DLL", then change the
    // extension to ".HLP" and call it as the help file.

    if (cchDllPath >=4 && !stricmp(&szDllPath[cchDllPath-4],".DLL"))
    {
        strcpy(&szDllPath[cchDllPath-4], ".HLP");
        ::WinHelp( GetSafeHwnd(), szDllPath, HELP_CONTENTS, 0);
    }

    // Otherwise it's an error.

    else
    {
        MODULE_WARNING(
            "GWPERF_DLL_ENTRY_MODULE_NAME: GetModuleFileName() failed.");
    }
}

//$--CSGWDlg::Refresh()---------------------------------------------------------
// Called so that property sheet can refresh with data that may have been
// changed by another property sheet.  Even though this DLL does not have more
// than one property sheet that modifies the same data fields the Exchange SDK
// provides a sample that lets an Administrator view and modify any Exchange
// SDK packed extension data array.
// -----------------------------------------------------------------------------

void CSGWDlg::Refresh()
{
    // While in refresh mode we do not want to the enable the APPLY
    // button if we are just repainting the screen.
    m_bRefresh = TRUE;

    // Set the check boxes in the dialog.
    m_CheckInBound.SetCheck(  GetExtBool( IDX_CHECK_IN_BOUND));
    m_CheckOutBound.SetCheck( GetExtBool( IDX_CHECK_OUT_BOUND));

    // Set the numerical values in the dialog.
    static char szFmt[] = "%lu";
    SetDlgItemText( IDC_EDIT_THREADS_IN,  Format( szFmt, GetExtDWord(  IDX_EDIT_THREADS_IN)));
    SetDlgItemText( IDC_EDIT_THREADS_OUT, Format( szFmt, GetExtDWord(  IDX_EDIT_THREADS_OUT)));
    SetDlgItemText( IDC_EDIT_MSGS_IN,     Format( szFmt, GetExtDWord(  IDX_EDIT_MSGS_IN)));
    SetDlgItemText( IDC_EDIT_MSGS_OUT,    Format( szFmt, GetExtDWord(  IDX_EDIT_MSGS_OUT)));
    SetDlgItemText( IDC_EDIT_SLEEP_IN,    Format( szFmt, GetExtDWord(  IDX_EDIT_SLEEP_IN)));
    SetDlgItemText( IDC_EDIT_SLEEP_OUT,   Format( szFmt, GetExtDWord(  IDX_EDIT_SLEEP_OUT)));

    if( GetExtDWord( IDX_EDIT_POLLING_IN) == INFINITE)
        SetDlgItemText( IDC_EDIT_POLLING_IN,  szNOTIFICATION);
    else
        SetDlgItemText( IDC_EDIT_POLLING_IN,  Format( szFmt, GetExtDWord(  IDX_EDIT_POLLING_IN )));

    if( GetExtDWord( IDX_EDIT_POLLING_OUT) == INFINITE)
        SetDlgItemText( IDC_EDIT_POLLING_OUT, szNOTIFICATION);
    else
        SetDlgItemText( IDC_EDIT_POLLING_OUT, Format( szFmt, GetExtDWord(  IDX_EDIT_POLLING_OUT)));

    // Place strings in the dialog.
    SetDlgItemText( IDC_EDIT_ADDR_TYPE,     GetExtString( IDX_ADDR_TYPE));
    SetDlgItemText( IDC_EDIT_DISP_NAME,     GetExtString( IDX_DISP_NAME));
    SetDlgItemText( IDC_EDIT_EMAIL_ADDR,    GetExtString( IDX_EMAIL_ADDR));
    SetDlgItemText( IDC_EDIT_DIR_IN,        GetExtString( IDX_DIR_IN));
    SetDlgItemText( IDC_EDIT_DIR_OUT,       GetExtString( IDX_DIR_OUT));
    SetDlgItemText( IDC_EDIT_CONVERTER_IN,  GetExtString( IDX_CONVERTER_IN));
    SetDlgItemText( IDC_EDIT_CONVERTER_OUT, GetExtString( IDX_CONVERTER_OUT));

    // Ending refresh mode.
    m_bRefresh = FALSE;
}

// -----------------------------------------------------------------------------

BOOL CSGWDlg::OnInitDialog()
{
CAdminDialog::OnInitDialog();

    ASSERTERROR( SGWDATA_PROP_COUNT == ARRAY_CNT( ExtensionProps), "Property count and default array inconsistent.");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Subclass all controls.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( FAILED( HrSubclassWindow( IDC_CHECK_IN_BOUND,     m_CheckInBound)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_OUT_BOUND,    m_CheckOutBound)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_ADDR_TYPE,     m_EditAddrType)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_DISP_NAME,     m_EditDispName)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_EMAIL_ADDR,    m_EditEmailAddr)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_THREADS_IN,    m_EditThreadsIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_THREADS_OUT,   m_EditThreadsOut)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_MSGS_IN,       m_EditMsgsIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_MSGS_OUT,      m_EditMsgsOut)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_SLEEP_IN,      m_EditSleepIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_SLEEP_OUT,     m_EditSleepOut)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_POLLING_IN,    m_EditPollingIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_POLLING_OUT,   m_EditPollingOut)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_DIR_IN,        m_EditDirIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_DIR_OUT,       m_EditDirOut)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_CONVERTER_IN,  m_EditConverterIn)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_CONVERTER_OUT, m_EditConverterOut)))
        return( TRUE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the extension data to defaults if it does not exist.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Is extension data available?
    if( GetExtCount() == 0)
    {   // NO, extension data is not available so set the
        // memory copy of this to the defaults.
        if( FAILED( HrSetExtProps( SGWDATA_PROP_COUNT, ExtensionProps)))
            return( TRUE);

        // Save it in Admin's memory buffer as well.
        CAdminDialog::bSaveData();
    }

return( TRUE);  // Return TRUE unless you set the focus to a control.
}

//$--CSGWDlg::bIsValidString()--------------------------------------------------
// Validate that a string is not empty and modify the extension data property
// in memory.
// -----------------------------------------------------------------------------

BOOL CSGWDlg::bIsValidString(
    IN ULONG iProp,         // Index of property
    IN CEditExt& editCtrl)  // Edit control containing data to validate.
{
    CString str = editCtrl.GetString();
    if( str.GetLength() == 0)
    {
        InvalidEntry( IDS_INVALID_STRING, editCtrl);
        return( FALSE);
    }
    CHRESULT hr = HrModExtString( iProp, str);
    if( FAILED( hr))
        return( FALSE);

    return( TRUE);
}

// -----------------------------------------------------------------------------
// Validate that a ulong is within a specified range and modify the extension
// data property in memory.
// -----------------------------------------------------------------------------

BOOL CSGWDlg::bIsValidULong(
    IN ULONG     iProp,         // Index of property
    IN CEditExt& editCtrl,  // Edit control containing data to validate.
    IN ULONG     ulLow,
    IN ULONG     ulHigh,
    IN int       idErrMsg)
{
    ULONG ul = editCtrl.GetULong();
    if( ul < ulLow || ul > ulHigh)
    {
        InvalidEntry( idErrMsg, editCtrl);
        return( FALSE);
    }
    CHRESULT hr = HrModExtDWord( iProp, ul);
    if( FAILED( hr))
        return( FALSE);

    return( TRUE);
}

//$--CSGWDlg::bSaveData()-------------------------------------------------------
// Called when a different property sheet has been selected or when either the
// OK or APPLY NOW button is pressed.  Returns TRUE if data has been validated
// and saved.
//
// When we call the CAdminDialog::bSaveData() function it gets saved in an
// admin memory space.  This makes it available to other property sheets that
// might be viewing the same data.
// -----------------------------------------------------------------------------

BOOL CSGWDlg::bSaveData()
{
    CHRESULT hr;
    ULONG    ul = 0;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Check boxes need no validation so just store them in the property value array.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrModExtBool(  IDX_CHECK_IN_BOUND,   m_CheckInBound.GetCheck());
    if( FAILED( hr))
        return( FALSE);

    hr = HrModExtBool(  IDX_CHECK_OUT_BOUND,  m_CheckOutBound.GetCheck());
    if( FAILED( hr))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// The thread count must be between 1 and 20.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( !bIsValidULong( IDX_EDIT_THREADS_IN, m_EditThreadsIn, 1, 20, IDS_INVALID_THREAD_CNT))
        return( FALSE);

    if( !bIsValidULong( IDX_EDIT_THREADS_OUT, m_EditThreadsOut, 1, 20, IDS_INVALID_THREAD_CNT))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Messages per thread must be between one and MAX_ULONG.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( !bIsValidULong( IDX_EDIT_MSGS_IN, m_EditMsgsIn, 1, MAX_ULONG, IDS_INVALID_MSGS_PER_THREAD))
        return( FALSE);

    if( !bIsValidULong( IDX_EDIT_MSGS_OUT, m_EditMsgsOut, 1, MAX_ULONG, IDS_INVALID_MSGS_PER_THREAD))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// The sleep between messages must be between zero and five minutes.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( !bIsValidULong( IDX_EDIT_SLEEP_IN, m_EditSleepIn, 0, 60000 * 5, IDS_INVALID_SLEEP_BETWEEN_MSGS))
        return( FALSE);

    if( !bIsValidULong( IDX_EDIT_SLEEP_OUT, m_EditSleepOut, 0, 60000 * 5, IDS_INVALID_SLEEP_BETWEEN_MSGS))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Polling interval needs no validation so just store them in the property value
// array.  Entering anything but a number causes us to set to notification.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( (ul = m_EditPollingIn.GetULong()) == 0)
        hr = HrModExtDWord( IDX_EDIT_POLLING_IN, INFINITE);   // Notification
    else
        hr = HrModExtDWord( IDX_EDIT_POLLING_IN, ul);
    if( FAILED( hr))
        return( FALSE);

    if( (ul = m_EditPollingOut.GetULong()) == 0)
        hr = HrModExtDWord( IDX_EDIT_POLLING_OUT, INFINITE);   // Notification
    else
        hr = HrModExtDWord( IDX_EDIT_POLLING_OUT, ul);
    if( FAILED( hr))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Validate that the string values are not empty.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( !bIsValidString( IDX_ADDR_TYPE, m_EditAddrType))
        return( FALSE);

    if( !bIsValidString( IDX_DISP_NAME, m_EditDispName))
        return( FALSE);

    if( !bIsValidString( IDX_EMAIL_ADDR, m_EditEmailAddr))
        return( FALSE);

    if( !bIsValidString( IDX_DIR_IN, m_EditDirIn))
        return( FALSE);

    if( !bIsValidString( IDX_DIR_OUT, m_EditDirOut))
        return( FALSE);

    if( !bIsValidString( IDX_CONVERTER_IN, m_EditConverterIn))
        return( FALSE);

    if( !bIsValidString( IDX_CONVERTER_OUT, m_EditConverterOut))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are complete, the data is valid, so lets save it.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    return( CAdminDialog::bSaveData());
}

// -----------------------------------------------------------------------------
// Called when either the OK or APPLY NOW button is pressed and after bSaveData().
// Return TRUE if it is OK to committed the saved data.
// -----------------------------------------------------------------------------

BOOL CSGWDlg::bCommitData()
{
    MessageBox2( IDS_RESTARTGATEWAY);
    return( TRUE);
}

// -----------------------------------------------------------------------------
// Admin will call this function directly (it is not a member function).  It is
// called once for each standard property sheet.  We have the option to keep one
// or more of the standard property sheets from showing.
// -----------------------------------------------------------------------------

BOOL PASCAL bShowPage( UINT iddAdminPage)
{
    BOOL bRet = TRUE;   // Show the standard property sheet.
    switch( iddAdminPage)
    {   // Do NOT show the following sheets:
        case iddGWSchedule:
        case iddGWDelivery_Restrictions:
        case iddGWAdvanced:
        case iddGWImportContainer:
        case iddGWExportContainers:
            bRet = FALSE;
    }
    return( bRet);
}

// $--iStartPage()--------------------------------------------------------------
// This function is called once durring initialization.  Return -1 to cause the
// first standard property sheet to be displayed.  Or return the index of one of
// our property sheets to have it come up first.
// -----------------------------------------------------------------------------
/*
INT PASCAL iStartPage( void)
{
    int i = -1; // Default to the first standard property sheet.
    return( i);
}
*/
// -----------------------------------------------------------------------------


SGWADMIN.H

// -----------------------------------------------------------------------------
// SGWAdmin.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _SGWADMIN_H_
#define _SGWADMIN_H_

#pragma message("Including " __FILE__)

// -----------------------------------------------------------------------------

#include "ExAdmin.h"
#include "Resource.h"
#include "EditExt.h"
#include "SGWData.h"

// -----------------------------------------------------------------------------

class CSGWDlg : public CAdminDialog
{
public:
    // A default constructor is the only one we will need.
    CSGWDlg() : CAdminDialog( IDD_SGW, IDS_SGW, SGWBLOBNAME) {}

protected:
    // Validate that a string is not empty and modify the extension data property
    // in memory.
    BOOL bIsValidString(
        IN ULONG iProp,         // Index of property
        IN CEditExt& editCtrl); // Edit control containing data to validate.

    // Validate that a ulong is within a specified range and modify the extension
    // data property in memory.
    BOOL bIsValidULong(
        IN ULONG     iProp,     // Index of property
        IN CEditExt& editCtrl,  // Edit control containing data to validate.
        IN ULONG     ulLow,     // Lowest valid value.
        IN ULONG     ulHigh,    // Highest valid value.
        IN int       idErrMsg); // Index of error message resource string.

    // Called when a different property sheet has been selected or when either the
    // OK or APPLY NOW button is pressed.  Returns TRUE if data has been validated
    // and saved.
    //
    // When we call the CAdminDialog::bSaveData() function it gets saved in an
    // admin memory space.  This makes it available to other property sheets that
    // might be viewing the same data.
    virtual BOOL bSaveData();

    // Called when either the OK or APPLY NOW button is pressed and after bSaveData().
    // Return TRUE if data has been committed.
    virtual BOOL bCommitData();

    // Called to determine if you supply help.  Return TRUE if you do, defaults to FALSE.
    virtual BOOL bHasHelp();

    // Called to start help.  Default does nothing.
    virtual VOID DoHelp();

    // Called when our property sheet is brought into focus to refresh data that
    // may have been altered by other property sheets.
    virtual void Refresh();

    // This boolean is used to keep the apply button from being enabled when
    // we are just repainting the data on the form.
    BOOL m_bRefresh;

    // All of the controls of this dialog.
    CButton  m_CheckInBound;
    CButton  m_CheckOutBound;
    CEditExt m_EditAddrType;
    CEditExt m_EditDispName;
    CEditExt m_EditEmailAddr;
    CEditExt m_EditThreadsIn;
    CEditExt m_EditThreadsOut;
    CEditExt m_EditMsgsIn;
    CEditExt m_EditMsgsOut;
    CEditExt m_EditSleepIn;
    CEditExt m_EditSleepOut;
    CEditExt m_EditPollingIn;
    CEditExt m_EditPollingOut;
    CEditExt m_EditDirIn;
    CEditExt m_EditDirOut;
    CEditExt m_EditConverterIn;
    CEditExt m_EditConverterOut;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Class wizard stuff.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public:
//{{AFX_DATA(CSGWDlg)
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSGWDlg)
//}}AFX_VIRTUAL

// Generated message map functions
//{{AFX_MSG(CSGWDlg)
virtual BOOL OnInitDialog();
afx_msg void OnEnChange();
afx_msg void OnBnClicked();
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

// Declare an instance of a CAdminDialog derived class.
CSGWDlg SGWDlg;

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif // _SGWADMIN_H_


GLOBAL.CPP

// -----------------------------------------------------------------------------
// Global.CPP -- Contains global object declarations.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#define _GLOBAL_OBJECTS_

// -----------------------------------------------------------------------------
// The purpose of this C++ file is to instantiate global objects in an order that
// can be depended upon.
//
// In C++, if you have a global object whose constructor depends upon another
// object having been constructed, you can run into problems with the randomness
// which the many object modules get linked.
//
// By instantiating all dependant objects in a single source module, construction
// will occur in the order defined.
//
// One way to do this is to instantiate the objects in the header file which they
// are declared in.  Surround the declarations by #ifdef _GLOBAL_OBJECTS_
// and #endif //_GLOBAL_OBJECTS_.  This will cause them to be defined only once
// in this module (Global.CPP).  Then include those header files below.
//
// Another way would be to include the header file which declare the objects
// needed and to then place the definition directly below the header file.  This
// is not as clean and is not recommended.
// -----------------------------------------------------------------------------

#include "ExAdmin.h"    // This header file MUST be listed here.
#include "SMBAdmin.h"
#include "ErrCpp.h"    // This MUST be included so that CHRESULT works.


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by smbadmin.rc
//
#define IDC_ADMINICON                   1000
#define IDC_TITLE                       1001
#define IDC_EDIT1                       1002
#define IDC_CHECK_READ                  1025
#define IDC_CHECK_CREATE                1026
#define IDC_CHECK_WRITE                 1027
#define IDC_CHECK_DELETE                1028
#define IDC_CHECK_CONTACT               1029
#define IDC_CHECK_OWNER                 1030
#define IDC_CHECK_PUBLIC_TOPIC_FOLDER   1031
#define IDC_EDIT_POLL_INBOX_MSEC        1032
#define IDC_EDIT_TOPIC_ROOT_FOLDER_COMMENT 1033
#define IDC_EDIT_TOPIC_ROOT_FOLDER_NAME 1034
#define IDD_SMBADMIN                    2001
#define IDS_WARNING_NO_DATA_FOUND       2007
#define IDS_ERROR_OUTBOUND_QUEUE        2008
#define IDS_WARNING_ACL_CHANGE          2008
#define IDS_MESSAGE_BOX_TITLE           2009
#define IDS_MESSAGE_BOX_WARNING         2010
#define IDS_LS_DLG_NAME                 2011
#define IDS_ERROR_CONFIG_UNAVAILABLE    2012
#define IDS_ERROR_CONFIG_CORRUPT        2014
#define IDS_ERROR_INBOUND_QUEUE         2016
#define IDS_SMBADMIN                    2017
#define IDS_TEST                        2018
#define IDD_TEST                        2019
#define IDS_INVALID_STRING              2019
#define IDS_INVALID_POLLING_FREQUENCY   2020

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        2020
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1035
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SMBADMIN.CPP

// -----------------------------------------------------------------------------
// SMBADMIN.cpp : Implements an Exchange Administration property sheet dialog.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edkafx.h"
#include "SMBADMIN.h"
#include "errcpp.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define MAX_STR_BUF 200

// -----------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CSMBAdminDlg, CAdminDialog)
//{{AFX_MSG_MAP(CSMBAdminDlg)
ON_EN_CHANGE(IDC_EDIT_POLL_INBOX_MSEC, OnEnChange)
ON_BN_CLICKED(IDC_CHECK_CONTACT, OnBnClicked)
ON_EN_CHANGE(IDC_EDIT_TOPIC_ROOT_FOLDER_COMMENT, OnEnChange)
ON_BN_CLICKED(IDC_CHECK_CREATE, OnBnClicked)
ON_BN_CLICKED(IDC_CHECK_DELETE, OnBnClicked)
ON_BN_CLICKED(IDC_CHECK_OWNER, OnBnClicked)
ON_BN_CLICKED(IDC_CHECK_PUBLIC_TOPIC_FOLDER, OnFolderBnClicked)
ON_BN_CLICKED(IDC_CHECK_READ, OnBnClicked)
ON_BN_CLICKED(IDC_CHECK_WRITE, OnBnClicked)
ON_EN_CHANGE(IDC_EDIT_TOPIC_ROOT_FOLDER_NAME, OnEnChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//$--CSMBAdminDlg::OnEnChange()-------------------------------------------------
// Called when an edit control is changed.
// -----------------------------------------------------------------------------

void CSMBAdminDlg::OnEnChange()
{
    if( !m_bRefresh)
        DataHasChanged();
}

//$--CSMBAdminDlg::OnFolderBnClicked()------------------------------------------
// Called when public store check box is changed.
// -----------------------------------------------------------------------------

void CSMBAdminDlg::OnFolderBnClicked()
{
    BOOL bPublicTopic = m_chkboxPublicTopicFolder.GetCheck();

    m_chkboxRead.EnableWindow(    bPublicTopic);
    m_chkboxWrite.EnableWindow(   bPublicTopic);
    m_chkboxCreate.EnableWindow(  bPublicTopic);
    m_chkboxDelete.EnableWindow(  bPublicTopic);
    m_chkboxOwner.EnableWindow(   bPublicTopic);
    m_chkboxContact.EnableWindow( bPublicTopic);

    if( !m_bRefresh)
        DataHasChanged();
}

//$--CSMBAdminDlg::OnBnClicked()------------------------------------------------
// Called when a check box is changed.
// -----------------------------------------------------------------------------

void CSMBAdminDlg::OnBnClicked()
{
    static BOOL bWarnedUser = FALSE;

    if( !bWarnedUser)
    {
        MessageBox2(
            IDS_WARNING_ACL_CHANGE,
            IDS_MESSAGE_BOX_WARNING,
            MB_ICONEXCLAMATION | MB_OK);

        bWarnedUser = TRUE;
    }

    if( !m_bRefresh)
        DataHasChanged();
}

//$--CSMBAdminDlg::bHasHelp()---------------------------------------------------
// Called to determine if you supply help.  Return TRUE if you do.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::bHasHelp()
{
    return( TRUE);
}

//$--CSMBAdminDlg::DoHelp()-----------------------------------------------------
// Called to start help.
// -----------------------------------------------------------------------------

VOID CSMBAdminDlg::DoHelp()
{
    CHAR szDllPath[MAX_PATH+1] = {0};
    DWORD cchDllPath = 0;

    // Get the path and file name of this DLL.

    cchDllPath = GetModuleFileName(
        (HMODULE)(AfxGetInstanceHandle()),
        szDllPath,
        sizeof(szDllPath) - 1);

    // If we got back something that ends in ".DLL", then change the
    // extension to ".HLP" and call it as the help file.

    if (cchDllPath >=4 && !stricmp(&szDllPath[cchDllPath-4],".DLL"))
    {
        strcpy(&szDllPath[cchDllPath-4], ".HLP");
        ::WinHelp( GetSafeHwnd(), szDllPath, HELP_CONTENTS, 0);
    }

    // Otherwise it's an error.

    else
    {
        MODULE_WARNING(
            "GWPERF_DLL_ENTRY_MODULE_NAME: GetModuleFileName() failed.");
    }
}

//$--CSMBAdminDlg::Refresh()----------------------------------------------------
// Called so that property sheet can refresh with data that may have been
// changed by another property sheet.  Even though this DLL does not have more
// than one property sheet that modifies the same data fields the Exchange SDK
// provides a sample that lets an Administrator view and modify any Exchange
// SDK packed extension data array.
// -----------------------------------------------------------------------------

void CSMBAdminDlg::Refresh()
{
    BOOL bPublicTopic = FALSE;

    // While in refresh mode we do not want to the enable the APPLY
    // button if we are just repainting the screen.
    m_bRefresh = TRUE;

    // So initialize controls.
    SetDlgItemText( IDC_EDIT_TOPIC_ROOT_FOLDER_NAME,    GetExtString( IDX_TOPIC_ROOT_FOLDER_NAME));
    SetDlgItemText( IDC_EDIT_TOPIC_ROOT_FOLDER_COMMENT, GetExtString( IDX_TOPIC_ROOT_FOLDER_COMMENT));

    // Display the inbox polling frequency.
    char szBuf[ 20];
    DWORD dwPollFreq = GetExtDWord( IDX_POLL_INBOX_MSEC);
    if( dwPollFreq == INFINITE)
        SetDlgItemText( IDC_EDIT_POLL_INBOX_MSEC, "NONE");
    else
    {   // Display the non infinite polling frequency as a number.
        wsprintf( szBuf, "%lu", dwPollFreq);
        SetDlgItemText( IDC_EDIT_POLL_INBOX_MSEC, szBuf);
    }

    // Set check for public folders.
    bPublicTopic = GetExtBool( IDX_PUBLIC_TOPIC_FOLDER);

    m_chkboxPublicTopicFolder.SetCheck( bPublicTopic);

    // Set all of the ACL Rights check boxes.
    DWORD dwACLrights = GetExtDWord( IDX_ACL_RIGHTS);

    m_chkboxRead.SetCheck(    dwACLrights & frightsReadAny);
    m_chkboxWrite.SetCheck(   dwACLrights & frightsEditAny);
    m_chkboxCreate.SetCheck(  dwACLrights & frightsCreate);
    m_chkboxDelete.SetCheck(  dwACLrights & frightsDeleteAny);
    m_chkboxOwner.SetCheck(   dwACLrights & frightsOwner);
    m_chkboxContact.SetCheck( dwACLrights & frightsContact);

    m_chkboxRead.EnableWindow(    bPublicTopic);
    m_chkboxWrite.EnableWindow(   bPublicTopic);
    m_chkboxCreate.EnableWindow(  bPublicTopic);
    m_chkboxDelete.EnableWindow(  bPublicTopic);
    m_chkboxOwner.EnableWindow(   bPublicTopic);
    m_chkboxContact.EnableWindow( bPublicTopic);

    // Ending refresh mode.
    m_bRefresh = FALSE;
}

//$--CSMBAdminDlg::OnInitDialog()-----------------------------------------------
// Initialize the dialog with extension data.  Create extension data if necessary.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::OnInitDialog()
{
CAdminDialog::OnInitDialog();

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Subclass all controls.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( FAILED( HrSubclassWindow( IDC_EDIT_TOPIC_ROOT_FOLDER_NAME,    m_editTopicRootFolderName)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_TOPIC_ROOT_FOLDER_COMMENT, m_editTopicRootFolderComment)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_EDIT_POLL_INBOX_MSEC,           m_editPollInboxMSec)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_PUBLIC_TOPIC_FOLDER,      m_chkboxPublicTopicFolder)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_READ,    m_chkboxRead)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_WRITE,   m_chkboxWrite)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_CREATE,  m_chkboxCreate)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_DELETE,  m_chkboxDelete)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_OWNER,   m_chkboxOwner)))
        return( TRUE);

    if( FAILED( HrSubclassWindow( IDC_CHECK_CONTACT, m_chkboxContact)))
        return( TRUE);

    // Limit the amount of text a tester can enter into
    // the string controls to a reasonable limit.
    m_editTopicRootFolderName.LimitText( MAX_TOPIC_STRING);
    m_editTopicRootFolderComment.LimitText( MAX_TOPIC_STRING);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the extension data to defaults if it does not exist.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Is extension data available?
    if( GetExtCount() == 0)
    {   // NO, extension data is not available so set the
        // memory copy of this to the defaults.

        // The following text string pointers are cast as LONGLONG here because
        // a bug in the MIPS C++ compiler causes problems if we try to do the cast
        // inside the ExtensionProps structure.

        static const LONGLONG llszDfltFolderName    = (LONGLONG) TEXT("Topics");
        static const LONGLONG llszDfltFolderComment = (LONGLONG) TEXT("Root Topic Folder");

        static SInitPropValue ExtensionProps[] =
        {
            { PT_BOOLEAN, 0, TRUE},           // Public folder (TRUE or FALSE)
            { PT_LONG,    0, 60000},          // Polling frequency.
            { PT_LONG,    0, frightsReadAny}, // Public folder Access Control List settings.
            { PT_STRING8, 0, llszDfltFolderName},
            { PT_STRING8, 0, llszDfltFolderComment},
        };

        // Initialize the property value array used to create the extension data blob.
        if( FAILED( HrSetExtProps( ARRAY_CNT( ExtensionProps), ExtensionProps)))
            return( TRUE);

        // Save it in Admin's memory buffer as well.
        CAdminDialog::bSaveData();
    }

    return( TRUE);  // return TRUE unless you set the focus to a control
}

//$--CSMBAdminDlg::bIsValidString()---------------------------------------------
// Validate that a string is not empty and modify the extension data property
// in memory.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::bIsValidString(
    IN ULONG iProp,         // Index of property
    IN CEditExt& editCtrl)  // Edit control containing data to validate.
{
    CString str = editCtrl.GetString();
    if( str.GetLength() == 0)
    {
        InvalidEntry( IDS_INVALID_STRING, editCtrl);
        return( FALSE);
    }
    CHRESULT hr = HrModExtString( iProp, str);
    if( FAILED( hr))
        return( FALSE);

    return( TRUE);
}

//$--CSMBAdminDlg::bIsValidULong()----------------------------------------------
// Validate that a ulong is within a specified range and modify the extension
// data property in memory.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::bIsValidULong(
    IN ULONG     iProp,         // Index of property
    IN CEditExt& editCtrl,  // Edit control containing data to validate.
    IN ULONG     ulLow,
    IN ULONG     ulHigh,
    IN int       idErrMsg)
{
    ULONG ul = editCtrl.GetULong();
    if( ul < ulLow || ul > ulHigh)
    {
        InvalidEntry( idErrMsg, editCtrl);
        return( FALSE);
    }
    CHRESULT hr = HrModExtDWord( iProp, ul);
    if( FAILED( hr))
        return( FALSE);

    return( TRUE);
}

//$--CSMBAdminDlg::bSaveData()--------------------------------------------------
// Called when a different property sheet has been selected or when either the
// OK or APPLY NOW button is pressed.  Returns TRUE if data has been validated
// and saved.
//
// When we call the CAdminDialog::bSaveData() function it gets saved in an
// admin memory space.  This makes it available to other property sheets that
// might be viewing the same data.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::bSaveData()
{
    CHRESULT hr;
    TCHAR szBuf[ MAX_STR_BUF];

    // Validate and modify the topic root folder name in memory.
    if( !bIsValidString( IDX_TOPIC_ROOT_FOLDER_NAME, m_editTopicRootFolderName))
        return( FALSE);

    // Modify the topic root folder comment in memory.
    CString str = m_editTopicRootFolderComment.GetString();
    hr = HrModExtString( IDX_TOPIC_ROOT_FOLDER_COMMENT, str);
    if( FAILED( hr))
        return( FALSE);

    // Modify the polling frequency in memory.
    m_editPollInboxMSec.GetLine( 0, szBuf, sizeof( szBuf));
    if( _tcsicmp( szBuf, TEXT( "NONE")) == 0)
    {
        hr = HrModExtDWord( IDX_POLL_INBOX_MSEC, INFINITE);
        if( FAILED( hr))
            return( FALSE);
    }
    else
        if( !bIsValidULong( IDX_POLL_INBOX_MSEC, m_editPollInboxMSec, 60000, MAX_ULONG, IDS_INVALID_POLLING_FREQUENCY))
            return( FALSE);

    // Modify the public topic folder flag.
    hr = HrModExtBool( IDX_PUBLIC_TOPIC_FOLDER, m_chkboxPublicTopicFolder.GetCheck());
    if( FAILED( hr))
        return( FALSE);

    // Modify the ACL rights in memory.
    DWORD dwACLrights =
        (m_chkboxRead.GetCheck()    * frightsReadAny) |
        (m_chkboxWrite.GetCheck()   * frightsEditAny) |
        (m_chkboxCreate.GetCheck()  * frightsCreate) |
        (m_chkboxDelete.GetCheck()  * frightsDeleteAny) |
        (m_chkboxOwner.GetCheck()   * frightsOwner) |
        (m_chkboxContact.GetCheck() * frightsContact);
    hr = HrModExtDWord( IDX_ACL_RIGHTS, dwACLrights);
    if( FAILED( hr))
        return( FALSE);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are complete, the data is valid, so lets save it.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    return( CAdminDialog::bSaveData());
}

//$--CSMBAdminDlg::bCommitData()------------------------------------------------
// Called when either the OK or APPLY NOW button is pressed and after bSaveData().
// Return TRUE if it is OK to commit the saved data.
// -----------------------------------------------------------------------------

BOOL CSMBAdminDlg::bCommitData()
{
    return( TRUE);
}

// -----------------------------------------------------------------------------
// Admin will call this function directly (it is not a member function).  It is
// called once for each standard property sheet.  We have the option to keep one
// or more of the standard property sheets from showing.
// -----------------------------------------------------------------------------
/*
BOOL PASCAL bShowPage( UINT iddAdminPage)
{
    BOOL bRet = TRUE;   // Show the standard property sheet.
    return( bRet);
}

// $--iStartPage()--------------------------------------------------------------
// This function is called once durring initialization.  Return -1 to cause the
// first standard property sheet to be displayed.  Or return the index of one of
// our property sheets to have it come up first.
// -----------------------------------------------------------------------------

INT PASCAL iStartPage( void)
{
    int i = -1; // Default to the first standard property sheet.
    return( i);
}
*/
// -----------------------------------------------------------------------------


SMBADMIN.H

// -----------------------------------------------------------------------------
// SMBADMIN.h : header file
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _SMBADMIN_H_
#define _SMBADMIN_H_

#pragma message("Including " __FILE__)

// -----------------------------------------------------------------------------

#include "ExAdmin.h"
#include "Resource.h"
#include "EditExt.h"
#include "smbdata.h"

#define MAX_TOPIC_STRING                160

// -----------------------------------------------------------------------------

BOOL PASCAL bShowPage( UINT iddAdminPage);

// -----------------------------------------------------------------------------

class CSMBAdminDlg : public CAdminDialog
{
public:
    // A default constructor is the only one we will need.
    CSMBAdminDlg() : CAdminDialog( IDD_SMBADMIN, IDS_SMBADMIN, SMBBLOBNAME),
                     m_bRefresh( FALSE) {}

protected:
    // Validate that a string is not empty and modify the extension data property
    // in memory.
    BOOL bIsValidString(
        IN ULONG iProp,         // Index of property
        IN CEditExt& editCtrl); // Edit control containing data to validate.

    // Validate that a ulong is within a specified range and modify the extension
    // data property in memory.
    BOOL bIsValidULong(
        IN ULONG     iProp,         // Index of property
        IN CEditExt& editCtrl,  // Edit control containing data to validate.
        IN ULONG     ulLow,
        IN ULONG     ulHigh,
        IN int       idErrMsg);

    // Called when a different property sheet has been selected or when either the
    // OK or APPLY NOW button is pressed.  Returns TRUE if data has been validated
    // and saved.
    //
    // When we call the CAdminDialog::bSaveData() function it gets saved in an
    // admin memory space.  This makes it available to other property sheets that
    // might be viewing the same data.
    virtual BOOL bSaveData();

    // Called when either the OK or APPLY NOW button is pressed and after bSaveData().
    // Return TRUE if data has been committed.
    virtual BOOL bCommitData();

    // Called to determine if you supply help.  Return TRUE if you do, defaults to FALSE.
    virtual BOOL bHasHelp();

    // Called to start help.  Default does nothing.
    virtual VOID DoHelp();

    // Called when our property sheet is brought into focus to refresh data that
    // may have been altered by other property sheets.
    virtual void Refresh();

    // This boolean is used to keep the apply button from being enabled when
    // we are just repainting the data on the form.
    BOOL m_bRefresh;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // All of the controls in the dialog.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CEditExt    m_editTopicRootFolderName;
    CEditExt    m_editTopicRootFolderComment;
    CEditExt    m_editPollInboxMSec;
    CButton     m_chkboxPublicTopicFolder;
    CButton     m_chkboxRead;
    CButton     m_chkboxWrite;
    CButton     m_chkboxCreate;
    CButton     m_chkboxDelete;
    CButton     m_chkboxOwner;
    CButton     m_chkboxContact;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Class wizard stuff.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public:
//{{AFX_DATA(CSMBAdminDlg)
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA

protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSMBAdminDlg)
//}}AFX_VIRTUAL

// Generated message map functions
//{{AFX_MSG(CSMBAdminDlg)
afx_msg void OnEnChange();
afx_msg void OnBnClicked();
    afx_msg void OnFolderBnClicked();
virtual BOOL OnInitDialog();
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

// -----------------------------------------------------------------------------
#ifdef _GLOBAL_OBJECTS_
#pragma message("Declaration of global objects for: " __FILE__)

// Declare an instances of a CAdminDialog derived class.
CSMBAdminDlg SMBAdminDlg;

#endif //_GLOBAL_OBJECTS_
// -----------------------------------------------------------------------------

#endif // _SMBADMIN_H_


ACL.C

// --acl.c----------------------------------------------------------------------
// Code to manipulate the access control list for public folders.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include <edk.h>

// CbNewROWLIST should be defined in EMSMDB.H but it isn't

#define CbNewROWLIST(_centries) \
    (offsetof(ROWLIST,aEntries) + (_centries)*sizeof(ROWENTRY))

typedef struct _FOLDERACL
{
    LPEXCHANGEMODIFYTABLE   lpWriteTable;
    LPMAPITABLE             lpReadTable;
}   FOLDERACL, *LPFOLDERACL;

#define TEST_ROW_FLAGS(x)  \
    ((((x) == ROW_ADD) |   \
     ((x) == ROW_MODIFY) | \
     ((x) == ROW_REMOVE) | \
     ((x) == ROW_EMPTY)))

#include "acl.chk"

//$--HrOpenACLInterface------------------------------------------------------
//  Opens the access control list interface on a folder.
// -----------------------------------------------------------------------------
static HRESULT HrOpenACLInterface(          // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,           // pointer to folder
    OUT LPFOLDERACL *lppACL)            // pointer to access control list
{
    HRESULT     hr    = NOERROR;
    HRESULT     hrT   = NOERROR;
    SCODE       sc    = 0;
    LPFOLDERACL lpACL = NULL;

    DEBUGPUBLIC("HrOpenACLInterface()");

    hr = CHK_HrOpenACLInterface(
        lpFolder,
        lppACL);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize output parameter

    *lppACL = NULL;

    // Allocate an ACL

    sc = MAPIAllocateBuffer(sizeof(ACL), (LPVOID FAR *)&lpACL);

    if(FAILED(sc))
    {
        hr = HR_FAILED(E_OUTOFMEMORY);
        goto cleanup;
    }

    lpACL->lpWriteTable = NULL;
    lpACL->lpReadTable  = NULL;

    // Open the ACL table property on the folder

    hrT = MAPICALL(lpFolder)->OpenProperty( lpFolder,
        PR_ACL_TABLE,
        (LPGUID)&IID_IExchangeModifyTable,
        0,
        MAPI_DEFERRED_ERRORS,
        (LPUNKNOWN FAR *)&lpACL->lpWriteTable);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Open a table on the ACL table property

    hrT = MAPICALL( lpACL->lpWriteTable)->GetTable( lpACL->lpWriteTable,
        0,
        &(lpACL->lpReadTable));

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    *lppACL = lpACL;

cleanup:

    if(FAILED(hr) && (lpACL != NULL))
    {
        ULRELEASE(lpACL->lpReadTable);
        ULRELEASE(lpACL->lpWriteTable);

        MAPIFREEBUFFER(lpACL);
    }

    RETURN(hr);
}

//$--HrCloseACLInterface-----------------------------------------------------
//  Closes the access control list interface on a folder.
// -----------------------------------------------------------------------------
static HRESULT HrCloseACLInterface(         // RETURNS: return code
    IN OUT LPFOLDERACL * lppACL)        // pointer to access control list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrCloseACLInterface()");

    hr = CHK_HrCloseACLInterface(
        lppACL);

    if(FAILED(hr))
        RETURN(hr);

    if((*lppACL) != NULL)
    {
        ULRELEASE((*lppACL)->lpReadTable);
        ULRELEASE((*lppACL)->lpWriteTable);

        MAPIFREEBUFFER(*lppACL);
    }

    RETURN(hr);
}

//$--HrOpenAccessControlList-------------------------------------------------
//  Opens the access control list for lpFolder and returns the first
//  EDK_MAX_QUERY_ROWS rows.
// -----------------------------------------------------------------------------
static HRESULT HrOpenAccessControlList(     // RETURNS: return code
    IN LPMAPIFOLDER lpFolder,           // pointer to folder
    OUT LPFOLDERACL * lppACL,           // pointer to access control list
    OUT LPSRowSet FAR *lppRows)         // pointer to SRowSet
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    static const SizedSPropTagArray(4, rgPropTag) =
    { 4,
        {
            PR_MEMBER_ID,
            PR_MEMBER_NAME,
            PR_MEMBER_RIGHTS,
            PR_MEMBER_ENTRYID
        }
    };

    DEBUGPUBLIC("HrOpenAccessControlList()");

    hr = CHK_HrOpenAccessControlList(
        lpFolder,
        lppACL,
        lppRows);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize output parameters

    *lppACL = NULL;
    *lppRows = NULL;

    // Open the ACL

    hr = HrOpenACLInterface(lpFolder, lppACL);

    if (FAILED(hr))
    {
        goto cleanup;
    }

    // Set the columns to return

    hrT = MAPICALL( (*lppACL)->lpReadTable)->SetColumns( (*lppACL)->lpReadTable,
        (SPropTagArray *)&rgPropTag,
        0);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Seek to the beginning of the table of ACL records

    hrT = MAPICALL( (*lppACL)->lpReadTable)->SeekRow( (*lppACL)->lpReadTable,
        BOOKMARK_BEGINNING,
        0,
        NULL);

    if (FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

    // Get the first EDK_MAX_QUERY_ROWS rows

    hrT = MAPICALL( (*lppACL)->lpReadTable)->QueryRows( (*lppACL)->lpReadTable,
        (ULONG)EDK_MAX_QUERY_ROWS,
        (ULONG)0,
        lppRows);

    if( FAILED( hrT) || *lppRows == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    if( (*lppRows)->cRows == 0)
    {
        FREEPROWS(*lppRows);

        hr = HR_LOG( EDK_E_END_OF_FILE);
    }

cleanup:

    if(((*lppACL) != NULL) && FAILED(hr))
    {
        (void)HrCloseACLInterface(lppACL);
    }

    RETURN(hr);
}

//$--HrNextAccessControlList-------------------------------------------------
//  Returns the next EDK_MAX_QUERY_ROWS rows for this ACL
// -----------------------------------------------------------------------------
static HRESULT HrNextAccessControlList(     // RETURNS: return code
    IN LPFOLDERACL lpACL,               // pointer to access control list
    OUT LPSRowSet FAR * lppRows)        // pointer to SRowSet
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrNextAccessControlList()");

    hr = CHK_HrNextAccessControlList(
        lpACL,
        lppRows);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize output parameter

    *lppRows = NULL;

    // Get the next EDK_MAX_QUERY_ROWS rows

    hrT = MAPICALL( lpACL->lpReadTable)->QueryRows( lpACL->lpReadTable,
        (ULONG)EDK_MAX_QUERY_ROWS,
        (ULONG)0,
        lppRows);

    if( FAILED( hrT) || *lppRows == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    if( (*lppRows)->cRows == 0)
    {
        FREEPROWS(*lppRows);

        hr = HR_LOG( EDK_E_END_OF_FILE);
    }

cleanup:

    RETURN(hr);
}

//$--HrCloseAccessControlList------------------------------------------------
//  Closes the access control list.
// -----------------------------------------------------------------------------
static HRESULT HrCloseAccessControlList(    // RETURNS: return code
    IN OUT LPFOLDERACL * lppACL)        // pointer to access control list
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC("HrCloseAccessControlList()");

    hr = CHK_HrCloseAccessControlList(
        lppACL);

    if(FAILED(hr))
        RETURN(hr);

    (void)HrCloseACLInterface(lppACL);

    RETURN(hr);
}

//$--HrFillROWENTRY----------------------------------------------------------
//  Helper function used by HrCreateRowList to assign/allocate members
//  of a ROWENTRY.
//------------------------------------------------------------------------------
static HRESULT HrFillROWENTRY(// RETURNS: return code
    IN ULONG ulRowFlags,         // row flags (ROW_ADD|ROW_MODIFY|ROW_REMOVE)
    IN LARGE_INTEGER liMemberID, // member ID (for ROW_MODIFY|ROW_REMOVE)
    IN ULONG ulRights,           // member rights (for ROW_ADD|ROW_MODIFY)
    IN ULONG cbeid,              // count of bytes in member entry ID (for ROW_ADD)
    IN LPVOID lpeid,             // pointer to member entry ID (for ROW_ADD)
    OUT LPROWENTRY  lpRowEntry)  // pointer to row entry
{
    HRESULT      hr         = NOERROR;
    SCODE        sc         = 0;
    ULONG        cb         = 0;
    LPSPropValue lpSPropVal = NULL;
    LPVOID       lpeidCopy  = NULL;
    UINT         i          = 0;
    ULONG        cProps     = 0;

    DEBUGPRIVATE("HrFillROWENTRY()");

    hr = CHK_HrFillROWENTRY(
        ulRowFlags,
        liMemberID,
        ulRights,
        cbeid,
        lpeid,
        lpRowEntry);

    if(FAILED(hr))
        RETURN(hr);

    // Allocate Prop Value array

    if (ulRowFlags == ROW_ADD)
    {
        cProps = 2; // PR_MEMBER_ENTRYID and PR_MEMBER_RIGHTS
    }
    else if (ulRowFlags == ROW_MODIFY)
    {
        cProps = 2; // PR_MEMBER_ID and PR_MEMBER_RIGHTS
    }
    else
    {
        cProps = 1; // PR_MEMBER_ID;
    }

    cb = CbNewSPropValue(cProps);

    sc = MAPIAllocateBuffer(cb, (LPVOID *)&lpSPropVal);

    if(FAILED(sc))
    {
        hr = HR_FAILED(E_OUTOFMEMORY);
        goto cleanup;
    }

    ZeroMemory(lpSPropVal, cb);

    // Allocate/Copy Entry ID, link to prop val array allocation

    if (cbeid != 0)
    {
        sc = MAPIAllocateMore(cbeid, lpSPropVal, &lpeidCopy);

        if (sc != SUCCESS_SUCCESS)
        {
            hr = HR_FAILED(E_OUTOFMEMORY);
            goto cleanup;
        }

        CopyMemory(lpeidCopy, lpeid, cbeid);
    }

    // Set Row Entry values

    lpRowEntry->ulRowFlags = ulRowFlags;
    lpRowEntry->cValues = cProps;
    lpRowEntry->rgPropVals = lpSPropVal;

    // Set Prop Value array

    i = 0;

    // Set PR_MEMBER_ENTRYID property

    if (ulRowFlags == ROW_ADD)
    {
        lpSPropVal[i].ulPropTag = PR_MEMBER_ENTRYID;
        lpSPropVal[i].Value.bin.cb = cbeid;
        lpSPropVal[i].Value.bin.lpb = (LPBYTE)lpeidCopy;
        i++;
    }

    // Set MEMBER_ID property
    if (ulRowFlags != ROW_ADD)
    {
        lpSPropVal[i].ulPropTag = PR_MEMBER_ID;
        lpSPropVal[i].Value.li = liMemberID;
        i++;
    }

    // Set PR_MEMBER_RIGHTS property

    if ((ulRowFlags == ROW_ADD) || (ulRowFlags == ROW_MODIFY))
    {
        lpSPropVal[i].ulPropTag = PR_MEMBER_RIGHTS;
        lpSPropVal[i].Value.ul = ulRights;
        i++;
    }

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(lpSPropVal);

        lpRowEntry->ulRowFlags = 0;
        lpRowEntry->cValues = 0;
        lpRowEntry->rgPropVals = NULL;
    }

    RETURN(hr);
}

//$--FreeROWENTRY------------------------------------------------------------
//  Helper function used wth HrFillROWENTRY to free dynamically allocated
//  members of a ROWENTRY.
//------------------------------------------------------------------------------
static VOID FreeROWENTRY(            // RETURNS: nothing
    IN OUT LPROWENTRY  lpRowEntry)      // pointer to row entry
{
    DEBUGPRIVATE("FreeROWENTRY()");

    if (lpRowEntry)
    {
        if (lpRowEntry->rgPropVals)
        {
            // Free the property value array and property values

            MAPIFREEBUFFER(lpRowEntry->rgPropVals);
        }

        lpRowEntry->ulRowFlags = 0;
        lpRowEntry->cValues = 0;
    }
}

//$--HrCreateROWLIST---------------------------------------------------------
//  Create a single item ROWLIST for use with HrModifyAccessControlList().
// -----------------------------------------------------------------------------
static HRESULT HrCreateROWLIST(      // RETURNS: return code
    IN ULONG ulRowFlags,         // row flags (ROW_ADD|ROW_MODIFY|ROW_REMOVE)
    IN LARGE_INTEGER liMemberID, // member ID (for ROW_MODIFY|ROW_REMOVE)
    IN LPTSTR lpszDisplayName,   // pointer to member display name
    IN ULONG ulRights,           // member rights (for ROW_ADD|ROW_MODIFY)
    IN ULONG cbeid,              // count of bytes in member entry ID (for ROW_ADD)
    IN LPVOID lpeid,             // pointer to member entry ID (for ROW_ADD)
    OUT LPROWLIST *lppRowList)   // pointer to row list
{
    HRESULT hr = NOERROR;
    SCODE   sc = 0;
    ULONG   cb = 0;

    DEBUGPUBLIC("HrCreateROWLIST()");

    hr = CHK_HrCreateROWLIST(
        ulRowFlags,
        liMemberID,
        lpszDisplayName,
        ulRights,
        cbeid,
        lpeid,
        lppRowList);

    if(FAILED(hr))
        RETURN(hr);

    // Initialize output variable

    *lppRowList = NULL;

    // Allocate a new ROWLIST

    cb = CbNewROWLIST(1);

    sc = MAPIAllocateBuffer(cb, (LPVOID *)lppRowList);

    if(FAILED(sc))
    {
        hr = HR_FAILED(E_OUTOFMEMORY);
        goto cleanup;
    }

    // Set RowList values

    ZeroMemory((*lppRowList), cb);

    (*lppRowList)->cEntries = 1;

    hr = HrFillROWENTRY(
        ulRowFlags,
        liMemberID,
        ulRights,
        cbeid,
        lpeid,
        &((*lppRowList)->aEntries[0]));

    if(FAILED(hr))
    {
       goto cleanup;
    }

cleanup:

    if(FAILED(hr))
    {
        MAPIFREEBUFFER(*lppRowList);
    }

    RETURN(hr);
}

//$--HrFreeROWLIST-----------------------------------------------------------
//  Free a ROWLIST for use with HrModifyAccessControlList().
// -----------------------------------------------------------------------------
static HRESULT HrFreeROWLIST(               // RETURNS: return code
    IN OUT LPROWLIST *lppRowList)       // pointer to row list
{
    HRESULT hr = NOERROR;
    ULONG   i  = 0;

    DEBUGPUBLIC("HrFreeROWLIST()");

    hr = CHK_HrFreeROWLIST(
        lppRowList);

    if(FAILED(hr))
        RETURN(hr);

    for(i = 0; i < (*lppRowList)->cEntries; ++i)
    {
        FreeROWENTRY(&((*lppRowList)->aEntries[i]));
    }

    MAPIFREEBUFFER(*lppRowList);
}

//$--HrModifyAccessControlList-----------------------------------------------
//  Modify the access control list.
// -----------------------------------------------------------------------------
static HRESULT HrModifyAccessControlList(   // RETURNS: return code
    IN LPFOLDERACL lpACL,               // pointer to access control list
    IN LPROWLIST FAR lpRowList)         // pointer to row list
{
    HRESULT hr  = NOERROR;
    HRESULT hrT = NOERROR;

    DEBUGPUBLIC("HrModifyAccessControlList()");

    hr = CHK_HrModifyAccessControlList(
        lpACL,
        lpRowList);

    if(FAILED(hr))
        RETURN(hr);

    if((lpRowList == NULL) || (lpRowList->cEntries == 0))
    {
        goto cleanup;
    }

    hrT = MAPICALL( lpACL->lpWriteTable)->ModifyTable( lpACL->lpWriteTable,
        0,
        lpRowList);

    if(FAILED(hrT))
    {
        hr = HR_LOG(E_FAIL);
        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrModifyACL----------------------------------------------------------------
//  If fRemove is FALSE then, for lpFolder, either add a new user with the
//  specified lRights or change the rights if the user exists to the specified
//  lRights.
//
//  If fRemove is TRUE the remove the ACL record for the indicated user.
//------------------------------------------------------------------------------
HRESULT HrModifyACL(
    IN LPMAPIFOLDER lpFolder,       // Folder to modify ACL for
    IN LPTSTR pszUserName,          // Name of user to change
    IN ULONG cbUserEid,             // Byte count for User's entry id.
    IN LPENTRYID lpUserEid,         // User's entry id.
    IN BOOL fRemove,                // Flag indicating whether to remove rights
    IN DWORD lRights)               // New rights (ignored if fRemove)
{
    HRESULT         hr              = NOERROR;
    LPFOLDERACL     lpACL           = NULL;
    LPSRowSet       lpRows          = NULL;
    LPROWLIST       lpRowList       = NULL;
    ULONG           iUser, iProp;
    LPSPropValue    lpsCurrentProp  = NULL;
    BOOL            fDisplayNameSet = FALSE;
    BOOL            fMemberIDSet    = FALSE;
    LPTSTR          lpszMemberName  = NULL;
    ULONG           ulRowFlags      = 0;
    LARGE_INTEGER   liMemberID      = { 0, 0 };
    LPTSTR          lpszDisplayName = NULL;

    DEBUGPUBLIC( "HrModifyACL()");

    hr = CHK_HrModifyACL( lpFolder, pszUserName, cbUserEid, lpUserEid, fRemove, lRights);
    if( FAILED( hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find row in the ACL corresponding to this user
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Open the ACL and read first list.
    hr = HrOpenAccessControlList( lpFolder, &lpACL, &lpRows);
    while( SUCCEEDED( hr))
    {   // Iterate through user rows
        for (iUser = 0; iUser < lpRows->cRows; iUser++)
        {
            // Clear properties for current user
            fDisplayNameSet = FALSE;
            fMemberIDSet = FALSE;

            // Iterate through properties for this user and set local copies
            for( iProp = 0; iProp < lpRows->aRow[iUser].cValues; iProp ++)
            {
                lpsCurrentProp = lpRows->aRow[iUser].lpProps + iProp;
                switch( lpsCurrentProp->ulPropTag)
                {
                    case PR_MEMBER_ID:
                        liMemberID = lpsCurrentProp->Value.li;
                        fMemberIDSet = TRUE;
                        break;

                    case PR_MEMBER_NAME:
                        lpszDisplayName = lpsCurrentProp->Value.LPSZ;
                        fDisplayNameSet = TRUE;
                        break;
                }
            }

            // Make sure all required properties are available.
            if ( (! fMemberIDSet) || (! fDisplayNameSet) )
            {
                hr = HR_LOG( E_FAIL);
                goto cleanup;
            }

            // Does this row correspond to the user to be changed?
            if( lstrcmpi( lpszDisplayName, pszUserName) == 0)
            {   // YES
                if (fRemove)
                    ulRowFlags = ROW_REMOVE;
                else
                    ulRowFlags = ROW_MODIFY;
                goto FoundUser;
            }
        }

        FREEPROWS(lpRows);

        // Get the next batch of rows in the access control list (if any).
        hr = HrNextAccessControlList( lpACL, &lpRows);
    }

    if( hr != EDK_E_END_OF_FILE)
        goto cleanup;

    // Didn't find row for user so need to add row if not fRemove.
    if( fRemove)
    {
        hr = HR_LOG( EDK_E_NOT_FOUND);
        goto cleanup;
    }

    ulRowFlags = ROW_ADD;
    liMemberID.LowPart = 0;
    liMemberID.HighPart = 0;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Modify the access control list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

FoundUser:
    // Create a row which will be used to modify the ACL table.
    hr = HrCreateROWLIST( ulRowFlags, liMemberID, lpszMemberName, lRights, cbUserEid, lpUserEid, &lpRowList);
    if( FAILED( hr))
        goto cleanup;

    ASSERTERROR( lpRowList != NULL, "lpRowList is NULL");

    // Modify the ACL on the folder.
    hr = HrModifyAccessControlList( lpACL, lpRowList);
    if( FAILED( hr))
        goto cleanup;

    // Save Changes to the folder.
    hr = MAPICALL( lpFolder)->SaveChanges( lpFolder, KEEP_OPEN_READWRITE);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// All done so clean up.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    if (lpRowList)
        HrFreeROWLIST( &lpRowList);
    FREEPROWS( lpRows);
    HrCloseAccessControlList( &lpACL);

    RETURN( hr);
}

//------------------------------------------------------------------------------


COMMANDS.C

// --Commands.c-----------------------------------------------------------------
//
// This module contains SMBAGENT's command table and the handling procedure for
// each of SMBAGENT's commands.
//
//  New         Create a new topic with an introduction.
//  Join        Join a topic as a subscriber.
//  Post        Post a message to a topic and broadcast it to all members.
//  Intro       Return the introductory message of a topic to the subscriber.
//  Leave       Remove a subscriber from the topic.
//  Topics      List all topics placing "***" before those you subscribe to.
//  Members     List all members of a topic.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include <ctype.h>
#include "commands.chk"

#include "smbagent.h"
#include "resource.h"

#define UNINITIALIZED   -1L                 // flag for uninitalized property tags

// Command handling procedure declarations
static  HRESULT      HrParseCommand( LPMESSAGE);
static  VOID         DoneWithMessage();

static HRESULT     HrProcessNew( LPMESSAGE);
static HRESULT     HrProcessJoin( LPMESSAGE);
static HRESULT     HrProcessPost( LPMESSAGE);
static HRESULT     HrProcessIntro( LPMESSAGE);
static HRESULT     HrProcessLeave( LPMESSAGE);
static HRESULT     HrProcessTopics( LPMESSAGE);
static HRESULT     HrProcessMembers( LPMESSAGE);

// Command table definition
typedef struct
{
    ULONG   ulCmdRID;                   // Resource ID for command string
    TCHAR   szCmdName[MAXCMDLEN];       // Command name string
    HRESULT (*lpfnCmdProc)              // Command handling procedure
        (LPMESSAGE  lpMessage);         // Entry ID and size for message
} CMDENTRY;                             // Command entry

static CMDENTRY aCmdTable[] =
{
    { IDS_CMD_NEW,      TEXT(""),   HrProcessNew },
    { IDS_CMD_JOIN,     TEXT(""),   HrProcessJoin },
    { IDS_CMD_POST,     TEXT(""),   HrProcessPost },
    { IDS_CMD_INTRO,    TEXT(""),   HrProcessIntro },
    { IDS_CMD_LEAVE,    TEXT(""),   HrProcessLeave },
    { IDS_CMD_TOPICS,   TEXT(""),   HrProcessTopics },
    { IDS_CMD_MEMBERS,  TEXT(""),   HrProcessMembers },
};

static  TCHAR   lpszTopicName[MAXSUBJLEN + 1] = {0};     // Topic of current msg.
static  LPTSTR  lpszCommand                   = NULL;    // Command of current msg.

//$--HrParseCommand()----------------------------------------------------------
// Parses out the command and the topic strings from the subject of the given
// message.  One thing we found that we needed to be careful of was to eliminate
// all un-necessary white space from the topic string.
//
// The globals lpszCommand and lpszTopicName are used to contain the result.
// ----------------------------------------------------------------------------

static HRESULT HrParseCommand(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT hr          = NOERROR;
    TCHAR   *pchSubject = lpSenderProps[IDX_SUBJECT].Value.LPSZ;
    TCHAR   *pchTopic   = lpszTopicName;

    DEBUGPUBLIC( "HrParseCommand()");

    hr = CHK_HrParseCommand( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( pchSubject == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Extract the command from the subject string.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Skip past any initial white space
    while( *pchSubject && isspace(*pchSubject)) pchSubject++;
    lpszCommand = pchSubject;

    // Skip past command string
    while( *pchSubject && !isspace(*pchSubject)) pchSubject++;
if(*pchSubject) *pchSubject++ = TEXT('\0');

    if( lstrlen( lpszCommand) > MAXCMDLEN)
    {   // The string is longer than any recognizeable command.
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INVALID_COMMAND));
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    if( lstrlen( pchSubject) > MAXSUBJLEN)
    {   // The topic name is much longer than a reasonable limit.
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_TOPIC_TOO_LONG));
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Extract the topic string from the subject string, eliminating all
// un-necessary white space.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    for( ;;)
    {
        // Skip white space
        while( *pchSubject && isspace(*pchSubject))
            pchSubject++;

        if( !*pchSubject)
        { // finish the topic string
            *pchTopic = TEXT('\0');
            goto cleanup;
        }

        // Add single space to topic
        if( pchTopic > lpszTopicName)
            *pchTopic++ = TEXT(' ');

        // Add the token to the topic string.
        while( *pchSubject && !isspace( *pchSubject))
            *pchTopic++ = *pchSubject++;
    }

cleanup:
    RETURN( hr);
}

//$--HrProcessNew---------------------------------------------------------------
// "New" command handling procedure
//  - creates a topic folder (if one does not exist) for the new topic,
//  - saves a modified version of the command message in the topic folder.
//
// The intro message is stored in the MAPI_ASSOCIATED portion of the topic
// folder.  We gave it a unique message class so that we can use the
// FindRow() function to locate it later.  This is a quick opperation
// since very few items will ever be stored in this location.
//
// We chose this method rather than storing it in the main portion of the topic
// folder to avoid sorting the folder, which could get quite large.  Sorting a
// folder is an expensive opperation at the server end and should only be used
// when it is necessary.
// -----------------------------------------------------------------------------
static HRESULT HrProcessNew(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    LPMAPIFOLDER    lpNewFolder     = NULL;
    LPMESSAGE       lpNewMsg        = NULL;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           cStrLen         = 0;
    ULONG           cbFolderEID     = 0L;
    LPENTRYID       lpFolderEID     = NULL;
    STopic*         lpTopic         = NULL;

    DEBUGPUBLIC( "HrProcessNew()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

    // If the topic already exists, generate an error report
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray != NOT_FOUND)
    {   // Send an error report
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_TOPIC_EXISTS));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Create the new topic folder and copy the topic introduction message into the
// associated portion of the folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Create a new topic folder.
    hr = STFolderArray_HrCreateFolder( lpszTopicName, &lpNewFolder, &lpTopic);
    if( FAILED(hr))
        goto cleanup;

    // Copy the command message into the topic folder
    hr = HrDuplicateMessage( lpMessage, lpNewFolder, MAPI_ASSOCIATED,
        lpPropsToCopyForKeep, &lpNewMsg);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the topic message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Set the recipient list to the topic creator.
    hr = MAPICALL( lpNewMsg)->ModifyRecipients( lpNewMsg, 0L, lpSenderAddr);
    if( FAILED(hr))
        goto cleanup;

    // Set the Subject field.
    cStrLen = LoadString( hInst, IDS_SUBJ_INTRO, (LPTSTR)&szSubjBuf, MAXSUBJLEN);
    _tcscat( szSubjBuf, lpszTopicName);

    {   // Encapsulate to make code clear
        SInitPropValue SPropValues[] =
        {
            { PR_SUBJECT,       0, (ULONG) szSubjBuf},
            { PR_MESSAGE_CLASS, 0, (ULONG) INTRO_MSG_CLASS}
        };

        hr = MAPICALL( lpNewMsg)->SetProps( lpNewMsg,
            2L, (LPSPropValue)&SPropValues, NULL);
        if( FAILED( hr))
            goto cleanup;
    }

    // Save the changes to the message.
    hr = MAPICALL(lpNewMsg)->SaveChanges( lpNewMsg,
        FORCE_SAVE | KEEP_OPEN_READWRITE);
    if( FAILED(hr))
        goto cleanup;

    // Let the topic object know about the open message interface pointer.
    STopic_SetIntroMsg( lpTopic, lpNewMsg);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add user to the access control list of the public folder.  This will allow
// the user to read the folder with the Capone client.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( bPublicTopic)
    {
        // This call will fail when the originator is a one-off.  Since this feature
        // is not necessary to interact with the list server we ignore the error.

        HR_LOG( HrModifyACL(
            lpNewFolder,            // Folder to modify ACL for.
            TEXT("Default"),        // Name of user to change.
            0L,                     // Byte count for User's entry id.
            NULL,                   // User's entry id.
            FALSE,                  // Do not remove rights.
            rightsReadOnly));

        HR_LOG( HrModifyACL(
            lpNewFolder,
            lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
            lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
            (LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
            FALSE,
            dwACLRights));
    }

    hr = HrSendConfirmReport( lpMessage, IDS_CMD_NEW, IDS_CNF_NEW, NULL);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    if( FAILED(hr) && hr != MAPI_E_NETWORK_ERROR && lpNewFolder)
    {   // We failed after the topic folder was created so delete it
        // from the MAPI store, the topic array, and the topic cache.
        HR_LOG( STFolderArray_HrDeleteFolderSZ( lpszTopicName));
    }

    RETURN( hr);
}

//$--HrProcessJoin----------------------------------------------------------
// "Join" command handling routine
// -------------------------------------------------------------------------
static HRESULT HrProcessJoin(
    IN  LPMESSAGE   lpMessage)     // Pointer to the current message object
{
    HRESULT         hr                  = NOERROR;
    LPMESSAGE       lpIntroMsg          = NULL;
    ULONG           iTFolderArray       = NOT_FOUND;
    ULONG           iSubscriber         = NOT_FOUND;
    STopic*         lpTopic             = NULL;

    DEBUGPUBLIC( "HrProcessJoin()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

// If the topic does not exist generate an error report.
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
        goto cleanup;
    }

    // Get the topic object from the cache.
    hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
    if( FAILED(hr))
        goto cleanup;

    // Check if the sender is already a subscriber of the topic
    hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
    if( FAILED(hr))
        goto cleanup;
    if( iSubscriber != NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_IS_SUBSCRIBER));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add user to the access control list of the public folder.  This will allow
// the user to read the folder with the Capone client.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( bPublicTopic)
    {
        LPMAPIFOLDER lpJoinFolder = STopic_GetFolder( lpTopic);

        // This call will fail when the originator is a one-off.  Since this feature
        // is not necessary to interact with the list server we ignore the error.

        HR_LOG( HrModifyACL(
            lpJoinFolder,
            lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
            lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
            (LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
            FALSE,
            dwACLRights));
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add this JOINer to the recipient list of the topic intro message, and send
// a confirmation to the originator.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Add the subscriber to intro msg and memory buffer.
    hr = STopic_HrAddSubscriber( lpTopic, lpSenderAddr);
    if( FAILED( hr))
        goto cleanup;

    // Send a confirmation report to the message originator.
    lpIntroMsg = STopic_GetIntroMsg( lpTopic);
    hr = HrSendConfirmReport( lpMessage, IDS_CMD_JOIN, IDS_CNF_JOIN, lpIntroMsg);
    if( FAILED(hr))
        goto cleanup;

cleanup:

    RETURN( hr);
}

//$--HrProcessPost----------------------------------------------------------
//  "Post" command handling procedure
//  - saves a copy of the command message in the topic folder, and
//  - distributes the command message to all subscribers of the topic
// -------------------------------------------------------------------------
static  HRESULT HrProcessPost(
    IN LPMESSAGE    lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    unsigned        cStrLen         = 0;
    ULONG           fModRecip       = 0L;   //ModifyRecipients flag -> replace list
    LPMAPIFOLDER    lpTopicFolder   = NULL;
    LPMESSAGE       lpPostMsg       = NULL;
    LPMESSAGE       lpSendMsg       = NULL;
    LPADRLIST       lpAdrList       = NULL;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           iSubscriber     = NOT_FOUND;
    STopic*         lpTopic         = NULL;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Two copies of the original posted message is made.  One to keep in the topic
// folder, and the other to be distributed to the subscribers.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    DEBUGPUBLIC( "HrProcessPost()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport(lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // If the topic does not exist generate an error report.
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
        goto cleanup;
    }

    // Get the topic object from the cache.
    hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
    if( FAILED(hr))
        goto cleanup;

    // Verify that the sender is a subscriber of the topic
    hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
    if( FAILED(hr))
        goto cleanup;
    if( iSubscriber == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
        goto cleanup;
    }

    // Get the pointer to the topic folder.
    lpTopicFolder = STopic_GetFolder( lpTopic);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Save a copy of the posting in the topic folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Make a copy of the command message in the topic-folder
    hr = HrDuplicateMessage( lpMessage, lpTopicFolder, 0, lpPropsToCopyForKeep, &lpPostMsg);
    if( FAILED(hr))
        goto cleanup;

    // Replace the Subject field with a fixed string
    cStrLen = LoadString( hInst, IDS_SUBJ_POST, (LPTSTR)&szSubjBuf, MAXSUBJLEN);
    _tcsncat( szSubjBuf, lpszTopicName, MAXSUBJLEN - cStrLen);

hr = HrMAPISetPropString( (LPMAPIPROP)lpPostMsg, PR_SUBJECT, szSubjBuf);
    if( FAILED( hr))
        goto cleanup;

// Save the changes to the message
    hr = MAPICALL(lpPostMsg)->SaveChanges( lpPostMsg, FORCE_SAVE);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Can't send a message that's was created in a public folder, so make a copy of
// the posting in the NDR folder.  Place the entire subscriber list in the
// 'To' field.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Make a copy of posted message in NDR folder for resubmission to all
    hr = HrDuplicateMessage( lpMessage, lpNDRFolder, 0, lpPropsToCopyForSend, &lpSendMsg);
    if( FAILED( hr))
        goto cleanup;

// Get a copy of the entire address list of subscribers.  We can't use the
    // original row set since ModifyRecipients will change it which would be bad.
    hr = STopic_GetCopyOfSubscriberRowSet( lpTopic, &lpAdrList);
    if( FAILED( hr))
        goto cleanup;

    // Replace/Add-to the TO:list in the msg. with the subscriber list
    hr = MAPICALL( lpSendMsg)->ModifyRecipients(lpSendMsg,
        fModRecip, lpAdrList);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Set additional properties then send the message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   // Concatenate the sender's display name onto the subject line.
    // Must truncate unusually long topic names so that there is enough space.
    { // Encapusulate to make code clear
        TCHAR szSenderName[MAXSUBJLEN + 1] = {0};
    ULONG cNameLen                     = 0L;

        cNameLen = LoadString( hInst, IDS_SUBJ_FROM, (LPTSTR)&szSenderName, MAXSUBJLEN);
        _tcsncat( szSenderName, lpSenderProps[IDX_SENDER_NAME].Value.LPSZ, MAXSUBJLEN - cNameLen);

        cNameLen = _tcslen( szSenderName);
    cStrLen  = _tcslen( szSubjBuf);

        if( cStrLen + cNameLen >= MAXSUBJLEN)
            cStrLen = max(MAXSUBJLEN - cNameLen, 15);

        _tcsncpy( &szSubjBuf[cStrLen], szSenderName, MAXSUBJLEN - cStrLen);
    }

    { // Encapsulate to make code clear
        SInitPropValue SPropValues[] =
        {
            { PR_SUBJECT,             0, (ULONG) szSubjBuf},
            { PR_DELETE_AFTER_SUBMIT, 0, (ULONG) TRUE},
            { PR_MESSAGE_CLASS,       0, (ULONG) SMBAGENT_MSG_CLASS}
        };

        hr = MAPICALL( lpSendMsg)->SetProps( lpSendMsg,
            3L, (LPSPropValue)&SPropValues, NULL);
        if( FAILED( hr))
            goto cleanup;
    }

    hr = MAPICALL(lpSendMsg)->SaveChanges( lpSendMsg,  KEEP_OPEN_READWRITE);
    if( FAILED( hr))
        goto cleanup;

    // Distribute the message to all subscribers
    hr = MAPICALL(lpSendMsg)->SubmitMessage( lpSendMsg, FORCE_SUBMIT);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    FREEPADRLIST( lpAdrList);
    ULRELEASE( lpPostMsg);
    ULRELEASE( lpSendMsg);

    RETURN( hr);
}

//$--HrProcessIntro----------------------------------------------------------
//  "Intro" command handling routine
//  - Sends a copy of the topic Intro message to the command originator
// -------------------------------------------------------------------------
static  HRESULT HrProcessIntro(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    LPMESSAGE       lpIntroMsg      = NULL;
    LPSRowSet       lpRows          = NULL;
    LPMAPITABLE     lpRecipTable    = NULL;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           iSubscriber     = NOT_FOUND;
    STopic*         lpTopic         = NULL;
    ENTRYLIST       elIntroMsg      = { 1L, NULL };
    SPropTagArray   aptEntryID      = { 1L, {PR_ENTRYID} };

    DEBUGPUBLIC( "HrProcessIntro()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // If the topic does not exist generate an error report.
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
        goto cleanup;
    }

    // Get the topic object from the cache.
    hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
    if( FAILED(hr))
        goto cleanup;

    // Verify that the sender is a subscriber of the topic
    hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
    if( FAILED(hr))
        goto cleanup;
    if( iSubscriber == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Send an confirmation report including the Intro message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    lpIntroMsg = STopic_GetIntroMsg( lpTopic);
    hr = HrSendConfirmReport( lpMessage, IDS_CMD_INTRO, IDS_CNF_INTRO, lpIntroMsg);
    if( FAILED(hr))
        goto cleanup;

cleanup:
    RETURN( hr);
}

//$--HrProcessLeave----------------------------------------------------------
//  "Leave" command handling routine
//  - deletes the sender's address from the topic's subscriber list.
// -------------------------------------------------------------------------
static  HRESULT HrProcessLeave(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           iSubscriber     = NOT_FOUND;
    ULONG           cSubscribers    = 0;
    STopic*         lpTopic         = NULL;

    DEBUGPUBLIC( "HrProcessLeave()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // If the topic does not exist generate an error report.
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
        goto cleanup;
    }

    // Get the topic object from the cache.
    hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
    if( FAILED(hr))
        goto cleanup;

    // Verify that the sender is a subscriber of the topic
    hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
    if( FAILED(hr))
        goto cleanup;
    if( iSubscriber == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remove user to the access control list of the public folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( bPublicTopic)
    {
        LPMAPIFOLDER lpLeaveFolder = STopic_GetFolder( lpTopic);

        // This call will fail when the originator is a one-off.  Since this feature
        // is not necessary to interact with the list server we ignore the error.

        HR_LOG( HrModifyACL(
            lpLeaveFolder,
            lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
            lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
            (LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
            TRUE,           // Remove the ACL
            dwACLRights));
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Delete the subscriber from the topic or delete the topic if this subscriber
// is the only one left.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Get the count of subscribers of the topic.
    cSubscribers = STopic_GetSubscriberCount( lpTopic);

    // Is this the last subscriber?
    if( cSubscribers == 1)
    {   // YES, so delete the topic and all its messages.
        hr = STFolderArray_HrDeleteFolder( iTFolderArray);
        if( FAILED(hr))
            goto cleanup;
    }
    else
    {   // Delete sender from the topic's subscriber list.  After this call
        // the subscriber portion of the topic will be uninitialized.
        hr = STopic_HrDeleteSubscriber( lpTopic, iSubscriber);
        if( FAILED(hr))
            goto cleanup;
    }

    // Send a confirmation report
    hr = HrSendConfirmReport( lpMessage, IDS_CMD_LEAVE, IDS_CNF_LEAVE, NULL);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    RETURN( hr);
}

//$--HrProcessTopics----------------------------------------------------------
//  "Topics" command handling routine
//  - Sends a list of topics sender is a member of to the command originator
// ----------------------------------------------------------------------------

// Max length of each topic line.  +10 to append other things such as ***\t and \n.
#define MAXTOPICSLEN (MAXSUBJLEN + 10)

static  HRESULT HrProcessTopics(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    ULONG           cTopics         = 0;
    ULONG           cBytes          = 0;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           iSubscriber     = NOT_FOUND;
    LPTSTR          lpTopicsList    = NULL;
    LPTSTR          lpTopicsEntry   = NULL;
    BOOL            bFolderErr      = FALSE;
    STopic*         lpTopic         = NULL;

    SizedSPropTagArray(1L,aptTableProps)= { 1L, { PR_DISPLAY_NAME } };

    DEBUGPUBLIC( "HrProcessTopics()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Allocate message buffer large enough for all topic names lines.  Each line
// may have three asterisks, a tab, the topic name and a return.  Also allow
// for two extra lines worth of space for extra message at end of buffer.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    cTopics = STFolderArray_GetCount();
    cBytes = (((cTopics + 2) * MAXTOPICSLEN) + 1) * sizeof (TCHAR);
    hr = MAPIAllocateBuffer( cBytes, &lpTopicsList);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }
    ASSERTERROR( lpTopicsList != NULL, "NULL lpTopicsList!");
    memset (lpTopicsList, 0, cBytes);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// List every folder placing three asterisks in front of the ones that the
// sender has subscribed to.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    lpTopicsEntry = lpTopicsList;
    for( iTFolderArray = 0; iTFolderArray < cTopics; iTFolderArray ++)
    {
        LPTSTR lpszSub;    // To hold either a blank or asterisks.

        // Get the topic object from the cache.
        hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
        if( SUCCEEDED(hr))
        {
            // Check to see if the sender is a subscriber of the topic.
            hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
            if( FAILED(hr))

goto cleanup;
            if( iSubscriber == NOT_FOUND)
                lpszSub = TEXT("\t");
            else
                lpszSub = TEXT("***\t");
        }
        else
        {
            lpszSub = TEXT("+++\t");
            bFolderErr = TRUE;
        }

        // Record the topic name in the buffer.
        lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("%s%s\r\n"), lpszSub,
            STFolderArray_GetName( iTFolderArray));

        ASSERTERROR( lpTopicsEntry <= ((LPBYTE) lpTopicsList) + cBytes, "ERROR: Buffer overwrite!");
    }

    if( lpTopicsEntry == lpTopicsList)
    {   // No topics were listed.
        lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("No topics found.\r\n"));
    }
    else
    {   // At least one topic was listed.
        lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("\r\n***\tindicates you are a subscriber.\r\n"));

        if( bFolderErr)
            lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT( "+++\tindicates subscription information not found.\r\n"));
    }

    ASSERTERROR( lpTopicsEntry <= ((LPBYTE) lpTopicsList) + cBytes, "ERROR: Buffer overwrite!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add the list to the original message body, save it, and send it as a
// confirmation report.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrMAPISetPropString( (LPMAPIPROP)lpMessage, PR_BODY, lpTopicsList);
    if( FAILED(hr))
        goto cleanup;

    hr = MAPICALL(lpMessage)->SaveChanges( lpMessage, KEEP_OPEN_READWRITE);
    if( FAILED(hr))
        goto cleanup;

    // Send an confirmation report, including the Topics list
    hr = HrSendConfirmReport( lpMessage, IDS_CMD_TOPICS, IDS_CNF_TOPICS, NULL);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    MAPIFREEBUFFER( lpTopicsList);
    RETURN( hr);
}

//$--HrProcessMembers----------------------------------------------------------
//  "Members" command handling routine
//  - Sends a list of members for a given topic to the command originator
// ----------------------------------------------------------------------------

#define MAXMEMBERLEN            110     // max length of each member's name/address

static  HRESULT HrProcessMembers(
    IN  LPMESSAGE   lpMessage)      // Pointer to the current message object
{
    HRESULT         hr              = NOERROR;
    ULONG           cSubscribers    = 0;
    ULONG           cBytes          = 0;
    LPTSTR          lpMemberList    = NULL;
    LPTSTR          lpMemberEntry   = NULL;
    LPSRow          lpRow           = NULL;
    ULONG           iTFolderArray   = NOT_FOUND;
    ULONG           iSubscriber     = NOT_FOUND;
    STopic*         lpTopic         = NULL;

    DEBUGPUBLIC( "HrProcessMembers()");
    hr = CHK_HrProcessAny( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    if( !*lpszTopicName)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // If the topic does not exist generate an error report.
    iTFolderArray = STFolderArray_Find( lpszTopicName);
    if( iTFolderArray == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
        goto cleanup;
    }

    // Get the topic object from the cache.
    hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
    if( FAILED( hr))
        goto cleanup;

    // Verify that the sender is a subscriber of the topic
    hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
    if( FAILED(hr))
        goto cleanup;
    if( iSubscriber == NOT_FOUND)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
        goto cleanup;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Allocate message buffer for subscriber names each terminated by <return>
// and the total list terminated by zero. Assume each name is MAXNAMELEN
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    cSubscribers = STopic_GetSubscriberCount( lpTopic);
    cBytes = ((cSubscribers * MAXMEMBERLEN) + 1) * sizeof(TCHAR);
    hr = MAPIAllocateBuffer( cBytes, &lpMemberList);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }
    ASSERTERROR( lpMemberList != NULL, "NULL lpMemberList!");
    memset (lpMemberList, 0, cBytes);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Make a list of all of the members with their display name and email address.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    lpMemberEntry = lpMemberList;
    lpRow = STopic_GetFirstSubscriber( lpTopic);
    while( lpRow)    // Loop through all subscribers to the topic
    {
        // Format display name and address for one subscriber.
        lpMemberEntry += wsprintf( lpMemberEntry, TEXT("%.32s (%.64s)\r\n"),
            lpRow->GetDispName(), lpRow->GetEmailAddr());

        lpRow = STopic_GetNextSubscriber( lpTopic);
    }

    ASSERTERROR( lpMemberEntry <= ((LPBYTE) lpMemberList) + cBytes, "ERROR: Buffer overwrite!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add the list to the original message body, save it, and send it back in a
// confirmation report.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrMAPISetPropString( (LPMAPIPROP)lpMessage, PR_BODY, lpMemberList);
    if( FAILED(hr))
        goto cleanup;

    hr = MAPICALL(lpMessage)->SaveChanges( lpMessage, KEEP_OPEN_READWRITE);
    if( FAILED(hr))
        goto cleanup;

    // Send an confirmation report, including the member list
    hr = HrSendConfirmReport( lpMessage, IDS_CMD_MEMBERS, IDS_CNF_MEMBERS, NULL);
    if( FAILED(hr))
        goto cleanup;

cleanup:
    RETURN( hr);
}

//$--HrProcessCommand ------------------------------------------------------
//  This procedure is called for each message read from Inbox to:
//  - validate the command, and
//  - invoke the appropriate handling procedure to process the command.
// -------------------------------------------------------------------------
HRESULT HrProcessCommand(
    IN LPMESSAGE    lpMessage,  // Pointer to the current message object
    IN LPENTRYLIST  lpelMsgID,  // Pointer to ENTRYLIST of the current message
    IN BOOL *       pfDeleted)  // Flag indicating if msg.is deleted upon return
{
    HRESULT     hr              = NOERROR;
    int         i               = 0;
    ULONG       iErrIndex       = 0;
    LPTSTR      lpszClass       = NULL;     // Class string of the current msg.
    static BOOL fInitialized    = FALSE;

    DEBUGPUBLIC( "HrProcessCommand()");

    hr = CHK_HrProcessCommand( lpMessage, lpelMsgID, pfDeleted);
    if( FAILED( hr))
        RETURN( hr);

    *pfDeleted = FALSE; // initialize

    hr = HrGetSenderProps( lpMessage);
    if( FAILED(hr))
        goto cleanup;   // If we can't do this then we can't send an error report.

// Initialize command name strings first time only.
    if( !fInitialized)
    {
        fInitialized = TRUE;
        for (i=0; i < (sizeof (aCmdTable) / sizeof (aCmdTable[0])); i++)
        {
            if (!LoadString( hInst, aCmdTable[i].ulCmdRID, aCmdTable[i].szCmdName, MAXCMDLEN))
            {   // ERROR: Command resource string not found!
                // We can not continue so we signal the service to stop and
                // complete this operation.  The actual stopping of the service
                // will occur a little later.
                EventLogMsg( EDKEVENT_ERROR, 1, "Could not initialize the command table.", 0);
                ServiceStop();

                // Since we haven't determined the validity of the message, set
                // the deleted flag to keep the message from being deleted later.
                *pfDeleted = TRUE;

                hr = HR_LOG( E_FAIL);
                goto cleanup;
            }
        }
    }

    // Initialize new topic folder info if config has changed...
    hr = HrInitTopicFolders();
    if( FAILED( hr))
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INIT_FAILURE));
        goto cleanup;
    }

    lpszClass = (LPTSTR) lpSenderProps[ IDX_MESSAGE_CLASS].Value.LPSZ;
    ASSERT_STRING_PTR(lpszClass, "lpszClass is not a valid string!");

    if( _tcsicmp(lpszClass, TEXT("IPM.NOTE")) != 0)
    {   // Not an IPM.Note so place in the NDR folder.
        hr = MAPICALL(lpInFolder)->CopyMessages( lpInFolder,
            lpelMsgID, NULL, lpNDRFolder, 0L, NULL, 0L);
        goto cleanup;
    }

    // Parse for the command in the Subject field
    hr = HrParseCommand( lpMessage);
    if( FAILED(hr))
        goto cleanup;
    if( !*lpszCommand)
    {
        HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_COMMAND));
        goto cleanup;
    }

    // Validate the command by comparing the command token with
    // the command entries in the command table; if match found,
    // invoke the corresponding handling procedure to process the command
    for (i=0; i < (sizeof (aCmdTable) / sizeof (aCmdTable[0])); i++)
    {
        if( _tcsicmp(lpszCommand, aCmdTable[i].szCmdName) == 0)
        {
            // NOTE: These command should NOT return an HRESULT error if it sent a mail
            // message to the originator due to an error in lpMessage.  E_FAILs
            // are reserved to indicate an unexpected error in MAPI or a logic error.
            hr = (aCmdTable[i].lpfnCmdProc)( lpMessage);
            if( FAILED( hr))
            {
                switch(hr)
                {
                case MAPI_E_NETWORK_ERROR:
                    // don't need to log an error
                    break;

                case EDK_E_NOT_FOUND:
                    HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_BAD_TOPIC));
                    break;

                default:
                    HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_SMBAGENT));
                    break;
                }
            }

            // We are done so we can clean up now.
            goto cleanup;
        }
    }

    // Command not in CmdTable; send an error report to originator
    HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INVALID_COMMAND));

cleanup:
    MAPIFREEBUFFER( lpSenderProps);
    lpszCommand = NULL;

    RETURN( hr);
}

// -------------------------------------------------------------------------


MESSAGES.C

// --Messages.c---------------------------------------------------------------------
//
//  This module contains miscellaneous utility procedures called
//  by the rest of SMBAGENT procedures to deal with
//  - creating a folder in a given folder;
//  - getting the sender's property values in a given message;
//  - getting the recipient list from a given message;
//  - etc.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "messages.chk"

#include "smbagent.h"
#include "resource.h"

#define EDK_ERROR_MEMORY_ALLOCATION     0x00000000U

extern HANDLE hEventSource;

// Global variables
externLPMAPISESSIONlphSession;
extern  LPADRBOOK       lpAdrBook;
extern  LPMAPIFOLDER    lpTopicsFolder;
extern  LPMAPIFOLDER    lpNDRFolder;
extern  HINSTANCE       hInst;

extern  TCHAR           szTextBuf[];
extern  TCHAR           szSubjBuf[];
extern  TCHAR           szCmdBuf[];

externLPENTRYIDlpSMBAgentEID;
externULONGcbSMBAgentEID;

LPADRLIST   lpSenderAddr = NULL;

//$--HrGetSenderProps----------------------------------------------------------
// Gets all properties of the sender's message that we use while processing it.
// The results are placed in a dynamically allocated array named lpSenderProps.
// There are defined constants that index into this array in SMBAGENT.H.
// Also transforms the sender props into a property array that can be used for
// replying to the sender.
// ----------------------------------------------------------------------------

HRESULT HrGetSenderProps(
    IN  LPMESSAGE       lpMessage)  // Ptr.to the msg.to read sender prop.from
{
    HRESULT         hr              = NOERROR;
    ULONG           ulObjType       = 0;
    ULONG           cValues         = 0;
    LPSPropTagArray lpSenderPTags   = NULL;
   ULONGfCompare= FALSE;
    int             ii              = 0;
    LPSPropValue    lpRecipProps    = NULL;

    static const SizedSPropTagArray( 7, aptSenderProp) =
    {   7,
        {
            PR_SENDER_NAME,         // The order of the first 5 must NOT change.
            PR_SENDER_ENTRYID,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_EMAIL_ADDRESS,
            PR_MESSAGE_FLAGS,      // Placeholder replaced by HrAddRecipProps().
            PR_MESSAGE_CLASS,
            PR_SUBJECT,
        }
    };

    static ULONG aRecipPropTags[] =
    {// This column's props will replace    these properties.
        PR_DISPLAY_NAME,                    // PR_SENDER_NAME
        PR_ENTRYID,                         // PR_SENDER_ENTRYID
        PR_ADDRTYPE,                        // PR_SENDER_ADDRTYPE
        PR_EMAIL_ADDRESS,                   // PR_SENDER_EMAIL_ADDRESS
        PR_RECIPIENT_TYPE,                  // PR_MESSAGE_FLAGS
    };
    int nRecipTags = ARRAY_CNT( aRecipPropTags);

    DEBUGPUBLIC( "HrGetSenderProps()");

    hr = CHK_HrGetSenderProps( lpMessage);
    if( FAILED( hr))
        RETURN( hr);

    ASSERTERROR( lpSenderProps == NULL, "lpSenderProps should be NULL");
    ASSERTERROR( lpSenderAddr  == NULL, "lpSenderAddr should be NULL");

    // Get the properties from the message originator.
    hr = MAPICALL(lpMessage)->GetProps( lpMessage,
        (LPSPropTagArray)&aptSenderProp,
        fMapiUnicode, &cValues, &lpSenderProps);
    if( hr == MAPI_W_ERRORS_RETURNED)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }
    if( FAILED(hr))
        goto cleanup;

    // Check to make sure we got the right properties
    if( cValues != 7 || lpSenderProps == NULL) {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( lpSenderProps[IDX_SENDER_NAME].ulPropTag == PR_SENDER_NAME &&
        lpSenderProps[IDX_SENDER_ENTRYID].ulPropTag       == PR_SENDER_ENTRYID &&
        lpSenderProps[IDX_SENDER_ADDRTYPE].ulPropTag      == PR_SENDER_ADDRTYPE &&
        lpSenderProps[IDX_SENDER_EMAIL_ADDRESS].ulPropTag == PR_SENDER_EMAIL_ADDRESS &&
        lpSenderProps[IDX_MESSAGE_FLAGS].ulPropTag        == PR_MESSAGE_FLAGS &&
        lpSenderProps[IDX_MESSAGE_CLASS].ulPropTag        == PR_MESSAGE_CLASS &&
        lpSenderProps[IDX_SUBJECT].ulPropTag              == PR_SUBJECT,
        "Mangled sender props detected");

// Verify that the message is NOT from the SMBAgent profile.
hr = MAPICALL( lphSession)->CompareEntryIDs( lphSession,
        lpSenderProps[IDX_SENDER_ENTRYID].Value.bin.cb,
        (LPENTRYID) lpSenderProps[IDX_SENDER_ENTRYID].Value.bin.lpb,
        cbSMBAgentEID, lpSMBAgentEID, 0L, &fCompare);
if( FAILED( hr))
goto cleanup;

if (fCompare == TRUE)
{ // Message is from SMBAgent profile.
MODULE_WARNING( "Skipping message from SMBAgent profile");
hr = HR_LOG(E_FAIL);
goto cleanup;
}

    // Create a single entry address list.
    hr = HrMAPICreateSizedAddressList( 1, &lpSenderAddr);
    if( FAILED( hr))
        goto cleanup;

    // Set the single entry to the sender properties.
    hr = HrMAPISetAddressList( 0, nRecipTags, lpSenderProps, lpSenderAddr);
    if( FAILED( hr))
        goto cleanup;

    // Get the pointer to the copy of the sender properties and transform
    // them into recipient properties.
    lpRecipProps = (lpSenderAddr)->aEntries[0].rgPropVals;
    for( ii = 0; ii < nRecipTags; ++ii)
    {
        lpRecipProps[ii].ulPropTag = aRecipPropTags[ii];
        lpRecipProps[ii].dwAlignPad = 0L;
    }

    // Set PR_RECIPIENT_TYPE's value.  All other values have been set
    // correctly by HrGetSenderProps().
    lpRecipProps[nRecipTags - 1].Value.l = MAPI_TO;

cleanup:
    // Free globals only if there was a failure.
    if( FAILED(hr))
    {
        MAPIFREEBUFFER( lpSenderProps);
        FREEPADRLIST( lpSenderAddr);
    }

    RETURN( hr);
}

//$--HrEmbedMsgInMsg------------------------------------------------------------
//  Embed a message within a message.
// -----------------------------------------------------------------------------

HRESULT HrEmbedMsgInMsg(
    IN LPMESSAGE lpNewMsg,      // Ptr to msg that will contain embedded msg.
    IN LPMESSAGE lpMsgToEmbed)  // Ptr to msg to embed.
{
    HRESULT             hr              = 0;
    ULONG               ulAttNum        = 0;
    LPATTACH            lpNewAttach     = NULL;
    LPMESSAGE           lpEmbeddedMsg   = NULL;
    LPSPropProblemArray lpProblems      = NULL;

    SPropValue rgPropValue[2] = {
        {PR_ATTACH_METHOD,      0, { ATTACH_EMBEDDED_MSG}},
        {PR_RENDERING_POSITION, 0, { 0}}
    };

    hr = CHK_HrEmbedMsgInMsg( lpNewMsg, lpMsgToEmbed);
    if( FAILED( hr))
        RETURN( hr);

    // Create the attachment in the message.
    hr = MAPICALL(lpNewMsg)->CreateAttach( lpNewMsg,
        NULL,       // IID for returned object
        MAPI_DEFERRED_ERRORS,
        &ulAttNum,
        &lpNewAttach);
    if( FAILED( hr))
        goto cleanup;

    ASSERT_IUNKNOWN_PTR( lpNewAttach, "INVALID lpNewAttach pointer");

    // Set the attachment method and rendering position.
    if( ulAttNum > 0)
            rgPropValue[1].Value.ul = 400;  // arbitrary value for end of message
    hr = MAPICALL( lpNewAttach)->SetProps( lpNewAttach,
        ARRAY_CNT( rgPropValue),
        rgPropValue,
        NULL);
    if( FAILED( hr))
        goto cleanup;

    // Open the property as a message.
    hr = MAPICALL( lpNewAttach)->OpenProperty( lpNewAttach,
        PR_ATTACH_DATA_OBJ,
        (LPIID)&IID_IMessage,
        0,
        MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
        (LPUNKNOWN *)&lpEmbeddedMsg);
    if( FAILED( hr))
        goto cleanup;

    ASSERT_IUNKNOWN_PTR( lpEmbeddedMsg, "INVALID lpEmbeddedMsg pointer");

    // Copy message to attached message.
    hr = MAPICALL( lpMsgToEmbed)->CopyTo( lpMsgToEmbed,
        0, NULL, NULL, 0, NULL,
        (LPIID) &IID_IMessage,
        lpEmbeddedMsg, 0,
        &lpProblems);
    if( FAILED( hr))
        goto cleanup;
    if( hr == MAPI_W_ERRORS_RETURNED)
    {   // If you find that this error occurs check lpProblems to see what went wrong.
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Save changes to the embedded message.
    hr = MAPICALL( lpEmbeddedMsg)->SaveChanges( lpEmbeddedMsg, 0);
    if( FAILED( hr))
        goto cleanup;

    // Save changes to the attachment.
    hr = MAPICALL( lpNewAttach)->SaveChanges( lpNewAttach, 0);
    if( FAILED( hr))
        goto cleanup;

cleanup:
    MAPIFREEBUFFER(lpProblems);
    ULRELEASE( lpEmbeddedMsg);
    ULRELEASE( lpNewAttach);

    RETURN( hr);
}

//$--HrCopyMessageBody---------------------------------------------------------
// Copy large (>=32K) PR_RTF_COMPRESSED body part.  The property must exist.
//-----------------------------------------------------------------------------

HRESULT
HrCopyMessageBody(
    IN  LPMESSAGE   lpSrcMsg,
    IN  LPMESSAGE   lpDestMsg)
{
    HRESULT             hr              = NOERROR;
STATSTGStatStg= {0};
ULARGE_INTEGERcbWritten= {0};

    PVIRTUALSTREAMONPROPERTYpvsSrcMsgBody   = NULL,
pvsDestMsgBody  = NULL;

    DEBUGPUBLIC( "HrCopyMessageBody()");

// Prepare to copy message body >= 32K
hr = HrOpenVirtualStreamOnProperty(
(LPMAPIPROP)lpSrcMsg,
PR_RTF_COMPRESSED,
0L,
&pvsSrcMsgBody);
if( FAILED( hr))
goto cleanup;

// Get size of body property.
hr = MAPICALL( pvsSrcMsgBody)->Stat( pvsSrcMsgBody,
&StatStg, 0L);
if( FAILED( hr))
goto cleanup;

// Open receiving message
hr = HrOpenVirtualStreamOnProperty(
(LPMAPIPROP)lpDestMsg,
PR_RTF_COMPRESSED,
MAPI_MODIFY | MAPI_CREATE,
&pvsDestMsgBody);
if( FAILED( hr))
goto cleanup;

// Perform complete copy
hr = MAPICALL( pvsSrcMsgBody)->CopyTo( pvsSrcMsgBody,
(LPSTREAM) pvsDestMsgBody,
StatStg.cbSize,
NULL,
&cbWritten);
if( FAILED( hr))
goto cleanup;

// Verify all bytes were written as expected
if( MAKEDWORDLONG( cbWritten.LowPart, cbWritten.HighPart) !=
MAKEDWORDLONG( StatStg.cbSize.LowPart, StatStg.cbSize.HighPart))
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// Commit change to destination message
hr = MAPICALL( pvsDestMsgBody)->Commit( pvsDestMsgBody, 0L);
if( FAILED( hr))
goto cleanup;

cleanup:
   ULRELEASE( pvsSrcMsgBody);
ULRELEASE( pvsDestMsgBody);

    RETURN( hr);
}

// --------------------------------------------------------------------------
// Property arrays for HrDuplicateMessage.
// --------------------------------------------------------------------------

SizedSPropTagArray( 14, rgptPropsToCopyForKeep) =
{ 14,
    {
        PR_SENDER_NAME,
        PR_SENDER_ENTRYID,
        PR_SENDER_ADDRTYPE,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_ENTRYID,
        PR_SENT_REPRESENTING_ADDRTYPE,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_CLIENT_SUBMIT_TIME,
        PR_MESSAGE_DELIVERY_TIME,
PR_IMPORTANCE,
PR_EXPIRY_TIME,
        PR_MESSAGE_ATTACHMENTS,
PR_RTF_COMPRESSED
    }
};

// An important thing to note is that you can not include the PR_SENDER...
// or the PR_SEND_REPRESENTING... properties when creating a message for
// to be sent.  It causes problems for One Off Recipients.
SizedSPropTagArray( 6, rgptPropsToCopyForSend) =
{ 6,
    {
        PR_CLIENT_SUBMIT_TIME,
        PR_MESSAGE_DELIVERY_TIME,
PR_IMPORTANCE,
PR_EXPIRY_TIME,
        PR_MESSAGE_ATTACHMENTS,
PR_RTF_COMPRESSED
    }
};

// Properties to copy when duplicating a message for keeping or sending.
LPSPropTagArray lpPropsToCopyForKeep = (LPSPropTagArray) &rgptPropsToCopyForKeep;
LPSPropTagArray lpPropsToCopyForSend = (LPSPropTagArray) &rgptPropsToCopyForSend;

//$--HrDuplicateMessage -----------------------------------------------------
// Duplicates a message by creating a new message in the given folder
// and copying all fields of the given message in the new message
// --------------------------------------------------------------------------

HRESULT HrDuplicateMessage(
    IN  LPMESSAGE       lpSrcMsg,           // pointer to the source message
    IN  LPMAPIFOLDER    lpDestFolder,       // pointer to the destination folder
    IN  ULONG           ulFlags,            // Intended to set MAPI_ASSOCIATED
    IN  LPSPropTagArray lpPropsToCopy,      // Pass in one of the above array pointers.
    OUT LPMESSAGE       *lppDestMsg)        // Addr of var.to contain ptr.to new msg.
{
    HRESULT             hr              = NOERROR;
    LPSPropProblemArray lpProblemArray  = NULL;

    DEBUGPUBLIC( "HrDuplicateMessage()");

    hr = CHK_HrDuplicateMessage( lpSrcMsg, lpDestFolder, ulFlags, lppDestMsg);
    if( FAILED( hr))
        RETURN( hr);

    // Create a new message in the given folder
    hr = MAPICALL(lpDestFolder)->CreateMessage( lpDestFolder,
    NULL, ulFlags | MAPI_DEFERRED_ERRORS,
        lppDestMsg);
    if( FAILED(hr))
        goto cleanup;

    // Duplicate content of source message in the new message.
    hr = MAPICALL(lpSrcMsg)->CopyProps( lpSrcMsg,
        lpPropsToCopy,
        0,
        NULL,
        &IID_IMessage,
        *lppDestMsg,
        0,
        &lpProblemArray);

    if( FAILED( hr) || hr == MAPI_W_ERRORS_RETURNED)
    {
        if( lpProblemArray != NULL &&
            lpProblemArray->cProblem == 1 &&
            lpProblemArray->aProblem->ulPropTag == PR_RTF_COMPRESSED)
        { // CopyProps may fail for message bodies sized >= 32K.
            hr = HrCopyMessageBody( lpSrcMsg, *lppDestMsg);
            if( FAILED( hr))
                goto cleanup;
        }
        else
        {
            hr = HR_LOG( E_FAIL);
            goto cleanup;
        }
    }

cleanup:
    MAPIFREEBUFFER( lpProblemArray);

    RETURN( hr);
}

//$--HrSendMessage ---------------------------------------------------------
// Creates and sends an IPM message with the given Subject, Recipient list,
// text and optional attachment(s).
// -------------------------------------------------------------------------

HRESULT HrSendMessage(
    IN  LPADRLIST   lpRecipList,    // Recipient list of the message
    IN  LPTSTR      lpszSubject,    // Message Subject string
    IN  LPTSTR      lpszText,       // Message text string
    IN  int         cNAttach,       // # of attachments to be included
    IN  LPMESSAGE   lpAtts[])       // Array of pointers to attachment messages
{
    HRESULT     hr              = NOERROR;
    int         i               = 0;
    ULONG       ulAttNum        = 0;
    LPMESSAGE   lpNewMsg        = NULL,
                lpEmbeddedMsg   = NULL;
    LPATTACH    lpNewAttach     = NULL;

    DEBUGPUBLIC( "HrSendMessage()");

    hr = CHK_HrSendMessage( lpRecipList, lpszSubject, lpszText, cNAttach, lpAtts);
    if( FAILED( hr))
        RETURN( hr);

    // Create a message in the NDR folder. We don't use the TOPICS folder because we
    // can't create a message to send when using public topic folders.
    hr = MAPICALL(lpNDRFolder)->CreateMessage( lpNDRFolder,
        NULL, MAPI_DEFERRED_ERRORS, &lpNewMsg);
    if( FAILED( hr))
        goto cleanup;
    ASSERTERROR( lpNewMsg!=NULL, "NULL lpNewMsg.");

    // Set the recipient list to that which has been given.
    hr = MAPICALL(lpNewMsg)->ModifyRecipients( lpNewMsg, 0L, lpRecipList);
    if( FAILED( hr))
        goto cleanup;

    // Encapsulate property setting in block to keep the code clear.
    {
        SInitPropValue SPropValues[] =
        {
            {PR_DELETE_AFTER_SUBMIT, 0, TRUE},
            {PR_MESSAGE_CLASS,       0, (ULONG)SMBAGENT_MSG_CLASS },
            {PR_SUBJECT,             0, (ULONG)lpszSubject},
            {PR_BODY,                0, (ULONG)lpszText},
        };

        hr = MAPICALL(lpNewMsg)->SetProps( lpNewMsg,
            4L, (LPSPropValue) SPropValues, NULL);
        if( FAILED( hr))
            goto cleanup;
    }

    // If lpAtts!=NULL, include it in the message
    for( i=0; i<cNAttach; i++)
    {
        hr = HrEmbedMsgInMsg( lpNewMsg, lpAtts[i]);
        if( FAILED(hr))
            goto cleanup;
    }

    // Submit the message
    hr = MAPICALL(lpNewMsg)->SubmitMessage( lpNewMsg, FORCE_SUBMIT);
    if( FAILED(hr))
        goto cleanup;

cleanup:
    ULRELEASE ( lpNewMsg);

    RETURN( hr);
}

//$--HrSendErrReport ----------------------------------------------------------
// Sends an error report to the originator of the given message, including
// the given message as an attachment in the error report.
// ----------------------------------------------------------------------------

HRESULT HrSendErrReport(
    IN LPMESSAGE    lpMessage,  //Ptr.to the message to be included in report
    IN int          iErrText)   //Index to text string
{
    HRESULT         hr              = NOERROR;
    LPMESSAGE       lpAtt           = NULL;
    int             cStrLen         = 0;

    DEBUGPUBLIC( "HrSendErrReport()");

    hr = CHK_HrSendErrReport( lpMessage, iErrText);
    if( FAILED( hr))
        RETURN( hr);

    // Load the Subject & Text field strings.
    cStrLen = LoadStringA( hInst, IDS_SUBJ_ERROR, (LPTSTR)&szSubjBuf, MAXSUBJLEN);
    cStrLen = LoadStringA( hInst, iErrText, (LPTSTR)&szTextBuf, MAXTEXTLEN);

    // Send the error report, including the given message as an attachment
    lpAtt = lpMessage;
    hr = HrSendMessage( lpSenderAddr, szSubjBuf, szTextBuf, 1, &lpAtt);
    if( FAILED( hr));

    RETURN( hr);
}

//$--HrSendConfirmReport ------------------------------------------------------
// Sends a confirmation to the given address w. reference to the given command
// ----------------------------------------------------------------------------

HRESULT HrSendConfirmReport(
    IN LPMESSAGE    lpMessage,      // Pointer to the command message
    IN int          iCmdName,       // Resource Id of command name
    IN int          iCnfText,       // Index to text string
    IN LPMESSAGE    lpAttach)
{
    HRESULT         hr                  = NOERROR;
    LPMESSAGE       alpAttachments[]    = {NULL, NULL};
    int             cNAttach            = 1L,   // >=1 attachment in error reports
                    cStrLen             = 0L;

    DEBUGPUBLIC( "HrSendConfirmReport()");

    hr = CHK_HrSendConfirmReport( lpMessage, iCmdName, iCnfText, lpAttach);
    if( FAILED( hr))
        RETURN( hr);

    alpAttachments[0] = lpMessage;
    if( lpAttach != NULL)
    {
        alpAttachments[1] = lpAttach;
        cNAttach = 2;
    }

    // Load subject & text field strings
    cStrLen = LoadString( hInst, iCmdName, szSubjBuf, MAXSUBJLEN);
    cStrLen = LoadString( hInst, IDS_CNF_SUBJECT, szSubjBuf + cStrLen, MAXSUBJLEN - cStrLen);
    cStrLen = LoadString( hInst, iCnfText, szTextBuf, MAXTEXTLEN);

    // Send the confirmation report, including the command message as an attachment
    hr = HrSendMessage( lpSenderAddr, szSubjBuf, szTextBuf, cNAttach, alpAttachments);
    if( FAILED( hr));

    RETURN( hr);
}

// ----------------------------------------------------------------------------


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by smbagent.rc
//
#define IDS_CNF_SUBJECT                 0
#define IDS_CNF_NEW                     1
#define IDS_CNF_JOIN                    2
#define IDS_CNF_POST                    3
#define IDS_CNF_INTRO                   4
#define IDS_CNF_LEAVE                   5
#define IDS_CNF_TOPICS                  6
#define IDS_CNF_MEMBERS                 7
#define IDS_ERR_NULL_COMMAND            8
#define IDS_ERR_INVALID_COMMAND         9
#define IDS_ERR_NULL_PARAM              10
#define IDS_ERR_TOPIC_EXISTS            11
#define IDS_ERR_NONEXIST_TOPIC          12
#define IDS_ERR_NONSUBSCRIBER           13
#define IDS_ERR_IS_SUBSCRIBER           14
#define IDS_ERR_BAD_TOPIC               15
#define IDS_ERR_TOPIC_TOO_LONG          16
#define IDS_ERR_INIT_FAILURE            17
#define IDS_ERR_SMBAGENT                18
#define IDS_SUBJ_ERROR                  19
#define IDS_SUBJ_INTRO                  20
#define IDS_SUBJ_POST                   21
#define IDS_SUBJ_FROM                   22
#define IDS_CMD_NEW                     23
#define IDS_CMD_JOIN                    24
#define IDS_CMD_POST                    25
#define IDS_CMD_INTRO                   26
#define IDS_CMD_LEAVE                   27
#define IDS_CMD_TOPICS                  28
#define IDS_CMD_MEMBERS                 29
#define IDI_SMBAGENT                    32512

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         101
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


SMBAGENT.C

// --smbagent.c-------------------------------------------------------------------
//
// This contains the main entry point, initialization and shutdown code, and
// the message processing loop.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "smbdata.h"
#include "smbagent.chk"

#include "smbagent.h"
#include "resource.h"

#define STOP_WAIT_HINT      20000       // 20 Second wait before considering the service dead.
#define CFG_POLL_MSEC20000       // 20 Second wait between configuration polls
#define MAX_REG_STR         800

#define TOPIC_CACHE_SIZE10
#define TOPIC_CACHE_DROP_ZONE5

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

//  Defined for NT service shell
TCHAR szAppName[]       = TEXT("SMBAgent");
TCHAR szWindowTitle[]   = TEXT("Sample Mailbox Agent");
TCHAR szServiceName[]   = TEXT("SMBAgent");

#ifdef __cplusplus
}

#endif // __cplusplus

static HANDLE   hServiceStopEvent       = NULL;

//Global variables
HINSTANCE       hInst                   = NULL;
LPMAPISESSION   lphSession              = NULL;     // This is a COPY do not release it!
LPMAPIFOLDER    lpStoreFolder           = NULL;     // Pointer to MDB root folder
LPMAPITABLE     lpRootTable             = NULL;     // Pointer to IPM_SUBTREE hierarchy table
LPMAPIFOLDER    lpRootFolder            = NULL;     // Pointer to IPM_SUBTREE folder
LPMAPIFOLDER    lpInFolder              = NULL;     // Pointer to Inbox folder
LPMAPIFOLDER    lpNDRFolder             = NULL;     // Pointer to the Undeliverable folder
LPMAPIFOLDER    lpTopicsFolder          = NULL;     // Pointer to the Topics folder
LPADRBOOK       lpAdrBook               = NULL;     // Pointer to address book
LPMDB           lpStore                 = NULL;     // Pointer to Private Store MDB.
LPMDB           lpPubStoreMDB           = NULL;     // Pointer to Public Store MDB.
LPMAPIFOLDER    lpPubStoreFolders       = NULL;     // Pointer to root of Public Folders.

TCHAR           szTextBuf[MAXTEXTLEN * 2] = TEXT("");     // Make these twice as big because sometimes
TCHAR           szSubjBuf[MAXSUBJLEN * 2] = TEXT("");     // we concat them with two pieces of data
TCHAR           szCmdBuf[MAXCMDLEN * 2]   = TEXT("");     // that can be the MAX size.

LPSPropValue    lpSenderProps;  // Pointer to array of sender properties.

ULONGcbSMBAgentEID;// Session identifiers
LPENTRYIDlpSMBAgentEID;

// These will be updated through the CfgAdviseObject.
LPSPropValue    lpCfgProps              = NULL;     // Ptr to array of cfg ext data properties.
LPTSTR          lpszTopicRootFolderName = TOPIC_ROOT_FOLDER_NAME;
LPTSTR          lpszTopicRootFolderComment = TOPIC_ROOT_FOLDER_COMMENT;
BOOL            bPublicTopic            = FALSE;
DWORD           dwPollInboxMsec         = 60000 * 5; // Default to 5 minutes before polling the inbox.
DWORD           dwACLRights             = rightsReadOnly;

static TCHAR INQUEUE_NAME[]  = TEXT( "Inbox");
static TCHAR OUTQUEUE_NAME[] = TEXT( "Outbox");
static TCHAR INQUEUE_MUTEX[] = TEXT( "InQueueLock");
static TCHAR EXT_DATA_NAME[] = TEXT( "SMBAgent");

static LPCTSTR              szInMutexName   = INQUEUE_MUTEX;// incoming queue mutex name
static BOOL                 fIsInit         = FALSE;
static HANDLE               hChkInBoxEvent  = NULL; // Signals notification of new mail.
static HANDLE               hInMutex        = NULL; // Signals inbox is NOT in use by
                                                    // another instance of SMBAGENT.
static ULONG                ulInConnection  = MAX_ULONG;

static BOOL                 fInitializedMAPI= FALSE;        // MAPI Initialized

static BOOL                 IsMAPILogon     = FALSE;        // logged onto MAPI
static LPADVISEOBJ          lpCfgAdviseObj  = NULL;     // Ptr to advise obj for cfg extension data.

static void RegNotifyThread( IN void* UnUsed);

//$--InBoxNotification---------------------------------------------------------
// This is an event handling procedure, called whenever new message(s) arrive
// in the inbox.
// ----------------------------------------------------------------------------
static SCODE STDAPICALLTYPE InBoxNotification(
    IN LPVOID           lpvContext,         //pointer to context
    IN ULONG            cNotification,      //count of notification
    IN LPNOTIFICATION   lpNotifications)    //pointer to notifications
{
    HRESULT hr = NOERROR;
    if( SetEvent( hChkInBoxEvent) == FALSE)
        hr = HR_LOG( E_FAIL);
    RETURN( hr);
}

//$--HrProcessInBoxMessages----------------------------------------------------
// Process all messages in the Inbox until a shut down event is signaled.
// ----------------------------------------------------------------------------
static HRESULT HrProcessInBoxMessages()
{
    HRESULT         hr                  = NOERROR;
    BOOL            bRc                 = TRUE;
    SCODE           sc                  = SUCCESS_SUCCESS;
    unsigned        iMsgIndx            = 0;
    ULONG           ulNMsgTotal         = 0;
    ULONG           ulObjType           = 0;
    LPMESSAGE       lpMessage           = NULL;
    ULONG           cbEntryID           = 0;
    LPENTRYID       lpEntryID           = NULL;
    LPTSTR          lpszCommand         = NULL;
    DWORD           dwRc                = 0;
    ENTRYLIST       elMsgID             = {1, NULL};
    BOOL            fDeleted            = FALSE;    // TRUE if curr.msg.has been deleted
    LPSRowSet       lpInRowSet          = NULL;     // pointer to inbox rows
    LPMAPITABLE     lpInBoxTable        = NULL;

    HANDLE hEventArray1[] =
    {
        hChkInBoxEvent,     // Signaled indicates that we recevied notification of new mail.
        hServiceStopEvent,  // Signaled indicates we are suposed to shutdown.
    };

    HANDLE hEventArray2[] =
    {
        hInMutex,           // Signaled indicates no other SMBAGENT process is using the inbox,
        hServiceStopEvent,  // Signaled indicates we are suposed to shutdown.
    };

    // Request the table be sorted by submittal time.
    static const SizedSSortOrderSet(1L,sSortPrioSet) =
        { 1L, 0L, 0L, { PR_CLIENT_SUBMIT_TIME, TABLE_SORT_ASCEND}};

    static const SizedSPropTagArray(2L,sPropColumns) =
        { 2L, {PR_ENTRYID, PR_PRIORITY}};

    DEBUGPUBLIC( "HrProcessInBoxMessages()");

    // Get the contents table for the inbox folder.
    ASSERTERROR( lpInFolder != NULL, "NULL lpInFolder!");
    hr = MAPICALL(lpInFolder)->GetContentsTable( lpInFolder,
        MAPI_DEFERRED_ERRORS, &lpInBoxTable);
    if( FAILED( hr))
        goto cleanup;

    // Table operations need only be performed once
    hr = MAPICALL( lpInBoxTable)->SetColumns( lpInBoxTable,
        (LPSPropTagArray)&sPropColumns, TBL_BATCH);
    if( FAILED( hr))
        goto cleanup;

    hr = MAPICALL( lpInBoxTable)->SortTable( lpInBoxTable,
        (LPSSortOrderSet)&sSortPrioSet, TBL_BATCH);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We go to sleep at the top of this while loop to wait for either the
// hServiceStopEvent to be signaled or both the hChkInBoxEvent and the hInMutex
// to be signaled.
//
// The amount of time we sleep is a SMBADMIN configuration setting.  If we time
// out of the sleep then we check the in box for messages.  Sometimes the
// notifications are not timely enough so this gives the user the option of
// setting this parameter to INFINITE or some value that makes sense for their
// system setup.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    while( TRUE)
    {
        // Wait for the Check In Box event or the Shutdown event to be signaled.
        dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray1), hEventArray1,
             FALSE, dwPollInboxMsec);
        switch( dwRc)
        {
            case WAIT_TIMEOUT:
            case WAIT_OBJECT_0:
                break;          // We are supposed to check the inbox.
            case WAIT_OBJECT_0 + 1:
                goto cleanup;   // We are supposed to shutdown.
            default:
                HR_LOG( E_FAIL);
                goto cleanup;
        }

        // Get ownership of InMutex to prevent another SMBAGENT instance from
        // accessing inbox. This only works if both are running on the same
        // computer.  (This is not recommended.)
        dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray2), hEventArray2,
            FALSE, INFINITE);
        switch( dwRc)
        {
            case WAIT_OBJECT_0:
                break;          // The inbox is available.
            case WAIT_OBJECT_0 + 1:
                goto cleanup;   // We are supposed to shutdown.
            default:
                HR_LOG( E_FAIL);
                goto cleanup;
        }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// At this point we have received notification of new mail or we are just polling
// to check the inbox for mail and we know no other SMBAGENT process on this machine
// is using the inbox.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        DEBUGACTION( "Checking inbox...");

        // Resetting InboxEvent will cause us to sleep once we reach the top of
        // the loop unless more mail comes in while we are processing.
        ResetEvent( hChkInBoxEvent);

        // Seek to the beginning of the inbox table.
        hr = MAPICALL( lpInBoxTable)->SeekRow( lpInBoxTable,
            BOOKMARK_BEGINNING, 0, NULL);
        if( FAILED( hr))
            goto cleanup;

        // Get the entire list of messages that are currently in the inbox.
        // More messages may come in while we are processing this list but they
        // will not show up for us until we get the list next time.
        hr = HrQueryAllRows( lpInBoxTable, NULL, NULL, NULL, 0, &lpInRowSet);
        if( FAILED( hr))
            goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Main loop that actualy processes the inbox messages until end of the table
// list is reached.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        for( iMsgIndx = 0; iMsgIndx < lpInRowSet->cRows; ++iMsgIndx)
        {
            // Check to make sure we are not supposed to shutdown.
            dwRc = WaitForSingleObject( hServiceStopEvent, 0);
            if( dwRc == WAIT_OBJECT_0)
                break;  // Time to shut down.
            if( dwRc != WAIT_TIMEOUT)
            {
                HR_LOG( E_FAIL);
                goto cleanup;
            }

            // Check to see if user paused the service.
            hr = HrServiceProcessControl();
            if( FAILED( hr))
                goto cleanup;

            // Initialize the ENTRYLIST with the ENTRYID of the message
            elMsgID.lpbin = &(lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin);

            // Open the next message in the inbox.
            cbEntryID = lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin.cb;
            lpEntryID = (LPENTRYID) (lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin.lpb);

            // Message has not been deleted since it has not been opened yet.
            fDeleted = FALSE;

            // Open the message for modification.
            hr = MAPICALL(lpInFolder)->OpenEntry( lpInFolder,
                cbEntryID, lpEntryID, NULL,
                MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
                &ulObjType, (LPUNKNOWN FAR *) &lpMessage);
            if( SUCCEEDED( hr) && ulObjType == MAPI_MESSAGE)
            {   // Process the message.
                ASSERTERROR( lpMessage != NULL, "NULL lpMessage!");
                hr = MAPICALL(lpMessage)->SetReadFlag(lpMessage,
                    MAPI_DEFERRED_ERRORS);
                if( FAILED( hr))
                    goto cleanup;

                hr = HrProcessCommand( lpMessage, &elMsgID, &fDeleted);
                if( hr == MAPI_E_NETWORK_ERROR)
                    goto cleanup;
            }

            // If not deleted during processing, delete the message from inbox
            if( lpMessage && !fDeleted)
            {
                hr = MAPICALL(lpInFolder)->DeleteMessages( lpInFolder,
                    &elMsgID, 0, NULL, 0);
                if( FAILED( hr))
                    goto cleanup;
            }

            ULRELEASE( lpMessage); // Free the message pointer
            lpMessage = NULL;
        }
        // Free the current row set of message structures.
        FREEPROWS( lpInRowSet);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are done with all messages in the table, there may be more in the inbox,
// but not in this table.  So we release the table and prepare to go back to
// sleep.  If more messages are waiting in the inbox we will have received a
// signal and will continue immediately.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Unlock the inbox.
        if( ReleaseMutex( hInMutex) == FALSE)
        {
            HR_LOG( E_FAIL);
            goto cleanup;
        }

    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are supposed to shutdown.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup:

    // Unlock the inbox.  This can fail if we don't have the inbox locked.
    // So we don't care about the return results in this case.
    ReleaseMutex( hInMutex);

    ULRELEASE( lpInBoxTable);

    RETURN( hr);
}

//$--HrOpenHierarchicalFolder---------------------------------------------------
//  Finds and opens hierarchical folder.  Creates folders as necessary.
// -----------------------------------------------------------------------------
static HRESULT HrOpenHierarchicalFolder(
    IN LPMAPIFOLDER lpParentFolder,     // pointer to parent folder
    IN LPTSTR lpszFolderPath,           // Folder name
    IN LPTSTR lpszDefaultComment,       // Folder comment
    OUT LPMAPIFOLDER *lppFolder)        // pointer to open folder
{
    HRESULT         hr              = NOERROR;
    LPMAPIFOLDER    lpFolder        = NULL;
    LPTSTR         *lppszFolderList = NULL;
    ULONG           ulFolderCount   = 0L;
    ULONG           i               = 0L;

    DEBUGPUBLIC( "HrOpenHierarchicalFolder()");

    hr = CHK_HrOpenHierarchicalFolder(
        lpParentFolder, lpszFolderPath, lpszDefaultComment,
        lppFolder);
    if( FAILED( hr))
        RETURN( hr);

    *lppFolder = NULL;

    hr = HrStrTokAll( lpszFolderPath, TEXT("\\"), &ulFolderCount, &lppszFolderList);
    if( FAILED( hr))
        goto cleanup;

    // Attempt to find the topics folder.
    for( i=0; i<ulFolderCount; i++)
    {
    hr = MAPICALL(lpParentFolder)->CreateFolder( lpParentFolder,
    FOLDER_GENERIC,
    lppszFolderList[i],
    lpszDefaultComment,
    NULL,
    fMapiUnicode | OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,
    &lpFolder);
    if( FAILED( hr))
    goto cleanup;

    ASSERT_IUNKNOWN_PTR( lpFolder, "INVALID lpFolder pointer");

        // No longer need parent folder.
        // (Don't release folder that was passed!)
        if( i > 0L)
            ULRELEASE( lpParentFolder);

        lpParentFolder = lpFolder;
        lpFolder = NULL;
    }

    // Success!
    *lppFolder = lpParentFolder;

cleanup:
    MAPIFREEBUFFER( lppszFolderList);

    if( FAILED( hr))
        ULRELEASE( lpFolder);

    RETURN( hr);
}

//$--HrInitTopicFolders--------------------------------------------------------
//  Initialize Topic folder interfaces and cache
// ----------------------------------------------------------------------------
HRESULT HrInitTopicFolders( VOID)
{
    HRESULT         hr              = NOERROR;
    LPMAPIFOLDER    lpParentFolder  = NULL;

    DEBUGPRIVATE( "HrInitTopicFolders()");

    if( !lpTopicsFolder)
    {
        if( bPublicTopic)
            lpParentFolder = lpPubStoreFolders;
        else
            lpParentFolder = lpRootFolder;

        hr = HrOpenHierarchicalFolder(
            lpParentFolder, lpszTopicRootFolderName, lpszTopicRootFolderComment, &lpTopicsFolder);
        if( FAILED(hr))
            goto cleanup;

        if( bPublicTopic)
        {
            hr = HrModifyACL(
                lpTopicsFolder,
                TEXT("Default"),
                0L,
                NULL,
                FALSE,
                rightsReadOnly);
            if( FAILED( hr))
                goto cleanup;
        }

        // Reinitialize the topic folder array and topic cache
        hr = STFolderArray_HrInit();
        if( FAILED( hr))
            goto cleanup;

        hr = STopicCache_HrInit( TOPIC_CACHE_SIZE, TOPIC_CACHE_DROP_ZONE);
        if( FAILED( hr))
            goto cleanup;
    }

cleanup:
    if( FAILED( hr))
        ULRELEASE( lpTopicsFolder);

    RETURN( hr);
}

//$--HrUninitTopicFolders------------------------------------------------------
//  Release folder interfaces
// ----------------------------------------------------------------------------
static VOID UninitTopicFolders( VOID)
{
    STopicCache_ReleaseAll();
    STFolderArray_Destroy();

ULRELEASE( lpTopicsFolder);

}

//$--HrCfgChanged()-------------------------------------------------------------
// This function gets called once when the advise is initialized and then when
// the configuration extension data changes.
//
// This data is maintained by a property sheet found in Exchange's Administration
// program for the mailbox that we have logged onto.  The SMBAdmin.DLL supports
// the property sheet.
// -----------------------------------------------------------------------------
static HRESULT HrCfgChanged(
    LPVOID          lpvUserContext,     // Not used.
    LPWSTR          lpwszBlobName,      // Not used.
    ULONG           cProps,             // Count of propetries in lpProps.
    LPSPropValue    lpProps)            // Pointer to the array of properties.
{
    HRESULT         hr              = NOERROR;
LPMAPIFOLDER    lpParentFolder  = NULL;
    DWORD           dwRc        = 0;

    HANDLE hEventArray[] =
    {
        hInMutex,           // Signaled indicates no other SMBAGENT process is using the inbox,
        hServiceStopEvent,  // Signaled indicates we are suposed to shutdown.
    };

    DEBUGPUBLIC( "HrCfgChanged()");

    // Return a special error if there is no extension data. This is an
    // expected condition, but we must log why we don't continue. This
    // MUST be done before the CHK_ function is called.
    if( cProps != SMBDATA_PROP_COUNT)
    {
        EventLogMsg( EDKEVENT_ERROR,
            1, "Extension data ("SMBBLOBNAME") corrupt or non-existent.  Install and run SMBAdmin.",
            0);
        RETURN( EDK_E_NOT_FOUND);
    }

    // This makes sure we have exactly the expected number of properties.
    hr = CHK_HrCfgChanged( lpvUserContext, lpwszBlobName, cProps, lpProps);
    if( FAILED( hr))
        RETURN( hr);

    // Get ownership of InMutex so that we don't interrupt work in progress
    dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray), hEventArray, FALSE, INFINITE);
    switch( dwRc)
    {
        case WAIT_OBJECT_0:         // The inbox is available.
            break;

        case WAIT_OBJECT_0 + 1:     // We are supposed to shutdown.
            goto cleanup;

        case WAIT_ABANDONED_0:      // mutex abandoned?
            MODULE_ERROR( "Mutex abandoned!  You may need to reboot.");
            /* FALL-THROUGH */

        default:
            hr = HR_LOG( E_FAIL);
            goto cleanup;
    }

    // Release old topic folder information
    UninitTopicFolders();

    // Extract data from property array.  Note that we keep the property array
    // around until next time a change occurs or termination.
    lpszTopicRootFolderName    = lpProps[ IDX_TOPIC_ROOT_FOLDER_NAME   ].Value.LPSZ;
    lpszTopicRootFolderComment = lpProps[ IDX_TOPIC_ROOT_FOLDER_COMMENT].Value.LPSZ;
    dwPollInboxMsec            = lpProps[ IDX_POLL_INBOX_MSEC          ].Value.ul;
    bPublicTopic           = lpProps[ IDX_PUBLIC_TOPIC_FOLDER      ].Value.b;
    dwACLRights                = lpProps[ IDX_ACL_RIGHTS               ].Value.ul;

    // Free the previous set of properties and remember the new one.
    MAPIFREEBUFFER( lpCfgProps);
    lpCfgProps = lpProps;

    // NOTE: Topic folder is initialized when processing mail
    // so error message can be returned if initialization fails.

if( ReleaseMutex( hInMutex) == FALSE)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

cleanup:
    RETURN( hr);
}

//$--UninitSMBAGENT ----------------------------------------------------------
//  Uninitialize the service
// ----------------------------------------------------------------------------
static VOID UninitSMBAGENT( VOID)
{
    // Release all data cached in the topic cache and destroy the folder array.
    UninitTopicFolders();

    // Stop configuration extension data notification.
    if( lpCfgAdviseObj)
    {
        HR_LOG( HrCfgDestroyAdviseObj( lpCfgAdviseObj));
        lpCfgAdviseObj = NULL;
    }
    MAPIFREEBUFFER( lpCfgProps);

    // Stop inbox notifications.
    if((ulInConnection != MAX_ULONG) && (lpStore != NULL))
        MAPICALL(lpStore)->Unadvise(lpStore, ulInConnection);

    CLOSEHANDLE( hChkInBoxEvent);
    CLOSEHANDLE( hInMutex);

    // Release global object pointers.
    ULRELEASE( lpAdrBook);
    ULRELEASE( lpNDRFolder);
    ULRELEASE( lpInFolder);
    ULRELEASE( lpRootTable);
    ULRELEASE( lpRootFolder);
    ULRELEASE( lpStoreFolder);

    // Release the message stores
    ULRELEASE(lpStore);

    ULRELEASE( lpPubStoreFolders);
    ULRELEASE( lpPubStoreMDB);

MAPIFREEBUFFER( lpSMBAgentEID);

    // Logoff a MAPI session
    if((IsMAPILogon == TRUE) && (lphSession != NULL))
    {
        MAPICALL( lphSession)->Logoff( lphSession, 0, 0, 0);
        ULRELEASE( lphSession);
        IsMAPILogon = FALSE;
    }

    if(fInitializedMAPI == TRUE)
    {
        MAPIUninitialize();
        fInitializedMAPI = FALSE;
    }

    fIsInit = FALSE;
}

//$--InitSMBAGENT ----------------------------------------------------------------
//  Initialize for receiving & sending messages
// -----------------------------------------------------------------------------
static  HRESULT HrInitSMBAGENT( void) // RETURNS: return code
{
    HRESULT         hr              = NOERROR;
    SCODE           sc              = SUCCESS_SUCCESS;
    LPTSTR *        lppszArgv       = NULL;
    LPTSTR          lpszPassword    = TEXT("");
    ULONG           dwArgc          = 0,    // number of arguments to service
                    cbInEntryID     = 0,    // #bytes in Inbox EntryID
                    cbRootEntryID   = 0,    // #bytes in Root Folder EntryID
                    cbNDREntryID    = 0,    // #bytes in Undeliverable Folder EntryID
                    ulObjType       = 0,
                    ulFlags         = 0;

    LPENTRYID       lpInEntryID     = NULL, // Pointer to Inbox folder EntryID
                    lpRootEntryID   = NULL, // Pointer to Root folder EntryID
                    lpNDREntryID    = NULL; // Pointer to Undeliverable Entry ID
    MAPIINIT_0      MapiInit;

    ULONG            cbeid          = 0;
    LPENTRYID        lpeid          = NULL;
    LPMAPIADVISESINK lpInAdvise     = NULL;

    TCHAR            szServiceName[MAX_SERVICE_NAME_LENGTH+1] = {TEXT("")};

    TCHAR            szProfileName[MAX_PATH+1] = {0};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize MAPI, create temporary profile, and logon
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if( !FIsService())
        hr = MAPIInitialize(NULL);  // Not a service.
    else
    {   // Services need special initialization.
        MapiInit.ulVersion = MAPI_INIT_VERSION;
        MapiInit.ulFlags = MAPI_NT_SERVICE;
        ulFlags = MAPI_NT_SERVICE | MAPI_NO_MAIL;
        hr = MAPIInitialize( &MapiInit);
    }
    if( FAILED( hr))
        goto cleanup;

    fInitializedMAPI = TRUE;

    hr = HrServiceGetArgv(&dwArgc, &lppszArgv);
    if( FAILED( hr))
        goto cleanup;

    ASSERTERROR( dwArgc != 0L, "Zero dwArgc");
    ASSERTERROR( lppszArgv != NULL, "NULL lpszArgv");

    if( dwArgc > 1)
        lpszPassword = lppszArgv[1];

    hr = HrServiceGetName( szServiceName);
    if(FAILED( hr))
        goto cleanup;

    hr = HrCreateProfileName( szServiceName, MAX_PATH+1, szProfileName);
    if(FAILED(hr))
        goto cleanup;

hr = HrCreateMailboxAgentProfile( szServiceName, szProfileName);

if(hr == E_ACCESSDENIED)
{
MODULE_WARNING("Can't create profile--it already exists.");
        hr = NOERROR;
}
    else if(FAILED(hr))
    {
        goto cleanup;
    }

    hr = MAPILogonEx(
        (ULONG) 0,
        szProfileName,
        lpszPassword,
        MAPI_NEW_SESSION | MAPI_EXTENDED | ulFlags,
        &lphSession);

    if( FAILED( hr))
        goto cleanup;

    ASSERTERROR( lphSession!=NULL, "Null lphSession!");

    IsMAPILogon = TRUE;

    // Mark profile for later deletion
    HR_LOG(HrRemoveProfile(szProfileName));

    // Make a note of who we are.  This will be used later to tell if messages are
// sent from the SMBAgent profile.  (Something only a tester would do...)
hr = MAPICALL(lphSession)->QueryIdentity (lphSession, &cbSMBAgentEID, &lpSMBAgentEID);
if (FAILED(hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Misc. folder finding and opening including the InBox.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Open the public store MDB.
    hr = HrOpenExchangePublicStore(lphSession, &lpPubStoreMDB);
    if( FAILED(hr))
        RETURN( hr);

    hr = HrOpenExchangePublicFolders( lpPubStoreMDB, &lpPubStoreFolders);
    if( FAILED( hr))
        goto cleanup;

    // Get entry ID of message store
    hr = HrMAPIFindDefaultMsgStore( lphSession, &cbeid, &lpeid);
    if( FAILED(hr))
        goto cleanup;

    hr = MAPICALL( lphSession)->OpenMsgStore( lphSession,
        (ULONG)0,
        cbeid,
        lpeid,
        NULL,
        MDB_NO_DIALOG | MDB_WRITE,
        &lpStore);

    MAPIFREEBUFFER(lpeid);

    if( FAILED( hr))
        goto cleanup;

    // Get the root folder in the default message store
    hr = MAPICALL(lpStore)->OpenEntry( lpStore,
        (ULONG)0,
        NULL,
        NULL,
        MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpStoreFolder);
    if( FAILED( hr))
        goto cleanup;

    // Find Message Store's IPM folder subtree
    hr = HrMAPIFindIPMSubtree( lpStore, &cbRootEntryID, &lpRootEntryID);
    if( FAILED(hr))
        goto cleanup;

    ASSERTERROR( cbRootEntryID != 0, "ZERO cbRootEntryID!");
    ASSERTERROR( lpRootEntryID != NULL, "NULL lpRootEntryID!");

    // Open SMBAGENT's IPM_SUBTREE folder
    hr = MAPICALL(lpStoreFolder)->OpenEntry( lpStoreFolder,
    cbRootEntryID,
    lpRootEntryID,
        NULL,
        MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpRootFolder);
    if( FAILED(hr))
        goto cleanup;
    if( ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( lpRootFolder != NULL, "NULL lpRootFolder!");

    // Get the hierarchy table for the root folder of the message store
    hr = MAPICALL(lpRootFolder)->GetHierarchyTable( lpRootFolder,
    MAPI_DEFERRED_ERRORS,
    &lpRootTable);
    if( FAILED( hr))
        goto cleanup;

    // Find SMBAGENT's Inbox folder
    hr = HrMAPIFindInbox( lpStore, &cbInEntryID, &lpInEntryID);
    if( FAILED( hr))
        goto cleanup;

    ASSERTERROR ( lpInEntryID != NULL, "NULL lpInEntryID");

    // Open SMBAGENT's Inbox folder
    hr = MAPICALL(lpRootFolder)->OpenEntry( lpRootFolder,
    cbInEntryID,
    lpInEntryID,
        NULL,
        MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN FAR *) &lpInFolder);
    if( FAILED(hr))
        goto cleanup;
    if( ulObjType != MAPI_FOLDER)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( lpInFolder != NULL, "NULL lpInFolder!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Open the "Undeliverable" folder, create it if necessary.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrOpenHierarchicalFolder( lpRootFolder, NDR_FOLDER_NAME, NDR_FOLDER_COMMENT, &lpNDRFolder);
    if( FAILED(hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Open the Address Book.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = MAPICALL(lphSession)->OpenAddressBook( lphSession, 0L, NULL, AB_NO_DIALOG, &lpAdrBook);
    if( FAILED( hr))
        goto cleanup;

    ASSERTERROR( lpAdrBook != NULL, "NULL lpAdrBook!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Support processing the in box messages using notification and event signals.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Create Inbox mutex (w/o ownership) for possible multiple accesses
    hInMutex = CreateMutex( NULL, FALSE, szInMutexName);
    if(hInMutex == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Create an event for signaling to check the inbox for messages to process.
    // This is a manual reset event that is initialy signaled.
    hChkInBoxEvent = CreateEvent( NULL, TRUE, TRUE, NULL);
    if( hChkInBoxEvent == NULL)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Register incoming queue event notification handler.
    hr = HrAllocAdviseSink( InBoxNotification, NULL, &lpInAdvise);
    if( FAILED( hr))
        goto cleanup;
    hr = MAPICALL(lpStore)->Advise( lpStore, cbInEntryID, lpInEntryID,
        fnevNewMail, lpInAdvise, &ulInConnection);
    if( FAILED( hr))
        goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Start configuration change notification engine.  This will cause the
// HrCfgChanged() function to be called immediately and then every time a change
// occurs.  The configuration data is stored by Exchange as extension data and
// it is maintained by SMBAdmin.DLL.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    hr = HrCfgCreateAdviseObj( lphSession, CFG_POLL_MSEC, &lpCfgAdviseObj);
    if( FAILED( hr))
        goto cleanup;

    hr = HrCfgAdvise( lpCfgAdviseObj, SMBBLOBNAME, &HrCfgChanged, NULL);
    if( FAILED( hr))
        goto cleanup;

// HrCfgChanged will open the appropriate Topics Folder and
// initialize the topic cache
ASSERTERROR( lpTopicsFolder != NULL, "NULL lpTopicsFolder!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    fIsInit = TRUE;

cleanup:

    ULRELEASE(lpInAdvise);

    MAPIFREEBUFFER(lpeid);
    MAPIFREEBUFFER(lpInEntryID);
    MAPIFREEBUFFER(lpRootEntryID);
    MAPIFREEBUFFER(lpNDREntryID);

    if( FAILED( hr))
{
        UninitSMBAGENT();

        SetServiceExitCode( ERROR_INTERNAL_ERROR, hr);

ServiceStop();
}

    RETURN(hr);
}

//$--HrServiceStartup----------------------------------------------------------
//  This function is called at startup to initialize the application.
//------------------------------------------------------------------------------
HRESULT HrServiceStartup(
    IN HINSTANCE    hInstance,      //  Handle of current instance
    IN HINSTANCE    hPrevInstance,  //  Handle of previous instance
    IN HWND         hwndMainWindow, //  Handle to Main window
    IN LPSTR        pszCmdLine)     //  Pointer to command line
{
    HRESULT         hr = NOERROR;

    DEBUGPUBLIC( "fNTServiceStartup()");

    hr = CHK_HrServiceStartup( hInstance, hPrevInstance, hwndMainWindow, pszCmdLine);
    if( FAILED( hr))
        RETURN( hr);

    // Save the instance value as a global variable
    hInst = hInstance;

    hServiceStopEvent = GetServiceStopEvent();

hr = HR_LOG(HrEventOpenLog(
    TEXT("SMBAGENT"), NULL, TEXT("EDKMSG.DLL"), NULL, NULL, NULL));

    //  Start message transfer.
    RETURN( HrInitSMBAGENT());
}

//$--ServiceMain--------------------------------------------------------------
// This function takes care of some service overhead and starts in box message
// processing.
//
// This is a thread process and keeps running until it is time to shut down.
//------------------------------------------------------------------------------
void ServiceMain(
    IN HANDLE NotUsed)  //  Handle to Shutdown event object
{
    HRESULT hr                      = NOERROR;
    DWORD   dwRc                    = 0;

    DEBUGPUBLIC( "ServiceMain()");
    hr = CHK_ServiceMain( NotUsed);
    if( FAILED( hr))
        goto cleanup;

    EventLogMsg( EDKEVENT_INFORMATION, 1, "Starting ServiceMain", 0);

    // We have a loop for processing messages so that
    // we can recover when the server goes down.
    while( TRUE)
    {
        // Check to see if user paused the service.
        hr = HrServiceProcessControl();
        if( FAILED( hr))
            goto cleanup;

        hr = HrProcessInBoxMessages();
        if( hr == MAPI_E_NETWORK_ERROR)
        {   // The server went down so we reset the program and wait for it to come back up.
            EventLogMsg( EDKEVENT_INFORMATION, 1, "Mailbox server is down.  Attempting to reconnect.", 0);

            UninitSMBAGENT();

            while( TRUE)
            {
                hr = HrInitSMBAGENT();
                if( SUCCEEDED( hr))
                    break;

                // Wait one minute or until administrator shuts us down.
                dwRc = WaitForSingleObject( hServiceStopEvent, 10000);
                switch( dwRc)
                {
                    case WAIT_TIMEOUT:
                        break;          // We are supposed to check the inbox.
                    case WAIT_OBJECT_0 + 1:
                        goto cleanup;   // We are supposed to shutdown.
                    default:
                        HR_LOG( E_FAIL);
                        goto cleanup;
                }
            }

            EventLogMsg( EDKEVENT_INFORMATION, 1, "Mailbox server came back up.", 0);
            continue;
        }

        // Exit loop after testing for error condition.
        if( FAILED( hr))
            goto cleanup;
        break;
    }

cleanup:
    // If an error occured we need to stop the service ourselves.
    if( FAILED( hr))
    {
        SetServiceExitCode( ERROR_INTERNAL_ERROR, hr);

        ServiceStop();
    }

    // Call this function to let winwrap know we are done.
    HrServiceConfirmStop();

    EventLogMsg( EDKEVENT_INFORMATION, 1, "Ending ServiceMain", 0);
    ExitThread( hr);
}

//$--HrServiceShutdown----------------------------------------------------------
//  This function is called to shutdown the application.
//------------------------------------------------------------------------------
HRESULT HrServiceShutdown (void)      // RETURNS:    Return value for WinMain
{
    //  Uninitialize...
    UninitSMBAGENT();

    (void) HrEventCloseLog();

    //  Return exit code for WinMain...
    return( NOERROR);
}

//------------------------------------------------------------------------------


SMBAGENT.H

// --smbagent.h-------------------------------------------------------------------
//
// SMBAGENT global definitions
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _SMBAGENT_H_
#define _SMBAGENT_H_

// -----------------------------------------------------------------------------

#define MAXSUBJLEN100 // Max.length of subject field strings
#define MAXTEXTLEN100// Max length of text strings
#defineMAXCMDLEN24// Max length of each command string

#define TPRINTF(x)_tprintf(TEXT(x))
#define TPRINTF1(x,y)_tprintf(TEXT(x),(y))
#define TPRINTF2(x,y,z)_tprintf(TEXT(x),(y),(z))

#define TOPIC_ROOT_FOLDER_NAME TEXT("List Server Topics")
#define TOPIC_ROOT_FOLDER_COMMENTTEXT("Root folder of all topic folders for the List Server.")

#define NDR_FOLDER_NAMETEXT("Undeliverable")
#define NDR_FOLDER_COMMENTTEXT("Undeliverable message folder.")

#define PROFILE_NAMETEXT("ListServer")
#define PROFILE_PASSWORD""

#define INTRO_MSG_CLASS             TEXT("IPM.NOTE.SMBAGENT.INTRO")
#define SMBAGENT_MSG_CLASS          TEXT("IPM.NOTE.SMBAGENT")

// -----------------------------------------------------------------------------
// The following macros and the SInitPropValue structure give the user the
// ability to easily create a property value array that is initialized with
// any property type and value.
//
// See usage example that follows.
// -----------------------------------------------------------------------------

#define MAKE_PROP_VALUE( cnt, ptr) ((LONGLONG) ((((LONGLONG) ((ULONG) (ptr))) << 32) + ((LONGLONG) ((ULONG) (cnt)))))
#define BINARY_PROP_VALUE( bin)  MAKE_PROP_VALUE( sizeof( bin), &bin)
#define MULTI_VALUE_PROP( array) MAKE_PROP_VALUE( ARRAY_CNT( array), array)

typedef struct
{
    ULONG    ulPropTag;
    ULONG    dwAlignPad;
    LONGLONG ll;
} SInitPropValue, *LPSInitPropValue;

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// Usage example:
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#ifdef  _USAGE_EXAMPLE_
#ifndef _USAGE_EXAMPLE_
    // Example of a binary structure that could be used by the property value array.
    typedef struct
    {
        TCHAR   ch[20];
        LPTSTR  lpsz;
        ULONG   ul;
        BOOL    flag;
    } ExampleStruct;

    // Example data that could go in the property value array.
    ExampleStruct BinData     = { TEXT("Binary Data"), TEXT( "A STRING"), 3752789, TRUE};
    LONG          LongArray[] = { 92314535, 2231223, 111, 424242312};

    // The example property value array.
    static SInitPropValue PropArray[] =
    {
        { PT_STRING8, 0, (ULONG) TEXT("Topic Folder")},
        { PT_LONG,    0, 0},
        { PT_BOOLEAN, 0, FALSE},
        { PT_BINARY,  0, BINARY_PROP_VALUE( BinData)},
        { PT_MV_LONG, 0, MULTI_VALUE_PROP( LongArray)},
    };
    LPSPropValue lpProps = (LPSPropValue) PropArray;

#endif
#endif

// -----------------------------------------------------------------------------
// These defines are indexes into the lpSenderProps array.  This array is filled
// by a call to HrGetSenderProps() found in MESSAGES.C
// -----------------------------------------------------------------------------

extern  LPSPropValue   lpSenderProps;  // Pointer to array of sender properties.
extern  LPADRLIST      lpSenderAddr;   // Pointer to sender's return address.

typedef enum
{
    IDX_SENDER_NAME = 0,
    IDX_SENDER_ENTRYID,
    IDX_SENDER_ADDRTYPE,
    IDX_SENDER_EMAIL_ADDRESS,
    IDX_MESSAGE_FLAGS,
    IDX_MESSAGE_CLASS,
    IDX_SUBJECT,

    // Leave this at the end of list.
    TOTAL_SENDER_PROPS
} SenderPropsIndexes;

// Returns a pointer to the sender's entry id as a SBinary structure.
__inline LPSBinary GetSenderEntryIdPtr()
{
    return( &(lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin));
}

// -----------------------------------------------------------------------------
// Global variables.
// -----------------------------------------------------------------------------

extern  LPMAPIFOLDER    lpInFolder;         // Pointer to INBOX folder
extern  LPMAPIFOLDER    lpNDRFolder;        // Pointer to the "Undeliverable" folder
extern  LPMAPIFOLDER    lpTopicsFolder;     // Pointer to the "TOPICS" folder
extern  LPADRBOOK       lpAdrBook;          // Pointer to Address Book
extern  HINSTANCE       hInst;
extern  TCHAR           szSubjBuf[];
extern  BOOL            bPublicTopic;
extern  DWORD           dwACLRights;

// -----------------------------------------------------------------------------
// Handle to a subscriber list
// -----------------------------------------------------------------------------

typedef struct
{
LPMESSAGElpIntroMsg; // intro message for requested topic folder
LPMAPITABLElpSubscriberTable;// table of recipients JOINed to the folder
}HSUBSCRIBERS, FAR * LPHSUBSCRIBERS;

// -----------------------------------------------------------------------------
// In SMBAGENT.C
// -----------------------------------------------------------------------------

HRESULT HrInitTopicFolders( VOID);

// -----------------------------------------------------------------------------
// Command handling procedures defined in COMMANDS.C
// -----------------------------------------------------------------------------

HRESULT HrProcessCommand(
IN LPMESSAGElpMessage,// Ptr to the current message object
IN LPENTRYLISTlpelMsgID,// Ptr to ENTRYLIST of current message
IN BOOL *pfDeleted);// Flag if msg.is deleted upon return

// -----------------------------------------------------------------------------
// Messaging procedures defined in MESSAGES.C
// -----------------------------------------------------------------------------

HRESULT HrGetSenderProps(
INLPMESSAGElpMessage);// Ptr to the msg.to read sender prop.from

HRESULT HrSendMessage(
INLPADRLISTlpRecipList,// Recipient list of the message
INLPTSTRlpszSubject,// Message Subject string
INLPTSTRlpszText, // Message text string
INintcNAttach, // #Attachments to be included
INLPMESSAGElpAtts[]);// Array of pointers to attachments

// Properties to copy when duplicating a message.
extern LPSPropTagArray lpPropsToCopyForKeep;    // Used when keeping in a folder.
extern LPSPropTagArray lpPropsToCopyForSend;    // Used when sending to a subscriber.

HRESULT HrDuplicateMessage(
    IN  LPMESSAGE       lpSrcMsg,           // pointer to the source message
    IN  LPMAPIFOLDER    lpDestFolder,       // pointer to the destination folder
    IN  ULONG           ulFlags,            // Intended to set MAPI_ASSOCIATED
    IN  LPSPropTagArray lpPropsToCopy,      // Pass in one of the above array pointers.
    OUT LPMESSAGE       *lppDestMsg);       // Addr of var.to contain ptr.to new msg.

HRESULT HrSendErrReport(
IN LPMESSAGElpMessage,// Ptr to message included in report
IN int iErrText);// Resource Id of error string

HRESULT HrSendConfirmReport(
IN LPMESSAGElpMessage,// Ptr to the command message
IN intiCmdName,// Resource Id of command name
IN int iCnfText,// Resource Id of confirm string
IN LPMESSAGElpAttach);

// -----------------------------------------------------------------------------
// Access control list functions defined in ACL.C.
// -----------------------------------------------------------------------------

HRESULT HrModifyACL(
    IN LPMAPIFOLDER lpFolder,       // Folder to modify ACL for
    IN LPTSTR pszUserName,          // Name of user to change
    IN ULONG cbUserEid,             // Byte count for User's entry id.
    IN LPENTRYID lpUserEid,         // User's entry id.
    IN BOOL fRemove,                // Flag indicating whether to remove rights
    IN DWORD lRights);              // New rights (ignored if fRemove)

// -----------------------------------------------------------------------------
// Include topic cache code and data definitions.
// -----------------------------------------------------------------------------

#include "Topics.h"
#include "TFldrAry.h"
#include "TopCache.h"

// -----------------------------------------------------------------------------

#endif //_SMBAGENT_H_


TFLDRARY.C

// --TFldrAry.C----------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic folder
// array for SMBAGENT.
//
// The array is essentialy a dynamicly expanding row set of properties of all
// topic folders.  Each row contains a Display Name and an Entry Id.  The array
// is sorted by display name.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

// Cause protected functions to be available.
#define FRIEND_OF_STOPIC
#define FRIEND_OF_STFOLDERARRAY
#define FRIEND_OF_STOPICCACHE

#include "smbagent.h"
#include "TFldrAry.CHK"

//$--Compare_DispName-----------------------------------------------------------
// Used by search routines to compare a search key to the display name which is
// the first property of a row in a SRowSet.
//
// This helper function is globaly available.
// RETURNS: -1 if Search Key < Display Name
//           0 if Search Key = Display Name
//           1 if Search Key > Display Name
// -----------------------------------------------------------------------------

PROTECTED int Compare_DispName( const void* lpszSrchKey, const void* lpRow)
{
    return( lstrcmpi( (LPTSTR) lpszSrchKey, ((LPSRow) lpRow)->lpProps->Value.LPSZ));
}

// -----------------------------------------------------------------------------
// This object contains an array of all folders.  The functions whose
// names that begin with "STFolderArray_" work on this object.
// -----------------------------------------------------------------------------

STFolderArray TFolderArray;

//$--STFolderArray_FindInsertionPt----------------------------------------------
// RECURSIVE binary search routine to find the insertion point for a folder.
// If the topic folder name already exists one or more times in the array the
// insertion point will be after the last item of the same name.
// RETURNS: the index to insert topic folder at.
// -----------------------------------------------------------------------------

static ULONG STFolderArray_FindInsertionPt(
    IN LPTSTR  lpszTFolderName, // The name of the topic folder you want to insert.
    IN ULONG   cRows,           // Can not be zero.
    IN LPSRow  lpRow)
{
    int     nCmp = 0;
    ULONG   iMid = cRows / 2;

    nCmp = Compare_DispName( lpszTFolderName, lpRow + iMid);
    if( nCmp < 0)
    {   // Topic folder name is less than topic folder row.
        if( iMid == 0)
            return( 0);
        return( STFolderArray_FindInsertionPt( lpszTFolderName, iMid, lpRow));
    }
    else
    {   // Topic folder name is greater than or equal to topic folder row.
        iMid ++;
        if( iMid == cRows)
            return( iMid);
        return( STFolderArray_FindInsertionPt( lpszTFolderName, cRows - iMid, lpRow + iMid) + iMid);
    }
}

//$--STFolderArray_HrInsert------------------------------------------------------
// Insert a new property value array in the row set of the topic folder array.
// This also adjusts the topic cache which has references to this array by index.
// -----------------------------------------------------------------------------

static HRESULT STFolderArray_HrInsert(
    IN ULONG        iTFolderArray,      // Index to insert a SRow object at.
    IN LPTSTR       lpszTFolderName,    // The name of the topic folder you want to insert.
    IN ULONG        cbEID,              // The count of bytes of the entry id.
    IN LPENTRYID    lpEID)              // The entry id of the topic folder you want to insert.
{
    HRESULT     hr          = NOERROR;
    ULONG       cBytes      = 0;
    LPBYTE      lpBuf       = NULL;
    LPSRow      lpRow       = NULL;
    ULONG       cExpand     = 10;   // Number of items to expand array when necessary.
    ULONG       cTFNameSize = 0;

    DEBUGPUBLIC( "STFolderArray_HrInsert()");
    hr = CHK_STFolderArray_HrInsert( iTFolderArray, lpszTFolderName, cbEID, lpEID);
    if( FAILED( hr))
        RETURN( hr);

    // Is the array big enough to insert a SRow?
    if( TFolderArray.lpRows->cRows == TFolderArray.cAllocatedRows)
    {   // NO, so expand it.

        // Allocate a new buffer for the row set.
        cBytes = CbNewSRowSet( TFolderArray.cAllocatedRows + cExpand);
        hr = MAPIAllocateBuffer( cBytes, &lpBuf);
        if( FAILED( hr) || !lpBuf)
        {
            hr = HR_LOG( E_OUTOFMEMORY);
            goto cleanup;
        }

        // Zero the new buffer.
        memset( lpBuf, 0, cBytes);

        // Was there any rows in the last allocation?
        if( TFolderArray.cAllocatedRows)
        {   // YES, so move the old buffer to the new one.
            cBytes = CbNewSRowSet( TFolderArray.cAllocatedRows);
            memmove( lpBuf, TFolderArray.lpRows, cBytes);
        }

        // Free the old buffer and use the new pointer.  Even if the last
        // alocation contained no rows there was minimum allocation for the
        // SRowSet structure with a count of zero.  We do NOT use FREEPROWS
        // since it would free each row which we just moved to our new buffer.
        MAPIFreeBuffer( TFolderArray.lpRows);
        TFolderArray.lpRows = (LPSRowSet) lpBuf;

        // Increase the maximum size of the array.
        TFolderArray.cAllocatedRows += cExpand;
    }

    // Allocate memory for the property value array.
    cTFNameSize = (lstrlen( lpszTFolderName) + 1) * sizeof(TCHAR);
    cBytes = sizeof( SPropValue) * 2 + cTFNameSize + cbEID;
    hr = MAPIAllocateBuffer( cBytes, &lpBuf);
    if( FAILED( hr) || !lpBuf)
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }

    // The address of the row we wish to place data at.
    lpRow = TFolderArray.lpRows->aRow + iTFolderArray;

    // Are we inserting in the middle of the array?
    if( iTFolderArray < TFolderArray.lpRows->cRows)
    {   // YES, so make space for the new entry.
        ULONG cBytes = (TFolderArray.lpRows->cRows - iTFolderArray) * sizeof( SRow);
        memmove( lpRow + 1, lpRow, cBytes);
        memset( lpRow, 0, sizeof( SRow));

        // Adjust the index in the topic cache that references any index >= iTFolderArray.
        STopicCache_AdjustIndex( iTFolderArray, 1);
    }

    // Increment the count of rows.
    TFolderArray.lpRows->cRows ++;

    // Initialize the SRow structure object.
    lpRow->cValues = 2;
    lpRow->lpProps = (LPSPropValue) lpBuf;
    lpBuf += sizeof( SPropValue) * 2;

    // Setup the PR_DISPLAY_NAME property.
    lpRow->lpProps[0].ulPropTag = PR_DISPLAY_NAME;
    lpRow->lpProps[0].Value.LPSZ = lpBuf;
    memmove( lpBuf, lpszTFolderName, cTFNameSize); // Copies trailing null as well.
    lpBuf += cTFNameSize;

    // Setup the PR_ENTRYID property.
    lpRow->lpProps[1].ulPropTag = PR_ENTRYID;
    lpRow->lpProps[1].Value.bin.cb = cbEID;
    lpRow->lpProps[1].Value.bin.lpb = lpBuf;
    memmove( lpBuf, lpEID, cbEID);

cleanup:
    RETURN( hr);
}

//$--STFolderArray_HrInit---------------------------------------------------------
// This MUST be called only once at the begining before using the topic folders
// or the topic cache functions.  Be sure to use STFolderArray_Destroy() when
// done with the array.
//
// Initialize the global topic folder array by filling it with the display name
// and entry id of all folders under the topics folder.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrInit()
{
    HRESULT     hr              = NOERROR;
    LPMAPITABLE lpTopicsTable   = NULL;

    SizedSPropTagArray( 2, PropTagArray) = { 2, { PR_DISPLAY_NAME, PR_ENTRYID}};
    SizedSSortOrderSet( 1, SortOrderSet) = { 1, 0, 0, { PR_DISPLAY_NAME, TABLE_SORT_ASCEND}};

    DEBUGPUBLIC( "STFolderArray_HrInit()");

    // Initialize to an empty array.
    TFolderArray.cAllocatedRows = 0;
    TFolderArray.lpRows = NULL;

    // Make sure the global lpTopicsFolder is initialized.
    if( !lpTopicsFolder)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Open an interface to a table that will give us a list of all topic folders.
    hr = MAPICALL( lpTopicsFolder)->GetHierarchyTable( lpTopicsFolder,
        MAPI_DEFERRED_ERRORS, &lpTopicsTable);
    if( FAILED( hr))
        goto cleanup;

    // Get all folder rows in a sorted order. If there were no rows we
    // still get a pointer to a SRowSet structure with a count of zero.
    hr = HrQueryAllRows( lpTopicsTable, (LPSPropTagArray) &PropTagArray, NULL,
        (LPSSortOrderSet) &SortOrderSet, 0, &TFolderArray.lpRows);
    if(FAILED( hr))
        goto cleanup;

    // Set the number of rows the current allocation of the array will hold.
    TFolderArray.cAllocatedRows = TFolderArray.lpRows->cRows;

cleanup:
    ULRELEASE( lpTopicsTable);
    RETURN( hr);
}

//$--STFolderArray_Find---------------------------------------------------------
// Find a topic folder in the global topic folder array.
// RETURNS: The index of the topic or NOT_FOUND.
// -----------------------------------------------------------------------------

ULONG STFolderArray_Find(
    IN LPTSTR lpszTFolderName)     // The name of the topic folder you want to find.
{
    LPBYTE lpTFolder = bsearch(
        lpszTFolderName,
        TFolderArray.lpRows->aRow,
        STFolderArray_GetCount(),
        sizeof( SRow),
        Compare_DispName);

    if( lpTFolder)
        return( (lpTFolder - ((LPBYTE) TFolderArray.lpRows->aRow)) / sizeof( SRow));
    return( NOT_FOUND);
}

//$--STFolderArray_HrCreateFolder------------------------------------------------
// Create a new topic folder and insert into the array so that the array remains
// sorted by PR_DISPLAY_NAME and we keep the entry id.  This will also place
// this folder in the topic cache with just the folder open.
//
// OUTPUT:  lppNewFolder    Open folder interface ptr.  Do NOT release this.
//          lppTopic        Open Topic cache object pointer.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrCreateFolder(
    IN  LPTSTR          lpszTFolderName,    // The name of the topic folder you want to create.
    OUT LPMAPIFOLDER*   lppNewFolder,       // Folder interface ptr to create.
    OUT STopic**        lppTopic)           // Open Topic cache object pointer.
{
    HRESULT      hr            = NOERROR;
    ULONG        iTFolderArray = NOT_FOUND;
    ULONG        cValues       = 0L;
    LPSPropValue lpProp        = NULL;       // Returned binary data

    static const SizedSPropTagArray(1,PropEID) = {1, {PR_ENTRYID}};

    DEBUGPUBLIC( "STFolderArray_HrCreateFolder()");
    hr = CHK_STFolderArray_HrCreateFolder( lpszTFolderName, lppNewFolder, lppTopic);
    if( FAILED( hr))
        RETURN( hr);

    // Initialize output ptrs to NULL in case of failure.
    *lppNewFolder = NULL;
    *lppTopic = NULL;

    // Create the MAPI folder.
    hr = MAPICALL( lpTopicsFolder)->CreateFolder( lpTopicsFolder,
        FOLDER_GENERIC,
        lpszTFolderName,
        lpszTFolderName,
        NULL,
        fMapiUnicode | OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,
        lppNewFolder);
    if( FAILED( hr))
        goto cleanup;

    ASSERT_IUNKNOWN_PTR( *lppNewFolder, "INVALID new folder pointer");

    hr = MAPICALL( *lppNewFolder)->GetProps( *lppNewFolder,
        (LPSPropTagArray)&PropEID,
        fMapiUnicode,
        &cValues,
        &lpProp);
    if( FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    ASSERTERROR( cValues != 0, "NO property values returned!");
    ASSERTERROR( lpProp->ulPropTag == PR_ENTRYID, "INVALID property tag returned!");

    // Find a slot in this array to insert this topic folder at.
    if( TFolderArray.lpRows->cRows == 0)
        iTFolderArray = 0;
    else
        iTFolderArray = STFolderArray_FindInsertionPt( lpszTFolderName,
            TFolderArray.lpRows->cRows, TFolderArray.lpRows->aRow);

    // Insert new topic folder info into the array and adjust the topic cache.
    hr = STFolderArray_HrInsert( iTFolderArray, lpszTFolderName,
        (ULONG)lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb);
    if( FAILED(hr) && hr != MAPI_E_NETWORK_ERROR)
    {
        if( FAILED( HrDeleteTopicFolder(
            (ULONG)lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb)));
        goto cleanup;
    }

    // Place just the folder interface ptr in the first topic slot of the cache.
    STopicCache_SetTopicFolder( iTFolderArray, *lppNewFolder, lppTopic);

cleanup:
    MAPIFREEBUFFER( lpProp);

    if( FAILED( hr))
        ULRELEASE( *lppNewFolder);

    RETURN( hr);
}

//$--STFolderArray_HrDeleteFolder-----------------------------------------------
// Deletes a topic folder from the MAPI store and the TFolderArray. This also
// removes it from the topic cache and adjusts the indexed references to this array.
//
// NOTE: Use STFolderArray_HrDeleteFolderSZ() if you only have the topic folder
//       name and not the index.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrDeleteFolder(
    IN ULONG iTFolderArray)     // Index of folder to be deleted.
{
    HRESULT     hr      = NOERROR;
    ULONG       cBytes  = 0;
    ULONG       cbEID   = 0;          // Count of bytes in Entry ID
    LPENTRYID   lpEID   = NULL;       // Pointer to Entry ID.
    LPSRow      lpRow   = NULL;

    DEBUGPUBLIC( "STFolderArray_HrDeleteFolder()");
    hr = CHK_STFolderArray_HrDeleteFolder( iTFolderArray);
    if( FAILED( hr))
        RETURN( hr);

    // Delete the folder from the MAPI store and any messages that are in it.
    // This must be done before freeing the buffer that contains the entry id.
    cbEID = STFolderArray_GetCbEID( iTFolderArray);
    lpEID = STFolderArray_GetEID( iTFolderArray);
    hr = HrDeleteTopicFolder( cbEID, lpEID);
    if( FAILED( hr))
        goto cleanup;

    // Remove the item from the cache and adjust the index in the
    // topic cache that references any index >= iTFolderArray.
    STopicCache_DeleteTopic( iTFolderArray);

    // The address of the row in the folder array we wish delete.
    lpRow = TFolderArray.lpRows->aRow + iTFolderArray;

    // Free the buffer that contains the properties for this row.
    MAPIFREEBUFFER( lpRow->lpProps);

    // We now have one less item in the array.
    TFolderArray.lpRows->cRows --;

    // Are we deleting from the middle of the array?
    if( iTFolderArray < TFolderArray.lpRows->cRows)
    {   // YES, so move all items up by one entry.
        cBytes = (TFolderArray.lpRows->cRows - iTFolderArray) * sizeof( SRow);
        memmove( lpRow, lpRow + 1, cBytes);
    }

    // Zero the memory of the old last item in the array.
    memset( TFolderArray.lpRows->aRow + TFolderArray.lpRows->cRows, 0, sizeof( SRow));

cleanup:
    RETURN( hr);
}

// -----------------------------------------------------------------------------


TFLDRARY.H

// --TFldrAry.h----------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic folder
// array for SMBAGENT.
//
// The array is essentialy a dynamicly expanding row set of properties of all
// topic folders.  Each row contains a Display Name and an Entry Id.  The array
// is sorted by display name.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _TFLDRARY_H_
#define _TFLDRARY_H_

#ifndef PROTECTED
#define PROTECTED
#endif

//$--STFolderArray--------------------------------------------------------------
// Topic folder array structure.  There will be only one instance of this object.
// -----------------------------------------------------------------------------

typedef struct
{
    ULONG       cAllocatedRows;   // Count of actual number of slots in lpRows->aRow[].
    LPSRowSet   lpRows;     // MAPI allocated buffer.
} STFolderArray;

#define NOT_FOUND (ULONG) ~0L   // Index when something is not found.

// Do NOT use this directly, use the helper or inline functions listed below.
extern STFolderArray TFolderArray;

// -----------------------------------------------------------------------------
// This MUST be called only once at the begining before using the topic folders
// or the topic cache functions.  Be sure to use STFolderArray_Destroy() when
// done with the array.
//
// Initialize the global topic folder array by filling it with the display name
// and entry id of all folders under the topics folder.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrInit();

// -----------------------------------------------------------------------------
// Find a topic folder in the global topic folder array.
// RETURNS: The index of the topic or NOT_FOUND.
// -----------------------------------------------------------------------------

ULONG STFolderArray_Find(
    IN LPTSTR lpszTFolderName);    // The name of the topic folder you want to find.

// -----------------------------------------------------------------------------
// Create a new topic folder and insert into the array so that the array remains
// sorted by PR_DISPLAY_NAME and we keep the entry id.  This will also place
// this folder in the topic cache with just the folder open.
//
// OUTPUT:  lppNewFolder    Open folder interface ptr.  Do NOT release this.
//          lppTopic        Open Topic cache object pointer.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrCreateFolder(
    IN  LPTSTR          lpszTFolderName,    // The name of the topic folder you want to create.
    OUT LPMAPIFOLDER*   lppNewFolder,       // Open folder interface ptr.
    OUT STopic**        lppTopic);          // Open Topic cache object pointer.

// -----------------------------------------------------------------------------
// Deletes a topic folder from the MAPI store and the TFolderArray. This also
// removes it from the topic cache and adjusts the indexed references to this array.
//
// NOTE: Use STFolderArray_HrDeleteFolderSZ() if you only have the topic folder
//       name and not the index.
// -----------------------------------------------------------------------------

HRESULT STFolderArray_HrDeleteFolder(
    IN ULONG iTFolderArray);     // Index of folder to be deleted.

// -----------------------------------------------------------------------------
// Topic folder array inline supporting "member" functions.  Since there is only
// one topic folder array we don't pass in a "this" type of pointer.
// -----------------------------------------------------------------------------

// Free the global topic folder array.
__inline void STFolderArray_Destroy()
{
    FREEPROWS( TFolderArray.lpRows);
    TFolderArray.cAllocatedRows = 0;
}

// Delete the topic folder taking the name of the folder as a string.
__inline HRESULT STFolderArray_HrDeleteFolderSZ( LPTSTR lpszTFolderName)
{
    return( STFolderArray_HrDeleteFolder( STFolderArray_Find( lpszTFolderName)));
}

// Return a count of the topic folders in the global topic folder array.
__inline ULONG STFolderArray_GetCount()
{
    return( TFolderArray.lpRows->cRows);
}

// Return the name of a topic folder based on its index.
__inline LPTSTR STFolderArray_GetName( ULONG iTFolderArray)
{
    if( iTFolderArray >= STFolderArray_GetCount())
        return( NULL);
    return( TFolderArray.lpRows->aRow[ iTFolderArray].lpProps[0].Value.LPSZ);
}

// -----------------------------------------------------------------------------
// Used only by the STopicCache and STopic objects.
// -----------------------------------------------------------------------------

#ifdef FRIEND_OF_STFOLDERARRAY

// Return the count of bytes of the entry id of a topic folder based on its index.
PROTECTED __inline ULONG STFolderArray_GetCbEID( ULONG iTFolderArray)
{
    if( iTFolderArray >= STFolderArray_GetCount())
        return( 0);
    return( TFolderArray.lpRows->aRow[ iTFolderArray].lpProps[1].Value.bin.cb);
}

// Return the entry id of a topic folder based on its index.
PROTECTED __inline LPENTRYID STFolderArray_GetEID( ULONG iTFolderArray)
{
    if( iTFolderArray >= STFolderArray_GetCount())
        return( 0);
    return( (LPENTRYID) TFolderArray.lpRows->aRow[ iTFolderArray].lpProps[1].Value.bin.lpb);
}

// Inline function to simplify deleting a topic folder.
PROTECTED __inline HRESULT HrDeleteTopicFolder( ULONG cbEID, LPENTRYID lpEID)
{
    return( MAPICALL( lpTopicsFolder)->DeleteFolder( lpTopicsFolder, cbEID, lpEID,
            0, NULL, DEL_MESSAGES | DEL_FOLDERS));
}

// -----------------------------------------------------------------------------
// Used by search routines to compare a search key to the display name which is
// the first property of a row in a SRowSet.
//
// This helper function is globaly available.
// RETURNS: -1 if Search Key < Display Name
//           0 if Search Key = Display Name
//           1 if Search Key > Display Name
// -----------------------------------------------------------------------------

PROTECTED int Compare_DispName( const void* lpszSrchKey, const void* lpRow);

#endif // FRIEND_OF_STFOLDERARRAY

// -----------------------------------------------------------------------------

#endif  //_TFLDRARY_H_


TOPCACHE.C

// --TopCache.C----------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic cache for
// SMBAGENT.
//
// See TOPCACHE.H for details on how the cache works and the relationship
// between the STFolderArray, the STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

// Cause protected functions to be available.
#define FRIEND_OF_STOPIC
#define FRIEND_OF_STOPICCACHE

#include "smbagent.h"
#include "TopCache.CHK"

// -----------------------------------------------------------------------------
// This object contains a cache of opened topics.  The functions whose
// names that begin with "STopicCache_" work on this object.
// -----------------------------------------------------------------------------

static STopicCache TopicCache;

//$--STopicCache_Search---------------------------------------------------------
// Search the cache for a folder with the given index.  Note that items in the
// cache are always changing slots.  What we are looking for is a topic object
// whose folder index matches the given index.
//
// RETURNS: The index in the cache of the topic or NOT_FOUND.
// -----------------------------------------------------------------------------

static ULONG STopicCache_Search(
    IN  ULONG       iTFolderArray)  // Index into TFolderArray of topic we want.
{
    ULONG   ii  = 0;

    // Search for the folder in the cache.
    for( ii = 0; ii < TopicCache.cTotItems; ii ++)
    {
        if( STopic_GetFolderIndex( TopicCache.lpTopicArray + ii) == iTFolderArray)
            return( ii);
    }
    return( NOT_FOUND);
}

//$--STopicCache_MoveSlotToTop--------------------------------------------------
// Move the item in the topic iSlot to the top of the cache and all other items
// down.
// -----------------------------------------------------------------------------

static void STopicCache_MoveSlotToTop(
    IN ULONG iSlot)             // Index of the slot to move to the top.
{
    STopic TempTopic;

    // Do we really need to move something?
    if( iSlot)
    {   // YES, so move slot from its present location to the top.

        // Copy the item in the iSlot into a temporary buffer.
        memmove( &TempTopic, TopicCache.lpTopicArray + iSlot, sizeof( STopic));

        // Move the first items down until iSlot is filled.
        memmove( TopicCache.lpTopicArray + 1, TopicCache.lpTopicArray, sizeof( STopic) * iSlot);

        // Copy the temporary buffer back into the top of the cache.
        memmove( TopicCache.lpTopicArray, &TempTopic, sizeof( STopic));
    }

    // Increment the usage count of the first item in the cache.
    STopic_IncUsageCnt( TopicCache.lpTopicArray);
}

//$--STopicCache_FreeFirstTopicSlot---------------------------------------------
// Frees the first slot in the cache to be used for a new topic.
// RETURNS: a pointer to the first slot in the cache.
// -----------------------------------------------------------------------------

PROTECTED STopic* STopicCache_FreeFirstTopicSlot()
{
    ULONG ii        = 0;
    ULONG iSlot     = NOT_FOUND;
    ULONG cLowestCnt= MAX_ULONG;
    ULONG cUsageCnt = 0;

    // First check to see if the cache is full.
    if( TopicCache.cTotItems < TopicCache.cMaxItems)
    {   // Cache is not full so move the first empty slot to the top and return it.
        STopicCache_MoveSlotToTop( TopicCache.cTotItems);
        TopicCache.cTotItems ++;
        return( TopicCache.lpTopicArray);
    }

    // Start at the drop zone of the cache and search to the end for the slot
    // whose item has had the least use.  If more than one item has the same
    // low usage count then the one with the highest index will be choosen.
    for( ii = TopicCache.iDropZone; ii < TopicCache.cTotItems; ii++)
    {
        cUsageCnt = STopic_GetUsageCnt( TopicCache.lpTopicArray + ii);
        if( cUsageCnt <= cLowestCnt)
        {   // Found the lowest usage and highest index up to this point.
            iSlot = ii;
            cLowestCnt = cUsageCnt;
        }
    }

    // Release and free everything associated with the topic.
    STopic_Release( TopicCache.lpTopicArray + iSlot);

    // Move the now empty item from iSlot to the top of the cache
    // and everything else down until it fills iSlot.
    STopicCache_MoveSlotToTop( iSlot);

    // Decrement the count of the last item in the cache.  This prevents an item
    // that got a burst of activity, accumulating a high usage count, and then was
    // never used again from remaining in the cache forever.
    STopic_DecUsageCnt( TopicCache.lpTopicArray + TopicCache.cTotItems - 1);

    // Return the pointer to the first slot.
    return( TopicCache.lpTopicArray);
}

//$--STopicCache_AdjustIndex----------------------------------------------------
// Adjust the index of all topic objects that contain an index that is greater
// than or equal to iTFolderArray.  This is used when a new folder in inserted
// into or deleted from the middle of the folder array.
// -----------------------------------------------------------------------------

PROTECTED void STopicCache_AdjustIndex(
    IN  ULONG       iTFolderArray,  // Index into TFolderArray of topic we want.
    IN  int         nAddSub)        // +1 indicates insert and -1 delete folder.
{
    ULONG ii        = 0;
    ULONG Index     = 0;

    for( ii = 0; ii < TopicCache.cTotItems; ii++)
    {
        Index = STopic_GetFolderIndex( TopicCache.lpTopicArray + ii);
        if( Index >= iTFolderArray)
            STopic_SetFolderIndex( TopicCache.lpTopicArray + ii, Index + nAddSub);
    }
}

//$--STopicCache_DeleteTopic----------------------------------------------------
// Remove the item from the cache and adjust the index in the topic cache that
// references any index >= iTFolderArray.
// -----------------------------------------------------------------------------

PROTECTED void STopicCache_DeleteTopic(
    IN  ULONG       iTFolderArray)  // Index into TFolderArray of topic we want to delete.
{
    STopic* lpTopic = NULL;
    ULONG   iSlot = 0;

    iSlot = STopicCache_Search( iTFolderArray);
    if( iSlot != NOT_FOUND)
    {   // The folder was found in the cache.
        lpTopic = TopicCache.lpTopicArray + iSlot;

        // Release all interfaces and free all buffers associated with this topic.
        STopic_Release( lpTopic);

        // There is now one less item in the cache.
        TopicCache.cTotItems --;

        // Make sure we really need to move something.
        if( iSlot < TopicCache.cTotItems)
        {   // YEP, we need to move cached items up to fill this now empty space.
            memmove( lpTopic, lpTopic + 1, sizeof( STopic) * (TopicCache.cTotItems - iSlot));

            // Initialize the last item to an empty state.
            STopic_Init( TopicCache.lpTopicArray + TopicCache.cTotItems);
        }
    }

    // Adjust all other folder indexes in the cache to reflect the deleted folder.
    STopicCache_AdjustIndex(  iTFolderArray, -1);
}

//$--STopicCache_HrInit---------------------------------------------------------
// Initialize the topic cache to an empty state.
// -----------------------------------------------------------------------------

HRESULT STopicCache_HrInit(
    IN  ULONG   cMaxItems, // The maximum number of topics the cache will hold.
    IN  ULONG   iDropZone)  // The starting index at which items become available
{                           // for removal from the cache.
    HRESULT hr          = NOERROR;
    ULONG   cBufSize    = 0;
    ULONG   ii          = 0;

    DEBUGPUBLIC( "STopicCache_HrInit()");
    hr = CHK_STopicCache_HrInit( cMaxItems, iDropZone);
    if( FAILED( hr))
        RETURN( hr);

    TopicCache.cMaxItems = cMaxItems;
    TopicCache.cTotItems = 0;
    TopicCache.iDropZone = iDropZone;

    // Allocate memory for the entire topic array.
    cBufSize = sizeof( STopic) * cMaxItems;
    hr = MAPIAllocateBuffer( cBufSize, &TopicCache.lpTopicArray);
    if( FAILED(hr) || !TEST_WRITE_PTR( TopicCache.lpTopicArray, cBufSize))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }

    // Initialize each item of array.
    for( ii = 0; ii < cMaxItems; ii ++)
        STopic_Init( TopicCache.lpTopicArray + ii);

cleanup:
    RETURN( hr);
}

//$--STopicCache_ReleaseAll-----------------------------------------------------
// Release all topic items.
// -----------------------------------------------------------------------------

void STopicCache_ReleaseAll()
{
    ULONG   ii  = 0;
    for( ii = 0; ii < TopicCache.cTotItems; ii ++)
        STopic_Release( TopicCache.lpTopicArray + ii);

    TopicCache.cTotItems = 0;
}

//$--STopicCache_GetTopic-------------------------------------------------------
// This function gets the topic associated with the folder specified by the
// iTFolderArray index.  If the topic is in the cache it moves it to the top and
// then returns it, otherwise it performs all the operations necessary to open
// the topic.
//
// NOTE: The data pointed to by the returned pointer will be different once this
//       function is called again for a new pointer.  In this implementation
//       it will always return a pointer to the first slot in the cache, but
//       don't depend upon it since the implementation could change.
//
// RETURNS: a pointer to the STopic object.
// -----------------------------------------------------------------------------

HRESULT STopicCache_HrGetTopic(
    IN  ULONG       iTFolderArray,  // Index into TFolderArray of topic we want.
    OUT STopic**    lppTopic)       // We return the topic ptr in this var.
{
    HRESULT hr      = NOERROR;
    ULONG   iSlot   = 0;

    DEBUGPUBLIC( "STopicCache_HrGetTopic()");
    hr = CHK_STopicCache_HrGetTopic( iTFolderArray, lppTopic);
    if( FAILED( hr))
        RETURN( hr);

    // Search for the topic folder in the cache.
    iSlot = STopicCache_Search( iTFolderArray);
    if( iSlot == NOT_FOUND)
    {   // Not found so we need to create an item in the cache for this topic.
        STopicCache_FreeFirstTopicSlot();
    }
    else
    {   // Found the topic folder in the cache so move it to the top.
        STopicCache_MoveSlotToTop( iSlot);
    }

    // Load only the topic member objects that are not currently loaded.
    hr = STopic_HrOpen( TopicCache.lpTopicArray, iTFolderArray);

    if( FAILED( hr))
        *lppTopic = NULL;
    else
        *lppTopic = TopicCache.lpTopicArray;
    return( hr);
}

// -----------------------------------------------------------------------------


TOPCACHE.H

// --TopCache.H----------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic cache for
// SMBAGENT.
//
// The cache is essentialy a fixed size array of open topic objects.  The first
// slot in the cache contains the most recently used topic and the last contains
// the oldest topic.
//
// Each topic item contains a count of the times it was used while in the cache.
// It also contains an index into the STFolderArray of the folder this topic
// belongs to.  This index is used to determine if a topic folder is in the
// cache.  The index is adjusted when an item is inserted or deleted in the
// STFolderArray.
//
// The cache has a DROP ZONE, which is at the end of the cache.  Both the size
// of the cache and the drop zone are adjustable by the user.  The drop zone is
// the area of the cache in which items are available for removal.  All slots
// above the drop zone are safe from being removed, thus giving them some time
// to accumulate a usage count.
//
// To decide which item to remove from the cache we start at the drop zone
// index and search to the end for the slot whose item has had the least use.
// If more than one item has the same low usage count then the one with the
// highest index will be choosen.
//
// When the cache is full the last item in the cache will have it's usage count
// decremented each time the cache is used.  This prevents an item that got a
// burst of activity, accumulating a high usage count, and then was never used
// again from remaining in the cache forever.
//
// The reason for a drop zone, as opposed to always tossing out the last item,
// is to give those items that are used a lot, but not always with in the time
// it takes to move from the top to the bottom of the cache, a chance to stick
// around long enough to get used again.
//
// NOTE: If you want the cache to always toss out the last item set the drop
//       zone index to the maximum cache size minus one.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _TOPCACHE_H_
#define _TOPCACHE_H_

#ifndef PROTECTED
#define PROTECTED
#endif

//$--STopicCache----------------------------------------------------------------
// Topic cache structure.  There will be only one instance of this object.
// -----------------------------------------------------------------------------

typedef struct
{
    ULONG   cMaxItems;      // Maximum items cache will hold.
    ULONG   cTotItems;      // Total items currently in the cache.
    ULONG   iDropZone;      // The starting index at which items become available
                            // for removal from the cache.
    STopic* lpTopicArray;   // The actual cache array.
} STopicCache;

// -----------------------------------------------------------------------------
// Initialize the topic cache to an empty state.
// -----------------------------------------------------------------------------

HRESULT STopicCache_HrInit(
    IN  ULONG   cMaxTopics, // The maximum number of topics the cache will hold.
    IN  ULONG   iDropZone); // The starting index at which items become available
                            // for removal from the cache.

// Release all topic items.
void STopicCache_ReleaseAll();

// -----------------------------------------------------------------------------
// This function gets the topic associated with the folder specified by the
// iTFolderArray index.  If the topic is in the cache it moves it to the top and
// then returns it, otherwise it performs all the operations necessary to open
// the topic.
//
// NOTE: The data pointed to by the returned pointer will be different once this
//       function is called again for a new pointer.  In this implementation
//       it will always return a pointer to the first slot in the cache, but
//       don't depend upon it since the implementation could change.
//
// RETURNS: a pointer to the STopic object.
// -----------------------------------------------------------------------------

HRESULT STopicCache_HrGetTopic(
    IN  ULONG       iTFolderArray,  // Index into TFolderArray of topic we want.
    OUT STopic**    lppTopic);       // We return the topic ptr in this var.

#ifdef FRIEND_OF_STOPICCACHE

// -----------------------------------------------------------------------------
// Frees the first slot in the cache to be used for a new topic.
// RETURNS: a pointer to the first slot in the cache.
// -----------------------------------------------------------------------------

PROTECTED STopic* STopicCache_FreeFirstTopicSlot();

// -----------------------------------------------------------------------------
// Adjust the index of all topic objects that contain an index that is greater
// than or equal to iTFolderArray.  This is used when a new folder in inserted
// into or deleted from the middle of the folder array.
// -----------------------------------------------------------------------------

PROTECTED void STopicCache_AdjustIndex(
    IN  ULONG       iTFolderArray,  // Index into TFolderArray of topic we want.
    IN  int         nAddSub);        // +1 indicates insert and -1 delete folder.

// -----------------------------------------------------------------------------
// Remove the item from the cache and adjust the index in the topic cache that
// references any index >= iTFolderArray.
// -----------------------------------------------------------------------------

PROTECTED void STopicCache_DeleteTopic(
    IN  ULONG       iTFolderArray);  // Index into TFolderArray of topic we want to delete.

// -----------------------------------------------------------------------------
// Free the first topic cache slot and place just the folder interface ptr in the slot.
// -----------------------------------------------------------------------------

PROTECTED __inline void STopicCache_SetTopicFolder(
    IN  ULONG           iTFolderArray, // Index into TFolderArray of topic we want.
    IN  LPMAPIFOLDER    lpNewFolder,   // An already open folder interface ptr.
    OUT STopic**        lppTopic)      // We return the topic ptr in this var.
{
    *lppTopic = STopicCache_FreeFirstTopicSlot();
    STopic_SetFolder( *lppTopic, iTFolderArray, lpNewFolder);
}

// -----------------------------------------------------------------------------

#endif //FRIEND_OF_STOPICCACHE
// -----------------------------------------------------------------------------

#endif // _TOPCACHE_H_


TOPICS.C

// --Topics.c-------------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic object for
// SMBAGENT.  A topic has a folder, an introductory message, and a list of
// subscribers.
//
// The list of subscribers are contained in the introductory
// message as the "TO" recipient list.  The introductory message is contained in
// the topic folder.  The topic folder is contained in a root topics folder.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

// Cause protected functions to be available.
#define FRIEND_OF_STOPIC
#define FRIEND_OF_STFOLDERARRAY

#include "smbagent.h"
#include "topics.CHK"

// -----------------------------------------------------------------------------

// Used to open the intro message.
static SizedSPropTagArray( 1, ptaIntroMsg) = { 1, {PR_ENTRYID}};
static LPSPropTagArray lpptaIntroMsg = (LPSPropTagArray) &ptaIntroMsg;

// Used to load all subscribers.
static SizedSPropTagArray( 6, ptaSubscriber) =
{ 6,
    {
        PR_DISPLAY_NAME,    // PT_STRING
        PR_ENTRYID,         // PT_BINARY   // for verifying an existing entry
        PR_ADDRTYPE,        // PT_STRING
        PR_EMAIL_ADDRESS,   // PT_STRING
        PR_RECIPIENT_TYPE,  // PT_LONG
        PR_ROWID,           // PT_LONG
    }
};
static LPSPropTagArray lpptaSubscriber = (LPSPropTagArray) &ptaSubscriber;

//$--STopic_HrOpenIntroMsg------------------------------------------------------
// Open the topic message in the already opened topic folder.
//
// The intro message has been stored in the MAPI_ASSOCIATED portion of the topic
// folder.  At the time we saved it we gave it a unique message class so that
// we could use the FindRow() function to locate it.  This is a quick opperation
// since very few items will ever be stored in this location.
//
// We chose this method rather than storing it in the main portion of the topic
// folder to avoid sorting the folder, which could get quite large.  Sorting a
// folder is an expensive opperation at the server end and should only be used
// when it is necessary.
// -----------------------------------------------------------------------------

static HRESULT STopic_HrOpenIntroMsg(
    THIS STopic* lpTopic)    // The topic object we are working on.
{
    HRESULT         hr          = NOERROR;
    ULONG           ulObjType   = 0;
    ULONG           cbEID       = 0;
    LPENTRYID       lpEID       = NULL;
    LPMAPITABLE     lpMsgTable  = NULL;
    LPSRowSet       lpRow       = NULL;

static SPropValue RestrictProp = { PR_MESSAGE_CLASS, 0L, { 0}};
static SRestriction Restrict   = { RES_PROPERTY, { RELOP_EQ, PR_MESSAGE_CLASS, (ULONG)&RestrictProp}};

    DEBUGPRIVATE ("STopic_HrOpenIntroMsg()");

    hr = CHK_STopic_THIS( lpTopic);
    if( FAILED( hr))
        RETURN( hr);

    RestrictProp.Value.LPSZ = INTRO_MSG_CLASS;

    // Get the contents table of the topic folder.
    hr = MAPICALL( lpTopic->lpFolder)->GetContentsTable( lpTopic->lpFolder,
        MAPI_ASSOCIATED | MAPI_DEFERRED_ERRORS,
        &lpMsgTable);
    if( FAILED( hr))
        goto cleanup;

    // Set the columns to return the entry id.
    hr = MAPICALL( lpMsgTable)->SetColumns( lpMsgTable,
        lpptaIntroMsg, TBL_BATCH);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Find just intro message.
    hr = MAPICALL( lpMsgTable)->Restrict( lpMsgTable,
        (LPSRestriction)&Restrict, TBL_BATCH);
    if( FAILED( hr))
{
hr = HR_LOG( E_FAIL);
        goto cleanup;
}

    // Get the intro message's EID.
    hr = MAPICALL( lpMsgTable)->QueryRows( lpMsgTable, 1, 0, &lpRow);
    if( FAILED( hr) || !lpRow || !lpRow->cRows)
    {   // If we don't get at least one message there is a problem with this topic.
        if( hr != MAPI_E_NETWORK_ERROR)
        {
            MODULE_ERROR1( "Intro message for topic '%s' not found!", STFolderArray_GetName(lpTopic->iTFolderArray));
            hr = HR_LOG( EDK_E_NOT_FOUND);
        }

        goto cleanup;
    }

    // Open the intro message for modification.
    cbEID = lpRow->aRow[0].lpProps->Value.bin.cb;
    lpEID = (LPENTRYID) (lpRow->aRow[0].lpProps->Value.bin.lpb);
    hr = MAPICALL( lpTopic->lpFolder)->OpenEntry( lpTopic->lpFolder,
        cbEID,
        lpEID,
        NULL,
        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
        &ulObjType,
        (LPUNKNOWN*)&lpTopic->lpIntroMsg);
    if( FAILED(hr) || ulObjType != MAPI_MESSAGE || !lpTopic->lpIntroMsg)
    {
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

cleanup:
    ULRELEASE(lpMsgTable);
    FREEPROWS(lpRow);
    RETURN( hr);
}

//$--STopic_HrReadSubscriberList-----------------------------------------------
// Reads the entire list of subscribers into a SRowSet structure.
// ----------------------------------------------------------------------------

static HRESULT STopic_HrReadSubscriberList(
    THIS STopic* lpTopic)    // The topic object we are working on.
{
    HRESULT     hr          = NOERROR;
    static BOOL fInRetryMode = FALSE;   // To recover from MAPI_E_CALL_FAILED.

    DEBUGPRIVATE ("STopic_HrReadSubscriberList()");

    hr = CHK_STopic_THIS( lpTopic);
    if( FAILED( hr))
        RETURN( hr);

    // Open the table of recipients.
    if( !lpTopic->lpSubTable)
    {
        hr = MAPICALL( lpTopic->lpIntroMsg)->GetRecipientTable( lpTopic->lpIntroMsg,
        MAPI_DEFERRED_ERRORS,
        &lpTopic->lpSubTable);
        if( FAILED(hr))
            goto cleanup;
    }

    // Read the entire list of subscribers into a row set structure.
    hr = HrQueryAllRows( lpTopic->lpSubTable, lpptaSubscriber, NULL, NULL, 0,
        &lpTopic->lpSubscriberRowSet);

    if( hr == MAPI_E_CALL_FAILED && !fInRetryMode)
    {   // Sometimes this call fails.  We can recover from this if we
        // reopen the intro message and try again.  WARNING: this is a
        // recursive call.
        ULRELEASE( lpTopic->lpSubTable);

        ULRELEASE( lpTopic->lpIntroMsg);
        hr = STopic_HrOpenIntroMsg( lpTopic);
        if( FAILED( hr))
            goto cleanup;

        fInRetryMode = TRUE;
        hr = STopic_HrReadSubscriberList( lpTopic);
        fInRetryMode = FALSE;
        if( FAILED( hr))
            goto cleanup;

// If previous call was successful, all currency pointers
// are already set.
        goto cleanup;
    }

    if( FAILED( hr) || !lpTopic->lpSubscriberRowSet ||
        (lpTopic->lpSubscriberRowSet && !lpTopic->lpSubscriberRowSet->cRows))
    {   // If we don't find at least one subscriber (topic creator) there is
        // a problem with the intro message.
        if( hr != MAPI_E_NETWORK_ERROR)
            hr = HR_LOG( EDK_E_NOT_FOUND);
        goto cleanup;
    }

    // Set subscriber currency pointers.
    lpTopic->lpCurSubscriber = NULL;
    lpTopic->lpLastSubscriber = lpTopic->lpSubscriberRowSet->aRow +
                                (lpTopic->lpSubscriberRowSet->cRows - 1);

cleanup:
    //ULRELEASE( lpTopic->lpSubTable);
    RETURN( hr);
}

//$--STopic_Init----------------------------------------------------------------
// Initialize a topic cache item, setting it to an empty state.
// -----------------------------------------------------------------------------

PROTECTED void STopic_Init(
    THIS STopic* lpTopic)   // The topic object we are working on.
{
    // Zero everything except the index which is set to NOT_FOUND.
    memset( lpTopic, 0, sizeof( STopic));
    lpTopic->iTFolderArray = NOT_FOUND;
}

//$--STopic_Release-------------------------------------------------------------
// Release and free any open interfaces or buffers in the topic cache object and
// re-initialize to an empty state.
// -----------------------------------------------------------------------------

PROTECTED void STopic_Release(
    THIS STopic* lpTopic)    // The topic object we are working on.
{
    lpTopic->iTFolderArray = NOT_FOUND;
    lpTopic->cUsageCnt = 0;
    ULRELEASE( lpTopic->lpFolder);
    ULRELEASE( lpTopic->lpIntroMsg);
    FREEPROWS( lpTopic->lpSubscriberRowSet);
    lpTopic->lpCurSubscriber = NULL;
    lpTopic->lpLastSubscriber = NULL;
    ULRELEASE( lpTopic->lpSubTable);
}

//$--STopic_HrOpen--------------------------------------------------------------
// Open all interface pointers and load the subscriber list for this topic cache
// item.  Some things may already be opened or loaded while others may not.
// -----------------------------------------------------------------------------

PROTECTED HRESULT STopic_HrOpen(
    THIS STopic* lpTopic,        // The topic object we are working on.
    IN   ULONG   iTFolderArray)  // Index into folder array used to initialize this topic.
{
    HRESULT     hr          = NOERROR;
    ULONG       ulObjType   = 0;
    ULONG       cbEID       = 0;
    LPENTRYID   lpEID       = NULL;
    LPMAPITABLE lpMsgTable  = NULL;
    LPSRowSet   lpRow       = NULL;

    DEBUGPUBLIC ("STopic_HrInit()");

    hr = CHK_STopic_HrInit( lpTopic, iTFolderArray);
    if( FAILED( hr))
        RETURN( hr);

    // If the topic already has a TFolderArray index verify
    // that it matches what we are trying to load now.
    if( lpTopic->iTFolderArray == NOT_FOUND)
        lpTopic->iTFolderArray = iTFolderArray; // Remember the index of this topic folder.
    else if( lpTopic->iTFolderArray != iTFolderArray)
    {   // This has not been released.
        hr = HR_LOG( E_FAIL);
        goto cleanup;
    }

    // Make sure the folder is not already opened.
    if( !lpTopic->lpFolder)
    {   // NO, it's not open so open the folder iterface for this topic.
        cbEID = STFolderArray_GetCbEID( iTFolderArray);
        lpEID = STFolderArray_GetEID( iTFolderArray);
        hr = MAPICALL( lpTopicsFolder)->OpenEntry( lpTopicsFolder, cbEID, lpEID, NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN FAR *) &lpTopic->lpFolder);

        if( FAILED( hr))
            goto cleanup;
    }

    // Make sure the introductory message is not already opened.
    if( !lpTopic->lpIntroMsg)
    {   //  NO, it's not open so open the introductory message.
        hr = STopic_HrOpenIntroMsg( lpTopic);
        if( FAILED( hr))
            goto cleanup;
    }

    // Make sure the subscriber list is not already loaded.
    if( !lpTopic->lpSubscriberRowSet)
    {   //  NO, it's not loaded so read the subscriber list into memory.
        hr = STopic_HrReadSubscriberList( lpTopic);
        if( FAILED( hr))
            goto cleanup;
    }

    // Reset the subscriber currency.
    lpTopic->lpCurSubscriber = NULL;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    if( FAILED( hr))
        STopic_Release( lpTopic);
    RETURN( hr);
}

//$--STopic_HrFindSubscriber----------------------------------------------------
// Find the sender as a subscriber in the topic object and places its index or
// NOT_FOUND in lpiSubscriber.
//
// RETURNS: HRESULT error only on failure. NOT_FOUND is NOT an error!
// -----------------------------------------------------------------------------

HRESULT STopic_HrFindSubscriber(
    THIS STopic* lpTopic,       // The topic object we are working on.
    OUT ULONG*   lpiSubscriber) // Index of subscriber.
{
    HRESULT   hr            = NOERROR;
    LPSBinary lpBinRow      = NULL;
    LPSBinary lpBinSrchKey  = GetSenderEntryIdPtr();
    LPSRow    lpRow         = lpTopic->lpSubscriberRowSet->aRow;
    ULONG     iSubscriber   = 0L;
    ULONG     fFoundAMatch  = FALSE;

    DEBUGPUBLIC ("STopic_HrFindSubscriber()");

    *lpiSubscriber = NOT_FOUND;

    // Search for the entry id in our row set.
    for( iSubscriber = 0;
         iSubscriber < STopic_GetSubscriberCount( lpTopic);
         iSubscriber++, lpRow++)
    {
        lpBinRow = &(lpRow->lpProps[IDX_ENTRYID].Value.bin);

        hr = MAPICALL( lpAdrBook)->CompareEntryIDs( lpAdrBook,
            lpBinSrchKey->cb, (LPENTRYID) lpBinSrchKey->lpb,
            lpBinRow->cb,     (LPENTRYID) lpBinRow->lpb, 0, &fFoundAMatch);
        if( FAILED( hr))
            goto cleanup;

        if( fFoundAMatch)
        {
            *lpiSubscriber = iSubscriber;
            goto cleanup;
        }
    }

cleanup:
    RETURN( hr);
}

//$--STopic_GetFirstSubscriber--------------------------------------------------
// RETURN the first subscriber's row of properties.
// -----------------------------------------------------------------------------

LPSRow STopic_GetFirstSubscriber(
    THIS STopic* lpTopic)        // The topic object we are working on.
{
    DEBUGPUBLIC ("STopic_GetFirstSubscriber()");

    if( !lpTopic->lpSubscriberRowSet)
        return( NULL);
    lpTopic->lpCurSubscriber = lpTopic->lpSubscriberRowSet->aRow;
    return( lpTopic->lpCurSubscriber);
}

//$--STopic_GetNextSubscriber---------------------------------------------------
// RETURN the next subscriber's row of properties.
// -----------------------------------------------------------------------------

LPSRow STopic_GetNextSubscriber(
    THIS STopic* lpTopic)        // The topic object we are working on.
{
    DEBUGPUBLIC ("STopic_GetNextSubscriber()");

    // Had we already reached past the end of the list?
    if( lpTopic->lpCurSubscriber)
    {   // NO. Then are we currently at the end of the list?
        if( lpTopic->lpCurSubscriber == lpTopic->lpLastSubscriber)
            lpTopic->lpCurSubscriber = NULL;    // YES, we have reached the end.
        else
            lpTopic->lpCurSubscriber ++;    // Nope, there are more subscribers.
    }
    return( lpTopic->lpCurSubscriber);
}

// -----------------------------------------------------------------------------
// Modifies the list of subscribers in the intro message and in memory.  You
// should use STopic_HrAddSubscriber() or STopic_HrRemoveSubscriber() and not
// this function directly.
//
// NOTE: The subscriber list will be invalid after this call.
// -----------------------------------------------------------------------------

HRESULT STopic_HrModifySubscriberList(
    THIS STopic*    lpTopic,    // The topic object we are working on.
    IN   LPADRLIST  lpAdrList,  // The address list to add or remove.
    IN   ULONG      ulFlags)    // MODRECIP_ADD or MODRECIP_REMOVE
{
    HRESULT hr = NOERROR;

    DEBUGPUBLIC ("STopic_HrModifySubscriberList()");

    // Add or Remove the entry to or from the subscriber list in the intro message.
    hr = MAPICALL( lpTopic->lpIntroMsg)->ModifyRecipients( lpTopic->lpIntroMsg,
        ulFlags, lpAdrList);
    if( FAILED( hr))
        goto cleanup;

    // Save changes to the message.
    hr = MAPICALL( lpTopic->lpIntroMsg)->SaveChanges( lpTopic->lpIntroMsg,
        FORCE_SAVE | KEEP_OPEN_READWRITE);
    if( FAILED(hr))
        goto cleanup;

    // Free the subscriber list so that next time we work with this
    // topic we will get a fresh list with updated row ids.
    STopic_FreeSubscriberList( lpTopic);

cleanup:
    RETURN( hr);
}

//$--STopic_HrDeleteSubscriber--------------------------------------------------
// Delete the subscriber from the recipient list of the Introductory message and
// then free the subscriber list so that next time we work with this topic we
// will get a fresh list with updated row ids.
//
// NOTE: The subscriber list will be invalid after this call.
// -----------------------------------------------------------------------------

HRESULT STopic_HrDeleteSubscriber(
    THIS STopic* lpTopic,            // The topic object we are working on.
    IN   ULONG   iSubscriber)        // The index of the subscriber to delete.
{
    HRESULT     hr              = NO_ERROR;
    ULONG       cBytes          = 0;
    LPADRLIST   lpAdrList       = NULL;
    LPSRow      lpSubscriberRow = NULL;

    DEBUGPUBLIC( "STopic_HrDeleteSubscriber()");
    hr = CHK_STopic_HrDeleteSubscriber( lpTopic, iSubscriber);
    if( FAILED( hr))
        RETURN( hr);

    // Allocate memory for the address list buffer.
    cBytes = CbNewADRLIST(1);
    hr = MAPIAllocateBuffer( cBytes, &lpAdrList);
    if( FAILED( hr))
    {
        hr = HR_LOG( E_OUTOFMEMORY);
        goto cleanup;
    }
    memset( lpAdrList, 0, cBytes);

    // Build an address list with one entry using the properties
    // found in the subscriber row passed in by the user.
    lpSubscriberRow = lpTopic->lpSubscriberRowSet->aRow + iSubscriber;
    lpAdrList->cEntries = 1;
    lpAdrList->aEntries[0].cValues  = lpSubscriberRow->cValues;
    lpAdrList->aEntries[0].rgPropVals = lpSubscriberRow->lpProps;

    // Remove the subscriber from intro msg and memory buffer.
    hr = STopic_HrModifySubscriberList( lpTopic, lpAdrList, MODRECIP_REMOVE);
    if( FAILED( hr))
        goto cleanup;

cleanup:
    // *************** W A R N I N G ! **************
    // Do NOT use FREEPADRLIST on this since it points to data (lpSubscriberRow->lpProps)
    // that will be freed in another way (or already has been freed).
    MAPIFREEBUFFER( lpAdrList);
    // *************** W A R N I N G ! **************

    RETURN( hr);
}

//$--STopic_GetCopyOfSubscriberRowSet-------------------------------------------
// Returns a copy of the subscriber's row set as andress list this MUST be freed
// using FREEPROWS when the user is done with it.
// -----------------------------------------------------------------------------

HRESULT STopic_GetCopyOfSubscriberRowSet(
    THIS STopic*    lpTopic,    // The topic object we are working on.
    OUT  LPADRLIST* lppAdrList) // Ptr to LPADRLIST that we will return.
{
    HRESULT         hr          = NOERROR;
    LPADRENTRY      lpAdrEntry  = NULL;         // Destination.
    LPSRow          lpRow       = NULL;         // Source.
    LPSRow          lpLastRow   = NULL;
    LPSPropValue    lpProps     = NULL;
    ULONG           cRows       = 0;
    ULONG           cBytes      = 0;
    ULONG           cBytesCopied= 0;

    DEBUGPUBLIC( "STopic_GetCopyOfSubscriberRowSet()");
    hr = CHK_STopic_GetCopyOfSubscriberRowSet( lpTopic, lppAdrList);
    if( FAILED( hr))
        RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Alocate enough memory for an address list to hold all subscribers.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    cRows = lpTopic->lpSubscriberRowSet->cRows;
    cBytes = CbNewADRLIST( cRows);
    hr = MAPIAllocateBuffer( cBytes, lppAdrList);
    if( FAILED( hr))
        goto cleanup;
    memset( *lppAdrList, 0, cBytes);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Loop through each subscriber row and copy the properties to a new buffer and
// a pointer to the new buffer into the address list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    lpAdrEntry = (*lppAdrList)->aEntries;       // Destination.
    lpRow = lpTopic->lpSubscriberRowSet->aRow;  // Source.
    lpLastRow = lpRow + (cRows - 1);
    while( lpRow <= lpLastRow)
    {
        // Count the bytes needed for the new property array.
        hr = ScCountProps( lpRow->cValues, lpRow->lpProps, &cBytes);
        if( FAILED( hr))
            goto cleanup;

        // Allocate a new buffer to hold the copied properties.
        hr = MAPIAllocateBuffer( cBytes, &lpProps);
        if( FAILED( hr))
            goto cleanup;
        memset( lpProps, 0, cBytes);

        // Copy the subscriber properties to the new buffer.
        hr = ScCopyProps( lpRow->cValues, lpRow->lpProps, lpProps, &cBytesCopied);
        if( FAILED( hr))
            goto cleanup;
        ASSERTERROR( cBytes == cBytesCopied, "ScCountProps & ScCopyProps are inconsistent!");

        // Set a single address list entry.
        (*lppAdrList)->cEntries ++;
        lpAdrEntry->cValues = lpRow->cValues;
        lpAdrEntry->rgPropVals = lpProps;
        lpProps = NULL;     // Just incase we bail out.

        // Move on to the next subscriber.
        lpRow ++;
        lpAdrEntry ++;
    }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cleanup:
    if( FAILED( hr))
        FREEPADRLIST( *lppAdrList);

    return( hr);
}

// -----------------------------------------------------------------------------


TOPICS.H

// --Topics.h-------------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic object for
// SMBAGENT.  A topic has a folder, an introductory message, and a list of
// subscribers.
//
// The list of subscribers are contained in the introductory
// message as the "TO" recipient list.  The introductory message is contained in
// the topic folder.  The topic folder is contained in a root topics folder.
//
// These functions all take as their first parameter a "THIS" pointer which is a
// pointer to the instance of the STopic object to work with.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#ifndef _TOPICS_H_

#ifdef THIS
#undef THIS
#endif
#define THIS    // Defined to do nothing but comment a function parameter.

#ifndef PROTECTED
#define PROTECTED
#endif

//$--STopic---------------------------------------------------------------------
// The following structure contains a single item of the topic cache.
// -----------------------------------------------------------------------------

typedef struct
{
    ULONG           iTFolderArray;      // Index into folder row set of this folder.
    ULONG           cUsageCnt;          // Number of times this topic was used while in the cache.
    LPMAPIFOLDER    lpFolder;           // Ptr to open folder interface.
    LPMESSAGE       lpIntroMsg;         // Ptr to open intro msg interface.
    LPSRowSet       lpSubscriberRowSet; // Ptr to row set of subscribers.
    LPSRow          lpCurSubscriber;    // Ptr to the current subscriber being accessed.
    LPSRow          lpLastSubscriber;   // Ptr to the last subscriber that can be accessed.
    LPMAPITABLE     lpSubTable;         // Ptr to subscriber table interface.
} STopic;

// -----------------------------------------------------------------------------
// Find the sender as a subscriber in the topic object and places its index or
// NOT_FOUND in lpiSubscriber.
//
// RETURNS: HRESULT error only on failure. NOT_FOUND is NOT an error!
// -----------------------------------------------------------------------------

HRESULT STopic_HrFindSubscriber(
    THIS STopic* lpTopic,       // The topic object we are working on.
    OUT ULONG*   lpiSubscriber);// Index of subscriber.

// -----------------------------------------------------------------------------
// Subscriber row functions and macros.
// -----------------------------------------------------------------------------

// RETURN the first subscriber's row of properties.
LPSRow STopic_GetFirstSubscriber( THIS STopic* lpTopic);

// RETURN the next subscriber's row of properties.
LPSRow STopic_GetNextSubscriber( THIS STopic* lpTopic);

// Indexes of subscriber properties.
typedef enum
{
    IDX_DISPLAY_NAME = 0,
    IDX_ENTRYID,
    IDX_ADDRTYPE,
    IDX_EMAIL_ADDRESS,
    IDX_RECIPIENT_TYPE,
    IDX_ROWID,
} SubscriberPropIndexes;

#define GetDispName()   lpProps[IDX_DISPLAY_NAME].Value.LPSZ      // USAGE: lpsz = lpRow->GetDispName();
#define GetEmailAddr()  lpProps[IDX_EMAIL_ADDRESS].Value.LPSZ     // USAGE: lpsz = lpRow->GetEmailAddr();

// -----------------------------------------------------------------------------
// Modifies the list of subscribers in the intro message and in memory.  You
// should use STopic_HrAddSubscriber() and not this function directly.
//
// NOTE: The Subscriber list will be invalid after this call.
// -----------------------------------------------------------------------------

HRESULT STopic_HrModifySubscriberList(
    THIS STopic*    lpTopic,    // The topic object we are working on.
    IN   LPADRLIST  lpAdrList,  // The address list to add or remove.
    IN   ULONG      ulFlags);   // MODRECIP_ADD or MODRECIP_REMOVE

// Use this to add a subscriber.
__inline HRESULT STopic_HrAddSubscriber( STopic* lpTopic, LPADRLIST lpAdrList)
{
    return( STopic_HrModifySubscriberList( lpTopic, lpAdrList, MODRECIP_ADD));
}

// -----------------------------------------------------------------------------
// Delete the subscriber from the recipient list of the Introductory message and
// then free the subscriber list so that next time we work with this topic we
// will get a fresh list with updated row ids.
// -----------------------------------------------------------------------------

HRESULT STopic_HrDeleteSubscriber(
    THIS STopic*    lpTopic,            // The topic object we are working on.
    IN   ULONG      iSubscriber);       // The index of the subscriber to delete.

//$--STopic_GetCopyOfSubscriberRowSet-------------------------------------------
// Returns a copy of the subscriber's row set as andress list this MUST be freed
// using FREEPROWS when the user is done with it.
// -----------------------------------------------------------------------------

HRESULT STopic_GetCopyOfSubscriberRowSet(
    THIS STopic*    lpTopic,    // The topic object we are working on.
    OUT  LPADRLIST* lppAdrList);// Ptr to LPADRLIST that we will return.

// -----------------------------------------------------------------------------
// The following are inline functions that opperate on the topic cache item.
// -----------------------------------------------------------------------------

// Returns the topic folder's interface pointer.
__inline LPMAPIFOLDER STopic_GetFolder( STopic* lpTopic)
{
    return( lpTopic->lpFolder);
}

// Returns the topic introduction message interface pointer.
__inline LPMESSAGE STopic_GetIntroMsg( STopic* lpTopic)
{
    return( lpTopic->lpIntroMsg);
}

// Call this to set only the message interface pointer of a topic object
// to the open introductory message.
__inline void STopic_SetIntroMsg( STopic* lpTopic, LPMESSAGE lpIntroMsg)
{
    lpTopic->lpIntroMsg = lpIntroMsg;
}

//  Returns the total number of subscribers to this topic.
__inline ULONG STopic_GetSubscriberCount( STopic* lpTopic)
{
    return( lpTopic->lpSubscriberRowSet->cRows);
}

// -----------------------------------------------------------------------------
// These functions are protected to be used only by STopicCache.
// -----------------------------------------------------------------------------

#ifdef FRIEND_OF_STOPIC

// Initialize a topic cache item, setting it to an empty state.
PROTECTED void STopic_Init( THIS STopic* lpTopic);

// Release and free any open interfaces or buffers in the topic cache object and
// re-initialize to an empty state.
PROTECTED void STopic_Release( THIS STopic* lpTopic);

// -----------------------------------------------------------------------------
// Open all interface pointers and load the subscriber list for this topic cache
// item.  Some things may already be opened or loaded while others may not.
// -----------------------------------------------------------------------------

PROTECTED HRESULT STopic_HrOpen(
    THIS   STopic*  lpTopic,        // The topic object we are working on.
    IN     ULONG    iTFolderArray); // Index into folder array used to initialize this topic.

// -----------------------------------------------------------------------------
// Protected inline functions.
// -----------------------------------------------------------------------------

// Returns the index into TFolderArray of the folder for this topic.
PROTECTED __inline ULONG STopic_GetFolderIndex( STopic* lpTopic)
{
    return( lpTopic->iTFolderArray);
}

// Call this to set the index of the topic within the TFolderArray.
PROTECTED __inline void STopic_SetFolderIndex( STopic* lpTopic, ULONG iTFolderArray)
{
    lpTopic->iTFolderArray = iTFolderArray;
}

// Returns the cUsageCnt.
PROTECTED __inline ULONG STopic_GetUsageCnt( STopic* lpTopic)
{
    return( lpTopic->cUsageCnt);
}

// Call this to increment the cUsageCnt.
PROTECTED __inline void STopic_IncUsageCnt( STopic* lpTopic)
{
    lpTopic->cUsageCnt ++;
}

// Call this to decrement the cUsageCnt.
PROTECTED __inline void STopic_DecUsageCnt( STopic* lpTopic)
{
    lpTopic->cUsageCnt --;
}

// Call this to set both the folder index and folder interface
// pointer of a topic object to an open folder interface.
PROTECTED __inline void STopic_SetFolder( STopic* lpTopic, ULONG iTFolderArray, LPMAPIFOLDER lpFolder)
{
    lpTopic->iTFolderArray = iTFolderArray;
    lpTopic->lpFolder = lpFolder;
}

// This will free the subscriber row set causing it to be reloaded the next time
// the topic is used.
PROTECTED __inline void STopic_FreeSubscriberList( STopic* lpTopic)
{
    FREEPROWS( lpTopic->lpSubscriberRowSet);
}

#endif // FRIEND_OF_STOPIC

// -----------------------------------------------------------------------------

#endif // _TOPICS_H_


CCMAIL.C

// --ccmail.c-----------------------------------------------------------------
//
//  Generate script for cc-mail address template
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

/*****************************************
                Constants
*****************************************/
// From MAPIDEFS.H
#define PROP_TAG(ulPropType,ulPropID)  ((((unsigned long)(ulPropID))<<16)|((unsigned long)(ulPropType)))
#define PT_STRING8      ((unsigned long) 30) /* Null terminated 8-bit character string */
#define PT_UNICODE      ((unsigned long) 31) /* Null terminated Unicode string */
#define PT_SYSTIME      ((unsigned long) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
#define PT_TSTRING         PT_UNICODE

#define PR_EMAIL_ADDRESSPROP_TAG( PT_STRING8,0x3003)

#define HALT    0L
#define ERRX    1L
#define EMIT    2L
#define JUMP    3L
#define JNX     4L
#define JE      5L

#define OP1_ADDR        0x80000000
#define OP2_ADDR        0x40000000

unsigned long  inst[400];  // Instruction Array
int            i = 0;      // Array index
char           *p;         // Array pointer
unsigned long  x;          // Terminator offset

/*********************
Write out a script file.
*********************/

void main()
   {
   FILE              *fOut;

   printf ("cc:Mail Address Template Script Generator\n");

   // EMIT the email address
   inst[i++] = EMIT;
   inst[i++] = PR_EMAIL_ADDRESS;

   // Done
   inst[i++] = HALT;
   p = (char *)&inst[i];               // Set the string pointer

   // Save the instructions to a file
   if( !(fOut = fopen( "ccmalscr.blt", "wb"))) { printf( "Can't create CCMALSCR.BLT\n"); return; }
   fwrite(inst, sizeof(char), p - (char *)inst, fOut);
   fclose(fOut);

   printf("Done\n");
   }


MACSCR.C

// --macscr.c-----------------------------------------------------------------
//
//  Generate script for Mac Mail address template
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

/*****************************************
                Constants
*****************************************/
// From MAPIDEFS.H
#define PROP_TAG(ulPropType,ulPropID)  ((((unsigned long)(ulPropID))<<16)|((unsigned long)(ulPropType)))
#define PT_STRING8      ((unsigned long) 30) /* Null terminated 8-bit character string */
#define PT_UNICODE      ((unsigned long) 31) /* Null terminated Unicode string */
#define PT_SYSTIME      ((unsigned long) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
#define PT_TSTRING         PT_UNICODE

#define HALT    0L
#define ERRX    1L
#define EMIT    2L
#define JUMP    3L
#define JNX     4L
#define JE      5L

#define OP1_ADDR        0x80000000
#define OP2_ADDR        0x40000000

#define PR_FIRST        PROP_TAG( PT_STRING8, 0x6701)
#define PR_LAST         PROP_TAG( PT_STRING8, 0x6702)

unsigned long  inst[400];  // Instruction Array
int            i = 0;      // Array index
char           *p;         // Array pointer
unsigned long  x;          // Terminator offset

/*********************
Write out a script file.
*********************/

void main()
   {
   int               s1;
   FILE              *fOut;

   printf ("Mac Mail Address Template Script Generator\n");

   // EMIT the First Half
   inst[i++] = EMIT;
   inst[i++] = PR_FIRST;

   // EMIT the Seperator
   inst[i++] = EMIT | OP1_ADDR;
   s1 = i++; // save the offset index

   // EMIT the Post Office Name
   inst[i++] = EMIT;
   inst[i++] = PR_LAST;

   // Done
   inst[i++] = HALT;

   x = i * sizeof(unsigned long);      // Save the seperator offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, "@") + 4) & ~3;    // Copy the seperator string

   inst[s1] = x;                       // Set the delimiter offset

   // Save the instructions to a file
   if( !(fOut = fopen( "macscr.blt", "wb"))) { printf( "Can't create MACSCR.BLT\n"); return; }
   fwrite(inst, sizeof(char), p - (char *)inst, fOut);
   fclose(fOut);

   printf("Done\n");
   }


PCMSCR.C

// --pcmscr.c-----------------------------------------------------------------
//
//  Generate script for MS Mail address template
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

/*****************************************
                Constants
*****************************************/
// From MAPIDEFS.H
#define PROP_TAG(ulPropType,ulPropID)  ((((unsigned long)(ulPropID))<<16)|((unsigned long)(ulPropType)))
#define PT_STRING8      ((unsigned long) 30) /* Null terminated 8-bit character string */
#define PT_UNICODE      ((unsigned long) 31) /* Null terminated Unicode string */
#define PT_SYSTIME      ((unsigned long) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
#define PT_TSTRING         PT_UNICODE

#define HALT    0L
#define ERRX    1L
#define EMIT    2L
#define JUMP    3L
#define JNX     4L
#define JE      5L
#define EMITUPPER    6L

#define OP1_ADDR        0x80000000
#define OP2_ADDR        0x40000000

#define PR_MAILBOX      PROP_TAG( PT_STRING8, 0x6701)
#define PR_POSTOFFICE   PROP_TAG( PT_STRING8, 0x6702)
#define PR_NETWORK      PROP_TAG( PT_STRING8, 0x6703)

unsigned long  inst[400];  // Instruction Array
int            i = 0;      // Array index
char           *p;         // Array pointer
unsigned long  x;          // Terminator offset

/*********************
Write out a script file.
*********************/

void main()
   {
   int               d1, d2;
   FILE              *fOut;

   printf ("MS Mail Address Template Script Generator\n");

   // EMIT the Network Name
   inst[i++] = EMIT;
   inst[i++] = PR_NETWORK;

   // EMIT the Delimiter
   inst[i++] = EMIT | OP1_ADDR;
   d1 = i++; // save the offset index

   // EMIT the Post Office Name
   inst[i++] = EMITUPPER;
   inst[i++] = PR_POSTOFFICE;

   // EMIT the Delimiter
   inst[i++] = EMIT | OP1_ADDR;
   d2 = i++; // save the offset index

   // EMIT the Mailbox Name
   inst[i++] = EMIT;
   inst[i++] = PR_MAILBOX;

   // Done
   inst[i++] = HALT;

   x = i * sizeof(unsigned long);      // Save the delimiter offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, "/") + 4) & ~3;    // Copy the delimiter string

   inst[d1] = x;                       // Set the delimiter offset
   inst[d2] = x;                       // Set the delimiter offset

   // Save the instructions to a file
   if( !(fOut = fopen( "pcmscr.blt", "wb"))) { printf( "Can't create PCMSCR.BLT\n"); return; }
   fwrite(inst, sizeof(char), p - (char *)inst, fOut);
   fclose(fOut);

   printf("Done\n");
   }


SDUMP.C

// --sdump.c------------------------------------------------------------------
//
//  Dump an address template
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

#define HALT    0L
#define ERRX    1L
#define EMIT    2L
#define JUMP    3L
#define JNX     4L
#define JE      5L
#define EMITUPPER    6L

#define OP1_ADDR        0x80000000
#define OP2_ADDR        0x40000000

#define NUL     0
#define OP      1
#define ADR     2
#define PROP    3

int            i;
unsigned long  *buf;

void instruction(char *s, int type1, int type2, int type3);
void operand(int type, int oper);

// Main routine
void main(int argc, char *argv[])
   {
   int      n;
   fpos_t   pos;
   FILE     *fh;

   if (argc != 2) { printf("Microsoft Exchange Script Dump Version 1.0\n\nUsage: SDUMP FILE\n"); exit(1); }

   // Read the script into the buffer
   strupr(argv[1]);
   if ((fh = fopen(argv[1], "rb")) == NULL) { printf("File \"%s\" not found!\n", argv[1]); exit(2); }
   fseek(fh, 0l, SEEK_END);   // seek to eof
   fgetpos(fh, &pos);         // get file length
   fseek(fh, 0l, SEEK_SET);   // seek to beginning
   n = (int)pos;              // file size
   buf = malloc(n);
   fread(buf, 1, n, fh) / sizeof(unsigned long);
   fclose(fh);

   printf("Script Dump of %s\n\n", argv[1]);

   // Step through the script
   while(i<n)
      {
      printf("%04X: ", i* sizeof(unsigned long));
      switch(buf[i] & 0xFL)
         {
         case HALT:  instruction("HALT", NUL, NUL, NUL); i = n; break;
         case ERRX:  instruction("ERRX", NUL, NUL, NUL); break;
         case EMIT:  instruction("EMIT",  OP, NUL, NUL); break;
         case JUMP:  instruction("JUMP", ADR, NUL, NUL); break;
         case JNX:   instruction("JNX",   OP, ADR, NUL); break;
         case JE:    instruction("JE",    OP,  OP, ADR); break;
         case EMITUPPER:  instruction("EMTU",  OP, NUL, NUL); break;
         default:    instruction("<UNKNOWN>", NUL, NUL, NUL); break;
         }
      printf("\n");
      }
   }

// Dump out an instruction
void instruction(char *s, int type1, int type2, int type3)
   {
   int   oper1, oper2;

   // Test the modifier bits
   if ((buf[i] & OP1_ADDR) == 0) oper1 = PROP; else oper1 = ADR;
   if ((buf[i] & OP2_ADDR) == 0) oper2 = PROP; else oper2 = ADR;

   printf("%-4s", s); i++;                   // Print the Instruction
   if (type1 != NUL) operand(type1, oper1);  // Print the first operand
   if (type2 != NUL) operand(type2, oper2);  // Print the second operand
   if (type3 != NUL) operand(type3, ADR);    // Print the third operand
   }

// Dump out an operand
void operand(int type, int oper)
   {
   // override the operand if the instruction type is an address
   if (type == ADR) oper = ADR;

   switch(oper)
      {
      case ADR:
         if (type == ADR) printf(" %08lX", buf[i]);
         else printf(" \"%s\"", (char *)buf + buf[i]);
         break;

      case PROP:
         printf(" %04lX+%04lX", buf[i] >> 16, buf[i] & 0xFFFF);
         break;
      }
   i++;  // Increment the instruction pointer
   }


X400SCR.C

// --X400scr.c----------------------------------------------------------------
//
//  Generate script for X.400 address template
//
//  Copyright (C) Microsoft Corp. 1986-1996.  All rights reserved.
//
// ---------------------------------------------------------------------------

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

/*****************************************
                Constants
*****************************************/

// From MAPIDEFS.H
#define PROP_TAG(ulPropType,ulPropID)  ((((unsigned long)(ulPropID))<<16)|((unsigned long)(ulPropType)))
#define PT_STRING8      ((unsigned long) 30) /* Null terminated 8-bit character string */
#define PT_UNICODE      ((unsigned long) 31) /* Null terminated Unicode string */
#define PT_SYSTIME      ((unsigned long) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
#define PT_TSTRING         PT_UNICODE

// From MAPITAGS.H
#define PR_MHS_COMMON_NAME                   PROP_TAG( PT_TSTRING,   0x3A0F)
#define PR_MHS_COMMON_NAME_W                 PROP_TAG( PT_UNICODE,   0x3A0F)
#define PR_MHS_COMMON_NAME_A                 PROP_TAG( PT_STRING8,   0x3A0F)

#define HALT    0L
#define ERRX    1L
#define EMIT    2L
#define JUMP    3L
#define JNX     4L
#define JE      5L

#define OP1_ADDR        0x80000000
#define OP2_ADDR        0x40000000

#define PR_GIVEN_NAME   PROP_TAG( PT_STRING8, 0x6701)
#define PR_SURNAME      PROP_TAG( PT_STRING8, 0x6702)
#define PR_INITIALS     PROP_TAG( PT_STRING8, 0x6703)
#define PR_GENERATION   PROP_TAG( PT_STRING8, 0x6704)
#define PR_COMMON_NAME  PROP_TAG( PT_STRING8, 0x6705)
#define PR_ORG          PROP_TAG( PT_STRING8, 0x6706)
#define PR_OU1          PROP_TAG( PT_STRING8, 0x6707)
#define PR_OU2          PROP_TAG( PT_STRING8, 0x6708)
#define PR_OU3          PROP_TAG( PT_STRING8, 0x6709)
#define PR_OU4          PROP_TAG( PT_STRING8, 0x670A)
#define PR_PRMD         PROP_TAG( PT_STRING8, 0x670B)
#define PR_ADMD         PROP_TAG( PT_STRING8, 0x670C)
#define PR_COUNTRY      PROP_TAG( PT_STRING8, 0x670D)
#define PR_DDAT1        PROP_TAG( PT_STRING8, 0x670E)
#define PR_DDAV1        PROP_TAG( PT_STRING8, 0x670F)
#define PR_DDAT2        PROP_TAG( PT_STRING8, 0x6710)
#define PR_DDAV2        PROP_TAG( PT_STRING8, 0x6711)
#define PR_DDAT3        PROP_TAG( PT_STRING8, 0x6712)
#define PR_DDAV3        PROP_TAG( PT_STRING8, 0x6713)
#define PR_DDAT4        PROP_TAG( PT_STRING8, 0x6714)
#define PR_DDAV4        PROP_TAG( PT_STRING8, 0x6715)
#define PR_NID          PROP_TAG( PT_STRING8, 0x6716)
#define PR_X121         PROP_TAG( PT_STRING8, 0x6717)
#define PR_TTY          PROP_TAG( PT_STRING8, 0x6718)
#define PR_TID          PROP_TAG( PT_STRING8, 0x6719)

unsigned long  inst[400];  // Instruction Array
int            i = 0;      // Array index
char           *p;         // Array pointer
unsigned long  dda;        // "dda:" offset
unsigned long  equals;     // "=" offset
unsigned long  semi;       // ";" offset
unsigned long  nul;        // "" offset

int   AddEMIT(unsigned long op);
void  AddText(int n, char *s);
int   AddDDAn(unsigned long typ, unsigned long val);
void  SetText(int n);

/*********************
Write out a script file.
*********************/

void main()
   {
   int               nG, nS, nI, nGQ, nCN, nO, nOU1, nOU2, nOU3, nOU4, nP, nA, nC;
   int               nDDA1, nDDA2, nDDA3, nDDA4;
   int               nNID, nX121, nTTY, nTID;
   FILE              *fOut;

   printf ("X.400 Address Template Script Generator\n");

   // Save the instructions
   nG    = AddEMIT(PR_GIVEN_NAME);
   nS    = AddEMIT(PR_SURNAME);
   nI    = AddEMIT(PR_INITIALS);
   nGQ   = AddEMIT(PR_GENERATION);
   nCN   = AddEMIT(PR_COMMON_NAME);
   nX121 = AddEMIT(PR_X121);
   nNID  = AddEMIT(PR_NID);
   nTTY  = AddEMIT(PR_TTY);
   nTID  = AddEMIT(PR_TID);
   nO    = AddEMIT(PR_ORG);
   nOU1  = AddEMIT(PR_OU1);
   nOU2  = AddEMIT(PR_OU2);
   nOU3  = AddEMIT(PR_OU3);
   nOU4  = AddEMIT(PR_OU4);
   nP    = AddEMIT(PR_PRMD);
   nA    = AddEMIT(PR_ADMD);
   nC    = AddEMIT(PR_COUNTRY);
   nDDA1 = AddDDAn(PR_DDAT1, PR_DDAV1);
   nDDA2 = AddDDAn(PR_DDAT2, PR_DDAV2);
   nDDA3 = AddDDAn(PR_DDAT3, PR_DDAV3);
   nDDA4 = AddDDAn(PR_DDAT4, PR_DDAV4);
   inst[i++] = HALT;

   // Save all the text
   dda = i * sizeof(unsigned long);    // Save the "dda:" terminator offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, "dda:") + 4) & ~3; // Copy the string
   i +=2;

   equals = i * sizeof(unsigned long); // Save the "=" offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, "=") + 4) & ~3;    // Copy the string
   i++;

   semi = i * sizeof(unsigned long);   // Save the ";" offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, ";") + 4) & ~3;    // Copy the string
   i++;

   nul = i * sizeof(unsigned long);    // Save the "" offset
   p = (char *)&inst[i];               // Set the string pointer
   p += (sprintf(p, "") + 4) & ~3;     // Copy the string

   AddText(nG,    "g=");
   AddText(nS,    "s=");
   AddText(nI,    "i=");
   AddText(nGQ,   "q=");
   AddText(nCN,   "cn=");
   AddText(nX121, "x.121=");
   AddText(nNID,  "n-id=");
   AddText(nTTY,  "t-ty=");
   AddText(nTID,  "t-id=");
   AddText(nO,    "o=");
   AddText(nOU1,  "ou1=");
   AddText(nOU2,  "ou2=");
   AddText(nOU3,  "ou3=");
   AddText(nOU4,  "ou4=");
   AddText(nP,    "p=");
   AddText(nA,    "a=");
   AddText(nC,    "c=");
   SetText(nDDA1);
   SetText(nDDA2);
   SetText(nDDA3);
   SetText(nDDA4);

   // Save the instructions to a file
   if( !(fOut = fopen( "x400scr.blt", "wb"))) { printf( "Can't create X400SCR.BLT\n"); return; }
   fwrite(inst, sizeof(char), p - (char *)inst, fOut);
   fclose(fOut);

   printf("Done\n");
   }

// *********************************************** Add a general EMIT set
int AddEMIT(unsigned long op)
   {
   int n;

   // Skip if the control is not dirty
   inst[i++] = JNX;                                // instruction op-code
   inst[i++] = op;                                 // property tag
   inst[i++] = (i + 11) * sizeof(unsigned long);   // jump address

#ifndef OLD
   // Skip if the data is null
   inst[i++] = JE | OP2_ADDR;                      // instruction op-code
   inst[i++] = op;                                 // property tag
   n = i++;                                        // save address -- literal string ("")
   inst[i++] = (i + 7) * sizeof(unsigned long);    // jump address
#else
   inst[i++] = JUMP;                               // jump
   inst[i++] = (i + 3) * sizeof(unsigned long);    // jump address
   n = i++;                                        // save address -- literal string ("")
   inst[i++] = 10L;
#endif

   // Write the tag string
   inst[i++] = EMIT | OP1_ADDR;                    // instruction op-code
   i++;                                            // literal string ("g=")

   // Write the control data
   inst[i++] = EMIT;                               // instruction op-code
   inst[i++] = op;                                 // property tag

   // Write the tag terminator
   inst[i++] = EMIT | OP1_ADDR;                    // instruction op-code
   i++;                                            // literal string (";")

   return(n);
   }

// *********************************** Add text and set the instruction offset
void  AddText(int n, char *s)
   {
   inst[n]   = nul;                 // Set the null string offset
   inst[n+3] = p - (char *)inst;    // Set the tag string offset
   inst[n+7] = semi;                // Set the semi-colon string offset
   p += (sprintf(p, s) + 4) & ~3;   // Append the string and adjust the pointer
   }

// ************************************************ Add a DDA EMIT set
int AddDDAn(unsigned long typ, unsigned long val)
   {
   int n;

   // JNZ instruction
   inst[i++] = JNX;                                // instruction op-code
   inst[i++] = typ;                                // skip if TYPE is empty
   inst[i++] = (i + 15) * sizeof(unsigned long);   // jump address

#ifndef OLD
   // Skip if the data is null
   inst[i++] = JE | OP2_ADDR;                      // instruction op-code
   inst[i++] = typ;                                // property tag
   n = i++;                                        // save offset -- literal string ("")
   inst[i++] = (i + 11) * sizeof(unsigned long);   // jump address
#else
   inst[i++] = JUMP;                               // jump
   inst[i++] = (i + 3) * sizeof(unsigned long);    // jump address
   n = i++;                                        // save address -- literal string ("")
   inst[i++] = 10L;
#endif

   // EMIT instruction for the "DDA:" tag
   inst[i++] = EMIT | OP1_ADDR;                    // instruction op-code
   i++;                                            // literal string ("dda:")

   // EMIT instruction for TYPE data
   inst[i++] = EMIT;                               // instruction op-code
   inst[i++] = typ;                                // property tag

   // EMIT instruction for the "="
   inst[i++] = EMIT | OP1_ADDR;                    // instruction op-code
   i++;                                            // literal string ("=")

   // EMIT instruction for VALUE data
   inst[i++] = EMIT;                               // instruction op-code
   inst[i++] = val;                                // property tag

   // EMIT instruction for ";"
   inst[i++] = EMIT | OP1_ADDR;                    // instruction op-code
   i++;                                            // literal string ("=")

   return(n);
   }

// ************************************************ Set DDA text
void  SetText(int n)
   {
   inst[n]     = nul;               // Set the "" offset
   inst[n+3]   = dda;               // Set the "dda:" offset
   inst[n+7]   = equals;            // Set the "=" offset
   inst[n+11]  = semi;              // Set the ";" offset
   }


FORM.CPP

// --form.cpp-----------------------------------------------------------------
//
//   Implementation of the FRM class
//
//   Implements a simple form that holds information about a software
//   testing/development tool.  This form can be installed in
//   a public folder, and then forms of this type can be placed in
//   the public folder to create a database of software tools.
//   Th form holds the following information:
//
//   * A topic, giving a brief description of the tool, which
//     appears as the subject in the folder list.
//   * Name of the tool
//   * The platform the tool runs on
//   * The type of tool
//   * The Operating System the tool runs under
//   * Who developed the tool
//   * The location of the tool
//   * An introduction, giving a longer description of the tool.
//
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

#include <ole2.h>
DEFINE_GUID(CLSID_MyFormsClsId,  0x861E4010, 0x5030, 0xFEDC, 0x99, 0x12, 0x00, 0x0a, 0x00, 0x38, 0x90, 0x1b);

#include "formdata.h"
#include "form.h"
#include "tooldoc.h"
#include "toolform.h"

HINSTANCE hinstOle   = NULL;
HINSTANCE hinstMapi  = NULL;

#ifdef WIN16
LPFNISEQUALGUID           lpfnIsEqualGUID;
#endif
LPFNCOREGISTERCLASSOBJECT lpfnCoRegisterClassObject;
LPFNHRQUERYALLROWS        lpfnHrQueryAllRows       ;
LPFNMAPIFREEBUFFER        lpfnMAPIFreeBuffer       ;
LPFNMAPIINITIALIZE        lpfnMAPIInitialize       ;
LPFNMAPIUNINITIALIZE      lpfnMAPIUninitialize     ;
FRMFAC *                  g_pfrmfac = NULL;

//$--HrStartOleAndRegisterClassFactory----------------------------------------
//
//  Purpose:
//      Initialize OLE, MAPI, and the Forms Interface
//      Should be called from WinMain() or InitApplication() in an SDI app
//
//      This function LoadLibraries the neccessary DLLs rather than
//      linking with them.  This permits the form to run as a stand-
//      alone executable even when MAPI and OLE are not installed.
//
//  Returns:
//      HRESULT
// ---------------------------------------------------------------------------
HRESULT HrStartOleAndRegisterClassFactory(void)
{
    HRESULT     hr      = NOERROR;

    // ----- LoadLibrary the essentials
    hinstOle   = LoadLibrary(szOleDll);
    hinstMapi  = LoadLibrary(szMapiDll);
    #ifdef WIN16
    if (hinstOle   < HINSTANCE_ERROR) hinstOle   = 0;
    if (hinstMapi  < HINSTANCE_ERROR) hinstMapi  = 0;
    #endif
    if (0 == hinstOle || 0 == hinstMapi)
    {
        return ResultFromScode(E_FAIL);
    }

    // ----- Setup a few function pointers
    lpfnCoRegisterClassObject = (LPFNCOREGISTERCLASSOBJECT) GetProcAddress(hinstOle,  "CoRegisterClassObject");
lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows");
if (NULL == lpfnHrQueryAllRows)
{
lpfnHrQueryAllRows    = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows@24");
}
#ifndef WIN32
    lpfnIsEqualGUID           = (LPFNISEQUALGUID          ) GetProcAddress(hinstOle,  "IsEqualGUID");
#endif
    lpfnMAPIFreeBuffer        = (LPFNMAPIFREEBUFFER       ) GetProcAddress(hinstMapi,"MAPIFreeBuffer");
    lpfnMAPIInitialize        = (LPFNMAPIINITIALIZE       ) GetProcAddress(hinstMapi,"MAPIInitialize");
    lpfnMAPIUninitialize      = (LPFNMAPIUNINITIALIZE     ) GetProcAddress(hinstMapi,"MAPIUninitialize");

    ASSERT(lpfnCoRegisterClassObject);
    ASSERT(lpfnHrQueryAllRows);
    ASSERT(lpfnMAPIFreeBuffer);
    ASSERT(lpfnMAPIInitialize);
    ASSERT(lpfnMAPIUninitialize);

    if (NULL == lpfnCoRegisterClassObject ||
        NULL == lpfnHrQueryAllRows        ||
        NULL == lpfnMAPIFreeBuffer        ||
        NULL == lpfnMAPIInitialize        ||
        NULL == lpfnMAPIUninitialize      )
    {
        return ResultFromScode(E_FAIL);
    }

    // ----- Initialize MAPI
    hr = lpfnMAPIInitialize(NULL);

    if (FAILED(hr))
    {
        return hr;
    }

    // ----- Allocate Memory for our class factory
    TRY
    {
        g_pfrmfac = new FRMFAC;
    }
    CATCH(CMemoryException, e)
    {
        hr = ResultFromScode(E_OUTOFMEMORY);
        return hr;
    }
    END_CATCH

    // ----- Register our class object(s)
    DWORD dwRegMyForm = 0;
    hr = lpfnCoRegisterClassObject(CLSID_MyFormsClsId, (LPUNKNOWN)g_pfrmfac,
            CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
            &dwRegMyForm); /* switch singleuse to multipleuse if you are an MDI app */

    return hr;
}

//$--HrStopForms--------------------------------------------------------------
//
//  Purpose:
//      UnInitialize OLE, MAPI, and the Forms Interface
//
//  Returns:
//      HRESULT == 0
// ---------------------------------------------------------------------------
HRESULT HrStopForms(void)
{
    HRESULT hr = ResultFromScode(S_OK);

    if (g_pfrmfac)
        delete g_pfrmfac;

    if (lpfnMAPIUninitialize)
        lpfnMAPIUninitialize();

    FreeLibrary(hinstOle);
    FreeLibrary(hinstMapi);

    return hr;
}

// Tool form specific methods follow ///////////////////////////

//$--FRM::FRM-----------------------------------------------------------------
//
//  Purpose:
//      Initialize or new form object
//
//----------------------------------------------------------------------------
FRM::FRM(REFCLSID clsid)
{
    LONG i;

    m_cRef = 1;
    m_clsid = clsid;

    m_pMessage = NULL;
    m_pMessageSite = NULL;
    m_pSession = NULL;

    m_pFormMgr = NULL;
    m_pFormInfo = NULL;

    m_fDirty = FALSE;

    for (i=0; i<MAX_ADVISE; i++)
    {
        m_aAdvisePtrs[i] = NULL;
        m_afAdvisee[i] = 0;
    }
}

//$--FRM::~FRM----------------------------------------------------------------
//
//  Purpose:
//      Destroy our form object
//
//----------------------------------------------------------------------------
FRM::~FRM()
{
    ASSERT(0 == m_cRef);
    ASSERT(NULL == m_pMessage);
    ASSERT(NULL == m_pMessageSite);
    ASSERT(NULL == m_pSession);
}

//$--FRM::SendForm------------------------------------------------------------
//
//
//  Purpose:
//      Have the message site send us
//      (also tries to send the message using mapi if message site fails)
//
//  Returns:
//      HRESULT             Error status.
//----------------------------------------------------------------------------
HRESULT FRM::SendForm(VOID)
{
    HRESULT hr = S_OK;

    ASSERT(m_cRef > 0);
    ASSERT(m_pMessageSite);
    ASSERT(m_pMessage);

    // ----- Submit message
    hr = m_pMessageSite->SubmitMessage(0);

    if (FAILED(hr))
    {
        return hr;
    }
    // ----- advise everyone of what we just did
    ADVISE(OnSubmitted);

    return hr;
}

//$--FRM::LaunchReplyMessage--------------------------------------------------
//
//  Purpose:
//      Construct a reply to PR_SENDER* (note: ignoring sent representing)
//      Display any form user interface on the existing form
//
//  Returns:
//      HRESULT             Error status.
//----------------------------------------------------------------------------
HRESULT
FRM::LaunchReplyMessage(ULONG ulhwndParent) // Parent window
{
    ULONG itaga;
    ADRLIST al = {1,0}; /* our adrlist will have exactly one entry */
    HRESULT hr = S_OK;
    LPMAPIMESSAGESITE pNewMessageSite;
    LPMAPIVIEWCONTEXT pNewMapiViewContext;
    LPMESSAGE pNewMessage;

    SizedSPropTagArray(6,tagaSender) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_ENTRYID,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_SEARCH_KEY } };
    SizedSPropTagArray(6,tagaRepliee) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_DISPLAY_NAME,
            PR_ADDRTYPE,
            PR_ENTRYID,
            PR_EMAIL_ADDRESS,
            PR_SEARCH_KEY
        } };
    static SizedSPropTagArray(26,tagaRemoveFromNewReply) =
        { 26,
        {   // Stuff you would typically want to remove on reply
            PR_MESSAGE_FLAGS,               // Want unsent compose note
            PR_MESSAGE_RECIPIENTS,          // Will generate new recip list
            PR_SENDER_ENTRYID,              // Clear sender/recipient info
            PR_SENDER_NAME,                 //
            PR_RECEIVED_BY_ENTRYID,         //
            PR_RECEIVED_BY_NAME,            //
            PR_SENT_REPRESENTING_ENTRYID,   // Clear delegate access stuff
            PR_SENT_REPRESENTING_NAME,      //
            PR_SENT_REPRESENTING_ADDRTYPE,  // 10961
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_RCVD_REPRESENTING_ENTRYID,   //
            PR_RCVD_REPRESENTING_NAME,      //
            PR_READ_RECEIPT_ENTRYID,        // Clear destination overrides
            PR_REPORT_ENTRYID,              //
            PR_REPLY_RECIPIENT_ENTRIES,     //
            PR_REPLY_RECIPIENT_NAMES,       //
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, // Clear delivery receipt
            PR_READ_RECEIPT_REQUESTED,      // Clear read receipt
            PR_CLIENT_SUBMIT_TIME,          // Clear submit time
            PR_MESSAGE_ATTACHMENTS,         // Drop attachments on reply
            PR_ORIGINAL_AUTHOR_ENTRYID,     // Keep original author information
            PR_ORIGINAL_AUTHOR_NAME,        //  on forwards
            PR_ORIGINAL_SUBMIT_TIME,        // Keep original time on forwards
            PR_IMPORTANCE,                  // Lose importance on reply
            PR_PRIORITY,                    // Lose priority on reply
            PR_SENSITIVITY                  // Lose sensitivity on reply
        } };

    ASSERT(m_cRef > 0);
    ASSERT(m_pMessageSite);
    ASSERT(m_pSession);
    ASSERT(m_pMessage);

    // ----- Create the reply message
    hr = m_pMessageSite->NewMessage(0,NULL,
        this, &pNewMessage,&pNewMessageSite,&pNewMapiViewContext);
    if (FAILED(hr))
    {
        return hr;
    }

    ASSERT(pNewMessage);
    ASSERT(pNewMessageSite);
    ASSERT(pNewMapiViewContext);

    // ----- Copy current message to new message
    hr = m_pMessage->CopyTo(0, NULL, (LPSPropTagArray)&tagaRemoveFromNewReply, 0, NULL,
                                    (LPIID) &IID_IMessage, pNewMessage, 0, NULL);
    if (FAILED(hr))
    {
        return hr;
    }

    // ----- who sent this to us?
    hr = m_pMessage->GetProps((LPSPropTagArray) &tagaSender, 0, &al.aEntries[0].cValues, &al.aEntries[0].rgPropVals);

    ASSERT(ResultFromScode(MAPI_W_ERRORS_RETURNED) == hr);

    // ----- Make the sender the recipient
    if (al.aEntries && al.aEntries[0].rgPropVals)
    {
        al.aEntries[0].rgPropVals[0].ulPropTag = PR_RECIPIENT_TYPE;
        al.aEntries[0].rgPropVals[0].Value.ul = MAPI_TO;
    }
    else
    {
        return ResultFromScode(E_FAIL);
    }

    // ----- Set our new recipients properties to their expected property ids
    itaga = 1;

    for (itaga = 1; itaga < tagaRepliee.cValues; itaga++)
    {
        al.aEntries[0].rgPropVals[itaga].ulPropTag =
            PROP_TAG(PROP_TYPE(al.aEntries[0].rgPropVals[itaga].ulPropTag),
                PROP_ID(tagaRepliee.aulPropTag[itaga]));

        ASSERT(SUCCEEDED(al.aEntries[0].rgPropVals[itaga].Value.ul));
    }

    // ----- Save out addresses
    ASSERT(1 == al.cEntries);
    hr = pNewMessage->ModifyRecipients(0, &al);
    if (FAILED(hr) )
    {
        return hr;
    }

    // ----- Call Load (this makes the current form the new form)
    hr = Load(pNewMessageSite,pNewMessage,0,MSGFLAG_UNSENT);
    if (FAILED(hr))
    {
        return hr;
    }

    // ----- Call DoVerb So we can see the reply form
    hr = DoVerb(OLEIVERB_PRIMARY,NULL,ulhwndParent,NULL);
    if (FAILED(hr))
    {
        return hr;
    }

    // ----- release stuff
    pNewMessage->Release();
    pNewMessageSite->Release();
    pNewMapiViewContext->Release();
    lpfnMAPIFreeBuffer(al.aEntries[0].rgPropVals);

    return hr;

}

//$--FRM::GetFormData---------------------------------------------------------
//
//  Purpose:
//      Allows anyone to query the form for it's current data
//
//  Returns:
//      void
//----------------------------------------------------------------------------
VOID FRM::GetFormData(CFormData& theLocalFormData)
{
    ASSERT(m_cRef > 0);

theLocalFormData = m_theFormData;
}

//$--FRM::SetFormData---------------------------------------------------------
//
//
//  Purpose:
//      Allows anyone to set the forms current data members
//
//  Returns:
//      void
//----------------------------------------------------------------------------
VOID FRM::SetFormData(const CFormData& theLocalFormData)
{
    ASSERT(m_cRef > 0);

m_theFormData = theLocalFormData;

    m_fDirty = TRUE;
}

//$--FRM::AddressForm---------------------------------------------------------
//
//  Purpose:
//      Add the address "Tools" to the "To:" list for the form.
//
//  Returns:
//      HRESULT Error Status.
//----------------------------------------------------------------------------
HRESULT
FRM::AddressForm(
         HWND hwnd,                     // parent
         BOOL fDontShowIfRecipsExist)   // true if no user interface should
                                        // be presented when recipients are
                                        // already present
{
    LPADRBOOKpAdrBook= NULL;
ULONGcbeid= 0;
LPENTRYIDlpeid= NULL;
    ULONGulUIParam = (ULONG) (UINT) hwnd;
    HRESULThr = S_OK;
    LPADRLISTpal = NULL;
LPSPropValuergAdrProps= NULL;

    ASSERT(m_cRef > 0);
    ASSERT(m_pMessageSite);
    ASSERT(m_pSession);
    ASSERT(m_pMessage);

    // ----- remember address book from the session
    hr = m_pSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &pAdrBook);
if (FAILED(hr))
    {
        goto cleanup;
    }

hr = MAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID FAR *)&pal);
if (FAILED(hr))
{
goto cleanup;
}

pal->cEntries = 1;

pal->aEntries[0].ulReserved1 = 0;
pal->aEntries[0].cValues = 2;
pal->aEntries[0].rgPropVals = NULL;

// Allocate memory for the recipient properties
hr = MAPIAllocateBuffer(2*sizeof(SPropValue), (LPVOID *)&rgAdrProps);

if (FAILED(hr))
{
printf("MAPIAllocateBuffer() failed\n");
goto cleanup;
}

pal->aEntries[0].rgPropVals = rgAdrProps;

rgAdrProps[0].ulPropTag = PR_DISPLAY_NAME;
rgAdrProps[0].Value.LPSZ = "Tools";

rgAdrProps[1].ulPropTag = PR_RECIPIENT_TYPE;
rgAdrProps[1].Value.l = MAPI_TO;

hr = pAdrBook->ResolveName(0,
   MAPI_DIALOG,
   NULL,
   pal);

if (FAILED(hr))
{
goto cleanup;
}

hr = m_pMessage->ModifyRecipients(MODRECIP_ADD, pal);

    // ----- Release the address book, adrlist, and clean up
cleanup:
    if (pAdrBook)
    {
        pAdrBook->Release();
    }
    if (pal)
    {
        lpfnMAPIFreeBuffer(pal->aEntries[0].rgPropVals);
        lpfnMAPIFreeBuffer(pal);
    }

    return hr;
}

//$--FRM::Remember------------------------------------------------------------
//
//  Purpose:
//      Store and addref the message site, the message, and the session
//      for later use
//
//  Returns:
//      HRESULT Error Status.
//----------------------------------------------------------------------------
HRESULT FRM::Remember(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg)
{
    HRESULT hr;

    ASSERT(m_cRef > 0);
    ASSERT(pmsite);
    ASSERT(pmsg);

    // ----- remember our message site object
    if(!m_pMessageSite)
    {
        m_pMessageSite = pmsite;
        m_pMessageSite->AddRef();
    }

    // ----- remember our message
    if(!m_pMessage)
    {
        m_pMessage = pmsg;
        m_pMessage->AddRef();
    }

    // ----- remember mapi session
    if(!m_pSession)
    {
        hr = m_pMessageSite->GetSession(&m_pSession);
    }

    return hr;

}

//$--FRM::Forget--------------------------------------------------------------
//
//  Purpose:
//      Release the message site, the message, and the session
//
//  Returns:
//      HRESULT Error Status.
//----------------------------------------------------------------------------
HRESULT FRM::Forget(VOID)
{
    if (m_pMessage)
        m_pMessage->Release();
    if (m_pMessageSite)
        m_pMessageSite->Release();
    if (m_pSession)
        m_pSession->Release();

    m_pMessage = NULL;
    m_pMessageSite = NULL;
    m_pSession = NULL;

    return NOERROR;
}

//$--FRM::ShowCurrentMessage--------------------------------------------------
//
//  Purpose:
//      Display any form user interface on a form
//
//  Returns:
//      HRESULT             Error status.
//----------------------------------------------------------------------------
HRESULT FRM::ShowCurrentMessage(ULONG ulhwndParent) // Parent window
{
    HRESULT hr = NOERROR;

ASSERT(m_cRef > 0);
    ASSERT(m_pMessageSite);
    ASSERT(m_pSession);
    ASSERT(m_pMessage);

    // ----- Give our user access to our form interface
if ((NULL != m_pMessageSite) && (NULL != m_pSession) && (NULL != m_pMessage))
{
g_pForm = this;

        g_pForm->AddRef();
}

    return hr;
}


FORM.H

// --form.h-------------------------------------------------------------------
//
//  Declaration for the FRMFAC and FRM classes
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

#ifndef _FORM_H
#define_FORM_H

#include <mapiform.h>
#define INITGUID
#include <initguid.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapinls.h>

#definekszFormsMessageName"IPM.ToolForm1"
#definekszAppName"Tool Form Sample"
#definekszAppLoadingTitle"Tool Form, Loading..."

HRESULT HrStartOleAndRegisterClassFactory(void);
HRESULT HrStopForms(void);

#define NOTPURE
#define MAX_ADVISE 48

#define CbNewFlagList(_cflag) \
(offsetof(FlagList,ulFlag) + (_cflag)*sizeof(ULONG))

/*
 *  Types
 */

// ----- class factory
class FRMFAC : public IClassFactory
{
public:
    FRMFAC();

    // *** IUnknown methods
    STDMETHOD(QueryInterface)   (THIS_ REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG,AddRef)    (THIS);
    STDMETHOD_(ULONG,Release)   (THIS);

    // *** IClassFactory methods
    STDMETHOD(CreateInstance)   (THIS_ LPUNKNOWN pUnkOuter, REFIID riid, LPVOID FAR* ppvObject);
    STDMETHOD(LockServer)       (THIS_ BOOL fLock);
protected:
    ULONG       m_cRef;
    CLSID       m_clsid;
};

// ----- The minimal interface a form can support
class FRM : public IPersistMessage, public IMAPIForm
{
  protected:
    ULONG                   m_cRef;               // Reference count
    CLSID                   m_clsid;

    BOOL                    m_fDirty;             // Is our message dirty?
    BOOL                    m_fSentMessage;       // Are we reading a sent message?

    // advise handling

    BOOL                    m_afAdvisee[MAX_ADVISE];
    LPMAPIVIEWADVISESINK    m_aAdvisePtrs[MAX_ADVISE];

    // This is all the data that is specific to this exact form.  To change
// the form, edit FormData.c and FormData.h which will be easier than
// adding data here.  Also, FormData.h will give instructions on what
// modifications are needed when data is added to a form.
CFormDatam_theFormData;

    ~FRM(void);

  public:
    LPMESSAGE               m_pMessage;
    LPMAPIMESSAGESITE       m_pMessageSite;
    LPMAPISESSION           m_pSession;

    LPMAPIFORMMGR           m_pFormMgr;
    LPMAPIFORMINFO          m_pFormInfo;

    FRM(REFCLSID clsid);

    MAPI_IUNKNOWN_METHODS(NOTPURE)
    MAPI_GETLASTERROR_METHOD(NOTPURE)
    MAPI_IPERSISTMESSAGE_METHODS(NOTPURE)
    MAPI_IMAPIFORM_METHODS(NOTPURE)

    HRESULT ShowCurrentMessage(ULONG ulhwndParent);
    HRESULT LaunchReplyMessage(ULONG ulhwndParent);
    VOID GetFormData(CFormData & theData);
    VOID SetFormData(const CFormData & theData);
    HRESULT Remember(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg);
    HRESULT Forget(VOID);
    HRESULT SendForm(VOID);
    HRESULT AddressForm(HWND hwnd, BOOL fDontShowIfRecipsExist);
};

/*
 *  Macros
 */

#define ADVISE(_func)                    \
{                                        \
   for (LONG i = 0; i < MAX_ADVISE; i++) \
      if (m_afAdvisee[i])                \
         m_aAdvisePtrs[i]->_func();      \
}

#define ReleaseObj(_pobj) \
        if (_pobj) \
                (_pobj)->Release()

/*
 *  Dependencies specific to this form
 */

#define PR_FORMDATAPROP_TAG( PT_BINARY,   0x6800)

typedef HRESULT (FAR PASCAL *LPFNCOREGISTERCLASSOBJECT)(
            REFCLSID rclsid,
            IUnknown FAR * pUnk,
            DWORD dwClsContext,
            DWORD flags,
            LPDWORD lpdwRegister);
#ifdef WIN16
typedef BOOL (FAR PASCAL *LPFNISEQUALGUID)(REFGUID rguid1, REFGUID rguid2);
#undef IsEqualIID
#define IsEqualIID(riid1, riid2) (*lpfnIsEqualGUID)(riid1, riid2)
#endif

typedef HRESULT (FAR PASCAL *LPFNHRQUERYALLROWS)(
            LPMAPITABLE ptable,
            LPSPropTagArray ptaga, LPSRestriction pres,
            LPSSortOrderSet psos, LONG crowsMax,
            LPSRowSet FAR *pprows);
typedef ULONG   (FAR PASCAL *LPFNMAPIFREEBUFFER)(LPVOID pv);
typedef HRESULT (FAR PASCAL *LPFNMAPIINITIALIZE)(LPVOID lpvReserved);
typedef void    (FAR PASCAL *LPFNMAPIUNINITIALIZE)(VOID);

#ifdef WIN32
#define szOleDll  "ole32.dll"
#define szMapiDll "mapi32.dll"
#else
#define szOleDll  "compobj.dll"
#define szMapiDll "mapi.dll"
#endif

#ifdef WIN16
extern LPFNISEQUALGUID           lpfnIsEqualGUID;
#endif
extern LPFNCOREGISTERCLASSOBJECT lpfnCoRegisterClassObject;
extern LPFNHRQUERYALLROWS        lpfnHrQueryAllRows       ;
extern LPFNMAPIFREEBUFFER        lpfnMAPIFreeBuffer       ;
extern LPFNMAPIINITIALIZE        lpfnMAPIInitialize       ;
extern LPFNMAPIUNINITIALIZE      lpfnMAPIUninitialize     ;

#endif


FORMDATA.CPP

// --formdata.cpp-------------------------------------------------------------
//
//   Implementation of the CFormData class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "formdata.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

//$--CFormData::CFormData-----------------------------------------------------
//
//Purpose:
//This is the constructor for the CFormData class.  This will set all the
//    default values for the form.
//
// ---------------------------------------------------------------------------
CFormData::CFormData()
{
ZeroMemory(&m_szTopic, sizeof(m_szTopic));
ZeroMemory(&m_szToolName,  sizeof(m_szToolName));
ZeroMemory(&m_szToolType, sizeof(m_szToolType));
ZeroMemory(&m_szPlatform, sizeof(m_szPlatform));
ZeroMemory(&m_szOS, sizeof(m_szOS));
ZeroMemory(&m_szDevelopedBy, sizeof(m_szDevelopedBy));
ZeroMemory(&m_szLocation, sizeof(m_szLocation));
ZeroMemory(&m_szIntroduction, sizeof(m_szIntroduction));
}

//$--CFormData::~CFormData----------------------------------------------------
//
//Purpose:
//This is the destructor for the CFormData class.  This will be useful to
//    deallocate any allocated memory.
//
// ---------------------------------------------------------------------------
CFormData::~CFormData()
{
}


FORMDATA.H

// --formdata.h---------------------------------------------------------------
//
//Declaration of the CFormData class
//
//Copyright (C) Microsoft Corporation, 1986-1996. All rights reserved.
//
// ---------------------------------------------------------------------------

#define MAX_STRING          64
#define MAX_DEV_BY          (2*64)
#define MAX_LOCATION        (5*64)
#define MAX_INTRODUCTION    (20*64)

class CFormData
{
public:
CFormData();
~CFormData();

// The form specific data.  These will map to the controls in the form.
char m_szTopic    [MAX_STRING];
char m_szToolName [MAX_STRING];
char m_szToolType [MAX_STRING];
char m_szPlatform [MAX_STRING];
char m_szOS       [MAX_STRING];
char m_szDevelopedBy  [MAX_DEV_BY];
char m_szLocation     [MAX_LOCATION];
char m_szIntroduction [MAX_INTRODUCTION];
};


FORMFACT.CPP

// --formfact.cpp-------------------------------------------------------------
//
// Implementation of the FRMFAC class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

#include <ole2.h>
#include <initguid.h>
#include <mapiform.h>
#define INITGUID
#include <initguid.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapinls.h>

// Tool form clsid.  This must match the configuration file.
DEFINE_GUID(CLSID_MyFormsClsId,  0x861E4010, 0x5030, 0xFEDC, 0x99, 0x12, 0x00, 0x0a, 0x00, 0x38, 0x90, 0x1b);

#include "formdata.h"
#include "form.h"
#include "tooldoc.h"
#include "toolform.h"

/*
 *  F o r m   C l a s s   F a c t o r y
 *
 *  Because we are an exe server, we must implement a class factory
 *  so that other viewers (like Exchange) can learn of our clsid
 *
 */

//$--FRMFAC::FRMFAC-----------------------------------------------------------
//
//  Purpose:
//      Creates a new form factory
//
//----------------------------------------------------------------------------
FRMFAC::FRMFAC()
{
    m_clsid = CLSID_MyFormsClsId;
    m_cRef = 1;
}

//$--FRMFAC::CreateInstance---------------------------------------------------
//
//  Purpose:
//      Creates a new form object of the IPM.Form class.
//
//  Returns:
//      HRESULT         S_OK, or one of the following errors:
//                      CLASS_E_NOAGGREGATION, E_OUTOFMEMORY,
//                      E_NOINTERFACE, E_INVALIDARG.
//----------------------------------------------------------------------------
STDMETHODIMP
FRMFAC::CreateInstance(
            LPUNKNOWN punkOuter,    // Outer object to aggregate with (not supported).
            REFIID riid,            // Desired interface on new form object.
            LPVOID FAR * ppvObject) // Where to put new form object.
{
    FRM *   pfrm = NULL;
    HRESULT hr = NOERROR;

    // ----- Initialize out parameter and check validity of parameters
    if (!ppvObject)
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto Cleanup;
    }
    *ppvObject = NULL;

    if (punkOuter)
    {
        hr = ResultFromScode(CLASS_E_NOAGGREGATION);
        goto Cleanup;
    }

    // ----- Instantiate new form
    TRY
    {
        pfrm = new FRM(m_clsid);
    }
    CATCH(CMemoryException, e)
    {
        hr = ResultFromScode(E_OUTOFMEMORY);
        goto Cleanup;
    }
    END_CATCH

    // ----- Get the desired interface
    hr = pfrm->QueryInterface(riid, ppvObject);

Cleanup:
    if (pfrm)
        ReleaseObj(pfrm);

    return hr;
}

//$--FRMFAC::QueryInterface---------------------------------------------------
//
//  Purpose:
//      Returns a pointer to the specified interface.
//
//  Returns:
//      HRESULT         Error status.
//----------------------------------------------------------------------------
STDMETHODIMP
FRMFAC::QueryInterface(
            REFIID riid,        // Interface we want.
            LPVOID FAR * ppvObj)// Interface we return.
{
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IClassFactory))
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }

    *ppvObj = NULL;

    return ResultFromScode(E_NOINTERFACE);
}

//$--FRMFAC::LockServer-------------------------------------------------------
//
//  Purpose:
//
//  Returns:
//      HRESULT         S_OK always.
//----------------------------------------------------------------------------
STDMETHODIMP
FRMFAC::LockServer(
            BOOL fLock) // Whether to increment or decrement DLL reference count.
{
    return NOERROR;
}

//$--FRMFAC::AddRef-----------------------------------------------------------
//
//  Purpose:
//      Increments reference count on the form class factory.
//
//  Returns:
//      ULONG           New value of reference count.
//----------------------------------------------------------------------------
STDMETHODIMP_(ULONG)
FRMFAC::AddRef(void)
{
    return ++m_cRef;
}

//$--FRMFAC::Release----------------------------------------------------------
//
//
//  Purpose:
//      Decrements reference count on the form class factory.
//      If count is decremented to zero, the object is freed.
//
//  Returns:
//      ULONG           New value of reference count.
//----------------------------------------------------------------------------
STDMETHODIMP_(ULONG) FRMFAC::Release(void)
{
    if (!(--m_cRef))
    {
        delete this;
        return 0;
    }
    return m_cRef;
}


FORMMAPI.CPP

// --formmapi.cpp-------------------------------------------------------------
//
//    Implementation of the FRM class MAPI methods
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#include "formdata.h"
#include "form.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

//$--FRM::DoVerb--------------------------------------------------------------
//
//  Purpose:
//      Performs the specified verb on the message.
//
//  Returns:
//      HRESULT             S_OK, or error value.
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::DoVerb(
         LONG iVerb,                // What to do.
         LPMAPIVIEWCONTEXT pmvc,    // Our view context.
         ULONG ulhwndParent,        // Our parent window.
         LPCRECT prcPosRect)        // Where we should display ourselves given
                                    // a choice.
{
    ASSERT(m_cRef > 0);

    switch (iVerb)
    {
    default:
    case OLEIVERB_HIDE:
    case OLEIVERB_DISCARDUNDOSTATE:
        return ResultFromScode(E_NOTIMPL);

    case OLEIVERB_UIACTIVATE:
    case OLEIVERB_INPLACEACTIVATE:
        if (iVerb < 0)
            return ResultFromScode(E_NOTIMPL);
        return ResultFromScode(OLEOBJ_S_INVALIDVERB);

    case OLEIVERB_SHOW:
        ShowCurrentMessage(ulhwndParent);
        return NOERROR;

    case OLEIVERB_OPEN:
    case OLEIVERB_PRIMARY:
        if (m_fSentMessage)
{
            LaunchReplyMessage(ulhwndParent);
}
        else
{
ShowCurrentMessage(ulhwndParent);
}

        return NOERROR;
    }
}

//$--FRM::ShutdownForm--------------------------------------------------------
//
//  Purpose:
//      Closes down any UI associated with the form.
//
//  Arguments:
//      DWORD
//
//  Returns:
//      HRESULT             S_OK, or error value.
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::ShutdownForm(
         DWORD dwSaveOptions) // One of OLECLOSE_SAVEIFDIRTY, OLECLOSE_NOSAVE,
                              // or OLECLOSE_PROMPTSAVE.
{
    ASSERT(m_cRef > 0);

    HRESULT hr = NOERROR;

    // ----- don't close if we're in a modal dialog
    //       especially if the modal dialog occurs in a remoted interface
if (!AfxGetMainWnd()->IsWindowEnabled())
        return ResultFromScode(E_ABORT);

    // ----- be kind, and save ourself
    switch (dwSaveOptions)
    {
        case OLECLOSE_NOSAVE:
            break;

        case OLECLOSE_SAVEIFDIRTY:
            if (m_fDirty)
                hr = m_pMessageSite->SaveMessage();
            break;

        default:
        case OLECLOSE_PROMPTSAVE:
            if (m_fDirty)
{
                if (IDYES == AfxMessageBox("Save changes?", MB_YESNO))
             {
                    hr = m_pMessageSite->SaveMessage();
}
}
            break;

    }

    if (NOERROR == hr)
    {
        // ----- let everyone know we're shutting down
        ADVISE(OnShutdown);

        // ----- Release everything we have remembered thus far
        Forget();

        // ----- make sure everyone has Unadvised
        ASSERT(0==m_afAdvisee[0]);
        ASSERT(0==m_afAdvisee[1]);
        ASSERT(0==m_afAdvisee[2]);
        ASSERT(0==m_afAdvisee[3]);

        // ----- post a quit message to our UI
        AfxGetMainWnd()->SendMessage(WM_CLOSE);
    }

    return hr;
}

//$--FRM::SetViewContext------------------------------------------------------
//
//  Puspose:
//      View context is used for next and previous behavior
//      The tools form does not do next and previous because
//      there is not a standard read note.  It is always in
//      reply mode.
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::SetViewContext(LPMAPIVIEWCONTEXT pViewContextNew)
{
    ASSERT(m_cRef > 0);
    ASSERT(pViewContextNew);

    return NOERROR;
}

//$--FRM::GetViewContext------------------------------------------------------
//
// Purpose:
//      Get the view context
//
//  Returns:
//      HRESULT
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::GetViewContext(LPMAPIVIEWCONTEXT FAR * ppViewContext)
{
    ASSERT(m_cRef > 0,);
    ASSERT(ppViewContext);

    if (ppViewContext)
        *ppViewContext = NULL;      // not supported

    return NOERROR;
}

//$--FRM::Advise--------------------------------------------------------------
//
//  Purpose:
//
//  Returns:
//      HRESULT
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::Advise(LPMAPIVIEWADVISESINK pAdvise, ULONG FAR * pdwStatus)
{
    LONG i = 0;

    ASSERT(m_cRef > 0);
    ASSERT(pdwStatus);
    ASSERT(pAdvise);

    // ----- remember who to advise
    for (i=0; i<MAX_ADVISE; i++)
    {
        if (!m_afAdvisee[i])
        {
            m_aAdvisePtrs[i] = pAdvise;
            m_afAdvisee[i] = 1;
            *pdwStatus = i + 1;     // ulConnection of zero is not valid
            pAdvise->AddRef();
            return NOERROR;
        }
    }

    // ----- bad news
    ASSERT(FALSE);
    return ResultFromScode(E_FAIL);
}

//$--FRM::Unadvise------------------------------------------------------------
//
//  Purpose:
//
//  Returns:
//      HRESULT
//----------------------------------------------------------------------------
STDMETHODIMP
FRM::Unadvise(ULONG ulConnection)
{
    ASSERT(m_cRef > 0);
    ASSERT(ulConnection < MAX_ADVISE && ulConnection >= 0);
    ASSERT(ulConnection);

    // ----- forget about advising this guy
    --ulConnection; // remember, we added one in advise

    ASSERT(m_afAdvisee[(int) ulConnection]);
    m_afAdvisee[(int) ulConnection] = 0;
    m_aAdvisePtrs[(int) ulConnection]->Release();

    return NOERROR;
}


FORMPRST.CPP

// --formprst.cpp-------------------------------------------------------------
//
//   Implementation of the FRM class IPersistMessage methods
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

#include "formdata.h"
#include "form.h"
#include "tooldoc.h"
#include "toolform.h"

//$--FRM::GetLastError--------------------------------------------------------
//
//  Purpose:
//      Get the last error
//
//  Returns:
//      HRESULT             NOERROR always.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::GetLastError(HRESULT hResult, ULONG ulFlags, LPMAPIERROR FAR * lppMAPIError)
{
    ASSERT(m_cRef > 0);

    if (lppMAPIError)
        *lppMAPIError = NULL;
    return NOERROR;
}

//$--FRM::GetClassID----------------------------------------------------------
//
//  Purpose:
//      Get the class ID associated with this message.
//
//  Returns:
//      HRESULT             NOERROR always.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::GetClassID(LPCLSID pclsid) // Where to put the class ID.
{
    ASSERT(m_cRef > 0);

    // The form only plays with things of its own class ID, so
    // this is easy; it's more complicated if code supports multiple
    // classes, or can do "treat as" operations
    if (pclsid)
        *pclsid = m_clsid;

    return NOERROR;
}

//$--FRM::IsDirty-------------------------------------------------------------
//
//  Purpose:
//      Returns whether the object has changed since the last save
//
//  Returns:
//      HRESULT             S_OK if dirty, S_FALSE if not dirty.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::IsDirty(void)
{
    ASSERT(m_cRef > 0);

    return m_fDirty ? NOERROR : ResultFromScode(S_FALSE);
}

//$--FRM::InitNew-------------------------------------------------------------
//
//  Purpose:
//      Create a new message of our message class in the provided pmsg.
//
//  Returns:
//      HRESULT             S_OK, or error value.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::InitNew(LPMAPIMESSAGESITE pmsite,
             LPMESSAGE pmsg)            // Message to create the new form in.
{
    HRESULT             hr              = NOERROR;
    SPropValue          prop            = {0};

    ASSERT(m_cRef > 0);

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
    {
        return hr;
    }

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT(kszFormsMessageName);
    hr = m_pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
    {
        return hr;
    }

    // ----- remind ourselves that this new message could not have been sent
    m_fSentMessage = 0;

    ADVISE(OnNewMessage);
    return hr;
}

//$--FRM::Load----------------------------------------------------------------
//
//  Purpose:
//      Attaches our object to the provided pmsg.
//
//  Returns:
//      HRESULT             S_OK, or error value.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::Load(LPMAPIMESSAGESITE pmsite,
          LPMESSAGE pmsg,           // Our message to remember
          ULONG ulMessageStatus,
          ULONG ulMessageFlags)
{
    ULONG cProps = 0;
    const ULONG ctagMax = 10;
    char rgchTaga[sizeof(SPropTagArray) + (ctagMax * sizeof(ULONG))] = {0};
    LPSPropTagArray ptaga = (LPSPropTagArray) rgchTaga;
    LPSPropValue rgval = NULL;
    LPSPropValue pval = NULL;
    HRESULT         hr=S_OK;

    ASSERT(m_cRef > 0);

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
    {
        return hr;
    }

    // ----- If this message has been sent we would like to remember that
    m_fSentMessage = !( ulMessageFlags & MSGFLAG_UNSENT);
    TRACE1("fSentMessage = %d",(int) m_fSentMessage);

    ptaga->cValues = 0;
    ptaga->aulPropTag[ptaga->cValues++] = PR_SUBJECT;
    ptaga->aulPropTag[ptaga->cValues++] = PR_BODY;
    ptaga->aulPropTag[ptaga->cValues++] = PR_FORMDATA;// Form Specific

    hr = pmsg->GetProps(ptaga, 0, &cProps, &rgval);
    if (FAILED(hr) )
    {
        return hr;
    }
    ASSERT(ptaga->cValues <= ctagMax);
    ASSERT(cProps == ptaga->cValues);
    ASSERT(NULL != rgval);

    // ----- set properties to variables

    // point pval at form-specific data
    pval = rgval + 2;

    POSITION pos = theApp.m_pDocTemplate->GetFirstDocPosition();
    ASSERT (pos);

    CToolDoc * pDoc = (CToolDoc*)theApp.m_pDocTemplate->GetNextDoc(pos);

    POSITION posVW = pDoc->GetFirstViewPosition();
    ASSERT (posVW);

    CToolForm * pView = (CToolForm*) pDoc->GetNextView (posVW);

    if (NULL != pView)
{
    if (pval->Value.bin.cb == sizeof(pDoc->m_theFormData))  // If the data is valid
        {
        memcpy(&(pDoc->m_theFormData), pval->Value.bin.lpb,(int) pval->Value.bin.cb);
    pval++; // CALLERSNAME
}

pView->PutDataIntoWindow();
}

    ASSERT(rgval);
    (*lpfnMAPIFreeBuffer)(rgval);
    ADVISE(OnNewMessage);

    return hr;
}

//$--FRM::Save----------------------------------------------------------------
//
//  Purpose:
//      Writes out our information to the provided pmsg. Does NOT commit
//      changes; this is the responsibility of the caller. Puts the form
//      into no-scribble mode until SaveCompleted is called.
//
//  Returns:
//      HRESULT             S_OK, or error value.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::Save(
         LPMESSAGE pmsg,    // Message to write our changes to.
         ULONG fSameAsLoad) // TRUE if this is our home message, FALSE if
                            // this is a different message.
{
    SPropValue prop = {0};
    HRESULT hr = NOERROR;

    ASSERT(m_cRef > 0);
    ASSERT(m_pMessage);

    // ----- If this is the same pmsg as we got back when we loaded ...
    if (fSameAsLoad)
    {
        pmsg = m_pMessage;
    }

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT(kszFormsMessageName);
    hr = m_pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
    {
        return hr;
    }

    POSITION pos = theApp.m_pDocTemplate->GetFirstDocPosition();
    ASSERT (pos);

    CToolDoc * pDoc = (CToolDoc*)theApp.m_pDocTemplate->GetNextDoc(pos);

    if (NULL != pDoc)
{
//This will set the Message body to a specific text value in the form.
//This will be dependent on the specific form.  The intended use of this
//is when a receiver or the form does not have access to the form, then
//he/she will view a text version of the form data.  This is the text
//version.
prop.ulPropTag  = PR_BODY;
prop.Value.lpszA = TEXT("Sorry, no text equivalent of tool form");
hr = m_pMessage->SetProps(1, &prop, NULL);
if (FAILED(hr) )
{
    return hr;
}

prop.ulPropTag = PR_SUBJECT;
prop.Value.LPSZ = TEXT(pDoc->m_theFormData.m_szTopic);

hr = m_pMessage->SetProps(1, &prop, NULL);
if (FAILED(hr))
{
   return hr;
}

    prop.ulPropTag  = PR_FORMDATA;
    prop.Value.bin.lpb = (unsigned char *) &(pDoc->m_theFormData);
    prop.Value.bin.cb  = sizeof(pDoc->m_theFormData);
    hr = m_pMessage->SetProps(1, &prop, NULL);
if (FAILED(hr))
{
        return hr;
    }
}
    ADVISE(OnSaved);
    return hr;
}

//$--FRM::SaveCompleted-------------------------------------------------------
//
//  Purpose:
//      Terminates no-scribble and hands-off modes, returning the object
//      to its normal storage mode.
//
//  Returns:
//      HRESULT             S_OK, or error value.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::SaveCompleted(LPMESSAGE pmsg)  // Our new home message, if we need to change.
{
    ASSERT(m_cRef > 0);

    // Reset modes
    m_fDirty = FALSE;

    return NOERROR;
}

//$--FRM::HandsOffMessage-----------------------------------------------------
//
//  Purpose:
//      Releases our reference on the message so that a Save As operation
//      can occur.
//
//  Returns:
//      HRESULT             S_OK, or error value.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::HandsOffMessage(void)
{
    ASSERT(m_cRef > 0);
    return NOERROR;
}


FORMUNK.CPP

// --formunk.cpp--------------------------------------------------------------
//
//   Implementation of the FRM IUnknown methods
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#include "formdata.h"
#include "form.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

//$--FRM::QueryInterface------------------------------------------------------
//
//  Purpose:
//      Returns a pointer to the specified interface.
//
//  Returns:
//      HRESULT             Error status.
// ---------------------------------------------------------------------------
STDMETHODIMP
FRM::QueryInterface(
         REFIID riid,           // Interface wanted.
         LPVOID FAR * ppvObj)   // Interface we return.
{
    HRESULT hr = NOERROR;

    ASSERT(m_cRef > 0);
    if (IsEqualIID(riid, IID_IUnknown))
    {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
    }
    else if (IsEqualIID(riid, IID_IPersistMessage))
    {
        AddRef();
        *ppvObj = (IPersistMessage *)this;
    }
    else if (IsEqualIID(riid, IID_IMAPIForm))
    {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
    }
    else
    {
        hr = ResultFromScode(E_NOINTERFACE);
        *ppvObj = NULL;
    }

    return hr;
}

//$--FRM::AddRef--------------------------------------------------------------
//
//  Purpose:
//      Increments reference count on the sample extension.
//
//  Returns:
//      ULONG               New value of reference count.
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG) FRM::AddRef(void)
{
    ASSERT(m_cRef > 0);
    return ++m_cRef;
}

//$--FRM::Release-------------------------------------------------------------
//
//  Purpose:
//      Decrements reference count on the sample extension.  If count is
//      decremented to zero, the object is freed.
//
//  Returns:
//      ULONG               New value of reference count.
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG) FRM::Release(void)
{
    ASSERT(m_cRef > 0);
    if (!(--m_cRef))
    {
        delete this;
        return 0;
    }
    return m_cRef;
}


MAINFRM.CPP

// --mainfrm.cpp--------------------------------------------------------------
//
//   Implementation of CMainFrame
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#include "mainfrm.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

#define kszAppName"ToolForm"
#define kDefaultWindowHSize 600// Form width
#define kDefaultWindowVSize 525// Form height

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code !
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// arrays of IDs used to initialize control bars

static UINT BASED_CODE indicators[] =
{
ID_SEPARATOR,           // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
  sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1;      // fail to create
}

SetWindowText(kszAppName);
SetWindowPos(NULL, 0, 0, kDefaultWindowHSize, kDefaultWindowVSize,
             SWP_NOMOVE | SWP_NOZORDER);

    return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    cs.style &= ~FWS_ADDTOTITLE;    // don't update the title bar

return CFrameWnd::PreCreateWindow(cs);
}


MAINFRM.H

// --mainfrm.h----------------------------------------------------------------
//
//  Interface of the CMainFrame class
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
CStatusBar  m_wndStatusBar;

// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by ToolForm.rc
//
#define IDD_ABOUTBOX                    100
#define IDR_MAINFRAME                   128
#define IDR_PHONETYPE                   129
#define IDD_FORM                        130
#define IDR_ACCELERATOR1                131
#define IDC_TOPIC                       1000
#define IDC_TOOLNAME                    1001
#define IDC_TOOLTYPE                    1002
#define IDC_DEVELOPEDBY                 1003
#define IDC_LOCATION                    1004
#define IDC_INTRODUCTION                1005
#define IDC_MAINFRAME                   1006
#define IDC_TO                          1007
#define IDC_PLATFORM                    1008
#define IDC_OS                          1009
#define IDC_EDIT2                       1038
#define ID_FILE_ADDRESS                 32771
#define ID_FILE_SEND                    32772
#define ID_ACCEL32773                   32773

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        133
#define _APS_NEXT_COMMAND_VALUE         32774
#define _APS_NEXT_CONTROL_VALUE         1039
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.H

// --stdafx.h-----------------------------------------------------------------
//
//  Include file for standard system include files, or project specific
//  include files that are used frequently, but are changed infrequently
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

#define VC_EXTRALEAN// Exclude rarely-used stuff from Windows headers

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT


TOOL.CPP

// --tool.cpp-----------------------------------------------------------------
//
//   Implementation of the CToolFormApp class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#include "mainfrm.h"
#include "formdata.h"
#include "form.h"
#include "tooldoc.h"
#include "toolform.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// CToolFormApp

BEGIN_MESSAGE_MAP(CToolFormApp, CWinApp)
//{{AFX_MSG_MAP(CToolFormApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CToolFormApp construction

CToolFormApp::CToolFormApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CToolFormApp object

CToolFormApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CToolFormApp initialization

BOOL CToolFormApp::InitInstance()
{
    // Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

Enable3dControls();

LoadStdProfileSettings(0);  // Load standard INI file options (including MRU)

// Register the application's document templates.  Document templates
//  serve as the connection between documents, frame windows and views.

m_pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CToolDoc),
RUNTIME_CLASS(CMainFrame),       // main SDI frame window
RUNTIME_CLASS(CToolForm));
AddDocTemplate(m_pDocTemplate);

// create a new (empty) document
OnFileNew();

if (m_lpCmdLine[0] != '\0')
{
// TODO: add command line processing here

if (m_lpCmdLine && (m_lpCmdLine[0] == '/' || m_lpCmdLine[0] == '-'))
   {
}
}

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// Implementation
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CToolFormApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

/////////////////////////////////////////////////////////////////////////////
// CToolFormApp commands


TOOL.H

// --tool.h-------------------------------------------------------------------
//
//  Main header file for the Tool application
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// Tool:
// See Tool.cpp for the implementation of this class
//

class CToolFormApp : public CWinApp
{
public:
CToolFormApp();

CSingleDocTemplate * m_pDocTemplate;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CToolFormApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CToolFormApp)
afx_msg void OnAppAbout();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
extern CToolFormApp theApp;


TOOLDOC.CPP

// --tooldoc.cpp--------------------------------------------------------------
//
//   Implementation of the CToolDoc class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "tool.h"

#include "formdata.h"
#include "form.h"

#include "tooldoc.h"
#include "toolform.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

FRM* g_pForm= NULL;

/////////////////////////////////////////////////////////////////////////////
// CToolDoc

IMPLEMENT_DYNCREATE(CToolDoc, CDocument)

BEGIN_MESSAGE_MAP(CToolDoc, CDocument)
//{{AFX_MSG_MAP(CToolDoc)
ON_COMMAND(ID_FILE_SEND, OnFileSend)
ON_UPDATE_COMMAND_UI(ID_FILE_SEND, OnUpdateFileSend)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CToolDoc construction/destruction

CToolDoc::CToolDoc()
{
// *** ADDED CODE ***
    m_fOleMapiINITed= FALSE;
m_fStandaloneApp= TRUE;

HRESULT hr = NOERROR;

hr = HrStartOleAndRegisterClassFactory();// Start OLE and MAPI
if (NOERROR == hr)
{
m_fOleMapiINITed = TRUE;
}
// *** END ADDED CODE ***
}

CToolDoc::~CToolDoc()
{
if (TRUE == m_fOleMapiINITed)
{
HrStopForms();
}
}

/////////////////////////////////////////////////////////////////////////////
// CToolDoc serialization

void CToolDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
}
else
{
}
}

/////////////////////////////////////////////////////////////////////////////
// CToolDoc diagnostics

#ifdef _DEBUG
void CToolDoc::AssertValid() const
{
CDocument::AssertValid();
}

void CToolDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CToolDoc commands

void CToolDoc::OnFileSend()
{
AfxGetMainWnd()->SetWindowText("Sending ...");

    POSITION pos = theApp.m_pDocTemplate->GetFirstDocPosition();
    ASSERT (pos);

    CToolDoc * pDoc = (CToolDoc*)theApp.m_pDocTemplate->GetNextDoc(pos);

    POSITION posVW = pDoc->GetFirstViewPosition();
    ASSERT (posVW);

    CToolForm * pView = (CToolForm*) pDoc->GetNextView (posVW);

    if (NULL != pView)
{
pView->GetDataFromWindow();
}

    if (g_pForm)
    {
    g_pForm->AddressForm(AfxGetMainWnd()->m_hWnd, FALSE);

    // ----- Update our forms information based on user interaction
    g_pForm->SetFormData(m_theFormData);

    // ----- Send this form, then shut it down
    g_pForm->SendForm();
    g_pForm->ShutdownForm(OLECLOSE_NOSAVE);
g_pForm->Release();
    }

}

void CToolDoc::OnUpdateFileSend(CCmdUI* pCmdUI)
{
    if (NULL != g_pForm)
{
pCmdUI->Enable(TRUE);
}
else
{
pCmdUI->Enable(FALSE);
}
}


TOOLDOC.H

// --tooldoc.h----------------------------------------------------------------
//
//  Interface of the CToolDoc class
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

class CToolDoc : public CDocument
{
protected: // create from serialization only
CToolDoc();
DECLARE_DYNCREATE(CToolDoc)

// Attributes

// *** ADDED CODE ***
public:
CFormDatam_theFormData;

BOOLm_fOleMapiINITed;
BOOLm_fStandaloneApp;
// *** END ADDED CODE ***

// Operations
public:
void PutDataIntoWindow();
void GetDataFromWindow();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CToolDoc)
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CToolDoc();
virtual void Serialize(CArchive& ar);   // overridden for document i/o
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
//{{AFX_MSG(CToolDoc)
afx_msg void OnFileSend();
afx_msg void OnUpdateFileSend(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

extern FRM * g_pForm;


TOOLFORM.CFG

[Description]
MessageClass=Ipm.ToolForm1
CLSID={861e4010-5030-FEDC-9912-000a0038901b}
Category=Standard
Subcategory=Tool Form
DisplayName=Tool Form
Comment=Tool Form
LargeIcon=toolform.ico
SmallIcon=toolform.ico
Version=1.0
Locale=enu
Hidden=0
Owner=Microsoft Corp
ComposeCommand=New Tool Form...

[Platforms]
Platform.2=NTx86
Platform.9=Win95

[Platform.NTx86]
CPU=ix86
OSVersion=WinNT3.5
file1=toolform.exe
registry1=LocalServer32 = %d\toolform.exe /f
registry4=InProcHandler32 = ole32.dll

[Platform.Win95]
CPU=ix86
OSVersion=Win95
file1=toolform.exe
registry1=LocalServer32 = %d\toolform.exe /f
registry4=InProcHandler32 = ole32.dll

[Properties]
Property1=CallersName

[Property.CallersName]
Type=3
NmidInteger=26624
DisplayName=FormDataStructure
SpecialType=0

[Extensions]
WMS_Extension=WMS_Extension

[Extension.WMS_Extension]
Type=30
NmidPropset={00020D35-0000-0000-C000-000000000046}
NmidInteger=1
Value=11222000
;
; ----- position
; 0
; 1 read
; 2 reply
; 3 reply all
; 4 forward
; 5 print
; 6 save
; 7 on delivery
; ----- meaning
; 0==default
; 1==DoVerb
; 2==Disabled

[Verbs]
Verb1=1

[Verb.1]
DisplayName=bogus
Code=0
Flags=0
Attribs=0


TOOLFORM.CPP

// --toolform.cpp-------------------------------------------------------------
//
//   Implementation of the CToolForm class
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// ---------------------------------------------------------------------------

#include "stdafx.h"
#include "Tool.h"

#include "formdata.h"
#include "form.h"

#include "ToolForm.h"
#include "ToolDoc.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// CToolForm

IMPLEMENT_DYNCREATE(CToolForm, CFormView)

CToolForm::CToolForm()
: CFormView(CToolForm::IDD)
{
//{{AFX_DATA_INIT(CToolForm)
m_strDevelopedBy = _T("");
m_strIntroduction = _T("");
m_strLocation = _T("");
m_strOS = _T("");
m_strPlatform = _T("");
m_strToolName = _T("");
m_strToolType = _T("");
m_strTopic = _T("");
//}}AFX_DATA_INIT
}

CToolForm::~CToolForm()
{
}

void CToolForm::OnFinalRelease()
{
// When the last reference for an automation object is released
//OnFinalRelease is called.  This implementation deletes the
//object.  Add additional cleanup required for your object before
//deleting it from memory.

delete this;
}

void CToolForm::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CToolForm)
DDX_Text(pDX, IDC_DEVELOPEDBY, m_strDevelopedBy);
DDX_Text(pDX, IDC_INTRODUCTION, m_strIntroduction);
DDX_Text(pDX, IDC_LOCATION, m_strLocation);
DDX_CBString(pDX, IDC_OS, m_strOS);
DDX_CBString(pDX, IDC_PLATFORM, m_strPlatform);
DDX_Text(pDX, IDC_TOOLNAME, m_strToolName);
DDX_CBString(pDX, IDC_TOOLTYPE, m_strToolType);
DDX_Text(pDX, IDC_TOPIC, m_strTopic);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CToolForm, CFormView)
//{{AFX_MSG_MAP(CToolForm)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CToolForm diagnostics

#ifdef _DEBUG
void CToolForm::AssertValid() const
{
CFormView::AssertValid();
}

void CToolForm::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}

CToolDoc* CToolForm::GetDocument() // non-debug version is inline
{
return (CToolDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CToolForm message handlers

void CToolForm::OnInitialUpdate()
{
 PutDataIntoWindow();

CFormView::OnInitialUpdate();
}

void CToolForm::PutDataIntoWindow(void)
{
    CToolDoc * pDoc = GetDocument();

    //TODO : Enter FORM DATA specific code here
m_strTopic         = pDoc->m_theFormData.m_szTopic;
m_strToolName      = pDoc->m_theFormData.m_szToolName;
m_strToolType      = pDoc->m_theFormData.m_szToolType;
m_strPlatform      = pDoc->m_theFormData.m_szPlatform;
m_strOS            = pDoc->m_theFormData.m_szOS;
m_strDevelopedBy   = pDoc->m_theFormData.m_szDevelopedBy;
m_strLocation      = pDoc->m_theFormData.m_szLocation;
m_strIntroduction  = pDoc->m_theFormData.m_szIntroduction;

UpdateData(FALSE);
}

void CToolForm::GetDataFromWindow(void)
{
UpdateData(TRUE);

    CToolDoc * pDoc = GetDocument();

    //TODO : Enter FORM DATA specific code here
strncpy(
        pDoc->m_theFormData.m_szTopic,
        m_strTopic,
        sizeof(pDoc->m_theFormData.m_szTopic) - 1);
strncpy(
        pDoc->m_theFormData.m_szToolName,
        m_strToolName,
        sizeof(pDoc->m_theFormData.m_szToolName) - 1);
strncpy(
        pDoc->m_theFormData.m_szToolType,
        m_strToolType,
        sizeof(pDoc->m_theFormData.m_szToolType) - 1);
strncpy(
        pDoc->m_theFormData.m_szPlatform,
        m_strPlatform,
        sizeof(pDoc->m_theFormData.m_szPlatform) - 1);
strncpy(
        pDoc->m_theFormData.m_szOS,
        m_strOS,
        sizeof(pDoc->m_theFormData.m_szOS) - 1);
strncpy(
        pDoc->m_theFormData.m_szDevelopedBy,
        m_strDevelopedBy,
        sizeof(pDoc->m_theFormData.m_szDevelopedBy) - 1);
strncpy(
        pDoc->m_theFormData.m_szLocation,
        m_strLocation,
        sizeof(pDoc->m_theFormData.m_szLocation) - 1);
strncpy(
        pDoc->m_theFormData.m_szIntroduction,
        m_strIntroduction,
        sizeof(pDoc->m_theFormData.m_szIntroduction) - 1);
}


TOOLFORM.H

// --toolform.h---------------------------------------------------------------
//
//  Declaration for the CToolForm class
//
//Copyright (C) 1986-1996. Microsoft Corporation.  All Rights Reserved.
//
// ---------------------------------------------------------------------------

class CToolDoc;

/////////////////////////////////////////////////////////////////////////////
// CToolForm form view

class CToolForm : public CFormView
{
protected:
CToolForm();           // protected constructor used by dynamic creation
DECLARE_DYNCREATE(CToolForm)

// Form Data
public:
//{{AFX_DATA(CToolForm)
enum { IDD = IDD_FORM };
CStringm_strDevelopedBy;
CStringm_strIntroduction;
CStringm_strLocation;
CStringm_strOS;
CStringm_strPlatform;
CStringm_strToolName;
CStringm_strToolType;
CStringm_strTopic;
//}}AFX_DATA

// Attributes
public:

// Operations
public:
CToolDoc * GetDocument();

// *** ADDED CODE ***
void PutDataIntoWindow(void);
void GetDataFromWindow(void);
// *** END ADDED CODE ***

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CToolForm)
public:
virtual void OnFinalRelease();
virtual void OnInitialUpdate();
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CToolForm();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
//{{AFX_MSG(CToolForm)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

#ifndef _DEBUG  // debug version in toolform.cpp
inline CToolDoc* CToolForm::GetDocument()
   { return (CToolDoc*)m_pDocument; }
#endif


TRACK.C

// --track.c--------------------------------------------------------------------
//
// Gateway message tracking sample application.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"

#define MAX_INPUT_SIZ   1024

#define MAX_RECIPIENTS  7

PVOID       pvSession                           = NULL;

UINT        nEventType                          = 0;

SYSTEMTIME  stEvent                             = {0};

TCHAR       szGatewayName[MAX_INPUT_SIZ+1]      = {0};

TCHAR       szPartnerName[MAX_INPUT_SIZ+1]      = {0};

TCHAR       szMtsID[MAX_INPUT_SIZ+1]            = {0};

TCHAR       szSubjectMtsID[MAX_INPUT_SIZ+1]     = {0};

TCHAR       szRemoteID[MAX_INPUT_SIZ+1]         = {0};

TCHAR       szOriginatorName[MAX_INPUT_SIZ+1]   = {0};

INT         nPriority                           = 0;

INT         nLength                             = 0;

INT         nSeconds                            = 0;

INT         nCost                               = 0;

INT         cRecipients                         = 0;

TCHAR       szRecip1[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip2[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip3[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip4[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip5[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip6[MAX_INPUT_SIZ+1]           = {0};
TCHAR       szRecip7[MAX_INPUT_SIZ+1]           = {0};

LPTSTR      lpszRecipients[MAX_RECIPIENTS]      = {0};

static BOOL fInitializedMAPI                    = FALSE;

#include "track.chk"

//
// Names of Command Line Arguments
//

#define ARG_SERVER"SERVER"
#define ARG_HELP1"?"
#define ARG_HELP2"HELP"

//
// Table of Command Line Switches for _HrExpandCommandLineArgument()
//

static char * rgpszArgArray[] = {
ARG_SERVER,
ARG_HELP1,
ARG_HELP2,
};

#define ARGARRAYSIZE (sizeof(rgpszArgArray)/sizeof(rgpszArgArray[0]))

//
// Variables For Command Line Arguments
//

char szServer[MAX_PATH+1]= {0};
BOOL fDisplayedHelp= FALSE;

//
// Functions
//

//$--ShowUsage------------------------------------------------------------------
//  Show usage information.
// -----------------------------------------------------------------------------
static VOID ShowUsage(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowUsage()\n");

    printf("USAGE: TRACK [Flags]\n\n");
printf("  [Flags]        Enter TRACK /? for details\n");

fDisplayedHelp = TRUE;
}

//$--ShowHelp-------------------------------------------------------------------
//  Show help information.
// -----------------------------------------------------------------------------
static VOID ShowHelp(void)// RETURNS: nothing
{
DEBUGPRIVATE("ShowHelp()\n");

printf("Message Tracking sample.\n\n");
printf("USAGE: TRACK [Flags]\n\n");
printf("  Required Flags\n");
printf("  /SERVER=             Server computer name\n");
printf("\n");
printf("  Optional Flags\n");
printf("  /HELP or /?          Display help\n");

fDisplayedHelp = TRUE;
}

//$--HrConfig-------------------------------------------------------------------
//  Read configuration from command line.
// -----------------------------------------------------------------------------
HRESULT HrConfig(           // RETURNS: return code
    IN int argc,            // number of arguments on command line
    IN char *argv[])        // array of command line arguments
{
    HRESULThr= NOERROR;
    HRESULThrT= NOERROR;
char *pszArgument= NULL;
char *pszValue= NULL;
    inti= 0;

DEBUGPRIVATE("HrConfig()\n");

// If there are no command line arguments then show a usage message.

if (argc < 2)
{
ShowUsage();
hr = E_FAIL;
goto cleanup;
}

// Do an initial check for /? or /HELP.  If found, show a help message
// and don't do any other parsing.

for (i = 1; i < argc; i++)
{
hr = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (SUCCEEDED(hr) && pszArgument &&
(!_stricmp(pszArgument,ARG_HELP1) ||
!_stricmp(pszArgument,ARG_HELP2)))
{
ShowHelp();
hr = E_FAIL;
goto cleanup;
}
}

// Loop through and parse all the command line arguments.

for (i = 1; i < argc; i++)
{
hrT = _HrExpandCommandLineArgument(
argv[i], rgpszArgArray, ARGARRAYSIZE, NULL, &pszArgument, &pszValue);

if (FAILED(hrT))
{
hr = hrT;

if (hr == EDK_E_NOT_FOUND)
{
fprintf(stderr, "ERROR: unknown command line flag: %s\n",
argv[i]);
continue;
}
else
{
fprintf(stderr, "ERROR: unable to parse command line.\n");
goto cleanup;
}
}

// Parse flag arguments that don't take a value.

else if (pszArgument != NULL && pszValue == NULL)
{
// Other flag (must take a value).
fprintf(stderr, "ERROR: flag /%s requires a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}

// Parse flag arguments that take a value.

else if (pszArgument != NULL && pszValue != NULL)
{
if (!_stricmp(pszArgument,ARG_SERVER))
{
strncpy(szServer, pszValue, MAX_PATH);
}

// Other flag (must not take a value).

else
{
fprintf(stderr, "ERROR: flag /%s does not take a value\n",
pszArgument);
hr = HR_LOG(E_FAIL);
}
}

// Catch unknown arguments.

else
{
fprintf(stderr, "ERROR: unknown argument %s\n", argv[i]);
hr = HR_LOG(E_FAIL);
}
}

// Make sure we have all the info we need.

if (SUCCEEDED(hr))
{
    if (szServer[0] == 0)
{
fprintf(stderr, "ERROR: please specify /%s\n", ARG_SERVER);
hr = HR_LOG(E_FAIL);
}
        else
        {
            DWORD dwService      = 0;
            DWORD dwCurrentState = 0;

            hr = HrGetExchangeServiceStatus(
                szServer,
                EXCHANGE_SA,
                &dwCurrentState);

            if(FAILED(hr))
            {
    fprintf(
        stderr,
        "ERROR: please start Exchange system attendant on %s\n",
        szServer);
    hr = HR_LOG(E_FAIL);
            }
        }
}

cleanup:
    RETURN(hr);
}

//$--HrDoEnterString------------------------------------------------------------
//  Enter a string.
// -----------------------------------------------------------------------------
HRESULT HrDoEnterString(                // RETURNS: return code
    IN LPTSTR lpszPrompt,               // prompt
    OUT LPTSTR lpszString)              // string buffer
{
    HRESULT hr                       = NOERROR;
    TCHAR   szInput[MAX_INPUT_SIZ+1] = {0};

    hr = CHK_HrDoEnterString(
        lpszPrompt,
        lpszString);

    if(FAILED(hr))
        RETURN(hr);

    lpszString[0] = 0;

    printf("\n%s: ", (lpszPrompt != NULL ? lpszPrompt : ""));

    if(gets(szInput) == NULL)
    {
        hr = HR_LOG(E_FAIL);

        printf("\nERROR: gets() failed\n");
    }
    else
    {
        strncpy(lpszString, szInput, MAX_INPUT_SIZ);
        lpszString[MAX_INPUT_SIZ] = 0;
    }

    RETURN(hr);
}

//$--HrDoEnterNumber------------------------------------------------------------
//  Enter a number.
// -----------------------------------------------------------------------------
HRESULT HrDoEnterNumber(                // RETURNS: return code
    IN LPTSTR lpszPrompt,               // prompt
    IN BOOL fAllowNegative,             // TRUE=allow negative number
    OUT LONG *lplNumber)                // number
{
    HRESULT hr                       = NOERROR;
    TCHAR   szInput[MAX_INPUT_SIZ+1] = {0};
    LONG    lNumber                  = 0;
    LONG    cMatch                   = 0;

    hr = CHK_HrDoEnterNumber(
        lpszPrompt,
        fAllowNegative,
        lplNumber);

    if(FAILED(hr))
        RETURN(hr);

    *lplNumber = 0;

    printf("\n%s: ", (lpszPrompt != NULL ? lpszPrompt : ""));

    if(gets(szInput) == NULL)
    {
        hr = HR_LOG(E_FAIL);

        printf("\nERROR: gets() failed\n");
    }
    else
    {
        cMatch = sscanf(szInput, "%ld", &lNumber);
        if (cMatch == 0 || cMatch == EOF)
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        if (!fAllowNegative && lNumber < 0)
        {
            printf("ERROR: negative number not allowed\n");
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        *lplNumber = lNumber;
    }

cleanup:

    RETURN(hr);
}

//$--DoEnterCommand-------------------------------------------------------------
//  Enter a command.
// -----------------------------------------------------------------------------
void DoEnterCommand(void)
{
    ULONG   ulInput                  = 0;
    TCHAR   szInput[MAX_INPUT_SIZ+1] = {0};
    ULONG   i                        = 0;

    nEventType = 0;

    ZeroMemory(&stEvent, sizeof(SYSTEMTIME));

    ZeroMemory(szGatewayName ,MAX_INPUT_SIZ+1);

    ZeroMemory(szPartnerName ,MAX_INPUT_SIZ+1);

    ZeroMemory(szMtsID, MAX_INPUT_SIZ+1);

    ZeroMemory(szSubjectMtsID, MAX_INPUT_SIZ+1);

    ZeroMemory(szRemoteID, MAX_INPUT_SIZ+1);

    ZeroMemory(szOriginatorName, MAX_INPUT_SIZ+1);

    nPriority   = 0;

    nLength     = 0;

    nSeconds    = 0;

    nCost       = 0;

    cRecipients = 0;

    ZeroMemory(szRecip1, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip2, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip3, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip4, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip5, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip6, MAX_INPUT_SIZ+1);

    ZeroMemory(szRecip7, MAX_INPUT_SIZ+1);

    for(;;)
    {
        ULONG ulChoice = 0;

        printf("\nMENU:\n");
        printf("\t 1. event type      [%u]\n", nEventType);
        printf("\t 2. gateway name    [%s]\n", szGatewayName);
        printf("\t 3. partner name    [%s]\n", szPartnerName);
        printf("\t 4. MTS-ID          [%s]\n", szMtsID);
        printf("\t 5. subject MTS-ID  [%s]\n", szSubjectMtsID);
        printf("\t 6. Remote-ID       [%s]\n", szRemoteID);
        printf("\t 7. originator name [%s]\n", szOriginatorName);
        printf("\t 8. priority        [%u]\n", nPriority);
        printf("\t 9. length          [%u]\n", nLength);
        printf("\t10. seconds         [%u]\n", nSeconds);
        printf("\t11. cost            [%u]\n", nCost);
        printf("\t12. #recipients     [%u]\n", cRecipients);
        printf("\t13. recipient #1    [%s]\n", szRecip1);
        printf("\t14. recipient #2    [%s]\n", szRecip2);
        printf("\t15. recipient #3    [%s]\n", szRecip3);
        printf("\t16. recipient #4    [%s]\n", szRecip4);
        printf("\t17. recipient #5    [%s]\n", szRecip5);
        printf("\t18. recipient #6    [%s]\n", szRecip6);
        printf("\t19. recipient #7    [%s]\n", szRecip7);
        printf("\t 0. Exit\n");

        if(FAILED(HrDoEnterNumber("SELECTION", TRUE, &ulChoice)))
        {
           continue;
        }

        switch(ulChoice)
        {
        case 1:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("event type", FALSE, &ulInput)))
            {
                continue;
            }
            nEventType = ulInput;
            break;
        case 2:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("gateway name", szInput)))
            {
                continue;
            }
            strncpy(szGatewayName, szInput, MAX_INPUT_SIZ);
            szGatewayName[MAX_INPUT_SIZ] = 0;
            break;
        case 3:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("partner name", szInput)))
            {
                continue;
            }
            strncpy(szPartnerName, szInput, MAX_INPUT_SIZ);
            szPartnerName[MAX_INPUT_SIZ] = 0;
            break;
        case 4:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("MTS-ID", szInput)))
            {
                continue;
            }
            strncpy(szMtsID, szInput, MAX_INPUT_SIZ);
            szMtsID[MAX_INPUT_SIZ] = 0;
            break;
        case 5:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("Subject MTS-ID", szInput)))
            {
                continue;
            }
            strncpy(szSubjectMtsID, szInput, MAX_INPUT_SIZ);
            szSubjectMtsID[MAX_INPUT_SIZ] = 0;
            break;
        case 6:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("Remote-ID", szInput)))
            {
                continue;
            }
            strncpy(szRemoteID, szInput, MAX_INPUT_SIZ);
            szRemoteID[MAX_INPUT_SIZ] = 0;
            break;
        case 7:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("originator name", szInput)))
            {
                continue;
            }
            strncpy(szOriginatorName, szInput, MAX_INPUT_SIZ);
            szOriginatorName[MAX_INPUT_SIZ] = 0;
            break;
        case 8:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("priority", FALSE, &ulInput)))
            {
                continue;
            }
            nPriority = ulInput;
            break;
        case 9:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("length", FALSE, &ulInput)))
            {
                continue;
            }
            nLength = ulInput;
            break;
        case 10:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("seconds", FALSE, &ulInput)))
            {
                continue;
            }
            nSeconds = ulInput;
            break;
        case 11:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("cost", FALSE, &ulInput)))
            {
                continue;
            }
            nCost = ulInput;
            break;
        case 12:
            ulInput = 0;
            if(FAILED(HrDoEnterNumber("#recipients", FALSE, &ulInput)))
            {
                continue;
            }
            cRecipients = ulInput;
            break;
        case 13:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip1", szInput)))
            {
                continue;
            }
            strncpy(szRecip1, szInput, MAX_INPUT_SIZ);
            szRecip1[MAX_INPUT_SIZ] = 0;
            break;
        case 14:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip2", szInput)))
            {
                continue;
            }
            strncpy(szRecip2, szInput, MAX_INPUT_SIZ);
            szRecip2[MAX_INPUT_SIZ] = 0;
            break;
        case 15:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip3", szInput)))
            {
                continue;
            }
            strncpy(szRecip3, szInput, MAX_INPUT_SIZ);
            szRecip3[MAX_INPUT_SIZ] = 0;
            break;
        case 16:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip4", szInput)))
            {
                continue;
            }
            strncpy(szRecip4, szInput, MAX_INPUT_SIZ);
            szRecip4[MAX_INPUT_SIZ] = 0;
            break;
        case 17:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip5", szInput)))
            {
                continue;
            }
            strncpy(szRecip5, szInput, MAX_INPUT_SIZ);
            szRecip5[MAX_INPUT_SIZ] = 0;
            break;
        case 18:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip6", szInput)))
            {
                continue;
            }
            strncpy(szRecip6, szInput, MAX_INPUT_SIZ);
            szRecip6[MAX_INPUT_SIZ] = 0;
            break;
        case 19:
            szInput[0] = 0;
            if(FAILED(HrDoEnterString("recip7", szInput)))
            {
                continue;
            }
            strncpy(szRecip7, szInput, MAX_INPUT_SIZ);
            szRecip7[MAX_INPUT_SIZ] = 0;
            break;
        case 0:
            goto cleanup;
        default:
            printf("ERROR: unknown menu selection\n");
            break;
        }
    }

cleanup:

    return;
}

//$--DoRunCommand---------------------------------------------------------------
//  Run a command.
// -----------------------------------------------------------------------------
void DoRunCommand(void)
{
    HRESULT hr = NOERROR;

    printf("running command....\n");

    ZeroMemory(&stEvent, sizeof(SYSTEMTIME));

    GetSystemTime(&stEvent);

    hr = HrTrackWriteLog(
        pvSession,
        nEventType,
        stEvent,
        szGatewayName,
        szPartnerName,
        szMtsID,
        szSubjectMtsID,
        szRemoteID,
        szOriginatorName,
        nPriority,
        nLength,
        nSeconds,
        nCost,
        cRecipients,
        ((cRecipients == 0) ? NULL : ((LPTSTR *)&lpszRecipients)));

    if(FAILED(hr))
    {
        printf("ERROR: tracking write failed\n");

        goto cleanup;
    }

    printf("finished command....\n");

cleanup:

    return;
}

//$--main-----------------------------------------------------------------------
//  Gateway message tracking sample application entry point.
// -----------------------------------------------------------------------------
int main(int argc, char *argv[])
{
    HRESULT hr                       = NOERROR;

    DEBUGPUBLIC("main()\n");

    //
    // Initialize MAPI
    //

    hr = MAPIInitialize(NULL);

    if(FAILED(hr))
    {
        printf("ERROR: MAPI initialize failed\n");

        goto cleanup;
    }

    fInitializedMAPI = TRUE;

    //
    // Configure
    //

    hr = HrConfig(argc, argv);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    //
    // Initialize gateway message tracking
    //

    hr = HrTrackInitLog(
    szServer,
    &pvSession);

    if(FAILED(hr))
    {
        printf("ERROR: tracking initialize failed\n");

        goto cleanup;
    }

    lpszRecipients[0] = (LPTSTR)szRecip1;
    lpszRecipients[1] = (LPTSTR)szRecip2;
    lpszRecipients[2] = (LPTSTR)szRecip3;
    lpszRecipients[3] = (LPTSTR)szRecip4;
    lpszRecipients[4] = (LPTSTR)szRecip5;
    lpszRecipients[5] = (LPTSTR)szRecip6;
    lpszRecipients[6] = (LPTSTR)szRecip7;

    printf("\n");

    printf("Processing commands.\n");

    for(;;)
    {
        ULONG ulChoice = 0;

        printf("\nMENU:\n");
        printf("\t 1. Enter command\n");
        printf("\t 2. Run command\n");
        printf("\t 0. Exit\n");

        if(FAILED(HrDoEnterNumber("SELECTION", TRUE, &ulChoice)))
        {
            continue;
        }

        switch(ulChoice)
        {
        case 1:
            DoEnterCommand();
            break;
        case 2:
            DoRunCommand();
            break;
        case 0:
            goto cleanup;
            break;
        default:
            printf("ERROR: unknown menu selection\n");
            break;
        }
    }

cleanup:

    //
    // Uninitialize gateway message tracking
    //

    if(pvSession != NULL)
    {
        hr = HrTrackUninitLog(&pvSession);

        if(FAILED(hr))
        {
            printf("ERROR: tracking uninitialize failed\n");
        }
    }

    if(fInitializedMAPI == TRUE)
    {
        MAPIUninitialize();

        fInitializedMAPI = FALSE;
    }

    // return exit code
    return _nEcFromHr(hr);

}


TNEFDEC.C

// --tnefdec.c------------------------------------------------------------------
//
//  Converter that decodes a TNEF copy of the message as an embedded message.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#define _MEMLOG_H           // skip mem logging stuff until it works in UNICODE.

#define USES_IID_IMessage
#include "edk.h"
#include "tnefdec.chk"

//
//  TNEF global variables.
//

// Note: OpenTnefStreamEx doesn't have exported wide and ansi versions.
// Thus, must let the compiler set the pszTNEFStreamName type.
static LPTSTR pszTNEFStreamName = TEXT("MAPIDATA.DAT");

//$--HrCnvQueryCapability-------------------------------------------------------
//
// DESCRIPTION: Routine called by the conversion engine to determine whether the converter
// can handle a particular message.
//
// INPUT:
//          pszOptions  --  options passed from conversion
//          pszClass    --  class of object to be converted
//          pContent    --  object to be converted
//          pEnv    --  environment for conversion
//
// OUTPUT:  pfAmCandidate   --  TRUE if can be converted, FALSE otherwise
//
// RETURNS: HRESULT --  NOERROR if function call overall is successful
//                      E_INVALIDARG if bad input
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
    IN LPCWSTR pszOptions,                  // options passed from conversion engine
    IN LPCWSTR pszClass,                    // class of object to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT BOOL * pfAmCandidate)               // can convert?
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("HrConvQueryCapability\n");

    // Check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass, pContent,
                                  pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pfAmCandidate = TRUE ;             // yes, we can process it.

    RETURN(hr);

}

//$--HrCnvConvert---------------------------------------------------------------
//
//  DESCRIPTION: Perform the conversion.
//
//  INPUT:
//          pszOptions  --  options
//          pszClass    --  object class to be converted
//          pContent    --  object to be converted (GW message envelope pointer)
//          pEnv    --  environment for conversion
//          pContentOut --  converted object (stream pointer)
//
//  OUTPUT:
//          pcr --  result of conversion
//
//  RETURNS:    HRESULT --  NOERROR if function call overall if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvConvert(
    IN LPCWSTR pszOptions,                  // options.
    IN LPCWSTR pszClass,                    // object class to be converted.
    IN PVOID pContent,                      // object to be converted.
    IN PVOID pContentOut,                   // where to put the result.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT EDKCNVRES * pcr)                    // result of conversion
{
    HRESULT     hr          =   NOERROR;
    LPMESSAGE   pEnvelope   =   NULL;       // gateway envelope pointer
    LPMESSAGE   pMessage    =   NULL;       // envelope content pointer
    LPATTACH    pAttach     =   NULL;       // envelope attachment pointer
    LPSTREAM    lpStream    =   NULL;       // stream to write to
    LPITNEF     lpTnef      =   NULL;       // pointer to TNEF object.
    WORD        wKey        =   0;          // magic text tag cookie.
    ULONG       ulAttachNum =   0;          // attachment number
    SPropValue  rgProps[2]  =   {0};        // attachment properties

    //
    //  Declare an empty prop tag array to contain the
    //  list of properties that should not be included.
    //  I.e.:  include all properties in the TNEF.
    //  MAPI requires a list of properites that should not
    //  be copied to the TNEF.  By giving a zero length list
    //  for the list of properties to be excluded, all props
    //  will be included.
    //
    static SPropTagArray propEmptyPropTagArray  = {0, } ;

    DEBUGPRIVATE("HrCnvConvert()\n");

    // Check input parameters
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // The output is a gateway message envelope pointer
    pEnvelope = (LPMESSAGE) pContentOut;

    // The input is a stream pointer.
    lpStream = (LPSTREAM) pContent;

    // Get the envelope's content.
    hr = MAPICALL(pEnvelope)->CreateAttach(
                pEnvelope,      // message envelope pointer
                NULL,           // interface ID
                MAPI_DEFERRED_ERRORS, // MAPI flags
                &ulAttachNum,
                &pAttach);      // pointer to attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    rgProps[0].ulPropTag = PR_ATTACH_METHOD;
    rgProps[0].Value.ul  = ATTACH_EMBEDDED_MSG;
    rgProps[1].ulPropTag = PR_RENDERING_POSITION;
    rgProps[1].Value.l   = 0;

    hr = MAPICALL(pAttach)->SetProps(pAttach, 2, rgProps, 0);

    if(FAILED(hr))
    {
    goto cleanup;
}

    // Open a message interface on the attachment.
    hr = MAPICALL(pAttach)->OpenProperty(
                pAttach,            // attachment pointer
                PR_ATTACH_DATA_OBJ, // property tag
                &IID_IMessage,       // interface ID reference
                0,                  // interface flags
                MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS, // MAPI flags
                (LPUNKNOWN *) &pMessage);         // message pointer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  Setup TNEF.  Create a TNEF object to encode the message into
    //    lpStream (an attachment of the message.)
    //
    // Note that there are no exported wide and ansi versions of OpenTnef
    // StreamEx.  Thus, pszTNEFStreamName's type must be set by the compiler.
    hr = OpenTnefStreamEx(
        NULL,
        lpStream,
        pszTNEFStreamName,
        TNEF_DECODE,
        pMessage,
        0x01AF,       // magic number
                      // used to ID the TNEF
                      // attachment.
pEnv->lpAB,
        &lpTnef);

    if (FAILED (hr))
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup ;
    }

    ASSERTERROR(lpTnef != NULL,"NULL lpTnef variable") ;

    //
    //  Specify which properties not to include. The list is empty,
    //  so no properties are excluded. All props are included.
    //

    hr = lpTnef->lpVtbl->ExtractProps(
        lpTnef,
        TNEF_PROP_EXCLUDE,
        &propEmptyPropTagArray,
        NULL);

    if (FAILED (hr))
    {
        goto cleanup ;
    }

hr = MAPICALL(pMessage)->SaveChanges(pMessage, KEEP_OPEN_READWRITE);

if(FAILED(hr))
{
    goto cleanup;
}

hr = MAPICALL(pAttach)->SaveChanges(pAttach, KEEP_OPEN_READWRITE);

    if(FAILED (hr))
    {
    goto cleanup;
}

hr = MAPICALL(pEnvelope)->SaveChanges(pEnvelope, KEEP_OPEN_READWRITE);

if(FAILED(hr))
{
    goto cleanup;
}

    //
    //  We're done.  Tell the converter we did OK.
    //

    *pcr = GCR_OK ;

cleanup:

    // Release OLE and MAPI objects
    ULRELEASE(pAttach);
    ULRELEASE(pMessage);
    ULRELEASE(lpTnef);

    RETURN(hr) ;

}

//structure that points to all routines exported by this converter.
static CONVDLLVECT cdExportVector =
{
    1,
    HrCnvQueryCapability,
    HrCnvConvert
} ;

//$--HrGetExportVector---------------------------------------------------------
//
// DESCRIPTION: Called by conversion engine to get the vector of conversion entry points.
//
// INPUT:   dwVersionRequired   --  desired version number
//
// OUTPUT:  ppConvDLLVect    --  pointer to export vector structure
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input.
//
// -----------------------------------------------------------------------------
extern HRESULT HrGetExportVector(        // RETURNS: HRESULT
    IN DWORD dwVersionRequired,             // desired version number
    OUT PCONVDLLVECT FAR * ppConvDLLVect)  // pointer to export vector structure
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrGetExportVector()\n");

    // Check input parameters
    hr = CHK_HrGetExportVector(dwVersionRequired, ppConvDLLVect);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *ppConvDLLVect = &cdExportVector;

    RETURN(hr);

}


TNEFENC.C

// --tnefenc.c------------------------------------------------------------------
//
//  Converter that Adds a TNEF copy of the message as an attachment.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
// -----------------------------------------------------------------------------

#define _MEMLOG_H           // skip mem logging stuff until it works in UNICODE.

#define USES_IID_IMessage
#include "edk.h"
#include "tnefenc.chk"

//
//  TNEF global variables.
//
// Note:  OpenTnefStreamEx does not export separate wide character and
// ansi versions.  Thus, we must let the compiler set the pszTNEFStreamName
// type.
static LPTSTR pszTNEFStreamName = TEXT("MAPIDATA.DAT");

//$--HrCnvQueryCapability-------------------------------------------------------
//
// DESCRIPTION: Routine called by the conversion engine to determine whether the converter
// can handle a particular message.
//
// INPUT:
//          pszOptions  --  options passed from conversion
//          pszClass    --  class of object to be converted
//          pContent    --  object to be converted
//          pEnv    --  environment for conversion
//
// OUTPUT:  pfAmCandidate   --  TRUE if can convert, FALSE otherwise
//
// RETURNS: HRESULT --  NOERROR if overall success,.
//                      E_INVALIDARG if bad input.
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
    IN LPCWSTR pszOptions,                  // options passed from conversion engine
    IN LPCWSTR pszClass,                    // class of object to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT BOOL * pfAmCandidate)               // can convert?
{
    HRESULT hr  =   NOERROR;        // return code

    DEBUGPRIVATE("HrCnvQueryCabability()\n");

    // check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass, pContent,
                                  pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pfAmCandidate = TRUE ;             // yes, we can process it.

    RETURN(hr) ;

}

//$--HrCnvConvert---------------------------------------------------------------
//
//  DESCRIPTION: Perform the conversion.
//
//  INPUT:
//          pszOptions  --  options
//          pszClass    --  class of object to convert
//          pContent    --  object to be converted (GW message envelope pointer)
//          pEnv    --  environment for conversion
//          pContentOut --  converted object ( stream pointer)
//
//  OUTPUT:
//          pcr --  result of conversion
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvConvert(
    IN LPCWSTR pszOptions,                  // options.
    IN LPCWSTR pszClass,                    // object class to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PVOID pContentOut,                   // where to put the result.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT EDKCNVRES * pcr)                    // result of conversion
{
    HRESULT     hr          =   NOERROR;
    LPMESSAGE   pEnvelope   =   NULL;       // gateway envelope pointer
    LPMESSAGE   pMessage    =   NULL;       // content is an IMessage.
    LPATTACH    pAttach     =   NULL;       // envelope attachment pointer
    LPSTREAM    lpStream    =   NULL;       // stream to be opened on attachment content.
    ULONG       i           =   0;
    ULONG       cValues     =   0;
    ULONG       ulPropTag   =   0;
    LPITNEF     lpTnef      =   NULL;       // pointer to TNEF object.
    WORD        wKey        =   0;          // magic text tag cookie.

    //
    //  Declare an property tag array to contain the
    //  list of properties that should not be included.
    //
    SPropTagArray *lpPropTagArray = NULL;

    DEBUGPRIVATE("HrCnvConvert()\n");

    // check input
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // The input is a gateway message envelope pointer
    pEnvelope = (LPMESSAGE) pContent;

    // The output is a stream pointer.
    lpStream = (LPSTREAM) pContentOut;

    // Get the envelope's content.
    hr = MAPICALL(pEnvelope)->OpenAttach(
                pEnvelope,      // message envelope pointer
                0,              // attachment number
                NULL,           // interface ID reference
                MAPI_DEFERRED_ERRORS, // MAPI flags
                &pAttach);      // pointer to attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Open a message interface on the attachment.
    hr = MAPICALL(pAttach)->OpenProperty(
                pAttach,            // attachment pointer
                PR_ATTACH_DATA_OBJ, // property tag
                &IID_IMessage,       // interface ID reference
                0,                  // interface flags
                MAPI_DEFERRED_ERRORS,             // MAPI flags
                (LPUNKNOWN *) &pMessage);         // message pointer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Note:  OpenTnefStreamEx does not have exported versions for
    // wide character and ansi.  Thus, must let compiler set the pszTNEFStreamName
    // type.
hr = OpenTnefStreamEx(
    NULL,
    lpStream,
    pszTNEFStreamName,
TNEF_ENCODE,
pMessage,
0x01AF,
pEnv->lpAB,
&lpTnef);

if(FAILED(hr))
{
        hr = HR_LOG(E_FAIL);

        goto cleanup;
}

    ASSERTERROR(lpTnef != NULL,"NULL lpTnef variable") ;

hr = pMessage->lpVtbl->GetPropList(
    pMessage,
    0,
&lpPropTagArray);

if(FAILED(hr))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

// Build a new prop tag array containing only non-transmittable properties.

cValues = 0;

for(i = 0; i < lpPropTagArray->cValues; i++)
{
ulPropTag = lpPropTagArray->aulPropTag[i];

if ((!FIsTransmittable(ulPropTag)) &&
(ulPropTag != PR_MESSAGE_RECIPIENTS) &&
(ulPropTag != PR_MESSAGE_ATTACHMENTS))
{
lpPropTagArray->aulPropTag[cValues++] = ulPropTag;
}
}

lpPropTagArray->cValues = cValues;

// Exclude selected properties from the TNEF encapsulation.

hr = lpTnef->lpVtbl->AddProps(
    lpTnef,
TNEF_PROP_EXCLUDE,
0,
NULL,
lpPropTagArray);

if(FAILED(hr))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

hr = lpTnef->lpVtbl->Finish(lpTnef, 0, &wKey, NULL);

if(FAILED(hr))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

    // Commit changes to stream.
hr = lpStream->lpVtbl->Commit(lpStream, STGC_DEFAULT);

if(FAILED(hr))
{
        hr = HR_LOG(E_FAIL);
        goto cleanup;
}

    //
    //  We're done.  Tell the converter we did OK.
    //
    *pcr = GCR_OK ;

cleanup:

    MAPIFREEBUFFER(lpPropTagArray);

    ULRELEASE(pAttach);
    ULRELEASE(pMessage);

    ULRELEASE(lpTnef);

    RETURN(hr);

}

// structure that points to all routines exported by this converter.
static CONVDLLVECT cdExportVector =
{
    1,
    HrCnvQueryCapability,
    HrCnvConvert
} ;

//$--HrGetExportVector---------------------------------------------------------
//
// DESCRIPTION: Called by conversion engine to get the vector of conversion entry points.
//
// INPUT:   dwVersionRequired   --  desired version number
//
// OUTPUT:  ppConvDLLVect   --  pointer to conversion vector structure
//
// RETURNS: HRESULT --  NOERROR if successful
//                      E_INVALIDARG if bad input
//
// -----------------------------------------------------------------------------
extern HRESULT HrGetExportVector(        // RETURNS: HRESULT
    IN DWORD dwVersionRequired,             // desired versioin number
    OUT PCONVDLLVECT FAR *ppConvDLLVect)    //  pointer to conversion vector structure
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrGetExportVector()\n");

    // check input parameters
    hr = CHK_HrGetExportVector(dwVersionRequired, ppConvDLLVect);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *ppConvDLLVect = &cdExportVector;

    RETURN(hr);

}


TOUPPER.C

// --toupper.c------------------------------------------------------------------
//
// Converter that renders a message body to an attachment
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#define _MEMLOG_H           // skip mem logging stuff until it works in UNICODE.

#define EDK_USES_IID
#include "edk.h"
#include "vsop.h"
#include "toupper.chk"

//$--HrCnvQueryCapability-------------------------------------------------------
//
// DESCRIPTION: Routine called by the conversion engine to determine whether the converter
// can handle a particular message.
//
// INPUT:
//          pszOptions  --  options passed from conversion
//          pszClass    --  class of object to be converted
//          pContent    --  object oto be converted.
//          pEnv    --  environment for conversion
//
// OUTPUT:  pfAmCandidate   --  TRUE if can convert, FALSE otherwise
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
    IN LPCWSTR pszOptions,                  // options passed from conversion engine
    IN LPCWSTR pszClass,                    // class of object to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT BOOL * pfAmCandidate)               // can convert?
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPRIVATE("HrCnvQueryCapability()\n");

    // check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass, pContent,
                                  pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pfAmCandidate = TRUE ;             // yes, we can process it.

    RETURN(hr) ;

}

//$--HrConvertStreamToUpperCase-----------------------------------------------
//
// DESCRIPTION: Convert data in input stream to upper-case
//              equivalent in output stream.
//
// INPUT:       lpStreamIn  --  input stream to convert
//              lpStreamOut --  output stream to convert to
//
// RETURNS:     HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise
//
// -----------------------------------------------------------------------------
static HRESULT HrConvertStreamToUpperCase(
        IN LPSTREAM lpStreamIn,         // input stream
        IN LPSTREAM lpStreamOut)        // output stream
{
// maximum # characters to read at a time
#defineMAX_CHARS_READ8192

    HRESULT hr          =   NOERROR;
    ULONG   cbRead      =   0;          // # bytes read
DWORDcchUpper=0;// # chars converted to upper case

    const ULONG cbWanted    =   8192 * sizeof(CHAR);   // # bytes wanted

CHARrgchBuffer[MAX_CHARS_READ]="";// text buffer

    DEBUGPRIVATE("HrConvertStreamToUpperCase()\n");

    // check input parameters
    hr = CHK_HrConvertStreamToUpperCase(lpStreamIn, lpStreamOut);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    while ( TRUE )
    {
        // Read a block of characters from the input stream
        // & write the uppercase version to the output
        // stream.
        hr = OLECALL(lpStreamIn)->Read(
                    lpStreamIn,     // object pointer
                    rgchBuffer,     // data buffer
                    cbWanted,       // # bytes to read
                    &cbRead);       // # bytes read

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Test for end of data
        if ( cbRead == 0 )
        {
            // we are done.
            goto cleanup;
        }

        ASSERTERROR(cbRead <= cbWanted, "Bad cbRead");

        // convert character buffer to upper case
        cchUpper = CharUpperBuffA(
        rgchBuffer,// character buffer
        cbRead/sizeof(CHAR));// # characters in buffer

ASSERTERROR((cchUpper == cbRead * sizeof(CHAR)), "Bad cchUpper");

        // Write the upper-cased character to the output
        // stream
        hr = OLECALL(lpStreamOut)->Write(
                lpStreamOut,        // object pointer
                rgchBuffer,         // upper cased data
                cbRead,             // # bytes to write
                NULL);              // don't care

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

    }   // end while

    // we are done

cleanup:

    RETURN(hr);

}

//$--HrWriteToStreamA-----------------------------------------------------------
//
//  DESCRIPTION: Write an ANSI string to a stream.
//
//  INPUT:  lpStream    --  stream to write to
//          lpsz    --  string to write to stream
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrWriteToStreamA(  // RETURNS: HRESULT
    IN LPSTREAM lpStream,                   // stream to write to.
    IN LPSTR lpsz)                          // ANSI string to write.
{
    HRESULT hr  =   NOERROR;        // return code
    ULONG ulBytesWritten = 0 ;
    ULONG ulBytesToWrite = 0;

    DEBUGPRIVATE("HrWriteToStreamA()\n");

    // check input parameters
    hr = CHK_HrWriteToStreamA(lpStream, lpsz);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ulBytesToWrite = lstrlenA(lpsz) * sizeof(CHAR) ;

    hr = OLECALL(lpStream)->Write(lpStream, lpsz, ulBytesToWrite, &ulBytesWritten);

    if ( FAILED(hr) || (ulBytesWritten != ulBytesToWrite) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--HrWriteToStreamW-----------------------------------------------------------
//
//  DESCRIPTION: Write a wide string to a stream.
//
//  INPUT:  lpStream    --  stream to write to
//          lpsz    --  string to write
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrWriteToStreamW(     // RETURNS: HRESULT
    IN LPSTREAM lpStream,                   // stream to write to.
    IN LPWSTR lpsz)                         // wide string to write.
{
    HRESULT hr  =   NOERROR;        // return code
    ULONG ulBytesWritten = 0 ;
    ULONG ulBytesToWrite = 0;

    DEBUGPRIVATE("HrWriteToStreamW()\n");

    // Check input parameters
    hr = CHK_HrWriteToStreamW(lpStream, lpsz);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ulBytesToWrite = lstrlenW(lpsz) * sizeof(WCHAR) ;

    hr = OLECALL(lpStream)->Write(lpStream, lpsz, ulBytesToWrite, &ulBytesWritten);

    if ( FAILED(hr) || (ulBytesWritten != ulBytesToWrite) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--HrCnvConvert---------------------------------------------------------------
//
//  DESCRIPTION: Perform the conversion.
//
//  INPUT:
//          pszOptions  --  options (may be null)
//          pszClass    --  object class to be converted
//          pContent    --  objec to convert (GW message envelope pointer)
//          pEnv    --  environment for conversion
//          pContentOut --  converted object (stream pointer)
//
//  OUTPUT:
//          pcr --  result of conversion
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvConvert(
    IN LPCWSTR pszOptions,                  // options.
    IN LPCWSTR pszClass,                    // object class to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PVOID pContentOut,                   // where to put the result.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT EDKCNVRES * pcr)                    // result of conversion
{
    HRESULT     hr          = NOERROR;
    LPMESSAGE   pEnvelope   = NULL; // gateway message envelope pointer
    LPATTACH    pAttach     = NULL; // envelope's attachment
    LPMESSAGE   pMessage    = NULL; // content is an IMessage.
    LPSTREAM lpStream    = NULL;// output stream pointer
    PVIRTUALSTREAMONPROPERTYpStrmBody= NULL;// buffered body text stream

    DEBUGPRIVATE("HrCnvConvert()\n");

    // check input parameters
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // The input is a gateway message envelope pointer
    pEnvelope = (LPMESSAGE) pContent;

    // The output is a stream pointer.
    lpStream = (LPSTREAM) pContentOut;

    // Get the envelope's content.
    hr = MAPICALL(pEnvelope)->OpenAttach(
                pEnvelope,      // message envelope pointer
                0,              // attachment number
                NULL,           // interface ID reference
                MAPI_DEFERRED_ERRORS, // MAPI flags
                &pAttach);      // pointer to attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(pAttach), "Bad lpAttach");

    // Open a message interface on the attachment.
    hr = MAPICALL(pAttach)->OpenProperty(
                pAttach,            // attachment pointer
                PR_ATTACH_DATA_OBJ, // property tag
                &IID_IMessage,       // interface ID reference
                0,                  // interface flags
                MAPI_DEFERRED_ERRORS,             // MAPI flags
                (LPUNKNOWN *) &pMessage);         // message pointer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    ASSERTERROR(!FBadUnknown(pMessage), "Bad pMessage");

    //
    //  Write a header to the the lpStream.
    //
    hr = HrWriteToStreamA(lpStream, "Message body follows:\r\n") ;

    if (FAILED(hr))
    {
        goto cleanup ;
    }

    //
    // Get a buffered stream on the ASCII body of the message.from the PR_BODY
    // property.  (We really want PR_BODY for this
    // sample, since we want to print out a human-readable
    // body text.)
hr = HrOpenVirtualStreamOnProperty(
(LPMAPIPROP) pMessage,// object pointer
PR_BODY_A,// property tag
MAPI_DEFERRED_ERRORS,// reduces RPCs
&pStrmBody);// buffered body stream pointer

    if (SUCCEEDED(hr))
    {
        ASSERT_IUNKNOWN_PTR(pStrmBody, "Bad pStrmBody");

        //
        // There was a body.
        //

        //
        //  Convert the message to upper case.
        //
        hr = HrConvertStreamToUpperCase(
            (LPSTREAM) pStrmBody,   // buffered input stream
            lpStream);   // output stream

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }
    else if (hr == MAPI_E_NOT_FOUND)
    {
        //
        //  No body.  Put in a fake body.
        // write fake body to stream
        hr = HrWriteToStreamA(
                lpStream,
                "No Body") ;

        if (FAILED(hr))
        {
            goto cleanup ;
        }

        hr = HR_LOG(NOERROR);
    }
    else
    {
        //
        //  Other error, die.
        //
        hr = HR_LOG(E_FAIL);

        goto cleanup ;
    }

    // Commit changes to the stream.
    hr = OLECALL(lpStream)->Commit(
                lpStream,       // stream pointer
                STGC_DEFAULT);  // commit flags

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  We're done.  Tell the converter we did OK.
    //
    *pcr = GCR_OK ;

cleanup:

    // Release MAPI objects.
    ULRELEASE(pMessage);
    ULRELEASE(pAttach);
    ULRELEASE(pStrmBody);

    RETURN(hr) ;

}

// structure that points to all routines exported by this converter.
static CONVDLLVECT cdExportVector =
{
    1,
    HrCnvQueryCapability,
    HrCnvConvert
} ;

//$--HrGetExportVector---------------------------------------------------------
//
// DESCRIPTION: Called by conversion engine to get the vector of conversion entry points.
//
// OUTPUT:  ppConvDLLVect   --  pointer to conversion vector structure
//
// -----------------------------------------------------------------------------
extern HRESULT HrGetExportVector( // RETURNS: HRESULT
    IN DWORD dwVersionRequired,             // desired conversion DLL version number
    OUT PCONVDLLVECT FAR * ppConvDLLVect)   // pointer to conversion vector structure
{
    HRESULT hr  =   NOERROR;

    DEBUGPUBLIC("HrGetExportVector()\n");

    // check input parameters
    hr = CHK_HrGetExportVector(dwVersionRequired, ppConvDLLVect);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *ppConvDLLVect = &cdExportVector;

    RETURN(hr);

}


TRIVIAL.C

// --trivial.c------------------------------------------------------------------
//
// Module demonstrates simplest message converter.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#define EDK_USES_IID
#include "edk.h"
#include "trivial.chk"

//$--HrCnvQueryCapability-------------------------------------------------------
//
// DESCRIPTION: Routine called by the conversion engine to determine whether the converter
// can handle a particular message.
//
// INPUT:
//          pszOptions  --  options passed from conversion (may be null)
//          pszClass    --  class of object to be converted
//          pContent    --  object to be converted
//          pEnv    --  environment for conversion
//
// OUTPUT:  pfAmCandidate   --  TRUE if can convert, FALSE otherwise.
//
// RETURNS: HRESULT --  NOERROR if call if successful,
//                      E_INVALIDARG if bad input,
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvQueryCapability(
    IN LPCWSTR pszOptions,                  // options passed from conversion engine
    IN LPCWSTR pszClass,                    // class of object to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT BOOL * pfAmCandidate)               // can convert?
{
    HRESULT hr  =   NOERROR;        // return code

    DEBUGPRIVATE("HrCnvQueryCapability()\n");

    // check input parameters
    hr = CHK_HrCnvQueryCapability(pszOptions, pszClass, pContent,
                                  pEnv, pfAmCandidate);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *pfAmCandidate = TRUE ;                 // say that we can take it.

    RETURN(hr) ;

}

//$--HrWriteToStreamA-----------------------------------------------------------
//
//  DESCRIPTION: Write an ANSI string to a stream.
//
//  INPUT:  lpStream --  stream to write to
//          lpsz    --  string to write
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input,
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrWriteToStreamA(         // RETURNS: HRESULT
    IN LPSTREAM lpStream,                   // stream to write to.
    IN LPSTR lpsz)                          // ANSI string to write.
{
    HRESULT hr  =   NOERROR;        // return code
    ULONG ulBytesWritten = 0 ;
    ULONG ulBytesToWrite = 0;

    DEBUGPRIVATE("HrWriteToStreamA()\n");

    // check input parameters
    hr = CHK_HrWriteToStreamA(lpStream, lpsz);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    ulBytesToWrite = lstrlenA(lpsz) * sizeof(CHAR) ;

    hr = OLECALL(lpStream)->Write(lpStream, lpsz, ulBytesToWrite, &ulBytesWritten);

    if ( FAILED(hr) || (ulBytesWritten != ulBytesToWrite) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--HrCnvConvert---------------------------------------------------------------
//
//  DESCRIPTION: Perform the conversion.
//
//  INPUT:
//          pszOptions  --  options         (may be null)
//          pszClass    --  object class to be converted
//          pContent    --  object to convert (GW message envelope)
//          pEnv    --  environment for conversion
//          pContentOut --  converted object (stream  pointer)
//
//  OUTPUT:
//          pcr --  result of conversion
//
//  RETURNS:    HRESULT --  NOERROR if successful,
//                          E_INVALIDARG if bad input
//                          E_FAIL otherwise.
//
// -----------------------------------------------------------------------------
static HRESULT HrCnvConvert(
    IN LPCWSTR pszOptions,                  // options.
    IN LPCWSTR pszClass,                    // object class to be converted.
    IN PVOID pContent,                      // object to be covnerted.
    IN PVOID pContentOut,                   // where to put the result.
    IN PEDKCNVENV pEnv,                     // environment for conversion.
    OUT EDKCNVRES * pcr)                    // result of conversion
{
    HRESULT     hr          =   NOERROR;    // return code
    LPMESSAGE   pEnvelope   =   NULL;       // gateway message envelope pointer
    LPATTACH    pAttach     =   NULL;       // envelope attachment pointer
    LPMESSAGE   pMessage    =   NULL;       // envelope content pointer
    LPSTREAM    lpStream    =   NULL;       // output stream pointer

    DEBUGPRIVATE("HrCnvConvert()\n");

    // check input paramters
    hr = CHK_HrCnvConvert(pszOptions, pszClass, pContent,
                          pContentOut, pEnv, pcr);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // The input is a gateway message envelope pointer
    pEnvelope = (LPMESSAGE) pContent;

    // The output is a stream pointer.
    lpStream = (LPSTREAM) pContentOut;

    // Get the envelope's content.
    hr = MAPICALL(pEnvelope)->OpenAttach(
                pEnvelope,      // message envelope pointer
                0,              // attachment number
                NULL,           // interface ID reference
                MAPI_DEFERRED_ERRORS, // MAPI flags
                &pAttach);      // pointer to attachment pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Open a message interface on the attachment.
    hr = MAPICALL(pAttach)->OpenProperty(
                pAttach,            // attachment pointer
                PR_ATTACH_DATA_OBJ, // property tag
                &IID_IMessage,       // interface ID reference
                0,                  // interface flags
                MAPI_DEFERRED_ERRORS,             // MAPI flags
                (LPUNKNOWN *) &pMessage);         // message pointer pointer

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  Write a header to the attachment via the lpStream.
    //
    hr = HrWriteToStreamA(lpStream, "This attachment is intentionally empty.\r\n") ;

    if (FAILED(hr))
    {
        goto cleanup ;
    }

    // Commit changes to the stream.
    hr = OLECALL(lpStream)->Commit(
                lpStream,       // stream pointer
                STGC_DEFAULT);  // commit flags

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    //
    //  We're done.  Tell the converter we did OK.
    //
    *pcr = GCR_OK ;

cleanup:

    ULRELEASE(pAttach);
    ULRELEASE(pMessage);

    RETURN(hr) ;

}

// structure that points to all routines exported by this converter.
static CONVDLLVECT cdExportVector =
{
    1,
    HrCnvQueryCapability,
    HrCnvConvert
} ;

//$--HrGetExportVector---------------------------------------------------------
//
// DESCRIPTION: Called by conversion engine to get the vector of conversion entry points.
//
// INPUT:  dwVersion    --  version of conversion requested.
//
// OUTPUT: ppConvDLLVect --  pointer to export vector structure
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG otherwise
//
// -----------------------------------------------------------------------------
extern  HRESULT HrGetExportVector(         // RETURNS: HRESULT
        IN DWORD dwVersion,                   // version requested
        OUT PCONVDLLVECT FAR * ppConvDLLVect) // pointert to export vector structure
{
    HRESULT hr  =   NOERROR;    // return code

    DEBUGPUBLIC("HrGetExportVector()\n");

    // Check input parameters
    hr = CHK_HrGetExportVector(dwVersion, ppConvDLLVect);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    *ppConvDLLVect = &cdExportVector;

    RETURN(hr);

}


XLATDRIV.C

// --xlatdriv.c-------------------------------------------------------------------
//
//  Message translation sample.
//
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved.
//
// -----------------------------------------------------------------------------

#include "edk.h"
#include "convcwrp.h"
#include "gwmain.h"
#include "ipmconv.h"        // for ipm_in & ipm_out conversion DLLs
#include "xlatmsg.h"        // resource string identifiers
#include "xlatdriv.chk"

// constants
#define MAX_MSG_LENGTH              1024            // maximum message length
#define APPLICATION_TITLE           TEXT("Message Translation Sample")    // application name
#define APPLICATION_EVENT_FILE      TEXT("EDKMSG.DLL")  // event logging message file

//  Defined for NT service shell
TCHAR szAppName[]       = TEXT("XLATDRIV");
TCHAR szWindowTitle[]   = APPLICATION_TITLE;

static LPMAPISESSION    lpSession       = NULL;
static LPMDB            lpStore         = NULL;
static LPMAPIFOLDER     lpRootFolder    = NULL;
static LPMAPIFOLDER     lpMtsInFolder   = NULL;
static LPMAPIFOLDER     lpMtsOutFolder  = NULL;
static ULONG            cbGalEid        = 0;
static LPENTRYID        lpGalEid        = NULL;

static HANDLE           hEventLog       = NULL;

static HINSTANCE        hInst           = NULL; // program instance handle

PVOID  pvConvInstance   = NULL;

// Prefix of output *.tmp files.
static const LPCTSTR    lpszPrefix      = TEXT("XLT");

// Default inbound/outbound file directory
const LPCTSTR           lpszDefaultDir  = TEXT(".\\");

// inbound and outbound file directory
static TCHAR    lpszDirectory[MAX_PATH + 1] =   TEXT("");

// Address type used for conversion (Currently "EX")
static TCHAR    lpszAddressType[]   =   TEXT(EXCHANGE_ADDRTYPE);   // address type

// Conversion options structure for the ipm_in & ipm_out conversion DLLs
static CONV_OPTIONS   sConvOptions =
{
    FALSE,              // don't want TNEF decoding
    lpszAddressType     // address type
};

static ULONG    nMessages           =   0;              // # messages converted

//$--ProcessMtsOut--------------------------------------------------------------------
//
//  DESCRIPTION: Process messages arriving at MTS-OUT.  This is a Windows thread
//  startup routine.
//
//  INPUT:  lpParameter --  pointer to gateway thread context structure
//                          (LPGWTHREADCONTEXT).
//
//  RETURNS:    DWORD   --  0
//
// -----------------------------------------------------------------------------
DWORD ProcessMtsOut(           // RETURNS: DWORD
    IN LPVOID lpParameter)     // gateway thread context structure pointer
{
    HRESULT         hr                 = NOERROR;   // temporary return code
    ULONG           i                  = 0;
    ULONG           cRows              = 0;
    ULONG           cValues            = 0;
    ULONG           ulObjType          = 0;

    LPADRBOOK       lpAdrBook          = NULL;

    LPMESSAGE       lpEnvelope         = NULL;

    ULONG           cbeid              = 0;
    LPENTRYID       lpeid              = NULL;

    LPWSTR          pszMessageClassW   = NULL;
    ULONG           cbMessageClass     = 0;
    EDKCNVRES       crRes              = GCR_OK;

    LPSPropValue    lpProps            = NULL;

    LPSTREAM        lpStream           = NULL; // stream to write output to

    // message output file name
    TCHAR           pszFileName[MAX_PATH+1] =   TEXT("");

    //
    //  Declare an environment for the conversion.
    //

    EDKCNVENV envTmp =
    {
        1,
        L"SAMPLEGW.OUTBOUND",            // L = UNICODE string always.
        (LHANDLE)NULL,
        NULL,
        NULL,
        NULL
    };

// Stream interface flags
const ULONGulFlags=STGM_READWRITE | STGM_CREATE |
                        SOF_UNIQUEFILENAME | STGM_SHARE_EXCLUSIVE;

    DEBUGPUBLIC("ProcessMtsOut()\n");

    // check input parameters
    hr = CHK_Process(lpParameter);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    cRows = GetGWSRowSet(lpParameter)->cRows;

    // Open the Address Book
    hr = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Initialize conversion environment structure
    envTmp.lphSession       = (LHANDLE)lpSession;
    envTmp.lpAB             = lpAdrBook;
    envTmp.pGatewayDefined  = &sConvOptions;

    for(i = 0; i < cRows; i++)
    {
        // Free MAPI buffer
        MAPIFREEBUFFER(pszMessageClassW);

        // Release MAPI objects
        ULRELEASE(lpEnvelope);
        ULRELEASE(lpStream);

        cValues = GetGWSRowSet(lpParameter)->aRow[i].cValues;

        lpProps = GetGWSRowSet(lpParameter)->aRow[i].lpProps;

        cbeid = lpProps[0].Value.bin.cb;
        lpeid = (LPENTRYID)lpProps[0].Value.bin.lpb;

        // Get the message in the MTS-OUT folder
        hr = MAPICALL(lpMtsOutFolder)->OpenEntry(
            lpMtsOutFolder,
            cbeid,
            lpeid,
            NULL,
            MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
            &ulObjType,
            (LPUNKNOWN FAR *)&lpEnvelope);

        if(FAILED(hr))
        {
            goto cleanup;
        }

        if(ulObjType != MAPI_MESSAGE)
        {
            goto cleanup;
        }

        // Initialize output file name.
        lstrcpy(pszFileName, lpszDirectory);

        // Open a buffered stream interface on a unique XLT*.tmp
        // output file.
        hr = OpenStreamOnFile(
                MAPIAllocateBuffer,     // allocation routine
                MAPIFreeBuffer,         // deallocation routine
                ulFlags,// stream interface flags
                pszFileName,            // file name
                (LPTSTR) lpszPrefix,    // file prefix
                &lpStream);             // stream pointer

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // Get the message's wide character message class.
        hr = HrMAPIGetPropString(
                        (LPMAPIPROP) lpEnvelope,          // message envelope pointer
                        PR_MESSAGE_CLASS_W, // wide character message class
                        &cbMessageClass,
                        &pszMessageClassW);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }

        //
        //  Perform the conversion.
        //

        hr = HrConvConvert(
            pvConvInstance,
            &envTmp,
            pszMessageClassW,
            lpEnvelope,
            lpStream,
            &crRes);

        if(FAILED(hr) || crRes != GCR_OK)
        {
            goto cleanup;
        }

        // Note: Conversion DLL commits changes to stream if
        // successful.

        ULRELEASE(lpEnvelope);

        // ---------------------------------------------------------------------

        //
        // Remove the converted message from the MTS-OUT folder.
        //

        hr = HrMAPIDeleteMessage(
                    lpMtsOutFolder,     // MAPI folder pointer
                    cbeid,              // # bytes in entry id
                    lpeid);             // message entry identifier

        if(FAILED(hr))
        {
            goto cleanup;
        }

        // increment message count
        nMessages++;

    }   // end for

cleanup:

    // Release MAPI and OLE objects
    ULRELEASE(lpStream);
    ULRELEASE(lpEnvelope);
    ULRELEASE(lpAdrBook);

    // Free MAPI buffers
    MAPIFREEBUFFER(pszMessageClassW);

    if ( (FAILED(hr) || (crRes != GCR_OK)) && (pszFileName[0] != 0) )
    {
        // If we failed, we may have an empty file
        // siting around.
        if ( lstrcmp(pszFileName, lpszDefaultDir) != 0 )
        {
            // Remove the empty file
            DeleteFile(pszFileName);
        }
    }

    return(0);
}

//$--ProcessMtsIn--------------------------------------------------------------------
//
//  DESCRIPTION: Process files arriving in input directory.  This is a Windows thread
//  startup routine.
//
//  INPUT:  lpParameter --  pointer to gateway thread context structure
//                          (LPGWTHREADCONTEXT).
//
//  RETURNS:    DWORD   --  0
//
// -----------------------------------------------------------------------------
DWORD ProcessMtsIn(            // RETURNS: DWORD
    IN LPVOID lpParameter)     // gateway thread context structure pointer
{
    HRESULT         hr                 = NOERROR;   // temporary error code
    ULONG           i                  = 0;         // index
    ULONG           cRows              = 0;         // row count
    LPADRBOOK       lpAdrBook          = NULL;      // address book pointer
    LPMESSAGE       lpEnvelope         = NULL;      // envelope pointer
    EDKCNVRES       crRes              = GCR_OK;    // result of conversion
    LPSTREAM        lpStream           = NULL;      // stream to read input from
    BOOL            fRetVal            = FALSE;     // Win32 return code

    // File data
LPWIN32_FIND_DATA   rgFindData               = NULL;
LPWIN32_FIND_DATA   lpFindData               = NULL;

    // message output file name
    TCHAR           pszFileName[MAX_PATH+1] =   TEXT("");

    //
    //  Declare an environment for the conversion.
    //

    EDKCNVENV envTmp =
    {
        1,
        L"SAMPLEGW.INBOUND",            // L = UNICODE string always.
        (LHANDLE)NULL,
        NULL,
        NULL,
        NULL
    };

    const LPCWSTR   pszMessageClassW    =   L"ENVELOPE.IPM";    // dummy message class

    DEBUGPUBLIC("ProcessMtsIn()\n");

    // check input parameters
    hr = CHK_Process(lpParameter);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    cRows = GetGWFindDataSize(lpParameter);

    rgFindData = GetGWFindData(lpParameter);

    // Open the Address Book
    hr = MAPICALL(lpSession)->OpenAddressBook(
        lpSession,
        0,
        NULL,
        AB_NO_DIALOG,
        &lpAdrBook);

    if(FAILED(hr))
    {
        goto cleanup;
    }

    // Initialize the conversion environment structure
    envTmp.lphSession       = (LHANDLE)lpSession;
    envTmp.lpAB             = lpAdrBook;
    envTmp.pGatewayDefined  = &sConvOptions;

    for(i = 0; i < cRows; i++)
    {
        // Release MAPI objects
        ULRELEASE(lpEnvelope);
        ULRELEASE(lpStream);

    // Build file name of incoming message

        lpFindData = &rgFindData[i];

    lstrcpy(pszFileName, lpszDirectory);
    lstrcat(pszFileName, lpFindData->cFileName);

        // Open a buffered stream on our input file.
    hr = OpenStreamOnFile(
        MAPIAllocateBuffer,     // allocation routine
        MAPIFreeBuffer,         // deallocation routine
    STGM_READ | STGM_SHARE_EXCLUSIVE,// open for reading
    pszFileName,            // file name
    NULL,                   // prefix
    &lpStream);             // stream pointer

        if ( (hr == MAPI_E_NO_ACCESS) || (hr == MAPI_E_NOT_FOUND) )
        {
            // These are not really errors
            hr = NOERROR;

            continue;
        }

    if ( FAILED(hr) )
    {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
    }

        // Create the message envelope in the MTS-IN folder.
        hr = MAPICALL(lpMtsInFolder)->CreateMessage(
            lpMtsInFolder,  // folder pointer
            NULL,
            MAPI_DEFERRED_ERRORS, // flags
            &lpEnvelope);   // message pointer

        if( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        //
        //  Perform the conversion.
        //
        hr = HrConvConvert(
            pvConvInstance,         // conversion instance pointer
            &envTmp,                // environment structure pointer
            pszMessageClassW,       // dummy message class
            lpStream,               // stream to read from
            lpEnvelope,             // message to convert to
            &crRes);                // result pointer

        if(FAILED(hr) || crRes != GCR_OK)
        {
            goto cleanup;
        }

        // Note: Conversion DLL saves changes to the envelope if
        // successful.

        // Save changes to the MTS-IN folder
        hr = MAPICALL(lpMtsInFolder)->SaveChanges(
                    lpMtsInFolder,
                    KEEP_OPEN_READWRITE);   // still need access to folder

        if ( FAILED(hr) )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        ULRELEASE(lpStream);

        // Remove the converted file from the input directory.
        fRetVal = DeleteFile(pszFileName);

        if ( fRetVal == FALSE )
        {
            hr = HR_LOG(E_FAIL);

            goto cleanup;
        }

        // increment message count
        nMessages++;

    }   // end for

cleanup:

    // Release MAPI and OLE objects
    ULRELEASE(lpStream);
    ULRELEASE(lpEnvelope);
    ULRELEASE(lpAdrBook);

    return(0);
}

// $--DisplayUserMsg----------------------------------------------------------
//
// DESCRIPTION: Utility to display message to command line user.
//
// INPUT:       HINSTANCE   --  XLATDRIV instance handle
//              UINT        --  Resource string identifier
//
// RETURNS:     VOID
//
// ----------------------------------------------------------------------------

static VOID DisplayUserMsg(     // RETURNS: nothing
            IN HINSTANCE hInst, // program handle
            IN UINT nResID,     // resource string identifier
            ...)                // additional arguments
{
    INT         nRetCode        =   0;              // return value
    va_list     vArgList        =   {0};            // variable argument list
    HANDLE      hFile           =   NULL;           // file handle

    TCHAR       szMessage[MAX_MSG_LENGTH]   = TEXT(""); // message buffer
    TCHAR       szFormatted[MAX_MSG_LENGTH] = TEXT(""); // formatted message

    DEBUGPRIVATE("DisplayUserMsg()\n");

    // No CHK_DisplayUserMsg() call.
    // This function can take a NULL HINSTANCE

    // Check the instance handle passed in.
    if ( !hInst )
    {
        HR_LOG(E_FAIL);

        goto cleanup;
    }

    nRetCode = LoadString(hInst, nResID, szMessage, MAX_MSG_LENGTH);
    if ( nRetCode == 0 )
    {
        HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Get an optional argument list pointer
    va_start(vArgList, nResID);

    // Fromat the message.
    _vstprintf(
            szFormatted,        // formatted message buffer
            szMessage,          // format string
            vArgList);          // variable argument list

    va_end(vArgList);

    // Display the error in a message box
    MessageBox(
            NULL,           // use parent window handle
            szFormatted,    // message text
            APPLICATION_TITLE,  // message box title
            MB_OK);

cleanup:

    // close console file
    CLOSEHANDLE(hFile);

    // Return
    return;

}   // end DisplayUserMsg()

// $--HrParseOptions-----------------------------------------------------------
//
// DESCRIPTION: parse command line flags.
//
// INPUT:   cArgs   --  # arguments
//          pArgv   --  argument array
//
// OUTPUT:
//          pfInbound    --  inbound flag
//          pfTnef       --  TNEF flag
//          pcThreads    --  # threads
//          lpszDirectory   --  file directory
//
// RETURNS: HRESULT --  NOERROR if successful,
//                      E_INVALIDARG if bad input
//                      E_FAIL otherwise.
//
// ----------------------------------------------------------------------------
static HRESULT HrParseOptions(
        IN DWORD cArgs,         // argument count
        IN LPTSTR * pArgv,      // argument array
        OUT BOOL * pfInbound,   // inbound flag
        OUT BOOL * pfTnef,      // TNEF encoding desired flag
        OUT DWORD * pcThreads,  // # threads
        OUT LPTSTR  lpszDirectory) // file directory
{
// Number of flag arguments and array indeces
#define             iDirection      0
#define             iThreads        1
#define             iLocation       2
#define             iHelp1          3
#define             iHelp2          4
#define             iTnef           5
#define             nFlags          6

    HRESULT         hr              =   NOERROR;
    UINT            iLoop           =   0;          // loop index
    ULONG           iFlag           =   0;          // flag index
    LPTSTR          lpFlagName      =   NULL;       // flag name pointer
    LPTSTR          lpData          =   NULL;       // flag data
    HANDLE          hFile           =   NULL;       // file handle
    BOOL            fInbound        =   FALSE;      // TRUE if direction inbound
    BOOL            fOutbound       =   FALSE;      // TRUE if direction outbound
    UINT            nRet            =   0;          // Win32 API return code
    UINT            nChars          =   0;

    const LPCTSTR   lpszInbound     =   TEXT("In");     // inbound value
    const LPCTSTR   lpszOutbound    =   TEXT("Out");    // outbound value
    const TCHAR     chBackSlash     =   TEXT('\\');     // back slash

    // array of valid flags
    LPTSTR                rgFlags[nFlags]   =
    {
        TEXT("DIRECTION"),      // Inbound or outbound
        TEXT("THREADS"),        // # of threads
        TEXT("LOCATION"),       // location of input/output files
        TEXT("HELP"),
        TEXT("?"),
        TEXT("ENCODE")          // TNEF encoding/decoding desired
    };

    // temporary file name
    TCHAR           szTempFile[MAX_PATH + 1]    =   TEXT("");

    DEBUGPRIVATE("HrParseOptions()\n");

    // check input parameters
    hr = CHK_HrParseOptions(cArgs, pArgv, pfInbound, pfTnef, pcThreads,
                    lpszDirectory);

    if ( FAILED(hr) )
    {
        RETURN(hr);
    }

    // Check to make sure that we have enough arguments.
    // Note:  If the use originally typed
    // xlatdriv notserv gdk-sample, then the arguments
    // which are passed to this program are everything
    // after the program name and the notserv option.
    if ( cArgs < 1 )
    {
        hr = HR_LOG(E_INVALIDARG);

        // Print out the syntax error message.
        DisplayUserMsg(hInst, IDS_ERROR_USAGE);

        goto cleanup;
    }

    // initialize output parameters
    *pfInbound = FALSE;
    *pfTnef = FALSE;
    *pcThreads = 1;
    lstrcpy(lpszDirectory, lpszDefaultDir);

    // Parse each argument, looking for a supported flag
    for ( iLoop = 0; iLoop < cArgs; iLoop++ )
    {
        hr = _HrExpandCommandLineArgument(
                pArgv[iLoop],       // command line argument
                rgFlags,            // array of flag names
                nFlags,             // number of flags
                &iFlag,             // flag index
                &lpFlagName,        // flag name
                &lpData);           // flag data

        if ( FAILED(hr) )
        {
            DisplayUserMsg(
                        hInst,              // instance handle
                        IDS_INVALID_ARG,    // string identifier
                        pArgv[iLoop]);      // argument

            goto cleanup;
        }

        if ( lpFlagName == NULL )
        {
            // not a flag.  We are only interested in flags
            continue;
        }

        // switch off of flag found
        switch ( iFlag )
        {
            case iDirection:         // direction index
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_ERROR_NOVALUE,  // string identifier
                            lpFlagName);        // argument

                    goto cleanup;
                }

                // Determine if inbound or outbound
                if ( _tcsnicmp(lpData, lpszInbound, lstrlen(lpszInbound))
                     == 0 )
                {
                    fInbound = TRUE;
                    *pfInbound = TRUE;
                }

                else if ( _tcsnicmp(lpData, lpszOutbound, lstrlen(lpszOutbound))
                          == 0 )
                {
                    fOutbound = TRUE;
                    *pfInbound = FALSE;
                }

                else
                {
                    // print out error.
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_INVALID_VALUE,  // string identifier
                            lpData,
                            lpFlagName);        // argument

                    goto cleanup;
                }

                break;

            case iThreads:     // threads index
                // Initialize number of threads
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    // print out error
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_ERROR_NOVALUE,  // string identifier
                            lpFlagName);        // argument

                    goto cleanup;
                }

                *pcThreads = atol(lpData);

                if ( *pcThreads == 0 )
                {
                    // print out error
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_INVALID_VALUE,  // string identifier
                            lpData,
                            lpFlagName);         // argument

                    goto cleanup;
                }

                break;

            case iLocation:         // location index
                if ( (lpData == NULL) || (*lpData == 0) )
                {
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_ERROR_NOVALUE,  // string identifier
                            lpFlagName);        // argument

                    goto cleanup;
                }

                // Make sure that we can create a file in the
                // directory.
                // First, get a temporary file name
                nRet = GetTempFileName(
                        lpData,     // directory
                        lpszPrefix, // file prefix
                        0,          // use time to create number
                        szTempFile);    // file name

                if ( nRet == 0 )
                {
                    hr = HR_LOG(E_FAIL);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_INVALID_VALUE,    // string identifier
                            lpData,
                            lpFlagName);      // argument

                    goto cleanup;
                }

                hFile = CreateFile(
                        szTempFile,            // file name
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        0,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        0);

                if ( hFile == INVALID_HANDLE_VALUE )
                {
                    // print out error
                    hr = HR_LOG(E_INVALIDARG);

                    DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_INVALID_VALUE,  // string identifier
                            lpData,
                            lpFlagName);        // argument

                    goto cleanup;
                }

                // Close the file
                CLOSEHANDLE(hFile);

                // Delete the file
                (VOID)DeleteFile(
                        szTempFile);   // file name

                lstrcpy(lpszDirectory, lpData);

                // Append a back slash to the directory
                // name if it doesn't alredy have one.
                nChars = lstrlen(lpszDirectory);
                if ( lpszDirectory[nChars - 1] != chBackSlash )
                {
                    lpszDirectory[nChars] = chBackSlash;
                    lpszDirectory[nChars + 1] = 0;  // null terminate
                }

                break;

            case iHelp1:     // help index
            case iHelp2:     // ? index
                // print out help.

                // Not really an error, but we need
                // to exit early.
                hr = HR_LOG(E_INVALIDARG);

                DisplayUserMsg(hInst, IDS_ERROR_HELP);

                goto cleanup;

            case iTnef:     // TNEF index
                // User wants TNEF encoding/decoding
                // Data portion of flag is ignored.
                *pfTnef = TRUE;

                break;

            default:

                hr = HR_LOG(E_FAIL);

                DisplayUserMsg(
                            hInst,              // instance handle
                            IDS_INVALID_ARG,    // string identifier
                            lpData);            // argument

                goto cleanup;

        }   // end switch

    }   // end for

    // make sure that haven't received conflicting directions
    if ( fInbound && fOutbound )
    {
        // print out error.
        hr = HR_LOG(E_INVALIDARG);

        DisplayUserMsg(
                    hInst,                      // instance handle
                    IDS_CONFLICTING_DIRECTION); // string identifier

        goto cleanup;
    }

cleanup:

    RETURN(hr);

}

//$--GWMain----------------------------------------------------------------
//
//  DESCRIPTION: Start threads.
//
//  INPUT:  none
//
//  RETURNS: VOID
//
// -----------------------------------------------------------------------------
VOID GWMain(       // RETURNS: VOID
    VOID)
{
    HRESULT               hr                     = NOERROR;
    EDK_SERVICE_CONTROL_T sc                     = 0;
    DWORD                 cArgs                  = 0;
    LPTSTR *              pArgv                  = NULL;
    BOOL                  fInbound               = FALSE;
    BOOL                  fTnef                  = FALSE;
    DWORD                 cThreads               = 1;
    TCHAR                 szTempPath[MAX_PATH+1] = {0};
    const DWORD           cMessages              = 1;
    DWORD                 cch                    = 0;

    DEBUGPUBLIC("GWMain()\n");

    // Get this program's instance handle.
    hInst = GetModuleHandle(NULL);

    if ( hInst == NULL )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Retrieve the command line arguments
    // Command line is of the form
    //
    //  XLATDRIV [notserv] GWProfileName [flags]
    //
    // where flags are:
    //
    //  /DIRECTION=Inbound or /DIRECTION=Outbound
    //  /THREADS=#Thread
    //  /LOCATION=DirectoryName     (for input or output files)
    //  /ENCODE                     (TNEF encoding/decoding desired)
    //
    // By default, the direction is assumed to be outbound,
    // TNEF encoding is assumed to be FALSE,
    // the file location is assumed to be the current directory
    // and the number of threads is assumed to be 1.  (If
    // notserv is not specified, winwrap.c assumes that XLATDRIV
    // is to be run as a service.)

    hr = HrServiceGetArgv(
                &cArgs,         // argument count pointer
                &pArgv);        // pointer to argument array pointer

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Parse out options.
    hr = HrParseOptions(
                cArgs,      // argument count
                pArgv,      // pointer to argument array
                &fInbound,  // inbound flag
                &fTnef,     // TNEF desired flag
                &cThreads,  // # threads
                lpszDirectory);    // file directory

    if ( FAILED(hr) )
    {
        goto cleanup;
    }

    // Set TNEF flag in gateway options.
    sConvOptions.fTnefEncode = fTnef;

    if ( fInbound )
    {
        // Want to do inbound conversion.
        // (Read files in and convert to messages in MTS-IN)
        hr = HrGWStartNewFileHandler(
            1000,                   // sleep interval
            (ULONG) -1,             // poll interval
            cMessages,              // # messages per thread
            cThreads,               // # of threads
            lpszAddressType,        // address type
            TEXT("XLT*.TMP"),       // file name template
            lpszDirectory,          // input file directory
            (LPTHREAD_START_ROUTINE)ProcessMtsIn);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }

    else
    {
        cch = GetTempPath(MAX_PATH+1, szTempPath);

        if((cch == 0) || (cch > (MAX_PATH+1)))
        {
            hr = HR_LOG(E_FAIL);
            goto cleanup;
        }

        // Want to do outbound conversions.
        // (Read messages from MTS-OUT and convert to output
        // files.
        hr = HrGWStartNewMailHandler(
            1000,
            (ULONG)-1,
            cMessages,
            cThreads,
            lpszAddressType,
            szTempPath,
            (LPTHREAD_START_ROUTINE)ProcessMtsOut);

        if ( FAILED(hr) )
        {
            goto cleanup;
        }
    }   // end if doing out bound conversions

cleanup:

    if(FAILED(hr))
    {
        ServiceStop();
    }

    //
    // Wait for a request for the service to stop.
    //

    hr = HR_LOG(HrServiceWaitForStop(INFINITE, &sc));

    return;
}

//$--HrGWLogon-------------------------------------------------------------
//
//  Logon to the gateway.
//
// -----------------------------------------------------------------------------
HRESULT HrGWLogon(                 // RETURNS: return code
        VOID)
{
    HRESULT hr = NOERROR;

    lpSession      = GetGWSession();
    lpStore        = GetGWDefaultStore();
    lpRootFolder   = GetGWRootFolder();
    lpMtsInFolder  = GetGWMtsInFolder();
    lpMtsOutFolder = GetGWMtsOutFolder();
    cbGalEid       = GetGWGALEntryIdSize();
    lpGalEid       = GetGWGALEntryId();

    DEBUGPUBLIC("HrGWLogon()\n");

    // Open an event log for this application.
    hr = HrEventOpenLog(
                APPLICATION_TITLE,  // application name
                NULL,           // executable name (will be computed)
                APPLICATION_EVENT_FILE, // event message file (will be computed)
                NULL,           // parameter message file (will be computed)
                NULL,           // category message file (will be computed)
                &hEventLog);    // event source handle

    if ( FAILED(hr) || (hEventLog == NULL) )
    {
        goto cleanup;
    }

    // Initialize conversion engine global data.
    hr = HrConvInitGlobals();

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

    // Initialize conversion engine instance data.
    hr = HrConvInitInstance(hEventLog, &pvConvInstance);

    if ( FAILED(hr) )
    {
        hr = HR_LOG(E_FAIL);

        goto cleanup;
    }

cleanup:

    RETURN(hr);
}

//$--HrGWLogoff------------------------------------------------------------
//
//  Logoff of the gateway.
//
// -----------------------------------------------------------------------------
HRESULT HrGWLogoff(                // RETURNS: return code
    VOID)
{
    HRESULT         hr          =   NOERROR;
    EDKEVENTCOUNT   sEventCount =   {0};        // EDK event count structure

    DEBUGPUBLIC("HrGWLogoff()\n");

    // De-initialize conversion engine instance data.
    (VOID)HrConvUninitInstance(pvConvInstance);
    pvConvInstance = NULL;

    // De-intialize global conversion engine data.
    ConvUninitGlobals();

    // Display number of messages processed.
    if ( nMessages >= 0 )
    {
        DisplayUserMsg(
            hInst,              // instance handle
            IDS_SUCCESS,        // string identifier
            nMessages);         // # messages processed
    }

    // Determine number of messages written to the event log.
    hr = HrEventGetCounts(
            &sEventCount);

    if ( SUCCEEDED(hr) )
    {
        // print out number of errors printed to the
        // event log.
        if ( sEventCount.cError == 1 )
        {
            DisplayUserMsg(
                hInst,
                IDS_1_ERROR);
        }

        else if ( sEventCount.cError > 1 )
        {
            DisplayUserMsg(
                hInst,
                IDS_NUM_ERRORS,
                sEventCount.cError);
        }

        // Print out the number of warnings logged
        if ( sEventCount.cWarning == 1 )
        {
            DisplayUserMsg(
                hInst,
                IDS_1_WARNING);
        }

        else if ( sEventCount.cWarning > 1 )
        {
            DisplayUserMsg(
                hInst,
                IDS_NUM_WARNINGS,
                sEventCount.cWarning);
        }
    }   // end if know # of messages logged to event file

    // Close the event log.
    (VOID)HrEventCloseLog();
    hEventLog = NULL;

    RETURN(hr);
}


XLATMSG.H

// --xlatmsg.h-----------------------------------------------------------------
//
// This file contains the string identifiers used to print
// xlatdriv syntax error messages.
//
// Copyright (c) Microsoft Corp., 1986-1996.  All rights reserved.
//
// ----------------------------------------------------------------------------

#ifndef _XLATMSG_H
#define _XLATMSG_H

#define IDS_ERROR_USAGE             100
#define IDS_ERROR_HELP              101
#define IDS_INVALID_ARG             102
#define IDS_CONFLICTING_DIRECTION   103
#define IDS_1_ERROR                 104
#define IDS_NUM_ERRORS              105
#define IDS_1_WARNING               106
#define IDS_NUM_WARNINGS            107
#define IDS_SUCCESS                 108
#define IDS_INVALID_VALUE           109
#define IDS_ERROR_NOVALUE           110
#endif


README.TXT

Custom Sendable Form

The Checkers.Frm sample demonstrates how to use a custom sendable form. It
is an executable (.EXE) server. It implements the minimum form interface
required to launch and send a form.

Files
-----

The following is a list of the source files for the Checkers.Frm sample:

FORMSMPL.TXT
WCHECK.EXE          ; The executable file generated by NMAKE
MAKEFILE
CHKRFORM.CFG
ENGINE.CPP
WCHECK.CPP          ; The user interface.
FORM.CPP            ; All form-specific code.
PIECE.CUR
WCHECK16.DEF
WCHECK.DLG
DLG.H
CHECKDTA.H
MOVELIST.H
WCHECK.H
DBUGIT.H
FORM.H              ; A description of the Checkers form object.
CHECK.H
WCHECK.ICO
WCHECKS.ICO
WCHECK.RC
WCHECK.RCV

ENGINE\LUT.CPP      ; A simple move generator used primarily to
ENGINE\PRUNE.CPP    ; determine whether a move is valid.
ENGINE\QUALITY.CPP
ENGINE\CHECK.CPP
ENGINE\VALID.CPP
ENGINE\DEBUGIO.CPP

Installation
------------

The form has to be properly registered to be used my mail clients.
Run the command

    pdkin32 -f chkrform.cfg

to register the form in the local form registry. Then run the command

    regedit wcheck.reg

to register wcheck.exe as an OLE server for the form in the registry.
The path to the form's .EXE file has to be put in to the .REG file or
the system PATH.

For more information about installing and launching forms, see
the documentation for the form interfaces in the MAPI SDK.

To send the Checkers form to another user:

1. Select the Compose New Form command in your mail client.
2. Choose the checkers form in the Choose Form dialog.
3. Select a recipient in the address book.
4. Drag a checkers piece to make a move.

The form is sent automatically when the move is complete.

Known Problems
--------------

Data for many of the custom properties is not written to the
message, and the data that is written might not actually be
meaningful.


CHECKDTA.H

// Some more defines

#define PLAYER1         BLACK
#define PLAYER2         RED

// Game type definitions

#define GAME_CHECKERS   0
#define GAME_GIVEAWAY   1

// Cursor states

#define UNDEFINED_CURSOR    -1
#define NORMAL_CURSOR       1
#define RESIZE_CURSOR       2

#define RESIZE_RANGE        10      // How far from bottom right should resize
                                    // cursor be shown.

// Data structures for Checkers.

struct rGameStateRec
{
    int iPlayerTurn;                // Who's turn
    BOOL fPlayAsForm;               // If we are currently a form
    BOOL fInitDone;                 // Game initialization over (when to use timer).
    BOOL fContinuable;              // Is the turn continuable?
    BOOL fSetupMode;                // Game in setup mode? Need for button processing
    BOOL fThinking;                 // Is the computer thinking?
    BOOL fPaused;                   // Is the game paused?
    BOOL fGameInProgress;           // Did the game start? How to react to "File:New"
    BOOL fLMouseButtonDown;         // Is the left mouse button currently down?
    BOOL fRMouseButtonDown;         // Is the right mouse button currently down?
    BOOL fMoveInProgress;           // Is the user in the middle of a move?
    BOOL fComputerBusy;             // Is the computer thinking?
    BOOL fGameOver;                 // Is the game over?
    BOOL fMouseResizeInProgress;    // Is the user resizing the board with the mouse?
    int iCursorState;               // What state is the cursor in (resize, normal?)
};

struct rPlayerRec
{
    int iPlayerType;                // (ENUM) Human/Computer/Network
    int iUseOpeningBook;            // (BOOL) Use the opening-book moves
    int iMemPositions;              // (BOOL) Use memorized positions?
    int iUseMoveTheory;             // (BOOL) Use move theory?
    int iUseMoveShuffling;          // (BOOL) Use move shuffling
    int iUseEqualMoveSkipping;      // (BOOL) Use Equal move skipping
    long lUseGoodMoveSkipping;      // (LONG) Use Good move skipping

    int iMaxRecursionDepth;             // (LONG) Max recursion depth
    int iUseAlphaBetaPruning;           // (BOOL) Use alpha-beta pruning
    long lAlphaPruningOriginalDepth;    // (LONG) Alpha pruning original depth
    long lAlphaPruningWidth;            // (LONG) Alpha pruning width
};

struct rCheckConfigRec
{
    int iGameType;                      // (INT) enum game type (Checkers/FoxGoose)
    int iMustJump;                      // (BOOL) Player must jump when available?
    int iBoardFlipped;                  // (BOOL) Board flipped?
    int iSquareSize;                    // (INT) Size of board square
    int iSetupPurgeBoards;              // (BOOL) Purge boards after setup?
    int iMaxMoves;

    struct rPlayerRec   rPlayer[3];       // Config info for players
};


CHKRFORM.CFG

[Description]
MessageClass=IPM.Checkers
CLSID={86174010-5030-0076-9912-00aa0038901b}
Category=Games
Subcategory=Board Games
DisplayName=Checkers Sample Form
Comment=Sample Checkers Form
LargeIcon=wcheck.ico
SmallIcon=wchecks.ico
Version=1.0
Locale=enu
Hidden=0
Owner=MAPI PDK
ComposeCommand=New Checkers Game ...

[Platforms]
Platform.1=Win16
Platform.2=NTx86
Platform.3=Win95
Platform.4=MIPS
Platform.5=ALPHA
Platform.6=PPC

[Platform.Win16]
CPU=ix86
OSVersion=Win3.1
file1=wcheck.exe
registry2=LocalServer = wcheck.exe /f
registry3=InProcHandler = ole2.dll

[Platform.NTx86]
CPU=ix86
OSVersion=WinNT3.5
file1=wcheck.exe
registry1=LocalServer32 = wcheck.exe /f
registry4=InProcHandler32 = ole32.dll

[Platform.Win95]
CPU=ix86
OSVersion=Win95
file1=wcheck.exe
registry1=LocalServer32 = wcheck.exe /f
registry4=InProcHandler32 = ole32.dll

[Platform.MIPS]
CPU=MIPS
OSVersion=WinNT3.5
file1=wcheck.exe
registry1=LocalServer32 = wcheck.exe /f
registry4=InProcHandler32 = ole32.dll

[Platform.ALPHA]
CPU=AXP
OSVersion=WinNT3.5
file1=wcheck.exe
registry1=LocalServer32 = wcheck.exe /f
registry4=InProcHandler32 = ole32.dll

[Platform.PPC]
CPU=PPC
OSVersion=WinNT3.5
file1=wcheck.exe
registry1=LocalServer32 = wcheck.exe /f
registry4=InProcHandler32 = ole32.dll

[Properties]
Property1=BoardData
Property2=Turn
Property3=MoveNumber
Property4=BoardScore

[Property.BoardData]
Type=258
NmidInteger=26624
DisplayName=Board Data
SpecialType=0

[Property.Turn]
Type=3
NmidInteger=26625
DisplayName=Turn
SpecialType=0

[Property.MoveNumber]
Type=3
NmidInteger=26626
DisplayName=Move Number
SpecialType=0

[Property.BoardScore]
Type=3
NmidInteger=26627
DisplayName=Scoring Function Output
SpecialType=0

[Extensions]
WMS_Extension=WMS_Extension

[Extension.WMS_Extension]
Type=30
NmidPropset={00020D35-0000-0000-C000-000000000046}
NmidInteger=1
Value=11222000
;
; ----- position
; 0
; 1 read
; 2 reply
; 3 reply all
; 4 forward
; 5 print
; 6 save
; 7 on delivery
; ----- meaning
; 0==default
; 1==DoVerb
; 2==Disabled

[Verbs]
Verb1=1

[Verb.1]
DisplayName=open
Code=0
Flags=0
Attribs=2


DBUGIT.H

// --------------------------------------------------------------------------
#define TraceTag     TRACEFUNC
#define TraceError(_sz,_x)   TRACEFUNC(NULL,"ERROR: %s %08x",_sz,_x)

#define DoTagsDialog()
#define TagRegister 0;
#define RestoreDefaultDebugState()

#define tagNull      __LINE__,NULL
#define tagUI        __LINE__,"ui"
#define tagUIOther   __LINE__,"uiother"
#define tagMouse     __LINE__,"mouse"
#define tagEngine    __LINE__,"engine"
#define tagEngineMin __LINE__,"enginemin"
#define tagForm      __LINE__,"form"
#define tagFormFunc  __LINE__,"formfunc"
#define tagFuncTriv  __LINE__,"functriv"

#define AssertSz(_x,_sz) if (!(_x)) ASSERTFUNC(_sz,__LINE__,__FILE__,#_x);
#define Assert(_x) AssertSz(_x,"assertion")
#define ASSERTDATA
#define pdebug
#define stddbgmin NULL
#define stddbg    NULL

#define NYI(_x) AssertSz(0,_x)

// --------------------------------------------------------------------------
#ifdef DEBUG
void _cdecl TRACEFUNC(int line, char * tag, char * szFmt, ...)
#ifdef DEBUG_CODE
{
    va_list valMarker;
    static char rgch[1024];
    static char szTraceNumber[32];
    static char szLineNumber[32];
    int  x,q;

    if (tag && 1 != GetPrivateProfileInt("tag",tag, 1, "c:\\trace.out")) return;

    va_start(valMarker, szFmt);
    wvsprintf(rgch, szFmt, valMarker);
    lstrcat(rgch,"                                                                                                                                                      ");
    rgch[125] = '\0';
    wsprintf(rgch + 125,"<<< (%5d) ",line);
    if (tag) lstrcat(rgch,tag);
    else lstrcat(rgch,"NULL");
    va_end(valMarker);

    if (GetPrivateProfileInt ("trace", "trace", 0, "c:\\trace.out"))
    {
        // ----- how many traces have we output

        x = GetPrivateProfileInt ("trace", "last", 0, "c:\\trace.out");
        ++x;
        wsprintf(szTraceNumber,"%d",x);
        WritePrivateProfileString ("trace", "last", szTraceNumber, "c:\\trace.out");

        // ----- cat the file

        FILE *fp;
        fp = fopen("c:\\trace.out", "a");
        if (fp)
        {
            fprintf(fp,"%s",rgch);
            fclose(fp);
        }
        else MessageBeep(0);

        // ----- debug break

        if (q=GetPrivateProfileInt ("trace", "break", 0, "c:\\trace.out"))
        {
            if (q == x || -1 == q)
                MessageBox(NULL,rgch,"TRACE TAG",MB_OK);
        }

    }
}
#else
;
#endif

void ASSERTFUNC(char *sz,int line,char *file,char* string)
#ifdef DEBUG_CODE
{
    static char rgch[1024];
    TraceTag(tagNull,"\n\nASSERTION FAILURE !!!");
    MessageBeep(0);
    wsprintf(rgch,"%s(%d) (%s)\n%s",file,line,string,sz);
    TraceTag(tagNull,rgch);
    MessageBox(NULL,rgch,"assertion failure",MB_TASKMODAL|MB_ABORTRETRYIGNORE);
}
#else
;
#endif
#ifndef IID_IPersistMessage
#ifdef _WIN32
#include <objbase.h>
#else
#include <compobj.h>
#endif
#include <mapiform.h>
#else
#pragma message("here we are now .. REFCLSID")
#endif
char *DumpCLSID(REFCLSID riid)
#ifdef DEBUG_CODE
    {
    static char ach[44];
    static char szName[256];

    szName[0] = '\0';

#ifdef _WIN32
    if      (IsEqualIID(riid, IID_IUnknown            )) lstrcpy(szName,"IID_IUnknown           ");
    else if (IsEqualIID(riid, IID_IClassFactory       )) lstrcpy(szName,"IID_IClassFactory      ");
    else if (IsEqualIID(riid, IID_IMalloc             )) lstrcpy(szName,"IID_IMalloc            ");
    else if (IsEqualIID(riid, IID_IMarshal            )) lstrcpy(szName,"IID_IMarshal           ");
    else if (IsEqualIID(riid, IID_IRpcChannel         )) lstrcpy(szName,"IID_IRpcChannel        ");
    else if (IsEqualIID(riid, IID_IRpcStub            )) lstrcpy(szName,"IID_IRpcStub           ");
    else if (IsEqualIID(riid, IID_IStubManager        )) lstrcpy(szName,"IID_IStubManager       ");
    else if (IsEqualIID(riid, IID_IRpcProxy           )) lstrcpy(szName,"IID_IRpcProxy          ");
    else if (IsEqualIID(riid, IID_IProxyManager       )) lstrcpy(szName,"IID_IProxyManager      ");
    else if (IsEqualIID(riid, IID_IPSFactory          )) lstrcpy(szName,"IID_IPSFactory         ");
    else if (IsEqualIID(riid, IID_ILockBytes          )) lstrcpy(szName,"IID_ILockBytes         ");
    else if (IsEqualIID(riid, IID_IStorage            )) lstrcpy(szName,"IID_IStorage           ");
    else if (IsEqualIID(riid, IID_IStream             )) lstrcpy(szName,"IID_IStream            ");
    else if (IsEqualIID(riid, IID_IEnumSTATSTG        )) lstrcpy(szName,"IID_IEnumSTATSTG       ");
    else if (IsEqualIID(riid, IID_IBindCtx            )) lstrcpy(szName,"IID_IBindCtx           ");
    else if (IsEqualIID(riid, IID_IMoniker            )) lstrcpy(szName,"IID_IMoniker           ");
    else if (IsEqualIID(riid, IID_IRunningObjectTable )) lstrcpy(szName,"IID_IRunningObjectTable");
    else if (IsEqualIID(riid, IID_IInternalMoniker    )) lstrcpy(szName,"IID_IInternalMoniker   ");
    else if (IsEqualIID(riid, IID_IRootStorage        )) lstrcpy(szName,"IID_IRootStorage       ");
    else if (IsEqualIID(riid, IID_IMessageFilter      )) lstrcpy(szName,"IID_IMessageFilter     ");
    else if (IsEqualIID(riid, CLSID_StdMarshal        )) lstrcpy(szName,"CLSID_StdMarshal       ");
    else if (IsEqualIID(riid, IID_IStdMarshalInfo     )) lstrcpy(szName,"IID_IStdMarshalInfo    ");
    else if (IsEqualIID(riid, IID_IExternalConnection )) lstrcpy(szName,"IID_IExternalConnection");
    else if (IsEqualIID(riid, IID_IPersistMessage     )) lstrcpy(szName,"IID_IPersistMessage    ");
    else if (IsEqualIID(riid, IID_IMAPIMessageSite    )) lstrcpy(szName,"IID_IMAPIMessageSite   ");
    else if (IsEqualIID(riid, IID_IMAPISession        )) lstrcpy(szName,"IID_IMAPISession       ");
    else if (IsEqualIID(riid, IID_IMAPIViewContext    )) lstrcpy(szName,"IID_IMAPIViewContext   ");
    else if (IsEqualIID(riid, IID_IMAPIViewAdviseSink )) lstrcpy(szName,"IID_IMAPIViewAdviseSink");
    else if (IsEqualIID(riid, IID_IMAPIFormAdviseSink )) lstrcpy(szName,"IID_IMAPIFormAdviseSink");
    else if (IsEqualIID(riid, IID_IMAPIFormInfo       )) lstrcpy(szName,"IID_IMAPIFormInfo      ");
    else if (IsEqualIID(riid, IID_IMAPIFormMgr        )) lstrcpy(szName,"IID_IMAPIFormMgr       ");
    else if (IsEqualIID(riid, IID_IMAPIFormContainer  )) lstrcpy(szName,"IID_IMAPIFormContainer ");
    else if (IsEqualIID(riid, IID_IMAPIForm           )) lstrcpy(szName,"IID_IMAPIForm          ");
    else                                                 lstrcpy(szName,"                       ");
#endif

    wsprintf(ach, "{%8.8lX-%4.4X-%4.4X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X} %s",
        riid.Data1,
        (int)riid.Data2,
        (int)riid.Data3,
        (int)riid.Data4[0],
        (int)riid.Data4[1],
        (int)riid.Data4[2],
        (int)riid.Data4[3],
        (int)riid.Data4[4],
        (int)riid.Data4[5],
        (int)riid.Data4[6],
        (int)riid.Data4[7],
        szName);
    return ach;
    }
#else
;
#endif
#else
#define TRACEFUNC   ;/##/
//#define TRACEFUNC
#define ASSERTFUNC(_1,_2,_3,_4)
#define DumpCLSID(_x) 0
#endif


DLG.H

// Menu ID's

#define IDM_EXIT            (WM_USER+4)

#define IDM_GAME_CHECKERS   (WM_USER+10)
#define IDM_GAME_GIVEAWAY   (WM_USER+11)
#define IDM_PLAYERS         (WM_USER+13)

#define IDM_BACK            (WM_USER+20)
#define IDM_FORWARD         (WM_USER+21)
#define IDM_SETUP_BOARD     (WM_USER+22)
#define IDM_CONTINUE        (WM_USER+23)

#define IDM_FLIP            (WM_USER+53)
#define IDM_SETDRAWMOVES    (WM_USER+72)
#define IDM_MUST_JUMP       (WM_USER+73)

#define IDM_ABOUT           (WM_USER+79)

// Some dialog ID's

#define IDT_COMPANY_NAME    (WM_USER+80)
#define IDT_FILE_DESC       (WM_USER+81)
#define IDT_PROD_VER        (WM_USER+82)
#define IDT_COPYRIGHT       (WM_USER+83)
#define IDT_TRADEMARKS      (WM_USER+84)

#define DLG_VERFIRST        IDT_COMPANY_NAME
#define DLG_VERLAST         IDT_TRADEMARKS

// Setup menu ID's

#define IDM_SETUP_EXIT      (WM_USER+93)

// Other ID's

#define WM_OTHERINIT        (WM_USER+100)

// SetWeights dialog

#define IDEC_PIECE                  111
#define IDEC_BLANK                  112
#define IDEC_MOVE_THEORY            113
#define IDEC_CENTER_POSITION        114
#define IDEC_DOUBLE_CORNERS         115
#define IDEC_KING_ROW               116
#define IDEC_KING                   118
#define IDEC_EDGES                  119

// Config Depth Dialog

#define IDEC_RECURSION_DEPTH        201
#define ID_HELP                     203
#define IDEC_PRUNING_DEPTH          204
#define IDEC_PRUNING_WIDTH          206
#define IDRB_P1_HUMAN               103
#define IDRB_P1_COMPUTER            110
#define IDRB_P1_NETWORK             117
#define IDRB_P2_HUMAN               121
#define IDRB_P2_COMPUTER            122
#define IDRB_P2_NETWORK             123
#define IDB_P1_COMPUTER_SETUP       3
#define IDB_P1_NETWORK_SETUP        4
#define IDB_P2_COMPUTER_SETUP       8
#define IDB_P2_NETWORK_SETUP        9
#define IDCB_MUST_JUMP              101
#define IDCB_OPENING_BOOK           102
#define IDCB_SHOW_COMMAND_WIN       105
#define IDCB_SHOW_MOVES             106
#define IDCB_SHOW_TIMER             107
#define IDCB_SHOW_COMMAND           108
#define IDCB_SHOW_OPEN_INFO         129
#define IDCB_SHOW_DEBUG             130
#define IDCB_USE_MEM_POSITIONS      104
#define IDCB_USE_MOVE_THEORY        109
#define IDCB_MOVE_SHUFFLING         120
#define IDB_WEIGHTS                 124
#define IDCB_EQUAL_MOVE_SKIPPING    125
#define IDCB_GOOD_MOVE_SKIPPING     126
#define IDCB_ALPHA_BETA_PRUNING     127
#define IDCB_USE_OPENING_BOOK       128
#define IDEC_BOOK_MOVE_DEPTH        131
#define IDEC_FORMATION              132
#define IDEC_FORMATION_CUTOFF       133
#define IDB_PLAYER1                 137
#define IDB_PLAYER2                 138
#define IDCB_SHORT_JUMP_EVAL        134
#define IDRB_SHORT_JUMP_EVAL        135
#define IDEC_MAX_MOVES              136

// Form specific menu items and helper messages

#ifndef NO_FORM
#define EM_GIVEFORMTOHWND           (WM_USER + 1000)
#define IDM_ADDRESS                 (WM_USER + 1001)
#define IDM_HINT                    (WM_USER + 1002)
#define IDM_SAVE                    (WM_USER+2)
#endif

#define IDM_BASE_LEVEL              (WM_USER + 5000) // from IDM_BASE_LEVEL through base level plus 20 are reserved


ENGINE.CPP

/* --------------------------------------------------------------------------

Basic Forms example of a custom sendable form.  It is an EXE server
rather than a DLL.  It implements the minimum form interface required
to launch and send a form.

Copyright (C) 1995 Microsoft Corporation

-------------------------------------------------------------------------- */

/* facilitate compilation accross modules without losing any optimizations
   of the engine */

#define WINDOWS

#include <windows.h>  // user interface stuff follows
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <malloc.h>
#include <memory.h>
#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <search.h>
#include <stdarg.h>

#ifdef _WIN32
#include <winver.h>
#else
#include <ver.h>
#endif

extern struct   rCheckConfigRec rConfig;
extern class    CMoveList CMoves;
extern struct   rGameStateRec rGameState;

extern unsigned int _charmax;

//---[ Local header user interface dependencies ]-----------------------------------------

#include "check.h"
#include "dlg.h"
#include "wcheck.h"
#include "checkdta.h"
#include "movelist.h"
#define DEBUG_CODE
#include "dbugit.h"

/* user interface externals */
#ifdef DEBUG
ASSERTDATA
#endif

/* --------------------------------------------------------------------------
Global variables
-------------------------------------------------------------------------- */

int debug=0;              /* these variables should not be used outside the
                             scope of the checkers engine */

int computer_color = BLACK;
int depth_maximum = 5;

#ifndef DEBUG
#pragma optimize("agtw",on)
#endif

#include ".\engine\lut.cpp"
#include ".\engine\debugio.cpp"
#include ".\engine\quality.cpp"
#include ".\engine\check.cpp"
#include ".\engine\prune.cpp"
#include ".\engine\valid.cpp"


FORM.CPP

/* --------------------------------------------------------------------------

Basic Forms example of a custom sendable form.  It is an EXE server
rather than a DLL.  It implements the minimum form interface required
to launch and send a form.

Copyright (C) 1995 Microsoft Corporation

-------------------------------------------------------------------------- */

//#define ALLOW_SUBCLASS_IPM // all other forms to subclass the reply behavior
                             // of this form (slower, but more correct)

#include <windows.h>
#include <windowsx.h>
#include <ole2.h>
#include <initguid.h>
#include <mapiform.h>
#define INITGUID
#include <initguid.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapinls.h>
#include "dbugit.h"
#include "check.h"
#include "form.h"
#include "dlg.h"

/*
 *
 * Checkers form clsid.  This must match the configuration file.
 *
 */

DEFINE_GUID(CLSID_MyFormsClsId,  0x86174010, 0x5030, 0x0076, 0x99, 0x12, 0x00, 0xaa, 0x00, 0x38, 0x90, 0x1b);

/*
 *  HrStartOleAndRegisterClassFactory
 *
 *  Purpose:
 *      Initialize OLE, MAPI, and the Forms Interface
 *      Should be called from WinMain() or InitApplication() in an SDI app
 *
 *      This function LoadLibraries the neccessary DLLs rather than
 *      linking with them.  This permits the form to run as a stand-
 *      alone executable even when MAPI and OLE are not installed.
 *
 *  Returns:
 *      HRESULT
 */

#ifdef _WIN32
#define szOleDll  "ole32.dll"
#define szMapiDll "mapi32.dll"
#else
#define szOleDll  "compobj.dll"
#define szMapiDll "mapi.dll"
#endif

HINSTANCE hinstOle   = NULL;
HINSTANCE hinstMapi  = NULL;

typedef HRESULT (FAR PASCAL *LPFNCOREGISTERCLASSOBJECT)(REFCLSID rclsid,
    IUnknown FAR * pUnk, DWORD dwClsContext, DWORD flags, LPDWORD lpdwRegister);
#ifdef WIN16
typedef BOOL (FAR PASCAL *LPFNISEQUALGUID)(REFGUID rguid1, REFGUID rguid2);
#undef IsEqualIID
#define IsEqualIID(riid1, riid2) (*lpfnIsEqualGUID)(riid1, riid2)
#endif
typedef HRESULT (FAR PASCAL *LPFNHRQUERYALLROWS)(LPMAPITABLE ptable,
                        LPSPropTagArray ptaga, LPSRestriction pres,
                        LPSSortOrderSet psos, LONG crowsMax,
                        LPSRowSet FAR *pprows);
typedef ULONG   (FAR PASCAL *LPFNMAPIFREEBUFFER)(LPVOID pv);
typedef HRESULT (FAR PASCAL *LPFNMAPIINITIALIZE)(LPVOID lpvReserved);
typedef void    (FAR PASCAL *LPFNMAPIUNINITIALIZE)(VOID);
typedef void    (FAR PASCAL *LPFNMAPIFREEPADRLIST)(LPADRLIST);

LPFNCOREGISTERCLASSOBJECT lpfnCoRegisterClassObject;
#ifdef WIN16
LPFNISEQUALGUID           lpfnIsEqualGUID;
#endif
LPFNHRQUERYALLROWS        lpfnHrQueryAllRows       ;
LPFNMAPIFREEBUFFER        lpfnMAPIFreeBuffer       ;
LPFNMAPIINITIALIZE        lpfnMAPIInitialize       ;
LPFNMAPIUNINITIALIZE      lpfnMAPIUninitialize     ;
LPFNMAPIFREEPADRLIST      lpfnFreePadrlist          ;

HRESULT
HrStartOleAndRegisterClassFactory(void)
{
    FRMFMR *    pfrmfmr = NULL;
    HRESULT     hr;

    TraceTag(tagFormFunc,"HrStartOleAndRegisterClassFactory");

    // ----- LoadLibrary the essentials
    hinstOle   = LoadLibrary(szOleDll);
    hinstMapi  = LoadLibrary(szMapiDll);
    #ifdef WIN16
    if (hinstOle   < HINSTANCE_ERROR) hinstOle   = 0;
    if (hinstMapi  < HINSTANCE_ERROR) hinstMapi  = 0;
    #endif
    if (0 == hinstOle || 0 == hinstMapi)
        {
        return ResultFromScode(E_FAIL);
        }

    // ----- Setup a few function pointers
    lpfnCoRegisterClassObject = (LPFNCOREGISTERCLASSOBJECT) GetProcAddress(hinstOle,  "CoRegisterClassObject");
#if defined(_WIN32)
    #if defined(_X86_)
    lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows@24");
    lpfnFreePadrlist          = (LPFNMAPIFREEPADRLIST     ) GetProcAddress(hinstMapi,"FreePadrlist@4");
    #else
    lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows");
    lpfnFreePadrlist          = (LPFNMAPIFREEPADRLIST     ) GetProcAddress(hinstMapi,"FreePadrlist");
    #endif //_X86_
#else
    lpfnIsEqualGUID           = (LPFNISEQUALGUID          ) GetProcAddress(hinstOle,  "IsEqualGUID");
    lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows");
    lpfnFreePadrlist          = (LPFNMAPIFREEPADRLIST     ) GetProcAddress(hinstMapi,"FreePadrlist");
#endif //_WIN32
    lpfnMAPIFreeBuffer        = (LPFNMAPIFREEBUFFER       ) GetProcAddress(hinstMapi,"MAPIFreeBuffer");
    lpfnMAPIInitialize        = (LPFNMAPIINITIALIZE       ) GetProcAddress(hinstMapi,"MAPIInitialize");
    lpfnMAPIUninitialize      = (LPFNMAPIUNINITIALIZE     ) GetProcAddress(hinstMapi,"MAPIUninitialize");
    AssertSz(lpfnCoRegisterClassObject ,"missing lpfnCoRegisterClassObject");
    AssertSz(lpfnHrQueryAllRows        ,"missing lpfnHrQueryAllRows       ");
    AssertSz(lpfnMAPIFreeBuffer        ,"missing lpfnMAPIFreeBuffer       ");
    AssertSz(lpfnMAPIInitialize        ,"missing lpfnMAPIInitialize       ");
    AssertSz(lpfnMAPIUninitialize      ,"missing lpfnMAPIUninitialize     ");
    AssertSz(lpfnFreePadrlist          ,"missing lpfnFreePadrlist         ");
    if (0 == lpfnCoRegisterClassObject ||
        0 == lpfnHrQueryAllRows        ||
        0 == lpfnMAPIFreeBuffer        ||
        0 == lpfnMAPIInitialize        ||
        0 == lpfnMAPIUninitialize      ||
        0 == lpfnFreePadrlist)
        {
        AssertSz(0,"get procaddress failed");
        return ResultFromScode(E_FAIL);
        }

    // ----- Initialize MAPI
    hr = (*lpfnMAPIInitialize)(NULL);
    if (S_OK != hr)
        {
        TraceTag(tagForm,"MapiInit failed 0x%08lx",hr);
        return hr;
        }

    // ----- Allocate Memory for our class factory
    pfrmfmr = new FRMFMR;
    if (NULL == pfrmfmr)
        {
        TraceTag(tagForm, "RegisterClassFactory: OOM 0x%08lx",hr);
        hr = ResultFromScode(E_OUTOFMEMORY);
        return hr;
        }

    // ----- Register our class object(s)
    DWORD dwRegMyForm = 0;
    hr = (*lpfnCoRegisterClassObject) (CLSID_MyFormsClsId, (LPUNKNOWN)pfrmfmr,
            CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
            &dwRegMyForm); /* switch singleuse to multipleuse if you are an MDI app */
    if (FAILED(hr))
        {
        TraceTag(tagForm,"CoRegisterClassObject() failed 0x%08lx",hr);
        return hr;
        }

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  HrStopForms
 *
 *  Purpose:
 *      UnInitialize OLE, MAPI, and the Forms Interface
 *
 *  Returns:
 *      HRESULT == 0
 */
HRESULT
HrStopForms(void)
{
    HRESULT hr = ResultFromScode(S_OK);

    TraceTag(tagFormFunc,"HrStopForms");

    (*lpfnMAPIUninitialize)();

    FreeLibrary(hinstOle);
    FreeLibrary(hinstMapi);

    return hr;
}

/*
 *  S a m p l e   F o r m
 */

// Checkers form specific methods follow ///////////////////////////

/*
 *  FRM::ScGetRecipientAdrList
 *
 *  Purpose:
 *      Fill the addrlist with the current recipients in the message
 *
 *  Arguments:
 *      LPMESSAGE - the message (in)
 *      LPADRLIST - the addr list destination (out)
 *
 *  Returns:
 *      SCODE       Error status.
 */
SCODE
FRM::ScGetRecipientAdrlist(LPMESSAGE pmsg, LPADRLIST * ppal)
{
    SCODE           sc      = S_OK;
    HRESULT         hr;
    LPMAPITABLE     pmt     = NULL;
    LPSPropTagArray ptaga   = NULL;

    TraceTag(tagFormFunc,"ScGetRecipientAdrlist");
    AssertSz(!*ppal, "pal should be NULL on entry");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // Get the recipient table from the message
    if (hr = pmsg->GetRecipientTable(MAPI_DEFERRED_ERRORS, &pmt))
    {
        TraceTag(tagForm,"2043 0x%08lx",hr);
        goto Cleanup;
    }

    if (hr = pmt->QueryColumns(TBL_ALL_COLUMNS, &ptaga))
    {
        TraceTag(tagForm,"sdlkfj 0x%08lx",hr);
        goto Cleanup;
    }

#ifdef NEVER
    ConvertToCorrectCharset(ptaga);
#endif

    // Read in the recipients
    hr = (*lpfnHrQueryAllRows)(pmt, ptaga, NULL, NULL, 0, (LPSRowSet *) ppal);
    if (hr)
    {
        TraceTag(tagForm,"sdfhjsadjfhadkflhxxxx 0x%08lx",hr);
        goto Cleanup;
    }

Cleanup:
    TraceTag(tagForm,"ScGetRecipientAdrlist 0x%08lx 0x%08lx", hr, sc);

    (*lpfnMAPIFreeBuffer)(ptaga);
    ReleaseObj(pmt);

    TraceTag(tagForm,"return 0x%08lx",ResultFromScode(sc));
    return sc;
}

/*
 *  FRM::SendForm
 *
 *  Purpose:
 *      Have the message site send us
 *      (also tries to send the message using mapi if message site fails)
 *
 *  Arguments:
 *      None.
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::SendForm(VOID)
{
    HRESULT hr = S_OK;

    TraceTag(tagFormFunc,"FRM::SendForm (this is not a standard form function)");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pMessage);

    // ----- Submit message
    if (hr = pMessageSite->SubmitMessage(0) )
    {
        TraceTag(tagForm,"failure pMessageSite->SubmitMessage 0x%08lx",hr);

        #ifndef GOOD_FORM_BEHAVIOR // the following is not standard behavior

        // ----- No harm in trying to send it myself
        if (IDYES == MessageBox(hMainWnd, "The message site failed to send this form.\nDo you want this form to attempt sending through the message interface?", "Checkers", MB_YESNO | MB_ICONSTOP))
        {
            // this is often times easier to debug than the remoted
            // message site interface (which does the same thing .. almost)

            if (hr = Save(NULL,TRUE))
                {
                TraceTag(tagForm,"NO: ::Save 0x%08lx",hr);
                }

            HandsOffMessage();

            if (hr = pMessage->SaveChanges(KEEP_OPEN_READWRITE) )
                {
                TraceTag(tagForm,"NO: pMessage->SaveChanges 0x%08lx",hr);
                }
            if (hr = pMessage->SubmitMessage(0) )
                {
                TraceTag(tagForm,"NO: pMessage->SubmitMessage 0x%08lx",hr);
                }
        }

        #endif // good_form_behavior
    }

    // ----- advise everyone of what we just did
    ADVISE(OnSubmitted)();

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  FRM::LaunchReplyMessage
 *
 *  Purpose:
 *      Construct a reply to PR_SENDER* (note: ignoring sent representing)
 *      Display any form user interface on the existing form
 *
 *  Arguments:
 *      HWND                Parent window
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::LaunchReplyMessage(ULONG ulhwndParent)
{
    #ifdef ALLOW_SUBCLASS_IPM
    LPMAPIFORM pNewForm;
    LPPERSISTMESSAGE pNewFormIPersist;
    #endif

    ULONG itaga;
    ADRLIST al = {1,0}; /* our adrlist will have exactly one entry */
    HRESULT hr = S_OK;
    LPMAPIMESSAGESITE pNewMessageSite;
    LPMAPIVIEWCONTEXT pNewMapiViewContext;
    LPMESSAGE pNewMessage;

    SizedSPropTagArray(6,tagaSender) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_ENTRYID,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_SEARCH_KEY } };
    SizedSPropTagArray(6,tagaRepliee) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_DISPLAY_NAME,
            PR_ADDRTYPE,
            PR_ENTRYID,
            PR_EMAIL_ADDRESS,
            PR_SEARCH_KEY
        } };
    static SizedSPropTagArray(26,tagaRemoveFromNewReply) =
        { 26,
        {   // Stuff you would typically want to remove on reply
            PR_MESSAGE_FLAGS,               // Want unsent compose note
            PR_MESSAGE_RECIPIENTS,          // Will generate new recip list
            PR_SENDER_ENTRYID,              // Clear sender/recipient info
            PR_SENDER_NAME,                 //
            PR_RECEIVED_BY_ENTRYID,         //
            PR_RECEIVED_BY_NAME,            //
            PR_SENT_REPRESENTING_ENTRYID,   // Clear delegate access stuff
            PR_SENT_REPRESENTING_NAME,      //
            PR_SENT_REPRESENTING_ADDRTYPE,  // 10961
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_RCVD_REPRESENTING_ENTRYID,   //
            PR_RCVD_REPRESENTING_NAME,      //
            PR_READ_RECEIPT_ENTRYID,        // Clear destination overrides
            PR_REPORT_ENTRYID,              //
            PR_REPLY_RECIPIENT_ENTRIES,     //
            PR_REPLY_RECIPIENT_NAMES,       //
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, // Clear delivery receipt
            PR_READ_RECEIPT_REQUESTED,      // Clear read receipt
            PR_CLIENT_SUBMIT_TIME,          // Clear submit time
            PR_MESSAGE_ATTACHMENTS,         // Drop attachments on reply
            PR_ORIGINAL_AUTHOR_ENTRYID,     // Keep original author information
            PR_ORIGINAL_AUTHOR_NAME,        //  on forwards
            PR_ORIGINAL_SUBMIT_TIME,        // Keep original time on forwards
            PR_IMPORTANCE,                  // Lose importance on reply
            PR_PRIORITY,                    // Lose priority on reply
            PR_SENSITIVITY                  // Lose sensitivity on reply
        } };

    TraceTag(tagFormFunc,"FRM::LaunchReplyMessage");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    #ifdef ALLOW_SUBCLASS_IPM

    /*
       Since I am a single instance exe, creating a new form results in
       a new process.  For performance reasons, this form does not conform
       to the forms API precisely. This effectively removes the ability to
       subclass the reply note for IPM.Checkers.  This is acceptable.

    */

    // ----- open form manager
    AssertSz(NULL == pFormMgr,"two form managers?");
    hr = pMessageSite->GetFormManager(&pFormMgr);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure MAPIOpenFormMgr 0x%08lx",hr);
        return hr;
        }
    Assert(pFormMgr);

    // ----- Get form info
    hr = pFormMgr->ResolveMessageClass("IPM.Checkers",0,NULL,&pFormInfo);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure to ResolveMessageClass 0x%08lx",hr);
        return hr;
        }

    // ----- Create the new form
    hr = pFormMgr->CreateForm(0,0,pFormInfo,IID_IMAPIForm,(LPVOID FAR*) &pNewForm);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure to CreateForm 0x%08lx",hr);
        return hr;
        }

    hr = pNewForm->QueryInterface(IID_IPersistMessage, (LPVOID FAR*) &pNewFormIPersist);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure asking form for IPersistMessage 0x%08lx",hr);
        return hr;
        }
    AssertSz(pNewFormIPersist,"have it?");

    #endif // ALLOW_SUBCLASS_IPM

    // ----- Create the reply message
    hr = pMessageSite->NewMessage(0,NULL,
        #ifdef ALLOW_SUBCLASS_IPM
        pNewFormIPersist
        #else
        this
        #endif // ALLOW_SUBCLASS_IPM
        ,&pNewMessage,&pNewMessageSite,&pNewMapiViewContext);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure VDOG_*NewMessage* comform...c 0x%08lx",hr);
        return hr;
        }
    AssertSz(pNewMessage,"nothing new with you");
    AssertSz(pNewMessageSite,"no new site?");
    AssertSz(pNewMapiViewContext,"no new view context ... did NewMessage work at all?");

    // ----- Copy current message to new message
    hr = pMessage->CopyTo(0, NULL, (LPSPropTagArray)&tagaRemoveFromNewReply, 0, NULL,
                                    (LPIID) &IID_IMessage, pNewMessage, 0, NULL);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure CopyTo 0x%08lx",hr);
        return hr;
        }

    // ----- who sent this to us?
    hr = pMessage->GetProps((LPSPropTagArray) &tagaSender, 0, &al.aEntries[0].cValues, &al.aEntries[0].rgPropVals);
    AssertSz(ResultFromScode(MAPI_W_ERRORS_RETURNED) == hr,"mapi gave me pr_recipient_type, but should not have");

    // ----- Make the sender the recipient
    if (al.aEntries && al.aEntries[0].rgPropVals)
    {
        al.aEntries[0].rgPropVals[0].ulPropTag = PR_RECIPIENT_TYPE;
        al.aEntries[0].rgPropVals[0].Value.ul = MAPI_TO;
    }
    else
    {
        AssertSz(0,"could not form reply message: al.aEntries && al.aEntries[0].rgPropVals");
        return ResultFromScode(E_FAIL);
    }

    // ----- Set our new recipients properties to their expected property ids
    itaga = 1;

    TraceTag(tagForm,"0x%08lx cEntries",al.cEntries);
    TraceTag(tagForm,"0x%08lx <0x%08lx> %s",al.aEntries[0].rgPropVals[itaga].ulPropTag,al.aEntries[0].rgPropVals[itaga].Value.ul,al.aEntries[0].rgPropVals[itaga].Value.LPSZ);

    for (itaga = 1; itaga < tagaRepliee.cValues; itaga++)
        {
        al.aEntries[0].rgPropVals[itaga].ulPropTag =
            PROP_TAG(PROP_TYPE(al.aEntries[0].rgPropVals[itaga].ulPropTag),
                PROP_ID(tagaRepliee.aulPropTag[itaga]));

        TraceTag(tagForm,"0x%08lx <0x%08lx> %d",al.aEntries[0].rgPropVals[itaga].ulPropTag,al.aEntries[0].rgPropVals[itaga].Value.ul,itaga);

        AssertSz(SUCCEEDED(al.aEntries[0].rgPropVals[itaga].Value.ul),"Failure to get PR_SENDER* 10961 ");
        }

    // ----- Save out addresses
    AssertSz(1 == al.cEntries,"we only reply to one person");
    hr = pNewMessage->ModifyRecipients(0, &al);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"pMessage->ModifyRecipients 0x%08lx",hr);
        return hr;
        }

    // ----- Release everything the read form was remembering (if we reply inplace)
    #ifndef ALLOW_SUBCLASS_IPM
    Forget();
    #endif

    // ----- Call LoadForm (this makes the current form the new form)
    hr =
    #ifdef ALLOW_SUBCLASS_IPM
    pNewFormIPersist->
    #endif
    Load(pNewMessageSite,pNewMessage,0,MSGFLAG_UNSENT);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure LoadForm 0x%08lx",hr);
        return hr;
        }

    // ----- Call SetViewContext
    #ifdef ALLOW_SUBCLASS_IPM
    hr = pNewForm->SetViewContext(pNewMapiViewContext);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure SetViewContext 0x%08lx",hr);
        return hr;
        }
    #endif

    // ----- Call DoVerb So we can see the reply form
    hr =
    #ifdef ALLOW_SUBCLASS_IPM
    pNewForm->
    #endif
    DoVerb(OLEIVERB_PRIMARY,NULL,ulhwndParent,NULL);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure DoVerb 0x%08lx",hr);
        return hr;
        }

    // ----- release stuff
    pNewMessage->Release();
    pNewMessageSite->Release();
    pNewMapiViewContext->Release();
    (*lpfnMAPIFreeBuffer)(al.aEntries[0].rgPropVals);

    // ----- Close down the read form (that's me)
    #ifdef ALLOW_SUBCLASS_IPM
    hr = ShutdownForm(OLECLOSE_NOSAVE);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure ShutdownForm'ing read form 0x%08lx",hr);
        return hr;
        }
    #endif // ALLOW_SUBCLASS_IPM

    // ----- return to caller
    return hr;

}

/*
 *  FRM::GetCheckersData
 *
 *  Purpose:
 *      Allows anyone to query the form for it's current data
 *
 *  Arguments:
 *      data members (out)
 *
 *  Returns:
 *      void
 */
VOID
FRM::GetCheckersData(SQUARE* out_b, int* out_turn, long* out_movenum, long* out_score)
{
    Assert(out_b);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (turn) /* set elements only if we have data to give out */
    {
        memcpy(out_b,b,sizeof(b));
        if (out_turn) *out_turn = turn;
        if (out_movenum) *out_movenum = movenum;
        if (out_score) *out_score = score;
    }
}

/*
 *  FRM::SetCheckersData
 *
 *  Purpose:
 *      Allows anyone to set the forms current data members
 *
 *  Arguments:
 *      data members (in)
 *
 *  Returns:
 *      void
 */
VOID
FRM::SetCheckersData(SQUARE* in_b, int in_turn, long in_movenumber, long in_score)
{
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(in_b && in_turn);

    fDirty = TRUE;

    memcpy(b,in_b,sizeof(b));
    turn    = in_turn;
    movenum = in_movenumber;
    score   = in_score;
}

/*
 *  FRM::AddressForm
 *
 *  Purpose:
 *      Look at the current message addresses, and show user
 *      interface to address the message.
 *
 *  Arguments:
 *      HWND - parent
 *      BOOL - true if no user interface should be presented when
 *             recipients are already present
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::AddressForm(HWND hwnd, BOOL fDontShowIfRecipsExist)
{
    LPADRBOOK   pAdrBook;
    ULONG       ulUIParam = (ULONG) (UINT) hwnd;
    SCODE       sc;
    HRESULT     hr = S_OK;
    ADRPARM     ap = { 0 };
    LPADRLIST   pal = NULL;
    BOOL        fCloseForm = FALSE;

    TraceTag(tagFormFunc,"FRM::AddressForm");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    // ----- Read in addresses from message
    sc = ScGetRecipientAdrlist(pMessage, &pal);
    hr = ResultFromScode(sc);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failed to read address into pal 0x%08lx",hr);
        return hr;
        }

    // ----- remember address book from the session
    hr = pSession->OpenAddressBook(ulUIParam, NULL, 0, &pAdrBook);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failed to get pAdrBook object 0x%08lx",hr);
        goto cleanuppal;
        }

    // ----- Show the address book
    ap.ulFlags = AB_RESOLVE | DIALOG_OPTIONS | DIALOG_MODAL | fMapiUnicode;
    ap.lpszCaption = TEXT("Select Checkers Opponent");
    ap.cDestFields = 1;
    if (0 == fDontShowIfRecipsExist || 0 == pal->cEntries)
        hr = pAdrBook->Address(&ulUIParam, &ap, &pal);
    #ifdef DEBUG
    if (hwnd != hMainWnd)
    {
        TraceTag(tagNull,"ADDRESSFORM: pAdrBook->Address changed it's out parameter even though DIALOG_MODAL");
    }
    #endif
    if (FAILED(hr))
    {
        // cancel is a failed scode
        TraceTag(tagForm,"cant pop up addr book 0x%08lx %d",hr,pal?pal->cEntries:0);
    }

    // ----- Save out addresses
    AssertSz(pMessage,"pMessage said goodbye during the Address function");
    hr = pMessage->ModifyRecipients(0, pal);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"pMessage->ModifyRecipients 0x%08lx",hr);
        goto cleanup;
        }

    // ----- Close the form if there are no recipients for this move
    if (pal->cEntries <= 0) fCloseForm = TRUE;

    // ----- Release the address book, adrlist, and clean up
cleanup:
    Assert(pAdrBook);
    pAdrBook->Release();
cleanuppal:
    Assert(pal);
    (*lpfnFreePadrlist)(pal);
    if (fCloseForm) ShutdownForm(OLECLOSE_PROMPTSAVE);

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  FRM::Remember
 *
 *  Purpose:
 *      Store and addref the message site, the message, and the session
 *      for later use
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::Remember(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg)
{
    HRESULT hr;

    TraceTag(tagFormFunc,"FRM::Remember");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pmsite,"what am I going to do without it?");
    AssertSz(pmsg,"what am I going to do without it? pmsg that is");

    // ----- remember our message site object
    AssertSz(!pMessageSite,"who me? a message site?");
    pMessageSite = pmsite;
    pMessageSite->AddRef();

    // ----- remember our message
    AssertSz(!pMessage,"a message in my");
    pMessage = pmsg;
    pMessage->AddRef();

    // ----- remember mapi session
    AssertSz(!pSession,"another session?");
    hr = pMessageSite->GetSession(&pSession);

    #ifdef DEBUG
    if (FAILED(hr))
    {
        TraceTag(tagForm,"failed to get session object %08lx",hr);
    }
    #endif

    // ----- return result to caller
    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;

}

/*
 *  FRM::Forget
 *
 *  Purpose:
 *      Release the message site, the message, and the session
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::Forget(VOID)
{
    TraceTag(tagFormFunc,"FRM::Forget");

    if (pMessage) pMessage->Release();
    if (pMessageSite) pMessageSite->Release();
    if (pSession) pSession->Release();

    pMessage = NULL;
    pMessageSite = NULL;
    pSession = NULL;

    return NOERROR;
}

/*
 *  FRM::ShowCurrentMessage
 *
 *  Purpose:
 *      Display any form user interface on a form
 *
 *  Arguments:
 *      HWND                Parent window
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::ShowCurrentMessage(ULONG ulhwndParent)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::ShowCurrentMessage");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    // ----- Give our user access to our form interface
    SendMessage(hMainWnd,EM_GIVEFORMTOHWND,0,(LPARAM) this);

    // ----- Display address book modal to form if this message has not yet been addressed
    FORWARD_WM_COMMAND(hMainWnd, IDM_ADDRESS, 0, 1 /* Don't Show Recips */, PostMessage);

    return hr;
}

// IUnknown methods follow ///////////////////////////

/*
 *  FRM::QueryInterface
 *
 *  Purpose:
 *      Returns a pointer to the specified interface.
 *
 *  Arguments:
 *      REFIID              Interface we want.
 *      LPUNKNOWN *         Interface we return.
 *
 *  Returns:
 *      HRESULT             Error status.
 */
STDMETHODIMP
FRM::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFuncTriv,"FRM::QueryInterface %s",DumpCLSID(riid));
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (IsEqualIID(riid, IID_IUnknown))
        {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
        }
    else if (IsEqualIID(riid, IID_IPersistMessage))
        {
        AddRef();
        *ppvObj = (IPersistMessage *)this;
        }
    else if (IsEqualIID(riid, IID_IMAPIForm))
        {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
        }
    else
        {
        hr = ResultFromScode(E_NOINTERFACE);
        *ppvObj = NULL;
        }

    #ifdef DEBUG
    if (hr != ResultFromScode(E_NOINTERFACE)) AssertSz(ppvObj,"no object pointer");
    #endif

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  FRM::AddRef
 *
 *  Purpose:
 *      Increments reference count on the sample extension.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG               New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRM::AddRef(void)
{
    TraceTag(tagFuncTriv,"FRM::AddRef ret %d",cRef + 1);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    return ++cRef;
}

/*
 *  FRM::Release
 *
 *  Purpose:
 *      Decrements reference count on the sample extension.  If count is
 *      decremented to zero, the object is freed.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG               New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRM::Release(void)
{
    TraceTag(tagFuncTriv,"FRM::Release cRef %d",cRef);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (!(--cRef))
        {
        // ----- be sure our ui is gone when we leave
        #ifdef DEBUG
        if (IsWindow(hMainWnd))
            {
            TraceTag(tagForm,"Last Release called, but IsWindow(hMainWnd).  ShutdownForm called?");
            }
        TraceTag(tagForm,"return 0");
        #endif //debug
        delete this;
        return 0;
        }
    return cRef;
}

// IPersistMessage methods follow ///////////////////////////

/*
 *  FRM::GetLastError
 *
 *  Purpose:
 *      Get the last error
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             NOERROR always.
 */
STDMETHODIMP
FRM::GetLastError(HRESULT hResult, ULONG ulFlags, LPMAPIERROR FAR * lppMAPIError)
{
    TraceTag(tagFormFunc,"FRM::GetLastError 0x%08x",hResult);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    if (lppMAPIError) *lppMAPIError = NULL;
    return NOERROR;
}

/*
 *  FRM::GetClassID
 *
 *  Purpose:
 *      Get the class ID associated with this message.

*
 *  Arguments:
 *      LPCLSID             Where to put the class ID.
 *
 *  Returns:
 *      HRESULT             NOERROR always.
 */
STDMETHODIMP
FRM::GetClassID(LPCLSID pclsid)
{

    TraceTag(tagFormFunc,"FRM::GetClassID");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // The form only plays with things of its own class ID, so
    // this is easy; it's more complicated if code supports multiple
    // classes, or can do "treat as" operations
    if (pclsid) *pclsid = clsid;

    return NOERROR;
}

/*
 *  FRM::IsDirty
 *
 *  Purpose:
 *      Returns whether the object has changed since the last save
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             S_OK if dirty, S_FALSE if not dirty.
 */
STDMETHODIMP
FRM::IsDirty(void)
{
    TraceTag(tagFormFunc,"FRM::IsDirty");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    return fDirty ? NOERROR : ResultFromScode(S_FALSE);
}

/*
 *  FRM::InitNew
 *
 *  Purpose:
 *      Create a new message of our message class in the provided pmsg.
 *
 *  Arguments:
 *      LPMAPISESSION       Session in which the message belongs.
 *      LPMESSAGE           Message to create the new form in.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::InitNew(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg)
{
    HRESULT             hr;
    SPropValue          prop;

    TraceTag(tagFormFunc,"FRM::InitNew");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
    {
        TraceTag(tagForm,"loss of memory in initnew 0x%08lx",hr);
        return hr;
    }

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT("IPM.Checkers");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in initnew 0x%08lx",hr);
        return hr;
        }

    // ----- remind ourselves that this new message could not have been sent
    fSentMessage = 0;

    // ----- set our special properties
    prop.ulPropTag  = PR_SUBJECT;
    prop.Value.LPSZ = TEXT("--- CHECKERS FORM ---");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_subject here 0x%08lx",hr);
        return hr;
        }

    ADVISE(OnNewMessage)();
    return hr;
}

/*
 *  FRM::Load
 *
 *  Purpose:
 *      Attaches our object to the provided pmsg.
 *
 *  Arguments:
 *      LPMAPISESSION       Our session to remember.
 *      LPMESSAGE           Our message to remember.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::Load(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg, ULONG ulMessageStatus, ULONG ulMessageFlags)
{
    ULONG cProps = 0;
    #define ctagMax 10
    char rgchTaga[sizeof(SPropTagArray) + (ctagMax * sizeof(ULONG))];
    LPSPropTagArray ptaga = (LPSPropTagArray) rgchTaga;
    LPSPropValue rgval = NULL;
    LPSPropValue pval = NULL;
    HRESULT         hr=S_OK;

    TraceTag(tagFormFunc,"FRM::Load status=0x%08lx flags=0x%08lx",ulMessageStatus,ulMessageFlags);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"loads call to remember failed 0x%08lx",hr);
        return hr;
        }

    // ----- If this message has been sent we would like to remember that
    fSentMessage = !( ulMessageFlags & MSGFLAG_UNSENT);
    TraceTag(tagForm,"fSentMessage = %d",(int) fSentMessage);

    // ----- Load our data out of the message like a nice form
    ptaga->cValues = 0;
    ptaga->aulPropTag[ptaga->cValues++] = PR_SUBJECT;
    ptaga->aulPropTag[ptaga->cValues++] = PR_BODY;
    ptaga->aulPropTag[ptaga->cValues++] = PR_BOARD;
    ptaga->aulPropTag[ptaga->cValues++] = PR_TURN;
    ptaga->aulPropTag[ptaga->cValues++] = PR_MOVENUMBER;

    hr = pmsg->GetProps(ptaga,0, &cProps, &rgval);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed getprops on pr_board there 0x%08lx",hr);
        return hr;
        }
    AssertSz(ptaga->cValues <= ctagMax, "Too many properties to read!");
    AssertSz(cProps == ptaga->cValues,"to mucho values");
    pval = rgval;

    // ----- set properties to variables
    pval;   // subject
    pval++; // body
    pval++; // board
    if (pval->Value.bin.cb == sizeof(b)) /* if it's a valid board */
        {
        memcpy(b,pval->Value.bin.lpb,(int) pval->Value.bin.cb);
        pval++; // turn
        AssertSz(pval->Value.l == RED || pval->Value.l == BLACK,"cool: neither red or blacks turn according to mapi");
        turn = (int) pval->Value.l;
        pval++; // movenumber
        }
    Assert(rgval);
    (*lpfnMAPIFreeBuffer)(rgval);
    ADVISE(OnNewMessage)();
    return hr;
}

/*
 *  FRM::Save
 *
 *  Purpose:
 *      Writes out our information to the provided pmsg. Does NOT commit
 *      changes; this is the responsibility of the caller. Puts the form
 *      into no-scribble mode until SaveCompleted is called.
 *
 *  Arguments:
 *      LPMESSAGE           Message to write our changes to.
 *      BOOL                TRUE if this is our home message, FALSE if
 *                          this is a different message.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::Save(LPMESSAGE pmsg, ULONG fSameAsLoad)
{
    SPropValue prop;
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::Save");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pMessage,"no pmesssg in ::Save");
    #ifdef DEBUG
    if (!pmsg)
    {
        TraceTag(tagNull,"NULL == pmsg in ::Save fsameasload==0x%08lx",fSameAsLoad);
    }
    #endif

    // ----- If this is the same pmsg as we got back when we loaded ...
    if (fSameAsLoad)
    {
        TraceTag(tagForm,"fSameAsLoad true");
        pmsg = pMessage;
    }

    // ----- Put ourselves in no-scribble mode
    fNoScribble = TRUE;

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT("IPM.Checkers");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in initnew 0x%08lx",hr);
        return hr;
        }

    // ----- Write out our data
    AssertSz(turn,"nobody's turn? this is not a good sign...");
    prop.ulPropTag  = PR_BOARD;
    prop.Value.bin.lpb = (unsigned char *) b;
    prop.Value.bin.cb  = sizeof(b);
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_board here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_TURN;
    prop.Value.l = (long) turn;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_MOVENUMBER;
    prop.Value.l = (long) movenum;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_SCORINGFUNC;
    prop.Value.l = (long) score;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_BODY;
    prop.Value.lpszA = TextizeBoard(b);
    TraceTag(tagForm,"Here's the board I saved:\n%s",prop.Value.lpszA);
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_body here 0x%08lx",hr);
        return hr;
        }

    ADVISE(OnSaved)();
    return hr;
}

/*
 *  FRM::SaveCompleted
 *
 *  Purpose:
 *      Terminates no-scribble and hands-off modes, returning the object
 *      to its normal storage mode.
 *
 *  Arguments:
 *      LPMESSAGE           Our new home message, if we need to change.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::SaveCompleted(LPMESSAGE pmsg)
{
    TraceTag(tagFormFunc,"FRM::SaveCompleted");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // Reset modes
    fDirty = FALSE;
    fNoScribble = FALSE;

    return NOERROR;
}

/*
 *  FRM::HandsOffMessage
 *
 *  Purpose:
 *      Releases our reference on the message so that a Save As operation
 *      can occur.
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::HandsOffMessage(void)
{
    TraceTag(tagFormFunc,"FRM::HandsOffMessage");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    return NOERROR;
}

// IMAPIForm methods follow /////////////////////////////

/*
 *  FRM::DoVerb
 *
 *  Purpose:
 *      Performs the specified verb on the message.
 *
 *  Arguments:
 *      LONG                What to do.
 *      LPMAPIVIEWCONTEXT   Our view context.
 *      HWND                Our parent window.
 *      LPCRECT             Where we should display ourselves given a choice.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::DoVerb(LONG iVerb, LPMAPIVIEWCONTEXT pmvc, ULONG ulhwndParent,
               LPCRECT prcPosRect)
{
    TraceTag(tagFormFunc,"FRM::DoVerb iVerb=%d",iVerb);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    switch (iVerb)
        {
        default:
        case OLEIVERB_HIDE:
        case OLEIVERB_DISCARDUNDOSTATE:
            TraceTag(tagForm,"DoVerb: not implemented iVerb");
            return ResultFromScode(E_NOTIMPL);

        case OLEIVERB_UIACTIVATE:
        case OLEIVERB_INPLACEACTIVATE:
            TraceTag(tagForm,"DoVerb: not implemented iVerb=%d",iVerb);
            if (iVerb < 0)
                return ResultFromScode(E_NOTIMPL);
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);

        case OLEIVERB_SHOW:
            ShowCurrentMessage(ulhwndParent);
            return NOERROR;

        case OLEIVERB_OPEN:
        case OLEIVERB_PRIMARY:
            TraceTag(tagForm,"fSentMessage = %d",(int) fSentMessage);
            if (fSentMessage)
                LaunchReplyMessage(ulhwndParent);
            else
                ShowCurrentMessage(ulhwndParent);

            return NOERROR;
        }
}

/*
 *  FRM::ShutdownForm
 *
 *  Purpose:
 *      Closes down any UI associated with the form.
 *
 *  Arguments:
 *      DWORD               One of OLECLOSE_SAVEIFDIRTY, OLECLOSE_NOSAVE,
 *                          or OLECLOSE_PROMPTSAVE.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::ShutdownForm(DWORD dwSaveOptions)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::ShutdownForm dwSaveOptions=%d",dwSaveOptions);
    TraceTag(tagForm,"pMessageSite 0x%08x",(ULONG) pMessageSite);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- no way I'm closeing if I'm in a modal dialog
    //       especially if the modal dialog occurs in a remoted interface
    if (!IsWindowEnabled(hMainWnd))
        return ResultFromScode(E_ABORT);

    // ----- be kind, and save ourself
    switch (dwSaveOptions)
    {
        case OLECLOSE_NOSAVE:
            break;

        case OLECLOSE_SAVEIFDIRTY:
            if (fDirty)
                hr = pMessageSite->SaveMessage();
            break;

        default:
        case OLECLOSE_PROMPTSAVE:
            if (fDirty)
                if (IDYES == MessageBox(hMainWnd, "Save changes?", "Checkers", MB_YESNO))
                    hr = pMessageSite->SaveMessage();
            break;

    }

    Assert(hMainWnd && IsWindow(hMainWnd));
    if (NOERROR == hr)
    {
        // ----- let everyone know we're shutting down
        ADVISE(OnShutdown)();

        // ----- Release everything we have remembered thus far
        Forget();

        // ----- make sure everyone has Unadvised
        AssertSz(0==afAdvisee[0],"0 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[1],"1 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[2],"2 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[3],"3 didn't Unadvise before ShutdownForm");

        // ----- post a quit message to our UI
        SendMessage(hMainWnd,WM_CLOSE,0,0);
    }

    return hr;
}

STDMETHODIMP
FRM::SetViewContext(LPMAPIVIEWCONTEXT pViewContextNew)
{
    TraceTag(tagFormFunc,"FRM::SetViewContext");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pViewContextNew,"no view context to set");

    /* View context is used for next and previous behavior
       The checkers form does not do next and previous because
       there is not a standard read note.  It is always in
       reply mode */

    return NOERROR;
}

STDMETHODIMP
FRM::GetViewContext(LPMAPIVIEWCONTEXT FAR * ppViewContext)
{
    TraceTag(tagFormFunc,"FRM::GetViewContext");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(ppViewContext,"get view context to where?");

    if (ppViewContext) *ppViewContext = NULL; /* not supported */

    return NOERROR;
}

STDMETHODIMP
FRM::Advise(LPMAPIVIEWADVISESINK pAdvise, ULONG FAR * pdwStatus)
{
    LONG i;

    TraceTag(tagFormFunc,"FRM::Advise");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pdwStatus);
    Assert(pAdvise);

    // ----- remember who to advise
    for (i=0; i<MAX_ADVISE; i++)
        if (!afAdvisee[i])
        {
            aAdvisePtrs[i] = pAdvise;
            afAdvisee[i] = 1;
            *pdwStatus = i + 1; /* ulConnection of zero is not valid */
            pAdvise->AddRef();
            return NOERROR;
        }

    // ----- bad news
    AssertSz(0,"out of aAdvisPtrs");
    return ResultFromScode(E_FAIL);
    return NOERROR;
}

STDMETHODIMP
FRM::Unadvise(ULONG ulConnection)
{
    TraceTag(tagFormFunc,"FRM::Unadvise");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(ulConnection < MAX_ADVISE && ulConnection >= 0,"testing, 123");
    AssertSz(ulConnection,"a non-zero ulConnection is not valid according to OLE");

    // ----- forget about advising this guy
    --ulConnection; // remember, we added one in advise
    AssertSz(afAdvisee[(int) ulConnection],"never wanted ::Advise in ::Unadvise?");
    afAdvisee[(int) ulConnection] = 0;
    aAdvisePtrs[(int) ulConnection]->Release();
    return NOERROR;
}

/*
 *  FRM::FRM
 *
 *  Purpose:
 *      Initialize or new form object
 *
 */
FRM::FRM(REFCLSID clsid)
{
    LONG i;

    TraceTag(tagFormFunc,"FRM::FRM .................");

    cRef = 1;
    this->clsid = clsid;

    pMessage = NULL;
    pMessageSite = NULL;
    pSession = NULL;

    pFormMgr = NULL;
    pFormInfo = NULL;

    fDirty = FALSE;

    for (i=0; i<MAX_ADVISE; i++)
        {
            aAdvisePtrs[i] = NULL;
            afAdvisee[i] = 0;
        }

    turn = 0;
}

/*
 *  FRM::~FRM
 *
 *  Purpose:
 *      Destroy our form object
 */
FRM::~FRM(void)
{
    TraceTag(tagFormFunc,"FRM::~FRM Bye now ...");
    AssertSz(0==cRef,"quit referring to this form please");
    AssertSz(NULL == pMessage,"still refing the message");
    AssertSz(NULL == pMessageSite,"still refing the messagesite");
    AssertSz(NULL == pSession,"still refing the session");
}

/*
 *  S a m p l e   F o r m   C l a s s   F a c t o r y
 *
 *  Because we are an exe server, we must implement a class factory
 *  so that other viewers (like Exchange) can learn of our clsid
 *
 */

/*
 *  FRMFAC::CreateInstance
 *
 *  Purpose:
 *      Creates a new form object of the IPM.Form class.
 *
 *  Arguments:
 *      LPUNKNOWN       Outer object to aggregate with (not supported).
 *      REFIID          Desired interface on new form object.
 *      LPVOID FAR *    Where to put new form object.
 *
 *  Returns:
 *      HRESULT         S_OK, or one of the following errors:
 *                      CLASS_E_NOAGGREGATION, E_OUTOFMEMORY,
 *                      E_NOINTERFACE, E_INVALIDARG.
 */
STDMETHODIMP
FRMFAC::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, LPVOID FAR * ppvObject)
{
    FRM *   pfrm = NULL;
    HRESULT hr;

    TraceTag(tagFormFunc,"FRMFAC::CreateInstance");

    // ----- Initialize out parameter and check validity of parameters
    if (!ppvObject)
        {
        hr = ResultFromScode(E_INVALIDARG);
        goto Cleanup;
        }
    *ppvObject = NULL;

    if (punkOuter)
        {
        hr = ResultFromScode(CLASS_E_NOAGGREGATION);
        goto Cleanup;
        }

    // ----- Instantiate new form
    if (!(pfrm = new FRM(clsid)))
        {
        hr = ResultFromScode(E_OUTOFMEMORY);
        TraceTag(tagForm,"E_OUTOFMEMORY 0x%08lx",hr);
        goto Cleanup;
        }

    // ----- Get the desired interface
    hr = pfrm->QueryInterface(riid, ppvObject);
    AssertSz(0==hr,"QueryInterface failed");

Cleanup:
    ReleaseObj(pfrm);
    TraceTag(tagForm,"return 0x%08lx initial reference %d",hr,cRef);
    return hr;
}

/*
 *  FRMFAC::QueryInterface
 *
 *  Purpose:
 *      Returns a pointer to the specified interface.
 *
 *  Arguments:
 *      REFIID          Interface we want.
 *      LPUNKNOWN *     Interface we return.
 *
 *  Returns:
 *      HRESULT         Error status.
 */
STDMETHODIMP
FRMFAC::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    TraceTag(tagFuncTriv,"FRMFAC::QueryInterface %s",DumpCLSID(riid));
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IClassFactory))
        {
        *ppvObj = this;
        AddRef();
        TraceTag(tagForm,"return ok");
        return NOERROR;
        }

    *ppvObj = NULL;
    TraceTag(tagForm,"return no interface");
    return ResultFromScode(E_NOINTERFACE);
}

/*
 *  FRMFAC::LockServer
 *
 *  Purpose:
 *
 *
 *  Arguments:
 *      BOOL            Whether to increment or decrement DLL reference count.
 *
 *  Returns:
 *      HRESULT         S_OK always.
 */
STDMETHODIMP
FRMFAC::LockServer(BOOL fLock)
{
    TraceTag(tagFormFunc,"LockServer (not implemented)");
    return NOERROR;
}

/*
 *  FRMFAC::AddRef
 *
 *  Purpose:
 *      Increments reference count on the form class factory.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG           New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRMFAC::AddRef(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::AddRef ret %d",cRef+1);
    return ++cRef;
}

/*
 *  FRMFAC::Release
 *
 *  Purpose:
 *      Decrements reference count on the form class factory.
 *      If count is decremented to zero, the object is freed.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG           New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRMFAC::Release(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::Release cRef %d",cRef);
    if (!(--cRef))
        {
        TraceTag(tagForm,"return 0");
        delete this;
        return 0;
        }
    return cRef;
}

FRMFMR::FRMFMR()
{
    TraceTag(tagFuncTriv,"FRMFMR::FRMFMR");
    clsid = CLSID_MyFormsClsId;
}

FRMFAC::FRMFAC()
{
    TraceTag(tagFuncTriv,"FRMFAC::FRMFAC");
    cRef = 1;
}

FRMFAC::~FRMFAC(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::~FRMFAC");
    AssertSz(!cRef,"0817236");
}


FORM.H

HRESULT HrStartOleAndRegisterClassFactory(void);
HRESULT HrStopForms(void);

#define NOTPURE
#define MAX_ADVISE 48

/*
 *  Types
 */

// ----- class factory
class FRMFAC : public IClassFactory
{
  protected:
    ULONG       cRef;
    CLSID       clsid;

    ~FRMFAC(void);

  public:
    FRMFAC();

    // *** IUnknown methods
    STDMETHOD(QueryInterface)   (THIS_ REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG,AddRef)    (THIS);
    STDMETHOD_(ULONG,Release)   (THIS);

    // *** IClassFactory methods
    STDMETHOD(CreateInstance)   (THIS_ LPUNKNOWN pUnkOuter, REFIID riid, LPVOID FAR* ppvObject);
    STDMETHOD(LockServer)       (THIS_ BOOL fLock);
};

class FRMFMR : public FRMFAC
{
  public:
    FRMFMR();
};

// ----- The minimal interface a form can support
class FRM : public IPersistMessage, public IMAPIForm
{
  protected:
    ULONG                   cRef;               // Reference count
    CLSID                   clsid;

    BOOL                    fDirty;             // Is our message dirty?
    BOOL                    fNoScribble;        // Are we in no-scribble mode?
    BOOL                    fSentMessage;       // Are we reading a sent message?

    // advise handling

    BOOL                    afAdvisee[MAX_ADVISE];
    LPMAPIVIEWADVISESINK    aAdvisePtrs[MAX_ADVISE];

    // data

    SQUARE                  b[SQRS_MAX];        // The board
    int                     turn;               // Who's turn it is
    long                    movenum;
    long                    score;

    ~FRM(void);

  public:
    LPMESSAGE               pMessage;
    LPMAPIMESSAGESITE       pMessageSite;
    LPMAPISESSION           pSession;

    LPMAPIFORMMGR           pFormMgr;
    LPMAPIFORMINFO          pFormInfo;

    FRM(REFCLSID clsid);

    MAPI_IUNKNOWN_METHODS(NOTPURE)
    MAPI_GETLASTERROR_METHOD(NOTPURE)
    MAPI_IPERSISTMESSAGE_METHODS(NOTPURE)
    MAPI_IMAPIFORM_METHODS(NOTPURE)

    HRESULT ShowCurrentMessage(ULONG ulhwndParent);
    HRESULT LaunchReplyMessage(ULONG ulhwndParent);
    VOID FRM::GetCheckersData(SQUARE* out_b, int* out_turn, long* out_movenum, long* out_score);
    VOID FRM::SetCheckersData(SQUARE* in_b, int in_turn, long in_movenumber, long in_score);
    HRESULT FRM::Remember(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg);
    HRESULT FRM::Forget(VOID);
    HRESULT SendForm(VOID);
    HRESULT FRM::AddressForm(HWND hwnd, BOOL fDontShowIfRecipsExist);
    SCODE FRM::ScGetRecipientAdrlist(LPMESSAGE pmsg, LPADRLIST * ppal);

};

/*
 *  Macros
 */

#define ADVISE(X)                        \
   for (LONG i = 0; i < MAX_ADVISE; i++) \
      if (afAdvisee[i])                  \
         aAdvisePtrs[i]->X

#define ReleaseObj(_pobj) \
        if (_pobj) \
                (_pobj)->Release()

/*
 *  Dependencies specific to this form
 */

#define CHKR_FORM
extern HWND hMainWnd;

#define PR_BOARD        PROP_TAG( PT_BINARY,    0x6800)
#define PR_TURN         PROP_TAG( PT_LONG,      0x6801)
#define PR_MOVENUMBER   PROP_TAG( PT_LONG,      0x6802)
#define PR_SCORINGFUNC  PROP_TAG( PT_LONG,      0x6803)


MOVELIST.H

// Movelist.h  -- Maintain forward/back/etc goodies for Wcheck.

struct rBoardNode
{
    SQUARE b[SQRS_MAX];
    int iPlayerTurn;
    struct rBoardNode *prNextBoard;
    struct rBoardNode *prPrevBoard;
};

class CMoveList
{
  private:
    int m_iNumBoards;
    BOOL m_fClassInitialized;
    struct rBoardNode *m_prFirstBoard;
    struct rBoardNode *m_prCurrentBoard;
    struct rBoardNode *m_prCurrentSearchBoard;

  public:
    CMoveList();
    ~CMoveList();
    Init();
    BOOL NewMove(BOARD b, int iPlayerTurn);
    BOOL BackMove(BOARD b, int *piPlayerTurn);
    BOOL ForwardMove(BOARD b, int *piPlayerTurn);
    BOOL PurgeBoards();
    BOOL ClearBoards();
    BOARD GetFirstBoard(BOARD b, int *piPlayerTurn);
    BOARD GetNextBoard(BOARD b, int *piPlayerTurn);
};


CHECK.H

/* Header file for checkers board data */

#define EMPTY   0
#define RED     1           /* you must not change these values */
#define BLACK   2
#define KING    4

#define SQRS_MAX    35  /* number of squares on a board plus some */

#define next(_t)    ((_t&1)+1)
#define CopyBoard(from,to) { int i; for (i=0; i<SQRS_MAX; i++) to[i] = from[i]; } 0

typedef long SQUARE;
typedef SQUARE FAR * BOARD;
typedef unsigned int BOOK_ELEMENT;

#define TYPE_RED0           1
#define TYPE_RED2           2
#define TYPE_BLACK0         3
#define TYPE_BLACK2         4 /* do not change the order of these values */
#define TYPE_RED_JUMP0      5
#define TYPE_RED_JUMP2      6
#define TYPE_BLACK_JUMP0    7
#define TYPE_BLACK_JUMP2    8

#define MakeMove_RED0(_b, _start) \
            _b[red_lut[_start][0]]  = _b[_start];\
            _b[_start]              = 0;\
            _b[red_lut[_start][0]] |= red_lut[_start][1]
#define MakeMove_RED2(_b, _start) \
            _b[red_lut[_start][2]]  = _b[_start];\
            _b[_start]              = 0;\
            _b[red_lut[_start][2]] |= red_lut[_start][3]
#define MakeMove_BLACK0(_b, _start) \
            _b[black_lut[_start][0]]  = _b[_start];\
            _b[_start]                = 0;\
            _b[black_lut[_start][0]] |= black_lut[_start][1]
#define MakeMove_BLACK2(_b, _start) \
            _b[black_lut[_start][2]]  = _b[_start];\
            _b[_start]                = 0;\
            _b[black_lut[_start][2]] |= black_lut[_start][3]

#define MakeMoveNoKing_RED_JUMP0(_b, _start) \
            _b[red_jump_lut[_start][1]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[red_jump_lut[_start][0]]  = 0
#define MakeMoveNoKing_RED_JUMP2(_b, _start) \
            _b[red_jump_lut[_start][3]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[red_jump_lut[_start][2]]  = 0
#define MakeMoveNoKing_BLACK_JUMP0(_b, _start) \
            _b[black_jump_lut[_start][1]]  = _b[_start];\
            _b[_start]                     = 0;\
            _b[black_jump_lut[_start][0]]  = 0
#define MakeMoveNoKing_BLACK_JUMP2(_b, _start) \
            _b[black_jump_lut[_start][3]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[black_jump_lut[_start][2]]  = 0

#define MakeMove_RED_JUMP0(_b, _start) \
            _b[red_jump_lut[_start][1]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[red_jump_lut[_start][0]]  = 0
#define MakeMove_RED_JUMP2(_b, _start) \
            _b[red_jump_lut[_start][3]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[red_jump_lut[_start][2]]  = 0
#define MakeMove_BLACK_JUMP0(_b, _start) \
            _b[black_jump_lut[_start][1]]  = _b[_start];\
            _b[_start]                     = 0;\
            _b[black_jump_lut[_start][0]]  = 0
#define MakeMove_BLACK_JUMP2(_b, _start) \
            _b[black_jump_lut[_start][3]]  = _b[_start];\
            _b[_start]                   = 0;\
            _b[black_jump_lut[_start][2]]  = 0

// ----- callable Functions in the engine
long PlayBestMove(BOARD b, int t, int j, int d,
                    int prune_depth, int prune_size, int max_depth);
int GameOver(BOARD b, int t);
int MoveValid(BOARD b, int start, int dest, int t);
long QualityOfBoard(BOARD b,int t);
char* TextizeBoard(BOARD b);


WCHECK.CPP

/* --------------------------------------------------------------------------

Basic Forms example of a custom sendable form.  It is an EXE server
rather than a DLL.  It implements the minimum form interface required
to launch and send a form.

Copyright (C) 1995 Microsoft Corporation

-------------------------------------------------------------------------- */

#define WINDOWS

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <malloc.h>
#include <memory.h>
#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <search.h>

#ifdef _WIN32
#include <winver.h>
#else
#include <ver.h>
#endif

//$FORM

//---[ Form specific includes ]--------------------------------------------

#include <ole2.h>
#include <mapiform.h>
#include <mapix.h>
#include <mapiutil.h>

//$FORM

//---[ Various platform defines ]------------------------------------------

#ifndef APIENTRY                // Windows NT defines APIENTRY,
#define APIENTRY far pascal             // but 3.x doesn't.
#endif

#ifndef _WIN32                   // Windows 3.x uses a FARPROC
#define DLGPROC FARPROC                 // for dialogs
#endif

unsigned int _charmax;

#ifdef _WIN32
#define MoveTo(_1, _2, _3) MoveToEx(_1,_2,_3,NULL)
#endif

//---[ Define Debug Trace Tags ]-------------------------------------------

#include "dbugit.h"

#ifdef DEBUG
ASSERTDATA
#endif

//---[ Local header dependencies ]-----------------------------------------

#include "check.h"
#include "dlg.h"
#include "wcheck.h"
#include "checkdta.h"
#include "movelist.h"
#include "form.h" //$FORM

//---[ Globally Accessible Config Info ]-----------------------------------

struct  rCheckConfigRec rConfig;                // Config struct
struct  rGameStateRec rGameState;               // Game state
class   CMoveList CMoves;                               // Move list
SQUARE  b[SQRS_MAX];                                    // Board structure

static  POINT       pntStart;                   // For buttonup/down processing.

char szAppName[] = APP_NAME;                    // The name of this application

#ifdef DEBUG
char szTitle[]   = "Checkers (debug)";
#else
#ifdef TEST
char szTitle[]   = "Checkers (test)";
#else
char szTitle[]   = "Checkers";
#endif
#endif

//---[ Globally Accessible Handles ]---------------------------------------

HINSTANCE       hInst;          // current instance handle

HWND    hMainWnd        = NULL;         // Main window handle
HWND    hClientWnd      = NULL;         // Client window handle
HBRUSH  hbrBlack;
HBRUSH  hbrWhite;
HBRUSH  hbrRed;
HBRUSH  hbrBckgrnd;
HBRUSH  hbrBlue;
HBRUSH  hbrHuman;
HBRUSH  hbrComputer;
HBRUSH  hbrDkgrey;
HPEN    hpenDkgrey;
HBITMAP hBtmaps;                        // Piece bitmaps

HCURSOR curNormal;                      // Standard cursor
HCURSOR curThink;                       // Computer is thinking cursor
HCURSOR curPiece;                       // Piece cursor (move in progress)
HCURSOR curResize;                      // Resize cursor

#define SQR_XSIZE       rConfig.iSquareSize
#define SQR_YSIZE       rConfig.iSquareSize

#define OFST_INCREMENT      rConfig.iSquareSize         // OFFSET into bitmap
#define OFST_BLANK          (0*OFST_INCREMENT)          // OFFSET into bitmap
#define OFST_BLANK_RED      (1*OFST_INCREMENT)          // OFFSET into bitmap
#define OFST_COMPUTER       (2*OFST_INCREMENT)          // OFFSET into bitmap
#define OFST_COMPUTER_KING  (3*OFST_INCREMENT)          // OFFSET into bitmap
#define OFST_HUMAN_KING     (4*OFST_INCREMENT)          // OFFSET into bitmap
#define OFST_HUMAN          (5*OFST_INCREMENT)          // OFFSET into bitmap

FRM *pfrm; //$FORM

//---[ Inline code includes ]----------------------------------------------

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/
CMoveList::CMoveList()
{
    m_iNumBoards = 0;
    m_fClassInitialized = FALSE;
    m_prFirstBoard = NULL;
    m_prCurrentBoard = m_prFirstBoard;
    m_prCurrentSearchBoard = m_prFirstBoard;
}

CMoveList::~CMoveList()
{
    m_prCurrentBoard = m_prFirstBoard;
    PurgeBoards();
}

BOARD CMoveList::GetFirstBoard(BOARD b, int *piPlayerTurn)
{
    AssertSz(m_fClassInitialized, "MoveList class not initialized in GetFirstBoard()");

    m_prCurrentSearchBoard = m_prFirstBoard;
    if (NULL == m_prCurrentSearchBoard)
    return NULL;
    else
    {
    AssertSz((BLACK == m_prCurrentSearchBoard->iPlayerTurn) ||
             (RED == m_prCurrentSearchBoard->iPlayerTurn),
             "EBr rocks!");

        *piPlayerTurn = m_prCurrentSearchBoard->iPlayerTurn;
    CopyBoard(m_prCurrentSearchBoard->b, b);

    m_prCurrentSearchBoard = m_prCurrentSearchBoard->prNextBoard;
    return b;
    }
}

BOARD CMoveList::GetNextBoard(BOARD b, int *piPlayerTurn)
{
    AssertSz(m_fClassInitialized, "MoveList class not initialized in GetNextBoard()");

    if (NULL == m_prCurrentSearchBoard)
    {
    return NULL;
    }
    else
    {
    AssertSz((BLACK == m_prCurrentSearchBoard->iPlayerTurn) ||
             (RED == m_prCurrentSearchBoard->iPlayerTurn),
             "Kt C abayn  04/07/94");

        *piPlayerTurn = m_prCurrentSearchBoard->iPlayerTurn;
    CopyBoard(m_prCurrentSearchBoard->b, b);
    m_prCurrentSearchBoard = m_prCurrentSearchBoard->prNextBoard;
    return b;
    }
}

BOOL CMoveList::Init()
{
    AssertSz(!m_fClassInitialized, "Class already initialized");

    m_fClassInitialized = TRUE;
    return TRUE;
}

BOOL CMoveList::BackMove(BOARD b, int *piPlayerTurn)
{
    AssertSz(m_fClassInitialized, "MoveList class not initialized in BackMove()");

    if (NULL == m_prFirstBoard)
    return FALSE;

    AssertSz(m_prCurrentBoard, "Current Board hosed");

    if (m_prCurrentBoard == m_prFirstBoard)
    {
    return FALSE;
    }
    else
    {
    m_prCurrentBoard = m_prCurrentBoard->prPrevBoard;

    AssertSz((BLACK == m_prCurrentBoard->iPlayerTurn) ||
             (RED == m_prCurrentBoard->iPlayerTurn),
             "Meatloaf");

        *piPlayerTurn = m_prCurrentBoard->iPlayerTurn;
    CopyBoard(m_prCurrentBoard->b, b);
    return TRUE;
    }
}

BOOL CMoveList::ForwardMove(BOARD b, int *piPlayerTurn)
{
    AssertSz(m_fClassInitialized, "MoveList class not initialized in ForwardMove()");

    if (NULL == m_prCurrentBoard)
    return FALSE;

    if (NULL == m_prCurrentBoard->prNextBoard)
    return FALSE;
    else
    {
    m_prCurrentBoard = m_prCurrentBoard->prNextBoard;

    AssertSz((BLACK == m_prCurrentBoard->iPlayerTurn) ||
             (RED == m_prCurrentBoard->iPlayerTurn),
             "Just say no to yes.");

        *piPlayerTurn = m_prCurrentBoard->iPlayerTurn;
    CopyBoard(m_prCurrentBoard->b, b);
    return TRUE;
    }
}

BOOL CMoveList::NewMove(BOARD b, int iPlayerTurn)
{
    struct rBoardNode *prNewBoard = NULL;

    AssertSz(m_fClassInitialized, "MoveList class not initialized in NewMove()");

    // Remove any old boards that would get overwritten.

    if (NULL != m_prCurrentBoard)
    {
    PurgeBoards();
    }

    prNewBoard = (struct rBoardNode *)malloc(sizeof(struct rBoardNode));
    if (!prNewBoard)
    {
    MessageBox(NULL, "Out of memory.", "Checkers", MB_ICONEXCLAMATION | MB_OK);
    return FALSE;
    }
    else
    {
    m_iNumBoards++;

    prNewBoard->prNextBoard = NULL;
    prNewBoard->prPrevBoard = NULL;
    CopyBoard(b, prNewBoard->b);

    if (NULL == m_prFirstBoard)
    {
        m_prFirstBoard = prNewBoard;
    }
    else
    {
        m_prCurrentBoard->prNextBoard = prNewBoard;
    }

    prNewBoard->prPrevBoard = m_prCurrentBoard;
    prNewBoard->iPlayerTurn = iPlayerTurn;
    m_prCurrentBoard = prNewBoard;
    return TRUE;
    }
}

BOOL CMoveList::ClearBoards()
{
    m_prCurrentBoard = m_prFirstBoard;
    return (PurgeBoards());
}

BOOL CMoveList::PurgeBoards()
{
    struct rBoardNode *prDeleteBoard;

    if (NULL == m_prCurrentBoard || 0 == m_fClassInitialized)
    return TRUE;
    else
    {
    prDeleteBoard = m_prCurrentBoard->prNextBoard;
    while (prDeleteBoard != NULL)
    {
        m_prCurrentBoard->prNextBoard = prDeleteBoard->prNextBoard;
        free (prDeleteBoard);
        m_iNumBoards--;
        prDeleteBoard = m_prCurrentBoard->prNextBoard;
    }
    return TRUE;
    }
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

BOOL WritePrivateProfileLong(LPCSTR lpszSection, LPCSTR lpszEntry, long lTheLong, LPCSTR lpszFilename);
BOOL WritePrivateProfileInt(LPCSTR lpszSection, LPCSTR lpszEntry, int iTheInt, LPCSTR lpszFilename);
long GetDlgItemLong(HWND hDlg, WORD id);
int  GetDlgItemInt(HWND hDlg, WORD id);
void SetDlgItemInt(HWND hDlg, WORD id, int iValue);
void SetDlgItemLong(HWND hDlg, WORD id, long lValue);
BOOL ValidateNumeric(char *pszText);
LONG GetPrivateProfileLong(LPCSTR lpszSection, LPCSTR lpszEntry, long lDefault, LPCSTR lpszFileName);

BOOL WritePrivateProfileInt(LPCSTR lpszSection, LPCSTR lpszEntry, int iTheInt, LPCSTR lpszFilename)
{
    char szINIString[32];

    AssertSz(lpszSection, "lpszSection == NULL");
    AssertSz(lpszEntry, "lpszEntry == NULL");
    AssertSz(lpszFilename, "lpszFilename == NULL");

    _itoa(iTheInt, szINIString, 10);   // Convert the int to a string. Base10
     return WritePrivateProfileString(lpszSection, lpszEntry, (LPSTR)szINIString, lpszFilename);
}

long GetDlgItemLong(HWND hDlg, WORD id)
{
    char szTemp[20];    // Can't imagine a number longer than this.
    long lTemp;

    GetDlgItemText (hDlg, id, (LPSTR) szTemp, sizeof(szTemp));
    lTemp = atol(szTemp);

    return lTemp;
}

int GetDlgItemInt(HWND hDlg, WORD id)
{
    char szTemp[20];    // Can't imagine a number longer than this.
    int iTemp;

    GetDlgItemText (hDlg, id, (LPSTR) szTemp, sizeof(szTemp));
    iTemp = atoi(szTemp);

    return iTemp;
}

void SetDlgItemInt(HWND hDlg, WORD id, int iValue)
{
    char szTemp[20];     // Can't imagine a number longer than this.

    _itoa(iValue, szTemp, 10);
    SetDlgItemText (hDlg, id, szTemp);
}

void SetDlgItemLong(HWND hDlg, WORD id, long lValue)
{
    char szTemp[20];     // Can't imagine a number longer than this.

    _ltoa(lValue, szTemp, 10);
    SetDlgItemText (hDlg, id, szTemp);
}

LONG GetPrivateProfileLong(LPCSTR lpszSection, LPCSTR lpszEntry, long lDefault, LPCSTR lpszFileName)
{
    LONG l;
    char szTemp[20];

    GetPrivateProfileString(lpszSection, lpszEntry, "~~", szTemp, 20, lpszFileName);
    if (lstrcmp(szTemp, "~~") == 0)
    l = lDefault;
    else
    l = atol(szTemp);
    return l;
}

BOOL WritePrivateProfileLong(LPCSTR lpszSection, LPCSTR lpszEntry, long lTheLong, LPCSTR lpszFilename)
{
    char szINIString[32];

    AssertSz(lpszSection, "lpszSection == NULL");
    AssertSz(lpszEntry, "lpszEntry == NULL");
    AssertSz(lpszFilename, "lpszFilename == NULL");

    _ltoa(lTheLong, szINIString, 10);   // Convert the int to a string. Base10
     return WritePrivateProfileString(lpszSection, lpszEntry, (LPSTR)szINIString, lpszFilename);
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/
int OpenCheckers(HINSTANCE hInstance, int nCmdShow)
{
    MSG msg;
    HACCEL hAccelTable = NULL;          // Accelerator table handle.

    // Perform initializations that apply to a specific instance

    if (!InitInstance(hInstance, nCmdShow))
    {
    AssertSz(0,"InitInst failed");
        return (FALSE);
    }

    hAccelTable = LoadAccelerators (hInstance, (LPSTR)"WCheck");
    AssertSz(hAccelTable,"no accelerators");

    // Acquire and dispatch messages until a WM_QUIT message is received.

    while (GetMessage(&msg,         // message structure
        NULL,                                   // handle of window receiving the message
        0,                                      // lowest message to examine
        0))                                     // highest message to examine
    {
        if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg); // Translates virtual key codes
            DispatchMessage(&msg);  // Dispatches message to window
        }
    }

    return 0;

} /* end open checkers */

/****************************************************************************

    FUNCTION: WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

    PURPOSE: calls initialization function, processes message loop

    COMMENTS:

        Windows recognizes this function by name as the initial entry point
        for the program.  This function calls the application initialization
        routine, if no other instance of the program is running, and always
        calls the instance initialization routine.  It then executes a message
        retrieval and dispatch loop that is the top-level control structure
        for the remainder of execution.  The loop is terminated when a WM_QUIT
        message is received, at which time this function exits the application
        instance by returning the value passed by PostQuitMessage().

        If this function must abort before entering the message loop, it
        returns the conventional value NULL.

****************************************************************************/
int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{

    AssertSz(((RED == PLAYER1) && (BLACK == PLAYER2)) ||
         ((BLACK == PLAYER1) && (RED == PLAYER2)), "Players are schizoid");

    hInst = hInstance;                              // Set global Instance handle

    if (!hPrevInstance)
    {
        if (!InitApplication(hInstance))
        {
            return (FALSE);
        }
    }

    // Check for the -Embedding switch

    if (lpCmdLine && (lpCmdLine[0] == '/' || lpCmdLine[0] == '-'))
    {

        // Attempt to be a form

    if (NOERROR == HrStartOleAndRegisterClassFactory())
        {
        rGameState.fPlayAsForm = TRUE; // we are acting as a form

        #ifdef DEBUG
        nCmdShow = SW_SHOWMINIMIZED;
        #else
        nCmdShow = SW_HIDE;
        #endif

        // Open the checkers form window .. but do not show until we're DoVerb'd

        OpenCheckers(hInstance, nCmdShow);

        // Call this function if ..StartForms succeeded

        HrStopForms();

        return 0;
        }

        if (IDYES == MessageBox(GetFocus(), "Unable to initialize messaging subsystem.\nWould you like to play against the computer?", "Checkers", MB_YESNO ))
        {
        OpenCheckers(hInstance, nCmdShow);
        return 0;
        }

    }

    OpenCheckers(hInstance, nCmdShow);
    return 0;
}

VOID ReadPlayerInfo(int iPlayerNum, char *pszPlayerName)
{
    AssertSz((iPlayerNum == BLACK) || (iPlayerNum == RED), "Invalid Player Num");
    AssertSz(pszPlayerName, "Playername can't be NULL");

    if (rGameState.fPlayAsForm)
    {
    rConfig.rPlayer[iPlayerNum].iPlayerType = HUMAN_PLAYER;
    if (22871 == GetPrivateProfileInt ("Computer", "Config", HUMAN_PLAYER, INI_NAME))
        {
        rConfig.rPlayer[iPlayerNum].iPlayerType = COMPUTER_PLAYER;
        }
    }
    else
    {
    if (PLAYER1 == iPlayerNum)
        rConfig.rPlayer[iPlayerNum].iPlayerType = HUMAN_PLAYER;
    else
        rConfig.rPlayer[iPlayerNum].iPlayerType = COMPUTER_PLAYER;
    }

    // Get computer config options.

    rConfig.rPlayer[iPlayerNum].iMemPositions = GetPrivateProfileInt (pszPlayerName, "MemPositions", 0, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iUseMoveTheory = GetPrivateProfileInt (pszPlayerName, "UseMoveTheory", 1, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iUseMoveShuffling = GetPrivateProfileInt (pszPlayerName, "UseMoveShuffling", 1, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iUseEqualMoveSkipping = GetPrivateProfileInt (pszPlayerName, "UseEqualMoveSkipping", 1, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iUseOpeningBook = GetPrivateProfileInt (pszPlayerName, "UseOpeningBook", 1, INI_NAME);
    rConfig.rPlayer[iPlayerNum].lUseGoodMoveSkipping = GetPrivateProfileLong (pszPlayerName, "UseGoodMoveSkipping", 0, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iMaxRecursionDepth = GetPrivateProfileInt (pszPlayerName, "MaxRecursionDepth", 5, INI_NAME);
    rConfig.rPlayer[iPlayerNum].iUseAlphaBetaPruning = GetPrivateProfileInt (pszPlayerName, "UseAlphaBetaPruning", 1, INI_NAME);
    rConfig.rPlayer[iPlayerNum].lAlphaPruningOriginalDepth = GetPrivateProfileLong (pszPlayerName, "AlphaPruningOriginalDepth", 2, INI_NAME);
    rConfig.rPlayer[iPlayerNum].lAlphaPruningWidth = GetPrivateProfileLong (pszPlayerName, "AlphaPruningWidth", 35, INI_NAME);

}

VOID    ReadINI ()
{
    TraceTag(tagUI,"ReadINI()");

    // Read general config info

    rConfig.iGameType = GetPrivateProfileInt ("Config", "GameType", GAME_CHECKERS, INI_NAME);
    rConfig.iMustJump = GetPrivateProfileInt ("Config", "MustJump", 1, INI_NAME);
    rConfig.iBoardFlipped = 1;
    rConfig.iSquareSize = GetPrivateProfileInt ("Config", "SquareSize", 30, INI_NAME);
    rConfig.iMaxMoves = GetPrivateProfileInt ("Config", "MaxMoves", 200, INI_NAME);

    // Read specific player config for both (potential) computer players.

    ReadPlayerInfo(PLAYER1, "Player1");
    ReadPlayerInfo(PLAYER2, "Player2");
}

VOID WritePlayerInfo(int iPlayerNum, char *pszPlayerName)
{
    AssertSz((iPlayerNum == RED) || (iPlayerNum == BLACK), "Invalid Player Num");
    AssertSz(pszPlayerName, "Playername can't be NULL");

    // Player specific config items

    WritePrivateProfileInt (pszPlayerName, "PlayerType", rConfig.rPlayer[iPlayerNum].iPlayerType, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "MemPositions", rConfig.rPlayer[iPlayerNum].iMemPositions, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "UseMoveTheory", rConfig.rPlayer[iPlayerNum].iUseMoveTheory, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "UseMoveShuffling", rConfig.rPlayer[iPlayerNum].iUseMoveShuffling, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "UseEqualMoveSkipping", rConfig.rPlayer[iPlayerNum].iUseEqualMoveSkipping, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "UseOpeningBook", rConfig.rPlayer[iPlayerNum].iUseOpeningBook, INI_NAME);
    WritePrivateProfileLong(pszPlayerName, "UseGoodMoveSkipping", rConfig.rPlayer[iPlayerNum].lUseGoodMoveSkipping, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "MaxRecursionDepth", rConfig.rPlayer[iPlayerNum].iMaxRecursionDepth, INI_NAME);
    WritePrivateProfileInt (pszPlayerName, "UseAlphaBetaPruning", rConfig.rPlayer[iPlayerNum].iUseAlphaBetaPruning, INI_NAME);
    WritePrivateProfileLong(pszPlayerName, "AlphaPruningOriginalDepth", rConfig.rPlayer[iPlayerNum].lAlphaPruningOriginalDepth, INI_NAME);
    WritePrivateProfileLong(pszPlayerName, "AlphaPruningWidth", rConfig.rPlayer[iPlayerNum].lAlphaPruningWidth, INI_NAME);

}

VOID    WriteINI ()
{
    RECT    rect;
    char    szTempStr[10];                       // For rect.value translation

    TraceTag(tagUI,"WriteINI()");

    // Store state of config radio buttons

    WritePrivateProfileInt ("Config", "GameType", rConfig.iGameType, INI_NAME);
    WritePrivateProfileInt ("Config", "MustJump", rConfig.iMustJump, INI_NAME);
    WritePrivateProfileInt ("Config", "SquareSize", rConfig.iSquareSize, INI_NAME);
    WritePrivateProfileInt ("Config", "MaxMoves", rConfig.iMaxMoves, INI_NAME);

    // Write player info

    WritePlayerInfo(PLAYER1, "Player1");
    WritePlayerInfo(PLAYER2, "Player2");

    // Store state of Window

    GetWindowRect (hMainWnd, &rect);
    if (!IsIconic(hMainWnd))
    {
    wsprintf (szTempStr, "%d", rect.left);
    WritePrivateProfileString ("Window", "Left", szTempStr, INI_NAME);
    wsprintf (szTempStr, "%d", rect.top);
    WritePrivateProfileString ("Window", "Top", szTempStr, INI_NAME);
    wsprintf (szTempStr, "%d", rect.right - rect.left);
    WritePrivateProfileString ("Window", "Width", szTempStr, INI_NAME);
    wsprintf (szTempStr, "%d", rect.bottom - rect.top);
    WritePrivateProfileString ("Window", "Height", szTempStr, INI_NAME);
    }

}

BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASS        wc;

    // Fill in window class structure with parameters that describe the
    // main window.

    wc.style         = CS_HREDRAW | CS_VREDRAW;             // Class style(s).
    wc.lpfnWndProc   = (WNDPROC)CheckersWndProc;    // Window Procedure
    wc.cbClsExtra    = 0;                                   // No per-class extra data.
    wc.cbWndExtra    = 0;                                   // No per-window extra data.
    wc.hInstance     = hInstance;                           // Owner of this class
    wc.hIcon         = LoadIcon (hInstance, "WCheckIcon");  // Icon name from .RC
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);                 // Cursor
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);                    // Default color
    wc.lpszMenuName  = (LPSTR)"WCheck_NORMAL";              // Menu name from .RC
    wc.lpszClassName = szAppName;                                           // Name to register as

    // Register the window class and return success/failure code.
    return (RegisterClass(&wc));
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND    hWnd; // Main window handle.
    HDC hdc = NULL;

    // Save the instance handle in static variable, which will be used in
    // many subsequence calls from this application to Windows.

    // need to set this here because we may get call from somewhere besides winmain
    hInst = hInstance; // Store instance handle in our global variable

    hbrWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);
    hbrBlack = (HBRUSH)GetStockObject(BLACK_BRUSH);

    hbrRed        = CreateSolidBrush(RGB(0xFF,0,0));
    hbrBckgrnd    = CreateSolidBrush(GetNearestColor(hdc, RGB(0xAA,0xAA,0xAA)));
    hbrBlue       = CreateSolidBrush(RGB(0,0,0xFF));
    hpenDkgrey    = CreatePen( PS_SOLID, 1, RGB( 0x55, 0x55, 0x55 ) );

    #ifdef _WIN32
        hbrDkgrey     = CreateSolidBrush(RGB(0x55,0x55,0x55));
    #else
        hbrDkgrey     = CreateSolidBrush(GetNearestColor(hdc, RGB(0x55,0x55,0x55)));
    #endif

    // Fail initialization if we can't create brushs/pens/etc..

    if (!hbrRed ||
        !hbrBckgrnd ||
        !hbrBlue ||
        !hbrWhite ||
        !hbrBlack ||
        !hpenDkgrey ||
        !hbrDkgrey)
    return(FALSE);

    curNormal   = LoadCursor(NULL, IDC_ARROW);
    curThink    = LoadCursor(NULL, IDC_WAIT);
    curResize       = LoadCursor(NULL, IDC_SIZENWSE);

    if (!curNormal || !curThink || !curResize)
    return(FALSE);

    // Create a main window for this application instance.
    hWnd = CreateWindow(
        szAppName,           // See RegisterClass() call.
        szTitle,             // Text for window title bar.
        WS_OVERLAPPEDWINDOW, // Window style.
    GetPrivateProfileInt ("Window", "Left",  13  /* CW_USEDEFAULT */, INI_NAME),
    GetPrivateProfileInt ("Window", "Top",   8   /* CW_USEDEFAULT */, INI_NAME),
    GetPrivateProfileInt ("Window", "Width", 356 /* CW_USEDEFAULT */, INI_NAME),
    GetPrivateProfileInt ("Window", "Height",306 /* CW_USEDEFAULT */, INI_NAME),
        NULL,                // Overlapped windows have no parent.
        NULL,                // Use the window class menu.
        hInstance,           // This instance owns this window.
        NULL                 // We don't use any data in our WM_CREATE
    );

    // If window could not be created, return "failure"
    if (!hWnd) {
        return (FALSE);
    }

    while (!SetTimer (hWnd, ID_TIMER, TIMER_GRANULARITY, NULL))
    {
    if (IDCANCEL == MessageBox (hWnd, "Too many clocks or timers!", szAppName,
        MB_ICONEXCLAMATION | MB_RETRYCANCEL))
        {
        return FALSE;
        }
    }

    // Make the window visible; update its client area; and return "success"

    ShowWindow(hWnd, nCmdShow); // Show the window // SW_SHOWNORMAL // SW_HIDE
    UpdateWindow(hWnd);         // Sends WM_PAINT message

    SendMessage(hWnd, WM_OTHERINIT, 0, 0L);

    return (TRUE);              // We succeeded...

}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

// FillBoard() puts the pieces on the board (draws them).

void NEAR PASCAL FillBoard(BOARD b, HDC hDC)

{
    int         i                       = 0;            // Loop (for pieces) variable
    HDC         hTmpDC          = NULL;         // Temp work DC
    HBITMAP hOldbm              = NULL;         // Store old Bitmap handle
    int         yBm             = 0;            // Bitmap to use (depending on piece)
    int         iOffset         = 0;            // Squares alternate starting postion..
    int         iX                      = 0;            // Location (x) to place square
    int             iY                      = 0;            // Location (y) to place square
    int     iLocation       = 0;            // For figuring real location after swap..

    hTmpDC = CreateCompatibleDC( hDC );
    hOldbm = (HBITMAP)SelectObject( hTmpDC, hBtmaps );

    for (i=1; i <= 32; i++)
    {
        if (rConfig.iBoardFlipped)
            iLocation = 33-i;
        else
            iLocation = i;

    AssertSz(iLocation > 0, "Bee-Gees r a bit");
    AssertSz(iLocation <= SQRS_MAX, "Jello: The environmentally safe defoliant");

    switch(b[iLocation])
    {
        case 0:
        yBm = 0;
        break;
        case (RED | KING):
        yBm = OFST_COMPUTER_KING;
        break;
        case RED:
        yBm = OFST_COMPUTER;
        break;
        case (BLACK | KING):
        yBm = OFST_HUMAN_KING;
        break;
        case BLACK:
        yBm = OFST_HUMAN;
        break;
        default:
        {
        AssertSz(FALSE, "Piece value invalid");
        TraceTag(tagUI, "Invalid piece value of piece %d is: %d", i, b[i]);
        }
        break;
    }

        // Calculate location of square on board. (offset?)

        iY = (i-1) / 4;
        iX = (i-1) % 4;
    if ((iY % 2) == 1)
            iOffset = 0;
        else
            iOffset = 1;

        // Place the square on the board

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize*(2*iX+iOffset)), BOARD_TOP + (rConfig.iSquareSize*iY),
        rConfig.iSquareSize, rConfig.iSquareSize, hTmpDC, 0, yBm, SRCCOPY );

        // All squares have an accompanying blank square. Depending on the
        // offset, it can be on either side of the filled square.

        if (iOffset == 1)
            BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize*(2*iX)), BOARD_TOP + (rConfig.iSquareSize*iY),
                rConfig.iSquareSize, rConfig.iSquareSize, hTmpDC, 0, OFST_BLANK_RED, SRCCOPY);
        else
            BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize*(2*iX+1)), BOARD_TOP + (rConfig.iSquareSize*iY),
                rConfig.iSquareSize, rConfig.iSquareSize, hTmpDC, 0, OFST_BLANK_RED, SRCCOPY);

    }

    // Draw setup rack if the user is in setup mode.

    if (rGameState.fSetupMode)
    {

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize * RACK_LEFT),
            BOARD_TOP + (rConfig.iSquareSize * (RACK_TOP)),
            rConfig.iSquareSize, rConfig.iSquareSize,
            hTmpDC, 0, OFST_COMPUTER, SRCCOPY);

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize * RACK_LEFT),
            BOARD_TOP + (rConfig.iSquareSize * (RACK_TOP + 1)),
            rConfig.iSquareSize, rConfig.iSquareSize,

hTmpDC, 0, OFST_HUMAN, SRCCOPY);

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize * RACK_LEFT),
            BOARD_TOP + (rConfig.iSquareSize * (RACK_TOP + 2)),
            rConfig.iSquareSize, rConfig.iSquareSize,
            hTmpDC, 0, OFST_COMPUTER_KING, SRCCOPY);

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize * RACK_LEFT),
            BOARD_TOP + (rConfig.iSquareSize * (RACK_TOP + 3)),
            rConfig.iSquareSize, rConfig.iSquareSize,
            hTmpDC, 0, OFST_HUMAN_KING, SRCCOPY);

        BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize * RACK_LEFT),
            BOARD_TOP + (rConfig.iSquareSize * (RACK_TOP + 4)),
            rConfig.iSquareSize, rConfig.iSquareSize,
            hTmpDC, 0, OFST_BLANK, SRCCOPY);
    }

    SelectObject( hTmpDC, hOldbm );
    DeleteDC( hTmpDC );
}

// Removes the piece that's moving, and replaces with a blank square.

void vClearMovingPiece(HDC hDC, int iX, int iY)
{
    HDC   hTmpDC;
    HBITMAP hOldbm;
    int   yBm;

    hTmpDC = CreateCompatibleDC( hDC );
    hOldbm = (HBITMAP)SelectObject( hTmpDC, hBtmaps );

    yBm = 0;

    BitBlt( hDC, BOARD_LEFT + (rConfig.iSquareSize*iX), BOARD_TOP + (rConfig.iSquareSize*iY),
    rConfig.iSquareSize, rConfig.iSquareSize, hTmpDC, 0, yBm, SRCCOPY );

    SelectObject( hTmpDC, hOldbm );
    DeleteDC( hTmpDC );
}

// Reset the board to starting positions.
void ClearBoard(BOARD b)
{
    Assert(b);

    b[0]  = 0;          b[1]  = BLACK;  b[2]  = BLACK;  b[3] = BLACK;
    b[4]  = BLACK;  b[5]  = BLACK;  b[6]  = BLACK;      b[7] = BLACK;
    b[8]  = BLACK;  b[9]  = BLACK;  b[10] = BLACK;      b[11] = BLACK;
    b[12] = BLACK;  b[13] = EMPTY;      b[14] = EMPTY;  b[15] = EMPTY;
    b[16] = EMPTY;  b[17] = EMPTY;      b[18] = EMPTY;  b[19] = EMPTY;
    b[20] = EMPTY;  b[21] = RED;        b[22] = RED;    b[23] = RED;
    b[24] = RED;        b[25] = RED;    b[26] = RED;    b[27] = RED;
    b[28] = RED;        b[29] = RED;    b[30] = RED;    b[31] = RED;
    b[32] = RED;
}

void DrawBoard(BOARD b)
{
    HDC hDC = GetDC(hMainWnd);

    Assert(b);

    FillBoard(b, hDC);
    ReleaseDC(hMainWnd, hDC);

}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void DisplayGameOver(HWND hWnd, int iPlayerTurn)
{
    int iWinner = BLACK;

    AssertSz(hWnd, "hWnd hosed, homey.");
    AssertSz((rGameState.iPlayerTurn == BLACK) ||
             (rGameState.iPlayerTurn == RED), "Very low sodium");

    // NOTE: Caller is passing in the losing player. The turn has already
    // switched before this gets called. This makes iPlayerTurn
    // equivalent to the winner in a give-away game. If the game type is
    // standard checkers, I want to invert winner.

    iWinner = iPlayerTurn;
    if (rConfig.iGameType == GAME_CHECKERS)
    iWinner = next(iWinner);

    if (RED == iWinner)
        MessageBox(hWnd, "Game over. Red wins!", "Checkers", MB_OK );
    else
        MessageBox(hWnd, "Game over. Black wins!", "Checkers", MB_OK );

}

void DisplayGameDrawn(HWND hWnd, int iPlayerTurn)
{
    AssertSz(hWnd, "hWnd hosed (in DisplayGameDrawn), homey.");
    AssertSz((rGameState.iPlayerTurn == BLACK) ||
             (rGameState.iPlayerTurn == RED), "Vl bites");

    MessageBox(hWnd, "Game drawn. (Game over)", "Checkers", MB_OK );
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void NEAR PASCAL CheckGenPieces(HDC hDC)
{
    HDC     hTmpDC  = NULL;
    HBITMAP hOldbm  = NULL;
    HBRUSH  hOldbr  = NULL;
    int     i               = 0;

    AssertSz(hDC, "Null hDC in CheckGenPieces");

    if( hBtmaps != NULL )
        DeleteObject( hBtmaps );

    // We have 6 different pieces (including the blank). So we just fill
    // a region big enough for 6 squares.
    hBtmaps = CreateCompatibleBitmap( hDC, SQR_XSIZE, 6*SQR_YSIZE );

    /* allocate an off-screen DC, so we can paint the ellipses now */
    hTmpDC = CreateCompatibleDC( hDC );
    hOldbm = (HBITMAP)SelectObject( hTmpDC, hBtmaps );

    /* blank out the off-screen bitmap, ie: paint the background into it */
    hOldbr = (HBRUSH)SelectObject( hTmpDC, GetStockObject( LTGRAY_BRUSH ));
    PatBlt( hTmpDC, 0, 0, SQR_XSIZE, SQR_YSIZE, PATCOPY );
    SetBkMode( hTmpDC, TRANSPARENT );

    /* draw the square's "grooved" borders */
    SelectObject( hTmpDC, hpenDkgrey );
    MoveTo( hTmpDC, 2, SQR_YSIZE-2 );
    LineTo( hTmpDC, SQR_XSIZE-2, SQR_YSIZE-2 );
    LineTo( hTmpDC, SQR_XSIZE-2, 1 );
    SelectObject( hTmpDC, GetStockObject( WHITE_PEN ) );
    LineTo( hTmpDC, 1, 1 );
    LineTo( hTmpDC, 1, SQR_YSIZE );
    SelectObject( hTmpDC, GetStockObject( BLACK_PEN ) );
    MoveTo( hTmpDC, 2, SQR_YSIZE-1 );
    LineTo( hTmpDC, SQR_XSIZE-1, SQR_YSIZE-1 );
    LineTo( hTmpDC, SQR_XSIZE-1, 0 );

    /* copy the borders to the other bitmaps */
    for( i=OFST_INCREMENT; i <= OFST_HUMAN; i+=OFST_INCREMENT )
    BitBlt(hTmpDC, 0, i, SQR_XSIZE, SQR_YSIZE, hTmpDC, 0, 0, SRCCOPY);

    /* Attempt at RED SQUARE*/
    SelectObject( hTmpDC, hbrDkgrey );
    hOldbr = (HBRUSH)SelectObject( hTmpDC, GetStockObject( LTGRAY_BRUSH ));
    SelectObject( hTmpDC, hbrRed);
    PatBlt( hTmpDC, 0+2, OFST_BLANK_RED+2, SQR_XSIZE-4, SQR_YSIZE-4, PATCOPY );

    /* first piece: computer */
    SelectObject(hTmpDC, GetStockObject( WHITE_PEN ) );
    SelectObject(hTmpDC, GetStockObject( WHITE_BRUSH ) );
    Ellipse( hTmpDC, 3, OFST_COMPUTER+3, SQR_XSIZE-6, OFST_COMPUTER+SQR_YSIZE-6 );
    SelectObject(hTmpDC, hpenDkgrey );
    SelectObject(hTmpDC, hbrDkgrey );
    Ellipse( hTmpDC, 6, OFST_COMPUTER+6, SQR_XSIZE-3, OFST_COMPUTER+SQR_YSIZE-3 );
    SelectObject(hTmpDC, GetStockObject( BLACK_PEN ) );
    SelectObject(hTmpDC, hbrRed);
    Ellipse( hTmpDC, 4, OFST_COMPUTER+4, SQR_XSIZE-5, OFST_COMPUTER+SQR_YSIZE-5 );

    /* computer king */
    BitBlt( hTmpDC, 0, OFST_COMPUTER_KING, SQR_XSIZE, OFST_COMPUTER_KING+SQR_YSIZE, hTmpDC,
        0, OFST_COMPUTER, SRCCOPY );
    SelectObject( hTmpDC, GetStockObject( WHITE_PEN ) );
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_COMPUTER_KING + (SQR_YSIZE / 2));
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 4), OFST_COMPUTER_KING + (SQR_YSIZE / 2));
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_COMPUTER_KING + (SQR_YSIZE / 2) + 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 4), OFST_COMPUTER_KING + (SQR_YSIZE / 2) + 1);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 2), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 2), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 1);

    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 - 6), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 3);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 + 3), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 5), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 3);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 2), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 2);
    LineTo(hTmpDC, (SQR_XSIZE / 2 - 3), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 4);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 + 1), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 2);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 3), OFST_COMPUTER_KING + (SQR_YSIZE / 2) - 4);

    /* human king */
    BitBlt( hTmpDC, 0, OFST_HUMAN_KING, SQR_XSIZE, OFST_HUMAN_KING+SQR_YSIZE, hTmpDC,
    0, OFST_COMPUTER, SRCCOPY );
    SelectObject(hTmpDC, hbrBlack);
    SelectObject(hTmpDC, GetStockObject( BLACK_PEN ) );
    Ellipse( hTmpDC, 4, OFST_HUMAN_KING+4, SQR_XSIZE-5, OFST_HUMAN_KING+SQR_YSIZE-5 );
    SelectObject( hTmpDC, GetStockObject( WHITE_PEN ) );
    SelectObject( hTmpDC, GetStockObject( WHITE_PEN ) );
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_HUMAN_KING + (SQR_YSIZE / 2));
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 4), OFST_HUMAN_KING + (SQR_YSIZE / 2));
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_HUMAN_KING + (SQR_YSIZE / 2) + 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 4), OFST_HUMAN_KING + (SQR_YSIZE / 2) + 1);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 2), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 2), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 1);

    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 4), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 - 6), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 3);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 + 3), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 1);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 5), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 3);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 - 2), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 2);
    LineTo(hTmpDC, (SQR_XSIZE / 2 - 3), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 4);
    MoveTo(hTmpDC, (SQR_XSIZE / 2 + 1), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 2);
    LineTo(hTmpDC, (SQR_XSIZE / 2 + 3), OFST_HUMAN_KING + (SQR_YSIZE / 2) - 4);

    /* human */
    BitBlt( hTmpDC, 0, OFST_HUMAN, SQR_XSIZE, SQR_YSIZE, hTmpDC,
    0, OFST_COMPUTER, SRCCOPY );
    SelectObject(hTmpDC, GetStockObject( BLACK_PEN ) );
    SelectObject(hTmpDC, hbrBlack);
    Ellipse( hTmpDC, 4, OFST_HUMAN+4, SQR_XSIZE-5, OFST_HUMAN+SQR_YSIZE-5 );

    SelectObject( hTmpDC, hOldbr );
    SelectObject( hTmpDC, hOldbm );
    DeleteDC( hTmpDC );
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void NEAR PASCAL CheckCreate()

{
    HDC  hDC;
    TEXTMETRIC    charsize;           /* characteristics of the characters */
    int COLOR = 1;

    hDC = GetDC(hMainWnd);
    GetTextMetrics(hDC, (LPTEXTMETRIC)&charsize);

    ReleaseDC(hMainWnd, hDC);

    COLOR = GetDeviceCaps(hDC, NUMCOLORS) > 2;

    if (COLOR == TRUE)
    {
    hbrComputer = hbrBlue;
    hbrHuman = hbrRed;
    }
    else
    {
    hbrComputer = hbrBlack;
    hbrHuman = hbrWhite;
    }
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

/* Called on WM_PAINT messages. */

void NEAR PASCAL CheckPaint(BOARD b, HDC hDC)

{
    CheckGenPieces(hDC);
    SetBkMode(hDC, OPAQUE);

    DrawBoard(b);
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void inline vTranslateCurPosToSquare(POINT pntCur, int *pix, int *piy)
{
    Assert(pix);
    Assert(piy);
    Assert(rConfig.iSquareSize != 0);

    *pix = (pntCur.x - BOARD_LEFT) / rConfig.iSquareSize;
    *piy = (pntCur.y - BOARD_TOP) / rConfig.iSquareSize;

    if ((*pix < 0) || (*pix > 7) || (*piy < 0) || (*piy > 7))
    {
        if ((!rGameState.fSetupMode) || (*pix != 10) || (*piy < 2) || (*piy > 6))
        {
            *pix = -1;              // Return invalid x/y locations
            *piy = -1;              // Return invalid x/y locations
        }
    }
}

void inline vTranslateUIToEngineSquare(int *piEngine, int iX, int iY)
{
    AssertSz(piEngine, "piEngine == NULL");

    if (((iY + iX) % 2 == 0) ||
    (iY < 0) || (iY > 7) || (iX < 0) || (iX > 7))
    {
    *piEngine = -1;         // Return invalid engine ID
        return;
    }
    else
    {
    *piEngine = iY * 4 + (iX / 2) + 1;
    }

    if (rConfig.iBoardFlipped)
        *piEngine = (33 - *piEngine);
}

void inline vTranslateEngineToUISquare(int iEngine, int *piX, int *piY)
{
    Assert(iEngine > 0);
    Assert(piX);
    Assert(piY);

    if (rConfig.iBoardFlipped)
        iEngine = (33 - iEngine);

    *piY = (iEngine - 1) / 4;
    *piX = ((iEngine - 1) % 4) * 2 + ((*piY + 1) % 2);
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

BOOL fValidSetupPiece(BOARD b, int ix, int iy)
{
    int iEngine = -1;
    int iPiece  = -1;
    BOOL fRackPiece = FALSE;

    AssertSz(b, "Where's the board");

    if ((ix < 0) || (ix > 7) || (iy < 0) || (iy > 7))
    {
        // Check for Setup Rack pieces

        if ((ix != 10) || (iy < 2) || (iy > 6))
            return FALSE;
        else
            fRackPiece = TRUE;
    }

    vTranslateUIToEngineSquare(&iEngine, ix, iy);

    AssertSz((iEngine == -1) ||
         ((iEngine > 0) && (iEngine <= SQRS_MAX)),
             "iEngine needs counseling");

    if (iEngine != -1)
    {
        iPiece = (int) b[iEngine];

        if ((iPiece & RED) || (iPiece & BLACK) || (iPiece == EMPTY))
            return TRUE;
        else
            return FALSE;
    }
    else
    {
        if (!fRackPiece)
            return FALSE;
        else
            return TRUE;
    }

}

BOOL fValidPlayerPiece(BOARD b, int ix, int iy)
{
    int iEngine = 0;
    int iPiece = 0;

    AssertSz(b, "Where's the board?");
    AssertSz(((rGameState.iPlayerTurn == RED) ||
          (rGameState.iPlayerTurn == BLACK)), "Harrison");

    if ((ix < 0) || (ix > 7) || (iy < 0) || (iy > 7))
    return FALSE;

    vTranslateUIToEngineSquare(&iEngine, ix, iy);

    // Assert that the engine return is either a "normal" invalid, or
    // a normal engine square. Should NOT be 0.

    AssertSz((iEngine == -1) ||
         ((iEngine > 0) && (iEngine <= SQRS_MAX)),
             "Abort, Retry, Fail?");

    if (iEngine != -1)
    iPiece = (int) b[iEngine];
    else
        return FALSE;

    if (RED == rGameState.iPlayerTurn)
    {
    if (iPiece & RED)
        return TRUE;
    else
        return FALSE;
    }
    else
    {
        if (BLACK == rGameState.iPlayerTurn)
    {
        if (iPiece & BLACK)
        return TRUE;
        else
        return FALSE;
        }
    else
        {
        AssertSz(FALSE, "rGameState.iPlayerTurn invalid!");
        return FALSE;
    }
    }
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void OnGamePlayableAfterMove(void)
{
    if (pfrm)
    {
    AssertSz(pfrm->pMessage,"no message to send");

    // ----- Update our forms information based on user interaction
    pfrm->SetCheckersData(b,rGameState.iPlayerTurn,0,QualityOfBoard(b,rGameState.iPlayerTurn));

    // ----- Send this baby, and shut her down
    SetWindowText(hMainWnd,"Sending ...");
    pfrm->SendForm();
    pfrm->ShutdownForm(OLECLOSE_NOSAVE);
    }
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/
// Handle WM_INITMENU message.

VOID vHandle_WM_INITMENU(HWND hWnd, HMENU hMenu)
{
    hMenu = GetMenu(hWnd);
    if (rGameState.fThinking)
    {
    EnableMenuItem(hMenu, 0, MF_DISABLED | MF_BYPOSITION);
    EnableMenuItem(hMenu, 1, MF_DISABLED | MF_BYPOSITION);
    }
    else
    {
    EnableMenuItem(hMenu, 0, MF_ENABLED | MF_BYPOSITION);
    EnableMenuItem(hMenu, 1, MF_ENABLED | MF_BYPOSITION);
    }
}

BOOL fHandle_WM_CREATE(HWND hWnd, CREATESTRUCT FAR* lpCreateStruct)
{
    HDC         hDC     = NULL;
    HMENU       hm              = NULL;

    hMainWnd = hWnd;
    ReadINI();

    // Set game state variables.

    rGameState.fSetupMode = FALSE;          // Game in setup mode? Need for button processing
    rGameState.fThinking = FALSE;           // Is the computer thinking?
    rGameState.fPaused = FALSE;             // Is the game paused?
    rGameState.fGameInProgress = FALSE;     // Did the game start? How to react to "File:New"
    rGameState.fLMouseButtonDown = FALSE;   // Is the left mouse-button currently down?
    rGameState.fRMouseButtonDown = FALSE;   // Is the left mouse-button currently down?
    rGameState.fMoveInProgress = FALSE;     // Is the user in the middle of a move?
    rGameState.fComputerBusy = FALSE;               // Is the computer thinking?
    rGameState.fGameOver = FALSE;                   // Is the game over?
    rGameState.fInitDone = FALSE;                   // Is initialization done?
    rGameState.iPlayerTurn = BLACK;
    rGameState.iCursorState = NORMAL_CURSOR;        // What state is the cursor in?
    rGameState.fMouseResizeInProgress = FALSE;      // Is the user resizing right now?
    rConfig.iSetupPurgeBoards = TRUE;               // Purge boards when exit setup?

    CMoves.Init();
    CheckCreate();
    hDC = GetDC(hWnd);
    ClearBoard(b);
    CMoves.NewMove(b, rGameState.iPlayerTurn);
    FillBoard(b, hDC);

    hm = GetMenu(hWnd);
    if (!rConfig.iBoardFlipped)
    {
        CheckMenuItem(hm, IDM_FLIP, MF_CHECKED);
    }

    CheckMenuItem(hm, IDM_BASE_LEVEL + rConfig.rPlayer[RED].iMaxRecursionDepth, MF_CHECKED);

    switch(rConfig.iGameType)
    {
    case GAME_CHECKERS:
        CheckMenuItem(hm, IDM_GAME_CHECKERS, MF_CHECKED);
        CheckMenuItem(hm, IDM_GAME_GIVEAWAY, MF_UNCHECKED);
        break;
    case GAME_GIVEAWAY:
        CheckMenuItem(hm, IDM_GAME_GIVEAWAY, MF_CHECKED);
        CheckMenuItem(hm, IDM_GAME_CHECKERS, MF_UNCHECKED);
        break;
    default:
        // Set to checkers, assert if debug
        CheckMenuItem(hm, IDM_GAME_CHECKERS, MF_CHECKED);
        CheckMenuItem(hm, IDM_GAME_GIVEAWAY, MF_UNCHECKED);
        MessageBox(hWnd, "Game type invalid. Setting to checkers", "Checkers", MB_OK);
        rConfig.iGameType = GAME_CHECKERS;
        break;
    }

    // Load "Piece" cursor.

    curPiece   = LoadCursor(hInst, "PieceCur");
    if (curPiece)
    TraceTag(tagUI, "Cur Piece is valid");
    else
    TraceTag(tagUI, "Cur Piece is invalid");

    return TRUE;

}

VOID vHandle_WM_DESTROY(HWND hWnd)
{
    WriteINI();

    if (pfrm)
    {
        FRM *pfrml; //$FORM
        pfrml = pfrm;
        pfrm = NULL;
        pfrml->ShutdownForm(OLECLOSE_NOSAVE);
        pfrml->Release();
    }

    DeleteObject(hbrBckgrnd);
    DeleteObject(hbrRed);
    DeleteObject(hbrBlue);
    DeleteObject(hbrDkgrey);
    DeleteObject(hpenDkgrey);
    DeleteObject(hBtmaps);

    // WinHelp(hWnd, (LPSTR)szHelpFile, HELP_QUIT, 0L);

    PostQuitMessage(0);
}

VOID vHandle_WM_TIMER(HWND hWnd, UINT id)
{
    #ifdef DEBUG
    static int x=0;
    if (0==x)
    {
    if (!((BLACK == rGameState.iPlayerTurn) ||
      (RED == rGameState.iPlayerTurn))) x++;
    AssertSz(((BLACK == rGameState.iPlayerTurn) ||
          (RED == rGameState.iPlayerTurn)),
          "Caffeine-Free? What's the point?");
    }
    #endif

    if ((rConfig.rPlayer[rGameState.iPlayerTurn].iPlayerType == COMPUTER_PLAYER) &&
        (rGameState.fInitDone) &&
        (!rGameState.fPaused) &&
        (!rGameState.fComputerBusy) &&
        (!rGameState.fGameOver))
    {
    ComputerMove();
    }
}

VOID vHandle_WM_RBUTTONDOWN(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
    AssertSz(!rGameState.fRMouseButtonDown, "David Copperfield with a mouse");
    TraceTag(tagMouse, "in vHandle_WM_RBUTTONDOWN");
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

    if (!rGameState.fLMouseButtonDown)
    {
        SetCapture(hWnd);

        if (rGameState.fSetupMode)
            SetupRButtonDown(hWnd, keyFlags, x, y);
        else
        {
        // No-op.
        }
    }
    else
    {
    // No-op.
    }

}

VOID vHandle_WM_LBUTTONDOWN(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
    AssertSz(!rGameState.fLMouseButtonDown, "How'd you do that?");

    TraceTag(tagMouse, "in vHandle_WM_LBUTTONDOWN");
    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

    SetCapture(hWnd);

    switch (rGameState.iCursorState)
    {
        case NORMAL_CURSOR:
            TraceTag(tagMouse, "Move LButtonDown");
            if (!rGameState.fRMouseButtonDown)
            {
                SetCapture(hWnd);

                if (rGameState.fSetupMode)
                SetupLButtonDown(hWnd, keyFlags, x, y);
                else
                {
                    AssertSz(((BLACK == rGameState.iPlayerTurn) ||
                        (RED == rGameState.iPlayerTurn)),
                        "Just how often am I going to check this?");

                    if ((!rGameState.fGameOver) &&
                    (HUMAN_PLAYER == rConfig.rPlayer[rGameState.iPlayerTurn].iPlayerType))
                    NormalLButtonDown(hWnd, keyFlags, x, y);
                }
            }
            else
            {
                // NO-OP if other button already down
            }
            break;

        case RESIZE_CURSOR:
        {
            TraceTag(tagMouse, "Resize LButtonDown");
            rGameState.fMouseResizeInProgress = TRUE;
        }
    }               // end switch

}

VOID vHandle_WM_LBUTTONUP(HWND hWnd, int x, int y, UINT keyFlags)
{
    TraceTag(tagMouse, "in vHandle_WM_LBUTTONUP");

    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

    ReleaseCapture();

    if (rGameState.fMouseResizeInProgress)
    {
        rGameState.fMouseResizeInProgress = FALSE;
    }
    else
    {
        if (rGameState.fSetupMode)
            SetupLButtonUp(hWnd, keyFlags, x, y);
        else
            if (!rGameState.fGameOver)
            NormalLButtonUp(hWnd, keyFlags, x, y);
    }
}

VOID vHandle_WM_RBUTTONUP(HWND hWnd, int x, int y, UINT keyFlags)
{

    TraceTag(tagMouse, "in vHandle_WM_RBUTTONUP");

    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

    ReleaseCapture();
    if (rGameState.fSetupMode)
    SetupRButtonUp(hWnd, keyFlags, x, y);
    else
    {
    // NO-OP
    }
}

VOID vHandle_WM_MOUSEMOVE(HWND hWnd, int x, int y, UINT keyFlags)
{

    TraceTag(tagMouse, "in vHandle_WM_MOUSEMOVE. Mouse position: x:%d  y:%d", x, y);

    if (rGameState.fRMouseButtonDown)
    {
        // NO-OP
    }
    else
    {
        if (rGameState.fLMouseButtonDown)
        {
            // NO-OP: Should be the actual mouse resize code.
        }
        else
        {
            if (rGameState.fMouseResizeInProgress)
            {
                int iNewBoardSize = 0;
                int iNewSquareSize = 0;
                HDC hDC = 0;
                RECT rectInvalidate;

                TraceTag(tagMouse, "End of mouse resize");

                iNewBoardSize = ((x - BOARD_LEFT) + (y - BOARD_TOP)) / 2;
                TraceTag(tagMouse, "iNewBoardSize: %d", iNewBoardSize);

                iNewSquareSize = iNewBoardSize / 8;

                AssertSz(MAX_SQUARE_SIZE >= MIN_SQUARE_SIZE, "Max < Min. Duh.");

                if (iNewSquareSize < MIN_SQUARE_SIZE)
                    iNewSquareSize = MIN_SQUARE_SIZE;

                if (iNewSquareSize > MAX_SQUARE_SIZE)
                    iNewSquareSize = MAX_SQUARE_SIZE;

                if (iNewSquareSize != rConfig.iSquareSize)
                {
                    rectInvalidate.left             = BOARD_LEFT;
                    rectInvalidate.right    = BOARD_LEFT + rConfig.iSquareSize*13;
                    rectInvalidate.top              = BOARD_TOP;
                    rectInvalidate.bottom   = BOARD_TOP + rConfig.iSquareSize*8;

                    InvalidateRect(hWnd, &rectInvalidate, TRUE);
                    hDC = GetDC(hWnd);
                    rConfig.iSquareSize = iNewSquareSize;
                CheckGenPieces(hDC);
                    UpdateWindow(hWnd);
                DrawBoard(b);
                }

                SetCursor(curResize);
                TraceTag(tagMouse, "Resize now: x: %d, y: %d", x, y);
            }
            else
            {
                // User is in normal play/setup mode. No mouse down. We want to
                // change the cursor based on whether we're scanning over pieces
                // or over the "resize" area.

                switch(rGameState.iCursorState)
                {
                    case NORMAL_CURSOR:
                        if ((x > BOARD_LEFT + rConfig.iSquareSize*8 - RESIZE_RANGE) &&
                        (x < BOARD_LEFT + rConfig.iSquareSize*8 + RESIZE_RANGE) &&
                            (y > BOARD_TOP + rConfig.iSquareSize*8 - RESIZE_RANGE) &&
                            (y < BOARD_TOP + rConfig.iSquareSize*8 + RESIZE_RANGE))
                        {
                            TraceTag(tagMouse, "Setting RESIZE_CURSOR");
                            SetCursor(curResize);
                            rGameState.iCursorState = RESIZE_CURSOR;
                        }
                        else
                        {
                            SetCursor(curNormal);
                        }
                        break;

                    case RESIZE_CURSOR:
                        if ((x < BOARD_LEFT + rConfig.iSquareSize*8 - RESIZE_RANGE) ||
                        (x > BOARD_LEFT + rConfig.iSquareSize*8 + RESIZE_RANGE) ||
                            (y < BOARD_TOP + rConfig.iSquareSize*8 - RESIZE_RANGE) ||
                            (y > BOARD_TOP + rConfig.iSquareSize*8 + RESIZE_RANGE))
                        {
                            TraceTag(tagMouse, "Setting NORMAL_CURSOR");
                            SetCursor(curNormal);
                            rGameState.iCursorState = NORMAL_CURSOR;
                        }
                        else
                        {
                            SetCursor(curResize);
                        }
                        break;
                    default:
                        AssertSz(FALSE, "Forest AND the trees");
                        break;
                }                                                       // end switch
            }                                                               // end else fMouseResizeInProgress
        }                                                                       // end else fLMouseButtonDown
    }                                                                               // end else fRMouseButtonDown
}

VOID vHandle_WM_PAINT(HWND hWnd)
{
    PAINTSTRUCT ps;

    BeginPaint(hWnd, (LPPAINTSTRUCT)&ps);
    CheckPaint(b, ps.hdc);
    EndPaint(hWnd, (LPPAINTSTRUCT)&ps);
}

VOID vHandle_WM_VSCROLL(HWND hWnd, HWND hWndCtl, UINT code, int pos)
{
    AssertSz(FALSE, "When did I start getting VSCROLL messages?");
}

VOID vHandle_WM_HSCROLL(HWND hWnd, HWND hWndCtl, UINT code, int pos)
{
    AssertSz(FALSE, "When did I start getting HSCROLL messages?");
}

VOID vHandle_WM_GETMINMAXINFO(HWND hWnd, MINMAXINFO FAR * lpMinMaxInfo)
{
    // NO-OP
}

VOID vHandle_WM_COMMAND(HWND hWnd, int wmId, HWND hwndCtl, UINT wmEvent)
{
    // Used by various commands

    FARPROC lpProcDialog = NULL;
    int iDialogBoxResult = -1;
    RECT rectInvalidate;
    HDC hDC = NULL;
    HMENU hm = NULL;

    switch (wmId)
    {
    #ifdef NEVER
    case IDM_FILENEW:
    {
        int iResponse;

        iResponse = MessageBox(hWnd, "You have asked to start " \
        "a new game. Note, this will end your current game. " \

"Would you like to save your game first?", "New Game?",
        MB_YESNOCANCEL | MB_DEFBUTTON2 | MB_ICONQUESTION);

        switch (iResponse)
        {
        case IDYES:
            SaveAsBoard(b);
            // We mean to fall through to the IDNO case below

        case IDNO:
            ClearBoard(b);
            CMoves.ClearBoards();
                    rGameState.fGameOver = FALSE;
                    rGameState.iPlayerTurn = BLACK;
                    CMoves.NewMove(b, rGameState.iPlayerTurn);
            DrawBoard(b);
            break;

        case IDCANCEL:
                    DrawBoard(b);
            break;
        }
        break;
    }
    #endif //never

    case IDM_SAVE:
        NYI("IDM_SAVE");
        break;

    case IDM_GAME_CHECKERS:
        if (rConfig.iGameType != GAME_CHECKERS)
        {
        hm = GetMenu(hWnd);
        CheckMenuItem(hm, IDM_GAME_CHECKERS, MF_CHECKED);
        CheckMenuItem(hm, IDM_GAME_GIVEAWAY, MF_UNCHECKED);
        rConfig.iGameType = GAME_CHECKERS;
        }
        break;

    case IDM_GAME_GIVEAWAY:
        if (rConfig.iGameType != GAME_GIVEAWAY)
        {
        hm = GetMenu(hWnd);
        CheckMenuItem(hm,IDM_GAME_CHECKERS,MF_UNCHECKED);
        CheckMenuItem(hm,IDM_GAME_GIVEAWAY,MF_CHECKED);
        rConfig.iGameType = GAME_GIVEAWAY;
        }
        break;

    case IDM_SETUP_BOARD:

        SetMenu(hWnd, LoadMenu(hInst, "WCheck_SETUP"));
        rGameState.fSetupMode = TRUE;

            rectInvalidate.left= BOARD_LEFT + rConfig.iSquareSize*10;
            rectInvalidate.right = rectInvalidate.left + rConfig.iSquareSize;
            rectInvalidate.top = BOARD_TOP + rConfig.iSquareSize*2;
            rectInvalidate.bottom = BOARD_TOP + rConfig.iSquareSize*7;
            InvalidateRect(hMainWnd, &rectInvalidate, TRUE);

            //$$ () game over state
            break;

    case IDM_SETUP_EXIT:

        lpProcDialog = MakeProcInstance ((FARPROC) PlayerTurnDialogProc, hInst);
        iDialogBoxResult = DialogBox(hInst, "PLAYER_TURN",
        hWnd, (DLGPROC) lpProcDialog);
        if (iDialogBoxResult != -1)
            {

            AssertSz((BLACK == iDialogBoxResult) ||
                     (RED == iDialogBoxResult),
                     "Darn tootin!");

        SetMenu(hWnd, LoadMenu(hInst, "WCheck_NORMAL"));
                rGameState.iPlayerTurn = iDialogBoxResult;
        rGameState.fSetupMode = FALSE;
                rectInvalidate.left= BOARD_LEFT + rConfig.iSquareSize*10;
                rectInvalidate.right = rectInvalidate.left + rConfig.iSquareSize;
                rectInvalidate.top = BOARD_TOP + rConfig.iSquareSize*2;
                rectInvalidate.bottom = BOARD_TOP + rConfig.iSquareSize*7;
                InvalidateRect(hMainWnd, &rectInvalidate, TRUE);
                if (rConfig.iSetupPurgeBoards)
                    CMoves.ClearBoards();
                else
                    CMoves.PurgeBoards();
                CMoves.NewMove(b, rGameState.iPlayerTurn);
        }
        break;

    case IDM_PLAYERS:
        lpProcDialog = MakeProcInstance ((FARPROC) PlayersDialogProc, hInst);
        DialogBox (hInst, "PLAYERS", hWnd, (DLGPROC) lpProcDialog);
        FreeProcInstance (lpProcDialog);
        break;

    case IDM_BACK:
        {
        // Change menu to say "Continue"
        rGameState.fPaused = TRUE;
                rGameState.fGameOver = FALSE;

                // $$ () game over state

        TraceTag(tagUI, "Calling CMoves.BackMove(b)");
        if (FALSE == CMoves.BackMove(b, &rGameState.iPlayerTurn))
                {
                    TraceTag(tagUI, "BEEP: Can't back up.");
            MessageBeep(MB_OK);
                }
        else
            DrawBoard(b);
        }
        break;

    case IDM_FORWARD:
    {
        // Change menu to say "Continue"
        rGameState.fPaused = TRUE;

            // $$ () game over state
        TraceTag(tagUI, "Calling CMoves.ForwardMove(b)");
        if (FALSE == CMoves.ForwardMove(b, &rGameState.iPlayerTurn))
            {
                TraceTag(tagUI, "BEEP: Can't go forward.");
                MessageBeep(MB_OK);
            }
        else
        DrawBoard(b);
    }
    break;

        case IDM_FLIP:
            hm = GetMenu(hWnd);
            if (FALSE == rConfig.iBoardFlipped)
            {
                rConfig.iBoardFlipped = TRUE;
                CheckMenuItem(hm, IDM_FLIP, MF_UNCHECKED);
            }
            else
            {
                rConfig.iBoardFlipped = FALSE;
                CheckMenuItem(hm, IDM_FLIP, MF_CHECKED);
            }

            DrawBoard(b);
            break;

        case IDM_CONTINUE:

        rGameState.fPaused = FALSE;
        CMoves.PurgeBoards();

            // $$ () game over state

        AssertSz(((BLACK == rGameState.iPlayerTurn) ||
                (RED == rGameState.iPlayerTurn)),
                "Who flicked that?");

        if (COMPUTER_PLAYER == rConfig.rPlayer[rGameState.iPlayerTurn].iPlayerType)
        {
        ComputerMove();
        }
        break;

        case IDM_SETDRAWMOVES:
        lpProcDialog = MakeProcInstance ((FARPROC) DebugConfigDialogProc, hInst);
        DialogBox (hInst, "DEBUG_CONFIG", hWnd, (DLGPROC) lpProcDialog);
        FreeProcInstance (lpProcDialog);
            break;

    case IDM_BASE_LEVEL:
    case IDM_BASE_LEVEL + 1:
    case IDM_BASE_LEVEL + 2:
    case IDM_BASE_LEVEL + 3:
    case IDM_BASE_LEVEL + 4:
    case IDM_BASE_LEVEL + 5:
    case IDM_BASE_LEVEL + 6:
    case IDM_BASE_LEVEL + 7:
    case IDM_BASE_LEVEL + 8:
    case IDM_BASE_LEVEL + 9:
    case IDM_BASE_LEVEL + 10:
    case IDM_BASE_LEVEL + 11:
    case IDM_BASE_LEVEL + 12:
    case IDM_BASE_LEVEL + 13:
    case IDM_BASE_LEVEL + 14:
    case IDM_BASE_LEVEL + 15:
    case IDM_BASE_LEVEL + 16:
    case IDM_BASE_LEVEL + 17:
    case IDM_BASE_LEVEL + 18:
    case IDM_BASE_LEVEL + 19:
    case IDM_BASE_LEVEL + 20:
        HMENU hm;
        int i;

        hm = GetMenu(hWnd);
        i=20;
        for (;i;i--)
            CheckMenuItem(hm, IDM_BASE_LEVEL + i, MF_UNCHECKED);
        CheckMenuItem(hm, wmId, MF_CHECKED);
        rConfig.rPlayer[BLACK].iMaxRecursionDepth =
            rConfig.rPlayer[RED].iMaxRecursionDepth = wmId - IDM_BASE_LEVEL;
        break;

    case IDM_HINT:
        {
        SQUARE      db[SQRS_MAX];
        int i;

        SetCursor(curThink);
        CopyBoard(b,db);
        PlayBestMove(db, rGameState.iPlayerTurn, 0, 0,
            (int)rConfig.rPlayer[rGameState.iPlayerTurn].lAlphaPruningOriginalDepth,    /* prune depth */
            (int)rConfig.rPlayer[rGameState.iPlayerTurn].lAlphaPruningWidth,    /* prune size */
            (int)rConfig.rPlayer[rGameState.iPlayerTurn].iMaxRecursionDepth   /* max depth */
            );
        SetCursor(curNormal);
        for (i=0; i<3; ++i)
        {
        #define WAIT_TICKS 250
        #ifndef _WIN32
        DWORD startTick;
        #endif
        DrawBoard(db);
        #ifdef _WIN32
        Sleep(WAIT_TICKS);
        #else
        startTick = GetTickCount() + WAIT_TICKS;
        while (GetTickCount() < startTick) /* do nothing */;
        #endif
        DrawBoard(b);
        #ifdef _WIN32
        Sleep(WAIT_TICKS);
        #else
        startTick = GetTickCount() + WAIT_TICKS;
        while (GetTickCount() < startTick) /* do nothing */;
        #endif
        }

        }
        break;

    case IDM_ADDRESS:
        if (pfrm) // $ FORM
        pfrm->AddressForm(hWnd,(BOOL) wmEvent);
        else
        MessageBeep(MB_OK);
        break;

        case IDM_ABOUT:

            lpProcDialog = MakeProcInstance((FARPROC)About, hInst);

            DialogBox(hInst,           // current instance
                "AboutBox",            // dlg resource to use
                hWnd,                  // parent handle
                (DLGPROC)lpProcDialog); // About() instance address

            FreeProcInstance(lpProcDialog);
            break;

        case IDM_EXIT:
            DestroyWindow (hWnd);
            break;
    }
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

LRESULT CALLBACK CheckersWndProc(
        HWND hWnd,         // window handle
        UINT message,      // type of message
        WPARAM wParam,     // additional information
        LPARAM lParam)     // additional information

{
    switch (message)
    {
    HANDLE_MSG(hWnd, WM_CREATE, fHandle_WM_CREATE);
        HANDLE_MSG(hWnd, WM_INITMENU, vHandle_WM_INITMENU);
        HANDLE_MSG(hWnd, WM_DESTROY, vHandle_WM_DESTROY);
        HANDLE_MSG(hWnd, WM_TIMER, vHandle_WM_TIMER);
        HANDLE_MSG(hWnd, WM_LBUTTONDOWN, vHandle_WM_LBUTTONDOWN);
        HANDLE_MSG(hWnd, WM_LBUTTONUP, vHandle_WM_LBUTTONUP);
        HANDLE_MSG(hWnd, WM_RBUTTONDOWN, vHandle_WM_RBUTTONDOWN);
        HANDLE_MSG(hWnd, WM_RBUTTONUP, vHandle_WM_RBUTTONUP);
        HANDLE_MSG(hWnd, WM_MOUSEMOVE, vHandle_WM_MOUSEMOVE);
        HANDLE_MSG(hWnd, WM_PAINT, vHandle_WM_PAINT);
        HANDLE_MSG(hWnd, WM_VSCROLL, vHandle_WM_VSCROLL);
        HANDLE_MSG(hWnd, WM_HSCROLL, vHandle_WM_HSCROLL);
        HANDLE_MSG(hWnd, WM_GETMINMAXINFO, vHandle_WM_GETMINMAXINFO);
        HANDLE_MSG(hWnd, WM_COMMAND, vHandle_WM_COMMAND);

        case EM_GIVEFORMTOHWND:

        ShowWindow(hWnd, SW_SHOWNORMAL);

        // ----- params valid
        AssertSz(NULL == pfrm,"two forms?");
        AssertSz(lParam,"no forms?");
        pfrm = (FRM*) lParam;
        TraceTag(tagForm,"wcheck pfrm->AddRef");
        pfrm->AddRef();
        Assert(pfrm->pMessage);

        pfrm->GetCheckersData(b,&rGameState.iPlayerTurn,NULL,NULL);
        AssertSz(rGameState.iPlayerTurn == RED || rGameState.iPlayerTurn == BLACK,"cool: neither red or blacks turn according to mapi");
        rConfig.iBoardFlipped = 1;
        if (RED == rGameState.iPlayerTurn)
        rConfig.iBoardFlipped = 0;
        rGameState.fPaused = FALSE;

        DrawBoard(b);

        break;

        case WM_OTHERINIT:

        if (!rGameState.fPlayAsForm)
        {
        if (COMPUTER_PLAYER == rConfig.rPlayer[PLAYER1].iPlayerType)
            {
            MessageBox(hWnd, "Computer is configured to move first. The " \
            "game will start out paused. Select 'Continue' from the " \
            "main menu to start.", "Checkers", MB_OK);
            rGameState.fPaused = TRUE;
            }
        }
        else
        {
        rGameState.fPaused = TRUE;
        }
            rGameState.fInitDone = TRUE;
        break;

    case WM_CLOSE:
        if (pfrm)
        {
        FRM *pfrml; //$FORM
        pfrml = pfrm;
        pfrm = NULL;
        pfrml->ShutdownForm(OLECLOSE_NOSAVE);
        pfrml->Release();
        }
        return(DefWindowProc(hWnd, message, wParam, lParam));

        default:          // Passes it on if unproccessed
            return (DefWindowProc(hWnd, message, wParam, lParam));
    }
    return (0);
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

long WINAPI ComputerMove()
{
    int iGameState = GAME_PLAYABLE;

    rGameState.fComputerBusy = TRUE;
    rGameState.fGameInProgress = TRUE;

    TraceTag(tagUI, "Calling PlayBestMove()");
    SetCursor(curThink);

    AssertSz(((BLACK == rGameState.iPlayerTurn) ||
              (RED == rGameState.iPlayerTurn)),
              "This one says x!");

        PlayBestMove(b, rGameState.iPlayerTurn, 0, 0,
        (int)rConfig.rPlayer[rGameState.iPlayerTurn].lAlphaPruningOriginalDepth,    /* prune depth */
        (int)rConfig.rPlayer[rGameState.iPlayerTurn].lAlphaPruningWidth,    /* prune size */
        (int)rConfig.rPlayer[rGameState.iPlayerTurn].iMaxRecursionDepth   /* max depth */
        );

    SetCursor(curNormal);
    TraceTag(tagUI, "Done with PlayBestMove()");
    DrawBoard(b);

    if (rGameState.iPlayerTurn == BLACK)
    rGameState.iPlayerTurn = RED;
    else
    rGameState.iPlayerTurn = BLACK;

    CMoves.NewMove(b, rGameState.iPlayerTurn);

    iGameState = GameOver(b, rGameState.iPlayerTurn);
    switch (iGameState)
    {
    case GAME_WON:
            rGameState.fGameOver = TRUE;
            DisplayGameOver(hMainWnd, rGameState.iPlayerTurn);
        break;
        case GAME_DRAWN:
        rGameState.fGameOver = TRUE;
        DisplayGameDrawn(hMainWnd, rGameState.iPlayerTurn);
            break;
        case GAME_PLAYABLE:
        OnGamePlayableAfterMove();
            break;
        default:
            AssertSz(FALSE, "STILL dead");
    }

    rGameState.fComputerBusy = FALSE;

    return(0);

}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

void inline SetupLButtonDown(HWND hWnd, UINT keyFlags, int x, int y)
{
    int iSX = 0;
    int iSY = 0;

    (pntStart).x = x;
    (pntStart).y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);

    if (fValidSetupPiece(b, iSX, iSY))
    {
    HDC hDC;

    SetCursor(curPiece);
    hDC = GetDC(hWnd);
    AssertSz((iSX >= 0) && ((iSX <= 7) || (iSX == 10)), "iSX out of range on ButtonDown");
    AssertSz((iSY >= 0) && (iSY <= 7), "iSY out of range on ButtonDown");
        if ((iSX != 10) && (!(GetKeyState(VK_CONTROL) & 0x8000)))
            vClearMovingPiece(hDC, iSX, iSY);
    rGameState.fMoveInProgress = TRUE;
    }
    else
    {
        TraceTag(tagUI, "BEEP: Not a valid setup piece");
    MessageBeep(MB_OK);
    rGameState.fMoveInProgress = FALSE;
    }
    rGameState.fLMouseButtonDown = TRUE;
    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);

}

void inline SetupRButtonDown(HWND hWnd, UINT keyFlags, int x, int y)
{
    int iSX = 0;
    int iSY = 0;

    TraceTag(tagMouse, "in SetupRButtonDown()");

    (pntStart).x = x;
    (pntStart).y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);

    if (fValidSetupPiece(b, iSX, iSY))
    {
    HDC hDC;

    SetCursor(curPiece);
    hDC = GetDC(hWnd);
    AssertSz((iSX >= 0) && ((iSX <= 7) || (iSX == 10)), "iSX out of range on ButtonDown");
    AssertSz((iSY >= 0) && (iSY <= 7), "iSY out of range on ButtonDown");
        vClearMovingPiece(hDC, iSX, iSY);
    rGameState.fMoveInProgress = TRUE;
    }
    else
    {
        TraceTag(tagUI, "BEEP: Not a valid setup piece (in RButtonDown)");
    MessageBeep(MB_OK);
    rGameState.fMoveInProgress = FALSE;
    }
    rGameState.fRMouseButtonDown = TRUE;
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

}

void inline NormalLButtonDown(HWND hWnd, UINT keyFlags, int x, int y)
{
    int iSX = 0;
    int iSY = 0;

    AssertSz((rGameState.iPlayerTurn == RED) ||
       (rGameState.iPlayerTurn == BLACK), "Player turn is bogus.");

    TraceTag(tagMouse, "in NormalLButtonDown()");

    if ((HUMAN_PLAYER == rConfig.rPlayer[rGameState.iPlayerTurn].iPlayerType) &&
    (!rGameState.fPaused))
    {
    TraceTag(tagUI, "Button down at x:%d - y:%d", x, y);
    (pntStart).x = x;
    (pntStart).y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);
    if (fValidPlayerPiece(b, iSX, iSY))
    {
        HDC hDC;

        SetCursor(curPiece);
        hDC = GetDC(hWnd);
        AssertSz((iSX >= 0) && (iSX <= 7), "iSX out of range on ButtonDown");
        AssertSz((iSY >= 0) && (iSY <= 7), "iSY out of range on ButtonDown");
        vClearMovingPiece(hDC, iSX, iSY);
        rGameState.fMoveInProgress = TRUE;
    }
    else
    {
            TraceTag(tagUI, "BEEP: Not a valid piece in LButtonDown");
        MessageBeep(MB_OK);
        rGameState.fMoveInProgress = FALSE;
    }
    }
    else
    {
        TraceTag(tagUI, "BEEP: Wrong player in LButtonDown");
    MessageBeep(MB_OK);
    }

    rGameState.fLMouseButtonDown = TRUE;
    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);

}

void inline SetupLButtonUp(HWND hWnd, WPARAM uParam, int x, int y)
{

    TraceTag(tagMouse, "in SetupLButtonUp()");

    if (rGameState.fMoveInProgress)
    {
    int     iEngineStart=0;
    int     iEngineEnd=0;
    int     iSX = 0;
    int     iSY = 0;
    int     iEX = 0;
    int     iEY = 0;
    BOOL    fRackSelect = FALSE;
        POINT   pntEnd;

    SetCursor(curNormal);

    rGameState.fLMouseButtonDown = FALSE;
    pntEnd.x = x;
    pntEnd.y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);
        if ((iSX == 10) && (iSY >=2) && (iSY <= 6))
            fRackSelect = TRUE;
        else
        {
            fRackSelect = FALSE;
            vTranslateUIToEngineSquare(&iEngineStart, iSX, iSY);
        }

    vTranslateCurPosToSquare(pntEnd, &iEX, &iEY);
    vTranslateUIToEngineSquare(&iEngineEnd, iEX, iEY);

    TraceTag(tagUI, "Button up at x:%d - y:%d", pntEnd.x, pntEnd.y);

        if (iEngineStart != iEngineEnd)
        {
        if ((iEngineEnd != -1) && (iEngineStart != -1) && (!fRackSelect))
        {
            TraceTag(tagUI, "St: %d * End: %d", iEngineStart, iEngineEnd);

            AssertSz(!fRackSelect, "Trains, Planes, and Automobiles");
            AssertSz(iEngineEnd < SQRS_MAX,"what could be a train");
            AssertSz(iEngineEnd > 0,"what could be trains");
            AssertSz(iEngineStart < SQRS_MAX,"two what could be a train");
            AssertSz(iEngineStart > 0,"two what could be trains");

                b[iEngineEnd] = b[iEngineStart];

            // If high-order bit is set, the control key is down, and I should
            // do a copy (not remove the source piece).

            if (!(GetKeyState(VK_CONTROL) & 0x8000))
                b[iEngineStart] = EMPTY;

        }
            else
            {
            AssertSz(iEngineEnd < SQRS_MAX,"while (TRUE) fork();");
            AssertSz((iEngineEnd > 0) || (-1 == iEngineEnd),"while (TRUE) spoon();");

                if ((iEngineEnd != -1) && (fRackSelect))
                {
                    switch(iSY)
                    {
                        case 2:
                            b[iEngineEnd] = RED;
                            break;
                        case 3:
                            b[iEngineEnd] = BLACK;
                            break;
                        case 4:
                            b[iEngineEnd] = RED | KING;
                            break;
                        case 5:
                            b[iEngineEnd] = BLACK | KING;
                            break;
                        case 6:
                            b[iEngineEnd] = EMPTY;
                            break;
                        default:
                            AssertSz(FALSE, "Phenylketonurics: Contains Phenylalanine");
                            break;
                    }
                }
        }
    }
    else
    {
        // This is what happens if we picked up a piece and dropped it on
        // the same square. Right now this is a no-op.
    }

    DrawBoard(b);
    }

    rGameState.fLMouseButtonDown = FALSE;
    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);

}

void inline SetupRButtonUp(HWND hWnd, WPARAM uParam, int x, int y)
{

    TraceTag(tagMouse, "in SetupRButtonUp()");

    if (rGameState.fMoveInProgress)
    {
    int     iEngineStart=0;
    int     iEngineEnd=0;
    int     iSX = 0;
    int     iSY = 0;
    int     iEX = 0;
    int     iEY = 0;
    BOOL    fRackSelect = FALSE;
        POINT   pntEnd;

    SetCursor(curNormal);

    rGameState.fRMouseButtonDown = FALSE;
    pntEnd.x = x;
    pntEnd.y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);
        if ((iSX == 10) && (iSY >=2) && (iSY <= 6))
            fRackSelect = TRUE;
        else
        {
            fRackSelect = FALSE;
            vTranslateUIToEngineSquare(&iEngineStart, iSX, iSY);
        }

    vTranslateCurPosToSquare(pntEnd, &iEX, &iEY);
    vTranslateUIToEngineSquare(&iEngineEnd, iEX, iEY);

    TraceTag(tagUI, "Button up at x:%d - y:%d", pntEnd.x, pntEnd.y);

        if (iEngineStart == iEngineEnd)
        {
        if ((iEngineStart != -1) && (!fRackSelect))
        {
            TraceTag(tagUI, "St: %d * End: %d", iEngineStart, iEngineEnd);

            AssertSz(!fRackSelect, "Trains, Planes, and Automobiles");
            AssertSz(iEngineEnd < SQRS_MAX,"what could be a train");
            AssertSz(iEngineEnd > 0,"what could be trains");
            AssertSz(iEngineStart < SQRS_MAX,"two what could be a train");
            AssertSz(iEngineStart > 0,"two what could be trains");

                b[iEngineStart] = EMPTY;
        }
    }
    else
    {
        // This is what happens if we clicked on a piece but moved off of the
        // original square.. Right now this is a no-op.
    }

    DrawBoard(b);
    }

    rGameState.fRMouseButtonDown = FALSE;
    TraceTag(tagMouse, "rGameState.fRMouseButtonDown: %d", rGameState.fRMouseButtonDown);

}

void inline NormalLButtonUp(HWND hWnd, WPARAM uParam, int x, int y)
{
    int iGameState = GAME_PLAYABLE;

    TraceTag(tagMouse, "in NormalLButtonUp()");

    if (rGameState.fMoveInProgress)
    {
    int     iEngineStart;
    int     iEngineEnd;
    int     iValid = 0;    // Valid move
    int     iSX = 0;
    int     iSY = 0;
    int     iEX = 0;
    int     iEY = 0;
    POINT   pntEnd;

    SetCursor(curNormal);

    rGameState.fLMouseButtonDown = FALSE;
    pntEnd.x = x;
    pntEnd.y = y;

    vTranslateCurPosToSquare(pntStart, &iSX, &iSY);
    vTranslateUIToEngineSquare(&iEngineStart, iSX, iSY);
    vTranslateCurPosToSquare(pntEnd, &iEX, &iEY);
    vTranslateUIToEngineSquare(&iEngineEnd, iEX, iEY);

    TraceTag(tagUI, "Button up at x:%d - y:%d", pntEnd.x, pntEnd.y);

    if ((iEngineEnd != -1) && (iEngineStart != -1))
    {
        TraceTag(tagUI, "St: %d * End: %d", iEngineStart, iEngineEnd);

        AssertSz(((BLACK == rGameState.iPlayerTurn) ||
              (RED == rGameState.iPlayerTurn)), "Fronk, lots!");

        if (HUMAN_PLAYER == rConfig.rPlayer[rGameState.iPlayerTurn].iPlayerType)
        {
        AssertSz(iEngineEnd < SQRS_MAX,"ugly players what could be a train");
        AssertSz(iEngineEnd >= 0,"ugly players what could be trains");
        AssertSz(iEngineStart < SQRS_MAX,"ugly players two what could be a train");
        AssertSz(iEngineStart >= 0,"ugly players two what could be trains");
        iValid = MoveValid(b, iEngineStart, iEngineEnd, rGameState.iPlayerTurn);
        }
    }
    else
        iValid = -1;

    DrawBoard(b);

        if (iValid == 1)
        {
        if (rGameState.iPlayerTurn == BLACK)
            rGameState.iPlayerTurn = RED;
        else
            rGameState.iPlayerTurn = BLACK;

        CMoves.NewMove(b, rGameState.iPlayerTurn);
            iGameState = GameOver(b, rGameState.iPlayerTurn);
            switch (iGameState)
            {
            case GAME_WON:
                    rGameState.fGameOver = TRUE;
                    DisplayGameOver(hMainWnd, rGameState.iPlayerTurn);
                break;
                case GAME_DRAWN:
                rGameState.fGameOver = TRUE;
                DisplayGameDrawn(hMainWnd, rGameState.iPlayerTurn);
                    break;
                case GAME_PLAYABLE:
            OnGamePlayableAfterMove();
                    break;
                default:
                    AssertSz(FALSE, "very dead");
            }
        }
    }
    rGameState.fMoveInProgress = FALSE;
    rGameState.fLMouseButtonDown = FALSE;
    TraceTag(tagMouse, "rGameState.fLMouseButtonDown: %d", rGameState.fLMouseButtonDown);

}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
    RECT    rChild, rParent;
    int     wChild, hChild, wParent, hParent;
    int     wScreen, hScreen, xNew, yNew;
    HDC     hdc;

    // Get the Height and Width of the child window
    GetWindowRect (hwndChild, &rChild);
    wChild = rChild.right - rChild.left;
    hChild = rChild.bottom - rChild.top;

    // Get the Height and Width of the parent window
    GetWindowRect (hwndParent, &rParent);
    wParent = rParent.right - rParent.left;
    hParent = rParent.bottom - rParent.top;

    // Get the display limits
    hdc = GetDC (hwndChild);
    wScreen = GetDeviceCaps (hdc, HORZRES);
    hScreen = GetDeviceCaps (hdc, VERTRES);
    ReleaseDC (hwndChild, hdc);

    // Calculate new X position, then adjust for screen
    xNew = rParent.left + ((wParent - wChild) /2);
    if (xNew < 0) {
        xNew = 0;
    } else if ((xNew+wChild) > wScreen) {
        xNew = wScreen - wChild;
    }

    // Calculate new Y position, then adjust for screen
    yNew = rParent.top  + ((hParent - hChild) /2);
    if (yNew < 0) {
        yNew = 0;
    } else if ((yNew+hChild) > hScreen) {
        yNew = hScreen - hChild;
    }

    // Set it, and return
    return SetWindowPos (hwndChild, NULL,
        xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

/****************************************************************************
*****************************************************************************
*****************************************************************************
****************************************************************************/

BOOL FAR PASCAL ComputerSettingsDialogProc (HWND hDlg,
                    WORD wMsgID,
                    WPARAM wParam,
                    LPARAM lParam)
{
    static struct rPlayerRec *prPlayer;

    switch (wMsgID)

    {
    case WM_INITDIALOG:
        prPlayer = (struct rPlayerRec FAR *) lParam;
        Assert(prPlayer);
        SendDlgItemMessage(hDlg, IDCB_USE_OPENING_BOOK, BM_SETCHECK, prPlayer->iUseOpeningBook, 0L);
        SendDlgItemMessage(hDlg, IDCB_USE_MEM_POSITIONS, BM_SETCHECK, prPlayer->iMemPositions, 0L);
        SendDlgItemMessage(hDlg, IDCB_USE_MOVE_THEORY, BM_SETCHECK, prPlayer->iUseMoveTheory, 0L);
        SendDlgItemMessage(hDlg, IDCB_MOVE_SHUFFLING, BM_SETCHECK, prPlayer->iUseMoveShuffling, 0L);
        SendDlgItemMessage(hDlg, IDCB_GOOD_MOVE_SKIPPING, BM_SETCHECK, (int)prPlayer->lUseGoodMoveSkipping, 0L);
        SendDlgItemMessage(hDlg, IDCB_EQUAL_MOVE_SKIPPING, BM_SETCHECK, prPlayer->iUseEqualMoveSkipping, 0L);
        SendDlgItemMessage(hDlg, IDCB_ALPHA_BETA_PRUNING, BM_SETCHECK, prPlayer->iUseAlphaBetaPruning, 0L);

        // Set other edit controls

        SetDlgItemLong(hDlg, IDEC_RECURSION_DEPTH, prPlayer->iMaxRecursionDepth);
            SetDlgItemLong(hDlg, IDEC_PRUNING_DEPTH, prPlayer->lAlphaPruningOriginalDepth);
            SetDlgItemLong(hDlg, IDEC_PRUNING_WIDTH, prPlayer->lAlphaPruningWidth);
        return TRUE;

    case WM_COMMAND:
        switch (wParam)
        {
        case IDOK:
            EndDialog (hDlg, TRUE);

            // Get Checkbox values
            prPlayer->iUseOpeningBook = (int)SendDlgItemMessage(hDlg, IDCB_USE_OPENING_BOOK, BM_GETCHECK, 0, 0L);
            prPlayer->iMemPositions = (int)SendDlgItemMessage(hDlg, IDCB_USE_MEM_POSITIONS, BM_GETCHECK, 0, 0L);
            prPlayer->iUseMoveTheory = (int)SendDlgItemMessage(hDlg, IDCB_USE_MOVE_THEORY, BM_GETCHECK, 0, 0L);
            prPlayer->iUseMoveShuffling = (int)SendDlgItemMessage(hDlg, IDCB_MOVE_SHUFFLING, BM_GETCHECK, 0, 0L);

prPlayer->lUseGoodMoveSkipping = (int)SendDlgItemMessage(hDlg, IDCB_GOOD_MOVE_SKIPPING, BM_GETCHECK, 0, 0L);
            prPlayer->iUseEqualMoveSkipping = (int)SendDlgItemMessage(hDlg, IDCB_EQUAL_MOVE_SKIPPING, BM_GETCHECK, 0, 0L);
            prPlayer->iUseAlphaBetaPruning = (int)SendDlgItemMessage(hDlg, IDCB_ALPHA_BETA_PRUNING, BM_GETCHECK, 0, 0L);
            prPlayer->iMemPositions = (int)SendDlgItemMessage(hDlg, IDCB_USE_MEM_POSITIONS, BM_GETCHECK, 0, 0L);

            // Get Edit Control Values

            prPlayer->iMaxRecursionDepth         = GetDlgItemInt(hDlg, IDEC_RECURSION_DEPTH);
                    prPlayer->lAlphaPruningOriginalDepth = GetDlgItemLong(hDlg, IDEC_PRUNING_DEPTH);
                    prPlayer->lAlphaPruningWidth         = GetDlgItemLong(hDlg, IDEC_PRUNING_WIDTH);

            return TRUE;

        case IDCANCEL:
            EndDialog (hDlg, TRUE);
            return TRUE;
        }                                    // end switch
        break;
    }
    return FALSE;
}

BOOL FAR PASCAL PlayersDialogProc (HWND hDlg,
                    WORD wMsgID,
                    WPARAM wParam,
                    LPARAM lParam)
{
    switch (wMsgID)

    {
    case WM_INITDIALOG:
        switch(rConfig.rPlayer[PLAYER1].iPlayerType)
        {
        case HUMAN_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P1_HUMAN, BM_SETCHECK, 1, 0L);
            break;
        case COMPUTER_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P1_COMPUTER, BM_SETCHECK, 1, 0L);
            break;
        case NETWORK_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P1_NETWORK, BM_SETCHECK, 1, 0L);
            break;
        default:
            AssertSz(FALSE, "rConfig.rPlayer[PLAYER1].iPlayerType not valid");
            break;
        }

        switch(rConfig.rPlayer[PLAYER2].iPlayerType)
        {
        case HUMAN_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P2_HUMAN, BM_SETCHECK, 1, 0L);
            break;
        case COMPUTER_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P2_COMPUTER, BM_SETCHECK, 1, 0L);
            break;
        case NETWORK_PLAYER:
            SendDlgItemMessage(hDlg, IDRB_P2_NETWORK, BM_SETCHECK, 1, 0L);
            break;
        default:
            AssertSz(FALSE, "rConfig.rPlayer[PLAYER2].iPlayerType not valid");
            break;
        }

        return TRUE;

    case WM_COMMAND:
        switch (wParam)
        {
        case IDB_P1_COMPUTER_SETUP:

            DialogBoxParam (hInst,       // current instance
                    "COMPUTER_SETUP_MASTER", // dlg resource to use
                    hDlg,        // parent handle
                    (DLGPROC)ComputerSettingsDialogProc, // Config() instance address
                    (LPARAM) (struct rPlayerRec *) (&(rConfig.rPlayer[PLAYER1])));
            break;

        case IDB_P2_COMPUTER_SETUP:

            DialogBoxParam (hInst,       // current instance
                    "COMPUTER_SETUP_MASTER", // dlg resource to use
                    hDlg,        // parent handle
                    (DLGPROC)ComputerSettingsDialogProc, // Config() instance address
                    (LPARAM) (struct rPlayerRec *) (&(rConfig.rPlayer[PLAYER2])));
            break;

        case IDB_P1_NETWORK_SETUP:
        case IDB_P2_NETWORK_SETUP:
            NYI("Player Network Setup");
            break;

        case IDOK:
            EndDialog (hDlg, TRUE);
            if (SendDlgItemMessage(hDlg, IDRB_P1_HUMAN, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER1].iPlayerType = HUMAN_PLAYER;
            if (SendDlgItemMessage(hDlg, IDRB_P1_COMPUTER, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER1].iPlayerType = COMPUTER_PLAYER;
            if (SendDlgItemMessage(hDlg, IDRB_P1_NETWORK, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER1].iPlayerType = NETWORK_PLAYER;
            if (SendDlgItemMessage(hDlg, IDRB_P2_HUMAN, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER2].iPlayerType = HUMAN_PLAYER;
            if (SendDlgItemMessage(hDlg, IDRB_P2_COMPUTER, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER2].iPlayerType = COMPUTER_PLAYER;
            if (SendDlgItemMessage(hDlg, IDRB_P2_NETWORK, BM_GETCHECK, 0, 0L))
            rConfig.rPlayer[PLAYER2].iPlayerType = NETWORK_PLAYER;
            return TRUE;

        case IDCANCEL:
            EndDialog (hDlg, TRUE);
            return TRUE;
        }                                    // end switch
        break;
    }
    return FALSE;
}

BOOL FAR PASCAL DebugConfigDialogProc (HWND hDlg,
                    WORD wMsgID,
                    WPARAM wParam,
                    LPARAM lParam)
{
    switch (wMsgID)

    {
    case WM_INITDIALOG:
        SetDlgItemInt(hDlg, IDEC_MAX_MOVES, rConfig.iMaxMoves);
        return TRUE;

    case WM_COMMAND:
        switch (wParam)
        {
        case IDOK:
            EndDialog (hDlg, TRUE);
            rConfig.iMaxMoves = GetDlgItemInt(hDlg, IDEC_MAX_MOVES);
            return TRUE;

        case IDCANCEL:
            EndDialog (hDlg, TRUE);
            return TRUE;
        }                                    // end switch
        break;
    }
    return FALSE;
}

int FAR PASCAL PlayerTurnDialogProc (HWND hDlg,
                    WORD wMsgID,
                    WPARAM wParam,
                    LPARAM lParam)
{
    switch (wMsgID)

    {
    case WM_INITDIALOG:
            CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
        return TRUE;

    case WM_COMMAND:
        switch (wParam)
        {
        case IDB_PLAYER1:
            EndDialog (hDlg, PLAYER1);
            return TRUE;

        case IDB_PLAYER2:
            EndDialog (hDlg, PLAYER2);
            return TRUE;

        case IDCANCEL:
            // Return -1 so the calling function will know that the result
            // from the dialog call isn't equal to a player ID.

            EndDialog (hDlg, -1);
            break;
        }                                    // end switch
        break;
    }
    return FALSE;
}

LRESULT CALLBACK About(
        HWND hDlg,           // window handle of the dialog box
        UINT message,        // type of message
        WPARAM wParam,       // message-specific information
        LPARAM lParam)
{
    static  HFONT hfontDlg;
    static  LPSTR   lpVersion;
    static  DWORD   dwVerInfoSize;
    static  DWORD   dwVerHnd;
    static  UINT    uVersionLen;
    static  WORD    wRootLen;
    BOOL    bRetCode;
    static  int     i;
    static  char    szFullPath[256];
    static  char    szResult[256];
    static  char    szGetName[256];

    switch (message) {
        case WM_INITDIALOG:  // message: initialize dialog box
            // Create a font to use
            hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0,
                VARIABLE_PITCH | FF_SWISS, "");

            // Center the dialog over the application window
            CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));

            // Get version information from the application
            GetModuleFileName (hInst, szFullPath, sizeof(szFullPath));
            dwVerInfoSize = GetFileVersionInfoSize(szFullPath, &dwVerHnd);
            if (dwVerInfoSize) {
                // If we were able to get the information, process it:
                LPSTR   lpstrVffInfo;
                HANDLE  hMem;
                hMem = GlobalAlloc(GMEM_MOVEABLE, dwVerInfoSize);
                lpstrVffInfo  = (char *)GlobalLock(hMem);
                GetFileVersionInfo(szFullPath, dwVerHnd, dwVerInfoSize, lpstrVffInfo);
                lstrcpy(szGetName, "\\StringFileInfo\\040904E4\\");
                wRootLen = lstrlen(szGetName);

                // Walk through the dialog items that we want to replace:
                for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++) {
                    GetDlgItemText(hDlg, i, szResult, sizeof(szResult));
                    szGetName[wRootLen] = (char)0;
                    lstrcat (szGetName, szResult);
                    uVersionLen   = 0;
                    lpVersion     = NULL;
                      bRetCode      =  VerQueryValue((LPVOID)lpstrVffInfo,
                          (LPSTR)szGetName,
                          (LPVOID *)&lpVersion,
#if defined (_WIN32)
                          (PUINT)&uVersionLen); // For MIPS strictness
#else
                          (UINT FAR *)&uVersionLen);
#endif

                      if ( bRetCode && uVersionLen && lpVersion) {
                          // Replace dialog item text with version info
                          lstrcpy(szResult, lpVersion);
                          SetDlgItemText(hDlg, i, szResult);
                          SendMessage (GetDlgItem (hDlg, i), WM_SETFONT, (UINT)hfontDlg, TRUE);
                      }
                } // for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++)

                GlobalUnlock(hMem);
                GlobalFree(hMem);
            } // if (dwVerInfoSize)

            return (TRUE);

        case WM_COMMAND:                      // message: received a command
            if (LOWORD(wParam) == IDOK        // "OK" box selected?
            || LOWORD(wParam) == IDCANCEL) {  // System menu close command?
                EndDialog(hDlg, TRUE);        // Exit the dialog
                DeleteObject (hfontDlg);
                return (TRUE);
            }
            break;
    }
    return (FALSE); // Didn't process the message

    lParam; // This will prevent 'unused formal parameter' warnings
}


WCHECK.H

//---[ Global defines ]----------------------------------------------------

#define MAX_SQUARE_SIZE     100
#define MIN_SQUARE_SIZE     15
#define SQUARE_GRANULARITY  2

#define BOARD_TOP       10
#define BOARD_LEFT      10

#define RACK_LEFT       10      // * Square size
#define RACK_TOP        2       // * Square size

#define GAME_WON        0
#define GAME_PLAYABLE   1
#define GAME_DRAWN      2

#define INI_NAME        "WCHECK.INI"
#define APP_NAME        "Checkers"
#define ID_TIMER            1
#define TIMER_GRANULARITY   500

#define HUMAN_PLAYER    0
#define COMPUTER_PLAYER 1
#define NETWORK_PLAYER  2

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK CheckersWndProc        (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About          (HWND, UINT, WPARAM, LPARAM);
void NEAR PASCAL FillBoard(BYTE *pb, HDC hDC);
void DrawBoard(BOARD b);
void NEAR PASCAL CheckGenPieces(HDC hDC);
void NEAR PASCAL CheckCreate();
void NEAR PASCAL CheckPaint(BOARD b, HDC hDC);
BOOL CenterWindow (HWND hwndChild, HWND hwndParent);
void NEAR PASCAL CheckMouseMove(POINT point);
void NEAR PASCAL CheckChar(WORD code);
void NEAR PASCAL CheckMouseClick(POINT point);
void NEAR PASCAL UpdateCursor(HWND hwnd);
void inline vTranslateCurPosToSquare(POINT pntCur, int *pix, int *piy);
void inline vTranslateUIToEngineSquare(int *piEngine, int iX, int iY);
void inline vTranslateEngineToUISquare(int iEngine, int *piX, int *piY);
BOOL fValidPlayerPiece(BOARD b, int ix, int iy);
void vClearMovingPiece(HDC hDC, int iX, int iY);
VOID WriteINI ();
VOID ReadINI ();
long WINAPI ComputerMove();
void _inline DisplayGameOver(HWND hWnd, int iPlayerTurn);
void _inline DisplayGameDrawn(HWND hWnd, int iPlayerTurn);

BOOL FAR PASCAL ConfigDialogProc (HWND hDlg,
                                        WORD wMsgID,
                                        WPARAM wParam,
                                        LPARAM lParam);
BOOL FAR PASCAL PlayersDialogProc (HWND hDlg,
                                        WORD wMsgID,
                                        WPARAM wParam,
                                        LPARAM lParam);
BOOL FAR PASCAL DebugConfigDialogProc (HWND hDlg,
                                        WORD wMsgID,
                                        WPARAM wParam,
                                        LPARAM lParam);
BOOL FAR PASCAL ComputerSettingsDialogProc (HWND hDlg,
                                        WORD wMsgID,
                                        WPARAM wParam,
                                        LPARAM lParam);

int FAR PASCAL PlayerTurnDialogProc (HWND hDlg,
                                        WORD wMsgID,
                                        WPARAM wParam,
                                        LPARAM lParam);

void MessageInt(char *pszText, int iValue);
void inline NormalLButtonUp(HWND hWnd, UINT keyFlags, int x, int y);
void inline SetupLButtonUp(HWND hWnd, UINT keyFlags, int x, int y);
void inline SetupRButtonUp(HWND hWnd, UINT keyFlags, int x, int y);
void inline NormalLButtonDown(HWND hWnd, UINT keyFlags, int x, int y);
void inline SetupLButtonDown(HWND hWnd, UINT keyFlags, int x, int y);
void inline SetupRButtonDown(HWND hWnd, UINT keyFlags, int x, int y);


CHECK.CPP

/* function prototyping for internal functions */
long CheckMiniMaxAlphaBeta(BOARD b, int t, int j, int d, long c, long p);

#define JUMP_CONT_DEEPER 1

/* --------------------------------------------------------------------------
true if previous level should prune
else false

note: it would be possible to not prune equal qualities becuase they
      may be used to increase the number of available suggestions

statistics: 02:42:00  3-13-1994
            if this function is return 0, six levels takes 33 seconds
            if this function is implemented, six levels takes 9 seconds
-------------------------------------------------------------------------- */
inline int AlphaBeta(int t,long q, long c)
{
    pdebug(stddbg,"alpha-beta pruning t=%d q=%ld c=%ld %s(%d)\n",t,q,c,__FILE__,__LINE__);

    if (c > 0)
        {
        if (computer_color & t)
            {
            if (q > c)
                {
                pdebug(stddbg,"alpha-beta COMP pruning t=%d q=%ld c=%ld %s(%d)\n",t,q,c,__FILE__,__LINE__);
                return 1;
                }
            }
        else
            {
            if (q < c)
                {
                pdebug(stddbg,"alpha-beta USER pruning t=%d q=%ld c=%ld %s(%d)\n",t,q,c,__FILE__,__LINE__);
                return 1;
                }
            }
        }
    return 0;
}

/* --------------------------------------------------------------------------
The algorithm is basically a min-max tree
This function takes care of whether or no we remember the Min or the Max
In checkers terms this means the following:
    if it is my (the computers) move, pick the move that results in the highest quality
    if it is your move, remember the move that does the most damage (min quality)

Parameters:
    t    - whos turn it is (RED or BLACK)
    type - the move type.  this parameter should be a constant from check.h
           it says if it the move is a black or red move, and if the move is
           a sliding or jumping move.  This also indicates if the move is a
           move towards the left or towards the right.
    number - this is the index into the move table derived from 'type'
    d      - current recursion depth (debugging purposes only)
    besttype    - type corresponding with bestquality
    bestnumber  - number corresponding with bestquality
    bestquality - either a min or a max of qualities depending on current turn
-------------------------------------------------------------------------- */
inline void RememberMove(int t,int type, int number, long q,int d,
                         int* besttype, int* bestnumber, long* bestquality,
                         long c, long p, int* fBreak)
{
    *fBreak = AlphaBeta(t,q,c);
    if (q == *bestquality)
        {
        pdebug(stddbg,"evaluation (equal move) t=%d type=%d num=%d q=%ld d=%d %s(%d)\n",t,type,number,q,d,__FILE__,__LINE__);
        if (1 & rand()) return;
        }
    else if (computer_color & t)
        {
        pdebug(stddbg,"evaluation (computer) t=%d type=%d num=%d q=%ld d=%d %s(%d)\n",t,type,number,q,d,__FILE__,__LINE__);
        if (q < *bestquality) return;
        }
    else
        {
        pdebug(stddbg,"evaluation (opponent) t=%d type=%d num=%d q=%ld d=%d %s(%d)\n",t,type,number,q,d,__FILE__,__LINE__);
        if (*bestquality > 0 && q > *bestquality) return;
        }

    pdebug(stddbg,"RememberMove! evaluated t=%d type=%d num=%d q=%ld d=%d %s(%d)\n",t,type,number,q,d,__FILE__,__LINE__);
    *besttype = type;
    *bestnumber = number;
    *bestquality = q;
    return;
}

/* --------------------------------------------------------------------------
This function calls recurse for each possible jumping move for t's turn
starting from the start position

Note: there is special logic to crown men since landing in a crown position
      can not continue the jump, but having a man that is already a king land
      in a crown position can continue a jump.
If the piece I am moving is a king, I look at both BLACK and RED moves.

Parameters:
    b     - board (value is not changed)
    start - starting position
    t     - whos turn it is (RED or BLACK)
    besttype    - type corresponding with bestquality
    bestnumber  - number corresponding with bestquality
    bestquality - either a min or a max of qualities depending on current turn
-------------------------------------------------------------------------- */
int IterateJumps(BOARD b, int start, int t, int d,
                 int* besttype, int* bestnumber, long* bestquality,
                 long c, long p, int *fBreak)
{
    int moves_made=0;
    long q;
    SQUARE play_board[SQRS_MAX];

    if ((RED == t) || (KING & b[start]))
        {
        AssertSz(b[start] & t, "chicken");
        if (0 == b[red_jump_lut[start][1]] && red_jump_lut[start][1] && (next(t) & b[red_jump_lut[start][0]]))
            {
            pdebug(stddbg,"trying RED_JUMP0 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMoveNoKing_RED_JUMP0(play_board,start);
            if (0 == red_jump_lut[start][4] || (KING & play_board[red_jump_lut[start][1]]))
                {
                q = CheckMiniMaxAlphaBeta(play_board,t,red_jump_lut[start][1],JUMP_CONT_DEEPER+d,c,p);
                }
            else
                {
                AssertSz(play_board[red_jump_lut[start][1]], "what the?");
                play_board[red_jump_lut[start][1]] |= red_jump_lut[start][4];
                q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
                }
            RememberMove(t,TYPE_RED_JUMP0,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        if (0 == b[red_jump_lut[start][3]] && red_jump_lut[start][3] && (next(t) & b[red_jump_lut[start][2]]))
            {
            pdebug(stddbg,"trying RED_JUMP2 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMoveNoKing_RED_JUMP2(play_board,start);
            if (0 == red_jump_lut[start][4] || (KING & play_board[red_jump_lut[start][3]]))
                {
                q = CheckMiniMaxAlphaBeta(play_board,t,red_jump_lut[start][3],JUMP_CONT_DEEPER+d,c,p);
                }
            else
                {
                AssertSz(play_board[red_jump_lut[start][3]], "this stupid piece");
                play_board[red_jump_lut[start][3]] |= red_jump_lut[start][4];
                q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
                }
            RememberMove(t,TYPE_RED_JUMP2,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        }
    if ((BLACK == t) || (KING & b[start]))
        {
        AssertSz(b[start] & t, "but... I thought it was my turn");
        if (0 == b[black_jump_lut[start][1]] && black_jump_lut[start][1] && (next(t) & b[black_jump_lut[start][0]]))
            {
            pdebug(stddbg,"trying BLACK_JUMP0 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMoveNoKing_BLACK_JUMP0(play_board,start);
            if (0 == black_jump_lut[start][4] || (KING & play_board[black_jump_lut[start][1]]))
                {
                q = CheckMiniMaxAlphaBeta(play_board,t,black_jump_lut[start][1],JUMP_CONT_DEEPER+d,c,p);
                }
            else
                {
                AssertSz(play_board[black_jump_lut[start][1]], "you thought it was who's turn?");
                play_board[black_jump_lut[start][1]] |= black_jump_lut[start][4];
                q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
                }
            RememberMove(t,TYPE_BLACK_JUMP0,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        if (0 == b[black_jump_lut[start][3]] && black_jump_lut[start][3] && (next(t) & b[black_jump_lut[start][2]]))
            {
            pdebug(stddbg,"trying BLACK_JUMP2 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMoveNoKing_BLACK_JUMP2(play_board,start);
            if (0 == black_jump_lut[start][4] || (KING & play_board[black_jump_lut[start][3]]))
                {
                q = CheckMiniMaxAlphaBeta(play_board,t,black_jump_lut[start][3],JUMP_CONT_DEEPER+d,c,p);
                }
            else
                {
                AssertSz(play_board[black_jump_lut[start][3]], "maggots");
                play_board[black_jump_lut[start][3]] |= black_jump_lut[start][4];
                q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
                }
            RememberMove(t,TYPE_BLACK_JUMP2,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        }
    return moves_made;
}

/* --------------------------------------------------------------------------
This function calls recurse after making each possible sliding move for t's turn
starting from the start position

If the piece I am moving is a king, I look at both BLACK and RED moves.

Parameters:
    b     - board (value is not changed)
    start - starting position
    t     - whos turn it is (RED or BLACK)
    besttype    - type corresponding with bestquality
    bestnumber  - number corresponding with bestquality
    bestquality - either a min or a max of qualities depending on current turn
-------------------------------------------------------------------------- */
int IterateMoves(BOARD b, int start, int t, int d,
                 int* besttype, int* bestnumber, long* bestquality,
                 long c, long p, int *fBreak)
{
    int moves_made=0;
    long q;
    SQUARE play_board[SQRS_MAX];

    if ((RED == t) || (KING & b[start]))
        {
        AssertSz(b[start] & t, "I wish I could fly");
        if (0 == b[red_lut[start][0]] && red_lut[start][0])
            {
            pdebug(stddbg,"trying RED0 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMove_RED0(play_board,start);
            q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
            RememberMove(t,TYPE_RED0,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        if (0 == b[red_lut[start][2]] && red_lut[start][2])
            {
            pdebug(stddbg,"trying RED2 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMove_RED2(play_board,start);
            q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
            RememberMove(t,TYPE_RED2,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        }

    if ((BLACK == t) || (KING & b[start]))
        {
        AssertSz(b[start] & t, "what are you... ");
        if (0 == b[black_lut[start][0]] && black_lut[start][0])
            {
            pdebug(stddbg,"trying BLACK0 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMove_BLACK0(play_board,start);
            q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
            RememberMove(t,TYPE_BLACK0,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        if (0 == b[black_lut[start][2]] && black_lut[start][2])
            {
            pdebug(stddbg,"trying BLACK2 std move %d %s(%d)\n",start,__FILE__,__LINE__);
            CopyBoard(b,play_board);
            MakeMove_BLACK2(play_board,start);
            q = CheckMiniMaxAlphaBeta(play_board,next(t),0,1+d,p,*bestquality);
            RememberMove(t,TYPE_BLACK2,start,q,d,besttype,bestnumber,bestquality,c,p,fBreak);
            ++moves_made;
            }
        }
    return moves_made;
}

/* --------------------------------------------------------------------------

RECURSIVE MIN MAX ALGORITHM

Parameters:
    b - pointer to the board
    t - whos turn it is (RED or BLACK)
    j - space on board in which jump landed or zero if this
        is not a jump continuation
    d - depth of recursion

    alpha-beta pruning parameters:

    c - cutoff value for min max algorithm
    p - threshold to be passed on to next level

always returns the value of the best outcome of the board

note: this function is to be called from within the checkers engine only
note: this function evaluates moves based on the piece order structure defined
      in lut.cpp.  The reason for this is that we gain two things by looking
      at the best moves first:
        1) pruning is more effective
        2) if we must give up due to time constraint, we have already
           evaluated some of the best moves.

-------------------------------------------------------------------------- */
long CheckMiniMaxAlphaBeta(BOARD b, int t, int j, int d, long c, long p)
{
    /* ----- stack variables ----- */
    int  moves_made        = 0;
    int  best_move_type    = -1;
    int  best_move_number  = -1;
    long best_move_quality = -1;
    int  fBreak            = 0;

    /* ----- debug ----- */
    #ifdef DEBUG
    debug=1;
    if (debug && d < 7) PrintBoard(b,d);
    #endif

    AssertSz(b[0] == 0, "no b for my b my for my be my");
    AssertSz(d >= 0, "i am so tired of this");

    /* ----- if we have reached maximum depth, return now ----- */
    if (d >= depth_maximum) return QualityOfBoard(b,next(t));

    /* ----- iterate through all possible moves ----- */
    if (0 == j)
        {
        int i;
        pdebug(stddbg,"standard iteration d=%d %s(%d)\n",d,__FILE__,__LINE__);
        for (i=0;i<SQRS_MAX; i++)
            {
            int piece;
            piece = (int) piece_order[i].m;
            if (0 == (b[piece] & t)) continue; /* not our piece */
            moves_made += IterateJumps(b,piece,t,d,&best_move_type,&best_move_number,&best_move_quality,c,p,&fBreak);
            if (fBreak)
                {
                AssertSz(moves_made,"I'm pruning something I have not looked at...");
                break;
                }
            }
        if (0 == moves_made /* enforce the must jump rule */
            #ifndef HIGH_PERFORMANCE
                || 0==rConfig.iMustJump
            #endif
            )
            {
            pdebug(stddbg,"could not find a jumping move to make %s(%d)\n",__FILE__,__LINE__);
            for (i=0;i<SQRS_MAX; i++)
                {
                int piece;
                piece = (int) piece_order[i].m;
                if (0 == (b[piece] & t)) continue; /* not our piece */
                moves_made += IterateMoves(b,piece,t,d,&best_move_type,&best_move_number,&best_move_quality,c,p,&fBreak);
                if (fBreak)
                    {
                    AssertSz(moves_made,"to Scottland, I have not been");
                    break;
                    }
                }
            }
        if (0 == moves_made)
            {
            pdebug(stddbg,"could not find a move to make (error not handled) %s(%d)\n",__FILE__,__LINE__);
            return QualityOfBoard(b,next(t));
            }

        return best_move_quality;
        }

    /* ----- handle jump continuation ----- */
    pdebug(stddbg,"jump continuation d=%d %s(%d)\n",d,__FILE__,__LINE__);
    moves_made = IterateJumps(b,j,t,d,&best_move_type,&best_move_number,&best_move_quality,c,p,&fBreak);
    // AssertSz(0==fBreak,"is this a valid assert?");
    if (0==moves_made)
        {
        return CheckMiniMaxAlphaBeta(b,next(t),0,d,p,best_move_quality);
        }
    return best_move_quality;

} /* end CheckMiniMaxAlphaBeta */


DEBUGIO.CPP

/* --------------------------------------------------------------------------
Print Board is a debugging routine and should not be used for any other purpose
-------------------------------------------------------------------------- */

int p(BOARD b, int x)
{
    int c=' '; /* empty square */
    if (b[x] & RED)   c = 'r';
    if (b[x] & BLACK) c = 'b';
    if (b[x] & KING)  c = toupper(c);
    return c;
}

void PrintBoard(BOARD b, int d)
{
    const unsigned char rs=254; /* red square */
    static char buf[256];

    pdebug(stddbg," ");
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",( rs),     (p(b, 1)), ( rs),     (p(b, 2)), ( rs),     (p(b, 3)), ( rs),     (p(b, 4)) );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",(p(b, 5)), ( rs),     (p(b, 6)), ( rs),     (p(b, 7)), ( rs),     (p(b, 8)), ( rs)     );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",( rs),     (p(b, 9)), ( rs),     (p(b,10)), ( rs),     (p(b,11)), ( rs),     (p(b,12)) );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",(p(b,13)), ( rs),     (p(b,14)), ( rs),     (p(b,15)), ( rs),     (p(b,16)), ( rs)     );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",( rs),     (p(b,17)), ( rs),     (p(b,18)), ( rs),     (p(b,19)), ( rs),     (p(b,20)) );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",(p(b,21)), ( rs),     (p(b,22)), ( rs),     (p(b,23)), ( rs),     (p(b,24)), ( rs)     );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",( rs),     (p(b,25)), ( rs),     (p(b,26)), ( rs),     (p(b,27)), ( rs),     (p(b,28)) );
    pdebug(stddbg,buf);
    wsprintf(buf,"  %c%c%c%c%c%c%c%c",(p(b,29)), ( rs),     (p(b,30)), ( rs),     (p(b,31)), ( rs),     (p(b,32)), ( rs)     );
    pdebug(stddbg,buf);
    pdebug(stddbg," ");

}

/* --------------------------------------------------------------------------
TextizeBoard produces a text version of the board for people who did
not install this form.
-------------------------------------------------------------------------- */
char* TextizeBoard(BOARD b)
{
    const unsigned char rs='='; /* red square */
    static char buf[2048];

    wsprintf(buf,
        "IPM.Checkers\r\n\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n"
        "  %c%c%c%c%c%c%c%c\r\n",

    ( rs),     (p(b, 1)), ( rs),     (p(b, 2)), ( rs),     (p(b, 3)), ( rs),     (p(b, 4)) ,
    (p(b, 5)), ( rs),     (p(b, 6)), ( rs),     (p(b, 7)), ( rs),     (p(b, 8)), ( rs)     ,
    ( rs),     (p(b, 9)), ( rs),     (p(b,10)), ( rs),     (p(b,11)), ( rs),     (p(b,12)) ,
    (p(b,13)), ( rs),     (p(b,14)), ( rs),     (p(b,15)), ( rs),     (p(b,16)), ( rs)     ,
    ( rs),     (p(b,17)), ( rs),     (p(b,18)), ( rs),     (p(b,19)), ( rs),     (p(b,20)) ,
    (p(b,21)), ( rs),     (p(b,22)), ( rs),     (p(b,23)), ( rs),     (p(b,24)), ( rs)     ,
    ( rs),     (p(b,25)), ( rs),     (p(b,26)), ( rs),     (p(b,27)), ( rs),     (p(b,28)) ,
    (p(b,29)), ( rs),     (p(b,30)), ( rs),     (p(b,31)), ( rs),     (p(b,32)), ( rs)     );

    return buf;

}


PRUNE.CPP

/* --------------------------------------------------------------------------
This function is qsort's callback
to sort the moves based on quality
-------------------------------------------------------------------------- */
int __cdecl cmpgle(const void* elem1, const void* elem2)
{
    if (    (*(struct _piece_order_struct *)elem1).q     >
            (*(struct _piece_order_struct *)elem2).q     )
        return -1;
    else if (    (*(struct _piece_order_struct *)elem1).q     <
                 (*(struct _piece_order_struct *)elem2).q     )
        return 1;
    else
        return 0;
}

/* --------------------------------------------------------------------------
THIS IS THE CHECKERS ENGINE::

b           - is the start board - it will be changed on return to
              reflect the move or moves (in the case of a double jump)
              made
t           - is whos turn it is RED or BLACK
j           - is jump continuation - must be 0
prune_depth - how many levels to recurse to determine what to prune
prune_size  - how many branches of the pruned tree to pursue (note:
              each piece is considered as one branch even though it
              it results in as many as four actual branches
max_depth   - after the tree has been pruned how far should it recurse
              must be greater than the prune_depth

side effects:
            Calls PrintBoard (debugio.cpp)

note: we may wish to use iterative deepening here and base things on time
-------------------------------------------------------------------------- */
long PlayBestMove(BOARD b, int t, int j, int d,
                    int prune_depth, int prune_size, int max_depth)
{
    /* ----- stack variables ----- */
    int  moves_made        =  0;
    int  best_move_type    = -1;
    int  best_move_number  = -1;
    long best_move_quality = -1;
    long alpha_beta_c      = -1;
    int  fBreak            = 0;
    int  i;

    #ifdef DEBUG
    int  jumps_made        =  0;
    #endif

    AssertSz(prune_size <= SQRS_MAX, "me. prune size is way to big");
    AssertSz(prune_size > 1, "how much? prune size is way to small");
    AssertSz(0 == b[0], "b sub zero should equal zero");
    AssertSz(0 == b[SQRS_MAX-1], "b sub MAX-1 should equal zero");

    /* ----- debug ----- */
    if (debug && d < 3) PrintBoard(b,d);

    /* ----- initialize tables and variables ----- */
    depth_maximum = max_depth;
    computer_color = t;
    for (i=0; i<SQRS_MAX; i++)
        {
        piece_order[i].m = (SQUARE) i;
        piece_order[i].q = -1;
        }

    /* ----- seed the random number generator ----- */
    #ifdef DEBUG
    srand( 0 );
    #else
    #ifdef TEST
    srand( 0 );
    #else
    srand( (unsigned)time( NULL ) );
    #endif
    #endif

    /* ----- iterate through all possible moves AT PRUNE_DEPTH ----- */
    if (prune_depth > 0)
        {
        depth_maximum = prune_depth;
        pdebug(stddbgmin,"PlayBestMove d=%d %s(%d)\n",d,__FILE__,__LINE__);
        for (i=0;i<SQRS_MAX; i++)
            {
            int piece;
            piece = (int) piece_order[i].m;
            if (0 == (b[piece] & t)) continue; /* not our piece */
            moves_made += IterateJumps(b,piece,t,d,&best_move_type,&best_move_number,&piece_order[i].q,alpha_beta_c,best_move_quality,&fBreak);
            AssertSz(0 == fBreak,"I must not prune at the root level of the tree");
            #ifdef DEBUG
            jumps_made += moves_made;
            #endif
            }
        if (0 == moves_made /* enforce the must jump rule */
            #ifndef HIGH_PERFORMANCE
                || 0==rConfig.iMustJump
            #endif
            )
            {
            pdebug(stddbgmin,"could not find a jumping move to make %s(%d)\n",__FILE__,__LINE__);
            for (i=0;i<SQRS_MAX; i++)
                {
                int piece;
                piece = (int) piece_order[i].m;
                if (0 == (b[piece] & t)) continue; /* not our piece */
                moves_made += IterateMoves(b,piece,t,d,&best_move_type,&best_move_number,&piece_order[i].q,alpha_beta_c,best_move_quality,&fBreak);
                AssertSz(0 == fBreak,"I ought not prune at the root level of the tree");
                }
            }
        if (0 == moves_made)
            {
            pdebug(stddbgmin,"could not find a move to make (error not handled) %s(%d)\n",__FILE__,__LINE__);
            AssertSz(0, "I give up... why don't you go instead");
            }
        }
    else
        {
        moves_made = 1000;
        prune_size = SQRS_MAX;
        }

    /* ----- if we only have one move to make, dont recurse again ----- */
    if (moves_made > 1)
        {
        /* ----- shuffle the moves ----- */
        for (i=SQRS_MAX-1; i > 0; i--)
            {
            int r;
            struct _piece_order_struct temp;

            /* ----- pick a random interchange ----- */
            r = ((unsigned int)rand()) % ((unsigned int) i);

            /* ----- swap the order ----- */
            temp = piece_order[i];
            piece_order[i] = piece_order[r];
            piece_order[r] = temp;
            }

        /* ----- sort the move table based on the move qualities
                of each move that we tried ----- */
        pdebug(stddbgmin,"sorting... %s(%d)\n",__FILE__,__LINE__);
        qsort(piece_order,SQRS_MAX, sizeof(struct _piece_order_struct), cmpgle);
        pdebug(stddbgmin,"done sorting. %s(%d)\n",__FILE__,__LINE__);

        /* ----- recurse the pruned board to a deeper level ----- */
continue_jump:
        moves_made = 0;
        depth_maximum = max_depth;
        pdebug(stddbgmin,"recursing pruned PlayBestMove d=%d %s(%d)\n",d,__FILE__,__LINE__);
        for (i=0;i<prune_size; i++)
            {
            int piece;
            piece = (int) piece_order[i].m;
            if (0 == (b[piece] & t)) continue; /* not our piece */
            moves_made += IterateJumps(b,piece,t,d,&best_move_type,&best_move_number,&best_move_quality,alpha_beta_c,best_move_quality,&fBreak);
            AssertSz(0 == fBreak,"x2 I must not prune at the root level of the tree");
            #ifdef DEBUG
            jumps_made += moves_made;
            #endif
            }
        if ((0 == moves_made
            #ifndef HIGH_PERFORMANCE
                || 0==rConfig.iMustJump
            #endif
            )&& 0 == j) /* enforce the must jump rule */
            {
            pdebug(stddbgmin,"could not find a jumping move to make %s(%d)\n",__FILE__,__LINE__);
            for (i=0;i<prune_size; i++)
                {
                int piece;
                piece = (int) piece_order[i].m;
                pdebug(stddbgmin,"Next move piece=%d i=%d %s(%d)\n",piece,i,__FILE__,__LINE__);
                if (0 == (b[piece] & t)) continue; /* not our piece */
                moves_made += IterateMoves(b,piece,t,d,&best_move_type,&best_move_number,&best_move_quality,alpha_beta_c,best_move_quality,&fBreak);
                AssertSz(0 == fBreak,"x3 I must not prune at the root level of the tree");
                }
            }
        if (0 == moves_made)
            {
            pdebug(stddbgmin,"could not find a move to make (error ! handled) %s(%d)\n",__FILE__,__LINE__);
            #ifdef DEBUG
            AssertSz(jumps_made, "Your turn.  I mean.  Can I move or something? .. nevermind .. why don't you just go");
            #endif
            }
        }

    /* ----- do the first step of any jumps ----- */
    if (best_move_type > TYPE_BLACK2)
        {
        switch (best_move_type)
            {
            case TYPE_RED_JUMP0   :
                j = red_jump_lut[best_move_number][1]  ;
                piece_order[0].m = (SQUARE) j;
                MakeMoveNoKing_RED_JUMP0(b,best_move_number);
                if (0 != red_jump_lut[best_move_number][4] && !(KING & b[red_jump_lut[best_move_number][1]])) goto no_jump;
                break;

            case TYPE_RED_JUMP2   :
                j = red_jump_lut[best_move_number][3]  ;
                piece_order[0].m = (SQUARE) j;
                MakeMoveNoKing_RED_JUMP2(b,best_move_number);
                if (0 != red_jump_lut[best_move_number][4] && !(KING & b[red_jump_lut[best_move_number][3]])) goto no_jump;
                break;

            case TYPE_BLACK_JUMP0 :
                j = black_jump_lut[best_move_number][1];
                piece_order[0].m = (SQUARE) j;
                MakeMoveNoKing_BLACK_JUMP0(b,best_move_number);
                if (0 != black_jump_lut[best_move_number][4] && !(KING & b[black_jump_lut[best_move_number][1]])) goto no_jump;
                break;

            case TYPE_BLACK_JUMP2 :
                j = black_jump_lut[best_move_number][3];
                piece_order[0].m = (SQUARE) j;
                MakeMoveNoKing_BLACK_JUMP2(b,best_move_number);
                if (0 != black_jump_lut[best_move_number][4] && !(KING & b[black_jump_lut[best_move_number][3]])) goto no_jump;
                break;

            default:
                AssertSz(0, "end case");
                break;
            }

        /* ----- re-initialize ----- */
        prune_size        =  1; /* ! force it to use only the jump destination as a move */
        moves_made        =  0;
        best_move_type    = -1;
        best_move_number  = -1;
        best_move_quality = -1;

        goto continue_jump;
        }
no_jump:

    switch (best_move_type)
        {
        case TYPE_RED0        :
            MakeMove_RED0(b,best_move_number);
            break;

        case TYPE_RED2        :
            MakeMove_RED2(b,best_move_number);
            break;

        case TYPE_BLACK0      :
            MakeMove_BLACK0(b,best_move_number);
            break;

        case TYPE_BLACK2      :
            MakeMove_BLACK2(b,best_move_number);
            break;

        case TYPE_RED_JUMP0   :
        case TYPE_RED_JUMP2   :
        case TYPE_BLACK_JUMP0 :
        case TYPE_BLACK_JUMP2 :
            pdebug(stddbgmin,"skipping a jump move in IO %s(%d)\n",__FILE__,__LINE__);
            break;

        default:
            pdebug(stddbgmin,"printmove did not make the move on the board %s(%d)\n",__FILE__,__LINE__);
            pdebug(stddbgmin,"this could be a jump continuation %s(%d)\n",__FILE__,__LINE__);
            break;
        }

    // make sure everything is kinged (in case we jumped to this point)
    if (RED & b[1]) b[1] |= KING;
    if (RED & b[2]) b[2] |= KING;
    if (RED & b[3]) b[3] |= KING;
    if (RED & b[4]) b[4] |= KING;
    if (BLACK & b[32]) b[32] |= KING;
    if (BLACK & b[31]) b[31] |= KING;
    if (BLACK & b[30]) b[30] |= KING;
    if (BLACK & b[29]) b[29] |= KING;

    /* ----- return to caller ----- */
    return best_move_quality;
}


QUALITY.CPP

/* --------------------------------------------------------------------------
returns the value of the board from 'computer_colors' perspective
-------------------------------------------------------------------------- */
long QualityOfBoard(BOARD b,int t)
{
    /* ----- constants (to be tweaked) ----- */
    const int wKing            = 32 + 256;
    const int wPiece           = 256;
    const int wBlank           = 1;
    const int wMoveTheory      = 32;
    const int wCenterPos       = 4;
    const int wDoubleCorner    = 2;
    const int wHoldingKingRow  = 2;
    const int wEdges           = -1;

    /* ----- variable factors ----- */
    long q=50000; /* quality */

    #define co_kings  (pieces[computer_color | KING]       )
    #define op_kings  (pieces[next(computer_color) | KING] )
    #define co_pieces (pieces[computer_color]              )
    #define op_pieces (pieces[next(computer_color)]        )
    #define blanks    (pieces[0]                           )

    int pieces[7];
    BOARD pb = b + 32;
    int mtpis = 0; // movetheory pieces in system

    /* ----- number of pieces ----- */
    memset(pieces,0,sizeof(pieces));
    for (;;)
        {
        ++pieces[pb[0]];
        --pb;
        Assert(pb >= b);
        ++pieces[pb[0]];
        --pb;
        Assert(pb >= b);
        ++pieces[pb[0]];
        --pb;
        Assert(pb >= b);
        ++pieces[pb[0]];
        --pb;
        if (pb <= b) break;
        }
    q += (co_kings  * wKing);    /* multiply piece count factor by weights */
    q -= (op_kings  * wKing);
    q += (co_pieces * wPiece);
    q -= (op_pieces * wPiece);
    q += (blanks    * wBlank);

    /* ----- move theory ----- */
    if (co_pieces == op_pieces && 0==co_kings && 0==op_kings)
        {
        int base_of_system=0;
        if (BLACK == next(t)) base_of_system=4;
        for (;base_of_system < 32; base_of_system += 8)
            {
            if (b[1+base_of_system]) mtpis++;
            if (b[2+base_of_system]) mtpis++;
            if (b[3+base_of_system]) mtpis++;
            if (b[4+base_of_system]) mtpis++;
            }
        mtpis &= 1;
        q += wMoveTheory;
        if (next(t) == computer_color)
            {
            if (!mtpis) /* opponent has the move */
                q -= (wMoveTheory * 2);
            }
        else
            {
            if (mtpis) /* opponent has the move */
                q -= (wMoveTheory * 2);
            }
        }

    /* ----- position of pieces ----- */
    static const long weightTable[][3] = {         {0,0,0},
        {0, wDoubleCorner, -wDoubleCorner},        {0,0,0}, {0,0,0}, {0,0,0},
        {0, wDoubleCorner, -wDoubleCorner},        {0,0,0}, {0,0,0}, {0,0,0},
        {0,0,0}, {0,0,0}, {0,0,0},                 {0, wEdges, -wEdges},
        {0, wEdges, -wEdges},                      {0, wCenterPos, -wCenterPos},
        {0, wCenterPos, -wCenterPos},              {0,0,0}, {0,0,0},
        {0, wCenterPos, -wCenterPos},              {0, wCenterPos, -wCenterPos},
        {0, wEdges, -wEdges},                      {0, wEdges, -wEdges},
        {0,0,0}, {0,0,0}, {0,0,0},                 {0,0,0}, {0,0,0}, {0,0,0},
        {0, wDoubleCorner, -wDoubleCorner},        {0,0,0}, {0,0,0}, {0,0,0},
        {0, wDoubleCorner, -wDoubleCorner},        {0,0,0}, {0,0,0}, {0,0,0},
        {0,0,0}, {0,0,0}, {0,0,0}                  };
    long q_posfactor = 0;
    q_posfactor += weightTable[  1][b[  1] & 3];
    q_posfactor += weightTable[  5][b[  5] & 3];
    q_posfactor += weightTable[ 12][b[ 12] & 3];
    q_posfactor += weightTable[ 13][b[ 13] & 3];
    q_posfactor += weightTable[ 14][b[ 14] & 3];
    q_posfactor += weightTable[ 15][b[ 15] & 3];
    q_posfactor += weightTable[ 18][b[ 18] & 3];
    q_posfactor += weightTable[ 19][b[ 19] & 3];
    q_posfactor += weightTable[ 20][b[ 20] & 3];
    q_posfactor += weightTable[ 21][b[ 21] & 3];
    q_posfactor += weightTable[ 28][b[ 28] & 3];
    q_posfactor += weightTable[ 32][b[ 32] & 3];
    if (BLACK == computer_color)
        q_posfactor = -q_posfactor;
    q += q_posfactor;

    /* ----- king row ----- */
    if ((0 == (KING & b[1])) && 0 != b[1])   {if (b[1] & computer_color)  q+=wHoldingKingRow; else q-=wHoldingKingRow;}
    if ((0 == (KING & b[3])) && 0 != b[3])   {if (b[3] & computer_color)  q+=wHoldingKingRow; else q-=wHoldingKingRow;}
    if ((0 == (KING & b[30])) && 0 != b[30]) {if (b[30] & computer_color) q+=wHoldingKingRow; else q-=wHoldingKingRow;}
    if ((0 == (KING & b[32])) && 0 != b[32]) {if (b[32] & computer_color) q+=wHoldingKingRow; else q-=wHoldingKingRow;}

    /* ----- return the results ----- */
    pdebug(stddbg,"QualityOfBoard=%ld %s(%d)\n",q,__FILE__,__LINE__);
    AssertSz(q > 0,"queue too small");
    #ifndef HIGH_PERFORMANCE
    if (0==rConfig.iGameType) return q;
    if (1==rConfig.iGameType) return 100000-q;
    AssertSz(0,"what kind of game are you playing anyway?");
    #endif
    return q;
}


VALID.CPP

/* --------------------------------------------------------------------------
b - board on which to make the move (will be changed on return)
t - RED, BLACK

returns 0 if invalid move
        2 if continuable
else    1
-------------------------------------------------------------------------- */
int MoveValid(BOARD b, int start, int dest, int t)
{
    int j; /* the piece we are jumping over */
    int restore_depth;
    int must_jump=0;

    AssertSz(start > 0 && start <= 32, "ducks");
    AssertSz(dest > 0 && dest <= 32, "geese");
    AssertSz(t==RED || t==BLACK, "The Tetonkaha Wildabeast");
    AssertSz(b[start], "it means buffalo");

    /* destination empty */
    if (0 != b[dest]) return 0;
    pdebug(stddbgmin,"MoveValid destination empty %s(%d)",__FILE__,__LINE__);

    /* if must jump rule is on */
    if (rConfig.iMustJump)
        {
        int besttype, bestnumber;
        long bestquality;
        long c=-1; long p=-1; int fBreak=0;
        int i=0;

        restore_depth = depth_maximum;
        depth_maximum = 1;
        for (i=0;i<SQRS_MAX;i++)
            {
            if (b[i] & t)
                {
                must_jump += IterateJumps(b,i,t,0,
                            &besttype, &bestnumber, &bestquality,
                            c,p,&fBreak);
                }
            AssertSz(fBreak==0,"no way samual jones");
            }
        depth_maximum = restore_depth;
        }

    if (t == RED || (b[start] & KING))
        {
        pdebug(stddbgmin,"MoveValid considering RED moves %s(%d)",__FILE__,__LINE__);
        if (red_lut[start][0] == dest) goto slide;
        if (red_lut[start][2] == dest) goto slide;
        if (red_jump_lut[start][1] == dest && (b[red_jump_lut[start][0]]|KING) == (next(t)|KING)) {j=red_jump_lut[start][0]; goto jump;}
        if (red_jump_lut[start][3] == dest && (b[red_jump_lut[start][2]]|KING) == (next(t)|KING)) {j=red_jump_lut[start][2]; goto jump;}
        }
    if (t == BLACK || (b[start] & KING))
        {
        pdebug(stddbgmin,"MoveValid considering BLACK moves %s(%d)",__FILE__,__LINE__);
        if (black_lut[start][0] == dest) goto slide;
        if (black_lut[start][2] == dest) goto slide;
        if (black_jump_lut[start][1] == dest && (b[black_jump_lut[start][0]]|KING) == (next(t)|KING)) {j=black_jump_lut[start][0]; goto jump;}
        if (black_jump_lut[start][3] == dest && (b[black_jump_lut[start][2]]|KING) == (next(t)|KING)) {j=black_jump_lut[start][2]; goto jump;}
        }

    /* illegal position */
    pdebug(stddbgmin,"MoveValid illegal position %s(%d)",__FILE__,__LINE__);
    return 0;

slide:
    pdebug(stddbgmin,"MoveValid found legal sliding move %s(%d)",__FILE__,__LINE__);
    if (must_jump)
        {
        pdebug(stddbgmin,"slide attempt when jumping move is possible %s(%d)",__FILE__,__LINE__);
        return 0;
        }
    b[dest] = b[start];
    b[start] = 0;

    if (RED & b[1]) b[1] |= KING;
    if (RED & b[2]) b[2] |= KING;
    if (RED & b[3]) b[3] |= KING;
    if (RED & b[4]) b[4] |= KING;
    if (BLACK & b[32]) b[32] |= KING;
    if (BLACK & b[31]) b[31] |= KING;
    if (BLACK & b[30]) b[30] |= KING;
    if (BLACK & b[29]) b[29] |= KING;

    return 1;

jump:
    pdebug(stddbgmin,"MoveValid found legal jump %s(%d)",__FILE__,__LINE__);
    b[dest] = b[start];
    b[start] = 0;
    b[j] = 0;

    if (t == BLACK || (b[dest] & KING))
        {
        if (b[black_jump_lut[dest][1]] == 0 && (b[black_jump_lut[dest][0]]|KING) == (next(t)|KING)) {return 2;}
        if (b[black_jump_lut[dest][3]] == 0 && (b[black_jump_lut[dest][2]]|KING) == (next(t)|KING)) {return 2;}
        }

    if (t == RED || (b[dest] & KING))
        {
        if (b[red_jump_lut[dest][1]] == 0 && (b[red_jump_lut[dest][0]]|KING) == (next(t)|KING)) {return 2;}
        if (b[red_jump_lut[dest][3]] == 0 && (b[red_jump_lut[dest][2]]|KING) == (next(t)|KING)) {return 2;}
        }

    pdebug(stddbgmin,"MoveValid found jump is NOT continuable %s(%d)",__FILE__,__LINE__);
    return 1;
}

/* --------------------------------------------------------------------------
GameOver returns

GAME_{WON}|{PLAYABLE}|{DRAWN}
--------------------------------------------------------------------------- */
int GameOver(BOARD b, int t)
{
    int restore_depth;
    int besttype, bestnumber;
    long bestquality;
    long c=-1; long p=-1; int fBreak=0;
    int i=0;                             // Piece I'm on.
    int move_is_possible=0;

    restore_depth = depth_maximum;
    depth_maximum = 1;
    for (i=0;i<SQRS_MAX;i++)
        {
        if (b[i] & t)
            {
            move_is_possible += IterateJumps(b,i,t,0,
                        &besttype, &bestnumber, &bestquality,
                        c,p,&fBreak);
            move_is_possible += IterateMoves(b,i,t,0,
                        &besttype, &bestnumber, &bestquality,
                        c,p,&fBreak);
            if (move_is_possible)
                break;
            }
            AssertSz(fBreak==0,"no way sammy jones");
        }
    depth_maximum = restore_depth;

    /* ----- make sure this isn't a drawn game ----- */
    if (move_is_possible)
    {
        int i=rConfig.iMaxMoves;
        int t=0;
        SQUARE b[SQRS_MAX];

        if (CMoves.GetFirstBoard(b,&t))
            while (CMoves.GetNextBoard(b,&t))
                i--;
        AssertSz(i > -5, "vania lorelle");
        if (i<0)
            return GAME_DRAWN;
    }

    if (move_is_possible)
        return(GAME_PLAYABLE);
    else
        return(GAME_WON);

}


README.TXT

MAPI Choose Folder Sample

Copyright 1986 - 1998 Microsoft Corporation. All Rights Reserved.

This samples illustrates implementing a MAPI Container hierarchy viewer.
It uses standard WIN32 Tree View Control to display folder hierarchy of
a message store and to allow a user to choose a particular folder.
Chsfld32.dll exports a single entry point  HrPickFolder. Please see
chsfld.h located in common directory for the exact description of
arguments and return values.

The use of HrPickFolder is straightforward: just pass in all the required
parameters and, assuming that everything goes OK, get back pointers to
IMAPIFolder interface of the folder chosen by the user and a pointer to
IMsgStore interface of the message store that contains the folder.
There are to optional parameters. If you use them (meaning they are not
zeroes), the function will save the expand/collapse state of the dialog
at the moment the user pressed "OK". If you pass this parameter back next
time you call HrPickFolder, the dialog will restore the saved state. See
route.cli and simple.frm samples for the example on how to use the function.

If you want to use the function as is, in a separate DLL, all you need to
do is to link with chsfld32.dll either statically or dynamically and call
the function. To reuse the source code, you will have to modify it a bit.
You have to make sure that the HINSTANCE parameter that you pass to
CChsFldDlg constructor in function HrPickFolder (file chsfld.cpp) is the
handle of the executable module that contains the resources used by the
class (the dialog and the icons).


CHSFLD.CPP

////////////////////////////////////////////////////////////////////////////
//
//      CHSFLD.CPP
//
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
///////////////////////////////////////////////////////////////////////

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapidbg.h>
#include "chsfld.h"
#include "lasterr.h"
#include "tvdlg.h"
#include "resource.h"
#include "tvstack.h"

#define cImageHeight    16
#define cImageWidth     16
#define cImages         4

//globals
LPSTR g_szAllStores = "All Message Stores";
LPSTR g_szModuleName = "Choose Folder Dialog";

//functions used only in this file
BOOL CALLBACK
ChsFldDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

HRESULT HrGetNewName(HINSTANCE hInst, HWND hwParent, LPSTR * pszNewName);

BOOL CALLBACK
NewNameDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//
//  HrPickFolder
//
//
STDAPI
HrPickFolder(HINSTANCE hInst, HWND hWnd, LPMAPISESSION pses, LPMAPIFOLDER * ppfld,
                LPMDB *ppmdb, ULONG * pcb, LPBYTE * ppb)
{
    HRESULT hr;

    //Assert(hInst);

    if((hWnd && !IsWindow(hWnd)) || (!pses) || (!ppfld) || (!ppmdb))
    {
        DebugTraceResult(HrPickFolder, E_INVALIDARG);
        return E_INVALIDARG;
    }

    if(pcb && IsBadWritePtr(pcb, sizeof(ULONG)))
    {
        DebugTraceArg(HrPickFolder, "pcb not writable");
        return E_INVALIDARG;
    }

    if(pcb && (*pcb & 0x3))
    {
        DebugTraceArg(HrPickFolder, "pcb not multiple of 4");
        return E_INVALIDARG;
    }

    if(ppb && IsBadWritePtr(ppb, sizeof(LPBYTE)))
    {
        DebugTraceArg(HrPickFolder, "ppb not writable");
        return E_INVALIDARG;
    }

    if(ppb && pcb && IsBadWritePtr(*ppb, *pcb))
    {
        DebugTraceArg(HrPickFolder, "*pcb or *ppb");
        return E_INVALIDARG;
    }

    //////////////////////////////////////////////////////////////////////
    // if you incorporate this code into you app, remove this and pass in
    // the right hInst
    // Start remove
    hInst = GetModuleHandle("chsfld32.dll");
    if(!hInst)
    {
        DebugTrace("GetModuleHandel failed\n");
        DebugTraceResult(HrPickFolder, E_FAIL);
        return E_FAIL;
    }
    // End remove
    ///////////////////////////////////////////////////////////////////////

    //ULONG cb = 0;
    //LPBYTE pb = NULL;

    CChsFldDlg PickDlg(pses, hInst, pcb, ppb);

    InitCommonControls();

    hr = PickDlg.HrPick(MAKEINTRESOURCE(IDD_CFDIALOG), hWnd,
                        (DLGPROC)ChsFldDlgProc, ppfld, ppmdb);

/*  if(SUCCEEDED(hr))
    {
        (*ppfld)->Release();
        (*ppmdb)->Release();
    }

    CChsFldDlg PickDlg1(pses, hInst, pcb, ppb);

    hr = PickDlg1.HrPick(MAKEINTRESOURCE(IDD_CFDIALOG), hWnd,
                        (DLGPROC)ChsFldDlgProc, ppfld, ppmdb);*/

//  if(!hr)
    //  MAPIFreeBuffer(pb);

    DebugTraceResult(HrPickFolder, hr);
    return hr;
}

//
//  CChsFldDlg::CChsFldDlg
//
inline
CChsFldDlg::CChsFldDlg(LPMAPISESSION pses, HINSTANCE hInst, ULONG * pcb,
                        LPBYTE * ppb)   : m_lsterr(g_szModuleName)
{
    Assert(pses);
    Assert(hInst);

    m_pses = pses;
    pses->AddRef();

    m_hr = hrSuccess;
    m_pfld = NULL;
    m_pmdb = NULL;
    m_hiRoot = NULL;
    m_hInst = hInst;
    m_hIml = NULL;
    m_hDlg = NULL;
    m_hwTreeCtl = NULL;
    m_pcbState = pcb;
    m_ppbState = ppb;
}

//
//  CChsFldDlg::~CChsFldDlg
//
CChsFldDlg::~CChsFldDlg()
{
    UlRelease(m_pses);
    UlRelease(m_pfld);
    UlRelease(m_pmdb);

    if(m_hIml)
        ImageList_Destroy(m_hIml);
}

//
//  CChsFldDlg::SetFolder
//
//  Store the folder chosen by the user
//
inline void CChsFldDlg::SetFolder(LPMAPIFOLDER pfld, LPMDB pmdb)
{
    UlRelease(m_pfld);

    m_pfld = pfld;

    if(pfld)
        pfld->AddRef();

    UlRelease(m_pmdb);

    m_pmdb = pmdb;
    if(pmdb)
        pmdb->AddRef();
}

//
//  CChsFldDlg::HrPick
//
// The outmost method.
//
HRESULT CChsFldDlg::HrPick(LPCTSTR lpTemplateName, HWND hWnd,
                DLGPROC pfnDlgProc, LPMAPIFOLDER * ppfld, LPMDB *ppmdb)
{
    if(-1 == DialogBoxParam(m_hInst, lpTemplateName, hWnd, pfnDlgProc, (LPARAM) this))
    {
        DebugTraceSc(CChsDldDlg::HrPick, MAPI_E_NOT_ENOUGH_MEMORY);
        return MAPI_E_NOT_ENOUGH_MEMORY;
    }

    //m_hr is set inside the dialog
    if(HR_SUCCEEDED(m_hr))
    {
        Assert(m_pfld);
        m_pfld->AddRef();
        *ppfld = m_pfld;

        Assert(m_pmdb);
        m_pmdb->AddRef();
        *ppmdb = m_pmdb;
    }

    return m_hr;
}

//
//  CChsFldDlg::HrInitTree
//
// Called from WM_INITDIALOG. Opens all message stores in the profile and
// puts the IPM subtrees in the tree control
//
HRESULT CChsFldDlg::HrInitTree(HWND hDlg, HWND hwTreeCtl)
{
    HRESULT     hr;
    LPSPropValue pval = NULL;
    LPTVNODE    pNode = NULL;
    HTREEITEM   hiRoot = NULL;
    HICON       hIcon = NULL;

    Assert(hDlg);
    Assert(hwTreeCtl);

    m_hwTreeCtl = hwTreeCtl;
    m_hDlg      = hDlg;

    //
    // Set up the image list
    //
    m_hIml = ImageList_Create(cImageWidth, cImageHeight, ILC_MASK,
                            cImages, 0);
    if(!m_hIml)
    {
        hr = MAPI_E_NOT_ENOUGH_MEMORY;
        goto err;
    }

    hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_ALLSTORES));
    m_iIconAllStores = ImageList_AddIcon(m_hIml, hIcon);

    hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_ROOTFLD));
    m_iIconRootFld = ImageList_AddIcon(m_hIml, hIcon);

    hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_OPENFLD));
    m_iIconOpenFld = ImageList_AddIcon(m_hIml, hIcon);

    hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_CLSDFLD));
    m_iIconClsdFld = ImageList_AddIcon(m_hIml, hIcon);

    if(ImageList_GetImageCount(m_hIml) < cImages)
    {
        hr = MAPI_E_NOT_ENOUGH_MEMORY;
        goto err;
    }

    TreeView_SetImageList(hwTreeCtl, m_hIml, TVSIL_NORMAL);

    //
    // create the root tree node
    // (fake a GetProps)
    //
    hr = MAPIAllocateBuffer(nhtProps * sizeof(SPropValue),
                        (LPVOID *)&pval);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr);
        m_lsterr.ShowError(hDlg);

        goto err;
    }

    ZeroMemory(pval, nhtProps * sizeof(SPropValue));

    //Set  proptags to make CNode constructor happy
    pval[iEID].ulPropTag = PR_ENTRYID;
    pval[iDispName].ulPropTag = PR_DISPLAY_NAME;
    pval[iDispName].Value.lpszA = g_szAllStores;
    pval[iSubfldrs].ulPropTag = PR_SUBFOLDERS;

    hr = HrCreateNode(pval, nhtProps, NULL, &pNode);
    if(hr)
        goto err;

    Assert(pNode);

    pval = NULL; //will be freed in ~CTVNode

    hiRoot = AddOneItem(NULL, TVI_ROOT, m_iIconAllStores, m_iIconAllStores,
                            hwTreeCtl, pNode, 1);
    if(!hiRoot)
    {
        hr = MAPI_E_NOT_ENOUGH_MEMORY;
        goto err;
    }

    pNode->SetKidsLoaded(TRUE);

    m_hiRoot = hiRoot;

    //
    //  Put the IPM subtrees of all the message stores in
    //
    hr = HrLoadRoots();
    if(HR_FAILED(hr))
        goto err;

    (void)HrRestoreTreeState();

err:
    MAPIFreeBuffer(pval);

    DebugTraceResult(CChsFldDlg::HrInitTree, hr);
    return hr;
}

//
//  CChsFldDlg::HrLoadRoots
//
HRESULT CChsFldDlg::HrLoadRoots(void)
{
    HRESULT hr;
    LPMAPITABLE ptblMStrs = NULL;
    UINT ind;
    LPSRowSet pRows = NULL;
    static SSortOrderSet sosName;

    sosName.cSorts = 1;
    sosName.cCategories = 0;
    sosName.cExpanded = 0;
    sosName.aSort[0].ulPropTag = PR_DISPLAY_NAME;
    sosName.aSort[0].ulOrder = TABLE_SORT_ASCEND;

    //Get Message Store Table
    hr = m_pses->GetMsgStoresTable(0, &ptblMStrs);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, m_pses);
        m_lsterr.ShowError(m_hDlg);

        goto err;
    }

    //For each msg store insert a node corresponding to PR_IPM_SUBTREE

    hr = HrQueryAllRows(ptblMStrs, (LPSPropTagArray) &spthtProps, NULL,
                        &sosName, 0, &pRows);

    if(HR_FAILED(hr))
        goto err;

    if(0 ==  pRows->cRows)  //$ No stores
    {
        MessageBox(m_hDlg,
                    "No message stores in the profile",
                    g_szModuleName,
                    MB_OK);
        hr = E_FAIL;
    }

    for(ind = 0; ind < pRows->cRows; ++ind)
    {
        LPSPropValue pval = pRows->aRow[ind].lpProps;
        Assert(pRows->aRow[ind].cValues == nhtProps);
        Assert(pval[iEID].ulPropTag == PR_ENTRYID);

        pval[iSubfldrs].ulPropTag = PR_SUBFOLDERS;
        pval[iSubfldrs].Value.b = TRUE;

        //pval is consumed by this function
        hr = HrInsertRoot(pval);
        pRows->aRow[ind].cValues = 0;
        pRows->aRow[ind].lpProps = NULL;
        if(FAILED(hr))
            goto err;

    }

err:
    FreeProws(pRows);
    UlRelease(ptblMStrs);

    DebugTraceResult(CChsFldDlg::HrLoadRoots, hr);
    return hr;
}

//
//  CChsFldDlg::HrInsertRoot
//
// Put the IPM subtree of the msg store in the tree control
//  pval is consumed
//
HRESULT CChsFldDlg::HrInsertRoot(LPSPropValue pval)
{
    HRESULT hr;
    HTREEITEM hItem;

    Assert(m_hiRoot);

    LPTVNODE pNode = NULL;
    hr = HrCreateNode(pval, nhtProps, NULL, &pNode);
    if(hr)
    {
        MAPIFreeBuffer(pval);
        goto err;
    }

    Assert(pNode);
    pval = NULL;

    hItem = AddOneItem(m_hiRoot, TVI_LAST, m_iIconRootFld, m_iIconRootFld,
                            m_hwTreeCtl, pNode, 1);
    if(!hItem)
    {
        hr = MAPI_E_NOT_ENOUGH_MEMORY;
        goto err;
    }

err:

    DebugTraceResult(CChsFldDlg::HrInsertRoots, hr);
    return hr;
}

//
//  CChsFldDlg::HrSaveTreeState
//
// Save expand - collapse state of the tree control
//
HRESULT CChsFldDlg::HrSaveTreeState(void)
{
    HRESULT hr;

    if(!m_pcbState || !m_ppbState)
        return hrSuccess;

    MAPIFreeBuffer(*m_ppbState);
    *m_ppbState = NULL;
    *m_pcbState = 0;

    hr = HrSaveTreeStateEx(FALSE, m_pcbState, NULL);
    if(hr)
        goto err;

    DebugTrace("ChsFld: size of state data: %ld\n", *m_pcbState);

    hr = HrSaveTreeStateEx(TRUE, m_pcbState, m_ppbState);

err:
    DebugTraceResult(CChsFldDlg::HrSaveTreeState, hr);
    return hr;
}

//
//  CChsFldDlg::HrSaveTreeStateEx
//
// Save expand - collapse state of the tree control
//
HRESULT CChsFldDlg::HrSaveTreeStateEx(BOOL fWrite, ULONG * pcb, LPBYTE * ppb)
{
    HRESULT hr = hrSuccess;
    CTIStack tiStack;
    HTREEITEM hti;
    LPBYTE pb = NULL;
    LPBYTE pBuffer = NULL;
    LONG iLevel;

    if(fWrite)
    {
        if(*pcb == 0)
        {
            *ppb = NULL;
            return hrSuccess;
        }
        else
        {
            hr = MAPIAllocateBuffer(*pcb, (LPVOID *) &pBuffer);
            if (hr)
            {
                *pcb = 0;
                return hr;
            }
            pb = pBuffer;
        }
    }

    hti = TreeView_GetRoot(m_hwTreeCtl);
    iLevel = 0;
    tiStack.Push(NULL);

    while(hti)
    {
        Assert(iLevel >= 0);

        while(hti)
        {
            TV_ITEM tvi;

            tvi.hItem = hti;
            tvi.mask = TVIF_STATE | TVIF_PARAM;
            tvi.lParam = 0;
            tvi.state = 0;
            tvi.stateMask = TVIS_EXPANDED;

            if(!TreeView_GetItem(m_hwTreeCtl, &tvi))
            {
                hr = E_FAIL;
                goto err;
            }

            if(tvi.state & TVIS_EXPANDED)
            {
                HTREEITEM htiChild = TreeView_GetChild(m_hwTreeCtl, hti);

                if(htiChild)
                {
                    LPTVNODE pNode = (LPTVNODE) tvi.lParam;
                    Assert(pNode);

                    pNode->Write(fWrite, iLevel, &pb);

                    HTREEITEM htiNextSibl = TreeView_GetNextSibling(m_hwTreeCtl, hti);

                    tiStack.Push(htiNextSibl);

                    hti = htiChild;
                    ++iLevel;

                    continue;
                }

            }

            hti = TreeView_GetNextSibling(m_hwTreeCtl, hti);
        }

        do
        {
            hti = tiStack.Pop();
            --iLevel;

        }while(!tiStack.IsEmpty() && hti == NULL);
    }

    Assert(iLevel == -1);

    *pcb = pb - pBuffer;
    if(pBuffer)
        *ppb = pBuffer;

err:
    DebugTraceResult(CChsFldDlg::HrSaveTreeStateEx, hr);
    return hr;
}

inline LONG GetLevel(LPBYTE * ppb)
{
    LONG level = *((LONG *) *ppb);

    *ppb += sizeof(LONG);

    return level;
}

inline ULONG GetCb(LPBYTE * ppb)
{
    ULONG cb = *((ULONG *) *ppb);

    *ppb += sizeof(ULONG);

    return cb;
}

HTREEITEM HtiFindChild(HWND hwTreeCtl, HTREEITEM hti, ULONG cb,
                    LPENTRYID pbEID, CChsFldDlg *pCFDlg, LPTVNODE *ppNode)
{
    HRESULT hr;
    HTREEITEM htiChild;

    htiChild = TreeView_GetChild(hwTreeCtl, hti);

    while(htiChild)
    {
        TV_ITEM tvi;

        tvi.hItem = htiChild;
        tvi.mask = TVIF_PARAM;
        tvi.lParam = 0;

        if(!TreeView_GetItem(hwTreeCtl, &tvi))
            return NULL;

        LPTVNODE pNode = (LPTVNODE) tvi.lParam;
        Assert(pNode);

        ULONG ulMatch = 0;
        hr = pCFDlg->Session()->CompareEntryIDs(cb, pbEID,
                            pNode->m_pval[iEID].Value.bin.cb,
                            (LPENTRYID)pNode->m_pval[iEID].Value.bin.lpb,
                            0, &ulMatch);
        if(SUCCEEDED(hr))
        {
            if(ulMatch)
            {
                *ppNode = pNode;
                return htiChild;
            }
        }

        htiChild = TreeView_GetNextSibling(hwTreeCtl, htiChild);
    }

    return htiChild;
}

//
//  CChsFldDlg::HrRestoreTreeState
//
HRESULT CChsFldDlg::HrRestoreTreeState(void)
{
    HRESULT hr = hrSuccess;
    LPBYTE pb;
    LPBYTE pbMax;
    CTIStack tiStack;
    HTREEITEM hti;
    LONG iLevel = 0;
    BOOL fNodeMissing = FALSE;

    if(!m_pcbState  || *m_pcbState == 0)
        return hrSuccess;

    try //protect ourself from callers who mess with the state data
    {
    Assert(m_hwTreeCtl);

    Assert(m_ppbState);
    pb = *m_ppbState;

    pbMax = pb + *m_pcbState;

    hti = TreeView_GetRoot(m_hwTreeCtl);

    iLevel = GetLevel(&pb);
    Assert(iLevel == 0);

    TreeView_Expand(m_hwTreeCtl, hti, TVE_EXPAND);

    while(hti)
    {
        if(pb >= pbMax)
            break; //done

        LONG iNewLevel = GetLevel(&pb);

        if(iNewLevel <= iLevel)
        {
            do
            {
                hti = tiStack.Pop();
                --iLevel;
            }while(iLevel >= iNewLevel);

            Assert(hti);
        }

        if(iNewLevel > iLevel)
        {
            if(!fNodeMissing)
                Assert(iNewLevel == iLevel + 1);

            ULONG cbEID = GetCb(&pb);
            LPENTRYID pbEID = (LPENTRYID)pb;
            pb += Align4(cbEID);

            if(iNewLevel != iLevel +1)
                continue;

            LPTVNODE pNodeChild = NULL;
            HTREEITEM htiChild = HtiFindChild(m_hwTreeCtl, hti, cbEID, pbEID,
                                            this, &pNodeChild);
            if(htiChild)
            {
                fNodeMissing = FALSE;

                hr = pNodeChild->HrExpand(this);
                if(FAILED(hr))
                    goto err;

                TreeView_Expand(m_hwTreeCtl, htiChild, TVE_EXPAND);

                tiStack.Push(hti);

                hti = htiChild;
                ++iLevel;

                continue;
            }
            else
            {
                //Assert(FALSE); //$ handle
                fNodeMissing = TRUE;
            }

        }
        /*else
        {
            do
            {
                hti = tiStack.Pop();
                --iLevel;
            }while(iLevel >= iNewLevel);
        }*/
    }
    }

    catch(...)
    {
        DebugTrace("chsfld: Exception caught in HrRestoreTreeState\n");
        hr = E_FAIL;
    }

err:

    /*MAPIFreeBuffer(*m_ppbState);
    *m_ppbState = NULL;
    *m_pcbState = 0;*/

    DebugTraceResult(CChsFldDlg::HrRestoreTreeState, hr);
    return hr;
}

//////////////////////////////////////////////////////////////////////////
// CTVNodeFactory

//
//  CTVNodeFactory::CTVNodeFactory
//
inline CTVNodeFactory::CTVNodeFactory()
{
    m_pHead = NULL;
}

//
//  CTVNodeFactory::~CTVNodeFactory
//
//  Destroy all created CTVNode s
CTVNodeFactory::~CTVNodeFactory()
{
    while(m_pHead)
    {
        LPTVNODE ptemp = m_pHead;

        m_pHead = m_pHead->m_pNext;

        delete ptemp;
    }
}

//
//  CTVNodeFactory::HrCreateNode
//
// All instances of CTVNode are created through this method
//
HRESULT CTVNodeFactory::HrCreateNode(LPSPropValue pval, ULONG cVals, LPMDB pmdb,
                                        LPTVNODE * pptvNode)
{
    HRESULT hr = hrSuccess;

    LPTVNODE pNode = new CTVNode(pval, cVals, pmdb);

    if(!pNode)
    {
        hr = MAPI_E_NOT_ENOUGH_MEMORY;
        goto err;
    }

    Insert(pNode);

    *pptvNode = pNode;

err:

    DebugTraceResult(CTVNodeFactory::HrCreateNode, hr);
    return hr;
}

//
//  CTVNodeFactory::Insert
//
// Store all created CTVNode s so that we can destroy them when we are done
//
void CTVNodeFactory::Insert(LPTVNODE pNode)
{
    pNode->m_pNext = m_pHead;
    m_pHead = pNode;
}

//
//  ChsFldDlgProc
//
// Dialog proc for the choose folder dialog
//
//  Controls:
//          IDOK        "OK"
//          IDCANCEL    "Cancel"
//          IDC_NEWFLD  "New Folder"
//
BOOL CALLBACK
ChsFldDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;
    CChsFldDlg * pCDlg = NULL;
    HWND hwTreeCtl = NULL;
    int wmId;
    int wmEvent;
    HTREEITEM hti = NULL;
    TV_ITEM tvi;

    switch(msg)
    {
    case WM_INITDIALOG:
        Assert(lParam);
        pCDlg = (CChsFldDlg *)lParam;

        hwTreeCtl = GetDlgItem(hDlg, IDC_TREEVIEW);
        Assert(hwTreeCtl);

        hr = pCDlg->HrInitTree(hDlg, hwTreeCtl);
        if(HR_FAILED(hr))
        {
            pCDlg->SetError(hr);
            EndDialog(hDlg, 1);
            break;
        }

        SetWindowLong(hDlg, DWL_USER, (LONG)pCDlg);

        break;

    case WM_COMMAND:
        wmId = GET_WM_COMMAND_ID(wParam, lParam);
        wmEvent = GET_WM_COMMAND_CMD(wParam, lParam);

        hwTreeCtl = GetDlgItem(hDlg, IDC_TREEVIEW);
        Assert(hwTreeCtl);

        pCDlg = (CChsFldDlg *)GetWindowLong(hDlg, DWL_USER);
        Assert(pCDlg);

        switch(wmId)
        {
        case IDOK:
            switch (wmEvent)
            {
            case BN_CLICKED:

                hti = TreeView_GetSelection(hwTreeCtl);
                AssertSz(hti, "No Selection?");

                tvi.hItem = hti;
                tvi.mask = TVIF_PARAM;

                if(TreeView_GetItem(hwTreeCtl, &tvi))
                {
                    LPTVNODE pNode = (LPTVNODE)tvi.lParam;
                    Assert(pNode);

                    LPMAPIFOLDER pfld = NULL;
                    LPMDB pmdb = NULL;

                    hr = pNode->HrGetFolder(pCDlg, &pfld, &pmdb);
                    if(HR_SUCCEEDED(hr))
                    {
                        pCDlg->SetFolder(pfld, pmdb);
                        pfld->Release();
                        pmdb->Release();

                        hr = pCDlg->HrSaveTreeState();
                    }
                    else
                    {
                        pCDlg->SetError(hr);
                    }
                }
                else
                {
                    pCDlg->SetError(E_FAIL);
                }

                EndDialog(hDlg, TRUE);

                break;

            default:
                return FALSE;
            }
            break;

        case IDC_NEWFLD:
            switch(wmEvent)
            {
            case BN_CLICKED:

                hti = TreeView_GetSelection(hwTreeCtl);
                AssertSz(hti, "No Selection?");

                tvi.hItem = hti;
                tvi.mask = TVIF_PARAM;

                if(TreeView_GetItem(hwTreeCtl, &tvi))
                {
                    LPTVNODE pNode = (LPTVNODE)tvi.lParam;
                    Assert(pNode);
                    LPSTR szName = NULL;

                    do
                    {
                        hr = HrGetNewName(pCDlg->hInst(), hDlg, &szName);
                        if(HR_SUCCEEDED(hr))
                        {
                            hr = pNode->HrNewFolder(pCDlg, szName);
                        }
                    }while(hr == MAPI_E_COLLISION);

                    MAPIFreeBuffer(szName);
                    szName = NULL;

                }

                SetFocus(hwTreeCtl);

                break;

            default:
                return FALSE;
            }
            break;

        case IDCANCEL:
            switch(wmEvent)
            {
            case BN_CLICKED:

                pCDlg->SetError(MAPI_E_USER_CANCEL);

                EndDialog(hDlg, TRUE);
                break;

            default:
                return FALSE;
            }
            break;
        }
        break;

    case WM_NOTIFY:
        switch( ((LPNMHDR)lParam)->code)
        {
        case TVN_ITEMEXPANDING:
            {
            Assert(((LPNMHDR)lParam)->idFrom == IDC_TREEVIEW);

            NM_TREEVIEW * ptntv = (NM_TREEVIEW *)lParam;

            if(ptntv->action != TVE_EXPAND)
                return FALSE;

            //
            // If the kids of this node are not loaded, load'em
            LPTVNODE pNode = (LPTVNODE)ptntv->itemNew.lParam;
            Assert(pNode);

            hwTreeCtl = ((LPNMHDR)lParam)->hwndFrom;

            pCDlg = (CChsFldDlg *)GetWindowLong(hDlg, DWL_USER);
            //Assert(pCDlg);

            hr = pNode->HrExpand(pCDlg);
            if(HR_FAILED(hr))
            {
                return TRUE;
            }
                return FALSE;
            }
            break;
        case TVN_GETDISPINFO:
            {
            Assert(((LPNMHDR)lParam)->idFrom == IDC_TREEVIEW);

            //
            // we don't give folder names to the tree control (to save space)
            // when it wants to display an item, it asks us for the name
            //
            TV_DISPINFO * pdi = (TV_DISPINFO *)lParam;

            if(pdi->item.mask & TVIF_TEXT)
            {
                pdi->item.pszText = ((LPTVNODE)pdi->item.lParam)->GetName();
                return TRUE;
            }
            else
            {
                return FALSE;
            }
            }

            break;

        case TVN_SELCHANGED:
            //
            //Enable "OK" and "New Folder" buttons only if it is not the
            //root node
            //
            {Assert(((LPNMHDR)lParam)->idFrom == IDC_TREEVIEW);

            NM_TREEVIEW *ptntv = (NM_TREEVIEW *)lParam;

            pCDlg = (CChsFldDlg *)GetWindowLong(hDlg, DWL_USER);
            Assert(pCDlg);

            EnableWindow(GetDlgItem(hDlg, IDOK),
                        !pCDlg->IsTreeRoot(ptntv->itemNew.hItem));
            EnableWindow(GetDlgItem(hDlg, IDC_NEWFLD),
                        !pCDlg->IsTreeRoot(ptntv->itemNew.hItem));
            break;
            }

            break;
        }

        break;

    default:
        return FALSE;
    }
    return TRUE;
}

//
//  AddOneItem
//
// Add a node to the tree control
//
HTREEITEM AddOneItem( HTREEITEM hParent, HTREEITEM hInsAfter,
    int iImage, int iImageSel, HWND hwndTree, LPTVNODE pNode, int cKids)
{
    HTREEITEM hItem;
    TV_INSERTSTRUCT tvIns;

    tvIns.item.mask             = TVIF_CHILDREN | TVIF_PARAM |TVIF_TEXT |
                                    TVIF_IMAGE | TVIF_SELECTEDIMAGE;
    tvIns.item.pszText          = LPSTR_TEXTCALLBACK;
    tvIns.item.cchTextMax       = 0;
    tvIns.item.lParam           = (LPARAM)pNode;
    tvIns.item.cChildren        = cKids;
    tvIns.item.iImage           = iImage;
    tvIns.item.iSelectedImage   = iImageSel;

    tvIns.hInsertAfter = hInsAfter;
    tvIns.hParent = hParent;

    // Insert the item into the tree.
    hItem = TreeView_InsertItem(hwndTree, &tvIns);

    pNode->SetHandle(hItem);

    return (hItem);
}

//
//  HrGetNewName
//
//  Display dialog asking the user for a new folder name
//
//  If *pszNewName is not NULL, it has to be a string allocated with
//  MAPIAllocateBuffer. It will be displayed in the dialog.
//  The returned string has to be freed with MAPIFreeBuffer.
//
HRESULT HrGetNewName(HINSTANCE hInst, HWND hwParent, LPSTR * pszNewName)
{
    Assert(pszNewName);

    int nRes = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_NEWNAME), hwParent,
                        NewNameDlgProc, (LPARAM)pszNewName);
    if(nRes == 1)
    {
        return hrSuccess;
    }
    else
    {
        DebugTraceSc(HrGetNewName, E_FAIL);
        return E_FAIL;
    }
}

//
// NewNameDlgProc
//
// Dlg proc for the "New Name" dialog;
// If user chooses OK, return 1 from EndDialog.
//
BOOL CALLBACK
NewNameDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    int wmId;
    int wmEvent;

    switch(msg)
    {
    case WM_INITDIALOG:
        {
        Assert(lParam);

        LPSTR * pszName = (LPSTR *)lParam;

        if(*pszName)
        {
            SetWindowText(GetDlgItem(hDlg, IDC_NAME), *pszName);
            MAPIFreeBuffer(*pszName);
            *pszName = NULL;

        }

        SetWindowLong(hDlg, DWL_USER, (LONG)lParam);
        SetFocus(GetDlgItem(hDlg, IDC_NAME));
        return FALSE;
        }
        break;

    case WM_COMMAND:
        wmId = GET_WM_COMMAND_ID(wParam, lParam);
        wmEvent = GET_WM_COMMAND_CMD(wParam, lParam);

        switch(wmId)
        {
        case IDOK:
            switch (wmEvent)
            {
            case BN_CLICKED:
                {
                HWND hwName = GetDlgItem(hDlg, IDC_NAME);

                int cb = Edit_GetTextLength(hwName);
                Assert(cb); //OK is disabled when edit control is empty

                LPSTR szName = NULL;
                if(!MAPIAllocateBuffer(cb + 1, (LPVOID *)&szName))
                {
                    GetWindowText(hwName, szName, cb+1);

                    LPSTR * pszName = (LPSTR *)GetWindowLong(hDlg, DWL_USER);

                    *pszName = szName;

                    EndDialog(hDlg, 1);
                }
                else
                {
                    EndDialog(hDlg, FALSE);
                    break;
                }
                }
                break;

            default:

return FALSE;
            }

            break;

        case IDCANCEL:
            switch (wmEvent)
            {
            case BN_CLICKED:
                EndDialog(hDlg, FALSE);
                break;

            default:
                return FALSE;
            }

            break;

        case IDC_NAME:
            switch(wmEvent)
            {
            case EN_CHANGE:
                Assert((HWND)lParam == GetDlgItem(hDlg, IDC_NAME));

                EnableWindow(GetDlgItem(hDlg, IDOK),
                            Edit_GetTextLength((HWND)lParam));

                break;

            default:
                return FALSE;
            }

            break;
        }
    default:
        return FALSE;
    }

    return TRUE;
}


RESOURCE.H

////////////////////////////////////////////////////////////////////////////
//
//      RESOURCE.H
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////

//tree view dialog
#define IDD_CFDIALOG            3101
#define IDC_TREEVIEW            3102
#define IDC_NEWFLD              3103

//icons
#define IDI_ALLSTORES           31001
#define IDI_ROOTFLD             31002
#define IDI_OPENFLD             31003
#define IDI_CLSDFLD             31004

//New name dialog
#define IDD_NEWNAME             3201
#define IDC_NAME                3202
#define IDC_NAMELABEL           3203


TVDLG.H

////////////////////////////////////////////////////////////////////////////
//
//      TVDLG.H
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////

#ifndef __TVDLG__H__
#define __TVDLG__H__

enum { iEID, iDispName, iSubfldrs, nhtProps};
static SizedSPropTagArray(nhtProps, spthtProps) =
{ nhtProps,
    {   PR_ENTRYID,
        PR_DISPLAY_NAME,
        PR_SUBFOLDERS
    }
};

#define Align4(_cb) (((_cb) + 3) & ~3)

extern LPSTR g_szNoFolderName;
extern LPSTR g_szAllStores;

class CTVNodeFactory;
class CChsFldDlg;

/////////////////////////////////////////////////////////////////////////
// CTVNode

class CTVNode;
typedef CTVNode * LPTVNODE;

class CTVNode
{

friend CTVNodeFactory;
friend HTREEITEM HtiFindChild(HWND hwTreeCtl, HTREEITEM hti, ULONG cb,
                LPENTRYID pbEID, CChsFldDlg *pCFDlg, LPTVNODE *ppNode);

public:
    LPSTR GetName(void);
    HRESULT HrExpand(CChsFldDlg * pCFDlg);
    HRESULT HrGetFolder(CChsFldDlg * pCFDlg, LPMAPIFOLDER * ppfld,
                            LPMDB *ppmdb);

    void SetHandle(HTREEITEM hItem) { m_htiMe = hItem;}
    void SetKidsLoaded(BOOL fLoaded) { m_fKidsLoaded = fLoaded;}

    HRESULT HrNewFolder(CChsFldDlg * pCFDlg, LPSTR szFldName);

    static LPVOID operator new( size_t cb );
    static VOID   operator delete( LPVOID pv );

    void Write(BOOL fWrite, LONG iLevel, LPBYTE * ppb);

    ~CTVNode();

private:

    //can only be created in CTVNodeFactory::HrCreateNode
    CTVNode(LPSPropValue pvals, ULONG cprops, LPMDB pmdb);

    HRESULT HrOpenMDB(CChsFldDlg * pCFDlg);
    HRESULT HrOpenFolder(CChsFldDlg * pCFDlg);

    HTREEITEM       m_htiMe;
    LPSPropValue    m_pval;
    BOOL            m_fKidsLoaded;
    LPMAPIFOLDER    m_pfld;
    CTVNode         *m_pNext;
    LPMDB           m_pmdb;
};

//
//  CTVNode::GetName
//
inline LPSTR CTVNode::GetName(void)
{

    if(m_pval[iDispName].ulPropTag == PR_DISPLAY_NAME)
        return m_pval[iDispName].Value.lpszA;
    else
        return g_szNoFolderName;
}

////////////////////////////////////////////////////////////////////////
// CTVNodeFactory

class CTVNodeFactory
{
public:
    HRESULT HrCreateNode(LPSPropValue pval, ULONG cVals, LPMDB pmdb,
                            LPTVNODE * pptvnode);

    CTVNodeFactory();
    ~CTVNodeFactory();

private:
    void Insert(CTVNode * ptvnode);

    LPTVNODE    m_pHead;
};

////////////////////////////////////////////////////////////////////////
// CChsFldDlg

class CChsFldDlg
{
public:
    HRESULT HrPick(LPCTSTR lpTemplateName, HWND hWnd,
                DLGPROC pfnDlgProc, LPMAPIFOLDER * ppfld, LPMDB *ppmdb);
    HRESULT HrInitTree(HWND hDlg, HWND hwTreeCtl);
    HRESULT HrLoadRoots(void);
    HRESULT HrInsertRoot(LPSPropValue pval);
    BOOL    IsTreeRoot(HTREEITEM hti) { return (hti == m_hiRoot); }

    int     IndAllStores(void)  { return m_iIconAllStores; }
    int     IndRootFld(void)    { return m_iIconRootFld; }
    int     IndOpenFld(void)    { return m_iIconOpenFld; }
    int     IndClsdFld(void)    { return m_iIconClsdFld; }
    HWND    hwDialog(void)      { return m_hDlg; }
    HWND    hwTreeCtl(void)     { return m_hwTreeCtl; }

    LPMAPISESSION   Session(void) { return m_pses; }

    HINSTANCE hInst(void)   { return m_hInst; }

    HRESULT HrCreateNode(LPSPropValue pval, ULONG cvals, LPMDB pmdb,
                            LPTVNODE * ppNode)
        { return m_NodeFactory.HrCreateNode(pval, cvals, pmdb, ppNode);}

    void    SetFolder(LPMAPIFOLDER plfd, LPMDB pmdb);
    void    SetError(HRESULT hr)    { m_hr = hr;}

    HRESULT HrSaveTreeState(void);
    HRESULT HrRestoreTreeState(void);

    CChsFldDlg(LPMAPISESSION pses, HINSTANCE hInst, ULONG * pcb, LPBYTE * ppb);
    ~CChsFldDlg();

    CLastError      m_lsterr;

private:

    HRESULT HrSaveTreeStateEx(BOOL fWrite, ULONG * pcb, LPBYTE * ppb);
    HRESULT HrRestoreTreeState(ULONG cb, LPBYTE pb);

    LPMAPISESSION   m_pses;
    LPMAPIFOLDER    m_pfld;
    LPMDB           m_pmdb;
    HRESULT         m_hr;
    HTREEITEM       m_hiRoot;
    HINSTANCE       m_hInst;
    HWND            m_hDlg;
    HWND            m_hwTreeCtl;

    HIMAGELIST      m_hIml;
    int             m_iIconAllStores;
    int             m_iIconRootFld;
    int             m_iIconOpenFld;
    int             m_iIconClsdFld;

    ULONG           *m_pcbState;
    LPBYTE          *m_ppbState;

    CTVNodeFactory  m_NodeFactory;

};

HTREEITEM AddOneItem( HTREEITEM hParent, HTREEITEM hInsAfter,
    int iImage, int iImageSel, HWND hwndTree, LPTVNODE pNode, int cKids);

#endif //__TVDLG__H__


TVNODE.CPP

///////////////////////////////////////////////////////////////////////
//
//  TVNODE.CPP
//
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
///////////////////////////////////////////////////////////////////////

#define STRICT
#include <windows.h>
#include <commctrl.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapidbg.h>
#include "lasterr.h"
#include "tvdlg.h"

LPSTR g_szNoFolderName = "<No Name>";

//
//  CTVNode::CTVNode
//
CTVNode::CTVNode(LPSPropValue pval, ULONG cProps, LPMDB pmdb)
{
    Assert(cProps == nhtProps);
    Assert(pval);

    m_pval = pval;

    m_htiMe = NULL;

    m_fKidsLoaded = FALSE;

    m_pfld = NULL;

    m_pNext = NULL;

    m_pmdb = pmdb;
    if(pmdb)
        pmdb->AddRef();

}

//
//  CTVNode::~CTVNode
//
CTVNode::~CTVNode()
{
    MAPIFreeBuffer(m_pval);
    UlRelease(m_pfld);
    UlRelease(m_pmdb);
}

//
//  CTVNode::HrExpand
//
//  Put all kids of the given folder in the tree control
//
HRESULT CTVNode::HrExpand(CChsFldDlg * pCFDlg)
{
    HRESULT hr;
    LPMAPITABLE ptblHier = NULL;
    LPSRowSet pRowSet = NULL;
    UINT ind;

    static SSortOrderSet sosName;

    sosName.cSorts = 1;
    sosName.cCategories = 0;
    sosName.cExpanded = 0;
    sosName.aSort[0].ulPropTag = PR_DISPLAY_NAME;
    sosName.aSort[0].ulOrder = TABLE_SORT_ASCEND;

    Assert(m_htiMe);

    if(m_fKidsLoaded || !m_pval[iSubfldrs].Value.b)
        return hrSuccess;

    if(!m_pmdb)
    {
    // this node corresponds to the top level of a message store which has
    // not been opend yet.
    // m_pval[iEID] contains entry ID of the message store
    //
        hr = HrOpenMDB(pCFDlg);
        if(FAILED(hr))
            goto err;
    }

    Assert(m_pmdb);

    if(!m_pfld)
    {
        hr = HrOpenFolder(pCFDlg);
        if(FAILED(hr))
            goto err;
    }

    Assert(m_pfld);

    hr = m_pfld->GetHierarchyTable(MAPI_DEFERRED_ERRORS, &ptblHier);
    if(HR_FAILED(hr))
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, m_pfld);
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());

        goto err;
    }

    hr = HrQueryAllRows(ptblHier, (LPSPropTagArray)&spthtProps, NULL, &sosName,
                        0, &pRowSet);
    if(HR_FAILED(hr))
        goto err;

    if(0 == pRowSet->cRows)
    {
        m_pval[iSubfldrs].Value.b = FALSE;
        goto err;
    }

    for(ind = 0; ind < pRowSet->cRows; ++ind)
    {
        LPSPropValue pval = pRowSet->aRow[ind].lpProps;

        Assert(pRowSet->aRow[ind].cValues == nhtProps);
        Assert(pval[iEID].ulPropTag == PR_ENTRYID);
        Assert(pval[iDispName].ulPropTag == PR_DISPLAY_NAME);
        Assert(pval[iSubfldrs].ulPropTag == PR_SUBFOLDERS);

        LPTVNODE pNode = NULL;

        hr = pCFDlg->HrCreateNode(pval, nhtProps, m_pmdb, &pNode);
        if(hr)
            goto err;

        //this row will be freed in ~CTVNode
        pRowSet->aRow[ind].cValues = 0;
        pRowSet->aRow[ind].lpProps = NULL;

        HTREEITEM hItem;

        hItem = AddOneItem(m_htiMe,  TVI_LAST, pCFDlg->IndClsdFld(),
                            pCFDlg->IndOpenFld(), pCFDlg->hwTreeCtl(), pNode,
                            pval[iSubfldrs].Value.b? 1: 0);
        if(!hItem)
        {
            hr = ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
            goto err;
        }

    }

    m_fKidsLoaded = TRUE;

err:
    UlRelease(ptblHier);
    FreeProws(pRowSet);

    DebugTraceResult(CTVNode::HrExpand, hr);
    return hr;
}

//
//  CTVNode::HrOpenMDB
//
HRESULT CTVNode::HrOpenMDB(CChsFldDlg * pCFDlg)
{
    HRESULT hr;
    LPMDB pmdb = NULL;
    LPSPropValue pvalIPM = NULL;
    ULONG ulObjType;

    Assert(m_pval[iEID].ulPropTag == PR_ENTRYID);

    DebugTrace("ChsFld: Openning Msg Store: %s\n", GetName());

    hr = pCFDlg->Session()->OpenMsgStore(0L, m_pval[iEID].Value.bin.cb,
                                (LPENTRYID)m_pval[iEID].Value.bin.lpb,
                                NULL, MAPI_BEST_ACCESS, &pmdb);
    if(hr) //Display warning messages too
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, pCFDlg->Session());
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());
    }

    if(HR_FAILED(hr))
        goto err;

    hr = HrGetOneProp(pmdb, PR_IPM_SUBTREE_ENTRYID, &pvalIPM);
    if(hr)
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, pmdb);
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());

        goto err;
    }

    hr = pmdb->OpenEntry(pvalIPM->Value.bin.cb,
                (LPENTRYID)pvalIPM->Value.bin.lpb,
                NULL, MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
                 &ulObjType, (LPUNKNOWN *) &m_pfld);
    if(HR_FAILED(hr))
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, pmdb);
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());

        goto err;
    }

    Assert(MAPI_FOLDER == ulObjType);

/*  if(pvalIPM->Value.bin.cb > m_pval[iEID].Value.bin.cb)
    {
        if(hr = MAPIAllocateMore(pvalIPM->Value.bin.cb,
                        m_pval, (LPVOID *)&m_pval[iEID].Value.bin.lpb))
            goto err;

    }

    CopyMemory(m_pval[iEID].Value.bin.lpb, pvalIPM->Value.bin.lpb,
                                        pvalIPM->Value.bin.cb);
    m_pval[iEID].Value.bin.cb = pvalIPM->Value.bin.cb;*/

err:
    if(HR_FAILED(hr))
    {
        UlRelease(pmdb);
        UlRelease(m_pfld);
        m_pfld = NULL;
    }
    else
    {
        m_pmdb = pmdb;
        hr = hrSuccess; //don't return warnings
    }

    MAPIFreeBuffer(pvalIPM);

    DebugTraceResult(CTVNode::HrOpenMDB, hr);
    return hr;
}

//
//  CTVNode::HrOpenFolder
//
HRESULT CTVNode::HrOpenFolder(CChsFldDlg * pCFDlg)
{
    HRESULT hr;
    ULONG ulObjType;

    Assert(m_pval[iEID].ulPropTag == PR_ENTRYID);
    Assert(m_pmdb);

    // MAPI_MODIFY flag affects only IMAPIProp interface of the object.
    // It does not guarantee permission to create subfolders.
    hr = m_pmdb->OpenEntry(m_pval[iEID].Value.bin.cb,
                (LPENTRYID)m_pval[iEID].Value.bin.lpb,
                NULL, MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
                 &ulObjType, (LPUNKNOWN *) &m_pfld);
    if(HR_FAILED(hr))
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, m_pmdb);
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());

        goto err;
    }

    Assert(MAPI_FOLDER == ulObjType);
err:

    DebugTraceResult(CTVNode::HrOpenFolder, hr);
    return hr;

}

//
//  CTVNode::HrGetFolder
//
//  return folder interface for the node
HRESULT CTVNode::HrGetFolder(CChsFldDlg * pCFDlg,
                            LPMAPIFOLDER * ppfld, LPMDB *ppmdb)
{
    HRESULT hr = hrSuccess;

    Assert(pCFDlg);
    Assert(ppfld);
    Assert(ppmdb);

    if(!m_pmdb)
    {
        hr = HrOpenMDB(pCFDlg);
        if(FAILED(hr))
            goto err;
    }
    Assert(m_pmdb);

    if(!m_pfld)
    {
        Assert(!m_fKidsLoaded);

        hr = HrOpenFolder(pCFDlg);
        if(FAILED(hr))
            goto err;

    }
    Assert(m_pfld);

    *ppfld = m_pfld;
    m_pfld->AddRef();

    m_pmdb->AddRef();
    *ppmdb = m_pmdb;

err:

    DebugTraceResult(CTVNode::HrGetFolder, hr);
    return hr;
}

//
//  CTVNode::HrNewFolder
//
// Create subfolder szFldName
//
HRESULT CTVNode::HrNewFolder(CChsFldDlg * pCFDlg,
                                     LPSTR szFldName)
{
    HRESULT hr;
    LPMAPIFOLDER pfldNew = NULL;
    LPTVNODE pNode = NULL;
    LPSPropValue pval = NULL;
    HTREEITEM hItem;

    Assert(szFldName);
    Assert(pCFDlg);

    if(!m_pmdb)
    {
        hr = HrOpenMDB(pCFDlg);
        if(FAILED(hr))
            goto err;
    }

    Assert(m_pmdb);

    if(!m_pfld)
    {
        hr = HrOpenFolder(pCFDlg);
        if(FAILED(hr))
            goto err;
    }

    Assert(m_pmdb);

    hr = m_pfld->CreateFolder(FOLDER_GENERIC, szFldName, NULL,
                                NULL, 0, &pfldNew);
    if(HR_FAILED(hr))
    {
        pCFDlg->m_lsterr.HrSetLastError(hr, m_pfld);
        pCFDlg->m_lsterr.ShowError(pCFDlg->hwDialog());

        goto err;
    }

    if(!m_pval[iSubfldrs].Value.b)
    {
        m_pval[iSubfldrs].Value.b = TRUE;

        TV_ITEM tvI;

        tvI.hItem           = m_htiMe;
        tvI.mask            = TVIF_CHILDREN;
        tvI.cChildren       = 1;

        TreeView_SetItem(pCFDlg->hwTreeCtl(), &tvI);
    }

    if(m_fKidsLoaded)
    {
        hr = MAPIAllocateBuffer(sizeof(SPropValue)* nhtProps, (LPVOID *)&pval);
        if(hr)
            goto err;

        ZeroMemory(pval, sizeof(SPropValue) * nhtProps );

        pval[iEID].ulPropTag = PR_ENTRYID;
        pval[iDispName].ulPropTag = PR_DISPLAY_NAME;
        pval[iSubfldrs].ulPropTag = PR_SUBFOLDERS;

        pval[iSubfldrs].Value.b = FALSE;

        int cb = lstrlen(szFldName) + 1;
        hr = MAPIAllocateMore(cb, pval, (LPVOID *)&pval[iDispName].Value.lpszA);
        if(hr)
            goto err;

        lstrcpy(pval[iDispName].Value.lpszA, szFldName);

        hr = pCFDlg->HrCreateNode(pval, nhtProps, m_pmdb, &pNode);
        if(HR_FAILED(hr))
            goto err;

        pval = NULL;

        hItem = AddOneItem(m_htiMe,  TVI_SORT, pCFDlg->IndClsdFld(),
                        pCFDlg->IndOpenFld(), pCFDlg->hwTreeCtl(), pNode, 0);
        if(!hItem)
        {
            hr = ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
            goto err;
        }

        pNode->m_pfld = pfldNew;
        pfldNew = NULL;

    }

err:
    MAPIFreeBuffer(pval);
    UlRelease(pfldNew);

    DebugTraceResult(CTVNode::HrNewFolder, hr);
    return hr;
}

//
//  CTVNode::Write
//
// Used in CChsFldDlg::HrSaveTreeState
void CTVNode::Write(BOOL fWrite, LONG iLevel, LPBYTE * ppb)
{
    if(fWrite)
        *((LONG *)*ppb) = iLevel;
    *ppb += sizeof(LONG);

    if(iLevel != 0)
    {
        ULONG cb = m_pval[iEID].Value.bin.cb;

        if(fWrite)
            *((ULONG *)*ppb) = cb;
        *ppb += sizeof(ULONG);

        if(fWrite)
            CopyMemory(*ppb, m_pval[iEID].Value.bin.lpb, cb);
        *ppb += Align4(cb);
    }
    else
    {
        Assert(m_pval[iDispName].Value.lpszA == g_szAllStores);
    }

}

LPVOID CTVNode::operator new( size_t cb )
{
    LPVOID pv;

    if ( MAPIAllocateBuffer( (ULONG)cb, &pv ) )
        pv = NULL;

    return pv;
}

void CTVNode::operator delete( LPVOID pv )
{
    MAPIFreeBuffer( pv );
}


TVSTACK.H

///////////////////////////////////////////////////////////////////////
//
//  TVSTACK.H
//
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
///////////////////////////////////////////////////////////////////////

#define StackSize 64

class CTIStack
{

public:
    CTIStack()  { m_ptos = m_ahti; m_pMax = m_ahti + StackSize - 1;}
    ~CTIStack() {};

    BOOL Push(HTREEITEM hti);
    HTREEITEM Pop(void);
    BOOL IsEmpty(void);

private:
    HTREEITEM m_ahti[StackSize];
    HTREEITEM *m_ptos;
    HTREEITEM *m_pMax;
};

inline BOOL CTIStack::Push(HTREEITEM hti)
{
    Assert(m_ptos >= m_ahti);
    Assert(m_ptos <= m_pMax);

    if(m_ptos == m_pMax)
        return FALSE;

    *m_ptos++ = hti;
    return TRUE;
}

inline BOOL CTIStack::IsEmpty(void)
{
    Assert(m_ptos >= m_ahti);
    Assert(m_ptos <= m_pMax);

    return (m_ptos == m_ahti);
}

inline HTREEITEM CTIStack::Pop(void)
{
    Assert(m_ptos > m_ahti);
    Assert(m_ptos <= m_pMax);

    if(m_ptos == m_ahti)
        return NULL;

    return *--m_ptos;
}


README.TXT

Sample CMC Client

The CMC.Cli sample demonstrates using Common Messaging Calls (CMC) functions.

The sample is a very simple mail client. Using it is intuitive and
straightforward.

To run this application, you have to have the MAPI run-time binaries
installed on your system. For installation instructions, see the
README.WRI in the mstools\mapi\ directory.

When writing CMC applications, the addresses of all of the CMC functions
has to be obtained explicitly using GetProcAddress, as opposed to
linking with the import library for MAPI.DLL. You may wish to adapt
the function InitSimpleCMC() from CMCCLI.C for this purpose.

If sending/receiving mail is not the primary function of your application,
you can test presence of CMC in a system without incurring the high cost of
trying to load a DLL. You can test the value of the CMC variable. It is 1
if CMC is installed. The variable is located in the [mail] section of your
WIN.INI file on a 16-bit Windows system and under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem
in the system registry on a 32-bit Windows system. You can cut and paste
the function fCMCInstalled() from CMCCLI.C for this purpose.


CMCCLI.H

/*
 -  C M C C L I . H
 -
 *  Purpose:
 *      Header file for the sample mail client based on CMC.
 *
 */

#ifndef CMC_H
//#include <xcmc.h>
#endif

#ifndef ULONG
typedef unsigned long ULONG;
#endif

/* Menu Item IDs */

#define IDM_LOGON           100
#define IDM_LOGOFF          101
#define IDM_EXIT            102
#define IDM_COMPOSE_CMC     103
#define IDM_COMPOSE_CUSTOM  104
#define IDM_READ            105
#define IDM_ADDRBOOK        106
#define IDM_ABOUT           107

/* Resource IDs */

#define ICON_NOMAIL     200
#define ICON_MAIL       201
#define IDREAD          300
#define IDREADA         301
#define IDUNREAD        302
#define IDUNREADA       303

/* Address Book Control IDs */

#define IDT_ADDRESS     101
#define IDC_ADDRESS     102
#define IDT_LIST        103
#define IDC_LIST        104
#define IDC_ADD         105
#define IDC_REMOVE      106

/* Compose Note Control IDs */

#define IDC_SEND        101
#define IDC_RESOLVE     102
#define IDC_ATTACH      103
#define IDC_OPTIONS     104
#define IDC_ADDRBOOK    105
#define IDT_TO          106
#define IDC_TO          107
#define IDT_CC          108
#define IDC_CC          109
#define IDT_SUBJECT     110
#define IDC_SUBJECT     111
#define IDC_NOTE        112
#define IDC_LINE1       113
#define IDC_LINE2       114

/* InBox Control IDs */

#define IDT_MSG         101
#define IDC_MSG         102
#define IDC_NEW         103
#define IDC_READ        104
#define IDC_DELETE      105
#define IDC_CLOSE       106

/* ReadNote Control IDs */

#define IDC_SAVECHANGES 101
#define IDC_SAVEATTACH  102
#define IDC_REPLY       103
#define IDC_REPLYALL    104
#define IDC_FORWARD     105
#define IDT_RFROM       106
#define IDT_RDATE       107
#define IDT_RTO         108
#define IDT_RCC         109
#define IDT_RSUBJECT    110
#define IDC_RFROM       111
#define IDC_RDATE       112
#define IDC_RTO         113
#define IDC_RCC         114
#define IDC_RSUBJECT    115
#define IDC_READNOTE    116
#define IDT_ATTACHMENT  117
#define IDC_ATTACHMENT  118
#define IDC_LINE3       119
#define IDC_LINE4       120

/* Options Control IDs */

#define IDC_RETURN      101
#define IDC_PRIORITY    102
#define IDC_URGENT      103
#define IDC_NORMAL      104
#define IDC_LOW         105

/* About Box Control IDs */

#define IDC_VERSION     101
#define IDC_STATIC1     102
#define IDC_STATIC2     103
#define IDC_STATIC3     104

/* String Table IDs */

#define CMC_E_MAX 40

#define IDS_LOGONFAIL           (CMC_E_MAX + 1)
#define IDS_ADDRBOOKFAIL        (CMC_E_MAX + 2)
#define IDS_RESOLVEFAIL         (CMC_E_MAX + 3)
#define IDS_UNRESOLVEDNAMES     (CMC_E_MAX + 4)
#define IDS_SENDERROR           (CMC_E_MAX + 5)
#define IDS_NORECIPS            (CMC_E_MAX + 6)
#define IDS_SAVEATTACHERROR     (CMC_E_MAX + 7)
#define IDS_READFAIL            (CMC_E_MAX + 8)
#define IDS_MAKENEWFAIL         (CMC_E_MAX + 9)

#define IDS_FILTER              (CMC_E_MAX + 50)

/* Manifest Constants */

#define ADDR_MAX            128
#define MAXUSERS            10
#define TO_EDIT_MAX         512
#define CC_EDIT_MAX         512
#define SUBJECT_EDIT_MAX    128
#define NOTE_LINE_MAX       1024
#define FILE_ATTACH_MAX     32

/* Message Box styles */

#define MBS_ERROR           (MB_ICONSTOP | MB_OK)
#define MBS_INFO            (MB_ICONINFORMATION | MB_OK)
#define MBS_OOPS            (MB_ICONEXCLAMATION | MB_OK)

/* Structure Definitions */

typedef struct _msgid FAR *LPMSGID;

typedef struct _msgid
{
    CMC_message_reference   *message_reference;
    CMC_string              from;
    CMC_string              subject;
    CMC_time                time_sent;
    CMC_boolean             fHasAttach;
    CMC_boolean             fRead;
    LPMSGID                 lpPrev;
    LPMSGID                 lpNext;
} MSGID;

/* Function Prototypes */

int  PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
void DeinitApplication(void);
BOOL InitSimpleCMC (void);
void DeinitSimpleCMC(void);
long FAR PASCAL MainWndProc(HWND, UINT, UINT, LPARAM);
BOOL FAR PASCAL AboutDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL InBoxDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL ReadMailDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL DetailsDlgProc (HWND, UINT, UINT, LONG);
BOOL FAR PASCAL ComposeDlgProc (HWND, UINT, UINT, LONG);
BOOL FAR PASCAL OptionsDlgProc (HWND, UINT, UINT, LONG);
void SecureMenu(HWND hWnd, BOOL fBeforeLogon);

void SaveFileAttachments(HWND, CMC_attachment FAR *);
void MakeDisplayNameStr(LPSTR, CMC_enum, ULONG, CMC_recipient FAR *);
ULONG CopyAttachment (CMC_attachment FAR *,CMC_attachment FAR *,CMC_attachment FAR *);
CMC_return_code ResolveFriendlyNames (HWND, LPSTR, CMC_enum, ULONG *, CMC_recipient FAR * *);
CMC_return_code CopyRecipient (CMC_recipient FAR *,CMC_recipient FAR *,CMC_recipient FAR *);
CMC_return_code SaveMsgChanges (HWND,CMC_message far *);
CMC_return_code MakeNewMessage (CMC_message far *, CMC_flags);
CMC_return_code GetNextFile (HWND, ULONG, ULONG *, CMC_attachment FAR * *);
BOOL FNameInList (LPSTR, ULONG, CMC_recipient FAR *);

void DrawMsgItem (DRAWITEMSTRUCT *);
void DrawMsgIcon(HDC , HBITMAP , int , int , int , int);

LPMSGID MakeMsgNode (CMC_message_summary FAR *);
LPMSGID FindNode (LPMSGID, CMC_message_reference FAR *);
void    InsertMsgNode (LPMSGID, LPMSGID *);
void    DeleteMsgNode (LPMSGID, LPMSGID *);
void    FreeMsgList (LPMSGID);

void ConvertDateRec (CMC_time far *, LPSTR);
void MakeMessageBox(HWND, ULONG, UINT, UINT);
void ToggleMenuState(HWND, BOOL);
void LogSendMail(ULONG ulResult);

BOOL fCMCInstalled(void);

/* CMC functions */

extern HINSTANCE    hlibCMC;

typedef CMC_return_code (FAR PASCAL *LPFNCMCQUERYCONFIGURATION)
    (CMC_session_id session, CMC_enum item,CMC_buffer reference,CMC_extension FAR *config_extensions);

extern LPFNCMCQUERYCONFIGURATION lpfnCMCQueryConfiguration;

#define CMCQueryConfiguration           (*lpfnCMCQueryConfiguration)

typedef CMC_return_code (FAR PASCAL *LPFNCMCLOGON)(
    CMC_string              service,
    CMC_string              user,
    CMC_string              password,
    CMC_enum                character_set,
    CMC_ui_id               ui_id,
    CMC_uint16              caller_cmc_version,
    CMC_flags               logon_flags,
    CMC_session_id FAR      *session,
    CMC_extension FAR       *logon_extensions
);

extern LPFNCMCLOGON lpfnCMCLogon;

#define CMCLogon            (*lpfnCMCLogon)

typedef CMC_return_code (FAR PASCAL *LPFNCMCLOGOFF)(
    CMC_session_id          session,
    CMC_ui_id               ui_id,
    CMC_flags               logoff_flags,
    CMC_extension FAR       *logoff_extensions
);

extern LPFNCMCLOGOFF lpfnCMCLogoff;

#define CMCLogoff           (*lpfnCMCLogoff)

typedef CMC_return_code (FAR PASCAL *LPFNCMCFREE)(
    CMC_buffer              memory
);

extern LPFNCMCFREE lpfnCMCFree;

#define CMCFree     (*lpfnCMCFree)

typedef CMC_return_code (FAR PASCAL *LPFNCMCLOOKUP)(
    CMC_session_id          session,
    CMC_recipient FAR       *recipient_in,
    CMC_flags               look_up_flags,
    CMC_ui_id               ui_id,
    CMC_uint32 FAR          *count,
    CMC_recipient FAR * FAR *recipient_out,
    CMC_extension FAR       *look_up_extensions
);

extern LPFNCMCLOOKUP lpfnCMCLookUp;

#define CMCLookUp (*lpfnCMCLookUp)

typedef CMC_return_code (FAR PASCAL *LPFNCMCLIST)(
    CMC_session_id          session,
    CMC_string              message_type,
    CMC_flags               list_flags,
    CMC_message_reference   *seed,
    CMC_uint32 FAR          *count,
    CMC_ui_id               ui_id,
    CMC_message_summary FAR * FAR *result,
    CMC_extension FAR       *list_extensions
);

extern LPFNCMCLIST lpfnCMCList;

#define CMCList  (*lpfnCMCList)

typedef CMC_return_code (FAR PASCAL *LPFNCMCSEND)(
    CMC_session_id          session,
    CMC_message FAR         *message,
    CMC_flags               send_flags,
    CMC_ui_id               ui_id,
    CMC_extension FAR       *send_extensions
);

extern LPFNCMCSEND lpfnCMCSend;

#define CMCSend (*lpfnCMCSend)

typedef CMC_return_code (FAR PASCAL *LPFNCMCREAD)(
    CMC_session_id          session,
    CMC_message_reference   *message_reference,
    CMC_flags               read_flags,
    CMC_message FAR * FAR   *message,
    CMC_ui_id               ui_id,
    CMC_extension FAR       *read_extensions
);

extern LPFNCMCREAD lpfnCMCRead;

#define CMCRead (*lpfnCMCRead)

typedef CMC_return_code (FAR PASCAL *LPFNCMCACTON)(
    CMC_session_id          session,
    CMC_message_reference   *message_reference,
    CMC_enum                operation,
    CMC_flags               act_on_flags,
    CMC_ui_id               ui_id,
    CMC_extension FAR       *act_on_extensions
);

extern LPFNCMCACTON lpfnCMCActOn;

#define CMCActOn (*lpfnCMCActOn)


CMCCLI.C

//
//  C M C C L I . C
//
//      Sample CMC mail client for the MAPI 1.0 PDK.
//      Uses the CMC interface without extensions.
//
//

#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <lzexpand.h>
#include <xcmc.h>
#include <xcmcext.h>
#include <xcmcmsxt.h>
#include "pvalloc.h"
#include "cmccli.h"

HINSTANCE hInst;
HINSTANCE hlibCMC = (HINSTANCE)NULL;

LPFNCMCQUERYCONFIGURATION lpfnCMCQueryConfiguration = NULL;
LPFNCMCLOGON lpfnCMCLogon = NULL;
LPFNCMCLOGOFF lpfnCMCLogoff = NULL;
LPFNCMCFREE lpfnCMCFree = NULL;
LPFNCMCLOOKUP lpfnCMCLookUp = NULL;
LPFNCMCLIST lpfnCMCList = NULL;
LPFNCMCSEND lpfnCMCSend = NULL;
LPFNCMCREAD lpfnCMCRead = NULL;
LPFNCMCACTON lpfnCMCActOn = NULL;

// Static Data

static BOOL fPriority = 0;
static BOOL fReturn = 0;
static CMC_extension extMsgOpt[2] = \
                     {{CMC_X_COM_PRIORITY, CMC_X_COM_NORMAL, NULL, 0}, \
                      {CMC_X_MS_MESSAGE_DATA, 0, NULL, CMC_EXT_LAST_ELEMENT}};

static CMC_session_id lhSession = 0;
static HBITMAP hReadBmp;
static HBITMAP hReadABmp;
static HBITMAP hUnReadBmp;
static HBITMAP hUnReadABmp;
static HCURSOR hWaitCur;
static LPMSGID lpReadMsgNode=NULL;
static CMC_message FAR *CmcMsg = NULL;

int PASCAL
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmd, int nCmdShow)
{
    MSG msg;

    hlibCMC = (HINSTANCE) NULL;

    if (!hPrevInst)
        if (!InitApplication (hInstance))
            return (FALSE);

    if (!InitInstance (hInstance, nCmdShow))
        return (FALSE);

    while (GetMessage (&msg, (HWND) NULL, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    if (hReadBmp)
        DeleteObject (hReadBmp);
    if (hReadABmp)
        DeleteObject (hReadABmp);
    if (hReadBmp)
        DeleteObject (hUnReadBmp);
    if (hReadBmp)
        DeleteObject (hUnReadABmp);

    DeinitApplication ();

    return (msg.wParam);
}

//
//  InitApplication
//
//  Purpose:
//      Initialize the application.
//
//  Parameters:
//      hInstance   - Instance handle
//
//  Returns:
//      True/False
//
//

BOOL
InitApplication (HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (hInstance, "NoMail");
    wc.hCursor = LoadCursor ((HINSTANCE) hInstance, IDC_ARROW);
    wc.hbrBackground = GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName = "MailMenu";
    wc.lpszClassName = "Client";

    return (RegisterClass (&wc));
}

//
//  InitInstance
//
//  Purpose:
//      Initialize this instance.
//
//  Parameters:
//      hInstance   - Instance handle
//      nCmdShow    - Do we show the window?
//
//  Returns:
//      True/False
//
//

BOOL
InitInstance (HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd;
    BOOL fInit;
    CMC_return_code ulResult;
    CMC_boolean UI_available;
    CMC_extension ext;
    CMC_X_COM_support sup[2];

    hInst = hInstance;

    hWnd = CreateWindow ("Client", "CMC Sample Mail Client", WS_OVERLAPPEDWINDOW,
        5, 5, 300, 75, (HWND) NULL, (HMENU) NULL, hInst, NULL);

    if (!hWnd)
        return (FALSE);

    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);

    hReadBmp = LoadBitmap (hInst, MAKEINTRESOURCE (IDREAD));
    hReadABmp = LoadBitmap (hInst, MAKEINTRESOURCE (IDREADA));
    hUnReadBmp = LoadBitmap (hInst, MAKEINTRESOURCE (IDUNREAD));
    hUnReadABmp = LoadBitmap (hInst, MAKEINTRESOURCE (IDUNREADA));

    hWaitCur = LoadCursor( (HINSTANCE) NULL, IDC_WAIT);

    if (fInit = InitSimpleCMC ())
    {
        if((ulResult = CMCQueryConfiguration(
           (CMC_session_id) NULL, // no session handle yet!
           CMC_CONFIG_UI_AVAIL,   // Make sure UI is available
           &UI_available,         // return value
           NULL))==CMC_SUCCESS)
        {
            sup[0].item_code = CMC_XS_COM;
            sup[0].flags = 0;
            sup[1].item_code = CMC_XS_MS;
            sup[1].flags = 0;
            ext.item_code = CMC_X_COM_SUPPORT_EXT;
            ext.item_data = 2;
            ext.item_reference = sup;
            ext.extension_flags = CMC_EXT_LAST_ELEMENT;

            /* CMCLogon might yield control to Windows. So to prevent the user
            from clicking "logon" while we are in the process of loggin on we
            have to disable it*/
            SecureMenu(hWnd, TRUE);

            if ((ulResult = CMCLogon (NULL, NULL, NULL, (CMC_enum)0,
                                  (CMC_ui_id)hWnd, CMC_VERSION,
                                  CMC_LOGON_UI_ALLOWED | CMC_ERROR_UI_ALLOWED,
                                  &lhSession, &ext))==CMC_SUCCESS)
            {
                if(sup[0].flags & CMC_X_COM_SUPPORTED)
                    fPriority = TRUE;
                else
                    fPriority = FALSE;
                if(sup[1].flags & CMC_X_COM_SUPPORTED)
                    fReturn = TRUE;
                else
                    fReturn = FALSE;

                ToggleMenuState (hWnd, TRUE);
            }
            else
            {
                SecureMenu(hWnd, FALSE);
                lhSession = 0;
                MakeMessageBox (hWnd, ulResult, IDS_LOGONFAIL, MBS_ERROR);
            }
        }
        else
        {
            lhSession = 0;
            MakeMessageBox (hWnd, ulResult, IDS_LOGONFAIL, MBS_ERROR);
        }
    }
    return (fInit);
}

//
//  InitSimpleCMC
//
//  Purpose:
//      Loads the DLL containing the simple CMC functions and sets
//      up a pointer to each. Wrappers for the  function pointers
//      are declared in CLIENT.H.
//
//  Returns:
//      TRUE if sucessful, else FALSE
//
//  Side effects:
//      Loads a DLL and sets up function pointers
//
BOOL
InitSimpleCMC (void)
{
    char    szMsgBuf[512]="";
    char    szLibName[32]
        #ifdef WIN16
            ="MAPI.DLL";
        #else
            ="MAPI32.dll";
        #endif

    /*
     *Check if CMC is installed on the system
     */
    if(!fCMCInstalled())
        return FALSE;

    // Get INI directives for alternate DLL PATH

#ifdef WIN16
    GetProfileString( "Mail", "CMCDLLNAME", szLibName, szLibName,
            sizeof(szLibName) - 1 );
#else
    GetProfileString( "Mail", "CMCDLLNAME32", szLibName, szLibName,
            sizeof(szLibName) - 1 );
#endif

#ifdef WIN16
    if ((hlibCMC = LoadLibrary (szLibName)) <  32)
#else
    if ((hlibCMC = LoadLibrary (szLibName)) <  (HINSTANCE) 32)
#endif
    {
        wsprintf(szMsgBuf,"Cannot Load %s.  Check to see that you have %s in your path or '~SystemRoot~\\System' "
                          "directory.  You may change the CMC dll path by adding a [MAIL] section to your "
                          "WIN.INI file wtih the following entry: CMCDLLNAME=\\MAPI.REL\\MAPI.DLL",
                          szLibName,szLibName);
        MessageBox(NULL, szMsgBuf,"Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCQueryConfiguration = (LPFNCMCQUERYCONFIGURATION)GetProcAddress (hlibCMC, "cmc_query_configuration") ))
    {
        wsprintf(szMsgBuf,"Cannot Load CMC process functions from %s.  Check that you do not have an "
                          "outdated version of %s in your '~SystemRoot~\\System' directory.  If you do then you "
                          "may change the CMC dll path by adding a [MAIL] section to your WIN.INI file with the "
                          "following entry: CMCDLLNAME=\\MAPI.REL\\%s",szLibName,szLibName,szLibName);
        MessageBox(NULL, szMsgBuf,"Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCLogon  =  (LPFNCMCLOGON)GetProcAddress (hlibCMC, "cmc_logon") ))
    {
        MessageBox(NULL, "Cannot Load cmc_logon process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCLogoff  =  (LPFNCMCLOGOFF)GetProcAddress (hlibCMC, "cmc_logoff") ))
    {
        MessageBox(NULL, "Cannot Load cmc_logoff process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCFree  =  (LPFNCMCFREE)GetProcAddress (hlibCMC, "cmc_free") ))
    {
        MessageBox(NULL, "Cannot Load cmc_free process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCLookUp  =  (LPFNCMCLOOKUP)GetProcAddress (hlibCMC, "cmc_look_up") ))
    {
        MessageBox(NULL, "Cannot Load cmc_look_up process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCList  =  (LPFNCMCLIST)GetProcAddress (hlibCMC, "cmc_list") ))
    {
        MessageBox(NULL, "Cannot Load cmc_list process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCSend  =  (LPFNCMCSEND)GetProcAddress (hlibCMC, "cmc_send") ))
    {
        MessageBox(NULL, "Cannot Load cmc_send process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCRead  =  (LPFNCMCREAD)GetProcAddress (hlibCMC, "cmc_read") ))
    {
        MessageBox(NULL, "Cannot Load cmc_read process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    if (!(lpfnCMCActOn  =  (LPFNCMCACTON)GetProcAddress (hlibCMC, "cmc_act_on") ))
    {
        MessageBox(NULL, "Cannot Load cmc_acto_on process address.","Error", MB_ICONSTOP | MB_OK);
        return FALSE;
    }

    return (TRUE);

}

/*
 -  fCMCInstalled
 -
 *  Purpose:
 *      Checks the appropriate win.ini/registry value to see if CMC is
 *      installed in the system.
 *
 *  Returns:
 *      TRUE if CMC is installed, else FALSE
 *
 */
BOOL
fCMCInstalled(void)
{
#ifdef _WIN32
    /* on win32 the value to check is
        HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem\CMC
    */

    LONG lr;
    HKEY hkWMS;

    #define CMCVSize 8
    char szCMCValue[CMCVSize];
    DWORD dwType;
    DWORD cbCMCValue = CMCVSize;

    lr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                    "SOFTWARE\\Microsoft\\Windows Messaging Subsystem",
                     0, KEY_READ, &hkWMS);
    if(ERROR_SUCCESS == lr)
    {
        lr = RegQueryValueEx(hkWMS, "CMC", 0, &dwType, szCMCValue, &cbCMCValue);
        RegCloseKey(hkWMS);
        if(ERROR_SUCCESS == lr)
        {
            if(lstrcmp(szCMCValue, "1") == 0)
                return TRUE;
        }
    }

    return FALSE;

#else /*_WIN32*/

    /*check the win.ini value*/
    return GetProfileInt("Mail", "CMC", 0);

#endif /*_WIN32*/
}

void
DeinitSimpleCMC ()
{
    if (hlibCMC)
    {
        FreeLibrary (hlibCMC);
        hlibCMC = (HINSTANCE) NULL;
    }
}

void
DeinitApplication ()
{
    DeinitSimpleCMC ();
}

//
//  MainWndProc
//
//  Purpose:
//      Main Window Procedure for test program.
//
//  Parameters:
//      hWnd
//      message
//      wParam
//      lParam
//
//  Returns:
//
//
//

LONG FAR PASCAL
MainWndProc (HWND hWnd, UINT msg, UINT wParam, LPARAM lParam)
{
    CMC_return_code ulResult;
    CMC_boolean UI_available;
    CMC_message far *lpmsg;
    ULONG cNewRecips;
    CMC_recipient FAR *lpNewRecips;
    CMC_extension ext;
    char *szFoo[2];

    switch (msg)
    {
    case WM_COMMAND:
        switch (LOWORD (wParam)) {
        case IDM_LOGON:
            if (!lhSession) {
                if ( (ulResult = CMCQueryConfiguration(
                     (CMC_session_id) NULL, // no session handle yet!
                    CMC_CONFIG_UI_AVAIL, // Make sure UI is available
                    &UI_available, // return value
                    NULL))==CMC_SUCCESS) { // no extensions

                    SecureMenu(hWnd, TRUE);

                    if ((ulResult = CMCLogon (NULL,  // Default service
                           NULL,  // Prompt for username
                           NULL,  // Prompt for password
                           (CMC_enum)NULL, // Default Character set
                           (CMC_ui_id) NULL, // Default UI ID
                           CMC_VERSION,  // Version 1 CMC calls
                           CMC_LOGON_UI_ALLOWED | CMC_ERROR_UI_ALLOWED, // full logon and allow errors
                           &lhSession, // returned session id
                           NULL))==CMC_SUCCESS) { // no extensions

                        ToggleMenuState (hWnd, TRUE);
                    } else {
                        SecureMenu(hWnd, FALSE);
                        lhSession = 0;
                        MakeMessageBox (hWnd, ulResult, IDS_LOGONFAIL, MBS_ERROR);
                    }
                }
            }
            break;

        case IDM_LOGOFF:
            if (lhSession) {
               CMCLogoff (lhSession, (CMC_ui_id) NULL, 0, 0);
               ToggleMenuState (hWnd, FALSE);
                lhSession = 0;
            }
            break;

        case IDM_COMPOSE_CUSTOM:
            DialogBox (hInst, "ComposeNote", hWnd, ComposeDlgProc);
            break;

        case IDM_COMPOSE_CMC:
            lpmsg = (CMC_message far *)GlobalAllocPtr(GPTR, sizeof(CMC_message));
            ulResult = CMCSend(lhSession,
                            lpmsg,
                            CMC_SEND_UI_REQUESTED | CMC_ERROR_UI_ALLOWED,
                            (CMC_ui_id)hWnd, NULL);
            if (ulResult != CMC_SUCCESS)
            {
                if (ulResult != CMC_E_USER_CANCEL)
                MakeMessageBox (hWnd, ulResult, IDS_SENDERROR, MBS_ERROR);
            }
            else
            GlobalFreePtr(lpmsg);
            break;

        case IDM_READ:
            DialogBox (hInst, "InBox", hWnd, InBoxDlgProc);
            break;

        case IDM_ADDRBOOK:
            if (lhSession)
            {
                szFoo[0] = "";
                szFoo[1] = "";
                cNewRecips = 0;
                ext.item_code = CMC_X_MS_ADDRESS_UI;
                ext.item_data = 0;
                ext.item_reference = szFoo;
                ext.extension_flags = CMC_EXT_LAST_ELEMENT;
                ulResult = CMCLookUp (lhSession, NULL, CMC_LOOKUP_ADDRESSING_UI,
                    (CMC_ui_id)hWnd, &cNewRecips, &lpNewRecips, &ext);

                if (ulResult != CMC_SUCCESS)
                {
                    if (ulResult != CMC_E_USER_CANCEL)
                        MakeMessageBox (hWnd, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);
                }
            }
            break;

        case IDM_ABOUT:
            DialogBox (hInst, "AboutBox", hWnd, AboutDlgProc);
            break;

        case IDM_EXIT:
            if (lhSession) {
               CMCLogoff (lhSession, (CMC_ui_id) NULL, 0, 0);
               ToggleMenuState (hWnd, FALSE);
                lhSession = 0;
            }

            PostQuitMessage (0);
            break;

        default:
            return (DefWindowProc (hWnd, msg, wParam, lParam));
        }
        break;

    case WM_ENDSESSION:
        DestroyWindow (hWnd);
        break;

    case WM_CLOSE:
    case WM_DESTROY:
        if (lhSession) {
           CMCLogoff (lhSession, (CMC_ui_id) NULL, 0, 0);
           ToggleMenuState (hWnd, FALSE);
            lhSession = 0;
        }
        PostQuitMessage (0);
        break;

    default:
        return (DefWindowProc (hWnd, msg, wParam, lParam));
    }
    return FALSE;
}

//
//  AboutDlgProc
//
//  Purpose:
//      About box dialog procedure
//
//  Parameters:
//      hDlg
//      message
//      wParam
//      lParam
//
//  Returns:
//      True/False
//
//

BOOL FAR PASCAL
AboutDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    char    rgchVersion[80];

    switch (msg)
    {
    case WM_INITDIALOG:
        sprintf(rgchVersion, "Version %01.1f", ((float)CMC_VERSION)/100);
        SetDlgItemText(hDlg, IDC_VERSION, rgchVersion);
        return TRUE;

    case WM_COMMAND:
        if (wParam == IDOK || wParam == IDCANCEL)
        {
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

//
//  ComposeDlgProc
//
//  Purpose:
//      Dialog procedure for the ComposeNote dialog.
//
//  Parameters:
//      hDlg
//      message
//      wParam
//      lParam
//
//  Returns:
//      True/False
//
//

BOOL FAR PASCAL
ComposeDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    char szUnResNames[TO_EDIT_MAX];
    char szDisplayNames[TO_EDIT_MAX];
    char szAttach[FILE_ATTACH_MAX];
    BOOL fUnResTo, fUnResCc;
    LONG cb, cLines;
    ULONG ulResult;
    HCURSOR hOldCur;
    static CMC_string lpszSubject;
    static CMC_string lpszTextNote;
    static ULONG cRecips;
    static ULONG cNewRecips;
    static ULONG cAttach;
    static CMC_recipient FAR *lpRecips;
    static CMC_recipient FAR *lpNewRecips;
    static CMC_attachment FAR *lpAttach;
    CMC_extension ext;
    char *szFoo[2]={"",""};

    switch (msg) {
    case WM_INITDIALOG:
        if (CmcMsg)
        {
            //  ComposeNote is being called to either forward or reply
            //  to a message in the Inbox.  So, we'll initialize the
            //  ComposeNote form with data from the global CMC_message

            lpszSubject = CmcMsg->subject;
            lpszTextNote = CmcMsg->text_note;

            lpRecips = CmcMsg->recipients;
            lpAttach = CmcMsg->attachments;

            if (lpRecips == NULL)
            {
              cRecips = 0;
            }
            else
            {
                cRecips = 0;
                while(!(lpRecips[cRecips].recip_flags & CMC_RECIP_LAST_ELEMENT))
                    cRecips++;

                cRecips++;
            }

            if (lpAttach == NULL)
            {
              cAttach = 0;
            }
            else
            {
                cAttach = 0;
                while(!(lpAttach[cAttach].attach_flags & CMC_ATT_LAST_ELEMENT))
                    cAttach++;

                cAttach++;
            }

            if (cRecips)
            {
                MakeDisplayNameStr (szDisplayNames, CMC_ROLE_TO, cRecips, lpRecips);
                if (*szDisplayNames)
                    SetDlgItemText (hDlg, IDC_TO, szDisplayNames);

                MakeDisplayNameStr (szDisplayNames, CMC_ROLE_CC, cRecips, lpRecips);
                if (*szDisplayNames)
                    SetDlgItemText (hDlg, IDC_CC, szDisplayNames);
            }
            SetDlgItemText (hDlg, IDC_SUBJECT, CmcMsg->subject);
            SetDlgItemText (hDlg, IDC_NOTE, CmcMsg->text_note);

            SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);
            SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);
            SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_SETMODIFY, FALSE, 0);
            SendDlgItemMessage (hDlg, IDC_NOTE, EM_SETMODIFY, FALSE, 0);
            if(cRecips)
                SetFocus (GetDlgItem (hDlg, IDC_NOTE));
            else
                SetFocus (GetDlgItem (hDlg, IDC_TO));
        }

        else
        {
            if(CmcMsg)
                PvFree(CmcMsg);

            CmcMsg = (CMC_message *)PvAlloc(sizeof(CMC_message));
            if (!CmcMsg)
                goto cleanup;

            _fmemset (CmcMsg, 0, sizeof (CMC_message));

            lpszSubject = NULL;
            lpszTextNote = NULL;
            cRecips = 0;
            cAttach = 0;
            lpRecips = NULL;
            lpNewRecips = NULL;
            lpAttach = NULL;

            SetFocus (GetDlgItem (hDlg, IDC_TO));
        }
        return FALSE;

    case WM_COMMAND:
        switch (LOWORD (wParam))
        {
        case IDC_ATTACH:
            if (GetNextFile (hDlg, (ULONG) -1, &cAttach, &lpAttach) == CMC_SUCCESS)
            {
                //   Now, send a little render message to the text_note edit

                wsprintf (szAttach, "<<File: %s>>",
                    lpAttach[cAttach - 1].attach_title);

                SendDlgItemMessage (hDlg, IDC_NOTE, EM_REPLACESEL, 0,
                    (LPARAM) ((LPSTR) szAttach));
            }
            break;

        case IDC_ADDRBOOK:

            // To do:
            // Add a parameter to ResolveFriendlyNames to prepare unresolved names
            // but only as DN strings in recip list.
            // When call returns from Address, rebuild the edit fields.

            cNewRecips = 0;
            ext.item_code = CMC_X_MS_ADDRESS_UI;
            ext.item_data = 2;
            ext.item_reference = szFoo;
            ext.extension_flags = CMC_EXT_LAST_ELEMENT;

            // include any recipients we've already resolved.

            ulResult = CMCLookUp (lhSession, lpRecips, CMC_LOOKUP_ADDRESSING_UI,
                          (CMC_ui_id)hDlg, &cNewRecips, &lpNewRecips, &ext);

//            cNewRecips = 0; // set to NULL to allow maximum number of names to be allocated
//            ulResult = CMCLookUp (lhSession, lpRecips, CMC_LOOKUP_ADDRESSING_UI,
//                                  (CMC_ui_id)hDlg, &cNewRecips, &lpNewRecips, NULL);

            if (ulResult)
            {
                if (ulResult != CMC_E_USER_CANCEL)
                    MakeMessageBox (hDlg, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);
            }
            else
            {
                if (cNewRecips)
                {
                    PvFree(lpRecips);
                    cRecips = 0;

                    lpRecips = (CMC_recipient far *)PvAlloc(cNewRecips * sizeof(CMC_recipient));

                    while(cRecips < cNewRecips)
                    {
                        if(CopyRecipient(lpRecips, &lpRecips[cRecips],
                                         &lpNewRecips[cRecips]))
                        {
                            PvFree(lpRecips);
                            lpRecips = NULL;
                            cRecips = 0;
                            break;
                        }
                        cRecips++;
                    }

                    CMCFree(lpNewRecips);
                    lpNewRecips = NULL;
                    cNewRecips = 0;

                    MakeDisplayNameStr(szDisplayNames, CMC_ROLE_TO,
                                       cRecips, lpRecips);
                    if (*szDisplayNames)
                        SetDlgItemText (hDlg, IDC_TO, szDisplayNames);

                    MakeDisplayNameStr(szDisplayNames, CMC_ROLE_CC,
                                       cRecips, lpRecips);
                    if (*szDisplayNames)
                        SetDlgItemText (hDlg, IDC_CC, szDisplayNames);

                    SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);
                    SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);
                }
            }
            break;

        case IDC_OPTIONS:
            DialogBox (hInst, "Options", hDlg, OptionsDlgProc);
            break;

        case IDC_SEND:
        case IDC_RESOLVE:
            fUnResTo = FALSE;
            fUnResCc = FALSE;

            hOldCur = SetCursor(hWaitCur);

            //   Get the names from the To: field and resolve them first

            if (SendDlgItemMessage (hDlg, IDC_TO, EM_GETMODIFY, 0, 0) &&
                (cb = SendDlgItemMessage (hDlg, IDC_TO, WM_GETTEXT,
                    (WPARAM)sizeof(szUnResNames),
                    (LPARAM) (LPSTR) szUnResNames)))
            {
                if (!ResolveFriendlyNames (hDlg, szUnResNames, CMC_ROLE_TO,
                        &cRecips, &lpRecips))
                {
                    MakeDisplayNameStr (szDisplayNames, CMC_ROLE_TO,
                        cRecips, lpRecips);
                    if (*szDisplayNames)
                    {
                        if (*szUnResNames)
                        {
                            lstrcat (szDisplayNames, "; ");
                            lstrcat (szDisplayNames, szUnResNames);
                            fUnResTo = TRUE;
                        }

                        SetDlgItemText (hDlg, IDC_TO, szDisplayNames);
                    }
                    else
                    {
                        if (*szUnResNames)
                        {
                            SetDlgItemText (hDlg, IDC_TO, szUnResNames);
                            fUnResTo = TRUE;
                        }
                    }
                }
                SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);
            }

            //   Now, get the names from the Cc: field and resolve them

            if (SendDlgItemMessage (hDlg, IDC_CC, EM_GETMODIFY, 0, 0) &&
                (cb = SendDlgItemMessage (hDlg, IDC_CC, WM_GETTEXT,
                    (WPARAM)sizeof(szUnResNames), (LPARAM)(LPSTR)szUnResNames)))
            {
                if (!ResolveFriendlyNames (hDlg, szUnResNames, CMC_ROLE_CC,
                        &cRecips, &lpRecips))
                {
                    MakeDisplayNameStr (szDisplayNames, CMC_ROLE_CC,
                        cRecips, lpRecips);
                    if (*szDisplayNames)
                    {
                        if (*szUnResNames)
                        {
                            lstrcat (szDisplayNames, "; ");
                            lstrcat (szDisplayNames, szUnResNames);
                            fUnResCc = TRUE;
                        }

                        SetDlgItemText (hDlg, IDC_CC, szDisplayNames);
                    }
                    else
                    {
                        if (*szUnResNames)
                        {
                            SetDlgItemText (hDlg, IDC_CC, szUnResNames);
                            fUnResCc = TRUE;
                        }
                    }
                }
                SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);
            }

            //   If we were just Resolving Names then we can leave now

            if (LOWORD (wParam) == IDC_RESOLVE)
            {
                SetCursor(hOldCur);
                break;
            }

            if (cRecips == 0 || fUnResTo || fUnResCc)
            {
                if (!cRecips)
                    MakeMessageBox (hDlg, 0, IDS_NORECIPS, MBS_OOPS);

                if (fUnResTo)
                    SetFocus (GetDlgItem (hDlg, IDC_TO));
                else if (fUnResCc)
                    SetFocus (GetDlgItem (hDlg, IDC_CC));
                else
                    SetFocus (GetDlgItem (hDlg, IDC_TO));

                SetCursor(hOldCur);
                break;
            }

            //   Everything is OK so far, lets get the Subject
            //   and the text_note and try to send the message.

            //   Get Subject from Edit

            if (SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_GETMODIFY, 0, 0))
            {
                cb = SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_LINELENGTH, 0, 0L);

                CMCFree (lpszSubject);
                lpszSubject = (LPSTR)PvAlloc(cb + 1);
                if (!lpszSubject)
                    goto cleanup;

                GetDlgItemText (hDlg, IDC_SUBJECT, lpszSubject, (int)cb+1);
            }

            //   Get the text_note from Edit

            if (SendDlgItemMessage (hDlg, IDC_NOTE, EM_GETMODIFY, 0, 0))
            {
                cLines = SendDlgItemMessage (hDlg, IDC_NOTE,
                    EM_GETLINECOUNT, 0, 0L);

                if (cLines)
                {
                    //   Get the total number of bytes in the multi-line

                    cb = SendDlgItemMessage (hDlg, IDC_NOTE, EM_LINEINDEX,
                        (WPARAM) cLines - 1, 0L);
                    cb += SendDlgItemMessage (hDlg, IDC_NOTE, EM_LINELENGTH,
                        (WPARAM)cb, 0L);

                    //   The next line is to account for CR-LF pairs per line.

                    cb += cLines * 2;

                    CMCFree (lpszTextNote);
                    lpszTextNote = (LPSTR)PvAlloc(cb + 1);
                    if (!lpszTextNote)
                        goto cleanup;

                    //   Get the Note Text from the edit

                    GetDlgItemText (hDlg, IDC_NOTE, lpszTextNote, (int)cb);

}
                else
                {
                    //   Make an empty string for text_note

                    lpszTextNote = (LPSTR)PvAlloc(1);
                    *lpszTextNote = '\0';
                }
            }

            CmcMsg->subject = lpszSubject;
            CmcMsg->text_note = lpszTextNote;
            CmcMsg->recipients = lpRecips;
            CmcMsg->attachments = lpAttach;
            CmcMsg->message_extensions = extMsgOpt;

            ulResult = CMCSend(lhSession, CmcMsg, CMC_ERROR_UI_ALLOWED,
                               (CMC_ui_id)hDlg, (CMC_extension far *)NULL);

            LogSendMail(ulResult);

            if (ulResult)
            {
                MakeMessageBox (hDlg, ulResult, IDS_SENDERROR, MBS_ERROR);
                SetCursor(hOldCur);
                break;
            }
cleanup:
        case IDCANCEL:
            SetCursor(hOldCur);
            PvFree(CmcMsg->message_type);
            PvFree(CmcMsg);
            PvFree(lpRecips);
            PvFree(lpAttach);
            PvFree(lpszSubject);
            PvFree(lpszTextNote);
            CmcMsg = NULL;

            EndDialog (hDlg, TRUE);
            return TRUE;
            break;

        default:
            break;
        }
        break;
    }
    return FALSE;
}

/*
 -  OptionsDlgProc
 -
 *  Purpose:
 *      Message Options dialog procedure
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
OptionsDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    switch (msg)
    {
    case WM_INITDIALOG:
        CheckDlgButton (hDlg, IDC_RETURN,
            (UINT)(extMsgOpt[1].item_data & (CMC_uint32)CMC_X_MS_MSG_RECEIPT_REQ));

        switch(extMsgOpt[0].item_data)
        {
        case CMC_X_COM_URGENT:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_URGENT);
            break;

        case CMC_X_COM_NORMAL:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_NORMAL);
            break;

        case CMC_X_COM_LOW:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_LOW);
            break;
        }
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD (wParam))
        {
        case IDC_RETURN:
            CheckDlgButton(hDlg, IDC_RETURN,
                         !IsDlgButtonChecked(hDlg, IDC_RETURN));
            break;

        case IDC_URGENT:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_URGENT);
            extMsgOpt[0].item_data = CMC_X_COM_URGENT;
            break;

        case IDC_NORMAL:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_NORMAL);
            extMsgOpt[0].item_data = CMC_X_COM_NORMAL;
            break;

        case IDC_LOW:
            CheckRadioButton(hDlg, IDC_URGENT, IDC_LOW, IDC_LOW);
            extMsgOpt[0].item_data = CMC_X_COM_LOW;
            break;

        case IDOK:
            if (IsDlgButtonChecked (hDlg, IDC_RETURN))
                extMsgOpt[1].item_data |= CMC_X_MS_MSG_RECEIPT_REQ;
            else
                extMsgOpt[1].item_data &= ~CMC_X_MS_MSG_RECEIPT_REQ;

        case IDCANCEL:
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

//
//  InBoxDlgProc
//
//  Purpose:
//      Dialog procedure for the InBox dialog.
//
//  Parameters:
//      hDlg
//      message
//      wParam
//      lParam
//
//  Returns:
//      True/False
//

BOOL FAR PASCAL
InBoxDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    MEASUREITEMSTRUCT* pmis;
    DRAWITEMSTRUCT *pdis;
    CMC_message_summary far * lpMsgSum = NULL;
    LPMSGID lpMsgNode;
    static LPMSGID lpMsgIdList = NULL;
    CMC_return_code ulResult;
    WORD nIndex;
    RECT Rect;
    HCURSOR hOldCur;

    CMC_uint32 iCount = 0;
    CMC_uint32 i;

    switch (msg)
    {
    case WM_INITDIALOG:
        hOldCur = SetCursor(hWaitCur);

        //   Populate List Box with all messages in InBox.

        ulResult = CMCList(lhSession, NULL, (CMC_flags) 0, NULL,
                           &iCount, (CMC_ui_id)hDlg, &lpMsgSum, NULL);

        if (ulResult == CMC_SUCCESS)
        {
            for (i = 0; i < iCount; i++)
            {
                lpMsgNode = MakeMsgNode (&lpMsgSum[i]);

                if (lpMsgNode)
                {
                    InsertMsgNode (lpMsgNode, &lpMsgIdList);

                    SendDlgItemMessage (hDlg, IDC_MSG, LB_ADDSTRING,
                        0, (LONG) lpMsgNode);
                }
            }
            CMCFree (lpMsgSum);
        }
        else
        {
            MakeMessageBox(hDlg, ulResult, IDS_READFAIL, MBS_ERROR);
        }

        SetCursor(hOldCur);
        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        return TRUE;
        break;

    case WM_SETFOCUS:
        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        break;

    case WM_MEASUREITEM:
        //   Sets the height of the owner-drawn List-Box
        pmis = (MEASUREITEMSTRUCT *) lParam;
        pmis->itemHeight = 15;
        break;

    case WM_DRAWITEM:
        pdis = (DRAWITEMSTRUCT *) lParam;
        DrawMsgItem (pdis);
        break;

    case WM_DELETEITEM:
        //   This message is handled by the IDC_DELETE message
        return TRUE;
        break;

    case WM_COMMAND:
        switch (LOWORD (wParam))
        {
        case IDC_NEW:
            hOldCur = SetCursor(hWaitCur);

            ulResult = CMCList(lhSession, NULL, CMC_LIST_UNREAD_ONLY, NULL,
                               &iCount, (CMC_ui_id)hDlg, &lpMsgSum, NULL);

            if (ulResult == CMC_SUCCESS) {
                for (i = 0; i < iCount; i++) {
                    if (!FindNode(lpMsgIdList,lpMsgSum[i].message_reference))
                    {
                        lpMsgNode = MakeMsgNode (&lpMsgSum[i]);

                        if (lpMsgNode)
                        {
                            InsertMsgNode (lpMsgNode, &lpMsgIdList);

                            SendDlgItemMessage (hDlg, IDC_MSG, LB_ADDSTRING,
                                0, (LONG) lpMsgNode);
                        }
                    }
                }
                CMCFree (lpMsgSum);
            }

            SetCursor(hOldCur);
            break;

        case IDC_MSG:
#ifdef _WIN32
            if(HIWORD(wParam) != LBN_DBLCLK)
#else
            if(HIWORD(lParam) != LBN_DBLCLK)
#endif
                break;

        case IDC_READ:
            nIndex = (WORD) SendDlgItemMessage (hDlg, IDC_MSG, LB_GETCURSEL, 0, 0);

            if (nIndex == LB_ERR)
                break;

            lpReadMsgNode = (LPMSGID) SendDlgItemMessage(hDlg, IDC_MSG,
                                                    LB_GETITEMDATA, nIndex, 0L);

            if (lpReadMsgNode == (LPMSGID) LB_ERR || !lpReadMsgNode) // Nothing is selected
                break;
            else
                DialogBox(hInst, "ReadNote", hDlg, ReadMailDlgProc);

            //   Update the Messages List-Box with new icon

            SendDlgItemMessage (hDlg, IDC_MSG, LB_GETITEMRECT,
                                (WPARAM)nIndex, (LPARAM)(RECT far *)&Rect);
            InvalidateRect(GetDlgItem(hDlg, IDC_MSG), &Rect, FALSE);
            break;

        case IDC_DELETE:
            nIndex = (WORD) SendDlgItemMessage (hDlg, IDC_MSG, LB_GETCURSEL,
                                                (WPARAM) 0, (LPARAM) 0);

            if (nIndex == LB_ERR)
                break;

            lpMsgNode = (LPMSGID) SendDlgItemMessage (hDlg, IDC_MSG,
                                                      LB_GETITEMDATA, nIndex, 0);

            if (lpMsgNode == (LPMSGID) LB_ERR || !lpMsgNode) // Nothing is selected
                break;
            else
            {
                ulResult = CMCActOn(lhSession, lpMsgNode->message_reference,
                                    CMC_ACT_ON_DELETE, CMC_ERROR_UI_ALLOWED,
                                    (CMC_ui_id)hDlg, NULL);

                DeleteMsgNode (lpMsgNode, &lpMsgIdList);
            }

            SendDlgItemMessage (hDlg, IDC_MSG, LB_DELETESTRING, nIndex, 0);
            break;

        case IDC_CLOSE:
        case IDCANCEL:
            FreeMsgList (lpMsgIdList);
            lpMsgIdList = NULL;
            EndDialog (hDlg, TRUE);
            return TRUE;
            break;

        default:
            break;
        }
        break;
    }

    return FALSE;
}

//
//  ReadMailDlgProc
//
//  Purpose:
//      Dialog procedure for the ReadMail dilaog.
//
//  Parameters:
//      hDlg
//      message
//      wParam
//      lParam
//
//  Returns:
//      True/False
//

BOOL FAR PASCAL
ReadMailDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    ULONG ulResult;
    char szTo[TO_EDIT_MAX];
    char szCc[TO_EDIT_MAX];
    char szChangeMsg[512];
    ULONG idx;
    static CMC_message far *lpReadMsg;
    char lpszDateDisplay[TO_EDIT_MAX];
    static ULONG cRecips;
    static ULONG cNewRecips;
    static ULONG cAttach;
    static CMC_recipient FAR *lpRecips;
    static CMC_recipient FAR *lpNewRecips;
    static CMC_attachment FAR *lpAttach;
    CMC_return_code rc;
    OFSTRUCT ofs;

    switch (msg)
    {
    case WM_INITDIALOG:
        ulResult = CMCRead(lhSession, lpReadMsgNode->message_reference,
                        CMC_ERROR_UI_ALLOWED, &lpReadMsg, (CMC_ui_id)hDlg, NULL);
        if (ulResult)
        {
            MakeMessageBox(hDlg, ulResult, IDS_READFAIL, MBS_ERROR);
            EndDialog (hDlg, TRUE);
            return TRUE;
        }

        lpReadMsgNode->fRead = TRUE;

        szTo[0] = '\0';
        szCc[0] = '\0';

        idx = 0;
        do
        {
            if (lpReadMsg->recipients[idx].role == CMC_ROLE_TO)
            {
                lstrcat (szTo, lpReadMsg->recipients[idx].name);
                lstrcat (szTo, "; ");
            }
            else if (lpReadMsg->recipients[idx].role == CMC_ROLE_CC)
            {
                lstrcat (szCc, lpReadMsg->recipients[idx].name);
                lstrcat (szCc, "; ");
            }
            else
            {
                //   Must be Bcc, lets ignore it!
            }

        }while(!(lpReadMsg->recipients[idx++].recip_flags & CMC_RECIP_LAST_ELEMENT));

        if(*szTo)
            szTo[lstrlen (szTo) - 2] = '\0';
        if(*szCc)
            szCc[lstrlen (szCc) - 2] = '\0';

        SetDlgItemText (hDlg, IDC_RFROM,
            (lpReadMsgNode->from ? lpReadMsgNode->from : ""));
        ConvertDateRec (&lpReadMsgNode->time_sent,
            lpszDateDisplay);

        SetDlgItemText (hDlg, IDC_RDATE,  (lpszDateDisplay ? lpszDateDisplay : ""));
        SetDlgItemText (hDlg, IDC_RTO, szTo);
        SetDlgItemText (hDlg, IDC_RCC, szCc);
        SetDlgItemText (hDlg, IDC_RSUBJECT, (lpReadMsg->subject ? lpReadMsg->subject : ""));
        SetDlgItemText (hDlg, IDC_READNOTE, (lpReadMsg->text_note ? lpReadMsg->text_note : ""));

        if (!lpReadMsg->attachments)
        {
            EnableWindow (GetDlgItem (hDlg, IDC_SAVEATTACH), FALSE);
            EnableWindow (GetDlgItem (hDlg, IDC_ATTACHMENT), FALSE);
            EnableWindow (GetDlgItem (hDlg, IDT_ATTACHMENT), FALSE);
        }
        else
        {
            idx = 0;
            do
            {
                if (lpReadMsg->attachments[idx].attach_title)
                    SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_ADDSTRING, 0,
                        (LPARAM)lpReadMsg->attachments[idx].attach_title);
            }while(!(lpReadMsg->attachments[idx++].attach_flags & CMC_ATT_LAST_ELEMENT));

            SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_SETCURSEL, 0, 0L);
        }

        SetFocus (GetDlgItem (hDlg, IDC_READNOTE));
        return FALSE;

    case WM_COMMAND:
        switch (LOWORD (wParam))
        {
        case IDC_SAVECHANGES:
            if (SendDlgItemMessage (hDlg, IDC_READNOTE, EM_GETMODIFY, 0, 0))
                ulResult = SaveMsgChanges (hDlg, lpReadMsg);
            SendDlgItemMessage (hDlg, IDC_READNOTE, EM_SETMODIFY, 0, 0);
            break;

        case IDC_ATTACHMENT:
#ifdef _WIN32
            if(HIWORD(wParam) != LBN_DBLCLK)
#else
            if(HIWORD(lParam) != LBN_DBLCLK)
#endif
                break;

        case IDC_SAVEATTACH:
            idx = SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_GETCURSEL, 0, 0L);

            if(idx != LB_ERR)
            {
                SaveFileAttachments(hDlg, &lpReadMsg->attachments[idx]);
                SetFocus(GetDlgItem (hDlg, IDC_ATTACHMENT));
                return FALSE;

            }
            break;

        case IDC_REPLY:
        case IDC_REPLYALL:
        case IDC_FORWARD:
            if(rc = MakeNewMessage (lpReadMsg, LOWORD (wParam)))
                MakeMessageBox(hDlg, rc, IDS_MAKENEWFAIL, MBS_ERROR);

            if(rc = CMCSend(lhSession, CmcMsg, CMC_SEND_UI_REQUESTED | CMC_ERROR_UI_ALLOWED,
                    (CMC_ui_id)hDlg, NULL))
                MakeMessageBox(hDlg, rc, IDS_SENDERROR, MBS_ERROR);

            PvFree(CmcMsg->message_type);
            PvFree(CmcMsg->subject);
            PvFree(CmcMsg->text_note);
            PvFree(CmcMsg->recipients);
            PvFree(CmcMsg->attachments);
            PvFree(CmcMsg);
            CmcMsg = NULL;
            break;

        case IDCANCEL:
            if (SendDlgItemMessage (hDlg, IDC_READNOTE, EM_GETMODIFY, 0, 0))
            {
                wsprintf (szChangeMsg, "Save changes to: '%s' in Inbox?",
                    (lpReadMsg->subject ? lpReadMsg->subject : ""));

                if(MessageBox (hDlg, szChangeMsg, "Mail", MB_YESNO) == IDYES)
                {
                    ulResult = SaveMsgChanges (hDlg, lpReadMsg);
                }
            }

            //   If there were file attachments, then delete the temps

            if(lpReadMsg->attachments)
            {
                idx = 0;
                do
                {
                    if (lpReadMsg->attachments[idx].attach_filename)
                        OpenFile(lpReadMsg->attachments[idx].attach_filename, &ofs, OF_DELETE);

                }while(!(lpReadMsg->attachments[idx++].attach_flags & CMC_ATT_LAST_ELEMENT));
            }

            CMCFree (lpReadMsg);
            lpReadMsg = NULL;
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

//
//  MakeMessageBox
//
//  Purpose:
//      Gets resource string and displays an error message box.
//
//  Parameters:
//      hWnd            - Handle to parent window
//      idString        - Resource ID of message in StringTable
//
//  Returns:
//      Void
//
//

void
MakeMessageBox (HWND hWnd, CMC_return_code ulResult, UINT idString, UINT fStyle)
{
    char szMessage[512];
    char szCmcReturn[256];
    UINT uResult;

    LoadString (hInst, idString, szMessage, sizeof(szMessage)-sizeof(szCmcReturn)-1);

    if (ulResult)
    {
        uResult = (UINT) ulResult;
        LoadString (hInst, uResult, szCmcReturn, sizeof(szCmcReturn)-1);
        lstrcat (szMessage, "\nReturn Code: ");
        lstrcat (szMessage, szCmcReturn);
    }

    MessageBox (hWnd, szMessage, "Problem", fStyle);
}

//
//  ResolveFriendlyNames
//
//  Purpose:
//      Helper function to convert a string of ';' delimited friendly
//      names into an array of CMC_recipients.
//
//  Side Effects:
//      The display string passed in is modified to contain the
//      friendly names of the mail users as found in the sample
//      address book.
//
//  Note:
//      Duplicate names in the address book will result in undefined
//      behavior.
//
//  Parameters:
//      hWnd                - Handle to parent window
//      lpszDisplayNames    - string of ';' delimited user names
//      ulRecipRole         - either CMC_ROLE_TO, CMC_ROLE_CC, or CMC_BCC
//      lpcRecips           - Address of recipient count to be returned
//      lppRecips           - Address of recipient array to be returned
//
//  Return:
//      ulResult

CMC_return_code
ResolveFriendlyNames(HWND hWnd, LPSTR lpszDisplayNames, CMC_enum ulRecipRole,
                     ULONG * lpcRecips, CMC_recipient FAR * * lppRecips)
{
    char szResolve[TO_EDIT_MAX];
    LPSTR lpszNameToken;
    ULONG cRecips = 0;
    ULONG cFails = 0;
    ULONG idx;
    CMC_return_code ulResult;
    CMC_recipient Recip;
    CMC_recipient FAR * lpNewRecip;
    CMC_recipient FAR * lpRecipList;
    CMC_uint32 cNewRecip;

    *szResolve = '\0';
    lpszNameToken = _fstrtok (lpszDisplayNames, ";\n");

    while (lpszNameToken)
    {
        //   Strip leading blanks from name

        while (*lpszNameToken == ' ')
            lpszNameToken++;

        //   Check if name has already been resolved

        if (!FNameInList (lpszNameToken, *lpcRecips, *lppRecips))
        {
            lstrcat (szResolve, lpszNameToken);
            lstrcat (szResolve, "; ");
            cRecips++;
        }

        //   Get Next Token

        lpszNameToken = _fstrtok (NULL, ";\n");
    }

    *lpszDisplayNames = '\0';

    if (!szResolve[0])
    {
        ulResult = CMC_SUCCESS;
        goto err;
    }

    szResolve[lstrlen (szResolve) - 2] = '\0';

    lpRecipList = (CMC_recipient *)PvAlloc((cRecips + *lpcRecips) * sizeof (CMC_recipient));
    if (!lpRecipList)
    {
        ulResult = CMC_E_INSUFFICIENT_MEMORY;
        goto err;
    }
    _fmemset (lpRecipList, 0, (UINT)(cRecips + *lpcRecips) * sizeof (CMC_recipient));

    cRecips = 0;

    while (cRecips < *lpcRecips)
    {
        ulResult = CopyRecipient (lpRecipList, &lpRecipList[cRecips],
            *lppRecips + cRecips);

        if (ulResult)
        {
            PvFree(lpRecipList);
            goto err;
        }

        cRecips++;
    }

    PvFree(*lppRecips);

    lpszNameToken = _fstrtok (szResolve, ";\n");

    while (lpszNameToken)
    {
        //   Strip leading blanks

        while (*lpszNameToken == ' ')
            lpszNameToken++;

        cNewRecip = 1; // just looking for one resolved name
        Recip.name      = lpszNameToken;
        Recip.name_type = CMC_TYPE_INDIVIDUAL;
        Recip.role      = ulRecipRole;
        Recip.recip_flags = CMC_RECIP_LAST_ELEMENT;
        Recip.address     = NULL;
        Recip.recip_extensions = NULL;

        ulResult = CMCLookUp(lhSession, (CMC_recipient far *)&Recip,
                             CMC_LOOKUP_RESOLVE_UI | CMC_ERROR_UI_ALLOWED
                             | CMC_LOOKUP_RESOLVE_PREFIX_SEARCH,
                             (CMC_ui_id)hWnd, &cNewRecip, &lpNewRecip, NULL);

        if (ulResult == CMC_SUCCESS)
        {
            lpNewRecip->role = ulRecipRole;
            ulResult = CopyRecipient (lpRecipList, &lpRecipList[cRecips], lpNewRecip);

            CMCFree(lpNewRecip);

            if(ulResult)
                goto cleanup;

            cRecips++;
        }
        else
        {
            MakeMessageBox (NULL, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);
            lstrcat (lpszDisplayNames, lpszNameToken);
            lstrcat (lpszDisplayNames, "; ");
            cFails++;
        }
        lpszNameToken = _fstrtok (NULL, ";\n");
    }

    //   if cFails > 0 then we have partial success

    ulResult = CMC_SUCCESS;

    if (cFails)
        MakeMessageBox (hWnd, 0, IDS_UNRESOLVEDNAMES, MBS_INFO);

cleanup:
    /* Re-position our LAST_ELEMENT flag in the list */
    if(*lpcRecips = cRecips)
    {
        for(idx = 0; idx < cRecips-1; idx++)
            lpRecipList[idx].recip_flags &= ~CMC_RECIP_LAST_ELEMENT;
        lpRecipList[idx].recip_flags |= CMC_RECIP_LAST_ELEMENT;

        *lppRecips = lpRecipList;
    }
    else
    {
        *lppRecips = NULL;
        PvFree(lpRecipList);
    }

err:
    if (*lpszDisplayNames)
        lpszDisplayNames[lstrlen (lpszDisplayNames) - 2] = '\0';

    return ulResult;
}

//
//  CopyRecipient
//
//  Purpose:
//      Called in support of ResolveFriendlyNames() to build an array
//      of chained CMC_recipients.
//
//  Parameters:
//      lpParent        - Parent memory that allocations get chained to
//      lpDest          - Destination Recipient
//      lpSrc           - Source Recipient
//
//  Return:
//      ulResult

CMC_return_code
CopyRecipient (CMC_recipient FAR * lpParent,
    CMC_recipient FAR * lpDest,
    CMC_recipient FAR * lpSrc)
{
    ULONG cRecipExt,cRE;
    lpDest->role = lpSrc->role;
    lpDest->name_type = lpSrc->name_type;
    lpDest->recip_flags = lpSrc->recip_flags;

    if (lpSrc->name)
    {
        lpDest->name = (CMC_string)PvAllocMore(lstrlen(lpSrc->name) + 1, lpParent);
        if (!lpDest->name)
                return CMC_E_INSUFFICIENT_MEMORY;

        lstrcpy (lpDest->name, lpSrc->name);
    }
    else
        lpDest->name = NULL;

    if (lpSrc->address)
    {
        lpDest->address = (CMC_string)PvAllocMore(lstrlen(lpSrc->address) + 1, lpParent);
        if (!lpDest->address)
            return CMC_E_INSUFFICIENT_MEMORY;

        lstrcpy (lpDest->address, lpSrc->address);
    }
    else
        lpDest->address = NULL;

    if(lpSrc->recip_extensions && (lpSrc->recip_extensions->item_code == CMC_X_COM_RECIP_ID))
    {

        cRecipExt = 0;
        while (!(lpSrc->recip_extensions[cRecipExt].extension_flags & CMC_EXT_LAST_ELEMENT))
            cRecipExt++;

        cRecipExt++;

        lpDest->recip_extensions = (CMC_extension *)PvAllocMore(sizeof(CMC_extension)*cRecipExt, lpParent);
        if(!lpDest->recip_extensions)
               return CMC_E_INSUFFICIENT_MEMORY;

        for (cRE = 0;cRE < cRecipExt;cRE++)
        {

            lpDest->recip_extensions[cRE].item_reference = \
                   (CMC_buffer)PvAllocMore( sizeof(CMC_message_reference)+
                   lpSrc->recip_extensions[cRE].item_data, lpParent);
            if(!lpDest->recip_extensions[cRE].item_reference)
                return CMC_E_INSUFFICIENT_MEMORY;

            lpDest->recip_extensions[cRE].item_code = CMC_X_COM_RECIP_ID;
            lpDest->recip_extensions[cRE].item_data = lpSrc->recip_extensions[cRE].item_data;
            lpDest->recip_extensions[cRE].extension_flags = lpSrc->recip_extensions[cRE].extension_flags;
            if (lpSrc->recip_extensions[cRE].item_reference)
                _fmemcpy(lpDest->recip_extensions[cRE].item_reference,
                    lpSrc->recip_extensions[cRE].item_reference,
                    sizeof(CMC_message_reference)+(size_t)lpSrc->recip_extensions[cRE].item_data);
            else
                // NULL item_reference (BUG?)
                lpDest->recip_extensions[cRE].item_reference = lpSrc->recip_extensions[cRE].item_reference;

        }

    }
    else
        lpDest->recip_extensions = NULL;

    return CMC_SUCCESS;
}

//
//  GetNextFile
//
//  Purpose:
//      Called when user clicks 'Attach' button in Compose Note form.
//      We will build a chained memory chunk for more than one file
//      attachment so the memory can be freed with a single call to
//      CMCFree.
//
//  Parameters:
//      hWnd            - Window handle of Compose Note dialog
//      nPos            - Render position of attachment in Notetext.
//      lpcAttach       - Pointer to the count of attachments.
//      lppAttach       - Pointer to the CMC_attachment array.
//
//  Return:
//      ulResult.

CMC_return_code
GetNextFile (HWND hWnd, ULONG nPos, ULONG * lpcAttach,
    CMC_attachment FAR * * lppAttach)
{
    CMC_attachment FAR * lpAttach;
    CMC_attachment FAR * lpAttachT;
    OPENFILENAME ofn;
    char szAttachFilename[256] = "";
    char szFilter[256];
    static char szFileTitle[16];
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    ULONG idx;
    CMC_return_code ulResult = CMC_SUCCESS;

    if (!szDirName[0])
        GetSystemDirectory ((LPSTR) szDirName, 255);
    else
        lstrcpy (szAttachFilename, szFileTitle);

    LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter));

    for (idx = 0; szFilter[idx] != '\0'; idx++)
        if (szFilter[idx] == '|')
            szFilter[idx] = '\0';

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = (HWND) NULL;
    ofn.hInstance = (HINSTANCE) NULL;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;
    ofn.lpstrFile = szAttachFilename;
    ofn.nMaxFile = 256;
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = 16;
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Attach";
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

    if (!GetOpenFileName (&ofn))
        return CMC_E_USER_CANCEL;

    //   Save the directory for the next time we call this

    lstrcpy (szDirName, szAttachFilename);
    if (lpszEndPath = strstr (szDirName, szFileTitle))
        *(--lpszEndPath) = '\0';

    lpAttach = (CMC_attachment *)PvAlloc((*lpcAttach + 1) * sizeof(CMC_attachment));
    if (!lpAttach)
        goto err;

    _fmemset (lpAttach, 0, ((UINT) *lpcAttach + 1) *sizeof (CMC_attachment));

    lpAttachT = *lppAttach;

    for (idx = 0; idx < *lpcAttach; idx++)
        if(ulResult = CopyAttachment (lpAttach, &lpAttach[idx], &lpAttachT[idx]))
            goto err;

    lpAttach[idx-1].attach_flags &= ~CMC_ATT_LAST_ELEMENT;
    lpAttach[idx].attach_flags = CMC_ATT_LAST_ELEMENT;
    lpAttach[idx].attach_type = NULL;
    lpAttach[idx].attach_extensions = NULL;

    lpAttach[idx].attach_filename = (CMC_string)PvAllocMore(lstrlen(szAttachFilename) + 1,  lpAttach);
    if(!lpAttach[idx].attach_filename)
        goto err;

    lpAttach[idx].attach_title = (CMC_string)PvAllocMore(lstrlen(szFileTitle) + 1, lpAttach);
    if (!lpAttach[idx].attach_title)
        goto err;

    lstrcpy (lpAttach[idx].attach_filename, szAttachFilename);
    lstrcpy (lpAttach[idx].attach_title, szFileTitle);

    PvFree(lpAttachT);

    *lppAttach = lpAttach;
    (*lpcAttach)++;

err:
    if(ulResult)
        PvFree(lpAttach);

    return ulResult;
}

//
//  CopyAttachment
//
//  Purpose:
//      Called in support of GetNextFile() to re-build an array
//      of chained CMC_attachments.
//
//  Parameters:
//      lpParent        - Parent memory that allocations get chained to
//      lpDest          - Destination Recipient
//      lpSrc           - Source Recipient
//
//  Return:
//      Void.

ULONG
CopyAttachment(CMC_attachment FAR * lpParent,
               CMC_attachment FAR * lpDest,
               CMC_attachment FAR * lpSrc)
{
    lpDest->attach_flags = lpSrc->attach_flags;

    if (lpSrc->attach_title)
    {
        lpDest->attach_title = (CMC_string)PvAllocMore(lstrlen(lpSrc->attach_title) + 1, (LPVOID)lpParent);
        if (!lpDest->attach_title)
            return CMC_E_INSUFFICIENT_MEMORY;

        lstrcpy (lpDest->attach_title, lpSrc->attach_title);
    }
    else
        lpDest->attach_title = NULL;

    if (lpSrc->attach_type)
    {
        lpDest->attach_type = (CMC_object_identifier)PvAllocMore(lstrlen(lpSrc->attach_type) + 1, (LPVOID)lpParent);
        if (!lpDest->attach_type)
            return CMC_E_INSUFFICIENT_MEMORY;

        lstrcpy (lpDest->attach_type, lpSrc->attach_type);
    }
    else
        lpDest->attach_type = NULL;

    if (lpSrc->attach_filename)
    {
        lpDest->attach_filename = (CMC_string)PvAllocMore(lstrlen(lpSrc->attach_filename) + 1, (LPVOID)lpParent);
        if (!lpDest->attach_filename)
            return CMC_E_INSUFFICIENT_MEMORY;

        lstrcpy (lpDest->attach_filename, lpSrc->attach_filename);
    }
    else
        lpDest->attach_filename = NULL;

    lpDest->attach_extensions = NULL;

    return CMC_SUCCESS;
}

//
//  FNameInList
//
//  Purpose:
//      To find lpszName in an array of recipients.  Used to determine
//      if user name has already been resolved.
//
//  Parameters:
//      lpszName        - Friendly name to search for

//      cRecips         - Count of recipients in lpRecips
//      lpRecips        - Array of CMC_recipients
//
//  Return:
//      TRUE/FALSE

BOOL
FNameInList (LPSTR lpszName, ULONG cRecips, CMC_recipient FAR * lpRecips)
{
    //   Case sensitive compare of each friendly name in list.

    if (!cRecips || !lpRecips)
        return FALSE;

    while (cRecips--)
        if (!lstrcmp (lpszName, lpRecips[cRecips].name))
            return TRUE;

    return FALSE;
}

//
//  DrawMsgItem
//
//  Purpose:
//      Paint the client area of the owner drawn listbox.
//
//  Parameters:
//      pdis        - Pointer to a DRAWITEMSTRUCT
//      cMsgIds     - Count of MsgId structs in List of current messages
//      lpMsgIds    - Pointer to linked-list of MsgIds
//
//  Returns:
//      void
//

void
DrawMsgItem (DRAWITEMSTRUCT * pdis)
{
    LPMSGID lpMsg;
    HBITMAP hBmp;
    char szDateRec[32];
    HBRUSH hSolidBrush, hOldBrush;

    hSolidBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    hOldBrush = SelectObject(pdis->hDC, hSolidBrush);

    if (ODA_DRAWENTIRE & pdis->itemAction)
    {
        //   Clear the item Rectangle

        PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
            pdis->rcItem.right - pdis->rcItem.left,
            pdis->rcItem.bottom - pdis->rcItem.top, PATCOPY);

        //   Draw the item

        lpMsg = (LPMSGID) pdis->itemData;

        if (lpMsg->fRead)
        {
            if (lpMsg->fHasAttach)
                hBmp = hReadABmp;
            else
                hBmp = hReadBmp;
        }
        else
        {
            if (lpMsg->fHasAttach)
                hBmp = hUnReadABmp;
            else
                hBmp = hUnReadBmp;
        }

        DrawMsgIcon (pdis->hDC, hBmp, pdis->rcItem.left + 2, pdis->rcItem.top,
            pdis->rcItem.left + 18, pdis->rcItem.top + 15);

        // Show originator
        TextOut (pdis->hDC, pdis->rcItem.left + 20, pdis->rcItem.top+2,
            (lpMsg->from ? lpMsg->from : ""),
            lstrlen ((lpMsg->from ? lpMsg->from : "")));

        // Show subject
        TextOut (pdis->hDC, pdis->rcItem.left + 120, pdis->rcItem.top+2,
            (lpMsg->subject ? lpMsg->subject : ""),
            lstrlen ((lpMsg->subject ? lpMsg->subject : "")));

        // Show time sent
        ConvertDateRec (&lpMsg->time_sent, szDateRec);
        TextOut (pdis->hDC, pdis->rcItem.left + 270, pdis->rcItem.top+2,
            szDateRec, lstrlen (szDateRec));

        //   Invert item rectangle if item is selected

        if (ODS_SELECTED & pdis->itemState)
            PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
                pdis->rcItem.right - pdis->rcItem.left,
                pdis->rcItem.bottom - pdis->rcItem.top, DSTINVERT);

        //   Draw a focus rectangle if item has focus

        if (ODS_FOCUS & pdis->itemState)
            DrawFocusRect (pdis->hDC, &pdis->rcItem);
    }
    else
    {
        //   Invert the item if the selection state is changing

        if (ODA_SELECT & pdis->itemAction)
            PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
                pdis->rcItem.right - pdis->rcItem.left,
                pdis->rcItem.bottom - pdis->rcItem.top, DSTINVERT);

        //   Draw a focus if the focus state is changing

        if (ODA_FOCUS & pdis->itemAction)
            DrawFocusRect (pdis->hDC, &pdis->rcItem);
    }

    SelectObject(pdis->hDC, hOldBrush);
    DeleteObject(hSolidBrush);
}

//
//  DrawMsgIcon
//
//  Purpose:
//      Paint the client area of an owner drawn List Box.
//
//  Parameters:
//      hDC         - Device context of button to be drawn on
//      hBitMap     - Handle to a bitmap
//      lx          - Upper left x coordinate
//      ly          - Upper left y coordinate
//      lcx         - Width of paint area
//      lcy         - Height of paint area
//
//  Returns:
//      None.
//

void
DrawMsgIcon (HDC hDC, HBITMAP hBmp, int x, int y, int cx, int cy)
{
    HDC hDCMem;
    HBITMAP hBmpOld;

    hDCMem = CreateCompatibleDC (hDC);
    hBmpOld = SelectObject (hDCMem, hBmp);

    if (hBmpOld)
    {
        BitBlt (hDC, x, y, cx, cy, hDCMem, 0, 0, SRCCOPY);
        SelectObject (hDCMem, hBmpOld);
    }

    DeleteDC (hDCMem);
}

//
//  ConvertDateRec
//
//  Purpose:
//      To convert the CMC_time field of a message to a
//      more paletable display format; namely: mm/dd/yy hh:mmAM.
//
//  Parameters:
//      stime         - Original format
//      lpszDateDisplay     - Display format
//
//  Return:
//      Void.

void
ConvertDateRec (CMC_time far *lpTime, LPSTR lpszDateDisplay)
{
    static char szCentury[2][3] =
    {"19", "20"};

    static char szAMPM[2][3] =
    {"AM", "PM"};

    wsprintf (lpszDateDisplay, "%i/%i/%s%2.2i %i:%2.2i%s",
        lpTime->month+1, // zero based
        lpTime->day,
         szCentury[(lpTime->year > 99 ? 1 : 0)],
        (lpTime->year > 99) ? lpTime->year - 100 : lpTime->year,
        (lpTime->hour == 0) ? 12 : ((lpTime->hour > 12) ? lpTime->hour - 12 : lpTime->hour),
        lpTime->minute,
        szAMPM[(lpTime->hour > 11 ? 1 : 0)]);
}

//
//  MakeMsgNode
//
//  Purpose:
//      Allocate memory for a new MSGID node and initialize its
//      data members to the values passed in.
//
//  Parameters:
//      lpMsg           - Pointer to a CMC_message
//      msgReference       - Opaque message identifier
//
//  Return:
//      lpMsgNode       - Pointer to new node

LPMSGID
MakeMsgNode (CMC_message_summary FAR *lpMsgSummary)
{
    CMC_return_code ulResult;
    LPMSGID lpMsgNode = NULL;
    CMC_message far * lpMessage;

    if (!lpMsgSummary)
        goto err;

    lpMsgNode = (LPMSGID)PvAlloc(sizeof(MSGID));
    if (!lpMsgNode)
        goto err;

    _fmemset (lpMsgNode, 0, sizeof (MSGID));

    // copy message regerence
    lpMsgNode->message_reference = (CMC_message_reference *)PvAllocMore( \
            sizeof(CMC_message_reference)+lpMsgSummary->message_reference->length,
            lpMsgNode);

    _fmemcpy(lpMsgNode->message_reference->string,
             lpMsgSummary->message_reference->string,
             (size_t) lpMsgSummary->message_reference->length);

    lpMsgNode->message_reference->length = lpMsgSummary->message_reference->length;

    // Who is the originator?
    if (lpMsgSummary->originator)
    {
        if (lpMsgSummary->originator->name)
        {
            lpMsgNode->from = (CMC_string)PvAllocMore(lstrlen(lpMsgSummary->originator->name) + 1, lpMsgNode);
            if (!lpMsgNode->from)
                goto err;

            lstrcpy (lpMsgNode->from, lpMsgSummary->originator->name);
        }
        else
        {
            lpMsgNode->from = NULL;
        }
    }
    else
    {
        lpMsgNode->from = NULL;
    }

    // What is the subject?
    if (lpMsgSummary->subject)
    {
        lpMsgNode->subject = (CMC_string)PvAllocMore(lstrlen(lpMsgSummary->subject) + 1, lpMsgNode);
        if (!lpMsgNode->subject)
            goto err;

        lstrcpy (lpMsgNode->subject, lpMsgSummary->subject);
    }
    else
    {
        lpMsgNode->subject = NULL;
    }

    // Time sent
    _fmemcpy(&lpMsgNode->time_sent, &lpMsgSummary->time_sent, sizeof(CMC_time));

    // Query Attachments and Read/Unread status
    ulResult = CMCRead( lhSession, lpMsgSummary->message_reference,
                CMC_MSG_AND_ATT_HDRS_ONLY | CMC_DO_NOT_MARK_AS_READ,
                &lpMessage, (CMC_ui_id)NULL, NULL );

    if ( ulResult )
        goto err;

    if (!ulResult && lpMessage->attachments)
        lpMsgNode->fHasAttach = CMC_TRUE;

    // Has the message been read?
    if (lpMessage->message_flags & CMC_MSG_READ)
        lpMsgNode->fRead = CMC_TRUE;
    else
        lpMsgNode->fRead = CMC_FALSE;

    CMCFree( lpMessage );

    lpMsgNode->lpPrev = NULL;
    lpMsgNode->lpNext = NULL;

    return lpMsgNode;

err:
    PvFree(lpMsgNode);
    return NULL;
}

//
//  InsertMsgNode
//
//  Purpose:
//      Currently (for simplicity) we will insert the nodes
//      at the beginning of the list.  This can later be
//      replaced with a routine that can insert sorted on
//      different criteria, like DateReceived, From, or
//      Subject.  But for now...
//
//  Parameters:
//      lpMsgNode       - Pointer to a MSGID node
//      lppMsgHead      - Pointer to the head of the list
//
//  Return:
//      Void.

void
InsertMsgNode (LPMSGID lpMsgNode, LPMSGID  * lppMsgHead)
{
    if (*lppMsgHead)
    {
        lpMsgNode->lpNext = *lppMsgHead;
        (*lppMsgHead)->lpPrev = lpMsgNode;
    }
    else
        lpMsgNode->lpNext = NULL;

    //   The next 2 assignments are here in case the node came from somewhere
    //   other than a call to MakeMsgNode () in which case we aren't sure
    //   they're already NULL.

    lpMsgNode->lpPrev = NULL;
    *lppMsgHead = lpMsgNode;
}

//
//  DeleteMsgNode
//
//  Purpose:
//      Removes the node passed in from the list.  This
//      may seem like a strange way to do this but it's
//      not, because the Owner-Drawn List Box gives us
//      direct access to elements in the list that makes
//      it easier to do things this way.
//
//  Parameters:
//      lpMsgNode       - Pointer to the MSGID node to delete
//      lppMsgHead      - Pointer to the head of the list
//
//  Return:
//      Void.

void
DeleteMsgNode (LPMSGID lpMsgNode, LPMSGID * lppMsgHead)
{
    if (!lpMsgNode)
        return;

    //   Check if we are the first node

    if (lpMsgNode->lpPrev)
        lpMsgNode->lpPrev->lpNext = lpMsgNode->lpNext;

    //   Check if we are the last node

    if (lpMsgNode->lpNext)
        lpMsgNode->lpNext->lpPrev = lpMsgNode->lpPrev;

    //   check if we are the only node

    if(lpMsgNode == *lppMsgHead)
        *lppMsgHead = NULL;

    PvFree(lpMsgNode);
    return;
}

//
//  FindNode
//
//  Purpose:
//      Returns a pointer to the node containing msgReference.
//      Returns NULL if node doesn't exist or msgReference is NULL.
//
//  Parameters:
//      lpMsgHead       - Pointer to the head of the list
//      msgReference       - Message ID to search for
//
//  Return:
//      lpMsgNode       - Pointer to the node returned

LPMSGID
FindNode (LPMSGID lpMsgHead, CMC_message_reference FAR *msgReference)
{
    if (!msgReference)
        return NULL;

    while (lpMsgHead)
    {
        if ((lpMsgHead->message_reference->length == msgReference->length) &&
                !_fmemcmp((void far *)lpMsgHead->message_reference->string,
                      (void far *)msgReference->string, (size_t)msgReference->length))
            break;

        lpMsgHead = lpMsgHead->lpNext;
    }

    return lpMsgHead;
}

//
//  FreeMsgList
//
//  Purpose:
//      Walks down the MsgList and frees each node.
//
//  Parameters:
//      lpMsgHead       - Pointer to the head of the list
//
//  Return:
//      Void.

void
FreeMsgList (LPMSGID lpMsgHead)
{
    LPMSGID lpT;

    while (lpMsgHead)
    {
        lpT = lpMsgHead;
        lpMsgHead = lpMsgHead->lpNext;
        PvFree(lpT);
    }
}

//
//  MakeDisplayNameStr
//
//  Purpose:
//      Finds all recipients of type ulRecipRole in lpRecips and adds
//      their friendly name to the display string.
//
//  Parameters:
//      lpszDisplay         - Destination string for names
//      ulRecipRole        - Recipient types to search for
//      cRecips             - Count of recipients in lpRecips
//      lpRecips            - Pointer to array of CMC_recipients
//
//  Return:
//      Void.

void
MakeDisplayNameStr (LPSTR lpszDisplay, CMC_enum ulRecipRole,
    ULONG cRecips, CMC_recipient FAR * lpRecips)
{
    ULONG idx;

    *lpszDisplay = '\0';

    for (idx = 0; idx < cRecips; idx++)
    {
        if (lpRecips[idx].role == ulRecipRole)
        {
            lstrcat (lpszDisplay, lpRecips[idx].name);
            lstrcat (lpszDisplay, "; ");
        }
    }

    if (*lpszDisplay)
        lpszDisplay[lstrlen (lpszDisplay) - 2] = '\0';
}

//
//  SaveMsgChanges
//
//  Purpose:
//      If while reading a message the user changes the notetext at all
//      then this function is called to save those changes in the Inbox.
//
//  Parameters:
//      hWnd            - handle to the window/dialog who called us
//      lpMsg           - pointer to the CMC message to be saved
//      msgReference       - ID of the message to save
//
//  Return:
//      ulResult        - Indicating success/failure

CMC_return_code
SaveMsgChanges (HWND hWnd, CMC_message far *lpMsg)
{
    LPSTR lpszT;
    LPSTR lpszTextNote = NULL;
    LONG cLines, cb;
    CMC_return_code ulResult = CMC_SUCCESS;
    CMC_extension   ext;

    lpszT = lpMsg->text_note;

    cLines = SendDlgItemMessage (hWnd, IDC_READNOTE, EM_GETLINECOUNT, 0, 0L);
    cb = (LRESULT) SendDlgItemMessage (hWnd,
                IDC_READNOTE,
                EM_LINEINDEX,
                (WPARAM) cLines - 1,
                0L);
    cb += (LRESULT) SendDlgItemMessage (hWnd, IDC_READNOTE, EM_LINELENGTH, (WPARAM)cb, 0L);
    cb += cLines * 2;

    lpszTextNote = (LPSTR)PvAlloc(cb + 1 );

    if (!lpszTextNote)
        goto err;

    SendDlgItemMessage (hWnd, IDC_READNOTE, WM_GETTEXT,
        (WPARAM) cb, (LPARAM) lpszTextNote);

    lpMsg->text_note = lpszTextNote;

    ext.item_data       = 0;
    ext.item_code       = CMC_X_COM_SAVE_MESSAGE;
    ext.item_reference  = (CMC_buffer)lpMsg;
    ext.extension_flags = CMC_EXT_REQUIRED | CMC_EXT_LAST_ELEMENT;

    ulResult = CMCActOn(lhSession, lpMsg->message_reference, CMC_ACT_ON_EXTENDED,
                        CMC_ERROR_UI_ALLOWED, (CMC_ui_id)hWnd, &ext);

    PvFree(lpszTextNote);

err:
    lpMsg->text_note = lpszT;
    return ulResult;
}

//
//  MakeNewMessage
//
//  Purpose:
//      This function is used to construct a new message for the
//      ComposeNote UI.  This gets called as a result of a Reply,
//      ReplyAll, or a Forward action on a message being read.
//      The destination for the new message is CmcMsg, the global
//      CMC_message struct pointer used by ComposeNoteDlgProc.
//      ComposeNoteDlgProc always frees the memory consumed by
//      this object whether it allocated it or not.
//
//  Parameters:
//      lpSrcMsg            - CMC_message to be copied
//      flType              - Specifies the action that caused this call
//                            either: IDC_REPLY, IDC_REPLYALL, or IDC_FORWARD
//
//  Return:
//      ulResult            - Indicates success/failure

CMC_return_code
MakeNewMessage (CMC_message far *lpSrcMsg, CMC_flags flType)
{

#define REPLY_SEPERATOR "\r\n--------------------------\r\n"

    ULONG cOldRecips;
    ULONG cNewRecips;
    ULONG cAttach;
    ULONG idx;
    CMC_return_code     ulResult = CMC_SUCCESS;
    CMC_uint32          cCurrentUser = 1;
    CMC_recipient FAR * CurrentUser = NULL;

    if (!lpSrcMsg)
        return CMC_E_FAILURE;

    if(CmcMsg)
        PvFree(CmcMsg);

    CmcMsg = (CMC_message far *)PvAlloc(sizeof(CMC_message));

    if (!CmcMsg)
        goto err;

    _fmemset (CmcMsg, 0, sizeof (CMC_message));

    if (lpSrcMsg->subject)
    {
        CmcMsg->subject = (LPSTR)PvAlloc(lstrlen(lpSrcMsg->subject) + 5);
        if (!CmcMsg->subject)
            goto err;

        if (flType == IDC_FORWARD)
            lstrcpy (CmcMsg->subject, "FW: ");
        else
            lstrcpy (CmcMsg->subject, "RE: ");

        lstrcat (CmcMsg->subject, lpSrcMsg->subject);
    }

    if (lpSrcMsg->text_note)
    {
        CmcMsg->text_note = (CMC_string)PvAlloc(lstrlen(lpSrcMsg->text_note) + 32);
        if (!CmcMsg->text_note)
            goto err;

        lstrcpy (CmcMsg->text_note, REPLY_SEPERATOR);
        lstrcat (CmcMsg->text_note, lpSrcMsg->text_note);
    }

    if (lpSrcMsg->message_type)
    {
        CmcMsg->message_type = (CMC_string)PvAlloc(lstrlen(lpSrcMsg->message_type) + 1);
        if (!CmcMsg->message_type)
            goto err;

        lstrcpy (CmcMsg->message_type, lpSrcMsg->message_type);
    }

    if (lpSrcMsg->attachments)
    {
        cAttach = 0;
        while (!(lpSrcMsg->attachments[cAttach].attach_flags & CMC_ATT_LAST_ELEMENT))
            cAttach++;

        cAttach++;

        CmcMsg->attachments = (CMC_attachment far *)PvAlloc(cAttach * sizeof(CMC_attachment));
        if (!CmcMsg->attachments)
            goto err;

        _fmemset (CmcMsg->attachments, 0, (size_t)cAttach*sizeof(CMC_attachment));

        for (idx = 0; idx < cAttach; idx++)
            CopyAttachment (CmcMsg->attachments, &CmcMsg->attachments[idx], &lpSrcMsg->attachments[idx]);

    }

    if (flType == IDC_REPLY || flType == IDC_REPLYALL)
    {
        cOldRecips = 0;

        while (!(lpSrcMsg->recipients[cOldRecips].recip_flags & CMC_RECIP_LAST_ELEMENT))
            cOldRecips++;
        cOldRecips++;

        if(flType == IDC_REPLYALL)
            cNewRecips = cOldRecips;
        else
            cNewRecips = 1;

        CmcMsg->recipients = (CMC_recipient far *)PvAlloc(cNewRecips * sizeof(CMC_recipient));

        if (!CmcMsg->recipients)
            goto err;

        _fmemset (CmcMsg->recipients, 0, (size_t)cNewRecips*sizeof(CMC_recipient));

        // Look up current user address
        ulResult = CMCLookUp(lhSession, NULL, CMC_LOOKUP_RESOLVE_IDENTITY,
                             (CMC_ui_id)NULL, &cCurrentUser, &CurrentUser, NULL);

        cNewRecips = 0;
        for (idx = 0; idx < cOldRecips; idx++)
        {
            // Always Reply to the originator and change their role
            if (lpSrcMsg->recipients[idx].role == CMC_ROLE_ORIGINATOR)
            {
                CopyRecipient(CmcMsg->recipients, &CmcMsg->recipients[cNewRecips],
                              &lpSrcMsg->recipients[idx]);
                CmcMsg->recipients[cNewRecips].role = CMC_ROLE_TO;
                cNewRecips++;
                if(flType == IDC_REPLY)
                    break;
            }
            else if((flType == IDC_REPLYALL) &&
                    (ulResult || lstrcmp(lpSrcMsg->recipients[idx].address,
                                         CurrentUser->address)))
            {
                // Don't reply to ourselves if replying to all
                CopyRecipient(CmcMsg->recipients, &CmcMsg->recipients[cNewRecips],
                              &lpSrcMsg->recipients[idx]);
                cNewRecips++;
            }
        }

        if(cNewRecips)
            CmcMsg->recipients[cNewRecips-1].recip_flags |= CMC_RECIP_LAST_ELEMENT;

        CMCFree(CurrentUser);
    }

    return ulResult;

err:
    PvFree(CmcMsg->subject);
    PvFree(CmcMsg->text_note);
    PvFree(CmcMsg->message_type);
    PvFree(CmcMsg->recipients);
    PvFree(CmcMsg->attachments);
    PvFree(CmcMsg);
    CmcMsg = NULL;

    return ulResult;
}

//
//  LogSendMail
//
//  Purpose:
//      Used to track how many messages were sent with this client.
//      This information is used strictly for gathering stats on
//      how many messages were pumped through the spooler/transport.
//
//  Usage:
//      Add the following to the win.ini file:
//          [CMC Client]
//          LogFile=filepath
//
//      where: filepath can be a full UNC path or some local path & file
//
//  Parameters:
//      ulResult        - Currently unused; should be used to count errors
//
//  Result:
//      Void.

void LogSendMail(ULONG ulResult)
{
    char szLogFile[128];
    char szCount[32];
    OFSTRUCT ofs;
    HFILE hf = HFILE_ERROR;
    int cSent = 1;

    if(!GetProfileString("CMC Client", "LogFile", "CMCcli.log",
            szLogFile, sizeof(szLogFile)))
        return;

    if((hf = OpenFile(szLogFile, &ofs, OF_READWRITE)) == HFILE_ERROR)
    {
        if((hf = OpenFile(szLogFile, &ofs, OF_CREATE|OF_READWRITE)) == HFILE_ERROR)
            return;
    }
    else
    {
        if(!_lread(hf, szCount, sizeof(szCount)))
        {
            _lclose(hf);
            return;
        }

        cSent = atoi(szCount) + 1;
    }

    wsprintf(szCount, "%d", cSent);

    _llseek(hf, 0, 0);

    _lwrite(hf, szCount, lstrlen(szCount));
    _lclose(hf);

    return;
}

//
//  SaveFileAttachments
//
//  Purpose:
//      Displays a 'Save As' common dialog to allow the user to save
//      file attachments contained in the current message.
//
//  Parameters:
//      hWnd            - Window handle of calling WndProc
//      cFiles          - Count of the files in the file array
//      lpFiles         - Array of CMC_attachments
//
//  Return:
//      Void.

void SaveFileAttachments(HWND hWnd, CMC_attachment FAR * lpFile)
{
    OPENFILENAME ofn;
    char szFileName[256];
    char szFilter[256];
    static char szFileTitle[16];
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    ULONG idx;
#ifdef WIN16
    OFSTRUCT ofStrSrc;
    OFSTRUCT ofStrDest;
    HFILE hfSrcFile, hfDstFile;
#endif

    _fmemset(&ofn, 0, sizeof(OPENFILENAME));

    if (!lpFile)
        return;

    if (!szDirName[0])
        GetWindowsDirectory ( szDirName,sizeof(szDirName));

    LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter));

    for (idx = 0; szFilter[idx] != '\0'; idx++)
        if (szFilter[idx] == '|')
            szFilter[idx] = '\0';

    lstrcpy(szFileName, lpFile->attach_title);

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = sizeof(szFileName);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Save Attachment";
    ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;

    if (!GetSaveFileName (&ofn))
        return;

    //   Save the directory for the next time we call this

    lstrcpy (szDirName, szFileName);
    if (lpszEndPath = strstr (szDirName, szFileTitle))
        *(--lpszEndPath) = '\0';

#ifdef WIN16
    // Use CopyLZFile to copy the file.
    // Open, copy, and then close the file.

    hfSrcFile = LZOpenFile(lpFile->attach_filename, &ofStrSrc, OF_READ);

    hfDstFile = LZOpenFile(szFileName, &ofStrDest, OF_CREATE);
    if (LZCopy(hfSrcFile, hfDstFile) < 0)
        MakeMessageBox (hWnd, 0, IDS_SAVEATTACHERROR, MBS_ERROR);
    LZClose(hfSrcFile);
    LZClose(hfDstFile);

#else
    /* Use CopyFile to carry out the operation. */

    if(!CopyFile(lpFile->attach_filename, szFileName, FALSE))
        MakeMessageBox (hWnd, 0, IDS_SAVEATTACHERROR, MBS_ERROR);
#endif

}

//
//  ToggleMenuState
//
//  Purpose:
//      Enables/Disables menu items depending on the session state.
//
//  Parameters:
//      hWnd            - handle to the window/dialog who called us
//      fLoggedOn       - TRUE if logged on, FALSE if logged off
//
//  Return:
//      Void.
//

void ToggleMenuState(HWND hWnd, BOOL fLoggedOn)
{
    EnableMenuItem (GetMenu (hWnd), IDM_LOGOFF,         !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_COMPOSE_CMC,    !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_COMPOSE_CUSTOM, !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_READ,           !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_ADDRBOOK,       !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON,          fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,           FALSE);
}

//
//  SecureMenu
//
//  Purpose:
//      Enables/Disables Logon and Exit menu items.
//      CMCLogon might yield control to Windows, so the user might be able to
//      access the window menu (for example click Logon) after we call
//      CMCLogon, but before it returns.
//
//  Parameters:
//      hWnd            - handle to the window/dialog who called us
//      fBeforeLogon    - TRUE when this function is called when we are about
//                      to call CMCLogon, FALSE if called after logon (failed)
//                      if Logon succeddes ToggleMenuState is called instead of
//                      this function.
//
//  Return:
//      Void.
//

void SecureMenu(HWND hWnd, BOOL fBeforeLogon)
{
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON, fBeforeLogon);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,  fBeforeLogon);
}


XCMCX.H

/*
 -  X C M C X . H
 -
 *  Purpose:
 *      CMC return codes to be used by the string table in the resource file.
 *      This is necessary since the resource compiler can't process IDs with
 *      casts in them.
 *
 */

#define CMC_SUCCESS                         0
#define CMC_E_AMBIGUOUS_RECIPIENT           1
#define CMC_E_ATTACHMENT_NOT_FOUND          2
#define CMC_E_ATTACHMENT_OPEN_FAILURE       3
#define CMC_E_ATTACHMENT_READ_FAILURE       4
#define CMC_E_ATTACHMENT_WRITE_FAILURE      5
#define CMC_E_COUNTED_STRING_UNSUPPORTED    6
#define CMC_E_DISK_FULL                     7
#define CMC_E_FAILURE                       8
#define CMC_E_INSUFFICIENT_MEMORY           9
#define CMC_E_INVALID_CONFIGURATION         10
#define CMC_E_INVALID_ENUM                  11
#define CMC_E_INVALID_FLAG                  12
#define CMC_E_INVALID_MEMORY                13
#define CMC_E_INVALID_MESSAGE_PARAMETER     14
#define CMC_E_INVALID_MESSAGE_REFERENCE     15
#define CMC_E_INVALID_PARAMETER             16
#define CMC_E_INVALID_SESSION_ID            17
#define CMC_E_INVALID_UI_ID                 18
#define CMC_E_LOGON_FAILURE                 19
#define CMC_E_MESSAGE_IN_USE                20
#define CMC_E_NOT_SUPPORTED                 21
#define CMC_E_PASSWORD_REQUIRED             22
#define CMC_E_RECIPIENT_NOT_FOUND           23
#define CMC_E_SERVICE_UNAVAILABLE           24
#define CMC_E_TEXT_TOO_LARGE                25
#define CMC_E_TOO_MANY_FILES                26
#define CMC_E_TOO_MANY_RECIPIENTS           27
#define CMC_E_UNABLE_TO_NOT_MARK_AS_READ    28
#define CMC_E_UNRECOGNIZED_MESSAGE_TYPE     29
#define CMC_E_UNSUPPORTED_ACTION            30
#define CMC_E_UNSUPPORTED_CHARACTER_SET     31
#define CMC_E_UNSUPPORTED_DATA_EXT          32
#define CMC_E_UNSUPPORTED_FLAG              33
#define CMC_E_UNSUPPORTED_FUNCTION_EXT      34
#define CMC_E_UNSUPPORTED_VERSION           35
#define CMC_E_USER_CANCEL                   36
#define CMC_E_USER_NOT_LOGGED_ON            37


README.TXT

Custom Command Extentions

SUMMARY
=======

The Command.Ext sample demonstrates how to implement custom command extensions
for the Microsoft Exchange Client. In particular, it demonstrates:

  - adding a menu command
  - adding a toolbar button
  - disabling/enabling the menu item and toolbar item depending what object
    is selected in the viewer
  - making MAPI calls from an extension
  - extending a command in the Main Viewer and the Search Viewer dialog
  - F1 response for context help on the menu item
  - Implementing custom Tooltip and Status Bar text

The custom command displays the number of subfolders, read messages, and
unread messages in a selected folder.

MORE INFORMATION
================

This sample requires Microsoft Windows NT 3.51 or Windows 95, the MAPI 1.0
PDK, Microsoft Visual C++ version 2.0 (or later), and the Win32 SDK.

To configure Microsoft Exchange to use the client extension, place the
following REG_SZ entry in the system registry in
HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Client\Extensions:

    Sample Command Extension=4.0;c:\<path>\CMDEXT32.dll;1;01010000000000

You can leave out an explicit path to CMDEXT32.DLL if it resides in a
directory listed in the system PATH.

Exchange client extensions are designed using OLE's Component Object Model.
The client calls methods which are provided in the extension. In some
calls to the extension interface, a pointer to a callback interface
(IExchExtCallback) for the extension to call back into the Exchange client.
For more information read "Extending the Microsoft Exchange Client" in the
MAPI PDK documentation.

This sample implements three interface objects: IExchExt, IExchExtCommands,
and IExchExtUserEvents. To extend the command set of Exchange, it is
necessary to provide implementations for IExchExt and IExchExtCommands.
It is optional to provide implementation for IExchExtUserEvents. This sample
implements IExchExtUserEvents to enable or disable the custom command,
depending on what object the user is selecting in the Exchange main viewer.
The menu item is always enabled in the Search Folder Dialog.

The custom command is available in both the Main viewer and the Search
Folder Dialog. The IExchExtCommands provides an interface for the client to
display context help on the custom menu item and to display tooltip text and
status window text.  Select the custom menu item and press F1 to pop-up an
About dialog box.

This extension works in two different contexts, main viewer and search dialog.
For each context an extension supports, a complete set of interface objects
is created. In this sample, the MyExchExt, MyExchExtCommands and
MyExchExtUserEvents objects are created once for the main viewer context and
once for the search dialog. However, because there is only one
implementation for all contexts, there is a context flag data member which
indicates which context Exchange is calling from.

A context in many cases corresponds to a user interface. For example, the
search viewer is one context and the main viewer is another. QueryInterface
is called for each IID for each context. In this example, QueryInterface is
called each time for all IIDs in the main viewer context when
Exchange first starts. QueryInterface is called again each time for all
IIDs in the search viewer context. For each of the contexts, a unique pointer
to the MyExchExtCommands and MyExchExtUserEvents objects is returned to
Exchange in the QueryInterface call. Then when Exchange is calling
DoCommands from the search viewer, it is calling the one using the interface
pointer returned to it through QueryInterface in the search viewer context.
And when Exchange is calling DoCommands from the main viewer, it is calling
the one using the interface pointer returned to it through QueryInterface in
the main viewer context.


CMDEXT.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      CMDEXT.H
//
//  Description
//      Interface Methods are declared in this file for cmdext.cpp.
//
//  Author
//      Gary Peluso
//
//  Revision: 1.00
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#ifndef _CMDEXT_H_
#define _CMDEXT_H_

#include <WINDOWS.H>
#include <COMMCTRL.H>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>
#include <INITGUID.H>
#include <MAPIGUID.H>
#include <EXCHEXT.H>

#include "RESOURCE.H"

///////////////////////////////////////////////////////////////////////////////
//    prototypes
void GetFolderStats(LPMAPIFOLDER lpFolder, ULONG FAR * ulSubFolders,
                    ULONG FAR * ulReadMsgs, ULONG FAR * ulUnReadMsgs);

///////////////////////////////////////////////////////////////////////////////
//    forward declarations
class MyExchExt;
class MyExchExtCommands;
class MyExchExtUserEvents;

extern "C"
{
    LPEXCHEXT CALLBACK ExchEntryPoint(void);
}

class MyExchExt : public IExchExt
{

 public:
    MyExchExt ();
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};
    STDMETHODIMP Install (LPEXCHEXTCALLBACK pmecb,
                        ULONG mecontext, ULONG ulFlags);

 private:
    ULONG m_cRef;
    UINT  m_context;

    MyExchExtCommands * m_pExchExtCommands;
    MyExchExtUserEvents * m_pExchExtUserEvents;
};

class MyExchExtCommands : public IExchExtCommands
{
 public:
    MyExchExtCommands ()
        { m_cRef = 0; m_context = 0;
          m_cmdid = 0;  m_itbb = 0; m_itbm = 0;  m_hWnd = 0; };
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};
    STDMETHODIMP InstallCommands(LPEXCHEXTCALLBACK pmecb,
                                HWND hwnd, HMENU hmenu,
                                UINT FAR * cmdidBase, LPTBENTRY lptbeArray,
                                UINT ctbe, ULONG ulFlags);
    STDMETHODIMP DoCommand(LPEXCHEXTCALLBACK pmecb, UINT mni);
    STDMETHODIMP_(VOID) InitMenu(LPEXCHEXTCALLBACK pmecb);
    STDMETHODIMP Help(LPEXCHEXTCALLBACK pmecb, UINT mni);
    STDMETHODIMP QueryHelpText(UINT mni, ULONG ulFlags, LPTSTR sz, UINT cch);
    STDMETHODIMP QueryButtonInfo(ULONG tbid, UINT itbb, LPTBBUTTON ptbb,
                                LPTSTR lpsz, UINT cch, ULONG ulFlags);
    STDMETHODIMP ResetToolbar(ULONG tbid, ULONG ulFlags);

    inline VOID SetContext
                (ULONG eecontext) { m_context = eecontext; };
    inline UINT GetCmdID() { return m_cmdid; };

 private:
    ULONG m_cRef;
    ULONG m_context;

    UINT  m_cmdid;     // cmdid for menu extension command

    UINT  m_itbb;      // toolbar
    UINT  m_itbm;

    HWND  m_hWnd;

};

class MyExchExtUserEvents : public IExchExtUserEvents
{
 public:
    MyExchExtUserEvents() { m_cRef = 0; m_context = 0;
                            m_pExchExt = NULL; };
    STDMETHODIMP QueryInterface
                (REFIID                     riid,
                 LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                () { ULONG ulCount = --m_cRef;
                     if (!ulCount) { delete this; }
                     return ulCount;};

    STDMETHODIMP_(VOID) OnSelectionChange(LPEXCHEXTCALLBACK pmecb);
    STDMETHODIMP_(VOID) OnObjectChange(LPEXCHEXTCALLBACK pmecb);

    inline VOID SetContext
                (ULONG eecontext) { m_context = eecontext; };
    inline VOID SetIExchExt
                (MyExchExt * pExchExt) { m_pExchExt = pExchExt; };

 private:
    ULONG m_cRef;
    ULONG m_context;

    MyExchExt * m_pExchExt;

};

#endif // _CMDEXT_H_


CMDEXT.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      CMDEXT.CPP
//
//  Description
//      Implementation of interface objects
//
//      IExchExt interface methods:
//          MyExchExt::QueryInterface()
//          MyExchExt::AddRef()
//          MyExchExt::Release()
//
//          MyExchExt::Install()
//
//      IExchExtCommands interface methods:
//          MyExchExtCommands::QueryInterface()
//          MyExchExtCommands::AddRef()
//          MyExchExtCommands::Release()
//
//          MyExchExtCommands::InstallCommands()
//          MyExchExtCommands::DoCommand()
//          MyExchExtCommands::InitMenu()
//          MyExchExtCommands::Help()
//          MyExchExtCommands::QueryHelpText()
//          MyExchExtCommands::QueryButtonInfo()
//          MyExchExtCommands::ResetToolbar()
//
//          MyExchExtCommands::SetContext()
//          MyExchExtCommands::GetCmdID()
//
//      IExchExtUserEvents interface methods:
//          MyExchExtUserEvents::QueryInterface()
//          MyExchExtUserEvents::AddRef()
//          MyExchExtUserEvents::Release()
//
//          MyExchExtUserEvents::OnSelectionChange()
//          MyExchExtUserEvents::OnObjectChange()
//
//          MyExchExtUserEvents::SetContext()
//          MyExchExtUserEvents::SetIExchExt()
//
//  Author
//      Gary Peluso
//
//  Revision: 1.00
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMAPIFolder

#include "CMDEXT.H"

///////////////////////////////////////////////////////////////////////////////
// data
static HINSTANCE ghInstDLL = NULL;  // instance handle of DLL

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: DLLMain()
//
//    Purpose
//    Do initilization processesing
//
//    Return Value
//    TRUE - DLL successfully loads and LoadLibrary will succeed.
//    FALSE - will cause an Exchange error message saying it cannot locate
//            the extension DLL.
//
//    Comments
//    We only need to get a copy of the DLL's HINSTANCE
//
BOOL WINAPI DllMain(
    HINSTANCE  hinstDLL,
    DWORD  fdwReason,
    LPVOID  lpvReserved)
{
 if (DLL_PROCESS_ATTACH == fdwReason)
 {
    ghInstDLL = hinstDLL;
 }
 return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: ExchEntryPoint
//
//    Parameters - none
//
//    Purpose
//    The entry point which Exchange calls.
//
//    Return Value
//    Pointer to Exchange Extension Object
//
//    Comments
//    This is called for each context entry.  Create a new MyExchExt object
//    every time so each context will get its own MyExchExt interface.
//
LPEXCHEXT CALLBACK ExchEntryPoint(void)
{
    return new MyExchExt;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::MyExchExt()
//
//    Parameters - none
//
//    Purpose
//    Constructor. Initialize members and create supporting interface objects
//
//    Comments
//    Each context of Exchange gets its own set of interface objects.
//    Furthermore, interface objects per context are kept track of by Exchange
//    and the interface methods are called in the proper context.
//
MyExchExt::MyExchExt ()
{
  m_cRef = 1;
  m_context = 0;

  m_pExchExtCommands = new MyExchExtCommands;
  m_pExchExtUserEvents = new MyExchExtUserEvents;

  // in MyExchExtUserEvents methods I need a reference to MyExchExt
  m_pExchExtUserEvents->SetIExchExt(this);

}

///////////////////////////////////////////////////////////////////////////////
//  IExchExt virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Called by Exchage to request for interfaces
//
//    Return Value
//    S_OK  -- interface is supported and returned in ppvObj pointer
//    E_NOINTERFACE -- interface is not supported and ppvObj is NULL
//
//    Comments
//    Exchange client calls QueryInterface for each object.  Only
//    Need to support objects that apply to the extension.  QueryInterface
//    is called onces for each IID for each context.  We support two
//    contexts in this example so QueryInterface is called twice for
//    each IID.
//
STDMETHODIMP MyExchExt::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    HRESULT hr = S_OK;

    *ppvObj = NULL;

    if ( (IID_IUnknown == riid) || (IID_IExchExt == riid) )
    {
        *ppvObj = (LPUNKNOWN)this;
    }
    else if ( IID_IExchExtCommands == riid)
    {
        *ppvObj = (LPUNKNOWN)m_pExchExtCommands;
        m_pExchExtCommands->SetContext(m_context);
    }
    else if ( IID_IExchExtUserEvents == riid)
    {
        *ppvObj = (LPUNKNOWN)m_pExchExtUserEvents;
        m_pExchExtUserEvents->SetContext(m_context);
    }
    else
        hr = E_NOINTERFACE;

    if (NULL != *ppvObj)
        ((LPUNKNOWN)*ppvObj)->AddRef();

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::Install()
//
//    Parameters
//    peecb     -- pointer to Exchange Extension callback function
//    eecontext -- context code at time of being called.
//    ulFlags   -- flag to say if install is for modal or not
//
//    Purpose
//    Called once for each new contexted that is entered.  Proper version
//    number is checked here.
//
//    Return Value
//    S_OK -- object supported in the requested context
//    S_FALSE -- object is not supported in teh requested context
//
//    Comments
//
 STDMETHODIMP MyExchExt::Install (LPEXCHEXTCALLBACK pmecb,
                         ULONG mecontext, ULONG ulFlags)
{
    ULONG ulBuildVersion;
    HRESULT hr;

    m_context = mecontext;

    // make sure this is the right version
    pmecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
    if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion & EECBGV_BUILDVERSION_MAJOR_MASK))
        return S_FALSE;

    switch (mecontext)
    {
        case EECONTEXT_VIEWER:
        case EECONTEXT_SEARCHVIEWER:
            hr = S_OK;
            break;
        default:
            hr = S_FALSE;
            break;
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//  IExchExtCommands virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Exchange Client does not call IExchExtCommands::QueryInterface().
//    So return nothing.
//
//    Return Value - none
//

STDMETHODIMP MyExchExtCommands::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    *ppvObj = NULL;
    if ( (riid == IID_IExchExtCommands) || (riid == IID_IUnknown) )
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::InstallCommands()
//
//    Parameters
//    pmecb  -- Exchange Callback Interface
//    hWnd   -- window handle to main window of context
//    hMenu  -- menu handle to main menu of context
//    lptbeArray -- array of toolbar button entries
//    ctbe   -- count of button entries in array
//    ulFlags -- reserved
//
//    Purpose
//    This function is called when commands are installed for each context
//    the extension services.
//
//    Return Value
//    S_FALSE means the commands have been handled.
//
//    Comments
//    The hWnd and hMenu are in context.  If the context is for the SENDNOTE
//    dialog, then the hWnd is the window handle to the dialog and the hMenu is
//    the main menu of the dialog.
//
//    Call ResetToolbar so that Exchange will show it's toolbar
//

STDMETHODIMP MyExchExtCommands::InstallCommands(LPEXCHEXTCALLBACK pmecb,
                            HWND hWnd, HMENU hMenu,
                            UINT FAR * pcmdidBase, LPTBENTRY lptbeArray,
                            UINT ctbe, ULONG ulFlags)
{
 HRESULT hr = S_FALSE;
 HMENU hMenuTools;

 if ((EECONTEXT_SEARCHVIEWER == m_context) || (EECONTEXT_VIEWER == m_context))
 {

  // -------  install the new menu command, append to end of Tools menu. ----
  m_hWnd = hWnd;

  pmecb->GetMenuPos(EECMDID_ToolsCustomizeToolbar, &hMenuTools, NULL, NULL, 0);

  // add a menu separator
   AppendMenu(hMenuTools, MF_SEPARATOR, 0, NULL);

  // add our extension command
   AppendMenu(hMenuTools,
         MF_BYPOSITION | MF_STRING,
         *pcmdidBase,
         "Folder Stats");

     m_cmdid = *pcmdidBase;

     (*pcmdidBase)++;

 }

 if (EECONTEXT_VIEWER == m_context)
 {

  // --------------  install the new toolbar button --------------

  // walk through the toolbars and find the standard toolbar

  int tbindx;
  HWND hwndToolbar = NULL;
  for (tbindx = ctbe-1; (int) tbindx > -1; --tbindx)
  {
   if (EETBID_STANDARD == lptbeArray[tbindx].tbid)
   {
    hwndToolbar = lptbeArray[tbindx].hwnd;
    m_itbb = lptbeArray[tbindx].itbbBase++;
    break;
   }
  }

  // add out button's bitmap to the toolbar's set of buttons
  if (hwndToolbar)
  {
   TBADDBITMAP tbab;

   tbab.hInst = ghInstDLL;
   tbab.nID = IDB_EXTBTN;
   m_itbm = SendMessage(hwndToolbar, TB_ADDBITMAP, 1, (LPARAM)&tbab);
  }

 }

 return hr;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::DoCommand()
//
//    Parameters
//    pmecb -- pointer to Exchange Callback Interface
//
//    Purpose
//    This method is called by Exchange for each WM_COMMAND is sent to the
//    window in context.
//
//    Return Value
//    S_OK if command is handled
//    S_FALSE if command is not handled
//
//    Comments
//    Use this function to either respond to the command item (menu or toolbar)
//    added or modify an existing command in Exchange.  Return S_OK to let
//    Exchange know the command was handled.  Return S_OK on commands you are
//    taking over from Exchange.  Return S_FALSE to let Exchange know you want
//    it to carry out its command, even if you modify its action.
//

STDMETHODIMP MyExchExtCommands::DoCommand(LPEXCHEXTCALLBACK pmecb, UINT cmdid)
{
 static char szBuffer[200];
 ULONG uNumSelected = 0;
 ULONG ulSubFolders = 0;
 ULONG ulReadMsgs = 0;
 ULONG ulUnReadMsgs = 0;
 LPMDB lpMDB = NULL;
 LPMAPIFOLDER lpFolder = NULL;
 LPMAPISESSION lpSession = NULL;
 LPADRBOOK lpAddrBook = NULL;
 ULONG cbeid;
 LPENTRYID lpeid = NULL;
 ULONG ulType;
 char szMsgClass[50];
 ULONG cbMsgClass;
// ULONG ulMsgFlags;

 HRESULT hr = S_FALSE;   // assume it is not our command

 if (m_cmdid != cmdid)
    return hr;           // not our command

 if (m_context == EECONTEXT_VIEWER)
 {

    cbMsgClass = 49;
    szMsgClass[0] = '\0';

    hr = pmecb->GetSession(&lpSession, &lpAddrBook);
    if (FAILED(hr))
    {
        hr = S_OK;     // we still handled the command
        goto error_return;
    }

    hr = pmecb->GetSelectionItem(0, &cbeid, &lpeid, &ulType, NULL,
        0, NULL, 0);
    if (FAILED(hr))
    {
        hr = S_OK;     // we still handled the command
        goto error_return;
    }

    if (ulType != MAPI_FOLDER)          // should never get this
    {
        hr = S_OK;     // we still handled the command
        goto error_return;
    }

    hr = lpSession->OpenEntry(cbeid, lpeid, NULL, 0, &ulType,
                         (LPUNKNOWN FAR *)&lpFolder);
    if (FAILED(hr))
    {
        hr = S_OK;     // we still handled the command
        goto error_return;
    }

    if (NULL == lpFolder)
    {
        hr = S_OK;     // we still handled the command
        goto error_return;              // no folder, not going to continue
    }

    GetFolderStats(lpFolder, &ulSubFolders, &ulReadMsgs, &ulUnReadMsgs);
    wsprintf(szBuffer, "Number of subfolders: %ld\n"
                       "Number of read messages: %ld\n"
                       "Number of unread messages: %ld",
                       ulSubFolders, ulReadMsgs, ulUnReadMsgs);

    MessageBox(m_hWnd, szBuffer, "Sample Extension - Folder Stats", MB_OK);

    hr = S_OK;

 }

 // must use a different technique in Find window
 if (m_context == EECONTEXT_SEARCHVIEWER)
 {

    pmecb->GetObject(&lpMDB, (LPMAPIPROP FAR *)&lpFolder);

    if (lpFolder == NULL)
    {
        hr = S_OK;     // we still handled the command
        goto error_return;              // no folder, not going to continue
    }

    GetFolderStats(lpFolder, &ulSubFolders, &ulReadMsgs, &ulUnReadMsgs);
    wsprintf(szBuffer, "Number of subfolders: %d\n"
                       "Number of read messages: %d\n"
                       "Number of unread messages: %d",
                       ulSubFolders, ulReadMsgs, ulUnReadMsgs);

    MessageBox(m_hWnd, szBuffer, "Sample Extension - Folder Stats", MB_OK);

    hr = S_OK;

 }

error_return:

    MAPIFreeBuffer(lpeid);

    if (NULL != lpSession)
        lpSession->Release();

    if (NULL != lpAddrBook)
        lpAddrBook->Release();

    if (NULL != lpMDB)
        lpMDB->Release();

    if (NULL != lpFolder)
        lpFolder->Release();

 return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::InitMenu()
//
//    Parameters
//    pmecb -- pointer to Exchange Callback Interface
//
//    Purpose
//    This method is called by Exchange when the menu of context is about to
//    be activated.  See WM_INITMENU in the Windows API Reference for more
//    information.
//
//    Return Value - none
//

STDMETHODIMP_(VOID) MyExchExtCommands::InitMenu(LPEXCHEXTCALLBACK pmecb)
{
 // do nothing

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::Help()
//
//    Parameters
//    pmecb -- pointer to Exchange Callback Interface
//    cmdid -- command id
//
//    Purpose
//    Respond when user presses F1 while custom menu item is selected.
//
//    Return Value
//    S_OK -- recognized the command and provided help
//    S_FALSE -- not our command and we didn't provide help
//

STDMETHODIMP MyExchExtCommands::Help(LPEXCHEXTCALLBACK pmecb, UINT cmdid)
{
 HRESULT hr;

 if (cmdid == m_cmdid)
 {

   MessageBox(m_hWnd, "Called through IExchExtCommands::Help()\n"
                      "Contained in cmdext32.dll\n\n"
                      "Copyright (c) 1995 Microsoft Corporation.\n"
                      "All rights reserved.",
                      "About Folder Stats Sample Extension", MB_OK);

   hr = S_OK;
 }
 else
   hr = S_FALSE;

 return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::QueryHelpText()
//
//    Parameters
//    cmdid -- command id corresponding to menu item activated
//    ulFlags -- identifies either EECQHT_STATUS or EECQHT_TOOLTIP
//    psz -- pointer to buffer to be populated with text to display
//    cch -- count of characters available in psz buffer
//
//    Purpose
//    Exchange calls this function each time it requires to update the status
//    bar text or if it is to display a tooltip on the toolbar.
//
//    Return Value
//    S_OK to indicate our command was handled
//    S_FALSE to tell Exchange it can continue with its function
//

STDMETHODIMP MyExchExtCommands::QueryHelpText(UINT cmdid, ULONG ulFlags,
                                              LPTSTR psz, UINT cch)
{

 HRESULT hr;

 if (cmdid == m_cmdid)
 {
  if (ulFlags == EECQHT_STATUS)
    lstrcpyn(psz, "Display Stats on Current Folder", cch);

  if (ulFlags == EECQHT_TOOLTIP)
    lstrcpyn(psz, "Folder Stats", cch);

  hr = S_OK;

 }
 else
  hr = S_FALSE;

 return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::QueryButtonInfo()
//
//    Parameters
//    tbid    -- toolbar identifier
//    itbb    -- toolbar button index
//    ptbb    -- pointer to toolbar button structure -- see TBBUTTON structure
//    lpsz    -- point to string describing button
//    cch     -- maximum size of lpsz buffer
//    ulFlags -- EXCHEXT_UNICODE may be specified
//
//    Purpose
//    For Exchange to find out about toolbar button information.
//
//    Return Value
//    S_FALSE - not our button
//    S_OK    - we filled information about our button
//
//    Comments
//    Called for every button installed for toolbars when IExchExtCommands
//    is installed for each context. The lpsz text is used when the Customize
//    Toolbar dialog is displayed.  The text will be displayed next to the
//    button.
//

STDMETHODIMP MyExchExtCommands::QueryButtonInfo (ULONG tbid, UINT itbb,
                            LPTBBUTTON ptbb, LPTSTR lpsz, UINT cch,
                            ULONG ulFlags)
{
 HRESULT hr = S_FALSE;

  if (m_itbb == itbb)
  {
   ptbb->iBitmap = m_itbm;             // see InstallCommands in this source file
   ptbb->idCommand = m_cmdid;
   ptbb->fsState = TBSTATE_ENABLED;
   ptbb->fsStyle = TBSTYLE_BUTTON;
   ptbb->dwData = 0;
   ptbb->iString = -1;
   lstrcpyn(lpsz, "Folder Stats Toolbar Button", cch);

   hr = S_OK;
  }
 return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtCommands::ResetToolbar()
//
//    Parameters
//    tbid
//    ulFlags
//
//    Purpose
//
//    Return Value  S_OK always
//
STDMETHODIMP MyExchExtCommands::ResetToolbar(ULONG tbid, ULONG ulFlags)
{
 return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//  IExchExtUserEvents virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtUserEvents::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Exchange Client does not call IExchExtUserEvents::QueryInterface().
//    So return nothing.
//
//    Return Value - none
//

STDMETHODIMP MyExchExtUserEvents::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    *ppvObj = NULL;
    if (( riid == IID_IExchExtUserEvents) || (riid == IID_IUnknown) )
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtUserEvents::OnSelectionChange()
//
//    Parameters
//    pmecb  -- pointer to Exchange Callback Object
//
//
//    Purpose
//    This function is called when the selection in the UI is changed.
//
//    Return Value - none
//
//    Comments
//    OnSelectionChange is called whenever the selection changes either within
//    a pane or is changed between panes.
//

STDMETHODIMP_(VOID) MyExchExtUserEvents::OnSelectionChange(LPEXCHEXTCALLBACK pmecb)
{
 static ULONG cbeid;
 static LPENTRYID lpeid = NULL;
 static ULONG ulType;
 static char szMsgClass[50];
 ULONG cbMsgClass;
 ULONG ulMsgFlags;
 HMENU hMenu;
 HRESULT hr;
 UINT cmdid;
 MyExchExtCommands * pExchExtCommands = NULL;

 // do not care about enabling or disabling menu item if in the search viewer
 if (m_context == EECONTEXT_SEARCHVIEWER)
    return;

 cbMsgClass = 49;
 szMsgClass[0] = '\0';

 hr = pmecb->GetSelectionItem(0, &cbeid, &lpeid, &ulType, szMsgClass,
        cbMsgClass, &ulMsgFlags, 0);

 if (FAILED(hr))
 {
  goto error_return;
 }

 pmecb->GetMenu(&hMenu);

 hr = m_pExchExt->QueryInterface(IID_IExchExtCommands, (LPVOID *)&pExchExtCommands);
 if (FAILED(hr))
 {
    goto error_return;
 }

 cmdid = pExchExtCommands->GetCmdID();
 pExchExtCommands->Release();
 pExchExtCommands = NULL;

 // enable or disable the command depending if the selected object is
 // a folder or a "non-folder"  Enable if object is a folder.
 if (ulType == MAPI_FOLDER)
 {
    EnableMenuItem(hMenu, cmdid, MF_BYCOMMAND | MF_ENABLED);
 }
 else
 {
    EnableMenuItem(hMenu, cmdid, MF_BYCOMMAND | MF_GRAYED);
 }

error_return:

 if (lpeid != NULL)
    MAPIFreeBuffer(lpeid);

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtUserEvents::OnObjectChange()
//
//    Parameters
//    pmecb  -- pointer to Exchange Callback Object
//
//
//    Purpose
//    This function is called when the selection in the UI is to a different
//    of object on the left pane.
//
//    Return Value - none
//
//    Comments
//    OnObjectChange is called whenever the selection is changed between
//    objects in the left pane only.  Change in selection between folders,
//    subfolders or container object in the left pane will be reflected with a
//    call to OnObjectChange.  Change in selection between objects (messages,
//    subfolders) in the right pane will not call OnObjectChange, only
//    OnSelectionChange.
//

STDMETHODIMP_(VOID) MyExchExtUserEvents::OnObjectChange(LPEXCHEXTCALLBACK pmecb)
{
 // no need to handle this one
}

///////////////////////////////////////////////////////////////////////////////
//  Helper functions to accomplish this extension's task
//

///////////////////////////////////////////////////////////////////////////////
//    GetFolderStats()
//
//    Parameters
//    lpFolder      -- pointer to Folder of which to get statistics
//    pulFolder     -- pointer to buffer to be filled with number of subfolders
//    pulReadMsgs   -- pointer to buffer to be filled with number of read
//                     messages
//    pulUnReadMsgs -- pointer to buffer to be filled with number of unread
//                     messages
//
//    Purpose
//    This function gathers information information about the given message
//    folder.  It calculates the number of subfolders, read messages, and
//    unread messages.
//
//    Return Value - none
//
//    Comments
//    The Find window results folder only contains messages and not subfolders
//    so it sets the number of subfolders to zero
//

void GetFolderStats(LPMAPIFOLDER lpFolder, ULONG FAR * pulSubFolders,
                    ULONG FAR * pulReadMsgs, ULONG FAR * pulUnReadMsgs)
{
 HRESULT hr = 0;
 LPMAPITABLE lpTable = NULL;
 ULONG ulRows = 0;
 UINT u;
 LPSRowSet lpRows = NULL;

 enum { MESSAGE_FLAGS, STATTAGS };

 SizedSPropTagArray(STATTAGS,  MsgTags) =
      { STATTAGS,
        {
         PR_MESSAGE_FLAGS        // contains read/unread flags
        }
      };

 hr = lpFolder->GetHierarchyTable(0, &lpTable);   // get table of subfolders
 if (MAPI_E_NO_SUPPORT == hr)                     // Find window folder doesn't
    *pulSubFolders = 0;                           // support GetHierarchyTable

 else if (S_OK != hr)              // some other error
    goto error_return;

 if (lpTable != NULL)
 {
    lpTable->GetRowCount(0, pulSubFolders);
    lpTable->Release();
    lpTable =NULL;
 }

 hr = lpFolder->GetContentsTable(0, &lpTable);    // get table of messages
 if (S_OK != hr)
    goto error_return;

 hr = lpTable->SetColumns((LPSPropTagArray)&MsgTags, 0);
 if (S_OK != hr)
    goto error_return;

 lpTable->GetRowCount(0, &ulRows);
 hr = lpTable->QueryRows(ulRows, 0, &lpRows);
 if (S_OK != hr)
    goto error_return;

 *pulReadMsgs = 0;
 *pulUnReadMsgs = 0;

 for (u=0; u<ulRows; u++)
 {

  if (MSGFLAG_READ & lpRows->aRow[u].lpProps[MESSAGE_FLAGS].Value.ul)
    (*pulReadMsgs)++;
  else
    (*pulUnReadMsgs)++;

 }

error_return:

 if (NULL != lpTable)
    lpTable->Release();

 if (NULL != lpRows)
    FreeProws(lpRows);

}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by cmdext.rc
//
#define IDB_EXTBTN                      101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


MAPIBASE.TXT

; DLL memory map for MAPI sample providers
;
; NOTE: when adding your own provider to this file, DO NOT
; simply overwrite or copy and paste one of these lines.
; Use a different base address, so that your provider does not
; try to load at the same address as an existing provider DLL.
;

SMPMS32     0x6E000000      0x80000
SMPAB32     0x6E080000      0x70000
SMPXP32     0x6E0F0000      0x80000
SMH32       0x6E170000      0x60000


_TAGS.H

/*
 *  _ T A G S . H
 *
 *  Property tag definitions for standard properties of MAPI
 *  objects.
 *
 *  The following ranges should be used for all property IDs. Note that
 *  property IDs for objects other than messages and recipients should
 *  all fall in the range 0x3000 to 0x3FFF:
 *
 *  From    To      Kind of property
 *  --------------------------------
 *  0001    0BFF    MAPI_defined envelope property
 *  0C00    0DFF    MAPI_defined per-recipient property
 *  1000    2FFF    MAPI_defined message content property
 *
 *  3000    3FFF    MAPI_defined property (usually not message or recipient)
 *
 *  4000    57FF    Transport-defined envelope property
 *  5800    5FFF    Transport-defined per-recipient property
 *  6800    7BFF    Message class-defined content property
 *                  property
 *
 *  8000    FFFE    User-defined Name-to-id mapped property
 *
 *  The 3000-3FFF range is further subdivided as follows:
 *
 *  From    To      Kind of property
 *  --------------------------------
 *  3000    33FF    Common property such as display name, entry ID
 *  3400    35FF    Message store object
 *  3600    36FF    Folder or AB container
 *  3700    38FF    Attachment
 *  3900    39FF    Address book object
 *  3A00    3BFF    Mail user
 *  3C00    3CFF    Distribution list
 *  3D00    3DFF    Profile section
 *  3E00    3FFF    Status object
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#ifndef _TAGS_H
#define _TAGS_H

/*
 *  Message envelope properties
 */

Pt(PR_ACKNOWLEDGEMENT_MODE),
Pt(PR_ALTERNATE_RECIPIENT_ALLOWED),
Pt(PR_AUTHORIZING_USERS),
Pt(PR_AUTO_FORWARD_COMMENT),
Pt(PR_AUTO_FORWARD_COMMENT_W),
Pt(PR_AUTO_FORWARD_COMMENT_A),
Pt(PR_AUTO_FORWARDED),
Pt(PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID),
Pt(PR_CONTENT_CORRELATOR),
Pt(PR_CONTENT_IDENTIFIER),
Pt(PR_CONTENT_IDENTIFIER_W),
Pt(PR_CONTENT_IDENTIFIER_A),
Pt(PR_CONTENT_LENGTH),
Pt(PR_CONTENT_RETURN_REQUESTED),

Pt(PR_CONVERSATION_KEY),

Pt(PR_CONVERSION_EITS),
Pt(PR_CONVERSION_WITH_LOSS_PROHIBITED),
Pt(PR_CONVERTED_EITS),
Pt(PR_DEFERRED_DELIVERY_TIME),
Pt(PR_DELIVER_TIME),
Pt(PR_DISCARD_REASON),
Pt(PR_DISCLOSURE_OF_RECIPIENTS),
Pt(PR_DL_EXPANSION_HISTORY),
Pt(PR_DL_EXPANSION_PROHIBITED),
Pt(PR_EXPIRY_TIME),
Pt(PR_IMPLICIT_CONVERSION_PROHIBITED),
Pt(PR_IMPORTANCE),
Pt(PR_IPM_ID),
Pt(PR_LATEST_DELIVERY_TIME),
Pt(PR_MESSAGE_CLASS),
Pt(PR_MESSAGE_CLASS_W),
Pt(PR_MESSAGE_CLASS_A),
Pt(PR_MESSAGE_DELIVERY_ID),

Pt(PR_MESSAGE_SECURITY_LABEL),
Pt(PR_OBSOLETED_IPMS),
Pt(PR_ORIGINALLY_INTENDED_RECIPIENT_NAME),
Pt(PR_ORIGINAL_EITS),
Pt(PR_ORIGINATOR_CERTIFICATE),
Pt(PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED),
Pt(PR_ORIGINATOR_RETURN_ADDRESS),

Pt(PR_PARENT_KEY),
Pt(PR_PRIORITY),

Pt(PR_ORIGIN_CHECK),
Pt(PR_PROOF_OF_SUBMISSION_REQUESTED),
Pt(PR_READ_RECEIPT_REQUESTED),
Pt(PR_RECEIPT_TIME),
Pt(PR_RECIPIENT_REASSIGNMENT_PROHIBITED),
Pt(PR_REDIRECTION_HISTORY),
Pt(PR_RELATED_IPMS),
Pt(PR_ORIGINAL_SENSITIVITY),
Pt(PR_LANGUAGES),
Pt(PR_LANGUAGES_W),
Pt(PR_LANGUAGES_A),
Pt(PR_REPLY_TIME),
Pt(PR_REPORT_TAG),
Pt(PR_REPORT_TIME),
Pt(PR_RETURNED_IPM),
Pt(PR_SECURITY),
Pt(PR_INCOMPLETE_COPY),
Pt(PR_SENSITIVITY),
Pt(PR_SUBJECT),
Pt(PR_SUBJECT_W),
Pt(PR_SUBJECT_A),
Pt(PR_SUBJECT_IPM),
Pt(PR_CLIENT_SUBMIT_TIME),
Pt(PR_REPORT_NAME),
Pt(PR_REPORT_NAME_W),
Pt(PR_REPORT_NAME_A),
Pt(PR_SENT_REPRESENTING_SEARCH_KEY),
Pt(PR_X400_CONTENT_TYPE),
Pt(PR_SUBJECT_PREFIX),
Pt(PR_SUBJECT_PREFIX_W),
Pt(PR_SUBJECT_PREFIX_A),
Pt(PR_NON_RECEIPT_REASON),
Pt(PR_RECEIVED_BY_ENTRYID),
Pt(PR_RECEIVED_BY_NAME),
Pt(PR_RECEIVED_BY_NAME_W),
Pt(PR_RECEIVED_BY_NAME_A),
Pt(PR_SENT_REPRESENTING_ENTRYID),
Pt(PR_SENT_REPRESENTING_NAME),
Pt(PR_SENT_REPRESENTING_NAME_W),
Pt(PR_SENT_REPRESENTING_NAME_A),
Pt(PR_RCVD_REPRESENTING_ENTRYID),
Pt(PR_RCVD_REPRESENTING_NAME),
Pt(PR_RCVD_REPRESENTING_NAME_W),
Pt(PR_RCVD_REPRESENTING_NAME_A),
Pt(PR_REPORT_ENTRYID),
Pt(PR_READ_RECEIPT_ENTRYID),
Pt(PR_MESSAGE_SUBMISSION_ID),
Pt(PR_PROVIDER_SUBMIT_TIME),
Pt(PR_ORIGINAL_SUBJECT),
Pt(PR_ORIGINAL_SUBJECT_W),
Pt(PR_ORIGINAL_SUBJECT_A),
Pt(PR_DISC_VAL),
Pt(PR_ORIG_MESSAGE_CLASS),
Pt(PR_ORIG_MESSAGE_CLASS_W),
Pt(PR_ORIG_MESSAGE_CLASS_A),
Pt(PR_ORIGINAL_AUTHOR_ENTRYID),
Pt(PR_ORIGINAL_AUTHOR_NAME),
Pt(PR_ORIGINAL_AUTHOR_NAME_W),
Pt(PR_ORIGINAL_AUTHOR_NAME_A),
Pt(PR_ORIGINAL_SUBMIT_TIME),
Pt(PR_REPLY_RECIPIENT_ENTRIES),
Pt(PR_REPLY_RECIPIENT_NAMES),
Pt(PR_REPLY_RECIPIENT_NAMES_W),
Pt(PR_REPLY_RECIPIENT_NAMES_A),

Pt(PR_RECEIVED_BY_SEARCH_KEY),
Pt(PR_RCVD_REPRESENTING_SEARCH_KEY),
Pt(PR_READ_RECEIPT_SEARCH_KEY),
Pt(PR_REPORT_SEARCH_KEY),
Pt(PR_ORIGINAL_DELIVERY_TIME),
Pt(PR_ORIGINAL_AUTHOR_SEARCH_KEY),

Pt(PR_MESSAGE_TO_ME),
Pt(PR_MESSAGE_CC_ME),
Pt(PR_MESSAGE_RECIP_ME),

Pt(PR_ORIGINAL_SENDER_NAME),
Pt(PR_ORIGINAL_SENDER_NAME_W),
Pt(PR_ORIGINAL_SENDER_NAME_A),
Pt(PR_ORIGINAL_SENDER_ENTRYID),
Pt(PR_ORIGINAL_SENDER_SEARCH_KEY),
Pt(PR_ORIGINAL_SENT_REPRESENTING_NAME),
Pt(PR_ORIGINAL_SENT_REPRESENTING_NAME_W),
Pt(PR_ORIGINAL_SENT_REPRESENTING_NAME_A),
Pt(PR_ORIGINAL_SENT_REPRESENTING_ENTRYID),
Pt(PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY),

Pt(PR_START_DATE),
Pt(PR_END_DATE),
Pt(PR_OWNER_APPT_ID),
Pt(PR_RESPONSE_REQUESTED),

Pt(PR_SENT_REPRESENTING_ADDRTYPE),
Pt(PR_SENT_REPRESENTING_ADDRTYPE_W),
Pt(PR_SENT_REPRESENTING_ADDRTYPE_A),
Pt(PR_SENT_REPRESENTING_EMAIL_ADDRESS),
Pt(PR_SENT_REPRESENTING_EMAIL_ADDRESS_W),
Pt(PR_SENT_REPRESENTING_EMAIL_ADDRESS_A),

Pt(PR_ORIGINAL_SENDER_ADDRTYPE),
Pt(PR_ORIGINAL_SENDER_ADDRTYPE_W),
Pt(PR_ORIGINAL_SENDER_ADDRTYPE_A),
Pt(PR_ORIGINAL_SENDER_EMAIL_ADDRESS),
Pt(PR_ORIGINAL_SENDER_EMAIL_ADDRESS_W),
Pt(PR_ORIGINAL_SENDER_EMAIL_ADDRESS_A),

Pt(PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE),
Pt(PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_W),
Pt(PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_A),
Pt(PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS),
Pt(PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_W),
Pt(PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_A),

Pt(PR_CONVERSATION_TOPIC),
Pt(PR_CONVERSATION_TOPIC_W),
Pt(PR_CONVERSATION_TOPIC_A),
Pt(PR_CONVERSATION_INDEX),

Pt(PR_ORIGINAL_DISPLAY_BCC),
Pt(PR_ORIGINAL_DISPLAY_BCC_W),
Pt(PR_ORIGINAL_DISPLAY_BCC_A),
Pt(PR_ORIGINAL_DISPLAY_CC),
Pt(PR_ORIGINAL_DISPLAY_CC_W),
Pt(PR_ORIGINAL_DISPLAY_CC_A),
Pt(PR_ORIGINAL_DISPLAY_TO),
Pt(PR_ORIGINAL_DISPLAY_TO_W),
Pt(PR_ORIGINAL_DISPLAY_TO_A),

Pt(PR_RECEIVED_BY_ADDRTYPE),
Pt(PR_RECEIVED_BY_ADDRTYPE_W),
Pt(PR_RECEIVED_BY_ADDRTYPE_A),
Pt(PR_RECEIVED_BY_EMAIL_ADDRESS),
Pt(PR_RECEIVED_BY_EMAIL_ADDRESS_W),
Pt(PR_RECEIVED_BY_EMAIL_ADDRESS_A),

Pt(PR_RCVD_REPRESENTING_ADDRTYPE),
Pt(PR_RCVD_REPRESENTING_ADDRTYPE_W),
Pt(PR_RCVD_REPRESENTING_ADDRTYPE_A),
Pt(PR_RCVD_REPRESENTING_EMAIL_ADDRESS),
Pt(PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W),
Pt(PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A),

Pt(PR_ORIGINAL_AUTHOR_ADDRTYPE),
Pt(PR_ORIGINAL_AUTHOR_ADDRTYPE_W),
Pt(PR_ORIGINAL_AUTHOR_ADDRTYPE_A),
Pt(PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS),
Pt(PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_W),
Pt(PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_A),

Pt(PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE),
Pt(PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_W),
Pt(PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_A),
Pt(PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS),
Pt(PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_W),
Pt(PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_A),

Pt(PR_TRANSPORT_MESSAGE_HEADERS),
Pt(PR_TRANSPORT_MESSAGE_HEADERS_W),
Pt(PR_TRANSPORT_MESSAGE_HEADERS_A),

Pt(PR_DELEGATION),

/*
 *  Message content properties
 */

Pt(PR_BODY),
Pt(PR_BODY_W),
Pt(PR_BODY_A),
Pt(PR_REPORT_TEXT),
Pt(PR_REPORT_TEXT_W),
Pt(PR_REPORT_TEXT_A),
Pt(PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY),
Pt(PR_REPORTING_DL_NAME),
Pt(PR_REPORTING_MTA_CERTIFICATE),

/*  Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */

Pt(PR_RTF_SYNC_BODY_CRC),
Pt(PR_RTF_SYNC_BODY_COUNT),
Pt(PR_RTF_SYNC_BODY_TAG),
Pt(PR_RTF_SYNC_BODY_TAG_W),
Pt(PR_RTF_SYNC_BODY_TAG_A),
Pt(PR_RTF_COMPRESSED),
Pt(PR_RTF_SYNC_PREFIX_COUNT),
Pt(PR_RTF_SYNC_TRAILING_COUNT),
Pt(PR_ORIGINALLY_INTENDED_RECIP_ENTRYID),

/*
 *  Message recipient properties
 */

Pt(PR_CONTENT_INTEGRITY_CHECK),
Pt(PR_EXPLICIT_CONVERSION),
Pt(PR_IPM_RETURN_REQUESTED),
Pt(PR_MESSAGE_TOKEN),
Pt(PR_NDR_REASON_CODE),
Pt(PR_NDR_DIAG_CODE),
Pt(PR_NON_RECEIPT_NOTIFICATION_REQUESTED),
Pt(PR_DELIVERY_POINT),

Pt(PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED),
Pt(PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT),
Pt(PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY),
Pt(PR_PHYSICAL_DELIVERY_MODE),
Pt(PR_PHYSICAL_DELIVERY_REPORT_REQUEST),
Pt(PR_PHYSICAL_FORWARDING_ADDRESS),
Pt(PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED),
Pt(PR_PHYSICAL_FORWARDING_PROHIBITED),
Pt(PR_PHYSICAL_RENDITION_ATTRIBUTES),
Pt(PR_PROOF_OF_DELIVERY),
Pt(PR_PROOF_OF_DELIVERY_REQUESTED),
Pt(PR_RECIPIENT_CERTIFICATE),
Pt(PR_RECIPIENT_NUMBER_FOR_ADVICE),
Pt(PR_RECIPIENT_NUMBER_FOR_ADVICE_W),
Pt(PR_RECIPIENT_NUMBER_FOR_ADVICE_A),
Pt(PR_RECIPIENT_TYPE),
Pt(PR_REGISTERED_MAIL_TYPE),
Pt(PR_REPLY_REQUESTED),
Pt(PR_REQUESTED_DELIVERY_METHOD),
Pt(PR_SENDER_ENTRYID),
Pt(PR_SENDER_NAME),
Pt(PR_SENDER_NAME_W),
Pt(PR_SENDER_NAME_A),
Pt(PR_SUPPLEMENTARY_INFO),
Pt(PR_SUPPLEMENTARY_INFO_W),
Pt(PR_SUPPLEMENTARY_INFO_A),
Pt(PR_TYPE_OF_MTS_USER),
Pt(PR_SENDER_SEARCH_KEY),
Pt(PR_SENDER_ADDRTYPE),
Pt(PR_SENDER_ADDRTYPE_W),
Pt(PR_SENDER_ADDRTYPE_A),
Pt(PR_SENDER_EMAIL_ADDRESS),
Pt(PR_SENDER_EMAIL_ADDRESS_W),
Pt(PR_SENDER_EMAIL_ADDRESS_A),

/*
 */

/*
 * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS,
 * are to be used in the exclude list passed to
 * IMessage::CopyTo when the caller wants either the recipients or attachments
 * of the message to not get copied.  It is also used in the ProblemArray
 * return from IMessage::CopyTo when an error is encountered copying them
 */

Pt(PR_CURRENT_VERSION),
Pt(PR_DELETE_AFTER_SUBMIT),
Pt(PR_DISPLAY_BCC),
Pt(PR_DISPLAY_BCC_W),
Pt(PR_DISPLAY_BCC_A),
Pt(PR_DISPLAY_CC),
Pt(PR_DISPLAY_CC_W),
Pt(PR_DISPLAY_CC_A),
Pt(PR_DISPLAY_TO),
Pt(PR_DISPLAY_TO_W),
Pt(PR_DISPLAY_TO_A),
Pt(PR_PARENT_DISPLAY),
Pt(PR_PARENT_DISPLAY_W),
Pt(PR_PARENT_DISPLAY_A),
Pt(PR_MESSAGE_DELIVERY_TIME),
Pt(PR_MESSAGE_FLAGS),
Pt(PR_MESSAGE_SIZE),
Pt(PR_PARENT_ENTRYID),
Pt(PR_SENTMAIL_ENTRYID),
Pt(PR_CORRELATE),
Pt(PR_CORRELATE_MTSID),
Pt(PR_DISCRETE_VALUES),
Pt(PR_RESPONSIBILITY),
Pt(PR_SPOOLER_STATUS),
Pt(PR_TRANSPORT_STATUS),
Pt(PR_MESSAGE_RECIPIENTS),
Pt(PR_MESSAGE_ATTACHMENTS),
Pt(PR_SUBMIT_FLAGS),
Pt(PR_RECIPIENT_STATUS),
Pt(PR_TRANSPORT_KEY),
Pt(PR_MSG_STATUS),
Pt(PR_MESSAGE_DOWNLOAD_TIME),
Pt(PR_CREATION_VERSION),
Pt(PR_MODIFY_VERSION),
Pt(PR_HASATTACH),
Pt(PR_BODY_CRC),
Pt(PR_NORMALIZED_SUBJECT),
Pt(PR_NORMALIZED_SUBJECT_W),
Pt(PR_NORMALIZED_SUBJECT_A),
Pt(PR_RTF_IN_SYNC),
Pt(PR_ATTACH_SIZE),
Pt(PR_ATTACH_NUM),
Pt(PR_PREPROCESS),

/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */

Pt(PR_ORIGINATING_MTA_CERTIFICATE),
Pt(PR_PROOF_OF_SUBMISSION),

/*
 * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is
 * further broken down into ranges to make assigning new property IDs easier.
 *
 *  From    To      Kind of property
 *  --------------------------------
 *  3000    32FF    MAPI_defined common property
 *  3200    33FF    MAPI_defined form property
 *  3400    35FF    MAPI_defined message store property
 *  3600    36FF    MAPI_defined Folder or AB Container property
 *  3700    38FF    MAPI_defined attachment property
 *  3900    39FF    MAPI_defined address book property
 *  3A00    3BFF    MAPI_defined mailuser property
 *  3C00    3CFF    MAPI_defined DistList property
 *  3D00    3DFF    MAPI_defined Profile Section property
 *  3E00    3EFF    MAPI_defined Status property
 *  3F00    3FFF    MAPI_defined display table property
 */

/*
 *  Properties common to numerous MAPI objects.
 *
 *  Those properties that can appear on messages are in the
 *  end of that range and work down.
 *
 *  Properties that never appear on messages are defined in the common
 *  property range (see above).
 */

/*
 * properties that are common to multiple objects (including message objects)
 */

Pt(PR_ENTRYID),
Pt(PR_OBJECT_TYPE),
Pt(PR_ICON),
Pt(PR_MINI_ICON),
Pt(PR_STORE_ENTRYID),
Pt(PR_STORE_RECORD_KEY),
Pt(PR_RECORD_KEY),
Pt(PR_MAPPING_SIGNATURE),
Pt(PR_ACCESS_LEVEL),
Pt(PR_INSTANCE_KEY),
Pt(PR_ROW_TYPE),
Pt(PR_ACCESS),

/*
 * properties that are common to multiple objects (usually not including message objects)
 */

Pt(PR_ROWID),
Pt(PR_DISPLAY_NAME),
Pt(PR_DISPLAY_NAME_W),
Pt(PR_DISPLAY_NAME_A),
Pt(PR_ADDRTYPE),
Pt(PR_ADDRTYPE_W),
Pt(PR_ADDRTYPE_A),
Pt(PR_EMAIL_ADDRESS),
Pt(PR_EMAIL_ADDRESS_W),
Pt(PR_EMAIL_ADDRESS_A),
Pt(PR_COMMENT),
Pt(PR_COMMENT_W),
Pt(PR_COMMENT_A),
Pt(PR_DEPTH),
Pt(PR_PROVIDER_DISPLAY),
Pt(PR_PROVIDER_DISPLAY_W),
Pt(PR_PROVIDER_DISPLAY_A),
Pt(PR_CREATION_TIME),
Pt(PR_LAST_MODIFICATION_TIME),
Pt(PR_RESOURCE_FLAGS),
Pt(PR_PROVIDER_DLL_NAME),
Pt(PR_PROVIDER_DLL_NAME_W),
Pt(PR_PROVIDER_DLL_NAME_A),
Pt(PR_SEARCH_KEY),
Pt(PR_PROVIDER_UID),
Pt(PR_PROVIDER_ORDINAL),

/*
 *  MAPI Form properties
 */
Pt(PR_FORM_VERSION),
Pt(PR_FORM_VERSION_W),
Pt(PR_FORM_VERSION_A),
Pt(PR_FORM_CLSID),
Pt(PR_FORM_CONTACT_NAME),
Pt(PR_FORM_CONTACT_NAME_W),
Pt(PR_FORM_CONTACT_NAME_A),
Pt(PR_FORM_CATEGORY),
Pt(PR_FORM_CATEGORY_W),
Pt(PR_FORM_CATEGORY_A),
Pt(PR_FORM_CATEGORY_SUB),
Pt(PR_FORM_CATEGORY_SUB_W),
Pt(PR_FORM_CATEGORY_SUB_A),
Pt(PR_FORM_HOST_MAP),
Pt(PR_FORM_HIDDEN),
Pt(PR_FORM_DESIGNER_NAME),
Pt(PR_FORM_DESIGNER_NAME_W),
Pt(PR_FORM_DESIGNER_NAME_A),
Pt(PR_FORM_DESIGNER_GUID),
Pt(PR_FORM_MESSAGE_BEHAVIOR),

/*
 *  Message store properties
 */

Pt(PR_DEFAULT_STORE),
Pt(PR_STORE_SUPPORT_MASK),
Pt(PR_STORE_STATE),

Pt(PR_IPM_SUBTREE_SEARCH_KEY),
Pt(PR_IPM_OUTBOX_SEARCH_KEY),
Pt(PR_IPM_WASTEBASKET_SEARCH_KEY),
Pt(PR_IPM_SENTMAIL_SEARCH_KEY),
Pt(PR_MDB_PROVIDER),
Pt(PR_RECEIVE_FOLDER_SETTINGS),

Pt(PR_VALID_FOLDER_MASK),
Pt(PR_IPM_SUBTREE_ENTRYID),

Pt(PR_IPM_OUTBOX_ENTRYID),
Pt(PR_IPM_WASTEBASKET_ENTRYID),
Pt(PR_IPM_SENTMAIL_ENTRYID),
Pt(PR_VIEWS_ENTRYID),
Pt(PR_COMMON_VIEWS_ENTRYID),
Pt(PR_FINDER_ENTRYID),

/* Proptags 35E8-35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */

/*
 *  Folder and AB Container properties
 */

Pt(PR_CONTAINER_FLAGS),
Pt(PR_FOLDER_TYPE),
Pt(PR_CONTENT_COUNT),
Pt(PR_CONTENT_UNREAD),
Pt(PR_CREATE_TEMPLATES),
Pt(PR_DETAILS_TABLE),
Pt(PR_SEARCH),
Pt(PR_SELECTABLE),
Pt(PR_SUBFOLDERS),
Pt(PR_STATUS),
Pt(PR_ANR),
Pt(PR_ANR_W),
Pt(PR_ANR_A),
Pt(PR_CONTENTS_SORT_ORDER),
Pt(PR_CONTAINER_HIERARCHY),
Pt(PR_CONTAINER_CONTENTS),
Pt(PR_FOLDER_ASSOCIATED_CONTENTS),
Pt(PR_DEF_CREATE_DL),
Pt(PR_DEF_CREATE_MAILUSER),
Pt(PR_CONTAINER_CLASS),
Pt(PR_CONTAINER_CLASS_W),
Pt(PR_CONTAINER_CLASS_A),
Pt(PR_CONTAINER_MODIFY_VERSION),
Pt(PR_AB_PROVIDER_ID),
Pt(PR_DEFAULT_VIEW_ENTRYID),
Pt(PR_ASSOC_CONTENT_COUNT),

/* Don't use 36FE and 36FF */

/*
 *  Attachment properties
 */

Pt(PR_ATTACHMENT_X400_PARAMETERS),
Pt(PR_ATTACH_DATA_OBJ),
Pt(PR_ATTACH_DATA_BIN),
Pt(PR_ATTACH_ENCODING),
Pt(PR_ATTACH_EXTENSION),
Pt(PR_ATTACH_EXTENSION_W),
Pt(PR_ATTACH_EXTENSION_A),
Pt(PR_ATTACH_FILENAME),
Pt(PR_ATTACH_FILENAME_W),
Pt(PR_ATTACH_FILENAME_A),
Pt(PR_ATTACH_METHOD),
Pt(PR_ATTACH_LONG_FILENAME),
Pt(PR_ATTACH_LONG_FILENAME_W),
Pt(PR_ATTACH_LONG_FILENAME_A),
Pt(PR_ATTACH_PATHNAME),
Pt(PR_ATTACH_PATHNAME_W),
Pt(PR_ATTACH_PATHNAME_A),
Pt(PR_ATTACH_RENDERING),
Pt(PR_ATTACH_TAG),
Pt(PR_RENDERING_POSITION),
Pt(PR_ATTACH_TRANSPORT_NAME),
Pt(PR_ATTACH_TRANSPORT_NAME_W),
Pt(PR_ATTACH_TRANSPORT_NAME_A),
Pt(PR_ATTACH_LONG_PATHNAME),
Pt(PR_ATTACH_LONG_PATHNAME_W),
Pt(PR_ATTACH_LONG_PATHNAME_A),
Pt(PR_ATTACH_MIME_TAG),
Pt(PR_ATTACH_MIME_TAG_W),
Pt(PR_ATTACH_MIME_TAG_A),

/*
 *  AB Object properties
 */

Pt(PR_DISPLAY_TYPE),
Pt(PR_TEMPLATEID),
Pt(PR_PRIMARY_CAPABILITY),

/*
 *  Mail user properties
 */

Pt(PR_ACCOUNT),
Pt(PR_ACCOUNT_W),
Pt(PR_ACCOUNT_A),
Pt(PR_ALTERNATE_RECIPIENT),
Pt(PR_CALLBACK_TELEPHONE_NUMBER),
Pt(PR_CALLBACK_TELEPHONE_NUMBER_W),
Pt(PR_CALLBACK_TELEPHONE_NUMBER_A),
Pt(PR_CONVERSION_PROHIBITED),
Pt(PR_DISCLOSE_RECIPIENTS),
Pt(PR_GENERATION),
Pt(PR_GENERATION_W),
Pt(PR_GENERATION_A),
Pt(PR_GIVEN_NAME),
Pt(PR_GIVEN_NAME_W),
Pt(PR_GIVEN_NAME_A),
Pt(PR_GOVERNMENT_ID_NUMBER),
Pt(PR_GOVERNMENT_ID_NUMBER_W),
Pt(PR_GOVERNMENT_ID_NUMBER_A),
Pt(PR_BUSINESS_TELEPHONE_NUMBER),
Pt(PR_BUSINESS_TELEPHONE_NUMBER_W),
Pt(PR_BUSINESS_TELEPHONE_NUMBER_A),
Pt(PR_OFFICE_TELEPHONE_NUMBER),
Pt(PR_OFFICE_TELEPHONE_NUMBER_W),
Pt(PR_OFFICE_TELEPHONE_NUMBER_A),
Pt(PR_HOME_TELEPHONE_NUMBER),
Pt(PR_HOME_TELEPHONE_NUMBER_W),
Pt(PR_HOME_TELEPHONE_NUMBER_A),
Pt(PR_INITIALS),
Pt(PR_INITIALS_W),
Pt(PR_INITIALS_A),
Pt(PR_KEYWORD),
Pt(PR_KEYWORD_W),
Pt(PR_KEYWORD_A),
Pt(PR_LANGUAGE),
Pt(PR_LANGUAGE_W),
Pt(PR_LANGUAGE_A),
Pt(PR_LOCATION),
Pt(PR_LOCATION_W),
Pt(PR_LOCATION_A),
Pt(PR_MAIL_PERMISSION),
Pt(PR_MHS_COMMON_NAME),
Pt(PR_MHS_COMMON_NAME_W),
Pt(PR_MHS_COMMON_NAME_A),
Pt(PR_ORGANIZATIONAL_ID_NUMBER),
Pt(PR_ORGANIZATIONAL_ID_NUMBER_W),
Pt(PR_ORGANIZATIONAL_ID_NUMBER_A),
Pt(PR_SURNAME),
Pt(PR_SURNAME_W),
Pt(PR_SURNAME_A),
Pt(PR_ORIGINAL_ENTRYID),
Pt(PR_ORIGINAL_DISPLAY_NAME),
Pt(PR_ORIGINAL_DISPLAY_NAME_W),
Pt(PR_ORIGINAL_DISPLAY_NAME_A),
Pt(PR_ORIGINAL_SEARCH_KEY),
Pt(PR_POSTAL_ADDRESS),
Pt(PR_POSTAL_ADDRESS_W),
Pt(PR_POSTAL_ADDRESS_A),
Pt(PR_COMPANY_NAME),
Pt(PR_COMPANY_NAME_W),
Pt(PR_COMPANY_NAME_A),
Pt(PR_TITLE),
Pt(PR_TITLE_W),
Pt(PR_TITLE_A),
Pt(PR_DEPARTMENT_NAME),
Pt(PR_DEPARTMENT_NAME_W),
Pt(PR_DEPARTMENT_NAME_A),
Pt(PR_OFFICE_LOCATION),
Pt(PR_OFFICE_LOCATION_W),
Pt(PR_OFFICE_LOCATION_A),
Pt(PR_PRIMARY_TELEPHONE_NUMBER),
Pt(PR_PRIMARY_TELEPHONE_NUMBER_W),
Pt(PR_PRIMARY_TELEPHONE_NUMBER_A),
Pt(PR_BUSINESS2_TELEPHONE_NUMBER),
Pt(PR_BUSINESS2_TELEPHONE_NUMBER_W),
Pt(PR_BUSINESS2_TELEPHONE_NUMBER_A),
Pt(PR_OFFICE2_TELEPHONE_NUMBER),
Pt(PR_OFFICE2_TELEPHONE_NUMBER_W),
Pt(PR_OFFICE2_TELEPHONE_NUMBER_A),
Pt(PR_MOBILE_TELEPHONE_NUMBER),
Pt(PR_MOBILE_TELEPHONE_NUMBER_W),
Pt(PR_MOBILE_TELEPHONE_NUMBER_A),
Pt(PR_CELLULAR_TELEPHONE_NUMBER),
Pt(PR_CELLULAR_TELEPHONE_NUMBER_W),
Pt(PR_CELLULAR_TELEPHONE_NUMBER_A),
Pt(PR_RADIO_TELEPHONE_NUMBER),
Pt(PR_RADIO_TELEPHONE_NUMBER_W),
Pt(PR_RADIO_TELEPHONE_NUMBER_A),
Pt(PR_CAR_TELEPHONE_NUMBER),
Pt(PR_CAR_TELEPHONE_NUMBER_W),
Pt(PR_CAR_TELEPHONE_NUMBER_A),
Pt(PR_OTHER_TELEPHONE_NUMBER),
Pt(PR_OTHER_TELEPHONE_NUMBER_W),
Pt(PR_OTHER_TELEPHONE_NUMBER_A),
Pt(PR_TRANSMITABLE_DISPLAY_NAME),
Pt(PR_TRANSMITABLE_DISPLAY_NAME_W),
Pt(PR_TRANSMITABLE_DISPLAY_NAME_A),
Pt(PR_PAGER_TELEPHONE_NUMBER),
Pt(PR_PAGER_TELEPHONE_NUMBER_W),
Pt(PR_PAGER_TELEPHONE_NUMBER_A),
Pt(PR_BEEPER_TELEPHONE_NUMBER),
Pt(PR_BEEPER_TELEPHONE_NUMBER_W),
Pt(PR_BEEPER_TELEPHONE_NUMBER_A),
Pt(PR_USER_CERTIFICATE),
Pt(PR_PRIMARY_FAX_NUMBER),
Pt(PR_PRIMARY_FAX_NUMBER_W),
Pt(PR_PRIMARY_FAX_NUMBER_A),
Pt(PR_BUSINESS_FAX_NUMBER),
Pt(PR_BUSINESS_FAX_NUMBER_W),
Pt(PR_BUSINESS_FAX_NUMBER_A),
Pt(PR_HOME_FAX_NUMBER),
Pt(PR_HOME_FAX_NUMBER_W),
Pt(PR_HOME_FAX_NUMBER_A),
Pt(PR_COUNTRY),
Pt(PR_COUNTRY_W),
Pt(PR_COUNTRY_A),
Pt(PR_LOCALITY),
Pt(PR_LOCALITY_W),
Pt(PR_LOCALITY_A),
Pt(PR_STATE_OR_PROVINCE),
Pt(PR_STATE_OR_PROVINCE_W),
Pt(PR_STATE_OR_PROVINCE_A),
Pt(PR_STREET_ADDRESS),
Pt(PR_STREET_ADDRESS_W),
Pt(PR_STREET_ADDRESS_A),
Pt(PR_POSTAL_CODE),
Pt(PR_POSTAL_CODE_W),
Pt(PR_POSTAL_CODE_A),
Pt(PR_POST_OFFICE_BOX),
Pt(PR_POST_OFFICE_BOX_W),
Pt(PR_POST_OFFICE_BOX_A),
Pt(PR_TELEX_NUMBER),
Pt(PR_TELEX_NUMBER_W),
Pt(PR_TELEX_NUMBER_A),
Pt(PR_ISDN_NUMBER),
Pt(PR_ISDN_NUMBER_W),
Pt(PR_ISDN_NUMBER_A),
Pt(PR_ASSISTANT_TELEPHONE_NUMBER),
Pt(PR_ASSISTANT_TELEPHONE_NUMBER_W),
Pt(PR_ASSISTANT_TELEPHONE_NUMBER_A),
Pt(PR_HOME2_TELEPHONE_NUMBER),
Pt(PR_HOME2_TELEPHONE_NUMBER_W),
Pt(PR_HOME2_TELEPHONE_NUMBER_A),
Pt(PR_ASSISTANT),
Pt(PR_ASSISTANT_W),
Pt(PR_ASSISTANT_A),
Pt(PR_SEND_RICH_INFO),

/*
 *  Profile section properties
 */

Pt(PR_STORE_PROVIDERS),
Pt(PR_AB_PROVIDERS),
Pt(PR_TRANSPORT_PROVIDERS),

Pt(PR_DEFAULT_PROFILE),
Pt(PR_AB_SEARCH_PATH),
Pt(PR_AB_DEFAULT_DIR),
Pt(PR_AB_DEFAULT_PAB),
Pt(PR_FILTERING_HOOKS),
Pt(PR_SERVICE_NAME),
Pt(PR_SERVICE_NAME_W),
Pt(PR_SERVICE_NAME_A),
Pt(PR_SERVICE_DLL_NAME),
Pt(PR_SERVICE_DLL_NAME_W),
Pt(PR_SERVICE_DLL_NAME_A),
Pt(PR_SERVICE_ENTRY_NAME),
Pt(PR_SERVICE_UID),
Pt(PR_SERVICE_EXTRA_UIDS),
Pt(PR_SERVICES),
Pt(PR_SERVICE_SUPPORT_FILES),
Pt(PR_SERVICE_SUPPORT_FILES_W),
Pt(PR_SERVICE_SUPPORT_FILES_A),
Pt(PR_SERVICE_DELETE_FILES),
Pt(PR_SERVICE_DELETE_FILES_W),
Pt(PR_SERVICE_DELETE_FILES_A),
Pt(PR_AB_SEARCH_PATH_UPDATE),
Pt(PR_PROFILE_NAME),
Pt(PR_PROFILE_NAME_A),
Pt(PR_PROFILE_NAME_W),

/*
 *  Status object properties
 */

Pt(PR_IDENTITY_DISPLAY),
Pt(PR_IDENTITY_DISPLAY_W),
Pt(PR_IDENTITY_DISPLAY_A),
Pt(PR_IDENTITY_ENTRYID),
Pt(PR_RESOURCE_METHODS),
Pt(PR_RESOURCE_TYPE),
Pt(PR_STATUS_CODE),
Pt(PR_IDENTITY_SEARCH_KEY),
Pt(PR_OWN_STORE_ENTRYID),
Pt(PR_RESOURCE_PATH),
Pt(PR_RESOURCE_PATH_W),
Pt(PR_RESOURCE_PATH_A),
Pt(PR_STATUS_STRING),
Pt(PR_STATUS_STRING_W),
Pt(PR_STATUS_STRING_A),
Pt(PR_X400_DEFERRED_DELIVERY_CANCEL),
Pt(PR_HEADER_FOLDER_ENTRYID),
Pt(PR_REMOTE_PROGRESS),
Pt(PR_REMOTE_PROGRESS_TEXT),
Pt(PR_REMOTE_PROGRESS_TEXT_W),
Pt(PR_REMOTE_PROGRESS_TEXT_A),
Pt(PR_REMOTE_VALIDATE_OK),

/*
 * Display table properties
 */

Pt(PR_CONTROL_FLAGS),
Pt(PR_CONTROL_STRUCTURE),
Pt(PR_CONTROL_TYPE),
Pt(PR_DELTAX),
Pt(PR_DELTAY),
Pt(PR_XPOS),
Pt(PR_YPOS),
Pt(PR_CONTROL_ID),
Pt(PR_INITIAL_DETAILS_PANE),

/*
 * Secure property id range
 */

#endif  /* _TAGS_H */


CHSFLD.H

////////////////////////////////////////////////////////////////////////////
//
//      CHSFLD.H
//
//      Prototype for HrPickFolder.
//      Displays a dialog box allowing user to choose a folder from message
//      stores in the current profile.
//
//  Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////

#ifdef _WIN32

#ifndef _CHSFLD_H_
#define _CHSFLD_H_

// Parameters:
//
// required:
//
// hInst    - [in] instance of the module containing resources for the dialog
//
// hWnd     - [in] handle of the parent window for the dialog
//
// pses     - [in] pointer to MAPI session object
//
// ppfld    - [out] on success points to the variable where a pointer to the
//                  selected folder is stored
//
// ppmdb    - [out] on success points to the variable where a pointer to a
//                  message store object containing the selected folder is stored
//
// optional:
//
// pcb      - [in/out] size of the buffer pointed to by *ppb
//
// ppb      - [in/out] *ppb is a pointer to the buffer where expand/collapse
//                      state of the dialog is stored. (don't mess with it).
//                      The state is valid only within the same MAPI session.
//
// Return Values:
//
// S_OK     - The call succeeded and has returned the expected values
//
// E_INVALIDARG - One or more of the parameters passed into the function
//                  were not valid
//
// MAPI_E_USER_CANCEL   - User canceled the dialog

STDAPI HrPickFolder(HINSTANCE hInst, HWND hWnd, LPMAPISESSION pses,
                    LPMAPIFOLDER * ppfld, LPMDB * ppmdb,
                    ULONG *pcb, LPBYTE *ppb);

typedef   HRESULT (STDAPICALLTYPE * HRPICKFOLDER)(HINSTANCE hInst, HWND hWnd,
                    LPMAPISESSION pses, LPMAPIFOLDER * ppfld, LPMDB * ppmdb,
                    ULONG *pcb, LPBYTE *ppb);

#define szChsFldDllName "chsfld32.dll"
#define szChsFldFnName "HrPickFolder"

#endif /* _CHSFLD_H_ */
#endif /* _WIN32 */


CINDEX.C

/*
 *  CINDEX.C
 *
 *  Create and extend PR_CONVERSATION_INDEX
 */

#include <windows.h>
#include <mapiwin.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapidbg.h>
#include <memory.h>

#define cbConvIndexHdr          22
#define cbConvIndexComponent    5
#define bConvIndexRes           (BYTE)1

/*
 *  ExtractLastFileTime()
 *
 *  Purpose:
 *
 *      Parses an existing covnersation index and extracts the last
 *      FILETIME value contained in the index.
 */
VOID
ExtractLastFileTime (LPBYTE lpb, ULONG cb, FILETIME FAR * lpft)
{
    FILETIME ft;
    FILETIME ftCur;
    LPBYTE lpbEnd;

    //  Lets do some verification on the key
    //
    Assert (!IsBadReadPtr (lpb, (UINT)cb));
    Assert (!IsBadWritePtr (lpft, sizeof(FILETIME)));
    Assert (*lpb == bConvIndexRes);
    Assert (cb >= cbConvIndexHdr);
    Assert (!((cb - cbConvIndexHdr) % cbConvIndexComponent));

    //  Rebuild the header time\date into FILETIME format
    //
    ft.dwHighDateTime = (((DWORD)(lpb[1])) << 16) |
                        (((DWORD)(lpb[2])) << 8) |
                        ((DWORD)(lpb[3]));

    ft.dwLowDateTime = (((DWORD)(lpb[4])) << 24) |
                       (((DWORD)(lpb[5])) << 16);

    //  See where the last child chunk ends
    //
    lpbEnd = lpb + cb;
    lpb += cbConvIndexHdr;

    //  Now go through the child chunks to compute
    //  for the last FILETIME using the delta
    //
    while (lpb < lpbEnd)
    {
        //  Convert the delta of the current child
        //  chunk into the FILETIME format.  Use the
        //  delta code in the first bit to get the
        //  real delta.
        //
        //  Delta code : 1 = mask 10000000 = 0x80
        //
        if ((*lpb & 0x80) == 0x80)
        {
            //  Mask out the first bit used for the delta code
            //  *lpb | 0x7F;
            //
            ftCur.dwHighDateTime = (((DWORD)(lpb[0] & 0x7F)) << 15) |
                                   (((DWORD)(lpb[1])) << 7) |
                                   (((DWORD)(lpb[2])) >> 1);

            ftCur.dwLowDateTime = (((DWORD)(lpb[2])) << 31) |
                                  (((DWORD)(lpb[3])) << 23);

            ft = FtAddFt (ft, ftCur);
        }
        else
        {
            ftCur.dwHighDateTime = (((DWORD)(lpb[0] & 0x7F)) << 10) |
                                   (((DWORD)(lpb[1])) << 2) |
                                   (((DWORD)(lpb[2])) >> 6);

            ftCur.dwLowDateTime = (((DWORD)(lpb[2])) << 26) |
                                  (((DWORD)(lpb[3])) << 18);

            ft = FtAddFt (ft, ftCur);
        }

        // Advance to next child
        //
        lpb += cbConvIndexComponent;
    }

    //  If all went well, we sould have ended up at
    //  lpbEnd
    //
    Assert (lpb == lpbEnd);
    *lpft = ft;
    return;
}

/*
 *  ScFillConvHeader()
 *
 *  Purpose:
 *
 *      Fills in the header of a conversation index.  This function is
 *      called when a new conversation index is created.
 *
 *  Assumptions:
 *
 *      The buffer passed in should be big enough to hold cbConvIndexHdr
 *      bytes (22 bytes).
 */
SCODE
ScFillConvHeader (LPBYTE rgb, ULONG cb)
{
    SCODE sc = S_OK;
    SYSTEMTIME st;
    FILETIME ft;
    GUID guid;

    Assert (cb >= cbConvIndexHdr);
    Assert (!IsBadWritePtr (rgb, cbConvIndexHdr));

    //  (Ha). Put the reserved byte
    //
    rgb[0] = bConvIndexRes;

    //  (Hb). Put the current time
    //
    GetSystemTime (&st);
    SystemTimeToFileTime (&st, &ft);

    //  Construct the date\time one byte at a time
    //
    rgb[1] = (BYTE) ((ft.dwHighDateTime & 0x00FF0000) >> 16);
    rgb[2] = (BYTE) ((ft.dwHighDateTime & 0x0000FF00) >> 8);
    rgb[3] = (BYTE) (ft.dwHighDateTime & 0x000000FF);

    //  Drop the rightmost least significant 2 bytes
    //
    rgb[4] = (BYTE) ((ft.dwLowDateTime & 0xFF000000) >> 24);
    rgb[5] = (BYTE) ((ft.dwLowDateTime & 0x00FF0000) >> 16);

    //  (Hc). Now put the GUID
    //      {
    //          DWORD Data1;
    //          WORD  Data2;
    //          WORD  Data3;
    //          BYTE  Data4[8];
    //      } GUID;
    //
    sc = GetScode (CoCreateGuid (&guid));
    if (!FAILED (sc))
    {
        //  Again, lets do it one byte at a time
        //
        rgb[6] = (BYTE) ((guid.Data1 & 0xFF000000) >> 24);
        rgb[7] = (BYTE) ((guid.Data1 & 0x00FF0000) >> 16);
        rgb[8] = (BYTE) ((guid.Data1 & 0x0000FF00) >> 8);
        rgb[9] = (BYTE) ((guid.Data1 & 0x000000FF));
        rgb[10] = (BYTE) ((guid.Data2 & 0xFF00) >> 8);
        rgb[11] = (BYTE) ((guid.Data2 & 0x00FF));
        rgb[12] = (BYTE) ((guid.Data3 & 0xFF00) >> 8);
        rgb[13] = (BYTE) ((guid.Data3 & 0x00FF));
    }

    //  Slurp the rest across
    //
    CopyMemory (&rgb[14], &guid.Data4, 8);
    DebugTraceSc (ScFillConvHeader(), sc);
    return sc;
}

/*
 *  ScAddConversationIndex()
 *
 *  Purpose:
 *
 *      Given the conversation index to a message, this function will
 *      create the conversation of a child message to the original.  If
 *      the no original is suplied, then an index is created that would
 *      signify the start of a new thread.
 */
SCODE
ScAddConversationIndex (ULONG cbParent,
    LPBYTE lpbParent,
    ULONG FAR * lpcb,
    LPBYTE FAR * lppb)
{
    SCODE sc;
    DWORD dwTemp;
    SYSTEMTIME st;
    FILETIME ft;
    FILETIME ftLast;
    FILETIME ftDelta;
    HMODULE hMAPIDll = NULL;
    typedef SCODE (STDAPICALLTYPE FAR *MAPICONVIDX)(ULONG, LPBYTE, ULONG FAR *, LPBYTE FAR *);
    MAPICONVIDX lpfnMAPIConvIdx = NULL;

#if defined (WIN16)
#define szScCreateConversationIndex "ScCreateConversationIndex"
#elif defined (_WIN32) && defined (_X86_)
#ifndef szScCreateConversationIndex
#define szScCreateConversationIndex "ScCreateConversationIndex@16"
#endif
#elif defined (_ALPHA_) || defined (_MIPS_) || defined (_PPC_)
#define szScCreateConversationIndex "ScCreateConversationIndex"
#endif

#ifdef _WIN32
    #define szMAPIDll "mapi32.dll"
#else
    #define szMAPIDll "mapi.dll"
#endif

    /*
     * MAPI is going to export a function that is doing the same thing as this one.
     * So if the function is present we'll use it.
     */
    hMAPIDll = GetModuleHandle(szMAPIDll);
    if(hMAPIDll)
    {
        lpfnMAPIConvIdx = (MAPICONVIDX)GetProcAddress(hMAPIDll,
                                            szScCreateConversationIndex);
        if(lpfnMAPIConvIdx)
        {
            return (*lpfnMAPIConvIdx)(cbParent, lpbParent, lpcb, lppb);
        }
    }
    //  Ensure that the parent is what we think
    //  it should be
    //
    if ((cbParent < cbConvIndexHdr) ||
        ((cbParent - cbConvIndexHdr) % cbConvIndexComponent) ||
        (lpbParent[0] != bConvIndexRes))
    {
        cbParent = 0;
        *lpcb = cbConvIndexHdr;
    }
    else
        *lpcb = cbParent + cbConvIndexComponent;

    sc = MAPIAllocateBuffer (*lpcb, (LPVOID FAR *)lppb);
    if (!FAILED (sc))
    {
        if (cbParent == 0)
        {
            //  This is a new key, so all it ever contains
            //  is a header.  Fill it in and we are done
            //
            sc = ScFillConvHeader (*lppb, *lpcb);
            if (FAILED (sc))
            {
                MAPIFreeBuffer (*lppb);
                *lppb = NULL;
            }
        }
        else
        {
            //  First copy the old key across
            //
            CopyMemory (*lppb, lpbParent, (UINT)cbParent);

            //  (Cb).  First get the current time (we'll then get
            //  the absolute distance between the current time and
            //  the time in the last chunk)
            //
            GetSystemTime (&st);
            SystemTimeToFileTime (&st, &ft);

            //  Now get the time of the last chunk
            //
            ExtractLastFileTime (lpbParent, cbParent, &ftLast);

            //  Now mask out the bits we don't want from the
            //  current time
            //
            ft.dwHighDateTime &= 0x00FFFFFF;
            ft.dwLowDateTime &= 0xFFFF0000;

            //  This assert is here to catch how often the
            //  5-byte time can collide and under what scenario,
            //  to see if 5 bytes + the next byte suffices to
            //  make this child chunk unique.
            //
            Assert (!((ftLast.dwHighDateTime == ft.dwHighDateTime) &&
                (ftLast.dwLowDateTime == ft.dwLowDateTime)));

            //  Get the change in time
            //
            if ((ft.dwHighDateTime > ftLast.dwHighDateTime) ||
                ((ft.dwHighDateTime == ftLast.dwHighDateTime) &&
                 (ft.dwLowDateTime > ftLast.dwLowDateTime)))
            {
                ftDelta = FtSubFt (ft, ftLast);
            }
            else
                ftDelta = FtSubFt (ftLast, ft);

            //  If the delta is less than 1.7 yrs, use 0
            //
            if (!(ftDelta.dwHighDateTime & 0x00FE0000))
            {
                //  Just mask out the 31 bits that we
                //  want from the ftDelta
                //
                dwTemp = ((DWORD)(ftDelta.dwHighDateTime & 0x0001FFFF)) << 14 |
                         ((DWORD)(ftDelta.dwLowDateTime & 0xFFFC0000)) >> 18;

                //  Only the first byte is different
                //
                (*lppb)[cbParent] = (BYTE)((dwTemp & 0xFF000000) >> 24 );
            }
            else
            {
                //  Just mask out the 31 bits that we
                //  want from the ftDelta
                //
                dwTemp = ((DWORD)(ftDelta.dwHighDateTime & 0x003FFFFF)) << 9 |
                         ((DWORD)(ftDelta.dwLowDateTime & 0xFF800000)) >> 23;

                // Only the first byte is different
                //
                (*lppb)[cbParent] = (BYTE)(HIBYTE(HIWORD(dwTemp)) | 0x080);
            }

            //  The remaining delta bytes are the same
            //
            (*lppb)[cbParent + 1] = (BYTE) ((dwTemp & 0x00FF0000) >> 16);
            (*lppb)[cbParent + 2] = (BYTE) ((dwTemp & 0x0000FF00) >> 8);
            (*lppb)[cbParent + 3] = (BYTE) ((dwTemp & 0x000000FF) );

            //  (Cc). Next get the random number
            //  (Cd). Next get the sequence count
            //  -- we are going to use part of the tick count
            //
            (*lppb)[cbParent + 4] = (BYTE) (GetTickCount() & 0x000000FF);
        }
    }

    DebugTraceSc (ScAddConversationIndex(), sc);
    return sc;
}


CINDEX.H

/*
 * CINDEX.H
 *
 * Prototype for ScAddConversationIndex
 *
 *  Copyright (c) 1995, Microsoft Corporation.
 *  All rights reserved.
 */

#ifdef __cplusplus
extern "C" {
#endif

SCODE ScAddConversationIndex (ULONG cbParent,
                              LPBYTE lpbParent,
                              ULONG FAR * lpcb,
                              LPBYTE FAR * lppb);

#ifdef __cplusplus
}
#endif


CTL3D.H

/*-----------------------------------------------------------------------
|CTL3D.DLL
|
|Adds 3d effects to Windows controls
|
|See ctl3d.doc for info
|
-----------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif

BOOL WINAPI Ctl3dSubclassDlg(HWND, WORD);
BOOL WINAPI Ctl3dSubclassDlgEx(HWND, DWORD);

WORD WINAPI Ctl3dGetVer(void);
BOOL WINAPI Ctl3dEnabled(void);

HBRUSH WINAPI Ctl3dCtlColor(HDC, LONG);// ARCHAIC, use Ctl3dCtlColorEx
HBRUSH WINAPI Ctl3dCtlColorEx(UINT wm, WPARAM wParam, LPARAM lParam);

BOOL WINAPI Ctl3dColorChange(void);

BOOL WINAPI Ctl3dSubclassCtl(HWND);
BOOL WINAPI Ctl3dSubclassCtlEx(HWND, int);
BOOL WINAPI Ctl3dUnsubclassCtl(HWND);

LONG WINAPI Ctl3dDlgFramePaint(HWND, UINT, WPARAM, LPARAM);

BOOL WINAPI Ctl3dAutoSubclass(HINSTANCE);
BOOL WINAPI Ctl3dAutoSubclassEx(HINSTANCE, DWORD);
BOOL WINAPI Ctl3dIsAutoSubclass(VOID);
BOOL WINAPI Ctl3dUnAutoSubclass(VOID);

BOOL WINAPI Ctl3dRegister(HINSTANCE);
BOOL WINAPI Ctl3dUnregister(HINSTANCE);

//begin DBCS: far east short cut key support
VOID WINAPI Ctl3dWinIniChange(void);
//end DBCS

/* Ctl3dAutoSubclassEx flags */
#define CTL3D_SUBCLASS_DYNCREATE0x0001
#define CTL3D_NOSUBCLASS_DYNCREATE0x0002

/* Ctl3d Control ID */
#define CTL3D_BUTTON_CTL0
#define CTL3D_LISTBOX_CTL1
#define CTL3D_EDIT_CTL2
#define CTL3D_COMBO_CTL 3
#define CTL3D_STATIC_CTL4

/* Ctl3dSubclassDlg3d flags */
#define CTL3D_BUTTONS0x0001
#define CTL3D_LISTBOXES0x0002
#define CTL3D_EDITS0x0004
#define CTL3D_COMBOS0x0008
#define CTL3D_STATICTEXTS0x0010
#define CTL3D_STATICFRAMES0x0020

#define CTL3D_NODLGWINDOW       0x00010000
#define CTL3D_ALL0xffff

#define WM_DLGBORDER (WM_USER+3567)
/* WM_DLGBORDER *(int FAR *)lParam return codes */
#define CTL3D_NOBORDER0
#define CTL3D_BORDER1

#define WM_DLGSUBCLASS (WM_USER+3568)
/* WM_DLGSUBCLASS *(int FAR *)lParam return codes */
#define CTL3D_NOSUBCLASS0
#define CTL3D_SUBCLASS1

#define CTLMSGOFFSET 3569
#ifdef WIN32
#define CTL3D_CTLCOLORMSGBOX(WM_USER+CTLMSGOFFSET)
#define CTL3D_CTLCOLOREDIT(WM_USER+CTLMSGOFFSET+1)
#define CTL3D_CTLCOLORLISTBOX(WM_USER+CTLMSGOFFSET+2)
#define CTL3D_CTLCOLORBTN(WM_USER+CTLMSGOFFSET+3)
#define CTL3D_CTLCOLORSCROLLBAR (WM_USER+CTLMSGOFFSET+4)
#define CTL3D_CTLCOLORSTATIC(WM_USER+CTLMSGOFFSET+5)
#define CTL3D_CTLCOLORDLG(WM_USER+CTLMSGOFFSET+6)
#else
#define CTL3D_CTLCOLOR (WM_USER+CTLMSGOFFSET)
#endif

/* Resource ID for 3dcheck.bmp (for .lib version of ctl3d) */
#define CTL3D_3DCHECK 26567

#ifdef __cplusplus
}
#endif


IMEM.C

/*
 *  IMEM.C
 *
 *  Per-instance global data for WIN32 (trivial), WIN16, and Mac.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#pragma warning(disable:4100)   /* unreferenced formal parameter */
#pragma warning(disable:4201)   /* nameless struct/union */
#pragma warning(disable:4209)   /* benign typedef redefinition */
#pragma warning(disable:4214)   /* bit field types other than int */
#pragma warning(disable:4001)   /* single line comments */
#pragma warning(disable:4115)   /* named type definition in parens */

#ifdef _WIN32
#define INC_OLE2 /* Get the OLE2 stuff */
#define INC_RPC  /* harmless on Windows NT; Windows 95 needs it */
#endif
#include <windows.h>

#include <windowsx.h>
#include <mapiwin.h>
#if defined (_WIN32) && !defined (_MAC)
#pragma warning(disable:4001)   /* single line comments */
#include <objerror.h>
#include <objbase.h>
#endif
#ifdef WIN16
#include <compobj.h>
#endif
#include <mapicode.h>
#include <mapidbg.h>

#ifdef _MAC
#include <macname1.h>
#include <macos\lowmem.h>
#include <macname2.h>
#include <utilmac.h>
#endif

#ifdef  DEBUG
#define STATIC
#else
#define STATIC static
#endif

#pragma warning (disable:4514)      /* unreferenced inline function */

#ifdef  WIN16

#pragma code_seg("IMAlloc")

#pragma warning(disable: 4005)      /* redefines MAX_PATH */
#include <toolhelp.h>
#pragma warning(default: 4005)

#pragma warning(disable: 4704)      /* Inline assembler */

/*
 *  These arrays are parallel. RgwInstKey holds the stack
 *  segment of each task that calls the DLL we're in; rgpvInst
 *  has a pointer to that task's instance globals in the slot with
 *  the same index. Since all Win16 tasks share the same x86
 *  segment descriptor tables, no two tasks can have the same stack
 *  segment.
 *
 *  Note carefully the last elements of the initializers. The value
 *  in rgwInstKey is a sentinel, which will always stop the scan
 *  whether the value being sought is a valid stack segment or
 *  zero.
 */

STATIC WORD   rgwInstKey[cInstMax+1]= { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xFFFF };
STATIC LPVOID rgpvInst[cInstMax+1]=   { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
STATIC DWORD  rgdwPid[cInstMax+1]=    { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };

STATIC WORD   wCachedKey            = 0;
STATIC LPVOID pvCachedInst          = NULL;

/*
 -  IFindInst
 -
 *  Purpose:
 *      Used to locate a particular task's instance pointer, and
 *      also to find a free slot in the table.
 *
 *  Arguments:
 *      The value to look up. This is either a task's stack
 *      segment, or 0 (if an empty slot is being sought).
 *
 *  Returns:
 *      Returns the index of the given value in rgwInstKey.
 *      If the value is not present, returns cInstMax.
 *
 */

#pragma warning(disable: 4035)      /* function return value done in asm */

STATIC int
IFindInst(WORD w)
{
    _asm
    {
        mov cx,cInstMax+1           /* count includes sentinel */
        mov ax,ds                   /* point es:di at rgwInstKey */
        mov es,ax
        mov di,OFFSET rgwInstKey
        mov ax,w                    /* scan for this value */
        cld                         /* scan forward... */
        repne scasw                 /* go */
        mov ax,cx                   /* Convert the number of items remaining */
        sub ax,cInstMax+1           /* to the index of the item found. */
        inc ax
        neg ax
    }
}

#pragma warning(default: 4035)

/*
 -  PvGetInstanceGlobals
 -
 *  Purpose:
 *      Returns a pointer to the instance global data structre for
 *      the current task.
 *
 *  Returns:
 *      Pointer to the instance data structure, or NULL if no
 *      structure has yet been installed for this task.
 */

LPVOID FAR PASCAL
PvGetInstanceGlobals(void)
{
    int iInst;
    WORD wMe;

    _asm mov wMe,ss         ; get key for this process

    /* First check cached value */
    if (wCachedKey == wMe)
        return pvCachedInst;

    /* Miss, do the lookup */
    iInst = IFindInst(wMe);

    /* Cache and return the found value */
    if (iInst != cInstMax)
    {
        wCachedKey = wMe;
        pvCachedInst = rgpvInst[iInst];
    }
    return rgpvInst[iInst];     /* Note: parallel to the lookup sentinel */
}

LPVOID FAR PASCAL
PvGetVerifyInstanceGlobals(DWORD dwPid)
{
    int iInst;
    WORD wMe;

    _asm mov wMe,ss         ; get key for this process

    /* Always do the lookup */
    iInst = IFindInst(wMe);

    /* If SS misses, return null right away */
    if (iInst == cInstMax)
        return NULL;

    /* SS hit, now check the OLE process ID */
    if (dwPid != rgdwPid[iInst])
    {
        wCachedKey = 0;         /* Take no chances */
        rgwInstKey[iInst] = 0;
        rgpvInst[iInst] = 0;
        rgdwPid[iInst] = 0;
        return NULL;
    }

    /* Cache and return the found value */
    wCachedKey = wMe;
    pvCachedInst = rgpvInst[iInst];
    return pvCachedInst;
}

LPVOID FAR PASCAL
PvSlowGetInstanceGlobals(DWORD dwPid)
{
    int iInst;

    /* Always do the lookup */
    for (iInst = 0; iInst < cInstMax; ++iInst)
    {
        if (rgdwPid[iInst] == dwPid)
            break;
    }

    /* If PID misses, return null */
    if (iInst == cInstMax)
        return NULL;

    /* Return the found value. Do not cache; this function is being
     * called because SS is not what it "normally" is.
     */
    return rgpvInst[iInst];
}

/*
 -  ScSetVerifyInstanceGlobals
 -
 *  Purpose:
 *      Installs or deinstalls instance global data for the current task.
 *
 *  Arguments:
 *      pv          in      Pointer to instance data structure (to
 *                          install); NULL (to deinstall).
 *      dwPid       in      Zero or process ID, for better matching.
 *
 *  Returns:
 *      MAPI_E_NOT_ENOUGH_MEMORY if no slot is available in the
 *      fixed-size table, else 0.
 */

LONG FAR PASCAL
ScSetVerifyInstanceGlobals(LPVOID pv, DWORD dwPid)
{
    int iInst;
    WORD wMe;

    _asm mov wMe,ss
    if (pv)
    {
        /* I am NOT supposed to be in the array at this time! */
        Assert(IFindInst(wMe) == cInstMax);

        /* Installing instance globals. Find a free slot and park there. */
        iInst = IFindInst(0);
        if (iInst == cInstMax)
        {
#ifdef  DEBUG
            OutputDebugString("Instance globals maxed out\r\n");
#endif
            return MAPI_E_NOT_ENOUGH_MEMORY;
        }
        rgpvInst[iInst] = pv;
        rgwInstKey[iInst] = wMe;
        rgdwPid[iInst] = dwPid;

        /* Set the cache. */
        wCachedKey = wMe;
        pvCachedInst = pv;
    }
    else
    {
        /* Deinstalling instance globals. Search and destroy. */
        iInst = IFindInst(wMe);
        if (iInst == cInstMax)
        {
#ifdef  DEBUG
            OutputDebugString("No instance globals to reset\r\n");
#endif
            return MAPI_E_NOT_INITIALIZED;
        }
        rgpvInst[iInst] = NULL;
        rgwInstKey[iInst] = 0;
        rgdwPid[iInst] = 0L;

        /* Clear the cache. */
        wCachedKey = 0;
        pvCachedInst = NULL;
    }

    return 0;
}

LONG FAR PASCAL
ScSetInstanceGlobals(LPVOID pv)
{
    return ScSetVerifyInstanceGlobals(pv, 0L);
}

BOOL __export FAR PASCAL
FCleanupInstanceGlobals(WORD wID, DWORD dwData)
{
    int iInst;
    WORD wMe;

    /*
     *  Would be nice if we could release the pmalloc
     *  and the inst structure in this function, but docs say
     *  don't make Windows calls from this callback.
     *  That means also NO DEBUG TRACES
     */

    /*
     *  First, double-check that the DLL's data segment is available.
     *  Code snitched from MSDN article "Loading, Initializing, and
     *  Terminating a DLL."
     */
    _asm
    {
        push cx
        mov cx, ds          ; get selector of interest
        lar ax, cx          ; get selector access rights
        pop cx
        jnz bail            ; failed, segment is bad
        test ax, 8000h      ; if bit 8000 is clear, segment is not loaded
        jz bail             ; we're OK
    }

    if (wID == NFY_EXITTASK)
    {
        _asm mov wMe,ss
        iInst = IFindInst(wMe);

        if (iInst < cInstMax)
        {
            /* Clear this process's entry */
            rgpvInst[iInst] = NULL;
            rgwInstKey[iInst] = 0;
        }

        /* Clear the cache too */
        wCachedKey = 0;
        pvCachedInst = NULL;
    }

bail:
    return 0;       /* don't suppress further notifications */
}

#elif defined(_MAC) /* !WIN16 */

#pragma code_seg("imalloc", "fixed")

/*
 *  The Mac implementation uses a linked list containing unique keys
 *  to the calling process and pointers to instance data. This linked
 *  list is n-dimensional because the Mac version often groups several
 *  dlls into one exe.
 *
 *  The OLE code that TomSax wrote allows us to keep track of the caller's
 *  %a5 world when we call from another application. This code depends on
 *  on that.
 *
 */

typedef struct tag_INSTDATA {
    DWORD                   dwInstKey;
    DWORD                   dwPid;
    LPVOID                  lpvInst[kMaxSet];
    struct tag_INSTDATA     *next;
} INSTDATA, *LPINSTDATA, **HINSTDATA;

LPINSTDATA      lpInstHead = NULL;

#define PvSlowGetInstanceGlobals(_dw, _dwId)    PvGetVerifyInstanceGlobals(_dw, _dwId)

VOID
DisposeInstData(LPINSTDATA lpInstPrev, LPINSTDATA lpInst)
{
    HINSTDATA   hInstHead = &lpInstHead;

    /* This better only happen when both elements are NULL! */
    if (lpInst->lpvInst[kInstMAPIX] == lpInst->lpvInst[kInstMAPIU])
    {
        /* No inst data, remove element from linked list */
        if (lpInst == *hInstHead)
            *hInstHead = lpInst->next;
        else
            lpInstPrev->next = lpInst->next;
        DisposePtr((Ptr)lpInst);
    }
}

/*
 -  PvGetInstanceGlobals
 -
 *  Purpose:
 *      Returns a pointer to the instance global data structre for
 *      the current task.
 *
 *  Returns:
 *      Pointer to the instance data structure, or NULL if no
 *      structure has yet been installed for this task.
 */

LPVOID FAR PASCAL
PvGetInstanceGlobals(WORD wDataSet)
{
    HINSTDATA       hInstHead = &lpInstHead;
    LPINSTDATA      lpInst = *hInstHead;

#ifdef DEBUG
    if (wDataSet >= kMaxSet)
    {
        DebugStr("\pPvGetInstanceGlobals : This data set has not been defined.");
        return NULL;
    }
#endif

    while (lpInst)
    {
        if (lpInst->dwInstKey == (DWORD)LMGetCurrentA5())
            break;
        lpInst = lpInst->next;
    }
    return(lpInst->lpvInst[wDataSet]);
}

LPVOID FAR PASCAL
PvGetVerifyInstanceGlobals(DWORD dwPid, DWORD wDataSet)
{
    HINSTDATA   hInstHead = &lpInstHead;
    LPINSTDATA  lpInst, lpInstPrev;

    lpInst = lpInstPrev = *hInstHead;

    /* Always do the lookup */
    while (lpInst)
    {
        if (lpInst->dwInstKey == (DWORD)LMGetCurrentA5())
            break;
        lpInstPrev = lpInst;
        lpInst = lpInst->next;
    }

    /* If PvGetInstanceGlobals() misses, return NULL right away */
    if (lpInst->lpvInst[wDataSet] == NULL)
        return NULL;

    /* Found a match, now check the OLE process ID */
    if (dwPid != lpInst->dwPid)
    {
        DisposeInstData(lpInstPrev, lpInst);
        return NULL;
    }

    /* Return the found value */
    return lpInst->lpvInst[wDataSet];
}

/*
 -  ScSetVerifyInstanceGlobals
 -
 *  Purpose:
 *      Installs or deinstalls instance global data for the current task.
 *
 *  Arguments:
 *      pv          in      Pointer to instance data structure (to
 *                          install); NULL (to deinstall).
 *      dwPid       in      Zero or process ID, for better matching.
 *      wDataSet    in      Inst data set to init or deinit (MAPIX or MAPIU)
 *
 *  Returns:
 *      MAPI_E_NOT_ENOUGH_MEMORY if a pointer of INSTDATA size cannot be
 *      created, else 0.
 */

LONG FAR PASCAL
ScSetVerifyInstanceGlobals(LPVOID pv, DWORD dwPid, WORD wDataSet)
{
    HINSTDATA       hInstHead = &lpInstHead;
    LPINSTDATA      lpInst, lpInstPrev;

    lpInst = lpInstPrev = *hInstHead;

    Assert(wDataSet < kMaxSet);

    /* Find our linked list element and the one before it */
    while (lpInst)
    {
        if (lpInst->dwInstKey == (DWORD)LMGetCurrentA5())
            break;
        lpInstPrev = lpInst;
        lpInst = lpInst->next;
    }

    if (pv)
    {
        if (lpInst)
        {
            /* I am NOT supposed to be in the array at this time! */
            Assert(lpInst->lpvInst[wDataSet] == NULL);
            lpInst->lpvInst[wDataSet] = pv;
        }
        else
        {
            /* Add a new linked list element and store <pv> there. */
            lpInst = (LPVOID) NewPtrClear(sizeof(INSTDATA));
            if (!lpInst)
            {
#ifdef  DEBUG
                OutputDebugString("Instance globals maxed out\r");
#endif
                return MAPI_E_NOT_ENOUGH_MEMORY;
            }
            if (lpInstPrev)
                lpInstPrev->next = lpInst;
            else
                *hInstHead = lpInst;
            lpInst->dwInstKey = (DWORD)LMGetCurrentA5();

            lpInst->dwPid = dwPid;
            lpInst->lpvInst[wDataSet] = pv;
        }
    }
    else
    {
        /* Deinstalling instance globals. Search and destroy. */
        if (lpInst == NULL || lpInst->lpvInst[wDataSet] == NULL)
        {
#ifdef  DEBUG
            OutputDebugString("No instance globals to reset\r");
#endif
            return MAPI_E_NOT_INITIALIZED;
        }
        /* The memory for <lpInst->lpvInst[wDataSet]> is disposed of    */
        /* elsewhere. just as it was allocated elsewhere.               */
        lpInst->lpvInst[wDataSet] = NULL;
        DisposeInstData(lpInstPrev, lpInst);
    }

    return 0;
}

LONG FAR PASCAL
ScSetInstanceGlobals(LPVOID pv, WORD wDataSet)
{
    return ScSetVerifyInstanceGlobals(pv, 0L, wDataSet);
}

BOOL FAR PASCAL
FCleanupInstanceGlobals(WORD wID, DWORD dwData)
{
/*
 * This is no longer used.
 *
 */

#ifdef DEBUG
    DebugStr("\pCalled FCleanupInstanceGlobals : Empty function");
#endif

    return 0;
}

#else /* !WIN16 && !_MAC */

/* This is the entire 32-bit implementation for instance globals. */

VOID FAR *pinstX = NULL;

#endif  /* WIN16 */


LASTERR.CPP

//
//  LASTERR.CPP
//
// implementation file for CLastError which implements the
// GetLastError helper
//
//
//
// Copyright (C) 1995 Microsoft Corp.
//

#define STRICT

#include <windows.h>
#include <stdarg.h>
#include <stdio.h>
#include <tchar.h>
#include <ctype.h>
#include <mapix.h>
#include <mapidbg.h>
#include "lasterr.h"

const int       CchMaxErrorMessage = 256;

extern const CHAR FAR  SzNull[] = "";
char szErrUnknown[] = "Error description is not available";

//
// some stuff to put a "Help" button on the error msgbox
//
#if defined(_WIN32)
char szHelpFile[_MAX_PATH];
VOID CALLBACK ErrorBoxCallBack(LPHELPINFO lpHelpInfo);
#endif //_WIN32

static LPSTR aszErrorStrings[] =
{
    "Not enough memory",
    "Invalid arguments",
    "Invalid object",
    "Interface not supported",
    "Access denied",
    "No support",
    "Wrong character set",
    "Item not found",
    "Call failed",
    "User cancel",
    "Errors returned",
    "Invalid flags",
    "Unexpected error",
    "Can't perform the required action at this time"
};

enum
{
    IDS_E_OUTOFMEMORY,
    IDS_INVALID_ARGUMENT,
    IDS_INVALID_OBJECT,
    IDS_INTERFACE_NOT_SUPPORTED,
    IDS_ACCESS_DENIED,
    IDS_NOT_SUPPORTED,
    IDS_INVALID_CHARWIDTH,
    IDS_NOT_FOUND,
    IDS_CALL_FAILED,
    IDS_USER_CANCEL,
    IDS_ERRORS_RETURNED,
    IDS_UNKNOWN_FLAGS,
    IDS_UNEXPECTED,
    IDS_CANTNOW
};

static int iFromHR(HRESULT hr)
{
    switch(GetScode(hr)) {
    case MAPI_E_NOT_ENOUGH_MEMORY:      return IDS_E_OUTOFMEMORY;
    case MAPI_E_INVALID_PARAMETER:      return IDS_INVALID_ARGUMENT;
    case MAPI_E_INVALID_OBJECT:         return IDS_INVALID_OBJECT;
    case MAPI_E_INTERFACE_NOT_SUPPORTED: return IDS_INTERFACE_NOT_SUPPORTED;
    case MAPI_E_NO_ACCESS:              return IDS_ACCESS_DENIED;
    case MAPI_E_NO_SUPPORT:             return IDS_NOT_SUPPORTED;
    case MAPI_E_BAD_CHARWIDTH:          return IDS_INVALID_CHARWIDTH;
    case MAPI_E_NOT_FOUND:              return IDS_NOT_FOUND;
    case MAPI_E_CALL_FAILED:            return IDS_CALL_FAILED;
    case MAPI_E_USER_CANCEL:            return IDS_USER_CANCEL;
    case MAPI_W_ERRORS_RETURNED:        return IDS_ERRORS_RETURNED;
    case MAPI_E_UNKNOWN_FLAGS:          return IDS_UNKNOWN_FLAGS;
    case E_UNEXPECTED:                  return IDS_UNEXPECTED;
    case OLEOBJ_S_CANNOT_DOVERB_NOW:    return IDS_CANTNOW;
    // if it's not in this list you need to add it.
    default:
        DebugTrace("lasterr: bad arg to FORMScodeFromHR");
        Assert(FALSE);
        return 0;
    }
}

HRESULT CLastError::HrSetLastError(HRESULT hr)
{
#if defined(DEBUG)
    //
    //  Ensure that the error string exists -- when we set it not when
    //  they ask for it.
    //

    (void) iFromHR(hr);
#endif

    //
    //  Release any previous error
    //

    if (m_pmapierr != NULL) {
        MAPIFreeBuffer(m_pmapierr);
        m_pmapierr = NULL;
    }

    if (hr) {
        m_eLastErr = eMAPI;
    }
    else {
        m_eLastErr = eNoError;
    }

    return (m_hrLast = hr);
}

HRESULT CLastError::HrSetLastError(HRESULT hr, IUnknown* punk)
{
    Assert(punk && hr);     // we have to have an object and an error.

    m_eLastErr = eObject;
    m_hrLast = hr;

    IMAPIProp* pmprp = (IMAPIProp*)punk;  // I hate this cast but c'est la vie.

    MAPIFreeBuffer(m_pmapierr);     // clean up previous error.
    m_pmapierr = NULL;

    m_hrGLE = pmprp->GetLastError(hr, 0, &m_pmapierr);
    if (m_hrGLE == S_OK) {
        if (m_pmapierr == NULL) {
            if (MAPIAllocateBuffer(sizeof(MAPIERROR), (void **) &m_pmapierr)) {
                m_hrGLE = ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
            }
            else {
                memset(m_pmapierr, 0, sizeof(MAPIERROR));
                m_pmapierr->ulVersion = MAPI_ERROR_VERSION;
                m_pmapierr->lpszError = szErrUnknown;
                m_pmapierr->lpszComponent = (char *) SzNull;
            }
        }
        else if (m_pmapierr->lpszError == NULL) {
            m_pmapierr->lpszError = (char *) SzNull;
        }
        else if (m_pmapierr->lpszComponent == NULL) {
            m_pmapierr->lpszComponent = (char *) SzNull;
        }
    }
    else {
        if (m_pmapierr != NULL) {
            MAPIFreeBuffer(m_pmapierr);
            m_pmapierr = NULL;
        }
    }
    return m_hrLast;
}

HRESULT CLastError::HrGetLastError(HRESULT hr, DWORD dwFlags,
                                   LPMAPIERROR FAR * lppMAPIError)
{
    //
    //  Start with parameter validation
    //

    if (IsBadWritePtr(lppMAPIError, sizeof(LPMAPIERROR))) {
        return HrSetLastError(ResultFromScode(MAPI_E_INVALID_PARAMETER));
    }

    if (MAPI_UNICODE == (dwFlags & MAPI_UNICODE)) {
        return HrSetLastError(ResultFromScode(MAPI_E_BAD_CHARWIDTH));
    }

    //
    //  Is the error asked for the last error registered with us?
    //

    if (hr != m_hrLast) {
        *lppMAPIError = NULL;
        return S_OK;
    }

    int         cch;
    int         cb;
    int         idsError;
    TCHAR*      szMessage = 0;
    TCHAR*      szComponent = 0;
    LPMAPIERROR pmapierr = NULL;

    //
    //  Based on the type of the last error, construct the appropriate
    //  return object
    //

    switch (m_eLastErr) {
    case eMAPI:
        //
        //  The last error registered was a MAPI error code.  For mapi
        //      error codes we map the MAPI error code into a resource
        //      id and return the appropriate string.
        //
        // as to spec, we allocate a single buffer for message and
        //      component.  no one will notice that we aren't doing
        //      MAPIAllocateMore for component.
        //
        //   We make an assumption as to the maximum possible length
        //      of the two strings combined.
        //

        Assert(m_pmapierr == NULL);
        if (MAPIAllocateBuffer(CchMaxErrorMessage*sizeof(TCHAR)+sizeof(MAPIERROR),
                               (void**)&pmapierr)) {
            return ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
        }

        //
        //  Set the version number
        //

        pmapierr->ulVersion = MAPI_ERROR_VERSION;
        pmapierr->ulLowLevelError = 0;

        //
        //   do the maping from the MAPI error code into a FORM string
        //      value.  The FORM eror code code will be set as the low
        //      level error value.
        //

        idsError = iFromHR(m_hrLast);
        pmapierr->ulContext = idsError;

        //
        //  Set the error string pointer to the appropriate location
        //      in the error buffer and load the error string.
        //

        pmapierr->lpszError = (LPTSTR) (sizeof(MAPIERROR) +
                                          (BYTE *) pmapierr);

        lstrcpy(pmapierr->lpszError, aszErrorStrings[idsError]);
        cch = lstrlen(aszErrorStrings[idsError]);

        //
        // Set the componment string pointer to the appropriate location
        //      in the error buffer and load the component string.
        //

        pmapierr->lpszComponent = pmapierr->lpszError + cch + 1;
        cch = CchMaxErrorMessage - cch - 1;

        lstrcpy(pmapierr->lpszComponent,
                        m_szComponent ? m_szComponent : SzNull);
        cch = lstrlen(pmapierr->lpszComponent);

        if (cch == 0) {
            *(pmapierr->lpszComponent) = 0;
        }

        break;

    case eObject:
        //
        //  The last regisered error message came from an object.  If we
        //      could not get the last error from the object, just return
        //      the error it returned and we are done.
        //

        if (m_hrGLE != NOERROR) {
            Assert( m_pmapierr == NULL );
            *lppMAPIError = NULL;
            return m_hrGLE;
        }

    case eExtended:
        //
        //  The last error was an extended error.  The error is in the
        //      structure, we need to copy this structure and return
        //      it back to the user
        //

        Assert( m_pmapierr != NULL );
        cb = lstrlen(m_pmapierr->lpszError) + lstrlen(m_pmapierr->lpszComponent);

        if (MAPIAllocateBuffer(cb + 2 + sizeof(MAPIERROR),
                               (void **) &pmapierr)) {
            return ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
        }

        *pmapierr = *m_pmapierr;
        pmapierr->lpszError = (LPTSTR) (sizeof(MAPIERROR) + (BYTE *) pmapierr);
        lstrcpy(pmapierr->lpszError, m_pmapierr->lpszError);
        pmapierr->lpszComponent = pmapierr->lpszError +
          lstrlen(pmapierr->lpszError) + 1;
        lstrcpy(pmapierr->lpszComponent, m_pmapierr->lpszComponent);

        break;

    case eNoError:
        break;

    default:
        Assert(0);
        return NOERROR;
    }

    *lppMAPIError = pmapierr;
    return ResultFromScode(S_OK);
}

int CLastError::ShowError(HWND hWnd)
{
    char szMessage[512];
    char szbuf[256];

    if(m_eLastErr != eObject || NULL == m_pmapierr) return 0;

    wsprintf(szMessage, "%s\n%s\nLowLevelError: 0x%08lx context: %ld ",
                        (m_pmapierr->lpszError ? m_pmapierr->lpszError:""),
                        m_pmapierr->lpszComponent ? m_pmapierr->lpszComponent:"",
                        m_pmapierr->ulLowLevelError, m_pmapierr->ulContext);

    wsprintf (szbuf, "\nReturn Code: 0x%08lx", SCODE(m_hrLast));
    lstrcat (szMessage, szbuf);

#if defined (_WIN32)
    *szHelpFile = '\0';

    int iret;
    BOOL fCanHelp;

    if(m_pmapierr->lpszError  &&  m_pmapierr->ulContext)
        fCanHelp = TRUE;
    else
        fCanHelp = FALSE;

    if(fCanHelp)
    {
        DWORD dw = GetPrivateProfileString("Help File Mappings", m_pmapierr->lpszComponent,
                            "", szHelpFile, _MAX_PATH, "mapisvc.inf");
        if(0 == dw)
            fCanHelp = FALSE;

        if(fCanHelp)
        {
            MSGBOXPARAMS mbp = {0};

            mbp.cbSize = sizeof(MSGBOXPARAMS);
            mbp.hwndOwner = hWnd;
            mbp.hInstance = NULL;
            mbp.lpszText = szMessage;
            mbp.lpszCaption = m_szComponent ? m_szComponent : "Error!";
            mbp.dwStyle = MB_ICONSTOP | MB_OK | MB_HELP;
            mbp.lpszIcon = NULL;
            mbp.dwContextHelpId = m_pmapierr->ulContext;
            mbp.lpfnMsgBoxCallback = ErrorBoxCallBack;
            mbp.dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);

            iret = MessageBoxIndirect(&mbp);
        }

    }

    if(!fCanHelp)
        iret = MessageBox (hWnd, szMessage,
                    m_szComponent ? m_szComponent : "Error!",
                        MB_ICONSTOP | MB_OK );

    *szHelpFile = '\0';

    return iret;

#else
    return MessageBox (hWnd, szMessage,
                     m_szComponent ? m_szComponent : "Error!",
                         MB_ICONSTOP | MB_OK );
#endif
}

#if defined(_WIN32)
VOID CALLBACK ErrorBoxCallBack(LPHELPINFO lpHelpInfo)
{
    Assert(*szHelpFile != '\0');

    WinHelp(NULL, szHelpFile, HELP_CONTEXT,
            lpHelpInfo->dwContextId);
}
#endif //_WIN32


LASTERR.H

/////////////////////////////////////////////////////////////////
//  LASTERR.H
//
// Copyright 1986-1996 Microsoft Corporation. All Rights Reserved.
//
//
// last err support object.
//

//There should be no global objects of this class
// because by the time the dectructor of a global CLastError
// is called, MAPIFreeBuffer does not work.

#ifndef __LASTERR_H__
#define __LASTERR_H__

class CLastError
{
public:
    CLastError(LPSTR);
    ~CLastError(void);

    // standard OLE or MAPI errors.
    HRESULT     HrSetLastError(HRESULT hr);

    // our internal extended error codes or a non-standard string for OLE or MAPI errors
    // scFORM is one of the errors defined by MAKE_FORM_X_SCODE macro family
    HRESULT     HrSetLastError(HRESULT hr, SCODE scFORM, ...);

    // errors returned from underlying objects.
    HRESULT     HrSetLastError(HRESULT hr, IUnknown* punk);

    // our implementation of GetLastError
    HRESULT     HrGetLastError(HRESULT hr, DWORD dwFlags,
                               LPMAPIERROR * lppMAPIError);

    //displays the last error info
    int         ShowError(HWND);

private:
    // we have three possible error types: our internal errors which
    //  we signify by MAPI_E_EXTENDED to the user, standard errors
    //  defined by MAPI and errors returned by objects we keep and utilize.

    enum {eNoError, eExtended, eMAPI, eObject} m_eLastErr;

    HRESULT     m_hrLast;

    HRESULT     m_hrGLE;  // what GetLastError on the object returned; mostly 0
    LPMAPIERROR m_pmapierr;
    LPSTR m_szComponent;
};

inline CLastError::CLastError(LPSTR szComponent)
{
    m_eLastErr = eNoError;
    m_hrLast = 0;
    m_hrGLE = 0;
    m_pmapierr = 0;

    m_szComponent = NULL;

    if(!MAPIAllocateBuffer(lstrlen(szComponent) +1,
                        (LPVOID *) &m_szComponent))
    {
        lstrcpy(m_szComponent, szComponent);
    }

}

inline CLastError::~CLastError()
{
    if (m_pmapierr != NULL)
    {
        MAPIFreeBuffer(m_pmapierr);
    }
    if(m_szComponent != NULL)
    {
        MAPIFreeBuffer(m_szComponent);
    }
}

#endif // __LASTERR_H__


_SCODE.H

/*
 *  _ S C O D E . H
 *
 *  Status Codes returned by MAPI routines
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#ifndef _SCODE_H
#define _SCODE_H

/* Define S_OK and ITF_* */

#ifdef _WIN32
#include <winerror.h>
#endif

/*
 *  MAPI Status codes follow the style of OLE 2.0 sCodes as defined in the
 *  OLE 2.0 Programmer's Reference and header file scode.h (Windows 3.x)
 *  or winerror.h (Windows NT and Windows 95).
 *
 */

/*  On Windows 3.x, status codes have 32-bit values as follows:
 *
 *   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
 *  +-+---------------------+-------+-------------------------------+
 *  |S|       Context       | Facil |               Code            |
 *  +-+---------------------+-------+-------------------------------+
 *
 *  where
 *
 *      S - is the severity code
 *
 *          0 - SEVERITY_SUCCESS
 *          1 - SEVERITY_ERROR
 *
 *      Context - context info
 *
 *      Facility - is the facility code
 *
 *          0x0 - FACILITY_NULL     generally useful errors ([SE]_*)
 *          0x1 - FACILITY_RPC      remote procedure call errors (RPC_E_*)
 *          0x2 - FACILITY_DISPATCH late binding dispatch errors
 *          0x3 - FACILITY_STORAGE  storage errors (STG_E_*)
 *          0x4 - FACILITY_ITF      interface-specific errors
 *
 *      Code - is the facility's status code
 *
 *
 */

/*
 *  On Windows NT 3.5 and Windows 95, scodes are 32-bit values
 *  laid out as follows:
 *
 *    3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 *    1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
 *   +-+-+-+-+-+---------------------+-------------------------------+
 *   |S|R|C|N|r|    Facility         |               Code            |
 *   +-+-+-+-+-+---------------------+-------------------------------+
 *
 *   where
 *
 *      S - Severity - indicates success/fail
 *
 *          0 - Success
 *          1 - Fail (COERROR)
 *
 *      R - reserved portion of the facility code, corresponds to NT's
 *          second severity bit.
 *
 *      C - reserved portion of the facility code, corresponds to NT's
 *          C field.
 *
 *      N - reserved portion of the facility code. Used to indicate a
 *          mapped NT status value.
 *
 *      r - reserved portion of the facility code. Reserved for internal
 *          use. Used to indicate HRESULT values that are not status
 *          values, but are instead message ids for display strings.
 *
 *      Facility - is the facility code
 *          FACILITY_NULL                    0x0
 *          FACILITY_RPC                     0x1
 *          FACILITY_DISPATCH                0x2
 *          FACILITY_STORAGE                 0x3
 *          FACILITY_ITF                     0x4
 *          FACILITY_WIN32                   0x7
 *          FACILITY_WINDOWS                 0x8
 *
 *      Code - is the facility's status code
 *
 */

/*
 *  We can't use OLE 2.0 macros to build sCodes because the definition has
 *  changed and we wish to conform to the new definition.
 */

/* The following two macros are used to build OLE 2.0 style sCodes */

#ifdef  SUCCESS_SUCCESS
#undef  SUCCESS_SUCCESS
#endif
#define SUCCESS_SUCCESS     0L

/* General errors (used by more than one MAPI object) */

Sc(MAPI_E_NO_SUPPORT),
Sc(MAPI_E_BAD_CHARWIDTH),
Sc(MAPI_E_STRING_TOO_LONG),
Sc(MAPI_E_UNKNOWN_FLAGS),
Sc(MAPI_E_INVALID_ENTRYID),
Sc(MAPI_E_INVALID_OBJECT),
Sc(MAPI_E_OBJECT_CHANGED),
Sc(MAPI_E_OBJECT_DELETED),
Sc(MAPI_E_BUSY),
Sc(MAPI_E_NOT_ENOUGH_DISK),
Sc(MAPI_E_NOT_ENOUGH_RESOURCES),
Sc(MAPI_E_NOT_FOUND),
Sc(MAPI_E_VERSION),
Sc(MAPI_E_LOGON_FAILED),
Sc(MAPI_E_SESSION_LIMIT),
Sc(MAPI_E_USER_CANCEL),
Sc(MAPI_E_UNABLE_TO_ABORT),
Sc(MAPI_E_NETWORK_ERROR),
Sc(MAPI_E_DISK_ERROR),
Sc(MAPI_E_TOO_COMPLEX),
Sc(MAPI_E_BAD_COLUMN),
Sc(MAPI_E_EXTENDED_ERROR),
Sc(MAPI_E_COMPUTED),
Sc(MAPI_E_CORRUPT_DATA),
Sc(MAPI_E_UNCONFIGURED),
Sc(MAPI_E_FAILONEPROVIDER),
Sc(MAPI_E_UNKNOWN_CPID),
Sc(MAPI_E_UNKNOWN_LCID),

/* MAPI base function and status object specific errors and warnings */

Sc(MAPI_E_END_OF_SESSION),
Sc(MAPI_E_UNKNOWN_ENTRYID),
Sc(MAPI_E_MISSING_REQUIRED_COLUMN),
Sc(MAPI_W_NO_SERVICE),

/* Property specific errors and warnings */

Sc(MAPI_E_BAD_VALUE),
Sc(MAPI_E_INVALID_TYPE),
Sc(MAPI_E_TYPE_NO_SUPPORT),
Sc(MAPI_E_UNEXPECTED_TYPE),
Sc(MAPI_E_TOO_BIG),
Sc(MAPI_E_DECLINE_COPY),
Sc(MAPI_E_UNEXPECTED_ID),

Sc(MAPI_W_ERRORS_RETURNED),

/* Table specific errors and warnings */

Sc(MAPI_E_UNABLE_TO_COMPLETE),
Sc(MAPI_E_TIMEOUT),
Sc(MAPI_E_TABLE_EMPTY),
Sc(MAPI_E_TABLE_TOO_BIG),

Sc(MAPI_E_INVALID_BOOKMARK),

Sc(MAPI_W_POSITION_CHANGED),
Sc(MAPI_W_APPROX_COUNT),

/* Transport specific errors and warnings */

Sc(MAPI_E_WAIT),
Sc(MAPI_E_CANCEL),
Sc(MAPI_E_NOT_ME),

Sc(MAPI_W_CANCEL_MESSAGE),

/* Message Store, Folder, and Message specific errors and warnings */

Sc(MAPI_E_CORRUPT_STORE),
Sc(MAPI_E_NOT_IN_QUEUE),
Sc(MAPI_E_NO_SUPPRESS),
Sc(MAPI_E_COLLISION),
Sc(MAPI_E_NOT_INITIALIZED),
Sc(MAPI_E_NON_STANDARD),
Sc(MAPI_E_NO_RECIPIENTS),
Sc(MAPI_E_SUBMITTED),
Sc(MAPI_E_HAS_FOLDERS),
Sc(MAPI_E_HAS_MESSAGES),
Sc(MAPI_E_FOLDER_CYCLE),

Sc(MAPI_W_PARTIAL_COMPLETION),

/* Address Book specific errors and warnings */

Sc(MAPI_E_AMBIGUOUS_RECIP),

/* The range 0x0800 to 0x08FF is reserved */

/* Obsolete typing shortcut that will go away eventually. */
#ifndef MakeResult
#endif

/* We expect these to eventually be defined by OLE, but for now,
 * here they are.  When OLE defines them they can be much more
 * efficient than these, but these are "proper" and don't make
 * use of any hidden tricks.
 */
#ifndef HR_SUCCEEDED
#endif

#endif  /* _SCODE_H */


MAPIDBG.C

/*
 *  MAPIDBG.C
 *
 *  MAPI Debugging Utilities
 *
 *  Copyright (C) 1986-1996 Microsoft Corporation. All rights reserved.
 */

#ifdef DEBUG

#pragma warning(disable:4100)   /* unreferenced formal parameter */
#pragma warning(disable:4127)   /* conditional expression is constant */
#pragma warning(disable:4201)   /* nameless struct/union */
#pragma warning(disable:4206)   /* translation unit is empty */
#pragma warning(disable:4209)   /* benign typedef redefinition */
#pragma warning(disable:4214)   /* bit field types other than int */
#pragma warning(disable:4001)   /* single line comments */
#pragma warning(disable:4050)   /* different code attributes */

#ifdef _MAC
#define INC_OLE2
#include <windows.h>
#include <macname1.h>
#include <macos\menus.h>
#include <stdio.h>
#include <mapiprof.h>

#define GetPrivateProfileIntA       MAPIGetPrivateProfileInt

#elif defined(WIN16) || defined(_WIN32)
#pragma warning(disable:4115)   /* named type definition in parentheses */
#include <windows.h>
#include <mapiwin.h>

#ifdef _WIN32
#pragma warning(disable:4001)   /* single line comments */
#pragma warning(disable:4115)   /* named type definition in parentheses */
#pragma warning (disable:4514)  /* unreferenced inline function */
#include <objerror.h>
#endif

#else

#include <stdio.h>
void __far __pascal OutputDebugString(char __far *);
#define wvsprintf           vsprintf
#define wsprintf            sprintf

#endif      /* _MAC */

#ifdef DOS
#define lstrcpyA            strcpy
#define lstrlenA            strlen
#define lstrcatA            strcat
#define wvsprintfA          wvsprintf
#define wsprintfA           wsprintf
#define OutputDebugStringA  OutputDebugString
#endif

#include <mapidbg.h>
#include <mapidefs.h>
#include <mapitags.h>
#include <mapicode.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#ifdef _MAC
#include <macname2.h>
#endif

#if defined(DBCS) && defined(DOS)
#include <gapidos.h>
#endif

#if defined(DEBUG) && defined(_WINNT)
#include <lmcons.h>
#include <lmalert.h>
#endif

/*  Patch/Hack for 16bit, optimized builds.
 *
 *  memcpy with a size of 0 bytes causes a
 *  crash.
 */

#ifndef __MEMCPY_H_
#define __MEMCPY_H_

#if defined(WIN16) && !defined(DEBUG)
#define MemCopy(_dst,_src,_cb)      do                                  \
                                    {                                   \
                                        size_t __cb = (size_t)(_cb);    \
                                        if (__cb)                       \
                                            memcpy(_dst,_src,__cb);     \
                                    } while (FALSE)
#else
#define MemCopy(_dst,_src,_cb)  memcpy(_dst,_src,(size_t)(_cb))
#endif

#endif

#if (defined(WIN16) || defined(DOS)) && !defined(NO_BASED_DEBUG)
#define BASED_DEBUG __based(__segname("DEBUG_DATA"))
#else
#define BASED_DEBUG
#endif

#if defined(WIN16)
#define BASED_CODE          __based(__segname("_CODE"))
#else
#define BASED_CODE
#endif

#if defined(WIN16) || defined(_WIN32)
static BOOL fTraceEnabled               = -1;
static BOOL fUseEventLog                = -1;
static BOOL fAssertLeaks                = -1;
#if defined(_WIN32) && !defined(_MAC)
BOOL fInhibitTrapThread                 = 2;
#endif

static char szKeyTraceEnabled[]         = "DebugTrace";
static char szKeyInhibitTrapThread[]    = "TrapOnSameThread";
static char szKeyEventLog[]             = "EventLog";
static char szKeyUseVirtual[]           = "VirtualMemory";
static char szKeyAssertLeaks[]          = "AssertLeaks";
static char szKeyCheckOften[]           = "CheckHeapOften";
static char szKeyFillRandom[]           = "MemoryFillRandom";
static char szSectionDebug[]            = "General";
static char szDebugIni[]                = "MAPIDBG.INI";
#endif

#ifndef VTABLE_FILL
#ifdef _MAC
#define VTABLE_FILL     NULL,
#else
#define VTABLE_FILL
#endif
#endif

#if defined(DEBUG) && defined(_WINNT)
typedef BOOL  (WINAPI   *ReportEventFN)(HANDLE, WORD, WORD, DWORD, PSID, WORD, DWORD, LPCTSTR *, LPVOID);
typedef HANDLE (WINAPI  *RegisterEventSourceAFN)(LPCTSTR, LPCTSTR);

ReportEventFN pfnReportEvent = NULL;
RegisterEventSourceAFN pfnRegisterEventSourceA = NULL;
#endif

#ifdef  WIN16
#pragma code_seg("Debug")
#endif

#if defined( _WINNT)

/*++

Routine Description:

    This routine returns if the service specified is running interactively
    (not invoked \by the service controller).

Arguments:

    None

Return Value:

    BOOL - TRUE if the service is an EXE.

Note:

--*/

BOOL WINAPI IsDBGServiceAnExe( VOID )
{
    HANDLE hProcessToken = NULL;
    DWORD groupLength = 50;

    PTOKEN_GROUPS groupInfo = (PTOKEN_GROUPS)LocalAlloc(0, groupLength);

    SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
    PSID InteractiveSid = NULL;
    PSID ServiceSid = NULL;
    DWORD i;

    // Start with assumption that process is an EXE, not a Service.
    BOOL fExe = TRUE;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hProcessToken))
        goto ret;

    if (groupInfo == NULL)
        goto ret;

    if (!GetTokenInformation(hProcessToken, TokenGroups, groupInfo,
        groupLength, &groupLength))
    {
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            goto ret;

        LocalFree(groupInfo);
        groupInfo = NULL;

        groupInfo = (PTOKEN_GROUPS)LocalAlloc(0, groupLength);

        if (groupInfo == NULL)
            goto ret;

        if (!GetTokenInformation(hProcessToken, TokenGroups, groupInfo,
            groupLength, &groupLength))
        {
            goto ret;
        }
    }

    //
    //  We now know the groups associated with this token.  We want to look to see if
    //  the interactive group is active in the token, and if so, we know that
    //  this is an interactive process.
    //
    //  We also look for the "service" SID, and if it's present, we know we're a service.
    //
    //  The service SID will be present iff the service is running in a
    //  user account (and was invoked by the service controller).
    //

    if (!AllocateAndInitializeSid(&siaNt, 1, SECURITY_INTERACTIVE_RID, 0, 0,
        0, 0, 0, 0, 0, &InteractiveSid))
    {
        goto ret;
    }

    if (!AllocateAndInitializeSid(&siaNt, 1, SECURITY_SERVICE_RID, 0, 0, 0,
        0, 0, 0, 0, &ServiceSid))
    {
        goto ret;
    }

    for (i = 0; i < groupInfo->GroupCount ; i += 1)
    {
        SID_AND_ATTRIBUTES sanda = groupInfo->Groups[i];
        PSID Sid = sanda.Sid;

        //
        //  Check to see if the group we're looking at is one of
        //  the 2 groups we're interested in.
        //

        if (EqualSid(Sid, InteractiveSid))
        {
            //
            //  This process has the Interactive SID in its
            //  token.  This means that the process is running as
            //  an EXE.
            //
            goto ret;
        }
        else if (EqualSid(Sid, ServiceSid))
        {
            //
            //  This process has the Service SID in its
            //  token.  This means that the process is running as
            //  a service running in a user account.
            //
            fExe = FALSE;
            goto ret;
        }
    }

    //
    //  Neither Interactive or Service was present in the current users token,
    //  This implies that the process is running as a service, most likely
    //  running as LocalSystem.
    //
    fExe = FALSE;

ret:

    if (InteractiveSid)
        FreeSid(InteractiveSid);

    if (ServiceSid)
        FreeSid(ServiceSid);

    if (groupInfo)
        LocalFree(groupInfo);

    if (hProcessToken)
        CloseHandle(hProcessToken);

    return(fExe);
}

#endif

/* LogIt */

#ifndef _MAC
void    LogIt(LPSTR plpcText, BOOL  fUseAlert)
{
#if defined(DEBUG) && defined(_WINNT)
    LPSTR           llpcStr[2];
    static HANDLE   hEventSource = NULL;

    if (pfnRegisterEventSourceA == NULL)
    {
        /* This handle is not important as the lib will be freed on exit (and it's debug only) */
        HINSTANCE       lhLib = LoadLibraryA("advapi32.dll");

        if (!lhLib)
            return;

        pfnRegisterEventSourceA = (RegisterEventSourceAFN) GetProcAddress(lhLib, "RegisterEventSourceA");
        pfnReportEvent = (ReportEventFN) GetProcAddress(lhLib, "ReportEventA");

        if (!pfnRegisterEventSourceA || !pfnReportEvent)
            return;
    }

    if (!hEventSource)
        hEventSource = pfnRegisterEventSourceA(NULL, "MAPIDebug");

    llpcStr[0] = "MAPI Debug Log";
    llpcStr[1] = plpcText;

    pfnReportEvent(hEventSource,    /* handle of event source */
        EVENTLOG_ERROR_TYPE,        /* event type             */
        0,                          /* event category         */
        0,                          /* event ID               */
        NULL,                       /* current user's SID     */
        2,                          /* strings in lpszStrings */
        0,                          /* no bytes of raw data   */
        llpcStr,                    /* array of error strings */
        NULL);                      /* no raw data            */

    /* Now we generate an Alert! */
    /* This code is adapted from PierreC's stuff, and NEEDS TO BE UNICODE!!!! */
    if (fUseAlert)
    {
#define MAX_LINE        256

typedef NET_API_STATUS  (WINAPI *NAREFN)(TCHAR *, ADMIN_OTHER_INFO *, ULONG, TCHAR *);

        BYTE                rgb[sizeof(ADMIN_OTHER_INFO) + (sizeof(WCHAR) * MAX_LINE)];
        ADMIN_OTHER_INFO *  poi     = (ADMIN_OTHER_INFO *) rgb;
        WCHAR *             pch     = (WCHAR *) (rgb + sizeof(ADMIN_OTHER_INFO));
        NET_API_STATUS      nas;
        static   NAREFN     fnNetAlertRaiseEx = NULL;

        /* Resolve function here, never free library as it's debug only */
        if (!fnNetAlertRaiseEx)
        {
            HINSTANCE       lhLib = LoadLibrary("NETAPI32.DLL");
            if (lhLib)
                fnNetAlertRaiseEx = (NAREFN) GetProcAddress(lhLib, "NetAlertRaiseEx");
        }

        if (fnNetAlertRaiseEx)
        {
            poi->alrtad_errcode = (DWORD) -1;
            poi->alrtad_numstrings = 1;

            if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plpcText, -1, pch, MAX_LINE))
            {
                nas = fnNetAlertRaiseEx(
                            (TCHAR *) L"ADMIN",
                            poi,
                            sizeof(ADMIN_OTHER_INFO) + ((lstrlenW(pch) + 1) * sizeof(WCHAR)),
                            (TCHAR *) L"MAPI Assert");

            }
        }
    }

#endif /* DEBUG && NT */
}
#endif /* !_MAC */

/* DebugOutputFn ------------------------------------------------------------ */

char BASED_CODE szCR[] = "\r";

void DebugOutputFn(char *psz)
{
#if defined(_MAC)

    OutputDebugString(psz);

#else

#if defined(WIN16) || defined(_WIN32)
    if (fTraceEnabled == -1)
    {
        fTraceEnabled = GetPrivateProfileIntA(szSectionDebug, szKeyTraceEnabled,
            0, szDebugIni);

        fUseEventLog = GetPrivateProfileIntA(szSectionDebug, szKeyEventLog,
            0, szDebugIni);
    }

    if (!fTraceEnabled)
        return;

    if (fUseEventLog)
#else
    if (FALSE)
#endif
        LogIt(psz, FALSE);

#ifdef WIN16
    OutputDebugString(psz);
    OutputDebugString(szCR);
#else
    OutputDebugStringA(psz);
    OutputDebugStringA(szCR);
#endif

#endif  /* _MAC */
}

/* DebugTrapFn -------------------------------------------------------------- */

#if defined(_WIN32) && !defined(_MAC)

typedef struct {
    char *      sz1;
    char *      sz2;
    UINT        rgf;
    int         iResult;
} MBContext;

DWORD WINAPI MessageBoxFnThreadMain(MBContext *pmbc)
{
    if (fUseEventLog)
    {
        LogIt(pmbc->sz1, TRUE);
        pmbc->iResult = IDIGNORE;
    }
    else
        pmbc->iResult = MessageBoxA(NULL, pmbc->sz1, pmbc->sz2,
            pmbc->rgf | MB_SETFOREGROUND);

    return(0);
}

int MessageBoxFn(char *sz1, char *sz2, UINT rgf)
{
    HANDLE      hThread;
    DWORD       dwThreadId;
    MBContext   mbc;

    mbc.sz1     = sz1;
    mbc.sz2     = sz2;
    mbc.rgf     = rgf;
    mbc.iResult = IDRETRY;

    #if defined(_WIN32) && !defined(_MAC)
    if (fInhibitTrapThread == 2)
        fInhibitTrapThread = GetPrivateProfileIntA(szSectionDebug,
            szKeyInhibitTrapThread, 0, szDebugIni);
    #endif

    if (fInhibitTrapThread)
    {
        MessageBoxFnThreadMain(&mbc);
    }
    else
    {
        hThread = CreateThread(NULL, 0,
            (PTHREAD_START_ROUTINE)MessageBoxFnThreadMain, &mbc, 0, &dwThreadId);

        if (hThread != NULL) {
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }
    }

    return(mbc.iResult);
}
#else
#define MessageBoxFn(sz1, sz2, rgf)     MessageBoxA(NULL, sz1, sz2, rgf)
#endif

int EXPORTDBG __cdecl DebugTrapFn(int fFatal, char *pszFile, int iLine, char *pszFormat, ...)
{
    char    sz[512];
    va_list vl;

    #if defined(WIN16) || defined(_WIN32)
    int     id;
    #endif
    #if defined(_WIN32) && !defined(_MAC)
    static int iServiceFlag = -1;
    #endif

    lstrcpyA(sz, "++++ MAPI Debug Trap (");
    _strdate(sz + lstrlenA(sz));
    lstrcatA(sz, " ");
    _strtime(sz + lstrlenA(sz));
    lstrcatA(sz, ")\n");
    DebugOutputFn(sz);

    va_start(vl, pszFormat);
    wvsprintfA(sz, pszFormat, vl);
    va_end(vl);

    wsprintfA(sz + lstrlenA(sz), "\n[File %s, Line %d]\n\n", pszFile, iLine);

    DebugOutputFn(sz);

    #if defined(DOS)
    _asm { int 3 }
    #endif

#if defined(WIN16) || defined(_WIN32)
    /* Hold down control key to prevent MessageBox */
    if ( GetAsyncKeyState(VK_CONTROL) >= 0 )
    {
        UINT uiFlags = MB_ABORTRETRYIGNORE;

        if (fFatal)
            uiFlags |= MB_DEFBUTTON1;
        else
            uiFlags |= MB_DEFBUTTON3;

        #ifdef WIN16
        uiFlags |= MB_ICONEXCLAMATION | MB_SYSTEMMODAL;
        #else
        uiFlags |= MB_ICONSTOP | MB_TASKMODAL;
        #endif

        #if defined(_WIN32) && !defined(_MAC)
        if (iServiceFlag == -1)
        {
            DWORD dwVersion = GetVersion();

            if (dwVersion & 0x80000000)
            {
                if (LOBYTE(LOWORD(dwVersion)) < 4)
                {
                    //  NT 3.51
                    iServiceFlag = 0x00040000;
                }
                else
                {
                    //  NT 4.0+
                    iServiceFlag = 0x00200000;
                }
            }
            else
                //  not NT, skip this
                iServiceFlag = 0;
        }

        if (!IsDBGServiceAnExe())
            uiFlags |= (UINT) iServiceFlag;
        #endif

        id = MessageBoxFn(sz, "MAPI Debug Trap", uiFlags);

        if (id == IDABORT)
            *((LPBYTE)NULL) = 0;
        else if (id == IDRETRY)
            DebugBreak();
    }
#endif

    return(0);
}

/* DebugTraceFn ------------------------------------------------------------- */

int EXPORTDBG __cdecl DebugTraceFn(char *pszFormat, ...)
{
    char    sz[768];
    int     fAutoLF = 0;
    va_list vl;

    if (*pszFormat == '~') {
        pszFormat += 1;
        fAutoLF = 1;
    }

    va_start(vl, pszFormat);
    wvsprintfA(sz, pszFormat, vl);
    va_end(vl);

#ifndef _MAC
    if (fAutoLF)
        lstrcatA(sz, "\n");
#endif

    DebugOutputFn(sz);

    return(0);
}

/* DebugTraceProblemsFn */

void EXPORTDBG __cdecl DebugTraceProblemsFn(LPSTR sz, LPVOID pv)
{
    LPSPropProblemArray pprobs = (LPSPropProblemArray)pv;
    SPropProblem *      pprob = pprobs->aProblem;
    int                 cprob = (int)pprobs->cProblem;

    DebugTraceFn("%s: SetProps problem\n", sz);
    while (cprob--)
    {
        DebugTraceFn("Property %s (index %ld): failed with %s\n",
            SzDecodeUlPropTagFn(pprob->ulPropTag),
            pprob->ulIndex,
            SzDecodeScodeFn(pprob->scode));
    }
}

/* SCODE & PropTag decoding ------------------------------------------------- */

typedef struct
{
    char *          psz;
    unsigned long   ulPropTag;
} PT;

typedef struct
{
    char *  psz;
    SCODE   sc;
} SC;

#define Pt(_ptag)   {#_ptag, _ptag}
#define Sc(_sc)     {#_sc, _sc}

#if !defined(DOS)
static PT BASED_DEBUG rgpt[] = {

#include "_tags.h"

/*
 * Property types
 */
    Pt(PR_NULL),
    Pt(PT_UNSPECIFIED),
    Pt(PT_NULL),
    Pt(PT_I2),
    Pt(PT_LONG),
    Pt(PT_R4),
    Pt(PT_DOUBLE),
    Pt(PT_CURRENCY),
    Pt(PT_APPTIME),
    Pt(PT_ERROR),
    Pt(PT_BOOLEAN),
    Pt(PT_OBJECT),
    Pt(PT_I8),
    Pt(PT_STRING8),
    Pt(PT_UNICODE),
    Pt(PT_SYSTIME),
    Pt(PT_CLSID),
    Pt(PT_BINARY),
    Pt(PT_TSTRING),
    Pt(PT_MV_I2),
    Pt(PT_MV_LONG),
    Pt(PT_MV_R4),
    Pt(PT_MV_DOUBLE),
    Pt(PT_MV_CURRENCY),
    Pt(PT_MV_APPTIME),
    Pt(PT_MV_SYSTIME),
    Pt(PT_MV_STRING8),
    Pt(PT_MV_BINARY),
    Pt(PT_MV_UNICODE),
    Pt(PT_MV_CLSID),
    Pt(PT_MV_I8)
};

#define cpt (sizeof(rgpt) / sizeof(PT))

static SC BASED_DEBUG rgsc[] = {

/* FACILITY_NULL error codes from OLE */

    Sc(S_OK),
    Sc(S_FALSE),

    Sc(E_UNEXPECTED),
    Sc(E_NOTIMPL),
    Sc(E_OUTOFMEMORY),
    Sc(E_INVALIDARG),
    Sc(E_NOINTERFACE),
    Sc(E_POINTER),
    Sc(E_HANDLE),
    Sc(E_ABORT),
    Sc(E_FAIL),
    Sc(E_ACCESSDENIED),

/* MAPI error codes from MAPICODE.H */
#include "_scode.h"

};

#define csc (sizeof(rgsc) / sizeof(SC))
#endif

char * EXPORTDBG __cdecl
SzDecodeScodeFn(SCODE sc)
{
    static char rgch[64];

    #if !defined(DOS)
    int isc;
    for (isc = 0; isc < csc; ++isc)
        if (sc == rgsc[isc].sc)
            return rgsc[isc].psz;
    #endif

    wsprintfA (rgch, "%08lX", sc);
    return rgch;
}

char * EXPORTDBG __cdecl
SzDecodeUlPropTypeFn(unsigned long ulPropType)
{
    static char rgch[8];

    switch (ulPropType)
    {
    case PT_UNSPECIFIED:    return("PT_UNSPECIFIED");   break;
    case PT_NULL:           return("PT_NULL");          break;
    case PT_I2:             return("PT_I2");            break;
    case PT_LONG:           return("PT_LONG");          break;
    case PT_R4:             return("PT_R4");            break;
    case PT_DOUBLE:         return("PT_DOUBLE");        break;
    case PT_CURRENCY:       return("PT_CURRENCY");      break;
    case PT_APPTIME:        return("PT_APPTIME");       break;
    case PT_ERROR:          return("PT_ERROR");         break;
    case PT_BOOLEAN:        return("PT_BOOLEAN");       break;
    case PT_OBJECT:         return("PT_OBJECT");        break;
    case PT_I8:             return("PT_I8");            break;
    case PT_STRING8:        return("PT_STRING8");       break;
    case PT_UNICODE:        return("PT_UNICODE");       break;
    case PT_SYSTIME:        return("PT_SYSTIME");       break;
    case PT_CLSID:          return("PT_CLSID");         break;
    case PT_BINARY:         return("PT_BINARY");        break;
    }

    wsprintfA(rgch, "0x%04lX", ulPropType);
    return rgch;
}

char *  EXPORTDBG __cdecl
SzDecodeUlPropTagFn(unsigned long ulPropTag)
{
    static char rgch[64];

    #if !defined(DOS)
    int ipt;
    for (ipt = 0; ipt < cpt; ++ipt)
        if (ulPropTag == rgpt[ipt].ulPropTag)
            return rgpt[ipt].psz;
    #endif

    wsprintfA(rgch, "PROP_TAG(%s, 0x%04lX)",
        SzDecodeUlPropType(PROP_TYPE(ulPropTag)),
        PROP_ID(ulPropTag));
    return rgch;
}

SCODE  EXPORTDBG __cdecl
ScodeFromSzFn(char *psz)
{
    #if !defined(DOS)
    int isc;
    for (isc = 0; isc < csc; ++isc)
        {
        if (lstrcmpA(psz, rgsc[isc].psz) == 0)
            {
            return rgsc[isc].sc;
            }
        }
    #endif
    return 0;
}

unsigned long EXPORTDBG __cdecl
UlPropTagFromSzFn(char *psz)
{
    #if !defined(DOS)
    int ipt;
    for (ipt = 0; ipt < cpt; ++ipt)
        {
        if (lstrcmpA(psz, rgpt[ipt].psz) == 0)
            {
            return rgpt[ipt].ulPropTag;
            }
        }
    #endif
    return 0;
}

/* ScCheckScFn -------------------------------------------------------------- */

#if !defined(DOS)

SCODE EXPORTDBG __cdecl ScCheckScFn(    SCODE   sc,
                    SCODE * lpscLegal,
                    char *  lpszMethod,
                    char *  lpszFile,
                    int     iLine)
{
    BOOL fIsQueryInterface = (lpscLegal == IUnknown_QueryInterface_Scodes);

    if (sc == S_OK)
        return(sc);

    while( *lpscLegal != S_OK && sc != *lpscLegal )
    {
        lpscLegal++;
    }

    if ( *lpscLegal == S_OK )
    {
        SCODE *lpscNextCommon = Common_Scodes;

        /* see if this is a common scode */
            if ( !fIsQueryInterface )
                while(  *lpscNextCommon != S_OK &&
                        sc != *lpscNextCommon )
                {
                    lpscNextCommon++;
                }

        /* this is an illegal error or an RPC error */
           if ( (*lpscNextCommon == S_OK || fIsQueryInterface) &&
                ( SCODE_FACILITY(sc) != FACILITY_RPC) )
           {
                DebugTrace( "Unrecognized scode %s from %s\n\t in file %s line %d\n",
                        SzDecodeScode( sc ), lpszMethod, lpszFile, iLine);
            }
    }

    return(sc);
}
#endif

/* SCODE lists -------------------------------------------------------------- */

#if !defined(DOS)

#define STANDARD_OPENENTRY_SCODES \
    E_NOINTERFACE,  \
    MAPI_E_NOT_FOUND

SCODE BASED_DEBUG Common_Scodes[] =
{
    MAPI_E_BAD_CHARWIDTH,
    MAPI_E_CALL_FAILED,
    MAPI_E_INVALID_ENTRYID,
    MAPI_E_INVALID_OBJECT,
    MAPI_E_INVALID_PARAMETER,
    MAPI_E_NO_ACCESS,
    MAPI_E_NO_SUPPORT,
    MAPI_E_NOT_ENOUGH_MEMORY,
    MAPI_E_UNKNOWN_FLAGS,
    S_OK
};

SCODE BASED_DEBUG MAPILogon_Scodes[] =
{
    MAPI_E_NOT_INITIALIZED,
    MAPI_E_LOGON_FAILED,
    S_OK
};

SCODE BASED_DEBUG MAPIAllocateBuffer_Scodes[] =
{
    MAPI_E_NOT_INITIALIZED,
    S_OK
};

SCODE BASED_DEBUG MAPIAllocateMore_Scodes[] =
{
    MAPI_E_NOT_INITIALIZED,
    S_OK
};

SCODE BASED_DEBUG MAPIFreeBuffer_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IUnknown_QueryInterface_Scodes[] =
{
    E_INVALIDARG,
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IUnknown_GetLastError_Scodes[] =
{
    MAPI_E_EXTENDED_ERROR,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_CopyTo_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    MAPI_E_INVALID_TYPE,
    MAPI_E_FOLDER_CYCLE,
    MAPI_E_DECLINE_COPY,
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_CopyProps_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    MAPI_W_PARTIAL_COMPLETION,
    MAPI_E_INVALID_TYPE,
    MAPI_E_FOLDER_CYCLE,
    MAPI_E_DECLINE_COPY,
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_DeleteProps_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    MAPI_E_INVALID_TYPE,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_GetIDsFromNames_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    MAPI_E_TABLE_TOO_BIG,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_GetLastError_Scodes[] =
{
    MAPI_E_EXTENDED_ERROR,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_GetNamesFromIDs_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_GetPropList_Scodes[] =
{
    MAPI_W_ERRORS_RETURNED,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_GetProps_Scodes[] =
{
    MAPI_E_NOT_FOUND,
    MAPI_E_OBJECT_DELETED,
    MAPI_W_ERRORS_RETURNED,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_OpenProperty_Scodes[] =
{
    MAPI_E_INTERFACE_NOT_SUPPORTED,
    MAPI_E_NOT_FOUND,
    MAPI_E_OBJECT_DELETED,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_SetProps_Scodes[] =
{
    MAPI_E_COMPUTED,
    MAPI_E_UNEXPECTED_TYPE,
    MAPI_E_INVALID_TYPE,
    S_OK
};

SCODE BASED_DEBUG IMAPIProp_SaveChanges_Scodes[] =
{
    MAPI_E_NOT_ENOUGH_DISK,
    MAPI_E_OBJECT_CHANGED,
    MAPI_E_OBJECT_DELETED,
    S_OK
};

SCODE BASED_DEBUG IStream_Read_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Write_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Seek_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_SetSize_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Tell_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_LockRegion_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_UnlockRegion_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Clone_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_CopyTo_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Revert_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Stat_Scodes[] = {S_OK};
SCODE BASED_DEBUG IStream_Commit_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMAPITable_GetLastError_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_Advise_Scodes[] =
{
    S_OK
};
SCODE BASED_DEBUG IMAPITable_Unadvise_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_GetStatus_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_SetColumns_Scodes[] =
{
    MAPI_E_BUSY,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_QueryColumns_Scodes[] =
{
    MAPI_E_BUSY,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_GetRowCount_Scodes[] =
{
    MAPI_E_BUSY,
    MAPI_W_APPROX_COUNT,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_SeekRow_Scodes[] =
{
    MAPI_E_INVALID_BOOKMARK,
    MAPI_E_UNABLE_TO_COMPLETE,
    MAPI_W_POSITION_CHANGED,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_SeekRowApprox_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_QueryPosition_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_FindRow_Scodes[] =
{
    MAPI_E_INVALID_BOOKMARK,
    MAPI_E_NOT_FOUND,
    MAPI_W_POSITION_CHANGED,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_Restrict_Scodes[] =
{
    MAPI_E_BUSY,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_CreateBookmark_Scodes[] =
{
    MAPI_E_UNABLE_TO_COMPLETE,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_FreeBookmark_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_SortTable_Scodes[] =
{
    MAPI_E_TOO_COMPLEX,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_QuerySortOrder_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_QueryRows_Scodes[] =
{
    MAPI_E_INVALID_BOOKMARK,
    MAPI_W_POSITION_CHANGED,
    S_OK
};

SCODE BASED_DEBUG IMAPITable_Abort_Scodes[] =
{
    MAPI_E_UNABLE_TO_ABORT,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_ExpandRow_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_CollapseRow_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_WaitForCompletion_Scodes[] =
{
    MAPI_E_TIMEOUT,
    S_OK
};
SCODE BASED_DEBUG IMAPITable_GetCollapseState_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPITable_SetCollapseState_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMAPISession_LogOff_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_Release_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_GetLastError_Scodes[] =
{
    MAPI_E_EXTENDED_ERROR,
    S_OK
};
SCODE BASED_DEBUG IMAPISession_GetMsgStoresTable_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_GetStatusTable_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_OpenMsgStore_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_OpenAddressBook_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMAPISession_OpenEntry_Scodes[] =
{
    STANDARD_OPENENTRY_SCODES,
    S_OK
};

SCODE BASED_DEBUG IMAPISession_OpenProfileSection_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_Advise_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_Unadvise_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_CompareEntryIDs_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_MessageOptions_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_QueryDefaultMessageOpt_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMAPISession_EnumAdrTypes_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPISession_QueryIdentity_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMsgStore_OpenEntry_Scodes[] =
{
    STANDARD_OPENENTRY_SCODES,
    MAPI_E_SUBMITTED,
    S_OK
};

SCODE BASED_DEBUG IMsgStore_SetReceiveFolder_Scodes[] =
{
    MAPI_E_BAD_CHARWIDTH,
    MAPI_E_NOT_FOUND,
    S_OK
};

SCODE BASED_DEBUG IMsgStore_GetReceiveFolder_Scodes[] =
{
    MAPI_E_BAD_CHARWIDTH,
    S_OK
};

SCODE BASED_DEBUG IMsgStore_GetReceiveFolderTable_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMsgStore_StoreLogoff_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMsgStore_Advise_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMsgStore_Unadvise_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMsgStore_CompareEntryIDs_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMsgStore_GetOutgoingQueue_Scodes[] = {
    MAPI_E_NO_SUPPORT,
    S_OK};
SCODE BASED_DEBUG IMsgStore_SetLockState_Scodes[] = {
    MAPI_E_NO_SUPPORT,
    MAPI_E_NOT_FOUND,
    S_OK};
SCODE BASED_DEBUG IMsgStore_FinishedMsg_Scodes[] = {
    MAPI_E_NO_SUPPORT,
    S_OK};
SCODE BASED_DEBUG IMsgStore_AbortSubmit_Scodes[] = {
    MAPI_E_UNABLE_TO_ABORT,
    MAPI_E_NOT_IN_QUEUE,
    S_OK};
SCODE BASED_DEBUG IMsgStore_NotifyNewMail_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMAPIFolder_GetContentsTable_Scodes[] =
{
    MAPI_E_OBJECT_DELETED,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_GetHierarchyTable_Scodes[] =
{
    MAPI_E_OBJECT_DELETED,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_SaveContentsSort_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_OpenEntry_Scodes[] =
{
    STANDARD_OPENENTRY_SCODES,
    MAPI_E_SUBMITTED,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_CreateMessage_Scodes[] =
{
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_CopyMessages_Scodes[] =
{
    E_NOINTERFACE,
    MAPI_E_SUBMITTED,
    MAPI_E_DECLINE_COPY,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_DeleteMessages_Scodes[] =
{
    MAPI_E_SUBMITTED,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_CreateFolder_Scodes[] =
{
    E_NOINTERFACE,
    MAPI_E_COLLISION,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_CopyFolder_Scodes[] =
{
    E_NOINTERFACE,
    MAPI_E_COLLISION,
    MAPI_E_FOLDER_CYCLE,
    MAPI_E_DECLINE_COPY,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_DeleteFolder_Scodes[] =
{
    MAPI_E_HAS_FOLDERS,
    MAPI_E_HAS_MESSAGES,
    MAPI_E_SUBMITTED,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_SetSearchCriteria_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_GetSearchCriteria_Scodes[] =
{
    MAPI_E_NOT_INITIALIZED,
    MAPI_E_CORRUPT_STORE,
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_SetReadFlags_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_GetMessageStatus_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_SetMessageStatus_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMAPIFolder_EmptyFolder_Scodes[] =
{
    MAPI_E_SUBMITTED,
    S_OK
};

SCODE BASED_DEBUG IMessage_GetAttachmentTable_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMessage_OpenAttach_Scodes[] =
{
    MAPI_E_NOT_FOUND,
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IMessage_CreateAttach_Scodes[] =
{
    E_NOINTERFACE,
    S_OK
};

SCODE BASED_DEBUG IMessage_DeleteAttach_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMessage_GetRecipientTable_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IMessage_ModifyRecipients_Scodes[] =
{
    MAPI_E_NOT_FOUND,
    S_OK
};

SCODE BASED_DEBUG IMessage_SubmitMessage_Scodes[] =
{
    MAPI_E_NO_RECIPIENTS,
    MAPI_E_NON_STANDARD,
    S_OK
};

SCODE BASED_DEBUG IMessage_SetReadFlag_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IAttach_SaveChanges_Scodes[] =
{
    S_OK
};

SCODE BASED_DEBUG IAddrBook_OpenEntry_Scodes[] =
{
    STANDARD_OPENENTRY_SCODES,
    S_OK
};

SCODE BASED_DEBUG IAddrBook_CompareEntryIDs_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_CreateOneOff_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_ResolveName_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_Address_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_Details_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_RecipOptions_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_QueryDefaultRecipOpt_Scodes[] = {S_OK};
SCODE BASED_DEBUG IAddrBook_ButtonPress_Scodes[] = {S_OK};
SCODE BASED_DEBUG IABContainer_GetContentsTable_Scodes[] = {S_OK};
SCODE BASED_DEBUG IABContainer_GetHierarchyTable_Scodes[] = {S_OK};
SCODE BASED_DEBUG INotifObj_ChangeEvMask_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPIStatus_ChangePassword_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPIStatus_FlushQueues_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPIStatus_SettingsDialog_Scodes[] = {S_OK};
SCODE BASED_DEBUG IMAPIStatus_ValidateState_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPILogon_Scodes[] = {
    MAPI_E_LOGON_FAILED,
    S_OK};
SCODE BASED_DEBUG SMAPI_MAPILogoff_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIFreeBuffer_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPISendMail_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPISendDocuments_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIFindNext_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIReadMail_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPISaveMail_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIDeleteMail_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIAddress_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIResolveName_Scodes[] = {S_OK};
SCODE BASED_DEBUG SMAPI_MAPIDetails_Scodes[] = {S_OK};

SCODE BASED_DEBUG IMSProvider_Logon_Scodes[] = {
    MAPI_E_UNCONFIGURED,
    MAPI_E_FAILONEPROVIDER,
    MAPI_E_STRING_TOO_LONG,
    MAPI_E_LOGON_FAILED,
    MAPI_E_CORRUPT_STORE,
    MAPI_E_USER_CANCEL,
    S_OK};
SCODE BASED_DEBUG IMSProvider_Deinit_Scodes[] = {
    S_OK};
SCODE BASED_DEBUG IMSProvider_Shutdown_Scodes[] = {
    S_OK};

SCODE BASED_DEBUG IMSProvider_Init_Scodes[] = {
    MAPI_E_VERSION,
    S_OK};
SCODE BASED_DEBUG IMSProvider_SpoolerLogon_Scodes[] = {
    MAPI_E_LOGON_FAILED,
    S_OK};

SCODE BASED_DEBUG IMSLogon_OpenEntry_Scodes[] =
{
    STANDARD_OPENENTRY_SCODES,
    S_OK
};

SCODE BASED_DEBUG IMSLogon_OpenStatusEntry_Scodes[] = {
    S_OK};

SCODE BASED_DEBUG IMSLogon_CompareEntryIDs_Scodes[] = {
    S_OK};

SCODE BASED_DEBUG IMSLogon_Advise_Scodes[] = {
    S_OK};
SCODE BASED_DEBUG IMSLogon_Unadvise_Scodes[] = {
    S_OK};
SCODE BASED_DEBUG IMSLogon_Logoff_Scodes[] = {
    S_OK};
#endif

/* DBGMEM ------------------------------------------------------------------- */

#undef  INTERFACE
#define INTERFACE struct _DBGMEM
DECLARE_INTERFACE(DBGMEM_)
{
    BEGIN_INTERFACE
    STDMETHOD(QueryInterface)       (THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE; \
    STDMETHOD_(ULONG,AddRef)        (THIS) PURE; \
    STDMETHOD_(ULONG,Release)       (THIS) PURE; \
    STDMETHOD_(void FAR*, Alloc)    (THIS_ ULONG cb) PURE; \
    STDMETHOD_(void FAR*, Realloc)  (THIS_ void FAR* pv, ULONG cb) PURE; \
    STDMETHOD_(void, Free)          (THIS_ void FAR* pv) PURE; \
    STDMETHOD_(ULONG, GetSize)      (THIS_ void FAR* pv) PURE; \
    STDMETHOD_(int, DidAlloc)       (THIS_ void FAR* pv) PURE; \
    STDMETHOD_(void, HeapMinimize)  (THIS) PURE; \
};

extern DBGMEM_Vtbl vtblDBGMEM;

typedef struct _DBGMEM  DBGMEM,  FAR *PDBGMEM;
typedef struct _BLK     BLK,     *PBLK;
typedef struct _BLK UNALIGNED * PUABLK;
typedef struct _BLKTAIL BLKTAIL, *PBLKTAIL;

struct _DBGMEM {
    DBGMEM_Vtbl *       lpVtbl;
    ULONG               cRef;
    LPMALLOC            pmalloc;
    char                szSubsys[16];
    ULONG               ulAllocNum;
    ULONG               ulAllocAt;
    ULONG               ulFailureAt;
    BOOL                fCheckOften;
    BOOL                fUnleakable;
    ULONG               cbVirtual;
    BOOL                fFillRandom;
    int                 cbExtra;
    int                 cbTail;
    PBLK                pblkHead;
#if defined(_WIN32) && defined(_X86_)
    CRITICAL_SECTION    cs;
#endif
};

#define NCALLERS    12

struct _BLK {
    PDBGMEM         pdbgmem;        /* pointer to the allocator */
    PBLK            pblkNext;       /* next link in chain of allocated blocks */
    PBLK            pblkPrev;       /* prev link in chain of allocated blocks */
    ULONG           ulAllocNum;     /* internal allocation number */
    BOOL            fUnleakable;    /* TRUE if leak code should ignore block */
    #if defined(_WIN32) && defined(_X86_)
    FARPROC         pfnCallers[NCALLERS];
    #endif
    PBLKTAIL        pblktail;       /* pointer to block tail */
};

struct _BLKTAIL {
    PBLK            pblk;           /* pointer back to beginning of the block */
};

#define PblkToPv(pblk)          ((LPVOID)((PBLK)(pblk) + 1))
#define PvToPblk(pblk)          ((PBLK)(pv) - 1)
#define PblkClientSize(pblk)    ((ULONG)((char *)(pblk)->pblktail - (char *)PblkToPv(pblk)))
#define PblkAllocSize(pblk)     (PblkClientSize(pblk) + sizeof(BLK) + (pblk)->pdbgmem->cbTail)

#if defined(_WIN32) && defined(_X86_)
#define DBGMEM_EnterCriticalSection(pdbgmem)    \
        EnterCriticalSection(&(pdbgmem)->cs)
#define DBGMEM_LeaveCriticalSection(pdbgmem)    \
        LeaveCriticalSection(&(pdbgmem)->cs)
#else
#define DBGMEM_EnterCriticalSection(pdbgmem)
#define DBGMEM_LeaveCriticalSection(pdbgmem)
#endif

#define INITGUID
#include <initguid.h>

DEFINE_OLEGUID(DBGMEM_IID_IUnknown,     0x00000000L, 0, 0);
DEFINE_OLEGUID(DBGMEM_IID_IMalloc,      0x00000002L, 0, 0);
DEFINE_OLEGUID(DBGMEM_IID_IBaseMalloc,  0x000203FFL, 0, 0);

/* Forward Declarations ----------------------------------------------------- */

BOOL DBGMEM_ValidatePblk(PDBGMEM pdbgmem, PBLK pblk, char ** pszReason);
BOOL DBGMEM_ValidatePv(PDBGMEM pdbgmem, void * pv, char * pszFunc);
STDMETHODIMP_(void) DBGMEM_Free(PDBGMEM pdbgmem, void * pv);

/* Call Stack (_WIN32) ------------------------------------------------------- */

#if defined(_WIN32) && defined(_X86_)

#ifdef _WIN95
#define dwStackLimit    0x00400000      /*  4MB for Windows 95 */
#else
#define dwStackLimit    0x00010000      /*  64KB for NT */
#endif

void EXPORTDBG __cdecl GetCallStack(DWORD *pdwCaller, int cSkip, int cFind)
{
    DWORD * pdwStack;
    DWORD * pdwStackPrev = (DWORD *)0;
    DWORD   dwCaller;

    __asm mov pdwStack, ebp

    memset(pdwCaller, 0, cFind * sizeof(DWORD));

    while (cSkip + cFind > 0)
    {
        pdwStack = (DWORD *)*pdwStack;

        if (    pdwStack <= (DWORD *)dwStackLimit
            ||  pdwStackPrev >= pdwStack
            ||  IsBadReadPtr(pdwStack, 2 * sizeof(DWORD)))
            break;

        dwCaller = *(pdwStack + 1);

        if (dwCaller <= dwStackLimit)
            break;
        else if (cSkip > 0)
            cSkip -= 1;
        else
        {
            *pdwCaller++ = dwCaller;
            cFind -= 1;

            pdwStackPrev = pdwStack;
        }
    }
}

#endif

/* Virtual Memory Support (_WIN32) ------------------------------------------- */

#if defined(_WIN32) && (defined(_X86_) || defined(_PPC_) || defined(_MIPS_))

#define PAGE_SIZE       4096
#define PvToVMBase(pv)  ((void *)((ULONG)pv & 0xFFFF0000))

BOOL VMValidatePvEx(void *pv, ULONG cbCluster)
{
    void *  pvBase;
    BYTE *  pb;

    pvBase = PvToVMBase(pv);

    pb = (BYTE *)pvBase + sizeof(ULONG);

    while (pb < (BYTE *)pv) {
        if (*pb++ != 0xAD) {
            TrapSz1("VMValidatePvEx(pv=%08lX): Block leader has been overwritten", pv);
            return(FALSE);
        }
    }

    if (cbCluster != 1)
    {
        ULONG cb = *((ULONG *)pvBase);
        ULONG cbPad = 0;

        if (cb % cbCluster)
            cbPad = (cbCluster - (cb % cbCluster));

        if (cbPad)
        {
            BYTE *pbMac;

            pb = (BYTE *)pv + cb;
            pbMac = pb + cbPad;

            while (pb < pbMac)
            {
                if (*pb++ != 0xBC)
                {
                    TrapSz1("VMValidatePvEx(pv=%08lX): Block trailer has been "
                        "overwritten", pv);
                    return(FALSE);
                }
            }
        }
    }

    return(TRUE);
}

void * EXPORTDBG __cdecl VMAlloc(ULONG cb)
{
    return VMAllocEx(cb, 1);
}

void * EXPORTDBG __cdecl VMAllocEx(ULONG cb, ULONG cbCluster)
{
    ULONG   cbAlloc;
    void *  pvR;
    void *  pvC;
    ULONG   cbPad   = 0;

    // a cluster size of 0 means don't use the virtual allocator.

    AssertSz(cbCluster != 0, "Cluster size is zero.");

    if (cb > 0x100000)
        return(0);

    if (cb % cbCluster)
        cbPad = (cbCluster - (cb % cbCluster));

    cbAlloc = sizeof(ULONG) + cb + cbPad + PAGE_SIZE - 1;
    cbAlloc -= cbAlloc % PAGE_SIZE;
    cbAlloc += PAGE_SIZE;

    pvR = VirtualAlloc(0, cbAlloc, MEM_RESERVE, PAGE_NOACCESS);

    if (pvR == 0)
        return(0);

    pvC = VirtualAlloc(pvR, cbAlloc - PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE);

    if (pvC != pvR)
    {
        VirtualFree(pvR, 0, MEM_RELEASE);
        return(0);
    }

    *(ULONG *)pvC = cb;

    memset((BYTE *)pvC + sizeof(ULONG), 0xAD,
        (UINT) cbAlloc - cb - cbPad - sizeof(ULONG) - PAGE_SIZE);

    if (cbPad)
        memset((BYTE *)pvC + cbAlloc - PAGE_SIZE - cbPad, 0xBC,
            (UINT) cbPad);

    return((BYTE *)pvC + (cbAlloc - cb - cbPad - PAGE_SIZE));
}

void EXPORTDBG __cdecl VMFree(void *pv)
{
    VMFreeEx(pv, 1);
}

void EXPORTDBG __cdecl VMFreeEx(void *pv, ULONG cbCluster)
{
    VMValidatePvEx(pv, cbCluster);

    if (!VirtualFree(PvToVMBase(pv), 0, MEM_RELEASE))
        TrapSz2("VMFreeEx(pv=%08lX): VirtualFree failed (%08lX)",
            pv, GetLastError());
}

void * EXPORTDBG __cdecl VMRealloc(void *pv, ULONG cb)
{
    return VMReallocEx(pv, cb, 1);
}

void * EXPORTDBG __cdecl VMReallocEx(void *pv, ULONG cb, ULONG cbCluster)
{
    void *  pvNew = 0;
    ULONG   cbCopy;

    VMValidatePvEx(pv, cbCluster);

    cbCopy = *(ULONG *)PvToVMBase(pv);
    if (cbCopy > cb)
        cbCopy = cb;

    pvNew = VMAllocEx(cb, cbCluster);

    if (pvNew)
    {
        MemCopy(pvNew, pv, cbCopy);
        VMFreeEx(pv, cbCluster);
    }

    return(pvNew);
}

ULONG EXPORTDBG __cdecl VMGetSize(void *pv)
{
    return VMGetSizeEx(pv, 1);
}

ULONG EXPORTDBG __cdecl VMGetSizeEx(void *pv, ULONG cbCluster)
{
    return(*(ULONG *)PvToVMBase(pv));
}

#endif

/* Virtual Memory Support (WIN16) ------------------------------------------- */

#ifdef WIN16

#define PvToVMBase(pv)  ((void *)((ULONG)pv & 0xFFFF0000))

BOOL VMValidatePvEx(void *pv, ULONG cbCluster)
{
    void *  pvBase;
    BYTE *  pb;

    pvBase = PvToVMBase(pv);

    pb = (BYTE *)pvBase + sizeof(ULONG);

    while (pb < (BYTE *)pv) {
        if (*pb++ != 0xAD) {
            TrapSz1("VMValidatePvEx(pv=%08lX): Block leader has been overwritten", pv);
            return(FALSE);
        }
    }

    if (cbCluster != 1)
    {
        ULONG cb = *((ULONG *)pvBase);
        ULONG cbPad = 0;

        if (cb % cbCluster)
            cbPad = (cbCluster - (cb % cbCluster));

        if (cbPad)
        {
            BYTE *pbMac;

            pb = (BYTE *)pv + cb;
            pbMac = pb + cbPad;

            while (pb < pbMac)
            {
                if (*pb++ != 0xBC)
                {
                    TrapSz1("VMValidatePvEx(pv=%08lX): Block trailer has been "
                        "overwritten", pv);
                    return(FALSE);
                }
            }
        }
    }

    return(TRUE);
}

BOOL VMValidatePv(void *pv)
{
    return VMValidatePvEx(pv, 1);
}

void * EXPORTDBG __cdecl VMAlloc(ULONG cb)
{
    return VMAllocEx(cb, 1);
}

void * EXPORTDBG __cdecl VMAllocEx(ULONG cb, ULONG cbCluster)
{
    HGLOBAL hGlobal;
    ULONG   cbAlloc;
    ULONG   cbAllocFromSys;
    void *  pvAlloc;
    ULONG   cbPad   = 0;

    if (cb > 0x10000 - sizeof(ULONG))
        return(0);

    if (cb % cbCluster)
        cbPad = (cbCluster - (cb % cbCluster));

    cbAlloc = sizeof(ULONG) + cb + cbPad;

    if (cbAlloc > 0x10000)
        return(0);

#ifdef SIMPLE_MAPI
    hGlobal = GlobalAlloc(GPTR | GMEM_SHARE, cbAlloc);
#else
    hGlobal = GlobalAlloc(GPTR, cbAlloc);
#endif

    if (hGlobal == 0)
        return(0);

    cbAllocFromSys = GlobalSize(hGlobal);

    Assert(cbAllocFromSys >= cbAlloc);

    cbAlloc = cbAllocFromSys;

    pvAlloc = GlobalLock(hGlobal);

    if (pvAlloc == 0) {
        GlobalFree(hGlobal);
        return(0);
    }

    Assert(((ULONG)pvAlloc & 0x0000FFFF) == 0);

    *(ULONG *)pvAlloc = cb;

    memset((BYTE *)pvAlloc + sizeof(ULONG), 0xAD,
        (size_t)(cbAlloc - cb - cbPad - sizeof(ULONG)));

    if (cbPad)
        memset((BYTE *)pvAlloc + cbAlloc - cbPad, 0xBC, (size_t) cbPad);

    return((BYTE *)pvAlloc + (cbAlloc - cb - cbPad));
}

void EXPORTDBG __cdecl VMFree(void *pv)
{
    VMFreeEx(pv, 1);
}

void EXPORTDBG __cdecl VMFreeEx(void *pv, ULONG cbCluster)
{
    if (VMValidatePvEx(pv, cbCluster))
    {
        HGLOBAL hGlobal;
        ULONG cb = *(ULONG *)PvToVMBase(pv);

        memset(pv, 0xFE, (size_t)cb);

        hGlobal = (HGLOBAL)((ULONG)pv >> 16);
        GlobalFree(hGlobal);
    }
}

void * EXPORTDBG __cdecl VMRealloc(void *pv, ULONG cb)
{
    return VMReallocEx(pv, cb, 1);
}

void * EXPORTDBG __cdecl VMReallocEx(void *pv, ULONG cb, ULONG cbCluster)
{
    void *  pvNew = 0;
    ULONG   cbCopy;

    if (VMValidatePvEx(pv, cbCluster)) {
        cbCopy = *(ULONG *)PvToVMBase(pv);
        if (cbCopy > cb)
            cbCopy = cb;

        pvNew = VMAllocEx(cb, cbCluster);

        if (pvNew) {
            MemCopy(pvNew, pv, (size_t)cbCopy);
            VMFreeEx(pv, cbCluster);
        }
    }

    return(pvNew);
}

ULONG EXPORTDBG __cdecl VMGetSize(void *pv)
{
    return VMGetSizeEx(pv, 1);
}

ULONG EXPORTDBG __cdecl VMGetSizeEx(void *pv, ULONG ulCluster)
{
    if (VMValidatePvEx(pv, ulCluster))
        return(*(ULONG *)PvToVMBase(pv));

    return(0);
}

#endif

/* Virtual Memory Support (Others) ------------------------------------------ */
/*
 *  The VM Allocators do not currently work on:
 *      ALPHA
 *      MAC
 */
#if defined(MAC) || defined(_ALPHA_)
#define VMAlloc(cb)             0
#define VMAllocEx(cb, ul)       0
#define VMRealloc(pv, cb)       0
#define VMReallocEx(pv, cb, ul) 0
#define VMFree(pv)
#define VMFreeEx(pv, ul)
#define VMGetSize(pv)           0
#define VMGetSizeEx(pv, ul)     0
#endif

/* PblkEnqueue / PblkDequeue ------------------------------------------------ */

void PblkEnqueue(PBLK pblk)
{
    pblk->pblkNext          = pblk->pdbgmem->pblkHead;
    pblk->pblkPrev          = 0;
    pblk->pdbgmem->pblkHead = pblk;

    if (pblk->pblkNext)
        pblk->pblkNext->pblkPrev = pblk;

}

void PblkDequeue(PBLK pblk)
{
    if (pblk->pblkNext)
        pblk->pblkNext->pblkPrev = pblk->pblkPrev;

    if (pblk->pblkPrev)
        pblk->pblkPrev->pblkNext = pblk->pblkNext;
    else
        pblk->pdbgmem->pblkHead  = pblk->pblkNext;
}

/* QueryInterface/AddRef/Release -------------------------------------------- */

STDMETHODIMP DBGMEM_QueryInterface(PDBGMEM pdbgmem, REFIID riid, LPVOID FAR* ppvObj)
{
    if (memcmp(riid, &DBGMEM_IID_IBaseMalloc, sizeof(IID)) == 0) {
        pdbgmem->pmalloc->lpVtbl->AddRef(pdbgmem->pmalloc);
        *ppvObj = pdbgmem->pmalloc;
        return(0);
    }

    if (memcmp(riid, &DBGMEM_IID_IMalloc, sizeof(IID)) == 0 ||
        memcmp(riid, &DBGMEM_IID_IUnknown, sizeof(IID)) == 0) {
        ++pdbgmem->cRef;
        *ppvObj = pdbgmem;
        return(0);
    }

    *ppvObj = NULL; /* OLE requires zeroing [out] parameter */
    return(ResultFromScode(E_NOINTERFACE));
}

STDMETHODIMP_(ULONG) DBGMEM_AddRef(PDBGMEM pdbgmem)
{
    ULONG cRef;

    DBGMEM_EnterCriticalSection(pdbgmem);
    cRef = ++pdbgmem->cRef;
    DBGMEM_LeaveCriticalSection(pdbgmem);

    return(cRef);
}

STDMETHODIMP_(ULONG) DBGMEM_Release(PDBGMEM pdbgmem)
{
    ULONG       cRef;
    LPMALLOC    pmalloc;

    DBGMEM_EnterCriticalSection(pdbgmem);
    cRef = --pdbgmem->cRef;
    DBGMEM_LeaveCriticalSection(pdbgmem);

    if (cRef == 0) {
        DBGMEM_CheckMemFn(pdbgmem, TRUE);
        pmalloc = pdbgmem->pmalloc;
        pdbgmem->lpVtbl = 0;
        #if defined(_WIN32) && defined(_X86_)
        DeleteCriticalSection(&pdbgmem->cs);
        #endif
        pmalloc->lpVtbl->Free(pmalloc, pdbgmem);
        pmalloc->lpVtbl->Release(pmalloc);
    }

    return(cRef);
}

/* IMalloc::Alloc ----------------------------------------------------------- */

STDMETHODIMP_(void FAR *) DBGMEM_Alloc(PDBGMEM pdbgmem, ULONG cb)
{
    PBLK    pblk;
    ULONG   cbAlloc;
    LPVOID  pvAlloc = 0;
    BYTE    bFill   = 0xFA;

    DBGMEM_EnterCriticalSection(pdbgmem);

    if (pdbgmem->fCheckOften)
        DBGMEM_CheckMemFn(pdbgmem, FALSE);

    cbAlloc = sizeof(BLK) + cb + pdbgmem->cbTail;

    if (pdbgmem->ulFailureAt != 0)
    {
        if (pdbgmem->ulFailureAt != pdbgmem->ulAllocAt)
            ++pdbgmem->ulAllocAt;
        else
            cbAlloc = 0;
    }

    if (cbAlloc < cb)
        pblk = 0;
    else if (pdbgmem->cbVirtual)
        pblk = VMAllocEx(cbAlloc, pdbgmem->cbVirtual);
    else
        pblk = (PBLK)pdbgmem->pmalloc->lpVtbl->Alloc(pdbgmem->pmalloc, cbAlloc);

    if (pblk) {
        pblk->pdbgmem       = pdbgmem;
        pblk->ulAllocNum    = ++pdbgmem->ulAllocNum;
        pblk->fUnleakable   = FALSE;
        pblk->pblktail      = (PBLKTAIL)((char *)pblk + sizeof(BLK) + cb);

        if (!pdbgmem->cbVirtual)
            *((PUABLK UNALIGNED * )
            &((struct _BLKTAIL UNALIGNED *) pblk->pblktail)->pblk) = pblk;

        PblkEnqueue(pblk);

        #if defined(_WIN32) && defined(_X86_)
        GetCallStack((DWORD *)pblk->pfnCallers, 0, NCALLERS);
        #endif

        if (pdbgmem->fCheckOften)
            DBGMEM_CheckMemFn(pdbgmem, FALSE);

        pvAlloc = PblkToPv(pblk);

        if (pdbgmem->fFillRandom)
            bFill = (BYTE)pblk->ulAllocNum;

        memset(pvAlloc, bFill, (size_t)cb);

        if (pdbgmem->cbExtra)
            memset(pblk->pblktail + 1, 0xAE, pdbgmem->cbExtra * sizeof(ULONG));
    }

    DBGMEM_LeaveCriticalSection(pdbgmem);

    return(pvAlloc);
}

/* IMalloc::Realloc --------------------------------------------------------- */

STDMETHODIMP_(void FAR *) DBGMEM_Realloc(PDBGMEM pdbgmem, void FAR* pv, ULONG cb)
{
    ULONG   cbAlloc;
    LPVOID  pvAlloc = 0;
    BYTE    bFill = 0xFA;

    DBGMEM_EnterCriticalSection(pdbgmem);

    if (pdbgmem->fCheckOften)
        DBGMEM_CheckMemFn(pdbgmem, FALSE);

    if (pv == 0) {
        TrapSz1("DBGMEM_Realloc(pv=NULL,cb=%ld): IMalloc::Realloc is being used allocate a new memory block.  Explicit use of IMalloc::Alloc is preferred.", cb);
        pvAlloc = DBGMEM_Alloc(pdbgmem, cb);
    } else if (cb == 0) {
        TrapSz1("DBGMEM_Realloc(pv=%08lX,cb=0): IMalloc::Realloc is being used to free a memory block.  Explicit use of IMalloc::Free is preferred.", pv);
        DBGMEM_Free(pdbgmem, pv);
        pvAlloc = 0;
    } else if (DBGMEM_ValidatePv(pdbgmem, pv, "DBGMEM_Realloc")) {
        PBLK    pblk    = PvToPblk(pv);
        ULONG   cbOld   = PblkClientSize(pblk);
        PBLK    pblkNew;

        PblkDequeue(pblk);

        cbAlloc = sizeof(BLK) + cb + pdbgmem->cbTail;

        if (pdbgmem->ulFailureAt != 0)
        {
            if (pdbgmem->ulFailureAt != pdbgmem->ulAllocAt)
                ++pdbgmem->ulAllocAt;
            else
                cbAlloc = 0;
        }

        if (cbAlloc < cb)
            pblkNew = 0;
        else if (pdbgmem->cbVirtual)
            pblkNew = (PBLK)VMReallocEx(pblk, cbAlloc, pdbgmem->cbVirtual);
        else
            pblkNew = (PBLK)pdbgmem->pmalloc->lpVtbl->Realloc(pdbgmem->pmalloc, pblk, cbAlloc);

        if (pblkNew == 0) {
            PblkEnqueue(pblk);
            pvAlloc = 0;
        } else {
            pblkNew->pblktail = (PBLKTAIL)((char *)pblkNew + sizeof(BLK) + cb);

            if (!pdbgmem->cbVirtual)
                *((PUABLK UNALIGNED * )
                &((struct _BLKTAIL UNALIGNED *) pblkNew->pblktail)->pblk) = pblkNew;

            PblkEnqueue(pblkNew);

            pvAlloc = PblkToPv(pblkNew);

            if (pdbgmem->fFillRandom)
                bFill = (BYTE)pblkNew->ulAllocNum;

            if (cb > cbOld)
                memset((char *)pvAlloc + cbOld, bFill, (size_t)(cb - cbOld));

            if (pdbgmem->cbExtra)
                memset(pblkNew->pblktail + 1, 0xAE, pdbgmem->cbExtra * sizeof(ULONG));
        }
    }

    DBGMEM_LeaveCriticalSection(pdbgmem);

    return(pvAlloc);
}

/* IMalloc::Free ------------------------------------------------------------ */

STDMETHODIMP_(void) DBGMEM_Free(PDBGMEM pdbgmem, void FAR * pv)
{
    DBGMEM_EnterCriticalSection(pdbgmem);

    if (pdbgmem->fCheckOften)
        DBGMEM_CheckMemFn(pdbgmem, FALSE);

    if (pv && DBGMEM_ValidatePv(pdbgmem, pv, "DBGMEM_Free")) {
        PBLK pblk = PvToPblk(pv);

        PblkDequeue(pblk);

        memset(pblk, 0xDC, (size_t)PblkAllocSize(pblk));

        if (pdbgmem->cbVirtual)
            VMFreeEx(pblk, pdbgmem->cbVirtual);
        else
            pdbgmem->pmalloc->lpVtbl->Free(pdbgmem->pmalloc, pblk);
    }

    DBGMEM_LeaveCriticalSection(pdbgmem);
}

/* IMalloc::GetSize --------------------------------------------------------- */

STDMETHODIMP_(ULONG) DBGMEM_GetSize(PDBGMEM pdbgmem, void FAR * pv)
{
    ULONG ulResult = (ULONG)(-1);

    DBGMEM_EnterCriticalSection(pdbgmem);

    if (pv == 0)
        TrapSz("Although technically not an error, I bet you didn't really want to pass a NULL pointer to IMalloc::GetSize, did you?  I hope you can deal with a size of -1, because that's the offical answer.  Good luck.");
    else if (DBGMEM_ValidatePv(pdbgmem, pv, "DBGMEM_GetSize"))
        ulResult = PblkClientSize(PvToPblk(pv));

    DBGMEM_LeaveCriticalSection(pdbgmem);

    return(ulResult);
}

/* IMalloc::DidAlloc -------------------------------------------------------- */

STDMETHODIMP_(int) DBGMEM_DidAlloc(PDBGMEM pdbgmem, void FAR * pv)
{
    PBLK    pblk;
    char *  pszReason;
    int     iResult = 0;

    DBGMEM_EnterCriticalSection(pdbgmem);

    for (pblk = pdbgmem->pblkHead; pblk; pblk = pblk->pblkNext)
    {
        AssertSz2(DBGMEM_ValidatePblk(pdbgmem,pblk,&pszReason)==TRUE,
                 "Block header (pblk=%08lX) is invalid\n%s",
                 pblk, pszReason);
        if (PblkToPv(pblk) == pv) {
            iResult = 1;
            break;
        }
    }

    DBGMEM_LeaveCriticalSection(pdbgmem);

    return(iResult);
}

/* IMalloc::HeapMinimize ---------------------------------------------------- */

STDMETHODIMP_(void) DBGMEM_HeapMinimize(PDBGMEM pdbgmem)
{
    pdbgmem->pmalloc->lpVtbl->HeapMinimize(pdbgmem->pmalloc);
}

/* DBGMEM_ValidatePblk ------------------------------------------------------ */

BOOL DBGMEM_ValidatePblk(PDBGMEM pdbgmem, PBLK pblk, char ** pszReason)
{
    #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
    if (IsBadWritePtr(pblk, sizeof(BLK))) {
        *pszReason = "Block header cannot be written to";
        goto err;
    }
    #endif

    if (pblk->pdbgmem != pdbgmem) {
        *pszReason = "Block header does not have correct pointer back to allocator";
        goto err;
    }

    if (pblk->pblkNext) {
        #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
        if (IsBadWritePtr(pblk->pblkNext, sizeof(BLK))) {
            *pszReason = "Block header has invalid next link pointer";
            goto err;
        }
        #endif

        if (pblk->pblkNext->pblkPrev != pblk) {
            *pszReason = "Block header points to a next block which doesn't point back to it";
            goto err;
        }
    }

    if (pblk->pblkPrev) {
        #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
        if (IsBadWritePtr(pblk->pblkPrev, sizeof(BLK))) {
            *pszReason = "Block header has invalid prev link pointer";
            goto err;
        }
        #endif

        if (pblk->pblkPrev->pblkNext != pblk) {
            *pszReason = "Block header points to a prev block which doesn't point back to it";
            goto err;
        }
    } else if (pdbgmem->pblkHead != pblk) {
        *pszReason = "Block header has a zero prev link but the allocator doesn't believe it is the first block";
        goto err;
    }

    if (pblk->ulAllocNum > pdbgmem->ulAllocNum) {
        *pszReason = "Block header has an invalid internal allocation number";
        goto err;
    }

    if (!pdbgmem->cbVirtual) {
        #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
        if (IsBadWritePtr(pblk->pblktail, pdbgmem->cbTail)) {
            *pszReason = "Block header has invalid pblktail pointer";
            goto err;
        }
        #endif

        if (*((PUABLK UNALIGNED * )
            &((struct _BLKTAIL UNALIGNED *) pblk->pblktail)->pblk) != pblk) {
            *pszReason = "Block trailer does not point back to the block header";
            goto err;
        }
    }

    if (pdbgmem->cbExtra) {
        ULONG UNALIGNED * pul = (ULONG UNALIGNED *)(pblk->pblktail + 1);
        int n = pdbgmem->cbExtra;
        for (; --n >= 0; ++pul)
            if (*pul != 0xAEAEAEAE) {
                *pszReason = "Block trailer spiddle-zone has been overwritten";
                goto err;
            }
    }

    return(TRUE);

err:
    return(FALSE);
}

/* DBGMEM_ValidatePv -------------------------------------------------------- */

BOOL DBGMEM_ValidatePv(PDBGMEM pdbgmem, void * pv, char * pszFunc)
{
    char *  pszReason;

    if (DBGMEM_DidAlloc(pdbgmem, pv) == 0) {
        TrapSz3("DBGMEM_ValidatePv(subsys=%s,pv=%08lX) [via %s]\nDetected a memory block which was not allocated by this allocator",
            pdbgmem->szSubsys, pv, pszFunc);
        return(FALSE);
    }

    if (DBGMEM_ValidatePblk(pdbgmem,PvToPblk(pv),&pszReason))
        return(TRUE);

    TrapSz4("DBGMEM_ValidatePv(%s,pv=%08lX) [via %s]\n%s",
        pdbgmem->szSubsys, pv, pszFunc, pszReason);

    return(FALSE);
}

/* DBGMEM_ReportLeak -------------------------------------------------------- */

#if defined(_WIN32) && defined(_X86_)
void EXPORTDBG __cdecl DBGMEM_LeakHook(FARPROC pfn)
{
    /* Dummy function so that you can set a breakpoint with command   */
    /* "ln ecx;g", in order to get the debugger to print out the name */
    /* of the function which allocated the leaked memory block        */
}
#endif

void DBGMEM_ReportLeak(PDBGMEM pdbgmem, PBLK pblk)
{
    int i = 0;

    DebugTrace("%s Memory Leak: @%08lX, allocation #%ld, size %ld\n",
        pdbgmem->szSubsys, PblkToPv(pblk), pblk->ulAllocNum, PblkClientSize(pblk));

    #if defined(_WIN32) && defined(_X86_)
    for (i = 0; i < NCALLERS && pblk->pfnCallers[i] != 0; i++) {
        DebugTrace("[%d] %08lX ", i, pblk->pfnCallers[i]);
        DBGMEM_LeakHook(pblk->pfnCallers[i]);
    }
    DebugTrace("\n");
    #endif
}

/* DBGMEM_NoLeakDetectFn ---------------------------------------------------- */

void EXPORTDBG __cdecl DBGMEM_NoLeakDetectFn(void * pmalloc, void *pv)
{
    PDBGMEM pdbgmem = (PDBGMEM)pmalloc;

    DBGMEM_EnterCriticalSection(pdbgmem);

    if (pv == 0)
        pdbgmem->fUnleakable = TRUE;
    else if (DBGMEM_ValidatePv(pdbgmem, pv, "DBGMEM_NoLeakDetectFn"))
        PvToPblk(pv)->fUnleakable = TRUE;

    DBGMEM_LeaveCriticalSection(pdbgmem);
}

/* DBGMEM_SetFailureAtFn ---------------------------------------------------- */

void EXPORTDBG __cdecl DBGMEM_SetFailureAtFn(void * pmalloc, ULONG ulFailureAt)
{
    PDBGMEM pdbgmem = (PDBGMEM)pmalloc;

    DBGMEM_EnterCriticalSection(pdbgmem);

    pdbgmem->ulFailureAt = ulFailureAt;

    DBGMEM_LeaveCriticalSection(pdbgmem);
}

/* DBGMEM_CheckMemFn -------------------------------------------------------- */

void EXPORTDBG __cdecl DBGMEM_CheckMemFn(void * pmalloc, BOOL fReportOrphans)
{
    PDBGMEM pdbgmem = (PDBGMEM)pmalloc;
    PBLK    pblk;
    int     cLeaks = 0;

    DBGMEM_EnterCriticalSection(pdbgmem);

    for (pblk = pdbgmem->pblkHead; pblk; pblk = pblk->pblkNext) {
        if (!DBGMEM_ValidatePv(pdbgmem, PblkToPv(pblk), "DBGMEM_CheckMemFn"))
            break;

        if (fReportOrphans && !pdbgmem->fUnleakable && !pblk->fUnleakable) {
            DBGMEM_ReportLeak(pdbgmem, pblk);
            cLeaks += 1;
        }
    }

    #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
    if (fAssertLeaks == -1)
    {
        fAssertLeaks = GetPrivateProfileIntA(szSectionDebug, szKeyAssertLeaks,
            0, szDebugIni);
    }
    #endif

    if (cLeaks > 0)
    {
        #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
        if (fAssertLeaks)
        {
            TrapSz3("DBGMEM detected %d memory leak%s in subsystem %s",
                cLeaks, cLeaks == 1 ? "" : "s", pdbgmem->szSubsys);
        }
        else
        {
            TraceSz3("DBGMEM detected %d memory leak%s in subsystem %s",
                cLeaks, cLeaks == 1 ? "" : "s", pdbgmem->szSubsys);
        }
        #else
        TraceSz3("DBGMEM detected %d memory leak%s in subsystem %s",
            cLeaks, cLeaks == 1 ? "" : "s", pdbgmem->szSubsys);
        #endif
    }

    DBGMEM_LeaveCriticalSection(pdbgmem);
}

/* vtblDBGMEM --------------------------------------------------------------- */

DBGMEM_Vtbl BASED_DEBUG vtblDBGMEM =
{
    VTABLE_FILL
    DBGMEM_QueryInterface,
    DBGMEM_AddRef,
    DBGMEM_Release,
    DBGMEM_Alloc,
    DBGMEM_Realloc,
    DBGMEM_Free,
    DBGMEM_GetSize,
    DBGMEM_DidAlloc,
    DBGMEM_HeapMinimize
};

/* DBGMEM_EncapsulateFn ----------------------------------------------------- */

void * EXPORTDBG __cdecl DBGMEM_EncapsulateFn(void * pvmalloc, char *pszSubsys, BOOL fCheckOften)
{
    LPMALLOC    pmalloc = (LPMALLOC)pvmalloc;
    PDBGMEM     pdbgmem;
    LPMALLOC    pmallocBase;
    ULONG       cbVirtual = 0;
    BOOL        fFillRandom = FALSE;
    HRESULT     hr;

    hr = pmalloc->lpVtbl->QueryInterface(pmalloc, &DBGMEM_IID_IBaseMalloc, &pmallocBase);
    if (hr) {
        pmallocBase = pmalloc;
        pmallocBase->lpVtbl->AddRef(pmallocBase);
    }

    pdbgmem = (PDBGMEM)pmallocBase->lpVtbl->Alloc(pmallocBase, sizeof(DBGMEM));

    if (pdbgmem == 0) {
        TrapSz("DBGMEM: Failed trying to allocate memory for the first time!\n");
        return(pmallocBase);
    }

    #if defined(WIN16) || (defined(_WIN32) && defined(_X86_))
    cbVirtual = GetPrivateProfileIntA(szSectionDebug, szKeyUseVirtual, 0,
        szDebugIni);

    if (cbVirtual != 0 && cbVirtual != 1 && cbVirtual != 4)
        cbVirtual = 1;

    if (cbVirtual)
        DebugTrace("DBGMEM: Subsystem '%s' using virtual memory allocator -"
            " align %d.\n", pszSubsys, cbVirtual);

    if (!fCheckOften)
        fCheckOften = GetPrivateProfileIntA(szSectionDebug, szKeyCheckOften, 0,
            szDebugIni);

    fFillRandom = GetPrivateProfileIntA(szSectionDebug, szKeyFillRandom, 0,
        szDebugIni);

    #endif

    memset(pdbgmem, 0, sizeof(DBGMEM));

    pdbgmem->lpVtbl         = &vtblDBGMEM;
    pdbgmem->cRef           = 1;
    pdbgmem->pmalloc        = pmallocBase;
    pdbgmem->fCheckOften    = fCheckOften;
    pdbgmem->fUnleakable    = FALSE;
    pdbgmem->cbVirtual      = cbVirtual;
    pdbgmem->fFillRandom    = fFillRandom;
    pdbgmem->cbExtra        = 0;
    pdbgmem->ulAllocAt      = 1L;
    pdbgmem->ulFailureAt    = 0L;

    if (pdbgmem->cbVirtual)
        pdbgmem->cbTail     = 0;
    else
        pdbgmem->cbTail     = sizeof(BLKTAIL) + pdbgmem->cbExtra * sizeof(ULONG);

    lstrcpyn(pdbgmem->szSubsys, pszSubsys, sizeof(pdbgmem->szSubsys));

    #if defined(_WIN32) && defined(_X86_)
    InitializeCriticalSection(&pdbgmem->cs);
    #endif

    return(pdbgmem);
}

/* DBGMEM_ShutdownFn -------------------------------------------------------- */

void EXPORTDBG __cdecl DBGMEM_ShutdownFn(void *pvmalloc)
{
    LPMALLOC    pmalloc = (LPMALLOC)pvmalloc;
    PDBGMEM     pdbgmem = (PDBGMEM)pvmalloc;
    LPMALLOC    pmallocBase;
    HRESULT     hr;

    hr = pmalloc->lpVtbl->QueryInterface(pmalloc, &DBGMEM_IID_IBaseMalloc, &pmallocBase);
    if (hr == 0) {
        pmallocBase->lpVtbl->Release(pmallocBase);
        if (pdbgmem->cRef != 1) {
            TrapSz2("DBGMEM_Shutdown: Expected a cRef of 1; instead have %ld for %s",
                pdbgmem->cRef, pdbgmem->szSubsys);
            pdbgmem->cRef = 1;
        }
    }

    pmalloc->lpVtbl->Release(pmalloc);
}

/* -------------------------------------------------------------------------- */

#endif


PDKVER.H

#define rmj     4
#define rmm     934
#define rup     0
#define szVerName   ""
#define szVerUser   "TDSRC"


PVALLOC.C

/*
 -  P V A L L O C . C
 -
 *  Copyright (C) 1995 Microsoft Corporation
 *  Purpose:
 *      Implementation of a chained memory manager.
 *
 */

#include <string.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <pvalloc.h>

#undef _PVALLOC_LOG

#ifdef _PVALLOC_LOG
static CB       cbTotalAlloc    = 0;
static CB       ulTotalBlockNum = 0;
#endif

/*
 -  PvAlloc
 -
 *  Purpose:
 *      Allocates a chunk of memory on the global heap.
 *
 *  Parameters:
 *      cbSize          - Count of bytes requested.
 *
 *  Returns:
 *      lpv             - Pointer to the allocated memory
 *
 */

PV PvAlloc(CB cbSize)
{
    PV      lpv         = pvNull;
    HANDLE  hMem;
    PPVINFO ppvinfo;
#ifdef _PVALLOC_LOG
    char    szFileName[80];
    LPSTR   lpszTemp    = NULL;
    FILE    *pFile      = NULL;
    char    szBuff[128];
#endif

    /* Make sure allocations are in multiples of 4 */

    if(cbSize < 4)
        cbSize = 4;
    else if(cbSize & 3)
        cbSize += 4 - (cbSize & 3);

    /* Allocate the block */

    hMem = GlobalAlloc(GMEM_MOVEABLE, cbSize + sizeof(PVINFO));
    if(hMem)
    {
        ppvinfo = (PPVINFO)GlobalLock(hMem);
        ppvinfo->hMem    = hMem;
        ppvinfo->lpvNext = pvNull;
        ppvinfo->lpvBuf  = ((PB)ppvinfo) + sizeof(PVINFO);
#ifdef _PVALLOC_LOG
        ppvinfo->cbSize  = cbSize;
        ulTotalBlockNum++;
        ppvinfo->ulBlockNum = ulTotalBlockNum;
        cbTotalAlloc += cbSize;

        // log to file
        lpszTemp = getenv("TEMP");

        if(lpszTemp)
            strcpy(szFileName, lpszTemp);
        else
            strcpy(szFileName, "c:\\temp");

        strcat(szFileName, "\\pvalloc.log");

        pFile = fopen(szFileName,"a");
        if (pFile == NULL)
            goto NoFile;
//           return NULL;

        fprintf(pFile, "Block: \t%lu\tPvAlloc: %ld Bytes\t\tTotal: %ld Bytes\n",
                 ulTotalBlockNum, cbSize, cbTotalAlloc);

        if (pFile)
            fclose(pFile);

        // log to comm port
        wsprintf(szBuff,"Block: \t%lu\tPvAlloc: %ld Bytes\t\tTotal: %ld Bytes\n",
                 ulTotalBlockNum, cbSize, cbTotalAlloc);
        OutputDebugString(szBuff);

NoFile:

#ifdef _WIN32
        memset(ppvinfo->lpvBuf, 0xaa, (size_t)cbSize);
#else
        _fmemset(ppvinfo->lpvBuf, 0xaa, (size_t)cbSize);
#endif  /* _WIN32 */

#endif  /* _PVALLOC_LOG */
        lpv = ppvinfo->lpvBuf;
    }

    return lpv;
}

/*
 -  PvAllocMore
 -
 *  Purpose:
 *      Allocates a chunk of memory and chains it to a parent block.
 *
 *  Parameters:
 *      cbSize          - Count of additional bytes to allocate
 *      lpvParent       - Pointer to parent in memory chain
 *
 *  Returns:
 *      lpv             - Pointer to the allocated memory
 *
 */

PV PvAllocMore(CB cbSize, PV lpvParent)
{
    PV          lpvStep = lpvParent;
    PV          lpv     = pvNull;
    PPVINFO     ppvinfoMore;
    HANDLE      hMem;
    PPVINFO     ppvinfo;

    /* Step to the last link */
    do
    {
        ppvinfoMore = (PPVINFO)(((PB)lpvStep) - sizeof(PVINFO));
        lpvStep = ppvinfoMore->lpvNext;
    }
    while(ppvinfoMore->lpvNext != pvNull);

    // beginning of section that was taken from PvAlloc

    if(cbSize < 4)
        cbSize = 4;
    else if(cbSize & 3)
        cbSize += 4 - (cbSize & 3);

    hMem = GlobalAlloc(GMEM_MOVEABLE, cbSize + sizeof(PVINFO));
    if(hMem)
    {
        ppvinfo = (PPVINFO)GlobalLock(hMem);
        ppvinfo->hMem       = hMem;
        ppvinfo->lpvNext    = pvNull;
        ppvinfo->lpvBuf     = ((PB)ppvinfo) + sizeof(PVINFO);
#ifdef _PVALLOC_LOG
        ppvinfo->cbSize     = cbSize;
        ppvinfo->ulBlockNum = ppvinfoMore->ulBlockNum;
        cbTotalAlloc += cbSize;

#ifdef _WIN32
        memset(ppvinfo->lpvBuf, 0xaa, (size_t)cbSize);
#else
        _fmemset(ppvinfo->lpvBuf, 0xaa, (size_t)cbSize);
#endif

#endif
        lpv = ppvinfo->lpvBuf;
    }
    else
        return lpv;

    // end of section taken from pvalloc

#ifdef _WIN32
        memset(lpv, 0xbb, (size_t)cbSize);
#else
        _fmemset(lpv, 0xbb, (size_t)cbSize);
#endif  /* _WIN32 */

    ppvinfoMore->lpvNext = lpv;

    return lpv;
}

/*
 -  PvFree
 -
 *  Purpose:
 *      This function frees memory allocated by PvAlloc or PvAllocMore.
 *      After the call, the pointer memory will be invalid and should
 *      not be referenced again.
 *      When memory is allocated by PvAlloc and PvAllocMore, which can
 *      contain several levels of pointers, all the application needs to
 *      do to free the entire structure is call this routine with the
 *      base pointer returned by the PvAlloc call.
 *
 *  Parameters:
 *      lpv             - Pointer to memory to be freed.
 *
 *  Returns:
 *      Void
 *
 */

BOOL PvFree(PV lpv)
{
    PPVINFO ppvinfo;
#ifdef _PVALLOC_LOG
    CB      cbSize;
    CB      ulBlockNum;
    FILE    *pFile  = NULL;
    CB      cbFree  = 0;
    CB      cbTotalBeforeFree = cbTotalAlloc;
    char    szFileName[80];
    LPSTR   lpszTemp    = NULL;
    char    szBuff[128];
#endif

    if(!lpv)
        return 0;

    ppvinfo = (PPVINFO)(((PB)lpv) - sizeof(PVINFO));

    while(ppvinfo)
    {
        lpv = ppvinfo->lpvNext;

#ifdef _PVALLOC_LOG
        cbSize      = ppvinfo->cbSize;
        cbFree      += ppvinfo->cbSize;
        ulBlockNum  = ppvinfo->ulBlockNum;

#ifdef _WIN32
        memset(ppvinfo->lpvBuf, 0xcc, (size_t)ppvinfo->cbSize);
#else
        _fmemset(ppvinfo->lpvBuf, 0xcc, (size_t)ppvinfo->cbSize);
#endif  /* _WIN32 */

#endif  /* _PVALLOC_LOG */

        if(GlobalUnlock(ppvinfo->hMem))
            goto err;  // Our lock count is non-zero

        if(GlobalFree(ppvinfo->hMem))
            goto err;  // Failure

#ifdef _PVALLOC_LOG
        cbTotalAlloc -= cbSize;
#endif

        if(lpv)
            ppvinfo = (PPVINFO)(((PB)lpv) - sizeof(PVINFO));
        else
            break;
    }

#ifdef _PVALLOC_LOG

    if((cbTotalBeforeFree - cbTotalAlloc) != cbFree)
       goto err;

    // log to file
    lpszTemp = getenv("TEMP");

    if(lpszTemp)
        strcpy(szFileName, lpszTemp);
    else
        strcpy(szFileName, "c:\\temp");

    strcat(szFileName, "\\pvalloc.log");

    pFile = fopen(szFileName,"a");

    if (pFile == NULL)
       goto err;

    fprintf(pFile, "Block: \t%lu\t\t***PvFree***,  Freeing  %lu Bytes(Alloc and AllocMore)\tUnFreed: %ld Bytes\n",
                    ulBlockNum, cbFree, cbTotalAlloc);
    if (pFile)
        fclose(pFile);

     // log to comm port
    wsprintf(szBuff,"Block: \t%lu\t\t***PvFree***,  Freeing  %lu Bytes(Alloc and AllocMore)\tUnFreed: %ld Bytes\n",
                    ulBlockNum, cbFree, cbTotalAlloc);
    OutputDebugString(szBuff);

#endif  /* _PVALLOC_LOG */

    return 0; // Success!

err:
#ifdef _PVALLOC_LOG

    // find file to open
    lpszTemp = getenv("TEMP");

    if(lpszTemp)
        strcpy(szFileName, lpszTemp);
    else
        strcpy(szFileName, "c:\\temp");

    strcat(szFileName, "\\pvalloc.log");

    pFile = fopen(szFileName,"a");

    if (pFile == NULL)
       return 1;

    fprintf(pFile, "Block: %lu Failure freeing: %ld Bytes\tUnFreed: %ld Bytes\n",
             ulBlockNum, cbSize, cbTotalAlloc);
    if (pFile)
        fclose(pFile);

#endif  /* _PVALLOC_LOG */

    return 1; // Failure!
}


PVALLOC.H

/*
 -  P V A L L O C . H
 -
 *  Purpose:
 *      Header file for sample memory manager.  Provides chained
 *      memory data structures.
 *
 */

#ifndef __PVALLOC_H__
#define __PVALLOC_H__

#ifdef __cplusplus
extern "C" {
#endif

#define pvNull NULL

typedef unsigned long CB;
typedef void FAR * PV;
typedef char FAR * SZ;
typedef BYTE FAR * PB;

/* Memory allocation node */

typedef struct
{
    HANDLE  hMem;
    CB      cbSize;
    PV      lpvNext;
    PV      lpvBuf;
    CB      ulBlockNum;
    unsigned long   ulAlignPad;
} PVINFO, * PPVINFO;

#define cbPvMax (65520L-sizeof(PVINFO))
#define cbPvMin (1024L-sizeof(PVINFO))

/* Memory manager function prototypes */

PV   PvAlloc(CB cbSize);
PV   PvAllocMore(CB cbSize, PV lpvParent);
BOOL PvFree(PV lpv);

#ifdef __cplusplus
}       /* extern "C" */
#endif

#endif  /* __PVALLOC_H__ */


SMPAB.H

/*
 *  S M P A B . H
 *
 *  Definitions used by the Microsoft Sample Address Book
 *  provider for service entry calls
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

/*
 *  Property tag definitions
 */

/*      The following MAPI-defined properties are settable in service entry */
/*      calls:                                                              */
/*                                                                          */
/*          PR_SAB_FILE_NAME                                                */
/*          PR_SAB_UID                                                      */

#define PR_SAB_FILE PROP_TAG(PT_STRING8,0x6604)
#define PR_SAB_UID  PROP_TAG(PT_BINARY,0x6601)

/*
 *  PR_SAB_FILE_NAME is the full path name of the .SAB file (e.g. c:\foo\mylist.sab).
 *                   This string must be ANSI.
 *
 *  PR_SAB_UID is the UID uniquely identifying this session of the SAB.  If you have
 *             multiple SABs configured, they must have different PR_SAB_UIDs.
 */

/*
 *  The Sample Address Book's PR_AB_PROVIDER_ID
 */
#define SAB_PROVIDER_ID {0x34,0xda,0x7e,0x60,0x03,0x1b,0x11,0xce,0x95,0x74,0x00,0xaa,0x00,0x3c,0xd2,0x07}


SMPMS.H

/*
 *  S M P M S . H
 *
 *  Definitions used by the Microsoft Sample Message Store Provider
 *  for service entry calls.
 *
 *  The following MAPI-defined properties are settable in service
 *  entry calls for the Sample Message Store Provider.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#ifndef _SMPMS_H_
#define _SMPMS_H_

#define SMS_EXTERN_PROPID_BASE  0x6700      /* From MAPITAGS.H comments */

#define PR_SMS_PATH         PROP_TAG(PT_STRING8, SMS_EXTERN_PROPID_BASE + 0)
#define PR_SMS_PASSWORD     PROP_TAG(PT_STRING8, SMS_EXTERN_PROPID_BASE + 1)
#define PR_SMS_REMEMBER_PW  PROP_TAG(PT_BOOLEAN, SMS_EXTERN_PROPID_BASE + 2)
#define PR_SMS_CREATE       PROP_TAG(PT_BOOLEAN, SMS_EXTERN_PROPID_BASE + 3)

/*
 *  The following is a description of each of the Sample Message Store
 *  Provider properties:
 *
 *  PR_SMS_PATH
 *      The full pathname to the root directory of the sample message store.
 *
 *  PR_SMS_PASSWORD
 *      The password needed to open the store (if already present), or the
 *      new password (if creating the store).
 *
 *  PR_SMS_REMEMBER_PW
 *      If non-zero (TRUE), this property asks the service entry to save the
 *      password in the profile, and to not prompt for it.
 *
 *  PR_SMS_CREATE
 *      If non-zero (TRUE), this property asks the service entry to create the
 *      sample store. Otherwise, the service entry will attempt to open an
 *      existing store.
 */

/*
 *  PR_MDB_PROVIDER is the GUID that represent the Sample Message Store
 *  Provider.  This guid is available as a property in the stores
 *  table and on the message store object.
 */
#define SMPMS_UID_PROVIDER      \
    {   0x38, 0x5d, 0x47, 0x5f, \
        0xec, 0xf1, 0xcd, 0x11, \
        0x93, 0xdc, 0x5a, 0xab, \
        0x3C, 0x47, 0x84, 0x37 }

#endif  /* _SMPMS_H_ */


SMPXP.H

/*
 *  S M P X P . H
 *
 *  Definitions used by the Microsoft Sample Transport provider
 *  for service entry calls.
 *
 *  The following MAPI-defined properties are settable in service
 *  entry calls for the Sample Transport Provider.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#define BASE_ID 0x6600      /* From MAPITAGS.H comments */

#define PR_SAMPLE_DISPLAY_NAME      PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0001))
#define PR_SAMPLE_EMAIL_ADDR_TYPE   PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0002))
#define PR_SAMPLE_EMAIL_ADDRESS     PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0003))
#define PR_SAMPLE_INBOUND_DIR       PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0004))
#define PR_SAMPLE_OUTBOUND_DIR      PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0005))
#define PR_SAMPLE_FILENAME          PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0006))
#define PR_SAMPLE_DIRECTORY         PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0007))
#define PR_SAMPLE_FLAGS             PROP_TAG (PT_LONG,      (BASE_ID + 0x0008))

#define PR_SAMPLE_FLAG_PEER_TO_PEER ((ULONG) 0x00000001)
#define PR_SAMPLE_FLAG_UI_ALWAYS    ((ULONG) 0x00000002)
#define PR_SAMPLE_FLAG_LOG_EVENTS   ((ULONG) 0x00000004)
#define PR_SAMPLE_FLAG_SAVE_DATA    ((ULONG) 0x00000008)

#define PR_SAMPLE_LOGFILE           PROP_TAG (PT_TSTRING,   (BASE_ID + 0x0009))
#define PR_SAMPLE_LOGHIGHWATER      PROP_TAG (PT_LONG,      (BASE_ID + 0x000A))
#define PR_SAMPLE_LOGLOWWATER       PROP_TAG (PT_LONG,      (BASE_ID + 0x000B))

/*
 *  The following is a description of each of the Sample Tranpsort
 *  Provider properties:
 *
 *  PR_SAMPLE_DISPLAY_NAME
 *      Display name of user.
 *
 *  PR_SAMPLE_EMAIL_ADDR_TYPE
 *      Address type of sample transport. This will be used to construct inbound
 *      and outbound addresses where needed. It also will be used to tell the
 *      Spooler what address types we should get.
 *
 *  PR_SAMPLE_EMAIL_ADDRESS
 *      Email address. This will usually be the same as the inbound directory,
 *      although this may tend to have a more canonical form for remote access.
 *
 *  PR_SAMPLE_INBOUND_DIR
 *      Inbound directory. This is where the transport will look in order to
 *      determine whether it has received any mail.
 *
 *  PR_SAMPLE_OUTBOUND_DIR
 *      Outbound directory. The transport will store its message files here
 *      while it is in the process of sending them. The presence of a file
 *      in this directory implies that the transport still has work to do.
 *
 *  PR_SAMPLE_FILENAME
 *      Filename. This is an 8-character root for use by mail-enabled or
 *      workgroup applications to build a complete file specification (by
 *      adding an extension) for data they wish to store for this user.
 *
 *  PR_SAMPLE_DIRECTORY
 *      Directory. This is a directory in which workgroup applications may
 *      wish to store their files pertaining to this user.
 *
 *  PR_SAMPLE_FLAGS
 *      Flags. Contain various options for this transport provider.
 *      Valid values are:
 *
 *          PR_SAMPLE_FLAG_PEER_TO_PEER
 *          PR_SAMPLE_FLAG_UI_ALWAYS
 *          PR_SAMPLE_FLAG_LOG_EVENTS
 *          PR_SAMPLE_FLAG_SAVE_DATA
 *
 *  PR_SAMPLE_LOGFILE
 *      Logfile. This is where logging information will be written. If this
 *      property is not present, we will use (PR_SAMPLE_FILENAME)".LOG"
 *
 *  PR_SAMPLE_LOGHIGHWATER
 *      Logfile high water mark. This is the point at which the log writer will
 *      truncate the logfile so as not to fill up the disk.
 *
 *  PR_SAMPLE_LOGLOWWATER
 *      Logfile low water mark. This is the approximate size that the logfile
 *      writer will truncate the logfile to (modulo a line ending).
 */


TRACES.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      TRACES.CPP
//
//  Description
//      This file implements tracing functions for sending debug/trace strings
//      to an attached debug monitor, the serial port, or a log file.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.3
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1994-1996 Microsoft Corporation. All rights reserved.
//

// These functions DO NOT support UNICODE of Multi-Byte character sets
// but can be easily extended to do so
#ifdef UNICODE
#undef UNICODE
#endif
#ifdef _UNICODE
#undef _UNICODE
#endif
#ifdef _MBCS
#undef _MBCS
#endif

#include <WINDOWS.H>
#ifndef TRACES_NO_MAPI
#include <MAPIDEFS.H>
#include <MAPITAGS.H>
#include <MAPICODE.H>
#endif // TRACES_NO_MAPI

#ifndef ENABLE_DEBUG_OUTPUT
#define ENABLE_DEBUG_OUTPUT
#endif // ENABLE_DEBUG_OUTPUT
#include "TRACES.H"

///////////////////////////////////////////////////////////////////////////////
//
// USER SETTINGS FOR TRACING FUNCTIONS
//
//  THIS IS THE ONLY PART THAT NEEDS TO BE ALTERED FOR A DIFFERENT DESTINATION
//  OUTPUT PORT OR LOG FILE NAME
//

// Change the port as necessary, depending on the COM port to which the
// debugger terminal is attached to.
#define COM_PORT_STRING         "COM1"
// Change the setting below to what ever is necessary. Check the Win32
// documentation on the DCB (Data communication block) structure for other
// valid values. Default: 9600, 8, N, 1
#define COM_BAUD_RATE           9600
#define COM_DATA_BYTE_SIZE      8
#define COM_PARITY              NOPARITY
#define COM_STOP_BIT            ONESTOPBIT

#define LOG_FILE_NAME           "C:\\MAPILOG.TXT"

static BOOL fFileInit   = FALSE;
static BOOL fComInit = FALSE;

static HANDLE hCom      = NULL;
static HANDLE hConsole  = NULL;
static HANDLE hLogFile  = NULL;

DWORD gTracesFlags = 0;

// Prototypes
inline void WINAPI OpenOutputHandles (void);
inline void WINAPI CloseOutputHandles (void);
inline void WINAPI WriteOutputString (LPSTR, DWORD);

void WINAPI InitTraces (DWORD dwFlags)
{
    if (dwFlags & (TRACES_CONSOLE |
                   TRACES_LOG_FILE |
                   TRACES_NO_COM_OUTPUT |
                   TRACES_NO_ASSERT_DIALOG))
    {
        gTracesFlags = dwFlags;
        if (gTracesFlags & TRACES_CONSOLE)
        {
            AllocConsole();
            hConsole = GetStdHandle (STD_OUTPUT_HANDLE);
            COORD coord;
            coord.X = 80;
            coord.Y = 300;
            SetConsoleScreenBufferSize( hConsole, coord );
        }
    }
}

void WINAPI UnInitTraces ()
{
    if (gTracesFlags & TRACES_CONSOLE)
    {
        FreeConsole();
    }
    if (!hLogFile)
    {
        CloseHandle (hLogFile);
    }
}

inline void WINAPI OpenOutputHandles ()
{
    if (!hCom && !(gTracesFlags & TRACES_NO_COM_OUTPUT))
    {
        hCom = CreateFile (COM_PORT_STRING,
                           GENERIC_WRITE | GENERIC_READ,
                           0,
                           NULL,
                           OPEN_EXISTING,
                           0,
                           NULL);
        if (INVALID_HANDLE_VALUE == hCom)
        {
            hCom = NULL;
        }
        else
        {
            DCB dcb;
            GetCommState (hCom, &dcb);
            dcb.BaudRate    = COM_BAUD_RATE;
            dcb.ByteSize    = COM_DATA_BYTE_SIZE;
            dcb.Parity      = COM_PARITY;
            dcb.StopBits    = COM_STOP_BIT;
            SetCommState (hCom, &dcb);
            if (!fComInit)
            {
                DWORD dwBytes;
                for (int i=0; i<55; i++)
                {
                    WriteFile (hCom, "\r\n", 2, &dwBytes, NULL);
                }
                fComInit = TRUE;
            }
        }
    }

    if (!hLogFile && (gTracesFlags & TRACES_LOG_FILE))
    {
        hLogFile = CreateFile (LOG_FILE_NAME,
                               GENERIC_WRITE,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL,
                               OPEN_ALWAYS,
                               0,
                               NULL);
        if (INVALID_HANDLE_VALUE == hLogFile)
        {
            hLogFile = NULL;
        }
        if (!fFileInit)
        {
            DWORD dwFileSize = GetFileSize (hLogFile, NULL);
            SetFilePointer (hLogFile, dwFileSize, NULL, FILE_BEGIN);

            char szTmpStr[64];
            DWORD dwBytes, nChars = wsprintf (szTmpStr,
            "======================================================\r\n");
            WriteFile (hLogFile, szTmpStr, nChars, &dwBytes, NULL);
            SYSTEMTIME st;
            GetSystemTime (&st);
            nChars = wsprintf (szTmpStr, "Log Date: %d/%d/%d - %d:%d\r\n",
            st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute);
            WriteFile (hLogFile, szTmpStr, nChars, &dwBytes, NULL);
            nChars = wsprintf (szTmpStr,
            "======================================================\r\n");
            WriteFile (hLogFile, szTmpStr, nChars, &dwBytes, NULL);
            fFileInit = TRUE;
        }
    }
}

inline void WINAPI CloseOutputHandles ()
{
    if (gTracesFlags & TRACES_NO_COM_OUTPUT)
    {
        return;
    }
    CloseHandle (hCom);
    hCom = NULL;
}

inline void WINAPI WriteOutputString (LPSTR pStr, DWORD dwSize)
{
    DWORD dwBytes;
    OpenOutputHandles ();
    if (hCom && !(gTracesFlags & TRACES_NO_COM_OUTPUT))
    {
        WriteFile (hCom, pStr, dwSize, &dwBytes, NULL);
    }

    if (hLogFile && (gTracesFlags & TRACES_LOG_FILE))
    {
        WriteFile (hLogFile, pStr, dwSize, &dwBytes, NULL);
    }

    if (hConsole && (gTracesFlags & TRACES_CONSOLE))
    {
        WriteFile (hConsole, pStr, dwSize, &dwBytes, NULL);
    }

    OutputDebugString (pStr);

    CloseOutputHandles();
}

void WINAPI TraceFn1 (LPSTR pString)
{
    char string[512];
    DWORD dwSize = wsprintf (string, "%s\r\n", pString);
    WriteOutputString (string, dwSize);
}

void WINAPI TraceFn2 (LPSTR pString, ULONG ulError)
{
    TCHAR   string[512];
    LPTSTR  szErr;

    switch(ulError)
    {
        case E_FAIL                             : szErr = "E_FAIL";                         break;
        case E_INVALIDARG                       : szErr = "E_INVALIDARG";                   break;
        case E_NOINTERFACE                      : szErr = "E_NOINTERFACE";                  break;
        case E_OUTOFMEMORY                      : szErr = "E_OUTOFMEMORY";                  break;
        case E_ACCESSDENIED                     : szErr = "E_ACCESSDENIED";                 break;

#ifndef TRACES_NO_MAPI
        case MAPI_E_NO_SUPPORT                  : szErr = "MAPI_E_NO_SUPPORT";              break;
        case MAPI_E_BAD_CHARWIDTH               : szErr = "MAPI_E_BAD_CHARWIDTH";           break;
        case MAPI_E_STRING_TOO_LONG             : szErr = "MAPI_E_STRING_TOO_LONG";         break;
        case MAPI_E_UNKNOWN_FLAGS               : szErr = "MAPI_E_UNKNOWN_FLAGS";           break;
        case MAPI_E_INVALID_ENTRYID             : szErr = "MAPI_E_INVALID_ENTRYID";         break;
        case MAPI_E_INVALID_OBJECT              : szErr = "MAPI_E_INVALID_OBJECT";          break;
        case MAPI_E_OBJECT_CHANGED              : szErr = "MAPI_E_OBJECT_CHANGED";          break;
        case MAPI_E_OBJECT_DELETED              : szErr = "MAPI_E_OBJECT_DELETED";          break;
        case MAPI_E_BUSY                        : szErr = "MAPI_E_BUSY";                    break;
        case MAPI_E_NOT_ENOUGH_DISK             : szErr = "MAPI_E_NOT_ENOUGH_DISK";         break;
        case MAPI_E_NOT_ENOUGH_RESOURCES        : szErr = "MAPI_E_NOT_ENOUGH_RESOURCES";    break;
        case MAPI_E_NOT_FOUND                   : szErr = "MAPI_E_NOT_FOUND";               break;
        case MAPI_E_VERSION                     : szErr = "MAPI_E_VERSION";                 break;
        case MAPI_E_LOGON_FAILED                : szErr = "MAPI_E_LOGON_FAILED";            break;
        case MAPI_E_SESSION_LIMIT               : szErr = "MAPI_E_SESSION_LIMIT";           break;
        case MAPI_E_USER_CANCEL                 : szErr = "MAPI_E_USER_CANCEL";             break;
        case MAPI_E_UNABLE_TO_ABORT             : szErr = "MAPI_E_UNABLE_TO_ABORT";         break;
        case MAPI_E_NETWORK_ERROR               : szErr = "MAPI_E_NETWORK_ERROR";           break;
        case MAPI_E_DISK_ERROR                  : szErr = "MAPI_E_DISK_ERROR";              break;
        case MAPI_E_TOO_COMPLEX                 : szErr = "MAPI_E_TOO_COMPLEX";             break;
        case MAPI_E_BAD_COLUMN                  : szErr = "MAPI_E_BAD_COLUMN";              break;
        case MAPI_E_EXTENDED_ERROR              : szErr = "MAPI_E_EXTENDED_ERROR";          break;
        case MAPI_E_COMPUTED                    : szErr = "MAPI_E_COMPUTED";                break;
        case MAPI_E_CORRUPT_DATA                : szErr = "MAPI_E_CORRUPT_DATA";            break;
        case MAPI_E_UNCONFIGURED                : szErr = "MAPI_E_UNCONFIGURED";            break;
        case MAPI_E_FAILONEPROVIDER             : szErr = "MAPI_E_FAILONEPROVIDER";         break;
        case MAPI_E_END_OF_SESSION              : szErr = "MAPI_E_END_OF_SESSION";          break;
        case MAPI_E_UNKNOWN_ENTRYID             : szErr = "MAPI_E_UNKNOWN_ENTRYID";         break;
        case MAPI_E_MISSING_REQUIRED_COLUMN     : szErr = "MAPI_E_MISSING_REQUIRED_COLUMN"; break;
        case MAPI_W_NO_SERVICE                  : szErr = "MAPI_W_NO_SERVICE";              break;
        case MAPI_E_BAD_VALUE                   : szErr = "MAPI_E_BAD_VALUE";               break;
        case MAPI_E_INVALID_TYPE                : szErr = "MAPI_E_INVALID_TYPE";            break;
        case MAPI_E_TYPE_NO_SUPPORT             : szErr = "MAPI_E_TYPE_NO_SUPPORT";         break;
        case MAPI_E_UNEXPECTED_TYPE             : szErr = "MAPI_E_UNEXPECTED_TYPE";         break;
        case MAPI_E_TOO_BIG                     : szErr = "MAPI_E_TOO_BIG";                 break;
        case MAPI_E_DECLINE_COPY                : szErr = "MAPI_E_DECLINE_COPY";            break;
        case MAPI_E_UNEXPECTED_ID               : szErr = "MAPI_E_UNEXPECTED_ID";           break;
        case MAPI_W_ERRORS_RETURNED             : szErr = "MAPI_W_ERRORS_RETURNED";         break;
        case MAPI_E_UNABLE_TO_COMPLETE          : szErr = "MAPI_E_UNABLE_TO_COMPLETE";      break;
        case MAPI_E_TIMEOUT                     : szErr = "MAPI_E_TIMEOUT";                 break;
        case MAPI_E_TABLE_EMPTY                 : szErr = "MAPI_E_TABLE_EMPTY";             break;
        case MAPI_E_TABLE_TOO_BIG               : szErr = "MAPI_E_TABLE_TOO_BIG";           break;
        case MAPI_E_INVALID_BOOKMARK            : szErr = "MAPI_E_INVALID_BOOKMARK";        break;
        case MAPI_W_POSITION_CHANGED            : szErr = "MAPI_W_POSITION_CHANGED";        break;
        case MAPI_W_APPROX_COUNT                : szErr = "MAPI_W_APPROX_COUNT";            break;
        case MAPI_E_WAIT                        : szErr = "MAPI_E_WAIT";                    break;
        case MAPI_E_CANCEL                      : szErr = "MAPI_E_CANCEL";                  break;
        case MAPI_E_NOT_ME                      : szErr = "MAPI_E_NOT_ME";                  break;
        case MAPI_W_CANCEL_MESSAGE              : szErr = "MAPI_W_CANCEL_MESSAGE";          break;
        case MAPI_E_CORRUPT_STORE               : szErr = "MAPI_E_CORRUPT_STORE";           break;
        case MAPI_E_NOT_IN_QUEUE                : szErr = "MAPI_E_NOT_IN_QUEUE";            break;
        case MAPI_E_NO_SUPPRESS                 : szErr = "MAPI_E_NO_SUPPRESS";             break;
        case MAPI_E_COLLISION                   : szErr = "MAPI_E_COLLISION";               break;
        case MAPI_E_NOT_INITIALIZED             : szErr = "MAPI_E_NOT_INITIALIZED";         break;
        case MAPI_E_NON_STANDARD                : szErr = "MAPI_E_NON_STANDARD";            break;
        case MAPI_E_NO_RECIPIENTS               : szErr = "MAPI_E_NO_RECIPIENTS";           break;
        case MAPI_E_SUBMITTED                   : szErr = "MAPI_E_SUBMITTED";               break;
        case MAPI_E_HAS_FOLDERS                 : szErr = "MAPI_E_HAS_FOLDERS";             break;
        case MAPI_E_HAS_MESSAGES                : szErr = "MAPI_E_HAS_MESSAGES";            break;
        case MAPI_E_FOLDER_CYCLE                : szErr = "MAPI_E_FOLDER_CYCLE";            break;
        case MAPI_W_PARTIAL_COMPLETION          : szErr = "MAPI_W_PARTIAL_COMPLETION";      break;
        case MAPI_E_AMBIGUOUS_RECIP             : szErr = "MAPI_E_AMBIGUOUS_RECIP";         break;

        #ifndef MAPI_E_UNKNOWN_CPID
        #define MAPI_E_UNKNOWN_CPID             MAKE_MAPI_E( 0x11E )
        #define MAPI_E_UNKNOWN_LCID             MAKE_MAPI_E( 0x11F )
        #endif
        case MAPI_E_UNKNOWN_CPID                : szErr = "MAPI_E_UNKNOWN_CPID";            break;
        case MAPI_E_UNKNOWN_LCID                : szErr = "MAPI_E_UNKNOWN_LCID";            break;
#endif // TRACES_NO_MAPI

        case STG_E_INVALIDFUNCTION              : szErr = "STG_E_INVALIDFUNCTION";          break;
        case STG_E_FILENOTFOUND                 : szErr = "STG_E_FILENOTFOUND";             break;
        case STG_E_PATHNOTFOUND                 : szErr = "STG_E_PATHNOTFOUND";             break;
        case STG_E_TOOMANYOPENFILES             : szErr = "STG_E_TOOMANYOPENFILES";         break;
        case STG_E_ACCESSDENIED                 : szErr = "STG_E_ACCESSDENIED";             break;
        case STG_E_INVALIDHANDLE                : szErr = "STG_E_INVALIDHANDLE";            break;
        case STG_E_INSUFFICIENTMEMORY           : szErr = "STG_E_INSUFFICIENTMEMORY";       break;
        case STG_E_INVALIDPOINTER               : szErr = "STG_E_INVALIDPOINTER";           break;
        case STG_E_NOMOREFILES                  : szErr = "STG_E_NOMOREFILES";              break;
        case STG_E_DISKISWRITEPROTECTED         : szErr = "STG_E_DISKISWRITEPROTECTED";     break;
        case STG_E_SEEKERROR                    : szErr = "STG_E_SEEKERROR";                break;
        case STG_E_WRITEFAULT                   : szErr = "STG_E_WRITEFAULT";               break;
        case STG_E_READFAULT                    : szErr = "STG_E_READFAULT";                break;
        case STG_E_SHAREVIOLATION               : szErr = "STG_E_SHAREVIOLATION";           break;
        case STG_E_LOCKVIOLATION                : szErr = "STG_E_LOCKVIOLATION";            break;
        case STG_E_FILEALREADYEXISTS            : szErr = "STG_E_FILEALREADYEXISTS";        break;
        case STG_E_INVALIDPARAMETER             : szErr = "STG_E_INVALIDPARAMETER";         break;
        case STG_E_MEDIUMFULL                   : szErr = "STG_E_MEDIUMFULL";               break;
        case STG_E_ABNORMALAPIEXIT              : szErr = "STG_E_ABNORMALAPIEXIT";          break;
        case STG_E_INVALIDHEADER                : szErr = "STG_E_INVALIDHEADER";            break;
        case STG_E_INVALIDNAME                  : szErr = "STG_E_INVALIDNAME";              break;
        case STG_E_UNKNOWN                      : szErr = "STG_E_UNKNOWN";                  break;
        case STG_E_UNIMPLEMENTEDFUNCTION        : szErr = "STG_E_UNIMPLEMENTEDFUNCTION";    break;
        case STG_E_INVALIDFLAG                  : szErr = "STG_E_INVALIDFLAG";              break;
        case STG_E_INUSE                        : szErr = "STG_E_INUSE";                    break;
        case STG_E_NOTCURRENT                   : szErr = "STG_E_NOTCURRENT";               break;
        case STG_E_REVERTED                     : szErr = "STG_E_REVERTED";                 break;
        case STG_E_CANTSAVE                     : szErr = "STG_E_CANTSAVE";                 break;
        case STG_E_OLDFORMAT                    : szErr = "STG_E_OLDFORMAT";                break;
        case STG_E_OLDDLL                       : szErr = "STG_E_OLDDLL";                   break;
        case STG_E_SHAREREQUIRED                : szErr = "STG_E_SHAREREQUIRED";            break;
        case STG_E_NOTFILEBASEDSTORAGE          : szErr = "STG_E_NOTFILEBASEDSTORAGE";      break;
        case STG_S_CONVERTED                    : szErr = "STG_S_CONVERTED";                break;

        case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) :         szErr = "WIN32 - ERROR_FILE_NOT_FOUND";         break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) :          szErr = "WIN32 - ERROR_INVALID_DRIVE";          break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) :         szErr = "WIN32 - ERROR_INVALID_HANDLE";         break;
        case HRESULT_FROM_WIN32(ERROR_SEEK) :                   szErr = "WIN32 - ERROR_SEEK";                   break;
        case HRESULT_FROM_WIN32(ERROR_SECTOR_NOT_FOUND) :       szErr = "WIN32 - ERROR_SECTOR_NOT_FOUND";       break;
        case HRESULT_FROM_WIN32(ERROR_WRITE_FAULT) :            szErr = "WIN32 - ERROR_WRITE_FAULT";            break;
        case HRESULT_FROM_WIN32(ERROR_READ_FAULT) :             szErr = "WIN32 - ERROR_READ_FAULT";             break;
        case HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) :      szErr = "WIN32 - ERROR_SHARING_VIOLATION";      break;
        case HRESULT_FROM_WIN32(ERROR_LOCK_VIOLATION) :         szErr = "WIN32 - ERROR_LOCK_VIOLATION";         break;
        case HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) :          szErr = "WIN32 - ERROR_NOT_SUPPORTED";          break;
        case HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER) :           szErr = "WIN32 - ERROR_NO_SUCH_USER";           break;
        case HRESULT_FROM_WIN32(ERROR_NO_SUCH_GROUP) :          szErr = "WIN32 - ERROR_NO_SUCH_GROUP";          break;
        case HRESULT_FROM_WIN32(ERROR_WRONG_PASSWORD) :         szErr = "WIN32 - ERROR_WRONG_PASSWORD";         break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD) :       szErr = "WIN32 - ERROR_INVALID_PASSWORD";       break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_FLAGS) :          szErr = "WIN32 - ERROR_INVALID_FLAGS";          break;
        case HRESULT_FROM_WIN32(ERROR_BAD_USERNAME) :           szErr = "WIN32 - ERROR_BAD_USERNAME";           break;
        case HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE) :            szErr = "WIN32 - ERROR_BROKEN_PIPE";            break;
        case HRESULT_FROM_WIN32(ERROR_PIPE_BUSY) :              szErr = "WIN32 - ERROR_PIPE_BUSY";              break;
        case HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED):      szErr = "WIN32 - ERROR_PIPE_NOT_CONNECTED";     break;
        case HRESULT_FROM_WIN32(ERROR_PIPE_CONNECTED):          szErr = "WIN32 - ERROR_PIPE_CONNECTED";         break;
        case HRESULT_FROM_WIN32(ERROR_STATIC_INIT) :            szErr = "WIN32 - ERROR_STATIC_INIT";            break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) :       szErr = "WIN32 - ERROR_INVALID_FUNCTION";       break;
        case HRESULT_FROM_WIN32(ERROR_EXCEPTION_IN_SERVICE):    szErr = "WIN32 - ERROR_EXCEPTION_IN_SERVICE";   break;
        case HRESULT_FROM_WIN32(ERROR_CANCELLED):               szErr = "WIN32 - ERROR_CANCELLED";              break;
        case HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY):            szErr = "WIN32 - ERROR_PARTIAL_COPY";           break;
        case HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER):     szErr = "WIN32 - ERROR_INSUFFICIENT_BUFFER";    break;
        case HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION):  szErr = "WIN32 - ERROR_NO_UNICODE_TRANSLATION"; break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_ACCOUNT_NAME):    szErr = "WIN32 - ERROR_INVALID_ACCOUNT_NAME";   break;
        case HRESULT_FROM_WIN32(ERROR_PRIVILEGE_NOT_HELD):      szErr = "WIN32 - ERROR_INVALID_ACCOUNT_NAME";   break;
        case HRESULT_FROM_WIN32(ERROR_BAD_NET_NAME):            szErr = "WIN32 - ERROR_BAD_NET_NAME";           break;
        case HRESULT_FROM_WIN32(ERROR_SERVICE_NO_THREAD):       szErr = "WIN32 - ERROR_SERVICE_NO_THREAD";      break;
        case HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS):    szErr = "WIN32 - ERROR_SHUTDOWN_IN_PROGRESS";   break;
        case HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID):      szErr = "WIN32 - ERROR_CONNECTION_INVALID";     break;
        case HRESULT_FROM_WIN32(ERROR_HANDLE_EOF):              szErr = "WIN32 - ERROR_HANDLE_EOF";             break;
        case HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS):          szErr = "WIN32 - ERROR_ALREADY_EXISTS";         break;
        case HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE):        szErr = "WIN32 - ERROR_HOST_UNREACHABLE";       break;
        case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS):             szErr = "WIN32 - ERROR_FILE_EXISTS";            break;
        case HRESULT_FROM_WIN32(ERROR_IO_PENDING):              szErr = "WIN32 - ERROR_IO_PENDING";             break;
        case HRESULT_FROM_WIN32(ERROR_ILL_FORMED_PASSWORD):     szErr = "WIN32 - ERROR_ILL_FORMED_PASSWORD";    break;
        case HRESULT_FROM_WIN32(ERROR_PASSWORD_RESTRICTION):    szErr = "WIN32 - ERROR_PASSWORD_RESTRICTION";   break;
        case HRESULT_FROM_WIN32(ERROR_LOGON_FAILURE):           szErr = "WIN32 - ERROR_LOGON_FAILURE";          break;
        case HRESULT_FROM_WIN32(ERROR_ACCOUNT_RESTRICTION):     szErr = "WIN32 - ERROR_ACCOUNT_RESTRICTION";    break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_LOGON_HOURS):     szErr = "WIN32 - ERROR_INVALID_LOGON_HOURS";    break;
        case HRESULT_FROM_WIN32(ERROR_INVALID_WORKSTATION):     szErr = "WIN32 - ERROR_INVALID_WORKSTATION";    break;
        case HRESULT_FROM_WIN32(ERROR_PASSWORD_EXPIRED):        szErr = "WIN32 - ERROR_PASSWORD_EXPIRED";       break;
        case HRESULT_FROM_WIN32(ERROR_ACCOUNT_DISABLED):        szErr = "WIN32 - ERROR_ACCOUNT_DISABLED";       break;
        case HRESULT_FROM_WIN32(ERROR_NONE_MAPPED):             szErr = "WIN32 - ERROR_NONE_MAPPED";            break;
        case HRESULT_FROM_WIN32(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) : szErr = "WIN32 - ERROR_FAILED_SERVICE_CONTROLLER_CONNECT"; break;

        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_CALL_FAILED):              szErr = "RPC Error - RPC_S_CALL_FAILED";            break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_CALL_FAILED_DNE):          szErr = "RPC Error - RPC_S_CALL_FAILED_DNE";        break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_PROTOCOL_ERROR):           szErr = "RPC Error - RPC_S_PROTOCOL_ERROR";         break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_UNSUPPORTED_TRANS_SYN):    szErr = "RPC Error - RPC_S_UNSUPPORTED_TRANS_SYN";  break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_UNSUPPORTED_TYPE):         szErr = "RPC Error - RPC_S_UNSUPPORTED_TYPE";       break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_TAG):              szErr = "RPC Error - RPC_S_INVALID_TAG";            break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_BOUND):            szErr = "RPC Error - RPC_S_INVALID_BOUND";          break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NO_ENTRY_NAME):            szErr = "RPC Error - RPC_S_NO_ENTRY_NAME";          break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_NAME_SYNTAX):      szErr = "RPC Error - RPC_S_INVALID_NAME_SYNTAX";    break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_UNSUPPORTED_NAME_SYNTAX):  szErr = "RPC Error - RPC_S_UNSUPPORTED_NAME_SYNTAX";break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_DUPLICATE_ENDPOINT):       szErr = "RPC Error - RPC_S_DUPLICATE_ENDPOINT";     break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_STRING_BINDING):   szErr = "RPC Error - RPC_S_INVALID_STRING_BINDING"; break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_WRONG_KIND_OF_BINDING):    szErr = "RPC Error - RPC_S_WRONG_KIND_OF_BINDING";  break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_BINDING):          szErr = "RPC Error - RPC_S_INVALID_BINDING";        break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_PROTSEQ_NOT_SUPPORTED):    szErr = "RPC Error - RPC_S_PROTSEQ_NOT_SUPPORTED";  break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_RPC_PROTSEQ):      szErr = "RPC Error - RPC_S_INVALID_RPC_PROTSEQ";    break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_STRING_UUID):      szErr = "RPC Error - RPC_S_INVALID_STRING_UUID";    break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_ENDPOINT_FORMAT):  szErr = "RPC Error - RPC_S_INVALID_ENDPOINT_FORMAT";break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_NET_ADDR):         szErr = "RPC Error - RPC_S_NO_ENDPOINT_FOUND";      break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NO_ENDPOINT_FOUND):        szErr = "RPC Error - RPC_S_DUPLICATE_ENDPOINT";     break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_INVALID_TIMEOUT):          szErr = "RPC Error - RPC_S_INVALID_TIMEOUT";        break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_OBJECT_NOT_FOUND):         szErr = "RPC Error - RPC_S_OBJECT_NOT_FOUND";       break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_ALREADY_REGISTERED):       szErr = "RPC Error - RPC_S_ALREADY_REGISTERED";     break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_TYPE_ALREADY_REGISTERED):  szErr = "RPC Error - RPC_S_TYPE_ALREADY_REGISTERED";break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_ALREADY_LISTENING):        szErr = "RPC Error - RPC_S_ALREADY_LISTENING";      break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NO_PROTSEQS_REGISTERED):   szErr = "RPC Error - RPC_S_NO_PROTSEQS_REGISTERED"; break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NOT_LISTENING):            szErr = "RPC Error - RPC_S_NOT_LISTENING";          break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_UNKNOWN_MGR_TYPE):         szErr = "RPC Error - RPC_S_UNKNOWN_MGR_TYPE";       break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_UNKNOWN_IF):               szErr = "RPC Error - RPC_S_UNKNOWN_IF";             break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NO_BINDINGS):              szErr = "RPC Error - RPC_S_NO_BINDINGS";            break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_NO_PROTSEQS):              szErr = "RPC Error - RPC_S_NO_PROTSEQS";            break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_CANT_CREATE_ENDPOINT):     szErr = "RPC Error - RPC_S_CANT_CREATE_ENDPOINT";   break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_OUT_OF_RESOURCES):         szErr = "RPC Error - RPC_S_OUT_OF_RESOURCES";       break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_SERVER_UNAVAILABLE):       szErr = "RPC Error - RPC_S_SERVER_UNAVAILABLE";     break;
        case MAKE_HRESULT(1, FACILITY_RPC, RPC_S_SERVER_TOO_BUSY):          szErr = "RPC Error - RPC_S_SERVER_TOO_BUSY";        break;

        default : szErr = "UNKNOWN"; break;
    }

    DWORD dwSize = wsprintf (string, "Error in %s, %s (%#08x)\r\n", pString, szErr, ulError);
    WriteOutputString(string, dwSize);
}

void WINAPI TraceFn3 (LPSTR pString)
{
    WriteOutputString (pString, lstrlen(pString));
}

void __cdecl TraceFn4 (BOOL fDebuggerOnly, LPSTR pszFormat, ...)
{
    char string[512];
    va_list vl;
    va_start(vl, pszFormat);
    DWORD dwSize = wvsprintf (string, pszFormat, vl);
    va_end(vl);
    strcat(string, "\r\n");
    if (fDebuggerOnly)
    {
        OutputDebugString (string);
    }
    else
    {
        dwSize += 2;
        WriteOutputString (string, dwSize);
    }
}

void WINAPI TraceIO (ULONG hResult)
{
    if (!(hResult<0))
    {
        return;
    }
    char string[34];
    DWORD dwSize = wsprintf (string,"I/O Function Failed: %#08x\r\n", hResult);
    WriteOutputString (string, dwSize);
}

void WINAPI TraceIO2 (LPSTR pszMsg, ULONG hResult)
{
    if (!(hResult<0))
    {
        return;
    }
    WriteOutputString (pszMsg, lstrlen(pszMsg));
    char string[48];
    DWORD dwSize = wsprintf (string,": I/O Function Failed: %#08x\r\n", hResult);
    WriteOutputString (string, dwSize);
}

void WINAPI AssertNow (LPSTR pszAssertMsg)
{
    HWND hWnd = GetActiveWindow();
    int nResponse = MessageBox (hWnd,
                                pszAssertMsg,
                                "WARNING",
                                MB_YESNO | MB_ICONSTOP |
                                MB_TASKMODAL | MB_SETFOREGROUND);
    if (IDYES == nResponse)
    {
        DebugBreak();
    }
}

void WINAPI AssertStatement (LPSTR pszFile, ULONG ulLine)
{
    if (gTracesFlags & TRACES_NO_ASSERTS)
    {
        return;
    }
    char szStr[1024];
    wsprintf (szStr, "Assertion failed in File: %s, Line: %d", pszFile, ulLine);
    TraceMessage (szStr);
    if (gTracesFlags & TRACES_NO_ASSERT_DIALOG)
    {
        return;
    }
    wsprintf (szStr, "%s - Break into the debugger?", szStr);
    AssertNow (szStr);
}

void WINAPI AssertStatementWithMsg (LPSTR pszMsg, LPSTR pszFile, ULONG ulLine)
{
    if (gTracesFlags & TRACES_NO_ASSERTS)
    {
        return;
    }
    char szStr[2048];
    wsprintf (szStr, "%s\r\nAssertion in File: %s, Line: %d", pszMsg, pszFile, ulLine);
    TraceMessage (szStr);
    if (gTracesFlags & TRACES_NO_ASSERT_DIALOG)
    {
        return;
    }
    wsprintf (szStr, "%s\r\nAssertion in File: %s, Line: %d\r\nBreak into the debugger?", pszMsg, pszFile, ulLine);
    AssertNow (szStr);
}

#ifndef TRACES_NO_MAPI

void WINAPI TraceProp (ULONG ulPropTag)
{
    char * pszTag;
    char message [128];
    char szUnkType[32];
    char szUnkTag[32];
    DWORD dwSize;
    switch (PROP_ID(ulPropTag))

{
        // Message envelope properties
        case PROP_ID(PR_ACKNOWLEDGEMENT_MODE) :                     pszTag = "PR_ACKNOWLEDGEMENT_MODE";                         break;
        case PROP_ID(PR_ALTERNATE_RECIPIENT_ALLOWED) :              pszTag = "PR_ALTERNATE_RECIPIENT_ALLOWED";                  break;
        case PROP_ID(PR_AUTHORIZING_USERS) :                        pszTag = "PR_AUTHORIZING_USERS";                            break;
        case PROP_ID(PR_AUTO_FORWARD_COMMENT) :                     pszTag = "PR_AUTO_FORWARD_COMMENT";                         break;
        case PROP_ID(PR_AUTO_FORWARDED) :                           pszTag = "PR_AUTO_FORWARDED";                               break;
        case PROP_ID(PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID) :     pszTag = "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID";         break;
        case PROP_ID(PR_CONTENT_CORRELATOR) :                       pszTag = "PR_CONTENT_CORRELATOR";                           break;
        case PROP_ID(PR_CONTENT_IDENTIFIER) :                       pszTag = "PR_CONTENT_IDENTIFIER";                           break;
        case PROP_ID(PR_CONTENT_LENGTH) :                           pszTag = "PR_CONTENT_LENGTH";                               break;
        case PROP_ID(PR_CONTENT_RETURN_REQUESTED) :                 pszTag = "PR_CONTENT_RETURN_REQUESTED";                     break;
        case PROP_ID(PR_CONVERSATION_KEY) :                         pszTag = "PR_CONVERSATION_KEY";                             break;
        case PROP_ID(PR_CONVERSION_EITS) :                          pszTag = "PR_CONVERSION_EITS";                              break;
        case PROP_ID(PR_CONVERSION_WITH_LOSS_PROHIBITED) :          pszTag = "PR_CONVERSION_WITH_LOSS_PROHIBITED";              break;
        case PROP_ID(PR_CONVERTED_EITS) :                           pszTag = "PR_CONVERTED_EITS";                               break;
        case PROP_ID(PR_DEFERRED_DELIVERY_TIME) :                   pszTag = "PR_DEFERRED_DELIVERY_TIME";                       break;
        case PROP_ID(PR_DELIVER_TIME) :                             pszTag = "PR_DELIVER_TIME";                                 break;
        case PROP_ID(PR_DISCARD_REASON) :                           pszTag = "PR_DISCARD_REASON";                               break;
        case PROP_ID(PR_DISCLOSURE_OF_RECIPIENTS) :                 pszTag = "PR_DISCLOSURE_OF_RECIPIENTS";                     break;
        case PROP_ID(PR_DL_EXPANSION_HISTORY) :                     pszTag = "PR_DL_EXPANSION_HISTORY";                         break;
        case PROP_ID(PR_DL_EXPANSION_PROHIBITED) :                  pszTag = "PR_DL_EXPANSION_PROHIBITED";                      break;
        case PROP_ID(PR_EXPIRY_TIME) :                              pszTag = "PR_EXPIRY_TIME";                                  break;
        case PROP_ID(PR_IMPLICIT_CONVERSION_PROHIBITED) :           pszTag = "PR_IMPLICIT_CONVERSION_PROHIBITED";               break;
        case PROP_ID(PR_IMPORTANCE) :                               pszTag = "PR_IMPORTANCE";                                   break;
        case PROP_ID(PR_IPM_ID) :                                   pszTag = "PR_IPM_ID";                                       break;
        case PROP_ID(PR_LATEST_DELIVERY_TIME) :                     pszTag = "PR_LATEST_DELIVERY_TIME";                         break;
        case PROP_ID(PR_MESSAGE_CLASS) :                            pszTag = "PR_MESSAGE_CLASS";                                break;
        case PROP_ID(PR_MESSAGE_DELIVERY_ID) :                      pszTag = "PR_MESSAGE_DELIVERY_ID";                          break;
        case PROP_ID(PR_MESSAGE_SECURITY_LABEL) :                   pszTag = "PR_MESSAGE_SECURITY_LABEL";                       break;
        case PROP_ID(PR_OBSOLETED_IPMS) :                           pszTag = "PR_OBSOLETED_IPMS";                               break;
        case PROP_ID(PR_ORIGINALLY_INTENDED_RECIPIENT_NAME) :       pszTag = "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME";           break;
        case PROP_ID(PR_ORIGINAL_EITS) :                            pszTag = "PR_ORIGINAL_EITS";                                break;
        case PROP_ID(PR_ORIGINATOR_CERTIFICATE) :                   pszTag = "PR_ORIGINATOR_CERTIFICATE";                       break;
        case PROP_ID(PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED) :     pszTag = "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED";         break;
        case PROP_ID(PR_ORIGINATOR_RETURN_ADDRESS) :                pszTag = "PR_ORIGINATOR_RETURN_ADDRESS";                    break;
        case PROP_ID(PR_PARENT_KEY) :                               pszTag = "PR_PARENT_KEY";                                   break;
        case PROP_ID(PR_PRIORITY) :                                 pszTag = "PR_PRIORITY";                                     break;
        case PROP_ID(PR_ORIGIN_CHECK) :                             pszTag = "PR_ORIGIN_CHECK";                                 break;
        case PROP_ID(PR_PROOF_OF_SUBMISSION_REQUESTED) :            pszTag = "PR_PROOF_OF_SUBMISSION_REQUESTED";                break;
        case PROP_ID(PR_READ_RECEIPT_REQUESTED) :                   pszTag = "PR_READ_RECEIPT_REQUESTED";                       break;
        case PROP_ID(PR_RECEIPT_TIME) :                             pszTag = "PR_RECEIPT_TIME";                                 break;
        case PROP_ID(PR_RECIPIENT_REASSIGNMENT_PROHIBITED) :        pszTag = "PR_RECIPIENT_REASSIGNMENT_PROHIBITED";            break;
        case PROP_ID(PR_REDIRECTION_HISTORY) :                      pszTag = "PR_REDIRECTION_HISTORY";                          break;
        case PROP_ID(PR_RELATED_IPMS) :                             pszTag = "PR_RELATED_IPMS";                                 break;

        #ifndef PR_ORIGINAL_SENSITIVITY
        #define PR_ORIGINAL_SENSITIVITY                             PROP_TAG( PT_LONG,      0x002E)
        #endif
        case PROP_ID(PR_ORIGINAL_SENSITIVITY) :                     pszTag = "PR_ORIGINAL_SENSITIVITY";                         break;

        case PROP_ID(PR_LANGUAGES) :                                pszTag = "PR_LANGUAGES";                                    break;
        case PROP_ID(PR_REPLY_TIME) :                               pszTag = "PR_REPLY_TIME";                                   break;
        case PROP_ID(PR_REPORT_TAG) :                               pszTag = "PR_REPORT_TAG";                                   break;
        case PROP_ID(PR_REPORT_TIME) :                              pszTag = "PR_REPORT_TIME";                                  break;
        case PROP_ID(PR_RETURNED_IPM) :                             pszTag = "PR_RETURNED_IPM";                                 break;
        case PROP_ID(PR_SECURITY) :                                 pszTag = "PR_SECURITY";                                     break;
        case PROP_ID(PR_INCOMPLETE_COPY) :                          pszTag = "PR_INCOMPLETE_COPY";                              break;
        case PROP_ID(PR_SENSITIVITY) :                              pszTag = "PR_SENSITIVITY";                                  break;
        case PROP_ID(PR_SUBJECT) :                                  pszTag = "PR_SUBJECT";                                      break;
        case PROP_ID(PR_SUBJECT_IPM) :                              pszTag = "PR_SUBJECT_IPM";                                  break;
        case PROP_ID(PR_CLIENT_SUBMIT_TIME) :                       pszTag = "PR_CLIENT_SUBMIT_TIME";                           break;
        case PROP_ID(PR_REPORT_NAME) :                              pszTag = "PR_REPORT_NAME";                                  break;
        case PROP_ID(PR_SENT_REPRESENTING_SEARCH_KEY) :             pszTag = "PR_SENT_REPRESENTING_SEARCH_KEY";                 break;
        case PROP_ID(PR_X400_CONTENT_TYPE) :                        pszTag = "PR_X400_CONTENT_TYPE";                            break;
        case PROP_ID(PR_NON_RECEIPT_REASON) :                       pszTag = "PR_NON_RECEIPT_REASON";                           break;
        case PROP_ID(PR_RECEIVED_BY_ENTRYID) :                      pszTag = "PR_RECEIVED_BY_ENTRYID";                          break;
        case PROP_ID(PR_RECEIVED_BY_NAME) :                         pszTag = "PR_RECEIVED_BY_NAME";                             break;
        case PROP_ID(PR_SENT_REPRESENTING_ENTRYID) :                pszTag = "PR_SENT_REPRESENTING_ENTRYID";                    break;
        case PROP_ID(PR_SENT_REPRESENTING_NAME) :                   pszTag = "PR_SENT_REPRESENTING_NAME";                       break;
        case PROP_ID(PR_RCVD_REPRESENTING_ENTRYID) :                pszTag = "PR_RCVD_REPRESENTING_ENTRYID";                    break;
        case PROP_ID(PR_RCVD_REPRESENTING_NAME) :                   pszTag = "PR_RCVD_REPRESENTING_NAME";                       break;
        case PROP_ID(PR_REPORT_ENTRYID) :                           pszTag = "PR_REPORT_ENTRYID";                               break;
        case PROP_ID(PR_READ_RECEIPT_ENTRYID) :                     pszTag = "PR_READ_RECEIPT_ENTRYID";                         break;
        case PROP_ID(PR_MESSAGE_SUBMISSION_ID) :                    pszTag = "PR_MESSAGE_SUBMISSION_ID";                        break;
        case PROP_ID(PR_PROVIDER_SUBMIT_TIME) :                     pszTag = "PR_PROVIDER_SUBMIT_TIME";                         break;
        case PROP_ID(PR_ORIGINAL_SUBJECT) :                         pszTag = "PR_ORIGINAL_SUBJECT";                             break;
        case PROP_ID(PR_DISC_VAL) :                                 pszTag = "PR_DISC_VAL";                                     break;
        case PROP_ID(PR_ORIG_MESSAGE_CLASS) :                       pszTag = "PR_ORIG_MESSAGE_CLASS";                           break;
        case PROP_ID(PR_ORIGINAL_AUTHOR_ENTRYID) :                  pszTag = "PR_ORIGINAL_AUTHOR_ENTRYID";                      break;
        case PROP_ID(PR_ORIGINAL_AUTHOR_NAME) :                     pszTag = "PR_ORIGINAL_AUTHOR_NAME";                         break;
        case PROP_ID(PR_ORIGINAL_SUBMIT_TIME) :                     pszTag = "PR_ORIGINAL_SUBMIT_TIME";                         break;
        case PROP_ID(PR_REPLY_RECIPIENT_ENTRIES) :                  pszTag = "PR_REPLY_RECIPIENT_ENTRIES";                      break;
        case PROP_ID(PR_REPLY_RECIPIENT_NAMES) :                    pszTag = "PR_REPLY_RECIPIENT_NAMES";                        break;
        case PROP_ID(PR_RECEIVED_BY_SEARCH_KEY) :                   pszTag = "PR_RECEIVED_BY_SEARCH_KEY";                       break;
        case PROP_ID(PR_RCVD_REPRESENTING_SEARCH_KEY) :             pszTag = "PR_RCVD_REPRESENTING_SEARCH_KEY";                 break;
        case PROP_ID(PR_READ_RECEIPT_SEARCH_KEY) :                  pszTag = "PR_READ_RECEIPT_SEARCH_KEY";                      break;
        case PROP_ID(PR_REPORT_SEARCH_KEY) :                        pszTag = "PR_REPORT_SEARCH_KEY";                            break;
        case PROP_ID(PR_ORIGINAL_DELIVERY_TIME) :                   pszTag = "PR_ORIGINAL_DELIVERY_TIME";                       break;
        case PROP_ID(PR_ORIGINAL_AUTHOR_SEARCH_KEY) :               pszTag = "PR_ORIGINAL_AUTHOR_SEARCH_KEY";                   break;
        case PROP_ID(PR_MESSAGE_TO_ME) :                            pszTag = "PR_MESSAGE_TO_ME";                                break;
        case PROP_ID(PR_MESSAGE_CC_ME) :                            pszTag = "PR_MESSAGE_CC_ME";                                break;
        case PROP_ID(PR_MESSAGE_RECIP_ME) :                         pszTag = "PR_MESSAGE_RECIP_ME";                             break;
        case PROP_ID(PR_ORIGINAL_SENDER_NAME) :                     pszTag = "PR_ORIGINAL_SENDER_NAME";                         break;
        case PROP_ID(PR_ORIGINAL_SENDER_ENTRYID) :                  pszTag = "PR_ORIGINAL_SENDER_ENTRYID";                      break;
        case PROP_ID(PR_ORIGINAL_SENDER_SEARCH_KEY) :               pszTag = "PR_ORIGINAL_SENDER_SEARCH_KEY";                   break;
        case PROP_ID(PR_ORIGINAL_SENT_REPRESENTING_NAME) :          pszTag = "PR_ORIGINAL_SENT_REPRESENTING_NAME";              break;
        case PROP_ID(PR_ORIGINAL_SENT_REPRESENTING_ENTRYID) :       pszTag = "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID";           break;
        case PROP_ID(PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY) :    pszTag = "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY";        break;
        case PROP_ID(PR_START_DATE) :                               pszTag = "PR_START_DATE";                                   break;
        case PROP_ID(PR_END_DATE) :                                 pszTag = "PR_END_DATE";                                     break;
        case PROP_ID(PR_OWNER_APPT_ID) :                            pszTag = "PR_OWNER_APPT_ID";                                break;
        case PROP_ID(PR_RESPONSE_REQUESTED) :                       pszTag = "PR_RESPONSE_REQUESTED";                           break;
        case PROP_ID(PR_SENT_REPRESENTING_ADDRTYPE) :               pszTag = "PR_SENT_REPRESENTING_ADDRTYPE";                   break;
        case PROP_ID(PR_SENT_REPRESENTING_EMAIL_ADDRESS) :          pszTag = "PR_SENT_REPRESENTING_EMAIL_ADDRESS";              break;
        case PROP_ID(PR_ORIGINAL_SENDER_ADDRTYPE) :                 pszTag = "PR_ORIGINAL_SENDER_ADDRTYPE";                     break;
        case PROP_ID(PR_ORIGINAL_SENDER_EMAIL_ADDRESS) :            pszTag = "PR_ORIGINAL_SENDER_EMAIL_ADDRESS";                break;
        case PROP_ID(PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE) :      pszTag = "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE";          break;
        case PROP_ID(PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS) : pszTag = "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS";     break;
        case PROP_ID(PR_CONVERSATION_TOPIC) :                       pszTag = "PR_CONVERSATION_TOPIC";                           break;
        case PROP_ID(PR_CONVERSATION_INDEX) :                       pszTag = "PR_CONVERSATION_INDEX";                           break;
        case PROP_ID(PR_ORIGINAL_DISPLAY_BCC) :                     pszTag = "PR_ORIGINAL_DISPLAY_BCC";                         break;
        case PROP_ID(PR_ORIGINAL_DISPLAY_CC) :                      pszTag = "PR_ORIGINAL_DISPLAY_CC";                          break;
        case PROP_ID(PR_ORIGINAL_DISPLAY_TO) :                      pszTag = "PR_ORIGINAL_DISPLAY_TO";                          break;
        case PROP_ID(PR_RECEIVED_BY_ADDRTYPE) :                     pszTag = "PR_RECEIVED_BY_ADDRTYPE";                         break;
        case PROP_ID(PR_RECEIVED_BY_EMAIL_ADDRESS) :                pszTag = "PR_RECEIVED_BY_EMAIL_ADDRESS";                    break;
        case PROP_ID(PR_RCVD_REPRESENTING_ADDRTYPE) :               pszTag = "PR_RCVD_REPRESENTING_ADDRTYPE";                   break;
        case PROP_ID(PR_RCVD_REPRESENTING_EMAIL_ADDRESS) :          pszTag = "PR_RCVD_REPRESENTING_EMAIL_ADDRESS";              break;
        case PROP_ID(PR_ORIGINAL_AUTHOR_ADDRTYPE) :                 pszTag = "PR_ORIGINAL_AUTHOR_ADDRTYPE";                     break;
        case PROP_ID(PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS) :            pszTag = "PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS";                break;
        case PROP_ID(PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE) :       pszTag = "PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE";           break;
        case PROP_ID(PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS) :  pszTag = "PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS";      break;
        case PROP_ID(PR_TRANSPORT_MESSAGE_HEADERS) :                pszTag = "PR_TRANSPORT_MESSAGE_HEADERS";                    break;

        #ifndef PR_DELEGATION
        #define PR_DELEGATION                                       PROP_TAG(PT_BINARY,     0x007E)
        #endif
        case PROP_ID(PR_DELEGATION) :                               pszTag = "PR_DELEGATION";                                   break;

        // Message content properties
        case PROP_ID(PR_BODY) :                                     pszTag = "PR_BODY";                                         break;
        case PROP_ID(PR_REPORT_TEXT) :                              pszTag = "PR_REPORT_TEXT";                                  break;
        case PROP_ID(PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY) :      pszTag = "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY";          break;
        case PROP_ID(PR_REPORTING_DL_NAME) :                        pszTag = "PR_REPORTING_DL_NAME";                            break;
        case PROP_ID(PR_REPORTING_MTA_CERTIFICATE) :                pszTag = "PR_REPORTING_MTA_CERTIFICATE";                    break;
        case PROP_ID(PR_RTF_SYNC_BODY_CRC) :                        pszTag = "PR_RTF_SYNC_BODY_CRC";                            break;
        case PROP_ID(PR_RTF_SYNC_BODY_COUNT) :                      pszTag = "PR_RTF_SYNC_BODY_COUNT";                          break;
        case PROP_ID(PR_RTF_SYNC_BODY_TAG) :                        pszTag = "PR_RTF_SYNC_BODY_TAG";                            break;
        case PROP_ID(PR_RTF_COMPRESSED) :                           pszTag = "PR_RTF_COMPRESSED";                               break;
        case PROP_ID(PR_RTF_SYNC_PREFIX_COUNT) :                    pszTag = "PR_RTF_SYNC_PREFIX_COUNT";                        break;
        case PROP_ID(PR_RTF_SYNC_TRAILING_COUNT) :                  pszTag = "PR_RTF_SYNC_TRAILING_COUNT";                      break;
        case PROP_ID(PR_ORIGINALLY_INTENDED_RECIP_ENTRYID) :        pszTag = "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID";            break;

        // Message recipient properties
        case PROP_ID(PR_CONTENT_INTEGRITY_CHECK) :                  pszTag = "PR_CONTENT_INTEGRITY_CHECK";                      break;
        case PROP_ID(PR_EXPLICIT_CONVERSION) :                      pszTag = "PR_EXPLICIT_CONVERSION";                          break;
        case PROP_ID(PR_IPM_RETURN_REQUESTED) :                     pszTag = "PR_IPM_RETURN_REQUESTED";                         break;
        case PROP_ID(PR_MESSAGE_TOKEN) :                            pszTag = "PR_MESSAGE_TOKEN";                                break;
        case PROP_ID(PR_NDR_REASON_CODE) :                          pszTag = "PR_NDR_REASON_CODE";                              break;
        case PROP_ID(PR_NDR_DIAG_CODE) :                            pszTag = "PR_NDR_DIAG_CODE";                                break;
        case PROP_ID(PR_NON_RECEIPT_NOTIFICATION_REQUESTED) :       pszTag = "PR_NON_RECEIPT_NOTIFICATION_REQUESTED";           break;

        #ifndef PR_DELIVERY_POINT
        #define PR_DELIVERY_POINT                                   PROP_TAG( PT_LONG,      0x0C07)
        #endif
        case PROP_ID(PR_DELIVERY_POINT) :                           pszTag = "PR_DELIVERY_POINT";                               break;

        case PROP_ID(PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED) : pszTag = "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED";     break;
        case PROP_ID(PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT) : pszTag = "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT";     break;
        case PROP_ID(PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY) :    pszTag = "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY";        break;
        case PROP_ID(PR_PHYSICAL_DELIVERY_MODE) :                   pszTag = "PR_PHYSICAL_DELIVERY_MODE";                       break;
        case PROP_ID(PR_PHYSICAL_DELIVERY_REPORT_REQUEST) :         pszTag = "PR_PHYSICAL_DELIVERY_REPORT_REQUEST";             break;
        case PROP_ID(PR_PHYSICAL_FORWARDING_ADDRESS) :              pszTag = "PR_PHYSICAL_FORWARDING_ADDRESS";                  break;
        case PROP_ID(PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED) :    pszTag = "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED";        break;
        case PROP_ID(PR_PHYSICAL_FORWARDING_PROHIBITED) :           pszTag = "PR_PHYSICAL_FORWARDING_PROHIBITED";               break;
        case PROP_ID(PR_PHYSICAL_RENDITION_ATTRIBUTES) :            pszTag = "PR_PHYSICAL_RENDITION_ATTRIBUTES";                break;
        case PROP_ID(PR_PROOF_OF_DELIVERY) :                        pszTag = "PR_PROOF_OF_DELIVERY";                            break;
        case PROP_ID(PR_PROOF_OF_DELIVERY_REQUESTED) :              pszTag = "PR_PROOF_OF_DELIVERY_REQUESTED";                  break;
        case PROP_ID(PR_RECIPIENT_CERTIFICATE) :                    pszTag = "PR_RECIPIENT_CERTIFICATE";                        break;
        case PROP_ID(PR_RECIPIENT_NUMBER_FOR_ADVICE) :              pszTag = "PR_RECIPIENT_NUMBER_FOR_ADVICE";                  break;
        case PROP_ID(PR_RECIPIENT_TYPE) :                           pszTag = "PR_RECIPIENT_TYPE";                               break;
        case PROP_ID(PR_REGISTERED_MAIL_TYPE) :                     pszTag = "PR_REGISTERED_MAIL_TYPE";                         break;
        case PROP_ID(PR_REPLY_REQUESTED) :                          pszTag = "PR_REPLY_REQUESTED";                              break;
        case PROP_ID(PR_REQUESTED_DELIVERY_METHOD) :                pszTag = "PR_REQUESTED_DELIVERY_METHOD";                    break;
        case PROP_ID(PR_SENDER_ENTRYID) :                           pszTag = "PR_SENDER_ENTRYID";                               break;
        case PROP_ID(PR_SENDER_NAME) :                              pszTag = "PR_SENDER_NAME";                                  break;
        case PROP_ID(PR_SUPPLEMENTARY_INFO) :                       pszTag = "PR_SUPPLEMENTARY_INFO";                           break;
        case PROP_ID(PR_TYPE_OF_MTS_USER) :                         pszTag = "PR_TYPE_OF_MTS_USER";                             break;
        case PROP_ID(PR_SENDER_SEARCH_KEY) :                        pszTag = "PR_SENDER_SEARCH_KEY";                            break;
        case PROP_ID(PR_SENDER_ADDRTYPE) :                          pszTag = "PR_SENDER_ADDRTYPE";                              break;
        case PROP_ID(PR_SENDER_EMAIL_ADDRESS) :                     pszTag = "PR_SENDER_EMAIL_ADDRESS";                         break;

        // Message non-transmittable properties
        case PROP_ID(PR_CURRENT_VERSION) :                          pszTag = "PR_CURRENT_VERSION";                              break;
        case PROP_ID(PR_DELETE_AFTER_SUBMIT) :                      pszTag = "PR_DELETE_AFTER_SUBMIT";                          break;
        case PROP_ID(PR_DISPLAY_BCC) :                              pszTag = "PR_DISPLAY_BCC";                                  break;
        case PROP_ID(PR_DISPLAY_CC) :                               pszTag = "PR_DISPLAY_CC";                                   break;
        case PROP_ID(PR_DISPLAY_TO) :                               pszTag = "PR_DISPLAY_TO";                                   break;
        case PROP_ID(PR_PARENT_DISPLAY) :                           pszTag = "PR_PARENT_DISPLAY";                               break;
        case PROP_ID(PR_MESSAGE_DELIVERY_TIME) :                    pszTag = "PR_MESSAGE_DELIVERY_TIME";                        break;
        case PROP_ID(PR_MESSAGE_FLAGS) :                            pszTag = "PR_MESSAGE_FLAGS";                                break;
        case PROP_ID(PR_MESSAGE_SIZE) :                             pszTag = "PR_MESSAGE_SIZE";                                 break;
        case PROP_ID(PR_PARENT_ENTRYID) :                           pszTag = "PR_PARENT_ENTRYID";                               break;
        case PROP_ID(PR_SENTMAIL_ENTRYID) :                         pszTag = "PR_SENTMAIL_ENTRYID";                             break;
        case PROP_ID(PR_CORRELATE) :                                pszTag = "PR_CORRELATE";                                    break;
        case PROP_ID(PR_CORRELATE_MTSID) :                          pszTag = "PR_CORRELATE_MTSID";                              break;
        case PROP_ID(PR_DISCRETE_VALUES) :                          pszTag = "PR_DISCRETE_VALUES";                              break;
        case PROP_ID(PR_RESPONSIBILITY) :                           pszTag = "PR_RESPONSIBILITY";                               break;
        case PROP_ID(PR_SPOOLER_STATUS) :                           pszTag = "PR_SPOOLER_STATUS";                               break;
        case PROP_ID(PR_TRANSPORT_STATUS) :                         pszTag = "PR_TRANSPORT_STATUS";                             break;
        case PROP_ID(PR_MESSAGE_RECIPIENTS) :                       pszTag = "PR_MESSAGE_RECIPIENTS";                           break;
        case PROP_ID(PR_MESSAGE_ATTACHMENTS) :                      pszTag = "PR_MESSAGE_ATTACHMENTS";                          break;
        case PROP_ID(PR_SUBMIT_FLAGS) :                             pszTag = "PR_SUBMIT_FLAGS";                                 break;
        case PROP_ID(PR_RECIPIENT_STATUS) :                         pszTag = "PR_RECIPIENT_STATUS";                             break;
        case PROP_ID(PR_TRANSPORT_KEY) :                            pszTag = "PR_TRANSPORT_KEY";                                break;
        case PROP_ID(PR_MSG_STATUS) :                               pszTag = "PR_MSG_STATUS";                                   break;
        case PROP_ID(PR_MESSAGE_DOWNLOAD_TIME) :                    pszTag = "PR_MESSAGE_DOWNLOAD_TIME";                        break;
        case PROP_ID(PR_CREATION_VERSION) :                         pszTag = "PR_CREATION_VERSION";                             break;
        case PROP_ID(PR_MODIFY_VERSION) :                           pszTag = "PR_MODIFY_VERSION";                               break;
        case PROP_ID(PR_HASATTACH) :                                pszTag = "PR_HASATTACH";                                    break;
        case PROP_ID(PR_BODY_CRC) :                                 pszTag = "PR_BODY_CRC";                                     break;
        case PROP_ID(PR_NORMALIZED_SUBJECT) :                       pszTag = "PR_NORMALIZED_SUBJECT";                           break;
        case PROP_ID(PR_RTF_IN_SYNC) :                              pszTag = "PR_RTF_IN_SYNC";                                  break;
        case PROP_ID(PR_ATTACH_SIZE) :                              pszTag = "PR_ATTACH_SIZE";                                  break;
        case PROP_ID(PR_ATTACH_NUM) :                               pszTag = "PR_ATTACH_NUM";                                   break;
        case PROP_ID(PR_PREPROCESS) :                               pszTag = "PR_PREPROCESS";                                   break;

        case PROP_ID(PR_ORIGINATING_MTA_CERTIFICATE) :              pszTag = "PR_ORIGINATING_MTA_CERTIFICATE";                  break;
        case PROP_ID(PR_PROOF_OF_SUBMISSION) :                      pszTag = "PR_PROOF_OF_SUBMISSION";                          break;

        // Properties common to multiple objects (including message objects)
        case PROP_ID(PR_ENTRYID) :                                  pszTag = "PR_ENTRYID";                                      break;
        case PROP_ID(PR_OBJECT_TYPE) :                              pszTag = "PR_OBJECT_TYPE";                                  break;
        case PROP_ID(PR_ICON) :                                     pszTag = "PR_ICON";                                         break;
        case PROP_ID(PR_MINI_ICON) :                                pszTag = "PR_MINI_ICON";                                    break;
        case PROP_ID(PR_STORE_ENTRYID) :                            pszTag = "PR_STORE_ENTRYID";                                break;
        case PROP_ID(PR_STORE_RECORD_KEY) :                         pszTag = "PR_STORE_RECORD_KEY";                             break;
        case PROP_ID(PR_RECORD_KEY) :                               pszTag = "PR_RECORD_KEY";                                   break;
        case PROP_ID(PR_MAPPING_SIGNATURE) :                        pszTag = "PR_MAPPING_SIGNATURE";                            break;
        case PROP_ID(PR_ACCESS_LEVEL) :                             pszTag = "PR_ACCESS_LEVEL";                                 break;
        case PROP_ID(PR_INSTANCE_KEY) :                             pszTag = "PR_INSTANCE_KEY";                                 break;
        case PROP_ID(PR_ROW_TYPE) :                                 pszTag = "PR_ROW_TYPE";                                     break;
        case PROP_ID(PR_ACCESS) :                                   pszTag = "PR_ACCESS";                                       break;

        // Properties common to multiple objects (NOT including message objects)
        case PROP_ID(PR_ROWID) :                                    pszTag = "PR_ROWID";                                        break;
        case PROP_ID(PR_DISPLAY_NAME) :                             pszTag = "PR_DISPLAY_NAME";                                 break;
        case PROP_ID(PR_ADDRTYPE) :                                 pszTag = "PR_ADDRTYPE";                                     break;
        case PROP_ID(PR_EMAIL_ADDRESS) :                            pszTag = "PR_EMAIL_ADDRESS";                                break;
        case PROP_ID(PR_COMMENT) :                                  pszTag = "PR_COMMENT";                                      break;
        case PROP_ID(PR_DEPTH) :                                    pszTag = "PR_DEPTH";                                        break;
        case PROP_ID(PR_PROVIDER_DISPLAY) :                         pszTag = "PR_PROVIDER_DISPLAY";                             break;
        case PROP_ID(PR_CREATION_TIME) :                            pszTag = "PR_CREATION_TIME";                                break;
        case PROP_ID(PR_LAST_MODIFICATION_TIME) :                   pszTag = "PR_LAST_MODIFICATION_TIME";                       break;
        case PROP_ID(PR_RESOURCE_FLAGS) :                           pszTag = "PR_RESOURCE_FLAGS";                               break;
        case PROP_ID(PR_PROVIDER_DLL_NAME) :                        pszTag = "PR_PROVIDER_DLL_NAME";                            break;
        case PROP_ID(PR_SEARCH_KEY) :                               pszTag = "PR_SEARCH_KEY";                                   break;

case PROP_ID(PR_PROVIDER_UID) :                             pszTag = "PR_PROVIDER_UID";                                 break;
        case PROP_ID(PR_PROVIDER_ORDINAL) :                         pszTag = "PR_PROVIDER_ORDINAL";                             break;

        // MAPI Form properties
        case PROP_ID(PR_FORM_VERSION) :                             pszTag = "PR_FORM_VERSION";                                 break;
        case PROP_ID(PR_FORM_CLSID) :                               pszTag = "PR_FORM_CLSID";                                   break;
        case PROP_ID(PR_FORM_CONTACT_NAME) :                        pszTag = "PR_FORM_CONTACT_NAME";                            break;
        case PROP_ID(PR_FORM_CATEGORY) :                            pszTag = "PR_FORM_CATEGORY";                                break;
        case PROP_ID(PR_FORM_CATEGORY_SUB) :                        pszTag = "PR_FORM_CATEGORY_SUB";                            break;
        case PROP_ID(PR_FORM_HOST_MAP) :                            pszTag = "PR_FORM_HOST_MAP";                                break;
        case PROP_ID(PR_FORM_HIDDEN) :                              pszTag = "PR_FORM_HIDDEN";                                  break;
        case PROP_ID(PR_FORM_DESIGNER_NAME) :                       pszTag = "PR_FORM_DESIGNER_NAME";                           break;
        case PROP_ID(PR_FORM_DESIGNER_GUID) :                       pszTag = "PR_FORM_DESIGNER_GUID";                           break;
        case PROP_ID(PR_FORM_MESSAGE_BEHAVIOR) :                    pszTag = "PR_FORM_MESSAGE_BEHAVIOR";                        break;

        //Message store properties
        case PROP_ID(PR_DEFAULT_STORE) :                            pszTag = "PR_DEFAULT_STORE";                                break;
        case PROP_ID(PR_STORE_SUPPORT_MASK) :                       pszTag = "PR_STORE_SUPPORT_MASK";                           break;
        case PROP_ID(PR_STORE_STATE) :                              pszTag = "PR_STORE_STATE";                                  break;
        case PROP_ID(PR_IPM_SUBTREE_SEARCH_KEY) :                   pszTag = "PR_IPM_SUBTREE_SEARCH_KEY";                       break;
        case PROP_ID(PR_IPM_OUTBOX_SEARCH_KEY) :                    pszTag = "PR_IPM_OUTBOX_SEARCH_KEY";                        break;
        case PROP_ID(PR_IPM_WASTEBASKET_SEARCH_KEY) :               pszTag = "PR_IPM_WASTEBASKET_SEARCH_KEY";                   break;
        case PROP_ID(PR_IPM_SENTMAIL_SEARCH_KEY) :                  pszTag = "PR_IPM_SENTMAIL_SEARCH_KEY";                      break;
        case PROP_ID(PR_MDB_PROVIDER) :                             pszTag = "PR_MDB_PROVIDER";                                 break;
        case PROP_ID(PR_RECEIVE_FOLDER_SETTINGS) :                  pszTag = "PR_RECEIVE_FOLDER_SETTINGS";                      break;
        case PROP_ID(PR_VALID_FOLDER_MASK) :                        pszTag = "PR_VALID_FOLDER_MASK";                            break;
        case PROP_ID(PR_IPM_SUBTREE_ENTRYID) :                      pszTag = "PR_IPM_SUBTREE_ENTRYID";                          break;
        case PROP_ID(PR_IPM_OUTBOX_ENTRYID) :                       pszTag = "PR_IPM_OUTBOX_ENTRYID";                           break;
        case PROP_ID(PR_IPM_WASTEBASKET_ENTRYID) :                  pszTag = "PR_IPM_WASTEBASKET_ENTRYID";                      break;
        case PROP_ID(PR_IPM_SENTMAIL_ENTRYID) :                     pszTag = "PR_IPM_SENTMAIL_ENTRYID";                         break;
        case PROP_ID(PR_VIEWS_ENTRYID) :                            pszTag = "PR_VIEWS_ENTRYID";                                break;
        case PROP_ID(PR_COMMON_VIEWS_ENTRYID) :                     pszTag = "PR_COMMON_VIEWS_ENTRYID";                         break;
        case PROP_ID(PR_FINDER_ENTRYID) :                           pszTag = "PR_FINDER_ENTRYID";                               break;

        // Folder and AB Container properties
        case PROP_ID(PR_CONTAINER_FLAGS) :                          pszTag = "PR_CONTAINER_FLAGS";                              break;
        case PROP_ID(PR_FOLDER_TYPE) :                              pszTag = "PR_FOLDER_TYPE";                                  break;
        case PROP_ID(PR_CONTENT_COUNT) :                            pszTag = "PR_CONTENT_COUNT";                                break;
        case PROP_ID(PR_CONTENT_UNREAD) :                           pszTag = "PR_CONTENT_UNREAD";                               break;
        case PROP_ID(PR_CREATE_TEMPLATES) :                         pszTag = "PR_CREATE_TEMPLATES";                             break;
        case PROP_ID(PR_DETAILS_TABLE) :                            pszTag = "PR_DETAILS_TABLE";                                break;
        case PROP_ID(PR_SEARCH) :                                   pszTag = "PR_SEARCH";                                       break;
        case PROP_ID(PR_SELECTABLE) :                               pszTag = "PR_SELECTABLE";                                   break;
        case PROP_ID(PR_SUBFOLDERS) :                               pszTag = "PR_SUBFOLDERS";                                   break;
        case PROP_ID(PR_STATUS) :                                   pszTag = "PR_STATUS";                                       break;
        case PROP_ID(PR_ANR) :                                      pszTag = "PR_ANR";                                          break;
        case PROP_ID(PR_CONTENTS_SORT_ORDER) :                      pszTag = "PR_CONTENTS_SORT_ORDER";                          break;
        case PROP_ID(PR_CONTAINER_HIERARCHY) :                      pszTag = "PR_CONTAINER_HIERARCHY";                          break;
        case PROP_ID(PR_CONTAINER_CONTENTS) :                       pszTag = "PR_CONTAINER_CONTENTS";                           break;
        case PROP_ID(PR_FOLDER_ASSOCIATED_CONTENTS) :               pszTag = "PR_FOLDER_ASSOCIATED_CONTENTS";                   break;
        case PROP_ID(PR_DEF_CREATE_DL) :                            pszTag = "PR_DEF_CREATE_DL";                                break;
        case PROP_ID(PR_DEF_CREATE_MAILUSER) :                      pszTag = "PR_DEF_CREATE_MAILUSER";                          break;
        case PROP_ID(PR_CONTAINER_CLASS) :                          pszTag = "PR_CONTAINER_CLASS";                              break;
        case PROP_ID(PR_CONTAINER_MODIFY_VERSION) :                 pszTag = "PR_CONTAINER_MODIFY_VERSION";                     break;
        case PROP_ID(PR_AB_PROVIDER_ID) :                           pszTag = "PR_AB_PROVIDER_ID";                               break;
        case PROP_ID(PR_DEFAULT_VIEW_ENTRYID) :                     pszTag = "PR_DEFAULT_VIEW_ENTRYID";                         break;
        case PROP_ID(PR_ASSOC_CONTENT_COUNT) :                      pszTag = "PR_ASSOC_CONTENT_COUNT";                          break;

        // Defined in Build 556 and above
        #ifndef PR_VIEW_BINARY
        #define PR_VIEW_BINARY          PROP_TAG( PT_BINARY,    0x3618)
        #define PR_VIEW_STRINGS         PROP_TAG( PT_TSTRING,   0x3619)
        #define PR_VIEW_FLAGS           PROP_TAG( PT_LONG,      0x361A)
        #define PR_VIEW_LINK_TO         PROP_TAG( PT_BINARY,    0x361B)
        #define PR_VIEW_VIEW_FOLDER     PROP_TAG( PT_BINARY,    0x361C)
        #define PR_VIEW_NAME            PROP_TAG( PT_TSTRING,   0x361D)
        #define PR_VIEW_VERSION         PROP_TAG( PT_LONG,      0x361E)
        #define PR_VIEW_COLLAPSE_STATE  PROP_TAG( PT_BINARY,    0x361F)
        #endif // PR_VIEW_BINARY

        case PROP_ID(PR_VIEW_BINARY) :                              pszTag = "PR_VIEW_BINARY";                                  break;
        case PROP_ID(PR_VIEW_STRINGS) :                             pszTag = "PR_VIEW_STRINGS";                                 break;
        case PROP_ID(PR_VIEW_FLAGS) :                               pszTag = "PR_VIEW_FLAGS";                                   break;
        case PROP_ID(PR_VIEW_LINK_TO) :                             pszTag = "PR_VIEW_LINK_TO";                                 break;
        case PROP_ID(PR_VIEW_VIEW_FOLDER) :                         pszTag = "PR_VIEW_VIEW_FOLDER";                             break;
        case PROP_ID(PR_VIEW_NAME) :                                pszTag = "PR_VIEW_NAME";                                    break;
        case PROP_ID(PR_VIEW_VERSION) :                             pszTag = "PR_VIEW_VERSION";                                 break;
        case PROP_ID(PR_VIEW_COLLAPSE_STATE) :                      pszTag = "PR_VIEW_COLLAPSE_STATE";                          break;

        // Attachment properties
        case PROP_ID(PR_ATTACHMENT_X400_PARAMETERS) :               pszTag = "PR_ATTACHMENT_X400_PARAMETERS";                   break;
        case PROP_ID(PR_ATTACH_DATA_OBJ) :                          pszTag = "PR_ATTACH_DATA_BIN or PR_ATTACH_DATA_OBJ";        break;
        case PROP_ID(PR_ATTACH_ENCODING) :                          pszTag = "PR_ATTACH_ENCODING";                              break;
        case PROP_ID(PR_ATTACH_EXTENSION) :                         pszTag = "PR_ATTACH_EXTENSION";                             break;
        case PROP_ID(PR_ATTACH_FILENAME) :                          pszTag = "PR_ATTACH_FILENAME";                              break;
        case PROP_ID(PR_ATTACH_METHOD) :                            pszTag = "PR_ATTACH_METHOD";                                break;
        case PROP_ID(PR_ATTACH_LONG_FILENAME) :                     pszTag = "PR_ATTACH_LONG_FILENAME";                         break;
        case PROP_ID(PR_ATTACH_PATHNAME) :                          pszTag = "PR_ATTACH_PATHNAME";                              break;
        case PROP_ID(PR_ATTACH_RENDERING) :                         pszTag = "PR_ATTACH_RENDERING";                             break;
        case PROP_ID(PR_ATTACH_TAG) :                               pszTag = "PR_ATTACH_TAG";                                   break;
        case PROP_ID(PR_RENDERING_POSITION) :                       pszTag = "PR_RENDERING_POSITION";                           break;
        case PROP_ID(PR_ATTACH_TRANSPORT_NAME) :                    pszTag = "PR_ATTACH_TRANSPORT_NAME";                        break;

        // Defined in Build 541 and above
        #ifndef PR_ATTACH_LONG_PATHNAME
        #define PR_ATTACH_LONG_PATHNAME                             PROP_TAG( PT_TSTRING,   0x370D)
        #define PR_ATTACH_MIME_TAG                                  PROP_TAG( PT_TSTRING,   0x370E)
        #endif
        case PROP_ID(PR_ATTACH_LONG_PATHNAME) :                     pszTag = "PR_ATTACH_LONG_PATHNAME";                         break;
        case PROP_ID(PR_ATTACH_MIME_TAG) :                          pszTag = "PR_ATTACH_MIME_TAG";                              break;

        // AB Object properties
        case PROP_ID(PR_DISPLAY_TYPE) :                             pszTag = "PR_DISPLAY_TYPE";                                 break;
        case PROP_ID(PR_TEMPLATEID) :                               pszTag = "PR_TEMPLATEID";                                   break;
        case PROP_ID(PR_PRIMARY_CAPABILITY) :                       pszTag = "PR_PRIMARY_CAPABILITY";                           break;

        // Mail user properties
        #ifndef PR_7BIT_DISPLAY_NAME
        #define PR_7BIT_DISPLAY_NAME                                PROP_TAG( PT_STRING8,   0x39FF)
        #endif
        case PROP_ID(PR_7BIT_DISPLAY_NAME) :                        pszTag = "PR_7BIT_DISPLAY_NAME";                                      break;
        case PROP_ID(PR_ACCOUNT) :                                  pszTag = "PR_ACCOUNT";                                      break;
        case PROP_ID(PR_ALTERNATE_RECIPIENT) :                      pszTag = "PR_ALTERNATE_RECIPIENT";                          break;
        case PROP_ID(PR_CALLBACK_TELEPHONE_NUMBER) :                pszTag = "PR_CALLBACK_TELEPHONE_NUMBER";                    break;
        case PROP_ID(PR_CONVERSION_PROHIBITED) :                    pszTag = "PR_CONVERSION_PROHIBITED";                        break;
        case PROP_ID(PR_DISCLOSE_RECIPIENTS) :                      pszTag = "PR_DISCLOSE_RECIPIENTS";                          break;
        case PROP_ID(PR_GENERATION) :                               pszTag = "PR_GENERATION";                                   break;
        case PROP_ID(PR_GIVEN_NAME) :                               pszTag = "PR_GIVEN_NAME";                                   break;
        case PROP_ID(PR_GOVERNMENT_ID_NUMBER) :                     pszTag = "PR_GOVERNMENT_ID_NUMBER";                         break;
        case PROP_ID(PR_OFFICE_TELEPHONE_NUMBER) :                  pszTag = "PR_OFFICE_TELEPHONE_NUMBER";                      break;
        case PROP_ID(PR_HOME_TELEPHONE_NUMBER) :                    pszTag = "PR_HOME_TELEPHONE_NUMBER";                        break;
        case PROP_ID(PR_INITIALS) :                                 pszTag = "PR_INITIALS";                                     break;
        case PROP_ID(PR_KEYWORD) :                                  pszTag = "PR_KEYWORD";                                      break;
        case PROP_ID(PR_LANGUAGE) :                                 pszTag = "PR_LANGUAGE";                                     break;
        case PROP_ID(PR_LOCATION) :                                 pszTag = "PR_LOCATION";                                     break;
        case PROP_ID(PR_MAIL_PERMISSION) :                          pszTag = "PR_MAIL_PERMISSION";                              break;
        case PROP_ID(PR_MHS_COMMON_NAME) :                          pszTag = "PR_MHS_COMMON_NAME";                              break;
        case PROP_ID(PR_ORGANIZATIONAL_ID_NUMBER) :                 pszTag = "PR_ORGANIZATIONAL_ID_NUMBER";                     break;
        case PROP_ID(PR_SURNAME) :                                  pszTag = "PR_SURNAME";                                      break;
        case PROP_ID(PR_ORIGINAL_ENTRYID) :                         pszTag = "PR_ORIGINAL_ENTRYID";                             break;
        case PROP_ID(PR_ORIGINAL_DISPLAY_NAME) :                    pszTag = "PR_ORIGINAL_DISPLAY_NAME";                        break;
        case PROP_ID(PR_ORIGINAL_SEARCH_KEY) :                      pszTag = "PR_ORIGINAL_SEARCH_KEY";                          break;
        case PROP_ID(PR_POSTAL_ADDRESS) :                           pszTag = "PR_POSTAL_ADDRESS";                               break;
        case PROP_ID(PR_COMPANY_NAME) :                             pszTag = "PR_COMPANY_NAME";                                 break;
        case PROP_ID(PR_TITLE) :                                    pszTag = "PR_TITLE";                                        break;
        case PROP_ID(PR_DEPARTMENT_NAME) :                          pszTag = "PR_DEPARTMENT_NAME";                              break;
        case PROP_ID(PR_OFFICE_LOCATION) :                          pszTag = "PR_OFFICE_LOCATION";                              break;
        case PROP_ID(PR_PRIMARY_TELEPHONE_NUMBER) :                 pszTag = "PR_PRIMARY_TELEPHONE_NUMBER";                     break;
        case PROP_ID(PR_OFFICE2_TELEPHONE_NUMBER) :                 pszTag = "PR_OFFICE2_TELEPHONE_NUMBER";                     break;
        case PROP_ID(PR_CELLULAR_TELEPHONE_NUMBER) :                pszTag = "PR_CELLULAR_TELEPHONE_NUMBER";                    break;
        case PROP_ID(PR_RADIO_TELEPHONE_NUMBER) :                   pszTag = "PR_RADIO_TELEPHONE_NUMBER";                       break;
        case PROP_ID(PR_CAR_TELEPHONE_NUMBER) :                     pszTag = "PR_CAR_TELEPHONE_NUMBER";                         break;
        case PROP_ID(PR_OTHER_TELEPHONE_NUMBER) :                   pszTag = "PR_OTHER_TELEPHONE_NUMBER";                       break;
        case PROP_ID(PR_TRANSMITABLE_DISPLAY_NAME) :                pszTag = "PR_TRANSMITABLE_DISPLAY_NAME";                    break;
        case PROP_ID(PR_BEEPER_TELEPHONE_NUMBER) :                  pszTag = "PR_BEEPER_TELEPHONE_NUMBER";                      break;
        case PROP_ID(PR_USER_CERTIFICATE) :                         pszTag = "PR_USER_CERTIFICATE";                             break;
        case PROP_ID(PR_PRIMARY_FAX_NUMBER) :                       pszTag = "PR_PRIMARY_FAX_NUMBER";                           break;
        case PROP_ID(PR_BUSINESS_FAX_NUMBER) :                      pszTag = "PR_BUSINESS_FAX_NUMBER";                          break;
        case PROP_ID(PR_HOME_FAX_NUMBER) :                          pszTag = "PR_HOME_FAX_NUMBER";                              break;
        case PROP_ID(PR_COUNTRY) :                                  pszTag = "PR_COUNTRY";                                      break;
        case PROP_ID(PR_LOCALITY) :                                 pszTag = "PR_LOCALITY";                                     break;
        case PROP_ID(PR_STATE_OR_PROVINCE) :                        pszTag = "PR_STATE_OR_PROVINCE";                            break;
        case PROP_ID(PR_STREET_ADDRESS) :                           pszTag = "PR_STREET_ADDRESS";                               break;
        case PROP_ID(PR_POSTAL_CODE) :                              pszTag = "PR_POSTAL_CODE";                                  break;
        case PROP_ID(PR_POST_OFFICE_BOX) :                          pszTag = "PR_POST_OFFICE_BOX";                              break;
        case PROP_ID(PR_TELEX_NUMBER) :                             pszTag = "PR_TELEX_NUMBER";                                 break;
        case PROP_ID(PR_ISDN_NUMBER) :                              pszTag = "PR_ISDN_NUMBER";                                  break;
        case PROP_ID(PR_ASSISTANT_TELEPHONE_NUMBER) :               pszTag = "PR_ASSISTANT_TELEPHONE_NUMBER";                   break;
        case PROP_ID(PR_HOME2_TELEPHONE_NUMBER) :                   pszTag = "PR_HOME2_TELEPHONE_NUMBER";                       break;
        case PROP_ID(PR_ASSISTANT) :                                pszTag = "PR_ASSISTANT";                                    break;
        case PROP_ID(PR_SEND_RICH_INFO) :                           pszTag = "PR_SEND_RICH_INFO";                               break;

        // Profile section properties
        case PROP_ID(PR_STORE_PROVIDERS) :                          pszTag = "PR_STORE_PROVIDERS";                              break;
        case PROP_ID(PR_AB_PROVIDERS) :                             pszTag = "PR_AB_PROVIDERS";                                 break;
        case PROP_ID(PR_TRANSPORT_PROVIDERS) :                      pszTag = "PR_TRANSPORT_PROVIDERS";                          break;
        case PROP_ID(PR_DEFAULT_PROFILE) :                          pszTag = "PR_DEFAULT_PROFILE";                              break;
        case PROP_ID(PR_AB_SEARCH_PATH) :                           pszTag = "PR_AB_SEARCH_PATH";                               break;
        case PROP_ID(PR_AB_DEFAULT_DIR) :                           pszTag = "PR_AB_DEFAULT_DIR";                               break;
        case PROP_ID(PR_AB_DEFAULT_PAB) :                           pszTag = "PR_AB_DEFAULT_PAB";                               break;
        case PROP_ID(PR_FILTERING_HOOKS) :                          pszTag = "PR_FILTERING_HOOKS";                              break;
        case PROP_ID(PR_SERVICE_NAME) :                             pszTag = "PR_SERVICE_NAME";                                 break;
        case PROP_ID(PR_SERVICE_DLL_NAME) :                         pszTag = "PR_SERVICE_DLL_NAME";                             break;
        case PROP_ID(PR_SERVICE_ENTRY_NAME) :                       pszTag = "PR_SERVICE_ENTRY_NAME";                           break;
        case PROP_ID(PR_SERVICE_UID) :                              pszTag = "PR_SERVICE_UID";                                  break;
        case PROP_ID(PR_SERVICE_EXTRA_UIDS) :                       pszTag = "PR_SERVICE_EXTRA_UIDS";                           break;
        case PROP_ID(PR_SERVICES) :                                 pszTag = "PR_SERVICES";                                     break;
        case PROP_ID(PR_SERVICE_SUPPORT_FILES) :                    pszTag = "PR_SERVICE_SUPPORT_FILES";                        break;
        case PROP_ID(PR_SERVICE_DELETE_FILES) :                     pszTag = "PR_SERVICE_DELETE_FILES";                         break;
        case PROP_ID(PR_AB_SEARCH_PATH_UPDATE) :                    pszTag = "PR_AB_SEARCH_PATH_UPDATE";                        break;

        // Status object properties
        case PROP_ID(PR_IDENTITY_DISPLAY) :                         pszTag = "PR_IDENTITY_DISPLAY";                             break;
        case PROP_ID(PR_IDENTITY_ENTRYID) :                         pszTag = "PR_IDENTITY_ENTRYID";                             break;
        case PROP_ID(PR_RESOURCE_METHODS) :                         pszTag = "PR_RESOURCE_METHODS";                             break;
        case PROP_ID(PR_RESOURCE_TYPE) :                            pszTag = "PR_RESOURCE_TYPE";                                break;
        case PROP_ID(PR_STATUS_CODE) :                              pszTag = "PR_STATUS_CODE";                                  break;
        case PROP_ID(PR_IDENTITY_SEARCH_KEY) :                      pszTag = "PR_IDENTITY_SEARCH_KEY";                          break;
        case PROP_ID(PR_OWN_STORE_ENTRYID) :                        pszTag = "PR_OWN_STORE_ENTRYID";                            break;
        case PROP_ID(PR_RESOURCE_PATH) :                            pszTag = "PR_RESOURCE_PATH";                                break;
        case PROP_ID(PR_STATUS_STRING) :                            pszTag = "PR_STATUS_STRING";                                break;
        case PROP_ID(PR_X400_DEFERRED_DELIVERY_CANCEL) :            pszTag = "PR_X400_DEFERRED_DELIVERY_CANCEL";                break;
        case PROP_ID(PR_HEADER_FOLDER_ENTRYID) :                    pszTag = "PR_HEADER_FOLDER_ENTRYID";                        break;
        case PROP_ID(PR_REMOTE_PROGRESS) :                          pszTag = "PR_REMOTE_PROGRESS";                              break;
        case PROP_ID(PR_REMOTE_PROGRESS_TEXT) :                     pszTag = "PR_REMOTE_PROGRESS_TEXT";                         break;
        case PROP_ID(PR_REMOTE_VALIDATE_OK) :                       pszTag = "PR_REMOTE_VALIDATE_OK";                           break;

        //Display table properties
        case PROP_ID(PR_CONTROL_FLAGS) :                            pszTag = "PR_CONTROL_FLAGS";                                break;
        case PROP_ID(PR_CONTROL_STRUCTURE) :                        pszTag = "PR_CONTROL_STRUCTURE";                            break;
        case PROP_ID(PR_CONTROL_TYPE) :                             pszTag = "PR_CONTROL_TYPE";                                 break;
        case PROP_ID(PR_DELTAX) :                                   pszTag = "PR_DELTAX";                                       break;
        case PROP_ID(PR_DELTAY) :                                   pszTag = "PR_DELTAY";                                       break;
        case PROP_ID(PR_XPOS) :                                     pszTag = "PR_XPOS";                                         break;
        case PROP_ID(PR_YPOS) :                                     pszTag = "PR_YPOS";                                         break;
        case PROP_ID(PR_CONTROL_ID) :                               pszTag = "PR_CONTROL_ID";                                   break;
        case PROP_ID(PR_INITIAL_DETAILS_PANE) :                     pszTag = "PR_INITIAL_DETAILS_PANE";                         break;

        default :
            if (0x8000 >= PROP_ID(ulPropTag))
            {
                wsprintf (szUnkTag, "Named Property (%x)", PROP_ID(ulPropTag));
            }
            else
            {
                wsprintf (szUnkTag, "UNKNOWN (%x)", PROP_ID(ulPropTag));
            }
            pszTag = szUnkTag;
            break;
    }
    char * pszType;
    switch (PROP_TYPE(ulPropTag))
    {
        case PT_UNSPECIFIED :   pszType = "PT_UNSPECIFIED"; break;
        case PT_NULL :          pszType = "PT_NULL";        break;
        case PT_SHORT :         pszType = "PT_SHORT";       break;
        case PT_LONG :          pszType = "PT_LONG";        break;
        case PT_FLOAT :         pszType = "PT_FLOAT";       break;
        case PT_DOUBLE :        pszType = "PT_DOUBLE";      break;
        case PT_CURRENCY :      pszType = "PT_CURRENCY";    break;
        case PT_APPTIME :       pszType = "PT_APPTIME";     break;
        case PT_ERROR :         pszType = "PT_ERROR";       break;
        case PT_BOOLEAN :       pszType = "PT_BOOLEAN";     break;
        case PT_OBJECT :        pszType = "PT_OBJECT";      break;
        case PT_I8 :            pszType = "PT_I8";          break;
        case PT_STRING8 :       pszType = "PT_STRING8";     break;
        case PT_UNICODE :       pszType = "PT_UNICODE";     break;
        case PT_SYSTIME :       pszType = "PT_SYSTIME";     break;
        case PT_CLSID :         pszType = "PT_CLSID";       break;
        case PT_BINARY :        pszType = "PT_BINARY";      break;
        default :
            wsprintf (szUnkType, "UNKNOWN (%x)", PROP_TYPE(ulPropTag));
            pszType = szUnkType;
            break;
    }
    dwSize = wsprintf (message, "Tag: %s  Type: %s\r\n", pszTag, pszType);
    WriteOutputString (message, dwSize);
}
#endif // TRACES_NO_MAPI

// End of file for TRACES.CPP


TRACES.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      TRACES.H
//
//  Description
//      Function declaration for the API's in TRACES.CPP
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.3
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1994-1996 Microsoft Corporation. All rights reserved.
//

#ifndef _TRACES_H
#define _TRACES_H

///////////////////////////////////////////////////////////////////////////////
//  Function prototype for the debug functions used to trace and log output
//  messages
//
#include <OBJBASE.H>

#define TRACES_CONSOLE              0x00000001
#define TRACES_LOG_FILE             0x00000002
#define TRACES_NO_COM_OUTPUT        0x00000004
#define TRACES_NO_ASSERTS           0x00000008
#define TRACES_NO_ASSERT_DIALOG     0x00000010

#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus

#ifdef ENABLE_DEBUG_OUTPUT

void WINAPI InitTraces (DWORD dwFlags);
void WINAPI UnInitTraces ();

void WINAPI  TraceFn1 (LPSTR);
void WINAPI  TraceFn2 (LPSTR, ULONG);
void WINAPI  TraceFn3 (LPSTR);
void __cdecl TraceFn4 (BOOL, LPSTR, ...);
void WINAPI  TraceIO  (ULONG);
void WINAPI  TraceIO2 (LPSTR, ULONG);

#ifndef TRACES_NO_MAPI
void WINAPI TraceProp (ULONG);
#else
#define TraceProp(a)
#endif // TRACES_NO_MAPI

#define TraceString1(a,b)           TraceFn4 (FALSE, a, b)
#define TraceString2(a,b,c)         TraceFn4 (FALSE, a, b, c)
#define TraceString3(a,b,c,d)       TraceFn4 (FALSE, a, b, c, d)
#define TraceString4(a,b,c,d,e)     TraceFn4 (FALSE, a, b, c, d, e)

#define TraceMessage(a)             TraceFn1 (a)
#define TraceMessageIf(a, b)        {if (b) TraceFn1 (a); }
#define TraceRaw(a)                 TraceFn3 (a)

// These functions will trace only if the error is non-zero
#define TraceRPCError(a,b)          {if (b) TraceFn4 (FALSE, "%s, RPC error: %d",a, b);}
#define TraceSysError(a,b)          {if (b) TraceFn4 (FALSE, "%s, Systen Error: %d",a, b);}
#define TraceResult(a, b)           {if (b) TraceFn2 (a, b);}
#define TraceDebugger(a,b)          TraceFn4 (TRUE, a, b)

void WINAPI AssertStatement (LPSTR, ULONG);
void WINAPI AssertStatementWithMsg (LPSTR, LPSTR, ULONG);

#ifndef ASSERT
#define ASSERT(a)           {if (!(a)) AssertStatement (__FILE__, __LINE__);}
#endif // ASSERT
#define ASSERTMSG(a, b)     {if (!(a)) AssertStatementWithMsg (b, __FILE__, __LINE__);}

#ifndef VERIFY
#define VERIFY(a)       ASSERT(a)
#endif // VERIFY

#ifdef  DISABLE_INFO_TRACES
#define TraceInfoMessage(a)
#else  // DISABLE_INFO_TRACES
#define TraceInfoMessage(a)     TraceFn1 (a)
#endif // DISABLE_INFO_TRACES

#else // ENABLE_DEBUG_OUTPUT

#define TraceMessage(a)
#define TraceMessageIf(a, b)
#define TraceInfoMessage(a)
#define TraceResult(a, b)
#define TraceIO(a)              a
#define TraceIO2(a, b)
#define TraceProp(a)
#define TraceRPCError(a,b)
#define TraceSysError(a,b)
#define TraceString1(a,b)
#define TraceString2(a,b,c)
#define TraceString3(a,b,c,d)
#define TraceString4(a,b,c,d,e)
#define TraceRaw(a)
#define TraceDebugger(a,b)

#ifndef ASSERT
#define ASSERT(a)
#endif // ASSERT
#define ASSERTMSG(a, b)

#ifndef VERIFY
#define VERIFY(a)               a
#endif // VERIFY

#define InitTraces(a)
#define UnInitTraces()

#endif // ENABLE_DEBUG_OUTPUT

#define AssertSz(a, b)      ASSERTMSG(a, b)

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // _TRACES_H

// End of file for TRACES.H


WEP.C

/*
 *  WEP.C
 *
 *  WEP function for MAPI DLLs.  This function is placed in a separate
 *  segment so marking the segment as FIXED is not hard on the system.
 *
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#if defined ( WIN16 )

#include <windows.h>
#include <mapidbg.h>

#pragma warning(disable:4100)   /* unreferenced formal parameter */

#pragma code_seg("WEP_TEXT")

/*--------------------------------------------------------------------------
 * Synopsis:    WEP
 *
 * Description: Required DLL exit function.  Does nothing.
 *
 * Parameters:
 * Returns:
 * Effects:
 * Notes:
 * Revision:
 *-------------------------------------------------------------------------*/
int FAR PASCAL __loadds  WEP( int nParam )
{
    return TRUE;
}

#endif


WRAP3D.C

/*
 *  WRAP3D.C
 */

#include <windows.h>
#include <mapix.h>
#ifdef  WIN16
#include <memory.h>
#endif
#include <mapiwin.h>
#include "wrap3d.h"

STDAPI_(LPVOID)
CTL3D_Initialize(HINSTANCE hinstMe)
{
    UINT        fuErr;
    HINSTANCE   hinst = 0;
    BOOL        fRegistered = FALSE;    /* assume failure */
    FARPROC     fpEnabled;
    FARPROC     fpRegister;
    LPCTX3D     pctx = NULL;
    DWORD       dwVer;

    /*
     * //$ If the Win 4 shell is present, don't even look for the DLL.
     * //$ Need to verify this algorithm.
     */

    dwVer = GetVersion();
    if (LOBYTE(LOWORD(dwVer)) >= 4)
        goto fail;

#ifdef _WIN32
    pctx = LocalAlloc(LPTR, sizeof(CTX3D));
    if (!pctx)
        goto fail;
#else
    if (MAPIAllocateBuffer(sizeof(CTX3D), &pctx))
        goto fail;
#endif
    ZeroMemory(pctx, sizeof(CTX3D));

    /* Attempt to load the DLL. Do not allow any error messages. */
    fuErr = SetErrorMode(SEM_NOOPENFILEERRORBOX);
#ifdef  _WIN32
    hinst = LoadLibraryA("CTL3D32.DLL");
    SetErrorMode(fuErr);
    if (!hinst)
        goto fail;
#else
    hinst = LoadLibrary("CTL3DV2.DLL");
    SetErrorMode(fuErr);
    if (hinst < HINSTANCE_ERROR)
        goto fail;
#endif

    /* Get the entry points we need. */
    if (!(fpEnabled = GetProcAddress(hinst, "Ctl3dEnabled")))
        goto fail;
    if (!(fpRegister = GetProcAddress(hinst, "Ctl3dRegister")))
        goto fail;
    if (!(pctx->fpUnregister = GetProcAddress(hinst, "Ctl3dUnregister")))
        goto fail;
    if (!(pctx->fpSubclassDlgEx = GetProcAddress(hinst, "Ctl3dSubclassDlgEx")))
        goto fail;
    if (!(pctx->fpColorChange = GetProcAddress(hinst, "Ctl3dColorChange")))
        goto fail;
    if ((pctx->fpIsAutoSubclass = GetProcAddress(hinst, "Ctl3dIsAutoSubclass")) &&
        (!(pctx->fpAutoSubclass = GetProcAddress(hinst, "Ctl3dAutoSubclass")) ||
        !(pctx->fpUnAutoSubclass = GetProcAddress(hinst, "Ctl3dUnAutoSubclass"))))
        goto fail;
    if (!(pctx->fpSubclassCtl = GetProcAddress(hinst, "Ctl3dSubclassCtl")))
        goto fail;
    if (!(pctx->fpGetVer = GetProcAddress(hinst, "Ctl3dGetVer")))
        goto fail;

    /* If we were not already registered, do so. */
    if (!(*fpEnabled)())
        pctx->fRegistered = (*fpRegister)(hinstMe);

    /* Remember the library handle. */
    pctx->hinst = hinst;
    pctx->hinstMe = hinstMe;

ret:
    return pctx;

fail:
    if (hinst)
        FreeLibrary(hinst);
#ifdef  _WIN32
    if (pctx)
        LocalFree(pctx);
#else
    MAPIFreeBuffer(pctx);
#endif
    pctx = NULL;
    goto ret;
}

STDAPI_(void)
CTL3D_Uninitialize(LPVOID lpv)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv))
        return;

    if (pctx->fRegistered)
        (*(pctx->fpUnregister))(pctx->hinstMe);

    FreeLibrary(pctx->hinst);

#ifdef  _WIN32
    LocalFree(pctx);
#else
    MAPIFreeBuffer(pctx);
#endif
}

STDAPI_(void)
CTL3D_Subclass(LPVOID lpv, HWND hwnd, DWORD dwFlags)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv))
        return;

    (*(pctx->fpSubclassDlgEx))(hwnd, dwFlags);
}

STDAPI_(void)
CTL3D_AutoSubclass(LPVOID lpv, HINSTANCE hinst, BOOL FAR * lpfAuto)
{
    LPCTX3D pctx;

    *lpfAuto = FALSE;
    if (!(pctx = (LPCTX3D) lpv) || !pctx->fpIsAutoSubclass)
        return;

    if (*lpfAuto = !pctx->fpIsAutoSubclass())
        pctx->fpAutoSubclass (hinst);

    return;
}

STDAPI_(BOOL)
CTL3D_IsAutoSubclass(LPVOID lpv)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv) || !pctx->fpIsAutoSubclass)
        return FALSE;

    return pctx->fpIsAutoSubclass();
}

STDAPI_(void)
CTL3D_CeaseAutoSubclass(LPVOID lpv, BOOL fAuto)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv) || !fAuto)
        return;

    pctx->fpUnAutoSubclass ();
    return;
}

STDAPI_(BOOL)
CTL3D_ColorChange(LPVOID lpv)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv))
        return FALSE;

    return pctx->fpColorChange();
}

STDAPI_(BOOL)
CTL3D_SubclassCtl(LPVOID lpv, HWND hwnd)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv))
        return FALSE;

    return pctx->fpSubclassCtl(hwnd);
}

STDAPI_(WORD)
CTL3D_GetVer(LPVOID lpv)
{
    LPCTX3D pctx;

    if (!(pctx = (LPCTX3D) lpv))
        return 0;

    return pctx->fpGetVer();
}


WRAP3D.H

/*
 *  WRAP3D.H
 *
 *  Wrapper functions for ctl3d. They are callable on WinNT, Win95
 *  (where they are no-ops), and Win16.
 *
 */

#ifndef _WRAP3D_H_
#define _WRAP3D_H_

#include <ctl3d.h>

#ifndef EXTERN_C
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif
#endif

EXTERN_C LPVOID WINAPI  CTL3D_Initialize(HINSTANCE hinstMe);
EXTERN_C void   WINAPI  CTL3D_Uninitialize(LPVOID lpv);
EXTERN_C void   WINAPI  CTL3D_Subclass(LPVOID lpv, HWND hwnd, DWORD dwFlags);
EXTERN_C void   WINAPI  CTL3D_AutoSubclass(LPVOID lpv, HINSTANCE hinst, BOOL FAR * pf);
EXTERN_C BOOL   WINAPI  CTL3D_IsAutoSubclass(LPVOID lpv);
EXTERN_C void   WINAPI  CTL3D_CeaseAutoSubclass(LPVOID lpv, BOOL f);
EXTERN_C BOOL   WINAPI  CTL3D_ColorChange(LPVOID lpv);
EXTERN_C BOOL   WINAPI  CTL3D_SubclassCtl(LPVOID lpv, HWND hwnd);
EXTERN_C WORD   WINAPI  CTL3D_GetVer(LPVOID lpv);

/*  Format of the LPV */

typedef struct
{
    HINSTANCE   hinst;              /* handle of ctl3d DLL */
    HINSTANCE   hinstMe;            /* handle of caller */
    BOOL        fRegistered;        /* TRUE if we've called Ctl3dRegister */

    FARPROC     fpUnregister;       /* entry points into ctl3d DLL... */
    FARPROC     fpSubclassDlgEx;
    FARPROC     fpIsAutoSubclass;
    FARPROC     fpAutoSubclass;
    FARPROC     fpUnAutoSubclass;
    FARPROC     fpColorChange;
    FARPROC     fpSubclassCtl;
    FARPROC     fpGetVer;
} CTX3D, FAR *LPCTX3D;

#endif /* _WRAP3D_H_ */


README.TXT

MAPI Sample Message Store

The DOCFile.Ms sample demonstrates how to use a sample message store.

The message store is based on the file system. Each folder is a directory
and each message is a file. Additional files hold properties of folders
and of the message store itself.

Files
-----

File          Description

*.FLD         Folder.
*.MSG         Message.
FOLDER.PRP    Folder properties (any folder).
MSGSTORE.PRP  Message store properties (root folder only).
RECFLDR.STG   Receive folder settings (root folder only).
CONTENTS.TBL  Folder contents table cache.
OUTGOING.TBL  Outgoing queue table cache (root folder only).
*.TMP         Message in composition.
*.RRT         Read receipt.

Entry IDs
---------

A sample store entry ID contains the path of the item to which it refers,
relative to the store root directory. This enables a sample message store
to be copied usnig the file system with relative impunity (except to a
subdirectory of another sample message store).

The sample message store does not distinguish between short- and long-term
entry IDs.

Message on Storage
------------------

The IMAPIProp interface for all objects relies heavily upon the IMessage
interface implemented in IMSG.DLL, which in turn is based upon OLE 2.0
docfiles (the IStorage interface).

Interface Lifetime and Cross-referencing
----------------------------------------

The sample message store links child objects back to their parents, for
leak detection and invalidation when the parent object is closed before
the child. Opening a table on an object (such as a folder) and then
closing the folder invalidates the object, but not the table. This method
is slightly more sophisticated than simply incrementing the object reference
count when the table is opened.

Contents and Hierarchy Tables
-----------------------------

The IMAPITable interface for contents and hierarchy tables relies upon the
IMAPITableData interface in MAPIU.DLL; the size of a folder is therefore
limited to the amount of RAM available for the contents table. Initially,
a contents table is created by scanning the directory for .MSG files,
extracting some properties from each file, and writing a row for each
message to the table data object. To speed up subsequent open operations,
the table is then written out to a .TBL file, and the .TBL file is carefully
kept up to date by subsequent changes in the folder. Hierarchy tables are not
cached; the sample message store uses notification internally to keep them
up to date while they are open.

Supported Features
------------------

Basic operations. The sample message store supports reading, creating,
modifying, and deleting messages; browsing, creating, and deleting folders;
submitting outbound message to transports; and associating all types of
attachments with messages. Multivalued properties are supported for all
objects. The sample message store can be used as the default message store
for a MAPI client.

Copy operations. The sample message store supports moving and copying
messages and folders. These operations are delegated to MAPI support
functions unless an operation occurs entirely within one sample message store.

Event notification. The sample message store supports event notification on
tables (again, courtesy of IMAPITableData). Object notifications and new-mail
notifications are not supported.

Multithread safety. On 32-bit Windows platforms, all sample message store
interfaces protect themselves from concurrent access by multiple threads.

Cross-platform support. The sample message store source code generates
executable files for Windows 3.X, Windows 95, and Windows NT 3.5 and above
from a single set of source files.

Unsupported Features
--------------------

Named properties (GetNamesFromIDs and GetIDsFromNames methods of IMAPIProp).

Searches (SetSearchCriteria method of IMAPIFolder).

Categorization of contents tables.

Folder-associated information (MAPI_ASSOCIATED, DEL_ASSOCIATED flags).

IMAPIStatus interface.

ServiceEntry interface for configuration.

Certain other methods are unsupported, including IMAPIFolder::OpenProperty
and IMsgStore::CopyTo.

Configuration
-------------

The sample message store has two configurable properties: the path to the
store root directory and a password. Configuration is supported at service
provider logon only.

Known Problems
--------------

No error message appears if a user types the wrong password when logging
onto a sample message store.

Named properties in folder contents tables are not handled correctly. The
store should not allow them, because every message has a different property
name to ID mapping; but they are allowed.

PR_NULL is handled incorrectly in IMAPIProps::GetProps. It should create
a placeholder in the returned property value array with PR_NULL for the
property tag. In fact, it places an error value (with MAPI_E_NOT_FOUND)
in the property value array and returns MAPI_W_ERRORS_RETURNED from
GetProps.

Installation
------------

To install the sample message store you have to

1. Merge the file smpms.inf with the mapisvc.inf located in your windows
   system directory. You can do it using mergeini utility that comes with
   MAPI SDK. The syntax is "mergeini -m smpms.inf".

2. Copy smpms(32).dll to the windows system directory.

3. Add the sample store to any MAPI profile using the Mail and Fax control
   panel applet.


MSP.C

/*
 *  M S P . C
 *
 *  Precompiled header source file
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"


MSP.H

/*
 *  M S P . H
 *
 *  Definitions for the MAPI Sample Message Store Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#ifdef _WIN32
#define _INC_OLE
#define INC_OLE2 /* Get the OLE2 stuff */
#define INC_RPC  /* harmless on NT 3.5; Win95 needs it */
#endif

#include <windows.h>
#include <ole2.h>

#include <mapispi.h>
#include <mapidbg.h>
#include <mapiwin.h>
#include <mapiutil.h>
#include <mapival.h>
#include <smpms.h>

#include <mapidefs.h>
#include <mapicode.h>
#include <mapitags.h>
#include <mapiguid.h>

#include <imessage.h>

#include <limits.h>
#include <memory.h>

#if defined(_WIN32)
#define OLE_CHAR WCHAR
#else
#define OLE_CHAR char
#endif

#define VALIDATE

#ifdef VALIDATE
#define OBJ_ValidateParameters(pobj, intf, method, cbObj, _lpVtbl, arglist) \
    {                                                                       \
        Validate##_##intf##_##method arglist;                               \
        if (IsBadWritePtr(pobj, cbObj) || (pobj)->lpVtbl != _lpVtbl)        \
            return(ResultFromScode(MAPI_E_INVALID_PARAMETER)); }

#define OBJ_CheckParameters(pobj, intf, method, cbObj, _lpVtbl, arglist)    \
    {                                                                       \
        CheckParameters##_##intf##_##method arglist;                        \
        AssertSz(!IsBadWritePtr(pobj, cbObj) && (pobj)->lpVtbl == _lpVtbl,  \
            "Bad object pointer"); }
#else
#define OBJ_ValidateParameters(pobj, intf, method, cbObj, _lpVtbl, arglist)
#define OBJ_CheckParameters(pobj, intf, method, cbObj, _lpVtbl, arglist)
#endif /* VALIDATE */

typedef LPVOID *PPV;

#define NUM_RETRIES 6           /* number of times to retry opening a file */

/* Per-instance data. */
typedef struct
{
    UINT        cRef;
    LPMALLOC    lpmalloc;
} INST, *PINST;

/* Linked Memory Routines */
typedef struct _lmr
{
    LPALLOCATEBUFFER    lpAllocBuf;
    LPALLOCATEMORE      lpAllocMore;
    LPFREEBUFFER        lpFreeBuf;
}   LMR, *PLMR;

#define         LMAlloc(plmr, lcb, ppv)             ((plmr)->lpAllocBuf(lcb, ppv))
#define         LMAllocMore(plmr, lcb, pvLink, ppv) ((plmr)->lpAllocMore(lcb, pvLink, ppv))
#define         LMFree(plmr, pv)                    ((plmr)->lpFreeBuf(pv))

#define IsBadIfacePtr(param, iface)                 \
            (IsBadReadPtr((param), sizeof(iface))   \
        ||  IsBadReadPtr((param)->lpVtbl, sizeof(iface##Vtbl)))

#define SMPMS_VERSION   (0x03)  /* For MAPI 1.0 */

typedef struct _EID             /* Sample Message Store EntryID */
{
    BYTE abFlags[4];
    MAPIUID uidResource;
    BYTE bVersion;
    BYTE bVerPad[3];
    TCHAR szPath[1];
} EID, *PEID;

#define CbNewEID(_cbPath) \
    (offsetof(EID,szPath) + (_cbPath)*sizeof(CHAR))

/* This includes the NULL terminator */
#define CbEID(_peid) \
    (offsetof(EID,szPath) + \
    (((UINT)(lstrlen((_peid)->szPath)))*sizeof(CHAR)) + sizeof(CHAR))

/* This includes the NULL */
#define CbEIDPath(peid) (CbEID(peid)-CbNewEID(0))

#define PR_FILENAME_SEQUENCE_NUMBER PROP_TAG(PT_LONG,       0x6600)
#define PR_SMS_CONTENTS_SORT_ORDER  PROP_TAG(PT_MV_LONG,    0x6601)

#if defined(_WIN32)
#define CRITICAL_SECTION_MEMBERS    CRITICAL_SECTION cs;
#define CRITICAL_SECTION_MEMBERS_P  CRITICAL_SECTION *pcs;
#else
#define CRITICAL_SECTION_MEMBERS
#define CRITICAL_SECTION_MEMBERS_P
#endif

/* Object typedefs ------------------------------------------------------- */

typedef struct _OBJ     OBJ,        * POBJ,     ** PPOBJ;
typedef struct _MSP     MSP,        * PMSP,     ** PPMSP;
typedef struct _MSL     MSL,        * PMSL,     ** PPMSL;
typedef struct _IMS     IMS,        * PIMS,     ** PPIMS;
typedef struct _IFLD    IFLD,       * PIFLD,    ** PPIFLD;
typedef struct _IMSG    IMSG,       * PIMSG,    ** PPIMSG;
typedef struct _IATCH   IATCH,      * PIATCH,   ** PPIATCH;
typedef struct _STM     STM,        * PSTM,     ** PPSTM;
typedef struct _STG     STG,        * PSTG,     ** PPSTG;

/* Standard Object --------------------------------------------------------- */

#undef  INTERFACE
#define INTERFACE struct _OBJ

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, OBJ_)
        MAPI_IUNKNOWN_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(OBJ_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
};

#define STANDARD_OBJECT_MEMBERS     \
    LONG                cRef;       \
    WORD                wType;      \
    WORD                wFlags;     \
    POBJ                pobjParent; \
    POBJ                pobjNext;   \
    POBJ                pobjHead;   \
    PIMS                pims;       \
    CRITICAL_SECTION_MEMBERS_P      \

/* NOTE: If you modify this enumeration, you must also update various */
/* structures in mspobj.c which use the type as an index into lookup tables */
enum {
    OT_MSPROVIDER,
    OT_MSLOGON,
    OT_MSGSTORE,
    OT_FOLDER,
    OT_MESSAGE,
    OT_ATTACH,
    OT_STREAM,
    OT_TABLE,
    OT_MAX
};

#define OBJF_MODIFY     ((WORD)0x0001)

struct _OBJ
{
    OBJ_Vtbl *      lpVtbl;
    STANDARD_OBJECT_MEMBERS
};

typedef void (*LPFNNEUTER)(POBJ);
extern LPFNNEUTER rgfnNeuter[];

#define OBJ_SetFlag(pobj, f)    ((pobj)->wFlags |=  (f))
#define OBJ_ClearFlag(pobj, f)  ((pobj)->wFlags &= ~(f))
#define OBJ_TestFlag(pobj, f)   ((pobj)->wFlags &   (f))

#ifdef _WIN32
#define OBJ_Initialize(pobj, _vtbl, _wType, _pims, _pcs)    \
    (pobj)->lpVtbl      = _vtbl;    \
    (pobj)->cRef        = 1;        \
    (pobj)->wType       = _wType;   \
    (pobj)->pims        = _pims;    \
    (pobj)->pcs         = _pcs;
#else
#define OBJ_Initialize(pobj, _vtbl, _wType, _pims, _pcs)    \
    (pobj)->lpVtbl      = _vtbl;    \
    (pobj)->cRef        = 1;        \
    (pobj)->wType       = _wType;   \
    (pobj)->pims        = _pims;
#endif

#define OBJ_EnterCriticalSection(pobj)  EnterCriticalSection((pobj)->pcs)
#define OBJ_LeaveCriticalSection(pobj)  LeaveCriticalSection((pobj)->pcs)
#define MSP_EnterCriticalSection(pmsp)  OBJ_EnterCriticalSection((POBJ)pmsp)
#define MSP_LeaveCriticalSection(pmsp)  OBJ_LeaveCriticalSection((POBJ)pmsp)
#define IMS_EnterCriticalSection(pims)  OBJ_EnterCriticalSection((POBJ)pims)
#define IMS_LeaveCriticalSection(pims)  OBJ_LeaveCriticalSection((POBJ)pims)

/*
 *  MSPOBJ.C
 */

BOOL    FQueryInterface(int wType, REFIID riid);

void    OBJ_Enqueue(POBJ pobj, POBJ pobjParent);
void    OBJ_Dequeue(POBJ pobj);
void    OBJ_Destroy(POBJ pobj);

extern CHAR szFolderTemplate[];         /*  "*.fld"         */
extern CHAR szMessageTemplate[];        /*  "*.msg"         */
extern CHAR szPropertyFileName[];       /*  "folder.prp"    */
extern CHAR szHierarchyFileName[];      /*  "hierarch.tbl"  */
extern CHAR szContentsFileName[];       /*  "contents.tbl"  */
extern CHAR szOutgoingFileName[];       /*  "outgoing.tbl"  */

/*
 *  MSPRFS.C
 */

/* Manifest constants */

#define RFS_CREATE      ((ULONG) 0x00000001)

/* Typedefs */

typedef struct _RFS         /* Receive Folder Storage */
{
    LPTSTR szFile;          /* Name of docfile containing RecFldr settings */
} RFS, * PRFS;

typedef struct _RFN         /* A single RFS Node */
{
    LPTSTR szClass;         /* Name of the message class */
    LPTSTR szName;          /* Relative path name of receive folder, i.e. */
                            /* EntryID minus the GUID                     */
} RFN, * PRFN;

/* Exported functions */

BOOL FIsValidMessageClass(LPTSTR szMessageClass);
HRESULT OpenRFS (LPTSTR szStoreRoot, LPTSTR szFile, ULONG ulFlags,
    PRFS * lpprfs);
HRESULT GetRFN (PRFS prfs, LPTSTR szClassName, PRFN * pprfn);
VOID    FreeRFN (PRFN prfn);
HRESULT DeleteRFN (PRFS prfs, LPTSTR szClassName);
HRESULT AddRFN (PRFS, PRFN prfn);
HRESULT CloseRFS (PRFS prfs);

/*
 *  MSPMS.C
 */

/* Manifest constants */

#define IMS_CREATE      ((WORD) 0x0002)
#define IMS_INVALID     ((WORD) 0x0004)

/* Typedefs */

#undef  INTERFACE
#define INTERFACE   struct _IMS

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, IMS_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMSGSTORE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, IMS_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMSGSTORE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(IMS_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMSGSTORE_METHODS(IMPL)
};

struct _IMS             /* Implementation of IMsgStore */
{
    IMS_Vtbl *      lpVtbl;     /* -> vtblIMS */
    STANDARD_OBJECT_MEMBERS
    LPTSTR      szStorePath;    /* Full path to the store root */
    MAPIUID     uidResource;    /* Message Store unique identifier */
    LPTSTR      szProps;        /* Full path to property docfile */
    PMSL        pmsl;           /* Logon object (MAPI session stuff) */
    PMSP        pmsp;           /* Provider object (global stuff) */
    LMR         lmr;            /* Linked memory routines */
    PRFS        prfs;           /* Struct handling access to receive folder */
    LPMAPISUP   psup;           /* MAPI Support object */
    SBinary     eidStore;       /* PR_STORE_ENTRYID */
    LPTABLEDATA lptblOutgoing;  /* outgoing queue for this store */
    HANDLE      hOGQueueMutex;  /* Mutex for reading/writing the OG Queue */
    FILETIME    ftOGQueue;      /* The time of the OQ file when we read it */
    ULONG       cOutgoingViews; /* number of views open on the outgoing queue */
    ULONG       ulOQConn;       /* Connection for OQ unadvise */
    HANDLE      hContTblMutex;  /* Mutex for reading/writing ALL cont tbls */
    ULONG       ulTblConn;      /* Connection for Tbl unadvise */
    LPMSGSESS   pmsgsess;       /* IMSG Session to create all messages within */
    ULONG       ulFlagsSLT;     /* Flags for StoreLogoffTransports */
};

#define MSF_SPOOLER         ((WORD)0x8000)
#define MSF_BEINGDESTROYED  ((WORD)0x4000)

#define SMS_SUPPORTMASK     (STORE_ENTRYID_UNIQUE |     \
                            STORE_ATTACH_OK |           \
                            STORE_OLE_OK |              \
                            STORE_NOTIFY_OK |           \
                            STORE_MV_PROPS_OK |         \
                            STORE_RESTRICTION_OK |      \
                            STORE_SORT_OK |             \
                            STORE_MODIFY_OK |           \
                            STORE_CREATE_OK |           \
                            STORE_SUBMIT_OK)

/* Outgoing Queue Notification Block (ONB) */
/* This is the format of our extended cross-process notification for the */
/* outgoing queue table. We use this to update the table data in other */
/* processes after we change it in the originating process. The changes */
/* that we pass across are either deleting a row (for example, when the */
/* spooler calls FinishedMsg), or adding a row (for example, when the */
/* client submits a message). */

typedef struct _ONB
{
    FILETIME ftBeforeUpdate;    /* time of the file before updating */
    FILETIME ftAfterUpdate;     /* time of the file after applying the update */
    LPVOID  pvRef;              /* memory offset from originating process */
    ULONG   cbNtf;              /* # of bytes in abNtf (the flat notif below) */
    BYTE    abNtf[1];           /* the update to apply to the table */
} ONB, * PONB;

#define CbNewONB(_cb)       (offsetof(ONB,abNtf) + (_cb))
#define CbONB(_ponb) \
    (offsetof(ONB,abNtf) + (UINT)((_ponb)->cbNtf))

/* Table Notification Block (TNB) */
/* This is the format of our extended cross-process notification for */
/* Contents and Hierarchy Tables. We use this notification to update a */
/* table in other processes after we change it in the originating process. */
/* The notification consists of an object notification containing the */
/* entryids we need. The fnev tells the event type. We only use two of */
/* the entryids in the object notification. The ParentID fields refer to */
/* the parent folder of the table we need to update. The EntryID fields */
/* refer to the object that changed within the folder. The ulObjType field */
/* will be either MAPI_MESSAGE (for contents table changes) or MAPI_FOLDER */
/* (for hierarchy table changes).  All other fields in the structure are */
/* unused and should be set to 0. */

typedef struct _TNB
{
    ULONG   ulTableEvent;   /* TABLE_ROW_ (ADDED, DELETED, MODIFIED) */
    LPVOID  pvRef;          /* memory offset from originating process */
    ULONG   cbNtf;          /* # of bytes in abNtf (the flat notif below) */
    BYTE    abNtf[1];       /* an object notification */
} TNB, * PTNB;

#define CbNewTNB(_cb)       (offsetof(TNB,abNtf) + (_cb))
#define CbTNB(_ptnb) \
    (offsetof(TNB,abNtf) + (UINT)((_ptnb)->cbNtf))

/* Exported functions */
HRESULT HrOpenIMSPropsFileRetry(LPMSGSESS pmsgsess, LPTSTR szFile, PLMR plmr,
    LPMAPISUP psup, BOOL fModify, LPMESSAGE * lppmsg);
BOOL IMS_IsInvalid(PIMS pims);

/* Constructors */

HRESULT HrNewIMS(LPTSTR szStorePath, LPTSTR szStoreProps, PMSP pmsp,
    PMSL pmsl, PRFS prfs, LPPROFSECT pps, LPMAPISUP psup, BOOL fCreate,
    PIMS *ppims);

/* Non-virtual public methods */

void IMS_Neuter(PIMS pims);
HRESULT HrInitIMSProps (PIMS pims, LPTSTR szPswd);
VOID GetResourceUID (PIMS pims, LPMAPIUID lpuid);
HRESULT HrUniqueFileName (PIMS pims, ULONG * lpulSeqNumber, LPTSTR *
    lppszNewName);
BOOL FIsInvalidEID (ULONG, PEID, PIMS);
/* Others */

STDAPI_(void) MsgReleaseStg (ULONG ulCallerData, LPMESSAGE lpMessage);
long STDAPICALLTYPE LSMSNotifCallback (LPVOID lpvContext,
    ULONG cNotif, LPNOTIFICATION lpNotifs);

/* column properties for outgoing queue */
#define OUTGOING_COLUMNS    13      /* number of columns in outgoing queue */
static const SizedSPropTagArray(OUTGOING_COLUMNS, sptaOutgoing) =
{
    OUTGOING_COLUMNS,
    {
        PR_ENTRYID,
        PR_SUBMIT_FLAGS,
        PR_INSTANCE_KEY,    /* the index column */
        PR_DISPLAY_TO,
        PR_DISPLAY_CC,
        PR_DISPLAY_BCC,
        PR_SENDER_NAME,
        PR_SUBJECT,
        PR_CLIENT_SUBMIT_TIME,
        PR_PRIORITY,
        PR_MESSAGE_FLAGS,
        PR_MESSAGE_SIZE,
        PR_SPOOLER_STATUS
    }
};

/*
 *  MSPFLD.C
 */

#define FAILED_SEARCH   (INVALID_HANDLE_VALUE)

#define MODIFY_INDEX    1       /* index of LAST_MODIFICATION_TIME in tables */

/* column properties for contents tables */
#define CONTENTS_COLUMNS    22      /* number of columns in a table of messages */
static const SizedSPropTagArray(CONTENTS_COLUMNS, sPropTagsContents) =
{
    CONTENTS_COLUMNS,
    {
        PR_ENTRYID,
        PR_LAST_MODIFICATION_TIME,  /* must be in position MODIFY_INDEX */
        PR_INSTANCE_KEY,            /* the index column */
        PR_HASATTACH,
        PR_SUBJECT,
        PR_SENDER_NAME,
        PR_DISPLAY_TO,
        PR_DISPLAY_CC,
        PR_CLIENT_SUBMIT_TIME,
        PR_MESSAGE_DELIVERY_TIME,
        PR_MESSAGE_FLAGS,
        PR_PRIORITY,
        PR_CONVERSATION_KEY,
        PR_SEARCH_KEY,
        PR_ICON,
        PR_MINI_ICON,
        PR_SENSITIVITY,
        PR_MESSAGE_CLASS,
        PR_RECORD_KEY,
        PR_SPOOLER_STATUS,
        PR_SENT_REPRESENTING_NAME,
        PR_MSG_STATUS
    }
};

/* column properties for hierarchy tables */
#define HIERARCHY_COLUMNS   12
static const SizedSPropTagArray(HIERARCHY_COLUMNS, sPropTagsHierarchy) =
{
    HIERARCHY_COLUMNS,
    {
        PR_ENTRYID,
        PR_LAST_MODIFICATION_TIME, /* must be in postion MODIFY_INDEX */
        PR_DISPLAY_NAME,    /* the folder name */
        PR_INSTANCE_KEY,    /* the index column */
        PR_OBJECT_TYPE,
        PR_COMMENT,
        PR_CONTENT_COUNT,
        PR_CONTENT_UNREAD,
        PR_STATUS,
        PR_SUBFOLDERS,
        PR_FOLDER_TYPE,
        PR_DEPTH            /* depth in hierarchy, must be last */
    }
} ;

#undef  INTERFACE
#define INTERFACE struct _IFLD

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, IFLD_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IMAPIFOLDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, IFLD_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IMAPIFOLDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(IFLD_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPICONTAINER_METHODS(IMPL)
    MAPI_IMAPIFOLDER_METHODS(IMPL)
};

extern IFLD_Vtbl    vtblIFLD;

/* we store PR_ENTRYID, PR_PARENT_ENTRYID and PR_INSTANCE_KEY internally */
#define cpropIFLDInternal       3

/* Folder's instance data */
struct _IFLD
{
    IFLD_Vtbl *     lpVtbl; /* -> vtblIFLD */
    STANDARD_OBJECT_MEMBERS
    PEID peid;                  /* EntryID of this folder */
    ULONG           cval;       /* # of internal props */
    LPSPropValue    pval;       /* internal prop values */
    LPTABLEDATA lptblContents;  /* contents table for this folder */
    ULONG cContentsViews;       /* number of views open on contents table*/
    LPTABLEDATA lptblHierarchy; /* hierarchy table for this folder */
    ULONG cHierarchyViews;      /* number of views open on hierarchy table */
};

HRESULT HrNewIFLD(PEID peid, PIMS pims, BOOL fModify, PIFLD * ppifld);
HRESULT HrCreateFolderStorage(PIFLD pifld, ULONG ulFolderType,
    LPSTR szFolderName, LPSTR szFolderComment, BOOL fCreateDir,
    PIMS pims, PEID *ppeid);
HRESULT HrNewEID (PIFLD, PIMS, LPTSTR, ULONG *, PEID *);
HRESULT HrIncrementOneROProp(PIFLD pifld, LONG lDelta, ULONG ulPT);
HRESULT HrIsParent(PEID peidParent, PEID peidChild, BOOL *pfIsParent);
HRESULT HrGetFileModTime(LPTSTR szStorePath, LPTSTR szFileName,
    FILETIME *pft);
HRESULT HrFullPathName (LPSTR, LPSTR, LPSTR, LPSTR *);
HRESULT HrFullToRelative(LPTSTR, PIMS, LPTSTR *);
HRESULT HrOpenPropertyMessageRetry(PEID peid, PIMS pims,
    BOOL fModifyExclusive, LPMESSAGE *lppmsg);
HRESULT HrFindFirstID( PIFLD, LPTSTR, ULONG *, LPTSTR *, HANDLE *,
                    WIN32_FIND_DATA *, PEID *);
HRESULT HrFindNextID( PIFLD, ULONG, LPTSTR, HANDLE, WIN32_FIND_DATA *, PEID *);
void CloseIDSearch( HANDLE *, LPTSTR *);
HRESULT HrFullToRelative( LPTSTR, PIMS, LPTSTR *);
HRESULT HrUpdateRow(PIMS pims, LPTABLEDATA lptbl, PEID peid,
    LPSPropTagArray pPTA, FILETIME *pft, ULONG ulObjType);
void    IFLD_Neuter (PIFLD);
void ChangeTable(PIMS pims, PEID peidTable, PEID peidObject,
    ULONG ulObjType, ULONG ulTableEvent, BOOL fSendNotif);
HRESULT HrRemoveRow( LPTABLEDATA lptbl, PEID peid);

/*
 *  MSPMSG.C
 */

#undef  INTERFACE
#define INTERFACE struct _IMSG

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, IMSG_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMESSAGE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, IMSG_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMESSAGE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(IMSG_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMESSAGE_METHODS(IMPL)
};

extern IMSG_Vtbl    vtblIMSG;

struct _IMSG            /* Implementation of IMessage */
{
    IMSG_Vtbl * lpVtbl; /* -> vtblIMSG */
    STANDARD_OBJECT_MEMBERS
    LPMESSAGE       lpmsg;
    PEID            peid;
    ULONG           cval;
    LPSPropValue    pval;
};

#define MSGF_NEWLYCREATED           ((WORD)0x4000)
#define MSGF_MSGINMSG               ((WORD)0x2000)
#define MSGF_FRESH                  ((WORD)0x1000)
#define MSGF_CREATEDSTORAGE         ((WORD)0x0800)

#define SET         1
#define UNSET       2
#define DONT_SAVE   4

/* Exported functions */

HRESULT HrNewIMSG(PEID peid, PIMS pims, BOOL fCreate, BOOL fModify,
    ULONG ulSeqNum, LPSTR *pszFull, PIMSG *ppimsg);
HRESULT HrSetFlags(PIMSG, ULONG, ULONG, ULONG);
HRESULT NewIMSGInIATCH (LPMESSAGE lpmsg, POBJ pobj, ULONG ulFlags, PIMSG * ppimsg);
HRESULT InitIMSGProps(PIMSG pimsg);
void IMSG_Neuter (PIMSG);
HRESULT HrSetInternalProps(PLMR plmr, ULONG cprop, LPSPropValue *ppval,
    ULONG *pcval, PEID peid, PEID peidParent, ULONG ulSeqNum);

/*
 *  MSPATCH.C
 */

#undef  INTERFACE
#define INTERFACE struct _IATCH

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, IATCH_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IATTACH_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, IATCH_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IATTACH_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(IATCH_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IATTACH_METHODS(IMPL)
};

extern IATCH_Vtbl   vtblIATCH;

struct _IATCH           /* Implementation of IAttach */
{
    IATCH_Vtbl *    lpVtbl; /* -> vtblIATCH */
    STANDARD_OBJECT_MEMBERS
    LPATTACH lpattach;
};

/* Exported functions */

HRESULT HrNewIATCH(LPATTACH lpattach, PIMSG pimsg, BOOL fModify,
    PIATCH * ppiatch);
void IATCH_Neuter (PIATCH piatch);

/*
 *  MSPGLE.C
 */

HRESULT MapScodeSz (SCODE scArg, PIMS pims, LPTSTR * lppszError);

/*
 *  MSPMISC.C
 */

/* length in chars of file extension including . (no NULL) */
#define CCH_EXT         4

/* length in chars of the base portion of a local name (no NULL) */
#define CCH_BASE        8

/* length in chars of a local name of a message or folder, including NULL */
#define CCH_NAME            (CCH_BASE + CCH_EXT + 1)

#define FOLDER_EXT      TEXT( ".fld" )  /* extension for folder directory names */
#define MESSAGE_EXT     TEXT( ".msg" )  /* File name extension for messages */
#define TEMP_EXT        TEXT( ".tmp" )  /* File name extension for messages */

/* Filename extension for read-receipt messages   */
#define READRECEIPT_EXT TEXT( ".rrt" )

SCODE ScAlloc(ULONG lcb, LPVOID *ppv);
SCODE ScAllocZ(ULONG lcb, LPVOID *ppv);
SCODE ScRealloc(ULONG lcb, LPVOID pvOrig, LPVOID * ppv);
void FreeNull(LPVOID pv);
SCODE LMAllocZ(PLMR plmr, ULONG lcb, LPVOID *ppv);
SCODE ScInitMSInstance(LPMALLOC lpmalloc);
void DeinitMSInstance(void);

#define HrAlloc(a,b)        ResultFromScode(ScAlloc((a), (b)))
#define HrAllocZ(a,b)       ResultFromScode(ScAllocZ((a), (b)))
#define HrRealloc(a,b,c)    ResultFromScode(ScRealloc((a), (b), (c)))

LPTSTR SzBaseName (PEID);

BOOL FCheckEIDType(PEID peid, LPSTR szExt);
BOOL FIsRoot (PEID peid);
BOOL FIsFolder (PEID peid);
BOOL FIsUnsavedMsg (PIMSG pimsg);
#define FIsMessage(_peid)       FCheckEIDType((_peid), MESSAGE_EXT)
#define FIsUnsavedEID(_peid)    FCheckEIDType((_peid), TEMP_EXT)

HRESULT HrDeconstructEID (PEID peid, LPMAPIUID * lppuid,
    LPTSTR * lppszPath, LPTSTR * lppszFile);
HRESULT HrAppendPath (LPTSTR szBase, LPTSTR szAppend, LPTSTR *
    lppszFullPath);
BOOL FAppendPathNoMem (LPTSTR szBase, LPTSTR szAppend,
    ULONG cchFullPath, LPTSTR szFullPath);
void ReplaceExt(LPTSTR szFile, LPTSTR szExt);
HRESULT HrConstructEID(LPMAPIUID lpuidStore, PLMR plmr, LPSTR szNewName,
    PEID *ppeidNew);
HRESULT HrGetParentEID (PLMR, PEID, PEID *);
HRESULT HrOpenParent(PIMS pims, PEID peid, ULONG ulFlags, PIFLD *ppifld);

void FreePropArrays (LPSPropValue *,
    LPSPropTagArray *, LPSPropAttrArray *);
HRESULT HrAllocPropArrays (ULONG, LPSPropValue *,
    LPSPropTagArray *, LPSPropAttrArray *);

HRESULT HrWrap_GetProps(HRESULT hr, PIMS pims, ULONG cvalInt,
    LPSPropValue pvalInt, ULONG * lpcValues, LPSPropValue * ppval,
    BOOL fStore, BOOL fTagsSpecified, POBJ pobj);

BOOL FIsSubmittedMessage(PIMS pims, PEID peid);

HRESULT HrOpenIMsgSession(LPMSGSESS *ppmsgsess);
HRESULT HrOpenIMsg(LPMSGSESS pmsgsess, LPSTR szFile, PLMR plmr, LPMAPISUP psup,
    BOOL fCreate, BOOL fModify, BOOL fExclusive, LPMESSAGE *lppmsg);

HRESULT HrSetOneROProp(LPMESSAGE lpmsg, PLMR plmr, ULONG ulPT, LPVOID pv);
HRESULT HrGetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv);
HRESULT HrSetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv);

BOOL FContainsProp(ULONG ulPropTag, LPSPropTagArray ptaga);

/*
 *  MSPROVIDER object.
 *  Returned by MSProviderInit() routine.
 *  One is created for each Session logged
 *  into this store provider on this process.
 */

#undef  INTERFACE
#define INTERFACE struct _MSP

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, MSP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMSPROVIDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, MSP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMSPROVIDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(MSP_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMSPROVIDER_METHODS(IMPL)
};

extern MSP_Vtbl vtblMSP;

struct _MSP
{
    MSP_Vtbl *  lpVtbl;         /* -> vtblMSP */
    STANDARD_OBJECT_MEMBERS
    CRITICAL_SECTION_MEMBERS        /* Critical section (_WIN32 only) */
    HINSTANCE       hInst;          /* Instance handle */
    LMR             lmr;            /* Linked memory routines */
    IFDBG(BOOL      fInvalid;)      /* TRUE if invalid (DEBUG only) */
};

/*
 *  LOGON object.
 *  Returned from MSP_Logon().
 *  Called by MAPI.DLL.  Equivalent to the IMSGSTORE object
 *  returned on the same call but is used by MAPI instead of the client.
 */

#undef  INTERFACE
#define INTERFACE struct _MSL

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, MSL_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMSLOGON_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, MSL_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMSLOGON_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(MSL_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMSLOGON_METHODS(IMPL)
};

extern MSL_Vtbl vtblMSL;
extern MAPIUID uidProvider;

struct _MSL
{
    MSL_Vtbl *      lpVtbl;     /* -> vtblMSL */
    STANDARD_OBJECT_MEMBERS
    CRITICAL_SECTION_MEMBERS        /* Critical section (_WIN32) */
    LMR                 lmr;        /* Linked memory routines */
    IFDBG(BOOL          fInvalid;)  /* TRUE if invalid (DEBUG only) */
};

/*
 *  MSPTBL.C
 */

HRESULT HrGetTableName(POBJ, LPSTR, LPSTR, LPSTR *);
HRESULT HrSyncOutgoingTable(LPTABLEDATA lptbl, PIMS pims);
HRESULT HrSyncContentsTable(PIFLD pifld, BOOL fWriteTable);
HRESULT HrReadTableFromDisk(LPTABLEDATA, POBJ, LPSTR, ULONG, LPSTR);
HRESULT HrWriteTableOnDisk(LPTABLEDATA, POBJ, LPSTR, LPSTR);

/*
 *  MSPNTFY.C
 */

HRESULT HrUpdateOutgoingQueue(PIMS pims, PIMSG pimsg, PEID peid,
    ULONG ulTableEvent);
HRESULT HrSetupPrivateNotifications(PIMS pims);
HRESULT HrNewOutgoingTableData(PIMS pims);
HRESULT HrCreateOGQueueMutex(HANDLE *phQMutex);

HRESULT HrSendNotif(PIMS pims, PEID peidParent, PEID peidObject,
    ULONG ulTableEvent, ULONG ulObjType);


MSPATCH.C

/*
 *  M S P A T C H . C
 *
 *  Code for the MAPI Sample Store Provider implementation of the
 *  IAttach object.  The implementation is, in reality, a thin
 *  wrapping layer around the implementation of IMessage on
 *  IStorage.  We wrap the IAttach object returned by IMsgOnIStg so
 *  that we can handle those methods (like GetLastError) not
 *  understood by a standalone attachment (e.g. one embedded in a
 *  word document) but which makes sense for a message in the
 *  context of a message store.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

#define ATCH_ValidateParameters(pobj, intf, method, arglist)        \
        OBJ_ValidateParameters(pobj, intf, method, sizeof(IATCH), &vtblIATCH, arglist)

#define IATCH_EnterCriticalSection(piatch) OBJ_EnterCriticalSection((POBJ)piatch)
#define IATCH_LeaveCriticalSection(piatch) OBJ_LeaveCriticalSection((POBJ)piatch)

/* Manifest constants */

#define cInitIATCHProps         3
#define grfInitIATCHPropAttr    (PROPATTR_MANDATORY | PROPATTR_READABLE)

/* Global variables */

/* Dispatch table for IAttach objects */
IATCH_Vtbl vtblIATCH =
{
    (IATCH_QueryInterface_METHOD *)     OBJ_QueryInterface,
    (IATCH_AddRef_METHOD *)             OBJ_AddRef,
    (IATCH_Release_METHOD *)            OBJ_Release,
    (IATCH_GetLastError_METHOD *)       IMS_GetLastError,
    IATCH_SaveChanges,
    IATCH_GetProps,
    IATCH_GetPropList,
    IATCH_OpenProperty,
    IATCH_SetProps,
    IATCH_DeleteProps,
    IATCH_CopyTo,
    IATCH_CopyProps,
    (IATCH_GetNamesFromIDs_METHOD *)    IMS_GetNamesFromIDs,
    (IATCH_GetIDsFromNames_METHOD *)    IMS_GetIDsFromNames
};

/*
 *  Object methods
 */

/*
 *  IATCH_SaveChanges
 *
 *  Purpose:
 *      Saves changes made to an attachment object and all of its
 *      sub-objects (messages, et al.).  Since the IMessage on
 *      IStorage implementation handles the invalidation of objects
 *      after SaveChanges is called, we do not need to worry about
 *      it at this level.
 *
 *  Arguments:
 *      piatch      Pointer to the object.
 *      ulFlags     Flags.  The following are defined:
 *                  KEEP_OPEN_READONLY  Do not invalidate the
 *                                      object, make it read-only.
 *                  KEEP_OPEN_READWRITE Don't invalidate the
 *                                      object, keep it open
 *                                      read/write.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IATCH_SaveChanges(PIATCH piatch, ULONG ulFlags)
{
    HRESULT hr;
    PIMSG pimsg;
    PLMR plmr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            SaveChanges,
            (piatch,
            ulFlags));

    IATCH_EnterCriticalSection(piatch);

    pimsg = (PIMSG) piatch->pobjParent;
    plmr = &piatch->pims->lmr;

    if (!OBJ_TestFlag(piatch, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    hr = piatch->lpattach->lpVtbl->SaveChanges(piatch->lpattach, ulFlags);
    if (hr != hrSuccess)
        goto exit;

    if (!(ulFlags & KEEP_OPEN_READWRITE))
        OBJ_ClearFlag(piatch, OBJF_MODIFY);

exit:
    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_SaveChanges, hr);
    return HrCheckHr(hr, IMAPIProp_SaveChanges);
}

/*
 *  IATCH_GetProps
 *
 *  Purpose:
 *      Returns to the caller the value(s) of one or more
 *      properties existent on an IATCH object.  The order of the
 *      properties in the returned lppPropArray structure exactly
 *      matches the order in which the properties were requested in
 *      lpPropTagArray.  The caller must free the returned
 *      structure by calling MAPIFreeBuffer(*lppPropArray), but
 *      only if the function returns zero or the error
 *      MAPI_W_ERRORS_RETURNED.  Uses the IMessage on IStorage
 *      property interface implementation.
 *
 *  Arguments:
 *      piatch          Pointer to the object.
 *       ptaga          Pointer to a counted array of property tags of
 *                      properties requested
 *       ulFlags        UNICODE / String8
 *       pcval          Pointer to number of values returned
 *       ppval          Pointer to a variable in which the address of the
 *                      returned property values is placed
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      If the call succeeded overall but access to one or more
 *      properties failed, the function returns the warning
 *      MAPI_W_ERRORS_RETURNED.  The calling application should
 *      then check the Property Tag of each of the returned
 *      properties to determine which ones failed.  Those that fail
 *      have their Property Type set to PT_ERROR and their value (a
 *      ULONG) indicates which error occurred.
 *
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_W_ERRORS_RETURNED      See above.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IATCH_GetProps(PIATCH piatch, LPSPropTagArray ptaga, ULONG ulFlags,
    ULONG *pcval, LPSPropValue *ppval)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            GetProps,
            (piatch,
            ptaga,
            ulFlags,
            pcval,
            ppval));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IATCH_EnterCriticalSection(piatch);

    /* Pass the call off to IMessage */
    hr = piatch->lpattach->lpVtbl->GetProps(piatch->lpattach, ptaga, ulFlags,
            pcval, ppval);

    /* Wrap specific store properties. Note that this function takes as an */
    /* argument the HRESULT from the previous GetProps call. */
    /* We aren't ignoring the error. */

    hr = HrWrap_GetProps(hr, piatch->pims, 0, NULL, pcval, ppval, FALSE,
        (ptaga != NULL), (POBJ)piatch);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_GetProps, hr);
    return HrCheckHr(hr, IMAPIProp_GetProps);
}

/*
 *  IATCH_GetPropList
 *
 *  Purpose:
 *      Returns a list of all the properties currently accessible.
 *      Uses the IMessage on IStorage property implementation.
 *
 *  Arguments:
 *      piatch              Pointer to the object.
 *      ulFlags             UNICODE / String8
 *      lppPropTagArray     Location in which to return a pointer
 *                          to a counted array of property tags.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP
IATCH_GetPropList(PIATCH piatch, ULONG ulFlags, LPSPropTagArray *lppPropTagArray)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            GetPropList,
            (piatch,
            ulFlags,
            lppPropTagArray));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IATCH_EnterCriticalSection(piatch);

    hr = piatch->lpattach->lpVtbl->GetPropList(piatch->lpattach, ulFlags,
        lppPropTagArray);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_GetPropList, hr);
    return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/*
 *  IATCH_OpenProperty
 *
 *  Purpose:
 *      Open a requested interface on a property for further
 *      access.  Commonly used for stream access to a large binary
 *      or text property.  This is the only way to access a
 *      property of type PT_OBJECT, and may be used on other
 *      properties depending on the implementation.  Uses the
 *      IMessage on IStorage property implementation.
 *
 *  Arguments:
 *      piatch      Pointer to the object.
 *      ulPropTag   Property tag for the desired property.  Only
 *                  the ID bits of the tag are used; the type bits
 *                  are ignored.
 *      lpiid       Pointer to the GUID identifying which interface
 *                  is desired.
 *      lppUnk      Location in which to return a pointer to the
 *                  newly created interface pointer.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_CALL_FAILED      An error occurred opening a
 *                              supported interface.
 *      MAPI_E_NO_SUPPORT   The requested interface is not
 *                              available on the given property.
 */
STDMETHODIMP IATCH_OpenProperty(PIATCH piatch, ULONG ulPropTag, LPCIID lpiid,
    ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN * lppUnk)
{
    HRESULT hr;
    LPUNKNOWN lpunk = NULL;
    PIMSG pimsg = NULL;
    LPSPropTagArray ptaga = NULL;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            OpenProperty,
            (piatch,
            ulPropTag,
            lpiid,
            ulInterfaceOptions,
            ulFlags,
            lppUnk));

    IATCH_EnterCriticalSection(piatch);

    /* If input parameters are okay, make OpenProperty call on lpmsg. */

    hr = piatch->lpattach->lpVtbl->OpenProperty(piatch->lpattach,
        ulPropTag, lpiid, ulInterfaceOptions, ulFlags, &lpunk);
    if (hr != hrSuccess)
        goto exit;

    /* If it's a message in message, we need to wrap the returned object */

    if (ulPropTag == PR_ATTACH_DATA_OBJ
        && IsEqualIID(lpiid, (LPIID) &IID_IMessage))
    {
        hr = NewIMSGInIATCH((LPMESSAGE) lpunk, (POBJ) piatch, ulFlags, &pimsg);
        if (hr != hrSuccess)
            goto exit;

        hr = pimsg->lpVtbl->GetPropList(pimsg, 0, /* ansi */
                &ptaga);
        if (hr != hrSuccess)
            goto exit;

        if (ptaga->cValues == 0)
        {
            hr = InitIMSGProps(pimsg);
            if (hr != hrSuccess)
                goto exit;
        }

        *lppUnk = (LPUNKNOWN) pimsg;
    }
    else
        *lppUnk = lpunk;

exit:
    LMFree(&piatch->pims->lmr, ptaga);

    if (hr != hrSuccess)
    {
        UlRelease(pimsg);
        UlRelease(lpunk);
    }

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_OpenProperty, hr);
    return HrCheckHr(hr, IMAPIProp_OpenProperty);
}

/*
 *  IATCH_SetProps
 *
 *  Purpose:
 *      Sets the value of one or more properties.  This call passes
 *      a number of Property Value structures.  The Property Tag in
 *      each indicates which property is having its values set and
 *      the value indicates what should be stored.  The caller must
 *      free the returned property problem structure by calling
 *      MAPIFreeBuffer(*pprba), but only if the call
 *      succeeded overall.  Uses the IMessage on IStorage property
 *      implementation.
 *
 *  Arguments:
 *      piatch          Pointer to the object.
 *      cValues         Number of values in pval.
 *      pval            Pointer to a Property Value array.
 *      pprba           Location in which to return a pointer to a
 *                      counted array of property problem
 *                      structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, a zero is returned.
 *      If there are problems with setting some or all of the
 *      selected values, and a non-NULL is passed for pprba,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in pprba is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *pprba are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IATCH_SetProps(PIATCH piatch, ULONG cValues, LPSPropValue pval,
    LPSPropProblemArray *pprba)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            SetProps,
            (piatch,
            cValues,
            pval,
            pprba));

    IATCH_EnterCriticalSection(piatch);

    hr = piatch->lpattach->lpVtbl->SetProps(piatch->lpattach, cValues,
        pval, pprba);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_SetProps, hr);
    return HrCheckHr(hr, IMAPIProp_SetProps);
}

/*
 *  IATCH_DeleteProps
 *
 *  Purpose:
 *      Deletes the list of properties given in ptaga.
 *      The caller must free the returned property problem
 *      structure by calling MAPIFreeBuffer(*pprba), but only
 *      if the call succeeded overall.  Uses the IMessage on
 *      IStorage property implementation.
 *
 *  Arguments:
 *      piatch          Pointer to the object.
 *      ptaga   Pointer to an array of Property Tags
 *                      identifying the properties to delete.
 *      pprba       Location in which to return a pointer to a
 *                      counted array of property problem
 *                      structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, a zero is returned.
 *      If there are problems with deleting some or all of the
 *      selected values, and a non-NULL is passed for pprba,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in pprba is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *pprba are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IATCH_DeleteProps(PIATCH piatch, LPSPropTagArray ptaga,
    LPSPropProblemArray * pprba)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            DeleteProps,
            (piatch,
            ptaga,
            pprba));

    IATCH_EnterCriticalSection(piatch);

    hr = piatch->lpattach->lpVtbl->DeleteProps(piatch->lpattach, ptaga, pprba);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_DeleteProps, hr);
    return HrCheckHr(hr, IMAPIProp_DeleteProps);
}

/*
 *  IATCH_CopyTo
 *
 *  Purpose:
 *      Copies the contents of the current object to a destination
 *      object.  The entire contents, including contained objects,
 *      are copied, or optionally the caller can provide a list of
 *      properties that are not to be copied.  Previous information
 *      in the destination object which is not overwritten by
 *      copied data is neither deleted nor modified.
 *
 *  Arguments:
 *      piatch          Pointer to the source object.
 *      ciidExcl        Count of the excluded interfaces in
 *                      rgiidExcl.
 *      rgiidExcl   Array of interface IDs specifying
 *                      interfaces not to be attempted in trying to
 *                      copy supplemental information to the
 *                      destination object.
 *      ptagaExcl   Counted array of property tags of
 *                      properties that are not to be copied to the
 *                      destination object.  NULL indicates all
 *                      properties are to be copied.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst     Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *                      MAPI_STD_DIALOG Use MAPI standard dialog
 *                                      instead of
 *                                      provider-specific dialog.
 *      pprba       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IATCH_CopyTo(PIATCH piatch, ULONG ciidExcl, LPCIID rgiidExcl,
    LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS
    lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray * pprba)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            CopyTo,
            (piatch,
            ciidExcl,
            rgiidExcl,
            ptagaExcl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            pprba));

    IATCH_EnterCriticalSection(piatch);

    hr = piatch->lpattach->lpVtbl->CopyTo(piatch->lpattach, ciidExcl,
        rgiidExcl, ptagaExcl, ulUIParam, lpProgress, piidDst,
        lpDestObj, ulFlags, pprba);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_CopyTo, hr);
    return HrCheckHr(hr, IMAPIProp_CopyTo);
}

/*
 *  IATCH_CopyProps
 *
 *  Purpose:
 *      Copies the specified properties of the current object to a destination
 *      object.
 *
 *  Arguments:
 *      piatch          Pointer to the source object.
 *      ptagaIncl       Counted array of property tags of
 *                      properties that are to be copied to the
 *                      destination object.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst     Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *                      MAPI_DECLINE_OK
 *      pprba       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IATCH_CopyProps(PIATCH piatch,
    LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS
    lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray * pprba)
{
    HRESULT hr;

    ATCH_ValidateParameters(
            piatch,
            IMAPIProp,
            CopyProps,
            (piatch,
            ptagaIncl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            pprba));

    IATCH_EnterCriticalSection(piatch);

    hr = piatch->lpattach->lpVtbl->CopyProps(piatch->lpattach,
        ptagaIncl, ulUIParam, lpProgress, piidDst,
        lpDestObj, ulFlags, pprba);

    IATCH_LeaveCriticalSection(piatch);

    DebugTraceResult(IATCH_CopyProps, hr);
    return HrCheckHr(hr, IMAPIProp_CopyProps);
}

/*
 *  External functions
 */

/*
 * IATCH_Neuter
 *
 * Purpose
 *  free storage for an attachment
 *
 * Parameter
 *      piatch      pointer to the open attachment
 *
 */
void IATCH_Neuter(PIATCH piatch)
{
    UlRelease(piatch->lpattach);
}

/*
 *  HrNewIATCH
 *
 *  Purpose:
 *      Allocates and initializes an IATCH object (internal
 *      implementation of IAttach).  This is just a thin wrapper
 *      around the attachment object of the IMessage on IStorage
 *      implementation -- we wrap it to catch certain methods,
 *      see below.
 *
 *  Arguments:
 *      lpattach    Pointer to an attachment object returned by
 *                  IMessage on IStorage.
 *      pimsg       Pointer to the attachment's parent message object.
 *      fModify     TRUE if the caller wants the attach opened for writing.
 *      ppiatch Location in which to return a pointer to the
 *                  newly created IATCH instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the IATCH instance.
 */
HRESULT HrNewIATCH(LPATTACH lpattach, PIMSG pimsg, BOOL fModify, PIATCH * ppiatch)
{
    HRESULT hr = hrSuccess;
    PIATCH piatchNew = NULL;
    SCODE sc;
    PIMS pims;

    AssertSz(lpattach, "Bad lpattach");
    AssertSz(pimsg, "Bad pimsg");
    AssertSz(ppiatch, "Bad ppiatch");

    pims = pimsg->pims;

    /* Allocate and initialize IATCH instance */

    sc = LMAllocZ(&pims->lmr, sizeof(IATCH), &piatchNew);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    /* Initialize member variables */

    OBJ_Initialize(piatchNew, &vtblIATCH, OT_ATTACH, pims, pims->pcs);

    UlAddRef(lpattach); /* we're keeping a reference */

    piatchNew->lpattach = lpattach;

    if (fModify)
        OBJ_SetFlag(piatchNew, OBJF_MODIFY);

    OBJ_Enqueue((POBJ) piatchNew, (POBJ) pimsg);

    *ppiatch = piatchNew;

exit:
    if (hr != hrSuccess)
        LMFree(&pims->lmr, piatchNew);

    DebugTraceResult(HrNewIATCH, hr);
    return hr;
}


MSPFLD.C

/*
 *  M S P F L D . C
 *
 *  Code that implements the object methods of IMAPIFolder.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

#define FLD_ValidateParameters(pobj, intf, method, arglist)     \
        OBJ_ValidateParameters(pobj, intf, method, sizeof(IFLD), &vtblIFLD, arglist)

static BOOL IFLD_IsInvalid(PIFLD pifld);
static HRESULT HrCreateContTblMutex(HANDLE *phCTMutex);
static BOOL FFoldInSameStore(PIFLD pifld, PIFLD lpDestFld);
static BOOL FIsAncestor(PIFLD pifldAncestor, PIFLD pifldDescendent);
static HRESULT HrGetSortOrder(PIFLD pifld, LPSSortOrderSet *lppsSortOrder);
static HRESULT HrFillHierarchyTable(PIFLD pifld, LPTABLEDATA lptbl);
static HRESULT HrIsRead(PIFLD pifld, PEID peidMsg, BOOL *pfRead);
static HRESULT HrCreateFolder(PIFLD pifldParent, LPSTR szName, LPSTR szComment,
    BOOL fOpenIfExists, PIFLD *ppifldNew, BOOL *pfCreated);
static HRESULT HrDeleteSubDirectory(PIFLD pifldParent, PEID peidToDelete,
    ULONG ulFlags, BOOL fContentsOnly);
static HRESULT HrCopyContents(PIFLD pifldSrc, PIFLD pifldDst, ULONG ulFlags,
    LPSPropTagArray ptagaExcl);
static BOOL FFolderExists(PEID peid, PIMS pims);
static HRESULT HrSetSubFolderProp(PIFLD pifld, BOOL fSubFolder);
static HRESULT HrSetOneROFolderProp(PIFLD pifld, LONG lValue, ULONG ulPT);
static HRESULT HrEIDFromDisplayName(LPMAPITABLE pmt, LPSTR szName, PLMR plmr,
    PEID *ppeid);
static HRESULT HrCreateMessageList(PIFLD pifld, LPENTRYLIST *lppEntryList);
static void DestroyMessageList(PLMR plmr, LPENTRYLIST *lppEntryList);

#define NUM_RW_FOLDER_PROPS      2  /* number read write initial folder props */

#define IFLD_EnterCriticalSection(pifld)    OBJ_EnterCriticalSection((POBJ)pifld)
#define IFLD_LeaveCriticalSection(pifld)    OBJ_LeaveCriticalSection((POBJ)pifld)

CALLERRELEASE ViewRelease;

TCHAR szSubfolderPropFile[] = TEXT("subfoldr.prp");
TCHAR szAllFilesTemplate[] = TEXT("*.*");

/* properties excluded on a copy of folders' .prp file */
const static SizedSPropTagArray(13, sptaExclFldProps) =
{
    13,
    {
        PR_CONTENT_COUNT,
        PR_CONTENT_UNREAD,
        PR_ENTRYID,
        PR_INSTANCE_KEY,
        PR_PARENT_ENTRYID,
        PR_OBJECT_TYPE,
        PR_STORE_ENTRYID,
        PR_STORE_RECORD_KEY,
        PR_RECORD_KEY,
        PR_FOLDER_TYPE,
        PR_SUBFOLDERS,
        PR_MESSAGE_RECIPIENTS,
        PR_MESSAGE_ATTACHMENTS
    }
};

/* read only property proptags */
/* order must match order initialized on CreateFolderStorage */
#define NUM_RO_FOLDER_PROPS     11  /* number read only initial folder props */
const static SizedSPropTagArray(NUM_RO_FOLDER_PROPS, sptaReadOnly) =
{
    NUM_RO_FOLDER_PROPS,
    {
        PR_OBJECT_TYPE,
        PR_RECORD_KEY,
        PR_FOLDER_TYPE,
        PR_CONTENT_COUNT,
        PR_CONTENT_UNREAD,
        PR_STORE_ENTRYID,
        PR_STORE_RECORD_KEY,
        PR_SUBFOLDERS,
        PR_ENTRYID,
        PR_PARENT_ENTRYID,
        PR_INSTANCE_KEY
    }
};

/* read only property attributes */
const static SizedSPropAttrArray(NUM_RO_FOLDER_PROPS, spaReadOnly) =
{
    NUM_RO_FOLDER_PROPS,
    {
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE,
        PROPATTR_READABLE
    }
};

/* initial sort order set for a contents table */
static SizedSSortOrderSet(1, sSortOrderContentsDefault) =
{
    1,
    0,
    0,
    {
        PR_INSTANCE_KEY,                /* the index column */
        TABLE_SORT_ASCEND
    }
};

/* message status property array */
static SPropTagArray sptaMessageStatus =
{
    1,
    {
        PR_MESSAGE_FLAGS
    }
};

/* DISPATCH TABLE */
IFLD_Vtbl vtblIFLD =
{
    (IFLD_QueryInterface_METHOD *)  OBJ_QueryInterface,
    (IFLD_AddRef_METHOD *)          OBJ_AddRef,
    IFLD_Release,
    (IFLD_GetLastError_METHOD *)    IMS_GetLastError,
    (IFLD_SaveChanges_METHOD *)     IMS_SaveChanges,
    IFLD_GetProps,
    IFLD_GetPropList,
    IFLD_OpenProperty,
    IFLD_SetProps,
    IFLD_DeleteProps,
    IFLD_CopyTo,
    IFLD_CopyProps,
    (IFLD_GetNamesFromIDs_METHOD *) IMS_GetNamesFromIDs,
    (IFLD_GetIDsFromNames_METHOD *) IMS_GetIDsFromNames,
    IFLD_GetContentsTable,
    IFLD_GetHierarchyTable,
    (IFLD_OpenEntry_METHOD *)       IMS_OpenEntry,
    IFLD_SetSearchCriteria,
    IFLD_GetSearchCriteria,
    IFLD_CreateMessage,
    IFLD_CopyMessages,
    IFLD_DeleteMessages,
    IFLD_CreateFolder,
    IFLD_CopyFolder,
    IFLD_DeleteFolder,
    IFLD_SetReadFlags,
    IFLD_GetMessageStatus,
    IFLD_SetMessageStatus,
    IFLD_SaveContentsSort,
    IFLD_EmptyFolder
};

/****************************************************
*           Methods on IMAPIFolder                  *
*****************************************************/

/****************************************************************
    Folder properties are all implemented by creating a file
    (FOLDER.PRP) inside each folder and using message properties
    on it.
*************************************************************/

/***************************************************************************
 -  IFLD_Release
 -
 *  Purpose:
 *      Given a valid folder object, decrements the reference count, and if
 *      zero, NULLS out the lpVtbl. If no child objects of the folder remain
 *      open, and no contents or hierarchy tables remain open, the routine
 *      also destroys the object (see OBJ_Destroy for details of that action).
 *
 *      This release method is very similar to the standard one found in
 *      mspobj.c. The only difference is that this one also checks for any
 *      open contents or hierarchy tables, and doesn't actually destroy
 *      the folder until they are gone. See ViewRelease() for the other side
 *      of this implementation.
 *
 *  Parameters
 *       pifld      The folder object to release
 *
 *  Returns:
 *       ULONG: The value of the reference count on the whole object. When
 *              fully released, it is zero. This information can be used for
 *              diagnostic and testing purposes only. It CANNOT be used by
 *              shipping code. See the OLE 2.0 programmers reference for more
 *              details. Note that this function will also return zero when
 *              the identity of the object has been destroyed. Therefore,
 *              clients may not rely on zero having any special significance
 *              other than for debugging purposes.
 */
STDMETHODIMP_(ULONG) IFLD_Release(PIFLD pifld)
{
    LONG cRef;
    POBJ pobj;

    if (IFLD_IsInvalid(pifld))
    {
        TraceSz1("SampleMS: IFLD_Release(pifld=%08lX): Folder is invalid and is "
            "being ignored", pifld);
        return (0);
    }

    IFLD_EnterCriticalSection(pifld);

    pobj = (POBJ) pifld;

    AssertSz(pobj->cRef > 0, "IFLD_Release(): Too many releases");

    cRef = --pobj->cRef;

    if (cRef == 0)
    {
        pobj->lpVtbl = 0;   /* should prevent being called again. */

        if (    pobj->pobjHead == 0
            &&  pifld->lptblContents == NULL
            &&  pifld->lptblHierarchy == NULL)
        {
            OBJ_Destroy(pobj);  /* will leave the critical section */
            return (0);
        }
    }

    IFLD_LeaveCriticalSection(pifld);

    return (cRef);
}

/***************************************************************************
 -  IFLD_GetProps
 -
 *  Purpose:
 *      Returns in pcval and ppval the values of the properties
 *      in ptaga.  If the latter is NULL, all properties in the folder
 *      are returned.
 *
 *  Parameters
 *       pifld      The folder object whose properties are requested
 *       ptaga      Pointer to a counted array of property tags of
 *                  properties requested
 *       ulFlags    UNICODE / String8
 *       pcval      Pointer to number of values returned
 *       ppval      Pointer to a variable in which the address of the
 *                  returned property values is placed
 *
 *  Returns:
 *       HRESULT
 *
 */
STDMETHODIMP IFLD_GetProps(PIFLD pifld, LPSPropTagArray ptaga, ULONG ulFlags,
    ULONG *pcval, LPSPropValue *ppval)
{
    LPMESSAGE lpmsg = NULL;     /* open property message */
    HRESULT hr = hrSuccess;

    FLD_ValidateParameters(
            pifld,
            IMAPIProp,
            GetProps,
            (pifld,
            ptaga,
            ulFlags,
            pcval,
            ppval));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IFLD_EnterCriticalSection(pifld);

    /* open the property message */
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    /* Pass the call off to IMessage */
    hr = lpmsg->lpVtbl->GetProps(lpmsg, ptaga, ulFlags, pcval, ppval);

    UlRelease(lpmsg);

    {if(HR_SUCCEEDED(hr))
    {
        LPSPropValue pvalStoreSupMask = PpropFindProp(*ppval, *pcval,
                    PROP_TAG(PT_UNSPECIFIED, PROP_ID(PR_STORE_SUPPORT_MASK)));
        if(pvalStoreSupMask)
        {
            pvalStoreSupMask->ulPropTag = PR_STORE_SUPPORT_MASK;
            pvalStoreSupMask->Value.l = SMS_SUPPORTMASK;

            /* fix up hr */
            if(ptaga->cValues == 1)
                hr = hrSuccess;
        }
    }
    }

    /* Wrap internal properties. Note that this function takes as an */
    /* argument the HRESULT from the previous IMessage::GetProps call. */
    /* We aren't ignoring the error. */

    hr = HrWrap_GetProps(hr, pifld->pims, pifld->cval, pifld->pval, pcval,
        ppval, FALSE, (ptaga != NULL), (POBJ)pifld);

exit:
    IFLD_LeaveCriticalSection(pifld);

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
        DebugTraceResult(IFLD_GetProps, hr);
    #endif

    return HrCheckHr(hr, IMAPIProp_GetProps);
}

/***************************************************************************
 -  IFLD_GetPropList
 -
 *  Purpose:
 *      Returns in ptaga the list of all currently accessible properties
 *      of pifld.
 *
 *  Parameters
 *       pifld          The folder object whose properties are requested
 *       ulFlags        UNICODE / String8
 *       pptaga         Pointer to a counted array of property tags of
 *                      properties requested
 *  Returns:
 *       hr
 *
 */
STDMETHODIMP IFLD_GetPropList(PIFLD pifld, ULONG ulFlags, LPSPropTagArray *pptaga)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL;

    FLD_ValidateParameters(
            pifld,
            IMAPIProp,
            GetPropList,
            (pifld,
            ulFlags,
            pptaga));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IFLD_EnterCriticalSection(pifld);

    /* open the property message */
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
            &lpmsg);

    /* If the property message was successfully opened, then pass the */
    /* call off to IMessage */

    if (hr == hrSuccess)
        hr = lpmsg->lpVtbl->GetPropList(lpmsg, ulFlags, pptaga);

    UlRelease(lpmsg);

    IFLD_LeaveCriticalSection(pifld);
    DebugTraceResult(IFLD_GetPropList, hr);
    return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/***************************************************************************
 -  IFLD_OpenProperty
 -
 *  Purpose:
 *      Returns in *lppUnk a pointer to a newly created interface for the
 *                  propery of pifld in ulPropTag
 *      Not supported on folders.
 *
 *  Parameters
 *       pifld          the folder object whose property interface is requested
 *       ulPropTag          Property tag for the desired property--only ID is used.
 *       lpiid              Pointer to the GUID for the interface
 *       ulInterfaceOptions Specifies interface-specific behavior
 *       ulFlags            MAPI_CREATE, MAPI_MODIFY, MAPI_DEFERRED_ERRORS
 *       lppUnk             Pointer to the newly created interface pointer
 *
 *  Returns:
 *       hr
 *
 */
STDMETHODIMP IFLD_OpenProperty(PIFLD pifld, ULONG ulPropTag, LPCIID lpiid,
    ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN *lppUnk)
{
    SCODE sc;

    FLD_ValidateParameters(
            pifld,
            IMAPIProp,
            OpenProperty,
            (pifld,
            ulPropTag,
            lpiid,
            ulInterfaceOptions,
            ulFlags,
            lppUnk));

    sc = MAPI_E_NO_SUPPORT;

    DebugTraceSc(IFLD_OpenProperty, sc);
    return ResultFromScode(sc);
}

/***************************************************************************
 -  IFLD_SetProps
 -
 *  Purpose:
 *      Sets the properties in pval for pifld.
 *      lppProblems is a pointer to a structure of problems,
 *      NULL If there weren't any.
 *
 *  Parameters
 *       pifld          the folder object whose properties are to be set
 *       cValues            count of properties to be set
 *       pval               Pointer to a an array of property value structures.
 *       ppErr      Pointer to address of a property problem structure
 *                              to be returned.
 *
 *  Returns:
 *       hr         PROP_E_SECURITY_VIOLATION, PROP_E_CALL_FAILED,
 *                          PROP_E_CALL_FAILED
 *
 */
STDMETHODIMP IFLD_SetProps(PIFLD pifld, ULONG cValues, LPSPropValue pval,
    LPSPropProblemArray *ppErr)
{
    LPMESSAGE lpmsgNew = NULL;      /* new instance of property message */
    HRESULT hr;

    FLD_ValidateParameters(
            pifld,
            IMAPIProp,
            SetProps,
            (pifld,
            cValues,
            pval,
            ppErr));

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* open the property message */
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
        TRUE, &lpmsgNew);
    if (hr != hrSuccess)
        goto exit;

    /* set props on the property message */
    hr = lpmsgNew->lpVtbl->SetProps(lpmsgNew, cValues, pval, ppErr);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsgNew->lpVtbl->SaveChanges(lpmsgNew, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    /* Must release the message before trying to update the hierarchy */
    /* table. Multiple opens on the internal message are not allowed by */
    /* IStorage. */

    UlRelease(lpmsgNew);
    lpmsgNew = NULL;

    /* If this isn't the root folder, get the parent entryid, and call */
    /* ChangeTable so that any new properties are updated in its */
    /* hierarchy table row. */

    if (FIsRoot(pifld->peid) == FALSE)
    {
        PEID peidParent = NULL;
        PIMS pims = pifld->pims;

        hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
        if (hr == hrSuccess)
        {
            ChangeTable(pims, peidParent, pifld->peid, MAPI_FOLDER,
                TABLE_ROW_MODIFIED, TRUE);
            LMFree(&pims->lmr, peidParent);
        }
        else
        {
            TraceSz1("Sample MS: IFLD_SetProps: failed to change hierarchy "
                "table. sc == %s\n", SzDecodeScode(GetScode(hr)));
            hr = hrSuccess;
        }
    }

exit:
    UlRelease(lpmsgNew);

    IFLD_LeaveCriticalSection(pifld);
    DebugTraceResult(IFLD_SetProps, hr);
    return HrCheckHr(hr, IMAPIProp_SetProps);
}

/***************************************************************************
 -  IFLD_DeleteProps
 -
 *  Purpose:
 *      Deletes the properties in ptaga for pifld.  ppErr
 *      is a pointer to a structure of problems, NULL If there weren't any.
 *
 *  Parameters
 *       pifld          the folder object whose properties are to be deleted
 *       ptaga      Pointer to a counted array of property tags of the
 *                          properties to be deleted.  Must not be NULL.
 *       ppErr      Pointer to address of a property problem structure
 *                          to be returned.
 *  Returns:
 *       hr         PROP_E_SECURITY_VIOLATION, PROP_E_CALL_FAILED,
 *                          PROP_E_CALL_FAILED
 */
STDMETHODIMP IFLD_DeleteProps(PIFLD pifld, LPSPropTagArray ptaga,
    LPSPropProblemArray *ppErr)
{
    LPMESSAGE lpmsgNew = NULL;  /* new instance of property message */
    HRESULT hr = hrSuccess;

    FLD_ValidateParameters(
            pifld,
            IMAPIProp,
            DeleteProps,
            (pifld,
            ptaga,
            ppErr));

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* open the property message */
    hr = HrOpenPropertyMessageRetry(pifld->peid,
        pifld->pims, TRUE, &lpmsgNew);
    if (hr != hrSuccess)
        goto exit;

    /* Pass the call off to IMessage */
    hr = lpmsgNew->lpVtbl->DeleteProps(lpmsgNew, ptaga, ppErr);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsgNew->lpVtbl->SaveChanges(lpmsgNew, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    /* Must release the message before trying to update the hierarchy */
    /* table. Multiple opens on the internal message are not allowed by */
    /* IStorage. */

    UlRelease(lpmsgNew);
    lpmsgNew = NULL;

    /* If this isn't the root folder, get the parent entryid, and call */
    /* ChangeTable so that any new properties are updated in its */
    /* hierarchy table row. */

    if (FIsRoot(pifld->peid) == FALSE)
    {
        PEID peidParent = NULL;
        PIMS pims = pifld->pims;

        hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
        if (hr == hrSuccess)
        {
            ChangeTable(pims, peidParent, pifld->peid, MAPI_FOLDER,
                TABLE_ROW_MODIFIED, TRUE);
            LMFree(&pims->lmr, peidParent);
        }
        else
        {
            TraceSz1("Sample MS: IFLD_DeleteProps: failed to change hierarchy "
                "table. sc == %s\n", SzDecodeScode(GetScode(hr)));
            hr = hrSuccess;
        }
    }

exit:
    UlRelease(lpmsgNew);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_DeleteProps, hr);
    return HrCheckHr(hr, IMAPIProp_DeleteProps);
}

/***************************************************************************
 -  IFLD_CopyTo
 -
 *  Purpose:
 *      Copy the properties and/or contents of the current object
 *          to a destination object
 *
 *  Parameters
 *       pifldSrc       current object
 *       ciidExcl       Count of excluded interfaces in rgiidExcl
 *       rgiidExcl      Array of iid's specifying excluded interfaces
 *       ptagaExcl      Pointer to a counted array of property tags of
 *                      properties not to be copied, can be NULL
 *       ulUIParam      Handle of parent window cast to ULONG, NULL if
 *                      no dialog requested
 *       piidDst        Interface ID of the interface of lpDestObj
 *       lpDestObj      destination object
 *       ulFlags        MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG, MAPI_DECLINE_OK
 *       pprba          Pointer to address of a property problem
 *                      structure to be returned.
 *
 *  Returns:
 *       hr
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_CopyTo(PIFLD pifldSrc, ULONG ciidExcl, LPCIID rgiidExcl,
    LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
    LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlagsIn,
    LPSPropProblemArray *pprba)
{
    HRESULT hr = hrSuccess;
    BOOL fIsIMapiProp = TRUE;   /* TRUE if are to copy as imapiprop */
    BOOL fIsIMAPIFolder = TRUE;     /* TRUE if are to copy as IMAPIFolder */
    SCODE sc = S_OK;
    LPCIID piid;
    LPCIID piidMax;
    ULONG ulFlags;

    FLD_ValidateParameters(
            pifldSrc,
            IMAPIProp,
            CopyTo,
            (pifldSrc,
            ciidExcl,
            rgiidExcl,
            ptagaExcl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlagsIn,
            pprba));

    IFLD_EnterCriticalSection(pifldSrc);

    /* Turn off MAPI_DIALOG flag, since we don't support it. */
    ulFlags = (ulFlagsIn & ~MAPI_DIALOG);

    piid = rgiidExcl;
    piidMax = piid + ciidExcl;

    /* find the interface to copy : IMAPIPROP or IMAPIFOLDER */
    while (piid < piidMax)
    {
        if (IsEqualIID(piid, &IID_IMAPIFolder))
            fIsIMAPIFolder = FALSE;
        else if (IsEqualIID(piid, (LPIID) &IID_IMAPIProp))
            fIsIMapiProp = FALSE;
        piid++;
    }

    /* make sure that the destination can support this */
    if (fIsIMAPIFolder && !IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder))
    {
        LPMAPIFOLDER pfld = NULL;

        hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
            (LPIID) &IID_IMAPIFolder, (LPVOID *) &pfld);
        if (hr != hrSuccess)
        {
            Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
            goto exit;
        }
        UlRelease(pfld);
    }
    else if (fIsIMapiProp &&
            !(IsEqualIID(piidDst, (LPIID) &IID_IMAPIProp) ||
            IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder)))
    {
        LPMAPIPROP pmp = NULL;

        hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
            (LPIID) &IID_IMAPIProp, (LPVOID *) &pmp);
        if (hr != hrSuccess)
        {
            Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
            goto exit;
        }
        UlRelease(pmp);
    }

    if (!FFoldInSameStore(pifldSrc, (PIFLD) lpDestObj))
    {
        LPMAPISUP psup = pifldSrc->pims->psup;

        /* leave critical section before calling back to MAPI */
        IFLD_LeaveCriticalSection(pifldSrc);

        /* call MAPI's CopyProps */
        hr = psup->lpVtbl->DoCopyTo(psup, (LPIID) &IID_IMAPIFolder, (LPVOID) pifldSrc,
            ciidExcl, rgiidExcl, ptagaExcl, ulUIParam,
            lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

        DebugTraceResult(IFLD_CopyTo, hr);
        return (HrCheckHr(hr, IMAPIProp_CopyTo));
    }

    /* copy them as folders */
    if (fIsIMAPIFolder)
    {
        PIFLD pifldDst = (PIFLD) lpDestObj;

        /* make sure we have write access */

        if (!OBJ_TestFlag(pifldDst, OBJF_MODIFY))
        {
            hr = ResultFromScode(MAPI_E_NO_ACCESS);
            goto exit;
        }

        if (!FContainsProp(PR_CONTAINER_HIERARCHY, ptagaExcl))
        {
            /* can not move or copy a folder to a decendant or itself */
            if (FIsAncestor(pifldSrc, pifldDst))
                hr = ResultFromScode(MAPI_E_NO_ACCESS);
        }
        else if (ulFlags & MAPI_MOVE)
        {
            /* Can't set MAPI_MOVE and also exclude subfolders. */
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
        }

        if (hr != hrSuccess)
            goto exit;

        if (!(ulFlags & MAPI_NOREPLACE))
        {
            /* Empty the destination folder before beginning. */
            hr = pifldDst->lpVtbl->EmptyFolder(pifldDst, 0, NULL, 0);
            if (hr != hrSuccess)
                goto exit;
        }

        /* copy the contents of the source folder to the destination folder */
        hr = HrCopyContents(pifldSrc, pifldDst, ulFlags, ptagaExcl);
        if (hr != hrSuccess)
            goto exit;
    }

    /* copy the properties */
    if (fIsIMapiProp || fIsIMAPIFolder)
    {
        LPMESSAGE lpmsg;
        BOOL fModify = FALSE;

        if (ulFlags & MAPI_MOVE)
            fModify = TRUE;

        hr = HrOpenPropertyMessageRetry(pifldSrc->peid,
            pifldSrc->pims, fModify, &lpmsg);
        if (hr != hrSuccess)
            goto exit;

        hr = lpmsg->lpVtbl->CopyTo(lpmsg, ciidExcl,
            rgiidExcl, ptagaExcl, ulUIParam, lpProgress,
            (LPIID) &IID_IMAPIProp, lpDestObj, ulFlags, pprba);

        if (hr == hrSuccess && fModify)
            lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

        UlRelease(lpmsg);
    }

exit:
    IFLD_LeaveCriticalSection(pifldSrc);

    DebugTraceResult(IFLD_CopyTo, hr);
    return HrCheckHr(hr, IMAPIProp_CopyTo);
}

/***************************************************************************
 -  IFLD_CopyProps
 -
 *  Purpose:
 *      Copy the properties and/or contents of the current object
 *          to a destination object
 *
 *  Parameters
 *       pifldSrc       current object
 *       ptagaIncl      Pointer to a counted array of property tags of
 *                      properties to be copied
 *       ulUIParam      Handle of parent window cast to ULONG, NULL if
 *                      no dialog requested
 *       piidDst        Interface ID of the interface of lpDestObj
 *       lpDestObj      destination object
 *       ulFlags        MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG, MAPI_DECLINE_OK
 *       pprba          Pointer to address of a property problem
 *                      structure to be returned.
 *
 *  Returns:
 *       hr
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_CopyProps(PIFLD pifldSrc,
    LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
    LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlagsIn,
    LPSPropProblemArray *pprba)
{
    HRESULT hr = hrSuccess;

#ifdef WHEN_WORKING
    PIFLD pifldDst = NULL;      /* lpDestObj if it's a folder */
    BOOL fIsIMAPIFolder;        /* TRUE if are to copy as IMAPIFolder */
    SCODE sc = S_OK;
    LPIID piid;
    LPIID piidMax;
    ULONG ulFlags;
#endif /* WHEN_WORKING */

    FLD_ValidateParameters(
            pifldSrc,
            IMAPIProp,
            CopyProps,
            (pifldSrc,
            ptagaIncl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlagsIn,
            pprba));

    IFLD_EnterCriticalSection(pifldSrc);

#ifdef WHEN_WORKING
    /* Turn off MAPI_DIALOG flag, since we don't support it. */
    ulFlags = (ulFlagsIn & ~MAPI_DIALOG);

    fIsIMAPIFolder =   FContainsProp(PR_CONTAINER_HIERARCHY, ptagaIncl)
                    || FContainsProp(PR_CONTAINER_CONTENTS, ptagaIncl);
    /* make sure that the destination can support this */
    if (fIsIMAPIFolder && !IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder))
    {
        LPMAPIFOLDER pfld = NULL;

        hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
            (LPIID) &IID_IMAPIFolder, (LPVOID *) &pfld);
        if (hr != hrSuccess)
        {
            Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
            goto exit;
        }
        UlRelease(pfld);
    }
    else if (!(IsEqualIID(piidDst, (LPIID) &IID_IMAPIProp) ||
            IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder)))
    {
        LPMAPIPROP pmp = NULL;

        hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
            (LPIID) &IID_IMAPIProp, (LPVOID *) &pmp);
        if (hr != hrSuccess)
        {
            Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
            goto exit;
        }
        UlRelease(pmp);
    }

    if (!FFoldInSameStore(pifldSrc, (PIFLD) lpDestObj))
    {
        LPMAPISUP psup = pifldSrc->pims->psup;

        /* leave critical section before calling back to MAPI */
        IFLD_LeaveCriticalSection(pifldSrc);

        /* call MAPI's CopyProps */
        hr = psup->lpVtbl->DoCopyProps(psup, (LPIID) &IID_IMAPIFolder,
            (LPVOID) pifldSrc, ptagaIncl, ulUIParam,
            lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

        DebugTraceResult(IFLD_CopyProps, hr);
        return (HrCheckHr(hr, IMAPIProp_CopyProps));
    }

    /* copy them as folders */
    if (fIsIMAPIFolder)
    {
        PIFLD pifldDst = (PIFLD) lpDestObj;

        /* make sure we have write access */

        if (!OBJ_TestFlag(pifldDst, OBJF_MODIFY))
        {
            hr = ResultFromScode(MAPI_E_NO_ACCESS);
            goto exit;
        }

        if (FContainsProp(PR_CONTAINER_HIERARCHY, ptagaIncl))
        {
            /* can not move or copy a folder to a decendant or itself */

if (FIsAncestor(pifldSrc, pifldDst))
                hr = ResultFromScode(MAPI_E_NO_ACCESS);
        }
        else if (ulFlags & MAPI_MOVE)
        {
            /* Can't set MAPI_MOVE and also exclude subfolders. */
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
        }

        if (hr != hrSuccess)
            goto exit;

        if (!(ulFlags & MAPI_NOREPLACE))
        {
            /* Empty the destination folder before beginning. */
            hr = pifldDst->lpVtbl->EmptyFolder(pifldDst, 0, NULL, 0);
            if (hr != hrSuccess)
                goto exit;
        }

        if (FContainsProp(PR_CONTAINER_CONTENTS, ptagaIncl))
        {
            /* copy the contents of the source folder to the destination folder */
            hr = HrCopyContents(pifldSrc, pifldDst, ulFlags, NULL);
        }
        if (hr != hrSuccess)
            goto exit;
    }

    /* copy the properties */
    /* //$ Rebuild ptagaIncl without Contents and Hierarchy */
    {
        LPMESSAGE lpmsg;
        BOOL fModify = FALSE;

        if (ulFlags & MAPI_MOVE)
            fModify = TRUE;

        hr = HrOpenPropertyMessageRetry(pifldSrc->peid,
            pifldSrc->pims, fModify, &lpmsg);
        if (hr != hrSuccess)
            goto exit;

        hr = lpmsg->lpVtbl->CopyProps(lpmsg,
            ptagaIncl, ulUIParam, lpProgress,
            (LPIID) &IID_IMAPIProp, lpDestObj, ulFlags, pprba);

        if (hr == hrSuccess && fModify)
            lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

        UlRelease(lpmsg);
    }

exit:
    IFLD_LeaveCriticalSection(pifldSrc);

#else

    IFLD_LeaveCriticalSection(pifldSrc);

    /* call MAPI's CopyProps */
    hr = pifldSrc->pims->psup->lpVtbl->DoCopyProps(pifldSrc->pims->psup,
        (LPIID) &IID_IMAPIFolder, (LPVOID) pifldSrc, ptagaIncl, ulUIParam,
        lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

#endif  /* WHEN_WORKING */

    DebugTraceResult(IFLD_CopyProps, hr);
    return HrCheckHr(hr, IMAPIProp_CopyProps);
}

/***************************************************************************
 -  IFLD_GetContentsTable
 -
 *  Purpose:
 *      Returns a table with the summary information about the messages
 *      in the folder
 *
 *  Parameters
 *       pifld  the folder object
 *       ulFlags    MAPI_DEFERRED_ERRORS, MAPI_ASSOCIATED
 *       lppTable   location to place the table object
 *
 *  Returns:
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_GetContentsTable(PIFLD pifld, ULONG ulFlags,
    LPMAPITABLE *lppTable)
{
    HRESULT hr = hrSuccess;
    SCODE sc;
    HANDLE hFindFile = FAILED_SEARCH;
    LPTSTR szMessageTemplate = NULL;    /* template for a message name */
    LPSSortOrderSet lpsSortOrder = NULL;
    BOOL fTableCreated = FALSE;
    BOOL fInMutex = FALSE;
    PIMS pims;

    FLD_ValidateParameters(
            pifld,
            IMAPIContainer,
            GetContentsTable,
            (pifld,
            ulFlags,
            lppTable));

    #ifdef VALIDATE
    if (ulFlags & MAPI_ASSOCIATED)
        return ResultFromScode(MAPI_E_NO_SUPPORT);

    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IFLD_EnterCriticalSection(pifld);

    pims = pifld->pims;

    /* If the table mutex doesn't yet exist on this process, create it. */

    if (pims->hContTblMutex == NULL)
    {
        hr = HrCreateContTblMutex(&pims->hContTblMutex);
        if (hr != hrSuccess)
            goto exit;
    }

    if (pifld->lptblContents == NULL)
    {
        PINST pinst = (PINST) PvGetInstanceGlobals();
        PLMR plmr = &pifld->pims->lmr;

        if (pinst == NULL)
        {
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            goto exit;
        }

        /* create the data for the table */
        sc = CreateTable((LPIID) &IID_IMAPITableData,
            plmr->lpAllocBuf, plmr->lpAllocMore,
            plmr->lpFreeBuf, pinst->lpmalloc, TBLTYPE_DYNAMIC,
            PR_INSTANCE_KEY, (LPSPropTagArray) &sPropTagsContents,
            &(pifld->lptblContents));

        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }
        fTableCreated = TRUE;

        /* Get the table mutex so that we can access the file without */
        /* crossing paths with another process. */

        WaitForSingleObject(pims->hContTblMutex, INFINITE);
        fInMutex = TRUE;

        hr = HrReadTableFromDisk(pifld->lptblContents, (POBJ) pifld,
            pifld->peid->szPath, CONTENTS_COLUMNS, szContentsFileName);
        if (hr != hrSuccess)
        {
            /* non-fatal. If the data on disk is missing or invalid, we */
            /* should regenerate the data via HrSyncContentsTable. */

            TraceSz1("SMS: Error %s reading table from disk. Regenerating "
                "table on disk.", SzDecodeScode(GetScode(hr)));

            hr = hrSuccess;
        }
    }

    /* If we don't already have the mutex, get it so that we can access */
    /* the file without crossing paths with another process. */

    if (!fInMutex)
    {
        WaitForSingleObject(pims->hContTblMutex, INFINITE);
        fInMutex = TRUE;
    }

    hr = HrSyncContentsTable(pifld, TRUE);

    Assert(fInMutex);

    ReleaseMutex(pims->hContTblMutex);
    fInMutex = FALSE;

    if (hr != hrSuccess)
        goto exit;

    /* open a view to the table */
    /* get the sort order from the PR_SMS_CONTENTS_SORT_ORDER property */
    hr = HrGetSortOrder(pifld, &lpsSortOrder);
    if (hr != hrSuccess)
        goto exit;

    /* get the view */
    hr = pifld->lptblContents->lpVtbl->HrGetView(pifld->lptblContents,
        lpsSortOrder, ViewRelease, (ULONG) pifld, lppTable);
    if (hr != hrSuccess)
        goto exit;

    /* record this view in pifld */
    pifld->cContentsViews++;

exit:
    if (fInMutex)
        ReleaseMutex(pims->hContTblMutex);

    /* free the sort order */
    LMFree(&pifld->pims->lmr, lpsSortOrder);

    /* remove the table if it is created in error */
    if (hr != hrSuccess && fTableCreated)
    {
        UlRelease(pifld->lptblContents);
        pifld->cContentsViews = 0;
        pifld->lptblContents = NULL;
    }

    CloseIDSearch(&hFindFile, &szMessageTemplate);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_GetContentsTable, hr);
    return HrCheckHr(hr, IMAPIFolder_GetContentsTable);
}

/***************************************************************************
 -  IFLD_GetHierarchyTable
 -
 *  Purpose:
 *      Returns a table with the summary information about the child
 *      folders of the current folder
 *
 *  Parameters
 *       pifld      the folder object
 *       ulFlags    CONVENIENT_DEPTH if set indicates pre-order traversal
 *                  MAPI_DEFERRED_ERRORS
 *                  MAPI_UNICODE
 *       lppTable   location to place the table object
 *
 *  Returns:
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_GetHierarchyTable(PIFLD pifld, ULONG ulFlags,
    LPMAPITABLE *lppTable)
{
    HRESULT hr;
    LPTABLEDATA lptad = NULL;

    FLD_ValidateParameters(
            pifld,
            IMAPIContainer,
            GetHierarchyTable,
            (pifld,
            ulFlags,
            lppTable));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IFLD_EnterCriticalSection(pifld);

    /* if table needs refreshing */
    if (pifld->lptblHierarchy == NULL)
    {
        PINST pinst = (PINST) PvGetInstanceGlobals();
        SCODE sc;
        PLMR plmr = &pifld->pims->lmr;

        pifld->cHierarchyViews = 0;

        if (pinst == NULL)
        {
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            goto exit;
        }

        /* create the table */
        sc = CreateTable((LPIID) &IID_IMAPITableData,
            plmr->lpAllocBuf, plmr->lpAllocMore,
            plmr->lpFreeBuf, pinst->lpmalloc, TBLTYPE_DYNAMIC,
            PR_INSTANCE_KEY, (LPSPropTagArray) &sPropTagsHierarchy, &lptad);

        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        /* add a row or traversal for each subfolder of pifld */
        /* Note that the sample store only supports a max depth of 1. */

        hr = HrFillHierarchyTable(pifld, lptad);
        if (hr != hrSuccess)
            goto exit;

        pifld->lptblHierarchy = lptad;
        lptad = NULL;
    }

    /* open a view to the table */
    hr = pifld->lptblHierarchy->lpVtbl->HrGetView(pifld->lptblHierarchy,
        NULL, ViewRelease, (ULONG) pifld, lppTable);
    if (hr != hrSuccess)
        goto exit;

    /* record this view in pifld */
    pifld->cHierarchyViews++;

exit:
    /* remove the table if it is created in error */
    if (hr != hrSuccess && lptad)
        UlRelease(lptad);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_GetHierarchyTable, hr);
    return HrCheckHr(hr, IMAPIFolder_GetHierarchyTable);
}

/***************************************************************************
 -  IFLD_CreateMessage
 -
 *  Purpose:
 *      Creates a new message in the store
 *
 *  Parameters
 *      pifld           the folder object
 *      ulFlags         flags. Valid flags to pass are MAPI_DEFERRED_ERRORS
 *                      and MAPI_ASSOCIATED. The sample store doesn't support
 *                      MAPI_ASSOCIATED, so the code returns no support if
 *                      that flag is passed.
 *      piid            reserved for future use
 *      lppMessage      location to place the message object
 *
 *  Returns:
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_CreateMessage(PIFLD pifld, LPCIID piid, ULONG ulFlags,
    LPMESSAGE *lppMessage)
{
    HRESULT hr;
    PEID peid = NULL;
    ULONG ulSeqNumber;
    PIMSG pimsg = NULL;
    LPTSTR szFull = NULL;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            CreateMessage,
            (pifld,
            piid,
            ulFlags,
            lppMessage));

    #ifdef VALIDATE
    if (ulFlags & MAPI_ASSOCIATED)
        return ResultFromScode(MAPI_E_NO_SUPPORT);

    if (piid && !FQueryInterface(OT_MESSAGE, piid))
        return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
    #endif

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* create the message */

    hr = HrNewEID(pifld, pifld->pims, TEMP_EXT, &ulSeqNumber, &peid);
    if (hr != hrSuccess)
        goto exit;

    hr = HrNewIMSG(peid, pifld->pims, TRUE, TRUE, ulSeqNumber, &szFull, &pimsg);
    if (hr != hrSuccess)
        goto exit;

    hr = InitIMSGProps(pimsg);
    if (hr != hrSuccess)
        goto exit;

    /* folder contents counts and tables are not updated until message */
    /* has changes saved */

exit:
    LMFree(&pifld->pims->lmr, peid);

    if (hr == hrSuccess)
        *lppMessage = (LPMESSAGE) pimsg;
    else if (pimsg)
    {
        UlRelease(pimsg);

        Assert(szFull);
        DeleteFile(szFull);
    }

    FreeNull(szFull);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_CreateMessage, hr);
    return HrCheckHr(hr, IMAPIFolder_CreateMessage);
}

/***************************************************************************
 -  IFLD_CopyMessages
 -
 *  Purpose:
 *      Moves/Copies messages from the source folder to the destination folder
 *      EntryId's are not changed here since they get stamped in when the
 *      messages are opened.
 *
 *  Parameters
 *      pifld                   the source folder
 *      lpMsgList               the list of messages to be moved/copied
 *      piid                    the interface id of destination folder
 *      lpfolderDst             the destination folder
 *      ulUIParam               Handle of parent window,
 *                                  ignored if MESSAGE_DIALOG not set
 *      lpProgress              points to progress dialog object
 *      ulFlags                 MAPI_DECLINE_OK, MESSAGE_MOVE, or MESSAGE_DIALOG
 */
STDMETHODIMP IFLD_CopyMessages(PIFLD pifld, LPENTRYLIST lpMsgList,
    LPCIID piid, LPMAPIFOLDER lpfolderDst, ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    LONG cHandled = 0;          /* number messages copied/moved */
    LPTSTR szSrcName = NULL;    /* name of file to be moved */
    LPTSTR szDestName = NULL;   /* name of the file after it is moved */
    ULONG cbSrcPath = 0;        /* length in bytes of path to src fld */
    ULONG cbDestPath = 0;       /* length in bytes of path to dest fld */
    ULONG cbStorePath = 0;      /* length in bytes of path to root of store */
    TCHAR szBlanks[CCH_NAME] = TEXT("        .   ");
    HRESULT hr = hrSuccess;
    PIFLD pifldDst;
    BOOL fMove;
    SBinary *psb;
    SBinary *psbMax;
    PIMS pims;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            CopyMessages,
            (pifld,
            lpMsgList,
            piid,
            lpfolderDst,
            ulUIParam,
            lpProgress,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    pifldDst = (PIFLD) lpfolderDst;
    pims = pifld->pims;

    /* if the folders aren't in the same store, call back to MAPI */

    if (!FFoldInSameStore(pifld, pifldDst))
    {
        /* leave critical section before calling back to MAPI */
        IFLD_LeaveCriticalSection(pifld);

        return pims->psup->lpVtbl->CopyMessages(pims->psup,
            (LPIID) &IID_IMAPIFolder, (LPVOID) pifld, lpMsgList, piid,
            (LPVOID) lpfolderDst, ulUIParam, lpProgress, ulFlags);
    }

    fMove   = !!(ulFlags & MESSAGE_MOVE);

    if (    !OBJ_TestFlag(pifldDst, OBJF_MODIFY)
        ||  (   fMove
            &&  !OBJ_TestFlag(pifld, OBJF_MODIFY)))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    if (fMove)
    {
        /* If moving messages and the source and destination are the same */
        /* folder, we are done even before we begin. */

        ULONG ulTheSame;

        hr = pims->lpVtbl->CompareEntryIDs(pims,
                CbEID(pifldDst->peid), (LPENTRYID) pifldDst->peid,
                CbEID(pifld->peid), (LPENTRYID) pifld->peid, 0L, &ulTheSame);
        if (hr != hrSuccess)
            goto exit;

        if (ulTheSame)
        {
            hr = hrSuccess;
            goto exit;
        }
    }

    /* get storage for the path names */
    cbStorePath = Cbtszsize(pims->szStorePath);
    cbSrcPath = cbStorePath + CbEIDPath(pifld->peid);
    if (FIsRoot(pifld->peid))
        cbSrcPath--;

    cbDestPath = cbStorePath + CbEIDPath(pifldDst->peid);
    if (FIsRoot(pifldDst->peid))
        cbDestPath--;

    hr = HrFullPathName(pims->szStorePath, pifld->peid->szPath,
        szBlanks, &szSrcName);
    if (hr != hrSuccess)
        goto exit;

    hr = HrFullPathName(pims->szStorePath, pifldDst->peid->szPath,
        szBlanks, &szDestName);
    if (hr != hrSuccess)
        goto exit;

    psb = lpMsgList->lpbin;
    psbMax = psb + lpMsgList->cValues;

    /* move/copy each message */
    while (psb < psbMax)
    {
        ULONG ulMF;
        ULONG ulObjType;
        BOOL fLocked;
        BOOL fRead;
        PIMSG pimsg;
        LPSTR szRelativePath;
        PEID peidNewID;
        PEID peidToCopy;
        ULONG cbEntryID;

        cbEntryID = psb->cb;
        peidToCopy = (PEID) (psb->lpb);

        if (    FIsInvalidEID(cbEntryID, peidToCopy, pims)
            ||  !FIsMessage((PEID) peidToCopy))
        {
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
            goto exit;
        }

        /* open the message */
        hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peidToCopy),
                (LPENTRYID) peidToCopy, NULL, 0L, &ulObjType,
                (LPUNKNOWN *) &pimsg);
        if (hr != hrSuccess)
            goto exit;

        Assert(ulObjType == MAPI_MESSAGE);

        /* retrieve the PR_MESSAGE_FLAGS property */
        hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pims->lmr,
                PR_MESSAGE_FLAGS, &ulMF);

        UlRelease(pimsg);

        if (hr != hrSuccess)
            goto exit;

        fLocked = !!(ulMF & MSGFLAG_SUBMIT);
        fRead = !!(ulMF & MSGFLAG_READ);

        /* if the message is locked then this is an error */
        if (fLocked)
        {
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
            goto exit;
        }

        /* store in szSrcName the root relative path of the message to be moved*/
        lstrcpy(szSrcName + cbSrcPath, SzBaseName(peidToCopy));

        /* store in szDestName the store relative path name of */
        /* destination file */
        if (fMove)
            lstrcpy(szDestName + cbDestPath, SzBaseName(peidToCopy));
        else
        {
            /* If we're copying, we need to generate a new filename for the */
            /* message, or else we will have two messages with the same */
            /* filename, and copying again (or moving back) will fail. */

            PEID peidTemp = NULL;

            hr = HrNewEID(pifld, pims, MESSAGE_EXT, NULL, &peidTemp);
            if (hr != hrSuccess)
                goto exit;

            lstrcpy(szDestName + cbDestPath, SzBaseName(peidTemp));

            LMFree(&pims->lmr, peidTemp);
        }

        /* either move or copy the message */
        if (fMove)
        {
            if (!MoveFile(szSrcName, szDestName))
            {
                TraceSz1("MoveFile failed: OS error %08lX.", GetLastError());
                hr = ResultFromScode(MAPI_E_NO_ACCESS);
                goto exit;
            }

            /* remove this message from contents tables */

            (void) HrIncrementOneROProp(pifld, -1, PR_CONTENT_COUNT);

            if (!fRead)
                (void) HrIncrementOneROProp(pifld, -1, PR_CONTENT_UNREAD);

            ChangeTable(pims, pifld->peid, peidToCopy, MAPI_MESSAGE,
                TABLE_ROW_DELETED, TRUE);
        }
        else
        {
            if (!CopyFile(szSrcName, szDestName, TRUE))
            {
                TraceSz1("CopyFile failed: OS error %08lX.", GetLastError());
                hr = ResultFromScode(MAPI_E_NO_ACCESS);
                goto exit;
            }
        }

        /* get new entryid for this message */
        hr = HrFullToRelative(szDestName, pims, &szRelativePath);
        if (hr != hrSuccess)
            goto exit;

        hr = HrConstructEID(&(peidToCopy->uidResource), &pims->lmr,
                szRelativePath, &peidNewID);

        FreeNull(szRelativePath);

        if (hr != hrSuccess)
            goto exit;

        /* If we're copying, then we just used a new name for the file. */
        /* Note that PR_RECORD_KEY, PR_ENTRYID, PR_INSTANCE_KEY, and */
        /* PR_PARENT_ENTRYID are now different, but since these are stored */
        /* in the opened msg, everything should be fine. */

        /* add a row to the contents table of the destination folder */
        (void) HrIncrementOneROProp(pifldDst, 1, PR_CONTENT_COUNT);

        if (!fRead)
            (void) HrIncrementOneROProp(pifldDst, 1, PR_CONTENT_UNREAD);

        ChangeTable(pims, pifldDst->peid, peidNewID, MAPI_MESSAGE,
            TABLE_ROW_ADDED, TRUE);

        LMFree(&pims->lmr, peidNewID);

        cHandled++;
        psb++;
    }

exit:
    FreeNull(szDestName);
    FreeNull(szSrcName);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_CopyMessages, hr);
    return HrCheckHr(hr, IMAPIFolder_CopyMessages);
}

/***************************************************************************
 -  IFLD_DeleteMessages
 -
 *  Purpose:
 *      Deletes from the folder pifld all messages in the list lpMsgList
 *
 *  Parameters
 *      pifld           the folder from which messages are to be deleted
 *      lpMsgList       the list of messages to be deleted
 *      ulUIParam       handle to main window, cast to ULONG
 *      lpProgress      points to progress dialog information
 *      ulFlags         MESSAGE_DIALOG
 *
 *  Returns:
 *
 *  Side effects:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_DeleteMessages(PIFLD pifld, LPENTRYLIST lpMsgList,
    ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    PEID peidToDelete = NULL;   /* pointer to next eid to be deleted */
    LONG cDeleted = 0;          /* number messages deleted */
    LONG cUnreadDeleted = 0;    /* number of unread messages deleted */
    LPTSTR szToDeleteName = NULL;   /* name of the next message to be deleted */
    HRESULT hr = hrSuccess;
    LPTSTR szBlankName = TEXT("        .   ");
    ULONG cchPath;              /* length of path to message */
    BOOL fIsRead;               /* TRUE if deleting a read message */
    DWORD dwErr;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            DeleteMessages,
            (pifld,
            lpMsgList,
            ulUIParam,
            lpProgress,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    hr = HrFullPathName(pifld->pims->szStorePath, pifld->peid->szPath,
        szBlankName, &szToDeleteName);
    if (hr != hrSuccess)
        goto exit;

    cchPath = lstrlen(szToDeleteName) - lstrlen(szBlankName);

    /* delete each message in lpMsgList up until the first error */
    while ((ULONG) cDeleted < lpMsgList->cValues)
    {
        peidToDelete = (PEID) (lpMsgList->lpbin[cDeleted].lpb);

        /* make sure that this is a message's eid */
        if (!FIsMessage(peidToDelete))
        {
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
            goto exit;
        }

        /* make sure the message is not submitted */
        if (FIsSubmittedMessage(pifld->pims, peidToDelete))
        {
            hr = ResultFromScode(MAPI_E_SUBMITTED);
            goto exit;
        }

        /* See if it has been read */
        hr = HrIsRead(pifld, peidToDelete, &fIsRead);
        if (hr != hrSuccess)
            goto exit;

        /* remove the message from disk */
        lstrcpy(szToDeleteName + cchPath, SzBaseName(peidToDelete));
        if (!DeleteFile(szToDeleteName))
        {
            dwErr = GetLastError();

            if ((dwErr != ERROR_FILE_NOT_FOUND && dwErr != ERROR_PATH_NOT_FOUND)
                || lpMsgList->cValues == 1)
            {
                hr = ResultFromScode(MAPI_E_NO_ACCESS);
                goto exit;
            }
        }

        /* remove this message from any open tables */
        ChangeTable(pifld->pims, pifld->peid, peidToDelete, MAPI_MESSAGE,
            TABLE_ROW_DELETED, TRUE);

        if (!fIsRead)
            cUnreadDeleted++;

        cDeleted++;
    }

exit:

    FreeNull(szToDeleteName);

    if (cDeleted)
        (void) HrIncrementOneROProp(pifld, -cDeleted, PR_CONTENT_COUNT);
    if (cUnreadDeleted)
        (void) HrIncrementOneROProp(pifld, -cUnreadDeleted, PR_CONTENT_UNREAD);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_DeleteMessages, hr);
    return HrCheckHr(hr, IMAPIFolder_DeleteMessages);
}

/***************************************************************************
 -  IFLD_CreateFolder
 -
 *  Purpose:
 *      Create a new folder within the message store
 *
 *  Parameters
 *      pifld               the parent folder of the newly created folder
 *      ulFldType           type of folder to be created
 *      szFldName           name of the new folder
 *      szComment           comment string for the new folder
 *      piid                Reserved; must be NULL.
 *      ulFlags             MAPI_UNICODE and/or MAPI_DEFERRED_ERRORS
 *      lppfolder           pointer to variable to receive new folder object
 *
 */
STDMETHODIMP IFLD_CreateFolder(PIFLD pifld, ULONG ulFldType, LPSTR szFldName,
    LPSTR szComment, LPCIID piid, ULONG ulFlags, LPMAPIFOLDER *lppfolder)
{
    HRESULT hr;
    PIFLD pifldNew = NULL;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            CreateFolder,
            (pifld,
            ulFldType,
            szFldName,
            szComment,
            piid,
            ulFlags,
            lppfolder));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);

    if (piid && !FQueryInterface(OT_FOLDER, piid))
        return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
    #endif

    /* Sample store can only create generic folders. */
    if (ulFldType != FOLDER_GENERIC)
        return ResultFromScode(MAPI_E_NO_SUPPORT);

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    hr = HrCreateFolder(pifld, szFldName, szComment,
        !!(ulFlags & OPEN_IF_EXISTS), &pifldNew, NULL);

    if (hr != hrSuccess)
        UlRelease(pifldNew);
    else
        *lppfolder = (LPMAPIFOLDER) pifldNew;

exit:
    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_CreateFolder, hr);
    return HrCheckHr(hr, IMAPIFolder_CreateFolder);
}

/***************************************************************************
 -  IFLD_CopyFolder
 -
 *  Purpose:
 *      Copy my child folder to the specified parent
 *
 *  Parameters
 *      pifld           the parent folder of the newly created folder
 *      cbEntryID       length of entry ID of folder to copy
 *      lpEntryID       entry ID of folder to copy
 *      lpiid           interface to the destination folder
 *      lpfolder        pointer to destination object
 *      szNewName       new name to give the copied folder, may be NULL
 *      ulUIParam       Handle of parent window,
 *                      ignored if MESSAGE_DIALOG not set
 *      lpProgress      points to progress dialog information
 *      ulFlags         control flags
 *
 */
STDMETHODIMP IFLD_CopyFolder(PIFLD pifld, ULONG cbEntryID, LPENTRYID lpEntryID,
    LPCIID piid, LPVOID lpfolder, LPSTR szNewName,
    ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    HRESULT hr;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            CopyFolder,
            (pifld,
            cbEntryID,
            lpEntryID,
            piid,
            lpfolder,
            szNewName,
            ulUIParam,
            lpProgress,
            ulFlags));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    /* For now, all we do is call back to mapi. */

    hr = pifld->pims->psup->lpVtbl->CopyFolder(pifld->pims->psup,
        (LPIID) &IID_IMAPIFolder, (LPVOID) pifld, cbEntryID, lpEntryID,
        piid, lpfolder, szNewName, ulUIParam, lpProgress, ulFlags);

    DebugTraceResult(IFLD_CopyFolder, hr);
    return HrCheckHr(hr, IMAPIFolder_CopyFolder);
}

/***************************************************************************
 -  IFLD_DeleteFolder
 -
 *  Purpose:
 *      Deletes a subfolder
 *
 *  Parameters
 *      pifld               the current folder
 *      cbEntryID           byte count of lpEntryID
 *      lpEntryID           entryID of subfolder to be deleted
 *      ulUIParam           handle to main window cast to ULONG
 *      lpProgress          pointer to progess dialog information
 *      ulFlags             DEL_MESSAGES and/or DEL_FOLDERS
 *                                  and/or FOLDER_DIALOG
 *
 *  Returns:
 *
 *  Errors:
 */
STDMETHODIMP IFLD_DeleteFolder(PIFLD pifld, ULONG cbEntryID,
    LPENTRYID lpEntryID, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
    ULONG ulFlags)
{
    HRESULT hr = hrSuccess;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            DeleteFolder,
            (pifld,
            cbEntryID,
            lpEntryID,
            ulUIParam,
            lpProgress,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims)
        || !FIsFolder((PEID) lpEntryID))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* delete this subdirectory of the current directory */
    hr = HrDeleteSubDirectory(pifld, (PEID) lpEntryID, ulFlags, FALSE);

exit:
    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_DeleteFolder, hr);
    return HrCheckHr(hr, IMAPIFolder_DeleteFolder);
}

STDMETHODIMP IFLD_SetSearchCriteria(PIFLD pifld, LPSRestriction lpRestriction,
    LPENTRYLIST lpentrylist, ULONG ulSearchFlags)
{
    DebugTraceSc(IFLD_SetSearchCriteria, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

STDMETHODIMP IFLD_GetSearchCriteria(PIFLD pifld, ULONG ulFlags,
    LPSRestriction *lppRestriction, LPENTRYLIST *lppfolderList,
    ULONG *lpulSearchState)
{
    DebugTraceSc(IFLD_GetSearchCriteria, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/***************************************************************************
 -  IFLD_SetReadFlags
 -
 *  Purpose:
 *      Sets the READ flag for each of the given messages.
 *
 *  Parameters
 *      pifld                   the source folder
 *      lpMsgList               the list of messages to be moved/copied
 *      ulUIParam               Handle of parent window,
 *                                  ignored if MESSAGE_DIALOG not set
 *      lpProgress              points to progress dialog object
 *      ulFlags                 SUPPRESS_RECEIPT, CLEAR_READ_FLAG
 */
STDMETHODIMP IFLD_SetReadFlags(PIFLD pifld, LPENTRYLIST lpMsgList,
    ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    HRESULT hr = hrSuccess;
    LPENTRYLIST pelst = NULL;
    BOOL fFreeList = FALSE;
    SBinary *psb;
    SBinary *psbMax;
    BOOL fMultipleNotifs = TRUE;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            SetReadFlags,
            (pifld,
            lpMsgList,
            ulUIParam,
            lpProgress,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    pelst = lpMsgList;

    if (pelst == NULL)
    {
        /* Create the message list if the caller didn't give us one. */

        hr = HrCreateMessageList(pifld, &pelst);
        if (hr != hrSuccess)
            goto exit;

        fFreeList = TRUE;
    }

    if (pelst->cValues > 5)
        fMultipleNotifs = FALSE;

    psb = pelst->lpbin;
    psbMax = psb + pelst->cValues;

    /* Call IMessage::SetReadFlag on each message */

    while (psb < psbMax)
    {
        ULONG ulObjType;
        PIMSG pimsg;
        PEID peid;
        ULONG cbEntryID;

        cbEntryID = psb->cb;
        peid = (PEID) (psb->lpb);

        if (    FIsInvalidEID(cbEntryID, peid, pifld->pims)
            ||  !FIsMessage((PEID) peid))
        {
            hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
            goto exit;
        }

        /* open the message */
        hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peid),
                (LPENTRYID) peid, NULL, 0L, &ulObjType,
                (LPUNKNOWN *) &pimsg);
        if (hr != hrSuccess)
            goto exit;

        Assert(ulObjType == MAPI_MESSAGE);

        hr = pimsg->lpVtbl->SetReadFlag(pimsg, ulFlags);

        if (hr == hrSuccess && fMultipleNotifs)
            ChangeTable(pimsg->pims, pifld->peid, pimsg->peid, MAPI_MESSAGE,
                TABLE_ROW_MODIFIED, TRUE);

        UlRelease(pimsg);

        if (hr != hrSuccess)
            goto exit;

        psb++;
    }

    /* If several messages changed, then just send one notification to */
    /* re-sync the entire contents table on other processes. */

    if (!fMultipleNotifs)
        ChangeTable(pifld->pims, pifld->peid, NULL, MAPI_MESSAGE,
            TABLE_CHANGED, TRUE);

exit:
    if (fFreeList)
        DestroyMessageList(&pifld->pims->lmr, &pelst);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_SetReadFlags, hr);
    return HrCheckHr(hr, IMAPIFolder_SetReadFlags);
}

/***************************************************************************
 -  GetMessageStatus
 -
 *  Purpose:
 *      Retrieves the status associated with a message in a folder
 *
 *  Parameters
 *      pifld               the current folder
 *      cbEntryID           byte count of lpEntryID
 *      lpEntryID           entryID of the message
 *      ulFlags             reserved for future use, must be 0
 *      lpulMessageStatus   pointer to variable to receive the status
 *
 */
STDMETHODIMP IFLD_GetMessageStatus(PIFLD pifld, ULONG cbEntryID,
    LPENTRYID lpEntryID, ULONG ulFlags, ULONG *lpulMessageStatus)
{
    LPMESSAGE lpmsgMsg = NULL;  /* lpEntryID as an open message */
    ULONG ulObjType;            /* type of object opened */
    HRESULT hr = hrSuccess;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            GetMessageStatus,
            (pifld,
            cbEntryID,
            lpEntryID,
            ulFlags,
            lpulMessageStatus));

    IFLD_EnterCriticalSection(pifld);

    if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims)
        || !FIsMessage((PEID) lpEntryID))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    /* open up lpEntryID */
    hr = pifld->lpVtbl->OpenEntry(pifld, cbEntryID,
        lpEntryID, NULL, 0L, &ulObjType, (LPUNKNOWN *) &lpmsgMsg);
    if (hr != hrSuccess)
        goto exit;

    /* get the property */
    hr = HrGetSingleProp((LPMAPIPROP) lpmsgMsg, &pifld->pims->lmr,
        PR_MSG_STATUS, lpulMessageStatus);

    if (hr != hrSuccess)
        goto exit;

exit:
    UlRelease(lpmsgMsg);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_GetMessageStatus, hr);
    return HrCheckHr(hr, IMAPIFolder_GetMessageStatus);
}

/***************************************************************************
 -  SetMessageStatus
 -
 *  Purpose:
 *      Sets the 32-bit status associated with a message in a folder
 *
 *  Parameters
 *      pifld               the current folder
 *      cbEntryID           byte count of lpentryID
 *      lpEntryID           entryID of the message
 *      ulNewStatus         new status to be assigned
 *      ulNewStatusMask     mask applied to new status indicating bits to set
 *      lpulOldStatus       pointer to variable to hold previous status
 *
 */
STDMETHODIMP IFLD_SetMessageStatus(PIFLD pifld, ULONG cbEntryID,
    LPENTRYID lpEntryID, ULONG ulNewStatus, ULONG ulNewStatusMask,
    ULONG *lpulOldStatus)
{
    PIMSG pimsg = NULL;
    LPMESSAGE lpmsg;
    ULONG ulObjType;            /* type of object just opened */
    HRESULT hr = hrSuccess;
    ULONG ulStatus;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            SetMessageStatus,
            (pifld,
            cbEntryID,
            lpEntryID,
            ulNewStatus,
            ulNewStatusMask,
            lpulOldStatus));

    IFLD_EnterCriticalSection(pifld);

    if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims) ||
        !FIsMessage((PEID) lpEntryID))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    if (    ulNewStatusMask
        &&  !OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* open up lpEntryID */
    hr = pifld->lpVtbl->OpenEntry(pifld, CbEID((PEID) lpEntryID),
        lpEntryID, NULL, 0L, &ulObjType, (LPUNKNOWN *) &pimsg);
    if (hr != hrSuccess)
        goto exit;

    lpmsg = pimsg->lpmsg;

    /* get the old status */
    hr = HrGetSingleProp((LPMAPIPROP) lpmsg, &pifld->pims->lmr,
        PR_MSG_STATUS, &ulStatus);
    if (hr != hrSuccess)
        goto exit;

    if (lpulOldStatus)
        *lpulOldStatus = ulStatus;

    if (ulNewStatusMask)
    {
        ulNewStatus &= ulNewStatusMask;
        ulStatus &= ~ulNewStatusMask;
        ulStatus |= ulNewStatus;

        hr = HrSetOneROProp(lpmsg, &pifld->pims->lmr, PR_MSG_STATUS,
            &ulStatus);
        if (hr != hrSuccess)
            goto exit;

        hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

        UlRelease(pimsg);
        pimsg = NULL;

        if (hr != hrSuccess)
            goto exit;

        ChangeTable(pifld->pims, pifld->peid, (PEID) lpEntryID, MAPI_MESSAGE,
            TABLE_ROW_MODIFIED, TRUE);
    }

exit:
    UlRelease(pimsg);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_SetMessageStatus, hr);
    return HrCheckHr(hr, IMAPIFolder_SetMessageStatus);
}

/***************************************************************************
 -  IFLD_SaveContentsSort
 -
 *  Purpose:
 *      set default sort order for contents tables on pifld
 *
 *  Parameters
 *      pifld               the current folder
 *      lpSortCriteria      points to the sort criteria
 *      ulFlags             RECURSIVE_SORT
 *
 */
STDMETHODIMP IFLD_SaveContentsSort(PIFLD pifld, LPSSortOrderSet lpSortCriteria,
    ULONG ulFlags)
{
    SPropValue pval;            /* sort order property value */
    LPSPropProblemArray pprba = NULL;
    HRESULT hr = hrSuccess;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            SaveContentsSort,
            (pifld,
            lpSortCriteria,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    pval.Value.MVl.lpl = NULL;

    /* store the sort order in pval */
    pval.ulPropTag = PR_SMS_CONTENTS_SORT_ORDER;
    pval.Value.MVl.cValues = 2 * lpSortCriteria->cSorts;
    pval.Value.MVl.lpl = (LONG *) lpSortCriteria->aSort;

    /*  set the sort order property */
    hr = pifld->lpVtbl->SetProps(pifld, 1, &pval, &pprba);

    if (pprba)
    {
        LMFree(&pifld->pims->lmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_SaveContentsSort, hr);
    return HrCheckHr(hr, IMAPIFolder_SaveContentsSort);
}

/*
 *  IFLD_EmptyFolder
 *
 *  Purpose:
 *      This function empties a folder based on the flags given. It will never
 *      actually delete the folder itself, and will only empty the specified
 *      items.
 *
 *  Parameters
 *      pifld           The address of the folder object to empty.
 *      ulUIParam       Handle of the parent window. (for progress). NYI
 *      lpProgress      Address of progress dialog object
 *      ulFlags         Control flags. Setting DEL_ASSOCIATED causes FAI
 *                      to also be deleted. The FOLDER_DIALOG flag is NYI.
 *                      DEL_ASSOCIATED has no meaning to the sample store.
 *
 *  Returns:
 */
STDMETHODIMP IFLD_EmptyFolder(PIFLD pifld, ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    HRESULT hr;
    PIFLD pifldParent = NULL;

    FLD_ValidateParameters(
            pifld,
            IMAPIFolder,
            EmptyFolder,
            (pifld,
            ulUIParam,
            lpProgress,
            ulFlags));

    IFLD_EnterCriticalSection(pifld);

    if (!OBJ_TestFlag(pifld, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* Empty the folder. */

    hr = HrOpenParent(pifld->pims, pifld->peid, MAPI_MODIFY, &pifldParent);
    if (hr != hrSuccess)
        goto exit;

    hr = HrDeleteSubDirectory(pifldParent, pifld->peid,
        (DEL_MESSAGES | DEL_FOLDERS), TRUE);
    if (hr != hrSuccess)
        goto exit;

exit:
    UlRelease(pifldParent);

    IFLD_LeaveCriticalSection(pifld);

    DebugTraceResult(IFLD_EmptyFolder, hr);
    return HrCheckHr(hr, IMAPIFolder_EmptyFolder);
}

/****************************************************
 *          Private Functions                       *
 ****************************************************/

/*
 *  IFLD_IsInvalid
 *
 *  Purpose         check if pifld points to an invalid folder.
 *
 *  Parameter
 *      pifld       pointer to the folder
 *
 *  Returns: BOOL - TRUE means the folder is invalid.
 */

static BOOL IFLD_IsInvalid(PIFLD pifld)
{
    return (IsBadWritePtr(pifld, sizeof(IFLD)) ||
        pifld->lpVtbl != &vtblIFLD);
}

/*
 * HrCreateContTblMutex
 *
 *  Purpose
 *      Create the contents table mutex, and return it to the caller.
 *
 *  Arguments
 *      phCTMutex: Pointer to the location to return the new mutex.
 *
 *  Returns:
 *      HRESULT: Will return an error only if the CreateMutex call fails.
 */
static HRESULT HrCreateContTblMutex(HANDLE *phCTMutex)
{
    HRESULT hr = hrSuccess;
    HANDLE hMutex;
    LPTSTR szMutexName = "SMS_CONTTBLFILE_MUTEX";

    hMutex = CreateMutex(NULL, FALSE, szMutexName);

    if (hMutex)
        *phCTMutex = hMutex;

    #ifndef WIN16
    else
    {
        TraceSz1("SampleMS: HrCreateContTblMutex: call to"
            " CreateMutex failed (error %08lX)", GetLastError());

        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }
    #endif

    DebugTraceResult(HrCreateContTblMutex, hr);
    return hr;
}

/*
 *  FFoldInSameStore
 *
 *  Purpose
 *      Given that the source folder is a folder object within
 *      our store (i.e. it's a PIFLD, not LPMAPIFOLDER), check if the
 *      destination folder object is also within the same store.
 *
 *  Parameters
 *      pifld: The folder that we already know is within this store.
 *      lpDestFld: The folder that we are checking. It may not be in this store.
 *
 *  Returns: BOOL - TRUE means the folders are both in this store.
 *
 */

static BOOL FFoldInSameStore(PIFLD pifld, PIFLD lpDestFld)
{
    if (IsBadWritePtr(lpDestFld, sizeof(IFLD)))
        return FALSE;

    if (lpDestFld->lpVtbl != &vtblIFLD)
        return FALSE;

    if (pifld->pims != lpDestFld->pims)
        return FALSE;

    return TRUE;
}

/***************************************************************************
 *  IFLD_Neuter
 *
 *  Purpose     Free all memory allocated inside a folder object, and release
 *              any objects held inside a folder object. Leaves the object
 *              itself alone (the caller will free it).
 *
 *  Argument    pifld       the folder to be cleaned out.
 *
 */
void IFLD_Neuter(PIFLD pifld)
{
    /* free allocated memory */
    FreeNull(pifld->peid);

    LMFree(&pifld->pims->lmr, pifld->pval);

    /* remove any tables */
    if (pifld->lptblContents)
    {
        TraceSz1("SampleMS (IFLD_Neuter): %d contents table views left",
            pifld->cContentsViews);

        UlRelease(pifld->lptblContents);
        pifld->cContentsViews = 0;
        pifld->lptblContents = NULL;
    }

    if (pifld->lptblHierarchy)
    {
        TraceSz1("SampleMS (IFLD_Neuter): %d hierarchy table views left",
            pifld->cHierarchyViews);

        UlRelease(pifld->lptblHierarchy);
        pifld->cHierarchyViews = 0;
        pifld->lptblHierarchy = NULL;
    }

    return;
}

/*
 -  ViewRelease
 -
 *  Purpose:
 *      Call back function from itable on release of a view
 *      removes the view from the list of open views
 *      releases the table if there are no more open views on it
 *
 *  Parameters
 *       ulCallerData   pointer to folder object
 *       lptbl          pointer to the table on which this is a view
 *       lpvtView       pointer to the view that was released
 *
 *
 */
STDAPI_(void)ViewRelease(ULONG ulCallerData, LPTABLEDATA lptbl,
    LPMAPITABLE lpvtView)
{
    PIFLD pifld;                /* folder object for this view */
    ULONG *pulViewsLeft;        /* ptr to number of open views left */
    LPTABLEDATA *pptbl;         /* ptr to folder's internal table data */

    pifld = (PIFLD) ulCallerData;

    /* do nothing if the folder is invalid. Don't use IFLD_IsInvalid */
    /* because the folder may have already been released, and we still */
    /* want to release the view in that case. */
    if (IsBadWritePtr(pifld, sizeof(IFLD)))
        return;

    IFLD_EnterCriticalSection(pifld);

    /* find the kind of table this view is on */
    if (pifld->lptblContents == lptbl)
    {
        pulViewsLeft = &(pifld->cContentsViews);
        pptbl = &(pifld->lptblContents);
    }
    else if (pifld->lptblHierarchy == lptbl)
    {
        pulViewsLeft = &(pifld->cHierarchyViews);
        pptbl = &(pifld->lptblHierarchy);
    }
    else
    {
        /* invalid table */
        TrapSz("Invalid call to ViewRelease");
        goto exit;
    }

    AssertSz(*pptbl == lptbl, "Different table data given to ViewRelease");

    /* decrement count of views and release*/
    /* the table data if the viewlist is empty */
    if (--(*pulViewsLeft) == 0)
    {
        POBJ pobj = (POBJ) pifld;

        UlRelease(lptbl);
        *pptbl = NULL;

        /* These tests are the same as IFLD_Release in this module. That is */
        /* the release method that folders use. */

        if (    pobj->cRef == 0
            &&  pobj->pobjHead == 0
            &&  pifld->lptblContents == NULL
            &&  pifld->lptblHierarchy == NULL)
        {
            OBJ_Destroy(pobj);  /* will leave the critical section */
            return;
        }

    }

exit:
    IFLD_LeaveCriticalSection(pifld);

    return;
}

/*
 *  HrIsRead
 *
 *  Purpose
 *      Given a parent folder and the entryid of a message, determine
 *      whether the message has been read (i.e., whether the MSGFLAG_READ
 *      bit inside the PR_MESSAGE_FLAGS property is set).
 *
 *  Parameters
 *      pifld       A pointer to the parent folder object of the message.
 *      peidMsg     The entryid of the message.
 *      pfRead      A pointer to the location to return a BOOL saying
 *                  whether or not the message was read. Set to TRUE
 *                  if the message was read, FALSE otherwise.
 */
static HRESULT HrIsRead(PIFLD pifld, PEID peidMsg, BOOL *pfRead)
{
    HRESULT hr;
    PIMSG pimsg = NULL;         /* opened version of lpidMsg */
    ULONG ulObjType;            /* type of object opened */
    ULONG ulMF;

    /* open the message */
    hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peidMsg), (LPENTRYID) peidMsg,
        NULL, 0L, &ulObjType, (LPUNKNOWN *) &pimsg);
    if (hr != hrSuccess)
        goto exit;

    Assert(ulObjType == MAPI_MESSAGE);

    /* retrieve the PR_MESSAGE_FLAGS property */
    hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pifld->pims->lmr,
            PR_MESSAGE_FLAGS, &ulMF);

    /* set pfRead: no PR_MESSAGE_FLAGS means unread */
    /* The "!!" operator is used to change a bit that is set anywhere in a */
    /* word to exactly TRUE (only one bit set at the far right of the word). */
    /* The first "!" is NOT, which makes all zeros in the value into TRUE, */
    /* and anything else into FALSE. The second "!" reverses that back, so */
    /* that all zeros become FALSE, and anything else becomes TRUE. */

    if (hr == hrSuccess)
        *pfRead = !!(ulMF & MSGFLAG_READ);
    else if (GetScode(hr) == MAPI_E_NOT_FOUND)
    {
        *pfRead = FALSE;
        hr = hrSuccess;
    }

    UlRelease(pimsg);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    DebugTraceResult(HrIsRead, hr);
    return hr;
}

/*
 *  HrRemoveRow
 *
 *  Purpose     remove a row from a contents or hierarchy table
 *
 *  Argument    lptbl       the table whose row is to be deleted
 *              peid        the entryid of row to be deleted. Used to
 *                          generate the instance key.
 *
 */
HRESULT HrRemoveRow(LPTABLEDATA lptbl, PEID peid)
{
    SPropValue valIK;   /* instance key stored in a property value */
    HRESULT hr;

    if (lptbl == NULL)
        return hrSuccess;

    /* make the instance key into a property value */
    valIK.ulPropTag = PR_INSTANCE_KEY;
    valIK.Value.bin.cb = CbEID(peid);
    valIK.Value.bin.lpb = (BYTE *) peid;

    /* delete the row from the table */
    hr = lptbl->lpVtbl->HrDeleteRow(lptbl, &valIK);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    DebugTraceResult(HrRemoveRow, hr);
    return hr;
}

/*
 *  HrUpdateRow
 *
 *  Purpose
 *      alter the row in the table with the given index property
 *      to the properties in the proptag array.  If the index is
 *      not present, a new row is added to the table. The index
 *      property MUST be the first in pPTA.
 *      For contents table, an entry is made only if the object has
 *      been saved (and therefore has a PR_ENTRYID property).
 *
 *  Parameters
 *      pims            A pointer to the message store object.
 *      lptbl           A pointer to the table data object to update.
 *      peid            EntryID of object whose row is to be added
 *      pPTA            array of property tags of properties to appear in
 *                      this row
 *      pft             pointer to the file time to write
 *      ulObjType       MAPI_MESSAGE (for contents table) or MAPI_FOLDER
 *                      (for hierarchy table)
 *
 */
HRESULT HrUpdateRow(PIMS pims, LPTABLEDATA lptbl, PEID peid,
    LPSPropTagArray pPTA, FILETIME *pft, ULONG ulObjType)
{
    SRow srUpdateRow;           /* new table row */
    LPSPropValue pval = NULL;
    LPMAPIPROP lpEntry = NULL;  /* szName as an open mapiprop */
    ULONG ulObjectType;         /* type of object szName is */
    HRESULT hr = hrSuccess;
    ULONG cbEID;                /* number of bytes in lpEntryID */
    ULONG cValues;

    if (lptbl == NULL)
        return hrSuccess;

    cbEID = CbEID(peid);

    /* Don't add a contents table row for unsaved messages */
    if (ulObjType == MAPI_MESSAGE && FIsUnsavedEID(peid))
        goto exit;

    /* open this object. Do not open with MAPI_MODIFY because if the */
    /* store is open read-only, the OpenEntry will fail. */

    hr = ((LPMDB) pims)->lpVtbl->OpenEntry((LPMDB) pims, cbEID,
        (LPENTRYID) peid, NULL, 0L, &ulObjectType, (LPUNKNOWN *) &lpEntry);
    if (hr != hrSuccess)
        goto exit;

    if (ulObjType == MAPI_MESSAGE && FIsUnsavedMsg((PIMSG) lpEntry))
    {
        UlRelease(lpEntry);
        goto exit;
    }

    hr = lpEntry->lpVtbl->GetProps(lpEntry, pPTA, 0, /* ansi */
            &cValues, &pval);

    UlRelease(lpEntry);

    if (HR_FAILED(hr))
        goto exit;

    hr = hrSuccess;             /* ignore warnings */

    Assert(cValues == pPTA->cValues);

    /* add depth if this is a hierarchy table */
    if (ulObjType == MAPI_FOLDER)
    {
        AssertSz(PROP_ID(pval[cValues - 1].ulPropTag) == PROP_ID(PR_DEPTH),
            "Invalid assumption. PR_DEPTH must be last in hier ptags.");

        pval[cValues - 1].ulPropTag = PR_DEPTH;
        pval[cValues - 1].Value.l = 1L;
    }

    /* stamp in the last mod time (based on what's passed in) */
    /* Note assumption of where the proptag is. */

    AssertSz1(pPTA->aulPropTag[MODIFY_INDEX] == PR_LAST_MODIFICATION_TIME,
        "Invalid assumption. PR_LAST_MODIFICATION_TIME must be at "
        "index %d in pPTA.", MODIFY_INDEX);

    pval[MODIFY_INDEX].ulPropTag = PR_LAST_MODIFICATION_TIME;
    pval[MODIFY_INDEX].Value.ft = *pft;

    /* add the row */
    srUpdateRow.cValues = cValues;
    srUpdateRow.lpProps = pval;

    hr = lptbl->lpVtbl->HrModifyRow(lptbl, &srUpdateRow);

    /* Fall through to exit */

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    LMFree(&pims->lmr, pval);

    DebugTraceResult(HrUpdateRow, hr);
    return hr;
}

/*
 * HrCreateFolder
 *
 * This routine searches for a child folder of the given parent folder
 * for a folder with the specified PR_DISPLAY_NAME. If found and the caller
 * wishes to open if exists, then the found folder is opened and returned. If
 * found and the caller doesn't want to open if exists, then the error
 * MAPI_E_COLLISION is returned. If no child folder with the matching display
 * name is found, then a child folder with the correct display name is created
 * and the new folder is returned.
 *
 * Parameters
 *
 * pifldParent: The parent folder in which to search for matching display names,
 *              and, if necessary, to create the child folder.
 * szName:      The display name to search for and to create the child folder
 *              with.
 * szComment:   If a child folder is created, the value to set its PR_COMMENT
 *              property to.
 * fOpenIfExists: If a child folder with a matching PR_DISPLAY_NAME is found,
 *              open the matching folder when this BOOL is TRUE. If this
 *              BOOL is FALSE, and a match is found, return MAPI_E_COLLISION.
 *              If no match is found, this parameter is not referenced.
 * ppifldNew:   The location to return the newly created or opened folder.
 * pfCreated:   The location to return whether this function created or opened
 *              the child folder. Returning TRUE means the folder was created.
 *
 * Errors: memory or disk errors, and MAPI_E_COLLISION.
 *
 */
static HRESULT HrCreateFolder(PIFLD pifldParent, LPSTR szName, LPSTR szComment,
    BOOL fOpenIfExists, PIFLD *ppifldNew, BOOL *pfCreated)
{
    HRESULT hr;
    BOOL fCreated = FALSE;
    PEID peid = NULL;
    PIFLD pifldNew = NULL;
    LPMAPITABLE pmt = NULL;
    PIMS pims = pifldParent->pims;
    ULONG ulRowCount;
    BOOL fFirstSubFld;

    hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);
    if (hr != hrSuccess)
        goto exit;

    hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);
    if (hr != hrSuccess)
        goto exit;

    fFirstSubFld = (ulRowCount == 0);

    if (!fFirstSubFld)
    {
        hr = HrEIDFromDisplayName(pmt, szName, &pims->lmr, &peid);

        if (hr != hrSuccess)
            goto exit;

        /* If we found a duplicate, and we don't want it, we have an error. */

        if ((peid != NULL) && (fOpenIfExists == FALSE))
        {
            hr = ResultFromScode(MAPI_E_COLLISION);
            goto exit;
        }
    }

    UlRelease(pmt);
    pmt = NULL;

    if (peid == NULL)
    {
        /* //$ Need to handle cleanup of directory and propfile when errors */
        /* //$ occur later in this function... */

        hr = HrCreateFolderStorage(pifldParent, FOLDER_GENERIC, szName,
            szComment, TRUE, pims, &peid);

        if (hr != hrSuccess)
            goto exit;

        fCreated = TRUE;
    }

    /* Create the instance data */
    hr = HrNewIFLD(peid, pims, TRUE, &pifldNew);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetInternalProps(&pims->lmr, cpropIFLDInternal, &(pifldNew->pval),
            &(pifldNew->cval), peid, pifldParent->peid, 0);
    if (hr != hrSuccess)
        goto exit;

    /* update the hierarchy tables, if any */
    if (fCreated)
    {
        if (fFirstSubFld)
        {
            hr = HrSetSubFolderProp(pifldParent, TRUE);
            if (hr != hrSuccess)
                goto exit;
        }

        ChangeTable(pims, pifldParent->peid, peid, MAPI_FOLDER,
            TABLE_ROW_ADDED, TRUE);
    }

    if (pfCreated)
        *pfCreated = fCreated;

    *ppifldNew = pifldNew;

exit:
    UlRelease(pmt);
    LMFree(&pims->lmr, peid);

    if (hr != hrSuccess)
        UlRelease(pifldNew);

    DebugTraceResult(HrCreateFolder, hr);
    return hr;
}

/*
 *  HrIsParent
 *
 *  Purpose
 *      Checks if peidParent is the parent of peidChild. Note that peidChild
 *      may be either a message or a folder entryid. Returns answer
 *      (TRUE or FALSE) in *pfIsParent.
 *
 *  Parameters
 *      peidParent      A pointer to the entryid of the potential parent.
 *      peidChild       A pointer to the entryid of the potential child.
 *      pfIsParent      A pointer to the location to return the answer.
 */
HRESULT HrIsParent(PEID peidParent, PEID peidChild, BOOL *pfIsParent)
{
    SCODE sc = S_OK;
    LPSTR szLastSlash;          /* pointer to last slash in Childs' path */
    BOOL fAnswer = FALSE;

LPSTR szTemp = NULL;
    ULONG cbPathCopy;

    if (!IsEqualMAPIUID(&(peidParent->uidResource), &(peidChild->uidResource)))
        goto exit;

    szLastSlash = SzFindLastCh(peidChild->szPath, '\\');
    if (szLastSlash == NULL)
    {
        /* if child is the root it can have no parent */
        /* otherwise, check if child lives in the root folder */

        if (*(peidChild->szPath) != '\0')
            fAnswer = (*(peidParent->szPath) == '\0');

        goto exit;
    }

    cbPathCopy = ((szLastSlash - peidChild->szPath) * sizeof(TCHAR))
        + sizeof(TCHAR);

    sc = ScAlloc(cbPathCopy, &szTemp);
    if (sc != S_OK)
        goto exit;

    memcpy(szTemp, peidChild->szPath, (UINT) cbPathCopy);
    szTemp[cbPathCopy - 1] = '\0';

    /* see if paths are the same */
    fAnswer = !lstrcmpi(peidParent->szPath, szTemp);

exit:
    if (sc == S_OK)
        *pfIsParent = fAnswer;

    FreeNull(szTemp);

    DebugTraceSc(HrIsParent, sc);
    return ResultFromScode(sc);
}

/*
 *  FIsAncestor
 *
 *  Purpose
 *      returns TRUE if pifldDescendent is a descendent of pifldAncestor
 *
 *  Parameters
 *      pifldAncestor       the ancestor(?) folder
 *      pifldDescendent     the descendent(?) folder
 *
 */
static BOOL FIsAncestor(PIFLD pifldAncestor, PIFLD pifldDescendent)
{
    PEID peidAncestor;          /* entryID of pifldAncestor */
    PEID peidDescendent;        /* entryID of pifldDescendent */
    LPTSTR szFirstOccurrence = NULL;    /* pointer to first occurrence in */

    /* pifldDescendent's path of */
    /* pifldAncestor's path */

    if (pifldAncestor == NULL || pifldDescendent == NULL)
        return FALSE;

    peidAncestor = pifldAncestor->peid;
    peidDescendent = pifldDescendent->peid;

    if (!IsEqualMAPIUID(&(peidAncestor->uidResource),
            &(peidDescendent->uidResource)))
        return FALSE;

    /* check for either one being the root */
    if (FIsRoot(peidAncestor))
        return TRUE;
    if (FIsRoot(peidDescendent))
        return FALSE;

    szFirstOccurrence = SzFindSz(peidDescendent->szPath,
        peidAncestor->szPath);

    return (BOOL) (szFirstOccurrence == peidDescendent->szPath);
}

/*
 *  HrFullToRelative
 *
 *  Purpose             given a full path name and store, returns the
 *                      root relative path.  Free with FreeNull.
 *
 *  Parameters
 *      szFullPath      the full path
 *      pims            the store object
 *      pszRelative     [out] pointer to the relative path
 *
 *  NB      memory is allocated using ScAlloc and should be freed
 *          with FreeNull
 */
HRESULT HrFullToRelative(LPTSTR szFullPath, PIMS pims, LPTSTR *pszRelative)
{
    ULONG cchStore;     /* length of the store path in tchars */
    ULONG cchFull;      /* length of the full path in tchars */
    SCODE sc;

    cchStore = lstrlen(pims->szStorePath);
    cchFull = lstrlen(szFullPath);

    Assert(cchFull >= cchStore);

    sc = ScAlloc((cchFull - cchStore + sizeof(TCHAR)), (PPV) pszRelative);

    if (sc != S_OK)
    {
        DebugTraceSc(HrFullToRelative, sc);
        return ResultFromScode(sc);
    }

    lstrcpy(*pszRelative, szFullPath + cchStore + 1);

    return hrSuccess;
}

/*
 *  HrFullPathName
 *
 *  Purpose         Allocate space for and return a pointer to the full path
 *                  name of the given object. Free with FreeNull.
 *
 *  Parameters
 *      szStorePath     full path to the store root
 *      szPath1         first portion of path past the store root.
 *                      May be NULL.
 *      szPath2         second portion of path to the object
 *                      whose full path name is needed, may be NULL.
 *      pszName         Pointer to a location to return the full pathname
 *
 *  Returns:
 *      HRESULT
 */
HRESULT HrFullPathName(LPTSTR szStorePath, LPTSTR szPath1, LPTSTR szPath2,
    LPTSTR *pszName)
{
    UINT cbPath1 = 0;           /* length in bytes of first portion of path */

    /* excluding NULL */
    UINT cbPath2 = 0;           /* length in bytes of second portion of path */

    /* excluding NULL */
    UINT cbStorePath = 0;
    SCODE sc;

    Assert(!IsBadWritePtr(pszName, sizeof(LPSTR)));
    Assert(!IsBadStringPtr(szStorePath, (UINT) -1));

    Assert(szPath1 == NULL || !IsBadStringPtr(szPath1, (UINT) -1));
    Assert(szPath2 == NULL || !IsBadStringPtr(szPath2, (UINT) -1));

    cbStorePath = lstrlen(szStorePath) * sizeof(TCHAR);

    if (szPath1 != NULL)
        cbPath1 = lstrlen(szPath1) * sizeof(TCHAR);

    if (szPath2 != NULL)
        cbPath2 = lstrlen(szPath2) * sizeof(TCHAR);

    /* allocate space for the path name */
    sc = ScAlloc(cbStorePath + cbPath1 + cbPath2 + (3 * sizeof(TCHAR)),
        (PPV) pszName);

    if (sc != S_OK)
    {
        DebugTraceSc(HrFullPathName, sc);
        return ResultFromScode(sc);
    }

    /* fill in the name */
    lstrcpy(*pszName, szStorePath);

    if (cbPath1 != 0)
    {
        lstrcat(*pszName, TEXT("\\"));
        lstrcat(*pszName, szPath1);
    }

    if (cbPath2 != 0)
    {
        lstrcat(*pszName, TEXT("\\"));
        lstrcat(*pszName, szPath2);
    }

    return hrSuccess;
}

/*
 -  HrNewEID
 -
 *  Purpose     Return the EID of a new object (folder or message)
 *
 *  Parameters
 *      pifld           Parent folder of object to receive new ID, NULL
 *                      means we need EID of the root folder
 *      pims            Store containing the new object
 *      szExtension     File extension of new object, including '.'
 *      lpulSeqNumber   pointer to sequence number of this entryID (optional)
 *      ppeid           Pointer to pointer to new entryID
 *
 *  Returns:
 *      HRESULT
 */
HRESULT HrNewEID(PIFLD pifld, PIMS pims, LPTSTR szExtension,
    ULONG *lpulSeqNumber, PEID *ppeid)
{
    PEID peidNew = NULL;
    LPSTR szNewPath = NULL;
    HRESULT hr = hrSuccess;
    MAPIUID uid;
    ULONG ulSeq;
    LPSTR szNewObjName = NULL;  /* name of new object */

    Assert(ppeid);
    Assert(pims);

    /* get the new name of the object, and a unique sequence number. */
    hr = HrUniqueFileName(pims, &ulSeq, &szNewObjName);
    if (hr != hrSuccess)
        goto exit;

    /* code in the sample store depends on the number of chars in the */
    /* file / folder name being OK. Verify that it is. */

    Assert(lstrlen(szNewObjName) == CCH_BASE);

    GetResourceUID(pims, &uid);

    if (pifld == NULL)
    {
        CHAR ch = '\0';

        /* Construct the EID for the root folder */
        hr = HrConstructEID(&uid, &pims->lmr, (LPSTR) &ch, &peidNew);
        if (hr != hrSuccess)
            goto exit;
    }
    else
    {
        ULONG cbPath;
        LPSTR szPath = NULL;
        SCODE sc;
        BOOL fIsRoot;

        /* Add 1 TCHAR for the NULL terminator */
        cbPath = (lstrlen(szNewObjName) + lstrlen(szExtension)) * sizeof(TCHAR)
            + sizeof(TCHAR);

        fIsRoot = FIsRoot(pifld->peid);

        if (fIsRoot == FALSE)
        {
            /* Add 1 TCHAR for the slash between the folder and the new name */
            /* Subtract 1 TCHAR because we don't need to count the NULL term */

            cbPath += CbEIDPath(pifld->peid) + sizeof(TCHAR) - sizeof(TCHAR);
        }

        /* allocate space for the new path */
        sc = ScAlloc(cbPath, &szPath);
        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        *szPath = 0;

        if (fIsRoot == FALSE)
        {
            lstrcpy(szPath, pifld->peid->szPath);
            lstrcat(szPath, TEXT("\\"));
        }

        lstrcat(szPath, szNewObjName);
        lstrcat(szPath, szExtension);

        hr = HrConstructEID(&uid, &pims->lmr, szPath, &peidNew);

        FreeNull(szPath);

        if (hr != hrSuccess)
            goto exit;
    }

    *ppeid = peidNew;

    if (lpulSeqNumber)
        *lpulSeqNumber = ulSeq;

    Assert(hr == hrSuccess);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (hr != hrSuccess)
        LMFree(&pims->lmr, peidNew);

    FreeNull(szNewObjName);

    DebugTraceResult(HrNewEID, hr);
    return hr;
}

/*
 * HrOpenPropertyMessage
 *
 *  Purpose         open the property message for the folder with entryid
 *                  pointed to peid
 *
 *  Parameters
 *      peid                pointer to folder's entryid
 *      pims                pointer to the message store object
 *      fModifyExclusive    TRUE if we want to open for read/write,
 *                          exclusive access.
 *      lppmsg              pointer to location to return the open message
 *                          object
 *
 *  Returns: Storage and IMSG errors.
 */
static HRESULT HrOpenPropertyMessage(PEID peid, PIMS pims, BOOL fModifyExclusive,
    LPMESSAGE *lppmsg)
{
    LPTSTR szPropFolderPathName = NULL;
    HRESULT hr;

    /* get full path to message file */
    hr = HrFullPathName(pims->szStorePath, peid->szPath, szPropertyFileName,
        &szPropFolderPathName);
    if (hr != hrSuccess)
        goto exit;

    hr = HrOpenIMsg(pims->pmsgsess, szPropFolderPathName, &pims->lmr,
        pims->psup, FALSE, fModifyExclusive, fModifyExclusive, lppmsg);

    if (hr != hrSuccess)
    {
        if (GetScode(hr) == MAPI_E_NOT_FOUND)
            hr = ResultFromScode(MAPI_E_OBJECT_DELETED);
        goto exit;
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    FreeNull(szPropFolderPathName);

    DebugTraceResult(HrOpenPropertyMessage, hr);
    return hr;
}

/*
 * HrOpenPropertyMessageRetry
 *
 *  Purpose         open the property message for the folder with entryid
 *                  pointed to peid. The function will retry if message is busy
 *                  for the specified number of times.
 *
 *  Parameters
 *      peid                pointer to folder's eid
 *      pims                pointer to the message store object
 *      fModifyExclusive    TRUE if we want to open for read/write,
 *                          exclusive access.
 *      lppmsg              pointer to location to return the open message
 *                          object
 *
 */
HRESULT HrOpenPropertyMessageRetry(PEID peid, PIMS pims,
    BOOL fModifyExclusive, LPMESSAGE *lppmsg)
{
    UINT iRetry;                /* number of attempts to open */
    HRESULT hr = hrSuccess;

    iRetry = 0;
    while (TRUE)
    {
        hr = HrOpenPropertyMessage(peid, pims, fModifyExclusive, lppmsg);

        if (GetScode(hr) != MAPI_E_NO_ACCESS || ++iRetry >= NUM_RETRIES)
            break;

        Sleep(500);
    }

    #ifdef DEBUG
    if (iRetry >= NUM_RETRIES)
        TraceSz("HrOpenPropertyMessageRetry: Failing open. Too many tries.");
    #endif

    DebugTraceResult(HrOpenPropertyMessageRetry, hr);
    return hr;
}

/*
 * HrGetFileModTime
 *
 * Gets the last write time of the file from the OS by calling
 * FindFirstFile, and retrieving it from the FIND_DATA.
 *
 * szStorePath: the path to the message store.
 * szFileName: the relative path to the file.
 * pft: a pointer to the location to return the FILETIME
 *
 */
HRESULT HrGetFileModTime(LPTSTR szStorePath, LPTSTR szFileName,
    FILETIME *pft)
{
    HRESULT hr;
    LPSTR szFullPath;

    hr = HrFullPathName(szStorePath, szFileName, NULL, &szFullPath);

    if (hr == hrSuccess)
    {
        HANDLE hFile;
        WIN32_FIND_DATA ffd;

        hFile = FindFirstFile(szFullPath, &ffd);

        if (hFile == FAILED_SEARCH)
            hr = ResultFromScode(MAPI_E_NOT_FOUND);
        else
        {
            *pft = ffd.ftLastWriteTime;
            FindClose(hFile);
        }

        FreeNull(szFullPath);
    }

    DebugTraceResult(HrGetFileModTime, hr);
    return hr;
}

/*
 *  HrFindFirstID
 *
 *  Purpose         returns in *ppeid the entryid of the
 *                  first object in pifldParent whose name is of the
 *                  form specified in szTemplate. The caller must free
 *                  storage with CloseIDSearch even in error,
 *                  except *ppeid is always freed with LMFree.
 *
 *  Parameters
 *      pifldParent     parent folder
 *      szTemplate      template local name for object to be found
 *      pichLocal       pointer to variable for position of start of local
 *                      name of the found object
 *      pszRRPath       pointer to root relative path of next object
 *      phFindFile      pointer to location to return search handle
 *      pffd            pointer to location to return Windows find file data
 *                      struct for use by HrFindNextID.
 *      ppeid           pointer to eid of found object
 *
 *  Returns:
 *      HRESULT
 */
HRESULT HrFindFirstID(PIFLD pifldParent, LPTSTR szTemplate, ULONG *pichLocal,
    LPTSTR *pszRRPath, HANDLE *phFindFile, WIN32_FIND_DATA *pffd, PEID *ppeid)
{
    HRESULT hr = hrSuccess;
    MAPIUID uidStore;           /* uid for message store */
    LPTSTR szSubObject = NULL;  /* template name of subobject */
    PEID peid = NULL;
    PLMR plmr;
    LPSTR szRRPath = NULL;
    ULONG ichLocal = 0;
    HANDLE hFindFile = 0;
    WIN32_FIND_DATA ffd;

    plmr = &pifldParent->pims->lmr;

    /* find the first subobject */
    hr = HrFullPathName(pifldParent->pims->szStorePath,
        pifldParent->peid->szPath, szTemplate, &szSubObject);
    if (hr != hrSuccess)
        goto exit;

    hFindFile = FindFirstFile(szSubObject, &ffd);

    if (hFindFile == FAILED_SEARCH)
    {
        hr = ResultFromScode(MAPI_E_NOT_FOUND);
        goto exit;
    }

    /* get components of entryids for messages in this folder */
    GetResourceUID(pifldParent->pims, &uidStore);

    /* get the root relative path */
    hr = HrAlloc(CbEIDPath(pifldParent->peid)
        + (CCH_NAME * sizeof(TCHAR)), (PPV) &szRRPath);
    if (hr != hrSuccess)
        goto exit;

    lstrcpy(szRRPath, pifldParent->peid->szPath);
    if (*szRRPath != (TCHAR) 0)
        lstrcat(szRRPath, TEXT("\\"));

    ichLocal = lstrlen(szRRPath);

    lstrcpy(szRRPath + ichLocal, ffd.cFileName);

    /* construct the ID */
    hr = HrConstructEID(&uidStore, plmr, szRRPath, &peid);
    /* fall through to exit */

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (hr != hrSuccess)
    {
        LMFree(plmr, peid);
        FreeNull(szRRPath);

        if (hFindFile && hFindFile != FAILED_SEARCH)
            FindClose(hFindFile);
    }
    else
    {
        *ppeid = peid;
        *pszRRPath = szRRPath;
        *phFindFile = hFindFile;
        *pichLocal = ichLocal;
        *pffd = ffd;
    }

    FreeNull(szSubObject);

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_E_NOT_FOUND)
        DebugTraceResult(HrFindFirstID, hr);
    #endif

    return hr;
}

/*
 *  HrFindNextID
 *
 *  Purpose         returns in ppeid the eid of the next object in the search
 *                  free *ppeid with LMFree.
 *
 *  Parameters
 *      pifldParent     parent folder
 *      ichLocal        position of start of local name of the found object
 *      szRRPath        name of found object from last search
 *      hFindFile       search handle
 *      pffd            [in/out] pointer to location to use and return
 *                      Windows find file data struct.
 *      ppeid           pointer to location to return eid of next file.
 *
 *  Returns:
 *      HRESULT
 */
HRESULT HrFindNextID(PIFLD pifldParent, ULONG ichLocal, LPTSTR szRRPath,
    HANDLE hFindFile, WIN32_FIND_DATA *lpFindFileData, PEID *ppeid)
{
    HRESULT hr = hrSuccess;
    MAPIUID uidStore;           /* resource uid of store */
    PLMR plmr;
    PEID peid = NULL;

    plmr = &pifldParent->pims->lmr;

    if (FindNextFile(hFindFile, lpFindFileData))
    {
        lstrcpy(szRRPath + ichLocal, lpFindFileData->cFileName);
        GetResourceUID(pifldParent->pims, &uidStore);

        /* construct the ID */
        hr = HrConstructEID(&uidStore, plmr, szRRPath, &peid);
    }
    else
        hr = ResultFromScode(MAPI_E_NOT_FOUND);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (hr != hrSuccess)
        LMFree(plmr, peid);
    else
        *ppeid = peid;

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_E_NOT_FOUND)
        DebugTraceResult(HrFindNextID, hr);
    #endif

    return hr;
}

/*
 *  CloseIDSearch
 *
 *  Purpose
 *      Calls FindClose on the search handle, and FreeNull on the root-relative
 *      pathname given as arguments. After cleaning up these values, the
 *      function also invalidates the pointers.
 *
 *  Parameters
 *      lphFindFile     pointer to search handle, returned with FAILED_SEARCH.
 *      pszRRPath       pointer to name of found object from last search,
 *                      returned with NULL.
 *
 *  Returns: void.
 */
void CloseIDSearch(HANDLE *lphFindFile, LPTSTR *pszRRPath)
{
    if (lphFindFile && *lphFindFile && *lphFindFile != FAILED_SEARCH)
    {
        FindClose(*lphFindFile);
        *lphFindFile = FAILED_SEARCH;
    }

    FreeNull(*pszRRPath);
    *pszRRPath = NULL;
}

/*
 *  HrNewIFLD
 *
 *  Purpose:
 *      Allocates and initializes new instance data for a folder
 *
 *  Parameters
 *      peid            EID of folder
 *      pims            address of the message store for this folder
 *      fModify         TRUE if caller wants to modify, FALSE otherwise
 *      ppifld          pointer to newly created instance data
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Allocates memory for IFLD object.  Freed by calling Release.
 *
 */
HRESULT HrNewIFLD(PEID peid, PIMS pims, BOOL fModify, PIFLD *ppifld)
{
    SCODE sc;
    PIFLD pifld = NULL;         /* pointer to new instance data */

    Assert(peid);
    Assert(pims);
    Assert(ppifld);

    /* Don't open the folder object unless the directory and the property */
    /* file are present on disk. */

    if (!FFolderExists(peid, pims))
    {
        sc = MAPI_E_NOT_FOUND;
        goto exit;
    }

    /* Allocate memory for the instance data */
    sc = LMAllocZ(&pims->lmr, sizeof(IFLD), &pifld);
    if (sc != S_OK)
        goto exit;

    /* allocate memory to hold the folder's eid */
    sc = ScAllocZ(CbEID(peid), &pifld->peid);
    if (sc != S_OK)
        goto exit;

    Assert(CbEIDPath(peid) == (UINT) Cbtszsize(peid->szPath));

    OBJ_Initialize(pifld, &vtblIFLD, OT_FOLDER, pims, pims->pcs);

    memcpy(pifld->peid, peid, CbEID(peid));

    if (fModify)
        OBJ_SetFlag(pifld, OBJF_MODIFY);

    OBJ_Enqueue((POBJ) pifld, (POBJ) pims);

    *ppifld = pifld;

exit:
    AssertSz(sc == S_OK || FAILED(sc), "No warning expected");

    if (sc != S_OK && pifld)
    {
        FreeNull(pifld->peid);
        LMFree(&pims->lmr, pifld);
    }

    DebugTraceSc(HrNewIFLD, sc);
    return ResultFromScode(sc);
}

/*
 * FFolderExists
 *
 * Check for the existence of the folder's property file inside the folder
 * passed in. If the file isn't there or we can't get to it, return FALSE.
 * If the file is there, return TRUE.
 *
 * Parameters:
 *      peid -- The entryid of the folder to test.
 *      pims -- a pointer to the message store object.
 *
 * Returns: TRUE if the folder's property file is found.
 *          FALSE in all other cases.
 *
 */
BOOL FFolderExists(PEID peid, PIMS pims)
{
    LPTSTR  szPropFolderPathName = NULL;
    HRESULT hr;
    BOOL    fFolderValid = FALSE;

    /* get full path to message file */
    hr = HrFullPathName(pims->szStorePath, peid->szPath, szPropertyFileName,
        &szPropFolderPathName);

    if (hr == hrSuccess)
    {
        HANDLE hFile;
        WIN32_FIND_DATA ffd;

        hFile = FindFirstFile(szPropFolderPathName, &ffd);

        if (hFile != FAILED_SEARCH)
        {
            fFolderValid = TRUE;  /* it's there! */
            FindClose(hFile);
        }

        FreeNull(szPropFolderPathName);
    }

    return fFolderValid;
}

/*
 -  HrCreateFolderStorage
 -
 *  Purpose:
 *      Create a new folder directory and its property file
 *
 *  Parameters
 *      pifld               the parent folder of the newly created folder
 *                          If NULL, the new folder is the root folder.
 *      ulFolderType        type of folder to be created
 *      szFolderName        name of the new folder
 *      szFolderComment     comment string for the new folder
 *      fCreateDir          TRUE means create directory for folder
 *      pims                pointer to store in which new folder resides
 *      ppeid               pointer to entryID of new folder
 *
 *  Returns:
 *
 *  Side effects:   Sets PR_COMMENT property to szFolderComment.
 *
 *  Errors:
 *
*/
HRESULT HrCreateFolderStorage(PIFLD pifld, ULONG ulFolderType,
    LPSTR szFolderName, LPSTR szFolderComment, BOOL fCreateDir,
    PIMS pims, PEID *ppeid)
{
    HRESULT hr;
    PEID peid = NULL;
    ULONG ulSeqNumber;          /* sequence number of new folder*/
    SPropValue pvalRO[NUM_RO_FOLDER_PROPS];
    SPropValue pvalRW[NUM_RW_FOLDER_PROPS];
    ULONG cSet;
    LPSPropProblemArray pprba = NULL;
    LPMESSAGE lpmsgProp = NULL; /* property message for this folder */
    BOOL fDirCreated = FALSE;
    BOOL fFolderPropFileCreated = FALSE;

    LPTSTR szNewDirectoryName = NULL;
    LPTSTR szPropertyFolderName = NULL;

    Assert(pims);
    Assert(ppeid);

    hr = HrNewEID(pifld, pims, FOLDER_EXT, &ulSeqNumber, &peid);
    if (hr != hrSuccess)
        goto exit;

    /* create new directory for the new folder object */
    hr = HrFullPathName(pims->szStorePath, peid->szPath, NULL,
        &szNewDirectoryName);
    if (hr != hrSuccess)
        goto exit;

    if (fCreateDir)
    {
        if (!CreateDirectory(szNewDirectoryName, NULL))
        {
            hr = ResultFromScode(MAPI_E_NO_ACCESS);
            goto exit;
        }
        fDirCreated = TRUE;
    }

    hr = HrFullPathName(pims->szStorePath, peid->szPath,
        szPropertyFileName, &szPropertyFolderName);
    if (hr != hrSuccess)
        goto exit;

    /* create and open the message object for properites */
    hr = HrOpenIMsg(pims->pmsgsess, szPropertyFolderName, &pims->lmr,
        pims->psup, TRUE, TRUE, TRUE, &lpmsgProp);

    if (hr != hrSuccess)
        goto exit;

    fFolderPropFileCreated = TRUE;

    /* set the initial read only properties */

    pvalRO[0].ulPropTag = sptaReadOnly.aulPropTag[0];
    pvalRO[1].ulPropTag = sptaReadOnly.aulPropTag[1];
    pvalRO[2].ulPropTag = sptaReadOnly.aulPropTag[2];
    pvalRO[3].ulPropTag = sptaReadOnly.aulPropTag[3];
    pvalRO[4].ulPropTag = sptaReadOnly.aulPropTag[4];
    pvalRO[5].ulPropTag = sptaReadOnly.aulPropTag[5];
    pvalRO[6].ulPropTag = sptaReadOnly.aulPropTag[6];
    pvalRO[7].ulPropTag = sptaReadOnly.aulPropTag[7];
    pvalRO[8].ulPropTag = sptaReadOnly.aulPropTag[8];
    pvalRO[9].ulPropTag = sptaReadOnly.aulPropTag[9];
    pvalRO[10].ulPropTag = sptaReadOnly.aulPropTag[10];

    pvalRO[0].Value.l = MAPI_FOLDER;
    pvalRO[1].Value.bin.cb = sizeof(ULONG);
    pvalRO[1].Value.bin.lpb = (BYTE *) &ulSeqNumber;
    pvalRO[2].Value.l = (pifld == NULL) ? FOLDER_ROOT : FOLDER_GENERIC;
    pvalRO[3].Value.l = 0;
    pvalRO[4].Value.l = 0;

    pvalRO[5].Value.bin.cb = pims->eidStore.cb;
    pvalRO[5].Value.bin.lpb = pims->eidStore.lpb;

    pvalRO[6].Value.bin.cb = sizeof(pims->uidResource);
    pvalRO[6].Value.bin.lpb = (LPBYTE) &pims->uidResource;

    pvalRO[7].Value.b = FALSE;

    /* Set PR_ENTRYID, PR_PARENT_ENTRYID and PR_INSTANCE_KEY to null */
    /* strings to keep clients from writing over them. */
    /* We get the actual values internally. */

    pvalRO[8].Value.bin.cb = 1;
    pvalRO[8].Value.bin.lpb = (LPBYTE) "";

    pvalRO[9].Value.bin.cb = 1;
    pvalRO[9].Value.bin.lpb = (LPBYTE) "";

    pvalRO[10].Value.bin.cb = 1;
    pvalRO[10].Value.bin.lpb = (LPBYTE) "";

    /* set these read only props */
    hr = lpmsgProp->lpVtbl->SetProps(lpmsgProp, NUM_RO_FOLDER_PROPS,
        pvalRO, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    Assert(sptaReadOnly.cValues == NUM_RO_FOLDER_PROPS);
    Assert(spaReadOnly.cValues == NUM_RO_FOLDER_PROPS);

    hr = SetAttribIMsgOnIStg(lpmsgProp, (LPSPropTagArray) &sptaReadOnly,
        (LPSPropAttrArray) &spaReadOnly, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    /* set the read write properties */

    cSet = NUM_RW_FOLDER_PROPS;

    pvalRW[0].ulPropTag = PR_DISPLAY_NAME;

    /* must be last since it is optional */
    pvalRW[1].ulPropTag = PR_COMMENT;

    pvalRW[0].Value.LPSZ = szFolderName;
    if (szFolderComment != NULL)
        pvalRW[1].Value.LPSZ = szFolderComment;
    else
        cSet--;

    /* set the ReadWrite properties */

    hr = lpmsgProp->lpVtbl->SetProps(lpmsgProp, cSet, pvalRW, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    hr = lpmsgProp->lpVtbl->SaveChanges(lpmsgProp, 0);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(lpmsgProp);

    /* free the property problem structure */
    if (pprba)
    {
        LMFree(&pims->lmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    if (hr != hrSuccess)
    {
        LMFree(&pims->lmr, peid);

        if (fFolderPropFileCreated == TRUE)
            DeleteFile(szPropertyFolderName);

        if (fDirCreated == TRUE)
            RemoveDirectory(szNewDirectoryName);
    }
    else
        *ppeid = peid;

    FreeNull(szNewDirectoryName);
    FreeNull(szPropertyFolderName);

    DebugTraceResult(HrCreateFolderStorage, hr);
    return hr;
}

/*
 *  HrIncrementOneROProp
 *
 *  Purpose     increment the read only property by delta. Only works on
 *              properties of type PT_LONG.
 *
 *  Argument    pifld   pointer to the folder object
 *              lDelta  size of increment
 *              ulPT    the property tag to be changed
 */
HRESULT HrIncrementOneROProp(PIFLD pifld, LONG lDelta, ULONG ulPT)
{
    LONG lValue;
    HRESULT hr;
    LPMESSAGE lpmsg = NULL; /* property message for pifld */
    PLMR plmr = &pifld->pims->lmr;

    AssertSz1(PROP_TYPE(ulPT) == PT_LONG,
        "Trying to increment property %s; not PT_LONG",
        SzDecodeUlPropTag(ulPT));

    /* open the property message exclusively */
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
        TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    /* get the current value of the properties */

    hr = HrGetSingleProp((LPMAPIPROP) lpmsg, plmr, ulPT, &lValue);
    if (hr != hrSuccess)
        goto exit;

    lValue += lDelta;

    /* reset the new value */

    hr = HrSetOneROProp(lpmsg, plmr, ulPT, &lValue);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(lpmsg);

    DebugTraceResult(HrIncrementOneROProp, hr);
    return hr;
}

/*
 *  HrSetOneROFolderProp
 *
 *  Purpose     set the read only folder property to the value given. The
 *              property must be of type PT_LONG.
 *
 *  Argument    pifld   pointer to the folder object
 *              lValue  value to set the property to.
 *              ulPT    the property tag to be changed
 */
static HRESULT HrSetOneROFolderProp(PIFLD pifld, LONG lValue, ULONG ulPT)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL; /* property message for pifld */
    PLMR plmr = &pifld->pims->lmr;

    AssertSz1(PROP_TYPE(ulPT) == PT_LONG,
        "Trying to increment property %s; not PT_LONG",
        SzDecodeUlPropTag(ulPT));

    /* open the property message exclusively */
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
        TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    /* reset the new value */

    hr = HrSetOneROProp(lpmsg, plmr, ulPT, &lValue);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(lpmsg);

    DebugTraceResult(HrSetOneROFolderProp, hr);
    return hr;
}

/*
 *  HrSetSubFolderProp
 *
 *  Purpose     set the PR_SUBFOLDERS property
 *
 *  Argument    pifld       pointer to the folder object
 *              fSubFolder  the value to write into the PR_SUBFOLDER property
 */
static HRESULT HrSetSubFolderProp(PIFLD pifld, BOOL fSubFolder)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL; /* property message for pifld */
    PIFLD pifldParent = NULL;
    PLMR plmr = &pifld->pims->lmr;
    PEID peid = pifld->peid;

    /* open the property message exclusively */
    hr = HrOpenPropertyMessageRetry(peid, pifld->pims, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    /* set the new value */

    hr = HrSetOneROProp(lpmsg, plmr, PR_SUBFOLDERS, &fSubFolder);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);
    if (hr != hrSuccess)
        goto exit;

    UlRelease(lpmsg);
    lpmsg = NULL;

    /* If this isn't the root folder, get the parent entryid, and call */
    /* ChangeTable so that any new properties are updated in its */
    /* hierarchy table row. */

    if (FIsRoot(peid) == FALSE)
    {
        PEID peidParent = NULL;
        PIMS pims = pifld->pims;

        hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
        if (hr == hrSuccess)
        {
            ChangeTable(pims, peidParent, peid, MAPI_FOLDER,
                TABLE_ROW_MODIFIED, TRUE);
            LMFree(&pims->lmr, peidParent);
        }
        else
        {
            TraceSz1("Sample MS: HrSetSubFolderProp: failed to change "
                "hierarchy table. sc == %s\n", SzDecodeScode(GetScode(hr)));
            hr = hrSuccess;
        }
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(lpmsg);
    UlRelease(pifldParent);

    DebugTraceResult(HrSetSubFolderProp, hr);
    return hr;
}

/*
 *  ChangeTable
 *
 * Purpose
 *  Changes all contents or hierarchy tables in all open folders to reflect
 *  the change specified. Also updates the folder's content and unread counts.
 *  Never updates the table on disk. That will happen when the table is
 *  read-in and verified.
 *
 *  Parameters
 *
 *      pims            Pointer to the message store object.
 *      peidTable       EID of the parent folder of the table to change
 *      peidObject      the object that has been added or deleted or modified.
 *                      May be NULL if ulTableEvent is TABLE_CHANGED. This
 *                      means multiple items changed.
 *      ulObjType       MAPI_MESSAGE (for contents tables) or MAPI_FOLDER
 *                      (for hierarchy tables)
 *      ulTableEvent    either TABLE_ROW_ADDED, DELETED, MODIFIED, or
 *                      TABLE_CHANGED (TABLE_CHANGED for contents tables only)
 *      fSendNotif      TRUE if this routine should send notifications to
 *                      other processes about this change.
 *
 */
void ChangeTable(PIMS pims, PEID peidTable, PEID peidObject,
    ULONG ulObjType, ULONG ulTableEvent, BOOL fSendNotif)
{
    HRESULT hr = hrSuccess;
    ULONG cbEIDTable;
    LPSPropTagArray pPTA;       /* proptags of column headings */
    POBJ pobj;

    Assert(pims);
    Assert(peidTable);
    AssertSz1(ulTableEvent == TABLE_ROW_ADDED
        || ulTableEvent == TABLE_ROW_MODIFIED
        || ulTableEvent == TABLE_ROW_DELETED
        || ulTableEvent == TABLE_CHANGED, "Bad ulTableEvent %08lX",
        ulTableEvent);
    AssertSz1(ulObjType == MAPI_MESSAGE || ulObjType == MAPI_FOLDER,
        "Bad ulObjType %08lX", ulObjType);

    /*
     * Look for all open tables within this process. We find them
     * by search the open object chain for folders whose entryids match
     * the eid of the folder passed in. We only update open tables.
     * Therefore, don't bother checking objects that aren't folders or
     * folder objects without open tables.
     */
    cbEIDTable = CbEID(peidTable);

    for (pobj = pims->pobjHead; pobj != NULL; pobj = pobj->pobjNext)
    {
        PIFLD pifld;
        ULONG ulTheSame;
        LPTABLEDATA lptbl;
        FILETIME ft;

        if (pobj->wType != OT_FOLDER)
            continue;

        pifld = (PIFLD) pobj;

        if (ulObjType == MAPI_MESSAGE)
        {
            pPTA = (LPSPropTagArray) &sPropTagsContents;
            lptbl = pifld->lptblContents;
        }
        else
        {
            pPTA = (LPSPropTagArray) &sPropTagsHierarchy;
            lptbl = pifld->lptblHierarchy;
        }

        if (lptbl == NULL)
            continue;

        hr = pims->lpVtbl->CompareEntryIDs(pims, cbEIDTable,
                (LPENTRYID) peidTable, CbEID(pifld->peid),
                (LPENTRYID) pifld->peid, 0L, &ulTheSame);
        if (hr != hrSuccess)
            goto exit;

        if (!ulTheSame)
            continue;

        switch (ulTableEvent)
        {
        case TABLE_CHANGED:
            if (ulObjType != MAPI_MESSAGE)
            {
                TrapSz("ChangeTable doesn't handle TABLE_CHANGED on"
                    " hierarchy tables");
                goto exit;
            }

            /* Don't update the file on disk. */
            hr = HrSyncContentsTable(pifld, FALSE);
            if (hr != hrSuccess)
                goto exit;
            break;

        case TABLE_ROW_DELETED:
            hr = HrRemoveRow(lptbl, peidObject);
            if (hr != hrSuccess)
            {
                if (GetScode(hr) == MAPI_E_NOT_FOUND)
                    hr = hrSuccess;
                else
                    goto exit;
            }
            break;

        case TABLE_ROW_ADDED:
        case TABLE_ROW_MODIFIED:
            hr = HrGetFileModTime(pims->szStorePath, peidObject->szPath, &ft);
            if (hr != hrSuccess)
                goto exit;

            hr = HrUpdateRow(pims, lptbl, peidObject, pPTA, &ft, ulObjType);
            if (hr != hrSuccess)
                goto exit;
            break;

        default:
            /* We've already asserted this above. */
            goto exit;
        }
    }

    if (fSendNotif)
        hr = HrSendNotif(pims, peidTable, peidObject, ulTableEvent, ulObjType);

exit:
    if (hr != hrSuccess)
        TraceSz1("SampleMS: ChangeTable: failed to update "
            "table. Error %s.\n", SzDecodeScode(GetScode(hr)));

    return;
}

/*
 *  HrDestroyFolderStorage
 *
 *  Purpose         Removes storage associated with a folder
 *
 *  Parameters
 *
 *      szFullPath  full path name of the folder to be removed
 *
 */
HRESULT HrDestroyFolderStorage(LPTSTR szFullPath)
{
    HRESULT hr;
    LPTSTR szAll = NULL;    /* path to all files in the folder */
    HANDLE hFile;
    WIN32_FIND_DATA ffd;

    /* Find all files in the directory, and attempt to delete them. */
    /* Note that if somehow a subdirectory was created in this directory, */
    /* this function will fail. The loop below (that deletes files) goes */
    /* ahead regardless of error until it has tried to delete all files. */
    /* The only fatal error occurs if we can't remove the directory itself. */

    hr = HrAppendPath(szFullPath, szAllFilesTemplate, &szAll);
    if (hr != hrSuccess)
        goto exit;

    hFile = FindFirstFile(szAll, &ffd);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        while (TRUE)
        {
            LPTSTR szTemp = NULL;

            /* Don't even attempt to delete directories. The directories */
            /* named "." and ".." are always included in the contents of */
            /* a directory listing, and this avoids the attempt to delete */
            /* them. */

            if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            {
                if (HrAppendPath(szFullPath, ffd.cFileName, &szTemp)
                    == hrSuccess)
                {
                    DeleteFile(szTemp);
                    FreeNull(szTemp);
                }
            }

            if (!FindNextFile(hFile, &ffd))
            {
                FindClose(hFile);
                break;
            }
        }
    }

    /* Attempt to delete the folder itself. */
    if (!RemoveDirectory(szFullPath))
        hr = ResultFromScode(MAPI_E_NO_ACCESS);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    FreeNull(szAll);

    DebugTraceResult(HrDestroyFolderStorage, hr);
    return hr;
}

/*
 *  HrFillHierarchyTable
 *
 *  Purpose     Construct table data for hierarchy below the folder pifld
 *
 *  Argument
 *              pifld       pointer to the folder to be added to
 *                          the hierarchy table.
 *              lptbl       underlying table data
 *
 */
static HRESULT HrFillHierarchyTable(PIFLD pifld, LPTABLEDATA lptbl)
{
    HRESULT hr;
    PEID peidSub = NULL;
    LPTSTR szSubFolder = NULL;  /* template for subfolder of szFolder */
    HANDLE hFindFile = FAILED_SEARCH;
    ULONG ichLocal;
    WIN32_FIND_DATA ffd;
    PIMS pims = pifld->pims;

    /* build a hierarchy entry for each subfolder */
    hr = HrFindFirstID(pifld, szFolderTemplate, &ichLocal,
        &szSubFolder, &hFindFile, &ffd, &peidSub);

    while (hr == hrSuccess)
    {
        /* build hierarchy entry for this subfolder */
        hr = HrUpdateRow(pims, lptbl, peidSub,
            (LPSPropTagArray) &sPropTagsHierarchy,
            &(ffd.ftLastWriteTime), MAPI_FOLDER);
        if (hr != hrSuccess)
            goto exit;

        LMFree(&pims->lmr, peidSub);
        peidSub = NULL;

        hr = HrFindNextID(pifld, ichLocal, szSubFolder,
            hFindFile, &ffd, &peidSub);
    }

    if (GetScode(hr) == MAPI_E_NOT_FOUND)
        hr = hrSuccess;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    /* close the search */
    CloseIDSearch(&hFindFile, &szSubFolder);
    LMFree(&pims->lmr, peidSub);

    DebugTraceResult(HrFillHierarchyTable, hr);
    return hr;
}

/*
 * HrDuplicateIFLD
 *
 *  Searches the existing subfolders of pifldParent for a folder with
 *  PR_DISPLAY_NAME of szName. If it finds a match, opens it and returns
 *  the matching folder (doing nothing else). If no folder matches, then
 *  the routine creates a new folder and copies all properties from the
 *  old folder into the new folder.
 *
 *      pifld           the old folder
 *      pifldParent     parent of the new folder
 *      szName          name of new folder
 *      szComment       comment for new folder
 *      ppifldNew       pointer to the location to return the new folder
 *
 */
static HRESULT HrDuplicateIFLD(PIFLD pifld, PIFLD pifldParent, LPTSTR szName,
    LPTSTR szComment, PIFLD *ppifldNew)
{
    HRESULT hr = hrSuccess;
    BOOL fCreated = FALSE;
    PIFLD pifldNew = NULL;

    /* create new folder */

    hr = HrCreateFolder(pifldParent, szName, szComment, TRUE, &pifldNew,
        &fCreated);
    if (hr != hrSuccess)
        goto exit;

    /* copy pifld's properties over except excluded stuff (see */
    /* sptaExclFldProps definition). */

    if (fCreated)
    {
        LPSPropProblemArray pprba = NULL;
        LPMESSAGE lpmsgPropSrc = NULL;  /* pifld's property message */
        LPMESSAGE lpmsgPropNew = NULL;  /* new ifld's property message */

        hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
                &lpmsgPropSrc);

        if (hr == hrSuccess)
            hr = HrOpenPropertyMessageRetry(pifldNew->peid,
                    pifldNew->pims, TRUE, &lpmsgPropNew);

        if (hr == hrSuccess)
            hr = lpmsgPropSrc->lpVtbl->CopyTo(lpmsgPropSrc, 0, NULL,
                    (LPSPropTagArray) &sptaExclFldProps, 0, NULL,
                    (LPIID) &IID_IMessage, lpmsgPropNew, 0L, &pprba);

        UlRelease(lpmsgPropSrc);
        UlRelease(lpmsgPropNew);

        if (pprba)
        {
            Assert(hr == hrSuccess);
            LMFree(&pifld->pims->lmr, pprba);
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
        }
    }

    if (hr != hrSuccess)
        goto exit;

    *ppifldNew = pifldNew;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    DebugTraceResult(HrDuplicateIFLD, hr);
    return hr;
}

/*
 -  HrGetSortOrder
 -
 *  Purpose:
 *      returns in *lppsSortOrder the order of contents tables.
 *      Caller must use MAPIFreeBuffer to release *lppsSortOrder.
 *
 *  Parameters
 *       pifld          folder whose contents table is referred to
 *       lppsSortOrder  pointer to sort order variable
 *
 *  Returns:
 *      HRESULT
 *
 */
static HRESULT HrGetSortOrder(PIFLD pifld, LPSSortOrderSet *lppsSortOrder)
{
    ULONG cValues;              /* number of property values returned */
    LPSPropValue pval = NULL;   /* returned property array */
    HRESULT hr;
    ULONG cbSos;                /* number of bytes in sort order */
    SCODE sc;
    static SizedSPropTagArray(1, sptaSortOrder) =
    {
        1,
        {
            PR_SMS_CONTENTS_SORT_ORDER
        }
    };

    Assert(pifld);
    Assert(lppsSortOrder);

    /* get the sort order property */
    hr = pifld->lpVtbl->GetProps(pifld,
        (LPSPropTagArray) &sptaSortOrder, 0, /* ansi */
        &cValues, &pval);

    if (hr != hrSuccess)
    {
        Assert(HR_FAILED(hr) || GetScode(hr) == MAPI_W_ERRORS_RETURNED);
        cbSos = CbSSortOrderSet((LPSSortOrderSet) &sSortOrderContentsDefault);

        /* sort order is the default */
        sc = LMAlloc(&pifld->pims->lmr, cbSos, lppsSortOrder);
        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        memcpy(*lppsSortOrder, &sSortOrderContentsDefault, (UINT) cbSos);
    }
    else
    {
        /* This property should contain a flattened array of */
        /* SSortOrder structures. */

        Assert(cValues == 1L);
        cbSos = sizeof(ULONG) * pval->Value.MVl.cValues;

        sc = LMAlloc(&pifld->pims->lmr, offsetof(SSortOrderSet, aSort) + cbSos,
            lppsSortOrder);
        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        (*lppsSortOrder)->cSorts = pval->Value.MVl.cValues / 2;
        (*lppsSortOrder)->cCategories = 0;
        (*lppsSortOrder)->cExpanded = 0;
        memcpy((*lppsSortOrder)->aSort, pval->Value.MVl.lpl, (UINT) cbSos);
    }
    hr = hrSuccess;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    LMFree(&pifld->pims->lmr, pval);
    DebugTraceResult(HrGetSortOrder, hr);
    return hr;
}

/*
 -  DestroyMessageList
 -
 *  Purpose:
 *      frees allocated memory inside lppEntryList. The cValues field
 *      inside the entrylist must contain the count of the number of
 *      entryids that are in the entrylist so that the entryids can be
 *      freed.
 *
 *  Parameters
 *       lppEntryList   pointer to a list of entryids; returned NULL
 *
 */
static void DestroyMessageList(PLMR plmr, LPENTRYLIST *lppEntryList)
{
    Assert(lppEntryList);

    if (*lppEntryList != NULL && (*lppEntryList)->lpbin != NULL)
    {
        ULONG cValues = (*lppEntryList)->cValues;
        LPSBinary lpbin = (*lppEntryList)->lpbin;

        Assert(cValues <= UINT_MAX / sizeof(SBinary));
        Assert(!IsBadReadPtr(lpbin, (UINT) cValues * sizeof(SBinary)));

        for (; cValues; cValues--, lpbin++)
        {
            Assert(lpbin->cb <= UINT_MAX);
            Assert(!IsBadReadPtr(lpbin->lpb, (UINT) lpbin->cb));
            LMFree(plmr, lpbin->lpb);
        }

        FreeNull(*lppEntryList);
    }

    *lppEntryList = NULL;
    return;
}

/*
 -  HrCreateMessageList
 -
 *  Purpose:
 *      Counts up and returns a list of entryids for every message in
 *      the given folder. If no messages are found in the folder, returns
 *      NULL instead of the list. To get the count, this routine uses
 *      FindFirstFile, FindNextFile on all files with a ".msg" extension.
 *      We use PR_CONTENT_COUNT to get an estimate of how many msgs are in
 *      the folder, but don't depend on that property being correct. If
 *      the property is incorrect, this routine updates it (SIDE EFFECT)
 *      before returning to the caller.
 *
 *  Parameters
 *       pifld          the folder object
 *       lppEntryList   [out] pointer to the location to return a list of
 *                      entryids of messages in pifld.
 *
 */
static HRESULT HrCreateMessageList(PIFLD pifld, LPENTRYLIST *lppEntryList)
{
    HRESULT hr;
    LONG cMsgsOrig;             /* value of PR_CONTENT_COUNT property */
    LONG cMsgsAlloced;          /* number of msgs allocated in pent->lpbin */
    ULONG ichLocal;             /* start of message name in full path name */
    LPSBinary lpbin;
    LPSBinary lpbinMac;

    PEID peidNext = NULL;       /* next entryID to be added to the list */
    WIN32_FIND_DATA ffd;
    LPTSTR szFile = NULL;
    HANDLE hFindFile = FAILED_SEARCH;
    LPENTRYLIST pent = NULL;

    /* get the number of messages in pifld */
    /* This value may be incorrect, so use it to simply get an idea */
    /* of how many messages are in the folder. Allocate extra space */
    /* for extra (uncounted) messages, and, if necessary, realloc */
    /* the array until we really count all of them. After we've counted */
    /* all messages, update PR_CONTENT_COUNT with the correct number. */
    /* Assuming that there are extra messages in the folder avoids having */
    /* to realloc the array as often when there actually are extra ones. */

#define CMSGS_EXTRA     10  /* # of extra spaces to alloc in msg array */

    hr = HrGetSingleProp((LPMAPIPROP) pifld, &pifld->pims->lmr,
            PR_CONTENT_COUNT, &cMsgsOrig);

    /* Don't allow the failure to get PR_CONTENT_COUNT to keep this call */
    /* from succeeding. Go ahead and do the call anyway. */

    if (hr != hrSuccess)
    {
        hr = hrSuccess;

        /* We failed to get the property. Set the "original value" */
        /* to an impossible value so that we will attempt to update it */
        /* at the bottom of this function. */

        cMsgsOrig = -1;
        cMsgsAlloced = CMSGS_EXTRA;
    }
    else
        cMsgsAlloced = cMsgsOrig + CMSGS_EXTRA;

    /* Allocate space for the entrylist. */

    hr = HrAlloc(sizeof(SBinaryArray) + (cMsgsAlloced * sizeof(SBinary)),
        &pent);
    if (hr != hrSuccess)
        goto exit;

    /* We allocated the space all at once; therefore, we initialize the */
    /* lpbin pointer to point just past the binary array. */

    pent->lpbin = (LPSBinary) ((LPBYTE) pent + sizeof(SBinaryArray));

    /* The cValues field of the entrylist must be kept up-to-date as we */
    /* add entryids to the entrylist. DestroyMessageList (see above) uses */
    /* that field when it frees up the entryids, and if it is wrong, the */
    /* wrong number of entryids will get freed (resulting in a crash if */
    /* it is too big, or a memory leak if it is too small). */

    pent->cValues = 0;

    lpbin = pent->lpbin;
    lpbinMac = lpbin + cMsgsAlloced;

    /* get the first entryID */
    hr = HrFindFirstID(pifld, szMessageTemplate, &ichLocal,
        &szFile, &hFindFile, &ffd, &peidNext);

    if (hr != hrSuccess)
    {
        /* MAPI_E_NOT_FOUND means that there are no more messages in */
        /* the folder. This is not an error. Because this was the first */
        /* file, there were no messages in the folder at all. Therefore, */
        /* free any allocated memory, reset hr, and get out. */

        if (GetScode(hr) == MAPI_E_NOT_FOUND)
        {
            DestroyMessageList(&pifld->pims->lmr, &pent);

            /* pent should be NULL now. We're going to return it to */
            /* our caller below. */

            AssertSz(pent == NULL, "DestroyMessageList is broken.");

            hr = hrSuccess;
        }

        goto exit;
    }

    while (TRUE)
    {
        lpbin->cb = CbEID(peidNext);
        lpbin->lpb = (LPBYTE) peidNext;

        ++(pent->cValues);

        hr = HrFindNextID(pifld, ichLocal, szFile, hFindFile, &ffd, &peidNext);

        if (hr != hrSuccess)
        {
            /* MAPI_E_NOT_FOUND means that there are no more messages in */
            /* the folder. This is not an error. */

            if (GetScode(hr) == MAPI_E_NOT_FOUND)
                hr = hrSuccess;

            goto exit;
        }

        lpbin++;

        if (lpbin >= lpbinMac)
        {
            /* We need to realloc the array. */
            cMsgsAlloced += CMSGS_EXTRA;

            hr = HrRealloc((cMsgsAlloced * sizeof(SBinary))
                + sizeof(SBinaryArray), pent, &pent);
            if (hr != hrSuccess)
                goto exit;

            /* reset the pointer to the end of the array. */
            lpbinMac = pent->lpbin + cMsgsAlloced;
        }
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    /* close the search */
    CloseIDSearch(&hFindFile, &szFile);

    if (hr == hrSuccess)
    {
        LONG cMsgsFound = 0;

        /* If PR_CONTENT_COUNT was incorrect, then update it. Note that */
        /* the number of messages we found is kept in pent->cValues unless */
        /* we didn't find any messages (in which case, pent is NULL, and */
        /* we assume no messages were found). */

        if (pent)
            cMsgsFound = pent->cValues;

        if (cMsgsFound != cMsgsOrig)
            (void) HrSetOneROFolderProp(pifld, cMsgsFound, PR_CONTENT_COUNT);

        *lppEntryList = pent;
    }
    else if (pent)
        DestroyMessageList(&pifld->pims->lmr, &pent);

    DebugTraceResult(HrCreateMessageList, hr);
    return hr;
}

/*
 -  HrDeleteSubDirectory
 -
 *  Purpose:
 *      Delete a subdirectory of the current directory
 *
 *  Parameters
 *       pifldParent    parent folder of folder to delete
 *       peid           entryid of folder to be deleted
 *       ulFlags        DEL_FOLDERS and/or DEL_MESSAGES
 *       fContentsOnly  BOOL. If TRUE, don't delete peid itself.
 *
 *  Side effects:
 *      If either flag is set, it deletes all that it can before returning.
 *      It is possible for the subdirectory to fail at being deleted but to
 *      have some of its contents removed.
 *
 */
static HRESULT HrDeleteSubDirectory(PIFLD pifldParent, PEID peidToDelete,
    ULONG ulFlags, BOOL fContentsOnly)
{

    HANDLE hFindFile = FAILED_SEARCH;
    LPTSTR szToDelete = NULL;   /* full path name of subdir to be deleted */
    PEID peidSubDir = NULL;     /* eid of subdirectory */
    PIFLD pifldToDelete = NULL; /* opened subdirectory to delete */
    ULONG ulOffset;             /* start of local name of subdir in szSubDir */
    ULONG ulObjType;
    LPENTRYLIST lpMessages = NULL;  /* list of messages to be deleted */
    LPTSTR szRelativePath = NULL;   /* relative path for id searches */
    WIN32_FIND_DATA ffd;
    HRESULT hr = hrSuccess;
    PIMS pims = pifldParent->pims;

    hr = HrFullPathName(pims->szStorePath, peidToDelete->szPath,
        NULL, &szToDelete);
    if (hr != hrSuccess)
        goto exit;

    /* open up the folder to be deleted */
    hr = pifldParent->lpVtbl->OpenEntry(pifldParent, CbEID(peidToDelete),
        (LPENTRYID) peidToDelete, NULL, MAPI_MODIFY,
        &ulObjType, (LPUNKNOWN *) &pifldToDelete);

    if (hr != hrSuccess)
        goto exit;

    if (!(ulFlags & DEL_FOLDERS))
    {
        /* The caller didn't specify DEL_FOLDERS. Check to make sure that */
        /* there aren't any subfolders before allowing the deletion to */
        /* continue. If there are subfolders, return MAPI_E_HAS_FOLDERS. */

        /* Look for any subfolders */

        hr = HrFindFirstID(pifldToDelete, szFolderTemplate, &ulOffset,
            &szRelativePath, &hFindFile, &ffd, &peidSubDir);

        CloseIDSearch(&hFindFile, &szRelativePath);

        if (hr == hrSuccess)
        {
            /* release the eid */
            LMFree(&pims->lmr, peidSubDir);
            peidSubDir = NULL;

            hr = ResultFromScode(MAPI_E_HAS_FOLDERS);
            goto exit;
        }
        else if (GetScode(hr) != MAPI_E_NOT_FOUND)
            goto exit;

        hr = hrSuccess;
    }
    else
    {
        /* set up the search for subdirectories */
        hr = HrFindFirstID(pifldToDelete, szFolderTemplate, &ulOffset,
            &szRelativePath, &hFindFile, &ffd, &peidSubDir);

        /* Delete each subdirectory*/
        while (hr == hrSuccess)
        {
            hr = HrDeleteSubDirectory(pifldToDelete, peidSubDir, ulFlags, FALSE);

            /* release the eid */
            LMFree(&pims->lmr, peidSubDir);
            peidSubDir = NULL;

            /* Errors returned from HrDeleteSubDirectory should be fatal. */
            /* Don't ignore them, because if we do, then we will leave a */
            /* corrupt message store behind. MAPI_E_HAS_MESSAGES can come */
            /* back from below, as well as MAPI_E_SUBMITTED, etc. */

            if (hr != hrSuccess)
                goto exit;

            /* Delete the other subdirectories */
            hr = HrFindNextID(pifldToDelete, ulOffset, szRelativePath,
                hFindFile, &ffd, &peidSubDir);
        }

        if (GetScode(hr) == MAPI_E_NOT_FOUND)
            hr = hrSuccess;
        else
            goto exit;

        /* end the search */
        CloseIDSearch(&hFindFile, &szRelativePath);
    }

    /* delete all messages if DEL_MESSAGES */
    hr = HrCreateMessageList(pifldToDelete, &lpMessages);
    if (hr != hrSuccess)
        goto exit;

    if (lpMessages && !(ulFlags & DEL_MESSAGES))
    {
        hr = ResultFromScode(MAPI_E_HAS_MESSAGES);
        goto exit;
    }

    if (lpMessages)
    {
        hr = pifldToDelete->lpVtbl->DeleteMessages(pifldToDelete,
            lpMessages, 0L, NULL, 0L);
        if (hr != hrSuccess)
            goto exit;
    }

    UlRelease(pifldToDelete);
    pifldToDelete = NULL;

    if (!fContentsOnly)
    {
        ULONG ulRowCount;
        LPMAPITABLE pmt;

        /* delete the files on the filesystem */
        hr = HrDestroyFolderStorage(szToDelete);
        if (hr != hrSuccess)
            goto exit;

        ChangeTable(pims, pifldParent->peid, peidToDelete, MAPI_FOLDER,
            TABLE_ROW_DELETED, TRUE);

        hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);

        if (hr == hrSuccess)
        {
            hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);
            UlRelease(pmt);
        }

        if (hr == hrSuccess && ulRowCount == 0)
            hr = HrSetSubFolderProp(pifldParent, FALSE);

        /* Ignore errors changing and updating the hierarchy table. */
        /* The entire delete shouldn't fail simply because we couldn't */
        /* update the hierarchy table. */

        #ifdef DEBUG
        if (hr != hrSuccess)
        {
            TraceSz1("HrDeleteSubDirectory: Ignoring partial failure (%s)"
                " fixing hierarchy table.", SzDecodeScode(GetScode(hr)));
        }
        #endif

        hr = hrSuccess;
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(pifldToDelete);

    LMFree(&pims->lmr, peidSubDir);
    FreeNull(szToDelete);

    CloseIDSearch(&hFindFile, &szRelativePath);

    if (lpMessages)
        DestroyMessageList(&pims->lmr, &lpMessages);

    DebugTraceResult(HrDeleteSubDirectory, hr);
    return hr;
}

/*
 -  HrCopyFolder
 -
 *  Purpose:
 *      moves or copies one folder into another
 *
 *  Parameters
 *      peidFldSrc      pointer to the entryid of the folder to copy or move.
 *      pifldDst        the parent folder into which we copy or move
 *      ulFlags         MAPI_MOVE, MAPI_NOREPLACE
 *      ptagaExcl       List of properties to exclude from the copy.
 *
 *  Returns:
 *
 *  Errors:
 */
static HRESULT HrCopyFolder(PEID peidFldSrc, PIFLD pifldDstPar, ULONG ulFlags,
    LPSPropTagArray ptagaExcl)
{
    HRESULT hr;
    PIFLD pifldSrc = NULL;

    PIFLD pifldCopy = NULL;     /* copy of pifldSrc */
    LPTSTR szComment = NULL;    /* comment for copy of pifldSrc */
    PIMS pims = pifldDstPar->pims;
    ULONG ulObjType;
    LPSTR szNewFolderName;
    PIFLD pifldParent = NULL;

ULONG cValNC;
    LPSPropValue pvalNC = NULL;
    const static SizedSPropTagArray(2, sptaNC) =
    {
        2,
        {
            PR_DISPLAY_NAME,
            PR_COMMENT
        }
    };

    /* Open the source folder for modification only if we are moving */

    hr = pims->lpVtbl->OpenEntry(pims, CbEID(peidFldSrc),
        (LPENTRYID) peidFldSrc, NULL, (ulFlags & MAPI_MOVE) ? MAPI_MODIFY : 0L,
        &ulObjType, (LPUNKNOWN *) &pifldSrc);
    if (hr != hrSuccess)
        goto exit;

    /* if this is a move, make sure the source folder is not in use */
    if ((ulFlags & MAPI_MOVE) && pifldSrc->cRef > 1)
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* get the name and comment from pifldSrc */
    hr = pifldSrc->lpVtbl->GetProps(pifldSrc,
        (LPSPropTagArray) &sptaNC, 0, /* ansi */
        &cValNC, &pvalNC);
    if (hr != hrSuccess)
    {
        if (GetScode(hr) == MAPI_W_ERRORS_RETURNED)
        {
            if (PROP_TYPE(pvalNC->ulPropTag) == PT_ERROR)
            {
                hr = ResultFromScode(pvalNC->Value.err);
                goto exit;
            }

            hr = hrSuccess;
        }
        else
            goto exit;
    }

    /* create new folder for the copy */
    szNewFolderName = pvalNC[0].Value.LPSZ;

    if (PROP_TYPE(pvalNC[1].ulPropTag) != PT_ERROR)
        szComment = pvalNC[1].Value.LPSZ;
    else
        szComment = NULL;

    hr = HrDuplicateIFLD(pifldSrc, pifldDstPar, szNewFolderName, szComment,
        &pifldCopy);
    if (hr != hrSuccess)
        goto exit;

    /* move or copy the interior of this folder (recursive) */
    hr = HrCopyContents(pifldSrc, pifldCopy, ulFlags, ptagaExcl);
    if (hr != hrSuccess)
        goto exit;

    UlRelease(pifldSrc);
    pifldSrc = NULL;

    /* invalidate the source folder if this is a move */
    if (ulFlags & MAPI_MOVE)
    {
        LPMAPITABLE pmt;
        ULONG ulRowCount;
        LPTSTR szFullPathFolder;

        hr = HrFullPathName(pims->szStorePath,
            peidFldSrc->szPath, NULL, &szFullPathFolder);
        if (hr != hrSuccess)
            goto exit;

        /* delete it */
        hr = HrDestroyFolderStorage(szFullPathFolder);

        FreeNull(szFullPathFolder);

        if (hr != hrSuccess)
            goto exit;

        /* update the hierarchy table of the source's parent */
        hr = HrOpenParent(pims, peidFldSrc, MAPI_MODIFY, &pifldParent);
        if (hr != hrSuccess)
            goto exit;

        ChangeTable(pims, pifldParent->peid, peidFldSrc, MAPI_FOLDER,
            TABLE_ROW_DELETED, TRUE);

        hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);

        if (hr == hrSuccess)
            hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);

        UlRelease(pmt);

        if (hr == hrSuccess && ulRowCount == 0)
            hr = HrSetSubFolderProp(pifldParent, FALSE);
    }

exit:
    UlRelease(pifldSrc);

    LMFree(&pims->lmr, pvalNC);
    UlRelease(pifldParent);

    UlRelease(pifldCopy);

    DebugTraceResult(HrCopyFolder, hr);
    return hr;
}

/*
 * HrCopyContents
 *
 *  Purpose         copy or move the contents of the source folder to
 *                  the destination
 *
 *  Parameters
 *      pifldSrc        source folder
 *      pifldDst        destination folder
 *      ulFlags         MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG
 *      ptagaExcl       List of properties to exclude from the copy.
 *
 */
static HRESULT HrCopyContents(PIFLD pifldSrc, PIFLD pifldDst, ULONG ulFlags,
    LPSPropTagArray ptagaExcl)
{
    PEID peidNext = NULL;       /* eid of next folder to move */
    ULONG ichLocal;
    PLMR plmr = &pifldSrc->pims->lmr;

    /* relative path name of next folder to be moved */
    LPTSTR szFile = NULL;
    HANDLE hFindFile = FAILED_SEARCH;
    HRESULT hr = hrSuccess;
    ULONG ulMessageFlags = 0L;  /* flags for the message copy */
    WIN32_FIND_DATA ffd;

    /* move or copy each message inside the folder */
    if (ulFlags & MAPI_MOVE)
        ulMessageFlags = MESSAGE_MOVE;

    if (!FContainsProp(PR_CONTAINER_CONTENTS, ptagaExcl))
    {
        LPENTRYLIST lpMessages = NULL;

        /* make a message list */
        hr = HrCreateMessageList(pifldSrc, &lpMessages);
        if (hr != hrSuccess)
            goto exit;

        /* move/copy messages */
        if (lpMessages)
        {
            hr = pifldSrc->lpVtbl->CopyMessages(pifldSrc,
                lpMessages, 0, (LPMAPIFOLDER) pifldDst, 0,
                NULL, ulMessageFlags);

            DestroyMessageList(plmr, &lpMessages);

            if (hr != hrSuccess)
                goto exit;
        }
    }

    /* move/copy subfolders if required */
    if ((ulFlags & MAPI_MOVE)
        || (!FContainsProp(PR_CONTAINER_HIERARCHY, ptagaExcl)))
    {
        /* get the first entryID for a subfolder */
        hr = HrFindFirstID(pifldSrc, szFolderTemplate, &ichLocal,
            &szFile, &hFindFile, &ffd, &peidNext);

        while (hr == hrSuccess)
        {
            /* copy the eid to the destination */
            hr = HrCopyFolder(peidNext, pifldDst, ulFlags, ptagaExcl);
            if (hr != hrSuccess)
                goto exit;

            LMFree(plmr, peidNext);
            peidNext = NULL;

            if (ulFlags & MAPI_MOVE)
            {
                /* The folder should have been destroyed; therefore, */
                /* start the search again. */

                CloseIDSearch(&hFindFile, &szFile);

                hr = HrFindFirstID(pifldSrc, szFolderTemplate, &ichLocal,
                    &szFile, &hFindFile, &ffd, &peidNext);
            }
            else
                hr = HrFindNextID(pifldSrc, ichLocal, szFile,
                    hFindFile, &ffd, &peidNext);
        }

        if (GetScode(hr) != MAPI_E_NOT_FOUND)
            goto exit;          /* uh oh.  got a real error */

        hr = hrSuccess;
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    /* close the search */
    CloseIDSearch(&hFindFile, &szFile);
    LMFree(plmr, peidNext);

    DebugTraceResult(HrCopyContents, hr);
    return hr;
}

/*
 * HrEIDFromDisplayName
 *
 *  Purpose
 *      Searches the table given, looking for a matching PR_DISPLAY_NAME.
 *      If found, the routine returns the value of the corresponding
 *      PR_ENTRYID column, converted to an internal PEID. If not found, the
 *      routine returns a NULL.
 *
 *  Parameters
 *      pmt: The IMAPITable object to search for the display name.
 *      szName: The display name to search for.
 *      plmr: A pointer to the linked memory routines to allow freeing memory.
 *      ppeid: A pointer to the location to return the internal entryid of
 *          the PR_ENTRYID column that corresponds to the matching display
 *          name. If no matching display name was found, the routine returns
 *          NULL in this location.
 *
 */
static HRESULT HrEIDFromDisplayName(LPMAPITABLE pmt, LPSTR szName, PLMR plmr,
    PEID *ppeid)
{
    HRESULT hr;
    LPSRowSet prws = NULL;
    PEID peid = NULL;

    SizedSPropTagArray(2, spta) =
    {
        2,
        {
            PR_ENTRYID,
            PR_DISPLAY_NAME
        }
    };

    hr = pmt->lpVtbl->SetColumns(pmt, (LPSPropTagArray) &spta, 0);
    if (hr != hrSuccess)
        goto exit;

    hr = pmt->lpVtbl->SeekRow(pmt, BOOKMARK_BEGINNING, 0, NULL);
    if (hr != hrSuccess)
        goto exit;

    while (TRUE)
    {
        LPSTR szCurName;
        LPSPropValue pval;

        hr = pmt->lpVtbl->QueryRows(pmt, 1, 0, &prws);
        if (hr != hrSuccess)
            goto exit;

        if (prws->cRows == 0)
            break;

        Assert(prws);
        Assert(prws->cRows == 1);
        Assert(prws->aRow[0].lpProps);
        Assert(prws->aRow[0].lpProps[0].ulPropTag == PR_ENTRYID);
        Assert(prws->aRow[0].lpProps[1].ulPropTag == PR_DISPLAY_NAME);

        pval = prws->aRow[0].lpProps;

        szCurName = (LPSTR) pval[1].Value.lpszA;

        if (lstrcmpi(szName, szCurName) == 0)
        {
            SCODE sc;
            PEID peidTemp = (PEID) pval->Value.bin.lpb;
            UINT cbEID = (UINT) pval->Value.bin.cb;

            sc = LMAlloc(plmr, cbEID, &peid);
            if (sc != S_OK)
            {
                hr = ResultFromScode(sc);
                goto exit;
            }

            if (cbEID)
                memcpy(peid, peidTemp, cbEID);

            break;
        }

        FreeProws(prws);
        prws = NULL;
    }

exit:
    if (prws)
    {
        if (prws->cRows && prws->aRow[0].lpProps)
            LMFree(plmr, prws->aRow[0].lpProps);

        LMFree(plmr, prws);
    }

    if (hr == hrSuccess)
    {
        AssertSz(!IsBadWritePtr(ppeid, sizeof(PEID)), "Bad parameter (ppeid) "
            "passed to HrEIDFromDisplayName");
        *ppeid = peid;
    }

    DebugTraceResult(HrEIDFromDisplayName, hr);
    return hr;
}


MSPGLE.C

/*
 *  M S P G L E . C
 *
 *  Code for implementing the GetLastError method of all store
 *  objects.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"
#include "msprc.h"

/* Manifest constants */

#define STRING_MAX      128

/* Global variables */

/* The following array maps a string identifier (IDS) to status code */
/* (SCODE).  The order of SCODEs in the array has an external        */
/* dependency:  the order of elements in the array is dictated by    */
/* the IDS definitions in msprc.h.  This implicit association must   */
/* be maintained for the strings associated with string identifiers  */
/* to make sense.  Thus, if either this structure or the one in      */
/* msprc.h changes, the other must change to match it.               */

SCODE mpIdsScode[] =
{
    S_OK,
    MAPI_E_NO_ACCESS,
    E_NOINTERFACE,
    E_INVALIDARG,
    MAPI_E_CALL_FAILED,
    MAPI_E_NOT_FOUND,
    MAPI_E_NO_SUPPORT,
    MAPI_W_ERRORS_RETURNED,
    MAPI_W_PARTIAL_COMPLETION,
    MAPI_E_BAD_CHARWIDTH,
    MAPI_E_BAD_VALUE,
    MAPI_E_BUSY,
    MAPI_E_COLLISION,
    MAPI_E_COMPUTED,
    MAPI_E_CORRUPT_DATA,
    MAPI_E_CORRUPT_STORE,
    MAPI_E_DISK_ERROR,
    MAPI_E_HAS_FOLDERS,
    MAPI_E_HAS_MESSAGES,
    MAPI_E_INVALID_ENTRYID,
    MAPI_E_INVALID_OBJECT,
    MAPI_E_LOGON_FAILED,
    MAPI_E_NETWORK_ERROR,
    MAPI_E_NON_STANDARD,
    MAPI_E_NOT_ENOUGH_DISK,
    MAPI_E_NOT_ENOUGH_MEMORY,
    MAPI_E_NOT_ENOUGH_RESOURCES,
    MAPI_E_NOT_IN_QUEUE,
    MAPI_E_OBJECT_CHANGED,
    MAPI_E_OBJECT_DELETED,
    MAPI_E_STRING_TOO_LONG,
    MAPI_E_SUBMITTED,
    MAPI_E_TOO_BIG,
    MAPI_E_UNABLE_TO_ABORT,
    MAPI_E_UNCONFIGURED,
    MAPI_E_UNEXPECTED_TYPE,
    MAPI_E_UNKNOWN_FLAGS,
    MAPI_E_USER_CANCEL,
    MAPI_E_VERSION
};

/*
 *  Exported functions
 */

/*
 *  MapScodeSz
 *
 *  Purpose:
 *      Look up an SCODE in a mapping of IDS <-> SCODE to find its
 *      associated informational string and return it (with memory
 *      allocated by this function) to the caller.
 *
 *  Arguments:
 *      scArg       The SCODE to look up.
 *      pims        Pointer to the message store object (where we
 *                  obtain the memory allocation functions).
 *      lppszError  Location in which to place an address to a
 *                  newly allocated buffer containing the
 *                  informational string associated with scArg.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the return string.
 */
HRESULT MapScodeSz(SCODE scArg, PIMS pims, LPTSTR * lppszError)
{
    HRESULT hr = hrSuccess;
    SCODE sc = SUCCESS_SUCCESS;
    UINT ui = 0;
    UINT uiMax = 0;
    LPTSTR szErr = NULL;
    TCHAR rgch[STRING_MAX];

    AssertSz(lppszError, "Bad lppszError\n");

    /* Linear search in mpIdsScode for scArg.  When found, index is IDS. */

    uiMax = sizeof mpIdsScode / sizeof mpIdsScode[0];
    for (ui = 0; ui < uiMax; ui++)
    {
        if (mpIdsScode[ui] == scArg)
            break;
    }

    /* If we didn't find the string, return a NULL string. */

    if (ui == uiMax)
        rgch[0] = (TCHAR) 0;
    else
    {
        /* Get the string from the resource.  Note:  the assumption that   */
        /* rgch is large enough to hold the largest string that LoadString */
        /* could return can be checked by looking at the resource strings  */
        /* file, msp.rc                                                    */

        int iLS = 0;

        Assert(pims->pmsp);

        iLS = LoadString(pims->pmsp->hInst, ui, rgch, STRING_MAX);
        AssertSz(iLS, "Unknown string identifier!");
        AssertSz(iLS < STRING_MAX-1, "String resource truncated!");
    }

    /* Allocate memory for return variable and set it */

    sc = LMAlloc(&pims->lmr, Cbtszsize(rgch), &szErr);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    lstrcpy(szErr, rgch);
    *lppszError = szErr;

exit:
    DebugTraceResult(MapScodeSz, hr);
    return hr;
}


MSPGUID.C

/*
 *  M S P G U I D . C
 *
 *  Define the guids that are used by the Sample Message Store Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#define USES_IID_IUnknown
#define USES_IID_IStream

#define USES_IID_IMSLogon
#define USES_IID_IMAPIProp
#define USES_IID_IAttachment
#define USES_IID_IMessage
#define USES_IID_IMAPIFolder
#define USES_IID_IMAPIContainer
#define USES_IID_IMAPITableData
#define USES_IID_IMAPITable
#define USES_IID_IMSProvider
#define USES_IID_IMsgStore

#ifdef _WIN32   /* Must include WINDOWS.H on Win32 */
#ifndef _WINDOWS_
#define INC_OLE2 /* Get the OLE2 stuff */
#define INC_RPC  /* harmless on Windows NT; Windows 95 needs it */
#define _INC_OLE /* Windows 95 will include OLE1 without this */
#include <windows.h>
#include <ole2.h>
#endif
#endif

#ifdef WIN16
#include <compobj.h>
#endif

#define INITGUID
#include <initguid.h>
#include <mapiguid.h>

DEFINE_OLEGUID(IID_IUnknown,            0x00000000L, 0, 0);
DEFINE_OLEGUID(IID_IStream,             0x0000000cL, 0, 0);


MSPINIT.C

/*
 *  M S P I N I T . C
 *
 *  Initialize the MAPI Sample Message Store Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

CHAR szFolderTemplate[]         = "*.fld";
CHAR szMessageTemplate[]        = "*.msg";
CHAR szPropertyFileName[]       = "folder.prp";
CHAR szHierarchyFileName[]      = "hierarch.tbl";
CHAR szContentsFileName[]       = "contents.tbl";
CHAR szOutgoingFileName[]       = "outgoing.tbl";

#define MSP_CheckParameters(pobj, intf, method, arglist)        \
        OBJ_CheckParameters(pobj, intf, method, sizeof(MSP), &vtblMSP, arglist)

MSP_Vtbl vtblMSP =
{
    (MSP_QueryInterface_METHOD *)   OBJ_QueryInterface,
    (MSP_AddRef_METHOD *)           OBJ_AddRef,
    MSP_Release,
    MSP_Shutdown,
    MSP_Logon,
    MSP_SpoolerLogon,
    MSP_CompareStoreIDs
};

/*
 *  Exported functions
 */

/*
 *  MSProviderInit
 *
 *  Purpose:
 *      Message Store Provider initialization and version handshake
 *      with MAPI.  Called once for each MAPI Session that uses this
 *      store provider DLL on this process.  Passes back an init
 *      object (LPMSPROVIDER) used for further access to this provider.
 *
 *  Arguments:
 *      ulFlags         Reserved for future use.  Ignored.
 *      ulMAPIVersion   Version of Message Store SPI used by MAPI.
 *      lpulMDBVersion  [out] Version of SPI supported by the provider.
 *      ppmsp   [out] MS Provider object for further access.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_VERSION              Require a higher version of MAPI
 *      MAPI_E_NOT_ENOUGH_MEMORY    Insufficient memory
 *      Any errors from ScInitMSInstance()
 */
STDINITMETHODIMP
MSProviderInit(HINSTANCE hInstance, LPMALLOC pmalloc,
    LPALLOCATEBUFFER pfnAllocBuf, LPALLOCATEMORE pfnAllocMore,
    LPFREEBUFFER pfnFreeBuf, ULONG ulFlags, ULONG ulMAPIVersion,
    ULONG * pulMDBVersion, LPMSPROVIDER * ppmsp)
{
    SCODE sc = S_OK;
    PMSP pmsp = NULL;

    AssertSz(pmalloc, "Bad pmalloc");
    AssertSz(pfnAllocBuf, "Bad pfnAllocBuf");
    AssertSz(pfnAllocMore, "Bad pfnAllocMore");
    AssertSz(pfnFreeBuf, "Bad pfnFreeBuf");
    NFAssertSz(!ulFlags, "Unknown flags, bug in MAPI DLL");
    AssertSz(pulMDBVersion, "Bad pulMDBVersion");
    AssertSz(ppmsp, "Bad ppmsp");

    /* This provider requires MAPI to be at least the version
     * of the SPI defined at the time this provider was compiled.
     */
    if (ulMAPIVersion < CURRENT_SPI_VERSION)
    {
        sc = MAPI_E_VERSION;
        goto exit;
    }

    /* Initialize the per-instance global data */
    sc = ScInitMSInstance(pmalloc);
    if (sc != S_OK)
        goto exit;

    /* Allocate and initialize the MSPROVIDER object. */
    sc = ScAllocZ(sizeof(MSP), (PPV) &pmsp);
    if (sc != S_OK)
    {
        DeinitMSInstance();
        goto exit;
    }

    OBJ_Initialize(pmsp, &vtblMSP, OT_MSPROVIDER, 0, &pmsp->cs);

    pmsp->hInst = hInstance;
    pmsp->lmr.lpAllocBuf = pfnAllocBuf;
    pmsp->lmr.lpAllocMore = pfnAllocMore;
    pmsp->lmr.lpFreeBuf = pfnFreeBuf;

    InitializeCriticalSection(&pmsp->cs);

    /* Pass back [out] parameters. */
    *pulMDBVersion = CURRENT_SPI_VERSION;
    *ppmsp = (LPMSPROVIDER) pmsp;

exit:
    DebugTraceSc(MSProviderInit, sc);
    return ResultFromScode(ScCheckSc(sc, IMSProvider_Init));
}

/*
 -  MSP_Release
 -
 */

STDMETHODIMP_(ULONG) MSP_Release(PMSP pmsp)
{
    LONG cRef;

    MSP_EnterCriticalSection(pmsp);

    cRef = --pmsp->cRef;

    AssertSz2(cRef >= 0, "MSP_Release(pmsp=%08lX): Bogus cRef (%08lX)",
        pmsp, cRef);

    AssertSz(cRef > 0 || pmsp->pobjHead == NULL, "There are still valid logons");

    MSP_LeaveCriticalSection(pmsp);

    if (cRef == 0)
    {
        DeleteCriticalSection(&pmsp->cs);
        FreeNull(pmsp);
        DeinitMSInstance();
    }

    return (cRef);
}

/*
 *  MSP_Shutdown
 *
 *  Purpose:
 *      Allow MAPI to specify flags related to the Release().
 *
 *  Arguments:
 *      pulFlags        Reserved for future use.  Ignored.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP
MSP_Shutdown(PMSP pmsp, ULONG * pulFlags)
{
    MSP_CheckParameters(
            pmsp,
            IMSProvider,
            Shutdown,
            (pmsp,
            pulFlags));

    MSP_EnterCriticalSection(pmsp);

    /* MAPI says it will never call Release with valid logons, */
    /* let's make sure.                                        */

    AssertSz(pmsp->pobjHead == NULL, "There are still valid logons");

#ifdef DEBUG
    pmsp->fInvalid = TRUE;
#endif

    MSP_LeaveCriticalSection(pmsp);

    return 0;
}

/*
 *  MSP_CompareStoreIDs
 *
 *  Purpose:
 *      Compare the EntryIDs of two message stores.
 *
 *  Arguments:
 *      pmsp                MSPROVIDER object returned by MSProviderInit.
 *      lcbEntryID1         Size of first EntryID.
 *      lpEntryID1          First EntryID to compare.
 *      lcbEntryID2         Size of second EntryID.
 *      lpEntryID2          Second EntryID to compare.
 *      ulFlags             Flags.  Reserved for future use.
 *      pulResult           Address in which to place the result of
 *                          the comparison (TRUE or FALSE).
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP
MSP_CompareStoreIDs(PMSP pmsp, ULONG lcbEntryID1, LPENTRYID lpEntryID1,
    ULONG lcbEntryID2, LPENTRYID lpEntryID2, ULONG ulFlags,
    ULONG *pulResult)
{
    PEID peid1 = (PEID) lpEntryID1;
    PEID peid2 = (PEID) lpEntryID2;

    MSP_CheckParameters(
            pmsp,
            IMSProvider,
            CompareStoreIDs,
            (pmsp,
            lcbEntryID1,
            lpEntryID1,
            lcbEntryID2,
            lpEntryID2,
            ulFlags,
            pulResult));

    AssertSz(!pmsp->fInvalid,
        "This MSProvider has already been shutdown");

    /* The Sample Store's method of deriving and comparing   */
    /* store EntryIDs has the quirky behavior that if any    */
    /* OTHER store's EntryIDs are binary-comparable, this    */
    /* function will return a successful match, without ever */
    /* knowing that the EntryIDs were not Sample Store       */
    /* EntryIDs.  A subsequent logon would, of course, fail. */

    /* Case-insensitive string compare on part, binary on rest. */
    if (    lcbEntryID1 >= offsetof(EID, szPath) + sizeof(TCHAR)
        &&  lcbEntryID1 == lcbEntryID2
        &&  memcmp(peid1, peid2, offsetof(EID, szPath)) == 0
        &&  peid1->bVersion == SMPMS_VERSION
        &&  lstrcmpi(peid1->szPath, peid2->szPath) == 0)
    {
        *pulResult = TRUE;
    }
    else
    {
        *pulResult = FALSE;
    }

    return hrSuccess;
}


MSPLOGON.C

/*
 *  M S P L O G O N . C
 *
 *  Logon to a MAPI Sample Message Store.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"
#include "msprc.h"
#include <stddef.h>

#include "..\common\wrap3d.h"

#define MSL_CheckParameters(pobj, intf, method, arglist)        \
        OBJ_CheckParameters(pobj, intf, method, sizeof(MSL), &vtblMSL, arglist)
#define MSP_CheckParameters(pobj, intf, method, arglist)        \
        OBJ_CheckParameters(pobj, intf, method, sizeof(MSP), &vtblMSP, arglist)

#ifndef MB_SETFOREGROUND
#define MB_SETFOREGROUND 0
#endif

#define MSL_EnterCriticalSection(pmsl)  OBJ_EnterCriticalSection((POBJ)pmsl)
#define MSL_LeaveCriticalSection(pmsl)  OBJ_LeaveCriticalSection((POBJ)pmsl)

MSL_Vtbl vtblMSL =
{
    (MSL_QueryInterface_METHOD *)       OBJ_QueryInterface,
    (MSL_AddRef_METHOD *)               OBJ_AddRef,
    MSL_Release,
    (MSL_GetLastError_METHOD *)         IMS_GetLastError,
    MSL_Logoff,
    (MSL_OpenEntry_METHOD *)            IMS_OpenEntry,
    (MSL_CompareEntryIDs_METHOD *)      IMS_CompareEntryIDs,
    (MSL_Advise_METHOD *)               IMS_Advise,
    (MSL_Unadvise_METHOD *)             IMS_Unadvise,
    MSL_OpenStatusEntry
};

MAPIUID uidProvider = SMPMS_UID_PROVIDER;

/* Manifest constants */

#define ACCTSIZE    16          /* Our choice for maximum password length. */

static SPropTagArray sptaPass =
{
    1, {PR_SMS_PASSWORD}
};

TCHAR szPropFileName[] = TEXT("msgstore.prp");
TCHAR szFolderFileName[] = TEXT("recfldr.stg");
TCHAR szProviderDisplayName[] = TEXT("Sample Message Store");
TCHAR szDisplayPrefix[] = TEXT("Sample store at ");

/* property tags needed for service creation or configuration */
static  SizedSPropTagArray(4,ptaConfig) = {4, { PR_SMS_PATH,
                                            PR_SMS_PASSWORD,
                                            PR_SMS_REMEMBER_PW,
                                            PR_SMS_CREATE } };

/* property tags that should be removed from the profile after */
/* service creation or configuration */
static  SizedSPropTagArray(2,ptaDel)= {2, { PR_SMS_REMEMBER_PW,
                                            PR_SMS_CREATE } };

/* service parameter block */
typedef struct _ServicePBLK
{
    PMSP            pmsp;
    LPPROFSECT      pps;
    LPMAPISUP       psup;
    ULONG           ulUIParam;
    ULONG           ulContext;
    ULONG           ulSEFlags;
    ULONG           cvalsProf;
    LPSPropValue    pvalsProf;
    ULONG           cvalsClient;
    LPSPropValue    pvalsClient;
} SERVICEPBLK, FAR * PSERVICEPBLK;

#define LGNCRED_PATH        ((ULONG) 0x00000001)
#define LGNCRED_PASS        ((ULONG) 0x00000002)
#define LGNCRED_CREATE      ((ULONG) 0x00000004)
#define LGNCRED_REMEMBER    ((ULONG) 0x00000008)

/* Typedefs */

typedef struct _LGNCRED
{
    ULONG ulFieldMask;
    TCHAR szPath[MAX_PATH];
    TCHAR szPass[ACCTSIZE];
    BOOL fCreate;
    BOOL fRemember;
    BOOL fCancel;
    LPVOID lpCtl3d;
} LGNCRED, * PLGNCRED;

typedef struct _DLGPSWD
{
    TCHAR szPath[MAX_PATH];
    TCHAR szPass[ACCTSIZE];
    LPMAPISUP psup;
    PLMR plmr;
    BOOL fRemember;
    BOOL fCancel;
    LPVOID lpCtl3d;
} DLGPSWD, * PDLGPSWD;

/* Internal function prototypes */

static HRESULT HrOpenSingleProvider(LPPROVIDERADMIN padp,
    LPPROFSECT FAR *lppProfSect);
static SCODE ScCreateOrConfigure(PSERVICEPBLK pspb);
static SCODE ScCreateStore(PSERVICEPBLK pspb, LPTSTR szPath,
    LPTSTR szPass, PMSL * ppmsl, PIMS * ppims);
static BOOL FAlreadyOpenStore(PMSP pmsp, LPTSTR szPath, PIMS * ppims);

static HRESULT HrGetLogonDlgCred(HINSTANCE hInst, ULONG ulUIParam, LPTSTR szPath,
    LPTSTR szPass, BOOL *pfCreate, BOOL *pfRemember);
STDAPI_(BOOL) LogonDlgProc(HWND hDlg, UINT message, WPARAM wParam,
    LPARAM lParam);
static HRESULT HrGetPassword(HINSTANCE hInst, LPMAPISUP psup, PLMR plmr,
    ULONG ulUIParam, LPTSTR szPath, LPTSTR szPass, BOOL *pfRemember);
STDAPI_(BOOL) PasswordDlgProc(HWND hDlg, UINT message, WPARAM wParam,
    LPARAM lParam);
static HRESULT HrGetProfileCred(PLMR plmr, LPPROFSECT pps, LPTSTR szPass,
    BOOL *pfFound);
static HRESULT HrSetProfileCred(LPPROFSECT pps, LPTSTR szPass, LPTSTR szPath,
    BOOL fRemember, ULONG cbEntryID, LPBYTE lpEntryID, LPMAPIUID puidRK);
static HRESULT HrConfirmCred(LPTSTR szPath, LPTSTR szPass, LPMAPISUP psup,
    PLMR plmr);
static HRESULT HrFilePathExists(LPTSTR szStorePath, LPTSTR szFileName);
static HRESULT HrCheckStoreIntegrity(LPTSTR szPath);
static BOOL FIsValidPath(LPTSTR szPath, BOOL fCreate);
static void DecodeSpoolSecurity(ULONG cbSpoolSecurity, LPBYTE pbSpoolSecurity,
    LPTSTR szPath, LPTSTR szPass);
static HRESULT HrGetSpoolSecurity(LPTSTR szPath, LPTSTR szPass,
    PLMR plmr, ULONG * lpcbSpoolSecurity, LPBYTE * lppbSpoolSecurity);
static HRESULT HrCheckAndOpenStore(PMSP pmsp, LPMAPISUP psup, BOOL fModify,
    LPPROFSECT pps, LPTSTR szPath, LPTSTR szPass, BOOL fIsSpooler,
    PMSL *ppmsl, PIMS *ppims);
static SCODE ScCreateMSL(PMSP pmsp, PMSL * ppmsl);
static BOOL FFindPropInPSPB(PSERVICEPBLK pspb, ULONG ulPT, LPSPropValue *ppval);
static SCODE ScGetFullFileName(PSERVICEPBLK pspb, BOOL *pfCreate,
    LPSTR pszPath);

/*
 *  MSP_Logon
 *
 *  Purpose:
 *      Logs in one user to the message store and returns a logon object
 *      and a message store object.  Gets credentials through the
 *      profile or a dialog.  Prompts if more information is needed.
 *
 *  Arguments:
 *      pmsp                MSPROVIDER object returned at INIT time.
 *      psup                Pointer to MAPI Support object.
 *      ulUIParam           Handle to parent window cast to a ULONG.
 *      szProfileName       String with the name of profile.
 *                          (UNICODE if MAPI_UNICODE flag set in ulFlags)
 *      cbEntryID           Size of lpEntryID.
 *      lpEntryID           Provider component of store resource EntryID.
 *      ulFlags             Flags for access methods:
 *                          MDB_WRITE           Write access.
 *                          MAPI_BEST_ACCESS    Write access if possible.
 *                          MDB_NO_DIALOG       No logon dialog.
 *                          MAPI_UNICODE        If szProfileName UNICODE
 *      piid                Identifies what interface is desired on the
 *                          about-to-be-opened object.
 *      lpcbSpoolSecurity   Address of the variable in which the
 *                          provider returns size of *lppbSpoolSecurity.
 *      lppbSpoolSecurity   Address to return validation data
 *                          necessary for SpoolerLogon to
 *                          piggyback off this logon session.
 *      lpulLowLevelError   Possible low level error to return if
 *                          error or MAPI_W_EXTENDED_WARNING is returned.
 *      pszMessage          Error / Warning string on logon.  If MAPI_UNICODE
 *                          is set in ulFlags then it is UNICODE.
 *      ppmslogon           Address to return a pointer to MSLOGON
 *                          object for use by MAPI.DLL.
 *      ppmdb               Address of a pointer to the message
 *                          store object for use by the client.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      May put up logon screen to select/open store if
 *      MDB_NO_DIALOG is not set.  For store creation, Logon
 *      creates "hidden" files for MDB management.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate memory.
 *      MAPI_E_CALL_FAILED          Failure creating a new store.
 *      MAPI_E_CALL_FAILED          Windowing system failure trying
 *                                  to display logon UI.
 *      MAPI_E_CORRUPT_STORE        One of the necessary store
 *                                  files is missing.
 *      MAPI_E_LOGON_FAILED         Insufficient or bad credentials.
 */
STDMETHODIMP
MSP_Logon(PMSP pmsp, LPMAPISUP psup, ULONG ulUIParam, LPSTR szProfileName,
    ULONG cbEntryID, LPENTRYID lpEntryID, ULONG ulFlags, LPCIID piid,
    ULONG * lpcbSpoolSecurity, LPBYTE * lppbSpoolSecurity,
    LPMAPIERROR * lppMapiError, LPMSLOGON * ppmslogon, LPMDB * ppmdb)
{
    HRESULT hr = hrSuccess;
    HRESULT hrT;
    BOOL fRemember = FALSE;     /* Remember password in profile? */
    BOOL fFoundPassword;        /* TRUE if the password was in the profile */
    PIMS pims = NULL;           /* Internal IMsgStore implementation */
    PMSL pmsl = NULL;           /* logon object to give to MAPI */
    TCHAR szPath[MAX_PATH];     /* Path to store root */
    TCHAR szPass[ACCTSIZE];     /* Store account password */
    PEID peid = (PEID) lpEntryID;
    LPPROFSECT pps = NULL;      /* our profile section */

    MSP_CheckParameters(
            pmsp,
            IMSProvider,
            Logon,
            (pmsp,
            psup,
            ulUIParam,
            szProfileName,
            cbEntryID,
            lpEntryID,
            ulFlags,
            piid,
            lpcbSpoolSecurity,
            lppbSpoolSecurity,
            lppMapiError,
            ppmslogon,
            ppmdb));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    MSP_EnterCriticalSection(pmsp);

    AssertSz(!pmsp->fInvalid, "This MSProvider has already been shutdown");

    *lpcbSpoolSecurity = 0L;
    *lppbSpoolSecurity = NULL;
    *lppMapiError = NULL;
    *ppmslogon = NULL;
    *ppmdb = NULL;

    if (piid && !FQueryInterface(OT_MSGSTORE, piid))
    {
        hr = ResultFromScode(E_NOINTERFACE);
        goto exit;
    }

    hr = psup->lpVtbl->OpenProfileSection(psup, NULL, MAPI_MODIFY, &pps);
    if (hr != hrSuccess)
        goto exit;

    /* Get the full path to the store out of the EntryID */

    if (peid == NULL)
    {
        hr = ResultFromScode(MAPI_E_UNCONFIGURED);
        goto exit;
    }
    else if (FIsInvalidEID(cbEntryID, peid, NULL))
    {
        hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
        goto exit;
    }

    lstrcpy(szPath, peid->szPath);

    DebugTrace("\nSMS: MSP_Logon %s\n\n", szPath);

    /* If we can piggyback on an existing open message store on this */
    /* session then do so. If we piggyback, then don't return a logon */
    /* object or addref the support object. */

    if (FAlreadyOpenStore(pmsp, szPath, &pims))
    {
        DebugTrace("\nSMS: Piggybacking client logon\n");
        Assert(hr == hrSuccess);
        goto exit;
    }

    /* Get store credentials from profile. */

    hr = HrGetProfileCred(&pmsp->lmr, pps, szPass, &fFoundPassword);
    if (hr != hrSuccess)
        goto exit;

    /* If the password was in the profile, then the user wanted us to */
    /* remember it (that's the only way it would be in the profile). */

    if (fFoundPassword)
        fRemember = TRUE;

    while (TRUE)
    {
        SCODE sc;

        hr = HrCheckAndOpenStore(pmsp, psup,
            !!(ulFlags & (MDB_WRITE | MAPI_BEST_ACCESS)), pps, szPath,
            szPass, FALSE, &pmsl, &pims);
        if (hr == hrSuccess)
            break;

        sc = GetScode(hr);

        /* Preset hr to the value that will cause MAPI to bring up our */
        /* config UI */

        hr = ResultFromScode(MAPI_E_UNCONFIGURED);

        if (!(ulFlags & MDB_NO_DIALOG))
        {
            if (sc == MAPI_E_LOGON_FAILED)
            {
                hr = HrGetPassword(pmsp->hInst, psup, &pmsp->lmr, ulUIParam,
                    szPath, szPass, &fRemember);
            }
            else if (sc == MAPI_E_NOT_FOUND)
            {
                MessageBox(NULL, "The path to the message store is invalid.",
                    "Sample Message Store",
                    MB_OK | MB_TASKMODAL | MB_SETFOREGROUND);
            }
        }

        if (hr != hrSuccess)
            goto exit;
    }

    /* Complete regular logon tasks. */

    /* Update saved credentials if they have changed. */

    hrT = HrSetProfileCred(pps, szPass, szPath, fRemember,
        pims->eidStore.cb, pims->eidStore.lpb, &pims->uidResource);

    if (hrT == hrSuccess)
        hrT = pps->lpVtbl->SaveChanges(pps, 0);

    /* Make the profile section permanent. */
    if (hrT == hrSuccess)
        hrT = psup->lpVtbl->ModifyProfile(psup, 0);

    hr = psup->lpVtbl->SetProviderUID(psup, &pims->uidResource, 0L);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetupPrivateNotifications(pims);
    if (hr != hrSuccess)
        goto exit;

    /* This call sets [out] parameters, and should therefore be the last */
    /* call that could fail. */
    hr = HrGetSpoolSecurity(szPath, szPass, &pims->lmr,
        lpcbSpoolSecurity, lppbSpoolSecurity);

    if (hr != hrSuccess)
        goto exit;

    /* Add this new store to the list of open stores. */
    OBJ_Enqueue((POBJ) pims, (POBJ) pmsp);

    UlAddRef(psup);         /* I'm keeping a reference */

    Assert(hr == hrSuccess);    /* no error */

exit:
    if (hr == hrSuccess)
    {
        /* Fill in [out] parameters. */
        *ppmdb = (LPMDB) pims;
        *ppmslogon = (LPMSLOGON) pmsl;
    }
    else
    {
        UlRelease(pims);

        /* Releasing the pmsl must come after releasing the pims. */
        UlRelease(pmsl);

        Assert(*lpcbSpoolSecurity == 0L);
        Assert(*lppbSpoolSecurity == NULL);
        Assert(*lppMapiError == NULL);
        Assert(*ppmslogon == NULL);
        Assert(*ppmdb == NULL);
    }

    UlRelease(pps);

    MSP_LeaveCriticalSection(pmsp);

    DebugTraceResult(MSP_Logon, hr);
    return HrCheckHr(hr, IMSProvider_Logon);
}

/*
 *  MSP_SpoolerLogon
 *
 *  Purpose:
 *      Logs in the Spooler to this message store.
 *
 *  Arguments:
 *      psup            Pointer to MAPI Support object.
 *      ulUIParam           HWND of parent window
 *      szProfileName       Name of profile
 *      cbEntryID           Size of lpEntryID.
 *      lpEntryID           Provider component of store resource
 *                          EntryID.
 *      ulFlags             Flags.  See Logon above, except
 *                          MDB_NO_DIALOG is normally (always?)
 *                          set on this call.
 *      piid                IID of desired interface for ppmdb
 *      cbSpoolSecurity     Size of data in lpbSpoolSecurity.
 *      lpbSpoolSecurity    Validation data necessary for
 *                          SpoolerLogon to complete.
 *      lpulLowLevelError   Possible low level error to return if
 *                          error or MAPI_W_EXTENDED_WARNING is returned.
 *      pszMessage          Error / Warning string on logon.  If MAPI_UNICODE
 *                          is set in ulFlags then it is UNICODE.
 *      ppmslogon           Address to return a pointer to MSLOGON
 *                          object for use by MAPI.DLL.
 *      ppmdb               Address of a pointer to the Spooler
 *                          Message Store Object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      May put up logon screen to select/open store if
 *      MDB_NO_DIALOG is not set.  Calls HrNewIMS to create the
 *      message store object, which allocates memory.
 *
 *  Errors:
 *      MAPI_E_NO_MEMORY        Could not allocate memory.
 *      MAPI_E_LOGON_FAILED     Bad credentials
 */
STDMETHODIMP
MSP_SpoolerLogon(PMSP pmsp, LPMAPISUP psup, ULONG ulUIParam,
    LPTSTR szProfileName, ULONG cbEntryID, LPENTRYID lpEntryID,
    ULONG ulFlags, LPCIID piid, ULONG cbSpoolSecurity,
    LPBYTE lpbSpoolSecurity, LPMAPIERROR * lppMapiError,
    LPMSLOGON * ppmslogon, LPMDB * ppmdb)
{
    TCHAR szPass[ACCTSIZE];     /* Store account password */
    TCHAR szPath[MAX_PATH];     /* path to store root */
    HRESULT hr = hrSuccess;
    PIMS pims = NULL;
    PMSL pmsl = NULL;
    LPPROFSECT pps = NULL;      /* our profile section */

    MSP_CheckParameters(
            pmsp,
            IMSProvider,
            SpoolerLogon,
            (pmsp,
            psup,
            ulUIParam,
            szProfileName,
            cbEntryID,
            lpEntryID,
            ulFlags,
            piid,
            cbSpoolSecurity,
            lpbSpoolSecurity,
            lppMapiError,
            ppmslogon,
            ppmdb));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    MSP_EnterCriticalSection(pmsp);

    AssertSz(!pmsp->fInvalid, "This MSProvider has already been shutdown");

    *lppMapiError = NULL;
    *ppmslogon = NULL;
    *ppmdb = NULL;

    if (piid && !FQueryInterface(OT_MSGSTORE, piid))
    {
        hr = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        goto exit;
    }

    hr = psup->lpVtbl->OpenProfileSection(psup, NULL, MAPI_MODIFY, &pps);
    if (hr != hrSuccess)
        goto exit;

    DecodeSpoolSecurity(cbSpoolSecurity, lpbSpoolSecurity, szPath, szPass);

    DebugTrace("\nSMS: MSP_SpoolerLogon %s\n\n",szPath);

    /* If we can piggyback on an existing open message store */
    /* on this session then do so. If we piggyback, then don't return */
    /* a logon object or addref the support object. */

    if (FAlreadyOpenStore(pmsp, szPath, &pims))
    {
        DebugTrace("\nSMS: Piggybacking spooler logon\n");
        goto exit;
    }

    hr = HrCheckAndOpenStore(pmsp, psup, TRUE, pps, szPath, szPass, TRUE,
        &pmsl, &pims);
    if (hr == hrSuccess)
        hr = psup->lpVtbl->SetProviderUID(psup, &pims->uidResource, 0L);
    if (hr == hrSuccess)
        hr = HrSetupPrivateNotifications(pims);

    if (hr == hrSuccess)
    {
        /* Add this new store to the list of open stores. */
        OBJ_Enqueue((POBJ) pims, (POBJ) pmsp);
        UlAddRef(psup);
    }

exit:
    if (hr == hrSuccess)
    {
        *ppmdb = (LPMDB) pims;
        *ppmslogon = (LPMSLOGON) pmsl;
    }

    UlRelease(pps);

    MSP_LeaveCriticalSection(pmsp);

    DebugTraceResult(MSP_SpoolerLogon, hr);
    return hr;
}

/*
 -  MSL_Release
 -
 */

STDMETHODIMP_(ULONG) MSL_Release(PMSL pmsl)
{
    LONG cRef;

    MSL_EnterCriticalSection(pmsl);

    AssertSz1(pmsl->cRef > 0, "MSL_Release(): Bogus cRef (%08lX)", pmsl->cRef);

    cRef = --pmsl->cRef;

    MSL_LeaveCriticalSection(pmsl);

    if (cRef == 0)
    {
        Assert(pmsl->pims == NULL);
        DeleteCriticalSection(&pmsl->cs);
        LMFree(&pmsl->lmr, pmsl);
    }

    return (cRef);
}

/*
 *  MSL_Logoff()
 */

STDMETHODIMP MSL_Logoff(PMSL pmsl, ULONG * pulFlags)
{
    MSL_CheckParameters(
            pmsl,
            IMSLogon,
            Logoff,
            (pmsl,
            pulFlags));

    MSL_EnterCriticalSection(pmsl);

    AssertSz(!pmsl->fInvalid, "Logon object already logged off");

    if (pmsl->pims)
        OBJ_Destroy((POBJ) pmsl->pims);
    else
        MSL_LeaveCriticalSection(pmsl);

#ifdef DEBUG
    pmsl->fInvalid = TRUE;
#endif

    return (hrSuccess);
}

/*
 *  MSL_OpenStatusEntry()
 *
 *  MAPI has determined that the client call to OpenStatusEntry
 *  is for OUR row in the status table.  Open the Status Object
 *  appropriate for our store.
 *
 *  This object is logically part of the store, and can be made
 *  invalid when the store (either the MSLogon or LPMDB object,
 *  at our choice) goes away.
 */

STDMETHODIMP MSL_OpenStatusEntry(PMSL pmsl, LPCIID lpiid, ULONG ulFlags,
    ULONG * lpulObjType, LPVOID * lppEntry)
{
    MSL_CheckParameters(
    pmsl,
    IMSLogon,
    OpenStatusEntry,
    (pmsl,
    lpiid,
    ulFlags,
    lpulObjType,
    lppEntry));

    AssertSz(!pmsl->fInvalid, "Logon object already logged off");

    DebugTraceSc(MSL_OpenStatusEntry, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

STDAPI SMSServiceEntry(HINSTANCE hInst, LPMALLOC lpMalloc, LPMAPISUP psup,
        ULONG ulUIParam, ULONG ulSEFlags, ULONG ulContext, ULONG cvals,
        LPSPropValue pvals, LPPROVIDERADMIN lpAdminProviders,
        LPMAPIERROR FAR *lppMapiError)
{
    HRESULT             hr;
    LPALLOCATEBUFFER    lpAllocBuff;
    LPALLOCATEMORE      lpAllocMore;
    LPFREEBUFFER        lpFreeBuff      = NULL;
    PMSP                pmsp            = NULL;
    LPPROFSECT          pps             = NULL;
    SERVICEPBLK         spb;
    ULONG               ulProvVer;
    ULONG               cvalsProf;
    LPSPropValue        pvalsProf       = NULL;

    /* check the array of profile section objects */
    if (IsBadReadPtr(psup, sizeof(LPMAPISUP))
        ||  (cvals && IsBadReadPtr(pvals, (UINT)(sizeof(SPropValue) * cvals))))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    if (ulSEFlags & ~(MAPI_UNICODE
                    | MSG_SERVICE_UI_READ_ONLY
                    | SERVICE_UI_ALWAYS     /* Same value as UI_SERVICE */
                    | SERVICE_UI_ALLOWED    /* New */
                    | UI_CURRENT_PROVIDER_FIRST
                    | SERVICE_LOGON_FAILED))
        return ResultFromScode(MAPI_E_UNKNOWN_FLAGS);

    if (ulSEFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);

    if (    (ulContext == MSG_SERVICE_INSTALL)
        ||  (ulContext == MSG_SERVICE_UNINSTALL)
        ||  (ulContext == MSG_SERVICE_DELETE))
        return (hrSuccess);

    if (ulContext != MSG_SERVICE_CONFIGURE && ulContext != MSG_SERVICE_CREATE)
        return (ResultFromScode(MAPI_E_NO_SUPPORT));

    /* get the profile section for the provider */
    hr = HrOpenSingleProvider(lpAdminProviders, &pps);
    if (hr != hrSuccess)
        goto err;

    /*  Get the memory allocation routines we'll be needing. */
    hr = psup->lpVtbl->GetMemAllocRoutines(psup, &lpAllocBuff, &lpAllocMore,
            &lpFreeBuff);
    if (hr != hrSuccess)
        goto err;

    hr = MSProviderInit(hInst, lpMalloc, lpAllocBuff, lpAllocMore,
            lpFreeBuff, 0, CURRENT_SPI_VERSION, &ulProvVer,
            &(LPMSPROVIDER)pmsp);
    if (hr != hrSuccess)
        goto err;

    /* get the properties out of the profile section that we will be */
    /* be needing */

    hr = pps->lpVtbl->GetProps(pps, (LPSPropTagArray) &ptaConfig, 0,
            &cvalsProf, &pvalsProf);
    if (HR_FAILED(hr))
        goto err;

    Assert (PROP_ID(pvalsProf[0].ulPropTag) == PROP_ID(PR_SMS_PATH));
    Assert (PROP_ID(pvalsProf[1].ulPropTag) == PROP_ID(PR_SMS_PASSWORD));
    Assert (PROP_ID(pvalsProf[2].ulPropTag) == PROP_ID(PR_SMS_REMEMBER_PW));
    Assert (PROP_ID(pvalsProf[3].ulPropTag) == PROP_ID(PR_SMS_CREATE));

    /* initialize the service parameter block */
    spb.pmsp            = pmsp;
    spb.pps             = pps;
    spb.psup            = psup;
    spb.ulUIParam       = ulUIParam;
    spb.ulSEFlags       = ulSEFlags;
    spb.ulContext       = ulContext;
    spb.cvalsProf       = cvalsProf;
    spb.pvalsProf       = pvalsProf;
    spb.cvalsClient     = cvals;
    spb.pvalsClient     = pvals;

    hr = ResultFromScode(ScCreateOrConfigure(&spb));

err:
    if (lpFreeBuff)
        (*lpFreeBuff)(pvalsProf);

    UlRelease(pmsp);
    UlRelease(pps);

    DebugTraceResult(SMSServiceEntry, hr);
    return hr;
}

/*
 *  Module internal functions
 */

static HRESULT
HrOpenSingleProvider(LPPROVIDERADMIN padp, LPPROFSECT FAR *lppProfSect)
{
    HRESULT     hr;
    LPMAPITABLE pmt = NULL;
    LPSRowSet   prws = NULL;
    LPSPropValue pval;
    SPropTagArray ptaProvUID = {1, {PR_PROVIDER_UID}};

    hr = padp->lpVtbl->GetProviderTable(padp, 0, &pmt);
    if (hr)
        goto err;

    hr = pmt->lpVtbl->SetColumns(pmt, &ptaProvUID, 0L);
    if (hr)
        goto err;

    hr = pmt->lpVtbl->QueryRows(pmt, 1, 0, &prws);
    if (hr || prws == NULL || prws->cRows == 0)
    {
        hr = ResultFromScode(MAPI_E_NOT_FOUND);
        goto err;
    }

    Assert(prws->aRow[0].cValues == 1);
    pval = prws->aRow[0].lpProps;
    if (!pval || pval->ulPropTag != PR_PROVIDER_UID)
    {
        /* The property we need is missing. Error. */
        hr = ResultFromScode(MAPI_E_NOT_FOUND);
        goto err;
    }

    /* Found the right property; use it to open our section */
    hr = padp->lpVtbl->OpenProfileSection(padp,
        (LPMAPIUID) pval->Value.bin.lpb, NULL, MAPI_MODIFY, lppProfSect);

err:
    FreeProws(prws);
    UlRelease(pmt);

    DebugTraceResult(HrOpenSingleProvider, hr);
    return hr;
}

static SCODE ScCreateOrConfigure(PSERVICEPBLK pspb)
{
    SCODE           sc              = S_OK;
    LPSPropValue    pval;
    LPSTR           pszFileTitle;
    CHAR            szPath[MAX_PATH];
    CHAR            szPass[ACCTSIZE];
    LPPROFSECT      pps             = pspb->pps;
    BOOL            fNeedUI         = FALSE;
    BOOL            fAllowUI        = FALSE;
    BOOL            fCreate         = FALSE;
    BOOL            fRemember       = FALSE;

    /* Check for UI */

    if (pspb->ulSEFlags & SERVICE_UI_ALWAYS)
        fAllowUI = fNeedUI = TRUE;
    else
        if (pspb->ulSEFlags & SERVICE_UI_ALLOWED)
            fAllowUI = TRUE;

    if (FFindPropInPSPB(pspb, PR_SMS_PASSWORD, &pval))
    {
        ULONG cbPass;

        /* Passwords must fit in ACCTSIZE bytes. Truncate the password if */
        /* it's too long. */

        cbPass = lstrlenA(pval->Value.lpszA) + 1;
        if (cbPass > ACCTSIZE)
            cbPass = ACCTSIZE;

        memcpy(szPass, pval->Value.lpszA, (UINT) cbPass);
        szPass[cbPass - 1] = '\0';

        /* We found the password. Since we found it, assume that the */
        /* user wants us to remember it. Only if PR_SMS_REMEMBER_PW is */
        /* present and FALSE will we remove the password. */

        fRemember = TRUE;
    }
    else
        szPass[0] = '\0';

    if (FFindPropInPSPB(pspb, PR_SMS_CREATE, &pval))
        fCreate = pval->Value.b;

    if (FFindPropInPSPB(pspb, PR_SMS_REMEMBER_PW, &pval))
        fRemember = pval->Value.b;

    /* try to find the path in either the properties passed in, or */
    /* the properties in the profile */

    if (    FFindPropInPSPB(pspb, PR_SMS_PATH, &pval)
        &&  GetFullPathName(pval->Value.lpszA, MAX_PATH, szPath,
                &pszFileTitle))
    {
        DWORD   dwFA;

        Assert (*(pszFileTitle - 1) == '\\');

        dwFA = GetFileAttributes(szPath);

        /* If the file isn't there, and we aren't creating, then error. */
        /* If the file is there, and we are creating, then error. */

        if (dwFA == 0xFFFFFFFF && fCreate == FALSE)
        {
            sc = MAPI_E_NOT_FOUND;
            fNeedUI = TRUE;
        }
        else if (dwFA != 0xFFFFFFFF && fCreate == TRUE)
        {
            sc = MAPI_E_NO_ACCESS;
            fNeedUI = TRUE;
        }
    }
    else
    {
        szPath[0] = '\0';
        sc = MAPI_E_NOT_FOUND;
        fNeedUI = TRUE;
    }

    while (TRUE)
    {
        if (sc == S_OK)
        {
            PMSL pmsl;
            PIMS pims;

            /* Verify that everything works before we exit. */

            if (fCreate)
                sc = ScCreateStore(pspb, szPath, szPass, &pmsl, &pims);
            else
                sc = GetScode(HrCheckAndOpenStore(pspb->pmsp, pspb->psup,
                    TRUE, pspb->pps, szPath, szPass, FALSE, &pmsl, &pims));
            if (sc)
                fNeedUI = TRUE;
            else
            {
                HRESULT hrT;

                /* delete the unnecessary properties from the profile */
                pps->lpVtbl->DeleteProps(pps, (LPSPropTagArray)&ptaDel,
                    NULL);

                /* Update saved credentials if they have changed. */

                hrT = HrSetProfileCred(pps, szPass, szPath, fRemember,
                    pims->eidStore.cb, pims->eidStore.lpb,
                    &pims->uidResource);

                if (hrT == hrSuccess)
                    hrT = pps->lpVtbl->SaveChanges(pps, 0);

                /* Can't make the profile section permanent because */
                /* the support object we are given for config doesn't */
                /* support ModifyProfile. Make it permanent during Logon. */

                /* Addref the support object because we kept a reference */
                /* in pims, and releasing pims will release psup. We want */
                /* the refcount on psup to be the same at config exit as */
                /* it was when we were called. */

                UlAddRef(pspb->psup);

                UlRelease(pims);
                UlRelease(pmsl);

                if (!fNeedUI)
                    goto ret;   /* SUCCESS! */
            }
        }

        if (fNeedUI && !fAllowUI)
        {
            sc = MAPI_E_UNCONFIGURED;
            goto ret;
        }

        /* Do the UI */

        sc = GetScode(HrGetLogonDlgCred(pspb->pmsp->hInst, pspb->ulUIParam,
            szPath, szPass, &fCreate, &fRemember));

        if (sc != S_OK)
            goto ret;

        fNeedUI = FALSE;
    }

ret:
    DebugTraceSc(ScCreateOrConfigure, sc);
    return sc;
}

/*
 -  ScCreateStore
 -
 *  Create a Sample Store out on disk.
 *
 *  Returns the pims which points to the store, and the
 *  pmsl which points to the MSLogon object.
 */
static SCODE ScCreateStore(PSERVICEPBLK pspb, LPTSTR szPath,
    LPTSTR szPass, PMSL * ppmsl, PIMS * ppims)
{
    HRESULT hr = hrSuccess;
    SCODE sc = S_OK;
    PRFS prfs = NULL;           /* Struct for receive folder settings */
    BOOL fCreateDir = FALSE;    /* Was new directory created? */
    BOOL fCreateProp = FALSE;   /* Was new MsgStore props file created? */
    BOOL fCreateFld = FALSE;    /* Was new root folder created? */
    BOOL fCreateRFS = FALSE;    /* Was receive folder file created? */
    PIMS pims = NULL;
    PMSL pmsl = NULL;

    /* Create the directory */

    if (!CreateDirectory(szPath, NULL))
    {
        sc = MAPI_E_CALL_FAILED;
        goto exit;
    }

    fCreateDir = TRUE;

    /* Create file to hold receive folder settings */

    hr = OpenRFS(szPath, szFolderFileName, RFS_CREATE, &prfs);
    if (hr != hrSuccess)
    {
        sc = GetScode(hr);
        goto exit;
    }

    fCreateRFS = TRUE;

    /* Create the MSLogon object. */

    sc = ScCreateMSL(pspb->pmsp, &pmsl);
    if (sc != S_OK)
        goto exit;

    /* Create instance, creates Message Store props file as well */

    hr = HrNewIMS(szPath, szPropFileName, pspb->pmsp, pmsl, prfs,
            pspb->pps, pspb->psup, TRUE, &pims);
    if (hr != hrSuccess)
    {
        sc = GetScode(hr);
        goto exit;
    }

    OBJ_SetFlag(pims, OBJF_MODIFY);
    pmsl->pims = pims;

    fCreateProp = TRUE;

    hr = HrInitIMSProps(pims, szPass);
    if (hr != hrSuccess)
    {
        sc = GetScode(hr);
        goto exit;
    }

    /* Create file to hold root folder properties */

    {
        PEID peidTmp = NULL;

        hr = HrCreateFolderStorage(NULL, FOLDER_ROOT, "", "",
            FALSE, pims, &peidTmp);

        if (hr != hrSuccess)
        {
            sc = GetScode(hr);
            goto exit;
        }

        /* We can throw away the returned EntryID because we know */
        /* how to construct the EntryID for the root folder when  */
        /* we need to open it (see pims->OpenEntry()).           */

        LMFree(&pspb->pmsp->lmr, peidTmp);
    }

    fCreateFld = TRUE;

    *ppmsl = pmsl;
    *ppims = pims;

exit:
    if (sc != S_OK)
    {
        TCHAR rgch[MAX_PATH];

        if (pims)
        {
            FreeNull(pims->szStorePath);
            LMFree(&pspb->pmsp->lmr, pims->eidStore.lpb);
            LMFree(&pspb->pmsp->lmr, pims);
        }
        else if (prfs)
        {
            /* If we have an RFS but NO IMS, close RFS manually. */
            NFSideAssertSz(CloseRFS(prfs) == hrSuccess, "RFS not closed");
        }

        if ( pmsl )
        {
            pmsl->pims = NULL;

            /* Releasing the pmsl must come after releasing the IMS. */
            UlRelease(pmsl);
        }

        /* We need to use state flags to know how much work to undo  */
        /* on a create because otherwise we may accidentally erase   */
        /* files that we did not create (because they were created   */
        /* by another Sample Store creation in a different session). */

        if (fCreateProp == TRUE)
        {
            if (FAppendPathNoMem(szPath, szPropFileName, MAX_PATH, rgch))
                (void)DeleteFile(rgch);
            else
                TrapSz("Can't clean up create #1!");
        }

        if (fCreateRFS == TRUE)
        {
            if (FAppendPathNoMem(szPath, szFolderFileName, MAX_PATH, rgch))
                (void)DeleteFile(rgch);
            else
                TrapSz("Can't clean up create #2!");
        }

        if (fCreateFld == TRUE)
        {
            if (FAppendPathNoMem(szPath, szPropertyFileName, MAX_PATH, rgch))
                (void)DeleteFile(rgch);
            else
                TrapSz("Can't clean up create #3!");
        }

        if (fCreateDir == TRUE)
            (void)RemoveDirectory(szPath);
    }

    DebugTraceSc(ScCreateStore, sc);
    return sc;
}

/*
 *  FAlreadyOpenStore
 *
 *  Purpose:
 *      Searches the list of stores open in this MAPI session and
 *      returns the MS object of a store that matches the path
 *      that's passed in.
 *
 *  Arguments:
 *      pmsp    Pointer to the Message Store Provider (MAPI Session
 *              context).  This is where the list of open stores
 *              lives.
 *      szPath  String containing full path to the store to open.
 *      ppims   Location in which to return the address of a
 *              message store object already open (only valid if
 *              the function returns TRUE).
 *
 *  Returns:
 *      BOOL.  Will be TRUE if the message store is already open,
 *      FALSE if not.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
static BOOL FAlreadyOpenStore(PMSP pmsp, LPTSTR szPath, PIMS * ppims)
{
    PIMS pimsPrev = NULL;

    for (pimsPrev = (PIMS) pmsp->pobjHead; pimsPrev;
        pimsPrev = (PIMS) pimsPrev->pobjNext)
    {
        if (!lstrcmpi(pimsPrev->szStorePath, szPath))
        {
            OBJ_EnterCriticalSection(pimsPrev);

            if(!OBJ_TestFlag(pimsPrev, MSF_BEINGDESTROYED))
            {
                *ppims = pimsPrev;
                UlAddRef(pimsPrev);

                OBJ_LeaveCriticalSection(pimsPrev);
                return TRUE;
            }

            DebugTrace("SMS: Attempt to OpenMsgStore at %s while a another IMsgStore\n"
                        "object opened on this directory is being released\n", szPath);

            OBJ_LeaveCriticalSection(pimsPrev);
        }
    }

    return FALSE;
}

/*
 *  HrGetPassword
 *
 *  Purpose:
 *      Get the password for a store interactively by putting up a dialog.
 *
 *  Parameters:
 *      hInst       [in] Instance handle of my DLL
 *      psup        [in] Pointer to the mapi support object.
 *      plmr        [in] Pointer to the memory allocation routines.
 *      ulUIParam   [in] Window handle cast to ULONG.
 *      szPath      [in] The pathname to the top of the message store.
 *      szPass      [out] Pointer to a buffer in which to place the password.
 *      pfRemember  [out] Address of boolean: should the password be saved
 *                        in profile?
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Puts up password dialog.
 *
 *  Errors:
 *      None.
 */
static HRESULT HrGetPassword(HINSTANCE hInst, LPMAPISUP psup, PLMR plmr,
    ULONG ulUIParam, LPTSTR szPath, LPTSTR szPass, BOOL *pfRemember)
{
    HRESULT hr = hrSuccess;
    DLGPSWD dlgpswd;

    NFAssertSz(szPass, "Bad szPass");

    lstrcpy(dlgpswd.szPath, szPath);
    dlgpswd.psup = psup;
    dlgpswd.plmr = plmr;
    dlgpswd.fRemember = FALSE;
    dlgpswd.fCancel = FALSE;
    dlgpswd.lpCtl3d = CTL3D_Initialize(hInst);

    if (DialogBoxParam(hInst, TEXT("PSWDDIALOGBOX"), (HWND) ulUIParam,
            (DLGPROC) PasswordDlgProc, (LPARAM) &dlgpswd) == -1)
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    if (dlgpswd.fCancel)
    {
        hr = ResultFromScode(MAPI_E_USER_CANCEL);
        goto exit;
    }

    if (szPass)
        lstrcpy(szPass, dlgpswd.szPass);

    if (pfRemember)
        *pfRemember = dlgpswd.fRemember;

    Assert(hr == hrSuccess);

exit:
    CTL3D_Uninitialize(dlgpswd.lpCtl3d);
    DebugTraceResult(HrGetPassword, hr);
    return hr;
}

/*
 *  PasswordDlgProc
 *
 *  Purpose:
 *      Dialog procedure to handle password dialog events.
 *
 *  Parameters:
 *      hDlg        Handle to the dialog.
 *      message     Message to be handled.
 *      wParam      Button ID if button is pressed.
 *      lParam      Context parameter from dialog creation (see
 *                  HrGetLogonDlgCred).
 *
 *  Returns:
 *      BOOL.  TRUE if handled, FALSE if not
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDAPI_(BOOL) PasswordDlgProc(HWND hDlg, UINT message, WPARAM wParam,
    LPARAM lParam)
{
    PDLGPSWD pdlgpswd;
    BOOL fHandled = FALSE;

    AssertSz(hDlg, "Bad hDlg");

    switch (message)
    {
    case WM_INITDIALOG:
        {
            SetWindowLong(hDlg, DWL_USER, lParam);
            pdlgpswd = (PDLGPSWD) lParam;

            CTL3D_Subclass(pdlgpswd->lpCtl3d, hDlg, CTL3D_ALL);

#ifdef _WIN32
            SetForegroundWindow(hDlg);
#endif
            BringWindowToTop(hDlg);

            SetDlgItemText(hDlg, TEXT_PATH, pdlgpswd->szPath);

            pdlgpswd->fRemember = FALSE;

            fHandled = TRUE;
            break;
        }

    case WM_COMMAND:
        {
            pdlgpswd = (PDLGPSWD) GetWindowLong(hDlg, DWL_USER);

            switch (LOWORD(wParam))
            {
            case IDOK:
                {
                    GetDlgItemText(hDlg, IDE_ACCOUNT_PASSWORD,
                        pdlgpswd->szPass, ACCTSIZE * sizeof(TCHAR));

                    if (HrConfirmCred(pdlgpswd->szPath, pdlgpswd->szPass,
                        pdlgpswd->psup, pdlgpswd->plmr) != hrSuccess)
                    {
                        /* put up a message box */
                        MessageBox(hDlg, "Password is incorrect.",
                            "Sample Store Password",
                            MB_OK | MB_APPLMODAL | MB_SETFOREGROUND);

                        /* highlight the password field */
                        SetFocus(hDlg);
                        break;
                    }

                    pdlgpswd->fRemember =
                        IsDlgButtonChecked(hDlg, IDC_REMEMBER);

                    EndDialog(hDlg, 0);
                    fHandled = TRUE;
                    break;
                }
            case IDCANCEL:
                {
                    pdlgpswd->fCancel = TRUE;
                    EndDialog(hDlg, 0);
                    fHandled = TRUE;
                    break;
                }
            default:
                {
                    fHandled = FALSE;
                    break;
                }
            }
        }

    default:
        {
            fHandled = FALSE;
            break;
        }
    }

    return fHandled;
}

/*
 *  HrGetLogonDlgCred
 *
 *  Purpose:
 *      Obtain credentials interactively for opening a store by
 *      putting up a dialog.
 *
 *  Parameters:
 *      hInst       Instance of my DLL
 *      ulUIParam   Window handle cast to ULONG.
 *      szPath      Address of buffer in which to place the path to
 *                  the root directory.
 *      szPass      Address of buffer in which to place the logon
 *                  Account Password.
 *      pfCreate    Address of boolean: should store be created?
 *      pfRemember  Address of boolean: should password be saved in profile?
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Puts up dialog.
 *
 *  Errors:
 *      None.
 */
static HRESULT HrGetLogonDlgCred(HINSTANCE hInst, ULONG ulUIParam,
    LPTSTR szPath, LPTSTR szPass, BOOL *pfCreate, BOOL *pfRemember)
{
    HRESULT hr = hrSuccess;
    LGNCRED lgncred;

    NFAssertSz(szPass, "Bad szPass");

    lstrcpy(lgncred.szPath, szPath);
    lstrcpy(lgncred.szPass, szPass);
    lgncred.fCreate = FALSE;
    lgncred.fRemember = *pfRemember;
    lgncred.fCancel = FALSE;
    lgncred.ulFieldMask = (szPath ? LGNCRED_PATH : 0L)
                        | (szPass ? LGNCRED_PASS : 0L)
                        | (pfCreate ? LGNCRED_CREATE : 0L)
                        | LGNCRED_REMEMBER;

    lgncred.lpCtl3d = CTL3D_Initialize(hInst);

    if (DialogBoxParam(hInst, TEXT("LOGONDIALOGBOX"), (HWND) ulUIParam,
            (DLGPROC) LogonDlgProc, (LPARAM) &lgncred) == -1)
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    if (lgncred.fCancel)
    {
        hr = ResultFromScode(MAPI_E_USER_CANCEL);
        goto exit;
    }

    if (szPath)
    {
        DWORD dwPathLength = 0L;
        LPTSTR szPathName = NULL;

        dwPathLength = GetFullPathName(lgncred.szPath, MAX_PATH,
            szPath, &szPathName);
        if (!dwPathLength || dwPathLength > MAX_PATH)
        {
            hr = ResultFromScode(MAPI_E_UNCONFIGURED);
            goto exit;
        }
    }

    if (szPass)
        lstrcpy(szPass, lgncred.szPass);

    if (pfCreate)
        *pfCreate = lgncred.fCreate;

    *pfRemember = lgncred.fRemember;

    Assert(hr == hrSuccess);

exit:
    CTL3D_Uninitialize(lgncred.lpCtl3d);

    DebugTraceResult(HrGetLogonDlgCred, hr);
    return hr;
}

/*
 *  LogonDlgProc
 *
 *  Purpose:
 *      Dialog procedure to handle logon events.
 *
 *  Parameters:
 *      hDlg        Handle to the dialog.
 *      message     Message to be handled.
 *      wParam      Button ID if button is pressed.
 *      lParam      Context parameter from dialog creation (see
 *                  HrGetLogonDlgCred).
 *
 *  Returns:
 *      BOOL.  TRUE if handled, FALSE if not
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDAPI_(BOOL) LogonDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    PLGNCRED plgncred;
    BOOL fHandled = FALSE;

    AssertSz(hDlg, "Bad hDlg");

    switch (message)
    {
    case WM_INITDIALOG:
        {
            SetWindowLong(hDlg, DWL_USER, lParam);
            plgncred = (PLGNCRED) lParam;

            CTL3D_Subclass(plgncred->lpCtl3d, hDlg, CTL3D_ALL);

#ifdef _WIN32
            SetForegroundWindow(hDlg);
#endif
            BringWindowToTop(hDlg);

            if (plgncred->ulFieldMask & LGNCRED_PATH)
                SetDlgItemText(hDlg, IDE_ROOT_PATH, plgncred->szPath);
            else
                EnableWindow(GetDlgItem(hDlg, IDE_ROOT_PATH), FALSE);

            if (plgncred->ulFieldMask & LGNCRED_PASS)
                SetDlgItemText(hDlg, IDE_ACCOUNT_PASSWORD, plgncred->szPass);
            else
                EnableWindow(GetDlgItem(hDlg, IDE_ACCOUNT_PASSWORD), FALSE);

            if (!(plgncred->ulFieldMask & LGNCRED_CREATE))
                EnableWindow(GetDlgItem(hDlg, IDC_CREATE), FALSE);

            if (plgncred->ulFieldMask & LGNCRED_REMEMBER)
                CheckDlgButton(hDlg, IDC_REMEMBER, plgncred->fRemember);
            else
                EnableWindow(GetDlgItem(hDlg, IDC_REMEMBER), FALSE);

            fHandled = TRUE;
            break;
        }

    case WM_COMMAND:
        {
            plgncred = (PLGNCRED) GetWindowLong(hDlg, DWL_USER);

            switch (LOWORD(wParam))
            {
            case IDOK:
                {
                    if (plgncred->ulFieldMask & LGNCRED_CREATE)
                    {
                        plgncred->fCreate =
                            IsDlgButtonChecked(hDlg, IDC_CREATE);
                    }

                    if (plgncred->ulFieldMask & LGNCRED_PATH)
                    {
                        GetDlgItemText(hDlg, IDE_ROOT_PATH,
                            plgncred->szPath, MAX_PATH * sizeof(TCHAR));
                    }

                    /* check if this is a valid path */
                    if (!FIsValidPath(plgncred->szPath, plgncred->fCreate))
                    {
                        /* put up a message box */
                        MessageBox(hDlg, (plgncred->fCreate) ?
                            "Root directory can not be created." :
                            "Path to root directory not found.",
                            "Message Store Logon", MB_OK | MB_APPLMODAL | MB_SETFOREGROUND);

                        /* highlight the path field */
                        SetFocus(hDlg);
                        break;
                    }

                    if (plgncred->ulFieldMask & LGNCRED_PASS)
                    {
                        GetDlgItemText(hDlg, IDE_ACCOUNT_PASSWORD,
                            plgncred->szPass, ACCTSIZE * sizeof(TCHAR));
                    }

                    if (plgncred->ulFieldMask & LGNCRED_REMEMBER)
                    {
                        plgncred->fRemember =
                            IsDlgButtonChecked(hDlg, IDC_REMEMBER);
                    }

                    EndDialog(hDlg, 0);
                    fHandled = TRUE;
                    break;
                }
            case IDCANCEL:
                {
                    plgncred->fCancel = TRUE;
                    EndDialog(hDlg, 0);
                    fHandled = TRUE;
                    break;
                }
            default:
                {
                    fHandled = FALSE;
                    break;
                }
            }
        }

    default:
        {
            fHandled = FALSE;
            break;
        }
    }

    return fHandled;
}

/*
 *  HrGetProfileCred
 *
 *  Purpose:
 *      Obtain password programmatically by reading the
 *      appropriate property out of the profile. If the
 *      property isn't there, don't fail the call. Return
 *      a NULL for the password, and clear the *pfFound flag.
 *
 *  Parameters:
 *      plmr    Pointer to the MAPI linked memory routines
 *      psup    Pointer to the MAPI support object (from which
 *              we can get the current profile section).
 *      szPass  Address of buffer in which to place the Logon
 *              Account Password.
 *      pfFound TRUE if the password was found in the profile.
 *              FALSE if the password wasn't in the profile.
 *
 *  Returns:
 *      HRESULT
 *
 *  Errors:
 *      None.
 */
static HRESULT
HrGetProfileCred(PLMR plmr, LPPROFSECT pps, LPTSTR szPass, BOOL *pfFound)
{
    HRESULT hr;
    LPSPropValue pval = NULL;

    AssertSz(szPass, "Bad szPass");
    AssertSz(pps, "No profile section");

    hr = HrGetOneProp((LPMAPIPROP) pps, PR_SMS_PASSWORD, &pval);

    if (hr != hrSuccess)
    {
        *pfFound = FALSE;
        szPass[0] = (TCHAR) 0;
        hr = hrSuccess;
    }
    else
    {
        lstrcpy(szPass, pval[0].Value.LPSZ);
        *pfFound = TRUE;
    }

    LMFree(plmr, pval);

    DebugTraceResult(HrGetProfileCred, hr);
    return hr;
}

/*
 *  HrSetProfileCred
 *
 *  Purpose:
 *      Save credential information in the profile for future
 *      programmatic opening.
 *
 *  Parameters:
 *      pps         Pointer to the profile section.
 *      szPass      Logon Account Password.
 *      szPath      Path name of root of the store
 *      fRemember   TRUE means save the password in the store.
 *      cbEntryID   Number of bytes in lpEntryID
 *      lpEntryID   Pointer to Wrapped Store EntryID
 *      puidRK      pointer to PR_RECORD_KEY data (always &pims->uidResource)
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
static HRESULT HrSetProfileCred(LPPROFSECT pps, LPTSTR szPass, LPTSTR szPath,
    BOOL fRemember, ULONG cbEntryID, LPBYTE lpEntryID, LPMAPIUID puidRK)
{
    HRESULT hr = hrSuccess;
    LPTSTR szDisplayName = NULL;
    SPropValue rgProps[7];
    ULONG cValues;

    AssertSz(pps, "Bad profile section");
    AssertSz(szPass, "Bad szPass");
    AssertSz(lpEntryID, "Bad lpEntryID");

    /* fill in the Display Name */
    hr = HrAlloc((lstrlen(szDisplayPrefix) + lstrlen(szPath) + 1)
            * sizeof(TCHAR), &szDisplayName);
    if (hr != hrSuccess)
        goto hr_err;

    lstrcpy(szDisplayName, szDisplayPrefix);
    lstrcat(szDisplayName, szPath);

    rgProps[0].ulPropTag = PR_PROVIDER_DISPLAY;
    rgProps[0].Value.LPSZ = szProviderDisplayName;

    rgProps[1].ulPropTag = PR_DISPLAY_NAME;
    rgProps[1].Value.LPSZ = szDisplayName;

    rgProps[2].ulPropTag = PR_ENTRYID;
    rgProps[2].Value.bin.cb = cbEntryID;
    rgProps[2].Value.bin.lpb = lpEntryID;

    rgProps[3].ulPropTag = PR_SMS_PATH;
    rgProps[3].Value.LPSZ = szPath;

    rgProps[4].ulPropTag = PR_MDB_PROVIDER;
    rgProps[4].Value.bin.cb = sizeof(MAPIUID);
    rgProps[4].Value.bin.lpb = (LPBYTE) &uidProvider;

    rgProps[5].ulPropTag = PR_RECORD_KEY;
    rgProps[5].Value.bin.cb = sizeof(MAPIUID);
    rgProps[5].Value.bin.lpb = (LPBYTE) puidRK;

    cValues = 6;

    if (fRemember)
    {
        rgProps[6].ulPropTag = PR_SMS_PASSWORD;
        rgProps[6].Value.LPSZ = szPass;
        ++cValues;
    }
    else
    {
        /* If the user doesn't want to remember the password, then we */
        /* should try to remove it from the profile section. */

        (void) pps->lpVtbl->DeleteProps(pps, &sptaPass, NULL);
    }

    hr = pps->lpVtbl->SetProps(pps, cValues, rgProps, NULL);
    if (hr != hrSuccess)
        goto hr_err;

hr_err:
    FreeNull(szDisplayName);

    DebugTraceResult(HrSetProfileCred, hr);
    return hr;
}

/*
 *  HrConfirmCred
 *
 *  Purpose:
 *      Check user's store opening credentials against the account
 *      information in the store.
 *
 *  Arguments:
 *      szPath      Path to "root" directory of store.
 *      szPass      Logon Account Password.
 *      psup        Pointer to a MAPI Support object (passed to IMsgOnIStg).
 *      plmr        Pointer to the linked mem alloc routines.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 */
static HRESULT HrConfirmCred(LPTSTR szPath, LPTSTR szPass, LPMAPISUP psup,
    PLMR plmr)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL;
    LPSPropValue pval = NULL;
    LPTSTR szT = NULL;

    AssertSz(szPath, "Bad szPath");
    AssertSz(szPass, "Bad szPass");

    /* Open the message store props file as an IMessage instance. */

    hr = HrAppendPath(szPath, szPropFileName, &szT);
    if (hr != hrSuccess)
        goto hr_err;

    hr = HrOpenIMSPropsFileRetry(NULL, szT, plmr, psup, FALSE, &lpmsg);
    if (hr != hrSuccess)
        goto hr_err;

    /* Get password from the message store props file   */
    /* and compare against the function parameter.      */

    hr = HrGetOneProp((LPMAPIPROP) lpmsg, PR_SMS_PASSWORD, &pval);

    if (hr != hrSuccess)
        goto hr_err;

    if (lstrcmp(szPass, pval[0].Value.LPSZ))
        hr = ResultFromScode(MAPI_E_LOGON_FAILED);

hr_err:
    FreeNull(szT);
    UlRelease(lpmsg);
    LMFree(plmr, pval);

    DebugTraceResult(HrConfirmCred, hr);
    return hr;
}

/*
 * HrFilePathExists
 *
 * Purpose:
 *      Helper function for HrCheckStoreIntegrity. This function creates a
 *      full pathname to the file given, and attempts to find it on disk.
 *
 * Returns:
 *      HRESULT
 *
 *      If the file exists, and no other errors occur, the function returns
 *      success. If the file doesn't exist, the function returns
 *      MAPI_E_CORRUPT_STORE. If the file exists, but the find can't be closed,
 *      the function returns MAPI_E_LOGON_FAILED.
 */
static HRESULT HrFilePathExists(LPTSTR szStorePath, LPTSTR szFileName)
{
    LPTSTR szT = NULL;
    WIN32_FIND_DATA filedata;
    HANDLE hFindFile;
    HRESULT hr;

    hr = HrAppendPath(szStorePath, szFileName, &szT);
    if (hr != hrSuccess)
        goto exit;

    hFindFile = FindFirstFile(szT, &filedata);
    if (hFindFile == INVALID_HANDLE_VALUE)
    {
        hr = ResultFromScode(MAPI_E_CORRUPT_STORE);
        goto exit;
    }

    if (!FindClose(hFindFile))
        hr = ResultFromScode(MAPI_E_LOGON_FAILED);

exit:
    FreeNull(szT);
    return hr;
}

/*
 *  HrCheckStoreIntegrity
 *
 *  Purpose:
 *      Quick check to see that all the necessary store components
 *      exist in the proper place (store root).  The check of file
 *      attributes assumes that OLE created its docfiles with
 *      normal file attributes.
 *
 *  Arguments:
 *      szPath      Path to "root" directory of store.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_CORRUPT_STORE    A necessary store component was
 *                              missing.
 */
static HRESULT HrCheckStoreIntegrity(LPTSTR szStorePath)
{
    HRESULT hr;

    AssertSz(!IsBadStringPtr(szStorePath, (UINT) -1), "Bad szStorePath");

    /* Check for the message store properties file */

    hr = HrFilePathExists(szStorePath, szPropFileName);
    if (hr != hrSuccess)
        goto hr_err;

    /* Check for the root folder properties file */

    hr = HrFilePathExists(szStorePath, szPropertyFileName);
    if (hr != hrSuccess)
        goto hr_err;

    /* Check for the receive folder settings storage file */

    hr = HrFilePathExists(szStorePath, szFolderFileName);

hr_err:
    DebugTraceResult(HrCheckStoreIntegrity, hr);
    return hr;
}

/*
 *  FIsValidPath
 *
 *  Purpose:
 *      Returns TRUE if the path in szPath is a valid path to
 *      to an existing directory if fCreate is FALSE. Or the
 *      specified directory can be created if fCreate is TRUE.
 *
 *  Arguments:
 *      szPath      Path to "root" directory of store.
 *      fCreate         TRUE if directory is to be created.
 *
 *  Returns:
 *      BOOL
 *
 *  Side effects:
 *      None.
 *
 */
static BOOL FIsValidPath(LPTSTR szPath, BOOL fCreate)
{
    LPTSTR szLastSlash = NULL;  /* pointer to last slash in szPath */
    DWORD dwAttributes;

    if (*szPath == '\0')
        return FALSE;

    /* if creating a root folder, the folder to check is the parent */
    /* of the one in szPath */
    if (fCreate)
    {
        /* must not already exist */
        if (GetFileAttributes(szPath) != -1)
            return FALSE;

        /* parent directory must exist */
        szLastSlash = SzFindLastCh(szPath, '\\');
        if (szLastSlash && szLastSlash != szPath &&
            *(szLastSlash - 1) != ':')
        {
            *szLastSlash = '\0';
        }
        else
        {
            /* creating a directory at the root level */
            return TRUE;
        }
    }

    /* get the attributes of the directory that must exist */
    dwAttributes = GetFileAttributes(szPath);

    /* restore szPath */
    if (szLastSlash)
        *szLastSlash = '\\';

    return ((dwAttributes != -1) && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)) ?
        TRUE : FALSE;
}

/*
 * HrGetSpoolSecurity
 *
 * Purpose
 *          return the spooler security information for use in SpoolerLogon
 *
 * Parameters
 *  szPath              path to root of store
 *  szPass              store account password
 *  plmr                pointer to the MAPI linked allocation routines
 *  lpcbSpoolSecurity   byte count of the security information
 *  lppbSpoolSecurity   security information
 */
static HRESULT HrGetSpoolSecurity(LPTSTR szPath, LPTSTR szPass,
    PLMR plmr, ULONG * lpcbSpoolSecurity, LPBYTE * lppbSpoolSecurity)
{
    ULONG cbPath = Cbtszsize(szPath);
    ULONG cbPass = Cbtszsize(szPass);
    SCODE sc = S_OK;

    /* allocate memory for the security information */
    *lpcbSpoolSecurity = cbPass + cbPath;
    sc = LMAlloc(plmr, *lpcbSpoolSecurity, lppbSpoolSecurity);
    if (sc)
    {
        *lpcbSpoolSecurity = 0;
        DebugTraceSc(HrGetSpoolSecurity, sc);
        return ResultFromScode(sc);
    }

    /* fill in the  spooler security information */
    if (cbPath)
        memcpy(*lppbSpoolSecurity, szPath, (UINT) cbPath);
    if (cbPass)
        memcpy(*lppbSpoolSecurity + (UINT) cbPath, szPass, (UINT) cbPass);

    return hrSuccess;
}

/*
 * DecodeSpoolSecurity
 *
 * Purpose
 *          return the spooler security information for use in SpoolerLogon

*
 * Parameters
 *  cbSpoolSecurity     byte count of the security information
 *  pbSpoolSecurity     security information
 *  szPath              pointer to variable for path to root of store
 *  szPass              poitner to varaible for store account password
 */
static void DecodeSpoolSecurity(ULONG cbSpoolSecurity, LPBYTE pbSpoolSecurity,
    LPTSTR szPath, LPTSTR szPass)
{
    ULONG cbPath = 0;

    lstrcpy(szPath, pbSpoolSecurity);
    cbPath = Cbtszsize(szPath);

    lstrcpy(szPass, pbSpoolSecurity + cbPath);

    return;
}

/*
 * HrCheckAndOpenStore
 *
 * Purpose
 *          check the integrity of store specified in szPath and return
 *          an open store if it's OK
 *
 * Parameters
 *  pmsp                pointer to our MSProvider object
 *  psup                mapi support object
 *  fModify             TRUE if write access desired
 *  pps                 pointer to our profile section
 *  szPath              path name to root of the store
 *  szPass              password to store
 *  fIsSpooler          TRUE if this is a spooler logon
 *  ppmsl               pointer to variable to hold MSLogon object
 *  ppims               pointer to variable to hold the opened store object
 */
static HRESULT HrCheckAndOpenStore(PMSP pmsp, LPMAPISUP psup, BOOL fModify,
    LPPROFSECT pps, LPTSTR szPath, LPTSTR szPass, BOOL fIsSpooler,
    PMSL *ppmsl, PIMS *ppims)
{
    HRESULT hr;
    PRFS prfs = NULL;
    PMSL pmsl = NULL;
    PIMS pims = NULL;
    SCODE sc;

    *ppmsl = NULL;

    hr = HrConfirmCred(szPath, szPass, psup, &pmsp->lmr);
    if (hr != hrSuccess)
        goto exit;

    hr = HrCheckStoreIntegrity(szPath);
    if (hr != hrSuccess)
        goto exit;

    hr = OpenRFS(szPath, szFolderFileName, 0, &prfs);
    if (hr != hrSuccess)
        goto exit;

    /* Create MSLogon object */

    sc = ScCreateMSL(pmsp, &pmsl);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    /* Create Message Store object */

    hr = HrNewIMS(szPath, szPropFileName, pmsp, pmsl, prfs, pps, psup,
            FALSE, &pims);
    if (hr != hrSuccess)
        goto exit;

    if (fIsSpooler)
        OBJ_SetFlag(pims, MSF_SPOOLER);

    if (fModify)
        OBJ_SetFlag(pims, OBJF_MODIFY);

    pmsl->pims = pims;

    *ppims = pims;
    *ppmsl = pmsl;

exit:
    if (hr != hrSuccess)
    {
        /* If we have an RFS but failed to create the MS, close RFS manually. */
        if (prfs)
            NFSideAssertSz(CloseRFS(prfs) == hrSuccess, "RFS not closed");

        UlRelease(pmsl);
        pmsl = NULL;
    }

    DebugTraceResult(HrCheckAndOpenStore, hr);
    return hr;
}

/*
 * ScCreateMSL
 *
 * Purpose
 *          returns a new logon object
 *
 * Parameters
 *      pmsp    A pointer to the MS Provider object.
 *      ppmsl   Location in which to return the address of the
 *              newly allocated MSL object.
 *
 *  Returns:
 *      SCODE
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the MSL object.
 */
static SCODE ScCreateMSL(PMSP pmsp, PMSL *ppmsl)
{
    PMSL pmsl;
    SCODE sc;

    sc = LMAllocZ(&pmsp->lmr, sizeof(MSL), &pmsl);

    if (sc == S_OK)
    {
        OBJ_Initialize(pmsl, &vtblMSL, OT_MSLOGON, NULL, &pmsl->cs);

        InitializeCriticalSection(&pmsl->cs);

        pmsl->lmr = pmsp->lmr;

        *ppmsl = pmsl;
    }

    DebugTraceSc(ScCreateMSL, sc);
    return sc;
}

/*
 *  FFindPropInPSPB
 *
 *  Purpose:
 *      Look for the given property in the PSPB structure. Search the
 *      client prop array before the profile prop array.
 *
 *  Arguments:
 *      pspb    pointer to the service parameter block
 *      ulPT    the property tag to look for
 *      ppval   location to place the pointer to the pval
 *
 *  Returns:
 */
static BOOL FFindPropInPSPB(PSERVICEPBLK pspb, ULONG ulPT, LPSPropValue *ppval)
{
    LPSPropValue pval;

    /* First, look in the client's array. If it's not there, look for */
    /* it in the profile array. */

    pval = PpropFindProp(pspb->pvalsClient, pspb->cvalsClient, ulPT);

    if (!pval)
        pval = PpropFindProp(pspb->pvalsProf, pspb->cvalsProf, ulPT);

    if (pval)
    {
        *ppval = pval;
        return TRUE;
    }

    return FALSE;
}

/*
 *  ScGetFullFileName
 *
 *  Purpose:
 *      Look for the file path and name in the property list given.  If
 *      found, convert it to a full (non-relative) path name, and check for
 *      the existence of the directory. Note that we return values even
 *      when an error occurs.
 *
 *  Arguments:
 *
 *  Returns:
 */
static SCODE ScGetFullFileName(PSERVICEPBLK pspb, BOOL *pfCreate,
    LPSTR pszPath)
{
    SCODE           sc  = S_OK;
    LPSTR           pszFileTitle;
    LPSPropValue    pval;
    BOOL            fCreate = FALSE;

    *pszPath = '\0';
    *pfCreate = FALSE;

    /* get the path to the file */
    if (    FFindPropInPSPB(pspb, PR_SMS_PATH, &pval)
        &&  GetFullPathName(pval->Value.lpszA, MAX_PATH, pszPath,
                &pszFileTitle))
    {
        DWORD   dwFA;
        LPSPropValue pvalT;

        if (FFindPropInPSPB(pspb, PR_SMS_CREATE, &pvalT))
            *pfCreate = pvalT->Value.b;

        Assert (*(pszFileTitle - 1) == '\\');

        dwFA = GetFileAttributes(pszPath);

        /* If the file isn't there, and we aren't creating, then error. */
        /* If the file is there, and we are creating, then error. */

        if (dwFA == 0xFFFFFFFF && *pfCreate == FALSE)
            sc = MAPI_E_NOT_FOUND;
        else if (dwFA != 0xFFFFFFFF && *pfCreate == TRUE)
            sc = MAPI_E_NO_ACCESS;
    }
    else
    {
        *pszPath = '\0';
        sc = MAPI_E_NOT_FOUND;
    }

    DebugTraceSc(ScGetFullFileName, sc);
    return sc;
}


MSPMISC.C

/*
 *  M S P M I S C . C
 *
 *  Utility functions needed by the MAPI Sample Store Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

/*
 *  Memory allocation and release functions.  They use the lpMalloc
 *  interface supplied by MAPI during provider initialization.
 *
 *  For internal memory allocations the Sample Store Provider uses
 *  the lpMalloc passed by MAPI on MSProviderInit().  For simplicity
 *  of coding we keep the pointer to the lpMalloc in a
 *  per-process global (which requires mucho work on Win16) instead
 *  of forcing ScAlloc() calls to pass in the pointer on each and every
 *  allocation.  We trade off code size with speed (on Win16) because
 *  of the searching necessary in finding the per-instance globals.
 *
 *  When multiple MSProviderInit's are done on the same process
 *  (by opening one or more stores on two different MAPI Profiles
 *  on the same process) we use the lpMalloc from the first init
 *  for all allocations, keeping a refcount of MSProviderInit's
 *  that have been done in order to know when to free the lpMalloc.
 *
 *  As a result we don't necessary use the lpMalloc for store X that
 *  was passed in when store X was opened, but as long as MAPI gives
 *  us the Component Object's lpMalloc on each open we're fine.
 */

SCODE ScAlloc(ULONG lcb, LPVOID * ppv)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();

    Assert(pinst);
    Assert(pinst->lpmalloc);

    *ppv = pinst->lpmalloc->lpVtbl->Alloc(pinst->lpmalloc, lcb);
    if (*ppv != NULL)
        return S_OK;

    DebugTraceSc(ScAlloc, MAPI_E_NOT_ENOUGH_MEMORY);
    return (MAPI_E_NOT_ENOUGH_MEMORY);
}

SCODE ScAllocZ(ULONG lcb, LPVOID * ppv)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();

    Assert(pinst);
    Assert(pinst->lpmalloc);

    *ppv = pinst->lpmalloc->lpVtbl->Alloc(pinst->lpmalloc, lcb);
    if (*ppv != NULL)
    {
        memset(*ppv, 0, (size_t) lcb);
        return S_OK;
    }

    DebugTraceSc(ScAllocZ, MAPI_E_NOT_ENOUGH_MEMORY);
    return (MAPI_E_NOT_ENOUGH_MEMORY);
}

SCODE ScRealloc(ULONG lcb, LPVOID pvOrig, LPVOID * ppv)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();
    LPVOID pvNew;

    Assert(pinst);
    Assert(pinst->lpmalloc);

    pvNew = pinst->lpmalloc->lpVtbl->Realloc(pinst->lpmalloc, pvOrig, lcb);
    if (pvNew != NULL)
    {
        *ppv = pvNew;
        return S_OK;
    }

    DebugTraceSc(ScRealloc, MAPI_E_NOT_ENOUGH_MEMORY);
    return (MAPI_E_NOT_ENOUGH_MEMORY);
}

void FreeNull(LPVOID pv)
{
    if (pv)
    {
        PINST pinst = (PINST) PvGetInstanceGlobals();

        Assert(pinst);
        Assert(pinst->lpmalloc);

        pinst->lpmalloc->lpVtbl->Free(pinst->lpmalloc, pv);
    }
}

/* Linked Memory Utilities ------------------------------------------------- */

SCODE LMAllocZ(PLMR plmr, ULONG lcb, LPVOID * ppv)
{
    SCODE sc;

    sc = LMAlloc(plmr, lcb, ppv);
    if (sc != S_OK)
        return (sc);

    memset(*ppv, 0, (size_t) lcb);
    return (S_OK);
}

/*
 *  ScInitMSInstance
 *
 *  Remember the given lpMalloc in a per-instance variable that
 *  can be looked up again in ScAlloc and FreeNull.
 */
SCODE ScInitMSInstance(LPMALLOC lpmalloc)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();
    SCODE sc;

    Assert(lpmalloc);

    if (pinst)
    {
        /* A usable allocator is already set.   */
        /* Ignore the one we were passed in     */
        /* and instead bump the refcount on the */
        /* one we're going to use.              */
        ++(pinst->cRef);
        return S_OK;
    }

    /* In our debugging version wrap the allocator for
     * extra help locating memory leaks or other problems.
     * In both debug and non-debug versions, this statement
     * addrefs the allocator, and the DBGMEM_Shutdown at the
     * end does a release on the allocator.
     */
    lpmalloc = DBGMEM_Encapsulate(lpmalloc, "Sample Store", 0);

    pinst = (PINST) lpmalloc->lpVtbl->Alloc(lpmalloc, sizeof(INST));

    if (pinst == NULL)
    {
        sc = MAPI_E_NOT_ENOUGH_MEMORY;
        goto ret;
    }

    sc = ScSetInstanceGlobals(pinst);
    if (sc != S_OK)
        goto ret;

    pinst->cRef = 1;
    pinst->lpmalloc = lpmalloc;

    return S_OK;

ret:
    if (pinst)
        lpmalloc->lpVtbl->Free(lpmalloc, pinst);

    DBGMEM_Shutdown(lpmalloc);
    DebugTraceSc(ScInitMSInstance, sc);
    return sc;
}

void DeinitMSInstance(void)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();
    LPMALLOC lpmalloc;

    if (!pinst || --(pinst->cRef) > 0)
        return;

    (void)ScSetInstanceGlobals(NULL);
    lpmalloc = pinst->lpmalloc;
    lpmalloc->lpVtbl->Free(lpmalloc, pinst);
    DBGMEM_Shutdown(lpmalloc);
}

/*
 *  SzBaseName
 *
 *  Purpose
 *      return the base name of the object with the given eid
 *
 *  Parameters
 *      peid                Pointer to entryID of object
 *
 *  Returns
 *      LPTSTR              base name, ie sequence number and extension
 *
 *  Note                    no memory is allocated, returned value must be
 *                          copied if it is to be saved.
 */
LPTSTR SzBaseName(PEID peid)
{
    LPTSTR szName;              /* local name of object in lpEntryID */
    LPTSTR szLastSlash;         /* position of last slash in peid */

    Assert(peid);

    /* Note that the entryid pathname is always relative to the root of */
    /* the store, and will therefore never contain a drive letter. */

    szLastSlash = SzFindLastCh(peid->szPath, '\\');

    if (NULL == szLastSlash)
    {
        szName = (LPTSTR) peid->szPath;
    }
    else
    {
        szName = szLastSlash + 1;
    }

    return szName;
}

/*
 * FCheckEIDType
 *
 *  Purpose
 *      This function checks the pathname extension portion of the given
 *      entryid against the string given. Since files in the sample store
 *      have extensions that depend on the type of object within, comparing
 *      the extension will tell the caller whether the entryid is of the type
 *      specified by the extension passed in.
 *
 *  Parameters
 *      peid: A pointer to the entryid to check.
 *      szExt: A pointer to the string containing the extension to compare.
 *
 *  Returns: TRUE if the extensions matched, FALSE otherwise.
 */
BOOL FCheckEIDType(PEID peid, LPSTR szExt)
{
    BYTE *pb;

    if (peid == NULL)
        return FALSE;

    pb = (BYTE *) peid;
    pb += CbEID(peid);
    pb -= ((CCH_EXT + 1) * sizeof(TCHAR));

    return (lstrcmpi((LPTSTR) pb, szExt) == 0);
}

/*
 *  FIsRoot
 *
 *  Purpose     Determine if an EID is one for the root folder
 *
 *  Parameters  peid
 *
 *  Returns     TRUE or FALSE
 */
BOOL FIsRoot(PEID peid)
{
    return (NULL == peid) || *(peid->szPath) == '\0';
}

/*
 *  FIsFolder
 *
 *  Parameters  peid    pointer to entryid of object
 *
 *  Purpose     return TRUE if the entryID is one for a folder
 *
 *  Returns     TRUE If the entryID is one for a folder, FALSE otherwixe
 *
 */
BOOL FIsFolder(PEID peid)
{
    /* root is a folder */
    if (FIsRoot(peid))
        return TRUE;

    return (FCheckEIDType(peid, FOLDER_EXT));
}

/*
 *  FIsUnsavedMsg
 *
 *  Purpose     Determine if a message has had changes saved
 *
 *  Parameters  pimsg: A pointer to the message object to check.
 *
 *  SideEffect
 *              Will say that a message is saved if another open version
 *              has saved it.
 *
 *  Returns     TRUE or FALSE
 */
BOOL FIsUnsavedMsg(PIMSG pimsg)
{
    return (FIsUnsavedEID((PEID) pimsg->peid));
}

/*
 *  HrDeconstructEID
 *
 *  Purpose:
 *      Given an EID, return its component parts (MAPIUID, path to
 *      file, and file name).  This is NOT a general path parser.
 *      Instead, because we know the exact structure of the EIDs
 *      and paths we construct, we can directly access the
 *      individual components.
 *
 *  Parameters
 *      peid        EID to deconstruct.
 *      ppuid       Location in which to return a pointer to the
 *                  EID's MAPIUID.
 *      pszPath     Location in which to return a pointer to the
 *                  EID's file path.
 *      pszFile     Location in which to return a pointer to the
 *                  EID's file name.
 *
 *  Returns:
 *      HRESULT
 *
 *  Size effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate memory for
 *                                  some or all of the return
 *                                  parameters.
 */
HRESULT HrDeconstructEID(PEID peid, LPMAPIUID *ppuid, LPTSTR *pszPath,
    LPTSTR *pszFile)
{
    SCODE sc;
    LPMAPIUID puid = NULL;
    LPTSTR szPath = NULL;
    LPTSTR szFile = NULL;
    LPTSTR szT = NULL;
    LPTSTR szEnd = NULL;

    AssertSz(!IsBadReadPtr(peid, CbNewEID(0)), "Bad peid #1");
    AssertSz(!IsBadReadPtr(peid, CbEID(peid)), "Bad peid #2");
    AssertSz(!IsBadWritePtr(ppuid, sizeof(LPMAPIUID)), "Bad ppuid");
    AssertSz(!IsBadWritePtr(pszPath, sizeof(LPTSTR)), "Bad pszPath");
    AssertSz(!IsBadWritePtr(pszFile, sizeof(LPTSTR)), "Bad pszFile");

    *ppuid = NULL;
    *pszPath = NULL;
    *pszFile = NULL;

    /* Get the UID out */

    sc = ScAlloc(sizeof(MAPIUID), &puid);
    if (sc != S_OK)
        goto exit;
    *puid = peid->uidResource;

    /* Get the path and file name out */
    szT = SzFindLastCh(peid->szPath, '\\');
    if (szT)
        szT++;
    else
        szT = peid->szPath;

    szEnd = (TCHAR *) ((BYTE *) peid + CbEID(peid));
    sc = ScAlloc((szEnd - szT) * sizeof(TCHAR), &szFile);
    if (sc != S_OK)
        goto exit;

    if ((LPBYTE) szEnd - (LPBYTE) szT)
        memcpy(szFile, szT, (LPBYTE) szEnd - (LPBYTE) szT);

    if (szT != peid->szPath)
    {
        sc = ScAlloc((LPBYTE) szT - (LPBYTE) (peid->szPath), &szPath);
        if (sc != S_OK)
            goto exit;

        /* We copy the trailing backslash along with the rest of   */
        /* the string, then overwrite it with the NULL terminator. */
        memcpy(szPath, peid->szPath, (LPBYTE) szT - (LPBYTE) (peid->szPath));
        *(szPath + (szT - peid->szPath) - 1) = '\0';
    }
    else
    {
        sc = ScAlloc(sizeof(TCHAR), (PPV) &szPath);
        if (sc != S_OK)
            goto exit;

        *szPath = '\0';
    }

exit:
    if (sc)
    {
        (void)FreeNull((LPVOID) puid);
        (void)FreeNull((LPVOID) szPath);
        (void)FreeNull((LPVOID) szFile);
    }
    else
    {
        *ppuid = puid;
        *pszPath = szPath;
        *pszFile = szFile;
    }

    DebugTraceSc(HrDeconstructEID, sc);
    return ResultFromScode(sc);
}

/*
 *  HrAppendPath
 *
 *  Purpose:
 *      Concatenate a path onto another, allocating space for the
 *      result.
 *
 *  Parameters
 *      szBase          Beginning of concatenated path.
 *      szAppend        End of concatenated path.
 *      pszFullPath     Pointer in which to place a pointer to the
 *                      newly allocated concatenated path.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Unable to allocate memory for
 *                                  the return variable.
 */
HRESULT HrAppendPath(LPTSTR szBase, LPTSTR szAppend, LPTSTR * pszFullPath)
{
    SCODE sc = S_OK;
    TCHAR rgch[512];

    AssertSz(szBase, "Bad szBase");
    AssertSz(szAppend, "Bad szAppend");
    AssertSz(pszFullPath, "Bad pszFullPath");
    AssertSz(szAppend[0] != '\\',
        "szAppend not relative path");

    if (!FAppendPathNoMem(szBase, szAppend, sizeof rgch / sizeof rgch[0],
            rgch))
    {
        sc = MAPI_E_STRING_TOO_LONG;
        goto exit;
    }

    sc = ScAlloc(Cbtszsize(rgch), (PPV) pszFullPath);
    if (sc != S_OK)
        goto exit;

    lstrcpy(*pszFullPath, rgch);

exit:
    DebugTraceSc(HrAppendPath, sc);
    return ResultFromScode(sc);
}

/*
 *  FAppendPathNoMem
 *
 *  Purpose:
 *      Concatenate two parts of a file path, returning result in a
 *      preallocated buffer.
 *
 *  Parameters
 *      szBase          First part of path to concatenate.
 *      szAppend        Second part of path to concatenate.
 *      cchFullPath     Size of return buffer.
 *      szFullPath      Return buffer.
 *
 *  Returns:
 *      BOOL.  TRUE if the return buffer is large enough to hold
 *      the resultant string, FALSE if the buffer is not large
 *      enough (value in szFullPath is undefined).
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
BOOL FAppendPathNoMem(LPTSTR szBase, LPTSTR szAppend, ULONG cchFullPath,
    LPTSTR szFullPath)
{
    UINT cchBase = 0;
    UINT cchAppend = 0;
    UINT cchFull = 0;
    BOOLEAN fPostSlash = FALSE;

    AssertSz(szBase, "Bad szBase");
    AssertSz(szAppend, "Bad szAppend");
    AssertSz(szFullPath, "Bad szFullPath");
    AssertSz(szAppend[0] != '\\',
        "szAppend not relative path");

    cchBase = lstrlen(szBase);
    cchAppend = lstrlen(szAppend);

    /* Check if szBase has trailing backslash, else we'll need to add it */

    if (*(szBase + cchBase - 1) == '\\')
    {
        fPostSlash = TRUE;
        cchFull = cchBase + cchAppend + 1;
    }
    else
    {
        fPostSlash = FALSE;
        cchFull = cchBase + cchAppend + 2;
    }

    if (cchFull <= cchFullPath)
    {
        lstrcpy(szFullPath, szBase);
        if (!fPostSlash)
        {
            lstrcat(szFullPath, TEXT("\\"));
        }
        lstrcat(szFullPath, szAppend);

        return TRUE;
    }

    return FALSE;
}

/*
 *  ReplaceExt
 *
 *  Purpose:
 *      Substitute the filename extension in szFile with the new
 *      one passed in as szExt.  It is the caller's
 *      responsibility to ensure that the filename has room for the
 *      new extension.  Also, the extension must include the
 *      period.
 *
 *  Parameters
 *      szFile      Filename on which to operate.
 *      szExt       New extension.
 *
 *  Returns:
 *      void.
 */
void ReplaceExt(LPTSTR szFile, LPTSTR szExt)
{
    LPTSTR szT = NULL;

    AssertSz(!IsBadStringPtr(szFile, (UINT) -1), "Bad szFile");
    AssertSz(!IsBadStringPtr(szExt, (UINT) -1), "Bad szExt");

    szT = SzFindLastCh(szFile, '.');

    if (szT)
        lstrcpy(szT, szExt);
    #ifdef DEBUG
    else
        TrapSz("No extension found on szFile. Not replacing extension.");
    #endif

    return;
}

/*
 *  HrConstructEID
 *
 *  Purpose     return an new EntryID for an object
 *
 *  Parameters
 *      puidStore       UID for the store containing the object
 *      plmr            Pointer to the MAPI linked memory allocators.
 *      szNewName       new root relative path name of the object
 *      ppeidNew        address of pointer to new entryID
 *
 *  Returns:
 *      ULONG, PEID
 */
HRESULT HrConstructEID(LPMAPIUID puidStore, PLMR plmr, LPSTR szNewName,
    PEID *ppeidNew)
{
    PEID peidNew;               /* new entry id */
    ULONG cbEID;                /* number of bytes in the new entry id */
    ULONG cbNewName;
    SCODE sc;

    cbNewName = lstrlen(szNewName) + 1; /* we count the NULL terminator */
    cbEID = CbNewEID(cbNewName);

    /* allocate space for the new entry id */
    /* Use the MAPI allocator because it may be returned to the client */
    /* Note that we zero-fill this allocation so that the entryid produced */
    /* by identical input parameters will always be the same. If we didn't */
    /* zero fill, the pad bytes after the version would be randomly filled. */

    sc = LMAllocZ(plmr, cbEID, (PPV) &peidNew);
    if (sc != S_OK)
        goto exit;

    *(DWORD *) (peidNew->abFlags) = (DWORD) 0;
    peidNew->uidResource = *puidStore;
    peidNew->bVersion = SMPMS_VERSION;

    lstrcpy(peidNew->szPath, szNewName);

    if (peidNew->szPath[0])
        AnsiLowerBuff(peidNew->szPath, lstrlen(peidNew->szPath));

    *ppeidNew = peidNew;

exit:
    DebugTraceSc(HrConstructEID, sc);
    return ResultFromScode(sc);
}

/*
 * HrGetParentEID
 *
 *  Purpose         construct an entry id for the parent of peid. This
 *                  assumes that all files in the sample message store
 *                  are CCH_NAME chars long (including NULL).
 *
 *  Parameters
 *      plmr        Pointer to the MAPI linked memory allocators.
 *      peid        entry id of object whose parent is requested
 *      ppeidParent pointer to parent's peid
 *
 */
HRESULT HrGetParentEID(PLMR plmr, PEID peid, PEID *ppeidParent)
{
    HRESULT hr;
    ULONG cbParentName = sizeof(TCHAR); /* number of bytes in szParentName */
    LPTSTR szParentName = NULL; /* name of parent */

    if (CbEIDPath(peid) > (CCH_NAME * sizeof(TCHAR)))
        cbParentName = CbEIDPath(peid) - (CCH_NAME * sizeof(TCHAR));

    hr = HrAlloc(cbParentName, (PPV) &szParentName);
    if (hr != hrSuccess)
        goto exit;

    if (cbParentName > sizeof(TCHAR))
        memcpy(szParentName, peid->szPath, (UINT) cbParentName);
    szParentName[cbParentName - sizeof(TCHAR)] = 0;

    hr = HrConstructEID(&(peid->uidResource), plmr, szParentName,
        ppeidParent);

exit:
    FreeNull(szParentName);
    DebugTraceResult(HrGetParentEID, hr);
    return hr;
}

/*
 * HrOpenParent
 *
 * Purpose  open the parent folder of the given entry id
 *
 * Parameters
 *  pims    store in which the object is
 *  peid    entry id of object whose parent is to be opened
 *  ulFlags MAPI_MODIFY if you want write permission on the store
 *  ppifld  pointer to variable to hold open parent
 */
HRESULT HrOpenParent(PIMS pims, PEID peid, ULONG ulFlags, PIFLD * ppifld)
{
    HRESULT hr = hrSuccess;
    PEID peidParent = NULL;
    ULONG ulObjectType;

    Assert(pims);
    Assert(peid);
    Assert(ppifld);

    *ppifld = NULL;
    hr = HrGetParentEID(&pims->lmr, peid, &peidParent);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->lpVtbl->OpenEntry(pims, CbEID(peidParent),
        (LPENTRYID) peidParent, NULL, ulFlags, &ulObjectType,
        (LPUNKNOWN *) ppifld);
    if (hr != hrSuccess)
        goto exit;

    Assert(ulObjectType == MAPI_FOLDER);

exit:
    LMFree(&pims->lmr, peidParent);

    DebugTraceResult(HrOpenParent, hr);
    return hr;
}

/*
 * FreePropArrays
 *
 * Purpose      deallocate space for PropTag, PropValue and PropAttr arrays
 *              allocated with HrAllocPropArrays
 * Parameters
 *  ppval       address of property value array
 *  pptaga  address of the property tag array
 *  ppatra  address of the property attribute array
 */
void FreePropArrays(LPSPropValue *ppval, LPSPropTagArray *pptaga,
    LPSPropAttrArray *ppatra)
{
    Assert(ppval);
    Assert(pptaga);
    Assert(ppatra);

    FreeNull(*ppval);
    FreeNull(*pptaga);
    FreeNull(*ppatra);

    *ppval = NULL;
    *pptaga = NULL;
    *ppatra = NULL;
}

/*
 * HrAllocPropArrays
 *
 * Purpose      allocate space for PropTag, PropValue and PropAttr arrays
 *              Free with FreeNull or FreePropArrays
 * Parameters
 *  cProps  number of properties in the arrays
 *  ppval   address of property value array
 *  pptaga  address of the property tag array
 *  ppatra  address of the property attribute array
 */
HRESULT HrAllocPropArrays(ULONG cProps, LPSPropValue *ppval,
    LPSPropTagArray *pptaga, LPSPropAttrArray *ppatra)
{
    HRESULT hr;

    /* All three pointers must be provided. */
    Assert(!IsBadWritePtr(ppval, sizeof(LPSPropValue)));
    Assert(!IsBadWritePtr(pptaga, sizeof(LPSPropTagArray)));
    Assert(!IsBadWritePtr(ppatra, sizeof(LPSPropAttrArray)));

    /* All must be zero on entry for our cleanup mechanism. */
    AssertSz(!*ppval, "bad ppval");
    AssertSz(!*pptaga, "bad pptaga");
    AssertSz(!*ppatra, "bad ppatra");

    hr = HrAlloc(cProps * sizeof(SPropValue), ppval);
    if (hr != hrSuccess)
        goto exit;

    hr = HrAlloc(CbNewSPropTagArray(cProps), pptaga);
    if (hr != hrSuccess)
        goto exit;

    hr = HrAlloc(CbNewSPropAttrArray(cProps), ppatra);
    if (hr != hrSuccess)
        goto exit;

exit:
    if (hr != hrSuccess)
        FreePropArrays(ppval, pptaga, ppatra);

    DebugTraceResult(HrAllocPropArrays, hr);
    return hr;
}

/*
 * ProcessGetProps
 *
 *  Purpose
 *      Helper routine from HrWrap_GetProps. Folder and message objects in
 *      the sample store keep a few property values in memory so that when
 *      we move or copy a folder or message, the entryid, parent entryid,
 *      and (for copied messages) record key are correct. This routine
 *      overrides the value returned from IMessage for any in-memory
 *      properties. We keep a very small placeholder property for each
 *      of the in-memory properties on disk and mark it read-only and
 *      not deletable. This keeps SetProps from succeeding on these
 *      properties. All properties that we override in this function are
 *      PT_BINARY property types, so HrWrap_GetProps only calls this
 *      function if the property it is looking for is a PT_BINARY.
 *
 *      This function tries to find the property that the client is
 *      requesting in the in-memory array of properties stored with the
 *      object. If the routine finds the property, then it tries to allocate
 *      and copy the correct data into the client's array. If the allocation
 *      fails, the routine fills in a PT_ERROR into the client's property
 *      value. Note that HrWrap_GetProps needs to watch for PT_ERROR coming
 *      back and return the appropriate warning to the client.
 *      If the routine doesn't find the property, then it returns to
 *      HrWrap_GetProps without modifying the client's array at all.
 *
 *  Parameters
 *      pvalClient: A pointer to the property value to search for in our
 *          in-memory property array. This function may MODIFY this value.
 *      cvalInt: The number of in-memory properties on this object. If the
 *          object has no in-memory properties, this value will be zero.
 *      pvalInt: A pointer to the object's array of in-memory properties.
 *      plmr: A pointer to the linked memory allocation routines.
 *      pvOrig: The original allocated pointer (AllocateMore needs this).
 *
 *  Returns
 *      None. May have modified the client's property value if there was
 *      a matching entry in the in-memory array.
 */
static void ProcessGetProps(LPSPropValue pvalClient, ULONG cvalInt,
    LPSPropValue pvalInt, PLMR plmr, LPVOID pvOrig)
{
    ULONG ulClientID = PROP_ID(pvalClient->ulPropTag);
    ULONG ulClientType = PROP_TYPE(pvalClient->ulPropTag);
    LPSPropValue pvalT;
    LPSPropValue pvalTMac;

    pvalT = pvalInt;
    pvalTMac = pvalT + cvalInt;

    for (; pvalT < pvalTMac; ++pvalT)
    {
        LPVOID pv;
        ULONG cb;
        SCODE sc;

        AssertSz(PROP_TYPE(pvalT->ulPropTag) == PT_BINARY,
            "Code assumes all internal props are PT_BINARY");

        if (ulClientID == PROP_ID(pvalT->ulPropTag))
        {
            cb = pvalT->Value.bin.cb;
            pv = pvalT->Value.bin.lpb;

            /* Link onto returned data our extra info. */
            sc = LMAllocMore(plmr, cb, pvOrig, &pvalClient->Value.bin.lpb);
            if (sc != S_OK)
            {
                pvalClient->Value.err = sc;
                pvalClient->ulPropTag = PROP_TAG(PT_ERROR, ulClientID);
            }
            else
            {
                pvalClient->Value.bin.cb = cb;
                if (cb)
                    memcpy(pvalClient->Value.bin.lpb, pv, (UINT) cb);
                pvalClient->ulPropTag = PROP_TAG(PT_BINARY, ulClientID);
            }

            break;
        }
    }

    return;
}

/*
 * HrWrap_GetProps
 *
 * Purpose
 *  Adjust return from GetProps (Store, Folder, Message, etc) for wrapped
 *  values of PR_STORE_ENTRYID, PR_STORE_RECORD_KEY and (if a store)
 *  PR_ENTRYID. Doesn't allow the return of PR_ENTRYID for an unsaved
 *  message. Also overwrites values for properties that the object has
 *  cached in memory using the helper routine ProcessGetProps.
 *
 * Parameters
 *  hr              HRESULT from the original GetProps call.
 *  pims            pointer to the message store object
 *  cvalInt         the number of property values that are held in memory
 *                  associated with the object.
 *  pvalInt         a pointer to the array of in-memory properties associated
 *                  with the object. May be NULL if cvalInt is 0.
 *  pcValues        The number of values in client's PropValue array
 *  ppval           a pointer to the client's PropValue array
 *  fStore          TRUE if the object given the GetProps call was the
 *                  message store object.
 *  fTagsSpecified  TRUE if the client specified a proptag array on the call;
 *                  FALSE if the client passed NULL for the proptag array.
 *
 * Coding comments:
 *  The objects must contain these properties, of the appropriate
 *  type, but it isn't necessary that their values IN the objects be
 *  accurate. The KEYS, for example, don't even have to be 16 bytes.
 *
 *  The result code may be adjusted depending on whether this routine
 *  or the underlying property implementation, ran out of memory
 *  while dealing with one of these.
 *
 *  The PR_RECORD_KEY (a UID) in a Store IS the right value,
 *  and does not need to be wrapped.
 */
HRESULT HrWrap_GetProps(HRESULT hr, PIMS pims, ULONG cvalInt,
    LPSPropValue pvalInt, ULONG * pcValues, LPSPropValue * ppval,
    BOOL fStore, BOOL fTagsSpecified, POBJ pobj)
{
    /* Warning: pcValues and ppval parameters may not           */
    /* have been validated.  Do not dereference them unless     */
    /* the "hr" says everything succeeded so far.               */

    BOOL fErrors = FALSE;
    LPSPropValue pval;
    LPSPropValue pvalMac;

    /* No work to do unless the GetProps() generally succeeded. */
    if (HR_FAILED(hr))
        goto exit;

    pval = *ppval;
    pvalMac = pval + *pcValues;

    for (; pval < pvalMac; ++pval)
    {
        UINT ulType = (UINT) PROP_TYPE(pval->ulPropTag);
        UINT ulID;
        LPVOID pv;
        ULONG cb;
        SCODE sc;

        if(pval->ulPropTag == PR_ACCESS_LEVEL)
        {
            pval->Value.l = OBJ_TestFlag(pobj, OBJF_MODIFY) ? MAPI_MODIFY : 0;
        }

        if(pval->ulPropTag == PR_ACCESS)
        {
            if(OT_FOLDER == pobj->wType)
            {
                pval->Value.l = MAPI_ACCESS_READ;

                if(OBJ_TestFlag(pobj, OBJF_MODIFY))
                    pval->Value.l |= MAPI_ACCESS_MODIFY |
                                    MAPI_ACCESS_CREATE_CONTENTS |
                                    MAPI_ACCESS_CREATE_HIERARCHY;

                if(OBJ_TestFlag(pobj->pobjParent, OBJF_MODIFY))
                    pval->Value.l |= MAPI_ACCESS_DELETE;
            }
            else if(OT_MESSAGE == pobj->wType)
            {
                pval->Value.l = MAPI_ACCESS_READ;

                if(OBJ_TestFlag(pobj, OBJF_MODIFY))
                    pval->Value.l |= MAPI_ACCESS_MODIFY;

                if(OBJ_TestFlag(pobj->pobjParent, OBJF_MODIFY))
                    pval->Value.l |= MAPI_ACCESS_DELETE;
            }
            else
            {
                pval->ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ACCESS));
                pval->Value.err = MAPI_E_NOT_FOUND;
            }
        }

        if (ulType != PT_BINARY && ulType != PT_ERROR)
            continue;

        if (ulType == PT_ERROR && pval->Value.err == MAPI_E_UNEXPECTED_TYPE)
        {
            fErrors = TRUE;
            continue;
        }

        if (cvalInt != 0)
        {
            ProcessGetProps(pval, cvalInt, pvalInt, &pims->lmr,
                (LPVOID) *ppval);

            /* Recompute the prop type in case ProcessGetProps changed it. */
            ulType = (UINT) PROP_TYPE(pval->ulPropTag);
        }

        /* These values should be computed here just in case ProcessGetProps */
        /* modifies pval. */

        ulID = (UINT) PROP_ID(pval->ulPropTag);

        if (ulID == PROP_ID(PR_STORE_RECORD_KEY))
        {
            cb = sizeof(pims->uidResource);
            pv = &pims->uidResource;

}
        else if (ulID == PROP_ID(PR_STORE_ENTRYID)
            || (fStore && ulID == PROP_ID(PR_ENTRYID)))
        {
            cb = pims->eidStore.cb;
            pv = pims->eidStore.lpb;
        }
        else if (ulID == PROP_ID(PR_ENTRYID) && ulType != PT_ERROR)
        {
            /* entryid of a message doesn't exist until SaveChanges(). */
            if (FIsUnsavedEID((PEID) pval->Value.bin.lpb))
            {
                if (fTagsSpecified)
                {
                    fErrors = TRUE;
                    pval->ulPropTag = PROP_TAG(PT_ERROR, ulID);
                    pval->Value.err = MAPI_E_NOT_FOUND;
                }
                else
                {
                    /* The client wants all properties, and did not */
                    /* specify a prop tag array. The client therefore */
                    /* doesn't want NOT_FOUND errors. */
                    /* Overwrite the error entry with the last SPropValue */
                    /* in the array. */

                    (*pcValues)--;
                    pvalMac--;

                    if (pval < pvalMac)
                    {
                        memcpy(pval, pvalMac, sizeof(SPropValue));
                        --pval; /* redo this value, since we just changed it */
                    }
                }
            }
            continue;
        }
        else
        {
            /* Remember if any errors occur in final array. */
            if (ulType == PT_ERROR)
            {
                if (    pval->Value.err == MAPI_E_NOT_FOUND
                    &&  !fTagsSpecified)
                {
                    /* The client wants all properties, and did not */
                    /* specify a prop tag array. The client therefore */
                    /* doesn't want NOT_FOUND errors. */
                    /* Overwrite the error entry with the last SPropValue */
                    /* in the array. */

                    (*pcValues)--;
                    pvalMac--;

                    if (pval < pvalMac)
                    {
                        memcpy(pval, pvalMac, sizeof(SPropValue));
                        --pval; /* redo this value, since we just changed it */
                    }
                }
                else
                    fErrors = TRUE;
            }
            continue;
        }

        /* Link onto returned data our extra info. */
        sc = LMAllocMore(&pims->lmr, cb, (LPVOID) *ppval,
            &pval->Value.bin.lpb);
        if (sc != S_OK)
        {
            fErrors = TRUE;
            pval->Value.err = sc;
            pval->ulPropTag = PROP_TAG(PT_ERROR, ulID);
        }
        else
        {
            pval->Value.bin.cb = cb;
            if (cb)
                memcpy(pval->Value.bin.lpb, pv, (UINT) cb);
            pval->ulPropTag = PROP_TAG(PT_BINARY, ulID);
        }
    }

    /* Adjust HRESULT based on PT_ERRORs now. */
    if (!fErrors)
        hr = hrSuccess;
    else if (hr == hrSuccess)
        hr = ResultFromScode(MAPI_W_ERRORS_RETURNED);

exit:
    #ifdef DEBUG
    if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
        DebugTraceResult(HrWrap_GetProps, hr);
    #endif

    return hr;
}

/*
 * FIsSubmittedMessage
 *
 * Purpose      return TRUE if the message (specified by entryid) is submitted.
 *
 * Parameters
 *  pims        A pointer to the message store object.
 *  peid        The entryid of message to check.
 */
BOOL FIsSubmittedMessage(PIMS pims, PEID peid)
{
    HRESULT hr;
    PIMSG pimsgT = NULL;
    ULONG ulObjType;

    hr = pims->lpVtbl->OpenEntry(pims, CbEID(peid), (LPENTRYID) peid,
        NULL, MAPI_MODIFY, &ulObjType, (LPUNKNOWN *) &pimsgT);

    UlRelease(pimsgT);

    return (GetScode(hr) == MAPI_E_SUBMITTED);
}

/*
 *  HrOpenIMsgSession
 *
 *  Purpose:
 *      Open an IMsgSession, and return the pointer to the caller.
 *
 *  Parameters
 *      ppmsgsess: Pointer to the location to return the opened msg session.
 *
 *  Returns:
 *      HRESULT
 */
HRESULT HrOpenIMsgSession(LPMSGSESS *ppmsgsess)
{
    PINST pinst = (PINST) PvGetInstanceGlobals();

    Assert(pinst);
    Assert(pinst->lpmalloc);

    if (pinst == NULL)
        return ResultFromScode(MAPI_E_CALL_FAILED);

    return ResultFromScode(OpenIMsgSession(pinst->lpmalloc, 0L, ppmsgsess));
}

/*
 *  HrOpenIMsg
 *
 *  Purpose:
 *      Open the file given as a docfile, and then create an IMSG.DLL
 *      object on top of the storage.
 *
 *  Parameters
 *      pmsgsess    The message session to open the message within.
 *                  May be NULL (for ConfirmCred in msplogon to work).
 *      szFile      The file to open.
 *      plmr        a pointer to the linked memory routines.
 *      psup        a pointer to the MAPI support object.
 *      fCreate     TRUE means the caller wants to create the storage.
 *                  FALSE means open an existing storage.
 *      fModify     TRUE means the caller wants read/write access.
 *                  FALSE means read-only access. (This argument is
 *                  ignored when fCreate is TRUE; in that case, the
 *                  file is always opened read/write.)
 *      fExclusive  TRUE means the caller wants exclusive access to the
 *                  storage, and to fail creation if the storage already
 *                  exists.
 *      ppmsg       Address of a location in which to return a
 *                  pointer to the newly opened IMessage instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      IMessage on IStorage opening errors.
 */
HRESULT HrOpenIMsg(LPMSGSESS pmsgsess, LPSTR szFile, PLMR plmr, LPMAPISUP psup,
    BOOL fCreate, BOOL fModify, BOOL fExclusive, LPMESSAGE *ppmsg)
{
    HRESULT hr;
    SCODE sc;
    DWORD grfMode;
    LPSTORAGE lpstg = NULL;
    PINST pinst = (PINST) PvGetInstanceGlobals();

#ifdef _WIN32
    OLE_CHAR szOle[MAX_PATH];
    int cbFile = 0L;
#else
    OLE_CHAR *szOle = NULL;
#endif

    Assert(pinst);
    Assert(pinst->lpmalloc);

#ifdef _WIN32
    cbFile = 1 + lstrlen(szFile);
    Assert(cbFile < MAX_PATH);

    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szFile, cbFile, szOle, cbFile);
#else
    szOle = szFile;
#endif

    grfMode = STGM_TRANSACTED;

    if (fExclusive)
    {
        grfMode |= STGM_SHARE_EXCLUSIVE;

        if (fCreate)
            grfMode |= STGM_FAILIFTHERE;
    }
    else
        grfMode |= STGM_SHARE_DENY_NONE;

    if (fCreate)
    {
        grfMode |= (STGM_READWRITE | STGM_CREATE);

        hr = StgCreateDocfile(szOle, grfMode, 0, &lpstg);

        /* Commit docfile changes.  If we don't do this now, the file on  */
        /* disk will NOT be a docfile (i.e. OLE2 will not recognize it as */
        /* a docfile) if opened again with no other changes made to it.   */

        if (hr == hrSuccess)
            hr = lpstg->lpVtbl->Commit(lpstg, 0);
    }
    else
    {
        if (fModify)
            grfMode |= STGM_READWRITE;
        else
            grfMode |= STGM_READ;

        hr = StgOpenStorage(szOle, NULL, grfMode, NULL, 0, &lpstg);
    }

    if (hr != hrSuccess)
    {
        sc = MapStorageSCode(GetScode(hr));
        goto exit;
    }

    sc = OpenIMsgOnIStg(pmsgsess, plmr->lpAllocBuf, plmr->lpAllocMore,
        plmr->lpFreeBuf, pinst->lpmalloc, psup, lpstg, NULL, 0, 0, ppmsg);

    UlRelease(lpstg);

exit:
    if (sc != S_OK && fCreate)
        DeleteFile(szFile);

    DebugTraceSc(HrOpenIMsg, sc);
    return ResultFromScode(sc);
}

/*
 * HrSetOneROProp
 *
 *  Purpose
 *      The sample store needs to change properties that the client isn't
 *      allowed to change. This function allows the sample store to change
 *      a single property in the underlying IMessage object by first
 *      setting the attributes on that property to allow it to be written,
 *      then writing the property, and finally, setting the attributes back
 *      to once again only allow reading. Note that if the sample store
 *      calls this routine on a property that is writable, this routine
 *      will make it non-writable.
 *
 *  Parameters
 *      lpmsg: A pointer to the IMessage object in which to set the property.
 *      plmr: A pointer to the linked memory allocation routines.
 *      ulPT: The property tag to set within the object.
 *      pv: A pointer to the property value to set.
 */
HRESULT HrSetOneROProp(LPMESSAGE lpmsg, PLMR plmr, ULONG ulPT, LPVOID pv)
{
    HRESULT hr;
    LPSPropAttrArray patra = NULL;
    LPSPropProblemArray pprba = NULL;

    SizedSPropTagArray(1, spta);

    /* Should be changing the pval array inside the object. */

    AssertSz(   ulPT != PR_ENTRYID
            &&  ulPT != PR_PARENT_ENTRYID
            &&  ulPT != PR_RECORD_KEY
            &&  ulPT != PR_INSTANCE_KEY, "Changing internal props in IMSG");

    spta.cValues = 1;
    spta.aulPropTag[0] = ulPT;

    /* Get current attributes and make the properties writable */

    hr = GetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, &patra);
    if (hr != hrSuccess)
        goto exit;

    patra->aPropAttr[0] |= PROPATTR_WRITEABLE;

    hr = SetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, patra, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    hr = HrSetSingleProp((LPMAPIPROP) lpmsg, plmr, ulPT, pv);
    if (hr != hrSuccess)
        goto exit;

    /* Restore the attribute */

    patra->aPropAttr[0] &= ~PROPATTR_WRITEABLE;

    hr = SetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, patra, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

exit:
    if (pprba)
    {
        LMFree(plmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    LMFree(plmr, patra);

    DebugTraceResult(HrSetOneROProp, hr);
    return hr;
}

/*
 * HrGetSingleProp
 *
 *  Purpose
 *      Gets a property from an object, and returns the value by stuffing
 *      it into a separately passed pointer. This function is nice because
 *      it doesn't require the caller to have an SPropValue around simply
 *      to retrieve the value of a property that is a known size.
 *
 *  Parameters
 *      pmprop  The property object to get the property from.
 *      plmr    Pointer to MAPI's linked memory routines.
 *      ulPT    The property tag to get.
 *      pv      A pointer to the location to place the value of the property.
 *
 *  Returns
 *      HRESULT. No warnings are returned because this function retrieves
 *      only one property at a time.
 */
HRESULT HrGetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv)
{
    LPSPropValue pval = NULL;
    SCODE sc;
    HRESULT hr;
    ULONG cValues;

    SizedSPropTagArray(1, spta);

    spta.cValues = 1;
    spta.aulPropTag[0] = ulPT;

    hr = pmprop->lpVtbl->GetProps(pmprop, (LPSPropTagArray) &spta, 0, /* ansi */
        &cValues, &pval);

    sc = GetScode(hr);

    if ((sc != S_OK)
        && (sc != MAPI_W_ERRORS_RETURNED))
        goto exit;

    switch (PROP_TYPE(pval->ulPropTag))
    {
    case PT_I2:
    case PT_BOOLEAN:
        Assert(!IsBadWritePtr(pv, sizeof(short)));
        *(short *)pv = pval->Value.i;
        break;

    case PT_LONG:
    case PT_R4:
        Assert(!IsBadWritePtr(pv, sizeof(LONG)));
        *(LONG *) pv = pval->Value.l;
        break;

    case PT_DOUBLE:
    case PT_APPTIME:
    case PT_SYSTIME:
    case PT_I8:
    case PT_CURRENCY:
        Assert(!IsBadWritePtr(pv, sizeof(LARGE_INTEGER)));
        *(LARGE_INTEGER *) pv = pval->Value.li;
        break;

    case PT_ERROR:
        sc = pval->Value.err;
        break;

    default:
        TrapSz1("Unimplemented PROP_TYPE %08lX passed in.",
            PROP_TYPE(pval->ulPropTag));
    }

    LMFree(plmr, pval);

exit:
    AssertSz1(sc <= 0, "Logic error: sc %s returned from HrGetSingleProp.",
        SzDecodeScode(sc));

    DebugTraceSc(HrGetSingleProp, sc);
    return (ResultFromScode(sc));
}

/*
 *  HrSetSingleProp
 *
 *  Purpose:
 *      Sets one property and its separately passed value. This function
 *      is nice because it doesn't require the caller to have a SPropValue
 *      to pass in.
 *
 *  Parameters
 *      pmprop  The property object to set the property into.
 *      plmr    Pointer to MAPI's linked memory routines.
 *      ulPT    The property to set
 *      pv      A pointer to the value of the property
 *
 *  Returns:
 *  Any errors from SetProps. Note that no warnings or problem arrays are
 *      returned because this routine only sets one property.
 */
HRESULT HrSetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv)
{
    HRESULT hr;
    SPropValue sval;
    LPSPropProblemArray pprba = NULL;

    sval.ulPropTag = ulPT;

    switch (PROP_TYPE(ulPT))
    {
    case PT_I2:
    case PT_BOOLEAN:
        sval.Value.i = *(short *)pv;
        break;

    case PT_LONG:
    case PT_R4:
    case PT_UNICODE:
    case PT_STRING8:
    case PT_CLSID:
        AssertSz(sizeof(LPVOID) == sizeof(LONG),
            "Pointers are not the size of a long on this machine");
        sval.Value.l = *(LONG *) pv;
        break;

    case PT_DOUBLE:
    case PT_APPTIME:
    case PT_SYSTIME:
    case PT_I8:
    case PT_CURRENCY:
    case PT_OBJECT:
    case PT_BINARY:
        sval.Value.li = *(LARGE_INTEGER *) pv;
        break;

    default:
        TrapSz1("Unimplemented PROP_TYPE %08lX passed in.", PROP_TYPE(ulPT));
    }

    hr = pmprop->lpVtbl->SetProps(pmprop, 1, &sval, &pprba);

    if (hr == hrSuccess && pprba)
        hr = ResultFromScode(pprba->aProblem[0].scode);

    LMFree(plmr, pprba);

    DebugTraceResult(HrSetSingleProp, hr);
    return hr;
}

/*
 *  FContainsProp
 *
 *  Purpose:
 *      returns whether or not a PropTag exists in a PropTagArray
 *
 *  Parameters
 *      ulPropTag   The property to search for.
 *      ptaga       A pointer to the SPropTagArray to search. May be null,
 *                  in which case, the function will return FALSE.
 *
 *  Returns:
 *      TRUE    if ulPropTag is in ptaga
 *      FALSE   if not
 */
BOOL FContainsProp(ULONG ulPropTag, LPSPropTagArray ptaga)
{
    ULONG *pulPropTag;
    ULONG *pulPropMax;

    if (!ptaga)
        return FALSE;

    pulPropTag = ptaga->aulPropTag;
    pulPropMax = pulPropTag + ptaga->cValues;

    while (pulPropTag < pulPropMax)
    {
        if (ulPropTag == *pulPropTag)
            return TRUE;

        pulPropTag++;
    }

    return FALSE;
}


MSPMS.C

/*
 *  M S P M S . C
 *
 *  Code for the MAPI Sample Store Provider implementation of the
 *  IMsgStore object.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"
#include <stdarg.h>

#define MS_ValidateParameters(pobj, intf, method, arglist)      \
        OBJ_ValidateParameters(pobj, intf, method, sizeof(IMS), &vtblIMS, arglist)

static HRESULT HrGetSequenceNum(PEID peid, ULONG *pulSequenceNum);
static HRESULT HrGetSMSStandardNotifKey(PIMS pims, PEID peid,
    LPNOTIFKEY * lppKey);

/* Dispatch table for IMsgStore objects */
IMS_Vtbl vtblIMS =
{
    (IMS_QueryInterface_METHOD *)   OBJ_QueryInterface,
    (IMS_AddRef_METHOD *)           OBJ_AddRef,
    (IMS_Release_METHOD *)          OBJ_Release,
    IMS_GetLastError,
    IMS_SaveChanges,
    IMS_GetProps,
    IMS_GetPropList,
    IMS_OpenProperty,
    IMS_SetProps,
    IMS_DeleteProps,
    IMS_CopyTo,
    IMS_CopyProps,
    IMS_GetNamesFromIDs,
    IMS_GetIDsFromNames,

    IMS_Advise,
    IMS_Unadvise,
    IMS_CompareEntryIDs,
    IMS_OpenEntry,
    IMS_SetReceiveFolder,
    IMS_GetReceiveFolder,
    IMS_GetReceiveFolderTable,
    IMS_StoreLogoff,
    IMS_AbortSubmit,

    IMS_GetOutgoingQueue,
    IMS_SetLockState,
    IMS_FinishedMsg,
    IMS_NotifyNewMail,
};

/* definitions for outgoing queues */
CALLERRELEASE OutgoingViewRelease;

/*
 *  Object methods
 */

/*
 *  IMS_GetLastError [also called by IMSLogon, IMAPIFolder, IMessage, and IAttach]
 *
 *  Purpose:
 *      Returns a localized text error message associated with the
 *      last error which occurred on a specific object (in
 *      actuality, the SCODE used is associated with the HRESULT
 *      passed in, and it is not checked that this HRESULT matches
 *      that of the last error on this object -- providing a
 *      different HRESULT will very likely return an inconsistent
 *      error message, though).
 *
 *  Arguments:
 *      pobj            Pointer to the object.
 *      hError          HRESULT containing the error code returned
 *                      by the last failed call on this object.
 *      ulFlags         MAPI_UNICODE, string8 is default
 *      pulLLErr        location to place the low level error code
 *                      (may be NULL)
 *      pszMessage      Location in which to return an address to
 *                      an allocated buffer containing the
 *                      localized text error message.
 *      pszComponent    Location in which to return an address to
 *                      an allocated buffer containing the
 *                      name of the component producing the error.
 *      lpulContext     Location in which to return a context number
 *                      for finding help within the Component's help file.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Unable to allocate memory for
 *                                  the return parameter.
 *      MAPI_E_INVALID_PARAMETER    hError contains an unknown
 *                                  SCODE.
 */
STDMETHODIMP IMS_GetLastError(PIMS pobj, HRESULT hError, ULONG ulFlags,
    LPMAPIERROR * lppMapiError)
{
    HRESULT hr = hrSuccess;
    SCODE   sc;
    PIMS pims;
    LPTSTR pszMessage   = NULL;

    #ifdef VALIDATE
    if (IsBadWritePtr(pobj, sizeof(OBJ))
        || (pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS
            && pobj->lpVtbl != (IMS_Vtbl *) &vtblMSL
            && pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
            && pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
            && pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMAPIProp_GetLastError(pobj, hError, ulFlags, lppMapiError);

    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    OBJ_EnterCriticalSection((POBJ) pobj);

    pims = pobj->pims;

    /* //$ Add param checking and correctly return Component & Context */
    *lppMapiError = NULL;

    sc = LMAlloc( &pims->lmr, sizeof( MAPIERROR ), lppMapiError );
    if ( FAILED( sc ) )
    {
        hr = ResultFromScode( sc );
        goto ret;
    }

    memset( *lppMapiError, 0, sizeof( MAPIERROR ) );

    (*lppMapiError)->ulVersion = MAPI_ERROR_VERSION;

    hr = MapScodeSz(GetScode(hError), pims, &pszMessage);
    if ( HR_FAILED( hr ) )
        goto ret;

    sc = LMAllocMore( &pims->lmr, Cbtszsize( pszMessage ), *lppMapiError,
            &(*lppMapiError)->lpszError );

    if ( FAILED( sc ) )
    {
        hr = ResultFromScode( sc );
        goto ret;
    }

    lstrcpy( (*lppMapiError)->lpszError, pszMessage );

ret:

    if ( hr )
    {
        LMFree( &pims->lmr, *lppMapiError );
        *lppMapiError = NULL;
    }

    LMFree( &pims->lmr, pszMessage );

    OBJ_LeaveCriticalSection(pobj);

    DebugTraceResult(IMS_GetLastError, hr);
    return HrCheckHr(hr, IUnknown_GetLastError);
}

/*
 *  IMS_SaveChanges [Also used by IMAPIFolder]
 *
 *  Purpose:
 *      Saves changes made to the message store object properties
 *      (does not propagate to sub-objects).  Because changes to
 *      message store object properties show up immediately,
 *      however, this call does nothing (but returns success).
 *
 *  Arguments:
 *      pims        Pointer to the object.
 *      ulFlags     Flags.  The following are defined:
 *                  KEEP_OPEN_READONLY  Do not invalidate the
 *                                      object, make it read-only.
 *                  KEEP_OPEN_READWRITE Don't invalidate the
 *                                      object, keep it open
 *                                      read/write.
 *                  FORCE_SAVE          Overwrite any changes made by
 *                                      others since store was opened
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_SaveChanges(PIMS pims, ULONG ulFlags)
{
    #ifdef VALIDATE
    if (    IsBadWritePtr(pims, sizeof(OBJ))
        ||  (   pims->lpVtbl != (IMS_Vtbl *) &vtblIMS
            &&  pims->lpVtbl != (IMS_Vtbl *) &vtblIFLD))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMAPIProp_SaveChanges(pims, ulFlags);
    #endif

    return hrSuccess;
}

/*
 *  IMS_GetProps
 *
 *  Purpose:
 *      Returns to the caller the value(s) of one or more
 *      properties existent on an IMS object.  The order of the
 *      properties in the returned ppval structure exactly
 *      matches the order in which the properties were requested in
 *      ptaga.  The caller must free the returned
 *      structure by calling MAPIFreeBuffer(*ppval), but
 *      only if the function returns zero or the error
 *      MAPI_W_ERRORS_RETURNED.  Uses the IMessage on IStorage
 *      property interface implementation.
 *
 *  Arguments:
 *      pims            Pointer to the object.
 *      ptaga           Pointer to a counted array of property tags
 *                      ("names") that identify the values to be
 *                      returned.
 *      ulFlags         UNICODE / String8
 *      pcval       Location in which to return the count of
 *                      elements in *ppval.
 *      ppval   Location in which to return an allocated
 *                      array of property values (the caller frees
 *                      by calling MAPIFreeBuffer).
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      If the call succeeded overall but access to one or more
 *      properties failed, the function returns the warning
 *      MAPI_W_ERRORS_RETURNED.  The calling application should
 *      then check the Property Tag of each of the returned
 *      properties to determine which ones failed.  Those that fail
 *      have their Property Type set to PT_ERROR and their value (a
 *      ULONG) indicates which error occurred.
 *
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_W_ERRORS_RETURNED      See above.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMS_GetProps(PIMS pims, LPSPropTagArray ptaga, ULONG ulFlags,
    ULONG *pcval, LPSPropValue *ppval)
{
    HRESULT hr = hrSuccess;
    LPMESSAGE lpmsg = NULL;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            GetProps,
            (pims,
            ptaga,
            ulFlags,
            pcval,
            ppval));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMS_EnterCriticalSection(pims);

    /* If input parameters are okay, make GetProps call on lpmsgProps. */

    *pcval = 0L;
    *ppval = NULL;

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, FALSE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->GetProps(lpmsg, ptaga, ulFlags, pcval, ppval);

    {if(HR_SUCCEEDED(hr))
    {
        LPSPropValue pvalStoreSupMask = PpropFindProp(*ppval, *pcval,
                                PROP_TAG(PT_UNSPECIFIED, PROP_ID(PR_STORE_SUPPORT_MASK)));
        if(pvalStoreSupMask)
        {
            pvalStoreSupMask->ulPropTag = PR_STORE_SUPPORT_MASK;
            pvalStoreSupMask->Value.l = SMS_SUPPORTMASK;

            /* fix up hr */
            if(ptaga->cValues == 1)
                hr = hrSuccess;
        }
    }
    }
    /* Wrap the store entryids. Note that this function takes as an */
    /* argument the HRESULT from the previous GetProps call. */
    /* We aren't ignoring the error. */

    hr = HrWrap_GetProps(hr, pims, 0, NULL, pcval, ppval, TRUE,
        (ptaga != NULL), (POBJ)pims);

exit:
    UlRelease(lpmsg);

    IMS_LeaveCriticalSection(pims);

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
        DebugTraceResult(IMS_GetProps, hr);
    #endif

    return HrCheckHr(hr, IMAPIProp_GetProps);
}

/*
 *  IMS_GetPropList
 *
 *  Purpose:
 *      Returns a list of all the properties currently accessible.
 *      Uses the IMessage on IStorage property implementation.
 *
 *  Arguments:
 *      pims        Pointer to the object.
 *      ulFlags     UNICODE / String8
 *      pptaga      Location in which to return a pointer
 *                  to a counted array of property tags.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                          to the requested properties.
 *      MAPI_E_CALL_FAILED  The mechanism for making the
 *                          call to the service provider
 *                          failed.
 */
STDMETHODIMP IMS_GetPropList(PIMS pims, ULONG ulFlags, LPSPropTagArray * pptaga)
{
    HRESULT hr = hrSuccess;
    LPMESSAGE lpmsg = NULL;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            GetPropList,
            (pims,
            ulFlags,
            pptaga));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMS_EnterCriticalSection(pims);

    /* If input parameters are okay, make GetPropList call on lpmsgProps. */

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, FALSE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->GetPropList(lpmsg, ulFlags, pptaga);
    /* if ( hr ) fall through to exit */

exit:
    UlRelease(lpmsg);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_GetPropList, hr);
    return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/*
 *  IMS_OpenProperty
 *
 *  Purpose:
 *      Open a requested interface on a property for further
 *      access.  Commonly used for stream access to a large binary
 *      or text property.  This is the only way to access a
 *      property of type PT_OBJECT, and may be used on other
 *      properties depending on the implementation.  Uses the
 *      IMessage on IStorage property implementation, but since the
 *      transaction model for IMsgOnIStg does not match that for
 *      IMsgStore, we fail this call.  (We could do a lot of work
 *      to wrap the interface that IMsgOnIStg returns to us before
 *      returning it to the client, but we don't.)
 *
 *  Arguments:
 *      pims        Pointer to the object.
 *      ulPropTag   Property tag for the desired property.  Only
 *                  the ID bits of the tag are used; the type bits
 *                  are ignored.
 *      lpiid       Pointer to the GUID identifying which interface
 *                  is desired.
 *      lppUnk      Location in which to return a pointer to the
 *                  newly created interface pointer.
 *
 *  Returns:
 *      HRESULT
 *
 *  Errors:
 *      MAPI_E_INVALID_PARAMETER
 *      MAPI_E_NO_SUPPORT   The requested interface is not
 *                              available on the given property.
 */
STDMETHODIMP IMS_OpenProperty(PIMS pims, ULONG ulPropTag, LPCIID lpiid,
    ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN * lppUnk)
{
    SCODE sc;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            OpenProperty,
            (pims,
            ulPropTag,
            lpiid,
            ulInterfaceOptions,
            ulFlags,
            lppUnk));

    sc = MAPI_E_NO_SUPPORT;

    DebugTraceSc(IFLD_OpenProperty, sc);
    return ResultFromScode(sc);
}

/*
 *  IMS_SetProps
 *
 *  Purpose:
 *      Sets the value of one or more properties.  This call passes
 *      a number of Property Value structures.  The Property Tag in
 *      each indicates which property is having its values set and
 *      the value indicates what should be stored.  The caller must
 *      free the returned property problem structure by calling
 *      MAPIFreeBuffer(*lppProblems), but only if the call
 *      succeeded overall.  Uses the IMessage on IStorage property
 *      implementation.
 *
 *  Arguments:
 *      pims            Pointer to the object.
 *      cValues         Number of values in lpPropArray.
 *      lpPropArray     Pointer to a Property Value array.
 *      lppProblems     Location in which to return a pointer to a
 *                      counted array of property problem
 *                      structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, a zero is returned.
 *      If there are problems with setting some or all of the
 *      selected values, and a non-NULL is passed for lppProblems,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in lppProblems is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *lppProblems are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED      A general problem affecting
 *                                  access to all of the object's
 *                                  properties occurred.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMS_SetProps(PIMS pims, ULONG cValues, LPSPropValue lpPropArray,
    LPSPropProblemArray * lppProblems)
{
    HRESULT hr = hrSuccess;
    LPMESSAGE lpmsg = NULL;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            SetProps,
            (pims,
            cValues,
            lpPropArray,
            lppProblems));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* If input parameters are okay, make SetProps call on lpmsgProps. */

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SetProps(lpmsg, cValues, lpPropArray, lppProblems);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
    /* if ( hr ), fall through to exit */

exit:
    UlRelease(lpmsg);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_SetProps, hr);
    return HrCheckHr(hr, IMAPIProp_SetProps);
}

/*
 *  IMS_DeleteProps
 *
 *  Purpose:
 *      Deletes the list of properties given in ptaga.
 *      The caller must free the returned property problem
 *      structure by calling MAPIFreeBuffer(*pprba), but only
 *      if the call succeeded overall.  Uses the IMessage on
 *      IStorage property implementation.
 *
 *  Arguments:
 *      pims    Pointer to the object.
 *      ptaga   Pointer to an array of Property Tags
 *              identifying the properties to delete.
 *      pprba   Location in which to return a pointer to a
 *              counted array of property problem
 *              structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, a zero is returned.
 *      If there are problems with deleting some or all of the
 *      selected values, and a non-NULL is passed for pprba,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in pprba is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *pprba are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED      A general problem affecting
 *                                  access to all of the object's
 *                                  properties occurred.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMS_DeleteProps(PIMS pims, LPSPropTagArray ptaga,
    LPSPropProblemArray *pprba)
{
    HRESULT hr = hrSuccess;
    LPMESSAGE lpmsg = NULL;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            DeleteProps,
            (pims,
            ptaga,
            pprba));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* If input parameters are okay, make DeleteProps call on lpmsg. */

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->DeleteProps(lpmsg, ptaga, pprba);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
    /* if ( hr ), fall through to exit */

exit:
    UlRelease(lpmsg);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_DeleteProps, hr);
    return HrCheckHr(hr, IMAPIProp_DeleteProps);
}

/*
 *  IMS_CopyTo
 *
 *  Purpose:
 *      Copies the contents of the current object to a destination
 *      object.  The entire contents, including contained objects,
 *      are copied, or optionally the caller can provide a list of
 *      properties that are not to be copied.  Previous information
 *      in the destination object which is not overwritten by
 *      copied data is neither deleted nor modified.  It is not a
 *      requirement in MAPI 1.0, however, that CopyTo semantics
 *      understood by the message store object or folder objects,
 *      and so this method always returns MAPI_E_NO_SUPPORT.
 *
 *  Arguments:
 *      pims            Pointer to the source object.
 *      ciidExclude     Count of the excluded interfaces in
 *                      rgiidExclude.
 *      rgiidExclude    Array of interface IDs specifying
 *                      interfaces not to be attempted in trying to
 *                      copy supplemental information to the
 *                      destination object.
 *      ptagaExcl   Counted array of property tags of
 *                      properties that are not to be copied to the
 *                      destination object.  NULL indicates all
 *                      properties are to be copied.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst     Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *      pprba       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_SUPPORT   This method is not supported by
 *                              this type of object.
 */
STDMETHODIMP IMS_CopyTo(PIMS pims, ULONG ciidExcl, LPCIID rgiidExcl,
    LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS
    lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray *pprba)
{
    SCODE sc;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            CopyTo,
            (pims,
            ciidExcl,
            rgiidExcl,
            ptagaExcl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            pprba));

    sc = MAPI_E_NO_SUPPORT;

    DebugTraceSc(IMS_CopyTo, sc);
    return ResultFromScode(sc);
}

/*
 *  IMS_CopyProps
 *
 *  Purpose:
 *      Copies the specified properties of the current object to a destination
 *      object.
 *
 *  Arguments:
 *      pims            Pointer to the source object.
 *      ptagaIncl       Counted array of property tags of
 *                      properties that are to be copied to the
 *                      destination object.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst         Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *                      MAPI_DECLINE_OK
 *      pprba       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_SUPPORT   This method is not supported by
 *                              this type of object.
 */
STDMETHODIMP IMS_CopyProps(PIMS pims,
    LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS
    lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray *pprba)
{
    SCODE sc;

    MS_ValidateParameters(
            pims,
            IMAPIProp,
            CopyProps,
            (pims,
            ptagaIncl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            pprba));

    sc = MAPI_E_NO_SUPPORT;

    DebugTraceSc(IMS_CopyProps, sc);
    return ResultFromScode(sc);
}

/*
 *  IMS_GetNamesFromIDs [also used by IFLD, IMSG, IATCH]
 *
 *  Purpose:
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *
 *  Errors:
 *      MAPI_E_NO_SUPPORT   This method is not yet supported in the sample ms.
 */
STDMETHODIMP IMS_GetNamesFromIDs(PIMS pobj, LPSPropTagArray * pptaga,
    LPGUID lpguid, ULONG ulFlags, ULONG * pcNames, LPMAPINAMEID ** pppNames)
{
    HRESULT hr = hrSuccess;
    LPMAPIPROP pmp = NULL;
    BOOL fReleasePMP = FALSE;

    /* Check input parameters. */

    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))
        || (    pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMAPIProp_GetNamesFromIDs(
                pobj,
                pptaga,
                lpguid,
                ulFlags,
                pcNames,
                pppNames);
    #endif

    OBJ_EnterCriticalSection(pobj);

    switch(pobj->wType)
    {
        case OT_MSGSTORE:
        {
            PIMS pims = pobj->pims;

            hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps,
                &pims->lmr, pims->psup, FALSE, (LPMESSAGE *) &pmp);
            if (hr != hrSuccess)
                goto exit;

            fReleasePMP = TRUE;
            break;
        }

        case OT_FOLDER:
        {
            PIFLD pifld = (PIFLD) pobj;

            hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
                (LPMESSAGE *) &pmp);
            if (hr != hrSuccess)
                goto exit;

            fReleasePMP = TRUE;
            break;
        }

        case OT_MESSAGE:
            pmp = (LPMAPIPROP) ((PIMSG) pobj)->lpmsg;
            break;

        case OT_ATTACH:
            pmp = (LPMAPIPROP) ((PIATCH) pobj)->lpattach;
            break;

        default:
            TrapSz1("Invalid Object Type %08lX found", pobj->wType);
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            break;
    }

    hr = pmp->lpVtbl->GetNamesFromIDs(pmp, pptaga, lpguid, ulFlags, pcNames,
        pppNames);

exit:
    if (fReleasePMP)
        UlRelease(pmp);

    OBJ_LeaveCriticalSection(pobj);

    DebugTraceResult(IMS_GetNamesFromIDs, hr);
    return hr;
}

/*
 *  IMS_GetIDsFromNames [also used by IFLD, IMSG, IATCH]
 *
 *  Purpose:
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IMS_GetIDsFromNames(PIMS pobj, ULONG cNames,
    LPMAPINAMEID * ppNames, ULONG ulFlags, LPSPropTagArray * pptaga)
{
    HRESULT hr = hrSuccess;
    LPMAPIPROP pmp = NULL;
    BOOL fSaveReleasePMP = FALSE;
    BOOL fModifyAccess;

    /* Check input parameters. */
    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))

|| (    pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
            &&  pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMAPIProp_GetIDsFromNames(pobj, cNames, ppNames, ulFlags, pptaga);
    #endif

    OBJ_EnterCriticalSection(pobj);

    fModifyAccess = !!(ulFlags & MAPI_CREATE);

    // Check for correct access mode
    if (    fModifyAccess
        &&  !OBJ_TestFlag(pobj, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    switch(pobj->wType)
    {
        case OT_MSGSTORE:
        {
            PIMS pims = pobj->pims;

            hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps,
                &pims->lmr, pims->psup, fModifyAccess, (LPMESSAGE *) &pmp);
            if (hr != hrSuccess)
                goto exit;

            fSaveReleasePMP = TRUE;
            break;
        }

        case OT_FOLDER:
        {
            PIFLD pifld = (PIFLD) pobj;

            hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
                fModifyAccess, (LPMESSAGE *) &pmp);
            if (hr != hrSuccess)
                goto exit;

            fSaveReleasePMP = TRUE;
            break;
        }

        case OT_MESSAGE:
            pmp = (LPMAPIPROP) ((PIMSG) pobj)->lpmsg;
            break;

        case OT_ATTACH:
            pmp = (LPMAPIPROP) ((PIATCH) pobj)->lpattach;
            break;

        default:
            TrapSz1("Invalid Object Type %08lX found", pobj->wType);
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            break;
    }

    // CAN RETURN WARNINGS!!! Should still continue even when a warning
    // is returned.

    hr = pmp->lpVtbl->GetIDsFromNames(pmp, cNames, ppNames, ulFlags, pptaga);
    if (HR_FAILED(hr))
        goto exit;

    if (fSaveReleasePMP && fModifyAccess)
    {
        HRESULT hrT;

        hrT = pmp->lpVtbl->SaveChanges(pmp, 0);
        if (HR_FAILED(hrT))
        {
            LMFree(&pobj->lmr, *pptaga);
            *pptaga = NULL;
            hr = hrT;
        }
    }

exit:
    if (fSaveReleasePMP)
        UlRelease(pmp);

    OBJ_LeaveCriticalSection(pobj);

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
        DebugTraceResult(IMS_GetIDsFromNames, hr);
    #endif

    return hr;
}

/*
 *  IMS_Advise [Also used by IMSLogon]
 *
 *  Purpose:
 *      Register a client's interest in a set of events that could
 *      occur to an object in this store.  The client's particular
 *      interest is expressed in a ulEventMask, and he is told of
 *      changes through an Advise object which he gives us.
 *      The Sample Store uses the MAPI
 *      Notification Engine to handle notifications, so the
 *      Advise call is translated into a notification
 *      subscription with MAPI.  When events occur in the store
 *      which would cause interested clients to be notified, the
 *      store calls back to MAPI to request that the occurrence of
 *      an event be broadcast.
 *
 *  Arguments:
 *      pims                Pointer to the message store object.
 *      cbEntryID           Size of lpEntryID.
 *      lpEntryID           Pointer to the ID for the object for
 *                          which interest is being registered.
 *                          if NULL then registration is for all changes.
 *      ulEventMask         Indicates the events of interest and
 *                          how to see them.
 *      lpAdviseSink        Pointer to client's Advise Sink object.
 *      lpulConnection      Pointer to a variable in which the
 *                          client gets a cookie for cancelling
 *                          the notifications.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_INVALID_ENTRYID      The EntryID is not recognized
 *                                  as belonging to this store.
 *      MAPI_E_NO_SUPPORT       The implementation does not
 *                                  support notification on this
 *                                  object.
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for a
 *                                  needed buffer.
 */
STDMETHODIMP IMS_Advise(PIMS pobj, ULONG cbEntryID, LPENTRYID lpEntryID,
    ULONG ulEventMask, LPMAPIADVISESINK lpAdviseSink,
    ULONG *lpulConnection)
{
    HRESULT hr = hrSuccess;
    LPNOTIFKEY lpnotifkey = NULL;
    PIMS pims;
    PEID peid;

    /* Check input parameters. */

    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))
        || (    pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
            &&  pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMsgStore_Advise(
            pobj,
            cbEntryID,
            lpEntryID,
            ulEventMask,
            lpAdviseSink,
            lpulConnection);
    #endif

    /* If this is an EntryID for which we support notifications, call the */
    /* MAPI registration function and return the notification object.     */

    OBJ_EnterCriticalSection(pobj);

    pims = pobj->pims;
    peid = (PEID) lpEntryID;
    *lpulConnection = 0;

    if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    if (cbEntryID && !FIsFolder(peid) && !FIsMessage(peid))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    if (cbEntryID == 0)
        peid = NULL;

    hr = HrGetSMSStandardNotifKey(pims, peid, &lpnotifkey);
    if (hr != hrSuccess)
        goto exit;

    if (pims->psup)
    {
        hr = pims->psup->lpVtbl->Subscribe(pims->psup,
            lpnotifkey, ulEventMask, 0L, lpAdviseSink, lpulConnection);
        if (hr != hrSuccess)
            goto exit;
    }
    else
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        /* fall through to exit */
    }

exit:
    OBJ_LeaveCriticalSection(pobj);

    FreeNull(lpnotifkey);

    DebugTraceResult(IMS_Advise, hr);
    return HrCheckHr(hr, IMsgStore_Advise);
}

/*
 *  IMS_Unadvise [Also used by IMSLogon]
 *
 *  Purpose:
 *      Deregister a previous notification.
 *
 *  Arguments:
 *      pims                Pointer to the message store object.
 *      ulConnection        Cookie given out at Advise time.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IMS_Unadvise(PIMS pobj, ULONG ulConnection)
{
    HRESULT hr = hrSuccess;
    LPMAPISUP psup;

    /* Check input parameters. */

    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))
        ||  (   pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
            &&  pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMsgStore_Unadvise(pobj, ulConnection);
    #endif

    OBJ_EnterCriticalSection(pobj);

    psup = pobj->pims->psup;

    if (psup)
        hr = psup->lpVtbl->Unsubscribe(psup, ulConnection);
    else
        hr = ResultFromScode(MAPI_E_CALL_FAILED);

    OBJ_LeaveCriticalSection(pobj);

    DebugTraceResult(IMS_Unadvise, hr);
    return HrCheckHr(hr, IMsgStore_Unadvise);
}

/*
 *  IMS_CompareEntryIDs
 *
 *  Purpose:
 *      Compares two EntryIDs to determine if they refer to the
 *      same object.  This is useful because, in general, an object
 *      may have more than one valid EntryID.  For the Sample
 *      Store, however, an object only has one valid EntryID at any
 *      time and so this function reduces to a check for binary
 *      equality of the EntryIDs.
 *
 *  Arguments:
 *      pims        Pointer to the Message Store Object.
 *      cbEntryID1  Size of first EntryID.
 *      lpEntryID1  Pointer to the first EntryID.
 *      cbEntryID2  Size of second EntryID.
 *      lpEntryID2  Pointer to the second EntryID.
 *      ulFlags     Flags.  Reserved.  Must be zero.
 *      lpulResult  Pointer to a variable in which the result of
 *                  the comparison (TRUE or FALSE) is placed.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_INVALID_ENTRYID  An EntryID is not recognized as
 *                              belonging to this store.
 */
STDMETHODIMP IMS_CompareEntryIDs(PIMS pobj, ULONG cbEntryID1,
    LPENTRYID lpEntryID1, ULONG cbEntryID2, LPENTRYID lpEntryID2, ULONG ulFlags,
    ULONG *lpulResult)
{
    HRESULT hr = hrSuccess;
    PEID peid1;
    PEID peid2;
    PIMS pims;
    BOOL fEID1IsRoot = FALSE;
    BOOL fEID2IsRoot = FALSE;

    /* Check input parameters. */

    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))
        || (    pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
            &&  pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMsgStore_CompareEntryIDs(
                pobj,
                cbEntryID1,
                lpEntryID1,
                cbEntryID2,
                lpEntryID2,
                ulFlags,
                lpulResult);
    #endif

    OBJ_EnterCriticalSection(pobj);

    /* Do a binary comparison of the EIDs, if they're the same size AND */
    /* if we recognize one of them as belonging to this store.          */

    peid1 = (PEID) lpEntryID1;
    peid2 = (PEID) lpEntryID2;
    pims = pobj->pims;

    if ((cbEntryID1 && FIsInvalidEID(cbEntryID1, peid1, pims))
        || (cbEntryID2 && FIsInvalidEID(cbEntryID2, peid2, pims)))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    if (cbEntryID1 == 0 || FIsRoot(peid1))
        fEID1IsRoot = TRUE;

    if (cbEntryID2 == 0 || FIsRoot(peid2))
        fEID2IsRoot = TRUE;

    *lpulResult = FALSE;

    if (fEID1IsRoot == fEID2IsRoot && fEID1IsRoot == TRUE)
        *lpulResult = TRUE;
    else if (cbEntryID1 == cbEntryID2
            && memcmp(peid1, peid2, offsetof(EID, szPath)) == 0
            && peid1->bVersion == SMPMS_VERSION
        && lstrcmpi(peid1->szPath, peid2->szPath) == 0)
        *lpulResult = TRUE;

exit:
    OBJ_LeaveCriticalSection(pobj);

    DebugTraceResult(IMS_CompareEntryIDs, hr);
    return hr;
}

/*
 *  IMS_OpenEntry [Also used by IMSLogon and IMAPIFolder]
 *
 *  Purpose:
 *      Opens an object in this message store.
 *
 *  Arguments:
 *      pobj            Message store on which this function was
 *                      called.
 *      cbEntryID       Size of lpEntryID.
 *      lpEntryID       EntryID of object to open.
 *      piid            IID of interface requested for the
 *                      newly-opened object.  NULL or IID_IMAPIProp
 *                      means to open the object using the standard
 *                      MAPI 1.0 interface for the object.
 *                      IID_IUnknown means to open it using
 *                      the easiest interface you can open.
 *      ulFlags         Flags.  The following are defined:
 *                      MAPI_MODIFY             Write access desired.
 *                      MAPI_DEFERRED_ERRORS    Delayed "open" errors OKAY.
 *                      MAPI_BEST_ACCESS        Open for writing if possible,
 *                                              otherwise, open for reading.
 *      lpulObjType     Address in which to place the type of the
 *                      opened object.
 *      lppUnk          Address in which to place a pointer to the
 *                      opened object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate memory for
 *                                  the to-be-opened object.
 *      MAPI_E_INVALID_ENTRYID      This message store does not
 *                                  recognize this EntryID.
 *      MAPI_E_UNKNOWN_FLAGS
 *      MAPI_E_INVALID_PARAMETER
 */
STDMETHODIMP IMS_OpenEntry(PIMS pobj, ULONG cbEntryID, LPENTRYID lpEntryID,
    LPCIID piid, ULONG ulFlags, ULONG *lpulObjType, LPUNKNOWN *lppUnk)
{
    HRESULT hr = hrSuccess;
    PIMSG pimsg = NULL;
    PIFLD pifld = NULL;
    PEID peidParent = NULL;
    BOOL fEIDIsRoot = FALSE;
    BOOL fModify;
    BOOL fBestAcc;
    PIMS pims;
    PEID peid;

    /* Check input parameters. */

    #ifdef VALIDATE
    if (    IsBadWritePtr(pobj, sizeof(OBJ))
        ||  (   pobj->lpVtbl != (IMS_Vtbl *)&vtblIFLD
             && pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
             && pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    Validate_IMAPIContainer_OpenEntry(
                pobj,
                cbEntryID,
                lpEntryID,
                piid,
                ulFlags,
                lpulObjType,
                lppUnk);
    #endif

    OBJ_EnterCriticalSection(pobj);

    /* Get the real pims object in case pobj is a PIFLD or PMSL */
    pims = pobj->pims;

    peid = (PEID) lpEntryID;

    /* If parameters are okay, see if this is an EntryID we understand. */

    if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    if (cbEntryID == 0 || FIsRoot(peid))
        fEIDIsRoot = TRUE;

    /* determine if the caller wants modification access */
    fModify = (ulFlags & (MAPI_MODIFY | MAPI_BEST_ACCESS)) != 0;
    fBestAcc = (ulFlags & MAPI_BEST_ACCESS) != 0;

    /* Fail if attempting to open an object for */
    /* modification in a read-only store.       */

    if (fModify
        && !OBJ_TestFlag(pims, OBJF_MODIFY))
    {
        if (fBestAcc)
            fModify = FALSE;
        else
        {
            hr = ResultFromScode(MAPI_E_NO_ACCESS);
            goto exit;
        }
    }

    /* Open the object */

    if (fEIDIsRoot)
    {
        PEID peidRoot = NULL;
        CHAR ch = '\0';
        MAPIUID uid;

        if (piid && !FQueryInterface(OT_FOLDER, piid))
        {
            hr = ResultFromScode(E_NOINTERFACE);
            goto exit;
        }

        GetResourceUID(pims, &uid);

        hr = HrConstructEID(&uid, &pims->lmr, (LPSTR) &ch, &peidRoot);
        if (hr != hrSuccess)
            goto exit;

        hr = HrNewIFLD(peidRoot, pims, fModify, &pifld);

        if (hr == hrSuccess)
            hr = HrSetInternalProps(&pims->lmr, cpropIFLDInternal,
                &(pifld->pval), &(pifld->cval), peidRoot, peidRoot, 0);

        LMFree(&pims->lmr, peidRoot);

        if (hr != hrSuccess)
            goto exit;

        *lppUnk = (LPUNKNOWN) pifld;
        *lpulObjType = MAPI_FOLDER;
    }
    else
    {
        hr = HrGetParentEID(&pims->lmr, peid, &peidParent);
        if (hr != hrSuccess)
            goto exit;

        if (FIsMessage(peid))
        {
            ULONG ulSeqNum;

            if (piid && !FQueryInterface(OT_MESSAGE, piid))
            {
                hr = ResultFromScode(E_NOINTERFACE);
                goto exit;
            }

            hr = HrGetSequenceNum(peid, &ulSeqNum);

            hr = HrNewIMSG(peid, pims, FALSE, fModify, ulSeqNum, NULL, &pimsg);

            if (GetScode(hr) == MAPI_E_SUBMITTED && fBestAcc && fModify)
            {
                fModify = FALSE;
                hr = HrNewIMSG(peid, pims, FALSE, fModify, ulSeqNum, NULL, &pimsg);
            }

            if (hr != hrSuccess)
                goto exit;

            *lppUnk = (LPUNKNOWN) pimsg;
            *lpulObjType = MAPI_MESSAGE;
        }
        else /* a folder */
        {
            if (!FIsFolder(peid))
            {
                TrapSz("Logic error");
                hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
                goto exit;
            }

            if (piid && !FQueryInterface(OT_FOLDER, piid))
            {
                hr = ResultFromScode(E_NOINTERFACE);
                goto exit;
            }

            hr = HrNewIFLD(peid, pims, fModify, &pifld);
            if (hr != hrSuccess)
                goto exit;

            hr = HrSetInternalProps(&pims->lmr, cpropIFLDInternal,
                &(pifld->pval), &(pifld->cval), peid, peidParent, 0);
            if (hr != hrSuccess)
                goto exit;

            *lppUnk = (LPUNKNOWN) pifld;
            *lpulObjType = MAPI_FOLDER;
        }
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No Warnings expected");

    LMFree(&pims->lmr, peidParent);

    if (hr != hrSuccess)
    {
        UlRelease(pimsg);
        UlRelease(pifld);
    }

    OBJ_LeaveCriticalSection(pobj);

    DebugTraceResult(IMS_OpenEntry, hr);
    return HrCheckHr(hr, IMsgStore_OpenEntry);
}

/*
 *  IMS_SetReceiveFolder
 *
 *  Purpose:
 *      Sets the receive folder for a particular message class.  A
 *      message class is a string with "." delimiters, e.g
 *      "IPM.Note".  This method also removes a setting if the
 *      EntryID parameter (see below) is NULL.  Because there must
 *      be only one set of receive folder settings per store (NOT
 *      per logon), the settings are stored on disk in an OLE2
 *      docfile, and all access to them is done on disk, not in
 *      memory (see recfldr.c, recfldr.h for details).
 *
 *  Arguments:
 *      pims                Pointer to the object.
 *      szMessageClass  String identifying a message class.  If
 *                          NULL, then the default receive folder
 *                          is set.
 *      ulFlags             Flags.
 *      cbEntryID           Size of lpEntryID.
 *      lpEntryID           Pointer to the identifier of a
 *                          particular folder in the store.  If
 *                          this pointer is NULL, the receive
 *                          folder setting is removed.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      IMS_SetReceiveFolder will not actually check to see if the
 *      EntryID it is given exists in the store at this time, so
 *      will go ahead and create an invalid receive folder setting
 *      for that particular message class.
 *
 *  Errors:
 *      MAPI_E_INVALID_ENTRYID  The store doesn't recognize this
 *                              EntryID as belonging to it.
 */
STDMETHODIMP IMS_SetReceiveFolder(PIMS pims, LPTSTR szMessageClass,
    ULONG ulFlags, ULONG cbEntryID, LPENTRYID lpEntryID)
{
    SCODE sc;
    HRESULT hr = hrSuccess;
    TCHAR rgchDefMsgClass[] = TEXT("");
    LPTSTR szNormalizedClass = NULL;
    RFN rfn;
    PEID peid;

    /* Check input parameters. */

    MS_ValidateParameters(
            pims,
            IMsgStore,
            SetReceiveFolder,
            (pims,
            szMessageClass,
            ulFlags,
            cbEntryID,
            lpEntryID));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    peid = (PEID) lpEntryID;

    /* don't allow removal of the default settings */
    if (peid == NULL &&
        (szMessageClass == NULL || *szMessageClass == '\0'))
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    /* If parameters are okay, see if this is an EntryID we understand. */

    if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    /* The RFS module always expects a valid string */
    if (!szMessageClass)
        szMessageClass = rgchDefMsgClass;

    /* Make a copy of the szMessageClass that is normalized */

    sc = LMAlloc(&pims->lmr, Cbtszsize(szMessageClass), &szNormalizedClass);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }
    lstrcpy (szNormalizedClass, szMessageClass);
    CharUpper (szNormalizedClass);

    if (cbEntryID)
    {
        PRFN prfnExisting = NULL;

        rfn.szClass = szNormalizedClass;
        rfn.szName = peid->szPath;

        hr = GetRFN(pims->prfs, szNormalizedClass, &prfnExisting);
        if (hr != hrSuccess)
        {
            if (GetScode(hr) == MAPI_E_NOT_FOUND)
                hr = hrSuccess;
            else
                goto exit;
        }
        else
        {
            /* Remove the existing receive folder setting, but only if it */
            /* _exactly_ matches the one that we're adding.               */
            /*  //$ Are message classes case-sensitive? */

            if (!lstrcmp(szMessageClass, prfnExisting->szClass))
                hr = DeleteRFN(pims->prfs, szNormalizedClass);

            FreeRFN(prfnExisting);

            if (hr != hrSuccess)
                goto exit;
        }

        /* WARNING:  If the addition fails, we can't easily  */
        /* revert to a previous version of the RFS settings. */

        hr = AddRFN(pims->prfs, &rfn);
        /* if ( hr ), fall through to exit */
    }
    else
    {
        hr = DeleteRFN(pims->prfs, szNormalizedClass);
        /* if ( hr ), fall through to exit */
    }

exit:
    LMFree(&pims->lmr, szNormalizedClass);
    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_SetReceiveFolder, hr);
    return HrCheckHr(hr, IMsgStore_SetReceiveFolder);
}

/*
 *  IMS_GetReceiveFolder
 *
 *  Purpose:
 *      Obtains the receive folder setting for a particular message
 *      class and other information about the receive behavior of
 *      that message class.  This function obtains the EntryID of
 *      the folder where messages of a specific class are placed.
 *      If szMessageClass does not explicitly set a receive
 *      folder, then the receive folder of the first superclass of
 *      szMessageClass which does explicitly set a receive folder
 *      is returned.  Whichever message class that explicitly sets
 *      the receive folder is returned in pszExplicitClass.  For
 *      example, if the receive folder of the message class
 *      "IPM.Note" has been set to the EntryID of the Inbox and an
 *      application calls GetReceiveFolder() on the message class
 *      "IPM.Note.Phone," the EntryID of the Inbox is returned as
 *      the lppEntryID, and "IPM.Note" is retuned in
 *      pszExplicitClass.  The converse is not true:  if the
 *      receive folder setting of "IPM.Note.Phone" is the Inbox and
 *      the client asks for the setting of "IPM.Note", NULL is
 *      returned.
 *
 *  Arguments:
 *      pims                Pointer to the object.
 *      szMessageClass  Identifies the particular message
 *                          class.  If this pointer is NULL, then
 *                          the default is returned.
 *      ulFlags             Flags.
 *      lpcbEntryID         Address of the location in which to
 *                          return the size of the EntryID in
 *                          *lppEntryID.
 *      lppEntryID          Address of the location in which to
 *                          return a pointer to an EntryID which is
 *                          the identifier of the receive folder.
 *      pszExplicitClass    Address of the location in which to
 *                          return a pointer a buffer containing
 *                          the message class that explicitly sets
 *                          its receive folder to *lppEntryID.  If
 *                          NULL indicates that no class name
 *                          should be returned.  If it is exactly
 *                          equal to szMessageClass, nothing is
 *                          returned (*pszExplicitClass == NULL).
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_GetReceiveFolder(PIMS pims, LPTSTR szMessageClass,
    ULONG ulFlags, ULONG *lpcbEntryID, LPENTRYID *lppEntryID,
    LPTSTR *pszExplicitClass)
{
    HRESULT hr = hrSuccess;
    ULONG cbeid = 0L;
    PEID peid = NULL;
    LPTSTR szExCls = NULL;
    PRFN prfn = NULL;
    TCHAR rgchDefMsgClass[] = TEXT("");
    LPTSTR szNormalizedClass = NULL;
    MAPIUID uid;
    SCODE sc = S_OK;

    /* Check input parameters. */

    MS_ValidateParameters(
            pims,
            IMsgStore,
            GetReceiveFolder,
            (pims,
            szMessageClass,
            ulFlags,
            lpcbEntryID,
            lppEntryID,
            pszExplicitClass));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMS_EnterCriticalSection(pims);

    *lpcbEntryID = 0L;
    *lppEntryID = NULL;

    if (pszExplicitClass)
        *pszExplicitClass = NULL;

    /* The RFS module always expects a valid string */

    if (!szMessageClass)
        szMessageClass = rgchDefMsgClass;

    /* Make a copy of the szMessageClass that is normalized */

    sc = LMAlloc(&pims->lmr, Cbtszsize(szMessageClass), &szNormalizedClass);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }
    lstrcpy (szNormalizedClass, szMessageClass);
    CharUpper (szNormalizedClass);

    hr = GetRFN(pims->prfs, szNormalizedClass, &prfn);
    if (hr != hrSuccess)
        goto exit;

    /* Allocate and set return variables. */

    GetResourceUID(pims, &uid);

    hr = HrConstructEID(&uid, &pims->lmr, prfn->szName, &peid);
    if (hr != hrSuccess)
        goto exit;

    cbeid = CbEID(peid);

    if (pszExplicitClass)
    {
        sc = LMAlloc(&pims->lmr, Cbtszsize(prfn->szClass), &szExCls);
        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        lstrcpy(szExCls, prfn->szClass);
        *pszExplicitClass = szExCls;
    }

    *lpcbEntryID = cbeid;
    *lppEntryID = (LPENTRYID) peid;

exit:
    FreeRFN(prfn);
    LMFree(&pims->lmr, szNormalizedClass);
    if (hr != hrSuccess)
    {
        LMFree(&pims->lmr, peid);
        LMFree(&pims->lmr, szExCls);
    }

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_GetReceiveFolder, hr);
    return HrCheckHr(hr, IMsgStore_GetReceiveFolder);
}

STDMETHODIMP IMS_GetReceiveFolderTable(PIMS pims, ULONG ulFlags,
    LPMAPITABLE *lppTable)
{
    MS_ValidateParameters(
            pims,
            IMsgStore,
            GetReceiveFolderTable,
            (pims,
            ulFlags,
            lppTable));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    DebugTraceSc(IMS_GetReceiveFolderTable, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/*
 *  IMS_StoreLogoff
 *
 *  Purpose:
 *      Allows the orderly release of a store under client control.
 *      Use of this function to release a store allows some client
 *      control over what MAPI will do about transport activity.
 *      The client can either put itself in the loop by setting the
 *      appropriate flags, or it can allow MAPI to either abort the
 *      sending of mail or to complete it prior to invalidating the
 *      object.  This behavior will only occur when the client is
 *      the only application which is using the message store, and
 *      will happen during release of the store object.  If
 *      another client is still using the store, the store object
 *      will remember the flags and will issue the call during the
 *      final release.
 *
 *  Arguments:
 *      pims        Pointer to the object.
 *      pulFlags    Flags.  The following are defined as input:
 *                  LOGOFF_NO_WAIT  Don't wait for the transports.
 *                                  All outboutnd mail that is
 *                                  ready to be sent will be sent.
 *                                  Control is returned to the
 *                                  client immediately.
 *                  LOGOFF_ORDERLY  Don't wait for the transports.
 *                                  Any currently in-process
 *                                  message on the store is
 *                                  completed; no new ones are

*                                  started.  Control is returned
 *                                  to the client immediately.
 *                  LOGOFF_PURGE    Same as LOGOFF_NO_WAIT but
 *                                  PurgeQueues() called for
 *                                  appropriate transports and
 *                                  client waits for completion.
 *                  LOGOFF_ABORT    Any transport activity on this
 *                                  store should be aborted.
 *                                  Control is returned to the
 *                                  client when abort completes.
 *                  LOGOFF_QUIET    If any transport activity is
 *                                  taking place, the logoff will
 *                                  not occur.
 *                  The following are defined as output flags:
 *                  LOGOFF_COMPLETE         All resources
 *                                          associated with the
 *                                          store have been
 *                                          released and the object
 *                                          invalidated.
 *                  LOGOFF_INBOUND          A message is currently
 *                                          coming into the store
 *                                          from one or more
 *                                          transports.
 *                  LOGOFF_OUTBOUND         A message is currently
 *                                          being sent from the
 *                                          store by one or more
 *                                          transports.
 *                  LOGOFF_OUTBOUND_QUEUE   Messages are currently
 *                                          in the outbound queue
 *                                          for the store.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      See flag description for possible side effects.
 *
 *  Errors:
 *      Various.
 */

STDMETHODIMP IMS_StoreLogoff(PIMS pims, ULONG * pulFlags)
{
    HRESULT hr = 0;
    LPMAPISUP psup = NULL;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            StoreLogoff,
            (pims,
            pulFlags));

    IMS_EnterCriticalSection(pims);

    pims->ulFlagsSLT = *pulFlags;
    *pulFlags = LOGOFF_COMPLETE;

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_StoreLogoff, hr);
    return HrCheckHr(hr, IMsgStore_StoreLogoff);
}

/*
 *  IMS_AbortSubmit
 *
 *  Purpose:
 *      Removes the current message from the submission queue.
 *      Since this is not implemented in IMessage on IStorage, we
 *      must do it ourselves.
 *
 *  Arguments:
 *      lpMS        Pointer to the message store.
 *      cbEntryID   the size of the entry ID
 *      lpEntryID   the entry ID of the message to abort
 *      ulFlags     Flags.  Reserved for future use.  Must be zero.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_IN_QUEUE     The message was never successfully
 *                              submitted (and is thus not in the
 *                              outgoing queue).
 *      MAPI_E_UNABLE_TO_ABORT  The underlying messaging system no
 *                              longer allows the submission to be
 *                              cancelled.
 */
STDMETHODIMP IMS_AbortSubmit(PIMS pims, ULONG cbEntryID, LPENTRYID lpEntryID,
    ULONG ulFlags)
{
    HRESULT hr = hrSuccess;
    PIMSG pimsg = NULL;
    ULONG ulObjType;
    BOOL fClearSpooler = FALSE;
    ULONG ulSF;
    PEID peid;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            AbortSubmit,
            (pims,
            cbEntryID,
            lpEntryID,
            ulFlags));

    IMS_EnterCriticalSection(pims);

    peid = (PEID) lpEntryID;

    /* Note that we don't allow a NULL entryid here, because the root */
    /* folder is not a valid input to AbortSubmit. */

    if (FIsInvalidEID(cbEntryID, peid, pims)
        && !FIsMessage(peid))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    /* unlock the message */
    /* make this store look like the spooler so we can open it if locked */

    if (!OBJ_TestFlag(pims, MSF_SPOOLER))
    {
        OBJ_SetFlag(pims, MSF_SPOOLER);
        fClearSpooler = TRUE;
    }

    hr = pims->lpVtbl->OpenEntry(pims, cbEntryID, lpEntryID,
        NULL, MAPI_MODIFY, &ulObjType, (LPUNKNOWN *) &pimsg);
    if (hr != hrSuccess)
        goto exit;

    /* If we can't get PR_SUBMIT_FLAGS from the message, then either */
    /* the message hasn't been submitted, or something else is broken. */
    /* In any case, we can't abort the submit. */
    /* If the message is locked already by the spooler, then we also */
    /* can't abort the submit. */

    hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
    if ((hr != hrSuccess) || (ulSF & SUBMITFLAG_LOCKED))
    {
        hr = ResultFromScode(MAPI_E_UNABLE_TO_ABORT);
        goto exit;
    }

    hr = HrSetFlags(pimsg, UNSET, PR_MESSAGE_FLAGS, MSGFLAG_SUBMIT);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetFlags(pimsg, UNSET, PR_SUBMIT_FLAGS, SUBMITFLAG_LOCKED);
    if (hr != hrSuccess)
        goto exit;

    hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    hr = HrUpdateOutgoingQueue(pims, NULL, (PEID) lpEntryID,
        TABLE_ROW_DELETED);

exit:
    UlRelease(pimsg);

    if (fClearSpooler)
        OBJ_ClearFlag(pims, MSF_SPOOLER);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_AbortSubmit, hr);
    return HrCheckHr(hr, IMsgStore_AbortSubmit);
}

/*
 *  IMS_GetOutgoingQueue
 *
 *  Purpose:
 *      Returns a MAPI Table Object of the queue of messages
 *      waiting to be sent.
 *
 *  Arguments:
 *      pims        Pointer to a Spooler Message Store Object.
 *      ulFlags     Reserved for future use.  Must be zero.
 *      lppTable    Location to return the new table object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_GetOutgoingQueue(PIMS pims, ULONG ulFlags,
    LPMAPITABLE *lppTable)
{
    SCODE sc = S_OK;
    HRESULT hr = hrSuccess;
    LPMAPITABLE ptbl = NULL;
    BOOL fInMutex = FALSE;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            GetOutgoingQueue,
            (pims,
            ulFlags,
            lppTable));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, MSF_SPOOLER))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    /* If the file mutex doesn't yet exist on this process, create it. */

    if (pims->hOGQueueMutex == NULL)
    {
        hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
        if (hr != hrSuccess)
            goto exit;
    }

    /* Get the file mutex so that we can use the file (and change it) */
    /* without crossing paths with another process. */

    WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
    fInMutex = TRUE;

    /* Create a new table if there currently is none */
    if (pims->lptblOutgoing == NULL)
    {
        hr = HrNewOutgoingTableData(pims);
        if (hr != hrSuccess)
            goto exit;
    }

    /* open a view on the table */
    hr = pims->lptblOutgoing->lpVtbl->HrGetView(pims->lptblOutgoing,
        NULL, OutgoingViewRelease, (ULONG) pims, &ptbl);
    if (hr != hrSuccess)
        goto exit;

    pims->cOutgoingViews++;

    *lppTable = ptbl;

    Assert(hrSuccess == hr);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr),
        "Unexpected warning return");

    if (fInMutex)
        ReleaseMutex(pims->hOGQueueMutex);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_GetOutgoingQueue, hr);
    return HrCheckHr(hr, IMsgStore_GetOutgoingQueue);
}

/*
 *  IMS_SetLockState
 *
 *  Purpose:
 *      Allows the spooler to lock a message so that no one else
 *      can modify it while the spooler processes it.
 *
 *  Arguments:
 *      pims        Pointer to the Message Store Object.
 *      lpMessage   The message object to be locked
 *      ulFlags     control flags
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_SetLockState(PIMS pims, LPMESSAGE lpMessage, ULONG ulFlags)
{
    SCODE sc = S_OK;
    HRESULT hr = hrSuccess;
    PIMSG pimsg = (PIMSG) lpMessage;
    ULONG ulSF;
    LPSPropValue pval = NULL;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            SetLockState,
            (pims,
            lpMessage,
            ulFlags));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, MSF_SPOOLER))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
    if (hr != hrSuccess)
    {
        if (GetScode(hr) == MAPI_E_NOT_FOUND)
        {
            ULONG ulMF;

            hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_MESSAGE_FLAGS, &ulMF);
            if (hr != hrSuccess)
            {
                if (GetScode(hr) == MAPI_E_NOT_FOUND)
                    hr = ResultFromScode(MAPI_E_CORRUPT_STORE);

                goto exit;
            }

            if (ulMF & MSGFLAG_SUBMIT)
                hr = ResultFromScode(MAPI_E_CORRUPT_STORE);
            else
                hr = ResultFromScode(MAPI_E_NOT_IN_QUEUE);
        }
        goto exit;
    }

    /* set the lock state, if the message is already in the correct state */
    /* just get outta here */

    if (ulFlags & MSG_LOCKED)
    {
        if (!(ulSF & SUBMITFLAG_LOCKED))
            ulSF |= SUBMITFLAG_LOCKED;
        else
            goto exit;
    }
    else
        /* unlock */
    {
        if (ulSF & SUBMITFLAG_LOCKED)
            ulSF &= ~SUBMITFLAG_LOCKED;
        else
            goto exit;
    }

    hr = HrSetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
    if (hr != hrSuccess)
        goto exit;

    hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    /* No need to call ChangeTable to update tables because the SaveChanges
     * call above just did that.
     */

exit:
    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_SetLockState, hr);
    return HrCheckHr(hr, IMsgStore_SetLockState);
}

/*
 *  IMS_FinishedMsg
 *
 *  Purpose:
 *      Allows the Spooler to inform the message store that it has
 *      finished processing a message (cancels a previous
 *      MDBLockMsg).
 *
 *  Arguments:
 *      pims        Pointer to a Message Store Object.
 *      ulFlags     Reserved for future use.  Ignored.
 *      lpEntryID   EntryID of message that was locked.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_FinishedMsg(PIMS pims, ULONG ulFlags, ULONG cbEntryID,
    LPENTRYID lpEntryID)
{
    PIMSG pimsg = NULL;         /* opened message */
    PIFLD pifldParent = NULL;   /* parent folder of this message */
    ULONG ulObjectType;
    HRESULT hr = hrSuccess;
    SCODE sc = S_OK;
    PEID peid = (PEID) lpEntryID;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            FinishedMsg,
            (pims,
            ulFlags,
            cbEntryID,
            lpEntryID));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, MSF_SPOOLER))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    if (FIsInvalidEID(cbEntryID, peid, pims))
    {
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    hr = pims->lpVtbl->OpenEntry(pims, cbEntryID, lpEntryID,
        NULL, MAPI_MODIFY, &ulObjectType, (LPUNKNOWN *) &pimsg);
    if (hr != hrSuccess)
        goto exit;

    Assert(ulObjectType == MAPI_MESSAGE);

    /* update the parent folder */
    hr = HrOpenParent(pims, peid, MAPI_MODIFY, &pifldParent);
    if (hr != hrSuccess)
        goto exit;

    /* unlock the message */
    hr = HrSetFlags(pimsg, UNSET, PR_MESSAGE_FLAGS,
        MSGFLAG_UNSENT | MSGFLAG_SUBMIT);
    if (hr != hrSuccess)
        goto exit;

    /* Mark the message read */
    hr = HrSetFlags(pimsg, SET, PR_MESSAGE_FLAGS, MSGFLAG_READ);
    if (hr != hrSuccess)
        goto exit;

    /* Clear submitflag_locked and save the message */
    hr = HrSetFlags(pimsg, UNSET, PR_SUBMIT_FLAGS, SUBMITFLAG_LOCKED);
    if (hr != hrSuccess)
        goto exit;

    hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->psup->lpVtbl->DoSentMail(pims->psup, 0L, (LPMESSAGE) pimsg);
    if (hr != hrSuccess)
        goto exit;
    pimsg = NULL;

    Assert(pims->lptblOutgoing);

    hr = HrUpdateOutgoingQueue(pims, NULL, (PEID) lpEntryID,
        TABLE_ROW_DELETED);

exit:
    UlRelease(pifldParent);
    UlRelease(pimsg);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_FinishedMsg, hr);
    return HrCheckHr(hr, IMsgStore_FinishedMsg);
}

/*
 *  IMS_NotifyNewMail
 *
 *  Purpose:
 *      Spooler tells us when to tell clients about a new message.
 *      Passed in on the call is the exact notification structure
 *      we need to give the client.
 *
 *      Stubbed for now.
 *
 *  Arguments:
 *      pims        Pointer to the object.
 *      pntf        Pointer to the newmail notification structure.
 *                                      read/write.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
STDMETHODIMP IMS_NotifyNewMail(PIMS pims, LPNOTIFICATION pntf)
{
    HRESULT hr = hrSuccess;
    LPNOTIFKEY lpKey = NULL;
    ULONG ulFlags = 0;
    PEID peidMsg;
    PEID peidFld;
    ULONG cbEIDMsg;
    ULONG cbEIDFld;

    MS_ValidateParameters(
            pims,
            IMsgStore,
            NotifyNewMail,
            (pims,
            pntf));

    IMS_EnterCriticalSection(pims);

    if (!OBJ_TestFlag(pims, MSF_SPOOLER))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    if (!pims->psup)
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    cbEIDMsg = pntf->info.newmail.cbEntryID;
    peidMsg = (PEID) pntf->info.newmail.lpEntryID;

    cbEIDFld = pntf->info.newmail.cbParentID;
    peidFld = (PEID) pntf->info.newmail.lpParentID;

    if (    cbEIDMsg == 0
        ||  FIsInvalidEID(cbEIDMsg, peidMsg, pims)
        ||  !FIsMessage(peidMsg)
        ||  cbEIDFld == 0
        ||  FIsInvalidEID(cbEIDFld, peidFld, pims)
        ||  !FIsFolder(peidFld))
    {
        TraceSz("SMS: Bad entryid given to NotifyNewMail");
        hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto exit;
    }

    /* Generate the key we use for notifications. First, get the key */
    /* for the folder that the message arrived into. */

    hr = HrGetSMSStandardNotifKey(pims, peidFld, &lpKey);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, pntf, &ulFlags);
    if (hr != hrSuccess)
        goto exit;

    FreeNull(lpKey);
    lpKey = NULL;
    ulFlags = 0;

    /* Now, get the key for the entire store, and send to that key also. */

    hr = HrGetSMSStandardNotifKey(pims, NULL, &lpKey);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, pntf, &ulFlags);
    if (hr != hrSuccess)
        goto exit;

exit:
    FreeNull(lpKey);

    IMS_LeaveCriticalSection(pims);

    DebugTraceResult(IMS_NotifyNewMail, hr);
    return HrCheckHr(hr, IMsgStore_NotifyNewMail);
}

/* Internal Functions */

BOOL IMS_IsInvalid(PIMS pims)
{
    return (IsBadWritePtr(pims, sizeof(IMS)) || pims->lpVtbl != &vtblIMS);
}

/***************************************************************************
 -  OutgoingViewRelease
 -
 *  Purpose:
 *      Call back function from itable on release of a view
 *      removes the view from the list of open views
 *      releases the table if there are no more open views on it
 *
 *  Arguments:
 *       ulCallerData   pointer to folder object
 *       lptbl      pointer to the table on which this is a view
 *       lpvtView       pointer to the view that was released
 *
 *
 */
STDAPI_(void) OutgoingViewRelease(ULONG ulCallerData, LPTABLEDATA lptbl,
    LPMAPITABLE lpvtView)
{
    PIMS pims;                  /* store who owns this view */
    ULONG ulViewsLeft;          /* number of open views left */

    pims = (PIMS) ulCallerData;

    /* do nothing if the message store is gone */
    if (IMS_IsInvalid(pims))
        return;

    IMS_EnterCriticalSection(pims);

    AssertSz(pims->lptblOutgoing == lptbl,
        "Different table data given to OutgoingViewRelease");

    ulViewsLeft = --(pims->cOutgoingViews);

    /* release the table data if the viewlist is empty */
    if (ulViewsLeft == 0)
    {
        UlRelease(lptbl);
        pims->lptblOutgoing = NULL;
    }

    IMS_LeaveCriticalSection(pims);

    return;
}

/*
 *  HrNewIMS
 *
 *  Purpose:
 *      Allocates and initializes an IMS object (internal
 *      implementation of IMsgStore).
 *
 *  Arguments:
 *      szStorePath         Path name of directory which is the
 *                          "root" of this message store.
 *      szStoreProps        Filename of IMsgStore properties
 *                          docfile in the store root.
 *      pmsp                pointer to the MS provider object.
 *      pmsl                Pointer to the MS logon object given to
 *                          MAPI when this store is created.
 *      prfs                Pointer to a context for accessing
 *                          receive folder settings.
 *      pps                 pointer to our profile section.
 *      psup                Pointer to MAPI Support Object.
 *      fCreate             TRUE if the function should create the docfile
 *                          containing IMS properties (FALSE opens existing).
 *      ppims               Location to return the address of the
 *                          newly created IMS object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Does AddRef() of support object.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate memory for
 *                                  the object.
 */
HRESULT HrNewIMS(LPTSTR szStorePath, LPTSTR szStoreProps, PMSP pmsp, PMSL pmsl,
    PRFS prfs, LPPROFSECT pps, LPMAPISUP psup, BOOL fCreate, PIMS *ppims)
{
    SCODE sc = S_OK;
    HRESULT hr;
    HRESULT hrStg = hrSuccess;
    LPTSTR szPropFull = NULL;
    PIMS pimsNew = NULL;
    PEID peid = NULL;
    LPMESSAGE lpmsg = NULL;
    BOOL fDoneCreate = FALSE;
    LPMSGSESS pmsgsess = NULL;
    LPSPropValue pvalDLLName = NULL;

    AssertSz(szStorePath, "Bad szStorePath");
    AssertSz(szStoreProps, "Bad szStoreProps");
    AssertSz(pmsl, "Bad pmsl");
    AssertSz(prfs, "Bad prfs");
    AssertSz(ppims, "Bad ppims");

    *ppims = NULL;

    /* Begin by creating or opening the message store's property file. */
    /* This implementation of the sample message store puts its message */
    /* store properties in a file called MSGSTORE.PRP in the root folder */
    /* of the store. If we're opening an existing store, then we'll read */
    /* PR_RECORD_KEY from this file below. If we're creating the file, we */
    /* don't actually use it in this function; however, we still need to */
    /* create the file. */

    hr = HrOpenIMsgSession(&pmsgsess);
    if (hr != hrSuccess)
        goto hr_err;

    hr = HrAppendPath(szStorePath, szStoreProps, &szPropFull);
    if (hr != hrSuccess)
        goto hr_err;

    hr = HrOpenIMsg(pmsgsess, szPropFull, &pmsp->lmr, psup, fCreate, FALSE,
        TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto hr_err;

    if (fCreate)
        fDoneCreate = TRUE;

    /* Allocate and fill in the new object. */

    sc = LMAllocZ(&pmsp->lmr, sizeof(IMS), &pimsNew);
    if (sc != S_OK)
        goto sc_err;

    OBJ_Initialize(pimsNew, &vtblIMS, OT_MSGSTORE, pimsNew, &pmsl->cs);

    pimsNew->pmsl = pmsl;
    pimsNew->pmsp = pmsp;
    pimsNew->prfs = prfs;
    pimsNew->psup = psup;
    pimsNew->lmr = pmsl->lmr;
    pimsNew->eidStore.cb = 0L;
    pimsNew->eidStore.lpb = NULL;
    pimsNew->lptblOutgoing = NULL;
    pimsNew->cOutgoingViews = 0L;
    pimsNew->ulOQConn = 0L;
    pimsNew->ulTblConn = 0L;
    pimsNew->pmsgsess = pmsgsess;
    pimsNew->ulFlagsSLT = LOGOFF_ABORT;

    sc = ScAlloc(Cbtszsize(szStorePath), (PPV) &pimsNew->szStorePath);
    if (sc != S_OK)
        goto sc_err;

    lstrcpy(pimsNew->szStorePath, szStorePath);

    pimsNew->szProps = szPropFull;

    /* Fill in the uidResource.
     * This is used as the PR_STORE_RECORD_KEY and
     * as the UID contained in EntryIDs.
     */
    if (fCreate)
    {
        hr = psup->lpVtbl->NewUID(psup, &pimsNew->uidResource);
        if (hr != hrSuccess)
            goto hr_err;
    }
    else
    {
        LPSPropValue pval;

        /* Read out the PR_RECORD_KEY from the store.   */
        /* DO NOT READ THE PR_STORE_RECORD_KEY as that  */
        /* gets wrapped back to pimsNew->uidResource and    */
        /* HAS NOT BEEN FILLED IN YET!                      */

        hr = HrGetOneProp((LPMAPIPROP) lpmsg, PR_RECORD_KEY, &pval);
        if (hr != hrSuccess)
            goto hr_err;

        AssertSz(pval[0].Value.bin.cb == sizeof(MAPIUID),
            "Corrupted data returned from GetProps");

        pimsNew->uidResource = *((LPMAPIUID) pval[0].Value.bin.lpb);

        LMFree(&pmsp->lmr, pval);
    }

    /* Generate the PR_STORE_ENTRYID property in memory. */

    hr = HrConstructEID(&pimsNew->uidResource, &pmsp->lmr,
            pimsNew->szStorePath, &peid);
    if (hr != hrSuccess)
        goto hr_err;

    hr = HrGetOneProp((LPMAPIPROP)pps, PR_PROVIDER_DLL_NAME, &pvalDLLName);
    if (hr != hrSuccess)
        goto hr_err;

    hr = WrapStoreEntryID(0, pvalDLLName->Value.lpszA, CbEID(peid),
            (LPENTRYID)peid, &pimsNew->eidStore.cb,
            (LPENTRYID *) &pimsNew->eidStore.lpb);
    if (hr != hrSuccess)
        goto hr_err;

sc_err:
    if (sc != S_OK)
        hr = ResultFromScode(sc);

hr_err:
    LMFree(&pmsp->lmr, pvalDLLName);
    UlRelease(lpmsg);
    LMFree(&pmsp->lmr, peid);

    if (hr != hrSuccess)
    {
        if (fDoneCreate)
            DeleteFile(szPropFull);

        FreeNull(szPropFull);
        if (pimsNew)
        {
            FreeNull(pimsNew->szStorePath);
            LMFree(&pmsp->lmr, pimsNew->eidStore.lpb);
            LMFree(&pmsp->lmr, pimsNew);
        }

        if (pmsgsess)
            CloseIMsgSession(pmsgsess);
    }
    else
    {
        /* SUCCESS! */
        *ppims = pimsNew;
    }

    DebugTraceResult(HrNewIMS, hr);
    return hr;
}

/*
 *  HrInitIMSProps
 *
 *  Purpose:
 *      Sets the initial (and for read-only properties, the only)
 *      values for the base properties of the Message Store Object:
 *      takes as input parameters the values of those properties
 *      that are specific to this store and calculates (hard-coded)
 *      the values of those properties that are the same for all
 *      stores created by the Microsoft Sample Store Provider.
 *      Also sets attributes.
 *
 *  Arguments:
 *      pims        Internal IMsgStore object instance.
 *      szPswd      Logon Account Password.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the property arrays.
 */
HRESULT HrInitIMSProps(PIMS pims, LPTSTR szPswd)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL;
    LPSPropValue pval = NULL;
    LPSPropProblemArray pprba = NULL;
    LPSPropTagArray ptaga = NULL;
    LPSPropAttrArray patra = NULL;
    SCODE sc = S_OK;
    LPSPropProblem pProbl = NULL;

#define cInitIMSProps       10
#define grfInitIMSProps     ( PROPATTR_MANDATORY | PROPATTR_READABLE )

    AssertSz(pims, "Bad pims");
    NFAssertSz(pims->psup, "Bad support object");
    AssertSz(szPswd, "Bad szPswd");

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    /* Allocate the property arrays. */
    hr = HrAllocPropArrays(cInitIMSProps, &pval, &ptaga, &patra);
    if (hr != hrSuccess)
        goto exit;

    /* Initialize property value array and all property tags. */

    ptaga->cValues = patra->cValues = cInitIMSProps;

    pval[0].ulPropTag = ptaga->aulPropTag[0] = PR_STORE_ENTRYID;
    pval[0].Value.bin.cb = pims->eidStore.cb;
    pval[0].Value.bin.lpb = pims->eidStore.lpb;
    pval[1].ulPropTag = ptaga->aulPropTag[1] = PR_STORE_RECORD_KEY;
    pval[1].Value.bin.cb = sizeof(pims->uidResource);
    pval[1].Value.bin.lpb = (LPBYTE) &pims->uidResource;
    pval[2].ulPropTag = ptaga->aulPropTag[2] = PR_ENTRYID;
    pval[2].Value.bin.cb = pims->eidStore.cb;
    pval[2].Value.bin.lpb = pims->eidStore.lpb;
    pval[3].ulPropTag = ptaga->aulPropTag[3] = PR_RECORD_KEY;
    pval[3].Value.bin.cb = sizeof(pims->uidResource);
    pval[3].Value.bin.lpb = (LPBYTE) &pims->uidResource;
    pval[4].ulPropTag = ptaga->aulPropTag[4] = PR_DISPLAY_NAME;
    pval[4].Value.LPSZ = pims->szStorePath;
    pval[5].ulPropTag = ptaga->aulPropTag[5] = PR_SMS_PASSWORD;
    pval[5].Value.LPSZ = szPswd;

    /* We don't set support properties that are changed by READONLY */
    /* when the store is open. Those are or'ed in during GetProps. */
    /* See HrWrap_GetProps for details. */

    pval[6].ulPropTag = ptaga->aulPropTag[6] = PR_STORE_SUPPORT_MASK;
    pval[6].Value.ul = SMS_SUPPORTMASK;

    pval[7].ulPropTag = ptaga->aulPropTag[7] = PR_OBJECT_TYPE;
    pval[7].Value.l = MAPI_STORE;
    pval[8].ulPropTag = ptaga->aulPropTag[8] = PR_FILENAME_SEQUENCE_NUMBER;
    pval[8].Value.ul = 0x10000000;
    pval[9].ulPropTag = ptaga->aulPropTag[9] = PR_MDB_PROVIDER;
    pval[9].Value.bin.cb = sizeof(MAPIUID);
    pval[9].Value.bin.lpb = (LPBYTE) &uidProvider;

    /* Initialize the property attribute array. */

    patra->aPropAttr[0] = grfInitIMSProps;
    patra->aPropAttr[1] = grfInitIMSProps;
    patra->aPropAttr[2] = grfInitIMSProps;
    patra->aPropAttr[3] = grfInitIMSProps;
    patra->aPropAttr[4] = grfInitIMSProps | PROPATTR_WRITEABLE;
    patra->aPropAttr[5] = grfInitIMSProps;
    patra->aPropAttr[6] = grfInitIMSProps;
    patra->aPropAttr[7] = grfInitIMSProps;
    patra->aPropAttr[8] = grfInitIMSProps;
    patra->aPropAttr[9] = grfInitIMSProps;

    /* Set the property values. */

    hr = lpmsg->lpVtbl->SetProps(lpmsg, cInitIMSProps, pval, &pprba);
    if (hr != hrSuccess) /* || pprba)*/
        goto exit;
    if(pprba)
    {
        for(pProbl = pprba->aProblem; pProbl < pprba->aProblem + pprba->cProblem; ++pProbl)
        {
            if(pProbl->ulPropTag != PR_STORE_SUPPORT_MASK)
                goto exit;
        }
        LMFree(&pims->lmr, pprba);
        pprba = NULL;
    }

    /* Set the property attributes. */

    hr = SetAttribIMsgOnIStg(lpmsg, ptaga, patra, &pprba);
    if (hr != hrSuccess) /* || pprba)*/
        goto exit;
    if(pprba)
    {
        for(pProbl = pprba->aProblem; pProbl < pprba->aProblem + pprba->cProblem; ++pProbl)
        {
            if(pProbl->ulPropTag != PR_STORE_SUPPORT_MASK)
                goto exit;
        }
        LMFree(&pims->lmr, pprba);
        pprba = NULL;
    }

    /* If we succeeded up to this point, commit the properties. */

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No Warnings expected");

    if (pprba)
    {
        LMFree(&pims->lmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    FreePropArrays(&pval, &ptaga, &patra);

    UlRelease(lpmsg);

    DebugTraceResult(InitIMSProps, hr);
    return hr;
}

/*
 *  GetResourceUID
 *
 *  Purpose:
 *      Returns the one UID that identifies this MAPI resource
 *      (message store).
 *
 *  Arguments:
 *      pims    Internal IMsgStore object instance.
 *      lpuid   Location in which to return the value of the
 *              Resource UID.
 *
 *  Returns:
 *      VOID
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
VOID GetResourceUID(PIMS pims, LPMAPIUID lpuid)
{
    AssertSz(pims, "Bad pims");
    AssertSz(lpuid, "Bad lpuid");

    *lpuid = pims->uidResource;

    return;
}

/*********************************************************
 * FIsInvalidEID
 *
 *  Purpose         check if the peid points to an invalid EID
 *                  Returns TRUE if it is invalid. This routine
 *                  considers EIDs of length 0 invalid.
 *
 *  Parameter
 *      cb          number of bytes believed to be in peid
 *      peid        pointer to the entryid
 *      pims        store in which the object should be. May be NULL,
 *                  in which case, no check of the uid is performed.
 */
BOOL FIsInvalidEID(ULONG cb, PEID peid, PIMS pims)
{
    BOOL fInvalid;

    fInvalid = (cb < CbNewEID(0)
        || cb > CbNewEID(MAX_PATH)
        || IsBadReadPtr(peid, (UINT) cb)
        || IsBadStringPtr(peid->szPath, (UINT) -1)
        || cb != CbEID(peid)
        || peid->bVersion != SMPMS_VERSION);

    /* If the eid still looks good, and we were given the message store */
    /* object, then do one final check of the uid in the eid versus the */
    /* uid of the store. */

    if (!fInvalid && pims)
    {
        MAPIUID uid;

        GetResourceUID(pims, &uid);
        fInvalid = !IsEqualMAPIUID(&uid, &peid->uidResource);
    }

    return fInvalid;
}

/*
 *  HrUniqueFileName
 *
 *  Purpose:
 *      Returns a unique file name base that can be used by other
 *      parts of the store when a file needs to be created.  Gets
 *      the PR_FILENAME_SEQUENCE_NUMBER property out of the message
 *      store object, uses its textized form as the unique name,
 *      increments it, and stores it back in the object.  Sequence
 *      numbers begin at 0x10000000 and increment so that every
 *      file name returned is the same 8-character length.  (See
 *      HrInitIMSProps.)
 *
 *  Arguments:
 *      pims            Message Store Object.
 *      lpulSeqNumber   pointer to sequence number of this file name
 *      lppstrNewName   Location in which to return a pointer to a
 *                      buffer containing the unique file name.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Increments the PR_FILENAME_SEQUENCE_NUMBER property of the
 *      message store object.
 *
 *  Errors:
 *      All SetProps and SaveChanges errors.  Also:
 *
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the return parameter.
 */
HRESULT HrUniqueFileName(PIMS pims, ULONG *lpulSeqNumber,
    LPTSTR *pszNewName)
{
    HRESULT hr = hrSuccess;
    LPTSTR szFileName = NULL;
    ULONG ulSeq = 0L;
    LPMESSAGE lpmsg = NULL;

    AssertSz(pims, "Bad pims");
    AssertSz(pszNewName, "Bad pszNewName");

    /* Allocate space for the return string */

    hr = HrAlloc((CCH_NAME - CCH_EXT) * sizeof(TCHAR), (PPV) &szFileName);
    if (hr != hrSuccess)
        goto exit;

    /* Get sequence number out of object, increment */
    /* sequence number, and turn it into a string.  */

    hr = HrGetSingleProp((LPMAPIPROP) pims, &pims->lmr,
        PR_FILENAME_SEQUENCE_NUMBER, &ulSeq);
    if (hr != hrSuccess)
        goto exit;

    ulSeq++;

    hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
        pims->psup, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetOneROProp(lpmsg, &pims->lmr, PR_FILENAME_SEQUENCE_NUMBER, &ulSeq);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    /* If this wsprintf statement changes, fix HrConvertSzToHex below. */

    wsprintf(szFileName, TEXT("%08lx"), ulSeq);

    *pszNewName = szFileName;
    *lpulSeqNumber = ulSeq;

exit:
    UlRelease(lpmsg);

    if (hr != hrSuccess)
        FreeNull(szFileName);

    DebugTraceResult(HrUniqueFileName, hr);
    return hr;
}

/*
 * HrConvertSzToHex
 *
 * Converts the string given into an equivalent number. The string must
 * contain characters in the range 0-9, A-F, or a-f. If the routine finds
 * characters outside these ranges in the string, it will terminate with
 * the error MAPI_E_CALL_FAILED. The string must be at least 8 characters
 * long (a 32-bit number is fully-specified by a 8 hex characters). The
 * routine will use only the first 8 characters in the string, even if the
 * string is longer than 8 characters. Note that only the first 8 characters
 * must be within the proper range. Characters after the first 8 will be
 * ignored.
 *
 * Parameters:
 *  szName: The string to convert.
 *  pulAnswer: A pointer to the location to return the converted answer.
 *
 * Errors:
 *  MAPI_E_CALL_FAILED -- when a character is out of range.
 */
static HRESULT HrConvertSzToHex(LPSTR szName, ULONG *pulAnswer)
{
    HRESULT hr = hrSuccess;
    CHAR *pch;
    CHAR *pchMax;
    ULONG ulAns = 0;
    ULONG ichConv = 0;

    /* String must be at least 8 hex chars long. It should have come from */
    /* the HrUniqueFileName function above, which uses wsprintf to generate */
    /* the string. */

    if (    IsBadStringPtr(szName, (UINT) -1)
        ||  lstrlen(szName) < 8)
    {
        TrapSz("Bad input to function");
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    pch = szName;
    pchMax = szName + lstrlen(szName);

    /* Only convert the first 8 characters, no matter how long the string is. */
    while(pch < pchMax && ichConv < 8)
    {
        ulAns <<= 4;

        if (*pch >= '0' && *pch <= '9')
            ulAns += (*pch - '0');
        else if (*pch >= 'a' && *pch <= 'f')
            ulAns += (*pch - 'a' + 0xA);
        else if (*pch >= 'A' && *pch <= 'F')
            ulAns += (*pch - 'A'+ 0xA);
        else
        {
            TraceSz2("SampleMS: HrConvertSztoHex: char %c(%#x) out of range.\n",
                *pch, *pch);
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            goto exit;
        }

        ++pch;
        ++ichConv;
    }

    *pulAnswer = ulAns;

exit:
    DebugTraceResult(HrConvertSzToHex, hr);
    return hr;
}

/*
 * HrGetSequenceNum
 *
 * This routine takes a message eid and gets the
 * numerical equivalent of the base file name in the entryid. So, if the
 * base name of the message was "00000005.msg", the function would return
 * 0x00000005.
 *
 * Parameters:
 *  pimsg: the message object to update.
 *  pulSequenceNum: a pointer to the location to place the generated number.
 *
 */
static HRESULT HrGetSequenceNum(PEID peid, ULONG *pulSequenceNum)
{
    HRESULT hr;
    LPSTR szBaseName;
    ULONG ulSeqNum;

    szBaseName = SzBaseName(peid);

    hr = HrConvertSzToHex(szBaseName, &ulSeqNum);
    if (hr != hrSuccess)
        goto exit;

    *pulSequenceNum = ulSeqNum;

exit:
    DebugTraceResult(HrGetSequenceNum, hr);
    return hr;
}

/*
 *  IMS_NeuterChildren, IMS_Neuter
 *
 *  Purpose:
 *      Free all memory and resources associated with a logon.  We
 *      cannot, however, remove the critical section:  that must be
 *      done by the caller of this routine.
 *
 *  Arguments:
 *      pims            Pointer to the MS object.
 *
 *  Returns:
 *      None.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
void IMS_NeuterChildren(POBJ pobj, int iLevel)
{
#ifdef DEBUG
    int i;

    DebugTrace("SMS:   ");
    for (i = 0; i < iLevel; ++i)
        DebugTrace("  ");

    switch (pobj->wType)
    {
    case OT_MSGSTORE:
        DebugTrace("IMsgStore (%s)", ((PIMS) pobj)->szStorePath);
        break;
    case OT_FOLDER:
        DebugTrace("IMAPIFolder");
        break;
    case OT_MESSAGE:
        DebugTrace("IMessage");
        break;
    case OT_ATTACH:
        DebugTrace("IAttach");
        break;
    case OT_STREAM:
        DebugTrace("IStream");
        break;
    }
    DebugTrace(" (@%08lX,cRef=%ld)\n", pobj, pobj->cRef);
#endif

    while (TRUE)
    {
        POBJ pobjChild;

        pobjChild = pobj->pobjHead;

        if (pobjChild == NULL)
            break;

        pobj->pobjHead = pobjChild->pobjNext;
        IMS_NeuterChildren(pobjChild, iLevel + 1);
    }

    if (pobj->wType != OT_MSGSTORE)
    {
        LPFNNEUTER lpfnNeuter;

        lpfnNeuter = rgfnNeuter[pobj->wType];

        if (lpfnNeuter != 0)
            lpfnNeuter(pobj);

        pobj->lpVtbl = 0;
        LMFree(&pobj->pims->lmr, pobj);
    }
}

void IMS_Neuter(PIMS pims)
{
    HRESULT hr;
    ULONG ulFlags;

    if (pims->ulOQConn)
    {
        pims->psup->lpVtbl->Unsubscribe(pims->psup, pims->ulOQConn);
        pims->ulOQConn = 0;
    }

    if (pims->ulTblConn)
    {
        pims->psup->lpVtbl->Unsubscribe(pims->psup, pims->ulTblConn);
        pims->ulTblConn = 0;
    }

    OBJ_SetFlag(pims, MSF_BEINGDESTROYED);

    ulFlags = pims->ulFlagsSLT;

    hr = pims->psup->lpVtbl->StoreLogoffTransports(pims->psup, &ulFlags);

#ifdef DEBUG
    /* The support object given us during config doesn't support */
    /* StoreLogoffTransports. It isn't an error to get no support */
    /* in that case. */
    if (hr != hrSuccess && GetScode(hr) != MAPI_E_NO_SUPPORT)
        TraceSz1("SMS: IMS_Neuter: StoreLogoffTransports(LOGOFF_ABORT) "
            "returned unexpected error %s", SzDecodeScode(GetScode(hr)));
#endif

    if (pims->cRef != 0 || pims->pobjHead != 0)
    {
        TraceSz("\n---------------------------------------------------------"
            "-------");
        TraceSz("SMS: The following objects were not released before Logoff:");
        IMS_NeuterChildren((POBJ) pims, 0);
        TraceSz("-----------------------------------------------------------"
            "-----\n");
    }

    /* Free MS object's resources */
    FreeNull(pims->szStorePath);
    FreeNull(pims->szProps);
    LMFree(&pims->lmr, pims->eidStore.lpb);
    CloseRFS(pims->prfs);

    if (pims->lptblOutgoing)
    {
        if (pims->cOutgoingViews)
        {
            TraceSz1("Sample MS: IMS_Neuter: Leaked outgoing queue table "
                "(# views left = %08lX)\n", pims->cOutgoingViews);
            pims->cOutgoingViews = 0;
        }

        UlRelease(pims->lptblOutgoing);
    }

    CloseIMsgSession(pims->pmsgsess);
    pims->pmsgsess = NULL;

    /* Make the logon object forget about us */
    if (pims->pmsl)
        pims->pmsl->pims = NULL;
}

/*
 *  HrOpenIMSPropsFileRetry
 *
 *  Purpose:
 *      Open the IMsgStore properties docfile as an IMessage
 *      instance to get/set properties.
 *      This retries up to NUM_RETRIES times on MAPI_E_NO_ACCESS
 *
 *  Arguments:
 *      szFile      The file to open.
 *      plmr        a pointer to the linked memory routines.
 *      psup        a pointer to the MAPI support object.
 *      fModify     TRUE means the caller wants read/write access.
 *                  FALSE means read-only access.
 *      lppmsg      Address of a location in which to return a
 *                  pointer to the newly opened IMessage instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      IMessage on IStorage opening errors.
 */
HRESULT HrOpenIMSPropsFileRetry(LPMSGSESS pmsgsess, LPTSTR szFile, PLMR plmr,
    LPMAPISUP psup, BOOL fModify, LPMESSAGE * lppmsg)
{
    UINT iRetry;                /* number of attempts to open */
    HRESULT hr;

    iRetry = 0;

    while (TRUE)
    {
        hr = HrOpenIMsg(pmsgsess, szFile, plmr, psup, FALSE, fModify, TRUE,
            lppmsg);

        if (GetScode(hr) != MAPI_E_NO_ACCESS || ++iRetry >= NUM_RETRIES)
            break;

        Sleep(500);
    }

    #ifdef DEBUG
    if (iRetry >= NUM_RETRIES)
        TraceSz("HrOpenIMSPropsFileRetry: Failing open. Too many tries.");
    #endif

    DebugTraceResult(HrOpenIMSPropsFileRetry, hr);
    return hr;
}

/*
 * HrGetSMSStandardNotifKey
 *
 * Purpose
 *
 * return the notification key for standard notifications (everything
 * except outgoing queue notifications).
 * memory should be freed with FreeNull
 *
 * In order to call the MAPI registration function, we need to turn
 * an EntryID into a "notification key" (something unique to an
 * object in the store that will remain constant throughout this
 * logon session).  In the case of the Sample Store, we can just
 * use the local file name of the object (folder or message).
 * If the user is interested in notifications about all objects in the
 * store, we use the store's uid for the key.
 *
 * Parameters
 * pims         pointer to the message store object
 * peid         the entryid of the object
 * lppKey       pointer to the location to return the key
 */
static HRESULT HrGetSMSStandardNotifKey(PIMS pims, PEID peid,
    LPNOTIFKEY * lppKey)
{
    HRESULT hr;
    LPNOTIFKEY lpKey;
    ULONG cb;           /* number of bytes in the key */

    if (peid)
    {
        LPMAPIUID lpuidEID = NULL;
        LPTSTR szPathEID = NULL;
        LPTSTR szFileEID = NULL;

        hr = HrDeconstructEID(peid, &lpuidEID, &szPathEID, &szFileEID);
        if (hr == hrSuccess)
        {
            cb = CbNewNOTIFKEY(Cbtszsize(szFileEID));
            hr = HrAlloc(cb, (PPV) &lpKey);
        }

        if (hr == hrSuccess)
        {
            lstrcpy((LPTSTR) &lpKey->ab, szFileEID);
            lpKey->cb = Cbtszsize(szFileEID);
        }

        FreeNull(lpuidEID);
        FreeNull(szPathEID);
        FreeNull(szFileEID);

        if (hr != hrSuccess)
            goto exit;
    }
    else
    {
        /* The caller is interested in notifications on all objects in the */
        /* store. Generate a key from our uid. */

        cb = CbNewNOTIFKEY(sizeof(MAPIUID));
        hr = HrAlloc(cb, (PPV) &lpKey);
        if (hr != hrSuccess)
            goto exit;

        GetResourceUID(pims, (MAPIUID *) &(lpKey->ab[0]));
        lpKey->cb = sizeof(MAPIUID);
    }

exit:
    if (hr != hrSuccess)
        FreeNull(lpKey);
    else
        *lppKey = lpKey;

    DebugTraceResult(HrGetSMSStandardNotifKey, hr);
    return hr;
}


MSPMSG.C

/*
 *  M S P M S G . C
 *
 *  Code for the MAPI Sample Store Provider implementation of the
 *  IMessage object.  The implementation is, in fact, a thin
 *  wrapping layer around the implementation of IMessage on
 *  IStorage.  We wrap the IMessage object returned by IMsgOnIStg
 *  so that we can handle those methods (like SubmitMessage) not
 *  understood by a standalone message (e.g. one embedded in a word
 *  document) but which makes sense for a message in the context of
 *  a message store.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

#define MSG_ValidateParameters(pobj, intf, method, arglist)     \
        OBJ_ValidateParameters(pobj, intf, method, sizeof(IMSG), &vtblIMSG, arglist)

/* Manifest constants */

/* Number of properties to initialize a normal message with */
#define cpropIMSGInit       11
/* Number of properties to initialize a message-in-message with */
#define cpropMsgInMsgInit   3
/* Number of in-memory properties associated with a message object */
#define cpropIMSGInternal   4
/* The property attributes to set on the initial set of message properties */
#define grfpropattrIMSGInit (PROPATTR_MANDATORY | PROPATTR_READABLE)

#define IMSG_EnterCriticalSection(pimsg)    OBJ_EnterCriticalSection((POBJ)pimsg)
#define IMSG_LeaveCriticalSection(pimsg)    OBJ_LeaveCriticalSection((POBJ)pimsg)

typedef enum _mrflavor
{
    ENUM_ADD = 1,
    ENUM_MODIFY,
    ENUM_REMOVE
} MRFLAVOR;

/* internal functions */
static HRESULT HrSaveMsgInMsg(PIMSG pimsg, ULONG ulFlags);
static SCODE ScFillOneSBPval(PLMR plmr, LPVOID pvOrigBuf, ULONG ulPropTag,
    ULONG cb, LPBYTE lpbData, LPSPropValue pval);

/* Global variables */

/* Dispatch table for IMessage objects */
IMSG_Vtbl vtblIMSG =
{
    (IMSG_QueryInterface_METHOD *)  OBJ_QueryInterface,
    (IMSG_AddRef_METHOD *)          OBJ_AddRef,
    (IMSG_Release_METHOD *)         OBJ_Release,
    (IMSG_GetLastError_METHOD *)    IMS_GetLastError,
    IMSG_SaveChanges,
    IMSG_GetProps,
    IMSG_GetPropList,
    IMSG_OpenProperty,
    IMSG_SetProps,
    IMSG_DeleteProps,
    IMSG_CopyTo,
    IMSG_CopyProps,
    (IMSG_GetNamesFromIDs_METHOD *) IMS_GetNamesFromIDs,
    (IMSG_GetIDsFromNames_METHOD *) IMS_GetIDsFromNames,
    IMSG_GetAttachmentTable,
    IMSG_OpenAttach,
    IMSG_CreateAttach,
    IMSG_DeleteAttach,
    IMSG_GetRecipientTable,
    IMSG_ModifyRecipients,
    IMSG_SubmitMessage,
    IMSG_SetReadFlag
};

/*
 *  OBJECT METHODS
 */

/*
 *  IMSG_SaveChanges
 *
 *  Purpose:
 *      Saves changes made to a message object and all of its
 *      sub-objects (attachments, et al.).
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     Flags.  The following are defined:
 *                  KEEP_OPEN_READONLY  Do not invalidate the
 *                                      object, make it read-only.
 *                  KEEP_OPEN_READWRITE Don't invalidate the
 *                                      object, keep it open
 *                                      read/write.
 *                  FORCE_SAVE          Overwrite any changes made by
 *                                      others since message was openned.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 */
STDMETHODIMP IMSG_SaveChanges(PIMSG pimsg, ULONG ulFlags)
{
    HRESULT hr = hrSuccess;
    PIFLD pifldParent = NULL;   /* parent as an open folder */
    ULONG ulPropMsgFlags;       /* flags for save on property message */
    ULONG ulChangeType = TABLE_ROW_MODIFIED;
    BOOL fUnread = FALSE;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            SaveChanges,
            (pimsg,
            ulFlags));

    IMSG_EnterCriticalSection(pimsg);

    /* Handle msg-in-msg separately. */
    if (OBJ_TestFlag(pimsg, MSGF_MSGINMSG))
    {
        hr = HrSaveMsgInMsg(pimsg, ulFlags);
        goto exit;
    }

    /* open up lpmsg's parent so we can update tables and contents counts */
    hr = HrOpenParent(pimsg->pims, pimsg->peid, MAPI_MODIFY, &pifldParent);
    if (hr != hrSuccess)
        goto exit;

    /* mark the new message as complete by updating its ENTRYID property */
    if (OBJ_TestFlag(pimsg, MSGF_NEWLYCREATED))
    {
        ULONG ulMF;
        PEID peidInt = NULL;

        ReplaceExt(pimsg->peid->szPath, MESSAGE_EXT);

        /* Assume that PR_ENTRYID is at position 0 in the in-memory */
        /* array. That's where it went in HrSetInternalProps. */

        AssertSz(pimsg->pval->ulPropTag == PR_ENTRYID,
            "The location of PR_ENTRYID in the in-memory array has changed");

        peidInt = (PEID) pimsg->pval->Value.bin.lpb;

        AssertSz(!FIsInvalidEID(pimsg->pval->Value.bin.cb, peidInt, NULL),
            "Invalid internal Entryid");

        if (peidInt)
            ReplaceExt(peidInt->szPath, MESSAGE_EXT);

        /* Now, we need to update PR_INSTANCE_KEY in the same way. Since */
        /* the message is just becoming permanent, it won't be in any */
        /* tables yet, so it can change without affecting any tables. */

        /* NOTE: this code assumes knowledge of the format of */
        /* PR_INSTANCE_KEY. */

        AssertSz(pimsg->pval[1].ulPropTag == PR_INSTANCE_KEY, "The location "
            "of PR_INSTANCE_KEY in the in-memory array has changed");

        peidInt = (PEID) pimsg->pval[1].Value.bin.lpb;

        AssertSz(!FIsInvalidEID(pimsg->pval[1].Value.bin.cb, peidInt, NULL),
            "Invalid internal Entryid");

        if (peidInt)
            ReplaceExt(peidInt->szPath, MESSAGE_EXT);

        hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pimsg->pims->lmr,
            PR_MESSAGE_FLAGS, &ulMF);
        if (hr != hrSuccess)
            goto exit;

        /* remember the message's unread status, and to update open tables */

        fUnread = !(ulMF & MSGFLAG_READ);
        ulChangeType = TABLE_ROW_ADDED;
    }

    /* When the spooler saves a message, from the client's perspective, it */
    /* hasn't been modified, because it has just arrived. */

    if (!OBJ_TestFlag(pimsg, MSGF_NEWLYCREATED)
        && !OBJ_TestFlag(pimsg->pims, MSF_SPOOLER))
    {
        /* unset the UNMODIFIED bit */
        hr = HrSetFlags(pimsg, UNSET, PR_MESSAGE_FLAGS, MSGFLAG_UNMODIFIED);
        if (hr != hrSuccess)
            goto exit;
    }

    /* save the changes but keep the property file open */
    ulPropMsgFlags = ulFlags;

    if (!(ulFlags & KEEP_OPEN_READWRITE))
        ulPropMsgFlags |= KEEP_OPEN_READONLY;

    hr = pimsg->lpmsg->lpVtbl->SaveChanges(pimsg->lpmsg, ulPropMsgFlags);
    if (hr != hrSuccess)
        goto exit;

    /* update the parent's contents table and parent folder properties */
    if (ulChangeType == TABLE_ROW_ADDED)
    {
        (void) HrIncrementOneROProp(pifldParent, 1, PR_CONTENT_COUNT);

        if (fUnread)
            (void) HrIncrementOneROProp(pifldParent, 1, PR_CONTENT_UNREAD);
    }

    ChangeTable(pimsg->pims, pifldParent->peid, pimsg->peid, MAPI_MESSAGE,
        ulChangeType, TRUE);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(pifldParent);

    if (hr == hrSuccess)
    {
        /* Unless the user requests to continue with modify access, switch */
        /* down to read-only access. This means that specifying neither of */
        /* the KEEP_OPEN flags means the same thing as KEEP_OPEN_READONLY. */
        if (!(ulFlags & KEEP_OPEN_READWRITE))
            OBJ_ClearFlag(pimsg, OBJF_MODIFY);

        OBJ_ClearFlag(pimsg, MSGF_NEWLYCREATED);
    }

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_SaveChanges, hr);
    return HrCheckHr(hr, IMAPIProp_SaveChanges);
}

/*
 *  IMSG_GetProps
 *
 *  Purpose:
 *      Returns to the caller the value(s) of one or more
 *      properties existent on an IMSG object.  The order of the
 *      properties in the returned ppval structure exactly
 *      matches the order in which the properties were requested in
 *      ptaga.  The caller must free the returned
 *      structure by calling MAPIFreeBuffer(*ppval), but
 *      only if the function returns zero or the error
 *      MAPI_W_ERRORS_RETURNED.  Uses the IMessage on IStorage
 *      property interface implementation.
 *
 *  Arguments:
 *      pimsg           Pointer to the object.
 *      ptaga   Pointer to a counted array of property tags
 *                      ("names") that identify the values to be
 *                      returned.
 *      ulFlags         UNICODE / String8
 *      pcval       Location in which to return the count of
 *                      elements in *ppval.
 *      ppval   Location in which to return an allocated
 *                      array of property values (the caller frees
 *                      by calling MAPIFreeBuffer).
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      If the call succeeded overall but access to one or more
 *      properties failed, the function returns the warning
 *      MAPI_W_ERRORS_RETURNED.  The calling application should
 *      then check the Property Tag of each of the returned
 *      properties to determine which ones failed.  Those that fail
 *      have their Property Type set to PT_ERROR and their value (a
 *      ULONG) indicates which error occurred.
 *
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_W_ERRORS_RETURNED      See above.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMSG_GetProps(PIMSG pimsg, LPSPropTagArray ptaga, ULONG ulFlags,
    ULONG *pcval, LPSPropValue *ppval)
{
    HRESULT hr = hrSuccess;
    BOOL fLocked = FALSE;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            GetProps,
            (pimsg,
            ptaga,
            ulFlags,
            pcval,
            ppval));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->GetProps(pimsg->lpmsg, ptaga, ulFlags,
        pcval, ppval);

    {if(HR_SUCCEEDED(hr))
    {
        LPSPropValue pvalStoreSupMask = PpropFindProp(*ppval, *pcval,
                    PROP_TAG(PT_UNSPECIFIED, PROP_ID(PR_STORE_SUPPORT_MASK)));
        if(pvalStoreSupMask)
        {
            pvalStoreSupMask->ulPropTag = PR_STORE_SUPPORT_MASK;
            pvalStoreSupMask->Value.l = SMS_SUPPORTMASK;

            /* fix up hr */
            if(ptaga->cValues == 1)
                hr = hrSuccess;
        }
    }
    }
    /* If not message-in-message then wrap values. */
    /* Note that this wrapping function takes as an */
    /* argument the HRESULT from the previous GetProps call. */
    /* We aren't ignoring the error. */

    if (!OBJ_TestFlag(pimsg, MSGF_MSGINMSG))
        hr = HrWrap_GetProps(hr, pimsg->pims, pimsg->cval, pimsg->pval,
            pcval, ppval, FALSE, (ptaga != NULL), (POBJ)pimsg);

    IMSG_LeaveCriticalSection(pimsg);

    #ifdef DEBUG
    if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
        DebugTraceResult(IMSG_GetProps, hr);
    #endif

    return HrCheckHr(hr, IMAPIProp_GetProps);
}

/*
 *  IMSG_GetPropList
 *
 *  Purpose:
 *      Returns a list of all the properties currently accessible.
 *      Uses the IMessage on IStorage property implementation.
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     UNICODE / String8
 *      pptaga      Location in which to return a pointer
 *                  to a counted array of property tags.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMSG_GetPropList(PIMSG pimsg, ULONG ulFlags, LPSPropTagArray *pptaga)
{
    HRESULT hr = hrSuccess;
    LPSPropTagArray ptaga = NULL;
    LPSPropTagArray ptagaRet = NULL;
    UINT ind;
    SizedSPropTagArray(2, sptaToAdd) =
        { 2,
            {   PR_MESSAGE_RECIPIENTS,
                PR_MESSAGE_ATTACHMENTS
            }
        };

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            GetPropList,
            (pimsg,
            ulFlags,
            pptaga));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->GetPropList(pimsg->lpmsg, ulFlags, &ptaga);

    if (hr == hrSuccess && FIsUnsavedMsg(pimsg))
    {
        /* Remove PR_ENTRYID from the array. Since the message is unsaved, */
        /* don't return the entryid. Overwrite the PR_ENTRYID entry with */
        /* the last prop tag in the array. */
        /* //$ Should PR_INSTANCE_KEY be removed from the array too? */

        ULONG *pulPT;
        ULONG *pulPTMac;

        pulPT = ptaga->aulPropTag;
        pulPTMac = pulPT + ptaga->cValues;

        while (pulPT < pulPTMac)
        {
            if (*pulPT == PR_ENTRYID)
            {
                ptaga->cValues--;
                pulPTMac--;

                if (pulPT < pulPTMac)
                    memcpy(pulPT, pulPTMac, sizeof(ULONG));
                break;
            }

            ++pulPT;
        }
    }

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if(hr == hrSuccess)
    {
        if(!(hr = ResultFromScode(MAPIAllocateBuffer(
                        CbNewSPropTagArray(ptaga->cValues + sptaToAdd.cValues),
                         (LPVOID *)&ptagaRet))))
        {
            CopyMemory(ptagaRet, ptaga, CbNewSPropTagArray(ptaga->cValues));

            for(ind = 0; ind < sptaToAdd.cValues; ++ind)
            {
                if(!FContainsProp(sptaToAdd.aulPropTag[ind], ptaga))
                {
                    ptagaRet->aulPropTag[ptagaRet->cValues++] =
                        sptaToAdd.aulPropTag[ind];
                }
            }

        }
    }

    MAPIFreeBuffer(ptaga);

    if (hr == hrSuccess)
        *pptaga = ptagaRet;

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_GetPropList, hr);
    return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/*
 *  IMSG_OpenProperty
 *
 *  Purpose:
 *      Open a requested interface on a property for further
 *      access.  Commonly used for stream access to a large binary
 *      or text property.  This is the only way to access a
 *      property of type PT_OBJECT, and may be used on other
 *      properties depending on the implementation.  Uses the
 *      IMessage on IStorage property implementation.
 *
 *  Arguments:
 *      pimsg           Pointer to the object.
 *      ulPropTag   Property tag for the desired property.  Only
 *                      the ID bits of the tag are used; the type bits
 *                      are ignored.
 *      lpiid           Pointer to the GUID identifying which interface
 *                      is desired.
 *      ulInterfaceOptions  specifies interface-specific behavior
 *      ulFlags     MAPI_CREATE, MAPI_MODIFY, MAPI_DEFERRED_ERRORS
 *      lppUnk      Location in which to return a pointer to the
 *                      newly created interface pointer.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_INTERFACE_NOT_SUPPORTED      An error occurred opening a
 *                                          supported interface.
 */
STDMETHODIMP IMSG_OpenProperty(PIMSG pimsg, ULONG ulPropTag, LPCIID lpiid,
    ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN *lppUnk)
{
    HRESULT hr = hrSuccess;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            OpenProperty,
            (pimsg,
            ulPropTag,
            lpiid,
            ulInterfaceOptions,
            ulFlags,
            lppUnk));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->OpenProperty(pimsg->lpmsg, ulPropTag, lpiid,
        ulInterfaceOptions, ulFlags, lppUnk);

    if (hr == hrSuccess
        && (ulFlags & (MAPI_MODIFY | MAPI_CREATE)))
        OBJ_ClearFlag(pimsg, MSGF_FRESH);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_OpenProperty, hr);
    return HrCheckHr(hr, IMAPIProp_OpenProperty);
}

/*
 *  IMSG_SetProps
 *
 *  Purpose:
 *      Sets the value of one or more properties.  This call passes
 *      a number of Property Value structures.  The Property Tag in
 *      each indicates which property is having its values set and
 *      the value indicates what should be stored.  The caller must
 *      free the returned property problem structure by calling
 *      MAPIFreeBuffer(*lppProblems), but only if the call
 *      succeeded overall.  Uses the IMessage on IStorage property
 *      implementation.
 *
 *  Arguments:
 *      pimsg           Pointer to the object.
 *      cValues         Number of values in lpPropArray.
 *      lpPropArray     Pointer to a Property Value array.
 *      lppProblems     Location in which to return a pointer to a
 *                      counted array of property problem
 *                      structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, a zero is returned.
 *      If there are problems with setting some or all of the
 *      selected values, and a non-NULL is passed for lppProblems,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in lppProblems is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *lppProblems are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED      A general problem affecting
 *                                  access to all of the object's
 *                                  properties occurred.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMSG_SetProps(PIMSG pimsg, ULONG cValues, LPSPropValue lpPropArray,
    LPSPropProblemArray *lppProblems)
{
    HRESULT hr;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            SetProps,
            (pimsg,
            cValues,
            lpPropArray,
            lppProblems));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->SetProps(pimsg->lpmsg, cValues, lpPropArray,
        lppProblems);

    if (hr == hrSuccess)
        OBJ_ClearFlag(pimsg, MSGF_FRESH);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_SetProps, hr);
    return HrCheckHr(hr, IMAPIProp_SetProps);
}

/*
 *  IMSG_DeleteProps
 *
 *  Purpose:
 *      Deletes the list of properties given in ptaga.
 *      The caller must free the returned property problem
 *      structure by calling MAPIFreeBuffer(*ppErr), but only
 *      if the call succeeded overall.  Uses the IMessage on
 *      IStorage property implementation.
 *
 *  Arguments:
 *      pimsg           Pointer to the object.
 *      ptaga   Pointer to an array of Property Tags
 *                      identifying the properties to delete.
 *      ppErr       Location in which to return a pointer to a
 *                      counted array of property problem
 *                      structures.
 *
 *  Returns:
 *      HRESULT.  If the call succeeds overall, zero is returned.
 *      If there are problems with deleting some or all of the
 *      selected values, and a non-NULL is passed for ppErr,
 *      then a SPropProblemArray structure is returned with details
 *      about each problem.  The value returned in ppErr is
 *      only valid if zero is returned in the HRESULT.  If an error
 *      occurs on the call such that a non-zero value is returned
 *      for the HRESULT then the contents of *ppErr are
 *      undefined.  In particular, do not use or free the structure
 *      if an error occurs on the call.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NO_ACCESS    The caller does not have access
 *                                  to the requested properties.
 *      MAPI_E_CALL_FAILED      A general problem affecting
 *                                  access to all of the object's
 *                                  properties occurred.
 *      MAPI_E_CALL_FAILED          The mechanism for making the
 *                                  call to the service provider
 *                                  failed.
 */
STDMETHODIMP IMSG_DeleteProps(PIMSG pimsg, LPSPropTagArray ptaga,
    LPSPropProblemArray *ppErr)
{
    HRESULT hr;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            DeleteProps,
            (pimsg,
            ptaga,
            ppErr));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->DeleteProps(pimsg->lpmsg, ptaga, ppErr);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_DeleteProps, hr);
    return HrCheckHr(hr, IMAPIProp_DeleteProps);
}

/*
 *  IMSG_CopyTo
 *
 *  Purpose:
 *      Copies the contents of the current object to a destination
 *      object.  The entire contents, including contained objects,
 *      are copied, or optionally the caller can provide a list of
 *      properties that are not to be copied.  Previous information
 *      in the destination object which is not overwritten by
 *      copied data is neither deleted nor modified.
 *
 *  Arguments:
 *      pimsg           Pointer to the source object.
 *      ciidExcl        Count of the excluded interfaces in
 *                      rgiidExcl.
 *      rgiidExcl   Array of interface IDs specifying
 *                      interfaces not to be attempted in trying to
 *                      copy supplemental information to the
 *                      destination object.
 *      ptagaExcl   Counted array of property tags of
 *                      properties that are not to be copied to the
 *                      destination object.  NULL indicates all
 *                      properties are to be copied.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst     Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *                      MAPI_STD_DIALOG Use MAPI standard dialog
 *                                      instead of
 *                                      provider-specific dialog.
 *      ppErr       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 */
STDMETHODIMP IMSG_CopyTo(PIMSG pimsg, ULONG ciidExcl, LPCIID rgiidExcl,
    LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
    LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray *ppErr)
{
    HRESULT hr;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            CopyTo,
            (pimsg,
            ciidExcl,
            rgiidExcl,
            ptagaExcl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            ppErr));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->CopyTo(pimsg->lpmsg, ciidExcl, rgiidExcl,
        ptagaExcl, ulUIParam, lpProgress, piidDst, lpDestObj,
        ulFlags, ppErr);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_CopyTo, hr);
    return HrCheckHr(hr, IMAPIProp_CopyTo);
}

/*
 *  IMSG_CopyProps
 *
 *  Purpose:
 *      Copies the specified properties of the current object to a destination
 *      object.  Previous information
 *      in the destination object which is not overwritten by
 *      copied data is neither deleted nor modified.
 *
 *  Arguments:
 *      pimsg           Pointer to the source object.
 *      ptagaIncl       Counted array of property tags of
 *                      properties that are not to be copied to the
 *                      destination object.  NULL indicates all
 *                      properties are to be copied.
 *      ulUIParam       Handle of parent window cast to ULONG.
 *      lpProgress      Callback for doing progress UI.
 *      piidDst         Interface ID of the interface of lpDestObj,
 *                      the destination object.
 *      lpDestObj       Pointer to the open destination object.
 *      ulFlags         Flags.  Defined as follows:
 *                      MAPI_MOVE       Indicates a move operation.
 *                                      The default is to copy.
 *                      MAPI_NOREPLACE  Indicates that existing
 *                                      properties should not be
 *                                      overridden.  The default is
 *                                      to overwrite existing
 *                                      properties.
 *                      MAPI_DIALOG     Display a progress dialog
 *                                      as the operation proceeds.
 *                      MAPI_DECLINE_OK
 *      ppErr       Pointer to a variable that is filled in
 *                      with a pointer to a set of property
 *                      problems.  If NULL, no problem set is
 *                      returned on an error.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 */
STDMETHODIMP IMSG_CopyProps(PIMSG pimsg,
    LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
    LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
    LPSPropProblemArray *ppErr)
{
    HRESULT hr;

    MSG_ValidateParameters(
            pimsg,
            IMAPIProp,
            CopyProps,
            (pimsg,
            ptagaIncl,
            ulUIParam,
            lpProgress,
            piidDst,
            lpDestObj,
            ulFlags,
            ppErr));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->CopyProps(pimsg->lpmsg,
        ptagaIncl, ulUIParam, lpProgress, piidDst, lpDestObj,
        ulFlags, ppErr);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_CopyProps, hr);
    return HrCheckHr(hr, IMAPIProp_CopyProps);
}

/*
 *  IMSG_GetAttachmentTable
 *
 *  Purpose:
 *      Returns, in table form, the list of attachments contained
 *      in this message (one row per attachment).  The table has at
 *      least the PR_ATTACH_NUM and PR_RENDERING_POSITION columns.
 *      Additional columns may be in the table depending on the
 *      implementation.  This table may change while it is open if
 *      the application calls CreateAttach() or DeleteAttach(), or

*      if an attachment is modified in a way that some properties
 *      in the table get changed.
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     Flags.  Reserved for future use.  Ignored.
 *      lppTable    Pointer to a variable in which the address of
 *                  the returned table object is placed.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Unable to allocate memory for
 *                                  the returned table object or
 *                                  its underlying data.
 */
STDMETHODIMP
IMSG_GetAttachmentTable(PIMSG pimsg, ULONG ulFlags, LPMAPITABLE *lppTable)
{
    HRESULT hr;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            GetAttachmentTable,
            (pimsg,
            ulFlags,
            lppTable));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->GetAttachmentTable(pimsg->lpmsg, ulFlags,
        lppTable);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_GetAttachmentTable, hr);
    return HrCheckHr(hr, IMessage_GetAttachmentTable);
}

/*
 *  IMSG_OpenAttach
 *
 *  Purpose:
 *      Opens an existing attachment and returns a pointer which
 *      provides further access to the open attachment.  We get a
 *      pointer to the attachment object from the IMessage on
 *      IStorage implementation, and then wrap it with our own
 *      attachment object.
 *
 *  Arguments:
 *      pimsg           Pointer to the object.
 *      ulAttachmentNum Number of the attachment to be opened (the
 *                      value of this parameter comes from the
 *                      attachment table.
 *      piidDst     IID of interface requested for the
 *                      newly-opened object.  NULL or IID_IMAPIProp
 *                      means to open the object using the standard
 *                      MAPI 1.0 interface for the object.
 *                      IID_IUnknown means to open it using
 *                      the easiest interface you can open.
 *      ulFlags         Flags.  The following are defined:
 *                      MAPI_MODIFY     Write access is desired.
 *                                      The message must also be
 *                                      open for write access.
 *      lppAttach       Pointer to a variable which is to receive
 *                      the pointer to the open attachment object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      IStorage errors, plus,
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the new attachment instance.
 */
STDMETHODIMP IMSG_OpenAttach(PIMSG pimsg, ULONG ulAttachmentNum, LPCIID piidDst,
    ULONG ulFlags, LPATTACH *lppAttach)
{
    HRESULT hr = hrSuccess;
    LPATTACH lpattach = NULL;
    PIATCH piatch = NULL;
    BOOL fModify;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            OpenAttach,
            (pimsg,
            ulAttachmentNum,
            piidDst,
            ulFlags,
            lppAttach));

    IMSG_EnterCriticalSection(pimsg);

    /* Check for modification rights on the message. Switch to read-only */
    /* if the client asked for best access. */

    if (ulFlags & MAPI_BEST_ACCESS)
        fModify = !!OBJ_TestFlag(pimsg, OBJF_MODIFY);
    else
    {
        fModify = !!(ulFlags & MAPI_MODIFY);

        if (fModify && !OBJ_TestFlag(pimsg, OBJF_MODIFY))
        {
            hr = ResultFromScode(MAPI_E_NO_ACCESS);
            goto exit;
        }
    }

    hr = pimsg->lpmsg->lpVtbl->OpenAttach(pimsg->lpmsg, ulAttachmentNum,
        piidDst, ulFlags, &lpattach);
    if (hr != hrSuccess)
        goto exit;

    hr = HrNewIATCH(lpattach, pimsg, fModify, &piatch);

    UlRelease(lpattach);
    lpattach = NULL;

    if (hr != hrSuccess)
        goto exit;

    *lppAttach = (LPATTACH) piatch;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (hr != hrSuccess)
        UlRelease(piatch);

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_OpenAttach, hr);
    return HrCheckHr(hr, IMessage_OpenAttach);
}

/*
 *  IMSG_CreateAttach
 *
 *  Purpose:
 *      Creates a new attachment in a message and returns a pointer
 *      which provides further access to the open attachment.  We
 *      get a pointer to the attachment object from the IMessage on
 *      IStorage implementation, and then wrap it with our own
 *      attachment object.
 *
 *  Arguments:
 *      pimsg               Pointer to the object.
 *      piidDst         IID of interface requested for the
 *                          newly-opened object.  NULL or IID_IMAPIProp
 *                          means to open the object using the standard
 *                          MAPI 1.0 interface for the object.
 *                          IID_IUnknown means to open it using
 *                          the easiest interface you can open.
 *      ulFlags             Flags.  Reserved for future use.
 *                          Ignored.
 *      lpulAttachmentNum   Pointer to a variable which is to
 *                          receive the number of the newly created
 *                          attachment.  This number is valid only
 *                          within this message.
 *      lppAttach           Pointer to a variable which is to
 *                          receive the pointer to the open
 *                          attachment object.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      IStorage errors, plus,
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the new attachment instance.
 */
STDMETHODIMP IMSG_CreateAttach(PIMSG pimsg, LPCIID piidDst, ULONG ulFlags,
    ULONG *lpulAttachmentNum, LPATTACH *lppAttach)
{
    HRESULT hr = hrSuccess;
    LPATTACH lpattach = NULL;
    PIATCH piatch = NULL;
    ULONG ulAttachNum = 0L;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            CreateAttach,
            (pimsg,
            piidDst,
            ulFlags,
            lpulAttachmentNum,
            lppAttach));

    IMSG_EnterCriticalSection(pimsg);

    if (!OBJ_TestFlag(pimsg, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    hr = pimsg->lpmsg->lpVtbl->CreateAttach(pimsg->lpmsg, piidDst,
        ulFlags, &ulAttachNum, &lpattach);
    if (hr != hrSuccess)
        goto exit;

    /* Wrap the attachment object returned by IMessage. */

    hr = HrNewIATCH(lpattach, pimsg, TRUE, &piatch);

    UlRelease(lpattach);
    lpattach = NULL;

    if (hr != hrSuccess)
        goto exit;

    *lpulAttachmentNum = ulAttachNum;
    *lppAttach = (LPATTACH) piatch;

    OBJ_ClearFlag(pimsg, MSGF_FRESH);

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (hr != hrSuccess)
        UlRelease(piatch);

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_CreateAttach, hr);
    return HrCheckHr(hr, IMessage_CreateAttach);
}

/*
 *  IMSG_DeleteAttach
 *
 *  Purpose:
 *      Deletes an attachment in a message.  The current
 *      application should release all pointers to an attachment
 *      and its streams prior to deleting the attachment.  Deleted
 *      attachments are not permanently gone until changes to the
 *      message are saved.
 *
 *  Arguments:
 *      pimsg               Pointer to the object.
 *      ulAttachmentNum     Index of the attachment to be deleted.
 *      ulUIParam           Window handle cast to a ULONG.
 *      lpProgress          Callback for displaying progress UI.
 *      ulFlags             Flags.  Reserved for future use.
 *                          Ignored.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      Invalidates all pointers to the attachment, if it is
 *      currently open (this is done by the IMessage on IStorage
 *      implementation).  These invalidated sub-objects then only
 *      support the Release, AddRef, and QueryInterface methods.
 *
 *  Errors:
 *      IStorage errors (MAPI_E_WRITE_FAULT, etc.).
 */
STDMETHODIMP IMSG_DeleteAttach(PIMSG pimsg, ULONG ulAttachmentNum,
    ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
    HRESULT hr = hrSuccess;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            DeleteAttach,
            (pimsg,
            ulAttachmentNum,
            ulUIParam,
            lpProgress,
            ulFlags));

    IMSG_EnterCriticalSection(pimsg);

    if (!OBJ_TestFlag(pimsg, OBJF_MODIFY))
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
    else
        hr = pimsg->lpmsg->lpVtbl->DeleteAttach(pimsg->lpmsg, ulAttachmentNum,
            ulUIParam, lpProgress, ulFlags);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_DeleteAttach, hr);
    return HrCheckHr(hr, IMessage_DeleteAttach);
}

/*
 *  IMSG_GetRecipientTable
 *
 *  Purpose:
 *      Opens the recipient table in a message.  The recipient
 *      table for a received message or a message under composition
 *      contains one row for each recipient of the message.  The
 *      table will have at least the following columns:  PR_ROWID,
 *      PR_DISPLAY_NAME, PR_ENTRYID, PR_RECIPIENT_TYPE.  The
 *      additional properties PR_ADDRTYPE, PR_SENDER_NAME,
 *      PR_SENDER_ENTRYID, and PR_CLIENT_SUBMIT_TIME will appear in sent
 *      messages (messages not under composition).  Additional
 *      columns may be in the table, depending on the
 *      implementation.
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     Flags.  Reserved for future use.  Ignored.
 *      lppTable    Pointer to a variable in which the address of
 *                  the returned table object is placed.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Unable to allocate memory for
 *                                  the returned table object or
 *                                  its underlying data.
 */
STDMETHODIMP IMSG_GetRecipientTable(PIMSG pimsg, ULONG ulFlags,
    LPMAPITABLE *lppTable)
{
    HRESULT hr = hrSuccess;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            GetRecipientTable,
            (pimsg,
            ulFlags,
            lppTable));

    #ifdef VALIDATE
    if (ulFlags & MAPI_UNICODE)
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    #endif

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->GetRecipientTable(pimsg->lpmsg, ulFlags,
        lppTable);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_GetRecipientTable, hr);
    return HrCheckHr(hr, IMessage_GetRecipientTable);
}

/*
 *  IMSG_ModifyRecipients
 *
 *  Purpose:
 *      Adds, deletes, and/or modifies the recipients in a message.
 *      The property set for each recipient being added or modified
 *      must include a PR_ROWID, PR_DISPLAY_NAME, PR_ADDRTYPE (it
 *      may be empty), PR_ENTRYID (it may be empty), and
 *      PR_RECIPIENT_TYPE.  Additional properties may be specified,
 *      but depending on the implementation they may be ignored or
 *      discarded.
 *
 *      The recipient table may be used to represent both
 *      "resolved" and "unresolved" entries.  An unresolved entry
 *      is one that consists only of a display name.  Applications
 *      which allow users to type recipient names directly will
 *      create these entries.  A resolved entry contains more
 *      information relating the display name to a recipient:  an
 *      email address type and an EntryID.  Unresolved entries are
 *      stored as entries with zero as a value for PR_ENTRYID and
 *      PR_ADDRTYPE.  A message with unresolved entries in the
 *      recipient table will generate a non-delivery-report if
 *      submitted.
 *
 *  Parameters
 *      pimsg       pointer to message object
 *      ulFlags     flags:  MESSAGE_REPLACE
 *      lpMods      Pointer to list of recipient modifications, additions, or
 *                  deletions to be performed on pimsg
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      This method converts all short-term EntryIDs to long-term
 *      EntryIDs.
 *
 *  Errors:
 */
STDMETHODIMP IMSG_ModifyRecipients(PIMSG pimsg, ULONG ulFlags, LPADRLIST lpMods)
{
    HRESULT hr = hrSuccess;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            ModifyRecipients,
            (pimsg,
            ulFlags,
            lpMods));

    IMSG_EnterCriticalSection(pimsg);

    hr = pimsg->lpmsg->lpVtbl->ModifyRecipients(pimsg->lpmsg, ulFlags, lpMods);

    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_ModifyRecipients, hr);
    return HrCheckHr(hr, IMessage_ModifyRecipients);
}

/*
 *  IMSG_SubmitMessage
 *
 *  Purpose:
 *      Mark a message as ready for sending and saves all changes
 *      to it and all its attachments.  Since this is not
 *      implemented in IMessage on IStorage, we must do it
 *      ourselves.
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     Flags.  Defined as follows:
 *                  FORCE_SUBMIT    If set, MAPI should submit the
 *                                  message even if it might not be
 *                                  sent right away.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      If the submission is successful, the pointer to the message
 *      and all associated sub-objects (messages, attachments,
 *      streams, tables) are no longer valid, except for their
 *      Release() methods.  No other operations on these pointers
 *      are permitted.  MAPI expects the application to release the
 *      message object and all associated sub-objects.
 *
 *  Errors:
 *      MAPI_E_NON_STANDARD     Unexpected queueing time is
 *                              possible.  This error is only
 *                              returned if the FORCE_SUBMIT flag
 *                              is not set.
 */
STDMETHODIMP IMSG_SubmitMessage(PIMSG pimsg, ULONG ulFlags)
{
    HRESULT hr = hrSuccess;
    LPMAPITABLE pmtRecip = NULL;
    PIMS pims;
    ULONG ulcRow = 0L;
    ULONG cValues = 0L;
    LPSPropValue pval = NULL;
    ULONG ulMF;
    LPSPropProblemArray pprba = NULL;
    SYSTEMTIME st;
    FILETIME ft;
    ULONG ulPrepareFlags;
    ULONG ulPreprocess;
    LPSRowSet prws = NULL;

#define NUM_FLAGTIME    3
    const static SizedSPropTagArray(NUM_FLAGTIME, proptagFlagTime) =
    {
        NUM_FLAGTIME,
        {
            PR_MESSAGE_FLAGS,
            PR_CLIENT_SUBMIT_TIME,
            PR_SUBMIT_FLAGS
        }
    };
#define NUM_RECIP_COLS  2
    const static SizedSPropTagArray(NUM_RECIP_COLS, proptagRecips) =
    {
        NUM_RECIP_COLS,
        {
            PR_ROWID,           /* make sure this stays first */
            PR_RESPONSIBILITY
        }
    };

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            SubmitMessage,
            (pimsg,
            ulFlags));

    IMSG_EnterCriticalSection(pimsg);

    pims = pimsg->pims;

    if (OBJ_TestFlag(pimsg, MSGF_MSGINMSG))
    {
        hr = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto exit;
    }

    if (!OBJ_TestFlag(pimsg, OBJF_MODIFY))
    {
        hr = ResultFromScode(MAPI_E_NO_ACCESS);
        goto exit;
    }

    /* Get message flag to check for resubmit. */

    hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pims->lmr,
        PR_MESSAGE_FLAGS, &ulMF);
    if (hr != hrSuccess)
        goto exit;

    /* Check to see if the message was already submitted. If so, clear the */
    /* Preprocess bit in PR_SUBMIT_FLAGS, save the message, and update the */
    /* outgoing queue. */

    if (ulMF & MSGFLAG_SUBMIT)
    {
        ULONG ulSF;

        AssertSz(OBJ_TestFlag(pimsg->pims, MSF_SPOOLER),
            "Message being resubmitted by other than the spooler");

        hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pims->lmr,
            PR_SUBMIT_FLAGS, &ulSF);
        if (hr != hrSuccess)
        {
            if (GetScode(hr) == MAPI_E_NOT_FOUND)
            {
                TraceSz1 ("SAMPLE MS: IMSG_SubmitMessage: Error %s getting "
                    "PR_SUBMIT_FLAGS during resubmit.",
                    SzDecodeScode(GetScode(hr)));

                hr = hrSuccess;
                ulSF = 0;
            }
            else
                goto exit;
        }

        ulSF &= ~SUBMITFLAG_PREPROCESS;

        hr = HrSetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pims->lmr,
            PR_SUBMIT_FLAGS, &ulSF);
        if (hr != hrSuccess)
            goto exit;
    }
    else
    {
        /* Begin by making sure that all recipients have a PR_RESPONSIBILITY */
        /* property. If they don't, we need to put it in. */

        hr = pimsg->lpmsg->lpVtbl->GetRecipientTable(pimsg->lpmsg, 0L,
            &pmtRecip);
        if (hr != hrSuccess)
            goto exit;

        /* Get all columns and ensure that the PR_ROWID and PR_RESPONSIBILITY */
        /* columns are the first two table columns using the MAPI API function */
        /* HrAddColumns. */

        hr = HrAddColumns(pmtRecip, (LPSPropTagArray) &proptagRecips,
            pims->lmr.lpAllocBuf, pims->lmr.lpFreeBuf);
        if (hr != hrSuccess)
            goto exit;

        /* Check for PR_RESPONSIBILITY in each table row and set it if it's */
        /* missing. */

        while (TRUE)
        {
            LPSRow prw;
            LPSRow prwMac;
            LPSPropValue pvalT;

            /* Get 10 rows at a time. In general, GetRowCount may not */
            /* be supported by every provider. This loop does not count */
            /* on it working, even though I know that it would in this */
            /* implementation. */

            hr = pmtRecip->lpVtbl->QueryRows(pmtRecip, 10, 0L, &prws);
            if (hr != hrSuccess)
                goto exit;

            /* All table implementations will return zero rows from QueryRows */
            /* when you're actually at the end of the table. This routine */
            /* uses that to test when to exit this loop. Note that this loop */
            /* doesn't need to check for no recipients at all, because */
            /* ExpandRecips (below) will do that as part of its processing. */
            if (prws->cRows == 0)
                break;

            prw = prws->aRow;
            prwMac = prw + prws->cRows;

            /* Loop through the rows. For each row, put in PR_RESPONSIBILITY */
            /* if it's missing. Don't change recipients that already have */
            /* a PR_RESPONSIBILITY property (due to resubmission). */

            while (prw < prwMac)
            {
                AssertSz(prw->cValues >= NUM_RECIP_COLS,
                    "Bad # of values returned");

                pvalT = prw->lpProps;

                AssertSz(!IsBadReadPtr(pvalT, (UINT) prws->aRow->cValues
                    * sizeof(SPropValue)), "Bad pval array");

                /* PR_ROWID is in column zero. Leave it alone, and start */
                /* with the next column. */
                ++pvalT;

                /* We don't ever want responsibility for any recipient. */
                /* If this isn't the spooler calling, then force ALL */
                /* responsibilities to FALSE regardless of what they were. */
                if (PROP_TYPE(pvalT->ulPropTag) == PT_NULL
                    || !OBJ_TestFlag(pimsg->pims, MSF_SPOOLER))
                {
                    pvalT->ulPropTag = PR_RESPONSIBILITY;
                    pvalT->Value.b = FALSE;
                }

                ++prw;
            }

            /* modify the rows */
            hr = pimsg->lpmsg->lpVtbl->ModifyRecipients(pimsg->lpmsg,
                MODRECIP_MODIFY, (LPADRLIST) prws);
            if (hr != hrSuccess)
                goto exit;

            FreeProws(prws);
            prws = NULL;
        }

        /* We're done with the recip table now, so release it. */
        UlRelease(pmtRecip);
        pmtRecip = NULL;

        ulPrepareFlags = 0;

        hr = pims->psup->lpVtbl->PrepareSubmit(pims->psup, pimsg->lpmsg,
            &ulPrepareFlags);
        if (hr != hrSuccess)
            goto exit;

        /* ExpandRecips checks for no recip, and will return an error. */
        /* Therefore, this code doesn't need to check here for that case. */

        hr = pims->psup->lpVtbl->ExpandRecips(pims->psup, pimsg->lpmsg,
            &ulPreprocess);
        if (hr != hrSuccess)
            goto exit;

        /* Now, your store provider, if it wishes, may take responsibility */
        /* for any recipients that it wishes to handle. (For example, if */
        /* it is tightly coupled to a transport.) */

        /* Get the time to add to the message as PR_CLIENT_SUBMIT_TIME */
        GetSystemTime(&st);
        SideAssert(SystemTimeToFileTime(&st, &ft));

        /* get the old values of PR_MESSAGE_FLAGS & PR_CLIENT_SUBMIT_TIME */

        hr = pimsg->lpmsg->lpVtbl->GetProps(pimsg->lpmsg,
            (LPSPropTagArray) &proptagFlagTime, 0, /* ansi */
            &cValues, &pval);
        if (HR_FAILED(hr))
            goto exit;

        /* Warnings are OK from GetProps here. */

        /* turn on the MSGFLAG_SUBMIT bit of PR_MESSAGE_FLAGS */
        pval[0].ulPropTag = PR_MESSAGE_FLAGS;
        pval[0].Value.l = pval[0].Value.l | MSGFLAG_SUBMIT;

        /* set the client submission time */
        pval[1].ulPropTag = PR_CLIENT_SUBMIT_TIME;
        pval[1].Value.ft = ft;

        /* set the submit flag. If necessary, turn on the preprocess flag */
        pval[2].ulPropTag = PR_SUBMIT_FLAGS;

        if (ulPreprocess & NEEDS_PREPROCESSING)
            pval[2].Value.l = SUBMITFLAG_PREPROCESS;
        else
            pval[2].Value.l = 0L;

        hr = pimsg->lpmsg->lpVtbl->SetProps(pimsg->lpmsg, NUM_FLAGTIME, pval,
            &pprba);
        if (hr != hrSuccess || pprba)
            goto exit;
    }

    hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
    if (hr != hrSuccess)
        goto exit;

    /* add or update (for resubmit) the message in the outgoing queue */
    hr = HrUpdateOutgoingQueue(pims, pimsg, NULL, TABLE_ROW_ADDED);
    if (hr != hrSuccess)
        goto exit;

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (pprba)
    {
        LMFree(&pims->lmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    UlRelease(pmtRecip);
    LMFree(&pims->lmr, pval);
    FreeProws(prws);

    if (hr == hrSuccess)
        OBJ_ClearFlag(pimsg, MSGF_NEWLYCREATED | OBJF_MODIFY);

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_SubmitMessage, hr);
    return HrCheckHr(hr, IMessage_SubmitMessage);
}

/*
 *  IMSG_SetReadFlag
 *
 *  Purpose:
 *      Sets the MSGFLAG_READ bit in the PR_MESSAGE_FLAGS property.
 *      In addition, it sends a read report to the originator, if
 *      appropriate.  A read report is only sent if the originator
 *      of the message requested it.  Applications generally cannot
 *      determine if a read report has been requested.
 *
 *  Arguments:
 *      pimsg       Pointer to the object.
 *      ulFlags     Flags.  SUPPRESS_RECEIPT
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      In order to generate a read report, a new message is
 *      created which gets filled in by MAPI and then submitted.
 *      This message will be in the same folder as pimsg, and will
 *      have the same filename, except that it will have the read
 *      receipt filename extension (.rrt) instead of the normal
 *      message filename extension (.msg).
 *
 *  Errors:
 *      All errors associated with property modification, message
 *      creation, or message submission.
 */
STDMETHODIMP IMSG_SetReadFlag(PIMSG pimsg, ULONG ulFlags)
{
    HRESULT hr;
    LPTSTR szFull = NULL;
    PEID peidCopy = NULL;
    PIMSG pimsgRRT = NULL;
    PIFLD pifldParent = NULL;
    ULONG ulObjType = 0L;
    PEID peidCopyFld = NULL;
    ULONG ulSeqNumber;
    PLMR plmr;
    WORD fRR;
    ULONG ulMF;
    LONG lUnreadChange;

    MSG_ValidateParameters(
            pimsg,
            IMessage,
            SetReadFlag,
            (pimsg,
            ulFlags));

    IMSG_EnterCriticalSection(pimsg);

    plmr = &pimsg->pims->lmr;

    hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, plmr, PR_MESSAGE_FLAGS,
        &ulMF);
    if (hr != hrSuccess)
        goto exit;

    /* if the flag is already set correctly, don't do anything. */
    if (ulFlags & CLEAR_READ_FLAG)
    {
        if (!(ulMF & MSGFLAG_READ))
            goto exit;
        ulMF &= ~MSGFLAG_READ;

        lUnreadChange = 1;
    }
    else
    {
        if (ulMF & MSGFLAG_READ)
            goto exit;
        else
            ulMF |= MSGFLAG_READ;

        lUnreadChange = -1;
    }

    hr = HrSetSingleProp((LPMAPIPROP) pimsg->lpmsg, plmr, PR_MESSAGE_FLAGS, &ulMF);
    if (hr != hrSuccess)
        goto exit;

    hr = HrOpenParent(pimsg->pims, pimsg->peid, MAPI_MODIFY, &pifldParent);
    if (hr != hrSuccess)
        goto exit;

    /* see if read receipts are requested */
    hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, plmr,
        PR_READ_RECEIPT_REQUESTED, &fRR);

    if (hr == hrSuccess
        && !(ulFlags & SUPPRESS_RECEIPT)
        && fRR
        && lUnreadChange == -1)
    {
        PIFLD pifldRoot = NULL;
        ULONG ulObjType;

        NFSideAssertSz(pimsg->peid,
            "PR_READ_RECEIPT_REQUESTED set on a message in a message ");

        /* Open the root folder */
        hr = pimsg->pims->lpVtbl->OpenEntry(pimsg->pims, 0, NULL,
            NULL, 0L, &ulObjType, (LPUNKNOWN *) &pifldRoot);
        if (hr != hrSuccess)
            goto exit;

        Assert(ulObjType == MAPI_FOLDER);

        /* Create a read receipt message in the root folder and call */
        /* IMAPISupport::ReadReceipt */

        hr = HrNewEID(pifldRoot, pimsg->pims, READRECEIPT_EXT,
            &ulSeqNumber, &peidCopy);

        UlRelease(pifldRoot);

        if (hr != hrSuccess)
            goto exit;

        hr = HrNewIMSG(peidCopy, pimsg->pims, TRUE, TRUE, ulSeqNumber,
                &szFull, &pimsgRRT);
        if (hr != hrSuccess)
            goto exit;

        hr = InitIMSGProps(pimsgRRT);
        if (hr != hrSuccess)
            goto exit;

        hr = pimsg->pims->psup->lpVtbl->ReadReceipt(pimsg->pims->psup, 0L,
            (LPMESSAGE) pimsg, (LPMESSAGE *) &pimsgRRT);
        if (hr != hrSuccess)
            goto exit;

        /* If ReadReceipt didn't release and NULL the returned message, */
        /* then submit it. */

        if (pimsgRRT)
        {
            hr = pimsgRRT->lpVtbl->SubmitMessage(pimsgRRT, FORCE_SUBMIT);
            if (hr != hrSuccess)
                goto exit;
        }
    }

    hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
    if (HR_FAILED(hr))
        goto exit;

    /* Update the folder properties file -- WARNING:  if we fail from   */
    /* now until the end of the procedure, the folder unread count will */
    /* be inconsistent with the actual unread messages in this folder,  */
    /* and we have no transactional way of backing out changes.         */
    /* don't change folder properties for a message in a message        */

    if (!OBJ_TestFlag(pimsg, MSGF_MSGINMSG))
    {
        hr = HrIncrementOneROProp(pifldParent, lUnreadChange, PR_CONTENT_UNREAD);

        #ifdef DEBUG
        if (HR_FAILED(hr))
            TraceSz1("Sample MS: IMSG_SetReadFlag: error %s changing the "
                "unread count on a folder.", SzDecodeScode(GetScode(hr)));

#endif

        /* Ignore the error. It isn't fatal. */
        hr = hrSuccess;
    }

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    UlRelease(pimsgRRT);

    FreeNull(szFull);
    LMFree(plmr, peidCopy);
    LMFree(plmr, peidCopyFld);
    UlRelease(pifldParent);

    IMSG_LeaveCriticalSection(pimsg);

    DebugTraceResult(IMSG_SetReadFlag, hr);
    return HrCheckHr(hr, IMessage_SetReadFlag);
}

/*
 *  External functions (called from outside of this file).
 */

/*
 *  HrNewIMSG
 *
 *  Purpose:
 *      Allocates and initializes an IMSG object (internal
 *      implementation of IMessage).  Optionally creates storage
 *      for the object (else it tries to open existing storage).
 *
 *  Arguments:
 *      peid        Internal form of EntryID for message.
 *      pims        Message Store in which this message resides.
 *      fCreate     Boolean. TRUE means to create the storage for this message.
 *      fModify     Boolean. TRUE means to open the message for writing.
 *      ulSeqNum    The sequence number of the entryid.
 *      pszFull     Location in which to return the full pathname to the
 *                  file created or opened. If NULL, don't return the path.
 *      ppimsg      Location in which to return a pointer to the
 *                  newly created IMSG instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the IMSG instance.
 */
HRESULT HrNewIMSG(PEID peid, PIMS pims, BOOL fCreate, BOOL fModify,
    ULONG ulSeqNum, LPSTR *pszFull, PIMSG *ppimsg)
{
    HRESULT hr;
    LPTSTR szFull = NULL;

    LPMESSAGE lpmsg = NULL;
    PEID peidCopy = NULL;
    ULONG cbeid;
    PIMSG pimsgNew = NULL;
    BOOL fDoneCreate = FALSE;
    SCODE sc = S_OK;
    PEID peidParent = NULL;

    AssertSz(peid, "Bad peid");
    AssertSz(pims, "Bad pims");
    AssertSz(ppimsg, "Bad ppimsg");

    /* Get all the necessary parts of an IMSG */

    hr = HrFullPathName(pims->szStorePath, peid->szPath, NULL, &szFull);
    if (hr != hrSuccess)
        goto exit;

    ReplaceExt(szFull, MESSAGE_EXT);

    /* always open the internal message for modification so that */
    /* SetReadFlag will work. */

    hr = HrOpenIMsg(pims->pmsgsess, szFull, &pims->lmr, pims->psup, fCreate,
        TRUE, FALSE, &lpmsg);

    if (hr != hrSuccess)
        goto exit;

    if (fCreate)
        fDoneCreate = TRUE;

    cbeid = CbEID(peid);
    hr = HrAlloc(cbeid, (PPV) &peidCopy);
    if (hr != hrSuccess)
        goto exit;

    memcpy(peidCopy, peid, (UINT) cbeid);

    /* Allocate and initialize IMSG instance */

    sc = LMAllocZ(&pims->lmr, sizeof(IMSG), &pimsgNew);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    OBJ_Initialize(pimsgNew, &vtblIMSG, OT_MESSAGE, pims, pims->pcs);

    pimsgNew->peid = peidCopy;
    pimsgNew->lpmsg = lpmsg;

    if (fCreate)
    {
        OBJ_SetFlag(pimsgNew, MSGF_NEWLYCREATED);
        OBJ_SetFlag(pimsgNew, MSGF_FRESH);
    }
    else
    {
        ULONG ulMF;

        hr = HrGetSingleProp((LPMAPIPROP) lpmsg, &pims->lmr,
                PR_MESSAGE_FLAGS, &ulMF);
        if (hr != hrSuccess)
            goto exit;

        if (fModify
            && (ulMF & MSGFLAG_SUBMIT)
            && !OBJ_TestFlag(pims, MSF_SPOOLER))
        {
            hr = ResultFromScode(MAPI_E_SUBMITTED);
            goto exit;
        }
    }

    if (fModify)
        OBJ_SetFlag(pimsgNew, OBJF_MODIFY);

    hr = HrGetParentEID(&pims->lmr, peid, &peidParent);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetInternalProps(&pims->lmr, cpropIMSGInternal, &(pimsgNew->pval),
            &(pimsgNew->cval), peid, peidParent, ulSeqNum);
    if (hr != hrSuccess)
        goto exit;

    OBJ_Enqueue((POBJ) pimsgNew, (POBJ) pims);

exit:
    LMFree(&pims->lmr, peidParent);

    if (hr != hrSuccess)
    {
        UlRelease(lpmsg);
        FreeNull(peidCopy);
        if (fDoneCreate)
            DeleteFile(szFull);

        if (pimsgNew)
            LMFree(&pims->lmr, pimsgNew->pval);

        LMFree(&pims->lmr, pimsgNew);
        FreeNull(szFull);
    }
    else
    {
        *ppimsg = pimsgNew;

        if (pszFull)
            *pszFull = szFull;
        else
            FreeNull(szFull);
    }

    DebugTraceResult(HrNewIMSG, hr);
    return hr;
}

/*
 *  IMSG_Neuter
 *
 *  Purpose:
 *      Neuters an IMSG
 *
 *  Parameter
 *      pimsg       pointer to IMSG to be neutered
 */
void IMSG_Neuter(PIMSG pimsg)
{
    /* Free IMSG object's internal memory */

    UlRelease(pimsg->lpmsg);

    LMFree(&pimsg->pims->lmr, pimsg->pval);

    /* delete the file if it was never saved */
    if (OBJ_TestFlag(pimsg, MSGF_NEWLYCREATED))
    {
        LPTSTR szFull = NULL;   /* full path name of message */

        /* delete the file */
        if (HrFullPathName(pimsg->pims->szStorePath, pimsg->peid->szPath,
                NULL, &szFull) == hrSuccess)
        {
            ReplaceExt(szFull, MESSAGE_EXT);
            DeleteFile(szFull);
            FreeNull(szFull);
        }
    }

    FreeNull(pimsg->peid);
}

/*
 *  NewIMSGInIATCH
 *
 *  Purpose:
 *      Wraps an IMSG object around a message that is contained in
 *      an attachment (message in message).  A message in message
 *      has no EntryID, does not expose a PR_STORE_ENTRYID or
 *      PR_STORE_RECORD_KEY, and cannot be submitted.
 *
 *  Arguments:
 *      lpmsg       Message in message we get back from the
 *                  IMessage on IStorage implementation.
 *      pobj        Pointer to the parent object of the msg-in-msg.
 *      ulFlags     ulFlags from OpenProperty call
 *      ppimsg      Address in which to place a pointer to the
 *                  newly created IMSG instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the IMSG instance.
 */
HRESULT NewIMSGInIATCH(LPMESSAGE lpmsg, POBJ pobj, ULONG ulFlags, PIMSG *ppimsg)
{
    PIMSG pimsgNew = NULL;
    SCODE sc = S_OK;
    PIMS pims;

    AssertSz(lpmsg, "Bad lpmsg");
    AssertSz(pobj, "Bad pobj");
    AssertSz(ppimsg, "Bad ppimsg");

    pims = pobj->pims;

    /* Allocate and initialize IMSG instance */

    sc = LMAllocZ(&pims->lmr, sizeof(IMSG), (LPVOID *) &pimsgNew);
    if (sc != S_OK)
        goto exit;

    OBJ_Initialize(pimsgNew, &vtblIMSG, OT_MESSAGE, pims, pims->pcs);

    OBJ_SetFlag(pimsgNew, MSGF_MSGINMSG);

    if(ulFlags & MAPI_MODIFY)
        OBJ_SetFlag(pimsgNew, OBJF_MODIFY);

    pimsgNew->peid = NULL;
    pimsgNew->lpmsg = lpmsg;

    OBJ_Enqueue((POBJ) pimsgNew, pobj);

    *ppimsg = pimsgNew;

exit:
    if (sc != S_OK)
        LMFree(&pims->lmr, pimsgNew);

    DebugTraceSc(NewIMSGInIATCH, sc);
    return ResultFromScode(sc);
}

/*
 *  InitIMSGProps
 *
 *  Purpose:
 *      Sets the initial (and for read-only properties, the only)
 *      values for the base properties of the Message Object:
 *      takes as input parameters the values of those properties
 *      that are specific to this message and derives the values of
 *      those properties that are identical for all messages in the
 *      Microsoft Sample Store Provider.
 *
 *  Arguments:
 *      pimsg       Internal IMessage object instance.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      MAPI_E_NOT_ENOUGH_MEMORY    Could not allocate space for
 *                                  the property arrays.
 */
HRESULT InitIMSGProps(PIMSG pimsg)
{
    HRESULT hr = hrSuccess;
    LPSPropValue pval = NULL;
    LPSPropProblemArray pprba = NULL;
    LPSPropTagArray ptaga = NULL;
    LPSPropAttrArray patra = NULL;
    ULONG cpropInit = 0L;
    BOOL fMsgInMsg;
    MAPIUID uid;
    PIMS pims;
    LPMESSAGE lpmsg;

    AssertSz(pimsg, "Bad pimsg");

    if (OBJ_TestFlag(pimsg, MSGF_MSGINMSG))
    {
        AssertSz(pimsg->peid == NULL, "Msg in msg has no entryid");

        fMsgInMsg = TRUE;
        cpropInit = cpropMsgInMsgInit;
    }
    else
    {
        fMsgInMsg = FALSE;
        cpropInit = cpropIMSGInit;
    }

    pims = pimsg->pims;
    lpmsg = pimsg->lpmsg;

    /* Allocate the property arrays. */
    hr = HrAllocPropArrays(cpropInit, &pval, &ptaga, &patra);
    if (hr != hrSuccess)
        goto exit;

    /* Initialize property value array and all property tags. */

    ptaga->cValues = patra->cValues = cpropInit;

    pval[0].ulPropTag = ptaga->aulPropTag[0] = PR_OBJECT_TYPE;
    pval[0].Value.l = MAPI_MESSAGE;
    pval[1].ulPropTag = ptaga->aulPropTag[1] = PR_MESSAGE_FLAGS;

    /* When the spooler creates a message, it is arriving into the store. */
    /* Therefore, the message should be unread, sent and unmodified. When */
    /* the client creates a message, it is unsent, read, and unmodified. */

    if (OBJ_TestFlag(pims, MSF_SPOOLER))
        pval[1].Value.l = MSGFLAG_UNMODIFIED;
    else
        pval[1].Value.l = MSGFLAG_READ | MSGFLAG_UNSENT | MSGFLAG_UNMODIFIED;

    pval[2].ulPropTag = ptaga->aulPropTag[2] = PR_MESSAGE_CLASS;
    pval[2].Value.LPSZ = TEXT("IPM");

    if (!fMsgInMsg)
    {
        hr = pims->psup->lpVtbl->NewUID(pims->psup, &uid);
        if (hr != hrSuccess)
            goto exit;

        pval[3].ulPropTag = ptaga->aulPropTag[3] = PR_SEARCH_KEY;
        pval[3].Value.bin.cb = sizeof(uid);
        pval[3].Value.bin.lpb = (LPBYTE) &uid;
        pval[4].ulPropTag = ptaga->aulPropTag[4] = PR_STORE_ENTRYID;
        pval[4].Value.bin.cb = pims->eidStore.cb;
        pval[4].Value.bin.lpb = pims->eidStore.lpb;
        pval[5].ulPropTag = ptaga->aulPropTag[5] = PR_STORE_RECORD_KEY;
        pval[5].Value.bin.cb = sizeof(pims->uidResource);
        pval[5].Value.bin.lpb = (LPBYTE) &pims->uidResource;
        pval[6].ulPropTag = ptaga->aulPropTag[6] = PR_MSG_STATUS;
        pval[6].Value.l = 0;

        /* Set PR_ENTRYID, PR_PARENT_ENTRYID, PR_RECORD_KEY and
         * PR_INSTANCE_KEY to null strings to keep clients from writing over
         * them. We get the actual values internally.
         */
        pval[7].ulPropTag = ptaga->aulPropTag[7] = PR_ENTRYID;
        pval[7].Value.bin.cb = 1;
        pval[7].Value.bin.lpb = (LPBYTE) "";
        pval[8].ulPropTag = ptaga->aulPropTag[8] = PR_PARENT_ENTRYID;
        pval[8].Value.bin.cb = 1;
        pval[8].Value.bin.lpb = (LPBYTE) "";
        pval[9].ulPropTag = ptaga->aulPropTag[9] = PR_RECORD_KEY;
        pval[9].Value.bin.cb = 1;
        pval[9].Value.bin.lpb = (LPBYTE) "";
        pval[10].ulPropTag = ptaga->aulPropTag[10] = PR_INSTANCE_KEY;
        pval[10].Value.bin.cb = 1;
        pval[10].Value.bin.lpb = (LPBYTE) "";

        /* this message is not complete until changes have been saved */
        /* mark it as such so that others don't see a partial message */
    }

    /* Initialize the property attribute array. */

    patra->aPropAttr[0] = grfpropattrIMSGInit;
    patra->aPropAttr[1] = grfpropattrIMSGInit | PROPATTR_WRITEABLE;
    patra->aPropAttr[2] = grfpropattrIMSGInit | PROPATTR_WRITEABLE;

    if (!fMsgInMsg)
    {
        patra->aPropAttr[3] = grfpropattrIMSGInit | PROPATTR_WRITEABLE;
        patra->aPropAttr[4] = grfpropattrIMSGInit;
        patra->aPropAttr[5] = grfpropattrIMSGInit;
        patra->aPropAttr[6] = grfpropattrIMSGInit;
        patra->aPropAttr[7] = grfpropattrIMSGInit;
        patra->aPropAttr[8] = grfpropattrIMSGInit;
        patra->aPropAttr[9] = grfpropattrIMSGInit;
        patra->aPropAttr[10] = grfpropattrIMSGInit;
    }

    /* Set the property values. */

    hr = lpmsg->lpVtbl->SetProps(lpmsg, cpropInit, pval, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    /* Set the property attributes. */

    hr = SetAttribIMsgOnIStg(lpmsg, ptaga, patra, &pprba);
    if (hr != hrSuccess || pprba)
        goto exit;

    /* If we succeeded up to this point, commit the properties. */

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
    /* if (hr), fall through to exit */

exit:
    AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

    if (pprba)
    {
        LMFree(&pims->lmr, pprba);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }

    FreePropArrays(&pval, &ptaga, &patra);

    DebugTraceResult(InitIMSGProps, hr);
    return hr;
}

/*
 *  HrSetFlags
 *
 *  Purpose
 *      Set the value of flag bits on a message property. The
 *      properties that this function is used for are PR_MESSAGE_FLAGS
 *      and PR_SUBMIT_FLAGS.
 *
 *  Parameters
 *      pimsg       A pointer to the message object.
 *      ulAction    SET or UNSET
 *      ulPropTag   the property tag to be changed.
 *      ulFlag      flags to be set or unset.
 */
HRESULT HrSetFlags(PIMSG pimsg, ULONG ulAction, ULONG ulPropTag, ULONG ulFlag)
{
    HRESULT hr;
    LPSPropValue pval = NULL;

    AssertSz1(ulPropTag == PR_MESSAGE_FLAGS || ulPropTag == PR_SUBMIT_FLAGS,
        "HrSetFlags: not designed for property %s", SzDecodeUlPropTag(ulPropTag));

    AssertSz1((ulAction == SET) || (ulAction == UNSET), "Bad ulAction: %08lX",
        ulAction);

    /* get the current value of the flag */
    hr = HrGetOneProp((LPMAPIPROP) pimsg, ulPropTag, &pval);

    /* compute and set the new value */
    if (HR_SUCCEEDED(hr))
    {
        LONG lOldFlag = pval->Value.l;

        if (ulAction & SET)
            pval->Value.l |= ulFlag;
        else
            pval->Value.l &= ~ulFlag;

        /* If the value that's there is correct, then don't set it. */

        if (pval->Value.l == lOldFlag)
            goto exit;
    }
    else if (GetScode(hr) == MAPI_E_NOT_FOUND)
    {
        pval->Value.l = (ulAction & SET) ? ulFlag : 0L;
        pval->ulPropTag = ulPropTag;
    }
    else
        goto exit;

    hr = HrSetOneProp((LPMAPIPROP) pimsg, pval);

exit:
    LMFree(&pimsg->pims->lmr, pval);

    return hr;
}

/*
 *  HrSetInternalProps
 *
 *  Purpose
 *      Sets up the in-memory array to hold properties that may change
 *      when the message or folder object is moved or copied. See the
 *      routine ProcessGetProps in mspmisc.c for details of how this
 *      array is used.
 *
 *  Parameters
 *      plmr: A pointer to the linked memory allocation routines.
 *      cprop: The number of properties to set into the in-memory array.
 *          Note that the properties that are saved in-memory are placed
 *          into the array in a hard-coded order (see below). Therefore,
 *          changing cprop to a smaller number eliminates specific props
 *          from the array.
 *      ppval: A pointer to the location to place the in-memory pval array.
 *      pcval: A pointer to the location to place the number of properties
 *          placed into *ppval.
 *      peid: The object's entryid that we are saving in-memory props for.
 *          (Used to get the value for PR_ENTRYID and PR_INSTANCE_KEY).
 *      peidParent: The entryid of the parent object. (Used to get the value
 *          for PR_PARENT_ENTRYID).
 *      ulSeqNum: The value with which to fill PR_RECORD_KEY. (Only used
 *          on message objects).
 *
 *  Returns
 *      HRESULT. Only errors are from memory allocation failures.
 */
HRESULT HrSetInternalProps(PLMR plmr, ULONG cprop, LPSPropValue *ppval,
    ULONG *pcval, PEID peid, PEID peidParent, ULONG ulSeqNum)
{
    LPSPropValue pval = NULL;
    LPSPropValue pvalT;
    LPSPropValue pvalTMac;
    SCODE sc;

    AssertSz(*ppval == NULL, "pval already allocated");
    AssertSz(*pcval == 0, "cval already non-zero");

    /* Allocate the property array. */
    sc = LMAlloc(plmr, cprop * sizeof(SPropValue), &pval);
    if (sc != S_OK)
        goto exit;

    pvalT = pval;
    pvalTMac = pvalT + cprop;

    if (pvalT < pvalTMac)
    {
        sc = ScFillOneSBPval(plmr, (LPVOID) pval, PR_ENTRYID,
            CbEID(peid), (LPBYTE) peid, pvalT);
        if (sc != S_OK)
            goto exit;

        pvalT++;
    }

    if (pvalT < pvalTMac)
    {
        sc = ScFillOneSBPval(plmr, (LPVOID) pval, PR_INSTANCE_KEY,
            CbEID(peid), (LPBYTE) peid, pvalT);
        if (sc != S_OK)
            goto exit;

        pvalT++;
    }

    if (pvalT < pvalTMac)
    {
        sc = ScFillOneSBPval(plmr, (LPVOID) pval, PR_PARENT_ENTRYID,
            CbEID(peidParent), (LPBYTE) peidParent, pvalT);
        if (sc != S_OK)
            goto exit;

        pvalT++;
    }

    if (pvalT < pvalTMac)
    {
        sc = ScFillOneSBPval(plmr, (LPVOID) pval, PR_RECORD_KEY,
            sizeof(ulSeqNum), (LPBYTE) &ulSeqNum, pvalT);
        if (sc != S_OK)
            goto exit;

        pvalT++;
    }

    AssertSz(pvalT == pvalTMac, "Not enough values to fill internal array");

    *pcval = cprop;
    *ppval = pval;

exit:
    if (sc != S_OK)
        LMFree(plmr, pval);

    DebugTraceSc(HrSetInternalProps, sc);
    return ResultFromScode(sc);
}

/*
 * Internal functions (called only from within this file).
 *
 */

/* HrSaveMsgInMsg
 *
 * Perform the necessary steps to save changes on a msg-in-msg type message.
 *
 */
static HRESULT HrSaveMsgInMsg(PIMSG pimsg, ULONG ulFlags)
{
    ULONG ulPropMsgFlags = ulFlags;

    if (!(ulFlags & KEEP_OPEN_READWRITE))
        ulPropMsgFlags |= KEEP_OPEN_READONLY;

    return pimsg->lpmsg->lpVtbl->SaveChanges(pimsg->lpmsg, ulPropMsgFlags);
}

/* Fills in an SBinary PropValue via AllocMore. */

static SCODE ScFillOneSBPval(PLMR plmr, LPVOID pvOrigBuf, ULONG ulPropTag,
    ULONG cb, LPBYTE lpbData, LPSPropValue pval)
{
    SCODE sc;

    sc = LMAllocMore(plmr, cb, pvOrigBuf, &(pval->Value.bin.lpb));
    if (sc == S_OK)
    {
        pval->ulPropTag = ulPropTag;
        pval->Value.bin.cb = cb;
        if (cb)
            memcpy(pval->Value.bin.lpb, lpbData, (UINT) cb);
    }

    return sc;
}


MSPNTFY.C

/*
 *  M S P N T F Y . C
 *
 *  Code for doing internal cross-process notifications within the
 *  Sample Message Store provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"
#include <stdarg.h>

/* INTERNAL Function prototypes. */

long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif,
    LPNOTIFICATION lpNotifs);
static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr);
static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn);
static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey);
static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw,
    ULONG ulTableEvent, FILETIME *pftBeforeUpdate);

long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif,
    LPNOTIFICATION lpNotifs);
HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn);
static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey);

/*
 *  EXTERNAL FUNCTIONS (called from outside this file).
 */

/*
 * HrCreateOGQueueMutex
 *
 *  Purpose
 *      Create the outgoing queue mutex, and return it to the caller.
 *
 *  Arguments
 *      phQMutex: Pointer to the location to return the new mutex.
 *
 *  Returns:
 *      HRESULT: Will return an error only if the CreateMutex call fails.
 */
HRESULT HrCreateOGQueueMutex(HANDLE *phQMutex)
{
    HRESULT hr = hrSuccess;
    HANDLE hMutex;
    LPTSTR szMutexName = "SMS_OGQUEUEFILE_MUTEX";

    hMutex = CreateMutex(NULL, FALSE, szMutexName);

    if (hMutex)
        *phQMutex = hMutex;

    #ifndef WIN16
    else
    {
        TraceSz1("SampleMS: HrCreateOGQueueMutex: call to"
            " CreateMutex failed (error %08lX)", GetLastError());

        hr = ResultFromScode(MAPI_E_CALL_FAILED);
    }
    #endif

    DebugTraceResult(HrCreateOGQueueMutex, hr);
    return hr;
}

/*
 * HrSetupPrivateNotifications
 *
 *  Purpose
 *      Setup two private channels via the MAPI notification engine to
 *      tell other processes running against this store when 1) the
 *      outgoing queue changes and 2) when contents and hierarchy tables
 *      change. We communicate between the multiple client processes and
 *      the one spooler process. For the outgoing queue, we use a key that
 *      is the full pathname to the outgoing queue file. For the other
 *      tables, we use a unique 16-byte ID. Remember the connections so
 *      that we can Unsubscribe when we shutdown the store.
 *
 *  Arguments
 *      pims: a pointer to the message store object.
 *
 *  Side Effects
 *      Fills in the ulOQConn and ulTblConn members of pims.
 *
 *  Returns
 *      HRESULT
 */
HRESULT HrSetupPrivateNotifications(PIMS pims)
{
    HRESULT hr;
    LPMAPIADVISESINK lpAdvise;
    ULONG ulOQConn = 0;
    ULONG ulTblConn = 0;
    LPNOTIFKEY lpKey = NULL;

    /* Use the MAPI notification engine to tell myself */
    /* (across processes) when to update outgoing queue. */

    /* The key is the path to the disk cache of the outbound queue */
    hr = HrGetOutgoingNotificationKey(pims, &lpKey);
    if (hr != hrSuccess)
        goto exit;

    hr = HrAllocAdviseSink(&LSMSOQNotifCallback, (LPVOID) pims, &lpAdvise);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey,
        fnevExtended, 0L, lpAdvise, &ulOQConn);

    /* Always release; mapi will have addref'ed it during Subscribe */
    UlRelease(lpAdvise);

    if (hr != hrSuccess)
        goto exit;

    FreeNull(lpKey);
    lpKey = NULL;

    /* Now, setup notifications for the other tables. */

    hr = HrGetTableNotificationKey(pims, &lpKey);
    if (hr != hrSuccess)
        goto exit;

    hr = HrAllocAdviseSink(&LSMSTblNotifCallback, (LPVOID) pims, &lpAdvise);
    if (hr != hrSuccess)
        goto exit;

    hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey,
        fnevExtended, 0L, lpAdvise, &ulTblConn);

    /* Always release; mapi will have addref'ed it during Subscribe */
    UlRelease(lpAdvise);

    if (hr != hrSuccess)
        goto exit;

exit:
    FreeNull(lpKey);

    if (hr == hrSuccess)
    {
        /* Remember our use of the notification engine. */
        pims->ulOQConn = ulOQConn;
        pims->ulTblConn = ulTblConn;
    }
    else if (ulOQConn != 0)
        (void) pims->psup->lpVtbl->Unsubscribe(pims->psup, ulOQConn);

    DebugTraceResult(HrSetupPrivateNotifications, hr);
    return hr;
}

/*
 * HrUpdateOutgoingQueue
 *
 *  Purpose
 *      Updates the outgoing queue based on the information given. If the
 *      outgoing queue table is not open, or is out-of-date with respect to
 *      the outgoing queue file on disk, initializes the table. The function
 *      then applies the change requested to the table, writes the table on
 *      disk, and notifies other processes of the change.
 *
 *  Arguments
 *      pims: A pointer to the message store object.
 *      pimsg: For a TABLE_ROW_ADDED event, a pointer to the message being
 *          added to the queue; otherwise, this parameter should be NULL.
 *      peid: For a TABLE_ROW_DELETED event, a pointer to the entryid of the
 *          message being deleted from the queue; otherwise, this parameter
 *          should be NULL.
 *      ulTableEvent: The type of update event: Either TABLE_ROW_ADDED or
 *          TABLE_ROW_DELETED.
 *
 *  Returns
 *      HRESULT
 */
HRESULT HrUpdateOutgoingQueue(PIMS pims, PIMSG pimsg, PEID peid,
    ULONG ulTableEvent)
{
    HRESULT hr = hrSuccess;
    LPTABLEDATA lptbl;
    SRow srNewRow = {0, 0, NULL};
    LPSRow prw;
    BOOL fInMutex = FALSE;
    FILETIME ftBeforeUpdate;

    /* If the file mutex doesn't yet exist on this process, create it. */

    if (pims->hOGQueueMutex == NULL)
    {
        hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
        if (hr != hrSuccess)
            goto exit;
    }

    /* Get the file mutex so that we can use the file (and change it) */
    /* without crossing paths with another process. */

    WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
    fInMutex = TRUE;

    /* This routine will open the outgoing queue table if it's not already */
    /* open in this process, and will leave the opened copy around in pims. */

    if (pims->lptblOutgoing == NULL)
    {
        hr = HrNewOutgoingTableData(pims);
        if (hr != hrSuccess)
            goto exit;
    }

    lptbl = pims->lptblOutgoing;
    ftBeforeUpdate = pims->ftOGQueue;

    if (ulTableEvent == TABLE_ROW_ADDED)
    {
        ULONG cValues;

        AssertSz(pimsg, "A msg should be provided on an add");
        AssertSz(peid == NULL, "No entryid should be provided on an add");

        hr = pimsg->lpVtbl->GetProps(pimsg, (LPSPropTagArray) &sptaOutgoing,
            0, /* ansi */
            &cValues, &srNewRow.lpProps);

        if (HR_FAILED(hr))          /* Ignore warnings from GetProps. */
            goto exit;

        srNewRow.cValues = cValues;

        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &srNewRow);
        if (hr != hrSuccess)
            goto exit;

        prw = &srNewRow;
    }
    else
    {
        AssertSz(ulTableEvent == TABLE_ROW_DELETED,
            "Bad event type received");
        AssertSz(pimsg == NULL, "No msg should be provided on a delete");
        AssertSz(peid, "An entryid should be provided on a delete");

        /* remove it from the outgoing queue */
        hr = HrRemoveRow(lptbl, peid);
        if (hr != hrSuccess)
            goto exit;

        prw = NULL;
    }

    hr = HrWriteTableOnDisk(lptbl, (POBJ) pims, NULL, szOutgoingFileName);
    if (hr != hrSuccess)
        goto exit;

    /* Update the last mod time of the table inside this process's */
    /* message store object. */

    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName,
        &pims->ftOGQueue);
    if (hr != hrSuccess)
        goto exit;

    hr = HrNotifyOnOutgoingQueue(pims, peid, prw, ulTableEvent,
        &ftBeforeUpdate);

exit:
    if (fInMutex)
        ReleaseMutex(pims->hOGQueueMutex);

    LMFree(&pims->lmr, srNewRow.lpProps);

    DebugTraceResult(HrUpdateOutgoingQueue, hr);
    return hr;
}

/*
 * HrNewOutgoingTableData
 *
 * Purpose  Checks the outgoing table data object in the message store.
 *          If there isn't one, creates it and initializes it from disk.
 *          If there is one, empties it, and re-initializes it from disk.
 *          Should be inside the outgoing queue mutex during this function.
 *
 * Parameters
 *  pims    A pointer to the message store object.
 *
 * Side Effects
 *          Fills in the lptblOutgoing member of the pims.
 *          Updates pims->ftOGQueue with the last mod time of the
 *          outgoing queue file.
 */
HRESULT HrNewOutgoingTableData(PIMS pims)
{
    HRESULT hr = hrSuccess;
    LPTABLEDATA lptbl = pims->lptblOutgoing;
    BOOL fTableCreated = FALSE;

    if (!lptbl)
    {
        PINST pinst;
        SCODE sc = S_OK;

        /* The table doesn't exist. Create it. */

        pinst = (PINST) PvGetInstanceGlobals();

        if (pinst == NULL)
        {
            hr = ResultFromScode(MAPI_E_CALL_FAILED);
            goto exit;
        }

        sc = CreateTable((LPIID) &IID_IMAPITableData, pims->lmr.lpAllocBuf,
            pims->lmr.lpAllocMore, pims->lmr.lpFreeBuf, pinst->lpmalloc,
            TBLTYPE_DYNAMIC, PR_INSTANCE_KEY, (LPSPropTagArray) &sptaOutgoing,
            &lptbl);

        if (sc != S_OK)
        {
            hr = ResultFromScode(sc);
            goto exit;
        }

        fTableCreated = TRUE;
    }
    else
    {
        /* The table exists already. Delete all rows from it. */
        EmptyTable(lptbl, &pims->lmr);
    }

    /* read the table in from disk */
    /* outgoing queue tables can't be regenerated, so any error reading */
    /* the table is fatal. We lose all messages in the OG queue if this */
    /* function has an error. */

    hr = HrReadTableFromDisk(lptbl, (POBJ) pims, NULL, OUTGOING_COLUMNS,
        szOutgoingFileName);
    if (hr != hrSuccess)
    {
        TraceSz("SMS: Bad OG Queue data on disk.");
        goto exit;
    }

    /* Verify that all messages in the table actually exist on disk. If */
    /* not, remove the row(s) and re-write the table to disk. */
    hr = HrSyncOutgoingTable(lptbl, pims);
    if (hr != hrSuccess)
        goto exit;

    /* Save away the last mod time of the table inside this process's */
    /* message store object. */

    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName,
        &pims->ftOGQueue);
    if (hr != hrSuccess)
        goto exit;

    pims->lptblOutgoing = lptbl;

exit:
    if (hr != hrSuccess && fTableCreated)
        UlRelease(lptbl);

    DebugTraceResult(HrNewOutgoingTableData, hr);
    return hr;
}

/*
 * INTERNAL Functions (called from within this file ONLY).
 *
 */

/*----------------------------------------------------------------------+
|                                                                       |
|           CONTENTS AND HIERARCHY TABLE NOTIFICATION HANDLING          |
|                                                                       |
+----------------------------------------------------------------------*/

/*
 * LSMSTblNotifCallback
 *
 *  Purpose
 *      Update the contents or hierarchy table associated with the folder eid
 *      passed across. We should receive a notification when the tabledata
 *      object needs to have a row added, deleted or modified. Calls
 *      ChangeTable after decoding the notification.
 *
 *  Arguments
 *      lpvContext: A pointer to the message store object to use. We need
 *          to verify that the object is still valid before using it.
 *      cNotif: The number of notifications to process.
 *      lpNotif: A pointer to an array of NOTIFICATION structures.
 *
 *  Returns
 *      LONG: Always returns 0.
 */
long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif,
    LPNOTIFICATION lpNotif)
{
    PIMS pims = (PIMS) lpvContext;
    SCODE sc = S_OK;
    HRESULT hr = hrSuccess;
    PTNB ptnb;

    /*
     * Our code sends one extended notification at a time.
     * The notification consists of the table event that occurred along with
     * an object notification containing the entryids we need. We only use
     * two of the entryids in the object notification. The ParentID fields
     * refer to the parent folder of the table we need to update. The EntryID
     * fields refer to the object that changed within the folder. The
     * ulObjType field will be either MAPI_MESSAGE (for contents table
     * changes) or MAPI_FOLDER (for hierarchy table changes).  All other
     * fields in the structure are unused and should be set to 0.
     */
    if (IMS_IsInvalid(pims)
        || cNotif != 1
        || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION)))
        || lpNotif->ulEventType != fnevExtended
        || lpNotif->info.ext.ulEvent != 0)
        return 0;

    ptnb = (PTNB) lpNotif->info.ext.pbEventParameters;

    if (IsBadReadPtr(ptnb, CbNewTNB(0))
        || IsBadReadPtr(ptnb, CbTNB(ptnb)))
        return 0;

    IMS_EnterCriticalSection(pims);

    hr = HrApplyTblNotifChanges(pims, ptnb);

    IMS_LeaveCriticalSection(pims);

    if (hr != hrSuccess)
        sc = GetScode(hr);

    return sc;
}

/*
 * HrApplyTblNotifChanges
 *
 * Purpose
 *  This function relocates and validates the internal object notification
 *  passed in, and then calls ChangeTable to actually update any open tables
 *  within this process with the change given. The notification needs to be
 *  relocated because the pointers from the other process may not be valid
 *  on this process.
 *
 * Parameters
 *  pims: A pointer to the message store object.
 *  ptnbIn: A pointer to the table notification block (TNB) containing the
 *      data we need in order to update any open tables om this process.
 *
 * Returns: validation errors or hrSuccess.
 */
HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn)
{
    HRESULT hr = hrSuccess;
    SCODE sc = S_OK;
    LPNOTIFICATION lpntf;
    PTNB ptnb = NULL;
    OBJECT_NOTIFICATION *pon;
    ULONG cb;

    /* Allocate a new notification block, and copy the data over before */
    /* relocation. */

    hr = HrAlloc(CbNewTNB(ptnbIn->cbNtf), &ptnb);
    if (hr != hrSuccess)
        goto ret;

    memcpy(ptnb, ptnbIn, (UINT) CbNewTNB(ptnbIn->cbNtf));

    lpntf = (LPNOTIFICATION) ptnb->abNtf;

    if (lpntf->ulEventType != fnevObjectModified)
    {
        TraceSz1("SMS: HrApplyTblNotifChanges: Bad ulEventType %08lX "
            "received", lpntf->ulEventType);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto ret;
    }

    /* Relocate the notification into the address space of this process. */
    /* We passed along the memory offset of the originating process to allow */
    /* this code to work. Note that ScRelocNotifications currently only */
    /* works from "bad" addresses to "good" addresses. The code assumes that */
    /* pointers are "bad" inside the notification, and that after conversion, */
    /* they are valid. */

    sc = ScRelocNotifications(1, lpntf, ptnb->pvRef, (LPVOID) lpntf, &cb);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto ret;
    }

    if (ptnb->cbNtf != cb)
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto ret;
    }

    pon = (OBJECT_NOTIFICATION *) &(lpntf->info.obj);

    if (pon->ulObjType != MAPI_MESSAGE && pon->ulObjType != MAPI_FOLDER)
    {
        TraceSz1("SMS: HrApplyTblNotifChanges: unexpected Object Type %08lX",
            pon->ulObjType);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto ret;
    }

    if (    FIsInvalidEID(pon->cbParentID, (PEID) pon->lpParentID, pims)
        ||  !FIsFolder((PEID) pon->lpParentID))
    {
        TraceSz("SMS: HrApplyTblNotifChanges: invalid parent entryid");
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto ret;
    }

    /* TABLE_CHANGED events don't require a lpEntryID, because multiple
     * objects have changed. ChangeTable() simply validates all rows in
     * the table against the files on disk.
     */
    if (    ptnb->ulTableEvent != TABLE_CHANGED
        &&  FIsInvalidEID(pon->cbEntryID, (PEID) pon->lpEntryID, pims))
    {
        TraceSz("SMS: HrApplyTblNotifChanges: invalid entryid");
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto ret;
    }

    ChangeTable(pims, (PEID) pon->lpParentID, (PEID) pon->lpEntryID,
        pon->ulObjType, ptnb->ulTableEvent, FALSE);

ret:
    FreeNull(ptnb);

    DebugTraceResult(HrApplyTblNotifChanges, hr);
    return hr;
}

/*
 * HrSendNotif
 *
 * Purpose
 *  This function constructs and sends a notification to other active Sample
 *  Message Store processes open on this message store file. The notification
 *  describes a change (add, delete, or modify) to either a message (contents
 *  table) or folder (hierarchy table). The receiver will need to get two
 *  entryids, one for the parent folder of the changed object, and one for
 *  the changed object itself. The receiver also needs to know whether the
 *  changed object is a message or a folder, and what type of change occurred:
 *  add, delete, modify, or change (contents tables only).
 *
 * Parameters
 *  pims: Pointer to the message store object.
 *  peidParent: The entryid of the folder containing the table to update and
 *              the object that changed.
 *  peidObject: The entryid of the object that changed. May be NULL when
 *              sending a TABLE_CHANGED notification (contents tables only).
 *  ulTableEvent: TABLE_ROW_ADDED, TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or
 *              TABLE_CHANGED (TABLE_CHANGED only works on contents tables).
 *  ulObjType: The type of object that changed. This implies the type of table
 *              to update. May be MAPI_MESSAGE (contents table) or MAPI_FOLDER
 *              (hierarchy table).
 *
 * Returns: Memory and disk errors or success.
 */
HRESULT HrSendNotif(PIMS pims, PEID peidParent, PEID peidObject,
    ULONG ulTableEvent, ULONG ulObjType)
{
    HRESULT hr = hrSuccess;
    SCODE sc;
    LPNOTIFKEY lpKey = NULL;
    NOTIFICATION ntfTemp;
    NOTIFICATION ntf;
    PTNB ptnb = NULL;
    ULONG cbNtf;
    ULONG cbOut;
    ULONG ulFlags = 0;

    /* Get the key */
    hr = HrGetTableNotificationKey(pims, &lpKey);
    if (hr != hrSuccess)
        goto ret;

    /*
     * Our code sends one extended notification at a time. The notification
     * consists of the table event that occurred along with an object
     * notification containing the entryids we need. We send the type of
     * table event as part of our extended notification structure, and
     * package the object notification always as "fnevObjectModified". We
     * only use two of the entryids in the object notification. The ParentID
     * fields refer to the parent folder of the table we need to update. The
     * EntryID fields refer to the object that changed within the folder. The
     * ulObjType field will be either MAPI_MESSAGE (for contents table
     * changes) or MAPI_FOLDER (for hierarchy table changes).  All other
     * fields in the structure are unused and should be set to 0.
     */
    memset(&ntfTemp, 0, sizeof(NOTIFICATION));

    /* We always send the same type of event here. This is enough to get the
     * notification code to count and relocate what we send. We send the real
     * table event in ptnb->ulTableEvent (see below).
     */
    ntfTemp.ulEventType = fnevObjectModified;

    ntfTemp.info.obj.ulObjType  = ulObjType;

    ntfTemp.info.obj.lpParentID = (LPENTRYID) peidParent;
    ntfTemp.info.obj.cbParentID = CbEID(peidParent);

    if (ulTableEvent != TABLE_CHANGED)
    {
        ntfTemp.info.obj.lpEntryID  = (LPENTRYID) peidObject;
        ntfTemp.info.obj.cbEntryID  = CbEID(peidObject);
    }

    sc = ScCountNotifications(1, &ntfTemp, &cbNtf);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto ret;
    }

    hr = HrAlloc(CbNewTNB(cbNtf), &ptnb);
    if (hr != hrSuccess)
        goto ret;

    /* Here's where we send the table event. It's either TABLE_ROW_ADDED,
     * TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or TABLE_CHANGED.
     */
    ptnb->ulTableEvent = ulTableEvent;
    ptnb->cbNtf = cbNtf;

    sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ptnb->abNtf, &cbOut);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto ret;
    }

    AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes "
        "than ScCountNotifications returned.");

    /* Pass across the notification's memory offset so that the receiving */
    /* process can relocate the notification to its address space. */
    ptnb->pvRef = (LPVOID) ptnb->abNtf;

    ntf.ulEventType = fnevExtended;
    ntf.info.ext.ulEvent = 0;
    ntf.info.ext.cb = CbTNB(ptnb);
    ntf.info.ext.pbEventParameters = (LPBYTE) ptnb;

    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags);

ret:
    FreeNull(lpKey);
    FreeNull(ptnb);

    DebugTraceResult(HrSendNotif, hr);
    return hr;
}

/*
 * HrGetTableNotificationKey
 *
 * Purpose
 *  Generate and return the notification key that will allow cross-process
 *  notifications for changes to any contents or hierarchy tables within a
 *  store. The memory returned should be freed with FreeNull. This
 *  notification key needs to work for processes attached to this particular
 *  message store, and should only receive notifications having to do with
 *  changes to tables. The key contains the store guid (unique for this store)
 *  preceeded by a ULONG with 0x0000ABCD in it. Note that the choice of
 *  0x0000ABCD is arbitrary. As long as the sender sends to the same key as
 *  the receiver listens to, and no unexpected sender sends to that key,
 *  we're fine.
 *
 * Parameters
 *  pims        pointer to the message store object.
 *  lppKey      pointer to the location to return the new key.
 */
static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey)
{
    HRESULT hr = hrSuccess;
    LPNOTIFKEY lpKey = NULL;
    ULONG cb;                   /* number of bytes in the key */

    /* allocate space for the key */
    cb = sizeof(ULONG) + sizeof(MAPIUID);
    hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey);
    if (hr != hrSuccess)
        goto exit;

    *((ULONG *) &(lpKey->ab[0])) = 0x0000ABCD;
    GetResourceUID(pims, (MAPIUID *) &(lpKey->ab[sizeof(ULONG)]));
    lpKey->cb = cb;

exit:
    if (HR_FAILED(hr))
    {
        FreeNull(lpKey);
        lpKey = NULL;
    }

    *lppKey = lpKey;

    DebugTraceResult(HrGetTableNotificationKey, hr);
    return hr;
}

/*----------------------------------------------------------------------+
|                                                                       |
|               OUTGOING QUEUE NOTIFICATION HANDLING                    |
|                                                                       |
+----------------------------------------------------------------------*/

/*
 * LSMSOQNotifCallback
 *
 *  Purpose
 *      Update the outgoing queue table associated with the process that
 *      the spooler is using. We should receive a notification when the
 *      tabledata object needs to have a row added or deleted. If the
 *      tabledata object exists on the message store object, then call
 *      HrModifyRow (when a row is added), or HrRemoveRow (when a row is
 *      deleted) to update the table appropriately.
 *
 *  Arguments
 *      lpvContext: A pointer to the message store object to use. We need
 *          to verify that the object is still valid before using it.
 *      cNotif: The number of notifications to process.
 *      lpNotif: A pointer to an array of NOTIFICATION structures.
 *
 *  Returns
 *      LONG: Always returns 0.
 */
long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif,
    LPNOTIFICATION lpNotif)
{
    PIMS pims = (PIMS) lpvContext;
    SCODE sc = S_OK;
    HRESULT hr = hrSuccess;
    FILETIME ftCurrent;
    BOOL fInMutex = FALSE;
    PONB ponb;

    /* Our code sends one extended notification at a time. This */
    /* extended notification contains two filetimes (the time */
    /* before the outgoing queue file was modified, and the time after */
    /* the change was made), and a standard notification with the change */
    /* to apply to the table. It should be an fnevTableModified, and */
    /* should be either TABLE_ROW_ADDED or TABLE_ROW_DELETED. If we ever */
    /* receive anything other than this, the code must change. */
    if (IMS_IsInvalid(pims)
        || cNotif != 1
        || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION)))
        || lpNotif->ulEventType != fnevExtended
        || lpNotif->info.ext.ulEvent != 0)
        return 0;

    ponb = (PONB) lpNotif->info.ext.pbEventParameters;

    if (IsBadReadPtr(ponb, CbNewONB(0))
        || IsBadReadPtr(ponb, CbONB(ponb)))
        return 0;

    IMS_EnterCriticalSection(pims);

    /* Check to see if the outgoing queue table data is open. If it isn't, */
    /* there is nothing to do, because the current table will be read and */
    /* initialized from disk when the spooler opens it. We're done. */

    if (!pims->lptblOutgoing)
        goto exit;

    /* If the file mutex doesn't yet exist on this process, create it. */

    if (pims->hOGQueueMutex == NULL)
    {
        hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
        if (hr != hrSuccess)
            goto exit;
    }

    /* Get the file mutex so that we can use the file (and change it) */
    /* without crossing paths with another process. */

    WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
    fInMutex = TRUE;

    /* Get time that the file was last modified */
    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName, &ftCurrent);
    if (hr != hrSuccess)
        goto exit;

    /* If the time that this process last read the file is the same as */
    /* the time that the file was last modified, then don't do anything */
    /* because we already have all changes, including the one sent to us */
    /* in this notification. This can happen because we picked up two */
    /* changes at once, or because this process is actually the same */
    /* process that sent the notification. */
    if (CompareFileTime(&ftCurrent, &pims->ftOGQueue) == 0)
        goto exit;

    /* If the time that this process last read the file is the same as */
    /* the time that the other process read the file, then we can simply */
    /* apply the changes sent in the notification itself, and update our */
    /* time to the time after update sent in the notification. */
    /* If the times are different, or there is a problem applying the */
    /* changes, then reconstruct the table from the on-disk copy. */

    if ((CompareFileTime(&ponb->ftBeforeUpdate, &pims->ftOGQueue) == 0)
        && (HrApplyOQNotifChanges(pims->lptblOutgoing, ponb) == hrSuccess))
    {
        pims->ftOGQueue = ponb->ftAfterUpdate;
    }
    else
    {
        hr = HrNewOutgoingTableData(pims);
        if (hr != hrSuccess)
            goto exit;
    }

exit:
    if (fInMutex)
        ReleaseMutex(pims->hOGQueueMutex);

    IMS_LeaveCriticalSection(pims);

    if (hr != hrSuccess)
        sc = GetScode(hr);

    return sc;
}

/*
 * EmptyTable
 *
 *  Purpose
 *      Deletes all rows from the table data object given.
 *      Helper function for the outgoing queue table notification callback
 *      routine.
 *
 *  Arguments
 *      lptbl: A pointer to the tabledata object to empty.
 *      plmr: A pointer to the linked memory routines.
 *
 *  Returns
 *      void.
 */
static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr)
{
    HRESULT hr;
    LPSRow lpsRow;
    LPSPropValue pval;
    LPSPropValue pvalMac;

    while (TRUE)
    {
        /* Get the first row. Note that as we delete rows, this will */
        /* keep giving us a new row. */

        hr = lptbl->lpVtbl->HrEnumRow(lptbl, 0, &lpsRow);
        if (hr != hrSuccess)
        {
            TraceSz1("Sample MS: EmptyTable: HrEnumRow failed with"
                " sc == %s", SzDecodeScode(GetScode(hr)));
            break;
        }

        /* The table is empty when no row is returned */
        if (!lpsRow)
            break;

        /* find the entryid in the property value array */

pval = lpsRow->lpProps;
        pvalMac = pval + lpsRow->cValues;

        for (; pval < pvalMac; ++pval)
            if (pval->ulPropTag == PR_INSTANCE_KEY)
                break;

        /* Every row should contain an inst key. It is the index property. */
        if (pval == pvalMac)
        {
            TrapSz("No PR_INSTANCE_KEY found in the table row");
            break;
        }

        /* delete this row from the table */
        hr = lptbl->lpVtbl->HrDeleteRow(lptbl, pval);

        LMFree(plmr, lpsRow);
        lpsRow = NULL;

        if (hr != hrSuccess)
        {
            TraceSz1("Sample MS: EmptyTable: HrDeleteRow failed with"
                " error %s", SzDecodeScode(GetScode(hr)));
            break;
        }
    }

    return;
}

/*
 * HrApplyOQNotifChanges
 *
 *  Purpose
 *      Helper function of the notification callback routine. If the
 *      table on disk hasn't changed except for the notification given,
 *      then we can update the outgoing queue table data directly instead
 *      of re-reading the table from disk. This function modifies the
 *      table data directly by calling HrModifyRow (when a row is added),
 *      or HrRemoveRow (when a row is deleted).
 *      Note that we have to convert the notification inside the ONB so
 *      that the pointers are valid. Also note that we may NOT modify the
 *      notification data at all; therefore, we must copy the data before
 *      changing it.
 *
 *  Arguments
 *      lptbl: a pointer to the table data object to update.
 *      ponbIn: a pointer to the ONB received in the callback.
 *
 *  Returns
 *      HRESULT
 */
static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn)
{
    HRESULT hr = hrSuccess;
    SCODE sc;
    ULONG cb;
    LPNOTIFICATION lpntf;
    PONB ponb = NULL;

    /* Allocate a new notification block, and copy the data over before */
    /* relocation. */

    hr = HrAlloc(CbNewONB(ponbIn->cbNtf), &ponb);
    if (hr != hrSuccess)
        goto exit;

    memcpy(ponb, ponbIn, (UINT) CbNewONB(ponbIn->cbNtf));

    lpntf = (LPNOTIFICATION) ponb->abNtf;

    /* Relocate the notification into the address space of this process. */
    /* We passed along the memory offset of the originating process to allow */
    /* this code to work. Note that ScRelocNotifications currently only */
    /* works from "bad" addresses to "good" addresses. The code assumes that */
    /* pointers are "bad" inside the notification, and that after conversion, */
    /* they are valid. */

    sc = ScRelocNotifications(1, lpntf, ponb->pvRef, (LPVOID) lpntf, &cb);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    if (ponb->cbNtf != cb)
    {
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    /* We don't expect any events other than those for a table. */

    if (lpntf->ulEventType != fnevTableModified)
    {
        TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulEventType %08lX",
            lpntf->ulEventType);
        hr = ResultFromScode(MAPI_E_CALL_FAILED);
        goto exit;
    }

    switch (lpntf->info.tab.ulTableEvent)
    {
    case TABLE_ROW_DELETED:

        /* delete the row from the table according to the index */
        /* property value in the notification structure */
        hr = lptbl->lpVtbl->HrDeleteRow(lptbl, &lpntf->info.tab.propIndex);
        if (hr != hrSuccess)
        {
            TraceSz1("SMS: HrApplyOQNotifChanges: HrDeleteRow returns sc == %s",
                SzDecodeScode(GetScode(hr)));
            goto exit;
        }
        break;

    case TABLE_ROW_ADDED:

        /* add the row to the table. We don't care where in the table */
        /* it goes, because the row will be sorted by the spooler in */
        /* its view anyway. */

        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &lpntf->info.tab.row);
        if (hr != hrSuccess)
        {
            TraceSz1("SMS: HrApplyOQNotifChanges: HrModifyRow returns sc == %s",
                SzDecodeScode(GetScode(hr)));
            goto exit;
        }
        break;

    default:

        /* We don't expect any other table events than the */
        /* two above. */

        TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulTableEvent %08lX",
            lpntf->info.tab.ulTableEvent);
        break;
    }

exit:
    FreeNull(ponb);

    DebugTraceResult(HrApplyOQNotifChanges, hr);
    return hr;
}

/*
 * HrGetOutgoingNotificationKey
 *
 * Purpose  return the nofication key for the outgoing queue
 *          memory should be freed with FreeNull
 *          The key we use contains the full pathname to the outgoing
 *          queue file on disk. This should be unique for the store
 *          that we're running against.
 *
 * Parameters
 * pims         the store whose outgoing queue is being referred to
 * lppKey       pointer to the key
 */
static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey)
{
    HRESULT hr = hrSuccess;
    LPNOTIFKEY lpKey = NULL;
    ULONG cb;                   /* number of bytes in the key */
    LPSTR szPath = NULL;        /* path to outgoing queue */

    hr = HrGetTableName((POBJ) pims, NULL, szOutgoingFileName, &szPath);
    if (HR_FAILED(hr))
        goto exit;

    /* allocate space for the key */
    cb = Cbtszsize(szPath);
    hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey);
    if (hr != hrSuccess)
        goto exit;

    lstrcpy(lpKey->ab, szPath);
    lpKey->cb = cb;

exit:
    FreeNull(szPath);
    if (HR_FAILED(hr))
    {
        FreeNull(lpKey);
        lpKey = NULL;
    }
    *lppKey = lpKey;
    return hr;
}

/*
 * HrNotifyOnOutgoingQueue
 *
 * Purpose
 *  Send out a notification that the Outgoing Queue has had a row added
 *  or deleted. Also send the filetime of the queue file before and after
 *  the modification.
 *
 * Parameters
 *  pims: A pointer to the message store object.
 *  peid: (For TABLE_ROW_DELETED) The entryid of the message in the
 *          queue that was deleted.
 *  prw: (For TABLE_ROW_ADDED) A pointer to the row of data added
 *          to the OG Queue.
 *  ulTableEvent: Either TABLE_ROW_ADDED or TABLE_ROW_DELETED.
 *  pftBeforeUpdate: A pointer to the filetime of the queue file before the
 *          update was performed.
 */
static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw,
    ULONG ulTableEvent, FILETIME *pftBeforeUpdate)
{
    HRESULT hr;
    LPNOTIFKEY lpKey = NULL;
    ULONG ulFlags = 0;
    NOTIFICATION ntf;
    NOTIFICATION ntfTemp;
    PONB ponb = NULL;
    ULONG cbNtf;
    ULONG cbOut;
    SCODE sc;

    /* get the key */
    hr = HrGetOutgoingNotificationKey(pims, &lpKey);
    if (HR_FAILED(hr))
        goto exit;

    /* Assemble the notification. */
    ntfTemp.ulEventType = fnevTableModified;
    ntfTemp.info.tab.ulTableEvent = ulTableEvent;
    ntfTemp.info.tab.hResult = hrSuccess;

    if (ulTableEvent == TABLE_ROW_DELETED)
    {
        /* Send across the index property for the row: PR_INSTANCE_KEY */
        ntfTemp.info.tab.propIndex.ulPropTag = PR_INSTANCE_KEY;
        ntfTemp.info.tab.propIndex.Value.bin.cb = CbEID(peid);
        ntfTemp.info.tab.propIndex.Value.bin.lpb = (BYTE *) peid;
        memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue));
        ntfTemp.info.tab.row.cValues = 0;
        ntfTemp.info.tab.row.lpProps = NULL;
    }
    else
    {
        AssertSz(ulTableEvent == TABLE_ROW_ADDED,
            "Bad event type: about to send bogus internal notification");

        memset(&(ntfTemp.info.tab.propIndex), 0, sizeof(SPropValue));
        memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue));
        ntfTemp.info.tab.row = *prw;
    }

    sc = ScCountNotifications(1, &ntfTemp, &cbNtf);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    hr = HrAlloc(CbNewONB(cbNtf), &ponb);
    if (hr != hrSuccess)
        goto exit;

    ponb->cbNtf = cbNtf;

    sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ponb->abNtf, &cbOut);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes "
        "than ScCountNotifications returned.");

    ponb->ftBeforeUpdate = *pftBeforeUpdate;
    ponb->ftAfterUpdate = pims->ftOGQueue;

    /* Pass across the notification's memory offset so that the receiving */
    /* process can relocate the notification to its address space. */
    ponb->pvRef = (LPVOID) ponb->abNtf;

    ntf.ulEventType = fnevExtended;
    ntf.info.ext.ulEvent = 0;
    ntf.info.ext.cb = CbONB(ponb);
    ntf.info.ext.pbEventParameters = (LPBYTE) ponb;

    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags);

exit:
    FreeNull(lpKey);
    FreeNull(ponb);

    DebugTraceResult(HrNotifyOnOutgoingQueue, hr);
    return hr;
}


MSPOBJ.C

/*
 * M S P O B J . C
 *
 * Common code for implementation of objects in the sample message store
 * provider.
 *
 * Copyright (C) 1992-1995 Microsoft Corporation. All Rights Reserved.
 */

#include "msp.h"

/* Object Type to Neuter Function Map -------------------------------------- */

LPFNNEUTER rgfnNeuter[] =
{
    (LPFNNEUTER) 0,             /* IMSProvider */
    (LPFNNEUTER) 0,             /* IMSLogon */
    (LPFNNEUTER) IMS_Neuter,    /* IMsgStore */
    (LPFNNEUTER) IFLD_Neuter,   /* IMAPIFolder */
    (LPFNNEUTER) IMSG_Neuter,   /* IMessage */
    (LPFNNEUTER) IATCH_Neuter,  /* IAttach */
    (LPFNNEUTER) 0,             /* IStream */
    (LPFNNEUTER) 0,             /* IMAPITable */
};

/* Object Type to Interfaces Supported Map --------------------------------- */

REFIID MSP_IIDs[] =
{
    &IID_IMSProvider,
    &IID_IUnknown
};

REFIID MSL_IIDs[] =
{
    &IID_IMSLogon,
    &IID_IUnknown
};

REFIID MS_IIDs[] =
{
    &IID_IMsgStore,
    &IID_IMAPIProp,
    &IID_IUnknown
};

REFIID FLD_IIDs[] =
{
    &IID_IMAPIFolder,
    &IID_IMAPIContainer,
    &IID_IMAPIProp,
    &IID_IUnknown
};

REFIID MSG_IIDs[] =
{
    &IID_IMessage,
    &IID_IMAPIProp,
    &IID_IUnknown
};

REFIID ATCH_IIDs[] =
{
    &IID_IAttachment,
    &IID_IMAPIProp,
    &IID_IUnknown
};

REFIID STM_IIDs[] =
{
    &IID_IStream,
    &IID_IUnknown
};

REFIID TBL_IIDs[] =
{
    &IID_IMAPITable,
    &IID_IUnknown
};

REFIID *rgpriid[] =
{
    MSP_IIDs,
    MSL_IIDs,
    MS_IIDs,
    FLD_IIDs,
    MSG_IIDs,
    ATCH_IIDs,
    STM_IIDs,
    TBL_IIDs
};

#define OBJ_IsInvalid(pobj, f)                                  \
    (IsBadWritePtr(pobj, sizeof(OBJ))                           \
    || IsBadReadPtr((pobj)->lpVtbl, sizeof(OBJ_Vtbl))           \
    || (pobj)->lpVtbl->f != OBJ_##f                             \
    || (pobj)->wType >= OT_MAX)

/* OBJ_QueryInterface ------------------------------------------------------ */

BOOL
FQueryInterface(int wType, REFIID riid)
{
    REFIID *priid = rgpriid[wType];

    while (1)
    {
        if (IsEqualGUID(riid, *priid))
            return (TRUE);

        if (*priid == &IID_IUnknown)
            break;

        priid += 1;
    }

    return (FALSE);
}

STDMETHODIMP
OBJ_QueryInterface(POBJ pobj, REFIID riid, LPVOID * ppvObj)
{
    if (    OBJ_IsInvalid(pobj, QueryInterface)
        ||  IsBadReadPtr(riid, sizeof(IID))
        ||  IsBadWritePtr(ppvObj, sizeof(LPVOID)))
        return ResultFromScode(E_INVALIDARG);

    /* Even if an error is returned, must zero *ppvObj */
    *ppvObj = 0;

    if (FQueryInterface(pobj->wType, riid))
    {
        UlAddRef(pobj);
        *ppvObj = pobj;
        return (0);
    }

    return (ResultFromScode(E_NOINTERFACE));
}

/* OBJ_AddRef -------------------------------------------------------------- */

STDMETHODIMP_(ULONG) OBJ_AddRef(POBJ pobj)
{
    LONG cRef;

    if (OBJ_IsInvalid(pobj, AddRef))
    {
        TraceSz1("Sample MS: OBJ_AddRef(pobj=%08lX): Object is invalid and "
            "is being ignored", pobj);
        return (0);
    }

    OBJ_EnterCriticalSection(pobj);

#ifdef DEBUG
    if (pobj->wType == OT_MSGSTORE)
        Assert(!OBJ_TestFlag(pobj, MSF_BEINGDESTROYED));
#endif

    AssertSz1(      pobj->wType == OT_MSGSTORE
              ||    pobj->cRef >= 1, "OBJ_AddRef(): Bogus cRef (%08lX)", pobj->cRef);

    cRef = ++pobj->cRef;

    OBJ_LeaveCriticalSection(pobj);

    return (cRef);
}

/* OBJ_Release ------------------------------------------------------------- */

/* Used by Message Store, Message, and Attachment objects. All other objects */
/* have their own implementations of Release. */

STDMETHODIMP_(ULONG) OBJ_Release(POBJ pobj)
{
    LONG cRef;

    if (!pobj)
        return (0);

    if (OBJ_IsInvalid(pobj, Release))
    {
        TraceSz1("SampleMS: OBJ_Release(pobj=%08lX): Object is invalid and is "
            "being ignored", pobj);
        return (0);
    }

    OBJ_EnterCriticalSection(pobj);

    AssertSz(pobj->cRef > 0, "OBJ_Release(): Too many releases");

    cRef = --pobj->cRef;

    if (cRef == 0)
    {
        if (pobj->wType != OT_MSGSTORE)
            pobj->lpVtbl = 0;

        if (pobj->pobjHead == 0)
        {
            OBJ_Destroy(pobj);
            return (0);
        }
    }

    OBJ_LeaveCriticalSection(pobj);

    return (cRef);
}

/* OBJ_Enqueue / OBJ_Dequeue / OBJ_Destroy --------------------------------- */

void OBJ_Enqueue(POBJ pobj, POBJ pobjParent)
{
    pobj->pobjParent = pobjParent;
    pobj->pobjNext = pobjParent->pobjHead;
    pobjParent->pobjHead = pobj;
}

void OBJ_Dequeue(POBJ pobj)
{
    if (pobj->pobjParent != NULL)
    {
        POBJ *ppobj = &pobj->pobjParent->pobjHead;

        while (TRUE)
        {
            POBJ pobjCur = *ppobj;

            if (pobjCur == NULL)
                break;

            if (pobjCur == pobj)
            {
                *ppobj = pobj->pobjNext;
                break;
            }

            ppobj = &pobjCur->pobjNext;
        }

        pobj->pobjParent = 0;
    }
}

/*
 * OBJ_Destroy
 *
 * Destroy an object. If this object was the last thing causing the parent to
 * exist, then we should destroy the parent, and so on up the chain. There are
 * two actual critical sections in the sample store. One is in the msp (message
 * store provider) object. The other is in the msl (message store logon) object.
 * All other objects in the sample store contain a pointer to the msl object's
 * critical section (they all share it). When we arrive at this routine, we
 * should have the object's critical section locked, i.e., the msl critical
 * section should be locked. That's why there are calls to leave a critical
 * section that aren't balanced with an enter.
 */

void OBJ_Destroy(POBJ pobj)
{
    PIMS pims;
    POBJ pobjParent;
    LPFNNEUTER lpfnNeuter;
    LPMAPISUP psup;

    pims = pobj->pims;

    while (1)
    {
        /* Call a routine to make the object free any memory */
        /* or other structures it has. (We call this "neutering" the object.) */

        if ((lpfnNeuter = rgfnNeuter[pobj->wType]) != 0)
            lpfnNeuter(pobj);

        pobjParent = pobj->pobjParent;

        if (pobj == (POBJ) pims)
        {
            if (pobjParent != NULL)
            {
                /* The parent in this case is the msp (message store provider) */
                /* object. We need to get its critical section in order to */
                /* safely dequeue the message store object. */

                OBJ_EnterCriticalSection(pobjParent);
                OBJ_Dequeue(pobj);
                OBJ_LeaveCriticalSection(pobjParent);
            }

            pobjParent = (POBJ) pims->pmsl;
            psup = pims->psup;

            pobj->lpVtbl = 0;
            LMFree(&pims->lmr, pobj);

            /* This leave balances the enter in the calling function. */
            /* Since we just freed the message store object, we can't leave */
            /* critical section using that pointer. Therefore, use the msl */
            /* (message store logon) object's critical section instead. They */
            /* are the same (see header comment at top of function). */

            OBJ_LeaveCriticalSection(pobjParent);

            UlRelease(psup);    /* do this last */
            break;
        }

        OBJ_Dequeue(pobj);

        pobj->lpVtbl = 0;
        LMFree(&pims->lmr, pobj);

        pobj = pobjParent;

        if (pobj == 0 || pobj->cRef || pobj->pobjHead)
        {
            /* This leave balances the enter in the calling function. */

            OBJ_LeaveCriticalSection((POBJ) pims);
            break;
        }
    }
}


MSPRC.H

/*
 *  M S P R C . H
 *
 *  Resource definitions for the Sample Message Store Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* EditText IDs */

#define IDE_ROOT_PATH           106
#define IDE_ACCOUNT_PASSWORD    107

/* CheckBox IDs */

#define IDC_CREATE              121
#define IDC_REMEMBER            122

/* Static Text IDs */
#define TEXT_PATH               141

/* String IDs */

#define IDS_SUCCESS                     0
#define IDS_NO_ACCESS                   1
#define IDS_NOINTERFACE                 2
#define IDS_INVALIDARG                  3
#define IDS_CALL_FAILED                 4
#define IDS_NOT_FOUND                   5
#define IDS_NO_SUPPORT                  6
#define IDS_ERRORS_RETURNED             7
#define IDS_PARTIAL_COMPLETION          8
#define IDS_BAD_CHARWIDTH               9
#define IDS_BAD_VALUE                   10
#define IDS_BUSY                        11
#define IDS_COLLISION                   12
#define IDS_COMPUTED                    13
#define IDS_CORRUPT_DATA                14
#define IDS_CORRUPT_STORE               15
#define IDS_DISK_ERROR                  16
#define IDS_HAS_FOLDERS                 17
#define IDS_HAS_MESSAGES                18
#define IDS_INVALID_ENTRYID             19
#define IDS_INVALID_OBJECT              20
#define IDS_LOGON_FAILED                21
#define IDS_NETWORK_ERROR               22
#define IDS_NON_STANDARD                23
#define IDS_NOT_ENOUGH_DISK             24
#define IDS_NOT_ENOUGH_MEMORY           25
#define IDS_NOT_ENOUGH_RESOURCES        26
#define IDS_NOT_IN_QUEUE                27
#define IDS_OBJECT_CHANGED              28
#define IDS_OBJECT_DELETED              29
#define IDS_STRING_TOO_LONG             30
#define IDS_SUBMITTED                   31
#define IDS_TOO_BIG                     32
#define IDS_UNABLE_TO_ABORT             33
#define IDS_UNCONFIGURED                34
#define IDS_UNEXPECTED_TYPE             35
#define IDS_UNKNOWN_FLAGS               36
#define IDS_USER_CANCEL                 37
#define IDS_VERSION                     38


MSPRFS.C

/*
 *  M S P R F S . C
 *
 *  Code for implementing Get/SetReceiveFolder for the Message
 *  Store object.
 *
 *  Hungarian shorthand:
 *      To avoid excessively long identifier names, throughout this
 *      file, RFS is used to mean "Receive Folder Settings", and RFN
 *      is used to mean an RFS Node.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

/* Manifest constants */

TCHAR szRFSStreamName[] = TEXT("RFS_STREAM");

/* GRoup of Flags (grf):
 *
 * grfStorageOpen:      Flags used to open a read/write OLE IStorage object.
 * grfStorageOpenRO:    Flags used to open a read only OLE IStorage object.
 * grfStorageCreate:    Flags used to create an OLE IStorage object.
 * grfStreamOpen:       Flags used to open a read/write OLE IStream object.
 * grfStreamOpenRO:     Flags used to open a read only OLE IStream object.
 * grfStreamCreate:     Flags used to create an OLE IStream object.
 *
 * See the OLE 2 Programmer's Reference for details on these flags.
 */

#define grfStorageOpen      STGM_READWRITE | STGM_SHARE_EXCLUSIVE | \
                                STGM_TRANSACTED
#define grfStorageOpenRO    STGM_READ | STGM_SHARE_EXCLUSIVE | \
                                STGM_TRANSACTED
#define grfStorageCreate    grfStorageOpen | STGM_FAILIFTHERE | STGM_CREATE

#define grfStreamOpen       STGM_SHARE_EXCLUSIVE | STGM_READWRITE
#define grfStreamOpenRO     STGM_SHARE_EXCLUSIVE | STGM_READ
#define grfStreamCreate     grfStreamOpen | STGM_FAILIFTHERE

/* Function prototypes */

static HRESULT OpenRFSStream(PRFS prfs, BOOL fModify, IStream **lppstream,
    LPSTORAGE *lppstg);
static void CloseRFSStream(IStream * lpstream, LPSTORAGE lpstg);

/*
 *  Exported functions
 */

/*
 *  FIsValidMessageClass
 *
 *  Purpose:
 *      Checks to see if a message class is valid.  A valid message
 *      class is defined to be a series of one or more
 *      period-delimited tokens with each token being a series of
 *      one or more ASCII characters in the range 32-126
 *      (inclusive) excluding period.  Note that this definition
 *      excludes message classes with a leading or trailing
 *      period, or two or more consecutive periods, because this
 *      would imply the existence of a zero-length token.
 *
 *      We put this function in the RFS module because dealing with
 *      receive folders is the primary place in the Sample Store where we
 *      care about message class.
 *
 *  Arguments:
 *      szMessageClass  String identifying the message class.
 *
 *  Returns:
 *      BOOL.  TRUE if szMessage is valid, FALSE if not.
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
BOOL FIsValidMessageClass(LPTSTR szMessageClass)
{
    TCHAR *pch = szMessageClass;
    BOOL fWasPeriod = TRUE;

    if (szMessageClass && IsBadStringPtr(szMessageClass, (UINT) -1))
        return FALSE;

    /* Handle the default message class */

    if (!szMessageClass || *szMessageClass == '\0')
        return TRUE;

    /* disallow things:bad chars and cases where */
    /* period is not a delim(.1, 1., and 1. .1 bad) */
    while (*pch)
    {
        if (*pch < 32 || *pch > 126)
            return FALSE;
        if (*pch == '.')
        {
            if (fWasPeriod)
                return FALSE;
            fWasPeriod = TRUE;
        }
        else
            fWasPeriod = FALSE;
        pch++;
    }

    return !fWasPeriod;
}

/*
 *  OpenRFS
 *
 *  Purpose:
 *      Given an OLE2 storage object, opens a stream on it and
 *      prepares it for handling receive folder settings.  OpenRFS
 *      returns to the caller a pointer to the RFS structure which
 *      is then used for access to the settings.  The stream format
 *      of the receive folder settings is extremely simple:  the
 *      first ULONG is a count of the number of RFNs in the
 *      stream, and the nodes themselves follow sequentially
 *      afterward.  A node on disk is not the same as an RFN in
 *      memory.  On disk, it is a variable-sized structure
 *      containing a ULONG which is the size (in bytes) of the
 *      node not including this field, then a ULONG which is the
 *      length (in bytes) of a
 *      string containing the message class (NULL inclusive), which
 *      immediately follows.  After that is a ULONG which is the
 *      size (in bytes) of a string containing the relative path
 *      name of the receive folder (NULL inclusive), which also
 *      immediately follows.  Visually, a node looks like the
 *      following:
 *
 *          +--------------------+
 *          | ULONG cbNode       |
 *          +--------------------+
 *          | ULONG cbClass      |
 *          +--------------------+
 *          | TCHAR szClass[]    |
 *          |   .                |
 *          |   .                |
 *          |   .                |
 *          +--------------------+
 *          | ULONG cbPath       |
 *          +--------------------+
 *          | TCHAR szPath[]     |
 *          |   .                |
 *          |   .                |
 *          |   .                |
 *          +--------------------+
 *
 *      The size of the message class name, cbClass, will always be
 *      > 0 for "valid" nodes (the default message class will be a
 *      NULL string of size 1 TCHAR).  Thus, a value of 0 for
 *      cbClass will signify a "free" node.  Free nodes are created
 *      in the normal use of the stream by DeleteRFN, which
 *      SetReceiveFolder calls, and are removed at close time (see
 *      CloseRFS, below).
 *
 *      Note that, because string lengths are byte-sized but the
 *      strings themselves are made of TCHARs, translation between
 *      BYTE and TCHAR sizes must be done.
 *
 *  Arguments:
 *      szStoreRoot Full path to the sample store "root"
 *                      directory.
 *      szFile      Relative path name of docfile containing
 *                      receive folder settings.
 *      ulFlags         Flags.  The following are defined:
 *                      RFS_CREATE  Create the docfile containing
 *                                  receive folder settings
 *                                  (default opens existing).
 *      pprfs           Location in which to return a pointer to
 *                      the newly created RFS structure.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various.
 */
HRESULT OpenRFS(LPTSTR szStoreRoot, LPTSTR szFile, ULONG ulFlags, PRFS *pprfs)
{
    HRESULT hr = hrSuccess;
    HRESULT hrStg = hrSuccess;
    SCODE sc;
    LPTSTR szFullPath = NULL;
    ULONG cRFN = 0L;
    LPSTORAGE lpstg = NULL;
    IStream *lpstream = NULL;
    PRFS prfs = NULL;

#ifdef _WIN32
    OLE_CHAR szOle[MAX_PATH];
    int cbOle = 0L;

#else
    OLE_CHAR *szOle;

#endif
    LARGE_INTEGER liBeg;

    /* initial default receive folder settings */
    ULONG cInitRFNs = 1;        /* number of default nodes */

#pragma pack(1)
    struct RFN
    {
        ULONG cbNode;
        ULONG cbClass;
        TCHAR szClass;
        ULONG cbFolder;
        TCHAR szFolderPath;
    } DefaultNode =
    {
        (2 * sizeof(ULONG)) + (2 * sizeof(TCHAR)),
        sizeof(TCHAR),
        '\0',
        sizeof(TCHAR),
        '\0'
    };
#pragma pack()

    LISet32(liBeg, 0);  /* This is an OLE initializer macro */

    AssertSz(szStoreRoot, "Bad szStoreRoot");
    AssertSz(szFile, "Bad szFile");
    AssertSz(pprfs, "Bad pprfs");

    hr = HrAppendPath(szStoreRoot, szFile, &szFullPath);
    if (hr != hrSuccess)
        goto exit;

    sc = ScAllocZ(sizeof(RFS), &prfs);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    prfs->szFile = szFullPath;

#ifdef _WIN32
    cbOle = 1 + lstrlen(szFullPath);
    Assert(cbOle < MAX_PATH);
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szFullPath, cbOle, szOle, cbOle);
#else
    szOle = szFullPath;
#endif

    if (ulFlags & RFS_CREATE)
    {
        hrStg = StgCreateDocfile(szOle, grfStorageCreate, 0, &lpstg);

        if (hrStg != hrSuccess)
            goto stg_err;

#ifdef _WIN32
        cbOle = 1 + lstrlen(szRFSStreamName);
        Assert(cbOle < MAX_PATH);
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szRFSStreamName, cbOle,
                szOle, cbOle);
#else
        szOle = szRFSStreamName;
#endif
        hrStg = lpstg->lpVtbl->CreateStream(lpstg, szOle, grfStreamCreate,
                0, 0, &lpstream);
        if (hrStg != hrSuccess)
            goto stg_err;

        /* Initialize the newly created stream */
        hrStg = lpstream->lpVtbl->Seek(lpstream, liBeg, STREAM_SEEK_SET, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        /* add the default RFS setting of the root (path = '\0') */
        hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &cInitRFNs,
                sizeof cInitRFNs, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &DefaultNode,
                DefaultNode.cbNode + sizeof(ULONG), NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        /* Commit docfile changes.  If we don't do this now, the file on  */
        /* disk will NOT be a docfile (i.e. OLE2 will not recognize it as */
        /* a docfile) if opened again with no other changes made to it.   */

        hrStg = lpstg->lpVtbl->Commit(lpstg, 0);
        if (hrStg != hrSuccess)
            goto stg_err;
    }
    else        /* Open an existing stream */
    {
        hr = OpenRFSStream(prfs, FALSE, &lpstream, &lpstg);
        if (hr != hrSuccess)
            goto exit;

        hrStg = lpstream->lpVtbl->Seek(lpstream, liBeg, STREAM_SEEK_SET, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cRFN,
                sizeof cRFN, NULL);

        /* fall through to error handler */
    }

stg_err:
    if (hrStg != hrSuccess)
        hr = ResultFromScode(MapStorageSCode(GetScode(hrStg)));

exit:
    if (lpstream)
    {
        CloseRFSStream(lpstream, lpstg);
        lpstg = NULL;
    }

    if (hr != hrSuccess)
    {
        FreeNull(szFullPath);
        FreeNull(prfs);
        UlRelease(lpstg);
    }
    else
        *pprfs = prfs;

    DebugTraceResult(OpenRFS, hr);
    return hr;
}

/*
 *  GetRFN
 *
 *  Purpose:
 *      Returns an RFN containing the receive folder setting
 *      for the message class that is passed in as a parameter.  If
 *      there is not a receive folder setting for this particular
 *      message class, the "best match" is returned, with best
 *      match being defined in GetReceiveFolder (see msgstobj.c).
 *      the way we measure this best match is to have a match index
 *      which is incremented every time a section of the message
 *      class is matched.  These values begin at 2 because 1 is
 *      reserved for the default message class.  For example, the
 *      message class "IPM.Note.Phone" matches against the
 *      following receive folder settings in the following way:
 *
 *          "" (default)        1
 *          "IPM"               2
 *          "IPM.Note"          3
 *          "IPM.Note.Phone"    4
 *          "IPC"               0
 *
 *  Arguments:
 *      prfs            Pointer to the RFS context.
 *      szClassName     Name of the message class for which to
 *                      search for a receive folder setting.
 *      pprfn           Address of location in which to return a
 *                      pointer to an RFN structure containing
 *                      the message class and folder name of the
 *                      "best match" receive folder setting.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various.
 *
 *  Notes:
 *      Use FreeRFN() to release the memory of the returned
 *      RFN structure in *pprfn.
 */
HRESULT GetRFN(PRFS prfs, LPTSTR szClassName, PRFN *pprfn)
{
    HRESULT hr = hrSuccess;
    HRESULT hrStg = hrSuccess;
    SCODE sc = S_OK;
    LPSTORAGE lpstg = NULL;
    LPSTREAM lpstream = NULL;
    PRFN prfn = NULL;
    ULONG ibNextNode = 0L;
    ULONG ibMatchNode = 0L;
    ULONG cbNode = 0L;
    ULONG cbClass = 0L;
    ULONG cRFN = 0L;
    UINT uiMatchLvl = 0;
    UINT ui = 0;
    TCHAR rgch[1024];
    LARGE_INTEGER li;
    ULONG cbCls = 0L;
    ULONG cbName = 0L;

    AssertSz(prfs, "Bad prfs");
    AssertSz(szClassName, "Bad szClassName");
    AssertSz(pprfn, "Bad pprfn");

    hr = OpenRFSStream(prfs, FALSE, &lpstream, &lpstg);
    if (hr != hrSuccess)
        goto exit;

    /* Read the count of RFS nodes from the stream. */

    LISet32(li, 0);     /* This is an OLE initializer macro */
    hrStg = lpstream->lpVtbl->Seek(lpstream, li, STREAM_SEEK_SET, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cRFN,
            sizeof cRFN, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* Loop over nodes, looking for the best message class match */

    for (ui = 0, ibNextNode = sizeof cRFN; ui < cRFN; ui++)
    {
        /* Set seek pointer to beginning of this node */

        LISet32(li, ibNextNode);
        hrStg = lpstream->lpVtbl->Seek(lpstream, li, STREAM_SEEK_SET, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        /* Need to have an absolute index to the NEXT node. */
        /* Remember:  cbNode is not self-inclusive, so the  */
        /* next node is (cbNode + sizeof cbNode) from the   */
        /* current node.                                    */

        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbNode,
                sizeof cbNode, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        ibNextNode += cbNode + sizeof cbNode;

        /* Get and compare message class */

        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbClass,
                sizeof cbClass, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        AssertSz(sizeof rgch >= cbClass, "Message class too big!");

        if (cbClass > 0L)       /* If it's not a free node */
        {
            UINT uiMatchCur = 0;

            hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) rgch,
                    cbClass, NULL);
            if (hrStg != hrSuccess)
                goto stg_err;

            if (rgch[0] == '\0')
            {
                uiMatchCur = 1;
            }
            else
                /* count the matching message class sections */
            {
                TCHAR *pch1 = rgch;
                TCHAR *pch1Ahead = NULL;
                TCHAR *pch2 = szClassName;
                TCHAR *pch2Ahead = NULL;

                /* if matching against a class that is less refined than */
                /* what we are searching for return 0 */
                if (lstrlen(szClassName) >= lstrlen(rgch))
                {
                    while (*pch1 && *pch2)
                    {
                        for (pch1Ahead = pch1 + 1; *pch1Ahead &&
                            *pch1Ahead != '.'; pch1Ahead++)
                            ;
                        for (pch2Ahead = pch2 + 1; *pch2Ahead &&
                            *pch2Ahead != '.'; pch2Ahead++)
                            ;
                        if (pch1Ahead - pch1 == pch2Ahead - pch2 &&
                            !memcmp(pch1, pch2, pch1Ahead - pch1))
                        {
                            uiMatchCur++;
                            pch1 = pch1Ahead;
                            pch2 = pch2Ahead;
                        }
                        else
                            break;
                    }
                }
                /* We want to match a "real" setting higher than the */
                /* default, so we increment a real match to be > 1.  */

                if (uiMatchCur > 0)
                {
                    uiMatchCur++;
                }
            }

            if (uiMatchCur > uiMatchLvl)
            {
                /* Here we set ibMatchNode to be the absolute index of */
                /* the cbClass member of the node (NOT the cbNode      */
                /* member.  When we seek back to this position, we can */
                /* begin reading the cbClass immediately (see below).  */

                ibMatchNode = ibNextNode - cbNode;
                uiMatchLvl = uiMatchCur;
            }
        }
    }

    if (uiMatchLvl == 0)
    {
        hr = ResultFromScode(MAPI_E_NOT_FOUND);
        goto exit;
    }

    /* Set the return variable w/best match */

    sc = ScAllocZ(sizeof(RFN), (PPV) &prfn);
    if (sc != S_OK)
        goto sc_err;

    /* Goto best match node, but seek pointer will be past cbNode */

    LISet32(li, ibMatchNode);
    hrStg = lpstream->lpVtbl->Seek(lpstream, li, STREAM_SEEK_SET, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* Read in class name */

    hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbCls,
            sizeof cbCls, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    sc = ScAlloc(cbCls, (PPV) &prfn->szClass);
    if (sc != S_OK)
        goto sc_err;

    hrStg = lpstream->lpVtbl->Read(lpstream,
            (LPVOID) prfn->szClass, cbCls, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* Read in folder name */

    hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbName,
            sizeof cbName, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    sc = ScAlloc(cbName, (PPV) &prfn->szName);
    if (sc != S_OK)
        goto sc_err;

    hrStg = lpstream->lpVtbl->Read(lpstream,
            (LPVOID) prfn->szName, cbName, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

sc_err:
    if (sc != S_OK)
    {
        Assert(hr == hrSuccess);
        Assert(hrStg == hrSuccess);
        hr = ResultFromScode(sc);
    }

stg_err:
    if (hrStg != hrSuccess)
    {
        Assert(sc == S_OK);
        Assert(hr == hrSuccess);
        hr = ResultFromScode(MapStorageSCode(GetScode(hrStg)));
    }

exit:
    if (lpstream)
        CloseRFSStream(lpstream, lpstg);

    if (hr != hrSuccess)
        FreeRFN(prfn);
    else
        *pprfn = prfn;

    DebugTraceResult(GetRFN, hr);
    return hr;
}

/*
 -  FreeRFN
 -
 *  Release the memory of an RFN allocated and returned
 *  by the GetRFN() procedure.
 */
void FreeRFN(PRFN prfn)
{
    if (prfn)
    {
        (void)FreeNull((LPVOID) prfn->szClass);
        (void)FreeNull((LPVOID) prfn->szName);
        (void)FreeNull((LPVOID) prfn);
    }
}

/*
 *  DeleteRFN
 *
 *  Purpose:
 *      Delete the receive folder setting associated with a
 *      particular message class.  We do this by "zeroing out" the
 *      node on disk, rather than actually removing it and
 *      compacting the stream.  We can easily zero out the node
 *      once we've found the right one by setting the length of the
 *      message class string contained in it to be zero (an invalid
 *      value).
 *
 *  Arguments:
 *      prfs            Pointer to the RFS context to use.
 *      szClassName     Buffer containing the name of the message
 *                      class for which to remove the receive
 *                      folder setting.  We do a linear search
 *                      through the stream to find the node on disk
 *                      with a matching message class.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various.
 */
HRESULT DeleteRFN(PRFS prfs, LPTSTR szClassName)
{
    HRESULT hr = hrSuccess;
    HRESULT hrStg = hrSuccess;
    LPSTORAGE lpstg = NULL;
    IStream *lpstream = NULL;
    ULONG ibNextNode = 0L;
    ULONG cbNode = 0L;
    ULONG cbClass = 0L;
    ULONG cRFN = 0L;
    UINT ui = 0;
    TCHAR rgch[1024];
    LARGE_INTEGER li;

    AssertSz(prfs, "Bad prfs");
    AssertSz(szClassName, "Bad szClassName");

    hr = OpenRFSStream(prfs, TRUE, &lpstream, &lpstg);
    if (hr != hrSuccess)
        goto exit;

    /* Read the count of RFS nodes from the stream. */

    LISet32(li, 0);     /* This is an OLE initializer macro */
    hrStg = lpstream->lpVtbl->Seek(lpstream, li, STREAM_SEEK_SET, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cRFN,
            sizeof cRFN, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* Loop over nodes, looking for a message class match */

    for (ui = 0, ibNextNode = sizeof cRFN; ui < cRFN; ui++)
    {
        /* Set seek pointer to beginning of first node */

        LISet32(li, ibNextNode);
        hrStg = lpstream->lpVtbl->Seek(lpstream, li, STREAM_SEEK_SET, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        /* Need to have an absolute index to the NEXT node. */
        /* Remember:  cbNode is not self-inclusive, so the  */
        /* next node is (cbNode + sizeof cbNode) from the   */
        /* current node.                                    */

        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbNode,
                sizeof cbNode, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        ibNextNode += cbNode + sizeof cbNode;

        /* Get and compare message class */

        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cbClass,
                sizeof cbClass, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        AssertSz(sizeof rgch >= cbClass * sizeof(TCHAR),
            "Message class too big!");

        if (cbClass > 0L)       /* If it's not a free node */
        {
            hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) rgch,
                    cbClass, NULL);
            if (hrStg != hrSuccess)
                goto stg_err;

            if (cbClass == Cbtszsize(szClassName)
                && !memcmp(szClassName, rgch, (UINT) cbClass))
            {
                LONG ibClass = 0L;

                /* Seek back to cbClass */

                ibClass -= (LONG) (cbClass + sizeof cbClass);
                LISet32(li, ibClass);
                hrStg = lpstream->lpVtbl->Seek(lpstream, li,
                        STREAM_SEEK_CUR, NULL);
                if (hrStg != hrSuccess)
                    goto stg_err;

                /* Zero out the node */

                cbClass = 0L;
                hrStg = lpstream->lpVtbl->Write(lpstream,
                        (LPVOID) &cbClass, sizeof cbClass, NULL);
                if (hrStg != hrSuccess)
                    goto stg_err;

                /* Commit the change */

                hrStg = lpstg->lpVtbl->Commit(lpstg, 0);
                if (hrStg != hrSuccess)
                    goto stg_err;

                break;
            }
        }
    }

stg_err:
    if (hrStg)
        hr = ResultFromScode(MapStorageSCode(GetScode(hrStg)));

exit:
    if (lpstream)
        CloseRFSStream(lpstream, lpstg);

    DebugTraceResult(DeleteRFN, hr);
    return hr;
}

/*
 *  AddRFN
 *
 *  Purpose:
 *      Adds a node (on disk) to the stream that holds receive
 *      folder settings for a message store.  Does this by creating
 *      a new node at the current End-Of-Stream (at the end of all
 *      other nodes).
 *
 *  Arguments:
 *      prfs        Pointer to the receive folder storage context.
 *      prfn    Pointer to the new node to add.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various.
 */
HRESULT AddRFN(PRFS prfs, PRFN prfn)
{
    HRESULT hr = hrSuccess;
    HRESULT hrStg = hrSuccess;
    LPSTORAGE lpstg = NULL;
    IStream *lpstream = NULL;
    UINT ui = 0;
    ULONG cb = 0L;
    ULONG cbNode = 0L;
    ULONG cbClass = 0L;
    ULONG cbName = 0L;
    ULONG cRFN = 0L;
    LARGE_INTEGER liEOS;

    AssertSz(prfs, "Bad prfs");
    AssertSz(prfn, "Bad prfn");
    AssertSz(prfn->szClass, "Bad prfn->szClass");
    AssertSz(prfn->szName, "Bad prfn->szName");

    hr = OpenRFSStream(prfs, TRUE, &lpstream, &lpstg);
    if (hr != hrSuccess)
        goto exit;

    /* Find the end of the stream.  Strictly speaking, we can't just seek */
    /* the current End-Of-Stream (what the IStream thinks is it's current */
    /* EOS), because we really want to be at the end of the last node,    */
    /* and there may have been stuff written after (from a failed write). */
    /* First, read the count of RFS nodes from the stream. */

    LISet32(liEOS, 0);      /* This is an OLE initializer macro */
    hrStg = lpstream->lpVtbl->Seek(lpstream, liEOS, STREAM_SEEK_SET, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cRFN,
            sizeof cRFN, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    for (ui = 0; ui < cRFN; ui++)
    {
        hrStg = lpstream->lpVtbl->Read(lpstream, (LPVOID) &cb,
                sizeof cb, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;

        LISet32(liEOS, (LONG) cb);

        hrStg = lpstream->lpVtbl->Seek(lpstream, liEOS, STREAM_SEEK_CUR, NULL);
        if (hrStg != hrSuccess)
            goto stg_err;
    }

    /* Write out the node */

    /* Size of node: length of 2 strings + 2 NULL characters + 2 times */
    /* the size of the space needed to hold the string lengths.        */

    cbClass = Cbtszsize(prfn->szClass);
    cbName = Cbtszsize(prfn->szName);
    cbNode = 2 * sizeof(ULONG) + cbClass + cbName;

    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &cbNode,
            sizeof cbNode, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &cbClass,
            sizeof cbClass, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) prfn->szClass,
            cbClass, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &cbName,
            sizeof cbName, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) prfn->szName,
            cbName, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* Keep cRFN, the in-memory and on-disk */
    /* copies, in sync with each other.     */

    LISet32(liEOS, 0L);
    hrStg = lpstream->lpVtbl->Seek(lpstream, liEOS, STREAM_SEEK_SET, NULL);
    if (hrStg != hrSuccess)
        goto stg_err;

    cRFN++;
    hrStg = lpstream->lpVtbl->Write(lpstream, (LPVOID) &cRFN,
            sizeof cRFN, NULL);
    if (hrStg != hrSuccess)
    {
        cRFN--;
        goto stg_err;
    }

    /* Commit the change */

    hrStg = lpstg->lpVtbl->Commit(lpstg, 0);
    /* if ( hrStg ), fall through to stg_err */

stg_err:
    if (hrStg)
        hr = ResultFromScode(MapStorageSCode(GetScode(hrStg)));

exit:
    if (lpstream)
        CloseRFSStream(lpstream, lpstg);

    DebugTraceResult(AddRFN, hr);
    return hr;
}

/*
 *  CloseRFS
 *
 *  Purpose:
 *      Frees and invalidates an open context for accessing receive
 *      folder settings.
 *
 *  Arguments:
 *      prfs        Pointer to the object to close.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various.
 */
HRESULT CloseRFS(PRFS prfs)
{
    HRESULT hr = hrSuccess;

    AssertSz(prfs, "Bad prfs");

    FreeNull(prfs->szFile);
    FreeNull(prfs);

    DebugTraceResult(CloseRFS, hr);
    return hr;
}

/*
 *  Internal functions
 */

/*
 *  OpenRFSStream
 *
 *  Purpose:
 *      Open the stream within a docfile that contains receive
 *      folder settings.
 *
 *  Arguments:
 *      prfs        Receive folder settings context.
 *      fModify     TRUE indicates the caller wants write access.
 *      lppstream   Address in which to return a pointer to the
 *                  newly opened stream.
 *
 *  Returns:
 *      HRESULT
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      Various storage errors.
 */
static HRESULT OpenRFSStream(PRFS prfs, BOOL fModify, IStream **lppstream,
    LPSTORAGE *lppstg)
{
    HRESULT hr = hrSuccess;

HRESULT hrStg = hrSuccess;
    DWORD grfMode;

#ifdef _WIN32
    OLE_CHAR szOle[MAX_PATH];
    int cbOle = 0L;
#else
    OLE_CHAR *szOle;
#endif

    LPSTORAGE lpstg = NULL;
    IStream *lpstream = NULL;

    AssertSz(prfs, "Bad prfs");
    AssertSz(lppstream, "Bad lppstream");

#ifdef _WIN32
    cbOle = 1 + lstrlen(prfs->szFile);
    Assert(cbOle < MAX_PATH);
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, prfs->szFile, cbOle, szOle, cbOle);
#else
    szOle = prfs->szFile;
#endif

    if (fModify)
        grfMode = STGM_SHARE_EXCLUSIVE | STGM_READWRITE;
    else
        grfMode = STGM_SHARE_EXCLUSIVE | STGM_READ;

    hrStg = StgOpenStorage(szOle, NULL, grfMode | STGM_TRANSACTED, NULL,
            0, &lpstg);
    if (hrStg != hrSuccess)
        goto stg_err;

#ifdef _WIN32
    cbOle = 1 + lstrlen(szRFSStreamName);
    Assert(cbOle < MAX_PATH);
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szRFSStreamName, cbOle, szOle, cbOle);
#else
    szOle = szRFSStreamName;
#endif

    hrStg = lpstg->lpVtbl->OpenStream(lpstg, szOle, NULL, grfMode, 0, &lpstream);
    if (hrStg != hrSuccess)
        goto stg_err;

    /* WARNING:  If any code is added between here and the error handler */
    /* that can fail, a check in the error handler must be added to free */
    /* the open stream in the event of an error.                         */

    *lppstream = lpstream;
    *lppstg = lpstg;

stg_err:
    if (hrStg != hrSuccess)
        hr = ResultFromScode(MapStorageSCode(GetScode(hrStg)));

    if (hr != hrSuccess)
        UlRelease(lpstg);

    DebugTraceResult(OpenRFSStream, hr);
    return hr;
}

/*
 *  CloseRFSStream
 *
 *  Purpose:
 *      Close the stream within a docfile that holds receive folder
 *      settings.
 *
 *  Arguments:
 *      lpstream    Pointer to the stream.
 *      lpstg       Pointer to the storage instance in which this
 *                  stream resides.
 *
 *  Returns:
 *      void
 *
 *  Side effects:
 *      None.
 *
 *  Errors:
 *      None.
 */
static void CloseRFSStream(IStream *lpstream, LPSTORAGE lpstg)
{
    AssertSz(lpstream, "Bad lpstream");
    AssertSz(lpstg, "Bad lpstg");

    NFSideAssertSz(UlRelease(lpstream) == 0L, "lpstream not released");
    NFSideAssertSz(UlRelease(lpstg) == 0L, "lpstg not released");

    return;
}


MSPTBL.C

/*
 *  M S P T B L . C
 *
 *  Functions to manage a hierarchy and contents tables cached on disk.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "msp.h"

static SCODE ScCreateFile(LPTSTR szFile, ULONG ulAccess, ULONG ulShare,
    ULONG ulCreate, HANDLE * lphFile);
static HRESULT HrOpenTblFileRetry(LPTSTR szFile, ULONG ulAccess, ULONG ulShare,
    ULONG ulCreate, HANDLE * lphFile);
static HRESULT HrNewCounts(PIFLD pifld, LPTABLEDATA lptbl);
static HRESULT HrReadBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToRead,
    BOOL * pfEOF);
static HRESULT HrWriteBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToWrite);
static HRESULT HrWriteRow(HANDLE hFile, LPSRow prw);
static VOID TranslateFileError(BOOL fSuccess, ULONG cbIn, ULONG cbOut,
    BOOL * pfEOF, SCODE * pscFile);
static HRESULT HrGetTime(LPSRow prw, FILETIME * pfiletime);
static HRESULT HrRemoveBadTableRows(LPTABLEDATA lptbl, PIFLD pifldParent,
    PIMS pims, BOOL * pfTableChanged);
static HRESULT HrAddMissingTableRows(LPTABLEDATA lptbl, PIFLD pifld,
    LPSTR szTemplate, LPSPropTagArray ptaga, BOOL * pfTableChanged);

/* format of a row of table data on disk (DRW) */
typedef struct _DRW
{
    ULONG   cbRow;
    SRow    rw;
    BYTE    ab[MAPI_DIM];
} DRW, *PDRW;

#define CbNewDRW(_cb)       (offsetof(DRW,ab) + (_cb))
#define CbDRW(_pdrw)        (offsetof(DRW,ab) + (UINT)((_pdrw)->cbRow))

/****************************************************************************
 * ScCreateFile
 *
 * Purpose      Open or create a file
 *
 * Parameters
 *  szFile          name of the file
 *  ulAccess        read/write access desired see CreateFile
 *  ulShare         sharing desired see CreateFile
 *  ulCreate        creation disposition see CreateFile
 *  lphFile         returns handle of open file, undefined if call fails
 *
 */
static SCODE
ScCreateFile(LPTSTR szFile, ULONG ulAccess, ULONG ulShare,
    ULONG ulCreate, HANDLE * lphFile)
{
    HANDLE hFile;
    SCODE sc = S_OK;

    hFile = CreateFile(szFile, ulAccess, ulShare, NULL,
        ulCreate, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        switch (GetLastError())
        {
        case ERROR_FILE_NOT_FOUND:
            sc = MAPI_E_NOT_FOUND;
            break;

        case ERROR_SHARING_VIOLATION:
        case ERROR_LOCK_VIOLATION:
            sc = MAPI_E_BUSY;
            break;

        default:
            sc = MAPI_E_NO_ACCESS;
            break;
        }
    }
    else
    {
        AssertSz(!IsBadWritePtr(lphFile, sizeof(HANDLE)), "Bad parameter"
            " (lphFile) given to ScCreateFile");

        *lphFile = hFile;
    }

    DebugTraceSc(ScCreateFile, sc);
    return sc;
}

/*
 * HrOpenTblFileRetry
 *
 * Purpose      Open or create a file, but retry if it is busy
 *
 * Parameters
 *  szFile          name of the file
 *  ulAccess        read/write access desired see CreateFile
 *  ulShare         sharing desired see CreateFile
 *  ulCreate        creation disposition see CreateFile
 *  lphFile         returns handle of open file, undefined if call fails
 *
 */
static HRESULT HrOpenTblFileRetry(LPTSTR szFile, ULONG ulAccess, ULONG ulShare,
    ULONG ulCreate, HANDLE * lphFile)
{
    UINT iRetry;
    SCODE sc = S_OK;
    HANDLE hFile;

    iRetry = 0;
    while (TRUE)
    {
        sc = ScCreateFile(szFile, ulAccess, ulShare, ulCreate, &hFile);

        if (sc != MAPI_E_BUSY || ++iRetry >= NUM_RETRIES)
            break;

        Sleep(500);
    }

    if (sc == S_OK)
        *lphFile = hFile;

    #ifdef DEBUG
    if (iRetry >= NUM_RETRIES)
        TraceSz("HrOpenTblFileRetry: Failing open. Too many tries.");
    #endif

    DebugTraceSc(HrOpenTblFileRetry, sc);
    return ResultFromScode(sc);
}

/*
 * HrWriteCounts
 *
 *  Purpose
 *      This function writes the counts of messages and unread messages
 *      in a folder to the folder's property file. After the code
 *      validates what the counts should be, this function writes the
 *      counts so that they are correct.
 *
 *  Parameters
 *      pifld: A pointer to the folder object to update.
 *      ulMsgs: The number of messages in the folder.
 *      ulUnread: The number of unread messages in the folder.
 *
 */
static HRESULT HrWriteCounts(PIFLD pifld, ULONG ulMsgs, ULONG ulUnread)
{
    HRESULT hr;
    LPMESSAGE lpmsg = NULL;
    PLMR plmr = &pifld->pims->lmr;
    ULONG ulMsgsCopy = ulMsgs;      /* workaround - MS C code-gen bug */

    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, TRUE, &lpmsg);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetOneROProp(lpmsg, plmr, PR_CONTENT_COUNT, &ulMsgsCopy);
    if (hr != hrSuccess)
        goto exit;

    hr = HrSetOneROProp(lpmsg, plmr, PR_CONTENT_UNREAD, &ulUnread);
    if (hr != hrSuccess)
        goto exit;

    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);

exit:
    UlRelease(lpmsg);

    DebugTraceResult(HrWriteCounts, hr);
    return hr;
}

/*
 * HrNewCounts
 *
 * Purpose  Make the contents count and unread count of the folder in pifld
 *          agree with the data in the contents table lptbl
 *
 * Parameters
 *      pifld       the folder
 *      lptbl       the table
 */
static HRESULT HrNewCounts(PIFLD pifld, LPTABLEDATA lptbl)
{
    HRESULT hr = hrSuccess;
    ULONG cMessages = 0;
    ULONG cUnread = 0;
    LPSRow lpsRow = NULL;
    ULONG ulRow = 0;
    PLMR plmr = &pifld->pims->lmr;
    LPMESSAGE lpmsg = NULL;

    /* check each row in the table to see if the message has been read */

    while (TRUE)
    {
        LPSPropValue pval;
        LPSPropValue pvalMax;

        hr = lptbl->lpVtbl->HrEnumRow(lptbl, ulRow++, &lpsRow);
        if (hr != hrSuccess)
            goto exit;

        if (lpsRow == NULL)
            break;

        cMessages++;

        pval = lpsRow->lpProps;
        pvalMax = pval + lpsRow->cValues;

        /* check PR_MESSAGE_FLAGS to see if this message is unread */
        while (pval < pvalMax)
        {
            if (pval->ulPropTag == PR_MESSAGE_FLAGS)
            {
                if (!(pval->Value.l & MSGFLAG_READ))
                    cUnread++;
                break;
            }
            pval++;
        }

        LMFree(&pifld->pims->lmr, lpsRow);
        lpsRow = NULL;
    }

    hr = HrWriteCounts(pifld, cMessages, cUnread);

exit:
    LMFree(&pifld->pims->lmr, lpsRow);

    DebugTraceResult(HrNewCounts, hr);
    return hr;
}

/**********************************************************************
 * HrGetTableName
 *
 * Purpose
 *      Given an open object and the kind of table
 *      returns the full path name of the file that caches that table's data.
 *      Must be freed with FreeNull.
 *
 * Parameters
 *  pobj        object whose table is needed
 *  szEIDPath   EID path (may be NULL)
 *  szFileName  Name of the file that holds the table.
 *  lppszTable  pointer to storage for the path name of the table file
 */
HRESULT HrGetTableName(POBJ pobj, LPSTR szEIDPath, LPSTR szFileName,
    LPSTR *pszTable)
{
    LPTSTR szDir = NULL;        /* Full Path name of directory containing table */
    HRESULT hr = hrSuccess;
    PIMS pims = pobj->pims;

    Assert(!IsBadWritePtr(pszTable, sizeof(LPTSTR)));

    hr = HrFullPathName(pims->szStorePath, szEIDPath, NULL, &szDir);
    if (hr != hrSuccess)
        goto exit;

    /* get memory to hold the filename that contains the table. */
    /* Add 1 char for the backslash before the filename. */

    hr = HrAlloc(CCH_NAME * sizeof(TCHAR)
        + ((lstrlen(szDir) + 1) * sizeof(TCHAR)), (PPV) pszTable);
    if (hr != hrSuccess)
        goto exit;

    lstrcpy(*pszTable, szDir);
    lstrcat(*pszTable, "\\");
    lstrcat(*pszTable, szFileName);

exit:
    FreeNull(szDir);
    DebugTraceResult(HrGetTableName, hr);
    return hr;
}

/**********************************************************************
 * HrReadBytes
 *
 * Read a number of bytes from a file into a buffer, checking for errors and
 * end-of-file.
 *
 * Parameters:
 *
 * hFile:       File handle to read from.
 * lpBuffer:    Pointer to a buffer to read into. The buffer should be big
 *              enough to hold the incoming data.
 * cbToRead:    The number of bytes to read. Should be <= UINT_MAX.
 * pfEOF:       A pointer to the location to return a boolean specifying
 *              whether the read encountered an end-of-file. If it did, the
 *              buffer will return with no data in it. This pointer may be NULL,
 *              in which case end-of-file will be treated as an error.
 *
 * Returns:
 *  HRESULT     (either a file read error, or MAPI_E_CALL_FAILED when either
 *              EOF is encountered and pfEOF is NULL, or when the number of
 *              bytes read is less than the number requested).
 */
static HRESULT HrReadBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToRead,
    BOOL *pfEOF)
{
    SCODE sc = S_OK;
    ULONG cbRead = 0;
    BOOL fSuccess;

    AssertSz(cbToRead <= UINT_MAX && !IsBadWritePtr(lpBuffer, (UINT) cbToRead),
        "Bad buffer");

    fSuccess = ReadFile(hFile, lpBuffer, cbToRead, &cbRead, NULL);

    TranslateFileError(fSuccess, cbToRead, cbRead, pfEOF, &sc);

    DebugTraceSc(HrReadBytes, sc);
    return ResultFromScode(sc);
}

/**********************************************************************
 * HrWriteBytes
 *
 * Write a number of bytes from a buffer into a file, checking for errors.
 *
 * Parameters:
 *
 * hFile:       File handle to write into.
 * lpBuffer:    Pointer to a buffer containing the data to write.
 * cbToRead:    The number of bytes to write. Should be <= UINT_MAX.
 *
 * Returns:
 *  HRESULT     (either a file write error, or MAPI_E_CALL_FAILED when the
 *              number of bytes written is less than the number requested).
 */
static HRESULT HrWriteBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToWrite)
{
    SCODE sc = S_OK;
    ULONG cbWritten = 0;
    BOOL fSuccess;

    AssertSz(cbToWrite <= UINT_MAX && !IsBadReadPtr(lpBuffer, (UINT) cbToWrite),
        "Bad buffer");

    fSuccess = WriteFile(hFile, lpBuffer, cbToWrite, &cbWritten, NULL);

    TranslateFileError(fSuccess, cbToWrite, cbWritten, NULL, &sc);

    DebugTraceSc(HrWriteBytes, sc);
    return ResultFromScode(sc);
}

/**********************************************************************
 * TranslateFileError
 *
 * Checks for errors from a windows ReadFile or WriteFile call, translating
 * windows error codes to MAPI errors, and, if requested, checking for end-of-
 * file. Also checks to make sure the number of bytes read or written equals
 * the number given as input.
 *
 * Parameters:
 *
 * fSuccess:    The return value from ReadFile or WriteFile.
 * cbIn:        The number of bytes given as input to ReadFile or WriteFile.
 * cbOut:       The number of bytes returned from ReadFile or WriteFile.
 * pfEOF:       A pointer to the location to return a BOOL specifying
 *              whether cbOut was 0 and fSuccess was TRUE. This condition
 *              indicates end-of-file when reading, and should not be treated
 *              as an error. The pointer may be NULL, in which case, this
 *              condition is treated as an error, and *psc is returned with
 *              MAPI_E_CALL_FAILED.
 * psc:         A pointer to the location to return an SCODE indicating
 *              the success or failure of the ReadFile or WriteFile.
 *
 * Returns: VOID
 */
static VOID TranslateFileError(BOOL fSuccess, ULONG cbIn, ULONG cbOut,
    BOOL *pfEOF, SCODE *pscFile)
{
    SCODE sc = S_OK;
    BOOL fEOF = FALSE;
    BOOL fCheckEOF = (pfEOF != NULL);

    if (!fSuccess)
    {
        DWORD dwError = GetLastError();

        if (dwError != 0)

            switch (dwError)
            {
            case 0:
                TraceSz("SampleMS: ScTranslateFileError found unexpected "
                    "success\n");
                break;

            case ERROR_SHARING_VIOLATION:
            case ERROR_LOCK_VIOLATION:
                sc = MAPI_E_BUSY;
                break;

            case ERROR_FILE_NOT_FOUND:
                sc = MAPI_E_NOT_FOUND;
                break;

            case ERROR_TOO_MANY_OPEN_FILES:
                sc = MAPI_E_NOT_ENOUGH_RESOURCES;
                break;

            case ERROR_ACCESS_DENIED:
            case ERROR_INVALID_ACCESS:
            case ERROR_INVALID_DRIVE:
                sc = MAPI_E_NO_ACCESS;
                break;

            default:
                sc = MAPI_E_CALL_FAILED;
                break;
            }
    }
    else if (cbOut != cbIn)
    {
        sc = MAPI_E_CALL_FAILED;

        /* If the caller wants to discriminate end-of-file from other */
        /* errors, and the file call returned zero bytes, return EOF, */
        /* and don't return an error. */

        if (fCheckEOF && cbOut == 0)
        {
            sc = S_OK;
            fEOF = TRUE;
        }
    }

    AssertSz(!IsBadWritePtr(pscFile, sizeof(SCODE)), "Bad parameter (pscFile) "
        "given to TranslateFileError");

    *pscFile = sc;

    if (pfEOF)
    {
        AssertSz(!IsBadWritePtr(pfEOF, sizeof(BOOL)), "Bad parameter (pfEOF) "
            "given to TranslateFileError");
        *pfEOF = fEOF;
    }

    return;
}

/*
 * HrWriteRow
 *
 *  Purpose
 *      Writes one row of data to the file handle given.
 *
 *  Arguments
 *      hFile: File handle to update.
 *      prw: Pointer to the row to write.
 *
 *  RETURNS: HRESULT
 */
static HRESULT HrWriteRow(HANDLE hFile, LPSRow prw)
{
    HRESULT hr = hrSuccess;
    SCODE sc;
    ULONG cb;
    PDRW pdrw = NULL;

    sc = ScCountProps((UINT) prw->cValues, prw->lpProps, &cb);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    /* Allocate space for the disk row */
    hr = HrAlloc(CbNewDRW(cb), &pdrw);
    if (hr != hrSuccess)
        goto exit;

    /* fill in the disk row structure */

    pdrw->cbRow = cb;
    pdrw->rw.cValues = prw->cValues;

    /* The lpProps field of the row we write should be point at the */
    /* row we are writing (i.e., pdrw->ab). We will use that pointer */
    /* value when we read the data off disk to fixup the pointers via */
    /* ScRelocProps. */
    pdrw->rw.lpProps = (LPSPropValue) pdrw->ab;

    sc = ScCopyProps((UINT) prw->cValues, prw->lpProps, &(pdrw->ab), NULL);
    if (sc != S_OK)
    {
        hr = ResultFromScode(sc);
        goto exit;
    }

    /* write the row */
    hr = HrWriteBytes(hFile, pdrw, CbDRW(pdrw));
    if (hr != hrSuccess)
        goto exit;

exit:
    FreeNull(pdrw);

    DebugTraceResult(HrWriteRow, hr);
    return hr;
}

/*************************************************************************
 * HrWriteTableOnDisk
 *
 * Purpose
 *      write the table in lptbl to the disk image of lpvObjects table
 *      of type ulType
 *
 *  lptbl       Table to be written
 *  pobj        object whose table is to be written
 *  szEIDPath   Pathname of the EID of the folder, or NULL
 *  szFileName  Name of the file that holds this table type.
 *
 */
HRESULT HrWriteTableOnDisk(LPTABLEDATA lptbl, POBJ pobj, LPSTR szEIDPath,
    LPSTR szFileName)
{
    LPTSTR szFile = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE; /* handle to open file */
    LPSRow lpsRow = NULL;               /* next row to be written */
    ULONG ulRowNumber;          /* number of the row being written */
    HRESULT hr = hrSuccess;
    PLMR plmr = &pobj->pims->lmr;

    /* get the name of the file holding the table */
    hr = HrGetTableName(pobj, szEIDPath, szFileName, &szFile);
    if (hr != hrSuccess)
        goto exit;

    /* open the file with exclusive access */

    hr = HrOpenTblFileRetry(szFile, GENERIC_WRITE, 0L, CREATE_ALWAYS, &hFile);
    if (hr != hrSuccess)
        goto exit;

    /* write the table data to the file */
    ulRowNumber = 0;
    while (TRUE)
    {
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, ulRowNumber, &lpsRow);
        if (hr != hrSuccess)
            goto exit;
        if (lpsRow == NULL)
            break;

        hr = HrWriteRow(hFile, lpsRow);
        if (hr != hrSuccess)
            goto exit;

        LMFree(plmr, lpsRow);
        lpsRow = NULL;
        ulRowNumber++;
    }

exit:
    AssertSz(GetScode(hr) != MAPI_W_ERRORS_RETURNED,
        "Unexpected warning return");

    /* Set the end of file marker, in case we shrank the file. */
    if (hr == hrSuccess && SetEndOfFile(hFile) == FALSE)
        hr = ResultFromScode(MAPI_E_DISK_ERROR);

    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    /* erase the file if in error */
    if (hr != hrSuccess && szFile)
        DeleteFile(szFile);

    FreeNull(szFile);
    LMFree(plmr, lpsRow);

    DebugTraceResult(HrWriteTableOnDisk, hr);
    return hr;
}

/*************************************************************************
 * HrReadTableFromDisk
 *
 * Purpose
 *      Read a table from this disk cache into the table lptbl
 *
 *  lptbl       the table to be built
 *  pobj        object whose table is to be read
 *  szEIDPath   Pathname of the EID of the folder, or NULL
 *  cCols       Number of columns in this type of table
 *  szFileName  Name of the file that holds this table type.
 */
HRESULT HrReadTableFromDisk(LPTABLEDATA lptbl, POBJ pobj, LPSTR szEIDPath,
    ULONG cCols, LPSTR szFileName)
{
    HRESULT hr;
    PLMR    plmr;
    BOOL    fBadTableData   = FALSE;
    LPTSTR  szFile          = NULL;
    HANDLE  hFile           = INVALID_HANDLE_VALUE;
    PDRW    pdrw            = NULL;

    plmr = &pobj->pims->lmr;

    /* get the name of the file holding the table */
    hr = HrGetTableName(pobj, szEIDPath, szFileName, &szFile);
    if (hr != hrSuccess)
        goto exit;

    /* open the file */
    hr = HrOpenTblFileRetry(szFile, GENERIC_READ, FILE_SHARE_READ,
        OPEN_ALWAYS, &hFile);
    if (hr != hrSuccess)
    {
        /* If the file wasn't found, then simply leave the table empty, */
        /* and return success. */

        if (GetScode(hr) == MAPI_E_NOT_FOUND)
            hr = hrSuccess;

        goto exit;
    }

    while (TRUE)
    {
        BOOL fEOF;
        DRW drwTemp;
        ULONG cbOut;
        SCODE sc;

        /* Read the beginning of the disk row. */
        hr = HrReadBytes(hFile, &drwTemp, CbNewDRW(0), &fEOF);
        if (hr != hrSuccess)
            goto exit;

        if (fEOF)
            break;

        /* Sanity check for bad data. */
        /* Note that the number of columns in the disk version of the */
        /* table can be less than the number of columns in the in-memory */
        /* table due to properties that were missing from the message */
        /* when it was written to disk. */

        if (    drwTemp.rw.cValues > cCols
            ||  drwTemp.rw.cValues == 0
            ||  drwTemp.cbRow < drwTemp.rw.cValues * sizeof(SPropValue))
        {
            hr = ResultFromScode(MAPI_E_CORRUPT_DATA);
            fBadTableData = TRUE;
            goto exit;
        }

        hr = HrAlloc(CbNewDRW(drwTemp.cbRow), &pdrw);
        if (hr != hrSuccess)
            goto exit;

        memcpy(pdrw, &drwTemp, CbNewDRW(0));

        /* read the rest of the row */
        hr = HrReadBytes(hFile, &pdrw->ab, pdrw->cbRow, NULL);
        if (hr != hrSuccess)
        {
            fBadTableData = TRUE;
            goto exit;
        }

        sc = ScRelocProps((UINT) pdrw->rw.cValues, (LPSPropValue) pdrw->ab,
            pdrw->rw.lpProps, &pdrw->ab, &cbOut);
        if (sc != S_OK || cbOut != pdrw->cbRow)
        {
            hr = ResultFromScode(MAPI_E_CORRUPT_DATA);
            fBadTableData = TRUE;
            goto exit;
        }

        pdrw->rw.lpProps = (LPSPropValue) pdrw->ab;

        /* add this row to the table */
        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &pdrw->rw);
        if (hr != hrSuccess)
            goto exit;

        FreeNull(pdrw);
        pdrw = NULL;
    }

exit:
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    /* erase the file if it is bogus. We will regenerate it if we can. */
    if (fBadTableData)
        DeleteFile(szFile);

    FreeNull(szFile);
    FreeNull(pdrw);

    DebugTraceResult(HrReadTableFromDisk, hr);
    return hr;
}

/************************************************************************
 * HrGetTime
 *
 * Purpose  return the value of PR_LAST_MODIFICATION_TIME from the properties
 *          in the given property array
 *
 * Parameters
 *  prw         pointer to the table row to search
 *  pfiletime   pointer to last modification time
 */
static HRESULT HrGetTime(LPSRow prw, FILETIME *pfiletime)
{
    LPSPropValue pvalT;
    LPSPropValue pvalMax;
    SCODE sc = MAPI_E_NOT_FOUND;

    Assert(!IsBadReadPtr(prw, sizeof(SRow)));
    Assert(prw->cValues <= UINT_MAX / sizeof(SPropValue));
    Assert(!IsBadReadPtr(prw->lpProps, ((UINT) prw->cValues) * sizeof(SPropValue)));
    Assert(!IsBadWritePtr(pfiletime, sizeof(FILETIME)));

    pvalT = prw->lpProps;
    pvalMax = pvalT + prw->cValues;

    while (pvalT < pvalMax)
    {
        if (pvalT->ulPropTag == PR_LAST_MODIFICATION_TIME)
        {
            sc = S_OK;
            *pfiletime = pvalT->Value.ft;
            break;
        }
        pvalT++;
    }

    DebugTraceSc(HrGetTime, sc);
    return ResultFromScode(sc);
}

/*************************************************************************
 * HrSyncOutgoingTable
 *
 * Purpose
 *      Verifies that every table row in memory actually corresponds to
 *      a message on disk. If bad rows are found, removes them and
 *      rewrites the disk version of the table data. Note that this code
 *      does not verify that the message on disk actually is in the queue,
 *      nor does it check for messages that are somehow missing from the table.
 *
 *  lptbl           pointer to the outgoing table data object.
 *  pims            a pointer to the message store object.
 *
 */
HRESULT HrSyncOutgoingTable(LPTABLEDATA lptbl, PIMS pims)
{
    HRESULT hr;
    BOOL fRowsRemoved;

    hr = HrRemoveBadTableRows(lptbl, NULL, pims, &fRowsRemoved);

    if (hr == hrSuccess && fRowsRemoved)
        hr = HrWriteTableOnDisk(lptbl, (POBJ) pims, NULL, szOutgoingFileName);

    DebugTraceResult(HrSyncOutgoingTable, hr);
    return hr;
}

/*************************************************************************
 * HrSyncContentsTable
 *
 * Purpose
 *      Verifies that the table in memory agrees with what's on disk.
 *      If discrepancies are found, fixes them, and rewrites the disk
 *      version of the table data.
 *
 *  pifld           the parent folder of the contents table.
 *  fWriteTable     if TRUE, write the disk version of the table if the
 *                  in-memory table is out of sync.
 *
 */
HRESULT HrSyncContentsTable(PIFLD pifld, BOOL fWriteTable)
{
    HRESULT hr;
    BOOL fRowsRemoved;
    BOOL fRowsAdded;
    LPTABLEDATA lptbl;

    lptbl = pifld->lptblContents;

    hr = HrRemoveBadTableRows(lptbl, pifld, pifld->pims, &fRowsRemoved);
    if (hr != hrSuccess)
        goto exit;

    hr = HrAddMissingTableRows(lptbl, pifld, szMessageTemplate,
        (LPSPropTagArray) &sPropTagsContents, &fRowsAdded);
    if (hr != hrSuccess)
        goto exit;

    /* update folder's content count and unread count if */
    /* the table was out of ssync */

    if (fRowsRemoved || fRowsAdded)
    {
        hr = HrNewCounts(pifld, lptbl);
        if (hr != hrSuccess)
            goto exit;

        if (fWriteTable)
        {
            hr = HrWriteTableOnDisk(lptbl, (POBJ) pifld, pifld->peid->szPath,
                szContentsFileName);
            if (hr != hrSuccess)
                goto exit;
        }
    }
    else
    {
        /* If there aren't any rows in the table, we still need to
         * update the folder's count of messages, because the counts
         * may not be zero, even though there aren't any messages on disk.
         */
        LPSRow lpSRow = NULL;

        hr = lptbl->lpVtbl->HrEnumRow(lptbl, 0, &lpSRow);
        if (hr != hrSuccess)
            goto exit;

        if (lpSRow == NULL)
            hr = HrWriteCounts(pifld, 0, 0);

        LMFree(&pifld->pims->lmr, lpSRow);
    }

exit:
    DebugTraceResult(HrSyncContentsTable, hr);
    return hr;
}

/*************************************************************************
 * HrRemoveBadTableRows
 *
 * Purpose
 *      Verifies that every row in the table given has a corresponding
 *      file on disk. If the file on disk does not exist, the routine
 *      removes the row from the table.
 *
 *  lptbl           pointer to table
 *  pifldParent     the folder that all objects should be in. May be NULL.
 *  pims            the message store object
 *  pfTableChanged  Pointer to a location to return a BOOL that, when TRUE,
 *                  indicates that a bad row was found, and the table was changed
 *
 */
static HRESULT HrRemoveBadTableRows(LPTABLEDATA lptbl, PIFLD pifldParent,
    PIMS pims, BOOL *pfTableChanged)
{
    HRESULT hr = hrSuccess;
    LPSRow lpSRow = NULL;
    BOOL fTableChanged = FALSE; /* TRUE if lptbl was changed */
    ULONG iRow = 0;

    while (TRUE)
    {
        LPSTR szPath;
        BOOL fIsParent = TRUE;
        PEID peid;

        hr = lptbl->lpVtbl->HrEnumRow(lptbl, iRow++, &lpSRow);

        if (hr != hrSuccess || lpSRow == NULL)
            break;

        peid = (PEID) lpSRow->lpProps->Value.bin.lpb;

        /* If the caller gave us a parent folder, verify that the message */
        /* is in that folder. For example, if the caller is checking the */
        /* contents table, we can check that all messages are in the folder */
        /* they should be, but for the outgoing queue table, we can't. */

        if (pifldParent)
        {
            hr = HrIsParent(pifldParent->peid, peid, &fIsParent);
            if (hr != hrSuccess)
                goto exit;
        }

        hr = HrFullPathName(pims->szStorePath, peid->szPath, NULL, &szPath);
        if (hr != hrSuccess)
            goto exit;

        /* Delete the row if it no longer exists on disk */
        /* or isn't in the correct folder */
        if ((GetFileAttributes(szPath) == -1) || !fIsParent)
        {
            HRESULT hrT;

            hrT = lptbl->lpVtbl->HrDeleteRow(lptbl, lpSRow->lpProps);

            if (hrT != hrSuccess)
            {
                TraceSz1("SampleMS: HrSyncTableWithDisk: error %s "
                    "deleting out-of-date table entry\n",
                    SzDecodeScode(GetScode(hrT)));
            }

            fTableChanged = TRUE;
        }

        LMFree(&pims->lmr, lpSRow);
        lpSRow = NULL;
        FreeNull(szPath);
    }

    AssertSz(!IsBadWritePtr(pfTableChanged, sizeof(BOOL)), "Bad parameter"
        " (pfTableChanged) given to HrRemoveBadTableRows");
    *pfTableChanged = fTableChanged;

exit:
    LMFree(&pims->lmr, lpSRow);
    DebugTraceResult(HrRemoveBadTableRows, hr);
    return hr;
}

/*************************************************************************
 * HrAddMissingTableRows
 *
 * Purpose
 *      Searches a folder for objects of the specified type, and verifies
 *      that each object exists in the table given, and is up-to-date.
 *      If the object does not exist or is out-of-date, the routine
 *      adds or updates the table row with current information from the object.
 *      This routine only works for contents tables currently.
 *
 *  lptbl           pointer to table
 *  pifld           the folder that all objects should be in.
 *  szTemplate      type of files to search for for this table type
 *  ptaga           list of proptags for this type of table
 *  pfTableChanged  Pointer to a location to return a BOOL that, when TRUE,
 *                  indicates that the table was changed to match the disk
 *
 */
static HRESULT HrAddMissingTableRows(LPTABLEDATA lptbl, PIFLD pifld,
    LPSTR szTemplate, LPSPropTagArray ptaga, BOOL * pfTableChanged)
{
    HRESULT hr;
    WIN32_FIND_DATA ffd;
    HANDLE hFindFile = INVALID_HANDLE_VALUE;
    ULONG ulOffset;
    LPTSTR szFile = NULL;       /* full path name of next file in the table */

PEID peid = NULL;
    BOOL fTableChanged = FALSE;
    LPSRow lpSRow = NULL;
    SPropValue pvInstKey;
    PLMR plmr = &pifld->pims->lmr;

    /* for each file of the right kind ( message or folder ) on disk */
    /* make sure its entry in the cache is up to date */

    pvInstKey.ulPropTag = PR_INSTANCE_KEY;

    /* get the next file */
    hr = HrFindFirstID(pifld, szTemplate, &ulOffset, &szFile, &hFindFile,
        &ffd, &peid);

    while (hr == hrSuccess)
    {
        FILETIME ftTableTime;   /* modify time of szFile in lptbl */

        /* get its row in the table */
        pvInstKey.Value.bin.cb = CbEID(peid);
        pvInstKey.Value.bin.lpb = (LPBYTE) peid;

        hr = lptbl->lpVtbl->HrQueryRow(lptbl, &pvInstKey, &lpSRow, NULL);

        if (GetScode(hr) == MAPI_E_NOT_ENOUGH_MEMORY)
            goto exit;

        /* update the row if necessary */
        /* store in ftTableTime, the modify time of this object */
        /* as stored in the lptbl */
        if (hr == hrSuccess && lpSRow)
            hr = HrGetTime(lpSRow, &ftTableTime);

        if (hr != hrSuccess
            || lpSRow == NULL
            || CompareFileTime(&ftTableTime, &(ffd.ftLastWriteTime)) == -1)
        {
            fTableChanged = TRUE;

            HrUpdateRow(pifld->pims, lptbl, peid, ptaga, &(ffd.ftLastWriteTime),
                MAPI_MESSAGE);
        }

        LMFree(plmr, peid);
        peid = NULL;
        LMFree(plmr, lpSRow);
        lpSRow = NULL;

        hr = HrFindNextID(pifld, ulOffset, szFile, hFindFile, &ffd, &peid);
    }

    if (GetScode(hr) == MAPI_E_NOT_FOUND)
        hr = hrSuccess;

    if (hr == hrSuccess)
    {
        AssertSz(!IsBadWritePtr(pfTableChanged, sizeof(BOOL)), "Bad parameter"
            " (pfTableChanged) given to HrAddMissingTableRows");
        *pfTableChanged = fTableChanged;
    }

exit:
    CloseIDSearch(&hFindFile, &szFile);
    LMFree(plmr, lpSRow);
    LMFree(plmr, peid);

    DebugTraceResult(HrAddMissingTableRows, hr);
    return hr;
}


README.TXT

Custom Message Event Extension

SUMMARY
=======

The Events.Ext sample demonstrates how to implement a custom message event
extension for the Microsoft Exchange Client. In particular, it demonstrates:

  - reading message text before displayed in the read note form
  - reading message text as it is submitted to MAPI

This sample uses a very basic form of signing a message. A checksum on the
message as it is written is stored as a custom property. Upon reading the
message, the extension verifies the checksum and displays one of two
possible outcomes: the message has been verified with correct contents, or
the message text has been altered.

MORE INFORMATION
================

This sample requires Microsoft Windows NT 3.51 or Windows 95, the MAPI 1.0
(Extended MAPI) PDK (Build 410 or above),  Microsoft Visual C++ version 2.0
(or later), and the Win32 SDK for Windows NT and Windows 95.

Configuration
-------------

To configure Exchange to use the client extension, place the following
REG_SZ entry in the system registry in:
HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Client\Extensions

    Samp Ext=4.0;C:\MAPISAMP\EVENTEXT\EVEXT32.DLL;1;00000111111100

You can leave out an explicit path to EVEXT32.DLL if it resides in a
directory listed in the system PATH.

Exchange client extensions are designed using OLE's Component Object Model.
The client calls methods which are provided in the extension. In some calls
to the extension interface, a pointer to a callback interface
(IExchExtCallback) is given for the extension to call back into the
Exchange client. For more information read "Extending the Microsoft Exchange
Client" in the MAPI PDK documentation.

This sample extension includes the implementation of a property page
displayed in the Tools Option property sheet to either enable or disable
signatures. The sample will check a flag before calculating or verifying
checksums.

GetObject is used during the OnWriteComplete and OnRead methods to obtain
the message body. The checksum is calculated and written as a custom
property of a message when the Send button is clicked. The checksum is
recalculated and verified when a message is read. For large messages, the
checksum may take a few seconds depending on the size of the message and the
speed of the machine.

During the case that a message is found to have "altered" text according
to the checksum, the extension gives the user a choice whether to continue
to read the message or not. If not, an error return value is set in the
OnRead method to be used by the OnReadComplete method. Returning
MAPI_E_CALL_FAILED from OnReadComplete causes Exchange to call OnReadComplete
again with ulFlags parameter set to an error. The parameter is ignored by
this sample and returning MAPI_E_CALL_FAILED causes Exchange to not display
the read note form.

This sample also supports the "Reply to Sender" context and signs the
reply when enabled. Replying to a non-signed message while the signatures
are enabled adds a signature to the entire message, counting original
message and reply. Replying to a signed message while the signature is
disabled removes the original signature.

The logic for signing is only calculating the checksum on the PR_BODY
message property. The extension does not take "quirks" of certain providers
into its logic and the extension will interpret that a message was "altered"
when in fact it has not. Two examples are the MSPERR Transport and the MSMail
provider. With the MSPeer transport, it will add the text
"*** End of message ***" at the end of the message. The checksum does not
include this upon sending. The MSMail provider strips whitespace at the end
of a line just before the carriage return characters (CRLF). When sending,
the checksum calculation includes the whitespace. So the checksum verification
upon reading will not match with the stored checksum property. If you write a
message without placing any spaces just before a carriage return, or just
allow the richedit control wordwrap, the checksums will be equal.


EVNTEXT.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      EVNTEXT.CPP
//
//  Description
//  This is an implementation of a message event extension example.  The
//  extension calculates the checksum of a message that is submitted by
//  The Exchange client and then signs the message with the checksum result
//  to be used upon reading.  The sample includes a Tools Options property
//  page to enable or disable signing.
//
//      IExchExt interface methods:
//          MyExchExt::QueryInterface()
//          MyExchExt::AddRef()
//          MyExchExt::Release()
//
//          MyExchExt::Install()
//
//      IExchExtPropertySheets interface methods:
//          IExchExtPropertySheets::QueryInterface()
//          IExchExtPropertySheets::AddRef()
//          IExchExtPropertySheets::Release()
//
//          IExchExtPropertySheets::GetMaxPageCount()
//          IExchExtPropertySheets::GetPages()
//          IExchExtPropertySheets::FreePages()
//
//      IExchExtMessageEvents interface methods:
//          IExchExtMessageEvents::QueryInterface()
//          IExchExtMessageEvents::AddRef()
//          IExchExtMessageEvents::Release()
//
//          IExchExtMessageEvents::OnCheckNames()
//          IExchExtMessageEvents::OnCheckNamesComplete()
//          IExchExtMessageEvents::OnRead()
//          IExchExtMessageEvents::OnReadComplete()
//          IExchExtMessageEvents::OnSubmit()
//          IExchExtMessageEvents::OnSubmitComplete()
//          IExchExtMessageEvents::OnWrite()
//          IExchExtMessageEvents::OnWriteComplete()
//
//  Author
//      Gary Peluso
//
//  Revision: 1.01
//
//  History
//  9/21/95 - fixed "signature" property sheet bug (two sheets incorrectly shown)
//  9/21/95 - changed QI for MyExchExtMessageEvents and MyExchExtPropertySheets
//               to return pointer to MyExchExt when IID_IUNKNOWN is requested
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMessage
#define USES_PS_MAPI
#define USES_PS_PUBLIC_STRINGS

#include "EVNTEXT.H"

//#include <INITGUID.H>
#include <MAPIGUID.H>

///////////////////////////////////////////////////////////////////////////////
//    global data that supports extension functionality
BOOL bSignatureOn = TRUE;

///////////////////////////////////////////////////////////////////////////////
//    global data for DLL
static HINSTANCE ghInstDLL = NULL;  // instance handle of DLL

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: DLLMain()
//
//    Purpose
//    Do initilization processesing
//
//    Return Value
//    TRUE - DLL successfully loads and LoadLibrary will succeed.
//    FALSE - will cause an Exchange error message saying it cannot locate
//            the extension DLL.
//
//    Comments
//    We only need to get a copy of the DLL's HINSTANCE.
//
BOOL WINAPI DllMain(
    HINSTANCE  hinstDLL,
    DWORD  fdwReason,
    LPVOID  lpvReserved)
{
 if (DLL_PROCESS_ATTACH == fdwReason)
 {
    ghInstDLL = hinstDLL;

 }
 return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: ExchEntryPoint
//
//    Parameters - none
//
//    Purpose
//    The entry point called by Exchange.
//
//    Return Value
//    Pointer to Exchange Extension (IExchExt) interface
//
//    Comments
//    Exchange Client calls this for each context entry.
//

LPEXCHEXT CALLBACK ExchEntryPoint(void)
{
     return new MyExchExt;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::MyExchExt()
//
//    Parameters - none
//
//    Purpose
//    Comstructor.  Called during instantiation of MyExchExt object.
//
//

MyExchExt::MyExchExt()
{
  m_cRef = 1;

  m_pExchExtPropertySheets = new MyExchExtPropertySheets(this);
  m_pExchExtMessageEvents = new MyExchExtMessageEvents(this);

};

///////////////////////////////////////////////////////////////////////////////
//  IExchExt virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::Release()
//
//    Parameters - none
//
//    Purpose
//    Frees memory when interface is not referenced any more
//
//    Return value
//    reference count of interface
//

STDMETHODIMP_(ULONG) MyExchExt::Release()
{
 ULONG ulCount = --m_cRef;

 if (!ulCount)
 {
  delete this;
 }

return ulCount;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Returns a pointer to an interface object that is requested by ID.
//
//    Comments
//    The interfaces are requested everytime a new context is entered.  The
//    IID_IExchExt* interfaces are ignored if not specified in the Exchange
//    extensions registry.
//
//    If an interface pointer is returned for more than one context, that
//    interface is used by the client for each of those contexts.  Check the
//    current context to verify if it is appropriate to pass back an interface
//    pointer.
//
//    Return Value - none
//

STDMETHODIMP MyExchExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    HRESULT hResult = S_OK;

    *ppvObj = NULL;

    if (( IID_IUnknown == riid) || ( IID_IExchExt == riid) )
    {
        *ppvObj = (LPUNKNOWN)this;
    }
    else if (IID_IExchExtPropertySheets == riid)
    {
     // if we are in the read or send context, do not return
     // propertysheet interface
        if ( (m_context == EECONTEXT_SENDNOTEMESSAGE)   ||
             (m_context == EECONTEXT_SENDPOSTMESSAGE)   ||
             (m_context == EECONTEXT_SENDRESENDMESSAGE) ||
             (m_context == EECONTEXT_READNOTEMESSAGE)   ||
             (m_context == EECONTEXT_READPOSTMESSAGE)   ||
             (m_context == EECONTEXT_READREPORTMESSAGE) )
                 return E_NOINTERFACE;

     // otherwise return the interface
        *ppvObj = (LPUNKNOWN) m_pExchExtPropertySheets;
    }
    else if (IID_IExchExtMessageEvents == riid)
    {
        *ppvObj = (LPUNKNOWN) m_pExchExtMessageEvents;
    }
    else
        hResult = E_NOINTERFACE;

    if (NULL != *ppvObj)
        ((LPUNKNOWN)*ppvObj)->AddRef();

    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::Install()
//
//    Parameters
//    peecb     -- pointer to Exchange Extension callback function
//    eecontext -- context code at time of being called.
//
//    Purpose
//    Called once for each new context that is entered.
//
//    Return Value
//    S_OK - the installation succeeded for the context
//    S_FALSE - deny the installation fo the extension for the context
//
STDMETHODIMP MyExchExt::Install(LPEXCHEXTCALLBACK peecb, ULONG eecontext, ULONG ulFlags)
{
    ULONG ulBuildVersion;
    HRESULT hr;

    m_context = eecontext;

    // make sure this is the right version
    peecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
    if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion &
                                      EECBGV_BUILDVERSION_MAJOR_MASK))
        return S_FALSE;

    switch (eecontext)
    {
     case EECONTEXT_PROPERTYSHEETS:
     case EECONTEXT_SENDNOTEMESSAGE:
     case EECONTEXT_SENDPOSTMESSAGE:
     case EECONTEXT_SENDRESENDMESSAGE:
     case EECONTEXT_READNOTEMESSAGE:
     case EECONTEXT_READPOSTMESSAGE:
     case EECONTEXT_READREPORTMESSAGE:
        hr = S_OK;
        break;

     default:
        hr = S_FALSE;
        break;
    }

    return hr;

}

///////////////////////////////////////////////////////////////////////////////
//  IExchExtPropertySheets virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Return interface object upon request
//
//    Return Value - none
//
//    Comments
//    Currently the Exchange client does not call QueryInterface from any object
//    except for IExchExt.  This is implemented in case features are added to
//    Exchange to require QueryInterface from any object.  Also, as a "rule of
//    OLE COM" this is the proper implementation of QueryInterface.
//

STDMETHODIMP MyExchExtPropertySheets::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{

    *ppvObj = NULL;
    if (riid == IID_IExchExtPropertySheets)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    if (riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)m_pExchExt;  // return parent interface
        m_pExchExt->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::GetMaxPageCount()
//
//    Parameters
//    ulFlags -- a bitmask indicating what type of property sheet is being
//               displayed
//
//    Purpose
//    Returns the number of property pages which are to be added.
//
//    Return Value - maximum number of custom pages for the property sheet
//
//    Exchange calls this to know how many PROPSHEETPAGE buffers it needs
//    to allocate.
//

STDMETHODIMP_ (ULONG) MyExchExtPropertySheets::GetMaxPageCount(ULONG ulFlags)
{
 ULONG ulNumExtSheets;

    switch (ulFlags)
    {
     // ignore these objects.
     case EEPS_FOLDER:
     case EEPS_STORE:
     case EEPS_MESSAGE:
        ulNumExtSheets = 0;
        break;

     case EEPS_TOOLSOPTIONS:
        ulNumExtSheets = 1;   // adding one propery page
        break;

     default:
        ulNumExtSheets = 0;
        break;
    }

    return ulNumExtSheets;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::GetPages()
//
//    Parameters
//    peecb   -- pointer to Exchange callback interface
//    ulFlags -- a bitmask indicating what type of property sheet is being
//               displayed
//    ppsp    -- output parm pointing to pointer to list of property sheets
//    pcpsp   -- output parm pointing to buffer contaiing number of property
//               sheets actually used.
//
//    Purpose
//    Fills the PROPSHEETPAGE members for the custom property page.
//
//    Return Value
//    S_FALSE - do not add a new page
//    S_OK - use the ppsp information for new pages.
//
//    Comments
//    Exchange calls this method to gather information for any custom
//    property pages to be added to the sheet.  Here we are only adding
//    one.  ppsp may be an array of PROPSHEETPAGE structures to allow you
//    to all multiple property pages.
//

STDMETHODIMP MyExchExtPropertySheets::GetPages(LPEXCHEXTCALLBACK peecb,
                      ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp)
{
 LPMDB pMDB = NULL;
 LPMESSAGE pItem = NULL;

    *pcpsp = 0;

    // fill out members for the property page
    ppsp[0].dwSize = sizeof (PROPSHEETPAGE);
    ppsp[0].dwFlags = PSP_DEFAULT | PSP_HASHELP;
    ppsp[0].hInstance = ghInstDLL;
    ppsp[0].pszTemplate = MAKEINTRESOURCE(IDD_SIGNATURE);
    ppsp[0].hIcon = NULL;     // not used in this sample
    ppsp[0].pszTitle = NULL;  // not used in this sample
    ppsp[0].pfnDlgProc = (DLGPROC)SignatureOptionsDlgProc;
    ppsp[0].lParam = 0;
    ppsp[0].pfnCallback = NULL;
    ppsp[0].pcRefParent = NULL; // not used in this sample

    *pcpsp = 1;

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::FreePages()
//
//    Parameters
//    ppsp -- pointer to a pointer to the first of a list of property pages
//    cpsp -- number of custom property pages in the list
//    ulFlags -- type of property page
//
//    Purpose
//    Free any memory associated to the property sheet.
//
//    Return Value - none
//
//    Comments
//    No parameters are used in this example but the function is used as
//    a signal that the property sheet is going away and so memory may
//    be freed.
//

STDMETHODIMP_ (VOID) MyExchExtPropertySheets::FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags,
                                        ULONG cpsp)
{
   // not used in this sample
}

///////////////////////////////////////////////////////////////////////////////
//  IExchExtMessageEvents virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Return interface object upon request
//
//    Return Value - none
//
//    Comments
//    Currently the Exchange client does not call QueryInterface from any object
//    except for IExchExt.  This is implemented in case features are added to
//    Exchange to require QueryInterface from any object.  Also, as a "rule of
//    OLE COM" this is the proper implementation of QueryInterface.
//

STDMETHODIMP MyExchExtMessageEvents::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{

    *ppvObj = NULL;
    if (riid == IID_IExchExtMessageEvents)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    if (riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)m_pExchExt;  // return parent interface
        m_pExchExt->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnRead()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    To extend or inhibit Exchange when displaying the send or read note form.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//    Other MAPI Code errors will abort the send or read note form.
//
//

STDMETHODIMP MyExchExtMessageEvents::OnRead(LPEXCHEXTCALLBACK lpeecb)
{
  HRESULT hr;
  LPMESSAGE pMessage = NULL;
  LPMDB pMDB = NULL;
  ULONG ulCheckSum = 0;
  LPSPropTagArray pNamedPropTags = NULL;
  LPSPropValue pPropValues = NULL;
  HWND hWnd;
  HCURSOR hOldCursor = NULL;
  MAPINAMEID NamedID;
  LPMAPINAMEID pnmid = &NamedID;

  m_hrOnReadComplete = S_FALSE; // tell OnReadComplete to show its dialog

  if (!bSignatureOn)      // user has turned off signatures
  {
    goto error_return;    // return and do nothing
  }

  hr = lpeecb->GetObject(&pMDB, (LPMAPIPROP *)&pMessage);
  if (FAILED(hr))
  {
    goto error_return;
  }

  lpeecb->GetWindow(&hWnd);
  hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

  // check to see if MsgChecksum exists
  // use a named property for the message body checksum
  NamedID.lpguid = (LPGUID) &PS_PUBLIC_STRINGS;
  NamedID.ulKind = MNID_STRING;
  NamedID.Kind.lpwstrName = L"MsgChecksum";

  hr = pMessage->GetIDsFromNames(1, &pnmid, 0, &pNamedPropTags);
  //here we can get away with checking for hr!=0, because we retrieve only one
  //PropTag. If more then one PropTag requested and GetIDsFromName can't find
  // some of them, a warning MAPI_W_ERRORS_RETURNED is returned and each PropTag
  //has to be checked separatly. (See Docs for details).
  if (hr)     // likely the message is not signed
  {
    goto error_return;
  }

  pNamedPropTags->aulPropTag[0] = PROP_TAG(PT_LONG, PROP_ID(pNamedPropTags->aulPropTag[0]));

  hr = HrGetOneProp(pMessage, pNamedPropTags->aulPropTag[0], &pPropValues);
  if (hr)
  {
    goto error_return;
  }

  // we have a signed message, continue with checking validity through checksum
  hr = CalcULONGCheckSum(pMessage, &ulCheckSum);
  if (FAILED(hr))
  {
    ErrorMessageBox(hWnd, hr, "OnRead",
        "An error occured while calculating\n"
        "the message body checksum.");
    goto error_return;
  }

  if (pPropValues[0].Value.ul == ulCheckSum)
  {
      MessageBox(hWnd, "Signed Message Verified.\n"
                       "Message has authentic contents.", "Event Extension", MB_OK);
  }
  else
  {
   int nRet = MessageBox(hWnd, "Signed Message was altered.\n"
                       "Message may not have authentic contents.\n"
                       "Do you wish to view the message anyway?", "Event Extension", MB_YESNO);

   if (nRet == IDNO)
   // this will tell OnReadComplete to not display the read note UI at all.
      m_hrOnReadComplete = MAPI_E_CALL_FAILED;
  }

  error_return:

  hr = S_FALSE;

  UlRelease(pMDB);

  UlRelease(pMessage);

  MAPIFreeBuffer(pNamedPropTags);

  MAPIFreeBuffer(pPropValues);

  if(hOldCursor)
      SetCursor(hOldCursor);

  return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnReadComplete()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    To do processing after message has been read.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//    Some MAPI Code error indicates a problem and will not display the send
//    or read note form.
//
//    Comments.
//    If an error code, such as MAPI_E_CALL_FAILED, is returned, Exchange will
//    call OnReadComplete again with the ulFlags parameter set to
//    EEME_COMPLETE_FAILED.  Returning the error code again will cause Exchange
//    to not display the UI.
//

STDMETHODIMP MyExchExtMessageEvents::OnReadComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags)
{

  return m_hrOnReadComplete;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnWrite()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    This method is called when a message is about to be written.  The message
//    only has default properties at this point.  It does not contain
//    properties which the user has added by way of recipients, subject,
//    message text, or attachments.
//    This method is called when the user Sends or Saves a message
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//
//

STDMETHODIMP MyExchExtMessageEvents::OnWrite(LPEXCHEXTCALLBACK lpeecb)
{
  HRESULT hr;

  hr = S_FALSE;

  return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnWriteComplete()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    This method is called when the data (recipients, attachments, body,
//    subject, etc.) has been written to the message.  This sample will
//    calculate the checksum on the PR_BODY property.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//
//

STDMETHODIMP MyExchExtMessageEvents::OnWriteComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags)
{
  HRESULT hr;
  LPMESSAGE pMessage = NULL;
  LPMDB pMDB = NULL;
  ULONG ulCheckSum = 0;
  SPropValue valChksumProp;
  LPSPropTagArray pNamedPropTags = NULL;
  HWND hWnd;
  HCURSOR hOldCursor;

  MAPINAMEID NamedID;

  LPMAPINAMEID pnmid = &NamedID;

  if (!bSignatureOn)      // user has turned off signatures
  {
    return S_FALSE;    // return and do nothing
  }

  if (!m_bInSubmitState)    // means user is just "saving the message"
  {
    return S_FALSE;
  }

  hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

  hr = lpeecb->GetObject(&pMDB, (LPMAPIPROP *)&pMessage);
  if (FAILED(hr))
  {
    goto error_return;
  }

  hr = CalcULONGCheckSum(pMessage, &ulCheckSum);
  if (FAILED(hr))
  {
    lpeecb->GetWindow(&hWnd);

    ErrorMessageBox(hWnd, hr, "OnWriteComplete",
        "An error occured while calculating\n"
        "the message body checksum.");
    goto error_return;
  }

  // use a named property for the message body checksum
  NamedID.lpguid = (LPGUID) &PS_PUBLIC_STRINGS;
  NamedID.ulKind = MNID_STRING;
  NamedID.Kind.lpwstrName = L"MsgChecksum";

  hr = pMessage->GetIDsFromNames(1, &pnmid, MAPI_CREATE, &pNamedPropTags);
  //here we can get away with checking for hr!=0, because we retrieve only one
  //PropTag. If more then one PropTag requested and GetIDsFromName can't find
  // some of them, a warning MAPI_W_ERRORS_RETURNED is returned and each PropTag
  //has to be checked separatly. (See Docs for details).
  if(hr)
  {
   goto error_return;
  }

  valChksumProp.ulPropTag = PROP_TAG(PT_LONG, PROP_ID(pNamedPropTags->aulPropTag[0]));
  valChksumProp.dwAlignPad = 0L;
  valChksumProp.Value.ul = ulCheckSum;

  hr = HrSetOneProp(pMessage, &valChksumProp);
  if (hr)
  {
   goto error_return;
  }

  // everything succeeded.  return S_FALSE to tell Exchange to continue calling
  // extensions.
  hr = S_FALSE;

  error_return:

  UlRelease(pMDB);

  UlRelease(pMessage);

  MAPIFreeBuffer(pNamedPropTags);

  SetCursor(hOldCursor);

  return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnCheckNames()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    Called when user selects the Check Names button and just before message
//    is submitted to MAPI.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//

STDMETHODIMP MyExchExtMessageEvents::OnCheckNames(LPEXCHEXTCALLBACK lpeecb)
{
  // Not used by the sample.
  return S_FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnCheckNamesComplete()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    Called after exchange has completed resolving names in the message
//    recipients table.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//

STDMETHODIMP MyExchExtMessageEvents::OnCheckNamesComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags)
{
  // Not used by the sample
  return S_FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnSubmit()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    Called before message data has been written and is
//    is submitted to MAPI.
//
//    Return Value
//    S_OK signals Exchange to not continue calling extensions
//    S_FALSE signals Exchange to continue calling extensions
//
//    Set a member function to show that submit has been called
//    to indicate to OnWriteComplete that the user has hit the
//    Send button and is not just saving the message.
//

STDMETHODIMP MyExchExtMessageEvents::OnSubmit(LPEXCHEXTCALLBACK lpeecb)
{
  HRESULT hr;

  hr = S_FALSE;

  m_bInSubmitState = TRUE;  // submit is called

  return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtMessageEvents::OnSubmitComplete()
//
//    Parameters
//    lpeecb -- pointer to IExchExtCallback interface
//
//    Purpose
//    Called after message has been submitted to MAPI.
//
//    Return Value - none
//
//    A flag is cleared to indicate to other methods that Exchange is
//    finished submitting the message.
//

STDMETHODIMP_ (VOID) MyExchExtMessageEvents::OnSubmitComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags)
{

  m_bInSubmitState = FALSE;  // out of submit state

}

///////////////////////////////////////////////////////////////////////////////
//  Helper Functions
//

///////////////////////////////////////////////////////////////////////////////
//    ErrorMessageBox()
//
//    Parameters
//    hWnd       -- parent window
//    hr         -- HRESULT value
//    szFunction -- function name in which the error occurred
//    szMessage  -- error message
//
//    Purpose
//    Displays an error message using MessageBox
//
//    Return Value - none
//
//    Comments
//    Pass 0 for hr to not display an error number.  Pass NULL for
//    szFunction to not display a function name.  Use these options
//    to display error messages for public consumption.  Use hr and
//    function name for internal errors and for debugging.  szMessage
//    is mandatory.
//
void ErrorMessageBox(HWND hWnd, HRESULT hr, LPSTR szFunction, LPSTR szMessage)
{
 static char szError[256];

 if (szMessage == NULL)
 {
    MessageBox(hWnd,
         "An unknown error occured in\nSample property sheet extension",
         "Sample Property Sheet Extension", MB_ICONEXCLAMATION | MB_OK);
    return;
 }

 if ((hr == 0) && (szFunction == NULL))
 {
    MessageBox(hWnd, szMessage, "Sample Extension Error", MB_ICONEXCLAMATION | MB_OK);
    return;
 }

 if (szFunction != NULL)
 {
  wsprintf(szError, "Error %08X in %s\n%s", hr, szFunction, szMessage);
  MessageBox(hWnd, szError, "Sample Extension Error", MB_ICONEXCLAMATION | MB_OK);
 }

}

///////////////////////////////////////////////////////////////////////////////
//    CalcULONGCheckSum(pMessage, &ulCheckSum)()
//
//    Parameters
//    pMessage -- pointer to message object
//    *pulCheckSum -- pointer to buffer to contain calculated checksum
//
//    Purpose
//    Calculates a checksum on the PR_BODY property of a message
//
//    Return Value
//    HRESULT value:
//       S_OK -- succeeded
//       anything means some internal call failed
//
//    Comments
//    Checksum is calculated by adding the ascii values for 4 bytes of data
//    concatenated together at a time.  If the message does not have an even
//    multiple of 4 characters in the PR_BODY, the remain 1 2 or 3 characters
//    are concatenated to make a ULONG with leading zeros.
//

HRESULT CalcULONGCheckSum(LPMESSAGE pMessage, ULONG *pulCheckSum)
{
 HRESULT hr;
 LPSTREAM pStreamBody = NULL;
 ULONG ulValue;
 ULONG ulRead;
 LARGE_INTEGER large_int;
 ULARGE_INTEGER ularge_int;

 if ( (pMessage == NULL) ||
      (pulCheckSum == NULL) )
 {
      hr = MAPI_E_INVALID_PARAMETER;
      goto error_return;
 }

 hr = pMessage->OpenProperty(PR_BODY, &IID_IStream, STGM_DIRECT | STGM_READ, 0,
                    (LPUNKNOWN *) &pStreamBody);

 if(GetScode(hr) == MAPI_E_NOT_FOUND)  //no body in the message
 {
     *pulCheckSum = 0;
     return hrSuccess;
 }
 if (FAILED(hr))
 {
    goto error_return;
 }

 large_int.LowPart = 0;
 large_int.HighPart = 0;

 hr = pStreamBody->Seek(large_int, STREAM_SEEK_SET, &ularge_int);
 if (FAILED(hr))
 {
    goto error_return;
 }

 (*pulCheckSum) = 0;
 ulValue = 0;
 while ( (S_OK == (hr = pStreamBody->Read((LPVOID)&ulValue, 4, &ulRead)))  &&
         (ulRead > 0) )
 {
    (*pulCheckSum) += ulValue;
    ulValue = 0;
 }

 error_return:

 if (pStreamBody != NULL)
    pStreamBody->Release();

return hr;

}


EVNTEXT.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      EVNTEXT.H
//
//  Description
//      Interface Methods are declared in this file for evntext.cpp
//
//  Author
//      Gary Peluso
//
//  Revision: 1.00
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#ifndef __EVNTEXT_H__
#define __EVNTEXT_H__

#include <WINDOWS.H>
#include <COMMCTRL.H>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>
#include <EXCHEXT.H>

#include "RESOURCE.H"

///////////////////////////////////////////////////////////////////////////////
//    Prototypes
//

extern "C"
{
 LPEXCHEXT CALLBACK ExchEntryPoint(void);
}

void ErrorMessageBox(HWND hWnd, HRESULT hr,
        LPSTR szFunction, LPSTR szMessage);
HRESULT CalcULONGCheckSum(LPMESSAGE pMessage, ULONG *pulCheckSum);
BOOL CALLBACK SignatureOptionsDlgProc(HWND hDlg, UINT uMsg,
        WPARAM wParam, LPARAM lParam);

///////////////////////////////////////////////////////////////////////////////
//    Global Data Declarations
//
extern BOOL bSignatureOn;   // defined in EVNTEXT.CPP

///////////////////////////////////////////////////////////////////////////////
//    forward declarations
//
class MyExchExt;
class MyExchExtPropertySheets;
class MyExchExtMessageEvents;

class MyExchExt : public IExchExt
{

 public:
    MyExchExt();
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP Install (LPEXCHEXTCALLBACK pmecb,
                        ULONG mecontext, ULONG ulFlags);

 private:
    ULONG m_cRef;
    UINT  m_context;
    MyExchExtPropertySheets * m_pExchExtPropertySheets;
    MyExchExtMessageEvents * m_pExchExtMessageEvents;

};

class MyExchExtPropertySheets : public IExchExtPropertySheets
{
 public:
    MyExchExtPropertySheets (LPUNKNOWN pParentInterface) {
    m_pExchExt = pParentInterface;
    m_cRef = 0;
    };

    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};

    STDMETHODIMP_ (ULONG) GetMaxPageCount(ULONG ulFlags);
    STDMETHODIMP  GetPages(LPEXCHEXTCALLBACK peecb,
                          ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp);
    STDMETHODIMP_ (VOID) FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags,
                                         ULONG cpsp);

 private:
    ULONG m_cRef;
    LPUNKNOWN m_pExchExt;
};

class MyExchExtMessageEvents : public IExchExtMessageEvents
{
 public:
    MyExchExtMessageEvents (LPUNKNOWN pParentInterface) {
    m_pExchExt = pParentInterface;
    m_cRef = 0;
    m_bInSubmitState = FALSE;
    };

    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};

    STDMETHODIMP OnRead(LPEXCHEXTCALLBACK lpeecb);
    STDMETHODIMP OnReadComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnWrite(LPEXCHEXTCALLBACK lpeecb);
    STDMETHODIMP OnWriteComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnCheckNames(LPEXCHEXTCALLBACK lpeecb);
    STDMETHODIMP OnCheckNamesComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnSubmit(LPEXCHEXTCALLBACK lpeecb);
    STDMETHODIMP_ (VOID)OnSubmitComplete(LPEXCHEXTCALLBACK lpeecb, ULONG ulFlags);

 private:
    ULONG   m_cRef;
    HRESULT m_hrOnReadComplete;
    BOOL    m_bInSubmitState;
    LPUNKNOWN m_pExchExt;

};

#endif // __EVNTEXT_H__


PRSHTDLG.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      PRSHTDLG.CPP
//
//  Description
//      This file contains the dialogproc that handles messages for
//      the property page extended onto the Tools Options Property Sheet
//
//  Revision: 1.00
//
//  Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//

#include "EVNTEXT.H"

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION SignatureOptionsDlgProc
//
//    Parameters
//    hDlg   - handle to modeless dialog, the property page
//    uMsg   - message
//    wParam - wParam of wndproc, not used
//    lParam - lParam of wndproc, points to NMHDR for notifications
//
//    Purpose
//    Handles the notifications sent for managing a custom property page
//    to allow user to turn Signtures on or off.
//
//    Return Value
//    Appropriate return values are set in the DWL_MSGRESULT longword
//    of the dialog handle.  See Win95 Common Controls documentation on
//    Property Sheets for more information.
//

BOOL CALLBACK SignatureOptionsDlgProc(HWND hDlg, UINT uMsg,
        WPARAM wParam, LPARAM lParam)
{
 BOOL bMsgResult;
 static HBRUSH hBrush;
 static COLORREF GrayColor;
 static LPNMHDR pnmhdr;
 static HWND hWndPage;

 switch (uMsg)
 {

  case WM_INITDIALOG:
  {
   LOGBRUSH lb;

    GrayColor = (COLORREF)GetSysColor(COLOR_BTNFACE);

    memset(&lb, 0, sizeof(LOGBRUSH));
    lb.lbStyle = BS_SOLID;
    lb.lbColor = GrayColor;
    hBrush = CreateBrushIndirect(&lb);

    return TRUE;
  }
  break;

  case WM_CTLCOLORDLG:
  case WM_CTLCOLORBTN:
  case WM_CTLCOLORSTATIC:

    if (hBrush != NULL)
    {
      SetBkColor((HDC)wParam, GrayColor);

      return (BOOL)hBrush;
    }

  break;

  case WM_DESTROY:
  {
   if (hBrush != NULL)
        DeleteObject(hBrush);

   return TRUE;
  }

  case WM_COMMAND:
  {
   if (LOWORD(wParam) == IDC_ENABLESGN)
   {
      SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
      bSignatureOn = SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_GETCHECK, 0, 0L);
      bMsgResult = FALSE;
   }
  }
  break;

  case WM_NOTIFY:
  {
   pnmhdr = ((LPNMHDR) lParam);

   switch ( pnmhdr->code)
   {
    case PSN_KILLACTIVE:
        bMsgResult = FALSE;  // allow this page to receive PSN_APPLY
        break;

    case PSN_SETACTIVE:

        // initialize controls
        if (bSignatureOn)
            SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETCHECK, 1, 0L);
        else
            SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETCHECK, 0, 0L);

        hWndPage = pnmhdr->hwndFrom;   // to be used in WM_COMMAND

        bMsgResult = FALSE;  // accepts activation
        break;

    case PSN_APPLY:

        // get user input
        bSignatureOn = SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_GETCHECK, 0, 0L);

        bMsgResult = PSNRET_NOERROR;
        break;

    case PSN_HELP:
        MessageBox( pnmhdr->hwndFrom,
                    "Microsoft Exchange Client\n"
                    "Message Event Extension Sample\n"
                    "Microsoft (c) Corporation, 1995",
                    "About",
                    MB_OK);
        bMsgResult = TRUE;    // doesn't matter on this notification
        break;

    default:
        bMsgResult = FALSE;
        break;
   }  // switch

    SetWindowLong( hDlg, DWL_MSGRESULT, bMsgResult);
    break;

  }   // case WM_NOTIFY

  default:
    bMsgResult = FALSE;
    break;

 }  // switch

 return bMsgResult;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by EVNTEXT.RC
//
#define IDD_SUMMARY                     101
#define IDD_SIGNATURE                   101
#define IDC_APP                         1000
#define IDC_AUTHOR                      1001
#define IDC_COMPANY                     1002
#define IDC_CATEGORY                    1003
#define IDC_KEYWORDS                    1004
#define IDC_EDITTIME                    1005
#define IDC_LASTSAVE                    1006
#define IDC_COMMENTS                    1007
#define IDC_TITLE                       1008
#define IDC_SUBJECT                     1009
#define IDC_ENABLESGN                   1009
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1010
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

Sample Address Book

The FlatFile.AB is a sample address book service provider (SMPAB.DLL) that is
part of the prerelease MAPI SDK.

The .SAB File
-------------

The sample address book provider is based on a read-only file of fixed-length
records. Each record contains the display name and e-mail address of a
messaging user. This file supplies the contents of a single, read-only
address container, which contains only e-mail users. A separate utility
program, SAB.EXE, converts a text file to an .SAB file. Each line of input
text must be formatted as follows:

display name<tab>e-mail address

The e-mail address must not contain white space, and the input file must be
sorted on the display name; the conversion utility does not sort.

Address Type
------------

The sample address book supports one e-mail address type; it is hard-coded
to MSPEER. If you plan to use the sample address book and sample transport
together, configure the sample transport provider to use MSPEER also.

Entry Identifiers
-----------------

The sample address book registers one MAPIUID with SetProviderUID(); it is
also hard-coded. The unique part of a messaging user entry identifier is
the e-mail address. Messaging user entry identifiers use the hard-coded UID,
so they are the same across all instances of the sample address book.
The sample address book chooses a UID at random the first time it is
configured for use in its top-level container entry identifier. This enables
multiple instances of the provider, each with its own top-level container,
to coexist in a single profile.

The sample address book does not distinguish between short- and long-term
entry identifiers.

Table Implementation
--------------------

The sample address book largely delegates the implementation of its hierarchy
table to the IMAPITableData interface. For its contents table, the sample
address book implements the IMAPITable interface directly on the .SAB file.
The contents table is a good example of a caching table implementation; it
defers reading records from disk until absolutely necessary and can handle
quite large files with limited memory. The contents table also supports prefix
searching and ambiguous name resolution (ANR). The contents table
implementation closes the .SAB file if the table interface is not accessed
for a while. This makes it possible to update the .SAB file while it is in
use. If the .SAB file changes, the sample address book notices at the next
file access and issues a notification on the contents table so that the user
interface can be updated. It uses the idle task engine to do the closing.

Supported Features
------------------

Table restrictions. The sample address book implements the two most commonly
used types of restrictions for an address book: prefix match and ANR. It does
not implement the full MAPI restriction language, and restrictions are
supported only on the display name.

Prefix matching enables incremental searching through an address book
container in which the user types the initial characters of a display name.
ANR, on the other hand, matches one or more prefix strings against the entire
container, regardless of first or last name order, and returns all matching
entries; this enables checking of partial names typed into a message.

Details. The sample address book implements its own details display table
for messaging users. There are two sets of properties, divided into two pages.
The first page has the standard properties: display name, e-mail address, and
address type.  The second page has an example of almost every control type
implemented in MAPI display tables (the data is hard-coded, not drawn from
the address book). There is a template identifier for messaging user details,
which enables all details to be supported for users copied from the sample
address book to the personal address book. Details are also supported for the
top-level directory, including the ability to change the .SAB file from the
details pane. The directory details view uses a notification to update the
display when the .SAB file is changed.

Custom recipients (one-offs). The sample address book implements a one-off
table with a set of properties different from the native details; these
properties combine to form the e-mail address. The template identifier
enables messaging users to be added to the personal address book, or directly
to a message, using a special dialog box; processing is routed through the
sample address book, which has the ability to generate the e-mail address
from the properties entered in the dialog box.

Advanced search dialog box. The sample address book implements an advanced
search dialog box, accessible by choosing the Advanced button on the MAPI
addressing dialog box. In reality, the advanced search is only slightly
different from the standard search, which uses an ANR restriction.

Status interface. The IMAPIStatus interface is partially supported; its
IMAPIProp methods are delegated to the IMAPIPropData interface.

Configuration interfaces. The sample address book supports both interactive
and programmatic configuration.

Cross-platform support. The sample address book source code generates
executables for Windows 3.X, Windows 95, and Windows NT 3.5 and later from a
single set of source files.

Unsupported Features
--------------------

Sorting.

Distribution lists.

Creating, deleting, and modifying entries.

Multi-valued properties.

Named properties.

Distinguishing first and last name in display names.

Configuration
-------------

The only configurable property supported by the sample address book is the
path to the .SAB file. The path can be set interactively either through the
service configuration dialog box or at provider logon. It can also be set
programmatically through the ServiceEntry function. For the definitions
needed to set the path programmatically, see the SMPAB.H header file.

Installation
------------

To install the sample address book, you have to:

1. Merge the file smpab.inf with mapisvc.inf located in your windows system
   directory. You can do this using the mergeini utility that comes with
   the MAPI SDK. The syntax is "mergeini -m smpab.inf".

2. Copy smpab(32).dll to the windows system directory.

3. Add the sample address book to any MAPI profile using the Mail and Fax
   control panel applet.


ABCTBL.H

/***********************************************************************
 *
 *  ABCTBL.H
 *
 *  Definitions for Sample Address Book Contents Table
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

/*
 *  BookMark structure
 */

#define MAX_BOOKMARKS   10

#define BK_PEGGEDTOEND      ((ULONG)0x00000001)

typedef struct _ABCBK
{

    ULONG       ulFlags;            /*  Flags */
    FILETIME    filetime;           /*  Time of file when bookmark created */
    ULONG       ulPosition;         /*  Position of record in file */
    ABCREC      abcrec;             /*  Record associated with this bookmark */

} ABCBK, *LPABCBK;

/*
 *  _IVTABC is the structure returned to the client when
 *  the contents table is retrieved from the ab container
 *  object (ABCONT.C).
 *
 *  The first thing in the structure must be a jump table
 *  with all the IMAPITable methods in it.
 *
 */
typedef struct _IVTABC
{
    const IVTABC_Vtbl FAR * lpVtbl;

    SAB_IUnkWithLogon;

    /*
     *  Private data
     */

    /*  The list of active columns */
    LPSPropTagArray lpPTAColSet;

    /*  HANDLE of file that I'm browsing... */
    HANDLE hFile;

    /*  FILETIME time the file was last changed */
    FILETIME filetime;

    /*  Size of file */
    ULONG ulMaxPos;

    /*  Current position in file */
    ULONG ulPosition;

    /*  BookMark array... */
    LPABCBK rglpABCBK[MAX_BOOKMARKS];

    /*
     *  Restriction stuff
     */

    ULONG ulRstrDenom;

    /*  Partial Name to match */
    LPSTR lpszPartialName;

    /*  2 bit arrays of what's been checked, and what (of them) matches */
    BYTE *rgChecked;
    BYTE *rgMatched;

    ULONG   ulConnectMic;
    UINT    cAdvise;                            /* Count of registered notifications */
    LPMAPIADVISESINK * parglpAdvise;    /* Registered notifications */
    FTG ftg;                                /* Idle function tag */
    LPSTR   lpszFileName;

} IVTABC, *LPIVTABC;

/*
 *  Record for PR_INSTANCE_KEY.  Need enough information to find the record quickly as
 *  well as determine if the record is still valid.
 */
typedef struct _abcrecinstance
{
    ULONG ulRecordPosition;
    FILETIME filetime;
    char rgchzFileName[1];  /* Get's sized accordingly */

} ABCRecInstance, FAR * LPABCRecInstance;

/*
 *  Private macros
 */

/* Counts the number of bits in a byte */
#define CBitsB(b, cbits) {                               \
                                                         \
        cbits = 0;                                       \
                                                         \
        cbits = ((b)   & 0x55) + ((b>>1) & 0x55);        \
        cbits = (cbits & 0x33) + ((cbits>>2) & 0x33);    \
        cbits = (cbits & 0x0f) + (cbits>>4);             \
    }

/*
 *  Internal abctbl functions
 */

HRESULT     HrOpenFile(LPIVTABC lpIVTAbc);
void        FreeANRBitmaps(LPIVTABC lpIVTAbc);
HRESULT     HrValidateEntry (LPIVTABC lpIVTAbc, ULONG ulNewPos);
BOOL        FNameMatch (LPIVTABC lpIVTAbc, LPSTR rgchDisplayName);
BOOL        FMatched (LPIVTABC lpIVTAbc, ULONG ulNewPos);
BOOL        FChecked (LPIVTABC lpIVTAbc, ULONG ulNewPos);

/*
 *  Default column set
 */
enum {  ivalvtPR_DISPLAY_NAME,
        ivalvtPR_ENTRYID,
        ivalvtPR_ADDRTYPE,
        ivalvtPR_EMAIL_ADDRESS,
        ivalvtPR_OBJECT_TYPE,
        ivalvtPR_DISPLAY_TYPE,
        ivalvtPR_INSTANCE_KEY,
        cvalvtMax };

/*
 *  Externs defined for use in the three table modules
 */

extern const IVTABC_Vtbl vtblIVTABC;
extern const LPSPropTagArray ptagaivtabcColSet;

/* Parameter Validation*/
#define IVTABC_ValidateObject(Method, pThis)                                \
{   HRESULT hr;                                                             \
    if(IsBadReadPtr(pThis, sizeof(IVTABC)))                                 \
    {                                                                       \
        hr = ResultFromScode(E_INVALIDARG);                                 \
        DebugTraceResult(IVTABC##_##Method, hr);                            \
        return hr;                                                          \
    }                                                                       \
    if(pThis->lpVtbl != &vtblIVTABC)                                        \
    {                                                                       \
        hr = ResultFromScode(E_INVALIDARG);                                 \
        DebugTraceResult(IVTABC##_##Method, hr);                            \
        return hr;                                                          \
    }                                                                       \
}


ABCTBL1.C

/***********************************************************************
 *
 *  ABCTBL1.C
 *
 *  Contents Table
 *
 *  The contents table associated with this provider.  It's file based.
 *  The format of the .SAB file is in ABP.H.
 *
 *  This implementation of IMAPITable is retrieved by calling the
 *  GetContentsTable() method of the sample ABP's single directory (see
 *  abcont.c).
 *
 *  There are a few interesting features in this implementation.  First
 *  it's implemented on a flat file (.SAB).  Second, it actually supports
 *  an implementation of ANR (Ambiguous Name Resolution).  And lastly, it
 *  supports FindRow (limited to prefix searching on display names).
 *
 *  This is the minimum set of restrictions that your provider MUST support.
 *  It MUST have an ANR implementation and support prefix (downward) searching
 *  on whatever your table is sorted on (in this case PR_DISPLAY_NAME).
 *
 *  The browsing of a flat file is done a record at a time.  It's never
 *  completely read into memory.  It only reads records from the file when
 *  it absolutely has to (like in QueryRows).  The advantage to this is
 *  a low memory footprint and the ability to browse rather large files
 *  with decent performance.
 */
/*
 *  ANR is also supported in this implementation.  In the code will often
 *  be 'if' statements making two different code paths between the restricted
 *  and unrestricted version.  The restrictions rely on a couple of
 *  bit arrays.  Each bit corresponds (in order) to the records in the .SAB
 *  file.  One bit array, rgChecked, says whether or not a record in the
 *  .SAB file has actually been compared to the restriction.  It's initially
 *  set to all 0s - which means that none of the records have been checked.
 *  The second bit array, rgMatched, says whether or not the particular
 *  record matches the current restriction.  This array is initialized to all
 *  1s - which says that all the records in the .SAB file match the current
 *  restriction.  The reason for this is for the fraction returned in
 *  QueryPosition--By assuming that all the rows match and only updating
 *  the array when something doesn't match, the fraction returned in
 *  QueryPosition will get larger and has the effect of making the thumb-bar
 *  on a listbox implemented on top of this table to scroll down as you go
 *  down the list.
 *
 *  As a row is about to be read it is checked to see if it's been compared
 *  to the current restriction.  If it has then to determine whether or not
 *  to actually read the record and build the row we just look at the matched
 *  bit array.  If it doesn't match we go on to the next record and check
 *  again.  If it does match we read the record and build the row.
 *
 *  Only if it hasn't been checked do we actually go and read the row and
 *  compare it to the current restriction, setting the rgChecked and
 *  rgMatched bits accordingly.
 *
 *  In this implementation the ANR comparison rules are as follows:
 *  (See FNameMatch for the actual code to do this)
 *
 *    A 'word' is defined as any substring separated by separators.
 *    The separators are defined as ',', ' ', and '-'.
 *    A restriction is said to match a display name iff all the words
 *    in the restriction are all prefixes of words in the display name.
 *
 *    For example, a restriction of "b d" will match:
 *     "Barney Donovan"
 *     "Donovan, Barney"
 *     "Danielle Blumenthal"
 *    But will not match:
 *     "Darby Opal"
 *     "Ben Masters"
 *
 *  Other providers can do whatever matching they want with the ANR
 *  restriction.  For example, soundex or additional field comparisons (like
 *  email addresses).  A good (and fast) implementation will differentiate
 *  your provider from others in a positive way.
 *
 *  FindRow's implementation effectively handles prefix searching on display
 *  names (PR_DISPLAY_NAME).  It does this by binary searching the .SAB file.
 *  The only tricky part is knowing when to stop.  Basically you want to stop
 *  on the first name in the list that matches the restriction.  It's easy to
 *  do linearly, but a little more trick with a binary search.  This
 *  implementation is a reasonable example.
 *
 *
 *  This table has only the following columns:
 */

#include "abp.h"
#include "abctbl.h"
#include "sampabp.rh"

const SizedSPropTagArray(cvalvtMax, tagaivtabcColSet) =
{
    cvalvtMax,
    {
        PR_DISPLAY_NAME_A,
        PR_ENTRYID,
        PR_ADDRTYPE_A,
        PR_EMAIL_ADDRESS_A,
        PR_OBJECT_TYPE,
        PR_DISPLAY_TYPE,
        PR_INSTANCE_KEY
    }
};
const LPSPropTagArray ptagaivtabcColSet = (LPSPropTagArray) &tagaivtabcColSet;

/*
 *
 *  The following routines are implemented in the files abctbl2.c and abctbl3.c:
 *
 *
 *      IVTABC_Release
 *      IVTABC_GetStatus
 *      IVTABC_SetColumns
 *      IVTABC_QueryColumns
 *      IVTABC_GetRowCount
 *      IVTABC_SeekRow
 *      IVTABC_SeekRowApprox
 *      IVTABC_QueryPosition
 *      IVTABC_FindRow
 *      IVTABC_Restrict
 *      IVTABC_CreateBookmark
 *      IVTABC_FreeBookmark
 *      IVTABC_SortTable
 *      IVTABC_QuerySortOrder
 *      IVTABC_QueryRows
 *      IVTABC_Abort
 *      IVTABC_ExpandRow
 *      IVTABC_CollapseRow
 *      IVTABC_WaitForCompletion
 *      IVTABC_GetCollapseState
 *      IVTABC_SetCollapseState
 *
 *
 *  This file (abctbl1.c) has all the utility functions used throughout this
 *  objects methods.  They are the following:
 *
 *      HrNewIVTAbc()
 *      HrValidateEntry()
 *      FChecked()
 *      FMatched()
 *      FNameMatch()
 *      HrOpenFile()
 *      fIVTAbcIdleRoutine()
 *      FreeANRBitmaps()
 *
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

/*
 *  vtbl filled in here.
 */

const IVTABC_Vtbl vtblIVTABC =
{

    IVTABC_QueryInterface,
    (IVTABC_AddRef_METHOD *)        ROOT_AddRef,
    IVTABC_Release,
    (IVTABC_GetLastError_METHOD *)  ROOT_GetLastError,
    IVTABC_Advise,
    IVTABC_Unadvise,
    IVTABC_GetStatus,
    IVTABC_SetColumns,
    IVTABC_QueryColumns,
    IVTABC_GetRowCount,
    IVTABC_SeekRow,
    IVTABC_SeekRowApprox,
    IVTABC_QueryPosition,
    IVTABC_FindRow,
    IVTABC_Restrict,
    IVTABC_CreateBookmark,
    IVTABC_FreeBookmark,
    IVTABC_SortTable,
    IVTABC_QuerySortOrder,
    IVTABC_QueryRows,
    IVTABC_Abort,
    IVTABC_ExpandRow,
    IVTABC_CollapseRow,
    IVTABC_WaitForCompletion,
    IVTABC_GetCollapseState,
    IVTABC_SetCollapseState
};

/* Idle function */
FNIDLE fIVTAbcIdleRoutine;

#define     IVTABC_IDLE_INTERVAL        300L
#define     IVTABC_IDLE_PRIORITY        -2
#define     IVTABC_IDLE_FLAGS           FIROINTERVAL

/*************************************************************************
 *
 -  NewIVTAbc
 -
 *  Creates a new IMAPITable on the contents of a .SAB file.
 *
 *
 */
HRESULT
HrNewIVTAbc(LPMAPITABLE *       lppIVTAbc,
            LPABLOGON           lpABLogon,
            LPABCONT            lpABC,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc )
{
    LPIVTABC lpIVTAbc = NULL;
    SCODE scode;
    HRESULT hResult = hrSuccess;
    ULONG ulBK, ulSize, ulSizeHigh;

    /*
     *  Allocate space for the IVTABC structure
     */

    scode = lpAllocBuff(sizeof(IVTABC), (LPVOID *) &lpIVTAbc);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto err;
    }

    lpIVTAbc->lpVtbl = &vtblIVTABC;
    lpIVTAbc->lcInit = 1;
    lpIVTAbc->hResult = hrSuccess;
    lpIVTAbc->idsLastError = 0;

    lpIVTAbc->hLibrary = hLibrary;
    lpIVTAbc->lpAllocBuff = lpAllocBuff;
    lpIVTAbc->lpAllocMore = lpAllocMore;
    lpIVTAbc->lpFreeBuff = lpFreeBuff;
    lpIVTAbc->lpMalloc = lpMalloc;
    lpIVTAbc->lpABLogon = lpABLogon;

    lpIVTAbc->lpszFileName = NULL;
    hResult = HrLpszGetCurrentFileName(lpABLogon, &(lpIVTAbc->lpszFileName));
    if (HR_FAILED(hResult))
    {
        goto err;
    }

    lpIVTAbc->lpPTAColSet = (LPSPropTagArray) &tagaivtabcColSet;

    /*
     *  Open the .SAB file associated with this logged in session.
     *  Currently it's opened with FILE_SHARED_READ.  This has an
     *  unpleasant side-effect of not being able to modify the .SAB
     *  file while this table is active.
     *
     *  It should be OF_SHARE_DENY_NONE.  I don't have the code to
     *  handle this in this version.
     */
    if ((lpIVTAbc->hFile = CreateFile(
                lpIVTAbc->lpszFileName,
                GENERIC_READ,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL)) == INVALID_HANDLE_VALUE)
    {
        /*
         *  The file didn't open...
         */

        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
        SetErrorIDS(lpABC, hResult, IDS_CANT_OPEN_SAB);

        goto err;
    }

    /*
     *  Get the time and date stamp
     *
     */
    (void)GetFileTime(lpIVTAbc->hFile, NULL, NULL, &(lpIVTAbc->filetime));

    /*  Get the size of the file */
    if ((ulSize = GetFileSize(lpIVTAbc->hFile, &ulSizeHigh)) == 0xFFFFFFFF)
    {
        /*
         *  MAYBE I have an error
         */
        if (GetLastError() != NO_ERROR)
        {
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
            SetErrorIDS(lpABC, hResult, IDS_SAB_FILE_ATTRIB);
            goto err;
        }

        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
        SetErrorIDS(lpABC, hResult, IDS_SAB_TOO_LARGE);
        goto err;
    }

    /*
     *  Actual number of valid positions
     */
    lpIVTAbc->ulMaxPos = (ulSize / sizeof(ABCREC));

    /*
     *  Check to see if it's an exact multiple of sizeof(ABCREC)
     */
    if (lpIVTAbc->ulMaxPos * sizeof(ABCREC) != ulSize)
    {
        /*
         *  Nope.
         */

        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
        SetErrorIDS(lpABC, hResult, IDS_SAB_CORRUPT);
        goto err;
    }

    lpIVTAbc->ulPosition = 0;

    /*  Setup bookmarks  */
    for (ulBK = 0; ulBK < MAX_BOOKMARKS; lpIVTAbc->rglpABCBK[ulBK++] = NULL);

    /*
     *  Restriction stuff
     */

    lpIVTAbc->rgChecked = NULL;
    lpIVTAbc->rgMatched = NULL;
    lpIVTAbc->lpszPartialName = NULL;

    /*
     *  Registered notification stuff
     */

    lpIVTAbc->cAdvise = 0;
    lpIVTAbc->parglpAdvise = NULL;
    lpIVTAbc->ulConnectMic = (ULONG) lpIVTAbc & 0xffffff;
    InitializeCriticalSection(&lpIVTAbc->cs);

    /*
     *  Register the idle function
     */

    lpIVTAbc->ftg = FtgRegisterIdleRoutine(
        fIVTAbcIdleRoutine,
        lpIVTAbc,
        IVTABC_IDLE_PRIORITY,
        IVTABC_IDLE_INTERVAL,
        IVTABC_IDLE_FLAGS);

    InitializeCriticalSection(&lpIVTAbc->cs);

    /*  We must AddRef the lpABLogon object since we will be using it
     */
    lpABLogon->lpVtbl->AddRef(lpABLogon);

    *lppIVTAbc = (LPVOID) lpIVTAbc;

out:

    DebugTraceResult(HrNewIVTAbc, hResult);
    return hResult;

err:
    if (lpIVTAbc)
    {
        if (lpIVTAbc->hFile != INVALID_HANDLE_VALUE)
        {
            /*
             *  Must've opened the file
             */
            CloseHandle(lpIVTAbc->hFile);
        }

        /*  Free the current .SAB file name */
        lpFreeBuff(lpIVTAbc->lpszFileName);

        /*  Free the mem */
        lpFreeBuff( lpIVTAbc );
    }

    goto out;

}

/*************************************************************************
 *
 -  HrValidateEntry
 -
 *
 *  Used in restricted lists
 *  Checks to see if a given entry at a particular location matches
 *  the active restriction.  It always modifies rgChecked, and may
 *  modify rgMatched.
 */
HRESULT
HrValidateEntry(LPIVTABC lpIVTAbc, ULONG ulNewPos)
{
    ABCREC abcrec;
    ULONG cbRead;
    HRESULT hResult;

    /*
     *  Open the file
     */
    hResult = HrOpenFile(lpIVTAbc);
    if (HR_FAILED(hResult))
    {
        DebugTraceResult(HrValidateEntry, hResult);
        return hResult;
    }

    /*
     *  Set the file position to lNewPos
     */

    (void) SetFilePointer(lpIVTAbc->hFile, ulNewPos * sizeof(ABCREC),
                          NULL, FILE_BEGIN);

    /*
     *  Read in the record from the file
     */
    if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec,
            sizeof(ABCREC), &cbRead, NULL))
    {
        DebugTraceSc(HrValidateEntry, MAPI_E_DISK_ERROR);
        return ResultFromScode(MAPI_E_DISK_ERROR);
    }

    /*  Second check  */
    if (cbRead != sizeof(ABCREC))
    {
        DebugTraceSc(HrValidateEntry, MAPI_E_DISK_ERROR);
        return ResultFromScode(MAPI_E_DISK_ERROR);
    }

    /*
     *  Always set the Checked flag
     */
    lpIVTAbc->rgChecked[ulNewPos / 8] |= (((unsigned char)0x80) >> (ulNewPos % 8));

    /*
     *  See if the rgchDisplayName matches the restriction
     */
    if (!FNameMatch(lpIVTAbc, abcrec.rgchDisplayName))
    {
        /*
         *  Apparently not.  Reset the Matched flag
         */

        lpIVTAbc->ulRstrDenom--;

        lpIVTAbc->rgMatched[ulNewPos / 8] &= ~(((unsigned char)0x80) >> (ulNewPos % 8));
    }

    return hrSuccess;
}

/*************************************************************************
 *
 -  FChecked
 -
 *
 *  Returns whether or not an entry has ever been checked
 *  Just looks at the bit in the rgChecked array that corresponds with
 *  lNewPos
 *
 */
BOOL
FChecked(LPIVTABC lpIVTAbc, ULONG ulNewPos)
{
    ULONG ulT = (ULONG) (lpIVTAbc->rgChecked[ulNewPos / 8] & (((unsigned char)0x80) >> (ulNewPos % 8)));

    return (BOOL) !!ulT;
}

/*************************************************************************
 *
 -  FMatched
 -
 *
 *  Returns whether or not an entry has been matched
 *  Just checks the bit in the rgMatched array corresponding with
 *  lNewPos
 *
 */
BOOL
FMatched(LPIVTABC lpIVTAbc, ULONG ulNewPos)
{
    ULONG ulT = (lpIVTAbc->rgMatched[ulNewPos / 8] & (((unsigned char)0x80) >> (ulNewPos % 8)));

    return (BOOL) !!ulT;

}

/*************************************************************************
 *
 -  FNameMatch
 -
 *  Tries to match the rgchDisplayName with the partial name of the
 *  restriction.
 *  It tries to prefix match each partial name component (i.e. word) with
 *  each rgchDisplayName name component (i.e. word).  Only if all of them
 *  match (from the partial name) does this return TRUE.
 */
BOOL
FCharInString(LPSTR lpsz, CHAR ch);

BOOL
FNameMatch(LPIVTABC lpIVTAbc, LPSTR rgchDisplayName)
{
    LPSTR szANRSep = ", -";
    LPSTR szANR = lpIVTAbc->lpszPartialName;
    LPSTR pchEndSzANR = szANR + lstrlenA(szANR);
    ULONG cchANRName;
    ULONG cchFullNameName;
    LPSTR szFullNameT;
    LPSTR szT;

    /*  If someone tries to match more than an iwMOMax-part name, the function
     *  will return fFalse.  But then if someone is trying to match a name
     *  with iwMOMax parts, chances are they weren't going to get it right
     *  anyway....
     */

#define iwMOMax 50

    WORD rgwMO[iwMOMax];
    int iwMOMac = 0;

    while (TRUE)
    {
        /*  Find the end of the partial name we're pointing at  */

        szT = szANR;
        while (!FCharInString(szANRSep, *szT))
            ++szT;
        cchANRName = szT - szANR;

        /*  Check if it matches any name in the full name  */

        szFullNameT = rgchDisplayName;
        while (TRUE)
        {
            szT = szFullNameT;

            /*  Find the length of the name in the full name  */
            /*  we're checking against.                       */

            while (!FCharInString(szANRSep, *szT))
                ++szT;
            cchFullNameName = szT - szFullNameT;

            if (cchANRName <= cchFullNameName &&
                CompareStringA( lcidUser,
                                NORM_IGNORECASE,
                                szANR,
                                (int) cchANRName,
                                szFullNameT,
                                (int) cchANRName) == 2 )
            {
                int iwMO;

                for (iwMO = 0; iwMO < iwMOMac; iwMO++)
                    if (rgwMO[iwMO] == (WORD) (szFullNameT - rgchDisplayName))
                        break;

                /*  We found the partial name so check the next ANR part */
                if (iwMO == iwMOMac)
                {
                    if (iwMOMac == iwMOMax - 1)
                    {
                        /*  If some user wants to match an iwMOMax part
                         *  name, chances are it wasn't going to match
                         *  anyway...
                         */
                        return FALSE;
                    }
                    rgwMO[iwMOMac++] = szFullNameT - rgchDisplayName;
                    break;
                }
            }

            /*  We didn't find the partial name this time around, so
             *  try to check the next name in the full name.
             */

            szFullNameT += cchFullNameName;

            while (*szFullNameT && FCharInString(szANRSep, *szFullNameT))
                ++szFullNameT;

            if (*szFullNameT == '\0')
                return FALSE;   /*  We never found the partial name. */
        }

        /* We found the partial name, so check the next ANR part */

        szANR += cchANRName;
        while (*szANR && FCharInString(szANRSep, *szANR))
            ++szANR;

        if (*szANR == '\0')
            return TRUE;        /* No more ANR to check, so we found `em all  */
    }

    /*  Not reached (we hope...)  */
    return FALSE;
}

/*
 -  HrOpenFile
 -
 *  Opens the .SAB file associated with the table and
 *  checks whether the .SAB file has changed.
 *  If it has changed, the table bookmarks and ANR bitmaps
 *  are updated and everyone on the advise list is notified.
 */
HRESULT
HrOpenFile(LPIVTABC lpIVTAbc)
{
    HRESULT hResult = hrSuccess;
    FILETIME filetime;
    ULONG ulSize, ulSizeHigh, ulMaxPos;
    LPSTR lpszFileName = NULL;

    /*
     *  If file is not open, open it
     */
    if (lpIVTAbc->hFile == INVALID_HANDLE_VALUE)
    {

        if (!FEqualSABFiles(lpIVTAbc->lpABLogon, lpIVTAbc->lpszFileName))
        {
            /*
             *  Get the new file name
             */
            hResult = HrLpszGetCurrentFileName(lpIVTAbc->lpABLogon, &lpszFileName);
            if (HR_FAILED(hResult))
            {
                goto err;
            }

            /*
             *  Replace the old one with this
             */
            lpIVTAbc->lpFreeBuff(lpIVTAbc->lpszFileName);
            lpIVTAbc->lpszFileName = lpszFileName;

            lpszFileName = NULL;
        }

        /*
         *  File is not open so lets try to open it
         */
        lpIVTAbc->hFile = CreateFile(
            lpIVTAbc->lpszFileName,
            GENERIC_READ,
            FILE_SHARE_READ|FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

        if (lpIVTAbc->hFile == INVALID_HANDLE_VALUE)
        {
            /*
             *  The file didn't open...
             */
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
            SetErrorIDS(lpIVTAbc, hResult, IDS_CANT_OPEN_SAB_FILE);
            goto err;
        }
    }

    /*
     *  Get the time and date stamp
     */
    if (!GetFileTime(lpIVTAbc->hFile, NULL, NULL, &filetime))
    {
        if (GetLastError() != NO_ERROR)
        {
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_FILE_ATTRIB);
        }

        goto err;
    }

    /*  Get the size of the file */
    if ((ulSize = GetFileSize(lpIVTAbc->hFile, &ulSizeHigh)) == 0xFFFFFFFF)
    {
        /*
         *  MAYBE I have an error
         */
        if (GetLastError() != NO_ERROR)
        {
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_FILE_ATTRIB);
            goto err;
        }

        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
        SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_TOO_LARGE);
        goto err;
    }

    /*
     *  Actual number of valid positions
     */
    ulMaxPos = (ulSize / sizeof(ABCREC));

    /*
     *  Check to see if it's an exact multiple of sizeof(ABCREC)
     */
    if (ulMaxPos * sizeof(ABCREC) != ulSize)
    {
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
        SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_CORRUPT);
        goto err;
    }

    /*
     *  if the file has changed set new position, reset bookmarks etc and
     *  notify everybody one the advise list
     */
    if (CompareFileTime(&filetime, &lpIVTAbc->filetime) || ulMaxPos != lpIVTAbc->ulMaxPos)
    {
        ULONG ulBK;
        ABCREC abcrec;
        ULONG cbT;
        LPMAPIADVISESINK *ppadvise;
        ULONG cAdvises;
        NOTIFICATION notif;

        /* save new max position and filetime */
        lpIVTAbc->filetime = filetime;
        lpIVTAbc->ulMaxPos = ulMaxPos;

        /* if current position is past the end of file set it to the end */
        if (lpIVTAbc->ulPosition > lpIVTAbc->ulMaxPos)
            lpIVTAbc->ulPosition = lpIVTAbc->ulMaxPos;

        if (ulMaxPos)
        {
            SetFilePointer(lpIVTAbc->hFile, (ulMaxPos - 1)*sizeof(ABCREC), NULL, FILE_BEGIN);

            /*  Read in the record at that location  */
            if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec,
                    sizeof(ABCREC), &cbT, NULL))
            {
                hResult = ResultFromScode(MAPI_E_DISK_ERROR);
                SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ);

                goto err;
            }

            /* if any of the bookmarks are past the end of file
             * set the file time to current file time, the position to last
             * record and record to last record
             */
            for (ulBK = 0; ulBK < MAX_BOOKMARKS; ulBK++)
                if (lpIVTAbc->rglpABCBK[ulBK] &&
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition > lpIVTAbc->ulMaxPos)
                {
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition = ulMaxPos - 1;
                    lpIVTAbc->rglpABCBK[ulBK]->filetime = filetime;
                    lpIVTAbc->rglpABCBK[ulBK]->abcrec = abcrec;
                }

            /*
            *  Reallocate the checked&matched arrays
            */

            cbT = (lpIVTAbc->ulMaxPos) / 8 + 1; /* Number of bytes in both arrays */

            /* Reallocate ANR bitmaps */
            if (lpIVTAbc->rgChecked)
            {
                lpIVTAbc->rgChecked = lpIVTAbc->lpMalloc->lpVtbl->Realloc(
                    lpIVTAbc->lpMalloc,
                    lpIVTAbc->rgChecked,
                    cbT);
            }

            if (lpIVTAbc->rgMatched)
            {
                lpIVTAbc->rgMatched = lpIVTAbc->lpMalloc->lpVtbl->Realloc(
                    lpIVTAbc->lpMalloc,
                    lpIVTAbc->rgMatched,
                    cbT);
            }
        }
        else
        {
            /* if any of the bookmarks are past the end of file
             * set the file time to current file time, the position to the
             * beginning of the file.
             */
            for (ulBK = 0; ulBK < MAX_BOOKMARKS; ulBK++)
                if (lpIVTAbc->rglpABCBK[ulBK] &&
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition > lpIVTAbc->ulMaxPos)
                {
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition = 0;
                    lpIVTAbc->rglpABCBK[ulBK]->filetime = filetime;
                }

            /* free the ANR bitmaps */
            FreeANRBitmaps(lpIVTAbc);
        }

        /* initialize the notification */
        ZeroMemory(&notif, sizeof(NOTIFICATION));
        notif.ulEventType = fnevTableModified;
        notif.info.tab.ulTableEvent = TABLE_RELOAD;

        /* notify everyone that the table has changed */
        for (ppadvise = lpIVTAbc->parglpAdvise, cAdvises = 0;
            cAdvises < lpIVTAbc->cAdvise;
            ++ppadvise, ++cAdvises)
        {
            Assert(*ppadvise);
            if (ppadvise)
                (void)(*ppadvise)->lpVtbl->OnNotify(*ppadvise, 1, &notif);
        }
    }

out:

    DebugTraceResult(NewIVTAbc, hResult);
    return hResult;
err:
    lpIVTAbc->lpFreeBuff(lpszFileName);

    goto out;

}

/*************************************************************************
 *
 -  fIVTAbcIdleRoutine
 -
 *  This function called during idle time closes the .SAB file and notifies
 *  everyone on the advise list if the file name has changed
 *
 */

BOOL STDAPICALLTYPE
fIVTAbcIdleRoutine(LPVOID lpv)
{
    LPIVTABC lpIVTAbc = (LPIVTABC) lpv;

    Assert(lpv);

    /* if file is open close it  */
    if (lpIVTAbc->hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(lpIVTAbc->hFile);
        lpIVTAbc->hFile = INVALID_HANDLE_VALUE;
    }

    /* has file name has changed? */
    if (!FEqualSABFiles(lpIVTAbc->lpABLogon, lpIVTAbc->lpszFileName))
    {
        /* file name has changed so call HrOpenFile to reset bookmarks etc */
        if (!HR_FAILED(HrOpenFile(lpIVTAbc)))
        {
            /* close the file */
            CloseHandle(lpIVTAbc->hFile);
            lpIVTAbc->hFile = INVALID_HANDLE_VALUE;
        }
    }
    return TRUE;
}

/*************************************************************************
 *
 -  FreeANRBitmaps
 -
 *  Frees the two ANR bitmaps associated with this table
 *
 *
 */
void
FreeANRBitmaps(LPIVTABC lpIVTAbc)
{
    if (lpIVTAbc->rgChecked)
    {
        lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->rgChecked);
        lpIVTAbc->rgChecked = NULL;
    }

    if (lpIVTAbc->rgMatched)
    {
        lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->rgMatched);
        lpIVTAbc->rgMatched = NULL;
    }
}

/*
 *  FCharInString
 *
 *  Finds a character in a string
 */
BOOL
FCharInString(LPSTR lpsz, CHAR ch)
{

    while (*lpsz && *lpsz != ch)
        lpsz++;

    return (*lpsz == ch);
}


ABCTBL2.C

/***********************************************************************
 *
 *  ABCTBL2.C
 *
 *  Contents Table - Part 2.
 *
 *
 *  The following routines are implemented in this file.
 *
 *
 *      IVTABC_SeekRow
 *      IVTABC_SeekRowApprox
 *      IVTABC_GetRowCount
 *      IVTABC_QueryPosition
 *      IVTABC_FindRow
 *      IVTABC_Restrict
 *      IVTABC_QueryRows
 *
 *
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "abctbl.h"
#include "sampabp.rh"

/*************************************************************************
 *
 -  IVTABC_SeekRow
 -
 *
 *  Tries to seek an appropriate number of rows.
 *
 *
 */
STDMETHODIMP
IVTABC_SeekRow(LPIVTABC lpIVTAbc,
    BOOKMARK bkOrigin,
    LONG lRowCount,
    LONG * lplRowsSought)
{
    LONG lNewPos;
    LONG lMoved;
    LONG lDelta;
    LONG lLast;
    HRESULT hResult = hrSuccess;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(SeekRow, lpIVTAbc);

    Validate_IMAPITable_SeekRow(lpIVTAbc, bkOrigin, lRowCount, lplRowsSought);

    EnterCriticalSection(&lpIVTAbc->cs);

    if (bkOrigin == BOOKMARK_BEGINNING)
    {
        lNewPos = 0;

    }
    else if (bkOrigin == BOOKMARK_CURRENT)
    {
        lNewPos = lpIVTAbc->ulPosition;

    }
    else if (bkOrigin == BOOKMARK_END)
    {
        lNewPos = lpIVTAbc->ulMaxPos;

    }
    else
    {
        ULONG ulBK = (ULONG) bkOrigin - 3;
        LPABCBK lpABCBK = NULL;

        /*
         *  See if it's out of range
         */
        if (ulBK < 0 || ulBK >= MAX_BOOKMARKS)
        {
            /*
             *  bad book mark, it's an error, so...
             */
            hResult = ResultFromScode(E_INVALIDARG);

            goto out;
        }

        if (!(lpABCBK = lpIVTAbc->rglpABCBK[ulBK]))
        {
            /*
             *  bookmark has not been allocated
             */
            hResult = ResultFromScode(MAPI_E_INVALID_BOOKMARK);

            goto out;
        }

        /* Not validating existing bookmark  */
        lNewPos = lpABCBK->ulPosition;
    }

    /*
     *  Figure out what endpoint to use and what direction to go to
     *  get there.
     */
    if (lRowCount < 0)
    {
        lLast = 0;
        lDelta = -1;
    }
    else
    {
        lLast = lpIVTAbc->ulMaxPos;
        lDelta = 1;
    }

    /*
     *  While there's rows to seek ...
     */
    lMoved = 0;
    while (lNewPos != lLast &&
        lMoved != lRowCount)
    {
        lNewPos += lDelta;

        /*
         *  Unrestricted list is easy: just seek.  Also, if the next
         *  'row' is the end of the table, just go there; don't want
         *  to check it against any restriction.
         */
        if (!lpIVTAbc->lpszPartialName ||
            lNewPos == (LONG) lpIVTAbc->ulMaxPos)
        {
            lMoved += lDelta;
        }

        /*
         *  Otherwise, deal with the restricted list:  only count
         *  the row if it's in the restriction.
         */
        else
        {
            if (!FChecked(lpIVTAbc, (ULONG) lNewPos))
            {
                hResult = HrValidateEntry(lpIVTAbc, (ULONG) lNewPos);

                if (HR_FAILED(hResult))
                {
                    goto out;
                }
            }

            if (FMatched(lpIVTAbc, (ULONG) lNewPos))
            {
                lMoved += lDelta;
            }
        }
    }

    if (lplRowsSought)
        *lplRowsSought = lMoved;

    lpIVTAbc->ulPosition = lNewPos;

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_SeekRow, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_SeekRowApprox
 -
 *  Tries to set the position of the table according to the approximate
 *  position passed in.
 *
 *
 */
STDMETHODIMP
IVTABC_SeekRowApprox(LPIVTABC lpIVTAbc,
    ULONG ulNumerator,
    ULONG ulDenominator)
{
    HRESULT hResult = hrSuccess;
    ULONG iByte;
    BYTE bCount;
    ULONG ulPos = 0;
    ULONG ulCount = 0;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(SeekRowApprox, lpIVTAbc);

    Validate_IMAPITable_SeekRowApprox(lpIVTAbc, ulNumerator, ulDenominator);

    EnterCriticalSection(&lpIVTAbc->cs);

    if (ulNumerator >= ulDenominator)
    {
        /*  We're at the end of the list */
        lpIVTAbc->ulPosition = lpIVTAbc->ulMaxPos;

        hResult = hrSuccess;
        goto out;
    }

    /*
     *  Since I'm using muldiv() which takes ints/longs, I should shift right
     *  so that I don't incorrectly call it.
     *  I'm really just checking to see if the sign bit is set...
     */

    if (((long)ulNumerator < 0) || ((long)ulDenominator < 0))
    {
        ulNumerator >>= 1;
        ulDenominator >>= 1;
    }

    if (!lpIVTAbc->lpszPartialName)
    {
        /*
         *  The NON-Restriction method
         */

        lpIVTAbc->ulPosition = MULDIV(lpIVTAbc->ulMaxPos, ulNumerator,
            ulDenominator);

        hResult = hrSuccess;
        goto out;
    }

    /*
     *  Restriction method
     */

    /*  Figure out % of which corresponds with numerator. */
    ulCount = MULDIV(lpIVTAbc->ulRstrDenom, ulNumerator, ulDenominator);

    /*  Count bits in rgMatched until I match numerator */

    for (iByte = 0; iByte < (lpIVTAbc->ulMaxPos / 8); iByte++)
    {
        CBitsB(lpIVTAbc->rgMatched[iByte], bCount); /* <-- MACRO  */
        ulPos += (ULONG) bCount;

        if (ulPos >= ulCount)
        {
            ulPos -= bCount;    /* Go back a byte */
            break;
        }
    }

    /*  My current position is there. */
    lpIVTAbc->ulPosition = iByte * 8;

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_SeekRowApprox, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_GetRowCount
 -
 *
 *  If there's a restriction applied, I don't necessarily know how many
 *  rows there are...
 */

STDMETHODIMP
IVTABC_GetRowCount(LPIVTABC lpIVTAbc,
    ULONG ulFlags,
    ULONG * lpulCount)
{
    HRESULT hResult;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(GetRowCount, lpIVTAbc);

    Validate_IMAPITable_GetRowCount(lpIVTAbc, ulFlags, lpulCount);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  If there's no restriction, you can actually calculate this..
     */
    if (!lpIVTAbc->lpszPartialName)
    {

        /*
         *  Number of actual rows
         */
        *lpulCount = lpIVTAbc->ulMaxPos;
        hResult = hrSuccess;
        goto out;
    }

    /*
     *  There's no way that I can tell how many actual entries there
     *  are without counting them.  That takes way too long, so we give
     *  the client our best guess.
     */

    *lpulCount = lpIVTAbc->ulRstrDenom;

    /*
     *  Then we warn the client that this count may not be accurate
     */

    hResult = ResultFromScode(MAPI_W_APPROX_COUNT);

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_GetRowCount, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_QueryPosition
 -
 *  Figures out the current fractional position
 *
 *
 *
 */
STDMETHODIMP
IVTABC_QueryPosition(LPIVTABC lpIVTAbc,
    ULONG * lpulRow,
    ULONG * lpulNumerator,
    ULONG * lpulDenominator)
{
    HRESULT hResult = hrSuccess;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(QueryPosition, lpIVTAbc);

    Validate_IMAPITable_QueryPosition(lpIVTAbc, lpulRow, lpulNumerator,
                                        lpulDenominator);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*  ...I don't have a restriction  */
    if (!lpIVTAbc->lpszPartialName)
    {
        *lpulRow = lpIVTAbc->ulPosition;

        *lpulNumerator = lpIVTAbc->ulPosition;
        *lpulDenominator = lpIVTAbc->ulMaxPos;
    }
    else
    {
        BYTE bCount = 0;
        BYTE bFrag;
        ULONG iByte;

        /*
         *  Zero out fraction
         */
        *lpulNumerator = 0;

        /*
         *  Set denominator that we've been keeping track of.
         */
        *lpulDenominator = (lpIVTAbc->ulRstrDenom ? lpIVTAbc->ulRstrDenom : 1);

        /*
         *  Handle corner case - we're at the beginning of the list...
         */
        if (lpIVTAbc->ulPosition == 0)
        {
            *lpulRow = 0;
            goto out;
        }

        /*
         *  Calculate Numerator
         *  We use the rgMatched bit array and count the bits up to
         *  our current position (lpIVTAbc->ulPosition).
         *
         */
        for (iByte = 0; iByte < (lpIVTAbc->ulPosition / 8); iByte++)
        {
            CBitsB(lpIVTAbc->rgMatched[iByte], bCount); /* <-- MACRO  */
            *lpulNumerator += (ULONG) bCount;
        }
        /*  Count the fragment  */
        bFrag = lpIVTAbc->rgMatched[iByte];
        bFrag = bFrag >> (8 - (lpIVTAbc->ulPosition % 8));

        CBitsB(bFrag, bCount);
        *lpulNumerator += (ULONG) bCount;

        /*
         *  Good guess here...
         */
        *lpulRow = *lpulNumerator;

    }

out:

    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_QueryPosition, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_FindRow
 -
 *
 *  Prefix searches to find a row
 *
 *
 */
STDMETHODIMP
IVTABC_FindRow(LPIVTABC lpIVTAbc,
    LPSRestriction lpRestriction,
    BOOKMARK bkOrigin,
    ULONG ulFlags)
{
    HRESULT hResult = hrSuccess;

    ULONG cbRead;
    ABCREC abcrec;
    LONG lpos;
    ULONG fFound = FALSE;
    LPSTR szPrefix;
    int nCmpResult;

    ULONG ulCurMin;
    ULONG ulCurMac;
    ULONG ulPosT;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(FindRow, lpIVTAbc);

    Validate_IMAPITable_FindRow(lpIVTAbc, lpRestriction, bkOrigin, ulFlags);

    /*
     *  I don't go backwards, yet.
     */
    if (ulFlags & DIR_BACKWARD)
    {
        hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

        DebugTraceResult(IVTABC_FindRow, hResult);
        return hResult;
    }

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Open the file
     */
    hResult = HrOpenFile(lpIVTAbc);
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    /*
     *  initialize
     */
    ulCurMin = lpIVTAbc->ulPosition;
    ulCurMac = lpIVTAbc->ulMaxPos;

    /*
     *  I handle two type of restrictions:
     *      prefix searching on Display Names
     *      Finding a row based on it's instance key
     */

    /*
     *  The prefix search on display name restriction looks like:
     *
     *  +-----------------
     *  | RES_PROPERTY
     *  +-----------------
     *  | RELOP_GE
     *  +-----------------
     *  | PR_DISPLAY_NAME
     *  +-----------------
     *  | LPSPropVal   -----+
     *  +-----------------  |
     *                      |   +-------------------------
     *                      +-->| PR_DISPLAY_NAME
     *                          +-------------------------
     *                          | 0 <-- for alignment (don't care)
     *                          +-------------------------
     *                          | lpszA <-- prefix for display name
     *                          +-------------------------
     *
     *
     *
     *              -OR-
     *
     *  Find a row based on it's instance key
     *
     *  +-----------------
     *  | RES_PROPERTY
     *  +-----------------
     *  | RELOP_EQ
     *  +-----------------
     *  | PR_INSTANCE_KEY
     *  +-----------------
     *  | LPSPropVal   -----+
     *  +-----------------  |
     *                      |   +-------------------------
     *                      +-->| PR_INSTANCE_KEY
     *                          +-------------------------
     *                          |     | cbInstanceKey
     *                          + bin +-------------------
     *                          |     | lpbInstanceKey
     *                          +-------------------------
     *
     *
     *  If it doesn't look like one of these, return MAPI_E_TOO_COMPLEX.
     */

    /*
     *  Both restrictions require this one
     */
    if (lpRestriction->rt != RES_PROPERTY)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    /*
     *  Look for Instance Key first - it's easiest to handle
     */
    if (lpRestriction->res.resProperty.relop == RELOP_EQ)
    {
        LPABCRecInstance lpABCRecInstance;

        if (lpRestriction->res.resProperty.ulPropTag != PR_INSTANCE_KEY)
        {
            /*
             *  Clearly something we don't recognize
             */
            hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

            goto out;
        }

        /*
         *  Crack the bin part of this restriction and
         *  see if we can still find our way back to this
         *  record - quickly...
         */
        lpABCRecInstance = (LPABCRecInstance) lpRestriction->res.resProperty.lpProp->Value.bin.lpb;

        /*
         *  First check to see that we're browsing the same file
         */
        if (lstrcmp(lpABCRecInstance->rgchzFileName, lpIVTAbc->lpszFileName))
        {
            /*
             *  Nope, different names, return not found and leave our position alone...
             */
            hResult = ResultFromScode(MAPI_E_NOT_FOUND);
            goto out;
        }

        /*
         *  Ok, so we think we're browsing the same file.  Has it been modified since the
         *  last time we looked?
         */
        if (memcmp(&(lpABCRecInstance->filetime), &(lpIVTAbc->filetime), sizeof (FILETIME)))
        {
            /*
             *  Nope, they're different, so no guarantees here...
             */
            hResult = ResultFromScode(MAPI_E_NOT_FOUND);
            goto out;
        }

        /*
         *  Now, I feel pretty confident about this instance key.  Just set my current position
         *  and go for it.
         */
        lpIVTAbc->ulPosition = lpABCRecInstance->ulRecordPosition;

        /* Done */
        goto out;

    }

    /*
     *  Now we're looking for prefix searching on display name
     */
    if (lpRestriction->res.resProperty.relop != RELOP_GE)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    if (lpRestriction->res.resProperty.ulPropTag != PR_DISPLAY_NAME_A)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    szPrefix = lpRestriction->res.resProperty.lpProp->Value.lpszA;

    if (bkOrigin == BOOKMARK_BEGINNING)
    {
        ulCurMin = 0;

    }
    else if (bkOrigin == BOOKMARK_END)
    {
        ulCurMin = lpIVTAbc->ulMaxPos;

    }
    else if (bkOrigin != BOOKMARK_CURRENT)
    {
        ULONG ulBK = (ULONG) bkOrigin - 3;
        LPABCBK lpABCBK = NULL;

        /*
         *  See if it's out of range
         */
        if (ulBK < 0 || ulBK >= MAX_BOOKMARKS)
        {
            /*
             *  bad book mark, it's an error, so...
             */
            hResult = ResultFromScode(E_INVALIDARG);

            goto out;
        }

        if (!(lpABCBK = lpIVTAbc->rglpABCBK[ulBK]))
        {
            /*
             *  bookmark has not been allocated
             */
            hResult = ResultFromScode(MAPI_E_INVALID_BOOKMARK);

            goto out;
        }

        /*  Not validating existing bookmark  */
        ulCurMin = lpABCBK->ulPosition;
    }

    while (ulCurMin < ulCurMac)
    {
        /*
         *  Look for a row which matches the table restriction (if any).
         */
        ulPosT = (ulCurMin + ulCurMac) / 2;

        lpos = (long)((long)ulPosT * (long)sizeof(ABCREC));

        SetFilePointer(lpIVTAbc->hFile, lpos, NULL, FILE_BEGIN);

        /*  Read in the record at that location  */
        if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec,
                sizeof(ABCREC), &cbRead, NULL))
        {
            hResult = ResultFromScode(MAPI_E_DISK_ERROR);
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ);

            goto out;
        }

        /*
         *  I want case insensitive comparisons here...
         */
        nCmpResult = lstrcmpi(szPrefix, abcrec.rgchDisplayName);

        if (nCmpResult > 0)
        {
            ulCurMin = ulPosT + 1;
        }
        else
        {
            ulCurMac = ulPosT;
            if (!lpIVTAbc->lpszPartialName ||
                FNameMatch(lpIVTAbc, abcrec.rgchDisplayName))
            {
                fFound = TRUE;
            }
        }
    }

    /*
     *  If I didn't find a row, return MAPI_E_NOT_FOUND.
     */
    if (!fFound)
    {
        hResult = ResultFromScode(MAPI_E_NOT_FOUND);

        goto out;
    }

    /*
     *  Otherwise, set the current position to the row found.
     */
    lpIVTAbc->ulPosition = ulCurMac;

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_FindRow, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_Restrict
 -
 *
 *      Should just support ANR type restrictions...
 */
STDMETHODIMP
IVTABC_Restrict(LPIVTABC lpIVTAbc,
    LPSRestriction lpRestriction,
    ULONG ulFlags)
{
    LPSTR szT = NULL;
    LPSTR lpszTNew = NULL;
    ULONG cbTB = 0;
    BYTE bFilter = 0;
    SCODE scode;
    HRESULT hResult = hrSuccess;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(Restrict, lpIVTAbc);

    Validate_IMAPITable_Restrict(lpIVTAbc, lpRestriction, ulFlags);

    /*
     *  I only handle ANR type restrictions
     */

    /*
     *  Check to see if they're resetting the restrictions
     */
    if (!lpRestriction)
    {
        EnterCriticalSection(&lpIVTAbc->cs);

        if (lpIVTAbc->lpszPartialName)
            (*(lpIVTAbc->lpFreeBuff)) (lpIVTAbc->lpszPartialName);
        lpIVTAbc->lpszPartialName = NULL;

        FreeANRBitmaps(lpIVTAbc);

        LeaveCriticalSection(&lpIVTAbc->cs);

        return hrSuccess;
    }

    /*
     *  The restriction must look like:
     *
     *
     *  +--------------
     *  | RES_PROPERTY
     *  +--------------
     *  | RELOP_EQ
     *  +--------------
     *  | PR_ANR
     *  +--------------
     *  | LPSPropVal ---+
     *  +-------------- |
     *                  |   +-------------------------
     *                  +-->| PR_ANR
     *                      +-------------------------
     *                      | 0 <-- for alignment (don't care)
     *                      +-------------------------
     *                      | lpszA <-- string to ANR on
     *                      +-------------------------
     *
     *
     *
     *  If it doesn't look like this, return MAPI_E_TOO_COMPLEX.
     *
     */

    if (lpRestriction->rt != RES_PROPERTY)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    if (lpRestriction->res.resProperty.relop != RELOP_EQ)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    if (lpRestriction->res.resProperty.ulPropTag != PR_ANR)
    {
        hResult = ResultFromScode(MAPI_E_TOO_COMPLEX);

        goto out;
    }

    /*
     *  NULL string is not defined - it's a bad restriction
     */
    if (!lpRestriction->res.resProperty.lpProp->Value.lpszA)
    {
        hResult = ResultFromScode(E_INVALIDARG);

        goto out;
    }

    szT = lpRestriction->res.resProperty.lpProp->Value.lpszA;

    /*
     *  Skip over leading spaces
     */

    while (*szT == ' ')
        szT++;

    /*
     *  Empty string is not defined - it's a bad restriction
     */
    if (*szT == '\0')
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    /*
     *  Copy the string for the partial name
     */

    scode = lpIVTAbc->lpAllocBuff(lstrlenA(szT) + 1, (LPVOID *) &lpszTNew);
    if (FAILED(scode))
    {
        /*
         *  Memory error
         */

        hResult = ResultFromScode(scode);
        goto out;
    }
    lstrcpyA(lpszTNew, szT);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Clear up any old restriction
     */

    if (lpIVTAbc->lpszPartialName)
        lpIVTAbc->lpFreeBuff(lpIVTAbc->lpszPartialName);

    lpIVTAbc->lpszPartialName = lpszTNew;

    FreeANRBitmaps(lpIVTAbc);

    /*
     *  Allocate enough bits for the checked&matched arrays
     */
    cbTB = (lpIVTAbc->ulMaxPos) / 8 + 1;    /* Number of bytes in both arrays */

    lpIVTAbc->rgChecked = lpIVTAbc->lpMalloc->lpVtbl->Alloc(
        lpIVTAbc->lpMalloc,
        cbTB);

    if (lpIVTAbc->rgChecked == NULL)
    {
        lpIVTAbc->lpFreeBuff(lpIVTAbc->lpszPartialName);
        lpIVTAbc->lpszPartialName = NULL;

        hResult = ResultFromScode(scode);

        LeaveCriticalSection(&lpIVTAbc->cs);
        goto out;
    }

    lpIVTAbc->rgMatched = lpIVTAbc->lpMalloc->lpVtbl->Alloc(
        lpIVTAbc->lpMalloc,
        cbTB);

    if (lpIVTAbc->rgMatched == NULL)
    {
        (*(lpIVTAbc->lpFreeBuff)) (lpIVTAbc->lpszPartialName);
        lpIVTAbc->lpszPartialName = NULL;

        lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->rgChecked);
        lpIVTAbc->rgChecked = NULL;

        hResult = ResultFromScode(scode);

        LeaveCriticalSection(&lpIVTAbc->cs);
        goto out;
    }

    /*
     *  Initialize the checked array with 0's
     */

    FillMemory(lpIVTAbc->rgChecked, (UINT) cbTB, 0x00);

    /*
     *  Initialize the matched array with 1's
     *  [we assume that if you haven't checked it, it matches]
     */

    FillMemory(lpIVTAbc->rgMatched, (UINT) cbTB, 0xFF);

    /*
     *  Fill in the end bits so we don't have to worry about them
     *  later
     */
    bFilter = (0xFF >> (lpIVTAbc->ulMaxPos % 8));

    /*  Checked end bits should be 1 */
    lpIVTAbc->rgChecked[cbTB - 1] = bFilter;

    /*  Matched end bits should be 0 */
    lpIVTAbc->rgMatched[cbTB - 1] = ~bFilter;

    /*
     *  Set the currenly known total number of rows
     *  that match the restriction.
     */

    lpIVTAbc->ulRstrDenom = lpIVTAbc->ulMaxPos;

    LeaveCriticalSection(&lpIVTAbc->cs);

out:

    DebugTraceResult(IVTABC_Restrict, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_QueryRows
 -
 *  Attempts to retrieve ulRowCount rows from the .SAB file.  Even in the
 *  restricted case.
 *
 *
 */
STDMETHODIMP
IVTABC_QueryRows(LPIVTABC lpIVTAbc,
    LONG lRowCount,
    ULONG ulFlags,
    LPSRowSet * lppRows)
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    LPSRowSet lpRowSet = NULL;
    LPSPropValue lpRow = NULL;
    int cbSizeOfRow;
    int cRows, iRow;
    int cCols;
    DWORD cbRead;
    ABCREC abcrec;
    ULONG ulOrigPosition;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(QueryRows, lpIVTAbc);

    Validate_IMAPITable_QueryRows(lpIVTAbc, lRowCount, ulFlags, lppRows);

    if (lRowCount < 0)
    {
        hResult = ResultFromScode(E_INVALIDARG);

        DebugTraceResult(IVTABC_QueryRows, hResult);
        return hResult;
    }

    /*
     *  Check the flags
     */
    if (ulFlags & ~TBL_NOADVANCE)
    {
        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);

        DebugTraceResult(IVTABC_QueryRows, hResult);
        return hResult;
    }

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Open the file
     */
    hResult = HrOpenFile(lpIVTAbc);
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    ulOrigPosition = lpIVTAbc->ulPosition;

    /*
     *  Calculate # of rows that will be read.
     */
    cRows = (int)lpIVTAbc->ulMaxPos - (int)lpIVTAbc->ulPosition;
    cRows = (cRows < (int)lRowCount ? cRows : (int)lRowCount);

    /*
     *  Allocate array for SRowSet
     */

    scode = lpIVTAbc->lpAllocBuff(sizeof(ULONG) + cRows * sizeof(SRow),
                                        (LPVOID *) &lpRowSet);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);

        goto out;
    }

    /*
     *  Initialize - so we can clean up later if we have to...
     */
    lpRowSet->cRows = cRows;

    for (iRow = 0; iRow < cRows; iRow++)
        lpRowSet->aRow[iRow].lpProps = NULL;

    /*
     *  Seek to correct position in file
     */
    (void) SetFilePointer(lpIVTAbc->hFile, lpIVTAbc->ulPosition * sizeof(ABCREC),
                        NULL, FILE_BEGIN);

    /*
     *  Read each row from the file
     */
    for (iRow = 0; iRow < cRows; iRow++)
    {

        /*  The only properties available are:
         *
         *  PR_DISPLAY_NAME, PR_ENTRYID, PR_ADDRTYPE, PR_EMAIL_ADDRESS,
         *  PR_OBJECT_TYPE, PR_DISPLAY_TYPE
         */

        /*
         *  Handle restricted lists
         */
        if (lpIVTAbc->lpszPartialName)
        {
            ULONG ulPos = lpIVTAbc->ulPosition;

next:
            if (ulPos == lpIVTAbc->ulMaxPos)
            {
                break;
            }

            if (!FChecked(lpIVTAbc, ulPos))
            {
                hResult = HrValidateEntry(lpIVTAbc, ulPos);
                if (HR_FAILED(hResult))
                {
                    goto err;
                }
            }

            if (!FMatched(lpIVTAbc, ulPos))
            {
                ulPos++;
                goto next;
            }

            lpIVTAbc->ulPosition = ulPos;
            (void) SetFilePointer(lpIVTAbc->hFile, lpIVTAbc->ulPosition * sizeof(ABCREC),
                                    NULL, FILE_BEGIN);

        }

        lpIVTAbc->ulPosition++;

        /*
         *  Read in the record from the file
         */
        if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec,
                sizeof(ABCREC), &cbRead, NULL))
        {
            hResult = ResultFromScode(MAPI_E_DISK_ERROR);
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ);

            goto err;
        }
        /*  Second check  */
        if ((UINT) cbRead != sizeof(ABCREC))
        {
            /*
             *  Should never get here.
             */
            hResult = ResultFromScode(MAPI_E_DISK_ERROR);
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ);

            goto err;
        }

        /*  Allocate memory to start a row.
         */
        cbSizeOfRow = sizeof(ULONG) +
            (int)lpIVTAbc->lpPTAColSet->cValues * sizeof(SPropValue);

        scode = lpIVTAbc->lpAllocBuff(cbSizeOfRow, (LPVOID *) &lpRow);
        if (FAILED(scode))
        {
            hResult = ResultFromScode(scode);

            goto err;
        }

        /*
         *  Get all the data
         */
        for (cCols = 0; cCols < (int)lpIVTAbc->lpPTAColSet->cValues; cCols++)
        {
            switch (lpIVTAbc->lpPTAColSet->aulPropTag[cCols])
            {
            case PR_DISPLAY_NAME_A:
                {

                    lpRow[cCols].ulPropTag = PR_DISPLAY_NAME_A;
                    scode = lpIVTAbc->lpAllocMore (lstrlenA(abcrec.rgchDisplayName) + 1,

lpRow,
                                                (LPVOID *) &(lpRow[cCols].Value.lpszA));

                    if (FAILED(scode))
                    {
                        lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                            PROP_ID(PR_DISPLAY_NAME_A));
                        lpRow[cCols].Value.err = scode;
                    }
                    else
                    {

                        lstrcpyA(lpRow[cCols].Value.lpszA,
                            abcrec.rgchDisplayName);
                    }

                }
                break;

            case PR_EMAIL_ADDRESS_A:
                {

                    lpRow[cCols].ulPropTag = PR_EMAIL_ADDRESS_A;
                    scode = lpIVTAbc->lpAllocMore (
                                lstrlenA(abcrec.rgchEmailAddress) + 1,
                                lpRow, (LPVOID *) &(lpRow[cCols].Value.lpszA));

                    if (FAILED(scode))
                    {
                        lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                            PROP_ID(PR_EMAIL_ADDRESS_A));
                        lpRow[cCols].Value.err = scode;
                    }
                    else
                    {

                        lstrcpyA(lpRow[cCols].Value.lpszA,
                            abcrec.rgchEmailAddress);
                    }

                }
                break;

            case PR_ADDRTYPE:
                {
                    /*
                     *  AddrType is always "MSPEER" for the SAB
                     */

                    lpRow[cCols].ulPropTag = PR_ADDRTYPE_A;
                    scode = lpIVTAbc->lpAllocMore (lstrlenA(lpszEMT) + 1,
                                lpRow, (LPVOID *) &(lpRow[cCols].Value.lpszA));

                    if (FAILED(scode))
                    {
                        lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                            PROP_ID(PR_ADDRTYPE_A));
                        lpRow[cCols].Value.err = scode;
                    }
                    else
                    {

                        lstrcpyA(lpRow[cCols].Value.lpszA, lpszEMT);
                    }

                }
                break;

            case PR_ENTRYID:
                {
                    /*
                     *  Fixed sized entryid.  Basically just the .SAB file record
                     */
                    LPUSR_ENTRYID lpUsrEid;

                    lpRow[cCols].ulPropTag = PR_ENTRYID;
                    scode = lpIVTAbc->lpAllocMore (sizeof(USR_ENTRYID), lpRow,
                                (LPVOID *) &(lpRow[cCols].Value.bin.lpb));

                    if (FAILED(scode))
                    {
                        lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                            PROP_ID(PR_ENTRYID));
                        lpRow[cCols].Value.err = scode;
                    }
                    else
                    {
                        lpUsrEid = (LPUSR_ENTRYID) lpRow[cCols].Value.bin.lpb;

                        ZeroMemory(lpUsrEid, sizeof(USR_ENTRYID));

                        /*  Size of entryid */
                        lpRow[cCols].Value.bin.cb = sizeof(USR_ENTRYID);

                        lpUsrEid->abFlags[0] = 0;   /*  long-term, recipient */
                        lpUsrEid->abFlags[1] = 0;
                        lpUsrEid->abFlags[2] = 0;
                        lpUsrEid->abFlags[3] = 0;
                        lpUsrEid->muid = muidABSample;
                        lpUsrEid->ulVersion = SAMP_VERSION;
                        lpUsrEid->ulType = SAMP_USER;
                        lpUsrEid->abcrec = abcrec;
                    }

                }
                break;

            case PR_OBJECT_TYPE:
                {
                    /*
                     *  MailUser
                     */

                    lpRow[cCols].ulPropTag = PR_OBJECT_TYPE;
                    lpRow[cCols].Value.ul = MAPI_MAILUSER;
                }
                break;

            case PR_DISPLAY_TYPE:
                {
                    /*
                     *  MailUser
                     */

                    lpRow[cCols].ulPropTag = PR_DISPLAY_TYPE;
                    lpRow[cCols].Value.ul = DT_MAILUSER;

                }
                break;

            case PR_INSTANCE_KEY:
                {
                    LPABCRecInstance lpABCRecInstance;
                    UINT cbRecInstance;
                    /*
                     *  Instance keys are made up of:
                     *      ulRecordPosition - current position in the file
                     *      filetime         - current date and time stamp of file
                     *      lpszFileName     - current file that we're browsing
                     */
                    lpRow[cCols].ulPropTag = PR_INSTANCE_KEY;

                    cbRecInstance = sizeof(ABCRecInstance)+lstrlen(lpIVTAbc->lpszFileName)+1;
                    scode = lpIVTAbc->lpAllocMore(cbRecInstance,
                                                lpRow,
                                                (LPVOID) &(lpRow[cCols].Value.bin.lpb));

                    if (FAILED(scode))
                    {
                        lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                            PROP_ID(PR_INSTANCE_KEY));
                        lpRow[cCols].Value.err = scode;
                    }
                    else
                    {
                        lpABCRecInstance = (LPABCRecInstance) lpRow[cCols].Value.bin.lpb;

                        ZeroMemory(lpABCRecInstance, cbRecInstance);

                        lpRow[cCols].Value.bin.cb = (ULONG) cbRecInstance;

                        lpABCRecInstance->ulRecordPosition = lpIVTAbc->ulPosition;
                        lpABCRecInstance->filetime = lpIVTAbc->filetime;
                        lstrcpy(lpABCRecInstance->rgchzFileName, lpIVTAbc->lpszFileName);

                    }
                }
                break;

            default:
                {

                    lpRow[cCols].ulPropTag = PROP_TAG(PT_ERROR,
                        PROP_ID(lpIVTAbc->lpPTAColSet->aulPropTag[cCols]));
                    lpRow[cCols].Value.err = MAPI_E_NOT_FOUND;
                }
                break;
            }
        }

        /*  # of columns  */
        lpRowSet->aRow[iRow].cValues = lpIVTAbc->lpPTAColSet->cValues;

        /*  Actual row of data */
        lpRowSet->aRow[iRow].lpProps = lpRow;

        lpRow = NULL;

    }

    /*
     *  it's always iRow.
     */
    lpRowSet->cRows = iRow;

    /*
     *  Handle Seeked position stuff
     */
    if (ulFlags & TBL_NOADVANCE)
    {
        /*
         *  Set it back to it's original position
         */
        lpIVTAbc->ulPosition = ulOrigPosition;
    }

    *lppRows = lpRowSet;

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_QueryRows, hResult);
    return hResult;

err:
    /*
     *  Clean up memory...
     */

    /*  Free the row  */
    lpIVTAbc->lpFreeBuff(lpRow);

    /*  Clean up the rest of the rows  */
    FreeProws(lpRowSet);

    *lppRows = NULL;

    goto out;

}


ABCTBL3.C

/***********************************************************************
 *
 *  ABCTBL3.C
 *
 *  Contents Table - Part 3.
 *
 *
 *  The following routines are implemented in this file.
 *
 *
 *      IVTABC_QueryInterface
 *      IVTABC_Release
 *      IVTABC_SortTable
 *      IVTABC_QuerySortOrder
 *      IVTABC_CreateBookmark
 *      IVTABC_FreeBookmark
 *      IVTABC_ExpandRow
 *      IVTABC_ColapseRow
 *      IVTABC_WaitForCompletion
 *      IVTABC_Abort
 *      IVTABC_Advise
 *      IVTABC_Unadvise
 *      IVTABC_GetStatus
 *      IVTABC_SetColumns
 *      IVTABC_QueryColumns
 *      IVTABC_GetCollapseState,
 *      IVTABC_SetCollapseState,
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "abctbl.h"
#include "sampabp.rh"

/*
 *  Default sort order set
 */
static const SizedSSortOrderSet(1, sosIVTABC) =
{
    1,
    0,
    0,
    {
        {
            PR_DISPLAY_NAME_A, TABLE_SORT_ASCEND
        }
    }
};

/*************************************************************************
 *
 *
 -  AVTABC_QueryInterface
 -
 *
 *
 *
 */
STDMETHODIMP
IVTABC_QueryInterface(LPIVTABC lpIVTAbc,
    REFIID lpiid,
    LPVOID FAR * lppNewObj)
{

    HRESULT hResult = hrSuccess;

    IVTABC_ValidateObject(QueryInterface, lpIVTAbc);

    Validate_IUnknown_QueryInterface(lpIVTAbc, lpiid, lppNewObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
    {
        *lppNewObj = NULL;      /* OLE requires zeroing the [out] parameter */
        DebugTraceSc(IVTABC_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpIVTAbc->cs);
    ++lpIVTAbc->lcInit;
    LeaveCriticalSection(&lpIVTAbc->cs);

    *lppNewObj = lpIVTAbc;

    DebugTraceResult(IVTABC_QueryInterface,hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_Release
 -
 *
 *      Decrement the reference count on this object and free it if
 *      the reference count is zero.
 *      Returns the reference count.
 */

STDMETHODIMP_(ULONG)
IVTABC_Release(LPIVTABC lpIVTAbc)
{
    ULONG ulBK;
    long lcInit;

    /*
     *  Check to see if it's big enough to hold this object
     */
    if (IsBadReadPtr(lpIVTAbc, sizeof(IVTABC)))
    {
        /*
         *  Not large enough
         */
        return 1;
    }

    /*
     *  Check to see that it's the correct vtbl
     */
    if (lpIVTAbc->lpVtbl != &vtblIVTABC)
    {
        /*
         *  Not my vtbl
         */
        return 1;
    }

    Validate_IUnknown_Release(lpIVTAbc);

    EnterCriticalSection(&lpIVTAbc->cs);
    lcInit = --lpIVTAbc->lcInit;
    LeaveCriticalSection(&lpIVTAbc->cs);

    if (lcInit == 0)
    {
        /*
         *  Free up the current column set
         */
        if (lpIVTAbc->lpPTAColSet != ptagaivtabcColSet)
        {
            lpIVTAbc->lpFreeBuff (lpIVTAbc->lpPTAColSet);
        }

        /*
         *  Close up the file
         */
        if (lpIVTAbc->hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(lpIVTAbc->hFile);
            lpIVTAbc->hFile = INVALID_HANDLE_VALUE;
        }

        /*
         *  Free up the file name
         */
        lpIVTAbc->lpFreeBuff(lpIVTAbc->lpszFileName);

        /*
         *  Rip through the bookmarks and free up any that are there
         */
        for (ulBK = 0; ulBK < MAX_BOOKMARKS; ulBK++)
            if (lpIVTAbc->rglpABCBK[ulBK])
            {
                (*(lpIVTAbc->lpFreeBuff)) (lpIVTAbc->rglpABCBK[ulBK]);
                lpIVTAbc->rglpABCBK[ulBK] = NULL;
            }

        /*
         *  Free up the ANR stuff, if used
         */
        lpIVTAbc->lpFreeBuff (lpIVTAbc->lpszPartialName);

        FreeANRBitmaps(lpIVTAbc);

        /*
         *  Free up the advise list, if used
         */
        if (lpIVTAbc->parglpAdvise)
            lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->parglpAdvise);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpIVTAbc->lpABLogon)
        {
            lpIVTAbc->lpABLogon->lpVtbl->Release(lpIVTAbc->lpABLogon);
            lpIVTAbc->lpABLogon = NULL;
        }

        /* Delete critical section for this object */
        DeleteCriticalSection(&lpIVTAbc->cs);

        /* Deregister the idle routine */

        DeregisterIdleRoutine(lpIVTAbc->ftg);

        /*
         *  Set the vtbl to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */
        lpIVTAbc->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpIVTAbc->lpFreeBuff(lpIVTAbc);
        return 0;
    }

    return lcInit;
}

/*
 -  IVTABC_SortTable
 -
 *  The Sample Address Book does not resort it's views.
 *
 */
STDMETHODIMP
IVTABC_SortTable(LPIVTABC lpIVTAbc,
    LPSSortOrderSet lpSortCriteria,
    ULONG ulFlags)
{
    HRESULT hResult;

    /*
     *  Validate parameters
     */
    IVTABC_ValidateObject(SortTable, lpIVTAbc);

    Validate_IMAPITable_SortTable(lpIVTAbc, lpSortCriteria, ulFlags);

    /*
     *  We don't support sorting this table
     */
    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(IVTABC_SortTable, hResult);
    return hResult;

}

/*
 -  IVTABC_QuerySortOrder
 -
 *
 *  For this implementation there is only one sort order
 */

STDMETHODIMP
IVTABC_QuerySortOrder(LPIVTABC lpIVTAbc,
    LPSSortOrderSet * lppSortCriteria)
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    int cbSize;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(QuerySortOrder, lpIVTAbc);

    Validate_IMAPITable_QuerySortOrder(lpIVTAbc, lppSortCriteria);

    /*  Calculate size of the structure we're gonna copy */
    cbSize = CbNewSSortOrderSet((int)sosIVTABC.cSorts);

    scode = lpIVTAbc->lpAllocBuff(cbSize, (LPVOID *) lppSortCriteria);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);

        goto out;
    }

    /*
     *  Copy the column set in
     */
    if (cbSize)
        memcpy(*lppSortCriteria, &sosIVTABC, cbSize);

out:

    DebugTraceResult(IVTABC_QuerySortOrder, hResult);
    return hResult;

}

/*
 -  IVTABC_CreateBookmark
 -
 *  Creates a bookmark associated with a row in a table
 *
 */
STDMETHODIMP
IVTABC_CreateBookmark(LPIVTABC lpIVTAbc,
    BOOKMARK * lpbkPosition)
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    ULONG ulBK;
    LPABCBK lpABCBK = NULL;
    ULONG cbRead = 0;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(CreateBookmark, lpIVTAbc);

    Validate_IMAPITable_CreateBookmark(lpIVTAbc, lpbkPosition);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Open the file
     */
    hResult = HrOpenFile(lpIVTAbc);
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    /*
     *  Shortcuts first
     */
    if (lpIVTAbc->ulPosition == lpIVTAbc->ulMaxPos)
    {
        *lpbkPosition = BOOKMARK_END;
        return hrSuccess;
    }

    /*
     *  search for a blank bookmark
     */
    for (ulBK = 0; lpIVTAbc->rglpABCBK[ulBK] && ulBK < MAX_BOOKMARKS; ulBK++);

    /*  did we find any??  */
    if (ulBK == MAX_BOOKMARKS)
    {
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);

        goto out;
    }

    scode = lpIVTAbc->lpAllocBuff (sizeof(ABCBK),(LPVOID *) &lpABCBK);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto out;
    }

    /*
     *  Fill in new bookmark
     */
    lpABCBK->filetime = lpIVTAbc->filetime;
    lpABCBK->ulPosition = lpIVTAbc->ulPosition;

    /*  Seek to position in file  */
    (void) SetFilePointer(lpIVTAbc->hFile, lpABCBK->ulPosition, NULL, FILE_BEGIN);

    /*  Read in the record at that location  */
    if (!ReadFile(lpIVTAbc->hFile,
            (LPVOID) &(lpABCBK->abcrec), sizeof(ABCREC), &cbRead, NULL))
    {
        goto readerror;
    }
    /*  Second check  */
    if (cbRead != sizeof(ABCREC))
    {
        goto readerror;
    }

    /*
     *  Put this in the bookmark structure
     */
    lpIVTAbc->rglpABCBK[ulBK] = lpABCBK;

    /*  Return the bookmark  */
    *lpbkPosition = ulBK + 3;

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_CreateBookmark, hResult);
    return hResult;

readerror:
    /*
     *  Didn't get the record.
     */

    /*  Restore back to original position  */
    (void) SetFilePointer(lpIVTAbc->hFile, lpIVTAbc->ulPosition, NULL, FILE_BEGIN);

    /*  Free up the new bookmark  */
    lpIVTAbc->lpFreeBuff(lpABCBK);

    hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE);
    SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ);

    goto out;
}

/*************************************************************************
 *
 -  IVTABC_FreeBookmark
 -
 *  Frees up the given bookmark
 *
 *
 *
 */
STDMETHODIMP
IVTABC_FreeBookmark(LPIVTABC lpIVTAbc,
    BOOKMARK bkPosition)
{
    HRESULT hResult = hrSuccess;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(FreeBookmark, lpIVTAbc);

    Validate_IMAPITable_FreeBookmark(lpIVTAbc, bkPosition);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Don't try and free up any of the standard bookmarks
     */
    if ((bkPosition != BOOKMARK_BEGINNING) &&
        (bkPosition != BOOKMARK_CURRENT) &&
        (bkPosition != BOOKMARK_END))
    {
        ULONG ulBK = (ULONG) bkPosition - 3;

        /*
         *  See if it's in range
         */
        if (ulBK >= 0 && ulBK < MAX_BOOKMARKS)
        {
            LPABCBK lpABCBK = NULL;

            /*  If it's valid...  */
            if (lpABCBK = lpIVTAbc->rglpABCBK[ulBK])    /* '=' on purpose */
            {
                /*  ...free it up.  */

                lpIVTAbc->lpFreeBuff(lpABCBK);
                lpIVTAbc->rglpABCBK[ulBK] = NULL;
            }

        }
        else
        {
            /*
             * It's an error
             */
            hResult = ResultFromScode(E_INVALIDARG);

        }
    }

    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_FreeBookmark, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_ExpandRow
 -
 *  Stubbed out.  This table doesn't implement catagorization.
 *
 *
 *
 */
STDMETHODIMP
IVTABC_ExpandRow(LPIVTABC lpIVTAbc, ULONG cbIKey, LPBYTE pbIKey,
    ULONG ulRowCount, ULONG ulFlags, LPSRowSet FAR * lppRows,
    ULONG FAR * lpulMoreRows)

{
    HRESULT hResult;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(ExpandRow, lpIVTAbc);

    Validate_IMAPITable_ExpandRow(lpIVTAbc,  cbIKey,  pbIKey,
                            ulRowCount,  ulFlags,  lppRows, lpulMoreRows);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(IVTABC_ExpandRow, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_CollapseRow
 -
 *  Stubbed out.  This table doesn't implement catagorization.
 *
 *
 *
 */
STDMETHODIMP
IVTABC_CollapseRow(LPIVTABC lpIVTAbc, ULONG cbIKey, LPBYTE pbIKey,
    ULONG ulFlags, ULONG FAR * lpulRowCount)

{
    HRESULT hResult;

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(CollapseRow, lpIVTAbc);

    Validate_IMAPITable_CollapseRow(lpIVTAbc, cbIKey, pbIKey, ulFlags, lpulRowCount);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(IVTABC_CollapseRow, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_WaitForCompletion
 -
 *  Stubbed out.
 *
 *
 *
 */
STDMETHODIMP
IVTABC_WaitForCompletion(LPIVTABC lpIVTAbc, ULONG ulFlags,
    ULONG ulTimeout, ULONG FAR * lpulTableStatus)
{
    HRESULT hResult;

    IVTABC_ValidateObject(WaitForCompletion, lpIVTAbc);

    Validate_IMAPITable_WaitForCompletion(lpIVTAbc, ulFlags, ulTimeout,
                                            lpulTableStatus);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(IVTABC_WaitForCompletion, hResult);
    return hResult;

}
/*************************************************************************
 *
 -  IVTABC_Abort
 -
 *  Nothing ever to abort...
 *
 *
 *
 */
STDMETHODIMP
IVTABC_Abort(LPIVTABC lpIVTAbc)

{

    /*
     *  Validate parameters
     */

    IVTABC_ValidateObject(Abort, lpIVTAbc);

    Validate_IMAPITable_Abort(lpIVTAbc);

    return hrSuccess;

}

/*************************************************************************
 *
 *
 -  IVTABC_Advise
 -
 *
 *
 *
 */
STDMETHODIMP
IVTABC_Advise(LPIVTABC lpIVTAbc,
    ULONG ulEventmask,
    LPMAPIADVISESINK lpAdviseSink,
    ULONG FAR * lpulConnection)
{
    HRESULT hResult = hrSuccess;
    UINT iAdvise;

    /*
     *  Validate the parameters
     */

    IVTABC_ValidateObject(Advise, lpIVTAbc);

    Validate_IMAPITable_Advise(lpIVTAbc, ulEventmask, lpAdviseSink,
                                lpulConnection);

    /* Get the Critical Section */
    EnterCriticalSection(&lpIVTAbc->cs);

    for (iAdvise = 0;
        lpIVTAbc->parglpAdvise && iAdvise < lpIVTAbc->cAdvise;
        ++iAdvise)
    {
        if (lpIVTAbc->parglpAdvise[iAdvise] == NULL)
            break;
    }

    if (iAdvise >= lpIVTAbc->cAdvise)
    {
        /*
         *   Realloc the array if it exists
         */
        if (lpIVTAbc->parglpAdvise)
        {
            lpIVTAbc->parglpAdvise = lpIVTAbc->lpMalloc->lpVtbl->Realloc(
                lpIVTAbc->lpMalloc,
                lpIVTAbc->parglpAdvise,
                (lpIVTAbc->cAdvise + 1) * sizeof(LPMAPIADVISESINK));
        }
        else
        {
            lpIVTAbc->parglpAdvise = lpIVTAbc->lpMalloc->lpVtbl->Alloc(
                lpIVTAbc->lpMalloc,
                (lpIVTAbc->cAdvise + 1) * sizeof(LPMAPIADVISESINK));
        }

        /*
         *  Could we get the desired memory?
         */
        if (lpIVTAbc->parglpAdvise == NULL)
        {
            hResult = MakeResult(E_OUTOFMEMORY);
            goto ret;
        }
    }

    lpIVTAbc->cAdvise++;

    *lpulConnection = lpIVTAbc->ulConnectMic + iAdvise;

    lpIVTAbc->parglpAdvise[iAdvise] = lpAdviseSink;

    lpAdviseSink->lpVtbl->AddRef(lpAdviseSink);

ret:
    /* leave critical section */
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_Advise, hResult);
    return hResult;
}

/*************************************************************************
 *
 *
 -  IVTABC_Unadvise
 -
 *
 *
 *
 */
STDMETHODIMP
IVTABC_Unadvise(LPIVTABC lpIVTAbc, ULONG ulConnection)
{
    LPMAPIADVISESINK padvise;
    UINT iAdvise;
    HRESULT hResult = hrSuccess;

    IVTABC_ValidateObject(Unadvise, lpIVTAbc);

    Validate_IMAPITable_Unadvise(lpIVTAbc, ulConnection);

    if (ulConnection - lpIVTAbc->ulConnectMic > (ULONG) lpIVTAbc->cAdvise)
    {
        DebugTraceSc(IVTABC_Unadvise, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /* Get the Critical Section */
    EnterCriticalSection(&lpIVTAbc->cs);

    iAdvise = (UINT) (ulConnection - lpIVTAbc->ulConnectMic);
    padvise = lpIVTAbc->parglpAdvise[iAdvise];
    padvise->lpVtbl->Release(padvise);
    lpIVTAbc->parglpAdvise[iAdvise] = NULL;
    lpIVTAbc->cAdvise--;

    /* leave critical section */
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_Unadvise, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_GetStatus
 -
 *  Returns the status of this table.  This table really isn't
 *  dynamic yet, but it could be...
 *
 *
 */
STDMETHODIMP
IVTABC_GetStatus(LPIVTABC lpIVTAbc,
    ULONG * lpulTableStatus,
    ULONG * lpulTableType)
{

    /*
     *  Parameter checking
     */

    IVTABC_ValidateObject(GetStatus, lpIVTAbc);

    Validate_IMAPITable_GetStatus(lpIVTAbc, lpulTableStatus, lpulTableType);

    *lpulTableStatus = TBLSTAT_COMPLETE;
    *lpulTableType = TBLTYPE_DYNAMIC;

    return hrSuccess;
}

/*************************************************************************
 *
 -  IVTABC_SetColumns
 -
 *
 *  SetColumns for contents table.
 *
 */
STDMETHODIMP
IVTABC_SetColumns(LPIVTABC lpIVTAbc,
    LPSPropTagArray lpPTAColSet,
    ULONG ulFlags)
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    int cbSizeOfColSet;
    LPSPropTagArray lpPTAColSetT;
    ULONG uliCol;

    /*
     *  Check parameters
     */
    IVTABC_ValidateObject(SetColumns, lpIVTAbc);

    Validate_IMAPITable_SetColumns(lpIVTAbc, lpPTAColSet, ulFlags);

    /*
     *  Verify that there are no PT_ERRORs here...
     */
    for (uliCol = 0; uliCol < lpPTAColSet->cValues; uliCol++)
    {
        if (PROP_TYPE(lpPTAColSet->aulPropTag[uliCol]) == PT_ERROR)
        {
            hResult = ResultFromScode(E_INVALIDARG);

            DebugTraceResult(IVTABC_GetStatus, hResult);
            return hResult;
        }
    }

    /*
     *  Allocate a new column set.
     */

    cbSizeOfColSet = CbNewSPropTagArray(lpPTAColSet->cValues);

    scode = lpIVTAbc->lpAllocBuff(cbSizeOfColSet,(LPVOID *) &lpPTAColSetT);

    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);

        DebugTraceResult(IVTABC_GetStatus, hResult);
        return hResult;
    }

    /*
     *  Copy the column set in
     */
    if (cbSizeOfColSet)
        memcpy(lpPTAColSetT, lpPTAColSet, cbSizeOfColSet);

    EnterCriticalSection(&lpIVTAbc->cs);

    if (lpIVTAbc->lpPTAColSet != ptagaivtabcColSet)
    {
        /*
         *  Free up the old column set
         */
        lpIVTAbc->lpFreeBuff(lpIVTAbc->lpPTAColSet);
    }

    lpIVTAbc->lpPTAColSet = lpPTAColSetT;

    LeaveCriticalSection(&lpIVTAbc->cs);

    return hrSuccess;

}

/*************************************************************************
 *
 -  IVTABC_QueryColumns
 -
 *
 *
 *  I always have all my columns available...  and active.
 */
STDMETHODIMP
IVTABC_QueryColumns(LPIVTABC lpIVTAbc,
    ULONG ulFlags,
    LPSPropTagArray FAR * lppColumns)
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    int cbSizeOfColSet;

    /*
     *  Check parameters
     */

    IVTABC_ValidateObject(QueryColumns, lpIVTAbc);

    Validate_IMAPITable_QueryColumns(lpIVTAbc, ulFlags, lppColumns);

    EnterCriticalSection(&lpIVTAbc->cs);

    /*
     *  Allocate enough memory for the column set
     */
    if (ulFlags & TBL_ALL_COLUMNS)

        cbSizeOfColSet = sizeof(ULONG) +
            (int)(ptagaivtabcColSet->cValues) * sizeof(ULONG);
    else
        cbSizeOfColSet = sizeof(ULONG) +
            (int)lpIVTAbc->lpPTAColSet->cValues * sizeof(ULONG);

    scode = lpIVTAbc->lpAllocBuff (cbSizeOfColSet,(LPVOID *) lppColumns);

    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto out;
    }

    /*
     *  Copy the column set in
     */
    if (ulFlags & TBL_ALL_COLUMNS)
        memcpy(*lppColumns, ptagaivtabcColSet, cbSizeOfColSet);
    else
        memcpy(*lppColumns, lpIVTAbc->lpPTAColSet, cbSizeOfColSet);

out:
    LeaveCriticalSection(&lpIVTAbc->cs);

    DebugTraceResult(IVTABC_QueryColumns, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  IVTABC_GetCollapseState
 -
 *  Stubbed out.  Only necessary if this table were to support categorization.
 *
 *
 *
 */
STDMETHODIMP
IVTABC_GetCollapseState(LPIVTABC lpIVTAbc,
                        ULONG ulFlags,
                        ULONG cbInstanceKey,
                        LPBYTE pbInstanceKey,
                        ULONG FAR * lpcbCollapseState,
                        LPBYTE FAR * lppbCollapseState)
{

    HRESULT hResult;

    /*
     *  Check parameters
     */

    IVTABC_ValidateObject(GetCollapseState, lpIVTAbc);

    Validate_IMAPITable_GetCollapseState(lpIVTAbc, ulFlags, cbInstanceKey,
                                        pbInstanceKey, lpcbCollapseState,
                                        lppbCollapseState);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);
    DebugTraceResult(IVTABC_GetCollapseState, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  IVTABC_SetCollapseState
 -
 *  Stubbed out.  Only necessary if this table were to support categorization.
 *
 *
 *
 */
STDMETHODIMP
IVTABC_SetCollapseState(LPIVTABC lpIVTAbc,
                        ULONG ulFlags,
                        ULONG cbCollapseState,
                        LPBYTE pbCollapseState,
                        BOOKMARK FAR * lpbkLocation)
{

    HRESULT hResult;

    /*
     *  Check parameters
     */

    IVTABC_ValidateObject(SetCollapseState, lpIVTAbc);

    Validate_IMAPITable_SetCollapseState(lpIVTAbc, ulFlags, cbCollapseState,
                                        pbCollapseState, lpbkLocation);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);
    DebugTraceResult(IVTABC_SetCollapseState, hResult);
    return hResult;
}


ABGUID.C

/**********************************************************************
 *
 *  ABGUID.C
 *
 *  Defines GUIDs for use in the Sample Address Book
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 **********************************************************************/

#define USES_IID_IUnknown
#define USES_IID_IStream

#define USES_IID_IMAPITableData
#define USES_IID_IMAPIPropData
#define USES_IID_IMAPITable
#define USES_IID_IMAPIControl
#define USES_IID_IABProvider
#define USES_IID_IABLogon
#define USES_IID_IMailUser
#define USES_IID_IMAPIContainer
#define USES_IID_IABContainer
#define USES_IID_IMAPIProp
#define USES_IID_IMAPIStatus

#ifdef _WIN32                   /* Must include WINDOWS.H on Win32 */
#ifndef _WINDOWS_
#define INC_OLE2                /* Get the OLE2 stuff */
#define INC_RPC
#define _INC_OLE
#include <windows.h>
#include <ole2.h>
#endif
#endif

#ifdef WIN16
#include <compobj.h>
#endif

#define INITGUID
#include <initguid.h>

DEFINE_OLEGUID(IID_IUnknown, 0x00000000L, 0, 0);
DEFINE_OLEGUID(IID_IStream,  0x0000000cL, 0, 0);

#include "mapiguid.h"


ABLOGON.C

/***********************************************************************
 *
 *  ABLOGON.C
 *
 *
 *  The Sample Address Book Provider.
 *
 *  This file has the code to implement the Sample Address Book's logon
 *  object.
 *
 *  The following routines are implemented in this file:
 *
 *  ABPLOGON_QueryInterface
 *  ABPLOGON_Release
 *  ABPLOGON_Logoff
 *  ABPLOGON_OpenEntry
 *  ABPLOGON_CompareEntryIDs
 *  ABPLOGON_Advise
 *  ABPLOGON_Unadvise
 *  ABPLOGON_OpenStatusEntry
 *  ABPLOGON_OpenTemplateID
 *  ABPLOGON_GetOneOffTable
 *  ABPLOGON_PrepareRecips
 *
 *  LpMuidFromLogon
 *  HrLpszGetCurrentFileName
 *  HrReplaceCurrentfileName
 *  GenerateContainerDN
 *  HrBuildRootHier
 *
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"
#include <smpab.h>

/*
 *  Definition of the logon object
 */
typedef struct _ABPLOGON {

    ABPLOGON_Vtbl FAR *     lpVtbl;

    SAB_IUnknown;

    /*
     *  Private structure
     */
    LPABPROVIDER lpABP;

    LPSTR lpszFileName;         /* Name of file that is browsed */
    MAPIUID muidID;             /* UID for this logon object */
    LPMAPISUP lpMapiSup;        /* MAPI Support object - gotten via ABP_Logon */

    /*
     *  Table Data for canned tables
     */

    LPTABLEDATA lpTDatRoot;     /*  Root hierarchy  */
    LPTABLEDATA lpTDatOO;       /*  One Off Table  */

} ABPLOGON, FAR *LPABPLOGON;

ABPLOGON_Vtbl vtblABPLOGON =
{
    ABPLOGON_QueryInterface,
    (ABPLOGON_AddRef_METHOD *) ROOT_AddRef,
    ABPLOGON_Release,
    (ABPLOGON_GetLastError_METHOD *) ROOT_GetLastError,
    ABPLOGON_Logoff,
    ABPLOGON_OpenEntry,
    ABPLOGON_CompareEntryIDs,
    ABPLOGON_Advise,
    ABPLOGON_Unadvise,
    ABPLOGON_OpenStatusEntry,
    ABPLOGON_OpenTemplateID,
    ABPLOGON_GetOneOffTable,
    ABPLOGON_PrepareRecips
};

/*
 -  HrNewABLogon
 -
 *
 *  Creates a new Sample AB Logon object.
 */

HRESULT
HrNewABLogon(   LPABLOGON *         lppABLogon,
                LPABPROVIDER        lpABP,
                LPMAPISUP           lpMAPISup,
                LPSTR               lpszSABFile,
                LPMAPIUID           lpmuid,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc )
{
    SCODE sc;
    HRESULT hResult = hrSuccess;
    SPropValue rgSPVStat[6];
    LPABPLOGON lpABPLogon = NULL;

    /*
     *  Allocate space for the lpABPLogon object
     */

    sc = lpAllocBuff(sizeof(ABPLOGON), &lpABPLogon);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /*
     *  Initialize the ABPLogon object
     */

    lpABPLogon->lpVtbl = &vtblABPLOGON;

    lpABPLogon->lcInit = 1;
    lpABPLogon->hResult = hrSuccess;
    lpABPLogon->idsLastError = 0;

    lpABPLogon->hLibrary = hLibrary;

    lpABPLogon->lpMalloc = lpMalloc;
    lpABPLogon->lpAllocBuff = lpAllocBuff;
    lpABPLogon->lpAllocMore = lpAllocMore;
    lpABPLogon->lpFreeBuff = lpFreeBuff;

    lpABPLogon->lpMapiSup = lpMAPISup;
    lpABPLogon->lpABP = (LPABPROVIDER) lpABP;
    lpABPLogon->lpszFileName = lpszSABFile;
    lpABPLogon->muidID = *lpmuid;

    lpABPLogon->lpTDatRoot = NULL;
    lpABPLogon->lpTDatOO = NULL;

    /*
     *  Register my status row...
     */
    rgSPVStat[0].ulPropTag = PR_DISPLAY_NAME_A;
    rgSPVStat[0].Value.lpszA = lpszSABFile;
    rgSPVStat[1].ulPropTag = PR_RESOURCE_METHODS;
    rgSPVStat[1].Value.l = 0;
    rgSPVStat[2].ulPropTag = PR_RESOURCE_FLAGS;
    rgSPVStat[2].Value.l = 0;
    rgSPVStat[3].ulPropTag = PR_STATUS_CODE;
    rgSPVStat[3].Value.l = STATUS_AVAILABLE;
    rgSPVStat[4].ulPropTag = PR_STATUS_STRING;
    rgSPVStat[4].Value.lpszA = "Available";
    rgSPVStat[5].ulPropTag = PR_PROVIDER_DISPLAY;
    rgSPVStat[5].Value.lpszA = "Sample Address Book Provider";

    /*
     *  Set the Status Row for this provider,
     *  but do not allow an error from setting the
     *  status row to cause failure to Logon.
     */

    (void)lpMAPISup->lpVtbl->ModifyStatusRow(lpMAPISup,
        sizeof(rgSPVStat) / sizeof(SPropValue), rgSPVStat, 0);

    /*
     *  AddRef the support object, because we're keeping
     *  a pointer to it in our Logon object.
     */
    lpMAPISup->lpVtbl->AddRef(lpMAPISup);

    /*
     *  AddRef our parent ABInit object
     */
    lpABP->lpVtbl->AddRef(lpABP);

    InitializeCriticalSection(&lpABPLogon->cs);

    *lppABLogon = (LPABLOGON) lpABPLogon;

out:

    DebugTraceResult(HrNewABPLogon, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  ABPLOGON_QueryInterface
 -
 */
STDMETHODIMP
ABPLOGON_QueryInterface(LPABPLOGON lpABPLogon, REFIID lpiid,
    LPVOID * ppvObj)
{

    Validate_IUnknown_QueryInterface(lpABPLogon, lpiid, ppvObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IABLogon, sizeof(IID)))
    {
        *ppvObj = NULL;         /* OLE requires zeroing [out] parameter on error */
        DebugTraceSc(ABPLOGON_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpABPLogon->cs);
    ++lpABPLogon->lcInit;
    LeaveCriticalSection(&lpABPLogon->cs);

    *ppvObj = lpABPLogon;

    return hrSuccess;
}

/*
 *  Use ROOTs AddRef
 */

/*************************************************************************
 *
 -  ABPLOGON_Release
 -
 */
STDMETHODIMP_(ULONG)
ABPLOGON_Release(LPABPLOGON lpABPLogon)
{
    LONG lcInit;

    EnterCriticalSection(&lpABPLogon->cs);
    lcInit = --lpABPLogon->lcInit;
    LeaveCriticalSection(&lpABPLogon->cs);

    if (lcInit == 0)
    {
        /*
         *  Free up the file
         */
        lpABPLogon->lpFreeBuff(lpABPLogon->lpszFileName);

        /*
         *  Release the Hierarchy Table Data
         */
        if (lpABPLogon->lpTDatRoot)
            lpABPLogon->lpTDatRoot->lpVtbl->Release(lpABPLogon->lpTDatRoot);

        /*
         *  Release the One-Off Table Data
         */
        if (lpABPLogon->lpTDatOO)
            lpABPLogon->lpTDatOO->lpVtbl->Release(lpABPLogon->lpTDatOO);

        /*
         *  No longer need to be holding on to our parent
         */
        lpABPLogon->lpABP->lpVtbl->Release(lpABPLogon->lpABP);

        /*
         *  Release the support object last.
         */
        lpABPLogon->lpMapiSup->lpVtbl->Release(lpABPLogon->lpMapiSup);

        DeleteCriticalSection(&lpABPLogon->cs);
        lpABPLogon->lpVtbl = NULL;
        lpABPLogon->lpFreeBuff(lpABPLogon);
        return (0);
    }
    return lcInit;
}

/*************************************************************************
 *
 -  ABPLOGON_Logoff
 -
 *  Logoff from this logon object.  Clean up any resources/objects that
 *  our logon object has accumulated.
 *
 *
 */
STDMETHODIMP
ABPLOGON_Logoff(LPABPLOGON lpABPLogon, ULONG ulFlags)
{

    /*
     *  Remove this logon object from the list of known
     *  logon objects associated with this initialization
     *  of this provider.
     */
    (void) RemoveLogonObject(lpABPLogon->lpABP, lpABPLogon, lpABPLogon->lpFreeBuff);

    return hrSuccess;
}

/*************************************************************************
 *
 -  ABPLOGON_OpenEntry
 -
 *  Creates an object with (at least) the IMAPIProp interface from an
 *  entryID.
 *
 *  There are four valid types of entryIDs handled:
 *
 *    NULL          <- return back the root container object
 *    DIR_ENTRYID   <- return back the directory container object
 *    USR_ENTRYID   <- return back the MAILUSER object
 *    OOUSR_ENTRYID <- return back the OneOff MAILUSER object
 *
 *  Note:  This call is reused for all other internal objects that support OpenEntry().
 *    Those other calls *must* check their parameters before calling this method.
 *    The only other way this method is called is via MAPI which does parameter checking
 *    for us.  The most we'll do here is assert our parameters.
 */
STDMETHODIMP
ABPLOGON_OpenEntry(LPABPLOGON lpABPLogon,
    ULONG cbEntryID,
    LPENTRYID lpEntryID,
    LPCIID lpInterface,
    ULONG ulFlags,
    ULONG * lpulObjType,
    LPUNKNOWN * lppUnk)
{

    LPDIR_ENTRYID lpSampEID = (LPDIR_ENTRYID) lpEntryID;
    HRESULT hResult = hrSuccess;

    /*
     *  Check the EntryID
     */

    if (!cbEntryID)
    {
        LPABCONT lpABCont = NULL;

        /*
         *  Special case:  the root level object
         */

        NFAssertSz(!lpEntryID, "Non-NULL entry id passed with 0 cb to OpenEntry()\n");

        /*  Make this new object  */

        hResult = HrNewROOT((LPABCONT *) lppUnk,
                            lpulObjType,
                            (LPABLOGON) lpABPLogon,
                            lpInterface,
                            lpABPLogon->hLibrary,
                            lpABPLogon->lpAllocBuff,
                            lpABPLogon->lpAllocMore,
                            lpABPLogon->lpFreeBuff,
                            lpABPLogon->lpMalloc);
        goto out;
    }

    /*
     *  There's an entryID there, is it mine??
     *  I need to check because I'm reusing this routine for
     *  my Container->OpenEntry call, and I can't be sure the
     *  client will always be well behaved.
     *
     *  When this routine is called from MAPI, this call is redundant.  But
     *  because I'm reusing this routine, I gotta check.
     */

    /*  Compare MAPIUIDs  */
    if (memcmp(&(((LPDIR_ENTRYID) lpEntryID)->muid), &muidABSample,
            sizeof(MAPIUID)))
    {
        /*
         *  Not mine!
         */

        hResult = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto out;
    }

    /*
     *  What object does this correspond to??
     */

    /*  I've only got two types: containers and users  */

    if (lpSampEID->ulType == SAMP_DIRECTORY)
    {
        LPABLOGON lpABPLogonT = NULL;

        /* entry id must have the same verson number */
        if (lpSampEID->ulVersion != SAMP_VERSION)
        {
            hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);
            SetErrorIDS(lpABPLogon, hResult, IDS_OLD_EID);

            goto out;
        }

        /*
         *  find the correct logon object for this entryid
         */

        (void) FindLogonObject(lpABPLogon->lpABP, &lpSampEID->muidID, &lpABPLogonT);

        /* did we find the corresponding logon object */
        if (!lpABPLogonT)
        {
            hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);
            goto out;
        }

        hResult = HrNewSampDirectory( (LPABCONT *) lppUnk,
                                    lpulObjType,
                                    (LPABLOGON) lpABPLogonT,
                                    lpInterface,
                                    lpABPLogon->hLibrary,
                                    lpABPLogon->lpAllocBuff,
                                    lpABPLogon->lpAllocMore,
                                    lpABPLogon->lpFreeBuff,
                                    lpABPLogon->lpMalloc);
        goto out;

    }

    if (lpSampEID->ulType == SAMP_USER)
        if (cbEntryID == (ULONG) sizeof(USR_ENTRYID))
        {
            hResult = HrNewSampUser( (LPMAILUSER *) lppUnk,
                                    lpulObjType,
                                    cbEntryID,
                                    lpEntryID,
                                    (LPABLOGON) lpABPLogon,
                                    lpInterface,
                                    lpABPLogon->hLibrary,
                                    lpABPLogon->lpAllocBuff,
                                    lpABPLogon->lpAllocMore,
                                    lpABPLogon->lpFreeBuff,
                                    lpABPLogon->lpMalloc);

            goto out;
        }

    if (lpSampEID->ulType == SAMP_OOUSER)
        if (cbEntryID == (ULONG) sizeof(OOUSR_ENTRYID))
        {
            hResult = HrNewSampOOUser( (LPMAILUSER *) lppUnk,
                                        lpulObjType,
                                        cbEntryID,
                                        lpEntryID,
                                        (LPABLOGON) lpABPLogon,
                                        lpInterface,
                                        lpABPLogon->hLibrary,
                                        lpABPLogon->lpAllocBuff,
                                        lpABPLogon->lpAllocMore,
                                        lpABPLogon->lpFreeBuff,
                                        lpABPLogon->lpMalloc);

            goto out;
        }

    hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

out:
    DebugTraceResult(ABPLOGON_OpenEntry, hResult);

    return hResult;

}

/*************************************************************************
 *
 -  ABPLOGON_CompareEntryIDs
 -
 *  If the two entryids are mine and they're of the same type, then
 *  just do a binary comparison to see if they're equal.
 *
 */
STDMETHODIMP
ABPLOGON_CompareEntryIDs(LPABPLOGON lpABPLogon,
    ULONG cbEntryID1,
    LPENTRYID lpEntryID1,
    ULONG cbEntryID2,
    LPENTRYID lpEntryID2,
    ULONG ulFlags,
    ULONG * lpulResult)
{

    LPDIR_ENTRYID lpSampEID1 = (LPDIR_ENTRYID) lpEntryID1;
    LPDIR_ENTRYID lpSampEID2 = (LPDIR_ENTRYID) lpEntryID2;
    HRESULT hResult = hrSuccess;

    /*
     *  Check to see if their MUID is mine
     */
    if (memcmp(&(lpSampEID1->muid), &muidABSample, sizeof(MAPIUID)) ||
        memcmp(&(lpSampEID2->muid), &muidABSample, sizeof(MAPIUID)))
    {
        /*
         *  No recognition of these entryids.
         */

        *lpulResult = (ULONG) FALSE;
        hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);
        goto out;
    }

    /*
     *  See if the type of entryids are the same
     */
    if (lpSampEID1->ulType != lpSampEID2->ulType)
    {
        /*
         *  They're not, so they don't match
         */

        *lpulResult = (ULONG) FALSE;
        goto out;

    }

    /*
     *  See if the entryids are the same size.  They'd better be
     *  if they're the same type.
     */
    if (cbEntryID1 != cbEntryID2)
    {
        /*
         *  They're not?!?  Then I don't know these...
         */

        *lpulResult = (ULONG) FALSE;
        hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

        goto out;
    }

    /*
     *  Check for Directory entryids
     */
    if (lpSampEID1->ulType == SAMP_DIRECTORY)
    {
        /*
         *  Ok, I'm dealing with directory entryids
         */

        /*
         *  Better make sure it's the right size
         */
        if (cbEntryID1 != sizeof(DIR_ENTRYID))
        {
            /*
             *  This doesn't make sense.  I don't recognize this entryid.
             */

            *lpulResult = (ULONG) FALSE;
            hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

            goto out;
        }

        /*
         *  At this point it's just a memcmp
         */
        if (memcmp(lpSampEID1, lpSampEID2, sizeof(DIR_ENTRYID)))
        {
            /*
             *  They're not equal
             */

            *lpulResult = (ULONG) FALSE;

            goto out;
        }

        /*
         *  They must be the same
         */

        *lpulResult = (ULONG) TRUE;

        goto out;
    }

    if (lpSampEID1->ulType == SAMP_USER)
    {
        /*
         *  Ok, I'm dealing with user entryids
         */

        /*
         *  Better make sure it's the right size
         */
        if (cbEntryID1 != sizeof(USR_ENTRYID))
        {
            /*
             *  This doesn't make sense.  I don't recognize this entryid.
             */

            *lpulResult = (ULONG) FALSE;
            hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

            goto out;
        }

        /*
         *  At this point it's just a memcmp
         */
        if (memcmp(lpSampEID1, lpSampEID2, sizeof(USR_ENTRYID)))
        {
            /*
             *  They're not equal
             */

            *lpulResult = (ULONG) FALSE;

            goto out;
        }

        /*
         *  They must be the same
         */

        *lpulResult = (ULONG) TRUE;

        goto out;
    }

    if (lpSampEID1->ulType == SAMP_OOUSER)
    {
        /*
         *  Ok, I'm dealing with oneoff user entryids
         */

        /*
         *  Better make sure it's the right size
         */
        if (cbEntryID1 != sizeof(OOUSR_ENTRYID))
        {
            /*
             *  This doesn't make sense.  I don't recognize this entryid.
             */

            *lpulResult = (ULONG) FALSE;
            hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

            goto out;
        }

        /*
         *  At this point it's just a memcmp
         */
        if (memcmp(lpSampEID1, lpSampEID2, sizeof(OOUSR_ENTRYID)))
        {
            /*
             *  They're not equal
             */

            *lpulResult = (ULONG) FALSE;

            goto out;
        }

        /*
         *  They must be the same
         */

        *lpulResult = (ULONG) TRUE;

        goto out;
    }

    /*
     *  It's no entryid I know of
     */

    *lpulResult = (ULONG) FALSE;
    hResult = ResultFromScode(MAPI_E_UNKNOWN_ENTRYID);

out:

    DebugTraceResult(ABPLOGON_CompareEntryIDs, hResult);
    return hResult;

}

/*************************************************************************
 *
 -  ABPLOGON_OpenStatusEntry
 -
 *
 *
 *
 */
STDMETHODIMP
ABPLOGON_OpenStatusEntry(LPABPLOGON lpABPLogon,
    LPCIID lpIID,
    ULONG ulFlags,
    ULONG FAR * lpulObjType,
    LPMAPISTATUS FAR * lppEntry)
{
    HRESULT hr;

    /*
     *  Validate Parameters
     */
    Validate_IABLogon_OpenStatusEntry(lpABPLogon, lpIID, ulFlags,
                                        lpulObjType, lppEntry);

    hr = HrNewStatusObject( lppEntry,
                            lpulObjType,
                            ulFlags,
                            (LPABLOGON) lpABPLogon,
                            lpIID,
                            lpABPLogon->hLibrary,
                            lpABPLogon->lpAllocBuff,
                            lpABPLogon->lpAllocMore,
                            lpABPLogon->lpFreeBuff,
                            lpABPLogon->lpMalloc);

    DebugTraceResult(ABPLOGON_OpenStatusEntry, hr);
    return hr;
}

/*************************************************************************
 *
 -  ABPLOGON_OpenTemplateID
 -
 *
 *
 *
 */
STDMETHODIMP
ABPLOGON_OpenTemplateID(LPABPLOGON lpABPLogon,
    ULONG cbTemplateId,
    LPENTRYID lpTemplateId,
    ULONG ulTemplateFlags,
    LPMAPIPROP lpMAPIPropData,
    LPCIID lpInterface,
    LPMAPIPROP * lppMAPIPropNew,
    LPMAPIPROP lpMAPIPropSibling)
{
    HRESULT hResult;

    /*
     *  Validate Parameters
     */
    Validate_IABLogon_OpenTemplateID(lpABPLogon, cbTemplateId, lpTemplateId,
                            ulTemplateFlags, lpMAPIPropData, lpInterface,
                            lppMAPIPropNew, lpMAPIPropSibling);

    /* //$ need stronger checking here... */
    /* entryid better be right size */
    if (cbTemplateId != sizeof(OOUSR_ENTRYID) && cbTemplateId != sizeof(USR_ENTRYID))
    {
        hResult = ResultFromScode(MAPI_E_INVALID_ENTRYID);
        goto out;
    }

    /*  is it my entry id compare MAPIUIDs  */
    if (memcmp(&(((LPUSR_ENTRYID) lpTemplateId)->muid), &muidABSample,
            sizeof(MAPIUID)))
    {
        hResult = ResultFromScode( MAPI_E_INVALID_ENTRYID );
        goto out;
    }

    /* better be a oneoff user entryid or a user entry id */
    if (((LPUSR_ENTRYID) lpTemplateId)->ulType == SAMP_OOUSER)
    {

        hResult = HrNewOOTID(   lppMAPIPropNew,
                                cbTemplateId,
                                lpTemplateId,
                                ulTemplateFlags,
                                lpMAPIPropData,
                                (LPABLOGON) lpABPLogon,
                                lpInterface,
                                lpABPLogon->hLibrary,
                                lpABPLogon->lpAllocBuff,
                                lpABPLogon->lpAllocMore,
                                lpABPLogon->lpFreeBuff,
                                lpABPLogon->lpMalloc);

    }
    else if (((LPUSR_ENTRYID) lpTemplateId)->ulType == SAMP_USER)
    {
        hResult = HrNewTID( lppMAPIPropNew,
                            cbTemplateId,
                            lpTemplateId,
                            ulTemplateFlags,
                            lpMAPIPropData,
                            (LPABLOGON) lpABPLogon,
                            lpInterface,
                            lpABPLogon->hLibrary,
                            lpABPLogon->lpAllocBuff,
                            lpABPLogon->lpAllocMore,
                            lpABPLogon->lpFreeBuff,
                            lpABPLogon->lpMalloc);
    }
    else
    {
        hResult = MakeResult(MAPI_E_INVALID_ENTRYID);
    }

out:

    DebugTraceResult(ABPLOGON_OpenTemplateID, hResult);
    return hResult;
}

/*
 -  ABPLOGON_GetOneOffTable
 -
 *  Returns the lists of one-offs that this providers can support creation of.
 *  This list is added to the entries gathered from all the other AB logon objects
 *  and exposed to the user as the list of things that can be created on a
 *  message.  Also this total list is available to other providers through the
 *  support method GetOneOffTable().
 *
 *  Note:  There's a bug here that if there are more than one Sample Address Books
 *  installed on a particular profile, then there will be multiple entries in the
 *  one-off table from this provider.  This can be changed to only have one one-off
 *  entry, no matter how many SABs are configured in a profile, if the one-off table
 *  was associated with the ABInit object.
 */

/*
 *  Column set for the oneoff table
 */
enum {  ivalootPR_DISPLAY_NAME_A = 0,
        ivalootPR_ENTRYID,
        ivalootPR_DEPTH,
        ivalootPR_SELECTABLE,
        ivalootPR_ADDRTYPE_A,
        ivalootPR_DISPLAY_TYPE,
        ivalootPR_INSTANCE_KEY,
        ivalootMax };

static const SizedSPropTagArray(ivalootMax, tagaColSetOOTable) =
{
    ivalootMax,
    {
        PR_DISPLAY_NAME_A,
        PR_ENTRYID,
        PR_DEPTH,
        PR_SELECTABLE,
        PR_ADDRTYPE_A,
        PR_DISPLAY_TYPE,
        PR_INSTANCE_KEY
    }
};

STDMETHODIMP
ABPLOGON_GetOneOffTable(
    LPABPLOGON lpABPLogon,
    ULONG ulFlags,
    LPMAPITABLE * lppTable)
{
    SCODE sc;
    HRESULT hResult;
    SRow sRow;
    SPropValue rgsPropValue[ivalootMax];
    ULONG ulInstanceKey = 1;
    OOUSR_ENTRYID EntryID;

    /*
     *  Validate Parameters
     */

    Validate_IABLogon_GetOneOffTable(lpABPLogon, ulFlags, lppTable);

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceArg( APBLOGON_GetOneOffTable, "UNICODE not supported" );
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    EnterCriticalSection(&lpABPLogon->cs);

    /*
     * If there's not one already associated with this logon object,
     * then create one.
     */
    if (!lpABPLogon->lpTDatOO)
    {
        /* Create a Table data object */
        sc = CreateTable(
            (LPIID) &IID_IMAPITableData,
            lpABPLogon->lpAllocBuff,
            lpABPLogon->lpAllocMore,
            lpABPLogon->lpFreeBuff,
            lpABPLogon->lpMalloc,
            0,
            PR_DISPLAY_NAME_A,
            (LPSPropTagArray) &tagaColSetOOTable,
            &(lpABPLogon->lpTDatOO));

        if (FAILED(sc))
        {
            hResult = ResultFromScode(sc);
            goto out;
        }

        /* Constants*/

        sRow.cValues = ivalootMax;
        sRow.lpProps = rgsPropValue;

        /* The Display Name */
        rgsPropValue[ivalootPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
        rgsPropValue[ivalootPR_DISPLAY_NAME_A].Value.lpszA = "Sample Address Book Recipient";

        /* the Entry ID*/
        ZeroMemory(&EntryID, sizeof(OOUSR_ENTRYID));

        EntryID.muid = muidABSample;
        EntryID.ulVersion = SAMP_VERSION;
        EntryID.ulType = SAMP_OOUSER;

        rgsPropValue[ivalootPR_ENTRYID].ulPropTag = PR_ENTRYID;
        rgsPropValue[ivalootPR_ENTRYID].Value.bin.cb = sizeof(OOUSR_ENTRYID);
        rgsPropValue[ivalootPR_ENTRYID].Value.bin.lpb = (LPVOID) &EntryID;

        /* the depth property */
        rgsPropValue[ivalootPR_DEPTH].ulPropTag = PR_DEPTH;
        rgsPropValue[ivalootPR_DEPTH].Value.l = 0;

        /* the selectable property */
        rgsPropValue[ivalootPR_SELECTABLE].ulPropTag = PR_SELECTABLE;
        rgsPropValue[ivalootPR_SELECTABLE].Value.b = TRUE;

        /*
         *  The address type that would be generated by an entry
         *  created from this template
         */
        rgsPropValue[ivalootPR_ADDRTYPE_A].ulPropTag = PR_ADDRTYPE;
        rgsPropValue[ivalootPR_ADDRTYPE_A].Value.lpszA = lpszEMT;

        /*
         *  The display type associated with a recipient built with this template
         */
        rgsPropValue[ivalootPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
        rgsPropValue[ivalootPR_DISPLAY_TYPE].Value.l = DT_MAILUSER;

        /*
         *  The instance key of this row in this one-off table.  Since there's only one
         *  row in this one-off table, just default it to a value of 1.
         */
        rgsPropValue[ivalootPR_INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
        rgsPropValue[ivalootPR_INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);
        rgsPropValue[ivalootPR_INSTANCE_KEY].Value.bin.lpb = (LPBYTE) &ulInstanceKey;

        (void) lpABPLogon->lpTDatOO->lpVtbl->HrModifyRow(
                            lpABPLogon->lpTDatOO,
                            &sRow);

    }

    /*
     *  Get a view to return to the caller
     */
    hResult = lpABPLogon->lpTDatOO->lpVtbl->HrGetView(
        lpABPLogon->lpTDatOO,
        NULL,
        NULL,
        0,
        (LPMAPITABLE *) lppTable);

out:

    LeaveCriticalSection(&lpABPLogon->cs);

    DebugTraceResult(ABPLogon_GetOneOffTable, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  ABPLOGON_Advise
 -
 *  NYI
 *
 *
 */
STDMETHODIMP
ABPLOGON_Advise(LPABPLOGON lpABPLogon,
    ULONG cbEntryID,
    LPENTRYID lpEntryID,
    ULONG ulEventMask,
    LPMAPIADVISESINK lpAdviseSink,
    ULONG FAR * lpulConnection)
{
    DebugTraceSc(ABPLOGON_Advise, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/*************************************************************************
 *
 -  ABPLOGON_Unadvise
 -
 *  NYI
 *
 *
 */
STDMETHODIMP
ABPLOGON_Unadvise(LPABPLOGON lpABPLogon, ULONG ulConnection)
{
    DebugTraceSc(ABPLOGON_Unadvise, MAPI_E_NO_SUPPORT);
    return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/*************************************************************************
 *
 -  ABPLOGON_PrepareRecips
 -
 *
 *
 *
 */
STDMETHODIMP
ABPLOGON_PrepareRecips(LPABPLOGON lpABPLogon,
    ULONG ulFlags,
    LPSPropTagArray lpPropTagArray,
    LPADRLIST lpRecipList)
{
    HRESULT         hResult         = hrSuccess;
    UINT            iRecip;
    UINT            iProp;
    ULONG           cValues;
    LPSPropValue    lpspvUser       = NULL;
    LPSPropValue    lpNewRecip      = NULL;
    LPMAPIPROP      lpMAPIPropEntry = NULL;
    SCODE           sc              = S_OK;

ULONG           ulObjType;
    BOOL            fUselpspvUser;

    /* loop through all the recipients */

    if (!lpPropTagArray)
    {
        /*
         *  They only want us to update our entryID from ephemeral to
         *  permanent.  Since ours are already permanent, we don't need to
         *  do anything.
         */
        goto out;
    }

    for (iRecip = 0; iRecip < lpRecipList->cEntries; iRecip++)
    {
        LPUSR_ENTRYID   lpEntryID   = NULL;
        ULONG           cbEntryID;
        LPSPropValue    lpPropVal       = NULL;
        LPSPropValue    rgpropvalsRecip = lpRecipList->aEntries[iRecip].rgPropVals;
        ULONG           cPropsRecip     = lpRecipList->aEntries[iRecip].cValues;

        /* For each recipient, find its entryid */

        lpPropVal = PpropFindProp( rgpropvalsRecip, cPropsRecip, PR_ENTRYID );

        if ( lpPropVal )
        {
            lpEntryID = (LPUSR_ENTRYID)lpPropVal->Value.bin.lpb;
            cbEntryID = lpPropVal->Value.bin.cb;
        }
        else
            continue;

        /* Is it one of ours? */

        if ( cbEntryID < CbNewENTRYID(0)
            || IsBadReadPtr( (LPVOID) lpEntryID, (UINT) cbEntryID ) )
        {
            continue;   /* no */
        }

        if ( memcmp( &(lpEntryID->muid), &muidABSample, sizeof(MAPIUID) ) )
            continue;   /* no */

        /* Try and open it. */

        hResult = HrNewSampUser((LPMAILUSER *)&lpMAPIPropEntry,
                                &ulObjType,
                                cbEntryID,
                                (LPENTRYID) lpEntryID,
                                (LPABLOGON) lpABPLogon,
                                NULL,
                                lpABPLogon->hLibrary,
                                lpABPLogon->lpAllocBuff,
                                lpABPLogon->lpAllocMore,
                                lpABPLogon->lpFreeBuff,
                                lpABPLogon->lpMalloc );

        if ( HR_FAILED(hResult) )
        {
             /* Couldn't open it...; Ignore it and keep looking */

            hResult = hrSuccess;
            DebugTrace( "ABPLOGON_PrepareRecips sees a bad user entry ID\n" );
            continue;
        }

        /* Get the properties requested */

        hResult = lpMAPIPropEntry->lpVtbl->GetProps( lpMAPIPropEntry,
                lpPropTagArray, 0, /* ansi */
                &cValues, &lpspvUser );

        /* No longer need lpMAPIPropEntry  */

        lpMAPIPropEntry->lpVtbl->Release(lpMAPIPropEntry);
        lpMAPIPropEntry = NULL;

        if (HR_FAILED(hResult))
        {
            /* Failed getting properties. Cleanup and ignore this entry */

            hResult = hrSuccess;
            continue;
        }

        hResult = hrSuccess;

        Assert(cValues == lpPropTagArray->cValues);

        /*
         *  This is the hard part.
         *  Merge the two property sets: lpspvUser and lpsPropVal.  Note that
         *  both of these sets may have the same property - chances are they do.
         *  for these conflicts, lpspvUser should be the one we get the property
         *  from.
         *
         *  Guess how big the resultant SPropValue array is, and allocate one of that
         *  size.
         */

        sc = lpABPLogon->lpAllocBuff( (cValues + cPropsRecip) * sizeof( SPropValue ),
                &lpNewRecip);
        if (FAILED(sc))
        {
            /*
             *  Ok, to fail the call here.  If we're running into out of memory conditions
             *  we're all in trouble.
             */

            hResult = ResultFromScode( sc );
            goto err;
        }

        /*
         *  Copy lpspvUser properties over to lpNewRecip
         *  Check each property in lpsvUser to ensure that it isn't PT_ERROR, if so
         *  find the propval in rgpropvalsRecip ( the [in] recip prop val array ),
         *  if it exists and use that property.
         */

        for (iProp = 0; iProp < cValues; iProp++)
        {
            fUselpspvUser = TRUE;

            if ( PROP_TYPE( lpspvUser[iProp].ulPropTag ) == PT_ERROR )
            {
                lpPropVal = PpropFindProp( rgpropvalsRecip, cPropsRecip,
                         lpPropTagArray->aulPropTag[iProp] );

                if ( lpPropVal )
                {
                    sc = PropCopyMore(  lpNewRecip + iProp, lpPropVal,
                            lpABPLogon->lpAllocMore, lpNewRecip );

                    fUselpspvUser = FALSE;
                }
            }

            if ( fUselpspvUser )
            {
                sc = PropCopyMore(  lpNewRecip + iProp, lpspvUser + iProp,
                        lpABPLogon->lpAllocMore, lpNewRecip );
            }

            if (FAILED(sc))
            {
                if (sc == MAPI_E_NOT_ENOUGH_MEMORY)
                {
                    hResult = MakeResult(sc);
                    goto err;
                }

                /*
                 *   Otherwise we've run into something wierd in the prop value array
                 *   like PT_UNSPECIFIED, PT_NULL, or PT_OBJECT.  In which case continue
                 *   on.
                 */
            }
        }

        /* Done with lpspvUser */

        lpABPLogon->lpFreeBuff( lpspvUser );
        lpspvUser = NULL;

        /*
         *  Copy those properties that aren't already in lpNewRecip
         *  from rgpropvalsRecip.  Don't copy over the PT_ERROR prop vals
         */
        for ( iProp = 0; iProp < cPropsRecip; iProp++ )
        {

            if ( PpropFindProp( lpNewRecip, cValues, rgpropvalsRecip[iProp].ulPropTag )
                || PROP_TYPE( rgpropvalsRecip[iProp].ulPropTag ) == PT_ERROR )
                continue;

            sc = PropCopyMore(  lpNewRecip + cValues, rgpropvalsRecip + iProp,
                    lpABPLogon->lpAllocMore, lpNewRecip );
            if ( FAILED( sc ) )
            {
                if (sc == MAPI_E_NOT_ENOUGH_MEMORY)
                {

                    hResult = ResultFromScode( sc );
                    goto err;
                }

                /*
                 *  Otherwise we've run into something wierd in the prop value array
                 *  like PT_UNSPECIFIED, PT_NULL, or PT_OBJECT.  In which case continue
                 *  on.
                 */
            }

            cValues++;
        }

        /*
         *  Replace the AdrEntry in the AdrList with this new lpNewRecip.  And
         *  don't forget the cValues!
         */

        lpRecipList->aEntries[iRecip].rgPropVals = lpNewRecip;
        lpRecipList->aEntries[iRecip].cValues    = cValues;

        /* Finally, free up the old AdrEntry. */

        lpABPLogon->lpFreeBuff( rgpropvalsRecip );

    }
out:

    DebugTraceResult( ABPLOGON_PrepareRecips, hResult );
    return hResult;

err:

    lpABPLogon->lpFreeBuff( lpspvUser );
    goto out;
}

/*
 *  LpMuidFromLogon -
 *    Returns the particular ABPLOGON object's unique identifier.
 *
 */

LPMAPIUID
LpMuidFromLogon(LPABLOGON lpABLogon)
{
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;

    AssertSz(!IsBadReadPtr(lpABPLogon, sizeof(ABPLOGON)), "Bad logon object!\n");

    return (&(lpABPLogon->muidID));
}

/*
 *  HrLpszGetCurrentFileName -
 *    Returns a copy of the current .SAB file pointed to by this logon object.
 *
 */

HRESULT
HrLpszGetCurrentFileName(LPABLOGON lpABLogon, LPSTR * lppszFileName)
{
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;
    SCODE sc;
    HRESULT hResult = hrSuccess;

    AssertSz(!IsBadReadPtr(lpABPLogon, sizeof(ABPLOGON)), "SAB: Bad logon object!\n");
    AssertSz(!IsBadWritePtr(lppszFileName, sizeof(LPSTR)), "SAB: Bad dest string!\n");

    EnterCriticalSection(&lpABPLogon->cs);

    sc = lpABPLogon->lpAllocBuff (lstrlenA(lpABPLogon->lpszFileName)+1, lppszFileName);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto ret;
    }

    lstrcpyA( *lppszFileName, lpABPLogon->lpszFileName);

ret:
    LeaveCriticalSection(&lpABPLogon->cs);

    DebugTraceResult(HrLpszGetCurrentFileName, hResult);
    return hResult;
}

/*
 *  HrReplaceCurrentFileName -
 *    Replaces the current file name associated with this logon object and tries
 *    to save it all away in the profile.
 */

HRESULT
HrReplaceCurrentFileName(LPABLOGON lpABLogon, LPSTR lpszNewFile)
{
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;
    HRESULT hResult = hrSuccess;
    LPPROFSECT lpProfSect = NULL;
    LPSTR lpstrT;
    SCODE sc;
    SPropValue rgspv[1];

    AssertSz(!IsBadReadPtr(lpABPLogon, sizeof(ABPLOGON)), "Bad logon object!\n");

    EnterCriticalSection(&lpABPLogon->cs);

    /*
     *  SAB file name has changed have to update profile and objects
     */
    if (lstrcmpA(lpszNewFile, lpABPLogon->lpszFileName))
    {

        /*
         *  Open the private profile section...
         */
        hResult = lpABPLogon->lpMapiSup->lpVtbl->OpenProfileSection(
            lpABPLogon->lpMapiSup,
            NULL,
            MAPI_MODIFY,
            &lpProfSect);
        if (HR_FAILED(hResult))
        {
            /*
             *  Shouldn't get here, but in case I do, just...
             */
            goto ret;
        }

        /*
         *  Save the new name back into the profile
         */
        rgspv[0].ulPropTag = PR_SAB_FILE;
        rgspv[0].Value.lpszA = lpszNewFile;

        /*
         *  Don't care if I can save it in the profile or not.
         *  Saving it's a nice to have, but absolutely required
         *  for operation of this particular provider.
         */
        (void) lpProfSect->lpVtbl->SetProps(
            lpProfSect,
            1,
            rgspv,
            NULL);

        lpProfSect->lpVtbl->Release(lpProfSect);

        /*
         *  Allocate and copy this new one
         */

        sc = lpABPLogon->lpAllocBuff (lstrlenA(lpszNewFile)+1, &lpstrT);
        if (FAILED(sc))
        {
            hResult = ResultFromScode(sc);
            goto ret;
        }

        lstrcpyA( lpstrT, lpszNewFile );

        /*
         *  Free up the old one...
         */
        lpABPLogon->lpFreeBuff(lpABPLogon->lpszFileName);

        /*
         *  Put in the new one.
         */
        lpABPLogon->lpszFileName = lpstrT;

        /*
         *  Update the hierarchy table
         */
        hResult = HrBuildRootHier((LPABLOGON)lpABPLogon, NULL);
    }

ret:
    LeaveCriticalSection(&lpABPLogon->cs);

    DebugTraceResult(HrReplaceCurrentFileName, hResult);
    return hResult;
}

/*
 *  GenerateContainerDN -
 *      Common code for generating the display name of the single
 *      container exposed from this logon object.
 */
void
GenerateContainerDN(LPABLOGON lpABLogon, LPSTR lpszName)
{
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;
    LPSTR lpszFileName;
    int ich;

    AssertSz(!IsBadReadPtr(lpABPLogon, sizeof(ABPLOGON)), "Bad logon object!\n");

    EnterCriticalSection(&lpABPLogon->cs);

    lpszFileName = lpABPLogon->lpszFileName;

    /* get the filename without the path */
    for (ich = lstrlenA(lpszFileName) - 1; ich >= 0; ich--)
    {
        if (lpszFileName[ich] == '\\')
            break;
    }

    /* skip past the backslash */
    ich++;

    wsprintfA(lpszName, "SAB using %s", lpszFileName + ich);

    LeaveCriticalSection(&lpABPLogon->cs);
}

/*
 -  HrBuildRootHier
 -
 *
 *  Builds up the root hierarchy for the Sample Address Book.
 *
 *
 */
enum {  ivalPR_DISPLAY_NAME_A = 0,
        ivalPR_ENTRYID,
        ivalPR_DEPTH,
        ivalPR_OBJECT_TYPE,
        ivalPR_DISPLAY_TYPE,
        ivalPR_CONTAINER_FLAGS,
        ivalPR_INSTANCE_KEY,
        ivalPR_AB_PROVIDER_ID,
        cvalMax };

static const SizedSPropTagArray(cvalMax, tagaRootColSet) =
{
    cvalMax,
    {
        PR_DISPLAY_NAME_A,
        PR_ENTRYID,
        PR_DEPTH,
        PR_OBJECT_TYPE,
        PR_DISPLAY_TYPE,
        PR_CONTAINER_FLAGS,
        PR_INSTANCE_KEY,
        PR_AB_PROVIDER_ID
    }
};

HRESULT
HrBuildRootHier(LPABLOGON lpABLogon, LPMAPITABLE * lppMAPITable)
{
    HRESULT hResult;
    SCODE sc;
    SRow sRow;
    SPropValue rgsPropValue[cvalMax];
    ULONG ulInstanceKey = 1;
    char szBuf[MAX_PATH];
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;
    DIR_ENTRYID eidRoot =   {   {0, 0, 0, 0},
                                MUIDABSAMPLE,
                                SAMP_VERSION,
                                SAMP_DIRECTORY };

    EnterCriticalSection(&lpABPLogon->cs);

    /*
     *  See if we have a TaD yet
     */
    if (!lpABPLogon->lpTDatRoot)
    {
        /* Create a Table Data object */
        if (sc = CreateTable((LPIID) &IID_IMAPITableData,
                    lpABPLogon->lpAllocBuff,
                    lpABPLogon->lpAllocMore,
                    lpABPLogon->lpFreeBuff,
                    lpABPLogon->lpMalloc,
                    0,
                    PR_ENTRYID,
                    (LPSPropTagArray) &tagaRootColSet,
                    &(lpABPLogon->lpTDatRoot)))
        {
            hResult = ResultFromScode(sc);
            goto out;
        }
    }
    /* Constants */

    sRow.cValues = cvalMax;
    sRow.lpProps = rgsPropValue;

    /* First, the Display Name stuff*/

    rgsPropValue[ivalPR_DISPLAY_NAME_A].ulPropTag   = PR_DISPLAY_NAME_A;
    GenerateContainerDN((LPABLOGON) lpABPLogon, szBuf);
    rgsPropValue[ivalPR_DISPLAY_NAME_A].Value.lpszA = szBuf;

    /*
     *  For each SAB logon object associated with it's init object,
     *  we have a unique MAPIUID.  It's the only thing that distinguishes
     *  one SAB entryid from another in the merged hierarchy table that
     *  MAPI generates.
     */

    rgsPropValue[ivalPR_ENTRYID].ulPropTag          = PR_ENTRYID;
    eidRoot.muidID = lpABPLogon->muidID;
    rgsPropValue[ivalPR_ENTRYID].Value.bin.cb       = sizeof(DIR_ENTRYID);
    rgsPropValue[ivalPR_ENTRYID].Value.bin.lpb      = (LPVOID) &eidRoot;

    rgsPropValue[ivalPR_DEPTH].ulPropTag            = PR_DEPTH;
    rgsPropValue[ivalPR_DEPTH].Value.l              = 0;

    rgsPropValue[ivalPR_OBJECT_TYPE].ulPropTag      = PR_OBJECT_TYPE;
    rgsPropValue[ivalPR_OBJECT_TYPE].Value.l        = MAPI_ABCONT;

    rgsPropValue[ivalPR_DISPLAY_TYPE].ulPropTag     = PR_DISPLAY_TYPE;
    rgsPropValue[ivalPR_DISPLAY_TYPE].Value.l       = DT_NOT_SPECIFIC;

    rgsPropValue[ivalPR_CONTAINER_FLAGS].ulPropTag  = PR_CONTAINER_FLAGS;
    rgsPropValue[ivalPR_CONTAINER_FLAGS].Value.l    = AB_RECIPIENTS | AB_UNMODIFIABLE;

    rgsPropValue[ivalPR_INSTANCE_KEY].ulPropTag     = PR_INSTANCE_KEY;
    rgsPropValue[ivalPR_INSTANCE_KEY].Value.bin.cb  = sizeof(ULONG);
    rgsPropValue[ivalPR_INSTANCE_KEY].Value.bin.lpb = (LPBYTE) &ulInstanceKey;

    rgsPropValue[ivalPR_AB_PROVIDER_ID].ulPropTag   = PR_AB_PROVIDER_ID;
    rgsPropValue[ivalPR_AB_PROVIDER_ID].Value.bin.cb= sizeof(MAPIUID);
    rgsPropValue[ivalPR_AB_PROVIDER_ID].Value.bin.lpb= (LPBYTE) &muidSABProviderID;

    hResult = lpABPLogon->lpTDatRoot->lpVtbl->HrModifyRow(
        lpABPLogon->lpTDatRoot, &sRow);

    if (HR_FAILED(hResult))
        goto out;

    /*
     *  Check to see if they want a view returned as well
     */
    if (lppMAPITable)
    {
        /* Get a view from the Table data object */
        hResult =
            lpABPLogon->lpTDatRoot->lpVtbl->HrGetView(
            lpABPLogon->lpTDatRoot,
            NULL,
            NULL,
            0,
            lppMAPITable);
    }

out:

    LeaveCriticalSection(&lpABPLogon->cs);

    DebugTraceResult(HrBuildRootHier, hResult);
    return hResult;
}

/*
 *  Checks to see if the file passed in is still the actual file that
 *  should be browsed.
 */
BOOL
FEqualSABFiles( LPABLOGON lpABLogon,
                LPSTR lpszFileName)
{
    LPABPLOGON lpABPLogon = (LPABPLOGON) lpABLogon;
    BOOL fEqual;

    AssertSz(!IsBadReadPtr(lpABPLogon, sizeof(ABPLOGON)), "Bad logon object!\n");

    EnterCriticalSection(&lpABPLogon->cs);

    fEqual = !lstrcmpA( lpszFileName, lpABPLogon->lpszFileName );

    LeaveCriticalSection(&lpABPLogon->cs);

    return fEqual;
}


ABP.C

/***********************************************************************
 *
 *  ABP.C
 *
 *
 *  The Sample Address Book Provider.
 *  This file contains the routines that handle the ABPJump table.
 *
 *  The following routines are implemented in this file:
 *
 *  ABProviderInit
 *  ABP_QueryInterface
 *  ABP_Release
 *  ABP_Shutdown
 *  ABP_Logon
 *
 *  ServiceEntry
 *  HrOpenSingleProvider
 *
 *  RemoveLogonObject
 *  FindLogonObject
 *  ScLoadString
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include <smpab.h>

/*
 *  The Local Identifier
 */
LCID lcidUser;

/*
 *  This provider's MAPIUID.  It's used as part of the entryIDs generated from this provider.
 */
MAPIUID muidABSample = MUIDABSAMPLE;

/*
 *  This provider's value for PR_AB_PROVIDER_ID.  It's found in the hierarchy table exposed
 *  by this provider
 */
MAPIUID muidSABProviderID = SAB_PROVIDER_ID;

/*
 *  This provider's Email Type
 */

LPSTR lpszEMT = SZEMAILTYPE;

/*
 *  Used to keep track of all logon objects created on the ABPInit object
 */

typedef struct _object
{
    struct _object *lppNext;
    LPVOID lpObject;

} OBJECTLIST, *LPOBJECTLIST;

/*
 *  structures and filters defined for display tables
 *
 */

DTBLLABEL dtbllabel = {sizeof(DTBLLABEL), 0};
DTBLPAGE dtblpage = {sizeof(DTBLPAGE), 0, 0, 0};
DTBLGROUPBOX dtblgroupbox = {sizeof(DTBLGROUPBOX), 0};
CHAR szNoFilter[] = "*";
CHAR szAddrTypeFilter[] = "[~:]";
CHAR szFileNameFilter[] = "[~   \\\\]"; /* ends up [~<space><tab>\\] */

/*
 *  Prototypes for the functions in this file, most of which I return
 *  in the jump table returned from ABProviderInit().
 */

//ABPROVIDERINIT ABProviderInit;
MSGSERVICEENTRY ServiceEntry;
HRESULT HrOpenSingleProvider(LPPROVIDERADMIN lpAdminProviders,
                             LPPROFSECT FAR * lppProfSect);

/*
 *  Definition of the init object
 */
typedef struct _ABP {
    ABP_Vtbl FAR *      lpVtbl;
    SAB_IUnknown;

    /*
     * list of logon objects
     */
    LPOBJECTLIST  lpObjectList;

} ABP, FAR *LPABP;

/*
 *  The ABInit object's vtbl is filled in here
 */
ABP_Vtbl vtblABP =
{
    ABP_QueryInterface,
    (ABP_AddRef_METHOD *) ROOT_AddRef,
    ABP_Release,
    ABP_Shutdown,
    ABP_Logon
};

/*
 -  ABProviderInit
 -
 *  Initializes this provider.  Returns an IABProvider object.
 *
 */

STDINITMETHODIMP
ABProviderInit(
    HINSTANCE hLibrary,
    LPMALLOC lpMalloc,
    LPALLOCATEBUFFER lpAllocBuff,
    LPALLOCATEMORE lpAllocMore,
    LPFREEBUFFER lpFreeBuff,
    ULONG ulFlags,
    ULONG ulMAPIVersion,
    ULONG FAR * lpulABVersion,
    LPABPROVIDER FAR * lppABProvider)
{
    HRESULT hResult = hrSuccess;
    LPABP lpABP;
    SCODE sc;

    /*
     *  Check the version
     */
    if (ulMAPIVersion < CURRENT_SPI_VERSION)
    {
        /*
         *  MAPI's version is too old.
         */

        /*
         *  See if they understand my version
         */
        *lpulABVersion = CURRENT_SPI_VERSION;

        DebugTraceSc(ABProviderInit, MAPI_E_VERSION);
        return ResultFromScode(MAPI_E_VERSION);
    }

    /*
     *  Allocate memory for this Init Object
     */
    sc = lpAllocBuff (sizeof(ABP), &lpABP);
    if (FAILED(sc))
    {
        /*
         *  Out of memory
         */
        DebugTraceSc(ABProviderInit, sc);
        return ResultFromScode(sc);
    }

    /*
     *  Initialize the idle engine that MAPI supplies.  This is most useful
     *  when browsing the .SAB file.  See ABCTBLn.C.
     */
    if (MAPIInitIdle(NULL) == -1)
    {
        hResult = ResultFromScode(E_FAIL);
        goto err;
    }

    /*
     *  Hold on to the lpMalloc
     */
    Assert(lpMalloc);
    lpMalloc->lpVtbl->AddRef(lpMalloc);

    lpABP->lpVtbl = &vtblABP;
    lpABP->lcInit = 1;
    lpABP->hLibrary = hLibrary;

    lpABP->lpMalloc = lpMalloc;
    lpABP->lpAllocBuff = lpAllocBuff;
    lpABP->lpAllocMore = lpAllocMore;
    lpABP->lpFreeBuff = lpFreeBuff;
    lpABP->lpObjectList = NULL;

    InitializeCriticalSection(&lpABP->cs);

    *lppABProvider = (LPABPROVIDER) lpABP;
    *lpulABVersion = CURRENT_SPI_VERSION;

    /*
     *  Get our Locale Identifier
     */
    lcidUser = GetUserDefaultLCID();

out:
    DebugTraceResult(ABProviderInit, hResult);
    return hResult;

err:
    lpFreeBuff(lpABP);

    goto out;
}

/*
 -  ABP_QueryInterface
 -
 *  Supports QI'ing to IUnknown and IABProvider
 *
 *  Note that for all the methods of IABProvider that parameter validation
 *  is unnecessary.  This is because MAPI is handling all the parameters
 *  being passed in.
 *  At best you can assert your parameters.
 */
STDMETHODIMP
ABP_QueryInterface(LPABP lpABP, REFIID lpiid, LPVOID * ppvObj)
{

    /*  See if the requested interface is one of ours */
    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IABProvider, sizeof(IID)))
    {
        *ppvObj = NULL;         /* OLE requires zeroing [out] parameter on error */
        DebugTraceSc(ABP_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpABP->cs);
    ++lpABP->lcInit;
    LeaveCriticalSection(&lpABP->cs);

    *ppvObj = lpABP;

    return hrSuccess;
}

/*
 -  ABP_Release
 -
 *  Releases and cleans up this object
 */
STDMETHODIMP_(ULONG)
ABP_Release(LPABP lpABP)
{
    long lcInit;

    EnterCriticalSection(&lpABP->cs);
    lcInit = --lpABP->lcInit;
    LeaveCriticalSection(&lpABP->cs);

    if (lcInit == 0)
    {
        DeleteCriticalSection(&lpABP->cs);

        lpABP->lpVtbl = NULL;
        lpABP->lpFreeBuff(lpABP);
        return (0);
    }
    return lcInit;
}

/*
 -  ABP_Shutdown
 -
 *  Informs this provider that MAPI is done with it.
 *
 *
 */
STDMETHODIMP
ABP_Shutdown(LPABP lpABP, ULONG FAR * lpulFlags)
{
    MAPIDeinitIdle();

    if (lpABP->lpMalloc)
    {
        lpABP->lpMalloc->lpVtbl->Release(lpABP->lpMalloc);
        lpABP->lpMalloc = NULL;
    }

    return hrSuccess;
}

/*
 -  ABP_Logon
 -
 *  Create a logon object and return it to MAPI.
 *
 */

/*
 *  The PropTagArray used to retrieve logon properties
 */
enum {  ivallogonPR_SAB_FILE,
        ivallogonPR_SAB_UID,
        cvallogonMax };

static const SizedSPropTagArray(cvallogonMax, tagaSABLogonProps) =
{
    cvallogonMax,
    {
        PR_SAB_FILE,
        PR_SAB_UID
    }
};

STDMETHODIMP
ABP_Logon(  LPABP             lpABP,
            LPMAPISUP         lpMAPISup,
            ULONG             ulUIParam,
            LPTSTR            lpszProfileName,
            ULONG             ulFlags,
            ULONG FAR *       lpulpcbSecurity,
            LPBYTE FAR *      lppbSecurity,
            LPMAPIERROR FAR * lppMapiError,
            LPABLOGON FAR *   lppABPLogon)
{
    LPABLOGON       lpABLogon       = NULL;
    HRESULT         hResult         = hrSuccess;
    SCODE           scode;
    LPPROFSECT      lpProfSect      = NULL;
    LPSPropValue    lpsPropVal      = NULL;
    ULONG           cValues         = 0;
    LPSTR           lpstrT          = NULL;
    BOOL            fUINeeded;
    BOOL            fNeedMAPIUID    = FALSE;
    MAPIUID         muidID;
    LPOBJECTLIST    lpObjectList;
    CHAR            szFileName[MAX_PATH];

    szFileName[0] = '\0';

    *lppMapiError = NULL;

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceArg( ABP_Logon, "Bad Character width" );
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    /*
     *  Get the name of my browse file from the profile section
     */

    /*  Open the section for my provider */
    hResult = lpMAPISup->lpVtbl->OpenProfileSection(lpMAPISup,
                NULL,
                MAPI_MODIFY,
                &lpProfSect);

    if (HR_FAILED(hResult))
        goto out;

    /*  Get the property containing the browse file name and the sab uid. */
    hResult = lpProfSect->lpVtbl->GetProps(
        lpProfSect,
        (LPSPropTagArray) &tagaSABLogonProps,
        0,      /* ansi */
        &cValues,
        &lpsPropVal);

    if (HR_FAILED(hResult))
        goto out;

#ifdef DEBUG
    if (hResult != hrSuccess)
    {
        if (PROP_TYPE(lpsPropVal[ivallogonPR_SAB_FILE].ulPropTag) == PT_ERROR
            && lpsPropVal[0].Value.err != MAPI_E_NOT_FOUND)
        {
            TraceSz1("ABP_Logon (PR_SAB_FILE) got unexpected scode: %s\n",
                SzDecodeScode(lpsPropVal[ivallogonPR_SAB_FILE].Value.err));
        }
        if (PROP_TYPE(lpsPropVal[ivallogonPR_SAB_UID].ulPropTag) == PT_ERROR
            && lpsPropVal[ivallogonPR_SAB_UID].Value.err != MAPI_E_NOT_FOUND)
        {
            TraceSz1("ABP_Logon (PR_SAB_UID) got unexpected scode: %s\n",
                SzDecodeScode(lpsPropVal[1].Value.err));
        }
    }
#endif

    /*  Ignore warnings from reading the property. */
    hResult = hrSuccess;

    /* copy the sab file name */
    if (lpsPropVal[0].ulPropTag == PR_SAB_FILE)
    {
        UINT cch = lstrlenA(lpsPropVal[0].Value.lpszA);

        if (cch >= MAX_PATH)
            cch = MAX_PATH - 1;

        if (cch)
            memcpy(szFileName, lpsPropVal[0].Value.lpszA, cch);

        szFileName[cch] = '\0';
    }
    else
    {
        DebugTrace("ABP_Logon PR_SAB_FILE not found");
    }

    /* copy the sab uid */
    if (lpsPropVal[1].ulPropTag == PR_SAB_UID)
    {
        memcpy(&muidID, lpsPropVal[1].Value.bin.lpb, sizeof(MAPIUID));
    }
    else
    {
        DebugTrace("ABP_Logon PR_SAB_UID not found");
        fNeedMAPIUID = TRUE;
    }

    /*  Discard GetProps() return data, if any. */
    lpABP->lpFreeBuff(lpsPropVal);

    /*
     *  UI needed unless the file name is good and file exists.
     */

    fUINeeded = TRUE;

    if (szFileName[0] != 0)
    {
        /* Verify the file exists. */

        HANDLE hFile = CreateFile(
            szFileName,
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

        if (hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFile);

            fUINeeded = FALSE;
        }
    }

    /*
    *  if the sab file name was not found in the profile or the sab file
    *  does not exist we have to get the user to pick another one and
    *  save back into the profile
    */

    if (fUINeeded)
    {
        OPENFILENAME openfilename;
        SPropValue sProp[1];

        /*
         *  Can't bring up UI unless the client allows it
         */
        if (ulFlags & AB_NO_DIALOG)
        {
            hResult = ResultFromScode(MAPI_E_LOGON_FAILED);
            goto out;
        }

        /*
         *  Get the user to select a file
         */
        openfilename.lStructSize = sizeof(OPENFILENAME);
        openfilename.hwndOwner = (HWND) ulUIParam;
        openfilename.hInstance = 0; /* Ignored */
        openfilename.lpstrFilter = TEXT("Sample AB files\0*.sab\0\0");
        openfilename.lpstrCustomFilter = NULL;
        openfilename.nMaxCustFilter = 0;
        openfilename.nFilterIndex = 0;
        openfilename.lpstrFile = szFileName;
        openfilename.nMaxFile = MAX_PATH;
        openfilename.lpstrFileTitle = NULL;
        openfilename.nMaxFileTitle = 0;
        openfilename.lpstrInitialDir = NULL;
        openfilename.lpstrTitle = TEXT("Sample Address Book");
        openfilename.Flags = OFN_FILEMUSTEXIST |
            OFN_HIDEREADONLY |
            OFN_NOCHANGEDIR;
        openfilename.nFileOffset = 0;
        openfilename.nFileExtension = 0;
        openfilename.lpstrDefExt = TEXT("sab");
        openfilename.lCustData = 0;
        openfilename.lpfnHook = NULL;
        openfilename.lpTemplateName = NULL;

        /*
         *  Call up the common dialog
         */
        if (!GetOpenFileName(&openfilename))
        {
#ifdef DEBUG
            DWORD dwT;

            dwT = CommDlgExtendedError();
#endif /* DEBUG */

            hResult = ResultFromScode(MAPI_E_LOGON_FAILED);
            goto out;
        }

        /*
         *  Set the sab file name property value
         */
        sProp[0].ulPropTag = PR_SAB_FILE;
        sProp[0].Value.lpszA = szFileName;

        /*
         *  Save the sab file into the profile
         */
        if (hResult = lpProfSect->lpVtbl->SetProps(lpProfSect, 1, sProp, NULL))
        {
            /*
             *  Do nothing...  So I couldn't save it away this time...
             */
            DebugTraceResult("ABP_Logon got unexpected result on SetProps\n", hResult);
            hResult = hrSuccess;
        }
    }

    /*
    *  if the uid was not found we have to generate a new muid for the
    *  PR_SAB_ID property and save it back into the profile
    */

    if (fNeedMAPIUID)
    {
        SPropValue sProp[1];

        /*
         *  Get a new sab uid
         */
        hResult = lpMAPISup->lpVtbl->NewUID(lpMAPISup, &muidID);
        if (HR_FAILED(hResult))
        {
            goto out;
        }

        /*
         *  Set the sab uid property value
         */
        sProp[0].ulPropTag = PR_SAB_UID;
        sProp[0].Value.bin.cb = sizeof(MAPIUID);
        sProp[0].Value.bin.lpb = (LPBYTE) &muidID;

        /*
         *  Save the sab uid into the profile
         */
        if (hResult = lpProfSect->lpVtbl->SetProps(lpProfSect, 1, sProp, NULL))
        {
            /*
             *  Do nothing...  So I couldn't save it away this time...
             */
            DebugTraceResult("ABP_Logon got unexpected result on SetProps\n", hResult);
            hResult = hrSuccess;
        }
    }

    /*
     *  Allocate space for keeping the file name in the ABLogon object
     */

    if (scode = lpABP->lpAllocBuff(lstrlenA(szFileName)+1, &lpstrT))
    {
        hResult = ResultFromScode(scode);
        goto out;
    }
    lstrcpyA(lpstrT, szFileName);

    hResult = HrNewABLogon(&lpABLogon,
                            (LPABPROVIDER) lpABP,
                            lpMAPISup,
                            lpstrT,
                            &muidID,
                            lpABP->hLibrary,
                            lpABP->lpAllocBuff,
                            lpABP->lpAllocMore,
                            lpABP->lpFreeBuff,
                            lpABP->lpMalloc );
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    /*
     *  Allocate space for another object list item
     */
    scode = lpABP->lpAllocBuff(sizeof(OBJECTLIST), &lpObjectList);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto out;
    }

    /* Get the Critical Section */
    EnterCriticalSection(&lpABP->cs);

    /* add logon object to begining of providers object list */
    lpObjectList->lpObject = (LPVOID) lpABLogon;
    lpObjectList->lppNext = lpABP->lpObjectList;

    /* insert new logon object into the head of the providers object list */
    lpABP->lpObjectList = lpObjectList;

    /* leave critical section */
    LeaveCriticalSection(&lpABP->cs);

    /*
     *  Register my MAPIUID for this provider,
     *  but do not allow an error from setting the
     *  MAPIUID to cause failure to Logon.
     */

    (void)lpMAPISup->lpVtbl->SetProviderUID(lpMAPISup,
        (LPMAPIUID) &muidABSample, 0);

    *lppABPLogon = lpABLogon;

out:
    if (lpProfSect)
        lpProfSect->lpVtbl->Release(lpProfSect);

    if (hResult)
    {
        lpABP->lpFreeBuff(lpstrT);

        Assert(lpABLogon == NULL);

        /* Verify we don't return warnings at this time. */
        Assert(HR_FAILED(hResult));
    }

    DebugTraceResult(ABP_Logon, hResult);
    return hResult;
}

/*
 *  Removes a particular logon object from the list of logon objects
 *  that's kept track of in the IABProvider object
 */
void
RemoveLogonObject(LPABPROVIDER lpABProvider, LPVOID lpvABLogon, LPFREEBUFFER lpFreeBuff)
{

    LPOBJECTLIST *lppObjectList;
    LPOBJECTLIST lpObjectListT;
    LPABP lpABP = (LPABP) lpABProvider;

#if defined DEBUG
    BOOL fFound = FALSE;

#endif

    /* Get the Critical Section */
    EnterCriticalSection(&lpABP->cs);

    /*
     *  remove this logon object from the provider init objects list
     *  of logon objects
     */
    lppObjectList = &(lpABP->lpObjectList);

    while (*lppObjectList)
    {
        /* is this the logon object? */
        if ((*lppObjectList)->lpObject == lpvABLogon)
        {
            /* save next list item */
            lpObjectListT = (*lppObjectList)->lppNext;

            /* free the object list item */
            lpFreeBuff(*lppObjectList);

            /* delete object from the list */
            *lppObjectList = lpObjectListT;

#if defined DEBUG
            fFound = TRUE;
#endif
            break;
        }

        lppObjectList = &(*lppObjectList)->lppNext;
    }

    /* leave critical section */
    LeaveCriticalSection(&lpABP->cs);

#if defined DEBUG
    AssertSz(fFound, TEXT("Logon object not found on providers object list"));
#endif

    return;
}

/*
 *  Finds a particular logon object by its muid.
 */
void
FindLogonObject(LPABPROVIDER lpABProvider, LPMAPIUID lpMuidToFind, LPABLOGON * lppABLogon)
{
    LPABP lpABP = (LPABP) lpABProvider;
    LPABLOGON lpABLogonT = NULL;
    LPOBJECTLIST lpObjectList = NULL;
    LPMAPIUID lpMuidLogon = NULL;

    Assert(!IsBadReadPtr(lpABP, sizeof(ABP)));
    Assert(!IsBadReadPtr(lpMuidToFind, sizeof(MAPIUID)));
    Assert(!IsBadReadPtr(lppABLogon, sizeof(LPABLOGON)));

    /* Get the Critical Section */
    EnterCriticalSection(&lpABP->cs);

    *lppABLogon = NULL;

    for (lpObjectList = lpABP->lpObjectList;
        lpObjectList; lpObjectList = lpObjectList->lppNext)
    {
        lpABLogonT = (LPABLOGON) lpObjectList->lpObject;

        lpMuidLogon = LpMuidFromLogon(lpABLogonT);

        if (memcmp((LPVOID) lpMuidLogon, (LPVOID) lpMuidToFind, sizeof(MAPIUID)) == 0)
        {
            *lppABLogon = lpABLogonT;
            break;
        }
    }

    /* leave critical section */
    LeaveCriticalSection(&lpABP->cs);
}

/*
 -  ServiceEntry
 -
 *  This funtion is used by MAPI to configure the Sample Address Book.
 *  It's a lot like ABP_Logon, except that it doesn't return a logon object
 *  and it can be passed in its configuration information (as defined in
 *  smpab.h) from MAPI so that no UI is required.
 *
 */
STDAPI
ServiceEntry(HINSTANCE hInstance,
             LPMALLOC lpMalloc,
             LPMAPISUP lpMAPISup,
             ULONG ulUIParam,
             ULONG ulFlags,
             ULONG ulContext,
             ULONG cValues,
             LPSPropValue lpProps,
             LPPROVIDERADMIN lpAdminProviders,
             LPMAPIERROR FAR *lppMapiError)
{
    OPENFILENAME openfilename;
    char szFileName[MAX_PATH];
    HRESULT hResult = hrSuccess;
    LPSPropValue lpsPropVal = NULL;
    ULONG ulCount = 0;
    LPALLOCATEBUFFER lpAllocBuff;
    LPALLOCATEMORE lpAllocMore;
    LPFREEBUFFER lpFreeBuff;
    LPPROFSECT lpProf = NULL;
    BOOL fUINeeded = FALSE;
    BOOL fNeedMAPIUID = FALSE;
    SPropValue sProp[2];
    ULONG uliProp;
    MAPIUID muid;

    /*  Validate parameters */

    /* check the support object */
    if (IsBadReadPtr(lpMAPISup, sizeof(LPMAPISUP)))
    {
        DebugTraceSc(ServiceEntry, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceArg( ServiceEntry, "Bad character width" );
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    /*
     *  check for context
     */
    if (ulContext == MSG_SERVICE_DELETE || ulContext == MSG_SERVICE_INSTALL
        || ulContext == MSG_SERVICE_UNINSTALL)
        return hrSuccess;
    if (ulContext != MSG_SERVICE_CONFIGURE && ulContext != MSG_SERVICE_CREATE)
    {
        DebugTrace(TEXT("ServiceEntry unsupported context"));
        return ResultFromScode(MAPI_E_NO_SUPPORT);
    }

    /*  Get the memory allocation routines we'll be needing. */
    hResult = lpMAPISup->lpVtbl->GetMemAllocRoutines(lpMAPISup,
        &lpAllocBuff, &lpAllocMore, &lpFreeBuff);
    if (hResult)
    {
        DebugTraceResult(MAPISUP: :GetMemAllocRoutines, hResult);
        goto out;
    }

    /* Open the profile section associated with our provider */
    hResult = HrOpenSingleProvider(lpAdminProviders, &lpProf);
    if (hResult)
    {
        DebugTrace(TEXT("Unable to open the profile."));
        goto out;
    }

    szFileName[0] = '\0';

    /* get sab filename and uid from profile */
    hResult = lpProf->lpVtbl->GetProps(
        lpProf,
        (LPSPropTagArray) &tagaSABLogonProps,
        ulFlags & MAPI_UNICODE,
        &ulCount,
        &lpsPropVal);

    /*  Ignore errors/warnings from reading the property. */
    hResult = hrSuccess;

    /* Look for the .SAB in the config props first */
    for (uliProp = 0; uliProp < cValues; uliProp++)
    {
        if (PROP_ID(lpProps[uliProp].ulPropTag) == PROP_ID(PR_SAB_FILE))
            break;
    }

    if (uliProp < cValues &&
        PROP_TYPE(lpProps[uliProp].ulPropTag) != PT_ERROR)
    {
        ULONG cch = lstrlenA(lpProps[uliProp].Value.lpszA);

        if (cch >= MAX_PATH)
            cch = MAX_PATH - 1;
        if (cch)
            memcpy(szFileName, lpProps[uliProp].Value.lpszA, (size_t) cch);
        szFileName[cch] = '\0';

    }
    else if (lpsPropVal && PROP_ID(lpsPropVal[0].ulPropTag) ==
        PROP_ID(PR_SAB_FILE) &&
        PROP_TYPE(lpsPropVal[0].ulPropTag) != PT_ERROR)
    {
        ULONG cch = lstrlenA(lpsPropVal[0].Value.lpszA);

        if (cch >= MAX_PATH)
            cch = MAX_PATH - 1;
        if (cch)
            memcpy(szFileName, lpsPropVal[0].Value.lpszA, (size_t) cch);
        szFileName[cch] = '\0';
    }
    else
    {
        /* need to ask the user for the sab file */
        fUINeeded = TRUE;
    }

    /* Look for the SAB_UID in the config props first */
    for (uliProp = 0; uliProp < cValues; uliProp++)
    {
        if (PROP_ID(lpProps[uliProp].ulPropTag) == PROP_ID(PR_SAB_UID))
            break;

    }

    if (uliProp < cValues &&
        PROP_TYPE(lpProps[uliProp].ulPropTag) != PT_ERROR)
    {
        memcpy(&muid, lpProps[uliProp].Value.bin.lpb, sizeof(MAPIUID));
    }
    else if (lpsPropVal && PROP_ID(lpsPropVal[1].ulPropTag) ==
        PROP_ID(PR_SAB_UID) && PROP_TYPE(lpsPropVal[1].ulPropTag) != PT_ERROR)
    {
        memcpy(&muid, lpsPropVal[1].Value.bin.lpb, sizeof(MAPIUID));
    }
    else
    {
        /* need to generate a uid */
        fNeedMAPIUID = TRUE;
    }

    /*  Discard GetProps() return data, if any.
     */
    if (lpsPropVal)
        lpFreeBuff(lpsPropVal);

    /*
     *  if the sab file name was not found in the profile we have to
     *  get the user to pick one and save it back into the profile
     */
    if (   fUINeeded
        && !(ulFlags & (SERVICE_UI_ALLOWED | SERVICE_UI_ALWAYS | UI_SERVICE)))
    {
        /*  We need UI to configure but it's not allowed so we can't configure.
         */
        hResult = ResultFromScode(MAPI_E_UNCONFIGURED);

        DebugTrace("SMPAB::ServiceEntry - Missing properties required to configure service.\n");

        goto out;
    }

    if ((fUINeeded) || (ulFlags & UI_SERVICE))
    {
        /*
        *  Get the user to pick a SAB file
        */
        openfilename.lStructSize = sizeof(OPENFILENAME);
        openfilename.hwndOwner = (HWND) ulUIParam;
        openfilename.hInstance = 0; /* Ignored */
        openfilename.lpstrFilter = "Sample AB files\0*.sab\0\0";
        openfilename.lpstrCustomFilter = NULL;
        openfilename.nMaxCustFilter = 0;
        openfilename.nFilterIndex = 0;
        openfilename.lpstrFile = szFileName;
        openfilename.nMaxFile = MAX_PATH;
        openfilename.lpstrFileTitle = NULL;
        openfilename.nMaxFileTitle = 0;
        openfilename.lpstrInitialDir = NULL;
        openfilename.lpstrTitle = "Pick a Sample Address Book file";
        openfilename.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
        openfilename.nFileOffset = 0;
        openfilename.nFileExtension = 0;
        openfilename.lpstrDefExt = "sab";
        openfilename.lCustData = 0;
        openfilename.lpfnHook = NULL;
        openfilename.lpTemplateName = NULL;

        /*
        *  Call up the common dialog
        */
        if (!GetOpenFileName(&openfilename))
        {
            /* user pressed cancel */
            goto out;
        }
    }

    /*
    *  if the uid was not found we have to generate a new muid for the
    *  PR_SAB_ID property and save it back into the profile
    */

    if (fNeedMAPIUID)
    {
        hResult = lpMAPISup->lpVtbl->NewUID(lpMAPISup, &muid);
        if (HR_FAILED(hResult))
        {
            /*
            *  Can't get a uid so just leave
            */
            goto out;
        }
    }

    /*
    *  Set the file name property
    */

    sProp[ivallogonPR_SAB_FILE].ulPropTag = PR_SAB_FILE;
    sProp[ivallogonPR_SAB_FILE].Value.lpszA = szFileName;

    /*
    *  Set the id property
    */

    sProp[ivallogonPR_SAB_UID].ulPropTag = PR_SAB_UID;
    sProp[ivallogonPR_SAB_UID].Value.bin.cb = sizeof(MAPIUID);
    sProp[ivallogonPR_SAB_UID].Value.bin.lpb = (LPBYTE) &muid;

    /*
    *  Save the sab file and the uid back into the profile
    */
    hResult = lpProf->lpVtbl->SetProps(
        lpProf,
        sizeof(sProp) / sizeof(SPropValue),
        sProp,
        NULL);

    if (HR_FAILED(hResult))
    {
        /*
        *  Do nothing...  So I couldn't save it away this time...
        */
        DebugTrace(TEXT("ServiceEntry could not SetProp in profile"));
        hResult = hrSuccess;
    }

out:
    if (lpProf)
        lpProf->lpVtbl->Release(lpProf);

    DebugTraceResult(ServiceEntry, hResult);
    return hResult;
}

/*
 -  HrOpenSingleProvider
 -
 *  Opens the profile section associated with this provider.
 *
 *  If the ServiceEntry() function exported from a provider had
 *  more than 1 section associated with it, this is where you'd get the chance
 *  to get all of them.
 */

static SizedSPropTagArray(1, tagaProviderTable) =
{
    1,
    {
        PR_PROVIDER_UID
    }
};

HRESULT
HrOpenSingleProvider(LPPROVIDERADMIN lpAdminProviders,
                     LPPROFSECT FAR * lppProfSect)
{
    HRESULT hResult;
    LPMAPITABLE lpTable = NULL;
    LPSRowSet lpRows = NULL;
    LPSPropValue lpProp;

    hResult = lpAdminProviders->lpVtbl->GetProviderTable(
        lpAdminProviders,
        0,
        &lpTable);
    if (HR_FAILED(hResult))
        goto out;

    hResult = lpTable->lpVtbl->SetColumns(lpTable, (LPSPropTagArray) &tagaProviderTable, 0);
    if (HR_FAILED(hResult))
        goto out;

    hResult = lpTable->lpVtbl->QueryRows(lpTable, 1, 0, &lpRows);
    if (HR_FAILED(hResult))
        goto out;

    if (lpRows->cRows == 0)
    {
        hResult = ResultFromScode(MAPI_E_NOT_FOUND);
        goto out;
    }

    lpProp = lpRows->aRow[0].lpProps;

    hResult = lpAdminProviders->lpVtbl->OpenProfileSection(
                lpAdminProviders,
                (LPMAPIUID) lpProp->Value.bin.lpb,
                NULL,
                MAPI_MODIFY,
                lppProfSect);

out:
    FreeProws(lpRows);

    if (lpTable)
        lpTable->lpVtbl->Release(lpTable);

    DebugTraceResult(HrOpenSingleProvider, hResult);
    return hResult;
}

/*
 -  ScLoadString
 -
 *  Loads a string from a resource.  It will optionally allocate the string if

*  a allocation function is passed in.  Otherwise it assumes that the *lppsz
 *  is already allocated.
 */

SCODE ScLoadString( UINT                ids,
                    ULONG               ulcch,
                    LPALLOCATEBUFFER    lpAllocBuff,
                    HINSTANCE           hLibrary,
                    LPSTR *             lppsz)
{
    SCODE sc = S_OK;
    int iRet;

    /*
     *  Assert parameters
     */
    Assert((lpAllocBuff ? !IsBadCodePtr((FARPROC) lpAllocBuff):TRUE));
    Assert(ids!=0);

    if (lpAllocBuff)
    {
        sc = lpAllocBuff(ulcch, lppsz);
        if (FAILED(sc))
        {
            goto out;
        }
    }
#ifdef DEBUG
    else
    {
        Assert(!IsBadWritePtr(*lppsz, (UINT) ulcch));
    }
#endif /* DEBUG */

    iRet = LoadStringA( hLibrary,
                        ids,
                        *lppsz,
                        (UINT) ulcch);

    if (!iRet)
    {
        DebugTrace("LoadString() failed...\n");
        sc = E_FAIL;
        goto out;
    }
out:

    DebugTraceSc(ScLoadString, sc);
    return sc;
}


ABP.H

/***********************************************************************
 *
 *  ABP.H
 *
 *  Definitions for Sample Address Book Provider
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#ifdef _WIN32
#define INC_OLE2                /* Get the OLE2 stuff */
#define INC_RPC                 /* harmless on Windows NT; Windows 95 needs it */
#endif
#include <windows.h>
#include <windowsx.h>
#include <limits.h>
#include <mapiwin.h>
#include <mapidbg.h>

#include <mapidefs.h>
#include <mapicode.h>
#include <mapitags.h>
#include <mapiguid.h>
#include <mapispi.h>
#include <mapiutil.h>
#include <mapival.h>

#include <memory.h>
#include <commdlg.h>

/*
 *  IUnknown base members
 */
#define SAB_IUnknown                    \
    LONG                lcInit;         \
    HRESULT             hResult;        \
    UINT                idsLastError;   \
    HINSTANCE           hLibrary;       \
    LPALLOCATEBUFFER    lpAllocBuff;    \
    LPALLOCATEMORE      lpAllocMore;    \
    LPFREEBUFFER        lpFreeBuff;     \
    LPMALLOC            lpMalloc;       \
    CRITICAL_SECTION    cs

#define SAB_IUnkWithLogon               \
    SAB_IUnknown;                       \
    LPABLOGON           lpABLogon

#define SAB_Wrapped                     \
    SAB_IUnkWithLogon;                  \
    LPMAPIPROP          lpPropData

/*
 *  Definition of all the objects referred to in the various source modules
 *  of this provider.
 */

/*
 *  Declaration of IABProvider object implementation
 *  Code for this is in ABP.C
 */
#undef  INTERFACE
#define INTERFACE struct _ABP

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IABPROVIDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IABPROVIDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABP_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IABPROVIDER_METHODS(IMPL)
};

/*
 *  Declaration of IABLogon object implementation
 *  Code for this is in ABP.C
 */
#undef  INTERFACE
#define INTERFACE struct _ABPLOGON

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABPLOGON_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IABLOGON_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABPLOGON_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IABLOGON_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABPLOGON_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IABLOGON_METHODS(IMPL)
};

/*
 *  Declaration of IABContainer object implementation
 *  Code for this is in ROOT.C
 */
#undef  INTERFACE
#define INTERFACE   struct _ROOT

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ROOT_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ROOT_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ROOT_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPICONTAINER_METHODS(IMPL)
    MAPI_IABCONTAINER_METHODS(IMPL)
};

#define ROOT_ValidateObject(Method, pThis)          \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ROOT)))          \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ROOT##_##Method, hResult); \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblROOT)                 \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ROOT##_##Method, hResult); \
        return hResult;                             \
    }                                               \
}

/*
 *  Declaration of IABContainer object implementation
 *  Code for this is in ABCONT.C
 */
#undef  INTERFACE
#define INTERFACE   struct _ABCNT

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABC_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABC_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABC_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPICONTAINER_METHODS(IMPL)
    MAPI_IABCONTAINER_METHODS(IMPL)
};

#define ABC_ValidateObject(Method, pThis)           \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ABC)))           \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABC##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblABC)                  \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABC##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
}

/*
 *  Declaration of IMAPIContainer object implementation
 *  Code for this is in ABSEARCH.C
 */
#undef  INTERFACE
#define INTERFACE   struct _ABSRCH

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABSRCH_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABSRCH_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPICONTAINER_METHODS(IMPL)
        MAPI_IABCONTAINER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABSRCH_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPICONTAINER_METHODS(IMPL)
};

#define ABSRCH_ValidateObject(Method, pThis)        \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ABSRCH)))        \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABSRCH##_##Method, hResult);   \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblABSRCH)                   \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABSRCH##_##Method, hResult);   \
        return hResult;                             \
    }                                               \
}

/*
 *  Declaration of IMailUser object implementation
 *  Code for this is in ABUSER.C
 */
#undef  INTERFACE
#define INTERFACE   struct _ABUSER

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABU_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAILUSER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABU_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAILUSER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABU_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAILUSER_METHODS(IMPL)
};

#define ABU_ValidateObject(Method, pThis)       \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ABUSER)))        \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABU##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblABU)                  \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABU##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
}

/*
 *  Declaration of IMAPIProp object implementation
 *  Code for this is in WRAP.C
 */
#undef  INTERFACE
#define INTERFACE   struct _WRAP

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, WRAP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, WRAP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(WRAP_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
};

#undef  INTERFACE
#define INTERFACE   struct _IVTABC

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, IVTABC_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPITABLE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, IVTABC_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPITABLE_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(IVTABC_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPITABLE_METHODS(IMPL)
};

/*
 *  Internal utility functions that allow access to data stored in the Init object
 */
void
RemoveLogonObject(LPABPROVIDER lpABProvider, LPVOID lpvABLogon, LPFREEBUFFER lpFreeBuff);

void
FindLogonObject(LPABPROVIDER lpABProvider, LPMAPIUID lpMuidToFind, LPABLOGON * lppABLogon);

/*
 *  Internal utility functions that allow access to data stored in the logon object
 */

LPMAPIUID
LpMuidFromLogon(LPABLOGON lpABLogon);

HRESULT
HrLpszGetCurrentFileName(LPABLOGON lpABLogon, LPSTR * lppszFileName);

HRESULT
HrReplaceCurrentFileName(LPABLOGON lpABLogon, LPSTR lpstrT);

void
GenerateContainerDN(LPABLOGON lpABLogon, LPSTR lpszName);

BOOL
FEqualSABFiles( LPABLOGON lpABLogon,
                LPSTR lpszFileName);

/*
 *
 *  Functions that return new objects
 *
 */

/*
 *  Creates a new ABPLogon object  (see ABLOGON.C)
 */
HRESULT
HrNewABLogon(   LPABLOGON *         lppABLogon,
                LPABPROVIDER        lpABP,
                LPMAPISUP           lpMAPISup,
                LPSTR               lpszSABFile,
                LPMAPIUID           lpmuid,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc );

/*
 *  Creates a new ROOT container object  (see ROOT.C)
 */
HRESULT
HrNewROOT(LPABCONT *        lppROOT,
          ULONG *           lpulObjType,
          LPABLOGON         lpABPLogon,
          LPCIID            lpInterface,
          HINSTANCE         hLibrary,
          LPALLOCATEBUFFER  lpAllocBuff,
          LPALLOCATEMORE    lpAllocMore,
          LPFREEBUFFER      lpFreeBuff,
          LPMALLOC          lpMalloc );

/*
 *  Creates a new directory container object (see ABCONT.C)
 */
HRESULT
HrNewSampDirectory( LPABCONT *          lppABC,
                    ULONG *             lpulObjType,
                    LPABLOGON           lpABPLogon,
                    LPCIID              lpInterface,
                    HINSTANCE           hLibrary,
                    LPALLOCATEBUFFER    lpAllocBuff,
                    LPALLOCATEMORE      lpAllocMore,
                    LPFREEBUFFER        lpFreeBuff,
                    LPMALLOC            lpMalloc );

/*
 *  Creates the search object associated with the SampDirectory (see ABSEARCH.C)
 */
HRESULT
HrNewSearch(LPMAPICONTAINER *   lppABSearch,
            LPABLOGON           lpABLogon,
            LPCIID              lpInterface,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc );

/*
 *  Creates a new Mail User object  (see ABUSER.C)
 */
HRESULT
HrNewSampUser(  LPMAILUSER *        lppMAPIPropEntry,
                ULONG *             lpulObjectType,
                ULONG               cbEntryID,
                LPENTRYID           lpEntryID,
                LPABLOGON           lpABPLogon,
                LPCIID              lpInterface,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc );

/*
 *  Creates a new one-off templateID object (see TID.C)
 */
HRESULT
HrNewOOTID( LPMAPIPROP *        lppMAPIPropNew,
            ULONG               cbTemplateId,
            LPENTRYID           lpTemplateId,
            ULONG               ulTemplateFlags,
            LPMAPIPROP          lpMAPIPropData,
            LPABLOGON           lpABPLogon,
            LPCIID              lpInterface,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc );

/*
 *  Creates a new one-off mail user object (see OOUSER.C)
 */
HRESULT
HrNewSampOOUser(LPMAILUSER *        lppMAPIPropEntry,
                ULONG *             lpulObjectType,
                ULONG               cbEntryID,
                LPENTRYID           lpEntryID,
                LPABLOGON           lpABPLogon,
                LPCIID              lpInterface,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc );

/*
 *  Creates a new status object for this provider (see STATUS.C)
 */
HRESULT
HrNewStatusObject(LPMAPISTATUS *    lppABS,
                ULONG *             lpulObjType,
                ULONG               ulFlags,
                LPABLOGON           lpABPLogon,
                LPCIID              lpInterface,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc );

/*
 * Creates a new templateID object that's associated with
 * a SAB mailuser object.
 */
HRESULT
HrNewTID (  LPMAPIPROP *        lppMAPIPropNew,
            ULONG               cbTemplateId,
            LPENTRYID           lpTemplateId,
            ULONG               ulTemplateFlags,
            LPMAPIPROP          lpMAPIPropData,
            LPABLOGON           lpABPLogon,
            LPCIID              lpInterface,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc );

/*
 *  Creates a new contents table object
 */
HRESULT
HrNewIVTAbc (LPMAPITABLE *      lppIVTAbc,
             LPABLOGON          lpABLogon,
             LPABCONT           lpABC,
             HINSTANCE          hLibrary,
             LPALLOCATEBUFFER   lpAllocBuff,
             LPALLOCATEMORE     lpAllocMore,
             LPFREEBUFFER       lpFreeBuff,
             LPMALLOC           lpMalloc );

/*
 *  Creates/Updates the SAB's root hierarchy
 */
HRESULT
HrBuildRootHier(LPABLOGON lpABLogon, LPMAPITABLE * lppMAPITable);

/*
 *  Sets an error string associated with a particular hResult on an object.
 *  It's used in conjunction with the method GetLastError.
 */
VOID SetErrorIDS (LPVOID lpObject, HRESULT hResult, UINT ids);

/*
 *  Loads a string from a resource.  Optionally allocates room for the string
 *  if lpAllocBuff is not NULL.  See ABP.C.
 */
SCODE ScLoadString( UINT                ids,
                    ULONG               ulcch,
                    LPALLOCATEBUFFER    lpAllocBuff,
                    HINSTANCE           hLibrary,
                    LPSTR *             lppsz);

/*
 *  The sample ABPs MAPIUID
 *
 *  This MAPIUID must be unique (see the Service Provider Writer's Guide on
 *  Constructing Entry IDs)
 */
#define MUIDABSAMPLE {0x81,0x2b,0x1f,0x40,0xbe,0xa3,0x10,0x19,0x9d,0x6e,0x00,0xdd,0x01,0x0f,0x54,0x02}

#define SZEMAILTYPE                         "MSPEER"
#define MAX_DISPLAY_NAME                    45

/*
 *  Browse record
 *
 *  The .SAB files are made up of the following records.
 */

#define MAX_NAME_SIZE   30
#define MAX_EMA_SIZE    50

#pragma pack(4)
typedef struct _ABCREC
{

    char rgchDisplayName[MAX_NAME_SIZE + 1];
    char rgchEmailAddress[MAX_EMA_SIZE + 1];

} ABCREC, *LPABCREC;
#pragma pack()

/*
 *  Defines of various entryid types
 */
#define SAMP_DIRECTORY  0x00000000
#define SAMP_USER       0x00000001
#define SAMP_UNKNOWN    0x00000002
#define SAMP_OOUSER     0x00000003

/*
 *  The version of this ABPs entryids
 */
#define SAMP_VERSION    0x000000002

/*
 *  Directory entry id structure
 *
 *  This entryid is permanent.
 */
typedef struct _dir_entryid
{

    BYTE abFlags[4];
    MAPIUID muid;
    ULONG ulVersion;
    ULONG ulType;
    MAPIUID muidID;

} DIR_ENTRYID, *LPDIR_ENTRYID;

/*
 *  Mail user entry id structure
 *
 *  This entryid is permanent.
 */
#pragma pack(4)
typedef struct _usr_entryid
{

    BYTE abFlags[4];
    MAPIUID muid;
    ULONG ulVersion;
    ULONG ulType;
    ABCREC abcrec;

} USR_ENTRYID, *LPUSR_ENTRYID;
#pragma pack()

/*
 *  One off user entry id structure
 *
 *  This entryid is permanent.
 */
typedef struct _oousr_entryid
{
    BYTE abFlags[4];
    MAPIUID muid;
    ULONG ulVersion;
    ULONG ulType;

} OOUSR_ENTRYID, *LPOOUSR_ENTRYID;

/*
 *  Externs used throughout various modules within this provider.
 *  They actually get defined in abp.c
 */

extern LCID lcidUser;

extern MAPIUID muidABSample;

extern MAPIUID muidSABProviderID;

extern DTBLLABEL dtbllabel;

extern DTBLPAGE dtblpage;

extern DTBLGROUPBOX dtblgroupbox;

extern CHAR szNoFilter[];

extern LPSTR lpszEMT;

extern CHAR szAddrTypeFilter[];

extern CHAR szFileNameFilter[];

#define PR_LISTBOX_TABLE                PROP_TAG(PT_OBJECT,0x6605)
#define PR_COMBOBOX_TABLE               PROP_TAG(PT_OBJECT,0x6607)
#define PR_DDLISTBOX_TABLE              PROP_TAG(PT_OBJECT,0x6609)

/*
 *  Properties defined for our one-off entry
 */
#define     PR_SERVER_NAME              PROP_TAG(PT_TSTRING,0x660b)
#define     PR_SHARE_NAME               PROP_TAG(PT_TSTRING,0x660c)
#define     PR_PATH_NAME                PROP_TAG(PT_TSTRING,0x660d)

#define MAX_SERVER_NAME                 15
#define MAX_SHARE_NAME                  12

/*
 *  MACROs for Win16
 */

#ifdef WIN16

#define lstrcatA        lstrcat
#define wsprintfA       wsprintf
#define CharUpperBuffA  CharUpperBuff

#endif /*WIN16*/


ABSEARCH.C

/***********************************************************************
 *
 *  ABSEARCH.C
 *
 *  Sample AB directory container Search object
 *
 *  This file contains the code for implementing the Sample AB
 *  directory container search object.  Also known as advanced
 *  search.
 *
 *  This search object was retrieved by OpenProperty on PR_SEARCH on the
 *  AB directory found in ABCONT.C.
 *
 *  The following routines are implemented in this file:
 *
 *      HrNewSearch
 *      ABSRCH_Release
 *      ABSRCH_SaveChanges
 *      ABSRCH_OpenProperty
 *      ABSRCH_GetSearchCriteria
 *
 *      HrGetSearchDialog
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"
#include <smpab.h>

/*
 *  Proptags used only in this module
 */
#define PR_ANR_STRING               PROP_TAG(PT_STRING8,0x6602)

/*
 *  Structure for the 'this'
 */

typedef struct _ABSRCH
{
    const ABSRCH_Vtbl FAR * lpVtbl;

    SAB_Wrapped;

    /*  Private data */

    LPSPropValue lpRestrictData;

} ABSRCH, *LPABSRCH;

/* Display table control structures for the Search property sheet. */

/*
 *  The Sample AB exposes an 'advanced' search dialog.  The following
 *  structures define it's layout.
 */

/*
 *  The edit control that will have the name to be search for on it.
 */
#define MAX_SEARCH_NAME                 50

DTBLEDIT editSearchName =
{
    sizeof(DTBLEDIT),
    0,
    MAX_SEARCH_NAME,
    PR_ANR_STRING
};

/*
 *  Display table pages for Search property sheet
 */
DTCTL rgdtctlSearchGeneral[] =
{

    /*
     *  Defines the name of this Pane.
     */
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},

    /* group box control */
    {DTCT_GROUPBOX, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtblgroupbox},

    /* control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDC_SEARCH_NAME,
        &editSearchName},
};

/*
 *  Actual definition of the set of pages that make up this advanced search
 *  dialog.  Note that there's no limit to the number of pages that can be
 *  displayed.  This sample AB, however, only exposes on page.
 */
DTPAGE rgdtpageSearch[] =
{
    {
        sizeof(rgdtctlSearchGeneral) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(SearchGeneralPage),
        "",
        rgdtctlSearchGeneral
    }
};

/*
 *  ABSearch vtbl is filled in here.
 */
ABSRCH_Vtbl vtblABSRCH =
{
    (ABSRCH_QueryInterface_METHOD *)        ROOT_QueryInterface,
    (ABSRCH_AddRef_METHOD *)                ROOT_AddRef,
    ABSRCH_Release,
    (ABSRCH_GetLastError_METHOD *)          ROOT_GetLastError,
    ABSRCH_SaveChanges,
    (ABSRCH_GetProps_METHOD *)              WRAP_GetProps,
    (ABSRCH_GetPropList_METHOD *)           WRAP_GetPropList,
    ABSRCH_OpenProperty,
    (ABSRCH_SetProps_METHOD *)              WRAP_SetProps,
    (ABSRCH_DeleteProps_METHOD *)           WRAP_DeleteProps,
    (ABSRCH_CopyTo_METHOD *)                WRAP_CopyTo,
    (ABSRCH_CopyProps_METHOD *)             WRAP_CopyProps,
    (ABSRCH_GetNamesFromIDs_METHOD *)       WRAP_GetNamesFromIDs,
    (ABSRCH_GetIDsFromNames_METHOD *)       WRAP_GetIDsFromNames,
    (ABSRCH_GetContentsTable_METHOD *)      ROOT_GetContentsTable,
    (ABSRCH_GetHierarchyTable_METHOD *)     ABC_GetHierarchyTable,
    (ABSRCH_OpenEntry_METHOD *)             ROOT_OpenEntry,
    (ABSRCH_SetSearchCriteria_METHOD *)     ROOT_SetSearchCriteria,
    ABSRCH_GetSearchCriteria,
};

HRESULT HrGetSearchDialog(LPABSRCH lpABSearch, LPMAPITABLE * lppSearchTable);

/*
 -  HrNewSearch
 -
 *  Creates an advanced search object
 *
 *
 */

/*
 *  Properties that are initially set on this object
 */
enum {  ivalabsrchPR_ANR_STRING = 0,
        cvalabsrchMax };

HRESULT
HrNewSearch(LPMAPICONTAINER *   lppABSearch,
            LPABLOGON           lpABLogon,
            LPCIID              lpInterface,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc )
{
    HRESULT hResult = hrSuccess;
    LPABSRCH lpABSearch = NULL;
    SCODE sc;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[cvalabsrchMax];

    /*  Do I support this interface?? */
    if (lpInterface)
    {
        if (memcmp(lpInterface, &IID_IMAPIContainer, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIProp, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IUnknown, sizeof(IID)))
        {
            DebugTraceSc(HrNewSearch, MAPI_E_INTERFACE_NOT_SUPPORTED);
            return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        }
    }

    /*
     *  Allocate space for the directory container structure
     */

    sc = lpAllocBuff( sizeof(ABSRCH), (LPVOID *) &lpABSearch );

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    lpABSearch->lpVtbl = &vtblABSRCH;
    lpABSearch->lcInit = 1;
    lpABSearch->hResult = hrSuccess;
    lpABSearch->idsLastError = 0;

    lpABSearch->hLibrary = hLibrary;
    lpABSearch->lpAllocBuff = lpAllocBuff;
    lpABSearch->lpAllocMore = lpAllocMore;
    lpABSearch->lpFreeBuff = lpFreeBuff;
    lpABSearch->lpMalloc = lpMalloc;

    lpABSearch->lpABLogon = lpABLogon;
    lpABSearch->lpRestrictData = NULL;

    /*
     *  Create property storage object
     */

    sc = CreateIProp((LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    spv[ivalabsrchPR_ANR_STRING].ulPropTag = PR_ANR_STRING;
    spv[ivalabsrchPR_ANR_STRING].Value.lpszA = "";

    /*
     *   Set the default properties
     */
    hResult = lpPropData->lpVtbl->SetProps(lpPropData,
        cvalabsrchMax,
        spv,
        NULL);

    InitializeCriticalSection(&lpABSearch->cs);

    /*  We must AddRef the lpABLogon object since we will be using it
     */
    lpABLogon->lpVtbl->AddRef(lpABLogon);

    lpABSearch->lpPropData = (LPMAPIPROP) lpPropData;
    *lppABSearch = (LPMAPICONTAINER) lpABSearch;

out:

    DebugTraceResult(HrNewSearch, hResult);
    return hResult;

err:
    /*
     *  free the ABContainer object
     */
    lpFreeBuff( lpABSearch );

    /*
     *  free the property storage object
     */
    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    goto out;
}

/*
 -  ABSRCH_Release
 -
 *  Decrement lcInit.
 *      When lcInit == 0, free up the lpABSearch structure
 *
 */

STDMETHODIMP_(ULONG)
ABSRCH_Release(LPABSRCH lpABSearch)
{

    long lcInit;

    /*
     *  Check to see if it has a jump table
     */
    if (IsBadReadPtr(lpABSearch, sizeof(ABSRCH)))
    {
        /*
         *  No jump table found
         */
        return 1;
    }

    /*
     *  Check to see that it's the correct jump table
     */
    if (lpABSearch->lpVtbl != &vtblABSRCH)
    {
        /*
         *  Not my jump table
         */
        return 1;
    }

    Validate_IUnknown_Release(lpABSearch);

    EnterCriticalSection(&lpABSearch->cs);
    lcInit = --lpABSearch->lcInit;
    LeaveCriticalSection(&lpABSearch->cs);

    if (lcInit == 0)
    {

        /*
         *  Get rid of the lpPropData
         */
        if (lpABSearch->lpPropData)
            lpABSearch->lpPropData->lpVtbl->Release(lpABSearch->lpPropData);

        /*
         *  Free up the restriction data
         */
        lpABSearch->lpFreeBuff(lpABSearch->lpRestrictData);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpABSearch->lpABLogon)
        {
            lpABSearch->lpABLogon->lpVtbl->Release(lpABSearch->lpABLogon);
            lpABSearch->lpABLogon = NULL;
        }

        /*
         *  Destroy the critical section for this object
         */

        DeleteCriticalSection(&lpABSearch->cs);

        /*
         *  Set the Jump table to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */
        lpABSearch->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpABSearch->lpFreeBuff(lpABSearch);
        return 0;
    }

    return lpABSearch->lcInit;

}

/*
 -  ABSRCH_SaveChanges
 -
 *  This is used to save changes associated with the search dialog
 *  in order to get the advanced search restriction and to save changes
 *  associated with the container details dialog.
 *
 */
SPropTagArray tagaANR_INT =
{
    1,
    {
        PR_ANR_STRING
    }
};

STDMETHODIMP
ABSRCH_SaveChanges(LPABSRCH lpABSearch, ULONG ulFlags)
{
    HRESULT hResult;
    ULONG ulCount;
    LPSPropValue lpspv = NULL;
    LPPROPDATA lpPropData = (LPPROPDATA) lpABSearch->lpPropData;

    ABSRCH_ValidateObject(SaveChanges, lpABSearch);

    Validate_IMAPIProp_SaveChanges(lpABSearch, ulFlags);

    EnterCriticalSection(&lpABSearch->cs);

    /*
     *  Is there a PR_ANR_STRING??
     */
    hResult = lpPropData->lpVtbl->GetProps(lpPropData,
        &tagaANR_INT,
        0,      /* ansi */
        &ulCount,
        &lpspv);
    if (HR_FAILED(hResult))
    {
        goto ret;
    }

    if ((lpspv->ulPropTag == PR_ANR_STRING) && (lpspv->Value.lpszA[0] != '\0'))
    {
        /*
         * save away the information to build up the new restriction
         */

        /*  Free any existing data */
        if (lpABSearch->lpRestrictData)
        {
            lpABSearch->lpFreeBuff(lpABSearch->lpRestrictData);
        }

        lpABSearch->lpRestrictData = lpspv;
        lpspv = NULL;
    }

ret:

    LeaveCriticalSection(&lpABSearch->cs);

    lpABSearch->lpFreeBuff(lpspv);
    DebugTraceResult(ABSRCH_SaveChanges, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  ABSRCH_OpenProperty
 -
 *
 *  This method allows the opening of the following object:
 *
 *  PR_DETAILS_TABLE        :-  Gets the display table associated with
 *                              the advanced search dialog.
 */
STDMETHODIMP
ABSRCH_OpenProperty(LPABSRCH lpABSearch,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    ABSRCH_ValidateObject(OpenProperty, lpABSearch);

    Validate_IMAPIProp_OpenProperty(lpABSearch, ulPropTag, lpiid,
                            ulInterfaceOptions, ulFlags, lppUnk);

    /*
     *  Check for flags we can't support
     */

    if (ulFlags & (MAPI_CREATE|MAPI_MODIFY))
    {
        hResult = ResultFromScode(E_ACCESSDENIED);
        goto out;
    }

    if (ulInterfaceOptions & ~MAPI_UNICODE)
    {
        /*
         *  Only UNICODE flag should be set for any of the objects that might
         *  be returned from this object.
         */

        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        goto out;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);
        DebugTraceArg( ABSRCH_OpenProperty, "bad character width" );
        goto out;

    }

    /*
     *  Details for this Search object
     */

    if (ulPropTag == PR_DETAILS_TABLE)
    {
        if (!memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
        {
            hResult = HrGetSearchDialog(lpABSearch, (LPMAPITABLE *) lppUnk);

            goto out;
        }

    }

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:

    DebugTraceResult(ABSRCH_OpenProperty, hResult);
    return hResult;

}

/*
 -  ABSRCH_GetSearchCriteria
 -
 *  Generates the restriction associated with the data from
 *  the advanced search dialog.  This restriction is subsequently
 *  applied to the contents table retrieved from this container.
 */
STDMETHODIMP
ABSRCH_GetSearchCriteria(   LPABSRCH lpABSearch,
                        ULONG   ulFlags,
                        LPSRestriction FAR * lppRestriction,
                        LPENTRYLIST FAR * lppContainerList,
                        ULONG FAR * lpulSearchState)
{
    HRESULT hResult = hrSuccess;
    SCODE sc;
    LPSRestriction lpRestriction = NULL;
    LPSPropValue lpPropANR = NULL;
    LPSTR lpszPartName;
    LPSTR lpszRestrName;

    ABSRCH_ValidateObject(GetSearchCriteria, lpABSearch);

    Validate_IMAPIContainer_GetSearchCriteria(lpABSearch, ulFlags,
                                lppRestriction, lppContainerList,
                                lpulSearchState);

    if (!lpABSearch->lpRestrictData)
    {
        hResult = ResultFromScode(MAPI_E_NOT_INITIALIZED);

        if (lppRestriction)
            *lppRestriction = NULL;

        if (lppContainerList)
            *lppContainerList = NULL;

        if (lpulSearchState)
            *lpulSearchState = 0L;

        DebugTraceResult(ABSRCH_GetSearchCriteria, hResult);
        return hResult;
    }

    if (ulFlags & MAPI_UNICODE)
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);

        DebugTraceResult(ABSRCH_GetSearchCriteria, hResult);
        return hResult;
    }

    /*
     *  Entering state dependant section
     */
    EnterCriticalSection(&lpABSearch->cs);

    /*
     *  Ok, now build up a restriction using lpRestrictData (an LPSPropValue)
     */

    sc = lpABSearch->lpAllocBuff(sizeof(SRestriction), &lpRestriction);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    sc = lpABSearch->lpAllocMore(sizeof(SPropValue), lpRestriction, &lpPropANR);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;

    }

    lpszRestrName = lpABSearch->lpRestrictData->Value.lpszA;

    sc = lpABSearch->lpAllocMore(lstrlenA(lpszRestrName)+1,
        lpRestriction,
        &lpszPartName);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    lstrcpyA(lpszPartName, lpszRestrName);

    lpPropANR->ulPropTag = PR_ANR;
    lpPropANR->Value.LPSZ = lpszPartName;

    lpRestriction->rt = RES_PROPERTY;
    lpRestriction->res.resProperty.relop = RELOP_EQ;
    lpRestriction->res.resProperty.ulPropTag = PR_ANR;
    lpRestriction->res.resProperty.lpProp = lpPropANR;

    *lppRestriction = lpRestriction;

    /*
     *  The returned SearchState is set to 0 because none
     *  of the defined states match what's going on.
     */
    if (lpulSearchState)
        *lpulSearchState = 0;

out:
    LeaveCriticalSection(&lpABSearch->cs);

    DebugTraceResult(ABSRCH_GetSearchCriteria, hResult);
    return hResult;

err:
    lpABSearch->lpFreeBuff(lpRestriction);

    goto out;
}

/*
 -  HrGetSearchDialog
 -
 *
 *  Builds a display table for the search dialog.
 */

HRESULT
HrGetSearchDialog(LPABSRCH lpABSearch, LPMAPITABLE * lppSearchTable)
{
    HRESULT hResult;

    /* Create a display table */
    hResult = BuildDisplayTable(
            lpABSearch->lpAllocBuff,
            lpABSearch->lpAllocMore,
            lpABSearch->lpFreeBuff,
            lpABSearch->lpMalloc,
            lpABSearch->hLibrary,
            sizeof(rgdtpageSearch) / sizeof(DTPAGE),
            rgdtpageSearch,
            0,
            lppSearchTable,
            NULL);

    DebugTraceResult(ABSRCH_GetSearchDialog, hResult);
    return hResult;
}


ABUSER.C

/***********************************************************************
 *
 *  ABUSER.C
 *
 *  Sample AB Mail User object
 *  This file contains the code for implementing the Sample AB
 *  Mail user.
 *
 *  The mail user has a read-only interface.  Hence a few of the methods
 *  will always return E_ACCESSDENIED.
 *
 *  Typically it would be in this module (particularly HrNewABUser) that
 *  your provider would validate the existance of the entry associated
 *  with a particular entryid.  The Sample Address Book doesn't do this
 *  because it doesn't need to.
 *
 *  The following routines are implemented in this file:
 *
 *  HrNewSampUser
 *  ABU_QueryInterface
 *  ABU_Release
 *  ABU_OpenProperty
 *  HrBuildListBoxTable
 *  HrBuildDDListboxTable
 *  HrBuildComboBoxTable
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"

/*
 *  Defined in ABP.C
 */

#define MAX_ADDRTYPE                        25
#define MAX_EMAIL_ADDRESS                   MAX_PATH
#define MAX_COMBO_EDIT                      25
#define MAX_LISTBOX_ROWS                    25

/* property tags used on details property sheet */
#define PR_CHECKBOX_1_VALUE             PROP_TAG(PT_BOOLEAN,0x6601)
#define PR_CHECKBOX_2_VALUE             PROP_TAG(PT_BOOLEAN,0x6602)
#define PR_CHECKBOX_3_VALUE             PROP_TAG(PT_BOOLEAN,0x6603)
#define PR_LISTBOX_VALUE                PROP_TAG(PT_LONG,0x6604)
#define PR_COMBOBOX_VALUE               PROP_TAG(PT_STRING8,0x6606)
#define PR_DDLISTBOX_VALUE              PROP_TAG(PT_LONG,0x6608)

/* Display table control structures for the General and Options property sheets. */

/*  First Pane "General", this is the information specific about the first
 *  edit control, which is used to display the display name of this user. */
DTBLEDIT editUserDisplayName =
{
    sizeof(DTBLEDIT),
    0,
    MAX_DISPLAY_NAME,
    PR_DISPLAY_NAME_A
};

/*
 *  "General" pane.  This is the information about the email address type associated
 *  with this entry
 */
DTBLEDIT editUserEmailType =
{
    sizeof(DTBLEDIT),
    0,
    MAX_ADDRTYPE,
    PR_ADDRTYPE_A
};

/*
 *  "General" pane.  This is the information about the email address associated with
 *  this entry.
 */
DTBLEDIT editUserEmailAddress =
{
    sizeof(DTBLEDIT),
    0,
    MAX_EMAIL_ADDRESS,
    PR_EMAIL_ADDRESS_A
};

/*
 *  This is the actual definition for the "General" pane.  It lists all the controls
 *  found on it.
 */
DTCTL rgdtctlUserGeneral[] =
{
    /*
     * general property page
     *  MAPI UI will default this first pane to be named "General" in this release.
     *  However, subsequent releases will not include this behaviour.  Because of this
     *  it is stongly suggested that your provider provide a DTCT_PAGE control for each
     *  pane.
     */
    {DTCT_PAGE, 0, NULL, 0, NULL, 0,
        &dtblpage},

    /* display name control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_EDIT, 0, NULL, 0, szNoFilter, IDC_USER_DISPLAY_NAME,
        &editUserDisplayName},

    /* inbox path control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_EDIT, 0, NULL, 0, szNoFilter, IDC_USER_INBOX_PATH,
        &editUserEmailAddress},

    /* address type control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_EDIT, 0, NULL, 0, szAddrTypeFilter, IDC_USER_EMAIL_TYPE,
        &editUserEmailType},
};

/* controls for the options property page */
/*
 *  "Options" property page.  This is the information that allows the
 *  UI to fill in the listbox control on this pane.
 */
 DTBLLBX listbox1 =
{
    0,
    PR_LISTBOX_VALUE,
    PR_LISTBOX_TABLE
};

/*
 *  "Options" property page.  The following three controls define a set of check boxes
 *  that are found on this pane.  Notice that the three prop tags in these structures
 *  are different - compare them to the radio buttons found in ABCONT.C.
 */
 DTBLCHECKBOX checkbox1 =
{
    sizeof(DTBLCHECKBOX),
    0,
    PR_CHECKBOX_1_VALUE
};

 DTBLCHECKBOX checkbox2 =
{
    sizeof(DTBLCHECKBOX),
    0,
    PR_CHECKBOX_2_VALUE
};

 DTBLCHECKBOX checkbox3 =
{
    sizeof(DTBLCHECKBOX),
    0,
    PR_CHECKBOX_3_VALUE
};

/*
 *   "Options" property page.  This is the information required for
 *  the UI to generate a drop down list box.
 */
 DTBLDDLBX ddlistbox1 =
{
    sizeof(DTBLDDLBX),
    PR_DISPLAY_NAME_A,
    PR_DDLISTBOX_VALUE,
    PR_DDLISTBOX_TABLE
};

/*
 *  "Options" property page.  This is the information required for the
 *  UI to generate a combo box control.
 */
 DTBLCOMBOBOX combobox1 =
{
    sizeof(DTBLCOMBOBOX),
    0,
    MAX_COMBO_EDIT,
    PR_COMBOBOX_VALUE,
    PR_COMBOBOX_TABLE
};

/*
 *  This is the actual definition of the "Options" pane.  This lists all the
 *  controls found on that page.
 */
 DTCTL rgdtctlUserAdvanced[] =
{

    /* options property page
     *  This tells the UI what the tab for this pane should say.
     */
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},

    /* control and listbox */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_LBX, 0, NULL, 0, NULL,
        IDC_USER_LISTBOX, &listbox1},

    /* group box and radio buttons */
    {DTCT_GROUPBOX, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtblgroupbox},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL,
        IDC_USER_CHECKBOX_1, &checkbox1},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL,
        IDC_USER_CHECKBOX_2, &checkbox2},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL,
        IDC_USER_CHECKBOX_3, &checkbox3},

    /* control and drop down listbox */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_DDLBX, DT_EDITABLE, NULL, 0, NULL,
        IDC_USER_DDLISTBOX, &ddlistbox1},

    /* control and combobox */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_COMBOBOX, DT_EDITABLE, NULL, 0, szNoFilter,
        IDC_USER_COMBOBOX, &combobox1},
};

/* Display table pages */

/*
 *  This lists the order in which the panes will appear in the UI.  The
 *  UI is driven by a display table which is generated by a call to
 *  BuildDisplayTable() which takes the following DTPAGE structure.
 */
DTPAGE rgdtpage[] =
{
    {
        sizeof(rgdtctlUserGeneral) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(UserGeneralPage),
        "",
        rgdtctlUserGeneral
    },
    {
        sizeof(rgdtctlUserAdvanced) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(UserAdvancedPage),
        "",
        rgdtctlUserAdvanced
    }

};

/*
 *  Definition of the ABUSER object
 */
typedef struct _ABUSER
{
    const ABU_Vtbl FAR * lpVtbl;

    SAB_Wrapped;

    /*
     *   Tables used in options property page
     */
    LPTABLEDATA lpTDatListBox;
    LPTABLEDATA lpTDatDDListBox;
    LPTABLEDATA lpTDatComboBox;

} ABUSER, *LPABUSER;

/*
 *  ABUser vtbl filled in here
 */
const ABU_Vtbl vtblABU =
{

    ABU_QueryInterface,
    (ABU_AddRef_METHOD *)           ROOT_AddRef,
    ABU_Release,
    (ABU_GetLastError_METHOD *)     ROOT_GetLastError,
    (ABU_SaveChanges_METHOD *)      WRAP_SaveChanges,
    (ABU_GetProps_METHOD *)         WRAP_GetProps,
    (ABU_GetPropList_METHOD *)      WRAP_GetPropList,
    ABU_OpenProperty,
    (ABU_SetProps_METHOD *)         WRAP_SetProps,
    (ABU_DeleteProps_METHOD *)      WRAP_DeleteProps,
    (ABU_CopyTo_METHOD *)           WRAP_CopyTo,
    (ABU_CopyProps_METHOD *)        WRAP_CopyProps,
    (ABU_GetNamesFromIDs_METHOD *)  WRAP_GetNamesFromIDs,
    (ABU_GetIDsFromNames_METHOD *)  WRAP_GetIDsFromNames,
};

HRESULT HrBuildListBoxTable(LPABUSER lpABUser);
HRESULT HrBuildDDListBoxTable(LPABUSER lpABUser);
HRESULT HrBuildComboBoxTable(LPABUSER lpABUser);

enum {  ivalusrPR_DISPLAY_TYPE = 0,
        ivalusrPR_OBJECT_TYPE,
        ivalusrPR_ENTRYID,
        ivalusrPR_RECORD_KEY,
        ivalusrPR_DISPLAY_NAME_A,
        ivalusrPR_TRANSMITABLE_DISPLAY_NAME_A,
        ivalusrPR_EMAIL_ADDRESS_A,
        ivalusrPR_ADDRTYPE_A,
        ivalusrPR_SEARCH_KEY,
        ivalusrPR_LISTBOX_VALUE,
        ivalusrPR_CHECKBOX_1_VALUE,
        ivalusrPR_CHECKBOX_2_VALUE,
        ivalusrPR_CHECKBOX_3_VALUE,
        ivalusrPR_DDLISTBOX_VALUE,
        ivalusrPR_COMBOBOX_VALUE,
        ivalusrPR_TEMPLATEID,
        cvalusrMax };

static SizedSPropTagArray( cvalusrMax, tagaABUAccess) =
{
    cvalusrMax,
    {   PR_DISPLAY_TYPE,
        PR_OBJECT_TYPE,
        PR_ENTRYID,
        PR_RECORD_KEY,
        PR_DISPLAY_NAME_A,
        PR_TRANSMITABLE_DISPLAY_NAME_A,
        PR_EMAIL_ADDRESS_A,
        PR_ADDRTYPE_A,
        PR_SEARCH_KEY,
        PR_LISTBOX_VALUE,
        PR_CHECKBOX_1_VALUE,
        PR_CHECKBOX_2_VALUE,
        PR_CHECKBOX_3_VALUE,
        PR_DDLISTBOX_VALUE,
        PR_COMBOBOX_VALUE,
        PR_TEMPLATEID
      }
};

static ULONG    rgulABUAccess[cvalusrMax] =
{
    IPROP_READONLY | IPROP_CLEAN,   /* PR_DISPLAY_TYPE */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_OBJECT_TYPE */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_ENTRYID */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_RECORD_KEY */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_DISPLAY_NAME_A */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_TRANSMITABLE_DISPLAY_NAME_A */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_EMAIL_ADDRESS_A */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_ADDRTYPE_A */
    IPROP_READONLY | IPROP_CLEAN,   /* PR_SEARCH_KEY */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_LISTBOX_VALUE */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_CHECKBOX_1_VALUE */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_CHECKBOX_2_VALUE */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_CHECKBOX_3_VALUE */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_DDLISTBOX_VALUE */
    IPROP_READWRITE | IPROP_CLEAN,  /* PR_COMBOBOX_VALUE */
    IPROP_READONLY | IPROP_CLEAN    /* PR_TEMPLATEID */
};

/*************************************************************************
 *
 -  HrNewSampUser
 -
 *  Creates the IMAPIProp associated with a mail user.
 *
 *
 */
HRESULT
HrNewSampUser(  LPMAILUSER *        lppMAPIPropEntry,
                ULONG *             lpulObjType,
                ULONG               cbEntryID,
                LPENTRYID           lpEntryID,
                LPABLOGON           lpABPLogon,
                LPCIID              lpInterface,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc )
{
    LPABUSER lpABUser = NULL;
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[cvalusrMax];
    ULONG cbT = 0;
    LPBYTE lpbT = NULL;
    LPSTR lpszEMA = NULL;

    /*  Do I support this interface?? */
    if (lpInterface)
    {
        if (memcmp(lpInterface, &IID_IMailUser, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIProp, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IUnknown, sizeof(IID)))
        {
            DebugTraceSc(HrNewSampUser, MAPI_E_INTERFACE_NOT_SUPPORTED);
            return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        }
    }
    /*
     *  Allocate space for the ROOT structure
     */
    sc = lpAllocBuff(sizeof(ABUSER), (LPVOID *) &lpABUser);
    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    lpABUser->lpVtbl = &vtblABU;
    lpABUser->lcInit = 1;
    lpABUser->hResult = hrSuccess;
    lpABUser->idsLastError = 0;

    lpABUser->hLibrary = hLibrary;
    lpABUser->lpAllocBuff = lpAllocBuff;
    lpABUser->lpAllocMore = lpAllocMore;
    lpABUser->lpFreeBuff = lpFreeBuff;
    lpABUser->lpMalloc = lpMalloc;

    lpABUser->lpABLogon = lpABPLogon;
    lpABUser->lpTDatListBox = NULL;
    lpABUser->lpTDatDDListBox = NULL;
    lpABUser->lpTDatComboBox = NULL;

    /*
     *  Create property storage object
     */

    sc = CreateIProp((LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    /*
     *  Set up initial set of properties associated with this
     *  mailuser.
     */

    /*
     *  Easy ones first
     */

    spv[ivalusrPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    spv[ivalusrPR_DISPLAY_TYPE].Value.l = DT_MAILUSER;

    spv[ivalusrPR_OBJECT_TYPE].ulPropTag = PR_OBJECT_TYPE;
    spv[ivalusrPR_OBJECT_TYPE].Value.l = MAPI_MAILUSER;

    spv[ivalusrPR_ENTRYID].ulPropTag = PR_ENTRYID;
    spv[ivalusrPR_ENTRYID].Value.bin.cb = sizeof(USR_ENTRYID);
    spv[ivalusrPR_ENTRYID].Value.bin.lpb = (LPBYTE) lpEntryID;

    /*
     *  Now the calculated props
     */

    spv[ivalusrPR_RECORD_KEY].ulPropTag = PR_RECORD_KEY;
    spv[ivalusrPR_RECORD_KEY].Value.bin.cb = sizeof(USR_ENTRYID);
    spv[ivalusrPR_RECORD_KEY].Value.bin.lpb = (LPBYTE) lpEntryID;

    spv[ivalusrPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
    spv[ivalusrPR_DISPLAY_NAME_A].Value.lpszA = ((LPUSR_ENTRYID) lpEntryID)->abcrec.rgchDisplayName;

    /* Should always be the same as PR_DISPLAY_NAME */
    spv[ivalusrPR_TRANSMITABLE_DISPLAY_NAME_A].ulPropTag = PR_TRANSMITABLE_DISPLAY_NAME_A;
    spv[ivalusrPR_TRANSMITABLE_DISPLAY_NAME_A].Value.lpszA = ((LPUSR_ENTRYID) lpEntryID)->abcrec.rgchDisplayName;

    spv[ivalusrPR_EMAIL_ADDRESS_A].ulPropTag = PR_EMAIL_ADDRESS_A;
    spv[ivalusrPR_EMAIL_ADDRESS_A].Value.lpszA = ((LPUSR_ENTRYID) lpEntryID)->abcrec.rgchEmailAddress;
    lpszEMA = ((LPUSR_ENTRYID) lpEntryID)->abcrec.rgchEmailAddress;

    spv[ivalusrPR_ADDRTYPE_A].ulPropTag = PR_ADDRTYPE_A;
    spv[ivalusrPR_ADDRTYPE_A].Value.lpszA = lpszEMT;

    /*
     *  Build the search key...
     */
    /*  Search keys for mailable recipients that have email addresses are
     *  defined as "EmailType':'EmailAddress\0".  We do the +2 for the ':' and
     *  '\0'.
     */
    cbT = lstrlenA(lpszEMA) + lstrlenA(lpszEMT) + 2;

    sc = lpAllocBuff( cbT, (LPVOID *) &lpbT );
    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }
    lstrcpyA((LPSTR) lpbT, lpszEMT);
    lstrcatA((LPSTR) lpbT, ":");
    lstrcatA((LPSTR) lpbT, lpszEMA);
    CharUpperBuff((LPTSTR) lpbT, (UINT) cbT);

    spv[ivalusrPR_SEARCH_KEY].ulPropTag = PR_SEARCH_KEY;
    spv[ivalusrPR_SEARCH_KEY].Value.bin.cb = cbT;
    spv[ivalusrPR_SEARCH_KEY].Value.bin.lpb = lpbT;

    /* properties used by the options property page */
    spv[ivalusrPR_LISTBOX_VALUE].ulPropTag = PR_LISTBOX_VALUE;
    spv[ivalusrPR_LISTBOX_VALUE].Value.l = 5;

    spv[ivalusrPR_CHECKBOX_1_VALUE].ulPropTag = PR_CHECKBOX_1_VALUE;
    spv[ivalusrPR_CHECKBOX_1_VALUE].Value.b = TRUE;

    spv[ivalusrPR_CHECKBOX_2_VALUE].ulPropTag = PR_CHECKBOX_2_VALUE;
    spv[ivalusrPR_CHECKBOX_2_VALUE].Value.b = FALSE;

    spv[ivalusrPR_CHECKBOX_3_VALUE].ulPropTag = PR_CHECKBOX_3_VALUE;
    spv[ivalusrPR_CHECKBOX_3_VALUE].Value.b = TRUE;

    spv[ivalusrPR_DDLISTBOX_VALUE].ulPropTag = PR_DDLISTBOX_VALUE;
    spv[ivalusrPR_DDLISTBOX_VALUE].Value.l = 10;

    spv[ivalusrPR_COMBOBOX_VALUE].ulPropTag = PR_COMBOBOX_VALUE;
    spv[ivalusrPR_COMBOBOX_VALUE].Value.lpszA = "ComboBox item 10";

    /*
     *  Note that we're using our entryID for our templateID.
     *  This is a really simple way to implement templateIDs.
     *  (See TID.C)
     */
    spv[ivalusrPR_TEMPLATEID].ulPropTag = PR_TEMPLATEID;
    spv[ivalusrPR_TEMPLATEID].Value.bin.cb = sizeof(USR_ENTRYID);
    spv[ivalusrPR_TEMPLATEID].Value.bin.lpb = (LPBYTE) lpEntryID;

    /*
     *   Set the default properties
     */
    hr = lpPropData->lpVtbl->SetProps(
        lpPropData,
        cvalusrMax,
        spv,
        NULL);

    if (HR_FAILED(hr))
    {
        goto err;
    }

    /*
     *  Although this object is basically read only, we wanted to show
     *  an example of how the check-boxes and other controls on the
     *  "Options" pane work.  If we had set this object to be read only,
     *  the values behind those controls would have been static.
     */
    (void)lpPropData->lpVtbl->HrSetObjAccess(lpPropData, IPROP_READWRITE);

    /*
     *  Set the client access rights to the various properties.  Notice
     *  that everything is READONLY except for the properties related to
     *  the controls on the "Options" pane.
     *
     *  All properties are intially marked as CLEAN.
     */
    (void) lpPropData->lpVtbl->HrSetPropAccess(lpPropData,
                                (LPSPropTagArray) &tagaABUAccess,
                                rgulABUAccess);

    lpABUser->lpPropData = (LPMAPIPROP) lpPropData;

    InitializeCriticalSection(&lpABUser->cs);

    /*  We must AddRef the lpABPLogon object since we will be using it
     */
    lpABPLogon->lpVtbl->AddRef(lpABPLogon);

    *lppMAPIPropEntry = (LPVOID) lpABUser;
    *lpulObjType = MAPI_MAILUSER;

out:
    lpFreeBuff(lpbT);

    DebugTraceResult(HrNewSampUser, hr);
    return hr;

err:
    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    lpFreeBuff(lpABUser);

    goto out;

}

/*************************************************************************
 *
 *
 -  ABU_QueryInterface
 -
 *  Allows QI'ing to only IUnknown, IMAPIProp, and IMailUser.
 *
 *  This method is reused in TID.C, OOTID.C, and ABOOSER.C.  Hence the
 *  difference in checking of the 'this' pointer from other methods within
 *  this object.
 */
STDMETHODIMP
ABU_QueryInterface(LPABUSER lpABUser,
    REFIID lpiid,
    LPVOID FAR * lppNewObj)
{

    HRESULT hr = hrSuccess;

    /*  Minimally check the lpABUser object
     *  Can't do any more extensive checking that this because this method is reused
     *  in OOUSER.C.
     */

    if (IsBadReadPtr(lpABUser, offsetof(ABUSER, lpVtbl)+sizeof(ABU_Vtbl *)))
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    if (IsBadReadPtr(lpABUser->lpVtbl,
                     offsetof(ABU_Vtbl, QueryInterface)+sizeof(ABU_QueryInterface_METHOD *)))
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    if (ABU_QueryInterface != lpABUser->lpVtbl->QueryInterface)
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IUnknown_QueryInterface(lpABUser, lpiid, lppNewObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIProp, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMailUser, sizeof(IID)))
    {
        *lppNewObj = NULL;      /* OLE requires zeroing [out] parameter */
        hr = ResultFromScode(E_NOINTERFACE);
        goto out;
    }

    /*  Bump the usage count and return same object pointer. */

    EnterCriticalSection(&lpABUser->cs);
    ++lpABUser->lcInit;
    LeaveCriticalSection(&lpABUser->cs);

    *lppNewObj = lpABUser;

out:

    DebugTraceResult(ABU_QueryInterface, hr);
    return hr;
}

/**************************************************
 *
 -  ABU_Release
 -
 *      Decrement lcInit.
 *      When lcInit == 0, free up the lpABUser structure
 *
 */
STDMETHODIMP_(ULONG)
ABU_Release(LPABUSER lpABUser)
{

    LONG lcInit;
    /*
     *  Check to see if it's big enough to hold this object
     */
    if (IsBadReadPtr(lpABUser, sizeof(ABUSER)))
    {
        /*
         *  Not large enough
         */
        return 1;
    }

    /*
     *  Check to see that it's the correct vtbl
     */
    if (lpABUser->lpVtbl != &vtblABU)
    {
        /*
         *  Not my vtbl
         */
        return 1;
    }

    Validate_IUnknown_Release(lpABUser);

    EnterCriticalSection(&lpABUser->cs);
    lcInit = --lpABUser->lcInit;
    LeaveCriticalSection(&lpABUser->cs);

    if (lcInit == 0)
    {

        /*
         *  Get rid of the lpPropData
         */

        lpABUser->lpPropData->lpVtbl->Release(lpABUser->lpPropData);

        /*
         *  Get rid of the tables used by the options property page
         */

        if (lpABUser->lpTDatListBox)
            lpABUser->lpTDatListBox->lpVtbl->Release(lpABUser->lpTDatListBox);

        if (lpABUser->lpTDatDDListBox)
            lpABUser->lpTDatDDListBox->lpVtbl->Release(lpABUser->lpTDatDDListBox);

        if (lpABUser->lpTDatComboBox)
            lpABUser->lpTDatComboBox->lpVtbl->Release(lpABUser->lpTDatComboBox);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpABUser->lpABLogon)
        {
            lpABUser->lpABLogon->lpVtbl->Release(lpABUser->lpABLogon);
            lpABUser->lpABLogon = NULL;
        }

        /*
         *  Destroy the critical section for this object
         */

        DeleteCriticalSection(&lpABUser->cs);

        /*
         *  Set the vtbl to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */

        lpABUser->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpABUser->lpFreeBuff( lpABUser );
        return 0;
    }

    return lcInit;

}

/*************************************************************************
 *
 -  ABU_OpenProperty
 -
 *
 *  This is how we get the display table associated with this users
 *  details screen.  This is also how we get the tables which are needed
 *  to fill out the "Options" property pane.
 */
STDMETHODIMP
ABU_OpenProperty(LPABUSER lpABUser,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{

    HRESULT hResult;

    ABU_ValidateObject(OpenProperty, lpABUser);

    Validate_IMAPIProp_OpenProperty(lpABUser, ulPropTag, lpiid,
                    ulInterfaceOptions, ulFlags, lppUnk);

    if (ulInterfaceOptions & ~MAPI_UNICODE )
    {
        /*
         *  Only the Unicode flag should be set for any of the objects that might
         *  be returned from this object.
         */

        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);

        DebugTraceResult(ABU_OpenProperty, hResult);
        return hResult;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);
        DebugTraceResult(ABU_OpenProperty, hResult);
        return hResult;

    }

    if (ulFlags & MAPI_CREATE)
    {
        hResult = ResultFromScode(E_ACCESSDENIED);

        DebugTraceResult(ABU_OpenProperty, hResult);
        return hResult;
    }

    if (ulPropTag == PR_DETAILS_TABLE ||
        ulPropTag == PR_LISTBOX_TABLE ||
        ulPropTag == PR_DDLISTBOX_TABLE ||
        ulPropTag == PR_COMBOBOX_TABLE)
    {
        /* Check to see if they're expecting a table interface */

        if (memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
        {
            hResult = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
            return hResult;
        }
    }

    EnterCriticalSection(&lpABUser->cs);

    switch (ulPropTag)
    {
        case PR_DETAILS_TABLE:
        {

            /* Looking for the display table*/
            /* Create a display table */

            hResult = BuildDisplayTable(
                lpABUser->lpAllocBuff,
                lpABUser->lpAllocMore,
                lpABUser->lpFreeBuff,
                lpABUser->lpMalloc,
                lpABUser->hLibrary,
                sizeof(rgdtpage) / sizeof(DTPAGE),
                rgdtpage,
                0,
                (LPMAPITABLE *) lppUnk,
                NULL);

            break;
        }

        case PR_LISTBOX_TABLE:
        {

            /*
             *  Looking for the table that fills the listbox
             *  on the Options pane on this user's details
             */

            /*
             *  If we don't already have a Table Data Object, make one.
             */
            if (!lpABUser->lpTDatListBox)
            {
                hResult = HrBuildListBoxTable(lpABUser);
                if (HR_FAILED(hResult))
                    goto out;
            }

            Assert(lpABUser->lpTDatListBox);

            /* Get a view from the table data object*/
            hResult = lpABUser->lpTDatListBox->lpVtbl->HrGetView(
                        lpABUser->lpTDatListBox,
                        NULL,
                        NULL,
                        0,
                        (LPMAPITABLE *) lppUnk);

            break;
        }

        case PR_DDLISTBOX_TABLE:
        {
            /*
             *  Locking for the table that fills the drop down listbox
             *  on the Options pane on this user's details.
             */

            if (!lpABUser->lpTDatDDListBox)
            {
                hResult = HrBuildDDListBoxTable(lpABUser);
                if (HR_FAILED(hResult))
                    goto out;
            }

            Assert(lpABUser->lpTDatDDListBox);

            /* Get a view from the table data object */
            hResult = lpABUser->lpTDatDDListBox->lpVtbl->HrGetView(
                        lpABUser->lpTDatDDListBox,
                        NULL,
                        NULL,
                        0,
                        (LPMAPITABLE *) lppUnk);

            break;
        }

        case PR_COMBOBOX_TABLE:
        {
            /*
             *  Locking for the table that fills the combobox
             *  on the Options pane on this user's details.
             */

            if (!lpABUser->lpTDatComboBox)
            {
                hResult = HrBuildComboBoxTable(lpABUser);
                if (HR_FAILED(hResult))
                    goto out;
            }

            Assert(lpABUser->lpTDatComboBox);

            /* Get a view from the table data object */
            hResult =
                lpABUser->lpTDatComboBox->lpVtbl->HrGetView(
                lpABUser->lpTDatComboBox,
                NULL,
                NULL,
                0,
                (LPMAPITABLE *) lppUnk);

            break;
        }

        default:
        {
            hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

            break;
        }
    }

out:

    LeaveCriticalSection(&lpABUser->cs);

    DebugTraceResult(ABU_OpenProperty, hResult);
    return hResult;

}

/**********************************************************************
 *
 *  Private functions
 */

/*
 *  Default column set for listbox table
 */

enum {  ivallbxPR_DISPLAY_NAME_A = 0,
        ivallbxPR_COMMENT_A,
        ivallbxPR_ENTRYID,
        ivallbxPR_DISPLAY_TYPE,
        ivallbxPR_LISTBOX_VALUE,
        ivallbxPR_INSTANCE_KEY,
        cvallbxMax };

const SizedSPropTagArray(cvallbxMax, tagaColSetListBox) =
{
    cvallbxMax,
    {
        PR_DISPLAY_NAME_A,
        PR_COMMENT_A,
        PR_ENTRYID,
        PR_DISPLAY_TYPE,
        PR_LISTBOX_VALUE,
        PR_INSTANCE_KEY
    }
};

typedef struct _options_entryid
{
    BYTE abFlags[4];
    MAPIUID muid;
    ULONG ulVersion;
    ULONG ulType;
    ULONG ulRowNumber;
} OPTIONS_ENTRYID, *LPOPTIONS_ENTRYID;

#define CBOPTIONS_ENTRYID sizeof(OPTIONS_ENTRYID)

OPTIONS_ENTRYID OptionsEntryID =
{
    {0, 0, 0, 0},
    MUIDABSAMPLE,
    SAMP_VERSION,
    SAMP_UNKNOWN,
    0
};

HRESULT
HrBuildListBoxTable(LPABUSER lpABUser)
{
    SCODE sc;
    HRESULT hResult;
    SPropValue rgsPropValue[cvallbxMax];
    SRow sRow;
    char szDisplay[MAX_FMT_DET_DN+2]; /* +2 for digits */
    char szComment[MAX_FMT_DET_COMMENT+2]; /* +2 for digits */
    char szFmtDisplay[MAX_FMT_DET_DN];

char szFmtComment[MAX_FMT_DET_COMMENT];
    LPSTR pszFmtComment = (LPSTR) szFmtComment;
    LPSTR pszFmtDisplay = (LPSTR) szFmtDisplay;

    ULONG uliRow;

    /*
     *  Get string resources
     */
    sc = ScLoadString(  IDS_FMT_DET_DN,
                        MAX_FMT_DET_DN,
                        NULL,
                        lpABUser->hLibrary,
                        (LPSTR *) &pszFmtDisplay);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    sc = ScLoadString(  IDS_FMT_DET_COMMENT,
                        MAX_FMT_DET_COMMENT,
                        NULL,
                        lpABUser->hLibrary,
                        (LPSTR *) &pszFmtComment);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* Create a Table Data object */
    sc = CreateTable(
        (LPIID) &IID_IMAPITableData,
        lpABUser->lpAllocBuff,
        lpABUser->lpAllocMore,
        lpABUser->lpFreeBuff,
        lpABUser->lpMalloc,
        0,
        PR_DISPLAY_NAME_A,
        (LPSPropTagArray) &tagaColSetListBox,
        &(lpABUser->lpTDatListBox));

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* constants */
    sRow.cValues = cvallbxMax;
    sRow.lpProps = rgsPropValue;

    /*
     *  Although assignment of szDisplay and szComment happen here, their actual
     *  values happen below in the for loop.
     */
    rgsPropValue[ivallbxPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
    rgsPropValue[ivallbxPR_DISPLAY_NAME_A].Value.lpszA = szDisplay;

    rgsPropValue[ivallbxPR_COMMENT_A].ulPropTag = PR_COMMENT_A;
    rgsPropValue[ivallbxPR_COMMENT_A].Value.lpszA = szComment;

    /*
     *  For this release of MAPI the following two properties are required
     *  for all listboxes exposed in any details dialog.  This requirement is
     *  scheduled to be removed before ship
     */
    rgsPropValue[ivallbxPR_ENTRYID].ulPropTag = PR_ENTRYID;
    rgsPropValue[ivallbxPR_ENTRYID].Value.bin.lpb = (LPBYTE) &OptionsEntryID;
    rgsPropValue[ivallbxPR_ENTRYID].Value.bin.cb = CBOPTIONS_ENTRYID;

    rgsPropValue[ivallbxPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    rgsPropValue[ivallbxPR_DISPLAY_TYPE].Value.l = 0;  /*  There are no defines for this yet */

    rgsPropValue[ivallbxPR_LISTBOX_VALUE].ulPropTag = PR_LISTBOX_VALUE;

    rgsPropValue[ivallbxPR_INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
    rgsPropValue[ivallbxPR_INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);

    /* fill up the table */
    for (uliRow = 0; uliRow < MAX_LISTBOX_ROWS; uliRow++)
    {
        wsprintfA(szDisplay, szFmtDisplay, uliRow);
        wsprintfA(szComment, szFmtComment, uliRow);

        OptionsEntryID.ulRowNumber = uliRow;
        rgsPropValue[ivallbxPR_LISTBOX_VALUE].Value.l = uliRow;

        rgsPropValue[ivallbxPR_INSTANCE_KEY].Value.bin.lpb = (LPBYTE) &uliRow;

        hResult = lpABUser->lpTDatListBox->lpVtbl->HrModifyRow(
            lpABUser->lpTDatListBox,
            &sRow);
        if (HR_FAILED(hResult))
        {
            /*
             *  Mask errors here...
             *  We want to do this because it's probibly still a valid
             *  table data object that I can get views from.  Most likely
             *  just some of the rows will be missing...
             */
            hResult = hrSuccess;
            break;
        }
        /*
         *  get rid of any warnings
         */
        hResult = hrSuccess;
    }

out:

    DebugTraceResult(hrBuildListBoxTable, hResult);
    return hResult;
}

/*
 *  Default column set for the dropdown listbox table
 */

enum {  ivalddPR_DISPLAY_NAME_A = 0,
        ivalddPR_ENTRYID,
        ivalddPR_DISPLAY_TYPE,
        ivalddPR_DDLISTBOX_VALUE,
        ivalddPR_INSTANCE_KEY,
        cvalddMax };

const SizedSPropTagArray(cvalddMax, tagaColSetDDListBox) =
{
    cvalddMax,
    {
        PR_DISPLAY_NAME_A,
        PR_ENTRYID,
        PR_DISPLAY_TYPE,
        PR_DDLISTBOX_VALUE,
        PR_INSTANCE_KEY
    }
};

HRESULT
HrBuildDDListBoxTable(LPABUSER lpABUser)
{
    SCODE sc;
    HRESULT hResult;
    SPropValue rgsPropValue[cvalddMax];
    SRow sRow;
    char szFmtDisplay[MAX_FMT_DD_DN];
    char szDisplay[MAX_FMT_DD_DN+2]; /* +2 for digits */
    ULONG uliRow;
    LPSTR pszFmtDisplay = (LPSTR) szFmtDisplay;

    /*
     *  Get resource strings
     */
    sc = ScLoadString(  IDS_FMT_DD_DN,
                        MAX_FMT_DD_DN,
                        NULL,
                        lpABUser->hLibrary,
                        (LPSTR *)&pszFmtDisplay );
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* Create a Table Data object */
    sc = CreateTable(
        (LPIID) &IID_IMAPITableData,
        lpABUser->lpAllocBuff,
        lpABUser->lpAllocMore,
        lpABUser->lpFreeBuff,
        lpABUser->lpMalloc,
        0,
        PR_DISPLAY_NAME_A,
        (LPSPropTagArray) &tagaColSetDDListBox,
        &(lpABUser->lpTDatDDListBox));

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* constants */
    sRow.cValues = cvalddMax;
    sRow.lpProps = rgsPropValue;

    /*
     *  Although assignment of szDisplay happens here, its actual
     *  values happen below in the for loop.
     */
    rgsPropValue[ivalddPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
    rgsPropValue[ivalddPR_DISPLAY_NAME_A].Value.lpszA = szDisplay;

    /*
     *  For this release of MAPI the following two properties are required
     *  for all listboxes exposed in any details dialog.  This requirement is
     *  scheduled to be removed before ship
     */
    rgsPropValue[ivalddPR_ENTRYID].ulPropTag = PR_ENTRYID;
    rgsPropValue[ivalddPR_ENTRYID].Value.bin.lpb = (LPBYTE) &OptionsEntryID;
    rgsPropValue[ivalddPR_ENTRYID].Value.bin.cb = CBOPTIONS_ENTRYID;

    rgsPropValue[ivalddPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    rgsPropValue[ivalddPR_DISPLAY_TYPE].Value.l = 0;

    rgsPropValue[ivalddPR_DDLISTBOX_VALUE].ulPropTag = PR_DDLISTBOX_VALUE;

    rgsPropValue[ivalddPR_INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
    rgsPropValue[ivalddPR_INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);

    /* fill up the table */
    for (uliRow = 0; uliRow < 25; uliRow++)
    {
        wsprintfA(szDisplay, szFmtDisplay, uliRow);

        OptionsEntryID.ulRowNumber = uliRow;
        rgsPropValue[ivalddPR_DDLISTBOX_VALUE].Value.l = uliRow;

        rgsPropValue[ivalddPR_INSTANCE_KEY].Value.bin.lpb = (LPBYTE) &uliRow;

        hResult = lpABUser->lpTDatDDListBox->lpVtbl->HrModifyRow(
            lpABUser->lpTDatDDListBox,
            &sRow);
        if (HR_FAILED(hResult))
        {
            /*
             *  Mask errors here...
             *  We want to do this because it's probibly still a valid
             *  table data object that I can get views from.  Most likely
             *  just some of the rows will be missing...
             */

            hResult = hrSuccess;
            break;
        }
        /*
         *  get rid of any warnings
         */
        hResult = hrSuccess;
    }

out:

    DebugTraceResult(HrBuildDDListBoxTable, hResult);
    return hResult;
}

/*
 *  Default column set for the combobox table
 */
enum { ivalcbPR_DISPLAY_NAME_A = 0,
        ivalcbPR_ENTRYID,
        ivalcbPR_DISPLAY_TYPE,
        ivalcbPR_COMBOBOX_VALUE,
        ivalcbPR_INSTANCE_KEY,
        cvalcbMax };

const SizedSPropTagArray(cvalcbMax, tagaColSetComboBox) =
{
    cvalcbMax,
    {
        PR_DISPLAY_NAME_A,
        PR_ENTRYID,
        PR_DISPLAY_TYPE,
        PR_COMBOBOX_VALUE,
        PR_INSTANCE_KEY
    }
};

HRESULT
HrBuildComboBoxTable(LPABUSER lpABUser)
{
    SCODE sc;
    HRESULT hResult;
    SPropValue rgsPropValue[cvalcbMax];
    SRow sRow;
    char szFmtDisplay[MAX_FMT_COMBO_DN];
    char szDisplay[MAX_FMT_COMBO_DN + 2]; /* +2 for digits */
    ULONG uliRow;
    LPSTR pszFmtDisplay = (LPSTR) szFmtDisplay;

    /*
     *  Get resource strings
     */
    sc = ScLoadString(  IDS_FMT_COMBO_DN,
                        MAX_FMT_COMBO_DN,
                        NULL,
                        lpABUser->hLibrary,
                        (LPSTR *) &pszFmtDisplay );
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* Create a table data object */
    sc = CreateTable(
        (LPIID) &IID_IMAPITableData,
        lpABUser->lpAllocBuff,
        lpABUser->lpAllocMore,
        lpABUser->lpFreeBuff,
        lpABUser->lpMalloc,
        0,
        PR_DISPLAY_NAME_A,
        (LPSPropTagArray) &tagaColSetComboBox,
        &(lpABUser->lpTDatComboBox));

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    /* constants */
    sRow.cValues = cvalcbMax;
    sRow.lpProps = rgsPropValue;

    /*
     *  Although assignment of szDisplay happens here, its actual
     *  values happen below in the for loop.
     */
    rgsPropValue[ivalcbPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
    rgsPropValue[ivalcbPR_DISPLAY_NAME_A].Value.lpszA = szDisplay;

    /*
     *  For this release of MAPI the following two properties are required
     *  for all listboxes exposed in any details dialog.  This requirement is
     *  scheduled to be removed before ship
     */
    rgsPropValue[ivalcbPR_ENTRYID].ulPropTag = PR_ENTRYID;
    rgsPropValue[ivalcbPR_ENTRYID].Value.bin.lpb = (LPBYTE) &OptionsEntryID;
    rgsPropValue[ivalcbPR_ENTRYID].Value.bin.cb = CBOPTIONS_ENTRYID;

    rgsPropValue[ivalcbPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    rgsPropValue[ivalcbPR_DISPLAY_TYPE].Value.l = 0;

    rgsPropValue[ivalcbPR_COMBOBOX_VALUE].ulPropTag = PR_COMBOBOX_VALUE;
    rgsPropValue[ivalcbPR_COMBOBOX_VALUE].Value.lpszA = szDisplay;

    rgsPropValue[ivalcbPR_INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
    rgsPropValue[ivalcbPR_INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);

    /* fill up the table */
    for (uliRow = 0; uliRow < 25; uliRow++)
    {
        wsprintfA(szDisplay, szFmtDisplay, uliRow);

        OptionsEntryID.ulRowNumber = uliRow;

        rgsPropValue[ivalcbPR_INSTANCE_KEY].Value.bin.lpb = (LPBYTE) &uliRow;

        hResult = lpABUser->lpTDatComboBox->lpVtbl->HrModifyRow(
            lpABUser->lpTDatComboBox,
            &sRow);
        if (HR_FAILED(hResult))
        {
            /*
             *  Mask errors here...
             *  We want to do this because it's probibly still a valid
             *  table data object that I can get views from.  Most likely
             *  just some of the rows will be missing...
             */

            hResult = hrSuccess;
            break;
        }

        /*
         *  get rid of any warnings
         */
        hResult = hrSuccess;
    }

out:
    DebugTraceResult(HrBuildComboBoxTable, hResult);
    return hResult;
}


OOTID.C

/***********************************************************************
 *
 *  OOTID.C
 *
 *  Sample Address Book OneOff Template ID object
 *  This file contains the code for implementing the Sample AB
 *  template ID for it's one-off.
 *
 *  The template ID for the Sample Address Book one-offs has only one
 *  purpose.  When the SaveChanges() method gets called it recalculates
 *  PR_EMAIL_ADDRESS_A and PR_SEARCH_KEY from data that was changed by the
 *  user.  See how this interacts with the one-off user object implemented
 *  in OOUSER.C.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"

/*
 *  Declaration of IMailUser object implementation
 */
#undef  INTERFACE
#define INTERFACE   struct _OOTID

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, OOTID_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, OOTID_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(OOTID_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
};

/*
 *  The structure behind the 'this' pointer
 */
typedef struct _OOTID {

    const OOTID_Vtbl * lpVtbl;

    SAB_Wrapped;

} OOTID, *LPOOTID;

/*
 *  OOTID vtbl is filled in here.
 */

static const OOTID_Vtbl vtblOOTID =
{
    (OOTID_QueryInterface_METHOD *)     ABU_QueryInterface,
    (OOTID_AddRef_METHOD *)             WRAP_AddRef,
    (OOTID_Release_METHOD *)            WRAP_Release,
    (OOTID_GetLastError_METHOD *)       WRAP_GetLastError,
    OOTID_SaveChanges,
    (OOTID_GetProps_METHOD *)           WRAP_GetProps,
    (OOTID_GetPropList_METHOD *)        WRAP_GetPropList,
    (OOTID_OpenProperty_METHOD *)       WRAP_OpenProperty,
    (OOTID_SetProps_METHOD *)           WRAP_SetProps,
    (OOTID_DeleteProps_METHOD *)        WRAP_DeleteProps,
    (OOTID_CopyTo_METHOD *)             WRAP_CopyTo,
    (OOTID_CopyProps_METHOD *)          WRAP_CopyProps,
    (OOTID_GetNamesFromIDs_METHOD *)    WRAP_GetNamesFromIDs,
    (OOTID_GetIDsFromNames_METHOD *)    WRAP_GetIDsFromNames,
};

/*************************************************************************
 *
 -  NewOOTID
 -
 *  Creates the OOTID object associated with a mail user.
 *
 *
 */

enum {
    isptOOTIDFillPR_ADDRTYPE_A = 0,
    isptOOTIDFillPR_TEMPLATEID,
    isptOOTIDFillPR_DISPLAY_TYPE,
    cmaxOOTIDFill
};

HRESULT
HrNewOOTID( LPMAPIPROP *        lppMAPIPropNew,
            ULONG               cbTemplateId,
            LPENTRYID           lpTemplateId,
            ULONG               ulTemplateFlags,
            LPMAPIPROP          lpPropData,
            LPABLOGON           lpABPLogon,
            LPCIID              lpInterface,
            HINSTANCE           hLibrary,
            LPALLOCATEBUFFER    lpAllocBuff,
            LPALLOCATEMORE      lpAllocMore,
            LPFREEBUFFER        lpFreeBuff,
            LPMALLOC            lpMalloc )
{
    LPOOTID lpOOTID = NULL;
    SCODE sc;
    HRESULT hResult = hrSuccess;

    /*
     *  Allocate space for the OOTID structure
     */
    sc = lpAllocBuff( sizeof(OOTID), (LPVOID *) &lpOOTID );
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    /*
     *  Initialize the OOTID structure
     */

    lpOOTID->lpVtbl = &vtblOOTID;
    lpOOTID->lcInit = 1;
    lpOOTID->hResult = hrSuccess;
    lpOOTID->idsLastError = 0;
    lpOOTID->hLibrary = hLibrary;
    lpOOTID->lpAllocBuff = lpAllocBuff;
    lpOOTID->lpAllocMore = lpAllocMore;
    lpOOTID->lpFreeBuff = lpFreeBuff;
    lpOOTID->lpMalloc = lpMalloc;
    lpOOTID->lpABLogon = lpABPLogon;
    lpOOTID->lpPropData = lpPropData;

    /*
     *  Fill in the wrapped object if we're asked to.
     */
    if (ulTemplateFlags & FILL_ENTRY)
    {
        SPropValue spv[cmaxOOTIDFill];

        spv[isptOOTIDFillPR_ADDRTYPE_A].ulPropTag = PR_ADDRTYPE_A;
        spv[isptOOTIDFillPR_ADDRTYPE_A].Value.lpszA = lpszEMT;

        spv[isptOOTIDFillPR_TEMPLATEID].ulPropTag = PR_TEMPLATEID;
        spv[isptOOTIDFillPR_TEMPLATEID].Value.bin.lpb = (LPBYTE) lpTemplateId;
        spv[isptOOTIDFillPR_TEMPLATEID].Value.bin.cb = cbTemplateId;

        spv[isptOOTIDFillPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
        spv[isptOOTIDFillPR_DISPLAY_TYPE].Value.l = DT_MAILUSER;

        hResult = lpPropData->lpVtbl->SetProps(
            lpPropData,
            cmaxOOTIDFill,
            spv,
            NULL);

        if (HR_FAILED(hResult))
        {
            goto err;
        }

    }

    /*
     *  AddRef lpPropData so we can use it after we return
     */

    (void)lpPropData->lpVtbl->AddRef(lpPropData);

    InitializeCriticalSection(&lpOOTID->cs);

    /*  We must AddRef the lpABPLogon object since we will be using it
     */
    lpABPLogon->lpVtbl->AddRef(lpABPLogon);

    *lppMAPIPropNew = (LPVOID) lpOOTID;

out:

    DebugTraceResult(HrNewOOTID, hResult);
    return hResult;

err:

    lpFreeBuff(lpOOTID);
    goto out;

}

/*
 *  These properties are used and set by the one off dialog, and are
 *  combined to make up a valid email address.
 */

enum {
    isptcontpropPR_SERVER_NAME = 0,
    isptcontpropPR_SHARE_NAME,
    isptcontpropPR_PATH_NAME,
    cmaxcontprop
};

static const SizedSPropTagArray(cmaxcontprop, pta) =
{
    cmaxcontprop,
    {
        PR_SERVER_NAME,
        PR_SHARE_NAME,
        PR_PATH_NAME,
    }
};

/*
 *  These properties are computed by this function and saved back into the underlying
 *  property storage.
 */
enum {
    isptcomppropsPR_EMAIL_ADDRESS_A = 0,
    isptcomppropsPR_SEARCH_KEY,
    cmaxcompprops
};

/*
 -  OOTID_SaveChanges
 -
 *  All this method does is build the PR_EMAIL_ADDRESS_A and PR_SEARCH_KEY from PR_SERVER_NAME,
 *  PR_SHARE_NAME, and PR_PATH_NAME.
 */

STDMETHODIMP
OOTID_SaveChanges(LPOOTID lpOOTID, ULONG ulFlags)
{
    HRESULT hResult;
    /*
     *  szEMA can be of the format:
     *
     *      \\SERVER_NAME\SHARE_NAME[\PATH]'\0'
     */
    CHAR szEMA[ MAX_SERVER_NAME + 2 + MAX_SHARE_NAME + 1 + MAX_PATH + 2 ];
    LPSPropValue lpspv = NULL;
    SPropValue rgspv[cmaxcompprops];
    ULONG ulcValues;
    ULONG cbT = 0;
    LPBYTE lpbT = NULL;
    SCODE sc;

    /*
     *  Check to see if it is big enough to be my object
     */
    if (IsBadReadPtr(lpOOTID, sizeof(OOTID)))
    {
        /*
         *  Not big enough
         */
        return MakeResult(E_INVALIDARG);
    }

    /*
     *  Check to see that it's OOTIDs vtbl
     */
    if (lpOOTID->lpVtbl != &vtblOOTID)
    {
        /*
         *  vtbl not ours
         */
        return MakeResult(E_INVALIDARG);
    }

    /*
     *  Get the properties that make up the email address from the
     *  mapiprop object
     */
    hResult = lpOOTID->lpPropData->lpVtbl->GetProps(
        lpOOTID->lpPropData,
        (LPSPropTagArray) &pta,
        0,      /* ansi */
        &ulcValues,
        &lpspv);

    if (HR_FAILED(hResult))
    {
        goto out;
    }

    /*
     *  Must have at least PR_SERVER_NAME and PR_SHARE_NAME to make a valid
     *  email address
     */
    if (lpspv[isptcontpropPR_SERVER_NAME].ulPropTag != PR_SERVER_NAME
        || lpspv[isptcontpropPR_SHARE_NAME].ulPropTag != PR_SHARE_NAME)
    {
        /*
         *  Without at least these two properties I cannot recalculate
         *  anything.  So, just exit cleanly without changing anything.
         */
        hResult = hrSuccess; /* to mask any warnings from above */
        goto out;
    }

    /* create the email address */
    wsprintfA(szEMA, "\\\\%s\\%s",
        lpspv[isptcontpropPR_SERVER_NAME].Value.lpszA, lpspv[isptcontpropPR_SHARE_NAME].Value.lpszA);

    /*  Did we also get a path??  If so append it on */
    if (lpspv[isptcontpropPR_PATH_NAME].ulPropTag == PR_PATH_NAME)
    {
        /*  But only if there's a value that make sense */
        if (*lpspv[isptcontpropPR_PATH_NAME].Value.lpszA)   /* i.e. !'\0' */
            wsprintfA(szEMA, "%s\\%s", szEMA, lpspv[isptcontpropPR_PATH_NAME].Value.lpszA);
    }

    /* initialize the email address prop value */
    rgspv[isptcomppropsPR_EMAIL_ADDRESS_A].ulPropTag = PR_EMAIL_ADDRESS_A;
    rgspv[isptcomppropsPR_EMAIL_ADDRESS_A].Value.lpszA = szEMA;

    /*
     *  Generate the PR_SEARCH_KEY
     */
    /*  Search keys for mailable recipients that have email addresses are
     *  defined as "EmailType':'EmailAddress\0".  We do the +2 for the ':' and
     *  '\0'.
     */
    cbT = lstrlenA(szEMA) + lstrlenA(lpszEMT) + 2;

    sc = lpOOTID->lpAllocBuff( cbT, (LPVOID *) &lpbT );
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }
    lstrcpyA((LPSTR) lpbT, lpszEMT);
    lstrcatA((LPSTR) lpbT, ":");
    lstrcatA((LPSTR) lpbT, szEMA);
    CharUpperBuffA((LPSTR) lpbT, (UINT) cbT);

    rgspv[isptcomppropsPR_SEARCH_KEY].ulPropTag = PR_SEARCH_KEY;
    rgspv[isptcomppropsPR_SEARCH_KEY].Value.bin.cb = cbT;
    rgspv[isptcomppropsPR_SEARCH_KEY].Value.bin.lpb = lpbT;

    /*
     *  set the email address and search key properties
     */
    hResult = lpOOTID->lpPropData->lpVtbl->SetProps(
        lpOOTID->lpPropData,
        cmaxcompprops,
        rgspv,
        NULL);

    lpOOTID->lpFreeBuff(lpbT);

    if (HR_FAILED(hResult))
    {
        goto out;
    }

out:
    //
    //  If I'm leaving this routine and everything up to this point has been successful,
    //  then pass on the SaveChanges to the underlying property storage.
    //
    if (!HR_FAILED(hResult))
    {
        hResult = lpOOTID->lpPropData->lpVtbl->SaveChanges(
            lpOOTID->lpPropData,
            ulFlags);
    }

    /* free the buffer */
    lpOOTID->lpFreeBuff(lpspv);

    DebugTraceResult(OOTID_SaveChanges, hResult);
    return hResult;
}


OOUSER.C

/***********************************************************************
 *
 *  OOUSER.C
 *
 *  Sample AB One Off Mail User object
 *  This file contains the code for implementing the Sample AB
 *  One Off Mail user.
 *
 *  This is the object that is opened in response to the entryID that is
 *  supplied by this provider in the one-off table (see GetOneOffTable() in
 *  ABP.C).  All this object does is supply a display table and any default
 *  values for properties that might need to be there.  One important property
 *  that this object exports is it's PR_TEMPLATEID (see how this object and
 *  the implementation in OOTID.C work together).
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"

/*
 *  Display table control structures for the OneOff property sheet.
 */

/*
 *  This is the edit control on the one-off pane.
 */
static DTBLEDIT editOOUserDisplayName =
{
    sizeof(DTBLEDIT),
    0,
    MAX_DISPLAY_NAME,
    PR_DISPLAY_NAME_A
};

/*
 *  This edit control is for the server name edit box.
 */
static DTBLEDIT editOOUserServer =
{
    sizeof(DTBLEDIT),
    0,
    MAX_SERVER_NAME,
    PR_SERVER_NAME
};

/*
 *  This is the edit control associated with the Share field.
 */
static DTBLEDIT editOOUserShare =
{
    sizeof(DTBLEDIT),
    0,
    MAX_SHARE_NAME,
    PR_SHARE_NAME
};

/*
 *  This is the edit control associated with the path.
 */
static DTBLEDIT editOOUserPath =
{
    sizeof(DTBLEDIT),
    0,
    MAX_PATH,
    PR_PATH_NAME
};

/*
 *  General Property Page definition
 */
static DTCTL rgdtctlOOUserGeneral[] =
{
    /* general property page */
    /*
     *  Although this control is not absolutely required,
     *  we suggest filling it in.  This non-requirement
     *  will probibly change before we actually ship!
     */
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},

    /* display name static control and edit control */
    /*
     *  Note that this edit control is editable and required
     */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE|DT_REQUIRED, NULL, 0, szNoFilter,
        IDC_OOUSER_DISPLAY_NAME, &editOOUserDisplayName},

    /* group box */
    {DTCT_GROUPBOX, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtblgroupbox},
    /*
     *  The following three label/edit pairs make up the
     *  calculated email address (see OOTID.C's SaveChanges() method).
     */
    /* server static control and edit control */

    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE | DT_REQUIRED, NULL, 0, szFileNameFilter,
        IDC_OOUSER_SERVER, &editOOUserServer},

    /* share static control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE | DT_REQUIRED, NULL, 0, szFileNameFilter,
        IDC_OOUSER_SHARE, &editOOUserShare},

    /* path static control and edit control */
    {DTCT_LABEL, 0, NULL, 0, NULL,
        IDC_STATIC_CONTROL, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter,
        IDC_OOUSER_PATH, &editOOUserPath},
};

/*
 *  The OneOff property sheet definition.  This is an array of
 *  property sheet pages that gets passed to BuildDisplayTable().
 */
static DTPAGE rgdtpage[] =
{
    {
        sizeof(rgdtctlOOUserGeneral) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(OOUserGeneralPage),
        "",
        rgdtctlOOUserGeneral
    },
};

/*
 *  Declaration of IMailUser object implementation
 */
#undef  INTERFACE
#define INTERFACE   struct _ABOOUSER

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABOOUSER_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAILUSER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABOOUSER_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAILUSER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABOOUSER_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAILUSER_METHODS(IMPL)
};

/*
 *  Structure behind the 'this'
 */

typedef struct _ABOOUSER
{
    const ABOOUSER_Vtbl FAR * lpVtbl;

    SAB_Wrapped;

} ABOOUSER, *LPABOOUSER;

#define ABOOUSER_ValidateObject(Method, pThis)      \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ABOOUSER)))      \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABOOUSER##_##Method, hResult); \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblABOOUSER)             \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABOOUSER##_##Method, hResult); \
        return hResult;                             \
    }                                               \
}

/*
 *  ABOOUser vtbl is set up here.
 */
static const ABOOUSER_Vtbl vtblABOOUSER =
{
    (ABOOUSER_QueryInterface_METHOD *)  ABU_QueryInterface,
    (ABOOUSER_AddRef_METHOD *)          WRAP_AddRef,
    (ABOOUSER_Release_METHOD *)         WRAP_Release,
    (ABOOUSER_GetLastError_METHOD *)    WRAP_GetLastError,
    (ABOOUSER_SaveChanges_METHOD *)     WRAP_SaveChanges,
    (ABOOUSER_GetProps_METHOD *)        WRAP_GetProps,
    (ABOOUSER_GetPropList_METHOD *)     WRAP_GetPropList,
    ABOOUSER_OpenProperty,
    (ABOOUSER_SetProps_METHOD *)        WRAP_SetProps,
    (ABOOUSER_DeleteProps_METHOD *)     WRAP_DeleteProps,
    (ABOOUSER_CopyTo_METHOD *)          WRAP_CopyTo,
    (ABOOUSER_CopyProps_METHOD *)       WRAP_CopyProps,
    (ABOOUSER_GetNamesFromIDs_METHOD *) WRAP_GetNamesFromIDs,
    (ABOOUSER_GetIDsFromNames_METHOD *) WRAP_GetIDsFromNames,
};

enum {  ivaloousrPR_DISPLAY_TYPE = 0,
        ivaloousrPR_ADDRTYPE_A,
        ivaloousrPR_TEMPLATEID,
        cvaloousrMax };

SizedSPropTagArray( cvaloousrMax, tagaABOOUSERAccess) =
{
      cvaloousrMax
    , { PR_DISPLAY_TYPE
      , PR_ADDRTYPE_A
      , PR_TEMPLATEID
      }
};

ULONG   rgulABOOUSERAccess[cvaloousrMax] =
{
      IPROP_READONLY | IPROP_CLEAN  /* PR_DISPLAY_TYPE */
    , IPROP_READONLY | IPROP_CLEAN  /* PR_ADDRTYPE_A */
    , IPROP_READONLY | IPROP_CLEAN  /* PR_TEMPLATEID */
};

/*************************************************************************
 *
 -  NewSampOOUser
 -
 *  Creates the IMailUser associated with a one off mail user.
 *
 *
 */
HRESULT
HrNewSampOOUser(LPMAILUSER *        lppMAPIPropEntry,
                ULONG *             lpulObjType,
                ULONG               cbEntryID,
                LPENTRYID           lpEntryID,
                LPABLOGON           lpABPLogon,
                LPCIID              lpInterface,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc )
{
    LPABOOUSER lpABOOUser = NULL;
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[4];

    /*  Do I support this interface?? */
    if (lpInterface)
    {
        if (memcmp(lpInterface, &IID_IMailUser, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIProp, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IUnknown, sizeof(IID)))
        {
            DebugTraceSc(HrNewSampOOUser, MAPI_E_INTERFACE_NOT_SUPPORTED);
            return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        }
    }

    /*
     *  Allocate space for the ABOOUser structure
     */
    sc = lpAllocBuff(sizeof(ABOOUSER), (LPVOID *) &lpABOOUser);

    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    lpABOOUser->lpVtbl = &vtblABOOUSER;
    lpABOOUser->lcInit = 1;
    lpABOOUser->hResult = hrSuccess;
    lpABOOUser->idsLastError = 0;
    lpABOOUser->hLibrary = hLibrary;
    lpABOOUser->lpAllocBuff = lpAllocBuff;
    lpABOOUser->lpAllocMore = lpAllocMore;
    lpABOOUser->lpFreeBuff = lpFreeBuff;
    lpABOOUser->lpMalloc = lpMalloc;
    lpABOOUser->lpABLogon = lpABPLogon;

    /*
     *  Create property storage object.
     */

    sc = CreateIProp(
        (LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    /*
     *  Set up the initial set of properties
     */

    spv[0].ulPropTag = PR_DISPLAY_TYPE;
    spv[0].Value.l = DT_MAILUSER;

    spv[1].ulPropTag = PR_OBJECT_TYPE;
    spv[1].Value.l = MAPI_MAILUSER;

    /*
     *  Note that we're using our entryID for our templateID.
     *  This is a really simple way to implement templateIDs.
     *  (See OOTID.C)
     */
    spv[2].ulPropTag = PR_TEMPLATEID;
    spv[2].Value.bin.cb = sizeof(OOUSR_ENTRYID);
    spv[2].Value.bin.lpb = (LPBYTE) lpEntryID;

    spv[3].ulPropTag = PR_ADDRTYPE;
    spv[3].Value.lpszA = lpszEMT;

    /*
     *   Set the default properties
     */
    hr = lpPropData->lpVtbl->SetProps(
        lpPropData,
        sizeof(spv) / sizeof(SPropValue),
        spv,
        NULL);

    if (HR_FAILED(hr))
    {
        goto err;
    }

    /*
    *   We mark these properties READONLY so that they can't be changed by
    *   the client.
    */
    (void) lpPropData->lpVtbl->HrSetPropAccess(lpPropData,
                                (LPSPropTagArray) &tagaABOOUSERAccess,
                                rgulABOOUSERAccess);

    (void) lpPropData->lpVtbl->HrSetObjAccess(lpPropData, IPROP_READWRITE);

    lpABOOUser->lpPropData = (LPMAPIPROP) lpPropData;

    InitializeCriticalSection(&lpABOOUser->cs);

    /*  We must AddRef the lpABPLogon object since we will be using it
     */
    lpABPLogon->lpVtbl->AddRef(lpABPLogon);

    *lppMAPIPropEntry = (LPVOID) lpABOOUser;
    *lpulObjType = MAPI_MAILUSER;

out:

    DebugTraceResult(HrNewSampOOUser, hr);
    return hr;

err:

    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    lpFreeBuff( lpABOOUser );

    goto out;
}

/*************************************************************************
 *
 -  ABOOUSER_OpenProperty
 -
 *
 *  This is how we get the display table associated with this one-off's
 *  details screen.
 */
STDMETHODIMP
ABOOUSER_OpenProperty(
    LPABOOUSER lpABOOUser,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{

    /*
     *  Since the default is that we do not support opening this
     *  object, we can initialize our hResult here.
     */
    HRESULT hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    ABOOUSER_ValidateObject(OpenProperty, lpABOOUser);

    Validate_IMAPIProp_OpenProperty(lpABOOUser, ulPropTag, lpiid,
                            ulInterfaceOptions, ulFlags, lppUnk);

    if (ulInterfaceOptions & ~MAPI_UNICODE )
    {
        /*
         *  Only UNICODE flag should be set for any of the objects that might
         *  be returned from this object.
         */
        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        goto ret;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto ret;
    }

    if (ulFlags & MAPI_CREATE)
    {
        /*
         *  Don't support creating any sub-objects
         */
        hResult = ResultFromScode(E_ACCESSDENIED);
        goto ret;
    }

    /*  Check to see if I need a display table*/

    if (ulPropTag == PR_DETAILS_TABLE)
    {
        /* Looking for the display table */
        /* Check to see if they're expecting a table interface*/

        if (memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
        {
            /*
             *  This provider will only export a table interface for
             *  PR_DETAILS_TABLE
             */
            hResult = ResultFromScode(E_NOINTERFACE);
            goto ret;
        }

        /* Create a display table */

        hResult = BuildDisplayTable(
            lpABOOUser->lpAllocBuff,
            lpABOOUser->lpAllocMore,
            lpABOOUser->lpFreeBuff,
            lpABOOUser->lpMalloc,
            lpABOOUser->hLibrary,
            sizeof(rgdtpage) / sizeof(DTPAGE),
            rgdtpage,
            0,
            (LPMAPITABLE *) lppUnk,
            NULL);

        if (HR_FAILED(hResult))
        {
            goto ret;
        }

        /*
         * We're succeeding. Ensure our hResult is set properly (i.e. mask warnings)
         */

        hResult = hrSuccess;
    }

ret:
    DebugTraceResult(ABOOUSER_OpenProperty, hResult);
    return hResult;
}


ABCONT.C

/***********************************************************************
 *
 *  ABCONT.C
 *
 *  Sample AB directory container object
 *
 *  This file contains the code for implementing the Sample AB
 *  directory container object.
 *
 *  This directory container was retrieved by OpenEntry on the entryid
 *  retrieved from the single row of the hierarchy table (IVTROOT in root.c).
 *
 *  The following routines are implemented in this file:
 *
 *      HrNewSampDirectory
 *      ABC_Release
 *      ABC_SaveChanges
 *      ABC_OpenProperty
 *      ABC_GetContentsTable
 *      ABC_GetHierarchyTable
 *
 *      HrGetDetailsDialog
 *
 *      HrNewABCButton
 *      ABCBUTT_QueryInterface
 *      ABCBUTT_Release
 *      ABCBUTT_Activate
 *      ABCBUTT_GetState
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"
#include <smpab.h>

/*
 *  Proptags used only in this module
 */
#define PR_RADIO_BUTTON_VALUE       PROP_TAG(PT_LONG,   0x6601)
#define PR_BUTTON_PRESS             PROP_TAG(PT_OBJECT, 0x6603)
#define PR_SAB_FILE_TEMP            PROP_TAG(PT_TSTRING,0x6605)

/*
 *  Possible values for PR_RADIO_BUTTON_VALUE
 */
#define RADIO_BUTTON_1_RETURN_VALUE 1
#define RADIO_BUTTON_2_RETURN_VALUE 2
#define RADIO_BUTTON_3_RETURN_VALUE 3
#define RADIO_BUTTON_4_RETURN_VALUE 4

/*
 *  Structure for the 'this'
 */

typedef struct _ABCNT
{
    const ABC_Vtbl FAR * lpVtbl;

    SAB_Wrapped;

    /* details display table */
    LPTABLEDATA lpTDatDetails;

} ABCNT, *LPABCNT;

/*
 *  This is the data structure passed to the
 *  HrNotify method of the Table Data Object
 *  in ABCBUTT_Activate that uniquely identifies
 *  the control in the display table that needs
 *  to be updated.
 */
typedef struct
{
    MAPIUID muid;
    ULONG ulIdc;
} NOTIFDATA;

/*
 *  The actual data.  Note that the control
 *  is specified by IDC_DIR_SAB_FILE_NAME.
 */
NOTIFDATA notifdata =
{MUIDABSAMPLE, IDC_DIR_SAB_FILE_NAME};

/* Display table control structures for the Directory property sheet. */

/*
 *  Edit control that displays the current .SAB file.
 */
DTBLEDIT editDirFileName =
{
    sizeof(DTBLEDIT),
    0,
    MAX_PATH,
    PR_SAB_FILE_TEMP
};

/*
 *  Button control for changing the current .SAB file.
 */
DTBLBUTTON buttonDirChange =
{
    sizeof(DTBLBUTTON),
    0,
    PR_BUTTON_PRESS
};

/*
 *  The next four controls are radio buttons.  Note how they
 *  are related by their use of PR_RADIO_BUTTON_VALUE and
 *  differentiated by RADIO_BUTTON_x_RETURN_VALUE.  Also note
 *  the '4' which says how many controls are in this group
 *  of radio buttons.
 */
DTBLRADIOBUTTON radiobuttonDir1 =
{
    sizeof(DTBLRADIOBUTTON),
    0,
    4,
    PR_RADIO_BUTTON_VALUE,
    RADIO_BUTTON_1_RETURN_VALUE
};

DTBLRADIOBUTTON radiobuttonDir2 =
{
    sizeof(DTBLRADIOBUTTON),
    0,
    4,
    PR_RADIO_BUTTON_VALUE,
    RADIO_BUTTON_2_RETURN_VALUE
};

DTBLRADIOBUTTON radiobuttonDir3 =
{
    sizeof(DTBLRADIOBUTTON),
    0,
    4,
    PR_RADIO_BUTTON_VALUE,
    RADIO_BUTTON_3_RETURN_VALUE
};

DTBLRADIOBUTTON radiobuttonDir4 =
{
    sizeof(DTBLRADIOBUTTON),
    0,
    4,
    PR_RADIO_BUTTON_VALUE,
    RADIO_BUTTON_4_RETURN_VALUE
};

/*
 *  This is the page for the directory details.
 */
DTCTL rgdtctlDirGeneral[] =
{
    /*
     *  directory general propery page
     *  This names the pane for this page.
     *  Although not currently not required in
     *  this version, we expect to require it by
     *  the time MAPI 1.0 ships.
     */
    {DTCT_PAGE, 0, NULL, 0, NULL, 0,
        &dtblpage},

    /* controls and edit control containing sab file name */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_EDIT, 0, (LPBYTE) &notifdata, sizeof(NOTIFDATA),
        szNoFilter, IDC_DIR_SAB_FILE_NAME, &editDirFileName},

    /* control and push button for changing sab file */
    {DTCT_LABEL, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtbllabel},
    {DTCT_BUTTON, 0, NULL, 0, NULL, IDC_DIR_CHANGE,
        &buttonDirChange},

    /* radio button group box and radio buttons */
    {DTCT_GROUPBOX, 0, NULL, 0, NULL, IDC_STATIC_CONTROL,
        &dtblgroupbox},
    {DTCT_RADIOBUTTON, DT_EDITABLE, NULL, 0, NULL, IDC_DIR_RADIO_1,
        &radiobuttonDir1},
    {DTCT_RADIOBUTTON, DT_EDITABLE, NULL, 0, NULL, IDC_DIR_RADIO_2,
        &radiobuttonDir2},
    {DTCT_RADIOBUTTON, DT_EDITABLE, NULL, 0, NULL, IDC_DIR_RADIO_3,
        &radiobuttonDir3},
    {DTCT_RADIOBUTTON, DT_EDITABLE, NULL, 0, NULL, IDC_DIR_RADIO_4,
        &radiobuttonDir4},
};

/*
 * Display table pages for Directory Details
 */
DTPAGE rgdtpageDir[] =
{
    {
        sizeof(rgdtctlDirGeneral) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(DirGeneralPage),
        "",
        rgdtctlDirGeneral
    }
};

/*
 *  ABCont vtbl is filled in here.
 */
ABC_Vtbl vtblABC =
{
    (ABC_QueryInterface_METHOD *)       ROOT_QueryInterface,
    (ABC_AddRef_METHOD *)               ROOT_AddRef,
    ABC_Release,
    (ABC_GetLastError_METHOD *)         ROOT_GetLastError,
    ABC_SaveChanges,
    (ABC_GetProps_METHOD *)             WRAP_GetProps,
    (ABC_GetPropList_METHOD *)          WRAP_GetPropList,
    ABC_OpenProperty,
    (ABC_SetProps_METHOD *)             WRAP_SetProps,
    (ABC_DeleteProps_METHOD *)          WRAP_DeleteProps,
    (ABC_CopyTo_METHOD *)               WRAP_CopyTo,
    (ABC_CopyProps_METHOD *)            WRAP_CopyProps,
    (ABC_GetNamesFromIDs_METHOD *)      WRAP_GetNamesFromIDs,
    (ABC_GetIDsFromNames_METHOD *)      WRAP_GetIDsFromNames,
    ABC_GetContentsTable,
    ABC_GetHierarchyTable,
    (ABC_OpenEntry_METHOD *)            ROOT_OpenEntry,
    (ABC_SetSearchCriteria_METHOD *)    ROOT_SetSearchCriteria,
    (ABC_GetSearchCriteria_METHOD *)    ROOT_GetSearchCriteria,
    (ABC_CreateEntry_METHOD *)          ROOT_CreateEntry,
    (ABC_CopyEntries_METHOD *)          ROOT_CopyEntries,
    (ABC_DeleteEntries_METHOD *)        ROOT_DeleteEntries,
    (ABC_ResolveNames_METHOD *)         ROOT_ResolveNames
};

/*
 *  Private functions
 */
HRESULT HrNewABCButton( LPABCNT lpABC,
                        ULONG ulInterfaceOptions,
                        ULONG ulFlags,
                        LPMAPICONTROL FAR * lppMAPICont);

//HRESULT HrGetSearchDialog(LPABCNT lpABC, LPMAPITABLE * lppSearchTable);
HRESULT HrGetDetailsDialog(LPABCNT lpABC, LPMAPITABLE * lppDetailsTable);

/*
 -  NewSampDirectory
 -
 *  Creates a Directory container object.
 *
 *
 */

/*
 *  properties of which I want to track whether or not they've
 *  been changed.
 */
enum { ivalWatchPR_SAB_FILE_TEMP = 0,
        cvalWatchMax };

SizedSPropTagArray(cvalWatchMax, tagaPropsToWatch) =
{
    cvalWatchMax,
    {
        PR_SAB_FILE_TEMP
    }
};

/*
 *  Properties that are initially set on this object
 */
enum {  ivalabcPR_DISPLAY_TYPE = 0,
        ivalabcPR_OBJECT_TYPE,
        ivalabcPR_ENTRYID,
        ivalabcPR_RECORD_KEY,
        ivalabcPR_SEARCH_KEY,
        ivalabcPR_DISPLAY_NAME_A,
        ivalabcPR_CONTAINER_FLAGS,
        ivalabcPR_SAB_FILE,
        ivalabcPR_SAB_FILE_TEMP,
        ivalabcPR_RADIO_BUTTON_VALUE,
        cvalabcMax };

static SizedSPropTagArray( cvalabcMax, tagaABCAccess) =
{
    cvalabcMax,
    {   PR_DISPLAY_TYPE,
        PR_OBJECT_TYPE,
        PR_ENTRYID,
        PR_RECORD_KEY,
        PR_SEARCH_KEY,
        PR_DISPLAY_NAME_A,
        PR_CONTAINER_FLAGS,
        PR_SAB_FILE,
        PR_SAB_FILE_TEMP,
        PR_RADIO_BUTTON_VALUE
      }
};

static ULONG    rgulABCAccess[cvalabcMax] =
{
      IPROP_READONLY | IPROP_CLEAN,     /* PR_DISPLAY_TYPE */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_OBJECT_TYPE */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_ENTRYID */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_RECORD_KEY */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_SEARCH_KEY */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_DISPLAY_NAME_A */
      IPROP_READONLY | IPROP_CLEAN,     /* PR_CONTAINER_FLAGS */
      IPROP_READWRITE | IPROP_CLEAN,    /* PR_SAB_FILE */
      IPROP_READWRITE | IPROP_CLEAN,    /* PR_SAB_FILE_TEMP */
      IPROP_READWRITE | IPROP_CLEAN     /* PR_RADIO_BUTTON_VALUE */
};

HRESULT
HrNewSampDirectory( LPABCONT *          lppABC,
                    ULONG *             lpulObjType,
                    LPABLOGON           lpABLogon,
                    LPCIID              lpInterface,
                    HINSTANCE           hLibrary,
                    LPALLOCATEBUFFER    lpAllocBuff,
                    LPALLOCATEMORE      lpAllocMore,
                    LPFREEBUFFER        lpFreeBuff,
                    LPMALLOC            lpMalloc )
{
    HRESULT hResult = hrSuccess;
    LPABCNT lpABC = NULL;
    SCODE sc;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[cvalabcMax];
    char szBuf[MAX_PATH];
    MAPIUID * lpMuidLogon;
    LPSTR lpszFileName;
    DIR_ENTRYID eidRoot =   {   {0, 0, 0, 0},
                                MUIDABSAMPLE,
                                SAMP_VERSION,
                                SAMP_DIRECTORY };

    /*  Do I support this interface?? */
    if (lpInterface)
    {
        if (memcmp(lpInterface, &IID_IABContainer, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIContainer, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIProp, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IUnknown, sizeof(IID)))
        {
            DebugTraceSc(HrNewSampDirectory, MAPI_E_INTERFACE_NOT_SUPPORTED);
            return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        }
    }

    /*
     *  Allocate space for the directory container structure
     */

    sc = lpAllocBuff( sizeof(ABCNT), (LPVOID *) &lpABC );

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    lpABC->lpVtbl = &vtblABC;
    lpABC->lcInit = 1;
    lpABC->hResult = hrSuccess;
    lpABC->idsLastError = 0;

    lpABC->hLibrary = hLibrary;
    lpABC->lpAllocBuff = lpAllocBuff;
    lpABC->lpAllocMore = lpAllocMore;
    lpABC->lpFreeBuff = lpFreeBuff;
    lpABC->lpMalloc = lpMalloc;

    lpABC->lpABLogon = lpABLogon;
    lpABC->lpTDatDetails = NULL;

    /*
     *  Create property storage object
     */

    sc = CreateIProp((LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto err;
    }

    /*
     *  initialize the muid in the entry id
     */
    lpMuidLogon = LpMuidFromLogon(lpABLogon);
    eidRoot.muidID = *lpMuidLogon;

    /*
     *  Set up initial set of properties associated with this
     *  container.
     */
    spv[ivalabcPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    spv[ivalabcPR_DISPLAY_TYPE].Value.l = DT_NOT_SPECIFIC;

    spv[ivalabcPR_OBJECT_TYPE].ulPropTag = PR_OBJECT_TYPE;
    spv[ivalabcPR_OBJECT_TYPE].Value.l = MAPI_ABCONT;

    spv[ivalabcPR_ENTRYID].ulPropTag = PR_ENTRYID;
    spv[ivalabcPR_ENTRYID].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalabcPR_ENTRYID].Value.bin.lpb = (LPBYTE) &eidRoot;

    spv[ivalabcPR_RECORD_KEY].ulPropTag = PR_RECORD_KEY;
    spv[ivalabcPR_RECORD_KEY].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalabcPR_RECORD_KEY].Value.bin.lpb = (LPBYTE) &eidRoot;

    spv[ivalabcPR_SEARCH_KEY].ulPropTag = PR_SEARCH_KEY;
    spv[ivalabcPR_SEARCH_KEY].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalabcPR_SEARCH_KEY].Value.bin.lpb = (LPBYTE) &eidRoot;

    spv[ivalabcPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;

    GenerateContainerDN(lpABLogon, szBuf);
    spv[ivalabcPR_DISPLAY_NAME_A].Value.lpszA = szBuf;

    spv[ivalabcPR_CONTAINER_FLAGS].ulPropTag = PR_CONTAINER_FLAGS;
    spv[ivalabcPR_CONTAINER_FLAGS].Value.l = AB_RECIPIENTS;

    /*
     *  Get the current .SAB file name from our logon object
     */
    hResult = HrLpszGetCurrentFileName(lpABLogon, &lpszFileName);
    if (HR_FAILED(hResult))
    {
        goto err;
    }

    spv[ivalabcPR_SAB_FILE].ulPropTag = PR_SAB_FILE;
    spv[ivalabcPR_SAB_FILE].Value.lpszA = lpszFileName;

    spv[ivalabcPR_SAB_FILE_TEMP].ulPropTag = PR_SAB_FILE_TEMP;
    spv[ivalabcPR_SAB_FILE_TEMP].Value.lpszA = lpszFileName;

    spv[ivalabcPR_RADIO_BUTTON_VALUE].ulPropTag = PR_RADIO_BUTTON_VALUE;
    spv[ivalabcPR_RADIO_BUTTON_VALUE].Value.l = RADIO_BUTTON_1_RETURN_VALUE;

    /*
     *   Set the default properties
     */
    hResult = lpPropData->lpVtbl->SetProps(lpPropData,
        cvalabcMax,
        spv,
        NULL);

    /*
     *  No longer need this buffer
     */
    lpFreeBuff(lpszFileName);

    if (HR_FAILED(hResult))
    {
        goto err;
    }

    /*
     *  We mark all properties as being CLEAN initially.  Only PR_SAB_FILE,
     *  PR_SAB_FILE_TEMP, and PR_RADIO_BUTTON_VALUE are marked as READWRITE
     *  (Readable/Writable by the client) the others are marked as READONLY
     *  so that the client may not alter them.
     *
     *  Notice that the ABC_SaveChanges code checks the IPROP_DIRTY bit
     *  on PR_SAB_FILE_TEMP to see if the client altered it!
     */
    (void) lpPropData->lpVtbl->HrSetPropAccess(lpPropData,
                                (LPSPropTagArray) &tagaABCAccess,
                                rgulABCAccess);

    InitializeCriticalSection(&lpABC->cs);

    /*  We must AddRef the lpABLogon object since we will be using it
     */
    lpABLogon->lpVtbl->AddRef(lpABLogon);

    lpABC->lpPropData = (LPMAPIPROP) lpPropData;
    *lppABC = (LPABCONT) lpABC;
    *lpulObjType = MAPI_ABCONT;

out:

    DebugTraceResult(HrNewSampDirectory, hResult);
    return hResult;

err:
    /*
     *  free the ABContainer object
     */
    lpFreeBuff( lpABC );

    /*
     *  free the property storage object
     */
    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    goto out;
}

/*
 -  ABC_Release
 -
 *  Decrement lcInit.
 *      When lcInit == 0, free up the lpABC structure
 *
 */

STDMETHODIMP_(ULONG)
ABC_Release(LPABCNT lpABC)
{

    long lcInit;

    /*
     *  Check to see if it has a jump table
     */

    ABC_ValidateObject(Release, lpABC);

    Validate_IUnknown_Release(lpABC);

    EnterCriticalSection(&lpABC->cs);
    lcInit = --lpABC->lcInit;
    LeaveCriticalSection(&lpABC->cs);

    if (lcInit == 0)
    {

        /*
         *  Get rid of the lpPropData
         */
        if (lpABC->lpPropData)
            lpABC->lpPropData->lpVtbl->Release(lpABC->lpPropData);

        /*
         *  Get rid of the details table
         */
        if (lpABC->lpTDatDetails)
            lpABC->lpTDatDetails->lpVtbl->Release(lpABC->lpTDatDetails);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpABC->lpABLogon)
        {
            lpABC->lpABLogon->lpVtbl->Release(lpABC->lpABLogon);
            lpABC->lpABLogon = NULL;
        }

        /*
         *  Destroy the critical section for this object
         */

        DeleteCriticalSection(&lpABC->cs);

        /*
         *  Set the Jump table to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */
        lpABC->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpABC->lpFreeBuff(lpABC);
        return 0;
    }

    return lpABC->lcInit;

}

/*
 -  ABC_SaveChanges
 -
 *  This is used to save changes associated with the search dialog
 *  in order to get the advanced search restriction and to save changes
 *  associated with the container details dialog.
 *
 */

SPropTagArray tagaSAB_FILE =
{
    1,
    {
        PR_SAB_FILE_TEMP
    }
};

STDMETHODIMP
ABC_SaveChanges(LPABCNT lpABC, ULONG ulFlags)
{
    HRESULT hResult = hrSuccess;
    ULONG ulCount;
    LPSPropValue lpspv = NULL;
    ULONG FAR *rgulAccess = NULL;
    ULONG ulAccess = IPROP_CLEAN | IPROP_READWRITE;
    LPSPropTagArray ptagaSAB_FILE = (LPSPropTagArray) &tagaSAB_FILE;
    LPPROPDATA lpPropData = (LPPROPDATA) lpABC->lpPropData;

    ABC_ValidateObject(SaveChanges, lpABC);

    Validate_IMAPIProp_SaveChanges(lpABC, ulFlags);

    EnterCriticalSection(&lpABC->cs);

    /*
     *  Check to see if anyone has dirtied the PR_SAB_FILE_TEMP
     */
    (void) lpPropData->lpVtbl->HrGetPropAccess(lpPropData,
                                        &ptagaSAB_FILE,
                                        &rgulAccess);

    if (!rgulAccess || !((*rgulAccess) & IPROP_DIRTY))
    {
        /*
         *  No, nothing to update then head on out
         */

        goto ret;
    }

    /*
     *  Set back to being clean
     */
    (void )lpPropData->lpVtbl->HrSetPropAccess(lpPropData,
                                        ptagaSAB_FILE,
                                        &ulAccess);

    /*
     * Get the temporary sab file name
     */
    hResult = lpPropData->lpVtbl->GetProps(
        lpPropData,
        &tagaSAB_FILE,
        0,      /* ansi */
        &ulCount,
        &lpspv);
    if (HR_FAILED(hResult))
    {
        goto ret;
    }

    if (lpspv->ulPropTag != PR_SAB_FILE_TEMP)
    {
        /*
         *  There's no reason this property shouldn't be there.
         */
        hResult = ResultFromScode(MAPI_E_CORRUPT_DATA);
        goto ret;
    }

    /*
     *  Save the new name back into the object as PR_SAB_FILE
     */
    lpspv->ulPropTag = PR_SAB_FILE;

    hResult = lpPropData->lpVtbl->SetProps(
            lpPropData,
            1,
            lpspv, NULL);

    if (HR_FAILED(hResult))
    {
        /*
         *  Do nothing...  So I couldn't save it away this time...
         */
        hResult = hrSuccess;
        goto ret;
    }

    /*
     *  Update every other object that needs this new information
     */
    hResult = HrReplaceCurrentFileName(lpABC->lpABLogon, lpspv->Value.lpszA);

ret:

    LeaveCriticalSection(&lpABC->cs);

    lpABC->lpFreeBuff(lpspv);
    lpABC->lpFreeBuff(rgulAccess);
    DebugTraceResult(ABC_SaveChanges, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  ABC_OpenProperty
 -
 *
 *  This method allows the opening of the following object:
 *
 *  PR_BUTTON_PRESS  :-  Gets the MAPIControl object associated
 *                       with the button on this container's details.
 *  PR_DETAILS_TABLE :-  Gets the display table associated with
 *                       the details for this container.
 *  PR_SEARCH        :-  Gets the advanced search object associated with
 *                       this container.
 *  PR_CONTAINER_CONTENTS  :-  Same as GetContentsTable()
 *  PR_CONTAINER_HIERARCHY :-  Same as GetHierarchyTable()
 *
 *
 */
STDMETHODIMP
ABC_OpenProperty(LPABCNT lpABC,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    ABC_ValidateObject(OpenProperty, lpABC);

    Validate_IMAPIProp_OpenProperty(lpABC, ulPropTag, lpiid, ulInterfaceOptions,
                                    ulFlags, lppUnk);
    /*
     *  Check for flags we can't support
     */

    if (ulFlags & (MAPI_CREATE|MAPI_MODIFY))
    {
        hResult = ResultFromScode(E_ACCESSDENIED);
        goto out;
    }

    if (ulInterfaceOptions & ~MAPI_UNICODE)
    {
        /*
         *  Only UNICODE flag should be set for any of the objects that might
         *  be returned from this object.
         */

        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        goto out;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);
        DebugTraceArg( ABC_OpenProperty, "bad character width" );
        goto out;

    }

    /*
     *  Details for this directory entry
     */

    if ((ulPropTag == PR_DETAILS_TABLE) ||
        (ulPropTag == PR_BUTTON_PRESS) ||
        (ulPropTag == PR_CONTAINER_CONTENTS) ||
        (ulPropTag == PR_CONTAINER_HIERARCHY) ||
        (ulPropTag == PR_SEARCH))
    {

        /*
         *  Check to see if they're expecting a table interface for all props but
         *  PR_BUTTON_PRESS.
         */
        if ((ulPropTag != PR_BUTTON_PRESS) &&
            (ulPropTag != PR_SEARCH) &&
            memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
        {
            hResult = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
            goto out;
        }

        switch (ulPropTag)
        {

        case PR_BUTTON_PRESS:
            {
                /*
                 *  Check to see if they're expecting a generic control interface
                 */
                if (memcmp(lpiid, &IID_IMAPIControl, sizeof(IID)))
                {
                    hResult = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
                    goto out;
                }

                hResult = HrNewABCButton(lpABC,
                    ulInterfaceOptions,
                    ulFlags,
                    (LPMAPICONTROL FAR *) lppUnk);

                break;
            }

        case PR_DETAILS_TABLE:
            {
                hResult = HrGetDetailsDialog(lpABC, (LPMAPITABLE *) lppUnk);
                break;
            }

        case PR_SEARCH:
            {
                /*
                 *  Check to see if they're expecting a generic control interface
                 */
                if (memcmp(lpiid, &IID_IMAPIContainer, sizeof(IID)))
                {
                    hResult = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
                    goto out;
                }

                hResult = HrNewSearch((LPMAPICONTAINER *) lppUnk,
                                        lpABC->lpABLogon,
                                        lpiid,
                                        lpABC->hLibrary,
                                        lpABC->lpAllocBuff,
                                        lpABC->lpAllocMore,
                                        lpABC->lpFreeBuff,
                                        lpABC->lpMalloc);
                break;
            }

        case PR_CONTAINER_CONTENTS:
            {
                hResult = ABC_GetContentsTable(lpABC, 0, (LPMAPITABLE *) lppUnk);
                break;
            }

        case PR_CONTAINER_HIERARCHY:
            {
                hResult = ABC_GetHierarchyTable(lpABC, 0, (LPMAPITABLE *) lppUnk);
                break;
            }

        default:
            Assert(FALSE);
            break;
        }
    } else
    {

        hResult = ResultFromScode(MAPI_E_NO_SUPPORT);
    }

out:

    DebugTraceResult(ABC_OpenProperty, hResult);
    return hResult;
}

/*************************************************************************
 *
 -  ABC_GetContentsTable
 -
 *
 *  Retrieves the IMAPITable that has the contents of this container.
 */
STDMETHODIMP
ABC_GetContentsTable(LPABCNT lpABC, ULONG ulFlags,
    LPMAPITABLE * lppTable)
{

    HRESULT hResult;

    /*
     *  Validate parameters
     */

    ABC_ValidateObject(GetContentsTable, lpABC);

    Validate_IMAPIContainer_GetContentsTable(lpABC, ulFlags, lppTable);

    /*
     *  Certain flags are not supported
     */
    if (ulFlags & (MAPI_ASSOCIATED))
    {
        hResult = ResultFromScode(MAPI_E_NO_SUPPORT);
        goto out;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceArg( ABC_GetContentsTable, "Bad character width" );
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto out;
    }

    /*
     *  Create the new Contents table
     */
    hResult = HrNewIVTAbc(lppTable,
                          lpABC->lpABLogon,
                          (LPABCONT) lpABC,
                          lpABC->hLibrary,
                          lpABC->lpAllocBuff,
                          lpABC->lpAllocMore,
                          lpABC->lpFreeBuff,
                          lpABC->lpMalloc);

out:

    DebugTraceResult(ABC_GetContentsTable, hResult);
    return hResult;
}

/*
 -  ABC_GetHierarchyTable
 -
 *
 *  There is no hierarchy table associated with this object.
 *
 */
STDMETHODIMP
ABC_GetHierarchyTable(LPABCNT lpABC, ULONG ulFlags,
    LPMAPITABLE * lppTable)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpABC, offsetof(ABCNT, lpVtbl)+sizeof(ABC_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hResult = MakeResult(E_INVALIDARG);
        DebugTraceResult(ABC_HierarchyTable, hResult);
        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpABC->lpVtbl,
        offsetof(ABC_Vtbl, GetHierarchyTable)+sizeof(ABC_GetHierarchyTable_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = MakeResult(E_INVALIDARG);
        DebugTraceResult(ABC_HierarchyTable, hResult);
        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ABC_GetHierarchyTable != lpABC->lpVtbl->GetHierarchyTable)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(ABC_HierarchyTable, hResult);
        return hResult;
    }

    Validate_IMAPIContainer_GetHierarchyTable(lpABC, ulFlags, lppTable);

    /*
     *  We don't support this method on this object
     */
    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(ABC_GetHierarchyTable, hResult);
    return hResult;
}

/*
 -  HrGetDetailsDialog
 -
 *  Builds a display table for this directory.
 */

HRESULT
HrGetDetailsDialog(LPABCNT lpABC, LPMAPITABLE * lppDetailsTable)
{
    HRESULT hResult;

    if (!lpABC->lpTDatDetails)
    {

        /* Create a display table */
        hResult = BuildDisplayTable(
            lpABC->lpAllocBuff,
            lpABC->lpAllocMore,
            lpABC->lpFreeBuff,
            lpABC->lpMalloc,
            lpABC->hLibrary,
            sizeof(rgdtpageDir) / sizeof(DTPAGE),
            rgdtpageDir,
            0,
            lppDetailsTable,
            &lpABC->lpTDatDetails);
    } else
    {
        hResult = lpABC->lpTDatDetails->lpVtbl->HrGetView(
            lpABC->lpTDatDetails,
            NULL,
            NULL,
            0,
            lppDetailsTable);
    }

    DebugTraceResult(HrGetDetailsDialog, hResult);
    return hResult;
}

/*
 *  Button object for this directory's details dialog
 */

/*
 *  Declaration of IMAPIControl object implementation
 */
#undef  INTERFACE
#define INTERFACE   struct _ABCBUTT

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type,method)    MAPIMETHOD_DECLARE(type,method,ABCBUTT_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPICONTROL_METHODS(IMPL)

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type,method)    MAPIMETHOD_TYPEDEF(type,method,ABCBUTT_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPICONTROL_METHODS(IMPL)

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type,method)    STDMETHOD_(type,method)

DECLARE_MAPI_INTERFACE(ABCBUTT_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPICONTROL_METHODS(IMPL)
};

/*
 *
 *  Declaration of structure behind button
 *
 */
typedef struct _ABCBUTT
{

ABCBUTT_Vtbl FAR * lpVtbl;

    SAB_IUnknown;
    /*
     *  My parent container object
     */
    LPABCNT     lpABC;

} ABCBUTT, *LPABCBUTT;

/*
 *  Fill in the vtbl
 */
ABCBUTT_Vtbl vtblABCBUTT =
{
    ABCBUTT_QueryInterface,
    (ABCBUTT_AddRef_METHOD *)       ROOT_AddRef,
    ABCBUTT_Release,
    (ABCBUTT_GetLastError_METHOD *) ROOT_GetLastError,
    ABCBUTT_Activate,
    ABCBUTT_GetState
};

#define ABCBUTT_ValidateObject(Method, pThis)       \
{                                                   \
    HRESULT hResult;                                \
    if (IsBadReadPtr(pThis, sizeof(ABCBUTT)))       \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABCBUTT##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
                                                    \
    if (pThis->lpVtbl != &vtblABCBUTT)              \
    {                                               \
        hResult = ResultFromScode(E_INVALIDARG);    \
        DebugTraceResult(ABCBUTT##_##Method, hResult);  \
        return hResult;                             \
    }                                               \
}

/*
 -  HrNewABCButton
 -
 *  Creates the MAPIControl object that is the code behind the button
 *  on the Sample AB's directory details.
 */
HRESULT
HrNewABCButton( LPABCNT lpABC,
                ULONG ulInterfaceOptions,
                ULONG ulFlags,
                LPMAPICONTROL FAR * lppMAPICont)
{
    LPABCBUTT lpABCButt = NULL;
    SCODE scode;

    scode = lpABC->lpAllocBuff(sizeof(ABCBUTT),(LPVOID *) &lpABCButt);

    if (FAILED(scode))
    {
        DebugTraceSc(HrNewABCButton, scode);
        return ResultFromScode(scode);
    }

    lpABCButt->lpVtbl = &vtblABCBUTT;
    lpABCButt->lcInit = 1;
    lpABCButt->hResult = hrSuccess;
    lpABCButt->idsLastError = 0;

    lpABCButt->hLibrary = lpABC->hLibrary;
    lpABCButt->lpAllocBuff = lpABC->lpAllocBuff;
    lpABCButt->lpAllocMore = lpABC->lpAllocMore;
    lpABCButt->lpFreeBuff = lpABC->lpFreeBuff;
    lpABCButt->lpMalloc = lpABC->lpMalloc;
    lpABCButt->lpABC = lpABC;

    /*
     *  I need my parent object to stay around while this object
     *  does so that I can get to it in my Activate() method.
     *  To do this just AddRef() it.
     */
    lpABC->lpVtbl->AddRef(lpABC);

    InitializeCriticalSection(&lpABCButt->cs);

    *lppMAPICont = (LPMAPICONTROL) lpABCButt;

    return hrSuccess;
}

/*************************************************************************
 *
 *
 -  ABCBUTT_QueryInterface
 -
 *
 *  Allows QI'ing to IUnknown and IMAPIControl.
 *
 */
STDMETHODIMP
ABCBUTT_QueryInterface( LPABCBUTT lpABCButt,
                        REFIID lpiid,
                        LPVOID FAR * lppNewObj)
{

    HRESULT hResult = hrSuccess;

    /*  Minimally validate the lpABCButt parameter */

    ABCBUTT_ValidateObject(QueryInterface, lpABCButt);

    Validate_IUnknown_QueryInterface(lpABCButt, lpiid, lppNewObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIControl, sizeof(IID)))
    {
        *lppNewObj = NULL;      /* OLE requires zeroing [out] parameter */
        hResult = ResultFromScode(E_NOINTERFACE);
        goto out;
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpABCButt->cs);
    ++lpABCButt->lcInit;
    LeaveCriticalSection(&lpABCButt->cs);

    *lppNewObj = lpABCButt;

out:

    DebugTraceResult(ABCBUTT_QueryInterface, hResult);
    return hResult;
}

/*
 -  ABCBUTT_Release
 -
 *  Releases and cleans up this object
 */
STDMETHODIMP_(ULONG)
ABCBUTT_Release(LPABCBUTT lpABCButt)
{
    long lcInit;

    /*  Minimally validate the lpABCButt parameter */

    if (IsBadReadPtr(lpABCButt, sizeof(ABCBUTT)))
    {
        return 1;
    }

    if (lpABCButt->lpVtbl != &vtblABCBUTT)
    {
        return 1;
    }

    Validate_IUnknown_Release(lpABCButt);

    EnterCriticalSection(&lpABCButt->cs);
    lcInit = --lpABCButt->lcInit;
    LeaveCriticalSection(&lpABCButt->cs);

    if (lcInit == 0)
    {

        /*
         *  Release my parent
         */
        lpABCButt->lpABC->lpVtbl->Release(lpABCButt->lpABC);

        /*
         *  Destroy the critical section for this object
         */

        DeleteCriticalSection(&lpABCButt->cs);

        /*
         *  Set the Jump table to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */
        lpABCButt->lpVtbl = NULL;

        /*
         *  Free the object
         */

        lpABCButt->lpFreeBuff(lpABCButt);
        return 0;
    }

    return lcInit;

}

/*
 -  ABCBUTT_Activate
 -
 *
 *  Activates this control.  In this case, it brings up the common file browsing
 *  dialog and allows the user to pick a different .SAB file.
 *
 *  Note that if all is successful it sends a display table notification.  The UI
 *  will respond to this by updating the particular control that was said to have
 *  changed in the notification.
 */
STDMETHODIMP
ABCBUTT_Activate(LPABCBUTT lpABCButt, ULONG ulFlags,
    ULONG ulUIParam)
{
    HRESULT hResult = hrSuccess;
    OPENFILENAME openfilename;
    char szFileName[MAX_PATH];
    char szDirName[MAX_PATH];
    SPropValue sProp;
    LPSPropValue lpspv = NULL;
    ULONG ulCount, ich;

    /*  Minimally validate the lpABCButt parameter */

    ABCBUTT_ValidateObject(Activate, lpABCButt);

    Validate_IMAPIControl_Activate(lpABCButt, ulFlags, ulUIParam);

    if (ulFlags)
    {
        /*
         *  No flags defined for this method
         */
        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        goto out;
    }

    /*
     *  First, get the old SAB file name so that it shows up in the
     *  choose file dialog
     */

    hResult = lpABCButt->lpABC->lpPropData->lpVtbl->GetProps(
        lpABCButt->lpABC->lpPropData,
        &tagaSAB_FILE,
        0,              /* ansi */
        &ulCount,
        &lpspv);
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    if (lpspv->ulPropTag != PR_SAB_FILE_TEMP)
    {
        /*
         *  Property wasn't there...
         */
        hResult = ResultFromScode(MAPI_E_CORRUPT_DATA);

        goto out;
    }

    lstrcpyA(szFileName, lpspv->Value.lpszA);

    szDirName[0] = '\0';

    /* get the path name */
    for (ich = lstrlenA(lpspv->Value.lpszA) - 1; ich > 0; ich--)
    {
        if (lpspv->Value.lpszA[ich] == '\\')
        {
            lpspv->Value.lpszA[ich] = '\0';
            break;
        }
        else if (lpspv->Value.lpszA[ich] == ':')
        {
            lpspv->Value.lpszA[ich + 1] = '\0';
            break;
        }
    }

    lstrcpyA(szDirName, lpspv->Value.lpszA);

    /*
     *  Get the user to select one
     */
    openfilename.lStructSize = sizeof(OPENFILENAME);
    openfilename.hwndOwner = (HWND) ulUIParam;
    openfilename.hInstance = 0; /* Ignored */
    openfilename.lpstrFilter = "Sample AB files\0*.sab\0\0";
    openfilename.lpstrCustomFilter = NULL;
    openfilename.nMaxCustFilter = 0;
    openfilename.nFilterIndex = 0;
    openfilename.lpstrFile = szFileName;
    openfilename.nMaxFile = MAX_PATH;
    openfilename.lpstrFileTitle = NULL;
    openfilename.nMaxFileTitle = 0;
    openfilename.lpstrInitialDir = szDirName;
    openfilename.lpstrTitle = "Sample Address Book";
    openfilename.Flags = OFN_FILEMUSTEXIST |
        OFN_HIDEREADONLY |
        OFN_NOCHANGEDIR;
    openfilename.nFileOffset = 0;
    openfilename.nFileExtension = 0;
    openfilename.lpstrDefExt = "sab";
    openfilename.lCustData = 0;
    openfilename.lpfnHook = NULL;
    openfilename.lpTemplateName = NULL;

    /*
     *  Call up the common dialog
     */
    if (!GetOpenFileName(&openfilename))
    {
        hResult = hrSuccess;
        goto out;
    }

    /*
     *  Save SAB FileName into the container object
     */

    sProp.ulPropTag = PR_SAB_FILE_TEMP;
    sProp.Value.lpszA = szFileName;

    hResult = lpABCButt->lpABC->lpPropData->lpVtbl->SetProps(
        lpABCButt->lpABC->lpPropData,
        1,
        &sProp,
        NULL);
    if (HR_FAILED(hResult))
    {
        goto out;
    }

    /*
     *  Notify the details table so that everyone with a view open
     *  will get notified
     */
    if (lpABCButt->lpABC->lpTDatDetails)
    {
        sProp.ulPropTag = PR_CONTROL_ID;
        sProp.Value.bin.lpb = (LPBYTE) &notifdata;
        sProp.Value.bin.cb = sizeof(NOTIFDATA);

        hResult = lpABCButt->lpABC->lpTDatDetails->lpVtbl->HrNotify(
            lpABCButt->lpABC->lpTDatDetails,
            0,
            1,
            &sProp);
    }

out:
    lpABCButt->lpFreeBuff(lpspv);
    DebugTraceResult(ABCBUTT_Activate, hResult);
    return hResult;
}

/*
 -  ABCBUTT_GetState
 -
 *  Says whether this control should appear enabled or not at this time.
 *
 */
STDMETHODIMP
ABCBUTT_GetState(LPABCBUTT lpABCButt, ULONG ulFlags,
    ULONG * lpulState)
{

    HRESULT hResult = hrSuccess;

    /*  Minimally validate the lpABCButt parameter */

    ABCBUTT_ValidateObject(GetStatus, lpABCButt);

    Validate_IMAPIControl_GetState(lpABCButt, ulFlags, lpulState);

    /*
     *  Means that at this time this button should appear enabled.
     */
    *lpulState = MAPI_ENABLED;

    DebugTraceResult(ABCBUTT_GetState, hResult);
    return hResult;
}


ROOT.C

/***********************************************************************
 *
 *  ROOT.C
 *
 *
 *  Sample AB Root object
 *      - This file contains the code for implementing the Sample AB
 *      root object and the hierarchy table.
 *
 *  The Root container object is returned via an ABPOpenEntry() with a
 *  0-sized entryid.  It only has a limited set of properties available on
 *  it.  The most useful method on it is GetHierarchyTable() which returns
 *  the root hierarchy associated with this provider.
 *
 *  The hierarchy table has only a single row in it.  The row represents the
 *  single .SAB file which this provider browses.  If a provider wanted to
 *  browse many different lists, it would have multiple rows.
 *
 *  The following routines are implemented in this file:
 *
 *
 *    To implement the Root container object:
 *
 *      HrNewROOT
 *      SetErrorSz
 *      ROOT_QueryInterface
 *      ROOT_AddRef
 *      ROOT_Release
 *      ROOT_GetLastError
 *      ROOT_SaveChanges
 *      ROOT_OpenProperty
 *      ROOT_GetContentsTable
 *      ROOT_GetHierarchyTable
 *      ROOT_OpenEntry
 *      ROOT_CreateEntry
 *      ROOT_CopyEntries
 *      ROOT_DeleteEntries
 *      ROOT_ResolveNames
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"

/*
 *  The structure behind the 'this' pointer
 */
typedef struct _ROOT
{
    const ROOT_Vtbl FAR *   lpVtbl;

    SAB_Wrapped;

} ROOT, *LPROOT;

/*
 *  Root vtbl is filled in here
 */
static const ROOT_Vtbl vtblROOT =
{
    ROOT_QueryInterface,
    ROOT_AddRef,
    ROOT_Release,
    ROOT_GetLastError,
    ROOT_SaveChanges,
    (ROOT_GetProps_METHOD *) WRAP_GetProps,
    (ROOT_GetPropList_METHOD *) WRAP_GetPropList,
    ROOT_OpenProperty,
    (ROOT_SetProps_METHOD *) WRAP_SetProps,
    (ROOT_DeleteProps_METHOD *) WRAP_DeleteProps,
    (ROOT_CopyTo_METHOD *) WRAP_CopyTo,
    (ROOT_CopyProps_METHOD *) WRAP_CopyProps,
    (ROOT_GetNamesFromIDs_METHOD *) WRAP_GetNamesFromIDs,
    (ROOT_GetIDsFromNames_METHOD *) WRAP_GetIDsFromNames,
    ROOT_GetContentsTable,
    ROOT_GetHierarchyTable,
    ROOT_OpenEntry,
    ROOT_SetSearchCriteria,
    ROOT_GetSearchCriteria,
    ROOT_CreateEntry,
    ROOT_CopyEntries,
    ROOT_DeleteEntries,
    ROOT_ResolveNames
};

/*
 *  Default properties in this object
 */
enum {  ivalPR_DISPLAY_TYPE = 0,
        ivalPR_OBJECT_TYPE,
        ivalPR_ENTRYID,
        ivalPR_RECORD_KEY,
        ivalPR_SEARCH_KEY,
        ivalPR_DISPLAY_NAME_A,
        ivalPR_CONTAINER_FLAGS,
        cvalMax };

/*
 -  HrNewROOT
 -
 *  Creates a new Root Container object.  This object is created
 *  when an lpABLogon::OpenEntry() is called with a 0-sized entryid.
 *
 *
 */
HRESULT
HrNewROOT(LPABCONT *        lppROOT,
          ULONG *           lpulObjType,
          LPABLOGON         lpABPLogon,
          LPCIID            lpInterface,
          HINSTANCE         hLibrary,
          LPALLOCATEBUFFER  lpAllocBuff,
          LPALLOCATEMORE    lpAllocMore,
          LPFREEBUFFER      lpFreeBuff,
          LPMALLOC          lpMalloc )
{
    LPROOT lpROOT = NULL;
    SCODE scode;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[cvalMax];
    HRESULT hResult;
    char szDisplayName[MAX_ROOT_NAME];
    LPSTR pszDisplayName = (LPSTR) szDisplayName;
    SCODE sc;
    DIR_ENTRYID eidRoot =   {   {0, 0, 0, 0},
                                MUIDABSAMPLE,
                                SAMP_VERSION,
                                SAMP_DIRECTORY };

    /*  Do I support this interface?? */
    if (lpInterface)
    {
        if (memcmp(lpInterface, &IID_IUnknown, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIProp, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IMAPIContainer, sizeof(IID)) &&
            memcmp(lpInterface, &IID_IABContainer, sizeof(IID)))
        {
            DebugTraceSc(HrNewROOT, MAPI_E_INTERFACE_NOT_SUPPORTED);
            return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
        }
    }
    /*
     *  Allocate space for the ROOT structure
     */
    scode = lpAllocBuff (sizeof(ROOT), (LPVOID *) &lpROOT);
    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto err;
    }

    lpROOT->lpVtbl = &vtblROOT;
    lpROOT->lcInit = 1;
    lpROOT->hResult = hrSuccess;
    lpROOT->idsLastError = 0;

    lpROOT->hLibrary = hLibrary;
    lpROOT->lpAllocBuff = lpAllocBuff;
    lpROOT->lpAllocMore = lpAllocMore;
    lpROOT->lpFreeBuff = lpFreeBuff;
    lpROOT->lpMalloc = lpMalloc;

    lpROOT->lpABLogon = lpABPLogon;

    /*
     *  Create a property storage object
     */

    scode = CreateIProp((LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(scode))
    {
        hResult = ResultFromScode(scode);
        goto err;
    }

    /*
     *  Set up initial set of properties associated with this
     *  container.
     */

    spv[ivalPR_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
    spv[ivalPR_DISPLAY_TYPE].Value.l = 0;           /* undefined for now */

    spv[ivalPR_OBJECT_TYPE].ulPropTag = PR_OBJECT_TYPE;
    spv[ivalPR_OBJECT_TYPE].Value.l = MAPI_ABCONT;

    spv[ivalPR_ENTRYID].ulPropTag = PR_ENTRYID;
    spv[ivalPR_ENTRYID].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalPR_ENTRYID].Value.bin.lpb = (LPBYTE) &eidRoot;

    spv[ivalPR_RECORD_KEY].ulPropTag = PR_RECORD_KEY;
    spv[ivalPR_RECORD_KEY].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalPR_RECORD_KEY].Value.bin.lpb = (LPBYTE) &eidRoot;

    spv[ivalPR_SEARCH_KEY].ulPropTag = PR_SEARCH_KEY;
    spv[ivalPR_SEARCH_KEY].Value.bin.cb = sizeof(DIR_ENTRYID);
    spv[ivalPR_SEARCH_KEY].Value.bin.lpb = (LPBYTE) &eidRoot;

    sc = ScLoadString(  IDS_ROOT_NAME,
                        MAX_ROOT_NAME,
                        NULL,
                        hLibrary,
                        (LPSTR *) &pszDisplayName);
    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto out;
    }

    spv[ivalPR_DISPLAY_NAME_A].ulPropTag = PR_DISPLAY_NAME_A;
    spv[ivalPR_DISPLAY_NAME_A].Value.lpszA = szDisplayName;

    spv[ivalPR_CONTAINER_FLAGS].ulPropTag = PR_CONTAINER_FLAGS;
    spv[ivalPR_CONTAINER_FLAGS].Value.l = AB_SUBCONTAINERS;

    /*
     *   Set the default properties
     */
    hResult = lpPropData->lpVtbl->SetProps(lpPropData,
        cvalMax,
        spv,
        NULL);

    if (HR_FAILED(hResult))
    {
        goto err;
    }

    /*
     *  The whole object is set READONLY thus eliminating the need to
     *  set access rights for the individual properties.
     */
    (void) lpPropData->lpVtbl->HrSetObjAccess(lpPropData, IPROP_READONLY);

    lpROOT->lpPropData = (LPMAPIPROP) lpPropData;

    InitializeCriticalSection(&lpROOT->cs);

    /*  We must AddRef the lpABPLogon object since we will be using it
     */
    lpABPLogon->lpVtbl->AddRef(lpABPLogon);

    *lpulObjType = MAPI_ABCONT;
    *lppROOT = (LPVOID) lpROOT;

out:
    DebugTraceResult(HrNewROOT, hResult);
    return hResult;

err:
    /*
     *  free the root object
     */
    lpFreeBuff (lpROOT);

    /*
     *  free the prop data
     */
    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    goto out;

}

/*************************************************************************
 *
 *
 -  SetErrorIDS
 -
 *  Handles remembering the last error string associated with an object.
 *  This string is retrieved by the method GetLastError()
 *
 *
 */

VOID
SetErrorIDS(LPVOID lpObject, HRESULT hResult, UINT ids)
{
    ((LPROOT) lpObject)->hResult = hResult;
    ((LPROOT) lpObject)->idsLastError = ids;
}

/*
 -  ROOT_QueryInterface
 -
 *  Supports QI'ing to IUnknown, IMAPIProp, IMAPIContainer, and IABContainer.
 *
 */
STDMETHODIMP
ROOT_QueryInterface(LPROOT lpROOT,
                    REFIID lpiid,
                    LPVOID FAR * lppNewObj)
{

    HRESULT hr = hrSuccess;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, QueryInterface)+sizeof(ROOT_QueryInterface_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_QueryInterface != lpROOT->lpVtbl->QueryInterface)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IUnknown_QueryInterface(lpROOT, lpiid, lppNewObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIProp, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIContainer, sizeof(IID)) &&
        memcmp(lpiid, &IID_IABContainer, sizeof(IID)))
    {
        *lppNewObj = NULL;      /* OLE requires zeroing [out] parameter */
        hr = ResultFromScode(E_NOINTERFACE);
        goto out;
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpROOT->cs);
    ++lpROOT->lcInit;
    LeaveCriticalSection(&lpROOT->cs);

    *lppNewObj = lpROOT;

out:

    DebugTraceResult(ROOT_QueryInterface, hr);
    return hr;
}

/**************************************************
 *
 -  ROOT_AddRef
 -
 *      Increment lcInit
 *
 */

STDMETHODIMP_(ULONG) ROOT_AddRef(LPROOT lpROOT)
{
    LONG lcInit;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        return 1;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, AddRef)+sizeof(ROOT_AddRef_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */
        return 1;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_AddRef != lpROOT->lpVtbl->AddRef)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        return 1;
    }

    Validate_IUnknown_AddRef(lpROOT);

    EnterCriticalSection(&lpROOT->cs);
    lcInit = ++lpROOT->lcInit;
    LeaveCriticalSection(&lpROOT->cs);

    return lcInit;
}

/**************************************************
 *
 -  ROOT_Release
 -
 *      Decrement lcInit.
 *      When lcInit == 0, free up the lpROOT structure
 *
 */

STDMETHODIMP_(ULONG) ROOT_Release(LPROOT lpROOT)
{

    LONG lcInit;

    /*
     *  Check to see if it can be a ROOT object
     */
    if (IsBadReadPtr(lpROOT, sizeof(ROOT)))
    {
        /*
         *  Not large enough
         */
        return 1;
    }

    /*
     *  Check to see that it's ROOTs vtbl
     */
    if (lpROOT->lpVtbl != &vtblROOT)
    {
        /*
         *  Not my jump table
         */
        return 1;
    }

    Validate_IUnknown_Release(lpROOT);

    EnterCriticalSection(&lpROOT->cs);
    lcInit = --lpROOT->lcInit;
    LeaveCriticalSection(&lpROOT->cs);

    if (lcInit == 0)
    {

        /*
         *  Delete our critical section
         */

        DeleteCriticalSection(&lpROOT->cs);

        /*
         *  Set the vtbl to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */
        lpROOT->lpVtbl = NULL;

        /*
         *  free the property storage object
         */
        if (lpROOT->lpPropData)
            lpROOT->lpPropData->lpVtbl->Release(lpROOT->lpPropData);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpROOT->lpABLogon)
        {
            lpROOT->lpABLogon->lpVtbl->Release(lpROOT->lpABLogon);
            lpROOT->lpABLogon = NULL;
        }

        /*
         *  Free the object
         */

        lpROOT->lpFreeBuff(lpROOT);
        return 0;
    }

    return lcInit;
}

/*
 -  ROOT_GetLastError
 -
 *      Returns a string associated with the last hResult
 *  returned by the ROOT object.
 *
 */

STDMETHODIMP
ROOT_GetLastError(  LPROOT lpROOT,
                    HRESULT hError,
                    ULONG ulFlags,
                    LPMAPIERROR FAR * lppMapiError )
{
    SCODE scode;
    HRESULT hResult = hrSuccess;
    LPSTR   lpszMessage = NULL;

    /*
     *  Validate parameters
     */

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
        offsetof(ROOT_Vtbl, GetLastError)+sizeof(ROOT_GetLastError_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_GetLastError != lpROOT->lpVtbl->GetLastError)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(ROOT_GetLastError, hResult);
        return hResult;
    }

    Validate_IMAPIProp_GetLastError(lpROOT, hError, ulFlags, lppMapiError);

    /*
     *  NOTE:  We don't handle UNICODE yet.  Everything is
     *  assumed to be working in 8-bit char mode.
     */
    if (ulFlags & MAPI_UNICODE)
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);
        DebugTraceResult(ROOT_GetLastError, hResult);
        return hResult;
    }

    EnterCriticalSection(&lpROOT->cs);

    if ((hError != hrSuccess) && (hError == lpROOT->hResult))
    {
        scode = lpROOT->lpAllocBuff( sizeof( MAPIERROR ), lppMapiError );
        if ( FAILED( scode ) )
        {
            hResult = ResultFromScode(scode);
            goto ret;
        }

        ZeroMemory( *lppMapiError, sizeof( MAPIERROR ) );

        (*lppMapiError)->ulVersion = MAPI_ERROR_VERSION;

        /*
         *  Get the MAPI Allocated string associated with the last error
         */
        scode = ScLoadString(lpROOT->idsLastError,
                             MAX_ERROR_STRING_LENGTH,
                             lpROOT->lpAllocBuff,
                             lpROOT->hLibrary,
                             &lpszMessage);
        if ( FAILED( scode) )
        {
            hResult = ResultFromScode(scode);
            goto ret;
        }

        scode = lpROOT->lpAllocMore( lstrlenA( lpszMessage ) + 1, *lppMapiError,
            &(*lppMapiError)->lpszError );
        if ( FAILED( scode ) )
        {
            hResult = ResultFromScode(scode);
            goto ret;
        }

        lstrcpyA( (*lppMapiError)->lpszError, lpszMessage );

    } else
    {
        *lppMapiError = NULL;
    }

ret:
    if ( hResult )
    {
        lpROOT->lpFreeBuff( *lppMapiError );
        *lppMapiError = NULL;
    }

    lpROOT->lpFreeBuff( lpszMessage );
    LeaveCriticalSection(&lpROOT->cs);

    return hResult;
}

/*
 -  ROOT_SaveChanges
 -
 *  Can't save changes on this object.
 *
 *
 */
STDMETHODIMP
ROOT_SaveChanges(LPROOT lpROOT, ULONG ulFlags)
{

    HRESULT hResult;

    ROOT_ValidateObject(ROOT_SaveChanges, lpROOT);

    Validate_IMAPIProp_SaveChanges(lpROOT, ulFlags);

    hResult = ResultFromScode(E_ACCESSDENIED);

    DebugTraceResult(ROOT_SaveChanges, hResult);
    return hResult;

}

/*
 -  ROOT_OpenProperty
 -
 *
 *  For this object I only need to support opening the hierarchy table.
 *
 */
STDMETHODIMP
ROOT_OpenProperty(  LPROOT lpROOT,
                    ULONG ulPropTag,
                    LPCIID lpiid,
                    ULONG ulInterfaceOptions,
                    ULONG ulFlags,
                    LPUNKNOWN * lppUnk)
{

    HRESULT hResult;

    ROOT_ValidateObject(ROOT_OpenProperty, lpROOT);

    Validate_IMAPIProp_OpenProperty(lpROOT, ulPropTag, lpiid,
                            ulInterfaceOptions, ulFlags, lppUnk);

    if (ulFlags & MAPI_CREATE)
    {
        hResult = ResultFromScode(E_ACCESSDENIED);
        goto out;
    }

    if (ulInterfaceOptions & ~MAPI_UNICODE)
    {
        /*
         *  Only UNICODE flag should be set for any of the objects that might
         *  be returned from this object.
         */

        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        goto out;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto out;
    }

    switch (ulPropTag)
    {

        case PR_CONTAINER_HIERARCHY:
        {
            /*
             *  Check to see if they're expecting a IMAPITable object
             */
            if (memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
            {
                hResult = ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
                goto out;
            }

            hResult = ROOT_GetHierarchyTable(lpROOT, 0, (LPMAPITABLE *) lppUnk);

            goto out;
        }

        default:
            break;

    }

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:

    DebugTraceResult(ROOT_OpenProperty, hResult);
    return hResult;

}

/*************************************************************************
 *
 *
 -  ROOT_GetContentsTable
 -
 *  There are no contents in the root
 *
 *
 *
 */
STDMETHODIMP
ROOT_GetContentsTable(  LPROOT lpROOT,
                        ULONG ulFlags,
                        LPMAPITABLE * lppTable)
{

    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
        offsetof(ROOT_Vtbl, GetContentsTable)+sizeof(ROOT_GetContentsTable_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_GetContentsTable != lpROOT->lpVtbl->GetContentsTable)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(ROOT_GetContentsTable, hResult);
        return hResult;
    }

    Validate_IMAPIContainer_GetContentsTable(lpROOT, ulFlags, lppTable);

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto out;
    }

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_GetContentsTable, hResult);
    return hResult;

}

/*
 -  ROOT_GetHierarchyTable
 -
 *  Returns the table with just one entry in it.
 *
 *
 *
 */
STDMETHODIMP
ROOT_GetHierarchyTable( LPROOT lpROOT,
                        ULONG ulFlags,
                        LPMAPITABLE * lppTable)
{

    HRESULT hResult = hrSuccess;

    ROOT_ValidateObject(ROOT_GetHierarchyTable, lpROOT);
    Validate_IMAPIContainer_GetHierarchyTable(lpROOT, ulFlags, lppTable);

    /*
     *  Since we only have one item in our hierarchy table, CONVENIENT_DEPTH
     *  is equivalent to any other depth level (>1).  So, just ignore the
     *  flag.  MAPI_DEFERRED_ERROR is fine.  We don't ever defer errors.
     */

    /*
     *  Create a View Table for the hierarchy.
     */
    hResult = HrBuildRootHier(lpROOT->lpABLogon, lppTable);

    DebugTraceResult(ROOT_GetHierarchyTable, hResult);
    return hResult;

}

/*
 -  ROOT_OpenEntry
 -
 *  Check parameters and use our logon object's OpenEntry method.
 */
STDMETHODIMP
ROOT_OpenEntry(LPROOT lpROOT,
    ULONG cbEntryID,
    LPENTRYID lpEntryID,
    LPCIID lpInterface,
    ULONG ulFlags,
    ULONG * lpulObjType,
    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  No vtbl found
         */
        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
        offsetof(ROOT_Vtbl, OpenEntry)+sizeof(ROOT_OpenEntry_METHOD *)))
    {
        /*
         *  vtbl cannot hold this method
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that it's the correct method
     */
    if (ROOT_OpenEntry != lpROOT->lpVtbl->OpenEntry)
    {
        /*
         *  Not my vtbl
         */
        hResult = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IABLogon_OpenEntry(lpROOT, cbEntryID, lpEntryID, lpInterface,
                                    ulFlags, lpulObjType, lppUnk);

    hResult = lpROOT->lpABLogon->lpVtbl->OpenEntry(lpROOT->lpABLogon,
        cbEntryID,
        lpEntryID,
        lpInterface,
        ulFlags,
        lpulObjType,
        lppUnk);

out:

    DebugTraceResult(ROOT_OpenEntry, hResult);
    return hResult;

}

/*
 -  ROOT_SetSearchCriteria
 -
 *
 *  Not implemented for this object
 */
STDMETHODIMP
ROOT_SetSearchCriteria(LPROOT lpROOT,
    LPSRestriction lpRestriction,
    LPENTRYLIST lpContainerList,
    ULONG ulSearchFlags)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hResult = MakeResult(E_INVALIDARG);

        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
        offsetof(ROOT_Vtbl, SetSearchCriteria)+sizeof(ROOT_SetSearchCriteria_METHOD *)))
    {
        /*
         *  vtbl not large enough to support this method
         */

        hResult = MakeResult(E_INVALIDARG);

        goto out;
    }

    /*
     *  Check to see that it's the correct method
     */
    if (ROOT_SetSearchCriteria != lpROOT->lpVtbl->SetSearchCriteria)
    {
        /*
         *  Not my vtbl
         */
        hResult = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IMAPIContainer_SetSearchCriteria(lpROOT, lpRestriction,
                                        lpContainerList, ulSearchFlags);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_SetSearchCriteria, hResult);
    return hResult;
}

/*
 -  ROOT_GetSearchCriteria
 -
 *
 *  Not implemented for this object
 *
 */
STDMETHODIMP
ROOT_GetSearchCriteria(LPROOT lpROOT,
    ULONG ulFlags,
    LPSRestriction FAR * lppRestriction,
    LPENTRYLIST FAR * lppContainerList,
    ULONG FAR * lpulSearchState)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  Not large enough
         */
        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
        offsetof(ROOT_Vtbl, GetSearchCriteria)+sizeof(ROOT_GetSearchCriteria_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = MakeResult(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_GetSearchCriteria != lpROOT->lpVtbl->GetSearchCriteria)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(ROOT_GetSearchCriteria, hResult);
        return hResult;
    }

    Validate_IMAPIContainer_GetSearchCriteria(lpROOT, ulFlags, lppRestriction,
                                            lppContainerList, lpulSearchState);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

    DebugTraceResult(ROOT_GetSearchCriteria, hResult);
    return hResult;
}

/*
 -  ROOT_CreateEntry
 -
 *  New entries cannot be created in the root
 *
 */
STDMETHODIMP
ROOT_CreateEntry(LPROOT lpROOT,
    ULONG cbEntryID,
    LPENTRYID lpEntryID,
    ULONG ulCreateFlags,
    LPMAPIPROP FAR * lppMAPIPropEntry)
{

    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  No vtbl found
         */
        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, CreateEntry)+sizeof(ROOT_CreateEntry_METHOD *)))
    {
        /*
         *  vtbl not large enough to hold this method
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_CreateEntry != lpROOT->lpVtbl->CreateEntry)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    Validate_IABContainer_CreateEntry(lpROOT, cbEntryID, lpEntryID,
                                ulCreateFlags, lppMAPIPropEntry );

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_CreateEntry, hResult);
    return hResult;

}

/*
 -  ROOT_CopyEntries
 -
 *  Entries cannot be copied into the root
 *
 *
 *
 */
STDMETHODIMP
ROOT_CopyEntries(LPROOT lpROOT,
    LPENTRYLIST lpEntries,
    ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress,
    ULONG ulFlags)
{

    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))

{
        /*
         *  No vtbl found
         */
        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, CopyEntries)+sizeof(ROOT_CopyEntries_METHOD *)))
    {
        /*
         *  vtbl not large enough to hold this method
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_CopyEntries != lpROOT->lpVtbl->CopyEntries)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    Validate_IABContainer_CopyEntries(lpROOT, lpEntries, ulUIParam,
                                        lpProgress, ulFlags);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_CopyEntries, hResult);
    return hResult;

}

/*
 -  ROOT_DeleteEntries
 -
 *  Entries cannot be deleted from the root
 *
 *
 *
 */
STDMETHODIMP
ROOT_DeleteEntries(LPROOT lpROOT,
    LPENTRYLIST lpEntries,
    ULONG ulFlags)
{

    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  No vtbl found
         */
        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, DeleteEntries)+sizeof(ROOT_DeleteEntries_METHOD *)))
    {
        /*
         *  vtbl not large enough to hold this method
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_DeleteEntries != lpROOT->lpVtbl->DeleteEntries)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    Validate_IABContainer_DeleteEntries(lpROOT, lpEntries, ulFlags);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_DeleteEntries, hResult);
    return hResult;
}

/*
 -  ROOT_ResolveNames
 -
 *  No special case handling of resolving names within this container
 *
 *
 *
 */
STDMETHODIMP
ROOT_ResolveNames(  LPROOT          lpROOT,
                    LPSPropTagArray lptagaColSet,
                    ULONG           ulFlags,
                    LPADRLIST       lpAdrList,
                    LPFlagList      lpFlagList )
{

    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpROOT, offsetof(ROOT, lpVtbl)+sizeof(ROOT_Vtbl *)))
    {
        /*
         *  No vtbl found
         */
        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpROOT->lpVtbl,
                     offsetof(ROOT_Vtbl, ResolveNames)+sizeof(ROOT_ResolveNames_METHOD *)))
    {
        /*
         *  vtbl not large enough to hold this method
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    /*
     *  Check to see if the method is the same
     */
    if (ROOT_ResolveNames != lpROOT->lpVtbl->ResolveNames)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = MakeResult(E_INVALIDARG);
        goto out;
    }

    CheckParameters_IABContainer_ResolveNames(lpROOT, lptagaColSet, ulFlags,
                                            lpAdrList, lpFlagList);

    hResult = ResultFromScode(MAPI_E_NO_SUPPORT);

out:
    DebugTraceResult(ROOT_ResolveNames, hResult);
    return hResult;
}


STATUS.C

/***********************************************************************
 *
 *  STATUS.C
 *
 *
 *  The Sample Address Book Provider.
 *  This file contains the methods that implement the status object.
 *
 *  The following routines are implemented in this file:
 *
 *      HrNewStatusObject()
 *      ABS_QueryInterface()
 *      ABS_Release()
 *      ABS_ValidateState()
 *      ABS_SettingsDialog()
 *      ABS_ChangePassword()
 *      ABS_FlushQueues()
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"
#include "sampabp.rh"

/*
 *  Declaration of IMAPIStatus object implementation
 */
#undef  INTERFACE
#define INTERFACE   struct _ABSTATUS

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, ABS_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPISTATUS_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, ABS_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPISTATUS_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(ABS_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPISTATUS_METHODS(IMPL)
};

/*
 *  The actual definition of the structure behind the 'this' pointer for this object
 */
typedef struct _ABSTATUS
{
    const ABS_Vtbl FAR * lpVtbl;

    SAB_Wrapped;

} ABSTATUS, *LPABSTATUS;

/*
 *  AB Status vtbl filled in here
 */

static const ABS_Vtbl vtblABS =
{

    ABS_QueryInterface,
    (ABS_AddRef_METHOD *)           ROOT_AddRef,
    ABS_Release,
    (ABS_GetLastError_METHOD *)     ROOT_GetLastError,
    (ABS_SaveChanges_METHOD *)      WRAP_SaveChanges,
    (ABS_GetProps_METHOD *)         WRAP_GetProps,
    (ABS_GetPropList_METHOD *)      WRAP_GetPropList,
    (ABS_OpenProperty_METHOD *)     WRAP_OpenProperty,
    (ABS_SetProps_METHOD *)         WRAP_SetProps,
    (ABS_DeleteProps_METHOD *)      WRAP_DeleteProps,
    (ABS_CopyTo_METHOD *)           WRAP_CopyTo,
    (ABS_CopyProps_METHOD *)        WRAP_CopyProps,
    (ABS_GetNamesFromIDs_METHOD *)  WRAP_GetNamesFromIDs,
    (ABS_GetIDsFromNames_METHOD *)  WRAP_GetIDsFromNames,
    ABS_ValidateState,
    ABS_SettingsDialog,
    ABS_ChangePassword,
    ABS_FlushQueues
};

/*************************************************************************
 *
 -  HrNewStatusObject
 -
 *  Creates the Status object associated with a particular SAB logon object
 *
 *
 */
HRESULT
HrNewStatusObject(LPMAPISTATUS *    lppABS,
                ULONG *             lpulObjType,
                ULONG               ulFlags,
                LPABLOGON           lpABLogon,
                LPCIID              lpIID,
                HINSTANCE           hLibrary,
                LPALLOCATEBUFFER    lpAllocBuff,
                LPALLOCATEMORE      lpAllocMore,
                LPFREEBUFFER        lpFreeBuff,
                LPMALLOC            lpMalloc )
{
    LPABSTATUS lpABS = NULL;
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPPROPDATA lpPropData = NULL;
    SPropValue spv[6];
    LPSTR lpszFileName;

    /*
     *
     */
    if (lpIID &&
        (memcmp(lpIID, &IID_IMAPIStatus, sizeof(IID)) &&
         memcmp(lpIID, &IID_IMAPIProp, sizeof(IID)) &&
         memcmp(lpIID, &IID_IUnknown, sizeof(IID))))
    {
        DebugTraceSc(HrNewStatusObject, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*
     *  Allocate space for the ABSTATUS structure
     */
    sc = lpAllocBuff( sizeof(ABSTATUS), (LPVOID *) &lpABS );

    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    lpABS->lpVtbl = &vtblABS;
    lpABS->lcInit = 1;
    lpABS->hResult = hrSuccess;
    lpABS->idsLastError = 0;

    lpABS->hLibrary = hLibrary;
    lpABS->lpAllocBuff = lpAllocBuff;
    lpABS->lpAllocMore = lpAllocMore;
    lpABS->lpFreeBuff = lpFreeBuff;
    lpABS->lpMalloc = lpMalloc;

    lpABS->lpABLogon = lpABLogon;

    /*
     *  Create lpPropData
     */

    sc = CreateIProp((LPIID) &IID_IMAPIPropData,
        lpAllocBuff,
        lpAllocMore,
        lpFreeBuff,
        lpMalloc,
        &lpPropData);

    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    /*
     *  Set up initial set of properties associated with this
     *  status object.
     */

    /*
     *  Register my status row...
     */
    hr = HrLpszGetCurrentFileName(lpABLogon, &lpszFileName);
    if (HR_FAILED(hr))
    {
        goto err;
    }

    spv[0].ulPropTag = PR_DISPLAY_NAME_A;
    spv[0].Value.lpszA = lpszFileName;

    spv[1].ulPropTag = PR_RESOURCE_METHODS;
    spv[1].Value.l = 0;

    spv[2].ulPropTag = PR_RESOURCE_FLAGS;
    spv[2].Value.l = 0;

    spv[3].ulPropTag = PR_STATUS_CODE;
    spv[3].Value.l = STATUS_AVAILABLE;

    spv[4].ulPropTag = PR_STATUS_STRING_A;
    spv[4].Value.lpszA = "Available";

    spv[5].ulPropTag = PR_PROVIDER_DISPLAY_A;
    spv[5].Value.lpszA = "Sample Address Book Provider";

    /*
     *   Set the default properties
     */
    hr = lpPropData->lpVtbl->SetProps(lpPropData,
        6,
        spv,
        NULL);

    /*
     *  Done with the current file name
     */
    lpFreeBuff(lpszFileName);

    if (HR_FAILED(hr))
    {
        goto err;
    }

    /*
     *  The whole object is set READONLY thus eliminating the need to
     *  set access rights for the individual properties.
     */
    (void) lpPropData->lpVtbl->HrSetObjAccess(lpPropData, IPROP_READONLY);

    lpABS->lpPropData = (LPMAPIPROP) lpPropData;

    InitializeCriticalSection(&lpABS->cs);

    /*  We must AddRef the lpABLogon object since we will be using it
     */
    lpABLogon->lpVtbl->AddRef(lpABLogon);

    *lpulObjType = MAPI_STATUS;
    *lppABS = (LPMAPISTATUS) lpABS;

out:

    DebugTraceResult(HrNewStatusObject, hr);
    return hr;

err:
    if (lpPropData)
        lpPropData->lpVtbl->Release(lpPropData);

    lpFreeBuff( lpABS );

    goto out;

}

/*************************************************************************
 *
 *
 -  ABS_QueryInterface
 -
 *  This method would allow this object to return a different interface than
 *  the current one.  This object need only support IMAPIStatus and any interface
 *  it derives from.
 *
 */
STDMETHODIMP
ABS_QueryInterface(LPABSTATUS lpABS,
    REFIID lpiid, LPVOID FAR * lppNewObj)
{

    HRESULT hr = hrSuccess;
    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS))
        || lpABS->lpVtbl != &vtblABS )
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IUnknown_QueryInterface(lpABS, lpiid, lppNewObj);

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIProp, sizeof(IID)) &&
        memcmp(lpiid, &IID_IMAPIStatus, sizeof(IID)))
    {
        *lppNewObj = NULL;      /* OLE requires zeroing [out] parameter */
        hr = ResultFromScode(E_NOINTERFACE);
        goto out;
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    EnterCriticalSection(&lpABS->cs);

    ++lpABS->lcInit;

    LeaveCriticalSection(&lpABS->cs);

    *lppNewObj = lpABS;

out:

    DebugTraceResult(ABS_QueryInterface, hr);
    return hr;
}

/**************************************************
 *
 -  ABS_Release
 -
 *      Decrement lpInit.
 *      When lcInit == 0, free up the lpABS structure
 *
 */
STDMETHODIMP_(ULONG) ABS_Release(LPABSTATUS lpABS)
{
    LONG lcInit;

    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS)))
    {
        /*
         *  No jump table found
         */
        return 1;
    }

    /*
     *  Check to see that it's the correct jump table
     */
    if (lpABS->lpVtbl != &vtblABS)
    {
        /*
         *  Not my jump table
         */
        return 1;
    }

    Validate_IUnknown_Release(lpABS);

    EnterCriticalSection(&lpABS->cs);

    lcInit = --lpABS->lcInit;

    LeaveCriticalSection(&lpABS->cs);

    if (lcInit == 0)
    {

        /*
         *  Get rid of the lpPropData
         */

        lpABS->lpPropData->lpVtbl->Release(lpABS->lpPropData);

        /*
         *  Delete the critical section
         */

        DeleteCriticalSection(&lpABS->cs);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpABS->lpABLogon)
        {
            lpABS->lpABLogon->lpVtbl->Release(lpABS->lpABLogon);
            lpABS->lpABLogon = NULL;
        }

        /*
         *  Set the Jump table to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */

        lpABS->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpABS->lpFreeBuff( lpABS );
        return 0;
    }

    return lcInit;

}

/**********************************************************************
 *
 -  ABS_ValidateState
 -
 *  Since I did not set any flags for the property PR_RESOURCE_METHODS
 *  I don't have to support this method.
 *
 */

STDMETHODIMP
ABS_ValidateState(LPABSTATUS lpABS,
                  ULONG ulUIParam,
                  ULONG ulFlags)
{
    HRESULT hr = ResultFromScode(MAPI_E_NO_SUPPORT);
    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS))
        || lpABS->lpVtbl != &vtblABS )
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IMAPIStatus_ValidateState(lpABS, ulUIParam, ulFlags);

out:
    DebugTraceResult(ABS_ValidateState, hr);
    return hr;
}

/**********************************************************************
 *
 -  ABS_SettingsDialog
 -
 *  Since I did not set any flags for the property PR_RESOURCE_METHODS
 *  I don't have to support this method.
 *
 */
STDMETHODIMP
ABS_SettingsDialog( LPABSTATUS lpABS,
                    ULONG ulUIParam,
                    ULONG ulFlags)
{
    HRESULT hr = ResultFromScode(MAPI_E_NO_SUPPORT);
    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS))
        || lpABS->lpVtbl != &vtblABS )
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IMAPIStatus_SettingsDialog(lpABS, ulUIParam, ulFlags);

out:
    DebugTraceResult(ABS_SettingsDialog, hr);
    return hr;
}

/**********************************************************************
 *
 -  ABS_ChangePassword
 -
 *  Since I did not set any flags for the property PR_RESOURCE_METHODS
 *  I don't have to support this method.
 *
 *  Note:   in the parameter validation below I chose only check the first 15
 *          characters of the passwords.  This was arbitrary.
 */
STDMETHODIMP
ABS_ChangePassword(LPABSTATUS lpABS,
    LPTSTR lpOldPass,
    LPTSTR lpNewPass,
    ULONG ulFlags)
{
    HRESULT hr = ResultFromScode(MAPI_E_NO_SUPPORT);
    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS))
        || lpABS->lpVtbl != &vtblABS )
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IMAPIStatus_ChangePassword(lpABS, lpOldPass, lpNewPass, ulFlags);

    if ( ulFlags & MAPI_UNICODE )
    {
        // UNICODE is currently not supported by the sample AB

        hr = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto out;
    }

out:
    DebugTraceResult(ABS_ChangePassword, hr);
    return hr;
}

/**********************************************************************
 *
 -  ABS_FlushQueues
 -
 *  Since I did not set any flags for the property PR_RESOURCE_METHODS
 *  I don't have to support this method.
 *
 */
STDMETHODIMP
ABS_FlushQueues(LPABSTATUS lpABS,
    ULONG ulUIParam,
    ULONG cbTargetTransport,
    LPENTRYID lpTargetTransport,
    ULONG ulFlags)
{
    HRESULT hr = ResultFromScode(MAPI_E_NO_SUPPORT);
    /*
     *  Check to see if lpABS is what we expect
     */
    if (IsBadReadPtr(lpABS, sizeof(ABSTATUS)) || lpABS->lpVtbl != &vtblABS )
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    Validate_IMAPIStatus_FlushQueues(lpABS, ulUIParam, cbTargetTransport,
                                    lpTargetTransport, ulFlags);

out:
    DebugTraceResult(ABS_FlushQueues, hr);
    return hr;
}


TID.C

/***********************************************************************
 *
 *  TID.C
 *
 *  Sample Address Book Template ID  object
 *  This file contains the code for implementing a template ID object
 *  that is associated with entries from this provider.  In particular
 *  the MailUser object generated in ABUSER.C needs this TID object when
 *  it's been copied into another provider (i.e. the PAB).
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"

/*
 *  Declaration of IMailUser object implementation
 */
#undef  INTERFACE
#define INTERFACE   struct _TID

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, TID_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, TID_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(TID_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
};

/*
 *  The definition of the TID object
 */
typedef struct _TID {

    const TID_Vtbl * lpVtbl;

    SAB_Wrapped;

    /*
     *  Private data
     */
    LPMAILUSER  lpABUser;

} TID, *LPTID;

/*
 *  TID jump table is defined here...
 */

static const TID_Vtbl vtblTID =
{
    (TID_QueryInterface_METHOD *)   ABU_QueryInterface,
    (TID_AddRef_METHOD *)           WRAP_AddRef,
    TID_Release,
    (TID_GetLastError_METHOD *)     WRAP_GetLastError,
    (TID_SaveChanges_METHOD *)      WRAP_SaveChanges,
    (TID_GetProps_METHOD *)         WRAP_GetProps,
    (TID_GetPropList_METHOD *)      WRAP_GetPropList,
    TID_OpenProperty,
    (TID_SetProps_METHOD *)         WRAP_SetProps,
    (TID_DeleteProps_METHOD *)      WRAP_DeleteProps,
    (TID_CopyTo_METHOD *)           WRAP_CopyTo,
    (TID_CopyProps_METHOD *)        WRAP_CopyProps,
    (TID_GetNamesFromIDs_METHOD *)  WRAP_GetNamesFromIDs,
    (TID_GetIDsFromNames_METHOD *)  WRAP_GetIDsFromNames,
};

/*************************************************************************
 *
 -  NewTID
 -
 *  Creates the TID object associated with a mail user.
 *
 *
 */
HRESULT
HrNewTID(LPMAPIPROP *       lppMAPIPropNew,
        ULONG               cbTemplateId,
        LPENTRYID           lpTemplateId,
        ULONG               ulTemplateFlags,
        LPMAPIPROP          lpPropData,
        LPABLOGON           lpABPLogon,
        LPCIID              lpInterface,
        HINSTANCE           hLibrary,
        LPALLOCATEBUFFER    lpAllocBuff,
        LPALLOCATEMORE      lpAllocMore,
        LPFREEBUFFER        lpFreeBuff,
        LPMALLOC            lpMalloc )
{
    LPTID lpTID = NULL;
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPMAILUSER lpABUser = NULL;
    ULONG ulObjType;

    /*
     *  Create the user object corresponding to the template id
     */
    hr = HrNewSampUser( &lpABUser,
                        &ulObjType,
                        cbTemplateId,
                        lpTemplateId,
                        lpABPLogon,
                        lpInterface,
                        hLibrary,
                        lpAllocBuff,
                        lpAllocMore,
                        lpFreeBuff,
                        lpMalloc);
    if (HR_FAILED(hr))
    {
        goto err;
    }

    Assert(ulObjType == MAPI_MAILUSER);
    /*
     *  Allocate space for the TID structure
     */
    sc = lpAllocBuff(sizeof(TID), (LPVOID *) &lpTID);
    if (FAILED(sc))
    {
        hr = ResultFromScode(sc);
        goto err;
    }

    /*
     *  Initialize the TID structure
     */

    lpTID->lpVtbl = &vtblTID;
    lpTID->lcInit = 1;
    lpTID->hResult = hrSuccess;
    lpTID->idsLastError = 0;
    lpTID->hLibrary = hLibrary;
    lpTID->lpAllocBuff = lpAllocBuff;
    lpTID->lpAllocMore = lpAllocMore;
    lpTID->lpFreeBuff = lpFreeBuff;
    lpTID->lpMalloc = lpMalloc;
    lpTID->lpABLogon = lpABPLogon;
    lpTID->lpPropData = lpPropData;
    lpTID->lpABUser = (LPMAILUSER) lpABUser;

    if (ulTemplateFlags & FILL_ENTRY)
    {
        ULONG ulCount;
        LPSPropValue lpspv = NULL;

        /*
         *  Copy all the properties from my object to the propdata
         */
        hr = lpABUser->lpVtbl->GetProps(lpABUser,
            NULL,
            0,      /* ansi */
            &ulCount,
            &lpspv);

        if (HR_FAILED(hr))
            goto err;

        hr = lpPropData->lpVtbl->SetProps(lpPropData,
            ulCount,
            lpspv,
            NULL);

        lpFreeBuff(lpspv);

        if (HR_FAILED(hr))
            goto err;
    }

    /*
     *  AddRef lpPropData so we can use it after we return
     */

    (void)lpPropData->lpVtbl->AddRef(lpPropData);

    InitializeCriticalSection(&lpTID->cs);

    /*  We must AddRef the lpABPLogon object since we will be using it
     */
    lpABPLogon->lpVtbl->AddRef(lpABPLogon);

    *lppMAPIPropNew = (LPVOID) lpTID;

out:

    DebugTraceResult(HrNewTID, hr);
    return hr;

err:

    if (lpABUser)
        lpABUser->lpVtbl->Release(lpABUser);

    lpFreeBuff(lpTID);

    goto out;
}

/*
 -  TID_Release
 -
 *  Releases the TID object.  The main difference between this
 *  Release and WRAP_Release() is that it also has to release the
 *  lpABUser object.
 *
 *  Note the Release on the lpPropData.  This required because of
 *  WRAP's implementation of IUnknown (which this object reuses).
 */

STDMETHODIMP_(ULONG) TID_Release(LPTID lpTID)
{
    LONG lcInit;

    /*
     * Check to see if it's large enough to hold this object
     */
    if (IsBadReadPtr(lpTID, sizeof(TID)))
    {
        /*
         *  I'm not looking at an object that I expect to be.
         */
        return 1;
    }

    /*
     *  Check to see that it's TIDs vtbl
     */
    if (lpTID->lpVtbl != &vtblTID)
    {
        /*
         *  It's big enough but it's got the wrong vtbl.
         */
        return 1;
    }

    /*
     *  Release the mapi property object
     */
    lpTID->lpPropData->lpVtbl->Release(
        lpTID->lpPropData);

    EnterCriticalSection(&lpTID->cs);
    lcInit = --lpTID->lcInit;
    LeaveCriticalSection(&lpTID->cs);

    if (lcInit == 0)
    {
        /*
         *  Release the ABUser object
         */
        lpTID->lpABUser->lpVtbl->Release(
            lpTID->lpABUser);

        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpTID->lpABLogon)
        {
            lpTID->lpABLogon->lpVtbl->Release(lpTID->lpABLogon);
            lpTID->lpABLogon = NULL;
        }

        /*
         *  Clean up the critical section
         */
        DeleteCriticalSection(&lpTID->cs);

        /*
         * Need to free the object
         */
        lpTID->lpFreeBuff(lpTID);
        return 0;
    }

    return lcInit;
}

/*
 -  TID_OpenProperty
 -
 *  Satisfies the object that are needed to support the "Options" details pane
 *  associated with the MailUser object from ABUSER.C.
 *
 *  Note:  We are masking error strings that might be possible to get from the
 *  lpABUser object.  Since (for the most part) the only errors that can be returned
 *  from this object are resource failure types, it wouldn't be of much use to the
 *  user.
 */

STDMETHODIMP
TID_OpenProperty(LPTID lpTID,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    /*
     *  Check to see if it's large enough to hold this object
     */
    if (IsBadReadPtr(lpTID, sizeof(TID)))
    {
        /*
         *  No vtbl found
         */
        return ResultFromScode(E_INVALIDARG);
    }

    /*
     *  Check to see that it's TIDs vtbl
     */
    if (lpTID->lpVtbl != &vtblTID)
    {
        /*
         *  It's big enough but it's got the wrong vtbl.
         */
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulInterfaceOptions & ~MAPI_UNICODE )
    {
        DebugTraceArg( TID_OpenProperty, "unknown flags" );
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        DebugTraceArg( TID_OpenProperty, "bad character width" );
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }
    /*
     *  Don't want to check any other parameters here.
     *  Calls down to wrapped objects will do this for
     *  me.
     */

    switch (ulPropTag)
    {
        case PR_LISTBOX_TABLE:
        case PR_DDLISTBOX_TABLE:
        case PR_COMBOBOX_TABLE:
        {
            hResult = lpTID->lpABUser->lpVtbl->OpenProperty(
                lpTID->lpABUser,
                ulPropTag,
                lpiid,
                ulInterfaceOptions,
                ulFlags,
                lppUnk);
            break;

        }

        default:
        {
            hResult = lpTID->lpPropData->lpVtbl->OpenProperty(
                lpTID->lpPropData,
                ulPropTag,
                lpiid,
                ulInterfaceOptions,
                ulFlags,
                lppUnk);
            break;
        }
    }

    DebugTraceResult(TID_OpenProperty, hResult);
    return hResult;
}


WRAP.C

/***********************************************************************
 *
 *  WRAP.C
 *
 *  Sample Address Book Wrap object
 *  This file contains the code for implementing the Sample AB
 *  WRAP object.
 *
 *  This file contains methods with "default" actions for objects which
 *  expose IUnknown and/or IMAPIProp.  These "default" actions are to call
 *  the appropriate method of a "wrapped" object that is a member of this
 *  object.  Various methods in this file are used throughout this sample
 *  provider and are especially useful with the objects that implement template
 *  ids (see TID.C and OOTID.C).
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include "abp.h"

typedef struct _WRAP {

    WRAP_Vtbl * lpVtbl;

    SAB_Wrapped;

} WRAP, *LPWRAP;

/*********************************************************************
 *
 *  The actual Wrapped IMAPIProp methods
 *
 */

STDMETHODIMP
WRAP_QueryInterface(LPWRAP lpWRAP,
                    REFIID lpiid,
                    LPVOID * lppNewObj)
{
    HRESULT hr;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        return ResultFromScode(E_INVALIDARG);
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, QueryInterface)+sizeof(WRAP_QueryInterface_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */
        return ResultFromScode(E_INVALIDARG);
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_QueryInterface != lpWRAP->lpVtbl->QueryInterface)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        return ResultFromScode(E_INVALIDARG);
    }

    EnterCriticalSection(&lpWRAP->cs);
    /*  Call the internal prop interface */

    hr = lpWRAP->lpPropData->lpVtbl->QueryInterface(
        lpWRAP->lpPropData,
        lpiid,
        lppNewObj);

    /*  If this object is successful in QI'ing and it returns exactly the
     *  same object, then I need to AddRef my own "wrapper" object so that
     *  my release code works correctly AND replace the *lppNewObj with the
     *  "wrapper" object.
     */
    if (!HR_FAILED(hr) && (lpWRAP->lpPropData == *lppNewObj))
    {
        ++lpWRAP->lcInit;
        *lppNewObj = lpWRAP;
    }

    LeaveCriticalSection(&lpWRAP->cs);

    return hr;

}

STDMETHODIMP_(ULONG) WRAP_AddRef(LPWRAP lpWRAP)
{
    ULONG ulRet;
    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        return 1;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, AddRef)+sizeof(WRAP_AddRef_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */
        return 1;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_AddRef != lpWRAP->lpVtbl->AddRef)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        return 1;
    }

    EnterCriticalSection(&lpWRAP->cs);
    ++lpWRAP->lcInit;

    /*  Call the internal prop interface */

    ulRet = lpWRAP->lpPropData->lpVtbl->AddRef(lpWRAP->lpPropData);

    LeaveCriticalSection(&lpWRAP->cs);

    return ulRet;
}

STDMETHODIMP_(ULONG) WRAP_Release(LPWRAP lpWRAP)
{
    long lcInit;
    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        return 1;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, Release)+sizeof(WRAP_Release_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */
        return 1;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_Release != lpWRAP->lpVtbl->Release)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */
        return 1;
    }

    EnterCriticalSection(&lpWRAP->cs);

    /*
     *  Release the imapiprop object
     */

    lpWRAP->lpPropData->lpVtbl->Release(
        lpWRAP->lpPropData);

    lcInit = --lpWRAP->lcInit;

    LeaveCriticalSection(&lpWRAP->cs);

    if (lcInit == 0)
    {
        /*
         *  Release our reference to the ABLogon object.
         */
        if (lpWRAP->lpABLogon)
        {
            lpWRAP->lpABLogon->lpVtbl->Release(lpWRAP->lpABLogon);
            lpWRAP->lpABLogon = NULL;
        }

        /*
         *  Get rid of my critical section
         */
        DeleteCriticalSection(&lpWRAP->cs);

        /*
         *  Set the Jump table to NULL.  This way the client will find out
         *  real fast if it's calling a method on a released object.  That is,
         *  the client will crash.  Hopefully, this will happen during the
         *  development stage of the client.
         */

        lpWRAP->lpVtbl = NULL;

        /*
         *  Need to free the object
         */

        lpWRAP->lpFreeBuff(lpWRAP);
        return 0;
    }

    return lcInit;
}

STDMETHODIMP
WRAP_GetLastError(  LPWRAP lpWRAP,
                    HRESULT hError,
                    ULONG ulFlags,
                    LPMAPIERROR FAR * lppMapiError )
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
        offsetof(WRAP_Vtbl, GetLastError)+sizeof(WRAP_GetLastError_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_GetLastError != lpWRAP->lpVtbl->GetLastError)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    if ( ulFlags & ~MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
        return hResult ;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->GetLastError(
        lpWRAP->lpPropData,
        hError,
        ulFlags,
        lppMapiError );
}

/* IProperty */

STDMETHODIMP
WRAP_SaveChanges(   LPWRAP lpWRAP,
                    ULONG ulFlags)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, SaveChanges)+sizeof(WRAP_SaveChanges_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_SaveChanges != lpWRAP->lpVtbl->SaveChanges)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    hResult = lpWRAP->lpPropData->lpVtbl->SaveChanges(
        lpWRAP->lpPropData,
        ulFlags);

    return hResult;

}

STDMETHODIMP
WRAP_GetProps(  LPWRAP lpWRAP,
                LPSPropTagArray lpPropTagArray,
                ULONG ulFlags,
                ULONG * lpcValues,
                LPSPropValue * lppPropArray)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, GetProps)+sizeof(WRAP_GetProps_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_GetProps != lpWRAP->lpVtbl->GetProps)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );

        return hResult;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->GetProps(
        lpWRAP->lpPropData,
        lpPropTagArray,
        ulFlags,
        lpcValues,
        lppPropArray);

}

STDMETHODIMP
WRAP_GetPropList(   LPWRAP lpWRAP,
                    ULONG ulFlags,
                    LPSPropTagArray * lppPropTagArray)
{
    HRESULT hResult = hrSuccess;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, GetPropList)+sizeof(WRAP_GetPropList_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_GetPropList != lpWRAP->lpVtbl->GetPropList)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );

        return hResult;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->GetPropList(
        lpWRAP->lpPropData,
        ulFlags,
        lppPropTagArray);

}

STDMETHODIMP
WRAP_OpenProperty(  LPWRAP lpWRAP,
                    ULONG ulPropTag,
                    LPCIID lpiid,
                    ULONG ulInterfaceOptions,
                    ULONG ulFlags,
                    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, OpenProperty)+sizeof(WRAP_OpenProperty_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_OpenProperty != lpWRAP->lpVtbl->OpenProperty)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    if ( ulInterfaceOptions & ~MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
        return hResult;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        return hResult;
    }

    hResult = lpWRAP->lpPropData->lpVtbl->OpenProperty(
        lpWRAP->lpPropData,
        ulPropTag,
        lpiid,
        ulInterfaceOptions,
        ulFlags,
        lppUnk);

    return hResult;

}

STDMETHODIMP
WRAP_SetProps(  LPWRAP lpWRAP,
                ULONG cValues,
                LPSPropValue lpPropArray,
                LPSPropProblemArray * lppProblems)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, SetProps)+sizeof(WRAP_SetProps_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_SetProps != lpWRAP->lpVtbl->SetProps)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->SetProps(
        lpWRAP->lpPropData,
        cValues,
        lpPropArray,
        lppProblems);

}

STDMETHODIMP
WRAP_DeleteProps(   LPWRAP lpWRAP,
                    LPSPropTagArray lpPropTagArray,
                    LPSPropProblemArray * lppProblems)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, DeleteProps)+sizeof(WRAP_DeleteProps_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_DeleteProps != lpWRAP->lpVtbl->DeleteProps)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->DeleteProps(
        lpWRAP->lpPropData,
        lpPropTagArray,
        lppProblems);

}

STDMETHODIMP
WRAP_CopyTo(LPWRAP lpWRAP,
            ULONG ciidExclude,
            LPCIID rgiidExclude,
            LPSPropTagArray lpExcludeProps,
            ULONG ulUIParam,
            LPMAPIPROGRESS lpProgress,
            LPCIID lpInterface,
            LPVOID lpDestObj,
            ULONG ulFlags,
            LPSPropProblemArray FAR * lppProblems)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, CopyTo)+sizeof(WRAP_CopyTo_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_CopyTo != lpWRAP->lpVtbl->CopyTo)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->CopyTo(
        lpWRAP->lpPropData,
        ciidExclude,
        rgiidExclude,
        lpExcludeProps,
        ulUIParam,
        lpProgress,
        lpInterface,
        lpDestObj,
        ulFlags,
        lppProblems);
}

STDMETHODIMP
WRAP_CopyProps(LPWRAP lpWRAP,
            LPSPropTagArray lpIncludeProps,
            ULONG ulUIParam,
            LPMAPIPROGRESS lpProgress,
            LPCIID lpInterface,
            LPVOID lpDestObj,
            ULONG ulFlags,
            LPSPropProblemArray FAR * lppProblems)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, CopyProps)+sizeof(WRAP_CopyProps_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_CopyProps != lpWRAP->lpVtbl->CopyProps)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->CopyProps(
        lpWRAP->lpPropData,
        lpIncludeProps,
        ulUIParam,
        lpProgress,
        lpInterface,
        lpDestObj,
        ulFlags,
        lppProblems);
}

STDMETHODIMP
WRAP_GetNamesFromIDs(   LPWRAP lpWRAP,
                        LPSPropTagArray * lppPropTags,
                        LPGUID lpPropSetGuid,
                        ULONG ulFlags,
                        ULONG * lpcPropNames,
                        LPMAPINAMEID ** lpppPropNames)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, GetNamesFromIDs)+sizeof(WRAP_GetNamesFromIDs_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_GetNamesFromIDs != lpWRAP->lpVtbl->GetNamesFromIDs)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->GetNamesFromIDs(
        lpWRAP->lpPropData,
        lppPropTags,
        lpPropSetGuid,
        ulFlags,
        lpcPropNames,
        lpppPropNames);
}

STDMETHODIMP
WRAP_GetIDsFromNames(   LPWRAP lpWRAP,
                        ULONG cPropNames,
                        LPMAPINAMEID * lppPropNames,
                        ULONG ulFlags,
                        LPSPropTagArray * lppPropTags)
{
    HRESULT hResult;

    /*
     *  Check to see if it has a lpVtbl object member
     */
    if (IsBadReadPtr(lpWRAP, offsetof(WRAP, lpVtbl)+sizeof(WRAP_Vtbl *)))
    {
        /*
         *  No jump table found
         */
        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see that the Vtbl is large enough to include this method
     */
    if (IsBadReadPtr(lpWRAP->lpVtbl,
                     offsetof(WRAP_Vtbl, GetIDsFromNames)+sizeof(WRAP_GetIDsFromNames_METHOD *)))
    {
        /*
         *  Jump table not derived from IUnknown
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    /*
     *  Check to see if the method is the same
     */
    if (WRAP_GetIDsFromNames != lpWRAP->lpVtbl->GetIDsFromNames)
    {
        /*
         *  Wrong object - the object passed doesn't have this
         *  method.
         */

        hResult = ResultFromScode(E_INVALIDARG);

        return hResult;
    }

    return lpWRAP->lpPropData->lpVtbl->GetIDsFromNames(
        lpWRAP->lpPropData,
        cPropNames,
        lppPropNames,
        ulFlags,
        lppPropTags);
}


README.TXT

Sample Message Hook

The Manager.Sh sample is a sample spooler message hook provider (SMH32.DLL).
The sample spooler hook is an example of a reasonably clever extension to
MAPI which uses the documented MAPI spooler message hook interfaces to
categorize and archive sent and received mail.

Installation
------------

The sample spooler hook is shipped in source form for 32-bit platforms only.
You must build SMH32.DLL first. For the DLL to build successfully, you must
have an SDK that includes the import libraries for the Windows 95 common
controls installed. For the DLL to run successfully on Windows NT 3.5, the
Windows 95 compatible common controls must be installed. Windows NT 3.51
includes these controls.

After building SMH32.DLL, copy it to the system directory of the target
machine, then use the mergeini utility to merge smh.inf into mapisvc.inf.

For example:

    copy smh32.dll c:\windows\system
    mergeini.exe -m .\smh.inf

Start the control panel and start the Mail and Fax applet. Select "Add..."
on the Services page of Profile Properties and double-click Sample Mail
Handler.

Configuration
-------------

The sample message hook has a three-page property sheet interface for
interactive configuration, which is accessible through service provider
logon, through the ServiceEntry call, and through the
IMAPIStatus::SettingsDialog method.

When you first add the sample message hook to the profile configuration,
you will need to log off the profile if you are already logged in, and
then log in again before the change will take effect.

General Page
------------

Incoming mail -- Use the "Enable filtering of inbound messages" checkbox
to start inbound/outbound mail "filter"ing. Use the "Add unread messages
folder to hierarchy" checkbox to create a search folder containing all
unread messages for easier access.

Sent mail -- Use the "Archive sent mail into monthly subfolders" checkbox
to instruct the hook processor to categorize sent mail by date and to use
and create subfolders based on the month of submission to contain these
messages. Use the "Archive sent mail monthly folders by year" checkbox to
further organize the archives by year (e.g. sent mail\year\month folder
hierarchy).

Deleted mail -- Use the "Archive deleted mail into monthly subfolders"
checkbox to instruct the hook processor to categorize deleted mail by date
and to use and create subfolders based on the month of creation to contain
these messages. Use the "Archive deleted mail monthly folders by year"
checkbox to further organize the archives by year
(e.g. deleted mail\year\month folder hierarchy).

Filters Page
------------

This page displays a listbox containing all the filters/actions which have
been defined for this profile. The sample hook processor traverses this
list from top to bottom in determining the disposition of a newly
sent/received message. The first rule that "fires" will be the only one
which is applied to the message in question.

To add a filter rule, use the "New" button. To change a rule, select it
and use the "Edit" button. To delete a rule, select it and use the "Delete"
button. To arrange the ordering of rules, select the rule whose position in
the list needs changing and use the up/down arrow buttons to make the
appropriate change.

When you select "New" or "Edit", the filter description dialog will be
presented to you. In this dialog you should enter a display name for the
filter rule that can be used for the listbox, select one of the filtering
criteria (self-explanatory). Enter the appropriate string in "Value contains"
(in the case of recipients, the filter compares both the display name and
the email address), the display name (as shown in the left-hand pane of the
Exchange mail client) of the target store in the "Target Store" field, and
the display name (again as shown in the Exchange mail client) of the folder
to which you want mail matching this filter rule to be copied in the "Target
Folder" field. The hook provider will create folders if need be; to specify
folders in a hierarchy, use a backslash character (e.g. My Mail\Clever
Sayings). To get the negative effect (ie this rule fires for a message not
matching the criteria), set the "Filter messages that do not match this
criteria" checkbox. To disable a filter without deleting it, you set the
"Discontinue the filtering of messages matching this criteria" checkbox. To
choose archiving options for the filter (like in the Sent Mail / Deleted Mail
option above) you may set the appropriate checkboxes for the rule.

Exclusions Page
---------------

This page displays a listbox containing all the message classes for which
filtering is disabled. The sample hook processor traverses this list from top
to bottom in determining whether it should filter a newly sent/received
message. If any message class matches, rules processing will not occur for
that message.

To add a message class, use the New button. To delete a rule, select it and
use the Delete button. To arrange the ordering of rules, select the rule
whose position in the list needs changing and use the up/down arrow buttons
to make the appropriate change.

Known Problems
--------------

Some of the sample hook's features will not work if you use a tightly-coupled
store and transport as your default message store. The archiving of sent and
received mail will not work, because the hook entry points are never called
when the message store server receives mail. Other features, such as archiving
of deleted messages and auto-reply, will work normally.

The hook only can be built on/for 32-bit platforms (this is a limitation of
the sample provider, not of the hook interface itself). The Unread Messages
folder doesn't always seem to be accessable from the Exchange client. The
"Filter messages that do not match this criteria" checkbox doesn't always
seem to work.


_SMH.H

/*
 *  _ S M H . H
 *
 *  Sample mail handling hook
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

/* RICHEDIT OLE callback object method definitions */
/* IRichEditOleCallback Interface --------------------------------------------- */

#define MAPI_IRICHEDITOLECALLBACK_METHODS(IPURE)                        \
    MAPIMETHOD(GetNewStorage)                                           \
        (THIS_  LPSTORAGE FAR * lppstg) IPURE;                          \
    MAPIMETHOD(GetInPlaceContext)                                       \
        (THIS_  LPOLEINPLACEFRAME FAR * ppipframe,                      \
                LPOLEINPLACEUIWINDOW FAR * ppipuiDoc,                   \
                LPOLEINPLACEFRAMEINFO pipfinfo) IPURE;                  \
    MAPIMETHOD(ShowContainerUI)                                         \
        (THIS_  BOOL fShow) IPURE;                                      \
    MAPIMETHOD(QueryInsertObject)                                       \
        (THIS_  LPCLSID pclsid,                                         \
                LPSTORAGE pstg,                                         \
                LONG cp) IPURE;                                         \
    MAPIMETHOD(DeleteObject)                                            \
        (THIS_  LPOLEOBJECT poleobj) IPURE;                             \
    MAPIMETHOD(QueryAcceptData)                                         \
        (THIS_  LPDATAOBJECT lpdataobj,                                 \
                CLIPFORMAT FAR * lpcfFormat,                            \
                DWORD reco,                                             \
                BOOL fReally,                                           \
                HGLOBAL hMetaPict) IPURE;                               \
    MAPIMETHOD(ContextSensitiveHelp)                                    \
        (THIS_  BOOL fEnterMode) IPURE;                                 \
    MAPIMETHOD(GetClipboardData)                                        \
        (THIS_  CHARRANGE FAR * lpchrg,                                 \
                DWORD reco,                                             \
                LPDATAOBJECT FAR * lppdataobj);                         \
    MAPIMETHOD(GetDragDropEffect)                                       \
        (THIS_  BOOL fDrag,                                             \
                DWORD grfKeyState,                                      \
                LPDWORD pdwEffect) IPURE;                               \
    MAPIMETHOD(GetContextMenu)                                          \
        (THIS_  WORD seltype,                                           \
                LPOLEOBJECT lpoleobj,                                   \
                CHARRANGE FAR * lpchrg,                                 \
                HMENU FAR * lphmenu) IPURE;                             \

/*
/*  Object Vtable definition and declairation
 */
#undef  INTERFACE
#define INTERFACE struct _REOC
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, REOC_)
        MAPI_IUNKNOWN_METHODS (IMPL)
        MAPI_IRICHEDITOLECALLBACK_METHODS (IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, REOC_)
        MAPI_IUNKNOWN_METHODS (IMPL)
        MAPI_IRICHEDITOLECALLBACK_METHODS (IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)
DECLARE_MAPI_INTERFACE (REOC_)
{
    MAPI_IUNKNOWN_METHODS (IMPL)
    MAPI_IRICHEDITOLECALLBACK_METHODS (IMPL)
};

/*
 *  Object Vtable definition and declairation
 */
#undef  INTERFACE
#define INTERFACE struct _SMH
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, SMH_)
        MAPI_IUNKNOWN_METHODS (IMPL)
        MAPI_ISPOOLERHOOK_METHODS (IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, SMH_)
        MAPI_IUNKNOWN_METHODS (IMPL)
        MAPI_ISPOOLERHOOK_METHODS (IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)
DECLARE_MAPI_INTERFACE (SMH_)
{
    MAPI_IUNKNOWN_METHODS (IMPL)
    MAPI_ISPOOLERHOOK_METHODS (IMPL)
};

/*
 *  Misc. Defines
 *
 *  GROW_SIZE is the incremental amount to grow buffers that hold arrays
 *  of structures.  This is used to reduce the number of allocations in
 *  certain situations (ie. configuration)
 */
#define GROW_SIZE   4

/*
 *  cStoMax is the maximum number of stores per profile supported by SMH.
 *  This limit is purely an imposed limit and can be increased or
 *  decreased with no other changes required.
 */
#define cStoMax     64

/*
 *  cchNameMax is used to limit the names and values of filters.
 */
#define cchNameMax  MAX_PATH

/*
 *  Store table structure definitions
 *
 *  STOENTRY
 *      This structure is used as a part of the STOTABLE structure.
 *      The union component to the structure allows SMH to cast an
 *      LPSPropValue pointer to a LPSTOENTRY where the first property
 *      is dropped in favor of storing an LPMDB.
 *
 *  STO
 *      This stucture is defined such that SMH can cast an SRow pointer
 *      to an LPSTO.  Again, this construct is used extensively by the
 *      STOTABLE implementation.
 *
 *  STOTABLE
 *      This stucture can be mapped to an SRowSet.  The store table
 *      implementation uses this to let MAPI allocate the memory needed
 *      for keeping pointers to open stores.
 */
typedef struct _STOENTRY
{
    union
    {
        LPMDB           lpmdb;
        SPropValue      valPad;
    };
    SPropValue          lpProps[];

} STOENTRY, FAR * LPSTOENTRY;

typedef struct _STO
{
    ULONG               ulReserved;
    ULONG               cValues;
    union
    {
        LPSTOENTRY      lpstoe;
        LPSPropValue    lpProps;
    };

} STO, FAR * LPSTO;

typedef struct _STOTABLE
{
    ULONG               cSto;
    STO                 aSto[cStoMax];

} STOTABLE, FAR * LPSTOTABLE;

/*
 *  Archive bucket structures definitions
 *
 *  DFT
 *      The DFT structure contains two FILETIMEs that define the delta of
 *      time supported by the archive folder.
 *
 *  BKIT
 *      The BKIT structure contains all the information required to
 *      process the archival of a message into that bucket.
 *
 *      The EntryID of the parent folder, the year folder, and the
 *      monthly folder are all cashed as is the MAPIFOLDER object.  The
 *      DFT for the year and month are cached as well.
 */
typedef struct _DFT
{
    FILETIME            ftStart;
    FILETIME            ftEnd;

} DFT, FAR * LPDFT;

typedef struct _BKIT
{
    ULONG               cbeid;
    LPENTRYID           lpeid;
    LPMAPIFOLDER        lpfldr;
    DFT                 dft;

    ULONG               cbeidYr;
    LPENTRYID           lpeidYr;
    LPMAPIFOLDER        lpfldrYr;
    DFT                 dftYr;

    ULONG               cbeidParent;
    LPENTRYID           lpeidParent;
    LPMAPIFOLDER        lpfldrParent;

} BKIT, FAR * LPBKIT;

/*
 *  SMH stucture definitions
 *
 *  WB
 *      The WB (wastebasket) structure contains the information
 *      required to archive deleted mail on any given message store
 *
 *  RULE
 *      The RULE structure contains the information required to filter an
 *      inbound message into the desired target folder
 *
 *  OOF
 *      The OOF structure contains the information required to generate
 *      out-of-office responses to incoming messages
 *
 *  SMH
 *      The SMH object is supports the ISpoolerHook interface and is the
 *      object handed to mapi to facilitate all message filtering.
 *
 */
typedef struct _WB
{
    struct _WB FAR *    wbNext;         /* next pointer */
    struct _SMH FAR *   lpsmh;          /* owning SMH object */

    LPMDB               lpmdb;          /* MDB on which this WB applies */
    LPMAPITABLE         lptbl;          /* contents table of the wastebasket */
    LPMAPIFOLDER        lpfldr;         /* folder object of the wastebasket */
    LPSPropValue        lpvalEid;       /* entryid of the wastebasket */
    ULONG               ulAdvz;         /* connection ID returned from Advise() */
    BKIT                bkit;           /* bucket cache for this WB */
    HANDLE              ht;             /* filtering thread */
    BOOL                fBail;          /* bailout flag */

} WB, FAR * LPWB;

typedef struct _RULE
{
    struct _RULE FAR *  rlNext;         /* next pointer */

    MAPIUID             muid;           /* profile section UID */
    UINT                rlTyp;          /* rule type */
    ULONG               ulFlags;        /* rule attributes */
    SRestriction FAR *  lpres;          /* recipient rule restriction */
    LPTSTR              lpszData;       /* value to filter against */
    LPTSTR              rgszSnd[3];     /* sound to play */
    LPMDB               lpmdb;          /* MDB owning the target folder */
    LPMAPIFOLDER        lpfldr;         /* target folder for filter */
    LPSPropValue        lpvalEid;       /* entryid of target folder */
    LPTSTR              lpszAnno;       /* auto-response annotation */
    LPBYTE              lpbRTF;         /* auto-response RTF */
    ULONG               cbRTF;          /* auto-response RTF size */
    ULONG               cvalRecip;      /* auto-response recip prop count */
    LPSPropValue        lpvalRecip;     /* auto-response recip */
    BKIT                bkit;           /* bucket cache for rule */

} RULE, FAR * LPRULE;

typedef struct _OOF
{
    LPTABLEDATA         lptad;          /* Table data object */
    LPMAPITABLE         lptbl;          /* Table data view */
    ULONG               cRecips;        /* Count of recips */
    LPTSTR              lpszBody;       /* Oof Body Text */
    LPBYTE              lpbRTF;         /* Body RTF */
    ULONG               cbRTF;          /* RTF size */

} OOF, FAR * LPOOF;

typedef struct _SMH
{
    SMH_Vtbl FAR *      lpVtbl;
    ULONG               lcInit;         /* object refcount */
    HINSTANCE           hinst;          /* DLL instance */
    HANDLE              hevtCfg;        /* configuration event */

    LPMAPISESSION       lpsess;         /* client session */
    LPALLOCATEBUFFER    lpfnAlloc;      /* MAPIAllocateBuffer */
    LPALLOCATEMORE      lpfnAllocMore;  /* MAPIAllocateMore */
    LPFREEBUFFER        lpfnFree;       /* MAPIFreeBuffer */
    MAPIUID             muid;           /* hook provider section UID */

    unsigned int        fCatSm:1;       /* options flags */
    unsigned int        fCatSmByYr:1;
    unsigned int        fCatWb:1;
    unsigned int        fOof:1;
    unsigned int        fAtp:1;
    LPSTOTABLE          lpstotbl;       /* store table */
    SPropValue          valEx;          /* msg class exclustions */
    LPWB                lstWb;          /* list pointer of WBs */
    LPRULE              lstRl;          /* list pointer of rules */
    BKIT                bkitSm;         /* bucket cache for sent mail */
    OOF                 oof;            /* OOF information */

} SMH, FAR * LPSMH;

/*
 *  Configuration dialog structure definition
 *
 *  REOC
 *      The Richedit OLE Callback structure refers to an object that is
 *      used by the configuration dialogs that support RTF edit controls
 *
 *  SCD (SMH Configuration Dialog)
 *      The SMH Configuration Dialog structure is used to pass
 *      important information in and out of the configuration dialogs
 *      and property sheets.
 */
typedef struct _SCD
{
    HINSTANCE           hinst;          /* DLL instance */
    HWND                hwnd;           /* parent window */

    LPALLOCATEBUFFER    lpfnAlloc;      /* MAPIAllocateBuffer */
    LPALLOCATEMORE      lpfnAllocMore;  /* MAPIAllocateMore */
    LPFREEBUFFER        lpfnFree;       /* MAPIFreeBuffer */
    LPMALLOC            lpmalloc;       /* pointer to LPMALLOC obj */
    LPMAPISUP           lpsup;          /* pointer to support obj */
    LPPROVIDERADMIN     lpadmin;        /* pointer to profile admin obj */
    LPPROFSECT          lpsec;          /* profile section obj */

    LPMAPISESSION       lpsess;         /* session pointer used in filters */

    TCHAR               rgch[cchNameMax];
    LPSPropValue        lpval;          /* current set of property values */

    UINT                crl;            /* count of current rules */
    UINT                crlMax;         /* count of maximum rules */
    LPTSTR FAR *        lppsz;          /* pointer to array of names */
    LPSBinary           lpbin;          /* pointer to array of muids */

    ULONG               ulFlags;        /* configuration flags */
    SCODE               sc;             /* dialog failure error code */

} SCD, FAR * LPSCD;

typedef struct _FORMATBAR
{
    LPSCD               lpscd;          /* owning config dialog */
    CHARFORMAT          cf;             /* character format */
    HBRUSH              hbrBtnFace;     /* button face color brush */
    HDC                 hdc;            /* device context */
    HMENU               hmnuColor;      /* color picker menu */
    HWND                hwndEdit;       /* associated edit control */
    HWND                hwndFont;       /* font name combo */
    HWND                hwndFrame;      /* toolbar HWND */
    HWND                hwndSize;       /* font size combo */
    HWND                hwndTb;         /* toolbar */
    HANDLE              hfnt;           /* toolbar font */
    PARAFORMAT          pf;             /* paragraph format */
    UINT                cSize;          /* font entry count */
    WNDPROC             lpfnToolbar;    /* subclassed toolbar proc */
    struct _REOC FAR *  lpreoc;         /* richedit ole callback */

} FORMATBAR, FAR * LPFORMATBAR;

typedef struct _REOC
{
    REOC_Vtbl FAR *     lpVtbl;
    ULONG               lcInit;         /* object refcount */
    LPSTORAGE           lpstg;          /* root storage obj */
    UINT                cSub;           /* count of sub obj's */
    LPALLOCATEBUFFER    lpfnAlloc;      /* MAPIAllocateBuffer */
    LPALLOCATEMORE      lpfnAllocMore;  /* MAPIAllocateMore */
    LPFREEBUFFER        lpfnFree;       /* MAPIFreeBuffer */
    LPFORMATBAR         lpfb;           /* formatting bar */

} REOC, FAR * LPREOC;

typedef struct _RTFS
{
    LPALLOCATEBUFFER    lpfnAlloc;      /* MAPIAllocateBuffer */
    LPFREEBUFFER        lpfnFree;       /* MAPIFreeBuffer */
    LPBYTE              lpb;            /* stream data */
    LONG                cb;             /* stream data size */
    LONG                cbMax;          /* max stream size */

} RTFS, FAR * LPRTFS;

/*
 *  SMH proptags
 */
#define PR_SMH_FLAGS                    PROP_TAG(PT_LONG,       0x6600)
#define PR_SMH_RULES                    PROP_TAG(PT_MV_BINARY,  0x6601)
#define PR_SMH_EXCLUSIONS               PROP_TAG(PT_MV_TSTRING, 0x6602)
#define PR_SMH_EXCLUSIONS_A             PROP_TAG(PT_MV_STRING8, 0x6602)
#define PR_SMH_EXCLUSIONS_W             PROP_TAG(PT_MV_UNICODE, 0x6602)
#define PR_SMH_RULE_NAMES               PROP_TAG(PT_MV_TSTRING, 0x6603)
#define PR_SMH_RULE_NAMES_A             PROP_TAG(PT_MV_STRING8, 0x6603)
#define PR_SMH_RULE_NAMES_W             PROP_TAG(PT_MV_UNICODE, 0x6603)
#define PR_SMH_OOF_ENABLED              PROP_TAG(PT_BOOLEAN,    0x6604)
#define PR_SMH_OOF_TEXT                 PROP_TAG(PT_TSTRING,    0x6605)
#define PR_SMH_OOF_TEXT_A               PROP_TAG(PT_STRING8,    0x6605)
#define PR_SMH_OOF_TEXT_W               PROP_TAG(PT_UNICODE,    0x6605)
#define PR_SMH_OOF_RTF                  PROP_TAG(PT_BINARY,     0x6606)
#define PR_SMH_SOUND_SCHEMES            PROP_TAG(PT_MV_BINARY,  0x6607)
#define PR_SMH_REPFWD_SCHEMES           PROP_TAG(PT_MV_BINARY,  0x6608)

/*
 *  Value for PR_SMH_VERSION
 */
#define MAKE_SMH_VERSION(_mj,_mn)       (((ULONG)(0x0000FFFF & _mj) << 16) | \
                                            (ULONG)(0x0000FFFF & _mn))
#define SMH_VERSION                     MAKE_SMH_VERSION (1,0)

/*
 *  Values for PR_SMH_FLAGS
 */
#define SMH_ADD_TO_PAB                  ((ULONG)0x00000001)
#define SMH_FILTER_DELETED              ((ULONG)0x00000010)
#define SMH_FILTER_DELETED_YR           ((ULONG)0x00000020)
#define SMH_FILTER_INBOUND              ((ULONG)0x00000100)
#define SMH_FILTER_SENTMAIL             ((ULONG)0x00001000)
#define SMH_FILTER_SENTMAIL_YR          ((ULONG)0x00002000)
#define SMH_UNREAD_VIEWER               ((ULONG)0x00010000)

/*
 *  Filtering rule proptags
 */
#define PR_RULE_TYPE                    PROP_TAG(PT_LONG,       0x6610)
#define PR_RULE_DATA                    PROP_TAG(PT_BINARY,     0x6611)
#define PR_RULE_FLAGS                   PROP_TAG(PT_LONG,       0x6612)
#define PR_RULE_TARGET_ENTRYID          PROP_TAG(PT_BINARY,     0x6613)
#define PR_RULE_TARGET_PATH             PROP_TAG(PT_TSTRING,    0x6614)
#define PR_RULE_TARGET_PATH_A           PROP_TAG(PT_STRING8,    0x6614)
#define PR_RULE_TARGET_PATH_W           PROP_TAG(PT_UNICODE,    0x6614)
#define PR_RULE_STORE_ENTRYID           PROP_TAG(PT_BINARY,     0x6615)
#define PR_RULE_STORE_DISPLAY_NAME      PROP_TAG(PT_TSTRING,    0x6616)
#define PR_RULE_STORE_DISPLAY_NAME_A    PROP_TAG(PT_STRING8,    0x6616)
#define PR_RULE_STORE_DISPLAY_NAME_W    PROP_TAG(PT_UNICODE,    0x6616)
#define PR_RULE_SOUND_NORMAL            PROP_TAG(PT_TSTRING,    0x6617)
#define PR_RULE_SOUND_NORMAL_A          PROP_TAG(PT_STRING8,    0x6617)
#define PR_RULE_SOUND_NORMAL_W          PROP_TAG(PT_UNICODE,    0x6617)
#define PR_RULE_SOUND_HIPRI             PROP_TAG(PT_TSTRING,    0x6618)
#define PR_RULE_SOUND_HIPRI_A           PROP_TAG(PT_STRING8,    0x6618)
#define PR_RULE_SOUND_HIPRI_W           PROP_TAG(PT_UNICODE,    0x6618)
#define PR_RULE_SOUND_LOPRI             PROP_TAG(PT_TSTRING,    0x6619)
#define PR_RULE_SOUND_LOPRI_A           PROP_TAG(PT_STRING8,    0x6619)
#define PR_RULE_SOUND_LOPRI_W           PROP_TAG(PT_UNICODE,    0x6619)

#define PR_RULE_FORWARD_RECIP           PROP_TAG(PT_TSTRING,    0x6620)
#define PR_RULE_FORWARD_RECIP_A         PROP_TAG(PT_STRING8,    0x6620)
#define PR_RULE_FORWARD_RECIP_W         PROP_TAG(PT_UNICODE,    0x6620)
#define PR_RULE_FORWARD_RECIP_ENTRYID   PROP_TAG(PT_BINARY,     0x6621)
#define PR_RULE_REP_FWD_TEXT            PROP_TAG(PT_TSTRING,    0x6624)
#define PR_RULE_REP_FWD_TEXT_A          PROP_TAG(PT_STRING8,    0x6624)
#define PR_RULE_REP_FWD_TEXT_W          PROP_TAG(PT_UNICODE,    0x6624)
#define PR_RULE_REP_FWD_RTF             PROP_TAG(PT_BINARY,     0x6625)

/*
 *  Values for PR_RULE_TYPE
 */
#define RL_ANY_RECIP                    ((UINT)0x0001)
#define RL_BCC_RECIP                    ((UINT)0x0002)
#define RL_BODY                         ((UINT)0x0003)
#define RL_CC_RECIP                     ((UINT)0x0004)
#define RL_HAS_ATTACH                   ((UINT)0x0005)
#define RL_MSG_CLASS                    ((UINT)0x0006)
#define RL_SENDER                       ((UINT)0x0007)
#define RL_SUBJECT                      ((UINT)0x0008)
#define RL_TO_RECIP                     ((UINT)0x0009)
#define RL_TYPE_MAX                     ((UINT)0x0010)

/*
 *  Values for PR_RULE_FLAGS
 */
#define RULE_ARCHIVED                   ((ULONG)0x00000001)
#define RULE_ARCHIVED_BY_YEAR           ((ULONG)0x00000002)
#define RULE_NOT                        ((ULONG)0x00000004)
#define RULE_DELETE                     ((ULONG)0x00000008)
#define RULE_AUTO_RESPONSE              ((ULONG)0x00000010)
#define RULE_AUTO_REPLY                 ((ULONG)0x00000020)
#define RULE_AUTO_FORWARD               ((ULONG)0x00000040)
#define RULE_AUTO_APPEND_ORIG           ((ULONG)0x00000080)
#define RULE_PLAY_SOUNDS                ((ULONG)0x00000100)
#define RULE_NO_MOVE                    ((ULONG)0x10000000)
#define RULE_TERMINAL                   ((ULONG)0x80000000)

/*
 *  Rule configuration flags
 */
#define NEW_RULE                        ((UINT)1)
#define EDIT_RULE                       ((UINT)2)

/* Logon properties */

enum {
    ipProfile,
    ipFlags,
    ipRules,
    ipNames,
    ipOof,
    ipOofEnabled,
    ipOofRtf,
    ipSounds,
    ipRepFwd,
    ipExc,
    cpMax
};

/* Rule properties */

enum {
    ipRLDisp,
    ipRLType,
    ipRLData,
    ipRLFlags,
    ipRLEid,
    ipRLPath,
    ipRLSEid,
    ipRLStore,
    ipRLLoPri,
    ipRLSound,
    ipRLHiPri,
    ipRLFwdRecip,
    ipRLFwdEid,
    ipRLRepFwdRTF,
    ipRLRepFwd,
    cpRLMax
};

enum {
    isndLo,         /*  IMPORTANCE_LOW      */
    isndNorm,       /*  IMPORTANCE_NORMAL   */
    isndHi,         /*  IMPORTANCE_HIGH     */
    csndMax
};

/* Message properties */

enum {
    ipMsgFlgs,
    ipMsgSubj,
    ipMsgSentRep,
    ipMsgSentRepEA,
    ipPriority,
    cpMsgPrps
};

/* Recipient restriction enums */

enum {ivRecip, ivEmail, ivDispNm, cvMax };
enum {iresAnd, iresRecip, iresOr, iresEmail, iresDispNm, cresMax };

/*
 *  Configuration dialogs
 */

typedef HRESULT (STDMETHODCALLTYPE FAR * LPOPENPROFSECT) (
    LPVOID              lpvOPSCtxt,
    LPMAPIUID           lpUID,
    LPCIID              lpInterface,
    ULONG               ulFlags,
    LPPROFSECT FAR *    lppProfSect);

HRESULT
HrUpdateProfileFormat (LPVOID lpvOPSCtxt,
    LPOPENPROFSECT lpfnOpenProfSect,
    LPALLOCATEMORE lpfnMore,
    LPFREEBUFFER lpfnFree,
    LPSPropValue lpvalNew,
    LPSPropValue lpvalOld);

extern LPVOID lpCtl3D;
#if 0
#if !defined (_WIN95)
#define RegDlg3D(_inst)         Ctl3dRegister (_inst);
#define MakeDlg3D(_dlg)         Ctl3dSubclassDlgEx(_dlg, CTL3D_ALL)
#define UnregDlg3D(_inst)       Ctl3dUnregister (_inst);
#else
#define RegDlg3D(_inst)
#define MakeDlg3D(_dlg)
#define UnregDlg3D(_inst)
#endif
#endif

#ifdef  WIN32
#define FWin4Shell()    ((GetVersion() & 0x000000FF) >= 0x04)
#else
#define FWin4Shell()    (FALSE)
#endif

#define FHandleWm(_fn,_dlg,_wm,_wp,_lp) HANDLE_WM_##_wm(_dlg,_wp,_lp,_fn##_##_wm)
HRESULT HrDisplayPropSheets (HINSTANCE hinst, HWND hwnd, LPSCD lpscd);
STDMETHODIMP_(SCODE) ScCreateToolbar (LPSCD lpscd, HWND hwnd, UINT idPeg, BOOL fPropsheet, LPFORMATBAR FAR *);
STDMETHODIMP_(BOOL) EXPORT FDoRTFCommand (HWND hctrl, UINT id, UINT command);
void UpdateFormatBar (HWND hdlg);

SCODE ScExportFilters (LPSCD lpscd, HWND hwnd);
SCODE ScImportFilters (LPSCD lpscd, HWND hwnd, HWND hctrl);

DWORD CALLBACK WriteRTFToBuffer (DWORD dwCookie, LPBYTE lpb, LONG cb, LONG FAR * lpcb);
DWORD CALLBACK ReadRTFFromBuffer (DWORD dwCookie, LPBYTE lpb, LONG cb, LONG FAR * lpcb);

HRESULT HrInitOof (LPSMH lpsmh, LPSPropValue lpvalAnno, LPSPropValue lpvalRTF);

/*
 *  Configuration Events
 */
#ifdef  _WIN32
HRESULT HrGetConfigEvent (HANDLE FAR *);
VOID SignalConfigChanged (VOID);
BOOL FConfigChanged (HANDLE);
#endif

/*
 *  FLpszContainsLpsz()
 *
 *  Purpose:
 *
 *      Finds the given sub-string in the passed in string.
 *
 *  Arguments:
 *
 *      _s  source string
 *      _p  pattern string
 *
 *  Returns:
 *
 *      FALSE iff the string was not found, TRUE otherwise.
 */
#define FLpszContainsLpsz(_s,_p) FRKFindSubpsz(_s,lstrlen(_s),_p,lstrlen(_p),FL_IGNORECASE)

/*
 *  Message Archive Functions
 */
HRESULT
HrArchiveMessage (LPSMH lpsmh,
    LPMESSAGE lpmsg,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    LPBKIT lpbkit,
    BOOL fCatByYear,
    ULONG FAR * lpcbeid,
    LPBYTE FAR * lppeid);

HRESULT
HrArchiveByDate (LPSMH lpsmh,
    FILETIME FAR * lpft,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    LPBKIT lpbkit,
    BOOL fCatByYear,
    ULONG cbeid,
    LPBYTE lpeid);

HRESULT HrInitDeletedMailFilter (LPSMH lpsmh);

BOOL
FRKFindSubpsz (LPSTR pszTarget,
    ULONG cbTarget,
    LPSTR pszPattern,
    ULONG cbPattern,
    ULONG ulFuzzyLevel);

/*
 *  Stores table
 */
HRESULT HrInitStoresTable (LPSMH, LPMAPISESSION);
HRESULT HrOpenMdbFromEid (LPSMH, ULONG, LPENTRYID, LPMDB FAR *);
HRESULT HrOpenMdbFromName (LPSMH, LPTSTR, LPMDB FAR *);
HRESULT HrOpenStoEntry (LPMAPISESSION, LPSTO, LPMDB FAR *);
VOID ReleaseStoresTable (LPSMH);

/*
 *  Automated Response Functions
 */
HRESULT HrGenerateResponse (LPSMH lpsmf, LPRULE lprl, LPMAPIFOLDER lpfldr, LPMESSAGE lpmsg);
VOID ReleaseOof (LPOOF lpoof);

/*
 *  Automated PAB additions
 */
HRESULT HrAddEntriesToPab (LPSMH, LPMESSAGE);

/*
 *  RTF\Richedit Components
 */
#ifdef  _WIN32
#define RICHEDIT_LIB    "RICHED32.DLL"
#else
#define RICHEDIT_LIB    "RICHED.DLL"
#endif

STDMETHODIMP_(SCODE)
ScNewRicheditCallback (LPFORMATBAR lpfb,
    LPALLOCATEBUFFER lpfnAlloc,
    LPALLOCATEMORE lpfnMore,
    LPFREEBUFFER lpfnFree,
    LPREOC FAR * lppreoc);

/*
 *  WM_NOTIFY #defines (taken from WINUSER.H)
 */
#ifdef  WIN16
#define WM_NOTIFY   0x004E
#define HANDLE_WM_NOTIFY(hwnd,wParam,lParam,fn) (fn)((hwnd),(int)(wParam),(NMHDR FAR*)(lParam))

/*
 *  Mapped bitmapi #defines (taken from COMMCTRL.H)
 */
typedef struct
{
    COLORREF from;
    COLORREF to;
} COLORMAP, FAR * LPCOLORMAP;

/*
 *  Toolbar button #defines (taken from COMMCTRL.H)
 */
typedef struct _TBBUTTON {
    int iBitmap;
    int idCommand;
    BYTE fsState;
    BYTE fsStyle;
    BYTE bReserved[2];
    DWORD dwData;
    int iString;
} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;
typedef const TBBUTTON FAR* LPCTBBUTTON;
#define TBSTATE_ENABLED         0x04
#define TBSTYLE_BUTTON          0x00
#define TBSTYLE_SEP             0x01
#define TBSTYLE_CHECK           0x02
#define TB_CHECKBUTTON      (WM_USER + 2)
#define TB_GETITEMRECT      (WM_USER + 29)
#define CCS_TOP         0x00000001L

WINCOMMCTRLAPI HWND WINAPI CreateToolbarEx (HWND hwnd, DWORD ws, UINT wID, int nBitmaps,
    HINSTANCE hBMInst, UINT wBMID, LPCTBBUTTON lpButtons,
    int iNumButtons, int dxButton, int dyButton,
    int dxBitmap, int dyBitmap, UINT uStructSize);

HBITMAP WINAPI CreateMappedBitmap (HINSTANCE hInstance, INT idBitmap, BOOL bDiscardable,
    LPCOLORMAP lpColorMap, INT iNumMaps);
#endif  /* WIN16 */


_PCH.H

/*
 *  _ P C H . H
 *
 *  Sample mail handling hook precompiled header source
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#pragma warning(disable:4001)   /* single line comments */
#pragma warning(disable:4050)   /* different code attributes */
#pragma warning(disable:4054)   /* cast from function pointer to data pointer */
#pragma warning(disable:4100)   /* unreferenced formal parameter */
#pragma warning(disable:4115)   /* named type def in parens */
#pragma warning(disable:4127)   /* conditional expression is constant */
#pragma warning(disable:4200)   /* zero sized array */
#pragma warning(disable:4201)   /* nameless struct/union */
#pragma warning(disable:4204)   /* non-constant aggregate initializer */
#pragma warning(disable:4206)   /* translation unit is empty */
#pragma warning(disable:4209)   /* benign typedef redefinition */
#pragma warning(disable:4213)   /* cast on l-value */
#pragma warning(disable:4214)   /* bit field types other than int */
#pragma warning(disable:4220)   /* varargs matches remaining parameters */
#pragma warning(disable:4505)   /* unreferenced local function removed */
#pragma warning(disable:4514)   /* unreferenced inline function removed */
#pragma warning(disable:4702)   /* unreachable code */
#pragma warning(disable:4704)   /* inline assembler turns off global optimizer */
#pragma warning(disable:4705)   /* statement has no effect */
#pragma warning(disable:4706)   /* assignment within conditional expression */
#pragma warning(disable:4710)   /* function not expanded */

#define STRICT

#include <windows.h>
#include <ole2.h>
#include <string.h>
#include <mapix.h>
#include <mapiwin.h>
#include <mapix.h>
#include <mapidefs.h>
#include <mapicode.h>
#include <mapitags.h>
#include <mapiguid.h>
#include <mapispi.h>
#include <mapihook.h>
#include <mapidbg.h>
#include <mapiutil.h>
#include <mapiwz.h>

#include <windowsx.h>
#include <wrap3d.h>
#include <richedit.h>
#include <prsht.h>

#pragma warning(disable:4001)   /* single line comments */
#pragma warning(disable:4200)   /* zero sized array */

#ifdef WIN16
#define EXPORT __export
#else
#define EXPORT
#endif

#include "_smh.h"
#include "smhrc.h"

#ifdef  WIN16
#include <mmsystem.h>
#endif


SMH.C

/*
 *  S M H . C
 *
 *  Sample mail handling hook
 *
 *  Purpose:
 *
 *      The sample mail handling (SMH) hook illustrates the use of the
 *      MAPI Spooler Hook Provider Interface (ISpoolerHook) and those
 *      parts of the general MAPI API that are available to a spooler
 *      hook implementation.
 *
 *      Specifically, SMH illusttrates the operation of both an inbound
 *      message hook as well as outbound.  SMH also has examples of the
 *      configuration of a spooler hook provider via calls to SMH's
 *      ServiceEntry() and/or through calls from the Profile Wizard.
 *
 *  Features:
 *
 *    Sent Mail:
 *
 *      SMH allows the archiving of outbound messages (sent mail), into a
 *      well defined set of subfolders in the default stores sent mail
 *      folder.  The archive folders are comprised of monthly archive
 *      folders.  The monthly folders can be, optionally, created under a
 *      year based folder in the sent mail folder.  Thus in a typical
 *      message store, a fully archived sent mail folder might have a
 *      hierarchy that looks similar to the following:
 *
 *          Sent Mail
 *              |
 *              |-- 1994
 *              |    |
 *              |    |-- 10 October
 *              |    |-- 11 November
 *              |    |-- 12 December
 *              |
 *              |-- 1995
 *                   |
 *                   |-- 01 January
 *
 *      This allows for a mail user to organize their outgoing mail in
 *      a managible fashion.
 *
 *    Deleted Mail:
 *
 *      SMH allows the archiving of deleted mail in the same fashion as
 *      sent mail can be archived.  This feature helps people who choose
 *      keep their 'deleted' mail accessible.  It should be noted here
 *      that this feature does not make use of the ISpoolerHook
 *      interface, but is an example of what all can be done with spooler
 *      hook providers.
 *
 *    Incoming Mail:
 *
 *      SMH can also 'filter' incoming messages and route the message
 *      directly to folders, other than the default stores inbox, based
 *      on message content.  The user can define any number of filters
 *      that can help organize and manage the email.  Additionally, the
 *      user can specify a sound scheme to be used on message delivery.
 *      This allows the user to specify specific sounds based on message
 *      content and priority.
 *
 *    Unread Search Folders:
 *
 *      Because SMH can filter unread messages deep into a message store
 *      folder hierarchy, SMH can also create a search folder in the root
 *      of the IPM_SUBTREE that searches the entire subtree for unread
 *      messages.
 *
 *    Out-of-office Processing:
 *
 *      SMH can generate OOF notifications for incomming mail.  It will
 *      do it's best to not send multiple OOF's to any single recipient
 *      over the period in which the OOF processing is enabled.  The OOF
 *      engine supports full rich-text.
 *
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

extern SPropTagArray sptRule;
extern SPropTagArray sptConfigProps;

/*
 *  sptMessageProps
 *
 *  These are the properties that are required to check filters against
 *  messages.
 */
const static SizedSPropTagArray (cpMsgPrps, sptMsgPrps) =
{
    cpMsgPrps,
    {
        PR_MESSAGE_FLAGS,
        PR_SUBJECT,
        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_IMPORTANCE
    }
};

/*
 *  vtblSMH
 *
 *  This is the SMH object's vtable.  The table and its functions are
 *  defined by MAPI's ISpoolerHook interface.
 */
static const SMH_Vtbl vtblSMH =
{
    SMH_QueryInterface,
    SMH_AddRef,
    SMH_Release,
    SMH_InboundMsgHook,
    SMH_OutboundMsgHook
};

/*
 *  lpCtl3D
 *
 *  Holds context for the 3D control DLL.
 */

LPVOID lpCtl3D = NULL;

/*
 *  HrInitUnreadSearch()
 *
 *  Purpose:
 *
 *      Inits/creates an 'Unread Messages' search folder in the root of
 *      the given store's IPM_SUBTREE hierarchy.
 *
 *  Arguments:
 *
 *      lpsmh           the sample mail handler object
 *      lpmdb           the store getting the search folder
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrInitUnreadSearch (LPSMH lpsmh)
{
    HRESULT hr;
    ENTRYLIST el = {0};
    LPMAPIFOLDER lpfldr = NULL;
    LPMAPIFOLDER lpfldrUM = NULL;
    LPMDB lpmdb = NULL;
    LPSPropValue lpval = NULL;
    SRestriction res = {0};
    ULONG ulType = 0;
    UINT cerr = 0;
    UINT i;

    for (i = 0; i < lpsmh->lpstotbl->cSto; i++)
    {
        hr = HrOpenStoEntry (lpsmh->lpsess, &lpsmh->lpstotbl->aSto[i], &lpmdb);
        if (!HR_FAILED (hr))
        {
            hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_SUBTREE_ENTRYID, &lpval);
            if (!HR_FAILED (hr))
            {
                hr = lpmdb->lpVtbl->OpenEntry (lpmdb,
                                        lpval->Value.bin.cb,
                                        (LPENTRYID)lpval->Value.bin.lpb,
                                        NULL,
                                        MAPI_MODIFY,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpfldr);
                if (!HR_FAILED (hr))
                {
                    hr = lpfldr->lpVtbl->CreateFolder (lpfldr,
                                        FOLDER_SEARCH,
                                        "Unread Messages",
                                        "Simple Mail Handler: unread message search",
                                        NULL,
                                        MAPI_MODIFY | OPEN_IF_EXISTS,
                                        &lpfldrUM);
                    if (!HR_FAILED (hr))
                    {
                        el.cValues = 1;
                        el.lpbin = &lpval->Value.bin;
                        res.rt = RES_BITMASK;
                        res.res.resBitMask.relBMR = BMR_EQZ;
                        res.res.resBitMask.ulPropTag = PR_MESSAGE_FLAGS;
                        res.res.resBitMask.ulMask = MSGFLAG_READ;
                        hr = lpfldrUM->lpVtbl->SetSearchCriteria (lpfldrUM,
                                        &res,
                                        &el,
                                        RECURSIVE_SEARCH | BACKGROUND_SEARCH | RESTART_SEARCH);
                        UlRelease (lpfldrUM);
                        lpfldrUM = NULL;
                    }
                    UlRelease (lpfldr);
                    lpfldr = NULL;
                }
                (*lpsmh->lpfnFree) (lpval);
                lpval = NULL;
            }
        }
        if (HR_FAILED (hr))
        {
            DebugTrace ("SMH: WARNING: failed to init unread search (store:%d)\n", i);
            cerr++;
        }
    }

    hr = ResultFromScode (cerr ? MAPI_W_ERRORS_RETURNED : S_OK);
    DebugTraceResult (HrInitUnreadSearch(), hr);
    return hr;
}

/*
 *  LpszFindChar()
 *
 *  Purpose:
 *
 *      Finds the given character in the passed in string.  This is an
 *      exact matching model so strings should be normalized to either
 *      upper or lower case if case insensitvity is required.
 *
 *  Arguments:
 *
 *      lpszSrc         source string
 *      ch              character to find
 *
 *  Returns:
 *
 *      NULL iff the char was not found, otherwise, the return is a
 *      pointer to the character in the source string.
 */
LPTSTR
LpszFindChar (LPTSTR lpszSrc, TCHAR ch)
{
    LPTSTR lpsz = lpszSrc;

    if (lpszSrc)
        while (*lpsz && (*lpsz != ch))
            lpsz++;

    return ((lpsz && *lpsz) ? lpsz : NULL);
}

/*
 *  HrCheckExclusions()
 *
 *  Purpose:
 *
 *      Checks the message class against the list of message classes
 *      excluded from filtering.
 *
 *  Arguments:
 *
 *      lpsmh           the smh parent object
 *      lpmsg           the message to check for exclusion
 *
 *  Returns:
 *
 *      (HRESULT)       If the message is to be excluded, then hrSuccess
 *                      is returned, otherwize MAPI_E_NOT_ME indicates it
 *                      is OK to filter the message
 */
HRESULT
HrCheckExclusions (LPSMH lpsmh, LPMESSAGE lpmsg)
{
    SCODE sc = S_OK;
    UINT isz = 0;
    LPSPropValue lpval = NULL;

    if (lpsmh->valEx.ulPropTag == PR_SMH_EXCLUSIONS)
    {
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpval)))
        {
            /*  See if this is in the list of exclusions */

            for (isz = 0; isz < lpsmh->valEx.Value.MVSZ.cValues; isz++)
                if (FLpszContainsLpsz (lpval->Value.LPSZ, lpsmh->valEx.Value.MVSZ.LPPSZ[isz]))
                    break;

            (*lpsmh->lpfnFree) (lpval);
        }
        sc = ((isz == lpsmh->valEx.Value.MVSZ.cValues) ? S_OK : MAPI_E_NOT_ME);
    }

    DebugTraceSc (HrCheckExclusions(), sc);
    return ResultFromScode (sc);
}

/*
 *  HrCheckRule()
 *
 *  Purpose:
 *
 *      Examines a message to see if the current rule applies to the
 *      message.
 *
 *      IMPORTANT: If a rule is of type RL_SUBJECT, RL_FROM, or
 *      RL_ATTACH, then the set of properties required to check for
 *      matches with the message are retained and passed back to the
 *      caller such that subsequent calls to HrCheckRule() will not have
 *      to get those props a second time.  THEREFORE the caller is
 *      responsible for cleaning up any returned property values.
 *
 *  Arguments:
 *
 *      lprl            pointer to the rule
 *      lpmsg           pointer to the message to examine
 *      lppval  [OUT]   buffer containing the target entryid value struct
 *
 *  Returns:
 *
 *      (HRESULT)       If the rule does apply, hrSuccess is returned and
 *                      the lppval parameter will point to a lpval struct
 *                      containing the entryid of the target folder.
 *
 *                      If the rule does not apply, the return value is
 *                      an HRESULT with MAPI_E_NOT_ME as the SCODE.
 *
 *                      Otherwise normal error codes apply.
 */
HRESULT
HrCheckRule (LPSMH lpsmh, LPRULE lprl, LPMESSAGE lpmsg, LPSPropValue FAR * lppval)
{
    HRESULT hr;
    LPMAPITABLE lptbl = NULL;
    LPSPropValue lpval = *lppval;
    LPSPropValue lpvalT = NULL;
    ULONG cval;

    if (!lpval)
    {
        hr = lpmsg->lpVtbl->GetProps (lpmsg,
                                (LPSPropTagArray)&sptMsgPrps,
                                0,
                                &cval,
                                &lpval);
        if (HR_FAILED (hr))
            goto ret;
    }

    /*  Init for failure */

    hr = ResultFromScode (MAPI_E_NOT_ME);

    if ((lprl->rlTyp == RL_TO_RECIP) ||
        (lprl->rlTyp == RL_CC_RECIP) ||
        (lprl->rlTyp == RL_BCC_RECIP) ||
        (lprl->rlTyp == RL_ANY_RECIP))
    {
        hr = lpmsg->lpVtbl->GetRecipientTable (lpmsg, 0, &lptbl);
        if (HR_FAILED (hr))
            goto ret;

        hr = lptbl->lpVtbl->FindRow (lptbl, lprl->lpres, BOOKMARK_BEGINNING, 0);
        UlRelease (lptbl);
        if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND))
            hr = ResultFromScode (MAPI_E_NOT_ME);
    }
    else if (lprl->rlTyp == RL_SUBJECT)
    {
        if (lpval[ipMsgSubj].ulPropTag == PR_SUBJECT)
            if (FLpszContainsLpsz (lpval[ipMsgSubj].Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;
    }
    else if (lprl->rlTyp == RL_SENDER)
    {
        if (lpval[ipMsgSentRep].ulPropTag == PR_SENT_REPRESENTING_NAME)
            if (FLpszContainsLpsz (lpval[ipMsgSentRep].Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;

        if (HR_FAILED (hr))
            if (lpval[ipMsgSentRepEA].ulPropTag == PR_SENT_REPRESENTING_EMAIL_ADDRESS)
                if (FLpszContainsLpsz (lpval[ipMsgSentRepEA].Value.LPSZ, lprl->lpszData))
                    hr = hrSuccess;
    }
    else if (lprl->rlTyp == RL_HAS_ATTACH)
    {
        if (lpval[ipMsgFlgs].ulPropTag == PR_MESSAGE_FLAGS)
            if (lpval[ipMsgFlgs].Value.l & MSGFLAG_HASATTACH)
                hr = hrSuccess;
    }
    else if (lprl->rlTyp == RL_BODY)
    {
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_BODY, &lpvalT)))
        {
            if (FLpszContainsLpsz (lpvalT->Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;

            (*lpsmh->lpfnFree) (lpvalT);
        }
    }
    else if (lprl->rlTyp == RL_MSG_CLASS)
    {
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpvalT)))
        {
            if (FLpszContainsLpsz (lpvalT->Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;

            (*lpsmh->lpfnFree) (lpvalT);
        }
    }

    if (lprl->ulFlags & RULE_NOT)
    {
        if (GetScode (hr) == MAPI_E_NOT_ME)
            hr = hrSuccess;
        else if (hr == hrSuccess)
            hr = ResultFromScode (MAPI_E_NOT_ME);
    }

ret:

    *lppval = lpval;
    DebugTraceResult (HrCheckRule(), hr);
    return hr;
}

/*
 *  HrFolderFromPath()
 *
 *  Purpose:
 *
 *      Takes a IPM root-based path string and returns a folder
 *      corresponding to the path given.  The '\' character is the path
 *      separator.  And non-existing folders are created as a psrt of the
 *      process.
 *
 *  Arguments:
 *
 *      lpsmh               pointer to smh parent object
 *      lpmdb               store in which the path is to exist
 *      lpszPath            the root-based path to use
 *      lppfldr     [OUT]   buffer to place target folder
 *      lppvalEid   [OUT]   buffer for target entryid value struct pointer
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrFolderFromPath (LPSMH lpsmh,
    LPMDB lpmdb,
    LPTSTR lpszPath,
    LPMAPIFOLDER FAR * lppfldr,
    LPSPropValue FAR * lppvalEid)
{
    HRESULT hr;
    LPMAPIFOLDER lpfldr = NULL;
    LPMAPIFOLDER lpfldrT = NULL;
    LPSPropValue lpval = NULL;
    LPTSTR lpch;
    TCHAR rgch[MAX_PATH];
    ULONG ulType;

    if (!LoadString (lpsmh->hinst, SMH_FolderComment, rgch, sizeof(rgch)))
        rgch[0] = 0;

    hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_SUBTREE_ENTRYID, &lpval);
    if (!HR_FAILED (hr))
    {
        hr = lpmdb->lpVtbl->OpenEntry (lpmdb,
                            lpval->Value.bin.cb,
                            (LPENTRYID)lpval->Value.bin.lpb,
                            NULL,
                            MAPI_MODIFY,
                            &ulType,
                            (LPUNKNOWN FAR *)&lpfldr);

        (*lpsmh->lpfnFree) (lpval);
        lpval = NULL;

        if (!HR_FAILED (hr))
        {
            do
            {
                if (lpch = LpszFindChar (lpszPath, '\\'))
                    *lpch = 0;

                Assert (lstrlen (lpszPath));
                hr = lpfldr->lpVtbl->CreateFolder (lpfldr,
                            FOLDER_GENERIC,
                            lpszPath,
                            rgch,
                            NULL,
                            MAPI_MODIFY | OPEN_IF_EXISTS,
                            &lpfldrT);
                if (HR_FAILED (hr))
                {
#ifdef  DEBUG
                    LPMAPIERROR lperr = NULL;
                    lpfldr->lpVtbl->GetLastError (lpfldr, hr, 0, &lperr);
                    DebugTrace ("SMH: WARNING: unable to open/create folder: '%s' in %s\n",
                        lperr->lpszError, lperr->lpszComponent);
                    (*lpsmh->lpfnFree) (lperr);
#endif
                    break;
                }

                UlRelease (lpfldr);
                lpfldr = lpfldrT;
                lpfldrT = NULL;

                lpszPath = (lpch ? ++lpch : NULL);

            } while (lpszPath);
        }
        if (!HR_FAILED (hr))
        {
            hr = HrGetOneProp ((LPMAPIPROP)lpfldr, PR_ENTRYID, &lpval);
            if (!HR_FAILED (hr))
            {
                *lppfldr = lpfldr;
                *lppvalEid = lpval;
                lpfldr = NULL;
                lpval = NULL;
            }
        }
    }

    (*lpsmh->lpfnFree) (lpval);
    UlRelease (lpfldr);

    DebugTraceResult (HrFolderFromPath(), hr);
    return hr;
}

/*
 *  HrBuildRule()
 *
 *  Purpose:
 *
 *      Takes a profile section and builds a rule structure that
 *      corresponds to the properties in the profile section.
 *
 *  Arguments:
 *
 *      lpsmh           pointer to smh parent object
 *      lpmuid          profile section UID
 *      lpprl   [OUT]   buffer for the newly created rule pointer
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrBuildRule (LPSMH lpsmh, LPMAPIUID lpmuid, LPRULE FAR * lpprl)
{
    SCODE sc;
    HRESULT hr;
    LPMAPISESSION lpsess = lpsmh->lpsess;
    LPPROFSECT lpprof = NULL;
    LPRULE lprl = NULL;
    LPSPropValue lpval = NULL;
    LPSPropValue lpvalEid = NULL;
    LPSPropValue lpvalT;
    LPSRestriction lpres = NULL;
    ULONG cval;
    ULONG ulType;
    UINT cb;
    UINT i;

    sc = (*lpsmh->lpfnAlloc) (sizeof(RULE), &lprl);
    if (FAILED (sc))
    {
        hr = ResultFromScode (sc);
        goto ret;
    }
    memset (lprl, 0, sizeof(RULE));
    memcpy (&lprl->muid, lpmuid, sizeof(MAPIUID));
    hr = lpsess->lpVtbl->OpenProfileSection (lpsess,
                            lpmuid,
                            NULL,
                            MAPI_MODIFY,
                            &lpprof);
    if (HR_FAILED (hr))
        goto ret;

    hr = lpprof->lpVtbl->GetProps (lpprof,
                            (LPSPropTagArray)&sptRule,
                            0,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    if ((lpval[ipRLType].ulPropTag != PR_RULE_TYPE) ||
        (lpval[ipRLData].ulPropTag != PR_RULE_DATA) ||
        (lpval[ipRLFlags].ulPropTag != PR_RULE_FLAGS) ||
        (!(lpval[ipRLFlags].Value.l & (RULE_DELETE | RULE_NO_MOVE)) &&
          (lpval[ipRLType].ulPropTag != PR_RULE_TYPE) ||
          (lpval[ipRLPath].ulPropTag != PR_RULE_TARGET_PATH) ||
          (lpval[ipRLStore].ulPropTag != PR_RULE_STORE_DISPLAY_NAME)))
    {
        /*  Something very important is missing */

        hr = ResultFromScode (MAPI_E_UNCONFIGURED);
        goto ret;
    }
    lprl->rlTyp = (UINT)lpval[ipRLType].Value.l;
    lprl->ulFlags = lpval[ipRLFlags].Value.l;

    /*  Get the filter value */

    sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLData].Value.bin.cb, lprl, &lprl->lpszData);
    if (FAILED (sc))
    {
        hr = ResultFromScode (sc);
        goto ret;
    }
    memcpy (lprl->lpszData,
        lpval[ipRLData].Value.bin.lpb,
        (UINT)lpval[ipRLData].Value.bin.cb);

    /*  Get the sounds */

    if (lpval[ipRLFlags].Value.l & RULE_PLAY_SOUNDS)
    {
        for (i = 0; i < csndMax; i++)
        {
            if ((lpval[ipRLLoPri + i].ulPropTag == sptRule.aulPropTag[ipRLLoPri + i]) &&
                (cb = lstrlen (lpval[ipRLLoPri + i].Value.lpszA)))
            {
                cb += sizeof(TCHAR);
                sc = (*lpsmh->lpfnAllocMore) (cb, lprl, (LPVOID FAR *)&lprl->rgszSnd[i]);
                if (FAILED (sc))
                {
                    hr = ResultFromScode (sc);
                    goto ret;
                }
                lstrcpy (lprl->rgszSnd[i], lpval[ipRLLoPri + i].Value.lpszA);
            }
        }
    }

    /*  Fill in the auto response */

    if (lpval[ipRLFlags].Value.l & RULE_AUTO_RESPONSE)
    {
        if (lpval[ipRLRepFwdRTF].ulPropTag == PR_RULE_REP_FWD_RTF)
        {
            sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLRepFwdRTF].Value.bin.cb,
                                          lprl,
                                          (LPVOID FAR *)&lprl->lpbRTF);
            if (FAILED (sc))
            {
                hr = ResultFromScode (sc);
                goto ret;
            }
            lprl->cbRTF = lpval[ipRLRepFwdRTF].Value.bin.cb;
            memcpy (lprl->lpbRTF, lpval[ipRLRepFwdRTF].Value.bin.lpb, (UINT)lprl->cbRTF);
        }
        else if (lpval[ipRLRepFwd].ulPropTag == PR_RULE_REP_FWD_TEXT)
        {
            cb = lstrlen (lpval[ipRLRepFwd].Value.lpszA) + sizeof(TCHAR);
            sc = (*lpsmh->lpfnAllocMore) (cb, lprl, (LPVOID FAR *)&lprl->lpszAnno);
            if (FAILED (sc))
            {
                hr = ResultFromScode (sc);
                goto ret;
            }
            lstrcpy (lprl->lpszAnno, lpval[ipRLRepFwd].Value.lpszA);
        }

        if (lpval[ipRLFlags].Value.l & RULE_AUTO_FORWARD)
        {
            if (lpval[ipRLFwdEid].ulPropTag != PR_RULE_FORWARD_RECIP_ENTRYID)
            {
                hr = ResultFromScode (MAPI_E_UNCONFIGURED);
                goto ret;
            }
            else
            {
                sc = (*lpsmh->lpfnAlloc) (sizeof(SPropValue), &lprl->lpvalRecip);
                if (!FAILED (sc))
                {
                    sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLFwdEid].Value.bin.cb,
                        lprl->lpvalRecip,
                        &lprl->lpvalRecip[0].Value.bin.lpb);
                    if (!FAILED (sc))
                    {
                        lprl->lpvalRecip[0].ulPropTag = PR_ENTRYID;
                        lprl->lpvalRecip[0].Value.bin.cb = lpval[ipRLFwdEid].Value.bin.cb;
                        memcpy (lprl->lpvalRecip[0].Value.bin.lpb,
                                lpval[ipRLFwdEid].Value.bin.lpb,
                                lpval[ipRLFwdEid].Value.bin.cb);
                    }
                }
            }
            if (FAILED (sc))
            {
                hr = ResultFromScode (sc);
                goto ret;
            }
        }
    }

    /*  See if we are not going to delete the message */

    if (!(lpval[ipRLFlags].Value.l & (RULE_DELETE | RULE_NO_MOVE)))
    {
        /*  Find the target folder */

        hr = HrOpenMdbFromName (lpsmh, lpval[ipRLStore].Value.LPSZ, &lprl->lpmdb);
        if (HR_FAILED (hr))
            goto ret;

        if ((lpval[ipRLEid].ulPropTag != PR_RULE_TARGET_ENTRYID) ||
            (HR_FAILED (lpsess->lpVtbl->OpenEntry (lpsess,
                            lpval[ipRLEid].Value.bin.cb,
                            (LPENTRYID)lpval[ipRLEid].Value.bin.lpb,
                            NULL,
                            MAPI_MODIFY,
                            &ulType,
                            (LPUNKNOWN FAR *)&lprl->lpfldr))))
        {
            hr = HrFolderFromPath (lpsmh,
                            lprl->lpmdb,
                            lpval[ipRLPath].Value.LPSZ,
                            &lprl->lpfldr,
                            &lpvalEid);
            if (HR_FAILED (hr))
                goto ret;

            lpvalEid->ulPropTag = PR_RULE_TARGET_ENTRYID;
            HrSetOneProp ((LPMAPIPROP)lpprof, lpvalEid);
            lprl->lpvalEid = lpvalEid;
        }
        else
        {
            hr = HrGetOneProp ((LPMAPIPROP)lprl->lpfldr, PR_ENTRYID, &lprl->lpvalEid);
            if (HR_FAILED (hr))
                goto ret;
        }
    }

    if ((lpval[ipRLType].Value.l == RL_TO_RECIP) ||
        (lpval[ipRLType].Value.l == RL_CC_RECIP) ||
        (lpval[ipRLType].Value.l == RL_BCC_RECIP) ||
        (lpval[ipRLType].Value.l == RL_ANY_RECIP))
    {
        cb = (sizeof(SRestriction) * cresMax) + (sizeof(SPropValue) * cvMax);
        sc = (*lpsmh->lpfnAllocMore) (cb, lprl, &lpres);
        if (FAILED (sc))
        {
            hr = ResultFromScode (sc);
            goto ret;
        }
        lpvalT = (LPSPropValue)&lpres[cresMax];

        lpres[iresAnd].rt = RES_AND;
        lpres[iresAnd].res.resAnd.cRes = 2;
        lpres[iresAnd].res.resAnd.lpRes = &lpres[iresRecip];

        lpvalT[ivRecip].ulPropTag = PR_RECIPIENT_TYPE;
        lpvalT[ivRecip].Value.l = ((lpval[ipRLType].Value.l == RL_TO_RECIP)
                                    ? MAPI_TO
                                    : ((lpval[ipRLType].Value.l == RL_CC_RECIP)
                                        ? MAPI_CC
                                        : ((lpval[ipRLType].Value.l == RL_BCC_RECIP)
                                            ? MAPI_BCC
                                            : 0)));
        lpres[iresRecip].rt = RES_PROPERTY;
        lpres[iresRecip].res.resProperty.relop = RELOP_EQ;
        lpres[iresRecip].res.resContent.ulPropTag = PR_RECIPIENT_TYPE;
        lpres[iresRecip].res.resContent.lpProp = &lpvalT[ivRecip];

        lpres[iresOr].rt = RES_OR;
        lpres[iresOr].res.resOr.cRes = 2;
        lpres[iresOr].res.resOr.lpRes = &lpres[iresEmail];

        lpvalT[ivEmail].ulPropTag = PR_EMAIL_ADDRESS;
        lpvalT[ivEmail].Value.LPSZ = (LPTSTR)lprl->lpszData;
        lpres[iresEmail].rt = RES_CONTENT;
        lpres[iresEmail].res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_IGNORECASE;
        lpres[iresEmail].res.resContent.ulPropTag = PR_EMAIL_ADDRESS;
        lpres[iresEmail].res.resContent.lpProp = &lpvalT[ivEmail];

        lpvalT[ivDispNm].ulPropTag = PR_DISPLAY_NAME;
        lpvalT[ivDispNm].Value.LPSZ = (LPTSTR)lprl->lpszData;
        lpres[iresDispNm].rt = RES_CONTENT;
        lpres[iresDispNm].res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_IGNORECASE;
        lpres[iresDispNm].res.resContent.ulPropTag = PR_DISPLAY_NAME;
        lpres[iresDispNm].res.resContent.lpProp = &lpvalT[ivDispNm];

        if ((lpval[ipRLType].Value.l == RL_TO_RECIP) ||
            (lpval[ipRLType].Value.l == RL_CC_RECIP) ||
            (lpval[ipRLType].Value.l == RL_BCC_RECIP))
            lprl->lpres = &lpres[iresAnd];
        else
            lprl->lpres = &lpres[iresOr];
    }

ret:

    (*lpsmh->lpfnFree) (lpval);

    UlRelease (lpprof);
    if (HR_FAILED (hr))
    {
        (*lpsmh->lpfnFree) (lprl->lpvalRecip);
        (*lpsmh->lpfnFree) (lprl->lpvalEid);
        (*lpsmh->lpfnFree) (lprl);
        lprl = NULL;
    }
    *lpprl = lprl;

    DebugTraceResult (HrBuildRule(), hr);
    return hr;
}

/*
 *  ReleaseBkit()
 *
 *  Purpose:
 *
 *      Cleans up all resources held by a bucket structure and wipes the
 *      structure clean.
 *
 *  Arguments:
 *
 *      lpsmh           pointer to the smh object (uses allocation fn's)
 *      lpbkit          pointer to the bucket needing cleaning
 *
 */
VOID
ReleaseBkit (LPSMH lpsmh, LPBKIT lpbkit)
{
    UlRelease (lpbkit->lpfldr);
    UlRelease (lpbkit->lpfldrYr);
    UlRelease (lpbkit->lpfldrParent);
    (*lpsmh->lpfnFree) (lpbkit->lpeid);
    (*lpsmh->lpfnFree) (lpbkit->lpeidYr);
    (*lpsmh->lpfnFree) (lpbkit->lpeidParent);
    memset (lpbkit, 0, sizeof(BKIT));
    return;
}

HRESULT
HrInitSMH (LPSMH lpsmh)
{
    HRESULT hr;
    LPPROFSECT lpprof = NULL;
    LPRULE lprl;
    LPSPropValue lpval = NULL;
    LPSPropValue lpvalOld;
    UINT crl;
    ULONG cval;

    /*  Get options from the profile */

    hr = lpsmh->lpsess->lpVtbl->OpenProfileSection (lpsmh->lpsess,
                            &lpsmh->muid,
                            NULL,
                            MAPI_MODIFY,
                            &lpprof);
    if (HR_FAILED (hr))
        goto ret;

    hr = lpprof->lpVtbl->GetProps (lpprof,
                            (LPSPropTagArray)&sptConfigProps,
                            0,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    /*  Check to see if we are configured */

    if (lpval[ipFlags].ulPropTag != PR_SMH_FLAGS)
    {
        hr = ResultFromScode (MAPI_E_UNCONFIGURED);
        goto ret;
    }

    /*  Check that the rules are stored in the correct format */

    if (lpval[ipRules].ulPropTag != PR_SMH_RULES)
    {
        hr = HrGetOneProp ((LPMAPIPROP)lpprof,
                    CHANGE_PROP_TYPE(PR_SMH_RULES, PT_BINARY),
                    &lpvalOld);
        if (!HR_FAILED (hr))
        {
            /*  The rules are stored in the wrong format */

            hr = HrUpdateProfileFormat (lpsmh->lpsess,
                            lpsmh->lpsess->lpVtbl->OpenProfileSection,
                            lpsmh->lpfnAllocMore,
                            lpsmh->lpfnFree,
                            lpval,
                            lpvalOld);
            (*lpsmh->lpfnFree) (lpvalOld);
            if (HR_FAILED (hr))
                goto ret;

            /*  Save out anything we got back */

            lpprof->lpVtbl->SetProps (lpprof, cpMax, lpval, NULL);
        }
    }
    UlRelease (lpprof);
    lpprof = NULL;

    /*  Grab the exclusions list */

    hr = ResultFromScode (PropCopyMore (&lpsmh->valEx,
                            &lpval[ipExc],
                            lpsmh->lpfnAllocMore,
                            lpsmh));
    if (HR_FAILED (hr))
        goto ret;

    /*  Init the stores table */

    hr = HrInitStoresTable (lpsmh, lpsmh->lpsess);
    if (HR_FAILED (hr))
        goto ret;

    /*  Store the values */

    lpsmh->fAtp = !!(lpval[ipFlags].Value.l & SMH_ADD_TO_PAB);
    lpsmh->fCatSm = !!(lpval[ipFlags].Value.l & SMH_FILTER_SENTMAIL);
    lpsmh->fCatSmByYr = !!(lpval[ipFlags].Value.l & SMH_FILTER_SENTMAIL_YR);

    /*  If we are archiving deleted mail, init the filters */

    if (lpval[ipFlags].Value.l & SMH_FILTER_DELETED)
    {
        HrInitDeletedMailFilter (lpsmh);
        lpsmh->fCatWb = !!(lpval[ipFlags].Value.l & SMH_FILTER_DELETED_YR);
    }

    /*  If the unread folder is desired, make sure it is there */

    if (!!(lpval[ipFlags].Value.l & SMH_UNREAD_VIEWER))
        HrInitUnreadSearch (lpsmh);

    /*  Setup the oof text */

    if ((lpval[ipOofEnabled].ulPropTag == PR_SMH_OOF_ENABLED) &&
        lpval[ipOofEnabled].Value.b)
    {
        lpsmh->fOof = !HR_FAILED (HrInitOof (lpsmh,
                                        &lpval[ipOof],
                                        &lpval[ipOofRtf]));
    }

    /*  Build the rules if need be */

    if ((lpval[ipFlags].Value.l & SMH_FILTER_INBOUND) &&
        (lpval[ipRules].ulPropTag == PR_SMH_RULES) &&
        (lpval[ipRules].Value.MVbin.cValues != 0))
    {
        crl = (UINT)lpval[ipRules].Value.MVbin.cValues;
        while (crl--)
        {
            hr = HrBuildRule (lpsmh,
                    (LPMAPIUID)lpval[ipRules].Value.MVbin.lpbin[crl].lpb,
                    &lprl);
            if (!HR_FAILED (hr))
            {
                lprl->rlNext = lpsmh->lstRl;
                lpsmh->lstRl = lprl;
            }
        }
        hr = hrSuccess;
    }

ret:

    UlRelease (lpprof);
    (*lpsmh->lpfnFree) (lpval);

    DebugTraceResult (HrInitSMH(), hr);
    return hr;
}

VOID
DeinitSMH (LPSMH lpsmh)
{
    LPRULE lprl;
    LPRULE lprlT;
    LPWB lpwb;
    LPWB lpwbT;

    ReleaseBkit (lpsmh, &lpsmh->bkitSm);
    memset (&lpsmh->bkitSm, 0, sizeof(BKIT));

    for (lpwb = lpsmh->lstWb; lpwb; lpwb = lpwbT)
    {
        lpwbT = lpwb->wbNext;
        lpwb->lptbl->lpVtbl->Unadvise (lpwb->lptbl, lpwb->ulAdvz);

#ifdef  _WIN32
        if (lpwb->ht)
        {
            lpwb->fBail = TRUE;
            WaitForSingleObject (lpwb->ht, INFINITE);
        }
#endif

        ReleaseBkit (lpsmh, &lpwb->bkit);
        UlRelease (lpwb->lptbl);
        UlRelease (lpwb->lpfldr);

        (*lpsmh->lpfnFree) (lpwb->lpvalEid);
        (*lpsmh->lpfnFree) (lpwb);

    }
    lpsmh->lstWb = NULL;
    for (lprl = lpsmh->lstRl; lprl; lprl = lprlT)
    {
        lprlT = lprl->rlNext;
        ReleaseBkit (lpsmh, &lprl->bkit);
        UlRelease (lprl->lpfldr);
        (*lpsmh->lpfnFree) (lprl->lpvalEid);
        (*lpsmh->lpfnFree) (lprl->lpvalRecip);
        (*lpsmh->lpfnFree) (lprl);
    }
    lpsmh->lstRl = NULL;

    UlRelease (lpsmh->oof.lptad);
    UlRelease (lpsmh->oof.lptbl);
    (*lpsmh->lpfnFree) (lpsmh->oof.lpbRTF);
    (*lpsmh->lpfnFree) (lpsmh->oof.lpszBody);
    memset (&lpsmh->oof, 0, sizeof(OOF));

    ReleaseStoresTable (lpsmh);
    return;
}

/*
 *  SMH Object Methods
 *
 *  SMH_QueryInterface      (See OLE IUnknown object methods)
 *  SMH_AddRef              (See OLE IUnknown object methods)
 *  SMH_Release             (See OLE IUnknown object methods)
 *  SMH_InboundMsgHook      Filters inbound messages
 *  SMH_OutboundMsgHook     Filters sent mail messages
 *
 */
STDMETHODIMP
SMH_QueryInterface (LPSMH lpsmh, REFIID lpiid, LPVOID FAR * lppv)
{
    if (IsBadWritePtr (lpsmh, sizeof(SMH)) ||
        IsBadReadPtr (lpiid, sizeof(IID)) ||
        IsBadWritePtr (lppv, sizeof(LPVOID)))
        return ResultFromScode (MAPI_E_INVALID_PARAMETER);

    if (!memcmp (lpiid, &IID_ISpoolerHook, sizeof(IID)) ||
        !memcmp (lpiid, &IID_IUnknown, sizeof(IID)))
    {
        *lppv = (LPVOID)lpsmh;
        lpsmh->lcInit++;
        return hrSuccess;
    }
    DebugTraceSc (SMH_QueryInterface(), MAPI_E_INTERFACE_NOT_SUPPORTED);
    return ResultFromScode (MAPI_E_INTERFACE_NOT_SUPPORTED);
}

STDMETHODIMP_ (ULONG)
SMH_AddRef (LPSMH lpsmh)
{
    if (IsBadWritePtr (lpsmh, sizeof(SMH)))
        return 0;

    return ++lpsmh->lcInit;
}

STDMETHODIMP_ (ULONG)
SMH_Release (LPSMH lpsmh)
{
    if (IsBadWritePtr (lpsmh, sizeof(SMH)))
        return 0;

    if (--lpsmh->lcInit)
        return lpsmh->lcInit;

    DeinitSMH (lpsmh);

    CloseHandle (lpsmh->hevtCfg);
    UlRelease (lpsmh->lpsess);
    (*lpsmh->lpfnFree) (lpsmh);
    if (lpCtl3D)
    {
        CTL3D_Uninitialize (lpCtl3D);
        lpCtl3D = NULL;
    }
    return 0;
}

/*
 *  SMH_InboundMsgHook()
 *
 *  Purpose:
 *
 *      The purpose of this filter is to match inbound messages to
 *      individual rules from the profile and re-route the messages based
 *      on the results of the comparisons.
 *
 *  Arguments:
 *
 *      lpsmh           this filter hook obj
 *      lpmsg           the message to be filtered
 *      lpfldrDef       owning folder of message
 *      lpmdbDef        owning store of message
 *      lpulFlags       flags returned by filter
 *      lpcbeid         cb for entryid of default target for message
 *      lppbeid         pb for entryid of default target for message
 *
 *  Operation:
 *
 *      Opens the suggested folder (if needed) and checks for the
 *      existence of the appropriate "bucket" folder.  If it does exist,
 *      then the  folder is created and cached.  The entryid is grabbed
 *      and passed back in to the spooler.
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpulFlags   [out]   set HOOK_CANCEL if this is the last hook
 *      lpcbeid     [out]   the size of the returned EntryID
 *      lppbeid     [out]   the data of the returned EntryID
 *
 */
STDMETHODIMP
SMH_InboundMsgHook (LPSMH lpsmh,
    LPMESSAGE lpmsg,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    ULONG FAR * lpulFlags,
    ULONG FAR * lpcbeid,
    LPBYTE FAR * lppeid)
{
    HRESULT hr = hrSuccess;
    LPRULE lprl;
    LPBYTE lpeid;
    LPSPropValue lpval = NULL;

    /*  Quick and dirty parameter check */

    if (IsBadWritePtr (lpsmh, sizeof(SMH)) ||
        IsBadWritePtr (lpcbeid, sizeof(ULONG)) ||
        IsBadWritePtr (lppeid, sizeof(LPBYTE)) ||
        IsBadWritePtr (*lppeid, (UINT)(*lpcbeid)))
        return ResultFromScode (MAPI_E_INVALID_PARAMETER);

#ifdef  _WIN32
    if (FConfigChanged (lpsmh->hevtCfg))
    {
        /*  reconfiguration required */

        DeinitSMH (lpsmh);
        hr = HrInitSMH (lpsmh);
        if (HR_FAILED (hr))
            return hr;

        ResetEvent (lpsmh->hevtCfg);
    }
#endif  // _WIN32

    if (lprl = lpsmh->lstRl)    /*  Yup '=' */
    {
        hr = HrCheckExclusions (lpsmh, lpmsg);
        if (!HR_FAILED (hr))
        {
            /*  We have not been excluded */

            for (; lprl; lprl = lprl->rlNext)
            {
                hr = HrCheckRule (lpsmh, lprl, lpmsg, &lpval);
                if (!HR_FAILED (hr))
                {
                    /*  We have a match. What do we do, filter or delete? */

                    if (!(lprl->ulFlags & (RULE_DELETE | RULE_NO_MOVE)))
                    {
                        /*  Filter the critter */

                        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lprl->lpvalEid->Value.bin.cb, &lpeid));
                        if (!HR_FAILED (hr))
                        {
                            memcpy (lpeid, lprl->lpvalEid->Value.bin.lpb,
                                (UINT)lprl->lpvalEid->Value.bin.cb);

                            (*lpsmh->lpfnFree) (*lppeid);
                            *lpcbeid = lprl->lpvalEid->Value.bin.cb;
                            *lppeid = lpeid;

                            if (lprl->ulFlags & RULE_ARCHIVED)
                            {
                                hr = HrArchiveMessage (lpsmh,
                                            lpmsg,
                                            lpfldrDef,
                                            lpmdbDef,
                                            &lprl->bkit,
                                            !!(lprl->ulFlags & RULE_ARCHIVED_BY_YEAR),
                                            lpcbeid,
                                            lppeid);

                                if (lprl->ulFlags & RULE_TERMINAL)
                                    *lpulFlags = HOOK_CANCEL;
                            }
                        }

                        /*  Play the sound */

                        if ((lpval[ipPriority].Value.l < 3) &&
                            lprl->rgszSnd[lpval[ipPriority].Value.l])
                            sndPlaySound (lprl->rgszSnd[lpval[ipPriority].Value.l], SND_ASYNC);
                    }
                    else
                    {
                        if (lprl->ulFlags & RULE_DELETE)
                            *lpulFlags |= HOOK_DELETE;

                        *lpulFlags |= HOOK_CANCEL;
                    }

                    /*  Check for an auto-forward\reply */

                    if (lprl->ulFlags & RULE_AUTO_RESPONSE)
                    {
                        /*  We have a response that needs to
                         *  be created, and sent on.
                         */
                        hr = HrGenerateResponse (lpsmh, lprl, lpfldrDef, lpmsg);
                    }
                    break;
                }
                else if (GetScode (hr) != MAPI_E_NOT_ME)
                {
                    /*  We have a failure that is not really
                     *  expected, we need to bail.  Also, this
                     *  should cancel any further hooking
                     */
                    *lpulFlags = HOOK_CANCEL;
                    break;
                }
                else
                    hr = hrSuccess;
            }
        }
    }

    if (lpsmh->fOof)
    {
        /*  OOF'ing is done by calling HrGenerateResponse()
         *  and passing in a NULL for the rule.
         */
        hr = HrGenerateResponse (lpsmh, NULL, lpfldrDef, lpmsg);
    }

    (*lpsmh->lpfnFree) (lpval);
    DebugTraceResult (SMH_InboundMsgHook(), hr);
    return hrSuccess;
}

/*
 *  SMH_OutboundMsgHook()
 *
 *  Purpose:
 *
 *      The purpose of this filter is to "hash" a users sent mail
 *      processing based on date.  The most obvious bucket size is
 *      monthly but there is no reason not to make this an option the
 *      user could confiigure.
 *
 *  Arguments:
 *
 *      lpsmh           this filter hook obj
 *      lpmsg           the message to be filtered
 *      lpfldrDef       owning folder of message
 *      lpmdbDef        owning store of message
 *      lpulFlags       flags returned by filter
 *      lpcbeid         cb for entryid of default target for message
 *      lppbeid         pb for entryid of default target for message
 *
 *  Operation:
 *
 *      Opens the suggested folder (if needed) and checks for the
 *      existence of the appropriate "bucket" folder.  If it does exist,
 *      then the  folder is created and cached.  The entryid is grabbed
 *      and passed back in to the spooler.
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpulFlags   [out]   set HOOK_CANCEL if this is the last hook
 *      lpcbeid     [out]   the size of the returned EntryID
 *      lppbeid     [out]   the data of the returned EntryID
 *
 */
STDMETHODIMP
SMH_OutboundMsgHook (LPSMH lpsmh,
    LPMESSAGE lpmsg,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    ULONG FAR * lpulFlags,
    ULONG FAR * lpcbeid,
    LPBYTE FAR * lppeid)
{
    HRESULT hr = hrSuccess;

    /*  Quick and dirty parameter check */

    if (IsBadWritePtr (lpsmh, sizeof(SMH)) ||
        IsBadWritePtr (lpcbeid, sizeof(ULONG)) ||
        IsBadWritePtr (lppeid, sizeof(LPBYTE)) ||
        IsBadWritePtr (*lppeid, (UINT)(*lpcbeid)))
        return ResultFromScode (MAPI_E_INVALID_PARAMETER);

#ifdef  _WIN32
    if (FConfigChanged (lpsmh->hevtCfg))
    {
        /*  reconfiguration required */

        DeinitSMH (lpsmh);
        hr = HrInitSMH (lpsmh);
        if (HR_FAILED (hr))
            return hr;

        ResetEvent (lpsmh->hevtCfg);
    }
#endif  // _WIN32

    if (lpsmh->fAtp)
        (void) HrAddEntriesToPab (lpsmh, lpmsg);

    if (lpsmh->fCatSm)
        hr = HrArchiveMessage (lpsmh,
                lpmsg,
                lpfldrDef,
                lpmdbDef,
                &lpsmh->bkitSm,
                lpsmh->fCatSmByYr,
                lpcbeid,
                lppeid);

    DebugTraceResult (SMH_OutboundMsgHook(), hr);
    return hrSuccess;
}

/*
 *  SMH_Init()
 *
 *  Purpose:
 *
 *      Spooler's entry into the sample mail handler.  This function is
 *      equivilent to a provider logon in that it returns an object to
 *      the spooler that will be used to make any additional calls into
 *      the handler.
 *
 *  Arguments:
 *
 *      lpsess          the session this handler relates to
 *      hinst           hinst of the SMH dll
 *      lpfnAlloc       pointer to MAPIAllocateBuffer()
 *      lpfnAllocMore   pointer to MAPIAllocateMore()
 *      lpfnFree        pointer to MAPIFreeBuffer()
 *      lpmuid          pointer to profile section muid
 *      ulFlags         flags
 *      lppHook         buffer to hold handler object
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpphook     [OUT]   holds the returned handler object iff successful
 */
STDINITMETHODIMP
SMH_Init (LPMAPISESSION lpsess,
    HINSTANCE hinst,
    LPALLOCATEBUFFER lpfnAlloc,
    LPALLOCATEMORE lpfnAllocMore,
    LPFREEBUFFER lpfnFree,
    LPMAPIUID lpmuid,
    ULONG ulFlags,
    LPSPOOLERHOOK FAR * lppHook)
{
    SCODE sc;
    LPSMH lpsmh = NULL;
    HRESULT hr;

    sc = (*lpfnAlloc) (sizeof(SMH), &lpsmh);
    if (FAILED (sc))
        return ResultFromScode (sc);
    memset (lpsmh, 0, sizeof(SMH));

    hr = lpsess->lpVtbl->QueryInterface (lpsess,
                            &IID_IMAPISession,
                            &lpsmh->lpsess);
    if (!HR_FAILED (hr))
    {
        /*  Fill in all fields of the object */

        lpsmh->lpVtbl = (SMH_Vtbl FAR *)&vtblSMH;
        lpsmh->lcInit = 1;
        lpsmh->hinst = hinst;
        lpsmh->lpsess = lpsess;
        lpsmh->lpfnAlloc = lpfnAlloc;
        lpsmh->lpfnAllocMore = lpfnAllocMore;
        lpsmh->lpfnFree = lpfnFree;
        memcpy (&lpsmh->muid, lpmuid, sizeof(MAPIUID));

#ifdef  _WIN32
        /*  Setup listening for configuration changes */

        (void)HrGetConfigEvent (&lpsmh->hevtCfg);
#endif

        /*  Fill out the rest of the structure */

        hr = HrInitSMH (lpsmh);
    }

    if (HR_FAILED (hr))
    {
        UlRelease (lpsmh);
        lpsmh = NULL;
    }
    *lppHook = (LPSPOOLERHOOK)lpsmh;
    DebugTraceResult (SMH_Init(), hr);
    return hr;
}


SMHARC.C

/*
 *  S M H A R C . C
 *
 *  Sample mail handling message archiving
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

/*
 *  FIsLeapYear()
 *
 *  Used to calculate leap years when determining month ranges for
 *  archive folders.
 */
#define FIsLeapYear(_yr) ((!((_yr) % 400) || ((_yr) % 100) && !((_yr) % 4)) ? TRUE : FALSE)

/*
 *  sptMsgDates
 *
 *  The list of properties that are used for archiving.  If
 *  PR_MESSAGE_DELIVERY_TIME is not available, then SMH will use
 *  PR_CLIENT_SUBMIT_TIME.
 */
const static SizedSPropTagArray(2, sptMsgDates) =
{
    2,
    {
        PR_MESSAGE_DELIVERY_TIME,
        PR_CLIENT_SUBMIT_TIME
    }
};

/*
 *  sptFldrDates
 *
 *  Theses properties describe the range of dates that an archive folder
 *  with the returned EntryID will support.  This is used in the call to
 *  find a supporting sub-folder.
 */
const static SizedSPropTagArray (3, sptFldrDates) =
{
    3,
    {
        PR_START_DATE,
        PR_END_DATE,
        PR_ENTRYID
    }
};

/*
 *  rgtstrMonth
 *  rgtstrMonthFull
 *  rgwDaysPerMonth
 *
 *  These arrays are used in the calculation and creation of supporting
 *  archive folders.
 */
const static TCHAR FAR * rgtstrMonth[] =
{
    TEXT ("Jan"),
    TEXT ("Feb"),
    TEXT ("Mar"),
    TEXT ("Apr"),
    TEXT ("May"),
    TEXT ("Jun"),
    TEXT ("Jul"),
    TEXT ("Aug"),
    TEXT ("Sep"),
    TEXT ("Oct"),
    TEXT ("Nov"),
    TEXT ("Dec")
};
const TCHAR FAR * rgtstrMonthFull[] =
{
    TEXT ("January"),
    TEXT ("February"),
    TEXT ("March"),
    TEXT ("April"),
    TEXT ("May"),
    TEXT ("June"),
    TEXT ("July"),
    TEXT ("August"),
    TEXT ("September"),
    TEXT ("October"),
    TEXT ("November"),
    TEXT ("December")
};
const static WORD rgwDaysPerMonth[] =
{
    31, //  JAN
    28, //  FEB
    31, //  MAR
    30, //  APR
    31, //  MAY
    30, //  JUN
    31, //  JUL
    31, //  AUG
    30, //  SEP
    31, //  OCT
    30, //  NOV
    31  //  DEC
};

/*
 *  HrCacheFolder()
 *
 *  Purpose:
 *
 *      Caches the passed in entryid along with a matching folder
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      cbeid       count of bytes for the entryid to check
 *      lpeid       data for the entryid to check
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *      lpfUpdated  points to cache update flag
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 *      lpfUpdated  [OUT] TRUE iff the out folder is not the
 *                          previously cached folder
 */
HRESULT
HrCacheFolder (LPSMH lpsmh,
    LPMDB lpmdbPref,
    ULONG cbeid,
    LPENTRYID lpeid,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr,
    BOOL far * lpfUpdated)
{
    HRESULT hr;
    ULONG ulType;
    ULONG ulMatch;
    LPMAPIPROP lpmp = NULL;

    /* Init the update flag */

    *lpfUpdated = FALSE;

    /*  Is the topmost sent mail folder the same folder
     *  as the last filtered message?
     */
    hr = lpsmh->lpsess->lpVtbl->CompareEntryIDs (lpsmh->lpsess,
                                        cbeid,
                                        lpeid,
                                        *lpcbeid,
                                        *lppeid,
                                        0,
                                        &ulMatch);
    if (HR_FAILED (hr) || !ulMatch)
    {
        /* Different folder, guess we better toss the cached one */

        (*lpsmh->lpfnFree) (*lppeid);
        *lppeid = NULL;
        *lpcbeid = 0;

        /* Cache the SentMail */

        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (cbeid, lppeid));
        if (HR_FAILED (hr))
            goto ret;
        memcpy (*lppeid, lpeid, (UINT)cbeid);
        *lpcbeid = cbeid;
        *lpfUpdated = TRUE;
    }
    else if (*lppfldr)
        return hrSuccess;

    /* Open the new folder, but try the preferred store first */

    hr = lpmdbPref->lpVtbl->OpenEntry (lpmdbPref,
                                        cbeid,
                                        lpeid,
                                        NULL,
                                        MAPI_BEST_ACCESS,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpmp);
    if (HR_FAILED (hr))
    {
        /* Try the MAPI session now and see if we can get the folder */

        hr = lpsmh->lpsess->lpVtbl->OpenEntry (lpsmh->lpsess,
                                        cbeid,
                                        lpeid,
                                        NULL,
                                        MAPI_BEST_ACCESS,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpmp);
    }
    if (HR_FAILED (hr))
        goto ret;

    if (ulType != MAPI_FOLDER)
    {
        hr = ResultFromScode (MAPI_E_UNEXPECTED_TYPE);
        goto ret;
    }

ret:

    if (HR_FAILED (hr))
    {
        UlRelease (lpmp);
        (*lpsmh->lpfnFree) (*lppeid);
        *lppeid = NULL;
        *lpcbeid = 0;
        lpmp = NULL;
    }

    UlRelease (*lppfldr);
    *lppfldr = (LPMAPIFOLDER)lpmp;

    DebugTraceResult (HrCacheFolder(), hr);
    return hr;
}

/*
 *  HrCreateHashedFolder()
 *
 *  Purpose:
 *
 *      Create/Caches the a folder that satisfies the hash value
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      lpdft       pointer the the hash interval
 *      lpfldrPar   parnet folder
 *      lpszName    name for folder
 *      lpszComment comment for folder
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 */
HRESULT
HrCreateHashedFolder (LPSMH lpsmh,
    LPDFT lpdft,
    LPMAPIFOLDER lpfldrPar,
    LPTSTR lpszName,
    LPTSTR lpszComment,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr)
{
    HRESULT hr;
    LPMAPIFOLDER lpfldr = NULL;
    LPSPropValue lpval = NULL;
    SPropValue rgval[2] = {0};
    ULONG cval;

    /* Toss the current cache info */

    (*lpsmh->lpfnFree) (*lppeid);
    *lppeid = NULL;
    *lpcbeid = 0;

    /* Create the new folder */

    hr = lpfldrPar->lpVtbl->CreateFolder (lpfldrPar,
                            FOLDER_GENERIC,
                            lpszName,
                            lpszComment,
                            NULL,
                            OPEN_IF_EXISTS,
                            &lpfldr);
    if (HR_FAILED (hr))
        goto ret;

    /* Set the hashing interval properties */

    rgval[0].ulPropTag = PR_START_DATE;
    rgval[0].Value.ft = lpdft->ftStart;
    rgval[1].ulPropTag = PR_END_DATE;
    rgval[1].Value.ft = lpdft->ftEnd;
    hr = lpfldr->lpVtbl->SetProps (lpfldr, 2, rgval, NULL);
    if (HR_FAILED (hr))
        goto ret;

    /* Cache the folder info */

    hr = lpfldr->lpVtbl->GetProps (lpfldr,
                            (LPSPropTagArray)&sptFldrDates,
                            FALSE,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    /* Make sure we have all the info we need */

    if ((lpval[0].ulPropTag != PR_START_DATE) ||
        (lpval[1].ulPropTag != PR_END_DATE) ||
        (lpval[2].ulPropTag != PR_ENTRYID))
    {
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }

    /* Cache the entryid */

    hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lpval[2].Value.bin.cb, lppeid));
    if (HR_FAILED (hr))
        goto ret;
    memcpy (*lppeid, lpval[2].Value.bin.lpb, (UINT)lpval[2].Value.bin.cb);
    *lpcbeid = lpval[2].Value.bin.cb;

ret:

    (*lpsmh->lpfnFree) (lpval);
    if (HR_FAILED (hr))
    {
        UlRelease (lpfldr);
        lpfldr = NULL;
    }
    UlRelease (*lppfldr);
    *lppfldr = lpfldr;

    DebugTraceResult (HrCreateHashedFolder(), hr);
    return hr;
}

/*
 *  HrCacheHashedFolder()
 *
 *  Purpose:
 *
 *      Caches the folder that matches the hash value (file time)
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      ft          hash filetime
 *      lpdft       pointer the the hash interval
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *      lpfUpdated  points to cache update flag
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 *      lpfUpdated  [OUT] TRUE iff the out folder is not the
 *                          previously cached folder
 */
HRESULT
HrCacheHashedFolder (LPSMH lpsmh,
    LPMDB lpmdbPref,
    FILETIME ft,
    LPDFT lpdft,
    LPMAPIFOLDER lpfldr,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr,
    BOOL far * lpfUpdated)
{
    HRESULT hr;
    LPMAPIPROP lpmp = NULL;
    LPMAPITABLE lptbl = NULL;
    LPSRow lprw = NULL;
    LPSRowSet lprws = NULL;
    ULONG ulType;
    UINT i;

    /*  Check to see if the new hash fits the
     *  the current hashed folder, if the hash
     *  value does not work, find one that does
     */
    if (!*lpfUpdated &&
        (CompareFileTime (&lpdft->ftStart, &ft) != 1) &&
        (CompareFileTime (&lpdft->ftEnd, &ft) != -1))
    {
        /* The hash works, but do we have a folder? */

        if (*lppfldr)
            return hrSuccess;

        hr = lpsmh->lpsess->lpVtbl->OpenEntry (lpsmh->lpsess,
                                        *lpcbeid,
                                        *lppeid,
                                        NULL,
                                        MAPI_BEST_ACCESS,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpmp);
        if (!HR_FAILED (hr) && (ulType != MAPI_FOLDER))
        {
            hr = ResultFromScode (MAPI_E_UNEXPECTED_TYPE);
            UlRelease (lpmp);
            lpmp = NULL;
        }
        goto ret;
    }

    /* Toss the cached info */

    (*lpsmh->lpfnFree) (*lppeid);
    *lppeid = NULL;
    *lpcbeid = 0;

    /* Get the hierachy and set it up to find the target folder */

    hr = lpfldr->lpVtbl->GetHierarchyTable (lpfldr, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptFldrDates, 0);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->QueryRows (lptbl, 12, 0L, &lprws);
    if (HR_FAILED (hr))
        goto ret;

    while (lprws->cRows)
    {
        for (i = 0; i < lprws->cRows; i++)
        {
            lprw = &lprws->aRow[i];

            if (!lpmp &&
                (lprw->lpProps[0].ulPropTag == PR_START_DATE) &&
                (lprw->lpProps[1].ulPropTag == PR_END_DATE) &&
                (CompareFileTime (&lprw->lpProps[0].Value.ft, &ft) != 1) &&
                (CompareFileTime (&lprw->lpProps[1].Value.ft, &ft) != -1))
            {
                /* Hey, this looks like the folder we want */

                hr = HrCacheFolder (lpsmh,
                        lpmdbPref,
                        lprw->lpProps[2].Value.bin.cb,
                        (LPENTRYID)lprw->lpProps[2].Value.bin.lpb,
                        lpcbeid,
                        lppeid,
                        (LPMAPIFOLDER FAR *)&lpmp,
                        lpfUpdated);
                if (!HR_FAILED (hr))
                {
                    lpdft->ftStart = lprw->lpProps[0].Value.ft;
                    lpdft->ftEnd = lprw->lpProps[1].Value.ft;
                }
            }
        }

        /* Clean up the row properies */

        for (i = 0; i < lprws->cRows; i++)
            (*lpsmh->lpfnFree) (lprws->aRow[i].lpProps);

        /* We either found the folder or we had an error */

        if (lpmp || HR_FAILED (hr))
            break;

        /* Clean up the row set */

        (*lpsmh->lpfnFree) (lprws);
        hr = lptbl->lpVtbl->QueryRows (lptbl, 12, 0L, &lprws);
        if (HR_FAILED (hr))
            goto ret;
    }

    /* Clean up the final row set */

    (*lpsmh->lpfnFree) (lprws);

ret:

    UlRelease (lptbl);
    UlRelease (*lppfldr);
    *lppfldr = (LPMAPIFOLDER)lpmp;

    DebugTraceResult (HrCacheHashedFolder(), hr);
    return hr ? hr : (lpmp ? hrSuccess : ResultFromScode (MAPI_E_NOT_FOUND));
}

/*
 *  HrArchiveByDate()
 *
 *  Purpose:
 *
 *      The purpose of this function is to "hash" a single message by
 *      processing based on date.  The most obvious bucket size is
 *      monthly but there is no reason not to make this an option the
 *      user could confiigure.
 *
 *  Arguments:
 *
 *      lpsmh           this filter hook obj
 *      lpft            the date to be filtered against
 *      lpfldrDef       the owning folder of the message
 *      lpmdbDef        the owning store of the message
 *      lpbkit          the cached bucket structure
 *      fCatByYear      uses yearly subfolders iff TRUE
 *      cbeid           cb for entryid of default target for message
 *      lpbeid          pb for entryid of default target for message
 *
 *  Operation:
 *
 *      Opens the suggested folder (if needed) and checks for the
 *      existence of the appropriate "bucket" folder.  If it does exist,
 *      then the  folder is created and cached.  The entryid is grabbed
 *      and passed back in to the spooler.
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrArchiveByDate (LPSMH lpsmh,
    FILETIME FAR * lpft,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    LPBKIT lpbkit,
    BOOL fCatByYear,
    ULONG cbeid,
    LPBYTE lpeid)
{
    HRESULT hr = hrSuccess;
    BOOL fUpdated = FALSE;
    LPMAPIFOLDER lpfldr;
    SYSTEMTIME st;
    SYSTEMTIME sty;
    TCHAR rgchName[64] = {0};

    if (!FileTimeToSystemTime (lpft, &st))
    {
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }

    /* Cache the parent folder */

    hr = HrCacheFolder (lpsmh,
                    lpmdbDef,
                    cbeid,
                    (LPENTRYID)lpeid,
                    &lpbkit->cbeidParent,
                    &lpbkit->lpeidParent,
                    &lpbkit->lpfldrParent,
                    &fUpdated);
    if (HR_FAILED (hr))
        goto ret;

    if (fCatByYear)
    {
        /* Cache the year folder */

        hr = HrCacheHashedFolder (lpsmh,
                    lpmdbDef,
                    *lpft,
                    &lpbkit->dftYr,
                    lpbkit->lpfldrParent,
                    &lpbkit->cbeidYr,
                    &lpbkit->lpeidYr,
                    &lpbkit->lpfldrYr,
                    &fUpdated);
        if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND))
        {
            wsprintf (rgchName, "%04hu", st.wYear);
            sty.wYear = st.wYear;
            sty.wMonth = 1;
            sty.wDayOfWeek = 0;
            sty.wDay = 1;
            sty.wHour = 0;
            sty.wMinute = 0;
            sty.wSecond = 0;
            sty.wMilliseconds = 0;
            if (!SystemTimeToFileTime (&sty, &lpbkit->dftYr.ftStart))
            {
                hr = ResultFromScode (MAPI_E_BAD_VALUE);
                goto ret;
            }
            sty.wMonth = 12;
            sty.wDay = rgwDaysPerMonth[sty.wMonth - 1];
            sty.wDayOfWeek = 0;
            sty.wDay = 31;
            sty.wHour = 23;
            sty.wMinute = 59;
            sty.wSecond = 59;
            sty.wMilliseconds = 999;
            if (!SystemTimeToFileTime (&sty, &lpbkit->dftYr.ftEnd))
            {
                hr = ResultFromScode (MAPI_E_BAD_VALUE);
                goto ret;
            }
            hr = HrCreateHashedFolder (lpsmh,
                            &lpbkit->dftYr,
                            lpbkit->lpfldrParent,
                            rgchName,
                            NULL,
                            &lpbkit->cbeidYr,
                            &lpbkit->lpeidYr,
                            &lpbkit->lpfldrYr);
            if (HR_FAILED (hr))
                goto ret;
        }
        else if (HR_FAILED (hr))
            goto ret;

        lpfldr = lpbkit->lpfldrYr;
    }
    else
        lpfldr = lpbkit->lpfldrParent;

    /* Cache the hashed target folder */

    hr = HrCacheHashedFolder (lpsmh,
                    lpmdbDef,
                    *lpft,
                    &lpbkit->dft,
                    lpfldr,
                    &lpbkit->cbeid,
                    &lpbkit->lpeid,
                    &lpbkit->lpfldr,
                    &fUpdated);
    if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND))
    {
        if (fCatByYear)
        {
            wsprintf (rgchName,
                "%02hu %s",
                st.wMonth,
                rgtstrMonthFull[st.wMonth - 1]);
        }
        else
        {
            wsprintf (rgchName,
                TEXT("'%02hu/%02hu %s"),
                st.wYear % 100,
                st.wMonth,
                rgtstrMonthFull[st.wMonth - 1]);
        }
        st.wDayOfWeek = (st.wDay - st.wDayOfWeek - 1) % 7;
        st.wDay = 1;
        st.wHour = 0;
        st.wMinute = 0;
        st.wSecond = 0;
        st.wMilliseconds = 0;
        if (!SystemTimeToFileTime (&st, &lpbkit->dft.ftStart))
        {
            hr = ResultFromScode (MAPI_E_BAD_VALUE);
            goto ret;
        }
        st.wDay = rgwDaysPerMonth[st.wMonth - 1];
        if ((st.wMonth == 2) && FIsLeapYear (st.wYear))
            st.wDay += 1;
        st.wDayOfWeek = (st.wDayOfWeek + st.wDay - 1) % 7;
        st.wHour = 23;
        st.wMinute = 59;
        st.wSecond = 59;
        st.wMilliseconds = 999;
        if (!SystemTimeToFileTime (&st, &lpbkit->dft.ftEnd))
        {
            hr = ResultFromScode (MAPI_E_BAD_VALUE);
            goto ret;
        }
        hr = HrCreateHashedFolder (lpsmh,
                            &lpbkit->dft,
                            lpfldr,
                            rgchName,
                            NULL,
                            &lpbkit->cbeid,
                            &lpbkit->lpeid,
                            &lpbkit->lpfldr);
    }

ret:

    DebugTraceResult (HrArchiveMessage(), hr);
    return hr;
}

/*
 *  HrArchiveMessage()
 *
 *  Purpose:
 *
 *      The purpose of this function is to "hash" a single message by
 *      processing based on date.  The most obvious bucket size is
 *      monthly but there is no reason not to make this an option the
 *      user could confiigure.
 *
 *  Arguments:
 *
 *      lpsmh           this filter hook obj
 *      lpmsg           the message to be filtered
 *      lpfldrDef       the owning folder of the message
 *      lpmdbDef        the owning store of the message
 *      lpbkit          the cached bucket structure
 *      fCatByYear      uses yearly subfolders iff TRUE
 *      lpcbeid         cb for entryid of default target for message
 *      lppbeid         pb for entryid of default target for message
 *
 *  Operation:
 *
 *      Extracts the date of the message that is to be used in the
 *      archiving, and calls off to HrArchiveByDate().
 *
 *      IMPORTANT: the entryid passed in will be swapped out by this
 *      call.  Therefore the *lppeid buffer must be allocated with the
 *      MAPIAllocateBuffer provider to the filter.
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid [out]   the size of the returned EntryID
 *      lppbeid [out]   the data of the returned EntryID
 */
HRESULT
HrArchiveMessage (LPSMH lpsmh,
    LPMESSAGE lpmsg,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    LPBKIT lpbkit,
    BOOL fCatByYear,
    ULONG FAR * lpcbeid,
    LPBYTE FAR * lppeid)
{
    HRESULT hr = hrSuccess;
    FILETIME ft;
    LPSPropValue lpval = NULL;
    ULONG cval;

    /* Quick and dirty parameter check */

    if (IsBadWritePtr (lpsmh, sizeof(SMH)) ||
        IsBadWritePtr (lpcbeid, sizeof(ULONG)) ||
        IsBadWritePtr (lppeid, sizeof(LPBYTE)) ||
        IsBadWritePtr (*lppeid, (UINT)(*lpcbeid)))
        return ResultFromScode (MAPI_E_INVALID_PARAMETER);

    /* Get the date used by the hash */

    hr = lpmsg->lpVtbl->GetProps (lpmsg,
                            (LPSPropTagArray)&sptMsgDates,
                            FALSE,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    /* Make sure what we end up with is usable */

    if (lpval[0].ulPropTag == PR_MESSAGE_DELIVERY_TIME)
    {
        DebugTrace ("SMH: filtering on PR_MESSAGE_DELIVERY_TIME\n");
        ft = lpval[0].Value.ft;
    }
    else if (lpval[1].ulPropTag == PR_CLIENT_SUBMIT_TIME)
    {
        DebugTrace ("SMH: filtering on PR_CLIENT_SUBMIT_TIME\n");
        ft = lpval[1].Value.ft;
    }
    else
    {
        DebugTrace ("SMH: cannot filter on provided time props\n");
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }

    hr = HrArchiveByDate (lpsmh,
                    &ft,
                    lpfldrDef,
                    lpmdbDef,
                    lpbkit,
                    fCatByYear,
                    *lpcbeid,
                    *lppeid);

ret:

    if (!HR_FAILED (hr))
    {
        LPBYTE lpeid;

        /* OK, If we get this far we are moving the message */

        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lpbkit->cbeid, &lpeid));
        if (HR_FAILED (hr))
            goto ret;
        memcpy (lpeid, lpbkit->lpeid, (UINT)lpbkit->cbeid);
        (*lpsmh->lpfnFree) (*lppeid);
        *lpcbeid = lpbkit->cbeid;
        *lppeid = lpeid;
    }
    (*lpsmh->lpfnFree) (lpval);

    DebugTraceResult (HrArchiveMessage(), hr);
    return hr;
}


SMHATP.C

/*
 *  S M H A T P . C
 *
 *  Automatic Additions To Personal Address Books
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#include <mspab.h>

static const MAPIUID muidPAB = PAB_PROVIDER_ID;
static const SizedSPropTagArray (2, sptEid) = { 1, { PR_ENTRYID, PR_RECIPIENT_TYPE }};

HRESULT
HrAddEntriesToPab (LPSMH lpsmh, LPMESSAGE lpmsg)
{
    HRESULT hr;
    ENTRYLIST el = {0};
    LPABCONT lpPab = NULL;
    LPADRBOOK lpab = NULL;
    LPENTRYID lpeid;
    LPMAPITABLE lptbl = NULL;
    LPSPropValue lpval = NULL;
    LPSRowSet lprws = NULL;
    UINT ie;
    ULONG cbeid;
    ULONG ce = 0;
    ULONG cRows = 0;
    ULONG ulT;

    /*  If this message is a report, we do not
     *  want to add the recipient to the pab
     */
    hr = HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpval);
    if (!HR_FAILED (hr) && FLpszContainsLpsz (lpval->Value.LPSZ, "Report"))
        goto ret;

    /*  Check to see if the installed PAB is the
     *  one supplied by MAPI
     */
    hr = lpsmh->lpsess->lpVtbl->OpenAddressBook (lpsmh->lpsess,
                                            0,
                                            NULL,
                                            0,
                                            &lpab);
    if (HR_FAILED(hr))
        goto ret;

    hr = lpab->lpVtbl->GetPAB (lpab, &cbeid, &lpeid);
    if (HR_FAILED (hr) || (0 == cbeid))
        goto ret;

    /*  If this is not the MAPI pab, then we better
     *  not play with it
     */
    if (memcmp (lpeid->ab, &muidPAB, sizeof(MAPIUID)))
        goto ret;

    hr = lpab->lpVtbl->OpenEntry (lpab,
                            cbeid,
                            lpeid,
                            NULL,
                            0,
                            &ulT,
                            (LPUNKNOWN *) &lpPab);
    (*lpsmh->lpfnFree) (lpeid);
    UlRelease (lpab);
    lpeid = NULL;
    lpab = NULL;
    if (HR_FAILED (hr))
        goto ret;

    /*  Get the recipient table from the message */

    hr = lpmsg->lpVtbl->GetRecipientTable (lpmsg, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    /*  Only get the entryIDs */

    hr = HrQueryAllRows (lptbl,
                    (LPSPropTagArray) &sptEid,
                    NULL,
                    NULL,
                    0,    /* all rows */
                    &lprws);
    UlRelease (lptbl);
    lptbl = NULL;
    if (HR_FAILED (hr))
        goto ret;

    /*  Loop through all the recipients and remove those that
     *  do not belong to the MAPI PAB
     */
    cRows = lprws->cRows;
    if (FAILED ((*lpsmh->lpfnAlloc) (cRows*sizeof(SBinary), &el.lpbin)))
        goto ret;

    for (ce = 0, ie = 0; ie < cRows; ie++)
    {
        LPSPropValue pval = lprws->aRow[ie].lpProps;
        Assert (pval);
        Assert (pval->ulPropTag == PR_ENTRYID);
        cbeid = pval->Value.bin.cb;
        lpeid = (LPENTRYID)pval->Value.bin.lpb;
        Assert (cbeid);
        Assert (lpeid);

        /*  If this is not a P1 and not from the MAPI PAB... */

        if (!(pval[1].Value.l & MAPI_P1) &&
            memcmp (lpeid->ab, &muidPAB, sizeof(MAPIUID)))
        {
            /*  ... set it into the entry list */

            el.lpbin[ce].cb = cbeid;
            el.lpbin[ce].lpb = (LPBYTE)lpeid;
            ce++;
        }
    }

    /*  Now, copy everything over into the PAB */

    el.cValues = ce;
    lpeid = NULL;
    cbeid = 0;

    (void) lpPab->lpVtbl->CopyEntries (lpPab,
                                &el,
                                0,
                                NULL,
                                CREATE_CHECK_DUP_STRICT);

ret:

    if (lprws)
    {
        for (ie = 0; ie < cRows; ie++)
            (*lpsmh->lpfnFree) (lprws->aRow[ie].lpProps);
        (*lpsmh->lpfnFree) (lprws);
    }
    (*lpsmh->lpfnFree) (el.lpbin);
    (*lpsmh->lpfnFree) (lpval);
    UlRelease (lpPab);
    UlRelease (lpab);

    DebugTraceResult (HrAddEntryiesToPab(), hr);
    return hr;
}


SMHCFG.C

/*
 *  S M H C F G . C
 *
 *  Sample mail handling hook configuration
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

/*
 *  Configuration Properties
 *
 *  sptConfigProps
 *
 *      This is the set of properties that we expect to find in the
 *      provider's profile section.
 *
 *  sptRule
 *
 *      This is the set of properties that are expected to be found in
 *      each rule's profile section.
 */
const SizedSPropTagArray (cpMax, sptConfigProps) =
{
    cpMax,
    {
        PR_PROFILE_NAME,
        PR_SMH_FLAGS,
        PR_SMH_RULES,
        PR_SMH_RULE_NAMES,
        PR_SMH_OOF_TEXT,
        PR_SMH_OOF_ENABLED,
        PR_SMH_OOF_RTF,
        PR_SMH_SOUND_SCHEMES,
        PR_SMH_REPFWD_SCHEMES,
        PR_SMH_EXCLUSIONS
    }
};
const SizedSPropTagArray (cpRLMax, sptRule) =
{
    cpRLMax,
    {
        PR_DISPLAY_NAME,
        PR_RULE_TYPE,
        PR_RULE_DATA,
        PR_RULE_FLAGS,
        PR_RULE_TARGET_ENTRYID,
        PR_RULE_TARGET_PATH,
        PR_RULE_STORE_ENTRYID,
        PR_RULE_STORE_DISPLAY_NAME,
        PR_RULE_SOUND_LOPRI,
        PR_RULE_SOUND_NORMAL,
        PR_RULE_SOUND_HIPRI,
        PR_RULE_FORWARD_RECIP,
        PR_RULE_FORWARD_RECIP_ENTRYID,
        PR_RULE_REP_FWD_RTF,
        PR_RULE_REP_FWD_TEXT
    }
};

/*
 *  Configuration event name
 */
const TCHAR lpszConfigEvt[] = "SMH_CONFIGURATION_EVENT";

/*
 *  HrOpenSingleProvider()
 *
 *  Purpose:
 *
 *      Opens the profile section of this service provider.  This is done
 *      by opening the message service table and querying all rows.
 *      Since the SMH service contains only one provider, there is only
 *      one row in the table.  This contains our profile section.  Use
 *      the PR_PROFILE_UID to open the section.
 *
 *  Arguments:
 *
 *      lpadmin         the IProfileAdmin object
 *      lppprof [OUT]   on return contains the profile section obj
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lppprof [OUT]   contains the profile section object iff the call
 *                      is successful.
 */
HRESULT
HrOpenSingleProvider (LPPROVIDERADMIN lpadmin, LPPROFSECT FAR * lppprof)
{
    HRESULT hr;
    LPMAPITABLE lptbl = NULL;
    LPSRowSet lprws = NULL;
    SPropTagArray sptProvider = {1, {PR_PROVIDER_UID}};
    SRestriction res = {0};

    res.rt = RES_EXIST;
    res.res.resExist.ulPropTag = PR_PROVIDER_DLL_NAME;

    hr = lpadmin->lpVtbl->GetProviderTable (lpadmin, 0, &lptbl);
    if (!HR_FAILED (hr))
    {
        hr = lptbl->lpVtbl->SetColumns (lptbl, &sptProvider, 0L);
        if (!HR_FAILED (hr))
        {
            hr = lptbl->lpVtbl->Restrict (lptbl, &res, 0);
            if (!HR_FAILED (hr))
            {
                hr = lptbl->lpVtbl->QueryRows (lptbl, 10, 0, &lprws);
                if (!HR_FAILED (hr) && lprws->cRows)
                {
                    Assert (lprws->cRows == 1);
                    Assert (lprws->aRow[0].cValues == 1);
                    Assert (lprws->aRow[0].lpProps);
                    Assert (lprws->aRow[0].lpProps[0].ulPropTag == PR_PROVIDER_UID);
                    hr = lpadmin->lpVtbl->OpenProfileSection (lpadmin,
                        (LPMAPIUID)lprws->aRow[0].lpProps[0].Value.bin.lpb,
                        NULL,
                        MAPI_MODIFY,
                        lppprof);
                    MAPIFreeBuffer (lprws->aRow[0].lpProps);
                }
                MAPIFreeBuffer (lprws);
            }
        }
        UlRelease (lptbl);
    }
    DebugTraceResult (HrOpenSingleProvider, hr);
    return hr;
}

/*
 *  HrMergeValues()
 *
 *  Purpose:
 *
 *      Merges two property value arrays into one.  By making a copy of
 *      the first and then adding/replacing props from the second.
 *
 *      IMPORTANT: if there are any property values that are separately
 *      allocated with either MAPIAllocateBuffer() or MAPIAllocateMore(),
 *      then these property values must remain valid as long as the
 *      merged property array is expected to be valid.
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrMergeValues (ULONG cval1,
    LPSPropValue lpval1,
    ULONG cval2,
    LPSPropValue lpval2,
    LPALLOCATEBUFFER lpfnAlloc,
    ULONG FAR * lpcvalMerged,
    LPSPropValue FAR * lppvalMerged)
{
    SCODE sc;
    LPSPropValue lpval = NULL;
    UINT cb;
    UINT ip;

    cb = (UINT)(cval1 + cval2) * sizeof(SPropValue);
    sc = (*lpfnAlloc)(cb, &lpval);
    if (!FAILED (sc))
    {
        /*  Slurp the original data across */

        memcpy (lpval, lpval1, (UINT)cval1 * sizeof(SPropValue));

        /*  Move the new stuff over */

        while (cval2--)
        {
            for (ip = 0; ip < (UINT)cval1; ip++)
            {
                /*  See if we match properties */

                if (PROP_ID (lpval[ip].ulPropTag) == PROP_ID (lpval2[cval2].ulPropTag))
                {
                    /*  We matched, but are we a real property */

                    if (PROP_TYPE (lpval2[cval2].ulPropTag) != PT_ERROR)
                        lpval[ip] = lpval2[cval2];
                    break;
                }
            }
            if (ip == cval1)
                lpval[cval1++] = lpval2[cval2];
        }
        *lpcvalMerged = cval1;
        *lppvalMerged = lpval;
    }
    DebugTraceSc (HrMergeValues(), sc);
    return ResultFromScode (sc);
}

/*
 *  HrUpdateProfileFormat()
 *
 *  Purpose:
 *
 *      Converts the WMS level of SMH profile properties to post WMS
 *      levels.  Namely rules are now stored via a pair of properties
 *      in the profile.  PR_SMH_RULES has been changed to a multi-valued
 *      binary property and PR_SMH_RULE_NAMES contains the display names
 *      for the rules.  The prevents the configuration functions from
 *      opening all profile sections to build the list of rules.
 *
 *  Arguments:
 *
 *      lpvOPSCtxt          Context passed to OpenProfileSection()
 *                          (a session or an profadmin object)
 *      lpfnOpenProfSect    Pointer to OpenProfileSection()
 *      lpfnMore            MAPIAllocateMore() function
 *      lpfnFree            MAPIFreeBuffer() function
 *      lpvalNew            propvalue array for the converted values
 *      lpvalOld            propvalue of the old PR_SMH_RULES
 *
 *  Returns:
 *
 *      (HRESULT)
 */

HRESULT
HrUpdateProfileFormat (LPVOID lpvOPSCtxt,
    LPOPENPROFSECT lpfnOpenProfSect,
    LPALLOCATEMORE lpfnMore,
    LPFREEBUFFER lpfnFree,
    LPSPropValue lpvalNew,
    LPSPropValue lpvalOld)
{
    SCODE sc = S_OK;
    LPMAPIUID lpmuid;
    LPPROFSECT lpsec = NULL;
    LPSBinary lpbin;
    LPSPropValue lpval = NULL;
    LPTSTR FAR * lppsz;
    SizedSPropTagArray (2, spt) = {2, {PR_DISPLAY_NAME, PR_RULE_TYPE}};
    UINT cb;
    UINT crl;
    UINT i;
    ULONG cval;

    DebugTrace ("SMH: updating profile to new format\n");

    /*  Make sure that the size is an even number of UIDs */

    Assert ((lpvalOld->Value.bin.cb % sizeof(MAPIUID)) == 0);
    crl = (UINT)lpvalOld->Value.bin.cb / sizeof(MAPIUID);

    /*  Allocate space for the two new properties */

    cb = crl * sizeof(SBinary);
    sc = (*lpfnMore) (cb, lpvalNew, &lpbin);
    if (FAILED (sc))
        goto ret;
    memset (lpbin, 0, cb);

    cb = crl * sizeof(LPTSTR);
    sc = (*lpfnMore) (cb, lpvalNew, (LPVOID FAR *)&lppsz);
    if (FAILED (sc))
        goto ret;
    memset (lppsz, 0, cb);

    /*  Iterate through the old list building the two new lists as we go */

    lpmuid = (LPMAPIUID)lpvalOld->Value.bin.lpb;
    for (i = 0; crl--; lpmuid++)
    {
        /*  Open the old profile section and get PR_DISPLAY_NAME
         *  and PR_RULE_TYPE for conversion purposes.  The type
         *  will be converted and reset into the profile section.
         *  Thus, completing the conversion for that section.
         *
         *  The display name will be extracted and copied out to
         *  the external names property and will be saved out when
         *  all section conversions are completed
         */
        if (!HR_FAILED ((*lpfnOpenProfSect) (lpvOPSCtxt, lpmuid, NULL, MAPI_MODIFY, &lpsec)) &&
            !HR_FAILED (lpsec->lpVtbl->GetProps (lpsec, (LPSPropTagArray)&spt, 0, &cval, &lpval)) &&
            !FAILED ((*lpfnMore) (sizeof(MAPIUID), lpvalNew, &lpbin[i].lpb)) &&
            !FAILED ((*lpfnMore) (lstrlen (lpval[0].Value.LPSZ) + 1, lpvalNew, &lppsz[i])))
        {
            switch (lpval[1].Value.l)
            {
              case 1:
                lpval[1].Value.l = RL_SUBJECT;
                break;
              case 2:
                lpval[1].Value.l = RL_SENDER;
                break;
              case 4:
                lpval[1].Value.l = RL_HAS_ATTACH;
                break;
              case 8:
                lpval[1].Value.l = RL_BODY;
                break;
              case 16:
                lpval[1].Value.l = RL_TO_RECIP;
                break;
              case 32:
                lpval[1].Value.l = RL_CC_RECIP;
                break;
              case 64:
                lpval[1].Value.l = RL_BCC_RECIP;
                break;
              case 128:
                lpval[1].Value.l = RL_ANY_RECIP;
                break;
              case 256:
                lpval[1].Value.l = RL_MSG_CLASS;
                break;
            }
            if (!HR_FAILED (HrSetOneProp ((LPMAPIPROP)lpsec, &lpval[1])))
            {
                DebugTrace ("SMH: converting '%s' rule\n", lpval[0].Value.LPSZ);
                lpbin[i].cb = sizeof(MAPIUID);
                memcpy (lpbin[i].lpb, lpmuid, sizeof(MAPIUID));
                lstrcpy (lppsz[i], lpval[0].Value.LPSZ);
                i++;
            }
        }
        (*lpfnFree) (lpval);
        UlRelease (lpsec);
        lpval = NULL;
        lpsec = NULL;
    }
    lpvalNew[ipRules].ulPropTag = PR_SMH_RULES;
    lpvalNew[ipRules].Value.MVbin.cValues = i;
    lpvalNew[ipRules].Value.MVbin.lpbin = lpbin;
    lpvalNew[ipNames].ulPropTag = PR_SMH_RULE_NAMES;
    lpvalNew[ipNames].Value.MVSZ.cValues = i;
    lpvalNew[ipNames].Value.MVSZ.LPPSZ = lppsz;

ret:

    DebugTraceSc (HrUpdateProfileFormat(), sc);
    return ResultFromScode (sc ? sc : ((i == crl) ? 0 : MAPI_W_ERRORS_RETURNED));
}

/*
 *  HrGetConfigEvent()
 *
 *  Purpose:
 *
 *      Gets the configuration event handle.  The handle is used to
 *      signal logged in handlers that their configuration has been
 *      modified and, at the next reasonable chance, should be reloaded.
 *
 *      Called at SMH init time only.
 *
 *  Arguments:
 *
 *      lphevt  [OUT]   contains the handle iff the call succeeds
 *
 *  Returns:
 *
 *      (HRESULT)
 */
#ifdef  _WIN32
HRESULT
HrGetConfigEvent (HANDLE FAR * lphevt)
{
    HANDLE hevt = NULL;

    if (!(hevt = CreateEvent (NULL, TRUE, FALSE, lpszConfigEvt)) &&
        !(hevt = OpenEvent (EVENT_MODIFY_STATE, FALSE, lpszConfigEvt)))
        ResultFromScode (MAPI_E_NOT_ENOUGH_RESOURCES);

    *lphevt = hevt;
    return hrSuccess;
}
#endif  /* _WIN32 */

/*
 *  SignalConfigChanged()
 *
 *  Purpose:
 *
 *      Sets the configuration event such that logged in hooks can update
 *      their configuration on the fly.
 *
 *      Called from within the service entry when configuration changes
 *      are commited.
 */
#ifdef  _WIN32
VOID
SignalConfigChanged (VOID)
{
    HANDLE hevt = NULL;

    if (hevt = OpenEvent (EVENT_MODIFY_STATE, FALSE, lpszConfigEvt))
    {
        SetEvent (hevt);
        CloseHandle (hevt);
    }
    return;
}
#endif  /* _WIN32 */

/*
 *  FConfigChanged()
 *
 *  Purpose:
 *
 *      Tests the configuration event such that logged in hooks can update
 *      their configuration on the fly if the configuration has changed.
 *
 *      Called from within the SMH object at regualr intervals
 *
 *  Returns:
 *
 *      TRUE iff the config changed
 *
 */
#ifdef  _WIN32
BOOL
FConfigChanged (HANDLE hevt)
{
    ULONG dw;

    dw = WaitForSingleObject (hevt, 0);
    Assert (dw != WAIT_ABANDONED);
    return (dw == WAIT_OBJECT_0);
}
#endif  /* _WIN32 */

/*
 *  SMH_ServiceEntry()
 *
 *  Purpose:
 *
 *      The ServiceEntry() function is the MAPI entry point to configure
 *      a service for use in a profile.  The call can then bring up UI to
 *      ensure configuration of the SMH provider.
 *
 *  Parameters:
 *
 *      hinst           DLL instance
 *      lpmalloc        OLE style allocator (used by PropSheet())
 *      lpsup           MAPI profile support object
 *      ulUIParam       hwnd that is to be used as UI parent
 *      ulFlags         configuration flags
 *      ulContext       configuration action
 *      cval            count of caller supplied properties
 *      lpval           caller supplied properties to be configured
 *      lpadmin         IProviderAdmin object
 *      lppmerr [OUT]   extended error information
 *
 *  Operation:
 *
 *      The ServiceEntry() uses the IProviderAdmin object to open its
 *      profile section and retrieve the current set of properties.  The
 *      caller supplied properties are then merged into the set of
 *      current properties.
 *
 *      If either this set of properties is not sufficient for
 *      configuration or the caller specifically asked for configuration
 *      UI, then ServiceEntry() will make calls to bring up its config
 *      UI.
 *
 *      ServiceEntry() recognizes several configuration flags.  If
 *      SERVICE_UI_ALWAYS and/or SERVICE_UI_ALLOWED are set, UI is
 *      allowed and we be brought up if appropriate.  Is
 *      MSG_SERVICE_UI_READ_ONLY is set, then the UI should not
 *      allow the configuration to be modified.
 *
 *      The configuration contexts MSG_SERVICE_DELETE, MSG_SERVICE_INSTALL,
 *      and MSG_SERVICE_UNINSTALL are ignored and no action is taken.
 *      MSG_SERVICE_CONFIGURE and MSG_SERVICE_CREATE allow the caller to
 *      create or update the configuration properties in this providers
 *      profile section.
 *
 *      SMH will not return extended information in the MAPIERROR in case
 *      of error
 */
HRESULT STDAPICALLTYPE
SMH_ServiceEntry(
    HINSTANCE hinst,
    LPMALLOC lpmalloc,
    LPMAPISUP lpsup,
    ULONG ulUIParam,
    ULONG ulFlags,
    ULONG ulContext,
    ULONG cval,
    LPSPropValue lpval,
    LPPROVIDERADMIN lpadmin,
    LPMAPIERROR FAR * lppmerr)
{
    HRESULT hr = hrSuccess;
    BOOL fUI = FALSE;
    LPALLOCATEBUFFER lpfnAlloc = NULL;
    LPALLOCATEMORE lpfnAllocMore = NULL;
    LPFREEBUFFER lpfnFree = NULL;
    LPMAPIUID lpmuid = NULL;
    LPPROFSECT lpprof = NULL;
    LPPROFSECT lpprofSvc = NULL;
    LPSCD lpscd = NULL;
    LPSPropValue lpvalCur = NULL;
    LPSPropValue lpvalNew = NULL;
    LPSPropValue lpvalOld;
    SPropValue val;
    UINT csec = 0;
    UINT i;
    ULONG cvalCur;
    ULONG cvalNew;
    ULONG ulMyFlags;

    if ((ulContext == MSG_SERVICE_INSTALL) ||
        (ulContext == MSG_SERVICE_UNINSTALL))
        goto ret;

    if ((ulContext != MSG_SERVICE_CONFIGURE) &&
        (ulContext != MSG_SERVICE_CREATE) &&
        (ulContext != MSG_SERVICE_DELETE))
    {
        hr = ResultFromScode (MAPI_E_NO_SUPPORT);
        goto ret;
    }

    if (ulFlags & MAPI_UNICODE)
    {
        /*  Unicode is not supported by SMH */

        hr = ResultFromScode (MAPI_E_BAD_CHARWIDTH);
        goto ret;
    }

    /*  Find out our UI options */

    fUI = !!(ulFlags & SERVICE_UI_ALWAYS);
    ulMyFlags = (ulFlags & MSG_SERVICE_UI_READ_ONLY)
        ? UI_READONLY
        : 0;

    /*  Get memory routines */

    hr = lpsup->lpVtbl->GetMemAllocRoutines (lpsup,
                            &lpfnAlloc,
                            &lpfnAllocMore,
                            &lpfnFree);
    if (HR_FAILED (hr))
        goto ret;

    /*  Open the profile section */

    hr = HrOpenSingleProvider (lpadmin, &lpprof);
    if (HR_FAILED (hr))
        goto ret;

    /*  Get the values already in the profile */

    hr = lpprof->lpVtbl->GetProps (lpprof,
                            (LPSPropTagArray)&sptConfigProps,
                            0,
                            &cvalCur,
                            &lpvalCur);
    if (HR_FAILED (hr))
        goto ret;

    /*  Check that the rules are stored in the correct format */

    if (lpvalCur[ipRules].ulPropTag != PR_SMH_RULES)
    {
        hr = HrGetOneProp ((LPMAPIPROP)lpprof,
                    CHANGE_PROP_TYPE(PR_SMH_RULES, PT_BINARY),
                    &lpvalOld);
        if (!HR_FAILED (hr))
        {
            /*  The rules are stored in the wrong format */

            hr = HrUpdateProfileFormat (lpadmin,
                            lpadmin->lpVtbl->OpenProfileSection,
                            lpfnAllocMore,
                            lpfnFree,
                            lpvalCur,
                            lpvalOld);
            (*lpfnFree) (lpvalOld);
            if (HR_FAILED (hr))
                goto ret;

            /*  Save out anything we got back */

            lpprof->lpVtbl->SetProps (lpprof, cpMax, lpvalCur, NULL);
        }
    }

    if (ulContext != MSG_SERVICE_DELETE)
    {
        /*  Merge what was in the profile with what was passed in */

        hr = HrMergeValues (cvalCur,
                            lpvalCur,
                            cval,
                            lpval,
                            lpfnAlloc,
                            &cvalNew,
                            &lpvalNew);
        if (HR_FAILED (hr))
            goto ret;

        /*  If we dont have all the props we need, then
         *  we will have to ask for them
         */
        fUI = fUI || (lpvalNew[ipFlags].ulPropTag != PR_SMH_FLAGS);
        if ((lpvalNew[ipFlags].ulPropTag != PR_SMH_FLAGS) &&
            (!(ulFlags & (SERVICE_UI_ALLOWED | SERVICE_UI_ALWAYS)) ||
            (ulFlags & MSG_SERVICE_UI_READ_ONLY)))
        {
            /*  We need UI but can't have it. */

            hr = ResultFromScode (MAPI_E_UNCONFIGURED);
            goto ret;
        }

        if (fUI)
        {
            /*  Do the config dialog */

            if (!FAILED ((*lpfnAlloc) (sizeof(SCD), &lpscd)))
            {
                memset (lpscd, 0, sizeof(SCD));
                lpscd->hinst = hinst;
                lpscd->hwnd = (HWND)ulUIParam;
                lpscd->lpfnAlloc = lpfnAlloc;
                lpscd->lpfnAllocMore = lpfnAllocMore;
                lpscd->lpfnFree = lpfnFree;
                lpscd->lpmalloc = lpmalloc;
                lpscd->lpval = lpvalNew;
                lpscd->lpsec = lpprof;
                lpscd->lpsup = lpsup;
                lpscd->lpadmin = lpadmin;
                lpscd->ulFlags = ulMyFlags;
                hr = HrDisplayPropSheets (hinst, (HWND)ulUIParam, lpscd);
            }
            else
            {
                hr = ResultFromScode (MAPI_E_NOT_ENOUGH_MEMORY);
                goto ret;
            }
        }

        /*  Open the service profile section and set PR_SERVICE_EXTRA_UIDS */

        if (!HR_FAILED (lpadmin->lpVtbl->OpenProfileSection (lpadmin,
                                            NULL,
                                            NULL,
                                            MAPI_MODIFY,
                                            &lpprofSvc)))
        {
            /*  PR_SERVICE_EXTRA_UIDS is composed of all additional
             *  sections used by SMH.  By setting this value, we gain
             *  the ability to have the service copied and\or moved
             *  while having the additional sections move across with
             *  it.
             */
            if (lpvalNew[ipRules].ulPropTag == PR_SMH_RULES)
                csec += (UINT)lpvalNew[ipRules].Value.MVbin.cValues;

            if (csec && !FAILED ((*lpfnAlloc) (csec * sizeof(MAPIUID), &lpmuid)))
            {
                for (i = 0; i < csec; i++)
                    memcpy (&lpmuid[i],
                        lpvalNew[ipRules].Value.MVbin.lpbin[i].lpb,
                        sizeof(MAPIUID));

                val.ulPropTag = PR_SERVICE_EXTRA_UIDS;
                val.Value.bin.cb = csec * sizeof(MAPIUID);
                val.Value.bin.lpb = (LPBYTE)lpmuid;
                lpprofSvc->lpVtbl->SetProps (lpprofSvc, 1, &val, NULL);
                lpprofSvc->lpVtbl->SaveChanges (lpprofSvc, 0);
                (*lpfnFree) (lpmuid);
            }
            UlRelease (lpprofSvc);
        }
    }

ret:

#ifdef  _WIN32
    if (!HR_FAILED (hr))
        SignalConfigChanged ();
#endif

    /*  Cleanup what was left behind */

    if (lpscd)
    {
        for (i = 0; i < lpscd->crl; i++)
        {
            (*lpfnFree) (lpscd->lppsz[i]);
            (*lpfnFree) (lpscd->lpbin[i].lpb);
        }
        (*lpfnFree) (lpscd->lppsz);
        (*lpfnFree) (lpscd->lpbin);
        (*lpfnFree) (lpscd);
    }

    if (lpfnFree)
    {
        (*lpfnFree) (lpvalCur);
        (*lpfnFree) (lpvalNew);
    }

    UlRelease (lpprof);
    DebugTraceResult (SMH_ServiceEntry, hr);
    return hr;
};


SMHGUID.C

/*
 *  S H M G U I D . C
 *
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#define USES_IID_IUnknown
#define USES_IID_ISpoolerHook
#define USES_IID_IMAPISession
#define USES_IID_IMAPITableData
#define USES_IID_IRichEditOleCallback
#define USES_IID_IMessage

#define STRICT

#ifdef _WIN32                   /* Must include WINDOWS.H on Win32 */
#ifndef _WINDOWS_
#define INC_OLE2                /* Get the OLE2 stuff */
#define INC_RPC                 /* harmless on Windows NT; Windows 95 needs it */
#define _INC_OLE                /* Windows 95 will include OLE1 without this */
#include <windows.h>
#include <ole2.h>
#endif
#endif

#ifdef WIN16
#include <compobj.h>
#endif

#define INITGUID
#include <initguid.h>
#include "mapiguid.h"

DEFINE_OLEGUID (IID_IRichEditOleCallback, 0x00020D03, 0, 0);


SMHINPXP.C

/*
 *  S M H I N P X P . C
 *
 *  Sample mail handling hook filter importing and exporting.
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#include <commdlg.h>

extern SPropTagArray sptRule;

#define Align(_cb,_pad)         (((_cb)+((_pad)-1)) & ~((_pad)-1))
#define Align8(_cb)             Align((_cb),8)

#ifdef WIN16
#define SEG(_fp)                HIWORD((DWORD)_fp)
#define OFF(_fp)                LOWORD((DWORD)_fp)
#define PvRelocPv(_p,_bo,_bn)   ((LPVOID)MAKELONG(OFF(_p)-OFF(_bo)+OFF(_bn),SEG(_bn)))
#else
#define PvRelocPv(_p,_bo,_bn)   ((LPVOID)((LPBYTE)(_p)-(LPBYTE)(_bo)+(LPBYTE)(_bn)))
#endif

static const BYTE rgbExportKey[] = { 0x4F, 0x6C, 0x61, 0x66, 0xDE, 0xAD };

/*
 *  Property Flattening -------------------------------------------------------
 */

/*
 *  ScSizeFlattenedProps()
 *  ScFlattenProps()
 *  ScNormalizeFlattenedProps()
 *
 *  These three functions are similar to the MAPI utility functions
 *  ScCountProps(), ScCopyProps() and ScRelocProps().  SMH uses these
 *  implementations because they are platform independant.  Additionally,
 *  the SMH rule property sets are very simple and do not require support
 *  for complex property types.
 *
 *  To be platform independent, SMH pads all variable length properties
 *  out to 8 byte aligment.  This ensures that all RISC platforms (MIPS,
 *  ALPHA and PowerPC) will not suffer aligment problems when using the
 *  normalized flattened property values.
 *
 *  ScSizeFlattenedProps() calculates the size of a buffer required to
 *  flatten the properties in the SPropValue array.
 *
 *  ScFlattenProps() copies the properties from the source array to the
 *  destination buffer.
 *
 *  ScNormalizeFlattenedProps() goes through a flattened a property array
 *  and bases all the pointers to a specific value.
 */
SCODE
ScSizeFlattenedProps (UINT cv, LPSPropValue rgval, ULONG FAR * lpcb)
{
    SCODE sc = S_OK;
    ULONG cb = 0;

    for (; cv--; rgval++)
    {
        cb += sizeof(SPropValue);
        switch (PROP_TYPE (rgval->ulPropTag))
        {
          default:
          case PT_UNSPECIFIED:
          case PT_I2:
          case PT_R4:
          case PT_APPTIME:
          case PT_DOUBLE:
          case PT_BOOLEAN:
          case PT_CURRENCY:
          case PT_SYSTIME:
          case PT_I8:
          case PT_OBJECT:
          case PT_CLSID:
          case PT_MV_I2:
          case PT_MV_LONG:
          case PT_MV_R4:
          case PT_MV_APPTIME:
          case PT_MV_DOUBLE:
          case PT_MV_CURRENCY:
          case PT_MV_SYSTIME:
          case PT_MV_CLSID:
          case PT_MV_I8:
          case PT_MV_BINARY:
          case PT_MV_STRING8:
          case PT_MV_UNICODE:

            sc = MAPI_E_UNEXPECTED_TYPE;
            break;

          case PT_LONG:
          case PT_ERROR:
          case PT_NULL:

            /*  There is no additional data to include in
             *  the size of the flattened props.
             */
            continue;

          case PT_BINARY:

            cb += Align8 (rgval->Value.bin.cb);
            break;

          case PT_STRING8:

            cb += Align8 ((lstrlenA (rgval->Value.lpszA ) + 1) * sizeof(CHAR));
            break;

          case PT_UNICODE:

            cb += Align8 ((lstrlenW (rgval->Value.lpszW ) + 1) * sizeof(WCHAR));
            break;

        }

        if (FAILED (sc))
            break;
    }

    *lpcb = cb;
    DebugTraceSc (ScSizeFlattenedProps(), sc);
    return sc;
}

SCODE
ScFlattenProps (UINT cv, LPSPropValue rgval, LPBYTE lpb)
{
    SCODE sc = S_OK;
    LPSPropValue rgvalDst = (LPSPropValue)lpb;
    ULONG cb;

    /*  Copy over the base properties and setup
     *  a pointer to the block of memory following
     *  the property value array
     */
    memcpy (rgvalDst, rgval, cv * sizeof(SPropValue));
    lpb += cv * sizeof(SPropValue);

    for (; cv--; rgval++, rgvalDst++)
    {
        switch (PROP_TYPE (rgval->ulPropTag))
        {
          default:
          case PT_UNSPECIFIED:
          case PT_I2:
          case PT_R4:
          case PT_APPTIME:
          case PT_DOUBLE:
          case PT_BOOLEAN:
          case PT_CURRENCY:
          case PT_SYSTIME:
          case PT_I8:
          case PT_OBJECT:
          case PT_CLSID:
          case PT_MV_I2:
          case PT_MV_LONG:
          case PT_MV_R4:
          case PT_MV_APPTIME:
          case PT_MV_DOUBLE:
          case PT_MV_CURRENCY:
          case PT_MV_SYSTIME:
          case PT_MV_CLSID:
          case PT_MV_I8:
          case PT_MV_BINARY:
          case PT_MV_STRING8:
          case PT_MV_UNICODE:

            sc = MAPI_E_UNEXPECTED_TYPE;
            break;

          case PT_LONG:
          case PT_ERROR:
          case PT_NULL:

            /*  There is no additional data to flatten for
             *  these types of properties.  All the data is
             *  contained in the SPropValue struct.
             */
            continue;

          case PT_BINARY:

            cb = rgval->Value.bin.cb;
            rgvalDst->Value.bin.lpb = lpb;
            memcpy (lpb, rgval->Value.bin.lpb, (UINT)cb);
            break;

          case PT_STRING8:

            cb = lstrlenA (rgval->Value.lpszA) + 1;
            rgvalDst->Value.lpszA = (LPSTR)lpb;
            memcpy (lpb, rgval->Value.lpszA, cb);
            break;

          case PT_UNICODE:

            cb = (lstrlenW (rgval->Value.lpszW) + 1) * sizeof(WCHAR);
            rgvalDst->Value.lpszW = (LPWSTR)lpb;
            memcpy (lpb, rgval->Value.lpszW, cb);
            break;
        }

        if (FAILED (sc))
            break;

        /*  Align the size and adjust the data pointer */

        lpb += Align8 (cb);
    }

    DebugTraceSc (ScFlattenProps(), sc);
    return sc;
}

SCODE
ScNormalizeFlattenedProps (UINT cv, LPSPropValue rgval, LPVOID lpvOld, LPVOID lpvNew)
{
    SCODE sc = S_OK;

    for (; cv--; rgval++)
    {
        switch (PROP_TYPE (rgval->ulPropTag))
        {
          default:
          case PT_UNSPECIFIED:
          case PT_I2:
          case PT_R4:
          case PT_APPTIME:
          case PT_DOUBLE:
          case PT_BOOLEAN:
          case PT_CURRENCY:
          case PT_SYSTIME:
          case PT_I8:
          case PT_OBJECT:
          case PT_CLSID:
          case PT_MV_I2:
          case PT_MV_LONG:
          case PT_MV_R4:
          case PT_MV_APPTIME:
          case PT_MV_DOUBLE:
          case PT_MV_CURRENCY:
          case PT_MV_SYSTIME:
          case PT_MV_CLSID:
          case PT_MV_I8:
          case PT_MV_BINARY:
          case PT_MV_STRING8:
          case PT_MV_UNICODE:

            sc = MAPI_E_UNEXPECTED_TYPE;
            break;

          case PT_LONG:
          case PT_ERROR:
          case PT_NULL:

            /*  Nothing to relocate */

            continue;

          case PT_BINARY:

            rgval->Value.bin.lpb = PvRelocPv (rgval->Value.bin.lpb, lpvOld, lpvNew);
            break;

          case PT_STRING8:

            rgval->Value.lpszA = PvRelocPv (rgval->Value.lpszA, lpvOld, lpvNew);
            break;

          case PT_UNICODE:

            rgval->Value.lpszW = PvRelocPv (rgval->Value.lpszW, lpvOld, lpvNew);
            break;
        }

        if (FAILED (sc))
            break;
    }

    DebugTraceSc (ScNormalizeFlattenedProps(), sc);
    return sc;
}

/*
 *  Common Dialog Helper Functions --------------------------------------------
 */

#ifdef  WIN16
#define COMMDLG             "commdlg.dll"
#define GETOPENFILENAME     "GetOpenFileName"
#define GETSAVEFILENAME     "GetSaveFileName"
#else
#define COMMDLG             "comdlg32"
#define GETOPENFILENAME     "GetOpenFileNameA"
#define GETSAVEFILENAME     "GetSaveFileNameA"
#endif
typedef BOOL  (WINAPI COMMDLG_GETOPENFILENAME)(LPOPENFILENAME);
typedef COMMDLG_GETOPENFILENAME FAR * LPCOMMDLG_GETOPENFILENAME;
typedef BOOL  (WINAPI COMMDLG_GETSAVEFILENAME)(LPOPENFILENAME);
typedef COMMDLG_GETSAVEFILENAME FAR * LPCOMMDLG_GETSAVEFILENAME;

SCODE
ScSetupCommdlg (HINSTANCE FAR * lplib,
    LPCOMMDLG_GETOPENFILENAME FAR * lppfnOpen,
    LPCOMMDLG_GETSAVEFILENAME FAR * lppfnSave)
{
    HINSTANCE hlib = LoadLibrary (COMMDLG);

#ifdef WIN16
    if (hlib < HINSTANCE_ERROR)
        hlib = NULL;
#endif

    if (hlib)
    {
        *lppfnOpen = (LPCOMMDLG_GETOPENFILENAME)GetProcAddress (hlib, GETOPENFILENAME);
        *lppfnSave = (LPCOMMDLG_GETSAVEFILENAME)GetProcAddress (hlib, GETSAVEFILENAME);
    }

    *lplib = hlib;

    return hlib ? S_OK : MAPI_E_CALL_FAILED;
}

UINT CALLBACK
CommdlgHook (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_INITDIALOG)
    {
        CTL3D_Subclass (lpCtl3D, hdlg, CTL3D_ALL);
        return TRUE;
    }

    return FALSE;
}

/*
 *  Filter Exporting ----------------------------------------------------------
 *
 *  SMH allows the importing and exporting of its filters.  This is
 *  useful when the user has multiple machines available for use.  Or for
 *  when the user needs to re-create their profile.
 *
 *  ScWriteRule() take the muid of a profile section that contains a
 *  filter and writes the rule out to the passed in file handle.
 *
 *  ScExportFilters() opens the export file and iterates through the
 *  rules and calls ScWriteRule() for each filter.
 */
SCODE
ScWriteRule (LPSCD lpscd, HFILE hf, LPMAPIUID lpmuid)
{
    SCODE sc = S_OK;
    HRESULT hr;
    LPPROFSECT lpsec = NULL;
    LPSPropValue lpval = NULL;
    LPSPropValue lpvalT = NULL;
    ULONG cb;
    ULONG cval;

    /*  Open the profile section for the rule */

    hr = lpscd->lpadmin->lpVtbl->OpenProfileSection (lpscd->lpadmin,
                                        lpmuid,
                                        NULL,
                                        0,
                                        &lpsec);
    if (!HR_FAILED (hr))
    {
        /*  Get the set of properties describing the rule */

        hr = lpsec->lpVtbl->GetProps (lpsec,
                                (LPSPropTagArray)&sptRule,
                                0,
                                &cval,
                                &lpval);
        if (!HR_FAILED (hr))
        {
            /*  When exporting, we do not want to
             *  export the values for store-based
             *  entryid's.  These can be profile
             *  specific.
             */
            lpval[ipRLEid].ulPropTag = PR_NULL;
            lpval[ipRLSEid].ulPropTag = PR_NULL;

            /*  Flatten the remaining property set and
             *  write them out to the export file
             */
            sc = ScSizeFlattenedProps (cval, lpval, &cb);
            if (!FAILED (sc))
            {
                sc = (*lpscd->lpfnAlloc) (cb, &lpvalT);
                if (!FAILED (sc))
                {
                    sc = ScFlattenProps (cval, lpval, (LPBYTE)lpvalT);
                    if (!FAILED (sc))
                    {
                        sc = ScNormalizeFlattenedProps (cval, lpvalT, lpvalT, 0);
                        if (!FAILED (sc))
                        {
                            /*  We have the flattened props, so
                             *  write them out.
                             */
                            _lwrite (hf, (LPBYTE)&cb, sizeof(ULONG));
                            _lwrite (hf, (LPBYTE)lpvalT, (UINT)cb);
                        }
                    }
                }
            }
        }
    }
    if (HR_FAILED (hr))
        sc = GetScode (sc);

    (*lpscd->lpfnFree) (lpvalT);
    (*lpscd->lpfnFree) (lpval);
    UlRelease (lpsec);

    DebugTraceSc (ScWriteRule(), sc);
    return sc;
}

SCODE
ScExportFilters (LPSCD lpscd, HWND hwnd)
{
    SCODE sc = S_OK;
    CHAR rgch[MAX_PATH];
    HFILE hf;
    HINSTANCE hlib = NULL;
    LPCOMMDLG_GETOPENFILENAME lpfnOpen;
    LPCOMMDLG_GETSAVEFILENAME lpfnSave;
    OFSTRUCT ob;
    OPENFILENAME ofn = {sizeof(OPENFILENAME)};
    UINT irl;
    UINT ofFlags = OF_READWRITE | OF_SHARE_EXCLUSIVE | OF_CREATE;

    /*  Setup the commdlg structures */

    lstrcpy (rgch, "smh.mrl");
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Filter Export Files (*.mrl)\0*.mrl\0";
    ofn.lpstrFile = rgch;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = "Create Filter Export File";
    ofn.lpstrDefExt = "MRL";
    ofn.hInstance = lpscd->hinst;
    ofn.lCustData = 0;
    ofn.lpfnHook = (LPOFNHOOKPROC)CommdlgHook;
    ofn.Flags = OFN_PATHMUSTEXIST       |
                OFN_HIDEREADONLY        |
                OFN_NOCHANGEDIR         |
                OFN_ENABLEHOOK          |
                OFN_OVERWRITEPROMPT;

    if (FWin4Shell ())
        ofn.Flags |= OFN_EXPLORER;

    /*  Get commdlg loaded and ready to serve */

    if (!FAILED (ScSetupCommdlg (&hlib, &lpfnOpen, &lpfnSave)))
    {
        /*  Prompt the user for the file to use */

        if ((*lpfnSave) (&ofn))
        {
            /*  Open the file and iterate through the rules;
             *  Writing each one out as we go.
             */
            hf = OpenFile (rgch, &ob, ofFlags);
            if (hf != HFILE_ERROR)
            {
                _lwrite (hf, (LPBYTE)rgbExportKey, sizeof(rgbExportKey));
                _lwrite (hf, (LPBYTE)&lpscd->crl, sizeof(ULONG));
                for (irl = 0; irl < lpscd->crl; irl++)
                {
                    sc = ScWriteRule (lpscd, hf, (LPMAPIUID)lpscd->lpbin[irl].lpb);
                    if (FAILED (sc))
                        break;
                }

                _lclose (hf);
            }
        }
        FreeLibrary (hlib);
    }

    DebugTraceSc (ScExportFilters(), sc);
    return sc;
}

/*
 *  Filter Importing ----------------------------------------------------------
 *
 *  SMH allows the importing and exporting of its filters.  This is
 *  useful when the user has multiple machines available for use.  Or for
 *  when the user needs to re-create their profile.
 *
 *  ScInsertImportedRule() takes a SPropValue array and sets those values
 *  into a profile section.  It will replace rules by name -- maybe some
 *  sort of a conflict message would be good here, but... -- otherwise,
 *  it will create a new profile section for each rule.  The filter is
 *  then added to the filters page of the SMH config.
 *
 *  ScReadRule() reads in the components of a flattened rule from the
 *  passed in file handle.  It then tries to insert the rule into the
 *  profile via ScInsertImportedRule().
 *
 *  ScImportFilters() opens the imported rule file and iterates through
 *  the file calling ScReadRule() for each contained filter.
 */
SCODE
ScInsertImportedRule (LPSCD lpscd, HWND hctrl, LPSPropValue lpval)
{
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPSBinary lpbin;
    LPTSTR FAR * lppsz;
    LPPROFSECT lpsec = NULL;
    MAPIUID muid;
    UINT irl;
    UINT cb;

    /*  See if the filter alread exists -- if so, we wil replace it */

    for (irl = 0; irl < lpscd->crl; irl++)
        if (!lstrcmpi (lpscd->lppsz[irl], lpval[ipRLDisp].Value.lpszA))
            break;

    if (irl == lpscd->crl)
        hr = lpscd->lpsup->lpVtbl->NewUID (lpscd->lpsup, &muid);
    else
        memcpy (&muid, lpscd->lpbin[irl].lpb, sizeof(MAPIUID));

    if (!HR_FAILED (hr))
    {
        hr = lpscd->lpadmin->lpVtbl->OpenProfileSection (lpscd->lpadmin,
                                        &muid,
                                        NULL,
                                        MAPI_MODIFY,
                                        &lpsec);
        if (!HR_FAILED (hr))
        {
            hr = lpsec->lpVtbl->SetProps (lpsec, cpRLMax, lpval, NULL);
            if (!HR_FAILED (hr))
                hr = lpsec->lpVtbl->SaveChanges (lpsec, 0);
        }
    }

    /*  Add the filter to the list of active filters */

    if (!HR_FAILED (hr))
    {
        if (irl == lpscd->crlMax)
        {
            /*  We need to make room for new entries */

            cb = (irl + GROW_SIZE) * sizeof(LPVOID);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, (LPVOID FAR *)&lppsz)))
                goto ret;
            memcpy (lppsz, lpscd->lppsz, lpscd->crl * sizeof(LPTSTR));

            cb = (irl + GROW_SIZE) * sizeof(SBinary);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, &lpbin)))
                goto ret;
            memcpy (lpbin, lpscd->lpbin, lpscd->crl * sizeof(SBinary));

            /*  Swap out the old for the new */

            (*lpscd->lpfnFree) (lpscd->lppsz);
            (*lpscd->lpfnFree) (lpscd->lpbin);
            lpscd->crlMax += GROW_SIZE;
            lpscd->lppsz = lppsz;
            lpscd->lpbin = lpbin;
        }

        sc = (*lpscd->lpfnAlloc) (lstrlen (lpval[ipRLDisp].Value.LPSZ) + 1, &lpscd->lppsz[irl]);
        if (!FAILED (sc) &&
            !FAILED (sc = (*lpscd->lpfnAlloc) (sizeof(MAPIUID), &lpscd->lpbin[irl].lpb)))
        {
            if ((ListBox_AddString (hctrl, lpval[ipRLDisp].Value.LPSZ) != LB_ERR) &&
                (ListBox_SetItemData (hctrl, irl, irl) != LB_ERR))
            {
                /*  Copy the identifiers across */

                lstrcpy (lpscd->lppsz[irl], lpval[ipRLDisp].Value.LPSZ);
                memcpy (lpscd->lpbin[irl].lpb, &muid, sizeof(MAPIUID));
                lpscd->lpbin[irl].cb = sizeof(MAPIUID);
                lpscd->crl++;
            }
        }
        else
            hr = ResultFromScode (sc);
    }

ret:

    UlRelease (lpsec);
    DebugTraceResult (ScInsertImportedRule(), hr);
    return GetScode (hr);
}

SCODE
ScReadRule (LPSCD lpscd, HWND hctrl, HFILE hf)
{
    SCODE sc = S_OK;
    LPSPropValue lpval = NULL;
    ULONG cb;

    if (_lread (hf, (LPBYTE)&cb, sizeof(ULONG)) == sizeof(ULONG))
    {
        sc = (*lpscd->lpfnAlloc) (cb, &lpval);
        if (!FAILED (sc))
        {
            if (_lread (hf, (LPBYTE)lpval, (UINT)cb) == cb)
            {
                sc = ScNormalizeFlattenedProps (cpRLMax, lpval, 0, lpval);
                if (!FAILED (sc))
                    sc = ScInsertImportedRule (lpscd, hctrl, lpval);
            }
        }
    }

    (*lpscd->lpfnFree) (lpval);
    DebugTraceSc (ScWriteRule(), sc);
    return sc;
}

SCODE
ScImportFilters (LPSCD lpscd, HWND hwnd, HWND hctrl)
{
    SCODE sc = S_OK;
    BOOL fCtl3d = FALSE;
    CHAR rgch[MAX_PATH];
    CHAR rgchT[MAX_PATH + 60];
    CHAR rgb[sizeof(rgbExportKey)];
    HFILE hf;
    OFSTRUCT ob;
    UINT ofFlags = OF_READ | OF_SHARE_EXCLUSIVE;
    UINT irl;
    ULONG crlNew;
    HINSTANCE hlib = NULL;
    LPCOMMDLG_GETOPENFILENAME lpfnOpen;
    LPCOMMDLG_GETSAVEFILENAME lpfnSave;
    OPENFILENAME ofn = {sizeof(OPENFILENAME)};

    lstrcpy (rgch, "*.mrl");
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Filter Export Files (*.mrl)\0*.mrl\0";
    ofn.lpstrFile = rgch;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = "Open Filter Export File";
    ofn.lpstrDefExt = "MRL";
    ofn.hInstance = lpscd->hinst;
    ofn.lCustData = 0;
    ofn.lpfnHook = (LPOFNHOOKPROC)CommdlgHook;
    ofn.Flags = OFN_PATHMUSTEXIST       |
                OFN_HIDEREADONLY        |
                OFN_ENABLEHOOK          |
                OFN_NOCHANGEDIR;

    if (FWin4Shell ())
        ofn.Flags |= OFN_EXPLORER;

    if (!FAILED (ScSetupCommdlg (&hlib, &lpfnOpen, &lpfnSave)))
    {
        if ((*lpfnOpen) (&ofn))
        {
            hf = OpenFile (rgch, &ob, ofFlags);
            if (hf != HFILE_ERROR)
            {
                _lread (hf, (LPBYTE)rgb, sizeof(rgbExportKey));
                if (!memcmp (rgb, rgbExportKey, sizeof(rgbExportKey)))
                {
                    _lread (hf, (LPBYTE)&crlNew, sizeof(ULONG));
                    for (irl = 0; irl < crlNew; irl++)
                    {
                        sc = ScReadRule (lpscd, hctrl, hf);
                        if (FAILED (sc))
                            break;
                    }
                }
                else
                {
                    wsprintf (rgchT,
                        "'%s' is an invalid or corrupt exported filter file.",
                        rgch);

                    if (CTL3D_GetVer(lpCtl3D) >= 0x220 && !CTL3D_IsAutoSubclass(lpCtl3D))
                        CTL3D_AutoSubclass (lpCtl3D, lpscd->hinst, &fCtl3d);
                    MessageBox (hwnd,
                        rgchT,
                        "Import filters",
                        MB_TASKMODAL | MB_OK | MB_ICONINFORMATION);
                    CTL3D_CeaseAutoSubclass(lpCtl3D, fCtl3d);
                }

                _lclose (hf);
            }
        }
        else
            sc = MAPI_E_USER_CANCEL;

        FreeLibrary (hlib);
    }

    DebugTraceSc (ScImportFilters(), sc);
    return sc;
}


SMHMDB.C

/*
 *  S M H M D B . C
 *
 *  Sample mail handling hook
 *  Store Tables
 *
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

/*
 *  sptStoTbl
 *
 *  The store table column set and enum
 */
enum { ipNull, ipEid, ipDispNm, ipRsrcFlags, cpStoTblMax };
static const SizedSPropTagArray (cpStoTblMax, sptStoTbl) =
{
    cpStoTblMax,
    {
        PR_NULL,
        PR_ENTRYID,
        PR_DISPLAY_NAME_A,
        PR_RESOURCE_FLAGS
    }
};

/*
 *  HrInitStoresTable()
 *
 *  Purpose:
 *
 *      Inits the stores table stucture and adds a reference to the SMH
 *      parent object.  This is done by getting the message stores table
 *      from the MAPI session and querying all rows (up to cStoMax) and
 *      mapping the return to an LPSTOTABLE structure.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 *      lpsess          the MAPI session that this SMH instance belongs to
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrInitStoresTable (LPSMH lpsmh, LPMAPISESSION lpsess)
{
    HRESULT hr;
    LPMAPITABLE lptbl;
    LPSRowSet lprws = NULL;
    LPSTOTABLE lpstotbl;
    UINT i;

    Assert (lpsmh->lpstotbl == NULL);
    hr = lpsess->lpVtbl->GetMsgStoresTable (lpsess, 0, &lptbl);
    if (!HR_FAILED (hr))
    {
        hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptStoTbl, 0);
        if (!HR_FAILED (hr))
        {
            hr = lptbl->lpVtbl->QueryRows (lptbl, cStoMax, 0, &lprws);
            if (!HR_FAILED (hr))
            {
                /*  Zero fill the initial property in all rows.
                 *  This has the nice side effect of NULL'ing the
                 *  lpmdb member of the structure
                 */
                lpstotbl = (LPSTOTABLE)lprws;
                for (i = 0; i < lpstotbl->cSto; i++)
                    memset (&lpstotbl->aSto[i].lpstoe->valPad,
                        0, sizeof(SPropValue));

                lpsmh->lpstotbl = lpstotbl;
            }
        }
        UlRelease (lptbl);
    }

    DebugTraceResult (HrInitStoresTable(), hr);
    return hr;
}

/*
 *  ReleaseStoresTable()
 *
 *  Purpose:
 *
 *      Releases all resources owned by the store table and disconnects
 *      the SMH parent from the table.
 *
 *  Arguments:
 *
 *      lpsmh       the SMH parent object
 */
VOID
ReleaseStoresTable (LPSMH lpsmh)
{
    LPSTOTABLE lpstotbl = lpsmh->lpstotbl;
    UINT i;

    if (lpstotbl)
    {
        for (i = 0; i < lpstotbl->cSto; i++)
        {
            UlRelease (lpstotbl->aSto[i].lpstoe->lpmdb);
            (*lpsmh->lpfnFree) (lpstotbl->aSto[i].lpProps);
        }
        (*lpsmh->lpfnFree) (lpstotbl);
        lpsmh->lpstotbl = NULL;
    }
    return;
}

/*
 *  HrOpenStoEntry()
 *
 *  Purpose:
 *
 *      Opens the store associated with a stoenty in the stores table.
 *
 *      IMPORTANT: the caller does not end up owning the store reference
 *      and should NOT call Release() on the store unless they also call
 *      AddRef().  The referece is owned by the stores table and is
 *      released upon the tables destruction.
 *
 *  Arguments:
 *
 *      lpsess          the MAPI session object
 *      lpsto           the STO structure for the MDB to open
 *      lppmdb  [OUT]   contains the LPMDB iff the call succeeds
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrOpenStoEntry (LPMAPISESSION lpsess, LPSTO lpsto, LPMDB FAR * lppmdb)
{
    HRESULT hr = hrSuccess;
    LPMDB lpmdb = NULL;

    if (!lpsto->lpstoe->lpmdb)
    {
        hr = lpsess->lpVtbl->OpenMsgStore (lpsess,
                                0,
                                lpsto->lpProps[ipEid].Value.bin.cb,
                                (LPENTRYID)lpsto->lpProps[ipEid].Value.bin.lpb,
                                NULL,
                                MDB_WRITE | MDB_NO_DIALOG | MDB_NO_MAIL,
                                &lpmdb);
        if (!HR_FAILED (hr))
            lpsto->lpstoe->lpmdb = lpmdb;

        *lppmdb = lpmdb;
    }
    else
    {
        *lppmdb = lpsto->lpstoe->lpmdb;
    }

    DebugTraceResult (HrOpenStoEntry(), hr);
    return hr;
}

/*
 *  HrOpenMdbFromEid()
 *
 *  Purpose:
 *
 *      Opens a message store by entryid.  The entryid is looked up in
 *      the stores table to find the proper STOENTRY.  If no entry is
 *      found, then the store is not opened.  If one is found, then the
 *      MDB is retrieved from the STOENTRY.  If it has not yet been opened,
 *      the MDB is opened at this time and stored (no pun intended).
 *
 *      IMPORTANT: the caller does not end up owning the store reference
 *      and should NOT call Release() on the store unless they also call
 *      AddRef().  The referece is owned by the stores table and is
 *      released upon the tables destruction.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 *      cbeid           size of the stores entryid
 *      lpeid           the entryid to be opened
 *      lppmdb  [OUT]   contains the LPMDB iff the call succeeds
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrOpenMdbFromEid (LPSMH lpsmh, ULONG cbeid, LPENTRYID lpeid, LPMDB FAR * lppmdb)
{
    HRESULT hr = ResultFromScode (MAPI_E_NOT_FOUND);
    LPSTOTABLE lpstotbl = lpsmh->lpstotbl;
    UINT i;

    if (lpstotbl)
    {
        for (i = 0; i < lpstotbl->cSto; i++)
        {
            if ((cbeid == lpstotbl->aSto[i].lpProps[ipEid].Value.bin.cb) &&
                !memcmp (lpeid, lpstotbl->aSto[i].lpProps[ipEid].Value.bin.lpb, (UINT)cbeid))
            {
                if (lpstotbl->aSto[i].lpstoe->lpmdb)
                {
                    *lppmdb = lpstotbl->aSto[i].lpstoe->lpmdb;
                    hr = hrSuccess;
                }
                else
                    hr = HrOpenStoEntry (lpsmh->lpsess, &lpstotbl->aSto[i], lppmdb);

                break;
            }
        }
    }

    if (HR_FAILED (hr))
        *lppmdb = NULL;

    DebugTraceResult (HrOpenMdbFromEid(), hr);
    return hr;
}

/*
 *  HrOpenMdbFromName()
 *
 *  Purpose:
 *
 *      Opens a message store by PR_DISPLAY_NAME value.  The name is
 *      looked up in the stores table to find the proper STOENTRY.  If no
 *      entry is found, then the store is not opened.  If one is found,
 *      then the MDB is retrieved from the STOENTRY.  If it has not yet
 *      been opened, the MDB is opened at this time and stored (no pun
 *      intended).
 *
 *      IMPORTANT: the caller does not end up owning the store reference
 *      and should NOT call Release() on the store unless they also call
 *      AddRef().  The referece is owned by the stores table and is
 *      released upon the tables destruction.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 *      lpszName        the name of the store to open
 *      lppmdb  [OUT]   contains the LPMDB iff the call succeeds
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrOpenMdbFromName (LPSMH lpsmh, LPTSTR lpszName, LPMDB FAR * lppmdb)
{
    HRESULT hr = ResultFromScode (MAPI_E_NOT_FOUND);
    LPSTOTABLE lpstotbl = lpsmh->lpstotbl;
    UINT i;

    if (lpstotbl)
    {
        for (i = 0; i < lpstotbl->cSto; i++)
        {
            if (!lstrcmpi (lpszName, lpstotbl->aSto[i].lpProps[ipDispNm].Value.LPSZ))
            {
                if (lpstotbl->aSto[i].lpstoe->lpmdb)
                {
                    *lppmdb = lpstotbl->aSto[i].lpstoe->lpmdb;
                    hr = hrSuccess;
                }
                else
                    hr = HrOpenStoEntry (lpsmh->lpsess, &lpstotbl->aSto[i], lppmdb);

                break;
            }
        }
    }

    if (HR_FAILED (hr))
        *lppmdb = NULL;

    DebugTraceResult (HrOpenMdbFromEid(), hr);
    return hr;
}


SMHNLS.H

/**************************************************************************/
/*      Case and Diacritic Insensitive Weight Table (DOS 850)             */
/**************************************************************************/

#pragma warning(disable:4305)
unsigned char rgchCidi[] =
{
      0,  /* (unprintable)       */
      1,  /* (unprintable)       */
      2,  /* (unprintable)       */
      3,  /* (unprintable)       */
      4,  /* (unprintable)       */
      5,  /* (unprintable)       */
      6,  /* (unprintable)       */
      7,  /* (unprintable)       */
      8,  /* (unprintable)       */
      9,  /* (unprintable)       */
     10,  /* (unprintable)       */
     11,  /* (unprintable)       */
     12,  /* (unprintable)       */
     13,  /* (unprintable)       */
     14,  /* (unprintable)       */
     15,  /* (unprintable)       */

     16,  /* (unprintable)       */
     17,  /* (unprintable)       */
     18,  /* (unprintable)       */
     19,  /* (unprintable)       */
     20,  /* (unprintable)       */
     21,  /* (unprintable)       */
     22,  /* (unprintable)       */
     23,  /* (unprintable)       */
     24,  /* (unprintable)       */
     25,  /* (unprintable)       */
     26,  /* (unprintable)       */
     27,  /* (unprintable)       */
     28,  /* (unprintable)       */
     29,  /* (unprintable)       */
     30,  /* (unprintable)       */
     31,  /* (unprintable)       */

     32,  /* space               */
     33,  /* !                   */
     34,  /* "                   */
     35,  /* #                   */
     36,  /* $                   */
     37,  /* %                   */
     38,  /* &                   */
     39,  /* '                   */
     40,  /* (                   */
     41,  /* )                   */
     42,  /* *                   */
     43,  /* +                   */
     44,  /* ,                   */
     45,  /* -                   */
     46,  /* .                   */
     47,  /* /                   */

     79,  /* 0                   */
     80,  /* 1                   */
     81,  /* 2                   */
     82,  /* 3                   */
     83,  /* 4                   */
     84,  /* 5                   */
     85,  /* 6                   */
     86,  /* 7                   */
     87,  /* 8                   */
     88,  /* 9                   */
     48,  /* :                   */
     49,  /* ;                   */
     50,  /* <                   */
     51,  /* =                   */
     52,  /* >                   */
     53,  /* ?                   */

     54,  /* @                   */
     89,  /* A                   */
    106,  /* B                   */
    108,  /* C                   */
    112,  /* D                   */
    116,  /* E                   */
    126,  /* F                   */
    128,  /* G                   */
    130,  /* H                   */
    132,  /* I                   */
    143,  /* J                   */
    145,  /* K                   */
    147,  /* L                   */
    149,  /* M                   */
    152,  /* N                   */
    156,  /* O                   */

    171,  /* P                   */
    175,  /* Q                   */
    177,  /* R                   */
    179,  /* S                   */
    182,  /* T                   */
    184,  /* U                   */
    194,  /* V                   */
    196,  /* W                   */
    198,  /* X                   */
    200,  /* Y                   */
    205,  /* Z                   */
     55,  /* [                   */
     56,  /* \                   */
     57,  /* ]                   */
     58,  /* ^                   */
     59,  /* _                   */

     60,  /* back quote          */
     89,  /* a                   */
    106,  /* b                   */
    108,  /* c                   */
    112,  /* d                   */
    116,  /* e                   */
    126,  /* f                   */
    128,  /* g                   */
    130,  /* h                   */
    132,  /* i                   */
    143,  /* j                   */
    145,  /* k                   */
    147,  /* l                   */
    149,  /* m                   */
    152,  /* n                   */
    156,  /* o                   */

    171,  /* p                   */
    175,  /* q                   */
    177,  /* r                   */
    179,  /* s                   */
    182,  /* t                   */
    184,  /* u                   */
    194,  /* v                   */
    196,  /* w                   */
    198,  /* x                   */
    200,  /* y                   */
    205,  /* z                   */
     61,  /* {                   */
     62,  /* |                   */
     63,  /* }                   */
     64,  /* ~                   */
     65,  /* (graphic)           */

    108,  /* C cedilla           */
    184,  /* u umlaut            */
    116,  /* e acute             */
     89,  /* a circumflex        */
     89,  /* a umlaut            */
     89,  /* a grave             */
     89,  /* a dot               */
    108,  /* c cedilla           */
    116,  /* e circumflex        */
    116,  /* e umlaut            */
    116,  /* e grave             */
    132,  /* i umlaut            */
    132,  /* i circumflex        */
    132,  /* i grave             */
     89,  /* A umlaut            */
     89,  /* A dot               */

    116,  /* E acute             */
    104,  /* ae ligature         */
    104,  /* AE ligature         */
    156,  /* o circumflex        */
    156,  /* o umlaut            */
    156,  /* o grave             */
    184,  /* u circumflex        */
    184,  /* u grave             */
    200,  /* y umlaut            */
    156,  /* O umlaut            */
    184,  /* U umlaut            */
    156,  /* o slash             */
     66,  /* pound sign          */
    156,  /* O slash             */
     67,  /* multiplication sign */
     68,  /* function sign       */

     89,  /* a acute             */
    132,  /* i acute             */
    156,  /* o acute             */
    184,  /* u acute             */
    152,  /* n tilde             */
    152,  /* N tilde             */
    103,  /* a underscore        */
    170,  /* o underscore        */
     69,  /* inverted ?          */
     70,  /* registered sign     */
    207,  /* logical not sign    */
     71,  /* 1/2                 */
     72,  /* 1/4                 */
     73,  /* inverted !          */
     74,  /* <<                  */
     75,  /* >>                  */

    208,  /* graphic 1           */
    209,  /* graphic 2           */
    210,  /* graphic 3           */
    211,  /* graphic 4           */
    212,  /* graphic 5           */
     89,  /* A acute             */
     89,  /* A circumflex        */
     89,  /* A grave             */
     76,  /* copyright sign      */
    213,  /* graphic 6           */
    214,  /* graphic 7           */
    215,  /* graphic 8           */
    216,  /* graphic 9           */
     77,  /* cent sign           */
     78,  /* yen sign            */
    217,  /* graphic 10          */

    218,  /* graphic 11          */
    219,  /* graphic 12          */
    220,  /* graphic 13          */
    221,  /* graphic 14          */
    222,  /* graphic 15          */
    223,  /* graphic 16          */
     89,  /* a tilde             */
     89,  /* A tilde             */
    224,  /* graphic 17          */
    225,  /* graphic 18          */
    226,  /* graphic 19          */
    227,  /* graphic 20          */
    228,  /* graphic 21          */
    229,  /* graphic 22          */
    230,  /* graphic 23          */
    231,  /* currency sign       */

    114,  /* d bar               */
    114,  /* D bar               */
    116,  /* E circumflex        */
    116,  /* E umlaut            */
    116,  /* E grave             */
    132,  /* i no dot            */
    132,  /* I acute             */
    132,  /* I circumflex        */
    132,  /* I umlaut            */
    232,  /* graphic 24          */
    233,  /* graphic 25          */
    234,  /* graphic 26          */
    235,  /* graphic 27          */
    236,  /* |                   */
    132,  /* I grave             */
    237,  /* graphic 28          */

    156,  /* O acute             */
    181,  /* double ss           */
    156,  /* O circumflex        */
    156,  /* O grave             */
    156,  /* o tilde             */
    156,  /* O tilde             */
    151,  /* micron              */
    173,  /* p bar               */
    173,  /* P bar               */
    184,  /* U acute             */
    184,  /* U circumflex        */
    184,  /* U grave             */
    200,  /* y acute             */
    200,  /* Y acute             */
    238,  /* upper line          */
    239,  /* acute accent        */

    240,  /* middle line         */
    241,  /* +/- sign            */
    242,  /* equal sign          */
    243,  /* 3/4                 */
    244,  /* paragraph sign      */
    245,  /* section sign        */
    246,  /* division sign       */
    247,  /* cedilla             */
    248,  /* degree sign         */
    249,  /* umlaut              */
    250,  /* middle dot          */
    251,  /* 1 superscript       */
    253,  /* 3 superscript       */
    252,  /* 2 superscript       */
    254,  /* graphic 29          */
    255   /* blank               */
};

/**************************************************************************/
/*      Case Insensitive and Diacritic Sensitive Weight Table   (DOS 850) */
/**************************************************************************/

unsigned char rgchCids[] =
{
      0,  /* (unprintable)       */
      1,  /* (unprintable)       */
      2,  /* (unprintable)       */
      3,  /* (unprintable)       */
      4,  /* (unprintable)       */
      5,  /* (unprintable)       */
      6,  /* (unprintable)       */
      7,  /* (unprintable)       */
      8,  /* (unprintable)       */
      9,  /* (unprintable)       */
     10,  /* (unprintable)       */
     11,  /* (unprintable)       */
     12,  /* (unprintable)       */
     13,  /* (unprintable)       */
     14,  /* (unprintable)       */
     15,  /* (unprintable)       */

     16,  /* (unprintable)       */
     17,  /* (unprintable)       */
     18,  /* (unprintable)       */
     19,  /* (unprintable)       */
     20,  /* (unprintable)       */
     21,  /* (unprintable)       */
     22,  /* (unprintable)       */
     23,  /* (unprintable)       */
     24,  /* (unprintable)       */
     25,  /* (unprintable)       */
     26,  /* (unprintable)       */
     27,  /* (unprintable)       */
     28,  /* (unprintable)       */
     29,  /* (unprintable)       */
     30,  /* (unprintable)       */
     31,  /* (unprintable)       */

     32,  /* space               */
     33,  /* !                   */
     34,  /* "                   */
     35,  /* #                   */
     36,  /* $                   */
     37,  /* %                   */
     38,  /* &                   */
     39,  /* '                   */
     40,  /* (                   */
     41,  /* )                   */
     42,  /* *                   */
     43,  /* +                   */
     44,  /* ,                   */
     45,  /* -                   */
     46,  /* .                   */
     47,  /* /                   */

     79,  /* 0                   */
     80,  /* 1                   */
     81,  /* 2                   */
     82,  /* 3                   */
     83,  /* 4                   */
     84,  /* 5                   */
     85,  /* 6                   */
     86,  /* 7                   */
     87,  /* 8                   */
     88,  /* 9                   */
     48,  /* :                   */
     49,  /* ;                   */
     50,  /* <                   */
     51,  /* =                   */
     52,  /* >                   */
     53,  /* ?                   */

     54,  /* @                   */
     89,  /* A                   */
    106,  /* B                   */
    108,  /* C                   */
    112,  /* D                   */
    116,  /* E                   */
    126,  /* F                   */
    128,  /* G                   */
    130,  /* H                   */
    132,  /* I                   */
    143,  /* J                   */
    145,  /* K                   */
    147,  /* L                   */
    149,  /* M                   */
    152,  /* N                   */
    156,  /* O                   */

    171,  /* P                   */
    175,  /* Q                   */
    177,  /* R                   */
    179,  /* S                   */
    182,  /* T                   */
    184,  /* U                   */
    194,  /* V                   */
    196,  /* W                   */
    198,  /* X                   */
    200,  /* Y                   */
    205,  /* Z                   */
     55,  /* [                   */
     56,  /* \                   */
     57,  /* ]                   */
     58,  /* ^                   */
     59,  /* _                   */

     60,  /* back quote          */
     89,  /* a                   */
    106,  /* b                   */
    108,  /* c                   */
    112,  /* d                   */
    116,  /* e                   */
    126,  /* f                   */
    128,  /* g                   */
    130,  /* h                   */
    132,  /* i                   */
    143,  /* j                   */
    145,  /* k                   */
    147,  /* l                   */
    149,  /* m                   */
    152,  /* n                   */
    156,  /* o                   */

    171,  /* p                   */
    175,  /* q                   */
    177,  /* r                   */
    179,  /* s                   */
    182,  /* t                   */
    184,  /* u                   */
    194,  /* v                   */
    196,  /* w                   */
    198,  /* x                   */
    200,  /* y                   */
    205,  /* z                   */
     61,  /* {                   */
     62,  /* |                   */
     63,  /* }                   */
     64,  /* ~                   */
     65,  /* (graphic)           */

    110,  /* C cedilla           */
    190,  /* u umlaut            */
    118,  /* e acute             */
     97,  /* a circumflex        */
     95,  /* a umlaut            */
     93,  /* a grave             */
     99,  /* a dot               */
    110,  /* c cedilla           */
    124,  /* e circumflex        */
    122,  /* e umlaut            */
    120,  /* e grave             */
    139,  /* i umlaut            */
    141,  /* i circumflex        */
    137,  /* i grave             */
     95,  /* A umlaut            */
     99,  /* A dot               */

    118,  /* E acute             */
    104,  /* ae ligature         */
    104,  /* AE ligature         */
    164,  /* o circumflex        */
    162,  /* o umlaut            */
    160,  /* o grave             */
    192,  /* u circumflex        */
    188,  /* u grave             */
    204,  /* y umlaut            */
    162,  /* O umlaut            */
    190,  /* U umlaut            */
    168,  /* o slash             */
     66,  /* pound sign          */
    168,  /* O slash             */
     67,  /* multiplication sign */
     68,  /* function sign       */

     91,  /* a acute             */
    135,  /* i acute             */
    158,  /* o acute             */
    186,  /* u acute             */
    154,  /* n tilde             */
    154,  /* N tilde             */
    103,  /* a underscore        */
    170,  /* o underscore        */
     69,  /* inverted ?          */
     70,  /* registered sign     */
    207,  /* logical not sign    */
     71,  /* 1/2                 */
     72,  /* 1/4                 */
     73,  /* inverted !          */
     74,  /* <<                  */
     75,  /* >>                  */

    208,  /* graphic 1           */
    209,  /* graphic 2           */
    210,  /* graphic 3           */
    211,  /* graphic 4           */
    212,  /* graphic 5           */
     91,  /* A acute             */
     97,  /* A circumflex        */
     93,  /* A grave             */
     76,  /* copyright sign      */
    213,  /* graphic 6           */
    214,  /* graphic 7           */
    215,  /* graphic 8           */
    216,  /* graphic 9           */
     77,  /* cent sign           */
     78,  /* yen sign            */
    217,  /* graphic 10          */

    218,  /* graphic 11          */
    219,  /* graphic 12          */
    220,  /* graphic 13          */
    221,  /* graphic 14          */
    222,  /* graphic 15          */
    223,  /* graphic 16          */
    101,  /* a tilde             */
    101,  /* A tilde             */
    224,  /* graphic 17          */
    225,  /* graphic 18          */
    226,  /* graphic 19          */
    227,  /* graphic 20          */
    228,  /* graphic 21          */
    229,  /* graphic 22          */
    230,  /* graphic 23          */
    231,  /* currency sign       */

    114,  /* d bar               */
    114,  /* D bar               */
    124,  /* E circumflex        */
    122,  /* E umlaut            */
    120,  /* E grave             */
    134,  /* i no dot            */
    135,  /* I acute             */
    141,  /* I circumflex        */
    139,  /* I umlaut            */
    232,  /* graphic 24          */
    233,  /* graphic 25          */
    234,  /* graphic 26          */
    235,  /* graphic 27          */
    236,  /* |                   */
    137,  /* I grave             */
    237,  /* graphic 28          */

    158,  /* O acute             */
    181,  /* double ss           */
    164,  /* O circumflex        */
    160,  /* O grave             */
    166,  /* o tilde             */
    166,  /* O tilde             */
    151,  /* micron              */
    173,  /* p bar               */
    173,  /* P bar               */
    186,  /* U acute             */
    192,  /* U circumflex        */
    188,  /* U grave             */
    202,  /* y acute             */
    202,  /* Y acute             */
    238,  /* upper line          */
    239,  /* acute accent        */

    240,  /* middle line         */
    241,  /* +/- sign            */
    242,  /* equal sign          */
    243,  /* 3/4                 */
    244,  /* paragraph sign      */
    245,  /* section sign        */
    246,  /* division sign       */
    247,  /* cedilla             */
    248,  /* degree sign         */
    249,  /* umlaut              */
    250,  /* middle dot          */
    251,  /* 1 superscript       */
    253,  /* 3 superscript       */
    252,  /* 2 superscript       */
    254,  /* graphic 29          */
    255   /* blank               */
};

/**************************************************************************/
/*      Case Sensitive and Diacritic Insenstive Weight Table    (DOS 850) */
/**************************************************************************/

unsigned char rgchCsdi[] =
{
      0,  /* (unprintable)       */
      1,  /* (unprintable)       */
      2,  /* (unprintable)       */
      3,  /* (unprintable)       */
      4,  /* (unprintable)       */
      5,  /* (unprintable)       */
      6,  /* (unprintable)       */
      7,  /* (unprintable)       */
      8,  /* (unprintable)       */
      9,  /* (unprintable)       */
     10,  /* (unprintable)       */
     11,  /* (unprintable)       */
     12,  /* (unprintable)       */
     13,  /* (unprintable)       */
     14,  /* (unprintable)       */
     15,  /* (unprintable)       */

     16,  /* (unprintable)       */
     17,  /* (unprintable)       */
     18,  /* (unprintable)       */
     19,  /* (unprintable)       */
     20,  /* (unprintable)       */
     21,  /* (unprintable)       */
     22,  /* (unprintable)       */
     23,  /* (unprintable)       */
     24,  /* (unprintable)       */
     25,  /* (unprintable)       */
     26,  /* (unprintable)       */
     27,  /* (unprintable)       */
     28,  /* (unprintable)       */
     29,  /* (unprintable)       */
     30,  /* (unprintable)       */
     31,  /* (unprintable)       */

     32,  /* space               */
     33,  /* !                   */
     34,  /* "                   */
     35,  /* #                   */
     36,  /* $                   */
     37,  /* %                   */
     38,  /* &                   */
     39,  /* '                   */
     40,  /* (                   */
     41,  /* )                   */
     42,  /* *                   */
     43,  /* +                   */
     44,  /* ,                   */
     45,  /* -                   */
     46,  /* .                   */
     47,  /* /                   */

     79,  /* 0                   */
     80,  /* 1                   */
     81,  /* 2                   */
     82,  /* 3                   */
     83,  /* 4                   */
     84,  /* 5                   */
     85,  /* 6                   */
     86,  /* 7                   */
     87,  /* 8                   */
     88,  /* 9                   */
     48,  /* :                   */
     49,  /* ;                   */
     50,  /* <                   */
     51,  /* =                   */
     52,  /* >                   */
     53,  /* ?                   */

     54,  /* @                   */
     90,  /* A                   */
    107,  /* B                   */
    109,  /* C                   */
    113,  /* D                   */
    117,  /* E                   */
    127,  /* F                   */
    129,  /* G                   */
    131,  /* H                   */
    133,  /* I                   */
    144,  /* J                   */
    146,  /* K                   */
    148,  /* L                   */
    150,  /* M                   */
    153,  /* N                   */
    157,  /* O                   */

    172,  /* P                   */
    176,  /* Q                   */
    178,  /* R                   */
    180,  /* S                   */
    183,  /* T                   */
    185,  /* U                   */
    195,  /* V                   */
    197,  /* W                   */
    199,  /* X                   */
    201,  /* Y                   */
    206,  /* Z                   */
     55,  /* [                   */
     56,  /* \                   */
     57,  /* ]                   */
     58,  /* ^                   */
     59,  /* _                   */

     60,  /* back quote          */
     89,  /* a                   */
    106,  /* b                   */
    108,  /* c                   */
    112,  /* d                   */
    116,  /* e                   */
    126,  /* f                   */
    128,  /* g                   */
    130,  /* h                   */
    132,  /* i                   */
    143,  /* j                   */
    145,  /* k                   */
    147,  /* l                   */
    149,  /* m                   */
    152,  /* n                   */
    156,  /* o                   */

    171,  /* p                   */
    175,  /* q                   */
    177,  /* r                   */
    179,  /* s                   */
    182,  /* t                   */
    184,  /* u                   */
    194,  /* v                   */
    196,  /* w                   */
    198,  /* x                   */
    200,  /* y                   */
    205,  /* z                   */
     61,  /* {                   */
     62,  /* |                   */
     63,  /* }                   */
     64,  /* ~                   */
     65,  /* (graphic)           */

    109,  /* C cedilla           */
    184,  /* u umlaut            */
    116,  /* e acute             */
     89,  /* a circumflex        */
     89,  /* a umlaut            */
     89,  /* a grave             */
     89,  /* a dot               */
    108,  /* c cedilla           */
    116,  /* e circumflex        */
    116,  /* e umlaut            */
    116,  /* e grave             */
    132,  /* i umlaut            */
    132,  /* i circumflex        */
    132,  /* i grave             */
     90,  /* A umlaut            */
     90,  /* A dot               */

    117,  /* E acute             */
    104,  /* ae ligature         */
    105,  /* AE ligature         */
    156,  /* o circumflex        */
    156,  /* o umlaut            */
    156,  /* o grave             */
    184,  /* u circumflex        */
    184,  /* u grave             */
    200,  /* y umlaut            */
    157,  /* O umlaut            */
    185,  /* U umlaut            */
    156,  /* o slash             */
     66,  /* pound sign          */
    157,  /* O slash             */
     67,  /* multiplication sign */
     68,  /* function sign       */

     89,  /* a acute             */
    132,  /* i acute             */
    156,  /* o acute             */
    184,  /* u acute             */
    152,  /* n tilde             */
    153,  /* N tilde             */
    103,  /* a underscore        */
    170,  /* o underscore        */
     69,  /* inverted ?          */
     70,  /* registered sign     */
    207,  /* logical not sign    */
     71,  /* 1/2                 */
     72,  /* 1/4                 */
     73,  /* inverted !          */
     74,  /* <<                  */
     75,  /* >>                  */

    208,  /* graphic 1           */
    209,  /* graphic 2           */
    210,  /* graphic 3           */
    211,  /* graphic 4           */
    212,  /* graphic 5           */
     90,  /* A acute             */
     90,  /* A circumflex        */
     90,  /* A grave             */
     76,  /* copyright sign      */
    213,  /* graphic 6           */
    214,  /* graphic 7           */
    215,  /* graphic 8           */
    216,  /* graphic 9           */
     77,  /* cent sign           */
     78,  /* yen sign            */
    217,  /* graphic 10          */

    218,  /* graphic 11          */
    219,  /* graphic 12          */
    220,  /* graphic 13          */
    221,  /* graphic 14          */
    222,  /* graphic 15          */
    223,  /* graphic 16          */
     89,  /* a tilde             */
     90,  /* A tilde             */
    224,  /* graphic 17          */
    225,  /* graphic 18          */
    226,  /* graphic 19          */
    227,  /* graphic 20          */
    228,  /* graphic 21          */
    229,  /* graphic 22          */
    230,  /* graphic 23          */
    231,  /* currency sign       */

    114,  /* d bar               */
    114,  /* D bar               */
    117,  /* E circumflex        */
    117,  /* E umlaut            */
    117,  /* E grave             */
    132,  /* i no dot            */
    133,  /* I acute             */
    133,  /* I circumflex        */
    133,  /* I umlaut            */
    232,  /* graphic 24          */
    233,  /* graphic 25          */
    234,  /* graphic 26          */
    235,  /* graphic 27          */
    236,  /* |                   */
    133,  /* I grave             */
    237,  /* graphic 28          */

    157,  /* O acute             */
    181,  /* double ss           */
    157,  /* O circumflex        */
    157,  /* O grave             */
    156,  /* o tilde             */
    157,  /* O tilde             */
    151,  /* micron              */
    173,  /* p bar               */
    174,  /* P bar               */
    185,  /* U acute             */
    185,  /* U circumflex        */
    185,  /* U grave             */
    200,  /* y acute             */
    201,  /* Y acute             */
    238,  /* upper line          */
    239,  /* acute accent        */

    240,  /* middle line         */
    241,  /* +/- sign            */
    242,  /* equal sign          */
    243,  /* 3/4                 */
    244,  /* paragraph sign      */
    245,  /* section sign        */
    246,  /* division sign       */
    247,  /* cedilla             */
    248,  /* degree sign         */
    249,  /* umlaut              */
    250,  /* middle dot          */
    251,  /* 1 superscript       */
    253,  /* 3 superscript       */
    252,  /* 2 superscript       */
    254,  /* graphic 29          */
    255   /* blank               */
};

/**************************************************************************/
/*      Case and Diacritic Senstive Weight Table                (DOS 850) */

/**************************************************************************/

unsigned char rgchCsds[] =
{
      0,  /* (unprintable)       */
      1,  /* (unprintable)       */
      2,  /* (unprintable)       */
      3,  /* (unprintable)       */
      4,  /* (unprintable)       */
      5,  /* (unprintable)       */
      6,  /* (unprintable)       */
      7,  /* (unprintable)       */
      8,  /* (unprintable)       */
      9,  /* (unprintable)       */
     10,  /* (unprintable)       */
     11,  /* (unprintable)       */
     12,  /* (unprintable)       */
     13,  /* (unprintable)       */
     14,  /* (unprintable)       */
     15,  /* (unprintable)       */

     16,  /* (unprintable)       */
     17,  /* (unprintable)       */
     18,  /* (unprintable)       */
     19,  /* (unprintable)       */
     20,  /* (unprintable)       */
     21,  /* (unprintable)       */
     22,  /* (unprintable)       */
     23,  /* (unprintable)       */
     24,  /* (unprintable)       */
     25,  /* (unprintable)       */
     26,  /* (unprintable)       */
     27,  /* (unprintable)       */
     28,  /* (unprintable)       */
     29,  /* (unprintable)       */
     30,  /* (unprintable)       */
     31,  /* (unprintable)       */

     32,  /* space               */
     33,  /* !                   */
     34,  /* "                   */
     35,  /* #                   */
     36,  /* $                   */
     37,  /* %                   */
     38,  /* &                   */
     39,  /* '                   */
     40,  /* (                   */
     41,  /* )                   */
     42,  /* *                   */
     43,  /* +                   */
     44,  /* ,                   */
     45,  /* -                   */
     46,  /* .                   */
     47,  /* /                   */

     79,  /* 0                   */
     80,  /* 1                   */
     81,  /* 2                   */
     82,  /* 3                   */
     83,  /* 4                   */
     84,  /* 5                   */
     85,  /* 6                   */
     86,  /* 7                   */
     87,  /* 8                   */
     88,  /* 9                   */
     48,  /* :                   */
     49,  /* ;                   */
     50,  /* <                   */
     51,  /* =                   */
     52,  /* >                   */
     53,  /* ?                   */

     54,  /* @                   */
     90,  /* A                   */
    107,  /* B                   */
    109,  /* C                   */
    113,  /* D                   */
    117,  /* E                   */
    127,  /* F                   */
    129,  /* G                   */
    131,  /* H                   */
    133,  /* I                   */
    144,  /* J                   */
    146,  /* K                   */
    148,  /* L                   */
    150,  /* M                   */
    153,  /* N                   */
    157,  /* O                   */

    172,  /* P                   */
    176,  /* Q                   */
    178,  /* R                   */
    180,  /* S                   */
    183,  /* T                   */
    185,  /* U                   */
    195,  /* V                   */
    197,  /* W                   */
    199,  /* X                   */
    201,  /* Y                   */
    206,  /* Z                   */
     55,  /* [                   */
     56,  /* \                   */
     57,  /* ]                   */
     58,  /* ^                   */
     59,  /* _                   */

     60,  /* back quote          */
     89,  /* a                   */
    106,  /* b                   */
    108,  /* c                   */
    112,  /* d                   */
    116,  /* e                   */
    126,  /* f                   */
    128,  /* g                   */
    130,  /* h                   */
    132,  /* i                   */
    143,  /* j                   */
    145,  /* k                   */
    147,  /* l                   */
    149,  /* m                   */
    152,  /* n                   */
    156,  /* o                   */

    171,  /* p                   */
    175,  /* q                   */
    177,  /* r                   */
    179,  /* s                   */
    182,  /* t                   */
    184,  /* u                   */
    194,  /* v                   */
    196,  /* w                   */
    198,  /* x                   */
    200,  /* y                   */
    205,  /* z                   */
     61,  /* {                   */
     62,  /* |                   */
     63,  /* }                   */
     64,  /* ~                   */
     65,  /* (graphic)           */

    111,  /* C cedilla           */
    190,  /* u umlaut            */
    118,  /* e acute             */
     97,  /* a circumflex        */
     95,  /* a umlaut            */
     93,  /* a grave             */
     99,  /* a dot               */
    110,  /* c cedilla           */
    124,  /* e circumflex        */
    122,  /* e umlaut            */
    120,  /* e grave             */
    139,  /* i umlaut            */
    141,  /* i circumflex        */
    137,  /* i grave             */
     96,  /* A umlaut            */
    100,  /* A dot               */

    119,  /* E acute             */
    104,  /* ae ligature         */
    105,  /* AE ligature         */
    164,  /* o circumflex        */
    162,  /* o umlaut            */
    160,  /* o grave             */
    192,  /* u circumflex        */
    188,  /* u grave             */
    204,  /* y umlaut            */
    163,  /* O umlaut            */
    191,  /* U umlaut            */
    168,  /* o slash             */
     66,  /* pound sign          */
    169,  /* O slash             */
     67,  /* multiplication sign */
     68,  /* function sign       */

     91,  /* a acute             */
    135,  /* i acute             */
    158,  /* o acute             */
    186,  /* u acute             */
    154,  /* n tilde             */
    155,  /* N tilde             */
    103,  /* a underscore        */
    170,  /* o underscore        */
     69,  /* inverted ?          */
     70,  /* registered sign     */
    207,  /* logical not sign    */
     71,  /* 1/2                 */
     72,  /* 1/4                 */
     73,  /* inverted !          */
     74,  /* <<                  */
     75,  /* >>                  */

    208,  /* graphic 1           */
    209,  /* graphic 2           */
    210,  /* graphic 3           */
    211,  /* graphic 4           */
    212,  /* graphic 5           */
     92,  /* A acute             */
     98,  /* A circumflex        */
     94,  /* A grave             */
     76,  /* copyright sign      */
    213,  /* graphic 6           */
    214,  /* graphic 7           */
    215,  /* graphic 8           */
    216,  /* graphic 9           */
     77,  /* cent sign           */
     78,  /* yen sign            */
    217,  /* graphic 10          */

    218,  /* graphic 11          */
    219,  /* graphic 12          */
    220,  /* graphic 13          */
    221,  /* graphic 14          */
    222,  /* graphic 15          */
    223,  /* graphic 16          */
    101,  /* a tilde             */
    102,  /* A tilde             */
    224,  /* graphic 17          */
    225,  /* graphic 18          */
    226,  /* graphic 19          */
    227,  /* graphic 20          */
    228,  /* graphic 21          */
    229,  /* graphic 22          */
    230,  /* graphic 23          */
    231,  /* currency sign       */

    114,  /* d bar               */
    115,  /* D bar               */
    125,  /* E circumflex        */
    123,  /* E umlaut            */
    121,  /* E grave             */
    134,  /* i no dot            */
    136,  /* I acute             */
    142,  /* I circumflex        */
    140,  /* I umlaut            */
    232,  /* graphic 24          */
    233,  /* graphic 25          */
    234,  /* graphic 26          */
    235,  /* graphic 27          */
    236,  /* |                   */
    138,  /* I grave             */
    237,  /* graphic 28          */

    159,  /* O acute             */
    181,  /* double ss           */
    165,  /* O circumflex        */
    161,  /* O grave             */
    166,  /* o tilde             */
    167,  /* O tilde             */
    151,  /* micron              */
    173,  /* p bar               */
    174,  /* P bar               */
    187,  /* U acute             */
    193,  /* U circumflex        */
    189,  /* U grave             */
    202,  /* y acute             */
    203,  /* Y acute             */
    238,  /* upper line          */
    239,  /* acute accent        */

    240,  /* middle line         */
    241,  /* +/- sign            */
    242,  /* equal sign          */
    243,  /* 3/4                 */
    244,  /* paragraph sign      */
    245,  /* section sign        */
    246,  /* division sign       */
    247,  /* cedilla             */
    248,  /* degree sign         */
    249,  /* umlaut              */
    250,  /* middle dot          */
    251,  /* 1 superscript       */
    252,  /* 3 superscript       */
    253,  /* 2 superscript       */
    254,  /* graphic 29          */
    255   /* blank               */
};


SMHOLE.C

/*
 *  S M H O L E . C
 *
 *  Sample mail handling hook OLE storage support for RICHEDIT
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
DEFINE_OLEGUID(IID_IRichEditOleCallback, 0x00020D03, 0, 0);
const REOC_Vtbl vtblREOC =
{
    REOC_QueryInterface,
    REOC_AddRef,
    REOC_Release,
    REOC_GetNewStorage,
    REOC_GetInPlaceContext,
    REOC_ShowContainerUI,
    REOC_QueryInsertObject,
    REOC_DeleteObject,
    REOC_QueryAcceptData,
    REOC_ContextSensitiveHelp,
    REOC_GetClipboardData,
    REOC_GetDragDropEffect,
    REOC_GetContextMenu
};

/*
 *  SMH Object Methods
 *
 *  SMH_QueryInterface      (See OLE IUnknown object methods)
 *  SMH_AddRef              (See OLE IUnknown object methods)
 *  SMH_Release             (See OLE IUnknown object methods)
 *  SMH_InboundMsgHook      Filters inbound messages
 *  SMH_OutboundMsgHook     Filters sent mail messages
 *
 */
STDMETHODIMP
REOC_QueryInterface (LPREOC lpreoc, REFIID lpiid, LPVOID FAR * lppv)
{
    if (!memcmp (lpiid, &IID_IRichEditOleCallback, sizeof(IID)) ||
        !memcmp (lpiid, &IID_IUnknown, sizeof(IID)))
    {
        *lppv = (LPVOID)lpreoc;
        lpreoc->lcInit++;
        return hrSuccess;
    }

    DebugTraceSc (SMH_QueryInterface(), MAPI_E_INTERFACE_NOT_SUPPORTED);
    return ResultFromScode (MAPI_E_INTERFACE_NOT_SUPPORTED);
}

STDMETHODIMP_(ULONG)
REOC_AddRef (LPREOC lpreoc)
{
    return ++lpreoc->lcInit;
}

STDMETHODIMP_(ULONG)
REOC_Release (LPREOC lpreoc)
{
    if (--lpreoc->lcInit)
        return lpreoc->lcInit;

    UlRelease (lpreoc->lpstg);
    (*lpreoc->lpfnFree) (lpreoc);
    return 0;
}

STDMETHODIMP
REOC_GetNewStorage (LPREOC lpreoc, LPSTORAGE FAR * lppstg)
{
    HRESULT hr = hrSuccess;
    LPSTORAGE lpstg = lpreoc->lpstg;

#ifdef  _WIN32
    WCHAR rgch[MAX_PATH];
    wsprintfW (rgch, L"SMHOBJ%08ld", lpreoc->cSub++);
#else
    CHAR rgch[MAX_PATH];
    wsprintf (rgch, "SMHOBJ%08ld", lpreoc->cSub++);
#endif  // WIN16

    if (lpstg)
    {
        lpstg->lpVtbl->CreateStorage (lpstg,
                            rgch,
                            STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
                            0,
                            0,
                            lppstg);
        if (HR_FAILED (hr))
            *lppstg = NULL;
    }
    DebugTraceResult (REOC_GetNewStorage(), hr);
    return hr;
}

STDMETHODIMP
REOC_GetInPlaceContext (LPREOC lpreoc,
   LPOLEINPLACEFRAME FAR * lppipframe,
   LPOLEINPLACEUIWINDOW FAR * lppipuiDoc,
   LPOLEINPLACEFRAMEINFO lpipfinfo)
{
    return ResultFromScode (E_NOTIMPL);
}

STDMETHODIMP
REOC_ShowContainerUI (LPREOC lpreoc, BOOL fShow)
{
    return ResultFromScode (E_NOTIMPL);
}

STDMETHODIMP
REOC_QueryInsertObject (LPREOC lpreoc, LPCLSID pclsid, LPSTORAGE pstg, LONG cp)
{
    return hrSuccess;
}

STDMETHODIMP
REOC_DeleteObject (LPREOC lpreoc, LPOLEOBJECT lpoleobj)
{
    return hrSuccess;
}

STDMETHODIMP
REOC_QueryAcceptData (LPREOC lpreoc,
    LPDATAOBJECT lpdataobj,
    CLIPFORMAT FAR * lpcfFormat,
    DWORD reco,
    BOOL fReally,
    HGLOBAL hMetaPict)
{
    return hrSuccess;
}

STDMETHODIMP
REOC_ContextSensitiveHelp (LPREOC lpreoc, BOOL fEnterMode)
{
    return hrSuccess;
}

STDMETHODIMP
REOC_GetClipboardData (LPREOC lpreoc,
    CHARRANGE FAR * lpchrg,
    DWORD reco,
    LPDATAOBJECT FAR * lppdataobj)
{
    return ResultFromScode (E_NOTIMPL);
}

STDMETHODIMP
REOC_GetDragDropEffect (LPREOC lpreoc,
    BOOL fDrag,
    DWORD grfKeyState,
    LPDWORD lpdwEffect)
{
    return ResultFromScode (E_NOTIMPL);
}

STDMETHODIMP
REOC_GetContextMenu (LPREOC lpreoc,
    WORD seltype,
    LPOLEOBJECT lpoleobj,
    CHARRANGE FAR * lpchrg,
    HMENU FAR * lphmenu)
{
    if (lpreoc->lpfb && (*lphmenu = CreatePopupMenu()))
    {
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Bullet, "Bullet Paragraph");
        AppendMenu (*lphmenu, MF_SEPARATOR, 0, NULL);
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Indent, "Increase Indent");
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Collapse, "Decrease Indent");
        AppendMenu (*lphmenu, MF_SEPARATOR, 0, NULL);
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Left, "Align Left");
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Center, "Align Center");
        AppendMenu (*lphmenu, MF_ENABLED | MF_STRING, ID_Right, "Align Right");
        if (lpreoc->lpfb->pf.wNumbering == PFN_BULLET)
            CheckMenuItem (*lphmenu, ID_Bullet, MF_BYCOMMAND | MF_CHECKED);
        if (lpreoc->lpfb->pf.dxStartIndent > 1440)
            EnableMenuItem (*lphmenu, ID_Indent, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        if (!lpreoc->lpfb->pf.dxStartIndent)
            EnableMenuItem (*lphmenu, ID_Collapse, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        return hrSuccess;
    }
    return ResultFromScode (E_NOTIMPL);
}

STDMETHODIMP_(SCODE)
ScNewRicheditCallback (LPFORMATBAR lpfb,
    LPALLOCATEBUFFER lpfnAlloc,
    LPALLOCATEMORE lpfnAllocMore,
    LPFREEBUFFER lpfnFree,
    LPREOC FAR * lppreoc)
{
    SCODE sc;
    LPREOC lpreoc = NULL;

    if (!FAILED (sc = (*lpfnAlloc) (sizeof(REOC), &lpreoc)))
    {
        memset (lpreoc, 0, sizeof (REOC));
        lpreoc->lpVtbl = (REOC_Vtbl FAR *)&vtblREOC;
        lpreoc->lcInit = 1;
        lpreoc->cSub = 0;
        lpreoc->lpfnAlloc = lpfnAlloc;
        lpreoc->lpfnAllocMore = lpfnAllocMore;
        lpreoc->lpfnFree = lpfnFree;
        lpreoc->lpfb = lpfb;

        sc = GetScode (StgCreateDocfile (NULL,
                                STGM_READWRITE           |
                                    STGM_TRANSACTED      |
                                    STGM_DELETEONRELEASE |
                                    STGM_SHARE_EXCLUSIVE,
                                0,
                                &lpreoc->lpstg));
        if (FAILED (sc))
        {
            (*lpfnFree) (lpreoc);
            lpreoc = NULL;
        }
    }

    *lppreoc = lpreoc;

    DebugTraceSc (ScNewRicheditCallback(), sc);
    return sc;
}


SMHOOF.C

/*
 *  S M H O O F . C
 *
 *  Sample mail handling hook
 *  Out of office management
 *
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#include <mapiutil.h>
#include <cindex.h>
#include <limits.h>

#ifdef _WIN32
#define szPlatform "32"
#else
#define szPlatform
#endif

enum { ipOofRId, ipOofREid, cpOofMax };
enum { icrgFrom, icrgSubmit, icrgTo, icrgCC, icrgSubj, icrgImp, icrgSen, ccrgMax };

enum
{
    ipRespSen,
    ipRespConvKey,
    ipRespConvIdx,
    ipRespConvTopic,
    ipRespReportTag,
    ipRespOrigAuthEid,
    ipRespOrigAuthorName,
    ipRespOrigAuthorSKey,
    ipRespOrigSubmitTime,
    ipRespPriority,
    ipRespImportance,
    ipRespSubject,
    ipRespSubjectPrefix,
    ipRespDelAfterSub,
    ipRespMessageClass,
    ipRespMessageFlags,
    cpTargetResponseMax
};

enum
{
    ipRespRecipName,
    ipRespRecipAdrType,
    ipRespRecipEmail,
    ipRespRecipType,
    ipRespRecipEid,
    ipRespRecipSKey,
    cpTargetRecipMax
};

enum
{
    ipMsgClass,
    ipMsgFlags,
    ipRecipMe,
    ipNSubj,
    ipSndrEid,
    ipSndrNm,
    ipSndrType,
    ipSndrEmail,
    ipSndrSKey,
    ipConvIndex,
    ipConvTopic,
    ipConvKey,
    ipOrigPriority,
    ipReportTag,
    ipOrigAuthorEid,
    ipOrigAuthorName,
    ipOrigAuthorSKey,
    ipOrigSubmitTime,
    ipSentRepName,
    ipSentRepType,
    ipSentRepEmail,
    ipSentRepSKey,
    ipStoreSupport,
    ipSubmitTime,
    ipDisplayTo,
    ipDisplayCc,
    ipOofSubj,
    ipImportance,
    ipSensitivity,
    cpResponseMax
};

const static SizedSPropTagArray (cpResponseMax, sptResponse) =
{
    cpResponseMax,
    {
        PR_MESSAGE_CLASS,
        PR_MESSAGE_FLAGS,
        PR_MESSAGE_RECIP_ME,
        PR_NORMALIZED_SUBJECT,
        PR_SENDER_ENTRYID,
        PR_SENDER_NAME,
        PR_SENDER_ADDRTYPE,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENDER_SEARCH_KEY,
        PR_CONVERSATION_INDEX,
        PR_CONVERSATION_TOPIC,
        PR_CONVERSATION_KEY,
        PR_PRIORITY,
        PR_REPORT_TAG,
        PR_ORIGINAL_AUTHOR_ENTRYID,
        PR_ORIGINAL_AUTHOR_NAME,
        PR_ORIGINAL_AUTHOR_SEARCH_KEY,
        PR_ORIGINAL_SUBMIT_TIME,
        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_ADDRTYPE,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_SEARCH_KEY,
        PR_STORE_SUPPORT_MASK,
        PR_CLIENT_SUBMIT_TIME,
        PR_DISPLAY_TO,
        PR_DISPLAY_CC,
        PR_SUBJECT,
        PR_IMPORTANCE,
        PR_SENSITIVITY,
    }
};

enum { ipDispNm, ipAdrTyp, ipEmail, ipSKey, cpUserMax };
const static SizedSPropTagArray (cpUserMax, sptUser) =
{
    cpUserMax,
    {
        PR_DISPLAY_NAME,
        PR_ADDRTYPE,
        PR_EMAIL_ADDRESS,
        PR_SEARCH_KEY,
    }
};

enum { ropOof, ropForward, ropReply };
static const LPTSTR rgszSubjPrfx[] =
{
    "OOF: ",
    "FW: ",
    "RE: "
};

enum
{
    ivHdrSndrName,
    ivHdrSndrType,
    ivHdrSndrEmail,
    ivHdrSentRepName,
    ivHdrSentRepType,
    ivHdrSentRepEmail,
    ivHdrSubmitTime,
    ivDisplayTo,
    ivDisplayCc,
    ivSubject,
    ivImportance,
    ivSensitivity,
    cvHeader
};

static const SizedSPropTagArray (cvHeader, sptHeader) =
{
    cvHeader,
    {
        PR_SENDER_NAME,
        PR_SENDER_ADDRTYPE,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_ADDRTYPE,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_CLIENT_SUBMIT_TIME,
        PR_DISPLAY_TO,
        PR_DISPLAY_CC,
        PR_SUBJECT,
        PR_IMPORTANCE,
        PR_SENSITIVITY
    }
};

static const SizedSPropTagArray (1, sptForward) =
{
    1,
    {
        PR_MESSAGE_ATTACHMENTS,
    }
};

enum { ipAttPos, ipAttNum, ipAttMeth, ipAttName, cpTaggingMax };
static const SizedSPropTagArray (cpTaggingMax, sptTagging) =
{
    cpTaggingMax,
    {
        PR_RENDERING_POSITION,
        PR_ATTACH_NUM,
        PR_ATTACH_METHOD,
        PR_ATTACH_FILENAME
    }
};

static const LPTSTR rgszHeaderField[] =
{
    "Sent:",
    "To:",
    "Cc:",
    "Subject:",
    "Importance:",
    "Sensitivity:"
};

static const LPTSTR rgszImportance[] =
{
    "Low",
    "Normal",
    "High"
};

static const LPTSTR rgszSensitivity[] =
{
    "Normal",
    "Personal",
    "Private",
    "Confidential"
};

static const TCHAR * rgszDay[] =
{
    TEXT ("Sunday"),
    TEXT ("Monday"),
    TEXT ("Tuesday"),
    TEXT ("Wednesday"),
    TEXT ("Thursday"),
    TEXT ("Friday"),
    TEXT ("Saturday")
};
extern TCHAR FAR * rgtstrMonthFull[];

LONG
CchInsertSz (HWND hwnd, LPTSTR lpsz)
{
    SendMessage (hwnd, EM_REPLACESEL, 0, (LPARAM) lpsz);
    return lstrlen (lpsz);
}

VOID
FileTimeToDateTimeSz (FILETIME FAR * lpft, LPTSTR rgch, UINT cb)
{
    SYSTEMTIME st;

    if (FileTimeToSystemTime (lpft, &st))
    {
        wsprintf (rgch,
            "%s, %s %02d, %4d %d:%02d %s",
            rgszDay[st.wDayOfWeek],
            rgtstrMonthFull[st.wMonth - 1],
            st.wDay,
            st.wYear,
            st.wHour & 12,
            st.wMinute,
            (st.wHour > 11) ? "PM" : "AM");
    }
    else
        lstrcpy (rgch, "Unavailable");
}

HRESULT
HrCopyOriginalBody (LPSMH lpsmh,
    HWND hwnd,
    LPMESSAGE lpmsg,
    LONG FAR * lpcch)
{
    HRESULT hr;
    CHARRANGE chrg = {0};
    EDITSTREAM es = {0};
    LPSTREAM lpstm = NULL;
    LPSTREAM lpstmT = NULL;

    *lpcch = 0;
    hr = lpmsg->lpVtbl->OpenProperty (lpmsg,
                                PR_RTF_COMPRESSED,
                                &IID_IStream,
                                0, 0,
                                (LPUNKNOWN FAR *)&lpstmT);
    if (!HR_FAILED (hr))
    {
        hr = WrapCompressedRTFStream (lpstmT, 0, &lpstm);
        if (!HR_FAILED (hr))
        {
            es.pfnCallback = (EDITSTREAMCALLBACK)lpstm->lpVtbl->Read;
            es.dwCookie = (DWORD)lpstm;

            /*  Stuff a newline into the edit
             *  control such that whatever preceeds
             *  the body will be separated from the
             *  the text of the original message
             */
            SendMessage (hwnd, EM_EXGETSEL, 0, (LPARAM) &chrg);
            CchInsertSz (hwnd, "\r\n");

            /*  Do the body now */

            SendMessage (hwnd,
                EM_STREAMIN,
                SF_RTF | SFF_SELECTION | SFF_PLAINRTF,
                (LPARAM)&es);

            /*  Calculate the size of the body in characters */

            Edit_SetSel (hwnd, chrg.cpMin, INT_MAX);
            SendMessage (hwnd, EM_EXGETSEL, 0, (LPARAM) &chrg);
            *lpcch = chrg.cpMax - chrg.cpMin;

            /*  Reset the selection to the begining
             *  of the edit control such that all
             *  additions occur before the original
             *  body
             */
            Edit_SetSel (hwnd, 0, 0);
        }
    }

    UlRelease (lpstm);
    UlRelease (lpstmT);
    DebugTraceResult (HrCopyOriginalBody(), hr);
    return hr;
}

HRESULT
HrInsertOriginalHeader (LPSMH lpsmh,
    HWND hwnd,
    LPSPropValue lpval,
    CHARFORMAT FAR * lpcf,
    LONG FAR * lpcch)
{
    CHAR rgch[MAX_PATH];
    CHARRANGE chrg = {0};
    CHARRANGE chrgHdr = {0};
    CHARRANGE rgchrg[ccrgMax] = {0};
    LONG cp;
    LPTSTR lpsz;
    UINT icrg = 0;
    UINT ip;

    /*  Stuff a newline into the edit
     *  control such that whatever preceeds
     *  the body will be separated from the
     *  the text of the original message
     */
    SendMessage (hwnd, EM_EXGETSEL, 0, (LPARAM) &chrgHdr);
    CchInsertSz (hwnd, "\r\n");

    /*  Mark the begining of the header */

    SendMessage (hwnd, EM_EXGETSEL, 0, (LPARAM) &chrg);

    cp = chrg.cpMin;
    cp += CchInsertSz (hwnd, "\r\n----------\r\n");

    /*  Insert the "From: xxxx" line */

    if ((lpval[ipSndrNm].ulPropTag == PR_SENDER_NAME) &&
        (lpval[ipSndrNm].Value.LPSZ != NULL) &&
        (*lpval[ipSndrNm].Value.LPSZ != 0))
    {
        rgchrg[icrg].cpMin = cp;
        cp += CchInsertSz (hwnd, "From:");
        rgchrg[icrg].cpMax = cp;
        icrg++;

        cp += CchInsertSz (hwnd, "\t");
        cp += CchInsertSz (hwnd, lpval[ipSndrNm].Value.LPSZ);

        /*  If we were representing someone else... */

        if ((lpval[ipSentRepName].ulPropTag == PR_SENT_REPRESENTING_NAME) &&
            (lpval[ipSentRepName].Value.LPSZ != NULL) &&
            (*lpval[ipSentRepName].Value.LPSZ != 0))
        {
            if ((lpval[ipSndrSKey].ulPropTag != PR_SENDER_SEARCH_KEY) ||
                (lpval[ipSentRepSKey].ulPropTag != PR_SENT_REPRESENTING_SEARCH_KEY) ||
                (lpval[ipSndrSKey].Value.bin.cb != lpval[ipSentRepSKey].Value.bin.cb) ||
                memcmp (lpval[ipSndrSKey].Value.bin.lpb,
                    lpval[ipSentRepSKey].Value.bin.lpb,
                    lpval[ipSndrSKey].Value.bin.cb))
            {
                cp += CchInsertSz (hwnd, " on behalf of ");
                cp += CchInsertSz (hwnd, lpval[ipSentRepName].Value.LPSZ);
            }
            cp += CchInsertSz (hwnd, TEXT("\r\n"));
        }

        /*  Insert the remaining lines */

        for (ip = ipSubmitTime; ip < cpResponseMax; ip++)
        {
            lpsz = NULL;
            switch (PROP_TYPE (lpval[ip].ulPropTag))
            {
              case PT_TSTRING:

                /*  Strings are strings */

                lpsz = lpval[ip].Value.LPSZ;
                break;

              case PT_SYSTIME:

                /*  Convertt the date to a string */

                FileTimeToDateTimeSz (&lpval[ip].Value.ft, rgch, sizeof(rgch));
                lpsz = rgch;
                break;

              case PT_LONG:

                /*  Importance and Sensitivity use a look-up to
                 *  find the proper string to insert.  If the value
                 *  equates to the "normal" level of a given message,
                 *  then no value is displayed.
                 */
                if ((lpval[ip].ulPropTag == PR_IMPORTANCE) &&
                    (lpval[ip].Value.l != IMPORTANCE_NORMAL))
                    lpsz = rgszImportance[lpval[ip].Value.l];
                else if ((lpval[ip].ulPropTag == PR_SENSITIVITY) &&
                    (lpval[ip].Value.l != SENSITIVITY_NONE))
                    lpsz = rgszSensitivity[lpval[ip].Value.l];
                break;
            }

            if (lpsz && *lpsz)
            {
                rgchrg[icrg].cpMin = cp;
                cp += CchInsertSz (hwnd, rgszHeaderField[ip - ipSubmitTime]);
                rgchrg[icrg].cpMax = cp;
                icrg++;

                cp += CchInsertSz (hwnd, "\t");
                cp += CchInsertSz (hwnd, lpsz);
                cp += CchInsertSz (hwnd, TEXT("\r\n"));
            }
        }
        cp += CchInsertSz (hwnd, TEXT("\r\n"));

        /*  Ensure that the text is formated in the
         *  charformat passed in.  Such that we can
         *  manipulate the rest with no worries
         */
        chrg.cpMin += 2;
        chrg.cpMax = cp;
        SendMessage (hwnd, EM_EXSETSEL, 0, (LPARAM) &chrg);
        SendMessage (hwnd, EM_SETCHARFORMAT, SCF_SELECTION|SCF_WORD, (LPARAM)lpcf);

        /*  Run through all the field headers and make them bold */

        lpcf->cbSize = sizeof(CHARFORMAT);
        lpcf->dwMask = CFM_BOLD;
        lpcf->dwEffects = CFE_BOLD;

        while (icrg)
        {
            chrg = rgchrg[--icrg];
            SendMessage (hwnd, EM_EXSETSEL, 0, (LPARAM)&chrg);
            SendMessage (hwnd, EM_SETCHARFORMAT, SCF_SELECTION|SCF_WORD, (LPARAM)lpcf);
        }

        /*  Calculate the size of the header */

        *lpcch = cp - chrgHdr.cpMin;
        Edit_SetSel (hwnd, 0, 0);
    }

    DebugTraceResult (HrInsertOriginalHeader(), hrSuccess);
    return hrSuccess;
}

HRESULT
HrInsertAnnotation (LPSMH lpsmh,
    HWND hwnd,
    UINT rop,
    LPRULE lprl,
    LONG FAR * lpcch)
{
    CHARRANGE chrg = {0};
    EDITSTREAM es = {0};
    LPBYTE lpb;
    RTFS rtfs = {0};
    ULONG cb;

    /*  Setup which annotation to use */

    if (rop != ropOof)
    {
        cb = lprl->cbRTF;
        lpb = lprl->lpbRTF;
    }
    else
    {
        cb = lpsmh->oof.cbRTF;
        lpb = lpsmh->oof.lpbRTF;
    }

    /*  Stream the bad boy in */

    rtfs.cb = 0;
    rtfs.cbMax = cb;
    rtfs.lpb = lpb;
    es.pfnCallback = ReadRTFFromBuffer;
    es.dwCookie = (DWORD)&rtfs;
    SendMessage (hwnd,
        EM_STREAMIN,
        SF_RTF | SFF_SELECTION | SFF_PLAINRTF,
        (LPARAM)&es);

    /*  Calculate the size of what we just streamed in */

    SendMessage (hwnd, EM_EXGETSEL, 0, (LPARAM) &chrg);
    *lpcch = chrg.cpMax;

    DebugTraceResult (HrInsertAnnotation(), hrSuccess);
    return hrSuccess;
}

HRESULT
HrTagAttachments (LPSMH lpsmh,
    HWND hwnd,
    LONG dch,
    LPMESSAGE lpmsg)
{
    HRESULT hr;
    CHAR rgch[MAX_PATH] = {0};
    LONG ichPos;
    LPATTACH lpatt = NULL;
    LPMAPITABLE lptbl = NULL;
    LPMESSAGE lpmsgT = NULL;
    LPSPropValue lpval = NULL;
    LPSRowSet lprws = NULL;
    UINT irw;

    hr = lpmsg->lpVtbl->GetAttachmentTable (lpmsg, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptTagging, 0);
    if (HR_FAILED (hr))
        goto ret;

    while (TRUE)
    {
        hr = lptbl->lpVtbl->QueryRows (lptbl, 64, 0, &lprws);
        if (HR_FAILED (hr))
            goto ret;

        if (lprws->cRows == 0)
            break;

        for (irw = 0; irw < lprws->cRows; irw++)
        {
            switch (lprws->aRow[irw].lpProps[ipAttMeth].Value.l)
            {
              case ATTACH_OLE:

                lstrcpy (rgch, "<<OLE Object: unknown>>");
                break;

              case ATTACH_EMBEDDED_MSG:

                hr = lpmsg->lpVtbl->OpenAttach (lpmsg,
                                        lprws->aRow[irw].lpProps[ipAttNum].Value.l,
                                        NULL,
                                        0,
                                        &lpatt);
                if (!HR_FAILED (hr))
                {
                    hr = lpatt->lpVtbl->OpenProperty (lpatt,
                                        PR_ATTACH_DATA_OBJ,
                                        &IID_IMessage,
                                        0, 0,
                                        (LPUNKNOWN FAR *)&lpmsgT);
                    if (!HR_FAILED (hr))
                    {
                        /*  Get the subject of the embedded message
                         *  as the tag identifier
                         */
                        hr = HrGetOneProp ((LPMAPIPROP)lpmsgT, PR_SUBJECT, &lpval);
                    }
                }

                wsprintf (rgch, "<<Message: %s>>",
                    HR_FAILED (hr) ? "" : lpval->Value.LPSZ);

                (*lpsmh->lpfnFree) (lpval);
                UlRelease (lpmsgT);
                UlRelease (lpatt);
                lpmsgT = NULL;
                lpval = NULL;
                lpatt = NULL;
                break;

              default:
              case ATTACH_BY_VALUE:
              case ATTACH_BY_REFERENCE:

                /*  Use the filename for the attachment tag */

                wsprintf (rgch, "<<File: %s>>",
                    (lprws->aRow[irw].lpProps[ipAttName].ulPropTag == PR_ATTACH_FILENAME)
                          ? lprws->aRow[irw].lpProps[ipAttName].Value.LPSZ
                          : "");
                break;
            }

            /*  Setup the selection such that we replace the attachment
             *  place holder wiht the attachment tag
             */
            ichPos = lprws->aRow[irw].lpProps[ipAttPos].Value.l;
            if (ichPos == -1)
                Edit_SetSel (hwnd, INT_MAX, INT_MAX);
            else
                Edit_SetSel (hwnd, ichPos + dch - 1, ichPos + dch);

            /*  Insert the tag and adjust the offset
             *  of the next attachment tag posiiton.
             */
            dch += CchInsertSz (hwnd, rgch) - 1;

            /*  Free the row data */

            (*lpsmh->lpfnFree) (lprws->aRow[irw].lpProps);
        }

        hr = hrSuccess;
        (*lpsmh->lpfnFree) (lprws);
        lprws = NULL;
    }
    (*lpsmh->lpfnFree) (lprws);
    lprws = NULL;

ret:

    UlRelease (lptbl);

    DebugTraceResult (HrTagAttachments(), hr);
    return hr;
}

HRESULT
HrOffsetAttachments (LPSMH lpsmh,
    LONG dch,
    LPMESSAGE lpmsg)
{
    HRESULT hr;
    LPATTACH lpatt = NULL;
    LPMAPITABLE lptbl = NULL;
    LPSRowSet lprws = NULL;
    UINT irw;

    hr = lpmsg->lpVtbl->GetAttachmentTable (lpmsg, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptTagging, 0);
    if (HR_FAILED (hr))
        goto ret;

    while (TRUE)
    {
        hr = lptbl->lpVtbl->QueryRows (lptbl, 64, 0, &lprws);
        if (HR_FAILED (hr))
            goto ret;

        if (lprws->cRows == 0)
            break;

        for (irw = 0; irw < lprws->cRows; irw++)
        {
            /*  If the rendering position is not -1, we
             *  want to adjust the positioning by the value
             *  passed in dch
             */
            if (lprws->aRow[irw].lpProps[ipAttPos].Value.l != -1)
            {
                /*  Adjust the positioning, and set in into the attachment */

                lprws->aRow[irw].lpProps[ipAttPos].Value.l += dch;

                hr = lpmsg->lpVtbl->OpenAttach (lpmsg,
                                            lprws->aRow[irw].lpProps[ipAttNum].Value.l,
                                            NULL,
                                            MAPI_MODIFY,
                                            &lpatt);
                if (!HR_FAILED (hr))
                {
                    hr = lpatt->lpVtbl->SetProps (lpatt,
                                            1,
                                            &lprws->aRow[irw].lpProps[ipAttPos],
                                            NULL);
                    if (!HR_FAILED (hr))
                    {
                        /*  Save out the new positioning */

                        hr = lpatt->lpVtbl->SaveChanges (lpatt, 0);

                    }
                    UlRelease (lpatt);
                    lpatt = NULL;
                }
                hr = hrSuccess;
            }

            /*  Free the row data */

            (*lpsmh->lpfnFree) (lprws->aRow[irw].lpProps);
        }

        (*lpsmh->lpfnFree) (lprws);
        lprws = NULL;
    }
    (*lpsmh->lpfnFree) (lprws);
    lprws = NULL;

ret:

    UlRelease (lptbl);

    DebugTraceResult (HrOffsetAttachments(), hr);
    return hr;
}

HRESULT
HrInsertBody (LPSMH lpsmh,
    HWND hwnd,
    LPSPropValue lpval,
    LPMESSAGE lpmsg)
{
    HRESULT hr = hrSuccess;
    BOOL fUpdated;
    EDITSTREAM es = {0};
    LPSTREAM lpstm = NULL;
    LPSTREAM lpstmRTF = NULL;
    SPropValue val;
    ULONG ulFlags = 0;

    /*  Do PR_BODY iff the store is not RTF_AWARE */

    if ((lpval[ipStoreSupport].ulPropTag != PR_STORE_SUPPORT_MASK) ||
        !(lpval[ipStoreSupport].Value.l & STORE_RTF_OK))
    {
        hr = lpmsg->lpVtbl->OpenProperty (lpmsg,
                                PR_BODY,
                                &IID_IStream,
                                0,
                                MAPI_CREATE | MAPI_MODIFY,
                                (LPUNKNOWN FAR *)&lpstm);
        if (HR_FAILED (hr))
            goto ret;

        es.dwCookie = (DWORD)lpstm;
        es.pfnCallback = (EDITSTREAMCALLBACK)lpstm->lpVtbl->Write;
        SendMessage (hwnd, EM_STREAMOUT, SF_TEXT, (LPARAM)&es);
        UlRelease (lpstm);
        lpstm = NULL;

        if (!es.dwError)
            ulFlags |= RTF_SYNC_BODY_CHANGED;
    }

    /*  Add in PR_COMPRESSED_RTF */

    hr = lpmsg->lpVtbl->OpenProperty (lpmsg,
                                PR_RTF_COMPRESSED,
                                &IID_IStream,
                                0,
                                MAPI_CREATE | MAPI_MODIFY,
                                (LPUNKNOWN FAR *)&lpstm);
    if (HR_FAILED (hr))
        goto ret;

    hr = WrapCompressedRTFStream (lpstm,
                MAPI_MODIFY | (lpval[ipStoreSupport].Value.l & STORE_UNCOMPRESSED_RTF),
                &lpstmRTF);
    if (HR_FAILED (hr))
        goto ret;

    es.dwCookie = (DWORD)lpstmRTF;
    es.pfnCallback = (EDITSTREAMCALLBACK)lpstmRTF->lpVtbl->Write;
    SendMessage (hwnd, EM_STREAMOUT, SF_RTF | SFF_PLAINRTF, (LPARAM)&es);

    hr = lpstmRTF->lpVtbl->Commit (lpstmRTF, 0);
    if (HR_FAILED (hr))
        goto ret;

    if (!es.dwError)
        ulFlags |= RTF_SYNC_RTF_CHANGED;

    /*  Sync the RTF and the body iff the store is not RTF aware */

    if ((lpval[ipStoreSupport].ulPropTag != PR_STORE_SUPPORT_MASK) ||
        !(lpval[ipStoreSupport].Value.l & STORE_RTF_OK))
    {
        /*  We are not aware, so we better do a full sync */

        hr = RTFSync (lpmsg, ulFlags, &fUpdated);
    }
    else
    {
        /*  If we are aware, then we want to tell the
         *  store that we are completely in sync.  Otherwise,
         *  we could loose our attachment positioning on
         *  RTF aware stores.  And that would be bad.
         */
        val.ulPropTag = PR_RTF_IN_SYNC;
        val.Value.b = TRUE;
        lpmsg->lpVtbl->SetProps (lpmsg, 1, &val, NULL);
    }

ret:

    UlRelease (lpstm);
    UlRelease (lpstmRTF);

    DebugTraceResult (HrInsertBody(), hr);
    return hr;
}

HRESULT
HrBuildRecipient (LPSMH lpsmh,
    LPSPropValue lpval,
    LPMESSAGE lpmsg)
{
    SCODE sc;
    HRESULT hr;
    LPADRLIST lpadr = NULL;
    LPMAPIPROP lpusr = NULL;
    LPSPropValue lpvalUsr = NULL;
    LPSPropValue rgval = NULL;
    UINT cval = 0;
    ULONG ulT;

    /*  Open the recipient up */

    hr = lpsmh->lpsess->lpVtbl->OpenEntry (lpsmh->lpsess,
                            lpval->Value.bin.cb,
                            (LPENTRYID)lpval->Value.bin.lpb,
                            NULL, 0,
                            &ulT,
                            (LPUNKNOWN FAR *)&lpusr);
    if (HR_FAILED (hr))
        goto ret;

    /*  Get the properties we need */

    hr = lpusr->lpVtbl->GetProps (lpusr,
                            (LPSPropTagArray)&sptUser,
                            0,
                            &ulT,
                            &lpvalUsr);
    if (HR_FAILED (hr))
        goto ret;

    /*  Allocate the adrlist */

    if (FAILED (sc = (*lpsmh->lpfnAlloc) (CbNewADRLIST (1), &lpadr)) ||
        FAILED (sc = (*lpsmh->lpfnAlloc) (cpTargetRecipMax * sizeof(SPropValue), &rgval)))
    {
        hr = ResultFromScode (sc);
        goto ret;
    }

    /*  Stuff the properties and add the recipient */

    rgval[cval].ulPropTag = PR_ENTRYID;
    rgval[cval].Value = lpval->Value;
    cval++;

    rgval[cval].ulPropTag = PR_DISPLAY_NAME;
    rgval[cval].Value.LPSZ = lpvalUsr[ipDispNm].Value.LPSZ;
    cval++;

    if (lpvalUsr[ipAdrTyp].ulPropTag == PR_ADDRTYPE)
    {
        rgval[cval].ulPropTag = PR_ADDRTYPE;
        rgval[cval].Value.LPSZ = lpvalUsr[ipAdrTyp].Value.LPSZ;
        cval++;
    }

    if (lpvalUsr[ipEmail].ulPropTag == PR_EMAIL_ADDRESS)
    {
        rgval[cval].ulPropTag = PR_EMAIL_ADDRESS;
        rgval[cval].Value.LPSZ = lpvalUsr[ipEmail].Value.LPSZ;
        cval++;
    }

    if (lpvalUsr[ipSKey].ulPropTag == PR_SEARCH_KEY)
    {
        rgval[cval].ulPropTag = PR_SEARCH_KEY;
        rgval[cval].Value = lpvalUsr[ipSKey].Value;
        cval++;
    }

    rgval[cval].ulPropTag = PR_RECIPIENT_TYPE;
    rgval[cval].Value.l = MAPI_TO;
    cval++;

    lpadr->cEntries = 1;
    lpadr->aEntries[0].cValues = cval;
    lpadr->aEntries[0].rgPropVals = rgval;
    hr = lpmsg->lpVtbl->ModifyRecipients (lpmsg, MODRECIP_ADD, lpadr);
    if (HR_FAILED (hr))
        goto ret;

ret:

    if (lpadr)
    {
        (*lpsmh->lpfnFree) (lpadr->aEntries[0].rgPropVals);
        (*lpsmh->lpfnFree) (lpadr);
    }
    (*lpsmh->lpfnFree) (lpvalUsr);
    UlRelease (lpusr);

    DebugTraceResult (HrBuildRecipient(), hr);
    return hr;
}

HRESULT
HrCreateResponse (LPSMH lpsmh,
    LPRULE lprl,
    LPMAPIFOLDER lpfldr,
    LPMESSAGE lpmsgOrig,
    LPSPropValue lpval,
    LPMESSAGE FAR * lppmsg)
{
    SCODE sc;
    HRESULT hr;
    CHARFORMAT cf = {0};
    HINSTANCE hlib = NULL;
    HWND hwnd = NULL;
    LONG cch = 0;
    LONG cchHdr = 0;
    LPBYTE lpbConvIndex = NULL;
    LPMESSAGE lpmsg = NULL;
    LPREOC lpreoc = NULL;
    LPSPropValue rgval = NULL;
    PARAFORMAT pf = {0};
    TCHAR rgchClass[MAX_PATH];
    TCHAR rgchSubj[MAX_PATH];
    UINT rop;
    ULONG cval = 0;

    *lppmsg = NULL;

    /*  Calculate the response operation based on
     *  the supplied rule.  If no rule is supplied
     *  then the response is an out-of-office msg.
     */
    if (lprl)
    {
        Assert (lprl->ulFlags & RULE_AUTO_RESPONSE);
        if (lprl->ulFlags & RULE_AUTO_FORWARD)
        {
            Assert (!(lprl->ulFlags & RULE_AUTO_REPLY));
            rop = ropForward;
        }
        else
        {
            Assert (!(lprl->ulFlags & RULE_AUTO_FORWARD));
            rop = ropReply;
        }
    }
    else
        rop = ropOof;

    /*  Create the response message */

    hr = lpfldr->lpVtbl->CreateMessage (lpfldr, NULL, 0, &lpmsg);
    if (HR_FAILED (hr))
        goto ret;

    /*  Create and initialized the RTF edit control */

    hlib = LoadLibrary (RICHEDIT_LIB);
    hwnd = CreateWindow (RICHEDIT_CLASS,
                        "",
                        WS_BORDER|ES_MULTILINE,
                        CW_USEDEFAULT, CW_USEDEFAULT, INT_MAX, INT_MAX,
                        NULL,
                        NULL,
                        lpsmh->hinst,
                        NULL);
    if (!hwnd)
    {
        hr = ResultFromScode (MAPI_E_CALL_FAILED);
        goto ret;
    }

    /*  Create the richedit OLE callback.  If this
     *  fails, it is non-fatal.  It just means that
     *  any OLE objects in the annotation will not
     *  be preserved in then response message.
     */
    if (!FAILED (ScNewRicheditCallback (NULL,
                        lpsmh->lpfnAlloc,
                        lpsmh->lpfnAllocMore,
                        lpsmh->lpfnFree,
                        &lpreoc)))
    {
        /*  We have an OLE callback that we need
         *  to hand off to the richedit control.
         *  Although, the richedit control should
         *  be AddRef()ing the object, we will hold
         *  our reference until we are through with
         *  the edit control.
         */
        SendMessage (hwnd, EM_SETOLECALLBACK, 0, (LPARAM)lpreoc);
    }

    /*  Setup the default character format */

    cf.cbSize = sizeof(CHARFORMAT);
    cf.dwMask = CFM_FACE | CFM_SIZE | CFM_COLOR | CFM_BOLD |
                CFM_ITALIC | CFM_UNDERLINE | CFM_STRIKEOUT |
                CFM_OFFSET | CFM_CHARSET;
    cf.dwEffects = CFE_AUTOCOLOR;
    cf.yHeight = 160;
    lstrcpy (cf.szFaceName, "MS Sans Serif");
    cf.bCharSet = DEFAULT_CHARSET;
    SendMessage (hwnd, EM_SETCHARFORMAT, 0, (LPARAM)&cf);

    /*  Copy over the original body if it makes sense to do so */

    if ((rop != ropOof) && (lprl->ulFlags & RULE_AUTO_APPEND_ORIG))
    {
        hr = HrCopyOriginalBody (lpsmh, hwnd, lpmsgOrig, &cch);
        if (HR_FAILED (hr) && (rop == ropForward))

goto ret;

        hr = hrSuccess;
    }

    /*  Create and insert the original message header */

    hr = HrInsertOriginalHeader (lpsmh, hwnd, lpval, &cf, &cchHdr);
    if (HR_FAILED (hr))
        goto ret;

    /*  If this response is not a forward, we will want to
     *  indent the header (and maybe the body) such that they
     *  are offset from the annotation.
     */
    if (rop != ropForward)
    {
        /*  Skip indenting the blankline */

        Edit_SetSel (hwnd, 2, INT_MAX);
        pf.cbSize = sizeof(PARAFORMAT);
        pf.dwMask = PFM_STARTINDENT | PFM_RIGHTINDENT | PFM_ALIGNMENT |
                    PFM_OFFSET | PFM_TABSTOPS | PFM_NUMBERING;
        pf.dxOffset = 1440;
        pf.cTabCount = 1;
        pf.rgxTabs[0] = 1440;
        pf.wAlignment = PFA_LEFT;
        pf.dxStartIndent = 1440 / 4;
        SendMessage (hwnd, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
        Edit_SetSel (hwnd, 0, 0);
    }

    /*  Copy over the annotation */

    hr = HrInsertAnnotation (lpsmh, hwnd, rop, lprl, &cch);
    if (HR_FAILED (hr))
        goto ret;

    /*  Tag the attachments for replies */

    if (rop == ropReply)
    {
        hr = HrTagAttachments (lpsmh, hwnd, cchHdr + cch + 3, lpmsgOrig);
        if (HR_FAILED (hr))
            goto ret;
    }

    /*  Allocate space for the new message properties */

    sc = (*lpsmh->lpfnAlloc) (cpTargetResponseMax * sizeof(SPropValue), &rgval);
    if (FAILED (sc))
    {
        hr = ResultFromScode (sc);
        goto ret;
    }

    /*  Build the response subject */

    lstrcpy (rgchSubj, rgszSubjPrfx[rop]);
    lstrcat (rgchSubj, lpval[ipNSubj].Value.LPSZ);
    rgval[cval].ulPropTag = PR_SUBJECT;
    rgval[cval].Value.LPSZ = rgchSubj;
    cval++;

    rgval[cval].ulPropTag = PR_SUBJECT_PREFIX;
    rgval[cval].Value.LPSZ = rgszSubjPrfx[rop];
    cval++;

    /*  Build the response message class */

    if (rop == ropOof)
        wsprintf (rgchClass, "Report.%s.OOF", lpval[ipMsgClass].Value.LPSZ);
    else if (rop == ropReply)
        lstrcpy (rgchClass, "IPM.Note.AutoReply");
    else
        lstrcpy (rgchClass, lpval[ipMsgClass].Value.LPSZ);
    rgval[cval].ulPropTag = PR_MESSAGE_CLASS;
    rgval[cval].Value.LPSZ = rgchClass;
    cval++;

    /*  Compose the set of remaining properties */

    rgval[cval].ulPropTag = PR_DELETE_AFTER_SUBMIT;
    rgval[cval].Value.b = TRUE;

#ifdef  DEBUG
    rgval[cval].Value.b = GetPrivateProfileInt ("SMH", "DeleteResponses", 1, "MAPIDBG.INI");
#endif

    cval++;

    /*  Tell the store that the message has not
     *  been sent.  Otherwise, since the spooler
     *  is the creater of the message, the store
     *  will treat the message as if it has been
     *  delivered into the store by a transport.
     */
    rgval[cval].ulPropTag = PR_MESSAGE_FLAGS;
    rgval[cval].Value.l = MSGFLAG_UNSENT;
    cval++;

    /*  Create appropriate conversation topic and indexes */

    if (lpval[ipConvKey].ulPropTag == PR_CONVERSATION_KEY)
        rgval[cval++] = lpval[ipConvKey];
    else
    {
        rgval[cval].ulPropTag = PR_CONVERSATION_TOPIC;
        rgval[cval].Value.LPSZ = lpval[ipNSubj].Value.LPSZ;
        cval++;
    }

    if (lpval[ipConvTopic].ulPropTag == PR_CONVERSATION_TOPIC)
        rgval[cval++] = lpval[ipConvTopic];
    else
    {
        rgval[cval].ulPropTag = PR_CONVERSATION_TOPIC;
        rgval[cval].Value.LPSZ = lpval[ipNSubj].Value.LPSZ;
        cval++;
    }

    if (lpval[ipConvIndex].ulPropTag == PR_CONVERSATION_INDEX)
    {
        sc = ScAddConversationIndex (lpval[ipConvIndex].Value.bin.cb,
                            lpval[ipConvIndex].Value.bin.lpb,
                            &rgval[cval].Value.bin.cb,
                            &lpbConvIndex);
    }
    else
    {
        sc = ScAddConversationIndex (0,
                            NULL,
                            &rgval[cval].Value.bin.cb,
                            &lpbConvIndex);
    }
    if (!FAILED (sc))
    {
        rgval[cval].ulPropTag = PR_CONVERSATION_INDEX;
        rgval[cval].Value.bin.lpb = lpbConvIndex;
        cval++;
    }

    /*  Report tag */

    if (lpval[ipReportTag].ulPropTag == PR_REPORT_TAG)
        rgval[cval++] = lpval[ipReportTag];

    /*  Sensitivity */

    if (lpval[ipSensitivity].ulPropTag == PR_SENSITIVITY)
        rgval[cval++] = lpval[ipSensitivity];

    /*  Build response specific properties */

    if (rop == ropForward)
    {
        /*  Importance */

        if (lpval[ipImportance].ulPropTag == PR_IMPORTANCE)
            rgval[cval++] = lpval[ipImportance];

        /*  Priority */

        if (lpval[ipOrigPriority].ulPropTag == PR_PRIORITY)
            rgval[cval++] = lpval[ipOrigPriority];

        /*  Original author */

        if (lpval[ipOrigAuthorName].ulPropTag == PR_ORIGINAL_AUTHOR_NAME)
            rgval[cval++] = lpval[ipOrigAuthorName];

        if (lpval[ipOrigAuthorEid].ulPropTag == PR_ORIGINAL_AUTHOR_ENTRYID)
            rgval[cval++] = lpval[ipOrigAuthorEid];

        if (lpval[ipOrigAuthorSKey].ulPropTag == PR_ORIGINAL_AUTHOR_SEARCH_KEY)
            rgval[cval++] = lpval[ipOrigAuthorSKey];

        /*  Original submit time */

        if (lpval[ipOrigSubmitTime].ulPropTag == PR_ORIGINAL_SUBMIT_TIME)
            rgval[cval++] = lpval[ipOrigSubmitTime];
    }

    /*  Set the properties */

    hr = lpmsg->lpVtbl->SetProps (lpmsg, cval, rgval, NULL);
    if (HR_FAILED (hr))
        goto ret;

    /*  Insert the body into the mesage directly */

    hr = HrInsertBody (lpsmh, hwnd, lpval, lpmsg);
    if (HR_FAILED (hr))
        goto ret;

    /*  Copy the attachment across in the case of a forward */

    if (rop == ropForward)
    {
        hr = lpmsgOrig->lpVtbl->CopyProps(lpmsgOrig,
                        (LPSPropTagArray)&sptForward,
                        0,
                        NULL,
                        (LPIID) &IID_IMessage,
                        lpmsg,
                        0,
                        NULL);
        if (HR_FAILED (hr))
            goto ret;

        hr = HrOffsetAttachments (lpsmh, cchHdr + cch + 2, lpmsg);
        if (HR_FAILED (hr))
            goto ret;
    }

    /*  Do the recipient */

    hr = HrBuildRecipient (lpsmh,
                    (rop == ropForward)
                        ? lprl->lpvalRecip
                        : &lpval[ipSndrEid],
                    lpmsg);
    if (HR_FAILED (hr))
        goto ret;

ret:

    if (hwnd)
        DestroyWindow (hwnd);

    if (HR_FAILED (hr))
    {
        UlRelease (lpmsg);
        lpmsg = NULL;
    }

    if (hlib)
        FreeLibrary (hlib);

    (*lpsmh->lpfnFree) (lpbConvIndex);
    (*lpsmh->lpfnFree) (rgval);
    UlRelease (lpreoc);

    *lppmsg = lpmsg;

    DebugTraceResult (HrCreateResponse(), hr);
    return hr;
}

BOOL
FOofRecip (LPOOF lpoof, LPSPropValue lpvalEid)
{
    BOOL fOof = TRUE;
    LPMAPITABLE lptbl = lpoof->lptbl;
    SPropValue val;
    SRestriction res;

    if (lptbl)
    {
#ifdef  DEBUG
        if (!GetPrivateProfileInt ("SMH", "OofAlways", 0, "MAPIDBG.INI"))
#endif
        {
            val.ulPropTag = PR_ENTRYID;
            val.Value = lpvalEid->Value;

            res.rt = RES_PROPERTY;
            res.res.resProperty.relop = RELOP_EQ;
            res.res.resProperty.ulPropTag = PR_ENTRYID;
            res.res.resProperty.lpProp = &val;
            fOof = HR_FAILED (lptbl->lpVtbl->FindRow (lptbl, &res, BOOKMARK_BEGINNING, 0L));
        }
    }
    return fOof;
}

HRESULT
HrRegOofRecip (LPSMH lpsmh, LPOOF lpoof, LPSPropValue lpvalEid)
{
    HRESULT hr;
    LPTABLEDATA lptad = lpoof->lptad;
    SizedSPropTagArray (cpOofMax, sptOofTbl) = {cpOofMax, { PR_ROWID, PR_ENTRYID }};
    SPropValue rgval[cpOofMax];
    SRow rw;

    if (!lptad)
    {
        //  This is the first OOF recip so we need to create the
        //  table of recips
        //
        hr = ResultFromScode (CreateTable ((LPIID)&IID_IMAPITableData,
                            lpsmh->lpfnAlloc,
                            lpsmh->lpfnAllocMore,
                            lpsmh->lpfnFree,
                            NULL,
                            TBLTYPE_DYNAMIC,
                            PR_ROWID,
                            (LPSPropTagArray)&sptOofTbl,
                            (LPTABLEDATA FAR *)&lptad));
        if (HR_FAILED (hr))
            goto ret;

        hr = lptad->lpVtbl->HrGetView (lptad, NULL, NULL, 0, &lpoof->lptbl);
        if (HR_FAILED (hr))
        {
            UlRelease (lptad);
            goto ret;
        }
        lpoof->lptad = lptad;
    }

    rgval[ipOofRId].ulPropTag = PR_ROWID;
    rgval[ipOofRId].Value.l = lpoof->cRecips++;
    rgval[ipOofREid].ulPropTag = PR_ENTRYID;
    rgval[ipOofREid].Value = lpvalEid->Value;

    rw.cValues = cpOofMax;
    rw.lpProps = rgval;
    hr = lptad->lpVtbl->HrModifyRow (lptad, &rw);
    if (HR_FAILED (hr))
        goto ret;

ret:

    DebugTraceResult (HrRegOofRecip(), hr);
    return hr;
}

HRESULT
HrGenerateResponse (LPSMH lpsmh, LPRULE lprl, LPMAPIFOLDER lpfldr, LPMESSAGE lpmsg)
{
    HRESULT hr;
    LPSPropValue lpval = NULL;
    LPMESSAGE lpmsgRep = NULL;
    ULONG cval;

    hr = lpmsg->lpVtbl->SaveChanges (lpmsg, KEEP_OPEN_READONLY);
    if (HR_FAILED (hr))
        goto ret;

    hr = lpmsg->lpVtbl->GetProps (lpmsg,
                            (LPSPropTagArray)&sptResponse,
                            0,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    hr = ResultFromScode (MAPI_E_NOT_ME);

    if ((lpval[ipMsgClass].ulPropTag == PR_MESSAGE_CLASS) &&
        (lpval[ipMsgClass].Value.LPSZ != NULL) &&
        FLpszContainsLpsz (lpval[ipMsgClass].Value.LPSZ, "Report."))
        goto ret;

    if ((lpval[ipFlags].ulPropTag == PR_MESSAGE_FLAGS) &&
        (lpval[ipFlags].Value.l == MSGFLAG_FROMME))
        goto ret;

    /*  If there is no rule, then we must be OOF'ing */
    if (!lprl && !FOofRecip (&lpsmh->oof, &lpval[ipSndrEid]))
        goto ret;

    if ((lpval[ipRecipMe].ulPropTag == PR_MESSAGE_RECIP_ME) &&
        (lpval[ipRecipMe].Value.b))
    {
        DebugTrace ("SMH: generating response message\n");

        if (!lprl)
        {
            /*  Register the OOF recipient */

            hr = HrRegOofRecip (lpsmh, &lpsmh->oof, &lpval[ipSndrEid]);
            if (HR_FAILED (hr))
                goto ret;
        }

        /*  Create the response and submit it */

        hr = HrCreateResponse (lpsmh,
                        lprl,
                        lpfldr,
                        lpmsg,
                        lpval,
                        &lpmsgRep);
        if (!HR_FAILED (hr))
            hr = lpmsgRep->lpVtbl->SubmitMessage (lpmsgRep, 0);

        UlRelease (lpmsgRep);
    }

ret:

    (*lpsmh->lpfnFree) (lpval);
    DebugTraceResult (HrGenerateResponse(), hr);
    return hr;
}

HRESULT
HrInitOof (LPSMH lpsmh, LPSPropValue lpvalAnno, LPSPropValue lpvalRTF)
{
    SCODE sc = MAPI_E_UNCONFIGURED;

    if (lpvalRTF->ulPropTag == PR_SMH_OOF_RTF)
    {
        if (!FAILED (sc = (*lpsmh->lpfnAlloc) (lpvalRTF->Value.bin.cb,
                                    (LPVOID FAR *)&lpsmh->oof.lpbRTF)))
        {
            lpsmh->oof.cbRTF = lpvalRTF->Value.bin.cb;
            memcpy (lpsmh->oof.lpbRTF,
                lpvalRTF->Value.bin.lpb,
                (UINT)lpvalRTF->Value.bin.cb);
        }
    }
    if (lpvalAnno->ulPropTag == PR_SMH_OOF_TEXT)
    {
        if (!FAILED (sc = (*lpsmh->lpfnAlloc) (lstrlen (lpvalAnno->Value.LPSZ) + sizeof(TCHAR),
                                    (LPVOID FAR *)&lpsmh->oof.lpszBody)))
            lstrcpy (lpsmh->oof.lpszBody, lpvalAnno->Value.LPSZ);
    }

    DebugTraceSc (HrInitOof(), sc);
    return ResultFromScode (sc);
}


SMHPS.C

/*
 *  S M H P S . C
 *
 *  Sample mail handling hook configuration property sheets
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#ifndef WIN16
#include <commctrl.h>
#endif

extern LPTSTR lpszConfigEvt;
extern SPropTagArray sptRule;
extern SPropTagArray sptConfigProps;

typedef struct _FILTER
{
    LPSCD               lpscd;              /* back-pointer to config struct */

    TCHAR               rgch[cchNameMax];   /* filter name */
    MAPIUID             muid;               /* muid of filter profile section */
    LPPROFSECT          lpsec;              /* profile section object */
    LPSPropValue        lpval;              /* filter property values */

    SCODE               sc;

} FILTER, FAR * LPFILTER;

/*  sptDelete
 *
 *  This is the set of properties that need to be deleted from a rule
 *  profile section any time the rule is edited.  Otherwise, changes in
 *  target folders may not be retained across edits.
 */
const static SizedSPropTagArray (2, sptDelete) =
{
    2,
    {
        PR_RULE_TARGET_ENTRYID,
        PR_RULE_STORE_ENTRYID
    }
};

enum {ipMsgSto, ipDefSto, cpStoTblMax};
const static SizedSPropTagArray (cpStoTblMax, sptStoTbl) =
{
    cpStoTblMax,
    {
        PR_DISPLAY_NAME,
        PR_DEFAULT_STORE
    }
};

/*
 *  Common Dialog Functions ---------------------------------------------------
 */

/*
 *  SizeODButtons()
 *
 *  Purpose:
 *
 *      Set the control size for the two owner-draw buttons in the filter
 *      page of the configuration property sheets.
 *
 *  Arguments:
 *
 *      hinst       the DLL instance
 *      hdlg        the dialog in which the buttons will be drawn
 *      id          the dialog ID identifying which buttons to size
 */
VOID
SizeODButtons (HINSTANCE hInst, HWND hdlg, UINT id)
{
    BITMAP  bitmap;
    HBITMAP hbmp;

    if (!(hbmp = LoadBitmap (hInst, MAKEINTRESOURCE(ID_UpArrow))))
        return;
    GetObject (hbmp, sizeof(BITMAP), &bitmap);

    if (id == SMH_FilterPage)
    {
        SetWindowPos (GetDlgItem (hdlg, ID_FilterUp), NULL, 0, 0, bitmap.bmWidth,
            bitmap.bmHeight, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
        SetWindowPos (GetDlgItem (hdlg, ID_FilterDown), NULL, 0, 0, bitmap.bmWidth,
            bitmap.bmHeight, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
    }
    DeleteBitmap (hbmp);
}

/*
 *  DrawODButton()
 *
 *  Purpose:
 *
 *      Draws the button control for either of the two owner-draw buttons
 *      in the filter page of the configuration property sheets.
 *
 *  Arguments:
 *
 *      hinst       the DLL instance
 *      pdi         the DRAWITEMSTRUCT info for drawing the button
 */
VOID
DrawODButton (HINSTANCE hInst, DRAWITEMSTRUCT FAR * lpdi, BOOL fDrawFocus)
{
    HDC hDC;
    HBITMAP hbmpOld;
    HBITMAP hbmpArw;
    WORD wBtnRes;
    BITMAP bitmap;

    Assert (lpdi->CtlType == ODT_BUTTON);
    if (!(hDC = CreateCompatibleDC (lpdi->hDC)))
        return;

    /*  Get the bitmap */

    if (lpdi->itemState & ODS_SELECTED)
        wBtnRes = (lpdi->CtlID == ID_FilterUp) ? ID_UpArrowInv : ID_DownArrowInv;
    else if (lpdi->itemState & ODS_DISABLED)
        wBtnRes = (lpdi->CtlID == ID_FilterUp) ? ID_UpArrowDis : ID_DownArrowDis;
    else
        wBtnRes = (lpdi->CtlID == ID_FilterUp) ? ID_UpArrow : ID_DownArrow;

    /*  blit the bitmap */

    if (!(hbmpArw = CreateMappedBitmap (hInst, wBtnRes, FALSE, NULL, 0)))
        goto ret;
    hbmpOld = SelectObject (hDC, hbmpArw);
    BitBlt (lpdi->hDC, 0, 0, lpdi->rcItem.right - lpdi->rcItem.left,
        lpdi->rcItem.bottom - lpdi->rcItem.top, hDC, 0, 0, SRCCOPY);

    /*  Draw a focus rectangle if the button has the focus */

    if(fDrawFocus && (lpdi->itemState & ODS_FOCUS))
    {
        GetObject (hbmpArw, sizeof(BITMAP), &bitmap);
        lpdi->rcItem.right = bitmap.bmWidth;
        lpdi->rcItem.bottom = bitmap.bmHeight;
        InflateRect (&lpdi->rcItem, -3, -3);
        if (lpdi->itemState & ODS_SELECTED)
            OffsetRect (&lpdi->rcItem, 1, 1);
        DrawFocusRect (lpdi->hDC, &lpdi->rcItem);
    }

    SelectObject (hDC, hbmpOld);
    DeleteBitmap (hbmpArw);
ret:
    DeleteDC (hDC);
}

/*
 *  RTF Stream Callbacks ------------------------------------------------------
 */

DWORD CALLBACK
WriteRTFToBuffer (DWORD dwCookie, LPBYTE lpb, LONG cb, LONG FAR * lpcb)
{
    LPBYTE lpbT = NULL;
    LPRTFS lprtfs = (LPRTFS)dwCookie;

    if ((lprtfs->cb + cb) > lprtfs->cbMax)
    {
        if (!FAILED ((*lprtfs->lpfnAlloc) (lprtfs->cb + (cb * 2), &lpbT)))
        {
            memcpy (lpbT, lprtfs->lpb, (UINT)lprtfs->cb);
            (*lprtfs->lpfnFree) (lprtfs->lpb);
            lprtfs->lpb = lpbT;
            lprtfs->cbMax = lprtfs->cb + (cb * 2);
        }
        else
            return (DWORD)E_FAIL;
    }

    memcpy (lprtfs->lpb + lprtfs->cb, lpb, (UINT)cb);
    lprtfs->cb += cb;
    *lpcb = cb;

    return (DWORD)NOERROR;
}

DWORD CALLBACK
ReadRTFFromBuffer (DWORD dwCookie, LPBYTE lpb, LONG cb, LONG FAR * lpcb)
{
    LPRTFS lprtfs = (LPRTFS)dwCookie;

    cb = min (lprtfs->cb + cb, lprtfs->cbMax) - lprtfs->cb;
    memcpy (lpb, lprtfs->lpb + lprtfs->cb, (UINT)cb);
    lprtfs->cb += cb;
    *lpcb = cb;

    return NOERROR;
}

/*
 *  Store listbox support -----------------------------------------------------
 */
VOID
FillStoresListbox (LPSCD lpscd, HWND hdlg)
{
    HRESULT hr = hrSuccess;
    CHAR rgch[MAX_PATH] = {0};
    HWND hctrl = GetDlgItem (hdlg, ID_Store);
    LPMAPISESSION lpsess;
    LPMAPITABLE lptbl = NULL;
    LPSRowSet lprws = NULL;
    UINT iDef;
    UINT irw;

    /*  Logon to the profile for access to the stores table */

    if (!(lpsess = lpscd->lpsess))
    {
        hr = MAPILogonEx ((ULONG)GetParent (hdlg),
                    lpscd->lpval[ipProfile].Value.lpszA,
                    NULL,
                    MAPI_NEW_SESSION            |
                         MAPI_EXPLICIT_PROFILE  |
                         MAPI_EXTENDED          |
                         MAPI_NO_MAIL           |
                         MAPI_PASSWORD_UI       |
                         MAPI_TIMEOUT_SHORT,
                    &lpsess);
    }
    if (HR_FAILED (hr))
        goto ret;

    hr = lpsess->lpVtbl->GetMsgStoresTable (lpsess, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptStoTbl, 0);
    if (HR_FAILED (hr))
        goto ret;

    while (TRUE)
    {
        hr = lptbl->lpVtbl->QueryRows (lptbl, 64, 0, &lprws);
        if (HR_FAILED (hr))
            goto ret;

        if (lprws->cRows == 0)
            break;

        for (irw = 0; irw < lprws->cRows; irw++)
        {
            ComboBox_AddString (hctrl,
                lprws->aRow[irw].lpProps[ipMsgSto].Value.lpszA);

            if (lprws->aRow[irw].lpProps[ipDefSto].Value.b)
                lstrcpy (rgch, lprws->aRow[irw].lpProps[ipMsgSto].Value.lpszA);

            /*  Free the row data */

            (*lpscd->lpfnFree) (lprws->aRow[irw].lpProps);
        }

        (*lpscd->lpfnFree) (lprws);
        lprws = NULL;
    }
    (*lpscd->lpfnFree) (lprws);
    lprws = NULL;

    iDef = ComboBox_FindString (hctrl, -1, rgch);
    ComboBox_SetCurSel (hctrl, iDef == CB_ERR ? 0 : iDef);

ret:

    lpscd->lpsess = lpsess;
    UlRelease (lptbl);
    return;
}

SCODE
ScPickResponseRecip (HWND hdlg, LPFILTER lpfltr)
{
    HRESULT hr;
    SCODE sc = S_OK;
    ADRPARM adrparm = {0};
    CHAR rgch[cchNameMax];
    LPADRBOOK lpab = NULL;
    LPADRLIST lpadr = NULL;
    LPSCD lpscd = lpfltr->lpscd;
    LPSPropValue lpval = lpfltr->lpval;
    LPSPropValue lpvalT;
    LPTSTR rglpszDestTitles[] = { "To" };
    ULONG rgulDestComps[] = { MAPI_TO };
    UINT ip;

    hr = lpscd->lpsess->lpVtbl->OpenAddressBook (lpscd->lpsess,
                                        0,
                                        NULL,
                                        AB_NO_DIALOG,
                                        &lpab);
    if (!HR_FAILED (hr))
    {
        /*  Iniitalize the adrparm structure */

        wsprintf (rgch, "Auto-Forward Recipient for '%s'", lpfltr->rgch);

        adrparm.ulFlags = ADDRESS_ONE | DIALOG_MODAL | AB_RESOLVE;
        adrparm.lpszCaption = rgch;
        adrparm.lpszNewEntryTitle = rgch;
        adrparm.lpszDestWellsTitle = "Auto-Forward Recipient";
        adrparm.lppszDestTitles = rglpszDestTitles;
        adrparm.lpulDestComps = rgulDestComps;
        adrparm.lpContRestriction = NULL;
        hr = lpab->lpVtbl->Address (lpab, (ULONG FAR*)&hdlg, &adrparm, &lpadr);
        if (!HR_FAILED (hr))
        {
            /*  Zip though and copy out the display name and the
             *  entryid
             */
            lpvalT = lpadr->aEntries[0].rgPropVals;
            for (ip = 0; ip < lpadr->aEntries[0].cValues; lpvalT++, ip++)
            {
                if (lpvalT->ulPropTag == PR_DISPLAY_NAME)
                {
                    sc = (*lpscd->lpfnAllocMore) (lstrlen (lpvalT->Value.LPSZ) + 1,
                        lpval, &(lpval[ipRLFwdRecip].Value.lpszA));
                    if (FAILED (sc))
                        goto ret;

                    lpval[ipRLFwdRecip].ulPropTag = PR_RULE_FORWARD_RECIP;
                    lstrcpy (lpval[ipRLFwdRecip].Value.lpszA,
                        lpvalT->Value.lpszA);
                }
                else if (lpvalT->ulPropTag == PR_ENTRYID)
                {
                    sc = (*lpscd->lpfnAllocMore) (lpvalT->Value.bin.cb, lpval,
                        &(lpval[ipRLFwdEid].Value.bin.lpb));
                    if (FAILED (sc))
                        goto ret;

                    lpval[ipRLFwdEid].ulPropTag = PR_RULE_FORWARD_RECIP_ENTRYID;
                    lpval[ipRLFwdEid].Value.bin.cb = lpvalT->Value.bin.cb;
                    memcpy (lpval[ipRLFwdEid].Value.bin.lpb,
                        lpvalT->Value.bin.lpb,
                        lpvalT->Value.bin.cb);
                }
            }
ret:
            (*lpscd->lpfnFree) (lpadr->aEntries[0].rgPropVals);
            (*lpscd->lpfnFree) (lpadr);
            hr = ResultFromScode (sc);
        }
        UlRelease (lpab);
    }

    DebugTraceResult (ScPickResponseRecip(), hr);
    return GetScode (hr);
}

SCODE
ScResolveResponseRecip (HWND hdlg, LPFILTER lpfltr, LPTSTR lpszRecip)
{
    SCODE sc = S_OK;
    HRESULT hr;
    CHAR rgch[cchNameMax];
    CHAR rgch2[256];
    BOOL fCtl3d = FALSE;
    LPADRBOOK lpab = NULL;
    LPADRLIST lpadr = NULL;
    LPSCD lpscd = lpfltr->lpscd;
    LPSPropValue lpval = lpfltr->lpval;
    LPSPropValue lpvalT;
    UINT ip;

    hr = lpscd->lpsess->lpVtbl->OpenAddressBook (lpscd->lpsess,
                                        0,
                                        NULL,
                                        AB_NO_DIALOG,
                                        &lpab);
    if (HR_FAILED (hr))
        goto ret;

    if (FAILED (sc = (*lpscd->lpfnAlloc) (sizeof(SPropValue), &lpvalT)) ||
        FAILED (sc = (*lpscd->lpfnAlloc) (CbNewADRLIST(1), &lpadr)))
    {
        hr = ResultFromScode (sc);
        goto ret;
    }

    lpadr->cEntries = 1;
    lpadr->aEntries[0].cValues = 1;
    lpadr->aEntries[0].rgPropVals = lpvalT;
    lpvalT[0].ulPropTag = PR_DISPLAY_NAME;
    lpvalT[0].Value.LPSZ = lpszRecip;
    wsprintf (rgch, "Auto-Forward Recipient for '%s'", lpszRecip);
    hr = lpab->lpVtbl->ResolveName (lpab, 0, 0, rgch, lpadr);
    if (HR_FAILED (hr))
    {
        wsprintf (rgch2,
            "Recipient '%s' for auto-forward filter '%s' is ambiguous.\n"
            "Please select a recipient from the Check Names dialog.",
            lpszRecip, lpfltr->rgch);

        if (CTL3D_GetVer(lpCtl3D) >= 0x220 && !CTL3D_IsAutoSubclass(lpCtl3D))
            CTL3D_AutoSubclass (lpCtl3D, lpscd->hinst, &fCtl3d);
        MessageBox (hdlg, rgch2, rgch, MB_TASKMODAL | MB_OK | MB_ICONINFORMATION);
        CTL3D_CeaseAutoSubclass(lpCtl3D, fCtl3d);

        hr = lpab->lpVtbl->ResolveName (lpab, (ULONG)hdlg, MAPI_DIALOG, rgch, lpadr);
    }

    if (!HR_FAILED (hr))
    {
        /*  Zip though and copy out the display name and the
         *  entryid
         */
        lpvalT = lpadr->aEntries[0].rgPropVals;
        for (ip = 0; ip < lpadr->aEntries[0].cValues; lpvalT++, ip++)
        {
            if (lpvalT->ulPropTag == PR_DISPLAY_NAME)
            {
                sc = (*lpscd->lpfnAllocMore) (lstrlen (lpvalT->Value.LPSZ) + 1,
                    lpval, &(lpval[ipRLFwdRecip].Value.lpszA));
                if (FAILED (sc))
                {
                    hr = ResultFromScode (sc);
                    goto ret;
                }

                lpval[ipRLFwdRecip].ulPropTag = PR_RULE_FORWARD_RECIP;
                lstrcpy (lpval[ipRLFwdRecip].Value.lpszA,
                         lpvalT->Value.lpszA);
            }
            else if (lpvalT->ulPropTag == PR_ENTRYID)
            {
                sc = (*lpscd->lpfnAllocMore) (lpvalT->Value.bin.cb, lpval,
                    &(lpval[ipRLFwdEid].Value.bin.lpb));
                if (FAILED (sc))
                {
                    hr = ResultFromScode (sc);
                    goto ret;
                }

                lpval[ipRLFwdEid].ulPropTag = PR_RULE_FORWARD_RECIP_ENTRYID;
                lpval[ipRLFwdEid].Value.bin.cb = lpvalT->Value.bin.cb;
                memcpy (lpval[ipRLFwdEid].Value.bin.lpb,
                        lpvalT->Value.bin.lpb,
                        lpvalT->Value.bin.cb);
            }
        }
    }

ret:

    if (lpadr)
    {
        (*lpscd->lpfnFree) (lpadr->aEntries[0].rgPropVals);
        (*lpscd->lpfnFree) (lpadr);
    }

    UlRelease (lpab);
    DebugTraceResult (ScResolveResponseRecip(), hr);
    return GetScode (hr);
}

/*
 *  Filter Description Property Sheet Page ------------------------------------
 *
 *  The Filter description page of the configuration property sheets
 *  provides access to the many flags that are available to the SMH
 *  service.
 */

/*
 *  FilterDescriptionPage_INITDAILOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description dialog
 */
BOOL
FilterDescriptionPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    LPFILTER lpfltr = (LPFILTER)(((PROPSHEETPAGE *)lParam)->lParam);
    LPSPropValue lpval;
    HWND hctrl;
    UINT iSto;

    /*  Setup the dialog */

    CTL3D_Subclass (lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    Edit_LimitText (GetDlgItem (hdlg, ID_Name), cchNameMax);

    if (lpval = lpfltr->lpval)
    {
        /*  Fill in the display name */

        Assert (!IsBadReadPtr (lpval, sizeof(SPropValue) * cpRLMax));
        if (lpval[ipRLDisp].ulPropTag == PR_DISPLAY_NAME)
            Edit_SetText (GetDlgItem (hdlg, ID_Name),
                    lpval[ipRLDisp].Value.LPSZ);

        /*  Check the corresponding rule type.  Note that the rule type
         *  enumeration follows the same order as the corresponding control
         *  ID's such that calculating the rule type is a simple addition to
         *  the base control ID.
         */
        if (lpval[ipRLType].ulPropTag == PR_RULE_TYPE)
            CheckRadioButton (hdlg,
                    ID_AnyRecip,
                    ID_ToRecip,
                    (UINT)(ID_AnyRecip + lpval[ipRLType].Value.l - 1));

        /*  Setup the rule data */

        if (lpval[ipRLData].ulPropTag == PR_RULE_DATA)
            Edit_SetText (GetDlgItem (hdlg, ID_Value),
                    lpval[ipRLData].Value.bin.lpb);

        /*  Setup the message store display name.  Note that an empty or
         *  missing value here implies uses the default store.
         */
        FillStoresListbox (lpfltr->lpscd, hdlg);
        if (lpval[ipRLStore].ulPropTag == PR_RULE_STORE_DISPLAY_NAME)
        {
            hctrl = GetDlgItem (hdlg, ID_Store);
            iSto = ComboBox_FindString (hctrl, -1, lpval[ipRLStore].Value.lpszA);
            ComboBox_SetCurSel (hctrl, iSto == CB_ERR ? 0 : iSto);
        }

        /*  Setup the target folder path */

        if (lpval[ipRLPath].ulPropTag == PR_RULE_TARGET_PATH)
            Edit_SetText (GetDlgItem (hdlg, ID_Folder),
                    lpval[ipRLPath].Value.LPSZ);

        /*  Check the boxes indicated by the rule flags */

        if (lpval[ipRLFlags].ulPropTag == PR_RULE_FLAGS)
        {
            CheckDlgButton (hdlg, ID_NotMatch,
                    !!(lpval[ipRLFlags].Value.l & RULE_NOT));
            CheckDlgButton (hdlg, ID_ArchTarg,
                    !!(lpval[ipRLFlags].Value.l & RULE_ARCHIVED));
            CheckDlgButton (hdlg, ID_ArchTargYr,
                    !!(lpval[ipRLFlags].Value.l & RULE_ARCHIVED_BY_YEAR));
            EnableWindow (GetDlgItem (hdlg, ID_ArchTargYr),
                    !!(lpval[ipRLFlags].Value.l & RULE_ARCHIVED));

            if (lpval[ipRLFlags].Value.l & RULE_DELETE)
            {
                CheckRadioButton (hdlg, ID_DeleteMsg, ID_FilterMsg, ID_DeleteMsg);
                EnableWindow (GetDlgItem (hdlg, ID_ArchTarg), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_ArchTargYr), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_Folder), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_ReplyFwd), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_Store), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_Txt3), FALSE);
                EnableWindow (GetDlgItem (hdlg, ID_Txt4), FALSE);
            }
            else if (lpval[ipRLFlags].Value.l & RULE_NO_MOVE)
                CheckRadioButton (hdlg, ID_DeleteMsg, ID_FilterMsg, ID_LeaveMsg);
            else
                CheckRadioButton (hdlg, ID_DeleteMsg, ID_FilterMsg, ID_FilterMsg);
        }
        else
        {
            Button_Enable (GetDlgItem (hdlg, ID_ArchTargYr), FALSE);
            CheckRadioButton (hdlg, ID_FilterMsg, ID_DeleteMsg, ID_FilterMsg);
            lpval[ipRLFlags].ulPropTag = PR_RULE_FLAGS;
            lpval[ipRLFlags].Value.l = 0;
        }
    }

    return TRUE;
}

/*
 *  FilterDescriptionPage_NOTIFY()
 *
 *  Purpose:
 *
 *      Handles the WM_NOTIFY message for the filter description dialog.
 *      On PSN_APPLY, SMHFlags is recomputed from the checkbox states.
 */
BOOL
FilterDescriptionPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    SCODE sc;
    HRESULT hr;
    HWND hctrl;
    LPFILTER lpfltr = (LPFILTER)GetWindowLong (hdlg, DWL_USER);
    LPSCD lpscd = lpfltr->lpscd;
    LPSPropValue lpval = lpfltr->lpval;
    UINT cb;
    UINT i;

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
        if (IsDlgButtonChecked (hdlg, ID_FilterMsg) &&
            Edit_GetTextLength (GetDlgItem(hdlg, ID_Folder)) == 0)
        {
            MessageBox(hdlg, "You have to enter folder name",
                        "Sample Spooler Hook",
                        MB_TASKMODAL | MB_OK | MB_ICONSTOP);
            SetWindowLong(hdlg, DWL_MSGRESULT, TRUE);
            return TRUE;
        }
        break;

      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        /*  Turn off any flags we may be setting down the road */

        lpval[ipRLFlags].Value.l &= ~(RULE_ARCHIVED |
                                      RULE_ARCHIVED_BY_YEAR |
                                      RULE_NOT |
                                      RULE_DELETE |
                                      RULE_NO_MOVE);

        /*  Get the rule name */

        hctrl = GetDlgItem (hdlg, ID_Name);
        cb = Edit_GetTextLength (hctrl) + 1;
        if(cb != 1)
        {
            Edit_GetText (hctrl, lpfltr->rgch, cb);

            lpval[ipRLDisp].ulPropTag = PR_DISPLAY_NAME;
            lpval[ipRLDisp].Value.LPSZ = lpfltr->rgch;
        }

        /*  Rule type */

        lpval[ipRLType].ulPropTag = PR_RULE_TYPE;
        for (i = RL_ANY_RECIP; i < RL_TYPE_MAX; i++)
        {
            if (IsDlgButtonChecked (hdlg, ID_AnyRecip + i - 1))
            {
                lpval[ipRLType].Value.l = i;
                break;
            }
        }

        /*  Rule data */

        hctrl = GetDlgItem (hdlg, ID_Value);
        cb = Edit_GetTextLength (hctrl) + 1;
        sc = (*lpscd->lpfnAllocMore) (cb, lpval, &lpval[ipRLData].Value.bin.lpb);
        if (FAILED (sc))
            goto ret;

        lpval[ipRLData].ulPropTag = PR_RULE_DATA;
        lpval[ipRLData].Value.bin.cb = (ULONG)cb;
        Edit_GetText (hctrl, lpval[ipRLData].Value.bin.lpb, cb);

        /*  Target store EID */

        lpval[ipRLSEid].ulPropTag = PR_NULL;

        /*  Target store */

        hctrl = GetDlgItem (hdlg, ID_Store);
        cb = ComboBox_GetTextLength (hctrl) + 1;
        sc = (*lpscd->lpfnAllocMore) (cb, lpval, &(lpval[ipRLStore].Value.lpszA));
        if (FAILED (sc))
            goto ret;

        lpval[ipRLStore].ulPropTag = PR_RULE_STORE_DISPLAY_NAME;
        ComboBox_GetText (hctrl, lpval[ipRLStore].Value.LPSZ, cb);

        /*  Target folder EID */

        lpval[ipRLEid].ulPropTag = PR_NULL;

        /*  Target folder */

        hctrl = GetDlgItem (hdlg, ID_Folder);
        cb = Edit_GetTextLength (hctrl) + 1;
        sc = (*lpscd->lpfnAllocMore) (cb, lpval, &(lpval[ipRLPath].Value.lpszA));
        if (FAILED (sc))
            goto ret;

        lpval[ipRLPath].ulPropTag = PR_RULE_TARGET_PATH;
        Edit_GetText (hctrl, lpval[ipRLPath].Value.LPSZ, cb);

        /*  Flags */

        if (IsDlgButtonChecked (hdlg, ID_ArchTarg))
        {
            lpval[ipRLFlags].Value.l |= RULE_ARCHIVED;
            if (IsDlgButtonChecked (hdlg, ID_ArchTargYr))
                lpval[ipRLFlags].Value.l |= RULE_ARCHIVED_BY_YEAR;
        }
        if (IsDlgButtonChecked (hdlg, ID_NotMatch))
            lpval[ipRLFlags].Value.l |= RULE_NOT;
        if (IsDlgButtonChecked (hdlg, ID_DeleteMsg))
            lpval[ipRLFlags].Value.l |= RULE_DELETE;
        if (IsDlgButtonChecked (hdlg, ID_LeaveMsg))
            lpval[ipRLFlags].Value.l |= RULE_NO_MOVE;

        /*  Delete the folder and store entryid's properties */

        lpfltr->lpsec->lpVtbl->DeleteProps (lpfltr->lpsec,
                                    (LPSPropTagArray)&sptDelete,
                                    NULL);

        /*  Set the values and save changes on the profile section */

        hr = lpfltr->lpsec->lpVtbl->SetProps (lpfltr->lpsec,
                                            cpRLMax,
                                            lpval,
                                            NULL);
        if (FAILED (sc = GetScode (hr)))
            goto ret;

        hr = lpfltr->lpsec->lpVtbl->SaveChanges (lpfltr->lpsec, KEEP_OPEN_READWRITE);
        if (FAILED (sc = GetScode (hr)))
            goto ret;

ret:
        /*  Set error code into filter */

        lpfltr->sc = sc;
        return !FAILED (sc);

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

/*
 *  FilterDescriptionPage_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  The yearly archive checkboxes must have an
 *      ID that is one greater than the companion checkbox.
 */
BOOL
FilterDescriptionPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    BOOL fCheck;

    switch (id)
    {
      case ID_LeaveMsg:
      case ID_FilterMsg:
      case ID_DeleteMsg:

        /*  Enable windows based on the check state of the ID_FilterMsg */

        fCheck = IsDlgButtonChecked (hdlg, ID_FilterMsg);
        EnableWindow (GetDlgItem (hdlg, ID_ArchTarg), fCheck);
        EnableWindow (GetDlgItem (hdlg, ID_ArchTargYr),
            fCheck && IsDlgButtonChecked (hdlg, ID_ArchTarg));
        EnableWindow (GetDlgItem (hdlg, ID_Folder), fCheck);
        EnableWindow (GetDlgItem (hdlg, ID_Store), fCheck);
        EnableWindow (GetDlgItem (hdlg, ID_Txt3), fCheck);
        EnableWindow (GetDlgItem (hdlg, ID_Txt4), fCheck);
        break;

      case ID_ArchTarg:

        /*  Enable yearly sub-archive button when the archiving is enabled */

        EnableWindow (GetDlgItem (hdlg, ID_ArchTargYr),
            IsDlgButtonChecked (hdlg, ID_ArchTarg));

        break;

      default:

        return TRUE;
    }

    PropSheet_Changed (GetParent (hdlg), hdlg);
    return TRUE;
}

/*
 *  FilterDescriptionPageProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
FilterDescriptionPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (FilterDescriptionPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (FilterDescriptionPage, hdlg, COMMAND, wParam, lParam);
        break;

      case WM_NOTIFY:

        return FHandleWm (FilterDescriptionPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

BOOL
ResponsePage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    EDITSTREAM es = {0};
    LPFILTER lpfltr = (LPFILTER)(((PROPSHEETPAGE *)lParam)->lParam);
    LPFORMATBAR lpfb;
    LPSCD lpscd = lpfltr->lpscd;
    LPSPropValue lpval;
    RTFS rtfs = {0};

    /*  Set the user data component to point to the SCD structure */

    CTL3D_Subclass (lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    /*  Setup the toolbar for the annotation */

    if (!FAILED (ScCreateToolbar (lpscd, hdlg, ID_Annotation, FALSE, &lpfb)) &&
        !FAILED (ScNewRicheditCallback (lpfb,
                                lpscd->lpfnAlloc,
                                lpscd->lpfnAllocMore,
                                lpscd->lpfnFree,
                                &lpfb->lpreoc)))
    {
        SendMessage (GetDlgItem (hdlg, ID_Annotation),
            EM_SETOLECALLBACK,
            0,
            (LPARAM)lpfb->lpreoc);
    }

    /*  Init the dialog fields */

    if (lpval = lpfltr->lpval)
    {
        /*  Fill in the display name */

        Assert (!IsBadReadPtr (lpval, sizeof(SPropValue) * cpRLMax));

        CheckDlgButton (hdlg, ID_ReplyFwd,
            !!(lpval[ipRLFlags].Value.l & RULE_AUTO_RESPONSE));

        /*  Check the boxes indicated by the rule flags */

        if (!(lpval[ipRLFlags].Value.l & RULE_AUTO_FORWARD))
        {
            CheckRadioButton (hdlg, ID_Reply, ID_Forward, ID_Reply);
            EnableWindow (GetDlgItem (hdlg, ID_Recip), FALSE);
            EnableWindow (GetDlgItem (hdlg, ID_PickRecip), FALSE);
        }
        else
            CheckRadioButton (hdlg, ID_Reply, ID_Forward, ID_Forward);

        /*  Setup the fowarding recipient */

        if (lpval[ipRLFwdRecip].ulPropTag == PR_RULE_FORWARD_RECIP)
            Edit_SetText (GetDlgItem (hdlg, ID_Recip),
                    lpval[ipRLFwdRecip].Value.LPSZ);

        /*  How 'bout the RTF */

        if (lpval[ipRLRepFwdRTF].ulPropTag == PR_RULE_REP_FWD_RTF)
        {
            rtfs.cbMax = lpval[ipRLRepFwdRTF].Value.bin.cb;
            rtfs.lpb = lpval[ipRLRepFwdRTF].Value.bin.lpb;
            es.pfnCallback = ReadRTFFromBuffer;
            es.dwCookie = (DWORD)&rtfs;
            SendMessage (GetDlgItem (hdlg, ID_Annotation),
                EM_STREAMIN,
                SF_RTF | SFF_SELECTION | SFF_PLAINRTF,
                (LPARAM)&es);
        }
        else if (lpval[ipRLRepFwd].ulPropTag == PR_RULE_REP_FWD_TEXT)
            Edit_SetText (GetDlgItem (hdlg, ID_Annotation),
                lpval[ipRLRepFwd].Value.LPSZ);
    }

    return TRUE;
}

BOOL
ResponsePage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    SCODE sc = S_OK;
    HRESULT hr;
    CHAR rgch[cchNameMax];
    EDITSTREAM es = {0};
    HWND hctrl;
    LPFILTER lpfltr = (LPFILTER)GetWindowLong (hdlg, DWL_USER);
    LPSCD lpscd = lpfltr->lpscd;

LPSPropValue lpval = lpfltr->lpval;
    RTFS rtfs = {0};
    SizedSPropTagArray (1, spt) = {1, { PR_RULE_FORWARD_RECIP_ENTRYID }};
    UINT cb;

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        if (lpval = lpfltr->lpval)
        {
            /*  Turn off any flags we may be setting down the road */

            lpval[ipRLFlags].Value.l &= ~(RULE_AUTO_RESPONSE |
                                          RULE_AUTO_APPEND_ORIG |
                                          RULE_AUTO_REPLY |
                                          RULE_AUTO_FORWARD);

            /*  See if we are enabled */

            if (IsDlgButtonChecked (hdlg, ID_ReplyFwd))
                lpval[ipRLFlags].Value.l |= RULE_AUTO_RESPONSE;

            /*  Grab the annotation in both plain text and RTF */

            hctrl = GetDlgItem (hdlg, ID_Annotation);
            cb = Edit_GetTextLength (hctrl) + 1;
            sc = (*lpscd->lpfnAllocMore) (cb, lpval, &lpval[ipRLRepFwd].Value.LPSZ);
            if (FAILED (sc))
                goto ret;

            lpval[ipRLRepFwd].ulPropTag = PR_RULE_REP_FWD_TEXT;
            Edit_GetText (hctrl, lpval[ipRLRepFwd].Value.LPSZ, cb);

            rtfs.cb = 0;
            rtfs.cbMax = 0;
            rtfs.lpfnAlloc = lpscd->lpfnAlloc;
            rtfs.lpfnFree = lpscd->lpfnFree;
            es.pfnCallback = WriteRTFToBuffer;
            es.dwCookie = (DWORD)&rtfs;
            SendMessage (hctrl, EM_STREAMOUT, SF_RTF | SFF_PLAINRTF, (LPARAM)&es);
            if (!es.dwError)
            {
                /* make a copy of the RTF data for long term storage */

                sc = (*lpscd->lpfnAllocMore) (rtfs.cb,
                                lpval,
                                &lpval[ipRLRepFwdRTF].Value.bin.lpb);
                if (!FAILED (sc))
                {
                    lpval[ipRLRepFwdRTF].ulPropTag = PR_RULE_REP_FWD_RTF;
                    lpval[ipRLRepFwdRTF].Value.bin.cb = rtfs.cb;
                    memcpy (lpval[ipRLRepFwdRTF].Value.bin.lpb, rtfs.lpb, (UINT)rtfs.cb);
                }
                else
                    lpval[ipRLRepFwdRTF].ulPropTag = PR_NULL;
            }

            (*lpscd->lpfnFree) (rtfs.lpb);

            /*  Delete props that need to be recalc'd */

            lpval[ipRLFwdEid].ulPropTag = PR_NULL;
            lpfltr->lpsec->lpVtbl->DeleteProps (lpfltr->lpsec,
                (LPSPropTagArray)&spt, NULL);

            /*  Grab the recipient information */

            hctrl = GetDlgItem (hdlg, ID_Recip);
            hctrl = GetDlgItem (hdlg, ID_Recip);
            cb = Edit_GetTextLength (hctrl) + 1;
            Edit_GetText (hctrl, rgch, cb);

            /*  See if what we have cached will work */

            if (IsDlgButtonChecked (hdlg, ID_ReplyFwd) &&
                IsDlgButtonChecked (hdlg, ID_Forward))
            {
                if ((lpval[ipRLFwdEid].ulPropTag != PR_RULE_FORWARD_RECIP_ENTRYID) ||
                    (lpval[ipRLFwdRecip].ulPropTag != PR_RULE_FORWARD_RECIP) ||
                    lstrcmp (lpval[ipRLFwdRecip].Value.lpszA, rgch))
                {
                    /*  We do not have a resolved recipient */

                    (void) ScResolveResponseRecip (GetParent (hdlg), lpfltr, rgch);
                }
            }

            /*  Set the response type */

            lpval[ipRLFlags].Value.l |= RULE_AUTO_APPEND_ORIG |
                (IsDlgButtonChecked (hdlg, ID_Reply)
                     ? RULE_AUTO_REPLY
                     : RULE_AUTO_FORWARD);

            /*  Set the values and save changes on the profile section */

            hr = lpfltr->lpsec->lpVtbl->SetProps (lpfltr->lpsec,
                                            cpRLMax,
                                            lpval,
                                            NULL);
            if (FAILED (sc = GetScode (hr)))
                goto ret;

            hr = lpfltr->lpsec->lpVtbl->SaveChanges (lpfltr->lpsec, KEEP_OPEN_READWRITE);
            if (FAILED (sc = GetScode (hr)))
                goto ret;
        }
ret:
        lpfltr->sc = sc;
        return !FAILED(sc);

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

BOOL
ResponsePage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    BOOL fCheck = FALSE;
    LPFILTER lpfltr = (LPFILTER)GetWindowLong (hdlg, DWL_USER);

    if (FDoRTFCommand (GetDlgItem (hdlg, ID_Frame), id, codeNotify))
        return TRUE;

    switch (id)
    {
      case ID_Forward:

        fCheck = TRUE;

        /*  Fall through */

      case ID_Reply:

        EnableWindow (GetDlgItem (hdlg, ID_Recip), fCheck);
        EnableWindow (GetDlgItem (hdlg, ID_PickRecip), fCheck);
        break;

      case ID_PickRecip:

        if (!FAILED (ScPickResponseRecip (GetParent (hdlg), lpfltr)))
        {
            Edit_SetText (GetDlgItem (hdlg, ID_Recip),
                    lpfltr->lpval[ipRLFwdRecip].Value.LPSZ);
        }
    }

    PropSheet_Changed (GetParent (hdlg), hdlg);
    return TRUE;
}

BOOL CALLBACK
ResponsePageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (ResponsePage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (ResponsePage, hdlg, COMMAND, wParam, lParam);
        break;

      case WM_NOTIFY:

        return FHandleWm (ResponsePage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

BOOL
SoundsPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    LPFILTER lpfltr = (LPFILTER)(((PROPSHEETPAGE *)lParam)->lParam);
    LPSPropValue lpval;
    UINT i;

    /*  Set the user data component to point to the SCD structure */

    CTL3D_Subclass (lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    /*  Init the dialog fields */

    if (lpval = lpfltr->lpval)
    {
        /*  See if we are enabled */

        CheckDlgButton (hdlg, ID_Sound,
            !!(lpval[ipRLFlags].Value.l & RULE_PLAY_SOUNDS));

        /*  Fill in the display name */

        Assert (!IsBadReadPtr (lpval, sizeof(SPropValue) * cpRLMax));
        Edit_SetText (GetDlgItem (hdlg, ID_Name), ((LPSCD)lParam)->rgch);

        /*  Setup the sound entries */

        for (i = 0; i < csndMax; i++)
        {
            if (lpval[ipRLLoPri + i].ulPropTag == sptRule.aulPropTag[ipRLLoPri + i])
            {
                Edit_SetText (GetDlgItem (hdlg, ID_LoPri + i),
                    lpval[ipRLLoPri + i].Value.LPSZ);
            }
        }
    }

    return TRUE;
}

BOOL
SoundsPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    SCODE sc = S_OK;
    HRESULT hr;
    HWND hctrl;
    LPFILTER lpfltr = (LPFILTER)GetWindowLong (hdlg, DWL_USER);
    LPSCD lpscd = lpfltr->lpscd;
    LPSPropValue lpval;
    UINT cb;
    UINT i;

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        if (lpval = lpfltr->lpval)
        {
            /*  Turn off any flags we may be setting down the road */

            lpval[ipRLFlags].Value.l &= ~RULE_PLAY_SOUNDS;

            /*  See if we are enabled */

            if (IsDlgButtonChecked (hdlg, ID_Sound))
                lpval[ipRLFlags].Value.l |= RULE_PLAY_SOUNDS;

            /*  Grab the sounds */

            for (i = 0; i < csndMax; i++)
            {
                hctrl = GetDlgItem (hdlg, ID_LoPri + i);
                cb = Edit_GetTextLength (hctrl) + 1;
                sc = (*lpscd->lpfnAllocMore) (cb, lpval, &(lpval[ipRLLoPri + i].Value.lpszA));
                if (FAILED (sc))
                    break;

                lpval[ipRLLoPri + i].ulPropTag = sptRule.aulPropTag[ipRLLoPri + i];
                Edit_GetText (hctrl, lpval[ipRLLoPri + i].Value.LPSZ, cb);
            }

            /*  Set the values and save changes on the profile section */

            hr = lpfltr->lpsec->lpVtbl->SetProps (lpfltr->lpsec,
                                            cpRLMax,
                                            lpval,
                                            NULL);
            if (!FAILED (sc = GetScode (hr)))
            {
                hr = lpfltr->lpsec->lpVtbl->SaveChanges (lpfltr->lpsec, KEEP_OPEN_READWRITE);
                sc = GetScode (hr);
            }
        }

        lpfltr->sc = sc;
        return !FAILED(sc);

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

BOOL
SoundsPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    PropSheet_Changed (GetParent (hdlg), hdlg);
    return TRUE;
}

BOOL CALLBACK
SoundsPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (SoundsPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (SoundsPage, hdlg, COMMAND, wParam, lParam);
        break;

      case WM_NOTIFY:

        return FHandleWm (SoundsPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

/*
 *  HrEditFilterProperties()
 *
 *  Purpose:
 *
 *      Brings up the filter description dialog for the rule properties
 *      found in the profile section muid passed in.
 *
 *  Arguments:
 *
 *      lpscd       SMH dialog structure
 *      hdlg        parent dialog for the filter description
 *      fEdit       indicates lpscd->muid contains a section to edit
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrEditFilterProperties (LPFILTER lpfltr, HWND hdlg, BOOL fEdit)
{
    HRESULT hr = hrSuccess;
    ULONG cval;
    UINT ipg;
    PROPSHEETPAGE psp[] =
    {
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            lpfltr->lpscd->hinst,
            MAKEINTRESOURCE(SMH_PropertiesPage),
            NULL,
            MAKEINTRESOURCE(SMH_GeneralTab),
            FilterDescriptionPageProc,
            0,
            NULL,
            NULL
        },
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            lpfltr->lpscd->hinst,
            MAKEINTRESOURCE(SMH_ResponsePage),
            NULL,
            MAKEINTRESOURCE(SMH_ResponseTab),
            ResponsePageProc,
            0,
            NULL,
            NULL
        },
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            lpfltr->lpscd->hinst,
            MAKEINTRESOURCE(SMH_SoundsPage),
            NULL,
            MAKEINTRESOURCE(SMH_SoundsTab),
            SoundsPageProc,
            0,
            NULL,
            NULL
        },
    };
    PROPSHEETHEADER psh =
    {
        sizeof(PROPSHEETHEADER),
        PSH_PROPSHEETPAGE | PSH_PROPTITLE,
        GetParent (hdlg),
        lpfltr->lpscd->hinst,
        NULL,
        NULL,
        sizeof(psp) / sizeof(PROPSHEETPAGE),
        0,
        (LPCPROPSHEETPAGE)&psp
    };

    /*  If we are creating a new entry, then we first need to create
     *  new profile section UID.
     */
    if (!fEdit)
    {
        lstrcpy (lpfltr->rgch, "Untitled");
        hr = lpfltr->lpscd->lpsup->lpVtbl->NewUID (lpfltr->lpscd->lpsup, &lpfltr->muid);
        if (HR_FAILED (hr))
            goto ret;
    }

    /*  Open the rules profile section */

    hr = lpfltr->lpscd->lpadmin->lpVtbl->OpenProfileSection (lpfltr->lpscd->lpadmin,
                                        &lpfltr->muid,
                                        NULL,
                                        MAPI_MODIFY,
                                        &lpfltr->lpsec);
    if (HR_FAILED (hr))
        goto ret;

    /*  Get the set of properties describing the rule */

    hr = lpfltr->lpsec->lpVtbl->GetProps (lpfltr->lpsec,
                                        (LPSPropTagArray)&sptRule,
                                        0,
                                        &cval,
                                        &lpfltr->lpval);
    if (HR_FAILED (hr))
        goto ret;

    /*  set property sheet data */

    psh.pszCaption = lpfltr->rgch;
    for (ipg = 0; ipg < psh.nPages; ipg++)
        psp[ipg].lParam = (LPARAM)lpfltr;

    switch (PropertySheet (&psh))
    {
      case -1:

        hr = ResultFromScode (MAPI_E_CALL_FAILED);
        break;

      case 0:

        hr = ResultFromScode (MAPI_E_USER_CANCEL);
        break;
    }

ret:

    /*  On success, the profile sub-section has been updated and
     *  committed.  We can now release the resources held for access
     *  to the section.
     */
    (*lpfltr->lpscd->lpfnFree) (lpfltr->lpval);
    UlRelease (lpfltr->lpsec);
    lpfltr->lpval = NULL;
    lpfltr->lpsec = NULL;

    DebugTraceResult (HrEditFilterProperties(), hr);
    return hr;
}

/*
 *  Filters Property Sheet Page -----------------------------------------------
 *
 *  The filters page of the configuration property sheets provides a list
 *  of the exisitng filter AND the order in which they are processed.  The
 *  page allows the user to delete, edit or create rules as well as customize
 *  the order in which the rules are processed.
 *
 *  The list is based on the profile section property PR_SMH_RULES.  This
 *  property is a multi-valued binary property that contains rule section
 *  muids.  These muids are paired with the values from PR_SMH_RULE_NAMES.
 *
 *  Furthermore, there exists an additional property, PR_SMH_ORPHANED_RULES,
 *  that is used to maintain references to rules that have been deleted.
 *
 *  When page processing is complete -- when either OK, APPLY or CANCEL
 *  is chosen, the properties related to this page are set in the profile
 *  and the changes are saved.  Note that switching out of the filters
 *  page to another page includes an implicit APPLY before switching
 *  pages.
 */

/*
 *  EnableFilterPageCtrls()
 *
 *  Purpose:
 *
 *      Enables the set of buttons on the filter page based on the
 *      current selection in the filter list dialog
 */
VOID
EnableFilterPageCtrls (HWND hdlg)
{
    BOOL fDelete;
    BOOL fEdit;
    BOOL fDown = FALSE;
    BOOL fUp = FALSE;
    HWND hctrl = GetDlgItem (hdlg, ID_FilterOrderLB);
    UINT isel = ListBox_GetCurSel (hctrl);

    if (fDelete = fEdit = (isel != LB_ERR))
    {
        /*  If we are at the top, "move up" is not very realistic */

        fUp = (isel != 0);

        /*  If we are at the bottom, "move down" is not very realistic */

        fDown = (isel != (UINT)(ListBox_GetCount (hctrl) - 1));
    }

    /*  Enable the controls */

    Button_Enable (GetDlgItem (hdlg, ID_FilterUp), fUp);
    Button_Enable (GetDlgItem (hdlg, ID_FilterDown), fDown);
    Button_Enable (GetDlgItem (hdlg, ID_EditFilter), fEdit);
    Button_Enable (GetDlgItem (hdlg, ID_RmvFilter), fDelete);
    return;
}

/*
 *  FilterPage_INITDIALOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description
 *      dialog.
 *
 *      The current list of filters are processed and the name is
 *      retrieved for use in the dialog.
 */
BOOL
FilterPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    SCODE sc = S_OK;
    BOOL fInit;
    HWND hctrl;
    LPBYTE lpmuid;
    LPSCD lpscd = (LPSCD)(((PROPSHEETPAGE *)lParam)->lParam);
    LPSPropValue lpval;
    LPTSTR lpsz;
    UINT cb;
    UINT crl;
    UINT irl = 0;

    /*  Setup the dialog */

    CTL3D_Subclass(lpCtl3D, hdlg, CTL3D_ALL);
    SizeODButtons (lpscd->hinst, hdlg, SMH_FilterPage);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    /*  Load the filter LBX and create the mapping */

    if ((lpval = lpscd->lpval) && (lpval[ipRules].ulPropTag == PR_SMH_RULES))
    {
        hctrl = GetDlgItem (hdlg, ID_FilterOrderLB);

        crl = (UINT)lpval[ipRules].Value.MVbin.cValues;
        Assert (crl == lpval[ipNames].Value.MVbin.cValues);

        if (fInit = !lpscd->lpbin)
        {
            /*  As we fill the listbox we are going to make a copy of the
             *  of the multi-valued arrays for use while editing
             */

            cb = (crl + GROW_SIZE) * sizeof(LPTSTR);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, (LPVOID FAR *)&lpscd->lppsz)))
                goto ret;

            cb = (crl + GROW_SIZE) * sizeof(SBinary);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, &lpscd->lpbin)))
                goto ret;

            lpscd->crlMax = crl + GROW_SIZE;
        }

        for (irl = 0; irl < crl; irl++)
        {
            if (fInit)
            {
                /*  Allocate space (linked) for the copy */

                lpsz = lpval[ipNames].Value.MVSZ.lppszA[irl];
                lpmuid = lpval[ipRules].Value.MVbin.lpbin[irl].lpb;
                cb = lstrlen (lpval[ipNames].Value.MVSZ.lppszA[irl]) + 1;
                if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, &lpscd->lppsz[irl])) ||
                    FAILED (sc = (*lpscd->lpfnAlloc) (sizeof(MAPIUID), &lpscd->lpbin[irl].lpb)))
                    break;
            }
            else
                lpsz = lpscd->lppsz[irl];

            /*  Add the item to the list and copy it local (maybe) */

            if ((ListBox_AddString (hctrl, lpsz) != LB_ERRSPACE) &&
                (ListBox_SetItemData (hctrl, irl, irl) != LB_ERR) &&
                fInit)
            {
                lstrcpy (lpscd->lppsz[irl], lpsz);
                lpscd->lpbin[irl].cb = sizeof(MAPIUID);
                memcpy (lpscd->lpbin[irl].lpb, lpmuid, sizeof(MAPIUID));
            }
        }

        /*  Set the selection to the first filter in the list */

        ListBox_SetCurSel (hctrl, 0);
    }
    else
        sc = MAPI_E_UNCONFIGURED;

ret:

    EnableFilterPageCtrls (hdlg);
    lpscd->crl = irl;
    lpscd->sc = sc;
    return TRUE;
}

/*
 *  FilterPage_NOTIFY()
 *
 *  Purpose:
 *
 *      Handles the WM_NOTIFY message for the filter description dialog.
 *      On PSN_APPLY, the filter order is computed and set in PR_SMH_RULES.
 */
BOOL
FilterPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    HRESULT hr;
    LPSCD lpscd;
    SPropValue rgval[2];

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        DebugTrace ("SMH: saving contents of filter page\n");

        /*  Setup and save out the new properties */

        lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);

        if(lpscd->crl)
        {
            rgval[0].ulPropTag = PR_SMH_RULES;
            rgval[0].Value.MVbin.cValues = lpscd->crl;
            rgval[0].Value.MVbin.lpbin = lpscd->lpbin;
            rgval[1].ulPropTag = PR_SMH_RULE_NAMES;
            rgval[1].Value.MVSZ.cValues = lpscd->crl;
            rgval[1].Value.MVSZ.lppszA = lpscd->lppsz;

            hr = lpscd->lpsec->lpVtbl->SetProps (lpscd->lpsec, 2, rgval, NULL);
        }
        else
        {
            SizedSPropTagArray(2, taga) = {2, {PR_SMH_RULES, PR_SMH_RULE_NAMES}};
            LPSPropProblemArray pProbl = NULL;

            hr = lpscd->lpsec->lpVtbl->DeleteProps(lpscd->lpsec,
                                            (LPSPropTagArray)&taga, &pProbl);
            if (!hr)
            {
                if(pProbl)
                {
                    DebugTraceProblems("SMH: DeleteProps :", pProbl);
                    MAPIFreeBuffer(pProbl);
                }
            }
        }

        if (!HR_FAILED (hr))
            hr = lpscd->lpsec->lpVtbl->SaveChanges (lpscd->lpsec, KEEP_OPEN_READWRITE);

        lpscd->sc = GetScode (hr);
        return TRUE;

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

/*
 *  FilterPage_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog.
 */
BOOL
FilterPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    SCODE sc;
    FILTER fltr = {0};
    HWND hctrl = GetDlgItem (hdlg, ID_FilterOrderLB);
    LPBYTE lpb;
    LPSBinary lpbin;
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    LPTSTR FAR * lppsz;
    LPTSTR lpsz;
    UINT cb;
    UINT irl = 0;
    INT nAdj;

    irl = ListBox_GetCurSel (hctrl);
    switch (id)
    {
      case ID_NewFilter:

        if (lpscd->crl == lpscd->crlMax)
        {
            /*  We need to make room for new entries */

            cb = (lpscd->crl + GROW_SIZE) * sizeof(LPVOID);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, (LPVOID FAR *)&lppsz)))
                break;
            memcpy (lppsz, lpscd->lppsz, lpscd->crl * sizeof(LPTSTR));

            cb = (lpscd->crl + GROW_SIZE) * sizeof(SBinary);
            if (FAILED (sc = (*lpscd->lpfnAlloc) (cb, &lpbin)))
                break;
            memcpy (lpbin, lpscd->lpbin, lpscd->crl * sizeof(SBinary));

            /*  Swap out the old for the new */

            (*lpscd->lpfnFree) (lpscd->lppsz);
            (*lpscd->lpfnFree) (lpscd->lpbin);
            lpscd->crlMax += GROW_SIZE;
            lpscd->lppsz = lppsz;
            lpscd->lpbin = lpbin;

        }
        irl = lpscd->crl;

        /*  Get the new filter */

        fltr.lpscd = lpscd;
        if (!FAILED (sc = GetScode (HrEditFilterProperties (&fltr, hdlg, FALSE))))
        {
            /*  Allocate space for the new rule identifiers */

            if (!FAILED (sc = (*lpscd->lpfnAlloc) (lstrlen (fltr.rgch) + 1, &lpscd->lppsz[irl])) &&
                !FAILED (sc = (*lpscd->lpfnAlloc) (sizeof(MAPIUID), &lpscd->lpbin[irl].lpb)))
            {
                if ((ListBox_AddString (hctrl, fltr.rgch) != LB_ERR) &&
                    (ListBox_SetItemData (hctrl, irl, irl) != LB_ERR))
                {
                    /*  Copy the identifiers across */

                    lstrcpy (lpscd->lppsz[irl], fltr.rgch);
                    memcpy (lpscd->lpbin[irl].lpb, &fltr.muid, sizeof(MAPIUID));
                    lpscd->lpbin[irl].cb = sizeof(MAPIUID);

                    ListBox_SetCurSel (hctrl, irl);
                    PropSheet_Changed (GetParent (hdlg), hdlg);
                    lpscd->crl++;
                }
                else
                    sc = MAPI_E_CALL_FAILED;
            }
        }
        lpscd->sc = sc;
        break;

      case ID_FilterOrderLB:

        if (codeNotify != LBN_DBLCLK)
            break;

        /*  Fall through to edit */

      case ID_EditFilter:

        /*  Copy the muid out for use in the dialog */

        fltr.lpscd = lpscd;
        lstrcpy (fltr.rgch, lpscd->lppsz[irl]);
        memcpy (&fltr.muid, lpscd->lpbin[irl].lpb, sizeof(MAPIUID));
        if (!FAILED (sc = GetScode (HrEditFilterProperties (&fltr, hdlg, TRUE))))
        {
            /*  The name may have changed.  If so, update it */

            if (lstrcmp (fltr.rgch, lpscd->lppsz[irl]))
            {
                /*  See if we need a bigger buffer and alloc one */

                if ((cb = lstrlen (fltr.rgch)) > (UINT)lstrlen (lpscd->lppsz[irl]))
                {
                    if (!FAILED (sc = (*lpscd->lpfnAlloc) (cb + 1, &lpsz)))
                    {
                        (*lpscd->lpfnFree) (lpscd->lppsz[irl]);
                        lstrcpy (lpsz, fltr.rgch);
                        lpscd->lppsz[irl] = lpsz;
                    }
                    else
                        break;
                }
                else
                    lstrcpy (lpscd->lppsz[irl], fltr.rgch);

                if ((ListBox_DeleteString (hctrl, irl) == LB_ERR) ||
                    (ListBox_InsertString (hctrl, irl, fltr.rgch) == LB_ERRSPACE) ||
                    (ListBox_SetItemData (hctrl, irl, irl) == LB_ERR))
                    sc = MAPI_E_NOT_ENOUGH_MEMORY;

                ListBox_SetCurSel (hctrl, irl);
            }
            PropSheet_Changed (GetParent (hdlg), hdlg);
        }
        lpscd->sc = sc;
        break;

      case ID_RmvFilter:

        /*  Remove the string and collapse the local copies */

        if ((irl != LB_ERR) && (ListBox_DeleteString (hctrl, irl) != LB_ERR))
        {
            (*lpscd->lpfnFree) (lpscd->lpbin[irl].lpb);
            (*lpscd->lpfnFree) (lpscd->lppsz[irl]);
            lpscd->crl -= 1;
            if (irl != lpscd->crl)
            {
                memcpy (&lpscd->lppsz[irl], &lpscd->lppsz[irl + 1],
                    (lpscd->crl - irl) * sizeof(LPTSTR));
                memcpy (&lpscd->lpbin[irl], &lpscd->lpbin[irl + 1],
                    (lpscd->crl - irl) * sizeof(SBinary));
            }
        }
        else
            MessageBeep (0);

        ListBox_SetCurSel (hctrl, irl);
        PropSheet_Changed (GetParent (hdlg), hdlg);
        break;

      case ID_FilterUp:
      case ID_FilterDown:

        /*  Ensure we really have something to move */

        if (id == ID_FilterUp)
        {
            nAdj = -1;
            if ((irl == LB_ERR) || (irl == 0))
                break;
        }
        else
        {
            nAdj = 1;
            if ((irl == LB_ERR) || (irl == (UINT)(ListBox_GetCount (hctrl) - 1)))
                break;
        }

        /*  Move it */

        if ((ListBox_DeleteString (hctrl, irl + nAdj) == LB_ERR) ||
            (ListBox_InsertString (hctrl, irl, lpscd->lppsz[irl + nAdj]) == LB_ERRSPACE))
        {
            ListBox_ResetContent (hctrl);
            lpscd->sc = MAPI_E_CALL_FAILED;
            break;
        }
        else
            ListBox_SetItemData (hctrl, irl, irl);

        /*  Move it in the local copies */

        lpsz = lpscd->lppsz[irl + nAdj];
        lpscd->lppsz[irl + nAdj] = lpscd->lppsz[irl];
        lpscd->lppsz[irl] = lpsz;

        lpb = lpscd->lpbin[irl + nAdj].lpb;
        lpscd->lpbin[irl + nAdj].lpb = lpscd->lpbin[irl].lpb;
        lpscd->lpbin[irl].lpb = lpb;

        /*  Set the selection to the new position */

        ListBox_SetCurSel (hctrl, irl + nAdj);
        PropSheet_Changed (GetParent (hdlg), hdlg);
        break;

      case ID_Export:

        ScExportFilters (lpscd, GetParent (hdlg));
        break;

      case ID_Import:

        ScImportFilters (lpscd, GetParent (hdlg), hctrl);
        PropSheet_Changed (GetParent (hdlg), hdlg);
        break;

      default:
        break;
    }

    EnableFilterPageCtrls (hdlg);
    return TRUE;
}

/*
 *  FilterPageProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
FilterPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (FilterPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (FilterPage, hdlg, COMMAND, wParam, lParam);
        return TRUE;

      case WM_DRAWITEM:

        DrawODButton (((LPSCD)GetWindowLong (hdlg, DWL_USER))->hinst,
            (DRAWITEMSTRUCT FAR *)lParam, TRUE);
        break;

      case WM_NOTIFY:

        return FHandleWm (FilterPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

/*
 *  General Property Sheet Page -----------------------------------------------
 *
 *  The general page of the configuration property sheets provides access
 *  to the many flags that are available to the SMH service.  The dialog
 *  is used to set the value for PR_SMH_FLAGS.
 */

/*
 *  GeneralPage_INITDAILOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description dialog
 */
BOOL
GeneralPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    LPSCD lpscd = (LPSCD)(((PROPSHEETPAGE *)lParam)->lParam);

LPSPropValue lpval = lpscd->lpval;
    ULONG ulFlags;

    CTL3D_Subclass(lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    ulFlags = (lpval[ipFlags].ulPropTag == PR_SMH_FLAGS)
                ? lpval[ipFlags].Value.l
                : 0;

    /*  Enable/check the checkboxes based on SMH flags */

    CheckDlgButton (hdlg, ID_AddToPab, !!(ulFlags & SMH_ADD_TO_PAB));
    CheckDlgButton (hdlg, ID_Deleted, !!(ulFlags & SMH_FILTER_DELETED));
    CheckDlgButton (hdlg, ID_DeletedYr, !!(ulFlags & SMH_FILTER_DELETED_YR));
    CheckDlgButton (hdlg, ID_Inbound, !!(ulFlags & SMH_FILTER_INBOUND));
    CheckDlgButton (hdlg, ID_SentMail, !!(ulFlags & SMH_FILTER_SENTMAIL));
    CheckDlgButton (hdlg, ID_SentMailYr, !!(ulFlags & SMH_FILTER_SENTMAIL_YR));
    CheckDlgButton (hdlg, ID_Unread, !!(ulFlags & SMH_UNREAD_VIEWER));
    EnableWindow (GetDlgItem (hdlg, ID_DeletedYr), !!(ulFlags & SMH_FILTER_DELETED));
    EnableWindow (GetDlgItem (hdlg, ID_SentMailYr), !!(ulFlags & SMH_FILTER_SENTMAIL));
    lpval[ipFlags].ulPropTag = PR_SMH_FLAGS;
    lpval[ipFlags].Value.l = ulFlags;
    return TRUE;
}

/*
 *  GeneralPage_NOTIFY()
 *
 *  Purpose:
 *
 *      Handles the WM_NOTIFY message for the filter description dialog.
 *      On PSN_APPLY, SMHFlags is recomputed from the checkbox states.
 */
BOOL
GeneralPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    HRESULT hr;
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    SPropValue val = {0};

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        DebugTrace ("SMH: saving contents of general page\n");

        /*  Calc the value for PR_SMH_FLAGS */

        if (IsDlgButtonChecked (hdlg, ID_SentMail))
        {
            val.Value.l |= SMH_FILTER_SENTMAIL;
            if (IsDlgButtonChecked (hdlg, ID_SentMailYr))
                val.Value.l |= SMH_FILTER_SENTMAIL_YR;
            else
                val.Value.l &= ~SMH_FILTER_SENTMAIL_YR;
        }
        else
            val.Value.l &= ~(SMH_FILTER_SENTMAIL | SMH_FILTER_SENTMAIL_YR);

        if (IsDlgButtonChecked (hdlg, ID_Deleted))
        {
            val.Value.l |= SMH_FILTER_DELETED;
            if (IsDlgButtonChecked (hdlg, ID_DeletedYr))
                val.Value.l |= SMH_FILTER_DELETED_YR;
            else
                val.Value.l &= ~SMH_FILTER_DELETED_YR;
        }
        else
            val.Value.l &= ~(SMH_FILTER_DELETED | SMH_FILTER_DELETED_YR);

        if (IsDlgButtonChecked (hdlg, ID_Inbound))
            val.Value.l |= SMH_FILTER_INBOUND;
        else
            val.Value.l &= ~SMH_FILTER_INBOUND;

        if (IsDlgButtonChecked (hdlg, ID_Unread))
            val.Value.l |= SMH_UNREAD_VIEWER;
        else
            val.Value.l &= ~SMH_UNREAD_VIEWER;

        if (IsDlgButtonChecked (hdlg, ID_AddToPab))
            val.Value.l |= SMH_ADD_TO_PAB;
        else
            val.Value.l &= ~SMH_ADD_TO_PAB;

        val.ulPropTag = PR_SMH_FLAGS;
        hr = lpscd->lpsec->lpVtbl->SetProps (lpscd->lpsec,
                                        1,
                                        &val,
                                        NULL);
        if (!HR_FAILED (hr))
            hr = lpscd->lpsec->lpVtbl->SaveChanges (lpscd->lpsec, KEEP_OPEN_READWRITE);

        lpscd->sc = GetScode (hr);
        return TRUE;

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

/*
 *  GeneralPage_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  The yearly archive checkboxes must have an
 *      ID that is one greater than the companion checkbox.
 */
BOOL
GeneralPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    switch (id)
    {
      case ID_SentMail:
      case ID_Deleted:

        EnableWindow (GetDlgItem (hdlg, id + 1),
                    !!IsDlgButtonChecked (hdlg, id));
        break;

      case ID_SentMailYr:
      case ID_DeletedYr:
      case ID_Inbound:
      case ID_Unread:
      case ID_AddToPab:

        break;

      default:

        return TRUE;
    }
    PropSheet_Changed (GetParent (hdlg), hdlg);
    return TRUE;
}

/*
 *  GeneralPageProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
GeneralPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (GeneralPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (GeneralPage, hdlg, COMMAND, wParam, lParam);
        break;

      case WM_NOTIFY:

        return FHandleWm (GeneralPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

/*
 *  Exclussion Edit Dialog ----------------------------------------------------
 *
 *  The exclusion edit dialog alows the user to input a single entry into
 *  the list of excluded message classes.
 *
 *  All information is passed via the SCD structure.
 *
 *      lpscd->rgch     [IN\OUT]    contains the message class to exclude
 *
 *  The caller is responsible for merging the new value into the
 *  exclusion list.
 */

/*
 *  ExclusionEdit_INITDIALOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description dialog
 */
BOOL
ExclusionEdit_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    CTL3D_Subclass(lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, lParam);

    /*  Enable/limit the exclusion edit */

    Edit_LimitText (GetDlgItem (hdlg, ID_ExclusionClass), cchNameMax - 1);
    Button_Enable (GetDlgItem (hdlg, IDOK), FALSE);
    return TRUE;
}

/*
 *  ExclusionEdit_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog
 */
BOOL ExclusionEdit_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    HWND hctrl = GetDlgItem (hdlg, ID_ExclusionClass);

    switch (id)
    {
      case IDOK:

        Edit_GetText (hctrl, lpscd->rgch, cchNameMax);
        break;

      case IDCANCEL:

        break;

      default:

        return FALSE;
    }

    EndDialog (hdlg, id);
    return TRUE;
}

/*
 *  ExclusionEditProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
ExclusionEditProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HWND hctrl = GetDlgItem (hdlg, ID_ExclusionClass);

    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (ExclusionEdit, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (ExclusionEdit, hdlg, COMMAND, wParam, lParam);
        break;
    }
    Button_Enable (GetDlgItem (hdlg, IDOK), !!Edit_GetTextLength (hctrl));
    return FALSE;
}

/*
 *  Exclussion Page Property Sheet --------------------------------------------
 *
 *  The exclusions page exposes the list of message classes that are
 *  excluded from inbound filtering.  The property that correlates to
 *  the list is PR_SMH_EXCLUSIONS.  Any actions required against this
 *  property are processed within the context of this property sheet.
 */

/*
 *  ExclusionPage_INITDIALOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description dialog
 */
BOOL
ExclusionPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    LPSCD lpscd = (LPSCD)(((PROPSHEETPAGE *)lParam)->lParam);
    HWND hctrl = GetDlgItem (hdlg, ID_ExclusionLB);
    UINT isz;

    CTL3D_Subclass(lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    /*  Populate the exclusion listbox */

    if (lpscd->lpval[ipExc].ulPropTag == PR_SMH_EXCLUSIONS)
        for (isz = 0; isz < lpscd->lpval[ipExc].Value.MVSZ.cValues; isz++)
            ListBox_AddString (hctrl, lpscd->lpval[ipExc].Value.MVSZ.LPPSZ[isz]);

    return TRUE;
}

/*
 *  ExclusionPage_NOTIFY()
 *
 *  Purpose:
 *
 *      Handles the WM_NOTIFY message for the filter description dialog
 */
BOOL
ExclusionPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    SCODE sc;
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    HWND hctrl = GetDlgItem (hdlg, ID_ExclusionLB);
    LPSPropValue lpval;
    LPTSTR FAR * lppsz = NULL;
    SizedSPropTagArray (1, spt) = {1, { PR_SMH_EXCLUSIONS }};
    UINT cex;
    UINT iex;

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        DebugTrace ("SMH: saving contents of exclusions page\n");

        /*  Assemble a new PR_SMH_EXCLUSIONS property value */

        lpval = &lpscd->lpval[ipExc];
        cex = ListBox_GetCount (hctrl);
        if (cex)
        {
            sc = (*lpscd->lpfnAllocMore) (cex * sizeof(LPTSTR),
                                    lpscd->lpval,
                                    (LPVOID FAR *)&lppsz);
            if (!FAILED (sc))
            {
                lpval->ulPropTag = PR_SMH_EXCLUSIONS;
                lpval->Value.MVSZ.LPPSZ = lppsz;
                for (iex = 0; iex < cex; iex++, lppsz++)
                {
                    sc = (*lpscd->lpfnAllocMore) (ListBox_GetTextLen (hctrl, iex) + 1,
                                        lpscd->lpval,
                                        lppsz);
                    if (FAILED (sc))
                        break;

                    ListBox_GetText (hctrl, iex, *lppsz);
                }
                lpval->Value.MVSZ.cValues = iex;

                /*  Set the new value */

                sc = GetScode (lpscd->lpsec->lpVtbl->SetProps (lpscd->lpsec,
                                                        1,
                                                        lpval,
                                                        NULL));
                if (!FAILED (sc))
                    sc = GetScode (lpscd->lpsec->lpVtbl->SaveChanges (lpscd->lpsec,
                                                        KEEP_OPEN_READWRITE));
            }
            lpscd->sc = sc;
        }
        else
        {
            lpval->ulPropTag = PR_NULL;
            lpscd->lpsec->lpVtbl->DeleteProps (lpscd->lpsec, (LPSPropTagArray)&spt, NULL);
        }
        return TRUE;

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

/*
 *  ExclusionPage_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog
 */
BOOL
ExclusionPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    HWND hctrl = GetDlgItem (hdlg, ID_ExclusionLB);
    UINT iex;

    switch (id)
    {
      case ID_NewExclusion:

        id = DialogBoxParam (lpscd->hinst,
                    MAKEINTRESOURCE (SMH_ExclusionEdit),
                    GetParent(hdlg),
                    ExclusionEditProc,
                    (LPARAM)lpscd);
        if (id == IDOK)
            ListBox_SetCurSel (hctrl, ListBox_AddString (hctrl, lpscd->rgch));

        PropSheet_Changed (GetParent (hdlg), hdlg);
        break;

      case ID_RmvExclusion:

        iex = ListBox_GetCurSel (hctrl);
        ListBox_DeleteString (hctrl, iex);
        ListBox_SetCurSel (hctrl, iex);
        PropSheet_Changed (GetParent (hdlg), hdlg);
        break;

      default:

        break;
    }
    return TRUE;
}

/*
 *  ExclusionPageProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
ExclusionPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (ExclusionPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (ExclusionPage, hdlg, COMMAND, wParam, lParam);
        return TRUE;

      case WM_NOTIFY:

        return FHandleWm (ExclusionPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

/*
 *  Out-Of-Office Page Property Sheet -----------------------------------------
 *
 *  The Out-of-office page exposes access to the OOF utility of SMH.
 *  The enabling and message text are both modified via this page.
 *  The corresponding properties are stored in PR_SMH_OOF_ENABLED,
 *  PR_SMH_OOF_TEXT and PR_SMH_OOF_RTF.
 *
 *  The RTF component of the OOF message uses the RICHEDIT implementation
 *  that is available as a part of the WIN95 system.
 */

/*
 *  OofPage_INITDIALOG()
 *
 *  Purpose:
 *
 *      Handles the WM_INITDIALOG message for the filter description dialog
 */
BOOL
OofPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    EDITSTREAM es = {0};
    LPSCD lpscd = (LPSCD)(((PROPSHEETPAGE *)lParam)->lParam);
    LPFORMATBAR lpfb;
    RTFS rtfs = {0};

    CTL3D_Subclass(lpCtl3D, hdlg, CTL3D_ALL);
    SetWindowLong (hdlg, DWL_USER, ((PROPSHEETPAGE *)lParam)->lParam);

    if (!FAILED (ScCreateToolbar (lpscd, hdlg, ID_OofText, TRUE, &lpfb)) &&
        !FAILED (ScNewRicheditCallback (lpfb,
                                lpscd->lpfnAlloc,
                                lpscd->lpfnAllocMore,
                                lpscd->lpfnFree,
                                &lpfb->lpreoc)))
    {
        SendMessage (GetDlgItem (hdlg, ID_OofText),
            EM_SETOLECALLBACK,
            0,
            (LPARAM)lpfb->lpreoc);
    }

    if (lpscd->lpval[ipOofRtf].ulPropTag == PR_SMH_OOF_RTF)
    {
        rtfs.cbMax = lpscd->lpval[ipOofRtf].Value.bin.cb;
        rtfs.lpb = lpscd->lpval[ipOofRtf].Value.bin.lpb;
        es.pfnCallback = ReadRTFFromBuffer;
        es.dwCookie = (DWORD)&rtfs;
        SendMessage (GetDlgItem (hdlg, ID_OofText),
            EM_STREAMIN,
            SF_RTF | SFF_SELECTION | SFF_PLAINRTF,
            (LPARAM)&es);
    }
    else if (lpscd->lpval[ipOof].ulPropTag == PR_SMH_OOF_TEXT)
        Edit_SetText (GetDlgItem (hdlg, ID_OofText), lpscd->lpval[ipOof].Value.LPSZ);

    if (lpscd->lpval[ipOofEnabled].ulPropTag == PR_SMH_OOF_ENABLED)
        CheckDlgButton (hdlg, ID_OofEnabled, lpscd->lpval[ipOofEnabled].Value.b);

    return TRUE;
}

/*
 *  OofPage_NOTIFY()
 *
 *  Purpose:
 *
 *      Handles the WM_NOTIFY message for the filter description dialog
 */
BOOL
OofPage_NOTIFY (HWND hdlg, UINT id, NMHDR FAR * lpnmhdr)
{
    SCODE sc;
    HWND hctrl;
    EDITSTREAM es = {0};
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);
    RTFS rtfs = {0};
    SPropValue rgval[3];
    UINT cb;

    switch (lpnmhdr->code)
    {
      case PSN_KILLACTIVE:
      case PSN_RESET:
      case PSN_SETACTIVE:
      default:

        break;

      case PSN_APPLY:

        DebugTrace ("SMH: saving contents of out-of-office page\n");

        hctrl = GetDlgItem (hdlg, ID_OofText);
        cb = Edit_GetTextLength (hctrl) + 1;
        sc = (*lpscd->lpfnAllocMore) (cb,
                            lpscd->lpval,
                            &lpscd->lpval[ipOof].Value.LPSZ);
        if (!FAILED (sc))
        {
            lpscd->lpval[ipOof].ulPropTag = PR_SMH_OOF_TEXT;
            Edit_GetText (hctrl, lpscd->lpval[ipOof].Value.LPSZ, cb);
            DebugTrace ("SMH: OOF: text size: %d\n", lstrlen (lpscd->lpval[ipOof].Value.LPSZ));

            lpscd->lpval[ipOofEnabled].ulPropTag = PR_SMH_OOF_ENABLED;
            lpscd->lpval[ipOofEnabled].Value.b =
                IsDlgButtonChecked (hdlg, ID_OofEnabled);

            /*  Grab the RTF */

            rtfs.cbMax = (FAILED ((*lpscd->lpfnAlloc) (cb * 2, &rtfs.lpb))) ? 0 : cb * 2;
            rtfs.lpfnAlloc = lpscd->lpfnAlloc;
            rtfs.lpfnFree = lpscd->lpfnFree;
            es.pfnCallback = WriteRTFToBuffer;
            es.dwCookie = (DWORD)&rtfs;
            SendMessage (hctrl, EM_STREAMOUT, SF_RTF | SFF_PLAINRTF, (LPARAM)&es);
            if (!es.dwError)
            {
                lpscd->lpval[ipOofRtf].ulPropTag = PR_SMH_OOF_RTF;
                lpscd->lpval[ipOofRtf].Value.bin.cb = rtfs.cb;
                lpscd->lpval[ipOofRtf].Value.bin.lpb = rtfs.lpb;
                DebugTrace ("SMH: OOF: RTF size: %ld\n", lpscd->lpval[ipOofRtf].Value.bin.cb);
            }

            rgval[0] = lpscd->lpval[ipOof];
            rgval[1] = lpscd->lpval[ipOofEnabled];
            rgval[2] = lpscd->lpval[ipOofRtf];
            sc = GetScode (lpscd->lpsec->lpVtbl->SetProps (lpscd->lpsec,
                                                        3,
                                                        rgval,
                                                        NULL));
            if (!FAILED (sc))
                    sc = GetScode (lpscd->lpsec->lpVtbl->SaveChanges (lpscd->lpsec,
                                                        KEEP_OPEN_READWRITE));
            (*lpscd->lpfnFree) (rtfs.lpb);
        }
        return TRUE;

      case EN_SELCHANGE:

        /*  Update the format bar */

        UpdateFormatBar (GetDlgItem (hdlg, ID_Frame));

        return FALSE;

      case PSN_HELP:

        return TRUE;
    }

    return FALSE;
}

/*
 *  OofPage_COMMAND()
 *
 *  Purpose:
 *
 *      Handles the WM_COMMAND message for the filter description dialog
 */
BOOL
OofPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    LPSCD lpscd = (LPSCD)GetWindowLong (hdlg, DWL_USER);

    if (!FDoRTFCommand (GetDlgItem (hdlg, ID_Frame), id, codeNotify))
    {
        switch (id)
        {
          case ID_OofText:
          case ID_OofEnabled:

            PropSheet_Changed (GetParent (hdlg), hdlg);
            break;
        }
    }
    return TRUE;
}

/*
 *  OofPageProc()
 *
 *  Purpose:
 *
 *      Dispatches window messages to the proper function for processing
 */
BOOL CALLBACK
OofPageProc (HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_INITDIALOG:

        FHandleWm (OofPage, hdlg, INITDIALOG, wParam, lParam);
        return TRUE;

      case WM_COMMAND:

        FHandleWm (OofPage, hdlg, COMMAND, wParam, lParam);
        return TRUE;

      case WM_NOTIFY:

        return FHandleWm (OofPage, hdlg, NOTIFY, wParam, lParam);
    }

    return FALSE;
}

/*
 *  HrDisplayPropSheets()
 *
 *  Purpose:
 *
 *      Brings up the SMH property sheets.
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrDisplayPropSheets (HINSTANCE hinst,
    HWND hwnd,
    LPSCD lpscd)
{
    HRESULT hr = hrSuccess;
    CHAR rgch[60] = {0};
    HINSTANCE hlib;
    UINT ipg;
    PROPSHEETPAGE psp[] =
    {
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            hinst,
            MAKEINTRESOURCE(SMH_GeneralPage),
            NULL,
            MAKEINTRESOURCE(SMH_GeneralTab),
            GeneralPageProc,
            0,
            NULL,
            NULL
        },
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            hinst,
            MAKEINTRESOURCE(SMH_FilterPage),
            NULL,
            MAKEINTRESOURCE(SMH_FilterTab),
            FilterPageProc,
            0,
            NULL,
            NULL
        },
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            hinst,
            MAKEINTRESOURCE(SMH_OofPage),
            NULL,
            MAKEINTRESOURCE(SMH_OofTab),
            OofPageProc,
            0,
            NULL,
            NULL
        },
        {
            sizeof(PROPSHEETPAGE),
            PSP_USETITLE,
            hinst,
            MAKEINTRESOURCE(SMH_ExclusionPage),
            NULL,
            MAKEINTRESOURCE(SMH_ExclusionTab),
            ExclusionPageProc,
            0,
            NULL,
            NULL
        }
    };
    PROPSHEETHEADER psh =
    {
        sizeof(PROPSHEETHEADER),
        PSH_PROPSHEETPAGE | PSH_PROPTITLE,
        hwnd,
        hinst,
        NULL,
        NULL,
        sizeof(psp) / sizeof(PROPSHEETPAGE),
        0,
        (LPCPROPSHEETPAGE)&psp
    };

    /*  set property sheet data */

    for (ipg = 0; ipg < psh.nPages; ipg++)
        psp[ipg].lParam = (LPARAM)lpscd;

    hlib = LoadLibrary (RICHEDIT_LIB);

    if (LoadString (hinst, SMH_ProviderName, rgch, sizeof(rgch)))
    {
        if (!lpCtl3D)
            lpCtl3D = CTL3D_Initialize (hinst);

        psh.pszCaption = rgch;

        switch (PropertySheet (&psh))
        {
          case -1:

            hr = ResultFromScode (MAPI_E_CALL_FAILED);
            break;

          case 0:

            hr = ResultFromScode (MAPI_E_USER_CANCEL);
            break;
        }

        if (lpCtl3D)
        {
            CTL3D_Uninitialize (lpCtl3D);
            lpCtl3D = NULL;
        }

        UlRelease (lpscd->lpsess);
        lpscd->lpsess = NULL;
    }
    else
        hr = ResultFromScode (MAPI_E_CALL_FAILED);

    FreeLibrary (hlib);
    DebugTraceResult (HrDisplayPropSheets(), hr);
    return hr;
}


SMHRC.H

/*
 *  SMHRC.H
 *
 *  Sample mail handling hook
 *  Resource IDs and configuration defines
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 */

#ifdef WIN16
#define DS_3DLOOK           0
#define RICHEDIT_CLASS      "RichEd16"
#else
#ifndef RICHEDIT_CLASS
#define RICHEDIT_CLASS      "RichEdit"
#endif
#endif

/*
 *  Configuration Property Sheets
 */

#define SMH_ProviderName    100
#define SMH_GeneralTab      101
#define SMH_GeneralPage     101
#define SMH_FilterTab       102
#define SMH_FilterPage      102
#define SMH_ExclusionPage   103
#define SMH_ExclusionTab    103
#define SMH_OofPage         104
#define SMH_OofTab          104
#define SMH_PropertiesPage  105
#define SMH_ResponsePage    106
#define SMH_ResponseTab     106
#define SMH_SoundsPage      107
#define SMH_SoundsTab       107

/*
 *  Misc. String Resources
 */

#define SMH_FolderComment   150

/*
 *  Property Sheet Control IDs
 */

#define ID_AddToPab         200
#define ID_DeletedGrp       201
#define ID_DownArrow        202
#define ID_DownArrowDis     203
#define ID_DownArrowInv     204
#define ID_EditFilter       205
#define ID_ExclusionLB      207
#define ID_FilterDown       208
#define ID_FilterOrderLB    209
#define ID_FilterUp         210
#define ID_InboundGrp       211
#define ID_NewExclusion     212
#define ID_NewFilter        213
#define ID_OofEnabled       214
#define ID_OofText          215
#define ID_RmvExclusion     216
#define ID_RmvFilter        217
#define ID_SentMailGrp      218
#define ID_UpArrow          219
#define ID_UpArrowDis       220
#define ID_UpArrowInv       221

#define ID_Frame            222
#define ID_Toolbar          223

#define ID_Font             224
#define ID_FontSize         225

#define ID_Bold             226
#define ID_Italic           227
#define ID_Underline        228

#define ID_Bullet           229
#define ID_Left             230
#define ID_Center           231
#define ID_Right            232
#define ID_Indent           233
#define ID_Collapse         234

#define ID_Import           235
#define ID_Export           236

#define ID_Color            250
#define ID_ColorAuto        251
#define ID_ColorBlack       252
#define ID_ColorMaroon      253
#define ID_ColorGreen       254
#define ID_ColorOlive       255
#define ID_ColorBlue        256
#define ID_ColorPurple      257
#define ID_ColorTeal        258
#define ID_ColorGray        259
#define ID_ColorSilver      260
#define ID_ColorRed         261
#define ID_ColorLime        262
#define ID_ColorYellow      263
#define ID_ColorNavy        264
#define ID_ColorFuchia      265
#define ID_ColorAqua        266
#define ID_ColorWhite       267

#define ID_NYI              999

/*
 *  Configuration Propertysheets
 */

#define ID_AnyRecip         301
#define ID_BccRecip         302
#define ID_Body             303
#define ID_CcRecip          304
#define ID_HasAttach        305
#define ID_MsgClass         306
#define ID_Sender           307
#define ID_Subject          308
#define ID_ToRecip          309

#define ID_ArchTarg         319
#define ID_ArchTargYr       320
#define ID_LeaveMsg         321
#define ID_DeleteMsg        322
#define ID_FilterMsg        323
#define ID_Folder           324
#define ID_Name             325
#define ID_NotMatch         326
#define ID_ReplyFwd         327

#define ID_Sound            330
#define ID_Store            331
#define ID_TargetGrp        332
#define ID_Txt1             333
#define ID_Txt2             334
#define ID_Txt3             335
#define ID_Txt4             336
#define ID_Txt5             337
#define ID_Txt6             338
#define ID_TypeGrp          339
#define ID_Value            340

#define SMH_ExclusionEdit   400
#define ID_ExclusionClass   401

#define ID_Reply            501
#define ID_Forward          502
#define ID_Recip            503
#define ID_Email            504
#define ID_Type             505
#define ID_Annotation       506
#define ID_PickRecip        507

#define ID_SoundsGrp        600
#define ID_LoPri            601
#define ID_NormPri          602
#define ID_HiPri            603

/*
 *  DLL Icon
 */

#define ID_Icon             431

/*
 *  Configuration Wizard
 */

#define WIZ_BASE            500
#define PAGE_INC             10
#define szWizardDialog      "SMH_WizardDialog"
#define ID_SentMailTxt      500
#define ID_SentMail         501
#define ID_SentMailYr       502
#define ID_DeletedTxt       510
#define ID_Deleted          511
#define ID_DeletedYr        512
#define ID_InboundTxt       520
#define ID_Inbound          521
#define ID_UnreadTxt        530
#define ID_UnreadTxt2       531
#define ID_Unread           532


SMHRK.C

/*
 *  S M H P S . C
 *
 *  Sample mail handling sub-string search functions
 *  Uses Rabin/Karp algorythms to find sub-strings
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#include "smhnls.h"

#define ulPrime ((ULONG) 0x00FF00F1)
#define ulBase  ((ULONG) 0x00000100)

BOOL
FRKFindSubpb (LPBYTE pbTarget,
    ULONG cbTarget,
    LPBYTE pbPattern,
    ULONG cbPattern)
{
    UINT    i;
    LPBYTE  pbTargetMax = pbTarget + cbTarget;
    LPBYTE  pbPatternMax = pbPattern + cbPattern;
    ULONG   ulBaseToPowerMod = 1;
    ULONG   ulHashPattern = 0;
    ULONG   ulHashTarget = 0;

    if (cbPattern > cbTarget)
        return FALSE;

    // Compute the power of the left most character in base ulBase
    for (i = 1; i < cbPattern; i++)
        ulBaseToPowerMod = (ulBase * ulBaseToPowerMod) % ulPrime;

    // Calculate the hash function for the src (and the first dst)
    while (pbPattern < pbPatternMax)
    {
        ulHashPattern = (ulHashPattern*ulBase+*pbPattern) % ulPrime;
        ulHashTarget = (ulHashTarget*ulBase+*pbTarget) % ulPrime;
        pbPattern++;
        pbTarget++;
    }

    // Dynamically produce hash values for the string as we go
    for ( ;; )
    {
        // Remember to do the memcmp for the off-chance it doesn't work
        // according to probability
        if (    ulHashPattern == ulHashTarget
            && !memcmp(pbPattern-cbPattern, pbTarget-cbPattern,
            (UINT)cbPattern))
            return TRUE;

        // Assert because this is very unprobable
#ifdef DEBUG
        if (ulHashPattern == ulHashTarget)
            DebugTrace("This is very unprobable!\n");
#endif

        if (pbTarget == pbTargetMax)
            return FALSE;

        ulHashTarget = (ulHashTarget+ulBase*ulPrime-
            *(pbTarget-cbPattern)*ulBaseToPowerMod) % ulPrime;
        ulHashTarget = (ulHashTarget*ulBase+*pbTarget) % ulPrime;
        pbTarget++;
    }
}

BOOL
FRKFindSubpsz (LPSTR pszTarget,
    ULONG cbTarget,
    LPSTR pszPattern,
    ULONG cbPattern,
    ULONG ulFuzzyLevel)
{
    UINT    i;
    ULONG   ulBaseToPowerMod = 1;
    ULONG   ulHashPattern = 0;
    ULONG   ulHashTarget = 0;
    LCID    lcid = GetUserDefaultLCID();
    LPBYTE  pbTarget;
    LPBYTE  pbPattern;
    LPBYTE  pbTargetMax;
    LPBYTE  pbPatternMax;
    BOOL    fResult = FALSE;
    CHAR    *rgchHash;

    // Validate parameters

    switch (ulFuzzyLevel & (FL_IGNORECASE | FL_IGNORENONSPACE))
    {
      case 0:
      default:
        rgchHash = rgchCsds;
        break;

      case FL_IGNORECASE:
        rgchHash = rgchCids;
        break;

      case FL_IGNORENONSPACE:
        rgchHash = rgchCsdi;
        break;

      case FL_IGNORECASE | FL_IGNORENONSPACE:
        rgchHash = rgchCidi;
        break;
    }

    if (ulFuzzyLevel & FL_LOOSE)
        rgchHash = rgchCids;

    pbTarget = (LPBYTE) pszTarget;
    pbPattern = (LPBYTE) pszPattern;
    pbTargetMax = pbTarget + cbTarget;
    pbPatternMax = pbPattern + cbPattern;

    if (cbPattern > cbTarget)
        goto end;

    // Compute the power of the left most character in base ulBase
    for (i = 1; i < cbPattern; i++)
        ulBaseToPowerMod = (ulBase * ulBaseToPowerMod) % ulPrime;

    // Calculate the hash function for the src (and the first dst)
    while (pbPattern < pbPatternMax)
    {
        ulHashPattern = (ulHashPattern*ulBase+rgchHash[*pbPattern]) % ulPrime;
        ulHashTarget = (ulHashTarget*ulBase+rgchHash[*pbTarget]) % ulPrime;
        pbPattern++;
        pbTarget++;
    }

    // Dynamically produce hash values for the string as we go
    for ( ;; )
    {
        if (ulHashPattern == ulHashTarget)
        {
            if (CompareStringA(lcid,
                ((ulFuzzyLevel & FL_IGNORECASE) ? NORM_IGNORECASE : 0) |
                ((ulFuzzyLevel & FL_LOOSE) ? NORM_IGNORECASE : 0) |
                ((ulFuzzyLevel & FL_IGNORENONSPACE) ? NORM_IGNORENONSPACE : 0),
                pbPattern-cbPattern, (UINT)cbPattern,
                pbTarget-cbPattern, (UINT)cbPattern) == 2)
            {
                fResult = TRUE;
                goto end;
            }
        }

#ifdef DEBUG
        if (ulHashPattern == ulHashTarget)
            DebugTrace ("This is very unprobable, unless you are doing "
                "FL_EXACT and an case insensitive match came up "
                "(or you are on DBCS)\n");
#endif

        if (pbTarget == pbTargetMax)
            goto end;

        ulHashTarget = (ulHashTarget+ulBase*ulPrime-
            rgchHash[*(pbTarget-cbPattern)]*ulBaseToPowerMod) % ulPrime;
        ulHashTarget = (ulHashTarget*ulBase+rgchHash[*pbTarget]) % ulPrime;
        pbTarget++;
    }

end:
    return fResult;
}


SMHTB.C

/*
 *  S M H T B . C
 *
 *  Sample mail handling hook configuration toolbar
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"
#include <richedit.h>

#ifndef WIN16
#include <commctrl.h>
#endif

/*  Maximum number of font digits */

#define cchSizeMax  16

/*  Enumeration of button bitmaps if toolbar bitmap */

enum {
    itbBold,
    itbItalic,
    itbUnderline,
    itbColor,
    ctbMax
};

/*  Gap sizes for format bar */

#define cxDownButton    16
#define cxName          (125 + cxDownButton)
#define cxSize          (40 + cxDownButton)
#define cxPlaceHolder   (cxName + 8 + cxSize + 8)
#define cxIndent        (1440 / 4)

/*  Button descriptions for format bar */

static const TBBUTTON rgtbbChar[] =
{
    { cxPlaceHolder, 0L, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, -1},
    { itbColor, ID_Color, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, -1 },
    { itbBold, ID_Bold, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, -1},
    { itbItalic, ID_Italic, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, -1},
    { itbUnderline, ID_Underline, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, -1},
};
#define ctbbChar   (sizeof(rgtbbChar) / sizeof(TBBUTTON))

/*  Color descriptions for dropdown color list */

DWORD rgrgb[] = {
    RGB(  0,   0,   0), /* Auto         */
    RGB(  0,   0,   0), /* Black        */
    RGB(128,   0,   0), /* Dark red     */
    RGB(  0, 128,   0), /* Dark green   */
    RGB(128, 128,   0), /* Dark yellow  */
    RGB(  0,   0, 128), /* Dark blue    */
    RGB(128,   0, 128), /* Dark purple  */
    RGB(  0, 128, 128), /* Dark aqua    */
    RGB(128, 128, 128), /* Dark grey    */
    RGB(192, 192, 192), /* Light grey   */
    RGB(255,   0,   0), /* Light red    */
    RGB(  0, 255,   0), /* Light green  */
    RGB(255, 255,   0), /* Light yellow */
    RGB(  0,   0, 255), /* Light blue   */
    RGB(255,   0, 255), /* Light purple */
    RGB(  0, 255, 255), /* Light aqua   */
    RGB(255, 255, 255), /* White        */
};
#define crgb    (sizeof(rgrgb) / sizeof(rgrgb[0]))
const LPTSTR rgszColor[] = {
    "Auto", "Black", "Maroon",
    "Green", "Olive", "Navy",
    "Purple", "Teal", "Gray",
    "Silver", "Red", "Lime",
    "Yellow", "Blue", "Fuchia",
    "Aqua", "White"
};

/*  Font and paragraph effects */

static const DWORD rgdwEffect[] = { CFE_BOLD, CFE_ITALIC, CFE_UNDERLINE };
static const WORD rgwAlign[] = { PFA_LEFT, PFA_CENTER, PFA_RIGHT };
static const UINT rglFontSize[] =
{
     8,  9, 10, 11,
    12, 14, 16, 18,
    20, 22, 24, 26,
    28, 36, 48, 72
};
#define FontSize(_y)    MulDiv((int)(_y), 72, 1440)

/*  Toolbar and combobox class names */

static const TCHAR rgchTBClass[] = "SMHToolbar";
static const TCHAR rgchComboBox[] = "ComboBox";

#ifdef  WIN16
#define GWL_USERDATA 0
#endif

/*
 *  Font, font size, and color dropdown and listing functions -----------------
 *
 *  These functions are used to fill in and display the character formats
 *  for the format bar.  The font listbox gets filled once and only once
 *  on format bar creation.  The font size listbox will get filled up each
 *  and everytime the font list gets dropped down.  The font color menu
 *  will get filled once at startup.
 */

INT CALLBACK EXPORT
EnumFontNames (LPVOID lpvLogFont,
    NEWTEXTMETRIC FAR * lpntm,
    INT nFontType,
    LPARAM lParam)
{
    UINT i;
    DWORD dw = 0;
    LOGFONT FAR * lplf = FWin4Shell()
                            ? &((ENUMLOGFONTEX FAR *)lpvLogFont)->elfLogFont
                            : (LOGFONT FAR *)lpvLogFont;

    /*  Add the face name into the listbox */

    DebugTrace ("SMH: adding font: %s%s\n", lplf->lfFaceName,
        (lpntm->tmPitchAndFamily & TMPF_TRUETYPE)
            ? " (TrueType)"
            : "");

    i = ComboBox_AddString ((HWND)lParam, lplf->lfFaceName);
    if ((i != CB_ERR) || (i != CB_ERRSPACE))
    {
        Assert (!(lplf->lfCharSet & 0xFFFFFF00));
        Assert (!(lplf->lfPitchAndFamily & 0xFFFFFF00));
        Assert (!(nFontType & 0xFFFF0000));
        dw = lplf->lfCharSet & 0xFF;
        dw <<= 8;
        dw |= lplf->lfPitchAndFamily & 0xFF;
        dw <<= 16;
        dw |= nFontType & 0xFFFF;
        ComboBox_SetItemData ((HWND)lParam, i, dw);
    }
    return TRUE;
}

void
FillFontNames (HWND hwnd)
{
    HDC hdc;

    /*  Empty the contents of the listbox and enumerate the fonts */

    ComboBox_ResetContent (hwnd);
    if (hdc = GetDC(NULL))
    {
        EnumFontFamilies (hdc,
            (LPTSTR)NULL,
            (FONTENUMPROC)EnumFontNames,
            (LPARAM)(LPVOID)hwnd);
        ReleaseDC (NULL, hdc);
    }
}

INT CALLBACK EXPORT
EnumFontSizes (LPVOID lpvLogFont,
    NEWTEXTMETRIC FAR * lpntm,
    INT nFontType,
    LPARAM lParam)
{
    TCHAR rgch[cchSizeMax];
    UINT i;

    if (lpntm->tmPitchAndFamily & TMPF_TRUETYPE)
    {
        /*  If the font is TrueType, then just fillin random sizes */

        for (i = 0; i < (sizeof(rglFontSize) / sizeof(UINT)); i++)
        {
            wsprintf (rgch, "%d", rglFontSize[i]);
            DebugTrace ("SMH: adding font size: %s\n", rgch);
            ComboBox_AddString (((LPFORMATBAR)lParam)->hwndSize, rgch);
        }
        return FALSE;
    }
    else
    {
        /*  Calculate the size of the current instance */

        wsprintf (rgch, "%d", MulDiv ((INT)lpntm->tmHeight - lpntm->tmInternalLeading, 72,
            GetDeviceCaps (((LPFORMATBAR)lParam)->hdc, LOGPIXELSY)));
        if (ComboBox_FindStringExact (((LPFORMATBAR)lParam)->hwndSize, -1, rgch) == CB_ERR)
        {
            DebugTrace ("SMH: font height: %d\n", lpntm->tmHeight - lpntm->tmInternalLeading);
            DebugTrace ("SMH: adding font size: %s\n", rgch);
            ComboBox_AddString (((LPFORMATBAR)lParam)->hwndSize, rgch);
            ((LPFORMATBAR)lParam)->cSize++;
        }
        return TRUE;
    }
}

void
FillSizes (LPFORMATBAR lpfb)
{
    TCHAR rgch[30];

    /*  Empty the contents of the listbox and enumerate the fonts */

    ComboBox_ResetContent (lpfb->hwndSize);
    ComboBox_GetText (lpfb->hwndFont, rgch, sizeof(rgch) - 1);

    if (lpfb->hdc = GetDC (lpfb->hwndSize))
    {
        lpfb->cSize = 0;
        EnumFontFamilies (lpfb->hdc, rgch, (FONTENUMPROC)EnumFontSizes, (LPARAM)lpfb);
        ReleaseDC (lpfb->hwndSize, lpfb->hdc);
    }
}

void CALLBACK
ColorMenu_MeasureItem (HWND hwnd, MEASUREITEMSTRUCT FAR * lpmis)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)GetWindowLong (hwnd, GWL_USERDATA);
    HFONT hfnt;
    TEXTMETRIC tm;

    hfnt = SelectObject (lpfb->hdc, lpfb->hfnt);
    GetTextMetrics (lpfb->hdc, &tm);
    SelectObject (lpfb->hdc, hfnt);
    lpmis->itemHeight = tm.tmHeight;
    lpmis->itemWidth = 14 * tm.tmAveCharWidth;
    return;
}

void CALLBACK EXPORT
ColorMenu_DrawItem (HWND hwnd, DRAWITEMSTRUCT FAR * lpdis)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)(GetWindowLong (hwnd, GWL_USERDATA));
    HBRUSH hbr;
    WORD dx;
    WORD dy;
    RECT rc;
    DWORD rgbBack;
    DWORD rgbText;

    if (lpdis->itemState & ODS_SELECTED)
    {
        rgbBack = SetBkColor (lpdis->hDC, GetSysColor (COLOR_HIGHLIGHT));
        rgbText = SetTextColor (lpdis->hDC, GetSysColor (COLOR_HIGHLIGHTTEXT));
    }
    else
    {
        rgbBack = SetBkColor (lpdis->hDC, GetSysColor(COLOR_WINDOW));
        rgbText = SetTextColor (lpdis->hDC, GetSysColor(COLOR_WINDOWTEXT));
    }

    ExtTextOut (lpdis->hDC,
            lpdis->rcItem.left,
            lpdis->rcItem.top,
            ETO_OPAQUE,
            &lpdis->rcItem,
            NULL, 0, NULL);

    dx = GetSystemMetrics(SM_CXBORDER);
    dy = GetSystemMetrics(SM_CYBORDER);
    rc.top = lpdis->rcItem.top + dy;
    rc.bottom = lpdis->rcItem.bottom - dy;
    rc.left = lpdis->rcItem.left + dx;
    rc.right = rc.left + 2 * (rc.bottom - rc.top);

    if (hbr = CreateSolidBrush (lpdis->itemData))
    {
        hbr = SelectObject (lpdis->hDC, hbr);
        Rectangle (lpdis->hDC, rc.left, rc.top, rc.right, rc.bottom);
        DeleteObject (SelectObject (lpdis->hDC, hbr));
    }

    DebugTrace ("SMH: color menu: painting item: %s\n",
        (LPCSTR)rgszColor[lpdis->itemID - ID_Color - 1]);
    SelectObject (lpdis->hDC, lpfb->hfnt);
    TextOut (lpdis->hDC,
        2 * dx + rc.right,
        lpdis->rcItem.top,
        rgszColor[lpdis->itemID - ID_Color - 1],
        lstrlen ((LPCSTR)rgszColor[lpdis->itemID - ID_Color - 1]));

    SetTextColor (lpdis->hDC, rgbText);
    SetBkColor (lpdis->hDC, rgbBack);
    return;
}

void
FillColors (LPFORMATBAR lpfb)
{
    UINT irgb;

    /*  Setup the auto color */

    rgrgb[0] = GetSysColor (COLOR_WINDOWTEXT);

    for (irgb = 0; irgb < crgb; irgb++)
    {
        DebugTrace ("SMH: color menu: adding item: %s (0x%08lX)\n",
            (LPCSTR)rgszColor[irgb], rgrgb[irgb]);
        AppendMenu (lpfb->hmnuColor,
            MF_ENABLED | MF_OWNERDRAW,
            ID_Color + irgb + 1,
            (LPCSTR)rgrgb[irgb]);
    }
}

/*
 *  FormatBar window proc -----------------------------------------------------
 *
 *  The formatbar window proc intercepts several window messages and
 *  processes them as needed.  The primary messages we operate on are the
 *  messages that will cause items to be redrawn or shown.
 *
 *  Additionally, WM_DESTROY is processed as for cleanup of all the
 *  formatbar components
 */

LRESULT CALLBACK EXPORT
FormatBarWndProc (HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)GetWindowLong (hwnd, GWL_USERDATA);

    switch (msg)
    {
      case WM_COMMAND:

        if ((GET_WM_COMMAND_CMD (wParam, lParam) == CBN_DROPDOWN) &&
            (GET_WM_COMMAND_ID (wParam, lParam) == ID_FontSize))
            FillSizes (lpfb);
        SendMessage (GetParent(hwnd), WM_COMMAND, wParam, lParam);
        return TRUE;

      case WM_SHOWWINDOW:

        SendMessage (lpfb->hwndTb, msg, wParam, lParam);
        break;

      case WM_DRAWITEM:

        ColorMenu_DrawItem (hwnd, (DRAWITEMSTRUCT FAR *)lParam);
        return TRUE;

      case WM_MEASUREITEM:

        ColorMenu_MeasureItem (hwnd, (MEASUREITEMSTRUCT FAR *)lParam);
        return TRUE;

      case WM_DESTROY:

        DebugTrace ("SMH: destroying format bar\n");
        DestroyWindow (lpfb->hwndTb);
        DestroyWindow (lpfb->hwndFont);
        DestroyWindow (lpfb->hwndSize);
        DeleteObject (lpfb->hbrBtnFace);
        DestroyMenu (lpfb->hmnuColor);
        UlRelease (lpfb->lpreoc);
        (*lpfb->lpscd->lpfnFree) (lpfb);
        break;
    }
    return DefWindowProc (hwnd, msg, wParam, lParam);
}

/*
 *  Toolbar subclass proc -----------------------------------------------------
 *
 *  The subclassing of the toolbar is done so that the listboxes added to
 *  the toolbar will be drawn with the correct control color.
 */

LRESULT CALLBACK EXPORT
ToolbarSubclassProc (HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)GetWindowLong (GetParent (hwnd), GWL_USERDATA);

    switch (msg)
    {
#ifdef _WIN32
      case WM_CTLCOLORLISTBOX:
#else
      case WM_CTLCOLOR:

        switch (HIWORD (lParam))
        {
          case CTLCOLOR_LISTBOX:
#endif
            if (!lpfb->hbrBtnFace)
            {
                lpfb->hbrBtnFace = CreateSolidBrush (FWin4Shell()
                                                        ? GetSysColor (COLOR_WINDOW)
                                                        : GetSysColor (COLOR_BTNFACE));
            }
            return (LRESULT)(LPVOID)lpfb->hbrBtnFace;
#ifdef _WIN32
            break;
#else
            break;
        }
        break;
#endif
    }

    return CallWindowProc (lpfb->lpfnToolbar, hwnd, msg, wParam, lParam);
}

STDMETHODIMP_(SCODE)
ScCreateToolbar (LPSCD lpscd,
    HWND hwnd,
    UINT idPeg,
    BOOL fPropsheet,
    LPFORMATBAR FAR * lppfb)
{
    SCODE sc;
    RECT rc;
    RECT rcId;
    RECT rcTb;
    RECT rcCli;
    UINT x;
    UINT y;
    WNDCLASS wc = {0};
    LPFORMATBAR lpfb = NULL;

    sc = (*lpscd->lpfnAlloc) (sizeof(FORMATBAR), &lpfb);
    if (!FAILED (sc))
    {
        memset (lpfb, 0, sizeof(FORMATBAR));
        lpfb->hwndEdit = GetDlgItem (hwnd, idPeg);

        lpfb->cf.cbSize = sizeof(CHARFORMAT);
        SendMessage (lpfb->hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)(LPVOID)&lpfb->cf);
        lpfb->pf.cbSize = sizeof(PARAFORMAT);
        SendMessage (lpfb->hwndEdit, EM_GETPARAFORMAT, 0, (LPARAM)(LPVOID)&lpfb->pf);
        SendMessage (lpfb->hwndEdit, EM_SETEVENTMASK, 0, ENM_SELCHANGE | ENM_CHANGE);

        lpfb->cf.dwMask = CFM_FACE | CFM_SIZE | CFE_BOLD | CFE_ITALIC | CFE_UNDERLINE | CFM_COLOR;
        lpfb->cf.dwEffects = CFE_AUTOCOLOR;
        lstrcpy (lpfb->cf.szFaceName, "Arial");
        lpfb->cf.yHeight = 200;
        SendMessage (lpfb->hwndEdit, EM_SETCHARFORMAT, 0, (LPARAM)(LPVOID)&lpfb->cf);

        if (!GetClassInfo (lpscd->hinst, rgchTBClass, &wc))
        {
            wc.style = CS_HREDRAW | CS_VREDRAW;
            wc.lpfnWndProc = FormatBarWndProc;
            wc.cbWndExtra = sizeof(LPVOID);
            wc.hInstance = lpscd->hinst;
            wc.hCursor = LoadCursor (NULL, IDC_ARROW);
            wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
            wc.lpszClassName = (LPCTSTR)rgchTBClass;
            SideAssert (RegisterClass (&wc));
        }
        Assert (IsWindow (hwnd));
        GetWindowRect (lpfb->hwndEdit, &rcId);
        if (lpfb->hwndFrame = CreateWindow ((LPCTSTR)rgchTBClass,
                                        "SMH",
                                        WS_CHILD,
                                        0, 0, rcId.right, 30,
                                        hwnd,
                                        (HMENU)ID_Frame,
                                        lpscd->hinst,
                                        NULL))
        {

            CTL3D_SubclassCtl (lpCtl3D, lpfb->hwndFrame);

            lpfb->hmnuColor = CreatePopupMenu();
            FillColors (lpfb);

            if (lpfb->hwndTb = CreateToolbarEx (lpfb->hwndFrame,
                                        CCS_TOP | WS_CHILD,
                                        0,
                                        ctbMax,
                                        lpscd->hinst,
                                        ID_Toolbar,
                                        rgtbbChar,
                                        ctbbChar,
                                        16, 16, 16, 16,
                                        sizeof(TBBUTTON)))
            {
                lpfb->lpscd = lpscd;
                SetWindowLong (lpfb->hwndFrame, GWL_USERDATA, (ULONG)lpfb);

                /*  Move the window to where it belongs */

                GetWindowRect (hwnd, &rc);
                GetClientRect (lpfb->hwndTb, &rcTb);

                x = rcId.left - rc.left;
                y = rcId.top - rc.top - rcTb.bottom - 2;

                /*  Adjust y by any title bars and framing */

                if (!fPropsheet)
                {
                    GetClientRect (hwnd, &rcCli);
                    y -= rc.bottom - rc.top - rcCli.bottom;
                    x -= (rc.right - rc.left - rcCli.right) / 2;
                    y += (rc.right - rc.left - rcCli.right) / 2;
                }

                MoveWindow (lpfb->hwndFrame, x, y, rcId.right - rcId.left, rcTb.bottom, FALSE);

                lpfb->hfnt = (HFONT) SendMessage (lpfb->hwndTb, WM_GETFONT, 0, 0);

                lpfb->lpfnToolbar = (WNDPROC)SetWindowLong (lpfb->hwndTb,
                                        GWL_WNDPROC,
                                        (LONG)ToolbarSubclassProc);

                lpfb->hwndFont = CreateWindow ((LPCTSTR)rgchComboBox,
                                        NULL,
                                        WS_CHILD | WS_VSCROLL | CBS_DROPDOWN | CBS_SORT | WS_VISIBLE,
                                        0, 3, cxName, rcTb.bottom * 5,
                                        lpfb->hwndTb,
                                        (HMENU) ID_Font,
                                        lpscd->hinst,
                                        NULL);

                CTL3D_SubclassCtl (lpCtl3D, lpfb->hwndFont);

                SetWindowFont (lpfb->hwndFont, lpfb->hfnt, TRUE);
                ComboBox_SetExtendedUI (lpfb->hwndFont, TRUE);
                FillFontNames (lpfb->hwndFont);

                lpfb->hwndSize = CreateWindow ((LPCTSTR)rgchComboBox,
                                        NULL,
                                        WS_CHILD | WS_VSCROLL | CBS_DROPDOWN | WS_VISIBLE,
                                        cxName + 8, 3, cxSize, rcTb.bottom * 5,
                                        lpfb->hwndTb,
                                        (HMENU) ID_FontSize,
                                        lpscd->hinst,
                                        NULL);

                CTL3D_SubclassCtl (lpCtl3D, lpfb->hwndSize);

                SetWindowFont (lpfb->hwndSize, lpfb->hfnt, TRUE);
                ComboBox_SetExtendedUI (lpfb->hwndSize, TRUE);
                ComboBox_LimitText (lpfb->hwndSize, cchSizeMax);
                FillSizes (lpfb);

                UpdateFormatBar (lpfb->hwndFrame);
                ShowWindow (lpfb->hwndFrame, SW_SHOW);
                ShowWindow (lpfb->hwndTb, SW_SHOW);
            }
        }
    }
    *lppfb = lpfb;
    DebugTraceSc (ScCreateToolbar(), (lpfb->hwndFrame ? S_OK : MAPI_E_CALL_FAILED));
    return (lpfb->hwndFrame ? S_OK : MAPI_E_CALL_FAILED);
}

STDMETHODIMP_(BOOL)
FDoRTFCommand (HWND hwnd, UINT id, UINT code)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)(GetWindowLong (hwnd, GWL_USERDATA));
    DWORD dw;
    RECT rc;
    UINT iSel;

    switch (id)
    {
      case ID_Font:

        if (code == CBN_SELENDOK)
        {
            FillSizes (lpfb);
            iSel = ComboBox_GetCurSel (lpfb->hwndFont);
            lpfb->cf.dwMask = CFM_FACE | CFM_CHARSET;
            ComboBox_GetLBText (lpfb->hwndFont, iSel, lpfb->cf.szFaceName);
            dw = ComboBox_GetItemData (lpfb->hwndFont, iSel);
            lpfb->cf.bPitchAndFamily = (BYTE)(dw >>= 16) & 0xFF;
            lpfb->cf.bCharSet = (BYTE)(dw >>= 8) & 0xFF;
            SendMessage (lpfb->hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)(LPVOID)&lpfb->cf);
            UpdateFormatBar (lpfb->hwndFrame);
            SetFocus (lpfb->hwndEdit);
        }
        break;

      case ID_FontSize:

        if (code == CBN_SELENDOK)
        {
            UINT i;
            TCHAR rgch[cchSizeMax];

            rgch[0] = 0;
            lpfb->cf.dwMask = CFM_SIZE;
            ComboBox_GetLBText (lpfb->hwndSize,
                        ComboBox_GetCurSel (lpfb->hwndSize),
                        rgch);
            for (lpfb->cf.yHeight = 0, i = 0; rgch[i]; i++)
                lpfb->cf.yHeight = (lpfb->cf.yHeight * 10) + (rgch[i] - '0');
            lpfb->cf.yHeight *= 20;
            SendMessage (lpfb->hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)(LPVOID)&lpfb->cf);
            UpdateFormatBar (lpfb->hwndFrame);
            SetFocus (lpfb->hwndEdit);
        }
        break;
        break;

      case ID_Color:

        if (lpfb->hdc = GetDC (lpfb->hwndTb))
        {
            SendMessage (lpfb->hwndTb, TB_GETITEMRECT, 1, (LPARAM) &rc);
            MapWindowPoints (lpfb->hwndTb, NULL, (LPPOINT)&rc, 2);
            TrackPopupMenu (lpfb->hmnuColor, TPM_LEFTALIGN | TPM_LEFTBUTTON,
                    rc.left, rc.bottom + 2, 0, hwnd, NULL);
            ReleaseDC (lpfb->hwndTb, lpfb->hdc);
        }
        break;

      case ID_ColorAuto:
      case ID_ColorBlack:
      case ID_ColorMaroon:
      case ID_ColorGreen:
      case ID_ColorOlive:
      case ID_ColorBlue:
      case ID_ColorPurple:
      case ID_ColorTeal:
      case ID_ColorGray:
      case ID_ColorSilver:
      case ID_ColorRed:
      case ID_ColorLime:
      case ID_ColorYellow:
      case ID_ColorNavy:
      case ID_ColorFuchia:
      case ID_ColorAqua:
      case ID_ColorWhite:

        lpfb->cf.dwMask = CFM_COLOR;
        lpfb->cf.dwEffects &= ~CFE_AUTOCOLOR;
        lpfb->cf.dwEffects |= (id == ID_ColorAuto) ? CFE_AUTOCOLOR : 0;
        lpfb->cf.crTextColor = (COLORREF)rgrgb[id - ID_Color - 1];
        SendMessage (lpfb->hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)(LPVOID)&lpfb->cf);
        break;

      case ID_Bold:
      case ID_Italic:
      case ID_Underline:

        DebugTrace ("dwMask before: 0x%08lX\n", lpfb->cf.dwMask);
        DebugTrace ("dwEffects before: 0x%08lX\n", lpfb->cf.dwEffects);
        lpfb->cf.dwMask = rgdwEffect[id - ID_Bold];
        lpfb->cf.dwEffects ^= rgdwEffect[id - ID_Bold];
        DebugTrace ("dwMask after: 0x%08lX\n", lpfb->cf.dwMask);
        DebugTrace ("dwEffects after: 0x%08lX\n", lpfb->cf.dwEffects);
        SendMessage (lpfb->hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)(LPVOID)&lpfb->cf);
        break;

      case ID_Indent:
      case ID_Collapse:

        lpfb->pf.dwMask = PFM_STARTINDENT;
        lpfb->pf.dxStartIndent += ((id == ID_Indent) ? cxIndent : -cxIndent);
        SendMessage (lpfb->hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)(LPVOID)&lpfb->pf);
        break;

      case ID_Left:
      case ID_Center:
      case ID_Right:

        lpfb->pf.dwMask = PFM_ALIGNMENT;
        lpfb->pf.wAlignment = rgwAlign[id - ID_Left];
        SendMessage (lpfb->hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)(LPVOID)&lpfb->pf);
        break;

      case ID_Bullet:

        lpfb->pf.dwMask = PFM_NUMBERING | PFM_OFFSET;
        if (lpfb->pf.wNumbering != PFN_BULLET)
        {
            lpfb->pf.wNumbering = PFN_BULLET;
            lpfb->pf.dxOffset = cxIndent;
        }
        else
        {
            lpfb->pf.wNumbering = 0;
            lpfb->pf.dxOffset = 0;
        }
        SendMessage (lpfb->hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)(LPVOID)&lpfb->pf);
        break;

      default:

        return FALSE;
    }
    return TRUE;
}

void
UpdateFormatBar (HWND hwnd)
{
    LPFORMATBAR lpfb = (LPFORMATBAR)(GetWindowLong (hwnd, GWL_USERDATA));
    TCHAR rgch[cchSizeMax];
    UINT i;

    SendMessage (lpfb->hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)(LPVOID)&lpfb->cf);
    DebugTrace ("SMH: updating formatbar: format: %s%s%s\n",
        (lpfb->cf.dwEffects & rgdwEffect[0]) ? "BOLD, " : "",
        (lpfb->cf.dwEffects & rgdwEffect[1]) ? "ITALIC, " : "",
        (lpfb->cf.dwEffects & rgdwEffect[2]) ? "UNDERLINED" : "");
    DebugTrace ("SMH: updating formatbar: current font: %s\n", lpfb->cf.szFaceName);
    DebugTrace ("SMH: updating formatbar: current font height: %d\n", lpfb->cf.yHeight);
    DebugTrace ("SMH: updating formatbar: current font size: %d\n", FontSize (lpfb->cf.yHeight));
    DebugTrace ("SMH: updating formatbar: color: 0x%2X (r), 0x%2X (g), 0x%2x (b)\n",
        GetRValue (lpfb->cf.crTextColor),
        GetGValue (lpfb->cf.crTextColor),
        GetBValue (lpfb->cf.crTextColor));

    for (i = ID_Bold; i <= ID_Underline; i++)
        SendMessage (lpfb->hwndTb, TB_CHECKBUTTON, i,
            MAKELONG (lpfb->cf.dwEffects & rgdwEffect[i - ID_Bold], 0));

    i = ComboBox_FindString (lpfb->hwndFont, -1, lpfb->cf.szFaceName);
    if (i != (UINT)ComboBox_GetCurSel (lpfb->hwndFont))
    {
        ComboBox_SetCurSel (lpfb->hwndFont, i);
        ComboBox_SetText (lpfb->hwndFont, lpfb->cf.szFaceName);
    }

    wsprintf (rgch, "%d", FontSize (lpfb->cf.yHeight));
    i = (UINT)ComboBox_FindStringExact (lpfb->hwndSize, -1, rgch);
    if ((i != CB_ERR) || (i != (UINT)ComboBox_GetCurSel (lpfb->hwndSize)))
        ComboBox_SetCurSel (lpfb->hwndSize, i);
    ComboBox_SetText (lpfb->hwndSize, rgch);
    return;
}


SMHWB.C

/*
 *  S M H W B . C
 *
 *  Sample mail handling wastbasket message archiving
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

/*
 *  sptFilterWb
 *
 *  These are the columns used in wastebasket filtering\archiving
 */
enum { iwbEid, iwbDlv, iwbSbmt };
const static SizedSPropTagArray (3, sptFilterWb) =
{
    3,
    {
        PR_ENTRYID,
        PR_MESSAGE_DELIVERY_TIME,
        PR_CLIENT_SUBMIT_TIME
    }
};

/*
 *  HrFilterDeleted()
 *
 *  Purpose:
 *
 *      Filters all the current message from the 'Wastebasket'/'Deleted Items'
 *      folder based on the archiving model used in sent mail processing.
 *
 *  Arguments:
 *
 *      lpwb            wastbucket struct for the current store
 *      lpbin           sbinary holding the entryid of the message
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrFilterDeleted (LPWB lpwb, LPSRowSet lprws)
{
    HRESULT hr = ResultFromScode (MAPI_E_NOT_ENOUGH_MEMORY);
    FILETIME ft;
    LPBYTE lpeid = NULL;
    LPMAPIFOLDER lpfldr = lpwb->lpfldr;
    LPMDB lpmdb = lpwb->lpmdb;
    LPSBinary lpbin = NULL;
    LPSMH lpsmh = lpwb->lpsmh;
    SBinaryArray sba;
    UINT ib;
    UINT irw;
    ULONG cb = lpwb->lpvalEid->Value.bin.cb;
    ULONG ulFlags = 0;

    if (!FAILED ((*lpsmh->lpfnAlloc) (lprws->cRows * sizeof(SBinary), &lpbin)))
    {
        for (irw = 0; irw < lprws->cRows; )
        {
            ib = 0;
            if (!FAILED ((*lpsmh->lpfnAlloc) (cb, &lpeid)))
            {
                memcpy (lpeid, lpwb->lpvalEid->Value.bin.lpb, (UINT)cb);
                if (lprws->aRow[irw].lpProps[iwbDlv].ulPropTag == PR_MESSAGE_DELIVERY_TIME)
                    ft = lprws->aRow[irw].lpProps[iwbDlv].Value.ft;
                else if (lprws->aRow[irw].lpProps[iwbSbmt].ulPropTag == PR_CLIENT_SUBMIT_TIME)
                    ft = lprws->aRow[irw].lpProps[iwbSbmt].Value.ft;
                else
                {
                    ++irw;
                    continue;
                }

                hr = HrArchiveByDate (lpsmh,
                                &ft,
                                lpfldr,
                                lpmdb,
                                &lpwb->bkit,
                                lpsmh->fCatWb,
                                cb,
                                lpeid);
                if (!HR_FAILED (hr))
                {
                    lpbin[ib++] = lprws->aRow[irw].lpProps[iwbEid].Value.bin;
                    for (irw += 1; irw < lprws->cRows; irw++)
                    {
                        if (lprws->aRow[irw].lpProps[iwbDlv].ulPropTag == PR_MESSAGE_DELIVERY_TIME)
                        {
                            ft = lprws->aRow[irw].lpProps[iwbDlv].Value.ft;
                            if ((CompareFileTime (&lpwb->bkit.dft.ftStart, &ft) == 1) ||
                                (CompareFileTime (&lpwb->bkit.dft.ftEnd, &ft) == -1))
                                break;

                            lpbin[ib++] = lprws->aRow[irw].lpProps[iwbEid].Value.bin;
                        }
                        else if (lprws->aRow[irw].lpProps[iwbSbmt].ulPropTag == PR_CLIENT_SUBMIT_TIME)
                        {
                            ft = lprws->aRow[irw].lpProps[iwbSbmt].Value.ft;
                            if ((CompareFileTime (&lpwb->bkit.dft.ftStart, &ft) == 1) ||
                                (CompareFileTime (&lpwb->bkit.dft.ftEnd, &ft) == -1))
                                break;

                            lpbin[ib++] = lprws->aRow[irw].lpProps[iwbEid].Value.bin;
                        }
                        else
                            break;
                    }

                    sba.cValues = ib;
                    sba.lpbin = lpbin;
                    hr = lpfldr->lpVtbl->CopyMessages (lpfldr,
                                &sba,
                                NULL,
                                lpwb->bkit.lpfldr,
                                0,
                                NULL,
                                MAPI_MOVE);
                }
            }
            (*lpsmh->lpfnFree) (lpeid);
            lpeid = NULL;
        }
    }
    (*lpsmh->lpfnFree) (lpbin);
    DebugTraceResult (HrFilterDeleted(), hr);
    return hr;
}

/*
 *  FilterDeletedThread()
 *
 *  Purpose:
 *
 *      This function will spin off and do the archiving of the messages
 *      in the deleted folder.  It is important to note that on 32 bit
 *      platforms, this function will belong to its own thread.  Thus
 *      cleanup and logoff must wait for the thread to terminate before
 *      releasing returning.
 *
 *  Arguments:
 *
 *      lpwb        pointer to the wastebasket object
 *
 *  Returns:
 *
 *      (DOWRD) : Ignored
 */
static DWORD WINAPI
FilterDeletedThread (LPWB lpwb)
{
    HRESULT hr;
    LPMAPITABLE lptbl;
    LPSRowSet lprws = NULL;

    lptbl = lpwb->lptbl;

#ifdef  _WIN32
    SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_IDLE);
#endif

    while (!lpwb->fBail)
    {
        lptbl->lpVtbl->SeekRow (lptbl, BOOKMARK_BEGINNING, 0, NULL);
        hr = lptbl->lpVtbl->QueryRows (lptbl, 64, 0, &lprws);
        if (HR_FAILED (hr))
            break;

        if (lprws->cRows)
        {
            //  Filter the deleted messages
            //
            HrFilterDeleted (lpwb, lprws);

            while (lprws->cRows)
                (*lpwb->lpsmh->lpfnFree) (lprws->aRow[--lprws->cRows].lpProps);
        }
        else
            break;

        (*lpwb->lpsmh->lpfnFree) (lprws);
        lprws = NULL;
    }

    (*lpwb->lpsmh->lpfnFree) (lprws);

    /*  Reset the filtering indicator */
#ifdef  _WIN32
    CloseHandle (lpwb->ht);
    lpwb->ht = NULL;
#endif
    return 0;
}

/*
 *  WBNotify()
 *
 *  Purpose:
 *
 *      Notification callback on the WB folders of message stores.  When
 *      rows are added to the WB contents table, we enum the table and
 *      filter each message added.
 *
 *  Arguments:
 *
 *      lpv         void pointer to current WB struct
 *      cntf        count of notifications
 *      lpntf       notifications
 *
 *  Returns:
 *
 *      (SCODE)
 */
STDAPI_(SCODE)
WBNotify (LPVOID lpv, ULONG cntf, LPNOTIFICATION lpntf)
{
    BOOL fFilter = FALSE;
    LPWB lpwb = (LPWB)lpv;

    /* Quick and dirty check on the context */

    if (IsBadReadPtr (lpv, sizeof(WB)) ||
        IsBadReadPtr (((LPWB)lpv)->lpsmh, sizeof(SMH)))
        return S_OK;

    /* Just incase we were turned off */

    if (lpwb->lpsmh->fCatWb)
    {
        while (cntf--)
        {
            Assert (lpntf->ulEventType == fnevTableModified);
            if (lpntf->info.tab.ulTableEvent == TABLE_ROW_ADDED)
            {
                fFilter |= TRUE;
                break;
            }
        }

        if (fFilter)
        {
#ifdef  _WIN32
            if(!lpwb->ht)
            {
                DWORD dw;

                lpwb->ht = CreateThread (NULL,
                            1024,
                            (LPTHREAD_START_ROUTINE)FilterDeletedThread,
                            lpwb,
                            0,
                            &dw);
            }
#else
            FilterDeletedThread (lpwb);
#endif  // _WIN32
        }
    }
    return S_OK;
}

/*
 *  HrInitDeletedMailFilter()
 *
 *  Purpose:
 *
 *      Inits the deleted mail filters by opening the store, finding the
 *      WB folder, opening the contents table of the WB, and registering
 *      for table modification notifications.
 *
 *  Arguments:
 *
 *      lpsmg           the sample mail handler object
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrInitDeletedMailFilter (LPSMH lpsmh)
{
    HRESULT hr;
    LPMAPIADVISESINK lpadvz = NULL;
    LPMAPIFOLDER lpfldr = NULL;
    LPMAPITABLE lptbl = NULL;
    LPMDB lpmdb = NULL;
    LPSPropValue lpval = NULL;
    LPWB lpwb = NULL;
    ULONG ulType;
    UINT cerr = 0;
    UINT i;

    for (i = 0; i < lpsmh->lpstotbl->cSto; i++)
    {
        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (sizeof(WB), &lpwb));
        if (HR_FAILED (hr))
            goto nxt;
        memset (lpwb, 0, sizeof(WB));

        hr = HrOpenStoEntry (lpsmh->lpsess, &lpsmh->lpstotbl->aSto[i], &lpmdb);
        if (HR_FAILED (hr))
            goto nxt;

        hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_WASTEBASKET_ENTRYID, &lpval);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lpmdb->lpVtbl->OpenEntry (lpmdb,
                                lpval->Value.bin.cb,
                                (LPENTRYID)lpval->Value.bin.lpb,
                                NULL,
                                MAPI_MODIFY,
                                &ulType,
                                (LPUNKNOWN FAR *)&lpfldr);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lpfldr->lpVtbl->GetContentsTable (lpfldr, 0, &lptbl);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptFilterWb, 0);
        if (HR_FAILED (hr))
            goto nxt;

        hr = HrAllocAdviseSink ((LPNOTIFCALLBACK)&WBNotify, lpwb, &lpadvz);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lptbl->lpVtbl->Advise (lptbl, fnevTableModified, lpadvz, &lpwb->ulAdvz);
        if (HR_FAILED (hr))
            goto nxt;

        UlAddRef (lptbl);
        UlAddRef (lpfldr);
        lpwb->lpmdb = lpmdb;
        lpwb->lptbl = lptbl;
        lpwb->lpfldr = lpfldr;
        lpwb->lpvalEid = lpval;
        lpwb->lpsmh = lpsmh;
        lpval = NULL;

        /* Hook it in */

        lpwb->wbNext = lpsmh->lstWb;
        lpsmh->lstWb = lpwb;
        lpwb = NULL;
nxt:

        if (HR_FAILED (hr))
            cerr++;

        (*lpsmh->lpfnFree) (lpval);
        lpval = NULL;

        (*lpsmh->lpfnFree) (lpwb);
        lpwb = NULL;

        UlRelease (lpadvz);
        lpadvz = NULL;

        UlRelease (lpfldr);
        lpfldr = NULL;

        UlRelease (lptbl);
        lptbl = NULL;
    }

    hr = ResultFromScode (cerr ? MAPI_W_ERRORS_RETURNED : S_OK);
    DebugTraceResult (HrInitDeletedMailFilter(), hr);
    return hr;
}


SMHWIZ.C

/*
 *  S M H W I Z . C
 *
 *  Sample mail handling hook configuration wizard
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

extern SPropTagArray sptConfigProps;

/*
 *  Wizard global and external variables
 */
static LPMAPIPROP lpmpWiz = NULL;
static LPSPropValue lpvalWiz = NULL;
static UINT ipgCur = (UINT)(-1);

/*
 *  MAPI Initialization structure.
 *
 *  By initializing MAPI, the wizard can allocate and/or free memory
 *  using MAPIAllocateBuffer(), MAPIAllocateMore(), and MAPIFreeBuffer()
 */
static const MAPIINIT mapiinit = { MAPI_INIT_VERSION, 0 };

/* Wizard page enumeration */

enum {ipgSentMail, ipgDeleted, ipgInbound, ipgUnread, cpgMax};

/*
 *  TogglePage()
 *
 *  Purpose:
 *
 *      Toggles the state of the current page to be either enabled and
 *      visible or disabled and hidden.
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  Each page exists in a certain range and all
 *      controls in each page must have contiguous IDs starting at the
 *      page ID value.
 *
 *  Arguments:
 *
 *      hdlg            the dialog
 *      ipg             the page index (0 - cpgMax)
 *      fEnable         boolean used to either enable for disable the page
 */
VOID
TogglePage (HWND hdlg, UINT ipg, BOOL fEnable)
{
    UINT ictl = 0;
    HANDLE hctl;

    if (ipg >= cpgMax)
        return;

    while (hctl = GetDlgItem (hdlg, (WIZ_BASE + (ipg * PAGE_INC) + ictl++)))
    {
        EnableWindow (hctl, fEnable);
        ShowWindow (hctl, (fEnable ? SW_SHOW : SW_HIDE));
    }
}

/*
 *  EnablePage()
 *
 *  Purpose:
 *
 *      Toggles the state of the current page controls to be either
 *      enabled or disabled.
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  Each page exists in a certain range and all
 *      controls in each page must have contiguous IDs starting at the
 *      page ID value.
 *
 *  Arguments:
 *
 *      hdlg            the dialog
 *      ipg             the page index (0 - cpgMax)
 *      fEnable         boolean used to either enable for disable the page
 */
VOID
EnablePage (HWND hdlg, UINT ipg, BOOL fEnable)
{
    UINT ictl = 0;
    HANDLE hctl;

    if (ipg >= cpgMax)
        return;

    while (hctl = GetDlgItem (hdlg, (WIZ_BASE + (ipg * PAGE_INC) + ictl++)))
        EnableWindow (hctl, fEnable);
}

/*
 *  ShowPage()
 *
 *  Purpose:
 *
 *      Toggles the state of the current page controls to be either
 *      visible or hidden.
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  Each page exists in a certain range and all
 *      controls in each page must have contiguous IDs starting at the
 *      page ID value.
 *
 *  Arguments:
 *
 *      hdlg            the dialog
 *      ipg             the page index (0 - cpgMax)
 *      fEnable         boolean used to either show or hide the page
 */
VOID
ShowPage (HWND hdlg, UINT ipg, BOOL fEnable)
{
    UINT ictl = 0;
    HANDLE hctl;

    if (ipg >= cpgMax)
        return;

    while (hctl = GetDlgItem (hdlg, (WIZ_BASE + (ipg * PAGE_INC) + ictl++)))
        ShowWindow (hctl, (fEnable ? SW_SHOW : SW_HIDE));
}

/*
 *  WizardPage_INITDIALOG()
 *
 *  Purpose:
 *
 *      Responds to the WM_INITDIALOG message.
 *
 *  Returns:
 *
 *      TRUE iff the initialization was successful
 */
BOOL
WizardPage_INITDIALOG (HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
    ULONG cval;
    ULONG ulFlags;

    if (!FAILED (MAPIInitialize ((LPMAPIINIT)&mapiinit)) &&
        !HR_FAILED (lpmpWiz->lpVtbl->GetProps (lpmpWiz,
                                        (LPSPropTagArray)&sptConfigProps,
                                        0,
                                        &cval,
                                        &lpvalWiz)))
    {
        ulFlags = (lpvalWiz[ipFlags].ulPropTag == PR_SMH_FLAGS)
            ? lpvalWiz[ipFlags].Value.l
            : 0;

        CheckDlgButton (hdlg, ID_SentMail, !!(ulFlags & SMH_FILTER_SENTMAIL));
        CheckDlgButton (hdlg, ID_SentMailYr, !!(ulFlags & SMH_FILTER_SENTMAIL_YR));
        CheckDlgButton (hdlg, ID_Deleted, !!(ulFlags & SMH_FILTER_DELETED));
        CheckDlgButton (hdlg, ID_DeletedYr, !!(ulFlags & SMH_FILTER_DELETED_YR));
        CheckDlgButton (hdlg, ID_Inbound, !!(ulFlags & SMH_FILTER_INBOUND));
        CheckDlgButton (hdlg, ID_Unread, !!(ulFlags & SMH_UNREAD_VIEWER));
        lpvalWiz[ipFlags].ulPropTag = PR_SMH_FLAGS;
        lpvalWiz[ipFlags].Value.l = ulFlags;
        DebugTrace ("SMH: wizard initialized\n");
        return TRUE;
    }
    else
    {
        DebugTrace ("SMH: wizard initializion failed\n");
        return FALSE;
    }
}

/*
 *  WizardPage_COMMAND()
 *
 *  Purpose:
 *
 *      Responds to the WM_COMMAND message.
 *
 *      IMPORTANT: This function relies on the dialog control IDs as
 *      defined in _SMH.RH.  Each page exists in a certain range and all
 *      controls in each page must have contiguous IDs starting at the
 *      page ID value.
 *
 *  Returns:
 *
 *      TRUE iff the command was processed by the wizard page
 */
BOOL
WizardPage_COMMAND (HWND hdlg, UINT id, HWND hwndCtl, UINT codeNotify)
{
    UINT cpgJmp = 1;

    switch (id)
    {
      case ID_SentMail:
      case ID_Deleted:

        /* Check/uncheck the companion checkbox */

        EnableWindow (GetDlgItem (hdlg, id + 1), !!IsDlgButtonChecked (hdlg, id));
        break;

      case WIZ_PREV:

        cpgJmp = (UINT)(-((INT)cpgJmp));

        /* Fall through! */

      case WIZ_NEXT:

        DebugTrace ("SMH: PREV/NEXT'd from pg %d to pg %d\n", ipgCur, ipgCur + cpgJmp);

        /* Disable/hide the current page and enable target page */
        TogglePage (hdlg, ipgCur, FALSE);
        EnablePage (hdlg, ipgCur += cpgJmp, TRUE);

        switch (ipgCur)
        {
          case ipgSentMail:

            EnableWindow (GetDlgItem (hdlg, ID_SentMailYr),
                !!IsDlgButtonChecked (hdlg, ID_SentMail));
            break;

          case ipgDeleted:

            EnableWindow (GetDlgItem (hdlg, ID_DeletedYr),
                !!IsDlgButtonChecked (hdlg, ID_Deleted));
            break;

          case ipgInbound:
          case ipgUnread:

            break;

          case cpgMax:

            if (IsDlgButtonChecked (hdlg, ID_SentMail))
            {
                lpvalWiz[ipFlags].Value.l |= SMH_FILTER_SENTMAIL;
                if (IsDlgButtonChecked (hdlg, ID_SentMailYr))
                    lpvalWiz[ipFlags].Value.l |= SMH_FILTER_SENTMAIL_YR;
                else
                    lpvalWiz[ipFlags].Value.l &= ~SMH_FILTER_SENTMAIL_YR;
            }
            else
                lpvalWiz[ipFlags].Value.l &= ~(SMH_FILTER_SENTMAIL | SMH_FILTER_SENTMAIL_YR);

            if (IsDlgButtonChecked (hdlg, ID_Deleted))
            {
                lpvalWiz[ipFlags].Value.l |= SMH_FILTER_DELETED;
                if (IsDlgButtonChecked (hdlg, ID_DeletedYr))
                    lpvalWiz[ipFlags].Value.l |= SMH_FILTER_DELETED_YR;
                else
                    lpvalWiz[ipFlags].Value.l &= ~SMH_FILTER_DELETED_YR;
            }
            else
                lpvalWiz[ipFlags].Value.l &= ~(SMH_FILTER_DELETED | SMH_FILTER_DELETED_YR);

            if (IsDlgButtonChecked (hdlg, ID_Inbound))
                lpvalWiz[ipFlags].Value.l |= SMH_FILTER_INBOUND;
            else
                lpvalWiz[ipFlags].Value.l &= ~SMH_FILTER_INBOUND;

            if (IsDlgButtonChecked (hdlg, ID_Unread))
                lpvalWiz[ipFlags].Value.l |= SMH_UNREAD_VIEWER;
            else
                lpvalWiz[ipFlags].Value.l &= ~SMH_UNREAD_VIEWER;

            lpmpWiz->lpVtbl->SetProps (lpmpWiz, cpMax, lpvalWiz, NULL);
            lpmpWiz->lpVtbl->SaveChanges (lpmpWiz, KEEP_OPEN_READWRITE);
            break;
        }

        /* Unhide the target page */

        ShowPage (hdlg, ipgCur, TRUE);
        return (BOOL)cpgJmp;

      default:

        return FALSE;
    }

    return TRUE;
}

/*
 *  SMH_WizProc()
 *
 *  Purpose:
 *
 *      The wizard page dialog proceedure.
 *
 *  Arguments:
 *
 *      hdlg        the dialog
 *      wMsgID      the message
 *      wParam
 *      lParam
 */
BOOL STDAPICALLTYPE
SMH_WizProc (HWND hdlg,
    UINT wMsgID,
    WPARAM wParam,
    LPARAM lParam)
{
    //static UINT ipgCur = (UINT)(-1);
    static LPSPropValue lpval = NULL;

    switch (wMsgID)
    {
      case WM_INITDIALOG:

        return (BOOL)FHandleWm (WizardPage, hdlg, INITDIALOG, wParam, lParam);

      case WIZ_QUERYNUMPAGES:

        DebugTrace ("SMH: wizard page count %d\n", cpgMax);
        return (BOOL)cpgMax;

      case WM_CLOSE:

        DebugTrace ("SMH: wizard closed\n");

        ipgCur = (UINT)(-1);
        MAPIFreeBuffer (lpvalWiz);
        MAPIUninitialize ();
        lpvalWiz = NULL;

        UlRelease (lpmpWiz);
        lpmpWiz = NULL;
        break;

      case WM_COMMAND:

        return (BOOL)FHandleWm (WizardPage, hdlg, COMMAND, wParam, lParam);

      default:

        return FALSE;
    }

    return TRUE;
}

/*
 *  WizardEntry()
 *
 *  Purpose:
 *
 *      This is the initial entrypoint for the MAPI 1.0 configuration
 *      wizard.  This function tells the wizard DLL how many pages the
 *      configuration for this service requires as well as the dialog
 *      procedure to call for each individual event.
 *
 *  Arguments:
 *
 *      hInstance       the instance of my dll, this can be used to
 *                      retrieve resources out of my DLL, etc.
 *
 *      lpszRsrcName    [OUT]   on return, this buffer is filled with
 *                              the full name of the dialog resource ID.
 *                              Note that this requires the name to be a
 *                              text value and not something generated
 *                              with the MAKEINTRESOURCE() macro
 *
 *      lpfnDlgProc     [OUT]   on return, holds a function pointer to
 *                              the dialog proc to call for each event
 *
 *      lpMapiProp      the pointer to a IMAPIProp object that is my
 *                      interface to the profile.
 *
 *  Returns:
 *
 *      (SCODE)         S_OK
 */
ULONG STDAPICALLTYPE
SMH_WizEntry (HINSTANCE hInstance,
    LPTSTR FAR * lppszRsrcName,
    DLGPROC FAR * lpfnDlgProc,
    LPMAPIPROP lpMapiProp,
    LPVOID lpsup)
{
    Unreferenced (lpsup);

    /*  Stash the MAPIPROP object in global for use in the
     *  configuration wizard
     */
    lpmpWiz = lpMapiProp;
    UlAddRef (lpmpWiz);

    /*  tell the configuration wizard the dialog template name
     *  and the window proceedure to use
     */
    *lppszRsrcName = (LPTSTR)szWizardDialog;
    *lpfnDlgProc = (DLGPROC)SMH_WizProc;

    return S_OK;
}


README.TXT

Sample Transport

The peer.xp sample is a transport service provider (SMPXP.DLL).

Peer-to-Peer
------------

The sample peer-to-peer transport transmits messages as files and uses
network file paths as its e-mail addresses. Each user has an inbound message
directory and an outbound message directory; the user e-mail address is the
network path to the inbound directory. The transport creates an outbound
message in each recipient's inbound directory (obtained from the recipient's
address).

Not Peer-to-Peer
----------------

If peer-to-peer transmission is disabled (this is a configuration option), the
transport simply leaves outbound messages in the outbound directory and
collects inbound messages from the inbound directory. It does not attempt
delivery. This mode can be used as the first of two stages, where the second
stage moves messages to and from the network.

Addressing
----------

In peer mode, the network path of the user's inbound directory is the e-mail
address. Network paths are normally Universal Naming Convention (UNC) paths,
but an MS-DOS drive and directory path can also be used.

Message in TNEF
---------------

A small set of message properties, including recipients, subject, and message
text, are encoded as text for transmission. Other message properties are
encapsulated in a binary file in the Transport Neutral Encapsulation Format
(TNEF), using functions supplied in TNEF.DLL. This division approximates
the division of properties that real transports must often make, where those
properties understood by non-MAPI clients must be encoded in a transport-
specific format and the remainder can be left to the TNEF.

Interface Lifetime and Cross-Referencing
----------------------------------------

Interfaces created by the transport in response to service provider interface
(SPI) calls, including status interfaces, are hooked to the transport logon
object and invalidated when MAPI logs off the transport. The provider INIT
object is an exception, as you might expect.

Address Type and UID
--------------------

The transport handles a single e-mail address type, which is configurable.
It does not handle any UIDs, that is, it does not call SetProviderUID.

Per-recipient and per-message Option Support
--------------------------------------------

The transport supports deferred delivery of messages on a per-recipient
basis, as well as on a per-message basis.

Supported Features
------------------

Basic features. The sample transport supports sending, receiving, and polling
for new messages.

Configuration. Interactive and programmatic configuration of 14 parameters
is supported.

Status interface. The sample transport supports all methods of the IMAPIStatus
interface, except property setting.

Multithread safety. MAPI carefully limits the exposure of transports to access
by multiple threads of execution on Win32 platforms. The sample transport
protects itself against concurrency problems in the remaining cases where they
might arise (in the status interface, for instance).

Cross-platform support. The sample transport generates executable files for
Windows 3.X, Windows 95, and Windows NT 3.5 and later from a single set of
source files.

Event logging. The sample transport supports event logging to a text file.
The file is automatically limited to a specified size. All transport sessions
use the same file.

Unsupported Features
--------------------

Asynchronous detection of incoming messages. The transport design does not
lend itself to asynchronous detection because the file system does not notify
the transport when new messages arrive.

Address Format
--------------

This transport supports a single address type whose name is configurable.
MSPEER is commonly chosen; this is the type supported by the sample address
book. When creating new one-off entries in an address book (i.e. the Personal
Address Book), this is the value for the Address Type field. Email addresses
for this transport look like DOS file paths. They can be either UNC paths,
or ordinary fully qualified paths:

    \\server-name\share-name\directory-name\...drive-letter:directory-name\...

Note: When typing one-off addresses in the Exchange client, you must double the
backslashes, like this:

    [MSPEER:\\\\server-name\\share-name\\directory\\...]

This is because the client treats the backslash as an escape character.

Configuration
-------------

The sample transport has a three-page property sheet interface for interactive
configuration, which is accessible through service provider logon, through
the ServiceEntry call, and through the IMAPIStatus::SettingsDialog method.
The configurable parameters appear following. The sample transport also
defines a property tag for each parameter so that parameters may be set
programmatically through ServiceEntry calls; see the SMPXP.H header file.

General Page
------------

Log Events. If this check box is selected, the transport logs events to the
log file specified on the Logging page. Otherwise, logging is disabled.

Peer to Peer Enabled. If this check box is selected, the transport attempts
to deliver outbound messages to the directory specified by the e-mail address.
Otherwise, outbound messages are simply placed in the user's own outbound
directory.

Logon UI Always. If this check box is selected, the transport always brings
up this dialog box the first time you log on. If Save Dialog Settings is also
selected, the dialog box comes up filled with the previously entered
information. If Logon UI Always is not selected, no dialog box appears at
logon, and the transport uses saved settings instead.

Save Dialog Settings. If checked, the transport saves the contents of the
dialog box in your MAPI profile. Otherwise, the dialog box contents are used
until you log off from MAPI and then discarded.

WGAP Filename. This edit control contains a name which could be used by an
application to designate resources owned by this user of the peer-to-peer
transport. No known application uses it.

WGAP Directory. This edit control contains the path to a directory which
could be used by an application to store files owned by this user of the
peer-to-peer transport. No known application uses it.

Peer to Peer Page
-----------------

Display Name. This edit control contains the user's full name. This item
appears in the From field of any messages sent using the peer-to-peer transport.

E-mail Type. This edit control contains the type component of the user's e-mail
address. Users who intend to exchange messages with the peer-to-peer transport
should agree on a common value for this item. MSPEER, which is supported by the
the sample address book, is a common choice.

E-mail Address. This edit control contains a network path, preferably a UNC
path, to the user's inbound directory. This item is the return address in
messages sent from this session.

Inbound Msg Dir. This edit control contains a path, preferably a local path,
to the user's inbound directory. This item does not appear in messages; the
transport simply uses it as a location to poll for inbound messages.

Outbound Msg Dir. This edit control contains a path, preferably a local path,
to the user's outbound directory.

Logging Page
------------

Logfile Name. This edit control contains a path to the text file the transport
uses for event logging. If Log Events is checked on the General page, the
transport writes a line of text to this file for each event.

Log High Water Mark. This edit control contains the maximum size of the log
file, in kilobytes. If the log file grows beyond this size, it is truncated.

Log Low Water Mark. This edit control contains the size in kilobytes to which
the log file is truncated when it grows beyond the high-water mark.

Known Problems
--------------

Self-relative UNC paths on Windows for Workgroups. If the user's inbound
directory is shared from the workstation using Windows for Workgroups and the
e-mail address is a UNC path, sending messages to oneself from this
workstation might not work.

Installation
------------

To install the sample transport you have to

1. Merge the file smpxp.inf with the mapisvc.inf located in your windows
   system directory. You can do this using the mergeini utility that comes
   with the MAPI SDK. The syntax is "mergeini -m smpxp.inf".

2. Copy smpxp(32).dll to the windows system directory.

3. Add the sample transport to any MAPI profile using the Mail and Fax
   control panel applet.


XPBASE.C

/*
 -  X P B A S E . C
 -
 *  Purpose:
 *      The sample transport provider illustrates the use of the MAPI
 *      Transport Service Provider Interface and those parts of the
 *      general SPI involved with the Provider Status, Notification,
 *      and some other stuff.
 *
 *      This module contains the following SPI entry points:
 *
 *          XPProviderInit()
 *          Shutdown()
 *          TransportLogon()
 *          AddressTypes()
 *          RegisterOptions()
 *          TransportLogoff()
 *          TransportNotify()
 *          ValidateState()
 *          FlushQueues()
 *          IUnknown methods for IXPProvider (XPP) & IXPLogon (XPL) objects
 *
 *      Additional support functions found here:
 *
 *          FIsValidSession()
 *          CleanupSession()
 *          ScCheckLogonProps()
 *          HrCheckSpoolerYield()
 *          ScCopySessionProps()
 *          ServiceEntry()
 *          HrOpenSingleProvider()
 *          ScMergeLogonProps()
 *
 *  NOTE:
 *  We have a luxury which most providers don't share: our SPI operations
 *  all occur on a single process context.  This means we don't have to
 *  worry a lot about DLL context, etc. like other providers.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include "xpresrc.h"

/* On 32-bit, we want MB_SETFOREGROUND on MessageBoxes. */

#ifndef MB_SETFOREGROUND
#define MB_SETFOREGROUND 0
#endif

/*
 *  No bitness in the name of this DLL.  MAPI will handle that.
 */

#define szDLLName   "smpxp.dll"

/* Property Tags of Profile/UI information for a transport session. */

/* NOTE!!! THE ORDERING OF THIS ARRAY MUST BE EXACTLY THE SAME AS THE
   ORDERING OF PROP ID'S IN SMPLXPT.H!!!! */

SizedSPropTagArray(MAX_LOGON_PROPERTIES, sptLogonArray) =
{
    MAX_LOGON_PROPERTIES,
    {
        /* Properties stored in Profile */
        PR_SAMPLE_DISPLAY_NAME,
        PR_SAMPLE_EMAIL_ADDR_TYPE,
        PR_SAMPLE_EMAIL_ADDRESS,
        PR_SAMPLE_INBOUND_DIR,
        PR_SAMPLE_OUTBOUND_DIR,
        PR_SAMPLE_FILENAME,
        PR_SAMPLE_DIRECTORY,
        PR_SAMPLE_FLAGS,
        PR_SAMPLE_LOGFILE,
        PR_SAMPLE_LOGHIGHWATER,
        PR_SAMPLE_LOGLOWWATER,

        /* UI Temporary properties */
        PR_TEMP_PEER_TO_PEER,
        PR_TEMP_UI_ALWAYS,
        PR_TEMP_LOG_EVENTS,
        PR_TEMP_SAVE_DATA,
        PR_TEMP_LOGHIGHWATER,
        PR_TEMP_LOGLOWWATER
    }
};

/* MessageOption & RecipientOption Default Property Value */

static SPropValue spvMsgOpt = {PR_SAMPLE_PER_MSG_DEFER, 0L, FALSE};
static SPropValue spvRecipOpt = {PR_SAMPLE_PER_RECIP_DEFER, 0L, FALSE};

/* Wizard configuration items */
static LPMAPIPROP lpmpWizard = NULL;
enum
{
    ipDispName,
    ipEmailType,
    ipEmailAddress,
    ipInbox,
    ipOutbox,
    ipFilename,
    ipDirectory,
    ipFlags,
    ipLogFile,
    ipLogHigh,
    ipLogLow,
    cWizProps
};
#define cchNameMax  40
#define cchTypeMax  8

/* Virtual Table for the IXPProvider object */

XPP_Vtbl vtblXPP =
{
    XPP_QueryInterface,
    XPP_AddRef,
    XPP_Release,
    XPP_Shutdown,
    XPP_TransportLogon,
};

/* Virtual Table for the IXPLogon object */

XPL_Vtbl vtblXPL =
{
    XPL_QueryInterface,
    XPL_AddRef,
    XPL_Release,
    XPL_AddressTypes,
    XPL_RegisterOptions,
    XPL_TransportNotify,
    XPL_Idle,
    XPL_TransportLogoff,
    XPL_SubmitMessage,
    XPL_EndMessage,
    XPL_Poll,
    XPL_StartMessage,
    XPL_OpenStatusEntry,
    XPL_ValidateState,
    XPL_FlushQueues
};

/* Miscellaneous function prototypes. */

HRESULT HrDeleteDeferred(LPXPL lpxpl, LPSBinary lpsbinEID);
static void CleanupSession(LPXPL lpxpl);

MSGSERVICEENTRY ServiceEntry;
WIZARDENTRY WizardEntry;
SERVICEWIZARDDLGPROC WizardWndProc;

HRESULT HrOpenSingleProvider(LPPROVIDERADMIN, LPPROFSECT FAR *);
SCODE ScMergeLogonProps(ULONG, LPSPropValue, ULONG, LPSPropValue,
    LPALLOCATEBUFFER, LPALLOCATEMORE, LPSPropValue FAR *);

/*
 -  XPProviderInit
 -
 *  Purpose:
 *      Called by the Spooler prior to initiating actual Transport operations.
 *      Only needs to be called once, regardless of the number of sessions
 *      which the Spooler might establish with this Transport provider.
 *
 *  Parameters:
 *      hInstance           Instance of this library
 *      lpMalloc            OLE2 PMALLOC memory allocator
 *      lpAllocateBuffer    MAPIAllocateBuffer
 *      lpAllocateMore      MAPIAllocateMore
 *      lpFreeBuffer        MAPIFreeBuffer
 *      ulFlags             Reserved for now. Should be zero.
 *      ulSpoolerVer        MAPI version the Spooler is implemented to
 *      lpulTransportVer    Transport puts its MAPI version here
 *      lppXPProvider       Transport returns Transport Init Object here
 *
 *  Returns:
 *      SUCCESS_SUCCESS     Transport thinks all is well. Transport's
 *                          version of MAPI has been stored in
 *                          *lpulTransportVer. Transport's Init Object
 *                          address has been stored in *lppEntryPoints.
 *
 *      MAPI_E_VERSION      Transport doesn't like version of Spooler.
 *                          Contents of lpulTransportVer and
 *                          lppXPProvider are untouched by the Transport.
 *
 *      MAPI_E_CALL_FAILED  Transport already inited or session table
 *                          non-empty. Contents of lpulTransportVer and
 *                          lppXPProvider are untouched by the Transport.
 *
 *  Operation:
 *      In the call, the Spooler will tell the Transport what version of MAPI
 *      it normally expects to support. If the Transport knows that it cannot
 *      handle this version, it should return an error on this call. If it
 *      doesn't know, it should trust the Spooler to handle the problem.
 *
 *      The Transport puts the version of MAPI it expects to support back into
 *      lpulTransportVer and the Spooler should Shutdown if it cannot or
 *      doesn't want to handle this version.
 *
 *      Besides the information which the Spooler will use for its half of the
 *      MAPI version handshake, the Transport will return a valid Init Object
 *      address into the location pointed to by lppXPProvider.
 */

STDINITMETHODIMP
XPProviderInit(HINSTANCE hInstance,
    LPMALLOC lpMalloc,
    LPALLOCATEBUFFER lpAllocateBuffer,
    LPALLOCATEMORE lpAllocateMore,
    LPFREEBUFFER lpFreeBuffer,
    ULONG ulFlags,
    ULONG ulSpoolerVer,
    ULONG * lpulTransportVer,
    LPXPPROVIDER * lppXPProvider)
{
    SCODE sc = S_OK;
    LPXPP lpxpp;

    /*  Test Spooler Version. As we are a MAPI 1.0 transport, we'll
        accept any MAPI version over 1.0 (and trust the Spooler to
        refuse to handle 1.0 if he's too far advanced!)
    */

    if (ulSpoolerVer < CURRENT_SPI_VERSION)
    {
        sc = MAPI_E_VERSION;
        DebugTrace("Spooler version check failed.\n");
        goto ret;
    }

    /*  Allocate space for the XPP structure */

    sc = (*lpAllocateBuffer) (sizeof(XPP), (LPVOID *) &lpxpp);

    if (sc)
    {
        DebugTrace("XPProvider allocation failed.\n");
        goto ret;
    }

    /*  Fill in the data members and the jump table. */

    lpxpp->lpVtbl = &vtblXPP;
    lpxpp->lcInit = 1;
    lpxpp->lpxppMyAddress = lpxpp;
    lpxpp->hInst = hInstance;
    lpxpp->XPSessionList = (LPXPL) NULL;
    lpxpp->fInited = TRUE;
    lpxpp->lpMalloc = lpMalloc;
    lpxpp->lpFreeBuffer = lpFreeBuffer;

    /* We're keeping it, we need to AddRef it! */

    UlAddRef(lpMalloc);

    /*  Initialize a critical section to be used while:
            1. Accessing the Status Object
            2. Doing session list management
            3. Initializing the logging sub-system
            4. Processing a TransportNotify call

        These are areas where the potential for re-entrancy
        is high and we need to protect ourselves.
    */

    InitializeCriticalSection(&lpxpp->csTransport);

    /*  Return version number and IXPProvider object to the Spooler. */

    *lpulTransportVer = CURRENT_SPI_VERSION;
    *lppXPProvider = (LPXPPROVIDER) lpxpp;

ret:
    DebugTraceSc(XPProviderInit, sc);
    return ResultFromScode(sc);
}

/*
 -  lpxpp->lpVtbl->Shutdown
 -
 *  Purpose:
 *      Called by the Spooler when all operations on a Transport are complete.
 *      Only needs to be called once, regardless of the number of sessions
 *      which the Spooler might have established with this Transport provider.
 *
 *  Parameters:
 *      lpulFlags           Contains flags passed from Spooler:
 *
 *                          DEINIT_NORMAL indicates a normal
 *                          operation. All cleanup should proceed
 *                          at whatever pace is required for
 *                          completeness.
 *
 *                          DEINIT_HURRY indicates rapid shutdown.
 *                          Anything that can be done very quickly
 *                          should be done; time-consuming operations
 *                          are to be avoided.
 *
 *                          In the absence of these flag bits, the
 *                          Transport should assume DEINIT_NORMAL.
 *
 *  Returns:
 *      SUCCESS_SUCCESS     Transport has done all cleanup. Go ahead
 *                          and release it.
 *
 *      MAPI_E_CALL_FAILED  Transport hasn't been inited or has
 *                          already been deinited.
 *
 *  Operation:
 *      This call normally will happen if the Spooler doesn't like the
 *      Transport version passed in from XPProviderInit; if the Spooler
 *      attempts to log into the Transport and fails; and after a
 *      TransportLogoff.
 *
 *      If there are any sessions active in this process, the Transport
 *      should clean them up (propagating the DEINIT_HURRY down to
 *      LOGOFF_HURRY in the TransportLogoff flags) before returning.
 *
 *      The Spooler guarantees non-reentrancy on this call.
 */

STDMETHODIMP
XPP_Shutdown(LPXPP lpxpp, ULONG FAR * lpulFlags)
{
    LPXPL lpxpl = NULL;
    ULONG ulFlags;
    HRESULT hResult = hrSuccess;

    /*  Walk down the session list. Log off any active sessions.

        NOTE: IT IS *REALLY* IMPORTANT THAT ANY WORK DONE TO CHANGE
        THE WAY THIS LIST IS WALKED OR RELINKED BY LOGOFF BE DONE IN
        SUCH A WAY THAT BOTH THIS CODE AND THE TRANSPORTLOGOFF CODE
        CONTINUE TO WORK CORRECTLY!!
    */

    /* Get the Critical Section */

    EnterCriticalSection(&lpxpp->csTransport);

    lpxpl = lpxpp->XPSessionList;
    ulFlags = (*lpulFlags & DEINIT_HURRY) ? LOGOFF_HURRY : LOGOFF_NORMAL;

    while (lpxpl)
    {
        LPXPL lpNext = lpxpl->lpNextSession;

        hResult = XPL_TransportLogoff(lpxpl, ulFlags);

        if (hResult)
        {
            DebugTrace("Logging off session failed.\n");

            /*
             * A logoff failed. If we are in HURRY mode, we'll let it
             * go. Otherwise, we pass the error along and get out!
             */

            if (!(*lpulFlags & DEINIT_HURRY))
            {
                DebugTrace("Returning Logoff result to caller.\n");

                /*
                 * Relink the listhead so that everything has some
                 * degree of internal consistency.
                 */

                lpxpp->XPSessionList = lpxpl;
                goto ret;
            }

            DebugTrace("DEINIT_HURRY set, deallocating session and proceeding.\n");
            CleanupSession(lpxpl);
        }
        lpxpl->lpVtbl->Release(lpxpl);
        lpxpl = lpNext;
    }

    /* Release the Critical Section */

    LeaveCriticalSection(&lpxpp->csTransport);

    /* Delete critical section which XPProviderInit() initialized. */

    DeleteCriticalSection(&lpxpp->csTransport);

    lpxpp->XPSessionList = NULL;
    lpxpp->fInited = FALSE;

    *lpulFlags = 0;

ret:
    /* Turn off logging if active */

    DeInitTransportLog((*lpulFlags & DEINIT_HURRY) ? LOG_DEINIT_HURRY : 0L);

    DebugTraceResult(XPP_Shutdown, hResult);
    return hResult;
}

/*
 -  lpxpp->lpVtbl->TransportLogon
 -
 *  Purpose:
 *      Called by the Spooler to establish a session with a Transport.
 *      This call might be made many times, once for each session the Spooler
 *      wants to establish with an external messaging system through this
 *      Transport. These multiple sessions will usually be established on a
 *      one-for-one basis with the number of identities which the current
 *      profile has established for the given messaging system.
 *
 *  Parameters:
 *      lpxpp               The parent XPProvider object who will
 *                          own this new session.
 *
 *      lpMAPISup           A pointer to a MAPI Support Object which
 *                          is uniquely associated with this session
 *                          by the Spooler. Any MAPI Support calls
 *                          associated with this session should be
 *                          done using this Support Object.
 *
 *      ulUIParam           The hWnd who will become the parent of any
 *                          UI we decide to put up (like a logon dialog).
 *
 *      lpszProfileName     Contains a display name associated with the
 *                          current user's profile.  Expected to be
 *                          used by the Transport primarily to make
 *                          any UI easier for the user to decipher.
 *
 *      lpulFlags           (In)
 *
 *                          Flags passed from Spooler to Transport:
 *
 *                          LOGON_NO_DIALOG instructs the Transport
 *                          not to put any UI up during this operation.
 *                          If the operation cannot be completed
 *                          without UI, an error should be returned.
 *
 *                          LOGON_NO_CONNECT is set by the Spooler
 *                          when the transport connection is being
 *                          made in order to do things like get
 *                          per-message/per-recipient options; using
 *                          preprocessor stuff, etc -- but to do so
 *                          "offline". The Transport is not to connect
 *                          to the outside world, and if there are
 *                          insufficient credentials to allow the
 *                          Transport to operate as if the user should
 *                          be permitted access, the Transport may
 *                          wish to fail without presenting UI.
 *
 *                          LOGON_NO_INBOUND is set by the Spooler
 *                          to signal the Transport not to accept
 *                          incoming mail on this session. The Spooler
 *                          will ignore the NOTIFY_NEWMAIL flag on
 *                          SpoolerNotify() and it will not Poll()
 *                          the Transport on this session. It's OK
 *                          to make external connections for reasons
 *                          other than mail reception. The Spooler
 *                          may TransportNotify() to enable inbound.
 *
 *                          LOGON_NO_OUTBOUND is set by the Spooler
 *                          to signal the Transport that outgoing
 *                          mail won't be sent right now on this
 *                          session. The Spooler will ignore the
 *                          NOTIFY_READYTOSEND flag on SpoolerNotify()
 *                          and will not be calling SubmitMessage().
 *                          It's OK to make external connections for
 *                          reasons other than mail transmission. The
 *                          Spooler may TransportNotify() to signal
 *                          the start of outbound operations.
 *  Returns:
 *      lpulFlags           (Out)
 *
 *                          Flags passed from Transport to Spooler:
 *
 *                          LOGON_SP_IDLE tells the Spooler to call
 *                          the Idle() entry point with this logon
 *                          handle as part of its idle processing.
 *                          The transport may use this to check its
 *                          underlying messaging system for incoming
 *                          mail, to do special processing outside
 *                          of the SPI, or maybe some kind of network
 *                          link maintenance, for example.
 *
 *                          LOGON_SP_POLL instructs the Spooler to
 *                          call the Transport at its Poll() entry
 *                          point on a regular basis to see if any
 *                          new mail is ready for import into the
 *                          MAPI subsystem.
 *
 *                          LOGON_SP_RESOLVE instructs the Spooler
 *                          and MAPI to ensure that all addresses in
 *                          an outgoing message's recipient table are
 *                          fully resolved before this transport is
 *                          given the message. This could be used by
 *                          a transport to make a best-guess routing
 *                          from a recipient's system to allow a given
 *                          recipient to include all other recipients
 *                          of a message in a reply ("Reply-All").
 *
 *      lppXPLogon          Contains a pointer to the Transport
 *                          Logon object (an instance of IXPLogon)
 *                          that is used to access the session we
 *                          just created.
 *
 *      MAPI_E_UNCONFIGURED Something's wrong with credentials or
 *                          other things needed for logon. We're
 *                          not really saying what.
 *
 *  Operation:
 *      The Spooler makes this call when it wants to establish a session with
 *      the Transport provider. When called here, the Transport will need to
 *      perform several actions:
 *
 *  1) Get credentials out of the profile.
 *
 *  2)  Determine if credentials are sufficient. Some providers may choose
 *      to just send along what they have to the host and let validation
 *      happen there; others will want to determine that credentials have
 *      some validity before setting off breakin alarms on a mail host system.
 *
 *  3) If credentials are insufficient, return MAPI_E_UNCONFIGURED.
 *
 *  4) Validate credentials. This may involve establishing a connection to the
 *     underlying messaging system and allowing it to determine that the
 *     credentials are OK.
 *
 *  5) If credentials not OK, the Transport provider should return
 *     MAPI_E_UNCONFIGURED.  This will reuqest that MAPI calls the ServiceEntry
 *     point to obtain the missing configuration information.
 *
 *  6) Save credentials. The profile section made available to the Transport
 *     Provider can be used to store this information if so indicated.
 *
 *      Transports that are less concerned about security may leave out any or
 *      all of the above.
 *
 *  7) Build the logon object.
 *
 *  8) Construct a Status Table row and call ModifyStatusRow().
 *
 *  9) Link the logon object into the list for the transport.
 *
 * 10) Start up logger if need be
 *
 * 11) Log completion of logon operation.
 *
 *
 *  Note:
 *      There are some parameters stored at the session level (in the
 *      profile) that are more global in nature, such as the logging stuff
 *      and peer-to-peer. The way we will resolve this is to interpret new
 *      information in the light of old stuff, favoring the "on" state. So
 *      a session that says "no logging" will in fact not be logged only if
 *      not preceded by a session with logging on, and the session will be
 *      logged as soon as a session that specifies logging comes online. The
 *      inbound and outbound paths will be respected; the first logfile name
 *      will rule.  A session that didn't want to be peer-to-peer could have
 *      its way among lots of peer-to-peer transports by specifying a
 *      different outbound path.
 *
 */

STDMETHODIMP
XPP_TransportLogon(LPXPP lpxpp,
    LPMAPISUP lpMAPISup,
    ULONG ulUIParam,
    LPTSTR lpszProfileName,
    ULONG * lpulFlags,
    LPMAPIERROR * lppMapiError,
    LPXPLOGON FAR * lppXPLogon)
{
    LPSPropValue lpPropArray = NULL;
    LPSPropValue lpMyIDArray = NULL;
    LPVOID lpvT, lpvT2;
    ULONG ulCount = 0;
    ULONG ulT;
    SCODE sc = 0;
    HRESULT hResult = 0;
    LPXPL lpxpl = NULL;
    LPPROFSECT lpProfileObj = NULL;
    LPOPTIONDATA lpOptData = NULL;

    LPALLOCATEBUFFER lpAllocBuffer;
    LPALLOCATEMORE lpAllocMore;
    LPFREEBUFFER lpFreeBuffer;

    BOOL fNeedUI = FALSE;
    BOOL fInitialParamsOk = TRUE;

    *lppMapiError = NULL;

    if ( *lpulFlags & MAPI_UNICODE )
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH ) ;

    /*  Get the memory allocation routines we'll be needing. */

    hResult = lpMAPISup->lpVtbl->GetMemAllocRoutines(lpMAPISup,
        &lpAllocBuffer, &lpAllocMore, &lpFreeBuffer);

    if (hResult)
    {
        DebugTrace("GetMemAllocRoutines failed in XP Logon.\n");
        goto ret;
    }

    /* Try to open our profile. */

    hResult = lpMAPISup->lpVtbl->OpenProfileSection(lpMAPISup,
        (LPMAPIUID) NULL, MAPI_MODIFY, &lpProfileObj);

    if (hResult)
    {
        DebugTrace("OpenProfileSection failed in XP Logon.\n");
        goto ret;
    }

    /* Profile is open, get the properties out of it. */

    hResult = lpProfileObj->lpVtbl->GetProps(lpProfileObj,
        (LPSPropTagArray) &sptLogonArray, 0, /* ansi */
        &ulCount, &lpPropArray);

    if (hResult)
    {
#ifdef DEBUG
        LPMAPIERROR lpMapiError = NULL;

        DebugTrace("GetProps failed in XP Logon.\n");

        lpProfileObj->lpVtbl->GetLastError(lpProfileObj, hResult,
            0L, &lpMapiError);

        if ( lpMapiError && lpMapiError->lpszError )
        {
            DebugTrace(lpMapiError->lpszError);
            DebugTrace("\n");

            lpFreeBuffer((LPVOID) lpMapiError);
        }
#endif
        /* Couldn't read all of the properties. We'll recover
           from the error by just bringing up UI if allowed. */

        hResult = hrSuccess;
    }

    /* Profile is open. Get all of our expected properties. */

    Assert(ulCount == MAX_LOGON_PROPERTIES);
    ulCount -= TEMP_LOGON_PROPERTIES;

    Assert(ulCount < MAX_LOGON_PROPERTIES);

    /* Make sure that we have the properties we want. If not, signal the
       need for UI.

       We could also check that network paths etc. exist at this point. */

    if (lpPropArray)
    {
        XPDLG       XPDialog;

        fNeedUI = FALSE;        /* Default to saying we have what we need */

        for (ulT = 0; ulT < ulCount; ulT++)
        {
            /* If any of the properties is PT_ERROR, we need to
               bring up UI. */

            if (PROP_TYPE(lpPropArray[ulT].ulPropTag) == PT_ERROR)
            {
                fNeedUI = TRUE;
                break;
            }
        }

        /* The configuration checking assumes that the information came from
           a dialog box, so we have to look like a dialog box to use it. */
        XPDialog.hInst = NULL;
        XPDialog.hwnd = (HWND) ulUIParam;
        XPDialog.lppPropArray = &lpPropArray;
        XPDialog.lpPTArray = (LPSPropTagArray) &sptLogonArray;
        XPDialog.AllocateBuffer = lpAllocBuffer;
        XPDialog.AllocateMore = lpAllocMore;
        XPDialog.FreeBuffer = lpFreeBuffer;
        XPDialog.lpMalloc = NULL;
        XPDialog.lpMAPISup = lpMAPISup;
        XPDialog.fLogon = TRUE;
        XPDialog.ulFlags = 0;

        /* Do some simple validation of the Logon Props */
        sc = ScCheckLogonProps(&XPDialog, FALSE);
        if ((sc == MAPI_E_USER_CANCEL) || (sc == MAPI_E_UNCONFIGURED))
            fNeedUI = TRUE;
    }
    else
        fNeedUI = TRUE;         /* No prop array means we need UI */

    /* Now we have a local flag that will tell us whether UI must be
       presented. We should consider it a "I require data to complete
       logon" state as well. At this point, act on the LOGON_NO_DIALOG
       flag and error out if we are not allowed to present UI but we
       have some reason for wanting it. */

    if (fNeedUI)
    {
        /* Fill in the logon UI structure */
        hResult = ResultFromScode(MAPI_E_UNCONFIGURED);
        DebugTrace("Need UI, returning MAPI_E_UNCONFIGURED to get some.\n");
        goto ret;
    }

    /* Create the session structure and copy important data into it */
    sc = lpAllocBuffer(sizeof(XPL), (LPVOID *) &lpxpl);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocating XPLogon object failed in XP Logon.\n");
        goto ret;
    }

    memset(lpxpl, 0, sizeof(XPL));

    lpxpl->lpVtbl = &vtblXPL;
    lpxpl->lcInit = 1;

    lpxpl->lpMySession = lpxpl; /* Used to validate structure */
    lpxpl->lpxppParent = lpxpp;
    lpxpl->lpMAPISup = lpMAPISup;   /* Copy of our support object */
    lpxpl->AllocateBuffer = lpAllocBuffer;
    lpxpl->AllocateMore = lpAllocMore;
    lpxpl->FreeBuffer = lpFreeBuffer;
    lpxpl->ulSessionFlags = *lpulFlags; /* Important session flags*/

    lpxpl->cLogonPropValues = ulCount;  /* Count of logon properties */
    lpxpl->lpPropArray = lpPropArray;   /* Array of properties */

    lpxpl->lpMyIDArray = NULL;  /* Transport ID */
    lpxpl->fRefSupport = FALSE;

    /* Figure out what the initial status of the transport will be */

    if ((*lpulFlags & LOGON_NO_CONNECT))
    {
        lpxpl->ulTransportStatus = STATUS_OFFLINE;
    }
    else
    {
        lpxpl->ulTransportStatus = STATUS_AVAILABLE;

        if (!(*lpulFlags & LOGON_NO_INBOUND))
            lpxpl->ulTransportStatus |= STATUS_INBOUND_ENABLED;

        if (!(*lpulFlags & LOGON_NO_OUTBOUND))
            lpxpl->ulTransportStatus |= STATUS_OUTBOUND_ENABLED;
    }

    /* Initialize Status object ResourceMethods.  FlushQueues
       and SettingsDialog are currently implemented */

    lpxpl->ulResourceMethods = STATUS_SETTINGS_DIALOG | STATUS_FLUSH_QUEUES;

    /* Initialize message send/receive state */

    lpxpl->fFoundInMessage = FALSE;
    lpxpl->hInFindHandle = INVALID_HANDLE_VALUE;
    lpxpl->hOutFindHandle = INVALID_HANDLE_VALUE;

    /* Allocate and initialize the Message & Recipient OptionData struct */

    sc = lpAllocBuffer(2 * sizeof(OPTIONDATA), (LPVOID *) &lpOptData);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocating OPTIONDATA failed in XP Logon.\n");
        goto ret;
    }

    /* Option Data is registered by Address Type.  In the SettingsDialog()
       call on the Status object we force the email address type field to
       be read-only.  This ensures that this OptionData remains valid for
       the duration of this session.

       NOTE:    The ulOrdinal must match the value assigned in the .DEF file.
                If this value changes, it must be changed here and in the
                .DEF file to the same value! */

    lpOptData[0].ulFlags = OPTION_TYPE_MESSAGE;
    lpOptData[0].lpRecipGUID = NULL;
    lpOptData[0].lpszAdrType = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ;
    lpOptData[0].lpszDLLName = szDLLName;
    lpOptData[0].ulOrdinal = 3;
    lpOptData[0].cbOptionsData = 0;
    lpOptData[0].lpbOptionsData = NULL;
    lpOptData[0].cOptionsProps = 1;
    lpOptData[0].lpOptionsProps = &spvMsgOpt;

    lpOptData[1].ulFlags = OPTION_TYPE_RECIPIENT;
    lpOptData[1].lpRecipGUID = NULL;
    lpOptData[1].lpszAdrType = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ;
    lpOptData[1].lpszDLLName = szDLLName;
    lpOptData[1].ulOrdinal = 3;
    lpOptData[1].cbOptionsData = 0;
    lpOptData[1].lpbOptionsData = NULL;
    lpOptData[1].cOptionsProps = 1;
    lpOptData[1].lpOptionsProps = &spvRecipOpt;

    lpxpl->cOptData = 2;
    lpxpl->lpOptData = lpOptData;
    lpOptData = NULL;

    /* Allocate initial property array for transport ID. */

    sc = lpAllocBuffer(sizeof(SPropValue) * NUM_SENDER_PROPS,
            (LPVOID *) &lpMyIDArray);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocating Sender ID array failed in XP Logon.\n");
        goto ret;
    }

    /* Zero the memory and hook it into the logon object now.  That
       way, if we get a failure between here and ret: then this
       memory will be freed in CleanupSession(). */

    memset(lpMyIDArray, 0, sizeof(SPropValue) * NUM_SENDER_PROPS);
    lpxpl->lpMyIDArray = lpMyIDArray;

    /* Create the One-Off directly into the property value structure. */

    lpMyIDArray[0].ulPropTag = PR_SENDER_ENTRYID;

    hResult = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ,
        ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ,
        ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ,
        fMapiUnicode,
        &lpMyIDArray[0].Value.bin.cb,
        (LPENTRYID *) &lpMyIDArray[0].Value.bin.lpb);

    if (hResult)
    {
        DebugTrace("CreateOneOff failed in XP Logon.\n");
        lpMyIDArray[0].Value.bin.lpb = NULL;
        goto ret;
    }

    /* Create the PR_SENDER_NAME property value. */

    lpMyIDArray[1].ulPropTag = PR_SENDER_NAME;
    lpvT2 = ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ;
    ulT = (lstrlen((LPCTSTR) lpvT2) + 1) * sizeof(TCHAR);

    sc = lpAllocMore(ulT, (LPVOID) lpMyIDArray, &lpvT);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocating user DisplayName failed in XP Logon.\n");
        goto ret;
    }

    lstrcpy((LPTSTR) lpvT, (LPCTSTR) lpvT2);
    lpMyIDArray[1].Value.LPSZ = (LPTSTR) lpvT;

    /* Create the PR_SENDER_SEARCH_KEY value. */

    lpMyIDArray[2].ulPropTag = PR_SENDER_SEARCH_KEY;

    /* Size of property = type plus colon plus address plus null. */

    ulT = 2 + lstrlen(ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ) +
          lstrlen(ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ);
    ulT *= sizeof(TCHAR);

    sc = lpAllocMore(ulT, (LPVOID) lpMyIDArray, &lpvT);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocating SearchKey failed in XP Logon.\n");
        goto ret;
    }

    /* PR_SENDER_SEARCH_KEY is "TYPE:ADDRESS" folded to uppercase. */

    wsprintf((LPTSTR) lpvT, TEXT("%s:%s"),
            ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ,
            ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ);

    CharUpperBuff((LPTSTR) lpvT, (UINT) ulT-sizeof(TCHAR));
    lpMyIDArray[2].Value.bin.cb = ulT;
    lpMyIDArray[2].Value.bin.lpb = lpvT;

    /* Register the preprocessor */

    hResult = lpMAPISup->lpVtbl->RegisterPreprocessor (lpMAPISup, NULL,
        ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ,
#ifdef  WIN16
        "SMPXP.DLL",
#else
        "SMPXP32.DLL",
#endif
        "PreprocessMessage", "RemovePreprocessInfo", 0L);

    if (hResult)
    {
        DebugTrace("RegisterPreprocessor failed in XP Logon.\n");
        goto ret;
    }

    /* Get the Critical Section */

    EnterCriticalSection(&lpxpp->csTransport);

    /* Build the status row and register it */

    hResult = HrBuildTransportStatus(lpxpl, 0L);

    if (hResult)
    {
        DebugTrace("HrBuildTransportStatus failed in XP Logon.\n");
        LeaveCriticalSection(&lpxpp->csTransport);
        goto ret;
    }

    /* Link the session structure into the list. */

    lpxpl->lpNextSession = lpxpp->XPSessionList;
    lpxpp->XPSessionList = lpxpl;

    /* AddRef() the support object. */

    lpMAPISup->lpVtbl->AddRef(lpMAPISup);
    lpxpl->fRefSupport = TRUE;

    /* Release the Critical Section. */

    LeaveCriticalSection(&lpxpp->csTransport);

    /* Start up logfile stuff if this session indicates it. */

    InitTransportLog(lpxpl, 0L);

    /* OK, we were successful. Set the return values. */

    *lppXPLogon = (LPXPLOGON) lpxpl;

    /* No flags necessary if no connect ... */

    if (lpxpl->ulSessionFlags & LOGON_NO_CONNECT)
    {
        *lpulFlags = 0;
    }
    else
    {
        /* Idle and resolve for transmit; Poll for receive */

        *lpulFlags = LOGON_SP_IDLE | LOGON_SP_RESOLVE | LOGON_SP_POLL;
    }

ret:
    UlRelease(lpProfileObj);

    /*  Make sure we have an hResult.

        Assumptions:
            if hResult is set, it reflects the last error.
            if sc is set and hResult is not, we want hResult to be set.
    */

    if (hResult)
    {
        /* Error. Get rid of anything already on the session object. */
        /* If no session object, free data that could have been      */
        /* allocated before session object creation.                 */

        if (lpxpl)
        {
            CleanupSession(lpxpl);
            lpxpl->lpVtbl->Release(lpxpl);
        }
        else lpFreeBuffer (lpPropArray);
    }

    DebugTraceResult(XPP_TransportLogon, hResult);
    return hResult;
}

/*
 -  lpxpl->lpVtbl->AddressTypes
 -
 *  Purpose:
 *      Called by the Spooler to find out what recipients it should expect
 *      this transport to handle.
 *
 *  Parameters:
 *      ulFlags             Ignored for now.
 *      lpcAdrType          Pointer: where to store number of address types
 *      lpppAdrTypeArray    Pointer: where to store list of address types
 *      lpcMAPIUID          Pointer: where to store number of UID's
 *      lpppMAPIUIDArray    Pointer: where to store list of MAPI UID's
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *
 *  Operation:
 *      Returns the Address Type entered at Logon time in the address type
 *      array. Makes no use of the UID array.
 */

STDMETHODIMP
XPL_AddressTypes(LPXPL lpxpl,
    ULONG * lpulFlags,
    ULONG * lpcAdrType, LPTSTR * *lpppAdrTypeArray,
    ULONG * lpcMAPIUID, LPMAPIUID * *lpppMAPIUIDArray)
{
    ULONG cb;
    SCODE sc;
    LPTSTR lpsz;

    /* We are returning an array with exactly one value.  We first need
       to AllocateMore a buffer (that is linked to the lpxpl object) and
       copy our Email Address Type into there.  We then return the address
       of this copy of our AddressType.  */

    if ( *lpulFlags & ~(MAPI_UNICODE) )
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );

    if ( *lpulFlags & MAPI_UNICODE )
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH ) ;

    *lpcAdrType = 1;

    lpsz = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpxpl->lpPropArray).Value.LPSZ;

    cb = (lstrlen(lpsz) + 1) * sizeof(TCHAR);

    sc = lpxpl->AllocateMore(cb, (LPVOID) lpxpl, (LPVOID *) &lpxpl->lpszAdrType);

    if (FAILED(sc))
    {
        DebugTrace("AllocateMore in XPL_AddressType failed!\n");
        return ResultFromScode(sc);
    }

    lstrcpy(lpxpl->lpszAdrType, lpsz);

    *lpppAdrTypeArray = &(lpxpl->lpszAdrType);

    /* Routing by UID has no particular meaning for this transport. */

    *lpcMAPIUID = 0;
    *lpppMAPIUIDArray = NULL;

    return hrSuccess;
}

/*
 -  lpxpl->lpVtbl->RegisterOptions
 -
 *  Purpose:
 *      Called by the Spooler to find out what per-message or per-recipient
 *      options this transport might wish supported.
 *
 *  Parameters:
 *      lpulFlags           Indicates whether strings are UNICODE or not.
 *      lpcOptions          Pointer: where to store number of address
 *                          types for which options are being requested
 *      lppOptions          Pointer: where to store a list of display
 *                          names associated with each address type
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 */

STDMETHODIMP
XPL_RegisterOptions(LPXPL lpxpl,
    ULONG * lpulFlags,
    ULONG * lpcOptions,
    LPOPTIONDATA * lppOptions)
{
    /* Just give them what we have in our XPLogon object.       */
    /* These options were initialized at TransportLogon() time. */

    if ( *lpulFlags & ~(MAPI_UNICODE) )
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );

    if ( *lpulFlags & MAPI_UNICODE )
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH ) ;

    *lpcOptions = lpxpl->cOptData;
    *lppOptions = lpxpl->lpOptData;

    return 0;
}

/*
 -  lpxpl->lpVtbl->TransportLogoff
 -
 *  Purpose:
 *      Called by the Spooler to log off a particular Transport session.
 *
 *  Parameters:
 *      ulFlags             May contain LOGOFF_HURRY, which tells
 *                          the transport to close this session
 *                          as quickly as possible.
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *
 *  Operation:
 *      Find the session. Tell MAPI the session's going away, unlink it from
 *      the list, close open handles and deallocate session memory.
 */

STDMETHODIMP
XPL_TransportLogoff(LPXPL lpxpl, ULONG ulFlags)
{
    SCODE sc = S_OK;
    LPXPL lpxplCurr;
    LPXPL lpxplPrev = NULL;
    LPXPP lpxpp = lpxpl->lpxppParent;

    /* Get the critical section */

    EnterCriticalSection(&lpxpp->csTransport);

    /* Find the session in the list. */

    lpxplCurr = lpxpp->XPSessionList;

    while (lpxplCurr)
    {
        if (lpxpl == lpxplCurr)
        {
            /* Unlink the session from the list. */

            if (lpxplPrev == NULL)
                lpxpp->XPSessionList = lpxpl->lpNextSession;
            else
                lpxplPrev->lpNextSession = lpxpl->lpNextSession;

            break;
        }

        lpxplPrev = lpxplCurr;
        lpxplCurr = lpxplCurr->lpNextSession;
    }

    /* Release the critical section */

    LeaveCriticalSection(&lpxpp->csTransport);

    /* lpxplCurr must be non-NULL if we found the session. */

    if (!lpxplCurr)
    {
        sc = MAPI_E_INVALID_PARAMETER;
        goto ret;
    }

    /* Clean up the stuff inside the session structure. This may
       cause the XPP object to be released. */

    CleanupSession(lpxpl);

ret:
    DebugTraceSc(TransportLogoff, sc);
    return ResultFromScode(sc);
}

/*
 -  lpxpl->lpVtbl->TransportNotify
 -
 *  Purpose:
 *      Called by the Spooler to call some event to the Transport's attention.
 *
 *  Parameters:
 *      lpulFlags           Flags passed bidirectionally.
 *                          This transport pays attention to the
 *                          (BEGIN/END/FLUSH)(INBOUND/OUTBOUND)
 *                          flags and ignores the others.
 *      lppvData            Data passed bidirectionally. Not
 *                          used by this transport.
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *
 *  Operation:
 *      If the flag has any bearing on inbound/outbound startup/shutdown,
 *      then the appropriate change is made to the transport status in the
 *      XPLogon object and the HrUpdateTransportStatus routine is called to
 *      ModifyStatusRow() the change.
 */

STDMETHODIMP
XPL_TransportNotify(LPXPL lpxpl,
    ULONG * lpulFlags,
    LPVOID * lppvData)
{
    HRESULT hResult = hrSuccess;
    LPXPP lpxpp = lpxpl->lpxppParent;

#define TRANSPORT_NOTIFY_FLAGS  (NOTIFY_BEGIN_INBOUND       | \
                                NOTIFY_END_INBOUND          | \
                                NOTIFY_BEGIN_INBOUND_FLUSH  | \
                                NOTIFY_END_INBOUND_FLUSH    | \
                                NOTIFY_BEGIN_OUTBOUND       | \
                                NOTIFY_END_OUTBOUND         | \
                                NOTIFY_BEGIN_OUTBOUND_FLUSH | \
                                NOTIFY_END_OUTBOUND_FLUSH   | \
                                NOTIFY_CANCEL_MESSAGE       | \
                                NOTIFY_ABORT_DEFERRED)

    /* Validate we were passed a legal flag. */

    Assert(*lpulFlags & TRANSPORT_NOTIFY_FLAGS);

    /* Make sure we have a good session. */

    if (!FIsValidSession(lpxpl))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTrace("FIsValidSession  failed in TransportNotify.\n");
        goto ret;
    }

    /* Get the Critical Section */

    EnterCriticalSection(&lpxpp->csTransport);

    /* Set appropriate status flags and re-register status row */

    if (*lpulFlags & NOTIFY_BEGIN_INBOUND)
    {
        DebugTrace("NOTIFY_BEGIN_INBOUND received and handled\n");
        lpxpl->ulTransportStatus |= STATUS_INBOUND_ENABLED;
    }

    if (*lpulFlags & NOTIFY_END_INBOUND)
    {
        DebugTrace("NOTIFY_END_INBOUND received and handled\n");
        lpxpl->ulTransportStatus &= ~STATUS_INBOUND_ENABLED;
    }

    if (*lpulFlags & NOTIFY_END_INBOUND_FLUSH)
    {
        DebugTrace("NOTIFY_END_INBOUND_FLUSH received and handled\n");
        lpxpl->ulTransportStatus &= ~STATUS_INBOUND_FLUSH;
    }

    if (*lpulFlags & NOTIFY_BEGIN_OUTBOUND)
    {
        DebugTrace("NOTIFY_BEGIN_OUTBOUND received and handled\n");
        lpxpl->ulTransportStatus |= STATUS_OUTBOUND_ENABLED;
    }

    if (*lpulFlags & NOTIFY_END_OUTBOUND)
    {
        DebugTrace("NOTIFY_END_OUTBOUND received and handled\n");
        lpxpl->ulTransportStatus &= ~STATUS_OUTBOUND_ENABLED;
    }

    if (*lpulFlags & NOTIFY_END_OUTBOUND_FLUSH)
    {
        DebugTrace("NOTIFY_END_OUTBOUND_FLUSH received and handled\n");
        lpxpl->ulTransportStatus &= ~STATUS_OUTBOUND_FLUSH;
    }

    if (*lpulFlags & NOTIFY_ABORT_DEFERRED)
    {
        DebugTrace("NOTIFY_ABORT_DEFERRED received and handled\n");

        hResult = HrDeleteDeferred(lpxpl, (LPSBinary)*lppvData);
    }

    /* We're just going to ignore NOTIFY_CANCEL_MESSAGE for now. */

    if (*lpulFlags & NOTIFY_CANCEL_MESSAGE)
    {
        DebugTrace("NOTIFY_CANCEL_MESSAGE received and ignored\n");
    }

    HrUpdateTransportStatus(lpxpl, 0L);

    /* Release the critical section. */

    LeaveCriticalSection(&lpxpp->csTransport);

ret:

    DebugTraceResult(XPL_TransportNotify, hResult);
    return hResult;
}

/*
 -  XPL_ValidateState
 -
 *  Purpose:
 *      Logon object method used by Spooler if ValidateState is called on
 *      the spooler status object.
 *
 *  Parameters:
 *      lpxpl               This pointer for logon object
 *      ulUIParam           Window handle
 *      ulFlags
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't
 *                          look like a XPL; MAPI_E_NO_SUPPORT
 *                          otherwise.
 *
 *  Operation:
 *      Compare the PR_ADDRTYPE found in the profile to the one I am
 *      currently using.  If it has changed, then SpoolerNotify() to
 *      ask to be reloaded so MAPI will call AddressTypes() and
 *      RegisterOptions() on us again!
 *
 *  Note: We could check all the properties in our profile and be a
 *        better implementation of this call.  But for now, AddrType
 *        is the most important property to check.
 */

STDMETHODIMP
XPL_ValidateState(LPXPL lpxpl,
    ULONG ulUIParam,
    ULONG ulFlags)
{
    SCODE sc = S_OK;
    HRESULT hResult;
    LPPROFSECT lpProf = NULL;
    LPSPropValue lpspvAddrType = NULL;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPSPropValue lpPropArray = NULL;
    LPTSTR lpszAddrType = NULL;

    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadReadPtr(lpxpl, sizeof(XPL)) ||
        lpxpl->lcInit == 0 ||
        lpxpl->lpMySession != lpxpl)
    {
        DebugTraceSc(XPL_ValidateState, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if (ulFlags & ~SUPPRESS_UI)
    {
        DebugTraceSc(XPL_ValidateState, MAPI_E_UNKNOWN_FLAGS);
        return ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
    }

    sc = ScCopySessionProps(lpxpl, &lpPropArray, NULL);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        DebugTrace("ScCopySessionProps failed in ValidateState.\n");
        goto ret;
    }

    lpszAddrType = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ;

    /* Try to open our profile. */

    hResult = lpMAPISup->lpVtbl->OpenProfileSection(lpMAPISup,
        (LPMAPIUID) NULL, MAPI_MODIFY, &lpProf);

    if (hResult)
    {
        DebugTrace("OpenProfileSection  failed in ValidateState.\n");
        goto ret;
    }

    hResult = HrGetOneProp((LPMAPIPROP)lpProf,
            PR_SAMPLE_EMAIL_ADDR_TYPE, &lpspvAddrType);

    if (HR_FAILED(hResult))
    {
        DebugTrace("HrGetOneProp failed in ValidateState.\n");
        goto ret;
    }

    /* Now, compare what I think my AddrType is to that in the profile.
       If they are different, tell the spooler I'd like to be reloaded. */

    if (lpspvAddrType->ulPropTag == PR_SAMPLE_EMAIL_ADDR_TYPE)
    {
        if (lstrcmp(lpszAddrType, lpspvAddrType->Value.LPSZ))
        {
            hResult = lpMAPISup->lpVtbl->SpoolerNotify(lpMAPISup,
                NOTIFY_CONFIG_CHANGE, NULL);

            if (HR_FAILED(hResult))
            {
                DebugTrace("SpoolerNotify failed  in ValidateState.\n");
                goto ret;
            }
        }
    }

    hResult = hrSuccess;

ret:
    UlRelease(lpProf);

    lpxpl->FreeBuffer(lpPropArray);
    lpxpl->FreeBuffer(lpspvAddrType);

    DebugTraceResult(XPL_ValidateState, hResult);
    return hResult;
}

/*
 -  XPL_FlushQueues
 -
 *  Purpose:
 *      Logon object method used by Spooler if FlushQueues is called on
 *      the spooler status object.
 *
 *  Parameters:
 *      lpxpl               This pointer for logon object
 *      ulUIParam           Window handle
 *      cbTargetTransport   Count of bytes in Entryid. Zero.
 *      lpTargetTransport   Entryid of transport. NULL.
 *      ulFlags
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't
 *                          look like a XPL; MAPI_E_NO_SUPPORT
 *                          otherwise.
 *
 *  Operation:
 *      Validate the object pointer. Return MAPI_E_NO_SUPPORT.
 */

STDMETHODIMP
XPL_FlushQueues(LPXPL lpxpl,
    ULONG ulUIParam,
    ULONG cbTargetTransport,
    LPENTRYID lpTargetTransport,
    ULONG ulFlags)
{
    HRESULT hResult;

    /*  Validate the object */

    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadReadPtr(lpxpl, sizeof(XPL)) ||
        lpxpl->lcInit == 0 ||
        lpxpl->lpMySession != lpxpl)
    {
        DebugTraceSc(XPL_FlushQueues, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  There is nothing special the sample transport
        needs to do before signaling its readiness for
        flushing */

    /*  Update our status row to inform the spooler
        that we are ready for flushing */

    if (ulFlags & FLUSH_UPLOAD)
        lpxpl->ulTransportStatus |= STATUS_OUTBOUND_FLUSH;
    if (ulFlags & FLUSH_DOWNLOAD)
        lpxpl->ulTransportStatus |= STATUS_INBOUND_FLUSH;

    hResult = HrUpdateTransportStatus(lpxpl, 0L);

    DebugTraceResult(XPL_FlushQueues, hResult);
    return hResult;
}

/*
 -  FIsValidSession
 -
 *  Purpose:
 *      Called from several places in the Transport to verify a session.
 *
 *  Parameters:
 *      lpxpl               handle which is to be confirmed
 *
 *  Returns:
 *      (BOOL)              TRUE if a valid session, FALSE if not.
 *
 *  Operation:
 *      Get the critical section, look for lpxpl in the session list,
 *      release the critical section, return boolean result.
 */

BOOL
FIsValidSession(LPXPL lpxpl)
{
    LPXPL lpxplT;
    LPXPP lpxpp;
    BOOL fIsValid = FALSE;

    /*  First make sure that it can possibly be a session; then
        Take advantage of the internal consistency check ... does
        it point to itself? then validate the parent XPP. */

    if ((IsBadWritePtr(lpxpl, sizeof(XPL))) ||
        (lpxpl->lpMySession != lpxpl) ||
        (IsBadWritePtr(lpxpl->lpxppParent, sizeof(XPP))))
        return FALSE;

    /*  Maybe it's a session ... see if it's in the list.
        Lock out session data structure during the check. */

    lpxpp = lpxpl->lpxppParent;
    EnterCriticalSection(&lpxpp->csTransport);

    /*  Look in the session list for the session */

    lpxplT = lpxpp->XPSessionList;

    while (lpxplT)
    {
        if (lpxplT == lpxpl)
        {
            fIsValid = TRUE;
            break;
        }
        lpxplT = lpxplT->lpNextSession;
    }

    /*  Release the critical section and return the result. */

    LeaveCriticalSection(&lpxpp->csTransport);

    return fIsValid;
}

/*
 -  CleanupSession
 -
 *  Purpose:
 *      Called from TransportLogoff() and error cases of TransportLogon()
 *      to cleanup session data.
 *
 *  Parameters:
 *      lpxpl           handle which is to be confirmed
 *
 *  Returns:
 *      none.
 *
 *  Operation:
 *      Close any open search handles, deallocate associated structures.
 *      We assume that if we need the critical section, we already have it.
 */

static void
CleanupSession(LPXPL lpxpl)
{
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    BOOL fRefSupport = lpxpl->fRefSupport;

    /* Get rid of any open handle hanging around from Poll() or
    StartMessage() */

    if (lpxpl->hInFindHandle != INVALID_HANDLE_VALUE)
        CloseHandle(lpxpl->hInFindHandle);

    if (lpxpl->hOutFindHandle != INVALID_HANDLE_VALUE)
        CloseHandle(lpxpl->hOutFindHandle);

    /* OK, Deallocate the memory. */

    /* Session User Display Name and Entry-ID */

    if (lpxpl->lpMyIDArray)
    {
        lpxpl->FreeBuffer(lpxpl->lpMyIDArray[0].Value.bin.lpb);
        lpxpl->FreeBuffer(lpxpl->lpMyIDArray);
    }

    /* Free the session property array */

    lpxpl->FreeBuffer(lpxpl->lpPropArray);

    /* Free the Message OptionData struct */

    lpxpl->FreeBuffer(lpxpl->lpOptData);

    /* Release() the support object if need be. Do this last,
       since it could cause spooler to call XPP_Release. */

    if (fRefSupport)
        lpMAPISup->lpVtbl->Release(lpMAPISup);
}

/*
 -  lpxpp->lpVtbl->QueryInterface
 -
 *  Purpose:
 *
 *  Parameters:
 *      lpxpp               Pointer to object
 *      lpiid               New interface to Query to
 *      lppUnk              Where to store pointer to new object
 *
 *  Returns:
 *      (SCODE)             E_INVALIDARG if the input
 *                          object doesn't look like a XPP;
 *                          E_INVALIDARG if the IID
 *                          isn't readable or lppNewObj isn't
 *                          writable; E_NOINTERFACE
 *                          if we don't know the IID.
 *
 *  Operation:
 *      Validate parameters. See if the caller wants IUnknown or IXPProvider.
 *      If so, increment the usage count and return a new object.
 */

STDMETHODIMP
XPP_QueryInterface(LPXPP lpxpp,
    REFIID lpiid,
    LPVOID FAR * lppUnk)
{
    /*  Validate the parameters: 1) Does it seem to be an object?
        2) is the refcount nonzero? 3) Is there enough there for
        an interface ID? 4) Is there enough there for a new object? */

    if ((IsBadWritePtr(lpxpp, sizeof(XPP))) ||
        (lpxpp->lcInit == 0) ||
        (lpxpp->lpxppMyAddress != lpxpp) ||
        (IsBadReadPtr(lpiid, sizeof(IID))) ||
        (IsBadWritePtr(lppUnk, sizeof(LPXPP))))
    {
        DebugTraceSc(XPP_QueryInterface, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IXPProvider, sizeof(IID)))
    {
        *lppUnk = NULL;         /* OLE requires zeroing [out] parameters */
        DebugTraceSc(XPP_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    ++lpxpp->lcInit;
    *lppUnk = lpxpp;

    return hrSuccess;
}

/*
 -  lpxpp->lpVtbl->AddRef
 -
 *  Purpose:
 *      Increment reference count if nonzero.
 *
 *  Parameters:
 *      lpxpp               Pointer to object (should be XPP)
 *
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                          it doesn't seem to be XPP.
 *
 *  Operation:
 *      Make sure it looks like a XPP, and if so, bump the reference count
 *      and return the result to the caller.
 */

STDMETHODIMP_(ULONG)
XPP_AddRef(LPXPP lpxpp)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpxpp, sizeof(XPP)) ||
        lpxpp->lcInit == 0 ||
        lpxpp->lpxppMyAddress != lpxpp)
        return 0;

    return ++lpxpp->lcInit;
}

/*
 -  lpxpp->lpVtbl->Release
 -
 *  Purpose:
 *      Decrement lcInit. If it's zero, release the object.
 *
 *  Parameters:
 *      lpxpp               Pointer to object (should be XPP)
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                                  it doesn't seem to be XPP.
 *
 *  Operation:
 *      Make sure it looks like a XPP, and if so, decrement the reference
 *      count. If the count is now zero, deallocate the object.
 *
 *      Return the reference count to the caller.
 */

STDMETHODIMP_(ULONG)
XPP_Release(LPXPP lpxpp)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpxpp, sizeof(XPP)) ||
        lpxpp->lcInit == 0 ||
        lpxpp->lpxppMyAddress != lpxpp)
        return 0;

    --lpxpp->lcInit;

    if (lpxpp->lcInit == 0)
    {
        ULONG ulT;

        DebugTrace("XPP_Release() freeing XPP.\n");

        /* If we've inited and not deinited, do it now */

        if (lpxpp->fInited)
        {
            ulT = DEINIT_HURRY;
            lpxpp->lpVtbl->Shutdown(lpxpp, &ulT);
        }

        lpxpp->lpVtbl = NULL;
        (*lpxpp->lpFreeBuffer) (lpxpp);
        return 0;
    }

    return lpxpp->lcInit;
}

/*
 -  lpxpl->lpVtbl->QueryInterface
 -
 *  Purpose:
 *
 *  Parameters:
 *      lpxpl               Pointer to object
 *      lpiid               New interface to Query to
 *      lppUnk              Where to store pointer to new object
 *
 *  Returns:
 *      (SCODE)             E_INVALIDARG if the input
 *                          object doesn't look like a XPL;
 *                          E_INVALIDARG if the IID
 *                          isn't readable or lppNewObj isn't
 *                          writable; E_NOINTERFACE
 *                          if we don't know the IID.
 *
 *  Operation:
 *      Validate parameters. See if the caller wants IUnknown or IXPLogon.
 *      If so, increment the usage count and return a new object.
 */

STDMETHODIMP
XPL_QueryInterface(LPXPL lpxpl,
    REFIID lpiid,
    LPVOID FAR * lppUnk)
{
    /*  Validate the parameters: 1) Does it seem to be an object?
        2) is the refcount nonzero? 3) Is there enough there for
        an interface ID? 4) Is there enough there for a new object? */

    if ((IsBadWritePtr(lpxpl, sizeof(XPL))) ||
        (lpxpl->lcInit == 0) ||
        (lpxpl->lpMySession != lpxpl) ||
        (IsBadReadPtr(lpiid, sizeof(IID))) ||
        (IsBadWritePtr(lppUnk, sizeof(LPXPL))))
    {
        DebugTraceSc(XPL_QueryInterface, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  See if the requested interface is one of ours */

    if (memcmp(lpiid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(lpiid, &IID_IXPLogon, sizeof(IID)))
    {
        *lppUnk = NULL;         /* OLE requires zeroing [out] parameters */
        DebugTraceSc(XPL_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    ++lpxpl->lcInit;
    *lppUnk = lpxpl;

    return hrSuccess;
}

/*
 -  lpxpl->lpVtbl->AddRef
 -
 *  Purpose:
 *      Increment reference count if nonzero.
 *
 *  Parameters:
 *      lpxpl               Pointer to object (should be XPL)
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                          it doesn't seem to be XPL.
 *
 *  Operation:
 *      Make sure it looks like a XPL, and if so, bump the reference count
 *      and return the result to the caller.
 */

STDMETHODIMP_(ULONG)
XPL_AddRef(LPXPL lpxpl)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpxpl, sizeof(XPL)) ||
        lpxpl->lcInit == 0 ||
        lpxpl->lpMySession != lpxpl)
        return 0;

    return ++lpxpl->lcInit;
}

/*
 -  lpxpl->lpVtbl->Release
 -
 *  Purpose:
 *      Decrement lcInit. If it's zero, release the object.

*
 *  Parameters:
 *      lpxpl               Pointer to object (should be XPL)
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                          it doesn't seem to be XPL.
 *
 *  Operation:
 *      Make sure it looks like a XPL, and if so, decrement the reference
 *      count. If the count is now zero, deallocate the object.
 *
 *      Return the reference count to the caller.
 */

STDMETHODIMP_(ULONG)
XPL_Release(LPXPL lpxpl)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpxpl, sizeof(XPL)) ||
        lpxpl->lcInit == 0 ||
        lpxpl->lpMySession != lpxpl)
        return 0;

    --lpxpl->lcInit;

    if (lpxpl->lcInit == 0)
    {
        DebugTrace("XPL_Release() freeing XPL.\n");

        /* Ignore return code; we're going away reguardless! */

        XPL_TransportLogoff(lpxpl, LOGOFF_HURRY);
        lpxpl->lpVtbl = NULL;
        (*lpxpl->FreeBuffer) (lpxpl);

        return 0;
    }

    return lpxpl->lcInit;
}

/*
 -  ScCheckLogonProps
 -
 *  Purpose:
 *      Looks at the properties returned from the XP Logon property
 *      sheet and does some 'lite' validation of the values.  Users
 *      of this function must accept the symantics associated with
 *      the three possible return values.  That is, if the caller
 *      is displaying UI and MAPI_E_INVALID_PARAMETER is returned
 *      then the UI must be re-displayed.  If the caller is NOT
 *      displaying UI and MAPI_E_UNCONFIGURED is returned then
 *      they should pass this error back up the call stack, as MAPI
 *      will call the Service Entry point to get this info.
 *      MAPI_E_USER_CANCEL will only be returned if UI is allowed,
 *      in which case this should be treated the same a when the
 *      user presses <Cancel> in the Logon dialog.
 *
 *  Parameters:
 *      lpXPDialog          - Contains everything I need
 *      fUIAllowed          - Indicates if MessageBox's are allowed
 *
 *  Returns:
 *      S_OK                        - All data appears valid
 *      MAPI_E_USER_CANCEL          - Bad data and the user wants to abort
 *      MAPI_E_UNCONFIGURED         - Bad data and the user wants to try again
 */

SCODE
ScCheckLogonProps(LPXPDLG lpXPDialog, BOOL fUIAllowed)
{
    SCODE sc = MAPI_E_UNCONFIGURED;
    LPTSTR lpszT = NULL;
    HINSTANCE hInst = lpXPDialog->hInst;
    HWND hWnd = lpXPDialog->hwnd;
    LPSPropValue lpProps = *(lpXPDialog->lppPropArray);
    HANDLE hf = INVALID_HANDLE_VALUE;
    ULONG ulT;
    BOOL fDeleteFile = FALSE;
    LPTSTR szFuBar = TEXT("fubar");
    TCHAR szFooUNC[MAX_PATH];
    TCHAR szFooPath[MAX_PATH];
    UINT ids = 0;
    LONG lFlags;

#ifdef _WINNT
    ULONG cb;
    TCHAR szBuff[16];
#endif  /* _WINNT */

    /* Check Display Name */

    lpszT = ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpProps).Value.LPSZ;

    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpProps).ulPropTag) == PT_ERROR ||
        !lpszT || (*lpszT == '\0'))
    {
        ids = IDS_NO_DISPLAY_NAME;
        goto ret;
    }

    /* Check Address Type */

    lpszT = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpProps).Value.LPSZ;

    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpProps).ulPropTag) == PT_ERROR ||
        !lpszT || (*lpszT == '\0'))
    {
        ids = IDS_NO_ADDR_TYPE;
        goto ret;
    }

    /* Check Email Address */

    lpszT = ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpProps).Value.LPSZ;

    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpProps).ulPropTag) == PT_ERROR ||
        !lpszT || (*lpszT == '\0'))
    {
        ids = IDS_NO_EMAIL_ADDRESS;
        goto ret;
    }

    /* Save this, we'll need it later */

    lstrcpy(szFooUNC, lpszT);

    if (szFooUNC[lstrlen(szFooUNC)-1] != '\\')
        lstrcat(szFooUNC, TEXT("\\"));

    lstrcat(szFooUNC, TEXT("foo.txt"));

    /* Check Inbound Directory */

    lpszT = ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpProps).Value.LPSZ;

    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpProps).ulPropTag) == PT_ERROR ||
        !lpszT || (*lpszT == '\0'))
    {
        ids = IDS_NO_INBOUND_DIR;
        goto ret;
    }

    lstrcpy(szFooPath, lpszT);

    if (szFooPath[lstrlen(szFooPath)-1] != '\\')
        lstrcat(szFooPath, TEXT("\\"));

    lstrcat(szFooPath, TEXT("foo.txt"));

    /* Check Outbound Directory */

    lFlags = ArrayIndex(PR_SAMPLE_FLAGS, lpProps).Value.l;

    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_FLAGS, lpProps).ulPropTag) == PT_ERROR ||
        !(lFlags & PR_SAMPLE_FLAG_PEER_TO_PEER))
    {
        lpszT = ArrayIndex(PR_SAMPLE_OUTBOUND_DIR, lpProps).Value.LPSZ;

        if (!lpszT || (*lpszT == '\0'))
        {
            ids = IDS_NO_OUTBOUND_DIR;
            goto ret;
        }
    }

#ifdef _WINNT

    /* Do a little loop-back test to validate PR_SAMPLE_EMAIL_ADDRESS
       and PR_SAMPLE_INBOUND_DIR point to the same place and are valid.
       The WFW and Windows 95 Redirector can't talk to itself, so we only
       do this on NT.  Shouldn't be done on platforms whose redirector
       cannot do the loop-back thang. */

    if ((hf = CreateFile(szFooUNC, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                FILE_FLAG_WRITE_THROUGH, NULL)) == INVALID_HANDLE_VALUE)
    {
        ids = IDS_BAD_EMAIL_ADDRESS;
        goto ret;
    }

    if (!WriteFile(hf, szFuBar, lstrlen(szFuBar), &cb, NULL))
    {
        ids = IDS_BAD_EMAIL_ADDRESS;
        goto ret;
    }

    CloseHandle(hf);

    fDeleteFile = TRUE;

    /* Now, read the file */

    if ((hf = CreateFile(szFooPath, GENERIC_READ, 0, NULL, OPEN_EXISTING,
                FILE_ATTRIBUTE_READONLY, NULL)) == INVALID_HANDLE_VALUE)
    {
        ids = IDS_BAD_INBOUND_DIR;
        goto ret;
    }

    if (!ReadFile(hf, szBuff, lstrlen(szFuBar), &cb, NULL))
    {
        ids = IDS_BAD_INBOUND_DIR;
        goto ret;
    }

    szBuff[lstrlen(szFuBar)] = '\0';

    CloseHandle(hf);
    hf = INVALID_HANDLE_VALUE;

    if (lstrcmp(szFuBar, szBuff))
    {
        ids = IDS_UNC_DIR_MISMATCH;
        goto ret;
    }

#endif  /* _WINNT */

    /*  If there's a high water mark, make sure there's a low water
        mark at least 1K less than high water and no more than 32K. */

    ulT = ArrayIndex(PR_SAMPLE_LOGHIGHWATER, lpProps).Value.ul;
    if (PROP_TYPE(ArrayIndex(PR_SAMPLE_LOGHIGHWATER, lpProps).ulPropTag) == PT_ERROR ||
        ulT)
    {
        /* It looks like we'll be doing size adjustment. See what
        the current "low water" mark is. */

        ULONG ulLow;

        ulLow = ArrayIndex(PR_SAMPLE_LOGLOWWATER, lpProps).Value.ul;

        /* Make sure it's not more than 32K. If so, trim it down. */

        if (ulLow > 32)
            ulLow = 32;

        /* Make sure there's at least 1K between high and low water. If
        not, increase high water mark to satisfy this. */

        if (ulLow > (ulT - 1))
            ulT = ulLow + 1;

        /* Save this stuff into the logon property array. */

        ArrayIndex(PR_SAMPLE_LOGLOWWATER, lpProps).Value.ul = ulLow;
        ArrayIndex(PR_SAMPLE_LOGHIGHWATER, lpProps).Value.ul = ulT;
    }

    sc = S_OK;

ret:
    if (fDeleteFile)
        DeleteFile(szFooUNC);

    if (hf != INVALID_HANDLE_VALUE)
        CloseHandle(hf);

    /*  OK, the following block goes something like this:
        We initialized sc to MAPI_E_UNCONFIGURED at the top
        and only set it to S_OK if we make it all the way through
        the validation stuff.  If, along the way, we find bad data
        we set ids to the appropriate error string (this is Assert'd
        below).  Then, if UI is allowed, we get a bunch of string
        resources via LoadString and format an error message that
        will be displayed in a MessageBox.  If we are running the
        debug version of the transport, then the MessageBox will
        have an Abort/Retry/Ignore button selection, else, it will
        only have a Yes/No button selection.  These buttons carry
        with them the following meaning:

        DEBUG
            Abort   Return MAPI_E_USER_CANCEL; causes Logon to fail;
                    indicates that data is not to be saved in the profile.

            Retry   Return MAPI_E_UNCONFIGURED; re-display
                    Logon UI so user can fix problem.

            Ignore  Return S_OK; we accept the data as is and will
                    write it into the profile (if asked to save).

        !DEBUG
            Yes     Same as Retry above.

            No      Same as Abort above.

        Since we are using MessageBox for our error dialog, we are
        limited to this confusing choice of buttons.
    */

    if (FAILED(sc) && fUIAllowed)
    {
        TCHAR szErrTitle[64];
        TCHAR szErrFormat[128];
        TCHAR szErrStr[128];
        TCHAR szErrMsg[256];
        UINT fuButtons = MB_ICONEXCLAMATION | MB_SETFOREGROUND;
        int rc;

        Assert(ids);

        if (!LoadString(hInst, ids, szErrStr, sizeof(szErrStr) - 1) ||
            !LoadString(hInst, IDS_BAD_LOGON_PROPS_TITLE,
                        szErrTitle, sizeof(szErrTitle) - 1) ||
            !LoadString(hInst, IDS_BAD_LOGON_PROPS_FORMAT,
                        szErrFormat, sizeof(szErrFormat) - 1))
        {
            DebugTrace("Unable to do a LoadString in ScCheckLogonProps.\n");
            return sc;
        }

        wsprintf(szErrMsg, szErrFormat, szErrStr);

#ifdef DEBUG
        fuButtons |= MB_ABORTRETRYIGNORE;
#else
        fuButtons |= MB_YESNO;
#endif

        rc = MessageBox(hWnd, szErrMsg, szErrTitle, fuButtons);

        if (rc == IDRETRY || rc == IDYES)
            sc = MAPI_E_UNCONFIGURED;
        else if (rc == IDABORT || rc == IDNO)
            sc = MAPI_E_USER_CANCEL;
        else
            sc = S_OK;
    }

    return sc;
}

/*
 -  HrCheckSpoolerYield
 -
 *  Purpose:
 *      Used to enforce the .2 second rule.  Called periodically while
 *      processing a message to determine if we have used more than .2
 *      seconds.  If so, then call SpoolerYield(), else just continue.
 *      This is called with fReset set to TRUE when we first enter one
 *      of the Transport Logon methods (usually one that is known to
 *      take a long time like StartMessage() or SubmitMessage(). )
 *
 *  Parameters:
 *      lpMAPISup           - The Transports support object
 *      fReset              - Sets dwStart to current TickCount and returns
 *
 */

HRESULT
HrCheckSpoolerYield(LPMAPISUP lpMAPISup, BOOL fReset)
{
    HRESULT hr = hrSuccess;
    DWORD dwStop;
    static DWORD dwStart;

    if (fReset)
    {
        dwStart = GetTickCount();
        return hr;
    }

    dwStop = GetTickCount();

    if ((dwStop - dwStart) > 200)
    {
        hr = lpMAPISup->lpVtbl->SpoolerYield(lpMAPISup, 0);
        dwStart = GetTickCount();
    }

    return hr;
}

/*
 -  ScCopySessionProps
 -
 *  Purpose:
 *      Used to make a working copy of the session properties associated
 *      with a particular logon object.  This is done to protect us from
 *      having our data change out from under us due to a call to some
 *      transport UI.  This is done instead of mutexing to avoid the chance
 *      of deadlock conditions and to avoid having to create an elaborate
 *      mutex object that suits our special needs.
 *
 *  Parameters:
 *      lpxpl               - The Transports logon object
 *      lppPropArray        - Receives the new PropArray set
 *      lppMyIdArray        - Receives the new IdArray
 */

SCODE
ScCopySessionProps(
    LPXPL lpxpl,
    LPSPropValue FAR * lppPropArray,
    LPSPropValue FAR * lppMyIDArray)
{
    SCODE sc = S_OK;
    ULONG cProps;
    LPSPropValue lpPropArray = lpxpl->lpPropArray;
    LPSPropValue lpMyIDArray = lpxpl->lpMyIDArray;
    LPALLOCATEBUFFER lpAllocBuff = lpxpl->AllocateBuffer;

    if (lppPropArray)
    {
        cProps = MAX_LOGON_PROPERTIES - TEMP_LOGON_PROPERTIES;

        sc = ScDupPropset((int)cProps, lpPropArray, lpAllocBuff, lppPropArray);

        if (FAILED(sc))
            goto ret;
    }

    if (lppMyIDArray)
    {
        cProps = NUM_SENDER_PROPS;

        sc = ScDupPropset((int)cProps, lpMyIDArray, lpAllocBuff, lppMyIDArray);

        if (FAILED(sc) && lppPropArray)
        {
            lpxpl->FreeBuffer(*lppPropArray);
            *lppPropArray = NULL;
        }
    }

ret:
    return sc;
}

/*
 -  ServiceEntry
 -
 *  Purpose:
 *      This is the service entry point for the MAPI Configuration
 *      application.  This function uses the XP Logon dialog to allow
 *      the user to configure the provider.  If asked to, through the
 *      UI, the new settings will be saved in the profile.
 *
 *  Parameters:
 *      hInstance           [IN] Instance handle of the calling process
 *      lpMalloc            [IN] OLE style allocator to be used by PropSheet.
 *      lpMAPISup           [IN] MAPI Support object - used to get memory
 *                          allocators, BuildDisplayTable, etc.
 *      ulUIParam           [IN] hWnd of the caller who is parent of my UI.
 *      ulFlags             [IN] UI_SERVICE indicates that the caller wants
 *                          UI to help in configuring this provider.
 *                          MSG_SERVICE_UI_READ_ONLY indicates the caller
 *                          does not want write access to the configuration
 *                          property sheet.  No other flags are supported.
 *      ulContext           [IN] MSG_SERVICE_DELETE, MSG_SERVICE_INSTALL, and
 *                          MSG_SERVICE_UNINSTALL are noops and simply return
 *                          hrSuccess.  MSG_SERVICE_CONFIGURE and
 *                          MSG_SERVICE_CREATE allow the caller to create
 *                          or update the Logon properties in this providers
 *                          profile section.
 *      cValues             [IN] The caller may supply an array of property
 *                          values to configure this provider with.  This is
 *                          the number of values found in the array.
 *      lpProps             [IN] Caller supplied configuration properties.
 *      lpAdminProviders    [IN] An IProviderAdmin object used to retrieve a
 *                          ProfileSection object for this provider.
 *      lppMapiError        [OUT] Extended error information optionally
 *                          returned when the call fails.
 */

HRESULT STDAPICALLTYPE
ServiceEntry(
    HINSTANCE hInstance,
    LPMALLOC lpMalloc,
    LPMAPISUP lpMAPISup,
    ULONG ulUIParam,
    ULONG ulFlags,
    ULONG ulContext,
    ULONG cValues,
    LPSPropValue lpProps,
    LPPROVIDERADMIN lpAdminProviders,
    LPMAPIERROR FAR *lppMapiError)
{
    SCODE sc = S_OK;
    HRESULT hResult = hrSuccess;
    ULONG ulT;
    ULONG ulCount = 0;
    LPSPropValue lpPropArray = NULL;
    LPSPropValue lpLogonProps = NULL;
    LPALLOCATEBUFFER lpAllocBuff;
    LPALLOCATEMORE lpAllocMore;
    LPFREEBUFFER lpFreeBuff;
    LPPROFSECT lpProfileObj = NULL;
    XPDLG XPDialog;
    BOOL fNeedUI = FALSE;
    BOOL fAllowUI = FALSE;
    BOOL fInitialParamsOk = TRUE;

    /* Validate parameters */
    //$BUG: No parameter checking....

    /* Check the Support Object */

    if (FBadUnknown(lpMAPISup))
    {
        DebugTraceSc(Bad lpMAPISup in XP ServiceEntry, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /* Check for context */

    if ((ulContext == MSG_SERVICE_DELETE) ||
        (ulContext == MSG_SERVICE_INSTALL) ||
        (ulContext == MSG_SERVICE_UNINSTALL))
        return hrSuccess;

    if (ulContext != MSG_SERVICE_CONFIGURE && ulContext != MSG_SERVICE_CREATE)
    {
        DebugTraceSc(ServiceEntry unsupported context, MAPI_E_NO_SUPPORT);
        return ResultFromScode(MAPI_E_NO_SUPPORT);
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceSc(ServiceEntry Bad character width, MAPI_E_BAD_CHARWIDTH);
        return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
    }

    /* Check for UI */

    if (ulFlags & SERVICE_UI_ALWAYS)
        fAllowUI = fNeedUI = TRUE;
    else
        if (ulFlags & SERVICE_UI_ALLOWED)
            fAllowUI = TRUE;

    if (ulFlags & MSG_SERVICE_UI_READ_ONLY)
        ulFlags = UI_READONLY;

    /* Get the memory allocation routines we'll be needing. */

    hResult = lpMAPISup->lpVtbl->GetMemAllocRoutines(lpMAPISup,
        &lpAllocBuff, &lpAllocMore, &lpFreeBuff);

    if (hResult)
    {
        DebugTrace("GetMemAllocRoutines failed in ServiceEntry.\n");
        goto ret;
    }

    /* Open the Profile Section for this Transport Provider. */

    hResult = HrOpenSingleProvider(lpAdminProviders, &lpProfileObj);

    if (hResult)
    {
        DebugTrace("Unable to open the profile.\n");
        goto ret;
    }

    /* Profile is open, get the properties out of it. */

    hResult = lpProfileObj->lpVtbl->GetProps(lpProfileObj,
        (LPSPropTagArray) &sptLogonArray, ulFlags & MAPI_UNICODE, &ulCount, &lpPropArray);

    if (HR_FAILED(hResult))
    {
        DebugTrace("GetProps failed in XP ServiceEntry.\n");
        goto ret;
    }

    hResult = hrSuccess;

    /*  If the user passed in a property array to configure our profile
        with, then we'll merge this array with that from the profile
        section.  This merged array will, conditionally, be used in our
        Logon UI and, conditionally,  written out to the profile section.
        Also, lpLogonProps becomes our alias for the array no matter where
        the properties come from. */

    if (lpProps)
    {
        sc = ScMergeLogonProps(cValues, lpProps, ulCount,
            lpPropArray, lpAllocBuff, lpAllocMore, &lpLogonProps);

        if (FAILED(sc))
        {
            hResult = ResultFromScode(sc);
            DebugTrace("ScMergeLogonProps failed in XP ServiceEntry.\n");
            goto ret;
        }
    }
    else
    {
        lpLogonProps = lpPropArray;
        lpPropArray = NULL;
    }

    Assert(ulCount == MAX_LOGON_PROPERTIES);
    ulCount -= TEMP_LOGON_PROPERTIES;

    /* Find out if the stored SAMPLE_FLAGS say we should have UI.
       But, only if we aren't already forcing UI to be presented. */

    if (fAllowUI && !fNeedUI && lpLogonProps)
    {
        if ((ArrayIndex(PR_SAMPLE_FLAGS, lpLogonProps).ulPropTag == PR_SAMPLE_FLAGS) &&
            (ArrayIndex(PR_SAMPLE_FLAGS, lpLogonProps).Value.l & PR_SAMPLE_FLAG_UI_ALWAYS))
                fNeedUI = TRUE;
    }

    /* Fill in the logon UI structure */

    XPDialog.hInst = hInstance;
    XPDialog.hwnd = (HWND) ulUIParam;
    XPDialog.lppPropArray = &lpLogonProps;
    XPDialog.lpPTArray = (LPSPropTagArray) &sptLogonArray;
    XPDialog.AllocateBuffer = lpAllocBuff;
    XPDialog.AllocateMore = lpAllocMore;
    XPDialog.FreeBuffer = lpFreeBuff;
    XPDialog.lpMalloc = lpMalloc;
    XPDialog.lpMAPISup = lpMAPISup;
    XPDialog.fLogon = TRUE;
    XPDialog.ulFlags = ulFlags;

    /* Check the logon props BEFORE the dialog call, as the props will
       be freed if Cancel is selected, and we will want to know if the
       provider configuration information was valid */

    sc = ScCheckLogonProps(&XPDialog, FALSE);
    if ((sc == MAPI_E_USER_CANCEL) || (sc == MAPI_E_UNCONFIGURED))
    {
        if (fAllowUI)
        {
            fNeedUI = TRUE;
            fInitialParamsOk = FALSE;
        }
        else
            goto ret;
    }

    while (fAllowUI && fNeedUI)
    {
        sc = ScDoLogonDlg(&XPDialog);

        if (FAILED(sc))
        {
            hResult = ResultFromScode(sc);

            if (sc != MAPI_E_USER_CANCEL)
            {
                DebugTrace("ScDoLogonDlg failed in XP ServiceEntry.\n");
            }
            else
                if (!fInitialParamsOk)
                    hResult = ResultFromScode(MAPI_E_UNCONFIGURED);

            goto ret;
        }

        if (!lpLogonProps)
        {
            hResult = ResultFromScode(MAPI_E_CALL_FAILED);
            DebugTrace("No Logon Props returned in XP ServiceEntry.\n");
            goto ret;
        }

        /* Got a prop array, make sure everything in it is good */

        for (ulT = 0; ulT < ulCount; ulT++)
        {
            if (PROP_TYPE((lpLogonProps)[ulT].ulPropTag) == PT_ERROR)
            {
                hResult = ResultFromScode(MAPI_E_UNCONFIGURED);
                DebugTrace("Property %x not available, returning MAPI_E_UNCONFIGURED\n", PROP_ID((lpLogonProps)[ulT].ulPropTag));
                goto ret;
            }
        }

        /* Do some simple validation of the Logon Props */

        sc = ScCheckLogonProps(&XPDialog, TRUE);

        if (sc == MAPI_E_USER_CANCEL)
            goto ret;
        else if (sc == MAPI_E_UNCONFIGURED)
            continue;
        else
            fNeedUI = FALSE;
    }

    /*  If the user passed in properties and didn't request UI, then
        we need to check the array of Logon Props now.  If they are
        bad, then we'll simply return an error. */

    if (lpProps && !(ulFlags & SERVICE_UI_ALWAYS))
    {
        if (sc = ScCheckLogonProps(&XPDialog, FALSE))
        {
            hResult = ResultFromScode(sc);
            DebugTrace("User supplied properties did not pass validation.\n");
            goto ret;
        }
    }

    /*  If we get here, everything is fine and we can proceed. But first
        we should write the properties out if the user is willing. */

    ulT = ArrayIndex(PR_SAMPLE_FLAGS, lpLogonProps).ulPropTag;
    Assert(PROP_TYPE(ulT) != PT_ERROR);

    ulT = ArrayIndex(PR_SAMPLE_FLAGS, lpLogonProps).Value.ul;

    if (ulT & PR_SAMPLE_FLAG_SAVE_DATA)
    {
        hResult = lpProfileObj->lpVtbl->SetProps(lpProfileObj,
            ulCount, lpLogonProps, NULL);

        if (hResult)
        {
            DebugTrace("SetProps failed in XP ServiceEntry.\n");
            goto ret;
        }

        lpProfileObj->lpVtbl->SaveChanges(lpProfileObj, 0);
    }

ret:
    UlRelease(lpProfileObj);
    lpFreeBuff(lpPropArray);
    lpFreeBuff(lpLogonProps);

    DebugTraceResult(ServiceEntry, hResult);
    return hResult;
};

/*
 -  HrOpenSingleProvider
 -
 *  Purpose:
 *      Gets the IProfileSect object for a single provider from the
 *      ProviderTable in the IProviderAdmin object.  My entry in the
 *      table is supposed to be in the first row - this better be true.
 *
 */

HRESULT
HrOpenSingleProvider(LPPROVIDERADMIN lpAdminProviders,
    LPPROFSECT FAR * lppProfSect)
{
    HRESULT hResult;
    LPMAPITABLE lpTable = NULL;
    LPSRowSet lpRows = NULL;
    LPSPropValue lpProp;
    SPropTagArray sptaProvider = {1, {PR_PROVIDER_UID}};

    hResult = lpAdminProviders->lpVtbl->GetProviderTable(
        lpAdminProviders, 0, &lpTable);

    if (hResult)
        goto ret;

    hResult = lpTable->lpVtbl->SetColumns(lpTable, &sptaProvider, 0L);

    if (hResult)
        goto ret;

    hResult = lpTable->lpVtbl->QueryRows(lpTable, 1, 0, &lpRows);

    if (hResult || !lpRows || !lpRows->cRows)
    {
        hResult = ResultFromScode(MAPI_E_NOT_FOUND);
        goto ret;
    }

    Assert(lpRows->aRow[0].cValues == 1);
    lpProp = lpRows->aRow[0].lpProps;
    Assert(lpProp);
    Assert(lpProp->ulPropTag == PR_PROVIDER_UID);

    /* Found the right property; use it to open our section */

    hResult = lpAdminProviders->lpVtbl->OpenProfileSection(
        lpAdminProviders, (LPMAPIUID) lpProp->Value.bin.lpb,
        NULL, MAPI_MODIFY, lppProfSect);

ret:
    FreeProws(lpRows);
    UlRelease(lpTable);

    DebugTraceResult(HrOpenSingleProvider, hResult);
    return hResult;
}

/*
 -  ScMergeLogonProps
 -
 *  Purpose:
 *      This function merges a property array that the user passed into
 *      ServiceEntry with the property array of Transport Logon props
 *      found in the Profile.  This merged array is what either gets
 *      passed into the XP Logon dialog (in the case when UI is requested)
 *      or simply gets written out to the profile section for the
 *      Transport Provider.
 *
 *  Parameters:
 *      cProps1             - count of properties in the users array
 *      lpProps1            - the users property array
 *      cProps2             - count of properties from the XP profile
 *      lpProps2            - property array from the XP profile
 *      lpAllocBuff         - MAPI allocator used by ScDupPropset
 *      lppPropsDest        - receives the newly built prop array
 */

SCODE
ScMergeLogonProps(
    ULONG cProps1,
    LPSPropValue lpProps1,
    ULONG cProps2,
    LPSPropValue lpProps2,
    LPALLOCATEBUFFER lpAllocBuff,
    LPALLOCATEMORE lpAllocMore,
    LPSPropValue FAR * lppPropsDest)
{
    SCODE sc;
    ULONG ulTag;
    TCHAR szEmpty[1] = "";
    ULONG cT = cProps2;
    UINT cch;
    UINT id;
    LPTSTR lpszT;

    /*  If any of the properties that we tried to read from the
        profile didn't exist, then the profile provider would return
        them to us as type PT_ERROR.  We will convert these to either
        empty strings or zeros before we try to merge in the user
        supplied values.  This will allow us to do parameter validation
        on the properties even if the user didn't ask for UI. */

    while (cT--)
    {
        ulTag = lpProps2[cT].ulPropTag;

        if (PROP_TYPE(ulTag) == PT_ERROR)
        {
            id = (UINT)PROP_ID(ulTag);

            switch (id)
            {
            case (UINT)PROP_ID(PR_SAMPLE_DISPLAY_NAME):
            case (UINT)PROP_ID(PR_SAMPLE_EMAIL_ADDR_TYPE):
            case (UINT)PROP_ID(PR_SAMPLE_EMAIL_ADDRESS):
            case (UINT)PROP_ID(PR_SAMPLE_INBOUND_DIR):
            case (UINT)PROP_ID(PR_SAMPLE_OUTBOUND_DIR):
            case (UINT)PROP_ID(PR_SAMPLE_FILENAME):
            case (UINT)PROP_ID(PR_SAMPLE_DIRECTORY):
            case (UINT)PROP_ID(PR_SAMPLE_LOGFILE):
            case (UINT)PROP_ID(PR_TEMP_LOGHIGHWATER):
            case (UINT)PROP_ID(PR_TEMP_LOGLOWWATER):
                lpProps2[cT].ulPropTag = PROP_TAG(PT_TSTRING, id);
                lpProps2[cT].Value.LPSZ = szEmpty;
                break;

            case (UINT)PROP_ID(PR_SAMPLE_LOGHIGHWATER):
            case (UINT)PROP_ID(PR_SAMPLE_LOGLOWWATER):
            case (UINT)PROP_ID(PR_SAMPLE_FLAGS):
                lpProps2[cT].ulPropTag = PROP_TAG(PT_LONG, id);
                lpProps2[cT].Value.l = 0;
                break;

            case (UINT)PROP_ID(PR_TEMP_PEER_TO_PEER):
            case (UINT)PROP_ID(PR_TEMP_UI_ALWAYS):
            case (UINT)PROP_ID(PR_TEMP_LOG_EVENTS):
            case (UINT)PROP_ID(PR_TEMP_SAVE_DATA):
                lpProps2[cT].ulPropTag = PROP_TAG(PT_BOOLEAN, id);
                lpProps2[cT].Value.b = FALSE;
                break;

            default:
                DebugTrace("Got a bad PropTag in ScMergeLogonProps.\n");
            }
        }
    }

    /*  Now, replace the profile properties with those passed in by the
        user.  Check the ones of the type "File Path" to see that they
        have a slash as their last character.  If not, then add one. */

    while (cProps1--)
    {
        ulTag = lpProps1[cProps1].ulPropTag;

        switch (ulTag)
        {
            /* Do the String properties first */

        case PR_SAMPLE_INBOUND_DIR:
        case PR_SAMPLE_OUTBOUND_DIR:
        case PR_SAMPLE_DIRECTORY:
            if (lpProps1[cProps1].Value.LPSZ)
            {
                cch = lstrlen(lpProps1[cProps1].Value.LPSZ);

                if (cch && lpProps1[cProps1].Value.LPSZ[cch - 1] != '\\')
                {
                    sc = lpAllocMore((cch + 2) * sizeof(TCHAR),
                        lpProps2, (LPVOID FAR *) &lpszT);

                    if (!FAILED(sc))
                    {
                        lstrcpy(lpszT, lpProps1[cProps1].Value.LPSZ);
                        lpszT[cch] = '\\';
                        lpszT[cch + 1] = '\0';
                        ArrayIndex(ulTag, lpProps2).Value.LPSZ = lpszT;
                    }
                }
                else
                    ArrayIndex(ulTag, lpProps2).Value.LPSZ = lpProps1[cProps1].Value.LPSZ;
            }
            else
                ArrayIndex(ulTag, lpProps2).Value.LPSZ = szEmpty;

            break;

        case PR_SAMPLE_DISPLAY_NAME:
        case PR_SAMPLE_EMAIL_ADDR_TYPE:

case PR_SAMPLE_EMAIL_ADDRESS:
        case PR_SAMPLE_FILENAME:
        case PR_SAMPLE_LOGFILE:
            if (lpProps1[cProps1].Value.LPSZ)
                ArrayIndex(ulTag, lpProps2).Value.LPSZ = lpProps1[cProps1].Value.LPSZ;
            else
                ArrayIndex(ulTag, lpProps2).Value.LPSZ = szEmpty;

            break;

            /* Then the long values */

        case PR_SAMPLE_LOGHIGHWATER:
        case PR_SAMPLE_LOGLOWWATER:
        case PR_SAMPLE_FLAGS:
            ArrayIndex(ulTag, lpProps2).Value.l = lpProps1[cProps1].Value.l;
            break;

            /* We have a bad value, lets return an error! */

        default:
            DebugTrace("Got a bad PropTag in ScMergeLogonProps()");
            return E_INVALIDARG;
        }
    }

    return ScDupPropset((int)cProps2, lpProps2, lpAllocBuff, lppPropsDest);
}

/*
 -  HrDeleteDeferred
 -
 *  Purpose:
 *      Removes a deferred message node from the list when the Spooler
 *      does a NOTIFY_ABORT_DEFERRED.
 *
 *  Parameters:
 *      lpxpl           The session owning the deferred message
 *      lpsbinEID       EntryID of the deferred message to delete
 *
 *  Returns:
 *      hResult         Indicating Success/Faailure
 */

HRESULT
HrDeleteDeferred(LPXPL lpxpl, LPSBinary lpsbinEID)
{
    LPDEFMSG lpDefMsg;
    LPDEFMSG *lppDefMsg = &(lpxpl->lpDeferredList);

    while (lpDefMsg = *lppDefMsg)
    {
        if ((lpDefMsg->sbinEIDDef.cb == lpsbinEID->cb) &&
            !memcmp(lpDefMsg->sbinEIDDef.lpb,
                    lpsbinEID->lpb,
                    (UINT)lpsbinEID->cb))
        {
            *lppDefMsg = lpDefMsg->lpNext;
            break;
        }
        else
            lppDefMsg = &(lpDefMsg->lpNext);
    }

    lpxpl->FreeBuffer(lpDefMsg);

    return hrSuccess;
}

/*
 *  WizardEntry()
 *
 *  Purpose:
 *
 *      This is the initial entrypoint for the MAPI 1.0 configuration
 *      wizard.  This function tells the wizard DLL how many pages the
 *      configuration for this service requires as well as the dialog
 *      procedure to call for each individual event.
 *
 *  Arguments:
 *
 *      hInstance       the instance of my dll, this can be used to
 *                      retrieve resources out of my DLL, etc.
 *
 *      lppszRsrcName   [OUT]   on return, this buffer is filled with
 *                              the full name of the dialog resource ID.
 *                              Note that this requires the name to be a
 *                              text value and not something generated
 *                              with the MAKEINTRESOURCE() macro
 *
 *      lpfnDlgProc     [OUT]   on return, holds a function pointer to
 *                              the dialog proc to call for each event
 *
 *      lpMapiProp      the pointer to a IMAPIProp object that is my
 *                      interface to the profile.
 *
 *      lpsup           A profile suport object that can be used to
 *                      get MAPI allocators
 *
 *  Returns:
 *
 *      (SCODE)         S_OK
 */

ULONG STDAPICALLTYPE
WizardEntry (HINSTANCE hInstance,
    LPTSTR FAR * lppszRsrcName,
    DLGPROC FAR * lpfnDlgProc,
    LPMAPIPROP lpMapiProp,
    LPVOID lpsup)
{
    const static TCHAR szWizTemplate[] = "SampleTransportWizard";

    Unreferenced (lpsup);

    /*  Should probably mutex access here */

    *lppszRsrcName = (LPTSTR)szWizTemplate;
    *lpfnDlgProc = (DLGPROC) WizardWndProc;
    lpmpWizard = lpMapiProp;
    UlAddRef (lpMapiProp);

    return S_OK;
}

/*
 *  TogglePage()
 *
 *  Purpose:
 *
 *      Loops through the controls on a wizard page and eiter
 *      enables/shows the control or hides/disables it.
 */

VOID
TogglePage (HWND hdlg, UINT ipage, BOOL fEnable)
{
    UINT ictl = 0;
    HANDLE hctl;

    while (hctl = GetDlgItem (hdlg, (WIZ_BASE + (ipage * 10) + ictl++)))
    {
        EnableWindow(hctl, fEnable);
        ShowWindow (hctl, (fEnable ? SW_SHOW : SW_HIDE));
    }
}

BOOL STDAPICALLTYPE
WizardWndProc (HWND hDlg,
    UINT wMsgID,
    WPARAM wParam,
    LPARAM lParam)
{
    UINT cb;
    UINT cpageJump = 1;
    UINT idFocus = 0;

    static fInited = FALSE;
    static UINT ipWiz = 0;
    static SPropValue rgvalWiz[cWizProps] = {0};
    static CHAR rgchName[cchNameMax + 1] = {0};
    static CHAR rgchType[cchTypeMax + 1] = {0};
    static CHAR rgchUNC[MAX_PATH] = {0};
    static CHAR rgchPath[MAX_PATH] = {0};

    switch (wMsgID)
    {
      case WM_INITDIALOG:

        if (!fInited)
        {
            fInited = TRUE;
            SetWindowText (GetDlgItem (hDlg, IDC_TypeEdit), "MSPEER");
            SendMessage (GetDlgItem (hDlg, IDC_TypeEdit), EM_LIMITTEXT, (WPARAM)cchTypeMax, 0);
            SendMessage (GetDlgItem (hDlg, IDC_NameEdit), EM_LIMITTEXT, (WPARAM)cchNameMax, 0);
            SendMessage (GetDlgItem (hDlg, IDC_UNCEdit), EM_LIMITTEXT, (WPARAM)MAX_PATH - 1, 0);
            SendMessage (GetDlgItem (hDlg, IDC_PathEdit), EM_LIMITTEXT, (WPARAM)MAX_PATH - 1, 0);
            rgvalWiz[ipDispName].ulPropTag = PR_NULL;
            rgvalWiz[ipEmailType].ulPropTag = PR_NULL;
            rgvalWiz[ipEmailAddress].ulPropTag = PR_NULL;
            rgvalWiz[ipInbox].ulPropTag = PR_NULL;
            rgvalWiz[ipOutbox].ulPropTag = PR_SAMPLE_OUTBOUND_DIR;
            rgvalWiz[ipOutbox].Value.lpszA = "\\TEMP";
            rgvalWiz[ipFilename].ulPropTag = PR_NULL;
            rgvalWiz[ipDirectory].ulPropTag = PR_NULL;
            rgvalWiz[ipFlags].ulPropTag = PR_SAMPLE_FLAGS;
            rgvalWiz[ipFlags].Value.l = PR_SAMPLE_FLAG_PEER_TO_PEER | PR_SAMPLE_FLAG_SAVE_DATA;
            rgvalWiz[ipLogFile].ulPropTag = PR_SAMPLE_LOGFILE;
            rgvalWiz[ipLogFile].Value.lpszA = "SMPXP.LOG";
            rgvalWiz[ipLogHigh].ulPropTag = PR_SAMPLE_LOGHIGHWATER;
            rgvalWiz[ipLogHigh].Value.l = 0;
            rgvalWiz[ipLogLow].ulPropTag = PR_SAMPLE_LOGLOWWATER;
            rgvalWiz[ipLogLow].Value.l = 0;
            DebugTrace ("MSPEER: Wizard page initialized\n");
        }
        break;

      case WIZ_QUERYNUMPAGES:

        DebugTrace ("MSPEER: Wizard page count %d\n", cpageMax);
        return (BOOL)cpageMax;

      case WM_CLOSE:

        UlRelease (lpmpWizard);
        lpmpWizard = NULL;
        break;

      case WM_COMMAND:

        switch (LOWORD(wParam))
        {
          case WIZ_NEXT:

            switch (ipWiz)
            {
              case 0:
                idFocus = IDC_NameEdit;
                break;

              case 1:

                /*  Going from name page to address type */

                if (!(cb = (UINT)GetWindowText (GetDlgItem (hDlg, IDC_NameEdit),
                                        rgchName, cchNameMax + 1)))
                {
                    /*  We require a display name */

                    MessageBeep (0);
                    return 0;
                }
                rgvalWiz[ipDispName].ulPropTag = PR_SAMPLE_DISPLAY_NAME;
                rgvalWiz[ipDispName].Value.lpszA = rgchName;
                idFocus = IDC_TypeEdit;
                break;

              case 2:

                /* Going from type page to inbox */

                if (!(cb = (UINT)GetWindowText (GetDlgItem (hDlg, IDC_TypeEdit),
                                        rgchType, cchTypeMax + 1)))
                {
                    /*  We require an address type */

                    MessageBeep (0);
                    return 0;
                }
                rgvalWiz[ipEmailType].ulPropTag = PR_SAMPLE_EMAIL_ADDR_TYPE;
                rgvalWiz[ipEmailType].Value.lpszA = rgchType;
                idFocus = IDC_UNCEdit;
                break;

              case 3:

                /* Going from inbox page to path page */

                if (!(cb = (UINT)GetWindowText (GetDlgItem (hDlg, IDC_UNCEdit),
                                        rgchUNC, MAX_PATH)))
                {
                    /*  We require an address type */

                    MessageBeep (0);
                    return 0;
                }
                rgvalWiz[ipEmailAddress].ulPropTag = PR_SAMPLE_EMAIL_ADDRESS;
                rgvalWiz[ipEmailAddress].Value.lpszA = rgchUNC;
                idFocus = IDC_PathEdit;
                break;

              case 4:

                /* Going from the path page to completion*/

                rgvalWiz[ipInbox].ulPropTag = PR_SAMPLE_INBOUND_DIR;
                if ((cb = (UINT)GetWindowText (GetDlgItem (hDlg, IDC_PathEdit), rgchPath, MAX_PATH)))
                    rgvalWiz[ipInbox].Value.lpszA = rgchPath;
                else
                    rgvalWiz[ipInbox].Value.lpszA = rgvalWiz[ipEmailAddress].Value.lpszA;

                Assert (lpmpWizard);
                lpmpWizard->lpVtbl->SetProps (lpmpWizard, cWizProps, rgvalWiz, NULL);
                break;

              default:
                Assert (FALSE);
                break;
            }

            /*  Disable Current Page */
            TogglePage (hDlg, ipWiz, FALSE);

            /*  Enable Next Page */
            TogglePage (hDlg, ++ipWiz, TRUE);
            SetFocus (GetDlgItem (hDlg, idFocus));
#ifdef  _WIN32
            SendDlgItemMessage (hDlg, idFocus, EM_SETSEL, 0, -1);
#else
            SendDlgItemMessage (hDlg, idFocus, EM_SETSEL, 0, MAKELONG(0,-1));
#endif
            return (BOOL)cpageJump;

          case WIZ_PREV:

            /*  Disable Current Page */
            TogglePage (hDlg, ipWiz, FALSE);

            /*  Enable Previous Page */
            TogglePage (hDlg, --ipWiz, TRUE);

            switch (ipWiz)
            {
              case 1:
                idFocus = IDC_NameEdit;
                break;

              case 2:
                idFocus = IDC_TypeEdit;
                break;

              case 3:
                idFocus = IDC_UNCEdit;
                break;

              case 4:
                idFocus = IDC_PathEdit;
                break;

              default:
                break;
            }
            SetFocus (GetDlgItem (hDlg, idFocus));
#ifdef  _WIN32
            SendDlgItemMessage (hDlg, idFocus, EM_SETSEL, 0, -1);
#else
            SendDlgItemMessage (hDlg, idFocus, EM_SETSEL, 0, MAKELONG(0,-1));
#endif
            return (BOOL)cpageJump;

          default:

            return FALSE;
        }
        break;

      default:

        return FALSE;
    }

    return TRUE;
}


XPBASE.H

/*
 -  X P B A S E . H
 -
 *
 *  Definitions, typedefs and prototypes used by the Sample Transport
 *  Provider modules.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Make sure that we are thinking "C" here... */
#ifdef __cplusplus
extern "C"
{
#endif

/*
 *  Linked-List of deferred messages
 */

typedef struct _DEFMSG *LPDEFMSG;

typedef struct _DEFMSG
{
    ULONG ulMsgRef;
    SBinary sbinEIDDef;
    LPDEFMSG lpNext;

} DEFMSG;

/*
 *  Sample Transport Provider DLL exported entry points.
 */

/* Transport Init Object */

#undef  INTERFACE
#define INTERFACE struct _XPP

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, XPP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IXPPROVIDER_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(XPP_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IXPPROVIDER_METHODS(IMPL)
};

typedef struct _XPP {
    XPP_Vtbl FAR *      lpVtbl;         /* Methods                          */
    LONG                lcInit;         /* Usage Count                      */
    struct _XPP FAR *   lpxppMyAddress; /* Pointer to myself, for validation*/
    CRITICAL_SECTION    csTransport;    /* Critical section for this object */
    struct _XPL FAR *   XPSessionList;  /* List of logon sessions on obj    */
    BOOL                fInited;        /* Are we in an inited state?       */
    HINSTANCE           hInst;          /* Instance of our library          */
    LPMALLOC            lpMalloc;       /* IMalloc object                   */
    LPFREEBUFFER        lpFreeBuffer;   /* Need this to free the object     */
} XPP, FAR *LPXPP;

/* Transport Logon Object */

#undef  INTERFACE
#define INTERFACE struct _XPL

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, XPL_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IXPLOGON_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(XPL_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IXPLOGON_METHODS(IMPL)
};

/*
 *  Logon object structure.
 */

typedef struct _XPL
{
    XPL_Vtbl FAR *      lpVtbl;             /* Methods                                  */
    LONG                lcInit;             /* Usage Count                              */
    struct _XPL FAR *   lpNextSession;      /* Link to next session                 */
    struct _XPL FAR *   lpMySession;        /* My address, used to validate         */
    LPXPP               lpxppParent;        /* Parent XPP object                        */
    LPMAPISTATUS        lpXPStatus;         /* Child Status Object                      */
    ULONG               ulSessionFlags;     /* Session flags                            */
    LPMAPISUP           lpMAPISup;          /* Support object for this session          */
    LPALLOCATEBUFFER    AllocateBuffer;     /* MAPIAllocateBuffer pointer           */
    LPALLOCATEMORE      AllocateMore;       /* MAPIAllocateMore pointer             */
    LPFREEBUFFER        FreeBuffer;         /* MAPIFreeBuffer function pointer          */
    ULONG               cLogonPropValues;   /* Number of Logon property values          */
    LPSPropValue        lpPropArray;        /* Logon property array                     */
    LPSPropValue        lpMyIDArray;        /* Properties to set on transmit            */
    ULONG               cOptData;           /* Number of Options we support             */
    LPOPTIONDATA        lpOptData;          /* Per-Message & Recip OptionData           */
    LPTSTR              lpszAdrType;        /* Used to pass back on AddressType call    */
    ULONG               ulTransportStatus;  /* Howzit going? Used by status             */
    ULONG               ulResourceMethods;  /* Methods supported by our status object   */

    /* Deferred message handling elements */
    ULONG               ulDeferredMsgRef;
    BOOL                fResendDeferred;
    LPDEFMSG            lpDeferredList;

    BOOL                fRefSupport;        /* We AddRef'd the support object */
    BOOL                fFoundInMessage;    /* Found a inbound message flag   */
    HANDLE              hInFindHandle;      /* Used in Poll(), StartMessage() */
    WIN32_FIND_DATA     wfdInFindData;      /* Used in Poll(), StartMessage() */
    HANDLE              hOutFindHandle;     /* Used in Outbound Queue         */
    WIN32_FIND_DATA     wfdOutFindData;     /* Used in Outbound Queue         */
} XPL, FAR *LPXPL;

/*
 *  Properties we store in the Profile.
 *
 * NOTE!!! THE ORDERING OF THE PROPERTY ID'S HERE MUST BE THE SAME
 * AS THE ORDERING OF PROP TAG ARRAY sptLogonArray IN SMPLXPT.C, WITH
 * SEQUENTIAL NUMBERING!!!!
 *
 */

#define BASE_PROVIDER_ID    0x6600  /* From MAPITAGS.H comments */
#define BASE_MSG_OPT_ID     0x4000  /* From MAPITAGS.H comments */
#define BASE_RECIP_OPT_ID   0x5800  /* From MAPITAGS.H comments */

/* Display name of user. Optional property. If not present we will use
   the email address as the display name -- ugly but effective.            */

#define PR_SAMPLE_DISPLAY_NAME      PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0001))

/* Address type of sample transport. This will be used to construct inbound
   and outbound addresses where needed. It also will be used to tell the
   Spooler what address types we should get.                               */

#define PR_SAMPLE_EMAIL_ADDR_TYPE   PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0002))

/* Email address. This will usually be the same as the inbound directory,
   although this may tend to have a more canonical form for remote access. */

#define PR_SAMPLE_EMAIL_ADDRESS     PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0003))

/* Inbound directory. This is where the transport will look in order to
   determine whether it has received any mail.                             */

#define PR_SAMPLE_INBOUND_DIR       PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0004))

/* Outbound directory. The transport will store its message files here
   while it is in the process of sending them. The presence of a file
   in this directory implies that the transport still has work to do.      */

#define PR_SAMPLE_OUTBOUND_DIR      PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0005))

/* Filename. This is an 8-character root for use by mail-enabled or
   workgroup applications to build a complete file specification (by
   adding an extension) for data they wish to store for this user.         */

#define PR_SAMPLE_FILENAME          PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0006))

/* Directory. This is a directory in which workgroup applications may
   wish to store their files pertaining to this user.                      */

#define PR_SAMPLE_DIRECTORY         PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0007))

/* Flags. Contain various options for this transport provider.             */

#define PR_SAMPLE_FLAGS             PROP_TAG (PT_LONG,      (BASE_PROVIDER_ID + 0x0008))

#define PR_SAMPLE_FLAG_PEER_TO_PEER ((ULONG) 0x00000001)
#define PR_SAMPLE_FLAG_UI_ALWAYS    ((ULONG) 0x00000002)
#define PR_SAMPLE_FLAG_LOG_EVENTS   ((ULONG) 0x00000004)
#define PR_SAMPLE_FLAG_SAVE_DATA    ((ULONG) 0x00000008)

/* Logfile. This is where logging information will be written. If this
   property is not present, we will use (PR_SAMPLE_FILENAME)".LOG"         */

#define PR_SAMPLE_LOGFILE           PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0009))

/* Logfile high water mark. This is the point at which the log writer will
   truncate the logfile so as not to fill up the disk.                     */

#define PR_SAMPLE_LOGHIGHWATER      PROP_TAG (PT_LONG,      (BASE_PROVIDER_ID + 0x000A))

/* Logfile low water mark. This is the approximate size that the logfile
   writer will truncate the logfile to (modulo a line ending).             */

#define PR_SAMPLE_LOGLOWWATER       PROP_TAG (PT_LONG,      (BASE_PROVIDER_ID + 0x000B))

/* The following property tags are temporaries. They are here because
   the configuration property sheets want one property tag for every
   control; after we run the dialogs, we roll them back up into
   PR_SAMPLE_FLAGS.                                                        */

#define PR_TEMP_PEER_TO_PEER        PROP_TAG (PT_BOOLEAN,   (BASE_PROVIDER_ID + 0x000C))
#define PR_TEMP_UI_ALWAYS           PROP_TAG (PT_BOOLEAN,   (BASE_PROVIDER_ID + 0x000D))
#define PR_TEMP_LOG_EVENTS          PROP_TAG (PT_BOOLEAN,   (BASE_PROVIDER_ID + 0x000E))
#define PR_TEMP_SAVE_DATA           PROP_TAG (PT_BOOLEAN,   (BASE_PROVIDER_ID + 0x000F))

#define PR_TEMP_LOGHIGHWATER        PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0010))
#define PR_TEMP_LOGLOWWATER         PROP_TAG (PT_TSTRING,   (BASE_PROVIDER_ID + 0x0011))

/* This property is a per-message option.  If TRUE, the message will be
   deferred until the next message comes along that is not to be deferred.
   At that point, the Transport will SpoolerNotify() to have all currently
   deferred messages submitted to it for sending.                           */

#define PR_SAMPLE_PER_MSG_DEFER     PROP_TAG (PT_BOOLEAN,   (BASE_MSG_OPT_ID + 0x0001))

/* This property is a per-recipient option.  If TRUE, the message will be
   deferred until the next message comes along that is not to be deferred.
   At that point, the Transport will SpoolerNotify() to have all currently
   deferred messages submitted to it for sending.  This property is ignored
   if PR_SAMPLE_PER_MSG_DEFER is set.

   This property is also used as a flag on the message that says - we've seen
   this message before and have deferred it due to some recipients with their
   property PR_SAMPLE_PER_RECIP_DEFER flag set.  When we run across this, we'll
   delete it from the message and send to the remaining recipients that
   have not been handled.                                                  */

#define PR_SAMPLE_PER_RECIP_DEFER   PROP_TAG (PT_BOOLEAN,   (BASE_RECIP_OPT_ID + 0x0001))

/* The following is used to access the properties in the logon array.      */

#define MAX_LOGON_PROPERTIES        17
#define TEMP_LOGON_PROPERTIES       6
#define ArrayIndex(PROP, ARRAY) (ARRAY)[(PROP_ID(PROP) - BASE_PROVIDER_ID - 1)]
#define NUM_SENDER_PROPS            3   /* How many sender ID properties?  */

/* Display name for my transport. Used for the status row. */

#ifdef DEBUG
#define MYDISPLAYNAME "Sample Transport Provider (Debug)"
#elif defined (TEST)
#define MYDISPLAYNAME "Sample Transport Provider (Test)"
#else
#define MYDISPLAYNAME "Sample Transport Provider"
#endif

/* Common global data/code. */

BOOL    FIsValidSession(LPXPL);
SCODE   ScCheckLogonProps(LPXPDLG lpXPDialog, BOOL fUIAllowed);
HRESULT MapScodeSz(SCODE sc, LPXPL lpxpl, LPTSTR * lppszMessage);
HRESULT HrCheckSpoolerYield(LPMAPISUP lpMAPISup, BOOL fReset);
SCODE   ScCopySessionProps(LPXPL lpxpl, LPSPropValue FAR *lppPropArray, LPSPropValue FAR *lppMyIDArray);

#ifdef __cplusplus
}
#endif


XPDIALOG.C

/*
 -  X P D I A L O G . C
 -
 *  Purpose:
 *      Logon UI code for use by the MAPI Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include "xpresrc.h"
#include <stdlib.h>
#include <mapiutil.h>

#ifdef DEBUG_DLGITEMS
#ifndef DEBUG
#define DEBUG
#endif
#endif

#define PropIndex(PROP)         (PROP_ID(PROP) - BASE_PROVIDER_ID - 1)

/* Generic control structures */

DTBLLABEL dtbllabel =
{sizeof(DTBLLABEL), 0};
DTBLPAGE dtblpage =
{sizeof(DTBLPAGE), 0, 0, 0};
TCHAR szNoFilter[] = TEXT("*");
TCHAR szDigitFilter[] = TEXT("[0-9]");

/* Display table control structures for the General property sheet. */

DTBLEDIT editWgapFname =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_PATH,
    PR_SAMPLE_FILENAME};
DTBLEDIT editWgapDir =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_PATH,
    PR_SAMPLE_DIRECTORY};
DTBLCHECKBOX chkLogEvents =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_TEMP_LOG_EVENTS};
DTBLCHECKBOX chkPeer =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_TEMP_PEER_TO_PEER};
DTBLCHECKBOX chkLogonUI =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_TEMP_UI_ALWAYS};
DTBLCHECKBOX chkSaveDialog =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_TEMP_SAVE_DATA};

DTCTL rgdtctlGeneral[] =
{
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_LOG_EVENTS, &chkLogEvents},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_PEER, &chkPeer},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_LOGON_UI, &chkLogonUI},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_SAVE_DIALOG, &chkSaveDialog},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_WGAP_FNAME, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_WGAP_FNAME, &editWgapFname},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_WGAP_DIR, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_WGAP_DIR, &editWgapDir}
};

/* Display table control structures for the Peer to Peer property sheet. */

DTBLEDIT editDisplay =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_TEXTFIELD,
    PR_SAMPLE_DISPLAY_NAME};
DTBLEDIT editEmailType =
{sizeof(DTBLEDIT), fMapiUnicode, 32,
    PR_SAMPLE_EMAIL_ADDR_TYPE};
DTBLEDIT editEmailAddress =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_TEXTFIELD,
    PR_SAMPLE_EMAIL_ADDRESS};
DTBLEDIT editInboundDir =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_PATH,
    PR_SAMPLE_INBOUND_DIR};
DTBLEDIT editOutboundDir =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_PATH,
    PR_SAMPLE_OUTBOUND_DIR};

DTCTL rgdtctlPeer[] =
{
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_DISPLAY, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_DISPLAY, &editDisplay},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_EMAIL_TYPE, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_EMAIL_TYPE, &editEmailType},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_EMAIL_ADDR, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_EMAIL_ADDR, &editEmailAddress},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_IN_DIR, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_IN_DIR, &editInboundDir},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_OUT_DIR, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_OUT_DIR, &editOutboundDir}
};

/* Display table control structures for the Logging property sheet. */

DTBLEDIT editLogFile =
{sizeof(DTBLEDIT), fMapiUnicode, MAX_PATH,
    PR_SAMPLE_LOGFILE};
DTBLEDIT editLogHigh =
{sizeof(DTBLEDIT), fMapiUnicode, 11,
    PR_TEMP_LOGHIGHWATER};
DTBLEDIT editLogLow =
{sizeof(DTBLEDIT), fMapiUnicode, 11,
    PR_TEMP_LOGLOWWATER};
DTCTL rgdtctlLogging[] =
{
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblpage},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_LOG_NAME, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szNoFilter, IDE_LOG_NAME, &editLogFile},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_LOG_HIGH, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szDigitFilter, IDE_LOG_HIGH, &editLogHigh},
    {DTCT_LABEL, 0, NULL, 0, NULL, IDT_LOG_LOW, &dtbllabel},
    {DTCT_EDIT, DT_EDITABLE, NULL, 0, szDigitFilter, IDE_LOG_LOW, &editLogLow}
};

/* Display table pages */

DTPAGE rgdtpage[] =
{
    {
        sizeof(rgdtctlGeneral) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(GeneralPage),
        "",
        rgdtctlGeneral
    },
    {
        sizeof(rgdtctlPeer) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(PeerPage),
        "",
        rgdtctlPeer
    },
    {
        sizeof(rgdtctlLogging) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(LoggingPage),
        "",
        rgdtctlLogging
    }
};

#ifdef DEBUG_DLGITEMS
static void
 DLGDump(LPXPDLG lpXPDialog);
#endif

/*
 -  ScDoLogonDlg
 -
 *  Purpose:
 *      Called by the TransportLogon code to present logon UI to the user.
 *
 *  Parameters:
 *      lpXPDialog          Structure through which we communicate
 *
 *  Returns:
 *      0                   Dialog came up, pLogonStruct->lppPropArray
 *                          contains a PropValueArray if OK, NULL if CANCEL
 *
 *      (Failure)           Some error occurred. lppPropArray will have
 *                          either the original array or NULL.
 *
 *  Operation:
 *      Gets the necessary information from Windows and activates the logon
 *      dialog box.
 */

SCODE
ScDoLogonDlg(LPXPDLG lpXPDialog)
{
    SCODE sc = S_OK;
    HRESULT hr;
    LPMAPITABLE ptable = NULL;
    LPPROPDATA pTempProps = NULL;
    ULONG flT = 0L;
    LPSPropValue rgprop;
    LPSPropValue pprop;
    LPSPropTagArray ptags;
    ULONG cprop;
    TCHAR szDialogTitle[128];
    TCHAR szLogHigh[12];
    TCHAR szLogLow[12];
    UINT i;

    /* Take ownership of the property value array, so calling code
       will not leak memory. */

    rgprop = *(lpXPDialog->lppPropArray);
    *(lpXPDialog->lppPropArray) = NULL;
    ptags = lpXPDialog->lpPTArray;

    /* Retrieve the dialog title string */

    if (!LoadString(lpXPDialog->hInst, IDS_CONFIG_TITLE,
        szDialogTitle, sizeof(szDialogTitle)))
    {
        sc = MAPI_E_NOT_ENOUGH_MEMORY;
        DebugTrace("LoadString failed in ScDoLogonDlg.\n");
        goto ret;
    }

    /* Create the configuration data interface.
       First, split the PR_SAMPLE_FLAGS property into four individual
       Boolean properties. */

    pprop = rgprop + (MAX_LOGON_PROPERTIES - TEMP_LOGON_PROPERTIES);

    if (rgprop[PropIndex(PR_SAMPLE_FLAGS)].ulPropTag == PR_SAMPLE_FLAGS)
        flT = rgprop[PropIndex(PR_SAMPLE_FLAGS)].Value.ul;
    else
    {
        /*  The Peer to Peer and Save Data flags default to TRUE */
        rgprop[PropIndex(PR_SAMPLE_FLAGS)].ulPropTag = PR_SAMPLE_FLAGS;
        flT = PR_SAMPLE_FLAG_PEER_TO_PEER | PR_SAMPLE_FLAG_SAVE_DATA;
        rgprop[PropIndex(PR_SAMPLE_FLAGS)].Value.ul = flT;
    }

    pprop[0].ulPropTag = PR_TEMP_PEER_TO_PEER;
    pprop[0].Value.b = !!(flT & PR_SAMPLE_FLAG_PEER_TO_PEER);
    pprop[1].ulPropTag = PR_TEMP_UI_ALWAYS;
    pprop[1].Value.b = !!(flT & PR_SAMPLE_FLAG_UI_ALWAYS);
    pprop[2].ulPropTag = PR_TEMP_LOG_EVENTS;
    pprop[2].Value.b = !!(flT & PR_SAMPLE_FLAG_LOG_EVENTS);
    pprop[3].ulPropTag = PR_TEMP_SAVE_DATA;
    pprop[3].Value.b = !!(flT & PR_SAMPLE_FLAG_SAVE_DATA);

    /* Next, make strings out of the two numeric logging parameters */

    if (rgprop[PropIndex(PR_SAMPLE_LOGHIGHWATER)].ulPropTag == PR_SAMPLE_LOGHIGHWATER)
        wsprintf(szLogHigh, "%ld",
            rgprop[PropIndex(PR_SAMPLE_LOGHIGHWATER)].Value.l);
    else
        szLogHigh[0] = '\0';

    pprop[4].ulPropTag = PR_TEMP_LOGHIGHWATER;
    pprop[4].Value.LPSZ = szLogHigh;

    if (rgprop[PropIndex(PR_SAMPLE_LOGLOWWATER)].ulPropTag == PR_SAMPLE_LOGLOWWATER)
        wsprintf(szLogLow, "%ld",
            rgprop[PropIndex(PR_SAMPLE_LOGLOWWATER)].Value.l);
    else
        szLogLow[0] = '\0';

    pprop[5].ulPropTag = PR_TEMP_LOGLOWWATER;
    pprop[5].Value.LPSZ = szLogLow;

    /* Now make a temporary property interface and feed it the data. */

    sc = CreateIProp((LPIID) &IID_IMAPIPropData,
            lpXPDialog->AllocateBuffer,
            lpXPDialog->AllocateMore,
            lpXPDialog->FreeBuffer,
            lpXPDialog->lpMalloc,
            &pTempProps);

    if (FAILED(sc))
    {
        DebugTrace("CreateIProp failed in ScDoLogonDlg.\n");
        goto ret;
    }

    hr = pTempProps->lpVtbl->SetProps(pTempProps, MAX_LOGON_PROPERTIES, rgprop, NULL);

    if (HR_FAILED(hr))
    {
        sc = GetScode(hr);
        DebugTrace("SetProps failed in ScDoLogonDlg.\n");
        goto ret;
    }

    /* If the UI_READONLY flag is set, then we are being called from
       the SettingDialog() method in Read Only mode.  Disable all of
       the controls on the three property sheets, else Enable them.  */

    if (lpXPDialog->ulFlags & UI_READONLY)
    {
        /* Disable controls on the General PropSheet */
        rgdtpage[0].lpctl[1].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[0].lpctl[2].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[0].lpctl[3].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[0].lpctl[4].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[0].lpctl[6].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[0].lpctl[8].ulCtlFlags &= ~DT_EDITABLE;

        /* Disable controls on the Peer-To-Peer PropSheet */
        rgdtpage[1].lpctl[2].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[1].lpctl[4].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[1].lpctl[6].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[1].lpctl[8].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[1].lpctl[10].ulCtlFlags &= ~DT_EDITABLE;

        /* Disable controls on the Logging PropSheet */
        rgdtpage[2].lpctl[2].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[2].lpctl[4].ulCtlFlags &= ~DT_EDITABLE;
        rgdtpage[2].lpctl[6].ulCtlFlags &= ~DT_EDITABLE;
    }
    else
    {
        /* Enable controls on the General PropSheet */
        rgdtpage[0].lpctl[1].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[0].lpctl[2].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[0].lpctl[3].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[0].lpctl[4].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[0].lpctl[6].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[0].lpctl[8].ulCtlFlags |= DT_EDITABLE;

        /* Enable controls on the Peer-To-Peer PropSheet */
        rgdtpage[1].lpctl[2].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[1].lpctl[4].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[1].lpctl[6].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[1].lpctl[8].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[1].lpctl[10].ulCtlFlags |= DT_EDITABLE;

        /* Enable controls on the Logging PropSheet */
        rgdtpage[2].lpctl[2].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[2].lpctl[4].ulCtlFlags |= DT_EDITABLE;
        rgdtpage[2].lpctl[6].ulCtlFlags |= DT_EDITABLE;
    }

    /* Check if we are being called at Logon time or from a call to
       SettingsDialog().  If from SettingsDialog() then disable the
       IDE_EMAIL_TYPE edit control reguardless of ulFlags setting. */

    if (!lpXPDialog->fLogon)
        rgdtpage[1].lpctl[4].ulCtlFlags &= ~DT_EDITABLE;

    /* Create the display table for the logon dialog. It is based on dialog
       resources plus the static information at the head of this module. */

    hr = BuildDisplayTable(
        lpXPDialog->AllocateBuffer,
        lpXPDialog->AllocateMore,
        lpXPDialog->FreeBuffer,
        lpXPDialog->lpMalloc,
        lpXPDialog->hInst,
        sizeof(rgdtpage) / sizeof(DTPAGE),
        rgdtpage,
        0,
        &ptable,
        NULL);

    if (HR_FAILED(hr))
    {
        sc = GetScode(hr);
        DebugTrace("BuildDisplayTable failed in ScDoLogonDlg.\n");
        goto ret;
    }

    /* Run the dialog */

    hr = lpXPDialog->lpMAPISup->lpVtbl->DoConfigPropsheet(lpXPDialog->lpMAPISup,
        (ULONG) lpXPDialog->hwnd,
        0,
        szDialogTitle,
        ptable,
        (LPMAPIPROP) pTempProps,
        1);

    if (HR_FAILED(hr))
    {
        sc = GetScode(hr);
        DebugTrace("DoConfigPropsheet failed in ScDoLogonDlg.\n");
        goto ret;
    }

    /* Retrieve the altered data */

    (*(lpXPDialog->FreeBuffer)) (rgprop);
    rgprop = NULL;

    hr = pTempProps->lpVtbl->GetProps(pTempProps, ptags, 0, /* ansi */
            &cprop, &rgprop);

    if (HR_FAILED(hr))
    {
        sc = GetScode(hr);
        DebugTrace("GetProps failed in ScDoLogonDlg.\n");
        goto ret;
    }

    Assert(cprop == MAX_LOGON_PROPERTIES);

    /* Supply default values for any unfilled fields.
       Also ensure that the directory name fields end in a slash. */

    for (i = 0; i < MAX_LOGON_PROPERTIES - TEMP_LOGON_PROPERTIES; ++i)
    {
        ULONG tagT = rgprop[i].ulPropTag;
        UINT cch;
        LPTSTR lpszT;

        if (tagT != ptags->aulPropTag[i])
        {
            /* This property did not make it. Integer properties default
               to 0, and string properties default to empty. */

            tagT = ptags->aulPropTag[i];

            if (PROP_TYPE(tagT) == PT_LONG)
            {
                rgprop[i].Value.l = 0;
                rgprop[i].ulPropTag = tagT;
            }
            else if (PROP_TYPE(tagT) == PT_TSTRING)
            {
                if (FAILED((*(lpXPDialog->AllocateMore)) (sizeof(TCHAR),
                            rgprop, (LPVOID FAR *) &rgprop[i].Value.LPSZ)))
                {
                    rgprop[i].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(tagT));
                    rgprop[i].Value.err = MAPI_E_NOT_ENOUGH_MEMORY;
                }
                else
                {
                    rgprop[i].Value.LPSZ[0] = '\0';
                    rgprop[i].ulPropTag = tagT;
                }
            }
        }
        else if (tagT == PR_SAMPLE_INBOUND_DIR ||
                tagT == PR_SAMPLE_OUTBOUND_DIR ||
            tagT == PR_SAMPLE_DIRECTORY)
        {
            cch = lstrlen(rgprop[i].Value.LPSZ);

            if (cch && rgprop[i].Value.LPSZ[cch - 1] != '\\')
            {
                if (FAILED((*(lpXPDialog->AllocateMore)) (
                            (cch + 2) * sizeof(TCHAR), rgprop, (LPVOID FAR *) &lpszT)))
                {
                    rgprop[i].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(tagT));
                    rgprop[i].Value.err = MAPI_E_NOT_ENOUGH_MEMORY;
                }
                else
                {
                    lstrcpy(lpszT, rgprop[i].Value.LPSZ);
                    lpszT[cch] = '\\';
                    lpszT[cch + 1] = '\0';
                    rgprop[i].Value.LPSZ = lpszT;
                }
            }
        }
    }

    /* Reassemble the PR_SAMPLE_FLAGS property */

    pprop = rgprop + (MAX_LOGON_PROPERTIES - TEMP_LOGON_PROPERTIES);
    flT = 0L;

    if (pprop[0].ulPropTag == PR_TEMP_PEER_TO_PEER && pprop[0].Value.b)
        flT |= PR_SAMPLE_FLAG_PEER_TO_PEER;
    if (pprop[1].ulPropTag == PR_TEMP_UI_ALWAYS && pprop[1].Value.b)
        flT |= PR_SAMPLE_FLAG_UI_ALWAYS;
    if (pprop[2].ulPropTag == PR_TEMP_LOG_EVENTS && pprop[2].Value.b)
        flT |= PR_SAMPLE_FLAG_LOG_EVENTS;
    if (pprop[3].ulPropTag == PR_TEMP_SAVE_DATA && pprop[3].Value.b)
        flT |= PR_SAMPLE_FLAG_SAVE_DATA;

    rgprop[PropIndex(PR_SAMPLE_FLAGS)].ulPropTag = PR_SAMPLE_FLAGS;
    rgprop[PropIndex(PR_SAMPLE_FLAGS)].Value.ul = flT;

    /* Reassemble the high and low water marks for logging */

    if (rgprop[PropIndex(PR_TEMP_LOGHIGHWATER)].ulPropTag == PR_TEMP_LOGHIGHWATER)
        rgprop[PropIndex(PR_SAMPLE_LOGHIGHWATER)].Value.l =
            atoi(rgprop[PropIndex(PR_TEMP_LOGHIGHWATER)].Value.LPSZ);

    if (rgprop[PropIndex(PR_TEMP_LOGLOWWATER)].ulPropTag == PR_TEMP_LOGLOWWATER)
        rgprop[PropIndex(PR_SAMPLE_LOGLOWWATER)].Value.l =
            atoi(rgprop[PropIndex(PR_TEMP_LOGLOWWATER)].Value.LPSZ);

    /* Return the new property array */

    *(lpXPDialog->lppPropArray) = rgprop;
    rgprop = NULL;              /* do not free this in cleanup code */
    sc = S_OK;                  /* no warnings */

#ifdef DEBUG_DLGITEMS
    DLGDump(lpXPDialog);        /* dump debug data. */
#endif

ret:
    DebugTraceSc(ScDoLogonDialog(), sc);

    UlRelease(ptable);
    UlRelease(pTempProps);

    lpXPDialog->FreeBuffer(rgprop);

    return sc;
}

#ifdef DEBUG_DLGITEMS

/*
 -  DLGDump
 -
 *  Purpose:
 *      Called by ScDoLogonDlg to dump the contents
 *      of the logon property array to the debugger.
 *
 *  Parameters:
 *      lpXPDialog      Pointer to logon parameter structure
 *
 *  Returns:
 *      none.
 *
 *  Operation:
 *      Walks through the property array inside the logon structure, and
 *      dumps its contents through DebugTrace().
 *
 *      This function only exists in the debug version.
 */

static void
DLGDump(LPXPDLG lpXPDialog)
{

    ULONG ulT, flT, ptT, ptT2;
    LPSPropValue lpPropArray;
    LPSPropTagArray lpPTArray;
    SPropValue propT;
    ULONG ulCount;

    if (!lpXPDialog)
    {
        DebugTrace("lpXPDialog is NULL");
        goto ret;
    }

    DebugTrace("LogonStruct:");

    lpPropArray = *(lpXPDialog->lppPropArray);
    lpPTArray = lpXPDialog->lpPTArray;

    if (!lpPropArray)
    {
        DebugTrace("NULL\n");
        goto ret;
    }

    ulCount = lpPTArray->cValues;

    for (ulT = 0; ulT < ulCount; ulT++)
    {
        propT = lpPropArray[ulT];
        ptT = lpPTArray->aulPropTag[ulT];
        ptT2 = propT.ulPropTag;

        switch (ptT)
        {
        case PR_SAMPLE_DISPLAY_NAME:
            DebugTrace("  DisplayName: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_EMAIL_ADDR_TYPE:
            DebugTrace("  EmailAddressType: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_EMAIL_ADDRESS:
            DebugTrace("  EmailAddress: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_INBOUND_DIR:
            DebugTrace("  Inbound Directory: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_OUTBOUND_DIR:
            DebugTrace("  Outbound Directory: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_FILENAME:
            DebugTrace("  WGApp Filename: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_DIRECTORY:
            DebugTrace("  WGApp Directory: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_FLAGS:

            flT = (ptT == ptT2) ? propT.Value.ul : 0;

            DebugTrace("  Peer-To-Peer: %s", (flT & PR_SAMPLE_FLAG_PEER_TO_PEER) ? "TRUE" : "FALSE");
            DebugTrace("  Always show UI: %s", (flT & PR_SAMPLE_FLAG_UI_ALWAYS) ? "TRUE" : "FALSE");
            DebugTrace("  Log Events: %s", (flT & PR_SAMPLE_FLAG_LOG_EVENTS) ? "TRUE" : "FALSE");
            DebugTrace("  Save Logon Input: %s", (flT & PR_SAMPLE_FLAG_SAVE_DATA) ? "TRUE" : "FALSE");
            break;

        case PR_SAMPLE_LOGFILE:
            DebugTrace("  Log File Name: %s", (ptT == ptT2) ? propT.Value.LPSZ : "NULL");
            break;

        case PR_SAMPLE_LOGHIGHWATER:
            DebugTrace("  Log File HighWater: %ld", (ptT == ptT2) ? propT.Value.ul : 0);
            break;

        case PR_SAMPLE_LOGLOWWATER:
            DebugTrace("  Log File LowWater: %ld", (ptT == ptT2) ? propT.Value.ul : 0);
            break;

        default:
            DebugTrace("  Unknown Logon Property: %08X", ptT);
            break;
        }
    }
ret:
    return;
}

#endif                          /* DEBUG_DLGITEMS */


XPDIALOG.H

/*
 -  X P T L O G O N . H
 -
 *  Purpose:
 *      Definitions, typedefs and prototypes used by the Sample Transport
 *      Provider logon UI.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Size of largest possible text edit in our logon dialog. */

#define MAX_TEXTFIELD   255

/* Structure passed into the logon dialog code from TransportLogon
   and propagated in/out through LPARAM at WM_INITDIALOG time. */

typedef struct _XPDLG
{
    HINSTANCE           hInst;          /* Instance of my DLL           */
    HWND                hwnd;           /* Handle of my parent window   */
    LPSPropValue        *lppPropArray;  /* Property array pointer       */
    LPSPropTagArray     lpPTArray;      /* List of property tags        */
    LPALLOCATEBUFFER    AllocateBuffer; /* MAPIAllocateBuffer function  */
    LPALLOCATEMORE      AllocateMore;   /* MAPIAllocateMore function    */
    LPFREEBUFFER        FreeBuffer;     /* MAPIFreeBuffer function      */
    LPMALLOC            lpMalloc;       /* IMalloc object               */
    LPMAPISUP           lpMAPISup;      /* Transport support object     */
    BOOL                fLogon;         /* Called at Logon time         */
    ULONG               ulFlags;        /* 0 or UI_READONLY             */
} XPDLG, * LPXPDLG;

/* Create a logon dialog. Return an error if unable to do so. Any
   other result from the dialog is communicated through XPDLG. */

SCODE ScDoLogonDlg (LPXPDLG lpXPDialog);


XPGUID.C

/*
 -  X P G U I D . C
 -
 *  Purpose:
 *      This file is used to build xpguid.obj, which contains the binary
 *      representation of several MAPI guid's.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#define USES_IID_IMAPIProp
#define USES_IID_IMessage
#define USES_IID_IMAPIStatus
#define USES_IID_IXPLogon
#define USES_IID_IXPProvider
#define USES_IID_IMAPITable
#define USES_IID_IMAPITableData
#define USES_IID_IMAPIPropData

#ifdef _WIN32                   /* Must include WINDOWS.H on Win32 */
#ifndef _WINDOWS_
#define INC_OLE2                /* Get the OLE2 stuff */
#define INC_RPC                 /* harmless on Windows NT; Windows 95 needs it */
#define _INC_OLE                /* Windows 95 will include OLE1 without this */
#include <windows.h>
#include <ole2.h>
#endif
#endif

#ifdef WIN16
#include <compobj.h>
#endif

#define INITGUID
#include <initguid.h>

#include "mapiguid.h"

DEFINE_OLEGUID(IID_IUnknown, 0x00000000L, 0, 0);
DEFINE_OLEGUID(IID_IStream, 0x0000000cL, 0, 0);


XPLOGGER.C

/*
 -  X P L O G G E R . C
 -
 *  Purpose:
 *      Code to support the Log File for the Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"

static HANDLE hLogHandle = INVALID_HANDLE_VALUE;
static DWORD dwLogLowWater = 0;
static DWORD dwLogHiWater = (DWORD) -1;
static TCHAR chLogFileName[MAX_PATH];

#define BUFSIZE 255             /* Size of buffer to use for LogPrintf */
static TCHAR _xpbuf[BUFSIZE];

/*
 -  InitTransportLog
 -
 *  Purpose:
 *      Called by TransportLogon() logic to start logging.
 *
 *  Parameters:
 *      lpxpl               Session object. We'll need it
 *                          to get the logfile name and
 *                          size constraints.
 *      ulFlags             Flags. Not used at present.
 *
 *  Returns:
 *      hLogHandle          Handle of open Log File.
 *      dwLogLowWater       "Low Water Mark" of Log File.
 *      dwLogHiWater        "High Water Mark" of Log File.
 *      chLogFileName       Filename of open Log File.
 *
 *  Operation:
 *      If a file is already open, this transport session doesn't want to log
 *      events, or the filename given is null, return to caller.  Open the
 *      file and save the filename into chLogFileName. Store water marks into
 *      the appropriate variables. Save the handle.  We grab the Critical
 *      Section here because it is possible to Init the logging at times
 *      other than TransportLogon.  This makes it necessary to protect this
 *      section of code.
 */

void
InitTransportLog(LPXPL lpxpl, ULONG ulFlags)
{
    LPSPropValue lpPropArray = lpxpl->lpPropArray;
    ULONG ulSessFlags;
    LPTSTR lpszFileName;
    HANDLE hTempHandle;
    DWORD dwOffset;
    ULONG ulLow, ulHigh;

    /*  Use the critical section here */

    EnterCriticalSection(&(lpxpl->lpxppParent->csTransport));

    /*  Are we already logging? If so there's nothing to do here. */

    if (hLogHandle != INVALID_HANDLE_VALUE)
    {
        DebugTrace("Already logging to %s\n", chLogFileName);
        goto ret;
    }

    /*  Logging isn't on. Would this session like it turned on? */

    ulSessFlags = (ArrayIndex(PR_SAMPLE_FLAGS, lpPropArray)).Value.ul;
    if (!(ulSessFlags & PR_SAMPLE_FLAG_LOG_EVENTS))
    {
        DebugTrace("Log flag isn't on, logfile won't be opened\n");
        goto ret;
    }

    /*  We're going to log if we can. Get the filename and see if it
        makes any sense. If so, try to open it. */

    lpszFileName = (ArrayIndex(PR_SAMPLE_LOGFILE, lpPropArray)).Value.LPSZ;
    if (!lpszFileName || !*lpszFileName)
    {
        DebugTrace("LogFile name wasn't given, logfile won't be opened\n");
        goto ret;
    }

    hTempHandle = CreateFile(lpszFileName,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_FLAG_WRITE_THROUGH | FILE_FLAG_RANDOM_ACCESS,
        NULL);

    if (hTempHandle == INVALID_HANDLE_VALUE)
    {
        DebugTrace("Error %x opening logfile\n", GetLastError());
        goto ret;
    }

    /*  The log file is open. Seek to the end so we can resume logging. */

    dwOffset = SetFilePointer(hTempHandle, 0L, NULL, FILE_END);
    if (dwOffset == -1)
    {
        DebugTrace("Error %x seeking to end of logfile\n", GetLastError());
        CloseHandle(hTempHandle);
        goto ret;
    }

    /*  Get the relevant data into the local structures. */

    lstrcpy(chLogFileName, lpszFileName);
    hLogHandle = hTempHandle;

    ulLow = (ArrayIndex(PR_SAMPLE_LOGLOWWATER, lpPropArray)).Value.ul;
    ulHigh = (ArrayIndex(PR_SAMPLE_LOGHIGHWATER, lpPropArray)).Value.ul;
    if (ulHigh)
    {
        dwLogHiWater = ulHigh * 1024;
        dwLogLowWater = ulLow * 1024;
    }

ret:

    /*  Release the critical section */

    LeaveCriticalSection(&(lpxpl->lpxppParent->csTransport));

    return;
}

/*
 -  DeInitTransportLog
 -
 *  Purpose:
 *      Called by DeinitTransport() to turn off all session logging.
 *
 *  Parameters:
 *      ulFlags             Flags. Not used at present.
 *
 *  Returns:
 *      none.
 *
 *  Operation:
 *      If we have a file open, try to close it. Reinitialize structures
 *      whether we succeeded or not.
 */

void
DeInitTransportLog(ULONG ulFlags)
{
    if (hLogHandle != INVALID_HANDLE_VALUE)
    {
        if (!CloseHandle(hLogHandle))
            DebugTrace("Unable to close logfile %s\n", chLogFileName);

        hLogHandle = INVALID_HANDLE_VALUE;
        dwLogHiWater = (DWORD) -1L;
        dwLogLowWater = 0;
        *chLogFileName = '\0';
    }
}

/*
 -  PrintfTransportLog
 -
 *  Purpose:
 *      Called by various parts of the Transport to log information (if
 *      logging is currently active).
 *
 *  Parameters:
 *      fmt                 Start of variable argument list.
 *
 *  Returns:
 *      none.
 *
 *  Operation:
 *      Format the input data and call PutsTransportLog to write to logfile.
 *      Current maximum length permitted is 255 characters (and is NOT
 *      protected!).
 */

void __cdecl
PrintfTransportLog(LPTSTR fmt,...)
{
    va_list marker;

    va_start(marker, fmt);

    if (hLogHandle != INVALID_HANDLE_VALUE)
    {
        if (wvsprintf((LPTSTR) _xpbuf, fmt, marker))
            PutsTransportLog((LPTSTR) _xpbuf);
    }

    va_end(marker);
}

/*
 -  PutsTransportLog
 -
 *  Purpose:
 *      Called mostly by PrintfTransportLog. Writes a single line to the
 *      logfile if it's open.
 *
 *  Parameters:
 *      str                 String that wants to be written.
 *
 *  Returns:
 *      none.
 *
 *  Operation:
 *      If we have set a high water mark, get the current logfile size. If it
 *      exceeds the high water mark, move back by the amount of the low water
 *      mark, find a line ending, read everything to the end, write to the
 *      front of the file, and truncate to that location.
 *
 *      Create a line consisting of the date and time, the input string, and
 *      line ending characters. Write this line to the logfile.
 */

void
PutsTransportLog(LPTSTR str)
{
    DWORD cWritten;
    SYSTEMTIME stOurTime;
    TCHAR tchData[512];
    HGLOBAL hGlobal = (HGLOBAL) 0L;
    LPVOID lpvBuf = NULL;

    if (hLogHandle == INVALID_HANDLE_VALUE)
        return;

    /*  See if we want to be adjusting the file size. */

    if (dwLogHiWater != -1)
    {
        DWORD finfo;

        /*  Get the current file size */

        finfo = GetFileSize(hLogHandle, NULL);

        if (finfo == 0xFFFFFFFF)
        {
            DebugTrace("Error %x getting logfile info\n", GetLastError());
            DeInitTransportLog(LOG_DEINIT_HURRY);
            return;
        }

        /*  Is it time to trim back? If so, let's do it. */

        if (finfo > dwLogHiWater)
        {
            DWORD dwOffset;
            ULONG ulT, ulLowWater;
            char *lpbT;

            /*  Allocate a buffer to read dwLogLowWater bytes. If
                dwLogLowWater > 32K, use 32K. */

            ulLowWater = (dwLogLowWater > 32000L ? 32000L : dwLogLowWater);
            if (ulLowWater == 0L)
                goto Truncate;

            hGlobal = GlobalAlloc(GPTR, (UINT) ulLowWater);
            if (hGlobal == NULL)
            {
                DebugTrace("Attempt to GlobalAlloc %l bytes for lowwater buffer failed\n", ulLowWater);
                goto Truncate;
            }

            lpvBuf = (LPVOID) GlobalLock(hGlobal);
            if (lpvBuf == NULL)
            {
                DebugTrace("Attempt to Lock allocated memory buffer failed\n");
                goto Truncate;
            }

            /*  Reposition the pointer back by the size of ulLowWater. */

            dwOffset = SetFilePointer(hLogHandle, 0L - ulLowWater, NULL, FILE_CURRENT);
            if (dwOffset == -1)
            {
                DebugTrace("Error %x seeking to low water mark\n", GetLastError());
                DeInitTransportLog(LOG_DEINIT_HURRY);
                return;
            }

            /*  Read (ulLowWater) bytes from that position. */

            if (!ReadFile(hLogHandle, lpvBuf, ulLowWater, &cWritten, NULL))
            {
                DebugTrace("Attempt to read %l bytes for low water copy failed, error = %lx\n", ulLowWater, GetLastError());
                goto Truncate;
            }

            Assert(cWritten == ulLowWater);

            /*  Find the first newline from that point */

            for (ulT = 0, lpbT = (char *)lpvBuf; ulT < ulLowWater; lpbT++, ulT++)
            {
                if (*lpbT == '\n')
                    break;
            }

            /* Did the search fail? If so, truncate the file to zero. */

            if (ulT == ulLowWater)
            {
                ulLowWater = 0L;
Truncate:
                /*  We failed for some reason. Deallocate the buffer if
                    any and truncate the file to ulLowWater. */

                if (lpvBuf)
                    GlobalUnlock(hGlobal);
                if (hGlobal)
                    (void)GlobalFree(hGlobal);

                dwOffset = SetFilePointer(hLogHandle, ulLowWater, NULL, FILE_BEGIN);
                if (dwOffset == -1)
                {
                    DebugTrace("Error %x seeking to low water mark\n", GetLastError());
                    DeInitTransportLog(LOG_DEINIT_HURRY);
                    return;
                }
                goto Adjusted;
            }

            /*  We found a newline. Skip over it and write the remaining
                data from the file start, if any. */

            ulT++;
            lpbT++;

            dwOffset = SetFilePointer(hLogHandle, 0L, NULL, FILE_BEGIN);
            if (dwOffset == -1)
            {
                if (lpvBuf)
                    GlobalUnlock(hGlobal);
                if (hGlobal)
                    (void)GlobalFree(hGlobal);

                DebugTrace("Error %x seeking to file start\n", GetLastError());
                DeInitTransportLog(LOG_DEINIT_HURRY);
                return;
            }

            if (ulT < ulLowWater)
            {
                cWritten = 0;
                WriteFile(hLogHandle, (LPVOID) lpbT, (DWORD) (ulLowWater - ulT), &cWritten, NULL);
                Assert((cWritten + ulT) == ulLowWater);
            }

            GlobalUnlock(hGlobal);
            GlobalFree(hGlobal);

Adjusted:
            if (!SetEndOfFile(hLogHandle))
            {
                DebugTrace("Error %x setting EOF\n", GetLastError());
                DeInitTransportLog(LOG_DEINIT_HURRY);
                return;
            }
        }
    }

    /* We can't handle more than 490 characters on a input line. */

    Assert(lstrlen(str) < 490L);

    GetLocalTime(&stOurTime);
    wsprintf(tchData, TEXT("%04d/%02d/%02d %02d:%02d:%02d %s\r\n"),
        stOurTime.wYear, stOurTime.wMonth, stOurTime.wDay,
        stOurTime.wHour, stOurTime.wMinute, stOurTime.wSecond,
        str);

    WriteFile(hLogHandle, tchData, (DWORD) (lstrlen(tchData) * sizeof(TCHAR)), &cWritten, NULL);
}


XPLOGGER.H

/*
 -  X P L O G G E R . H
 -
 *  Purpose:
 *      Logfile definitions for Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Start up the logging function if indicated and not already
   started on another session. Called at logon time. */

void InitTransportLog (LPXPL lpSession, ULONG ulFlags);

/* Shut down the logging function if started. Called at
   Deinit time. */

void DeInitTransportLog (ULONG ulFlags);

/* One flag value is defined -- a "do it quickly" bit. */

#define LOG_DEINIT_HURRY (ULONG)(0x00000001)

/* Log a formatted line. Always called; determination of
   whether the file is open happens at the lower level. */

void __cdecl PrintfTransportLog (LPTSTR fmt,...);

/* Log a line of text. Always called. */

void PutsTransportLog (LPTSTR str);


XPOPTION.C

/*
 -  X P O P T I O N . C
 -
 *  Purpose:
 *      Defines a wrapped implementation of an IMAPIProp interface
 *      to be used by ScMsgOptionCallBack() and ScRecipOptionCallBack()
 *      to wrap the IMAPIProp passed in.  This object is wrapped so we
 *      can effect the behavior of the OpenProperty() call.  Specifically,
 *      we wish to put up a Property Sheet when the user tries to open
 *      the PR_DETAILS_TABLE property.
 *
 *      This file overloads the OpenProperty() method on the Wrapped
 *      IMAPIProp in order to slam properties from a Display Table
 *      into the underlying message object.  It also creates the
 *      Display Table used by IMAPISession::MessageOptions().
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include "xpresrc.h"
#include "xpoption.h"
#include <mapiutil.h>

/* Generic control structures */

DTBLPAGE dtblMsgOptPage =
{sizeof(DTBLPAGE), 0, 0, 0};
DTBLPAGE dtblRecipOptPage =
{sizeof(DTBLPAGE), 0, 0, 0};

/* Display table control structures for the General property sheet. */

DTBLCHECKBOX chkPerMsgDefer =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_SAMPLE_PER_MSG_DEFER};
DTBLCHECKBOX chkPerRecipDefer =
{sizeof(DTBLCHECKBOX), fMapiUnicode,
    PR_SAMPLE_PER_RECIP_DEFER};

DTCTL rgdtctlMsgOpt[] =
{
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblMsgOptPage},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_PER_MSG_DEFER, &chkPerMsgDefer}
};

DTCTL rgdtctlRecipOpt[] =
{
    {DTCT_PAGE, 0, NULL, 0, NULL, 0, &dtblRecipOptPage},
    {DTCT_CHECKBOX, DT_EDITABLE, NULL, 0, NULL, IDC_PER_RECIP_DEFER, &chkPerRecipDefer}
};

/* Display table pages */

DTPAGE rgdtpageMsgOpt[] =
{
    {
        sizeof(rgdtctlMsgOpt) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(PerMsgOpt),
        "",
        rgdtctlMsgOpt
    }
};

DTPAGE rgdtpageRecipOpt[] =
{
    {
        sizeof(rgdtctlRecipOpt) / sizeof(DTCTL),
        (LPTSTR) MAKEINTRESOURCE(PerRecipOpt),
        "",
        rgdtctlRecipOpt
    }
};

/*
 *  WMProp jump table is defined here...
 */

WMPROP_Vtbl vtblOPT =
{
    WMPROP_QueryInterface,
    WMPROP_AddRef,
    WMPROP_Release,
    WMPROP_GetLastError,
    WMPROP_SaveChanges,
    WMPROP_GetProps,
    WMPROP_GetPropList,
    WMPROP_OpenProperty,
    WMPROP_SetProps,
    WMPROP_DeleteProps,
    WMPROP_CopyTo,
    WMPROP_CopyProps,
    WMPROP_GetNamesFromIDs,
    WMPROP_GetIDsFromNames,
};

/*
 *  Private functions prototypes
 */

SCODE ScNewWrappedMAPIProp(
    LPMAPIPROP lpMAPIProp,
    HINSTANCE hInst,
    LPMALLOC lpMalloc,
    ULONG ulType,
    ULONG cbOptionData,
    LPBYTE lpbOptionData,
    LPMAPISUP lpMAPISup,
    LPWMPROP * lppWMProp);

OPTIONCALLBACK ScOptionDataCallBack;

/*
 -  ScOptionDataCallBack
 -
 *  Purpose:
 *      Gets called by MAPI to wrap the source IMessage in
 *      preparation to Display Table calls.
 *
 *  Parameters:
 *      hInst               - Instance of myself from LoadLibrary call
 *      lpMalloc            - OLE 2.0 style allocator
 *      ulFlags             - OPTION_TYPE_MESSAGE | OPTION_TYPE_RECIPIENT
 *      cbOptionData        - Size of OptionData I sent at Register time
 *      lpbOptionData       - OptionData I sent at Register time
 *      lpMAPISup           - A support object for mem routines
 *      lpDataSource        - Underlying IMessage object
 *      lppWrappedSource    - Returned wrapped version of lpDataSource
 *      lppszErrorMsg       - Context sensitive error msg for MAPI
 *      lppszErrorComponent - Help filename if one exists
 *      lpulErrorContext    - Context in Help File
 *
 *  Returns:
 *      sc                  - Scode of failure if any
 */

STDINITMETHODIMP_(SCODE)
ScOptionDataCallBack(
    HINSTANCE hInst,
    LPMALLOC lpMalloc,
    ULONG ulFlags,
    ULONG cbOptionData,
    LPBYTE lpbOptionData,
    LPMAPISUP lpMAPISup,
    LPMAPIPROP lpDataSource,
    LPMAPIPROP FAR * lppWrappedSource,
    LPMAPIERROR FAR * lppMapiError )
{
    SCODE sc;
    LPWMPROP lpWMProp = NULL;

    /* Do Parameter Validation Now */

#ifdef _WIN32
    if (!hInst)
#else
    if (hInst < HINSTANCE_ERROR)
#endif
    {
        DebugTrace("ScOptionDataCallBack() - Invalid hInst");
        return E_INVALIDARG;
    }

    if (ulFlags & (~(OPTION_TYPE_MESSAGE | OPTION_TYPE_RECIPIENT)))
    {
        DebugTrace("ScOptionDataCallBack() - Unknown ulFlags!");
        return E_INVALIDARG;
    }

    if (cbOptionData && !lpbOptionData)
    {
        DebugTrace("ScOptionDataCallBack() - Invalid lpbOptionData");
        return E_INVALIDARG;
    }

    if (cbOptionData)
    {
        if (IsBadReadPtr(lpbOptionData, (UINT) cbOptionData))
        {
            DebugTrace("ScOptionDataCallBack() - Invalid lpbOptionData");
            return E_INVALIDARG;
        }
    }

    if (!lpMAPISup || IsBadReadPtr(lpMAPISup, sizeof(LPVOID)))
    {
        DebugTrace("ScOptionDataCallBack() - Invalid lpMAPISup");
        return E_INVALIDARG;
    }

    if (!lpDataSource || IsBadReadPtr(lpDataSource, sizeof(LPVOID)))
    {
        DebugTrace("ScOptionDataCallBack() - Invalid lpDataSource");
        return E_INVALIDARG;
    }

    if (!lppWrappedSource || IsBadWritePtr(lppWrappedSource, sizeof(LPVOID)))
    {
        DebugTrace("ScOptionDataCallBack() - Invalid lppWrappedSource");
        return E_INVALIDARG;
    }

    if (!lppMapiError || IsBadWritePtr(lppMapiError, sizeof(LPMAPIERROR)))
    {
        DebugTrace("ScOptionDataCallBack() - Invalid lppMapiError");
        return E_INVALIDARG;
    }

    *lppWrappedSource = NULL;
    *lppMapiError = NULL;

    /* Everything is OK, Lets build the Wrapped Source now. */

    sc = ScNewWrappedMAPIProp(lpDataSource, hInst, lpMalloc, ulFlags,
            cbOptionData, lpbOptionData, lpMAPISup, &lpWMProp);

    if (sc)
        goto ret;

    *lppWrappedSource = (LPMAPIPROP) lpWMProp;

ret:
    return sc;
}

/*
 -  ScNewWrappedMAPIProp
 -
 *  Purpose:
 *      Creates a wrapped Per-Message or Per-Recipient Options
 *      property interface to pass back to caller.  All methods
 *      call through to the base class except OpenProperty(),
 *      which intercepts PR_DETAILS_TABLE and creates a Display
 *      Table to pass back to the caller.
 *
 *  Parameters:
 *      lpMAPIProp          - The Property Interface to wrapper (an IMsg)
 *      lpXPLogon           - The opaque XP Logon object
 *      hInst               - My hInst of this DLL
 *      ulType              - Either OPTION_TYPE_MESSAGE or _RECIPIENT
 *      cbOptionData        - Size of my OptionData blob
 *      lpbOptionData       - My OptionData blob
 *      lpMAPISup           - Needed for memory alloc routines
 *      lppWMProp           - Returned wrapped IMAPIProp object
 *
 *  Returns:
 *      sc                  - Indicating Success/Failure
 */

SCODE
ScNewWrappedMAPIProp(
    LPMAPIPROP lpMAPIProp,
    HINSTANCE hInst,
    LPMALLOC lpMalloc,
    ULONG ulType,
    ULONG cbOptionData,
    LPBYTE lpbOptionData,
    LPMAPISUP lpMAPISup,
    LPWMPROP FAR * lppWMProp)
{
    HRESULT hr;
    SCODE sc;
    LPWMPROP lpWMProp = NULL;
    LPBYTE lpb = NULL;
    LPALLOCATEBUFFER lpAllocBuff = NULL;
    LPALLOCATEMORE lpAllocMore = NULL;
    LPFREEBUFFER lpFreeBuff = NULL;

    hr = lpMAPISup->lpVtbl->GetMemAllocRoutines(lpMAPISup,
        &lpAllocBuff, &lpAllocMore, &lpFreeBuff);

    if (hr)
    {
        sc = GetScode(hr);
        goto ret;
    }

    sc = (*lpAllocBuff) (sizeof(WMPROP), &lpWMProp);

    if (sc)
    {
        goto ret;
    }

    lpWMProp->lpVtbl = &vtblOPT;
    lpWMProp->lcInit = 1;
    lpWMProp->lpAllocBuff = lpAllocBuff;
    lpWMProp->lpAllocMore = lpAllocMore;
    lpWMProp->lpFreeBuff = lpFreeBuff;
    lpWMProp->hInst = hInst;
    lpWMProp->lpMalloc = lpMalloc;
    lpWMProp->ulType = ulType;
    lpWMProp->lpMAPIProp = lpMAPIProp;

    if (cbOptionData)
    {
        sc = (*lpAllocMore) (cbOptionData, lpWMProp, &lpb);

        if (sc)
        {
            goto ret;
        }

        memcpy(lpb, lpbOptionData, (UINT) cbOptionData);

        lpWMProp->cbOptionData = cbOptionData;
        lpWMProp->lpbOptionData = lpb;
    }

    lpMalloc->lpVtbl->AddRef(lpMalloc);
    lpMAPIProp->lpVtbl->AddRef(lpMAPIProp);

    *lppWMProp = lpWMProp;

ret:
    if (sc && lpFreeBuff)
        (*lpFreeBuff) (lpWMProp);

    return sc;
}

/*
 -  WMPROP_OpenProperty
 -
 *  Purpose:
 *      Traps a request to OpenProperty() on PR_DETAILS_TABLE so I
 *      can supply a DisplayTable for the user.  Else, passes the
 *      request to the underlying object.
 *
 */

STDMETHODIMP
WMPROP_OpenProperty(
    LPWMPROP lpWMProp,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{
    HRESULT hResult;

    /* Check to see if it has a jump table */

    if (FBadUnknown(lpWMProp))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(WMPROP_OpenProperty, hResult);
        return hResult;
    }

    /* Check to see that it's the correct jump table */

    if (lpWMProp->lpVtbl != &vtblOPT)
    {
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(WMPROP_OpenProperty, hResult);
        return hResult;
    }

    if (IsBadWritePtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, OpenProperty)))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTraceResult(WMPROP_OpenProperty, hResult);
        return hResult;
    }

    if ( ulInterfaceOptions & ~MAPI_UNICODE )
    {
        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        DebugTraceResult(WMPROP_OpenProperty, hResult);
        return hResult;
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        hResult = ResultFromScode(MAPI_E_BAD_CHARWIDTH);
        DebugTraceResult(WMPROP_OpenProperty, hResult);
        return hResult;
    }

    /* PR_DETAILS_TABLE is the secret code for "DisplayTable" */

    if (ulPropTag == PR_DETAILS_TABLE)
    {
        /* Check to see if they're expecting a table interface */

        if (memcmp(lpiid, &IID_IMAPITable, sizeof(IID)))
        {
            hResult = ResultFromScode(E_NOINTERFACE);
            goto ret;
        }

        if (lpWMProp->ulType == OPTION_TYPE_MESSAGE)
        {
            hResult = BuildDisplayTable(
                lpWMProp->lpAllocBuff,
                lpWMProp->lpAllocMore,
                lpWMProp->lpFreeBuff,
                lpWMProp->lpMalloc,
                lpWMProp->hInst,
                sizeof(rgdtpageMsgOpt) / sizeof(DTPAGE),
                rgdtpageMsgOpt,
                0,
                (LPMAPITABLE FAR *) lppUnk,
                NULL);
        }
        else
        {
            Assert(lpWMProp->ulType == OPTION_TYPE_RECIPIENT);

            hResult = BuildDisplayTable(
                lpWMProp->lpAllocBuff,
                lpWMProp->lpAllocMore,
                lpWMProp->lpFreeBuff,
                lpWMProp->lpMalloc,
                lpWMProp->hInst,
                sizeof(rgdtpageRecipOpt) / sizeof(DTPAGE),
                rgdtpageRecipOpt,
                0,
                (LPMAPITABLE FAR *) lppUnk,
                NULL);
        }
    }
    else
    {
        hResult = lpWMProp->lpMAPIProp->lpVtbl->OpenProperty(
            lpWMProp->lpMAPIProp, ulPropTag, lpiid,
            ulInterfaceOptions, ulFlags, lppUnk);
    }

ret:
    DebugTraceResult(WMPROP_OpenProperty, hResult);
    return hResult;
}

/*
 -  WMPROP_QueryInterface
 -  WMPROP_AddRef
 -  WMPROP_Release
 *
 *  Purpose:
 *      Wrapped version of the IUnknown methods.
 */

STDMETHODIMP
WMPROP_QueryInterface(
    LPWMPROP lpWMProp,
    REFIID lpiid,
    LPVOID * lppNewObj)
{
    /* Check to see if it has a jump table */

    if (FBadUnknown(lpWMProp))
    {
        DebugTraceSc(WMPROP_QueryInterface, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /* Check to see if the method is the same */

    if (WMPROP_QueryInterface != lpWMProp->lpVtbl->QueryInterface)
    {
        DebugTraceSc(WMPROP_QueryInterface, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  Call the internal prop interface */

    return lpWMProp->lpMAPIProp->lpVtbl->QueryInterface(
        lpWMProp->lpMAPIProp, lpiid, lppNewObj);
}

STDMETHODIMP_(ULONG)
WMPROP_AddRef(LPWMPROP lpWMProp)
{
    /* Check to see if it has a jump table */

    if (FBadUnknown(lpWMProp))
    {
        DebugTrace("Bad lpWMProp in WMPROP_AddRef\n");
        return 1;
    }

    /* Check to see if the method is the same */

    if (WMPROP_AddRef != lpWMProp->lpVtbl->AddRef)
    {
        DebugTrace("Bad lpWMProp in WMPROP_AddRef\n");
        return 1;
    }

    return ++lpWMProp->lcInit;
}

STDMETHODIMP_(ULONG)
WMPROP_Release(LPWMPROP lpWMProp)
{
    ULONG cRef;

    /* Check to see if it has a jump table */

    if (FBadUnknown(lpWMProp))
    {
        DebugTrace("Bad lpWMProp in WMPROP_Release\n");
        return 1;
    }

    /* Check to see if the method is the same */

    if (WMPROP_Release != lpWMProp->lpVtbl->Release)
    {
        DebugTrace("Bad lpWMProp in WMPROP_Release\n");
        return 1;
    }

    cRef = --lpWMProp->lcInit;

    if (lpWMProp->lcInit == 0)
    {
        /* Release the allocator that was handed to us */

        lpWMProp->lpMalloc->lpVtbl->Release(lpWMProp->lpMalloc);

        /* Release the underlying IMAPIProp object */

        lpWMProp->lpMAPIProp->lpVtbl->Release(lpWMProp->lpMAPIProp);

        /* Need to free the object */

        (*lpWMProp->lpFreeBuff) (lpWMProp);
    }

    return cRef;
}

/*
 -  WMPROP_GetLastError
 -  WMPROP_SaveChanges
 -  WMPROP_GetProps
 -  WMPROP_GetPropList
 -  WMPROP_OpenProperty
 -  WMPROP_SetProps
 -  WMPROP_DeleteProps
 -  WMPROP_CopyTo
 -  WMPROP_CopyProps
 -  WMPROP_GetNamesFromID
 -  WMPROP_GetIDsFromName
 *
 *  Purpose:
 *      Wrapped version of the IMAPIProp methods.
 */

STDMETHODIMP
WMPROP_GetLastError(
    LPWMPROP lpWMProp,
    HRESULT hError,
    ULONG ulFlags,
    LPMAPIERROR FAR * lppMapiError )
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, GetLastError)) ||
        WMPROP_GetLastError != lpWMProp->lpVtbl->GetLastError)
    {
        DebugTraceSc(WMPROP_GetLastError, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & ~MAPI_UNICODE )
    {
        DebugTraceSc(WMPROP_GetLastError, MAPI_E_UNKNOWN_FLAGS);
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceSc(WMPROP_GetLastError, MAPI_E_BAD_CHARWIDTH);
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    return lpWMProp->lpMAPIProp->lpVtbl->GetLastError(
            lpWMProp->lpMAPIProp, hError, ulFlags, lppMapiError );
}

STDMETHODIMP
WMPROP_SaveChanges(
    LPWMPROP lpWMProp,
    ULONG ulFlags)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, SaveChanges)) ||
        WMPROP_SaveChanges != lpWMProp->lpVtbl->SaveChanges)
    {
        DebugTraceSc(WMPROP_SaveChanges,  E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  We don't want to really call SaveChanges() on the underlying object,
        as this would make an incomplete message still under composition
        actually appear in the folder where it was created.

        If we had some computed properties here, we should do so (since some
        of them might have changed) and return any error encountered in
        doing so. But we don't. So all we need to do is return success. */

    return hrSuccess;
}

STDMETHODIMP
WMPROP_GetProps(
    LPWMPROP lpWMProp,
    LPSPropTagArray lpPropTagArray,
    ULONG ulFlags,
    ULONG * lpcValues,
    LPSPropValue * lppPropArray)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, GetProps)) ||
        WMPROP_GetProps != lpWMProp->lpVtbl->GetProps)
    {
        DebugTraceSc(WMPROP_GetProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        DebugTraceSc(WMPROP_GetProps, MAPI_E_UNKNOWN_FLAGS);
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceSc(WMPROP_GetProps, MAPI_E_BAD_CHARWIDTH);
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    return lpWMProp->lpMAPIProp->lpVtbl->GetProps(
        lpWMProp->lpMAPIProp, lpPropTagArray, ulFlags,
        lpcValues, lppPropArray);
}

STDMETHODIMP
WMPROP_GetPropList(
    LPWMPROP lpWMProp,
    ULONG ulFlags,
    LPSPropTagArray * lppPropTagArray)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, GetPropList)) ||
        WMPROP_GetPropList != lpWMProp->lpVtbl->GetPropList)
    {
        DebugTraceSc(WMPROP_GetPropList, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        DebugTraceSc(WMPROP_GetProps, MAPI_E_UNKNOWN_FLAGS);
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceSc(WMPROP_GetProps, MAPI_E_BAD_CHARWIDTH);
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    return lpWMProp->lpMAPIProp->lpVtbl->GetPropList(
        lpWMProp->lpMAPIProp, ulFlags, lppPropTagArray);
}

STDMETHODIMP
WMPROP_SetProps(
    LPWMPROP lpWMProp,
    ULONG cValues,
    LPSPropValue lpPropArray,
    LPSPropProblemArray * lppProblems)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, SetProps)) ||
        WMPROP_SetProps != lpWMProp->lpVtbl->SetProps)
    {
        DebugTraceSc(WMPROP_SetProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->SetProps(lpWMProp->lpMAPIProp,
        cValues, lpPropArray, lppProblems);
}

STDMETHODIMP
WMPROP_DeleteProps(
    LPWMPROP lpWMProp,
    LPSPropTagArray lpPropTagArray,
    LPSPropProblemArray * lppProblems)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, DeleteProps)) ||
        WMPROP_DeleteProps != lpWMProp->lpVtbl->DeleteProps)
    {
        DebugTraceSc(WMPROP_DeleteProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->DeleteProps(lpWMProp->lpMAPIProp,
        lpPropTagArray, lppProblems);
}

STDMETHODIMP
WMPROP_CopyTo(
    LPWMPROP lpWMProp,
    ULONG ciidExclude,
    LPCIID rgiidExclude,
    LPSPropTagArray lpExcludeProps,
    ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress,
    LPCIID lpInterface,
    LPVOID lpDestObj,
    ULONG ulFlags,
    LPSPropProblemArray FAR * lppProblems)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, CopyTo)) ||
        WMPROP_CopyTo != lpWMProp->lpVtbl->CopyTo)
    {
        DebugTraceSc(WMPROP_CopyTo, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->CopyTo(lpWMProp->lpMAPIProp,
        ciidExclude, rgiidExclude, lpExcludeProps, ulUIParam,
        lpProgress, lpInterface, lpDestObj, ulFlags, lppProblems);
}

STDMETHODIMP
WMPROP_CopyProps(
    LPWMPROP lpWMProp,
    LPSPropTagArray lpIncludeProps,
    ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress,
    LPCIID lpInterface,
    LPVOID lpDestObj,
    ULONG ulFlags,
    LPSPropProblemArray FAR * lppProblems)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, CopyProps)) ||
        WMPROP_CopyProps != lpWMProp->lpVtbl->CopyProps)
    {
        DebugTraceSc(WMPROP_CopyProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->CopyProps(lpWMProp->lpMAPIProp,
        lpIncludeProps, ulUIParam, lpProgress, lpInterface, lpDestObj,
        ulFlags, lppProblems);
}

STDMETHODIMP
WMPROP_GetNamesFromIDs(
    LPWMPROP lpWMProp,
    LPSPropTagArray * lppPropTags,
    LPGUID lpPropSetGuid,
    ULONG ulFlags,
    ULONG * lpcPropNames,
    LPMAPINAMEID * *lpppPropNames)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, GetNamesFromIDs)) ||
        WMPROP_GetNamesFromIDs != lpWMProp->lpVtbl->GetNamesFromIDs)
    {
        DebugTraceSc(WMPROP_GetNamesFromIDs, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->GetNamesFromIDs(lpWMProp->lpMAPIProp,
        lppPropTags, lpPropSetGuid, ulFlags, lpcPropNames, lpppPropNames);
}

STDMETHODIMP
WMPROP_GetIDsFromNames(
    LPWMPROP lpWMProp,
    ULONG cPropNames,
    LPMAPINAMEID * lppPropNames,
    ULONG ulFlags,
    LPSPropTagArray * lppPropTags)
{
    /* Validate the object */

    if (FBadUnknown(lpWMProp) ||
        IsBadWritePtr(lpWMProp, sizeof(WMPROP)) ||
        IsBadReadPtr(lpWMProp->lpVtbl, offsetof(WMPROP_Vtbl, GetIDsFromNames)) ||
        WMPROP_GetIDsFromNames != lpWMProp->lpVtbl->GetIDsFromNames)
    {
        DebugTraceSc(WMPROP_GetIDsFromNames, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    return lpWMProp->lpMAPIProp->lpVtbl->GetIDsFromNames(lpWMProp->lpMAPIProp,
        cPropNames, lppPropNames, ulFlags, lppPropTags);
}


XPOPTION.H

/*
 -  X P O P T I O N . H
 -
 *  Purpose:
 *      Defines a wrapped implementation of an IMAPIProp interface
 *      to be used by ScOptionDataCallBack() to wrap the IMAPIProp passed
 *      in.  This object is wrapped so we can effect the behavior of the
 *      OpenProperty() call.  Specifically, we wish to put up a Property
 *      Sheet when the user tries to open the PR_DETAILS_TABLE property.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/*
 *  Wrapped IMAPIProp Interface declaration.
 */

#undef  INTERFACE
#define INTERFACE   struct _WMPROP

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, WMPROP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, WMPROP_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(WMPROP_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
};

typedef struct _WMPROP
{
    WMPROP_Vtbl *       lpVtbl;

    /* Need to be the same as other objects */

    LONG                lcInit;

    /* MAPI memory routines */

    LPALLOCATEBUFFER    lpAllocBuff;
    LPALLOCATEMORE      lpAllocMore;
    LPFREEBUFFER        lpFreeBuff;

    /* Private data */

    HINSTANCE           hInst;
    LPMALLOC            lpMalloc;
    ULONG               ulType;
    ULONG               cbOptionData;
    LPBYTE              lpbOptionData;
    LPMAPIPROP          lpMAPIProp;

} WMPROP, FAR *LPWMPROP;


XPPCH.C

/*
 -  X P P C H . C
 -
 *  Purpose:
 *      Pre-Compiled Header Source for Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"


XPPCH.H

/*
 -  X P P C H . H
 -
 *  Pre-Compiled Header File for Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#define _INC_OLE
#include <windows.h>
#include <windowsx.h>
#include <mapiwin.h>
#include <mapidbg.h>
#include <ole2.h>

#include <mapidefs.h>
#include <mapicode.h>
#include <mapitags.h>
#include <mapiguid.h>
#include <mapispi.h>
#include <mapiutil.h>
#include <mapival.h>
#include <imessage.h>
#include <mapiwz.h>

#ifndef MAC
#include <memory.h>
#endif
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <stddef.h>

#include "xpdialog.h"
#include "xpbase.h"
#include "xpstatus.h"
#include "xpqueue.h"
#include "xplogger.h"


XPQUEUE.C

/*
 -  X P Q U E U E . C
 -
 *  Purpose:
 *      Code to support the background queueing mechanism for the Sample
 *      Transport Provider.
 *      This module contains the following SPI entry points:
 *
 *          Idle()
 *
 *      Additional support functions found here:
 *
 *          HrBuildAdrList()
 *          HrSendOneMessage()
 *          HrIMsgToTextMsg()
 *          HrPrepareRecipientTable()
 *          HrCrackSenderEID()
 *          FPropIndex()
 *          FormatFileTime()
 *          FreeMyAdrList()
 *          ScLoadTnef()
 *
 *      Also present in this module is the transmit list management code and
 *      all the recipient list manipulation logic.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include <tnef.h>
#include "xpsof.h"
#include "xptxtmsg.h"
#include "xpresrc.h"

BOOL FIsTextizedProp(ULONG ulPropTag);

/* Generic BAD_VALUE for use in comparisons below */

#define BAD_VALUE (0xFFFFFFFF)

/*
 -  lpxpl->lpVtbl->Idle
 -
 *  Purpose:
 *      Called by the Spooler periodically in its idle loop.
 *
 *      The Transport will determine if there's any incoming mail for the
 *      Spooler and will TransportNotify (NOTIFY_NEWMAIL) if so.
 *
 *  Parameters:
 *      ulFlags             Flags. None are currently defined.
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *
 *  Operation:
 *      If inbound operation is currently enabled, call the Poll() entry point
 *      to see if there's anything there. If we find something with Poll(), then
 *      SpoolerNotify(NOTIFY_NEWMAIL).
 */

STDMETHODIMP
XPL_Idle(LPXPL lpxpl, ULONG ulFlags)
{
    HRESULT hResult = 0;
    LPDEFMSG lpDefMsg;

    /* Incoming messages? */

    if (lpxpl->ulTransportStatus & STATUS_INBOUND_ENABLED)
    {
        ULONG ulT = 0;

        if ((hResult = XPL_Poll(lpxpl, &ulT)) != 0)
            goto ret;
        if (ulT != 0)
        {
            /* SpoolerNotify() could theoretically return a nonzero
            HRESULT, but it's much easier and unambiguous to ignore
            that possibility and field Spooler errors elsewhere. */

            (void)lpxpl->lpMAPISup->lpVtbl->SpoolerNotify(lpxpl->lpMAPISup, NOTIFY_NEWMAIL, NULL);
        }
    }

    if (lpxpl->fResendDeferred)
    {
        while (lpxpl->lpDeferredList)
        {
            lpDefMsg = lpxpl->lpDeferredList;
            lpxpl->lpDeferredList = lpDefMsg->lpNext;

            lpxpl->lpMAPISup->lpVtbl->SpoolerNotify(lpxpl->lpMAPISup,
                NOTIFY_SENTDEFERRED, &(lpDefMsg->sbinEIDDef));

            lpxpl->FreeBuffer(lpDefMsg);
        }

        lpxpl->fResendDeferred = FALSE;
    }

ret:

    DebugTraceResult(XPL_Idle, hResult);
    return hResult;
}

/*
 -  HrBuildAdrList
 -
 *  Called by outbound and inbound logic.
 *
 *  Traverses a restricted recipient table and collates the rows into
 *  a "done" adrlist structure and a "not done" adrlist structure. The
 *  only distinction between done and not done is the result of a call
 *  to an optional function to attempt to send the open message to
 *  a particular recipient.
 *
 *  Obviously, if the callback isn't specified, the "done" adrlist will
 *  contain all recipients that matched the restriction.
 *
 *  Parameters:
 *      lpxpl               Session in which we're executing
 *      lpMessage           Message containing the recipient table
 *      lpTable             Recipient table
 *      fSetResponsibility  TRUE/FALSE = Set/Clear PR_RESPONSIBILITY
 *                          in good list (clear bad list always)
 *      lpfnCallBack        Routine to call when trying to send
 *      lppMyAdrListGood    Where to store the list of done recips
 *      lppMyAdrListBad     Where to store the list of not done
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *
 *
 *  Operation:
 *      This routine builds a pair of MYADRLISTS from the (restricted)
 *      table which is passed to it.
 *
 *      Do a SetColumns so we know where to find PR_EMAIL_ADDRESS, PR_ROWID
 *      and other necessary columns. Included in the SetColumns is
 *      PR_REPORT_TEXT as a placeholder for NDR information.
 *
 *      Then, until we run out of data,
 *
 *      1) QueryRows on the input table
 *      2) For each row:
 *          a) If lpfnCallBack and PR_EMAIL_ADDRESS, invoke callback.
 *          b) If callback wasn't defined or if successful, add row to
 *             "good" list, else add to "bad" list with NDR reason
 *      3) Free RowSet
 *
 *      The caller can tell if any recipients remain by whether the
 *      "not done" set contains any elements.
 *
 *      The "not done" set will set PR_RESPONSIBILITY to FALSE if the caller
 *      wants "done" recipients set,
 */

HRESULT
HrBuildAdrList(LPXPL lpxpl,
    LPSPropValue lpPropArray,
    LPMESSAGE lpMessage,
    LPMAPITABLE lpTable,
    BOOL fSetResponsibility,
    LPMYCALLBACK lpfnCallBack,
    LPMYADRLIST FAR * lppMyAdrListGood,
    LPMYADRLIST FAR * lppMyAdrListBad)
{
    HRESULT hResult = hrSuccess;
    SCODE sc = S_OK;
    LPVOID lpvT;
    TCHAR rgchBuffer[512];
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;

    LPSRowSet lpRow = NULL;
    LPMYADRLIST lpDone = NULL;
    LPMYADRLIST lpNotDone = NULL;
    LPSPropTagArray lpsptT = NULL;

    LPMYADRLIST FAR *lppMyAdrList;
    LPSPropTagArray lpsptNew;
    LPSPropValue lpspvT;

    ULONG ulT;
    ULONG ulNew;
    ULONG ulRow;

    /*  These are the columns I need to have in the recipient table,
        even if all I get is a placeholder. If you need to add any,
        bump the definition of NUM_REQUIRED_COLS, add new defines
        for the new columns, and add the new column proptags to the
        switch below. If the property isn't necessarily going to be
        in the table, use the PR_NULL trick you see below for the
        PR_REPORT_TEXT property. */

    enum enumColumns
    {
        COLUMN_PR_ROWID,
        COLUMN_PR_EMAIL_ADDRESS,
        COLUMN_PR_RESPONSIBILITY,
        COLUMN_PR_REPORT_TEXT,
        COLUMN_PR_RECIPIENT_TYPE,
        NUM_REQUIRED_COLS
    };

    Assert(lppMyAdrListGood);
    Assert(lppMyAdrListBad);

    /*
        Arrange the columns so that we'll have the recipient properties
        we really want.

        So as not to lose any recipient properties, we need to do it in
        this fashion:

        1)  QueryColumns() on the input table
        2)  Build a new column set based on the properties we care
            about plus all other properties of the input table
        3)  SetColumns() on the input table

        The properties we care about are the following:

        1)  PR_ROWID
        2)  PR_EMAIL_ADDRESS
        3)  PR_RESPONSIBILITY */

    /*  Get the complete column set */

    hResult = lpTable->lpVtbl->QueryColumns(lpTable, TBL_ALL_COLUMNS, &lpsptT);

    if (hResult)
    {
        DebugTrace("QueryColumns failed.\n");
        goto ret;
    }

    /*  Gotta have a PR_ROWID. That guarantees at least one column! */

    Assert(lpsptT->cValues >= 1);

    /*  Allocate a new column set, linked to the old one for cleanup */

    ulT = CbNewSPropTagArray(NUM_REQUIRED_COLS + lpsptT->cValues);
    sc = (*lpxpl->AllocateMore) (ulT, (LPVOID) lpsptT, (LPVOID) &lpsptNew);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        DebugTrace("New column set allocation failed.\n");
        goto ret;
    }

    /*  Fill in the new column set, required fields first in our order. */

    lpsptNew->aulPropTag[COLUMN_PR_ROWID] = PR_ROWID;
    lpsptNew->aulPropTag[COLUMN_PR_EMAIL_ADDRESS] = PR_EMAIL_ADDRESS;
    lpsptNew->aulPropTag[COLUMN_PR_RESPONSIBILITY] = PR_RESPONSIBILITY;
    lpsptNew->aulPropTag[COLUMN_PR_REPORT_TEXT] = PR_NULL;
    lpsptNew->aulPropTag[COLUMN_PR_RECIPIENT_TYPE] = PR_RECIPIENT_TYPE;
    ulNew = NUM_REQUIRED_COLS;

    for (ulT = 0; ulT < lpsptT->cValues; ulT++)
    {
        switch (lpsptT->aulPropTag[ulT])
        {
        case PR_REPORT_TEXT:
            lpsptNew->aulPropTag[COLUMN_PR_REPORT_TEXT] = PR_REPORT_TEXT;
            break;

        case PR_ROWID:
        case PR_EMAIL_ADDRESS:
        case PR_RESPONSIBILITY:
        case PR_RECIPIENT_TYPE:
            break;

        default:
            lpsptNew->aulPropTag[ulNew++] = lpsptT->aulPropTag[ulT];
            break;
        }
    }

    lpsptNew->cValues = ulNew;

    /*  Set the new columns */

    hResult = lpTable->lpVtbl->SetColumns(lpTable, lpsptNew, 0L);

    if (hResult)
    {
        DebugTrace("SetColumns failed.\n");
        goto ret;
    }

    /*  Free both old and new PropTagArrays. */

    (*lpxpl->FreeBuffer) ((LPVOID) lpsptT);
    lpsptT = NULL;

    /*  Build two big ADRLISTs from this table. Do minimal allocations so
        that all the reallocation code will be properly exercised. We can
        optimize later when we've tested.

        The obvious underlying assumption of this code is that it's
        possible to get the entire recipient list into memory at once. */

#ifdef DEBUG
#define GROW_SIZE   1       /* How much we'll add on every reallocation. */
#define QUERY_SIZE  1       /* How much we'll try for on query rows */
#else
#define GROW_SIZE   20
#define QUERY_SIZE  20
#endif

    for (;;)
    {
        /*  Get some rows from the recipient table. */

        hResult = lpTable->lpVtbl->QueryRows(lpTable, QUERY_SIZE, 0L, &lpRow);

        if (hResult)
        {
            DebugTrace("QueryRows failed.\n");
            goto ret;
        }

        /*  Are we finished getting rows? */

        if (!lpRow || !(lpRow->cRows))
        {
            /*  Free the row if any */

            (*lpxpl->FreeBuffer) ((LPVOID) lpRow);
            lpRow = NULL;

            /*  We're finished now, break out of the (for (;;)) loop. */

            break;
        }

        /*  Work our way through the RowSet */

        for (ulRow = 0; ulRow < lpRow->cRows; ulRow++)
        {
            BOOL fDone = TRUE;

            Assert(lpRow->aRow[ulRow].cValues);
            Assert(lpRow->aRow[ulRow].lpProps);

            lpspvT = lpRow->aRow[ulRow].lpProps;

            /* Check our .2 second timer before processing each row. */

            sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

            if (sc == MAPI_W_CANCEL_MESSAGE)
            {
                DebugTrace("Cancelling message delivery.\n");
                goto ret;
            }

            /*  See if we need to do the callback. If so, make the call now */

            if (lpfnCallBack)
            {
                Assert(lpspvT[COLUMN_PR_EMAIL_ADDRESS].ulPropTag == PR_EMAIL_ADDRESS);
                Assert(lpspvT[COLUMN_PR_RECIPIENT_TYPE].ulPropTag == PR_RECIPIENT_TYPE);

                hResult = lpfnCallBack(lpxpl,
                    lpPropArray,
                    lpMessage,
                    lpspvT[COLUMN_PR_RECIPIENT_TYPE].Value.ul,
                    lpspvT[COLUMN_PR_EMAIL_ADDRESS].Value.LPSZ,
                    &fDone);

                if (hResult)
                {
                    DebugTrace("IGNORE: Callback function  failed.\n");
                    hResult = hrSuccess;
                }
            }

            /*  Set the PR_RESPONSIBILITY flag to its indicated new state. */

            Assert(lpRow->aRow[ulRow].cValues > COLUMN_PR_RESPONSIBILITY);

            /*  Make the appropriate change to PR_RESPONSIBILITY. If we're
                setting it, just do it into whatever column we've set;
                is pointing at, Prop Tag and Value. If we're clearing it,
                we only need to do anything if there was a discrete column,
                and all that's needed is to set the Prop Tag to PR_NULL. */

            if (fSetResponsibility)
            {
                lpspvT[COLUMN_PR_RESPONSIBILITY].ulPropTag = PR_RESPONSIBILITY;
                lpspvT[COLUMN_PR_RESPONSIBILITY].Value.b = fDone;
            }
            else
            {
                /*  We want to clear the flag */
                lpspvT[COLUMN_PR_RESPONSIBILITY].ulPropTag = PR_NULL;
            }

            /*  PR_NULL the PR_ROWID column if this will be a "bad" recipient, also
                adding NDR information to the row (in a column we set earlier) */

            if (!fDone)
            {
                TCHAR szReportText[512];

                lpspvT[COLUMN_PR_ROWID].ulPropTag = PR_NULL;

                /*  The Spooler will default to a NDR report and will fill in all
                    required properties in the StatusRecips call. The only thing
                    we need to do is to fill in a specific per-recipient text
                    description of the problem (it defaults too but it's good
                    to have real info from the horse's mouth) */

                LoadString(lpxpl->lpxppParent->hInst, IDS_REPORT_TEXT_MSG,
                    szReportText, sizeof(szReportText));

                wsprintf(rgchBuffer, "%s%s", szReportText,
                    lpspvT[COLUMN_PR_EMAIL_ADDRESS].Value.LPSZ);

                ulT = Cbtszsize(rgchBuffer);
                sc = lpxpl->AllocateMore(ulT, lpspvT, &lpvT);

                if (sc)
                {
                    hResult = ResultFromScode(sc);
                    DebugTrace("NDR text allocation failed.\n");
                    goto ret;
                }

                /*  Memory allocated, copy the formatted string and hook it into
                    the pre-allocated column. */

                lstrcpy((LPTSTR) lpvT, rgchBuffer);
                lpspvT[COLUMN_PR_REPORT_TEXT].ulPropTag = PR_REPORT_TEXT;
                lpspvT[COLUMN_PR_REPORT_TEXT].Value.LPSZ = (LPTSTR) lpvT;
            }

            /*  Now point ourselves at one of the two LPMYADRLIST pointers */

            lppMyAdrList = (fDone ? &lpDone : &lpNotDone);

            /*  If we didn't already have a LPMYADRLIST, allocate one now. */

            if (*lppMyAdrList == NULL)
            {
                /*  Allocate an initial MYADRLIST structure. Then allocate
                    enough memory for (GROW_SIZE) ADRENTRYs and store
                    it into the structure. Initialize the members. */

                sc = (*lpxpl->AllocateBuffer) (sizeof(MYADRLIST), &lpvT);

                if (sc)
                {
                    hResult = ResultFromScode(sc);
                    DebugTrace("Initial MYADRLIST allocation failed.\n");
                    goto ret;
                }

                /*  Hook up the MYADRLIST with no entries. */

                *lppMyAdrList = (LPMYADRLIST) lpvT;
                (*lppMyAdrList)->cMaxEntries = 0;
                (*lppMyAdrList)->lpAdrList = NULL;

                /* Now allocate a ADRLIST with GROW_SIZE entries in it. */

                sc = (*lpxpl->AllocateBuffer) (CbNewADRLIST(GROW_SIZE), &lpvT);

                if (sc)
                {
                    hResult = ResultFromScode(sc);
                    DebugTrace("Initial ADRLIST allocation failed.\n");
                    goto ret;
                }

                /*  Now hook up this ADRLIST into the MYADRLIST. */

                (*lppMyAdrList)->lpAdrList = (LPADRLIST) lpvT;
                (*lppMyAdrList)->cMaxEntries = GROW_SIZE;
                (*lppMyAdrList)->lpAdrList->cEntries = 0;
            }

            /*  Make sure that the selected MYADRLIST has room for another row */

            ulT = (*lppMyAdrList)->lpAdrList->cEntries + 1;
            Assert(ulT <= (*lppMyAdrList)->cMaxEntries + 1);

            if (ulT > (*lppMyAdrList)->cMaxEntries)
            {
                LPADRLIST lpAdrListT;

                /*  Not enough space, we need to create a new ADRLIST. */

                ulT = CbNewADRLIST((*lppMyAdrList)->cMaxEntries + GROW_SIZE);
                sc = (*lpxpl->AllocateBuffer) (ulT, &lpvT);

                if (sc)
                {
                    hResult = ResultFromScode(sc);
                    DebugTrace("Reallocation of ADRLIST failed.\n");
                    goto ret;
                }

                /*  Got it, now copy the data from the old one */

                lpAdrListT = (LPADRLIST) lpvT;
                ulT = CbNewADRLIST((*lppMyAdrList)->cMaxEntries);

                if (ulT)
                    memcpy(lpAdrListT, (*lppMyAdrList)->lpAdrList, (UINT) ulT);

                (*lppMyAdrList)->cMaxEntries += GROW_SIZE;

                /*  Exchange the pointers */

                lpvT = (LPVOID) (*lppMyAdrList)->lpAdrList;
                (*lppMyAdrList)->lpAdrList = lpAdrListT;

                /*  Free the old memory */

                lpxpl->FreeBuffer(lpvT);
            }

            /*  We have room now so store the new ADRENTRY. As part of the
                storage, we're going to copy the SRow pointer from the SRowSet
                into the ADRENTRY. Once we've done this, we won't need the
                SRowSet any more ... and the SRow will be deallocated when
                we unwind the ADRLIST. */

            /*  Calculate location in ADRLIST for the new entry. */

            ulT = (*lppMyAdrList)->lpAdrList->cEntries++;

            /*  Copy the data from the SRowSet. */

            (*lppMyAdrList)->lpAdrList->aEntries[ulT].cValues = lpRow->aRow[ulRow].cValues;
            (*lppMyAdrList)->lpAdrList->aEntries[ulT].rgPropVals = lpRow->aRow[ulRow].lpProps;

            /*  Now that we are finished with this row (eg it is in the
                adrlist) we want to disassociate it from the rowset. */

            lpRow->aRow[ulRow].lpProps = NULL;
        }

        /*  We're finished with the SRowSet (since it is allocated
            separately from the SRow). Deallocate it. */

        lpxpl->FreeBuffer((LPVOID) lpRow);
        lpRow = NULL;

    }                           /* End of big ADRLIST builder loop */

    /*  Fall into exit with hResult set if something failed. */

ret:

    /*  Clean up RowSet if any is left */

    FreeProws(lpRow);

    /*  Clean up column stuff if any's left */

    lpxpl->FreeBuffer((LPVOID) lpsptT);

    /*  If sc is set, feed it into hResult. Afterwards, hResult is the
        determinant of whether there was an error or not. */

    if (hResult)
    {
        /*  This memory should only be hanging around in the error case. */

        FreeMyAdrList(lpxpl, lpDone);
        FreeMyAdrList(lpxpl, lpNotDone);

        *lppMyAdrListGood = NULL;
        *lppMyAdrListBad = NULL;
    }
    else
    {
        /*  Success, pass the adrlists we built back to the caller */

        *lppMyAdrListGood = lpDone;
        *lppMyAdrListBad = lpNotDone;
    }

    DebugTraceResult(HrBuildAdrList, hResult);
    return hResult;
}

/*
 -  HrSendOneMessage
 -
 *  Purpose:
 *      Called by HrBuildAdrList() to send a message to a recipient
 *      that matched all of the caller's restrictions. Also called by
 *      XPL_SubmitMessage() if transport is not peer-to-peer, in which
 *      case the "Email address" is just our outbound directory.
 *
 *  Parameters:
 *      lpxpl               The Transports logon object for this session
 *      lpPropArray         Logon properties that have been copied from lpxpl
 *      lpMessage           Message to send
 *      ulRecipType         MAPI_TO, MAPI_CC, etc. If 0, not a recip.
 *      lpszEmailAddress    Email address of this recipient.
 *      lpfSent             Pointer to the boolean result
 *                          of the operation.
 *
 *  Returns:
 *      (HRESULT)           Set if an error encountered.
 *      *lpfSent            FALSE if sending failed,
 *                          TRUE if sending succeeded.
 *
 *  Operation:
 *      Initializes result in *lpfSent to FALSE.  Open a stream interface
 *      on the destination message file.  Write all the textized envelope
 *      properties into the stream, then use TNEF to encapsulate the remaining
 *      message properties into the stream.
 */

HRESULT
HrSendOneMessage(LPXPL lpxpl,
    LPSPropValue lpPropArray,
    LPMESSAGE lpMessage,
    ULONG ulRecipType,
    LPTSTR lpszEmailAddress,
    BOOL FAR * lpfSent)
{
    TCHAR rgchOutFileName[MAX_PATH];
    HRESULT hResult = 0;
    SCODE sc = 0;
    ULONG ulT;
    ULONG cValues;
    BOOL fFromMe;

    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPIID lpidMessage = (LPIID) &IID_IMessage;
    SPropValue spvDestMsgProps[3];

    LPSPropProblemArray lpProblems = NULL;
    LPSPropTagArray lpPropTagArray = NULL;

    LPSTnefProblemArray lptpa = NULL;

    WORD wKey = 0;
    LPITNEF lpTnef = (LPITNEF) NULL;
    LPSTREAM lpSof = (LPSTREAM) NULL;
    LPSTREAM lpXPSof = (LPSTREAM) NULL;

    /* Assume the worst, we'll fix it later if need be */

    *lpfSent = FALSE;

    /* Build file name of outgoing message. Because loopback doesn't
       always work on all platforms, we check the email address against
       our own and just substitute the inbox path if it matches. */

    if (!lstrcmpi(lpszEmailAddress,
            ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ))
    {
        DebugTrace("Email Address is mine, doing my own internal loopback\n");
        lstrcpy(rgchOutFileName, ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray).Value.LPSZ);
        fFromMe = TRUE;
    }
    else
    {
        lstrcpy(rgchOutFileName, lpszEmailAddress);

        /*  If this is a real email address, it won't have a
            trailing backslash. But if it's our outbound dir (when
            we're not p2p) it wiil have one. Only add one if
            it's needed. */

        ulT = lstrlen(rgchOutFileName);
        Assert(ulT > 1);

        if (lstrcmp(&rgchOutFileName[ulT], TEXT("\\")))
            lstrcat(rgchOutFileName, TEXT("\\"));
        fFromMe = FALSE;
    }

    hResult = OpenStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
        STGM_CREATE | STGM_READWRITE | SOF_UNIQUEFILENAME,
        rgchOutFileName, TEXT("TNF"), &lpSof);
    if (HR_FAILED(hResult))
    {
        DebugTrace("OpenStreamOnFile(%s) failed.\n",rgchOutFileName);
        PrintfTransportLog(TEXT("Delivery failed in OpenStreamOnFile."));
        goto ret;
    }

    /* Wrap the Stream-On-File object in our buffered wrapper. */

    hResult = HrWrapStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
            XPSOF_READWRITE, lpSof, &lpXPSof);
    if (HR_FAILED(hResult))
    {
        DebugTrace("HrWrapStreamOnFile() failed\n");
        goto ret;
    }

    /* Write all non-TNEF properties to the text file */

    hResult = HrIMsgToTextMsg(lpxpl, lpPropArray, lpMessage, lpXPSof);

    if (HR_FAILED(hResult))
    {
        DebugTrace("HrIMsgToTextMsg() failed.\n");
        PrintfTransportLog(TEXT("Delivery failed in HrIMsgToTextMsg."));
        goto ret;
    }

    hResult = hrSuccess;

    /* Check our .2 second timer after writting textized properties. */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message delivery.\n");
        goto ret;
    }

    /* Open a TNEF encapsulation on the StreamOnFile interface */

    hResult = OpenTnefStream(lpMAPISup, lpXPSof, TEXT("MAPIMAIL.DAT"),
            TNEF_ENCODE, lpMessage, 0x01AF, &lpTnef);

    if (HR_FAILED(hResult))
    {
        DebugTrace("OpenTNEFStream() failed.\n");
        PrintfTransportLog(TEXT("Delivery failed in OpenTNEFStream."));
        goto ret;
    }

    /* Find out what properties there are, so we can exclude the
       "nontransmittables" and the properties we've textized. */

    hResult = lpMessage->lpVtbl->GetPropList(lpMessage, 0, /* ansi */
            &lpPropTagArray);

    if (hResult)
    {
        DebugTrace("GetPropList failed.\n");
        goto ret;
    }

    Assert(lpPropTagArray);

    /* Build a new prop tag array on the memory we just got back. This
       prop tag array will only contain nontransmittable properties. */

    cValues = 0;

    for (ulT = 0; ulT < lpPropTagArray->cValues; ulT++)
    {
        ULONG ulPropTagT = lpPropTagArray->aulPropTag[ulT];

        /*  FIsTransmittable is a macro in the MAPI headers. Makes it
            really easy to determine transmittable/not when we need to */

        if ((!FIsTransmittable(ulPropTagT) || FIsTextizedProp(ulPropTagT)) &&
            (ulPropTagT != PR_MESSAGE_ATTACHMENTS))
        {
            lpPropTagArray->aulPropTag[cValues++] = ulPropTagT;
        }
    }
    lpPropTagArray->cValues = cValues;

    /* Exclude selected properties from our TNEF encapsulation. */

    hResult = lpTnef->lpVtbl->AddProps(lpTnef,
        TNEF_PROP_EXCLUDE, 0L, NULL, lpPropTagArray);

    if (HR_FAILED(hResult))
    {
        DebugTrace("AddProps failed.\n");
        goto ret;
    }

    /* Check our .2 second timer! */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message delivery.\n");
        goto ret;
    }

    /* If we had a recipient type, we should help the receiver side out
       with some properties, as follows:

       PR_MESSAGE_TO_ME should be set TRUE if ulRecipType == MAPI_TO
       PR_MESSAGE_CC_ME should be set TRUE if ulRecipType == MAPI_CC
       PR_MESSAGE_RECIP_ME should be set TRUE for either of the above */

    if (ulRecipType)
    {
        spvDestMsgProps[0].ulPropTag = PR_MESSAGE_TO_ME;
        spvDestMsgProps[0].Value.b = (ulRecipType == MAPI_TO);

        spvDestMsgProps[1].ulPropTag = PR_MESSAGE_CC_ME;
        spvDestMsgProps[1].Value.b = (ulRecipType == MAPI_CC);

        spvDestMsgProps[2].ulPropTag = PR_MESSAGE_RECIP_ME;
        spvDestMsgProps[2].Value.b = ((ulRecipType == MAPI_TO) || (ulRecipType == MAPI_CC));

        lpxpl->FreeBuffer(lpProblems);
        lpProblems = NULL;

        hResult = lpTnef->lpVtbl->SetProps(lpTnef, 0L, 0L, 3,
            spvDestMsgProps);

        if (HR_FAILED(hResult))
        {
            DebugTrace("SetProps failed");
            goto ret;
        }
    }

    /* OK. All the properties are copied over. Save Changes on the
       message we created. */

    hResult = lpTnef->lpVtbl->Finish(lpTnef, 0L, &wKey, &lptpa);
    lpxpl->FreeBuffer(lptpa);
    if (HR_FAILED(hResult))
    {
        DebugTrace("Finish failed.\n");
        goto ret;
    }

    hResult = lpXPSof->lpVtbl->Commit(lpXPSof, STGC_DEFAULT);

    if (HR_FAILED(hResult))
    {
        DebugTrace("Commit stream failed.\n");
        goto ret;
    }

    /* Check our .2 second timer! */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message delivery.\n");
        goto ret;
    }

    *lpfSent = TRUE;

    /* Log successful transmission. */

    PrintfTransportLog(TEXT("Delivery Complete: %s"), rgchOutFileName);

ret:
    /* Release any open object */

    UlRelease(lpTnef);
    UlRelease(lpXPSof);
    UlRelease(lpSof);

    if (HR_FAILED(hResult) && lpSof)
        DeleteFile(rgchOutFileName);

    /* Release the prop tag array and/or problem array if any */

    lpxpl->FreeBuffer(lpPropTagArray);

    DebugTraceResult(HrSendOneMessage, hResult);
    return hResult;
}

/* Stuff to support the textized message formatting */

const static SizedSPropTagArray(4, sptMsgProps) =
{
    4,
    {
        PR_CLIENT_SUBMIT_TIME,
        PR_SUBJECT,
        PR_PRIORITY,
        PR_BODY
    }
};

const static SizedSPropTagArray(5, sptRecipProps) =
{
    5,
    {
        PR_RECIPIENT_TYPE,
        PR_EMAIL_ADDRESS,
        PR_ADDRTYPE,
        PR_DISPLAY_NAME,
        PR_RESPONSIBILITY
    }
};

const static SizedSPropTagArray(3, sptSenderDelegate) =
{
    3,
    {
        PR_SENT_REPRESENTING_ENTRYID,
        PR_SENDER_ENTRYID,
        PR_REPLY_RECIPIENT_ENTRIES,
    }
};

TCHAR rgszTags[NUM_TAGS][MAX_TAG_LEN] =
{
    TEXT("Message: "),
    TEXT("From: "),
    TEXT("Representing: "),
    TEXT("Reply To: "),
    TEXT("Date: "),
    TEXT("To: "),
    TEXT("Cc: "),
    TEXT("Bcc: "),
    TEXT("Subject: "),
    TEXT("Priority Urgent: "),
    TEXT("Priority Normal: "),
    TEXT("Priority Low: "),
    TEXT("Contents: "),
    TEXT("Text Item: "),
    TEXT("File Item: ")
};

TCHAR szCRLF[3] = {TEXT("\r\n")};

TCHAR szCRLFCRLF[5] = {TEXT("\r\n\r\n")};

/*
 -  HrIMsgToTextMsg
 -
 *  Purpose:
 *      Called by HrSendOneMessage() to write the envelope properties
 *      to the destination message text file.  Uses the StreamOnFile
 *      interface for all the file access.  This stream interface
 *      is then passed to the TNEF encoder, with its current file
 *      pointer un-modified, so the non-envelope properties can be
 *      encapsulated in a TNEF encapsulation in a "File Item" section.
 *
 *  Parameters:
 *      lpxpl               Pointer to Transport Logon object
 *      lpPropArray         Copy of the sessions logon properties
 *      lpMessage           Message to send
 *      lpSof               Pointer to the stream interface
 *
 *  Returns:
 *      (HRESULT)           Set if an error encountered.
 *
 *  Operation:
 *      Call GetProps() on the message to extract PR_SUBMIT_DATE, PR_SUBJECT,
 *      PR_PRIORITY, and PR_BODY.  Then call GetRecipientTable() to extract
 *      the PR_DISPLAY_NAME and PR_EMAIL_ADDRESS for each type of
 *      PR_RECIPIENT_TYPE.  Write all this info to the stream (after
 *      formatting appropriately).
 */

HRESULT
HrIMsgToTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof)
{
    HRESULT hr = hrSuccess;
    ULONG cMsgVals = 0;
    ULONG cSndrVals = 0;
    LPSPropValue lpMsgProps = NULL;
    LPSPropValue lpSndrProps = NULL;
    LPSPropValue lpPropT = NULL;
    LPMAPITABLE lpTbl = NULL;
    LPSRowSet lpRows = NULL;
    ULONG uli;
    ULONG cbOut;
    ULONG cbRead;
    ULONG cbCRLF = lstrlen(szCRLF);
    ULONG cbCRLFCRLF = lstrlen(szCRLFCRLF);
    TCHAR szRep[128];
    TCHAR szFrom[128];
    TCHAR szOutBuf[128];
    TCHAR rgchStrmBuf[MAX_STRM_BUF];
    LPTSTR lpszT1;
    LPTSTR lpszT2;
    LPSTREAM lpStrm = NULL;

    /* Get the 4 Message properties to be textized */

    hr = lpMessage->lpVtbl->GetProps(lpMessage,
            (LPSPropTagArray) &sptMsgProps, 0, /* ansi */
            &cMsgVals, &lpMsgProps);

    if (HR_FAILED(hr) || !cMsgVals)
    {
        DebugTrace("GetProps() MsgProps failed.\n");
        goto ret;
    }

    /* Get the 3 Sender/Delegate properties to be textized */

    hr = lpMessage->lpVtbl->GetProps(lpMessage,
            (LPSPropTagArray) &sptSenderDelegate, 0, /* ansi */
            &cSndrVals, &lpSndrProps);

    if (HR_FAILED(hr) || !cSndrVals)
    {
        DebugTrace("GetProps()  SndrProps failed.\n");
        goto ret;
    }

    /* Returns the Recipient Table in a well defined state
       (i.e. minimal column set and restricted on our AddrType). */

    hr = HrPrepareRecipientTable(lpPropArray, lpMessage, &lpTbl);

    if (HR_FAILED(hr))
    {
        DebugTrace("HrPrepareRecipientTable()  failed.\n");
        goto ret;
    }

    /* Write Message: field */

    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagMessage],
            lstrlen(rgszTags[tagMessage]), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
            cbCRLFCRLF, &cbOut), ret);

    /* Write From: and Representing: fields */

    /* The following code sets the Sender/Delegate */
    /* properties as follows:

        1) If no PR_SENT_REPRESENTING_??? in message,
            a)  If no PR_SENDER_??? in message,
                    PR_SENDER_??? = transport identities
            b)  PR_SENT_REPRESENTING_??? = PR_SENDER_???
        2) (else) If there was a PR_SENT_REPRESENTING_??? in message,
            a)  If no PR_SENDER_??? in message,
                    PR_SENDER_??? = PR_SENT_REPRESENTING_???
        3)  If no PR_REPLY_RECIPIENT_???,
                PR_REPLY_RECIPIENT_ENTRIES = PR_SENT_REPRESENTING_ENTRYID
                PR_REPLY_RECIPIENT_NAMES = PR_SENT_REPRESENTING_NAME

       This just works because HrCrackSenderEID() doesn't change the
       szFrom or szRep memory unless it is successful.  It is for this
       reason that we don't check the return from HrCrackSenderEID() */

    hr = hrSuccess;
    *szFrom = '\0';
    *szRep = '\0';

    if (lpSndrProps[0].ulPropTag == PR_SENT_REPRESENTING_ENTRYID)
        HrCrackSenderEID(lpxpl, lpSndrProps[0].Value.bin.cb,
            lpSndrProps[0].Value.bin.lpb, szRep);

    if (lpSndrProps[1].ulPropTag == PR_SENDER_ENTRYID)
        HrCrackSenderEID(lpxpl, lpSndrProps[1].Value.bin.cb,
            lpSndrProps[1].Value.bin.lpb, szFrom);

    if (!*szFrom && !*szRep)
    {
        lpszT1 = ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ;
        lpszT2 = ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ;
        wsprintf(szFrom, TEXT("%s[%s]"), lpszT1, lpszT2);
        lstrcpy(szRep, szFrom);
    }
    else if (*szFrom && !*szRep)
    {
        lstrcpy(szRep, szFrom);
    }
    else
    {
        lstrcpy(szFrom, szRep);
    }

    wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagFrom], szFrom);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
            lstrlen(szOutBuf), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
            cbCRLFCRLF, &cbOut), ret);

    wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagRepresenting], szRep);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
            lstrlen(szOutBuf), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
            cbCRLFCRLF, &cbOut), ret);

    /* Write Reply To: fields */

    if (lpSndrProps[2].ulPropTag == PR_REPLY_RECIPIENT_ENTRIES)
    {
        LPFLATENTRYLIST lpList = (LPFLATENTRYLIST) lpSndrProps[2].Value.bin.lpb;
        LPBYTE lpb;
        ULONG cEntries;

        /* Attempt some level of validation for this property */

        if (!lpList
            || IsBadReadPtr(lpList, CbNewFLATENTRYLIST(0))
            || !lpList->abEntries
            || IsBadReadPtr(lpList, (UINT) CbFLATENTRYLIST(lpList))
            || !lpList->cEntries
            || (lpList->cEntries * sizeof(GUID) > lpList->cbEntries))
        {
            DebugTrace("Bad PR_REPLY_RECIPIENT_ENTRIES!\n");
            DebugTrace("Skipping the Reply To: field.\n");
        }
        else
        {
            lpb = lpList->abEntries;
            cEntries = lpList->cEntries;

            while (cEntries--)
            {
                LPFLATENTRY lpEntry = (LPFLATENTRY) lpb;
                ULONG ulSize;

                if (IsBadReadPtr(lpEntry, CbNewFLATENTRY(0))
                    || IsBadReadPtr(lpEntry, (UINT) CbFLATENTRY(lpEntry)))
                {
                    DebugTrace("Bad entry inside PR_REPLY_RECIPIENT_ENTRIES!\n");
                    break;
                }

                ulSize = lpEntry->cb;

                hr = HrCrackSenderEID(lpxpl, ulSize, lpEntry->abEntry, szFrom);

                if (!hr)
                {
                    wsprintf(szOutBuf, TEXT("%s%s"),
                        rgszTags[tagReplyTo], szFrom);
                    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
                            lstrlen(szOutBuf), &cbOut), ret);
                    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                            cbCRLF, &cbOut), ret);
                }

                lpb += offsetof (FLATENTRY, abEntry) + ((ulSize + 3) & -4L);
            }

            /* Add one more CR/LF pair after Reply Recipients */

            TraceFailedWrite(lpSof->lpVtbl->Write(lpSof,
                    szCRLF, cbCRLF, &cbOut), ret);
        }
    }
    else
    {
        wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagReplyTo], szFrom);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
                lstrlen(szOutBuf), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);
    }

    /* Write Date: field */

    if (FPropIndex(lpMsgProps, cMsgVals, PR_CLIENT_SUBMIT_TIME, &uli))
    {
        /* Property exists in message; write it to the stream */
        FormatFileTime(&lpMsgProps[uli].Value.ft, szOutBuf);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagDate],
                lstrlen(rgszTags[tagDate]), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
                lstrlen(szOutBuf), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);
    }

    /* Write To: & Cc: fields */

    while (TRUE)
    {
        /* Get a row from the Recipient Table */

        hr = lpTbl->lpVtbl->QueryRows(lpTbl, 1, 0, &lpRows);

        if (hr || !lpRows || (lpRows->cRows != 1))
            break;

        lpPropT = lpRows->aRow[0].lpProps;

        /* Throw away MAPI_ORIG and P1 Recipient Types */

        if ((lpPropT[0].Value.l != MAPI_TO) &&
            (lpPropT[0].Value.l != MAPI_CC))
        {
            FreeProws(lpRows);
            lpRows = NULL;
            lpPropT = NULL;
            continue;
        }

        /* Write Recipients as:
            '{To: | Cc: } Display Name [email-address]' */

        Assert((lpPropT[0].Value.l == MAPI_TO) ||
            (lpPropT[0].Value.l == MAPI_CC));

        wsprintf(szOutBuf, TEXT("%s%s[%s]"),
            rgszTags[tagDate + lpPropT[0].Value.l],
            lpPropT[3].Value.LPSZ, lpPropT[1].Value.LPSZ);

        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
                lstrlen(szOutBuf), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                cbCRLF, &cbOut), ret);

        /* Clean-Up */

        FreeProws(lpRows);
        lpRows = NULL;
        lpPropT = NULL;
    }

    /* Add one more CR/LF pair after recipients */

    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF, cbCRLF, &cbOut), ret);

    /* Write Subject: field */

    if (FPropIndex(lpMsgProps, cMsgVals, PR_SUBJECT, &uli))
    {
        /* Property exists and is small enough to not require a
           stream interface to be opened on it; just write it. */

        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagSubject],
                lstrlen(rgszTags[tagSubject]), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                cbCRLF, &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, lpMsgProps[uli].Value.LPSZ,
                lstrlen(lpMsgProps[uli].Value.LPSZ), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);
    }
    else if (!(hr = lpMessage->lpVtbl->OpenProperty(lpMessage, PR_SUBJECT,
                (LPIID) &IID_IStream, 0, 0, (LPUNKNOWN *) &lpStrm)))
    {
        /* Property exists and requires a stream interface to
           access all the data.  Copy between streams! */

        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagSubject],
                lstrlen(rgszTags[tagSubject]), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                cbCRLF, &cbOut), ret);

        while (TRUE)
        {
            hr = lpStrm->lpVtbl->Read(lpStrm, (LPVOID) rgchStrmBuf,
                MAX_STRM_BUF, &cbRead);

            if (hr || (cbRead == 0))
                break;          /* There's nothing to write; we're done! */

            TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, (LPVOID) rgchStrmBuf,
                    cbRead, &cbOut), ret);

            if (cbRead < MAX_STRM_BUF)
                break;          /* We've exhausted the stream; we're done! */
        }
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);

        lpStrm->lpVtbl->Release(lpStrm);
        lpStrm = NULL;
    }

    /* Write Priority: field */

    if (FPropIndex(lpMsgProps, cMsgVals, PR_PRIORITY, &uli))
    {
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof,
                rgszTags[tagPrioNormal - lpMsgProps[uli].Value.l],
                lstrlen(rgszTags[tagPrioNormal - lpMsgProps[uli].Value.l]),
                &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);
    }

    /* Write Contents: field */

    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagContents],
            lstrlen(rgszTags[tagContents]), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
            cbCRLFCRLF, &cbOut), ret);

    /* Text Item: */

    if (FPropIndex(lpMsgProps, cMsgVals, PR_BODY, &uli))
    {
        /* Property exists and is small enough to not require
           a stream interface to be opened on it; just copy it */

        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagTextItem],
                lstrlen(rgszTags[tagTextItem]), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                cbCRLF, &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, lpMsgProps[uli].Value.LPSZ,
                lstrlen(lpMsgProps[uli].Value.LPSZ), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);
    }
    else if (!(hr = lpMessage->lpVtbl->OpenProperty(lpMessage, PR_BODY,
                (LPIID) &IID_IStream, 0, 0, (LPUNKNOWN *) &lpStrm)))
    {
        /* Property exists and requires a stream interface to
           access all the data.  Copy between streams! */

        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagTextItem],
                lstrlen(rgszTags[tagTextItem]), &cbOut), ret);
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
                cbCRLF, &cbOut), ret);

        while (TRUE)
        {
            hr = lpStrm->lpVtbl->Read(lpStrm, (LPVOID) rgchStrmBuf,
                MAX_STRM_BUF, &cbRead);

            if (hr || (cbRead == 0))
                break;          /* There's nothing to write; we're done! */

            TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, (LPVOID) rgchStrmBuf,
                    cbRead, &cbOut), ret);

            if (cbRead < MAX_STRM_BUF)
                break;          /* We've exhausted the stream; we're done! */
        }
        TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
                cbCRLFCRLF, &cbOut), ret);

        lpStrm->lpVtbl->Release(lpStrm);
        lpStrm = NULL;
    }

    /* File Item: */

    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagFileItem],
            lstrlen(rgszTags[tagFileItem]), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, TEXT("MESSAGE.TNF"),
            lstrlen(TEXT("MESSAGE.TNF")), &cbOut), ret);
    TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
            cbCRLF, &cbOut), ret);

ret:
    lpxpl->FreeBuffer(lpMsgProps);
    lpxpl->FreeBuffer(lpSndrProps);
    FreeProws(lpRows);

    UlRelease(lpTbl);

    DebugTraceResult(HrIMsgToTextMsg(), hr);
    return hr;
}

/*
 -  HrPrepareRecipientTable
 -
 *  Purpose:
 *      Gets the Recipient Table from an IMAPIMessage and sets its
 *      columns, restricts the view, and sorts it (if sorts are
 *      supported).  All this in preparation of writing recipients
 *      to the destination message file.
 *
 *  Parameters:
 *      lpPropArray         Pointer to Transport Logon object
 *      lpMsg               Message to GetRecipientTable on
 *      lppTbl              Receives the RecipientTable
 *
 *  Returns:
 *      hr                  Indicating Success/Failure
 */

HRESULT
HrPrepareRecipientTable(LPSPropValue lpPropArray, LPMESSAGE lpMsg, LPMAPITABLE * lppTbl)
{
    HRESULT hr = hrSuccess;
    LPMAPITABLE lpTbl = NULL;

    SizedSSortOrderSet(2, rgSort) =
    {
        2, 0, 0,
        {
            PR_RECIPIENT_TYPE, TABLE_SORT_ASCEND,
                PR_ROWID, TABLE_SORT_ASCEND
        }
    };

    *lppTbl = NULL;

    hr = lpMsg->lpVtbl->GetRecipientTable(lpMsg, 0, &lpTbl);

    if (HR_FAILED(hr))
    {
        DebugTrace("GetRecipientTable() failed in HrPrepareRecipientTable()");
        goto ret;
    }

    /* SetColumns to: PR_RECIPIENT_TYPE, PR_EMAIL_ADDRESS, PR_ADDRTYPE,
       PR_DISPLAY_NAME, and PR_RESPONSIBILITY in that order */

    hr = lpTbl->lpVtbl->SetColumns(lpTbl, (LPSPropTagArray) &sptRecipProps,
        TBL_BATCH);

    if (HR_FAILED(hr))
    {
        DebugTrace("SetColumns() failed in HrPrepareRecipientTable()");
        goto ret;
    }

    /* Sort by: PR_RECIPIENT_TYPE (i.e. MAPI_TO, MAPI_CC, MAPI_BCC)
            and PR_ROWID, both in acsending order */

    hr = lpTbl->lpVtbl->SortTable(lpTbl, (LPSSortOrderSet) &rgSort, TBL_BATCH);

    if (hr)
    {
        /* Don't fail the call for no support!  Just return
           the table in whatever order it may be in by default. */

        if (GetScode(hr) == MAPI_E_NO_SUPPORT)
            hr = 0;
        else
            DebugTrace("SortTable() failed in HrPrepareRecipientTable()");
    }

    /* Seek to the beginning since sorting may have moved out position */

    hr = lpTbl->lpVtbl->SeekRow(lpTbl, BOOKMARK_BEGINNING, 0, NULL);

    if (hr)
    {
        DebugTrace("SeekRow() failed in HrPrepareRecipientTable()");
    }

ret:
    if (HR_FAILED(hr))
    {
        UlRelease(lpTbl);
    }
    else
        *lppTbl = lpTbl;

    DebugTraceResult(HrPrepareRecipientTable(), hr);
    return hr;
}

/*
 -  HrCrackSenderEID
 -
 *  Purpose:
 *      Does an OpenEntry() on the EntryID and GetProps() PR_DISPLAY_NAME
 *      and PR_EMAIL_ADDRESS.  Then formats the return string like:
 *          "Display Name[email-address]"
 *
 *  Parameters:
 *      lpxpl           Pointer to Transport Logon object
 *      cb              Count of bytes in EntryID
 *      lpb             Pointer to EntryID
 *      lpsz            Receives the formatted Name/Address pair
 *
 *  Returns:
 *      hr              Indicating Success/Failure
 *
 *  Note:
 *      No parameter validation!  I assume the caller knew what was
 *      being passed in and ensured all was well.
 */

HRESULT
HrCrackSenderEID(LPXPL lpxpl, ULONG cb, LPBYTE lpb, LPTSTR lpsz)
{
    HRESULT hr = hrSuccess;
    ULONG ulObjType;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPMAPIPROP lpMAPIProp = NULL;
    ULONG cVals = 0;
    LPSPropValue lpProps = NULL;

    const static SizedSPropTagArray(2, sptCracked) =
    {
        2,
        {
            PR_DISPLAY_NAME,
            PR_EMAIL_ADDRESS
        }
    };

    /* Open a Property Interface on this EntryID */

    hr = lpMAPISup->lpVtbl->OpenEntry(lpMAPISup, cb, (LPENTRYID) lpb,
        NULL, 0, &ulObjType, (LPUNKNOWN *) &lpMAPIProp);

    if (hr)
    {
        DebugTrace("OpenEntry() Failed in HrCrackSenderEID().\n");
        goto ret;
    }

    /* Get the 2 properties we need from this object */

    hr = lpMAPIProp->lpVtbl->GetProps(lpMAPIProp,
        (LPSPropTagArray) &sptCracked, 0, /* ansi */
        &cVals, &lpProps);

    if (hr || !cVals)
    {
        DebugTrace("GetProps() Failed in HrCrackSenderEID().\n");
        goto ret;
    }

    /* Assert that all went well so far!!! */

    Assert(lpProps);
    Assert(cVals == 2);
    Assert(lpProps[0].ulPropTag == PR_DISPLAY_NAME);
    Assert(lpProps[1].ulPropTag == PR_EMAIL_ADDRESS);

    /* Format our Name/Address pair as desired */

    wsprintf(lpsz, "%s[%s]", lpProps[0].Value.LPSZ, lpProps[1].Value.LPSZ);

ret:
    lpxpl->FreeBuffer(lpProps);

    UlRelease(lpMAPIProp);

    DebugTraceResult(HrCrackSenderEID(), hr);
    return hr;
}

/*
 -  FPropIndex
 -
 *  Purpose:
 *      Finds and returns (if it exists) the index of ulPropTag
 *      in the lpProps SPropValue array.
 *
 *  Parameters:
 *      lpProps         PropValue array to search through
 *      cVals           Count of properties in lpProps
 *      ulPropTag       PropTag to search for
 *      puli            Receives the index of ulPropTag in lpProps
 *
 *  Returns:
 *      TRUE/FALSE      TRUE if found, FALSE otherwise
 */

BOOL
FPropIndex(LPSPropValue lpProps, ULONG cVals, ULONG ulPropTag, ULONG * puli)
{
    Assert(lpProps);
    Assert(cVals);
    Assert(puli);

    while (cVals--)
    {
        if (lpProps[cVals].ulPropTag == ulPropTag)
        {
            *puli = cVals;
            return TRUE;
        }
    }
    return FALSE;
}

/*
 -  FormatFileTime
 -
 *  Purpose:
 *      Formats a Windows NT file time as a MAPI date/time string
 *      of the format:  yyyy/mm/dd hh:mm
 *
 *  Parameters:
 *      pft             Pointer to FILETIME to convert
 *      szTime          Destination string
 *
 *  Returns:
 *      void.
 */

void
FormatFileTime(FILETIME * pft, LPTSTR szTime)
{
    SYSTEMTIME systime;

    FileTimeToSystemTime(pft, &systime);
    wsprintf(szTime, "%04.4d/%02.2d/%02.2d %02.2d:%02.2d",
        systime.wYear, systime.wMonth, systime.wDay,
        systime.wHour, systime.wMinute);
}

/*
 -  FIsTextizedProp
 -
 *  Purpose:
 *      Used to determine if the property is one we wish to
 *      exclude from the TNEF encapsulation (i.e. one we've
 *      textized in the envelope of the message file).
 *
 *  Parameters:
 *      ulPropTag       PropTag to test
 *
 *  Return:
 *      BOOL            Indicating if the property is textized
 */

BOOL
FIsTextizedProp(ULONG ulPropTag)
{
    ULONG i;

    static SizedSPropTagArray(16, spta) =
    {
        16,
        {
            PR_SENDER_NAME,
            PR_SENDER_ENTRYID,
            PR_SENDER_SEARCH_KEY,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_ADDRTYPE,
            PR_SENT_REPRESENTING_NAME,
            PR_SENT_REPRESENTING_ENTRYID,
            PR_SENT_REPRESENTING_SEARCH_KEY,
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_SENT_REPRESENTING_ADDRTYPE,
            PR_REPLY_RECIPIENT_ENTRIES,
            PR_REPLY_RECIPIENT_NAMES,
            PR_SUBJECT,
            PR_CLIENT_SUBMIT_TIME,
            PR_BODY,
            PR_PRIORITY
        }
    };

    for (i = 0; i < spta.cValues; i++)
        if (spta.aulPropTag[i] == ulPropTag)
            return TRUE;

    return FALSE;
}

/*
 -  FreeMyAdrList
 -
 *  Purpose:
 *      Called by anyone who winds up with a MYADRLIST structure
 *      and wants to free it.
 *
 *  Parameters:
 *      lpxpl               Session context.
 *      lpMyAdrList         Structure to free.
 *
 *  Returns:
 *      void                Data in lpMyAdrList freed.
 *
 *  Operation:
 *      Walks through adrlist and frees all the memory.
 */

void
FreeMyAdrList(LPXPL lpxpl, LPMYADRLIST lpMyAdrList)
{
    ULONG ulT;
    LPSPropValue lpspvT;
    LPADRLIST lpAdrListT;

    /*  Clean up any adrlist stuff that's lying around. */

    if (lpMyAdrList)
    {
        lpAdrListT = lpMyAdrList->lpAdrList;

        if (lpAdrListT && lpAdrListT->cEntries)
        {
            for (ulT = 0; ulT < lpAdrListT->cEntries; ulT++)
            {
                lpspvT = (lpAdrListT->aEntries[ulT]).rgPropVals;

                lpxpl->FreeBuffer((LPVOID) lpspvT);
            }
        }
        lpxpl->FreeBuffer((LPVOID) lpAdrListT);
        lpxpl->FreeBuffer((LPVOID) lpMyAdrList);
    }
}

CHAR lpszEOM[] = "\n*** End of Message ***\n";
#define cchEOM (sizeof(lpszEOM) - 1)

STDMETHODIMP
PreprocessMessage (LPMAPISESSION lpSession,
    LPMESSAGE lpMessage,
    LPADRBOOK lpAdrBook,
    LPMAPIFOLDER lpFolder,
    LPALLOCATEBUFFER AllocateBuffer,
    LPALLOCATEMORE AllocateMore,
    LPFREEBUFFER FreeBuffer,
    ULONG FAR *lpcOutbound,
    LPMESSAGE FAR * FAR * lpppMessage,
    LPADRLIST FAR *lppRecipList)
{
    HRESULT hr;
    LPSTREAM lpstrm = NULL;
    ULONG cb;
    LARGE_INTEGER liTo = {0};

    hr = lpMessage->lpVtbl->OpenProperty (lpMessage,
                                    PR_BODY_A,
                                    (LPIID)&IID_IStream,
                                    0,
                                    MAPI_MODIFY,
                                    (LPUNKNOWN FAR *)&lpstrm);
    if (!HR_FAILED (hr))
    {
        hr = lpstrm->lpVtbl->Seek (lpstrm, liTo, STREAM_SEEK_END, NULL);
        if (!HR_FAILED (hr))
        {
            hr = lpstrm->lpVtbl->Write (lpstrm, lpszEOM, cchEOM, &cb);
            if (!HR_FAILED (hr) && (cb == cchEOM))
            {
                if (!HR_FAILED (hr = lpstrm->lpVtbl->Commit (lpstrm, 0L)))
                    hr = lpMessage->lpVtbl->SaveChanges (lpMessage, KEEP_OPEN_READWRITE);
            }
        }
        UlRelease (lpstrm);
    }

    *lpcOutbound = 0;
    *lpppMessage = NULL;

    DebugTraceResult (PreprocessMessage(), hr);
    return hr;
}

STDMETHODIMP
RemovePreprocessInfo (LPMESSAGE lpMessage)
{
    CHAR lpszBuf[sizeof(lpszEOM)] = {0};
    HRESULT hr;
    LARGE_INTEGER liTo;
    LPSTREAM lpstrm = NULL;
    ULARGE_INTEGER liSize;
    ULONG cb;
    BOOL fUpd = FALSE;

    hr = lpMessage->lpVtbl->OpenProperty (lpMessage,
                                    PR_BODY_A,
                                    (LPIID)&IID_IStream,
                                    0,
                                    MAPI_MODIFY,
                                    (LPUNKNOWN FAR *)&lpstrm);
    if (!HR_FAILED (hr))
    {
        liTo.HighPart =  -1;
        liTo.LowPart = (ULONG)(-(LONG)(cchEOM));
        hr = lpstrm->lpVtbl->Seek (lpstrm, liTo, STREAM_SEEK_END, &liSize);
        if (!HR_FAILED (hr))
        {
            hr = lpstrm->lpVtbl->Read (lpstrm, lpszBuf, cchEOM, &cb);
            if (!HR_FAILED (hr) && (cb == cchEOM))
            {
                if (!lstrcmpiA (lpszEOM, lpszBuf))
                {
                    if (!HR_FAILED (hr = lpstrm->lpVtbl->SetSize (lpstrm, liSize)) &&
                        !HR_FAILED (hr = lpstrm->lpVtbl->Commit (lpstrm, 0L)))
                        hr = lpMessage->lpVtbl->SaveChanges (lpMessage, KEEP_OPEN_READWRITE);
                }
            }
        }
    }
    UlRelease (lpstrm);
    DebugTraceResult (RemovePreprocessInfo(), hr);
    return hr;
}


XPQUEUE.H

/*
 -  X P Q U E U E . H
 -
 *  Purpose:
 *      Background transmit queue definitions for Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Structures used to build a adrlist from a recipient table. */

/* This is a lot like a ADRLIST except we keep track of the size of
the contained ADRLIST for resizing purposes. */

typedef struct _myadrlist
{
    ULONG cMaxEntries;
    LPADRLIST lpAdrList;
} MYADRLIST, *LPMYADRLIST;

/* Prototype of callback function which can be invoked as part of
operation of the HrBuildAdrList() function. */

typedef HRESULT (*LPMYCALLBACK) (LPXPL lpxpl,
    LPSPropValue lpPropArray,
    LPMESSAGE lpMessage,
    ULONG ulRecipType,
    LPTSTR lpszEmailAddress,
    BOOL FAR * lpfSent);

/* Build one or two ADRLISTs for use by send/receive logic */

HRESULT HrBuildAdrList (LPXPL lpxpl,
    LPSPropValue lpPropArray,
    LPMESSAGE lpMessage,
    LPMAPITABLE lpTable,
    BOOL fSetResponsibility,
    LPMYCALLBACK lpfnCallBack,
    LPMYADRLIST FAR * lppMyAdrListGood,
    LPMYADRLIST FAR * lppMyAdrListBad);

/* Send a single message from inside a restriction. */

HRESULT HrSendOneMessage (LPXPL lpxpl,
    LPSPropValue lpPropArray,
    LPMESSAGE lpMessage,
    ULONG ulRecipType,
    LPTSTR lpszEmailAddress,
    BOOL FAR * lpfSent);

/* Free a MYADRLIST structure */

void FreeMyAdrList (LPXPL lpxpl, LPMYADRLIST lpMyAdrList);


XPRCVMSG.C

/*
 -  X P R C V M S G . C
 -
 *  Purpose:
 *      Code to support the MAPI Transport SPI entry points for
 *      message reception.  This module contains the following
 *      SPI entry points:
 *
 *          Poll()
 *          StartMessage()
 *
 *      Additional support functions found here:
 *
 *          HrIMsgFromTextMsg()
 *          HrBuildSenderProps()
 *          HrAddRecipToAdrList()
 *          HrAddRecipToReplyList()
 *          HrMakeSearchKey()
 *          HrGetLine()
 *          FGetTagAndToken()
 *          FileTimeFromSzTime()
 *          SetFromMeFlag()
 *
 *      Also, the Idle() code in XPQUEUE.C will call into this module to
 *      find out if there's any mail to tell SpoolerNotify() about.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include <tnef.h>
#include <stdlib.h>
#include "xpsof.h"
#include "xptxtmsg.h"

/* Local function prototype(s) */

VOID SetFromMeFlag(LPXPL lpxpl, LPMESSAGE lpMsg);

/*
 -  Poll
 -
 *  Purpose:
 *      Called by the Spooler periodically in its idle loop. Also called from
 *      the Idle() entry point code.
 *
 *  Parameters:
 *      lpulIncoming        Pointer to a ULONG.
 *
 *  Returns:
 *      *lpulIncoming       Nonzero if any messages are found;
 *                          Zero if no messages were found.
 *
 *  Operation:
 *      This routine uses FindFirst/FindNext to look for message container
 *      files in the inbound directory. Because its FIND_DATA structure and
 *      the associated Handle is contained in the session structure, it's
 *      easy to keep context and pass it along to StartMessage().
 *
 *      Because it can be called from multiple places, and there's no
 *      guarantee that StartMessage() will be called between the time we
 *      signal the presence of inbound mail and the next time we're called,
 *      we maintain a boolean in the session structure which we set to TRUE
 *      when we find a message, and which StartMessage() sets to FALSE after
 *      processing that message. If we see it as TRUE when called, we return
 *      immediately with *lpulIncoming set.
 *
 *      Otherwise, we do a FindNext if a find handle is open. If it fails, we
 *      close the handle.
 *
 *      If we have no open handle (either because one wasn't open or because
 *      we just closed one), we do a FindFirst.
 *
 *      If we find a message container file, we set the boolean and
 *      *lpulIncoming and return. Else we close the find handle.
 *
 *      This function always returns SUCCESS_SUCCESS.
 */

STDMETHODIMP
XPL_Poll(LPXPL lpxpl, ULONG * lpulIncoming)
{
    HANDLE *lpPollHandle;
    LPWIN32_FIND_DATA lpFindData;
    LPMAPISUP lpMAPISup;
    LPSPropValue lpPropArray = lpxpl->lpPropArray;
    BOOL fFound = FALSE;
    DWORD dwIgnoreAttrs =
    FILE_ATTRIBUTE_READONLY |
    FILE_ATTRIBUTE_SYSTEM |
    FILE_ATTRIBUTE_DIRECTORY |
    FILE_ATTRIBUTE_TEMPORARY;

    lpPollHandle = &lpxpl->hInFindHandle;
    lpFindData = &lpxpl->wfdInFindData;

    lpMAPISup = lpxpl->lpMAPISup;

    /* Start out with default of no incoming message */

    *lpulIncoming = 0;

    /* Is inbound enabled for this session? */

    if (!(lpxpl->ulTransportStatus & STATUS_INBOUND_ENABLED))
        goto ret;

    /* See if we found one on a previous pass through here and haven't
       done a StartMessage() yet... */

    if (lpxpl->fFoundInMessage)
    {
        *lpulIncoming = 1;
        goto ret;
    }

    /*  One of the following is now true:

        A)  We have a search already active, use FindNextFile.
            If it fails, we will want to restart the search
            (in case something appeared in the search behind
            us.

        B)  We don't have a search active, use FindFirstFile.
    */

    /* Try the active search */

    if (*lpPollHandle != INVALID_HANDLE_VALUE)
    {
        fFound = FindNextFile(*lpPollHandle, lpFindData);

        /* If we fail, close the old search so we can start
           a new one below. */

        if (!fFound)
        {
            FindClose(*lpPollHandle);
            *lpPollHandle = INVALID_HANDLE_VALUE;
        }
    }

    /* If there was no search or if the old one was just closed above */

    if (*lpPollHandle == INVALID_HANDLE_VALUE)
    {
        HANDLE hFindT;
        TCHAR chFileName[MAX_PATH];

        /* Copy the directory name. Note that we trust the value in the
           profile to be correct, since the UI should have enforced a
           syntax that included a trailing : or \ in the spec. */

        lstrcpy(chFileName,
            (ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray)).Value.LPSZ);

        lstrcat(chFileName, TEXT("TNF*.TMP"));

        hFindT = FindFirstFile(chFileName, lpFindData);

        /* If nothing's found, we're done here. */

        if (hFindT == INVALID_HANDLE_VALUE)
            goto ret;

        /* Found something, continue along. */

        fFound = TRUE;
        *lpPollHandle = hFindT;
    }

    /*
        Here on a match. Exclude unwanted files.

        Any match with DIRECTORY, READONLY, SYSTEM or TEMPORARY attribute
        is ignored. Keep trying until we exhaust the current supply or we
        find a file without these attributes. Also, ignore files smaller
        than some arbitrary size, they're probably trash.
    */

    while (fFound)
    {
        /*  We found a file. Does it have any of the attributes we
            want to ignore? If not, get out. If so, try another. */

#define MIN_USEFUL_FILESIZE ((DWORD) 64)

        if ((!((lpFindData)->dwFileAttributes & dwIgnoreAttrs)) &&
            ((lpFindData->nFileSizeHigh != 0) ||
                (lpFindData->nFileSizeLow >= MIN_USEFUL_FILESIZE)))
            break;

        fFound = FindNextFile(*lpPollHandle, lpFindData);
    }

    if (fFound)
        lpxpl->fFoundInMessage = TRUE;
    else
    {
        FindClose(*lpPollHandle);
        *lpPollHandle = INVALID_HANDLE_VALUE;
    }

ret:
    if (lpxpl->fFoundInMessage)
    {
        /*  Got a hit. If fFound is set, we found it this time. If fFound
            is not set, we got it before and we were called again before
            StartMessage(). */

        *lpulIncoming = 1;
        DebugTrace("XPL_Poll returns *lpulIncoming=%lx\n", *lpulIncoming);
    }
    else if (lpxpl->ulTransportStatus & STATUS_INBOUND_FLUSH)
    {
        lpxpl->ulTransportStatus &= ~STATUS_INBOUND_FLUSH;
        (void)HrUpdateTransportStatus(lpxpl, 0L);
    }
    return hrSuccess;
}

/*
 -  StartMessage
 -
 *  Purpose:
 *      Called by the Spooler for receipt of an inbound message. This sequence
 *      of events is set off by a SpoolerNotify (NOTIFY_NEWMAIL) or a Poll()
 *      returning *lpulIncoming != 0.
 *
 *  Parameters:
 *      ulFlags             Flags from the Spooler. Currently
 *                          there are no StartMessage() flags
 *                          defined in the MAPI 1.0 TSPI.
 *      lpMessage           Pointer to message object into which
 *                          the Spooler wants the transport to
 *                          store the incoming message.
 *      lpulMsgRef          Pointer to where the transport should
 *                          store a unsigned long for use in
 *                          identifying TransportNotify() message
 *                          events. Initialized to 0 by the
 *                          Spooler. We don't do anything with
 *                          message events in this transport, so
 *                          we don't store anything there.
 *
 *  Returns:
 *      (HRESULT)           MAPI_E_BUSY if the Spooler calls
 *                          here again while we're busy, else
 *                          errors encountered if any.
 *      (*lpMessage)        Contains the new input message if any.
 *
 *  Operation:
 *      Checks for the result of a FindFirst/FindNext operation in the
 *      session's FIND_DATA buffer. If none, exit without changing the input
 *      message (this should result in the new message being destroyed when
 *      the Spooler releases its object).
 *
 *      If a file was found, attempt to open it. If the open fails and the
 *      reason is not attributable to network locking, return the error; if
 *      attributable to network locking (like if a peer's transmit code is
 *      writing a container file), return no error. In either case, exit
 *      without changing the input message.
 *
 *      Open a stream interface on the input message file.  Pass this off
 *      to HrIMsgFromTxtMsg() to convert all the textized envelope properties
 *      to SPropValues which are then set on the spoolers IMessage.  When
 *      we return, the input stream will be pointing to the beginning of
 *      the embedded TNEF encapsulation in the input message file.  Make
 *      the appropriate calls to TNEF to extract the properties from the
 *      encapsulation and set them on the message.
 *
 *      Finally, SaveChanges() on the Spooler's message to retain the result,
 *      delete the container file, and reset the "found message" flag for the
 *      benefit of Poll().
 */

STDMETHODIMP
XPL_StartMessage(LPXPL lpxpl,
    ULONG ulFlags,
    LPMESSAGE lpMessage,
    ULONG * lpulMsgRef)
{
    LPWIN32_FIND_DATA lpFindData;
    LPSPropValue lpMyIDArray = NULL;
    LPSPropValue lpPropArray = NULL;
    SPropValue rgDelegateProps[3];
    SPropValue spvTime;
    LPSPropProblemArray lpProblems = NULL;
    TCHAR rgchFileName[MAX_PATH];
    LPTSTR lptMyDir;
    HRESULT hResult = 0;
    SCODE sc = 0;
    BOOL fUpdatedStatus = FALSE;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    WORD wKey = 0;
    SPropTagArray sptExcludeNone = {0};
    LPITNEF lpTnef = (LPITNEF) NULL;
    LPSTREAM lpSof = (LPSTREAM) NULL;
    LPSTREAM lpXPSof = (LPSTREAM) NULL;
    LPSTnefProblemArray lptpa = NULL;

    /* Reset our .2 second timer before starting. */

    HrCheckSpoolerYield(lpMAPISup, TRUE);

    /* Do this first so we know at exit time if we tried to open a file */

    rgchFileName[0] = '\0';

    /* Simple re-entrancy test. Should never happen anyway. */

    if (lpxpl->ulTransportStatus & STATUS_INBOUND_ACTIVE)
    {
        hResult = ResultFromScode(MAPI_E_BUSY);
        DebugTrace("XPL_StartMessage reentrancy test failed\n");
        goto ret;
    }

    /* Signal that we're downloading. */

    *lpulMsgRef = 1L;           /* This is good enough. */
    lpxpl->ulTransportStatus |= STATUS_INBOUND_ACTIVE;
    hResult = HrUpdateTransportStatus(lpxpl, 0L);
    if (hResult)
    {
        DebugTrace("Update of status row failed\n");
        goto ret;
    }
    fUpdatedStatus = TRUE;

    /* Get the current findfirst/findnext buffer */

    lpFindData = &lpxpl->wfdInFindData;

    /* Is there actually a message available? If not, go away. */

    if (!lpxpl->fFoundInMessage)
        goto ret;

    sc = ScCopySessionProps(lpxpl, &lpPropArray, &lpMyIDArray);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        goto ret;
    }

    /* Build file name of incoming message */

    lptMyDir = ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray).Value.LPSZ;

    lstrcpy(rgchFileName, lptMyDir);
    lstrcat(rgchFileName, lpFindData->cFileName);

    PrintfTransportLog(TEXT("Start Incoming: %s"), rgchFileName);

    hResult = OpenStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
        STGM_READ, rgchFileName, NULL, &lpSof);

    if (hResult)
    {
        sc = GetScode(hResult);
        DebugTrace("OpenStreamOnFile() failed in StartMessage()\n");
        PrintfTransportLog(TEXT("OpenStreamOnFile(%s) returns %lx"), rgchFileName, sc);

        /*  If "Access Denied" just don't do anything.
            It's usually a situation that will clear up (when another
            instance of this transport closes the msg file or when the
            other system comes online) */

        if (sc == MAPI_E_NO_ACCESS)
            hResult = hrSuccess;

        /*  If "Not Found", clear fFoundInMessage so that we'll do a
            FindNext. */

        if (sc == MAPI_E_NOT_FOUND)
            lpxpl->fFoundInMessage = FALSE;

        goto ret;
    }

    /* Wrap the Stream-On-File object in our buffered wrapper. */

    hResult = HrWrapStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
            XPSOF_READ, lpSof, &lpXPSof);

    if (HR_FAILED(hResult))
    {
        DebugTrace("HrWrapStreamOnFile() failed\n");
        goto ret;
    }

    /* Check our .2 second timer before attempting to receive */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message download.\n");
        goto ret;
    }

    hResult = HrIMsgFromTextMsg(lpxpl, lpPropArray, lpMessage, lpXPSof);

    if (HR_FAILED(hResult))
    {
        DebugTrace("HrIMsgFromTextMsg() failed\n");
        goto ret;
    }

    /* Check our .2 second timer again. */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message download.\n");
        goto ret;
    }

    /* The 0x01AF if a key used to identify the TNEF.  A real transport
       should generate a pseudo-random sequence for this field. */

    hResult = OpenTnefStream(lpxpl->lpMAPISup, lpXPSof,
            TEXT("MAPIMAIL.DAT"), TNEF_DECODE, lpMessage, 0x01AF, &lpTnef);

    if (HR_FAILED(hResult))
    {
        DebugTrace("OpenTNEF() failed.\n");
        goto ret;
    }

    /* Extract properties from the incomming message and add them to
       the target message. */

    hResult = lpTnef->lpVtbl->ExtractProps(lpTnef,
        TNEF_PROP_EXCLUDE, &sptExcludeNone, &lptpa);
    lpxpl->FreeBuffer(lptpa);
    if (HR_FAILED(hResult))
    {
        DebugTrace("GetTNEFProps() failed.\n");
        goto ret;
    }

    /* Check our .2 second timer again. */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message download.\n");
        goto ret;
    }

    /* All the properties have been copied over. Set the PR_RECEIVED_BY
       delegate properties (all the others were set by the transmitter) */

    if (lpMyIDArray)
    {
        Assert(!IsBadReadPtr(lpMyIDArray, 3 * sizeof(SPropValue)));
        Assert(lpMyIDArray[0].ulPropTag == PR_SENDER_ENTRYID);
        Assert(lpMyIDArray[1].ulPropTag == PR_SENDER_NAME);
        Assert(lpMyIDArray[2].ulPropTag == PR_SENDER_SEARCH_KEY);

        memcpy(rgDelegateProps, lpMyIDArray, 3 * sizeof(SPropValue));
        rgDelegateProps[0].ulPropTag = PR_RECEIVED_BY_ENTRYID;
        rgDelegateProps[1].ulPropTag = PR_RECEIVED_BY_NAME;
        rgDelegateProps[2].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;

        /* At this point we have all the delegate properties set. Put them into
           the old message and then we'll just get them on the CopyTo(). */

        hResult = lpMessage->lpVtbl->SetProps(lpMessage, 3,
            rgDelegateProps, &lpProblems);

        if (hResult)
        {
            DebugTrace("SetProps of Receiver ID to message failed.\n");
            goto ret;
        }

        if (lpProblems)
        {
            /* If there were problems, let's dump them to the debugger. */

            DebugTraceProblems("XPL_StartMessage", lpProblems);

            lpxpl->FreeBuffer(lpProblems);
            lpProblems = NULL;
        }
    }

    /* Set the Received Time to be the last-modified time on the file. */

    spvTime.ulPropTag = PR_MESSAGE_DELIVERY_TIME;
    spvTime.Value.ft = lpFindData->ftLastWriteTime;

    hResult = lpMessage->lpVtbl->SetProps(lpMessage, 1, &spvTime, NULL);

    if (hResult)
    {
        DebugTrace("SetProps of PR_MESSAGE_DELIVERY_TIME failed.\n");
        goto ret;
    }

    /*  Finished with all properties and recipients now, SaveChanges
        on the message. */

    /* Check our .2 second timer again. */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message download.\n");
        goto ret;
    }

    hResult = lpMessage->lpVtbl->SaveChanges(lpMessage, 0L);

    if (hResult)
    {
        DebugTrace("SaveChanges on incoming message failed.\n");
        goto ret;
    }

    /* Finally, set the found message flag so we'll get another file. */

    lpxpl->fFoundInMessage = FALSE;

ret:
    /* Log end of incoming if we logged start. */

    if (*rgchFileName)
        PrintfTransportLog(TEXT("End Incoming: %s"), rgchFileName);

    UlRelease(lpTnef);
    UlRelease(lpXPSof);
    UlRelease(lpSof);

    /* If we got the message into the store, delete the inbound file. */

    if (!(HR_FAILED (hResult)) && *rgchFileName)
        (void)DeleteFile(rgchFileName);

    /* Release the prop tag array and/or problem array if any */

    lpxpl->FreeBuffer(lpPropArray);
    lpxpl->FreeBuffer(lpMyIDArray);

    /* Reset download status if set. */

    if (fUpdatedStatus)
    {
        lpxpl->ulTransportStatus &= ~STATUS_INBOUND_ACTIVE;
        (void)HrUpdateTransportStatus(lpxpl, 0L);
    }

    DebugTraceResult(XPL_StartMessage, hResult);
    return hResult;
}

/*
 -  HrIMsgFromTextMsg
 -
 *  Purpose:
 *      Called by StartMessage() to read a text formatted message file,
 *      containing a TNEF encapsulation, and converting it into a
 *      MAPI Message.  The TNEF DLL is used to decode the binary portion
 *      of this message into all the correct IMessage components.
 *
 *  Parameters:
 *      lpxpl               Pointer to Transport Logon object
 *      lpPropArray         Array of the transports logon properties
 *      lpMessage           Message to receive into
 *      lpSof               Pointer to the stream interface
 *
 *  Returns:
 *      hr                  Indicating Success/Failure
 *
 *  Operation:
 *      Read each Tag out of the text file and process its Token according
 *      to my rules for the Tag ID.  PR_SUBJECT and PR_BODY are automatically
 *      streamed into the message, PR_CLIENT_SUBMIT_TIME, PR_PRIORITY,
 *      PR_SENDER_NAME, and PR_SENDER_ENTRYID are added with SetProps().
 *      All To: and Cc: recipients are added by building an AdrList
 *      and doing a ModifyRecipients() on the message.  When we're finished
 *      here, the file pointer in the input stream will (hopefully) be
 *      left pointing to the start of the TNEF encapsulation.
 */

HRESULT
HrIMsgFromTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof)
{
    SCODE sc;
    HRESULT hr = hrSuccess;
    BOOL fHaveTagAndToken = FALSE;
    TCHAR szLine[MAX_LINE];
    ULONG cbRead;
    ULONG ulTag;
    LPTSTR lpszToken;
    ULONG cValues = 0;
    LPSPropValue lpMsgProps = NULL;
    LPMYADRLIST lpMyRecipList = NULL;
    LPTSTR lpszAddrType;
    LPTSTR lpszReplyNames = NULL;
    ULONG cbReplyEntryList = 0;
    LPFLATENTRYLIST lpReplyEntryList = NULL;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;

    lpszAddrType = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ;

    sc = lpxpl->AllocateBuffer(MAX_TXTMSG_PROPS * sizeof(SPropValue),
            &lpMsgProps);

    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("Allocation failed.\n");
        goto ret;
    }

    memset(lpMsgProps, 0, MAX_TXTMSG_PROPS * sizeof(SPropValue));

    sc = lpxpl->AllocateBuffer(sizeof(MYADRLIST), &lpMyRecipList);

    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("Allocation failed.\n");
        goto ret;
    }

    memset(lpMyRecipList, 0, sizeof(MYADRLIST));

    while (TRUE)
    {
        /* fHaveTagAndToken gets set only when we return from
           HrGetStreamedProp and the call actually gets
           the next tagged line and tokenizes it for us. */

        if (!fHaveTagAndToken)
        {
            hr = HrGetLine(lpSof, MAX_LINE, szLine, &cbRead);

            if (hr)
                break;

            if (szLine[0] == '\0')
                continue;
        }

        if (fHaveTagAndToken || FGetTagAndToken(szLine, &ulTag, &lpszToken))
        {
            fHaveTagAndToken = FALSE;

            /* Check our .2 second timer again. */

            sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

            if (sc == MAPI_W_CANCEL_MESSAGE)
            {
                DebugTrace("Cancelling message download.\n");
                goto ret;
            }

            switch (ulTag)
            {
            case tagFrom:
            case tagRepresenting:
                /* Create an addressing triplet (DisplayName, EntryID,
                   SearchKey) from this line and add these properties
                   to the array of props. */

                hr = HrBuildSenderProps(lpxpl, lpPropArray, ulTag, lpszToken,
                        lpszAddrType, lpMessage, &cValues, lpMsgProps);

                if (hr)
                {
                    DebugTrace("HrBuildSenderProps() failed.\n");
                    goto ret;
                }
                break;

            case tagReplyTo:
                hr = HrAddRecipToReplyList(lpxpl, lpszToken, lpszAddrType,
                    &lpszReplyNames, &cbReplyEntryList, &lpReplyEntryList);

                if (hr)
                    goto ret;
                break;

            case tagDate:
                lpMsgProps[cValues].ulPropTag = PR_CLIENT_SUBMIT_TIME;
                FileTimeFromSzTime(lpszToken, &lpMsgProps[cValues++].Value.ft);
                break;

            case tagTo:
            case tagCc:
                hr = HrAddRecipToAdrList(lpxpl, (LONG) ulTag - tagDate, lpszToken,
                    lpszAddrType, lpMyRecipList);
                if (hr)
                    goto ret;
                break;

            case tagSubject:
            case tagTextItem:
                hr = HrGetStreamedProp(lpxpl, lpSof, lpMessage,
                        ((ulTag == tagSubject) ? PR_SUBJECT : PR_BODY),
                        &cValues, lpMsgProps, szLine, &ulTag, &lpszToken);

                if (HR_FAILED(hr))
                    goto ret;

                if(S_OK ==GetScode(hr))
                    fHaveTagAndToken = TRUE;
                break;

            case tagPrioLow:
            case tagPrioNormal:
            case tagPrioUrgent:
                lpMsgProps[cValues].ulPropTag = PR_PRIORITY;
                lpMsgProps[cValues++].Value.l = tagPrioNormal - ulTag;
                break;

            case tagFileItem:
                goto ret;

            case tagMessage:
            case tagBcc:
            case tagContents:
            default:
                break;
            }   /* end switch() */
        }       /* end if()     */
    }           /* end while()  */

ret:
    if (lpszReplyNames && lpReplyEntryList)
    {
        lpMsgProps[cValues].ulPropTag = PR_REPLY_RECIPIENT_NAMES;
        lpMsgProps[cValues++].Value.LPSZ = lpszReplyNames;

        lpMsgProps[cValues].ulPropTag = PR_REPLY_RECIPIENT_ENTRIES;
        lpMsgProps[cValues].Value.bin.cb = cbReplyEntryList;
        lpMsgProps[cValues++].Value.bin.lpb = (LPBYTE) lpReplyEntryList;
    }

    if (cValues)
        lpMessage->lpVtbl->SetProps(lpMessage, cValues, lpMsgProps, NULL);

    if (lpMyRecipList && lpMyRecipList->lpAdrList)
    {
        hr = lpMessage->lpVtbl->ModifyRecipients(lpMessage,
            MODRECIP_ADD, lpMyRecipList->lpAdrList);

        FreeMyAdrList(lpxpl, lpMyRecipList);

        if (hr)
            DebugTrace("ModifyRecipients failed.\n");
    }
    else
        lpxpl->FreeBuffer(lpMyRecipList);

    lpxpl->FreeBuffer(lpMsgProps);
    lpxpl->FreeBuffer(lpszReplyNames);
    lpxpl->FreeBuffer(lpReplyEntryList);

    DebugTraceResult(HrIMsgFromTextMsg(), hr);

    return hr;
}

/*
 -  HrBuildSenderProps
 -
 *  Purpose:
 *      Creates the 5 identity properties: PR_***_SEARCH_KEY,
 *      PR_***_NAME, PR_***_ENTRYID, PR_***_EMAIL_ADDRESS,
 *      PR_***_ADDRTYPE, where *** is either SENDER or
 *      SENT_REPRESENTING.
 *
 *  Parameters:
 *      lpxpl               The transports logon object
 *      lpPropArray         The session logon properties
 *      ulTag               Either tagFrom or tagRepresenting
 *      lpszToken           Display name and address for triplet
 *      lpszAddrType        This transports address type
 *      lpMessage           The spoolers IMessage object
 *      lpcValues           Count of and index into lpMsgProps
 *      lpMsgProps          Array of message properties we are building
 *
 *  Returns:
 *      hr                  Indicating Success/Failure
 */

HRESULT
HrBuildSenderProps(LPXPL lpxpl,
    LPSPropValue lpPropArray,
    ULONG ulTag,
    LPTSTR lpszToken,
    LPTSTR lpszAddrType,
    LPMESSAGE lpMessage,
    ULONG * lpcValues,
    LPSPropValue lpMsgProps)
{
    SCODE sc;
    HRESULT hr;
    LPTSTR lpszDisplayName = NULL;
    LPTSTR lpszAddress = NULL;
    LPTSTR lpsz;
    ULONG cbEntryID = 0;
    LPBYTE lpEntryID = NULL;
    LPBYTE lpb = NULL;
    ULONG cbSK;
    LPBYTE lpSearchKey;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    ULONG cValues = *lpcValues;

    lpsz = strtok(lpszToken, "[");

    sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1,
        lpMsgProps, &lpszDisplayName);

    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("AllocateMore failed.\n");
        goto ret;
    }

    lstrcpy(lpszDisplayName, lpsz);

    lpsz = strtok(NULL, "]");

    sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1,
        lpMsgProps, &lpszAddress);

    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("AllocateMore failed.\n");
        goto ret;
    }

    lstrcpy(lpszAddress, lpsz);

    /* Create OneOff Entry ID for Sender/Delegate */

    hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
        lpszDisplayName, lpszAddrType, lpszAddress, 0,
        &cbEntryID, (LPENTRYID FAR *) &lpEntryID);

    if (hr)
    {
        DebugTrace("CreateOneOff() failed.\n");
        goto ret;
    }

    /* Chain the EntryID to the lpMsgProp block */

    sc = lpxpl->AllocateMore(cbEntryID, lpMsgProps, &lpb);

    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("AllocateMore failed.\n");
        goto ret;
    }

    if (cbEntryID)
        memcpy(lpb, lpEntryID, (size_t) cbEntryID);

    lpxpl->FreeBuffer(lpEntryID);
    lpEntryID = NULL;

    /* Make the PR_***_SEARCH_KEY */

    hr = HrMakeSearchKey(lpxpl, lpMsgProps, lpszAddrType,
        lpszAddress, &cbSK, &lpSearchKey);

    if (hr)
    {
        DebugTrace("HrMakeSearchKey() failed.\n");
        goto ret;
    }

    if (ulTag == tagFrom)
    {
        lpMsgProps[cValues].ulPropTag = PR_SENDER_NAME;
        lpMsgProps[cValues + 1].ulPropTag = PR_SENDER_ENTRYID;
        lpMsgProps[cValues + 2].ulPropTag = PR_SENDER_SEARCH_KEY;
        lpMsgProps[cValues + 3].ulPropTag = PR_SENDER_ADDRTYPE;
        lpMsgProps[cValues + 4].ulPropTag = PR_SENDER_EMAIL_ADDRESS;

        if (!lstrcmpi(lpszAddress,
                ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS,
                    lpPropArray).Value.LPSZ))
            SetFromMeFlag(lpxpl, lpMessage);
    }
    else
    {
        Assert(ulTag == tagRepresenting);
        lpMsgProps[cValues].ulPropTag = PR_SENT_REPRESENTING_NAME;
        lpMsgProps[cValues + 1].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
        lpMsgProps[cValues + 2].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
        lpMsgProps[cValues + 3].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
        lpMsgProps[cValues + 4].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
    }

    lpMsgProps[cValues++].Value.LPSZ = lpszDisplayName;
    lpMsgProps[cValues].Value.bin.cb = cbEntryID;
    lpMsgProps[cValues++].Value.bin.lpb = lpb;
    lpMsgProps[cValues].Value.bin.cb = cbSK;
    lpMsgProps[cValues++].Value.bin.lpb = lpSearchKey;
    lpMsgProps[cValues++].Value.LPSZ = lpszAddrType;
    lpMsgProps[cValues++].Value.LPSZ = lpszAddress;

    *lpcValues = cValues;

ret:
    lpxpl->FreeBuffer(lpEntryID);

    DebugTraceResult(HrBuildSenderProps(), hr);
    return hr;
}

/*
 -  HrGetStreamedProp
 -
 *  Purpose:
 *      Reads either the PR_BODY or PR_SUBJECT from the message file and

*      adds it to the spoolers IMessage.
 *
 *  Parameters:
 *      lpSof               The stream we are reading the message from
 *      lpMsg               The spoolers IMessage we are building up
 *      ulPropTag           Either PR_SUBJECT or PR_BODY
 *      lpcValues           Current size of the lpMsgProps array
 *      lpMsgProps          The message property array we are building
 *      lpszLine            Char array we use to read lines into
 *      lpulTag             The text file tag we return as a side effect
 *      lppszToken          The token we return as a side effect
 *
 *  Returns:
 *      hr                  Indicating Success/Failure
 *
 *  10/5/95 the functon can return S_FALSE when the call to OpenProperty fails.
 *          in this case the property still gets set using SetProps, but we don't
 *          get the next token for the caller. So S_FALSE is to inform the
 *          caller that we didn't get token for him and he has to do it himself.
 */

HRESULT
HrGetStreamedProp(LPXPL lpxpl,
    LPSTREAM lpSof,
    LPMESSAGE lpMsg,
    ULONG ulPropTag,
    ULONG * lpcValues,
    LPSPropValue lpMsgProps,
    LPTSTR lpszLine,
    ULONG * lpulTag,
    LPTSTR * lppszToken)
{
    SCODE sc;
    HRESULT hr;
    LPSTREAM lpStrm = NULL;
    LPTSTR lpsz = NULL;
    ULONG cbCRLF = lstrlen(szCRLF);
    ULONG cbRead;
    ULONG cbWritten;

    #define cbSubjMax 4096
    LPSTR szSubj = NULL;

    sc = lpxpl->AllocateBuffer(cbSubjMax, &szSubj);
    if (sc)
    {
        hr = ResultFromScode(sc);
        DebugTrace("Allocation failed.\n");
        goto ret;
    }

    *szSubj = '\0';

    hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

    if (hr)
    {
        DebugTrace("HrGetLine failed.\n");
        goto ret;
    }

    if (ulPropTag == PR_SUBJECT)
    {
        /* Once in the context of the subject, go until we reach the
           next tag.  Then return and process the next tag and token */

        UINT cbCurrentSubj = 0;
        while (!FGetTagAndToken(lpszLine, lpulTag, lppszToken))
        {
            if(cbCurrentSubj < cbSubjMax)
            {
                cbCurrentSubj += lstrlen(lpszLine);

                if(cbCurrentSubj < cbSubjMax)
                    lstrcat(szSubj, lpszLine);
            }

            hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

            if (hr)
                goto ret;
        }

        /* this is in order to get inside the 'if' clause after OpenProperty*/
        hr = 1;
    }
    else
    {
    /* If we fail to open a stream on the property, then slam
       this line into the property array and break outta here */

    hr = lpMsg->lpVtbl->OpenProperty(lpMsg, ulPropTag,
        (LPIID) &IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY,
        (LPUNKNOWN *) &lpStrm);
    }

    if (hr)
    {
        LPSTR szBuf = (ulPropTag == PR_SUBJECT) ? szSubj : lpszLine;
        sc = lpxpl->AllocateMore(lstrlen(szBuf) + 1, lpMsgProps, &lpsz);

        if (sc)
        {
            hr = ResultFromScode(sc);
            DebugTrace("Allocation failed.\n");
            goto ret;
        }

        lstrcpy(lpsz, szBuf);
        lpMsgProps[*lpcValues].ulPropTag = ulPropTag;
        lpMsgProps[(*lpcValues)++].Value.LPSZ = lpsz;

        hr = (ulPropTag == PR_SUBJECT) ? hrSuccess : ResultFromScode(S_FALSE);
        goto ret;
    }

    /* Once we're in the context of the body, the only
       valid Tag is the TNEF File Item Tag.  So, we
       ignore all other message text lines that may
       contain a Tag (by chance). */

    while (TRUE)
    {
        FGetTagAndToken(lpszLine, lpulTag, lppszToken);

        if ((*lpulTag == tagFileItem) &&
            !lstrcmp(*lppszToken, "MESSAGE.TNF"))
            break;

        TraceFailedWrite(hr = lpStrm->lpVtbl->Write(lpStrm, lpszLine,
                lstrlen(lpszLine), &cbWritten), ret);

        if (cbRead < MAX_LINE - 1)
        {
            TraceFailedWrite(lpStrm->lpVtbl->Write(lpStrm,
                    szCRLF, cbCRLF, &cbWritten), ret);
        }

        hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

        if (hr)
            break;
    }

ret:
    UlRelease(lpStrm);

    lpxpl->FreeBuffer(szSubj);

    DebugTraceResult(HrGetStreamedProp(), hr);
    return hr;
}

/*
 -  HrAddRecipToAdrList
 -
 *  Purpose:
 *      Called by HrIMsgFromTextMsg() to add a single recipient to the
 *      AdrList.  Pre-allocates space (cMaxEntries) for 10 AdrEntrys the
 *      first time called.  If all cMaxEntries slots are filled, then we
 *      ReAlloc for (cMaxEntries + cMaxEntries/2) more slots and continue
 *      adding the recipient.  New memory is allocated for the attributes
 *      passed in and chained-up to the rgProps array.
 *
 *  Parameters:
 *      lpxpl           Needed for access to support obj and mem allocators
 *      ulRecipType     MAPI_TO or MAPI_CC
 *      lpszNameAddr    recipient token (Format: Display Name[email-address] )
 *      lpszAddrType    This transports Address Type
 *      lpMyAdrList     Pointer to the Recipient List
 *
 *  Returns:
 *      hr              Indicating Suucess/Failure
 */

HRESULT
HrAddRecipToAdrList(LPXPL lpxpl, LONG lRecipType,
    LPTSTR lpszNameAddr, LPTSTR lpszAddrType, LPMYADRLIST lpMyAdrList)
{
    HRESULT hr = hrSuccess;
    SCODE sc;
    BOOL fAlloc = FALSE;
    BOOL fReAlloc = FALSE;
    ULONG cb;
    ULONG cMaxEntries;
    LPADRLIST lpAdrList = NULL;
    LPSPropValue rgProps = NULL;
    LPTSTR lpsz;
    LPTSTR lpszDisplayName = NULL;
    LPTSTR lpszAddress = NULL;
    LPTSTR lpszAddrTypeT = NULL;
    ULONG cbEntryID = 0;
    LPBYTE lpEntryID = NULL;
    LPBYTE lpb = NULL;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    enum enumRecipProps
    {
        iRecipType,
        iDisplayName,
        iAddrType,
        iAddress,
        iEntryID,
        cRecipProps
    };

    /* Determine if we need to Allocate or ReAllocate memory */

    if (!lpMyAdrList->cMaxEntries)
    {
        /* First time in; list should be NULL */
        Assert(lpMyAdrList->lpAdrList == NULL);
        fAlloc = TRUE;
        cMaxEntries = 10;
    }
    else if (lpMyAdrList->lpAdrList &&
        (lpMyAdrList->lpAdrList->cEntries == lpMyAdrList->cMaxEntries))
    {
        /* List is full; we need to ReAlloc */
        fReAlloc = TRUE;
        cMaxEntries = lpMyAdrList->cMaxEntries + lpMyAdrList->cMaxEntries / 2;
    }
    else
    {
        /* List exists and is not full; just point to it */
        Assert(lpMyAdrList->lpAdrList);
        Assert(lpMyAdrList->lpAdrList->cEntries < lpMyAdrList->cMaxEntries);
        lpAdrList = lpMyAdrList->lpAdrList;
    }

    /* If the list was NULL or full we'll Alloc/ReAlloc */

    if (fAlloc || fReAlloc)
    {
        cb = CbNewADRLIST(cMaxEntries);
        sc = lpxpl->AllocateBuffer(cb, &lpAdrList);
        if (sc)
        {
            DebugTrace("AllocateBuffer() failed in HrAddRecipToAdrList()");
            goto ret;
        }

        /* Zero-out new list */

        memset(lpAdrList, 0, (size_t) cb);

        if (fReAlloc)
        {
            /* We're ReAllocing; copy old list into new memory */

            cb = CbNewADRLIST(lpMyAdrList->lpAdrList->cEntries);
            if (cb)
                memcpy(lpAdrList, lpMyAdrList->lpAdrList, (size_t) cb);

            /* Free old list */

            lpxpl->FreeBuffer(lpMyAdrList->lpAdrList);
        }

        /* Fix-up size and pointer elements */

        lpMyAdrList->cMaxEntries = cMaxEntries;
        lpMyAdrList->lpAdrList = lpAdrList;
    }

    /* Allocate room for cRecipProps PropValues and chain memory needed
       for AdrEntry data to the rgProps block to make freeing easier. */

    sc = lpxpl->AllocateBuffer(cRecipProps * sizeof(SPropValue), &rgProps);

    if (sc)
    {
        DebugTrace("AllocateBuffer() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* Allocate memory for AddrType */

    sc = lpxpl->AllocateMore(lstrlen(lpszAddrType) + 1, rgProps, &lpszAddrTypeT);

    if (sc)
    {
        DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* Copy AddrType into chained memory buffer */

    lstrcpy(lpszAddrTypeT, lpszAddrType);

    /* Break lpszNameAddr into lpszDisplayName and lpszAddress */

    lpsz = strtok(lpszNameAddr, "[");

    sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1, rgProps, &lpszDisplayName);

    if (sc)
    {
        DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* Copy Display Name into chained memory buffer */

    lstrcpy(lpszDisplayName, lpsz);

    lpsz = strtok(NULL, "]");

    sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1, rgProps, &lpszAddress);

    if (sc)
    {
        DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* Copy Address into chained memory buffer */

    lstrcpy(lpszAddress, lpsz);

    /* Create OneOff Entry ID */

    hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
        lpszDisplayName, lpszAddrType, lpszAddress, 0,
        &cbEntryID, (LPENTRYID FAR *) &lpEntryID);

    if (hr)
    {
        DebugTrace("CreateOneOff() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* We need to copy the EntryID into chained memory */

    sc = lpxpl->AllocateMore(cbEntryID, rgProps, &lpb);

    if (sc)
    {
        DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
        goto ret;
    }

    /* Copy EntryID into chained memory buffer */

    if (cbEntryID)
        memcpy(lpb, lpEntryID, (size_t) cbEntryID);

    lpxpl->FreeBuffer(lpEntryID);
    lpEntryID = NULL;

    /* Now, build the PropValue array */

    rgProps[iRecipType].ulPropTag = PR_RECIPIENT_TYPE;
    rgProps[iRecipType].Value.l = lRecipType;

    rgProps[iDisplayName].ulPropTag = PR_DISPLAY_NAME;
    rgProps[iDisplayName].Value.LPSZ = lpszDisplayName;

    rgProps[iAddrType].ulPropTag = PR_ADDRTYPE;
    rgProps[iAddrType].Value.LPSZ = lpszAddrTypeT;

    rgProps[iAddress].ulPropTag = PR_EMAIL_ADDRESS;
    rgProps[iAddress].Value.LPSZ = lpszAddress;

    rgProps[iEntryID].ulPropTag = PR_ENTRYID;
    rgProps[iEntryID].Value.bin.cb = cbEntryID;
    rgProps[iEntryID].Value.bin.lpb = lpb;

    /* It's now safe to hook in the new AdrEntry */

    lpAdrList->aEntries[lpAdrList->cEntries].cValues = cRecipProps;
    lpAdrList->aEntries[lpAdrList->cEntries++].rgPropVals = rgProps;

    return hrSuccess;

ret:
    lpxpl->FreeBuffer(rgProps);
    lpxpl->FreeBuffer(lpEntryID);

    if (lpAdrList != lpMyAdrList->lpAdrList)
        lpxpl->FreeBuffer(lpAdrList);

    if (!hr && sc)
        hr = ResultFromScode(sc);

    DebugTraceResult(HrAddRecipToAdrList()Failed !, hr);

    return hr;
}

/*
 -  HrAddRecipToReplyList
 -
 *  Purpose:
 *      Builds the PR_REPLY_RECIPIENT_NAMES and PR_REPLY_RECIPIENT_ENTRIES
 *      properties by re-allocing as new ones are added to the list.
 *
 *  Parameters:
 *      lpxpl           Points to Transport Logon object
 *      lpszToken       Contains Display Name and E-Mail Address
 *      lpszAddrType    Address Type for this transport
 *      lppszNames      Semi-colon delimited list of Display Names
 *      lpcbEIDList     Current size of EntryList
 *      lppEIDList      Pointer to current EntryList
 *
 *  Returns:
 *      hr              Indicating Suucess/Failure
 */

HRESULT
HrAddRecipToReplyList(LPXPL lpxpl, LPTSTR lpszToken, LPTSTR lpszAddrType,
    LPTSTR * lppszNames, ULONG * lpcbEIDList, LPFLATENTRYLIST * lppEIDList)
{
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPTSTR lpszAddress;
    LPTSTR lpszName;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    ULONG cbEID;
    LPBYTE lpEID = NULL;
    LPFLATENTRYLIST lpOld = *lppEIDList;
    ULONG cbOld;
    LPFLATENTRYLIST lpNew = NULL;
    LPFLATENTRY lpEntry;
    ULONG cbNew;
    LPTSTR lpszNewNames = NULL;

    lpszName = strtok(lpszToken, "[");

    lpszAddress = strtok(NULL, "]");

    /* Create OneOff Entry ID for this Recipient */

    hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
        lpszName, lpszAddrType, lpszAddress, 0,
        &cbEID, (LPENTRYID FAR *) &lpEID);

    if (hr)
        goto ret;

    /* Determine size of new list and allocate memory for it.
       The "+ 3) & -4L" will round up the allocation to be a
       multiple of 4 bytes. */

    if (lpOld)
    {
        Assert(!IsBadReadPtr(lpOld, CbNewFLATENTRYLIST(0)));
        Assert(!IsBadReadPtr(lpOld->abEntries, (UINT) lpOld->cbEntries));

        cbOld = lpOld->cbEntries;
        cbNew = (cbOld + offsetof(FLATENTRY, abEntry) + cbEID + 3) & -4L;
    }
    else
    {
        cbNew = cbOld = (cbEID + offsetof(FLATENTRY, abEntry) + 3) & -4L;
    }

    sc = lpxpl->AllocateBuffer(cbNew + offsetof(FLATENTRYLIST, abEntries), &lpNew);

    if (sc)
    {
        hr = ResultFromScode(sc);
        goto ret;
    }

    /* If Re-Allocing then copy old list and new EID, else build new list */

    if (lpOld)
    {
        ULONG cbNewOff = (cbOld + 3) & -4L;

        /* Copy the old data to the new structure */

        lpNew->cEntries = lpOld->cEntries + 1;
        lpNew->cbEntries = cbNew;

        if (cbOld)
            memcpy(lpNew->abEntries, lpOld->abEntries, (size_t) cbOld);

        /* Resolve the pointer to the new FLATENTRY */

        lpEntry = (LPFLATENTRY) & lpNew->abEntries[cbNewOff];

    }
    else
    {
        lpNew->cEntries = 1;
        lpNew->cbEntries = cbNew;

        /* Resolve the pointer to the new FLATENTRY */

        lpEntry = (LPFLATENTRY) lpNew->abEntries;
    }

    /* Add in the new FLATENTRY */

    lpEntry->cb = cbEID;
    if (cbEID)
        memcpy(lpEntry->abEntry, lpEID, (size_t) cbEID);

    /* Now, build the Display Name(s) String */

    if (*lppszNames)
    {
        /* We're Re-Allocing: copy old string and cat new one */

        sc = lpxpl->AllocateBuffer(lstrlen(*lppszNames) + lstrlen(lpszName) + 3,
            &lpszNewNames);

        if (sc)
        {
            hr = ResultFromScode(sc);
            goto ret;
        }

        lstrcpy(lpszNewNames, *lppszNames);
        lstrcat(lpszNewNames, "; ");
        lstrcat(lpszNewNames, lpszName);

        lpxpl->FreeBuffer(*lppszNames);
    }
    else
    {
        /* First name; just alloc and copy... */

        sc = lpxpl->AllocateBuffer(lstrlen(lpszName) + 1, &lpszNewNames);

        if (sc)
        {
            hr = ResultFromScode(sc);
            goto ret;
        }

        lstrcpy(lpszNewNames, lpszName);
    }

    /* It's now safe to hook in the new list. */
    /* Free old list and pass back new one.   */

    lpxpl->FreeBuffer(lpOld);

    *lppEIDList = lpNew;
    *lpcbEIDList = cbNew + offsetof(FLATENTRYLIST, abEntries);
    *lppszNames = lpszNewNames;

ret:
    lpxpl->FreeBuffer(lpEID);

    if (hr)
    {
        lpxpl->FreeBuffer(lpNew);
        lpxpl->FreeBuffer(lpszNewNames);
    }
    return hr;
}

/*
 -  HrMakeSearchKey
 -
 *  Purpose:
 *      Makes a Search Key (for the PR_???_SEARCH_KEY property) from
 *      the values passed in.  Memory is chained to some parent block.
 *      SearchKeys look like: ADDRTYPE:EMAILADDRESS.
 *
 *  Parameters:
 *      lpxpl           Points to Transport Logon object
 *      lpParent        Memory block to chain Search Key to
 *      lpszAddrType    Address Type
 *      lpszAddress     E-mail Address
 *      lpcbSK          Returned size of Search Key
 *      lppSK           The returned Search Key
 *
 *  Returns:
 *      hr              Indicating Suucess/Failure
 */

HRESULT
HrMakeSearchKey(LPXPL lpxpl, LPVOID lpParent, LPTSTR lpszAddrType,
    LPTSTR lpszAddress, ULONG * lpcbSK, LPBYTE * lppSK)
{
    SCODE sc;
    HRESULT hr = hrSuccess;
    LPBYTE lpb = NULL;
    ULONG ulSize;

    /* The 2 is for the colon and the NULL terminator */

    ulSize = sizeof(TCHAR) * (2 + lstrlen(lpszAddrType) + lstrlen(lpszAddress));

    sc = lpxpl->AllocateMore(ulSize, lpParent, &lpb);

    if (sc)
    {
        hr = ResultFromScode(sc);
        goto ret;
    }

    /* We need to convert to upper case, that's the law! */

    wsprintf((LPTSTR) lpb, "%s:%s", lpszAddrType, lpszAddress);
    CharUpperBuff((LPTSTR) lpb, (UINT) (ulSize - sizeof(TCHAR)));

    *lpcbSK = ulSize;
    *lppSK = lpb;

ret:
    return hr;
}

/*
 -  HrGetLine
 -
 *  Purpose:
 *      Kind of like fgets() except it strips CR\LF pairs for us.
 *      Reads cbDest bytes from the lpSof stream or until a CR/LF
 *      pair is reached, whichever comes first.  Returns count of
 *      bytes read into lpsz.
 *
 *  Parameters:
 *      lpSof           Points to an OLE 2.0 Stream (to be read from)
 *      cbDest          Size of memory pointed to by lpsz
 *      lpsz            Points to a chunck of memory that receives the
 *                      line being read in.  Must be of size cbDest.
 *      pcbRead         Receives the count of bytes actually read in.
 *
 *  Returns:
 *      hr              Indicating Suucess/Failure
 */

HRESULT
HrGetLine(LPSTREAM lpSof, ULONG cbDest, LPTSTR lpsz, ULONG * pcbRead)
{
    HRESULT hr = S_OK;
    BOOL fCRLF = FALSE;
    TCHAR rgch1[1];
    TCHAR rgch2[1];
    ULONG cbRead;

    if (!lpSof || (cbDest == 0) || !lpsz || !pcbRead)
        return ResultFromScode(MAPI_E_INVALID_PARAMETER);

    for (*pcbRead = 0; *pcbRead < cbDest - sizeof(TCHAR);)
    {
        /* read one TCHAR from stream */

        hr = lpSof->lpVtbl->Read(lpSof, (LPVOID) rgch1, sizeof(TCHAR), &cbRead);

        if (hr || (cbRead != sizeof(TCHAR)))
            break;

        /* Test for CR/LF pair; if not then add to line */

        if (*rgch1 == '\r')
        {
            hr = lpSof->lpVtbl->Read(lpSof, (LPVOID) rgch2, sizeof(TCHAR), &cbRead);

            if (hr)
                break;

            if (cbRead == sizeof(TCHAR))
            {
                if (*rgch2 == '\n')
                {
                    fCRLF = TRUE;
                    break;
                }
            }
            else
            {
                *lpsz++ = *rgch1;
                *lpsz++ = *rgch2;
                *pcbRead += 2 * sizeof(TCHAR);
            }
        }
        else
        {
            *lpsz++ = *rgch1;
            *pcbRead += sizeof(TCHAR);
        }
    }

    /* NULL terminate and leave */

    *lpsz = '\0';

    /* Test for the EOF case.  Since the stream
       won't return errors, we will!!!! */

    if (!fCRLF && !*pcbRead)
        hr = ResultFromScode(MAPI_E_CALL_FAILED);

    return hr;
}

/*
 -  FGetTagAndToken
 -
 *  Purpose:
 *      Breaks a line read from the input stream into Tag and Token.
 *      If no Tag is found, then returns FALSE indicating this is not
 *      a Tag'd line.
 *
 *  Parameters:
 *      lpsz            A line from the messsage file that may have
 *                      the format: 'Tag: Token'.  Must be of size MAX_LINE.
 *      pulTag          Will receive the index of lpsz's Tag in rgszTag
 *      lppszToken      Will point to the token in lpsz
 *
 *  Returns:
 *      TRUE            If lpsz starts with a valid Tag
 *      FALSE           otherwise
 */

BOOL
FGetTagAndToken(LPTSTR lpsz, ULONG * pulTag, LPTSTR * lppszToken)
{
    ULONG uli;
    TCHAR chT;
    LPTSTR lpszT;

    if (!lpsz || !pulTag || !lppszToken)
        return FALSE;

    /* Tags end with ':'  If lpsz has a ':' then it MIGHT be
       a Tag'd line, else it's definitely NOT a Tag'd line. */

    lpszT = strchr(lpsz, ':');

    if (!lpszT)
        return FALSE;

    /* Check that we're not at the MAX_LINE extent of lpsz.  If we are
       then just return, cause this can't possibly be a Tag'd line!
       The '3' accounts for the space, colon, and null terminator. */

    if ((lpszT - lpsz) > (MAX_LINE - 3))
        return FALSE;

    /* Swap *(lpszT+2) with a NULL to seperate Tag from Token */

    lpszT += 2;
    chT = *lpszT;
    *lpszT = '\0';

    /* Look-Up 'Potential' Tag in Tag Table */

    for (uli = 0; uli < NUM_TAGS; uli++)
    {
        if (!lstrcmp(lpsz, rgszTags[uli]))
        {
            /* Found!  Remember index */

            *pulTag = uli;
            break;
        }
    }

    /* Swap that NULL out.  lpszT now points to the token (maybe) */

    *lpszT = chT;

    if (uli == NUM_TAGS)
        return FALSE;           /* Tag wasn't found; it's just a line */

    *lppszToken = lpszT;

    return TRUE;
}

/*
 -  FileTimeFromSzTime
 -
 *  Purpose:
 *      Converts the textized data field in the text file format
 *      to a FILETIME struct format.  If we encounter errors in
 *      parsing the lpszDateTime string, we jump to the conversion
 *      call and will translate as much as we've filled in so far.
 *
 *  Parameters:
 *      lpszDateTime        Date/Time in the format: yyyy/mm/dd hh:mm
 *      pft                 Pointer to a FILETIME struct
 */

void
FileTimeFromSzTime(LPTSTR lpszDateTime, FILETIME * pft)
{
    SYSTEMTIME systime =
    {0, 0, 0, 0, 0, 0, 0, 0};
    LPTSTR lpsz;

    /* Feeble attempt at parameter validation! */

    if (!lpszDateTime || !pft)
        return;

    /* Grab the Year */

    lpsz = strtok(lpszDateTime, "/");
    if (!lpsz)
        goto ret;

    systime.wYear = atoi(lpsz);

    /* Grab the Month */

    lpsz = strtok(NULL, "/");
    if (!lpsz)
        goto ret;

    systime.wMonth = atoi(lpsz);

    /* Grab the Day */

    lpsz = strtok(NULL, " ");
    if (!lpsz)
        goto ret;

    systime.wDay = atoi(lpsz);

    /* Grab the Hour */

    lpsz = strtok(NULL, ":");
    if (!lpsz)
        goto ret;

    systime.wHour = atoi(lpsz);

    /* Grab the Minutes */

    lpsz = strtok(NULL, "\r\n:");
    if (!lpsz)
        goto ret;

    systime.wMinute = atoi(lpsz);

ret:
    SystemTimeToFileTime(&systime, pft);
}

/*
 -  SetFromMeFlag
 -
 *  Purpose:
 *      Sets the PR_MESSAGE_FLAGS MSGFLAG_FROMME bit to on.
 *
 */

VOID
SetFromMeFlag(LPXPL lpxpl, LPMESSAGE lpMsg)
{
    HRESULT hResult;
    LPSPropValue lpProp = NULL;

    /* Get the current state of the Message Flags */

    hResult = HrGetOneProp((LPMAPIPROP)lpMsg, PR_MESSAGE_FLAGS, &lpProp);

    if (HR_FAILED(hResult))
        goto ret;

    /* Add the FromMe bit */

    lpProp->Value.l |= MSGFLAG_FROMME;

    hResult = HrSetOneProp((LPMAPIPROP)lpMsg, lpProp);

ret:
    lpxpl->FreeBuffer(lpProp);
}


XPRESRC.H

/*
 -  R E S O U R C E . H
 -
 *  Resource definitions for the Sample Transport Provider.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/*   DialogBox IDs  */

#define GeneralPage                     100
#define PeerPage                        200
#define LoggingPage                     300
#define PerMsgOpt                       400
#define PerRecipOpt                     500

/*   EditText IDs   */

#define IDE_DISPLAY                     106
#define IDE_EMAIL_TYPE                  107
#define IDE_EMAIL_ADDR                  108
#define IDE_IN_DIR                      109
#define IDE_OUT_DIR                     110
#define IDE_WGAP_FNAME                  112
#define IDE_WGAP_DIR                    114
#define IDE_LOG_NAME                    118
#define IDE_LOG_HIGH                    119
#define IDE_LOG_LOW                     120

/*  CheckBox IDs    */

#define IDC_PEER                        121
#define IDC_LOGON_UI                    122
#define IDC_LOG_EVENTS                  123
#define IDC_SAVE_DIALOG                 124
#define IDC_PER_MSG_DEFER               125
#define IDC_PER_RECIP_DEFER             126

/* Static Label IDs */

#define IDT_WGAP_FNAME                  127
#define IDT_WGAP_DIR                    128
#define IDT_DISPLAY                     129
#define IDT_EMAIL_TYPE                  130
#define IDT_EMAIL_ADDR                  131
#define IDT_IN_DIR                      132
#define IDT_OUT_DIR                     133
#define IDT_LOG_NAME                    134
#define IDT_LOG_HIGH                    135
#define IDT_LOG_LOW                     136

/* String Table IDs */

/* Error messages offset and strings */

#define LIB_ERRORS                      1000
#define IDS_SUCCESS                     1000
#define IDS_E_BUSY                      1001
#define IDS_E_CALL_FAILED               1002
#define IDS_E_INVALID_PARAMETER         1003
#define IDS_E_NO_ACCESS                 1004
#define IDS_E_NO_SUPPORT                1005
#define IDS_E_NOT_FOUND                 1006
#define IDS_E_UNKNOWN_FLAGS             1007
#define IDS_E_VERSION                   1008
#define IDS_E_NOT_ENOUGH_MEMORY         1009
#define IDS_W_ERRORS_RETURNED           1010
#define IDS_NO_DISPLAY_NAME             1011
#define IDS_NO_ADDR_TYPE                1012
#define IDS_NO_EMAIL_ADDRESS            1013
#define IDS_NO_INBOUND_DIR              1014
#define IDS_NO_OUTBOUND_DIR             1015
#define IDS_BAD_EMAIL_ADDRESS           1016
#define IDS_BAD_INBOUND_DIR             1017
#define IDS_UNC_DIR_MISMATCH            1018
#define IDS_UNKNOWN_ERROR               1999

/* Report Text */

#define IDS_REPORT_TEXT_MSG             2001

/* Status Table Strings */

#define LIB_STATUS                      3000
#define IDS_STATUS_OFFLINE              3000
#define IDS_STATUS_AVAILABLE            3001
#define IDS_STATUS_ONLINE               3002
#define IDS_STATUS_UPLOADING            3003
#define IDS_STATUS_DOWNLOADING          3004
#define IDS_STATUS_INFLUSHING           3005
#define IDS_STATUS_OUTFLUSHING          3006

/* Configuration dialog title */

#define IDS_CONFIG_TITLE                4001
#define IDS_BAD_LOGON_PROPS_TITLE       4002
#define IDS_BAD_LOGON_PROPS_FORMAT      4003

/* Wizard IDs */

enum {ipgName, ipgType, ipgInbox, ipgPath, cpageMax };
#define WIZ_BASE                        5000
#define IDC_NameText                    5010
#define IDC_NameLbl                     5011
#define IDC_NameEdit                    5012
#define IDC_TypeText                    5020
#define IDC_TypeLbl                     5021
#define IDC_TypeEdit                    5022
#define IDC_UNCText                     5030
#define IDC_UNCLbl                      5031
#define IDC_UNCEdit                     5032
#define IDC_PathText                    5040
#define IDC_PathLbl                     5041
#define IDC_PathEdit                    5042


XPSNDMSG.C

/*
 -  X P S N D M S G . C
 -
 *  Purpose:
 *      Code to support the MAPI Transport SPI entry points for
 *      message transmission.
 *      This module contains the following SPI entry points:
 *
 *          SubmitMessage()
 *          EndMessage()
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"

HRESULT HrAddDeferred(LPXPL lpxpl, LPMESSAGE lpMsg, ULONG *lpulMsgRef);

/*
 -  XPL_SubmitMessage
 -
 *  Purpose:
 *      Called by the Spooler when it wishes to attempt transmission
 *      of a message.
 *
 *  Parameters:
 *      ulFlags             Flags from the Spooler. The only
 *                          flag defined in the MAPI 1.0 TSPI
 *                          is BEGIN_DEFERRED, and this transport
 *                          doesn't do deferred transmission.
 *      lpMessage           Pointer to message object that the
 *                          Spooler wants sent.
 *      lpulMsgRef          Pointer to where the transport should
 *                          store a unsigned long for use in
 *                          identifying TransportNotify() message
 *                          events. Initialized to 1 by the
 *                          Spooler. We use this to ID deferred
 *                          messages using DEFERRED_MSG_REF.
 *      lpulReturnParm      Used for several MAPI_E_XXX returns,
 *                          but this transport doesn't do any
 *                          of them.
 *
 *  Returns:
 *      (HRESULT)           MAPI_E_BUSY if the Spooler calls
 *                          here again while we're busy, else
 *                          errors encountered if any.
 *
 *  Operation:
 *      For non peer-to-peer, "send" the message to our outbound directory.
 *
 *      For peer-to-peer, use the ADRLIST builder HrBuildAdrList to attempt
 *      transmission to each recipient and build both a sent ADRLIST and a
 *      NDR ADRLIST. ModifyRecipients() on the Spooler message if there's
 *      anyone in the sent ADRLIST. This tells the spooler which ones got
 *      the mail.  If there were any recipients in the NDR ADRLIST, we need
 *      to StatusRecips() with that list, and we will need to
 *      ModifyRecipients() again with them.
 *
 *      For non peer-to-peer or p2p with NDR recipients, use the ADRLIST
 *      builder HrBuildAdrList to make a ADRLIST with all of our unsent
 *      recipients (for non p2p, delivery to all recipients consists of
 *      putting a file into the outbound and for NDR cases, we just take
 *      responsibility because we've already NDR'ed them) and
 *      ModifyRecipients().
 */

STDMETHODIMP
XPL_SubmitMessage(LPXPL lpxpl,
    ULONG ulFlags,
    LPMESSAGE lpMessage,
    ULONG * lpulMsgRef,
    ULONG * lpulReturnParm)
{
    LPTSTR lpszMyDir;
    HRESULT hResult = 0;
    SCODE sc = 0;
    BOOL fUpdatedStatus = FALSE;
    BOOL fPeer2Peer;

    LPSPropValue lpPropArray = NULL;
    LPMAPISUP lpMAPISup;

    ULONG cValues;
    LPSPropValue lpMsgProps = NULL;

    static const SizedSPropTagArray(2, sptMsgDefer) =
    {
        2,
        {
            PR_SAMPLE_PER_MSG_DEFER,
            PR_SAMPLE_PER_RECIP_DEFER
        }
    };

    SPropValue spvRecipUnsent =
    {PR_RESPONSIBILITY, 0L, FALSE};
    SPropValue spvRecipDefer =
    {PR_SAMPLE_PER_RECIP_DEFER, 0L, TRUE};
    SRestriction rgsrOr[2];
    SRestriction rgsrAnd[2];
    SRestriction srExist;
    SRestriction srRecipUnsent;

    LPMAPITABLE lpTable = NULL;
    LPMYADRLIST lpMyAdrListDone = NULL;
    LPMYADRLIST lpMyAdrListNotDone = NULL;

    /* Simple re-entrancy test. Should never happen anyway. */

    if (lpxpl->ulTransportStatus & STATUS_OUTBOUND_ACTIVE)
    {
        hResult = ResultFromScode(MAPI_E_BUSY);
        DebugTrace("XPL_SubmitMessage reentrancy test failed.\n");
        goto ret;
    }

    /* Signal that we're uploading. */

    lpxpl->ulTransportStatus |= STATUS_OUTBOUND_ACTIVE;
    hResult = HrUpdateTransportStatus(lpxpl, 0L);
    if (hResult)
        goto ret;
    fUpdatedStatus = TRUE;

    /*  Session's OK, hook up some local references */

    sc = ScCopySessionProps(lpxpl, &lpPropArray, NULL);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        DebugTrace("ScCopySessionProps failed in SubmitMessage.\n");
        goto ret;
    }

    lpMAPISup = lpxpl->lpMAPISup;
    fPeer2Peer = ((ArrayIndex(PR_SAMPLE_FLAGS, lpPropArray).Value.ul) & PR_SAMPLE_FLAG_PEER_TO_PEER) != 0;
    lpszMyDir = ArrayIndex(PR_SAMPLE_OUTBOUND_DIR, lpPropArray).Value.LPSZ;

    PrintfTransportLog(TEXT("Start Outbound: %s"),
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ);

    /* Reset the timer for SpoolerYield() */

    HrCheckSpoolerYield(lpMAPISup, TRUE);

    /* Check if we are to defer this message */

    hResult = lpMessage->lpVtbl->GetProps(lpMessage,
        (LPSPropTagArray) &sptMsgDefer, 0, /* ansi */
        &cValues, &lpMsgProps);

    if (HR_FAILED(hResult))
    {
        DebugTrace("Failed getting props on the message.\n");
        goto ret;
    }

    /* We'll default fResendDeferred to TRUE unless we defer this msg */

    lpxpl->fResendDeferred = TRUE;

    /* If PR_SAMPLE_PER_MSG_DEFER exists and is TRUE, then we delete
       this property from the message, and add this message to our
       deferred list.  The lpulMsgRef is set to the value assigned to
       the node in our list.  When EndMessage is called we search for
       this Ref in to list.  If found, we return END_DONT_RESEND,
       causing the spooler to defer the msg. */

    if (lpMsgProps->ulPropTag == PR_SAMPLE_PER_MSG_DEFER)
    {
        lpMessage->lpVtbl->DeleteProps(lpMessage,
            (LPSPropTagArray) &sptMsgDefer, NULL);

        lpMessage->lpVtbl->SaveChanges(lpMessage, KEEP_OPEN_READWRITE);

        if (lpMsgProps->Value.b == TRUE)
        {
            hResult = HrAddDeferred(lpxpl, lpMessage, lpulMsgRef);
            goto ret;
        }
    }

    /* Now, see if any Recipients have PR_SAMPLE_PER_RECIP_DEFER set to
       TRUE in the RecipientTable.  If so, then slam a non-transmittable
       property into the message to indicate we've deferred this message
       for some recipients this time around.  If this property already
       exists, then we know it has already been deferred and we should
       just go ahead and send to all un-handled recipients.              */

    if (lpMsgProps[1].ulPropTag == PR_SAMPLE_PER_RECIP_DEFER)
    {
        lpMessage->lpVtbl->DeleteProps(lpMessage,
            (LPSPropTagArray) &sptMsgDefer, NULL);

        lpMessage->lpVtbl->SaveChanges(lpMessage, KEEP_OPEN_READWRITE);

        /*  Initialize a restriction -- we'll need it soon. */

        srRecipUnsent.rt = RES_PROPERTY;
        srRecipUnsent.res.resProperty.relop = RELOP_EQ;
        srRecipUnsent.res.resProperty.ulPropTag = PR_RESPONSIBILITY;
        srRecipUnsent.res.resProperty.lpProp = &spvRecipUnsent;
    }
    else
    {
        SRestriction srRecipDefer;

        srRecipDefer.rt = RES_PROPERTY;
        srRecipDefer.res.resProperty.relop = RELOP_EQ;
        srRecipDefer.res.resProperty.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER;
        srRecipDefer.res.resProperty.lpProp = &spvRecipDefer;

        hResult = lpMessage->lpVtbl->GetRecipientTable(
            lpMessage, 0L, &lpTable);

        if (hResult)
        {
            DebugTrace("GetRecipientTable failed in SubmitMessage.\n");
            goto ret;
        }

        hResult = lpTable->lpVtbl->FindRow(lpTable, &srRecipDefer,
            BOOKMARK_BEGINNING, 0);

        /* Check our .2 second timer! */

        sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

        if (sc == MAPI_W_CANCEL_MESSAGE)
        {
            DebugTrace("Cancelling message delivery.\n");
            goto ret;
        }

        if (GetScode(hResult) != MAPI_E_NOT_FOUND)
        {
            hResult = HrAddDeferred(lpxpl, lpMessage, lpulMsgRef);

            if (HR_FAILED(hResult))
            {
                DebugTrace("HrAddDeferred failed doing a per-recip deferral.\n");
                goto ret;
            }
        }
        else
        {
            hResult = lpTable->lpVtbl->SeekRow(lpTable,
                BOOKMARK_BEGINNING, 0L, NULL);
        }

        /*  Initialize a restriction -- we'll need it soon. */

        spvRecipDefer.Value.b = FALSE;

        srExist.rt = RES_EXIST;
        srExist.res.resExist.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER;

        rgsrOr[0].rt = RES_NOT;
        rgsrOr[0].res.resNot.lpRes = &srExist;

        rgsrOr[1].rt = RES_PROPERTY;
        rgsrOr[1].res.resProperty.relop = RELOP_EQ;
        rgsrOr[1].res.resProperty.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER;
        rgsrOr[1].res.resProperty.lpProp = &spvRecipDefer;

        rgsrAnd[0].rt = RES_PROPERTY;
        rgsrAnd[0].res.resProperty.relop = RELOP_EQ;
        rgsrAnd[0].res.resProperty.ulPropTag = PR_RESPONSIBILITY;
        rgsrAnd[0].res.resProperty.lpProp = &spvRecipUnsent;

        rgsrAnd[1].rt = RES_OR;
        rgsrAnd[1].res.resOr.cRes = 2;
        rgsrAnd[1].res.resOr.lpRes = rgsrOr;

        srRecipUnsent.rt = RES_AND;
        srRecipUnsent.res.resAnd.cRes = 2;
        srRecipUnsent.res.resAnd.lpRes = rgsrAnd;
    }

    /* Check our .2 second timer before attempting to send */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message delivery.\n");
        goto ret;
    }

    /*  If not peer-to-peer, "send message" to our outbound directory. */

    if (!fPeer2Peer)
    {
        BOOL fSent;

        hResult = HrSendOneMessage(lpxpl, lpPropArray,
                lpMessage, 0, lpszMyDir, &fSent);

        if (hResult)
        {
            DebugTrace("Copying message to outbound failed.\n");
            goto ret;
        }
    }

    /*  If we are peer-to-peer, send to all our recipients. */

    else
    {
        /*  Get the recipient table from the message if we haven't already */

        if (!lpTable)
        {
            hResult = lpMessage->lpVtbl->GetRecipientTable(
                lpMessage, 0L, &lpTable);

            if (hResult)
            {
                DebugTrace("GetRecipientTable failed in SubmitMessage.\n");
                goto ret;
            }
        }

        /*  Restrict to all unsent recipients */

        hResult = lpTable->lpVtbl->Restrict(lpTable, &srRecipUnsent, 0L);

        if (hResult)
        {
            DebugTrace("Restriction on recipient table  failed in SubmitMessage.\n");
            goto ret;
        }

        /* Check our .2 second timer! */

        sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

        if (sc == MAPI_W_CANCEL_MESSAGE)
        {
            DebugTrace("Cancelling message delivery.\n");
            goto ret;
        }

        /*  Send to the recipients which we can reach, and build ADRLISTs of
            the sent and unsent recipients */

        hResult = HrBuildAdrList(lpxpl, lpPropArray, lpMessage, lpTable,
                TRUE, HrSendOneMessage, &lpMyAdrListDone, &lpMyAdrListNotDone);

        if (hResult)
        {
            DebugTrace("HrBuildAdrList failed in SubmitMessage.\n");
            goto ret;
        }

        /*  No error, do we have some recipients? If so, do the ModifyRecipients(). */

        if (lpMyAdrListDone)
        {
            hResult = lpMessage->lpVtbl->ModifyRecipients(lpMessage,
                    MODRECIP_MODIFY, lpMyAdrListDone->lpAdrList);

            if (hResult)
            {
                DebugTrace("ModifyRecipients failed in SubmitMessage.\n");
                goto ret;
            }

            /* Now we need to save changes on the message. */

            hResult = lpMessage->lpVtbl->SaveChanges(lpMessage,
                    lpMyAdrListNotDone ? KEEP_OPEN_READWRITE : 0L);

            if (hResult)
            {
                DebugTrace("SaveChanges failed in SubmitMessage.\n");
                goto ret;
            }
        }

        /*  Check for unsent recipients. If there were any, we need to NDR
            them and (finally) mark them as taken. If there were not any,
            we're really finished and can just go and release the message. */

        if (!lpMyAdrListNotDone)
            goto ret;

        hResult = lpMAPISup->lpVtbl->StatusRecips(lpMAPISup,
                lpMessage, lpMyAdrListNotDone->lpAdrList);

        if (HR_FAILED(hResult))
        {
            DebugTrace("StatusRecips failed in SubmitMessage.\n");
            goto ret;
        }

        /*  StatusRecips killed the ADRLIST so zero out our pointer. */

        lpMyAdrListNotDone->lpAdrList = NULL;

        /* Get rid of the MYADRLISTs. */

        FreeMyAdrList(lpxpl, lpMyAdrListDone);
        FreeMyAdrList(lpxpl, lpMyAdrListNotDone);

        lpMyAdrListDone = lpMyAdrListNotDone = NULL;

        /*  Free the recipient table. */

        lpTable->lpVtbl->Release(lpTable);
        lpTable = NULL;
    }

    /* Check our .2 second timer after attempting to send */

    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

    if (sc == MAPI_W_CANCEL_MESSAGE)
    {
        DebugTrace("Cancelling message delivery.\n");
        goto ret;
    }

    /*  At this point we have either:

        a)  If not peer-to-peer, made a single container file in
            the outbound directory
        b)  If peer-to-peer, created a container file in every
            inbound directory we were able to reach (but not all
            of them) -- and have NDR'ed the ones we could not reach

        All that remains to do is to mark all unsent recipients as
        handled, since in the non-peer case the creation of the
        container suffices as "handling" and in the peer case the
        NDR message also does this.
    */

    hResult = lpMessage->lpVtbl->GetRecipientTable(lpMessage, 0L, &lpTable);

    if (hResult)
    {
        DebugTrace("Second GetRecipientTable failed in SubmitMessage.\n");
        goto ret;
    }

    /*  Restrict the recipient table to the remaining unsent */

    hResult = lpTable->lpVtbl->Restrict(lpTable, &srRecipUnsent, 0L);

    if (hResult)
    {
        DebugTrace("Restriction on recipient table failed.\n");
        goto ret;
    }

    /*  Build the ADRLIST of the unsent recipients */

    hResult = HrBuildAdrList(lpxpl, lpPropArray, lpMessage, lpTable,
            TRUE, NULL,  &lpMyAdrListDone, &lpMyAdrListNotDone);

    if (hResult)
    {
        DebugTrace("HrBuildAdrList failed in SubmitMessage.\n");
        goto ret;
    }

    Assert(!lpMyAdrListNotDone);

    /*  No error, do we have some recipients? If so, do the ModifyRecipients(). */

    if (lpMyAdrListDone)
    {
        hResult = lpMessage->lpVtbl->ModifyRecipients(lpMessage,
                MODRECIP_MODIFY, lpMyAdrListDone->lpAdrList);

        if (hResult)
        {
            DebugTrace("ModifyRecipients failed in SubmitMessage.\n");
            goto ret;
        }
    }

    /*  Release the table, we're finished with it */

    UlRelease(lpTable);
    lpTable = NULL;

    /* With the recipient table work done, save changes again. */

    hResult = lpMessage->lpVtbl->SaveChanges(lpMessage, 0L);

    if (hResult)
    {
        DebugTrace("SaveChanges failed in SubmitMessage.\n");
        goto ret;
    }

    PrintfTransportLog(TEXT("End Outbound: %s"),
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ);

ret:
    /* Free-Up Memory */

    lpxpl->FreeBuffer(lpPropArray);
    lpxpl->FreeBuffer(lpMsgProps);

    /* Get rid of any MYADRLISTs. */

    FreeMyAdrList(lpxpl, lpMyAdrListDone);
    FreeMyAdrList(lpxpl, lpMyAdrListNotDone);

    /* Release any open table */

    UlRelease(lpTable);

    /* Release the spooler's message if need be */

    UlRelease(lpMessage);

    /* Reset upload status if set. If this errors, use the error only
       if no other error had occurred here. */

    if (fUpdatedStatus)
    {
        lpxpl->ulTransportStatus &= ~STATUS_OUTBOUND_ACTIVE;
        (void)HrUpdateTransportStatus(lpxpl, 0L);
    }

    DebugTraceResult(XPL_SubmitMessage, hResult);
    return hResult;

}

/*
 -  XPL_EndMessage
 -
 *  Purpose:
 *      Called by the Spooler to complete transmission of a message.
 *
 *  Parameters:
 *      ulMsgRef            Opaque identifier from SubmitMessage
 *      lpulFlags           Pointer to where the transport should
 *                          store special flags on return. This
 *                          Transport doesn't use any.
 *
 *  Returns:
 *      (HRESULT)           Success.
 *
 *  Operation:
 *      Clears unsigned long status, and returns success.
 */

STDMETHODIMP
XPL_EndMessage(LPXPL lpxpl, ULONG ulMsgRef, ULONG * lpulFlags)
{
    LPDEFMSG lpDefMsg;

    Assert(!IsBadWritePtr(lpulFlags, sizeof(ULONG)));

    *lpulFlags = 0L;

    /* If the ulMsgRef is non zero, look for it in the deferred
       list.  If found, return END_DONT_RESEND to the Spooler.  */

    if (ulMsgRef)
    {
        lpDefMsg = lpxpl->lpDeferredList;

        while (lpDefMsg)
        {
            if (lpDefMsg->ulMsgRef == ulMsgRef)
            {
                *lpulFlags = END_DONT_RESEND;
                break;
            }
        }
    }

    return hrSuccess;
}

/*
 -  HrAddDeferred
 -
 *  Purpose:
 *      Adds the EntryID and ulMsgRef of a message we are deferring
 *      to our list of deferred message.
 *
 *  Parameters:
 *      lpxpl           Transport session
 *      lpMsg           The message we wish to defer
 *      lpulMsgRef      Receives the reference we assign to this message
 *
 *  Returns:
 *      hResult         Indicating Success/Failure
 */

HRESULT
HrAddDeferred(LPXPL lpxpl, LPMESSAGE lpMsg, ULONG *lpulMsgRef)
{
    HRESULT hResult;
    SCODE sc;
    LPDEFMSG lpDefMsg = NULL;
    ULONG cVals;
    LPSPropValue lpEIDProp = NULL;
    static SPropTagArray spta = {1, {PR_ENTRYID}};

    /* Allocate a new DefMsg node */

    sc = lpxpl->AllocateBuffer(sizeof(DEFMSG), (LPVOID *)&lpDefMsg);

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocation failed in HrAddDeferred.\n");
        goto ret;
    }

    /* Get the EntryID of the message we are deferring */

    hResult = lpMsg->lpVtbl->GetProps(lpMsg, &spta, 0, &cVals, &lpEIDProp);

    if (HR_FAILED(hResult))
    {
        DebugTrace("GetProps failed in HrAddDeferred.\n");
        goto ret;
    }

    Assert(cVals == 1);
    Assert(lpEIDProp);
    Assert(lpEIDProp->ulPropTag == PR_ENTRYID);

    /* We'll make our own copy of the EntryID */

    sc = lpxpl->AllocateMore(lpEIDProp->Value.bin.cb, (LPVOID)lpDefMsg,
            (LPVOID *)&(lpDefMsg->sbinEIDDef.lpb));

    if (FAILED(sc))
    {
        hResult = ResultFromScode(sc);
        DebugTrace("Allocation failed in HrAddDeferred.\n");
        goto ret;
    }

    /* Fill in this new node and add it to the list */

    lpDefMsg->sbinEIDDef.cb = lpEIDProp->Value.bin.cb;
    memcpy(lpDefMsg->sbinEIDDef.lpb, lpEIDProp->Value.bin.lpb,
            (UINT)lpDefMsg->sbinEIDDef.cb);

    lpDefMsg->ulMsgRef = ++lpxpl->ulDeferredMsgRef;
    lpDefMsg->lpNext = lpxpl->lpDeferredList;
    lpxpl->lpDeferredList = lpDefMsg;

    lpxpl->fResendDeferred = FALSE;

    *lpulMsgRef = lpDefMsg->ulMsgRef;

ret:
    if (HR_FAILED(hResult))
        lpxpl->FreeBuffer(lpDefMsg);

    lpxpl->FreeBuffer(lpEIDProp);

    DebugTraceResult(HrAddDeferred, hResult);
    return hResult;
}


XPSOF.C

/*
 -  X P S O F . C
 -
 *  Purpose:
 *      Wraps the StreamOnFile object to add buffering of the stream.
 *      The wrappered version uses an in-memory buffer, or cache, to
 *      reduce the number of actual Reads and Writes to the underlying
 *      stream.  The goal is, obviously, to improve performance.
 *      Note: This code is not thread safe.  Also, the implementation
 *      is not optimized for streams that both read and write continuously
 *      because there will be too much flushing going on.
 *
 *  Copyright 1992-1995, Microsoft Corporation.  All Rights Reserved.
 */

#include "xppch.h"
#include "xpsof.h"

/* Private support function */

HRESULT HrRewindStream(LPXPSOF, ULONG);
VOID    HMemCopy(VOID HUGEP * lpvDst, VOID HUGEP * lpvSrc, ULONG cb);

XPSOF_Vtbl VtblXPSOF =
{
    XPSOF_QueryInterface,
    XPSOF_AddRef,
    XPSOF_Release,
    XPSOF_Read,
    XPSOF_Write,
    XPSOF_Seek,
    XPSOF_SetSize,
    XPSOF_CopyTo,
    XPSOF_Commit,
    XPSOF_Revert,
    XPSOF_LockRegion,
    XPSOF_UnlockRegion,
    XPSOF_Stat,
    XPSOF_Clone
};

/*
 -  HrWrapStreamOnFile
 -
 *  Purpose:
 *      This function takes a Stream-On-File object and wraps it.  The
 *      process of wrappering involves creating a similar object,
 *      allocating memory for the buffer, AddRefing the original object,
 *      and returning this new object to the caller.
 *
 *  Parameters:
 *      lpAllocBuffer           A MAPI memory allocator function
 *      lpFreeBuffer            The corresponding FreeBuffer function
 *      ulFlags                 XPSOF_READ, XPSOF_WRITE, or XPSOF_READWRITE
 *      lpStream                The original Stream-On-File to be wrapped
 *      lppWrappedStream        The new XPSOF object.
 *
 *  Returns:
 *      HRESULT                 Indicating Success/Failure
 */

STDMETHODIMP
HrWrapStreamOnFile(
    LPALLOCATEBUFFER    lpAllocBuffer,
    LPFREEBUFFER        lpFreeBuffer,
    ULONG               ulFlags,
    LPSTREAM            lpStream,
    LPSTREAM *          lppWrappedStream)
{
    SCODE sc;
    LPXPSOF lpxpsof = NULL;

    /* Allocate the new Stream object */

    sc = lpAllocBuffer(sizeof(XPSOF), (LPVOID *)&lpxpsof);

    if (FAILED(sc))
    {
        DebugTrace("Allocation of XPSOF object failed.\n");
        goto ret;
    }

    /* Init the object */

    lpxpsof->lpVtbl = &VtblXPSOF;
    lpxpsof->lcInit = 1;
    lpxpsof->ulFlags = ulFlags;
    lpxpsof->lpstrm = lpStream;
    lpxpsof->lpvBuff = NULL;
    lpxpsof->libBuff = 0;
    lpxpsof->cbBuffMac = 0;
    lpxpsof->fDirty = FALSE;
    lpxpsof->FreeBuffer = lpFreeBuffer;

    /* Allocate the buffer or cache */

    sc = lpAllocBuffer(XPSOF_BUFF_MAX, (LPVOID *)&lpxpsof->lpvBuff);

    if (FAILED(sc))
    {
        DebugTrace("Allocation of stream buffer failed.\n");
        goto ret;
    }

    /* Now that we've succeeded, AddRef the original object */

    lpStream->lpVtbl->AddRef(lpStream);

    *lppWrappedStream = (LPSTREAM)lpxpsof;

ret:
    if (FAILED(sc))
    {
        if (lpxpsof)
        {
            lpFreeBuffer(lpxpsof->lpvBuff);
            lpFreeBuffer(lpxpsof);
        }
    }

    DebugTraceSc(HrWrapStreamOnFile, sc);
    return ResultFromScode(sc);
}

/*
 -  IUnknown::QueryInterface
 -  IUnknown::AddRef
 -  IUnknown::Release
 -
 *  Purpose:
 *      These are the XPSOF's OLE IUnknown methods.
 */

STDMETHODIMP
XPSOF_QueryInterface(LPXPSOF lpXPSOF, REFIID riid, LPVOID * lppvObj)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        IsBadReadPtr (riid, sizeof(IID)) ||
        IsBadWritePtr (lppvObj, sizeof(LPVOID)))
    {
        DebugTraceSc(XPSOF_QueryInterface, E_INVALIDARG);
        return ResultFromScode (E_INVALIDARG);
    }

    if (memcmp(riid, &IID_IUnknown, sizeof(IID))
        && memcmp(riid, &IID_IStream, sizeof(IID)))
    {
        *lppvObj = NULL;    // OLE requires zeroing [out] parameters
        DebugTraceSc(XPSOF_QueryInterface, E_NOINTERFACE);
        return ResultFromScode (E_NOINTERFACE);
    }

    lpXPSOF->lcInit++;
    *lppvObj = lpXPSOF;

    return hrSuccess;
}

STDMETHODIMP_(ULONG)
XPSOF_AddRef(LPXPSOF lpXPSOF)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_AddRef(), E_INVALIDARG);
        return 1;
    }

    return ++lpXPSOF->lcInit;
}

STDMETHODIMP_(ULONG)
XPSOF_Release (LPXPSOF lpXPSOF)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_Release(), E_INVALIDARG);
        return 1;
    }

    if (--lpXPSOF->lcInit == 0)
    {
        if (lpXPSOF->lpstrm)
            lpXPSOF->lpstrm->lpVtbl->Release(lpXPSOF->lpstrm);

        if (lpXPSOF->lpvBuff)
            lpXPSOF->FreeBuffer(lpXPSOF->lpvBuff);

        lpXPSOF->FreeBuffer(lpXPSOF);

        return 0;
    }

    return lpXPSOF->lcInit;
}

/*
 -  IStream::Read
 -
 *  Purpose:
 *      To read data from the stream.
 */

STDMETHODIMP
XPSOF_Read (LPXPSOF lpXPSOF,
    VOID HUGEP * lpvData,
    ULONG cbSize,
    ULONG * lpcbRead)
{
    HRESULT hr = hrSuccess;
    ULONG cbRead = 0;
    ULONG cbT;
    VOID HUGEP * lpvRead = NULL;

    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        IsBadHugeWritePtr (lpvData, cbSize) ||
        (lpcbRead && IsBadWritePtr (lpcbRead, sizeof(ULONG))))
    {
        hr = ResultFromScode(STG_E_INVALIDPARAMETER);
        goto ret;
    }

    if (!(lpXPSOF->ulFlags & XPSOF_READ))
    {
        hr = ResultFromScode(STG_E_ACCESSDENIED);
        goto ret;
    }

    Assert(lpXPSOF->cbBuffMac >= lpXPSOF->libBuff);

    /* First, flush the buffer if it has been written into.  This     */
    /* operation empties our buffer and zeros the offset and size.    */
    /* We do this because we also buffer writes and we need to force  */
    /* the underlying stream to point to where the caller expects.    */

    if ((lpXPSOF->ulFlags & XPSOF_WRITE) && lpXPSOF->fDirty)
    {
        hr = XPSOF_Commit(lpXPSOF, 0);

        if (HR_FAILED(hr))
            goto ret;
    }

    /* Determine if the buffer is empty (cbT == 0) or not (cbT != 0). */
    /* We consider the buffer empty if we've read past the end of it  */
    /* or if cbBuffMac and libBuff are equal to zero.                 */

    cbT = lpXPSOF->cbBuffMac - lpXPSOF->libBuff;

    /* If the buffer is empty and the caller wants to read less than  */
    /* the size of our buffer, then we'll fill the buffer from the    */
    /* underlying stream object.  Adjust our buffer offset and size.  */

    if (!cbT && (cbSize < XPSOF_BUFF_MAX))
    {
        hr = lpXPSOF->lpstrm->lpVtbl->Read(lpXPSOF->lpstrm,
                lpXPSOF->lpvBuff, XPSOF_BUFF_MAX, &cbRead);

        if (HR_FAILED(hr))
            goto ret;

        lpXPSOF->libBuff = 0;
        lpXPSOF->cbBuffMac = cbT = cbRead;
    }

    /* Now, if the buffer is *not* empty and the caller wants to read */
    /* fewer bytes than what is in the buffer, then we read it from   */
    /* our buffer, fix-up our offset, set the count read and leave.   */

    if (cbT && (cbSize <= cbT))
    {
        lpvRead = (VOID HUGEP *)((LPBYTE)lpXPSOF->lpvBuff + lpXPSOF->libBuff);
        HMemCopy(lpvData, lpvRead, cbSize);
        lpXPSOF->libBuff += cbSize;
        cbRead = cbSize;
        goto ret;
    }

    /* If we are here, then the caller has requested more bytes to be */
    /* read than what can fit in our buffer.  In this case, we copy   */
    /* the remaining data from the buffer (if any) into lpvData and   */
    /* then go straight to the underlying stream for the remainder.   */
    /* Either way, our buffer is empty after this operation.          */

    lpvRead = lpvData;

    if (cbT)
    {
        HMemCopy(lpvRead, (VOID HUGEP *)((LPBYTE)lpXPSOF->lpvBuff + lpXPSOF->libBuff), cbT);
        lpvRead = (BYTE HUGEP *)lpvRead + cbT;
        lpXPSOF->libBuff = 0;
        lpXPSOF->cbBuffMac = 0;
    }

    hr = lpXPSOF->lpstrm->lpVtbl->Read(lpXPSOF->lpstrm,
            lpvRead, cbSize-cbT, &cbRead);

    if (HR_FAILED(hr))
        goto ret;

    cbRead += cbT;

ret:
    if (lpcbRead)
        *lpcbRead = cbRead;

    DebugTraceResult (XPSOF_Read(), hr);
    return hr;
}

/*
 -  IStream::Write
 -
 *  Purpose:
 *      To write data to the stream.
 */

STDMETHODIMP
XPSOF_Write (LPXPSOF lpXPSOF,
    VOID HUGEP * lpvData,
    ULONG cbSize,
    ULONG * lpcbWritten)
{
    HRESULT hr = hrSuccess;
    ULONG cbWritten = 0;
    ULONG cbT;
    VOID HUGEP * lpvWrite = NULL;

    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        IsBadHugeReadPtr (lpvData, cbSize) ||
        (lpcbWritten && IsBadWritePtr (lpcbWritten, sizeof(ULONG))))
    {
        hr = ResultFromScode(STG_E_INVALIDPARAMETER);
        goto ret;
    }

    if (!(lpXPSOF->ulFlags & XPSOF_WRITE))
    {
        hr = ResultFromScode(STG_E_ACCESSDENIED);
        goto ret;
    }

    Assert(lpXPSOF->cbBuffMac >= lpXPSOF->libBuff);

    /* First, if we've been Reading, then we need to re-wind the file  */
    /* pointer in the underlying stream to compensate for the last     */
    /* buffered Read.  Our new vacancy = the Max Size of our buffer.   */

    if (!lpXPSOF->fDirty)
    {
        if (lpXPSOF->libBuff != lpXPSOF->cbBuffMac)
        {
            hr = HrRewindStream(lpXPSOF, lpXPSOF->cbBuffMac-lpXPSOF->libBuff);

            if (HR_FAILED(hr))
                goto ret;
        }

        lpXPSOF->libBuff = 0;
        lpXPSOF->cbBuffMac = XPSOF_BUFF_MAX;
    }

    /* Determine the total vacancy of the buffer. */

    cbT = lpXPSOF->cbBuffMac - lpXPSOF->libBuff;

    /* If the caller wants to Write more bytes than the current  */
    /* vacancy of the buffer, then commit the current buffer and */
    /* Write the callers data directly to the stream.  If the    */
    /* buffer is not dirty, then the Commit call is a no-op.     */

    if (cbSize > cbT)
    {
        hr = XPSOF_Commit(lpXPSOF, 0);

        if (HR_FAILED(hr))
            goto ret;

        hr = lpXPSOF->lpstrm->lpVtbl->Write(lpXPSOF->lpstrm,
                lpvData, cbSize, &cbWritten);

        goto ret;
    }

    /* The callers data will fit in our current buffer.  Copy the */
    /* data into the buffer, mark the buffer as dirty, and adjust */
    /* the buffer offset.  Set cbWritten to cbSize and return.    */

    lpvWrite = (VOID HUGEP *)((LPBYTE)lpXPSOF->lpvBuff + lpXPSOF->libBuff);
    HMemCopy(lpvWrite, lpvData, cbSize);
    lpXPSOF->fDirty = TRUE;
    lpXPSOF->libBuff += cbSize;
    cbWritten = cbSize;

ret:
    if (lpcbWritten)
        *lpcbWritten = cbWritten;

    DebugTraceResult (XPSOF_Write(), hr);
    return hr;
}

/*
 -  IStream::Seek
 -
 *  Purpose:
 *      To move the file pointer in the stream.
 */

STDMETHODIMP
XPSOF_Seek (LPXPSOF lpXPSOF,
    LARGE_INTEGER liMove,
    DWORD dwMode,
    ULARGE_INTEGER * lpliPos)
{
    HRESULT hr = hrSuccess;

    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        (lpliPos && IsBadWritePtr (lpliPos, sizeof(ULARGE_INTEGER))))
    {
        DebugTraceSc (XPSOF_Seek(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    Assert(lpXPSOF->cbBuffMac >= lpXPSOF->libBuff);

    /* If our buffer is dirty, then we've been writing into it and   */
    /* we need to flush it.  Else, if it isn't dirty and our offset  */
    /* and buffer size are not equal, then we've been reading and we */
    /* need to rewind the underlying stream to match our position.   */

    if (lpXPSOF->fDirty)
    {
        hr = XPSOF_Commit(lpXPSOF, 0);

        if (HR_FAILED(hr))
            goto ret;
    }
    else
    {
        if ((dwMode == STREAM_SEEK_CUR) &&
            (lpXPSOF->libBuff != lpXPSOF->cbBuffMac))
        {
            hr = HrRewindStream(lpXPSOF, lpXPSOF->cbBuffMac-lpXPSOF->libBuff);

            if (HR_FAILED(hr))
                goto ret;
        }

        lpXPSOF->libBuff = 0;
        lpXPSOF->cbBuffMac = 0;
    }

    /* Now, call the real streams Seek method. */

    hr = lpXPSOF->lpstrm->lpVtbl->Seek(lpXPSOF->lpstrm,
            liMove, dwMode, lpliPos);

ret:
    return hr;
}

/*
 -  IStream::SetSize
 -
 *  Purpose:
 *      To set a max size on the file.
 */

STDMETHODIMP
XPSOF_SetSize (LPXPSOF lpXPSOF, ULARGE_INTEGER liSize)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_SetSize(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->SetSize(lpXPSOF->lpstrm, liSize);
}

/*
 -  IStream::CopyTo
 -
 *  Purpose:
 *      To copy data from one stream to another.
 */

STDMETHODIMP
XPSOF_CopyTo (LPXPSOF lpXPSOF,
    LPSTREAM lpStrmDst,
    ULARGE_INTEGER cbCopy,
    ULARGE_INTEGER * lpcbRead,
    ULARGE_INTEGER * lpcbWritten)
{
    HRESULT hr;

    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        IsBadReadPtr (lpStrmDst, sizeof(LPVOID)) ||
        IsBadWritePtr (lpcbRead, sizeof(ULARGE_INTEGER)) ||
        IsBadWritePtr (lpcbWritten, sizeof(ULARGE_INTEGER)))
    {
        DebugTraceSc (XPSOF_CopyTo(), STG_E_INVALIDPARAMETER);
        return ResultFromScode(STG_E_INVALIDPARAMETER);
    }

    Assert(lpXPSOF->cbBuffMac >= lpXPSOF->libBuff);

    /* If our buffer is dirty, then we've been writing into it and   */
    /* we need to flush it.  Else, if it isn't dirty and our offset  */
    /* and buffer size are not equal, then we've been reading and we */
    /* need to rewind the underlying stream to match our position.   */

    if (lpXPSOF->fDirty)
    {
        hr = XPSOF_Commit(lpXPSOF, 0);

        if (HR_FAILED(hr))
            goto ret;
    }
    else
    {
        if (lpXPSOF->libBuff != lpXPSOF->cbBuffMac)
        {
            hr = HrRewindStream(lpXPSOF, lpXPSOF->cbBuffMac-lpXPSOF->libBuff);

            if (HR_FAILED(hr))
                goto ret;
        }

        lpXPSOF->libBuff = 0;
        lpXPSOF->cbBuffMac = 0;
    }

    /* Now, call the real streams CopyTo method. */

    hr = lpXPSOF->lpstrm->lpVtbl->CopyTo(lpXPSOF->lpstrm, lpStrmDst,
            cbCopy, lpcbRead, lpcbWritten);

ret:
    return hr;
}

/*
 -  IStream::Commit
 -
 *  Purpose:
 *      To force a write to disk of any data that is buffered
 *      by either this object or the operating system.
 */

STDMETHODIMP
XPSOF_Commit (LPXPSOF lpXPSOF, ULONG ulFlags)
{
    HRESULT hr;

    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_Commit(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    /* Flush my internal buffer if it is dirty. */

    if ((lpXPSOF->ulFlags & XPSOF_WRITE) && (lpXPSOF->fDirty))
    {
        hr = lpXPSOF->lpstrm->lpVtbl->Write(lpXPSOF->lpstrm,
                lpXPSOF->lpvBuff, lpXPSOF->libBuff, NULL);

        if (HR_FAILED(hr))
            goto ret;
    }

    /* Call Commit on the underlying stream. */

    hr = lpXPSOF->lpstrm->lpVtbl->Commit(lpXPSOF->lpstrm, ulFlags);

    /* Mark my buffer as empty cal clean. */

    lpXPSOF->fDirty = FALSE;
    lpXPSOF->libBuff = 0;
    lpXPSOF->cbBuffMac = 0;

ret:
    return hr;
}

/*
 -  IStream::Stat
 -
 *  Purpose:
 *      To retrieve information about the stream.
 */

STDMETHODIMP
XPSOF_Stat (LPXPSOF lpXPSOF, STATSTG * lpStg, DWORD dwFlags)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)) ||
        IsBadWritePtr (lpStg, sizeof(STATSTG)))
    {
        DebugTraceSc (XPSOF_Stat(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->Stat(lpXPSOF->lpstrm, lpStg, dwFlags);
}

/*
 -  HrRewindStream
 -
 *  Purpose:
 *      This gets called to back-up the file pointer when a Write operation
 *      follows a Read operation.  This is necessary because the file pointer
 *      is actually further ahead in the file than the buffered file pointer.
 *
 *  Parameters:
 *      lpXPSOF             - Our wrapped stream-on-file object
 *      ib                  - Number of bytes to rewind
 *
 *  Returns:
 *      hr                  - Result of the underlying Seek() call
 */

HRESULT HrRewindStream(LPXPSOF lpXPSOF, ULONG ib)
{
    LARGE_INTEGER liRewind;

    if (!ib)
        return hrSuccess;

    liRewind.HighPart = 0xFFFFFFFF;
    liRewind.LowPart = -((LONG)ib);

    return lpXPSOF->lpstrm->lpVtbl->Seek(lpXPSOF->lpstrm,
            liRewind, STREAM_SEEK_CUR, NULL);
}

/* Unimplemented methods; call straight through to the underlying stream. */

STDMETHODIMP
XPSOF_Revert (LPXPSOF lpXPSOF)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_Revert(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->Revert(lpXPSOF->lpstrm);
}

STDMETHODIMP
XPSOF_LockRegion (LPXPSOF lpXPSOF,
    ULARGE_INTEGER uliOffset,
    ULARGE_INTEGER uliSize,
    DWORD dwLockType)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_LockRegion(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->LockRegion(lpXPSOF->lpstrm,
            uliOffset, uliSize, dwLockType);
}

STDMETHODIMP
XPSOF_UnlockRegion (LPXPSOF lpXPSOF,
    ULARGE_INTEGER uliOffset,
    ULARGE_INTEGER uliSize,
    DWORD dwLockType)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_UnlockRegion(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->UnlockRegion(lpXPSOF->lpstrm,
            uliOffset, uliSize, dwLockType);
}

STDMETHODIMP
XPSOF_Clone (LPXPSOF lpXPSOF, LPSTREAM * lppStm)
{
    if (IsBadWritePtr (lpXPSOF, sizeof(XPSOF)))
    {
        DebugTraceSc (XPSOF_Clone(), STG_E_INVALIDPARAMETER);
        return ResultFromScode (STG_E_INVALIDPARAMETER);
    }

    return lpXPSOF->lpstrm->lpVtbl->Clone(lpXPSOF->lpstrm, lppStm);
}

/*
 -  HMemCopy
 -
 *  Purpose:
 *      Same as memcpy but works with huge pointers.
 */

VOID
HMemCopy(VOID HUGEP * lpvDst, VOID HUGEP * lpvSrc, ULONG cb)
{
    while (cb--)
        *((BYTE HUGEP *)lpvDst)++ = *((BYTE HUGEP *)lpvSrc)++;
}


XPSOF.H

/*
 -  X P S O F . H
 -
 *  Purpose:
 *      Wraps the StreamOnFile object to add buffering of the stream.
 *      The wrappered version uses an in-memory buffer, or cache, to
 *      reduce the number of actual Reads and Writes to the underlying
 *      stream.  The goal is, obviously, to improve performance.
 *
 *  Copyright 1992-1995, Microsoft Corporation.  All Rights Reserved.
 */

#define XPSOF_BUFF_MAX      ((ULONG) 1024)

#define XPSOF_READ          ((ULONG) 0x00000001)
#define XPSOF_WRITE         ((ULONG) 0x00000002)
#define XPSOF_READWRITE     ((ULONG) 0x00000003)

/* Creates the wrapped SOF object */

STDMETHODIMP HrWrapStreamOnFile(
    LPALLOCATEBUFFER    lpAllocBuffer,
    LPFREEBUFFER        lpFreeBuffer,
    ULONG               ulFlags,
    LPSTREAM            lpStream,
    LPSTREAM *          lppWrappedStream);

/* IStream Interface */

#define MAPI_ISTREAM_METHODS(IPURE)                                     \
    MAPIMETHOD(Read)                                                    \
        (THIS_  VOID HUGEP *                lpv,                        \
                ULONG                       cb,                         \
                ULONG FAR *                 lpcbRead) IPURE;            \
    MAPIMETHOD(Write)                                                   \
        (THIS_  VOID const HUGEP *          lpv,                        \
                ULONG                       cb,                         \
                ULONG FAR *                 lpcbWritten) IPURE;         \
    MAPIMETHOD(Seek)                                                    \
        (THIS_  LARGE_INTEGER               libMove,                    \
                DWORD                       dwOrigin,                   \
                ULARGE_INTEGER FAR *        lplibNewPosition) IPURE;    \
    MAPIMETHOD(SetSize)                                                 \
        (THIS_  ULARGE_INTEGER              libNewSize) IPURE;          \
    MAPIMETHOD(CopyTo)                                                  \
        (THIS_  IStream FAR *               lpstm,                      \
                ULARGE_INTEGER              cb,                         \
                ULARGE_INTEGER FAR *        lpcbRead,                   \
                ULARGE_INTEGER FAR *        lpcbWritten) IPURE;         \
    MAPIMETHOD(Commit)                                                  \
        (THIS_  DWORD                       dwCommitFlags) IPURE;       \
    MAPIMETHOD(Revert)                                                  \
        (THIS)                              IPURE;                      \
    MAPIMETHOD(LockRegion)                                              \
        (THIS_  ULARGE_INTEGER              libOffset,                  \
                ULARGE_INTEGER              cb,                         \
                DWORD                       dwLockType) IPURE;          \
    MAPIMETHOD(UnlockRegion)                                            \
        (THIS_  ULARGE_INTEGER              ulibOffset,                 \
                ULARGE_INTEGER              cb,                         \
                DWORD                       dwLockType) IPURE;          \
    MAPIMETHOD(Stat)                                                    \
        (THIS_  STATSTG FAR *               lpstatstg,                  \
                DWORD                       dwStatFlag) IPURE;          \
    MAPIMETHOD(Clone)                                                   \
        (THIS_  IStream FAR * FAR *         lppstm) IPURE;              \

/* Interface declaration */

#undef  INTERFACE
#define INTERFACE struct _XPSOF
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, XPSOF_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_ISTREAM_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, XPSOF_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_ISTREAM_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(XPSOF_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_ISTREAM_METHODS(IMPL)
};

typedef struct _XPSOF
{
    XPSOF_Vtbl *        lpVtbl;
    LONG                lcInit;
    ULONG               ulFlags;
    LPSTREAM            lpstrm;
    LPVOID              lpvBuff;
    ULONG               libBuff;
    ULONG               cbBuffMac;
    BOOL                fDirty;
    LPFREEBUFFER        FreeBuffer;

} XPSOF, FAR * LPXPSOF;


XPSTATUS.C

/*
 -  X P S T A T U S . C
 -
 *  Purpose:
 *      Sample transport provider status interface code.  This module
 *      contains the following Transport SPI entry points:
 *
 *          OpenStatusEntry()
 *
 *      The Status Object methods implemented in this module are:
 *
 *          QueryInterface,
 *          AddRef,
 *          Release,
 *          GetLastError,
 *          GetProps,
 *          GetPropList,
 *          SettingsDialog,
 *          FlushQueues,
 *
 *      Additional support functions found here:
 *
 *          HrBuildTransportStatus
 *          HrUpdateTransportStatus
 *          NewSOB
 *          HrLoadStatusString
 *          MapScodeSz
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

//$BUG  Invalid flags should return MAPI_E_UNKNOWN_FLAGS!

#include "xppch.h"
#include "xpresrc.h"

#define MAX_STRING          8192
#define MAX_RESRC_STRING    256

/* Declared in xpbase.c */

extern sptLogonArray;

/*  SOB IMAPIProp jump table */

SOB_Vtbl vtblSOB =
{
    SOB_QueryInterface,
    SOB_AddRef,
    SOB_Release,
    SOB_GetLastError,
    SOB_SaveChanges,
    SOB_GetProps,
    SOB_GetPropList,
    SOB_OpenProperty,
    SOB_SetProps,
    SOB_DeleteProps,
    SOB_CopyTo,
    SOB_CopyProps,
    SOB_GetNamesFromIDs,
    SOB_GetIDsFromNames,
    SOB_ValidateState,
    SOB_SettingsDialog,
    SOB_ChangePassword,
    SOB_FlushQueues,
};

/*  Static properties. In this case the array of Property Tags available
    from the Status object if opened.  The PR_RESOURCE_PATH must be last
    because we will not tell the client about this property if it isn't
    set in the Logon dialog.  Being last makes GetPropList() easier to do. */

/* Number of columns in Status row. */

#define NUM_STATUS_ROW_PROPS 10

const static SizedSPropTagArray(NUM_STATUS_ROW_PROPS, sptaStatusRow) =
{
    NUM_STATUS_ROW_PROPS,
    {
        PR_RESOURCE_METHODS,
        PR_PROVIDER_DISPLAY,
        PR_DISPLAY_NAME,
        PR_IDENTITY_DISPLAY,
        PR_IDENTITY_ENTRYID,
        PR_IDENTITY_SEARCH_KEY,
        PR_STATUS_CODE,
        PR_STATUS_STRING,
        PR_OBJECT_TYPE,
        PR_RESOURCE_PATH
    }
};

/* List of IID's we support on open/query */

#define N_IID 3
static const LPIID lpStatusFamilyIID[N_IID] =
{
    (LPIID) &IID_IUnknown,      /* IUnknown is everyone's parent */
    (LPIID) &IID_IMAPIProp,     /* IMAPIProp follows from this   */
    (LPIID) &IID_IMAPIStatus    /* My actual interface ID        */
};

/* Local code */

static HRESULT NewSOB(LPCIID lpInterface,
    ULONG ulOpenFlags,
    LPXPL lpxpl,
    ULONG * lpulObjType,
    LPSOB * lppSOB);

static HRESULT HrLoadStatusString(LPXPL lpxpl,
    LPVOID lpvParent,
    LPTSTR * lppsz);

/*
 -  HrBuildTransportStatus
 -
 *  Purpose:
 *      Called by TransportLogon to build the Status Table entry for the
 *      Sample Transport Provider.
 *
 *  Parameters:
 *      lpxpl               The current session structure.
 *      ulFlags             0 or STATUSROW_UPDATE
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *      (Status Row)        Contains properties from session
 *
 *  Operation:
 *      This one's relatively simple: build a property value array based on
 *      data in the session structure, and call (*lpMAPISup)->ModifyStatusRow
 *      to register the row in the table.
 */

HRESULT
HrBuildTransportStatus(LPXPL lpxpl, ULONG ulFlags)
{
    SCODE sc = 0;
    LPSPropValue lpPropArray = NULL;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPSPropValue pPropValT;
    LPVOID lpvT;
    LPVOID lpvT1;
    LPVOID lpvT2;
    LPTSTR lpszStatus = NULL;
    ULONG ulT;

    /*  Allocate initial property array now. */

    sc = lpxpl->AllocateBuffer(sizeof(SPropValue) * (NUM_STATUS_ROW_PROPS - 1), (LPVOID *) &lpPropArray);

    if (sc)
    {
        DebugTraceSc(Status Row Allocation, sc);
        goto ret;
    }

    /*  Store the properties into the status row */

    pPropValT = lpPropArray;

    /*  1. Transport's Display Name. */

    pPropValT->ulPropTag = PR_PROVIDER_DISPLAY;
    pPropValT->Value.LPSZ = (LPTSTR) MYDISPLAYNAME;
    pPropValT++;

    /*  2. Extra methods on status object */

    pPropValT->ulPropTag = PR_RESOURCE_METHODS;
    pPropValT->Value.ul = lpxpl->ulResourceMethods;
    pPropValT++;

    /*  3. Display Name associated with session. Use email address. */

    lpvT1 = ArrayIndex (PR_SAMPLE_DISPLAY_NAME, lpxpl->lpPropArray).Value.LPSZ;
    lpvT2 = ArrayIndex (PR_SAMPLE_EMAIL_ADDRESS, lpxpl->lpPropArray).Value.LPSZ;
    ulT = (lstrlen((LPCTSTR) lpvT1)+lstrlen((LPCTSTR) lpvT2)+4)*sizeof (TCHAR);

    sc = lpxpl->AllocateMore(ulT, (LPVOID) lpPropArray, &lpvT);

    if (sc)
    {
        DebugTraceSc(Session Display Name allocation, sc);
        goto ret;
    }
    wsprintf((LPTSTR) lpvT, TEXT("%s [%s]"), (LPTSTR) lpvT1, (LPTSTR) lpvT2);
    pPropValT->ulPropTag = PR_DISPLAY_NAME;
    pPropValT->Value.LPSZ = (LPTSTR) lpvT;
    pPropValT++;

    /*  4. User's Display Name. */

    Assert(lpxpl->lpMyIDArray);
    *pPropValT = lpxpl->lpMyIDArray[1];

    Assert(pPropValT->ulPropTag == PR_SENDER_NAME);
    Assert(!IsBadStringPtr(pPropValT->Value.LPSZ, MAX_STRING));

    pPropValT->ulPropTag = PR_IDENTITY_DISPLAY;
    pPropValT++;

    /*  5. User Entry-ID. */

    Assert(lpxpl->lpMyIDArray);
    *pPropValT = lpxpl->lpMyIDArray[0];

    Assert(pPropValT->ulPropTag == PR_SENDER_ENTRYID);
    Assert(pPropValT->Value.bin.cb);
    Assert(!IsBadReadPtr(pPropValT->Value.bin.lpb, (UINT) pPropValT->Value.bin.cb));

    pPropValT->ulPropTag = PR_IDENTITY_ENTRYID;
    pPropValT++;

    /*  6. User Search Key. */

    Assert(lpxpl->lpMyIDArray);
    *pPropValT = lpxpl->lpMyIDArray[2];

    Assert(pPropValT->ulPropTag == PR_SENDER_SEARCH_KEY);
    Assert(pPropValT->Value.bin.cb);
    Assert(!IsBadReadPtr(pPropValT->Value.bin.lpb, (UINT) pPropValT->Value.bin.cb));

    pPropValT->ulPropTag = PR_IDENTITY_SEARCH_KEY;
    pPropValT++;

    /*  7. Code. Online/Offline, Send/Receive, Uploading/Downloading. */

    pPropValT->ulPropTag = PR_STATUS_CODE;
    pPropValT->Value.ul = lpxpl->ulTransportStatus;
    pPropValT++;

    /*  8. Status String based on Status Code. */

    if (HrLoadStatusString(lpxpl, lpPropArray, &lpszStatus))
    {
        pPropValT->ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_STATUS_STRING));
        pPropValT->Value.err = MAPI_E_NOT_FOUND;
    }
    else
    {
        pPropValT->ulPropTag = PR_STATUS_STRING;
        pPropValT->Value.LPSZ = lpszStatus;
    }
    pPropValT++;

    /*  9. Resource Path == WGAP Directory. */

    lpvT = (LPVOID) ArrayIndex(PR_SAMPLE_DIRECTORY, lpxpl->lpPropArray).Value.LPSZ;

    if (lstrlen((LPCTSTR) lpvT))
    {
        pPropValT->ulPropTag = PR_RESOURCE_PATH;
        pPropValT->Value.LPSZ = (LPTSTR) lpvT;
        pPropValT++;
    }

    /*  Status Row is built. Register it. */

    sc = GetScode(lpMAPISup->lpVtbl->ModifyStatusRow(lpMAPISup,
            (pPropValT - lpPropArray), lpPropArray, ulFlags));

    if (FAILED(sc))
        DebugTrace("ModifyStatusRow failed.\n");

ret:
    /*  Free the allocated memory */

    lpxpl->FreeBuffer(lpPropArray);

    DebugTraceSc(HrBuildTransportStatus, sc);
    return ResultFromScode(sc);
}

/*
 -  HrUpdateTransportStatus
 -
 *  Purpose:
 *      Called by Transport code to update the PR_STATUS_CODE property in the
 *      Status Table row for the Sample Transport Provider.
 *
 *  Parameters:
 *      lpxpl               The current session structure.
 *      ulFlags             Flags. Not currently used.
 *
 *  Returns:
 *      (HRESULT)           Errors encountered if any.
 *      (Status Row)        PR_STATUS_CODE and PR_STATUS_STRING updated
 *
 *  Operation:
 *      Transport should already have updated lpxpl->ulTransportStatus
 *      before calling here.  So all this routine does is construct a
 *      notification structure for a status object modification and call
 *      (*lpMAPISup)->ModifyStatusRow() to update the table.  If there is a
 *      string available in the StringTable, then our cProps goes to 2 and
 *      we assign the string to the 2nd element of rgProps.
 */

HRESULT
HrUpdateTransportStatus(LPXPL lpxpl, ULONG ulFlags)
{
    HRESULT hResult;
    ULONG cProps = 2;
    SPropValue rgProps[2];
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPTSTR lpszStatus = NULL;

    /*  Store the new Transport Provider Status Code. */

    rgProps[0].ulPropTag = PR_STATUS_CODE;
    rgProps[0].Value.ul = lpxpl->ulTransportStatus;

    /* Set the Status String according to ulStatus */

    if (HrLoadStatusString(lpxpl, NULL, &lpszStatus))
    {
        rgProps[1].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_STATUS_STRING));
        rgProps[1].Value.err = MAPI_E_NOT_FOUND;
    }
    else
    {
        rgProps[1].ulPropTag = PR_STATUS_STRING;
        rgProps[1].Value.LPSZ = lpszStatus;
    }

    /*  OK. Notify the Spooler. It will tell MAPI. */

    hResult = lpMAPISup->lpVtbl->ModifyStatusRow(lpMAPISup,
            cProps, rgProps, STATUSROW_UPDATE);

    lpxpl->FreeBuffer(lpszStatus);

    DebugTraceResult(ModifyStatusRow, hResult);
    return hResult;
}

/*
 -  OpenStatusEntry
 -
 *  Purpose:
 *      Called by Spooler to service client OpenEntry request.
 *
 *  Parameters:
 *      lpiid               Interface identifier.
 *      ulFlags             Open flags. The only doc'ed flag
 *                          is MAPI_MODIFY, which we don't support.
 *      lpulObjType         Pointer to a unsigned long into which
 *                          we are to store the type of the
 *                          object we've just opened.
 *      lppEntry            Points to a variable into which we
 *                          may store the object pointer.
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if the session
 *                          pointer isn't valid, or any other
 *                          parameter not to our liking,
 *                          or any other errors we encounter.
 *      *lpulObjType        MAPI_STATUS if we open the entry,
 *                          unchanged if not
 *      *lppEntry           Pointer to object if we open the
 *                          entry, unchanged if not
 *
 *  Operation:
 *      Validate parameters. Call NewSOB() to create the object, returning
 *      object type and object pointer into user-supplied locations.
 */

STDMETHODIMP
XPL_OpenStatusEntry(LPXPL lpxpl,
    LPCIID lpiid,
    ULONG ulFlags,
    ULONG * lpulObjType,
    LPMAPISTATUS * lppEntry)
{
    HRESULT hResult;
#ifndef MAC
    LPXPP lpxpp;
#endif

    /* Need to do weak session validation to do this */

    if (IsBadWritePtr(lpxpl, sizeof(XPL)) ||
        IsBadWritePtr((lpxpp = lpxpl->lpxppParent), sizeof(XPP)) ||
        (lpiid != NULL && IsBadReadPtr(lpiid, sizeof(IID))) ||
        (IsBadWritePtr(lpulObjType, sizeof(ULONG))) ||
        (IsBadWritePtr(lppEntry, sizeof(LPMAPISTATUS))))
    {
        DebugTraceSc(OpenStatusEntry, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /* Get the Critical Section */

    EnterCriticalSection(&lpxpp->csTransport);

    /*  Validate the user's parameters always */

    /*  Invalid parameter checking: 1) make sure the passed session
        is still valid; 2) lpiid should either be null or point
        to a piece of memory at least the size of a iid; 3) lpulObjType
        must point to a writable piece of memory the size of a ulong;
        4) lppEntry must point to enough writable memory to store a
        LPMAPISTATUS. */

    if (!FIsValidSession(lpxpl))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        DebugTrace("Invalid Logon object.\n");
        goto ret;
    }

    /*  We don't support MAPI_MODIFY and no other flags are spec'ed. */

    if (ulFlags & ~MAPI_MODIFY)
    {
        hResult = ResultFromScode(MAPI_E_UNKNOWN_FLAGS);
        DebugTrace("Unknown Flags.\n");
        goto ret;
    }

    if (ulFlags & MAPI_MODIFY)
    {
        hResult = ResultFromScode(MAPI_E_NO_ACCESS);
        DebugTrace("XP Support Object doesn't support Modify access.\n");
        goto ret;
    }

    /*  The argument list looks good to us. Now, if we already have opened
        a status object on this logon context, we'll just use QueryInterface
        to get a new copy of the object... */

    if (lpxpl->lpXPStatus)
    {
        hResult = lpxpl->lpXPStatus->lpVtbl->QueryInterface(lpxpl->lpXPStatus,
                (lpiid ? lpiid : &IID_IMAPIStatus), lppEntry);

        if (HR_FAILED(hResult))
            DebugTrace("QueryInterface failed.\n");
        else
            *lpulObjType = MAPI_STATUS;
    }
    else
    {
        /*  Or if we don't already have an object, create it, saving a
            copy in the logon context. */

        hResult = NewSOB(lpiid, ulFlags, lpxpl,
                lpulObjType, (LPSOB *) lppEntry);

        if (HR_FAILED(hResult))
            DebugTrace("NewSOB failed.\n");
        else
            lpxpl->lpXPStatus = *lppEntry;
    }

ret:
    /*  Release the critical section. */

    LeaveCriticalSection(&lpxpp->csTransport);

    /*  Errors returned from this routine are always in sc. So if
        sc is zero we return 0. If it's nonzero we return a hResult
        built here from sc. */

    DebugTraceResult(OpenStatusEntry, hResult);
    return hResult;
}

/*
 -  NewSOB
 -
 *  Purpose:
 *      Called from OpenStatusEntry to create a Status Object.
 *
 *  Parameters:
 *      lpInterface         If non-null must be IID_IMAPIStatus
 *      ulOpenFlags         Open flags. The only doc'ed flag
 *                          is MAPI_MODIFY, which we don't support.
 *      lpxpl               The handle of the session for which
 *                          we want to open a Status object. In
 *                          Spooler context, a pointer to the
 *                          session data structure.
 *      lpulObjType         Pointer to a unsigned long into which
 *                          we are to store the type of the
 *                          object we've just opened.
 *      lppSOB              Points to a variable into which we
 *                          may store the object pointer.
 *
 * Returns:
 *      (HRESULT)           MAPI_E_NO_SUPPORT if an
 *                          interface is specified, or any
 *                          other errors we encounter.
 *      *lpulObjType        MAPI_STATUS if we open the entry,
 *                          unchanged if not
 *      *lppEntry           Pointer to object if we open the
 *                          entry, unchanged if not
 *
 *  Operation:
 *      Allocates the memory for the object, initializes its data
 *      members, plugs in the jump table and returns the appropriate
 *      stuff to the caller.
 */

static HRESULT
NewSOB(LPCIID lpInterface,
    ULONG ulOpenFlags,
    LPXPL lpxpl,
    ULONG * lpulObjType,
    LPSOB * lppSOB)
{
    SCODE sc;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPSOB lpSOB = NULL;
    ULONG i;

    *lpulObjType = 0;
    *lppSOB = NULL;

    /*  Make sure no interface (or ours) was specified. */

    if (lpInterface)
    {
        for (i = 0; i < N_IID; i++)
        {
            if (!memcmp(lpInterface, lpStatusFamilyIID[i], sizeof(IID)))
                break;
        }

        if (i == N_IID)
        {
            DebugTraceSc(NewSOB, E_INVALIDARG);
            return ResultFromScode(E_INVALIDARG);
        }
    }

    /*  Allocate space for the SOB structure */

    sc = lpxpl->AllocateBuffer(sizeof(SOB), (LPVOID *) &lpSOB);

    if (sc)
    {
        DebugTrace("Allocation of Status Object failed.\n");
        return ResultFromScode(sc);
    }

    /*  Fill in the data members and the jump table. */

    lpSOB->lpVtbl = &vtblSOB;
    lpSOB->lcInit = 1;
    lpSOB->lpsobMyAddress = lpSOB;

    lpSOB->hrLastError = 0;
    lpSOB->ulOpenFlags = ulOpenFlags;
    lpSOB->AllocateBuffer = lpxpl->AllocateBuffer;
    lpSOB->AllocateMore = lpxpl->AllocateMore;
    lpSOB->FreeBuffer = lpxpl->FreeBuffer;
    lpSOB->lpMAPISup = lpMAPISup;
    lpSOB->lpxpl = lpxpl;

    /*  Return the newly constructed object to the caller. */

    *lpulObjType = MAPI_STATUS;
    *lppSOB = lpSOB;

    return 0;
}

/*
 -  HrLoadStatusString
 -
 *  Purpose:
 *      Retrieves a Status String from the resource StringTable
 *      based on ulStatus code passed in.
 *
 *  Parameters:
 *      lpxpl               The Transport Logon session
 *      lpvParent           If non-null we chain the memory to this block
 *      lppsz               Place to copy Status String to.
 *
 * Returns:
 *      hr                  Indicating Success/Failure
 *
 */

static HRESULT
HrLoadStatusString(LPXPL lpxpl, LPVOID lpvParent, LPTSTR *lppsz)
{
    SCODE sc = S_OK;
    UINT ids;
    ULONG cb;
    ULONG ulStatus = lpxpl->ulTransportStatus;
    TCHAR szStatus[MAX_RESRC_STRING];

    /* Determine the IDS of the status.  Since the status' are bit fields
       of ulStatus, we apply this hierarcy to determine the correct string. */

    if (ulStatus & STATUS_INBOUND_ACTIVE)
        ids = IDS_STATUS_UPLOADING;
    else if (ulStatus & STATUS_OUTBOUND_ACTIVE)
        ids = IDS_STATUS_DOWNLOADING;
    else if (ulStatus & STATUS_INBOUND_FLUSH)
        ids = IDS_STATUS_INFLUSHING;
    else if (ulStatus & STATUS_OUTBOUND_FLUSH)
        ids = IDS_STATUS_OUTFLUSHING;
    else if ((ulStatus & STATUS_AVAILABLE) &&
            ((ulStatus & STATUS_INBOUND_ENABLED) ||
            (ulStatus & STATUS_OUTBOUND_ENABLED)))
        ids = IDS_STATUS_ONLINE;
    else if (ulStatus & STATUS_AVAILABLE)
        ids = IDS_STATUS_AVAILABLE;
    else
        ids = IDS_STATUS_OFFLINE;

    /* Attempt to load the resource into our automatic variable. */

    cb = LoadString(lpxpl->lpxppParent->hInst, ids, szStatus, MAX_RESRC_STRING);

    if (!cb)
    {
        sc = MAPI_E_CALL_FAILED;
        DebugTrace("LoadString failed in HrLoadStatusString.\n");
        goto ret;
    }

    /* We'll get the exact size of the string and put it on the heap.
       The caller had the luxury of specifying a parent block to chain
       this allocation to. */

    cb = (cb + 1) * sizeof(TCHAR);

    if (lpvParent)
        sc = lpxpl->AllocateMore(cb, lpvParent, (LPVOID *)lppsz);
    else
        sc = lpxpl->AllocateBuffer(cb, (LPVOID *)lppsz);

    if (FAILED(sc))
    {
        DebugTrace("Allocation failed in HrLoadStatusString.\n");
        goto ret;
    }

    lstrcpy(*lppsz, szStatus);

ret:
    DebugTraceSc(HrLoadStatusString, sc);
    return ResultFromScode(sc);
}

/*
 -  IMAPISTATUS::QueryInterface
 -
 *  Purpose:
 *      Standard IUnknown method.
 *
 *  Parameters:
 *      lpObject            Pointer to object
 *      lpiid               New interface to Query to
 *      lppNewObj           Where to store pointer to new object
 *
 *  Returns:
 *      (SCODE)             E_INVALIDARG if the input object
 *                          doesn't look like a SOB, if the
 *                          IID isn't readable or lppNewObj
 *                          isn't writable; E_NOINTERFACE
 *                          if we don't know the IID.
 *
 *  Operation:
 *      Validate parameters. See if the caller wants IUnknown, IMAPIProp or
 *      IStatus. If so, increment the usage count and return a new object.
 */

STDMETHODIMP
SOB_QueryInterface(LPSOB lpSOB,
    REFIID lpiid,
    LPVOID * lppNewObj)
{
    ULONG i;

    /*  Validate the parameters: 1) Does it seem to be an object?
        2) is the refcount nonzero? 3) Is there enough there for
        an interface ID? 4) Is there enough there for a new object? */

    if ((IsBadWritePtr(lpSOB, sizeof(SOB))) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (IsBadReadPtr(lpiid, sizeof(IID))) ||
        (IsBadWritePtr(lppNewObj, sizeof(LPSOB))))
    {
        DebugTraceSc(SOB_QueryInterface, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    /*  See if the requested interface is one of ours */

    for (i = 0; i < N_IID; i++)
    {
        if (!memcmp(lpiid, lpStatusFamilyIID[i], sizeof(IID)))
            break;
    }

    /*  If we didn't find the interface, get out now. */

    if (i == N_IID)
    {
        /* OLE requires zeroing [out] parameters */
        *lppNewObj = NULL;
        DebugTraceSc(SOB_QueryInterface, E_NOINTERFACE);
        return ResultFromScode(E_NOINTERFACE);
    }

    /*  We'll do this one. Bump the usage count and return a new pointer. */

    ++lpSOB->lcInit;
    *lppNewObj = lpSOB;

    return hrSuccess;

}

/*
 -  IMAPISTATUS::AddRef
 -
 *  Purpose:
 *      Increment reference count if nonzero.
 *
 *  Parameters:
 *      lpObject            Pointer to object (should be SOB)
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                          it doesn't seem to be SOB.
 *
 *  Operation:
 *      Make sure it looks like a SOB, and if so, bump the reference count
 *      and return the result to the caller.
 */

STDMETHODIMP_(ULONG)
SOB_AddRef(LPSOB lpSOB)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        lpSOB->lcInit == 0 ||
        lpSOB->lpsobMyAddress != lpSOB)
        return 0;

    return ++lpSOB->lcInit;

}

/*
 -  IMAPISTATUS::Release
 -
 *  Purpose:
 *      Decrement lcInit. If it's zero, release the object.
 *
 *  Parameters:
 *      lpObject            Pointer to object (should be SOB)
 *
 *  Returns:
 *      (ULONG)             Current reference count or zero if
 *                          it doesn't seem to be SOB.
 *
 *  Operation:
 *      Make sure it looks like a SOB, and if so, decrement the reference
 *      count. If the count is now zero, deallocate the object.
 *      Return the reference count to the caller.
 */

STDMETHODIMP_(ULONG)
SOB_Release(LPSOB lpSOB)
{
    /*  If it doesn't seem to be an object or refcount is zero, return zero */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        lpSOB->lcInit == 0 ||
        lpSOB->lpsobMyAddress != lpSOB)
        return 0;

    --lpSOB->lcInit;

    if (lpSOB->lcInit == 0)
    {
        DebugTrace("SOB::Release() freeing SOB.\n");

        /* Unlink the status object from the logon object */

        if (FIsValidSession(lpSOB->lpxpl))
            lpSOB->lpxpl->lpXPStatus = NULL;

        lpSOB->lpVtbl = NULL;
        (*lpSOB->FreeBuffer) (lpSOB);
        return 0;
    }

    return lpSOB->lcInit;
}

/*
 -  IMAPISTATUS::GetLastError
 -
 *  Purpose:
 *      Returns a string associated with the last HRESULT returned
 *      by the SOB object.
 *
 *  Parameters:
 *      lpObject            Pointer to object (should be SOB)
 *      hError              HRESULT that caller is interested in
 *      ulFlags             Ignored.
 *      lppszMessage        Pointer to where message pointer should
 *                          be copied.
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't look
 *                          like a SOB or if any parameters look
 *                          bad; other errors if any; 0 if successful.
 *      *lppszMessage       Pointer to error message if any, else NULL
 *
 *  Operation:
 *      Confirm the parameters. Compare the HRESULT to our last saved one. If
 *      they match, copy the string into a new buffer and store the pointer
 *      into the location provided by the caller.
 */

STDMETHODIMP
SOB_GetLastError(LPSOB lpSOB,
    HRESULT hError,
    ULONG ulFlags,
    LPMAPIERROR * lppMapiError )
{
    HRESULT hResult = hrSuccess;
    SCODE   sc;
    LPTSTR  pszMessage  = NULL;

    /*  Validate the object and return pointer */

    *lppMapiError = NULL;

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (IsBadWritePtr(lppMapiError, sizeof(LPMAPIERROR))))
    {
        DebugTraceSc(SOB_GetLastError, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & ~MAPI_UNICODE )
    {
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    /*  See if we have the message the caller wants. If so,
        make a copy and pass it back. */

    if ((hError != 0) && (hError == lpSOB->hrLastError))
    {
        sc = lpSOB->AllocateBuffer( sizeof( MAPIERROR ), lppMapiError );
        if ( FAILED( sc ) )
        {
            hResult = ResultFromScode( sc );
            goto ret;
        }

        memset( *lppMapiError, 0, sizeof( MAPIERROR ) );

        (*lppMapiError)->ulVersion = MAPI_ERROR_VERSION;

        hResult = MapScodeSz(GetScode(hError), lpSOB->lpxpl, &pszMessage);
        if ( HR_FAILED( hResult ) )
            goto ret;

        sc = lpSOB->AllocateMore( Cbtszsize( pszMessage ), *lppMapiError,
                &(*lppMapiError)->lpszError );
        if ( FAILED( sc ) )
        {
            hResult = ResultFromScode( sc );
            goto ret;
        }

        lstrcpy( (*lppMapiError)->lpszError, pszMessage );
    }

ret:
    if ( hResult )
    {
        lpSOB->FreeBuffer( *lppMapiError );
        *lppMapiError = NULL;
    }

    lpSOB->FreeBuffer( pszMessage );
    return hResult;
}

/*
 -  IMAPISTATUS::GetProps
 -
 *  Purpose:
 *      Returns the properties listed in the lpPropTagArray.
 *
 *  Parameters:
 *      lpObject            Pointer to object (should be SOB)
 *      lpPropTagArray      List of tags for which values are desired
 *      ulFlags             UNICODE / String8
 *      lpcValues           Pointer: where to store count of values
 *      lppPropArray        Pointer: where to store pointer to values
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't look like
 *                          a SOB or if any of the parameters look
 *                          bad; other errors if any; 0 if successful.
 *      *lpcValues          Contains a property count if successful
 *      *lppPropArray       Undefined if not successful; NULL if
 *                          no properties; points to array of
 *                          properties if any.
 *
 *  Operation:
 *      Confirm the parameters. If lpPropTagArray is NULL, use our canned
 *      property list.
 *      Walk through the list, and give the caller any properties in the
 *      list which we can provide. Return count in *lpcValues and pointer to
 *      the array in *lppPropArray.
 */

STDMETHODIMP
SOB_GetProps(LPSOB lpSOB,
    LPSPropTagArray lpPropTagArray,
    ULONG ulFlags,
    ULONG * lpcValues,
    LPSPropValue * lppPropArray)
{
    SCODE sc = 0;
    HRESULT hResult = 0;
    ULONG ulT = 0;
    LPXPL lpxpl = NULL;
    HINSTANCE hInst;
    LPSPropValue lpPropArray = NULL;
    LPSPropValue lpMyIDArray = NULL;
    LPSPropTagArray lpspta = NULL;
    ULONG cb;
    LPTSTR lpsz;
    LPSPropValue lpPropT;
    BOOL fGotMemErrors = FALSE;
    BOOL fMyPTA = TRUE;
    BOOL fNoResourcePath = FALSE;

    /*  Validate the object and the return pointers */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        lpSOB->lcInit == 0 ||
        lpSOB->lpsobMyAddress != lpSOB)
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto ret;
    }

    /* Validate the Flags */

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
        goto ret;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto ret;
    }

    /*  Validate the return pointers */

    if ((IsBadWritePtr(lpcValues, sizeof(ULONG))) ||
        (IsBadWritePtr(lppPropArray, sizeof(LPSPropValue))))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto ret;
    }

    /*  Validate the passed property tag array if any */

    if (lpPropTagArray)
    {
        if ((IsBadReadPtr(lpPropTagArray, CbNewSPropTagArray(0))) ||
            (lpPropTagArray->cValues == 0) ||
            (IsBadReadPtr(&lpPropTagArray->aulPropTag[0],
                    (UINT) (lpPropTagArray->cValues * sizeof(ULONG)))))
        {
            hResult = ResultFromScode(E_INVALIDARG);
            goto ret;
        }
        fMyPTA = FALSE;
        lpspta = lpPropTagArray;
    }
    else
    {
        /*  An array wasn't passed. Use ours. */

        lpspta = (LPSPropTagArray) &sptaStatusRow;
    }

    /*  Parameters have passed muster. Create the property array. */

    lpxpl = lpSOB->lpxpl;
    hInst = lpxpl->lpxppParent->hInst;

    sc = ScCopySessionProps(lpxpl, &lpPropArray, &lpMyIDArray);

    if (FAILED(sc))
        goto ret;

    sc = (*lpSOB->AllocateBuffer) (sizeof(SPropValue) * (lpspta->cValues), (LPVOID *) lppPropArray);
    if (sc)
    {
        hResult = ResultFromScode(sc);
        lpSOB->hrLastError = hResult;
        goto ret;
    }

    /*  Count will always equal input number since we'll either get a
        property value or a PT_ERROR. */

    for (ulT = 0; ulT < lpspta->cValues; ulT++)
    {
        switch (lpspta->aulPropTag[ulT])
        {
        case PR_RESOURCE_METHODS:
            (*lppPropArray)[ulT].ulPropTag = PR_RESOURCE_METHODS;
            (*lppPropArray)[ulT].Value.ul = lpxpl->ulResourceMethods;
            break;

        case PR_PROVIDER_DISPLAY:
            (*lppPropArray)[ulT].ulPropTag = PR_PROVIDER_DISPLAY;
            (*lppPropArray)[ulT].Value.LPSZ = (LPTSTR)MYDISPLAYNAME;
            break;

        case PR_DISPLAY_NAME:
            lpsz = ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ;
            cb = sizeof(TCHAR) * (lstrlen(lpsz) + 1);

            sc = lpSOB->AllocateMore(cb, (LPVOID) *lppPropArray,
                    (LPVOID *) &((*lppPropArray)[ulT].Value.LPSZ));

            if (sc)
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_DISPLAY_NAME;
                lstrcpy((*lppPropArray)[ulT].Value.LPSZ, lpsz);
            }
            break;

        case PR_IDENTITY_DISPLAY:
            lpPropT = &(lpMyIDArray[1]);
            Assert(lpPropT);
            Assert(lpPropT->ulPropTag == PR_SENDER_NAME);

            cb = sizeof(TCHAR) * (lstrlen(lpPropT->Value.LPSZ) + 1);

            sc = lpSOB->AllocateMore(cb, (LPVOID) * lppPropArray,
                    (LPVOID *) &((*lppPropArray)[ulT].Value.LPSZ));

            if (sc)
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_IDENTITY_DISPLAY;
                lstrcpy((*lppPropArray)[ulT].Value.LPSZ, lpPropT->Value.LPSZ);
            }
            break;

        case PR_IDENTITY_ENTRYID:
            lpPropT = &(lpMyIDArray[0]);
            Assert(lpPropT);
            Assert(lpPropT->ulPropTag == PR_SENDER_ENTRYID);

            cb = lpPropT->Value.bin.cb;
            Assert(cb);
            Assert(!IsBadReadPtr(lpPropT->Value.bin.lpb, (UINT)cb));

            sc = lpSOB->AllocateMore(cb, (LPVOID) *lppPropArray,
                    (LPVOID *) &((*lppPropArray)[ulT].Value.bin.lpb));

            if (sc)
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_IDENTITY_ENTRYID;
                (*lppPropArray)[ulT].Value.bin.cb = cb;
                if (cb)
                    memcpy((*lppPropArray)[ulT].Value.bin.lpb,
                            lpPropT->Value.bin.lpb, (UINT)cb);
            }
            break;

        case PR_IDENTITY_SEARCH_KEY:
            lpPropT = &(lpMyIDArray[2]);
            Assert(lpPropT);
            Assert(lpPropT->ulPropTag == PR_SENDER_SEARCH_KEY);

            cb = lpPropT->Value.bin.cb;
            Assert(cb);
            Assert(!IsBadReadPtr(lpPropT->Value.bin.lpb, (UINT)cb));

            sc = lpSOB->AllocateMore(cb, (LPVOID) *lppPropArray,
                    (LPVOID *) &((*lppPropArray)[ulT].Value.bin.lpb));

            if (sc)
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_IDENTITY_SEARCH_KEY;
                (*lppPropArray)[ulT].Value.bin.cb = cb;
                if (cb)
                    memcpy((*lppPropArray)[ulT].Value.bin.lpb,
                            lpPropT->Value.bin.lpb, (UINT)cb);
            }
            break;

        case PR_STATUS_CODE:
            (*lppPropArray)[ulT].ulPropTag = PR_STATUS_CODE;
            (*lppPropArray)[ulT].Value.ul = lpxpl->ulTransportStatus;
            break;

        case PR_STATUS_STRING:
            lpsz = NULL;

            if (sc = GetScode(HrLoadStatusString(lpxpl, *lppPropArray, &lpsz)))
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_STATUS_STRING;
                (*lppPropArray)[ulT].Value.LPSZ = lpsz;
            }
            break;

        case PR_RESOURCE_PATH:
            lpsz = (LPVOID) ArrayIndex(PR_SAMPLE_DIRECTORY, lpPropArray).Value.LPSZ;
            Assert(lpsz);

            cb = (lstrlen(lpsz) + 1) * sizeof(TCHAR);

            if (cb == sizeof(TCHAR))
            {
                if (fMyPTA)
                {
                    fNoResourcePath = TRUE;
                }
                else
                {
                    (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_RESOURCE_PATH));
                    (*lppPropArray)[ulT].Value.err = MAPI_E_NOT_FOUND;
                }
                break;
            }

            sc = (*lpSOB->AllocateMore) (cb, (LPVOID) * lppPropArray, (LPVOID *) &((*lppPropArray)[ulT].Value.LPSZ));

            if (sc)
            {
                fGotMemErrors = TRUE;
                (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_RESOURCE_PATH));
                (*lppPropArray)[ulT].Value.err = sc;
            }
            else
            {
                (*lppPropArray)[ulT].ulPropTag = PR_RESOURCE_PATH;
                lstrcpy((*lppPropArray)[ulT].Value.LPSZ, lpsz);
            }
            break;

        case PR_OBJECT_TYPE:
            (*lppPropArray)[ulT].ulPropTag = PR_OBJECT_TYPE;
            (*lppPropArray)[ulT].Value.ul = MAPI_STATUS;
            break;

        default:
            (*lppPropArray)[ulT].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(lpspta->aulPropTag[ulT]));
            (*lppPropArray)[ulT].Value.err = MAPI_E_NOT_FOUND;

            hResult = ResultFromScode(MAPI_W_ERRORS_RETURNED);
            lpSOB->hrLastError = hResult;
            break;
        }
    }

    /*  Store number of properties we're returning, i.e., the number
        asked for (with possible errors). */

    if (fMyPTA && fNoResourcePath)
        *lpcValues = lpspta->cValues - 1;
    else
        *lpcValues = lpspta->cValues;

    if (fGotMemErrors)
    {
        hResult = ResultFromScode(MAPI_W_ERRORS_RETURNED);
        lpSOB->hrLastError = hResult;
    }

ret:
    if(lpxpl)
    {
        lpxpl->FreeBuffer(lpPropArray);
        lpxpl->FreeBuffer(lpMyIDArray);
    }

    DebugTraceResult(SOB_GetProps, hResult);
    return hResult;
}

/*
 -  IMAPISTATUS::GetPropList
 -
 *  Purpose:
 *
 *  Parameters:
 *      lpObject            Pointer to object (should be SOB)
 *      ulFlags             UNICODE / String8
 *      lppPropTagArray     Pointer: where to store pointer to tags
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't look like a
 *                          SOB or if any of the parameters look bad;
 *                          other errors if any; 0 if successful.
 *      *lppPropArray       Undefined if not successful; points to
 *                          array of property tags otherwise.
 *
 *  Operation:
 *      Validate parameters; Create enough memory for out property tag list,
 *      copy the list into it and do so. Return the new list into the caller's
 *      pointer.
 */

STDMETHODIMP
SOB_GetPropList(LPSOB lpSOB,
    ULONG ulFlags,
    LPSPropTagArray * lppPropTagArray)
{
    SCODE sc;
    HRESULT hResult = 0;
    LPXPL lpxpl;
    ULONG ulArraySize;
    ULONG cActualProps;

    /* Validate the Flags */

    if ( ulFlags & ~(MAPI_UNICODE) )
    {
        hResult = ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
        goto ret;
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        hResult = ResultFromScode( MAPI_E_BAD_CHARWIDTH );
        goto ret;
    }

    /*  Validate the object and the return pointers */

    if ((IsBadWritePtr(lpSOB, sizeof(SOB))) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (IsBadWritePtr(lppPropTagArray, sizeof(LPSPropTagArray))))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto ret;
    }

    lpxpl = lpSOB->lpxpl;

    /* If WGAP Directory is empty, don't tell them about this property. */
    cActualProps = sptaStatusRow.cValues;

    if (lstrlen((LPTSTR) ArrayIndex(PR_SAMPLE_DIRECTORY, lpxpl->lpPropArray).Value.LPSZ))
        ulArraySize = CbNewSPropTagArray(sptaStatusRow.cValues);
    else
    {
        /* Adjust for removing a prop tag */
        cActualProps--;
        ulArraySize = CbNewSPropTagArray(sptaStatusRow.cValues - 1);
    }

    /*  Allocate the required amount of memory */

    sc = (*lpSOB->AllocateBuffer) (ulArraySize, (LPVOID *) lppPropTagArray);

    if (sc)
    {
        hResult = ResultFromScode(sc);
        lpSOB->hrLastError = hResult;
        goto ret;
    }

    /*  Copy the contents of our canned property tag array into the buffer */

    if (ulArraySize)
    {
        memcpy(*lppPropTagArray, &sptaStatusRow, (size_t) ulArraySize);
        (*lppPropTagArray)->cValues = cActualProps;
    }

ret:
    DebugTraceResult(SOB_GetPropList, hResult);
    return hResult;
}

/*
 -  SOB_SettingsDialog
 -
 *  Purpose:
 *      This routine will invoke the Transports Logon dialog to allow
 *      the user to change the Logon properties.  The lpPropArray on the
 *      current session will be updated.
 *
 *  Parameters:
 *      lpSOB               The status object for this transport session
 *      ulUIParam           The HWnd of the caller (may be null)
 *      ulFlags             If UI_READONLY will not update the lpPropArray
 *
 *  Returns:
 *      hResult             Indicating success/failure.
 *
 *  Operation:
 *
 */

STDMETHODIMP
SOB_SettingsDialog(LPSOB lpSOB,
    ULONG ulUIParam,
    ULONG ulFlags)
{
    HRESULT hResult = hrSuccess;
    SCODE sc = S_OK;
    LPSPropValue lpMyIDArray = NULL;
    LPVOID lpvT, lpvT2;
    ULONG ulCount = MAX_LOGON_PROPERTIES - TEMP_LOGON_PROPERTIES;
    ULONG ulT;
    LPXPL lpxpl = lpSOB->lpxpl;
    LPSPropValue lpPropArray = NULL;
    LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
    LPPROFSECT lpProfileObj = NULL;
    XPDLG XPDialog;
    BOOL fNeedUI = TRUE;
    BOOL fInCS = FALSE;

    if ((IsBadReadPtr(lpSOB, sizeof(SOB))) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB))
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto ret;
    }

    if (ulFlags & ~UI_READONLY)
    {
        hResult = ResultFromScode(E_INVALIDARG);
        goto ret;
    }

    /* Get the Critical Section */

    EnterCriticalSection(&(lpxpl->lpxppParent->csTransport));

    fInCS = TRUE;

    sc = ScCopySessionProps(lpxpl, &lpPropArray, NULL);

    if (FAILED(sc))
        goto ret;

    /* Fill in the logon UI structure */

    XPDialog.hInst = lpxpl->lpxppParent->hInst;
    XPDialog.hwnd = (HWND) ulUIParam;
    XPDialog.lppPropArray = &lpPropArray;
    XPDialog.lpPTArray = (LPSPropTagArray) &sptLogonArray;
    XPDialog.AllocateBuffer = lpxpl->AllocateBuffer;
    XPDialog.AllocateMore = lpxpl->AllocateMore;
    XPDialog.FreeBuffer = lpxpl->FreeBuffer;
    XPDialog.lpMalloc = lpxpl->lpxppParent->lpMalloc;
    XPDialog.lpMAPISup = lpxpl->lpMAPISup;
    XPDialog.fLogon = FALSE;
    XPDialog.ulFlags = ulFlags;

    while (fNeedUI)
    {
        sc = ScDoLogonDlg(&XPDialog);

        if (FAILED(sc))
        {
            DebugTraceSc(Logon UI activation, sc);
            goto ret;
        }

        if (lpPropArray)
        {
            /* Got a prop array, make sure everything in it is good */

            for (ulT = 0; ulT < ulCount; ulT++)
            {
                if (PROP_TYPE((lpPropArray)[ulT].ulPropTag) == PT_ERROR)
                {
                    DebugTrace("Property %x not available.\n", PROP_ID((lpPropArray)[ulT].ulPropTag));
                    sc = MAPI_E_NO_ACCESS;
                }
            }
        }
        else
            sc = MAPI_E_NO_ACCESS;

        if (sc)
        {
            DebugTraceSc(Logon UI returning, sc);
            goto ret;
        }

        /* Do some simple validation of the Logon Props */

        sc = ScCheckLogonProps(&XPDialog, TRUE);

        if (sc == MAPI_E_USER_CANCEL)
            goto ret;
        else if (sc == MAPI_E_INVALID_PARAMETER)
            continue;
        else
            fNeedUI = FALSE;

        /* If we get here, everything is fine and we can proceed. But first
           we should write the properties out if the user is willing. */

        ulT = ArrayIndex(PR_SAMPLE_FLAGS, lpPropArray).Value.ul;

        if ((ulT & PR_SAMPLE_FLAG_SAVE_DATA))
        {
            /* Try to open our profile. */

            hResult = lpMAPISup->lpVtbl->OpenProfileSection(lpMAPISup,
                (LPMAPIUID) NULL, MAPI_MODIFY, &lpProfileObj);

            if (HR_FAILED(hResult))
            {
              DebugTraceResult(MAPISUP: :OpenProfileSection, hResult);
                goto ret;
            }

            hResult = lpProfileObj->lpVtbl->SetProps(lpProfileObj,
                ulCount, lpPropArray, NULL);

            if (HR_FAILED(hResult))
            {
              DebugTraceResult(PROFSECT: :SetProps, hResult);
                goto ret;
            }
        }
    }

    /* Allocate initial property array for transport ID. */

    sc = (*lpxpl->AllocateBuffer) (sizeof(SPropValue) * NUM_SENDER_PROPS,
        (LPVOID *) &lpMyIDArray);

    if (sc)
    {
        DebugTraceSc(Sender ID property array allocation, sc);
        goto ret;
    }

    memset(lpMyIDArray, 0, sizeof(SPropValue) * NUM_SENDER_PROPS);

    /* Create the One-Off directly into the property value structure. */

    lpMyIDArray[0].ulPropTag = PR_SENDER_ENTRYID;

    hResult = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ,
        ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ,
        ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ,
        fMapiUnicode,
        &lpMyIDArray[0].Value.bin.cb,
        (LPENTRYID *) &lpMyIDArray[0].Value.bin.lpb);

    if (hResult)
    {
      DebugTraceResult(MAPISUP: :CreateOneOff, hResult);
        lpMyIDArray[0].Value.bin.lpb = NULL;
        goto ret;
    }

    /* Create the PR_SENDER_NAME property value. */

    lpvT2 = ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ;
    ulT = lstrlen((LPCTSTR) lpvT2) + 1;

    sc = (*lpxpl->AllocateMore) (ulT, (LPVOID) lpMyIDArray,
        (LPVOID *) &lpvT);

    if (sc)
    {
        DebugTraceSc(User Display Name allocation, sc);
        goto ret;
    }

    lstrcpy((LPTSTR) lpvT, (LPCTSTR) lpvT2);

    lpMyIDArray[1].ulPropTag = PR_SENDER_NAME;
    lpMyIDArray[1].Value.LPSZ = (LPTSTR) lpvT;

    /* Create the PR_SENDER_SEARCH_KEY value. */

    lpMyIDArray[2].ulPropTag = PR_SENDER_SEARCH_KEY;

    /* Size of property = type plus colon plus address plus null. */

    ulT = 2 + lstrlen(ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ) +
        lstrlen(ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ);

    sc = (*lpxpl->AllocateMore) (ulT, (LPVOID) lpMyIDArray,
        (LPVOID *) &lpvT);

    if (sc)
    {
        DebugTraceSc(User Search Key allocation, sc);
        goto ret;
    }

    /* PR_SENDER_SEARCH_KEY is "TYPE:ADDRESS" folded to uppercase. */

    wsprintf((LPTSTR) lpvT, TEXT("%s:%s"),
        (ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ),
        (ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ));

    CharUpperBuff((LPTSTR) lpvT, (UINT)-- ulT);

    lpMyIDArray[2].Value.bin.cb = sizeof(TCHAR) * (1 + lstrlen((LPTSTR) lpvT));
    lpMyIDArray[2].Value.bin.lpb = lpvT;

    /* Replace the original PropArray with the new one. */

    lpxpl->FreeBuffer(lpxpl->lpPropArray);
    lpxpl->lpPropArray = lpPropArray;

    /* Now, free the original User Display Name and Entry-ID */

    if (lpxpl->lpMyIDArray)
    {
        (*lpxpl->FreeBuffer) (lpxpl->lpMyIDArray[0].Value.bin.lpb);
        (*lpxpl->FreeBuffer) (lpxpl->lpMyIDArray);
    }

    lpxpl->lpMyIDArray = lpMyIDArray;

    hResult = HrBuildTransportStatus(lpxpl, STATUSROW_UPDATE);

ret:
    /* Release the Critical Section. */

    if (fInCS)
        LeaveCriticalSection(&(lpxpl->lpxppParent->csTransport));

    UlRelease(lpProfileObj);

    if (lpPropArray && (lpxpl->lpPropArray != lpPropArray))
        lpxpl->FreeBuffer(lpPropArray);

    if (lpMyIDArray && (lpxpl->lpMyIDArray != lpMyIDArray))
    {
        (*lpxpl->FreeBuffer) (lpMyIDArray[0].Value.bin.lpb);
        (*lpxpl->FreeBuffer) (lpMyIDArray);
    }

    if (!hResult && sc)
        hResult = ResultFromScode(sc);

    DebugTraceResult(SOB_SettingsDialog, hResult);
    return hResult;
}

/*
 -  SOB_FlushQueues
 -
 *  Purpose:
 *      Logon object method used by Spooler if FlushQueues is called on
 *      the spooler status object.
 *
 *  Parameters:
 *      lpSOB               This pointer for Status Object
 *      ulUIParam           Window handle
 *      cbTargetTransport   Count of bytes in Entryid. Zero.
 *      lpTargetTransport   Entryid of transport. NULL.
 *      ulFlags
 *
 *  Returns:
 *      (HRESULT)           E_INVALIDARG if object doesn't
 *                          look like a XPL; MAPI_E_NO_SUPPORT
 *                          otherwise.
 *
 *  Operation:
 *      Validate the object pointer.
 */

STDMETHODIMP
SOB_FlushQueues(LPSOB lpSOB,
    ULONG ulUIParam,
    ULONG cbTargetTransport,
    LPENTRYID lpTargetTransport,
    ULONG ulFlags)
{
    HRESULT hResult;

    if ((IsBadReadPtr(lpSOB, sizeof(SOB))) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB))
    {
        return ResultFromScode(E_INVALIDARG);
    }

    if (ulFlags & FLUSH_UPLOAD)
        lpSOB->lpxpl->ulTransportStatus |= STATUS_OUTBOUND_FLUSH;
    if (ulFlags & FLUSH_DOWNLOAD)
        lpSOB->lpxpl->ulTransportStatus |= STATUS_INBOUND_FLUSH;

    hResult = HrUpdateTransportStatus(lpSOB->lpxpl, 0L);
    return hResult;
}

/*
 -  Unimplemented functions. Stubbed to give access or NYI.
 -
 */

STDMETHODIMP
SOB_SaveChanges(LPSOB lpSOB,
    ULONG ulFlags)
{
    /*  Do parameter validation */

    if (IsBadReadPtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (ulFlags & ~(KEEP_OPEN_READWRITE | KEEP_OPEN_READONLY | FORCE_SAVE)))
    {
        DebugTraceSc(SOB_SaveChanges, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_SaveChanges, MAPI_E_NO_ACCESS);
    return (ResultFromScode(MAPI_E_NO_ACCESS));
}

STDMETHODIMP
SOB_OpenProperty(LPSOB lpSOB,
    ULONG ulPropTag,
    LPCIID lpiid,
    ULONG ulInterfaceOptions,
    ULONG ulFlags,
    LPUNKNOWN * lppUnk)
{
    /*  Do parameter validation */

    if (IsBadReadPtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (!lpiid || IsBadReadPtr(lpiid, sizeof(IID))) ||
        (ulFlags & ~(MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS)) ||
        (!lppUnk || IsBadWritePtr(lppUnk, sizeof(LPVOID))))
    {
        DebugTraceSc(SOB_OpenProperty, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulInterfaceOptions & ~(MAPI_UNICODE) )
    {
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    if ( ulInterfaceOptions & MAPI_UNICODE )
    {
        return ResultFromScode( MAPI_E_UNKNOWN_FLAGS );
    }

    DebugTraceSc(SOB_OpenProperty, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_SetProps(LPSOB lpSOB,
    ULONG cValues,
    LPSPropValue lpPropArray,
    LPSPropProblemArray * lppProblems)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (cValues && IsBadReadPtr(lpPropArray, (UINT)cValues*sizeof(SPropValue))) ||
        (IsBadWritePtr(lppProblems, sizeof(LPSPropProblemArray))))
    {
        DebugTraceSc(SOB_SetProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_SetProps, MAPI_E_NO_ACCESS);
    return (ResultFromScode(MAPI_E_NO_ACCESS));
}

STDMETHODIMP
SOB_DeleteProps(LPSOB lpSOB,
    LPSPropTagArray lpPropTagArray,
    LPSPropProblemArray * lppProblems)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (!lpPropTagArray) ||
        (IsBadReadPtr(lpPropTagArray, (UINT)lpPropTagArray->cValues*sizeof(ULONG))) ||
        (IsBadWritePtr(lppProblems, sizeof(LPSPropProblemArray))))
    {
        DebugTraceSc(SOB_DeleteProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_DeleteProps, MAPI_E_NO_ACCESS);
    return (ResultFromScode(MAPI_E_NO_ACCESS));
}

STDMETHODIMP
SOB_CopyTo(LPSOB lpSOB,
    ULONG ciidExclude,
    LPCIID rgiidExclude,
    LPSPropTagArray lpExcludeProps,
    ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress,
    LPCIID lpInterface,
    LPVOID lpDestObj,
    ULONG ulFlags,
    LPSPropProblemArray * lppProblems)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (ciidExclude && (!rgiidExclude || IsBadReadPtr(rgiidExclude, (UINT)ciidExclude*sizeof(IID)))) ||
        (lpExcludeProps && IsBadReadPtr(lpExcludeProps, (UINT)lpExcludeProps->cValues*sizeof(ULONG))) ||
        (lpProgress && IsBadReadPtr(lpProgress, sizeof(LPMAPIPROGRESS))) ||
        (!lpInterface || IsBadReadPtr(lpInterface, sizeof(IID))) ||
        (IsBadReadPtr(lpDestObj, sizeof(LPVOID))) ||
        (ulFlags & ~(MAPI_MOVE | MAPI_NOREPLACE | MAPI_DIALOG | MAPI_DECLINE_OK)) ||
        (IsBadWritePtr(lppProblems, sizeof(LPSPropProblemArray))))
    {
        DebugTraceSc(SOB_CopyTo, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_CopyTo, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_CopyProps(LPSOB lpSOB,
    LPSPropTagArray lpIncludeProps,
    ULONG ulUIParam,
    LPMAPIPROGRESS lpProgress,
    LPCIID lpInterface,
    LPVOID lpDestObj,
    ULONG ulFlags,
    LPSPropProblemArray * lppProblems)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (lpIncludeProps && IsBadReadPtr(lpIncludeProps, (UINT)lpIncludeProps->cValues*sizeof(ULONG))) ||
        (lpProgress && IsBadReadPtr(lpProgress, sizeof(LPMAPIPROGRESS))) ||
        (!lpInterface || IsBadReadPtr(lpInterface, sizeof(IID))) ||
        (IsBadReadPtr(lpDestObj, sizeof(LPVOID))) ||
        (ulFlags & ~(MAPI_MOVE | MAPI_NOREPLACE | MAPI_DIALOG | MAPI_DECLINE_OK)) ||
        (IsBadWritePtr(lppProblems, sizeof(LPSPropProblemArray))))
    {
        DebugTraceSc(SOB_CopyProps, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_CopyProps, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_GetNamesFromIDs(LPSOB lpSOB,
    LPSPropTagArray * lppPropTags,
    LPGUID lpPropSet,
    ULONG ulFlags,
    ULONG * lpcPropNames,
    LPMAPINAMEID * * lpppPropNames)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (IsBadReadPtr(lppPropTags, sizeof(LPSPropTagArray))) ||
        (IsBadReadPtr(lpPropSet, sizeof(GUID))) ||
        (IsBadWritePtr(lpcPropNames, sizeof(ULONG))) ||
        (IsBadWritePtr(lpppPropNames, sizeof(LPVOID))))
    {
        DebugTraceSc(SOB_GetNamesFromIDs, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_GetNamesFromIDs, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_GetIDsFromNames(LPSOB lpSOB,
    ULONG cPropNames,
    LPMAPINAMEID * lppPropNames,
    ULONG ulFlags,
    LPSPropTagArray * lppPropTags)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (!lppPropNames || IsBadReadPtr(lppPropNames, (UINT)cPropNames*sizeof(LPMAPINAMEID))) ||
        (IsBadReadPtr(lppPropTags, sizeof(LPSPropTagArray))))
    {
        DebugTraceSc(SOB_GetIDsFromNames, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_GetIDsFromNames, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_ChangePassword(LPSOB lpSOB,
    LPTSTR lpOldPass,
    LPTSTR lpNewPass,
    ULONG ulFlags)
{
    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (IsBadStringPtr(lpOldPass, MAX_STRING)) ||
        (IsBadStringPtr(lpNewPass, MAX_STRING)) ||
        (ulFlags & ~MAPI_UNICODE))
    {
        DebugTraceSc(SOB_ChangePassword, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    if ( ulFlags & MAPI_UNICODE )
    {
        DebugTraceSc(SOB_ChangePassword, MAPI_E_BAD_CHARWIDTH);
        return ResultFromScode( MAPI_E_BAD_CHARWIDTH );
    }

    DebugTraceSc(SOB_ChangePassword, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

STDMETHODIMP
SOB_ValidateState(LPSOB lpSOB,
    ULONG ulUIParam,
    ULONG ulFlags)
{
    ULONG ulFlagMask =  REFRESH_XP_HEADER_CACHE | \
                        PROCESS_XP_HEADER_CACHE | \
                        FORCE_XP_CONNECT | \
                        FORCE_XP_DISCONNECT | \
                        SUPPRESS_UI;

    /*  Do parameter validation */

    if (IsBadWritePtr(lpSOB, sizeof(SOB)) ||
        (lpSOB->lcInit == 0) ||
        (lpSOB->lpsobMyAddress != lpSOB) ||
        (ulFlags & (~(ulFlagMask))))
    {
        DebugTraceSc(SOB_ValidateState, E_INVALIDARG);
        return ResultFromScode(E_INVALIDARG);
    }

    DebugTraceSc(SOB_ValidateState, MAPI_E_NO_SUPPORT);
    return (ResultFromScode(MAPI_E_NO_SUPPORT));
}

/* The following array maps a string identifier (IDS) to status code  */
/* (SCODE).  The order of SCODEs in the array has an external         */
/* dependency:  the order of elements in the array is dictated by     */

/* the IDS definitions in xpresrc.h.  This implicit association must  */
/* be maintained for the strings associated with string identifiers   */
/* to make sense.  Thus, if either this structure or the rc.h defines */
/* changes, the other must change to match it.                        */

SCODE mpIdsScode[] =
{
    SUCCESS_SUCCESS,
    MAPI_E_BUSY,
    MAPI_E_CALL_FAILED,
    MAPI_E_INVALID_PARAMETER,
    MAPI_E_NO_ACCESS,
    MAPI_E_NO_SUPPORT,
    MAPI_E_NOT_FOUND,
    MAPI_E_UNKNOWN_FLAGS,
    MAPI_E_VERSION,
    MAPI_E_NOT_ENOUGH_MEMORY,
    MAPI_W_ERRORS_RETURNED
};

/*
 *  MapScodeSz
 *
 *  Purpose:
 *      Look up an SCODE in a mapping of IDS <-> SCODE to find its
 *      associated informational string and return it (with memory
 *      allocated by this function) to the caller.
 *
 *  Arguments:
 *      sc          The SCODE to look up.
 *      lpxpl       Pointer to the Transport Logon object
 *      lppszError  Location in which to place an address to a
 *                  newly allocated buffer containing the
 *                  informational string associated with scArg.
 *
 *  Returns:
 *      HRESULT
 *
 *  Errors:
 *      MAPI_E_NO_MEMORY    Could not allocate space for
 *                          the return string.
 */

HRESULT
MapScodeSz(SCODE scArg, LPXPL lpxpl, LPTSTR * lppszError)
{
    HRESULT hr = hrSuccess;
    SCODE sc = SUCCESS_SUCCESS;
    ULONG cb;
    UINT ui;
    UINT ids;
    UINT uiMax = sizeof mpIdsScode / sizeof mpIdsScode[0];
    TCHAR rgch[512];

    if (!lppszError || IsBadWritePtr(lppszError, sizeof(LPVOID)))
    {
        DebugTrace("Bad lppszError in MapScodeSz\n");
        return ResultFromScode(E_INVALIDARG);
    }

    *lppszError = NULL;

    /* Linear search in mpIdsScode for sc. */

    for (ui = 0; ui < uiMax; ui++)
        if (mpIdsScode[ui] == scArg)
            break;

    if (ui == uiMax)
        ids = IDS_UNKNOWN_ERROR;
    else
        ids = ui + LIB_ERRORS;

    if (!LoadString(lpxpl->lpxppParent->hInst, ids, rgch, sizeof(rgch)))
    {
        hr = ResultFromScode(MAPI_E_NOT_ENOUGH_MEMORY);
        DebugTraceResult(LoadString failed in MapScodeSz, hr);
        goto ret;
    }

    /* Allocate memory for return variable and set it */

    cb = (lstrlen(rgch) + 1) * sizeof(TCHAR);
    sc = lpxpl->AllocateBuffer(cb, (LPVOID *) lppszError);

    if (sc != SUCCESS_SUCCESS)
    {
        hr = ResultFromScode(sc);
        DebugTraceResult(AllocateBuffer failed in MapScodeSz, hr);
        goto ret;
    }

    lstrcpy(*lppszError, rgch);

ret:
    DebugTraceResult(MapScodeSz, hr);
    return hr;
}


XPSTATUS.H

/*
 -  X P S T A T U S . H
 -
 *  Purpose:
 *      Definitions, typedefs and prototypes used by the Sample Transport
 *      Provider status interface.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Local (not exported) functions */

/*  Called at TransportLogon() time to register the Status
    Table row for this transport session. */

HRESULT HrBuildTransportStatus (LPXPL lpSession, ULONG ulFlags);

/*  Called at various times (send, receive, notify) to cause
    the PR_STATUS_CODE property in the Status Table to be
    updated with a new value. */

HRESULT HrUpdateTransportStatus (LPXPL lpSession, ULONG ulFlags);

/* Status Object method prototypes */

#undef  INTERFACE
#define INTERFACE   struct _SOB

#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_DECLARE(type, method, SOB_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPISTATUS_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   MAPIMETHOD_TYPEDEF(type, method, SOB_)
        MAPI_IUNKNOWN_METHODS(IMPL)
        MAPI_IMAPIPROP_METHODS(IMPL)
        MAPI_IMAPISTATUS_METHODS(IMPL)
#undef  MAPIMETHOD_
#define MAPIMETHOD_(type, method)   STDMETHOD_(type, method)

DECLARE_MAPI_INTERFACE(SOB_)
{
    MAPI_IUNKNOWN_METHODS(IMPL)
    MAPI_IMAPIPROP_METHODS(IMPL)
    MAPI_IMAPISTATUS_METHODS(IMPL)
};

/* Actual status object memory structure */

typedef struct _SOB
{
    SOB_Vtbl FAR *      lpVtbl;         /* Call Table                           */
    LONG                lcInit;         /* Usage Count                          */
    struct _SOB *       lpsobMyAddress; /* Pointer to myself, used to validate  */
    HRESULT             hrLastError;    /* Saved last error                     */
    ULONG               ulOpenFlags;    /* Flags used to open the object        */
    LPXPL               lpxpl;          /* XP Session associated with object    */
    LPALLOCATEBUFFER    AllocateBuffer; /* MAPIAllocateBuffer func pointer      */
    LPALLOCATEMORE      AllocateMore;   /* MAPIAllocateMore func pointer        */
    LPFREEBUFFER        FreeBuffer;     /* MAPIFreeBuffer func pointer          */
    LPMAPISUP           lpMAPISup;      /* Support object. So we can release it */
} SOB, *LPSOB;

STDMETHODIMP SOB_Reserved (LPSOB lpSOB);    /* All MAPI_E_NO_SUPPORT */
STDMETHODIMP SOB_AccessDenied(LPSOB);       /* All MAPI_NO_ACCESS */


XPTXTMSG.H

/*
 -  X P T X T M S G . H
 -
 *  Purpose:
 *      Support for the textized TNEF encapsulated message transport.
 *
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
 */

/* Stuff to support the textized message formating */

enum
{
    tagMessage,
    tagFrom,
    tagRepresenting,
    tagReplyTo,
    tagDate,
    tagTo,
    tagCc,
    tagBcc,
    tagSubject,
    tagPrioUrgent,
    tagPrioNormal,
    tagPrioLow,
    tagContents,
    tagTextItem,
    tagFileItem,
    NUM_TAGS
};

#define MAX_TAG_LEN         32
#define MAX_TXTMSG_PROPS    17
#define MAX_STRM_BUF        256
#define MAX_LINE            256

/* Macro to DebugTrace failures from a Read/Write calls */

#define TraceFailedRead(_a, _b) \
    hr = (_a); \
    if(HR_FAILED(hr)) \
    { \
        DebugTraceResult(Read(), hr); \
        goto _b; \
    }

#define TraceFailedWrite(_a, _b) \
    hr = (_a); \
    if(HR_FAILED(hr)) \
    { \
        DebugTraceResult(Write(), hr); \
        goto _b; \
    }

/* All extern variables are declared in xpqueue.c  */

extern TCHAR rgszTags[NUM_TAGS][MAX_TAG_LEN];
extern TCHAR szCRLF[3];
extern TCHAR szCRLFCRLF[5];

/* Function Prototypes */

HRESULT HrGetLine(LPSTREAM lpSof, ULONG cbDest, LPTSTR lpsz, ULONG *pcbRead);
HRESULT HrBuildSenderProps(LPXPL lpxpl, LPSPropValue lpPropArray, ULONG ulTag,
        LPTSTR lpszToken, LPTSTR lpszAddrType, LPMESSAGE lpMessage,
        ULONG * lpcValues, LPSPropValue lpMsgProps);
HRESULT HrGetStreamedProp(LPXPL lpxpl, LPSTREAM lpSof, LPMESSAGE lpMsg,
        ULONG ulPropTag, ULONG * lpcValues, LPSPropValue lpMsgProps,
        LPTSTR lpszLine, ULONG * lpulTag, LPTSTR * lppszToken);
HRESULT HrAddRecipToAdrList(LPXPL lpxpl, LONG lRecipType,
        LPTSTR lpszNameAddr, LPTSTR lpszAddrType, LPMYADRLIST lpMyAdrList);
HRESULT HrAddRecipToReplyList(LPXPL lpxpl, LPTSTR lpszToken, LPTSTR lpszAddrType,
        LPTSTR *lppszNames, ULONG *lpcbEIDList, LPFLATENTRYLIST *lppEIDList);
HRESULT HrCrackSenderEID(LPXPL lpxpl, ULONG cb, LPBYTE lpb, LPTSTR lpsz);
HRESULT HrMakeSearchKey(LPXPL lpxpl, LPVOID lpParent, LPTSTR lpszAddrType,
        LPTSTR lpszAddress, ULONG *lpcbSK, LPBYTE *lppSK);
BOOL    FGetTagAndToken(LPTSTR lpsz, ULONG *pulTag, LPTSTR *lppszToken);
void    FileTimeFromSzTime(LPTSTR lpszTime, FILETIME *pft);

HRESULT HrPrepareRecipientTable(LPSPropValue, LPMESSAGE, LPMAPITABLE *);
BOOL    FPropIndex(LPSPropValue, ULONG, ULONG, ULONG *);
void    FormatFileTime(FILETIME *, LPTSTR);

HRESULT HrIMsgToTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof);
HRESULT HrIMsgFromTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof);


CREATEPR.C

/*
 *  CREATEPR.C
 *
 *      Very simple console app creating a profile with hardcoded values.
 *
 *   Copyright (c) 1995, Microsoft Corporation. All Rights Reserved.
 *
 */

#include <mapiutil.h>
#include <mapiwin.h>
#include <mapidbg.h>
#include <stdio.h>
#include <smpms.h>
#include <smpxp.h>
#include <smpab.h>

#ifdef DEBUG
#define TraceFnResult(f, hr)    \
{ (hr) ?   \
printf(#f " returns 0x%08lX %s\n", GetScode(hr), SzDecodeScode(GetScode(hr))) : 0;\
}
#else
#define TraceFnResult(f, hr)
#endif  /*/DEBUG*/

HRESULT HrCreateProfile(void);

int main (void)
{
    HRESULT hr = 0;

    hr = HrCreateProfile();

    return (int) hr;
}

HRESULT HrCreateProfile(void)
{
    LPPROFADMIN         ppa = NULL;
    LPSERVICEADMIN      psa = NULL;
    LPMAPISESSION       pses = NULL;
    LPMAPITABLE         ptblSvc = NULL;
    HRESULT             hr;

    LPSRowSet           prows = NULL;
    LPSRow              prow = NULL;

    enum {iSvcName, iSvcUID, cptaSvc};
    SizedSPropTagArray (cptaSvc, ptaSvc) = { cptaSvc,
                                            {   PR_SERVICE_NAME,
                                                PR_SERVICE_UID } };
    LPSTR               szProfile = "User XXX";
    #define cProviders  3
    #define nMAXProps   6
    SPropValue          rgval[nMAXProps];

    hr = MAPIInitialize(NULL);
    if (HR_FAILED(hr))
    {
        TraceFnResult(MAPIInitialize, hr);
        return hr;
    }

    hr = MAPIAdminProfiles(0, &ppa);
    if (HR_FAILED(hr))
    {
        TraceFnResult(MAPIAdminProfiles, hr);
        goto ret;
    }

    (void) ppa->lpVtbl->DeleteProfile(ppa, szProfile, 0);

    printf("Creating profile \"%s\"\n", szProfile);
    hr = ppa->lpVtbl->CreateProfile(ppa, szProfile, NULL, 0, 0);
    if (HR_FAILED(hr))
    {
        TraceFnResult(CreateProfile, hr);
        goto ret;
    }

    hr = MAPILogonEx(0, szProfile, NULL, MAPI_NO_MAIL | MAPI_NEW_SESSION,
                               &pses);
    if (HR_FAILED(hr))
    {
        TraceFnResult(MAPILogonEx, hr);
        goto ret;
    }

    hr = pses->lpVtbl->AdminServices(pses, 0, &psa);
    if (HR_FAILED(hr))
        goto ret;

    printf("Creating Sample Message Store\n");
    hr = psa->lpVtbl->CreateMsgService(psa, "SMPMS", "MAPI Sample Msg Store", 0, 0);
    if (HR_FAILED(hr))
    {
        TraceFnResult(CreateMsgService, hr);
        goto ret;
    }

    printf("Creating Sample Transport\n");
    hr = psa->lpVtbl->CreateMsgService(psa, "SMPXP",
                                        "Sample Peer To Peer Transport", 0, 0);
    if (HR_FAILED(hr))
    {
        TraceFnResult(CreateMsgService, hr);
        goto ret;
    }

    printf("Creating Sample Address Book\n");
    hr = psa->lpVtbl->CreateMsgService(psa, "SMPAB",
                                        "Sample Addres Book", 0, 0);
    if (HR_FAILED(hr))
    {
        TraceFnResult(CreateMsgService, hr);
        goto ret;
    }

    hr = psa->lpVtbl->GetMsgServiceTable(psa, 0, &ptblSvc);
    if (HR_FAILED(hr))
    {
        TraceFnResult(GetMsgServiceTable, hr);
        goto ret;
    }

    hr = HrQueryAllRows(ptblSvc, (LPSPropTagArray)&ptaSvc, NULL, NULL, 0, &prows);
    if (HR_FAILED(hr))
    {
        TraceFnResult(HrQueryAllRows, hr);
        goto ret;
    }

    Assert(prows->cRows == cProviders);

    for(prow = prows->aRow; prow < prows->aRow + cProviders; ++prow)
    {
        Assert(prow->cValues == cptaSvc);
        Assert(prow->lpProps[iSvcName].ulPropTag == PR_SERVICE_NAME);
        Assert(prow->lpProps[iSvcUID].ulPropTag == PR_SERVICE_UID);
        Assert(prow->lpProps[iSvcUID].Value.bin.cb == sizeof(MAPIUID));

        if(!lstrcmp(prow->lpProps[iSvcName].Value.lpszA, "SMPMS"))
        {
            printf("Configuring Sample Message Store...");

            rgval[0].ulPropTag      = PR_SMS_PATH;
            rgval[0].Value.lpszA    = "d:\\sms";

            rgval[1].ulPropTag      = PR_SMS_PASSWORD;
            rgval[1].Value.lpszA    = "PASSWORD";

            rgval[2].ulPropTag      = PR_SMS_REMEMBER_PW;
            rgval[2].Value.b        = TRUE;

            rgval[3].ulPropTag      = PR_SMS_CREATE;
            rgval[3].Value.b        = TRUE;

            hr = psa->lpVtbl->ConfigureMsgService(psa,
                                (LPMAPIUID) prow->lpProps[iSvcUID].Value.bin.lpb,
                                            0, 0, 4, rgval);
            if (HR_FAILED(hr))
            {
                printf("failed\n");
                TraceFnResult(ConfigureMsgService, hr);
                //goto ret;
            }
            else
            {
                printf("OK\n");
            }
        }
        else if(!lstrcmp(prow->lpProps[iSvcName].Value.lpszA, "SMPXP"))
        {
            printf("Configuring Sample Transport...");

            rgval[0].ulPropTag  = PR_SAMPLE_DISPLAY_NAME;
            rgval[0].Value.LPSZ = "USER XXX";

            rgval[1].ulPropTag  = PR_SAMPLE_EMAIL_ADDR_TYPE;
            rgval[1].Value.LPSZ = "MSPEER";

            rgval[2].ulPropTag  = PR_SAMPLE_EMAIL_ADDRESS;
            rgval[2].Value.LPSZ = "\\\\aleksank\\d\\test\\inbound";

            rgval[3].ulPropTag  = PR_SAMPLE_INBOUND_DIR;
            rgval[3].Value.LPSZ = "d:\\test\\inbound";

            rgval[4].ulPropTag  = PR_SAMPLE_OUTBOUND_DIR;
            rgval[4].Value.LPSZ = "d:\\test\\outbound";

            rgval[5].ulPropTag  = PR_SAMPLE_FLAGS;
            rgval[5].Value.l    = 9;

            hr = psa->lpVtbl->ConfigureMsgService(psa,
                                (LPMAPIUID) prow->lpProps[iSvcUID].Value.bin.lpb,
                                            0, 0, 6, rgval);
            if (HR_FAILED(hr))
            {
                printf("failed\n");
                TraceFnResult(ConfigureMsgService, hr);
                //goto ret;
            }
            else
            {
                printf("OK\n");
            }
        }
        else if(!lstrcmp(prow->lpProps[iSvcName].Value.lpszA, "SMPAB"))
        {
            printf("Configuring Sample Address Book...");

            rgval[0].ulPropTag  = PR_SAB_FILE;
            rgval[0].Value.LPSZ = "d:\\sampab.sab";

            hr = psa->lpVtbl->ConfigureMsgService(psa,
                                (LPMAPIUID) prow->lpProps[iSvcUID].Value.bin.lpb,
                                            0, 0, 1, rgval);
            if (HR_FAILED(hr))
            {
                printf("failed\n");
                TraceFnResult(ConfigureMsgService, hr);
                //goto ret;
            }
            else
            {
                printf("OK\n");
            }
        }
        else
        {
            Assert(FALSE);
        }
    }

ret:

    UlRelease(ptblSvc);
    UlRelease(psa);
    UlRelease(pses);
    UlRelease(ppa);
    FreeProws(prows);
    MAPIUninitialize();
    return hr;
}


README.TXT

Sample Property Sheet Extension

SUMMARY
=======

The PropSh.Ext sample demonstrates how to implement custom property sheet
extensions for the Microsoft Exchange Client. In particular, it demonstrates
adding a custom property page that displays summary sheet information for
Microsoft Office documents in a message store.  It adds the custom page when
viewing properties with a message of class IPM.Document.

MORE INFORMATION
================

This sample requires Microsoft Windows NT 3.51 or Windows 95, the MAPI 1.0
(Extended MAPI) PDK (Build 410 or above), Microsoft Visual C++ version 2.0
(or later), and the Win32 SDK for Windows NT and Windows 95.

To configure Exchange to use the client extension, place the following
REG_SZ entry in the system registry in:
HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Client\Extensions:

    Sample Property Extension=4.0;c:\<path>\EPRSHT32.DLL;1;00000000000100

You can leave out an explicit path to EPRSHT32.DLL if it resides in a
directory listed in the system's PATH.

Exchange client extensions are designed using OLE's Component Object Model.
The client calls methods which are provided in the extension. In some calls
to the extension interface, a pointer to a callback interface
(IExchExtCallback) for the extension to call back into the Exchange client.
For more information read "Extending the Microsoft Exchange Client" in the
MAPI PDK documentation.

This sample implements two interface objects, IExchExt and
IExchExtPropertySheets. The extension responds to IExchExtPropertySheets
methods called by the Exchange client to append a property page if the selected
message object is a IPM.Document class message.

MAPI stores Microsoft Office documents as a set of properties with the
summary sheet information is retained as named properties. The sample
extracts the property IDs from the names the first time it displays the
properties for an Office document with the GetIDsFromNames.  After that, it
stores and reuses those ids until the Exchange client is closed.

The summary info custom property page displays information in read-only edit
controls. The information is read from pointers to elements of a SPropValue
array filled by the GetProps function. If a particular property is not found,
the string "No <property>" is diplayed in the corresponding field. The
field will be blank if the property is found but is an empty string. Each time
the summary info page is activated without closing the property sheet dialog,
the controls are re-initialized with the same data. The SPropValue array is
freed when the property sheet dialog is closed.

The Help button is available for the summary info page and will display About
information in the sample extension in a message box.

To demonstrate the functionality of the extension, drag a Microsoft Office
document that has summary sheet information into a folder in the Microsoft
Exchange client. Then select the document and press Alt+ENTER or choose
Properties... from the File menu in Exchange.


EXTPRSHT.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      EXTPRSHT.CPP
//
//  Description
//      Implementation of MyExchExt and MyExchExtPropertySheets along
//      with helper functions towards the end.
//
//      IExchExt interface methods:
//          MyExchExt::QueryInterface()
//          MyExchExt::AddRef()
//          MyExchExt::Release()
//
//          MyExchExt::Install()
//
//      IExchExtPropertySheets interface methods:
//          IExchExtPropertySheets::QueryInterface()
//          IExchExtPropertySheets::AddRef()
//          IExchExtPropertySheets::Release()
//
//          IExchExtPropertySheets::GetMaxPageCount()
//          IExchExtPropertySheets::GetPages()
//          IExchExtPropertySheets::FreePages()
//
//  Author
//      Gary Peluso
//
//  Revision: 1.01
//
//  History
//  9/21/95 - changed QI for MyExchExtPropertySheets to return pointer to MyExchExt
//            when IID_IUNKNOWN is requested
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMAPIFolder
#define USES_IID_IMAPIFormInfo
#define USES_PS_PUBLIC_STRINGS

#include "EXTPRSHT.H"

#include <INITGUID.H>
#include <MAPIGUID.H>

///////////////////////////////////////////////////////////////////////////////
//  tag enumeration definitions and arrays
//
//
 enum { MESSAGE_CLASS, OBJECT_TYPE, OBJPROPTAGS };

static const SizedSPropTagArray(OBJPROPTAGS,  ObjPropTags) =
      { OBJPROPTAGS,
        {
         PR_MESSAGE_CLASS,
         PR_OBJECT_TYPE,
        }
      };

 enum { LASTEDIT, MORETAGS };

static const SizedSPropTagArray(MORETAGS,  MoreObjTags) =
      { MORETAGS,
        {
         PR_LAST_MODIFICATION_TIME,
        }
      };

///////////////////////////////////////////////////////////////////////////////
//    Office Document Named Properties
//

enum { KEYWORDS = 0, COMMENTS, AUTHOR, COMPANY,
       APPNAME, CATEGORY, TITLE, DOCSUBJECT,
       NAMEIDS // number of properties
};

LPWSTR awszDocProperties[] = { L"Keywords", L"Comments", L"Author", L"Company",
                               L"AppName",  L"Category", L"Title", L"Subject"};

// array of property tags for named properties
SizedSPropTagArray(NAMEIDS, tagaDoc);

// arrays for summary sheet properties
LPSPropValue pSummaryProps = NULL;
LPSPropValue pMoreDocProps = NULL;

// instance handle of DLL
static HINSTANCE ghInstDLL = NULL;

// flag to prevent reentrancy
BOOL bBusy = FALSE;

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: DLLMain()
//
//    Purpose
//    Do initilization processesing
//
//    Return Value
//    TRUE - DLL successfully loads and LoadLibrary will succeed.
//    FALSE - will cause an Exchange error message saying it cannot locate
//            the extension DLL.
//
//    Comments
//    We only need to get a copy of the DLL's HINSTANCE.
//
BOOL WINAPI DllMain(
    HINSTANCE  hinstDLL,
    DWORD  fdwReason,
    LPVOID  lpvReserved)
{
 if (DLL_PROCESS_ATTACH == fdwReason)
 {
    ghInstDLL = hinstDLL;

 }
 return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION: ExchEntryPoint
//
//    Parameters - none
//
//    Purpose
//    The entry point called by Exchange.
//
//    Return Value
//    Pointer to Exchange Extension (IExchExt) interface
//
//    Comments
//    Exchange Client calls this for each context entry.
//

LPEXCHEXT CALLBACK ExchEntryPoint(void)
{

     return new MyExchExt;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::MyExchExt()
//
//    Parameters - none
//
//    Purpose
//    Comstructor.  Called during instantiation of MyExchExt object.
//
//

MyExchExt::MyExchExt()
{
  m_cRef = 1;
  m_context = 0;

  m_pExchExtPropertySheets = new MyExchExtPropertySheets(this);

};

///////////////////////////////////////////////////////////////////////////////
//  IExchExt virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::Release()
//
//    Parameters - none
//
//    Purpose
//    Frees memory when interface is not referenced any more
//
//    Return value
//    reference count of interface
//

STDMETHODIMP_(ULONG) MyExchExt::Release()
{
 ULONG ulCount = --m_cRef;

 if (!ulCount)
 {
  tagaDoc.cValues = 0;
  delete this;
 }

return ulCount;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Returns a pointer to an interface object that is requested by ID.
//
//    Comments
//    The interfaces are requested everytime a new context is entered.  The
//    IID_IExchExt* interfaces are ignored if not specified in the Exchange
//    extensions registry.
//
//    If an interface pointer is returned for more than one context, that
//    interface is used by the client for each of those contexts.  Check the
//    current context to verify if it is appropriate to pass back an interface
//    pointer.
//
//    Return Value - none
//

STDMETHODIMP MyExchExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    HRESULT hResult = S_OK;

    *ppvObj = NULL;

    if (( IID_IUnknown == riid) || ( IID_IExchExt == riid) )
    {
        *ppvObj = (LPUNKNOWN)this;
    }
    else if (IID_IExchExtPropertySheets == riid)
    {
        *ppvObj = (LPUNKNOWN) m_pExchExtPropertySheets;
    }
    else
        hResult = E_NOINTERFACE;

    if (NULL != *ppvObj)
        ((LPUNKNOWN)*ppvObj)->AddRef();

    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExt::Install()
//
//    Parameters
//    peecb     -- pointer to Exchange Extension callback function
//    eecontext -- context code at time of being called.
//
//    Purpose
//    Called once for each new context that is entered.
//
//    Return Value
//    S_OK - the installation succeeded for the context
//    S_FALSE - deny the installation fo the extension for the context
//
STDMETHODIMP MyExchExt::Install(LPEXCHEXTCALLBACK peecb, ULONG eecontext, ULONG ulFlags)
{
    ULONG ulBuildVersion;
    HRESULT hr;

    m_context = eecontext;

    // make sure this is the right version
    peecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
    if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion &
                                      EECBGV_BUILDVERSION_MAJOR_MASK))
        return S_FALSE;

    switch (eecontext)
    {
     case EECONTEXT_PROPERTYSHEETS:
        hr = S_OK;
        break;

     default:
        hr = S_FALSE;
        break;
    }

    return hr;

}

///////////////////////////////////////////////////////////////////////////////
//  IExchExtPropertySheets virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::QueryInterface()
//
//    Parameters
//    riid   -- Interface ID.
//    ppvObj -- address of interface object pointer.
//
//    Purpose
//    Return interface object upon request
//
//    Return Value - none
//
//    Comments
//    Currently the Exchange client does not call QueryInterface from any object
//    except for IExchExt.  This is implemented in case features are added to
//    Exchange to require QueryInterface from any object.  Also, as a "rule of
//    OLE COM" this is the proper implementation of QueryInterface.
//

STDMETHODIMP MyExchExtPropertySheets::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{

    *ppvObj = NULL;
    if (riid == IID_IExchExtPropertySheets)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    if (riid == IID_IUnknown)
    {
     // return MyExchExt as a rule of OLE COM since MyExchExtPropertySheets
     // was obtained vi MyExchExt::QueryInterface
        *ppvObj = (LPVOID)m_pExchExt;
        m_pExchExt->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;

}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::GetMaxPageCount()
//
//    Parameters
//    ulFlags -- a bitmask indicating what type of property sheet is being
//               displayed
//
//    Purpose
//    Returns the number of property pages which are to be added.
//
//    Return Value - maximum number of custom pages for the property sheet
//
//    Exchange Calls this to know how many PROPSHEETPAGE buffers it needs
//    to allocate.
//

ULONG MyExchExtPropertySheets::GetMaxPageCount(ULONG ulFlags)
{
 ULONG ulNumExtSheets;

    switch (ulFlags)
    {
     // ignore these objects.
     case EEPS_FOLDER:
     case EEPS_STORE:
     case EEPS_TOOLSOPTIONS:
        ulNumExtSheets = 0;
        break;

     case EEPS_MESSAGE:
        ulNumExtSheets = 1;
        break;

     default:
        ulNumExtSheets = 0;
        break;
    }

    return ulNumExtSheets;   // adding one propery page
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::GetPages()
//
//    Parameters
//    peecb   -- pointer to Exchange callback interface
//    ulFlags -- a bitmask indicating what type of property sheet is being
//               displayed
//    ppsp    -- output parm pointing to pointer to list of property sheets
//    pcpsp   -- output parm pointing to buffer contaiing number of property
//               sheets actually used.
//
//    Purpose
//    Fills the PROPSHEETPAGE members for the custom property page.
//
//    Return Value
//    S_FALSE - do not add a new page
//    S_OK - use the ppsp information for new pages.
//
//    Comments
//    Exchange calls this method to gather information for any custom
//    property pages to be added to the sheet.  Here we are only adding
//    one.  ppsp may be an array of PROPSHEETPAGE structures to allow you
//    to all multiple property pages.
//

STDMETHODIMP MyExchExtPropertySheets::GetPages(LPEXCHEXTCALLBACK peecb,
                      ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp)
{
    HRESULT     hr;
    LPMDB       pMDB = NULL;
    LPMESSAGE   pItem = NULL;

    *pcpsp = 0;

    // Check for reentrancy
    if (bBusy)
    {
        hr = MAPI_E_BUSY;
        ErrorMessageBox(NULL, hr, "GetPages", "The summary sheet is already open");
        return hr;
    }

    bBusy = TRUE;

    // Get our message from the caller
    hr = peecb->GetObject(&pMDB, (LPMAPIPROP *)&pItem);
    if (pItem == NULL)
    {
        ErrorMessageBox(NULL, hr, "GetPages", "GetObject failed");
        goto error_return;
    }

    // If the message does not belong to a document class, we don't
    // do anything
    if (!ValidDocObject(pItem))
    {
        // nothing to do, must clear bBusy
        bBusy = FALSE;
        hr = S_OK;
        goto error_return;
    }

    // Retrieve document summary properties from the message
    if (!GetSummarySheetInfo(pItem))
    {
        ErrorMessageBox(NULL, 0, "GetPages", "Could not get summary sheet information");
        hr = E_FAIL;
        goto error_return;
    }

    // fill out members for the property page
    ppsp[0].dwSize = sizeof (PROPSHEETPAGE);
    ppsp[0].dwFlags = PSP_DEFAULT;
    ppsp[0].hInstance = ghInstDLL;
    ppsp[0].pszTemplate = MAKEINTRESOURCE(IDD_SUMMARY);
    ppsp[0].hIcon = NULL;     // not used in this sample
    ppsp[0].pszTitle = NULL;  // not used in this sample
    ppsp[0].pfnDlgProc = (DLGPROC)SummaryPageDlgProc;
    ppsp[0].lParam = 0;
    ppsp[0].pfnCallback = NULL;
    ppsp[0].pcRefParent = NULL; // not used in this sample

    *pcpsp = 1;
    // DO NOT clear bBusy in this case. It will be cleared evenmtually
    // when the property sheet is dismissed.
    hr = S_OK;

error_return:

    UlRelease(pItem);
    UlRelease(pMDB);

    if (hr)
        bBusy = FALSE;

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//    MyExchExtPropertySheets::FreePages()
//
//    Parameters
//    ppsp -- pointer to a pointer to the first of a list of property pages
//    cpsp -- number of custom property pages in the list
//    ulFlags -- type of property page
//
//    Purpose
//    Free any memory associated to the property sheet.
//
//    Return Value - none
//
//    Comments
//    No parameters are used in this example but the function is used as
//    a signal that the property sheet is going away and so memory may
//    be freed.
//

VOID MyExchExtPropertySheets::FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags,
                                        ULONG cpsp)
{
    MAPIFreeBuffer(pSummaryProps);
    pSummaryProps = NULL;

    MAPIFreeBuffer(pMoreDocProps);
    pMoreDocProps = NULL;

    // Clear the reentrancy flag.
    // This must be done here rather than in the dialog proc, because
    // our property page may be dismissed without ever having been
    // displayed.
    bBusy = FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//  Helper Functions
//

///////////////////////////////////////////////////////////////////////////////
//    ErrorMessageBox()
//
//    Parameters
//    hWnd       -- parent window
//    hr         -- HRESULT value
//    szFunction -- function name in which the error occurred
//    szMessage  -- error message
//
//    Purpose
//    Displays an error message using MessageBox
//
//    Return Value - none
//
//    Comments
//    Pass 0 for hr to not display an error number.  Pass NULL for
//    szFunction to not display a function name.  Use these options
//    to display error messages for public consumption.  Use hr and
//    function name for internal errors and for debugging.  szMessage
//    is mandatory.
//
void ErrorMessageBox(HWND hWnd, HRESULT hr, LPSTR szFunction, LPSTR szMessage)
{
 static char szError[256];

 if (szMessage == NULL)
 {
    MessageBox(hWnd,
         "An unknown error occured in\nSample property sheet extension",
         "Sample Property Sheet Extension", MB_ICONEXCLAMATION | MB_OK);
    return;
 }

 if ((hr == 0) && (szFunction == NULL))
 {
    MessageBox(hWnd, szMessage, "Sample Extension Error", MB_ICONEXCLAMATION | MB_OK);
    return;
 }

 if (szFunction != NULL)
 {
  wsprintf(szError, "Error %08X in %s\n%s", hr, szFunction, szMessage);
  MessageBox(hWnd, szError, "Sample Extension Error", MB_ICONEXCLAMATION | MB_OK);
 }

}

///////////////////////////////////////////////////////////////////////////////
//    GetSummarySheetInfo()
//
//    Parameters
//    pMessage -- pointer to an Office Document object, message object of
//                class IPM.Document
//
//    Purpose
//    gathers the summary sheet information from the properties of a message
//
//    Return Value
//    TRUE if everything succeeded
//    FALSE something failed, inform the caller
//
//    Comments
//    The pointers to the buffers are defined in the PRSHTDLG.CPP file and are
//    externally referenced here.
//
//    tagaDoc is a static array of property tags from GetNamedIDs
//    Only need to call this function once when tags are first needed.
//    The buffer for the tags are freed in the Release method of MyExchExt when
//    Exchange is closing down.
//

BOOL GetSummarySheetInfo(LPMESSAGE pMessage)
{
    HRESULT     hr;
    ULONG       ulcValues;
    BOOL        bSuccess;

    bSuccess = FALSE;

    if (pMessage == NULL)
    {
        ErrorMessageBox(NULL, 0, "GetSummarySheetInfo", "Null pMessage");
        goto error_return;
    }

    // Retrieve property IDs of the summary properties from the message.
    tagaDoc.cValues = 0;
    if (!GetNamedIDs(pMessage))
    {
        ErrorMessageBox(NULL, 0, "GetSummarySheetInfo", "GetNamedIDs failed");
        goto error_return;
    }

    hr = pMessage->GetProps((LPSPropTagArray)&tagaDoc, 0, &ulcValues,
        &pSummaryProps);
    if (FAILED(hr))
    {
        ErrorMessageBox(NULL, hr, "GetSummarySheetInfo", "GetProps failed");
        goto error_return;
    }

    // These must be validated in the same order as the named id property tag
    // array. See GetNamedIDs below.

    if (PROP_TYPE(pSummaryProps[KEYWORDS].ulPropTag) == PT_MV_STRING8)
        mvszKeywords = pSummaryProps[KEYWORDS].Value.MVszA;
    else
    {
        char szDefault[] = "No Keywords";
        szDocKeywords = (LPSTR)malloc(lstrlen(szDefault) + 1);
        lstrcpy (szDocKeywords, szDefault);
        mvszKeywords.cValues = 0;
    }

    if (PROP_TYPE(pSummaryProps[COMMENTS].ulPropTag) == PT_STRING8)
        szDocComments = pSummaryProps[COMMENTS].Value.lpszA;
    else
        szDocComments = "No Comments";

    if (PROP_TYPE(pSummaryProps[AUTHOR].ulPropTag) == PT_STRING8)
        szDocAuthor = pSummaryProps[AUTHOR].Value.lpszA;
    else
        szDocAuthor = "No Author";

    if (PROP_TYPE(pSummaryProps[COMPANY].ulPropTag) == PT_STRING8)
        szDocCompany = pSummaryProps[COMPANY].Value.lpszA;
    else
        szDocCompany = "No Company";

    if (PROP_TYPE(pSummaryProps[APPNAME].ulPropTag) == PT_STRING8)
        szDocApplication = pSummaryProps[APPNAME].Value.lpszA;
    else
        szDocApplication = "No Application";

    if (PROP_TYPE(pSummaryProps[CATEGORY].ulPropTag) == PT_STRING8)
        szDocCategory = pSummaryProps[CATEGORY].Value.lpszA;
    else
        szDocCategory = "No Category";

    if (PROP_TYPE(pSummaryProps[TITLE].ulPropTag) == PT_STRING8)
        szDocTitle = pSummaryProps[TITLE].Value.lpszA;
    else
        szDocTitle = "No Title";

    if (PROP_TYPE(pSummaryProps[DOCSUBJECT].ulPropTag) == PT_STRING8)
        szDocSubject = pSummaryProps[DOCSUBJECT].Value.lpszA;
    else
        szDocSubject = "No Subject";

    // Retrieve additional non-named properties
    hr = pMessage->GetProps((LPSPropTagArray)&MoreObjTags, 0, &ulcValues, &pMoreDocProps);
    if (FAILED(hr))
    {
        ErrorMessageBox(NULL, hr, "GetSummarySheetInfo", "GetProps failed");
        goto error_return;
    }

    if (PROP_TYPE(pMoreDocProps[LASTEDIT].ulPropTag) == PT_SYSTIME)
        szDocLastSaved = GetFileTimeString(&(pMoreDocProps[LASTEDIT].Value.ft));
    else
        szDocLastSaved = "No last saved time";

    bSuccess = TRUE;

error_return:

return bSuccess;

}

///////////////////////////////////////////////////////////////////////////////
//    GetNamedIDs()
//
//    Parameters
//    pMessage -- message object of which to call GetIDsFromNames()
//
//    Purpose
//    This function retrieves the property IDs of names from Office Document
//    summary sheet properties.
//
//    Return Value - none
//
//    Comments
//    The name strings used were found imperically because there were no
//    documentation publishing the Office document named properties at the
//    time this sample was implemented.  It is possible to get a list
//    of all public named properties by calling GetNamesFromIDs().
//
//    tagaDoc is a global variable listed at the top of this file.
//
BOOL GetNamedIDs(LPMESSAGE pMessage)
{
    HRESULT         hr;
    LPMAPINAMEID    apMAPINameId[NAMEIDS];
    MAPINAMEID      aMAPINameId[NAMEIDS];
    UINT            i;
    LPSPropTagArray pTags = NULL;
    BOOL            bSucceeded = FALSE;

    if (pMessage == NULL)
    {
        ErrorMessageBox(NULL, 0, "GetNamedIDs", "Null pMessage");
        goto error_return;
    }

    memset(apMAPINameId, 0, NAMEIDS * sizeof(LPMAPINAMEID));
    memset(aMAPINameId, 0, NAMEIDS * sizeof(MAPINAMEID));

    // create an array of property names
    for (i=0; i<NAMEIDS; i++)
    {
        aMAPINameId[i].lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
        aMAPINameId[i].ulKind = MNID_STRING;
        aMAPINameId[i].Kind.lpwstrName = awszDocProperties[i];

        apMAPINameId[i] = &aMAPINameId[i];
    }

    // Get the property IDs for these names from the message.
    // A PT_ERROR tag is returned for any unrecognized names.
    hr = pMessage->GetIDsFromNames(NAMEIDS, apMAPINameId, 0, &pTags);
    if (FAILED(hr))
    {
        ErrorMessageBox(NULL, hr, "GetNamedIDs", "GetIDsFromNames failed");
        goto error_return;
    }

    // Copy the returned property tags to the global array tagaDoc.
    // For the unrecognized properties, substitute a tag that GetProps
    // will ignore.
    for (i=0; i<NAMEIDS; ++i)
    {
        if (PT_ERROR != PROP_TYPE(pTags->aulPropTag[i]))
            tagaDoc.aulPropTag[i] = pTags->aulPropTag[i];
        else
            tagaDoc.aulPropTag[i] = PR_NULL;
    }

    tagaDoc.cValues = NAMEIDS;

    bSucceeded = TRUE;

error_return:

    MAPIFreeBuffer(pTags);
    return bSucceeded;
}

///////////////////////////////////////////////////////////////////////////////
//    ValidDocObject()
//
//    Parameters
//    pObject -- pointer to property object validate
//
//    Purpose
//    Inspects the properties of the object to see if the selected object
//    is a message type object and has the proper class.
//
//    Return Value
//    TRUE if the object is MAPI_MESSAGE and the class is IPM.Document
//    FALSE if either the object is not MAPI_MESSAGE or class is not
//    IPM.Document
//
//    Comments
//    This function is intended to be called from GetPages with an
//    object pointer from GetObject.
//
BOOL ValidDocObject(LPMAPIPROP pObject)
{
    HRESULT         hr;
    ULONG           ulcValues;
    LPSPropValue    pObjectProps = NULL;
    BOOL            bRet;

    bRet = FALSE;   // assume invalid object

    if (!pObject)
    {
        ErrorMessageBox(NULL, 0, "ValidDocObject", "Null pObject");
        return FALSE;
    }

    hr = pObject->GetProps((LPSPropTagArray)&ObjPropTags, 0, &ulcValues,
        &pObjectProps);
    if (FAILED(hr))
    {
        ErrorMessageBox(NULL, hr, "ValidDocObject", "pObject->GetProps failed");
        goto error_return;
    }

    if ( (pObjectProps[OBJECT_TYPE].Value.l == MAPI_MESSAGE)  &&
         (pObjectProps[MESSAGE_CLASS].ulPropTag == PR_MESSAGE_CLASS) &&
         (strstr(pObjectProps[MESSAGE_CLASS].Value.lpszA, "IPM.Document")) )
        bRet = TRUE;

    // clean up
error_return:

    MAPIFreeBuffer(pObjectProps);
    return bRet;
}

///////////////////////////////////////////////////////////////////////////////
//    GetFileTimeString()
//
//    Parameters
//    pft -- filetime structure pointer
//
//    Purpose
//    convert the given filetime date into a string.
//
//    Return Value
//    pointer to a string buffer containing formatted filed time.
//
//    Comments
//    The filetime is formated into dd/mm/yy hh:mm.  The string buffer
//    is static (not on the stack) because it is referred to outside this
//    function.
//

LPSTR GetFileTimeString(FILETIME * pft)
{
 static char szTimeStringBuff[25];
 FILETIME ftLocal;
 SYSTEMTIME systime;
 WORD wAdjustedHour;
 LPSTR pszAMPM[2] = { "AM", "PM" };
 int nAMPM;

 FileTimeToLocalFileTime(pft, &ftLocal);
 FileTimeToSystemTime(&ftLocal, &systime);

 szTimeStringBuff[0] = '\0';

// convert from Military time to Civilian time format
 wAdjustedHour = systime.wHour;

 nAMPM = 1;  // assume PM time
 if ( wAdjustedHour < 12 )
    nAMPM = 0;   // AM time

 if (wAdjustedHour > 12)
 {
    wAdjustedHour-=12;
 }

 wsprintf(szTimeStringBuff, "%02d/%02d/%02d %02d:%02d%s",
            systime.wMonth,
            systime.wDay,
            systime.wYear,
            wAdjustedHour,
            systime.wMinute,
            pszAMPM[nAMPM]);

 return szTimeStringBuff;

}


EXTPRSHT.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      EXTPRSHT.H
//
//  Description
//      Interface Methods are declared in this file for extprsht.cpp
//
//  Author
//      Gary Peluso
//
//  Revision: 1.00
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#ifndef __EXTPRSHT_H__
#define __EXTPRSHT_H__

#include <WINDOWS.H>
#include <COMMCTRL.H>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>
#include <EXCHEXT.H>

#include "RESOURCE.H"

///////////////////////////////////////////////////////////////////////////////
//    Prototypes
//

extern "C" LPEXCHEXT CALLBACK ExchEntryPoint(void);

BOOL CALLBACK SummaryPageDlgProc(HWND hDlg, UINT uMsg,
        WPARAM wParam, LPARAM lParam);
void ErrorMessageBox(HWND hWnd, HRESULT hr,
        LPSTR szFunction, LPSTR szMessage);
BOOL GetNamedIDs(LPMESSAGE pMessage);
BOOL GetSummarySheetInfo(LPMESSAGE pMessage);
BOOL ValidDocObject(LPMAPIPROP pObject);
LPSTR GetFileTimeString(FILETIME * pft);

///////////////////////////////////////////////////////////////////////////////
//    Office document summary sheet information data
//

extern LPSPropValue pSummaryProps;
extern LPSTR szDocComments;
extern LPSTR szDocApplication;
extern LPSTR szDocTitle;
extern LPSTR szDocSubject;
extern LPSTR szDocAuthor;
extern SLPSTRArray mvszKeywords;
extern LPSTR szDocKeywords;
extern LPSTR szDocLastSaved;
extern LPSTR szDocEditTime;
extern LPSTR szDocCategory;
extern LPSTR szDocCompany;

///////////////////////////////////////////////////////////////////////////////
//   Other global data

extern BOOL bBusy;

///////////////////////////////////////////////////////////////////////////////
//    forward declarations
//
class MyExchExt;
class MyExchExtPropertySheets;

class MyExchExt : public IExchExt
{

 public:
    MyExchExt();
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP Install (LPEXCHEXTCALLBACK pmecb,
                        ULONG mecontext, ULONG ulFlags);

 private:
    ULONG m_cRef;
    UINT  m_context;
    MyExchExtPropertySheets * m_pExchExtPropertySheets;

};

class MyExchExtPropertySheets : public IExchExtPropertySheets
{
 public:
    MyExchExtPropertySheets (LPUNKNOWN pParentInterface) {
    m_pExchExt = pParentInterface;
    m_cRef = 0;
    };
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};

    STDMETHODIMP_ (ULONG) GetMaxPageCount(ULONG ulFlags);
    STDMETHODIMP  GetPages(LPEXCHEXTCALLBACK peecb,
                          ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp);
    STDMETHODIMP_ (VOID) FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags,
                                         ULONG cpsp);

 private:
    ULONG m_cRef;
    LPUNKNOWN m_pExchExt;

};

#endif // __EXTPRSHT_H__


PRSHTDLG.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      PRSHTDLG.CPP
//
//  Description
//      This file contains the dialogproc that handles messages for
//      the property page extended onto the Exchange property sheet.
//
//
//  Revision: 1.00
//
//  Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#include "EXTPRSHT.H"

///////////////////////////////////////////////////////////////////////////////
//    Office document summary sheet information data
//    Pointers to items in pSummaryProps.  See GetPages function
//    in EXTPRSHT.CPP
//
LPSTR szDocComments = NULL;
LPSTR szDocApplication = NULL;
LPSTR szDocTitle = NULL;
LPSTR szDocSubject = NULL;
LPSTR szDocAuthor = NULL;
SLPSTRArray mvszKeywords;
LPSTR szDocKeywords = NULL;
LPSTR szDocLastSaved = NULL;
LPSTR szDocEditTime = NULL;
LPSTR szDocCategory = NULL;
LPSTR szDocCompany = NULL;

///////////////////////////////////////////////////////////////////////////////
//    FUNCTION SummaryPageDlgProc
//
//    Parameters
//    hDlg   - handle to modeless dialog, the property page
//    uMsg   - message
//    wParam - wParam of wndproc, not used
//    lParam - lParam of wndproc, points to NMHDR for notifications
//
//    Purpose
//    Handles the notifications sent for managing a custom property page
//    appended to those on an Exchange Client property sheet.
//
//    Return Value
//    Appropriate return values are set in the DWL_MSGRESULT longword
//    of the dialog handle.  See Win95 Common Controls documentation on
//    Property Sheets for more information.
//

BOOL CALLBACK SummaryPageDlgProc(HWND hDlg, UINT uMsg,
        WPARAM wParam, LPARAM lParam)
{
 BOOL bMsgResult = FALSE;
 UINT i;
 static HBRUSH hBrush;
 static COLORREF GrayColor;

 switch (uMsg)
 {

  case WM_INITDIALOG:
  {
   LPSTR szTemp;
   LOGBRUSH lb;

   // build a string of space delimited keywords from the mvstring.
        for (i=0; i<mvszKeywords.cValues; i++)
        {
           if (szDocKeywords == NULL)
           {
                szDocKeywords = (LPSTR)malloc(lstrlen(mvszKeywords.lppszA[i]) + 1);
                lstrcpy(szDocKeywords, mvszKeywords.lppszA[i]);
           }
           else
           {
            szTemp = (LPSTR) malloc(lstrlen(szDocKeywords) + lstrlen(mvszKeywords.lppszA[i]) + 3);
            lstrcpy(szTemp, szDocKeywords);
            lstrcat(szTemp, " ");
            lstrcat(szTemp, mvszKeywords.lppszA[i]);

            free(szDocKeywords);
            szDocKeywords = szTemp;
           }

        }

        GrayColor = (COLORREF)GetSysColor(COLOR_BTNFACE);

        memset(&lb, 0, sizeof(LOGBRUSH));
        lb.lbStyle = BS_SOLID;
        lb.lbColor = GrayColor;
        hBrush = CreateBrushIndirect(&lb);

    return TRUE;
  }
  break;

  case WM_CTLCOLORDLG:
  case WM_CTLCOLORSTATIC:

    if (hBrush != NULL)
    {
      SetBkColor((HDC)wParam, GrayColor);

      return (BOOL)hBrush;
    }

  break;

  case WM_DESTROY:
  {
   if (hBrush != NULL)
        DeleteObject(hBrush);

   free(szDocKeywords);
   szDocKeywords = NULL;

   szDocComments = NULL;
   szDocApplication = NULL;
   szDocTitle = NULL;
   szDocSubject = NULL;
   szDocAuthor = NULL;
   szDocLastSaved = NULL;
   szDocEditTime = NULL;
   szDocCategory = NULL;
   szDocCompany = NULL;

   return TRUE;
  }

  case WM_NOTIFY:
  {
   switch ( ((LPNMHDR) lParam)->code)
   {
    case PSN_KILLACTIVE:
        bMsgResult = FALSE;  // allow this page to receive PSN_APPLY
        break;

    case PSN_SETACTIVE:

        //  fill the controls in the page with information
        SetDlgItemText( hDlg, IDC_COMMENTS, szDocComments);
        SetDlgItemText( hDlg, IDC_APP, szDocApplication);
        SetDlgItemText( hDlg, IDC_TITLE, szDocTitle);
        SetDlgItemText( hDlg, IDC_SUBJECT, szDocSubject);
        SetDlgItemText( hDlg, IDC_AUTHOR, szDocAuthor);
        SetDlgItemText( hDlg, IDC_CATEGORY, szDocCategory);
        SetDlgItemText( hDlg, IDC_COMPANY, szDocCompany);
        SetDlgItemText( hDlg, IDC_LASTSAVE, szDocLastSaved);
        SetDlgItemText( hDlg, IDC_KEYWORDS, szDocKeywords);

        bMsgResult = FALSE;  // accepts activation
        break;

    case PSN_APPLY:
        bMsgResult = PSNRET_NOERROR;
        break;

    case PSN_HELP:
        MessageBox( ((LPNMHDR)lParam)->hwndFrom,
                    "Microsoft Exchange Client\n"
                    "Property Sheet Extension Sample\n"
                    "Microsoft (c) Corporation, 1995",
                    "About",
                    MB_OK);
        bMsgResult = TRUE;    // doesn't matter on this notification
        break;

    default:
        bMsgResult = FALSE;
        break;
   }  // switch

    SetWindowLong( hDlg, DWL_MSGRESULT, bMsgResult);
    break;

  }   // case WM_NOTIFY

  default:
    bMsgResult = FALSE;
    break;

 }  // switch

 return bMsgResult;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by EXTPRSHT.RC
//
#define IDD_SUMMARY                     101
#define IDC_APP                         1000
#define IDC_AUTHOR                      1001
#define IDC_COMPANY                     1002
#define IDC_CATEGORY                    1003
#define IDC_KEYWORDS                    1004
#define IDC_EDITTIME                    1005
#define IDC_LASTSAVE                    1006
#define IDC_COMMENTS                    1007
#define IDC_TITLE                       1008
#define IDC_SUBJECT                     1009
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1008
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

Sample Server Messaging Host

The Remote.Srv sample serves as the server based message repository
for MAPI 1.0 transports that handle the Windows Developer Supprt (WINDS)
address type. This program simulates a foreign messaging system.
WINDS also acts as the server host for address book with a global address list.
The global address list is the list of recipients in the WINDS host. Since
WINDS supports gateways, foreign message recipients appear in the user
directories of the gateways currently installed.

Requirements
------------

To build this program you must have:

    Windows NT 3.5 (or later),
    Microsoft Visual C++ version 2.0 (or later) or the Win32 SDK tools.

This sample uses RPC over named pipes to establish connections with remote
clients.

Building the Sample Code
------------------------

This sample runs under Windows NT 3.5x on Intel x86, MIPS R4xxx and Digital
AXP platforms.

If you are using Microsoft Visual C++, update the project file dependencies
as soon as the files are copied to your machine. Use the PROJECTS.UPDATE
DEPENDENCIES options in the Developer Studio.

This sample was developed using Microsoft Visual C++ 2.x. The associated
makefile is WINDSSRV.MAK for the service component and WINDSADM for the
remote administrator component. Traces can be enabled for Release builds by
defining ENABLE_DEBUG_OUTPUT in the preprocessor symbols define in the
project settings menu.

A command-line compiler-independent makefile has been provided for users
of command-line tools. The command line tool will, by default build a
DEBUG version of both components. If NODEBUG is defined, a RELEASE version
of the both components will be built.

Usage
-----

There are two pieces for the WINDS server sample. The WINDS service component
and the WINDS administrator component.

WINDS Service Installation
--------------------------

The WINDS service performs the actual processing. It can be run on a
Windows NT machine on any platform. To install the service, simply run the
built executable from the command line with the INSTALL option:

    C:\SAMPLES\MAPI\WINDS> WINDSSRV INSTALL

This will add the service to the registry and start the service immediately.
Note that you must be in the directory where the service executable is.

To stop and remove the server from the system use the REMOVE option:

    C:\SAMPLES\MAPI\WINDS> WINDSSRV  REMOVE

If the service is run without parameters, a window will come up for
interactive configuration (install and removal).

The WINDS service supports interaction with the Services Applet in the
control panel so it can be started, stopped, paused, resumed, and queried
using the standard UI.

Once the WINDS Service is installed, it can run unattended, even when no one
is logged into the Windows NT machine.

The service does not expose any UI for normal interaction and administration.
All of this is done through the WINDS Administrator program (explained below),
either by running it on the same machine where the WINDS service is running
or across the network.

WINDS Administrator
-------------------

Some of the menu items and property pages maybe grayed out because they have
not been implemented yet.

The WINDS administrator is a program that can be run on the same machine
where the WINDS service is running or across the network.

The administrator can be run on Windows NT (workstation or server) or from
Windows 95. The executable is single binary for all Intel Win32 platfoms,
so there is no need to rebuild it for Windows 95 once built for Windows NT
or vice versa.

The administrator is a graphical interface to the data stored in the WINDS
Server. Through this program, you can create and control the list of user
mailboxes, distribution lists, and public folders. You can also manage the
gateways and do other administrative tasks on the WINDS server, remotely,
through a graphical interface.

Debug Traces and Asserts
------------------------

This sample uses several output debug string functions in the Win32
environment to avoid attaching the DLL to any debugger. By default, the trace
messages are output to a debug terminal attached to COM1 with settings at
9600, N, 8, 1. The debug messages can also be written to a log file whose
default location is C:\MAPILOG.TXT. The file TRACES.CPP defines some macros
that can be easily modified for different communications settings, output
port, and log file name. It also implements a macro (ASSERT) and function that
test the validity of a given statement. If the assertion fails, the
user/developer has the opportunity to break into the debugger at the exact
point where the assertion occurred.

The debug routines are found in the TRACES.CPP and TRACES.H files.

To enable the TRACExx functions in the TRACES.CPP files, define the
ENABLE_DEBUG_OUTPUT macro during compilation. These functions work in DEBUG
or RELEASE builds. You can only enable/disable traces at compile time
through ENABLE_DEBUG_OUTPUT. There is no run time switch to enable or
disable the traces.

TRACES.CPP implements an ASSERT macro which evaluates an expression
expecting a TRUE result, i.e. ASSERT (expression) will interrupt execution
if the expression is NOT TRUE (non-zero).

The ASSERT macro displays the line and source code file name where the
assertion failed and writes the result to a debug trace. The assert
information is also written to the log file.

By default, if an assertion fails, a dialog will come up and interrupt
execution until a selection is made to break into the debugger or ignore the
assertion.


ADMDLIST.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      ADMDLIST.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "ADMIN.H"
#include "COMMON.H"

extern "C"
{
    HRESULT WINAPI DisplayDistListPropSheets
                        (HWND                       hOwnerWnd,
                         POBJECT_INFO               pObjInfo,
                         BOOL                       fCreate);
    BOOL CALLBACK DLProps1DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK DLProps2DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK DLProps3DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ModifyListDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK WaitDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    void WINAPI AddMembersToPageLV
                        (HWND                       hOwnerWnd,
                         HWND                       hListView,
                         DLM_LIST *                 pMembers);
};

///////////////////////////////////////////////////////////////////////////////
//    GetServerDistLists()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetServerDistLists (HWND hOwnerWnd)
{
    return DownloadBulkInfo (hOwnerWnd, ghListView, ITEM_SERVER_DIST_LISTS);
}

///////////////////////////////////////////////////////////////////////////////
//    DownloadBulkInfo()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DownloadBulkInfo (HWND hOwnerWnd, HWND hListView, ITEM_TYPE Type)
{
    LV_ITEM lvi = { 0 };
    lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
    int i = 0;
    long lPipeNum;
    TCHAR achPipeName[128];
    LPTSTR szObjAlias;
    HANDLE hPipe;
    DWORD dwBytesRead;
    DWORD * pdwObjID;
    AB_ENTRY_INFO abEntry = { 0 };
    HRESULT hResult = S_OK;
    EnterCriticalSection (&csRemoteServer);
    SendMessage (hOwnerWnd, WM_WINDS_REMOTE_CALL_IN_PROGRESS, hResult, 0);
    ListView_DeleteAllItems (hListView);
    SendMessage (ghStatusBar, SB_SETTEXT, 0, (LPARAM)TEXT("Connecting to the server..."));
    RpcTryExcept
    {
        // Make the remote call to request the server to open a pipe for the download
        switch (Type)
        {
            case ITEM_SERVER_USER_MAILBOXES :
                hResult = RemoteAdmGetServerMailboxes (&lPipeNum);
                lvi.iImage = IMG_USER_MAILBOX;
                pdwObjID = &(abEntry.Info.MB.dwObjID);
                szObjAlias = abEntry.Info.MB.szMailboxName;
                break;
            case ITEM_SERVER_DIST_LISTS :
                hResult = RemoteAdmGetServerDistLists (&lPipeNum);
                lvi.iImage = IMG_DIST_LIST;
                pdwObjID = &(abEntry.Info.DL.dwObjID);
                szObjAlias = abEntry.Info.DL.szDLAlias;
                break;
            default :
                ASSERTMSG (FALSE, "Unknown item type");
                hResult = E_FAIL;
                break;
        }
        if (!hResult)
        {
            SendMessage (ghStatusBar, SB_SETTEXT, 0, (LPARAM)TEXT("Connection established... Pending pipe connection..."));
            // Construct the download pipe name
            wsprintf (achPipeName, PIPE_NAME_FORMAT, g_szCurrentServer, lPipeNum);
            // Create our endpoint and connect
            hPipe = CreateFile (achPipeName,
                                GENERIC_READ,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (!hResult)
    {
        SendMessage (ghStatusBar, SB_SETTEXT, 0, (LPARAM)TEXT("Pipe opened. Downloading server information..."));
        do
        {
            // Read from the pipe
            if (!ReadFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesRead, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                    HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                {
                    // There was an error and we can't continue
                    TraceResult ("DownloadBulkInfo: Failed to read from the source", hResult);
                }
                else
                {
                    // If the pipe was broken, it means the server finished writing
                    // to the it, so we are finished reading from it.
                    hResult = S_OK;
                }
            }
            else
            {
                lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
                lvi.iItem = i;
                lvi.iSubItem = 0;
                lvi.pszText = szObjAlias;
                lvi.lParam = *pdwObjID;
                lvi.iItem = ListView_InsertItem (hListView, &lvi);

                switch (Type)
                {
                    case ITEM_SERVER_USER_MAILBOXES :
                        ListView_SetItemText (hListView, lvi.iItem, 1, abEntry.Info.MB.szFullName);
                        ListView_SetItemText (hListView, lvi.iItem, 2, abEntry.Info.MB.szJobTitle);
                        ListView_SetItemText (hListView, lvi.iItem, 3, abEntry.Info.MB.szOffice);
                        ListView_SetItemText (hListView, lvi.iItem, 4, abEntry.Info.MB.szDepartment);
                        ListView_SetItemText (hListView, lvi.iItem, 5, abEntry.Info.MB.szPhone);
                        ListView_SetItemText (hListView, lvi.iItem, 6, abEntry.Info.MB.szFax);
                        break;
                    case ITEM_SERVER_DIST_LISTS :
                        ListView_SetItemText (hListView, lvi.iItem, 1, abEntry.Info.DL.szDLFullName);
                        break;
                }
                i++;
            }
            if (AbortRemoteCall())
            {
                dwBytesRead = 0; // This will cause the DO-WHILE() loop to terminate
                hResult = S_OK; // If the user aborted the call, don't return any error.
            }
        } while (dwBytesRead && !hResult);
        CloseHandle (hPipe);
        SendMessage (ghStatusBar, SB_SETTEXT, 0, (LPARAM)TEXT("Connection Closed..."));
    }
    LeaveCriticalSection (&csRemoteServer);
    TraceResult ("DownloadBulkInfo", hResult);
    SendMessage (hOwnerWnd, WM_WINDS_REMOTE_CALL_COMPLETED, hResult, 0);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateNewDistList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI CreateNewDistList (HWND hOwnerWnd)
{
    DLM_XMIT_LIST Members = { 0 };
    OBJECT_INFO ObjInfo = { 0 };
    ObjInfo.DL.pMembers = &Members;
    ObjInfo.Type = SERVER_DISTRIBUTION_LIST;
    HRESULT hResult = DisplayDistListPropSheets (hOwnerWnd, &ObjInfo, TRUE);
    if (!hResult)
    {
        RpcTryExcept
        {
            hResult = RemoteAdmCreateDistList ((WINDS_RPC_STRING)ObjInfo.DL.szDLAlias,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szDLFullName,
                                               ObjInfo.DL.dwFlags,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szOwnerAlias,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szOwnerName,
                                               ObjInfo.DL.dwOwnerID,
                                               &Members);
        }
        RpcExcept(1)
        {
            // If we got here is because there was an error while call was made
            // or when it was about to be made.
            hResult = RpcExceptionCode();
            if (RPC_S_SERVER_UNAVAILABLE == hResult)
            {
                hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
            }
            else
            {
                hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
            }
        }
        RpcEndExcept
        FreeDLMList (&Members);
    }
    else
    {
        if (S_FALSE == hResult)
        {
            hResult = S_OK;
        }
    }
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ShowDistListProps()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI ShowDistListProps (HWND hOwnerWnd, DWORD dwObjID)
{
    DLM_XMIT_LIST_A Members = { 0 };
    OBJECT_INFO ObjInfo = { 0 };
    ObjInfo.DL.dwObjID = dwObjID;
    ObjInfo.DL.pMembers = &Members;
    ObjInfo.Type = SERVER_DISTRIBUTION_LIST;

    HRESULT hResult = S_OK;
    RpcTryExcept
    {
        hResult = RemoteAdmGetDLProps (dwObjID,
                                       (WINDS_RPC_STRING)ObjInfo.DL.szDLAlias,
                                       (WINDS_RPC_STRING)ObjInfo.DL.szDLFullName,
                                       &ObjInfo.DL.dwFlags,
                                       (WINDS_RPC_STRING)ObjInfo.DL.szOwnerAlias,
                                       (WINDS_RPC_STRING)ObjInfo.DL.szOwnerName,
                                       &ObjInfo.DL.dwOwnerID,
                                       (WINDS_RPC_STRING)ObjInfo.DL.szComments,
                                       &Members);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (!hResult)
    {
        hResult = DisplayDistListPropSheets (hOwnerWnd, &ObjInfo, FALSE);
        if (S_OK == hResult)
        {
            RpcTryExcept
            {
                hResult = RemoteAdmSetDLProps (dwObjID,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szDLAlias,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szDLFullName,
                                               ObjInfo.DL.dwFlags,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szOwnerAlias,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szOwnerName,
                                               ObjInfo.DL.dwOwnerID,
                                               (WINDS_RPC_STRING)ObjInfo.DL.szComments,
                                               &Members);
            }
            RpcExcept(1)
            {
                // If we got here is because there was an error while call was made
                // or when it was about to be made.
                hResult = RpcExceptionCode();
                if (RPC_S_SERVER_UNAVAILABLE == hResult)
                {
                    hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
                }
                else
                {
                    hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
                }
            }
            RpcEndExcept
        }
        if (S_FALSE == hResult)
        {
            hResult = S_OK;
        }
        FreeDLMList (&Members);
    }
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DisplayDistListPropSheets()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DisplayDistListPropSheets (HWND           hOwnerWnd,
                                          POBJECT_INFO   pObjInfo,
                                          BOOL           fCreate)
{
    PROPSHEETHEADER psh = { 0 };
    PROPSHEETPAGE psp[3] = { 0 };
    TCHAR szHeaderTitle[64];

    OBJECT_INFO NewObjInfo = *pObjInfo;

    psp[0].dwSize      = sizeof(PROPSHEETPAGE);
    psp[0].dwFlags     = PSP_USETITLE;
    psp[0].hInstance   = ghInstance;
    psp[0].pszTemplate = MAKEINTRESOURCE (IDD_DL_PROPS1);
    psp[0].pfnDlgProc  = DLProps1DlgProc;
    psp[0].pszTitle    = TEXT("General");
    psp[0].lParam      = (LPARAM)&NewObjInfo;

    psp[1].dwSize      = sizeof(PROPSHEETPAGE);
    psp[1].dwFlags     = PSP_USETITLE;
    psp[1].hInstance   = ghInstance;
    psp[1].pszTemplate = MAKEINTRESOURCE (IDD_DL_PROPS2);
    psp[1].pfnDlgProc  = DLProps2DlgProc;
    psp[1].pszTitle    = TEXT("Members");
    psp[1].lParam      = (LPARAM)&NewObjInfo;

    psp[2].dwSize      = sizeof(PROPSHEETPAGE);
    psp[2].dwFlags     = PSP_USETITLE;
    psp[2].hInstance   = ghInstance;
    psp[2].pszTemplate = MAKEINTRESOURCE (IDD_DL_PROPS3);
    psp[2].pfnDlgProc  = DLProps3DlgProc;
    psp[2].pszTitle    = TEXT("Ownership");
    psp[2].lParam      = (LPARAM)&NewObjInfo;

    psh.dwSize     = sizeof(PROPSHEETHEADER);
    psh.dwFlags    = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
    psh.hwndParent = hOwnerWnd;
    psh.hInstance  = ghInstance;
    psh.nPages     = sizeof(psp)/sizeof(PROPSHEETPAGE);
    psh.ppsp       = (LPCPROPSHEETPAGE)&psp;
    if (fCreate)
    {
        psh.pszCaption = TEXT("New Distribution List Properties");
        psh.nStartPage = 0;
    }
    else
    {
        wsprintf (szHeaderTitle, TEXT("Distribution List Properties for %s"), pObjInfo->DL.szDLAlias);
        psh.pszCaption = szHeaderTitle;
        psh.nStartPage = 1;
    }

    // If the user hit OK and at least one of the properties changed,
    // we must change the properties on the server
ReEnterProperties:
    if (1 == PropertySheet (&psh) &&
        (fCreate || NewObjInfo.dwData ||
        NewObjInfo.DL.dwFlags != pObjInfo->DL.dwFlags ||
        NewObjInfo.DL.dwOwnerID != pObjInfo->DL.dwOwnerID ||
        lstrcmpi (NewObjInfo.DL.szDLFullName, pObjInfo->DL.szDLFullName) ||
        lstrcmpi (NewObjInfo.DL.szDLAlias, pObjInfo->DL.szDLAlias) ||
        lstrcmpi (NewObjInfo.DL.szComments, pObjInfo->DL.szComments)))
    {
        if (fCreate)
        {
            if (!IsObjAliasValid (hOwnerWnd, NewObjInfo.DL.szDLAlias))
            {
                goto ReEnterProperties;
            }
        }
        if (0 == lstrlen (NewObjInfo.DL.szDLFullName))
        {
            PrivateMessageBox (IDS_MSG_NO_DL_NAME, hOwnerWnd, 0);
            goto ReEnterProperties;
        }
        *pObjInfo = NewObjInfo;
        return S_OK;
    }
    return S_FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    DLProps1DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK DLProps1DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    static HWND hCtlLabel;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_OBJECT_ALIAS);
                SetWindowText (hCtl, pObjInfo->DL.szDLAlias);
                if (0 == pObjInfo->DL.dwObjID)
                {
                    EnableWindow (hCtl, TRUE);
                    Edit_LimitText (hCtl, MAX_ALIAS_SIZE);
                    hCtl = GetDlgItem (hDlg, IDC_OBJECT_ALIAS_LABEL);
                    EnableWindow (hCtl, TRUE);
                }

                hCtl = GetDlgItem (hDlg, IDC_DL_FULLNAME);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->DL.szDLFullName);

                hCtlLabel = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtlLabel, pObjInfo->DL.szDLAlias);
                SetWindowFont (hCtlLabel, ghBoldFont, TRUE);

                if (OBJECT_DISABLED & pObjInfo->DL.dwFlags)
                {
                    Button_SetCheck (GetDlgItem (hDlg, IDC_DISABLE), BST_CHECKED);
                }
                if (HIDE_IN_DIR & pObjInfo->DL.dwFlags)
                {
                    Button_SetCheck (GetDlgItem (hDlg, IDC_HIDE), BST_CHECKED);
                }

                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_LARGE_ENVEL, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY:
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_DISABLE)))
                {
                    pObjInfo->DL.dwFlags |= OBJECT_DISABLED;
                }
                else
                {
                    pObjInfo->DL.dwFlags &= ~OBJECT_DISABLED;
                }
                if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_HIDE)))
                {
                    pObjInfo->DL.dwFlags |= HIDE_IN_DIR;
                }
                else
                {
                    pObjInfo->DL.dwFlags &= ~HIDE_IN_DIR;
                }
                GetWindowText (GetDlgItem (hDlg, IDC_OBJECT_ALIAS),
                               pObjInfo->DL.szDLAlias,
                               MAX_ALIAS_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_DL_FULLNAME),
                               pObjInfo->DL.szDLFullName,
                               MAX_STRING_SIZE+1);
                return PSNRET_NOERROR;
            }
            break;

        case WM_COMMAND :
            if (HIWORD(wParam) == EN_CHANGE && LOWORD(wParam) == IDC_OBJECT_ALIAS)
            {
                GetWindowText ((HWND)lParam, pObjInfo->DL.szDLAlias, MAX_ALIAS_SIZE+1);
                SetWindowText (hCtlLabel, pObjInfo->DL.szDLAlias);
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    DLProps2DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK DLProps2DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;

                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->DL.szDLAlias);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                hCtl = GetDlgItem (hDlg, IDC_MEMBERS);
                ListView_SetImageList (hCtl, g_hImages, LVSIL_SMALL);

                RECT rc;
                GetClientRect (hCtl, &rc);
                LV_COLUMN lvc;
                lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
                lvc.fmt = LVCFMT_LEFT;
                lvc.pszText = TEXT("Full Name");
                lvc.iSubItem = 0;
                lvc.cx = 160;
                ListView_InsertColumn (hCtl, 0, &lvc);
                lvc.pszText = TEXT("Alias");
                lvc.iSubItem = 1;
                lvc.cx = min (110, (rc.right - rc.left - lvc.cx));
                ListView_InsertColumn (hCtl, 1, &lvc);

                AddMembersToPageLV (hDlg, hCtl, (DLM_LIST *)pObjInfo->DL.pMembers);

                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_DL_MEMBERS, ILD_NORMAL));
            }
            return TRUE;

        case WM_DESTROY :
            ListView_SetImageList (GetDlgItem (hDlg, IDC_MEMBERS), NULL, LVSIL_SMALL);
            break;

        case WM_COMMAND :
            if (LOWORD(wParam) == IDC_MODIFY)
            {
                pObjInfo->pMembers = (DLM_LIST *)pObjInfo->DL.pMembers;
                if (TRUE == DialogBoxParam (ghInstance,
                                            MAKEINTRESOURCE(IDD_ADDTOLIST),
                                            hDlg,
                                            ModifyListDlgProc,
                                            (LPARAM)pObjInfo))
                {
                    AddMembersToPageLV (hDlg, GetDlgItem (hDlg, IDC_MEMBERS), (DLM_LIST *)pObjInfo->DL.pMembers);
                }
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    AddMembersToPageLV()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI AddMembersToPageLV (HWND        hOwnerWnd,
                                HWND        hListView,
                                DLM_LIST *  pMembers)
{
    ListView_DeleteAllItems (hListView);
    int i = 0;
    LV_ITEM lvi = { 0 };
    lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
    WINDS_AB_OBJTYPE Type;
    DLM_LIST * pNode = pMembers;
    while (pNode && pNode->Info.szMemberAlias[0])
    {
        Type = (WINDS_AB_OBJTYPE)pNode->Info.dwMemberType;
        switch (Type)
        {
            case GATEWAY_RECIPIENT :
            case SERVER_USER_MAILBOX :
                lvi.iImage = IMG_USER_MAILBOX;
                break;
            case ITEM_SERVER_DIST_LISTS :
                lvi.iImage = IMG_DIST_LIST;
                break;
            case PUBLIC_FOLDER :
                TraceMessage ("AddMembersToPageLV: Public Folder added - WARNING NYI");
            default :
                goto NextNode;
        }
        lvi.iItem = i;
        lvi.iSubItem = 0;
        lvi.pszText = (LPSTR)pNode->Info.szMemberName;
        lvi.lParam = pNode->Info.dwMemberID;
        lvi.iItem = ListView_InsertItem (hListView, &lvi);
        ListView_SetItemText (hListView, lvi.iItem, 1, (LPSTR)pNode->Info.szMemberAlias);
        i++;
NextNode:
        pNode = pNode->pNext;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DLProps3DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK DLProps3DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->DL.szDLAlias);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                if (DL_OWNED & pObjInfo->DL.dwFlags)
                {
                    SetWindowText (GetDlgItem (hDlg, IDC_DL_OWNER), pObjInfo->DL.szOwnerName);
                    hCtl = GetDlgItem (hDlg, IDC_COMMENTS);
                    SetWindowText (hCtl, pObjInfo->DL.szComments);
                    Edit_LimitText (hCtl, sizeof(pObjInfo->DL.szComments)/sizeof(TCHAR) - 1);
                    hCtl = GetDlgItem (hDlg, IDC_OWNED);
                    Button_SetCheck (hCtl, BST_CHECKED);
                    PostMessage (hDlg, WM_COMMAND, MAKEWPARAM(IDC_OWNED, BN_CLICKED), (LPARAM)hCtl);
                }
                else
                {
                    Button_SetCheck (GetDlgItem (hDlg, IDC_NOT_OWNED), BST_CHECKED);
                }

                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_DL_OWNER, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY:
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_OWNED)))
                {
                    pObjInfo->DL.dwFlags |= DL_OWNED;
                    TCHAR achBuffer[64] = { 0 };
                    GetWindowText (GetDlgItem (hDlg, IDC_DL_OWNER), achBuffer, 64);
                    if (NULL == achBuffer[0])
                    {
                        pObjInfo->DL.dwFlags &= ~DL_OWNED;
                    }
                    else
                    {
                        GetWindowText (GetDlgItem (hDlg, IDC_COMMENTS), pObjInfo->DL.szComments, sizeof(pObjInfo->DL.szComments)/sizeof(TCHAR));
                    }
                }
                else
                {
                    pObjInfo->DL.dwFlags &= ~DL_OWNED;
                }
                if (!(DL_OWNED & pObjInfo->DL.dwFlags))
                {
                    pObjInfo->DL.dwOwnerID = 0;
                    ZeroMemory (pObjInfo->DL.szOwnerAlias, sizeof(pObjInfo->DL.szOwnerAlias));
                    ZeroMemory (pObjInfo->DL.szOwnerName, sizeof(pObjInfo->DL.szOwnerName));
                    ZeroMemory (pObjInfo->DL.szComments, sizeof(pObjInfo->DL.szComments));
                }
                return PSNRET_NOERROR;
            }
            break;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_OWNED :
                case IDC_NOT_OWNED :
                    if (HIWORD(wParam) == BN_CLICKED)
                    {
                        SetFocus ((HWND)lParam);
                        BOOL fEnabled = (LOWORD(wParam) == IDC_OWNED ? TRUE : FALSE);
                        HWND hCtl;
                        hCtl = GetDlgItem (hDlg, IDC_DL_OWNER);
                        EnableWindow (hCtl, fEnabled);

hCtl = GetDlgItem (hDlg, IDC_OWNER_LABEL);
                        EnableWindow (hCtl, fEnabled);
                        hCtl = GetDlgItem (hDlg, IDC_MODIFY);
                        EnableWindow (hCtl, fEnabled);
                        hCtl = GetDlgItem (hDlg, IDC_COMMENTS_LABEL);
                        EnableWindow (hCtl, fEnabled);
                        hCtl = GetDlgItem (hDlg, IDC_COMMENTS);
                        EnableWindow (hCtl, fEnabled);
                    }
                    break;

                case IDC_MODIFY :
                    if (TRUE == DialogBoxParam (ghInstance,
                                                MAKEINTRESOURCE(IDD_ONE_MAILBOX),
                                                hDlg,
                                                SelectUserDlgProc,
                                                (LPARAM)pObjInfo))
                    {
                        SetWindowText (GetDlgItem (hDlg, IDC_DL_OWNER), pObjInfo->DL.szOwnerName);
                    }
                    break;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    SelectUserDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK SelectUserDlgProc (HWND    hDlg,
                                 UINT    message,
                                 WPARAM  wParam,
                                 LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                pObjInfo = (POBJECT_INFO)lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hWaitWnd = CreateDialog (ghInstance, MAKEINTRESOURCE(IDD_DIR_DOWNLOAD_WAIT), hDlg, WaitDlgProc);

                long lPipeNum;
                HANDLE hPipe;
                DWORD dwBytesRead, dwUserID, dwListUserIndex = 0;
                if (SERVER_DISTRIBUTION_LIST == pObjInfo->Type)
                {
                    dwUserID = pObjInfo->DL.dwOwnerID;
                }
                else
                {
                    dwUserID = pObjInfo->MB.dwManagerID;
                }
                AB_ENTRY_INFO abEntry = { 0 };
                HRESULT hResult = S_OK;
                int nTabStops[1] = { 1000 }; // Off the screen
                TCHAR achPipeName[64];

                HWND hListView = GetDlgItem (hDlg, IDC_ALL_USERS);
                ListView_SetImageList (hListView, g_hImages, LVSIL_SMALL);

                RECT rc;
                GetClientRect (hListView, &rc);
                LV_COLUMN lvc;
                lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
                lvc.fmt = LVCFMT_LEFT;
                lvc.pszText = TEXT("Full Name");
                lvc.iSubItem = 0;
                lvc.cx = 160;
                ListView_InsertColumn (hListView, 0, &lvc);
                lvc.pszText = TEXT("Alias");
                lvc.iSubItem = 1;
                lvc.cx = min (110, (rc.right - rc.left - lvc.cx));
                ListView_InsertColumn (hListView, 1, &lvc);

                int i = 0;
                LV_ITEM lvi = { 0 };
                lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
                lvi.iImage = IMG_USER_MAILBOX;

                RpcTryExcept
                {
                    hResult = RemoteAdmGetServerMailboxes (&lPipeNum);
                    if (!hResult)
                    {
                        // Construct the download pipe name
                        wsprintf (achPipeName, PIPE_NAME_FORMAT, g_szCurrentServer, lPipeNum);
                        // Create our endpoint and connect
                        hPipe = CreateFile (achPipeName,
                                            GENERIC_READ,
                                            0,
                                            NULL,
                                            OPEN_EXISTING,
                                            0,
                                            NULL);
                        if (INVALID_HANDLE_VALUE == hPipe)
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                        }
                    }
                }
                RpcExcept(1)
                {
                    // If we got here is because there was an error while call was made
                    // or when it was about to be made.
                    hResult = RpcExceptionCode();
                    if (RPC_S_SERVER_UNAVAILABLE == hResult)
                    {
                        hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
                    }
                    else
                    {
                        hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
                    }
                }
                RpcEndExcept
                if (!hResult)
                {

                    do
                    {
                        // Read from the pipe
                        if (!ReadFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesRead, NULL))
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                            if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                                HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                            {
                                // There was an error and we can't continue
                                TraceResult ("ModifyListDlgProc: Failed to read from the source", hResult);
                            }
                            else
                            {
                                // If the pipe was broken, it means the server finished writing
                                // to the it, so we are finished reading from it.
                                hResult = S_OK;
                            }
                        }
                        else
                        {
                            lvi.iItem = i;
                            lvi.iSubItem = 0;
                            lvi.pszText = abEntry.Info.MB.szFullName;
                            lvi.lParam = abEntry.Info.MB.dwObjID;
                            lvi.iItem = ListView_InsertItem (hListView, &lvi);
                            ListView_SetItemText (hListView, lvi.iItem, 1, abEntry.Info.MB.szMailboxName);
                            i++;
                            if (abEntry.Info.MB.dwObjID == dwUserID)
                            {
                                dwListUserIndex = abEntry.Info.MB.dwObjID;
                            }
                        }
                    } while (dwBytesRead && !hResult);
                    CloseHandle (hPipe);
                    if (0 != dwListUserIndex)
                    {
                        ListView_SetItemState (hListView,
                                               dwListUserIndex,
                                               LVIS_FOCUSED | LVIS_SELECTED,
                                               LVIS_FOCUSED | LVIS_SELECTED);
                    }
                }
                DestroyWindow (hWaitWnd);
                if (hResult)
                {
                    ErrorHandler (hDlg, hResult);
                    EndDialog (hDlg, -1);
                }
            }
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_ALL_USERS :
                    if (LBN_DBLCLK == HIWORD(wParam))
                    {
                        PostMessage (hDlg, WM_COMMAND, IDOK, 0);
                    }
                    break;
                case IDOK :
                    {
                        TCHAR achName[MAX_STRING_SIZE+1], achAlias[MAX_ALIAS_SIZE+1];
                        DWORD dwUserID;
                        HWND hListView= GetDlgItem (hDlg, IDC_ALL_USERS);

                        LV_ITEM lvi = { 0 };
                        lvi.mask = LVIF_PARAM | LVIF_TEXT;
                        lvi.iItem = ListView_GetNextItem (hListView, -1, LVNI_FOCUSED | LVNI_ALL);
                        lvi.iSubItem = 0;
                        lvi.pszText = achName;
                        lvi.cchTextMax = MAX_STRING_SIZE+1;
                        if (ListView_GetItem (hListView, &lvi))
                        {
                            dwUserID = lvi.lParam;
                            lvi.mask = LVIF_TEXT;
                            lvi.iSubItem = 1;
                            lvi.pszText = achAlias;
                            lvi.cchTextMax = MAX_ALIAS_SIZE+1;
                            if (FALSE == ListView_GetItem (hListView, &lvi))
                            {
                                wParam = IDCANCEL;
                            }
                        }
                        else
                        {
                            wParam = IDCANCEL;
                        }
                        if (IDOK == wParam)
                        {
                            if (SERVER_DISTRIBUTION_LIST == pObjInfo->Type)
                            {
                                pObjInfo->DL.dwOwnerID = dwUserID;
                            }
                            else
                            {
                                pObjInfo->MB.dwManagerID = dwUserID;
                            }
                            if (SERVER_DISTRIBUTION_LIST == pObjInfo->Type)
                            {
                                lstrcpy (pObjInfo->DL.szOwnerName, achName);
                            }
                            else
                            {
                                lstrcpy (pObjInfo->MB.szManagerName, achName);
                            }
                            if (SERVER_DISTRIBUTION_LIST == pObjInfo->Type)
                            {
                                lstrcpy (pObjInfo->DL.szOwnerAlias, achAlias);
                            }
                            else
                            {
                                lstrcpy (pObjInfo->MB.szManagerAlias, achAlias);
                            }
                        }
                    }
                    // Fall through
                case IDCANCEL :
                    ListView_SetImageList (GetDlgItem (hDlg, IDC_ALL_USERS), NULL, LVSIL_SMALL);
                    EndDialog (hDlg, (LOWORD(wParam) == IDOK ? TRUE : FALSE));
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    WaitDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK WaitDlgProc (HWND    hDlg,
                           UINT    message,
                           WPARAM  wParam,
                           LPARAM  lParam)
{
    if (WM_INITDIALOG == message)
    {
        CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
        Static_SetIcon(GetDlgItem (hDlg, IDC_STOPWATCH), GetAnimatedTimerCursor());
        return TRUE;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    ModifyListDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ModifyListDlgProc (HWND    hDlg,
                                 UINT    message,
                                 WPARAM  wParam,
                                 LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo;
    static HWND hAllUsers, hMembers;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                pObjInfo = (POBJECT_INFO)lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hWaitWnd = CreateDialog (ghInstance, MAKEINTRESOURCE(IDD_DIR_DOWNLOAD_WAIT), hDlg, WaitDlgProc);

                long lPipeNum;
                TCHAR achBuffer[128];
                HANDLE hPipe;
                DWORD dwBytesRead;
                AB_ENTRY_INFO abEntry = { 0 };
                HRESULT hResult = S_OK;
                int nTabStops[1];
                nTabStops[0] = 1000; // Off the screen

                hAllUsers = GetDlgItem (hDlg, IDC_ALL_USERS);
                hMembers = GetDlgItem (hDlg, IDC_MEMBERS);

                ListBox_SetTabStops (hAllUsers, sizeof(nTabStops)/sizeof(int), nTabStops);
                ListBox_SetTabStops (hMembers, sizeof(nTabStops)/sizeof(int), nTabStops);
                RpcTryExcept
                {
                    hResult = RemoteAdmGetGALDirectory (0, &lPipeNum);
                    if (!hResult)
                    {
                        // Construct the download pipe name
                        wsprintf (achBuffer, PIPE_NAME_FORMAT, g_szCurrentServer, lPipeNum);
                        // Create our endpoint and connect
                        hPipe = CreateFile (achBuffer,
                                            GENERIC_READ,
                                            0,
                                            NULL,
                                            OPEN_EXISTING,
                                            0,
                                            NULL);
                        if (INVALID_HANDLE_VALUE == hPipe)
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                        }
                    }
                }
                RpcExcept(1)
                {
                    // If we got here is because there was an error while call was made
                    // or when it was about to be made.
                    hResult = RpcExceptionCode();
                    if (RPC_S_SERVER_UNAVAILABLE == hResult)
                    {
                        hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
                    }
                    else
                    {
                        hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
                    }
                }
                RpcEndExcept
                if (!hResult)
                {
                    do
                    {

                        // Read from the pipe
                        if (!ReadFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesRead, NULL))
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                            if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                                HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                            {
                                // There was an error and we can't continue
                                TraceResult ("ModifyListDlgProc: Failed to read from the source", hResult);
                            }
                            else
                            {
                                // If the pipe was broken, it means the server finished writing
                                // to the it, so we are finished reading from it.
                                hResult = S_OK;
                            }
                        }
                        else
                        {
                            switch (abEntry.Type)
                            {
                                case GATEWAY_RECIPIENT :
                                case SERVER_USER_MAILBOX :
                                    if (abEntry.Info.MB.dwObjID != pObjInfo->MB.dwObjID)
                                    {
                                        wsprintf (achBuffer,
                                                  TEXT("%s\t%s\t%d\t%d"),
                                                  abEntry.Info.MB.szFullName,
                                                  abEntry.Info.MB.szMailboxName,
                                                  abEntry.Info.MB.dwObjID,
                                                  abEntry.Type);
                                        ListBox_AddString (hAllUsers, achBuffer);
                                    }
                                    break;
                                case SERVER_DISTRIBUTION_LIST :
                                    if (abEntry.Info.DL.dwObjID != pObjInfo->DL.dwObjID)
                                    {
                                        wsprintf (achBuffer,
                                                  TEXT("%s\t%s\t%d\t%d"),
                                                  abEntry.Info.DL.szDLFullName,
                                                  abEntry.Info.DL.szDLAlias,
                                                  abEntry.Info.DL.dwObjID,
                                                  abEntry.Type);
                                        ListBox_AddString (hAllUsers, achBuffer);
                                    }
                                    break;
                            }
                        }
                    } while (dwBytesRead && !hResult);
                    CloseHandle (hPipe);
                }
                if (!hResult)
                {
                    WINDS_AB_OBJTYPE Type;
                    DLM_LIST * pNode = pObjInfo->pMembers;
                    while (pNode && pNode->Info.szMemberAlias[0])
                    {
                        Type = (WINDS_AB_OBJTYPE)pNode->Info.dwMemberType;
                        wsprintf (achBuffer,
                                  TEXT("%s\t%s\t%d\t%d"),
                                  pNode->Info.szMemberName,
                                  pNode->Info.szMemberAlias,
                                  pNode->Info.dwMemberID,
                                  pNode->Info.dwMemberType);
                        ListBox_AddString (hMembers, achBuffer);
                        pNode = pNode->pNext;
                    }
                }
                DestroyWindow (hWaitWnd);
                if (hResult)
                {
                    ErrorHandler (hDlg, hResult);
                    EndDialog (hDlg, -1);
                }
            }
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_REMOVE_ALL :
                    pObjInfo->dwData = TRUE;
                    ListBox_ResetContent (hMembers);
                    break;

                case IDC_MEMBERS :
                    if (LBN_DBLCLK != HIWORD(wParam))
                    {
                        break;
                    }
                    // else fall through
                case IDC_REMOVE_USER :
                    {
                        int nIndex = ListBox_GetCurSel (hMembers);
                        if (LB_ERR != nIndex)
                        {
                            ListBox_DeleteString (hMembers, nIndex);
                            pObjInfo->dwData = TRUE;
                        }
                    }
                    break;

                case IDC_ALL_USERS :
                    if (LBN_DBLCLK != HIWORD(wParam))
                    {
                        break;
                    }
                    // else fall through
                case IDC_ADD_USER :
                    {
                        TCHAR szBuffer[128];
                        int iUsers = ListBox_GetSelCount (hAllUsers);
                        if (iUsers == LB_ERR) // Nothins is selected
                        {
                            break;
                        }
                        int *piList = (int *)HeapAlloc (ghMemHeap, 0, (DWORD)(iUsers * sizeof(int)));
                        if (NULL == piList) // Low system resources
                        {
                            ErrorHandler (hDlg, E_OUTOFMEMORY);
                            break;
                        }
                        ListBox_GetSelItems (hAllUsers, iUsers, piList);

                        for (int i=0; i<iUsers; i++)
                        {
                            ListBox_GetText (hAllUsers, piList[i], szBuffer);
                            if (LB_ERR == ListBox_FindStringExact (hMembers, -1, szBuffer))
                            {
                                ListBox_AddString (hMembers, szBuffer);
                            }
                            ListBox_SetSel (hMembers, FALSE, i);
                        }
                        pObjInfo->dwData = TRUE;
                        HeapFree (ghMemHeap, 0, piList);
                    }
                    break;

                case IDOK :
                    if (pObjInfo->dwData) // Means that something was changed
                    {
                        TCHAR achBuffer[128];
                        DLM_INFO Info = { 0 };
                        TCHAR * szSubStr;
                        FreeDLMList ((DLM_LIST*)pObjInfo->DL.pMembers);
                        DLM_LIST * pMemberList = (DLM_LIST*)pObjInfo->DL.pMembers;
                        int nCount = ListBox_GetCount (hMembers);
                        for (int i=0; i<nCount; i++)
                        {
                            if (LB_ERR == ListBox_GetText (hMembers, i, achBuffer))
                            {
                                continue;
                            }
                            strtok (achBuffer, "\t");
                            lstrcpy ((LPSTR)Info.szMemberName, achBuffer);

                            szSubStr = strtok (NULL, "\t");
                            lstrcpy ((LPSTR)Info.szMemberAlias, szSubStr);

                            szSubStr = strtok (NULL, "\t");
                            Info.dwMemberID = (DWORD)atol (szSubStr);

                            szSubStr = strtok (NULL, "\t");
                            Info.dwMemberType = (DWORD)atol (szSubStr);

                            if (0 == i)
                            {
                                pMemberList->Info = Info;
                            }
                            else
                            {
                                InsertNewDLMNode (Info, (DLM_XMIT_LIST *)pMemberList);
                            }
                        }
                    }
                    // Fall through
                case IDCANCEL :
                    EndDialog (hDlg, (LOWORD(wParam) == IDOK ? TRUE : FALSE));
                    return TRUE;
            }
    }
    return FALSE;
}

// End of file for ADMDLIST.CPP


ADMIN.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      ADMIN.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "ADMIN.H"
#include "COMMON.H"

extern "C"
{
    TCHAR g_szAppTitle[] = TEXT("WINDS Administrator");
    ITEM_TYPE g_LVItemsType = ITEM_UNDEFINED_TYPE;
    HINSTANCE ghInstance = NULL;
    HANDLE ghMemHeap = NULL;
    HANDLE ghCancelEvent;
    HWND ghTreeView, ghListView, ghWnd, ghStatusBar, ghToolBar;
    BOOL gfCancel = FALSE, gfTerminate = FALSE;
    HFONT ghBoldFont = NULL;
    CRITICAL_SECTION csCancel, csRemoteServer;
    TCHAR g_szCurrentServer[64] = { 0 };
    HIMAGELIST g_hImages = NULL, g_hIconImgs = NULL;
    LPVOID g_pctl3d = NULL;

    HRESULT WINAPI ServerChanged
                        (HWND                       hTreeView);

    LRESULT CALLBACK MainWndProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL WINAPI ShowConnectDialog
                        (HWND                       hOwnerWnd,
                         LPTSTR                     szServer,
                         BOOL *                     pfServerIsOnLine);
    BOOL WINAPI IsValidServerName
                        (LPTSTR                     szServerName);
    void WINAPI LoadSettingsFromRegistry
                        (LPTSTR                     szServer);
    void WINAPI SaveSettingsToRegistry
                        (LPTSTR                     szServer);
    void WINAPI HandleItemAction
                        (HWND                       hOwnerWnd,
                         int                        nItem,
                         WORD                       wAction);
    void WINAPI GetServerNameFromTitle
                        (LPTSTR                     szItemTitle,
                         LPTSTR *                   pszServerName);
    HRESULT WINAPI CancelThreadProc
                        ();
}

CNotifLink * gpLink = NULL;

///////////////////////////////////////////////////////////////////////////////
//    WinMain()
//
//    Parameters
//      { Refer to Win32 SDK documentation }
//
//    Purpose
//      This is the entry function. The application will start executing here.
//
//    Return Value
//      Return code of the application to the system.
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCmdLine, int nCmdLine)
{
    #define WINDOW_CLASSNAME        TEXT("WINDS_ADMIN_CLASS")
    // Check for other instances of this application running. If one is found,
    // bring it to the foreground
    ghWnd = FindWindow (WINDOW_CLASSNAME, NULL);
    if (NULL != ghWnd)
    {
        HWND hChildWnd;
        hChildWnd = GetLastActivePopup (ghWnd);
        if (hChildWnd != ghWnd)
        {
            BringWindowToTop (hChildWnd);
        }
        else
        {
            SetForegroundWindow (ghWnd);
        }
        return 0;
    }

    MSG msg;
    HACCEL hAccel;
    InitTraces (0);
    TraceMessage ("WINDS Admin is initializing");
    ghInstance = hInstance;
    ghMemHeap = GetProcessHeap();

    WNDCLASS wc = { 0 };
    // Register the class for this application
    wc.lpfnWndProc      = MainWndProc;
    wc.hInstance        = ghInstance;
    wc.hIcon            = LoadIcon (ghInstance, MAKEINTRESOURCE (IDI_MAINFRAME));
    wc.lpszMenuName     = MAKEINTRESOURCE (IDM_MENU);
    wc.lpszClassName    = WINDOW_CLASSNAME;
    wc.hbrBackground    = (HBRUSH)(COLOR_BTNFACE);
    if (!RegisterClass (&wc))
    {
        TraceMessage ("WinMain: Failed to register the application class");
        return -1;
    }
    gpLink = new CNotifLink;
    InitializeCriticalSection (&csRemoteServer);
    InitializeCriticalSection (&csCancel);

    ghCancelEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

    if (!g_pctl3d)
    {
        BOOL f;
        g_pctl3d = CTL3D_Initialize(ghInstance);
        CTL3D_AutoSubclass(g_pctl3d, ghInstance, &f);
    }

    InitCommonControls();

    ghWnd = CreateWindowEx (WS_EX_DLGMODALFRAME | WS_EX_APPWINDOW | WS_EX_CONTROLPARENT,
                            WINDOW_CLASSNAME,
                            TEXT("WINDS Administrator"),
                            WS_OVERLAPPED   | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU,
                            100,
                            100,
                            800,
                            500,
                            NULL,
                            NULL,
                            ghInstance,
                            NULL);
    if (NULL == ghWnd)
    {
        PrivateMessageBox (IDS_MSG_NO_INTERNAL_RESOURCES, NULL, 0);
        goto ErrorExit;
    }
    hAccel = LoadAccelerators (ghInstance, MAKEINTRESOURCE (IDA_ACCELTABLE));
    if (NULL == hAccel)
    {
        TraceMessage ("WinMain: Failed to load accelerator table");
    }

    ShowWindow (ghWnd, nCmdLine);
    UpdateWindow (ghWnd);

    while (GetMessage (&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator (msg.hwnd, hAccel, &msg))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }
ErrorExit:

    DeleteCriticalSection (&csCancel);
    DeleteCriticalSection (&csRemoteServer);
    delete gpLink;

    // Free any pending RPC binding resources.
    BindToServer (NULL);

    CloseHandle (ghCancelEvent);

    CTL3D_Uninitialize(g_pctl3d);
    g_pctl3d = NULL;
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//    MainWndProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//
LRESULT CALLBACK MainWndProc (HWND    hWnd,
                              UINT    message,
                              WPARAM  wParam,
                              LPARAM  lParam)
{
    static HBRUSH hbrFace = NULL;
    static HMENU hMBPopupMenu, hDLPopupMenu;
    static HCURSOR hCurrentCursor = NULL;
    static BOOL fRPCInProgress = FALSE;
    switch (message)
    {
        case WM_SETCURSOR :
            SetCursor (hCurrentCursor);
            return TRUE;

        case WM_CREATE :
            {
                hCurrentCursor = LoadCursor (NULL, IDC_ARROW);
                g_hIconImgs = ImageList_LoadBitmap (ghInstance, MAKEINTRESOURCE(IDB_ICONS), 32, 2, CLR_NONE);
                g_hImages = ImageList_LoadBitmap (ghInstance, MAKEINTRESOURCE(IDB_OBJECTS), 18, 2, CLR_NONE);
                ImageList_SetBkColor (g_hImages, GetSysColor(COLOR_WINDOW));

                if (!CreateListView (hWnd, g_hImages))
                {
                    return -1;
                }
                if (!CreateTreeView (hWnd, g_hImages))
                {
                    return -1;
                }
                if (!CreateToolBar (hWnd))
                {
                    return -1;
                }
                BOOL fServerIsOnline = FALSE;
                LoadSettingsFromRegistry (g_szCurrentServer);
                if (ShowConnectDialog (hWnd, g_szCurrentServer, &fServerIsOnline) &&
                    fServerIsOnline)
                {
                    AddServerToList (g_szCurrentServer, TRUE);
                    gpLink->StartNotification();
                }
                hbrFace = CreateSolidBrush (GetSysColor (COLOR_BTNFACE));
                ghBoldFont = CreateFont (-24,                    // Height
                                         0,                      // Width
                                         0,                      // Escapement
                                         0,                      // Orientation
                                         FW_BOLD,                // Font Weigth
                                         FALSE,                  // Italic?
                                         FALSE,                  // Underlined?
                                         FALSE,                  // Strike out?
                                         ANSI_CHARSET,           // Character set
                                         OUT_STROKE_PRECIS,      // Font precision
                                         CLIP_STROKE_PRECIS,     // Clip precision
                                         DRAFT_QUALITY,          // Font apperance quality
                                         FF_SWISS,               // Font pitch and family,
                                         TEXT("Arial"));         // Font face name
                CreateStatusBar (hWnd);

                hMBPopupMenu = LoadMenu (ghInstance, MAKEINTRESOURCE (IDM_MAILBOX_POPUP));
                hMBPopupMenu = GetSubMenu (hMBPopupMenu, 0);

                hDLPopupMenu = LoadMenu (ghInstance, MAKEINTRESOURCE (IDM_DISTLIST_POPUP));
                hDLPopupMenu = GetSubMenu (hDLPopupMenu, 0);

                DWORD dwThreadID;
                HANDLE hThread = CreateThread (NULL,
                                               0,
                                               (LPTHREAD_START_ROUTINE)CancelThreadProc,
                                               NULL,
                                               0,
                                               &dwThreadID);
                ASSERTMSG (hThread, "Failed to spawn cancel thread");
                TraceDebugger ("MainWndProc: RPC cancel thread spawned. ID: %X", dwThreadID);
                CloseHandle (hThread);
            }
            break;

        case WM_MEASUREITEM :
            {
                PMEASUREITEMSTRUCT pMIS = (PMEASUREITEMSTRUCT)lParam;
                TEXTMETRIC tm;
                HDC hdc;
                HFONT hFont;
                if (pMIS->CtlType == ODT_LISTVIEW)
                {
                    hFont = (HFONT)SendMessage (ghListView, WM_GETFONT, 0, 0L);
                    hdc = GetDC (ghListView);
                    SelectObject (hdc, hFont);
                    GetTextMetrics (hdc, &tm);

                    pMIS->itemHeight = tm.tmHeight + 1;

                    if (pMIS->itemHeight < (18 + 1))
                    {
                        pMIS->itemHeight = 18 + 1;
                    }
                    ReleaseDC (ghListView, hdc);
                }
            }
            return TRUE;

        case WM_DRAWITEM :
            {
                PDRAWITEMSTRUCT pDIS = (PDRAWITEMSTRUCT)lParam;
                if (pDIS->CtlType != ODT_LISTVIEW)
                {
                    return FALSE;
                }
                switch (pDIS->itemAction)
                {
                    case ODA_DRAWENTIRE:
                    case ODA_FOCUS:
                    case ODA_SELECT:
                        DrawListViewItem (pDIS);
                        break;
                }
            }
            return TRUE;

        case WM_PAINT :
            {
                PAINTSTRUCT ps;
                RECT rc;
                GetClientRect (hWnd, &rc);
                BeginPaint (hWnd, &ps);
                FillRect (ps.hdc, &rc, hbrFace);
                EndPaint (hWnd, &ps);
            }
            break;

        case WM_MENUCHAR :
            if (LOWORD(wParam) == 13 && 0 == HIWORD(wParam)) // 13 == RETURN
            {
                PostMessage (hWnd, WM_COMMAND, IDM_FILE_PROPERTIES, 0);
                return (BOOL)1;
            }
            break;

        case WM_VKEYTOITEM :
            if ((HWND)lParam == ghListView)
            {
                if (LOWORD(wParam) == VK_DELETE)
                {
                    PostMessage (hWnd, WM_COMMAND, IDM_FILE_DELETE, 0);
                }
                else
                {
                    if (LOWORD(wParam) == VK_INSERT)
                    {
                        PostMessage (hWnd, WM_COMMAND, IDM_FILE_NEW_MAILBOX, 0);
                    }
                }
            }
            return (BOOL)-1;

        case WM_SYSCOLORCHANGE :
            SendMessage (ghListView, message, wParam, lParam);
            SendMessage (ghTreeView, message, wParam, lParam);
            break;

        case WM_WINDS_RESET_NOTIF_LINK :
            gpLink->EndNotifications();
            gpLink->StartNotification();
            break;

        case WM_WINDS_UPDATEHEADERWIDTH :
            UpdateHeaderWidth ((HWND)wParam, (int)lParam);
            break;

        case WM_WINDS_REMOTE_CALL_IN_PROGRESS :
            fRPCInProgress = TRUE;
            SendMessage (ghToolBar, TB_SETSTATE, IDC_CANCEL_REMOTE_CALL, TBSTATE_ENABLED);
            break;

        case WM_WINDS_REMOTE_CALL_COMPLETED :
            hCurrentCursor = LoadCursor (NULL, IDC_ARROW);
            SetCursor (hCurrentCursor);
            SendMessage (ghStatusBar, SB_SETTEXT, 0, (LPARAM)TEXT("Ready"));
            if (wParam)
            {
                ErrorHandler (hWnd, (HRESULT)wParam);
            }
            fRPCInProgress = FALSE;
            SendMessage (ghToolBar, TB_SETSTATE, IDC_CANCEL_REMOTE_CALL, TBSTATE_INDETERMINATE);
            break;

        case WM_NOTIFY :
            switch (((LPNMHDR)lParam)->code)
            {
                case TTN_NEEDTEXT :
                    // Time to display a tool tip text box. Return the ID of
                    // the string in our resource table. The control will
                    // pick it up from there.
                    ((LPTOOLTIPTEXT)lParam)->hinst = ghInstance;
                    ((LPTOOLTIPTEXT)lParam)->lpszText = (LPTSTR)((LPTOOLTIPTEXT)lParam)->hdr.idFrom;
                    break;

                case HDN_ENDTRACK:
                    {
                        HD_NOTIFY *phdn = (HD_NOTIFY *)lParam;
                        PostMessage (hWnd,
                                     WM_WINDS_UPDATEHEADERWIDTH,
                                     (WPARAM)phdn->hdr.hwndFrom,
                                     (LPARAM)phdn->iItem);
                    }
                    break;

                case TVN_SELCHANGED :
                    {
                        DWORD dwThreadID;
                        HANDLE hThread = NULL;
                        NM_TREEVIEW * pNMTV = (NM_TREEVIEW *)lParam;
                        ITEM_TYPE tvItemType = (ITEM_TYPE)pNMTV->itemNew.lParam;
                        // Cancel the remote call in progress, if any
                        if (fRPCInProgress)
                        {
                            SetEvent (ghCancelEvent);
                        }
                        g_LVItemsType = ITEM_UNDEFINED_TYPE;

                        // If necessary, reset the RPC binding to the new server
                        if (S_OK != ServerChanged (ghTreeView))
                        {
                            break;
                        }

                        switch (tvItemType)
                        {
                            case ITEM_SERVER_USER_MAILBOXES :
                                g_LVItemsType = ITEM_SERVER_USER_MAILBOXES;
                                hThread = CreateThread (NULL,
                                                        0,
                                                        (LPTHREAD_START_ROUTINE)GetServerMailboxes,
                                                        (LPVOID)hWnd,
                                                        CREATE_SUSPENDED,
                                                        &dwThreadID);
                                break;
                            case ITEM_SERVER_DIST_LISTS :
                                g_LVItemsType = ITEM_SERVER_DIST_LISTS;
                                hThread = CreateThread (NULL,
                                                        0,
                                                        (LPTHREAD_START_ROUTINE)GetServerDistLists,
                                                        (LPVOID)hWnd,
                                                        CREATE_SUSPENDED,
                                                        &dwThreadID);
                                break;
                            default :
                                ListView_DeleteAllItems (ghListView);
                                break;
                        }
                        if (hThread)
                        {
                            hCurrentCursor = LoadCursor (NULL, IDC_APPSTARTING);
                            SetCursor (hCurrentCursor);
                            ResumeThread (hThread);
                            CloseHandle (hThread);
                            TraceDebugger ("MainWndProc: RPC worker thread spawned. ID: %X", dwThreadID);
                        }
                    }
                    break;

                case NM_RCLICK :
                case NM_DBLCLK :
                    if (((LPNMHDR)lParam)->idFrom == IDC_LISTVIEW)
                    {
                        if (NM_RCLICK == ((LPNMHDR)lParam)->code)
                        {
                            HMENU hMenu = NULL;
                            switch (g_LVItemsType)
                            {
                                case ITEM_SERVER_USER_MAILBOXES :
                                    hMenu = hMBPopupMenu;
                                    break;
                                case ITEM_SERVER_DIST_LISTS :
                                    hMenu = hDLPopupMenu;
                                    break;
                            }
                            POINT pt;
                            GetCursorPos (&pt);
                            TrackPopupMenu (hMenu,
                                            TPM_CENTERALIGN | TPM_LEFTBUTTON,
                                            pt.x,
                                            pt.y,
                                            0,
                                            hWnd,
                                            NULL);
                        }
                        else
                        {
                            PostMessage (hWnd, WM_COMMAND, IDM_POPUP_MB_PROPS, 0);
                        }
                    }
                    break;
            }
            break;

        case WM_COMMAND :
            // If a background thread is download something, we can't
            // allow many things to happen.
            if (fRPCInProgress &&
                IDM_ABOUT != LOWORD(wParam) &&
                IDC_CANCEL_REMOTE_CALL != LOWORD(wParam) &&
                IDM_FILE_EXIT != LOWORD(wParam))
            {
                PrivateMessageBox (IDS_MSG_WAIT_FOR_COMPLETION,
                                   hWnd,
                                   MB_OK |
                                   MB_ICONEXCLAMATION|
                                   MB_SETFOREGROUND);
                break;
            }
            switch (LOWORD(wParam))
            {
                case IDM_ABOUT :
                    DialogBox (ghInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, AboutDlgProc);
                    break;

                case IDM_SERVER_EXPORT :
                    DialogBox (ghInstance, MAKEINTRESOURCE(IDD_EXPORT), hWnd, ExportDlgProc);
                    break;

                case IDM_SERVER_IMPORT :
                    DialogBox (ghInstance, MAKEINTRESOURCE(IDD_IMPORT), hWnd, ImportDlgProc);
                    break;

                case IDC_CANCEL_REMOTE_CALL :
                    SetEvent (ghCancelEvent);
                    break;

                case IDM_FILE_PROPERTIES :
                case IDM_FILE_DELETE :
                case IDM_POPUP_MB_PROPS :
                case IDM_POPUP_MB_EMPTY :
                case IDM_POPUP_MB_HEADERS :
                case IDM_POPUP_MB_DELETE :
                    {
                        int nItem = ListView_GetNextItem (ghListView, -1, LVNI_FOCUSED | LVNI_ALL);
                        if (-1 == nItem)
                        {
                            break;
                        }
                        HandleItemAction (hWnd, nItem, LOWORD(wParam));
                    }
                    break;

                case IDM_FIND_MAILBOX :
                    break;

                case IDM_FILE_NEW_MAILBOX :
                    CreateNewMailbox (hWnd);
                    break;

                case IDM_FILE_NEW_DISTRIBUTIONLIST :
                    CreateNewDistList (hWnd);
                    break;

                case IDM_VIEW_DISCONNECT :
                    DialogBox (ghInstance, MAKEINTRESOURCE(IDD_REMOVE_SERVER), hWnd, RemoveServersDlgProc);
                    break;

                case IDM_VIEW_CONNECT :
                    {
                        TCHAR szNewServer[64] = { 0 };
                        BOOL fServerIsOnline = FALSE;
                        if (ShowConnectDialog (hWnd, szNewServer, &fServerIsOnline) &&
                            fServerIsOnline)
                        {
                            AddServerToList (szNewServer, (NULL == g_szCurrentServer[0] ? TRUE : FALSE));
                            if (NULL == g_szCurrentServer[0])
                            {
                                lstrcpy (g_szCurrentServer, szNewServer);
                            }
                        }
                    }
                    break;

                case IDM_FILE_EXIT :
                    // Terminate the application
                    PostMessage (hWnd, WM_CLOSE, 0, 0);
                    break;
            }
            break;

        case WM_QUERYENDSESSION :
        case WM_DESTROY :
            gfTerminate = TRUE;
            SetEvent (ghCancelEvent);

            DeleteObject (hbrFace);
            DeleteObject (ghBoldFont);
            ImageList_Destroy (g_hImages);
            ImageList_Destroy (g_hIconImgs);
            DestroyWindow (ghListView);
            DestroyWindow (ghTreeView);
            DestroyWindow (ghStatusBar);
            DestroyWindow (ghToolBar);
            DestroyMenu (hMBPopupMenu);
            DestroyMenu (hDLPopupMenu);
            PostQuitMessage (0);
            break;

        default :
            return DefWindowProc (hWnd, message, wParam, lParam);
    }
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//    HandleItemAction()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI HandleItemAction (HWND hOwnerWnd, int nItem, WORD wAction)
{
    LV_ITEM lvi = { 0 };
    lvi.mask = LVIF_PARAM;
    lvi.iItem = nItem;
    lvi.iSubItem = 0;
    if (FALSE == ListView_GetItem (ghListView, &lvi))
    {
        return;
    }
    switch (wAction)
    {
        case IDM_FILE_PROPERTIES :
        case IDM_POPUP_MB_PROPS :
            switch (g_LVItemsType)
            {
                case ITEM_SERVER_USER_MAILBOXES :
                    ShowMailboxProps (hOwnerWnd, lvi.lParam);
                    break;
                case ITEM_SERVER_DIST_LISTS :
                    ShowDistListProps (hOwnerWnd, lvi.lParam);
                    break;
                case ITEM_SERVER_FOREIGN_MAILBOXES :
                    TraceMessage ("Properties of Foreign Recipient");
                    break;
                case ITEM_SERVER_PUBLIC_FOLDERS :
                    TraceMessage ("Properties of public folder");
                    break;
                case ITEM_SERVER_GATEWAYS :
                    TraceMessage ("Properties of Server gateway");
                    break;
            }
            break;

        case IDM_POPUP_MB_EMPTY :
            if (ITEM_SERVER_USER_MAILBOXES == g_LVItemsType)
            {
                PurgeMailboxMessages (hOwnerWnd, lvi.lParam);
            }
            break;

        case IDM_FILE_DELETE :
        case IDM_POPUP_MB_DELETE :
            switch (g_LVItemsType)
            {
                case ITEM_SERVER_USER_MAILBOXES :
                case ITEM_SERVER_DIST_LISTS :
                    DeleteServerObject (hOwnerWnd, lvi.lParam);
                    break;
                case ITEM_SERVER_FOREIGN_MAILBOXES :
                    TraceMessage ("Delete Foreign Recipient");
                    break;
                case ITEM_SERVER_PUBLIC_FOLDERS :
                    TraceMessage ("Delete Public folder");
                    break;
            }
            break;

        case IDM_POPUP_MB_HEADERS :
            TraceMessage ("View mailbox headers");
            break;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    AboutDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//      Displays a dialog box with the copyright and program information
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK AboutDlgProc (HWND    hDlg,
                            UINT    message,
                            WPARAM  wParam,
                            LPARAM  lParam)
{
    if (WM_INITDIALOG == message)
    {
        CenterDialogBox (hDlg);
        return TRUE;
    }
    else
    {
        if (WM_COMMAND == message && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL))
        {
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    AddServerToList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI AddServerToList (LPTSTR szServer, BOOL fExpandRoot)
{
    HTREEITEM hItem, hRoot;
    TV_INSERTSTRUCT tvIns = { 0 };
    TV_ITEM tvItem =
    {
        TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM,
        NULL,
        0,
        0,
        NULL,
        0,
        IMG_SERVER,
        IMG_SERVER,
        0,
        NULL
    };
    TCHAR szName[128];

    // Add the root item
    wsprintf (szName, TEXT("WINDS Server: %s"), szServer);
    tvItem.pszText = szName,
    tvIns.hParent = NULL;
    tvItem.lParam = (LPARAM)ITEM_REMOTE_SERVER;
    tvIns.item    = tvItem;
    hRoot = TreeView_InsertItem (ghTreeView, &tvIns);

    // Add the "Object Directory" item
    tvItem.pszText = TEXT("Object Directory");
    tvItem.iImage = IMG_MAILABLE_OBJECTS_CLOSE;
    tvItem.iSelectedImage = IMG_MAILABLE_OBJECTS_OPEN;
    tvItem.lParam = (LPARAM)ITEM_OBJECT_DIRECTORY;
    tvIns.hParent = hRoot;
    tvIns.item    = tvItem;
    hItem = TreeView_InsertItem (ghTreeView, &tvIns);

    // Add the "User Mailboxes" item
    tvItem.pszText = TEXT("User Mailboxes");
    tvItem.iImage = IMG_USER;
    tvItem.iSelectedImage = IMG_USER;
    tvItem.lParam  = (LPARAM)ITEM_SERVER_USER_MAILBOXES;
    tvIns.hParent = hItem;
    tvIns.item    = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    // Add the "Server DLs" item
    tvItem.pszText = TEXT("Server Distribution Lists");
    tvItem.iImage = IMG_MAIL_GROUP;
    tvItem.iSelectedImage = IMG_MAIL_GROUP;
    tvItem.lParam  = (LPARAM)ITEM_SERVER_DIST_LISTS;
    tvIns.hParent = hItem;
    tvIns.item = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    // Add the "Foreign Entries" item
    tvItem.pszText = TEXT("Foreign Mailboxes");
    tvItem.iImage = IMG_FOREIGN_USER;
    tvItem.iSelectedImage = IMG_FOREIGN_USER;
    tvItem.lParam = (LPARAM)ITEM_SERVER_FOREIGN_MAILBOXES;
    tvItem.lParam = NULL;
    tvIns.hParent = hItem;
    tvIns.item = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    TreeView_Expand (ghTreeView, hItem, TVE_EXPAND);

    tvItem.pszText = TEXT("Public Folders");
    tvItem.iImage = IMG_SERVER_PUB_FOLDER;
    tvItem.iSelectedImage = IMG_SERVER_PUB_FOLDER;
    tvItem.lParam = (LPARAM)ITEM_SERVER_PUBLIC_FOLDERS;
    tvIns.hParent = hRoot;
    tvIns.item = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    tvItem.pszText = TEXT("Gateways");
    tvItem.iImage = IMG_SERVER_GATEWAY;
    tvItem.iSelectedImage = IMG_SERVER_GATEWAY;
    tvItem.lParam = (LPARAM)ITEM_SERVER_GATEWAYS;
    tvIns.hParent = hRoot;
    tvIns.item = tvItem;
    hItem = TreeView_InsertItem (ghTreeView, &tvIns);

    tvItem.pszText = TEXT("Exchange Gateway");
    tvItem.iImage = IMG_GATEWAY;
    tvItem.iSelectedImage = IMG_GATEWAY;
    tvItem.lParam = (LPARAM)ITEM_SERVER_GATEWAYS;
    tvIns.hParent = hItem;
    tvIns.item = tvItem;

TreeView_InsertItem (ghTreeView, &tvIns);

    tvItem.pszText = TEXT("SMTP Gateway");
    tvItem.iImage = IMG_GATEWAY;
    tvItem.iSelectedImage = IMG_GATEWAY;
    tvItem.lParam = (LPARAM)ITEM_SERVER_GATEWAYS;
    tvIns.hParent = hItem;
    tvIns.item = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    tvItem.pszText = TEXT("FAX Gateway");
    tvItem.iImage = IMG_GATEWAY;
    tvItem.iSelectedImage = IMG_GATEWAY;
    tvItem.lParam = (LPARAM)ITEM_SERVER_GATEWAYS;
    tvIns.hParent = hItem;
    tvIns.item = tvItem;
    TreeView_InsertItem (ghTreeView, &tvIns);

    TreeView_Expand (ghTreeView, hItem, TVE_EXPAND);

    if (fExpandRoot)
    {
        TreeView_Expand (ghTreeView, hRoot, TVE_EXPAND);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CancelThreadProc()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CancelThreadProc()
{
    while (TRUE)
    {
        WaitForSingleObject (ghCancelEvent, INFINITE);
        if (gfTerminate)
        {
            break; // Out of the WHILE() loop
        }
        EnterCriticalSection (&csCancel);
        gfCancel = TRUE;
        LeaveCriticalSection (&csCancel);
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    LoadSettingsFromRegistry()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
void WINAPI LoadSettingsFromRegistry (LPTSTR szServer)
{
    HKEY hAppKey;
    // Try to open the key in the registry, open it with READ rights,
    // and read the data into our strcture
    if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_CURRENT_USER,
                                       WINDS_ADMIN_REGISTRY_KEY,
                                       0,
                                       KEY_READ,
                                       &hAppKey))
    {
        ULONG cbSize = 64;
        DWORD dwError = RegQueryValueEx (hAppKey,
                                         SERVER_SUB_KEY,
                                         NULL,
                                         NULL,
                                         (LPBYTE)szServer,
                                         &cbSize);
        RegCloseKey (hAppKey);
        // If we failed to get the data from the registry we can't return
        // yet, we will proceed to initialize the options with default
        // values
        if (ERROR_SUCCESS == dwError)
        {
            return;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    SaveSettingsToRegistry()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
void WINAPI SaveSettingsToRegistry (LPTSTR szServer)
{
    HKEY hAppKey;
    DWORD dwDisposition;
    // Try to open the key in the registry, open it with READ/WRITE rights
    if (ERROR_SUCCESS != RegCreateKeyEx (HKEY_CURRENT_USER,
                                         WINDS_ADMIN_REGISTRY_KEY,
                                         0,
                                         NULL,
                                         REG_OPTION_NON_VOLATILE,
                                         KEY_WRITE,
                                         NULL,
                                         &hAppKey,
                                         &dwDisposition))
    {
        // If we fail to access the registry, we need to get out, we don't
        // have a key opened where to write
        TraceResult ("SaveSettingsToRegistry: Failed to open app key", HRESULT_FROM_WIN32(GetLastError()));
    }
    else
    {
        // With the opened key handle, create a sub entry where we save our
        // structure values. For this sample we just write the entire
        // structure as a big blob of data, which we read the save way.
        if (ERROR_SUCCESS != RegSetValueEx (hAppKey,
                                            SERVER_SUB_KEY,
                                            0,
                                            REG_SZ,
                                            (LPBYTE)szServer,
                                            (1+lstrlen(szServer))*sizeof(TCHAR)))
        {
            TraceResult ("SaveSettingsToRegistry: Failed to save registry values", HRESULT_FROM_WIN32(GetLastError()));
        }
        // Close the handle we have open and return the appropiate flag
        RegCloseKey (hAppKey);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ShowConnectDialog()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
BOOL WINAPI ShowConnectDialog (HWND hOwnerWnd, LPTSTR szServer, BOOL * pfServerIsOnLine)
{
    *pfServerIsOnLine = FALSE;
    BOOL bResult = FALSE;
    if (lstrlen(szServer))
    {
        goto ConnectToServer;
    }
ReEnterServerName:
    // Free any previous bindings
    BindToServer (NULL);
    SetCursor (LoadCursor (NULL, IDC_ARROW));
    if (TRUE == DialogBoxParam (ghInstance,
                                MAKEINTRESOURCE(IDD_SERVER_CONNECT),
                                hOwnerWnd,
                                ServerConnectDlgProc,
                                (LPARAM)szServer))
    {
ConnectToServer:
        SetCursor (LoadCursor (NULL, IDC_WAIT));
        HRESULT hResult = BindToServer (szServer);
        if (!hResult)
        {
            RpcTryExcept
            {
                hResult = RemoteAdmIsServerRunning();
                if (!hResult)
                {
                    *pfServerIsOnLine = TRUE;
                }
            }
            RpcExcept(1)
            {
                // If we got here is because there was an error while call was made
                // or when it was about to be made.
                hResult = RpcExceptionCode();
                if (RPC_S_SERVER_UNAVAILABLE == hResult)
                {
                    hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
                }
                else
                {
                    hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
                }
            }
            RpcEndExcept
            switch (hResult)
            {
                case S_OK :
                    break;
                case HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE) :
                    SetCursor (LoadCursor (NULL, IDC_ARROW));
                    if (IDYES == PrivateMessageBox (IDS_MSG_SERVER_NOT_FOUND,
                                                    hOwnerWnd,
                                                    MB_YESNO |
                                                    MB_ICONSTOP |
                                                    MB_SETFOREGROUND))
                    {
                        goto ReEnterServerName;
                    }
                    break;
                default :
                    TraceResult ("ShowConnectDialog", hResult);
                    goto ReEnterServerName;
                    break;
            }
            bResult = TRUE;
        }
    }
    SetCursor (LoadCursor (NULL, IDC_ARROW));
    BindToServer (g_szCurrentServer);
    return bResult;
}

///////////////////////////////////////////////////////////////////////////////
//    ServerConnectDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ServerConnectDlgProc (HWND    hDlg,
                                    UINT    message,
                                    WPARAM  wParam,
                                    LPARAM  lParam)
{
    static LPTSTR szServer;
    switch (message)
    {
        case WM_INITDIALOG :
            szServer = (LPTSTR)lParam;
            CenterDialogBox (hDlg);
            Edit_LimitText (GetDlgItem (hDlg, IDC_SERVER_NAME), 32);
            SetFocus (GetDlgItem (hDlg, IDC_SERVER_NAME));
            if (NULL == g_szCurrentServer[0])
            {
                Button_SetCheck (GetDlgItem (hDlg, IDC_SERVER_DEFAULT), BST_CHECKED);
            }
            Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                            ImageList_GetIcon (g_hIconImgs, ICON_CONNECT, ILD_NORMAL));
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDOK :
                    if (GetWindowText (GetDlgItem (hDlg, IDC_SERVER_NAME), szServer, 64))
                    {
                        if (FALSE == IsValidServerName (szServer))
                        {
                            PrivateMessageBox (IDS_MSG_INVALID_SERVER_NAME, hDlg, 0);
                            return TRUE;
                        }
                        BOOL fDuplicate = FALSE;
                        TCHAR szBuffer[128], *szConnectedServer;
                        TV_ITEM tvItem = { 0 };
                        tvItem.mask = TVIF_TEXT;
                        tvItem.pszText = szBuffer;
                        tvItem.cchTextMax = 128;
                        HTREEITEM hItem = TreeView_GetRoot (ghTreeView);
                        while (hItem)
                        {
                            tvItem.hItem = hItem;
                            if (TRUE == TreeView_GetItem (ghTreeView, &tvItem))
                            {
                                GetServerNameFromTitle (szBuffer, &szConnectedServer);
                                if (0 == lstrcmpi(szServer, szConnectedServer))
                                {
                                    fDuplicate = TRUE;
                                    break; // Out of the WHILE() loop
                                }
                            }
                            hItem = TreeView_GetNextItem (ghTreeView, hItem, TVGN_NEXT);
                        }
                        if (fDuplicate)
                        {
                            wParam = IDCANCEL;
                        }
                        else
                        {
                            if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_SERVER_DEFAULT)))
                            {
                                SaveSettingsToRegistry (szServer);
                            }
                        }
                    }
                    else
                    {
                        return TRUE;
                    }
                    // fall through
                case IDCANCEL :
                    EndDialog (hDlg, (LOWORD(wParam) == IDOK ? TRUE : FALSE));
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    IsValidServerName()
//
//    Parameters
//      pszServerName       Pointer to a string with the UNC name of a
//                          network or local server
//    Purpose
//      Check the string for a server name and determines if the name is a
//      valid UNC name for a network server
//
//    Return Value
//      TRUE if the server name is valid, FALSE otherwise.
//
BOOL WINAPI IsValidServerName (LPTSTR pszServerName)
{
    // Server name need to be at least "\\x"
    if (lstrlen(pszServerName) < 3)
    {
        return FALSE;
    }
    // The first two character in the string must be "\". This is standard
    // UNC (Universal Naming Convention) for server names
    if (pszServerName[0] != '\\' || pszServerName[1] != '\\')
    {
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoveServersDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK RemoveServersDlgProc (HWND    hDlg,
                                    UINT    message,
                                    WPARAM  wParam,
                                    LPARAM  lParam)
{
    switch (message)
    {
        case WM_INITDIALOG :
            {
                CenterDialogBox (hDlg);
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hListView = GetDlgItem (hDlg, IDC_SERVER_LIST);
                ListView_SetImageList (hListView, g_hImages, LVSIL_SMALL);
                RECT rc;
                GetClientRect (hListView, &rc);
                LV_COLUMN lvc = { 0 };
                lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
                lvc.fmt = LVCFMT_LEFT;
                lvc.pszText = TEXT("Server Name");
                lvc.cx = rc.right - rc.left;
                ListView_InsertColumn (hListView, 0, &lvc);

                TCHAR szBuffer[128];

                LV_ITEM lvi = { 0 };
                lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
                lvi.iImage = IMG_SERVER;

                TV_ITEM tvItem = { 0 };
                tvItem.mask = TVIF_TEXT;
                tvItem.pszText = szBuffer;
                tvItem.cchTextMax = 128;
                HTREEITEM hItem = TreeView_GetRoot (ghTreeView);
                while (hItem)
                {
                    tvItem.hItem = hItem;
                    if (TRUE == TreeView_GetItem (ghTreeView, &tvItem))
                    {
                        GetServerNameFromTitle (szBuffer, &lvi.pszText);
                        lvi.lParam = (LPARAM)hItem;
                        ListView_InsertItem (hListView, &lvi);
                        lvi.iItem++;
                    }
                    hItem = TreeView_GetNextItem (ghTreeView, hItem, TVGN_NEXT);
                }
            }
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDOK :
                    {
                        HWND hListView = GetDlgItem (hDlg, IDC_SERVER_LIST);
                        TCHAR szBuffer[128];
                        BOOL fSuccess;
                        LV_ITEM lvi = { 0 };
                        lvi.mask = LVIF_TEXT | LVIF_PARAM;
                        lvi.pszText = szBuffer;
                        lvi.cchTextMax = 128;
                        lvi.iItem = ListView_GetNextItem (hListView, -1, LVNI_SELECTED);
                        while (-1 != lvi.iItem)
                        {
                            if (TRUE == ListView_GetItem (hListView, &lvi))
                            {
                                fSuccess = TreeView_DeleteItem (ghTreeView, (HTREEITEM)lvi.lParam);
                                if (fSuccess && 0 == lstrcmpi (g_szCurrentServer, szBuffer))
                                {
                                    gpLink->EndNotifications();
                                    BindToServer (NULL);
                                    ZeroMemory (g_szCurrentServer, sizeof(g_szCurrentServer));
                                    ListView_DeleteAllItems (ghListView);
                                }
                            }
                            lvi.iItem = ListView_GetNextItem (hListView, lvi.iItem, LVNI_SELECTED);
                        }
                    }
                    // fall through
                case IDCANCEL :
                    ListView_SetImageList (GetDlgItem (hDlg, IDC_SERVER_LIST), NULL, LVSIL_SMALL);
                    EndDialog (hDlg, (LOWORD(wParam) == IDOK ? TRUE : FALSE));
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    GetServerNameFromTitle()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI GetServerNameFromTitle (LPTSTR szItemTitle, LPTSTR * pszServerName)
{
    LPTSTR pStr = &(szItemTitle[lstrlen (szItemTitle)]);
    while ('\\' != *pStr)
    {
        pStr--;
    }
    pStr--;
    *pszServerName = pStr;
}

///////////////////////////////////////////////////////////////////////////////
//    ServerChanged()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI ServerChanged (HWND hTreeView)
{
    HTREEITEM hParent, hItem = TreeView_GetSelection (hTreeView);
    do
    {
        hParent = TreeView_GetParent (hTreeView, hItem);
        if (hParent)
        {
            hItem = hParent;
        }
    } while (hParent);

    LPTSTR szServerName;
    HRESULT hResult = S_OK;;
    TCHAR szBuffer[128];
    TV_ITEM tvItem = { 0 };
    tvItem.mask = TVIF_TEXT;
    tvItem.pszText = szBuffer;
    tvItem.cchTextMax = 128;
    tvItem.hItem = hItem;
    if (TRUE == TreeView_GetItem (hTreeView, &tvItem))
    {
        GetServerNameFromTitle (szBuffer, &szServerName);
        if (szServerName && lstrcmpi (g_szCurrentServer, szServerName) && szServerName[0])
        {
            // Terminate the notifications with the current selected server
            gpLink->EndNotifications();
            // Release the RPC binding resources
            BindToServer (NULL);
            // Copy the server name to the global buffer
            lstrcpy (g_szCurrentServer, szServerName);
            // Set the RPC binding resources to point to the new server
            BindToServer (g_szCurrentServer);
            // Establish notification with the new server we are talking to
            gpLink->StartNotification();
        }
    }
    else
    {
        hResult = E_FAIL;
    }
    return hResult;
}

// End of file for ADMIN.CPP


ADMIN.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      WINDSADM.H
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _WINDSADM_H
#define _WINDSADM_H

#define STRICT
#include <WINDOWS.H>
#include <WINDOWSX.H>
#include <COMMDLG.H>
#include <COMMCTRL.H>
#include "WRAP3D.H"

#ifdef _DEBUG
#define ENABLE_DEBUG_OUTPUT     1
#endif // _DEBUG
#define TRACES_NO_MAPI          1

#include "TRACES.H"
#include "RESOURCE.H"

#include <RPC.H>
#include "WDSADM.H"    // Header file generated by the MIDL compiler
#include "WINDSGBL.H"

//  CTL3D wrapper
extern "C" LPVOID g_pctl3d;

#ifdef UNICODE
#error WINDS Administrator: Not ready for UNICODE yet... IrvingD (9/10/95)
#define RemoteAdmTerminateNotif     RemoteAdmTerminateNotifW
#define RemoteAdmValidateNotif      RemoteAdmValidateNotifW
#define RemoteAdmCreateMailbox      RemoteAdmCreateMailboxW
#define RemoteAdmGetMailboxProps    RemoteAdmGetMailboxPropsW
#define RemoteAdmSetMailboxProps    RemoteAdmSetMailboxPropsW
#define RemoteAdmCreateDistList     RemoteAdmCreateDistListW
#define RemoteAdmGetDLProps         RemoteAdmGetDLPropsW
#define RemoteAdmSetDLProps         RemoteAdmSetDLPropsW
#define ADMIN_DL_MEMBERS            ADMIN_DL_MEMBERS_W
#define DLM_LIST                    DLM_LIST_W
#define DLM_INFO                    DLM_INFO_W
#define ADM_MAILBOX_INFO            ADM_MAILBOX_INFO_W
#else
#define RemoteAdmTerminateNotif     RemoteAdmTerminateNotifA
#define RemoteAdmValidateNotif      RemoteAdmValidateNotifA
#define RemoteAdmCreateMailbox      RemoteAdmCreateMailboxA
#define RemoteAdmGetMailboxProps    RemoteAdmGetMailboxPropsA
#define RemoteAdmSetMailboxProps    RemoteAdmSetMailboxPropsA
#define RemoteAdmCreateDistList     RemoteAdmCreateDistListA
#define RemoteAdmGetDLProps         RemoteAdmGetDLPropsA
#define RemoteAdmSetDLProps         RemoteAdmSetDLPropsA
#define ADMIN_DL_MEMBERS            ADMIN_DL_MEMBERS_A
#define DLM_LIST                    DLM_LIST_A
#define DLM_INFO                    DLM_INFO_A
#define ADM_MAILBOX_INFO            ADM_MAILBOX_INFO_A
#endif // UNICODE

enum
{
    IMG_MAILABLE_OBJECTS_CLOSE  = 0,
    IMG_MAILABLE_OBJECTS_OPEN,
    IMG_PUBLIC_FOLDER,
    IMG_USER,
    IMG_FOREIGN_USER,
    IMG_MAIL_GROUP,
    IMG_DIST_LIST,
    IMG_SERVER_PUB_FOLDER,
    IMG_SERVER,
    IMG_SERVER_OBJECTS,
    IMG_SERVER_GATEWAY,
    IMG_GATEWAY,
    IMG_USER_MAILBOX,
    IMG_FOLDER_CLOSE,
    IMG_FOLDER_OPEN,
};

enum
{
    ICON_AUTO_REPLY     = 0,
    ICON_CONSTRUCT,
    ICON_DL_MEMBERS,
    ICON_LABELS,
    ICON_LARGE_ENVEL,
    ICON_MAILBOX,
    ICON_PHONE_HAND,
    ICON_KEY,
    ICON_PEOPLE,
    ICON_DL_OWNER,
    ICON_CONNECT,
    ICON_TIMEUP,
    ICON_WORLD,
    ICON_CLIP
};

#define WM_WINDS_UPDATEHEADERWIDTH          (WM_USER + 100)
#define WM_WINDS_REMOTE_CALL_IN_PROGRESS    (WM_USER + 101)
#define WM_WINDS_REMOTE_CALL_COMPLETED      (WM_USER + 102)
#define WM_WINDS_RESET_NOTIF_LINK           (WM_USER + 103)
#define WM_WINDS_IMPORT_START               (WM_USER + 104)
#define WM_WINDS_IMPORT_FINISHED            (WM_USER + 105)
#define WM_WINDS_EXPORT_START               (WM_USER + 106)
#define WM_WINDS_EXPORT_FINISHED            (WM_USER + 107)

typedef enum _ITEM_TYPE
{
    ITEM_UNDEFINED_TYPE,
    ITEM_SERVER_USER_MAILBOXES,
    ITEM_SERVER_DIST_LISTS,
    ITEM_SERVER_FOREIGN_MAILBOXES,
    ITEM_SERVER_PUBLIC_FOLDERS,
    ITEM_SERVER_GATEWAYS,
    ITEM_REMOTE_SERVER,
    ITEM_OBJECT_DIRECTORY
} ITEM_TYPE;

typedef struct _OBJECT_INFO_A
{
    DWORD               dwData;
    WINDS_AB_OBJTYPE    Type;
    MAILBOX_INFO_A      MB;
    DIST_LIST_INFO_A    DL;
    DLM_LIST_A *        pMembers;
} OBJECT_INFO_A, *POBJECT_INFO_A;

#ifdef UNICODE
#define OBJECT_INFO     OBJECT_INFO_W
#define POBJECT_INFO    POBJECT_INFO_W
#else
#define OBJECT_INFO     OBJECT_INFO_A
#define POBJECT_INFO    POBJECT_INFO_A
#endif // UNICODE

extern "C"
{
    extern ITEM_TYPE g_LVItemsType;
    extern HINSTANCE ghInstance;
    extern HANDLE ghMemHeap;
    extern HANDLE ghCancelEvent;
    extern HWND ghTreeView, ghListView, ghWnd, ghStatusBar, ghToolBar;
    extern BOOL gfCancel, gfRPCinProgress;
    extern HFONT ghBoldFont;
    extern CRITICAL_SECTION csCancel;
    extern CRITICAL_SECTION csRemoteServer;
    extern TCHAR g_szCurrentServer[];
    extern HIMAGELIST g_hImages, g_hIconImgs;

    void WINAPI AddServerToList
                        (LPTSTR                     szServer,
                         BOOL                       fExpandRoot);

    void WINAPI CreateNewMailbox
                        (HWND                       hOwnerWnd);
    void WINAPI CreateNewDistList
                        (HWND                       hOwnerWnd);
    void WINAPI CreateNewPubFolder
                        (HWND                       hOwnerWnd);
    void WINAPI ShowMailboxProps
                        (HWND                       hOwnerWnd,
                         DWORD                      dwObjID);
    void WINAPI ShowDistListProps
                        (HWND                       hOwnerWnd,
                         DWORD                      dwObjID);
    void WINAPI DeleteServerObject
                        (HWND                       hOwnerWnd,
                         DWORD                      dwObjID);
    void WINAPI PurgeMailboxMessages
                        (HWND                       hOwnerWnd,
                         DWORD                      dwObjID);
    void WINAPI ErrorHandler
                        (HWND                       hOwnerWnd,
                         HRESULT                    hError);
    void WINAPI DrawListViewItem
                        (LPDRAWITEMSTRUCT           pDIS);
    void WINAPI UpdateHeaderWidth
                        (HWND                       hWndHeader,
                         int                        iItem);

    BOOL WINAPI CreateListView
                        (HWND                       hOwnerWnd,
                         HIMAGELIST                 hImages);
    BOOL WINAPI CreateTreeView
                        (HWND                       hOwnerWnd,
                         HIMAGELIST                 hImages);
    BOOL WINAPI CreateStatusBar
                        (HWND                       hOwnerWnd);
    BOOL WINAPI CreateToolBar
                        (HWND                       hOwnerWnd);
    BOOL WINAPI IsObjAliasValid
                        (HWND                       hOwnerWnd,
                         LPTSTR                     szObjAlias);

    HRESULT WINAPI BindToServer
                        (LPTSTR                     szServer);
    HRESULT WINAPI GetServerMailboxes
                        (HWND                       hOwnerWnd);
    HRESULT WINAPI GetServerDistLists
                        (HWND                       hOwnerWnd);
    HRESULT WINAPI DownloadBulkInfo
                        (HWND                       hOwnerWnd,
                         HWND                       hListView,
                         ITEM_TYPE                  Type);

    HCURSOR WINAPI GetAnimatedTimerCursor
                        ();

    BOOL WINAPI BrowseFileName
                        (HWND                       hOwnerWnd,
                         LPTSTR                     szFileName,
                         BOOL                       fImport);
    BOOL CALLBACK AboutDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ServerConnectDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ExportDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ImportDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ImportProgressDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK ExportProgressDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK RemoveServersDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK SelectUserDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
};

inline BOOL WINAPI AbortRemoteCall()
{
    BOOL fState;
    EnterCriticalSection (&csCancel);
    fState = gfCancel;
    if (TRUE == gfCancel)
    {
        gfCancel = FALSE;
    }
    LeaveCriticalSection (&csCancel);
    return fState;
}

class CNotifLink;

extern CNotifLink * gpLink;

DWORD WINAPI MailslotListenThreadProc (CNotifLink * pLink);

class CNotifLink
{
public :
    STDMETHODIMP StartNotification
                        ();
    STDMETHODIMP EndNotifications
                        ();
    inline HANDLE WINAPI GetListenMailslot
                        () { return m_hMailSlot; }

public :
    CNotifLink();
    ~CNotifLink();

private :
    HANDLE      m_hMailSlot;
    ULONG       m_ulConnectionID;
    TCHAR       m_szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
};

#endif // _WINDSADM_H

// End of file for WINDSADM.H


ADMINUI.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      ADMINUI.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "ADMIN.H"

#define MAXCOLUMNS        7

extern "C"
{
    void WINAPI InitListViewColumns
                        ();
    BOOL WINAPI CalcStringEllipsis
                        (HDC                        hdc,
                         LPTSTR                     szString,
                         int                        cchMax,
                         UINT                       uColWidth);
    void WINAPI DrawItemColumn
                        (HDC                        hdc,
                         LPTSTR                     szText,
                         LPRECT                     prcClip);
};

UINT g_uColumns[MAXCOLUMNS] =
{
    100,
    125,
    175,
    75,
    175,
    80,
    80
};

void WINAPI InitListViewColumns()
{
    TCHAR achTemp[256];
    LV_COLUMN lvc;

    // Initialize the LV_COLUMN structure
    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    lvc.fmt = LVCFMT_LEFT;
    lvc.pszText = achTemp;

    // Add the columns
    for (int iCol = 0; iCol < MAXCOLUMNS; iCol++)
    {
        lvc.iSubItem = iCol;
        lvc.cx = g_uColumns[iCol];
        LoadString (ghInstance, IDS_COLUMN1 + iCol, achTemp, sizeof(achTemp));
        ListView_InsertColumn (ghListView, iCol, &lvc);
    }
}

#define TOP_CLEAR               30
#define V_BORDER_SIZE           5
#define H_BORDER_SIZE           3
#define CONTROL_HEIGHT          403
#define TREEVIEW_WIDTH          215
#define TREEVIEW_HEIGHT         CONTROL_HEIGHT
#define LISTVIEW_WIDTH          560
#define LISTVIEW_HEIGHT         CONTROL_HEIGHT

///////////////////////////////////////////////////////////////////////////////
//    CreateListView()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI CreateListView (HWND hOwnerWnd, HIMAGELIST hImages)
{
    RECT rc;
    GetClientRect (hOwnerWnd, &rc);
    ghListView = CreateWindowEx (0,
                                 WC_LISTVIEW,
                                 TEXT(""),
                                 WS_VISIBLE | WS_CHILD | WS_BORDER |
                                 LVS_REPORT | LVS_OWNERDRAWFIXED |
                                 LVS_SHOWSELALWAYS | LVS_SINGLESEL |
                                 LVS_SORTASCENDING,
                                 V_BORDER_SIZE + TREEVIEW_WIDTH + V_BORDER_SIZE,
                                 TOP_CLEAR,
                                 LISTVIEW_WIDTH,
                                 LISTVIEW_HEIGHT,
                                 hOwnerWnd,
                                 (HMENU)IDC_LISTVIEW,
                                 ghInstance,
                                 NULL);
    if (ghListView == NULL)
    {
        return FALSE;
    }
    ListView_SetImageList (ghListView, hImages, LVSIL_SMALL);
    InitListViewColumns();
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateTreeView()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI CreateTreeView (HWND hOwnerWnd, HIMAGELIST hImages)
{
    ghTreeView = CreateWindowEx (0,
                                 WC_TREEVIEW,
                                 TEXT(""),
                                 WS_BORDER | WS_CHILD | WS_VISIBLE |
                                 TVS_HASLINES | TVS_HASBUTTONS |
                                 TVS_LINESATROOT | TVS_SHOWSELALWAYS |
                                 TVS_DISABLEDRAGDROP,
                                 V_BORDER_SIZE,
                                 TOP_CLEAR,
                                 TREEVIEW_WIDTH,
                                 TREEVIEW_HEIGHT,
                                 hOwnerWnd,
                                 (HMENU)IDC_TREEVIEW,
                                 ghInstance,
                                 NULL);
    if (ghTreeView == NULL)
    {
        return FALSE;
    }
    TreeView_SetImageList (ghTreeView, hImages, TVSIL_NORMAL);
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateStatusBar()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI CreateStatusBar (HWND hOwnerWnd)
{
    ghStatusBar = CreateStatusWindow (WS_BORDER | WS_CHILD | WS_VISIBLE,
                                      TEXT("Ready"),
                                      hOwnerWnd,
                                      IDC_STATUSBAR);
    if (ghStatusBar == NULL)
    {
        return FALSE;
    }
    return TRUE;
}

// Define this enumaration for easier array indexing
enum
{
    SPACE_1,
    ADD_USER,
    ADD_DL,
    SPACE_2,
    SPACE_3,
    DELETE_OBJ,
    SPACE_4,
    SPACE_5,
    STOP_HAND,
    NUMBER_OF_TB_BUTTONS,
    NUMBER_OF_SEPARATORS = 5
};

///////////////////////////////////////////////////////////////////////////////
//    CreateToolBar()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI CreateToolBar (HWND hOwnerWnd)
{
    // Initialize each of the buttons with the appropiate values
    TBBUTTON tbButton[NUMBER_OF_TB_BUTTONS] = { 0 };

    tbButton[SPACE_1].fsStyle   = TBSTYLE_SEP;
    tbButton[SPACE_2].fsStyle   = TBSTYLE_SEP;
    tbButton[SPACE_3].fsStyle   = TBSTYLE_SEP;
    tbButton[SPACE_4].fsStyle   = TBSTYLE_SEP;
    tbButton[SPACE_5].fsStyle   = TBSTYLE_SEP;

    tbButton[ADD_USER].iBitmap   = 0;
    tbButton[ADD_USER].idCommand = IDM_FILE_NEW_MAILBOX;
    tbButton[ADD_USER].fsState   = TBSTATE_ENABLED;
    tbButton[ADD_USER].fsStyle   = TBSTYLE_BUTTON;

    tbButton[ADD_DL].iBitmap      = 1;
    tbButton[ADD_DL].idCommand    = IDM_FILE_NEW_DISTRIBUTIONLIST;
    tbButton[ADD_DL].fsState      = TBSTATE_ENABLED;
    tbButton[ADD_DL].fsStyle      = TBSTYLE_BUTTON;

    tbButton[DELETE_OBJ].iBitmap  = 2;
    tbButton[DELETE_OBJ].idCommand= IDM_FILE_DELETE;
    tbButton[DELETE_OBJ].fsState  = TBSTATE_ENABLED;
    tbButton[DELETE_OBJ].fsStyle  = TBSTYLE_BUTTON;

    tbButton[STOP_HAND].iBitmap  = 3;
    tbButton[STOP_HAND].idCommand= IDC_CANCEL_REMOTE_CALL;
    tbButton[STOP_HAND].fsState  = TBSTATE_INDETERMINATE;
    tbButton[STOP_HAND].fsStyle  = TBSTYLE_BUTTON;

    // Create the toolbar with the most commonly used functions
    ghToolBar = CreateToolbarEx (hOwnerWnd,
                                 WS_VISIBLE | WS_CHILD | WS_BORDER | TBSTYLE_TOOLTIPS,
                                 IDC_TOOLBAR,
                                 // The number of images is the number of
                                 // buttons minus the separators
                                 NUMBER_OF_TB_BUTTONS - NUMBER_OF_SEPARATORS,
                                 ghInstance,
                                 IDB_TOOLBAR,
                                 tbButton,
                                 NUMBER_OF_TB_BUTTONS,
                                 16, 16, 16, 32,
                                 sizeof(TBBUTTON));
    if (NULL == ghToolBar)
    {
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    UpdateHeaderWidth()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI UpdateHeaderWidth (HWND hWndHeader, int iItem)
{
    HD_ITEM hdi;
    // Get the column width from the control
    hdi.mask = HDI_WIDTH;
    if (Header_GetItem (hWndHeader, iItem, &hdi))
    {
        g_uColumns[iItem] = hdi.cxy;
    }
    // Force the ListView control to repaint
    InvalidateRect (ghListView, NULL, TRUE);
}

///////////////////////////////////////////////////////////////////////////////
//    DrawListViewItem()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI DrawListViewItem (LPDRAWITEMSTRUCT pDIS)
{
    TCHAR szBuffer[512];
    LV_ITEM lvi;
    int cxImage = 0, cyImage = 0;
    UINT uFirstColWidth;
    RECT rcClip;
    int iColumn = 1;
    UINT uiFlags = ILD_TRANSPARENT;

    // Get the item image to be displayed
    lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_TEXT;
    lvi.iItem = pDIS->itemID;
    lvi.iSubItem = 0;
    lvi.pszText = szBuffer;
    lvi.cchTextMax = 512;
    ListView_GetItem (pDIS->hwndItem, &lvi);

    // Check to see if this item is selected
    if (pDIS->itemState & ODS_SELECTED)
    {
        // Set the text background and foreground colors
        SetTextColor (pDIS->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
        SetBkColor (pDIS->hDC, GetSysColor(COLOR_HIGHLIGHT));

        // Also add the ILD_BLEND50 so the images come out selected
        uiFlags |= ILD_BLEND50;
    }
    else
    {
        // Set the text background and foreground colors to the standard window colors
        SetTextColor (pDIS->hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor (pDIS->hDC, GetSysColor(COLOR_WINDOW));
    }

    // Get the image list and draw the image
    ImageList_Draw (g_hImages,
                    lvi.iImage,
                    pDIS->hDC,
                    pDIS->rcItem.left,
                    pDIS->rcItem.top,
                    uiFlags);
    // Find out how big the image we just drew was
    ImageList_GetIconSize (g_hImages, &cxImage, &cyImage);

    // Calculate the width of the first column after the image width.  If
    // There was no image, then cxImage will be zero.
    uFirstColWidth = g_uColumns[0] - cxImage;

    // Set up the new clipping rect for the first column text and draw it
    rcClip.left = pDIS->rcItem.left + cxImage;
    rcClip.right = pDIS->rcItem.left + g_uColumns[0];
    rcClip.top = pDIS->rcItem.top;
    rcClip.bottom = pDIS->rcItem.bottom;

    DrawItemColumn (pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    lvi.mask = LVIF_TEXT;
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[1];
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[2];
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[3];
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[4];
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[5];
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + g_uColumns[6];
    lvi.iSubItem++;
    ListView_GetItem (pDIS->hwndItem, &lvi);
    DrawItemColumn(pDIS->hDC, szBuffer, &rcClip);
    szBuffer[0] = 0;

    // If we changed the colors for the selected item, undo it
    if (pDIS->itemState & ODS_SELECTED)
    {
        // Set the text background and foreground colors
        SetTextColor (pDIS->hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor (pDIS->hDC, GetSysColor(COLOR_WINDOW));
    }

    // If the item is focused, now draw a focus rect around the entire row
    if (pDIS->itemState & ODS_FOCUS)
    {
        // Adjust the left edge to exclude the image
        rcClip = pDIS->rcItem;
        rcClip.left += cxImage;

        // Draw the focus rect
        DrawFocusRect (pDIS->hDC, &rcClip);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DrawItemColumn()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI DrawItemColumn (HDC hdc, LPTSTR szText, LPRECT prcClip)
{
    TCHAR szString[256];
    // Check to see if the string fits in the clip rect.  If not, truncate
    // the string and add "...".
    lstrcpy(szString, szText);
    CalcStringEllipsis (hdc, szString, 256, prcClip->right - prcClip->left);
    ExtTextOut (hdc,
                prcClip->left + 2,
                prcClip->top + 1,
                ETO_CLIPPED | ETO_OPAQUE,
                prcClip,
                szString,
                lstrlen(szString),
                NULL);
}

///////////////////////////////////////////////////////////////////////////////
//    CalcStringEllipsis()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI CalcStringEllipsis (HDC     hdc,
                                LPTSTR  szString,
                                int     cchMax,
                                UINT    uColWidth)
{
    const TCHAR szEllipsis[] = TEXT("...");
    SIZE   sizeString;
    SIZE   sizeEllipsis;
    int    cbString;
    LPTSTR lpszTemp;
    BOOL   fSuccess = FALSE;

    // Adjust the column width to take into account the edges
    uColWidth -= 4;
    __try
    {
        // Allocate a string for us to work with.  This way we can mangle the
        // string and still preserve the return value
        lpszTemp = (LPTSTR)HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, cchMax);
        if (!lpszTemp)
        {
            __leave;
        }
        lstrcpy (lpszTemp, szString);

        // Get the width of the string in pixels
        cbString = lstrlen(lpszTemp);
        if (!GetTextExtentPoint32 (hdc, lpszTemp, cbString, &sizeString))
        {
            __leave;
        }

        // If the width of the string is greater than the column width shave
        // the string and add the ellipsis
        if ((ULONG)sizeString.cx > uColWidth)
        {
            if (!GetTextExtentPoint32 (hdc,
                                       szEllipsis,
                                       lstrlen(szEllipsis),
                                       &sizeEllipsis))
            {
                __leave;
            }

            while (cbString > 0)
            {
                lpszTemp[--cbString] = 0;
                if (!GetTextExtentPoint32 (hdc, lpszTemp, cbString, &sizeString))
                {
                    __leave;
                }

                if ((ULONG)(sizeString.cx + sizeEllipsis.cx) <= uColWidth)
                {
                    // The string with the ellipsis finally fits, now make sure
                    // there is enough room in the string for the ellipsis
                    if (cchMax >= (cbString + lstrlen(szEllipsis)))
                    {
                        // Concatenate the two strings and break out of the loop
                        lstrcat (lpszTemp, szEllipsis);
                        lstrcpy (szString, lpszTemp);
                        fSuccess = TRUE;
                        __leave;
                    }
                }
            }
        }
        else
        {
            // No need to do anything, everything fits great.
            fSuccess = TRUE;
        }
    }
    __finally
    {
        // Free the memory
        HeapFree (GetProcessHeap(), 0, (LPVOID)lpszTemp);
        return fSuccess;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ErrorHandler()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI ErrorHandler (HWND hOwnerWnd, HRESULT hError)
{
    LPTSTR szSystemMessage = NULL;
    TCHAR achBuffer[1024];
    DWORD dwSystemError;
    // If the facility is RPC remove it before calling FormatMessage(). That
    // Win32 API does not recognizes the facility.
    if (HRESULT_FACILITY(hError) == FACILITY_RPC)
    {
        dwSystemError = LOWORD(hError);
    }
    else
    {
        dwSystemError = hError;
    }
    // Get an error description from the system
    DWORD dwChars = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                                   NULL,
                                   dwSystemError,
                                   MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
                                   (LPTSTR)&szSystemMessage,
                                   0,
                                   NULL);
    // Check to see if FormatMessage() returned us an error
    if (0 == dwChars)
    {
        szSystemMessage = TEXT("Generic Error");
    }
    else
    {
        LPTSTR pStr = &szSystemMessage[lstrlen (szSystemMessage)];
        while ('\r' != *pStr)
        {
            pStr--;
        }
        *pStr = 0;
    }
    ASSERT (dwChars < 1024);
    // Add the error code to the string returned and then show it.
    switch (HRESULT_FACILITY(hError))
    {
        case FACILITY_WIN32 :
            wsprintf (achBuffer,
                      TEXT("%s (Win32 Error %d)"),
                      szSystemMessage,
                      LOWORD(hError));
            break;
        case FACILITY_STORAGE :
            wsprintf (achBuffer,
                      TEXT("%s (IStorage Error %08X)"),
                      szSystemMessage,
                      hError);
            break;
        case FACILITY_ITF :
            wsprintf (achBuffer,
                      TEXT("%s (Interface Error %08X)"),
                      szSystemMessage,
                      hError);
            break;
        case FACILITY_RPC :
            wsprintf (achBuffer,
                      TEXT("%s (RPC Error %d - %08X)"),
                      szSystemMessage,
                      LOWORD(hError),
                      hError);
            break;
        default :
            wsprintf (achBuffer,
                      TEXT("%s (Unknown Facility(%d) Error %d - %08X)"),
                      szSystemMessage,
                      HRESULT_FACILITY(hError),
                      hError,
                      hError);
            break;
    }
    if (dwChars)
    {
        LocalFree ((HLOCAL)szSystemMessage);
    }
    MessageBox (hOwnerWnd, achBuffer, TEXT("WINDS Administrator - Error"), MB_ICONSTOP | MB_OK);
}

// End of file for ADMINUI.CPP


ADMMBOX.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      ADMMBOX.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "ADMIN.H"
#include "COMMON.H"

extern "C"
{
    HRESULT WINAPI DisplayMailboxPropSheets
                        (HWND                       hOwnerWnd,
                         POBJECT_INFO               pObjInfo,
                         BOOL                       fCreate);
    BOOL CALLBACK MBProps1DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK MBProps2DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK MBProps3DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK MBProps4DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK MBProps5DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK MBProps6DlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);

    void WINAPI RemoveSpaces
                        (LPTSTR *                   ppStr,
                         UINT                       uMaxChars);
    HRESULT WINAPI ExportDirectoryToFile
                        (HWND                       hOwnerWnd);
    HRESULT WINAPI ImportDirectoryFromFile
                        (HWND                       hOwnerWnd);
};

///////////////////////////////////////////////////////////////////////////////
//    ExportDirectoryToFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI ExportDirectoryToFile (HWND hOwnerWnd)
{
    LPTSTR szExportFile = (LPTSTR)GetWindowLong (hOwnerWnd, GWL_USERDATA);
    HWND hLabel = GetDlgItem (hOwnerWnd, IDC_MAILBOX_LABEL);
    SetWindowText (hLabel, TEXT(""));
    HRESULT hResult = S_OK;
    long lPipeNum;
    TCHAR achPipeName[128];
    HANDLE hPipe;
    DWORD dwBytesRead, dwBytesWritten;
    AB_ENTRY_INFO abEntry = { 0 };
    char achCRLF[] = "\r\n", achComma[] = ", ", chQuote = '"';
    HANDLE hFile = CreateFile (szExportFile,
                               GENERIC_WRITE,
                               FILE_SHARE_READ,
                               NULL,
                               CREATE_ALWAYS,
                               FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                               NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        hResult = HRESULT_FROM_WIN32 (GetLastError());
        goto ExportFinished;
    }
    RpcTryExcept
    {
        hResult = RemoteAdmGetServerMailboxes (&lPipeNum);
        if (!hResult)
        {
            // Construct the download pipe name
            wsprintf (achPipeName, PIPE_NAME_FORMAT, g_szCurrentServer, lPipeNum);
            // Create our endpoint and connect
            hPipe = CreateFile (achPipeName,
                                GENERIC_READ,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (!hResult)
    {
        do
        {
            // Read from the pipe
            if (!ReadFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesRead, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                    HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                {
                    // There was an error and we can't continue
                    TraceResult ("ExportDirectoryToFile: Failed to read from the source", hResult);
                }
                else
                {
                    // If the pipe was broken, it means the server finished writing
                    // to the it, so we are finished reading from it.
                    hResult = S_OK;
                }
            }
            else
            {
                SetWindowText (hLabel, abEntry.Info.MB.szMailboxName);
                WriteFile (hFile, abEntry.Info.MB.szMailboxName, lstrlen (abEntry.Info.MB.szMailboxName), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szFullName, lstrlen (abEntry.Info.MB.szFullName), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szJobTitle, lstrlen (abEntry.Info.MB.szJobTitle), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szOffice, lstrlen (abEntry.Info.MB.szOffice), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szPhone, lstrlen (abEntry.Info.MB.szPhone), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szCompany, lstrlen (abEntry.Info.MB.szCompany), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szDepartment, lstrlen (abEntry.Info.MB.szDepartment), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szAltPhone, lstrlen (abEntry.Info.MB.szAltPhone), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szFax, lstrlen (abEntry.Info.MB.szFax), &dwBytesWritten, NULL);
                WriteFile (hFile, achComma, 2, &dwBytesWritten, NULL);
                WriteFile (hFile, abEntry.Info.MB.szComments, lstrlen (abEntry.Info.MB.szComments), &dwBytesWritten, NULL);

                WriteFile (hFile, achCRLF, 2, &dwBytesWritten, NULL);
            }
            if (AbortRemoteCall())
            {
                break; // Out of the DO-WHILE() loop
            }
        } while (dwBytesRead && !hResult);
        CloseHandle (hPipe);
    }
    CloseHandle (hFile);
    SetWindowText (hLabel, TEXT("Done!"));
ExportFinished:
    PostMessage (hOwnerWnd, WM_WINDS_EXPORT_FINISHED, hResult, 0);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    ImportDirectoryFromFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI ImportDirectoryFromFile (HWND hOwnerWnd)
{
    LPTSTR szImportFile = (LPTSTR)GetWindowLong (hOwnerWnd, GWL_USERDATA);
    HWND hLabel = GetDlgItem (hOwnerWnd, IDC_MAILBOX_LABEL);
    SetWindowText (hLabel, TEXT(""));
    HWND hListBox = GetDlgItem (hOwnerWnd, IDC_IMPORT_LIST);
    TCHAR szBuffer[256];

    MAILBOX_INFO MB = { 0 };
    DWORD i, dwBytesRead = 1;
    TCHAR achBuffer[4096], *szSubStr, achEmpty[] = TEXT("");
    HRESULT hResult = S_OK;
    HANDLE hFile = CreateFile (szImportFile,
                               GENERIC_READ,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                               NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        goto ImportFinished;
    }
    do
    {
        ZeroMemory (achBuffer, sizeof(achBuffer));
        ZeroMemory (&MB, sizeof(MAILBOX_INFO));
        for (i=0; i<4096 && !hResult && dwBytesRead; i++)
        {
            if (!ReadFile (hFile, &(achBuffer[i]), 1, &dwBytesRead, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("ImportDirectoryToFile: Failed to read from the source", hResult);
            }
            else
            {
                if (achBuffer[i] == '\n')
                {
                    break;  // Out of the FOR() loop
                }
            }
        }
        i = lstrlen(achBuffer);
        if (hResult || (!dwBytesRead && 0 == i))
        {
            break;
        }
        if (i>1 && '\n' == achBuffer[i - 1])
        {
            achBuffer[i - 1] = 0;
        }
        if (i>2 && '\r' == achBuffer[i - 2])
        {
            achBuffer[i - 2] = 0;
        }

        if (AbortRemoteCall())
        {
            break; // Out of the DO-WHILE() loop
        }

        // Get the ALIAS of the mailbox
        szSubStr = strtok (achBuffer, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_ALIAS_SIZE);
        SetWindowText (hLabel, szSubStr);
        if (!IsObjAliasValid (NULL, szSubStr))
        {
            wsprintf (szBuffer, TEXT("%s\tFAILED: Invalid object alias name"), szSubStr);
            ListBox_AddString (hListBox, szBuffer);
            continue;
        }
        lstrcpy (MB.szMailboxName, szSubStr);
        CharUpper (MB.szMailboxName);

        // Get the FULL NAME of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_STRING_SIZE);
        lstrcpy (MB.szFullName, szSubStr);

        // Get the JOB TITLE of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_STRING_SIZE);
        lstrcpy (MB.szJobTitle, szSubStr);

        // Get the OFFICE LOCATION of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_STRING_SIZE);
        lstrcpy (MB.szOffice, szSubStr);

        // Get the PHONE NUMBER of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_PHONE_SIZE);
        lstrcpy (MB.szPhone, szSubStr);

        // Get the COMPANY of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_STRING_SIZE);
        lstrcpy (MB.szCompany, szSubStr);

        // Get the DEPARTMENT of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_STRING_SIZE);
        lstrcpy (MB.szDepartment, szSubStr);

        // Get the ALTERNATE PHONE NUMBER of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_PHONE_SIZE);
        lstrcpy (MB.szAltPhone, szSubStr);

        // Get the FAX NUMBER of the mailbox owner
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_PHONE_SIZE);
        lstrcpy (MB.szFax, szSubStr);

        // Get the COMMENTS of the mailbox
        szSubStr = strtok (NULL, ",");
        if (NULL == szSubStr) { szSubStr = achEmpty; }
        RemoveSpaces (&szSubStr, MAX_COMMENT_SIZE);
        lstrcpy (MB.szComments, szSubStr);

        lstrcpy (MB.szPassword, TEXT("PASSWORD"));

        if (AbortRemoteCall())
        {
            break; // Out of the DO-WHILE() loop
        }
        RpcTryExcept
        {
            hResult = RemoteAdmCreateMailbox ((ADM_MAILBOX_INFO*)&MB);
        }
        RpcExcept(1)
        {
            // If we got here is because there was an error while call was made
            // or when it was about to be made.
            hResult = RpcExceptionCode();
            if (RPC_S_SERVER_UNAVAILABLE == hResult)
            {
                hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
            }
            else
            {
                hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
            }
        }
        RpcEndExcept
        if (hResult)
        {
            if (HRESULT_FROM_WIN32(ERROR_USER_EXISTS) == hResult)
            {
                wsprintf (szBuffer, TEXT("%s\tFAILED: The object already exists in the directory"), MB.szMailboxName);
            }
            else
            {
                wsprintf (szBuffer, TEXT("%s\tFAILED: Error %X"), MB.szMailboxName, hResult);
            }
        }
        else
        {
            lstrcpy (szBuffer, MB.szMailboxName);
            lstrcat (szBuffer, TEXT("\tSuccessfully Imported"));
        }
        ListBox_AddString (hListBox, szBuffer);

        if (hResult &&
            hResult != HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE) &&
            HRESULT_FACILITY(hResult) != FACILITY_RPC)
        {
            hResult = S_OK;
        }
        if (AbortRemoteCall())
        {
            break; // Out of the DO-WHILE() loop
        }
    } while (!hResult && dwBytesRead);
    CloseHandle (hFile);
    SetWindowText (hLabel, TEXT("Done!"));
ImportFinished:
    PostMessage (hOwnerWnd, WM_WINDS_IMPORT_FINISHED, hResult, 0);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoveSpaces()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI RemoveSpaces (LPTSTR * ppStr, UINT uMaxChars)
{
    LPTSTR pStr = *ppStr;
    // On an empty string, return. Nothing to do here
    if (0 == *pStr)
    {
        return;
    }
    while (' ' == *pStr || '\t' == *pStr)
    {
        pStr++;
    }
    *ppStr = pStr;
    // On an empty string or a single chracter stringm, return.
    int i = lstrlen (pStr);
    if (0 == *pStr || i < 2)
    {
        return;
    }
    pStr = &(pStr[i]);
    while (' ' == *pStr || '\t' == *pStr || NULL == *pStr)
    {
        *pStr = NULL;
        pStr--;
    }
    if (uMaxChars < (UINT)lstrlen (*ppStr))
    {
        (*ppStr)[uMaxChars] = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CreateNewMailbox()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI CreateNewMailbox (HWND hOwnerWnd)
{
    OBJECT_INFO ObjInfo = { 0 };
    lstrcpy (ObjInfo.MB.szPassword, TEXT("PASSWORD"));
    ObjInfo.Type = SERVER_USER_MAILBOX;
    HRESULT hResult = DisplayMailboxPropSheets (hOwnerWnd, &ObjInfo, TRUE);
    if (!hResult)
    {
        RpcTryExcept
        {
            hResult = RemoteAdmCreateMailbox ((ADM_MAILBOX_INFO*)&ObjInfo.MB);
        }
        RpcExcept(1)
        {
            // If we got here is because there was an error while call was made
            // or when it was about to be made.
            hResult = RpcExceptionCode();
            if (RPC_S_SERVER_UNAVAILABLE == hResult)
            {
                hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
            }
            else
            {
                hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
            }
        }
        RpcEndExcept
    }
    else
    {
        if (S_FALSE == hResult)
        {
            hResult = S_OK;
        }
    }
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DeleteServerObject()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI DeleteServerObject (HWND hOwnerWnd, DWORD dwObjID)
{
    if (IDNO == PrivateMessageBox (IDS_MSG_CONFIRM_OBJECT_DELETE,
                                   hOwnerWnd,
                                   MB_YESNO | MB_ICONEXCLAMATION))
    {
        return;
    }
    HRESULT hResult = S_OK;
    RpcTryExcept
    {
        hResult = RemoteAdmDeleteObject (dwObjID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ShowMailboxProps()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI ShowMailboxProps (HWND hOwnerWnd, DWORD dwObjID)
{
    OBJECT_INFO ObjInfo = { 0 };
    ObjInfo.MB.dwObjID = dwObjID;
    ObjInfo.Type = SERVER_USER_MAILBOX;
    HRESULT hResult = S_OK;
    DWORD dwObjType;
    RpcTryExcept
    {
        hResult = RemoteAdmGetMailboxProps (dwObjID, &dwObjType, (ADM_MAILBOX_INFO*)&ObjInfo.MB);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (!hResult)
    {
        hResult = DisplayMailboxPropSheets (hOwnerWnd, &ObjInfo, FALSE);
        if (S_OK == hResult)
        {
            RpcTryExcept
            {
                hResult = RemoteAdmSetMailboxProps ((ADM_MAILBOX_INFO*)&ObjInfo.MB);
            }
            RpcExcept(1)
            {
                // If we got here is because there was an error while call was made
                // or when it was about to be made.
                hResult = RpcExceptionCode();
                if (RPC_S_SERVER_UNAVAILABLE == hResult)
                {
                    hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
                }
                else
                {
                    hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
                }
            }
            RpcEndExcept
        }
        if (S_FALSE == hResult)
        {
            hResult = S_OK;
        }
    }
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DisplayMailboxPropSheets()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DisplayMailboxPropSheets (HWND           hOwnerWnd,
                                         POBJECT_INFO   pObjInfo,
                                         BOOL           fCreate)
{
    PROPSHEETHEADER psh = { 0 };
    PROPSHEETPAGE psp[6] = { 0 };
    TCHAR szHeaderTitle[64];

    OBJECT_INFO NewObjInfo = *pObjInfo;

    psp[0].dwSize      = sizeof(PROPSHEETPAGE);
    psp[0].dwFlags     = PSP_USETITLE;
    psp[0].hInstance   = ghInstance;
    psp[0].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS1);
    psp[0].pfnDlgProc  = MBProps1DlgProc;
    psp[0].pszTitle    = TEXT("General");
    psp[0].lParam      = (LPARAM)&NewObjInfo;

    psp[1].dwSize      = sizeof(PROPSHEETPAGE);
    psp[1].dwFlags     = PSP_USETITLE;
    psp[1].hInstance   = ghInstance;
    psp[1].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS2);
    psp[1].pfnDlgProc  = MBProps2DlgProc;
    psp[1].pszTitle    = TEXT("Phones");
    psp[1].lParam      = (LPARAM)&NewObjInfo;

    psp[2].dwSize      = sizeof(PROPSHEETPAGE);
    psp[2].dwFlags     = PSP_USETITLE;
    psp[2].hInstance   = ghInstance;
    psp[2].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS3);
    psp[2].pfnDlgProc  = MBProps3DlgProc;
    psp[2].pszTitle    = TEXT("Organization");
    psp[2].lParam      = (LPARAM)&NewObjInfo;

    psp[3].dwSize      = sizeof(PROPSHEETPAGE);
    psp[3].dwFlags     = PSP_USETITLE;
    psp[3].hInstance   = ghInstance;
    psp[3].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS4);
    psp[3].pfnDlgProc  = MBProps4DlgProc;
    psp[3].pszTitle    = TEXT("Account Security");
    psp[3].lParam      = (LPARAM)&NewObjInfo;

    psp[4].dwSize      = sizeof(PROPSHEETPAGE);
    psp[4].dwFlags     = PSP_USETITLE;
    psp[4].hInstance   = ghInstance;
    psp[4].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS5);
    psp[4].pfnDlgProc  = MBProps5DlgProc;
    psp[4].pszTitle    = TEXT("Delegate Access");
    psp[4].lParam      = (LPARAM)&NewObjInfo;

    psp[5].dwSize      = sizeof(PROPSHEETPAGE);
    psp[5].dwFlags     = PSP_USETITLE;
    psp[5].hInstance   = ghInstance;
    psp[5].pszTemplate = MAKEINTRESOURCE (IDD_MB_PROPS6);
    psp[5].pfnDlgProc  = MBProps6DlgProc;
    psp[5].pszTitle    = TEXT("Auto Reply");
    psp[5].lParam      = (LPARAM)&NewObjInfo;

    psh.dwSize     = sizeof(PROPSHEETHEADER);
    psh.dwFlags    = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
    psh.hwndParent = hOwnerWnd;
    psh.hInstance  = ghInstance;
    psh.nPages     = sizeof(psp)/sizeof(PROPSHEETPAGE);
    psh.ppsp       = (LPCPROPSHEETPAGE)&psp;
    if (fCreate)
    {
        psh.pszCaption  = TEXT("New Mailbox Properties");
    }
    else
    {
        wsprintf (szHeaderTitle, TEXT("Mailbox Properties for %s"), pObjInfo->MB.szMailboxName);
        psh.pszCaption  = szHeaderTitle;
    }

    // If the user hit OK and at least one of the properties changed,
    // we must change the properties on the server
ReEnterProperties:
    if (1 == PropertySheet (&psh) &&
        (fCreate ||
        (NewObjInfo.MB.dwFlags != pObjInfo->MB.dwFlags ||
         NewObjInfo.MB.dwManagerID != pObjInfo->MB.dwManagerID ||
         lstrcmpi (NewObjInfo.MB.szFullName,    pObjInfo->MB.szFullName) ||
         lstrcmpi (NewObjInfo.MB.szPassword,    pObjInfo->MB.szPassword) ||
         lstrcmpi (NewObjInfo.MB.szJobTitle,    pObjInfo->MB.szJobTitle) ||
         lstrcmpi (NewObjInfo.MB.szOffice,      pObjInfo->MB.szOffice) ||
         lstrcmpi (NewObjInfo.MB.szPhone,       pObjInfo->MB.szPhone) ||
         lstrcmpi (NewObjInfo.MB.szAltPhone,    pObjInfo->MB.szAltPhone) ||
         lstrcmpi (NewObjInfo.MB.szFax,         pObjInfo->MB.szFax) ||
         lstrcmpi (NewObjInfo.MB.szCompany,     pObjInfo->MB.szCompany) ||
         lstrcmpi (NewObjInfo.MB.szDepartment,  pObjInfo->MB.szDepartment) ||
         lstrcmpi (NewObjInfo.MB.szManagerAlias,pObjInfo->MB.szManagerAlias) ||
         lstrcmpi (NewObjInfo.MB.szManagerName,pObjInfo->MB.szManagerName) ||
         lstrcmpi (NewObjInfo.MB.szComments,  pObjInfo->MB.szComments))))
    {
        if (fCreate)
        {
            psh.nStartPage = 0;
            if (!IsObjAliasValid (hOwnerWnd, NewObjInfo.MB.szMailboxName))
            {
                goto ReEnterProperties;
            }
        }
        if (0 == lstrlen (NewObjInfo.MB.szFullName))
        {
            PrivateMessageBox (IDS_MSG_NO_USER_NAME, hOwnerWnd, 0);
            psh.nStartPage = 0;
            goto ReEnterProperties;
        }
        if (0 == lstrlen (NewObjInfo.MB.szPassword))
        {
            PrivateMessageBox (IDS_MSG_NO_PASSWORD, hOwnerWnd, 0);
            psh.nStartPage = 2;
            goto ReEnterProperties;
        }
        *pObjInfo = NewObjInfo;
        return S_OK;
    }
    return S_FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    IsObjAliasValid()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI IsObjAliasValid (HWND hOwnerWnd, LPTSTR szObjAlias)
{
    TCHAR achTempAlias[MAX_ALIAS_SIZE+1];
    lstrcpy (achTempAlias, szObjAlias);
    szObjAlias[0] = NULL;
    int iChars = lstrlen (achTempAlias);
    if (0 == iChars)
    {
        if (hOwnerWnd)
        {
            PrivateMessageBox (IDS_MSG_NO_OBJECT_ALIAS, hOwnerWnd, 0);
        }
        return FALSE;
    }
    if (!isalpha (achTempAlias[0]))
    {
        if (hOwnerWnd)
        {
            PrivateMessageBox (IDS_MSG_INVALID_OBJECT_ALIAS, hOwnerWnd, 0);
        }
        return FALSE;
    }

    for (int i=1; i<iChars; i++)
    {
        if (!isdigit(achTempAlias[i]) &&
            !isalpha(achTempAlias[i]) &&
            '-' != achTempAlias[i] &&
            '_' != achTempAlias[i])
        {
            if (hOwnerWnd)
            {
                PrivateMessageBox (IDS_MSG_INVALIS_CHARS_IN_ALIAS, hOwnerWnd, 0);
            }
            return FALSE;
        }
    }
    lstrcpy (szObjAlias, achTempAlias);
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps1DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps1DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    static HWND hAliasLabel;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_OBJECT_ALIAS);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                if (0 == pObjInfo->MB.dwObjID)
                {
                    EnableWindow (hCtl, TRUE);
                    Edit_LimitText (hCtl, MAX_ALIAS_SIZE);
                    hCtl = GetDlgItem (hDlg, IDC_OBJECT_ALIAS_LABEL);
                    EnableWindow (hCtl, TRUE);
                }

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_OWNER);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szFullName);

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_JOBTITLE);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szJobTitle);

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_OFFICE);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szOffice);

                hAliasLabel = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hAliasLabel, pObjInfo->MB.szMailboxName);

SetWindowFont (hAliasLabel, ghBoldFont, TRUE);

                if (OBJECT_DISABLED & pObjInfo->MB.dwFlags)
                {
                    Button_SetCheck (GetDlgItem (hDlg, IDC_DISABLE), BST_CHECKED);
                }
                if (HIDE_IN_DIR & pObjInfo->MB.dwFlags)
                {
                    Button_SetCheck (GetDlgItem (hDlg, IDC_HIDE), BST_CHECKED);
                }
                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_MAILBOX, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY :
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_DISABLE)))
                {
                    pObjInfo->MB.dwFlags |= OBJECT_DISABLED;
                }
                else
                {
                    pObjInfo->MB.dwFlags &= ~OBJECT_DISABLED;
                }
                if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_HIDE)))
                {
                    pObjInfo->MB.dwFlags |= HIDE_IN_DIR;
                }
                else
                {
                    pObjInfo->MB.dwFlags &= ~HIDE_IN_DIR;
                }
                GetWindowText (GetDlgItem (hDlg, IDC_OBJECT_ALIAS),
                               pObjInfo->MB.szMailboxName,
                               MAX_ALIAS_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_OWNER),
                               pObjInfo->MB.szFullName,
                               MAX_STRING_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_JOBTITLE),
                               pObjInfo->MB.szJobTitle,
                               MAX_STRING_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_OFFICE),
                               pObjInfo->MB.szOffice,
                               MAX_STRING_SIZE+1);

                return PSNRET_NOERROR;
            }
            break;

        case WM_COMMAND :
            if (HIWORD(wParam) == EN_CHANGE && LOWORD(wParam) == IDC_OBJECT_ALIAS)
            {
                GetWindowText ((HWND)lParam, pObjInfo->MB.szMailboxName, MAX_ALIAS_SIZE+1);
                SetWindowText (hAliasLabel, pObjInfo->MB.szMailboxName);
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps2DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps2DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;

                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_MAILBOX_PHONE);
                Edit_LimitText (hCtl, MAX_PHONE_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szPhone);

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_FAX);
                Edit_LimitText (hCtl, MAX_PHONE_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szFax);

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_ALTPHONE);
                Edit_LimitText (hCtl, MAX_PHONE_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szAltPhone);

                hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                SetWindowFont (hCtl, ghBoldFont, TRUE);
                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_PHONE_HAND, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY :
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_PHONE),
                               pObjInfo->MB.szPhone,
                               MAX_PHONE_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_FAX),
                               pObjInfo->MB.szFax,
                               MAX_PHONE_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_ALTPHONE),
                               pObjInfo->MB.szAltPhone,
                               MAX_PHONE_SIZE+1);
                return PSNRET_NOERROR;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps3DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps3DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;

                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_MAILBOX_PHONE);
                Edit_LimitText (hCtl, MAX_PHONE_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szPhone);

                hCtl = GetDlgItem (hDlg, IDC_COMPANY);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szCompany);

                hCtl = GetDlgItem (hDlg, IDC_DEPARTMENT);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szDepartment);

                hCtl = GetDlgItem (hDlg, IDC_MANAGER);
                Edit_LimitText (hCtl, MAX_STRING_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szManagerName);

                hCtl = GetDlgItem (hDlg, IDC_MAILBOX_COMMENTS);
                Edit_LimitText (hCtl, MAX_COMMENT_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szComments);

                hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                SetWindowFont (hCtl, ghBoldFont, TRUE);
                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_WORLD, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY :
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                GetWindowText (GetDlgItem (hDlg, IDC_COMPANY),
                               pObjInfo->MB.szCompany,
                               MAX_STRING_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_DEPARTMENT),
                               pObjInfo->MB.szDepartment,
                               MAX_STRING_SIZE+1);
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_COMMENTS),
                               pObjInfo->MB.szComments,
                               MAX_COMMENT_SIZE+1);
                return PSNRET_NOERROR;
            }
            break;

        case WM_COMMAND :
            if (LOWORD(wParam) == IDC_BROWSE)
            {
                if (TRUE == DialogBoxParam (ghInstance,
                                            MAKEINTRESOURCE(IDD_ONE_MAILBOX),
                                            hDlg,
                                            SelectUserDlgProc,
                                            (LPARAM)pObjInfo))
                {
                    SetWindowText (GetDlgItem (hDlg, IDC_MANAGER), pObjInfo->MB.szManagerName);
                }
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps4DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps4DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_MAILBOX_PASSWORD);
                Edit_LimitText (hCtl, MAX_PASSWORD_SIZE);
                SetWindowText (hCtl, pObjInfo->MB.szPassword);

                hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                SetWindowFont (hCtl, ghBoldFont, TRUE);
                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_KEY, ILD_NORMAL));
            }
            return TRUE;

        case WM_NOTIFY :
            if (PSN_APPLY == ((LPNMHDR)lParam)->code)
            {
                GetWindowText (GetDlgItem (hDlg, IDC_MAILBOX_PASSWORD),
                               pObjInfo->MB.szPassword,
                               MAX_STRING_SIZE+1);
                return PSNRET_NOERROR;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps5DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps5DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                hCtl = GetDlgItem (hDlg, IDC_NYI_LABEL);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_PEOPLE, ILD_NORMAL));
                Static_SetIcon (GetDlgItem (hDlg, IDC_CONSTRUCT_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_CONSTRUCT, ILD_NORMAL));
            }
            return TRUE;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    MBProps6DlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK MBProps6DlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static POBJECT_INFO pObjInfo = NULL;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // The pointer to the folder object came in the PROPSHEETPAGE
                // structure. The lParam of this message has a pointer to the
                // structure used to create this page. Get the OBJECT_INFO
                // pointer and save it.
                pObjInfo = (POBJECT_INFO)((PROPSHEETPAGE *)lParam)->lParam;
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);
                HWND hCtl = GetDlgItem (hDlg, IDC_ICON_TITLE);
                SetWindowText (hCtl, pObjInfo->MB.szMailboxName);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                hCtl = GetDlgItem (hDlg, IDC_NYI_LABEL);
                SetWindowFont (hCtl, ghBoldFont, TRUE);

                Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_AUTO_REPLY, ILD_NORMAL));
                Static_SetIcon (GetDlgItem (hDlg, IDC_CONSTRUCT_ICON),
                                ImageList_GetIcon (g_hIconImgs, ICON_CONSTRUCT, ILD_NORMAL));
            }
            return TRUE;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    GetServerMailboxes()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetServerMailboxes (HWND hOwnerWnd)
{
    return DownloadBulkInfo (hOwnerWnd, ghListView, ITEM_SERVER_USER_MAILBOXES);
}

///////////////////////////////////////////////////////////////////////////////
//    PurgeMailboxMessages()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI PurgeMailboxMessages (HWND hOwnerWnd, DWORD dwObjID)
{
    if (IDNO == PrivateMessageBox (IDS_MSG_CONFIRM_MB_MSGS_PURGE,
                                   hOwnerWnd,
                                   MB_YESNO | MB_ICONEXCLAMATION))
    {
        return;
    }
    HRESULT hResult = S_OK;
    RpcTryExcept
    {
        hResult = RemoteAdmEmptyMailbox (dwObjID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (hResult)
    {
        ErrorHandler (hOwnerWnd, hResult);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    BrowseFileName()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI BrowseFileName (HWND hOwnerWnd, LPTSTR szFileName, BOOL fImport)
{
    szFileName[0] = NULL;
    // Filter for files for the browse file dialog box
    static LPTSTR szFilter = TEXT("Comma Delimeted (*.CSV)\0*.CSV\0All Files (*.*)\0*.*\0");
    OPENFILENAME ofn = { 0 };
    ofn.lStructSize  = sizeof(OPENFILENAME);
    ofn.hwndOwner    = hOwnerWnd;
    ofn.hInstance    = ghInstance;
    ofn.lpstrFilter  = szFilter;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile    = szFileName;
    ofn.nMaxFile     = _MAX_PATH;
    ofn.lpstrTitle   = TEXT("WINDS Administrator - File Browse");
    ofn.lpstrDefExt  = TEXT("*.CSV");
    if (fImport)
    {
        ofn.Flags = OFN_HIDEREADONLY |
                    OFN_PATHMUSTEXIST |
                    OFN_FILEMUSTEXIST;
    }
    else
    {
        ofn.Flags = OFN_HIDEREADONLY |
                    OFN_OVERWRITEPROMPT |
                    OFN_CREATEPROMPT    |
                    OFN_NOREADONLYRETURN;
    }

    BOOL fReturn;
    if (fImport)
    {
        fReturn = GetOpenFileName (&ofn);
    }
    else
    {
        fReturn = GetSaveFileName (&ofn);
    }
    return fReturn;
}

///////////////////////////////////////////////////////////////////////////////
//    ExportDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ExportDlgProc (HWND    hDlg,
                             UINT    message,
                             WPARAM  wParam,
                             LPARAM  lParam)
{
    static BOOL fMailboxes, fGwayDirs;
    TCHAR szFile[_MAX_PATH];
    switch (message)
    {
        case WM_INITDIALOG :
            CenterDialogBox (hDlg);
            SetFocus (GetDlgItem (hDlg, IDC_BROWSE));
            Button_SetCheck (GetDlgItem (hDlg, IDC_MAILBOXES), BST_CHECKED);
            Button_SetCheck (GetDlgItem (hDlg, IDC_GW_DIRECTORIES), BST_CHECKED);
            fMailboxes = fGwayDirs = TRUE;
            Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                            ImageList_GetIcon (g_hIconImgs, ICON_LABELS, ILD_NORMAL));
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_MAILBOXES :
                case IDC_GW_DIRECTORIES :
                    if (HIWORD(wParam) == BN_CLICKED)
                    {
                        if (LOWORD(wParam) == IDC_MAILBOXES)
                        {
                            fMailboxes = !fMailboxes;
                        }
                        else
                        {
                            fGwayDirs = !fGwayDirs;
                        }
                        if (fGwayDirs || fMailboxes)
                        {
                            EnableWindow (GetDlgItem (hDlg, IDOK), TRUE);
                        }
                        else
                        {
                            EnableWindow (GetDlgItem (hDlg, IDOK), FALSE);
                        }
                    }
                    break;

                case IDC_BROWSE :
                    if (BrowseFileName (hDlg, szFile, FALSE))
                    {
                        SetWindowText (GetDlgItem (hDlg, IDC_FILENAME), szFile);
                    }
                    break;

                case IDOK :
                    if (!GetWindowText (GetDlgItem (hDlg, IDC_FILENAME), szFile, _MAX_PATH))
                    {
                        break;
                    }
                    DialogBoxParam (ghInstance,
                                    MAKEINTRESOURCE (IDD_EXPORT_STATE),
                                    hDlg,
                                    ExportProgressDlgProc,
                                    (LPARAM)szFile);
                    break;

                case IDCANCEL :
                    EndDialog (hDlg, TRUE);
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    ImportDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ImportDlgProc (HWND    hDlg,
                             UINT    message,
                             WPARAM  wParam,
                             LPARAM  lParam)
{
    TCHAR szFile[_MAX_PATH];
    switch (message)
    {
        case WM_INITDIALOG :
            CenterDialogBox (hDlg);
            PostMessage (hDlg, WM_COMMAND, IDC_BROWSE, 0);
            Static_SetIcon (GetDlgItem (hDlg, IDC_PAGE_ICON),
                            ImageList_GetIcon (g_hIconImgs, ICON_CLIP, ILD_NORMAL));
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_BROWSE :
                    if (BrowseFileName (hDlg, szFile, TRUE))
                    {
                        SetWindowText (GetDlgItem (hDlg, IDC_FILENAME), szFile);
                    }
                    break;

                case IDOK :
                    if (!GetWindowText (GetDlgItem (hDlg, IDC_FILENAME), szFile, _MAX_PATH))
                    {
                        break;
                    }
                    DialogBoxParam (ghInstance,
                                    MAKEINTRESOURCE (IDD_IMPORT_STATE),
                                    hDlg,
                                    ImportProgressDlgProc,
                                    (LPARAM)szFile);
                    break;

                case IDCANCEL :
                    EndDialog (hDlg, TRUE);
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    ImportProgressDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ImportProgressDlgProc (HWND    hDlg,
                                     UINT    message,
                                     WPARAM  wParam,
                                     LPARAM  lParam)
{
    switch (message)
    {
        case WM_INITDIALOG :
            {
                SetWindowLong (hDlg, GWL_USERDATA, (long)lParam);
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);

                Static_SetIcon (GetDlgItem (hDlg, IDC_STOPWATCH), GetAnimatedTimerCursor());

                SetWindowFont (GetDlgItem (hDlg, IDC_ICON_TITLE), ghBoldFont, FALSE);
                SetWindowFont (GetDlgItem (hDlg, IDC_MAILBOX_LABEL), ghBoldFont, FALSE);

                int nTabStops[1] = { 80 };
                ListBox_SetTabStops (GetDlgItem (hDlg, IDC_IMPORT_LIST), sizeof(nTabStops)/sizeof(int), nTabStops);

                PostMessage (hDlg, WM_WINDS_IMPORT_START, 0, 0);
            }
            return TRUE;

        case WM_WINDS_IMPORT_START :
            {
                DWORD dwThreadID;
                HANDLE hThread = CreateThread (NULL,
                                               0,
                                               (LPTHREAD_START_ROUTINE)ImportDirectoryFromFile,
                                               (LPVOID)hDlg,
                                               0,
                                               &dwThreadID);
                if (hThread)
                {
                    CloseHandle (hThread);
                }
                else
                {
                    PostMessage (hDlg,
                                 WM_WINDS_IMPORT_FINISHED,
                                 HRESULT_FROM_WIN32(GetLastError()),
                                 0);
                }
            }
            break;

        case WM_WINDS_IMPORT_FINISHED :
            EnableWindow (GetDlgItem (hDlg, IDOK), TRUE);
            EnableWindow (GetDlgItem (hDlg, IDCANCEL), FALSE);
            Static_SetIcon (GetDlgItem (hDlg, IDC_STOPWATCH),
                            ImageList_GetIcon (g_hIconImgs, ICON_TIMEUP, ILD_NORMAL));
            if (wParam)
            {
                ErrorHandler (hDlg, wParam);
            }
            break;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDCANCEL :
                    SetEvent (ghCancelEvent);
                    break;

                case IDOK :
                    EndDialog (hDlg, TRUE);
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    ExportProgressDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK ExportProgressDlgProc (HWND    hDlg,
                                     UINT    message,
                                     WPARAM  wParam,
                                     LPARAM  lParam)
{
    switch (message)
    {
        case WM_INITDIALOG :
            {
                SetWindowLong (hDlg, GWL_USERDATA, (long)lParam);
                CTL3D_Subclass(g_pctl3d, hDlg, CTL3D_ALL);

                Static_SetIcon (GetDlgItem (hDlg, IDC_STOPWATCH), GetAnimatedTimerCursor());

                SetWindowFont (GetDlgItem (hDlg, IDC_ICON_TITLE), ghBoldFont, FALSE);
                SetWindowFont (GetDlgItem (hDlg, IDC_MAILBOX_LABEL), ghBoldFont, FALSE);

                PostMessage (hDlg, WM_WINDS_EXPORT_START, 0, 0);
            }
            return TRUE;

        case WM_WINDS_EXPORT_START :
            {
                DWORD dwThreadID;
                HANDLE hThread = CreateThread (NULL,
                                               0,
                                               (LPTHREAD_START_ROUTINE)ExportDirectoryToFile,
                                               (LPVOID)hDlg,
                                               0,
                                               &dwThreadID);
                if (hThread)
                {
                    CloseHandle (hThread);
                }
                else
                {
                    PostMessage (hDlg,
                                 WM_WINDS_EXPORT_FINISHED,
                                 HRESULT_FROM_WIN32(GetLastError()),
                                 0);
                }
            }
            break;

        case WM_WINDS_EXPORT_FINISHED :
            TraceMessage ("Export Finished");
            EnableWindow (GetDlgItem (hDlg, IDOK), TRUE);
            EnableWindow (GetDlgItem (hDlg, IDCANCEL), FALSE);
            Static_SetIcon (GetDlgItem (hDlg, IDC_STOPWATCH),
                            ImageList_GetIcon (g_hIconImgs, ICON_TIMEUP, ILD_NORMAL));
            if (wParam)
            {
                ErrorHandler (hDlg, wParam);
            }
            break;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDCANCEL :
                    SetEvent (ghCancelEvent);
                    break;

                case IDOK :
                    EndDialog (hDlg, TRUE);
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    GetAnimatedTimerCursor()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HCURSOR WINAPI GetAnimatedTimerCursor()
{
    static HCURSOR hCursor = NULL;
    if (hCursor)
    {
        return hCursor;
    }
    HMODULE hModule;
    HRSRC hResource;
    DWORD dwBytes, dwSize;
    TCHAR szANIFile[MAX_PATH];
    GetTempPath (MAX_PATH, szANIFile);
    lstrcat (szANIFile, TEXT("__WINDS_TIMER__CURSOR.ANI"));
    HANDLE hResBits, hFile = CreateFile (szANIFile,
                                         GENERIC_WRITE,
                                         0,
                                         NULL,
                                         CREATE_ALWAYS,
                                         FILE_ATTRIBUTE_NORMAL,
                                         NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        TraceResult ("GetAnimatedTimerCursor: Failed to create temp file", HRESULT_FROM_WIN32(GetLastError()));
    }
    else
    {
        hModule = GetModuleHandle (NULL);
        hResource = FindResource (hModule, TEXT("#5000"), RT_RCDATA);
        hResBits = LoadResource (hModule, hResource);
        dwSize = SizeofResource (hModule, hResource);

WriteFile (hFile, (LPVOID)hResBits, dwSize, &dwBytes, NULL);
        CloseHandle (hFile);
        FreeResource (hResource);
        hCursor = LoadCursorFromFile (szANIFile);
        DeleteFile (szANIFile);
    }
    return hCursor;
}

// End of file for ADMMBOX.CPP


ADMNOTIF.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      ADMNOTIF.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "ADMIN.H"

// Remark this line to turn verbose tracing OFF
//#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)        TraceInfoMessage(a)
#else
#define InfoTrace(a)        0
#endif // DO_INFO_TRACES

///////////////////////////////////////////////////////////////////////////////
//    BindToServer()
//
//    Parameters
//      [IN]    szServer   Name of the remote server to which we will bind
//                         for RPC calls. If this pointer is NULL, we
//                         unbind from that server.
//
//    Purpose
//      This function makes the necessary calls to the RPC runtime library
//      to bind to the remote server so that we can start RPC function calls.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI BindToServer (LPTSTR szServer)
{
    static TCHAR szLastServer[64] = { 0 };
    static TCHAR * szStringBinding = NULL;

    // If a server name was given, we compare to the current server that we are
    // already bounded to. If we are connected return the call.
    if (szServer)
    {
        if (0 == lstrcmpi (szLastServer, szServer))
        {
            if (szStringBinding)
            {
                return 0;
            }
        }
        else
        {
            // Save the name of the server in the static buffer
            lstrcpy (szLastServer, szServer);
        }
    }

    RPC_STATUS status = 0;
    if (szStringBinding) // Unbind only if bound
    {
        status = RpcStringFree ((WINDS_RPC_STRING*)&szStringBinding);
        if (!status)
        {
            szStringBinding = NULL;
            status = RpcBindingFree (&hWINDSADM);  // hWINDSADM is defined in WDSADM.H and WDSADM.ACF
        }
        if (status)
        {
            status = MAKE_HRESULT(1, FACILITY_RPC, status);
            TraceResult ("BindToServer: Failed to free the binding", status);
        }
    }
    // If this is NULL, then we don't need to bind to anything.
    if (!szServer)
    {
        szLastServer[0] = 0;
        return S_OK;
    }
    if (!status)
    {
        status = RpcStringBindingCompose (NULL,
                                          (WINDS_RPC_STRING)WINDS_RPC_PROTOCOL,
                                          (WINDS_RPC_STRING)szServer,
                                          (WINDS_RPC_STRING)WINDS_ADMIN_RPC_ENDPOINT,
                                          NULL,
                                          (WINDS_RPC_STRING*)&szStringBinding);
        if (!status)
        {
            status = RpcBindingFromStringBinding ((WINDS_RPC_STRING)szStringBinding, &hWINDSADM); // hWINDSADM is defined in WDSADM.H and WDSADM.ACF
        }
    }
    if (status)
    {
        szLastServer[0] = 0;
        status = MAKE_HRESULT(1, FACILITY_RPC, status);
        TraceResult ("BindToServer: Failed to create remote server binding handle", status);
    }
    return status;
}

///////////////////////////////////////////////////////////////////////////////
//    CNotifLink::CNotifLink()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
CNotifLink::CNotifLink()
{
    m_hMailSlot = NULL;
    m_ulConnectionID = 0;
    ZeroMemory (m_szComputerName, sizeof(m_szComputerName));
    DWORD dwNameSize = MAX_COMPUTERNAME_LENGTH + 1;
    GetComputerName (m_szComputerName, &dwNameSize);
}

///////////////////////////////////////////////////////////////////////////////
//    CNotifLink::~CNotifLink()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
CNotifLink::~CNotifLink()
{
    EndNotifications();
}

///////////////////////////////////////////////////////////////////////////////
//    CNotifLink::EndNotifications()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
STDMETHODIMP CNotifLink::EndNotifications()
{
    if (0 == m_ulConnectionID)
    {
        ASSERT (NULL == m_hMailSlot);
        return S_OK;
    }
    CloseHandle (m_hMailSlot);
    m_hMailSlot = NULL;
    HRESULT hResult;
    RpcTryExcept
    {
        hResult = RemoteAdmTerminateNotif ((WINDS_RPC_STRING)m_szComputerName, m_ulConnectionID);
    }
    RpcExcept(1)
    {
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    m_ulConnectionID = 0;
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CNotifLink::StartNotification()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
STDMETHODIMP CNotifLink::StartNotification()
{
    if (0 != m_ulConnectionID)
    {
        return S_OK;
    }
    DWORD dwThreadID, dwFlags = WINDS_ADMINISTRATOR |
                                WINDS_NOTIF_ON_USER |
                                WINDS_NOTIF_ON_AB |
                                WINDS_NOTIF_ON_MS;
#ifdef UNICODE
    dwFlags |= WINDS_UNICODE;
#endif // UNICODE

    HANDLE hThread;
    TCHAR szMailslotName[64];
    HRESULT hResult = S_OK;
    RpcTryExcept
    {
        hResult = RemoteAdmValidateNotif ((WINDS_RPC_STRING)m_szComputerName,
                                          dwFlags,
                                          &m_ulConnectionID);
    }
    RpcExcept(1)
    {
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    if (!hResult)
    {
        wsprintf (szMailslotName,
                  CLIENT_MAILSLOT_SINK_NAME_FORMAT,
                  ADMIN_WINDS_NOTIFICATION_MAILSLOT,
                  m_ulConnectionID);
        m_hMailSlot = CreateMailslot (szMailslotName, 0, MAILSLOT_WAIT_FOREVER, NULL);
        if (INVALID_HANDLE_VALUE == m_hMailSlot)
        {
            hResult = HRESULT_FROM_WIN32(GetLastError());
        }
        else
        {
            hThread = CreateThread (NULL,
                                    0,
                                    (LPTHREAD_START_ROUTINE)MailslotListenThreadProc,
                                    (LPVOID)this,
                                    CREATE_SUSPENDED,
                                    &dwThreadID);
            if (hThread)
            {
                SetThreadPriority (hThread, THREAD_PRIORITY_LOWEST);
                ResumeThread (hThread);
                CloseHandle (hThread);
            }
            else
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                CloseHandle (m_hMailSlot);
            }
        }
        if (hResult)
        {
            EndNotifications();
        }
    }
    TraceResult ("CNotifLink::StartNotification", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    MailslotListenThreadProc()
//
//    Parameters
//
//    Purpose
//      This is function runs in a separate low priority thread listening for
//      notification that a WINDS server sends.. When a
//      notification of interest is received, an appropiate action is taken.
//      The notification arrive on a mailslot created by the logon object.
//
//    Return Value
//      S_OK always.
//
DWORD WINAPI MailslotListenThreadProc (CNotifLink * pLink)
{
    TCHAR szBuffer[256] = { 0 };
    LV_FINDINFO lfi = { 0 };
    LV_ITEM lvi = { 0 };

    HANDLE hMailslot = pLink->GetListenMailslot();
    DWORD dwRead;
    FILETIME ftLastNotifTime = { 0 };
    WINDS_NOTIF_EVENT LastEvent;
    WINDS_NOTIFICATION Notif;
    HRESULT hReadError;
    while (TRUE)
    {
        if (!ReadFile (hMailslot, &Notif, sizeof(WINDS_NOTIFICATION), &dwRead, NULL))
        {
            hReadError = HRESULT_FROM_WIN32 (GetLastError());
            if (HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) == hReadError ||
                HRESULT_FROM_WIN32(ERROR_HANDLE_EOF) == hReadError)
            {
                break; // out of the WHILE() loop and terminate the thread
            }
            TraceResult ("MailslotListenThreadProc: ReadFile", HRESULT_FROM_WIN32(GetLastError()));
            continue;
        }
        // Mailslot are received in each network protocol stack installed on the system.
        // Check the time stamp and event of the last notification received. If it
        // is the same, drop it and continue listen for new and different incoming
        // data on the mailslot.
        if (ftLastNotifTime.dwLowDateTime  == Notif.ftEventTime.dwLowDateTime &&
            ftLastNotifTime.dwHighDateTime == Notif.ftEventTime.dwHighDateTime &&
            LastEvent                      == Notif.Event)
        {
            continue;
        }
        // Save the last event information
        ftLastNotifTime = Notif.ftEventTime;
        LastEvent = Notif.Event;

        #ifdef DO_INFO_TRACES
        switch (Notif.Event)
        {
            case AB_USER_ADDED :
                InfoTrace ("AB_USER_ADDED notification received from the WINDS server");
                break;
            case AB_USER_MODIFIED :
                InfoTrace ("AB_USER_MODIFIED notification received from the WINDS server");
                break;
            case AB_USER_DELETED :
                InfoTrace ("AB_USER_DELETED notification received from the WINDS server");
                break;
            case AB_DL_ADDED :
                InfoTrace ("AB_DL_ADDED notification received from the WINDS server");
                break;
            case AB_DL_MODIFIED :
                InfoTrace ("AB_DL_MODIFIED notification received from the WINDS server");
                break;
            case AB_DL_DELETED :
                InfoTrace ("AB_DL_DELETED notification received from the WINDS server");
                break;
            case MS_MESSAGE_ADDED :
            case MS_MESSAGE_MODIFIED :
            case MS_MESSAGE_DELETED :
            case MS_FOLDER_ADDED :
            case MS_FOLDER_MODIFIED :
            case MS_FOLDER_DELETED :
                break;
            case SERVER_IS_SHUTTING_DOWN :
                InfoTrace ("SERVER_IS_SHUTTING_DOWN notification received from the WINDS server");
                break;
            case RESET_LINKS_WITH_SERVER :
                InfoTrace ("RESET_LINKS_WITH_SERVER notification received from the WINDS server");
                break;
            case SERVER_HAS_RESTARTED :
                InfoTrace ("SERVER_HAS_RESTARTED notification received from the WINDS server");
                break;
            case AB_GET_LOCAL_ABDATA_NOW :
                InfoTrace ("AB_GET_LOCAL_ABDATA_NOW notification received from the WINDS server");
                break;
            default :
                InfoTrace ("UNKNOWN notification received from the WINDS server");
                break;
        }
        #endif // DO_INFO_TRACES

        switch (Notif.Event)
        {
            case AB_USER_ADDED :
            case AB_DL_ADDED :
                {
                    if (AB_USER_ADDED == Notif.Event)
                    {
                        if (ITEM_SERVER_USER_MAILBOXES != g_LVItemsType)
                        {
                            break;
                        }
                    }
                    else
                    {
                        if (ITEM_SERVER_DIST_LISTS != g_LVItemsType)
                        {
                            break;
                        }
                    }

                    int iNewItem = ListView_GetItemCount (ghListView);
                    lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
                    lvi.iItem = iNewItem;
                    lvi.iSubItem = 0;
                    if (AB_USER_ADDED == Notif.Event)
                    {

                        lvi.pszText = Notif.Info.MB.szMailboxName;
                        lvi.lParam = Notif.Info.MB.dwObjID;
                        lvi.iImage = IMG_USER_MAILBOX;
                    }
                    else
                    {
                        lvi.pszText = Notif.Info.DL.szDLAlias;
                        lvi.lParam = Notif.Info.DL.dwObjID;
                        lvi.iImage = IMG_DIST_LIST;
                    }
                    lvi.iItem = ListView_InsertItem (ghListView, &lvi);
                    if (AB_USER_ADDED == Notif.Event)
                    {
                        ListView_SetItemText (ghListView, lvi.iItem, 1, Notif.Info.MB.szFullName);
                        ListView_SetItemText (ghListView, lvi.iItem, 2, Notif.Info.MB.szJobTitle);
                        ListView_SetItemText (ghListView, lvi.iItem, 3, Notif.Info.MB.szOffice);
                        ListView_SetItemText (ghListView, lvi.iItem, 4, Notif.Info.MB.szPhone);
                        ListView_SetItemText (ghListView, lvi.iItem, 5, Notif.Info.MB.szAltPhone);
                        ListView_SetItemText (ghListView, lvi.iItem, 6, Notif.Info.MB.szFax);
                    }
                    else
                    {
                        ListView_SetItemText (ghListView, lvi.iItem, 1, Notif.Info.DL.szDLFullName);
                    }
                }
                break;

            case AB_USER_DELETED :
            case AB_USER_MODIFIED :
            case AB_DL_DELETED :
            case AB_DL_MODIFIED :
                if (AB_USER_DELETED == Notif.Event || AB_USER_MODIFIED == Notif.Event)
                {
                    if (ITEM_SERVER_USER_MAILBOXES != g_LVItemsType)
                    {
                        break;
                    }
                }
                else
                {
                    if (ITEM_SERVER_DIST_LISTS != g_LVItemsType)
                    {
                        break;
                    }
                }
                lfi.flags = LVFI_STRING;
                if (AB_USER_MODIFIED == Notif.Event || AB_USER_DELETED == Notif.Event)
                {
                    lfi.psz = Notif.Info.MB.szMailboxName;
                }
                else
                {
                    lfi.psz = Notif.Info.DL.szDLAlias;
                }
                lvi.iItem = ListView_FindItem (ghListView, -1, &lfi);
                if (-1 == lvi.iItem)
                {
                    break;
                }
                if (AB_DL_DELETED == Notif.Event || AB_USER_DELETED == Notif.Event)
                {
                    ListView_DeleteItem (ghListView, lvi.iItem);
                }
                else
                {
                    lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
                    lvi.iSubItem = 0;
                    if (AB_USER_MODIFIED == Notif.Event)
                    {
                        lvi.pszText = Notif.Info.MB.szMailboxName;
                        lvi.lParam = Notif.Info.MB.dwObjID;
                        lvi.iImage = IMG_USER_MAILBOX;
                        ListView_SetItem (ghListView, &lvi);
                        ListView_SetItemText (ghListView, lvi.iItem, 1, Notif.Info.MB.szFullName);
                        ListView_SetItemText (ghListView, lvi.iItem, 2, Notif.Info.MB.szJobTitle);
                        ListView_SetItemText (ghListView, lvi.iItem, 3, Notif.Info.MB.szOffice);
                        ListView_SetItemText (ghListView, lvi.iItem, 4, Notif.Info.MB.szPhone);
                        ListView_SetItemText (ghListView, lvi.iItem, 5, Notif.Info.MB.szAltPhone);
                        ListView_SetItemText (ghListView, lvi.iItem, 6, Notif.Info.MB.szFax);
                    }
                    else
                    {
                        lvi.pszText = Notif.Info.DL.szDLAlias;
                        lvi.lParam = Notif.Info.DL.dwObjID;
                        lvi.iImage = IMG_DIST_LIST;
                        ListView_SetItem (ghListView, &lvi);
                        ListView_SetItemText (ghListView, lvi.iItem, 1, Notif.Info.DL.szDLFullName);
                    }
                }
                break;
            case RESET_LINKS_WITH_SERVER :
                PostMessage (ghWnd, WM_WINDS_RESET_NOTIF_LINK, 0, 0);
                break;
        }
    }
    return S_OK;
}

// End of file for ADMNOTIF.CPP


COMMON.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      COMMON.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include <WINDOWS.H>
#include "TRACES.H"
#include "WINDSGBL.H"
#include "WDSADM.H"
#include "COMMON.H"

// Remark this line to turn verbose tracing OFF
//#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)        TraceInfoMessage(a)
#else
#define InfoTrace(a)        0
#endif // DO_INFO_TRACES

extern "C"
{
    extern HINSTANCE ghInstance;
    extern TCHAR g_szAppTitle[];
}

///////////////////////////////////////////////////////////////////////////////
//    InsertNewDLMNodeA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI InsertNewDLMNodeA (DLM_INFO_A Info, DLM_XMIT_LIST_A * pList)
{
    DLM_LIST_A * pNewNode = (DLM_LIST_A *)midl_user_allocate (sizeof (DLM_LIST_A));
    if (NULL == pNewNode)
    {
        TraceMessage ("InsertNewDLMNodeA: Failed to allocate new node");
        return;
    }
    pNewNode->Info = Info;
    pNewNode->pNext = NULL;

    DLM_XMIT_LIST_A * pCurrent = pList;
    while (pCurrent->pNext)
    {
        pCurrent = pCurrent->pNext;
    }
    pCurrent->pNext = pNewNode;
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_A_to_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_A_to_xmit (DLM_XMIT_LIST_A __RPC_FAR * pList,
                                         DLM_ARRAY_A __RPC_FAR * __RPC_FAR * ppArray)
{
    InfoTrace ("DLM_XMIT_LIST_A_to_xmit: Function invoked");

    DLM_XMIT_LIST_A * pHead = pList;  // save pointer to start
    DLM_ARRAY_A * pArray;

    // Count the number of member in the list
    ULONG ulCount = 0;
    for (; pList != NULL; pList = pList->pNext)
    {
        ulCount++;
    }

    *ppArray = NULL;

    // Allocate the memory for the array using the MIDL memory allocator (which we implement)
    pArray = (DLM_ARRAY_A *)midl_user_allocate (sizeof(DLM_ARRAY_A) + (ulCount * sizeof(DLM_INFO_A)));
    if (NULL == pArray)
    {
        TraceMessage ("DLM_XMIT_LIST_A_to_xmit: Failed to allocate XMIT array");
        return;
    }
    pArray->dwCount = ulCount;

    // Copy the linked list of members into the allocated array.
    for (ulCount = 0, pList = pHead; pList != NULL; pList = pList->pNext)
    {
        pArray->pMembers[ulCount++] = pList->Info;
    }

    // Return the array allocated to the RPC stubs
    *ppArray = pArray;
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_A_from_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_A_from_xmit (DLM_ARRAY_A __RPC_FAR * pArray,
                                           DLM_XMIT_LIST_A __RPC_FAR * pList)
{
    InfoTrace ("DLM_XMIT_LIST_A_from_xmit: Function invoked");

    if (pArray->dwCount <= 0)
    {
        pList = NULL;
        return;
    }

    if (pList == NULL)
    {
        InsertNewDLMNodeA (pArray->pMembers[0], pList);
    }
    else
    {
        DLM_XMIT_LIST_A_free_inst (pList);
        pList->Info = pArray->pMembers[0];
        pList->pNext = NULL;
    }

    for (UINT i=1; i<pArray->dwCount; i++)
    {
        InsertNewDLMNodeA (pArray->pMembers[i], pList);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_A_free_inst()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_A_free_inst (DLM_XMIT_LIST_A __RPC_FAR * pList)
{
    InfoTrace ("DLM_XMIT_LIST_A_free_inst: Function invoked");
    // Don't delete the first node.
    DLM_XMIT_LIST_A * pNextNode, *pNode = pList->pNext;
    ZeroMemory (pList, sizeof(DLM_XMIT_LIST_A));

    while (pNode)
    {
        pNextNode = pNode->pNext;
        midl_user_free (pNode);
        pNode = pNextNode;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_A_free_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_A_free_xmit (DLM_ARRAY_A __RPC_FAR * pArray)
{
    InfoTrace ("DLM_XMIT_LIST_A_free_xmit: Function invoked");
    midl_user_free (pArray);
}

///////////////////////////////////////////////////////////////////////////////
//    InsertNewDLMNodeW()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI InsertNewDLMNodeW (DLM_INFO_W Info, DLM_XMIT_LIST_W * pList)
{
    DLM_LIST_W * pNewNode = (DLM_LIST_W *)midl_user_allocate (sizeof (DLM_LIST_W));
    if (NULL == pNewNode)
    {
        TraceMessage ("InsertNewDLMNodeW: Failed to allocate new node");
        return;
    }
    pNewNode->Info = Info;
    pNewNode->pNext = NULL;

    DLM_XMIT_LIST_W * pCurrent = pList;
    while (pCurrent->pNext)
    {
        pCurrent = pCurrent->pNext;
    }
    pCurrent->pNext = pNewNode;
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_W_to_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_W_to_xmit (DLM_XMIT_LIST_W __RPC_FAR * pList,
                                         DLM_ARRAY_W __RPC_FAR * __RPC_FAR * ppArray)
{
    InfoTrace ("DLM_XMIT_LIST_W_to_xmit: Function invoked");

    DLM_XMIT_LIST_W * pHead = pList;  // save pointer to start
    DLM_ARRAY_W * pArray;

    // Count the number of member in the list
    ULONG ulCount = 0;
    for (; pList != NULL; pList = pList->pNext)
    {
        ulCount++;
    }

    *ppArray = NULL;

    // Allocate the memory for the array using the MIDL memory allocator (which we implement)
    pArray = (DLM_ARRAY_W *)midl_user_allocate (sizeof(DLM_ARRAY_W) + (ulCount * sizeof(DLM_INFO_W)));
    if (NULL == pArray)
    {
        TraceMessage ("DLM_XMIT_LIST_W_to_xmit: Failed to allocate XMIT array");
        return;
    }
    pArray->dwCount = ulCount;

    // Copy the linked list of members into the allocated array.
    for (ulCount = 0, pList = pHead; pList != NULL; pList = pList->pNext)
    {
        pArray->pMembers[ulCount++] = pList->Info;
    }

    // Return the array allocated to the RPC stubs
    *ppArray = pArray;
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_W_from_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_W_from_xmit (DLM_ARRAY_W __RPC_FAR * pArray,
                                           DLM_XMIT_LIST_W __RPC_FAR * pList)
{
    InfoTrace ("DLM_XMIT_LIST_W_from_xmit: Function invoked");

    if (pArray->dwCount <= 0)
    {
        pList = NULL;
        return;
    }

    if (pList == NULL)
    {
        InsertNewDLMNodeW (pArray->pMembers[0], pList);
    }
    else
    {
        DLM_XMIT_LIST_W_free_inst (pList);
        pList->Info = pArray->pMembers[0];
        pList->pNext = NULL;
    }

    for (UINT i=1; i<pArray->dwCount; i++)
    {
        InsertNewDLMNodeW (pArray->pMembers[i], pList);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_W_free_inst()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_W_free_inst (DLM_XMIT_LIST_W __RPC_FAR * pList)
{
    InfoTrace ("DLM_XMIT_LIST_W_free_inst: Function invoked");
    // Don't delete the first node.
    DLM_XMIT_LIST_W * pNextNode, * pNode = pList->pNext;
    ZeroMemory (pList, sizeof(DLM_XMIT_LIST_W));

    while (pNode)
    {
        pNextNode = pNode->pNext;
        midl_user_free (pNode);
        pNode = pNextNode;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    DLM_XMIT_LIST_W_free_xmit()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void __RPC_USER DLM_XMIT_LIST_W_free_xmit (DLM_ARRAY_W __RPC_FAR * pArray)
{
    InfoTrace ("DLM_XMIT_LIST_W_free_xmit: Function invoked");
    midl_user_free (pArray);
}

///////////////////////////////////////////////////////////////////////////////
//    PrivateMessageBox()
//
//    Parameters
//      ids             String ID of a string in the resource string table
//      hOwnerWnd       Window who will own the Message Box
//      uFlags          Flags (options) for the message box
//
//    Purpose
//      This function display a message box with a string loaded from the
//      string table of this app. The ID of the string is passed in the ids
//      parameter. The dialog box is modal with respect to the window
//      identified in hOwnerWnd. The options to display the dialog box are
//      passed in uFlags
//
//    Return Value
//      ID of the button pressed in the message box dialog box
//
int WINAPI PrivateMessageBox (UINT ids, HWND hOwnerWnd, UINT uFlags)
{
    // Default response
    int nResponse = -1;
    // Get the string from the string table. The size of the buffer is the
    // maximum number of character allowed in the character buffer, without
    // the accounting for the NULL terminator
    TCHAR szBuffer[256];
    // Set the cursor to an arrow, in case it wasn't.
    HCURSOR hPrevCursor = SetCursor (LoadCursor (NULL, MAKEINTRESOURCE(IDC_ARROW)));
    if (LoadString (ghInstance, ids, szBuffer, 255))
    {
        if (0 == uFlags)
        {
            uFlags = MB_OK | MB_ICONSTOP;
        }
        uFlags |= MB_SETFOREGROUND;
        nResponse = MessageBox (hOwnerWnd, szBuffer, g_szAppTitle, uFlags);
    }
    // Before returning, reset the cursor to the previous state
    SetCursor (hPrevCursor);
    return nResponse;
}

///////////////////////////////////////////////////////////////////////////////
//    CenterDialogBox()
//
//    Parameters
//      hDlg    Handle to the dialog (or window) that we want to center.
//
//    Purpose
//      This function centers a window, vertically and horizontally,
//      with respect to the desktop (or screen)
//
//    Return Value
//      None.
//
void WINAPI CenterDialogBox (HWND hDlg)
{
    POINT   pt;
    RECT    r1, r2;
    int    iWidth, iHeight;
    GetWindowRect (GetDesktopWindow(), &r1);
    GetWindowRect (hDlg, &r2);
    iWidth = r2.right - r2.left;
    iHeight = r2.bottom - r2.top;
    pt.x = (r1.right - r1.left) / 2;
    pt.y = (r1.bottom - r1.top) / 2;
    pt.x = pt.x - (iWidth / 2);
    pt.y = pt.y - (iHeight / 2);
    MoveWindow (hDlg, pt.x, pt.y, iWidth, iHeight, FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//    midl_user_allocate()
//
//    Parameters
//      len     Size (in bytes) of the memory block to allocate for the
//              RPC object
//
//    Purpose
//      Allocates memory as needed by the RPC runtime library.
//      The stubs or runtime libraries may need to allocate memory.
//      By convention, they call a user-specified function named
//      midl_user_allocate.
//
//    Return Value
//      Pointer to a block of memory of len byte in size
//
void __RPC_FAR * __RPC_API midl_user_allocate (size_t len)
{
    LPVOID pVoid = HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, (DWORD)len);
    if (NULL == pVoid)
    {
        TraceMessage ("midl_user_allocate: Failed to allocated RPC memory block");
    }
    return pVoid;
}

///////////////////////////////////////////////////////////////////////////////
//    midl_user_free()
//
//    Parameters
//      ptr     Pointer to memory block to release
//
//    Purpose
//      Free memory as needed by the RPC runtime library.
//      The stubs or runtime libraries may need to free memory.
//      By convention, they call a user-specified function named
//      midl_user_free.
//
//    Return Value
//      None
//
void __RPC_API midl_user_free (void __RPC_FAR * ptr)
{
    HeapFree (GetProcessHeap(), 0, ptr);
}

// End of file for COMMON.CPP


COMMON.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      COMMON.H
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include <WINDOWS.H>
#include "TRACES.H"
#include "WDSADM.H"

extern "C"
{
    void WINAPI InsertNewDLMNodeA
                        (DLM_INFO_A                 Info,
                         DLM_XMIT_LIST_A *          pList);
    void WINAPI InsertNewDLMNodeW
                        (DLM_INFO_W                 Info,
                         DLM_XMIT_LIST_W *          pList);

    void __RPC_USER DLM_XMIT_LIST_A_free_inst
                        (DLM_XMIT_LIST_A __RPC_FAR * pList);
    void __RPC_USER DLM_XMIT_LIST_W_free_inst
                        (DLM_XMIT_LIST_W __RPC_FAR * pList);

    int WINAPI PrivateMessageBox
                        (UINT                       ids,
                         HWND                       hOwnerWnd,
                         UINT                       uFlags);
    void WINAPI CenterDialogBox
                        (HWND                       hDlg);
};

#ifdef UNICODE
#define InsertNewDLMNode        InsertNewDLMNodeW
#define FreeDLMList(a)          DLM_XMIT_LIST_W_free_inst((DLM_XMIT_LIST_W *)a)
#define DLM_XMIT_LIST           DLM_XMIT_LIST_W
#else
#define InsertNewDLMNode        InsertNewDLMNodeA
#define FreeDLMList(a)          DLM_XMIT_LIST_A_free_inst((DLM_XMIT_LIST_A *)a)
#define DLM_XMIT_LIST           DLM_XMIT_LIST_A
#endif // UNICODE

// End of file for COMMON.H


NOTIFY.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      NOTIFY.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <RPC.H>
#include "WINDS.H"     // Header file generated by the MIDL compiler

typedef struct _WINDS_NOTIF_CLIENT
{
    struct _WINDS_NOTIF_CLIENT *    pNext;
    TCHAR                           szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    TCHAR                           szObjectName[MAX_ALIAS_SIZE+1];
    DWORD                           dwFlags;
    DWORD                           dwNotifMask;
} WINDS_NOTIF_CLIENT, *PWINDS_NOTIF_CLIENT;

PWINDS_NOTIF_CLIENT gpNotifLinks = NULL;

///////////////////////////////////////////////////////////////////////////////
//    RemoteLogonMailBoxAndSetNotifA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteLogonMailBoxAndSetNotifA (unsigned char *    szMailbox,
                                     unsigned char *    szPassword,
                                     unsigned char *    szFullName,
                                     unsigned long *    pdwMailboxID,
                                     unsigned char *    szComputerName,
                                     unsigned long      ulNotifMask,
                                     unsigned long *    pulConnectionID)
{
    long lResult = RemoteLogonMailBoxA (szMailbox, szPassword, szFullName, pdwMailboxID);
    if (lResult)
    {
        return lResult;
    }
    RemoteValidateNotifA (szComputerName, szMailbox, ulNotifMask, pulConnectionID);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteValidateNotifA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteValidateNotifA (unsigned char *  szComputerName,
                           unsigned char *  szMailbox,
                           unsigned long    ulNotifMask,
                           unsigned long *  pulConnectionID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    PWINDS_NOTIF_CLIENT pNode, pNewNode = (PWINDS_NOTIF_CLIENT)HeapAlloc (ghHeap,
                                                                          HEAP_ZERO_MEMORY,
                                                                          sizeof(WINDS_NOTIF_CLIENT));
    if (pNewNode)
    {
        pNewNode->dwNotifMask = ulNotifMask;
        lstrcpy (pNewNode->szComputerName, (LPSTR)szComputerName);
        lstrcpy (pNewNode->szObjectName, (LPSTR)szMailbox);

        EnterCriticalSection (&g_csNotifLinks);
        if (gpNotifLinks)
        {
            pNode = gpNotifLinks;
            while (pNode)
            {
                if (NULL == pNode->pNext)
                {
                    pNode->pNext = pNewNode;
                    break; // Out of the WHILE() loop
                }
                pNode = pNode->pNext;
            }
        }
        else
        {
            gpNotifLinks = pNewNode;
        }
        LeaveCriticalSection (&g_csNotifLinks);

        *pulConnectionID = (unsigned long)pNewNode;
    }
    TraceMessageIf ("RemoteValidateNotifA: Failed to allocated new node", 0 == *pulConnectionID);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteTerminateNotifA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteTerminateNotifA (unsigned char * szComputerName,
                            unsigned long ulConnectionID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    long cbSize = lstrlen ((LPSTR)szComputerName);
    EnterCriticalSection (&g_csNotifLinks);
    PWINDS_NOTIF_CLIENT pNext, pPrev = NULL, pNode = gpNotifLinks;
    while (pNode)
    {
        if (ulConnectionID == (unsigned long)pNode && (0 == memcmp (pNode->szComputerName, szComputerName, cbSize)))
        {
            // If the node is the head of the list, make the head point to the next node
            if (gpNotifLinks == pNode)
            {
                gpNotifLinks = gpNotifLinks->pNext;
            }
            // Save the next pointer so that we may delete the current node
            pNext = pNode->pNext;
            HeapFree (ghHeap, 0, pNode);
            // If we have a previous node, we are doing a delete in the middle of a single link list
            if (pPrev)
            {
                pPrev->pNext = pNext;
            }
            break; // Out of the WHILE() loop
        }
        else
        {
            // Don't delete this node, save is as the "previous" node in the linear list
            pPrev = pNode;
            pNode = pNode->pNext;
        }
    }
    LeaveCriticalSection (&g_csNotifLinks);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoveAllNotifLinks()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
void WINAPI RemoveAllNotifLinks()
{
    EnterCriticalSection (&g_csNotifLinks);
    PWINDS_NOTIF_CLIENT pNode = gpNotifLinks;
    while (gpNotifLinks)
    {
        pNode = gpNotifLinks->pNext;
        HeapFree (ghHeap, 0, gpNotifLinks);
        gpNotifLinks = pNode;
    }
    gpNotifLinks = NULL;
    LeaveCriticalSection (&g_csNotifLinks);
}

///////////////////////////////////////////////////////////////////////////////
//    NotifyClients()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
void WINAPI NotifyClients (PWINDS_NOTIFICATION pNotif)
{
    DWORD dwThreadID;
    HANDLE hThread = CreateThread (NULL,
                                   0,
                                   (LPTHREAD_START_ROUTINE)ClientNotifThreadProc,
                                   (LPVOID)pNotif,
                                   CREATE_SUSPENDED,
                                   &dwThreadID);
    if (hThread)
    {
        TraceDebugger ("NotifyClients: Notif dispatcher thread spawned. ID: %X", dwThreadID);
        ResumeThread (hThread);
        CloseHandle (hThread);
    }
    else
    {
        TraceResult ("NotifyClients: Failed to create notification dispatching thread", HRESULT_FROM_WIN32(GetLastError()));
        HeapFree (ghHeap, 0, pNotif);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ClientNotifThreadProc()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
DWORD WINAPI ClientNotifThreadProc (PWINDS_NOTIFICATION pNotif)
{
    TCHAR szTargetMailslot[MAX_PATH] = { 0 };
    DWORD dwWritten, dwNotifEvent = 0;
    HANDLE hClientMailSlot;
    LPTSTR szMailSlotSink = NULL;

    SYSTEMTIME st;
    GetSystemTime (&st);
    SystemTimeToFileTime (&st, &(pNotif->ftEventTime));

    if (pNotif->Event >= AB_NOTIF_MIN  && pNotif->Event <= AB_NOTIF_MAX)
    {
        dwNotifEvent = WINDS_NOTIF_ON_AB | WINDS_NOTIF_ON_USER;
        szMailSlotSink = AB_WINDS_NOTIFICATION_MAILSLOT;
    }
    else
    {
        if (pNotif->Event >= XP_NOTIF_MIN  && pNotif->Event <= XP_NOTIF_MAX)
        {
            dwNotifEvent = WINDS_NOTIF_ON_XP;
            szMailSlotSink = XP_WINDS_NOTIFICATION_MAILSLOT;
        }
        else
        {
            if (pNotif->Event >= MS_NOTIF_MIN  && pNotif->Event <= MS_NOTIF_MAX)
            {
                dwNotifEvent = WINDS_NOTIF_ON_MS;
                szMailSlotSink = MS_WINDS_NOTIFICATION_MAILSLOT;
            }
            else
            {
                if (pNotif->Event >= GENERAL_NOTIF_MIN  && pNotif->Event <= GENERAL_NOTIF_MAX)
                {
                    dwNotifEvent = WINDS_NOTIF_ON_USER;
                }
            }
        }
    }

    EnterCriticalSection (&g_csNotifLinks);
    PWINDS_NOTIF_CLIENT pNode = gpNotifLinks;
    while (pNode)
    {
        if (dwNotifEvent & pNode->dwNotifMask)
        {
            if (WINDS_NOTIF_ON_USER & dwNotifEvent)
            {
                if (WINDS_NOTIF_ON_AB & pNode->dwNotifMask)
                {
                    szMailSlotSink = AB_WINDS_NOTIFICATION_MAILSLOT;
                }
                else
                {
                    if (WINDS_NOTIF_ON_XP & pNode->dwNotifMask)
                    {
                        szMailSlotSink = XP_WINDS_NOTIFICATION_MAILSLOT;
                    }
                    else
                    {
                        szMailSlotSink = MS_WINDS_NOTIFICATION_MAILSLOT;
                    }
                }
            }
            if (WINDS_NOTIF_ON_XP == dwNotifEvent && XP_NEW_MAIL_ARRIVED == pNotif->Event)
            {
                if (lstrcmpi (pNode->szObjectName, pNotif->Info.MB.szMailboxName))
                {
                    goto NextNode;
                }
            }
            if (WINDS_ADMINISTRATOR & pNode->dwNotifMask)
            {
                szMailSlotSink = ADMIN_WINDS_NOTIFICATION_MAILSLOT;
            }
            wsprintf (szTargetMailslot,
                      SERVER_MAILSLOT_SINK_NAME_FORMAT,
                      pNode->szComputerName,
                      szMailSlotSink,
                      pNode); // the connection number is the pointer address in the server side (this is unique within a given process)

            hClientMailSlot = CreateFile (szTargetMailslot,
                                          GENERIC_WRITE,
                                          FILE_SHARE_READ,
                                          NULL,
                                          OPEN_EXISTING,
                                          FILE_ATTRIBUTE_NORMAL,
                                          NULL);
            if (INVALID_HANDLE_VALUE == hClientMailSlot)
            {
                TraceResult ("ClientNotifThreadProc: Failed to open client mailslot", HRESULT_FROM_WIN32(GetLastError()));
            }
            else
            {
                if (!WriteFile (hClientMailSlot, pNotif, sizeof(WINDS_NOTIFICATION), &dwWritten, NULL))
                {
                    TraceResult ("ClientNotifThreadProc: Failed to write to the client mailslot", HRESULT_FROM_WIN32(GetLastError()));
                }
                CloseHandle (hClientMailSlot);
            }
        }
NextNode:
        pNode = pNode->pNext;
    }

#define CLIENTS_NOTIF_CACHE_FILE    TEXT("WINDS Service - Last Session Clients.WINDS")
    if (SERVER_IS_SHUTTING_DOWN == pNotif->Event && gpNotifLinks)
    {
        DWORD dwBytesWritten;
        WINDS_NOTIF_CLIENT tmpNode;
        HANDLE hFile = CreateFile (CLIENTS_NOTIF_CACHE_FILE,
                                   GENERIC_WRITE,
                                   0,
                                   NULL,
                                   CREATE_ALWAYS,
                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                                   NULL);
        if (INVALID_HANDLE_VALUE == hFile)
        {
            TraceResult ("ClientNotifThreadProc: Failed to create clients cache file", HRESULT_FROM_WIN32(GetLastError()));
        }
        else
        {
            pNode = gpNotifLinks;
            while (pNode)
            {
                tmpNode = *pNode;
                tmpNode.pNext = pNode;
                // This is so that the node address gets written to the disk. This is the
                // connection number and the name of the mailslot in the client side.
                WriteFile (hFile, &tmpNode, sizeof(WINDS_NOTIF_CLIENT), &dwBytesWritten, NULL);
                pNode = pNode->pNext;
            }
        }
    }
    if (SERVER_HAS_RESTARTED == pNotif->Event)
    {
        DWORD dwBytesRead;
        WINDS_NOTIF_CLIENT tmpNode = { 0 };
        HANDLE hFile = CreateFile (CLIENTS_NOTIF_CACHE_FILE,
                                   GENERIC_READ,
                                   0,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_DELETE_ON_CLOSE,
                                   NULL);
        if (INVALID_HANDLE_VALUE != hFile)
        {
            do
            {
                if (TRUE == ReadFile (hFile, &tmpNode, sizeof(WINDS_NOTIF_CLIENT), &dwBytesRead, NULL))
                {
                    if (WINDS_NOTIF_ON_AB & tmpNode.dwNotifMask)
                    {
                        szMailSlotSink = AB_WINDS_NOTIFICATION_MAILSLOT;
                    }
                    else
                    {
                        if (WINDS_NOTIF_ON_XP & tmpNode.dwNotifMask)
                        {
                            szMailSlotSink = XP_WINDS_NOTIFICATION_MAILSLOT;
                        }
                        else
                        {
                            szMailSlotSink = MS_WINDS_NOTIFICATION_MAILSLOT;
                        }
                    }
                    if (WINDS_ADMINISTRATOR & tmpNode.dwNotifMask)
                    {
                        szMailSlotSink = ADMIN_WINDS_NOTIFICATION_MAILSLOT;
                    }
                    wsprintf (szTargetMailslot,
                              SERVER_MAILSLOT_SINK_NAME_FORMAT,
                              tmpNode.szComputerName,
                              szMailSlotSink,
                              tmpNode.pNext); // The connection number is the pointer address in the server side (this is unique within a process address space)
                    hClientMailSlot = CreateFile (szTargetMailslot,
                                                  GENERIC_WRITE,
                                                  FILE_SHARE_READ,
                                                  NULL,
                                                  OPEN_EXISTING,
                                                  FILE_ATTRIBUTE_NORMAL,
                                                  NULL);
                    if (INVALID_HANDLE_VALUE != hClientMailSlot)
                    {
                        pNotif->Event = SERVER_HAS_RESTARTED;
                        pNotif->ftEventTime.dwLowDateTime++;
                        pNotif->ftEventTime.dwHighDateTime++;
                        WriteFile (hClientMailSlot, pNotif, sizeof(WINDS_NOTIFICATION), &dwWritten, NULL);
                        pNotif->Event = RESET_LINKS_WITH_SERVER;
                        pNotif->ftEventTime.dwLowDateTime++;
                        pNotif->ftEventTime.dwHighDateTime++;
                        WriteFile (hClientMailSlot, pNotif, sizeof(WINDS_NOTIFICATION), &dwWritten, NULL);
                        CloseHandle (hClientMailSlot);
                    }
                }
            } while (dwBytesRead);
            CloseHandle (hFile);
        }
    }

    LeaveCriticalSection (&g_csNotifLinks);
    HeapFree (ghHeap, 0, pNotif);
    return S_OK;
}

// End of file for NOTIFY.CPP


RABRPC.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      RABRPC.CPP
//
//  Description
//      This file implements all the remote functions available to client
//      WINDS address book providers.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <RPC.H>
#include "WINDS.H"      // Header file generated by the MIDL compiler
#include "WDSADM.H"     // Header file generated by the MIDL compiler

///////////////////////////////////////////////////////////////////////////////
//    RemoteChangePasswordA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteChangePasswordA (unsigned char *  szMailbox,
                            unsigned char *  szOldPassword,
                            unsigned char *  szNewPassword)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    MAILBOX_INFO MBInfo = { 0 };
    if (S_OK != GlobalObjectMap.FindObjFromName (SERVER_USER_MAILBOX, (LPSTR)szMailbox, &MBInfo.dwObjID))
    {
        TraceMessage ("RemoteChangePasswordA: Invalid mailbox");
        return HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
    }
    lstrcpy (MBInfo.szMailboxName, (LPSTR)szMailbox);

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteChangePasswordA: Failed to create I/O event", lResult);
        return lResult;
    }
    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_GET_MAILBOX_PROPERTIES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pMBInfo          = &MBInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    if (S_OK == lResult)
    {
        if (0 == lstrcmp (MBInfo.szPassword, (LPSTR)szOldPassword))
        {
            lstrcpy (MBInfo.szPassword, (LPSTR)szNewPassword);
            EnterCriticalSection (&g_csIOInfo);
            SetEvent (g_IOInfo.hResumeEvent);
            g_IOInfo.Action           = IO_SET_MAILBOX_PROPERTIES;
            g_IOInfo.hActionCompleted = hWaitEvent;
            g_IOInfo.phLastError      = &lResult;
            g_IOInfo.pMBInfo          = &MBInfo;
            LeaveCriticalSection (&g_csIOInfo);
            WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
        }
        else
        {
            lResult = HRESULT_FROM_WIN32(ERROR_WRONG_PASSWORD);
        }
    }
    CloseHandle (hWaitEvent);
    TraceResult ("RemoteChangePasswordA", lResult);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetRecipientPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetRecipientPropsA (unsigned long    dwObjID,
                               unsigned long *  pdwObjType,
                               unsigned char *  szMailboxName,
                               unsigned char *  szFullName,
                               unsigned char *  szJobTitle,
                               unsigned char *  szOffice,
                               unsigned char *  szPhone,
                               unsigned char *  szAltPhone,
                               unsigned char *  szFax,
                               unsigned char *  szComments,
                               unsigned char *  szCompany,
                               unsigned char *  szDepartment,
                               unsigned char *  szManagerName,
                               unsigned char *  szManagerAlias,
                               unsigned long *  pdwManagerID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    MAILBOX_INFO_A MBInfo = { 0 };
    MBInfo.dwObjID = dwObjID;
    lResult = GetObjectProp (dwObjID, pdwObjType, &MBInfo);
    if (S_OK == lResult)
    {
        lstrcpy ((LPSTR)szMailboxName,  MBInfo.szMailboxName);
        lstrcpy ((LPSTR)szFullName,     MBInfo.szFullName);
        lstrcpy ((LPSTR)szJobTitle,     MBInfo.szJobTitle);
        lstrcpy ((LPSTR)szOffice,       MBInfo.szOffice);
        lstrcpy ((LPSTR)szPhone,        MBInfo.szPhone);
        lstrcpy ((LPSTR)szFax,          MBInfo.szFax);
        lstrcpy ((LPSTR)szComments,     MBInfo.szComments);
        lstrcpy ((LPSTR)szAltPhone,     MBInfo.szAltPhone);
        lstrcpy ((LPSTR)szCompany,      MBInfo.szCompany);
        lstrcpy ((LPSTR)szDepartment,   MBInfo.szDepartment);
        lstrcpy ((LPSTR)szManagerName,  MBInfo.szManagerName);
        lstrcpy ((LPSTR)szManagerAlias, MBInfo.szManagerAlias);
        *pdwManagerID = MBInfo.dwManagerID;
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetObjectProp()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetObjectProp (DWORD             dwObjID,
                              DWORD *           pdwObjType,
                              MAILBOX_INFO *    pMailboxInfo)
{
    if (dwObjID == 0)
    {
        TraceMessage ("GetObjectProp: Invoked with Object ID = 0, cannot accept");
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER);
    }

    MAILBOX_INFO MBInfo = { 0 };
    WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
    // If an object with the given ID is not found or if the
    // found object is not the expected, the fail the client request
    if ((S_OK != GlobalObjectMap.FindObjFromID (dwObjID, MBInfo.szMailboxName, &Type)) ||
        (!(SERVER_USER_MAILBOX == Type || GATEWAY_RECIPIENT == Type)))
    {
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER);
    }
    ASSERT (SERVER_USER_MAILBOX == Type || GATEWAY_RECIPIENT == Type);
    *pdwObjType = (unsigned long)Type;
    HRESULT hResult;

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("GetMailboxProp: Failed to create I/O event", hResult);
        return hResult;
    }
    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    if (SERVER_USER_MAILBOX == Type)
    {
        g_IOInfo.Action = IO_GET_MAILBOX_PROPERTIES;
    }
    else
    {
        ASSERT (FALSE); // We haven't implemented gateway yet
        g_IOInfo.Action = IO_GET_MAILBOX_PROPERTIES;
    }
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &hResult;
    g_IOInfo.pMBInfo          = &MBInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (!hResult)
    {
        if (SERVER_USER_MAILBOX == Type)
        {
            *pMailboxInfo = MBInfo;
        }
        else
        {
            ASSERT (FALSE);
            hResult = E_FAIL;
        }
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetDistListPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetDistListPropsA (unsigned long     dwObjID,
                              unsigned char *   szDLAlias,
                              unsigned char *   szDLFullName,
                              unsigned long *   pdwContentCount,
                              unsigned char *   szOwnerAlias,
                              unsigned char *   szOwnerName,
                              unsigned char *   szComments)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    DIST_LIST_INFO DLInfo = { 0 };
    WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
    if ((S_OK != GlobalObjectMap.FindObjFromID (dwObjID, DLInfo.szDLAlias, &Type)) ||
        (SERVER_DISTRIBUTION_LIST != Type))
    {
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_GROUP);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteGetDistListPropsA: Failed to create I/O event", lResult);
        return HRESULT_FROM_WIN32(lResult);
    }
    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_GET_DL_PROPERTIES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pDLInfo          = &DLInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (!lResult)
    {
        lstrcpy ((LPSTR)szDLAlias, DLInfo.szDLAlias);
        lstrcpy ((LPSTR)szDLFullName, DLInfo.szDLFullName);
        lstrcpy ((LPSTR)szOwnerAlias, DLInfo.szOwnerAlias);
        lstrcpy ((LPSTR)szOwnerName, DLInfo.szOwnerName);
        lstrcpy ((LPSTR)szComments, DLInfo.szComments);
        *pdwContentCount = DLInfo.dwMemberCount;
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetContainerProps()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetContainerProps (unsigned long     dwContainerID,
                              unsigned long *   pdwContentCount)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    switch (dwContainerID)
    {
        case ROOT_CONTAINER_ID :
        case GATEWAY_CONTAINERS_ID :
        case GAL_CONTAINER_ID :
            *pdwContentCount = 0;
            if (GAL_CONTAINER_ID == dwContainerID)
            {
                *pdwContentCount = 0;
            }
            return S_OK;
        case FAX_CONTAINER_ID :
        case SMTP_CONTAINER_ID :
        case EXCHANGE_CONTAINER_ID :
            break;

        default :
            TraceMessage ("RemoteGetContainerProps: Invalid container ID passed in");
            return HRESULT_FROM_WIN32(ERROR_NO_SUCH_GROUP);
    }
    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteGetContainerProps: Failed to create I/O event", lResult);
        return lResult;
    }
    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_GET_GW_CONTAINER_COUNT;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pdwData          = pdwContentCount;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetContainerRecipients()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetContainerRecipients (unsigned long    dwFlags,
                                   unsigned long    dwContainerID,
                                   long *           pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    // Create a pipe where we will expect the transport to send the data
    TCHAR szPipeName[64];
    wsprintf (szPipeName, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    HANDLE hPipe;
    hPipe = CreateNamedPipe (szPipeName,
                             PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
                             PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                             1,
                             IO_BUFFERSIZE,
                             IO_BUFFERSIZE,
                             0,
                             &sa);
    if (INVALID_HANDLE_VALUE == hPipe || ERROR_INVALID_PARAMETER == (DWORD)hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteGetContainerRecipients: Failed to create pipe", lResult);
        goto ErrorExit;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_GET_CONTAINER_RECIPIENTS;
    g_IOInfo.hActionCompleted = NULL;
    g_IOInfo.phLastError      = NULL;
    g_IOInfo.dwObjID          = dwContainerID;
    g_IOInfo.hTmpFile         = hPipe;
    LeaveCriticalSection (&g_csIOInfo);

ErrorExit:
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteIsServerRunning()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteIsServerRunning()
{
    return GetServiceState();
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetAllAccounts()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetAllAccounts (long * pPipeNumber)
{
    return RemoteAdmGetServerMailboxes (pPipeNumber);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteLogonMailBoxA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteLogonMailBoxA (unsigned char *    szMailbox,
                          unsigned char *    szPassword,
                          unsigned char *    szFullName,
                          unsigned long *    pdwMailboxID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    if (S_OK != GlobalObjectMap.FindObjFromName (SERVER_USER_MAILBOX, (LPSTR)szMailbox, pdwMailboxID))
    {
        return HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
    }

    DWORD dwObjType;
    MAILBOX_INFO_A MBInfo = { 0 };
    lResult = GetObjectProp (*pdwMailboxID, &dwObjType, &MBInfo);
    if (lstrcmp (MBInfo.szPassword, (LPSTR)szPassword))
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD);
    }
    lstrcpy ((LPSTR)szMailbox, MBInfo.szMailboxName);
    lstrcpy ((LPSTR)szFullName, MBInfo.szFullName);
    return S_OK;
}

// The UNICODE version of these function, has not been implemented yet.
long RemoteChangePasswordW (wchar_t *       szMailbox,
                            wchar_t *       szOldPassword,
                            wchar_t *       szNewPassword)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteLogonMailBoxW (wchar_t *          szMailbox,
                          wchar_t *          szPassword,
                          wchar_t *          szFullName,
                          unsigned long *    pdwMailboxID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteLogonMailBoxAndSetNotifW (wchar_t *          szMailbox,
                                     wchar_t *          szPassword,
                                     wchar_t *          szFullName,
                                     unsigned long *    pdwMailboxID,
                                     wchar_t *          szComputerName,
                                     unsigned long      ulNotifMask,
                                     unsigned long *    pulConnectionID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteTerminateNotifW (wchar_t *       szComputerName,
                            unsigned long   ulConnectionID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteValidateNotifW (wchar_t *        szComputerName,
                           wchar_t *        szMailboxName,
                           unsigned long    ulNotifMask,
                           unsigned long *  pulConnectionID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteOpenMsgUploadPipeW (wchar_t *        szSenderMailbox,
                               long *           pPipeNumber,
                               unsigned char *  szCookie)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteSendMsgToAccountW (wchar_t * szRecipAccount,
                              wchar_t * szHeaderInfo,
                              wchar_t * szCookie)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteOpenMsgDownloadPipeW (wchar_t *          szMailbox,
                                 unsigned long *    pPipeNumber)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteOpenHeaderDownloadPipeW (wchar_t *   szMailbox,
                                    long *      pPipeNumber)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteCheckNewMailW (wchar_t *         szMailbox,
                          unsigned long *   pulPending)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteOpenOneMsgDownloadPipeW (wchar_t *   szMailbox,
                                    long *      pPipeNumber)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteGetRecipientPropsW (unsigned long    dwObjID,
                               unsigned long *  pdwObjType,
                               wchar_t *        szMailboxName,
                               wchar_t *        szFullName,
                               wchar_t *        szJobTitle,
                               wchar_t *        szOffice,
                               wchar_t *        szPhone,
                               wchar_t *        szAltPhone,
                               wchar_t *        szFax,
                               wchar_t *        szComments,
                               wchar_t *        szCompany,
                               wchar_t *        szDepartment,
                               wchar_t *        szManagerName,
                               wchar_t *        szManagerAlias,
                               unsigned long *  pdwManagerID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteGetDistListPropsW (unsigned long     dwObjID,
                              wchar_t *         szDLAlias,
                              wchar_t *         szDLFullName,
                              unsigned long *   pdwContentCount,
                              wchar_t *         szOwnerAlias,
                              wchar_t *         szOwnerName,
                              wchar_t *         szComments)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }

// End of file for RABRPC.CPP


RADMRPC.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      RADMRPC.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <RPC.H>
#include "WINDS.H"      // Header file generated by the MIDL compiler
#include "WDSADM.H"     // Header file generated by the MIDL compiler
#include "COMMON.H"     // For the transmition of the DL members list

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmGetServerMailboxes()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmGetServerMailboxes (long * pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    // Create a pipe where we will expect the transport to send the data
    TCHAR szPipeName[64];
    wsprintf (szPipeName, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    HANDLE hPipe;
    hPipe = CreateNamedPipe (szPipeName,
                             PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
                             PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                             1,
                             IO_BUFFERSIZE,
                             IO_BUFFERSIZE,
                             0,
                             &sa);
    if (INVALID_HANDLE_VALUE == hPipe || ERROR_INVALID_PARAMETER == (DWORD)hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmGetServerMailboxes: Failed to create pipe", lResult);
        return lResult;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_ADMIN_GET_SERVER_MAILBOXES;
    g_IOInfo.hActionCompleted = NULL;
    g_IOInfo.phLastError      = NULL;
    g_IOInfo.hTmpFile         = hPipe;
    LeaveCriticalSection (&g_csIOInfo);

    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmGetServerDistLists()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmGetServerDistLists (long * pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    TCHAR szPipeName[64];
    wsprintf (szPipeName, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    HANDLE hPipe;
    hPipe = CreateNamedPipe (szPipeName,
                             PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
                             PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                             1,
                             IO_BUFFERSIZE,
                             IO_BUFFERSIZE,
                             0,
                             &sa);
    if (INVALID_HANDLE_VALUE == hPipe || ERROR_INVALID_PARAMETER == (DWORD)hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmGetServerDistLists: Failed to create pipe", lResult);
        return lResult;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_ADMIN_GET_SERVER_DISTLISTS;
    g_IOInfo.hActionCompleted = NULL;
    g_IOInfo.phLastError      = NULL;
    g_IOInfo.hTmpFile         = hPipe;
    LeaveCriticalSection (&g_csIOInfo);

    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmIsServerRunning()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmIsServerRunning()
{
    return GetServiceState();
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmCreateMailboxA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmCreateMailboxA (ADM_MAILBOX_INFO_A * pMailboxInfo)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    if (FALSE == GlobalObjectMap.IsAliasNameAvailable ((LPSTR)pMailboxInfo->szMailboxName))
    {
        return HRESULT_FROM_WIN32(ERROR_USER_EXISTS);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmCreateMailboxA: Failed to create event for I/O thread", lResult);
        return lResult;
    }

    MAILBOX_INFO_A MBInfo = { 0 };
    CopyMemory (&MBInfo, pMailboxInfo, sizeof(MAILBOX_INFO_A));

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_CREATE_NEW_MAILBOX;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pMBInfo          = &MBInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (lResult)
    {
        TraceResult ("RemoteAdmCreateMailboxA: Failed to create the mailbox", lResult);
        return lResult;
    }

    GlobalObjectMap.Insert (MBInfo.dwObjID, MBInfo.szMailboxName, SERVER_USER_MAILBOX);

    // Notify clients (i.e. address books) that a new mail user has been added
    PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                 HEAP_ZERO_MEMORY,
                                                                 sizeof(WINDS_NOTIFICATION));
    if (pNotif)
    {
        pNotif->Event = AB_USER_ADDED;
        CopyMemory (&(pNotif->Info.MB), &MBInfo, sizeof(MAILBOX_INFO));
        NotifyClients (pNotif); // This will take care of freeing the notification structure
    }
    else
    {
        // This is non-fatal
        TraceResult ("RemoteAdmCreateMailboxA: Failed to allocate notification structure", E_OUTOFMEMORY);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmDeleteObject()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmDeleteObject (unsigned long dwObjID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    TCHAR szObjAlias[MAX_ALIAS_SIZE+1];
    WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
    if (S_OK != GlobalObjectMap.FindObjFromID (dwObjID, szObjAlias, &Type))
    {
        return HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmDeleteObject: Failed to create event for I/O thread", lResult);
        return lResult;
    }
    MAILBOX_INFO MBInfo = { 0 };
    DIST_LIST_INFO DLInfo = { 0 };

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError = &lResult;

    if (SERVER_USER_MAILBOX == Type)
    {
        lstrcpy (MBInfo.szMailboxName, szObjAlias);
        g_IOInfo.Action = IO_REMOVE_MAILBOX;
        g_IOInfo.pMBInfo = &MBInfo; // On return this will have all the mailbox properties
    }
    else
    {
        if (SERVER_DISTRIBUTION_LIST == Type)
        {
            lstrcpy (DLInfo.szDLAlias, szObjAlias);
            g_IOInfo.Action = IO_DELETE_DISTRIBUTION_LIST;
            g_IOInfo.pDLInfo = &DLInfo;
        }
        else
        {
            TraceMessage ("RemoteAdmDeleteObject: Remove GATEWAY recipient NYI");
        }
    }

    LeaveCriticalSection (&g_csIOInfo);

    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);

    if (lResult)
    {
        TraceResult ("RemoteAdmDeleteObject: Failed to delete the mailbox", lResult);
        return lResult;
    }

    GlobalObjectMap.Delete (dwObjID);
    // Notify client address books that a mail user has been deleted
    PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                 HEAP_ZERO_MEMORY,
                                                                 sizeof(WINDS_NOTIFICATION));
    if (pNotif)
    {
        if (SERVER_USER_MAILBOX == Type)
        {
            pNotif->Event = AB_USER_DELETED;
            CopyMemory (&(pNotif->Info.MB), &MBInfo, sizeof(MAILBOX_INFO));
        }
        else
        {
            if (SERVER_DISTRIBUTION_LIST == Type)
            {
                pNotif->Event = AB_DL_DELETED;
                CopyMemory (&(pNotif->Info.DL), &DLInfo, sizeof(DIST_LIST_INFO));
            }
            else
            {
                // TODO: Implement this for gateways
            }
        }
        NotifyClients (pNotif); // This will take care of freeing the notification structure
    }
    else
    {
        // Non-fatal error
        TraceResult ("RemoteAdmDeleteObject: Failed to allocate notification structure", E_OUTOFMEMORY);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmGetMailboxPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmGetMailboxPropsA (unsigned long           dwObjID,
                                unsigned long *         pdwObjType,
                                ADM_MAILBOX_INFO_A *    pMailboxInfo)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    return GetObjectProp (dwObjID, pdwObjType, (MAILBOX_INFO*)pMailboxInfo);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmSetMailboxPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmSetMailboxPropsA (ADM_MAILBOX_INFO_A * pMailboxInfo)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    if (pMailboxInfo->dwObjID == 0)
    {
        TraceMessage ("RemoteAdmSetMailboxPropsA: Invoked with Object ID = 0, cannot accept");
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmSetMailboxPropsA: Failed to create event for I/O thread", lResult);
        return lResult;
    }

    MAILBOX_INFO_A MBInfo = { 0 };
    CopyMemory (&MBInfo, pMailboxInfo, sizeof(MAILBOX_INFO_A));

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_SET_MAILBOX_PROPERTIES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pMBInfo          = &MBInfo;
    LeaveCriticalSection (&g_csIOInfo);

    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (!lResult)
    {
        // Notify client address books that a mail user has been modified
        PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                     HEAP_ZERO_MEMORY,
                                                                     sizeof(WINDS_NOTIFICATION));
        if (pNotif)
        {
            pNotif->Event = AB_USER_MODIFIED;
            CopyMemory (&(pNotif->Info.MB), &MBInfo, sizeof(MAILBOX_INFO));
            NotifyClients (pNotif); // This will take care of freeing the notification structure
        }
        else
        {
            // This is non-fatal
            TraceResult ("RemoteAdmSetMailboxPropsA: Failed to allocate notification structure", E_OUTOFMEMORY);
        }
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmTerminateNotifA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmTerminateNotifA (unsigned char *   szComputerName,
                              unsigned long     ulConnectionID)
{
    return RemoteTerminateNotifA (szComputerName, ulConnectionID);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmValidateNotifA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmValidateNotifA (unsigned char *    szComputerName,
                              unsigned long      ulNotifMask,
                              unsigned long *    pulConnectionID)
{
    return RemoteValidateNotifA (szComputerName,
                                 (unsigned char *)"WINDS ADMIN",
                                 ulNotifMask,
                                 pulConnectionID);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmGetGALDirectory()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmGetGALDirectory (unsigned long dwFlags, long * pPipeNumber)
{
    return RemoteGetContainerRecipients (dwFlags, GAL_CONTAINER_ID, pPipeNumber);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmEmptyMailbox()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmEmptyMailbox (unsigned long dwMailboxID)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    TCHAR szObjAlias[MAX_ALIAS_SIZE+1];
    WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
    if (S_OK != GlobalObjectMap.FindObjFromID (dwMailboxID, szObjAlias, &Type))
    {
        return HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND);
    }
    if (SERVER_USER_MAILBOX != Type)
    {
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmEmptyMailbox: Failed to create event for I/O thread", lResult);
        return lResult;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    lstrcpy (g_IOInfo.szObject, szObjAlias);
    g_IOInfo.Action           = IO_EMPTY_MAILBOX;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    LeaveCriticalSection (&g_csIOInfo);

    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);

    TraceResult ("RemoteAdmEmptyMailbox", lResult);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmGetDLPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmGetDLPropsA (unsigned long        dwObjID,
                           unsigned char *      szDLAlias,
                           unsigned char *      szDLFullName,
                           unsigned long *      pdwFlags,
                           unsigned char *      szOwnerAlias,
                           unsigned char *      szOwnerName,
                           unsigned long *      pdwOwnerID,
                           unsigned char *      szComments,
                           DLM_XMIT_LIST_A *    pMembers)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }

    if (0 == dwObjID)
    {
        TraceMessage ("RemoteAdmGetDLPropsA: Invoked with Object ID = 0, cannot accept");
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_GROUP);
    }

    DIST_LIST_INFO DLInfo = { 0 };
    WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
    if ((S_OK != GlobalObjectMap.FindObjFromID (dwObjID, DLInfo.szDLAlias, &Type)) ||
        (SERVER_DISTRIBUTION_LIST != Type))
    {
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_GROUP);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmGetDLPropsA: Failed to create event for I/O thread", lResult);
        return lResult;
    }
    DLInfo.pMembers = (LPVOID)pMembers;

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_GET_DL_PROPERTIES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pDLInfo          = &DLInfo;
    LeaveCriticalSection (&g_csIOInfo);

    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (!lResult)
    {
        lstrcpy ((LPSTR)szDLAlias, DLInfo.szDLAlias);
        lstrcpy ((LPSTR)szDLFullName, DLInfo.szDLFullName);
        *pdwFlags = DLInfo.dwFlags;
        lstrcpy ((LPSTR)szOwnerAlias, DLInfo.szOwnerAlias);
        lstrcpy ((LPSTR)szOwnerName, DLInfo.szOwnerName);
        *pdwOwnerID = DLInfo.dwOwnerID;
    }

    TraceResult ("RemoteAdmGetDLPropsA", lResult);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmSetDLPropsA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmSetDLPropsA (unsigned long        dwObjID,
                           unsigned char *      szDLAlias,
                           unsigned char *      szDLFullName,
                           unsigned long        dwFlags,
                           unsigned char *      szOwnerAlias,
                           unsigned char *      szOwnerName,
                           unsigned long        dwOwnerID,
                           unsigned char *      szComments,
                           DLM_XMIT_LIST_A *    pMembers)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    if (dwObjID == 0)
    {
        TraceMessage ("RemoteAdmSetDLPropsA: Invoked with Object ID = 0, cannot accept");
        return HRESULT_FROM_WIN32 (ERROR_NO_SUCH_GROUP);
    }

    DIST_LIST_INFO DLInfo = { 0 };
    lstrcpy (DLInfo.szDLAlias, (LPSTR)szDLAlias);
    lstrcpy (DLInfo.szDLFullName, (LPSTR)szDLFullName);
    DLInfo.dwObjID= dwObjID;
    DLInfo.dwFlags = dwFlags;
    lstrcpy (DLInfo.szOwnerAlias, (LPSTR)szOwnerAlias);
    lstrcpy (DLInfo.szOwnerName, (LPSTR)szOwnerName);
    DLInfo.dwOwnerID = dwOwnerID;
    DLInfo.pMembers = (LPVOID)pMembers;

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmSetDLPropsA: Failed to create event for I/O thread", lResult);
        return lResult;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_SET_DL_PROPERTIES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pDLInfo          = &DLInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);

    if (!lResult)
    {
        PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                     HEAP_ZERO_MEMORY,
                                                                     sizeof(WINDS_NOTIFICATION));
        if (pNotif)
        {
            pNotif->Event = AB_DL_MODIFIED;
            CopyMemory (&(pNotif->Info.DL), &DLInfo, sizeof(DIST_LIST_INFO));
            NotifyClients (pNotif); // This will take care of freeing the notification structure
        }
        else
        {
            // This is non-fatal
            TraceResult ("RemoteAdmSetDLPropsA: Failed to allocate the notification structure", E_OUTOFMEMORY);
        }
    }
    TraceResult ("RemoteAdmSetDLPropsA", lResult);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteAdmCreateDistListA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteAdmCreateDistListA (unsigned char *   szDLAlias,
                               unsigned char *   szDLFullName,
                               unsigned long     dwFlags,
                               unsigned char *   szOwnerAlias,
                               unsigned char *   szOwnerName,
                               unsigned long     dwOwnerID,
                               DLM_XMIT_LIST_A * pMembers)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    if (FALSE == GlobalObjectMap.IsAliasNameAvailable ((LPSTR)szDLAlias))
    {
        return HRESULT_FROM_WIN32(ERROR_GROUP_EXISTS);
    }

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteAdmCreateDistListA: Failed to create event for I/O thread", lResult);
        return lResult;
    }

    DIST_LIST_INFO DLInfo = { 0 };
    lstrcpy (DLInfo.szDLAlias, (LPSTR)szDLAlias);
    lstrcpy (DLInfo.szDLFullName, (LPSTR)szDLFullName);
    DLInfo.dwFlags = dwFlags;

    lstrcpy (DLInfo.szOwnerAlias, (LPSTR)szOwnerAlias);
    lstrcpy (DLInfo.szOwnerName, (LPSTR)szOwnerName);
    DLInfo.dwOwnerID = dwOwnerID;
    if (pMembers->Info.szMemberName[0])
    {
        DLInfo.pMembers = (LPVOID)pMembers;
    }
    else
    {
        DLInfo.pMembers = NULL;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_CREATE_DISTRIBUTION_LIST;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pDLInfo          = &DLInfo;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);

    if (lResult)
    {
        TraceResult ("RemoteAdmCreateDistListA", lResult);
        return lResult;
    }

    GlobalObjectMap.Insert (DLInfo.dwObjID, DLInfo.szDLAlias, SERVER_DISTRIBUTION_LIST);

    // Notify client address books that a distribution list has been added
    PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                 HEAP_ZERO_MEMORY,
                                                                 sizeof(WINDS_NOTIFICATION));
    if (pNotif)
    {
        pNotif->Event = AB_DL_ADDED;
        CopyMemory (&(pNotif->Info.DL), &DLInfo, sizeof(DIST_LIST_INFO));
        NotifyClients (pNotif); // This will take care of freeing the notification structure
    }
    else
    {
        // This is non-fatal
        TraceResult ("RemoteAdmCreateDistListA: Failed to allocate the notification structure", E_OUTOFMEMORY);
    }
    return lResult;
}

// The UNICODE version of these function, has not been implemented yet.
long RemoteAdmTerminateNotifW (wchar_t *        szComputerName,
                               unsigned long    ulConnectionID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmValidateNotifW (wchar_t *         szComputerName,
                              unsigned long     ulNotifMask,
                              unsigned long *   pulConnectionID)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmCreateMailboxW (ADM_MAILBOX_INFO_W * pMailboxInfo)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmGetMailboxPropsW (unsigned long        dwObjID,
                                unsigned long *      pdwUserType,
                                ADM_MAILBOX_INFO_W * pMailboxInfo)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmSetMailboxPropsW (ADM_MAILBOX_INFO_W * pMailboxInfo)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmCreateDistListW (wchar_t *         szDLAlias,
                               wchar_t *         szDLFullName,
                               unsigned long     dwFlags,
                               wchar_t *         szOwnerAlias,
                               wchar_t *         szOwnerName,
                               unsigned long     dwOwnerID,
                               DLM_XMIT_LIST_W * pMembers)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmGetDLPropsW (unsigned long        dwObjID,
                           wchar_t *            szDLAlias,
                           wchar_t *            szDLFullName,
                           unsigned long *      pdwFlags,
                           wchar_t *            szOwnerAlias,
                           wchar_t *            szOwnerName,
                           unsigned long *      pdwOwnerID,
                           wchar_t *            szComments,
                           DLM_XMIT_LIST_W *    pMembers)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
long RemoteAdmSetDLPropsW (unsigned long        dwObjID,
                           wchar_t *            szDLAlias,
                           wchar_t *            szDLFullName,
                           unsigned long        dwFlags,
                           wchar_t *            szOwnerAlias,
                           wchar_t *            szOwnerName,
                           unsigned long        dwOwnerID,
                           wchar_t *            szComments,
                           DLM_XMIT_LIST_W *    pMembers)
{ return HRESULT_FROM_WIN32(E_NOTIMPL); }
// End of file for RADMRPC.CPP


_WINDS.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      _WINDS.H
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _WINDS_H
#define _WINDS_H

#define STRICT
#include <WINDOWS.H>
#include "WINDSGBL.H"
#include "WINDSRC.H"

#ifdef _DEBUG
#define ENABLE_DEBUG_OUTPUT     1
#endif // _DEBUG
#define TRACES_NO_MAPI          1

#include "TRACES.H"

#define WINDS_DATABASE_VERSION          6

// Buffers and pipe symbolic constants.
#define READ_TIME_OUT       INFINITE
#define WRITE_TIME_OUT      INFINITE
#define GENERAL_TIME_OUT    INFINITE

#define MIN_PIPE_NUMBER                 0x00000001
#define MAX_PIPE_NUMBER                 0x00FFFFFF

#define ELEMENTS_TO_FETCH   25

typedef enum _ACTION
{
    IO_IDLE,
    IO_OPEN_STORAGE_FILE,
    IO_LOAD_MAILBOX_NAMES,
    IO_CREATE_NEW_MAILBOX,
    IO_REMOVE_MAILBOX,
    IO_EMPTY_MAILBOX,
    IO_GET_MAILBOX_PROPERTIES,
    IO_SET_MAILBOX_PROPERTIES,

    // Message Transport services
    IO_COPY_MSG_FROM_FILE,
    IO_COPY_MSG_TO_FILE,
    IO_MOVE_MSG_TO_FILE,
    IO_COPY_HEADERS_TO_FILE,
    IO_DELETE_MSG_IN_MAILBOX,
    IO_CHECK_PENDING_MESSAGES,
    IO_MOVE_NEXT_MSG_TO_FILE,

    // Address Book services
    IO_GET_CONTAINER_RECIPIENTS,
    IO_CREATE_DISTRIBUTION_LIST,
    IO_DELETE_DISTRIBUTION_LIST,
    IO_GET_DL_PROPERTIES,
    IO_SET_DL_PROPERTIES,
    IO_GET_GW_CONTAINER_COUNT,

    // Public folder services
    IO_CREATE_PUBLIC_FOLDER,
    IO_DELETE_PUBLIC_FOLDER,
    IO_GET_PF_PROPERTIES,
    IO_SET_PF_PROPERTIES,
    IO_CREATE_PF_NEW_MESSAGE,
    IO_DELETE_PF_MESSAGE,
    IO_OPEN_PF_MESSAGE,
    IO_MODIFY_PF_MESSAGE,

    // Remote Administrator
    IO_ADMIN_GET_SERVER_MAILBOXES,
    IO_ADMIN_GET_SERVER_DISTLISTS,

    // Helper actions for the notification mechanism
    IO_SENT_SHUTDOWN_NOTIFICATION,
    IO_RESTORE_CLIENT_NOTIFICATIONS
} ACTION;

typedef struct _IOTHREADINFO
{
    BOOL                    fAppIsTerminating;
    BOOL                    fCloseHandle;
    ACTION                  Action;
    HANDLE                  hResumeEvent;
    HANDLE                  hActionCompleted;
    HANDLE                  hTmpFile;
    TCHAR                   szObject[MAX_ALIAS_SIZE+1];
    TCHAR                   szHeader[IO_BUFFERSIZE];
    PMAILBOX_INFO           pMBInfo;
    PDIST_LIST_INFO         pDLInfo;
    PPUBLIC_FOLDER_INFO     pPFInfo;
    DWORD                   dwObjID;
    HRESULT *               phLastError;
    DWORD *                 pdwData;
    PWINDS_NOTIFICATION     pNotif;
} IOTHREADINFO, *PIOTHREADINFO;

typedef struct _DBINFO_DATA
{
    DWORD dwVersion;
} DBINFO_DATA;

// This is the structure with the information that gets written to the
// system registry database to save per-OS-USER-session information
typedef struct tagAPP_OPTIONS
{
    DWORD       cbSize;
    TCHAR       szDataFile[256];
} APP_OPTIONS;

#define SERVER_PIPE_NAME_FORMAT     TEXT("\\\\.\\pipe\\%d")

#define NAME_FORMAT_SIZE        32

// Name string format for the OLE sub storages of the object database
#define DBINFO_STREAM           OLESTR("DATABASE INFO")
#define OBJ_ID_POOL_STREAM      OLESTR("OBJECT ID POOL")
#define USER_PROPERTIES         OLESTR("USER PROPERTIES")
#define HEADERS_STORAGE         OLESTR("HEADERS")
#define MSGS_STORAGE            OLESTR("MESSAGES")
#define MSG_STREAM_FORMAT       OLESTR("MSG %X")
#define HEADER_STREAM_FORMAT    OLESTR("HEADER %X")
// Server Distribution List Support
#define DISTRIBUTION_LISTS      OLESTR("_DISTRIBUTION LISTS")
#define DL_PROPS                OLESTR("DL PROPERTIES")
#define DL_MEMBERS              OLESTR("DL MEMBERS")
// Gateway Support
#define FOREIGN_DIRECTORIES     OLESTR("_GATEWAYS")
#define GW_OUTBOX               OLESTR("_GATEWAY OUT")
#define GW_INBOX                OLESTR("_GATEWAY IN")
#define SMTP_GATEWAY            OLESTR("SMTP")
#define EXCHANGE_GATEWAY        OLESTR("EXCHANGE")
#define FAX_GATEWAY             OLESTR("FAX")
// Public Folders Support
#define PUBLIC_FOLDERS_ROOT     OLESTR("_PUBLIC FOLDERS ROOT")
#define FOLDER_PROPS_STREAM     OLESTR("FOLDER PROPERTIES")
#define PUBLIC_FOLDER           OLESTR("FOLDER %X")

#define CREATE_FLAGS    (STGM_CREATE | STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE)
#define OPEN_FLAGS      (STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE)

#define MAINDLGCLASS    TEXT("WindsDlgClass")

extern "C"
{
    extern IOTHREADINFO g_IOInfo;
    extern TCHAR g_szDataFile[];
    extern HRESULT g_hServerState;
    extern CRITICAL_SECTION g_csIOInfo;
    extern CRITICAL_SECTION g_csNotifLinks;
    extern CRITICAL_SECTION g_csServiceState;
    extern CRITICAL_SECTION g_csPipeID;
    extern DWORD g_dwNextPipeID;
    extern BOOL g_fRunningAsNTService;
    extern HINSTANCE ghInstance;
    extern HANDLE    ghHeap;

    HRESULT WINAPI CreateDLsDirectory
                        (LPSTORAGE                  pStorage);
    HRESULT WINAPI CreateGatewayDirectories
                        (LPSTORAGE                  pStorage);
    HRESULT WINAPI CreatePublicFoldersDirectory
                        (LPSTORAGE                  pStorage);
    HRESULT WINAPI GetObjectProp
                        (DWORD                      dwObjID,
                         DWORD *                    pdwObjType,
                         MAILBOX_INFO *             pMailboxInfo);
    HRESULT WINAPI GetServiceState
                        ();
    HRESULT WINAPI AnsiToUnicode
                        (LPSTR                      szStr,
                         LPWSTR                     wcStr,
                         ULONG                      cchUnicode);
    HRESULT WINAPI UnicodeToAnsi
                        (LPWSTR                     wcStr,
                         LPSTR                      szStr,
                         ULONG                      cchAnsi);
    DWORD WINAPI GetNextPipeID
                        ();
    void WINAPI ReleaseSyncObj
                        ();
    void WINAPI LoadSettingsFromRegistry
                        ();
    void WINAPI SaveSettingsToRegistry
                        ();
    void WINAPI RemoveAllNotifLinks
                        ();
    void WINAPI NotifyClients
                        (PWINDS_NOTIFICATION        pNotif);

    HRESULT WINAPI GetLocalTempFileName
                        (LPTSTR                     szTmpFileName);
    HRESULT WINAPI FileCopy
                        (HANDLE,
                         HANDLE,
                         DWORD);
    HRESULT WINAPI WaitForClientConnection
                        (HANDLE                     hPipe,
                         DWORD                      dwTimeOut);
    DWORD WINAPI WorkingIOProc
                        ();
    DWORD WINAPI ClientNotifThreadProc
                        (PWINDS_NOTIFICATION        pNotif);
};

typedef struct _OBJLIST_NODE
{
    struct _OBJLIST_NODE *      pNext;              // 4
    DWORD                       dwObjID;            // 4
    WINDS_AB_OBJTYPE            ObjType;            // 4
    TCHAR                       szObjAlias[20];     // 20
                                   // Structure size = 32
} OBJLIST_NODE, *POBJLIST_NODE;

class CObjectList
{
public:
    STDMETHODIMP Insert
                    (DWORD                      dwObjID,
                     LPTSTR                     pObjName,
                     WINDS_AB_OBJTYPE           ObjType);
    STDMETHODIMP Delete
                    (DWORD                      dwObjID);
    STDMETHODIMP FindObjFromID
                    (DWORD                      dwObjID,
                     LPTSTR                     pObjName,
                     WINDS_AB_OBJTYPE *         pObjType);
    STDMETHODIMP FindObjFromName
                    (WINDS_AB_OBJTYPE           ObjType,
                     LPTSTR                     pObjName,
                     DWORD *                    pdwObjID);
    STDMETHODIMP FindObjAndTypeFromName
                    (LPTSTR                     pObjName,
                     WINDS_AB_OBJTYPE *         pObjType,
                     DWORD *                    pdwObjID);
    BOOL WINAPI IsAliasNameAvailable
                    (LPTSTR                     pObjName);

public:
    CObjectList();
    ~CObjectList();

private:
    HANDLE              m_hHeap;
    CRITICAL_SECTION    m_csObj;
    POBJLIST_NODE       m_pHead;
};

extern CObjectList GlobalObjectMap;

#endif // _WINDS_H

// End of file for WINDS.H


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by WINDSADM.RC
//
#define IDS_COLUMN1                     1
#define IDS_COLUMN2                     2
#define IDS_COLUMN3                     3
#define IDS_COLUMN4                     4
#define IDS_COLUMN5                     5
#define IDS_COLUMN6                     6
#define IDS_COLUMN7                     7
#define IDM_MENU                        100
#define IDM_MAILBOX_POPUP               101
#define IDA_ACCELTABLE                  102
#define IDM_DISTLIST_POPUP              102
#define IDB_OBJECTS                     103
#define IDB_TOOLBAR                     104
#define IDB_ICONS                       105
#define IDI_MAINFRAME                   106
#define IDD_ABOUTBOX                    107
#define IDD_SERVER_CONNECT              108
#define IDD_EXPORT                      109
#define IDD_MB_PROPS1                   110
#define IDD_MB_PROPS2                   111
#define IDD_MB_PROPS3                   112
#define IDD_MB_PROPS4                   113
#define IDD_MB_PROPS5                   114
#define IDD_DL_PROPS1                   115
#define IDD_DL_PROPS3                   116
#define IDD_DL_PROPS2                   117
#define IDD_ADDTOLIST                   118
#define IDD_DIR_DOWNLOAD_WAIT           119
#define IDD_ONE_MAILBOX                 120
#define IDD_IMPORT                      121
#define IDD_IMPORT_STATE                122
#define IDD_REMOVE_SERVER               123
#define IDD_MB_PROPS6                   124
#define IDD_EXPORT_STATE                125
#define IDR_ANIMATED_TIMER              127
#define IDR_DEFAULT1                    129
#define IDC_LISTVIEW                    1001
#define IDC_TREEVIEW                    1002
#define IDC_STATUSBAR                   1003
#define IDC_TOOLBAR                     1004
#define IDC_ICON_TITLE                  1005
#define IDC_PAGE_ICON                   1006
#define IDC_MAILBOX_OWNER               1007
#define IDC_MAILBOX_PHONE               1008
#define IDC_MAILBOX_FAX                 1009
#define IDC_MAILBOX_JOBTITLE            1010
#define IDC_MAILBOX_OFFICE              1011
#define IDC_MAILBOX_COMMENTS            1012
#define IDC_MAILBOX_ALTPHONE            1013
#define IDC_MAILBOX_PASSWORD            1014
#define IDC_ADD_USER                    1015
#define IDC_REMOVE_USER                 1016
#define IDC_ALL_USERS                   1017
#define IDC_SELECTED_USERS              1018
#define IDC_REMOVE_ALL                  1019
#define IDC_OBJECT_ALIAS                1020
#define IDC_OBJECT_ALIAS_LABEL          1021
#define IDC_SERVER_NAME                 1022
#define IDC_SERVER_DEFAULT              1023
#define IDC_MAILBOXES                   1024
#define IDC_GW_DIRECTORIES              1025
#define IDC_FILENAME                    1026
#define IDC_BROWSE                      1027
#define IDC_DL_OWNER                    1028
#define IDC_NOT_OWNED                   1029
#define IDC_OWNED                       1030
#define IDC_HIDE                        1031
#define IDC_MEMBERS                     1032
#define IDC_REMOVE                      1033
#define IDC_ADD                         1034
#define IDC_DL_FULLNAME                 1035
#define IDC_MODIFY                      1036
#define IDC_DELEGATE_LIST               1037
#define IDC_OWNER_LABEL                 1038
#define IDC_DISABLE                     1039
#define IDC_STOPWATCH                   1040
#define IDC_IMPORT_LIST                 1041
#define IDC_COMMENTS_LABEL              1042
#define IDC_COMMENTS                    1043
#define IDC_SERVER_LIST                 1044
#define IDC_MAILBOX_LABEL               1045
#define IDC_CANCEL_REMOTE_CALL          1046
#define IDC_NYI_LABEL                   1047
#define IDC_CONSTRUCT_ICON              1048
#define IDC_MAILBOX_NT_ACCOUNT          1050
#define IDC_IS_WINDS_ADMIN              1051
#define IDC_COMPANY                     1053
#define IDC_DEPARTMENT                  1054
#define IDC_MANAGER                     1055
#define IDS_MSG_INVALID_SERVER_NAME     10000
#define IDS_MSG_SERVER_NOT_FOUND        10001
#define IDS_MSG_NO_OBJECT_ALIAS         10002
#define IDS_MSG_INVALID_OBJECT_ALIAS    10003
#define IDS_MSG_INVALIS_CHARS_IN_ALIAS  10004
#define IDS_MSG_NO_USER_NAME            10005
#define IDS_MSG_NO_DL_NAME              10006
#define IDS_MSG_NO_PASSWORD             10007
#define IDS_MSG_CONFIRM_OBJECT_DELETE   10008
#define IDS_MSG_CONFIRM_MB_MSGS_PURGE   10009
#define IDS_MSG_NO_INTERNAL_RESOURCES   10010
#define IDS_MSG_WAIT_FOR_COMPLETION     10011
#define IDM_ABOUT                       40000
#define IDM_SERVER_EXPORT               40001
#define IDM_SERVER_IMPORT               40002
#define IDM_FIND_MAILBOX                40003
#define IDM_FIND_DL                     40004
#define IDM_VIEW_CONNECT                40005
#define IDM_VIEW_DISCONNECT             40006
#define IDM_VIEW_REFRESH                40007
#define IDM_FILE_NEW_MAILBOX            40008
#define IDM_FILE_NEW_DISTRIBUTIONLIST   40009
#define IDM_FILE_DELETE                 40010
#define IDM_FILE_PROPERTIES             40011
#define IDM_FILE_EXIT                   40012
#define IDM_POPUP_MB_PROPS              40013
#define IDM_POPUP_MB_EMPTY              40014
#define IDM_POPUP_MB_DELETE             40015
#define IDM_POPUP_MB_HEADERS            40016
#define IDM_FILE_NEW_PUB_FOLDER         40017
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        133
#define _APS_NEXT_COMMAND_VALUE         40018
#define _APS_NEXT_CONTROL_VALUE         1058
#define _APS_NEXT_SYMED_VALUE           125
#endif
#endif


RMSRPC.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      RMSRPC.CPP
//
//  Description
//      This file implements all the remote functions available to client
//      WINDS public folder store providers.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <RPC.H>
#include "WINDS.H"     // Header file generated by the MIDL compiler

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetPublicFoldersList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetPublicFoldersList (short nFlags, long * pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteGetPublicFolderMessages()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteGetPublicFolderMessages (short nFlags, unsigned long ulFolderID, long * pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    return lResult;
}

// End of file for RMSRPC.CPP


RXPRPC.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      RXPRPC.CPP
//
//  Description
//      This file implements all the remote functions available to client
//      WINDS message transport providers.
//
//  Authors
//      Irving De la Cruz
//      Les Thaler
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <RPC.H>
#include "WINDS.H"     // Header file generated by the MIDL compiler
#include "WDSADM.H"    // Header file generated by the MIDL compiler

typedef struct tagTHREAD_PROC_INFO
{
    HANDLE          hPipe;
    HANDLE          hTmpFile;
    TCHAR           szTmpFile[MAX_PATH];
    TCHAR           szMailbox[MAX_ALIAS_SIZE+1];
} THREAD_PROC_INFO, *PTHREAD_PROC_INFO;

typedef struct tagDL_DELIVERY_PROC_INFO
{
    TCHAR               szTmpFile[MAX_PATH];
    TCHAR               szHeader[1024];
    DIST_LIST_INFO      DLInfo;
    DWORD               dwAliasDelivered;
    LPTSTR *            ppszAliasList;
    HANDLE              hWaitEvent;
    HANDLE              hFile;
    BOOL                fDeleteInfoStruct;
} DL_DELIVERY_PROC_INFO, *PDL_DELIVERY_PROC_INFO;

extern "C"
{
    HRESULT WINAPI MsgUploadPipeThread
                        (PTHREAD_PROC_INFO          pInfo);
    HRESULT WINAPI MsgDownloadThread
                        (PTHREAD_PROC_INFO          pInfo);
    HRESULT WINAPI OneMsgDownloadPipeThread
                        (PTHREAD_PROC_INFO          pInfo);
    HRESULT WINAPI HeaderDLPipeThread
                        (PTHREAD_PROC_INFO          pInfo);
    DWORD WINAPI DistListDeliveryThread
                        (PDL_DELIVERY_PROC_INFO     pDLInfo);
    BOOL WINAPI DeleteMessage
                        (HANDLE                     hResumeEvt,
                         LPTSTR                     szMBox,
                         LPBYTE                     pEID);
    HANDLE WINAPI OpenMessage
                        (HANDLE                     hResumeEvt,
                         LPTSTR                     pszMailBox,
                         LPBYTE                     pEID,
                         ULONG                      ulCmd,
                         ULONG *                    pulMsgLen);
    HRESULT WINAPI ObjectDelivered
                        (PDL_DELIVERY_PROC_INFO     pInfo,
                         LPTSTR                     szObject);
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteOpenMsgUploadPipeA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteOpenMsgUploadPipeA (unsigned char *   szSenderMailbox,
                               long *            pPipeNumber,
                               unsigned char *   szCookie)
{
    LPTSTR pStrTok;
    TCHAR szBuffer[_MAX_PATH];
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    DWORD dwMailboxID;
    if (S_OK != GlobalObjectMap.FindObjFromName (SERVER_USER_MAILBOX, (LPSTR)szSenderMailbox, &dwMailboxID))
    {
        TraceMessage ("RemoteOpenUploadPipeA: The originator no longer has an account in this server");
        return HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
    }

    PTHREAD_PROC_INFO pInfo = (PTHREAD_PROC_INFO)HeapAlloc (ghHeap,
                                                            HEAP_ZERO_MEMORY,
                                                            sizeof(THREAD_PROC_INFO));
    if (NULL == pInfo)
    {
        TraceMessage ("RemoteOpenMsgUploadPipeA: Failed to allocate info structure");
        return E_OUTOFMEMORY;
    }

    lResult = GetLocalTempFileName (szBuffer);
    if (lResult)
    {
        goto TerminateAndCleanup;
    }
    lstrcpy (pInfo->szTmpFile, szBuffer);
    pInfo->hTmpFile = CreateFile (szBuffer,
                                  GENERIC_WRITE,
                                  FILE_SHARE_READ,
                                  NULL,
                                  CREATE_ALWAYS,
                                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                                  NULL);
    if (INVALID_HANDLE_VALUE == pInfo->hTmpFile)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenMsgUploadPipeA: Failed to create the temp file", lResult);
        goto TerminateAndCleanup;
    }

    pStrTok = &szBuffer[lstrlen(szBuffer)];
    while ('\\' != *pStrTok)
    {
        pStrTok--;
    }
    pStrTok++;
    lstrcpy ((LPSTR)szCookie, pStrTok);

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    // Create a pipe where we will expect the transport to send the data
    wsprintf (szBuffer, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    pInfo->hPipe = CreateNamedPipe (szBuffer,
                                    PIPE_ACCESS_INBOUND,
                                    PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                                    1,
                                    IO_BUFFERSIZE,
                                    IO_BUFFERSIZE,
                                    0,
                                    &sa);
    if (INVALID_HANDLE_VALUE == pInfo->hPipe || ERROR_INVALID_PARAMETER == (DWORD)pInfo->hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenMsgUploadPipeA: Failed to create pipe", lResult);
    }
    else
    {
        DWORD dwThreadID;
        HANDLE hThread = CreateThread (NULL,
                                       0,
                                       (LPTHREAD_START_ROUTINE)MsgUploadPipeThread,
                                       (LPVOID)pInfo,
                                       0,
                                       &dwThreadID);
        if (hThread)
        {
            TraceDebugger ("RemoteOpenMsgUploadPipeA: Message Upload thread spawned. ID: %X", dwThreadID);
            CloseHandle (hThread);
            pInfo = NULL; // The thread will free this memory
        }
        else
        {
            lResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("RemoteOpenMsgUploadPipeA: Failed to create pipe thread", lResult);
        }
    }
TerminateAndCleanup :
    if (pInfo)
    {
        if (pInfo->hTmpFile && (ERROR_INVALID_HANDLE != (DWORD)pInfo->hTmpFile))
        {
            CloseHandle (pInfo->hTmpFile);
        }
        if (pInfo->hPipe)
        {
            CloseHandle (pInfo->hPipe);
        }
        HeapFree (ghHeap, 0, pInfo);
    }
    if (lResult)
    {
        if (FACILITY_NULL == HRESULT_FACILITY(lResult))
        {
            lResult = HRESULT_FROM_WIN32 (lResult);
        }
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteSendMsgToAccountA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteSendMsgToAccountA (unsigned char * szRecipAccount,
                              unsigned char * szHeaderInfo,
                              unsigned char * szCookie)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    DWORD dwMailboxID;
    WINDS_AB_OBJTYPE Type;
    if (S_OK != GlobalObjectMap.FindObjAndTypeFromName ((LPSTR)szRecipAccount, &Type, &dwMailboxID))
    {
        TraceMessage ("RemoteSendMsgToAccountA: Rejecting message for an non-existing recipient");
        return HRESULT_FROM_WIN32(ERROR_INVALID_ACCOUNT_NAME);
    }

    TCHAR szTmpPath[_MAX_PATH];
    if (!GetTempPath (_MAX_PATH, szTmpPath))
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteSendMsgToAccountA: Failed to get temp path", lResult);
        return HRESULT_FROM_WIN32 (lResult);
    }

    lstrcat (szTmpPath, (LPSTR)szCookie);
    PTHREAD_PROC_INFO pInfo = NULL;
    PDL_DELIVERY_PROC_INFO pDLInfo = NULL;
    HANDLE hFile = NULL, hWaitEvent;
RetryOpenFile:
    hFile = CreateFile (szTmpPath,
                        GENERIC_READ,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                        NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        if (HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) == lResult)
        {
            lResult = S_OK;
            TraceMessage ("RemoteSendMsgToAccountA: Sharing Violation, waiting before retry");
            SleepEx (200, FALSE);
            goto RetryOpenFile;
        }
        TraceResult ("RemoteSendMsgToAccountA: Failed to open the temp file", lResult);
        return lResult;
    }
    hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteSendMsgToAccountA: Failed to create event for I/O thread", lResult);
        goto ErrorExit;
    }
    if (SERVER_USER_MAILBOX == Type)
    {
        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        lstrcpy (g_IOInfo.szObject, (LPSTR)szRecipAccount);
        lstrcpy (g_IOInfo.szHeader, (LPSTR)szHeaderInfo);
        g_IOInfo.Action           = IO_COPY_MSG_FROM_FILE;
        g_IOInfo.hActionCompleted = hWaitEvent;
        g_IOInfo.hTmpFile         = hFile;
        g_IOInfo.fCloseHandle     = TRUE;
        hFile = NULL;
        g_IOInfo.phLastError      = &lResult;
        LeaveCriticalSection (&g_csIOInfo);
        WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    }
    else
    {
        if (SERVER_DISTRIBUTION_LIST == Type)
        {
            pDLInfo = (PDL_DELIVERY_PROC_INFO)HeapAlloc (ghHeap,
                                                         HEAP_ZERO_MEMORY,
                                                         sizeof(DL_DELIVERY_PROC_INFO));
            if (NULL == pDLInfo)
            {
                TraceMessage ("RemoteSendMsgToAccountA: Failed to allocate info structure");
                lResult = E_OUTOFMEMORY;
                goto ErrorExit;
            }
            pDLInfo->fDeleteInfoStruct = TRUE;
            lstrcpy (pDLInfo->DLInfo.szDLAlias, (LPSTR)szRecipAccount);
            lstrcpy (pDLInfo->szHeader, (LPSTR)szHeaderInfo);
            GetLocalTempFileName (pDLInfo->szTmpFile);
            if (FALSE == CopyFile (szTmpPath, pDLInfo->szTmpFile, FALSE))
            {
                lResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("RemoteSendMsgToAccountA: Failed to create duplicate tmp file for DL delivery", lResult);
                goto ErrorExit;
            }

            EnterCriticalSection (&g_csIOInfo);
            SetEvent (g_IOInfo.hResumeEvent);
            g_IOInfo.Action           = IO_GET_DL_PROPERTIES;
            g_IOInfo.hActionCompleted = hWaitEvent;
            g_IOInfo.phLastError      = &lResult;
            g_IOInfo.pDLInfo          = &(pDLInfo->DLInfo);
            LeaveCriticalSection (&g_csIOInfo);
            WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
            if (lResult)
            {
                goto ErrorExit;
            }
            DWORD dwThreadID;
            HANDLE hThread = CreateThread (NULL,
                                           0,
                                           (LPTHREAD_START_ROUTINE)DistListDeliveryThread,
                                           (LPVOID)pDLInfo,
                                           CREATE_SUSPENDED,
                                           &dwThreadID);
            if (hThread)
            {
                TraceDebugger ("RemoteSendMsgToAccountA: Server DL delivery thread spawned. ID: %X", dwThreadID);
                SetThreadPriority (hThread, THREAD_PRIORITY_BELOW_NORMAL);

                ResumeThread (hThread);
                CloseHandle (hThread);
                pDLInfo = NULL; // The thread will free this memory
            }
            else
            {
                lResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("RemoteSendMsgToAccountA: Failed to create Server DL delivery thread", lResult);
            }
        }
        else
        {
            TraceMessage ("RemoteSendMsgToAccountA: This type has not been implemented yet");
            lResult = HRESULT_FROM_WIN32(ERROR_INVALID_ACCOUNT_NAME);
        }
    }
ErrorExit:
    if (lResult)
    {
        if (pDLInfo)
        {
            DeleteFile (pDLInfo->szTmpFile);
            HeapFree (ghHeap, 0, pDLInfo);
        }
        if (pInfo)
        {
            DeleteFile (pInfo->szTmpFile);
            HeapFree (ghHeap, 0, pInfo);
        }
    }
    if (hFile)
    {
        CloseHandle (hFile);
    }
    if (hWaitEvent)
    {
        CloseHandle (hWaitEvent);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteFinishUpload()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteFinishUpload (unsigned char * szCookie)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    TCHAR szTmpPath[_MAX_PATH];
    if (!GetTempPath (_MAX_PATH, szTmpPath))
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteFinishUpload: Failed to get temp path", lResult);
        lResult = lResult;
    }
    else
    {
        lstrcat (szTmpPath, (LPSTR)szCookie);
        DeleteFile (szTmpPath);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteOpenHeaderDownloadPipeA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteOpenHeaderDownloadPipeA (unsigned char * szMailbox, long * pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    DWORD dwMailboxID;
    if (S_OK != GlobalObjectMap.FindObjFromName (SERVER_USER_MAILBOX, (LPSTR)szMailbox, &dwMailboxID))
    {
        TraceMessage ("RemoteOpenHeaderDownloadPipeA: Invalid user mailbox");
        return HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
    }

    *pPipeNumber = GetNextPipeID();

    TCHAR szBuffer[_MAX_PATH];
    lResult = GetLocalTempFileName (szBuffer);
    if (lResult)
    {
        return lResult;
    }
    HANDLE hFile = CreateFile (szBuffer,
                               GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_ALWAYS,
                               FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
                               NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenHeaderDownloadPipeA: Failed to temp file", lResult);
        return lResult;
    }

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    wsprintf (szBuffer, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    HANDLE hPipe = CreateNamedPipe (szBuffer,
                                    PIPE_ACCESS_OUTBOUND,
                                    PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                                    1,
                                    IO_BUFFERSIZE,
                                    IO_BUFFERSIZE,
                                    0,
                                    &sa);
    if (INVALID_HANDLE_VALUE == hPipe || ERROR_INVALID_PARAMETER == (DWORD)hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenHeaderDownloadPipeA: Failed to create pipe", lResult);
    }
    else
    {
        PTHREAD_PROC_INFO pInfo = (PTHREAD_PROC_INFO)HeapAlloc (ghHeap,
                                                                HEAP_ZERO_MEMORY,
                                                                sizeof(THREAD_PROC_INFO));
        if (pInfo)
        {
            lstrcpy (pInfo->szMailbox, (LPSTR)szMailbox);
            pInfo->hPipe = hPipe;
            pInfo->hTmpFile = hFile;
            DWORD dwThreadID;
            HANDLE hThread = CreateThread (NULL,
                                           0,
                                           (LPTHREAD_START_ROUTINE)HeaderDLPipeThread,
                                           (LPVOID)pInfo,
                                           0,
                                           &dwThreadID);
            if (hThread)
            {
                TraceDebugger ("RemoteOpenHeaderDownloadPipeA: Header download thread spawned. ID: %X", dwThreadID);
                CloseHandle (hThread);
            }
            else
            {
                lResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("RemoteOpenHeaderDownloadPipeA: Failed to create pipe thread", lResult);
            }
        }
        else
        {
            TraceMessage ("RemoteOpenHeaderDownloadPipeA: Failed to allocate info structure");
            lResult = E_OUTOFMEMORY;
        }
    }
    if (lResult)
    {
        CloseHandle (hPipe);
        CloseHandle (hFile);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteOpenMsgDownloadPipeA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteOpenMsgDownloadPipeA (unsigned char *     szMailbox,
                                 unsigned long *     pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    DWORD dwMailboxID;
    if (S_OK != GlobalObjectMap.FindObjFromName (SERVER_USER_MAILBOX, (LPSTR)szMailbox, &dwMailboxID))
    {
        TraceMessage ("RemoteOpenMsgDownloadPipeA: Invalid user mailbox");
        return HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
    }

    PTHREAD_PROC_INFO pInfo = (PTHREAD_PROC_INFO)HeapAlloc (ghHeap,
                                                            HEAP_ZERO_MEMORY,
                                                            sizeof(THREAD_PROC_INFO));
    if (!pInfo)
    {
        TraceMessage ("RemoteOpenMsgDownloadPipeA: Failed to allocate info structure");
        return E_OUTOFMEMORY;
    }
    lstrcpy (pInfo->szMailbox, (LPSTR)szMailbox);

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    TCHAR szPipeName[64];
    wsprintf (szPipeName, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    pInfo->hPipe = CreateNamedPipe (szPipeName,
                                    PIPE_ACCESS_DUPLEX,
                                    PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                                    1,
                                    IO_BUFFERSIZE,
                                    IO_BUFFERSIZE,
                                    0,
                                    &sa);
    if (INVALID_HANDLE_VALUE == pInfo->hPipe || ERROR_INVALID_PARAMETER == (DWORD)pInfo->hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenMsgDownloadPipeA: Failed to create pipe", lResult);
    }
    else
    {
        DWORD dwThreadID;
        HANDLE hThread = CreateThread (NULL,
                                       0,
                                       (LPTHREAD_START_ROUTINE)MsgDownloadThread,
                                       (LPVOID)pInfo,
                                       0,
                                       &dwThreadID);
        if (hThread)
        {
            TraceDebugger ("RemoteOpenMsgDownloadPipeA: Header download thread spawned. ID: %X", dwThreadID);
            CloseHandle (hThread);
        }
        else
        {
            lResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("RemoteOpenMsgDownloadPipeA: Failed create doanload thread", lResult);
        }
    }

    if (lResult)
    {
        if (FACILITY_NULL == HRESULT_FACILITY(lResult))
        {
            lResult = HRESULT_FROM_WIN32 (lResult);
        }
        HeapFree (ghHeap, 0, pInfo);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    MsgUploadPipeThread()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI MsgUploadPipeThread (PTHREAD_PROC_INFO pInfo)
{
    BYTE abBuffer[IO_BUFFERSIZE];
    DWORD dwBytesWrite, dwBytesRead = 0;

    ConnectNamedPipe (pInfo->hPipe, NULL);
    HRESULT hResult = S_OK;
    do
    {
        // Wait until the client writes to the pipe
        if (!ReadFile (pInfo->hPipe, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
        {
            // There was an error and we can't continue
            hResult = HRESULT_FROM_WIN32(GetLastError());
            if (hResult != HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE))
            {
                TraceResult ("MsgUploadPipeThread: Failed to read from the msg upload pipe", hResult);
            }
            else
            {
                hResult = 0;
            }
        }
        if (dwBytesRead && !hResult)
        {
            if (!WriteFile (pInfo->hTmpFile, abBuffer, dwBytesRead, &dwBytesWrite, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("MsgUploadPipeThread: Failed to write to the local tmp file", hResult);
            }
        }
    } while (dwBytesRead && !hResult);

    TraceResult ("MsgUploadPipeThread", hResult);
    CloseHandle (pInfo->hTmpFile);
    CloseHandle (pInfo->hPipe);
    if (hResult)
    {
        DeleteFile (pInfo->szTmpFile);
    }
    HeapFree (ghHeap, 0, pInfo);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    HeaderDLPipeThread()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI HeaderDLPipeThread (PTHREAD_PROC_INFO pInfo)
{
    BYTE abBuffer[IO_BUFFERSIZE];
    DWORD dwBytesRead, dwBytesWrite;
    HRESULT hResult;
    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("HeaderDLPipeThread: Failed to create event for I/O thread", hResult);
        goto Error;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    lstrcpy (g_IOInfo.szObject, pInfo->szMailbox);
    g_IOInfo.Action           = IO_COPY_HEADERS_TO_FILE;
    g_IOInfo.hTmpFile         = pInfo->hTmpFile;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &hResult;
    LeaveCriticalSection (&g_csIOInfo);

    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    if (hResult)
    {
        goto Error;
    }

    // Wait until the client connects to the pipe
    ConnectNamedPipe (pInfo->hPipe, NULL);

    do
    {
        if (!ReadFile (pInfo->hTmpFile, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
        {
            hResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("HeaderDLPipeThread: Failed to read the tmp file", hResult);
        }
        if (dwBytesRead && !hResult)
        {
            if (!WriteFile (pInfo->hPipe, abBuffer, dwBytesRead, &dwBytesWrite, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("HeaderDLPipeThread: Failed to write to the pipe", hResult);
            }
        }
    } while (dwBytesRead && !hResult);

Error:
    CloseHandle (pInfo->hTmpFile);
    CloseHandle (pInfo->hPipe);
    HeapFree (ghHeap, 0, pInfo);
    return hResult;
}

////////////////////////////////////////////////////////////////////////////
//    MsgDownloadThread()
//
//    Parameters
//          pInfo       pointer to a THREAD_PROC_INFO containing the handle to
//                      the open pipe for data transmission to the remote XP
//
//    Purpose
//          This is the worker thread that is spawned when a request to
//          messages arrives from the remote XP via RemoteOpenMsgDownloadPipe.
//          Once the pipe has been opened the remote XP sends command messages
//          on the pipe to request data from the server. These command messages
//          are of 3 types: MSG_MOVE, MSG_DOWNLOAD, and MSG_DELETE. The command
//          messages have a msg ID that tells the requested operation. When this
//          thread is started, it creates an event object for synchronization
//          with the storage thread that accesses the message database then
//          immediately blocks waiting for the XP to connect to the pipe. We
//          unblock and start reading command requests. Each mail message we
//          process goes through the following cycle:
//
//              1. read the command message, this contains the operation
//                 requested by the XP and the mail message's entry ID
//
//              2. if the command is to HANG UP, quit, otherwise:
//
//                 look up the entry ID of the mail message in the storage.
//                 if unsuccessful, send a NAK message back to the XP
//
//                 if the request is to DELETE, delete the message and if
//                 successful send an ACK message back to the XP, otherwise
//                 send a NAK message back, then start on the next request
//
//                 if the request is to MOVE or DOWNLOAD, get the mail message's
//                 size, stuff it in an ACK message and write it to the XP.then
//                 stream the mail message over the pipe
//
//              3. read the next command
//
//          We treat write errors to the pipe as fatal and disconnect.
//
//    Return Value
//
HRESULT WINAPI MsgDownloadThread(PTHREAD_PROC_INFO pInfo)
{
    MSG_HDR     MsgHdr;
    HANDLE      hTmpFile;
    DWORD       dwBytesRead, dwBytesWritten;
    HRESULT hResult = S_OK;
    HANDLE      hResumeEvt  = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (!hResumeEvt)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("MsgDownloadThread: Failed to create event", hResult);
        goto Quit;
    }

    // block until XP connects to us
    ConnectNamedPipe (pInfo->hPipe, NULL);

    while (TRUE)
    {
        if (!ReadFile(pInfo -> hPipe, &MsgHdr, sizeof(MSG_HDR), &dwBytesRead, NULL))
            break; // Out of the WHILE() loop

        ASSERT (dwBytesRead == sizeof(MSG_HDR));

        switch (MsgHdr.ulMID)
        {
            case MSG_MOVE:  // Fall through
            case MSG_DOWNLOAD:

                // tell IO thread to open msg's container, write it
                // to a temp file and return us its size and handle

                hTmpFile = OpenMessage(hResumeEvt,
                                       pInfo->szMailbox,
                                       MsgHdr.Info.EID,
                                       MsgHdr.ulMID,
                                       &MsgHdr.Info.
                                       ulMsgLen);
                if (!hTmpFile)
                {
                    MsgHdr.ulMID = OP_FAILED;           // send a NAK back to XP
                    MsgHdr.Info.ulMsgLen = 0;
                    if (!WriteFile(pInfo->hPipe, &MsgHdr, sizeof(MSG_HDR), &dwBytesWritten, NULL))
                    {
                        goto Quit;
                    }
                }
                else

{
                    MsgHdr.ulMID = OP_STARTED;         // send an ACK to XP
                    // Bail on any write errors, the connection is down
                    if (!WriteFile(pInfo->hPipe, &MsgHdr, sizeof(MSG_HDR), &dwBytesWritten, NULL))
                    {
                        goto Quit;
                    }

                    // set seek pointer to beginning of temp file
                    if ((DWORD)-1 == SetFilePointer(hTmpFile, 0, NULL, FILE_BEGIN))
                        break;

                    if (NO_ERROR != FileCopy(pInfo -> hPipe, hTmpFile, MsgHdr.Info.ulMsgLen))
                        goto Quit;

                }
                break;

            case MSG_DELETE:
                // tell storage thread to delete msg's substorage
                MsgHdr.ulMID = (DeleteMessage (hResumeEvt, pInfo->szMailbox, MsgHdr.Info.EID) ? OP_COMPLETE : OP_FAILED);
                MsgHdr.Info.ulMsgLen = 0;

                // send ACK/NAK to XP
                if (!WriteFile (pInfo->hPipe, &MsgHdr, sizeof(MSG_HDR), &dwBytesWritten, NULL))
                {
                    goto Quit;
                }
                break;

            case GOODBYE:            // HANG UP
                goto Quit;

            default :
                // don't attempt to resynchronize
                goto Quit;
        }
    }

Quit:
    CloseHandle (pInfo->hPipe);
    CloseHandle (hResumeEvt);
    HeapFree (ghHeap, 0, pInfo);
    return hResult;
}

////////////////////////////////////////////////////////////////////////////
//    DeleteMessage()
//
//    Parameters
//          hResumeEvt          handle to event object for signaling completion
//                              of storage operation
//          szMailBox           Name of mailbox for the mail msg we're handling
//          pEID                entry ID of msg we're handling
//    Purpose
//          Sends a DELETE request to the storage thread.The request, along
//          with the message's EID, and the mailbox name where the message
//          resides are passed to the storage thread in g_IOInfo. We then block
//          on an event from the storage thread signaling completetion of the
//          requested operations. The storage finds the mail message and
//          deletes it.
//
//    Return Value
//          TRUE on success,FALSE otherwise
//
BOOL WINAPI DeleteMessage(HANDLE hResumeEvt, LPTSTR pszMailBox, LPBYTE pEID)
{
    HRESULT hResult = S_OK;
    // In Windows NT 3.5 OLE storages can only be accessed by a single thread, so use
    // this global data struct to pass data to the storage thread
    EnterCriticalSection (&g_csIOInfo);
    SetEvent(g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = IO_DELETE_MSG_IN_MAILBOX;
    g_IOInfo.hActionCompleted = hResumeEvt;
    g_IOInfo.phLastError      = &hResult;
    g_IOInfo.dwObjID          = (DWORD)atol((LPCSTR) &pEID[4]);
    lstrcpy (g_IOInfo.szObject, pszMailBox);
    LeaveCriticalSection (&g_csIOInfo);

    // Block until the storage thread finishes
    WaitForSingleObject(hResumeEvt, GENERAL_TIME_OUT);
    return (S_OK == hResult ? TRUE : FALSE);
}

////////////////////////////////////////////////////////////////////////////
//    OpenMessage()
//
//    Parameters
//          hResumeEvt          handle to event object for signaling completion
//                              of storage operation
//          pszMailBox          name of mailbox for the mail msg we're handling
//          pEID                entry ID of msg we're handling
//          ulCmd               operation to perform on the message
//          pulMsgLen           to pass back the size of the data stream
//    Purpose
//          Create a temporary file to hold the mail message, then send
//          a request for the operation passed in ulCmd to the storage
//          thread.The request, along with the message's EID, the mailbox
//          name where the message resides, and the handle to the tempfile
//          are passed to the storage thread in g_IOInfo. We then block on
//          an event from the storage thread signaling completetion of the
//          requested operations. The storage finds the mail message and
//          copies it into the tempfile passing back the size in pulMsgLen.
//
//    Return Value
//          Handle of temp file on success, NULL otherwise
//
HANDLE WINAPI OpenMessage(HANDLE   hResumeEvt,
                          LPTSTR   pszMailBox,
                          LPBYTE   pEID,
                          ULONG    ulCmd,
                          ULONG *  pulMsgLen)
{
    HANDLE      hTempFile = NULL;
    HRESULT     hResult;
    TCHAR       szTempFile[MAX_PATH];
    ACTION      Action;
    // creates a unique tempfile name
    if (GetLocalTempFileName (szTempFile))
    {
        return NULL;
    }

    switch (ulCmd)
    {
        case MSG_DOWNLOAD :
            Action = IO_COPY_MSG_TO_FILE;
            break;

        case MSG_MOVE :
            Action = IO_MOVE_MSG_TO_FILE;
            break;

        default :
            TraceMessage ("OpenMessage: Invalid command received");
            ASSERT (FALSE);
            return NULL;
    }

    // create tempfile to hold message
    if (! (hTempFile = CreateFile(szTempFile,
                                  GENERIC_READ | GENERIC_WRITE,
                                  FILE_SHARE_READ,
                                  NULL,
                                  CREATE_ALWAYS,
                                  FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
                                  NULL)))
        return NULL;

    // OLE storages can only be accessed by a single thread, so use
    // this global data struct to pass data to the storage thread

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action           = Action;        // requested operation
    g_IOInfo.hActionCompleted = hResumeEvt;    // signal us on this event
    g_IOInfo.phLastError      = &hResult;
    g_IOInfo.dwObjID          = (DWORD) atol((const char *) &pEID[4]);
    g_IOInfo.hTmpFile         = hTempFile;     // storage thread writes msg here
    g_IOInfo.pdwData          = pulMsgLen;     // storage thread writes length here
    lstrcpy (g_IOInfo.szObject, pszMailBox);
    LeaveCriticalSection (&g_csIOInfo);

    // block until storage thread finishes
    WaitForSingleObject(hResumeEvt, GENERAL_TIME_OUT);
    return (hResult ? NULL : hTempFile);
}

////////////////////////////////////////////////////////////////////////////
//    DistListDeliveryThread()
//
//    Parameters
//
//    Purpose
//      This function is RECURSIVE
//
//    Return Value
//
DWORD WINAPI DistListDeliveryThread (PDL_DELIVERY_PROC_INFO pDLInfo)
{
    DLM_LIST_A * pNode, *pSubNode;
    DL_DELIVERY_PROC_INFO SubDLInfo = { 0 };
    DWORD j, dwResult = S_OK;
    HRESULT hResult;
    HANDLE hWaitEvent, hFile;
    ObjectDelivered (pDLInfo, pDLInfo->DLInfo.szDLAlias);
    if (NULL == pDLInfo->hFile)
    {
        hFile = CreateFile (pDLInfo->szTmpFile,
                            GENERIC_READ,
                            0,
                            NULL,
                            OPEN_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
                            NULL);
        if (INVALID_HANDLE_VALUE == hFile)
        {
            dwResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("DistListDeliveryThread: Failed to open the temp file", dwResult);
            goto ErrorExit;
        }
        pDLInfo->hFile = hFile;
    }
    else
    {
        hFile = pDLInfo->hFile;
    }
    if (NULL == pDLInfo->hWaitEvent)
    {
        hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
        if (!hWaitEvent)
        {
            TraceResult ("DistListDeliveryThread: Failed to create event for I/O thread", HRESULT_FROM_WIN32(GetLastError()));
        }
        pDLInfo->hWaitEvent = hWaitEvent;
    }
    else
    {
        hWaitEvent = pDLInfo->hWaitEvent;
    }
    // Process the USER mailboxes first
    pNode = (DLM_LIST_A *)pDLInfo->DLInfo.pMembers;
    while (pNode)
    {
        if (SERVER_USER_MAILBOX != (WINDS_AB_OBJTYPE)pNode->Info.dwMemberType)
        {
            continue;
        }
        SetFilePointer (hFile, 0, NULL, FILE_BEGIN);
        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        lstrcpy (g_IOInfo.szObject, (LPSTR)pNode->Info.szMemberAlias);
        lstrcpy (g_IOInfo.szHeader, pDLInfo->szHeader);
        g_IOInfo.Action           = IO_COPY_MSG_FROM_FILE;
        g_IOInfo.hActionCompleted = hWaitEvent;
        g_IOInfo.hTmpFile         = hFile;
        g_IOInfo.fCloseHandle     = FALSE;
        g_IOInfo.phLastError      = &hResult;
        LeaveCriticalSection (&g_csIOInfo);
        WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);

        // The message was successfully delivered to the recipient, so add it to the "DELIVERED" list
        if (S_OK == hResult)
        {
            ObjectDelivered (pDLInfo, (LPSTR)pNode->Info.szMemberAlias);
        }
        pNode = pNode->pNext;
    }
    lstrcpy (SubDLInfo.szHeader, pDLInfo->szHeader);
    SubDLInfo.hFile = hFile;
    SubDLInfo.hWaitEvent = hWaitEvent;

    // Process the Distribution lists second
    pNode = (DLM_LIST_A *)pDLInfo->DLInfo.pMembers;
    while (pNode)
    {
        if (SERVER_DISTRIBUTION_LIST != (WINDS_AB_OBJTYPE)pNode->Info.dwMemberType)
        {
            continue;
        }
        for (j=0; j<pDLInfo->dwAliasDelivered; j++)
        {
            // If the message has already been delivered to this sub DL, skip it
            if (0 == lstrcmp ((LPSTR)pNode->Info.szMemberAlias, pDLInfo->ppszAliasList[j]))
            {
                continue;
            }
        }

        lstrcpy (SubDLInfo.DLInfo.szDLAlias, (LPSTR)pNode->Info.szMemberAlias);
        SubDLInfo.fDeleteInfoStruct = FALSE;
        SubDLInfo.DLInfo.pMembers = NULL;
        SubDLInfo.dwAliasDelivered = pDLInfo->dwAliasDelivered;
        SubDLInfo.ppszAliasList = pDLInfo->ppszAliasList;

        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        g_IOInfo.Action           = IO_GET_DL_PROPERTIES;
        g_IOInfo.hActionCompleted = hWaitEvent;
        g_IOInfo.phLastError      = &hResult;
        g_IOInfo.pDLInfo          = &(SubDLInfo.DLInfo);
        LeaveCriticalSection (&g_csIOInfo);
        WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
        if (hResult)
        {
            TraceResult ("DistListDeliveryThread: Could not get DL properties", hResult);
            continue;
        }

        pSubNode = (DLM_LIST_A *)SubDLInfo.DLInfo.pMembers;
        while (pSubNode)
        {
            for (j=0; j<pDLInfo->dwAliasDelivered; j++)
            {
                // If the message has already been delivered to a member of the Sub DL
                // mark the entry in the sub DL so that it won't deliver to that recipient.
                if (0 == lstrcmp ((LPSTR)pSubNode->Info.szMemberAlias, pDLInfo->ppszAliasList[j]))
                {
                    pSubNode->Info.dwMemberType = (DWORD)UNDEFINED_OBJECT_TYPE;
                }
            }
            pSubNode = pSubNode->pNext;
        }
        // Deliver to the members of the sub-distribution list
        hResult = DistListDeliveryThread (&SubDLInfo);
        // Remember how many recipients were handled in this sub DL
        pDLInfo->dwAliasDelivered = SubDLInfo.dwAliasDelivered;
        pDLInfo->ppszAliasList = SubDLInfo.ppszAliasList;
        if (SubDLInfo.DLInfo.pMembers)
        {
            HeapFree (ghHeap, 0, SubDLInfo.DLInfo.pMembers);
        }
        pNode = pNode->pNext;
    }

ErrorExit:
    if (pDLInfo->fDeleteInfoStruct)
    {
        CloseHandle (pDLInfo->hWaitEvent);
        CloseHandle (pDLInfo->hFile);
        DeleteFile (pDLInfo->szTmpFile);
        if (pDLInfo->DLInfo.pMembers)
        {
            HeapFree (ghHeap, 0, pDLInfo->DLInfo.pMembers);
        }
        if (pDLInfo->ppszAliasList)
        {
            for (j=0; j<pDLInfo->dwAliasDelivered; j++)
            {
                HeapFree (ghHeap, 0, pDLInfo->ppszAliasList[j]);
            }
            HeapFree (ghHeap, 0, pDLInfo->ppszAliasList);
        }
        HeapFree (ghHeap, 0, pDLInfo);
    }
    return dwResult;
}

////////////////////////////////////////////////////////////////////////////
//    ObjectDelivered()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI ObjectDelivered (PDL_DELIVERY_PROC_INFO pDLInfo, LPTSTR szObject)
{
    DWORD dwIndex = pDLInfo->dwAliasDelivered;
    if (NULL == pDLInfo->ppszAliasList)
    {
        ASSERT (0 == pDLInfo->dwAliasDelivered);
        pDLInfo->ppszAliasList = (LPTSTR *)HeapAlloc (ghHeap, 0, sizeof(LPTSTR));
        if (NULL == pDLInfo->ppszAliasList)
        {
            TraceMessage ("ObjectDelivered: Failed to allocate Delivered Alias List");
            return E_OUTOFMEMORY;
        }
    }
    else
    {
        pDLInfo->ppszAliasList = (LPTSTR *)HeapReAlloc (ghHeap,
                                                        0,
                                                        pDLInfo->ppszAliasList,
                                                        sizeof(LPTSTR) * (pDLInfo->dwAliasDelivered + 1));
        if (NULL == pDLInfo->ppszAliasList[dwIndex])
        {
            TraceMessage ("ObjectDelivered: Failed to re-allocate Delivered Alias List");
            return E_OUTOFMEMORY;
        }
    }

    pDLInfo->ppszAliasList[dwIndex] = (LPTSTR)HeapAlloc (ghHeap, 0, (sizeof (TCHAR)*(lstrlen(szObject)+1)));
    if (NULL == pDLInfo->ppszAliasList[dwIndex])
    {
        TraceMessage ("ObjectDelivered: Failed to allocate object alias string");
        return E_OUTOFMEMORY;
    }
    lstrcpy (pDLInfo->ppszAliasList[dwIndex], szObject);
    pDLInfo->dwAliasDelivered++;
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteCheckNewMailA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteCheckNewMailA (unsigned char * szMailbox, unsigned long * pulPending)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteCheckNewMailA: Failed to create event for I/O thread", lResult);
        return lResult;
    }
    *pulPending = 0;
    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    lstrcpy (g_IOInfo.szObject, (LPSTR)szMailbox);
    g_IOInfo.Action           = IO_CHECK_PENDING_MESSAGES;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.phLastError      = &lResult;
    g_IOInfo.pdwData          = pulPending;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    CloseHandle (hWaitEvent);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoteOpenOneMsgDownloadPipeA()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long RemoteOpenOneMsgDownloadPipeA (unsigned char *    szMailbox,
                                    long *             pPipeNumber)
{
    long lResult = GetServiceState();
    if (lResult)
    {
        return lResult;
    }
    TCHAR szTmpFile[_MAX_PATH], szPipeName[64];
    lResult = GetLocalTempFileName (szTmpFile);
    if (lResult)
    {
        return lResult;
    }

    PTHREAD_PROC_INFO pInfo = (PTHREAD_PROC_INFO)HeapAlloc (ghHeap,
                                                            HEAP_ZERO_MEMORY,
                                                            sizeof(THREAD_PROC_INFO));
    if (NULL == pInfo)
    {
        TraceMessage ("RemoteOpenOneMsgDownloadPipeA: Failed to allocate info structure");
        return E_OUTOFMEMORY;
    }
    DWORD dwThreadID, dwMsgID;
    HANDLE hThread, hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!hWaitEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenOneMsgDownloadPipeA: Failed to create event for I/O thread", lResult);
        goto ErrorExit;
    }

    pInfo->hTmpFile = CreateFile (szTmpFile,
                                  GENERIC_WRITE | GENERIC_READ,
                                  0,
                                  NULL,
                                  CREATE_ALWAYS,
                                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_DELETE_ON_CLOSE,
                                  NULL);
    if (INVALID_HANDLE_VALUE == pInfo->hTmpFile)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenOneMsgDownloadPipeA: Failed to create the temp file", lResult);
        goto ErrorExit;
    }

    *pPipeNumber = GetNextPipeID();

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    // Initialize the new security descriptor.
    InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

    // Add a NULL descriptor ACL to the security descriptor.
    SetSecurityDescriptorDacl (&sd, TRUE, (PACL)NULL, FALSE);

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = TRUE;

    wsprintf (szPipeName, SERVER_PIPE_NAME_FORMAT, *pPipeNumber);
    pInfo->hPipe = CreateNamedPipe (szPipeName,
                                    PIPE_ACCESS_OUTBOUND,
                                    PIPE_WAIT | PIPE_READMODE_BYTE | PIPE_TYPE_BYTE,
                                    1,
                                    IO_BUFFERSIZE,
                                    IO_BUFFERSIZE,
                                    0,
                                    &sa);
    if (INVALID_HANDLE_VALUE == pInfo->hPipe || ERROR_INVALID_PARAMETER == (DWORD)pInfo->hPipe)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenMsgDownloadPipeA: Failed to create pipe", lResult);
        goto ErrorExit;
    }

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    lstrcpy (g_IOInfo.szObject, (LPSTR)szMailbox);
    g_IOInfo.Action           = IO_MOVE_NEXT_MSG_TO_FILE;
    g_IOInfo.hActionCompleted = hWaitEvent;
    g_IOInfo.hTmpFile         = pInfo->hTmpFile;
    g_IOInfo.pdwData          = &dwMsgID;
    g_IOInfo.phLastError      = &lResult;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    if (lResult)
    {
        goto ErrorExit;
    }

    hThread = CreateThread (NULL,
                            0,
                            (LPTHREAD_START_ROUTINE)OneMsgDownloadPipeThread,
                            (LPVOID)pInfo,
                            0,
                            &dwThreadID);
    if (hThread)
    {
        TraceDebugger ("RemoteOpenOneMsgDownloadPipeA: Message Download thread spawned. ID: %X", dwThreadID);
        CloseHandle (hThread);
        pInfo = NULL; // The thread will free this memory
        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        lstrcpy (g_IOInfo.szObject, (LPSTR)szMailbox);
        g_IOInfo.Action           = IO_DELETE_MSG_IN_MAILBOX;
        g_IOInfo.hActionCompleted = NULL;
        g_IOInfo.dwObjID          = dwMsgID;
        g_IOInfo.phLastError      = NULL;
        LeaveCriticalSection (&g_csIOInfo);
    }
    else
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("RemoteOpenOneMsgDownloadPipeA: Failed to create pipe thread", lResult);
    }

ErrorExit:
    if (pInfo)
    {
        if (pInfo->hTmpFile)
        {
            CloseHandle (pInfo->hTmpFile);
        }
        if (pInfo->hPipe)
        {
            CloseHandle (pInfo->hPipe);
        }
        HeapFree (ghHeap, 0, pInfo);
    }
    if (hWaitEvent)
    {
        CloseHandle (hWaitEvent);
    }
    #ifdef _DEBUG
    if (lResult && S_FALSE != lResult)
    {
        TraceResult ("RemoteOpenOneMsgDownloadPipeA", lResult);
    }
    #endif // _DEBUG
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    OneMsgDownloadPipeThread()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI OneMsgDownloadPipeThread (PTHREAD_PROC_INFO pInfo)
{
    DWORD dwBytesRead, dwBytesWritten;
    BYTE abBuffer[IO_BUFFERSIZE];
    HRESULT hResult = S_OK;

    SetFilePointer (pInfo->hTmpFile, 0, NULL, FILE_BEGIN);

    ConnectNamedPipe (pInfo->hPipe, NULL);

    do
    {
        if (!ReadFile (pInfo->hTmpFile, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
        {
            hResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("OneMsgDownloadPipeThread: Failed to read the tmp file", hResult);
        }
        if (dwBytesRead && !hResult)
        {
            if (!WriteFile (pInfo->hPipe, abBuffer, dwBytesRead, &dwBytesWritten, NULL))
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("OneMsgDownloadPipeThread: Failed to write to the pipe", hResult);
            }
        }
    } while (dwBytesRead && !hResult);

    TraceResult ("OneMsgDownloadPipeThread", hResult);
    CloseHandle (pInfo->hPipe);
    CloseHandle (pInfo->hTmpFile);
    HeapFree (ghHeap, 0, pInfo);
    return hResult;
}

// End of file for RXPRPC.CPP


SERVICE.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      SERVICE.CPP
//
//  Description
//      This is the file where the entry point is (WinMain).
//      Here we also implement the registration and unregistration of
//      the RPC server.
//      The server register its services with NAMED PIPES as the exposed
//      protocol sequence for RPC calls. This server can only be run on
//      Windows NT platforms. Windows 95 does not support the creation of
//      named pipes.
//      This is where we register the NT service with the Service control
//      manager and where the service control handler is implemented
//
//      The WinMain, function can take certain paramenters from the command
//      line to control the action of the executable
//
//      Argument        Action
//      ========        ======
//      <None>          If normally started by the Service control manager,
//                      the WINDS NT Service starts and it is ready to accept
//                      request from WINDS MAPI service providers and from
//                      the WINDS administrator.
//                      If started as an application, a message dialog will
//                      come up to allow interactive install and removal of
//                      the service.
//
//      APP             Start the service as an APPLICATION EXECUTABLE, as
//                      opposed to an NT SERVICE. This is useful for
//                      debugging the WINDS server.
//
//      INSTALL         Adds the WINDS NT Service to the Service Control
//                      Manager list, modify the registry entries and starts
//                      the NT services normally.
//
//      REMOVE          Remove the WINDS NT service from the Service
//                      control list, and stops the WINDS service, halting
//                      services WINDS MAPI providers and WINDS administrators
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include <WINDOWSX.H>
#include "WRAP3D.H"
#include <RPC.H>
#include "WINDS.H"     // Header file generated by the MIDL compiler
#include "WDSADM.H"    // Header file generated by the MIDL compiler
#include "COMMON.H"

extern "C"
{
    BOOL WINAPI InitRPCServer
                        ();
    BOOL WINAPI TerminateAllRPCServer
                        ();
    void WINAPI RegisterWindsPrivateClass
                        ();
    void WindsServiceMain
                        (DWORD                      dwArgc,
                         LPTSTR *                   pszArgv);
    void WindsServiceCtrlHandler
                        (DWORD                      dwControlCode);
    void WINAPI TerminateService
                        (HRESULT                    hErrror);
    HRESULT WINAPI SendStatusToSCM
                        (DWORD                      dwServiceState,
                         HRESULT                    hError,
                         DWORD                      dwCheckPoint,
                         DWORD                      dwWait);
    HRESULT WINAPI InstallWindsService
                        (DWORD                      dwStartType,
                         LPTSTR                     szFullPathToServiceExe,
                         BOOL                       fStartImmediately);
    HRESULT WINAPI RemoveWindsService
                        ();
    HRESULT WINAPI InitWindsService
                        ();
    void WINAPI PauseWindsService
                        ();
    void WINAPI ResumeWindsService
                        ();
    void WINAPI StopWindsService
                        ();
    BOOL CALLBACK GenericDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    BOOL CALLBACK InstallDlgProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);
    LRESULT CALLBACK PrivateDlgClassProc
                        (HWND                       hDlg,
                         UINT                       message,
                         WPARAM                     wParam,
                         LPARAM                     lParam);

    TCHAR g_szAppTitle[] = TEXT("WINDS Messaging Host");
    HINSTANCE ghInstance = NULL;
    HANDLE ghHeap = NULL;
    IOTHREADINFO     g_IOInfo = { 0 };
    TCHAR            g_szDataFile[MAX_PATH] = { 0 };
    HRESULT          g_hServerState = HRESULT_FROM_WIN32(ERROR_BUSY);
    CRITICAL_SECTION g_csIOInfo;
    CRITICAL_SECTION g_csNotifLinks;
    CRITICAL_SECTION g_csServiceState;
    CRITICAL_SECTION g_csPipeID;
    DWORD g_dwNextPipeID = MIN_PIPE_NUMBER;

    SERVICE_STATUS_HANDLE   g_ssHandle = NULL;
    HANDLE                  g_hTerminateEvent = NULL;
    LPVOID                  g_pctl3d = NULL;
};

CObjectList GlobalObjectMap;

#define WINDS_SERVICE_NAME          TEXT("WINDS Messaging Host")
#define WINDS_SERVICE_NAME_SHORT    TEXT("WindsMessagingHost")
#define WINDS_SERVICE_EXECUTABLE    TEXT("\\WINDSSRV.EXE")

///////////////////////////////////////////////////////////////////////////////
//    WindsServiceMain()
//
//    Parameters
//      { Refer to Win32 API documentation on ServiceMain }
//
//    Purpose
//
//    Return Value
//      None
//
void WindsServiceMain (DWORD dwArgc, LPTSTR * pszArgv)
{
    g_ssHandle = RegisterServiceCtrlHandler (WINDS_SERVICE_NAME,
                                             (LPHANDLER_FUNCTION)WindsServiceCtrlHandler);
    if (NULL == g_ssHandle)
    {
        TraceResult ("WindsServiceMain: Failed to register the control handle", HRESULT_FROM_WIN32(GetLastError()));
        return;
    }
    HRESULT hResult = SendStatusToSCM (SERVICE_START_PENDING, S_OK, 1, 5);
    if (hResult)
    {
        TerminateService (hResult);
        return;
    }
    g_hTerminateEvent = CreateEvent (0, TRUE, FALSE, 0);
    if (NULL == g_hTerminateEvent)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("WindsServiceMain: Failed to create terminate event", hResult);
        TerminateService (hResult);
        return;
    }

    hResult = SendStatusToSCM (SERVICE_START_PENDING, S_OK, 2, 1);
    if (!hResult)
    {
        hResult = InitWindsService();
        if (!hResult)
        {
            hResult = SendStatusToSCM (SERVICE_RUNNING, S_OK, 0, 0);
            // If an error occured, terminate the service
            if (hResult)
            {
                StopWindsService();
            }
        }
    }
    if (S_OK == hResult)
    {
        WaitForSingleObject (g_hTerminateEvent, INFINITE);
    }
    TerminateService (hResult);

}

///////////////////////////////////////////////////////////////////////////////
//    TerminateService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
void WINAPI TerminateService (HRESULT hError)
{
    if (g_hTerminateEvent)
    {
        CloseHandle (g_hTerminateEvent);
    }
    if (g_ssHandle)
    {
        SendStatusToSCM (SERVICE_STOPPED, hError, 0, 0);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    SendStatusToSCM()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
HRESULT WINAPI SendStatusToSCM (DWORD   dwServiceState,
                                HRESULT hError,
                                DWORD   dwCheckPoint,
                                DWORD   dwWait)
{
    SERVICE_STATUS ssStatus = { 0 };
    ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ssStatus.dwCurrentState = dwServiceState;
    if (SERVICE_START_PENDING == dwServiceState)
    {
        ssStatus.dwControlsAccepted = 0;
    }
    else
    {
        ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
                                      SERVICE_ACCEPT_PAUSE_CONTINUE |
                                      SERVICE_ACCEPT_SHUTDOWN;
    }
    if (hError)
    {
        ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
        ssStatus.dwServiceSpecificExitCode = (DWORD)hError;
    }
    ssStatus.dwCheckPoint = dwCheckPoint;
    ssStatus.dwWaitHint = (dwWait * 1000);

    HRESULT hResult = S_OK;
    if (!SetServiceStatus (g_ssHandle, &ssStatus))
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("SendStatusToSCM: Failed to set service status", hResult);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    WindsServiceCtrlHandler()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None
//
void WindsServiceCtrlHandler (DWORD dwControlCode)
{
    switch (dwControlCode)
    {
        case SERVICE_CONTROL_SHUTDOWN :
        case SERVICE_CONTROL_STOP :
            SendStatusToSCM (SERVICE_STOP_PENDING, S_OK, 1, 10);
            StopWindsService();
            SendStatusToSCM (SERVICE_STOPPED, S_OK, 0, 0);
            break;

        case SERVICE_CONTROL_PAUSE :
            SendStatusToSCM (SERVICE_PAUSE_PENDING, S_OK, 1, 1);
            PauseWindsService();
            SendStatusToSCM (SERVICE_PAUSED, S_OK, 0, 0);
            break;

        case SERVICE_CONTROL_CONTINUE :
            SendStatusToSCM (SERVICE_CONTINUE_PENDING, S_OK, 1, 1);
            ResumeWindsService();
            SendStatusToSCM (SERVICE_RUNNING, S_OK, 0, 0);
            break;

        case SERVICE_CONTROL_INTERROGATE :
            SendStatusToSCM (0, S_OK, 0, 0);
            break;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    InitWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI InitWindsService()
{
    HRESULT hResult = S_OK;
    LoadSettingsFromRegistry();
    SaveSettingsToRegistry();

    InitializeCriticalSection (&g_csIOInfo);
    InitializeCriticalSection (&g_csPipeID);
    InitializeCriticalSection (&g_csNotifLinks);
    InitializeCriticalSection (&g_csServiceState);

    g_IOInfo.hResumeEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (!g_IOInfo.hResumeEvent)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("InitWindsService: Failed to create sync object (EVENT)", hResult);
        return FALSE;
    }

    DWORD dwThreadID;
    HANDLE hThread = CreateThread (NULL,
                                   0,
                                   (LPTHREAD_START_ROUTINE)WorkingIOProc,
                                   NULL,
                                   0,
                                   &dwThreadID);
    if (!hThread)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("InitWindsService: Failed to create I/O thread", hResult);
        ReleaseSyncObj();
        return hResult;
    }
    TraceDebugger ("InitWindsService: IO thread spawned. ID: %X", dwThreadID);
    SetThreadPriority (hThread, THREAD_PRIORITY_HIGHEST);
    CloseHandle (hThread);

    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

    if (InitRPCServer())
    {
        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        g_IOInfo.Action           = IO_OPEN_STORAGE_FILE;
        g_IOInfo.hActionCompleted = hWaitEvent;
        g_IOInfo.phLastError      = &hResult;
        LeaveCriticalSection (&g_csIOInfo);
        WaitForSingleObject (hWaitEvent, INFINITE);
    }
    else
    {
        TraceMessage ("InitWindsService: Failed to initialize the RPC server");
        hResult = E_FAIL;
    }
    CloseHandle (hWaitEvent);

    EnterCriticalSection (&g_csIOInfo);
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.Action            = IO_LOAD_MAILBOX_NAMES;
    g_IOInfo.hActionCompleted  = NULL;
    g_IOInfo.phLastError       = NULL;
    LeaveCriticalSection (&g_csIOInfo);

    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    PauseWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI PauseWindsService()
{
    EnterCriticalSection (&g_csServiceState);
    g_hServerState = HRESULT_FROM_WIN32(ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
    LeaveCriticalSection (&g_csServiceState);
}

///////////////////////////////////////////////////////////////////////////////
//    ResumeWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI ResumeWindsService()
{
    EnterCriticalSection (&g_csServiceState);
    g_hServerState = S_OK;
    LeaveCriticalSection (&g_csServiceState);
}

///////////////////////////////////////////////////////////////////////////////
//    StopWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI StopWindsService()
{
    HANDLE hWaitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

    PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                 HEAP_ZERO_MEMORY,
                                                                 sizeof(WINDS_NOTIFICATION));
    if (pNotif)
    {
        pNotif->Event = SERVER_IS_SHUTTING_DOWN;
        EnterCriticalSection (&g_csIOInfo);
        SetEvent (g_IOInfo.hResumeEvent);
        g_IOInfo.Action           = IO_SENT_SHUTDOWN_NOTIFICATION;
        g_IOInfo.hActionCompleted = hWaitEvent;
        g_IOInfo.pNotif           = pNotif;
        g_IOInfo.phLastError      = NULL;
        LeaveCriticalSection (&g_csIOInfo);
        WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);
    }
    else
    {
        TraceMessage ("StopWindsService: Failed to create structure for shutdown notification");
    }
    TerminateAllRPCServer();
    RemoveAllNotifLinks();

    EnterCriticalSection (&g_csIOInfo);
    // Resume the thread so that it terminate ifself properly
    SetEvent (g_IOInfo.hResumeEvent);
    g_IOInfo.fAppIsTerminating = TRUE;
    g_IOInfo.hActionCompleted  = hWaitEvent;
    LeaveCriticalSection (&g_csIOInfo);
    WaitForSingleObject (hWaitEvent, GENERAL_TIME_OUT);

    CloseHandle (hWaitEvent);

    UnInitTraces ();

    ReleaseSyncObj();
}

///////////////////////////////////////////////////////////////////////////////
//    InstallWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI InstallWindsService (DWORD   dwStartType,
                                    LPTSTR  szFullPathToServiceExe,
                                    BOOL    fStartImmediately)
{
    HRESULT hResult = S_OK;
    SC_HANDLE schSCM, schWINDS;
    schSCM = OpenSCManager (0, 0, SC_MANAGER_CREATE_SERVICE);
    if (NULL == schSCM)
    {
        hResult = HRESULT_FROM_WIN32 (GetLastError());
    }
    else
    {
RestartInstall:
        schWINDS = CreateService (schSCM,
                                  WINDS_SERVICE_NAME_SHORT,
                                  WINDS_SERVICE_NAME,
                                  SERVICE_ALL_ACCESS,
                                  SERVICE_WIN32_OWN_PROCESS,
                                  dwStartType,
                                  SERVICE_ERROR_NORMAL,
                                  szFullPathToServiceExe,
                                  NULL,
                                  NULL,
                                  NULL,
                                  NULL,
                                  NULL);
        if (NULL == schWINDS)
        {
            hResult = HRESULT_FROM_WIN32 (GetLastError());
        }
        else
        {
            if (fStartImmediately)
            {
                StartService (schWINDS, 0, NULL);
            }
            CloseServiceHandle (schWINDS);
        }
        if (HRESULT_FROM_WIN32 (ERROR_SERVICE_EXISTS) == hResult)
        {
            RemoveWindsService();
            goto RestartInstall;
        }
        CloseServiceHandle (schSCM);
    }
    TraceResult ("InstallWindsService", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoveWindsService()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI RemoveWindsService()
{
    HRESULT hResult = S_OK;
    SC_HANDLE schSCM, schWINDS;
    schSCM = OpenSCManager (0, 0, SC_MANAGER_CREATE_SERVICE);
    if (NULL == schSCM)
    {
        hResult = HRESULT_FROM_WIN32 (GetLastError());
    }
    else
    {
        schWINDS = OpenService (schSCM,
                                WINDS_SERVICE_NAME_SHORT,
                                SERVICE_ALL_ACCESS);
        if (NULL == schWINDS)
        {
            hResult = HRESULT_FROM_WIN32 (GetLastError());
        }
        else
        {
            SERVICE_STATUS ssState;
            ControlService (schWINDS, SERVICE_CONTROL_STOP, &ssState);
            DeleteService (schWINDS);
            CloseServiceHandle (schWINDS);
        }
        CloseServiceHandle (schSCM);
    }
    TraceResult ("RemoveWindsService", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    WinMain()
//
//    Parameters
//      { Refer to Win32 SDK documentation }
//
//    Purpose
//      This is the entry function. The application will start executing here.
//
//    Return Value
//      Return code of the application to the system.
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCmdLine, int nCmdShow)
{
    InitTraces (TRACES_NO_ASSERT_DIALOG | TRACES_NO_COM_OUTPUT);
    //InitTraces (TRACES_NO_ASSERT_DIALOG);
    ghInstance = hInstance;
    ghHeap = GetProcessHeap();

    if (0 == lstrcmpi ("APP", pszCmdLine))
    {
        // Check for other instances of this application running. If one is found,
        // bring it to the foreground
        HWND hOtherInstance = FindWindow (MAINDLGCLASS, NULL);
        if (NULL != hOtherInstance)
        {
            HWND hChildWnd = GetLastActivePopup (hOtherInstance);
            if (hChildWnd != hOtherInstance)
            {
                BringWindowToTop (hOtherInstance);
            }
            else
            {
                SetForegroundWindow (hOtherInstance);
            }
            return 1;
        }

        RegisterWindsPrivateClass();

        if (S_OK != InitWindsService())
        {
            return -1;
        }

        DialogBoxParam (ghInstance, MAKEINTRESOURCE(IDD_WINDS_APP), NULL, GenericDlgProc, (LPARAM)TRUE);

        StopWindsService();

        return 0;
    }

    if (0 == lstrcmpi ("INSTALL", pszCmdLine))
    {
        TCHAR szFullPath[_MAX_PATH] = { 0 };
        GetCurrentDirectory (_MAX_PATH, szFullPath);
        lstrcat (szFullPath, WINDS_SERVICE_EXECUTABLE);
        if (S_OK != InstallWindsService (SERVICE_AUTO_START, szFullPath, TRUE))
        {
            return -1;
        }
        return 0;
    }

    if (0 == lstrcmpi ("REMOVE", pszCmdLine))
    {
        if (S_OK != RemoveWindsService())
        {
            return -1;
        }
        return 0;
    }

    SERVICE_TABLE_ENTRY ServiceTable[] =
    {
        { WINDS_SERVICE_NAME,
          (LPSERVICE_MAIN_FUNCTION)WindsServiceMain,
        },
        { NULL, NULL }
    };

    if (FALSE == StartServiceCtrlDispatcher (ServiceTable))
    {
        if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError())
        {
            RegisterWindsPrivateClass();
            DialogBoxParam (ghInstance, MAKEINTRESOURCE(IDD_WINDS_DIALOG), NULL, GenericDlgProc, (LPARAM)FALSE);
        }
    }
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//    InitRPCServer()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI InitRPCServer()
{
    #define MAX_NUM_OF_CALLS    65536
    RPC_STATUS status = RpcServerUseProtseqEp ((WINDS_RPC_STRING)WINDS_RPC_PROTOCOL,
                                               MAX_NUM_OF_CALLS,
                                               (WINDS_RPC_STRING)WINDS_PROVIDERS_RPC_ENDPOINT,
                                               NULL);  // Security descriptor
    if (status)
    {
        TraceMessage ("InitRPCServer: RpcServerUseProtseqEp failed (PROVIDER)");
        goto ErrorExit;
    }

    status = RpcServerRegisterIf (WINDSREM_v1_6_s_ifspec,   // Interface to register
                                  NULL,                     // MgrTypeUuid
                                  NULL);                    // MgrEpv; null means use default
    if (status)
    {
        TraceMessage ("InitRPCServer: RpcServerRegisterIf failed (PROVIDER)");
        goto ErrorExit;
    }

    status = RpcServerUseProtseqEp ((WINDS_RPC_STRING)WINDS_RPC_PROTOCOL,
                                    MAX_NUM_OF_CALLS,
                                    (WINDS_RPC_STRING)WINDS_ADMIN_RPC_ENDPOINT,
                                    NULL);  // Security descriptor
    if (status)
    {
        TraceMessage ("InitRPCServer: RpcServerUseProtseqEp failed (ADMIN)");
        goto ErrorExit;
    }

    status = RpcServerRegisterIf (WINDSADM_v1_0_s_ifspec,   // Interface to register
                                  NULL,                     // MgrTypeUuid
                                  NULL);                    // MgrEpv; null means use default
    if (status)
    {
        TraceMessage ("InitRPCServer: RpcServerRegisterIf failed (ADMIN)");
        goto ErrorExit;
    }

    status = RpcServerListen (1, MAX_NUM_OF_CALLS, TRUE);
    if (status)
    {
        TraceMessage ("InitRPCServer: RpcServerListen failed");
        goto ErrorExit;
    }
ErrorExit:
    if (status)
    {
        TraceResult ("InitRPCServer", MAKE_HRESULT(1, FACILITY_RPC, status));
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    TerminateAllRPCServer()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
BOOL WINAPI TerminateAllRPCServer()
{
    RPC_STATUS status = RpcMgmtStopServerListening (NULL);
    if (status)
    {
        TraceMessage ("TerminateRPCServer: RpcMgmtStopServerListening failed");
    }
    else
    {
        status = RpcServerUnregisterIf (NULL, NULL, FALSE);
        if (status)
        {
            TraceMessage ("TerminateRPCServer: RpcServerUnregisterIf failed");
        }
    }
    if (status)
    {
        TraceResult ("TerminateRPCServer", MAKE_HRESULT(1, FACILITY_RPC, status));
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    GetServiceState()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetServiceState()
{
    HRESULT hServiceState;
    EnterCriticalSection (&g_csServiceState);
    hServiceState = g_hServerState;
    LeaveCriticalSection (&g_csServiceState);
    return hServiceState;
}

///////////////////////////////////////////////////////////////////////////////
//    PrivateDlgClassProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//
LRESULT CALLBACK PrivateDlgClassProc (HWND    hDlg,
                                      UINT    message,
                                      WPARAM  wParam,
                                      LPARAM  lParam)
{
    return DefDlgProc (hDlg, message, wParam, lParam);
}

///////////////////////////////////////////////////////////////////////////////
//    RegisterWindsPrivateClass()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI RegisterWindsPrivateClass()
{
    WNDCLASS wc = { 0 };
    // Register the class for this application
    wc.hInstance        = ghInstance;
    wc.hCursor          = LoadCursor (NULL, IDC_ARROW);
    wc.lpfnWndProc      = PrivateDlgClassProc;
    wc.cbWndExtra       = DLGWINDOWEXTRA;
    wc.hIcon            = LoadIcon (ghInstance, MAKEINTRESOURCE (IDI_WINDSSERVICE));
    wc.lpszClassName    = MAINDLGCLASS;
    if (!RegisterClass (&wc))
    {
        TraceResult ("RegisterWindsPrivateClass: Failed to register the main class", HRESULT_FROM_WIN32(GetLastError()));
    }
}

///////////////////////////////////////////////////////////////////////////////
//    GenericDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK GenericDlgProc (HWND    hDlg,
                              UINT    message,
                              WPARAM  wParam,
                              LPARAM  lParam)
{
    HRESULT hResult;
    switch (message)
    {
        case WM_INITDIALOG :
            if (TRUE == lParam)
            {
                ShowWindow (hDlg, SW_MINIMIZE);
            }
            else
            {
                if (!g_pctl3d)
                {
                    BOOL f;
                    g_pctl3d = CTL3D_Initialize(ghInstance);
                    CTL3D_AutoSubclass(g_pctl3d, ghInstance, &f);
                }
                CenterDialogBox (hDlg);
            }
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDOK :
                case IDCANCEL :
                    PostMessage (hDlg, WM_SYSCOMMAND, SC_CLOSE, 0);
                    return TRUE;

                case IDC_INSTALL_SERVICE :
                    DialogBox (ghInstance, MAKEINTRESOURCE(IDD_INSTALL_OPTIONS), hDlg, InstallDlgProc);
                    break;

                case IDC_REMOVE_SERVICE :
                    hResult = RemoveWindsService();
                    if (S_OK == hResult)
                    {
                        MessageBox (hDlg,
                                    TEXT("Service successfully removed"),
                                    TEXT("WINDS Services - Remove"),
                                    MB_OK | MB_ICONEXCLAMATION);
                    }
                    else
                    {
                        MessageBox (hDlg,
                                    TEXT("Failed to remove the service"),
                                    TEXT("WINDS Services - Remove"),
                                    MB_OK | MB_ICONSTOP);
                    }
                    break;
            }
            break;

        case WM_SYSCOMMAND:
            if (wParam == SC_CLOSE)
            {
                CTL3D_Uninitialize(g_pctl3d);
                g_pctl3d = NULL;
                EndDialog (hDlg, TRUE);
                return TRUE;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    InstallDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK InstallDlgProc (HWND    hDlg,
                              UINT    message,
                              WPARAM  wParam,
                              LPARAM  lParam)
{
    TCHAR szFullPath[_MAX_PATH];
    DWORD dwStart;
    BOOL fStartNow;
    switch (message)
    {
        case WM_INITDIALOG :
            CenterDialogBox (hDlg);
            Edit_LimitText (GetDlgItem (hDlg, IDC_SERVICE_FILEPATH), _MAX_PATH - 1);
            GetCurrentDirectory (_MAX_PATH, szFullPath);
            lstrcat (szFullPath, WINDS_SERVICE_EXECUTABLE);
            SetWindowText (GetDlgItem (hDlg, IDC_SERVICE_FILEPATH), szFullPath);
            Button_SetCheck (GetDlgItem (hDlg, IDC_AUTO_START), BST_CHECKED);
            Button_SetCheck (GetDlgItem (hDlg, IDC_START_NOW), BST_CHECKED);

return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDOK :
                    GetWindowText (GetDlgItem (hDlg, IDC_SERVICE_FILEPATH), szFullPath, _MAX_PATH);
                    if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_AUTO_START)))
                    {
                        dwStart = SERVICE_AUTO_START;
                    }
                    else
                    {
                        dwStart = SERVICE_DEMAND_START;
                    }
                    if (BST_CHECKED == Button_GetCheck (GetDlgItem (hDlg, IDC_START_NOW)))
                    {
                        fStartNow = TRUE;
                    }
                    else
                    {
                        fStartNow = FALSE;
                    }
                    if (lstrlen (szFullPath) && S_OK == InstallWindsService (dwStart, szFullPath, fStartNow))
                    {
                        MessageBox (hDlg,
                                    TEXT("Service successfully installed"),
                                    TEXT("WINDS Services - Install"),
                                    MB_OK | MB_ICONEXCLAMATION);
                    }
                    else
                    {
                        MessageBox (hDlg,
                                    TEXT("Failed to install the service"),
                                    TEXT("WINDS Services - Install"),
                                    MB_OK | MB_ICONSTOP);
                    }
                    // Fall through
                case IDCANCEL :
                    EndDialog (hDlg, TRUE);
                    return TRUE;
            }
    }
    return FALSE;
}

// End of file for SERVICE.CPP


STORAGE.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      STORAGE.CPP
//
//  Description
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"
#include "WDSADM.H"    // Header file generated by the MIDL compiler
#include "COMMON.H"
#include "STDIO.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)        TraceInfoMessage(a)
#define InfoTraceRaw(a)     TraceRaw(a)
#define InfoTraceProp(a)    TraceProp(a)
#else
#define InfoTrace(a)        0
#define InfoTraceRaw(a)     0
#define InfoTraceProp(a)    0
#endif // DO_INFO_TRACES

extern "C"
{
    HRESULT WINAPI LoadObjectProperties
                        (LPSTORAGE                  pStorage,
                         WINDS_AB_OBJTYPE           Type,
                         LPTSTR                     pszObjectType,
                         LPMALLOC                   pMalloc);
    HRESULT WINAPI GetDLProperties
                        (LPSTORAGE                  pStorage,
                         LPOLESTR                   pwcsDLName,
                         PDIST_LIST_INFO            pDLInfo,
                         BOOL                       fGetMembers = FALSE);
    HRESULT WINAPI SetDLProperties
                        (LPSTORAGE                  pStorage,
                         LPOLESTR                   pwcsDLName,
                         PDIST_LIST_INFO            pDLInfo);
    HRESULT WINAPI DLPropsAndMembers
                        (LPSTORAGE                  pStorage,
                         PDIST_LIST_INFO            pDLInfo,
                         BOOL                       fGetProps);
    HRESULT WINAPI CreateDistributionList
                        (LPSTORAGE                  pStorage,
                         PDIST_LIST_INFO            pDLInfo);
    HRESULT WINAPI DeleteDistributionList
                        (LPSTORAGE                  pStorage,
                         PDIST_LIST_INFO            pDLInfo);
    HRESULT WINAPI GetNextObjID
                        (LPSTORAGE                  pStorage,
                         DWORD &                    dwNextID);
    HRESULT WINAPI ReadABContainerInfo
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc,
                         DWORD                      dwContainerID,
                         HANDLE                     hPipe);
    HRESULT WINAPI EnumContainerObjs
                        (LPSTORAGE                  pDirectoryStg,
                         HANDLE                     hPipe,
                         WINDS_AB_OBJTYPE           Type,
                         LPMALLOC                   pMalloc);
    HRESULT WINAPI GetDLRecipientsInfo
                        (LPSTORAGE                  pMBDirectory,
                         LPSTORAGE                  pDLDirectory,
                         HANDLE                     hPipe,
                         LPOLESTR                   pwcsDLName);
    HRESULT WINAPI CreateMailbox
                        (LPSTORAGE                  pMBDirectory,
                         PMAILBOX_INFO              pMBInfo);
    HRESULT WINAPI MailboxProps
                        (LPSTORAGE                  pMBDirectory,
                         PMAILBOX_INFO              pMBInfo,
                         BOOL                       fRetrieve);
    HRESULT WINAPI GetGWContainerCount
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc,
                         DWORD                      dwContainerID,
                         DWORD *                    pdwContainerCount);
    HRESULT WINAPI CopyMsgFromFile
                        (LPSTORAGE                  pStorage,
                         HANDLE                     hFile,
                         LPTSTR                     pszMailbox,
                         LPTSTR                     pszHeader);
    HRESULT WINAPI RemoveMessageFromMailbox
                        (LPSTORAGE                  pStorage,
                         LPTSTR                     pszMailbox,
                         DWORD                      dwStreamID);
    HRESULT WINAPI UpdateMailboxHeaders
                        (LPSTORAGE                  pStorage,
                         LPSTORAGE                  pMBStg,
                         LPTSTR                     pszMailbox,
                         DWORD                      dwStreamID,
                         LPTSTR                     pszHeader,
                         BOOL                       fAddMsgToHeadersList);
    HRESULT WINAPI LoadServerObjects
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc);
    HRESULT WINAPI OpenMsgsDataFile
                        (LPSTORAGE *                ppStorage);
    HRESULT WINAPI GetMailBoxInfo
                        (LPSTORAGE                  pStorage,
                         LPOLESTR                   pwcsName,
                         PMAILBOX_INFO              pInfo);
    HRESULT WINAPI SetMailBoxInfo
                        (LPSTORAGE                  pStorage,
                         LPOLESTR                   pwcsName,
                         PMAILBOX_INFO              pInfo);
    HRESULT WINAPI DeleteMailbox
                        (LPSTORAGE                  pStorage,
                         PMAILBOX_INFO              pInfo);
    HRESULT WINAPI EmptyMailBoxMsgs
                        (LPSTORAGE                  pStorage,
                         LPTSTR                     pszMailbox);
    HRESULT WINAPI CopyHeadersToFile
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc,
                         HANDLE                     hFile,
                         LPTSTR                     pszMailbox);
    HRESULT WINAPI CopyMsgToFile
                        (LPSTORAGE                  pStorage,
                         LPTSTR                     pszMailbox,
                         HANDLE                     hFile,
                         ULONG *                    pulMsgLen,
                         DWORD                      dwStreamID,
                         BOOL                       fDeleteAfterCopy);
    HRESULT WINAPI CopyStreamToFile
                        (LPSTREAM                   pStream,
                         HANDLE                     hFile,
                         ULONG *                    pulWritten);
    HRESULT WINAPI CheckForPendingMailboxMsgs
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc,
                         LPTSTR                     szMailbox,
                         DWORD *                    pdwMsgCount);
    HRESULT WINAPI GetNextMailboxMsg
                        (LPSTORAGE                  pStorage,
                         LPMALLOC                   pMalloc,
                         LPTSTR                     szMailbox,
                         DWORD *                    pdwMsgID,
                         HANDLE                     hFile);
};

///////////////////////////////////////////////////////////////////////////////
//    WorkingIOProc()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
DWORD WINAPI WorkingIOProc()
{
    //InfoTrace ("WorkingIOProc: Just starting up");
    // Standard COM initialization so that we may use IStorage and IStream
    LPMALLOC pMalloc;
    HRESULT hResult = CoInitialize (NULL);
    TraceResult ("WorkingIOProc: Failed to initialize the COM libraries", hResult);
    if (!hResult)
    {
        hResult = CoGetMalloc (MEMCTX_TASK, &pMalloc);
        TraceResult ("WorkingIOProc: Failed to get an IMalloc interface", hResult);
    }
    if (FAILED(hResult))
    {
        InfoTrace ("WorkingIOProc: Just bailing out now");
        CoUninitialize();
        g_hServerState = CO_E_NOTINITIALIZED;
        return g_hServerState;
    }
    LPSTORAGE pStorage = NULL;

    DWORD dwException = 0;

    while (TRUE)
    {
        WaitForSingleObject (g_IOInfo.hResumeEvent, INFINITE);
        //InfoTrace ("WorkingIOProc: Resumed. About to request the critical section...");
        EnterCriticalSection (&g_csIOInfo);
        //InfoTrace ("WorkingIOProc: ...Executing in critical section");
        if (g_IOInfo.fAppIsTerminating)
        {
            InfoTrace ("WorkingIOProc: We are going down");
            if (pStorage)
            {
                pStorage->Release();
                pStorage = NULL;
            }
            break; // out of the WHILE() loop
        }
ProcessNextAction:
        switch (g_IOInfo.Action)
        {
            case IO_OPEN_STORAGE_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_OPEN_STORAGE_FILE");
                __try
                {
                    hResult = OpenMsgsDataFile (&pStorage);
                    if (hResult)
                    {
                        g_hServerState = HRESULT_FROM_WIN32(ERROR_STATIC_INIT);
                    }
                    else
                    {
                        g_hServerState = S_OK; // Server is ready to accept requests
                    }
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_LOAD_MAILBOX_NAMES :
                InfoTrace ("WorkingIOProc: Servicing IO_LOAD_MAILBOX_NAMES");
                __try
                {
                    hResult = LoadServerObjects (pStorage, pMalloc);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                // Restore notification links with the clients from a previous session
                g_IOInfo.Action = IO_RESTORE_CLIENT_NOTIFICATIONS;
                goto ProcessNextAction;
                break;

            case IO_GET_CONTAINER_RECIPIENTS :
                InfoTrace ("WorkingIOProc: Servicing IO_GET_CONTAINER_RECIPIENTS");
                __try
                {
                    hResult = ReadABContainerInfo (pStorage,
                                                   pMalloc,
                                                   g_IOInfo.dwObjID,
                                                   g_IOInfo.hTmpFile);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_CREATE_NEW_MAILBOX :
                InfoTrace ("WorkingIOProc: Servicing IO_CREATE_NEW_MAILBOX");
                __try
                {
                    hResult = CreateMailbox (pStorage, g_IOInfo.pMBInfo);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_EMPTY_MAILBOX :
                InfoTrace ("WorkingIOProc: Servicing IO_EMPTY_MAILBOX");
                __try
                {
                    hResult = EmptyMailBoxMsgs (pStorage, g_IOInfo.szObject);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_REMOVE_MAILBOX :
                InfoTrace ("WorkingIOProc: Servicing IO_REMOVE_MAILBOX");
                __try
                {
                    hResult = DeleteMailbox (pStorage, g_IOInfo.pMBInfo);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_GET_MAILBOX_PROPERTIES :
                InfoTrace ("WorkingIOProc: Servicing IO_GET_MAILBOX_PROPERTIES");
                __try
                {
                    hResult = MailboxProps (pStorage, g_IOInfo.pMBInfo, TRUE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_SET_MAILBOX_PROPERTIES :
                InfoTrace ("WorkingIOProc: Servicing IO_SET_MAILBOX_PROPERTIES");
                __try
                {
                    hResult = MailboxProps (pStorage, g_IOInfo.pMBInfo, FALSE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_COPY_MSG_FROM_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_COPY_MSG_FROM_FILE");
                __try
                {
                    hResult = CopyMsgFromFile (pStorage,
                                               g_IOInfo.hTmpFile,
                                               g_IOInfo.szObject,
                                               g_IOInfo.szHeader);
                    if (g_IOInfo.fCloseHandle)
                    {
                        CloseHandle (g_IOInfo.hTmpFile);
                    }
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_COPY_HEADERS_TO_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_COPY_HEADERS_TO_FILE");
                __try
                {
                    hResult = CopyHeadersToFile (pStorage,
                                                 pMalloc,
                                                 g_IOInfo.hTmpFile,
                                                 g_IOInfo.szObject);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_CHECK_PENDING_MESSAGES :
                InfoTrace ("WorkingIOProc: Servicing IO_CHECK_PENDING_MESSAGES");
                __try
                {
                    hResult = CheckForPendingMailboxMsgs (pStorage,
                                                          pMalloc,
                                                          g_IOInfo.szObject,
                                                          g_IOInfo.pdwData);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_COPY_MSG_TO_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_COPY_MSG_TO_FILE");
                __try
                {
                    hResult = CopyMsgToFile (pStorage,
                                             g_IOInfo.szObject,
                                             g_IOInfo.hTmpFile,
                                             g_IOInfo.pdwData,
                                             g_IOInfo.dwObjID,
                                             FALSE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_MOVE_MSG_TO_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_MOVE_MSG_TO_FILE");
                __try
                {
                    hResult = CopyMsgToFile (pStorage,
                                             g_IOInfo.szObject,
                                             g_IOInfo.hTmpFile,
                                             g_IOInfo.pdwData,
                                             g_IOInfo.dwObjID,
                                             TRUE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_MOVE_NEXT_MSG_TO_FILE :
                InfoTrace ("WorkingIOProc: Servicing IO_MOVE_NEXT_MSG_TO_FILE");
                __try
                {
                    hResult = GetNextMailboxMsg (pStorage,
                                                 pMalloc,
                                                 g_IOInfo.szObject,
                                                 g_IOInfo.pdwData,
                                                 g_IOInfo.hTmpFile);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_DELETE_MSG_IN_MAILBOX :
                InfoTrace ("WorkingIOProc: Servicing IO_DELETE_MSG_IN_MAILBOX");
                __try
                {
                    hResult = RemoveMessageFromMailbox (pStorage,
                                                        g_IOInfo.szObject,
                                                        g_IOInfo.dwObjID);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_CREATE_DISTRIBUTION_LIST :
                InfoTrace ("WorkingIOProc: Servicing IO_CREATE_DISTRIBUTION_LIST");
                __try
                {
                    hResult = CreateDistributionList (pStorage, g_IOInfo.pDLInfo);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_DELETE_DISTRIBUTION_LIST :
                InfoTrace ("WorkingIOProc: Servicing IO_DELETE_DISTRIBUTION_LIST");
                __try
                {
                    hResult = DeleteDistributionList (pStorage, g_IOInfo.pDLInfo);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_GET_DL_PROPERTIES :
                InfoTrace ("WorkingIOProc: Servicing IO_GET_DL_PROPERTIES");
                __try
                {
                    hResult = DLPropsAndMembers (pStorage, g_IOInfo.pDLInfo, TRUE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_SET_DL_PROPERTIES :
                InfoTrace ("WorkingIOProc: Servicing IO_SET_DL_PROPERTIES");
                __try
                {
                    hResult = DLPropsAndMembers (pStorage, g_IOInfo.pDLInfo, FALSE);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_GET_GW_CONTAINER_COUNT :
                InfoTrace ("WorkingIOProc: Servicing IO_GET_GW_CONTAINER_COUNT");
                __try
                {
                    hResult = GetGWContainerCount (pStorage,
                                                   pMalloc,
                                                   g_IOInfo.dwObjID,
                                                   g_IOInfo.pdwData);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_SENT_SHUTDOWN_NOTIFICATION :
                InfoTrace ("WorkingIOProc: Servicing IO_SENT_SHUTDOWN_NOTIFICATION");
                __try
                {
                    ClientNotifThreadProc (g_IOInfo.pNotif);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_RESTORE_CLIENT_NOTIFICATIONS :
                InfoTrace ("WorkingIOProc: Servicing IO_RESTORE_CLIENT_NOTIFICATIONS");
                __try
                {
                    PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                                HEAP_ZERO_MEMORY,
                                                                                sizeof(WINDS_NOTIFICATION));
                    if (NULL == pNotif)
                    {
                        TraceMessage ("WorkingIOProc: Failed to allocate notification structure");
                    }
                    else
                    {
                        pNotif->Event = SERVER_HAS_RESTARTED;
                        ClientNotifThreadProc (pNotif);
                    }
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            case IO_ADMIN_GET_SERVER_MAILBOXES :
                InfoTrace ("WorkingIOProc: Servicing IO_ADMIN_GET_SERVER_MAILBOXES");
                __try
                {
                    hResult = WaitForClientConnection (g_IOInfo.hTmpFile, 15);
                    if (S_OK == hResult)
                    {
                        hResult = EnumContainerObjs (pStorage, g_IOInfo.hTmpFile, SERVER_USER_MAILBOX, pMalloc);
                    }
                    CloseHandle (g_IOInfo.hTmpFile);
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;
            case IO_ADMIN_GET_SERVER_DISTLISTS :
                InfoTrace ("WorkingIOProc: Servicing IO_ADMIN_GET_SERVER_DISTLISTS");
                __try
                {
                    LPSTORAGE pDirectoryStg;
                    hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS,
                                                     NULL,
                                                     OPEN_FLAGS,
                                                     NULL,
                                                     0,
                                                     &pDirectoryStg);
                    if (S_OK == hResult)
                    {
                        hResult = WaitForClientConnection (g_IOInfo.hTmpFile, 15);
                        if (S_OK == hResult)
                        {
                            hResult = EnumContainerObjs (pDirectoryStg,
                                                         g_IOInfo.hTmpFile,
                                                         SERVER_DISTRIBUTION_LIST,
                                                         pMalloc);
                        }
                        CloseHandle (g_IOInfo.hTmpFile);
                        pDirectoryStg->Release();
                    }
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    dwException = GetExceptionCode();
                }
                break;

            default :
                TraceString1 ("WorkingIOProc: Unknown action requested: %d", g_IOInfo.Action);
                hResult = (DWORD)HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
                break;
        }
        if (dwException)
        {
            TraceString2 ("WorkingIOProc: Exception %d occured processing action id %d", dwException, g_IOInfo.Action);
            dwException = 0;
            hResult = (DWORD)HRESULT_FROM_WIN32(ERROR_EXCEPTION_IN_SERVICE);
        }

        __try
        {
            if (g_IOInfo.phLastError)
            {
                *g_IOInfo.phLastError = hResult;
            }
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            dwException = GetExceptionCode();
            TraceString2 ("WorkingIOProc: Exception %d occured setting return error code for action id %d", dwException, g_IOInfo.Action);
            dwException = 0;
        }

        if (g_IOInfo.hActionCompleted)
        {
            SetEvent (g_IOInfo.hActionCompleted);
        }
        g_IOInfo.Action = IO_IDLE;
        g_IOInfo.phLastError = NULL;
        g_IOInfo.hActionCompleted = NULL;
        g_IOInfo.pdwData = NULL;

        //InfoTrace ("WorkingIOProc: About to leave critical section...");
        LeaveCriticalSection (&g_csIOInfo);
        //InfoTrace ("WorkingIOProc: ...Left critical section");
    }
    pMalloc->Release();

    CoUninitialize();

    //InfoTrace ("WorkingIOProc: Just closing down");
    if (g_IOInfo.hActionCompleted)
    {
        SetEvent (g_IOInfo.hActionCompleted);
    }
    //InfoTrace ("WorkingIOProc: About to leave critical section...");
    LeaveCriticalSection (&g_csIOInfo);
    //InfoTrace ("WorkingIOProc: ...Left critical section");
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//    OpenMsgsDataFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI OpenMsgsDataFile (LPSTORAGE * ppStorage)
{
    DBINFO_DATA dbInfo = { 0 };
    LPSTREAM pStream;
    HRESULT hResult = S_OK;

#ifdef UNICODE
    LPWSTR szFileName = g_szDataFile;
#else
    WCHAR wcsFileName[MAX_PATH];
    LPWSTR szFileName = wcsFileName;
    hResult = AnsiToUnicode (g_szDataFile, wcsFileName, MAX_PATH);
    if (hResult)
    {
        TraceResult ("OpenMsgDataFile: Failed to convert file name to UNICODE", hResult);
        return hResult;
    }
#endif // UNICODE

    hResult = StgIsStorageFile (szFileName);
    if (S_OK != hResult)
    {
        if (S_FALSE == hResult)
        {
            hResult = S_OK;
            goto CreateFileNow;
        }
        else
        {
            if (STG_E_FILENOTFOUND != hResult)
            {
                // else, if any other error occurred
                TraceResult ("OpenMsgsDataFile: StgIsStorageFile failed", hResult);
                return hResult;
            }
            else
            {
                goto CreateFileNow;
            }
        }
    }

    hResult = StgOpenStorage (szFileName, NULL, OPEN_FLAGS, NULL, 0, ppStorage);
    if (!hResult)
    {
        hResult = (*ppStorage)->OpenStream (DBINFO_STREAM, NULL, OPEN_FLAGS, 0, &pStream);
        if (hResult)
        {
            TraceResult ("OpenMsgsDataFile: Failed to open DBINFO stream", hResult);
            if (STG_E_FILENOTFOUND == hResult)
            {
                hResult = STG_E_FILENOTFOUND;
            }
        }
        else
        {
            hResult = pStream->Read (&dbInfo, sizeof(DBINFO_DATA), NULL);
            TraceResult ("OpenMsgsDataFile: Failed to read from the DBINFO stream", hResult);
            if (!hResult)
            {
                // If the database version does not match, we will delete
                // it and create a new one.
                if (WINDS_DATABASE_VERSION != dbInfo.dwVersion)
                {
                    hResult = STG_E_FILENOTFOUND;
                }
            }
            pStream->Release();
        }
        if (hResult)
        {
            (*ppStorage)->Release();
            *ppStorage = NULL;
        }
    }
    if (STG_E_FILENOTFOUND == hResult)
    {
CreateFileNow:
        DeleteFile (g_szDataFile);
        hResult = StgCreateDocfile (szFileName, CREATE_FLAGS, 0, ppStorage);
        TraceResult ("OpenMsgsDataFile: failed to open or create storage object", hResult);
        if (!hResult)
        {
            hResult = (*ppStorage)->CreateStream (DBINFO_STREAM, CREATE_FLAGS, 0, 0, &pStream);
            TraceResult ("OpenMsgsDataFile: Failed to create DBINFO stream", hResult);
            if (!hResult)
            {
                dbInfo.dwVersion = WINDS_DATABASE_VERSION;
                hResult = pStream->Write (&dbInfo, sizeof(DBINFO_DATA), NULL);
                TraceResult ("OpenMsgsDataFile: Failed to write to the DBINFO stream", hResult);
                pStream->Release();
                if (!hResult)
                {
                    hResult = (*ppStorage)->CreateStream (OBJ_ID_POOL_STREAM, CREATE_FLAGS, 0, 0, &pStream);
                    TraceResult ("OpenMsgsDataFile: Failed to create Object ID Pool stream", hResult);
                    if (!hResult)
                    {
                        DWORD dwZero = 0;
                        hResult = pStream->Write (&dwZero, sizeof(DWORD), NULL);

pStream->Release();
                    }
                }
            }
        }
        if (!hResult)
        {
            CreateDLsDirectory (*ppStorage);
            CreateGatewayDirectories (*ppStorage);
            CreatePublicFoldersDirectory (*ppStorage);
        }
        if (hResult)
        {
            DeleteFile (g_szDataFile);
        }
    }
    TraceResult ("OpenMsgsDataFile", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    ReadABContainerInfo()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI ReadABContainerInfo (LPSTORAGE pStorage,
                                    LPMALLOC  pMalloc,
                                    DWORD     dwContainerID,
                                    HANDLE    hPipe)
{
    BOOL fGalContainer = FALSE;
    DWORD dwLastContainerDone = 0;
    LPSTORAGE pDirectoryStg;
    HRESULT hResult = WaitForClientConnection (hPipe, 15); // 15 second timeout
    if (hResult)
    {
        goto ErrorExit;
    }
DoNextContainer:
    switch (dwContainerID)
    {
        case GAL_CONTAINER_ID :
            fGalContainer = TRUE;
            EnumContainerObjs (pStorage, hPipe, SERVER_USER_MAILBOX, pMalloc);
            if (S_OK == pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg))
            {
                EnumContainerObjs (pDirectoryStg, hPipe, SERVER_DISTRIBUTION_LIST, pMalloc);
                pDirectoryStg->Release();
            }
            dwLastContainerDone = GAL_CONTAINER_ID;
            break;
        case EXCHANGE_CONTAINER_ID :
        case SMTP_CONTAINER_ID :
        case FAX_CONTAINER_ID :
            break;
        default :
            {
                TCHAR szDLAlias[MAX_ALIAS_SIZE+1] = { 0 };
                WINDS_AB_OBJTYPE Type = UNDEFINED_OBJECT_TYPE;
                WCHAR wcsDLalias[64] = { 0 };
                if (S_OK == GlobalObjectMap.FindObjFromID (dwContainerID, szDLAlias, &Type) &&
                    SERVER_DISTRIBUTION_LIST == Type)
                {
                    AnsiToUnicode (szDLAlias, wcsDLalias, 64);
                    hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg);
                    if (!hResult)
                    {
                        hResult = GetDLRecipientsInfo (pStorage, pDirectoryStg, hPipe, wcsDLalias);
                        pDirectoryStg->Release();
                    }
                    TraceResult ("ReadABContainerInfo: Getting DL recipients", hResult);
                    hResult = S_OK;
                }
                else
                {
                    hResult = HRESULT_FROM_WIN32(ERROR_NO_SUCH_GROUP);
                    TraceResult ("ReadABContainerInfo: Invalid container ID passed in", hResult);
                }
            }
            break;
    }
    if (fGalContainer)
    {
        dwContainerID++;
        if (dwContainerID <= FAX_CONTAINER_ID)
        {
            goto DoNextContainer;
        }
    }
ErrorExit:
    TraceResult ("ReadABContainerInfo", hResult);
    CloseHandle (hPipe);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    EnumContainerObjs()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI EnumContainerObjs (LPSTORAGE             pDirectoryStg,
                                  HANDLE                hPipe,
                                  WINDS_AB_OBJTYPE      Type,
                                  LPMALLOC              pMalloc)
{
    LPENUMSTATSTG pEnum;
    STATSTG aStat[ELEMENTS_TO_FETCH];
    AB_ENTRY_INFO abEntry = { 0 };
    abEntry.Type = Type;
    LPTSTR pszObjAlias = (SERVER_DISTRIBUTION_LIST == Type ? abEntry.Info.DL.szDLAlias : abEntry.Info.MB.szMailboxName);
    ULONG ulMBoxFound = 1;
    DWORD dwBytesWritten;
    BOOL fFailure;
    int nChars;
    HRESULT hResult = pDirectoryStg->EnumElements (0, NULL, 0, &pEnum);
    TraceResult ("EnumContainerObjs: Failed to get ENUM interface", hResult);
    if (!hResult)
    {
        while (!hResult && ulMBoxFound)
        {
            hResult = pEnum->Next (ELEMENTS_TO_FETCH, aStat, &ulMBoxFound);
            if (FAILED(hResult)) // We might get S_FALSE
            {
                TraceResult ("EnumContainerObjs: Failed to ENUM streams", hResult);
            }
            else
            {
                if (ulMBoxFound)
                {
                    for (ULONG i=0; i<ulMBoxFound; i++)
                    {
                        if (STGTY_STORAGE == aStat[i].type && (L'_' != aStat[i].pwcsName[0]))
                        {
                            nChars = WideCharToMultiByte (CP_ACP,
                                                          0,
                                                          aStat[i].pwcsName,
                                                          -1,
                                                          pszObjAlias,
                                                          MAX_ALIAS_SIZE+1,
                                                          NULL,
                                                          NULL);
                            if (!nChars)
                            {
                                TraceResult ("EnumContainerObjs: Failed to convert storage name", HRESULT_FROM_WIN32(GetLastError()));
                            }
                            else
                            {
                                if (SERVER_DISTRIBUTION_LIST == Type)
                                {
                                    fFailure = GetDLProperties (pDirectoryStg, aStat[i].pwcsName, &abEntry.Info.DL);
                                }
                                else
                                {
                                    fFailure = GetMailBoxInfo (pDirectoryStg, aStat[i].pwcsName, &abEntry.Info.MB);
                                }
                                if (!fFailure)
                                {
                                    WriteFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesWritten, NULL);
                                }
                            }
                        }
                        pMalloc->Free (aStat[i].pwcsName);
                    }
                }
            }
        }
        pEnum->Release();
    }
    if (S_FALSE == hResult)
    {
        hResult = S_OK;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    LoadServerObjects()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI LoadServerObjects (LPSTORAGE pStorage, LPMALLOC pMalloc)
{
    LoadObjectProperties (pStorage, SERVER_USER_MAILBOX, TEXT("User Mailbox"), pMalloc);
    LPSTORAGE pDirectoryStg;
    HRESULT hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg);
RestartDL:
    if (!hResult)
    {
        LoadObjectProperties (pDirectoryStg, SERVER_DISTRIBUTION_LIST, TEXT("Distribution List"), pMalloc);
        pDirectoryStg->Release();
    }
    else
    {
        if (STG_E_FILENOTFOUND == hResult)
        {
            hResult = pStorage->CreateStorage (DISTRIBUTION_LISTS, CREATE_FLAGS, 0, 0, &pDirectoryStg);
            goto RestartDL;
        }
    }
    LPSTORAGE pGatewaysStg;
    hResult = pStorage->OpenStorage (FOREIGN_DIRECTORIES, NULL, OPEN_FLAGS, NULL, 0, &pGatewaysStg);
RestartGateways:
    if (!hResult)
    {
        if (!pGatewaysStg->OpenStorage (EXCHANGE_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg))
        {
            LoadObjectProperties (pDirectoryStg, GATEWAY_RECIPIENT, TEXT("Gateway Recipient"), pMalloc);
            pDirectoryStg->Release();
        }
        if (!pGatewaysStg->OpenStorage (FAX_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg))
        {
            LoadObjectProperties (pDirectoryStg, GATEWAY_RECIPIENT, TEXT("Gateway Recipient"), pMalloc);
            pDirectoryStg->Release();
        }
        if (!pGatewaysStg->OpenStorage (SMTP_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pDirectoryStg))
        {
            LoadObjectProperties (pDirectoryStg, GATEWAY_RECIPIENT, TEXT("Gateway Recipient"), pMalloc);
            pDirectoryStg->Release();
        }
        pGatewaysStg->Release();
    }
    else
    {
        if (STG_E_FILENOTFOUND == hResult)
        {
            hResult = pStorage->CreateStorage (FOREIGN_DIRECTORIES, CREATE_FLAGS, 0, 0, &pGatewaysStg);
            goto RestartGateways;
        }
    }
    return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
//    LoadObjectProperties()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI LoadObjectProperties (LPSTORAGE          pStorage,
                                     WINDS_AB_OBJTYPE   Type,
                                     LPTSTR             pszObjectType,
                                     LPMALLOC           pMalloc)
{
    LPENUMSTATSTG pEnum;
    STATSTG aStat[ELEMENTS_TO_FETCH];
    MAILBOX_INFO MBInfo = { 0 };
    DIST_LIST_INFO DLInfo = { 0 };
    ULONG i, ulMBoxFound = 1;
    HRESULT hResult = pStorage->EnumElements (0, NULL, 0, &pEnum);
    TraceResult ("LoadObjectProperties: Failed to get ENUM interface", hResult);
    if (!hResult)
    {
        while (!hResult && ulMBoxFound)
        {
            hResult = pEnum->Next (ELEMENTS_TO_FETCH, aStat, &ulMBoxFound);
            if (FAILED(hResult)) // We might get S_FALSE
            {
                TraceResult ("LoadObjectProperties: Failed to ENUM streams", hResult);
            }
            else
            {
                if (ulMBoxFound)
                {
                    for (i=0; i<ulMBoxFound; i++)
                    {
                        if (STGTY_STORAGE == aStat[i].type && (L'_' != aStat[i].pwcsName[0]))
                        {
                            int nChars = WideCharToMultiByte (CP_ACP,
                                                              0,
                                                              aStat[i].pwcsName,
                                                              -1,
                                                              (SERVER_DISTRIBUTION_LIST == Type ? DLInfo.szDLAlias : MBInfo.szMailboxName),
                                                              MAX_ALIAS_SIZE+1,
                                                              NULL,
                                                              NULL);
                            if (!nChars)
                            {
                                TraceResult ("LoadObjectProperties: Failed to convert storage name", HRESULT_FROM_WIN32(GetLastError()));
                            }
                            else
                            {
                                if (SERVER_DISTRIBUTION_LIST == Type)
                                {
                                    if (S_OK == GetDLProperties (pStorage, aStat[i].pwcsName, &DLInfo))
                                    {
                                        GlobalObjectMap.Insert (DLInfo.dwObjID, DLInfo.szDLAlias, SERVER_DISTRIBUTION_LIST);
                                    }
                                }
                                else
                                {
                                    if (S_OK == GetMailBoxInfo (pStorage, aStat[i].pwcsName, &MBInfo))
                                    {
                                        GlobalObjectMap.Insert (MBInfo.dwObjID, MBInfo.szMailboxName, SERVER_USER_MAILBOX);
                                    }
                                }
                            }
                        }
                        pMalloc->Free (aStat[i].pwcsName);
                    }
                }
            }
        }
        pEnum->Release();
    }
    if (S_FALSE == hResult)
    {
        hResult = S_OK;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    MailboxProps()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI MailboxProps (LPSTORAGE pMBDirectory, PMAILBOX_INFO pMBInfo, BOOL fRetrieve)
{
    HRESULT hResult;
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (pMBInfo->szMailboxName, wcsMailbox, 64);
    if (fRetrieve)
    {
        hResult = GetMailBoxInfo (pMBDirectory, wcsMailbox, pMBInfo);
    }
    else
    {
        hResult = SetMailBoxInfo (pMBDirectory, wcsMailbox, pMBInfo);
    }
    TraceResult ("MailboxProps", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetMailBoxInfo()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetMailBoxInfo (LPSTORAGE pStorage, LPOLESTR pwcsName, PMAILBOX_INFO pInfo)
{
    LPSTREAM pStream;
    LPSTORAGE pSubStorage;
    HRESULT hResult = pStorage->OpenStorage (pwcsName, NULL, OPEN_FLAGS, NULL, 0, &pSubStorage);
    TraceResult ("GetMailBoxInfo: Failed to open mailbox storage", hResult);
    if (!hResult)
    {
        hResult = pSubStorage->OpenStream (USER_PROPERTIES, NULL, OPEN_FLAGS, 0, &pStream);
        TraceResult ("GetMailBoxInfo: Failed to open the properties stream", hResult);
        if (!hResult)
        {
            hResult = pStream->Read (pInfo, sizeof(MAILBOX_INFO), NULL);
            TraceResult ("GetMailBoxInfo: Failed to read the MBOX stream", hResult);
            pStream->Release();
        }
        pSubStorage->Release();
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    SetMailBoxInfo()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI SetMailBoxInfo (LPSTORAGE pStorage, LPOLESTR pwcsName, PMAILBOX_INFO pInfo)
{
    LPSTREAM pStream;
    LPSTORAGE pSubStorage;
    HRESULT hResult = pStorage->OpenStorage (pwcsName, NULL, OPEN_FLAGS, NULL, 0, &pSubStorage);
    TraceResult ("SetMailBoxInfo: Failed to open mailbox storage", hResult);
    if (!hResult)
    {
        hResult = pSubStorage->OpenStream (USER_PROPERTIES, NULL, OPEN_FLAGS, 0, &pStream);
        TraceResult ("SetMailBoxInfo: Failed to open the properties stream", hResult);
        if (!hResult)
        {
            hResult = pStream->Write (pInfo, sizeof(MAILBOX_INFO), NULL);
            TraceResult ("SetMailBoxInfo: Failed to write to the MBOX stream", hResult);
            pStream->Release();
        }
        pSubStorage->Release();
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    EmptyMailBoxMsgs()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI EmptyMailBoxMsgs (LPSTORAGE pStorage, LPTSTR pszMailbox)
{
    LPSTORAGE pMBStg, pSubStg;
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (pszMailbox, wcsMailbox, 64);
    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
    if (!hResult)
    {
        hResult = pMBStg->DestroyElement (HEADERS_STORAGE);
        if (!hResult)
        {
            hResult = pMBStg->CreateStorage (HEADERS_STORAGE, CREATE_FLAGS, 0, 0, &pSubStg);
            if (!hResult)
            {
                pSubStg->Release();
                hResult = pMBStg->DestroyElement (MSGS_STORAGE);
                if (!hResult)
                {
                    hResult = pMBStg->CreateStorage (MSGS_STORAGE, CREATE_FLAGS, 0, 0, &pSubStg);
                    if (!hResult)
                    {
                        pSubStg->Release();
                    }
                }
            }
        }
        pMBStg->Release();
    }
    TraceResult ("EmptyMailBoxMsgs", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetNextObjID()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetNextObjID (LPSTORAGE pStorage, DWORD & dwNextID)
{
    LPSTREAM pStream;
    LARGE_INTEGER liZero = { 0 };
    HRESULT hResult = pStorage->OpenStream (OBJ_ID_POOL_STREAM, NULL, OPEN_FLAGS, 0, &pStream);
    if (!hResult)
    {
        // From the opened stream, read the next available ID
        hResult = pStream->Read (&dwNextID, sizeof(DWORD), NULL);
        if (!hResult)
        {
            // Set the stream seek pointer back to the beginning and write the ID
            hResult = pStream->Seek (liZero, STREAM_SEEK_SET, NULL);
            if (!hResult)
            {
                dwNextID++;
                hResult = pStream->Write (&dwNextID, sizeof(DWORD), NULL);
            }
        }
        pStream->Release();
    }
    // If any, trace out the error code.
    TraceResult ("GetNextObjID", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateMailbox()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreateMailbox (LPSTORAGE pMBDirectory, PMAILBOX_INFO pMBInfo)
{
    LPSTORAGE pMBStg, pSubStg;
    LPSTREAM pStream;
    DWORD dwZero = 0;
    HRESULT hResult = GetNextObjID (pMBDirectory, pMBInfo->dwObjID);
    if (hResult)
    {
        return hResult;
    }
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (pMBInfo->szMailboxName, wcsMailbox, 64);
    hResult = pMBDirectory->CreateStorage (wcsMailbox, CREATE_FLAGS, 0, 0, &pMBStg);
    if (!hResult)
    {
        hResult = pMBStg->CreateStream (USER_PROPERTIES, CREATE_FLAGS, 0, 0, &pStream);
        if (!hResult)
        {
            hResult = pStream->Write (pMBInfo, sizeof(MAILBOX_INFO), NULL);
            if (!hResult)
            {
                hResult = pMBStg->CreateStorage (HEADERS_STORAGE, CREATE_FLAGS, 0, 0, &pSubStg);
                if (!hResult)
                {
                    pSubStg->Release();
                    hResult = pMBStg->CreateStream (OBJ_ID_POOL_STREAM, CREATE_FLAGS, 0, 0, &pStream);
                    if (!hResult)
                    {
                        hResult = pStream->Write (&dwZero, sizeof(DWORD), NULL);
                        pStream->Release();
                        if (!hResult)
                        {
                            hResult = pMBStg->CreateStorage (MSGS_STORAGE, CREATE_FLAGS, 0, 0, &pSubStg);
                            if (!hResult)
                            {
                                pSubStg->Release();
                            }
                        }
                    }
                }
            }
        }
        pMBStg->Release();
        if (hResult)
        {
            hResult = pMBDirectory->DestroyElement (wcsMailbox);
        }
    }
    TraceResult ("CreateMailbox", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    DeleteMailbox()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DeleteMailbox (LPSTORAGE pStorage, PMAILBOX_INFO pMBInfo)
{
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (pMBInfo->szMailboxName, wcsMailbox, 64);
    GetMailBoxInfo (pStorage, wcsMailbox, pMBInfo);
    HRESULT hResult = pStorage->DestroyElement (wcsMailbox);
    TraceResult ("DeleteMailbox: Failed to delete storage of mailbox", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CopyMsgFromFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CopyMsgFromFile (LPSTORAGE      pStorage,
                                HANDLE         hFile,
                                LPTSTR         szMailbox,
                                LPTSTR         szHeader)
{
    LPSTORAGE pMBStg, pMsgStg;
    LPSTREAM pStream;
    WCHAR wcsMailbox[64] = { 0 }, wcsStreamName[NAME_FORMAT_SIZE];
    AnsiToUnicode (szMailbox, wcsMailbox, 64);

    BYTE abBuffer[IO_BUFFERSIZE];
    DWORD dwBytesRead, dwLastError = 0, dwStreamID;
    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
    if (!hResult)
    {
        hResult = pMBStg->OpenStorage (MSGS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pMsgStg);
        if (!hResult)
        {
            hResult = GetNextObjID (pMBStg, dwStreamID);
            if (!hResult)
            {
                wsprintfW (wcsStreamName, MSG_STREAM_FORMAT, dwStreamID);
                hResult = pMsgStg->CreateStream (wcsStreamName, CREATE_FLAGS, 0, 0, &pStream);
                if (!hResult)
                {
                    do
                    {
                        if (!ReadFile (hFile, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
                        {
                            dwLastError = HRESULT_FROM_WIN32(GetLastError());
                            TraceResult ("CopyMsgFromFile: Failed to read from the tmp file", dwLastError);
                        }
                        if (dwBytesRead)
                        {
                            hResult = pStream->Write (abBuffer, dwBytesRead, NULL);
                            TraceResult ("CopyMsgFromFile: Failed to write into mb stream", hResult);
                        }
                    } while (dwBytesRead && !hResult && !dwLastError);
                    if (!hResult && !dwLastError)
                    {
                        hResult = UpdateMailboxHeaders (pStorage, pMBStg, NULL, dwStreamID, szHeader, TRUE);
                    }
                    pStream->Release();
                    if (hResult || dwLastError)
                    {
                        pMsgStg->DestroyElement (wcsStreamName);
                        if (dwLastError)
                        {
                            hResult = dwLastError;
                        }
                    }
                }
            }
            pMsgStg->Release();
        }
        pMBStg->Release();
    }
    // If there were no error storing the message in the user mailbox, send a
    // notification to client transports indicating that a new message
    // is pending download.
    if (S_OK == hResult)
    {
        PWINDS_NOTIFICATION pNotif = (PWINDS_NOTIFICATION)HeapAlloc (ghHeap,
                                                                     HEAP_ZERO_MEMORY,
                                                                     sizeof(WINDS_NOTIFICATION));
        if (pNotif)
        {
            pNotif->Event = XP_NEW_MAIL_ARRIVED;
            lstrcpy (pNotif->Info.MB.szMailboxName, szMailbox);
            NotifyClients (pNotif); // This will take care of freeing the notification structure
        }
        else
        {
            TraceMessage ("CopyMsgFromFile: Failed to allocate memory for client notification");
        }
    }
    TraceResult ("CopyMsgFromFile", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    RemoveMessageFromMailbox()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI RemoveMessageFromMailbox (LPSTORAGE      pStorage,
                                         LPTSTR         szMailbox,
                                         DWORD          dwStreamID)
{
    LPSTORAGE pHeaders, pMBStg, pMsgStg;
    WCHAR wcsMailbox[64] = { 0 }, wcsStreamName[NAME_FORMAT_SIZE];
    AnsiToUnicode (szMailbox, wcsMailbox, 64);

    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
    if (!hResult)
    {
        hResult = pMBStg->OpenStorage (MSGS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pMsgStg);
        if (!hResult)
        {
            wsprintfW (wcsStreamName, MSG_STREAM_FORMAT, dwStreamID);
            hResult = pMsgStg->DestroyElement (wcsStreamName);
            if (!hResult)
            {
                hResult = pMBStg->OpenStorage (HEADERS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pHeaders);
                if (!hResult)
                {
                    wsprintfW (wcsStreamName, HEADER_STREAM_FORMAT, dwStreamID);
                    pHeaders->DestroyElement (wcsStreamName); // Drop the error code here
                    pHeaders->Release();
                }
            }
            pMsgStg->Release();
        }
        pMBStg->Release();
    }
    TraceResult ("RemoveMessageFromMailbox", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    UpdateMailboxHeaders()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long WINAPI UpdateMailboxHeaders (LPSTORAGE pStorage,
                                  LPSTORAGE pMBStg,
                                  LPTSTR    szMailbox,
                                  DWORD     dwStreamID,
                                  LPTSTR    pszHeader,
                                  BOOL      fAddMsgToHeadersList)
{
    WCHAR wcsMailbox[64] = { 0 }, wcsStreamName[NAME_FORMAT_SIZE];

    LPSTORAGE pHeadersStg;
    LPSTREAM pHeader;
    HRESULT hResult = S_OK;
    BOOL fReleaseLocalStorage = FALSE;
    if (!pMBStg)
    {
        fReleaseLocalStorage = TRUE;
        AnsiToUnicode (szMailbox, wcsMailbox, 64);
        hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
        TraceResult ("UpdateMailboxHeaders: Failed to open mailbox", hResult);
    }
    if (!hResult)
    {
        hResult = pMBStg->OpenStorage (HEADERS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pHeadersStg);
        TraceResult ("UpdateMailboxHeaders: Failed to open header storage", hResult);
        if (!hResult)
        {
            wsprintfW (wcsStreamName, HEADER_STREAM_FORMAT, dwStreamID);
            if (fAddMsgToHeadersList)
            {
                hResult = pHeadersStg->CreateStream (wcsStreamName, CREATE_FLAGS, 0, 0, &pHeader);
                TraceResult ("UpdateMailboxHeaders: Failed to create header stream", hResult);
                if (!hResult)
                {
                    BYTE abEntryID[16];
                    ZeroMemory (abEntryID, 16);
                    wsprintfA ((LPSTR)&abEntryID[4], "%d", dwStreamID);

                    ULONG cbSize = 16 + lstrlen(pszHeader) * sizeof(TCHAR);
                    hResult = pHeader->Write (&cbSize, sizeof(ULONG), NULL);
                    TraceResult ("UpdateMailboxHeaders: Failed to write header size", hResult);
                    if (!hResult)
                    {
                        hResult = pHeader->Write (abEntryID, 16, NULL);
                        TraceResult ("UpdateMailboxHeaders: Failed to write message EID", hResult);
                        if (!hResult)
                        {
                            hResult = pHeader->Write (pszHeader, cbSize - 16, NULL);
                            TraceResult ("UpdateMailboxHeaders: Failed to write header to stream", hResult);
                        }
                    }
                    pHeader->Release();
                    if (hResult)
                    {
                        TraceIO2 ("UpdateMailboxHeaders", pHeadersStg->DestroyElement(wcsStreamName));
                    }
                }
            }
            else
            {
                hResult = pMBStg->DestroyElement (wcsStreamName);
                TraceResult ("UpdateMailboxHeaders: Failed to remove header stream", hResult);
            }
            pHeadersStg->Release();
        }
        if (fReleaseLocalStorage)
        {
            pMBStg->Release();
        }
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CopyHeadersToFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CopyHeadersToFile (LPSTORAGE     pStorage,
                                  LPMALLOC      pMalloc,
                                  HANDLE        hFile,
                                  LPTSTR        szMailbox)
{
    BOOL fAtLeastOneCopied = FALSE, fSubStreamFailed = FALSE;
    LPSTORAGE pMBStg, pHeadersStg;
    LPSTREAM pStream;
    LPENUMSTATSTG pEnum;
    BYTE abBuffer[IO_BUFFERSIZE];
    STATSTG aStat[ELEMENTS_TO_FETCH];
    ULONG ulStreamsFound, ulBytesRead;
    DWORD dwBytesWrite;

    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (szMailbox, wcsMailbox, 64);

    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
    TraceResult ("CopyHeadersToFile: Failed to mailbox storage", hResult);

if (!hResult)
    {
        hResult = pMBStg->OpenStorage (HEADERS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pHeadersStg);
        TraceResult ("CopyHeadersToFile: Failed to open headers storage", hResult);
        if (!hResult)
        {
            hResult = pHeadersStg->EnumElements (0, NULL, 0, &pEnum);
            TraceResult ("CopyHeadersToFile: Failed to get ENUM interface", hResult);
            if (!hResult)
            {
                hResult = 0;
                ulStreamsFound = 1;
                while (!hResult && ulStreamsFound)
                {
                    hResult = pEnum->Next (ELEMENTS_TO_FETCH, aStat, &ulStreamsFound);
                    if (ulStreamsFound)
                    {
                        for (ULONG i=0; i<ulStreamsFound; i++)
                        {
                            hResult = pHeadersStg->OpenStream (aStat[i].pwcsName, NULL, OPEN_FLAGS, 0, &pStream);
                            TraceResult ("CopyHeadersToFile: Failed to open header stream", hResult);
                            if (!hResult)
                            {
                                ASSERT (IO_BUFFERSIZE >= aStat[i].cbSize.LowPart);
                                hResult = pStream->Read (abBuffer, aStat[i].cbSize.LowPart, &ulBytesRead);
                                if (!hResult)
                                {
                                    if (!WriteFile (hFile, abBuffer, ulBytesRead, &dwBytesWrite, NULL))
                                    {
                                        fSubStreamFailed = TRUE;
                                        TraceResult ("CopyHeadersToFile: Failed to write header to tmp file", HRESULT_FROM_WIN32(GetLastError()));
                                    }
                                    else
                                    {
                                        fAtLeastOneCopied = TRUE;
                                    }
                                }
                                pStream->Release();
                            }
                            pMalloc->Free (aStat[i].pwcsName);
                        }
                    }
                    else
                    {
                        if (FAILED(hResult)) // We might get S_FALSE
                        {
                            TraceResult ("CopyHeadersToFile: Failed to ENUM streams", hResult);
                        }
                    }
                }
                if (S_FALSE == hResult)
                {
                    hResult = S_OK;
                }
                pEnum->Release();
            }
            pHeadersStg->Release();
        }
        pMBStg->Release();
    }
    if (hResult)
    {
        if (fSubStreamFailed)
        {
            if (fAtLeastOneCopied)
            {
                hResult = MAKE_HRESULT(0, FACILITY_WIN32, ERROR_PARTIAL_COPY);
            }
        }
    }
    SetFilePointer (hFile, 0, NULL, FILE_BEGIN);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CopyStreamToFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CopyStreamToFile (LPSTREAM   pStream,
                                 HANDLE     hFile,
                                 ULONG *    pulWritten)
{
    BYTE aBuffer[IO_BUFFERSIZE];
    ULONG ulRead, ulRemain, ulWritten;
    STATSTG StatInfo;
    *pulWritten = 0;
    HRESULT hResult = pStream->Stat (&StatInfo, STATFLAG_NONAME);
    if (!hResult)
    {
        ulRemain  = StatInfo.cbSize.LowPart;
        for (; ulRemain>0; ulRemain -= ulWritten)
        {
            hResult = pStream->Read (aBuffer, min(ulRemain, IO_BUFFERSIZE), &ulRead);
            if (hResult)
            {
                break; // Out of the FOR() loop
            }
            else
            {
                if (!WriteFile (hFile, aBuffer, ulRead, &ulWritten, NULL))
                {
                    hResult = HRESULT_FROM_WIN32(GetLastError());
                    break; // Out of the FOR() loop
                }
            }
            *pulWritten += ulWritten;
        }
    }
    TraceResult ("CopyStreamToFile", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CopyMsgToFile()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CopyMsgToFile (LPSTORAGE    pStorage,
                              LPTSTR       szMailbox,
                              HANDLE       hFile,
                              ULONG *      pulMsgLen,
                              DWORD        dwStreamID,
                              BOOL         fDeleteAfterCopy)
{
    LPSTORAGE pMBStg, pMsgStg, pHeaders;
    LPSTREAM pStream;
    WCHAR wcsMailbox[64] = { 0 }, wcsStreamName[NAME_FORMAT_SIZE];
    wsprintfW (wcsStreamName, MSG_STREAM_FORMAT, dwStreamID);

    AnsiToUnicode (szMailbox, wcsMailbox, 64);
    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMBStg);
    if (!hResult)
    {
        hResult = pMBStg->OpenStorage (MSGS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pMsgStg);
        if(!hResult)
        {
            hResult = pMsgStg->OpenStream (wcsStreamName, NULL, OPEN_FLAGS, 0, &pStream);
            if (!hResult)
            {
                hResult = CopyStreamToFile (pStream, hFile, pulMsgLen);
                pStream->Release();
                if (!hResult && fDeleteAfterCopy)
                {
                    hResult = pMsgStg->DestroyElement (wcsStreamName);
                    if (!hResult)
                    {
                        hResult = pMBStg->OpenStorage (HEADERS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pHeaders);
                        if (!hResult)
                        {
                            wsprintfW (wcsStreamName, HEADER_STREAM_FORMAT, dwStreamID);
                            hResult = pHeaders->DestroyElement (wcsStreamName);
                            pHeaders->Release();
                        }
                    }
                    hResult = S_OK; // We don't want to propagate errors at this level, since the message is already copied.
                }
            }
            pMsgStg->Release();
        }
        pMBStg->Release();
    }
    TraceResult ("CopyMsgToFile", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateDistributionList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreateDistributionList (LPSTORAGE pStorage, PDIST_LIST_INFO pDLInfo)
{
    HRESULT hResult = GetNextObjID (pStorage, pDLInfo->dwObjID);
    if (hResult)
    {
        return hResult;
    }
    LPSTORAGE pDLDirectoryStg, pDLStg;
    LPSTREAM pStream;
    WCHAR wcsDLalias[64] = { 0 };
    AnsiToUnicode (pDLInfo->szDLAlias, wcsDLalias, 64);

    hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDLDirectoryStg);
Restart:
    if (!hResult)
    {
        hResult = pDLDirectoryStg->CreateStorage (wcsDLalias, CREATE_FLAGS, 0, 0, &pDLStg);
        if (!hResult)
        {
            hResult = pDLStg->CreateStream (DL_PROPS, CREATE_FLAGS, 0, 0, &pStream);
            if (!hResult)
            {
                pStream->Release();
                hResult = pDLStg->CreateStream (DL_MEMBERS, CREATE_FLAGS, 0, 0, &pStream);
                if (!hResult)
                {
                    pStream->Release();
                }
            }
            pDLStg->Release();
            hResult = SetDLProperties (pDLDirectoryStg, wcsDLalias, pDLInfo);
        }
        if (hResult)
        {
            pDLDirectoryStg->DestroyElement (wcsDLalias);
        }
        pDLDirectoryStg->Release();
    }
    else
    {
        if (STG_E_FILENOTFOUND == hResult)
        {
            hResult = pStorage->CreateStorage (DISTRIBUTION_LISTS, CREATE_FLAGS, 0, 0, &pDLDirectoryStg);
            goto Restart;
        }
    }
    TraceResult ("CreateDistributionList", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    DLPropsAndMembers()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DLPropsAndMembers (LPSTORAGE pStorage, PDIST_LIST_INFO pDLInfo, BOOL fGetProps)
{
    LPSTORAGE pDLDirectoryStg;
    WCHAR wcsDLalias[64] = { 0 };
    AnsiToUnicode (pDLInfo->szDLAlias, wcsDLalias, 64);
    HRESULT hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDLDirectoryStg);
    if (!hResult)
    {
        if (fGetProps)
        {
            hResult = GetDLProperties (pDLDirectoryStg, wcsDLalias, pDLInfo, TRUE);
        }
        else
        {
            hResult = SetDLProperties (pDLDirectoryStg, wcsDLalias, pDLInfo);
        }
        pDLDirectoryStg->Release();
    }
    TraceResult ("DLPropsAndMembers", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetDLProperties()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetDLProperties (LPSTORAGE           pStorage,
                                LPOLESTR            pwcsDLName,
                                PDIST_LIST_INFO     pDLInfo,
                                BOOL                fGetMembers)
{
    LPSTREAM pStream;
    LPSTORAGE pDLStg;
    DWORD i;
    DLM_INFO_A Info = { 0 };
    DLM_LIST_A * pList = (DLM_LIST_A *)pDLInfo->pMembers;

    HRESULT hResult = pStorage->OpenStorage (pwcsDLName, NULL, OPEN_FLAGS, NULL, 0, &pDLStg);
    if (!hResult)
    {
        hResult = pDLStg->OpenStream (DL_PROPS, NULL, OPEN_FLAGS, 0, &pStream);
        if (!hResult)
        {
            hResult = pStream->Read (pDLInfo,
                                     sizeof(DIST_LIST_INFO) - 4, // Don't read the pMember pointer value
                                     NULL);
            pStream->Release();
        }
        if (!hResult && fGetMembers && pDLInfo->dwMemberCount && pList)
        {
            hResult = pDLStg->OpenStream (DL_MEMBERS, NULL, OPEN_FLAGS, 0, &pStream);
            if (!hResult)
            {
                for (i=0; i<pDLInfo->dwMemberCount && !hResult; i++)
                {
                    hResult = pStream->Read (&Info, sizeof(DLM_INFO_A), NULL);
                    if (!hResult)
                    {
                        if (i == 0)
                        {
                            pList->Info = Info;
                        }
                        else
                        {
                            InsertNewDLMNodeA (Info, pList);
                        }
                    }
                }
                pStream->Release();
            }
        }
        pDLStg->Release();
    }
    TraceResult ("GetDLProperties", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    SetDLProperties()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI SetDLProperties (LPSTORAGE           pStorage,
                                LPOLESTR            pwcsDLName,
                                PDIST_LIST_INFO     pDLInfo)
{
    LPSTREAM pStream;
    LPSTORAGE pDLStg;
    ULARGE_INTEGER uliTotalSize = { 0 };
    ULONG ulBytesWritten;
    DLM_LIST_A * pNode;
    HRESULT hResult = pStorage->OpenStorage (pwcsDLName, NULL, OPEN_FLAGS, NULL, 0, &pDLStg);
    if (!hResult)
    {
        pDLInfo->dwMemberCount = 0;
        hResult = pDLStg->OpenStream (DL_MEMBERS, NULL, OPEN_FLAGS, 0, &pStream);
        if (!hResult)
        {
            pNode = (DLM_LIST_A *)pDLInfo->pMembers;
            while (pNode && !hResult)
            {
                hResult = pStream->Write (&(pNode->Info),
                                          sizeof(DLM_INFO_A),
                                          &ulBytesWritten);
                uliTotalSize.LowPart += ulBytesWritten;
                pDLInfo->dwMemberCount++;
                pNode = pNode->pNext;
            }
            pStream->SetSize (uliTotalSize);
            pStream->Release();
        }
        if (!hResult)
        {
            hResult = pDLStg->OpenStream (DL_PROPS, NULL, OPEN_FLAGS, 0, &pStream);
            if (!hResult)
            {
                hResult = pStream->Write (pDLInfo,
                                          sizeof(DIST_LIST_INFO) - 4, // Don't write the last 4 byte (pMembers)
                                          NULL);
                pStream->Release();
            }
        }
        pDLStg->Release();
    }
    TraceResult ("SetDLProperties", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    DeleteDistributionList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI DeleteDistributionList (LPSTORAGE pStorage, PDIST_LIST_INFO pDLInfo)
{
    LPSTORAGE pDLDirectoryStg;
    WCHAR wcsDLalias[64] = { 0 };
    AnsiToUnicode (pDLInfo->szDLAlias, wcsDLalias, 64);
    HRESULT hResult = pStorage->OpenStorage (DISTRIBUTION_LISTS, NULL, OPEN_FLAGS, NULL, 0, &pDLDirectoryStg);
    if (!hResult)
    {
        GetDLProperties (pDLDirectoryStg, wcsDLalias, pDLInfo, FALSE);
        hResult = pDLDirectoryStg->DestroyElement (wcsDLalias);
        pDLDirectoryStg->Release();
    }
    TraceResult ("DeleteDistributionList", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetDLRecipientsInfo()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetDLRecipientsInfo (LPSTORAGE   pMBDirectory,
                                    LPSTORAGE   pDLDirectory,
                                    HANDLE      hPipe,
                                    LPOLESTR    pwcsDLName)
{
    DLM_LIST_A Members = { 0 };
    DIST_LIST_INFO DLInfo = { 0 };
    DLInfo.pMembers = (LPVOID)&Members;
    AB_ENTRY_INFO abEntry = { 0 };
    OLECHAR wcsMemberAlias[MAX_ALIAS_SIZE+1];
    int nChars;
    DWORD dwBytesWritten;
    DLM_LIST_A * pNode;

    HRESULT hEntry, hResult = GetDLProperties (pDLDirectory, pwcsDLName, &DLInfo, TRUE);
    if (!hResult && (0 != Members.Info.dwMemberID))
    {
        pNode = (DLM_LIST_A *)DLInfo.pMembers;
        while (pNode)
        {
            nChars = MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, (LPSTR)pNode->Info.szMemberAlias, -1, wcsMemberAlias, MAX_ALIAS_SIZE+1);
            if (!nChars)
            {
                TraceResult ("GetDLRecipientsInfo: Failed to convert alias name", HRESULT_FROM_WIN32(GetLastError()));
            }
            else
            {
                abEntry.Type = (WINDS_AB_OBJTYPE)pNode->Info.dwMemberType;
                if (SERVER_DISTRIBUTION_LIST == abEntry.Type)
                {
                    hEntry = GetDLProperties (pDLDirectory, wcsMemberAlias, &abEntry.Info.DL, FALSE);
                }
                else
                {
                    if (SERVER_USER_MAILBOX == abEntry.Type)
                    {
                        hEntry = GetMailBoxInfo (pMBDirectory, wcsMemberAlias, &abEntry.Info.MB);
                    }
                    else
                    {
                        ASSERT (GATEWAY_RECIPIENT == abEntry.Type);
                        ASSERT (FALSE); // not implemented yet
                        hEntry = E_FAIL;
                    }
                }
                if (!hEntry)
                {
                    if (!WriteFile (hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesWritten, NULL))
                    {
                        TraceResult ("GetDLRecipientsInfo: Failed to write to client pipe", HRESULT_FROM_WIN32(GetLastError()));
                    }
                }
            }
            pNode = pNode->pNext;
        }
        FreeDLMList (&Members);
    }
    TraceResult ("GetDLRecipientsInfo", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetGWContainerCount()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetGWContainerCount (LPSTORAGE   pStorage,
                                    LPMALLOC    pMalloc,
                                    DWORD       dwContainerID,
                                    DWORD *     pdwContainerCount)
{
    LPENUMSTATSTG pEnum;
    STATSTG aStat[ELEMENTS_TO_FETCH];
    ULONG ulMBoxFound = 1;
    LPSTORAGE pDirectory, pGWContainer;
    HRESULT hResult = pStorage->OpenStorage (FOREIGN_DIRECTORIES, NULL, OPEN_FLAGS, NULL, 0, &pDirectory);
    if (!hResult)
    {
        switch (dwContainerID)
        {
            case FAX_CONTAINER_ID :
                hResult = pStorage->OpenStorage (FAX_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pGWContainer);
                break;
            case SMTP_CONTAINER_ID :
                hResult = pStorage->OpenStorage (SMTP_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pGWContainer);
                break;
            case EXCHANGE_CONTAINER_ID :
                hResult = pStorage->OpenStorage (EXCHANGE_GATEWAY, NULL, OPEN_FLAGS, NULL, 0, &pGWContainer);
                break;
        }
        if (!hResult)
        {
            hResult = pGWContainer->EnumElements (0, NULL, 0, &pEnum);
            if (!hResult)
            {
                while (!hResult && ulMBoxFound)
                {
                    hResult = pEnum->Next (ELEMENTS_TO_FETCH, aStat, &ulMBoxFound);
                    if (SUCCEEDED(hResult))
                    {
                        if (ulMBoxFound)
                        {
                            for (ULONG i=0; i<ulMBoxFound; i++)
                            {
                                if (STGTY_STORAGE == aStat[i].type && (L'_' != aStat[i].pwcsName[0]))
                                {
                                    (*pdwContainerCount)++;
                                }
                                pMalloc->Free (aStat[i].pwcsName);
                            }
                        }
                    }
                }
                pEnum->Release();
            }
            hResult = S_OK;
            pGWContainer->Release();
        }
        pDirectory->Release();
    }
    TraceResult ("GetGWContainerCount", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CheckForPendingMailboxMsgs()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CheckForPendingMailboxMsgs (LPSTORAGE pStorage,
                                           LPMALLOC  pMalloc,
                                           LPTSTR    szMailbox,
                                           DWORD *   pdwMsgCount)
{
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (szMailbox, wcsMailbox, 64);

    ULONG ulMsgsFound, dwTotal = 0;
    LPENUMSTATSTG pEnum;
    STATSTG aStat[ELEMENTS_TO_FETCH];
    LPSTORAGE pMailbox, pMessages;
    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMailbox);
    if (!hResult)
    {
        hResult = pMailbox->OpenStorage (MSGS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pMessages);
        if (!hResult)
        {
            hResult = pMessages->EnumElements (0, NULL, 0, &pEnum);
            if (!hResult)
            {
                while (!hResult)
                {
                    hResult = pEnum->Next (ELEMENTS_TO_FETCH, aStat, &ulMsgsFound);
                    if (FAILED(hResult)) // We might get S_FALSE
                    {
                        TraceResult ("CheckForPendingMailboxMsgs: Failed to ENUM streams", hResult);
                    }
                    else
                    {
                        if (ulMsgsFound)
                        {
                            dwTotal += ulMsgsFound;
                        }
                        pMalloc->Free (aStat[0].pwcsName);
                    }
                }
                pEnum->Release();
                if (S_FALSE == hResult)
                {
                    hResult = S_OK;
                    *pdwMsgCount = dwTotal;
                }
            }
            pMessages->Release();
        }
        pMailbox->Release();
    }
    TraceResult ("CheckForPendingMailboxMsgs", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetNextMailboxMsg()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI GetNextMailboxMsg (LPSTORAGE pStorage,
                                  LPMALLOC  pMalloc,
                                  LPTSTR    szMailbox,
                                  DWORD *   pdwMsgID,
                                  HANDLE    hFile)
{
    WCHAR wcsMailbox[64] = { 0 };
    AnsiToUnicode (szMailbox, wcsMailbox, 64);

    BYTE abBuffer[IO_BUFFERSIZE];
    int nChars;
    char szObjName[64];
    LPSTR pSubStr;
    DWORD dwBytesWritten;
    ULONG ulMsgsFound = 0, ulBytesRead;
    LPENUMSTATSTG pEnum;
    STATSTG aStat[1];
    LPSTORAGE pMailbox, pMessages;
    LPSTREAM pOneMsg;
    HRESULT hResult = pStorage->OpenStorage (wcsMailbox, NULL, OPEN_FLAGS, NULL, 0, &pMailbox);
    if (!hResult)
    {
        hResult = pMailbox->OpenStorage (MSGS_STORAGE, NULL, OPEN_FLAGS, NULL, 0, &pMessages);
        if (!hResult)
        {
            hResult = pMessages->EnumElements (0, NULL, 0, &pEnum);
            if (!hResult)
            {
                hResult = pEnum->Next (1, aStat, &ulMsgsFound);
                if (FAILED(hResult)) // We might get S_FALSE
                {
                    TraceResult ("GetNextMailboxMsg: Failed to ENUM streams", hResult);
                }
                else
                {
                    if (ulMsgsFound)
                    {
                        ASSERT (STGTY_STREAM == aStat[0].type)
                        nChars = WideCharToMultiByte (CP_ACP,
                                                      0,
                                                      aStat[0].pwcsName,
                                                      -1,
                                                      szObjName,
                                                      64,
                                                      NULL,
                                                      NULL);
                        if (!nChars)
                        {
                            TraceResult ("GetNextMailboxMsg: Failed to convert storage name", HRESULT_FROM_WIN32(GetLastError()));
                        }
                        hResult = pMessages->OpenStream (aStat[0].pwcsName,
                                                         NULL,
                                                         OPEN_FLAGS,
                                                         0,
                                                         &pOneMsg);
                        if (!hResult)
                        {
                            do
                            {
                                hResult = pOneMsg->Read (abBuffer, IO_BUFFERSIZE, &ulBytesRead);
                                if (!hResult && ulBytesRead)
                                {
                                    if (!WriteFile (hFile, abBuffer, ulBytesRead, &dwBytesWritten, NULL))
                                    {
                                        hResult = HRESULT_FROM_WIN32(GetLastError());
                                    }
                                }
                            } while (!hResult && ulBytesRead);
                            pOneMsg->Release();
                        }
                        if (!hResult && nChars)
                        {
                            pSubStr = &(szObjName[lstrlenA (szObjName)]);
                            while (' ' != *pSubStr)
                            {
                                pSubStr--;
                            }
                            pSubStr++;
                            sscanf (pSubStr, "%X", pdwMsgID);
                        }
                        pMalloc->Free (aStat[0].pwcsName);
                    }
                }
                pEnum->Release();
            }
            pMessages->Release();
        }
        pMailbox->Release();
    }
    #ifdef _DEBUG
    if (hResult && S_FALSE != hResult)
    {
        TraceResult ("GetNextMailboxMsg", hResult);
    }
    #endif // _DEBUG

    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateGatewayQueues()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreateGatewayQueues (LPSTORAGE pStorage)
{
    LPSTORAGE pQueueStg;
    if (!pStorage->CreateStorage (GW_OUTBOX, CREATE_FLAGS, 0, 0, &pQueueStg))
    {
        pQueueStg->Release();
    }
    if (!pStorage->CreateStorage (GW_INBOX, CREATE_FLAGS, 0, 0, &pQueueStg))
    {
        pQueueStg->Release();
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateGatewayDirectories()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreateGatewayDirectories (LPSTORAGE pStorage)
{
    LPSTORAGE pDirsStg, pGatewaysStg;
    HRESULT hResult = pStorage->CreateStorage (FOREIGN_DIRECTORIES, CREATE_FLAGS, 0, 0, &pDirsStg);
    if (!hResult)
    {
        if (!pDirsStg->CreateStorage (EXCHANGE_GATEWAY, CREATE_FLAGS, 0, 0, &pGatewaysStg))
        {
            CreateGatewayQueues (pGatewaysStg);
            pGatewaysStg->Release();
        }
        if (!pDirsStg->CreateStorage (FAX_GATEWAY, CREATE_FLAGS, 0, 0, &pGatewaysStg))
        {
            CreateGatewayQueues (pGatewaysStg);
            pGatewaysStg->Release();
        }
        if (!pDirsStg->CreateStorage (SMTP_GATEWAY, CREATE_FLAGS, 0, 0, &pGatewaysStg))
        {
            CreateGatewayQueues (pGatewaysStg);
            pGatewaysStg->Release();
        }
        pDirsStg->Release();
    }
    return hResult;
}

// End of file for STORAGE.CPP


SUPPORT.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      SUPPORT.CPP
//
//  Description
//      Support functions for remote admin program.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "_WINDS.H"

///////////////////////////////////////////////////////////////////////////////
//    ReleaseSyncObj()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
void WINAPI ReleaseSyncObj()
{
    DeleteCriticalSection (&g_csIOInfo);
    DeleteCriticalSection (&g_csPipeID);
    DeleteCriticalSection (&g_csNotifLinks);
    DeleteCriticalSection (&g_csServiceState);
    CloseHandle (g_IOInfo.hResumeEvent);
}

///////////////////////////////////////////////////////////////////////////////
//    LoadSettingsFromRegistry()
//
//    Parameters
//      None
//
//    Purpose
//      This function is used to load the application setting structure values
//      from the registry under a subkey for our application. The entire data
//      structure is read from a single sub entry as an array of binary data,
//      unreadable to humans.
//
//    Return Value
//      None
//
void WINAPI LoadSettingsFromRegistry()
{
    HKEY hAppKey;
    // Try to open the key in the registry, open it with READ rights,
    // and read the data into our strcture
    if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE,
                                       WINDS_SERVICE_REGISTRY_KEY,
                                       0,
                                       KEY_READ,
                                       &hAppKey))
    {
        ULONG cbSize = MAX_PATH*sizeof(TCHAR);
        DWORD dwError = RegQueryValueEx (hAppKey,
                                         DATA_FILE_SUB_KEY,
                                         NULL,
                                         NULL,
                                         (LPBYTE)g_szDataFile,
                                         &cbSize);
        RegCloseKey (hAppKey);
        // If we failed to get the data from the registry we can't return
        // yet, we will proceed to initialize the options with default
        // values
        if (ERROR_SUCCESS == dwError)
        {
            return;
        }
    }
    // If we couldn't read the file name, then initialize to default values
    lstrcpy (g_szDataFile, TEXT(".\\WINDS.DAT"));
}

///////////////////////////////////////////////////////////////////////////////
//    SaveSettingsToRegistry()
//
//    Parameters
//      None
//
//    Purpose
//      This function is used to save the settings structure values to the
//      registry under a subkey for our application. The key where we write
//      the values is created if it doesn't exist. The entire structure data
//      is written to a single sub entry as an array of binary data,
//      unreadable to humans.
//
//    Return Value
//      None
//
void WINAPI SaveSettingsToRegistry()
{
    HKEY hAppKey;
    DWORD dwDisposition = MAX_PATH*sizeof(TCHAR);
    // Try to open the key in the registry, open it with READ/WRITE rights
    if (ERROR_SUCCESS != RegCreateKeyEx (HKEY_LOCAL_MACHINE,
                                         WINDS_SERVICE_REGISTRY_KEY,
                                         0,
                                         NULL,
                                         REG_OPTION_NON_VOLATILE,
                                         KEY_WRITE,
                                         NULL,
                                         &hAppKey,
                                         &dwDisposition))
    {
        // If we fail to access the registry, we need to get out, we don't
        // have a key opened where to write
        TraceResult ("SaveSettingsToRegistry: Failed to open app key", HRESULT_FROM_WIN32(GetLastError()));
    }
    else
    {
        // With the opened key handle, create a sub entry where we save our
        // structure values. For this sample we just write the entire
        // structure as a big blob of data, which we read the save way.
        if (ERROR_SUCCESS != RegSetValueEx (hAppKey,
                                            DATA_FILE_SUB_KEY,
                                            0,
                                            REG_SZ,
                                            (LPBYTE)g_szDataFile,
                                            (lstrlen (g_szDataFile)+1)*sizeof(TCHAR)))
        {
            TraceResult ("SaveSettingsToRegistry: Failed to save registry values", HRESULT_FROM_WIN32(GetLastError()));
        }
        // Close the handle we have open and return the appropiate flag
        RegCloseKey (hAppKey);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    GetLocalTempFileName()
//
//    Parameters
//      pszFileName     Pointer to a buffer allocated by the caller where the
//                      function returns a fully qualified patha and file for
//                      a uniquely named temporary file name.
//
//    Purpose
//      This function creates a temporary file name. The file name will be
//      returned in the pszFileName buffer which must have been allocated by
//      the caller. The file will have a fully qualified path to its location.
//      The location of the file is on the TEMP directory as set in the system.
//
//    Return Value
//      0 if the function is successful at creating a temporary
//      unique file name. A system error code otherwise.
//
HRESULT WINAPI GetLocalTempFileName(LPTSTR pszFileName)
{
    HRESULT hResult = S_OK;
    TCHAR szTmpPath[_MAX_PATH];

    // Ask the system for the TEMP directory
    if (!GetTempPath (_MAX_PATH, szTmpPath))
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("GetLocalTempFileName: Failed to get temp path", hResult);
        return hResult;
    }

    if (!GetTempFileName (szTmpPath,        // Call the Win32 API
                          TEXT("WDS"),      // Our fixed prefix for temp files
                          0,                // Use a pseudo-unique number
                          pszFileName))     // Destination buffer
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("GetLocalTempFileName: Failed to get temp file name", hResult);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    FileCopy()
//
//    Parameters
//      hDest           handle to destination file/stream
//      hSrc            handle to source file/stream
//      dwMsgLen        number of bytes to copy
//
//    Purpose
//      Copies bytes from one open file or stream object to another file
//      stream object.
//
//    Return Value
//      NO_ERROR on success, ERROR_READ/WRITE_FAULT otherwise
//
long WINAPI FileCopy(HANDLE hDest, HANDLE hSrc, DWORD dwMsgLen)
{
    BYTE  buf[IO_BUFFERSIZE];
    DWORD nRead, nRemaining = dwMsgLen;
    BOOL  bRes;
    for (DWORD nWritten=0; nRemaining>0; nRemaining -= nWritten)
    {
        bRes = ReadFile (hSrc, buf, min(nRemaining, (DWORD)IO_BUFFERSIZE), &nRead, NULL);
        if (!nRead || !bRes)
        {
            return HRESULT_FROM_WIN32(ERROR_READ_FAULT);
        }
        bRes = WriteFile(hDest, buf, nRead, &nWritten, NULL);
        if (!nWritten || !bRes)
        {
            return HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
        }
    }
    return NO_ERROR;
}

///////////////////////////////////////////////////////////////////////////////
//    WaitForClientConnection()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
long WINAPI WaitForClientConnection (HANDLE hPipe, DWORD dwTimeOut)
{
    dwTimeOut *= 1000; // The timeout value comes is seconds so we must convert them to milliseconds.

    long lResult = 0;
    BOOL bResult;
    OVERLAPPED ovWait = { 0 };
    ovWait.hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    if (NULL == ovWait.hEvent)
    {
        lResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("WaitForClientConnection: Failed to create wait event", lResult);
    }
    else
    {
        bResult = ConnectNamedPipe (hPipe, &ovWait);
        if (FALSE == bResult)
        {
            lResult = HRESULT_FROM_WIN32(GetLastError());
            if (HRESULT_FROM_WIN32(lResult) == HRESULT_FROM_WIN32(ERROR_IO_PENDING) ||
                HRESULT_FROM_WIN32(lResult) == HRESULT_FROM_WIN32(ERROR_PIPE_CONNECTED))
            {
                lResult = S_OK;
            }
        }
        if (!lResult)
        {
            lResult = WaitForSingleObject (ovWait.hEvent, dwTimeOut);
            if (WAIT_OBJECT_0 == lResult)
            {
                lResult = 0; // No error;
            }
            else
            {
                TraceMessage ("WaitForClientConnection: Client did not connect. Timeout");
                lResult = HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED);
            }
        }
        else
        {
            lResult = HRESULT_FROM_WIN32(GetLastError());
            TraceResult ("WaitForClientConnection: Failed to wait for connection", lResult);
        }
        CloseHandle (ovWait.hEvent);
    }
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////
//    AnsiToUnicode()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
HRESULT WINAPI AnsiToUnicode (LPSTR   szStr,
                              LPWSTR  wcStr,
                              ULONG   cchUnicode)
{
    HRESULT hResult = S_OK;
    // Do we have a string to duplicate?
    if (!szStr || !wcStr)
    {
        TraceMessage ("AnsiToUnicode: NULL string pointers");
        return E_INVALIDARG;
    }
    int iChars = MultiByteToWideChar (CP_ACP,
                                      MB_PRECOMPOSED,
                                      szStr,
                                      -1,
                                      wcStr,
                                      cchUnicode);
    if (0 == iChars)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("AnsiToUnicode: Failed tp convert to UNICODE", hResult);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    UnicodeToAnsi()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
HRESULT WINAPI UnicodeToAnsi (LPWSTR  wcStr,
                              LPSTR   szStr,
                              ULONG   cchAnsi)
{
    HRESULT hResult = S_OK;
    // Do we have a string to duplicate?
    if (!szStr || !wcStr)
    {
        TraceMessage ("UnicodeToAnsi: NULL string pointers");
        return E_INVALIDARG;
    }
    int iChars = WideCharToMultiByte (CP_ACP,
                                      0,
                                      wcStr,
                                      -1,
                                      szStr,
                                      cchAnsi,
                                      NULL,
                                      NULL);
    if (0 == iChars)
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("UnicodeToAnsi: Failed tp convert to UNICODE", hResult);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetNextPipeID()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
DWORD WINAPI GetNextPipeID()
{
    DWORD dwPipeID;
    EnterCriticalSection (&g_csPipeID);
    if (g_dwNextPipeID > MAX_PIPE_NUMBER)
    {
        g_dwNextPipeID = MIN_PIPE_NUMBER;
    }
    dwPipeID = g_dwNextPipeID;
    g_dwNextPipeID++;
    LeaveCriticalSection (&g_csPipeID);
    return dwPipeID;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::CObjectList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
CObjectList::CObjectList()
{
    TraceMessage ("CObjectList: Constructor called");
    m_hHeap = GetProcessHeap();
    m_pHead = NULL;
    InitializeCriticalSection (&m_csObj);
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::~CObjectList()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      None.
//
CObjectList::~CObjectList()
{
    TraceMessage ("CObjectList: Destructor called");
    // Free the entire list
    POBJLIST_NODE pNode = m_pHead;
    while (m_pHead)
    {
        pNode = m_pHead->pNext;
        HeapFree (m_hHeap, 0, m_pHead);
        m_pHead = pNode;
    }
    DeleteCriticalSection (&m_csObj);
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::Insert()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CObjectList::Insert (DWORD             dwObjID,
                                  LPTSTR            pObjName,
                                  WINDS_AB_OBJTYPE  ObjType)
{
    // Allocate a new node
    POBJLIST_NODE pNode, pNewNode = (POBJLIST_NODE)HeapAlloc (m_hHeap,
                                                              HEAP_ZERO_MEMORY,
                                                              sizeof(OBJLIST_NODE));
    if (NULL == pNewNode)
    {
        TraceResult ("CObjectList::Insert: Failed to allocate new node", E_OUTOFMEMORY);
        return E_OUTOFMEMORY;
    }
    // Copy the data of the node
    // pNewNode->pNext = NULL; <- implied in the allocation
    pNewNode->dwObjID = dwObjID;
    pNewNode->ObjType = ObjType;
    lstrcpy (pNewNode->szObjAlias, pObjName);

    EnterCriticalSection (&m_csObj);
    // Insert the node at the end of the list
    if (m_pHead)
    {
        pNode = m_pHead;
        while (m_pHead)
        {
            if (NULL == pNode->pNext)
            {
                pNode->pNext = pNewNode;
                break; // Out of the WHILE() loop
            }
            pNode = pNode->pNext;
        }
    }
    else
    {
        m_pHead = pNewNode;
    }
    LeaveCriticalSection (&m_csObj);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::Delete()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CObjectList::Delete (DWORD dwObjID)
{
    HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    EnterCriticalSection (&m_csObj);
    POBJLIST_NODE pNext, pPrev = NULL, pNode = m_pHead;
    while (pNode)
    {
        if (pNode->dwObjID == dwObjID)
        {
            // If the node is the head of the list, make the head point to the next node
            if (m_pHead == pNode)
            {
                m_pHead = m_pHead->pNext;
            }
            // Save the next pointer so that we may delete the current node
            pNext = pNode->pNext;
            HeapFree (m_hHeap, 0, pNode);
            if (pPrev)
            {
                pPrev->pNext = pNext;
            }
            hResult = S_OK;
            break; // Out of the WHILE() loop
        }
        else
        {
            pPrev = pNode;
            pNode = pNode->pNext;
        }
    }
    LeaveCriticalSection (&m_csObj);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::FindObjFromID()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CObjectList::FindObjFromID (DWORD              dwObjID,
                                         LPTSTR             pObjName,
                                         WINDS_AB_OBJTYPE * pObjType)
{
    HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    EnterCriticalSection (&m_csObj);
    POBJLIST_NODE pNode = m_pHead;
    while (pNode)
    {
        if (pNode->dwObjID == dwObjID)
        {
            *pObjType = pNode->ObjType;
            lstrcpy (pObjName, pNode->szObjAlias);
            hResult = S_OK;
            break; // Out of the WHILE() loop
        }
        pNode = pNode->pNext;
    }
    LeaveCriticalSection (&m_csObj);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::FindObjFromName()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CObjectList::FindObjFromName (WINDS_AB_OBJTYPE     ObjType,
                                           LPTSTR               pObjName,
                                           DWORD *              pdwObjID)
{
    HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    EnterCriticalSection (&m_csObj);
    POBJLIST_NODE pNode = m_pHead;
    while (pNode)
    {
        if (pNode->ObjType == ObjType && 0 == lstrcmpi (pNode->szObjAlias, pObjName))
        {
            *pdwObjID = pNode->dwObjID;
            hResult = S_OK;
            break; // Out of the WHILE() loop
        }
        pNode = pNode->pNext;
    }
    LeaveCriticalSection (&m_csObj);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::FindObjAndTypeFromName()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CObjectList::FindObjAndTypeFromName (LPTSTR               pObjName,
                                                  WINDS_AB_OBJTYPE *   pObjType,
                                                  DWORD *              pdwObjID)
{
    HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    EnterCriticalSection (&m_csObj);
    POBJLIST_NODE pNode = m_pHead;
    while (pNode)
    {
        if (0 == lstrcmpi (pNode->szObjAlias, pObjName))
        {
            *pObjType = pNode->ObjType;
            *pdwObjID = pNode->dwObjID;
            hResult = S_OK;
            break; // Out of the WHILE() loop
        }
        pNode = pNode->pNext;
    }
    LeaveCriticalSection (&m_csObj);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CObjectList::IsAliasNameAvailable()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
BOOL WINAPI CObjectList::IsAliasNameAvailable (LPTSTR pObjName)
{
    BOOL bResult = TRUE;
    EnterCriticalSection (&m_csObj);
    POBJLIST_NODE pNode = m_pHead;
    while (pNode)
    {
        if (0 == lstrcmpi (pNode->szObjAlias, pObjName))
        {
            bResult = FALSE;
            break; // Out of the WHILE() loop
        }
        pNode = pNode->pNext;
    }
    LeaveCriticalSection (&m_csObj);
    return bResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreateDLsDirectory()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreateDLsDirectory (LPSTORAGE pStorage)
{
    LPSTORAGE pPFStg;
    HRESULT hResult = pStorage->CreateStorage (DISTRIBUTION_LISTS, CREATE_FLAGS, 0, 0, &pPFStg);
    if (!hResult)
    {
        pPFStg->Release();
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CreatePublicFoldersDirectory()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//
HRESULT WINAPI CreatePublicFoldersDirectory (LPSTORAGE pStorage)
{
    LPSTORAGE pPFStg;
    HRESULT hResult = pStorage->CreateStorage (PUBLIC_FOLDERS_ROOT, CREATE_FLAGS, 0, 0, &pPFStg);
    if (!hResult)
    {
        pPFStg->Release();
    }
    return hResult;
}

// End of file for SUPPORT.CPP


WDSADM.ACF

[implicit_handle(handle_t hWINDSADM)]
interface WINDSADM
{

}


WDSADM.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 2.00.0102 */
/* at Wed Sep 27 11:30:53 1995
 */
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"

#ifndef __wdsadm_h__
#define __wdsadm_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __WINDSADM_INTERFACE_DEFINED__
#define __WINDSADM_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: WINDSADM
 * at Wed Sep 27 11:30:53 1995
 * using MIDL 2.00.0102
 ****************************************/
/* [implicit_handle][unique][version][uuid] */

            /* size is 396 */
typedef struct  _ADM_MAILBOX_INFO_A
    {
    unsigned char szMailboxName[ 17 ];
    unsigned char szFullName[ 33 ];
    unsigned char szJobTitle[ 33 ];
    unsigned char szOffice[ 33 ];
    unsigned char szPhone[ 13 ];
    unsigned char szAltPhone[ 13 ];
    unsigned char szFax[ 13 ];
    unsigned char szPassword[ 17 ];
    unsigned char szCompany[ 33 ];
    unsigned char szDepartment[ 33 ];
    unsigned char szComments[ 80 ];
    unsigned char szManagerName[ 33 ];
    unsigned char szManagerAlias[ 33 ];
    unsigned long dwManagerID;
    unsigned long dwObjID;
    unsigned long dwFlags;
    }   ADM_MAILBOX_INFO_A;

            /* size is 64 */
typedef struct  _DLM_INFO_A
    {
    unsigned char szMemberName[ 36 ];
    unsigned char szMemberAlias[ 20 ];
    unsigned long dwMemberID;
    unsigned long dwMemberType;
    }   DLM_INFO_A;

            /* size is 68 */
typedef struct  _DLM_LIST_A
    {
    DLM_INFO_A Info;
    struct _DLM_LIST_A __RPC_FAR *pNext;
    }   DLM_LIST_A;

            /* size is 4 */
typedef struct  _DLM_ARRAY_A
    {
    unsigned long dwCount;
    /* [size_is] */ DLM_INFO_A pMembers[ 1 ];
    }   DLM_ARRAY_A;

            /* size is 68 */
typedef /* [transmit] */ DLM_LIST_A DLM_XMIT_LIST_A;

            /* size is 780 */
typedef struct  _ADM_MAILBOX_INFO_W
    {
    wchar_t szMailboxName[ 17 ];
    wchar_t szFullName[ 33 ];
    wchar_t szJobTitle[ 33 ];
    wchar_t szOffice[ 33 ];
    wchar_t szPhone[ 13 ];
    wchar_t szAltPhone[ 13 ];
    wchar_t szFax[ 13 ];
    wchar_t szPassword[ 17 ];
    wchar_t szCompany[ 33 ];
    wchar_t szDepartment[ 33 ];
    wchar_t szComments[ 80 ];
    wchar_t szManagerName[ 33 ];
    wchar_t szManagerAlias[ 33 ];
    unsigned long dwManagerID;
    unsigned long dwObjID;
    unsigned long dwFlags;
    }   ADM_MAILBOX_INFO_W;

            /* size is 120 */
typedef struct  _DLM_INFO_W
    {
    wchar_t szMemberName[ 36 ];
    wchar_t szMemberAlias[ 20 ];
    unsigned long dwMemberID;
    unsigned long dwMemberType;
    }   DLM_INFO_W;

            /* size is 124 */
typedef struct  _DLM_LIST_W
    {
    DLM_INFO_W Info;
    struct _DLM_LIST_W __RPC_FAR *pNext;
    }   DLM_LIST_W;

            /* size is 4 */
typedef struct  _DLM_ARRAY_W
    {
    unsigned long dwCount;
    /* [size_is] */ DLM_INFO_W pMembers[ 1 ];
    }   DLM_ARRAY_W;

            /* size is 124 */
typedef /* [transmit] */ DLM_LIST_W DLM_XMIT_LIST_W;

            /* size is 4 */
long RemoteAdmIsServerRunning( void);

            /* size is 4 */
long RemoteAdmDeleteObject(
    /* [in] */ unsigned long dwObjID);

            /* size is 4 */
long RemoteAdmGetServerMailboxes(
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteAdmGetServerDistLists(
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteAdmEmptyMailbox(
    /* [in] */ unsigned long dwMailboxID);

            /* size is 4 */
long RemoteAdmGetGALDirectory(
    /* [in] */ unsigned long dwFlags,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteAdmTerminateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID);

            /* size is 4 */
long RemoteAdmValidateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteAdmCreateMailboxA(
    /* [in] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmGetMailboxPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwObjType,
    /* [out] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmSetMailboxPropsA(
    /* [in] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmCreateDistListA(
    /* [string][in] */ unsigned char __RPC_FAR *szDLAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers);

            /* size is 4 */
long RemoteAdmGetDLPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwFlags,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerName,
    /* [out] */ unsigned long __RPC_FAR *pdwOwnerID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers);

            /* size is 4 */
long RemoteAdmSetDLPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [string][in] */ unsigned char __RPC_FAR *szDLAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [string][in] */ unsigned char __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers);

            /* size is 4 */
long RemoteAdmTerminateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID);

            /* size is 4 */
long RemoteAdmValidateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteAdmCreateMailboxW(
    /* [in] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmGetMailboxPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwObjType,
    /* [out] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmSetMailboxPropsW(
    /* [in] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo);

            /* size is 4 */
long RemoteAdmCreateDistListW(
    /* [string][in] */ wchar_t __RPC_FAR *szDLAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers);

            /* size is 4 */
long RemoteAdmGetDLPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwFlags,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerName,
    /* [out] */ unsigned long __RPC_FAR *pdwOwnerID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers);

            /* size is 4 */
long RemoteAdmSetDLPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [string][in] */ wchar_t __RPC_FAR *szDLAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [string][in] */ wchar_t __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers);

extern handle_t hWINDSADM;

extern RPC_IF_HANDLE WINDSADM_v1_0_c_ifspec;
extern RPC_IF_HANDLE WINDSADM_v1_0_s_ifspec;
#endif /* __WINDSADM_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

void __RPC_USER DLM_XMIT_LIST_A_to_xmit( DLM_XMIT_LIST_A __RPC_FAR *, DLM_ARRAY_A  __RPC_FAR * __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_A_from_xmit( DLM_ARRAY_A  __RPC_FAR *, DLM_XMIT_LIST_A __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_A_free_inst( DLM_XMIT_LIST_A __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_A_free_xmit( DLM_ARRAY_A  __RPC_FAR * );

void __RPC_USER DLM_XMIT_LIST_W_to_xmit( DLM_XMIT_LIST_W __RPC_FAR *, DLM_ARRAY_W  __RPC_FAR * __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_W_from_xmit( DLM_ARRAY_W  __RPC_FAR *, DLM_XMIT_LIST_W __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_W_free_inst( DLM_XMIT_LIST_W __RPC_FAR * );
void __RPC_USER DLM_XMIT_LIST_W_free_xmit( DLM_ARRAY_W  __RPC_FAR * );

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


WDSADM.IDL

[
    uuid(2BC66860-E91E-11CE-B2F0-00AA00510E3B),
    version(1.0),
    pointer_default(unique)
]

#define MAX_ALIAS_SIZE      16+1
#define MAX_STRING_SIZE     32+1
#define MAX_PHONE_SIZE      12+1
#define MAX_COMMENT_SIZE    79+1
#define MAX_PASSWORD_SIZE   MAX_ALIAS_SIZE

interface WINDSADM
{
    // ANSI data structures
    typedef struct _ADM_MAILBOX_INFO_A
    {
        char            szMailboxName[MAX_ALIAS_SIZE];
        char            szFullName[MAX_STRING_SIZE];
        char            szJobTitle[MAX_STRING_SIZE];
        char            szOffice[MAX_STRING_SIZE];
        char            szPhone[MAX_PHONE_SIZE];
        char            szAltPhone[MAX_PHONE_SIZE];
        char            szFax[MAX_PHONE_SIZE];
        char            szPassword[MAX_ALIAS_SIZE];
        char            szCompany[MAX_STRING_SIZE];
        char            szDepartment[MAX_STRING_SIZE];
        char            szComments[MAX_COMMENT_SIZE];
        char            szManagerName[MAX_STRING_SIZE];
        char            szManagerAlias[MAX_STRING_SIZE];
        unsigned long   dwManagerID;
        unsigned long   dwObjID;
        unsigned long   dwFlags;
    } ADM_MAILBOX_INFO_A;

    typedef struct _DLM_INFO_A
    {
        char            szMemberName[36];
        char            szMemberAlias[20];
        unsigned long   dwMemberID;
        unsigned long   dwMemberType;
    } DLM_INFO_A;

    typedef struct _DLM_LIST_A
    {
        DLM_INFO_A            Info;
        struct _DLM_LIST_A *  pNext;
    } DLM_LIST_A;

    typedef struct _DLM_ARRAY_A
    {
        unsigned long                   dwCount;
        [size_is(dwCount)] DLM_INFO_A   pMembers[];
    } DLM_ARRAY_A;

    typedef [transmit_as(DLM_ARRAY_A)] DLM_LIST_A   DLM_XMIT_LIST_A;

    // UNICODE data structures
    typedef struct _ADM_MAILBOX_INFO_W
    {
        wchar_t         szMailboxName[MAX_ALIAS_SIZE];
        wchar_t         szFullName[MAX_STRING_SIZE];
        wchar_t         szJobTitle[MAX_STRING_SIZE];
        wchar_t         szOffice[MAX_STRING_SIZE];
        wchar_t         szPhone[MAX_PHONE_SIZE];
        wchar_t         szAltPhone[MAX_PHONE_SIZE];
        wchar_t         szFax[MAX_PHONE_SIZE];
        wchar_t         szPassword[MAX_ALIAS_SIZE];
        wchar_t         szCompany[MAX_STRING_SIZE];
        wchar_t         szDepartment[MAX_STRING_SIZE];
        wchar_t         szComments[MAX_COMMENT_SIZE];
        wchar_t         szManagerName[MAX_STRING_SIZE];
        wchar_t         szManagerAlias[MAX_STRING_SIZE];
        unsigned long   dwManagerID;
        unsigned long   dwObjID;
        unsigned long   dwFlags;
    } ADM_MAILBOX_INFO_W;

    typedef struct _DLM_INFO_W
    {
        wchar_t         szMemberName[36];
        wchar_t         szMemberAlias[20];
        unsigned long   dwMemberID;
        unsigned long   dwMemberType;
    } DLM_INFO_W;

    typedef struct _DLM_LIST_W
    {
        DLM_INFO_W            Info;
        struct _DLM_LIST_W *  pNext;
    } DLM_LIST_W;

    typedef struct _DLM_ARRAY_W
    {
        unsigned long                   dwCount;
        [size_is(dwCount)] DLM_INFO_W   pMembers[];
    } DLM_ARRAY_W;

    typedef [transmit_as(DLM_ARRAY_W)] DLM_LIST_W   DLM_XMIT_LIST_W;

    long RemoteAdmIsServerRunning
            ();
    long RemoteAdmDeleteObject
            ([in] unsigned long                                         dwObjID);
    long RemoteAdmGetServerMailboxes
            ([out] long *                                               pPipeNumber);
    long RemoteAdmGetServerDistLists
            ([out] long *                                               pPipeNumber);
    long RemoteAdmEmptyMailbox
            ([in] unsigned long                                         dwMailboxID);
    long RemoteAdmGetGALDirectory
            ([in] unsigned long                                         dwFlags,
             [out] long *                                               pPipeNumber);

    // ANSI version of the RPC calls
    long RemoteAdmTerminateNotifA
            ([in, string] unsigned char *                               szComputerName,
             [in] unsigned long                                         ulConnectionID);
    long RemoteAdmValidateNotifA
            ([in, string] unsigned char *                               szComputerName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);
    long RemoteAdmCreateMailboxA
            ([in] ADM_MAILBOX_INFO_A *                                  pMailboxInfo);
    long RemoteAdmGetMailboxPropsA
            ([in] unsigned long                                         dwObjID,
             [out] unsigned long *                                      pdwObjType,
             [out] ADM_MAILBOX_INFO_A *                                 pMailboxInfo);
    long RemoteAdmSetMailboxPropsA
            ([in] ADM_MAILBOX_INFO_A *                                  pMailboxInfo);

    long RemoteAdmCreateDistListA
            ([in, string] unsigned char *                               szDLAlias,
             [in, string] unsigned char *                               szDLFullName,
             [in] unsigned long                                         dwFlags,
             [in, string] unsigned char *                               szOwnerAlias,
             [in, string] unsigned char *                               szOwnerName,
             [in] unsigned long                                         dwOwnerID,
             [in] DLM_XMIT_LIST_A *                                     pMembers);
    long RemoteAdmGetDLPropsA
            ([in] unsigned long                                         dwObjID,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szDLAlias,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szDLFullName,
             [out] unsigned long *                                      pdwFlags,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szOwnerAlias,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szOwnerName,
             [out] unsigned long *                                      pdwOwnerID,
             [out, string, size_is(MAX_COMMENT_SIZE)] unsigned char *   szComments,
             [in, out] DLM_XMIT_LIST_A *                                pMembers);
    long RemoteAdmSetDLPropsA
            ([in] unsigned long                                         dwObjID,
             [in, string] unsigned char *                               szDLAlias,
             [in, string] unsigned char *                               szDLFullName,
             [in] unsigned long                                         dwFlags,
             [in, string] unsigned char *                               szOwnerAlias,
             [in, string] unsigned char *                               szOwnerName,
             [in] unsigned long                                         dwOwnerID,
             [in, string] unsigned char *                               szComments,
             [in, out] DLM_XMIT_LIST_A *                                pMembers);

    // UNICODE version of the RPC calls
    long RemoteAdmTerminateNotifW
            ([in, string] wchar_t *                                     szComputerName,
             [in] unsigned long                                         ulConnectionID);
    long RemoteAdmValidateNotifW
            ([in, string] wchar_t *                                     szComputerName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);

    long RemoteAdmCreateMailboxW
            ([in] ADM_MAILBOX_INFO_W *                                  pMailboxInfo);
    long RemoteAdmGetMailboxPropsW
            ([in] unsigned long                                         dwObjID,
             [out] unsigned long *                                      pdwObjType,
             [out] ADM_MAILBOX_INFO_W *                                 pMailboxInfo);
    long RemoteAdmSetMailboxPropsW
            ([in] ADM_MAILBOX_INFO_W *                                  pMailboxInfo);

    long RemoteAdmCreateDistListW
            ([in, string] wchar_t *                                     szDLAlias,
             [in, string] wchar_t *                                     szDLFullName,
             [in] unsigned long                                         dwFlags,
             [in, string] wchar_t *                                     szOwnerAlias,
             [in, string] wchar_t *                                     szOwnerName,
             [in] unsigned long                                         dwOwnerID,
             [in] DLM_XMIT_LIST_W *                                     pMembers);
    long RemoteAdmGetDLPropsW
            ([in] unsigned long                                         dwObjID,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szDLAlias,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szDLFullName,
             [out] unsigned long *                                      pdwFlags,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szOwnerAlias,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szOwnerName,
             [out] unsigned long *                                      pdwOwnerID,
             [out, string, size_is(MAX_COMMENT_SIZE)] wchar_t *         szComments,
             [in, out] DLM_XMIT_LIST_W *                                pMembers);
    long RemoteAdmSetDLPropsW
            ([in] unsigned long                                         dwObjID,
             [in, string] wchar_t *                                     szDLAlias,
             [in, string] wchar_t *                                     szDLFullName,
             [in] unsigned long                                         dwFlags,
             [in, string] wchar_t *                                     szOwnerAlias,
             [in, string] wchar_t *                                     szOwnerName,
             [in] unsigned long                                         dwOwnerID,
             [in, string] wchar_t *                                     szComments,
             [in, out] DLM_XMIT_LIST_W *                                pMembers);
}


WDSADM_C.C

/* this ALWAYS GENERATED file contains the RPC client stubs */

/* File created by MIDL compiler version 2.00.0102 */
/* at Wed Sep 27 11:30:53 1995
 */
//@@MIDL_FILE_HEADING(  )

#include <string.h>
#if defined( _ALPHA_ )
#include <stdarg.h>
#endif

#include "wdsadm.h"

extern const MIDL_FORMAT_STRING __MIDLFormatString;

extern const MIDL_FORMAT_STRING __MIDLProcFormatString;
handle_t hWINDSADM;

static const RPC_CLIENT_INTERFACE WINDSADM___RpcClientInterface =
    {
    sizeof(RPC_CLIENT_INTERFACE),
    {{0x2BC66860,0xE91E,0x11CE,{0xB2,0xF0,0x00,0xAA,0x00,0x51,0x0E,0x3B}},{1,0}},
    {{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
    0,
    0,
    0,
    0,
    0
    };
RPC_IF_HANDLE WINDSADM_v1_0_c_ifspec = (RPC_IF_HANDLE)& WINDSADM___RpcClientInterface;

extern const MIDL_STUB_DESC WINDSADM_StubDesc;

static RPC_BINDING_HANDLE WINDSADM__MIDL_AutoBindHandle;

long RemoteAdmIsServerRunning( void)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          0);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[0] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmDeleteObject(
    /* [in] */ unsigned long dwObjID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          1);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[2] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetServerMailboxes(
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          2);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[6] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetServerDistLists(
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          3);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[6] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmEmptyMailbox(
    /* [in] */ unsigned long dwMailboxID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          4);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwMailboxID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[2] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetGALDirectory(
    /* [in] */ unsigned long dwFlags,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          5);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[12] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmTerminateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          6);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 11U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulConnectionID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[20] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmValidateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          7);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 11U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[28] );

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmCreateMailboxA(
    /* [in] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          8);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetMailboxPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwObjType,
    /* [out] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          9);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[46] );

        *pdwObjType = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36],
                                   (unsigned char)0 );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmSetMailboxPropsA(
    /* [in] */ ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          10);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmCreateDistListA(
    /* [string][in] */ unsigned char __RPC_FAR *szDLAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          11);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 13U + 11U + 13U + 13U + 11U + 0U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[156] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwOwnerID;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[156] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[58] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetDLPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwFlags,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerName,
    /* [out] */ unsigned long __RPC_FAR *pdwOwnerID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          12);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U + 0U;
        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[220] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[220] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[84] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[170],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[180],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[190],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[200],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[210],
                                       (unsigned char)0 );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[220],
                                  (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmSetDLPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [string][in] */ unsigned char __RPC_FAR *szDLAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [string][in] */ unsigned char __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [string][in] */ unsigned char __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_A __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          13);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U + 12U + 13U + 11U + 13U + 13U + 11U + 13U + 0U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwOwnerID;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[120] );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[234],
                                  (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmTerminateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          14);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 10U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulConnectionID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[152] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmValidateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          15);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 10U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[160] );

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmCreateMailboxW(
    /* [in] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          16);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetMailboxPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwObjType,
    /* [out] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          17);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[178] );

        *pdwObjType = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276],
                                   (unsigned char)0 );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmSetMailboxPropsW(
    /* [in] */ ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          18);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 0U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmCreateDistListW(
    /* [string][in] */ wchar_t __RPC_FAR *szDLAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          19);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 12U + 14U + 10U + 14U + 14U + 10U + 0U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[396] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwOwnerID;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[396] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[190] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmGetDLPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwFlags,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerName,
    /* [out] */ unsigned long __RPC_FAR *pdwOwnerID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          20);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U + 0U;
        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[460] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[460] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[216] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[410],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[420],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[430],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[440],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[450],
                                       (unsigned char)0 );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[460],
                                  (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteAdmSetDLPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [string][in] */ wchar_t __RPC_FAR *szDLAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szDLFullName,
    /* [in] */ unsigned long dwFlags,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [string][in] */ wchar_t __RPC_FAR *szOwnerName,
    /* [in] */ unsigned long dwOwnerID,
    /* [string][in] */ wchar_t __RPC_FAR *szComments,
    /* [out][in] */ DLM_XMIT_LIST_W __RPC_FAR *pMembers)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSADM_StubDesc,
                          21);

        _Handle = hWINDSADM;

        _StubMsg.BufferLength = 4U + 12U + 14U + 10U + 14U + 14U + 10U + 14U + 0U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[474] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwOwnerID;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[246] );

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[474] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[252] );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[474],
                                  (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

extern const XMIT_ROUTINE_QUINTUPLE TransmitAsRoutines[];

static const MIDL_STUB_DESC WINDSADM_StubDesc =
    {
    (void __RPC_FAR *)& WINDSADM___RpcClientInterface,
    MIDL_user_allocate,
    MIDL_user_free,
    &hWINDSADM,
    0,
    0,
    0,
    TransmitAsRoutines,
    __MIDLFormatString.Format,
    0, /* -error bounds_check flag */
    0x10001, /* Ndr library version */
    0,
    0x2000066, /* MIDL Version 2.0.102 */
    0
    };
void __RPC_USER
DLM_XMIT_LIST_A_XmitTranslateToXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_to_xmit( (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType,
        (DLM_ARRAY_A __RPC_FAR * __RPC_FAR *) &pStubMsg->pTransmitType );

}
void __RPC_USER
DLM_XMIT_LIST_A_XmitTranslateFromXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_from_xmit( (DLM_ARRAY_A __RPC_FAR *) pStubMsg->pTransmitType,
        (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_A_XmitFreeXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_free_xmit( (DLM_ARRAY_A __RPC_FAR *) pStubMsg->pTransmitType );
}
void __RPC_USER
DLM_XMIT_LIST_A_XmitFreeInst_0000( PMIDL_STUB_MESSAGE pStubMsg )

{
    DLM_XMIT_LIST_A_free_inst( (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitTranslateToXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_to_xmit( (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType,
        (DLM_ARRAY_W __RPC_FAR * __RPC_FAR *) &pStubMsg->pTransmitType );

}
void __RPC_USER
DLM_XMIT_LIST_W_XmitTranslateFromXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_from_xmit( (DLM_ARRAY_W __RPC_FAR *) pStubMsg->pTransmitType,
        (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitFreeXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_free_xmit( (DLM_ARRAY_W __RPC_FAR *) pStubMsg->pTransmitType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitFreeInst_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_free_inst( (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType );
}

static const XMIT_ROUTINE_QUINTUPLE TransmitAsRoutines[] =
        {

            {
            DLM_XMIT_LIST_A_XmitTranslateToXmit_0000
            ,DLM_XMIT_LIST_A_XmitTranslateFromXmit_0000
            ,DLM_XMIT_LIST_A_XmitFreeXmit_0000
            ,DLM_XMIT_LIST_A_XmitFreeInst_0000
            },
            {
            DLM_XMIT_LIST_W_XmitTranslateToXmit_0001
            ,DLM_XMIT_LIST_W_XmitTranslateFromXmit_0001
            ,DLM_XMIT_LIST_W_XmitFreeXmit_0001
            ,DLM_XMIT_LIST_W_XmitFreeInst_0001
            }

        };

#if !defined(__RPC_WIN32__)
#error  Invalid build platform for this stub.
#endif

static const MIDL_FORMAT_STRING __MIDLProcFormatString =
    {
        0,
        {
            0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  2 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  4 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  6 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/*  8 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 10 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 12 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 14 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 16 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 18 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 20 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 22 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 24 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 26 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 28 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 30 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 32 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 34 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 36 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 38 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 40 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 42 */    NdrFcShort( 0x8 ),  /* Type Offset=8 */
/* 44 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 46 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 48 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 50 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 52 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 54 */    NdrFcShort( 0x62 ), /* Type Offset=98 */
/* 56 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 58 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 60 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 62 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 64 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 66 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 68 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 70 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 72 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 74 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 76 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 78 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 80 */    NdrFcShort( 0x66 ), /* Type Offset=102 */
/* 82 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 84 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 86 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 88 */    NdrFcShort( 0xa6 ), /* Type Offset=166 */
/* 90 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 92 */    NdrFcShort( 0xb0 ), /* Type Offset=176 */
/* 94 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 96 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 98 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 100 */   NdrFcShort( 0xba ), /* Type Offset=186 */
/* 102 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 104 */   NdrFcShort( 0xc4 ), /* Type Offset=196 */
/* 106 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 108 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 110 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 112 */   NdrFcShort( 0xce ), /* Type Offset=206 */
/* 114 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 116 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 118 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 120 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 122 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 124 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 126 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 128 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 130 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 132 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 134 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 136 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 138 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 140 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 142 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 144 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 146 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 148 */   NdrFcShort( 0xe6 ), /* Type Offset=230 */
/* 150 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 152 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 154 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 156 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 158 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 160 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 162 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 164 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 166 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 168 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 170 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 172 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 174 */   NdrFcShort( 0xf8 ), /* Type Offset=248 */
/* 176 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 178 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 180 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 182 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 184 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 186 */   NdrFcShort( 0x152 ),    /* Type Offset=338 */
/* 188 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 190 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 192 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 194 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 196 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 198 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 200 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 202 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 204 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 206 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 208 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 210 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 212 */   NdrFcShort( 0x156 ),    /* Type Offset=342 */
/* 214 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 216 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 218 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 220 */   NdrFcShort( 0x196 ),    /* Type Offset=406 */
/* 222 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 224 */   NdrFcShort( 0x1a0 ),    /* Type Offset=416 */
/* 226 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 228 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 230 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 232 */   NdrFcShort( 0x1aa ),    /* Type Offset=426 */
/* 234 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 236 */   NdrFcShort( 0x1b4 ),    /* Type Offset=436 */
/* 238 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 240 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 242 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 244 */   NdrFcShort( 0x1be ),    /* Type Offset=446 */
/* 246 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 248 */   NdrFcShort( 0x1c8 ),    /* Type Offset=456 */
/* 250 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 252 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 254 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 256 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 258 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 260 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 262 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 264 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 266 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 268 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 270 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 272 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 274 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 276 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 278 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 280 */   NdrFcShort( 0x1d6 ),    /* Type Offset=470 */
/* 282 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */

            0x0
        }
    };

static const MIDL_FORMAT_STRING __MIDLFormatString =
    {
        0,
        {
            0x11, 0xc,  /* FC_RP [alloced_on_stack] [simple_pointer] */
/*  2 */    0x8,        /* FC_LONG */
            0x5c,       /* FC_PAD */
/*  4 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/*  6 */
            0x22,       /* FC_C_CSTRING */
            0x5c,       /* FC_PAD */
/*  8 */
            0x11, 0x0,  /* FC_RP */
/* 10 */    NdrFcShort( 0x1a ), /* Offset= 26 (36) */
/* 12 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 14 */    NdrFcShort( 0x11 ), /* 17 */
/* 16 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 18 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 20 */    NdrFcShort( 0x21 ), /* 33 */
/* 22 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 24 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 26 */    NdrFcShort( 0xd ),  /* 13 */
/* 28 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 30 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 32 */    NdrFcShort( 0x50 ), /* 80 */
/* 34 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 36 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 38 */    NdrFcShort( 0x18c ),    /* 396 */
/* 40 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 42 */    NdrFcShort( 0xffffffe2 ),   /* Offset= -30 (12) */
/* 44 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 46 */    NdrFcShort( 0xffffffe4 ),   /* Offset= -28 (18) */
/* 48 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 50 */    NdrFcShort( 0xffffffe0 ),   /* Offset= -32 (18) */
/* 52 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 54 */    NdrFcShort( 0xffffffdc ),   /* Offset= -36 (18) */
/* 56 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 58 */    NdrFcShort( 0xffffffde ),   /* Offset= -34 (24) */
/* 60 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 62 */    NdrFcShort( 0xffffffda ),   /* Offset= -38 (24) */
/* 64 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 66 */    NdrFcShort( 0xffffffd6 ),   /* Offset= -42 (24) */
/* 68 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 70 */    NdrFcShort( 0xffffffc6 ),   /* Offset= -58 (12) */
/* 72 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 74 */    NdrFcShort( 0xffffffc8 ),   /* Offset= -56 (18) */
/* 76 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 78 */    NdrFcShort( 0xffffffc4 ),   /* Offset= -60 (18) */
/* 80 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 82 */    NdrFcShort( 0xffffffcc ),   /* Offset= -52 (30) */
/* 84 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 86 */    NdrFcShort( 0xffffffbc ),   /* Offset= -68 (18) */
/* 88 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 90 */    NdrFcShort( 0xffffffb8 ),   /* Offset= -72 (18) */
/* 92 */    0x38,       /* FC_ALIGNM4 */
            0x8,        /* FC_LONG */
/* 94 */    0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 96 */    0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 98 */
            0x11, 0x4,  /* FC_RP [alloced_on_stack] */
/* 100 */   NdrFcShort( 0xffffffc0 ),   /* Offset= -64 (36) */
/* 102 */
            0x11, 0x0,  /* FC_RP */
/* 104 */   NdrFcShort( 0x34 ), /* Offset= 52 (156) */
/* 106 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 108 */   NdrFcShort( 0x24 ), /* 36 */
/* 110 */   0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 112 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 114 */   NdrFcShort( 0x14 ), /* 20 */
/* 116 */   0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 118 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 120 */   NdrFcShort( 0x40 ), /* 64 */
/* 122 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 124 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (106) */
/* 126 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 128 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (112) */
/* 130 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 132 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 134 */
            0x1b,       /* FC_CARRAY */
            0x3,        /* 3 */
/* 136 */   NdrFcShort( 0x40 ), /* 64 */
/* 138 */   0x8,        /* 8 */
            0x0,        /*  */
/* 140 */   NdrFcShort( 0xfffffffc ),   /* -4 */
/* 142 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 144 */   NdrFcShort( 0xffffffe6 ),   /* Offset= -26 (118) */
/* 146 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 148 */
            0x17,       /* FC_CSTRUCT */
            0x3,        /* 3 */
/* 150 */   NdrFcShort( 0x4 ),  /* 4 */
/* 152 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (134) */
/* 154 */   0x8,        /* FC_LONG */
            0x5b,       /* FC_END */
/* 156 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 158 */   NdrFcShort( 0x0 ),  /* 0 */
/* 160 */   NdrFcShort( 0x44 ), /* 68 */
/* 162 */   NdrFcShort( 0x0 ),  /* 0 */
/* 164 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (148) */
/* 166 */
            0x11, 0x0,  /* FC_RP */
/* 168 */   NdrFcShort( 0x2 ),  /* Offset= 2 (170) */
/* 170 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 172 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 174 */   NdrFcShort( 0x11 ), /* 17 */
/* 176 */
            0x11, 0x0,  /* FC_RP */
/* 178 */   NdrFcShort( 0x2 ),  /* Offset= 2 (180) */
/* 180 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 182 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 184 */   NdrFcShort( 0x21 ), /* 33 */
/* 186 */
            0x11, 0x0,  /* FC_RP */
/* 188 */   NdrFcShort( 0x2 ),  /* Offset= 2 (190) */
/* 190 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 192 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 194 */   NdrFcShort( 0x11 ), /* 17 */
/* 196 */
            0x11, 0x0,  /* FC_RP */
/* 198 */   NdrFcShort( 0x2 ),  /* Offset= 2 (200) */
/* 200 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 202 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 204 */   NdrFcShort( 0x21 ), /* 33 */
/* 206 */
            0x11, 0x0,  /* FC_RP */
/* 208 */   NdrFcShort( 0x2 ),  /* Offset= 2 (210) */
/* 210 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 212 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 214 */   NdrFcShort( 0x50 ), /* 80 */
/* 216 */
            0x11, 0x0,  /* FC_RP */
/* 218 */   NdrFcShort( 0x2 ),  /* Offset= 2 (220) */
/* 220 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 222 */   NdrFcShort( 0x0 ),  /* 0 */
/* 224 */   NdrFcShort( 0x44 ), /* 68 */
/* 226 */   NdrFcShort( 0x0 ),  /* 0 */
/* 228 */   NdrFcShort( 0xffffffb0 ),   /* Offset= -80 (148) */
/* 230 */
            0x11, 0x0,  /* FC_RP */
/* 232 */   NdrFcShort( 0x2 ),  /* Offset= 2 (234) */
/* 234 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 236 */   NdrFcShort( 0x0 ),  /* 0 */
/* 238 */   NdrFcShort( 0x44 ), /* 68 */
/* 240 */   NdrFcShort( 0x0 ),  /* 0 */
/* 242 */   NdrFcShort( 0xffffffa2 ),   /* Offset= -94 (148) */
/* 244 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 246 */
            0x25,       /* FC_C_WSTRING */
            0x5c,       /* FC_PAD */
/* 248 */
            0x11, 0x0,  /* FC_RP */
/* 250 */   NdrFcShort( 0x1a ), /* Offset= 26 (276) */
/* 252 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 254 */   NdrFcShort( 0x22 ), /* 34 */
/* 256 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 258 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 260 */   NdrFcShort( 0x42 ), /* 66 */
/* 262 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 264 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 266 */   NdrFcShort( 0x1a ), /* 26 */
/* 268 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 270 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 272 */   NdrFcShort( 0xa0 ), /* 160 */
/* 274 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 276 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 278 */   NdrFcShort( 0x30c ),    /* 780 */
/* 280 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 282 */   NdrFcShort( 0xffffffe2 ),   /* Offset= -30 (252) */
/* 284 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 286 */   NdrFcShort( 0xffffffe4 ),   /* Offset= -28 (258) */
/* 288 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 290 */   NdrFcShort( 0xffffffe0 ),   /* Offset= -32 (258) */
/* 292 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 294 */   NdrFcShort( 0xffffffdc ),   /* Offset= -36 (258) */
/* 296 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 298 */   NdrFcShort( 0xffffffde ),   /* Offset= -34 (264) */
/* 300 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 302 */   NdrFcShort( 0xffffffda ),   /* Offset= -38 (264) */
/* 304 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 306 */   NdrFcShort( 0xffffffd6 ),   /* Offset= -42 (264) */
/* 308 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 310 */   NdrFcShort( 0xffffffc6 ),   /* Offset= -58 (252) */
/* 312 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 314 */   NdrFcShort( 0xffffffc8 ),   /* Offset= -56 (258) */

/* 316 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 318 */   NdrFcShort( 0xffffffc4 ),   /* Offset= -60 (258) */
/* 320 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 322 */   NdrFcShort( 0xffffffcc ),   /* Offset= -52 (270) */
/* 324 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 326 */   NdrFcShort( 0xffffffbc ),   /* Offset= -68 (258) */
/* 328 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 330 */   NdrFcShort( 0xffffffb8 ),   /* Offset= -72 (258) */
/* 332 */   0x38,       /* FC_ALIGNM4 */
            0x8,        /* FC_LONG */
/* 334 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 336 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 338 */
            0x11, 0x4,  /* FC_RP [alloced_on_stack] */
/* 340 */   NdrFcShort( 0xffffffc0 ),   /* Offset= -64 (276) */
/* 342 */
            0x11, 0x0,  /* FC_RP */
/* 344 */   NdrFcShort( 0x34 ), /* Offset= 52 (396) */
/* 346 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 348 */   NdrFcShort( 0x48 ), /* 72 */
/* 350 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 352 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 354 */   NdrFcShort( 0x28 ), /* 40 */
/* 356 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 358 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 360 */   NdrFcShort( 0x78 ), /* 120 */
/* 362 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 364 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (346) */
/* 366 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 368 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (352) */
/* 370 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 372 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 374 */
            0x1b,       /* FC_CARRAY */
            0x3,        /* 3 */
/* 376 */   NdrFcShort( 0x78 ), /* 120 */
/* 378 */   0x8,        /* 8 */
            0x0,        /*  */
/* 380 */   NdrFcShort( 0xfffffffc ),   /* -4 */
/* 382 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 384 */   NdrFcShort( 0xffffffe6 ),   /* Offset= -26 (358) */
/* 386 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 388 */
            0x17,       /* FC_CSTRUCT */
            0x3,        /* 3 */
/* 390 */   NdrFcShort( 0x4 ),  /* 4 */
/* 392 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (374) */
/* 394 */   0x8,        /* FC_LONG */
            0x5b,       /* FC_END */
/* 396 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 398 */   NdrFcShort( 0x1 ),  /* 1 */
/* 400 */   NdrFcShort( 0x7c ), /* 124 */
/* 402 */   NdrFcShort( 0x0 ),  /* 0 */
/* 404 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (388) */
/* 406 */
            0x11, 0x0,  /* FC_RP */
/* 408 */   NdrFcShort( 0x2 ),  /* Offset= 2 (410) */
/* 410 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 412 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 414 */   NdrFcShort( 0x11 ), /* 17 */
/* 416 */
            0x11, 0x0,  /* FC_RP */
/* 418 */   NdrFcShort( 0x2 ),  /* Offset= 2 (420) */
/* 420 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 422 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 424 */   NdrFcShort( 0x21 ), /* 33 */
/* 426 */
            0x11, 0x0,  /* FC_RP */
/* 428 */   NdrFcShort( 0x2 ),  /* Offset= 2 (430) */
/* 430 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 432 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 434 */   NdrFcShort( 0x11 ), /* 17 */
/* 436 */
            0x11, 0x0,  /* FC_RP */
/* 438 */   NdrFcShort( 0x2 ),  /* Offset= 2 (440) */
/* 440 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 442 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 444 */   NdrFcShort( 0x21 ), /* 33 */
/* 446 */
            0x11, 0x0,  /* FC_RP */
/* 448 */   NdrFcShort( 0x2 ),  /* Offset= 2 (450) */
/* 450 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 452 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 454 */   NdrFcShort( 0x50 ), /* 80 */
/* 456 */
            0x11, 0x0,  /* FC_RP */
/* 458 */   NdrFcShort( 0x2 ),  /* Offset= 2 (460) */
/* 460 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 462 */   NdrFcShort( 0x1 ),  /* 1 */
/* 464 */   NdrFcShort( 0x7c ), /* 124 */
/* 466 */   NdrFcShort( 0x0 ),  /* 0 */
/* 468 */   NdrFcShort( 0xffffffb0 ),   /* Offset= -80 (388) */
/* 470 */
            0x11, 0x0,  /* FC_RP */
/* 472 */   NdrFcShort( 0x2 ),  /* Offset= 2 (474) */
/* 474 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 476 */   NdrFcShort( 0x1 ),  /* 1 */
/* 478 */   NdrFcShort( 0x7c ), /* 124 */
/* 480 */   NdrFcShort( 0x0 ),  /* 0 */
/* 482 */   NdrFcShort( 0xffffffa2 ),   /* Offset= -94 (388) */

            0x0
        }
    };


WDSADM_S.C

/* this ALWAYS GENERATED file contains the RPC server stubs */

/* File created by MIDL compiler version 2.00.0102 */
/* at Wed Sep 27 11:30:53 1995
 */
//@@MIDL_FILE_HEADING(  )

#include <string.h>
#include "wdsadm.h"

extern const MIDL_FORMAT_STRING __MIDLFormatString;

extern const MIDL_FORMAT_STRING __MIDLProcFormatString;

extern RPC_DISPATCH_TABLE WINDSADM_v1_0_DispatchTable;

static const RPC_SERVER_INTERFACE WINDSADM___RpcServerInterface =
    {
    sizeof(RPC_SERVER_INTERFACE),
    {{0x2BC66860,0xE91E,0x11CE,{0xB2,0xF0,0x00,0xAA,0x00,0x51,0x0E,0x3B}},{1,0}},
    {{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
    &WINDSADM_v1_0_DispatchTable,
    0,
    0,
    0,
    0
    };
RPC_IF_HANDLE WINDSADM_v1_0_s_ifspec = (RPC_IF_HANDLE)& WINDSADM___RpcServerInterface;

extern const MIDL_STUB_DESC WINDSADM_StubDesc;

void __RPC_STUB
WINDSADM_RemoteAdmIsServerRunning(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    RpcTryFinally
        {

        _RetVal = RemoteAdmIsServerRunning();

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmDeleteObject(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[2] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteAdmDeleteObject(dwObjID);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetServerMailboxes(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M64;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        pPipeNumber = &_M64;

        _RetVal = RemoteAdmGetServerMailboxes(pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetServerDistLists(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M65;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        pPipeNumber = &_M65;

        _RetVal = RemoteAdmGetServerDistLists(pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmEmptyMailbox(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwMailboxID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[2] );

        dwMailboxID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteAdmEmptyMailbox(dwMailboxID);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetGALDirectory(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M66;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFlags;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[12] );

        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pPipeNumber = &_M66;

        _RetVal = RemoteAdmGetGALDirectory(dwFlags,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmTerminateNotifA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned char __RPC_FAR *szComputerName;
    unsigned long ulConnectionID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szComputerName = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[20] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteAdmTerminateNotifA(szComputerName,ulConnectionID);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmValidateNotifA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M67;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulConnectionID;
    unsigned char __RPC_FAR *szComputerName;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szComputerName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[28] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pulConnectionID = &_M67;

        _RetVal = RemoteAdmValidateNotifA(
                                  szComputerName,
                                  ulNotifMask,
                                  pulConnectionID);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmCreateMailboxA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36],
                                   (unsigned char)0 );

        _RetVal = RemoteAdmCreateMailboxA(pMailboxInfo);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetMailboxPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M68;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_A _pMailboxInfoM;
    unsigned long dwObjID;
    ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo;
    unsigned long __RPC_FAR *pdwObjType;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pdwObjType = 0;
    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[46] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pdwObjType = &_M68;
        pMailboxInfo = &_pMailboxInfoM;

        _RetVal = RemoteAdmGetMailboxPropsA(
                                    dwObjID,
                                    pdwObjType,
                                    pMailboxInfo);

        _StubMsg.BufferLength = 4U + 0U + 11U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwObjType;

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmSetMailboxPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_A __RPC_FAR *pMailboxInfo;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[36],
                                   (unsigned char)0 );

        _RetVal = RemoteAdmSetMailboxPropsA(pMailboxInfo);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmCreateDistListA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFlags;
    unsigned long dwOwnerID;
    DLM_XMIT_LIST_A __RPC_FAR *pMembers;
    unsigned char __RPC_FAR *szDLAlias;
    unsigned char __RPC_FAR *szDLFullName;
    unsigned char __RPC_FAR *szOwnerAlias;
    unsigned char __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[58] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[156],
                                  (unsigned char)0 );

        _RetVal = RemoteAdmCreateDistListA(
                                   szDLAlias,
                                   szDLFullName,
                                   dwFlags,
                                   szOwnerAlias,
                                   szOwnerName,
                                   dwOwnerID,
                                   pMembers);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[102] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetDLPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M69;
    unsigned long _M70;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    DLM_XMIT_LIST_A __RPC_FAR *pMembers;
    unsigned long __RPC_FAR *pdwFlags;
    unsigned long __RPC_FAR *pdwOwnerID;
    unsigned char __RPC_FAR *szComments;
    unsigned char __RPC_FAR *szDLAlias;
    unsigned char __RPC_FAR *szDLFullName;
    unsigned char __RPC_FAR *szOwnerAlias;
    unsigned char __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    pdwFlags = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    pdwOwnerID = 0;
    szComments = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[84] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[220],
                                  (unsigned char)0 );

        szDLAlias = _StubMsg.pfnAllocate((16 + 1) * 1);
        szDLFullName = _StubMsg.pfnAllocate((32 + 1) * 1);
        pdwFlags = &_M69;
        szOwnerAlias = _StubMsg.pfnAllocate((16 + 1) * 1);
        szOwnerName = _StubMsg.pfnAllocate((32 + 1) * 1);
        pdwOwnerID = &_M70;
        szComments = _StubMsg.pfnAllocate((79 + 1) * 1);

        _RetVal = RemoteAdmGetDLPropsA(
                               dwObjID,
                               szDLAlias,
                               szDLFullName,
                               pdwFlags,
                               szOwnerAlias,
                               szOwnerName,
                               pdwOwnerID,
                               szComments,
                               pMembers);

        _StubMsg.BufferLength = 12U + 13U + 11U + 13U + 13U + 11U + 13U + 0U + 11U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[170] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[180] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[190] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[200] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[210] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[220] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[170] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[180] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwFlags;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[190] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[200] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwOwnerID;

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[210] );

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[220] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLAlias,

&__MIDLFormatString.Format[166] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLFullName,
                        &__MIDLFormatString.Format[176] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerAlias,
                        &__MIDLFormatString.Format[186] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerName,
                        &__MIDLFormatString.Format[196] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[206] );

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[216] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmSetDLPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFlags;
    unsigned long dwObjID;
    unsigned long dwOwnerID;
    DLM_XMIT_LIST_A __RPC_FAR *pMembers;
    unsigned char __RPC_FAR *szComments;
    unsigned char __RPC_FAR *szDLAlias;
    unsigned char __RPC_FAR *szDLFullName;
    unsigned char __RPC_FAR *szOwnerAlias;
    unsigned char __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    szComments = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[120] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[234],
                                  (unsigned char)0 );

        _RetVal = RemoteAdmSetDLPropsA(
                               dwObjID,
                               szDLAlias,
                               szDLFullName,
                               dwFlags,
                               szOwnerAlias,
                               szOwnerName,
                               dwOwnerID,
                               szComments,
                               pMembers);

        _StubMsg.BufferLength = 0U + 11U;
        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[230] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmTerminateNotifW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    wchar_t __RPC_FAR *szComputerName;
    unsigned long ulConnectionID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szComputerName = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[152] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteAdmTerminateNotifW(szComputerName,ulConnectionID);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmValidateNotifW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M71;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulConnectionID;
    wchar_t __RPC_FAR *szComputerName;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szComputerName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[160] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pulConnectionID = &_M71;

        _RetVal = RemoteAdmValidateNotifW(
                                  szComputerName,
                                  ulNotifMask,
                                  pulConnectionID);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmCreateMailboxW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276],
                                   (unsigned char)0 );

        _RetVal = RemoteAdmCreateMailboxW(pMailboxInfo);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetMailboxPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M72;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_W _pMailboxInfoM;
    unsigned long dwObjID;
    ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo;
    unsigned long __RPC_FAR *pdwObjType;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pdwObjType = 0;
    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[178] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pdwObjType = &_M72;
        pMailboxInfo = &_pMailboxInfoM;

        _RetVal = RemoteAdmGetMailboxPropsW(
                                    dwObjID,
                                    pdwObjType,
                                    pMailboxInfo);

        _StubMsg.BufferLength = 4U + 0U + 11U;
        NdrSimpleStructBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR *)pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwObjType;

        NdrSimpleStructMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                 (unsigned char __RPC_FAR *)pMailboxInfo,
                                 (PFORMAT_STRING) &__MIDLFormatString.Format[276] );

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmSetMailboxPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    ADM_MAILBOX_INFO_W __RPC_FAR *pMailboxInfo;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    pMailboxInfo = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        NdrSimpleStructUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                   (unsigned char __RPC_FAR * __RPC_FAR *)&pMailboxInfo,
                                   (PFORMAT_STRING) &__MIDLFormatString.Format[276],
                                   (unsigned char)0 );

        _RetVal = RemoteAdmSetMailboxPropsW(pMailboxInfo);

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmCreateDistListW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFlags;
    unsigned long dwOwnerID;
    DLM_XMIT_LIST_W __RPC_FAR *pMembers;
    wchar_t __RPC_FAR *szDLAlias;
    wchar_t __RPC_FAR *szDLFullName;
    wchar_t __RPC_FAR *szOwnerAlias;
    wchar_t __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[190] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[396],
                                  (unsigned char)0 );

        _RetVal = RemoteAdmCreateDistListW(
                                   szDLAlias,
                                   szDLFullName,
                                   dwFlags,
                                   szOwnerAlias,
                                   szOwnerName,
                                   dwOwnerID,
                                   pMembers);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[342] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmGetDLPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M73;
    unsigned long _M74;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    DLM_XMIT_LIST_W __RPC_FAR *pMembers;
    unsigned long __RPC_FAR *pdwFlags;
    unsigned long __RPC_FAR *pdwOwnerID;
    wchar_t __RPC_FAR *szComments;
    wchar_t __RPC_FAR *szDLAlias;
    wchar_t __RPC_FAR *szDLFullName;
    wchar_t __RPC_FAR *szOwnerAlias;
    wchar_t __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    pdwFlags = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    pdwOwnerID = 0;
    szComments = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[216] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[460],
                                  (unsigned char)0 );

        szDLAlias = _StubMsg.pfnAllocate((16 + 1) * 2);
        szDLFullName = _StubMsg.pfnAllocate((32 + 1) * 2);
        pdwFlags = &_M73;
        szOwnerAlias = _StubMsg.pfnAllocate((16 + 1) * 2);
        szOwnerName = _StubMsg.pfnAllocate((32 + 1) * 2);
        pdwOwnerID = &_M74;
        szComments = _StubMsg.pfnAllocate((79 + 1) * 2);

        _RetVal = RemoteAdmGetDLPropsW(
                               dwObjID,
                               szDLAlias,
                               szDLFullName,
                               pdwFlags,
                               szOwnerAlias,
                               szOwnerName,
                               pdwOwnerID,
                               szComments,
                               pMembers);

        _StubMsg.BufferLength = 12U + 14U + 10U + 14U + 14U + 10U + 14U + 0U + 11U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[410] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[420] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[430] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[440] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[450] );

        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[460] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[410] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[420] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwFlags;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[430] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[440] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwOwnerID;

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[450] );

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[460] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLAlias,
                        &__MIDLFormatString.Format[406] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLFullName,
                        &__MIDLFormatString.Format[416] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerAlias,
                        &__MIDLFormatString.Format[426] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerName,
                        &__MIDLFormatString.Format[436] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[446] );

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[456] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSADM_RemoteAdmSetDLPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFlags;
    unsigned long dwObjID;
    unsigned long dwOwnerID;
    DLM_XMIT_LIST_W __RPC_FAR *pMembers;
    wchar_t __RPC_FAR *szComments;
    wchar_t __RPC_FAR *szDLAlias;
    wchar_t __RPC_FAR *szDLFullName;
    wchar_t __RPC_FAR *szOwnerAlias;
    wchar_t __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSADM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    szComments = 0;
    pMembers = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[252] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,

(unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        dwOwnerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[246],
                                       (unsigned char)0 );

        NdrXmitOrRepAsUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR * __RPC_FAR *)&pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[474],
                                  (unsigned char)0 );

        _RetVal = RemoteAdmSetDLPropsW(
                               dwObjID,
                               szDLAlias,
                               szDLFullName,
                               dwFlags,
                               szOwnerAlias,
                               szOwnerName,
                               dwOwnerID,
                               szComments,
                               pMembers);

        _StubMsg.BufferLength = 0U + 11U;
        NdrXmitOrRepAsBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                  (unsigned char __RPC_FAR *)pMembers,
                                  (PFORMAT_STRING) &__MIDLFormatString.Format[474] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        NdrXmitOrRepAsMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                (unsigned char __RPC_FAR *)pMembers,
                                (PFORMAT_STRING) &__MIDLFormatString.Format[474] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)pMembers,
                        &__MIDLFormatString.Format[470] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

extern const XMIT_ROUTINE_QUINTUPLE TransmitAsRoutines[];

static const MIDL_STUB_DESC WINDSADM_StubDesc =
    {
    (void __RPC_FAR *)& WINDSADM___RpcServerInterface,
    MIDL_user_allocate,
    MIDL_user_free,
    0,
    0,
    0,
    0,
    TransmitAsRoutines,
    __MIDLFormatString.Format,
    0, /* -error bounds_check flag */
    0x10001, /* Ndr library version */
    0,
    0x2000066, /* MIDL Version 2.0.102 */
    0
    };

static RPC_DISPATCH_FUNCTION WINDSADM_table[] =
    {
    WINDSADM_RemoteAdmIsServerRunning,
    WINDSADM_RemoteAdmDeleteObject,
    WINDSADM_RemoteAdmGetServerMailboxes,
    WINDSADM_RemoteAdmGetServerDistLists,
    WINDSADM_RemoteAdmEmptyMailbox,
    WINDSADM_RemoteAdmGetGALDirectory,
    WINDSADM_RemoteAdmTerminateNotifA,
    WINDSADM_RemoteAdmValidateNotifA,
    WINDSADM_RemoteAdmCreateMailboxA,
    WINDSADM_RemoteAdmGetMailboxPropsA,
    WINDSADM_RemoteAdmSetMailboxPropsA,
    WINDSADM_RemoteAdmCreateDistListA,
    WINDSADM_RemoteAdmGetDLPropsA,
    WINDSADM_RemoteAdmSetDLPropsA,
    WINDSADM_RemoteAdmTerminateNotifW,
    WINDSADM_RemoteAdmValidateNotifW,
    WINDSADM_RemoteAdmCreateMailboxW,
    WINDSADM_RemoteAdmGetMailboxPropsW,
    WINDSADM_RemoteAdmSetMailboxPropsW,
    WINDSADM_RemoteAdmCreateDistListW,
    WINDSADM_RemoteAdmGetDLPropsW,
    WINDSADM_RemoteAdmSetDLPropsW,
    0
    };
RPC_DISPATCH_TABLE WINDSADM_v1_0_DispatchTable =
    {
    22,
    WINDSADM_table
    };
void __RPC_USER
DLM_XMIT_LIST_A_XmitTranslateToXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_to_xmit( (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType,
        (DLM_ARRAY_A __RPC_FAR * __RPC_FAR *) &pStubMsg->pTransmitType );

}
void __RPC_USER
DLM_XMIT_LIST_A_XmitTranslateFromXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_from_xmit( (DLM_ARRAY_A __RPC_FAR *) pStubMsg->pTransmitType,
        (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_A_XmitFreeXmit_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_free_xmit( (DLM_ARRAY_A __RPC_FAR *) pStubMsg->pTransmitType );
}
void __RPC_USER
DLM_XMIT_LIST_A_XmitFreeInst_0000( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_A_free_inst( (DLM_XMIT_LIST_A __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitTranslateToXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_to_xmit( (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType,
        (DLM_ARRAY_W __RPC_FAR * __RPC_FAR *) &pStubMsg->pTransmitType );

}
void __RPC_USER
DLM_XMIT_LIST_W_XmitTranslateFromXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_from_xmit( (DLM_ARRAY_W __RPC_FAR *) pStubMsg->pTransmitType,
        (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitFreeXmit_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_free_xmit( (DLM_ARRAY_W __RPC_FAR *) pStubMsg->pTransmitType );
}
void __RPC_USER
DLM_XMIT_LIST_W_XmitFreeInst_0001( PMIDL_STUB_MESSAGE pStubMsg )
{
    DLM_XMIT_LIST_W_free_inst( (DLM_XMIT_LIST_W __RPC_FAR *) pStubMsg->pPresentedType );
}

static const XMIT_ROUTINE_QUINTUPLE TransmitAsRoutines[] =
        {

            {
            DLM_XMIT_LIST_A_XmitTranslateToXmit_0000
            ,DLM_XMIT_LIST_A_XmitTranslateFromXmit_0000
            ,DLM_XMIT_LIST_A_XmitFreeXmit_0000
            ,DLM_XMIT_LIST_A_XmitFreeInst_0000
            },
            {
            DLM_XMIT_LIST_W_XmitTranslateToXmit_0001
            ,DLM_XMIT_LIST_W_XmitTranslateFromXmit_0001
            ,DLM_XMIT_LIST_W_XmitFreeXmit_0001
            ,DLM_XMIT_LIST_W_XmitFreeInst_0001
            }

        };

#if !defined(__RPC_WIN32__)
#error  Invalid build platform for this stub.
#endif

static const MIDL_FORMAT_STRING __MIDLProcFormatString =
    {
        0,
        {
            0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  2 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  4 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  6 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/*  8 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 10 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 12 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 14 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 16 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 18 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 20 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 22 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 24 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 26 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 28 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 30 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 32 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 34 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 36 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 38 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 40 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 42 */    NdrFcShort( 0x8 ),  /* Type Offset=8 */
/* 44 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 46 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 48 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 50 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 52 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 54 */    NdrFcShort( 0x62 ), /* Type Offset=98 */
/* 56 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 58 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 60 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 62 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 64 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 66 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 68 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 70 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 72 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 74 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 76 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 78 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 80 */    NdrFcShort( 0x66 ), /* Type Offset=102 */
/* 82 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 84 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 86 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 88 */    NdrFcShort( 0xa6 ), /* Type Offset=166 */
/* 90 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 92 */    NdrFcShort( 0xb0 ), /* Type Offset=176 */
/* 94 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 96 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 98 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 100 */   NdrFcShort( 0xba ), /* Type Offset=186 */
/* 102 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 104 */   NdrFcShort( 0xc4 ), /* Type Offset=196 */
/* 106 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 108 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 110 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 112 */   NdrFcShort( 0xce ), /* Type Offset=206 */
/* 114 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 116 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 118 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 120 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 122 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 124 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 126 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 128 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 130 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 132 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 134 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 136 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 138 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 140 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 142 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 144 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 146 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 148 */   NdrFcShort( 0xe6 ), /* Type Offset=230 */
/* 150 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 152 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 154 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 156 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 158 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 160 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 162 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 164 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 166 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 168 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 170 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 172 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 174 */   NdrFcShort( 0xf8 ), /* Type Offset=248 */
/* 176 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 178 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 180 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 182 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 184 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 186 */   NdrFcShort( 0x152 ),    /* Type Offset=338 */
/* 188 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 190 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 192 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 194 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 196 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 198 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 200 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 202 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 204 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 206 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 208 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 210 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 212 */   NdrFcShort( 0x156 ),    /* Type Offset=342 */
/* 214 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 216 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 218 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 220 */   NdrFcShort( 0x196 ),    /* Type Offset=406 */
/* 222 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 224 */   NdrFcShort( 0x1a0 ),    /* Type Offset=416 */
/* 226 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 228 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 230 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 232 */   NdrFcShort( 0x1aa ),    /* Type Offset=426 */
/* 234 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 236 */   NdrFcShort( 0x1b4 ),    /* Type Offset=436 */
/* 238 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 240 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 242 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 244 */   NdrFcShort( 0x1be ),    /* Type Offset=446 */
/* 246 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 248 */   NdrFcShort( 0x1c8 ),    /* Type Offset=456 */
/* 250 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 252 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 254 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 256 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 258 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 260 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 262 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 264 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 266 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 268 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 270 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 272 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 274 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 276 */   NdrFcShort( 0xf4 ), /* Type Offset=244 */
/* 278 */
            0x50,       /* FC_IN_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 280 */   NdrFcShort( 0x1d6 ),    /* Type Offset=470 */
/* 282 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */

            0x0
        }
    };

static const MIDL_FORMAT_STRING __MIDLFormatString =
    {
        0,
        {
            0x11, 0xc,  /* FC_RP [alloced_on_stack] [simple_pointer] */
/*  2 */    0x8,        /* FC_LONG */
            0x5c,       /* FC_PAD */
/*  4 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/*  6 */
            0x22,       /* FC_C_CSTRING */
            0x5c,       /* FC_PAD */
/*  8 */
            0x11, 0x0,  /* FC_RP */
/* 10 */    NdrFcShort( 0x1a ), /* Offset= 26 (36) */
/* 12 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 14 */    NdrFcShort( 0x11 ), /* 17 */
/* 16 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 18 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 20 */    NdrFcShort( 0x21 ), /* 33 */
/* 22 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 24 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 26 */    NdrFcShort( 0xd ),  /* 13 */
/* 28 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 30 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 32 */    NdrFcShort( 0x50 ), /* 80 */
/* 34 */    0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 36 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 38 */    NdrFcShort( 0x18c ),    /* 396 */
/* 40 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 42 */    NdrFcShort( 0xffffffe2 ),   /* Offset= -30 (12) */
/* 44 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 46 */    NdrFcShort( 0xffffffe4 ),   /* Offset= -28 (18) */
/* 48 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 50 */    NdrFcShort( 0xffffffe0 ),   /* Offset= -32 (18) */
/* 52 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 54 */    NdrFcShort( 0xffffffdc ),   /* Offset= -36 (18) */
/* 56 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 58 */    NdrFcShort( 0xffffffde ),   /* Offset= -34 (24) */
/* 60 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 62 */    NdrFcShort( 0xffffffda ),   /* Offset= -38 (24) */
/* 64 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 66 */    NdrFcShort( 0xffffffd6 ),   /* Offset= -42 (24) */
/* 68 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 70 */    NdrFcShort( 0xffffffc6 ),   /* Offset= -58 (12) */
/* 72 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 74 */    NdrFcShort( 0xffffffc8 ),   /* Offset= -56 (18) */
/* 76 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 78 */    NdrFcShort( 0xffffffc4 ),   /* Offset= -60 (18) */
/* 80 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 82 */    NdrFcShort( 0xffffffcc ),   /* Offset= -52 (30) */
/* 84 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 86 */    NdrFcShort( 0xffffffbc ),   /* Offset= -68 (18) */
/* 88 */    0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 90 */    NdrFcShort( 0xffffffb8 ),   /* Offset= -72 (18) */
/* 92 */    0x38,       /* FC_ALIGNM4 */
            0x8,        /* FC_LONG */
/* 94 */    0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 96 */    0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 98 */
            0x11, 0x4,  /* FC_RP [alloced_on_stack] */
/* 100 */   NdrFcShort( 0xffffffc0 ),   /* Offset= -64 (36) */
/* 102 */
            0x11, 0x0,  /* FC_RP */
/* 104 */   NdrFcShort( 0x34 ), /* Offset= 52 (156) */
/* 106 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 108 */   NdrFcShort( 0x24 ), /* 36 */
/* 110 */   0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 112 */
            0x1d,       /* FC_SMFARRAY */
            0x0,        /* 0 */
/* 114 */   NdrFcShort( 0x14 ), /* 20 */
/* 116 */   0x2,        /* FC_CHAR */
            0x5b,       /* FC_END */
/* 118 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 120 */   NdrFcShort( 0x40 ), /* 64 */
/* 122 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */

/* 124 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (106) */
/* 126 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 128 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (112) */
/* 130 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 132 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 134 */
            0x1b,       /* FC_CARRAY */
            0x3,        /* 3 */
/* 136 */   NdrFcShort( 0x40 ), /* 64 */
/* 138 */   0x8,        /* 8 */
            0x0,        /*  */
/* 140 */   NdrFcShort( 0xfffffffc ),   /* -4 */
/* 142 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 144 */   NdrFcShort( 0xffffffe6 ),   /* Offset= -26 (118) */
/* 146 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 148 */
            0x17,       /* FC_CSTRUCT */
            0x3,        /* 3 */
/* 150 */   NdrFcShort( 0x4 ),  /* 4 */
/* 152 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (134) */
/* 154 */   0x8,        /* FC_LONG */
            0x5b,       /* FC_END */
/* 156 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 158 */   NdrFcShort( 0x0 ),  /* 0 */
/* 160 */   NdrFcShort( 0x44 ), /* 68 */
/* 162 */   NdrFcShort( 0x0 ),  /* 0 */
/* 164 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (148) */
/* 166 */
            0x11, 0x0,  /* FC_RP */
/* 168 */   NdrFcShort( 0x2 ),  /* Offset= 2 (170) */
/* 170 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 172 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 174 */   NdrFcShort( 0x11 ), /* 17 */
/* 176 */
            0x11, 0x0,  /* FC_RP */
/* 178 */   NdrFcShort( 0x2 ),  /* Offset= 2 (180) */
/* 180 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 182 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 184 */   NdrFcShort( 0x21 ), /* 33 */
/* 186 */
            0x11, 0x0,  /* FC_RP */
/* 188 */   NdrFcShort( 0x2 ),  /* Offset= 2 (190) */
/* 190 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 192 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 194 */   NdrFcShort( 0x11 ), /* 17 */
/* 196 */
            0x11, 0x0,  /* FC_RP */
/* 198 */   NdrFcShort( 0x2 ),  /* Offset= 2 (200) */
/* 200 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 202 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 204 */   NdrFcShort( 0x21 ), /* 33 */
/* 206 */
            0x11, 0x0,  /* FC_RP */
/* 208 */   NdrFcShort( 0x2 ),  /* Offset= 2 (210) */
/* 210 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 212 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 214 */   NdrFcShort( 0x50 ), /* 80 */
/* 216 */
            0x11, 0x0,  /* FC_RP */
/* 218 */   NdrFcShort( 0x2 ),  /* Offset= 2 (220) */
/* 220 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 222 */   NdrFcShort( 0x0 ),  /* 0 */
/* 224 */   NdrFcShort( 0x44 ), /* 68 */
/* 226 */   NdrFcShort( 0x0 ),  /* 0 */
/* 228 */   NdrFcShort( 0xffffffb0 ),   /* Offset= -80 (148) */
/* 230 */
            0x11, 0x0,  /* FC_RP */
/* 232 */   NdrFcShort( 0x2 ),  /* Offset= 2 (234) */
/* 234 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 236 */   NdrFcShort( 0x0 ),  /* 0 */
/* 238 */   NdrFcShort( 0x44 ), /* 68 */
/* 240 */   NdrFcShort( 0x0 ),  /* 0 */
/* 242 */   NdrFcShort( 0xffffffa2 ),   /* Offset= -94 (148) */
/* 244 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 246 */
            0x25,       /* FC_C_WSTRING */
            0x5c,       /* FC_PAD */
/* 248 */
            0x11, 0x0,  /* FC_RP */
/* 250 */   NdrFcShort( 0x1a ), /* Offset= 26 (276) */
/* 252 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 254 */   NdrFcShort( 0x22 ), /* 34 */
/* 256 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 258 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 260 */   NdrFcShort( 0x42 ), /* 66 */
/* 262 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 264 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 266 */   NdrFcShort( 0x1a ), /* 26 */
/* 268 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 270 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 272 */   NdrFcShort( 0xa0 ), /* 160 */
/* 274 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 276 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 278 */   NdrFcShort( 0x30c ),    /* 780 */
/* 280 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 282 */   NdrFcShort( 0xffffffe2 ),   /* Offset= -30 (252) */
/* 284 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 286 */   NdrFcShort( 0xffffffe4 ),   /* Offset= -28 (258) */
/* 288 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 290 */   NdrFcShort( 0xffffffe0 ),   /* Offset= -32 (258) */
/* 292 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 294 */   NdrFcShort( 0xffffffdc ),   /* Offset= -36 (258) */
/* 296 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 298 */   NdrFcShort( 0xffffffde ),   /* Offset= -34 (264) */
/* 300 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 302 */   NdrFcShort( 0xffffffda ),   /* Offset= -38 (264) */
/* 304 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 306 */   NdrFcShort( 0xffffffd6 ),   /* Offset= -42 (264) */
/* 308 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 310 */   NdrFcShort( 0xffffffc6 ),   /* Offset= -58 (252) */
/* 312 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 314 */   NdrFcShort( 0xffffffc8 ),   /* Offset= -56 (258) */
/* 316 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 318 */   NdrFcShort( 0xffffffc4 ),   /* Offset= -60 (258) */
/* 320 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 322 */   NdrFcShort( 0xffffffcc ),   /* Offset= -52 (270) */
/* 324 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 326 */   NdrFcShort( 0xffffffbc ),   /* Offset= -68 (258) */
/* 328 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 330 */   NdrFcShort( 0xffffffb8 ),   /* Offset= -72 (258) */
/* 332 */   0x38,       /* FC_ALIGNM4 */
            0x8,        /* FC_LONG */
/* 334 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 336 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 338 */
            0x11, 0x4,  /* FC_RP [alloced_on_stack] */
/* 340 */   NdrFcShort( 0xffffffc0 ),   /* Offset= -64 (276) */
/* 342 */
            0x11, 0x0,  /* FC_RP */
/* 344 */   NdrFcShort( 0x34 ), /* Offset= 52 (396) */
/* 346 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 348 */   NdrFcShort( 0x48 ), /* 72 */
/* 350 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 352 */
            0x1d,       /* FC_SMFARRAY */
            0x1,        /* 1 */
/* 354 */   NdrFcShort( 0x28 ), /* 40 */
/* 356 */   0x5,        /* FC_WCHAR */
            0x5b,       /* FC_END */
/* 358 */
            0x15,       /* FC_STRUCT */
            0x3,        /* 3 */
/* 360 */   NdrFcShort( 0x78 ), /* 120 */
/* 362 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 364 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (346) */
/* 366 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 368 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (352) */
/* 370 */   0x8,        /* FC_LONG */
            0x8,        /* FC_LONG */
/* 372 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 374 */
            0x1b,       /* FC_CARRAY */
            0x3,        /* 3 */
/* 376 */   NdrFcShort( 0x78 ), /* 120 */
/* 378 */   0x8,        /* 8 */
            0x0,        /*  */
/* 380 */   NdrFcShort( 0xfffffffc ),   /* -4 */
/* 382 */   0x4c,       /* FC_EMBEDDED_COMPLEX */
            0x0,        /* 0 */
/* 384 */   NdrFcShort( 0xffffffe6 ),   /* Offset= -26 (358) */
/* 386 */   0x5c,       /* FC_PAD */
            0x5b,       /* FC_END */
/* 388 */
            0x17,       /* FC_CSTRUCT */
            0x3,        /* 3 */
/* 390 */   NdrFcShort( 0x4 ),  /* 4 */
/* 392 */   NdrFcShort( 0xffffffee ),   /* Offset= -18 (374) */
/* 394 */   0x8,        /* FC_LONG */
            0x5b,       /* FC_END */
/* 396 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 398 */   NdrFcShort( 0x1 ),  /* 1 */
/* 400 */   NdrFcShort( 0x7c ), /* 124 */
/* 402 */   NdrFcShort( 0x0 ),  /* 0 */
/* 404 */   NdrFcShort( 0xfffffff0 ),   /* Offset= -16 (388) */
/* 406 */
            0x11, 0x0,  /* FC_RP */
/* 408 */   NdrFcShort( 0x2 ),  /* Offset= 2 (410) */
/* 410 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 412 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 414 */   NdrFcShort( 0x11 ), /* 17 */
/* 416 */
            0x11, 0x0,  /* FC_RP */
/* 418 */   NdrFcShort( 0x2 ),  /* Offset= 2 (420) */
/* 420 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 422 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 424 */   NdrFcShort( 0x21 ), /* 33 */
/* 426 */
            0x11, 0x0,  /* FC_RP */
/* 428 */   NdrFcShort( 0x2 ),  /* Offset= 2 (430) */
/* 430 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 432 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 434 */   NdrFcShort( 0x11 ), /* 17 */
/* 436 */
            0x11, 0x0,  /* FC_RP */
/* 438 */   NdrFcShort( 0x2 ),  /* Offset= 2 (440) */
/* 440 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 442 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 444 */   NdrFcShort( 0x21 ), /* 33 */
/* 446 */
            0x11, 0x0,  /* FC_RP */
/* 448 */   NdrFcShort( 0x2 ),  /* Offset= 2 (450) */
/* 450 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 452 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 454 */   NdrFcShort( 0x50 ), /* 80 */
/* 456 */
            0x11, 0x0,  /* FC_RP */
/* 458 */   NdrFcShort( 0x2 ),  /* Offset= 2 (460) */
/* 460 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 462 */   NdrFcShort( 0x1 ),  /* 1 */
/* 464 */   NdrFcShort( 0x7c ), /* 124 */
/* 466 */   NdrFcShort( 0x0 ),  /* 0 */
/* 468 */   NdrFcShort( 0xffffffb0 ),   /* Offset= -80 (388) */
/* 470 */
            0x11, 0x0,  /* FC_RP */
/* 472 */   NdrFcShort( 0x2 ),  /* Offset= 2 (474) */
/* 474 */   0x2d,       /* FC_TRANSMIT_AS */
            0x3,        /* 3 */
/* 476 */   NdrFcShort( 0x1 ),  /* 1 */
/* 478 */   NdrFcShort( 0x7c ), /* 124 */
/* 480 */   NdrFcShort( 0x0 ),  /* 0 */
/* 482 */   NdrFcShort( 0xffffffa2 ),   /* Offset= -94 (388) */

            0x0
        }
    };


WINDS.ACF

[implicit_handle(handle_t hWINDSREM)]
interface WINDSREM
{

}


WINDS.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 2.00.0102 */
/* at Mon Sep 25 11:13:21 1995
 */
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"

#ifndef __winds_h__
#define __winds_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __WINDSREM_INTERFACE_DEFINED__
#define __WINDSREM_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: WINDSREM
 * at Mon Sep 25 11:13:21 1995
 * using MIDL 2.00.0102
 ****************************************/
/* [implicit_handle][unique][version][uuid] */

            /* size is 4 */
long RemoteIsServerRunning( void);

            /* size is 4 */
long RemoteGetAllAccounts(
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteFinishUpload(
    /* [string][in] */ unsigned char __RPC_FAR *szCookie);

            /* size is 4 */
long RemoteGetContainerProps(
    /* [in] */ unsigned long dwContainerID,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount);

            /* size is 4 */
long RemoteGetContainerRecipients(
    /* [in] */ unsigned long dwFlags,
    /* [in] */ unsigned long dwContainerID,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteGetPublicFoldersList(
    /* [in] */ short nFlags,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteGetPublicFolderMessages(
    /* [in] */ short nFlags,
    /* [in] */ unsigned long dwFolderID,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteChangePasswordA(
    /* [in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szOldPassword,
    /* [string][in] */ unsigned char __RPC_FAR *szNewPassword);

            /* size is 4 */
long RemoteLogonMailBoxA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szPassword,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID);

            /* size is 4 */
long RemoteLogonMailBoxAndSetNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szPassword,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID,
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteTerminateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID);

            /* size is 4 */
long RemoteValidateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [string][in] */ unsigned char __RPC_FAR *szMailboxName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteOpenMsgUploadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szSenderMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCookie);

            /* size is 4 */
long RemoteSendMsgToAccountA(
    /* [string][in] */ unsigned char __RPC_FAR *szRecipAccount,
    /* [string][in] */ unsigned char __RPC_FAR *szHeaderInfo,
    /* [string][in] */ unsigned char __RPC_FAR *szCookie);

            /* size is 4 */
long RemoteOpenMsgDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteOpenHeaderDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteCheckNewMailA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pulPending);

            /* size is 4 */
long RemoteOpenOneMsgDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteGetRecipientPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwUserType,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szMailboxName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szJobTitle,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOffice,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szPhone,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szAltPhone,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFax,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCompany,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDepartment,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szManagerName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szManagerAlias,
    /* [out] */ unsigned long __RPC_FAR *pdwManagerID);

            /* size is 4 */
long RemoteGetDistListPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments);

            /* size is 4 */
long RemoteChangePasswordW(
    /* [in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szOldPassword,
    /* [string][in] */ wchar_t __RPC_FAR *szNewPassword);

            /* size is 4 */
long RemoteLogonMailBoxW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szPassword,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID);

            /* size is 4 */
long RemoteLogonMailBoxAndSetNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szPassword,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID,
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteTerminateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID);

            /* size is 4 */
long RemoteValidateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [string][in] */ wchar_t __RPC_FAR *szMailboxName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID);

            /* size is 4 */
long RemoteOpenMsgUploadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szSenderMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCookie);

            /* size is 4 */
long RemoteSendMsgToAccountW(
    /* [string][in] */ wchar_t __RPC_FAR *szRecipAccount,
    /* [string][in] */ wchar_t __RPC_FAR *szHeaderInfo,
    /* [string][in] */ wchar_t __RPC_FAR *szCookie);

            /* size is 4 */
long RemoteOpenMsgDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteOpenHeaderDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteCheckNewMailW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pulPending);

            /* size is 4 */
long RemoteOpenOneMsgDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber);

            /* size is 4 */
long RemoteGetRecipientPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwUserType,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szMailboxName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szJobTitle,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOffice,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szPhone,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szAltPhone,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFax,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szCompany,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDepartment,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szManagerName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szManagerAlias,
    /* [out] */ unsigned long __RPC_FAR *pdwManagerID);

            /* size is 4 */
long RemoteGetDistListPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments);

extern handle_t hWINDSREM;

extern RPC_IF_HANDLE WINDSREM_v1_6_c_ifspec;
extern RPC_IF_HANDLE WINDSREM_v1_6_s_ifspec;
#endif /* __WINDSREM_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


WINDS.IDL

[
    uuid(DDFFFE60-36D0-11CE-9F2D-00AA00510E3B),
    version(1.6),
    pointer_default(unique)
]

#define MAX_ALIAS_SIZE      16+1
#define MAX_STRING_SIZE     32+1
#define MAX_PHONE_SIZE      12+1
#define MAX_COMMENT_SIZE    79+1
#define MAX_PASSWORD_SIZE   MAX_ALIAS_SIZE

interface WINDSREM
{
    long RemoteIsServerRunning
            ();
    long RemoteGetAllAccounts
            ([out] long *                                               pPipeNumber);
    long RemoteFinishUpload
            ([in, string] unsigned char *                               szCookie);
    long RemoteGetContainerProps
            ([in] unsigned long                                         dwContainerID,
             [out] unsigned long *                                      pdwContentCount);
    long RemoteGetContainerRecipients
            ([in] unsigned long                                         dwFlags,
             [in] unsigned long                                         dwContainerID,
             [out] long *                                               pPipeNumber);
    long RemoteGetPublicFoldersList
            ([in] short                                                 nFlags,
             [out] long *                                               pPipeNumber);
    long RemoteGetPublicFolderMessages
            ([in] short                                                 nFlags,
             [in] unsigned long                                         dwFolderID,
             [out] long *                                               pPipeNumber);

    // ANSI Remote server calls
    long RemoteChangePasswordA
            ([in] unsigned char *                                       szMailbox,
             [in, string] unsigned char *                               szOldPassword,
             [in, string] unsigned char *                               szNewPassword);
    long RemoteLogonMailBoxA
            ([in, string] unsigned char *                               szMailbox,
             [in, string] unsigned char *                               szPassword,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szFullName,
             [out] unsigned long *                                      pdwMailboxID);
    long RemoteLogonMailBoxAndSetNotifA
            ([in, string] unsigned char *                               szMailbox,
             [in, string] unsigned char *                               szPassword,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szFullName,
             [out] unsigned long *                                      pdwMailboxID,
             [in, string] unsigned char *                               szComputerName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);
    long RemoteTerminateNotifA
            ([in, string] unsigned char *                               szComputerName,
             [in] unsigned long                                         ulConnectionID);
    long RemoteValidateNotifA
            ([in, string] unsigned char *                               szComputerName,
             [in, string] unsigned char *                               szMailboxName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);
    long RemoteOpenMsgUploadPipeA
            ([in, string] unsigned char *                               szSenderMailbox,
             [out] long *                                               pPipeNumber,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szCookie);
    long RemoteSendMsgToAccountA
            ([in, string] unsigned char *                               szRecipAccount,
             [in, string] unsigned char *                               szHeaderInfo,
             [in, string] unsigned char *                               szCookie);
    long RemoteOpenMsgDownloadPipeA
            ([in, string] unsigned char *                               szMailbox,
             [out] unsigned long *                                      pPipeNumber);
    long RemoteOpenHeaderDownloadPipeA
            ([in, string] unsigned char *                               szMailbox,
             [out] long *                                               pPipeNumber);
    long RemoteCheckNewMailA
            ([in, string] unsigned char *                               szMailbox,
             [out] unsigned long *                                      pulPending);
    long RemoteOpenOneMsgDownloadPipeA
            ([in, string] unsigned char *                               szMailbox,
             [out] long *                                               pPipeNumber);
    long RemoteGetRecipientPropsA
            ([in] unsigned long                                         dwObjID,
             [out] unsigned long *                                      pdwUserType,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szMailboxName,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szFullName,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szJobTitle,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szOffice,
             [out, string, size_is(MAX_PHONE_SIZE)] unsigned char *     szPhone,
             [out, string, size_is(MAX_PHONE_SIZE)] unsigned char *     szAltPhone,
             [out, string, size_is(MAX_PHONE_SIZE)] unsigned char *     szFax,
             [out, string, size_is(MAX_COMMENT_SIZE)] unsigned char *   szComments,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szCompany,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szDepartment,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szManagerName,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szManagerAlias,
             [out] unsigned long *                                      pdwManagerID);
    long RemoteGetDistListPropsA
            ([in] unsigned long                                         dwObjID,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szDLAlias,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szDLFullName,
             [out] unsigned long *                                      pdwContentCount,
             [out, string, size_is(MAX_ALIAS_SIZE)] unsigned char *     szOwnerAlias,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szOwnerName,
             [out, string, size_is(MAX_COMMENT_SIZE)] unsigned char *   szComments);

    // UNICODE Remote server calls
    long RemoteChangePasswordW
            ([in] wchar_t *                                             szMailbox,
             [in, string] wchar_t *                                     szOldPassword,
             [in, string] wchar_t *                                     szNewPassword);
    long RemoteLogonMailBoxW
            ([in, string] wchar_t *                                     szMailbox,
             [in, string] wchar_t *                                     szPassword,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szFullName,
             [out] unsigned long *                                      pdwMailboxID);
    long RemoteLogonMailBoxAndSetNotifW
            ([in, string] wchar_t *                                     szMailbox,
             [in, string] wchar_t *                                     szPassword,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szFullName,
             [out] unsigned long *                                      pdwMailboxID,
             [in, string] wchar_t *                                     szComputerName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);
    long RemoteTerminateNotifW
            ([in, string] wchar_t *                                     szComputerName,
             [in] unsigned long                                         ulConnectionID);
    long RemoteValidateNotifW
            ([in, string] wchar_t *                                     szComputerName,
             [in, string] wchar_t *                                     szMailboxName,
             [in] unsigned long                                         ulNotifMask,
             [out] unsigned long *                                      pulConnectionID);
    long RemoteOpenMsgUploadPipeW
            ([in, string] wchar_t *                                     szSenderMailbox,
             [out] long *                                               pPipeNumber,
             [out, string, size_is(MAX_STRING_SIZE)] unsigned char *    szCookie);
    long RemoteSendMsgToAccountW
            ([in, string] wchar_t *                                     szRecipAccount,
             [in, string] wchar_t *                                     szHeaderInfo,
             [in, string] wchar_t *                                     szCookie);
    long RemoteOpenMsgDownloadPipeW
            ([in, string] wchar_t *                                     szMailbox,
             [out] unsigned long *                                      pPipeNumber);
    long RemoteOpenHeaderDownloadPipeW
            ([in, string] wchar_t *                                     szMailbox,
             [out] long *                                               pPipeNumber);
    long RemoteCheckNewMailW
            ([in, string] wchar_t *                                     szMailbox,
             [out] unsigned long *                                      pulPending);
    long RemoteOpenOneMsgDownloadPipeW
            ([in, string] wchar_t *                                     szMailbox,
             [out] long *                                               pPipeNumber);
    long RemoteGetRecipientPropsW
            ([in] unsigned long                                         dwObjID,
             [out] unsigned long *                                      pdwUserType,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szMailboxName,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szFullName,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szJobTitle,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szOffice,
             [out, string, size_is(MAX_PHONE_SIZE)] wchar_t *           szPhone,
             [out, string, size_is(MAX_PHONE_SIZE)] wchar_t *           szAltPhone,
             [out, string, size_is(MAX_PHONE_SIZE)] wchar_t *           szFax,
             [out, string, size_is(MAX_COMMENT_SIZE)] wchar_t *         szComments,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szCompany,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szDepartment,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szManagerName,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szManagerAlias,
             [out] unsigned long *                                      pdwManagerID);
    long RemoteGetDistListPropsW
            ([in] unsigned long                                         dwObjID,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szDLAlias,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szDLFullName,
             [out] unsigned long *                                      pdwContentCount,
             [out, string, size_is(MAX_ALIAS_SIZE)] wchar_t *           szOwnerAlias,
             [out, string, size_is(MAX_STRING_SIZE)] wchar_t *          szOwnerName,
             [out, string, size_is(MAX_COMMENT_SIZE)] wchar_t *         szComments);
}


WINDS_C.C

/* this ALWAYS GENERATED file contains the RPC client stubs */

/* File created by MIDL compiler version 2.00.0102 */
/* at Mon Sep 25 11:13:21 1995
 */
//@@MIDL_FILE_HEADING(  )

#include <string.h>
#if defined( _ALPHA_ )
#include <stdarg.h>
#endif

#include "winds.h"

extern const MIDL_FORMAT_STRING __MIDLFormatString;

extern const MIDL_FORMAT_STRING __MIDLProcFormatString;
handle_t hWINDSREM;

static const RPC_CLIENT_INTERFACE WINDSREM___RpcClientInterface =
    {
    sizeof(RPC_CLIENT_INTERFACE),
    {{0xDDFFFE60,0x36D0,0x11CE,{0x9F,0x2D,0x00,0xAA,0x00,0x51,0x0E,0x3B}},{1,6}},
    {{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
    0,
    0,
    0,
    0,
    0
    };
RPC_IF_HANDLE WINDSREM_v1_6_c_ifspec = (RPC_IF_HANDLE)& WINDSREM___RpcClientInterface;

extern const MIDL_STUB_DESC WINDSREM_StubDesc;

static RPC_BINDING_HANDLE WINDSREM__MIDL_AutoBindHandle;

long RemoteIsServerRunning( void)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          0);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 0U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[0] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetAllAccounts(
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          1);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 0U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[2] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteFinishUpload(
    /* [string][in] */ unsigned char __RPC_FAR *szCookie)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          2);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCookie,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[8] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetContainerProps(
    /* [in] */ unsigned long dwContainerID,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          3);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwContainerID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[14] );

        *pdwContentCount = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetContainerRecipients(
    /* [in] */ unsigned long dwFlags,
    /* [in] */ unsigned long dwContainerID,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          4);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U + 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFlags;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwContainerID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[22] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetPublicFoldersList(
    /* [in] */ short nFlags,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          5);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 2U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( short __RPC_FAR * )_StubMsg.Buffer)++ = nFlags;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[32] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetPublicFolderMessages(
    /* [in] */ short nFlags,
    /* [in] */ unsigned long dwFolderID,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          6);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 2U + 6U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( short __RPC_FAR * )_StubMsg.Buffer)++ = nFlags;

        _StubMsg.Buffer += 2;
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwFolderID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteChangePasswordA(
    /* [in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szOldPassword,
    /* [string][in] */ unsigned char __RPC_FAR *szNewPassword)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          7);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 1U + 12U + 13U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOldPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szNewPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned char __RPC_FAR * )_StubMsg.Buffer)++ = *szMailbox;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOldPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szNewPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[50] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteLogonMailBoxA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szPassword,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          8);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 13U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[64] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[16],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwMailboxID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteLogonMailBoxAndSetNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [string][in] */ unsigned char __RPC_FAR *szPassword,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID,
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          9);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 13U + 13U + 11U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[82] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[26],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwMailboxID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteTerminateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          10);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 11U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulConnectionID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[110] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteValidateNotifA(
    /* [string][in] */ unsigned char __RPC_FAR *szComputerName,
    /* [string][in] */ unsigned char __RPC_FAR *szMailboxName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          11);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 13U + 11U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailboxName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[118] );

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenMsgUploadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szSenderMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCookie)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          12);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szSenderMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szSenderMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[134] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[36],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteSendMsgToAccountA(
    /* [string][in] */ unsigned char __RPC_FAR *szRecipAccount,
    /* [string][in] */ unsigned char __RPC_FAR *szHeaderInfo,
    /* [string][in] */ unsigned char __RPC_FAR *szCookie)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          13);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 13U + 13U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szRecipAccount,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,

(unsigned char __RPC_FAR *)szHeaderInfo,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szRecipAccount,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szHeaderInfo,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCookie,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[148] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenMsgDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          14);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        *pPipeNumber = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenHeaderDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          15);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteCheckNewMailA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pulPending)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          16);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        *pulPending = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenOneMsgDownloadPipeA(
    /* [string][in] */ unsigned char __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          17);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[6] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetRecipientPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwUserType,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szMailboxName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFullName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szJobTitle,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOffice,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szPhone,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szAltPhone,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szFax,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCompany,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDepartment,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szManagerName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szManagerAlias,
    /* [out] */ unsigned long __RPC_FAR *pdwManagerID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          18);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        *pdwUserType = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[46],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[56],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szJobTitle,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[66],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOffice,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[76],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[86],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szAltPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[96],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFax,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[106],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[116],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCompany,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[126],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDepartment,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[136],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szManagerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[146],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szManagerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[156],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwManagerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetDistListPropsA(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szOwnerName,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szComments)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          19);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[232] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[166],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[176],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwContentCount = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[186],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[196],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[206],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteChangePasswordW(
    /* [in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szOldPassword,
    /* [string][in] */ wchar_t __RPC_FAR *szNewPassword)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          20);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 2U + 12U + 14U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOldPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szNewPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( wchar_t __RPC_FAR * )_StubMsg.Buffer)++ = *szMailbox;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOldPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szNewPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[260] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteLogonMailBoxW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szPassword,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          21);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 14U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[274] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[224],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwMailboxID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteLogonMailBoxAndSetNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [string][in] */ wchar_t __RPC_FAR *szPassword,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwMailboxID,
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          22);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 14U + 14U + 10U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPassword,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[292] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[234],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwMailboxID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteTerminateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [in] */ unsigned long ulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          23);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 10U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulConnectionID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[320] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteValidateNotifW(
    /* [string][in] */ wchar_t __RPC_FAR *szComputerName,
    /* [string][in] */ wchar_t __RPC_FAR *szMailboxName,
    /* [in] */ unsigned long ulNotifMask,
    /* [out] */ unsigned long __RPC_FAR *pulConnectionID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          24);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 14U + 10U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComputerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailboxName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = ulNotifMask;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[328] );

        *pulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenMsgUploadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szSenderMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber,
    /* [size_is][string][out] */ unsigned char __RPC_FAR *szCookie)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          25);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szSenderMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szSenderMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[344] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[244],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteSendMsgToAccountW(
    /* [string][in] */ wchar_t __RPC_FAR *szRecipAccount,
    /* [string][in] */ wchar_t __RPC_FAR *szHeaderInfo,
    /* [string][in] */ wchar_t __RPC_FAR *szCookie)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          26);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U + 14U + 14U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szRecipAccount,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szHeaderInfo,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szRecipAccount,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szHeaderInfo,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCookie,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[358] );

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenMsgDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          27);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        *pPipeNumber = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenHeaderDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          28);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteCheckNewMailW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ unsigned long __RPC_FAR *pulPending)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          29);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        *pulPending = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteOpenOneMsgDownloadPipeW(
    /* [string][in] */ wchar_t __RPC_FAR *szMailbox,
    /* [out] */ long __RPC_FAR *pPipeNumber)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          30);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 12U;
        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailbox,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[218] );

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        *pPipeNumber = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetRecipientPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [out] */ unsigned long __RPC_FAR *pdwUserType,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szMailboxName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFullName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szJobTitle,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOffice,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szPhone,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szAltPhone,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szFax,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szCompany,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDepartment,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szManagerName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szManagerAlias,
    /* [out] */ unsigned long __RPC_FAR *pdwManagerID)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          31);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[382] );

        *pdwUserType = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[254],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[264],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szJobTitle,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[274],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOffice,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[284],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[294],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szAltPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[304],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szFax,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[314],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[324],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCompany,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[334],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDepartment,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[344],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szManagerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[354],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szManagerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[364],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwManagerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

long RemoteGetDistListPropsW(
    /* [in] */ unsigned long dwObjID,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szDLFullName,
    /* [out] */ unsigned long __RPC_FAR *pdwContentCount,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerAlias,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szOwnerName,
    /* [size_is][string][out] */ wchar_t __RPC_FAR *szComments)
{

    RPC_BINDING_HANDLE _Handle  =   0;

    long _RetVal;

    RPC_MESSAGE _RpcMessage;

    MIDL_STUB_MESSAGE _StubMsg;

    RpcTryFinally
        {
        NdrClientInitializeNew(
                          ( PRPC_MESSAGE  )&_RpcMessage,
                          ( PMIDL_STUB_MESSAGE  )&_StubMsg,
                          ( PMIDL_STUB_DESC  )&WINDSREM_StubDesc,
                          32);

        _Handle = hWINDSREM;

        _StubMsg.BufferLength = 4U;
        NdrGetBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg, _StubMsg.BufferLength, _Handle );

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = dwObjID;

        NdrSendReceive( (PMIDL_STUB_MESSAGE) &_StubMsg, (unsigned char __RPC_FAR *) _StubMsg.Buffer );

        if ( (_RpcMessage.DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[442] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[374],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[384],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *pdwContentCount = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[394],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[404],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[414],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        _RetVal = *(( long __RPC_FAR * )_StubMsg.Buffer)++;

        }
    RpcFinally
        {
        NdrFreeBuffer( (PMIDL_STUB_MESSAGE) &_StubMsg );

        }
    RpcEndFinally

    return _RetVal;
}

static const MIDL_STUB_DESC WINDSREM_StubDesc =
    {
    (void __RPC_FAR *)& WINDSREM___RpcClientInterface,
    MIDL_user_allocate,
    MIDL_user_free,
    &hWINDSREM,
    0,
    0,
    0,
    0,
    __MIDLFormatString.Format,
    0, /* -error bounds_check flag */
    0x10001, /* Ndr library version */
    0,
    0x2000066, /* MIDL Version 2.0.102 */
    0
    };

#if !defined(__RPC_WIN32__)
#error  Invalid build platform for this stub.
#endif

static const MIDL_FORMAT_STRING __MIDLProcFormatString =
    {
        0,
        {
            0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  2 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/*  4 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/*  6 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  8 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 10 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 12 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 14 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 16 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 18 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 20 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 22 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 24 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 26 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 28 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 30 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 32 */    0x4e,       /* FC_IN_PARAM_BASETYPE */

0x6,        /* FC_SHORT */
/* 34 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 36 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 38 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 40 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x6,        /* FC_SHORT */
/* 42 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 44 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 46 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 48 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 50 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 52 */    NdrFcShort( 0x8 ),  /* Type Offset=8 */
/* 54 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 56 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 58 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 60 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 62 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 64 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 66 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 68 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 70 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 72 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 74 */    NdrFcShort( 0xc ),  /* Type Offset=12 */
/* 76 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 78 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 80 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 82 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 84 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 86 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 88 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 90 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 92 */    NdrFcShort( 0x16 ), /* Type Offset=22 */
/* 94 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 96 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 98 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 100 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 102 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 104 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 106 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 108 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 110 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 112 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 114 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 116 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 118 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 120 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 122 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 124 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 126 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 128 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 130 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 132 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 134 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 136 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 138 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 140 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 142 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 144 */   NdrFcShort( 0x20 ), /* Type Offset=32 */
/* 146 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 148 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 150 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 152 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 154 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 156 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 158 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 160 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 162 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 164 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 166 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 168 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 170 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 172 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 174 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 176 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 178 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 180 */   NdrFcShort( 0x2a ), /* Type Offset=42 */
/* 182 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 184 */   NdrFcShort( 0x34 ), /* Type Offset=52 */
/* 186 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 188 */   NdrFcShort( 0x3e ), /* Type Offset=62 */
/* 190 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 192 */   NdrFcShort( 0x48 ), /* Type Offset=72 */
/* 194 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 196 */   NdrFcShort( 0x52 ), /* Type Offset=82 */
/* 198 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 200 */   NdrFcShort( 0x5c ), /* Type Offset=92 */
/* 202 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 204 */   NdrFcShort( 0x66 ), /* Type Offset=102 */
/* 206 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 208 */   NdrFcShort( 0x70 ), /* Type Offset=112 */
/* 210 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 212 */   NdrFcShort( 0x7a ), /* Type Offset=122 */
/* 214 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 216 */   NdrFcShort( 0x84 ), /* Type Offset=132 */
/* 218 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 220 */   NdrFcShort( 0x8e ), /* Type Offset=142 */
/* 222 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 224 */   NdrFcShort( 0x98 ), /* Type Offset=152 */
/* 226 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 228 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 230 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 232 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 234 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 236 */   NdrFcShort( 0xa2 ), /* Type Offset=162 */
/* 238 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 240 */   NdrFcShort( 0xac ), /* Type Offset=172 */
/* 242 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 244 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 246 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 248 */   NdrFcShort( 0xb6 ), /* Type Offset=182 */
/* 250 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 252 */   NdrFcShort( 0xc0 ), /* Type Offset=192 */
/* 254 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 256 */   NdrFcShort( 0xca ), /* Type Offset=202 */
/* 258 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 260 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 262 */   NdrFcShort( 0xd4 ), /* Type Offset=212 */
/* 264 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 266 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 268 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 270 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 272 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 274 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 276 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 278 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 280 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 282 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 284 */   NdrFcShort( 0xdc ), /* Type Offset=220 */
/* 286 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 288 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 290 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 292 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 294 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 296 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 298 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 300 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 302 */   NdrFcShort( 0xe6 ), /* Type Offset=230 */
/* 304 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 306 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 308 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 310 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 312 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 314 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 316 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 318 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 320 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 322 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 324 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 326 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 328 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 330 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 332 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 334 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 336 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 338 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 340 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 342 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 344 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 346 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 348 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 350 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 352 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 354 */   NdrFcShort( 0xf0 ), /* Type Offset=240 */
/* 356 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 358 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 360 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 362 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 364 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 366 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 368 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 370 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 372 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 374 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 376 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 378 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 380 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 382 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 384 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 386 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 388 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 390 */   NdrFcShort( 0xfa ), /* Type Offset=250 */
/* 392 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 394 */   NdrFcShort( 0x104 ),    /* Type Offset=260 */
/* 396 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 398 */   NdrFcShort( 0x10e ),    /* Type Offset=270 */
/* 400 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 402 */   NdrFcShort( 0x118 ),    /* Type Offset=280 */
/* 404 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 406 */   NdrFcShort( 0x122 ),    /* Type Offset=290 */
/* 408 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 410 */   NdrFcShort( 0x12c ),    /* Type Offset=300 */
/* 412 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 414 */   NdrFcShort( 0x136 ),    /* Type Offset=310 */
/* 416 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 418 */   NdrFcShort( 0x140 ),    /* Type Offset=320 */
/* 420 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 422 */   NdrFcShort( 0x14a ),    /* Type Offset=330 */
/* 424 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 426 */   NdrFcShort( 0x154 ),    /* Type Offset=340 */
/* 428 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 430 */   NdrFcShort( 0x15e ),    /* Type Offset=350 */
/* 432 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 434 */   NdrFcShort( 0x168 ),    /* Type Offset=360 */
/* 436 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 438 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 440 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 442 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 444 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 446 */   NdrFcShort( 0x172 ),    /* Type Offset=370 */
/* 448 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 450 */   NdrFcShort( 0x17c ),    /* Type Offset=380 */
/* 452 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 454 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 456 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 458 */   NdrFcShort( 0x186 ),    /* Type Offset=390 */
/* 460 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 462 */   NdrFcShort( 0x190 ),    /* Type Offset=400 */
/* 464 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 466 */   NdrFcShort( 0x19a ),    /* Type Offset=410 */
/* 468 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */

            0x0
        }
    };

static const MIDL_FORMAT_STRING __MIDLFormatString =
    {
        0,
        {
            0x11, 0xc,  /* FC_RP [alloced_on_stack] [simple_pointer] */
/*  2 */    0x8,        /* FC_LONG */
            0x5c,       /* FC_PAD */
/*  4 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/*  6 */
            0x22,       /* FC_C_CSTRING */
            0x5c,       /* FC_PAD */
/*  8 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 10 */    0x2,        /* FC_CHAR */
            0x5c,       /* FC_PAD */
/* 12 */
            0x11, 0x0,  /* FC_RP */
/* 14 */    NdrFcShort( 0x2 ),  /* Offset= 2 (16) */
/* 16 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 18 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 20 */    NdrFcShort( 0x21 ), /* 33 */
/* 22 */
            0x11, 0x0,  /* FC_RP */
/* 24 */    NdrFcShort( 0x2 ),  /* Offset= 2 (26) */
/* 26 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 28 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 30 */    NdrFcShort( 0x21 ), /* 33 */
/* 32 */
            0x11, 0x0,  /* FC_RP */
/* 34 */    NdrFcShort( 0x2 ),  /* Offset= 2 (36) */
/* 36 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 38 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 40 */    NdrFcShort( 0x21 ), /* 33 */
/* 42 */
            0x11, 0x0,  /* FC_RP */
/* 44 */    NdrFcShort( 0x2 ),  /* Offset= 2 (46) */
/* 46 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 48 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 50 */    NdrFcShort( 0x11 ), /* 17 */
/* 52 */
            0x11, 0x0,  /* FC_RP */
/* 54 */    NdrFcShort( 0x2 ),  /* Offset= 2 (56) */
/* 56 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 58 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 60 */    NdrFcShort( 0x21 ), /* 33 */
/* 62 */
            0x11, 0x0,  /* FC_RP */
/* 64 */    NdrFcShort( 0x2 ),  /* Offset= 2 (66) */
/* 66 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */

/* 68 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 70 */    NdrFcShort( 0x21 ), /* 33 */
/* 72 */
            0x11, 0x0,  /* FC_RP */
/* 74 */    NdrFcShort( 0x2 ),  /* Offset= 2 (76) */
/* 76 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 78 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 80 */    NdrFcShort( 0x21 ), /* 33 */
/* 82 */
            0x11, 0x0,  /* FC_RP */
/* 84 */    NdrFcShort( 0x2 ),  /* Offset= 2 (86) */
/* 86 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 88 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 90 */    NdrFcShort( 0xd ),  /* 13 */
/* 92 */
            0x11, 0x0,  /* FC_RP */
/* 94 */    NdrFcShort( 0x2 ),  /* Offset= 2 (96) */
/* 96 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 98 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 100 */   NdrFcShort( 0xd ),  /* 13 */
/* 102 */
            0x11, 0x0,  /* FC_RP */
/* 104 */   NdrFcShort( 0x2 ),  /* Offset= 2 (106) */
/* 106 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 108 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 110 */   NdrFcShort( 0xd ),  /* 13 */
/* 112 */
            0x11, 0x0,  /* FC_RP */
/* 114 */   NdrFcShort( 0x2 ),  /* Offset= 2 (116) */
/* 116 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 118 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 120 */   NdrFcShort( 0x50 ), /* 80 */
/* 122 */
            0x11, 0x0,  /* FC_RP */
/* 124 */   NdrFcShort( 0x2 ),  /* Offset= 2 (126) */
/* 126 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 128 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 130 */   NdrFcShort( 0x21 ), /* 33 */
/* 132 */
            0x11, 0x0,  /* FC_RP */
/* 134 */   NdrFcShort( 0x2 ),  /* Offset= 2 (136) */
/* 136 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 138 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 140 */   NdrFcShort( 0x21 ), /* 33 */
/* 142 */
            0x11, 0x0,  /* FC_RP */
/* 144 */   NdrFcShort( 0x2 ),  /* Offset= 2 (146) */
/* 146 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 148 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 150 */   NdrFcShort( 0x21 ), /* 33 */
/* 152 */
            0x11, 0x0,  /* FC_RP */
/* 154 */   NdrFcShort( 0x2 ),  /* Offset= 2 (156) */
/* 156 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 158 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 160 */   NdrFcShort( 0x11 ), /* 17 */
/* 162 */
            0x11, 0x0,  /* FC_RP */
/* 164 */   NdrFcShort( 0x2 ),  /* Offset= 2 (166) */
/* 166 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 168 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 170 */   NdrFcShort( 0x11 ), /* 17 */
/* 172 */
            0x11, 0x0,  /* FC_RP */
/* 174 */   NdrFcShort( 0x2 ),  /* Offset= 2 (176) */
/* 176 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 178 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 180 */   NdrFcShort( 0x21 ), /* 33 */
/* 182 */
            0x11, 0x0,  /* FC_RP */
/* 184 */   NdrFcShort( 0x2 ),  /* Offset= 2 (186) */
/* 186 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 188 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 190 */   NdrFcShort( 0x11 ), /* 17 */
/* 192 */
            0x11, 0x0,  /* FC_RP */
/* 194 */   NdrFcShort( 0x2 ),  /* Offset= 2 (196) */
/* 196 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 198 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 200 */   NdrFcShort( 0x21 ), /* 33 */
/* 202 */
            0x11, 0x0,  /* FC_RP */
/* 204 */   NdrFcShort( 0x2 ),  /* Offset= 2 (206) */
/* 206 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 208 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 210 */   NdrFcShort( 0x50 ), /* 80 */
/* 212 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 214 */   0x5,        /* FC_WCHAR */
            0x5c,       /* FC_PAD */
/* 216 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 218 */
            0x25,       /* FC_C_WSTRING */
            0x5c,       /* FC_PAD */
/* 220 */
            0x11, 0x0,  /* FC_RP */
/* 222 */   NdrFcShort( 0x2 ),  /* Offset= 2 (224) */
/* 224 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 226 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 228 */   NdrFcShort( 0x21 ), /* 33 */
/* 230 */
            0x11, 0x0,  /* FC_RP */
/* 232 */   NdrFcShort( 0x2 ),  /* Offset= 2 (234) */
/* 234 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 236 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 238 */   NdrFcShort( 0x21 ), /* 33 */
/* 240 */
            0x11, 0x0,  /* FC_RP */
/* 242 */   NdrFcShort( 0x2 ),  /* Offset= 2 (244) */
/* 244 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 246 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 248 */   NdrFcShort( 0x21 ), /* 33 */
/* 250 */
            0x11, 0x0,  /* FC_RP */
/* 252 */   NdrFcShort( 0x2 ),  /* Offset= 2 (254) */
/* 254 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 256 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 258 */   NdrFcShort( 0x11 ), /* 17 */
/* 260 */
            0x11, 0x0,  /* FC_RP */
/* 262 */   NdrFcShort( 0x2 ),  /* Offset= 2 (264) */
/* 264 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 266 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 268 */   NdrFcShort( 0x21 ), /* 33 */
/* 270 */
            0x11, 0x0,  /* FC_RP */
/* 272 */   NdrFcShort( 0x2 ),  /* Offset= 2 (274) */
/* 274 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 276 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 278 */   NdrFcShort( 0x21 ), /* 33 */
/* 280 */
            0x11, 0x0,  /* FC_RP */
/* 282 */   NdrFcShort( 0x2 ),  /* Offset= 2 (284) */
/* 284 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 286 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 288 */   NdrFcShort( 0x21 ), /* 33 */
/* 290 */
            0x11, 0x0,  /* FC_RP */
/* 292 */   NdrFcShort( 0x2 ),  /* Offset= 2 (294) */
/* 294 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 296 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 298 */   NdrFcShort( 0xd ),  /* 13 */
/* 300 */
            0x11, 0x0,  /* FC_RP */
/* 302 */   NdrFcShort( 0x2 ),  /* Offset= 2 (304) */
/* 304 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 306 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 308 */   NdrFcShort( 0xd ),  /* 13 */
/* 310 */
            0x11, 0x0,  /* FC_RP */
/* 312 */   NdrFcShort( 0x2 ),  /* Offset= 2 (314) */
/* 314 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 316 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 318 */   NdrFcShort( 0xd ),  /* 13 */
/* 320 */
            0x11, 0x0,  /* FC_RP */
/* 322 */   NdrFcShort( 0x2 ),  /* Offset= 2 (324) */
/* 324 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 326 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 328 */   NdrFcShort( 0x50 ), /* 80 */
/* 330 */
            0x11, 0x0,  /* FC_RP */
/* 332 */   NdrFcShort( 0x2 ),  /* Offset= 2 (334) */
/* 334 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 336 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 338 */   NdrFcShort( 0x21 ), /* 33 */
/* 340 */
            0x11, 0x0,  /* FC_RP */
/* 342 */   NdrFcShort( 0x2 ),  /* Offset= 2 (344) */
/* 344 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 346 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 348 */   NdrFcShort( 0x21 ), /* 33 */
/* 350 */
            0x11, 0x0,  /* FC_RP */
/* 352 */   NdrFcShort( 0x2 ),  /* Offset= 2 (354) */
/* 354 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 356 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 358 */   NdrFcShort( 0x21 ), /* 33 */
/* 360 */
            0x11, 0x0,  /* FC_RP */
/* 362 */   NdrFcShort( 0x2 ),  /* Offset= 2 (364) */
/* 364 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 366 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 368 */   NdrFcShort( 0x11 ), /* 17 */
/* 370 */
            0x11, 0x0,  /* FC_RP */
/* 372 */   NdrFcShort( 0x2 ),  /* Offset= 2 (374) */
/* 374 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 376 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 378 */   NdrFcShort( 0x11 ), /* 17 */
/* 380 */
            0x11, 0x0,  /* FC_RP */
/* 382 */   NdrFcShort( 0x2 ),  /* Offset= 2 (384) */
/* 384 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 386 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 388 */   NdrFcShort( 0x21 ), /* 33 */
/* 390 */
            0x11, 0x0,  /* FC_RP */
/* 392 */   NdrFcShort( 0x2 ),  /* Offset= 2 (394) */
/* 394 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 396 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 398 */   NdrFcShort( 0x11 ), /* 17 */
/* 400 */
            0x11, 0x0,  /* FC_RP */
/* 402 */   NdrFcShort( 0x2 ),  /* Offset= 2 (404) */
/* 404 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 406 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 408 */   NdrFcShort( 0x21 ), /* 33 */
/* 410 */
            0x11, 0x0,  /* FC_RP */
/* 412 */   NdrFcShort( 0x2 ),  /* Offset= 2 (414) */
/* 414 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 416 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 418 */   NdrFcShort( 0x50 ), /* 80 */

            0x0
        }
    };


WINDS_S.C

/* this ALWAYS GENERATED file contains the RPC server stubs */

/* File created by MIDL compiler version 2.00.0102 */
/* at Mon Sep 25 11:13:21 1995
 */
//@@MIDL_FILE_HEADING(  )

#include <string.h>
#include "winds.h"

extern const MIDL_FORMAT_STRING __MIDLFormatString;

extern const MIDL_FORMAT_STRING __MIDLProcFormatString;

extern RPC_DISPATCH_TABLE WINDSREM_v1_6_DispatchTable;

static const RPC_SERVER_INTERFACE WINDSREM___RpcServerInterface =
    {
    sizeof(RPC_SERVER_INTERFACE),
    {{0xDDFFFE60,0x36D0,0x11CE,{0x9F,0x2D,0x00,0xAA,0x00,0x51,0x0E,0x3B}},{1,6}},
    {{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
    &WINDSREM_v1_6_DispatchTable,
    0,
    0,
    0,
    0
    };
RPC_IF_HANDLE WINDSREM_v1_6_s_ifspec = (RPC_IF_HANDLE)& WINDSREM___RpcServerInterface;

extern const MIDL_STUB_DESC WINDSREM_StubDesc;

void __RPC_STUB
WINDSREM_RemoteIsServerRunning(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    RpcTryFinally
        {

        _RetVal = RemoteIsServerRunning();

        _StubMsg.BufferLength = 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetAllAccounts(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M154;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        pPipeNumber = &_M154;

        _RetVal = RemoteGetAllAccounts(pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteFinishUpload(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned char __RPC_FAR *szCookie;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szCookie = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[8] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _RetVal = RemoteFinishUpload(szCookie);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetContainerProps(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M155;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwContainerID;
    unsigned long __RPC_FAR *pdwContentCount;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pdwContentCount = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[14] );

        dwContainerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pdwContentCount = &_M155;

        _RetVal = RemoteGetContainerProps(dwContainerID,pdwContentCount);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwContentCount;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetContainerRecipients(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M156;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwContainerID;
    unsigned long dwFlags;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[22] );

        dwFlags = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        dwContainerID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pPipeNumber = &_M156;

        _RetVal = RemoteGetContainerRecipients(
                                       dwFlags,
                                       dwContainerID,
                                       pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetPublicFoldersList(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M157;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    short nFlags;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[32] );

        nFlags = *(( short __RPC_FAR * )_StubMsg.Buffer)++;

        pPipeNumber = &_M157;

        _RetVal = RemoteGetPublicFoldersList(nFlags,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetPublicFolderMessages(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M158;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwFolderID;
    short nFlags;
    long __RPC_FAR *pPipeNumber;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[40] );

        nFlags = *(( short __RPC_FAR * )_StubMsg.Buffer)++;

        _StubMsg.Buffer += 2;
        dwFolderID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pPipeNumber = &_M158;

        _RetVal = RemoteGetPublicFolderMessages(
                                        nFlags,
                                        dwFolderID,
                                        pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteChangePasswordA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned char __RPC_FAR *szMailbox;
    unsigned char __RPC_FAR *szNewPassword;
    unsigned char __RPC_FAR *szOldPassword;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szOldPassword = 0;
    szNewPassword = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[50] );

        szMailbox = ( unsigned char __RPC_FAR * )_StubMsg.Buffer;
        _StubMsg.Buffer += sizeof( unsigned char  );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOldPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szNewPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _RetVal = RemoteChangePasswordA(
                                szMailbox,
                                szOldPassword,
                                szNewPassword);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteLogonMailBoxA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M159;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pdwMailboxID;
    unsigned char __RPC_FAR *szFullName;
    unsigned char __RPC_FAR *szMailbox;
    unsigned char __RPC_FAR *szPassword;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szPassword = 0;
    szFullName = 0;
    pdwMailboxID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[64] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        szFullName = _StubMsg.pfnAllocate((32 + 1) * 1);
        pdwMailboxID = &_M159;

        _RetVal = RemoteLogonMailBoxA(
                              szMailbox,
                              szPassword,
                              szFullName,
                              pdwMailboxID);

        _StubMsg.BufferLength = 12U + 11U + 7U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[16] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[16] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwMailboxID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[12] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteLogonMailBoxAndSetNotifA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M160;
    unsigned long _M161;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pdwMailboxID;
    unsigned long __RPC_FAR *pulConnectionID;
    unsigned char __RPC_FAR *szComputerName;
    unsigned char __RPC_FAR *szFullName;
    unsigned char __RPC_FAR *szMailbox;
    unsigned char __RPC_FAR *szPassword;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szPassword = 0;
    szFullName = 0;
    pdwMailboxID = 0;
    szComputerName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[82] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        szFullName = _StubMsg.pfnAllocate((32 + 1) * 1);
        pdwMailboxID = &_M160;
        pulConnectionID = &_M161;

        _RetVal = RemoteLogonMailBoxAndSetNotifA(
                                         szMailbox,
                                         szPassword,
                                         szFullName,
                                         pdwMailboxID,
                                         szComputerName,
                                         ulNotifMask,
                                         pulConnectionID);

        _StubMsg.BufferLength = 12U + 11U + 7U + 7U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[26] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[26] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwMailboxID;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[22] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteTerminateNotifA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned char __RPC_FAR *szComputerName;
    unsigned long ulConnectionID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szComputerName = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[110] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteTerminateNotifA(szComputerName,ulConnectionID);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteValidateNotifA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M162;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulConnectionID;
    unsigned char __RPC_FAR *szComputerName;
    unsigned char __RPC_FAR *szMailboxName;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szComputerName = 0;
    szMailboxName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[118] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pulConnectionID = &_M162;

        _RetVal = RemoteValidateNotifA(
                               szComputerName,
                               szMailboxName,
                               ulNotifMask,
                               pulConnectionID);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenMsgUploadPipeA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M163;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    unsigned char __RPC_FAR *szCookie;
    unsigned char __RPC_FAR *szSenderMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szSenderMailbox = 0;
    pPipeNumber = 0;
    szCookie = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[134] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szSenderMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        pPipeNumber = &_M163;
        szCookie = _StubMsg.pfnAllocate((32 + 1) * 1);

        _RetVal = RemoteOpenMsgUploadPipeA(
                                   szSenderMailbox,
                                   pPipeNumber,
                                   szCookie);

        _StubMsg.BufferLength = 4U + 12U + 11U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCookie,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[36] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szCookie,
                        &__MIDLFormatString.Format[32] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteSendMsgToAccountA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned char __RPC_FAR *szCookie;
    unsigned char __RPC_FAR *szHeaderInfo;
    unsigned char __RPC_FAR *szRecipAccount;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szRecipAccount = 0;
    szHeaderInfo = 0;
    szCookie = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[148] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szRecipAccount,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szHeaderInfo,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        _RetVal = RemoteSendMsgToAccountA(
                                  szRecipAccount,
                                  szHeaderInfo,
                                  szCookie);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenMsgDownloadPipeA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M164;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pPipeNumber;
    unsigned char __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        pPipeNumber = &_M164;

        _RetVal = RemoteOpenMsgDownloadPipeA(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenHeaderDownloadPipeA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M165;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    unsigned char __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        pPipeNumber = &_M165;

        _RetVal = RemoteOpenHeaderDownloadPipeA(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteCheckNewMailA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M166;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulPending;
    unsigned char __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pulPending = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        pulPending = &_M166;

        _RetVal = RemoteCheckNewMailA(szMailbox,pulPending);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulPending;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenOneMsgDownloadPipeA(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M167;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    unsigned char __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[162] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[6],
                                       (unsigned char)0 );

        pPipeNumber = &_M167;

        _RetVal = RemoteOpenOneMsgDownloadPipeA(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetRecipientPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M168;
    unsigned long _M169;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    unsigned long __RPC_FAR *pdwManagerID;
    unsigned long __RPC_FAR *pdwUserType;
    unsigned char __RPC_FAR *szAltPhone;
    unsigned char __RPC_FAR *szComments;
    unsigned char __RPC_FAR *szCompany;
    unsigned char __RPC_FAR *szDepartment;
    unsigned char __RPC_FAR *szFax;
    unsigned char __RPC_FAR *szFullName;
    unsigned char __RPC_FAR *szJobTitle;
    unsigned char __RPC_FAR *szMailboxName;
    unsigned char __RPC_FAR *szManagerAlias;
    unsigned char __RPC_FAR *szManagerName;
    unsigned char __RPC_FAR *szOffice;
    unsigned char __RPC_FAR *szPhone;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pdwUserType = 0;
    szMailboxName = 0;
    szFullName = 0;
    szJobTitle = 0;
    szOffice = 0;
    szPhone = 0;
    szAltPhone = 0;
    szFax = 0;
    szComments = 0;
    szCompany = 0;
    szDepartment = 0;
    szManagerName = 0;
    szManagerAlias = 0;
    pdwManagerID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[172] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pdwUserType = &_M168;
        szMailboxName = _StubMsg.pfnAllocate((16 + 1) * 1);
        szFullName = _StubMsg.pfnAllocate((32 + 1) * 1);
        szJobTitle = _StubMsg.pfnAllocate((32 + 1) * 1);
        szOffice = _StubMsg.pfnAllocate((32 + 1) * 1);
        szPhone = _StubMsg.pfnAllocate((12 + 1) * 1);
        szAltPhone = _StubMsg.pfnAllocate((12 + 1) * 1);
        szFax = _StubMsg.pfnAllocate((12 + 1) * 1);
        szComments = _StubMsg.pfnAllocate((79 + 1) * 1);
        szCompany = _StubMsg.pfnAllocate((32 + 1) * 1);
        szDepartment = _StubMsg.pfnAllocate((32 + 1) * 1);
        szManagerName = _StubMsg.pfnAllocate((32 + 1) * 1);
        szManagerAlias = _StubMsg.pfnAllocate((16 + 1) * 1);
        pdwManagerID = &_M169;

        _RetVal = RemoteGetRecipientPropsA(
                                   dwObjID,
                                   pdwUserType,
                                   szMailboxName,
                                   szFullName,
                                   szJobTitle,
                                   szOffice,
                                   szPhone,
                                   szAltPhone,
                                   szFax,
                                   szComments,
                                   szCompany,
                                   szDepartment,
                                   szManagerName,
                                   szManagerAlias,
                                   pdwManagerID);

        _StubMsg.BufferLength = 4U + 12U + 13U + 13U + 13U + 13U + 13U + 13U + 13U + 13U + 13U + 13U + 13U + 11U + 7U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[46] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[56] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szJobTitle,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[66] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOffice,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[76] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[86] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szAltPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[96] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFax,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[106] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[116] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCompany,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[126] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDepartment,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[136] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szManagerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[146] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szManagerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[156] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwUserType;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailboxName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[46] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[56] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szJobTitle,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[66] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOffice,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[76] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPhone,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[86] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szAltPhone,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[96] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFax,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[106] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[116] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCompany,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[126] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDepartment,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[136] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szManagerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[146] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szManagerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[156] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwManagerID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szMailboxName,
                        &__MIDLFormatString.Format[42] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[52] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szJobTitle,
                        &__MIDLFormatString.Format[62] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOffice,
                        &__MIDLFormatString.Format[72] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szPhone,
                        &__MIDLFormatString.Format[82] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szAltPhone,
                        &__MIDLFormatString.Format[92] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFax,
                        &__MIDLFormatString.Format[102] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[112] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szCompany,
                        &__MIDLFormatString.Format[122] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDepartment,
                        &__MIDLFormatString.Format[132] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szManagerName,
                        &__MIDLFormatString.Format[142] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szManagerAlias,
                        &__MIDLFormatString.Format[152] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetDistListPropsA(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M170;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    unsigned long __RPC_FAR *pdwContentCount;
    unsigned char __RPC_FAR *szComments;
    unsigned char __RPC_FAR *szDLAlias;
    unsigned char __RPC_FAR *szDLFullName;
    unsigned char __RPC_FAR *szOwnerAlias;
    unsigned char __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    pdwContentCount = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    szComments = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[232] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        szDLAlias = _StubMsg.pfnAllocate((16 + 1) * 1);
        szDLFullName = _StubMsg.pfnAllocate((32 + 1) * 1);
        pdwContentCount = &_M170;
        szOwnerAlias = _StubMsg.pfnAllocate((16 + 1) * 1);
        szOwnerName = _StubMsg.pfnAllocate((32 + 1) * 1);
        szComments = _StubMsg.pfnAllocate((79 + 1) * 1);

        _RetVal = RemoteGetDistListPropsA(
                                  dwObjID,
                                  szDLAlias,
                                  szDLFullName,
                                  pdwContentCount,
                                  szOwnerAlias,
                                  szOwnerName,
                                  szComments);

        _StubMsg.BufferLength = 12U + 13U + 11U + 13U + 13U + 13U + 11U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[166] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[176] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[186] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[196] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[206] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[166] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[176] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwContentCount;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[186] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[196] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[206] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,

(unsigned char __RPC_FAR *)szDLAlias,
                        &__MIDLFormatString.Format[162] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLFullName,
                        &__MIDLFormatString.Format[172] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerAlias,
                        &__MIDLFormatString.Format[182] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerName,
                        &__MIDLFormatString.Format[192] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[202] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteChangePasswordW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    wchar_t __RPC_FAR *szMailbox;
    wchar_t __RPC_FAR *szNewPassword;
    wchar_t __RPC_FAR *szOldPassword;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szOldPassword = 0;
    szNewPassword = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[260] );

        szMailbox = ( wchar_t __RPC_FAR * )_StubMsg.Buffer;
        _StubMsg.Buffer += sizeof( wchar_t  );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szOldPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szNewPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        _RetVal = RemoteChangePasswordW(
                                szMailbox,
                                szOldPassword,
                                szNewPassword);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteLogonMailBoxW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M171;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pdwMailboxID;
    wchar_t __RPC_FAR *szFullName;
    wchar_t __RPC_FAR *szMailbox;
    wchar_t __RPC_FAR *szPassword;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szPassword = 0;
    szFullName = 0;
    pdwMailboxID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[274] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        szFullName = _StubMsg.pfnAllocate((32 + 1) * 2);
        pdwMailboxID = &_M171;

        _RetVal = RemoteLogonMailBoxW(
                              szMailbox,
                              szPassword,
                              szFullName,
                              pdwMailboxID);

        _StubMsg.BufferLength = 12U + 10U + 7U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[224] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[224] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwMailboxID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[220] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteLogonMailBoxAndSetNotifW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M172;
    unsigned long _M173;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pdwMailboxID;
    unsigned long __RPC_FAR *pulConnectionID;
    wchar_t __RPC_FAR *szComputerName;
    wchar_t __RPC_FAR *szFullName;
    wchar_t __RPC_FAR *szMailbox;
    wchar_t __RPC_FAR *szPassword;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    szPassword = 0;
    szFullName = 0;
    pdwMailboxID = 0;
    szComputerName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[292] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szPassword,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        szFullName = _StubMsg.pfnAllocate((32 + 1) * 2);
        pdwMailboxID = &_M172;
        pulConnectionID = &_M173;

        _RetVal = RemoteLogonMailBoxAndSetNotifW(
                                         szMailbox,
                                         szPassword,
                                         szFullName,
                                         pdwMailboxID,
                                         szComputerName,
                                         ulNotifMask,
                                         pulConnectionID);

        _StubMsg.BufferLength = 12U + 10U + 7U + 7U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[234] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwMailboxID;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[230] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteTerminateNotifW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    wchar_t __RPC_FAR *szComputerName;
    unsigned long ulConnectionID;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szComputerName = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[320] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulConnectionID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        _RetVal = RemoteTerminateNotifW(szComputerName,ulConnectionID);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteValidateNotifW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M174;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulConnectionID;
    wchar_t __RPC_FAR *szComputerName;
    wchar_t __RPC_FAR *szMailboxName;
    unsigned long ulNotifMask;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szComputerName = 0;
    szMailboxName = 0;
    pulConnectionID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[328] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szComputerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        ulNotifMask = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pulConnectionID = &_M174;

        _RetVal = RemoteValidateNotifW(
                               szComputerName,
                               szMailboxName,
                               ulNotifMask,
                               pulConnectionID);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulConnectionID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenMsgUploadPipeW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M175;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    unsigned char __RPC_FAR *szCookie;
    wchar_t __RPC_FAR *szSenderMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szSenderMailbox = 0;
    pPipeNumber = 0;
    szCookie = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[344] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szSenderMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        pPipeNumber = &_M175;
        szCookie = _StubMsg.pfnAllocate((32 + 1) * 1);

        _RetVal = RemoteOpenMsgUploadPipeW(
                                   szSenderMailbox,
                                   pPipeNumber,
                                   szCookie);

        _StubMsg.BufferLength = 4U + 12U + 11U;
        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[244] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCookie,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[244] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szCookie,
                        &__MIDLFormatString.Format[240] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteSendMsgToAccountW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    wchar_t __RPC_FAR *szCookie;
    wchar_t __RPC_FAR *szHeaderInfo;
    wchar_t __RPC_FAR *szRecipAccount;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szRecipAccount = 0;
    szHeaderInfo = 0;
    szCookie = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[358] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szRecipAccount,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szHeaderInfo,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szCookie,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        _RetVal = RemoteSendMsgToAccountW(
                                  szRecipAccount,
                                  szHeaderInfo,
                                  szCookie);

        _StubMsg.BufferLength = 4U;
        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenMsgDownloadPipeW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M176;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pPipeNumber;
    wchar_t __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        pPipeNumber = &_M176;

        _RetVal = RemoteOpenMsgDownloadPipeW(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenHeaderDownloadPipeW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M177;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    wchar_t __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        pPipeNumber = &_M177;

        _RetVal = RemoteOpenHeaderDownloadPipeW(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteCheckNewMailW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M178;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long __RPC_FAR *pulPending;
    wchar_t __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pulPending = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        pulPending = &_M178;

        _RetVal = RemoteCheckNewMailW(szMailbox,pulPending);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pulPending;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteOpenOneMsgDownloadPipeW(
    PRPC_MESSAGE _pRpcMessage )
{
    long _M179;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    long __RPC_FAR *pPipeNumber;
    wchar_t __RPC_FAR *szMailbox;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szMailbox = 0;
    pPipeNumber = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[372] );

        NdrConformantStringUnmarshall( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR * __RPC_FAR *)&szMailbox,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[218],
                                       (unsigned char)0 );

        pPipeNumber = &_M179;

        _RetVal = RemoteOpenOneMsgDownloadPipeW(szMailbox,pPipeNumber);

        _StubMsg.BufferLength = 4U + 4U;
        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = *pPipeNumber;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetRecipientPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M180;
    unsigned long _M181;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    unsigned long __RPC_FAR *pdwManagerID;
    unsigned long __RPC_FAR *pdwUserType;
    wchar_t __RPC_FAR *szAltPhone;
    wchar_t __RPC_FAR *szComments;
    wchar_t __RPC_FAR *szCompany;
    wchar_t __RPC_FAR *szDepartment;
    wchar_t __RPC_FAR *szFax;

wchar_t __RPC_FAR *szFullName;
    wchar_t __RPC_FAR *szJobTitle;
    wchar_t __RPC_FAR *szMailboxName;
    wchar_t __RPC_FAR *szManagerAlias;
    wchar_t __RPC_FAR *szManagerName;
    wchar_t __RPC_FAR *szOffice;
    wchar_t __RPC_FAR *szPhone;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    pdwUserType = 0;
    szMailboxName = 0;
    szFullName = 0;
    szJobTitle = 0;
    szOffice = 0;
    szPhone = 0;
    szAltPhone = 0;
    szFax = 0;
    szComments = 0;
    szCompany = 0;
    szDepartment = 0;
    szManagerName = 0;
    szManagerAlias = 0;
    pdwManagerID = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[382] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        pdwUserType = &_M180;
        szMailboxName = _StubMsg.pfnAllocate((16 + 1) * 2);
        szFullName = _StubMsg.pfnAllocate((32 + 1) * 2);
        szJobTitle = _StubMsg.pfnAllocate((32 + 1) * 2);
        szOffice = _StubMsg.pfnAllocate((32 + 1) * 2);
        szPhone = _StubMsg.pfnAllocate((12 + 1) * 2);
        szAltPhone = _StubMsg.pfnAllocate((12 + 1) * 2);
        szFax = _StubMsg.pfnAllocate((12 + 1) * 2);
        szComments = _StubMsg.pfnAllocate((79 + 1) * 2);
        szCompany = _StubMsg.pfnAllocate((32 + 1) * 2);
        szDepartment = _StubMsg.pfnAllocate((32 + 1) * 2);
        szManagerName = _StubMsg.pfnAllocate((32 + 1) * 2);
        szManagerAlias = _StubMsg.pfnAllocate((16 + 1) * 2);
        pdwManagerID = &_M181;

        _RetVal = RemoteGetRecipientPropsW(
                                   dwObjID,
                                   pdwUserType,
                                   szMailboxName,
                                   szFullName,
                                   szJobTitle,
                                   szOffice,
                                   szPhone,
                                   szAltPhone,
                                   szFax,
                                   szComments,
                                   szCompany,
                                   szDepartment,
                                   szManagerName,
                                   szManagerAlias,
                                   pdwManagerID);

        _StubMsg.BufferLength = 4U + 12U + 14U + 14U + 14U + 14U + 14U + 14U + 14U + 14U + 14U + 14U + 14U + 10U + 7U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szMailboxName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[254] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[264] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szJobTitle,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[274] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOffice,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[284] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[294] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szAltPhone,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[304] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szFax,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[314] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[324] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szCompany,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[334] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDepartment,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[344] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szManagerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[354] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szManagerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[364] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwUserType;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szMailboxName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[254] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[264] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szJobTitle,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[274] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOffice,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[284] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szPhone,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[294] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szAltPhone,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[304] );

        _StubMsg.MaxCount = 12 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szFax,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[314] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[324] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szCompany,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[334] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDepartment,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[344] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szManagerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[354] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szManagerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[364] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwManagerID;

        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szMailboxName,
                        &__MIDLFormatString.Format[250] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFullName,
                        &__MIDLFormatString.Format[260] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szJobTitle,
                        &__MIDLFormatString.Format[270] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOffice,
                        &__MIDLFormatString.Format[280] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szPhone,
                        &__MIDLFormatString.Format[290] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szAltPhone,
                        &__MIDLFormatString.Format[300] );

        _StubMsg.MaxCount = 12 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szFax,
                        &__MIDLFormatString.Format[310] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[320] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szCompany,
                        &__MIDLFormatString.Format[330] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDepartment,
                        &__MIDLFormatString.Format[340] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szManagerName,
                        &__MIDLFormatString.Format[350] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szManagerAlias,
                        &__MIDLFormatString.Format[360] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

void __RPC_STUB
WINDSREM_RemoteGetDistListPropsW(
    PRPC_MESSAGE _pRpcMessage )
{
    unsigned long _M182;
    long _RetVal;
    MIDL_STUB_MESSAGE _StubMsg;
    unsigned long dwObjID;
    unsigned long __RPC_FAR *pdwContentCount;
    wchar_t __RPC_FAR *szComments;
    wchar_t __RPC_FAR *szDLAlias;
    wchar_t __RPC_FAR *szDLFullName;
    wchar_t __RPC_FAR *szOwnerAlias;
    wchar_t __RPC_FAR *szOwnerName;
    RPC_STATUS _Status;

    NdrServerInitializeNew(
                          _pRpcMessage,
                          &_StubMsg,
                          &WINDSREM_StubDesc);

    szDLAlias = 0;
    szDLFullName = 0;
    pdwContentCount = 0;
    szOwnerAlias = 0;
    szOwnerName = 0;
    szComments = 0;
    RpcTryFinally
        {
        if ( (_pRpcMessage->DataRepresentation & 0X0000FFFFUL) != NDR_LOCAL_DATA_REPRESENTATION )
            NdrConvert( (PMIDL_STUB_MESSAGE) &_StubMsg, (PFORMAT_STRING) &__MIDLProcFormatString.Format[442] );

        dwObjID = *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++;

        szDLAlias = _StubMsg.pfnAllocate((16 + 1) * 2);
        szDLFullName = _StubMsg.pfnAllocate((32 + 1) * 2);
        pdwContentCount = &_M182;
        szOwnerAlias = _StubMsg.pfnAllocate((16 + 1) * 2);
        szOwnerName = _StubMsg.pfnAllocate((32 + 1) * 2);
        szComments = _StubMsg.pfnAllocate((79 + 1) * 2);

        _RetVal = RemoteGetDistListPropsW(
                                  dwObjID,
                                  szDLAlias,
                                  szDLFullName,
                                  pdwContentCount,
                                  szOwnerAlias,
                                  szOwnerName,
                                  szComments);

        _StubMsg.BufferLength = 12U + 14U + 10U + 14U + 14U + 14U + 10U;
        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[374] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szDLFullName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[384] );

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerAlias,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[394] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szOwnerName,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[404] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringBufferSize( (PMIDL_STUB_MESSAGE) &_StubMsg,
                                       (unsigned char __RPC_FAR *)szComments,
                                       (PFORMAT_STRING) &__MIDLFormatString.Format[414] );

        _StubMsg.BufferLength += 16;

        _pRpcMessage->BufferLength = _StubMsg.BufferLength;

        _Status = I_RpcGetBuffer( _pRpcMessage );
        if ( _Status )
            RpcRaiseException( _Status );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[374] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szDLFullName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[384] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( unsigned long __RPC_FAR * )_StubMsg.Buffer)++ = *pdwContentCount;

        _StubMsg.MaxCount = 16 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerAlias,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[394] );

        _StubMsg.MaxCount = 32 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szOwnerName,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[404] );

        _StubMsg.MaxCount = 79 + 1;

        NdrConformantStringMarshall( (PMIDL_STUB_MESSAGE)& _StubMsg,
                                     (unsigned char __RPC_FAR *)szComments,
                                     (PFORMAT_STRING) &__MIDLFormatString.Format[414] );

        _StubMsg.Buffer = (unsigned char __RPC_FAR *)(((long)_StubMsg.Buffer + 3) & ~ 0x3);
        *(( long __RPC_FAR * )_StubMsg.Buffer)++ = _RetVal;

        }
    RpcFinally
        {
        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLAlias,
                        &__MIDLFormatString.Format[370] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szDLFullName,
                        &__MIDLFormatString.Format[380] );

        _StubMsg.MaxCount = 16 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerAlias,
                        &__MIDLFormatString.Format[390] );

        _StubMsg.MaxCount = 32 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szOwnerName,
                        &__MIDLFormatString.Format[400] );

        _StubMsg.MaxCount = 79 + 1;

        NdrPointerFree( &_StubMsg,
                        (unsigned char __RPC_FAR *)szComments,
                        &__MIDLFormatString.Format[410] );

        }
    RpcEndFinally
    _pRpcMessage->BufferLength =
        (unsigned int)((long)_StubMsg.Buffer - (long)_pRpcMessage->Buffer);

}

static const MIDL_STUB_DESC WINDSREM_StubDesc =
    {
    (void __RPC_FAR *)& WINDSREM___RpcServerInterface,
    MIDL_user_allocate,
    MIDL_user_free,
    0,
    0,
    0,
    0,
    0,
    __MIDLFormatString.Format,
    0, /* -error bounds_check flag */
    0x10001, /* Ndr library version */
    0,
    0x2000066, /* MIDL Version 2.0.102 */
    0
    };

static RPC_DISPATCH_FUNCTION WINDSREM_table[] =
    {
    WINDSREM_RemoteIsServerRunning,
    WINDSREM_RemoteGetAllAccounts,
    WINDSREM_RemoteFinishUpload,
    WINDSREM_RemoteGetContainerProps,
    WINDSREM_RemoteGetContainerRecipients,
    WINDSREM_RemoteGetPublicFoldersList,
    WINDSREM_RemoteGetPublicFolderMessages,
    WINDSREM_RemoteChangePasswordA,
    WINDSREM_RemoteLogonMailBoxA,
    WINDSREM_RemoteLogonMailBoxAndSetNotifA,
    WINDSREM_RemoteTerminateNotifA,
    WINDSREM_RemoteValidateNotifA,
    WINDSREM_RemoteOpenMsgUploadPipeA,
    WINDSREM_RemoteSendMsgToAccountA,
    WINDSREM_RemoteOpenMsgDownloadPipeA,
    WINDSREM_RemoteOpenHeaderDownloadPipeA,
    WINDSREM_RemoteCheckNewMailA,
    WINDSREM_RemoteOpenOneMsgDownloadPipeA,
    WINDSREM_RemoteGetRecipientPropsA,
    WINDSREM_RemoteGetDistListPropsA,
    WINDSREM_RemoteChangePasswordW,
    WINDSREM_RemoteLogonMailBoxW,
    WINDSREM_RemoteLogonMailBoxAndSetNotifW,
    WINDSREM_RemoteTerminateNotifW,
    WINDSREM_RemoteValidateNotifW,
    WINDSREM_RemoteOpenMsgUploadPipeW,
    WINDSREM_RemoteSendMsgToAccountW,
    WINDSREM_RemoteOpenMsgDownloadPipeW,
    WINDSREM_RemoteOpenHeaderDownloadPipeW,
    WINDSREM_RemoteCheckNewMailW,
    WINDSREM_RemoteOpenOneMsgDownloadPipeW,
    WINDSREM_RemoteGetRecipientPropsW,
    WINDSREM_RemoteGetDistListPropsW,
    0
    };
RPC_DISPATCH_TABLE WINDSREM_v1_6_DispatchTable =
    {
    33,
    WINDSREM_table
    };

#if !defined(__RPC_WIN32__)
#error  Invalid build platform for this stub.
#endif

static const MIDL_FORMAT_STRING __MIDLProcFormatString =
    {
        0,
        {
            0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  2 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/*  4 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/*  6 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/*  8 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 10 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 12 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 14 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 16 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 18 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 20 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 22 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 24 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 26 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 28 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 30 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 32 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x6,        /* FC_SHORT */
/* 34 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 36 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 38 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 40 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x6,        /* FC_SHORT */
/* 42 */    0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 44 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 46 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 48 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 50 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 52 */    NdrFcShort( 0x8 ),  /* Type Offset=8 */
/* 54 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 56 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 58 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 60 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 62 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 64 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 66 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 68 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 70 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 72 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 74 */    NdrFcShort( 0xc ),  /* Type Offset=12 */
/* 76 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 78 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 80 */    0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 82 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 84 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 86 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 88 */    NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 90 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 92 */    NdrFcShort( 0x16 ), /* Type Offset=22 */
/* 94 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 96 */    NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 98 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 100 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 102 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 104 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 106 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 108 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 110 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 112 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 114 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 116 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 118 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 120 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 122 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 124 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 126 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */

/* 128 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 130 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 132 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 134 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 136 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 138 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 140 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 142 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 144 */   NdrFcShort( 0x20 ), /* Type Offset=32 */
/* 146 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 148 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 150 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 152 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 154 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 156 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 158 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 160 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 162 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 164 */   NdrFcShort( 0x4 ),  /* Type Offset=4 */
/* 166 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 168 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 170 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 172 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 174 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 176 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 178 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 180 */   NdrFcShort( 0x2a ), /* Type Offset=42 */
/* 182 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 184 */   NdrFcShort( 0x34 ), /* Type Offset=52 */
/* 186 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 188 */   NdrFcShort( 0x3e ), /* Type Offset=62 */
/* 190 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 192 */   NdrFcShort( 0x48 ), /* Type Offset=72 */
/* 194 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 196 */   NdrFcShort( 0x52 ), /* Type Offset=82 */
/* 198 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 200 */   NdrFcShort( 0x5c ), /* Type Offset=92 */
/* 202 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 204 */   NdrFcShort( 0x66 ), /* Type Offset=102 */
/* 206 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 208 */   NdrFcShort( 0x70 ), /* Type Offset=112 */
/* 210 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 212 */   NdrFcShort( 0x7a ), /* Type Offset=122 */
/* 214 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 216 */   NdrFcShort( 0x84 ), /* Type Offset=132 */
/* 218 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 220 */   NdrFcShort( 0x8e ), /* Type Offset=142 */
/* 222 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 224 */   NdrFcShort( 0x98 ), /* Type Offset=152 */
/* 226 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 228 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 230 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 232 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 234 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 236 */   NdrFcShort( 0xa2 ), /* Type Offset=162 */
/* 238 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 240 */   NdrFcShort( 0xac ), /* Type Offset=172 */
/* 242 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 244 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 246 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 248 */   NdrFcShort( 0xb6 ), /* Type Offset=182 */
/* 250 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 252 */   NdrFcShort( 0xc0 ), /* Type Offset=192 */
/* 254 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 256 */   NdrFcShort( 0xca ), /* Type Offset=202 */
/* 258 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 260 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 262 */   NdrFcShort( 0xd4 ), /* Type Offset=212 */
/* 264 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 266 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 268 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 270 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 272 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 274 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 276 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 278 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 280 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 282 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 284 */   NdrFcShort( 0xdc ), /* Type Offset=220 */
/* 286 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 288 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 290 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 292 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 294 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 296 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 298 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 300 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 302 */   NdrFcShort( 0xe6 ), /* Type Offset=230 */
/* 304 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 306 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 308 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 310 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 312 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 314 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 316 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 318 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 320 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 322 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 324 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 326 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 328 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 330 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 332 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 334 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 336 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 338 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 340 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 342 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 344 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 346 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 348 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 350 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 352 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 354 */   NdrFcShort( 0xf0 ), /* Type Offset=240 */
/* 356 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 358 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 360 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 362 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 364 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 366 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 368 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 370 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 372 */
            0x4d,       /* FC_IN_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 374 */   NdrFcShort( 0xd8 ), /* Type Offset=216 */
/* 376 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 378 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 380 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 382 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 384 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 386 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 388 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 390 */   NdrFcShort( 0xfa ), /* Type Offset=250 */
/* 392 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 394 */   NdrFcShort( 0x104 ),    /* Type Offset=260 */
/* 396 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 398 */   NdrFcShort( 0x10e ),    /* Type Offset=270 */
/* 400 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 402 */   NdrFcShort( 0x118 ),    /* Type Offset=280 */
/* 404 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 406 */   NdrFcShort( 0x122 ),    /* Type Offset=290 */
/* 408 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 410 */   NdrFcShort( 0x12c ),    /* Type Offset=300 */
/* 412 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 414 */   NdrFcShort( 0x136 ),    /* Type Offset=310 */
/* 416 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 418 */   NdrFcShort( 0x140 ),    /* Type Offset=320 */
/* 420 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 422 */   NdrFcShort( 0x14a ),    /* Type Offset=330 */
/* 424 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 426 */   NdrFcShort( 0x154 ),    /* Type Offset=340 */
/* 428 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 430 */   NdrFcShort( 0x15e ),    /* Type Offset=350 */
/* 432 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 434 */   NdrFcShort( 0x168 ),    /* Type Offset=360 */
/* 436 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 438 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 440 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 442 */   0x4e,       /* FC_IN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */
/* 444 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 446 */   NdrFcShort( 0x172 ),    /* Type Offset=370 */
/* 448 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 450 */   NdrFcShort( 0x17c ),    /* Type Offset=380 */
/* 452 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 454 */   NdrFcShort( 0x0 ),  /* Type Offset=0 */
/* 456 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 458 */   NdrFcShort( 0x186 ),    /* Type Offset=390 */
/* 460 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 462 */   NdrFcShort( 0x190 ),    /* Type Offset=400 */
/* 464 */
            0x51,       /* FC_OUT_PARAM */
#ifndef _ALPHA_
            0x1,        /* x86, MIPS & PPC Stack size = 1 */
#else
            0x2,        /* Alpha Stack size = 2 */
#endif
/* 466 */   NdrFcShort( 0x19a ),    /* Type Offset=410 */
/* 468 */   0x53,       /* FC_RETURN_PARAM_BASETYPE */
            0x8,        /* FC_LONG */

            0x0
        }
    };

static const MIDL_FORMAT_STRING __MIDLFormatString =
    {
        0,
        {
            0x11, 0xc,  /* FC_RP [alloced_on_stack] [simple_pointer] */
/*  2 */    0x8,        /* FC_LONG */
            0x5c,       /* FC_PAD */
/*  4 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/*  6 */
            0x22,       /* FC_C_CSTRING */
            0x5c,       /* FC_PAD */
/*  8 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 10 */    0x2,        /* FC_CHAR */
            0x5c,       /* FC_PAD */
/* 12 */
            0x11, 0x0,  /* FC_RP */
/* 14 */    NdrFcShort( 0x2 ),  /* Offset= 2 (16) */
/* 16 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 18 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 20 */    NdrFcShort( 0x21 ), /* 33 */
/* 22 */
            0x11, 0x0,  /* FC_RP */
/* 24 */    NdrFcShort( 0x2 ),  /* Offset= 2 (26) */
/* 26 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 28 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 30 */    NdrFcShort( 0x21 ), /* 33 */
/* 32 */
            0x11, 0x0,  /* FC_RP */
/* 34 */    NdrFcShort( 0x2 ),  /* Offset= 2 (36) */
/* 36 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 38 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 40 */    NdrFcShort( 0x21 ), /* 33 */
/* 42 */
            0x11, 0x0,  /* FC_RP */
/* 44 */    NdrFcShort( 0x2 ),  /* Offset= 2 (46) */
/* 46 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 48 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 50 */    NdrFcShort( 0x11 ), /* 17 */
/* 52 */
            0x11, 0x0,  /* FC_RP */
/* 54 */    NdrFcShort( 0x2 ),  /* Offset= 2 (56) */
/* 56 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 58 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 60 */    NdrFcShort( 0x21 ), /* 33 */
/* 62 */
            0x11, 0x0,  /* FC_RP */
/* 64 */    NdrFcShort( 0x2 ),  /* Offset= 2 (66) */
/* 66 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 68 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 70 */    NdrFcShort( 0x21 ), /* 33 */
/* 72 */
            0x11, 0x0,  /* FC_RP */
/* 74 */    NdrFcShort( 0x2 ),  /* Offset= 2 (76) */
/* 76 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 78 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 80 */    NdrFcShort( 0x21 ), /* 33 */
/* 82 */
            0x11, 0x0,  /* FC_RP */
/* 84 */    NdrFcShort( 0x2 ),  /* Offset= 2 (86) */
/* 86 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 88 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 90 */    NdrFcShort( 0xd ),  /* 13 */
/* 92 */
            0x11, 0x0,  /* FC_RP */
/* 94 */    NdrFcShort( 0x2 ),  /* Offset= 2 (96) */
/* 96 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 98 */    0x40,       /* 64 */
            0x0,        /* 0 */
/* 100 */   NdrFcShort( 0xd ),  /* 13 */
/* 102 */
            0x11, 0x0,  /* FC_RP */
/* 104 */   NdrFcShort( 0x2 ),  /* Offset= 2 (106) */
/* 106 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 108 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 110 */   NdrFcShort( 0xd ),  /* 13 */
/* 112 */
            0x11, 0x0,  /* FC_RP */
/* 114 */   NdrFcShort( 0x2 ),  /* Offset= 2 (116) */
/* 116 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 118 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 120 */   NdrFcShort( 0x50 ), /* 80 */
/* 122 */
            0x11, 0x0,  /* FC_RP */
/* 124 */   NdrFcShort( 0x2 ),  /* Offset= 2 (126) */
/* 126 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 128 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 130 */   NdrFcShort( 0x21 ), /* 33 */
/* 132 */
            0x11, 0x0,  /* FC_RP */
/* 134 */   NdrFcShort( 0x2 ),  /* Offset= 2 (136) */
/* 136 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 138 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 140 */   NdrFcShort( 0x21 ), /* 33 */
/* 142 */
            0x11, 0x0,  /* FC_RP */
/* 144 */   NdrFcShort( 0x2 ),  /* Offset= 2 (146) */
/* 146 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 148 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 150 */   NdrFcShort( 0x21 ), /* 33 */
/* 152 */
            0x11, 0x0,  /* FC_RP */
/* 154 */   NdrFcShort( 0x2 ),  /* Offset= 2 (156) */
/* 156 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 158 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 160 */   NdrFcShort( 0x11 ), /* 17 */
/* 162 */
            0x11, 0x0,  /* FC_RP */
/* 164 */   NdrFcShort( 0x2 ),  /* Offset= 2 (166) */
/* 166 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 168 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 170 */   NdrFcShort( 0x11 ), /* 17 */
/* 172 */
            0x11, 0x0,  /* FC_RP */
/* 174 */   NdrFcShort( 0x2 ),  /* Offset= 2 (176) */
/* 176 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 178 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 180 */   NdrFcShort( 0x21 ), /* 33 */
/* 182 */
            0x11, 0x0,  /* FC_RP */
/* 184 */   NdrFcShort( 0x2 ),  /* Offset= 2 (186) */
/* 186 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 188 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 190 */   NdrFcShort( 0x11 ), /* 17 */
/* 192 */
            0x11, 0x0,  /* FC_RP */
/* 194 */   NdrFcShort( 0x2 ),  /* Offset= 2 (196) */
/* 196 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 198 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 200 */   NdrFcShort( 0x21 ), /* 33 */
/* 202 */
            0x11, 0x0,  /* FC_RP */
/* 204 */   NdrFcShort( 0x2 ),  /* Offset= 2 (206) */
/* 206 */
            0x22,       /* FC_C_CSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 208 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 210 */   NdrFcShort( 0x50 ), /* 80 */
/* 212 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 214 */   0x5,        /* FC_WCHAR */
            0x5c,       /* FC_PAD */
/* 216 */
            0x11, 0x8,  /* FC_RP [simple_pointer] */
/* 218 */
            0x25,       /* FC_C_WSTRING */
            0x5c,       /* FC_PAD */
/* 220 */
            0x11, 0x0,  /* FC_RP */
/* 222 */   NdrFcShort( 0x2 ),  /* Offset= 2 (224) */
/* 224 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 226 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 228 */   NdrFcShort( 0x21 ), /* 33 */
/* 230 */
            0x11, 0x0,  /* FC_RP */
/* 232 */   NdrFcShort( 0x2 ),  /* Offset= 2 (234) */
/* 234 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 236 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 238 */   NdrFcShort( 0x21 ), /* 33 */
/* 240 */
            0x11, 0x0,  /* FC_RP */
/* 242 */   NdrFcShort( 0x2 ),  /* Offset= 2 (244) */
/* 244 */
            0x22,       /* FC_C_CSTRING */

0x44,       /* FC_STRING_SIZED */
/* 246 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 248 */   NdrFcShort( 0x21 ), /* 33 */
/* 250 */
            0x11, 0x0,  /* FC_RP */
/* 252 */   NdrFcShort( 0x2 ),  /* Offset= 2 (254) */
/* 254 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 256 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 258 */   NdrFcShort( 0x11 ), /* 17 */
/* 260 */
            0x11, 0x0,  /* FC_RP */
/* 262 */   NdrFcShort( 0x2 ),  /* Offset= 2 (264) */
/* 264 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 266 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 268 */   NdrFcShort( 0x21 ), /* 33 */
/* 270 */
            0x11, 0x0,  /* FC_RP */
/* 272 */   NdrFcShort( 0x2 ),  /* Offset= 2 (274) */
/* 274 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 276 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 278 */   NdrFcShort( 0x21 ), /* 33 */
/* 280 */
            0x11, 0x0,  /* FC_RP */
/* 282 */   NdrFcShort( 0x2 ),  /* Offset= 2 (284) */
/* 284 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 286 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 288 */   NdrFcShort( 0x21 ), /* 33 */
/* 290 */
            0x11, 0x0,  /* FC_RP */
/* 292 */   NdrFcShort( 0x2 ),  /* Offset= 2 (294) */
/* 294 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 296 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 298 */   NdrFcShort( 0xd ),  /* 13 */
/* 300 */
            0x11, 0x0,  /* FC_RP */
/* 302 */   NdrFcShort( 0x2 ),  /* Offset= 2 (304) */
/* 304 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 306 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 308 */   NdrFcShort( 0xd ),  /* 13 */
/* 310 */
            0x11, 0x0,  /* FC_RP */
/* 312 */   NdrFcShort( 0x2 ),  /* Offset= 2 (314) */
/* 314 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 316 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 318 */   NdrFcShort( 0xd ),  /* 13 */
/* 320 */
            0x11, 0x0,  /* FC_RP */
/* 322 */   NdrFcShort( 0x2 ),  /* Offset= 2 (324) */
/* 324 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 326 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 328 */   NdrFcShort( 0x50 ), /* 80 */
/* 330 */
            0x11, 0x0,  /* FC_RP */
/* 332 */   NdrFcShort( 0x2 ),  /* Offset= 2 (334) */
/* 334 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 336 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 338 */   NdrFcShort( 0x21 ), /* 33 */
/* 340 */
            0x11, 0x0,  /* FC_RP */
/* 342 */   NdrFcShort( 0x2 ),  /* Offset= 2 (344) */
/* 344 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 346 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 348 */   NdrFcShort( 0x21 ), /* 33 */
/* 350 */
            0x11, 0x0,  /* FC_RP */
/* 352 */   NdrFcShort( 0x2 ),  /* Offset= 2 (354) */
/* 354 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 356 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 358 */   NdrFcShort( 0x21 ), /* 33 */
/* 360 */
            0x11, 0x0,  /* FC_RP */
/* 362 */   NdrFcShort( 0x2 ),  /* Offset= 2 (364) */
/* 364 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 366 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 368 */   NdrFcShort( 0x11 ), /* 17 */
/* 370 */
            0x11, 0x0,  /* FC_RP */
/* 372 */   NdrFcShort( 0x2 ),  /* Offset= 2 (374) */
/* 374 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 376 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 378 */   NdrFcShort( 0x11 ), /* 17 */
/* 380 */
            0x11, 0x0,  /* FC_RP */
/* 382 */   NdrFcShort( 0x2 ),  /* Offset= 2 (384) */
/* 384 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 386 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 388 */   NdrFcShort( 0x21 ), /* 33 */
/* 390 */
            0x11, 0x0,  /* FC_RP */
/* 392 */   NdrFcShort( 0x2 ),  /* Offset= 2 (394) */
/* 394 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 396 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 398 */   NdrFcShort( 0x11 ), /* 17 */
/* 400 */
            0x11, 0x0,  /* FC_RP */
/* 402 */   NdrFcShort( 0x2 ),  /* Offset= 2 (404) */
/* 404 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 406 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 408 */   NdrFcShort( 0x21 ), /* 33 */
/* 410 */
            0x11, 0x0,  /* FC_RP */
/* 412 */   NdrFcShort( 0x2 ),  /* Offset= 2 (414) */
/* 414 */
            0x25,       /* FC_C_WSTRING */
            0x44,       /* FC_STRING_SIZED */
/* 416 */   0x40,       /* 64 */
            0x0,        /* 0 */
/* 418 */   NdrFcShort( 0x50 ), /* 80 */

            0x0
        }
    };


WINDSGBL.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      WINDSGBL.H
//
//  Description
//      Global declarations for providers communicating with the WINDS Sample
//      Server Messaging Host.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _WINDSGBL_H
#define _WINDSGBL_H

///////////////////////////////////////////////////////////////////////////////
// General constants
#ifdef UNICODE
#define WINDS_RPC_STRING        unsigned short *
#else
#define WINDS_RPC_STRING        unsigned char *
#endif

// WinDS string for the RPC manual binding functions
// The WinDS service uses RPC over the NAMED PIPES protocols and the
// especific end-points for the protocol are listed below: One for the remote
// administrator and one for the MAPI providers
#define WINDS_RPC_PROTOCOL              TEXT("ncacn_np")
#define WINDS_ADMIN_RPC_ENDPOINT        TEXT("\\pipe\\WINDS-ADMIN")
#define WINDS_PROVIDERS_RPC_ENDPOINT    TEXT("\\pipe\\WINDS")

// WinDS registry key for store permanent information
#define WINDS_ADMIN_REGISTRY_KEY        TEXT("Software\\Microsoft\\PSS MAPI Samples\\WinDS Remote Administrator")
#define WINDS_SERVICE_REGISTRY_KEY      TEXT("System\\CurrentControlSet\\Services\\WindsMessagingHost")

// WinDS administrator subkeys
#define SERVER_SUB_KEY                  TEXT("Default Server")

// WinDS service subkeys
#define DATA_FILE_SUB_KEY               TEXT("Data File")

// Name of the address type of the user in the system and for the transports to register for.
#define WINDS_ADDRESS_TYPE                      TEXT("WINDS")

// Name of the Mailslot we use to send notifications to in the client machine
#define AB_WINDS_NOTIFICATION_MAILSLOT          TEXT("WINDS_AB_NOTIFICATIONS")
#define XP_WINDS_NOTIFICATION_MAILSLOT          TEXT("WINDS_XP_NOTIFICATIONS")
#define MS_WINDS_NOTIFICATION_MAILSLOT          TEXT("WINDS_MS_NOTIFICATIONS")
#define ADMIN_WINDS_NOTIFICATION_MAILSLOT       TEXT("WINDS_ADMIN_NOTIFICATIONS")
#define CLIENT_MAILSLOT_SINK_NAME_FORMAT        TEXT("\\\\.\\mailslot\\%s\\%x")
#define SERVER_MAILSLOT_SINK_NAME_FORMAT        TEXT("\\\\%s\\mailslot\\%s\\%x")

// Bulk download/upload pipe name
#define PIPE_NAME_FORMAT                        TEXT("%s\\pipe\\%d")

// Handy buffer size
#define IO_BUFFERSIZE                   1024

#define WINDS_UNICODE                   0x80000000
#define WINDS_ADMINISTRATOR             0x40000000

#define HIDE_IN_DIR                     0x0001
#define OBJECT_DISABLED                 0x0002

#define ADMIN_RIGHTS                    0x0010
#define DL_OWNED                        0x0010

// Field sizes in the object properties structures
#define MAX_ALIAS_SIZE      16
#define MAX_STRING_SIZE     32
#define MAX_PHONE_SIZE      12
#define MAX_COMMENT_SIZE    79
#define MAX_PASSWORD_SIZE   MAX_ALIAS_SIZE

// Constants for the transports
#define TRANSPORT_MESSAGE_EID_SIZE              MAX_ALIAS_SIZE
#define ENTRYID_DATA_FIELD_SIZE                 MAX_ALIAS_SIZE

enum
{
    ROOT_CONTAINER_ID       = 0xFFFFFFFF,
    GATEWAY_CONTAINERS_ID   = 0xFFFFFFFE,
    FAX_CONTAINER_ID        = 0xFFFFFFFD,   // Don't change the order of this (see STORAGE.CPP (ReadABContainerInfo) in the WINDS project)
    SMTP_CONTAINER_ID       = 0xFFFFFFFC,
    EXCHANGE_CONTAINER_ID   = 0xFFFFFFFB,
    GAL_CONTAINER_ID        = 0xFFFFFFFA
};

typedef enum _WINDS_AB_OBJTYPE
{
    UNDEFINED_OBJECT_TYPE       = 0,
    SERVER_USER_MAILBOX         = 1,    // Don't change the order or the values
    SERVER_DISTRIBUTION_LIST    = 2,
    GATEWAY_RECIPIENT           = 3,
    PUBLIC_FOLDER               = 4
} WINDS_AB_OBJTYPE;

typedef struct _PUBLIC_FOLDER_INFO_A
{
    char        szFolderName[MAX_STRING_SIZE+4];
    char        szComments[MAX_COMMENT_SIZE+4];
    DWORD       dwObjID;
    FILETIME    ftCreationTime;
    FILETIME    ftLastModTime;
    DWORD       dwMessageStorageSize;
    DWORD       dwMessageCount;
    DWORD       pdwMessagesIDs;
} PUBLIC_FOLDER_INFO_A, *PPUBLIC_FOLDER_INFO_A;

typedef struct _PUBLIC_FOLDER_INFO_W
{
    WCHAR       szFolderName[MAX_STRING_SIZE+4];
    WCHAR       szComments[MAX_COMMENT_SIZE+4];
    DWORD       dwObjID;
    FILETIME    ftCreationTime;
    FILETIME    ftLastModTime;
    DWORD       dwMessageStorageSize;
    DWORD       dwMessageCount;
    DWORD       pdwMessagesIDs;
} PUBLIC_FOLDER_INFO_W, *PPUBLIC_FOLDER_INFO_W;

typedef struct _PF_MESSAGE_PROPERTIES_A
{
    char    szAuthor[MAX_ALIAS_SIZE+4];
    DWORD   dwObjID;
} PF_MESSAGE_PROPERTIES_A, *PPF_MESSAGE_PROPERTIES_A;

typedef struct _PF_MESSAGE_PROPERTIES_W
{
    WCHAR   szAuthor[MAX_ALIAS_SIZE+4];
    DWORD   dwObjID;
} PF_MESSAGE_PROPERTIES_W, *PPF_MESSAGE_PROPERTIES_W;

typedef struct _MAILBOX_INFO_A
{
    char    szMailboxName[MAX_ALIAS_SIZE+1];
    char    szFullName[MAX_STRING_SIZE+1];
    char    szJobTitle[MAX_STRING_SIZE+1];
    char    szOffice[MAX_STRING_SIZE+1];
    char    szPhone[MAX_PHONE_SIZE+1];
    char    szAltPhone[MAX_PHONE_SIZE+1];
    char    szFax[MAX_PHONE_SIZE+1];
    char    szPassword[MAX_ALIAS_SIZE+1];
    char    szCompany[MAX_STRING_SIZE+1];
    char    szDepartment[MAX_STRING_SIZE+1];
    char    szComments[MAX_COMMENT_SIZE+1];
    char    szManagerName[MAX_STRING_SIZE+1];
    char    szManagerAlias[MAX_STRING_SIZE+1];
    DWORD   dwManagerID;
    DWORD   dwObjID;
    DWORD   dwFlags;
} MAILBOX_INFO_A, *PMAILBOX_INFO_A;

typedef struct _MAILBOX_INFO_W
{
    WCHAR   szMailboxName[MAX_ALIAS_SIZE+1];
    WCHAR   szFullName[MAX_STRING_SIZE+1];
    WCHAR   szJobTitle[MAX_STRING_SIZE+1];
    WCHAR   szOffice[MAX_STRING_SIZE+1];
    WCHAR   szPhone[MAX_PHONE_SIZE+1];
    WCHAR   szAltPhone[MAX_PHONE_SIZE+1];
    WCHAR   szFax[MAX_PHONE_SIZE+1];
    WCHAR   szPassword[MAX_ALIAS_SIZE+1];
    WCHAR   szCompany[MAX_STRING_SIZE+1];
    WCHAR   szDepartment[MAX_STRING_SIZE+1];
    WCHAR   szComments[MAX_COMMENT_SIZE+1];
    WCHAR   szManagerName[MAX_STRING_SIZE+1];
    WCHAR   szManagerAlias[MAX_STRING_SIZE+1];
    DWORD   dwManagerID;
    DWORD   dwObjID;
    DWORD   dwFlags;
} MAILBOX_INFO_W, *PMAILBOX_INFO_W;

typedef struct _DIST_LIST_INFO_A
{
    char                szDLAlias[MAX_ALIAS_SIZE+1];
    char                szDLFullName[MAX_STRING_SIZE+1];
    DWORD               dwObjID;
    DWORD               dwFlags;
    char                szOwnerAlias[MAX_ALIAS_SIZE+1];
    char                szOwnerName[MAX_STRING_SIZE+1];
    DWORD               dwOwnerID;
    char                szComments[MAX_COMMENT_SIZE+1];
    DWORD               dwMemberCount;
    LPVOID              pMembers;
} DIST_LIST_INFO_A, *PDIST_LIST_INFO_A;

typedef struct _DIST_LIST_INFO_W
{
    WCHAR               szDLAlias[MAX_ALIAS_SIZE+1];
    WCHAR               szDLFullName[MAX_STRING_SIZE+1];
    DWORD               dwObjID;
    DWORD               dwFlags;
    WCHAR               szOwnerAlias[MAX_ALIAS_SIZE+1];
    WCHAR               szOwnerName[MAX_STRING_SIZE+1];
    DWORD               dwOwnerID;
    WCHAR               szComments[MAX_COMMENT_SIZE+1];
    DWORD               dwMemberCount;
    LPVOID              pMembers;
} DIST_LIST_INFO_W, *PDIST_LIST_INFO_W;

// UNICODE/ANSI macro for the structures
#ifdef UNICODE
#error WINDS: UNICODE has not been implemented yet. IrvingD 9/11/95
#define MAILBOX_INFO                MAILBOX_INFO_W
#define PMAILBOX_INFO               PMAILBOX_INFO_W
#define DIST_LIST_INFO              DIST_LIST_INFO_W
#define PDIST_LIST_INFO             PDIST_LIST_INFO_W
#define PUBLIC_FOLDER_INFO          PUBLIC_FOLDER_INFO_W
#define PPUBLIC_FOLDER_INFO         PPUBLIC_FOLDER_INFO_W
#define PF_MESSAGE_PROPERTIES       PF_MESSAGE_PROPERTIES_W
#define PPF_MESSAGE_PROPERTIES      PPF_MESSAGE_PROPERTIES_W
#else
#define MAILBOX_INFO                MAILBOX_INFO_A
#define PMAILBOX_INFO               PMAILBOX_INFO_A
#define DIST_LIST_INFO              DIST_LIST_INFO_A
#define PDIST_LIST_INFO             PDIST_LIST_INFO_A
#define PUBLIC_FOLDER_INFO          PUBLIC_FOLDER_INFO_A
#define PPUBLIC_FOLDER_INFO         PPUBLIC_FOLDER_INFO_A
#define PF_MESSAGE_PROPERTIES       PF_MESSAGE_PROPERTIES_A
#define PPF_MESSAGE_PROPERTIES      PPF_MESSAGE_PROPERTIES_A
#endif

typedef union _WINDS_OBJS_INFO
{
    DWORD               dwFlags;
    MAILBOX_INFO        MB;
    DIST_LIST_INFO      DL;
    PUBLIC_FOLDER_INFO  PF;
} WINDS_OBJS_INFO;

typedef struct _AB_ENTRY_INFO
{
    WINDS_OBJS_INFO     Info;
    WINDS_AB_OBJTYPE    Type;
} AB_ENTRY_INFO, *PAB_ENTRY_INFO;

///////////////////////////////////////////////////////////////////////////////
// Message Transport Support

// Command numbers for telling the remote server what to do (from a transport)
typedef enum tagMID
{
    MSG_DOWNLOAD,
    MSG_DELETE,
    MSG_MOVE,
    OP_STARTED,
    OP_COMPLETE,
    OP_FAILED,
    GOODBYE,
    UNMARKED
} MID;

// Command message header

typedef union _MSG_HDR_INFO
{
    BYTE    EID[TRANSPORT_MESSAGE_EID_SIZE];
    ULONG   ulMsgLen;
} MSG_HDR_INFO;

typedef struct _MSG_HDR
{
    MID             ulMID;
    MSG_HDR_INFO    Info;
} MSG_HDR;

///////////////////////////////////////////////////////////////////////////////
// Client/Server notification support

enum
{
    WINDS_NOTIF_ON_USER         = 0x0001,
    WINDS_NOTIF_ON_XP           = 0x0002,
    WINDS_NOTIF_ON_AB           = 0x0004,
    WINDS_NOTIF_ON_MS           = 0x0008
};

typedef enum _WINDS_NOTIF_EVENT
{
    GENERAL_NOTIF_MIN           = 0x0000,
    LOGGED_USER_PROPS_CHANGED   = 0x0000,
    SERVER_IS_SHUTTING_DOWN     = 0x0001,
    SERVER_HAS_RESTARTED        = 0x0002,
    RESET_LINKS_WITH_SERVER     = 0x0003,
    GENERAL_NOTIF_MAX           = 0x000F,

    XP_NOTIF_MIN                = 0x0010,
    XP_NEW_MAIL_ARRIVED         = 0x0010,
    XP_NOTIF_MAX                = 0x00F0,

    AB_NOTIF_MIN                = 0x0100,
    AB_USER_ADDED               = 0x0100,
    AB_USER_MODIFIED            = 0x0200,
    AB_USER_DELETED             = 0x0300,
    AB_DL_ADDED                 = 0x0400,
    AB_DL_MODIFIED              = 0x0500,
    AB_DL_DELETED               = 0x0600,
    AB_GET_LOCAL_ABDATA_NOW     = 0x0700,
    AB_NOTIF_MAX                = 0x0F00,

    MS_NOTIF_MIN                = 0x1000,
    MS_MESSAGE_ADDED            = 0x1000,
    MS_MESSAGE_MODIFIED         = 0x2000,
    MS_MESSAGE_DELETED          = 0x3000,
    MS_FOLDER_ADDED             = 0x4000,
    MS_FOLDER_MODIFIED          = 0x5000,
    MS_FOLDER_DELETED           = 0x6000,
    MS_NOTIF_MAX                = 0xF000

} WINDS_NOTIF_EVENT;

typedef struct _WINDS_NOTIFICATION
{
    WINDS_NOTIF_EVENT   Event;
    FILETIME            ftEventTime;
    WINDS_OBJS_INFO     Info;
} WINDS_NOTIFICATION, *PWINDS_NOTIFICATION;

///////////////////////////////////////////////////////////////////////////////
// Generic exception class for C++'s throw and catch

class CException
{
public:
    CException()
    {
        m_hResult = E_FAIL;
    }
    CException (HRESULT hResult)
    {
        if (0 == hResult)
        {
            hResult = E_FAIL;
        }
        m_hResult = hResult;
    }
    inline HRESULT WINAPI GetError()
    {
        return m_hResult;
    }
private:
    HRESULT m_hResult;
};

#endif // _WINDSGBL_H

// End of file for WINDSGBL.H


WINDSRC.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by WINDSSRV.RC
//
#define IDD_WINDS_DIALOG                100
#define IDD_WARNING                     101
#define IDD_WINDS_APP                   102
#define IDD_INSTALL_OPTIONS             103
#define IDI_WINDSSERVICE                500
#define IDM_MENU                        501
#define IDC_OBJECT_LIST                 1001
#define IDC_INSTALL_SERVICE             1002
#define IDC_SERVICE_FILEPATH            1002
#define IDC_REMOVE                      1003
#define IDC_REMOVE_SERVICE              1003
#define IDC_AUTO_START                  1003
#define IDC_MANUAL_START                1004
#define IDC_START_NOW                   1005
#define IDM_FILE_EXIT                   40000
#define IDM_ABOUT                       40001
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40002
#define _APS_NEXT_CONTROL_VALUE         1006
#define _APS_NEXT_SYMED_VALUE           502
#endif
#endif


README.TXT

WINDS Remote Transport

SUMMARY
=======

The Remote.Xp sample is a remote transport provider (XPWDSR) implemented
in C++. This transport demonstrates the following extended MAPI features:

 - Support for the REMOTE interface for slow-link connection to a remote host.
   This includes implementation of the IMAPIFolder methods needed to interact
   with a client remote viewer application.
 - Support for deferred delivery mechanism for scheduled message submission to
   a remote host at a user-settable upload time.
 - Support for forced immediate connection and submission (using the
   FlushQueues method implemented in the IMAPIStatus object) in response to
   Deliver Mail Now and Transfer Mail in the Microsoft Exchange Client.
 - How to implement server-based messaging.
 - Real-world working mail transport which sends messages to a Windows NT
   workstation or server running the Windows Developer Support (WINDS) Sample
   Server Messaging Host included with this and other MAPI samples.
 - Use of the Transport Neutral Encapsulation Format (TNEF) interface to
   encapsulate ALL the message properties.
 - How to implement the IMAPIStatus interface for client access to transport
   status.
 - Multithread safe.
 - Configuration dialogs.
 - Generation of delivery and non-delivery reports.
 - Support for the profile Wizard for interactive profile configuration.
 - Support for the WINDS address type.
 - Support for background download and FlushQueues spooler interaction for
   spooler-marshalled remote connections.
 - Single binary file for Windows NT and Windows 95.

MORE INFORMATION
================

This program requires:

    Windows NT 3.51 (or later) or Windows 95,
    The MAPI 1.0 SDK included in the Win32 SDK,
    Microsoft Visual C++ version 2.1 (or later.)

Optionally, the Win32 SDK tools with the MIDL compiler.

This sample uses RPCs over named pipes to establish connections with the
remote server. For convenience to customers without the full Win32 SDK, we
have provided the MIDL compiler-generated C and header files (WINDS.H,
WINDS_S.C, and WINDS_C.C) along with the IDL and ACF files. If you make changes
to the IDL or ACF, you'll NEED to recompile them using the MIDL compiler
available only in the Win32 SDK and Visual C++ 4.0 and later.

Building the Sample Code
------------------------

The sample remote transport provider has only been compiled and tested under
Windows NT 3.51 and Windows 95, there are no plans for a Windows 3.1x (16-bit)
release of this sample.

Users should update the project file dependencies as soon as the files are
copied to your machine. Use the PROJECTS.UPDATE DEPENDENCIES options in the
Visual C++ Developer Studio.

This sample was developed using Microsoft Visual C++ 2.x. Its associated
makefile is XPWDSR32.MAK. Traces can be enabled for Release builds by defining
ENABLE_DEBUG_OUTPUT in the preprocessor symbols define in the project settings
menu.

A command-line compiler-independent MAKE file has been provided for users of
command-line tools.

Configuration
-------------

Use the MERGEINI.EXE utility from the MAPI PDK to merge this transport's
message service INF file, XPWDSR.INF, with the MAPISVC.INF file:

    MERGEINI  {full-path}\XPWDSR.INF -m -q

for example:

    MERGEINI  C:\SAMPLES\MAPI\XPWDSR\XPWDSR.INF -m -q

You will then need to configure a profile that includes this transport. When
you create a new profile, the Profile Wizard will detect this transport and set
the initial properties through the wizard pages.

XPWDSR has two property sheets for interactive configuration which are
accessible through the service provider logon, through the ServiceEntry call
and through the IMAPIStatus::SettingsDialog method. The sample also defines
custom properties for the configuration options so they may be set and
retrieved programmatically.

User Account Configuration Page
-------------------------------

Server Name: This is the network UNC (Universal Naming Convention) name of
the machine where the user account is. The server machine MUST be running the
WINDS Sample Server Messaging Host application prior to the provider setup.

Mailbox Name: The mailbox name is the name of the account on the remote server.
To select a mailbox or change the current one, click on Browse and select a
name from the list that will appear. Once selected, you will be asked to enter
the password for the mailbox. For new mailboxes, the default password is
PASSWORD. If the list of mailboxes is empty or you don't find the one for you,
then a new account must be created on the server program. This field cannot be
edited manually by the user.

Full Name: This is the complete name of the owner of the mailbox selected. This
field is updated automatically when a new mailbox is selected. This information
cannot be edited manually.

Change Mailbox Password: Click on this button to change the password for this
mailbox. You will be asked to type the old password, a new password, and then
confirm the new password.

Using Local Network (LAN): This is the option to use if the machine is directly
hooked to on the network there is a connection to the server machine at all
times. If this option is selected, when running, the provider will install
notification links with the remote system allowing the provider to update
internal data dynamically as the data changes on the server, and to react to
changes or commands from the server.

Remote - With Local Address Book Directory: This setting instructs the provider
to adjust itself to use local data and to defer communication with the server
until the user changes the connection mode. This mode is useful if the server
or the clientmachine are not on a persistent connection link with each other.
If this option is selected the provider will not know of changes that occur on
the server to cached data.

Message Transport Configuration Page
------------------------------------

Automatic Remote Uploads At: The daily time when the transport will submit
all the messages it has deferred. At this time it will connect to the server
and submit all the messages in a batch. The time must be entered in the
24-hour format, i.e. 12:00 AM is 0:00. 10:30 PM is 22:30, etc.

When Connected: If this option is selected, after the messages have been
uploaded at the scheduled time, the transport will request to update the
message headers.

Note: Prior to installing this transport into a profile, the WINDS Sample
Server Messaging Host should be running in the remote (or local) computer.
See the readme file in the above sample for instructions on compiling,
installing, and using it.

Address Format
--------------

This transport supports the address type WINDS. When creating new one-off
entries in an address book (i.e. the Personal Address Book), this is the
value for the Address Type field. Email addresses for this transport look
like this:

    \\server-name\mailbox-name

The backslashes are literals, while the server-name and mailbox-name should
be replaced with the server and mailbox name you wish to send to.

Note: When typing one-off addresses in the Exchange client, you must double
the backslashes, like this:

    [WINDS:\\\\myserver\\mymailbox]

This is because the client treats the backslash as an escape character.

Remote Access
-------------

The main difference between this transport and a non-remote transport is the
ability to schedule mail transmission and to preview the header information
of messages residing in a user's remote mailbox. Outgoing messages aren't
transmitted at time of submission, but are queued by the spooler until the
scheduled delivery time occurs. This is known as deferred submission. As long
as the message is deferred, it stays in the user's local message store and the
spooler retains responsibility. The spooler maintains an internal queue of
deferred messages which it empties at the scheduled upload time, passing each
message in sequence to the transport for delivery.

Inbound messages stay in the user's mailbox on the remote server until
explicitly downloaded. This transport supports the Remote Viewer semantics,
where the user can request the transport to connect to the server and
retrieve a table of mail headers of messages in the user's remote mailbox. The
remote viewer lets the user mark those rows of the header table he wishes to
download. Rows are marked for move, delete, or copy operations according to
which operation the user wants to perform on the corresponding message.
Downloads are started explicitly, not scheduled, since the user has to
explicitly mark the rows of the table that are to be downloaded.

The download table is a contents table of a MAPI folder that is returned by
the IMAPIStatus object. This folder has no objects in it, but only exists to
provide the contents table. When the user marks messages for download in the
Remote Viewer, the corresponding row in the table has the PR_MSG_STATUS
property set to a bitmask indicating the requested operation. To obtain the
data for the header table, the transport makes an RPC to the server
requesting the name of a named pipe. The server creates the pipe and
returns the name. The transport then opens its end of the pipe and streams
the actual data over it. This data is used to populate the contents table.

Download Semantics
------------------

A user request to start downloading marked messages causes the transport's
IMAPIStatus::ValidateState method to be called with the
PROCESS_XP_HEADER_CACHE flag passed. At this point the download table is
walked and each marked entry is added to a list of messages to be
downloaded. When the list has been constructed, an RPC is made to the server
to obtain the name of a named pipe over which to stream the data.

The transport opens the pipe and requests the first message in the to-download
list. The transport uses a simple form of handshaking: control messages are
passed first to tell the server what the operation is, ACK the request, and get
the size of the following stream. Transmission errors can be reported in these
messages also. An error causes the message-in-transit to be dropped and the
next one started.

When a message is downloaded, its storage is backed by a temporary file which
is created on the fly to hold the incoming stream. When the message stream
arrives successfully on the client side, its to-download list node is moved
to a just-downloaded list. If the user logs off the session before the
just-dowloaded list is processed, the downloaded file is kept in a to-do
directory which will be processed in the next session logon.

Processing the just-downloaded list occurs in the same manner as for a
non-deferred message; each message is pumped through the spooler's
StartMessage loop. The spooler is notified that new mail has arrived by
setting the STATUS_INBOUND_FLUSH in the PR_STATUS_CODE bit in the status table
row. This causes the spooler to start requesting messages from the XP (by
calling StartMessage). We stream the message in from the temp file, decode the
TNEF stream into the passed MAPI message object, remove the non-transmittable
properties, set the transport computed properties, delete the temporary file,
and remove that message's node from the just-downloaded list. When the list is
empty, we signal the spooler to stop requesting incoming messages by clearing
the STATUS_INBOUND_FLUSH bit and passing back the untouched message object in
StartMessage.

Upload Semantics
----------------

Uploading messages works in a similar fashion except that we initiate the
process based on the time of day. The user configuration specifies a daily
upload time which is saved in the profile section. The XP can be in several
states with respect to the upload operation. When the upload time arrives, the
state changes from WAITING to PROCESSING_TIMER_EVENT.

When a message is submitted by the user, SubmitMessage is called and the
message is passed to us by the spooler. If our state is WAITING, we return
without doing anything. In the subsequent call to EndMessage, we check the
state and if we're WAITING, pass back END_DONT_RESEND. This signals the
spooler to queue this message until later requested.

A timer event is initialized on logon with the interval between the current
time and the upload time. When the timer expires, the spooler is notified to
start submitting the earlier deferred messages from its internal queue. We get
called again at SubmitMessage and are passed each message that's dequeued.
In SubmitMessage, we check our state, recognize that we're being called this
time to send the message, take responsibility (PR_RESPONSIBILITY), and go ahead
and deliver it. We also change our status to STATUS_OUTBOUND_FLUSH to signal
the spooler to keep calling our SubmitMessage method as long as it's queue is
not empty. In the subsequent EndMessage, we check our state, realize that a
message has just been dequeued and sent, decrement our counter and pass back
0. When the spooler has dequeued the last message, it callls TranportNotify,
passing NOTIFY_END_OUTBOUND_FLUSH and we change state back to WAITING.

After the transport has finished uploading the messages at the scheduled time,
we also request to get a new table of message headers on the server. This table
is downloaded to the local file and if the remote viewer is displayed, the
table of contents is refreshed.

Aborting Message Delivery
-------------------------

Once deferred, a message can be aborted any time before the actual delivery.
This happens if the user opens or deletes the message before it's sent. The
XP gets called at TransportNotify with NOTIFY_ABORT_DEFERRED. The spooler
will remove this message from its internal queue and we won't be called at
SubmitMessage for it.

Debug Traces and Asserts
------------------------

This sample uses several output debug string functions in the Win32
environment to avoid attaching the DLL to any debugger. By default, the trace
messages are output to a debug terminal attached to COM1 with settings at
9600, N, 8, 1. The debug messages can also be written to a log file whose
default location is C:\MAPILOG.TXT. The file TRACES.CPP defines some macros
that can be easily modified for different communications settings, output port,
and log file name. It also implements a macro (ASSERT) and function that test
the validity of a given statement. If the assertion fails, the user/developer
has the opportunity to break into the debugger at the exact point where the
assertion occurred.

The debug routines are found in the TRACES.CPP and TRACES.H files.

To enable the TRACExx functions in the TRACES.CPP files, define the
ENABLE_DEBUG_OUTPUT macro during compilation. These functions work in DEBUG or
RELEASE builds. You can only enable/disable traces at compile time through
ENABLE_DEBUG_OUTPUT. There is no run time switch to enable or disable the
traces.

TRACES.CPP implements an ASSERT macro which test a evaluates an expression
expecting a TRUE result, i.e. ASSERT (expression) will interrupt execution if the expression is NOT TRUE (non-zero).

The ASSERT macro informs the line and source code file name where the assertion
failed and writes the result to a debug trace. The assert information is also
written to the log file.

Also the this file implements a variation of assert: ASSERTMSG (expression,
user-message) which behaves identical to ASSERT except that is the assertion
fails, the message string is printed along with the assertion line and file
information.

By default, if an assertion fails, a dialog will come up and interrupt
execution until a selection is made to break into the debugger or ignore the
assertion.


COMWINDS.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      COMWINDS.H
//
//  Description
//      Common remote call and client-side utility functions for provider
//      communicating with the WINDS messaging host.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _COMWINDS_H
#define _COMWINDS_H

#define STRICT
#include <WINDOWS.H>
#include <WINDOWSX.H>
#include <CTL3D.H>
#include <MAPIWIN.H>
#include <MAPISPI.H>
#include <MAPIUTIL.H>
#include <MAPICODE.H>
#include <MAPIVAL.H>
#include <MAPIWZ.H>

// Constants for communicating with the WINDS Server Messaging Host
#include "WINDSGBL.H"

// Header file for the trace and logging functions.
#include "TRACES.H"

// This header file is generated by the MIDL compiler
#include "WINDS.H"

#include "UISRVCS.H"

#ifdef UNICODE
#define RemoteChangePassword            RemoteChangePasswordW
#define RemoteLogonMailBox              RemoteLogonMailBoxW
#define RemoteLogonMailBoxAndSetNotif   RemoteLogonMailBoxAndSetNotifW
#define RemoteTerminateNotif            RemoteTerminateNotifW
#define RemoteValidateNotif             RemoteValidateNotifW
#define RemoteOpenMsgUploadPipe         RemoteOpenMsgUploadPipeW
#define RemoteSendMsgToAccount          RemoteSendMsgToAccountW
#define RemoteOpenMsgDownloadPipe       RemoteOpenMsgDownloadPipeW
#define RemoteOpenHeaderDownloadPipe    RemoteOpenHeaderDownloadPipeW
#define RemoteCheckNewMail              RemoteCheckNewMailW
#define RemoteOpenOneMsgDownloadPipe    RemoteOpenOneMsgDownloadPipeW
#define RemoteGetRecipientProps         RemoteGetRecipientPropsW
#define RemoteGetDistListProps          RemoteGetDistListPropsW
#else
#define RemoteChangePassword            RemoteChangePasswordA
#define RemoteLogonMailBox              RemoteLogonMailBoxA
#define RemoteLogonMailBoxAndSetNotif   RemoteLogonMailBoxAndSetNotifA
#define RemoteTerminateNotif            RemoteTerminateNotifA
#define RemoteValidateNotif             RemoteValidateNotifA
#define RemoteOpenMsgUploadPipe         RemoteOpenMsgUploadPipeA
#define RemoteSendMsgToAccount          RemoteSendMsgToAccountA
#define RemoteOpenMsgDownloadPipe       RemoteOpenMsgDownloadPipeA
#define RemoteOpenHeaderDownloadPipe    RemoteOpenHeaderDownloadPipeA
#define RemoteCheckNewMail              RemoteCheckNewMailA
#define RemoteOpenOneMsgDownloadPipe    RemoteOpenOneMsgDownloadPipeA
#define RemoteGetRecipientProps         RemoteGetRecipientPropsA
#define RemoteGetDistListProps          RemoteGetDistListPropsA
#endif // UNICODE

// Custom properties used by this service provider. These properties are
// non-transmittable and only used in the configuration and private
// profile section of this provider/service.
#define BASE_PROVIDER_ID                    0x6600  // Look at the comments in MAPITAGS.H

// Common Custom Properties for all WINDS service providers
#define PR_SMP_REMOTE_SERVER            PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0000))
#define PR_SMP_USER_NAME                PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0001))
#define PR_SMP_BROWSE_MB                PROP_TAG (PT_OBJECT,     (BASE_PROVIDER_ID + 0x0002))
#define PR_SMP_CHANGE_PASSWORD          PROP_TAG (PT_OBJECT,     (BASE_PROVIDER_ID + 0x0003))
#define PR_SMP_SESSION_UID              PROP_TAG (PT_BINARY,     (BASE_PROVIDER_ID + 0x0004))
#define PR_SMP_CONNECTION_TYPE          PROP_TAG (PT_LONG,       (BASE_PROVIDER_ID + 0x0005))

// Secured properties that users should not see by looking at the registry.
// On Windows NT the registry is secured by the system. On Windows 95, this
// properties are stored encrypted, along with the system's list of passwords
#define PR_SMP_MAILBOX_NAME         PROP_TAG (PT_TSTRING,    (PROP_ID_SECURE_MIN + 0x0001))
#define PR_SMP_MAILBOX_ID           PROP_TAG (PT_LONG,       (PROP_ID_SECURE_MIN + 0x0002))
#define PR_SMP_MAILBOX_PASSWORD     PROP_TAG (PT_TSTRING,    (PROP_ID_SECURE_MIN + 0x0003))

// Address books
#define PR_SMP_LUL_LAST_UPDATE      PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0020))
#define PR_SMP_LUL_FILENAME         PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0021))
#define PR_SMP_LUL_DOWNLOAD         PROP_TAG (PT_OBJECT,     (BASE_PROVIDER_ID + 0x0022))
#define PR_SMP_MANAGER_NAME_TBL     PROP_TAG (PT_OBJECT,     (BASE_PROVIDER_ID + 0x0023))
// Custom properties for IMailUser and IDistList objects. This properties are in the named
// property range so that PAB address books and other writeable address books may copy them.
// Address book service providers should not use the 0x6600-0x67FF property ID range for
// custom properties in the recipient objects. Instead user NAMED properties
#define PR_WINDS_AB_MANAGER_NAME        PROP_TAG (PT_TSTRING, 0x8000)
#define PR_WINDS_AB_MANAGER_ENTRYID     PROP_TAG (PT_BINARY,  0x8001)
#define PR_WINDS_AB_DL_OWNER_ACCT       PROP_TAG (PT_TSTRING, 0x8002)
#define PR_WINDS_AB_DL_OWNER_NAME       PROP_TAG (PT_TSTRING, 0x8003)
// Address entry named property names. They have to be UNICODE strings
#define WINDS_AB_MANAGER_NAME       L"WINDS AB - Manager Name"
#define WINDS_AB_MANAGER_EID        L"WINDS AB - Manager Entry ID"
#define WINDS_AB_DL_OWNER_ACCT      L"WINDS AB - DL Owner Account"
#define WINDS_AB_DL_OWNER_NAME      L"WINDS AB - DL Owner Name"

// Transports
#define PR_SMP_UPLOAD_TIME          PROP_TAG (PT_SYSTIME,    (BASE_PROVIDER_ID + 0x0040))
#define PR_SMP_HEADERS_FILENAME     PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0041))
#define PR_TMP_UPLOAD_TIME          PROP_TAG (PT_TSTRING,    (BASE_PROVIDER_ID + 0x0042))
#define PR_SMP_GET_HEADERS          PROP_TAG (PT_BOOLEAN,    (BASE_PROVIDER_ID + 0x0043))

// Values available for PR_SMP_CONNECTION_TYPE
typedef enum _CONNECTION_TYPE
{
    // Don't change the order of these unless the string resources ID's are modified accordingly
    LAN_CONNECT         = 1,
    OFFLINE_CONNECT     = 2,
    CONNECTION_BROKEN   = 3,
    NOT_AVAIL_NO_ABD    = 4
} CONNECTION_TYPE;

// Index properties into the logon property array of ALL providers using the WINDS server
enum
{
    SERVER_NAME             = 0,
    MAILBOX_NAME            = 1,
    MAILBOX_ID              = 2,
    USER_NAME               = 3,
    PASSWORD                = 4,
    NET_CON                 = 5,
    // Number of properties the WIZARD setup pages collect for the configuration of the WINDS providers
    WIZARD_PROPERTIES       = 5
};

// Number of controls in the configuration page of ALL WINDS Providers
#define NUM_CFG_USERINFO_PAGE_CTLS     13

// Structure passed to the configuration dialog.
typedef struct _CFGDLG
{
    HINSTANCE      hInst;      // Instance of provider's DLL for loading resources for the UI
    HWND           hWnd;       // Handle of parent window or NULL is not UI should be used
    LPSPropValue * ppProps;    // Property array with the configuration properties
    LPMAPISUP      pSupObj;    // IMAPISupport object for creating the UI property sheets
    ULONG          ulFlags;    // 0 or UI_READONLY for access control to the configuration values
    HANDLE         hUIMutex;   // Handle to the MUTEX to control exclusive access to the configuration UI
} CFGDLG, *PCFGDLG;

// This code is to protect on reentrancy in the function
// that display the configuration UI for the WINDS services
#define CONFIG_UI_MUTEX     TEXT("WINDS_SERVICE_CONFIG_UI_MUTEX")

// PR_AB_PROVIDER_ID is the GUID that represents this Address Book Provider.
// This GUID is available as a property in the hierarchy. The UID is hard coded.
// This number is generated only once per provider during the developement
// of it using the standard tools (GUIDGEN or UUIDGEN) in VC++ or the Win32 SDK.
// With this number you could identify your provider across multiple platforms.
// GUIDGEN: 0xe33c67a0, 0xc81f, 0x11ce, 0xb2, 0xe4, 0x0, 0xaa, 0x0, 0x51, 0xe, 0x3b
// This is the UID of the entry ID of objects in the WINDS address book provider.
#define AB_UID_PROVIDER { 0xe3, 0x3c, 0x67, 0xa0, \
                          0xc8, 0x1f, 0x11, 0xce, \
                          0xb2, 0xe4, 0x0,  0xaa, \
                          0x0,  0x51, 0xe,  0x3b }

// This is the UID in the entry ID's the a WINDS transport uses to open entries in
// the WINDS address book, through the support object.
// This UID is hard coded because we only allow one instance of our provider
// to be installed in a single profile at any time.
// GUIDGEN: 0x8B43A160, 0xDFAC, 0x11ce, 0xB2, 0xEC, 0x00, 0xAA, 0x00, 0x51, 0x0E, 0x3B
#define XP_WINDS_AB_ENTRIES { 0x8b, 0x43, 0xa1, 0x60, \
                              0xdf, 0xac, 0x11, 0xce, \
                              0xb2, 0xec, 0x0,  0xaa, \
                              0x0,  0x51, 0xe,  0x3b }

// This is the private entry ID structure for entries that a WINDS transport
// would like to open in the WINDS address book
typedef struct _PRIVATE_XP_ENTRYID
{
    // MAPI-required fields
    BYTE        abFlags[4];         // 4 bytes      // MAPI Flags
    MAPIUID     uidGlobal;          // 16 bytes     // UID Unique for XP entries
    // Provider-defined fields
    MAPIUID     uidWINDSEntries;    // 16 bytes     // UID Unique for WINDS entries in the WINDS address book
    char        szObjectAlias[34];  // 34 bytes
                                    // 70 Bytes Total
} PRIVATE_XP_ENTRYID;
#define     CB_PRIVATE_XP_EID       70      // Size of the structure above

// This is the subdirectory in the TEMP directoy where WINDS service
// providers create temporary data files
#define WINDS_DATA_DIRECTORY                TEXT("WINDS Messaging Services Data Files")
// This is the directory where the temporary files for downloaded messages are stored
#define WINDS_DOWNLOAD_DIR_NAME_FORMAT      TEXT("%s\\Pending Downloads - %s")
#define XP_MSG_FILE_PREFIX                  TEXT("XRM")
#define XP_MSG_FILE_SUFFIX                  TEXT("TMP")

// Variables and static data structure used by all WINDS providers
extern "C"
{
    extern HINSTANCE    ghInstance;
    extern TCHAR        szBlank[];
    extern TCHAR        szFilter[];
    extern TCHAR        szPhoneFilter[];
    extern TCHAR        szTimeFilter[];
    extern DTBLLABEL    DtLabel;
    extern DTBLGROUPBOX DtGroupBox;
    extern DTBLPAGE     DtPage;
    extern DTBLEDIT     DtObjAlias;
    extern DTBLEDIT     DtObjFullName;
    extern DTBLEDIT     DtUserName;
    extern DTBLEDIT     DtServer;
    extern DTBLEDIT     DtComment;
    extern DTCTL        UserConfigPage[];
};

int WINAPI PrivateMessageBox
                    (UINT                       ids,
                     HWND                       hOwnerWnd = NULL,
                     UINT                       uFlags = MB_OK | MB_ICONSTOP,
                     HINSTANCE                  hInst = NULL);
extern "C"
{
    HRESULT WINAPI BindToServer
                    (LPTSTR                     szServer);
    BOOL WINAPI IsWINDSServerAvailable
                    (LPTSTR                     szServerName);
    BOOL WINAPI PingRemoteServer
                    (HWND                       hOwnerWnd,
                     LPSPropValue               pProps);
    BOOL WINAPI IsValidServerName
                    (LPTSTR                     szServerName);
    HRESULT WINAPI DoServerLogon
                    (PMAILBOX_INFO              pMBInfo,
                     LPSPropValue               pProps,
                     LPPROFSECT                 pProfileObj,
                     BOOL                       fUIAllowed,
                     HWND                       hOwnerWnd,
                     BOOL                       fSetNotification,
                     DWORD                      dwNotifMask,
                     DWORD *                    pdwConnectID,
                     BOOL                       fOffLineOK);
    HRESULT WINAPI ChangeMBServerPassword
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     LPTSTR                     szOldPassword,
                     LPTSTR                     szNewPassword);
    HRESULT WINAPI TerminateServerNotifications
                    (LPTSTR                     szServerName,
                     DWORD                      dwConnectionID);
    HRESULT WINAPI ResetServerNotifications
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     DWORD                      dwNotifMask,
                     DWORD *                    pdwConnectionID);
    void WINAPI TerminateRemoteConnections
                    ();
    HRESULT WINAPI MergeConfigProps
                    (ULONG                      ulCfgProps,
                     LPSPropValue               pCfgProps,
                     LPSPropValue               pLogonProps);
    HRESULT WINAPI OpenServiceProfileSection
                    (LPMAPISUP                  pSupObj,
                     LPPROFSECT *               ppProfSectObj,
                     LPFREEBUFFER               pfnFreeBuffer);
    // Transport Providers
    HRESULT WINAPI OpenServerUploadPipe
                    (LPTSTR                     szServerName,
                     LPTSTR                     szSenderMailbox,
                     HANDLE                     hMsgFile,
                     LPTSTR                     szConnectionInfo,
                     BOOL *                     pfErrorInTheServer);
    HRESULT WINAPI SendMsgToAccount
                    (LPTSTR                     szServerName,
                     LPTSTR                     szRecipMailbox,
                     LPTSTR                     szHeader,
                     LPTSTR                     szConnectionInfo,
                     BOOL *                     pfErrorInTheServer);
    HRESULT WINAPI FinishUploadConnection
                    (LPTSTR                     szServerName,
                     LPTSTR                     szConnectionInfo);
    HRESULT WINAPI GetHeadersFromServer
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailbox,
                     LPTSTR                     szHeaderFile);
    HRESULT WINAPI OpenRemoteServerDownLoadPipe
                    (LPTSTR                     szServer,
                     LPTSTR                     szMBox,
                     HANDLE *                   phPipe);
    HRESULT WINAPI GetNextMailboxMsg
                    (LPTSTR                     szServer,
                     LPTSTR                     szMailbox,
                     LPTSTR                     szLocalMsgFile);
    HRESULT WINAPI CheckForPendingMessages
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     ULONG *                    pulMsgWaiting);
};

// CTL3D Dynamic Loading Support
void WINAPI PrivInitialize3DCtl
                    (HINSTANCE                  hInstance);
void WINAPI PrivUninitialize3DCtl
                    (HINSTANCE                  hInstance);

// Forward declarations
class CUIMAPIProp;

// Custom callback function prototype for the implementation
// and activation of IMAPIControl on display tables on property sheets.
typedef void (STDAPICALLTYPE BUTTONCALLBACK) (CUIMAPIProp * pObj, HWND hOwnerWnd, HINSTANCE hInstance);
typedef BUTTONCALLBACK *LPBUTTONCALLBACK;

class CUIMAPIProp : public IMAPIProp
{
public:
    inline STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj)
                    { *ppvObj = NULL;
                      HRESULT hResult = S_OK;
                      EnterCriticalSection (&m_csObj);
                      if (riid == IID_IMAPIProp || riid == IID_IUnknown)
                      {
                          *ppvObj = (LPVOID)this;
                          AddRef();
                      }
                      else
                      {
                          hResult = E_NOINTERFACE;
                      }
                      LeaveCriticalSection (&m_csObj);
                      return hResult; }
    inline STDMETHODIMP_(ULONG) AddRef
                    () { EnterCriticalSection (&m_csObj);
                         ++m_cRef; ULONG ulCount = m_cRef;
                         LeaveCriticalSection (&m_csObj);
                         return ulCount; }
    inline STDMETHODIMP_(ULONG) Release
                    () { EnterCriticalSection (&m_csObj);
                         ULONG ulCount = --m_cRef;
                         LeaveCriticalSection (&m_csObj);
                         if (!ulCount) { delete this; }
                         return ulCount;}
    inline STDMETHODIMP GetLastError
                    (HRESULT                    hResult,
                     ULONG                      ulFlags,
                     LPMAPIERROR *              ppMAPIError)
                    { return m_pImpObj->GetLastError (hResult, ulFlags, ppMAPIError); }
    inline STDMETHODIMP SaveChanges
                    (ULONG                      ulFlags)
                    { return S_OK; }
    inline STDMETHODIMP GetProps
                    (LPSPropTagArray            pPropTagArray,
                     ULONG                      ulFlags,
                     ULONG *                    pcValues,
                     LPSPropValue *             ppPropArray)
                    { return m_pImpObj->GetProps (pPropTagArray, ulFlags, pcValues, ppPropArray); }
    inline STDMETHODIMP GetPropList
                    (ULONG                      ulFlags,
                     LPSPropTagArray *          ppAllTags)
                    { return m_pImpObj->GetPropList (ulFlags, ppAllTags); }
    STDMETHODIMP OpenProperty
                    (ULONG                      ulPropTag,
                     LPCIID                     piid,
                     ULONG                      ulInterfaceOptions,
                     ULONG                      ulFlags,
                     LPUNKNOWN *                ppUnk);
    inline STDMETHODIMP SetProps
                    (ULONG                      cValues,
                     LPSPropValue               pPropArray,
                     LPSPropProblemArray *      ppProblems)
                    { return m_pImpObj->SetProps (cValues, pPropArray, ppProblems); }
    inline STDMETHODIMP DeleteProps
                    (LPSPropTagArray            pPropTagArray,
                     LPSPropProblemArray *      ppProblems)
                    { return m_pImpObj->DeleteProps (pPropTagArray, ppProblems); }
    inline STDMETHODIMP CopyTo
                    (ULONG                      ciidExclude,
                     LPCIID                     rgiidExclude,
                     LPSPropTagArray            pExcludeProps,
                     ULONG                      ulUIParam,
                     LPMAPIPROGRESS             pProgress,
                     LPCIID                     pInterface,
                     LPVOID                     pDestObj,
                     ULONG                      ulFlags,
                     LPSPropProblemArray *      ppProblems)
                    { return m_pImpObj->CopyTo (ciidExclude, rgiidExclude, pExcludeProps, ulUIParam, pProgress, pInterface, pDestObj, ulFlags, ppProblems); }
    inline STDMETHODIMP CopyProps
                    (LPSPropTagArray            pIncludeProps,
                     ULONG                      ulUIParam,
                     LPMAPIPROGRESS             pProgress,
                     LPCIID                     pInterface,
                     LPVOID                     pDestObj,
                     ULONG                      ulFlags,
                     LPSPropProblemArray *      ppProblems)
                    { return m_pImpObj->CopyProps (pIncludeProps, ulUIParam, pProgress, pInterface, pDestObj, ulFlags, ppProblems); }
    inline STDMETHODIMP GetNamesFromIDs
                    (LPSPropTagArray *          ppPropTags,
                     LPGUID                     pPropSetGuid,
                     ULONG                      ulFlags,
                     ULONG *                    pcPropNames,
                     LPMAPINAMEID **            pppPropNames)
                    { return MAPI_E_NO_SUPPORT; }
    inline STDMETHODIMP GetIDsFromNames
                    (ULONG                      cPropNames,
                     LPMAPINAMEID *             ppPropNames,
                     ULONG                      ulFlags,
                     LPSPropTagArray *          ppPropTags)
                    { return MAPI_E_NO_SUPPORT; }

///////////////////////////////////////////////////////////////////////////////
// Other methods private to this class
//
    inline void WINAPI SetTableData
                    (LPTABLEDATA                pTable)
                    { m_pTableData = pTable; m_pTableData->AddRef(); }
    inline void WINAPI SetPropCallBack
                    (ULONG                      ulPropTag,
                     LPBUTTONCALLBACK           pfnCallBack)
                    { m_ulCustomPropTag = ulPropTag; m_pfnCallBack = pfnCallBack; }

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CUIMAPIProp     (HINSTANCE                  hInstance,
                     LPALLOCATEBUFFER           pfnAllocateBuffer,
                     LPALLOCATEMORE             pfnAllocateMore,
                     LPFREEBUFFER               pfnFreeBuffer,
                     BOOL                       fReadOnly);
    ~CUIMAPIProp    () { m_pImpObj->Release(); m_pImpObj = NULL;
                         if (m_pTableData) { m_pTableData->Release(); }
                         DeleteCriticalSection (&m_csObj);}

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG               m_cRef;
    BOOL                m_fReadOnly;
    LPPROPDATA          m_pImpObj;
    ULONG               m_ulCustomPropTag;
    LPBUTTONCALLBACK    m_pfnCallBack;
    CRITICAL_SECTION    m_csObj;
public :
    LPTABLEDATA         m_pTableData;
    HINSTANCE           m_hInstance;
};

class CMAPIControl : public IMAPIControl
{
    inline STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj)
                    { *ppvObj = NULL;
                      HRESULT hResult = S_OK;
                      EnterCriticalSection (&m_csObj);
                      if (riid == IID_IMAPIControl || riid == IID_IUnknown)
                      {
                          *ppvObj = (LPVOID)this;
                          // Increase usage count of this object
                          AddRef();
                      }
                      else
                      {
                          hResult = E_NOINTERFACE;
                      }
                      LeaveCriticalSection (&m_csObj);
                      return hResult; }
    inline STDMETHODIMP_(ULONG) AddRef
                    () { EnterCriticalSection (&m_csObj);
                         ++m_cRef; ULONG ulCount = m_cRef;
                         LeaveCriticalSection (&m_csObj);
                         return ulCount; }
    inline STDMETHODIMP_(ULONG) Release
                    () { EnterCriticalSection (&m_csObj);
                         ULONG ulCount = --m_cRef;
                         LeaveCriticalSection (&m_csObj);
                         if (!ulCount) { delete this; }
                         return ulCount;}
    inline STDMETHODIMP GetLastError
                    (HRESULT                    hResult,
                     ULONG                      ulFlags,
                     LPMAPIERROR *              ppMAPIError)
                    { return MAPI_E_NO_SUPPORT; }
    inline STDMETHODIMP GetState
                    (ULONG                      ulFlags,
                     ULONG *                    pulState)
                    { *pulState = (m_fReadOnly ? MAPI_DISABLED : MAPI_ENABLED); return S_OK; }
    inline STDMETHODIMP Activate
                    (ULONG                      ulFlags,
                     ULONG                      ulUIParam)
                    { (*m_pfnCallBack)(m_pProp, (HWND)ulUIParam, m_pProp->m_hInstance); return S_OK; }

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CMAPIControl    (CUIMAPIProp *              pProp,
                     LPBUTTONCALLBACK           pfnCallBack,
                     BOOL                       fReadOnly)
                    { m_cRef = 1; m_pfnCallBack = pfnCallBack; m_fReadOnly = fReadOnly;
                      m_pProp = pProp; m_pProp->AddRef();
                      InitializeCriticalSection (&m_csObj); }
    ~CMAPIControl   ()
                    { m_pProp->Release();
                      DeleteCriticalSection (&m_csObj);}
///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG               m_cRef;
    LPBUTTONCALLBACK    m_pfnCallBack;
    BOOL                m_fReadOnly;
    CUIMAPIProp *       m_pProp;
    CRITICAL_SECTION    m_csObj;
};

#endif // _COMWINDS_H

// End of file for COMWINDS.H


COMWINDS.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      COMWINDS.CPP
//
//  Description
//      This file implement the RPC code to talk to the WINDS Sample Server
//      Messaging Host.
//      Here we also implement common code used by providers in the service
//      that talking to WINDS and that share similiar configuration UI.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "COMWINDS.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

typedef struct tagSELECTED_MAILBOX_INFO
{
    HANDLE  hPipe;
    HRESULT hLastError;
    LPTSTR  szMailBoxName;
    LPTSTR  szDisplayName;
    LPTSTR  szPassword;
    DWORD * pdwMailboxID;
} SELECTED_MAILBOX_INFO;

HINSTANCE ghUIControl = NULL;
typedef BOOL (WINAPI CTL3DFUNCTION) (HINSTANCE hInstance);
typedef CTL3DFUNCTION *LPCTL3DFUNCTION;
typedef BOOL (WINAPI CTL3DSUBCLASS) (HWND hDlg, DWORD dwFlags);
typedef CTL3DSUBCLASS *LPCTL3DSUBCLASS;

LPCTL3DFUNCTION pfn3DRegister = NULL, pfn3DUnregister = NULL, pfn3DAutoSubclass = NULL;
LPCTL3DSUBCLASS pfnDlgSubclassEx = NULL;

LPFREEBUFFER pfnMAPIFreeBuffer;

extern "C"
{
    void STDAPICALLTYPE BrowseMailboxes
                    (CUIMAPIProp *              pObj,
                     HWND                       hOwnerWnd,
                     HINSTANCE                  hInstance);
    void STDAPICALLTYPE ConfigChangePassword
                    (CUIMAPIProp *              pObj,
                     HWND                       hOwnerWnd,
                     HINSTANCE                  hInstance);
    HRESULT WINAPI ShowServerAccounts
                    (HWND                       hOwnerWnd,
                     HINSTANCE                  hInstance,
                     LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     LPTSTR                     szFullName,
                     LPTSTR                     szPassword,
                     DWORD *                    pdwMailboxID);
    BOOL CALLBACK PasswordDlgProc
                    (HWND                       hDlg,
                     UINT                       message,
                     WPARAM                     wParam,
                     LPARAM                     lParam);
    BOOL CALLBACK EnterMBPasswordDlgProc
                    (HWND                       hDlg,
                     UINT                       message,
                     WPARAM                     wParam,
                     LPARAM                     lParam);
    BOOL CALLBACK SelectMailBoxDlgProc
                    (HWND                       hDlg,
                     UINT                       message,
                     WPARAM                     wParam,
                     LPARAM                     lParam);
    void WINAPI TogglePage
                    (HWND                       hDlg,
                     UINT                       iPage,
                     BOOL                       fState);
    HRESULT WINAPI LogonServerMailbox
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     LPTSTR                     szDisplayName,
                     LPTSTR                     szPassword,
                     DWORD *                    pdwMailboxID);
};

HRESULT WINAPI LogonServerMailboxAndSetNotif
                    (LPTSTR                     szServerName,
                     LPTSTR                     szMailboxName,
                     LPTSTR                     szDisplayName,
                     LPTSTR                     szPassword,
                     DWORD *                    pdwMailboxID,
                     DWORD                      dwNotifMask,
                     DWORD &                    dwConnectID);

// Static data for the transport provider configuration property sheets
TCHAR szFilter[] = TEXT("*"); // "*" to allow any character
TCHAR szTimeFilter[] = TEXT("[0-9:]");
TCHAR szPhoneFilter[] = TEXT("[0-9-]");
TCHAR szBlank[] = TEXT("");

extern TCHAR gszProviderName[];

// Control description structures
DTBLLABEL       DtLabel      = { sizeof(DTBLLABEL),       fMapiUnicode};
DTBLGROUPBOX    DtGroupBox   = { sizeof(DTBLGROUPBOX),    fMapiUnicode};
DTBLPAGE        DtPage       = { sizeof(DTBLPAGE),        fMapiUnicode};
DTBLEDIT        DtServer     = { sizeof(DTBLEDIT),        fMapiUnicode, MAX_STRING_SIZE, PR_SMP_REMOTE_SERVER};
DTBLEDIT        DtUserName   = { sizeof(DTBLEDIT),        fMapiUnicode, MAX_STRING_SIZE, PR_SMP_USER_NAME};
DTBLEDIT        DtMBAlias    = { sizeof(DTBLEDIT),        fMapiUnicode, MAX_ALIAS_SIZE,  PR_SMP_MAILBOX_NAME};
DTBLBUTTON      DtBrowseMB   = { sizeof(DTBLBUTTON),      fMapiUnicode, PR_SMP_BROWSE_MB};
DTBLBUTTON      DtChangePW   = { sizeof(DTBLBUTTON),      fMapiUnicode, PR_SMP_CHANGE_PASSWORD};
DTBLRADIOBUTTON DtLANConnect = { sizeof(DTBLRADIOBUTTON), fMapiUnicode, 2, PR_SMP_CONNECTION_TYPE, LAN_CONNECT };
DTBLRADIOBUTTON DtREMConnect = { sizeof(DTBLRADIOBUTTON), fMapiUnicode, 2, PR_SMP_CONNECTION_TYPE, OFFLINE_CONNECT };

// Keys for field the we are going to update dynamically and we
// want MAPI's property pages to pick the changes on thes
#define ALIAS_KEY_VALUE     "UI_CONTROL_ALIAS"
#define ALIAS_KEY           ((LPBYTE)ALIAS_KEY_VALUE)
#define CB_ALIAS_KEY        sizeof(ALIAS_KEY_VALUE)

#define NAME_KEY_VALUE      "UI_CONTROL_NAME"
#define NAME_KEY            ((LPBYTE)NAME_KEY_VALUE)
#define CB_NAME_KEY         sizeof(NAME_KEY_VALUE)

// Description table for the controls
DTCTL UserConfigPage[] =
{
    { DTCT_PAGE,        0,           NULL,      0,            NULL,     0,                   &DtPage       },
    { DTCT_EDIT,        DT_REQUIRED, NULL,      0,            szFilter, IDC_SERVERNAME,      &DtServer     },
    { DTCT_EDIT,        DT_REQUIRED, ALIAS_KEY, CB_ALIAS_KEY, szFilter, IDC_MAILBOXNAME,     &DtMBAlias    },
    { DTCT_BUTTON,      0,           NULL,      0,            NULL,     IDC_BROWSE_MB,       &DtBrowseMB   },
    { DTCT_EDIT,        DT_REQUIRED, NAME_KEY,  CB_NAME_KEY,  szFilter, IDC_USER_NAME,       &DtUserName   },
    { DTCT_BUTTON,      0,           NULL,      0,            NULL,     IDC_CHANGE_PASSWORD, &DtChangePW   },
    { DTCT_RADIOBUTTON, DT_SET_IMMEDIATE,NULL,  0,            NULL,     IDC_CONNECT_ONLINE,  &DtLANConnect },
    { DTCT_RADIOBUTTON, DT_SET_IMMEDIATE,NULL,  0,            NULL,     IDC_CONNECT_OFFLINE, &DtREMConnect },
    { DTCT_LABEL,       0,           NULL,      0,            NULL,     IDC_STATIC1,         &DtLabel      },
    { DTCT_LABEL,       0,           NULL,      0,            NULL,     IDC_STATIC2,         &DtLabel      },
    { DTCT_GROUPBOX,    0,           NULL,      0,            NULL,     IDC_STATIC3,         &DtGroupBox   },
    { DTCT_GROUPBOX,    0,           NULL,      0,            NULL,     IDC_STATIC4,         &DtGroupBox   },
    { DTCT_GROUPBOX,    0,           NULL,      0,            NULL,     IDC_STATIC5,         &DtGroupBox   }
};

// Global data used in the WizardDlgProc function
LPMAPIPROP pPropObj = NULL;
BOOL fInitalizeControls, fDownloadMBList;

extern "C"
{
    HINSTANCE            ghInstance = NULL;
    WIZARDENTRY          WizardEntry;
    SERVICEWIZARDDLGPROC WizardDlgProc;
};

///////////////////////////////////////////////////////////////////////////////
//    IsWINDSServerAvailable()
//
//    Parameters
//      szServer        Pointer to a string with the name of the remote
//                      server to verify
//
//    Purpose
//      This functions makes remote procedure call to a server to make sure
//      the remote machine is available.
//      The remote server must be running the WINDS sample server messaging
//      host because that is the server procedure that will answer the
//      RPC call.
//
//    Return Value
//      TRUE if the server was found, FALSE otherwise.
//
BOOL WINAPI IsWINDSServerAvailable (LPTSTR szServer)
{
    // We the user didn't supply a server, then fail.
    if (!szServer)
    {
        TraceMessage ("IsWINDSServerAvailable: No server name?");
        return FALSE;
    }
    HCURSOR hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT));
    // Before we make an RPC call, we must initialize the RPC runtime
    // binding handle with the server
    HRESULT hResult = BindToServer (szServer);
    if (!hResult)
    {
        RpcTryExcept
        {
            // Call the server.
            hResult = RemoteIsServerRunning();
        }
        RpcExcept(1)
        {
            // If we got here is because there was an error while call was made
            // or when it was about to be made.
            hResult = RpcExceptionCode();
            if (RPC_S_SERVER_UNAVAILABLE == hResult)
            {
                hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
            }
            else
            {
                hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
            }
        }
        RpcEndExcept
        TraceResult ("IsWINDServerAvailable", hResult);
    }
    SetCursor (hCursor);
    return (S_OK == hResult ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//    ShowServerAccounts()
//
//    Parameters
//      hOwnerWnd       Handle to a parent window. If NULL we don't display a
//                      list of available mailboxes in the server.
//      hInstance       Handle to the instance of the DLL where the dialog
//                      resource is located
//      szServerName    Name of the server we are going to make an RPC call to
//      szMailboxName   Where we return the name of the mailbox selected.
//      szFullName      Where we return the name of the owner of the
//                      mailbox seleted.
//      szPassword      Where we return the password of the mailbox seleted
//      pdwMailboxID    Where we return the ID of the mailbox seleted
//
//    Purpose
//      This function displays a list of available mailboxes in the server.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI ShowServerAccounts (HWND        hOwnerWnd,
                                   HINSTANCE   hInstance,
                                   LPTSTR      szServerName,
                                   LPTSTR      szMailboxName,
                                   LPTSTR      szFullName,
                                   LPTSTR      szPassword,
                                   DWORD *     pdwMailboxID)
{
    ASSERT (hOwnerWnd);
    ASSERT (hInstance);
    ASSERT (szServerName);
    ASSERT (szMailboxName);
    ASSERT (szFullName);
    ASSERT (szPassword);
    ASSERT (pdwMailboxID);
    HRESULT hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    long lPipeNumber;
    HANDLE hPipe;
    TCHAR szPipeName[64];
    SELECTED_MAILBOX_INFO info = { 0 };
    RpcTryExcept
    {
        hResult = RemoteGetAllAccounts (&lPipeNumber);
        if (S_OK == hResult)
        {
            wsprintf (szPipeName, PIPE_NAME_FORMAT, szServerName, lPipeNumber);
            hPipe = CreateFile (szPipeName,
                                GENERIC_READ,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("ShowServerAccounts: Failed to open pipe to server", hResult);
            }
            else
            {
                info.hPipe = hPipe;
                info.hLastError = E_FAIL;
                info.szDisplayName = szFullName;
                info.szMailBoxName = szMailboxName;
                info.szPassword = szPassword;
                info.pdwMailboxID = pdwMailboxID;
                hResult = DialogBoxParam (hInstance,
                                          MAKEINTRESOURCE(IDD_SELECTMB),
                                          hOwnerWnd,
                                          SelectMailBoxDlgProc,
                                          (LPARAM)&info);
                if (-1 == hResult)
                {
                    hResult = info.hLastError;
                }
                else
                {
                    if (FALSE == hResult)
                    {
                        hResult = MAPI_E_USER_CANCEL;
                    }
                    else
                    {
                        hResult = S_OK;
                    }
                }
                CloseHandle (hPipe);
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("ShowServerAccounts", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    LogonServerMailbox()
//
//    Parameters
//      szServerName        Name of the server where are connecting to.
//      szMailboxName       Name of the mailbox we are attempting to access
//      szDisplayName       Name of the owner of the mailbox
//      szPassword          Password to access the mailbox
//      pdwMailboxID        Where we return the ID of the mailbox as stored
//                          on the server
//
//    Purpose
//      This function makes a connection to the remote server and makes sure
//      we can access it with the given password. Upon success, the remote
//      call returns us the full name of the owner of the mailbox and the
//      ID of the recipient.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI LogonServerMailbox (LPTSTR   szServerName,
                                   LPTSTR   szMailboxName,
                                   LPTSTR   szDisplayName,
                                   LPTSTR   szPassword,
                                   DWORD *  pdwMailboxID)
{
    ASSERT (szServerName);
    ASSERT (szMailboxName);
    ASSERT (szDisplayName);
    ASSERT (szPassword);
    ASSERT (pdwMailboxID);
    HRESULT hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteLogonMailBox ((WINDS_RPC_STRING)szMailboxName,
                                      (WINDS_RPC_STRING)szPassword,
                                      (WINDS_RPC_STRING)szDisplayName,
                                      pdwMailboxID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("LogonServerMailbox", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    LogonServerMailboxAndSetNotif()
//
//    Parameters
//      szServerName       Name of the server where are connecting to.
//      szMailboxName      Name of the mailbox we are attempting to access
//      szDisplayName      Name of the owner of the mailbox
//      szPassword         Password to access the mailbox
//      pdwMailboxID        Where we return the ID of the mailbox as stored
//                          on the server
//      dwNotifMask         Mask of events the loggin service is intersted in.
//      dwConnectID         Where we return the connection number the server
//                          assigned to us.
//
//    Purpose
//      In addition to what LogonServerMailbox() does, this function also
//      instructs the remote server to set up a notification link with us
//      to advise us of changes in the server's data or the server state.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI LogonServerMailboxAndSetNotif (LPTSTR    szServerName,
                                              LPTSTR    szMailboxName,
                                              LPTSTR    szDisplayName,
                                              LPTSTR    szPassword,
                                              DWORD *   pdwMailboxID,
                                              DWORD     dwNotifMask,
                                              DWORD &   dwConnectID)
{
    ASSERT (szServerName);
    ASSERT (szMailboxName);
    ASSERT (szDisplayName);
    ASSERT (szPassword);
    ASSERT (pdwMailboxID);

    // Get the computer name of us (the client) so that the server can
    // create the mailslot name appropiately.
    TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    HRESULT hResult;
    DWORD dwNameSize = MAX_COMPUTERNAME_LENGTH + 1;
    if (!GetComputerName (szComputerName, &dwNameSize))
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("LogonServerMailboxAndSetNotif: Failed to get the computer name", hResult);
    }
    else
    {
        hResult = BindToServer (szServerName);
    }
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteLogonMailBoxAndSetNotif ((WINDS_RPC_STRING)szMailboxName,
                                                 (WINDS_RPC_STRING)szPassword,
                                                 (WINDS_RPC_STRING)szDisplayName,
                                                 pdwMailboxID,
                                                 (WINDS_RPC_STRING)szComputerName,
                                                 dwNotifMask,
                                                 &dwConnectID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("LogonServerMailboxAndSetNotif", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    TerminateServerNotifications()
//
//    Parameters
//      szServerName           Name of the server we want to terminate
//                              connections with.
//      dwConnectionID          Connection ID assigned to us in that server
//
//    Purpose
//      Tell the server that this provider is shutting down an it show remove
//      it from the list of advise clients.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI TerminateServerNotifications (LPTSTR     szServerName,
                                             DWORD      dwConnectionID)
{
    // If the connection is zero, there is nothing to do here.
    if (0 == dwConnectionID)
    {
        return S_OK;
    }
    TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    HRESULT hResult;
    DWORD dwNameSize = MAX_COMPUTERNAME_LENGTH + 1;
    if (!GetComputerName (szComputerName, &dwNameSize))
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("TerminateServerNotifications: Failed to get the computer name", hResult);
    }
    else
    {
        hResult = BindToServer (szServerName);
    }
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteTerminateNotif ((WINDS_RPC_STRING)szComputerName, dwConnectionID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("TerminateServerNotifications", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    ResetServerNotifications()
//
//    Parameters
//      szServerName        Name of the remote server
//      szMailboxName       Name of the owner mailbox where the notifations
//                          are set
//      dwNotifMask         Mask of events the service is interested in.
//      pdwConnectionID     Where the server returns the new connection number
//
//    Purpose
//      This function is called in response to a notification sent by the
//      server to advise clients that they need to reset the notification
//      because the old connection is no longer valid.
//      The server send this notification periodically to make sure the advise
//      client he sending notifications to are still around.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI ResetServerNotifications (LPTSTR     szServerName,
                                         LPTSTR     szMailboxName,
                                         DWORD      dwNotifMask,
                                         DWORD *    pdwConnectionID)
{
    // The connections with the server is dead anyways so 0 the connection ID even we fail.
    *pdwConnectionID = 0;
    TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    HRESULT hResult;
    DWORD dwNameSize = MAX_COMPUTERNAME_LENGTH + 1;
    if (!GetComputerName (szComputerName, &dwNameSize))
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
        TraceResult ("ResetServerNotifications: Failed to get the computer name", hResult);
    }
    else
    {
        hResult = BindToServer (szServerName);
    }
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteValidateNotif ((WINDS_RPC_STRING)szComputerName,
                                       (WINDS_RPC_STRING)szMailboxName,
                                       dwNotifMask,
                                       pdwConnectionID);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("ResetServerNotifications", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetHeadersFromServer()
//
//    Parameters
//      szServerName   Name of the server we are going to make an RPC call to
//      szMailbox      Mailbox for which we are going to retrieve the
//                      headers for.
//      szHeaderFile   The name of the local file where we will copy the
//                      message headers found in the remote mailbox
//
//    Purpose
//      This function makes an RPC call to the remote server host to retrieve
//      the message header information of a particular mailbox. The headers
//      found are save to a local file and then used to fill the contents
//      table of the remote folder object we return to a client remote
//      viewer application.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI GetHeadersFromServer (LPTSTR szServerName,
                                     LPTSTR szMailbox,
                                     LPTSTR szHeaderFile)
{
    HANDLE hFile, hPipe;
    DWORD dwBytesWrite, dwBytesRead;
    BYTE abBuffer[IO_BUFFERSIZE];
    long lPipeNumber;
    HRESULT hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    HCURSOR hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT));
    RpcTryExcept
    {
        hResult = RemoteOpenHeaderDownloadPipe ((WINDS_RPC_STRING)szMailbox, &lPipeNumber);
        if (S_OK == hResult)
        {
            wsprintf ((LPTSTR)abBuffer, PIPE_NAME_FORMAT,szServerName, lPipeNumber);
            hPipe = CreateFile ((LPTSTR)abBuffer,
                                GENERIC_READ,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("GetHeadersFromServer: Failed to open pipe to server", hResult);
            }
            else
            {
                hFile = CreateFile (szHeaderFile,
                                    GENERIC_WRITE,
                                    0,
                                    NULL,
                                    CREATE_ALWAYS,
                                    FILE_FLAG_SEQUENTIAL_SCAN,
                                    NULL);
                if (INVALID_HANDLE_VALUE == hFile)
                {
                    hResult = HRESULT_FROM_WIN32(GetLastError());
                    TraceResult ("GetHeadersFromServer: Failed to open local headers file", hResult);
                }
                else
                {
                    do
                    {
                        // Read the pipe handle.
                        if (!ReadFile (hPipe, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                            if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                                HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                            {
                                // There was an error and we can't continue
                                TraceResult ("GetHeadersFromServer: Failed to read from the pipe", hResult);
                            }
                            else
                            {
                                // If the pipe was broken, it means the server finished writing
                                // to the it, so we are finished reading from it.
                                hResult = S_OK;
                            }
                        }
                        if (dwBytesRead)
                        {
                            if (!WriteFile (hFile, abBuffer, dwBytesRead, &dwBytesWrite, NULL))
                            {
                                hResult = HRESULT_FROM_WIN32(GetLastError());
                                TraceResult ("GetHeadersFromServer: Failed to write to local header file", hResult);
                            }
                        }
                    } while (dwBytesRead && !hResult);
                    CloseHandle (hFile);
                }
                CloseHandle (hPipe);
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    // Terminate connections with the remote server
    BindToServer (NULL);
    SetCursor (hCursor);
    TraceResult ("GetHeadersFromServer", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    OpenServerUploadPipe()
//
//    Parameters
//      hMsgFile            Handle to the local TNEF file with the message to
//                          upload
//      szServerName       String with the name of the remote server
//      szRecipMailbox     String with the name of the mailbox where the
//                          message will be uploaded.
//

//    Purpose
//      This function uploads a file to a server. The file must have been
//      opened by the caller and a handle to it must have been passed in
//      hMsgFile. The function will bind to the server (the name is is
//      szServerName).
//      This functions makes a remote procedure call to a server to request
//      the a pipe to upload the data through.
//      The remote server must be running the WINDS sample server messaging
//      host because that is the server procedure that will answer the
//      RPC call.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI OpenServerUploadPipe (LPTSTR     szServerName,
                                     LPTSTR     szSenderMailbox,
                                     HANDLE     hMsgFile,
                                     LPTSTR     szConnectionInfo,
                                     BOOL *     pfErrorInTheServer)
{
    *pfErrorInTheServer = FALSE;
    HANDLE hPipe;
    char szPipeName[64];
    DWORD dwBytesWrite, dwBytesRead;
    BYTE abBuffer[IO_BUFFERSIZE];
    long lPipeNumber, hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteOpenMsgUploadPipe ((WINDS_RPC_STRING)szSenderMailbox,
                                           &lPipeNumber,
                                           (WINDS_RPC_STRING)szConnectionInfo);
        if (!hResult)
        {
            wsprintf (szPipeName, PIPE_NAME_FORMAT, szServerName, lPipeNumber);
            hPipe = CreateFile (szPipeName,
                                GENERIC_WRITE,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("OpenServerUploadPipe: Failed to open pipe to server", hResult);
            }
            else
            {
                do
                {
                    if (!ReadFile (hMsgFile, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
                    {
                        hResult = HRESULT_FROM_WIN32(GetLastError());
                        TraceResult ("OpenServerUploadPipe: Failed to read local msg file", hResult);
                    }
                    if (dwBytesRead)
                    {
                        if (!WriteFile (hPipe, abBuffer, dwBytesRead, &dwBytesWrite, NULL))
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                            TraceResult ("OpenServerUploadPipe: Failed to write to the pipe", hResult);
                        }
                    }
                } while (dwBytesRead && !hResult);
                CloseHandle (hPipe);
            }
        }
        else
        {
            if (hResult)
            {
                *pfErrorInTheServer = TRUE;
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            *pfErrorInTheServer = TRUE;
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("OpenServerUploadPipe", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    SendMsgToAccount()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI SendMsgToAccount (LPTSTR    szServerName,
                                 LPTSTR    szRecipMailbox,
                                 LPTSTR    szHeader,
                                 LPTSTR    szConnectionInfo,
                                 BOOL *    pfErrorInTheServer)
{
    *pfErrorInTheServer = FALSE;
    long hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteSendMsgToAccount ((WINDS_RPC_STRING)szRecipMailbox,
                                          (WINDS_RPC_STRING)szHeader,
                                          (WINDS_RPC_STRING)szConnectionInfo);
        if (hResult)
        {
            *pfErrorInTheServer = TRUE;
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            *pfErrorInTheServer = TRUE;
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("SendMsgToAccount", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    FinishUploadConnection()
//
//    Parameters
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI FinishUploadConnection (LPTSTR   szServerName,
                                       LPTSTR   szConnectionInfo)
{
    long hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteFinishUpload ((WINDS_RPC_STRING)szConnectionInfo);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    TraceResult ("FinishUploadConnection", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    OpenRemoteServerDownLoadPipe()
//
//    Parameters
//      szServer   Name of the server we are downloading the message from
//      szMBox     Name of the mailbox we are downloading from.
//      phPipe      Pointer where I return the handle to the opened named
//                  pipe to the server machine.
//
//    Purpose
//      Makes an RPC to the remote server which sets up a pipe
//      over which the download occurs. The RPC returns to the
//      client (us) the pipe's ID that the server creates. We
//      then connect to the pipe.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI OpenRemoteServerDownLoadPipe (LPTSTR     szServer,
                                             LPTSTR     szMBox,
                                             HANDLE *   phPipe)
{
    ASSERT (szServer);
    ASSERT (szMBox);
    HRESULT hResult = BindToServer (szServer);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        ULONG ulPipeNum;
        hResult = RemoteOpenMsgDownloadPipe ((WINDS_RPC_STRING)szMBox, &ulPipeNum);
        if (!hResult)
        {
            TCHAR szPipeName[_MAX_PATH];
            HANDLE hPipe;
            // Construct the download pipe name
            wsprintf (szPipeName, PIPE_NAME_FORMAT, szServer, ulPipeNum);
            // Create our endpoint and connect
            hPipe = CreateFile (szPipeName,
                                GENERIC_READ | GENERIC_WRITE,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
            }
            else
            {
                *phPipe = hPipe;
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    // Terminate connections with the remote server
    BindToServer (NULL);
    TraceResult ("OpenRemoteServerDownLoadPipe", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    SelectMailBoxDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//      Displays a dialog with all available mailboxes in the remote machine
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK SelectMailBoxDlgProc (HWND    hDlg,
                                    UINT    message,
                                    WPARAM  wParam,
                                    LPARAM  lParam)
{
    static SELECTED_MAILBOX_INFO * pInfo;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                HWND hCtl = GetDlgItem (hDlg, IDC_LIST);
                int nTabStops[3];
                nTabStops[0] = 75;
                nTabStops[1] = 1000; // Off the screen
                nTabStops[2] = 1015; // Off the screen
                ListBox_SetTabStops (hCtl, sizeof(nTabStops)/sizeof(int), nTabStops);
                pInfo = (SELECTED_MAILBOX_INFO *)lParam;
                TCHAR szFormated[256];
                AB_ENTRY_INFO abEntry = { 0 };
                DWORD dwLastError = 0, dwBytesRead = 0;
                do
                {
                    // Read the pipe handle.
                    if (!ReadFile (pInfo->hPipe, &abEntry, sizeof(AB_ENTRY_INFO), &dwBytesRead, NULL))
                    {
                        dwLastError = HRESULT_FROM_WIN32(GetLastError());
                        if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != dwLastError &&   // For Windows NT
                            HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != dwLastError)     // For Windows 95
                        {
                            // There was an error and we can't continue
                            TraceResult ("SelectMailBoxDlgProc: Failed to read from the pipe", dwLastError);
                        }
                        else
                        {
                            // If the pipe was broken, it means the server finished writing
                            // to the it, so we are finished reading from it.
                            dwLastError = 0;
                        }
                    }
                    else
                    {
                        wsprintf (szFormated,
                                  TEXT("%s\t%s\t%s\t%d"),
                                  abEntry.Info.MB.szMailboxName,
                                  abEntry.Info.MB.szFullName,
                                  abEntry.Info.MB.szPassword,
                                  abEntry.Info.MB.dwObjID);
                        ListBox_AddString (hCtl, szFormated);
                    }
                } while (dwBytesRead && !dwLastError);
                if (!dwLastError)
                {
                    if (pfnDlgSubclassEx)
                    {
                        pfnDlgSubclassEx (hDlg, CTL3D_ALL);
                    }
                    return TRUE;
                }
                pInfo->hLastError = dwLastError;
                EndDialog (hDlg, -1);
            }
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_LIST :
                    if (LBN_DBLCLK != HIWORD(wParam))
                    {
                        break;
                    }
                case IDOK :
                    {
                        TCHAR szBuffer[256];
                        LPTSTR pStr;
                        HWND hCtl = GetDlgItem (hDlg, IDC_LIST);
                        int i = ListBox_GetCurSel (hCtl);
                        if (i != LB_ERR)
                        {
                            ListBox_GetText (hCtl, i, szBuffer);
                            pStr = strtok (szBuffer, "\t");
                            lstrcpy (pInfo->szMailBoxName, pStr);
                            pStr = strtok (NULL, "\t");
                            lstrcpy (pInfo->szDisplayName, pStr);
                            pStr = strtok (NULL, "\t");
                            lstrcpy (pInfo->szPassword, pStr);
                            pStr = strtok (NULL, "\t");
                            *(pInfo->pdwMailboxID) = (DWORD)atol (pStr);
                        }
                        else
                        {
                            PrivateMessageBox (IDS_MSG_SELECT_MB_NAME, hDlg);
                            return FALSE;
                        }
                    }
                case IDCANCEL :
                    EndDialog (hDlg, (LOWORD(wParam) == IDCANCEL ? FALSE : TRUE));
                    return TRUE;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    ChangeMBServerPassword()
//
//    Parameters
//      szServerName        Name of the server machine we are connecting to
//      szMailboxName       Name of the mailbox where the password will
//                          be changed.
//      szOldPassword       Old password of the mailbox
//      szNewPassword       New password for the mailbox
//
//    Purpose
//      Changes the access password in the specified mailbox in
//      the specified server.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI ChangeMBServerPassword (LPTSTR   szServerName,
                                       LPTSTR   szMailboxName,
                                       LPTSTR   szOldPassword,
                                       LPTSTR   szNewPassword)
{
    if (BindToServer (szServerName))
    {
        return FALSE;
    }
    HRESULT hResult;
    RpcTryExcept
    {
        hResult = RemoteChangePassword ((WINDS_RPC_STRING)szMailboxName,
                                        (WINDS_RPC_STRING)szOldPassword,
                                        (WINDS_RPC_STRING)szNewPassword);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    BindToServer (NULL);
    TraceResult ("ChangeMBServerPassword", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    PrivateMessageBox()
//
//    Parameters
//      ids             String ID of a string in the resource string table
//      hOwnerWnd       Window who will own the Message Box
//      uFlags          Flags (options) for the message box
//
//    Purpose
//      This function display a message box with a string loaded from the
//      string table of this app. The ID of the string is passed in the ids
//      parameter. The dialog box is modal with respect to the window
//      identified in hOwnerWnd. The options to display the dialog box are
//      passed in uFlags
//
//    Return Value
//      ID of the button pressed in the message box dialog box
//
int WINAPI PrivateMessageBox (UINT          ids,
                              HWND          hOwnerWnd,
                              UINT          uFlags,
                              HINSTANCE     hInst)
{
    int nResponse = -1; // Default response
    // Get the string from the string table. The size of the buffer is the
    // maximum number of character allowed in the character buffer, without
    // the accounting for the NULL terminator
    if (NULL == hInst)
    {
        hInst = ghInstance;
    }
    ASSERT (hInst);
    TCHAR szBuffer[256];
    if (LoadString (hInst, ids, szBuffer, 255))
    {
        if (!(MB_ICONSTOP & uFlags))
        {
            uFlags |= MB_ICONINFORMATION;
        }
        // Show the message box and get the ID of the button pressed
        nResponse = MessageBox (hOwnerWnd,
                                szBuffer,
                                gszProviderName,
                                uFlags |
                                MB_SETFOREGROUND |
                                MB_SYSTEMMODAL);
    }
    return nResponse;
}

///////////////////////////////////////////////////////////////////////////////
//    IsValidServerName()
//
//    Parameters
//      szServerName       Pointer to a string with the UNC name of a
//                          network or local server
//    Purpose
//      Check the string for a server name and determines if the name is a
//      valid UNC name for a network server
//
//    Return Value
//      TRUE if the server name is valid, FALSE otherwise.
//
BOOL WINAPI IsValidServerName (LPTSTR szServerName)
{
    // Server name need to be at least "\\x"
    if (lstrlen(szServerName) < 3)
    {
        return FALSE;
    }
    // The first two character in the string must be "\". This is standard
    // UNC (Universal Naming Convention) for server names
    if (szServerName[0] != '\\' || szServerName[1] != '\\')
    {
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    BindToServer()
//
//    Parameters
//      [IN]    szServer   Name of the remote server to which we will bind
//                          for RPC calls. If this pointer is NULL, we
//                          unbind from that server.
//
//    Purpose
//      This function makes the necessary calls to the RPC runtime library
//      to bind to the remote server so that we can start RPC function calls.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI BindToServer (LPTSTR szServer)
{
    static unsigned char * szStringBinding = NULL;
    static char szLastServer[32] = { 0 };

    // If a server name was given, we compare to the current server that we are
    // already bounded to. If we are connected return the call.
    if (szServer)
    {
        if (0 == lstrcmpi (szLastServer, szServer))
        {
            if (szStringBinding)
            {
                return 0;
            }
        }
        else
        {
            // Save the name of the server in the static buffer
            lstrcpy (szLastServer, szServer);
        }
    }

    RPC_STATUS status = 0;
    if (szStringBinding) // Unbind only if bound
    {
        status = RpcStringFree (&szStringBinding);
        TraceRPCError  ("BindToServer: RpcStringFree failed", status);
        if (!status)
        {
            szStringBinding = NULL;
            status = RpcBindingFree (&hWINDSREM);  // hWINDSREM is defined in WINDS.H and WINDS.ACF
            TraceRPCError  ("BindToServer: RpcBindingFree failed", status);
        }
    }
    // If this is NULL, then we don't need to bind to anything.
    if (!szServer)
    {
        szLastServer[0] = 0;
        return 0;
    }
    if (!status)
    {
        status = RpcStringBindingCompose (NULL,
                                          (WINDS_RPC_STRING)WINDS_RPC_PROTOCOL,
                                          (WINDS_RPC_STRING)szServer,
                                          (WINDS_RPC_STRING)WINDS_PROVIDERS_RPC_ENDPOINT,
                                          NULL,
                                          &szStringBinding);
        TraceRPCError  ("BindToServer: RpcStringBindingCompose failed", status);
        if (!status)
        {
            status = RpcBindingFromStringBinding (szStringBinding, &hWINDSREM); // hWINDSREM is defined in WINDS.H and WINDS.ACF
            TraceRPCError ("BindToServer: RpcBindingFromStringBinding failed", status);
        }
    }
    if (status)
    {
        szLastServer[0] = 0;
        status = MAKE_HRESULT(1, FACILITY_RPC, status);;
    }
    return status;
}

///////////////////////////////////////////////////////////////////////////////
//    TerminateRemoteConnections()
//
//    Parameters
//      None.
//
//    Purpose
//      Termintes the binding links in the RPC runtime library to the remote
//      server after a sequence of RPC calls.
//
//    Return Value
//      None
//
void WINAPI TerminateRemoteConnections()
{
    BindToServer (NULL);
}

///////////////////////////////////////////////////////////////////////////////
//    midl_user_allcate()
//
//    Parameters
//      len     Size (in bytes) of the memory block to allocate for the
//              RPC object
//
//    Purpose
//      Allocates memory as needed by the RPC runtime library.
//      The stubs or runtime libraries may need to allocate memory.
//      By convention, they call a user-specified function named
//      midl_user_allocate.
//
//    Return Value
//      Pointer to a block of memory of len byte in size
//
void __RPC_FAR * __RPC_API midl_user_allocate (size_t len)
{
    LPVOID pVoid = HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, (DWORD)len);
    if (NULL == pVoid)
    {
        TraceMessage ("midl_user_allocate: Failed to allocated RPC memory block");
    }
    return pVoid;
}

///////////////////////////////////////////////////////////////////////////////
//    midl_user_free()
//
//    Parameters
//      ptr     Pointer to memory block to release
//
//    Purpose
//      Free memory as needed by the RPC runtime library.
//      The stubs or runtime libraries may need to free memory.
//      By convention, they call a user-specified function named
//      midl_user_free.
//
//    Return Value
//      None
//
void __RPC_API midl_user_free (void __RPC_FAR * ptr)
{
    HeapFree (GetProcessHeap(), 0, ptr);
}

///////////////////////////////////////////////////////////////////////////////
//    CUIMAPIProp::CUIMAPIProp()
//
//    Parameters
//      hInstance           Instance of the DLL where this provider is
//      pfnAllocateBuffer   MAPI allocation function
//      pfnAllocateMore     MAPI allocation function
//      pfnFreeBuffer       MAPI allocation function
//      fReadOnly           Weather or not the UI is in read-only mode
//
//    Purpose
//      Constructor of the class.
//
//    Return Value
//      None.
//
CUIMAPIProp::CUIMAPIProp (HINSTANCE             hInstance,
                          LPALLOCATEBUFFER      pfnAllocateBuffer,
                          LPALLOCATEMORE        pfnAllocateMore,
                          LPFREEBUFFER          pfnFreeBuffer,
                          BOOL                  fReadOnly)
{
    // Save in global pointer
    pfnMAPIFreeBuffer = pfnFreeBuffer;

    // Use MAPI's IMAPIProp in-memory implementation to do the actual
    // work of store the properties. If we fail to create this, we throw
    // and exception, to new creator of this object must have the
    // construction of this object protected within a try-catch block.
    HRESULT hResult = CreateIProp ((LPIID)&IID_IMAPIPropData,
                                   pfnAllocateBuffer,
                                   pfnAllocateMore,
                                   pfnFreeBuffer,
                                   NULL,
                                   &m_pImpObj);
    if (hResult)
    {
        TraceResult ("CUIMAPIProp: CreateIProp failed to create IPropData object", hResult);
        throw CException (hResult);
    }
    m_hInstance = hInstance;
    m_fReadOnly = fReadOnly;
    m_cRef = 1;
    m_ulCustomPropTag = 0;
    m_pfnCallBack = NULL;
    m_pTableData = NULL;
    InitializeCriticalSection (&m_csObj);
}

///////////////////////////////////////////////////////////////////////////////
//    CUIMAPIProp::OpenProperty()
//
//    Parameters
//      { Refer to the MAPI documentation for parameter description }
//
//    Purpose
//      Open the property for the BUTTON control on the display table. We
//      only allow the IID_IMAPIControl interface specified in this method.
//      If we have the property specified, create a new IMAPIControl
//      interface and return it to the caller.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CUIMAPIProp::OpenProperty (ULONG       ulPropTag,
                                        LPCIID      piid,
                                        ULONG       ulInterfaceOptions,
                                        ULONG       ulFlags,
                                        LPUNKNOWN * ppUnk)
{
    Validate_IMAPIProp_OpenProperty (this,
                                     ulPropTag,
                                     piid,
                                     ulInterfaceOptions,
                                     ulFlags,
                                     ppUnk);
    if (IID_IMAPIControl != *piid)
    {
        return E_NOINTERFACE;
    }
    LPBUTTONCALLBACK pfnCallBack = NULL;
    switch (ulPropTag)
    {
        case PR_SMP_BROWSE_MB :
            pfnCallBack = BrowseMailboxes;
            break;
        case PR_SMP_CHANGE_PASSWORD :
            pfnCallBack = ConfigChangePassword;
            break;
        default :
            // We allow client of CUIMAPIProp to set one custom property
            // that is not know internally and a custom callback.
            if (m_ulCustomPropTag == ulPropTag)
            {
                pfnCallBack = m_pfnCallBack;
            }
    }
    if (NULL == pfnCallBack)
    {
        return MAPI_E_NOT_FOUND;
    }
    CMAPIControl * pControl = new CMAPIControl (this, pfnCallBack, m_fReadOnly);
    if (!pControl)
    {
        return E_OUTOFMEMORY;
    }
    *ppUnk = (LPUNKNOWN)pControl;
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    EnterMBPasswordDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//      Display a dialog where the user enters the password for a mailbox
//      on the server.
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK EnterMBPasswordDlgProc (HWND    hDlg,
                                      UINT    message,
                                      WPARAM  wParam,
                                      LPARAM  lParam)
{
    static LPTSTR szStoredPassword;
    static LPTSTR szReturningBuffer;
    switch (message)
    {
        case WM_INITDIALOG :
            {
                // On this message only, the lParam is an argument passed by the
                // caller who instantiated this dialog.
                LPSPropValue pProps = (LPSPropValue)lParam;
                SetWindowText (GetDlgItem (hDlg, IDC_MAILBOXNAME), pProps[0].Value.LPSZ);
                SetWindowText (GetDlgItem (hDlg, IDC_USER_NAME), pProps[1].Value.LPSZ);
                szStoredPassword = pProps[2].Value.LPSZ;
                szReturningBuffer = pProps[3].Value.LPSZ;
                Edit_LimitText (GetDlgItem (hDlg, IDC_PASSWORD), MAX_PASSWORD_SIZE);
                // If this value is false, we should disable the BROWSE button to prevent
                // users from changing the mailbox.
                if (FALSE == pProps[4].Value.b)
                {
                    EnableWindow (GetDlgItem (hDlg, IDC_BROWSE_MB), FALSE);
                }
                if (pfnDlgSubclassEx)
                {
                    pfnDlgSubclassEx (hDlg, CTL3D_ALL);
                }
            }
            return TRUE;
        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_BROWSE_MB :
                    EndDialog (hDlg, IDC_BROWSE_MB);
                    break;
                case IDOK :
                    // Get the password and do a CASE-SENSITIVE comparison of the stored and entered psswords.
                    GetWindowText (GetDlgItem (hDlg, IDC_PASSWORD), szReturningBuffer, MAX_PASSWORD_SIZE);

if (szStoredPassword && (0 != lstrcmp (szReturningBuffer, szStoredPassword)))
                    {
                        MessageBeep (MB_ICONEXCLAMATION);
                        PrivateMessageBox (IDS_MSG_INVALID_MB_PASSWORD, hDlg);
                        SetFocus (GetDlgItem (hDlg, IDC_PASSWORD));
                        return TRUE;
                    }
                    // else fall through
                case IDCANCEL :
                    EndDialog (hDlg, (LOWORD(wParam) == IDCANCEL ? FALSE : TRUE));
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    PasswordDlgProc()
//
//    Parameters
//      { Refer to Win32 API documentation on dialog procedures }
//
//    Purpose
//      Display a dialog where the user enters a new password for the mailbox.
//
//    Return Value
//      TRUE if message was handled, FALSE if we don't handle the message
//
BOOL CALLBACK PasswordDlgProc (HWND    hDlg,
                               UINT    message,
                               WPARAM  wParam,
                               LPARAM  lParam)
{
    static LPTSTR * pszPasswords;
    switch (message)
    {
        case WM_INITDIALOG :
            // On this message only, the lParam is an argument passed by the
            // caller who instantiated this dialog.
            pszPasswords = (LPTSTR *)lParam;
            // Limit the fields the user can type in.
            Edit_LimitText  (GetDlgItem (hDlg, IDC_PASSWORD), MAX_PASSWORD_SIZE);
            Edit_LimitText  (GetDlgItem (hDlg, IDC_PASSWORD_CONFIRM), MAX_PASSWORD_SIZE);
            Edit_LimitText  (GetDlgItem (hDlg, IDC_OLD_PASSWORD), MAX_PASSWORD_SIZE);
            if (pfnDlgSubclassEx)
            {
                pfnDlgSubclassEx (hDlg, CTL3D_ALL);
            }
            return TRUE;
        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDOK :
                    {
                        // Get and verify the OLD password
                        TCHAR szConfirm[MAX_PASSWORD_SIZE+1],  szOldPassword[MAX_PASSWORD_SIZE+1];
                        if (!GetWindowText (GetDlgItem (hDlg, IDC_OLD_PASSWORD), szOldPassword, MAX_PASSWORD_SIZE+1))
                        {
                            MessageBeep (MB_ICONEXCLAMATION);
                            PrivateMessageBox (IDS_MSG_NEED_OLD_PASSWORD, hDlg);
                            SetFocus (GetDlgItem (hDlg, IDC_OLD_PASSWORD));
                            return TRUE;
                        }
                        if (lstrcmp (szOldPassword, pszPasswords[1]))
                        {
                            MessageBeep (MB_ICONEXCLAMATION);
                            PrivateMessageBox (IDS_MSG_OLD_PASSWORD_NO_MATCH, hDlg);
                            SetWindowText (GetDlgItem (hDlg, IDC_OLD_PASSWORD), szBlank);
                            SetFocus (GetDlgItem (hDlg, IDC_OLD_PASSWORD));
                            return TRUE;
                        }
                        // Get the NEW password
                        if (!GetWindowText (GetDlgItem (hDlg, IDC_PASSWORD), pszPasswords[0], MAX_PASSWORD_SIZE+1))
                        {
                            MessageBeep (MB_ICONEXCLAMATION);
                            PrivateMessageBox (IDS_MSG_NEED_PASSWORD, hDlg);
                            SetFocus (GetDlgItem (hDlg, IDC_PASSWORD));
                            return TRUE;
                        }
                        // Get the NEW password (CONFIRM)
                        if (!GetWindowText (GetDlgItem (hDlg, IDC_PASSWORD_CONFIRM), szConfirm, MAX_PASSWORD_SIZE+1))
                        {
                            MessageBeep (MB_ICONEXCLAMATION);
                            PrivateMessageBox (IDS_MSG_NEED_PASSWORD_CONFIRM, hDlg);
                            SetFocus (GetDlgItem (hDlg, IDC_PASSWORD_CONFIRM));
                            return TRUE;
                        }
                        // Compare (case-sensitive) the instances of the new password typed in
                        if (lstrcmp (szConfirm, pszPasswords[0]))
                        {
                            MessageBeep (MB_ICONEXCLAMATION);
                            PrivateMessageBox (IDS_MSG_NEW_PASSWORD_NO_MATCH, hDlg);
                            SetWindowText (GetDlgItem (hDlg, IDC_PASSWORD), szBlank);
                            SetWindowText (GetDlgItem (hDlg, IDC_PASSWORD_CONFIRM), szBlank);
                            SetFocus (GetDlgItem (hDlg, IDC_PASSWORD));
                            return TRUE;
                        }
                    }
                case IDCANCEL :
                    EndDialog (hDlg, (IDOK == LOWORD(wParam) ? TRUE : FALSE));
                    return TRUE;
            }
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    BrowseMailboxes()
//
//    Parameters
//      pObj            Pointer to our IMAPIProp implementation (CUIMAPIProp)
//                      passed in to the DoConfidPropSheet() function
//      hOwnerWnd       The owner of any window we display
//      hInstance       The instance of this DLL
//
//    Purpose
//      This function get called by our implementation of
//      IMAPIControl::Active() by a user hitting a "Browse"
//      on the provider configuration property sheet.
//      Display a dialog box with all the mailboxes in the remote system
//
//    Return Value
//      None
//
void STDAPICALLTYPE BrowseMailboxes (CUIMAPIProp *  pObj,
                                     HWND           hOwnerWnd,
                                     HINSTANCE      hInstance)
{
    TCHAR szDisplayName[MAX_STRING_SIZE+1], szMailboxName[MAX_ALIAS_SIZE+1];
    TCHAR szStoredPassword[MAX_PASSWORD_SIZE+1], szEnterPassword[MAX_PASSWORD_SIZE+1];
    LPSPropValue pProp = NULL;
    DWORD dwMailboxID;
    SPropTagArray sptServer;
    sptServer.cValues = 1;
    sptServer.aulPropTag[0] = PR_SMP_REMOTE_SERVER;
    ULONG cValues;
    // Get the server name the user may have typed in the edit field
    HRESULT hResult = pObj->GetProps (&sptServer, fMapiUnicode, &cValues, &pProp);
    if (FAILED(hResult) || MAPI_W_ERRORS_RETURNED == hResult)
    {
        pfnMAPIFreeBuffer (pProp);
        PrivateMessageBox (IDS_MSG_NEED_SERVER_NAME, hOwnerWnd);
        return;
    }
    ASSERT (pProp->ulPropTag == PR_SMP_REMOTE_SERVER);
    TCHAR szServer[MAX_STRING_SIZE+1];
    lstrcpy (szServer, pProp->Value.LPSZ);
    pfnMAPIFreeBuffer (pProp);
    // Validate the server
    if (!IsValidServerName (szServer))
    {
        MessageBeep (MB_ICONEXCLAMATION);
        PrivateMessageBox (IDS_MSG_INVALID_SERVER_NAME, hOwnerWnd);
        return;
    }
    if (!IsWINDSServerAvailable (szServer))
    {
        PrivateMessageBox (IDS_MSG_SERVER_NOT_FOUND, hOwnerWnd);
        return;
    }
    // Show the list of mailboxes in the server. If the user hit OK, process the selected entry.
Restart:
    if (S_OK == ShowServerAccounts  (hOwnerWnd,
                                     hInstance,
                                     szServer,
                                     szMailboxName,
                                     szDisplayName,
                                     szStoredPassword,
                                     &dwMailboxID))
    {
        // Set the selected mailbox properties in the property object
        SPropValue spvProps[5] = { 0 };
        spvProps[0].ulPropTag = PR_SMP_MAILBOX_NAME;
        spvProps[0].Value.LPSZ = szMailboxName;
        spvProps[1].ulPropTag = PR_SMP_USER_NAME;
        spvProps[1].Value.LPSZ = szDisplayName;
        spvProps[2].ulPropTag = PR_SMP_MAILBOX_PASSWORD;
        spvProps[2].Value.LPSZ = szStoredPassword;
        spvProps[3].ulPropTag = PR_SMP_MAILBOX_PASSWORD;
        spvProps[3].Value.LPSZ = szEnterPassword;
        spvProps[4].Value.b = TRUE; // Activate the "BROWSE" button on the dialog below
        // Before continuing, the user must verify it's access to the mailbox by typing the correct password
        int nAnswer = DialogBoxParam (hInstance,
                                      MAKEINTRESOURCE(IDD_ENTER_PASSWORD),
                                      hOwnerWnd,
                                      EnterMBPasswordDlgProc,
                                      (LPARAM)spvProps);
        if (IDC_BROWSE_MB == nAnswer)
        {
            goto Restart;
        }
        if (TRUE == nAnswer)
        {
            // Access is verified, continue
            spvProps[3].ulPropTag = PR_SMP_MAILBOX_ID;
            spvProps[3].Value.l = dwMailboxID;
            pObj->SetProps (4, spvProps, NULL);
            // Now we must tell the property sheet handler to re-read the data for
            // this field from the IMAPIProp object any of the DTCTL members with a key, for a notification on them.
            for (ULONG iRow=0; ; iRow++)
            {
                LPSRow pRow;
                LPSPropValue pControlID;
                hResult = pObj->m_pTableData->HrEnumRow (iRow, &pRow);
                if (hResult || !pRow)
                {
                    TraceResult ("BrowseMailboxes: Traversing the display table", hResult);
                    return;
                }
                pControlID = PpropFindProp (pRow->lpProps, pRow->cValues, PR_CONTROL_ID);
                if (pControlID->Value.bin.cb)
                {
                    pObj->m_pTableData->HrNotify (0, 1, pControlID);
                }
                pfnMAPIFreeBuffer (pRow);
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    ConfigChangePassword()
//
//    Parameters
//      pObj            Pointer to our IMAPIProp implementation (CUIMAPIProp)
//                      passed in to the DoConfidPropSheet() function
//      hOwnerWnd       The owner of any window we display
//      hInstance       The instance of this DLL
//
//    Purpose
//      This function get called by our implementation of
//      IMAPIControl::Active() by a user hitting a "Change Mailbox Password"
//      on the provider configuration property sheet.
//      Display a dialog box where the user can change the password of the
//      mailbox.
//
//    Return Value
//      None
//
void STDAPICALLTYPE ConfigChangePassword (CUIMAPIProp * pObj, HWND hOwnerWnd, HINSTANCE hInstance)
{
    const static SizedSPropTagArray(3, sptServerAndPass) =
    {
        3,
        {
            PR_SMP_REMOTE_SERVER,
            PR_SMP_MAILBOX_NAME,
            PR_SMP_MAILBOX_PASSWORD
        }
    };
    TCHAR szOldPassword[MAX_PASSWORD_SIZE+1], szNewPassword[MAX_PASSWORD_SIZE+1];
    TCHAR szServer[MAX_STRING_SIZE+1], szMBName[MAX_STRING_SIZE+1];
    LPTSTR aszPasswords[2] = { 0 };
    ULONG cValues;
    LPSPropValue pProp = NULL;
    SPropValue spvNewPassword = { 0 };
    // Get the server and mailbox information for the property object
    HRESULT hResult = pObj->GetProps ((LPSPropTagArray)&sptServerAndPass, fMapiUnicode, &cValues, &pProp);
    if (FAILED(hResult) || MAPI_W_ERRORS_RETURNED == hResult)
    {
        pfnMAPIFreeBuffer (pProp);
        PrivateMessageBox (IDS_MSG_NEED_SERVER_NAME, hOwnerWnd);
        return;
    }
    ASSERTMSG (pProp[0].ulPropTag == PR_SMP_REMOTE_SERVER, "Where is PR_SMP_REMOTE_SERVER?!?!");
    ASSERTMSG (pProp[1].ulPropTag == PR_SMP_MAILBOX_NAME, "Where is PR_SMP_MAILBOX_NAME?!?!");
    ASSERTMSG (pProp[2].ulPropTag == PR_SMP_MAILBOX_PASSWORD, "Where is PR_SMP_MAILBOX_PASSWORD?!?!");
    lstrcpy (szServer, pProp[0].Value.LPSZ);
    lstrcpy (szMBName, pProp[1].Value.LPSZ);
    lstrcpy (szOldPassword, pProp[2].Value.LPSZ);
    pfnMAPIFreeBuffer (pProp);
    // Validate the server name and availability
    if (!IsValidServerName (szServer))
    {
        MessageBeep (MB_ICONEXCLAMATION);
        PrivateMessageBox (IDS_MSG_INVALID_SERVER_NAME, hOwnerWnd);
        return;
    }
    if (!IsWINDSServerAvailable (szServer))
    {
        PrivateMessageBox (IDS_MSG_SERVER_NOT_FOUND, hOwnerWnd);
        return;
    }
    aszPasswords[0] = szNewPassword;
    aszPasswords[1] = szOldPassword;

    // Show the dialog where the user can change the information
    if (TRUE == DialogBoxParam (hInstance,
                                MAKEINTRESOURCE(IDD_NEW_PASSWORD),
                                hOwnerWnd,
                                PasswordDlgProc,
                                (LPARAM)&aszPasswords))
    {
        // If the user is ok call the server
        if (S_OK == ChangeMBServerPassword (szServer, szMBName, szOldPassword, szNewPassword))
        {
            // If the change was successfull, save the new password in the property object.
            spvNewPassword.ulPropTag = PR_SMP_MAILBOX_PASSWORD;
            spvNewPassword.Value.LPSZ = szNewPassword;
            pObj->SetProps (1, &spvNewPassword, NULL);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    PingRemoteServer()
//
//    Parameters
//      hOwnerWnd       Handle to a windows that owns any UI done by
//                      this provider
//      pProps          An array of SPropValue structures with the logon
//                      properties of the service. The first 5 properties
//                      must be the standard properties for a WINDS service
//                      and they must be in the standard order (see COMWINDS.H)
//
//    Purpose
//      Make a quick connection to the server verifying a mailbox access
//      with the current password and name.
//
//    Return Value
//      TRUE if the connection was successful, or the user does not care that
//      the server is offline.
//      FALSE if the user wants to change the configuration properties again.
//
BOOL WINAPI PingRemoteServer (HWND hOwnerWnd, LPSPropValue pProps)
{
    BOOL fPingedOK = TRUE;
    DWORD dwMsgIDS = 0, dwMailboxID;
    TCHAR szServerUserFullName[MAX_STRING_SIZE+1];
    HRESULT hLogonError;
    CharUpper (pProps[SERVER_NAME].Value.LPSZ);
    // Try to connect to the server and see if it is around
    if (IsWINDSServerAvailable (pProps[SERVER_NAME].Value.LPSZ))
    {
        hLogonError = LogonServerMailbox (pProps[SERVER_NAME].Value.LPSZ,
                                          pProps[MAILBOX_NAME].Value.LPSZ,
                                          szServerUserFullName,
                                          pProps[PASSWORD].Value.LPSZ,
                                          &dwMailboxID);
        if (hLogonError)
        {
            switch (hLogonError)
            {
                case HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER) :
                    dwMsgIDS = IDS_MSG_CONFIG_INVALID_USER;
                    break;
                case HRESULT_FROM_WIN32 (ERROR_BAD_USERNAME) :
                    dwMsgIDS = IDS_MSG_CONFIG_INVALID_USER_NAME;
                    break;
                case HRESULT_FROM_WIN32 (ERROR_INVALID_PASSWORD) :
                    dwMsgIDS = IDS_MSG_INVALID_MB_PASSWORD;
                    break;
                default :
                    dwMsgIDS = IDS_MSG_CONFIG_NO_CONNECT;
                    break;
            }
            PrivateMessageBox (dwMsgIDS, hOwnerWnd);
            fPingedOK = FALSE;
        }
    }
    else
    {
        if (IDNO == PrivateMessageBox (IDS_MSG_NO_SERVER_GO_OFFLINE, hOwnerWnd, MB_YESNO | MB_ICONSTOP))
        {
            // The user wants to re-enter the name of the remote server
            fPingedOK = FALSE;
        }
    }
    return fPingedOK;
}

///////////////////////////////////////////////////////////////////////////////
//    DoServerLogon()
//
//    Parameters
//      pMBInfo             Pointer to the mailbox info structure where this
//                          function returns the mailbox information of the
//                          user logging in.
//      pProps              An array of SPropValue structures with the logon
//                          properties of the service. The first 5 properties
//                          must be the standard properties for a WINDS
//                          service and they must be in the standard order
//                          (see COMWINDS.H)
//      pProfileObj         Profile object to which this function write
//                          changes, if any are required based on the latest
//                          information comming down from the server or
//                          entered by the user in the configuration dialog.
//      fUIAllowed          Weather or not the function is allowed to display
//                          any UI of information or request input from
//                          the user.
//      hOwnerWnd           Handle to the window that owns any UI displayed.
//      fSetNotification    Weather or not to set notifications with the
//                          remote server
//      dwNotifMask         Mask of events this provider is intersted in
//                          receiving.
//      pdwConnectID        Where I return the connection ID returned by
//                          the server
//      fOffLineOK          Flag that indicates that the caller does not
//                          want to be bother with a "server offline"
//                          notification/UI and that we should return success
//                          even if the server could not be reached.
//
//    Purpose
//      This function does a complete logon to the server with the
//      information passed in the pProps arguments. The function makes a
//      remote call to the server to request access to a mailbox. If
//      access is not granted we display messages and if needed, display the
//      appropiate dialog boxes for reconfiguration or change of password.
//      If the server was off-line, but the user does not mind, we return
//      MAPI_W_NO_SERVICE.
//      If the server is off-line, but no UI is allowed we return
//      MAPI_E_NETWORK_ERROR.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI DoServerLogon (PMAILBOX_INFO pMBInfo,
                              LPSPropValue  pProps,
                              LPPROFSECT    pProfileObj,
                              BOOL          fUIAllowed,
                              HWND          hOwnerWnd,
                              BOOL          fSetNotification,
                              DWORD         dwNotifMask,
                              DWORD *       pdwConnectID,
                              BOOL          fOffLineOK)
{
    ASSERT (PR_SMP_REMOTE_SERVER == pProps[SERVER_NAME].ulPropTag);
    ASSERT (PR_SMP_MAILBOX_NAME == pProps[MAILBOX_NAME].ulPropTag);
    ASSERT (PR_SMP_MAILBOX_ID == pProps[MAILBOX_ID].ulPropTag);
    ASSERT (PR_SMP_USER_NAME == pProps[USER_NAME].ulPropTag);
    ASSERT (PR_SMP_MAILBOX_PASSWORD == pProps[PASSWORD].ulPropTag);

    pMBInfo->dwObjID = pProps[MAILBOX_ID].Value.l;
    lstrcpy (pMBInfo->szMailboxName, pProps[MAILBOX_NAME].Value.LPSZ);
    lstrcpy (pMBInfo->szFullName, pProps[USER_NAME].Value.LPSZ);
    lstrcpy (pMBInfo->szPassword, pProps[PASSWORD].Value.LPSZ);

    SPropValue spvProps[5] = { 0 };
    DWORD dwMsgIDS = 0, dwStyle = MB_YESNO | MB_ICONSTOP, dwMailboxID;
    HRESULT hLogonError;
    long nResponse;

    // Try to connect to the server and see if it is around
    if (IsWINDSServerAvailable (pProps[SERVER_NAME].Value.LPSZ))
    {
RestartMailboxLogon:
        if (fSetNotification)
        {
            hLogonError = LogonServerMailboxAndSetNotif (pProps[SERVER_NAME].Value.LPSZ,
                                                         pMBInfo->szMailboxName,
                                                         pMBInfo->szFullName,
                                                         pMBInfo->szPassword,
                                                         &dwMailboxID,
                                                         dwNotifMask,
                                                         *pdwConnectID);
        }
        else
        {
            hLogonError = LogonServerMailbox (pProps[SERVER_NAME].Value.LPSZ,
                                              pMBInfo->szMailboxName,
                                              pMBInfo->szFullName,
                                              pMBInfo->szPassword,
                                              &dwMailboxID);
        }
        if (hLogonError)
        {
            if (fUIAllowed)
            {
                switch (hLogonError)
                {
                    case HRESULT_FROM_WIN32 (ERROR_NO_SUCH_USER) :
                        dwMsgIDS = IDS_MSG_LOGON_INVALID_USER;
                        break;
                    case HRESULT_FROM_WIN32 (ERROR_BAD_USERNAME) :
                        dwMsgIDS = IDS_MSG_LOGON_INVALID_USER_NAME;
                        break;
                    case HRESULT_FROM_WIN32 (ERROR_INVALID_PASSWORD) :
                        dwMsgIDS = IDS_MSG_INVALID_MB_PASSWORD;
                        dwStyle = MB_OK | MB_ICONSTOP;
                        break;
                    default :
                        dwMsgIDS = IDS_MSG_LOGON_NO_CONNECT;
                        break;
                }
                if (dwMsgIDS)
                {
                    PrivInitialize3DCtl (ghInstance);
                    nResponse = PrivateMessageBox (dwMsgIDS, hOwnerWnd, dwStyle);
                    if (IDYES == nResponse && hLogonError != HRESULT_FROM_WIN32 (ERROR_INVALID_PASSWORD))
                    {
                        return S_FALSE;
                    }
                    if (hLogonError == HRESULT_FROM_WIN32 (ERROR_INVALID_PASSWORD))
                    {
                        spvProps[0].ulPropTag = PR_SMP_MAILBOX_NAME;
                        spvProps[0].Value.LPSZ = pMBInfo->szMailboxName;
                        spvProps[1].ulPropTag = PR_SMP_USER_NAME;
                        spvProps[1].Value.LPSZ = pMBInfo->szFullName;
                        spvProps[2].ulPropTag = PR_SMP_MAILBOX_PASSWORD;
                        spvProps[2].Value.LPSZ = NULL;
                        spvProps[3].ulPropTag = PR_SMP_MAILBOX_PASSWORD;
                        spvProps[3].Value.LPSZ = pMBInfo->szPassword;
                        spvProps[4].Value.b = FALSE; // De-activate the "BROWSE" button on the dialog below
                        ASSERT (NULL != ghInstance);
                        nResponse = DialogBoxParam (ghInstance,
                                                    MAKEINTRESOURCE(IDD_ENTER_PASSWORD),
                                                    hOwnerWnd,
                                                    EnterMBPasswordDlgProc,
                                                    (LPARAM)spvProps);
                        if (FALSE == nResponse)
                        {
                            return MAPI_E_EXTENDED_ERROR;
                        }
                        else
                        {
                            goto RestartMailboxLogon;
                        }
                    }
                }
            }
            else
            {
                return hLogonError;
            }
        }
        else
        {
            ULONG ulProps = 0;
            // If any of this properties that came from the server is different to
            // what we have stored on the profile, then set them back for the next session.
            if (lstrcmp (pProps[USER_NAME].Value.LPSZ, pMBInfo->szFullName))
            {
                spvProps[ulProps].ulPropTag = PR_SMP_USER_NAME;
                spvProps[ulProps++].Value.LPSZ = pMBInfo->szFullName;
            }
            if (lstrcmp (pProps[PASSWORD].Value.LPSZ, pMBInfo->szPassword))
            {
                spvProps[ulProps].ulPropTag = PR_SMP_MAILBOX_PASSWORD;
                spvProps[ulProps++].Value.LPSZ = pMBInfo->szPassword;
            }
            if (dwMailboxID != pProps[MAILBOX_ID].Value.ul)
            {
                spvProps[ulProps].ulPropTag = PR_SMP_MAILBOX_ID;
                spvProps[ulProps++].Value.l = dwMailboxID;
            }
            if (ulProps)
            {
                HRESULT hResult = pProfileObj->SetProps (ulProps, spvProps, NULL);
                TraceResult ("DoServerLogon: Could not set last-minute props", hResult);
            }
        }
    }
    else
    {
        // If the caller says that is OK to be offline, then return.
        if (fOffLineOK)
        {
            return S_OK;
        }
        if (fUIAllowed)
        {
            PrivInitialize3DCtl (ghInstance);
            if (IDYES == PrivateMessageBox (IDS_MSG_NO_SERVER_ONLINE, hOwnerWnd, MB_YESNO))
            {
                // The user wants to re-enter the name of the remote server
                return MAPI_W_NO_SERVICE;
            }
        }
        else
        {
            return MAPI_E_NETWORK_ERROR;
        }
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    WizardEntry()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Returns the name of the resource where the controls for the wizard
//      pages are defined and outlined. The Wizard gives us a pointer to an
//      IMAPIProp object. We will release this object after we are finished
//      in the dialog procedure of our configuration pages.
//
//    Return Value
//      S_OK always.
//
ULONG STDAPICALLTYPE WizardEntry (HINSTANCE     hProviderDLLInstance,
                                  LPTSTR *      ppcsResourceName,
                                  DLGPROC *     ppDlgProc,
                                  LPMAPIPROP    pMapiProp,
                                  LPVOID        pSupObj)
{
    InfoTrace ("WizardEntry function called");
    // Quick sanity check on the input parameters
    ASSERT (ppcsResourceName);
    ASSERT (pMapiProp);
    ASSERT (ppDlgProc);
    ASSERT (hProviderDLLInstance);
    // The resource name needs to be an actual text string not
    // something generated with the MAKEINTRESOURCE() macro
    const static TCHAR szTemplate[] = TEXT("WIZARD_PAGES");
    *ppcsResourceName = (LPTSTR)szTemplate;
    *ppDlgProc = (DLGPROC)WizardDlgProc;
    pPropObj = pMapiProp;
    pPropObj->AddRef();
    ghInstance = hProviderDLLInstance;

    fInitalizeControls = fDownloadMBList = TRUE;
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    WizardDlgProc()
//
//    Parameters
//      { Refer to MAPI and Win32 SDK Documentation on this method }
//
//    Purpose
//      This is the function that the profile wizard will call while
//      interacting with our configuration pages.
//
//    Return Value
//      TRUE if we handle a message, FALSE if we don't or there was an error
//      in the data
//
BOOL STDAPICALLTYPE WizardDlgProc (HWND           hDlg,
                                   UINT           message,
                                   WPARAM         wParam,
                                   LPARAM         lParam)
{
    static SPropValue spvWizardProps[WIZARD_PROPERTIES] = { 0 };
    static TCHAR szDisplayName[MAX_STRING_SIZE+1];
    static TCHAR szServerName[MAX_STRING_SIZE+1];
    static TCHAR szMailboxName[MAX_ALIAS_SIZE+1];
    static TCHAR szStoredPassword[MAX_PASSWORD_SIZE+1];
    static TCHAR szEnterPassword[MAX_PASSWORD_SIZE+1];
    static DWORD dwMailboxID;
    static UINT iPageNum;
    switch (message)
    {
        case WM_INITDIALOG:
            // If the control have not been initialized do so
            if (fInitalizeControls)
            {
                // Send output to a console window BUT NOT to the COM1
                InitTraces (TRACES_CONSOLE | TRACES_NO_COM_OUTPUT);
                dwMailboxID = 0;
                // Set the flag to avoid re-initialization
                fInitalizeControls = FALSE;
                // Limit the maximum number of character users can enter for the mailbox PASSWORD
                Edit_LimitText  (GetDlgItem (hDlg, IDC_PASSWORD), MAX_PASSWORD_SIZE);
                PrivInitialize3DCtl (ghInstance);
                iPageNum = 0;
            }
            return TRUE;

        case WIZ_QUERYNUMPAGES :
            // Return the number of pages the our provider needs to display
            return (BOOL)2;

        case WM_CLOSE :
            pPropObj->Release();
            pPropObj = NULL;
            PrivUninitialize3DCtl (ghInstance);
            // Close down the log file/com port/console window/etc.
            UnInitTraces();
            return TRUE;

        case WM_COMMAND :
            switch (LOWORD(wParam))
            {
                case IDC_BROWSE_MB :
                    if (S_OK == ShowServerAccounts  (hDlg,
                                                     ghInstance,
                                                     szServerName,
                                                     szMailboxName,

szDisplayName,
                                                     szStoredPassword,
                                                     &dwMailboxID))
                    {
                        SetWindowText (GetDlgItem (hDlg, IDC_MAILBOXNAME), szMailboxName);
                        SetWindowText (GetDlgItem (hDlg, IDC_USER_NAME), szDisplayName);
                    }
                    return TRUE;

                case WIZ_NEXT:
                    switch (iPageNum)
                    {
                        // We are about to leave a page in each of the
                        // following cases. Here we should get the data from
                        // the controls in that page, validate it if necessary
                        // and copy the validated data into the property array
                        case 0:
                            break;

                        case 1:
                            if (!GetWindowText (GetDlgItem (hDlg, IDC_SERVERNAME),
                                                szServerName,
                                                sizeof(szServerName)))
                            {
                                MessageBeep (MB_ICONEXCLAMATION);
                                PrivateMessageBox (IDS_MSG_NEED_SERVER_NAME, hDlg);
                                return FALSE;
                            }
                            if (!IsValidServerName (szServerName))
                            {
                                MessageBeep (MB_ICONEXCLAMATION);
                                PrivateMessageBox (IDS_MSG_INVALID_SERVER_NAME, hDlg);
                                return FALSE;
                            }
                            if (!IsWINDSServerAvailable (szServerName))
                            {
                                PrivateMessageBox (IDS_MSG_SERVER_NOT_FOUND, hDlg);
                                return FALSE;
                            }
                            if (fDownloadMBList)
                            {
                                if (S_OK == ShowServerAccounts  (hDlg,
                                                                 ghInstance,
                                                                 szServerName,
                                                                 szMailboxName,
                                                                 szDisplayName,
                                                                 szStoredPassword,
                                                                 &dwMailboxID))
                                {
                                    SetWindowText (GetDlgItem (hDlg, IDC_MAILBOXNAME), szMailboxName);
                                    SetWindowText (GetDlgItem (hDlg, IDC_USER_NAME), szDisplayName);
                                    fDownloadMBList = FALSE;
                                }
                                else
                                {
                                    return FALSE;
                                }
                            }
                            break;

                        case 2:
                            if (!GetWindowTextLength (GetDlgItem (hDlg, IDC_PASSWORD)))
                            {
                                MessageBeep (MB_ICONEXCLAMATION);
                                PrivateMessageBox (IDS_MSG_SELECT_MB_NAME, hDlg);
                                SetFocus (GetDlgItem (hDlg, IDC_BROWSE_MB));
                                return FALSE;
                            }
                            if (!GetWindowText (GetDlgItem (hDlg, IDC_PASSWORD), szEnterPassword, sizeof(szEnterPassword)))
                            {
                                MessageBeep (MB_ICONEXCLAMATION);
                                PrivateMessageBox (IDS_MSG_NEED_PASSWORD, hDlg);
                                SetFocus (GetDlgItem (hDlg, IDC_PASSWORD));
                                return FALSE;
                            }
                            if (lstrcmp (szEnterPassword, szStoredPassword))
                            {
                                MessageBeep (MB_ICONEXCLAMATION);
                                PrivateMessageBox (IDS_MSG_INVALID_MB_PASSWORD, hDlg);
                                SetFocus (GetDlgItem (hDlg, IDC_PASSWORD));
                                return FALSE;
                            }
                            spvWizardProps[SERVER_NAME].ulPropTag  = PR_SMP_REMOTE_SERVER;
                            spvWizardProps[SERVER_NAME].Value.LPSZ = szServerName;
                            spvWizardProps[MAILBOX_NAME].ulPropTag  = PR_SMP_MAILBOX_NAME;
                            spvWizardProps[MAILBOX_NAME].Value.LPSZ = szMailboxName;
                            spvWizardProps[MAILBOX_ID].ulPropTag  = PR_SMP_MAILBOX_ID;
                            spvWizardProps[MAILBOX_ID].Value.l = dwMailboxID;
                            spvWizardProps[USER_NAME].ulPropTag  = PR_SMP_USER_NAME;
                            spvWizardProps[USER_NAME].Value.LPSZ = szDisplayName;
                            spvWizardProps[PASSWORD].ulPropTag  = PR_SMP_MAILBOX_PASSWORD;
                            spvWizardProps[PASSWORD].Value.LPSZ = szStoredPassword;
                            // Since this is the last page in our configuration
                            // we must save the obtained data into the
                            // IMAPIProp object that the wizard gave us. In
                            // turn this data will be saved in the profile
                            // section of this provider
                            ASSERT (pPropObj);
                            if (pPropObj)
                            {
                                HRESULT hResult = pPropObj->SetProps (WIZARD_PROPERTIES, spvWizardProps, NULL);
                                if (hResult)
                                {
                                    // Something went wrong
                                    TraceResult ("WizardDlgPrc: Failed to set props", hResult);
                                    return FALSE;
                                }
                            }
                            break;

                        default:
                            // We should not get here. No pages after three
                            ASSERT (FALSE);
                            break;
                    }
                    // Disable the page we were in and enable the next one
                    // before it is display by the wizard
                    TogglePage (hDlg, iPageNum, FALSE);
                    TogglePage (hDlg, ++iPageNum, TRUE);
                    return TRUE;

                case WIZ_PREV:
                    // Disable the page we were in and enable the next one
                    // before it is display by the wizard
                    TogglePage (hDlg, iPageNum, FALSE);
                    TogglePage (hDlg, --iPageNum, TRUE);
                    return TRUE;
            }
            break;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    TogglePage()
//
//    Parameters
//      hDlg        Handle to the dialogs where the controls are located
//      iPage       Number of the page we are going to toggle
//      fState      State of the controls: TRUE shows them, FALSE hides them
//
//    Purpose
//      This function will hide or show the controls for the page number
//      specified in the iPage parameter.
//
//    Return Value
//      None
//
void WINAPI TogglePage (HWND hDlg, UINT iPage, BOOL fState)
{
    // What page is it?
    HWND hEditCtl = NULL;
    switch (iPage)
    {
        // Toggle the controls that should be displayed for a particular page
        case 1 :
            EnableWindow (GetDlgItem (hDlg, IDC_STATIC1), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_STATIC1), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_INFO1), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_INFO1), fState ? SW_SHOW : SW_HIDE);
            hEditCtl = GetDlgItem (hDlg, IDC_SERVERNAME);
            break;

        case 2 :
            EnableWindow (GetDlgItem (hDlg, IDC_STATIC2), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_STATIC2), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_STATIC3), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_STATIC3), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_STATIC4), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_STATIC4), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_INFO2), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_INFO2), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_MAILBOXNAME), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_MAILBOXNAME), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_USER_NAME), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_USER_NAME), fState ? SW_SHOW : SW_HIDE);
            EnableWindow (GetDlgItem (hDlg, IDC_BROWSE_MB), fState);
            ShowWindow   (GetDlgItem (hDlg, IDC_BROWSE_MB), fState ? SW_SHOW : SW_HIDE);
            hEditCtl = GetDlgItem (hDlg, IDC_PASSWORD); // We want the button to have the focus by default
            break;
    }
    if (hEditCtl)
    {
        EnableWindow (hEditCtl, fState);
        ShowWindow   (hEditCtl, fState ? SW_SHOW : SW_HIDE);
        if (fState)
        {
            SetFocus (hEditCtl);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    MergeConfigProps()
//
//    Parameters
//      ulCfgProps      Number of properties for configuration
//      pCfgProps       Array of properties to configure the provider
//      pLogonProps     Array of properties found in the profile section of
//                      this service provider
//
//    Purpose
//      This function merges a property array that the user (or configuration
//      application) passed into ServiceEntry with the property array of
//      the provider, stored in the profile. This merged array is what
//      either gets passed into the configuration dialog (if UI is allowed)
//      and gets written out to the profile section for the service provider.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI MergeConfigProps (ULONG                  ulCfgProps,
                                 LPSPropValue           pCfgProps,
                                 LPSPropValue           pLogonProps)
{
    while (ulCfgProps--)
    {
        // For the particular property found, set the proptag (in case
        // the one in the profile was not valid), and the value. The
        // value will be the value passed in the configuration prop array.
        switch (pCfgProps[ulCfgProps].ulPropTag)
        {
            case PR_SMP_REMOTE_SERVER :
                if (FALSE == IsValidServerName (pCfgProps[ulCfgProps].Value.LPSZ))
                {
                    return E_INVALIDARG;
                }
                pLogonProps[SERVER_NAME] = pCfgProps[ulCfgProps];
                break;
            case PR_SMP_USER_NAME :
                pLogonProps[USER_NAME] = pCfgProps[ulCfgProps];
                break;
            case PR_SMP_MAILBOX_NAME :
                pLogonProps[MAILBOX_NAME] = pCfgProps[ulCfgProps];
                break;
            case PR_SMP_MAILBOX_ID :
                pLogonProps[MAILBOX_ID] = pCfgProps[ulCfgProps];
                break;
            case PR_SMP_MAILBOX_PASSWORD :
                pLogonProps[PASSWORD] = pCfgProps[ulCfgProps];
                break;
        }
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    OpenServiceProfileSection()
//
//    Parameters
//      pSupObj         Pointer to the provider support object
//      ppProfSectObj   Where we return a pointer to the service profile
//                      section of the provider
//      pfnFreeBuffer   MAPI memory allocation function
//
//    Purpose
//      This function opens the profile section of this service, where the
//      properties of a WINDS provider (AB, MS, or XP) are stored.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI OpenServiceProfileSection (LPMAPISUP    pSupObj,
                                          LPPROFSECT * ppProfSectObj,
                                          LPFREEBUFFER pfnFreeBuffer)
{
    SPropTagArray sptService = { 1, { PR_SERVICE_UID } };
    LPPROFSECT pProvProfSectObj;
    ULONG cValues;
    LPSPropValue pProp;
    // Get the PROVIDER profile section
    HRESULT hResult = pSupObj->OpenProfileSection (NULL,
                                                   MAPI_MODIFY,
                                                   &pProvProfSectObj);
    if (!hResult)
    {
        // Get the UID of the profile section of the service where this provider is installed
        hResult = pProvProfSectObj->GetProps (&sptService, fMapiUnicode, &cValues, &pProp);
        if (SUCCEEDED(hResult))
        {
            if (S_OK == hResult)
            {
                // Now, with the obtained UID, open the profile section of the service
                ASSERTMSG (PR_SERVICE_UID == pProp->ulPropTag, "Where is PR_SERVICE_UID");
                hResult = pSupObj->OpenProfileSection ((LPMAPIUID)pProp->Value.bin.lpb,
                                                       MAPI_MODIFY,
                                                       ppProfSectObj);
            }
            else
            {
                hResult = E_FAIL;
            }
            pfnFreeBuffer (pProp);
        }
        pProvProfSectObj->Release();
    }
    TraceResult ("OpenServiceProfileSection", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    PrivInitialize3DCtl()
//
//    Parameters
//      hInstance   Handle to the instance the control 3d library will
//                  subclass
//
//    Purpose
//      This function dynamically binds to the CTL3D library for UI apperance
//      on Windows NT. On Windows 95 the library may or may not be on the
//      system. We do this to have a single binary file that works on
//      Windows NT and Windows 95 and ensure that the WINDS service DLL loads
//      correctly in both platforms.
//      The CTL3D provider a less disparate user interface between the two OS's
//
//    Return Value
//      None
//
void WINAPI PrivInitialize3DCtl (HINSTANCE hInstance)
{
    if (NULL == ghUIControl)
    {
        ghUIControl = LoadLibrary (TEXT("CTL3D32.DLL"));
        if (ghUIControl)
        {
            pfn3DRegister = (LPCTL3DFUNCTION)GetProcAddress (ghUIControl, TEXT("Ctl3dRegister"));
            pfn3DUnregister = (LPCTL3DFUNCTION)GetProcAddress (ghUIControl, TEXT("Ctl3dUnregister"));
            pfn3DAutoSubclass = (LPCTL3DFUNCTION)GetProcAddress (ghUIControl, TEXT("Ctl3dAutoSubclass"));
            pfnDlgSubclassEx = (LPCTL3DSUBCLASS)GetProcAddress (ghUIControl, TEXT("Ctl3dSubclassDlgEx"));

            if (pfn3DRegister &&
                pfn3DUnregister &&
                pfn3DAutoSubclass &&
                pfnDlgSubclassEx)
            {
                pfn3DRegister (hInstance);
                pfn3DAutoSubclass (hInstance);
            }
            else
            {
                PrivUninitialize3DCtl (hInstance);
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    PrivUninitialize3DCtl()
//
//    Parameters
//      hInstance   Handle to the instance the control 3d library will
//                  de-attach from.
//
//    Purpose
//      Unregister the 3D UI control stuff and unload the library of the
//      3D controls.
//
//    Return Value
//      None
//
void WINAPI PrivUninitialize3DCtl (HINSTANCE hInstance)
{
    if (ghUIControl)
    {
        if (pfn3DUnregister)
        {
            pfn3DUnregister (hInstance);
        }
        FreeLibrary (ghUIControl);
        ghUIControl = NULL;
        pfn3DRegister = NULL;
        pfn3DUnregister = NULL;
        pfn3DAutoSubclass = NULL;
        pfnDlgSubclassEx = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CheckForPendingMessages()
//
//    Parameters
//      szServerName        Name of the server we are going to connect
//      szMailboxName       Name of the mailbox we are going to check
//      pulMsgWaiting       Number of messages waiting for download
//
//    Purpose
//      This function queries the remote server for how many messages are
//      waiting in the specified mailbox.
//      If there are not messages waiting, the function return S_FALSE.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI CheckForPendingMessages (LPTSTR  szServerName,
                                        LPTSTR  szMailboxName,
                                        ULONG * pulMsgWaiting)
{
    HRESULT hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    RpcTryExcept
    {
        hResult = RemoteCheckNewMail ((WINDS_RPC_STRING)szMailboxName, pulMsgWaiting);
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    #ifdef _DEBUG
    if (hResult && S_FALSE != hResult)
    {
        TraceResult ("CheckForPendingMessages", hResult);
    }
    #endif // _DEBUG
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    GetNextMailboxMsg()
//
//    Parameters
//      szServerName        Name of the server we are going to connect
//      szMailboxName       Name of the mailbox we are going to get the
//                          messages from
//      szLocalMsgFile      Name of the local file where the message
//                          should be placed.
//
//    Purpose
//      This function connect to the server an request the next message to
//      be downloaded for the mailbox specified in call. If there are no
//      more messages to download, we return S_FALSE.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI GetNextMailboxMsg (LPTSTR szServerName,
                                  LPTSTR szMailboxName,
                                  LPTSTR szLocalMsgFile)
{
    HRESULT hResult = BindToServer (szServerName);
    if (hResult)
    {
        return hResult;
    }
    long lPipeNumber;
    HANDLE hPipe, hFile;
    TCHAR szPipeName[64];
    BYTE abBuffer[IO_BUFFERSIZE];
    DWORD dwBytesRead, dwBytesWritten;
    RpcTryExcept
    {
        hResult = RemoteOpenOneMsgDownloadPipe ((WINDS_RPC_STRING)szMailboxName, &lPipeNumber);
        if (!hResult)
        {
            wsprintf (szPipeName, PIPE_NAME_FORMAT, szServerName, lPipeNumber);
            hPipe = CreateFile (szPipeName,
                                GENERIC_READ,
                                0,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
            if (INVALID_HANDLE_VALUE == hPipe)
            {
                hResult = HRESULT_FROM_WIN32(GetLastError());
                TraceResult ("GetHeadersFromServer: Failed to open pipe to server", hResult);
            }
            else
            {
                hFile = CreateFile (szLocalMsgFile,
                                    GENERIC_WRITE,
                                    0,
                                    NULL,
                                    CREATE_ALWAYS,
                                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                                    NULL);
                if (INVALID_HANDLE_VALUE == hFile)
                {
                    hResult = HRESULT_FROM_WIN32(GetLastError());
                    TraceResult ("GetNextMailboxMsg: Failed to open local headers file", hResult);
                }
                else
                {
                    do
                    {
                        // Read the pipe handle.
                        if (!ReadFile (hPipe, abBuffer, IO_BUFFERSIZE, &dwBytesRead, NULL))
                        {
                            hResult = HRESULT_FROM_WIN32(GetLastError());
                            if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)   != hResult &&   // For Windows NT
                                HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) != hResult)     // For Windows 95
                            {
                                // There was an error and we can't continue
                                TraceResult ("GetNextMailboxMsg: Failed to read from the pipe", hResult);
                            }
                            else
                            {
                                // If the pipe was broken, it means the server finished writing
                                // to the it, so we are finished reading from it.
                                hResult = S_OK;
                            }
                        }
                        if (dwBytesRead)
                        {
                            if (!WriteFile (hFile, abBuffer, dwBytesRead, &dwBytesWritten, NULL))
                            {
                                hResult = HRESULT_FROM_WIN32(GetLastError());
                                TraceResult ("GetNextMailboxMsg: Failed to write to local header file", hResult);
                            }
                        }
                    } while (dwBytesRead && !hResult);
                    CloseHandle (hFile);
                }
                CloseHandle (hPipe);
            }
        }
    }
    RpcExcept(1)
    {
        // If we got here is because there was an error while call was made
        // or when it was about to be made.
        hResult = RpcExceptionCode();
        if (RPC_S_SERVER_UNAVAILABLE == hResult)
        {
            hResult = HRESULT_FROM_WIN32(ERROR_HOST_UNREACHABLE);
        }
        else
        {
            hResult = MAKE_HRESULT(1, FACILITY_RPC, hResult);
        }
    }
    RpcEndExcept
    #ifdef _DEBUG
    if (hResult && S_FALSE != hResult)
    {
        TraceResult ("GetNextMailboxMsg", hResult);
    }
    #endif // _DEBUG
    return hResult;
}

// End of file for COMWINDS.CPP


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by XPWDSR.RC
//
#define IDD_CFG_XPCONNECT                   500
#define IDC_UPDATE_HEADERS                  1028
#define IDS_XP_STATUS_AVAILABLE             7000
#define IDS_XP_STATUS_READY                 7001
#define IDS_XP_STATUS_FLUSHING_INBOUND      7002
#define IDS_XP_STATUS_FLUSHING_OUTBOUND     7003
#define IDS_XP_STATUS_TIMER_EVENT           7004
#define IDS_XP_STATUS_INBOUND_AND_HEADERS   7005
#define IDS_SUCCESS                         8000
#define IDS_E_FAIL                          8001
#define IDS_E_OUTOFMEMORY                   8002
#define IDS_E_INVALIDARG                    8003
#define IDS_E_NOINTERFACE                   8004
#define IDS_E_ACCESSDENIED                  8005
#define IDS_E_BUSY                          8006
#define IDS_E_NO_SUPPORT                    8007
#define IDS_E_NOT_FOUND                     8008
#define IDS_E_UNKNOWN_FLAGS                 8009
#define IDS_E_VERSION                       8010
#define IDS_E_INVALID_ENTRYID               8011
#define IDS_E_USER_CANCEL                   8012
#define IDS_E_DISK_ERROR                    8013
#define IDS_E_CORRUPT_DATA                  8014
#define IDS_E_BAD_CHARWIDTH                 8015
#define IDS_E_UNCONFIGURED                  8016
#define IDS_CO_E_NOTINITIALIZED             8017
#define IDS_W_ERRORS_RETURNED               8018
#define IDS_E_UNKNOWN_ERROR                 8100
#define IDS_RPC_ERROR                       8101
#define IDS_SYSTEM_ERROR                    8102
#define IDS_REMOTE_ERROR                    8103
#define IDS_MSG_BAD_TIME_FORMAT             9000
#define IDS_REMOTE_DOWNLOADING              9001
#define IDS_REMOTE_REFRESHING_HEADERS       9002
#define IDS_REMOTE_PROCESSING_MSGS          9003

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1029
#define _APS_NEXT_SYMED_VALUE           10000
#endif
#endif


UISRVCS.H

#define IDD_CFG_USERINFO                    25000
#define IDD_SELECTMB                        25001
#define IDD_PASSWORD                        25002
#define IDD_NEW_PASSWORD                    25003
#define IDD_ENTER_PASSWORD                  25004

#define IDC_STATIC                          -1

#define IDC_STATIC1                         26000
#define IDC_STATIC2                         26001
#define IDC_STATIC3                         26002
#define IDC_STATIC4                         26003
#define IDC_STATIC5                         26004
#define IDC_STATIC6                         26005
#define IDC_STATIC7                         26006
#define IDC_STATIC8                         26007
#define IDC_LIST                            26008
#define IDC_INFO1                           26009
#define IDC_INFO2                           26010
#define IDC_INFO3                           26011
#define IDC_SERVERNAME                      26012
#define IDC_USER_NAME                       26013
#define IDC_MAILBOXNAME                     26014
#define IDC_UPLOAD_TIME                     26015
#define IDC_PASSWORD                        26016
#define IDC_PASSWORD_CONFIRM                26017
#define IDC_OLD_PASSWORD                    26018
#define IDC_BROWSE_MB                       26019
#define IDC_CHANGE_PASSWORD                 26020
#define IDC_CONNECT_ONLINE                  26021
#define IDC_CONNECT_OFFLINE                 26022

#define IDS_MSG_SELECT_MB_NAME              30000
#define IDS_MSG_SERVER_NOT_FOUND            30001
#define IDS_MSG_NEED_SERVER_NAME            30002
#define IDS_MSG_INVALID_SERVER_NAME         30003
#define IDS_MSG_INVALID_MB_PASSWORD         30004
#define IDS_MSG_NEW_PASSWORD_NO_MATCH       30005
#define IDS_MSG_OLD_PASSWORD_NO_MATCH       30006
#define IDS_MSG_NEED_PASSWORD_CONFIRM       30007
#define IDS_MSG_NEED_PASSWORD               30008
#define IDS_MSG_NEED_OLD_PASSWORD           30009
#define IDS_MSG_NO_SERVER_ONLINE            30010
#define IDS_MSG_LOGON_INVALID_USER          30011
#define IDS_MSG_LOGON_INVALID_USER_NAME     30012
#define IDS_MSG_LOGON_NO_CONNECT            30013
#define IDS_MSG_NO_SERVER_GO_OFFLINE        30014
#define IDS_MSG_CONFIG_INVALID_USER         30015
#define IDS_MSG_CONFIG_INVALID_USER_NAME    30016
#define IDS_MSG_CONFIG_NO_CONNECT           30017
#define IDS_MSG_REMOTE_FOLDER_VIEW_NAME     30018
#define IDS_MSG_SERVICE_NOT_PROPERLY_CFG    30019
#define IDS_MSG_SYSTEM_CALL_FAILED          30020
#define IDS_MSG_CONFIG_UI_IN_USE            30021

#define IDS_DELIVERY_ERROR_NO_SUCH_USER     35000
#define IDS_DELIVERY_ERROR_INVALID_ACCT     35001
#define IDS_DELIVERY_ERROR_OFFLINE_SERVER   35002
#define IDS_DELIVERY_ERROR_OUTOFMEMORY      35003
#define IDS_DELIVERY_ERROR_INVALID_PARAM    35004
#define IDS_DELIVERY_ERROR_ISTORAGE         35005
#define IDS_DELIVERY_ERROR_WIN32            35006
#define IDS_DELIVERY_ERROR_INVALID_ADDRESS  35007

#define IDS_SERVICE_PROPERTY_SHEETS_TITLE   40000


XPFOLDER.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPFOLDER.CPP
//
//  Description
//      This file implements the IMAPIFolder interface with the methods
//      specified in the MAPI SPI 1.0.
//
//  Authors
//      Irving De la Cruz
//      Les Thaler
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "XPWDSR.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CMAPIFolder()
//
//    Parameters
//      pStatusObj      Pointer to parent status object
//      pLogonObj       Pointer to logon session
//
//    Purpose
//      Constructor of the object. Parameters are passed to initialize the
//      data members with the appropiate values.
//
//    Return Value
//      None
//
CMAPIFolder::CMAPIFolder (CMAPIStatus * pStatusObj,
                          CXPLogon *    pLogonObj)
{
    InfoTrace ("CMAPIFolder: Constructor called");
    m_cRef = 1;
    m_pTableData = NULL;
    m_pLogon = pLogonObj;
    m_pLogon->AddRef();
    m_pStatusObj = pStatusObj;
//    m_pStatusObj->AddRef(); the status object holds a reference to us, so
//  addref will result in circular reference.
    m_fNoHeadersFile = FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::~CMAPIFolder()
//
//    Parameters
//      None
//
//    Purpose
//      Destructor of CMAPIFolder. Releases memory allocated for internal
//      properties during the life of this transport logon object.
//
//    Return Value
//      None
//
CMAPIFolder::~CMAPIFolder ()
{
    InfoTrace ("CMAPIFolder: Destructor called");
    if (m_pTableData)
    {
        m_pTableData->Release();
        m_pTableData = NULL;
    }
//    m_pStatusObj->Release(); we didn't addref it in the constructor.
    m_pStatusObj = NULL;
    m_pLogon->Release();
    m_pLogon = NULL;
    m_fNoHeadersFile = FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::QueryInterface()
//
//    Parameters
//      { Refer to OLE Documentation on this method }
//
//    Purpose
//      Returns a pointer to a interface requested if the interface is
//      supported and implemented by this object. If it is not supported, it
//      returns NULL
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    // OLE requires NULLing parameter
    *ppvObj = NULL;
    // If this is one of the two IID return an interface pointer to it
    if (riid == IID_IMAPIFolder || riid == IID_IMAPIContainer || riid == IID_IMAPIProp)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    // OLE's COM rules for IUnknown establish that if an interface B
    // was obtained using QueryInterface() on interface A, querying B for
    // its IUnknown IID, should return a pointer to the A interface.
    // Since this IMAPIFolder interface was obtained from IMAPIStatus::QueryInterface,
    // when requested for IUnknown, return the contained pointer to the parent status
    // object
    if (riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)m_pStatusObj;
        // Increase usage count of this object
        m_pStatusObj->AddRef();
        return S_OK;
    }
    // This object does not support the interface requested
    return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::Release()
//
//    Parameters
//      None
//
//    Purpose
//      Decrements the usage count of the Folder object.
//
//    Return Value
//      Usage count of this object.
//
STDMETHODIMP_(ULONG) CMAPIFolder::Release()
{
    ULONG ulCount = --m_cRef;
    if (!ulCount)
    {
        delete this;
    }
    // If the count is 1, is only the internal reference kept in the parent
    // status object. This is the time to flush the contents table to
    // the headers file.
    if (1 == ulCount)
    {
        CopyTableToFile();
    }
    return ulCount;
}

///////////////////////////////////////////////////////////////////////////////
// IMAPIProp virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetLastError()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This function finds a string for the hError passed. The hError is the
//      last error that occurred in one of our methods. Here we call a
//      function to allocate a MAPIERROR structure and passed the information
//      to the caller.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::GetLastError (HRESULT        hError,
                                        ULONG          ulFlags,
                                        LPMAPIERROR *  ppMAPIError)
{
    InfoTrace ("CMAPIFolder::GetLastError method called");
    Validate_IMAPIProp_GetLastError (this, hError, ulFlags, ppMAPIError);

    // Allocate and fill a MAPIERROR structure with the error passed in.
    // Use the common helper function GetMAPIError
    return GetMAPIError (ppMAPIError, ulFlags, hError, m_pLogon->GetInstance());
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SaveChanges()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      We don't implement this. Besides, folder objects are not transacted.
//
//    Return Value
//      S_OK always.
//
STDMETHODIMP CMAPIFolder::SaveChanges (ULONG ulFlags)
{
    InfoTrace ("CMAPIFolder::SaveChanges method called");
    Validate_IMAPIProp_SaveChanges (this, ulFlags);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Gets the properties of the remote message folder container. A client
//      requests the properties it needs by passing an array of property tags
//      in pTags. If the array pointer is NULL, we return ALL the properties
//      we have.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::GetProps (LPSPropTagArray pTags,
                                    ULONG           ulFlags,
                                    ULONG *         pcValues,
                                    LPSPropValue *  ppPropArray)
{
    InfoTrace ("CMAPIFolder::GetProps method called");
    Validate_IMAPIProp_GetProps (this, pTags, ulFlags, pcValues, ppPropArray);

    #ifdef DO_INFO_TRACES
    if (!pTags)
    {
        TraceInfoMessage ("CMAPIFolder::GetProps: All properties requested");
    }
    else
    {
        TraceMessage ("CMAPIFolder::GetProps Properties requested..."); for (ULONG j=0; j<pTags->cValues; j++) { TraceProp (pTags->aulPropTag[j]); }
    }
    #endif // DO_INFO_TRACES

    BOOL fGetAllProps = pTags ? FALSE : TRUE;
    if (!pTags)
    {
        pTags = (LPSPropTagArray)&sptFolderProps;
    }
    *pcValues = pTags->cValues;
    HRESULT hResult = gpfnAllocateBuffer (sizeof(SPropValue)*(*pcValues), (LPVOID *)ppPropArray);
    if (hResult)
    {
        TraceResult ("CMAPIFolder::GetProps: Memory allocation failed", hResult);
        return hResult;
    }
    if (fGetAllProps)
    {
        for (ULONG i=0; i<sptFolderProps.cValues; i++)
        {
            (*ppPropArray)[i].ulPropTag = sptFolderProps.aulPropTag[i];
        }
    }
    else
    {
        // A client of the IMAPIProp::GetProps() method can pass PT_UNSPECIFIED in the
        // tag of a property. Is the provider's responsiblity to return
        // the proper type of the property.
        for (ULONG k=0; k<pTags->cValues; k++)
        {
            for (ULONG i=0; i<sptFolderProps.cValues; i++)
            {
                if (PROP_ID(pTags->aulPropTag[k]) == PROP_ID(sptFolderProps.aulPropTag[i]))
                {
                    (*ppPropArray)[k].ulPropTag = sptFolderProps.aulPropTag[i];
                }
                else
                {
                    (*ppPropArray)[k].ulPropTag = pTags->aulPropTag[k];
                }
            }
        }
    }
    TCHAR szBuffer[64];
    BOOL fGotErrors = FALSE;
    LPVOID pSrc, pDst;
    ULONG cbSize;
    for (ULONG i=0; i<*pcValues; i++)
    {
        hResult = S_OK;
        switch ((*ppPropArray)[i].ulPropTag)
        {
            case PR_NULL :
                (*ppPropArray)[i].Value.err = S_OK;
                break;
            case PR_CREATION_TIME :
                if (m_fNoHeadersFile)
                {
                    hResult = MAPI_E_NOT_FOUND;
                }
                else
                {
                    (*ppPropArray)[i].Value.ft = m_ftLastUpdate;
                }
                break;
            case PR_CONTENT_COUNT :
                hResult = MAPI_E_NOT_FOUND;
                if (m_pTableData)
                {
                    LPMAPITABLE pTable;
                    if (!m_pTableData->HrGetView (NULL, NULL, 0, &pTable))
                    {
                        if (!pTable->GetRowCount (0, &(*ppPropArray)[i].Value.ul))
                        {
                            hResult = S_OK;
                        }
                        pTable->Release();
                    }
                }
                break;
            case PR_ASSOC_CONTENT_COUNT :
                (*ppPropArray)[i].Value.l = 0;
                break;
            case PR_ACCESS :
                (*ppPropArray)[i].Value.l = MAPI_ACCESS_READ;
                break;
            case PR_ACCESS_LEVEL :
                (*ppPropArray)[i].Value.l = 0;
                break;
            case PR_FOLDER_TYPE :
                (*ppPropArray)[i].Value.l = FOLDER_GENERIC;
                break;
            case PR_OBJECT_TYPE :
                (*ppPropArray)[i].Value.l = MAPI_FOLDER;
                break;
            case PR_DISPLAY_TYPE :
                (*ppPropArray)[i].Value.l = DT_FOLDER;
                break;
            case PR_SUBFOLDERS :
                (*ppPropArray)[i].Value.b = FALSE;
                break;
            case PR_CREATION_VERSION :
                (*ppPropArray)[i].Value.li.LowPart = TRANSPORT_VERSION_MAJOR;
                (*ppPropArray)[i].Value.li.HighPart = TRANSPORT_VERSION_MINOR;
                break;
            case PR_CURRENT_VERSION :
                (*ppPropArray)[i].Value.li.LowPart = TRANSPORT_VERSION_MAJOR;
                (*ppPropArray)[i].Value.li.HighPart = TRANSPORT_VERSION_MINOR;
                break;
            case PR_DISPLAY_NAME :
                szBuffer[0] = 0;
                LoadString (m_pLogon->GetInstance(), IDS_MSG_REMOTE_FOLDER_VIEW_NAME, szBuffer, 64);
                pSrc = (LPVOID)szBuffer;
                break;
            default:
                hResult = MAPI_E_NOT_FOUND;
                #ifdef DO_INFO_TRACES
                TraceRaw ("CMAPIFolder::GetProps: Not Found - "); TraceProp ((*ppPropArray)[i].ulPropTag);
                #endif // #DO_INFO_TRACES
                break;
        }
        if (!hResult &&
            (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_BINARY ||
             PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_TSTRING))
        {
            if (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_TSTRING)
            {
                cbSize = Cbtszsize ((LPTSTR)pSrc);
            }
            hResult = gpfnAllocateMore (cbSize, *ppPropArray, &pDst);
            if (!hResult)
            {
                CopyMemory (pDst, pSrc, cbSize);
                if (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_BINARY)
                {
                    (*ppPropArray)[i].Value.bin.cb  = cbSize;
                    (*ppPropArray)[i].Value.bin.lpb = (LPBYTE)pDst;
                }
                else
                {
                    (*ppPropArray)[i].Value.LPSZ = (LPTSTR)pDst;
                }
            }
        }
        if (hResult)
        {
            (*ppPropArray)[i].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID((*ppPropArray)[i].ulPropTag));
            (*ppPropArray)[i].Value.err = hResult;
            fGotErrors = TRUE;
        }
    }
    if (fGotErrors)
    {
        hResult = MAPI_W_ERRORS_RETURNED;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetPropList()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Returns an array with all the property tags that we support in
//      this folder
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::GetPropList (ULONG ulFlags, LPSPropTagArray * ppTags)
{
    InfoTrace ("CMAPIFolder::GetPropList method called");
    Validate_IMAPIProp_GetPropList (this, ulFlags, ppTags);

    ULONG cbTagsArraySize = CbNewSPropTagArray(NUM_FOLDER_PROPS);
    // Allocate the required amount of memory
    HRESULT hResult = gpfnAllocateBuffer (cbTagsArraySize, (LPVOID *)ppTags);
    TraceResult ("CMAPIFolder::GetPropList: Memory allocation failed", hResult);
    if (!hResult)
    {
        // Copy the contents of our property tag array into the buffer
        CopyMemory (*ppTags, &sptFolderProps, cbTagsArraySize);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::OpenProperty()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::OpenProperty (ULONG       ulPropTag,
                                        LPCIID      piid,
                                        ULONG       ulInterfaceOptions,
                                        ULONG       ulFlags,
                                        LPUNKNOWN * ppUnk)
{
    InfoTrace ("CMAPIFolder::OpenProperty method called");
    Validate_IMAPIProp_OpenProperty (this, ulPropTag,  piid, ulInterfaceOptions, ulFlags, ppUnk);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SetProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::SetProps (ULONG                   cValues,
                                    LPSPropValue            pPropArray,
                                    LPSPropProblemArray *   ppProblems)
{
    InfoTrace ("CMAPIFolder::SetProps method called");
    Validate_IMAPIProp_SetProps (this, cValues, pPropArray, ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::DeleteProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::DeleteProps (LPSPropTagArray       pPropTagArray,
                                       LPSPropProblemArray * ppProblems)
{
    InfoTrace ("CMAPIFolder::DeleteProps method called");
    Validate_IMAPIProp_DeleteProps (this, pPropTagArray, ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CopyTo()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CopyTo (ULONG                 ciidExclude,
                                  LPCIID                rgiidExclude,
                                  LPSPropTagArray       pExcludeProps,
                                  ULONG                 ulUIParam,
                                  LPMAPIPROGRESS        pProgress,
                                  LPCIID                pInterface,
                                  LPVOID                pDestObj,
                                  ULONG                 ulFlags,
                                  LPSPropProblemArray * ppProblems)
{
    InfoTrace ("CMAPIFolder::CopyTo method called");
    Validate_IMAPIProp_CopyTo (this,
                               ciidExclude,
                               rgiidExclude,
                               pExcludeProps,
                               ulUIParam,
                               pProgress,
                               pInterface,
                               pDestObj,
                               ulFlags,
                               ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CopyProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CopyProps (LPSPropTagArray        pIncludeProps,
                                     ULONG                  ulUIParam,
                                     LPMAPIPROGRESS         pProgress,
                                     LPCIID                 pInterface,
                                     LPVOID                 pDestObj,
                                     ULONG                  ulFlags,
                                     LPSPropProblemArray *  ppProblems)
{
    InfoTrace ("CMAPIFolder::CopyProps method called");
    Validate_IMAPIProp_CopyProps (this,
                                  pIncludeProps,
                                  ulUIParam,
                                  pProgress,
                                  pInterface,
                                  pDestObj,
                                  ulFlags,
                                  ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetNamesFromIDs()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::GetNamesFromIDs (LPSPropTagArray *    ppPropTags,
                                           LPGUID               pPropSetGuid,
                                           ULONG                ulFlags,
                                           ULONG *              pcPropNames,
                                           LPMAPINAMEID **      pppPropNames)
{
    InfoTrace ("CMAPIFolder::GetNamesFromIDs method called");
    Validate_IMAPIProp_GetNamesFromIDs (this,
                                        ppPropTags,
                                        pPropSetGuid,
                                        ulFlags,
                                        pcPropNames,
                                        pppPropNames);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetIDsFromNames()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::GetIDsFromNames (ULONG                cPropNames,
                                           LPMAPINAMEID *       ppPropNames,
                                           ULONG                ulFlags,
                                           LPSPropTagArray *    ppPropTags)
{
    InfoTrace ("CMAPIFolder::GetIDsFromNames method called");
    Validate_IMAPIProp_GetIDsFromNames (this,
                                        cPropNames,
                                        ppPropNames,
                                        ulFlags,
                                        ppPropTags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
// IMAPIContainer virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetContentsTable()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::GetContentsTable (ULONG ulFlags, LPMAPITABLE * ppTable)
{
    InfoTrace ("CMAPIFolder::GetContentsTable method called");
    Validate_IMAPIContainer_GetContentsTable (this, ulFlags, ppTable);

    if (MAPI_ASSOCIATED & ulFlags)
    {
        TraceMessage ("CMAPIFolder::GetContentsTable: We don't have associated messages");
        return MAPI_E_NO_SUPPORT;
    }
    HRESULT hResult;
    // If we don't have an ITableData interface pointer instantiated, create one.
    if (!m_pTableData)
    {
        hResult = CreateTable (&IID_IMAPITableData,
                               gpfnAllocateBuffer,
                               gpfnAllocateMore,
                               gpfnFreeBuffer,
                               NULL,
                               TBLTYPE_DYNAMIC,
                               PR_ENTRYID,
                               (LPSPropTagArray)&sptFldContents,
                               &m_pTableData);
        if (hResult)
        {
            TraceResult ("CMAPIFolder::GetContentsTable: Failed to create an ITableData object", hResult);
            return hResult;
        }

        // Populate the data for the table here
        hResult = FillContentsTable (m_pLogon->GetLocalHeadersCache());
        if (hResult)
        {
            TraceResult ("CMAPIFolder::GetContentsTable: Failed to populate table data", hResult);
            return hResult;
        }
    }
    hResult = m_pTableData->HrGetView (NULL, NULL, 0, ppTable);
    // If any, trace out the error code.
    TraceResult ("CMAPIFolder::GetContentsTable: Failed to create a view of the table data", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetHierarchyTable()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::GetHierarchyTable (ULONG          ulFlags,
                                             LPMAPITABLE *  ppTable)
{
    InfoTrace ("CMAPIFolder::GetHierarchyTable method called");
    Validate_IMAPIContainer_GetHierarchyTable (this, ulFlags, ppTable);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::OpenEntry()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::OpenEntry (ULONG          cbEntryID,
                                     LPENTRYID      pEntryID,
                                     LPCIID         pInterface,
                                     ULONG          ulFlags,
                                     ULONG *        pulObjType,
                                     LPUNKNOWN *    ppUnk)
{
    InfoTrace ("CMAPIFolder::OpenEntry method called");
    Validate_IMAPIContainer_OpenEntry (this,
                                       cbEntryID,
                                       pEntryID,
                                       pInterface,
                                       ulFlags,
                                       pulObjType,
                                       ppUnk);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SetSearchCriteria()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::SetSearchCriteria (LPSRestriction pRestriction,
                                             LPENTRYLIST    pContainerList,
                                             ULONG          ulSearchFlags)
{
    InfoTrace ("CMAPIFolder::SetSearchCriteria method called");
    Validate_IMAPIContainer_SetSearchCriteria (this, pRestriction, pContainerList, ulSearchFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetSearchCriteria()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::GetSearchCriteria (ULONG             ulFlags,
                                             LPSRestriction *  ppRestriction,
                                             LPENTRYLIST *     ppContainerList,
                                             ULONG *           pulSearchState)
{
    InfoTrace ("CMAPIFolder::GetSearchCriteria method called");
    Validate_IMAPIContainer_GetSearchCriteria (this, ulFlags, ppRestriction, ppContainerList, pulSearchState);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
// IMAPIFolder virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CreateMessage()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CreateMessage (LPCIID         pInterface,
                                         ULONG          ulFlags,
                                         LPMESSAGE *    ppMessage)
{
    InfoTrace ("CMAPIFolder::CreateMessage method called");
    Validate_IMAPIFolder_CreateMessage (this, pInterface, ulFlags, ppMessage);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CopyMessages()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CopyMessages (LPENTRYLIST     pMsgList,
                                        LPCIID          pInterface,
                                        LPVOID          pDestFolder,
                                        ULONG           ulUIParam,
                                        LPMAPIPROGRESS  pProgress,
                                        ULONG           ulFlags)
{
    InfoTrace ("CMAPIFolder::CopyMessages method called");
    Validate_IMAPIFolder_CopyMessages (this,
                                       pMsgList,
                                       pInterface,
                                       pDestFolder,
                                       ulUIParam,
                                       pProgress,
                                       ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::DeleteMessages()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::DeleteMessages (LPENTRYLIST       pMsgList,
                                          ULONG             ulUIParam,
                                          LPMAPIPROGRESS    pProgress,
                                          ULONG             ulFlags)
{
    InfoTrace ("CMAPIFolder::DeleteMessages method called");
    Validate_IMAPIFolder_DeleteMessages (this,
                                         pMsgList,
                                         ulUIParam,
                                         pProgress,
                                         ulFlags);
    return MAPI_E_NO_SUPPORT;

}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CreateFolder()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CreateFolder (ULONG           ulFolderType,
                                        LPTSTR          pszFolderName,
                                        LPTSTR          pszFolderComment,
                                        LPCIID          pInterface,
                                        ULONG           ulFlags,
                                        LPMAPIFOLDER *  ppFolder)
{
    InfoTrace ("CMAPIFolder::CreateFolder method called");
    Validate_IMAPIFolder_CreateFolder (this,
                                       ulFolderType,
                                       pszFolderName,
                                       pszFolderComment,
                                       pInterface,
                                       ulFlags,
                                       ppFolder);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CopyFolder()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::CopyFolder (ULONG             cbEntryID,
                                      LPENTRYID         pEntryID,
                                      LPCIID            pInterface,
                                      LPVOID            pDestFolder,
                                      LPTSTR            pszNewFolderName,
                                      ULONG             ulUIParam,
                                      LPMAPIPROGRESS    pProgress,
                                      ULONG             ulFlags)
{
    InfoTrace ("CMAPIFolder::CopyFolder method called");
    Validate_IMAPIFolder_CopyFolder (this,
                                     cbEntryID,
                                     pEntryID,
                                     pInterface,
                                     pDestFolder,
                                     pszNewFolderName,
                                     ulUIParam,
                                     pProgress,
                                     ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::DeleteFolder()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::DeleteFolder (ULONG           cbEntryID,
                                        LPENTRYID       pEntryID,
                                        ULONG           ulUIParam,
                                        LPMAPIPROGRESS  pProgress,
                                        ULONG           ulFlags)
{
    InfoTrace ("CMAPIFolder::DeleteFolder method called");
    Validate_IMAPIFolder_DeleteFolder (this,
                                       cbEntryID,
                                       pEntryID,
                                       ulUIParam,
                                       pProgress,
                                       ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SetReadFlags()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::SetReadFlags (LPENTRYLIST     pMsgList,
                                        ULONG           ulUIParam,
                                        LPMAPIPROGRESS  pProgress,
                                        ULONG           ulFlags)
{
    InfoTrace ("CMAPIFolder::SetReadFlags method called");
    Validate_IMAPIFolder_SetReadFlags (this,
                                       pMsgList,
                                       ulUIParam,
                                       pProgress,
                                       ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::GetMessageStatus()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::GetMessageStatus (ULONG       cbEntryID,
                                            LPENTRYID   pEntryID,
                                            ULONG       ulFlags,
                                            ULONG *     pulMessageStatus)
{
    InfoTrace ("CMAPIFolder::GetMessageStatus method called");
    Validate_IMAPIFolder_GetMessageStatus (this,
                                           cbEntryID,
                                           pEntryID,
                                           ulFlags,
                                           pulMessageStatus);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SetMessageStatus()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::SetMessageStatus (ULONG       cbEntryID,
                                            LPENTRYID   pEntryID,
                                            ULONG       ulNewStatus,
                                            ULONG       ulNewStatusMask,
                                            ULONG *     pulOldStatus)
{
    InfoTrace ("CMAPIFolder::SetMessageStatus method called");
    Validate_IMAPIFolder_SetMessageStatus (this,
                                           cbEntryID,
                                           pEntryID,
                                           ulNewStatus,
                                           ulNewStatusMask,
                                           pulOldStatus);

    ASSERT (TRANSPORT_MESSAGE_EID_SIZE == cbEntryID);
    SPropValue spvSearchProp;
    spvSearchProp.ulPropTag     = PR_ENTRYID;
    spvSearchProp.Value.bin.cb  = cbEntryID;
    spvSearchProp.Value.bin.lpb = (LPBYTE)pEntryID;

    LPSRow psrTargetRow;
    HRESULT hResult = m_pTableData->HrQueryRow (&spvSearchProp, &psrTargetRow, NULL);
    if (hResult)
    {
        TraceResult ("CMAPIFolder::SetMessageStatus: Failed to query row from internal table", hResult);
        return hResult;
    }

    BOOL bFound = FALSE;
    ULONG ulOldStatus;
    for (ULONG i=0; i<psrTargetRow->cValues; i++)
    {
        if (PR_MSG_STATUS == psrTargetRow->lpProps[i].ulPropTag)
        {
            bFound = TRUE;
            ulOldStatus = psrTargetRow->lpProps[i].Value.l;
            if (pulOldStatus)
            {
                *pulOldStatus = ulOldStatus;
            }
            ulOldStatus &= ulNewStatusMask;
            ulNewStatus &= ulNewStatusMask;
            if (ulNewStatus != ulOldStatus)
            {
                psrTargetRow->lpProps[i].Value.l &= ~ulNewStatusMask;
                psrTargetRow->lpProps[i].Value.l |= ulNewStatus;
                hResult= m_pTableData->HrModifyRow (psrTargetRow);
                // If any, trace out the error code.
                TraceResult ("CMAPIFolder::SetMessageStatus: Failed to modify row in the internal table", hResult);
            }
            break; // Out of the FOR() loop
        }
    }
    if (!hResult && !bFound)
    {
        TraceMessage ("CMAPIFolder::SetMessageStatus: Could not find property in table row");
        hResult = MAPI_E_NOT_FOUND;
    }
    gpfnFreeBuffer (psrTargetRow);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::SaveContentsSort()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::SaveContentsSort (LPSSortOrderSet pSortCriteria,
                                            ULONG           ulFlags)
{
    InfoTrace ("CMAPIFolder::SaveContentsSort method called");
    Validate_IMAPIFolder_SaveContentsSort (this, pSortCriteria, ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::EmptyFolder()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIFolder::EmptyFolder (ULONG            ulUIParam,
                                       LPMAPIPROGRESS   pProgress,
                                       ULONG            ulFlags)
{
    InfoTrace ("CMAPIFolder::EmptyFolder method called");
    Validate_IMAPIFolder_EmptyFolder (this, ulUIParam, pProgress, ulFlags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this class
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::FillContentsTable()
//
//    Parameters
//      pszHeaderFileName       String with a fully qualified path to the
//                              headers data file
//    Purpose
//      This function reads the file with the headers information to
///     populate the table of contents of the remote mail bag folder.
//      The data is store in a file whose path is given in the
//      pszHeaderFileName parameter.
//
//    Return Value
//      S_OK  If the function is successful or an hResult with the
//      error that occurr
//
STDMETHODIMP CMAPIFolder::FillContentsTable (LPTSTR pszHeaderFileName)
{
    ASSERT(pszHeaderFileName);
    if (NULL == m_pTableData)
    {
        // Nothing to do
        return S_OK;
    }

    HRESULT hResult = m_pTableData->HrDeleteRows (TAD_ALL_ROWS, NULL, NULL);
    TraceResult ("CMAPIFolder::FillContentsTable: Failed to delete all rows in internal table", hResult);

    // Check that the file exists. If it doesn't exist, then there is no headers file.
    HANDLE hFile = CreateFile (pszHeaderFileName,
                               GENERIC_READ,
                               0,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                               NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        // Set the internal flag that the user HAS NOT downloaded headers from the server
        m_fNoHeadersFile = TRUE;
        return S_OK;
    }
    else
    {
        // The time the headers file was last updated, is the last time the file was written to
        if (GetFileTime (hFile, NULL, NULL, &m_ftLastUpdate))
        {
            // Set the internal flag that the user HAS downloaded headers from the server
            m_fNoHeadersFile = FALSE;
        }
        TraceResult ("CMAPIFolder::FillContentsTable: Failed to get info about the headers file", ::GetLastError()); // Call the Win32 API
    }
    if (!GetFileSize (hFile, NULL))
    {
        CloseHandle(hFile);
        return S_OK;
    }

    SRow srTblRow;
    // Allocate an array of properties for the colums in the contents
    // tables of the folder
    hResult = gpfnAllocateBuffer (NUM_HDR_PROPS*sizeof(SPropValue), (LPVOID *)&(srTblRow.lpProps));
    if (hResult)
    {
        TraceResult ("CMAPIFolder::FillContentsTable: Failed to allocate memory for properties", hResult);
        CloseHandle (hFile);
        return hResult;
    }

    // Initialize the property tags in the row set
    srTblRow.cValues = NUM_HDR_PROPS;
    for (UINT i=0; i<NUM_HDR_PROPS; i++)
    {
        srTblRow.lpProps[i].ulPropTag = sptFldContents.aulPropTag[i];
    }
    // The MS Exchange remote viewer wants PR_MESSAGE_DOWNLOAD_TIME in the rows of the contents table.
    // However in our transport we compute it. This property gets set when we download the message.
    // This property is listed and shown here for information purposes only.
    srTblRow.lpProps[HDR_DOWNLOAD_TIME].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_MESSAGE_DOWNLOAD_TIME));
    srTblRow.lpProps[HDR_DOWNLOAD_TIME].Value.err = S_OK;
    srTblRow.lpProps[HDR_OBJ_TYPE].Value.l = MAPI_MESSAGE;

    DWORD dwBytesRead, dwInstanceKey = 1;
    srTblRow.lpProps[HDR_INST_KEY].Value.bin.cb  = sizeof (DWORD); // This is a fixed size.
    srTblRow.lpProps[HDR_INST_KEY].Value.bin.lpb = (LPBYTE)&dwInstanceKey;
    ULONG ulHeaderSize, iNextField;
    LONG lFieldSize, lValue = 0;
    LPTSTR pStr, pszMsgClass;
    TCHAR szDisplayTo[256], szSubject[256], szSender[256];
    BYTE abBuffer[IO_BUFFERSIZE];
    __try
    {
        do
        {
            ZeroMemory (szDisplayTo, 256);
            ZeroMemory (szSubject, 256);
            ZeroMemory (szSender, 256);
            if (ReadFile (hFile, &ulHeaderSize, sizeof(ULONG), &dwBytesRead, NULL))
            {
                if (ReadFile (hFile, abBuffer, ulHeaderSize, &dwBytesRead, NULL))
                {
                    if (dwBytesRead)
                    {
                        // Put the data into the property value members
                        srTblRow.lpProps[HDR_EID].Value.bin.cb  = TRANSPORT_MESSAGE_EID_SIZE; // This is a fixed size.
                        srTblRow.lpProps[HDR_EID].Value.bin.lpb = abBuffer;                   // It will only read the first 16 bytes
                        iNextField = TRANSPORT_MESSAGE_EID_SIZE;                              // Skip the entry ID

                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lFieldSize = atol (pStr);
                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        CopyMemory (szSender, &abBuffer[iNextField], lFieldSize);
                        iNextField += lstrlen (szSender) + 1;   // +1 to skip the comma
                        srTblRow.lpProps[HDR_SNDR].Value.LPSZ = szSender;
                        srTblRow.lpProps[HDR_REPR].Value.LPSZ = szSender;

                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lFieldSize = atol (pStr);
                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        CopyMemory (szDisplayTo, &abBuffer[iNextField], lFieldSize);
                        srTblRow.lpProps[HDR_DISP_TO].Value.LPSZ = szDisplayTo;

                        iNextField += lFieldSize + 1;       // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lFieldSize = atol (pStr);
                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        CopyMemory (szSubject, &abBuffer[iNextField], lFieldSize);
                        srTblRow.lpProps[HDR_SUBJ].Value.LPSZ = szSubject;
                        srTblRow.lpProps[HDR_NORM_SUBJ].Value.LPSZ = szSubject;

                        iNextField += lFieldSize + 1;       // +1 to skip the comma
                        pszMsgClass = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        srTblRow.lpProps[HDR_CLASS].Value.LPSZ = pszMsgClass;

                        iNextField += lstrlen (pszMsgClass) + 1;       // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_FLAGS].Value.l = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_SIZE].Value.l = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_PRIOR].Value.l = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_IMPORTANCE].Value.l = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_SENS].Value.l = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_TIME].Value.ft.dwLowDateTime = lValue;

                        iNextField += lstrlen (pStr) + 1;   // +1 to skip the comma
                        pStr = strtok ((LPTSTR)&abBuffer[iNextField], ",");
                        lValue = atol (pStr);
                        srTblRow.lpProps[HDR_TIME].Value.ft.dwHighDateTime = lValue;

                        srTblRow.lpProps[HDR_STAT].Value.l = 0;

                        srTblRow.lpProps[HDR_HASATTACH].Value.l = (BOOL)!!(srTblRow.lpProps[HDR_FLAGS].Value.l & MSGFLAG_HASATTACH);

                        hResult = m_pTableData->HrModifyRow (&srTblRow);
                        if (hResult)
                        {
                            TraceResult ("CMAPIFolder::FillContentsTable: Failed to add row to table data", hResult);
                        }
                        dwInstanceKey++;
                    }
                }
                else
                {
                    hResult = ::GetLastError(); // Call the Win32 API
                    TraceResult ("CMAPIFolder::FillContentsTable: Failed to read header data", hResult);
                    hResult = HRESULT_FROM_WIN32 (hResult);
                }
            }
            else
            {
                hResult = ::GetLastError(); // Call the Win32 API
                TraceResult ("CMAPIFolder::FillContentsTable: Failed to read header size", hResult);
                hResult = HRESULT_FROM_WIN32 (hResult);
            }
        } while (dwBytesRead && !hResult);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        TraceString1 ("CMAPIFolder::FillContentsTable: An exception occured. Exception code: %d", GetExceptionCode());
        hResult = MAPI_E_CORRUPT_DATA;
    }

    // Free the array and close the file
    gpfnFreeBuffer (srTblRow.lpProps);
    CloseHandle (hFile);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIFolder::CopyTableToFile()
//
//    Parameters
//      None.
//
//    Purpose
//      This function copies the data from the in-memory contents table into
//      the headers file. If the user selected any action on the messages in
//      the contents table, those changes will be reflected in the headers
//      file and restored the next time the remote viewer is brought up.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIFolder::CopyTableToFile()
{
    if (!m_pTableData)
    {
        return S_OK;
    }
    HRESULT hResult;
    HANDLE hFile = CreateFile (m_pLogon->GetLocalHeadersCache(),
                               GENERIC_WRITE,
                               0,
                               NULL,
                               CREATE_ALWAYS,
                               FILE_FLAG_SEQUENTIAL_SCAN,
                               NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        hResult = ::GetLastError();
        TraceResult ("CMAPIFolder::CopyTableToFile: Failed to open local headers file", hResult);
        return HRESULT_FROM_WIN32(hResult);
    }
    BOOL fDeleteFile = TRUE;
    LPMAPITABLE pTable = NULL;
    hResult = m_pTableData->HrGetView (NULL, NULL, 0, &pTable);
    if (hResult)
    {
        TraceResult ("CMAPIFolder::CopyTableToFile: Failed to get a view of the table data", hResult);
        goto ErrorExit;
    }

    hResult = pTable->SetColumns ((LPSPropTagArray)&sptFldContents, 0);
    if (hResult)
    {
        TraceResult ("CMAPIFolder::CopyTableToFile: Failed to set the columns", hResult);
        goto ErrorExit;
    }

    LPSPropValue pProps;
    BYTE abBuffer[IO_BUFFERSIZE];
    LPSRowSet pRows;
    LPVOID pVoid;
    DWORD i, cbSize, dwBytesWrittem;
    BOOL fSuccess;
    while (TRUE)
    {
        #define QUERY_SIZE  10
        hResult = pTable->QueryRows (QUERY_SIZE, 0, &pRows);
        if (hResult)
        {
            TraceResult ("CMAPIFolder::CopyTableToFile: Failed to query the rows in the table", hResult);
            goto ErrorExit;
        }
        // Did we finish reading all the rows?
        if (!pRows->cRows)
        {
            FreeProws (pRows);
            break; // Out of the WHILE() loop
        }
        for (i=0; i<pRows->cRows; i++)
        {
            pProps = pRows->aRow[i].lpProps;
            if (pProps[HDR_EID].ulPropTag != PR_ENTRYID ||
                pProps[HDR_SNDR].ulPropTag != PR_SENDER_NAME ||
                pProps[HDR_DISP_TO].ulPropTag != PR_DISPLAY_TO ||
                pProps[HDR_SUBJ].ulPropTag != PR_SUBJECT ||
                pProps[HDR_CLASS].ulPropTag != PR_MESSAGE_CLASS ||
                pProps[HDR_FLAGS].ulPropTag != PR_MESSAGE_FLAGS ||
                pProps[HDR_SIZE].ulPropTag != PR_MESSAGE_SIZE ||
                pProps[HDR_PRIOR].ulPropTag != PR_PRIORITY ||
                pProps[HDR_IMPORTANCE].ulPropTag != PR_IMPORTANCE ||
                pProps[HDR_SENS].ulPropTag != PR_SENSITIVITY ||
                pProps[HDR_TIME].ulPropTag != PR_MESSAGE_DELIVERY_TIME ||
                pProps[HDR_STAT].ulPropTag != PR_MSG_STATUS)
            {
                TraceMessage ("CMAPIFolder::CopyTableToFile: Missing property in a row. Skipping it");
                continue; // The FOR() loop
            }

            ZeroMemory (abBuffer, IO_BUFFERSIZE);

            // Put the properties' data into the buffer.
            pVoid = (LPVOID)abBuffer;
            CopyMemory (pVoid, pProps[HDR_EID].Value.bin.lpb, TRANSPORT_MESSAGE_EID_SIZE);
            cbSize = (DWORD)pVoid + TRANSPORT_MESSAGE_EID_SIZE;
            pVoid = (LPVOID)cbSize;

            wsprintf ((LPTSTR)pVoid,
                      TEXT("%d,%s,%d,%s,%d,%s,%s,%d,%d,%d,%d,%d,%d,%d,"),
                      lstrlen (pProps[HDR_SNDR].Value.LPSZ),
                      pProps[HDR_SNDR].Value.LPSZ,
                      lstrlen (pProps[HDR_DISP_TO].Value.LPSZ),
                      pProps[HDR_DISP_TO].Value.LPSZ,
                      lstrlen (pProps[HDR_SUBJ].Value.LPSZ),
                      pProps[HDR_SUBJ].Value.LPSZ,
                      pProps[HDR_CLASS].Value.LPSZ,
                      pProps[HDR_FLAGS].Value.l,
                      pProps[HDR_SIZE].Value.l,
                      pProps[HDR_PRIOR].Value.l,
                      pProps[HDR_IMPORTANCE].Value.l,
                      pProps[HDR_SENS].Value.l,
                      pProps[HDR_TIME].Value.ft.dwLowDateTime,
                      pProps[HDR_TIME].Value.ft.dwHighDateTime);

            cbSize = TRANSPORT_MESSAGE_EID_SIZE + lstrlen ((LPTSTR)pVoid);
            fSuccess = WriteFile (hFile, &cbSize, sizeof(DWORD), &dwBytesWrittem, NULL);
            if (fSuccess)
            {
                fSuccess = WriteFile (hFile, abBuffer, cbSize, &dwBytesWrittem, NULL);
                if (fSuccess)
                {
                    fDeleteFile = FALSE;
                }
            }
            if (!fSuccess)
            {
                hResult = ::GetLastError();
                TraceResult ("CMAPIFolder::CopyTableToFile: Write header data to file", hResult);
                hResult = HRESULT_FROM_WIN32(hResult);
                break; // Ouf of the FOR() loop
            }
        }
        FreeProws (pRows);
        if (hResult)
        {
            goto ErrorExit;
        }
    }

ErrorExit:
    if (pTable)
    {
        pTable->Release();
    }
    CloseHandle (hFile);
    if (hResult && fDeleteFile)
    {
        DeleteFile (m_pLogon->GetLocalHeadersCache());
    }
    m_pTableData->Release();
    m_pTableData = NULL;
    return hResult;
}

// End of file for XPFOLDER.CPP


XPLIST.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPLIST.CPP
//
//  Description
//      This file implements the classe CMsgQueue and CList used in this
//      transport to process remote messages. The two classes help us process
//      the list of messages selected in the Remote Viewer of a mail client.
//
//  Authors
//      Les Thaler
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "XPWDSR.H"

enum
{
    STAT,
    EID,
    NUM_PROPS
};
static const SizedSPropTagArray (NUM_PROPS, sptProps) =
{
    NUM_PROPS,
    {
        PR_MSG_STATUS,
        PR_ENTRYID
    }
};

///////////////////////////////////////////////////////////////////////////////
//    CMsgQueue::CMsgQueue()
//
//    Parameters
//      None.
//
//    Purpose
//      CMsgQueue class constructor.
//
//    Return Value
//      None.
//
CMsgQueue::CMsgQueue()
{
    m_ulItems  = 0;
    m_pHead = NULL;
    m_pTail = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//    CMsgQueue::~MsgQueue()
//
//    Parameters
//      None.
//
//    Purpose
//      CMsgQueue class destructor, walks the queue freeing each node
//      and the node's members.
//
//    Return Value
//      None
//
CMsgQueue::~CMsgQueue()
{
    if (!Empty())
    {
        PLIST_NODE pNode;
        do
        {
            pNode = m_pHead->pNext;
            delete m_pHead;
            m_pHead = pNode;
        } while (pNode);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CMsgQueue::Insert()
//
//    Parameters
//      pNode       Pointer to the node to queue
//
//    Purpose
//      Queues a node into the message queue
//
//    Return Value
//      None.
//
void WINAPI CMsgQueue::Insert (PLIST_NODE pNode)
{
    pNode->pNext = NULL;
    if (Empty())
    {
        m_pHead = m_pTail = pNode;
    }
    else
    {
        m_pTail->pNext = pNode;
        m_pTail = m_pTail->pNext;
    }
    m_ulItems++;
}

///////////////////////////////////////////////////////////////////////////////
//    CMsgQueue::Delete()
//
//    Parameters
//      None.
//
//    Purpose
//      Dequeues a node from the message queue. The caller is responsible for
//      freeing the node after use.
//
//    Return Value
//      The pointer to the dequeued node on success, NULL otherwise
//
PLIST_NODE WINAPI CMsgQueue::Delete()
{
    if (!m_ulItems)
    {
        return NULL;
    }
    PLIST_NODE pNode = m_pHead;
    if (m_pHead->pNext == NULL)
    {
        m_pHead = m_pTail = NULL;
    }
    else
    {
        m_pHead = m_pHead->pNext;
    }
    m_ulItems--;
    pNode->pNext = NULL;
    return pNode;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::CList()
//
//    Parameters
//      None
//
//    Purpose
//      Class constructor for the CList class, the list of downloaded
//      messages. Encapsulates the message queue and methods for manipulating
//      in the context of the CXPLogon object.
//
//    Return Value
//      None.
//
CList::CList()
{
    m_pToDownload = NULL;
    m_pDownloaded = NULL;
    m_pLogon      = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::~CList()
//
//    Parameters
//      None.
//
//    Purpose
//      CList class destructor. Frees the TO DO and DONE queues
//
//    Return Value
//      None.
//
CList::~CList()
{
    // free the msg queues
    delete m_pToDownload;
    m_pToDownload = NULL;
    delete m_pDownloaded;
    m_pDownloaded = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//    PropToMID()
//
//    Parameters
//      lProp       the MAPI MSGSTATUS_REMOTExx flag
//
//    Purpose
//      Translate the MAPI MSGSTATUS_REMOTExx flag bimask into an internal
//      message ID. This utility function is useful for walking the contents
//      table and checking which messages have been marked for downloading,
//      moving, etc.
//
//    Return Value
//      The message ID if lProp indicates the message was marked for a remote
//      operation, UNMARKED otherwise.
//
MID WINAPI PropToMID (long lProp)
{
    if (lProp & MSGSTATUS_REMOTE_DOWNLOAD)
    {
        if (lProp & MSGSTATUS_REMOTE_DELETE)
        {
            return MSG_MOVE;
        }
        else
        {
            return MSG_DOWNLOAD;
        }
    }
    else
    {
        if (lProp & MSGSTATUS_REMOTE_DELETE)
        {
            return MSG_DELETE;
        }
    }
    return UNMARKED;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::UpdateTableRow()
//
//    Parameters
//      pEID        Pointer to a message entry ID
//      midAction   Action taken on a row of the remote folder contents table
//
//    Purpose
//      Update a message's row in the contents table. Call this
//      method after a remote DOWNLOAD, MOVE or DELETE to synch the local
//      header contents table with what's really on the server.
//
//    Return Value
//      TRUE on success, FALSE otherwise
//
BOOL WINAPI CList::UpdateTableRow (LPBYTE pEID, MID midAction)
{
    SPropValue spvEID = { 0 };
    spvEID.ulPropTag = PR_ENTRYID;
    spvEID.Value.bin.cb = TRANSPORT_MESSAGE_EID_SIZE;
    spvEID.Value.bin.lpb = pEID;
    LPTABLEDATA pTableData = m_pLogon->GetRemoteFolderTableData();
    HRESULT hResult;
    if (midAction == MSG_DOWNLOAD)
    {
        LPSRow pRow;
        ULONG i;
        long lOldFlags;
        hResult = pTableData->HrQueryRow (&spvEID, &pRow, NULL);
        TraceResult ("CList::UpdateTableRow: Failed to query the table row", hResult);
        if (!hResult)
        {
            for (i=0; i<pRow->cValues; i++)
            {
                if (PR_MESSAGE_FLAGS == pRow->lpProps[i].ulPropTag)
                {
                    lOldFlags = pRow->lpProps[i].Value.l;
                    pRow->lpProps[i].Value.l |= MSGFLAG_READ;
                    if (pRow->lpProps[i].Value.l != lOldFlags)
                    {
                        hResult = pTableData->HrModifyRow (pRow);
                    }
                    break; // Out of the FOR() loop
                }
            }
            gpfnFreeBuffer (pRow);
        }
    }
    else
    {
        hResult = pTableData->HrDeleteRow (&spvEID);
    }
    TraceResult ("CList::UpdateTableRow: Failed to update the table row", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::Init()
//
//    Parameters
//      none.
//
//    Purpose
//      Initializes the CList object. Creates the 'TO DO' and "DONE'
//      message queues.
//
//    Return Value
//      TRUE on success, FALSE otherwise
//
BOOL WINAPI CList::Init()
{
    if (m_pToDownload)
    {
        delete m_pToDownload;
    }
    m_pToDownload = new CMsgQueue;
    if (m_pDownloaded)
    {
        delete m_pDownloaded;
    }
    m_pDownloaded = new CMsgQueue;

    if (!m_pToDownload || !m_pDownloaded)
    {
        delete m_pToDownload;
        delete m_pDownloaded;
        TraceMessage ("CList::Init: Failed to create download queues");
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::DownLoadMsgs()
//
//    Parameters
//      pTable          Pointer to the header contents table
//      ulRowCount      Count of entries in the contents table
//      hPipe           Handle to the named pipe opened to the server
//                      where the messages in being downloaded through.
//
//    Purpose
//      Walks through the contents table and checks the PR_MSG_STATUS of each
//      entry. Messages marked for download, move, or delete are allocated a
//      node for queuing the "TO DO' queue. Messages marked for download or
//      move additionally have a temporary filename created for them to hold
//      the downloaded message.
//
//      If the resulting 'TO DO' queue is non-empty, the download worker
//      thread is started and the CList object passed to it. This starts the
//      background processing of messages in the 'TO DO' queue.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI CList::DownLoadMsgs (LPMAPITABLE pTable, ULONG ulRowCount, HANDLE hPipe)
{
    HRESULT hResult = pTable->SetColumns ((LPSPropTagArray)&sptProps, 0);
    if (hResult)
    {
        TraceResult ("CList::DownLoadMsgs: Failed to set the columns", hResult);
        return hResult;
    }

    LPSRowSet pRows;
    hResult = pTable->QueryRows (ulRowCount, 0, &pRows);
    if (hResult)
    {
        TraceResult ("CList::DownLoadMsgs: Failed to get the rows", hResult);
        return hResult;
    }

    // construct the list of msgs to download/delete/move
    ASSERT (pRows->cRows);
    MID midMsgAction;
    for (ULONG i=0; i<pRows->cRows; i++)
    {
        ASSERT (PR_MSG_STATUS == pRows->aRow[i].lpProps[STAT].ulPropTag);
        ASSERT (PR_ENTRYID == pRows->aRow[i].lpProps[EID].ulPropTag);
        if (pRows->aRow[i].lpProps[STAT].ulPropTag == PR_MSG_STATUS &&
            pRows->aRow[i].lpProps[EID].ulPropTag  == PR_ENTRYID)
        {
            midMsgAction= PropToMID (pRows->aRow[i].lpProps[STAT].Value.l);
            // Queue message only if marked for remote processing
            if (UNMARKED != midMsgAction)
            {
                PLIST_NODE pNode= new LIST_NODE;
                if (NULL == pNode)
                {
                    TraceMessage ("CList::DownLoadMsgs: Failed to allocate new node");
                    hResult = E_OUTOFMEMORY;
                    break;
                }
                ZeroMemory (pNode, sizeof(LIST_NODE));
                pNode->fRetry = FALSE;

                // Only downloaded/moved msgs need the tempfile
                if (midMsgAction == MSG_DOWNLOAD || midMsgAction == MSG_MOVE)
                {
                    m_pLogon->GetMsgTempFileName (pNode->szFileName);
                }
                ASSERT (TRANSPORT_MESSAGE_EID_SIZE == pRows->aRow[i].lpProps[EID].Value.bin.cb);
                CopyMemory (pNode->Hdr.Info.EID, pRows->aRow[i].lpProps[EID].Value.bin.lpb, TRANSPORT_MESSAGE_EID_SIZE);
                pNode->Hdr.ulMID = pNode->OpStat = midMsgAction;
                // insert in 'TO DO' queue
                m_pToDownload->Insert (pNode);
            }
        }
    }
    FreeProws(pRows);
    if (!hResult)
    {
        ASSERT (!m_pToDownload->Empty());
        DownLoadNow (hPipe);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::DownLoadNow()
//
//    Parameters
//      hPipe           Handle to the named pipe opened to the server
//                      where the messages in being downloaded through.
//
//    Purpose
//      For each message in the 'TO DO' queue, check its requested
//      operation and format a control command to send over the pipe to
//      the server.
//
//      The control command will contain the requested operation code and
//      the remote entry ID of the message. The server will ACK the control
//      command if the operation can be performed on the server side. The
//      ACK message sent back by the server contains the size in bytes of
//      the data stream that the server will next write to the pipe.
//
//      If the server can't perform the requested operation (e.g. the message
//      can't be opened), it sends back an OP_FAILED command (NAK) and the
//      dequeued TO DO message is discarded. This is considered a non-fatal error.
//      and the rest of the queue is processed.
//
//      Downloaded and moved messages have a temporary file created for them before
//      the data transfer to the message store is started. A failure creating the
//      file is also non-fatal, the current message is discarded but processing
//      continues. After the temp file is created, data transfer is started on
//      the pipe. The stream is copied from the pipe into the temp file.
//
//      If the MOVE/DOWNLOAD operation succeeds, the 'TO DO' message is inserted
//      into the 'DONE' queue which is then passed to StartMessage for processing
//      in the spooler Poll() loop.
//
//      On exit, we send the server a 'HANG UP' command.
//
//    Return Value
//      None
//
void WINAPI CList::DownLoadNow (HANDLE hPipe)
{
    PLIST_NODE pNode;
    MSG_HDR CtrlMsg, InMsg;
    DWORD dwBytes;
    m_ulMsgCount = m_pToDownload->m_ulItems;
    for (ULONG i=0; i<m_ulMsgCount; i++)
    {
        pNode = m_pToDownload->Delete();
        if (NULL == pNode)
        {
            TraceMessage ("CList::DownLoadNow: Failed to get node from internal queue");
            continue; // The FOR() loop
        }
        CtrlMsg = pNode->Hdr;
        switch (pNode->Hdr.ulMID)
        {
            case MSG_MOVE :
            case MSG_DOWNLOAD :
                // We need a temp file where we download the server message temporeraly
                // until it gets picked up in IXPLogon::StartMessage()
                pNode->hFile = CreateFile (pNode->szFileName,
                                           GENERIC_READ | GENERIC_WRITE,
                                           FILE_SHARE_READ,
                                           NULL,
                                           CREATE_ALWAYS,
                                           FILE_ATTRIBUTE_TEMPORARY,
                                           NULL);
                if (INVALID_HANDLE_VALUE == pNode->hFile)
                {
                    pNode->OpStat = OP_FAILED;
                    TraceResult ("CList::DownLoadNow: Failed to create local file", GetLastError());
                    goto ProcessNextMessage;
                }
                // The node has a header structure with the command action to instruct
                // the server on what we need to do on this message
                if (!WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL))
                {
                    TraceResult ("CList::DownLoadNow: (MOVE/COPY) Failed to send control message to host", GetLastError());
                    goto ProcessNextMessage;
                }
                // Upon receiving the control header, the remote host sent a response
                if (!ReadFile (hPipe, &InMsg, sizeof(MSG_HDR), &dwBytes, NULL))
                {
                    TraceResult ("CList::DownLoadNow: (MOVE/COPY) Failed to receive message to data", GetLastError());
                    goto ProcessNextMessage;
                }
                // Check the result of the host and if successful, copy the message to a local file
                if (OP_FAILED == InMsg.ulMID ||
                    NO_ERROR != FileCopy (pNode->hFile, hPipe, InMsg.Info.ulMsgLen))
                {
                    goto ProcessNextMessage;
                }
                // If the message was moved from the remote server, we must remote the
                // row in the remote folder viewer.
                UpdateTableRow (pNode->Hdr.Info.EID, pNode->Hdr.ulMID);
                m_pDownloaded->Insert (pNode);
                pNode = NULL;
                break;

            case MSG_DELETE :
                if (!WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL))
                {
                    TraceResult ("CList::DownLoadNow: (DELETE) Failed to send control message to host", GetLastError());
                    goto ProcessNextMessage;
                }
                if (!ReadFile (hPipe, &InMsg, sizeof(MSG_HDR), &dwBytes, NULL))
                {
                    TraceResult ("CList::DownLoadNow: (DELETE) Failed to receive message to data", GetLastError());
                    goto ProcessNextMessage;
                }
                if (InMsg.ulMID != OP_FAILED)
                {
                    UpdateTableRow (pNode->Hdr.Info.EID, MSG_DELETE);
                }
                break;
            default :
                TraceMessage ("CList::DownLoadNow: Unknown action on this node");
                break;
        }
ProcessNextMessage:
        m_pLogon->UpdateProgress ((((i + 1) * 100) / m_ulMsgCount), REMOTE_ACTION_DOWNLOADING_MSGS);
        if (pNode)
        {
            if (pNode->hFile)
            {
                CloseHandle (pNode->hFile);
                DeleteFile (pNode->szFileName);
            }
            delete pNode;
        }
    }
    CtrlMsg.ulMID = GOODBYE;
    WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL);
    m_ulMsgLeft = m_ulMsgCount;
}

///////////////////////////////////////////////////////////////////////////////
//    CList::UpdateProgress()
//
//    Parameters
//      None
//
//    Purpose
//      Update the progress percentage in the status row after a message is
//      processed in IXPLogon::StartMessage()
//
//    Return Value
//      None
//
void WINAPI CList::UpdateProgress()
{
    if (m_ulMsgCount)
    {
        m_ulMsgLeft--;
        m_pLogon->UpdateProgress ((((m_ulMsgCount - m_ulMsgLeft) * 100) / m_ulMsgCount),
                                    REMOTE_ACTION_PROCESSING_MSGS);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    FileCopy()
//
//    Parameters
//      hDest       Handle of stream object receiving the data
//      hSrc        Handle of stream object whence data
//      dwMsgLen    Number of bytes to transfer
//
//    Purpose
//      Transfers dwMsgLen bytes from hSrc to hDest. The objects can be
//      open file handles, pipes, or any stream oriented object that uses
//      the Read/WriteFile semantics.
//
//    Return Value
//      NO_ERROR on success, ERROR_READ/WRITE_FAULT otherwise
//
long WINAPI FileCopy (HANDLE hDest, HANDLE hSrc, DWORD dwMsgLen)
{
    BYTE abBuffer[IO_BUFFERSIZE];
    DWORD dwRead, dwRemaining = dwMsgLen;
    BOOL bSuccess;
    long lResult = NO_ERROR;
    for (DWORD dwWritten=0 ; dwRemaining>0; dwRemaining -= dwWritten)
    {
        bSuccess = ReadFile (hSrc, abBuffer, min(dwRemaining, IO_BUFFERSIZE), &dwRead, NULL);
        if (!dwRead || !bSuccess)
        {
            lResult = ERROR_READ_FAULT;
            break;
        }
        bSuccess = WriteFile (hDest, abBuffer, dwRead, &dwWritten, NULL);
        if (!dwWritten || !bSuccess)
        {
            lResult = ERROR_WRITE_FAULT;
            break;
        }
    }
    if (lResult)
    {
        TraceResult ("CList::FileCopy: IO operation failed", GetLastError());
    }
    else
    {
        FlushFileBuffers (hDest);
        SetFilePointer (hDest, 0, NULL, FILE_BEGIN);
    }
    return lResult;
}

// End of file for XPLIST.CPP


XPLIST.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPLIST.H
//
//  Description
//      Data structures, class definitions, and prototypes for downloading
//      messages. Here define CList and CMsgQueue.
//      CList holds a pointer to two messages queues: Messages to download
//      and downloaded messages that are pending processing from the spooler.
//
//  Author
//      Les Thaler
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _XPLIST_H_
#define _XPLIST_H_

long WINAPI FileCopy (HANDLE hTemp, HANDLE hPipe, DWORD dwMsgLen);
MID WINAPI PropToMID (long lProp);

class CList;
class CXPLogon;

// node in queue of downloaded/to-be-downloaded messages
typedef struct _LIST_NODE
{
    struct _LIST_NODE *     pNext;
    HANDLE                  hFile;
    TCHAR                   szFileName[_MAX_PATH];
    MSG_HDR                 Hdr;
    MID                     OpStat;
    BOOL                    fRetry;
} LIST_NODE, *PLIST_NODE;

// FIFO queue of downloaded/to-be-downloaded messages
class CMsgQueue
{
friend CList;
///////////////////////////////////////////////////////////////////////////////
// Member functions specific to this class
//
public:
    void WINAPI Insert
                    (PLIST_NODE             pNode);
    PLIST_NODE WINAPI Delete
                    ();
    inline BOOL WINAPI Empty
                    () { return m_ulItems == 0;};

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
    CMsgQueue();
    ~CMsgQueue();

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    PLIST_NODE      m_pHead;
    PLIST_NODE      m_pTail;
public:
    ULONG           m_ulItems;
};

// Object for manipulating downloaded/to-be-downloaded queues
// including managing data transfer with remote server
class CList
{
///////////////////////////////////////////////////////////////////////////////
// Member functions specific to this class
//
public :
    BOOL WINAPI Init
                    ();
    STDMETHODIMP DownLoadMsgs
                    (LPMAPITABLE                pTable,
                     ULONG                      ulRowCount,
                     HANDLE                     hPipe);
    BOOL WINAPI UpdateTableRow
                    (LPBYTE                     pEID,
                     MID                        midAction);
    inline void WINAPI SetLogon
                    (CXPLogon *                 pLogon)
                    { m_pLogon = pLogon; }
    inline PLIST_NODE WINAPI GetDownloadNode
                    () { if (m_pDownloaded)
                         { return m_pDownloaded->Delete(); }
                         return NULL; }
    inline BOOL WINAPI AreTherePendingDownloads
                    () { if (m_pDownloaded)
                         { return (!m_pDownloaded->Empty()); }
                         return FALSE; }
    inline BOOL WINAPI QueuePendingMsgFile
                    (LPTSTR                     pstrFileName)
                    { PLIST_NODE pNode = new LIST_NODE;
                      if (pNode)
                      { ZeroMemory (pNode, sizeof(LIST_NODE));
                        lstrcpy (pNode->szFileName, pstrFileName);
                        m_pDownloaded->Insert (pNode);
                        return TRUE;
                      }
                      TraceMessage ("CLisg::QueuePendingMsgFile: Failed to allocate new node");
                      return FALSE; }
    void WINAPI UpdateProgress
                    ();
    inline void WINAPI ReQueueNode
                    (PLIST_NODE                 pNode)
                    { m_pToDownload->Insert (pNode); }

private :
    void WINAPI DownLoadNow
                    (HANDLE                     hPipe);
///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CList();
    ~CList();

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG               m_ulMsgCount;
    ULONG               m_ulMsgLeft;
    CXPLogon *          m_pLogon;           // We need access to his stuff
    CMsgQueue *         m_pToDownload;      // 'TO DO' queue
    CMsgQueue *         m_pDownloaded;      // 'DONE' queue
};

#endif // _XPLIST_H_

// End of file for XPLIST.H


XPLOGON.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPLOGON.CPP
//
//  Description
//      This file implements the IXPLogon interface with the methods specified
//      in the MAPI SPI 1.0.
//
//  Authors
//      Irving De la Cruz
//      Les Thaler
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
//
#include "XPWDSR.H"

#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

// Remark this line to turn verbose tracing OFF
MAPIUID guidABEntries = AB_UID_PROVIDER;
MAPIUID guidXPABEntries = XP_WINDS_AB_ENTRIES;
MAPIUID * puidWINDSEntries;
TCHAR gszProviderName[] = TEXT("WINDS Transport Service");
LPTSTR gszWINDSAddressType = WINDS_ADDRESS_TYPE;
LPTSTR * gpszXPAddressTypes;

void CALLBACK UploadTimerProc (HWND    hOwnerWnd,
                               UINT    Message,
                               UINT    idEvent,
                               DWORD   dwTime);

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::CXPLogon()
//
//    Parameters
//      hInstance               Instance of the provider DLL
//      pSupObj                 Pointer to IMAPISupport object used in
//                              CXPLogon methods
//      pszHeadersFile          Full file path where the remote headers are
//                              store in the local computer.
//      fGetHeaders             Weather or not to get the headers of the
//                              mailbox during a scheduled connection.
//      pUserMailboxInfo        Pointer to a MAILBOX_INFO structure with the
//                              information of the logged user as store in
//                              the WINDS server.
//      dwMailboxID             ID of the user in the WINDS system.
//      pszRemoteServer         Name of the remote WINDS server to which
//                              this transport communicates
//      hUIMutex                Handle to a WINDS mutex to allow single access
//                              to the UI configuration of this provider.
//
//    Purpose
//      Constructor of the object. Parameters are passed to initialize the
//      data members with the appropiate values.
//
//    Return Value
//      None
//
CXPLogon::CXPLogon (HINSTANCE       hInstance,
                    LPMAPISUP       pSupObj,
                    LPTSTR          pszHeadersFile,
                    BOOL            fGetHeaders,
                    PMAILBOX_INFO   pUserMailboxInfo,
                    DWORD           dwMailboxID,
                    LPTSTR          pszRemoteServer,
                    HANDLE          hUIMutex)
{
    InfoTrace ("CXPLogon: Constructor called");
    m_cRef           = 1;
    m_pIdentityProps = NULL;
    m_hInstance      = hInstance;
    m_pSupObj        = pSupObj;
    m_pStatusObj     = NULL;
    m_pIdentityProps = NULL;
    ZeroMemory (&m_stDelivTime, sizeof(SYSTEMTIME));
    m_uTimerID       = 0;
    m_hTimerWnd      = NULL;
    m_fABWDSInstalled = FALSE;
    m_fGetHeaders    = fGetHeaders;
    m_hUIMutex       = hUIMutex;
    m_raAction       = REMOTE_ACTION_IDLE;
    m_UserInfo       = *pUserMailboxInfo;
    ZeroMemory (&m_UserEID, CB_PRIVATE_EID);
    m_UserEID.uidGlobal = guidABEntries;
    m_UserEID.bVersion = ENTRYID_VERSION;
    m_UserEID.bObject = MAPI_MAILUSER;
    m_UserEID.dwObjID = dwMailboxID;

    m_pSupObj->AddRef();

    lstrcpy (m_szHeaders, pszHeadersFile);
    lstrcpy (m_szServer, pszRemoteServer);

    wsprintf (m_szAddress, TEXT("%s\\%s"), m_szServer, m_UserInfo.szMailboxName);

    // Initialize the messages download list object
    m_List.SetLogon(this);
    if (!m_List.Init())
    {
        throw CException (E_OUTOFMEMORY);
    }
    SetTransportState (WAITING);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::~CXPLogon()
//
//    Parameters
//      None
//
//    Purpose
//      Destructor of CXPLogon. Releases memory allocated for internal
//      properties during the life of this transport logon object.
//
//    Return Value
//      None
//
CXPLogon::~CXPLogon()
{
    InfoTrace ("CXPLogon: Destructor called");
    gpfnFreeBuffer (m_pIdentityProps);
    CloseHandle (m_hUIMutex);

    EmptyInboundQueue();

    // Delete CMAPIStatus Object
/*    if (m_pStatusObj)
    {
        m_pStatusObj->Release();
        m_pStatusObj = NULL;
    }
*/
    // Release the IMAPISupport object
    m_pSupObj->Release();
    m_pSupObj = NULL;

    // Just as a safety precaution, make sure the timer is off.
    StopUploadTimer();

    // Just in case the DLL is still hanging around
    PrivUninitialize3DCtl (m_hInstance);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::QueryInterface()
//
//    Parameters
//      { Refer to OLE Documentation on this method }
//
//    Purpose
//      Returns a pointer to a interface requested if the interface is
//      supported and implemented by this object. If it is not supported, it
//      returns NULL
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    // OLE requires NULLing parameter
    *ppvObj = NULL;
    // If this is one of the two IID return an interface pointer to it
    if (riid == IID_IXPLogon || riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    // This object does not support the interface requested
    return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
// IXPLogon virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::AddressType()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Called by the MAPI Spooler when initializing this XP logon object to
//      allow the transport to register the address it will handle
//
//    Return Value
//      S_OK always
//
STDMETHODIMP CXPLogon::AddressTypes (ULONG *        pulFlags,
                                     ULONG *        pcAdrType,
                                     LPTSTR **      pppAdrTypeArray,
                                     ULONG *        pcMAPIUID,
                                     LPMAPIUID **   pppMAPIUIDArray)
{
    InfoTrace ("CXPLogon::AddressTypes method called");
    CheckParameters_IXPLogon_AddressTypes (this,
                                           pulFlags,
                                           pcAdrType,
                                           pppAdrTypeArray,
                                           pcMAPIUID,
                                           pppMAPIUIDArray);

    // This is how many address type strings we are returning in pppAdrTypeArray
    *pcAdrType = 1;

    // Copy address types back to the MAPI pointer
    gpszXPAddressTypes = &gszWINDSAddressType;
    *pppAdrTypeArray = gpszXPAddressTypes;
    *pulFlags = fMapiUnicode;

    // The UID for routing message has to be set here. If it is not used, set it NULL.
    // In the case of WINDS transport registers support for the UID of
    // the WINDS address book's entry ID
    puidWINDSEntries = &guidABEntries;
    *pcMAPIUID = 1;
    *pppMAPIUIDArray = &puidWINDSEntries;
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::RegisterOptions()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This transport does not registers any per-recipient or per-message
//      option processing, so we return 0 options. And NULL in the OPTIONDATA
//      structure pointer.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::RegisterOptions (ULONG *         pulFlags,
                                        ULONG *         pcOptions,
                                        LPOPTIONDATA *  ppOptions)
{
    InfoTrace ("CXPLogon::RegisterOptions method called");
    CheckParameters_IXPLogon_RegisterOptions (this,
                                              pulFlags,
                                              pcOptions,
                                              ppOptions);
    *pulFlags = 0;
    *pcOptions = 0;
    *ppOptions = NULL;
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::TransportNotify()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Update the status row registered by this transport with MAPI
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::TransportNotify (ULONG * pulFlags, LPVOID * ppvData)
{
    InfoTrace ("CXPLogon::TransportNotify method called");
    CheckParameters_IXPLogon_TransportNotify (this,
                                              pulFlags,
                                              ppvData);

    ULONG ulOldStatus = GetTransportStatusCode();
    // Set appropriate status flags and re-register status row
    if (*pulFlags & NOTIFY_BEGIN_INBOUND)
    {
        InfoTrace ("CXPLogon::TransportNotify: Begin INBOUND logic");
        AddStatusBits (STATUS_INBOUND_ENABLED);
    }
    if (*pulFlags & NOTIFY_END_INBOUND)
    {
        InfoTrace ("CXPLogon::TransportNotify: Terminate INBOUND logic");
        RemoveStatusBits (STATUS_INBOUND_ENABLED);
    }
    if (*pulFlags & NOTIFY_BEGIN_OUTBOUND)
    {
        InfoTrace ("CXPLogon::TransportNotify: Begin OUTBOUND logic");
        AddStatusBits (STATUS_OUTBOUND_ENABLED);
        InitializeTimer();
        StartUploadTimer();
    }
    if (*pulFlags & NOTIFY_END_OUTBOUND)
    {
        InfoTrace ("CXPLogon::TransportNotify: Terminate OUTBOUND logic");
        RemoveStatusBits (STATUS_OUTBOUND_ENABLED);
        StopUploadTimer();
    }
    if (*pulFlags & NOTIFY_BEGIN_OUTBOUND_FLUSH)
    {
        InfoTrace ("CXPLogon::TransportNotify: Begin OUTBOUND flush");
        // If the spooler need to flush us for some reason, we put ourselves
        // in FLUSH and tell the spooler to give us any deferred messages
        // it has queued up for us.
        AddStatusBits (UPLOADING_MESSAGES); // Add these bits to the status code
        SetTransportState (READY);
        m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
    }
    if (*pulFlags & NOTIFY_END_INBOUND_FLUSH)
    {
        InfoTrace ("CXPLogon::TransportNotify: Terminate INBOUND flush");
        if (FALSE == m_List.AreTherePendingDownloads())
        {
            RemoveStatusBits (DOWNLOADING_MESSAGES); // Tell the spooler to take us off from inbound flush
            AddStatusBits (STATUS_OFFLINE); // The transport is now OFF line
            UpdateStatus (TRUE, TRUE);
        }
    }
    if (*pulFlags & NOTIFY_END_OUTBOUND_FLUSH)
    {
        InfoTrace ("CXPLogon::TransportNotify: Terminate OUTBOUND flush");
        if (GetTransportStatusCode() & UPLOADING_MESSAGES)
        {
            // When the spooler finished sending messages to our CXPLogon::SubmitMessage
            // method, he will call us to let us know there is nothing else we need to
            // flush, and we need to reset our status row.
            RemoveStatusBits (UPLOADING_MESSAGES); // Tell the spooler to take us off from outbound flush
            AddStatusBits (STATUS_OFFLINE); // The transport is now OFF line
            if (PENDING_RETURN_CODE != GetTransportState())
            {
                SetTransportState (WAITING);
            }
        }
    }

    // We get called here if user deletes/modifies queued msg in Outbox.
    // If the user modifies and resends it, we get called again at SubmitMessage.
    // If the user closes the message viewer or just saves it, it's removed
    // from the spooler queue and discarded.
    if (*pulFlags & NOTIFY_ABORT_DEFERRED)
    {
        InfoTrace ("CXPLogon::TransportNotify: Abort deferred message");
    }
    if (*pulFlags & NOTIFY_CANCEL_MESSAGE)
    {
        InfoTrace ("CXPLogon::TransportNotify: Cancel message");
    }
    if (ulOldStatus != GetTransportStatusCode())
    {
        UpdateStatus();
    }
    if (*pulFlags & NOTIFY_BEGIN_INBOUND)
    {
        // If we have pending message from a previous session, notify the
        // spooler that we are ready to give them to the store.
        if (m_List.AreTherePendingDownloads())
        {
            // Put ourselves into flush mode and have the spooler call us until
            // we are finished putting the downloaded messages into the default store.
            AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
            UpdateStatus();
        }
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::Idle()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method. We should not get called here, because we told
//      the spooler not to call us here.
//
//    Return Value
//      S_OK always.
//
STDMETHODIMP CXPLogon::Idle (ULONG ulFlags)
{
    InfoTrace ("CXPLogon::Idle method called");
    CheckParameters_IXPLogon_Idle (this, ulFlags);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::TransportLogoff()
//
//    Parameters
//      ulFlags     Priority with which the transport should log off
//
//    Purpose
//      This method is called by the spooler when the transport should do final
//      arragements before it gets released.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::TransportLogoff (ULONG ulFlags)
{
    InfoTrace ("CXPLogon::TransportLogoff method called");
    CheckParameters_IXPLogon_TransportLogoff (this, ulFlags);
    StopUploadTimer();

    EmptyInboundQueue();

    // We should attempt to remove the transport's status row from
    // the system, but if we fail we won't fail the call.
    HRESULT hResult = m_pSupObj->ModifyStatusRow (0, NULL, 0);
    TraceResult ("CXPLogon::TransportLogoff: Failed to remove status row", hResult);

    if (m_pStatusObj)
    {
        m_pStatusObj->Release();
        m_pStatusObj = NULL;
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SubmitMessage()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This method is called by the spooler when a client submits a
//      message to a recipient whose address type this transport handles.
//      The spooler calls this method twice for each deferred message.
//      The first time (before the delivery time) when the message is
//      submitted by the client, we simply return. The message is then queued
//      by the spooler for later delivery. We keep track of when it's time
//      to send deferred messages.
//
//      The second time we're called, the state variable will be 'READY' and
//      we go ahead and start the actual transmission. While we're in the
//      body of this function, the implied state is 'SENDING'
//
//      If the client logs out of this session, any pending messages get
//      queued again the next time it logs in.
//
//      In this transport we get a recipient table, we restrict the table for
//      unmarked recipients. After the table is ready we invoke a helper
//      method to do the actual transmission.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::SubmitMessage (ULONG     ulFlags,
                                      LPMESSAGE pMsgObj,
                                      ULONG *   pulMsgRef,
                                      ULONG *   pulReturnParm)
{
    InfoTrace ("CXPLogon::SubmitMessage method called");
    CheckParameters_IXPLogon_SubmitMessage (this,
                                            ulFlags,
                                            pMsgObj,
                                            pulMsgRef,
                                            pulReturnParm);

    #ifdef _DEBUG
    // Before we get called in SubmitMessage or StartMessage, the spooler should have called
    // us in TransportNotify() to let us know which logic (IN, OUT, BOTH) of the
    // transporting mechanism should be activated for this IXPLogon session.
    if (!(GetTransportStatusCode() & STATUS_OUTBOUND_ENABLED))
    {
        TraceMessage ("CXPLogon::SubmitMessage: We can't send a message if the outbound is not enabled");
        ASSERTMSG (FALSE, "We can't submit a message if we have not started OUTBOUND logic");
        return MAPI_E_NOT_ME;
    }
    #endif // _DEBUG

    // Lock the object against the timer thread re-entrancy. Timer events are handle
    // in a separate thread. This section will get unlocked in the CPXLogon::EndMessage() call
    if (WAITING == GetTransportState())
    {
        // Not scheduled upload time, return and let IXPLogon::EndMessage() defer the message
        InfoTrace ("CXPLogon::SubmitMessage: Deferring message for scheduled submission");
        return S_OK;
    }
    InfoTrace ("CXPLogon::SubmitMessage: Processing deferred message");

    CheckSpoolerYield (TRUE);

    // Notify the MAPI spooler the transport is going to flush any queued up messages and submit them.
    // Transport to used shared resources, such as COM ports, should put them selves into flush mode
    // when submitting a batch of messages. This gives the transport the advantage of locking shared
    // resources across submitted messages and ensures that the spooler does not call other
    // transports while another is flushing because it could have something locked.
    // This transport does not uses any shared resources that we would need to lock, but for the
    // purposes of demonstration, we put our selves into flush mode until the MAPI spooler
    // tells us to step down.
    if (!(GetTransportStatusCode() & UPLOADING_MESSAGES))
    {
        AddStatusBits (UPLOADING_MESSAGES);
        UpdateStatus();
    }

    // Get the recipient table from the message
    LPMAPITABLE pTable = NULL;
    HRESULT hResult = pMsgObj->GetRecipientTable (fMapiUnicode, &pTable);
    if (hResult)
    {
        TraceResult ("CXPLogon::SubmitMessage: Failed to get the recipient table", hResult);
        goto ErrorExit;
    }

    // The spooler marks all the message recipients this transport has to
    // handle with PR_RESPONSIBILITY set to FALSE
    SPropValue spvRecipUnsent;
    spvRecipUnsent.ulPropTag                       = PR_RESPONSIBILITY;
    spvRecipUnsent.Value.b                         = FALSE;

    SRestriction srRecipientUnhandled;
    srRecipientUnhandled.rt                        = RES_PROPERTY;
    srRecipientUnhandled.res.resProperty.relop     = RELOP_EQ;
    srRecipientUnhandled.res.resProperty.ulPropTag = PR_RESPONSIBILITY;
    srRecipientUnhandled.res.resProperty.lpProp    = &spvRecipUnsent;

    hResult = pTable->Restrict (&srRecipientUnhandled, 0L);
    if (hResult)
    {
        TraceResult ("CXPLogon::SubmitMessage: Failed to restrict the recipient table", hResult);
        goto ErrorExit;
    }

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    hResult = HrAddColumns (pTable,
                            (LPSPropTagArray)&sptRecipTable,
                            gpfnAllocateBuffer,
                            gpfnFreeBuffer);
    if (hResult)
    {
        TraceResult ("CXPLogon::SubmitMessage: Failed to expand the columns in the recipient table", hResult);
        goto ErrorExit;
    }

    LPSRowSet pRecipRows;
    hResult = HrQueryAllRows (pTable,
                              NULL,
                              NULL,
                              NULL,
                              0,
                              &pRecipRows);
    if (!hResult)
    {
        // Let the MAPI spooler do other things
        CheckSpoolerYield();
        // Send to the recipients which we can reach, and generate NDR's for the ones we can't
        hResult = SendMailMessage (pMsgObj, pRecipRows);
        FreeProws (pRecipRows);
        if (!hResult)
        {
            // Now we need to save changes on the message and close it.
            // After this, the message object can't be used.
            hResult = pMsgObj->SaveChanges (0);
            TraceResult ("CXPLogon::SubmitMessage: Failed to save the message object", hResult);
        }
    }

ErrorExit:
     // Release the table, we're finished with it
    if (pTable)
    {
        pTable->Release();
    }

    // Release the spooler's message if needed to
    if (pMsgObj)
    {
        pMsgObj->Release ();
    }

    // In case there is a warning or error floating around, don't let it escape to the spooler.
    if (FAILED(hResult))
    {
        // We default to MAPI_E_NOT_ME so that the spooler would attempt handle
        // the message to other transport (currently running in this profile)
        // that handle the same address type as ours.
        TraceMessage ("CXPLogon::SubmitMessage: An error occurred in the submission, returning MAPI_E_NOT_ME");
        hResult = MAPI_E_NOT_ME;
    }
    else
    {
        hResult = S_OK;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::EndMessage()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This method is called by the spooler for each message we're to
//      deliver. It's the mate to SubmitMessage. We're called here twice
//      for each deferred message and once for non-deferred (realtime)
//      messages.
//
//      We first check the transport state, and if we're
//      WAITING for the scheduled delivery time to arrive, we return
//      END_DONT_RESEND in *pulFlags, which tells the spooler to queue this
//      message for deferred delivery.
//
//      If the state is SENDING, we're getting called here after
//      a message has been dequeued and delivered. Return 0 in *pulFlags
//      to tell the spooler the message has been delivered.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::EndMessage (ULONG ulMsgRef, ULONG * pulFlags)
{
    InfoTrace ("CXPLogon::EndMessage method called");
    CheckParameters_IXPLogon_EndMessage (this, ulMsgRef, pulFlags);

    if (WAITING == GetTransportState())
    {
        // Tell spooler to queue msg for deferred delivery
        *pulFlags = END_DONT_RESEND;
    }
    else
    {
        *pulFlags = 0;
    }
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::Poll()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method. We should not get called here, because we told
//      the spooler not to call us here.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::Poll (ULONG * pulIncoming)
{
    InfoTrace ("CXPLogon::Poll method called");
    CheckParameters_IXPLogon_Poll (this, pulIncoming);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::StartMessage()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This method gets called when an incoming message is pending to be
//      processed.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::StartMessage (ULONG      ulFlags,
                                     LPMESSAGE  pMsgObj,
                                     ULONG *    pulMsgRef)
{
    InfoTrace ("CXPLogon::StartMessage method called");
    CheckParameters_IXPLogon_StartMessage (this, ulFlags, pMsgObj, pulMsgRef);

    // Initialize the pseudo-timer for the SpoolerYield() call
    CheckSpoolerYield (TRUE);

    HRESULT hResult = S_OK;
    LPSTREAM pFileStream = NULL;
    CCachedStream * pStream = NULL;
    LPITNEF pTNEFObj = NULL;
    SPropTagArray sptExcludedProps = { 0 };

    PLIST_NODE pNode = m_List.GetDownloadNode();
    if (!pNode)
    {
        RemoveStatusBits (DOWNLOADING_MESSAGES); // Remove these bits from the status code
        UpdateStatus();
        if (READY == GetTransportState())
        {
            SetTransportState (WAITING);
        }
        goto ErrorExit;
    }
    // Close the file so that we may open it in the call below
    if (pNode->hFile)
    {
        CloseHandle (pNode->hFile);
        // NULL the handle to avoid closing the file again during cleanup.
        pNode->hFile = NULL;
    }
    // Open the stream where the message properties are.
    hResult = OpenStreamOnFile (gpfnAllocateBuffer,
                                gpfnFreeBuffer,
                                STGM_READ,
                                pNode->szFileName,
                                NULL,
                                &pFileStream);
    if (hResult)
    {
        TraceResult ("CXPLogon::StartMessage: Failed to open stream on the message file", hResult);
        goto ErrorExit;
    }
    try
    {
        // Create a buffer IStream interface for the TNEF decoding
        pStream = new CCachedStream (pFileStream, XPSOF_READ);
        if (!pStream)
        {
            TraceMessage ("CXPLogon::StartMessage: Failed to allocate cached stream object");
            hResult = E_OUTOFMEMORY;
        }
    }
    catch (CException & Exception)
    {
        hResult = Exception.GetError();
    }
    pFileStream->Release();
    pFileStream = NULL;
    if (hResult)
    {
        goto ErrorExit;
    }
    hResult = OpenTnefStream (m_pSupObj,
                              pStream,
                              TNEF_FILE_NAME,
                              TNEF_DECODE,
                              pMsgObj,
                              0,    // Not needed when decoding TNEF
                              &pTNEFObj);
    if (hResult)
    {
        TraceResult ("CXPLogon::StartMessage: Failed to open TNEF stream object", hResult);
        goto ErrorExit;
    }

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    // Get the properties for the message which are encoded in the TNEF
    // message stream. The sptExcludedProps argument, is just a stub,
    // we are not excluding any properties
    LPSTnefProblemArray pProblems;
    pProblems = NULL;
    hResult = pTNEFObj->ExtractProps (TNEF_PROP_EXCLUDE, &sptExcludedProps, &pProblems);
    if (pProblems)
    {
        gpfnFreeBuffer (pProblems);
        pProblems = NULL;
    }
    if (hResult)
    {
        TraceResult ("CXPLogon::StartMessage: Failed to open TNEF stream", hResult);
        goto ErrorExit;
    }
    if (MAPI_E_CORRUPT_DATA == SetIncomingProps (pMsgObj, pNode))
    {
        pNode = NULL; // To avoid deleting it below.
    }

    // Save all chages and new properties, back on the message
    // Don't release the message object. The spooler is still using it.
    // After this, the message object can't be used by this transport.
    hResult = pMsgObj->SaveChanges (0);
    TraceResult ("CXPLogon::StartMessage: Failed to save the message object", hResult);

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

ErrorExit:
    // Release used objects
    if (pTNEFObj)
    {
        pTNEFObj->Release();
    }
    if (pStream)
    {
        pStream->Release();
    }
    if (pNode)
    {
        if (pNode->hFile)
        {
            CloseHandle (pNode->hFile);
        }
        // Delete the file ONLY if the we were successful putting the
        // file data into the message. Otherwise leave the file in
        // the directory, and it will be picked up later
        if (S_OK == hResult)
        {
            DeleteFile (pNode->szFileName);
        }
        delete pNode;
    }
    m_List.UpdateProgress();
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::OpenStatusEntry()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This method is called to get an IMAPIStatus object for this XPLOGON
//      session.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::OpenStatusEntry (LPCIID          pInterface,
                                        ULONG           ulFlags,
                                        ULONG *         pulObjType,

LPMAPISTATUS *  ppEntry)
{
    InfoTrace ("CXPLogon::OpenStatusEntry method called");
    CheckParameters_IXPLogon_OpenStatusEntry (this,
                                              pInterface,
                                              ulFlags,
                                              pulObjType,
                                              ppEntry);
    if (MAPI_MODIFY & ulFlags)
    {
        TraceMessage ("CXPLogon::OpenStatusEntry: We don't support WRITE access to the status object");
        return E_ACCESSDENIED;
    }

    HRESULT hResult = S_OK;
    // Now, if we already have created a status object on this logon context,
    // we'll just use QueryInterface to get a new copy (AddRef() it) of the object
    if (!m_pStatusObj)
    {
        // Get the profile section of the PROVIDER so that we may get some properties,
        // assigned by MAPI to this provider, directly on the status object.
        LPPROFSECT pProfileObj = NULL;
        m_pSupObj->OpenProfileSection (NULL, 0, &pProfileObj);
        // If we don't have an object, create it, and save a copy in the logon object
        m_pStatusObj = new CMAPIStatus (this, pProfileObj);
        if (!m_pStatusObj)
        {
            TraceMessage ("CXPLogon::OpenStatusEntry: Could not allocate new CMAPIStatus object");
            hResult = E_OUTOFMEMORY;
        }
        // The constructor of CMAPIStatus AddRef()'ed this object
        if (pProfileObj)
        {
            pProfileObj->Release();
        }
    }
    if (!hResult)
    {
        // The transport will return *ppEntry == NULL or a pointer to the new memory allocated
        m_pStatusObj->AddRef();
        *ppEntry = m_pStatusObj;
        *pulObjType = MAPI_STATUS;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::ValidateState()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This function gets caller by a client in order to validate the
//      transport logon properties. This function open the profile with the
//      most up-to-date properties and then compares them to what the transport
//      has stored internally
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::ValidateState (ULONG ulUIParam, ULONG ulFlags)
{
    InfoTrace ("CXPLogon::ValidateState method called");
    CheckParameters_IXPLogon_ValidateState (this,
                                            ulUIParam,
                                            ulFlags);

    // Try to open the transport profile section
    LPPROFSECT pProfileObj;
    HRESULT hResult = OpenServiceProfileSection (m_pSupObj, &pProfileObj, gpfnFreeBuffer);
    if (hResult)
    {
        return hResult;
    }

    LPSPropValue pProps = NULL;
    ULONG ulPropCount;
    // Read the properties stored in the profile of this user
    hResult = pProfileObj->GetProps ((LPSPropTagArray)&sptLogonProps,
                                     fMapiUnicode,
                                     &ulPropCount,
                                     &pProps);
    TraceResult ("CXPLogon::ValidateState: Failed to get profile properties", hResult);
    if (!hResult)
    {
        // Now, compare what the transport thinks the information in the profile is
        // to the real data. If they are different, tell the spooler the transport
        // would like to be reloaded
        if (lstrcmp (m_UserInfo.szMailboxName, pProps[MAILBOX_NAME].Value.LPSZ)||
            lstrcmp (m_UserInfo.szPassword, pProps[PASSWORD].Value.LPSZ) ||
            lstrcmp (m_UserInfo.szFullName, pProps[USER_NAME].Value.LPSZ) ||
            lstrcmpi (m_szServer, pProps[SERVER_NAME].Value.LPSZ))
        {
            hResult = m_pSupObj->SpoolerNotify (NOTIFY_CONFIG_CHANGE, NULL);
            TraceResult ("CXPLogon::ValidateState: Failed to notify the spooler", hResult);
        }
    }
    pProfileObj->Release();
    gpfnFreeBuffer (pProps);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::FlushQueues()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Called by the MAPI spooler when, upon request of the client or
//      ourselves, we need to flush the inbound or outbound queue.
//      Here we make connections to the server to download messages, refresh
//      the remote message headers, and request the spooler to send us any
//      deferred messages.
//      Transport connecting only in FlushQueues() allow the MAPI spooler to
//      better manage contention of multiple transport accessing common
//      communication resources (such as COM ports) and let the spooler give us
//      messages to process when is best for the overall subsystem.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::FlushQueues (ULONG       ulUIParam,
                                    ULONG       cbTargetTransport,
                                    LPENTRYID   pTargetTransport,
                                    ULONG       ulFlags)
{
    InfoTrace ("CXPLogon::FlushQueues method called");
    CheckParameters_IXPLogon_FlushQueues (this,
                                          ulUIParam,
                                          cbTargetTransport,
                                          pTargetTransport,
                                          ulFlags);

    HRESULT hResult = S_OK;
    // Update status flags and then update update the transport status rows
    if (ulFlags & FLUSH_UPLOAD)
    {
        // If the remote server is offline, the transport is not going to send the deferred messages
        if (TRUE == IsWINDSServerAvailable (m_szServer))
        {
            // If we are already in delivery mode submitted messages will not get deferred,
            // so don't bother the spooler asking for deferred messages.
            if (READY != GetTransportState())
            {
                // Guard against re-entrancy from the timer call back which happens on a separate thread
                if (PROCESSING_TIMER_EVENT != GetTransportState())
                {
                    SetTransportState (READY);
                }
                // We pass NULL for the entry ID so that the spooler would resend ALL the
                // deferred messages. Transport may pass individual entry IDs for specific messages.
                hResult = m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
            }
        }
    }
    if (ulFlags & FLUSH_DOWNLOAD)
    {
        if (HEADERS_AND_DOWNLOAD == GetTransportState())
        {
            m_hRemoteActionErr = ProcessHeaders();
            if (!m_hRemoteActionErr && !m_fCancelPending)
            {
                m_pSupObj->SpoolerYield (0);
                if (m_List.AreTherePendingDownloads())
                {
                    // Put ourselves into flush mode and have the spooler call us until
                    // we are finished putting the downloaded messages into the default store.
                    AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
                }
                if (!m_fCancelPending)
                {
                    // Upload any deferred messages the spooler has for our transport
                    if (IsWINDSServerAvailable (m_szServer))
                    {
                        m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
                    }
                    if (!m_fCancelPending)
                    {
                        m_hRemoteActionErr = DownloadMessageHeaders();
                        if (!m_hRemoteActionErr && !(GetTransportStatusCode() & DOWNLOADING_MESSAGES))
                        {
                            RemoveStatusBits (STATUS_INBOUND_FLUSH);
                            UpdateStatus (TRUE, TRUE);
                            m_pSupObj->SpoolerYield (0);
                        }
                    }
                }
            }
            SetTransportState (PENDING_RETURN_CODE);
            AddStatusBits (STATUS_OFFLINE);
            if (m_hRemoteActionErr)
            {
                RemoveStatusBits (STATUS_INBOUND_FLUSH);
                UpdateStatus (TRUE, TRUE);
            }
            else
            {
                if (m_fCancelPending)
                {
                    UpdateStatus (TRUE, TRUE);
                }
                else
                {
                    UpdateStatus();
                }
            }
        }
        else
        {
            if (PROCESSING_TIMER_EVENT == GetTransportState())
            {
                if (m_fGetHeaders)
                {
                    RemoveStatusBits (STATUS_OFFLINE);
                    UpdateStatus();
                    DownloadMessageHeaders();
                    AddStatusBits (STATUS_OFFLINE);
                    UpdateStatus();
                }
            }
            else
            {
                if (m_List.AreTherePendingDownloads())
                {
                    // Put ourselves into flush mode and have the spooler call us until
                    // we are finished putting the downloaded messages into the default store.
                    AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
                    UpdateStatus();
                }
            }
        }
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::ProcessHeaders()
//
//    Parameters
//      None.
//
//    Purpose
//      This method is called from the status object's ValidateState when
//      it's passed PROCESS_XP_HEADER_CACHE. It starts the download processing.
//      First we get the header folder contents table and restrict it to rows
//      where PR_MSG_STATUS is >= MSGSTATUS_REMOTE_DOWNLOAD. These are rows
//      for messages that have the MSGSTATUS_REMOTE_DOWNLOAD, or
//      MSGSTATUS_REMOTE_DELETE, or both, bits set.
//
//      We then connect to the server to obtain the download named pipe and
//      start the download.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::ProcessHeaders()
{
    // If we don't have a folder in the status object or the folder doesn't
    // have an initialized contents table, there is nothing to do.
    if (!m_pStatusObj->m_pHeaderFolder || !m_pStatusObj->m_pHeaderFolder->m_pTableData)
    {
        return S_OK;
    }

    // Get headers folder contents table, some are marked for download
    LPMAPITABLE pTable;
    HRESULT hResult = m_pStatusObj->m_pHeaderFolder->m_pTableData->HrGetView (NULL, NULL, 0, &pTable);
    if (hResult)
    {
        TraceResult ("CXPLogon::ProcessHeaders: Failed to get a view on the contents table", hResult);
        return hResult;
    }

    // we only want rows marked for download, form restriction
    SPropValue spvFilter = { 0 };
    spvFilter.ulPropTag = PR_MSG_STATUS;
    spvFilter.Value.l = MSGSTATUS_REMOTE_DOWNLOAD;

    // This restriction relies in the current values of the flags in PR_MSG_STATUS
    SRestriction srStatus = { 0 };
    srStatus.rt = RES_PROPERTY;
    srStatus.res.resProperty.relop = RELOP_GE;
    srStatus.res.resProperty.ulPropTag = PR_MSG_STATUS;
    srStatus.res.resProperty.lpProp = &spvFilter;

    hResult = pTable->Restrict (&srStatus, 0);
    if (hResult)
    {
        // This is a non-fatal error (i.e. the implementation does not support restrictions)
        TraceResult ("CXPLogon::ProcessHeaders: Failed to restrict the table", hResult);
    }
    ULONG ulRowCount;
    // Providers and client should not, when ever possible, use IMAPITable::GetRowCount()
    // because depending on the implementation of the table, the returned count by not
    // be exact.
    // We use it here for convenience knowing the fact the MAPI's ITableData implementation
    // returns and EXACT count of the rows in the memory table.
    hResult = pTable->GetRowCount (0, &ulRowCount);
    if (hResult)
    {
        goto ErrorExit;
    }
    // Nothing to do.
    if (0 == ulRowCount)
    {
        goto ErrorExit;
    }
    else
    {
        // Make sure our download directory is around.
        TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
        GetTempPath (_MAX_PATH, szTmpDir);
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
        CreateDirectory (szDownloadDir, NULL);
    }

    // Initialize the progress property in the status row of this transport.
    UpdateProgress (0, REMOTE_ACTION_DOWNLOADING_MSGS);

    // Make a call to the remote server to open the connection
    HANDLE hPipe;
    hResult = OpenRemoteServerDownLoadPipe (m_szServer, m_UserInfo.szMailboxName, &hPipe);
    if (hResult)
    {
        goto ErrorExit;
    }
    // Do the actual download transmission now.
    hResult = m_List.DownLoadMsgs (pTable, ulRowCount, hPipe);
    CloseHandle (hPipe);

    TerminateRemoteConnections();

ErrorExit:
    TraceResult ("CXPLogon::ProcessHeaders", hResult);
    pTable->Release();
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::UpdateProgress()
//
//    Parameters
//      lPercentComplete    Percent completion of download
//      raFlag              Flag that indicate to what remote action the
//                          update is tied to, so that we may properly
//                          update PR_REMOTE_PROGRESS_TEXT.
//
//    Purpose
//      Sets the status row property PR_REMOTE_PROGRESS with a number from
//      0-100. In example, if 10 messages are queued and 5 have been
//      downloaded, lPercentComplete will be 50, regardless of individual
//      messages' sizes.
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::UpdateProgress (long          lPercentComplete,
                                      REMOTE_ACTION raFlag)
{
    SPropValue spProgress[2] = { 0 };
    ULONG cProps = 1;
    TCHAR szRemoteProgress[64] = { 0 };
    spProgress[0].ulPropTag = PR_REMOTE_PROGRESS;
    spProgress[0].Value.l = lPercentComplete;
    if (raFlag != m_raAction)
    {
        spProgress[1].ulPropTag = PR_REMOTE_PROGRESS_TEXT;
        spProgress[1].Value.LPSZ = szRemoteProgress;
        DWORD idString;
        m_raAction = raFlag;
        switch (raFlag)
        {
            case REMOTE_ACTION_DOWNLOADING_MSGS :
                idString = IDS_REMOTE_DOWNLOADING;
                break;
            case REMOTE_ACTION_PROCESSING_MSGS :
                idString = IDS_REMOTE_PROCESSING_MSGS;
                break;
            case REMOTE_ACTION_HEADER_REFRESH :
                idString = IDS_REMOTE_REFRESHING_HEADERS;
                break;
            case REMOTE_ACTION_IDLE :
                idString = 0;
                break;
        }
        if (idString)
        {
            LoadString (m_hInstance, idString, szRemoteProgress, 64);
        }
        cProps++;
    }

    HRESULT hResult = m_pSupObj->ModifyStatusRow (cProps, spProgress, STATUSROW_UPDATE);
    TraceResult ("CXPLogon::UpdateProgress: Failed to modify the status row", hResult);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::LoadStatusString()
//
//    Parameters
//      pString         Pointer to a string which will hold the status string
//      uStringSize     Maximum number of characters allowed in the string
//
//    Purpose
//      Loads a string from the transport's stringtable. This method is called
//      by the CXPLogon::UpdateStatus method when updating a status row. This
//      method loads the string based on the status bits of the transport
//      status code
//
//    Return Value
//      TRUE            If the string was found in the string table.
//      FALSE           The string was not found. The String indicated by
//                      pString is set to hold 0 characters
//
BOOL WINAPI CXPLogon::LoadStatusString (LPTSTR pString, UINT uStringSize)
{
    UINT uStringID;
    DWORD dwStatusCode = GetTransportStatusCode();
    if (dwStatusCode & (DOWNLOADING_MESSAGES | UPLOADING_MESSAGES))
    {
        uStringID = IDS_XP_STATUS_TIMER_EVENT;
    }
    else
    {
        if (dwStatusCode & DOWNLOADING_MESSAGES)
        {
            if (dwStatusCode & STATUS_OFFLINE)
            {
                uStringID = IDS_XP_STATUS_FLUSHING_INBOUND;
            }
            else
            {
                uStringID = IDS_XP_STATUS_INBOUND_AND_HEADERS;
            }
        }
        else
        {
            if (dwStatusCode & UPLOADING_MESSAGES)
            {
                uStringID = IDS_XP_STATUS_FLUSHING_OUTBOUND;
            }
            else
            {
                if (dwStatusCode & STATUS_INBOUND_ENABLED &&
                    dwStatusCode & STATUS_OUTBOUND_ENABLED)
                {
                    uStringID = IDS_XP_STATUS_READY;
                }
                else
                {
                    uStringID = IDS_XP_STATUS_AVAILABLE;
                }
            }
        }
    }
    if (LoadString (m_hInstance, uStringID, pString, uStringSize))
    {
        return TRUE;
    }
    pString[0] = '\0';
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::MakeSearchKey()
//
//    Parameters
//      pParentMemBlock     Pointer the a block of memory allocated with the
//                          MAPI Mem. alloc. functions.
//      pszAddress          String with the actual mail address
//      pcbSearchKey        Pointer to a ULONG which returns the size
//                          (in bytes) of the newly created search key
//      ppSearchKey         Pointer to an array ob BYTES
//
//    Purpose
//      Returns a MAPI search key of the form TYPE:ADDRESS in upper case.
//      The string is allocated with the MAPI Mem Alloc functions and linked
//      to the block of memory indicated by pParentMemBlock
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::MakeSearchKey (LPVOID        pParentMemBlock,
                                      LPTSTR        pszAddress,
                                      ULONG *       pcbSearchKey,
                                      LPBYTE *      ppSearchKey)
{
    LPTSTR pszSearchKey;
    // The 2 is for the COLON and the NULL terminator
    ULONG cbStrSize = sizeof (TCHAR) * (2 + lstrlen (WINDS_ADDRESS_TYPE) + lstrlen (pszAddress));
    HRESULT hResult = gpfnAllocateMore (cbStrSize, pParentMemBlock, (LPVOID *)&pszSearchKey);
    TraceResult ("CXPLogon::MakeSearchKey: Failed to allocate search key string", hResult);
    if (!hResult)
    {
        // We need to convert the address to upper case
        wsprintf (pszSearchKey, TEXT("%s:%s"), WINDS_ADDRESS_TYPE, pszAddress);
        CharUpper (pszSearchKey);
        *pcbSearchKey = cbStrSize;
        *ppSearchKey = (LPBYTE)pszSearchKey;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::UpdateStatus()
//
//    Parameters
//      fAddValidate
//      fValidateOkState
//
//    Purpose
//      Updates the transport status row of this transport in the MAPI Mail
//      subsystem. Updates the flags according the internal state flags
//      maintained in status code of the transport and loads a readable status
//      string to reset the status row. The caller of this method should update
//      the status code member variable prior to calling UpdateStatus()
//
//    Return Value
//      None
//
void WINAPI CXPLogon::UpdateStatus (BOOL fAddValidate, BOOL fValidateOkState)
{
    ULONG cProps = 1;
    SPropValue rgProps[3] = { 0 };
    //  Store the new Transport Provider Status Code
    rgProps[0].ulPropTag = PR_STATUS_CODE;
    rgProps[0].Value.l = GetTransportStatusCode();

    TCHAR szStatus[64];
    if  (LoadStatusString (szStatus, sizeof(szStatus) - 1 ))
    {
        rgProps[1].ulPropTag = PR_STATUS_STRING;
        rgProps[1].Value.LPSZ = szStatus;
        cProps++;
    }
    if (fAddValidate)
    {
        ULONG index;
        if (2 == cProps)
        {
            index = 2;
        }
        else
        {
            index = 1;
        }
        rgProps[index].ulPropTag = PR_REMOTE_VALIDATE_OK;
        rgProps[index].Value.b = fValidateOkState;
        cProps++;
    }
    //  OK. Notify the messaging subsystem of state changes in this transport.
    HRESULT hResult = m_pSupObj->ModifyStatusRow (cProps, rgProps, STATUSROW_UPDATE);
    TraceResult ("CXPLogon::UpdateStatus: ModifyStatusRow failed", hResult);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::InitializeTransportStatusFlags()
//
//    Parameters
//      ulFlags     Flags passed in to initialize the transport status flags.
//
//    Purpose
//      Initialize the transport status flags with the flags passed in by the
//      MAPI spooler. This method gets called when initializing a CXPLogon
//      object in the CXPProvider::TransportLogon method.
//
//    Return Value
//      None
//
void WINAPI CXPLogon::InitializeTransportStatusFlags (ULONG ulFlags)
{
    AddStatusBits (STATUS_AVAILABLE | STATUS_OFFLINE | STATUS_REMOTE_ACCESS);
    if (!(ulFlags & LOGON_NO_INBOUND))
    {
        AddStatusBits (STATUS_INBOUND_ENABLED);
    }
    if (!(ulFlags & LOGON_NO_OUTBOUND))
    {
        AddStatusBits (STATUS_OUTBOUND_ENABLED);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SetIdentityProps()
//
//    Parameters
//      None
//
//    Purpose
//      Initializes the transport ID array with the properties found in the
//      profile for the user.
//      If the ABWDS address book is installed, we open an entry in it to
//      supply the identity properties.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::SetIdentityProps()
{
    // Allocate the property array for transport. This memory block is freed
    // in the destructor of the CXPLogon object
    HRESULT hResult = gpfnAllocateBuffer (sizeof(SPropValue)*NUM_IDENTITY_PROPS, (LPVOID *)&m_pIdentityProps);
    if (hResult)
    {
        TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate property array", hResult);
        return hResult;
    }
    LPMAILUSER pUser;
    ULONG ulObjType;
    TCHAR szSearchKey[128];
    LPTSTR pStr;
    // Try to open out entry ID in the ABWDS address book (see if it is around)
    // If the call fails, we must create a one-off for our identity.
    hResult = m_pSupObj->OpenEntry (CB_PRIVATE_EID,
                                    (LPENTRYID)&m_UserEID,
                                    NULL,
                                    0,
                                    &ulObjType,
                                    (LPUNKNOWN *)&pUser);
    if (S_OK == hResult)
    {
        ASSERTMSG (MAPI_MAILUSER == ulObjType, "What kind object is this?");

        // Looks like ABWDS is around, mark the internal flag.
        // ABWDS is the native WINDS address book service provider. If present,
        // instead of creating a one-off entry for the transport identity, we
        // will simply open an entry in the WINDS address book and get the
        // necessary properties out from it.
        m_fABWDSInstalled = TRUE;

        m_pIdentityProps[XPID_NAME].ulPropTag = PR_SENDER_NAME;
        m_pIdentityProps[XPID_NAME].Value.LPSZ = m_UserInfo.szFullName;
        m_pIdentityProps[XPID_EID].ulPropTag = PR_SENDER_ENTRYID;
        m_pIdentityProps[XPID_EID].Value.bin.cb = CB_PRIVATE_EID;
        m_pIdentityProps[XPID_EID].Value.bin.lpb = (LPBYTE)&m_UserEID;
        wsprintf (szSearchKey,
                  TEXT("%s:%s\\%s"),
                  WINDS_ADDRESS_TYPE,
                  m_szServer,       // this is already in the format "\\<servername>"
                  m_UserInfo.szMailboxName);
        CharUpper (szSearchKey);
        hResult = gpfnAllocateMore (Cbtszsize(szSearchKey), m_pIdentityProps, (LPVOID *)&pStr);
        if (hResult)
        {
            TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate string for native search key", hResult);
            m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_SEARCH_KEY));
        }
        else
        {
            lstrcpy (pStr, szSearchKey);
            m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PR_SENDER_SEARCH_KEY;
            m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.cb = Cbtszsize(pStr);
            m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.lpb = (LPBYTE)pStr;
        }
        pUser->Release();
        return hResult;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_SENDER_ENTRYID property. Create an entry for the user and
    // set it in the property array
    m_pIdentityProps[XPID_EID].ulPropTag = PR_SENDER_ENTRYID;
    LPBYTE lpeid = NULL;
    ULONG  cbeid = 0;
    hResult = m_pSupObj->CreateOneOff (m_UserInfo.szFullName,
                                       WINDS_ADDRESS_TYPE,
                                       m_szAddress,
                                       fMapiUnicode,
                                       &cbeid,
                                       (LPENTRYID *)&lpeid);
 if (hResult)
    {
        TraceResult ("CXPLogon::SetIdentityProps: Failed to create a one off identity", hResult);
        m_pIdentityProps[XPID_EID].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_ENTRYID));
        return hResult;
    }

    hResult = gpfnAllocateMore(cbeid, m_pIdentityProps,
                            (LPVOID *)&m_pIdentityProps[XPID_EID].Value.bin.lpb);
    if(hResult)
    {
        TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate memory", hResult);
        m_pIdentityProps[XPID_EID].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_ENTRYID));
        gpfnFreeBuffer(lpeid);
        lpeid = NULL;
        return hResult;
    }

    m_pIdentityProps[XPID_EID].Value.bin.cb = cbeid;
    CopyMemory(m_pIdentityProps[XPID_EID].Value.bin.lpb, lpeid, cbeid);

    gpfnFreeBuffer(lpeid);
    lpeid = NULL;

    //////////////////////////////////////////////////////////////////////////
    // Set the PR_SENDER_NAME property
    m_pIdentityProps[XPID_NAME].ulPropTag = PR_SENDER_NAME;
    LPTSTR pStr1, pStr2 = m_UserInfo.szFullName;
    hResult = gpfnAllocateMore (Cbtszsize(pStr2), m_pIdentityProps, (LPVOID *)&pStr1);
    if (hResult)
    {
        TraceResult ("CXPLogon::SetIdentityProps: Memory allocation failed for display name string", hResult);
        m_pIdentityProps[XPID_NAME].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_NAME));
        return hResult;
    }
    lstrcpy (pStr1, pStr2);
    m_pIdentityProps[XPID_NAME].Value.LPSZ = pStr1;

    //////////////////////////////////////////////////////////////////////////
    // Set the PR_SENDER_SEARCH_KEY property
    m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PR_SENDER_SEARCH_KEY;
    // First, create the search key
    hResult = MakeSearchKey (m_pIdentityProps,
                             m_szAddress,
                             &m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.cb,
                             &m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.lpb);
    if (hResult)
    {
        m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_SEARCH_KEY));
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SetSessionFlags()
//
//    Parameters
//      [IN, OUT]   pulFlags    Flags returned to caller
//
//    Purpose
//      To set the flags we will return to the spooler during the CXPLogon
//      initialization in the CXPProvider::TransportLogon method. The ulFlags
//      parameter is filled as a return value to the caller
//
//    Return Value
//      None
//
void WINAPI CXPLogon::SetSessionFlags (ULONG * pulFlags)
{
    if (*pulFlags & LOGON_NO_CONNECT)
    {
        // Nothing is set
        *pulFlags = 0;
    }
    else
    {
        // We DON'T want the MAPI spooler to call our IXPLogon::Idle method (LOGON_SP_IDLE not added)
        // We DON'T want the MAPI spooler to call our IXPLogon::Poll method (LOGON_SP_POLL not added)
        // Also the names have to be fully resolved (LOGON_SP_RESOLVE)
        *pulFlags = LOGON_SP_RESOLVE;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SendMailMessage()
//
//    Parameters
//      pMsgObj     Pointer to an IMessage object passed to us by the
//                  MAPI spooler.

//      pRecipRows  Row set with ALL the rows from the recipient table which
//                  have the properties of the recipients we are sending the
//                  message to.
//
//    Purpose
//      This method gets called by SubmitMessage() to do the delivery of the
//      message to the list of recipients. Each recipient will have the
//      PR_RESPONSIBILITY property set indicating weather or not message
//      reached the recipient.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::SendMailMessage (LPMESSAGE pMsgObj, LPSRowSet pRecipRows)
{
    LPSPropValue pMsgProps = NULL;
    LPADRLIST pAdrList = NULL, pAdrListFailed = NULL;
    LPSTnefProblemArray pProblems = NULL;
    CCachedStream * pStream = NULL;
    LPSTREAM pFileStream;
    HANDLE hMsgFile = NULL;
    HRESULT hOpenConnect, hUploadError;
    BOOL fErrorInServer, fSentSuccessfully;
    ULONG ulRow, ulCount1 = 0 , ulCount2 = 0;
    LPTSTR pszServer, pszMailbox;
    LPSPropValue pProps;
    TCHAR szHeaderText[1024], szTmpFile[_MAX_PATH], szConnectInfo[MAX_STRING_SIZE+1] = { 0 };
    if (!GetMsgTempFileName (szTmpFile))   // Not the Win32 API, but an internal implementation
    {
        return E_FAIL;
    }

    // Create a stream where all message information will be saved.
    HRESULT hResult = OpenStreamOnFile (gpfnAllocateBuffer,
                                        gpfnFreeBuffer,
                                        STGM_CREATE | STGM_READWRITE,
                                        szTmpFile,
                                        NULL,
                                        &pFileStream);
    if (hResult)
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to create stream object", hResult);
        return hResult;
    }
    try
    {
        pStream = new CCachedStream (pFileStream, XPSOF_READWRITE);
        if (!pStream)
        {
            TraceMessage ("CXPLogon::SendMailMessage: Failed to allocate cached stream object");
            hResult = E_OUTOFMEMORY;
        }
    }
    catch (CException & Exception)
    {
        hResult = Exception.GetError();
    }
    pFileStream->Release();
    pFileStream = NULL;
    if (hResult)
    {
        return hResult;
    }

    // The wKey is a key used to identify the TNEF property stream. Transports
    // should generate a pseudo-random number for this field. Here we get
    // a number based upon the system's tic count
    // Note that this number cannot be zero of the  OpenTnefStream call will be fail.
    WORD wKey = LOWORD (GetTickCount());
    if (!wKey)
    {
        ASSERTMSG (FALSE, "I'll be darn! It's zero!!!");
        wKey = LOWORD (GetTickCount()) + 1;
        ASSERTMSG (0 != wKey, "No way! What is going on!?!?");
    }
    LPITNEF pTNEFObj = NULL;
    hResult = OpenTnefStream (m_pSupObj,
                              pStream,
                              TNEF_FILE_NAME,
                              TNEF_ENCODE | TNEF_PURE,
                              pMsgObj,
                              wKey,
                              &pTNEFObj);
    if (hResult)
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to create TNEF object", hResult);
        goto ErrorExit;
    }

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    // Get the current time. We need to set some properties that require the time
    SYSTEMTIME st;
    FILETIME ft;
    GetSystemTime (&st);
    SystemTimeToFileTime (&st, &ft);

    SetOutgoingProps (pMsgObj, ft);

    // Check what properties there are and exclude the Non-Transmittables ones
    LPSPropTagArray pTags;
    hResult = pMsgObj->GetPropList (fMapiUnicode, &pTags);
    if (hResult)
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to get the message property tags", hResult);
        goto ErrorExit;
    }

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    // In this sample transport we opted to let TNEF encapsulate
    // the table of recipients of this message. This has a side effect: The
    // addresses of the recipients get munged into the TNEF stream. So, for
    // example, if the TNEF file goes through a foreign mail system and it
    // needs to translate the address of the recipients, it will not be
    // able to. A tranport using TNEF which trasmits messages to a foreign
    // mail system, must do custom processing of the recipients and their
    // address so the receiving side will understands them.
    // This sample code must be modified if you want it as the base code
    // for a gateway transport.
    // Here we also let TNEF encode all the attachments we want to send.
    ULONG cValues, i;
    cValues = 0;
    for (i=0; i<pTags->cValues; i++)
    {
        // Use the FIsTransmittable macro in MAPI to determine if a
        // property is transmittable or not.
        if (!FIsTransmittable(pTags->aulPropTag[i]) &&
            PR_MESSAGE_RECIPIENTS != pTags->aulPropTag[i] &&
            PR_MESSAGE_ATTACHMENTS != pTags->aulPropTag[i])
        {
            pTags->aulPropTag[cValues++] = pTags->aulPropTag[i];
        }
    }
    pTags->cValues = cValues;

    // Encode the properties now
    hResult = pTNEFObj->AddProps (TNEF_PROP_EXCLUDE, 0, NULL, pTags);
    gpfnFreeBuffer (pTags);
    if (FAILED(hResult)) // There could be warnings
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to Add the TNEF properties to the message stream", hResult);
        goto ErrorExit;
    }

    hResult = pTNEFObj->Finish (0, &wKey, &pProblems);
    if (FAILED(hResult)) // There could be warnings
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to save the TNEF props", hResult);
        goto ErrorExit;
    }
    if (pProblems)
    {
        TraceMessage ("CXPLogon::SendMailMessage: Some problem(s) occurred while finishing the TNEF encoding");
        gpfnFreeBuffer (pProblems);
        pProblems = NULL;
        // Don't have to fail submission
    }

    // Release used objects and NULL the object pointers to avoid re-releasing them during cleanup
    if (NULL != pTNEFObj)
    {
        pTNEFObj->Release();
        pTNEFObj = NULL;
    }
    if (NULL != pStream)
    {
        pStream->Release();
        pStream = NULL;
    }

    hMsgFile = CreateFile (szTmpFile,
                           GENERIC_READ,
                           0,
                           NULL,
                           OPEN_EXISTING,
                           FILE_ATTRIBUTE_TEMPORARY |
                           FILE_FLAG_DELETE_ON_CLOSE |
                           FILE_FLAG_SEQUENTIAL_SCAN,
                           NULL);
    if (INVALID_HANDLE_VALUE == hMsgFile)
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to open local msg file", GetLastError());
        hResult = E_FAIL;
        goto ErrorExit;
    }

    // Get some properties in the message need to send the message
    // and delivery report information
    hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptPropsForHeader,
                                 fMapiUnicode,
                                 &cValues,
                                 &pMsgProps);
    if (FAILED(hResult))
    {
        TraceResult ("CXPLogon::SendMailMessage: Failed to get message props", hResult);
        goto ErrorExit;
    }

    // We need to check if the sender requeste a delivery report or not.
    BOOL fNeedDeliveryReport;
    if (PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED == pMsgProps[MSG_DR_REPORT].ulPropTag &&
        pMsgProps[MSG_DR_REPORT].Value.b)
    {
        fNeedDeliveryReport = TRUE;
    }
    else
    {
        fNeedDeliveryReport = FALSE;
    }

    // Create the header that we transmit to the server. The remote host will
    // store this information and make it available to us when then recipients
    // of the message ask for a headers update on their remote mailboxes.
    CreateMsgHeaderTextLine (pMsgProps, szHeaderText, ft);

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    hOpenConnect = OpenServerUploadPipe (m_szServer,
                                         m_UserInfo.szMailboxName,
                                         hMsgFile,
                                         szConnectInfo,
                                         &fErrorInServer);
    for (ulRow=0; ulRow<pRecipRows->cRows; ulRow++)
    {
        pProps = pRecipRows->aRow[ulRow].lpProps;

        // Assume the worst. If hOpenConnect is not S_OK, the fSentSuccessfully
        // must be FALSE to generate NDR for each recipient.
        fSentSuccessfully = FALSE;
        if (S_OK == hOpenConnect)
        {
            if (IsValidAddress (pProps[RECIP_EMAIL_ADR].Value.LPSZ, &pszServer, &pszMailbox))
            {
                // pszServer should be the same as m_szServer
                hUploadError = SendMsgToAccount (pszServer,
                                                 pszMailbox,
                                                 szHeaderText,
                                                 szConnectInfo,
                                                 &fErrorInServer);
                if (!hUploadError)
                {
                    // If we got here, we assume the message has been received in the server
                    fSentSuccessfully = TRUE;
                }
            }
            else
            {
                hUploadError = MAKE_HRESULT(1, FACILITY_WIN32, ERROR_INVALID_ADDRESS);
            }
        }
        else
        {
            hUploadError = hOpenConnect;
        }

        // Let the MAPI spooler do other things
        CheckSpoolerYield();

        // Set the PR_RESPONSIBILITY flag to indicated we have handled this
        // recipient (row). If the flag is not modified, MAPI will pass this
        // message to the next transport in the profile that knows how to
        // handle the address types this transport knows.
        // In this case WE want to be the last to handle the message. If
        // we fail the submission, we should tell MAPI to generate an NDR.
        pProps[RECIP_RESPONSIBILITY].ulPropTag = PR_RESPONSIBILITY;
        pProps[RECIP_RESPONSIBILITY].Value.b = TRUE;

        // Set the report time for DR's and NDR's
        pProps[RECIP_REPORT_TIME].ulPropTag = PR_REPORT_TIME;
        pProps[RECIP_REPORT_TIME].Value.ft = ft;

        if (!fSentSuccessfully)
        {
            // Make the spooler generate an NDR instead of DR
            pProps[RECIP_DELIVER_TIME].ulPropTag = PR_NULL;

            // The Spooler will generate an NDR report and will fill in
            // all required properties in the StatusRecips call. The only
            // thing we need to do is to fill in a specific per-recipient
            // text description of the problem. It's good to have real
            // info from the transport indicating the real cause for the
            // failure
            wsprintf (szHeaderText,
                      TEXT("\tThe WINDS transport service failed to deliver the message to this recipient.\r\n"
                           "\tRecipient Address: Server: %s  Mailbox: %s. Delivery error: %#08X.  %s"),
                           (pszServer ? pszServer : ""),
                           (pszMailbox ? pszMailbox : ""),
                           hUploadError,
                      (fErrorInServer ? TEXT("The error occurred in the server host.") :
                                        TEXT("The error occurred in local processing.")));
            if (fErrorInServer && hUploadError)
            {
                DWORD dwServerErrorIDS = 0;
                switch (hUploadError)
                {
                    case HRESULT_FROM_WIN32(ERROR_INVALID_ACCOUNT_NAME) :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_ACCT;
                        break;
                    case HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER) :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_NO_SUCH_USER;
                        break;
                    case HRESULT_FROM_WIN32 (ERROR_HOST_UNREACHABLE) :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_OFFLINE_SERVER;
                        break;
                    case HRESULT_FROM_WIN32 (ERROR_INVALID_ADDRESS) :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_ADDRESS;
                        break;
                    case E_OUTOFMEMORY :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_OUTOFMEMORY;
                        break;
                    case E_INVALIDARG :
                        dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_PARAM;
                        break;
                }
                if (!dwServerErrorIDS && FACILITY_STORAGE == HRESULT_FACILITY(hUploadError))
                {
                    dwServerErrorIDS = IDS_DELIVERY_ERROR_ISTORAGE;
                }
                if (!dwServerErrorIDS && FACILITY_WIN32 == HRESULT_FACILITY(hUploadError))
                {
                    dwServerErrorIDS = IDS_DELIVERY_ERROR_WIN32;
                }
                if (dwServerErrorIDS)
                {
                    TCHAR szBuffer[256];
                    if (LoadString (m_hInstance, dwServerErrorIDS, szBuffer, 255))
                    {
                        lstrcat (szHeaderText, TEXT("\r\n\t"));
                        lstrcat (szHeaderText, szBuffer);
                    }
                }
            }
            LPTSTR pStr;
            hResult = gpfnAllocateMore (Cbtszsize(szHeaderText), pProps, (LPVOID *)&pStr);
            if (SUCCEEDED(hResult))
            {
                // Copy the formatted string and hook it into the
                // pre-allocated (by MAPI) column
                lstrcpy (pStr, szHeaderText);
                pProps[RECIP_REPORT_TEXT].ulPropTag = PR_REPORT_TEXT;
                pProps[RECIP_REPORT_TEXT].Value.LPSZ = pStr;
            }
            else
            {
                pProps[RECIP_REPORT_TEXT].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_REPORT_TEXT));
                pProps[RECIP_REPORT_TEXT].Value.err = hResult;
                TraceResult ("CXPLogon::SendMailMessage: memory allocation failed for the NDR report string", hResult);
            }
        }
        else
        {
            // For delivery report, each recipient must have this property set.
            // Otherwise the spooler will default to generate an NDR instead.
            pProps[RECIP_DELIVER_TIME].ulPropTag = PR_DELIVER_TIME;
            pProps[RECIP_DELIVER_TIME].Value.ft = ft;

            pProps[RECIP_REPORT_TEXT].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_REPORT_TEXT));
            pProps[RECIP_REPORT_TEXT].Value.err = S_OK;
        }

        // Based on the result of the submission to the remote host we determine into
        // which address list to add this recipient
        LPADRLIST * ppTmpList = (fSentSuccessfully ? &pAdrList : &pAdrListFailed);
        ULONG ulTmpCount = (fSentSuccessfully ? ulCount1 : ulCount2);

        // Does the list where this recipient goes have enough room for one more entry?
        // If not, resize the address list to hold QUERY_SIZE more entries.
        if (!(*ppTmpList) || ((*ppTmpList)->cEntries + 1 > ulTmpCount))
        {
            hResult= GrowAddressList (ppTmpList, 10, &ulTmpCount);
            if (hResult)
            {
                goto ErrorExit;
            }
            ulCount1 = (fSentSuccessfully ? ulTmpCount : ulCount1);
            ulCount2 = (!fSentSuccessfully ? ulTmpCount : ulCount2);
        }

        // We have room now so store the new ADRENTRY. As part of the
        // storage, we're going to copy the SRow pointer from the SRowSet
        // into the ADRENTRY. Once we've done this, we won't need the
        // SRowSet any more ... and the SRow will be released when
        // we unwind the ADRLIST
        (*ppTmpList)->aEntries[(*ppTmpList)->cEntries].cValues = pRecipRows->aRow[ulRow].cValues;
        (*ppTmpList)->aEntries[(*ppTmpList)->cEntries].rgPropVals = pRecipRows->aRow[ulRow].lpProps;

        // Increase the number of entries in the address list
        (*ppTmpList)->cEntries++;

        // Now that we are finished with this row (it is in the right
        // adrlist) we want to disassociate it from the rowset
        // so we don't delete this before we modify the recipients list
        pRecipRows->aRow[ulRow].lpProps = NULL;
        pRecipRows->aRow[ulRow].cValues = 0;

    }

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    // Do we have some recipients that the message arrived to?
    if (pAdrList)
    {
        hResult = pMsgObj->ModifyRecipients (MODRECIP_MODIFY, pAdrList);
        TraceResult ("CXPLogon::SendMailMessage: ModifyRecipients failed (DELIVERED)", hResult);
        hResult = S_OK; // We'll drop the error code from the modify recipients call
        if (fNeedDeliveryReport)
        {
            hResult = m_pSupObj->StatusRecips (pMsgObj, pAdrList);
            TraceResult ("CXPLogon::SendMailMessage: StatusRecips (DR) failed", hResult);
            if (!HR_FAILED(hResult))
            {
                // If we were successful, we should null out the pointer becase MAPI released
                // the memory for this structure. And we should not try to release it
                // again in the cleanup code.
                pAdrList = NULL;
            }
        }
    }
    // Do we have some recipients that the message DID NOT arrived to?
    if (pAdrListFailed)
    {
        hResult = pMsgObj->ModifyRecipients (MODRECIP_MODIFY, pAdrListFailed);
        // We'll drop the error code from the modify recipients call
        TraceResult ("CXPLogon::SendMailMessage: ModifyRecipients failed (NON-DELIVERED)", hResult);

        // The address list has the entries with the PR_RESPONSIBILITY set, so the
        // spooler will know if it has to generate NDR reports.
        hResult = m_pSupObj->StatusRecips (pMsgObj, pAdrListFailed);
        TraceResult ("CXPLogon::SendMailMessage: StatusRecips (NDR) failed", hResult);
        if (!HR_FAILED(hResult))
        {
            // If we were successful, we should null out the pointer becase MAPI released
            // the memory for this structure. And we should not try to release it
            // again in the cleanup code.
            pAdrListFailed = NULL;
        }
    }

ErrorExit:
    gpfnFreeBuffer (pProblems);
    gpfnFreeBuffer (pMsgProps);
    FreePadrlist (pAdrList);
    FreePadrlist (pAdrListFailed);

    // Release used objects
    if (pTNEFObj)
    {
        pTNEFObj->Release();
    }
    if (pStream)
    {
        pStream->Release();
    }
    // Close the file if it has been opened
    CloseHandle (hMsgFile);

    // Delete the message file in case the function fails before the call to CreateFile() above
    DeleteFile (szTmpFile);

    // Call the sender function will NULL parameters to reset internal data buffers
    if (lstrlen(szConnectInfo))
    {
        FinishUploadConnection (m_szServer, szConnectInfo);
    }

    // Once we have sent all messages to the remote server(s), terminate all connections
    TerminateRemoteConnections();

    // Let the MAPI spooler do other things
    CheckSpoolerYield();

    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SetOutgoingProps()
//
//    Parameters
//      pMsgObj     Pointer to the IMessage object we are processing.
//      ft          FILETIME structure with the time the message is
//                  being delivered.
//
//    Purpose
//      This function checks that the sender information is on the message
//      so that we may get replies from the intended recipients.
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::SetOutgoingProps (LPMESSAGE pMsgObj, FILETIME ft)
{
    LPSPropValue pSender;
    ULONG ulValues;
    HRESULT hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptOutMsgProps, fMapiUnicode, &ulValues, &pSender);
    if (FAILED(hResult))
    {
        TraceResult ("CXPLogon::SetOutgoingProps: Failed to get properties from the message", hResult);
        pSender = NULL; // So that we may recover and continue using default values
    }
    ASSERT (2 == ulValues);
    #define NUM_OUTGOING_PROPS  11
    SPropValue spvProps[NUM_OUTGOING_PROPS] = { 0 };
    ULONG i = 0;
    // If no sender has been stamped on the message use the identity of the transport
    if (!pSender || PR_SENDER_ENTRYID != pSender[0].ulPropTag)
    {
        spvProps[i].ulPropTag = PR_SENDER_NAME;
        spvProps[i++].Value.LPSZ = m_UserInfo.szFullName;

        spvProps[i].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
        spvProps[i++].Value.LPSZ = m_szAddress;

        spvProps[i].ulPropTag = PR_SENDER_ADDRTYPE;
        spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;

        spvProps[i].ulPropTag = PR_SENDER_ENTRYID;
        spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;

        spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
        spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
    }
    // The MS Exchange mail viewer requires these properties
    if (!pSender || PR_SENT_REPRESENTING_NAME != pSender[1].ulPropTag)
    {
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_NAME;
        spvProps[i++].Value.LPSZ = m_UserInfo.szFullName;
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
        spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
        spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
        spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
        spvProps[i++].Value.LPSZ = m_szAddress;
    }
    gpfnFreeBuffer (pSender);

    // Set the time when this transport actually transmitted the message
    spvProps[i].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
    spvProps[i++].Value.ft = ft;
    spvProps[i].ulPropTag = PR_PROVIDER_SUBMIT_TIME;
    spvProps[i++].Value.ft = ft;

    ASSERT (i <= NUM_OUTGOING_PROPS);
    hResult = pMsgObj->SetProps (i, spvProps, NULL);
    TraceResult ("CXPLogon::SetOutgoingProps: Failed to set properties in the message", hResult);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::SetIncomingProps()
//
//    Parameters
//      pMsgObj     Pointer to the IMessage object we are processing.
//
//    Purpose
//      This function sets the PR_SENT_REPRESENTING_xxx properties in an
//      incoming message if those properties aren't already in the message.
//      For these properties we use the values for the sender of the message.
//      We also walk the recipient list restricted to the address type of our
//      transport looking for us, the recipient of the message, s that we may
//      set the PR_MESSAGE_TO_xxx and PR_MESSAGE_RECIP_ME properties in
//      the message.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI CXPLogon::SetIncomingProps (LPMESSAGE pMsgObj, PLIST_NODE pNode)
{
    // This is the maximum number of properties that we could set in the message
    #define NUM_INCOMING_PROPS      28
    SPropValue spvProps[NUM_INCOMING_PROPS] =  { 0 };
    ULONG ulValues, i = 0;
    BOOL fNameIsMissing = FALSE, fAddressIsMissing = FALSE;
    TCHAR szUnknown[] = TEXT("Unknown"), szAddress[128];
    TCHAR szUnknownAddress[] = TEXT("\\\\Unknown\\Unknown"), szSender[MAX_STRING_SIZE+1];
    LPTSTR pszAlias, pszServer;
    LPSPropValue pProps = NULL, pSenderSKEY = NULL, pSenderEID = NULL, pObjProps = NULL;
    LPVOID lpToBeFreed = NULL;

    HRESULT hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptNewMsgProps, fMapiUnicode, &ulValues, &pProps);
    if (FAILED(hResult))
    {
        TraceResult ("CXPLogon::SetIncomingProps: Failed to get the message changes", hResult);
        goto RecoverAndContinue;
    }
    hResult = S_OK; // Get rid of any warnings
    // These properties should ALWAYS be in an incoming message, but just in case, we have a backup plan.
    if ((PR_SENDER_NAME != pProps[NEW_SENDER_NAME].ulPropTag ||
        PR_SENDER_EMAIL_ADDRESS != pProps[NEW_SENDER_EMAIL].ulPropTag) && !pNode->fRetry)
    {
        pNode->fRetry = TRUE;
        m_List.ReQueueNode (pNode);
        gpfnFreeBuffer (pProps);
        return MAPI_E_CORRUPT_DATA;
    }

    if (PR_SENDER_NAME != pProps[NEW_SENDER_NAME].ulPropTag)
    {
        fNameIsMissing = TRUE;
        pProps[NEW_SENDER_NAME].ulPropTag = PR_SENDER_NAME;
        if (PR_SENDER_EMAIL_ADDRESS == pProps[NEW_SENDER_EMAIL].ulPropTag)
        {
            // Decompose the address and get the alias. At least this is better that UNKNOWN!
            lstrcpy (szAddress, pProps[NEW_SENDER_EMAIL].Value.LPSZ);
            DecomposeAddress (szAddress, &pszServer, &pszAlias);
            lstrcpy (szSender, pszAlias);
            pProps[NEW_SENDER_NAME].Value.LPSZ = szSender;
        }
        else
        {
            // This is the best we can do.
            pProps[NEW_SENDER_NAME].Value.LPSZ = szUnknown;
        }
    }
    if (PR_SENDER_EMAIL_ADDRESS != pProps[NEW_SENDER_EMAIL].ulPropTag)
    {
        pProps[NEW_SENDER_EMAIL].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
        pProps[NEW_SENDER_EMAIL].Value.LPSZ = szUnknownAddress;
        fAddressIsMissing = TRUE;
    }
    spvProps[i].ulPropTag = PR_SENDER_ADDRTYPE;
    spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
    spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
    spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;

    // Looks like ABWDS is around, mark the internal flag.
    // ABWDS is the native WINDS address book service provider. If present,
    // instead of creating a one-off entry for the sender, we
    // will simply open the entry in the WINDS address book and get the
    // necessary properties out from it.
    // Note that in other to open an entry in the ABWDS address book provider
    // we need to have at least the email address of the sender, otherwise we
    // will default to create one-off entries in the sender (and delagate sender)
    if (m_fABWDSInstalled && !fAddressIsMissing)
    {
        PRIVATE_XP_ENTRYID eidEntry = { 0 };
        eidEntry.uidGlobal = guidXPABEntries;
        eidEntry.uidWINDSEntries = guidABEntries;
        // Decompose the address and get the alias.
        DecomposeAddress (pProps[NEW_SENDER_EMAIL].Value.LPSZ, &pszServer, &pszAlias);
        lstrcpy (eidEntry.szObjectAlias, pszAlias);
        // Put the Email address back to what it was
        RecomposeAddress (pszServer, pszAlias, pProps[NEW_SENDER_EMAIL].Value.LPSZ);
        LPMAILUSER pUser = NULL;
        ULONG ulObjType;
        const static SizedSPropTagArray (3, sptWINDSObjProps) =
        {
            3,
            {
                PR_DISPLAY_NAME,
                PR_SEARCH_KEY,
                PR_ENTRYID
            }
        };

        HRESULT hTmpResult = m_pSupObj->OpenEntry (CB_PRIVATE_XP_EID,
                                                   (LPENTRYID)&eidEntry,
                                                   NULL,
                                                   0,
                                                   &ulObjType,
                                                   (LPUNKNOWN *)&pUser);
        if (!hTmpResult)
        {
            hTmpResult = pUser->GetProps ((LPSPropTagArray)&sptWINDSObjProps,
                                          fMapiUnicode,
                                          &ulValues,
                                          &pObjProps);
            if (SUCCEEDED(hTmpResult)) // We might get warnings
            {
                if (PR_DISPLAY_NAME == pObjProps[0].ulPropTag &&
                    PR_SEARCH_KEY == pObjProps[1].ulPropTag &&
                    PR_ENTRYID == pObjProps[2].ulPropTag)
                {
                    if (fNameIsMissing)
                    {
                        spvProps[i].ulPropTag = PR_SENDER_NAME;
                        spvProps[i++].Value.LPSZ = pObjProps[0].Value.LPSZ;
                    }
                    spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
                    spvProps[i++].Value.bin = pObjProps[1].Value.bin;
                    spvProps[i].ulPropTag = PR_ENTRYID;
                    spvProps[i++].Value.bin = pObjProps[2].Value.bin;
                    if (PR_SENT_REPRESENTING_NAME != pProps[NEW_SENT_NAME].ulPropTag &&
                        PR_SENT_REPRESENTING_EMAIL_ADDRESS != pProps[NEW_SENT_EMAIL].ulPropTag)
                    {
                        // If the message doesn't have PR_SENT_REPRESENTING_xxx use the sender
                        // properties for the delagate sender properties
                        spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_NAME;
                        spvProps[i++].Value.LPSZ = pObjProps[0].Value.LPSZ;
                        spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_SEARCH_KEY;
                        spvProps[i++].Value.bin  = pObjProps[1].Value.bin;

spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_ENTRYID;
                        spvProps[i++].Value.bin  = pObjProps[2].Value.bin;
                        spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_ADDRTYPE;
                        spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
                        spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
                        spvProps[i++].Value.LPSZ = pProps[NEW_SENDER_EMAIL].Value.LPSZ;
                    }
                }
                else
                {
                    // We couldn't find the properties needed from the WINDS
                    // address book entry. Continue with one-off entries
                    hTmpResult = MAPI_W_ERRORS_RETURNED;
                }
            }
            pUser->Release();
        }
        if (S_OK == hTmpResult)
        {
            goto ContinueAddingProps;
        }
    }

    // We need to make a search key for this recipient.
    spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
    hResult = MakeSearchKey (pProps,
                             pProps[NEW_SENDER_EMAIL].Value.LPSZ,
                             &spvProps[i].Value.bin.cb,
                             &spvProps[i].Value.bin.lpb);
    if (!hResult)
    {
        pSenderSKEY = &spvProps[i];
        i++;
        // We need to create a one-off entry in the address book so that we may
        // reply to this recipient. If the recipient already exist, its
        // entry Id will be returned instead.
        spvProps[i].ulPropTag = PR_SENDER_ENTRYID;

        //else
        {
            hResult = m_pSupObj->CreateOneOff (pProps[NEW_SENDER_NAME].Value.LPSZ,
                                               WINDS_ADDRESS_TYPE,
                                               pProps[NEW_SENDER_EMAIL].Value.LPSZ,
                                               fMapiUnicode,
                                               &spvProps[i].Value.bin.cb,
                                               (LPENTRYID *)&spvProps[i].Value.bin.lpb);
        }
        if (hResult)
        {
            i--; // Ignore this property and the previous
        }
        else
        {
            pSenderEID = &spvProps[i];
            i++;
        }
    }
    TraceResult ("CXPLogon::SetIncomingProps: Failed to create a one-off for the sender", hResult);

    if (PR_SENT_REPRESENTING_NAME == pProps[NEW_SENT_NAME].ulPropTag &&
        PR_SENT_REPRESENTING_EMAIL_ADDRESS == pProps[NEW_SENT_EMAIL].ulPropTag)
    {
        spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
        hResult = MakeSearchKey (pProps,
                                 pProps[NEW_SENT_EMAIL].Value.LPSZ,
                                 &spvProps[i].Value.bin.cb,
                                 &spvProps[i].Value.bin.lpb);
        if (!hResult)
        {
            i++;
            spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
            hResult = m_pSupObj->CreateOneOff (pProps[NEW_SENT_NAME].Value.LPSZ,
                                               WINDS_ADDRESS_TYPE,
                                               pProps[NEW_SENT_EMAIL].Value.LPSZ,
                                               fMapiUnicode,
                                               &spvProps[i].Value.bin.cb,
                                               (LPENTRYID *)&spvProps[i].Value.bin.lpb);
            if (hResult)
            {
                i--; // Ignore this property and the previous
            }
            else
            {
                lpToBeFreed = spvProps[i].Value.bin.lpb;
                i++;
            }
        }
        // We trace the error but is not fatal
        TraceResult ("CXPLogon::SetIncomingProps: Failed to create a oneoff for the delegated sender", hResult);
    }
    else
    {
        // If the message doesn't have PR_SENT_REPRESENTING_xxx properties AND we were
        // successful at creating the one-off for the sender, we set PR_SENT_REPRESENTING_xxx
        // to the same values as PR_SENDER_xxx
        if (!hResult)
        {
            spvProps[i].ulPropTag       = PR_SENT_REPRESENTING_NAME;
            spvProps[i++].Value.LPSZ    = pProps[NEW_SENDER_NAME].Value.LPSZ;
            spvProps[i].ulPropTag       = PR_SENT_REPRESENTING_ADDRTYPE;
            spvProps[i++].Value.LPSZ    = WINDS_ADDRESS_TYPE;
            spvProps[i].ulPropTag       = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
            spvProps[i++].Value.LPSZ    = pProps[NEW_SENDER_EMAIL].Value.LPSZ;

            ASSERT (PR_SENDER_ENTRYID == pSenderEID->ulPropTag);
            spvProps[i].ulPropTag       = PR_SENT_REPRESENTING_ENTRYID;
            spvProps[i++].Value         = pSenderEID->Value;

            ASSERT (PR_SENDER_SEARCH_KEY == pSenderSKEY->ulPropTag);
            spvProps[i].ulPropTag       = PR_SENT_REPRESENTING_SEARCH_KEY;
            spvProps[i++].Value         = pSenderSKEY->Value;
        }
    }

ContinueAddingProps:
    // If the sender's address is the same as our address (the recipient), then the message
    // was sent from us, to ourselves, therefore we must set the flag bit in the message.
    if (0 == lstrcmpi (pProps[NEW_SENDER_EMAIL].Value.LPSZ, m_szAddress))
    {
        ASSERT (PR_MESSAGE_FLAGS == pProps[NEW_MSG_FLAGS].ulPropTag);
        spvProps[i].ulPropTag = PR_MESSAGE_FLAGS;
        spvProps[i++].Value.l = pProps[NEW_MSG_FLAGS].Value.l | MSGFLAG_FROMME;
    }

RecoverAndContinue:
    // We must stamp the message with this properties since we (this transport)
    // were the ones who processed the incoming message.
    spvProps[i].ulPropTag       = PR_RECEIVED_BY_ENTRYID;
    spvProps[i++].Value.bin     = m_pIdentityProps[XPID_EID].Value.bin;
    spvProps[i].ulPropTag       = PR_RECEIVED_BY_NAME;
    spvProps[i++].Value.LPSZ    = m_pIdentityProps[XPID_NAME].Value.LPSZ;
    spvProps[i].ulPropTag       = PR_RECEIVED_BY_SEARCH_KEY;
    spvProps[i++].Value.bin     = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
    spvProps[i].ulPropTag       = PR_RECEIVED_BY_ADDRTYPE;
    spvProps[i++].Value.LPSZ    = WINDS_ADDRESS_TYPE;
    spvProps[i].ulPropTag       = PR_RECEIVED_BY_EMAIL_ADDRESS;
    spvProps[i++].Value.LPSZ    = m_szAddress;

    // In this transport we set PR_RCVD_xxx with the values of PR_RECEIVED_xxx
    spvProps[i].ulPropTag       = PR_RCVD_REPRESENTING_ENTRYID;
    spvProps[i++].Value.bin     = m_pIdentityProps[XPID_EID].Value.bin;
    spvProps[i].ulPropTag       = PR_RCVD_REPRESENTING_NAME;
    spvProps[i++].Value.LPSZ    = m_pIdentityProps[XPID_NAME].Value.LPSZ;
    spvProps[i].ulPropTag       = PR_RCVD_REPRESENTING_SEARCH_KEY;
    spvProps[i++].Value.bin     = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
    spvProps[i].ulPropTag       = PR_RCVD_REPRESENTING_ADDRTYPE;
    spvProps[i++].Value.LPSZ    = WINDS_ADDRESS_TYPE;
    spvProps[i].ulPropTag       = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
    spvProps[i++].Value.LPSZ    = m_szAddress;

    SYSTEMTIME st;
    FILETIME ftTime;
    GetSystemTime (&st);
    SystemTimeToFileTime (&st, &ftTime);
    spvProps[i].ulPropTag = PR_MESSAGE_DOWNLOAD_TIME;
    spvProps[i++].Value.ft = ftTime;

    LPMAPITABLE pTable;
    LPSRowSet pRows;
    BOOL fFoundCC, fFoundTO;
    fFoundCC = fFoundTO = FALSE;
    LPSPropValue pRecip;
    SPropValue spvFilter = { 0 };
    spvFilter.ulPropTag = PR_ADDRTYPE;
    spvFilter.Value.LPSZ = WINDS_ADDRESS_TYPE;

    SRestriction srRecips = { 0 };
    srRecips.rt = RES_PROPERTY;
    srRecips.res.resProperty.relop = RELOP_EQ;
    srRecips.res.resProperty.ulPropTag = PR_ADDRTYPE;
    srRecips.res.resProperty.lpProp = &spvFilter;

    hResult = pMsgObj->GetRecipientTable (fMapiUnicode, &pTable);
    if (!hResult)
    {
        hResult = HrQueryAllRows (pTable,
                                  (LPSPropTagArray)&sptMsgRecipProps,
                                  &srRecips,
                                  NULL,
                                  0,
                                  &pRows);
        if (!hResult)
        {
            ASSERTMSG (pRows->cRows > 0, "Huh?, Zero rows?");
            for (ULONG ulIndex=0; ulIndex<pRows->cRows; ulIndex++)
            {
                pRecip = pRows->aRow[ulIndex].lpProps;
                ASSERTMSG (PR_EMAIL_ADDRESS == pRecip[0].ulPropTag, "Where is the PR_EMAIL_ADDRESS?");
                ASSERTMSG (PR_RECIPIENT_TYPE == pRecip[1].ulPropTag, , "Where is the PR_RECIPIENT_TYPE?");
                if (PR_EMAIL_ADDRESS == pRecip[0].ulPropTag)
                {
                    // We use the address for the comparison because the sender might
                    // have create a one-off to send us this message and the only thing that this
                    // transport can count on being the same across all computers
                    // is the email address. The display name is modifyable by the user.
                    if (0 == lstrcmpi (pRecip[0].Value.LPSZ, m_szAddress))
                    {
                        if (PR_RECIPIENT_TYPE == pRecip[1].ulPropTag)
                        {
                            if (MAPI_TO == pRecip[1].Value.l)
                            {
                                fFoundTO = TRUE;
                            }
                            else
                            {
                                if (MAPI_CC == pRecip[1].Value.l)
                                {
                                    fFoundCC = TRUE;
                                }
                            }
                        }
                    }
                }
            }
            FreeProws (pRows);
        }
        pTable->Release();
    }
    TraceResult ("CXPLogon::SetIncomingProps: Something went wrong setting PR_MESSAGE_xxx", hResult);

    if (!hResult)
    {
        spvProps[i].ulPropTag = PR_MESSAGE_TO_ME;
        spvProps[i++].Value.b = fFoundTO;

        spvProps[i].ulPropTag = PR_MESSAGE_CC_ME;
        spvProps[i++].Value.b = fFoundCC;

        spvProps[i].ulPropTag = PR_MESSAGE_RECIP_ME;
        spvProps[i++].Value.b = fFoundTO || fFoundCC;
    }
    ASSERT (i <= NUM_INCOMING_PROPS);
    hResult = pMsgObj->SetProps (i, spvProps, NULL);
    TraceResult ("CXPLogon::SetIncomingProps: Failed to set properties in the message", hResult);
    gpfnFreeBuffer(lpToBeFreed);
    if(pSenderEID)
        gpfnFreeBuffer(pSenderEID->Value.bin.lpb);
    gpfnFreeBuffer (pProps);
    gpfnFreeBuffer (pObjProps);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::GetMsgTempFileName()
//
//    Parameters
//      pszFileName     Pointer to a buffer allocated by the caller where the
//                      function returns a fully qualified path and file for
//                      a uniquely named temporary file.
//
//    Purpose
//      This function creates a temporary file name. The file name will be
//      returned in the pszFileName buffer which must have been allocated by
//      the caller. The file will have a fully qualified path to its location.
//      The location of the file is on the TEMP directory, as set in the system,
//      and within it, in the directory for the downloads of the WINDS
//      message transport
//
//    Return Value
//      TRUE if the function is successful at creating a temporary
//      unique file name. FALSE otherwise.
//
BOOL WINAPI CXPLogon::GetMsgTempFileName (LPTSTR pszFileName)
{
    TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
    // Ask the system for the TEMP directory
    DWORD dwChars = GetTempPath (_MAX_PATH, szTmpDir);
    if (dwChars)
    {
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
        dwChars = ::GetTempFileName (szDownloadDir,         // Call the Win32 API
                                     XP_MSG_FILE_PREFIX,    // Our transport's fixed prefix for temp files
                                     0,                     // Use the time to create a pseudo-random number
                                     pszFileName);          // Destination buffer
    }
    if (!dwChars)
    {
        TraceResult ("CXPLogon::GetMsgTempFileName: Failed to get temp path or file name", GetLastError());
    }
    return (0 != dwChars ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::CreateMsgHeaderTextLine()
//
//    Parameters
//      pProps              Array of properties of the message being
//                          submitted where we get the information to create a
//                          message header string to send to the server
//      pszBuffer           Buffer where the function copies the formated data
//                          for the server message header string
//      time                Reference to a FILETIME structure with the time
//                          the transport is delivering the message.
//
//    Purpose
//      This function creates a string with the data needed in the server
//      side to update the remote server mailboxes message header file of
//      the recipients.
//
//    Return Value
//      None
//
void WINAPI CXPLogon::CreateMsgHeaderTextLine (LPSPropValue     pProps,
                                               LPTSTR           pszBuffer,
                                               FILETIME &       time)
{
    // In case we don't find the properties in the array, we need
    // to supply default values for the strings. This method
    // should never fail.
    LPTSTR pszSubject = TEXT("<Subject Not Found>");
    LPTSTR pszMsgClass = TEXT("IPM.Note");
    TCHAR szDisplayTo[256] = { 0 };

    // In case we don't have the expected properties, assign default values.
    long lMsgFlags = 0;
    if (PR_MESSAGE_FLAGS == pProps[MSG_FLAGS].ulPropTag)
    {
        // Filter out the flags to only allow the ones listed
        lMsgFlags = pProps[MSG_FLAGS].Value.l & (MSGFLAG_UNMODIFIED |
                                                 MSGFLAG_HASATTACH |
                                                 MSGFLAG_FROMME |
                                                 MSGFLAG_RESEND);
    }
    if (pProps[MSG_SIZE].ulPropTag != PR_MESSAGE_SIZE)
    {
        pProps[MSG_SIZE].Value.l = 1024; // Default to 1Kb
    }
    if (pProps[MSG_PRIORITY].ulPropTag != PR_PRIORITY)
    {
        pProps[MSG_PRIORITY].Value.l = PRIO_NORMAL;
    }
    if (pProps[MSG_IMPORTANCE].ulPropTag != PR_IMPORTANCE)
    {
        pProps[MSG_IMPORTANCE].Value.l = IMPORTANCE_NORMAL;
    }
    if (pProps[MSG_SENSITIVITY].ulPropTag != PR_SENSITIVITY)
    {
        pProps[MSG_SENSITIVITY].Value.l = SENSITIVITY_NONE;
    }

    // Get the string of TO recipients. If the string is too long, set the elipsis at the end of the buffer
    if (pProps[MSG_DISP_TO].ulPropTag == PR_DISPLAY_TO)
    {
        LPTSTR pszDisplayTo = pProps[MSG_DISP_TO].Value.LPSZ;
        if (lstrlen (pszDisplayTo) > 256)
        {
            pszDisplayTo[252] = '.'; // Copy the elipsis for a long string
            pszDisplayTo[253] = '.';
            pszDisplayTo[254] = '.';
            pszDisplayTo[255] = 0;
        }
        LPTSTR pszOneName = strtok (pszDisplayTo, ";");
        if (pszOneName)
        {
            pszOneName[lstrlen(pszOneName) - 1] = 0;
            lstrcpy (szDisplayTo, &pszOneName[1]);
        }
        while (pszOneName)
        {
            pszOneName = strtok (NULL, ";");
            if (pszOneName)
            {
                pszOneName[0] = TEXT(';');
                pszOneName[1] = TEXT(' ');
                pszOneName[lstrlen(pszOneName) - 1] = 0;
                lstrcat (szDisplayTo, pszOneName);
            }
        }
    }
    if (0 == szDisplayTo[0])
    {
        lstrcpy (szDisplayTo, TEXT("<Display To Names Not Found>"));
    }
    // Get the message class string.
    if (pProps[MSG_CLASS].ulPropTag == PR_MESSAGE_CLASS)
    {
        pszMsgClass = pProps[MSG_CLASS].Value.LPSZ;
    }
    // Get the subject. If the string is too long, set the elipsis at the end of the buffer
    if (pProps[MSG_SUBJECT].ulPropTag == PR_SUBJECT)
    {
        pszSubject = pProps[MSG_SUBJECT].Value.LPSZ;
        if (lstrlen (pszSubject) > 256)
        {
            pszSubject[252] = '.'; // Copy the elipsis for a long string
            pszSubject[253] = '.';
            pszSubject[254] = '.';
            pszSubject[255] = 0;
        }
    }
    // Write the properties into the buffer. This buffer is the header
    // information we send to the server The buffer passed in must hold
    // at least 1024 characters
    wsprintf (pszBuffer,
              // The string must end with a ',' (for the parsing logic
              // in the downloading of the headers)
              TEXT("%d,%s,%d,%s,%d,%s,%s,%d,%d,%d,%d,%d,%d,%d,"),
              lstrlen(m_UserInfo.szFullName),
              m_UserInfo.szFullName,
              lstrlen(szDisplayTo),
              szDisplayTo,
              lstrlen(pszSubject),
              pszSubject,
              pszMsgClass,
              lMsgFlags,
              pProps[MSG_SIZE].Value.l,
              pProps[MSG_PRIORITY].Value.l,
              pProps[MSG_IMPORTANCE].Value.l,
              pProps[MSG_SENSITIVITY].Value.l,
              time.dwLowDateTime,
              time.dwHighDateTime);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::IsValidAddress()
//
//    Parameters
//      pszAddress      The address of the recipient. This string gets
//                      decomposed locally, so it can't be re-used by
//                      the caller.
//      ppszServer      Pointer to a location where the function returns
//                      the remote server name
//      ppszMailbox     Pointer to a location where the function returns
//                      the remote user mailbox name
//
//    Purpose
//      This functions takes the address of a recipient and makes sure it is
//      valid (as far as syntax) and decomposes the address into two
//      components: Remote server name and Remote user mailbox name. The
//      two component strings are returned to the caller.
//
//    Return Value
//      TRUE if the address of the recipient is valid. FALSE otherwise.
//
BOOL WINAPI CXPLogon::IsValidAddress (LPTSTR        pszAddress,
                                      LPTSTR *      ppszServer,
                                      LPTSTR *      ppszMailbox)
{
    // Make sure that we can read the string
    if (!pszAddress || IsBadStringPtr (pszAddress, 32))
    {
        TraceMessage ("CXPLogon::IsValidAddress: Invalid string pointer");
        return FALSE;
    }
    DecomposeAddress (pszAddress, ppszServer, ppszMailbox);
    // Validate the names of the server and the mailbox
    if (!IsValidServerName (*ppszServer) || !*ppszMailbox)
    {
        TraceMessage ("CXPLogon::IsValidAddress: Invalid address for remote server host");
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::GrowAddressList()
//
//    Parameters
//      ppAdrList           Pointer to an address where the old address list
//                          is and where the new resized address list will
//                          be returned
//      ulResizeBy          Number of new address entries to add to the list
//      pulOldAndNewCount   Number of entries in the old address list. In
//                          this parameter, upon sucessful return, will have
//                          the number of in the new address list
//
//    Purpose
//      In this function, given an address list with pulOldAndNewCount of
//      entries, we resize the address list to hold the old number of
//      entries plus the ulResizeBy entries. The old address list contents
//      are copied to the new list and the count reset. The memory for the
//      old address list is released here.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::GrowAddressList (LPADRLIST *   ppAdrList,
                                        ULONG         ulResizeBy,
                                        ULONG *       pulOldAndNewCount)
{
    LPADRLIST pNewAdrList;
    // Calculate how big the new buffer for the expanded address list should be
    ULONG cbSize = CbNewADRLIST ((*pulOldAndNewCount) + ulResizeBy);
    // Allocate the memory for it
    HRESULT hResult = gpfnAllocateBuffer (cbSize, (LPVOID *)&pNewAdrList);
    if (hResult)
    {
        // We can't continue
        TraceResult ("CXPLogon::GrowAddressList: Failed to allocate memory for resized address list", hResult);
        return hResult;
    }

    // Zero-out all memory for neatness
    ZeroMemory (pNewAdrList, cbSize);

    // If we had entries in the old address list, copy the memory from
    // the old addres list into the new expanded list
    if ((*pulOldAndNewCount))
    {
        CopyMemory (pNewAdrList, *ppAdrList, CbNewADRLIST ((*pulOldAndNewCount)));
    }

    // Set the number of entries in the new address list to the OLD size
    pNewAdrList->cEntries = (*pulOldAndNewCount);

    // We must return the number of available entries in the new expanded address list
    (*pulOldAndNewCount) += ulResizeBy;

    // Free the old memory and put the new pointer in place
    gpfnFreeBuffer (*ppAdrList);
    *ppAdrList = pNewAdrList;
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::CheckSpoolerYield()
//
//    Parameters
//      fReset      Resets the pseudo-timer
//
//    Purpose
//      Enforce the 0.2 second rule for transport that need to yield to the
//      MAPI spooler.  Called periodically while processing a message to
//      determine if we have used more than 0.2 seconds.  If so, then call
//      SpoolerYield(), else just continue.
//      This is called with fReset set to TRUE when we first enter one
//      of the Transport Logon methods (usually one that is known to
//      take a long time like StartMessage() or SubmitMessage(). )
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::CheckSpoolerYield (BOOL fReset)
{
    DWORD dwStop;
    static DWORD dwStart;
    if (fReset)
    {
        dwStart = GetTickCount();
    }
    else
    {
        dwStop = GetTickCount();
        if ((dwStop - dwStart) > 200) // 200 milliseconds
        {
            m_pSupObj->SpoolerYield (0);
            dwStart = GetTickCount();
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::InitializeStatusRow()
//
//    Parameters
//      ulFlags     0 if the properties are being created the first time.
//                  MODIFY_FLAGS if a change is being made to the properties
//
//    Purpose
//      To initialize or modify the status properties of a CXPLogon
//      object. This function allocates an array with NUM_STATUS_ROW_PROPS
//      properties and initializes them.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::InitializeStatusRow (ULONG ulFlags)
{
    #define NUM_STATUS_ROW_PROPS    10
    SPropValue spvStatusRow[NUM_STATUS_ROW_PROPS] = { 0 };
    ULONG i = 0;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_PROVIDER_DISPLAY property: The transport readable name
    spvStatusRow[i].ulPropTag = PR_PROVIDER_DISPLAY;
    spvStatusRow[i++].Value.LPSZ = TRANSPORT_DISPLAY_NAME_STRING;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_RESOURCE_METHODS property. These are the methods implemented
    // in the our IMAPIStatus implementation (CMAPIStatus class.)
    spvStatusRow[i].ulPropTag = PR_RESOURCE_METHODS;
    // we support ALL the methods in our implementation of IMAPIStatus interface (except the WRITABLE ones)
    spvStatusRow[i++].Value.l = STATUS_SETTINGS_DIALOG |
                                STATUS_FLUSH_QUEUES |
                                STATUS_VALIDATE_STATE |
                                STATUS_CHANGE_PASSWORD;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_STATUS_CODE property.
    spvStatusRow[i].ulPropTag = PR_STATUS_CODE;
    spvStatusRow[i++].Value.l = GetTransportStatusCode();

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_STATUS_STRING property
    TCHAR szStatus[64];
    LoadStatusString (szStatus, sizeof(szStatus));
    spvStatusRow[i].ulPropTag = PR_STATUS_STRING;
    spvStatusRow[i++].Value.LPSZ = szStatus;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_DISPLAY_NAME property
    TCHAR szDisplayName[64];
    wsprintf (szDisplayName, TEXT("%s (%s)"), TRANSPORT_DISPLAY_NAME_STRING, m_szServer);
    spvStatusRow[i].ulPropTag = PR_DISPLAY_NAME;
    spvStatusRow[i++].Value.LPSZ = szDisplayName;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_IDENTITY_ENTRYID property
    spvStatusRow[i].ulPropTag = PR_IDENTITY_ENTRYID;
    spvStatusRow[i++].Value = m_pIdentityProps[XPID_EID].Value;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_IDENTITY_DISPLAY property
    spvStatusRow[i].ulPropTag = PR_IDENTITY_DISPLAY;
    spvStatusRow[i++].Value.LPSZ = m_pIdentityProps[XPID_NAME].Value.LPSZ;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_IDENTITY_SEARCH_KEY property
    spvStatusRow[i].ulPropTag = PR_IDENTITY_SEARCH_KEY;
    spvStatusRow[i++].Value = m_pIdentityProps[XPID_SEARCH_KEY].Value;

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_REMOTE_PROGRESS property
    spvStatusRow[i].ulPropTag = PR_REMOTE_PROGRESS;
    spvStatusRow[i++].Value.l = -1; // Not initialized

    ///////////////////////////////////////////////////////////////////////////
    // Set the PR_REMOTE_VALIDATE_OK property
    spvStatusRow[i].ulPropTag = PR_REMOTE_VALIDATE_OK;
    spvStatusRow[i++].Value.b = TRUE;
    ASSERT (NUM_STATUS_ROW_PROPS == i);

    // Write the entries on the provider's session status row
    HRESULT hResult = m_pSupObj->ModifyStatusRow (i, spvStatusRow, ulFlags);
    TraceResult ("CXPLogon::InitializeStatusRow: Failed to modify the status row", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::CheckForUnfinishedDownloads()
//
//    Parameters
//      None.
//
//    Purpose
//      This function checks to see if we have files where downloaded messages
//      were lefted from a previous session or from a previous failed download
//      sequence. The message files, if any, should be in the data directory of
//      this service. If any files are found, the message queue is loaded and
//      the during out inbound logic initialization in
//      IXPLogon::TransportNotify(), we check the queue. If the queue is not
//      empty, we tell the transport to flush our inbound flush.
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::CheckForUnfinishedDownloads()
{
    WIN32_FIND_DATA wfdFile = { 0 };
    TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH], szSearchMask[512];
    GetTempPath (_MAX_PATH, szTmpDir);
    lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
    wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
    wsprintf (szSearchMask, TEXT("%s\\%s*.%s"), szDownloadDir, XP_MSG_FILE_PREFIX, XP_MSG_FILE_SUFFIX);
    HANDLE hSearch = FindFirstFile (szSearchMask, &wfdFile);
    if (hSearch == INVALID_HANDLE_VALUE)
    {
        return;
    }
    BOOL fFound = TRUE;
    while (fFound)
    {
        wsprintf (szTmpDir, TEXT("%s\\%s"), szDownloadDir, wfdFile.cFileName);
        // If the file size of 0, then is probably some garbage file. Get rid of it.
        // A zero-sized file does not contains a message.
        if (0 == wfdFile.nFileSizeHigh && 0 == wfdFile.nFileSizeLow)
        {
            DeleteFile (szTmpDir);
        }
        else
        {
            m_List.QueuePendingMsgFile (szTmpDir);
        }
        fFound = FindNextFile (hSearch, &wfdFile);
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::DownloadMessageHeaders()
//
//    Parameters
//      None
//
//    Purpose
//      This function downloads the message headers for the user's mailbox and
//      is a remote folder is available, re-load the contents table of the
//      folder with the new message headers information.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPLogon::DownloadMessageHeaders()
{
    // Guard against re-entrancy from the timer call back which happens on a separate thread
    UpdateProgress (0, REMOTE_ACTION_HEADER_REFRESH);     // Start the progress bar
    HRESULT hResult = GetHeadersFromServer (m_szServer,

m_UserInfo.szMailboxName,
                                            m_szHeaders);
    UpdateProgress (50, REMOTE_ACTION_HEADER_REFRESH);    // Half way through
    if (!hResult && m_pStatusObj && m_pStatusObj->m_pHeaderFolder)
    {
        hResult = m_pStatusObj->m_pHeaderFolder->FillContentsTable (m_szHeaders);
    }
    UpdateProgress (100, REMOTE_ACTION_HEADER_REFRESH);   // Finish
    TraceResult ("CXPLogon::DownloadMessageHeaders", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::EmptyInboundQueue()
//
//    Parameters
//      None
//
//    Purpose
//      Deletes all the node in the queue of downloaded messages, deleting
//      the used memory and closing the handle to the local temporary
//      message file.
//
//    Return Value
//      None
//
void WINAPI CXPLogon::EmptyInboundQueue()
{
    // If we have nodes at this point, is because the transport could not
    // process them at this time. They will be retrieved in the next time this
    // transport comes on line on the same profile or on a profile that has
    // the same user identity.
    PLIST_NODE pNode = m_List.GetDownloadNode();
    while (pNode)
    {
        CloseHandle (pNode->hFile);
        delete pNode;
        // If the list is empty, pNode will be NULL.
        // CList::GetDownloadNode() returns NULL for an empty list.
        pNode = m_List.GetDownloadNode();
    }
}

///////////////////////////////////////////////////////////////////////////////
//    TimerWndProc()
//
//    Parameters
//      { Refer to Win32 documentation }
//
//    Purpose
//      Stub window procedure for the hidden timer window class.
//
//    Return Value
//      Default value return by the default window procedure
//
LRESULT CALLBACK TimerWndProc (HWND      hWnd,
                               UINT      message,
                               WPARAM    wParam,
                               LPARAM    lParam)
{
    return DefWindowProc(hWnd, message, wParam, lParam);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::InitializeTimer()
//
//    Parameters
//      None.
//
//    Purpose
//      Initialize the hidden window for the upload timer.
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::InitializeTimer()
{
    WNDCLASS wc = { 0 };
    // Register the class for this application
    wc.lpfnWndProc      = TimerWndProc;
    wc.hInstance        = m_hInstance;
    wc.lpszClassName    = TIMER_WND_CLASS;
    if (!RegisterClass (&wc))
    {
        TraceResult ("InitializeTimer: Failed to register the timer window class", GetLastError());
        return;
    }
    m_hTimerWnd = CreateWindow (TIMER_WND_CLASS,
                                NULL,
                                WS_OVERLAPPEDWINDOW,
                                0, 0, 0, 0,
                                NULL,
                                NULL,
                                m_hInstance,
                                NULL);
    if (NULL == m_hTimerWnd)
    {
        TraceResult ("InitializeTimer: Failed to create timer window", GetLastError());
        return;
    }
    SetWindowLong (m_hTimerWnd, GWL_USERDATA, (LONG)this);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::StartUploadTimer()
//
//    Parameters
//      None.
//
//    Purpose
//      This function sets a timer with the operating system. When
//      the specified period elapses, we will get called in the procedure
//      specified and we will then flush the incoming and outgoing queues.
//      Note that we round the delivery time to the HOUR
//
//    Return Value
//      None.
//
void WINAPI CXPLogon::StartUploadTimer()
{
    SYSTEMTIME stNow = { 0 };
    GetLocalTime (&stNow);
    WORD wUploadHour = m_stDelivTime.wHour;
    // If the time is 0:00, this is 12:00 AM midnight
    if (!wUploadHour)
    {
        wUploadHour = 24;
    }
    UINT uDelay = 0;
    // How many hours before the next upload?
    int nHours = wUploadHour - stNow.wHour;
    if (nHours <= 0)
    {
        // If the hours is 0 and the time hasn't arrived with in this hour,
        // set the upload timer for THIS hour.
        if (0 == nHours && stNow.wMinute < m_stDelivTime.wMinute)
        {
            nHours = -24;
            uDelay += (UINT)(m_stDelivTime.wMinute - stNow.wMinute) * 60 * 1000;
        }
        nHours += 24;
    }
    uDelay += (UINT)nHours * 60 * 60 * 1000; // Number of milliseconds in the hours
    m_uTimerID = SetTimer (m_hTimerWnd, 0, uDelay, UploadTimerProc);
    if (0 == m_uTimerID)
    {
        TraceResult ("CXPLogon::StartUploadTimer: Failed to set the timer", GetLastError());
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CXPLogon::StopUploadTimer()
//
//    Parameters
//      None.
//
//    Purpose
//      Terminates the timer used for the upload time.
//
//    Return Value
//      None
//
void WINAPI CXPLogon::StopUploadTimer()
{
    // If we have a timer around, terminate it and all its associated resources
    if (m_uTimerID)
    {
        KillTimer (m_hTimerWnd, m_uTimerID);
        DestroyWindow (m_hTimerWnd);
        UnregisterClass (TIMER_WND_CLASS, m_hInstance);
        m_hTimerWnd = NULL;
        m_uTimerID = 0;
    }
}

///////////////////////////////////////////////////////////////////////////////
//    UploadTimerProc()
//
//    Parameters
//      { Refer to the Win32 TIMERPROC callback parameters }
//
//    Purpose
//      This function executes when the timer delay has expired.
//      Now we ask the spooler to send us the deferred messages, and we set
//      the transport state ready to receive updated headers.
//
//    Return Value
//      None
//
void CALLBACK UploadTimerProc (HWND    hTimerWnd,
                               UINT    Message,
                               UINT    idEvent,
                               DWORD   dwTime)
{
    KillTimer (hTimerWnd, idEvent);
    // We asked to get the CXPLogon object who set the timer. We now
    // check and see if the object is valid before we attempt to use it.
    CXPLogon * pLogon = (CXPLogon *)GetWindowLong (hTimerWnd, GWL_USERDATA);
    if (!pLogon ||
        IsBadWritePtr (pLogon, sizeof(CXPLogon)) ||
        IsBadReadPtr (pLogon, sizeof(CXPLogon)))
    {
        TraceMessage ("UploadTimerProc: We got a bogus logon object");
        return;
    }
    pLogon->m_uTimerID = 0;

    // If the transport's outgoing logic has been activated in this session,
    // send all the deferred messages, by flushing the transport.
    if (pLogon->GetTransportStatusCode() & STATUS_OUTBOUND_ENABLED)
    {
        pLogon->AddStatusBits (UPLOADING_MESSAGES);
    }
    // If the transport's incoming logic has been activated in this session,
    // get any pending messages that need to be placed in the default inbox,
    // and set the transport state, ready to get the headers from the server..
    if (pLogon->GetTransportStatusCode() & STATUS_INBOUND_ENABLED)
    {
        pLogon->AddStatusBits (DOWNLOADING_MESSAGES);
        pLogon->SetTransportState (PROCESSING_TIMER_EVENT);
    }
    // Modify the status row now
    pLogon->UpdateStatus();
    // Reset the time for the next upload. Uploading time is a recurring event.
    pLogon->StartUploadTimer();
}

// End of file for XPLOGON.CPP


XPPROV.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPPROV.CPP
//
//  Description
//      This file implements the IXPProvider interface with the methods
//      specified in the MAPI SPI 1.0 specifications. Also some helper
//      functions are implemented in this file.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#define INITGUID
#define USES_IID_IXPProvider
#define USES_IID_IXPLogon
#define USES_IID_IMAPIStatus
#define USES_IID_IMAPIProp
#define USES_IID_IMAPIPropData
#define USES_IID_IMAPIControl
#define USES_IID_IMAPIContainer
#define USES_IID_IMAPIFolder
#define USES_IID_IMAPITableData

#include "XPWDSR.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

///////////////////////////////////////////////////////////////////////////////
//    CXPProvider::CXPProvider()
//
//    Parameters
//      hInst                   Handle to instance of this XP DLL
//
//    Purpose
//      Constructor of the object. Parameters are passed to initialize the
//      data members with the appropiate values.
//
//    Return Value
//      None
//
CXPProvider::CXPProvider (HINSTANCE hInst)
{
    InfoTrace ("CXPProvider: Constructor called");
    m_hInstance    = hInst;
    m_cRef         = 1;
    // Initialize critical sections for this transport
    InitializeCriticalSection (&m_csTransport);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPProvider::~CXPProvider()
//
//    Parameters
//      None
//
//    Purpose
//      Close down and release resources and libraries
//
//    Return Value
//      None
//
CXPProvider::~CXPProvider()
{
    InfoTrace ("CXPProvider: Destructor called");
    m_hInstance = NULL;

    // This is the last method called on a transport, close down the traces
    UnInitTraces();
    DeleteCriticalSection (&m_csTransport);
}

///////////////////////////////////////////////////////////////////////////////
//    CXPProvider::QueryInterface()
//
//    Parameters
//      { Refer to OLE Documentation on this method }
//
//    Purpose
//      Returns a pointer to a interface requested if the interface is
//      supported and implemented by this object. If it is not supported, it
//      returns NULL
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPProvider::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    // OLE requires NULLing parameter
    *ppvObj = NULL;
    // If this is one of the two IID return an interface pointer to it
    if (riid == IID_IXPProvider || riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    // This object does not support the interface requested
    return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
// IXPProvider virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CXPProvider::Shutdown()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub method.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPProvider::Shutdown (ULONG * pulFlags)
{
    InfoTrace ("CXPProvider::Shutdown method called");
    CheckParameters_IXPProvider_Shutdown (this, pulFlags);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CXPProvider::TransportLogon()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Display the logon dialog to show the options saved in the profile for
//      this provider and allow changes to it. Save new configuration settings
//      back in the profile.
//      Create a new CXPLogon object and return it to the spooler. Also,
//      initialize the properties array for each address type handled
//      by this transport. Check all the flags and return them to the spooler
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CXPProvider::TransportLogon (LPMAPISUP     pSupObj,
                                          ULONG         ulUIParam,
                                          LPTSTR        pszProfileName,
                                          ULONG *       pulFlags,
                                          LPMAPIERROR * ppMAPIError,
                                          LPXPLOGON *   ppXPLogon)
{
    InfoTrace ("CXPProvider::TransportLogon method called");
    CheckParameters_IXPProvider_TransportLogon (this,
                                                pSupObj,
                                                ulUIParam,
                                                pszProfileName,
                                                pulFlags,
                                                ppMAPIError,
                                                ppXPLogon);
    CXPLogon * pXPLogon = NULL;
    ULONG ulPropCount;
    LPSPropValue pProps = NULL;
    MAILBOX_INFO UserMBInfo = { 0 };
    CFGDLG CfgDialog = { 0 };
    LPPROFSECT pProfileObj;
    HRESULT hResult = OpenServiceProfileSection (pSupObj, &pProfileObj, gpfnFreeBuffer);
    if (hResult)
    {
        TraceResult ("CXPProvider::TransportLogon: Failed to open the service profile section", hResult);
        return hResult;
    }

    HANDLE hUIMutex = CreateMutex (NULL, FALSE, CONFIG_UI_MUTEX);
    if (NULL == hUIMutex)
    {
        HRESULT hResult = HRESULT_FROM_WIN32 (GetLastError());
        TraceResult ("CXPProvider::TransportLogon: Failed to create UI mutext", hResult);
    }

    hResult = pProfileObj->GetProps ((LPSPropTagArray)&sptLogonProps,
                                     fMapiUnicode,
                                     &ulPropCount,
                                     &pProps);
    if (FAILED(hResult))
    {
        TraceResult ("CXPProvider::TransportLogon: Failed to get the logon props", hResult);
        goto ErrorExit;
    }

    // Fill in the logon UI structure
    CfgDialog.hInst    = m_hInstance;
    CfgDialog.hWnd     = (HWND)ulUIParam;
    CfgDialog.ppProps  = &pProps;
    CfgDialog.pSupObj  = pSupObj;
    CfgDialog.hUIMutex = hUIMutex;

    // In case we get MAPI_W_ERRORS_RETURNED, ignore it and reset
    // to S_OK. Now display the logon configuration dialog
    if (MAPI_W_ERRORS_RETURNED == hResult)
    {
        if (PR_SMP_MAILBOX_ID       != pProps[MAILBOX_ID].ulPropTag  ||
            PR_SMP_CONNECTION_TYPE  != pProps[NET_CON].ulPropTag     ||
            PR_SMP_UPLOAD_TIME      != pProps[UPLOAD_TIME].ulPropTag ||
            PR_SMP_HEADERS_FILENAME != pProps[HEADER_FILE].ulPropTag ||
            PR_SMP_GET_HEADERS      != pProps[GET_HEADERS].ulPropTag)
        {
            TraceMessage ("CXPProvider::TransportLogon: Where are the provider properties?");
            hResult = MAPI_E_UNCONFIGURED;
            if (!(LOGON_NO_DIALOG & *pulFlags))
            {
                PrivInitialize3DCtl (m_hInstance);
                PrivateMessageBox (IDS_MSG_SERVICE_NOT_PROPERLY_CFG, (HWND)ulUIParam);
                PrivUninitialize3DCtl (m_hInstance);
            }
            goto ErrorExit;
        }

        if (PR_SMP_REMOTE_SERVER != pProps[SERVER_NAME].ulPropTag ||
            PR_SMP_MAILBOX_NAME != pProps[MAILBOX_NAME].ulPropTag ||
            PR_SMP_USER_NAME != pProps[USER_NAME].ulPropTag ||
            PR_SMP_MAILBOX_PASSWORD != pProps[PASSWORD].ulPropTag)
        {
            if (LOGON_NO_DIALOG & *pulFlags)
            {
                TraceMessage ("CXPProvider::TransportLogon: UI not allowed but it is needed");
                hResult = MAPI_E_UNCONFIGURED;
                goto ErrorExit;
            }
            PrivInitialize3DCtl (m_hInstance);
ReStartLogonDlg :
            if ((hResult = DoLogonDlg (&CfgDialog)))
            {
                goto ErrorExit;
            }
            hResult = pProfileObj->SetProps (4, pProps, NULL);
            if (hResult)
            {
                TraceResult ("CXPProvider::TransportLogon: Failed to set the properties", hResult);
                goto ErrorExit;
            }
        }
    }
    // Connect to the server with the stored information. If necessary (and allowed) display UI.
    hResult = DoServerLogon (&UserMBInfo,
                             pProps,
                             pProfileObj,
                             !(LOGON_NO_DIALOG & *pulFlags),
                             (HWND)ulUIParam,
                             FALSE,
                             0,
                             NULL,
                             TRUE);
    if (S_FALSE == hResult)
    {
        goto ReStartLogonDlg;
    }
    if (hResult)
    {
        goto ErrorExit;
    }

    // Once we have validated the remote credentials, terminate all connections
    TerminateRemoteConnections();

    try
    {
        // Allocate the IXPLogon-derived object. Initialize its data members with the necessary values
        pXPLogon = new CXPLogon (m_hInstance,
                                 pSupObj,
                                 pProps[HEADER_FILE].Value.LPSZ,
                                 pProps[GET_HEADERS].Value.b,
                                 &UserMBInfo,
                                 pProps[MAILBOX_ID].Value.l,
                                 pProps[SERVER_NAME].Value.LPSZ,
                                 hUIMutex);
        if (!pXPLogon)
        {
            // New failed, propably due to memory shortage
            TraceMessage ("CXPProvider::TransportLogon: Failed to allocate new CXPLogon object");
            hResult = E_OUTOFMEMORY;
        }
    }
    catch (CException & Exception)
    {
        hResult = Exception.GetError();
    }
    if (hResult)
    {
        goto ErrorExit;
    }
    hUIMutex = NULL;

    pXPLogon->CheckForUnfinishedDownloads();

    // Lock other threads because we are going to work on data that is
    // global to all threads (sessions) using this transport
    EnterCriticalSection (&m_csTransport);
    // Check the mode in which MAPI is logging into our transport and set
    // the appropiate internal (this CXPLogon object) transport session flags
    pXPLogon->InitializeTransportStatusFlags (*pulFlags);
    // Initialize the transport ID prop array for this session
    hResult = pXPLogon->SetIdentityProps();
    if (!hResult)
    {
        // Build the transport status row for this session
        hResult = pXPLogon->InitializeStatusRow();
        if (!hResult)
        {
            // Set the session flags returned to MAPI by the transport
            pXPLogon->SetSessionFlags (pulFlags);
            // Set the upload delivery time to the current day, mo, yr and the hour and
            // minute from the config dialog saved in the profile. The profile info
            // is only valid for the hour and minute of day, the date can be old
            pXPLogon->SetUploadTime (pProps[UPLOAD_TIME].Value.ft);
            // Copy our allocated object back to the returned MAPI object pointer
            *ppXPLogon = (LPXPLOGON)pXPLogon;
        }
    }
    // Release the critical section
    LeaveCriticalSection (&m_csTransport);

ErrorExit:
    // Release pProfileObj. This object was AddRef() by MAPI before it
    // came to us. The transport is in charge of cleaning it up (Release()ing it).
    if (pProfileObj)
    {
        pProfileObj->Release();
    }
    PrivUninitialize3DCtl (m_hInstance);

    if (hUIMutex)
    {
        CloseHandle (hUIMutex);
    }
    gpfnFreeBuffer (pProps);
    if (hResult)
    {
        // Something failed, so clean the session if it has been allocated.
        // The Release() method of CXPLogon does the memory deallocation
        // of this object. The destructor of CXPLogon does the rest of
        // the clean up
        if (pXPLogon)
        {
            pXPLogon->Release();
        }
    }
    return hResult;
}

// End of File for XPPROV.CPP


XPSRVCE.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPSRVCE.CPP
//
//  Description
//      This file implements the entry points, XPProviderInit and
//      ServiceEntry for this provider's DLL.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "XPWDSR.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

DTBLEDIT     DtUploadTime = { sizeof(DTBLEDIT), fMapiUnicode, 5, PR_TMP_UPLOAD_TIME };
DTBLCHECKBOX DtGetHeaders = { sizeof(DTBLCHECKBOX), fMapiUnicode, PR_SMP_GET_HEADERS };

DTCTL XPConfigPage[] =
{
    { DTCT_PAGE,     0,           NULL, 0, NULL,     0,                  &DtPage       },
    { DTCT_EDIT,     DT_REQUIRED, NULL, 0, szFilter, IDC_UPLOAD_TIME,    &DtUploadTime },
    { DTCT_CHECKBOX, 0,           NULL, 0, NULL,     IDC_UPDATE_HEADERS, &DtGetHeaders },
    { DTCT_LABEL,    0,           NULL, 0, NULL,     IDC_STATIC1,        &DtLabel      },
    { DTCT_GROUPBOX, 0,           NULL, 0, NULL,     IDC_STATIC2,        &DtGroupBox   },
    { DTCT_GROUPBOX, 0,           NULL, 0, NULL,     IDC_STATIC3,        &DtGroupBox   }
};

TCHAR szDefaultTime[] = TEXT("23:59");

// Global pointer to the MAPI memory allocation functions
LPALLOCATEBUFFER    gpfnAllocateBuffer;  // MAPIAllocateBuffer function
LPALLOCATEMORE      gpfnAllocateMore;    // MAPIAllocateMore function
LPFREEBUFFER        gpfnFreeBuffer;      // MAPIFreeBuffer function

///////////////////////////////////////////////////////////////////////////////
//    ServiceEntry()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Called by the profile setup API to display the provider
//      configuration properties for this transport provider
//
//    Return Value
//      An HRESULT
//
HRESULT STDAPICALLTYPE ServiceEntry (HINSTANCE          hInstance,
                                     LPMALLOC           pMallocObj,
                                     LPMAPISUP          pSupObj,
                                     ULONG              ulUIParam,
                                     ULONG              ulFlags,
                                     ULONG              ulContext,
                                     ULONG              ulCfgPropCount,
                                     LPSPropValue       pCfgProps,
                                     LPPROVIDERADMIN    pAdminProvObj,
                                     LPMAPIERROR *      ppMAPIError)
{
    // Look at TRACES.H and TRACES.CPP for more options to the InitTraces() function
    //InitTraces (0);                                       // Send output only to the COM1 port
    InitTraces (TRACES_CONSOLE | TRACES_NO_COM_OUTPUT);     // Send output to a console window BUT NOT to the COM1
    //InitTraces (TRACES_CONSOLE | TRACES_LOG_FILE);        // Send output to COM1 port AND a console window AND to a log file in C:\MAPILOG.TXT

    InfoTrace ("ServiceEntry function called");
    HRESULT hResult = S_OK;
    LPPROFSECT pProfileObj = NULL;
    ULONG ulPropCount;
    LPSPropValue pProps = NULL;
    HANDLE hUIMutex = NULL;

    // What context where were we called in?
    if (MSG_SERVICE_INSTALL         == ulContext ||
        MSG_SERVICE_UNINSTALL       == ulContext ||
        MSG_SERVICE_PROVIDER_CREATE == ulContext ||
        MSG_SERVICE_PROVIDER_DELETE == ulContext)
    {
        // We don't handle any of these contexts
        TraceString1 ("ServiceEntry: Immediate return. Context: %d", ulContext);
        goto ErrorExit;
    }
    TraceString1 ("ServiceEntry: Context for call: %d", ulContext);
    ghInstance = hInstance;

    // Get the MAPI memory allocation routines we'll be needing
    hResult = pSupObj->GetMemAllocRoutines (&gpfnAllocateBuffer, &gpfnAllocateMore, &gpfnFreeBuffer);
    if (hResult)
    {
        TraceResult ("ServiceEntry: Failed to get the memory allocation functions", hResult);
        goto ErrorExit;
    }

    // Open the Profile Section for this service
    hResult = pAdminProvObj->OpenProfileSection (NULL,
                                                 NULL,
                                                 MAPI_MODIFY,
                                                 &pProfileObj);
    if (hResult)
    {
        goto ErrorExit;
    }
    if (MSG_SERVICE_CREATE == ulContext)
    {
        // If ServiceEntry is called because it the service is being created, we create a temporary
        // file where we save the headers text file when the user updates them from
        // the remote host. The headers should be persistent from session to
        // session (using the same profile, of course) but unique per session.
        TCHAR szTmpDir[_MAX_PATH], szFile[_MAX_PATH];
        GetTempPath (_MAX_PATH, szTmpDir);
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        CreateDirectory (szTmpDir, NULL);
        GetTempFileName (szTmpDir,      // Create the file in the system temp directory
                         TEXT("XRH"),   // Our transport fixed prefix for temp files
                         0,             // Use the time to create a pseudo-random number
                         szFile);       // Destination buffer
        SPropValue spvProps[4] = { 0 };
        spvProps[0].ulPropTag = PR_SMP_HEADERS_FILENAME;
        spvProps[0].Value.LPSZ = szFile;
        SYSTEMTIME st;
        GetLocalTime (&st);
        st.wHour = 23; st.wMinute = 59; st.wSecond = 0;
        spvProps[1].ulPropTag = PR_SMP_UPLOAD_TIME;
        SystemTimeToFileTime (&st, &spvProps[1].Value.ft);
        spvProps[2].ulPropTag = PR_SMP_CONNECTION_TYPE;
        spvProps[2].Value.l = OFFLINE_CONNECT;
        spvProps[3].ulPropTag = PR_SMP_GET_HEADERS;
        spvProps[3].Value.b = TRUE;
        hResult = pProfileObj->SetProps (sizeof(spvProps)/sizeof(SPropValue), spvProps, NULL);
        TraceResult ("ServiceEntry: Failed to save the headers file name back into the profile", hResult);
    }
    // The profile section is open, get the properties out of it
    hResult = pProfileObj->GetProps ((LPSPropTagArray)&sptLogonProps,
                                     fMapiUnicode,
                                     &ulPropCount,
                                     &pProps);
    if (FAILED(hResult))
    {
        TraceResult ("ServiceEntry: Failed to get profile section properties", hResult);
        goto ErrorExit;
    }
    ASSERT (NUM_LOGON_PROPERTIES == ulPropCount);

    if (MSG_SERVICE_DELETE == ulContext)
    {
        // If the service is being removed from a profile, we must cleanup
        // any temporary file (i.e. the headers text file)
        if (PR_SMP_HEADERS_FILENAME == pProps[HEADER_FILE].ulPropTag)
        {
            DeleteFile (pProps[HEADER_FILE].Value.LPSZ);
        }
        TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
        GetTempPath (_MAX_PATH, szTmpDir);
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        if (PR_SMP_MAILBOX_NAME == pProps[MAILBOX_NAME].ulPropTag)
        {
            wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, pProps[MAILBOX_NAME].Value.LPSZ);
            RemoveDirectory (szDownloadDir);
        }
        RemoveDirectory (szTmpDir);
        // We are done
        hResult = S_OK;
        goto ErrorExit;
    }
    // After the setup wizard has been called the ServiceEntry functions gets called
    // with a set of properties obtained from the user for configuring this provider.
    // Merge or replace those properties onto the one we obtained from the profile section.
    if (pCfgProps && ulCfgPropCount)
    {
        hResult = MergeConfigProps (ulCfgPropCount, pCfgProps, pProps);
        if (hResult)
        {
            goto ErrorExit;
        }
    }
    // If we can, display the UI...
    if (SERVICE_UI_ALWAYS & ulFlags || SERVICE_UI_ALLOWED & ulFlags)
    {
        CFGDLG CfgDialog = { 0 };
        hUIMutex = CreateMutex (NULL, FALSE, CONFIG_UI_MUTEX);
        if (NULL == hUIMutex)
        {
            TraceResult ("ServiceEntry: Failed to create UI mutext", GetLastError());
        }
        PrivInitialize3DCtl (hInstance);
        if (WAIT_TIMEOUT == WaitForSingleObject (hUIMutex, 250)) // Wait for the MUTEX for 1/4 second
        {
            PrivateMessageBox (IDS_MSG_CONFIG_UI_IN_USE, (HWND)ulUIParam);
            hResult = MAPI_E_BUSY;
            goto ErrorExit;
        }

        // Fill in the logon UI structure
        CfgDialog.hInst    = hInstance;
        CfgDialog.hWnd     = (HWND)ulUIParam;
        CfgDialog.ppProps  = &pProps;
        CfgDialog.pSupObj  = pSupObj;
        CfgDialog.ulFlags  = MSG_SERVICE_UI_READ_ONLY & ulFlags ? UI_READONLY : 0;
        CfgDialog.hUIMutex = NULL; // So that the DoLogonDlg() won't have to wait.
ShowPropsAgain:
        hResult = DoLogonDlg (&CfgDialog);
        if (hResult)
        {
            goto ErrorExit;
        }
        if (!PingRemoteServer ((HWND)ulUIParam, pProps))
        {
            goto ShowPropsAgain;
        }
    }
    if (PR_SMP_MAILBOX_NAME == pProps[MAILBOX_NAME].ulPropTag)
    {
        TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
        GetTempPath (_MAX_PATH, szTmpDir);
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, pProps[MAILBOX_NAME].Value.LPSZ);
        CreateDirectory (szDownloadDir, NULL);
    }

    hResult = pProfileObj->SetProps (NUM_LOGON_PROPERTIES, pProps, NULL);
    TraceResult ("ServiceEntry: Failed to save the properties back into the profile", hResult);

ErrorExit:
    // Clean up
    if (pProfileObj)
    {
        pProfileObj->Release();
    }
    if (gpfnFreeBuffer)
    {
        gpfnFreeBuffer (pProps);
    }
    if (hUIMutex)
    {
        ReleaseMutex (hUIMutex);
        CloseHandle (hUIMutex);
    }
    // In case the DLL was loaded, unload it.
    // This DLL gets loaded and initialized in the call to DoLogonDlg()
    PrivUninitialize3DCtl (hInstance);

    // If we have remote connections going on, close them now
    TerminateRemoteConnections();

    // Close down the traces
    UnInitTraces();
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    DoLogonDlg()
//
//    Parameters
//      pCfgDialog   Pointer to an CFGDLG structure.
//
//    Purpose
//      This functions gets called to display the configuration properties for
//      this transport. When invoked, it will parse the transport logon
//      property array and initialize the fields in the property sheet with it.
//      The function could also get invoked to display the settings in
//      READ-ONLY mode in which case we disable writing to the editable fields.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI DoLogonDlg (PCFGDLG pCfgDialog)
{
    DWORD dwWait = WaitForSingleObject (pCfgDialog->hUIMutex, 1000); // Wait for the MUTEX for 1 second
    PrivInitialize3DCtl (pCfgDialog->hInst);
    if (WAIT_TIMEOUT == dwWait)
    {
        PrivateMessageBox (IDS_MSG_CONFIG_UI_IN_USE, pCfgDialog->hWnd);
        return MAPI_E_BUSY;
    }

    LPMAPITABLE pTableObj= NULL;
    ULONG ulPropCount;
    SizedSPropTagArray(NUM_LOGON_PROPERTIES, sptCfgProps);
    CopyMemory (&sptCfgProps, &sptLogonProps, sizeof(sptLogonProps));
    sptCfgProps.aulPropTag[UPLOAD_TIME] = PR_TMP_UPLOAD_TIME;

    // Take ownership of the property value array, so calling code
    // will not leak memory
    LPSPropValue pProps = *pCfgDialog->ppProps;
    *pCfgDialog->ppProps = NULL;

    // Retrieve the dialog title string
    TCHAR szDialogTitle[64], szTimeString[32];
    LoadString (pCfgDialog->hInst, IDS_SERVICE_PROPERTY_SHEETS_TITLE, szDialogTitle, 64);
    CUIMAPIProp * pPropObj = NULL;
    HRESULT hResult = S_OK;
    try
    {
        pPropObj = new CUIMAPIProp (pCfgDialog->hInst,
                                    gpfnAllocateBuffer,
                                    gpfnAllocateMore,
                                    gpfnFreeBuffer,
                                    pCfgDialog->ulFlags & UI_READONLY);
        if (!pPropObj)
        {
            hResult = E_OUTOFMEMORY;
        }
    }
    catch (CException & Exception)
    {
        hResult = Exception.GetError();
    }
    if (hResult)
    {
        goto ErrorExit;
    }

    ASSERT (PR_SMP_UPLOAD_TIME == pProps[UPLOAD_TIME].ulPropTag);
    GetTimeString (szTimeString, pProps[UPLOAD_TIME].Value.ft);
    pProps[UPLOAD_TIME].ulPropTag = PR_TMP_UPLOAD_TIME;
    pProps[UPLOAD_TIME].Value.LPSZ = szTimeString;

    hResult = pPropObj->SetProps (NUM_LOGON_PROPERTIES, pProps, NULL);
    if (hResult)
    {
        TraceResult ("DoLogonDlg: Failed to set the properties", hResult);
        goto ErrorExit;
    }
    DTPAGE DtPropPages[2];
    DtPropPages[0].cctl = NUM_CFG_USERINFO_PAGE_CTLS;   // Count of controls in the ConfigPage array
    DtPropPages[0].lpszComponent = szBlank;
    DtPropPages[0].lpszResourceName = MAKEINTRESOURCE (IDD_CFG_USERINFO);
    DtPropPages[0].lpctl = UserConfigPage;

    DtPropPages[1].cctl = sizeof(XPConfigPage)/sizeof(DTCTL);   // Count of controls in the ConfigPage array
    DtPropPages[1].lpszComponent = szBlank;
    DtPropPages[1].lpszResourceName = MAKEINTRESOURCE (IDD_CFG_XPCONNECT);
    DtPropPages[1].lpctl = XPConfigPage;

    // If the UI_READONLY flag is set, then we are being called from the
    // SettingDialog method in Read Only mode. Disable all of the controls
    // on the property sheets, else Enable them
    if (pCfgDialog->ulFlags & UI_READONLY)
    {
        // Disable the controls (make them read-only)
        DtPropPages[0].lpctl[1].ulCtlFlags &= ~(DT_EDITABLE | DT_SET_IMMEDIATE);
        DtPropPages[0].lpctl[7].ulCtlFlags &= ~(DT_EDITABLE | DT_SET_IMMEDIATE);
        DtPropPages[1].lpctl[1].ulCtlFlags &= ~DT_EDITABLE;
        DtPropPages[1].lpctl[2].ulCtlFlags &= ~DT_EDITABLE;
    }
    else
    {
        // Enable the controls (make them read/write)
        DtPropPages[0].lpctl[1].ulCtlFlags |= (DT_EDITABLE | DT_SET_IMMEDIATE);
        DtPropPages[0].lpctl[7].ulCtlFlags |= (DT_EDITABLE | DT_SET_IMMEDIATE);
        DtPropPages[1].lpctl[1].ulCtlFlags |= DT_EDITABLE;
        DtPropPages[1].lpctl[2].ulCtlFlags |= DT_EDITABLE;
    }

    // Create the display table for the logon dialog. It is based on dialog
    // resources plus the static information at the head of this module.
    LPTABLEDATA pTableData;
    hResult = BuildDisplayTable (gpfnAllocateBuffer,
                                 gpfnAllocateMore,
                                 gpfnFreeBuffer,
                                 NULL,
                                 pCfgDialog->hInst,
                                 sizeof(DtPropPages)/sizeof(DTPAGE),
                                 DtPropPages,
                                 fMapiUnicode,
                                 &pTableObj,
                                 &pTableData);
    if (!hResult)
    {
        pPropObj->SetTableData (pTableData);
        pTableData->Release();

        // Display the dialog/property sheets
DisplayPropsAgain:
        hResult = pCfgDialog->pSupObj->DoConfigPropsheet ((ULONG)pCfgDialog->hWnd,
                                                          0,
                                                          szDialogTitle,
                                                          pTableObj,
                                                          pPropObj,
                                                          0);     // Default index for the top property sheet
        if (!hResult)
        {
            // Retrieve the altered data
            gpfnFreeBuffer (pProps);
            pProps = NULL;
            hResult = pPropObj->GetProps ((LPSPropTagArray)&sptCfgProps, fMapiUnicode, &ulPropCount, &pProps);
            if (hResult)
            {
                if (MAPI_W_ERRORS_RETURNED == hResult)
                {
                    if (PR_TMP_UPLOAD_TIME != pProps[UPLOAD_TIME].ulPropTag)
                    {
                        pProps[UPLOAD_TIME].ulPropTag = PR_TMP_UPLOAD_TIME;
                        pProps[UPLOAD_TIME].Value.LPSZ = szDefaultTime;
                    }
                    hResult = S_OK;
                }
                else
                {
                    TraceResult ("DoLogonDlg: GetProps failed", hResult);
                    goto ErrorExit;
                }
            }
            // Check the Server name
            if (!IsValidServerName (pProps[SERVER_NAME].Value.LPSZ))
            {
                gpfnFreeBuffer (pProps);
                pProps = NULL;
                PrivateMessageBox (IDS_MSG_INVALID_SERVER_NAME, pCfgDialog->hWnd);
                goto DisplayPropsAgain;
            }

            ASSERT (PR_TMP_UPLOAD_TIME == pProps[UPLOAD_TIME].ulPropTag);
            SYSTEMTIME st;
            GetLocalTime (&st);
            if (ParseTime (pProps[UPLOAD_TIME].Value.LPSZ, &st.wHour, &st.wMinute))
            {
                pProps[UPLOAD_TIME].ulPropTag = PR_SMP_UPLOAD_TIME;
                SystemTimeToFileTime (&st, &pProps[UPLOAD_TIME].Value.ft);
            }
            else
            {
                gpfnFreeBuffer (pProps);
                pProps = NULL;
                PrivateMessageBox (IDS_MSG_BAD_TIME_FORMAT, pCfgDialog->hWnd);
                goto DisplayPropsAgain;
            }

            // Return the new property array
            *pCfgDialog->ppProps = pProps;
            pProps = NULL;      // do not free this in cleanup code below
        }
    }

ErrorExit:
    // Release objects
    if (pTableObj)
    {
        pTableObj->Release();
    }
    if (pPropObj)
    {
        pPropObj->Release();
    }
    gpfnFreeBuffer (pProps);
    PrivUninitialize3DCtl (pCfgDialog->hInst);
    ReleaseMutex (pCfgDialog->hUIMutex);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    ParseTime()
//
//    Parameters
//      [IN]    pszTime     String in the form HH:MM.
//      [OUT]   pwHour      Hour of day, 0-23
//      [OUT]   pwMinute    Minute of hour, 0-59
//
//    Purpose
//      This function parses the string in pszTime and returns the numbers
//      for the hour and minute of day in a 24 hour clock format.
//
//    Return Value
//      TRUE on success, FALSE if we failed or the string passed
//      in is invalid.
//
BOOL WINAPI ParseTime (LPTSTR pszTime, WORD * pwHour, WORD * pwMinute)
{
    TCHAR szTemp[6];
    lstrcpy (szTemp, pszTime);
    LPTSTR pszSubStr = strtok(szTemp, TEXT(":"));
    if (!pszSubStr)
    {
        return FALSE;
    }
    *pwHour = atoi (pszSubStr);
    if (*pwHour < 0 || *pwHour > 23)
    {
        return FALSE;
    }
    pszSubStr = strtok (NULL, TEXT(" "));
    if (!pszSubStr)
    {
        return FALSE;
    }
    *pwMinute = atoi(pszSubStr);
    if (*pwMinute < 0 || *pwMinute > 59)
    {
        return FALSE;
    }
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//    GetTimeString()
//
//    Parameters
//      [OUT]   pszTime     Pointer to a buffer where the function copies the
//                          formatted time string. The buffer must have been
//                          allocated by the caller.
//      [IN]    FileTime    FILETIME structure with the time for the string.
//
//    Purpose
//      This function returns a string formatted as "HH:MM"
//      The formatted string is returned in a buffer supplied by the caller
//      of the function.
//
//    Return Value
//      None.
//
void WINAPI GetTimeString (LPTSTR pszTime, FILETIME FileTime)
{
    // Convert the FILETIME structure to a more readable form
    SYSTEMTIME sysTime;
    FileTimeToSystemTime (&FileTime, &sysTime);
    wsprintf (pszTime, TEXT("%d:%02d"), sysTime.wHour, sysTime.wMinute);
}

///////////////////////////////////////////////////////////////////////////////
//    GetMAPIError()
//
//    Parameters
//      ppMAPIError             Pointer to a location where the newly
//                              allocated MAPIERROR structure is returened.
//      ulFlags                 Flags for the allocation of the structure
//      hError                  Handle to the error for which a string will
//                              be returned
//      hInstance               Handle to instance of DLL where the resource
//                              strings are located
//
//    Purpose
//      This provider implements a shared function for the two different
//      GetLastError methods in the IMAPIFolder and IMAPIStatus interfaces.
//      These interfaces do not maintain (cached) the last error result, so
//      we trust the client that the hError passed in is indeed the last error.
//      This simplifies implementation of GetLastError, but it will give up
//      giving error strings on the exact context of the actual last error
//      in the case a client passes the wrong hResult.
//      ISV's should weight the benefits and trade-off of this implementation.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI GetMAPIError (LPMAPIERROR *          ppMAPIError,
                             ULONG                  ulFlags,
                             HRESULT                hError,
                             HINSTANCE              hInstance)
{
    // The following array maps a string identifier (IDS) to an hResult
    // The order of HRESULTs in the array has an external dependency:
    // the order of elements in the array is dictated by the IDS definitions
    // in RESOURCE.H. This implicit association must be maintained for the
    // strings associated with string identifiers to make sense. Thus, if
    // either this structure or the RESOURCE.H defines changes, the other
    // must change to match it
    static const HRESULT MapOfhResults[] =
    {
        S_OK,

        E_FAIL,
        E_OUTOFMEMORY,
        E_INVALIDARG,
        E_NOINTERFACE,
        E_ACCESSDENIED,

        MAPI_E_BUSY,
        MAPI_E_NO_SUPPORT,
        MAPI_E_NOT_FOUND,
        MAPI_E_UNKNOWN_FLAGS,
        MAPI_E_VERSION,
        MAPI_E_INVALID_ENTRYID,
        MAPI_E_USER_CANCEL,
        MAPI_E_DISK_ERROR,
        MAPI_E_CORRUPT_DATA,
        MAPI_E_BAD_CHARWIDTH,
        MAPI_E_UNCONFIGURED,

        CO_E_NOTINITIALIZED,    // This error could only be comming from the server

        MAPI_W_ERRORS_RETURNED
    };

    // Default the a return value in case we don't have the specified error
    // Allocate the memory for the MAPIERROR structure. The caller is
    // responsible for free this
    HRESULT hResult = gpfnAllocateBuffer (sizeof(MAPIERROR), (LPVOID *)ppMAPIError);
    if (hResult)
    {
        TraceResult ("GetMAPIError: Failed to allocate memory for the MAPIERROR structure", hResult);
        return hResult;
    }

    // Zero-out all the members in the structure
    ZeroMemory (*ppMAPIError, sizeof(MAPIERROR));
    (*ppMAPIError)->ulVersion = MAPI_ERROR_VERSION;
    // See if we have the string for the error. If so, make a copy
    // and pass it back. The memory for the string will be linked to
    // the memory of the structure so that the caller can make a
    // single release on the structure

    // Search for the hResult in the HRESULT map
    UINT i, uMaxNum, uStringID;
    uStringID = 0;
    uMaxNum = sizeof (MapOfhResults) / sizeof (HRESULT);
    for (i=0; i<uMaxNum; i++ )
    {
        if (MapOfhResults[i] == hError)
        {
            break;
        }
    }

    if (i == uMaxNum)
    {
        (*ppMAPIError)->ulLowLevelError = LOWORD(hError);
        if (HRESULT_FACILITY(hError) == FACILITY_RPC)
        {
            uStringID = IDS_RPC_ERROR;
        }
        else
        {
            if (HRESULT_FACILITY(hError) == FACILITY_WIN32 ||
                HRESULT_FACILITY(hError) == FACILITY_NULL)
            {
                uStringID = IDS_SYSTEM_ERROR;
            }
            else
            {
                uStringID = IDS_E_UNKNOWN_ERROR;
            }
        }
    }
    else
    {
        uStringID = i + IDS_SUCCESS;
    }

    TCHAR szBuffer[512];
    int nCharsRead = LoadString (hInstance, uStringID, szBuffer, sizeof(szBuffer) -1);
    if (!nCharsRead)
    {
        TraceString1 ("GetMAPIError: Could not find error string for ID %d", uStringID);
        hResult = MAPI_E_NOT_FOUND;
    }
    else
    {
        // Allocate memory for return variable and set it.
        // The memory for this string is linked to the memory in the pMemBlock parameter
        hResult = gpfnAllocateMore (Cbtszsize (szBuffer),
                                    *ppMAPIError,
                                    (LPVOID *)&(*ppMAPIError)->lpszError);
        if (!hResult)
        {
            lstrcpy ((*ppMAPIError)->lpszError, szBuffer);
            hResult = gpfnAllocateMore (Cbtszsize (TRANSPORT_DISPLAY_NAME_STRING),
                                        *ppMAPIError,
                                        (LPVOID *)&(*ppMAPIError)->lpszComponent);
            if (!hResult)
            {
                lstrcpy ((*ppMAPIError)->lpszComponent, TRANSPORT_DISPLAY_NAME_STRING);
            }
            else
            {
                TraceResult ("GetMAPIError: Failed to allocate the memory for the component string", hResult);
            }
        }
        else
        {
            TraceResult ("GetMAPIError: Failed to allocate the memory for the error string", hResult);
        }
    }

    if (hResult)
    {
        gpfnFreeBuffer (*ppMAPIError);
        *ppMAPIError = NULL;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    XPProviderInit()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Entry point called by the MAPI spooler when a profile uses this
//      transport. The spooler calls this method and expects a pointer to an
//      implementation of the IXPProvider interface. MAPI uses the returned
//      IXPProvider interface pointer to logon on the transport provider
//
//    Return Value
//      An HRESULT
//
STDINITMETHODIMP XPProviderInit (HINSTANCE          hInstance,
                                 LPMALLOC           pMallocObj,
                                 LPALLOCATEBUFFER   pfnAllocateBuffer,
                                 LPALLOCATEMORE     pfnAllocateMore,
                                 LPFREEBUFFER       pfnFreeBuffer,
                                 ULONG              ulFlags,
                                 ULONG              ulMAPIVer,
                                 ULONG *            pulProviderVer,
                                 LPXPPROVIDER *     ppXPProviderObj)
{
    // Look at TRACES.H and TRACES.CPP for more options to the InitTraces() function
    //InitTraces (0);                                       // Send output only to the COM1 port
    InitTraces (TRACES_CONSOLE | TRACES_NO_COM_OUTPUT);     // Send output to a console window BUT NOT to the COM1
    //InitTraces (TRACES_CONSOLE | TRACES_LOG_FILE);        // Send output to COM1 port AND a console window AND to a log file in C:\MAPILOG.TXT
    InfoTrace ("XPProviderInit function called");
    *pulProviderVer = CURRENT_SPI_VERSION;
    if (ulMAPIVer < CURRENT_SPI_VERSION)
    {
        TraceMessage ("XPProviderInit: The version of the subsystem cannot handle this version of the provider");
        return MAPI_E_VERSION;
    }
    // Save the pointer to the allocation routines in global variables
    gpfnAllocateBuffer = pfnAllocateBuffer;
    gpfnAllocateMore   = pfnAllocateMore;
    gpfnFreeBuffer     = pfnFreeBuffer;
    ghInstance         = hInstance;
    // Allocate a new XPProvider object, the constructor will initialize the data member
    CXPProvider * pXPProvider = new CXPProvider (hInstance);
    if (!pXPProvider)
    {
        TraceMessage ("XPProviderInit: Failed to allocate a new CXPProvider object");
        return E_OUTOFMEMORY;
    }
    // Copy pointer to the allocated object back into the return IXPProvider object pointer
    *ppXPProviderObj = (LPXPPROVIDER)pXPProvider;
    return S_OK;
}

// End of file for XPSRVCE.CPP


XPSTATUS.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPSTATUS.CPP
//
//  Description
//      This file implements the methods for the IMAPIStatus interface.
//
//  Authors
//      Irving De la Cruz
//      Les Thaler
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "XPWDSR.H"

// Remark this line to turn verbose tracing OFF
#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a)   TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::CMAPIStatus()
//
//    Parameters
//      pLogon          Parent logon session for this IMAPIStatus object
//      pProfSectObj    Profile section object for this provider
//
//    Purpose
//      Constructor of the object. Parameters are passed to initialize the
//      data members with the appropiate values.
//
//    Return Value
//      None
//
CMAPIStatus::CMAPIStatus (CXPLogon * pLogon, LPPROFSECT pProfSectObj)

{
    InfoTrace ("CMAPIStatus: Constructor called");
    m_cRef          = 1;
    m_pLogon        = pLogon; // Don't AddRef() this object (to avoid circular reference)
    m_pHeaderFolder = NULL;
    m_pProfSectObj  = pProfSectObj;
    if (m_pProfSectObj)
    {
        m_pProfSectObj->AddRef();
    }
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::~CMAPIStatus()
//
//    Parameters
//      None
//
//    Purpose
//      Destructor of CMAPIStatus
//
//    Return Value
//      None
//
CMAPIStatus::~CMAPIStatus ()
{
    InfoTrace ("CMAPIStatus: Destructor called");
    if (m_pProfSectObj)
    {
        m_pProfSectObj->Release();
    }
    if (m_pHeaderFolder && m_pHeaderFolder->m_cRef)
    {
        m_pHeaderFolder->Release();     // Release our instance of the folder
    }
    m_pHeaderFolder = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::QueryInterface()
//
//    Parameters
//      { Refer to OLE Documentation on this method }
//
//    Purpose
//      Returns a pointer to a interface requested if the interface is
//      supported and implemented by this object. If it is not supported, it
//      returns NULL
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    HRESULT hResult = S_OK;
    // OLE requires NULLing parameter
    *ppvObj = NULL;
    // If this is one of the supported IIDs return an interface pointer to it
    if (riid == IID_IMAPIStatus || riid == IID_IMAPIProp || riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)this;
        AddRef();
    }
    else
    {
        // The status object of a remote transport can return a folder object,
        // if the IID requested is for a MAPI folder object. The object returned in
        // this case is the folder that has the headers of messages available in
        // the remote host. A remote client viewer application requests operations
        // on those message by modifying the PR_MESSAGE_STATUS property in each
        // row of the contents table of this folder and later calls the
        // ValidateState method of this IMAPIStatus object
        if (riid == IID_IMAPIFolder)
        {
            // If we already have a folder around, return the same object to the caller.
            if (!m_pHeaderFolder)
            {
                m_pHeaderFolder = new CMAPIFolder (this, m_pLogon);
                if (!m_pHeaderFolder)
                {
                    TraceMessage("CMAPIStatus::QueryInterface: Unable to create remote viewer folder");
                    hResult = E_OUTOFMEMORY;
                }
            }
            if (!hResult)
            {
                m_pHeaderFolder->AddRef();      // One for the caller requesting it
                *ppvObj = (LPVOID)m_pHeaderFolder;
            }
        }
        else
        {
            // This object does not support the interface requested
            hResult = E_NOINTERFACE;
        }
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// IMAPIProp virtual member functions
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::GetLastError()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Called by a client to get a string from the transport describing the
//      last error that occurred.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::GetLastError (HRESULT         hError,
                                        ULONG           ulFlags,
                                        LPMAPIERROR *   ppMAPIError)
{
    InfoTrace ("CMAPIStatus::GetLastError method called");
    Validate_IMAPIProp_GetLastError (this, hError, ulFlags, ppMAPIError);
    // Allocate and fill a MAPIERROR structure with the error passed in.
    // Use the common helper function GetMAPIError
    return GetMAPIError (ppMAPIError, ulFlags, hError, m_pLogon->GetInstance());
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::SaveChanges()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      S_OK always.
//
STDMETHODIMP CMAPIStatus::SaveChanges (ULONG ulFlags)
{
    InfoTrace ("CMAPIStatus::SaveChanges method called");
    Validate_IMAPIProp_SaveChanges (this, ulFlags);
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::GetProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      The caller specifies an array of tags, and this method returns a
//      property value array for thoses tag, if found. The tags are the ones
//      available in the transport ID array
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::GetProps (LPSPropTagArray     pTags,
                                    ULONG               ulFlags,
                                    ULONG *             pcValues,
                                    LPSPropValue *      ppPropArray)
{
    InfoTrace ("CMAPIStatus::GetProps method called");
    Validate_IMAPIProp_GetProps (this, pTags, ulFlags, pcValues, ppPropArray);

    #ifdef DO_INFO_TRACES
    if (!pTags)
    {
        TraceInfoMessage ("CMAPIStatus::GetProps: All properties requested");
    }
    else
    {
        TraceMessage ("CMAPIStatus::GetProps Properties requested..."); for (ULONG j=0; j<pTags->cValues; j++) { TraceProp (pTags->aulPropTag[j]); }
    }
    #endif // DO_INFO_TRACES

    BOOL fGetAllProps = pTags ? FALSE : TRUE;
    if (!pTags)
    {
        pTags = (LPSPropTagArray)&sptStatusObj;
    }

    *pcValues = pTags->cValues;
    HRESULT hResult = gpfnAllocateBuffer (sizeof(SPropValue)*(*pcValues), (LPVOID *)ppPropArray);
    if (hResult)
    {
        TraceResult ("CMAPIStatus::GetProps: Memory allocation failed", hResult);
        return hResult;
    }
    if (fGetAllProps)
    {
        for (ULONG i=0; i<sptStatusObj.cValues; i++)
        {
            (*ppPropArray)[i].ulPropTag = sptStatusObj.aulPropTag[i];
        }
    }
    else
    {
        // A client of the IMAPIProp::GetProps() method can pass PT_UNSPECIFIED in the
        // tag of a property. Is the provider's responsiblity to return
        // the proper type of the property.
        for (ULONG k=0; k<pTags->cValues; k++)
        {
            for (ULONG i=0; i<sptStatusObj.cValues; i++)
            {
                if (PROP_ID(pTags->aulPropTag[k]) == PROP_ID(sptStatusObj.aulPropTag[i]))
                {
                    (*ppPropArray)[k].ulPropTag = sptStatusObj.aulPropTag[i];
                }
                else
                {
                    (*ppPropArray)[k].ulPropTag = pTags->aulPropTag[k];
                }
            }
        }
    }

    TCHAR szBuffer[128];
    BOOL fGotErrors = FALSE;
    LPVOID pSrc, pDst;
    ULONG cbSize;
    for (ULONG i=0; i<*pcValues; i++)
    {
        hResult = S_OK;
        switch ((*ppPropArray)[i].ulPropTag)
        {
            case PR_NULL :
                (*ppPropArray)[i].Value.err = S_OK;
                break;
            case PR_RESOURCE_METHODS :
                (*ppPropArray)[i].Value.l = STATUS_SETTINGS_DIALOG |
                                            STATUS_FLUSH_QUEUES |
                                            STATUS_VALIDATE_STATE |
                                            STATUS_CHANGE_PASSWORD;
                break;
            case PR_RESOURCE_TYPE :
                (*ppPropArray)[i].Value.l = MAPI_TRANSPORT_PROVIDER;
                break;
            case PR_RESOURCE_FLAGS :
                hResult = E_FAIL;
                if (m_pProfSectObj)
                {
                    LPSPropValue pResFlags;
                    SPropTagArray sptResFlags = { 1, { PR_RESOURCE_FLAGS } };
                    ULONG cValues;
                    hResult = m_pProfSectObj->GetProps (&sptResFlags, fMapiUnicode, &cValues, &pResFlags);
                    if (SUCCEEDED(hResult))
                    {
                        if (PR_RESOURCE_FLAGS == pResFlags->ulPropTag)
                        {
                            (*ppPropArray)[i].Value.l = pResFlags->Value.l;
                        }
                        gpfnFreeBuffer (pResFlags);
                    }
                }
                if (hResult)
                {
                    (*ppPropArray)[i].Value.l = STATUS_PRIMARY_IDENTITY;
                    hResult = S_OK;
                }
                break;
            case PR_DISPLAY_NAME :
                wsprintf (szBuffer,
                          TEXT("%s (%s)"),
                          TRANSPORT_DISPLAY_NAME_STRING,
                          m_pLogon->GetServerName());
                pSrc = (LPVOID)szBuffer;
                break;
            case PR_PROVIDER_DLL_NAME :
                pSrc = (LPVOID)TRANSPORT_DLL_NAME_STRING;
                break;
            case PR_PROVIDER_DISPLAY :
                pSrc = (LPVOID)TRANSPORT_DISPLAY_NAME_STRING;
                break;
            case PR_IDENTITY_DISPLAY :
                pSrc = (LPVOID)m_pLogon->m_pIdentityProps[XPID_NAME].Value.LPSZ;
                break;
            case PR_IDENTITY_ENTRYID :
                cbSize = m_pLogon->m_pIdentityProps[XPID_EID].Value.bin.cb;
                pSrc = (LPVOID)m_pLogon->m_pIdentityProps[XPID_EID].Value.bin.lpb;
                break;
            case PR_IDENTITY_SEARCH_KEY :
                cbSize = m_pLogon->m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.cb;
                pSrc = (LPVOID)m_pLogon->m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.lpb;
                break;
            case PR_STATUS_CODE :
                (*ppPropArray)[i].Value.l = (long)m_pLogon->GetTransportStatusCode();
                break;
            case PR_STATUS_STRING :
                szBuffer[0] = 0;
                m_pLogon->LoadStatusString (szBuffer, sizeof(szBuffer) - 1);
                pSrc = (LPVOID)szBuffer;
                break;
            case PR_CURRENT_VERSION :
                (*ppPropArray)[i].Value.li.LowPart = TRANSPORT_VERSION_MAJOR;
                (*ppPropArray)[i].Value.li.HighPart = TRANSPORT_VERSION_MINOR;
                break;
            case PR_OBJECT_TYPE :
                (*ppPropArray)[i].Value.l = MAPI_STATUS;
                break;
            default:
                hResult = MAPI_E_NOT_FOUND;
                #ifdef DO_INFO_TRACES
                TraceRaw ("CMAPIStatus::GetProps: Not Found - "); TraceProp ((*ppPropArray)[i].ulPropTag);
                #endif // #DO_INFO_TRACES
                break;
        }
        if (!hResult &&
            (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_BINARY ||
             PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_TSTRING))
        {
            if (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_TSTRING)
            {
                cbSize = Cbtszsize ((LPTSTR)pSrc);
            }
            hResult = gpfnAllocateMore (cbSize, *ppPropArray, &pDst);
            if (!hResult)
            {
                CopyMemory (pDst, pSrc, cbSize);
                if (PROP_TYPE((*ppPropArray)[i].ulPropTag) == PT_BINARY)
                {
                    (*ppPropArray)[i].Value.bin.cb  = cbSize;
                    (*ppPropArray)[i].Value.bin.lpb = (LPBYTE)pDst;
                }
                else
                {
                    (*ppPropArray)[i].Value.LPSZ = (LPTSTR)pDst;
                }
            }
        }
        if (hResult)
        {
            (*ppPropArray)[i].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID((*ppPropArray)[i].ulPropTag));
            (*ppPropArray)[i].Value.err = hResult;
            fGotErrors = TRUE;
        }
    }
    if (fGotErrors)
    {
        hResult = MAPI_W_ERRORS_RETURNED;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::GetPropList()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Returns a tag array with the properties in the status row of this
//      transport. The caller is responsible for freeing the memory allocated
//      for the tag array.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::GetPropList (ULONG                ulFlags,
                                       LPSPropTagArray *    ppPropTagArray)
{
    InfoTrace ("CMAPIStatus::GetPropList method called");
    Validate_IMAPIProp_GetPropList (this, ulFlags, ppPropTagArray);

    ULONG cbTagsArraySize = CbNewSPropTagArray(NUM_STATUS_OBJECT_PROPS);
    // Allocate the required amount of memory
    HRESULT hResult = gpfnAllocateBuffer (cbTagsArraySize, (LPVOID *)ppPropTagArray);
    TraceResult ("CMAPIStatus::GetPropList: Memory allocation failed", hResult);
    if (!hResult)
    {
        // Copy the contents of our property tag array into the buffer
        CopyMemory (*ppPropTagArray, &sptStatusObj, cbTagsArraySize);
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::OpenProperty()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::OpenProperty (ULONG           ulPropTag,
                                        LPCIID          piid,
                                        ULONG           ulInterfaceOptions,
                                        ULONG           ulFlags,
                                        LPUNKNOWN *     ppUnk)
{
    InfoTrace ("CMAPIStatus::OpenProperty method called");
    Validate_IMAPIProp_OpenProperty (this, ulPropTag,  piid, ulInterfaceOptions, ulFlags, ppUnk);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::SetProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::SetProps (ULONG                   cValues,
                                    LPSPropValue            pPropArray,
                                    LPSPropProblemArray *   ppProblems)
{
    InfoTrace ("CMAPIStatus::SetProps method called");
    Validate_IMAPIProp_SetProps (this, cValues, pPropArray, ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::DeleteProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::DeleteProps (LPSPropTagArray          pPropTagArray,
                                       LPSPropProblemArray *    ppProblems)
{
    InfoTrace ("CMAPIStatus::DeleteProps method called");
    Validate_IMAPIProp_DeleteProps (this, pPropTagArray, ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::CopyTo()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::CopyTo (ULONG                   ciidExclude,
                                  LPCIID                  rgiidExclude,
                                  LPSPropTagArray         pExcludeProps,
                                  ULONG                   ulUIParam,
                                  LPMAPIPROGRESS          pProgress,
                                  LPCIID                  pInterface,
                                  LPVOID                  pDestObj,
                                  ULONG                   ulFlags,
                                  LPSPropProblemArray *   ppProblems)
{
    InfoTrace ("CMAPIStatus::CopyTo method called");
    Validate_IMAPIProp_CopyTo (this,
                               ciidExclude,
                               rgiidExclude,
                               pExcludeProps,
                               ulUIParam,
                               pProgress,
                               pInterface,
                               pDestObj,
                               ulFlags,
                               ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::CopyProps()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::CopyProps (LPSPropTagArray            pIncludeProps,
                                     ULONG                      ulUIParam,
                                     LPMAPIPROGRESS             pProgress,
                                     LPCIID                     pInterface,
                                     LPVOID                     pDestObj,
                                     ULONG                      ulFlags,
                                     LPSPropProblemArray *      ppProblems)
{
    InfoTrace ("CMAPIStatus::CopyProps method called");
    Validate_IMAPIProp_CopyProps (this,
                                  pIncludeProps,
                                  ulUIParam,
                                  pProgress,
                                  pInterface,
                                  pDestObj,
                                  ulFlags,
                                  ppProblems);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::GetNamesFromIDs()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::GetNamesFromIDs (LPSPropTagArray *    ppPropTags,
                                           LPGUID               pPropSetGuid,
                                           ULONG                ulFlags,
                                           ULONG *              pcPropNames,
                                           LPMAPINAMEID **      pppPropNames)
{
    InfoTrace ("CMAPIStatus::GetNamesFromIDs method called");
    Validate_IMAPIProp_GetNamesFromIDs (this,
                                        ppPropTags,
                                        pPropSetGuid,
                                        ulFlags,
                                        pcPropNames,
                                        pppPropNames);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::GetIDsFromNames()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Place holder. Replace this function with actual implementation
//      if needed.
//
//    Return Value
//      MAPI_E_NO_SUPPORT always.
//
STDMETHODIMP CMAPIStatus::GetIDsFromNames (ULONG                cPropNames,
                                           LPMAPINAMEID *       ppPropNames,
                                           ULONG                ulFlags,
                                           LPSPropTagArray *    ppPropTags)
{
    InfoTrace ("CMAPIStatus::GetIDsFromNames method called");
    Validate_IMAPIProp_GetIDsFromNames (this,
                                        cPropNames,
                                        ppPropNames,
                                        ulFlags,
                                        ppPropTags);
    return MAPI_E_NO_SUPPORT;
}

///////////////////////////////////////////////////////////////////////////////
// IMAPIStatus virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::ValidateState()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      This method is called by the REMOTE viewer UI to start the remote
//      processing. We check the ulFlags bits to see what operation is being
//      requested and do it. All the operation of this transport are
//      ASYNCHRONOUS meaning, that the call returns to the client before the
//      action is truly completed.
//      To control this, we dynamically change PR_REMOTE_VALIDATE_OK on the
//      status row so that the client calls us at intervals to check if we
//      finished or not.
//      When an action is requested, we change the internal state of the
//      transport and we tell the spooler to FLUSH us, so that it can better
//      manage interaction with multiple transport.
//      This is the recommended way for remote transports to connect using
//      shared resources such as COM port. In this transport, we do it this
//      way for purposes of demonstration of how it should be done.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::ValidateState (ULONG ulUIParam, ULONG ulFlags)
{
    InfoTrace ("CMAPIStatus::ValidateState method called");
    Validate_IMAPIStatus_ValidateState (this, ulUIParam, ulFlags);

    // We only support this flags
    if (ulFlags & ~(SHOW_XP_SESSION_UI |
                    REFRESH_XP_HEADER_CACHE |
                    PROCESS_XP_HEADER_CACHE |
                    FORCE_XP_CONNECT |
                    FORCE_XP_DISCONNECT |
                    ABORT_XP_HEADER_OPERATION))
    {
        return MAPI_E_UNKNOWN_FLAGS;
    }

    if (ulFlags & SHOW_XP_SESSION_UI)
    {
        // We don't implement UI for processing the headers
        return MAPI_E_NO_SUPPORT;
    }
    if (ulFlags & ABORT_XP_HEADER_OPERATION)
    {
        // If we are called to cancel an opertaion, if we can't cancel we will set a
        // flag so that when the current operation finishes, we won't continue.
        if (HEADERS_AND_DOWNLOAD == m_pLogon->GetTransportState())
        {
            m_pLogon->m_fCancelPending = TRUE;
            return MAPI_E_BUSY;
        }
        // The cancel occured while we where flushing the inbound queue. We can
        // cancel this by telling the spooler to stop flushing us. Note that at
        // this point ALL the message have been downloaded to temporary files
        // and are simply pending to be placed in the default inbox
        if (DOWNLOADING_MESSAGES & m_pLogon->GetTransportStatusCode())
        {
            m_pLogon->RemoveStatusBits (DOWNLOADING_MESSAGES); // Tell the spooler to take us off from inbound flush
            m_pLogon->AddStatusBits (STATUS_OFFLINE); // The transport is now OFF line
            m_pLogon->UpdateStatus (TRUE, TRUE); // Set PR_REMOTE_VALIDATE_OK to TRUE
            m_pLogon->SetTransportState (WAITING); // The transport is now IDLE
        }
        return S_OK;
    }
    HRESULT hResult = S_OK;
    // The request is to process remote messages and upload any deferred messages
    if (ulFlags & PROCESS_XP_HEADER_CACHE)
    {
        if (PENDING_RETURN_CODE == m_pLogon->GetTransportState())
        {
            // The transport had finished a request and is ready to return the results error (if any)
            m_pLogon->SetTransportState (WAITING);
            m_pLogon->UpdateStatus (TRUE, FALSE);
            m_pLogon->UpdateProgress (-1, REMOTE_ACTION_IDLE); // Reset PR_REMOTE_PROGRESS_TEXT
            return m_pLogon->m_hRemoteActionErr;
        }
        if (HEADERS_AND_DOWNLOAD == m_pLogon->GetTransportState())
        {
            // We are busy, poll me later
            return MAPI_E_BUSY;
        }

        // The MS Exchange viewer wants a remote transport to update the headers
        // during this call with this flag, so we must update the headers.
        // The MS Exchange remote viewer does not call this method with
        // REFRESH_XP_HEADER_CACHE alone, any longer.
        ulFlags |= REFRESH_XP_HEADER_CACHE;
        m_pLogon->m_fCancelPending = FALSE;
    }
    // The request is to get remote headers
    if (!hResult && (ulFlags & REFRESH_XP_HEADER_CACHE))
    {
        if (PENDING_RETURN_CODE == m_pLogon->GetTransportState())
        {
            m_pLogon->SetTransportState (WAITING);
            m_pLogon->UpdateStatus (TRUE, FALSE);
            m_pLogon->UpdateProgress (-1, REMOTE_ACTION_IDLE); // Reset PR_REMOTE_PROGRESS_TEXT
            return m_pLogon->m_hRemoteActionErr;
        }
        if (HEADERS_AND_DOWNLOAD == m_pLogon->GetTransportState())
        {
            return MAPI_E_BUSY;
        }
        m_pLogon->m_fCancelPending = FALSE;

        // Fire up a request to the spooler to flush the INBOUD logic
        // which in turn will also get new headers.
        m_pLogon->SetTransportState (HEADERS_AND_DOWNLOAD);
        m_pLogon->AddStatusBits (DOWNLOADING_MESSAGES);
        m_pLogon->RemoveStatusBits (STATUS_OFFLINE);
        m_pLogon->UpdateStatus (TRUE, FALSE);
        return MAPI_E_BUSY;
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::SettingsDialog()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Called by a client to display the configuration settings, if any, for
//      a particular transport. This method could be used to display the
//      current setting in a READ-ONLY mode
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::SettingsDialog (ULONG ulUIParam, ULONG ulFlags)
{
    InfoTrace ("CMAPIStatus::SettingsDialog method called");
    Validate_IMAPIStatus_SettingsDialog (this, ulUIParam, ulFlags);

    ULONG ulPropCount;
    LPSPropValue pProps = NULL;
    LPPROFSECT pProfileObj = NULL;
    HRESULT hResult = S_OK;
    BOOL fChangeAccount = FALSE;
    TCHAR szOldMailboxName[MAX_ALIAS_SIZE+1], szOldServer[MAX_STRING_SIZE+1];
    CFGDLG CfgDialog = { 0 };

    PrivInitialize3DCtl (m_pLogon->GetInstance());
    DWORD dwWait = WaitForSingleObject (m_pLogon->m_hUIMutex, 1000); // Wait for the MUTEX for 1 second
    if (WAIT_TIMEOUT == dwWait)
    {
        PrivateMessageBox (IDS_MSG_CONFIG_UI_IN_USE, (HWND)ulUIParam);
        PrivUninitialize3DCtl (m_pLogon->GetInstance());
        return MAPI_E_BUSY;
    }

    // Open the profile for our provider and get what ever is set there
    hResult = OpenServiceProfileSection (m_pLogon->m_pSupObj, &pProfileObj, gpfnFreeBuffer);
    if (hResult)
    {
        TraceResult ("CMAPIStatus::SettingsDialog: Failed to open the profile section", hResult);
        goto ErrorExit;
    }

    hResult = pProfileObj->GetProps ((LPSPropTagArray)&sptLogonProps,

fMapiUnicode,
                                     &ulPropCount,
                                     &pProps);
    if (FAILED(hResult))
    {
        TraceResult ("CMAPIStatus::SettingsDialog: Failed to get profile section properties", hResult);
        goto ErrorExit;
    }

    lstrcpy (szOldMailboxName, pProps[MAILBOX_NAME].Value.LPSZ);
    lstrcpy (szOldServer, pProps[SERVER_NAME].Value.LPSZ);

    // Fill in the logon UI structure
    CfgDialog.hInst   = m_pLogon->GetInstance();
    CfgDialog.hWnd    = (HWND)ulUIParam;
    CfgDialog.ppProps = &pProps;
    CfgDialog.pSupObj = m_pLogon->m_pSupObj;
    CfgDialog.ulFlags = ulFlags;

ShowPropsAgain:
    hResult = DoLogonDlg (&CfgDialog);
    // If the dialog was displayed READ-only, we don't need to verify
    // anything because the user didn't change anything and we are
    // still runnig with the logon settings of the session.
    if (hResult || (ulFlags & UI_READONLY))
    {
        goto ErrorExit;
    }
    if (!PingRemoteServer ((HWND)ulUIParam, pProps))
    {
        goto ShowPropsAgain;
    }

    // We don't need to set the header file name back into the profile.
    hResult = pProfileObj->SetProps (NUM_LOGON_PROPERTIES, pProps, NULL);
    if (hResult)
    {
        TraceResult ("CMAPIStatus::SetttingsDialog: Failed to save the properties back into the profile", hResult);
        goto ErrorExit;
    }
    if (lstrcmpi (szOldMailboxName, pProps[SERVER_NAME].Value.LPSZ))
    {
        m_pLogon->SetServerName (pProps[SERVER_NAME].Value.LPSZ);
        fChangeAccount = TRUE;
    }
    // If the configuration changed, we need to change the download directory
    // which is based on the mailbox name.
    if (PR_SMP_MAILBOX_NAME == pProps[MAILBOX_NAME].ulPropTag &&
        lstrcmpi (szOldMailboxName, pProps[MAILBOX_NAME].Value.LPSZ))
    {
        TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
        GetTempPath (_MAX_PATH, szTmpDir);
        lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
        // Remove the download directory for the old mailbox
        wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, szOldMailboxName);
        RemoveDirectory (szDownloadDir);
        // Create the download directory for the new mailbox
        wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, pProps[MAILBOX_NAME].Value.LPSZ);
        CreateDirectory (szDownloadDir, NULL);
        fChangeAccount = TRUE;
    }
    if (fChangeAccount)
    {
        m_pLogon->SetAccountProps (pProps[MAILBOX_NAME].Value.LPSZ,
                                   (DWORD)pProps[MAILBOX_ID].Value.l,
                                   pProps[USER_NAME].Value.LPSZ,
                                   pProps[PASSWORD].Value.LPSZ);
    }

    // Reflect the new changes in the status row.
    m_pLogon->InitializeStatusRow (STATUSROW_UPDATE);

    // If the user changed the upload time, reset the upload timer.
    if (m_pLogon->SetUploadTime (pProps[UPLOAD_TIME].Value.ft))
    {
        m_pLogon->StopUploadTimer();
        m_pLogon->InitializeTimer();
        m_pLogon->StartUploadTimer();
    }

ErrorExit:
    if (pProfileObj)
    {
        pProfileObj->Release();
    }
    gpfnFreeBuffer (pProps);
    PrivUninitialize3DCtl (m_pLogon->GetInstance());
    ReleaseMutex (m_pLogon->m_hUIMutex);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::ChangePassword()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Changes the password for the mailbox the service is logged in.
//
//    Return Value
//      An HRESULT
//
STDMETHODIMP CMAPIStatus::ChangePassword (LPTSTR    pszOldPass,
                                          LPTSTR    pszNewPass,
                                          ULONG     ulFlags)
{
    InfoTrace ("CMAPIStatus::ChangePassword method called");
    Validate_IMAPIStatus_ChangePassword (this, pszOldPass, pszNewPass, ulFlags);
    HRESULT hResult = ChangeMBServerPassword (m_pLogon->GetServerName(),
                                              m_pLogon->GetAccountName(),
                                              pszOldPass,
                                              pszNewPass);
    if (hResult)
    {
        TraceResult ("CMAPIStatus::ChangePassword", hResult);
        if (HRESULT_FROM_WIN32 (ERROR_INVALID_PASSWORD) == hResult)
        {
            hResult = E_ACCESSDENIED;
        }
    }
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CMAPIStatus::FlushQueues()
//
//    Parameters
//      { Refer to MAPI Documentation on this method }
//
//    Purpose
//      Stub function that calls the IXPLogon object for this status object
//      to actually flush any deferred messages.
//      This only flushes messages deferred for delivery. It does not
//      downloads any messages from the server.
//
//    Return Value
//      S_OK always
//
STDMETHODIMP CMAPIStatus::FlushQueues (ULONG        ulUIParam,
                                       ULONG        cbTargetTransport,
                                       LPENTRYID    pTargetTransport,
                                       ULONG        ulFlags)
{
    InfoTrace ("CMAPIStatus::FlushQueues method called");
    Validate_IMAPIStatus_FlushQueues (this,
                                      ulUIParam,
                                      cbTargetTransport,
                                      pTargetTransport,
                                      ulFlags);
    DWORD dwFlushBits = 0;
    if (ulFlags & FLUSH_UPLOAD)
    {
        dwFlushBits |= UPLOADING_MESSAGES;
    }
    if (ulFlags & FLUSH_DOWNLOAD)
    {
        dwFlushBits |= DOWNLOADING_MESSAGES;
    }
    if (dwFlushBits)
    {
        m_pLogon->AddStatusBits (dwFlushBits);
        m_pLogon->UpdateStatus();
    }
    return S_OK;
}

// End of File for XPSTATUS.CPP


XPSTREAM.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPSTREAM.CPP
//
//  Description
//      Wraps the StreamOnFile object to add buffering of the stream.
//      The wrappered version uses an in-memory buffer, or cache, to
//      reduce the number of actual Reads and Writes to the underlying
//      stream.  The goal is, obviously, to improve performance.
//      This code is not thread safe.  Also, the implementation
//      is not optimized for streams that both read and write continuously
//      because there will be too much flushing going on.
//
//  Author
//      Irving De la Cruz
//
//  Note: This file is the C++ version of the buffered IStream wrapper
//        developed originally for the C-based MSPEER transport in
//        the MAPI SDK samples.
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#include "XPSTREAM.H"
#include "TRACES.H"
#include "COMWINDS.H"

CCachedStream::CCachedStream (LPSTREAM pImpStream, ULONG ulFlags)
{
    m_cRef = 1;
    m_fDirty = FALSE;
    m_ulFlags = ulFlags;
    m_libBuff = 0;
    m_cbBuffMac = 0;
    m_pvCache = HeapAlloc (GetProcessHeap(), 0, XPSOF_BUFF_MAX);
    if (NULL == m_pvCache)
    {
        throw CException (E_OUTOFMEMORY);
    }
    m_pImpStream = pImpStream;
    m_pImpStream->AddRef();
}

CCachedStream::~CCachedStream()
{
    m_pImpStream->Release();
    if (m_pvCache)
    {
        HeapFree (GetProcessHeap(), 0, m_pvCache);
    }
}

STDMETHODIMP CCachedStream::Read (LPVOID lpvData, ULONG cbSize, ULONG * lpcbRead)
{
    HRESULT hResult = S_OK;
    ULONG cbRead = 0;
    ULONG cbT;
    LPVOID lpvRead = NULL;

    if (IsBadWritePtr (lpvData, cbSize) || (lpcbRead && IsBadWritePtr (lpcbRead, sizeof(ULONG))))
    {
        hResult = STG_E_INVALIDPARAMETER;
        goto ret;
    }
    if (!(XPSOF_READ & m_ulFlags))
    {
        hResult = STG_E_ACCESSDENIED;
        goto ret;
    }
    ASSERT (m_cbBuffMac >= m_libBuff);

    // First, flush the buffer if it has been written into.  This
    // operation empties our buffer and zeros the offset and size.
    // We do this because we also buffer writes and we need to force
    // the underlying stream to point to where the caller expects.
    if ((XPSOF_WRITE & m_ulFlags) && m_fDirty)
    {
        hResult = Commit (0);
        if (hResult)
        {
            goto ret;
        }
    }

    // Determine if the buffer is empty (cbT == 0) or not (cbT != 0).
    // We consider the buffer empty if we've read past the end of it
    // or if m_cbBuffMac and m_libBuff are equal to zero.
    cbT = m_cbBuffMac - m_libBuff;

    // If the buffer is empty and the caller wants to read less than
    // the size of our buffer, then we'll fill the buffer from the
    // underlying stream object.  Adjust our buffer offset and size.
    if (!cbT && (cbSize < XPSOF_BUFF_MAX))
    {
        hResult = m_pImpStream->Read (m_pvCache, XPSOF_BUFF_MAX, &cbRead);
        if (hResult)
        {
            goto ret;
        }
        m_libBuff = 0;
        m_cbBuffMac = cbT = cbRead;
    }

    // Now, if the buffer is *not* empty and the caller wants to read
    // fewer bytes than what is in the buffer, then we read it from
    // our buffer, fix-up our offset, set the count read and leave.
    if (cbT && (cbSize <= cbT))
    {
        lpvRead = (LPVOID)((LPBYTE)m_pvCache + m_libBuff);
        CopyMemory (lpvData, lpvRead, cbSize);
        m_libBuff += cbSize;
        cbRead = cbSize;
        goto ret;
    }

    // If we are here, then the caller has requested more bytes to be
    // read than what can fit in our buffer.  In this case, we copy
    // the remaining data from the buffer (if any) into lpvData and
    // then go straight to the underlying stream for the remainder.
    // Either way, our buffer is empty after this operation.
    lpvRead = lpvData;
    if (cbT)
    {
        CopyMemory (lpvRead, (LPVOID)((LPBYTE)m_pvCache + m_libBuff), cbT);
        lpvRead = (LPBYTE)lpvRead + cbT;
        m_libBuff = 0;
        m_cbBuffMac = 0;
    }

    hResult = m_pImpStream->Read (lpvRead, cbSize - cbT, &cbRead);
    if (hResult)
    {
        goto ret;
    }
    cbRead += cbT;

ret:
    if (lpcbRead)
    {
        *lpcbRead = cbRead;
    }
    TraceResult ("CCachedStream::Read", hResult);
    return hResult;
}

STDMETHODIMP CCachedStream::Write (const void * lpvData, ULONG cbSize, ULONG * lpcbWritten)
{
    HRESULT hResult = S_OK;
    ULONG cbWritten = 0;
    ULONG cbT;
    LPVOID lpvWrite = NULL;

    if (IsBadReadPtr (lpvData, cbSize) || (lpcbWritten && IsBadWritePtr (lpcbWritten, sizeof(ULONG))))
    {
        hResult = STG_E_INVALIDPARAMETER;
        goto ret;
    }
    if (!(XPSOF_WRITE & m_ulFlags))
    {
        hResult = STG_E_ACCESSDENIED;
        goto ret;
    }
    ASSERT (m_cbBuffMac >= m_libBuff);

    // First, if we've been Reading, then we need to re-wind the file
    // pointer in the underlying stream to compensate for the last
    // buffered Read.  Our new vacancy = the Max Size of our buffer.
    if (!m_fDirty)
    {
        if (m_libBuff != m_cbBuffMac)
        {
            hResult = RewindStream (m_cbBuffMac - m_libBuff);
            if (hResult)
            {
                goto ret;
            }
        }
        m_libBuff = 0;
        m_cbBuffMac = XPSOF_BUFF_MAX;
    }

    // Determine the total vacancy of the buffer.
    cbT = m_cbBuffMac - m_libBuff;

    // If the caller wants to Write more bytes than the current
    // vacancy of the buffer, then commit the current buffer and
    // Write the callers data directly to the stream.  If the
    // buffer is not dirty, then the Commit call is a no-op.
    if (cbSize > cbT)
    {
        hResult = Commit (0);
        if (hResult)
        {
            goto ret;
        }
        hResult = m_pImpStream->Write (lpvData, cbSize, &cbWritten);
        goto ret;
    }

    // The callers data will fit in our current buffer.  Copy the
    // data into the buffer, mark the buffer as dirty, and adjust
    // the buffer offset.  Set cbWritten to cbSize and return.
    lpvWrite = (LPVOID)((LPBYTE)m_pvCache + m_libBuff);
    CopyMemory (lpvWrite, lpvData, cbSize);
    m_fDirty = TRUE;
    m_libBuff += cbSize;
    cbWritten = cbSize;

ret:
    if (lpcbWritten)
    {
        *lpcbWritten = cbWritten;
    }
    TraceResult ("CCachedStream::Write", hResult);
    return hResult;
}

STDMETHODIMP CCachedStream::Seek (LARGE_INTEGER liMove, DWORD dwMode, ULARGE_INTEGER * lpliPos)
{
    HRESULT hResult = S_OK;
    if (lpliPos && IsBadWritePtr (lpliPos, sizeof(ULARGE_INTEGER)))
    {
        hResult = STG_E_INVALIDPARAMETER;
        goto ret;
    }
    ASSERT (m_cbBuffMac >= m_libBuff);

    // If our buffer is dirty, then we've been writing into it and
    // we need to flush it.  Else, if it isn't dirty and our offset
    // and buffer size are not equal, then we've been reading and we
    // need to rewind the underlying stream to match our position.
    if (m_fDirty)
    {
        hResult = Commit (0);
        if (hResult)
        {
            goto ret;
        }
    }
    else
    {
        if ((dwMode == STREAM_SEEK_CUR) && (m_libBuff != m_cbBuffMac))
        {
            hResult = RewindStream (m_cbBuffMac - m_libBuff);
            if (hResult)
            {
                goto ret;
            }
        }
        m_libBuff = 0;
        m_cbBuffMac = 0;
    }
    // Now, call the real stream's Seek method.
    hResult = m_pImpStream->Seek (liMove, dwMode, lpliPos);
ret:
    TraceResult ("CCachedStream::Seek", hResult);
    return hResult;
}

STDMETHODIMP CCachedStream::CopyTo (LPSTREAM lpStrmDst,
                                    ULARGE_INTEGER cbCopy,
                                    ULARGE_INTEGER * lpcbRead,
                                    ULARGE_INTEGER * lpcbWritten)
{
    HRESULT hResult;
    if (IsBadReadPtr (lpStrmDst, sizeof(LPVOID)) ||
        IsBadWritePtr (lpcbRead, sizeof(ULARGE_INTEGER)) ||
        IsBadWritePtr (lpcbWritten, sizeof(ULARGE_INTEGER)))
    {
        hResult = STG_E_INVALIDPARAMETER;
        goto ret;
    }
    ASSERT (m_cbBuffMac >= m_libBuff);

    // If our buffer is dirty, then we've been writing into it and
    // we need to flush it.  Else, if it isn't dirty and our offset
    // and buffer size are not equal, then we've been reading and we
    // need to rewind the underlying stream to match our position.
    if (m_fDirty)
    {
        hResult = Commit (0);
        if (hResult)
        {
            goto ret;
        }
    }
    else
    {
        if (m_libBuff != m_cbBuffMac)
        {
            hResult = RewindStream (m_cbBuffMac - m_libBuff);
            if (hResult)
            {
                goto ret;
            }
        }
        m_libBuff = 0;
        m_cbBuffMac = 0;
    }

    // Now, call the real streams CopyTo method.
    hResult = m_pImpStream->CopyTo (lpStrmDst, cbCopy, lpcbRead, lpcbWritten);
ret:
    TraceResult ("CCachedStream::CopyTo", hResult);
    return hResult;
}

STDMETHODIMP CCachedStream::Commit (ULONG ulFlags)
{
    HRESULT hResult = S_OK;
    // Flush my internal buffer if it is dirty.
    if ((XPSOF_WRITE & m_ulFlags) && m_fDirty)
    {
        hResult = m_pImpStream->Write (m_pvCache, m_libBuff, NULL);
        if (hResult)
        {
            goto ret;
        }
    }
    // Mark my buffer as empty cal clean.
    m_fDirty = FALSE;
    m_libBuff = 0;
    m_cbBuffMac = 0;
ret:
    TraceResult ("CCachedStream::Commit", hResult);
    return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//    CCachedStream::RewindStream()
//
//    Parameters
//      ib      Number of bytes to rewind
//
//    Purpose
//      This gets called to back-up the file pointer when a Write operation
//      follows a Read operation.  This is necessary because the file pointer
//      is actually further ahead in the file than the buffered file pointer.
//
//    Return Value
//      An HRESULT
//
HRESULT WINAPI CCachedStream::RewindStream (ULONG ib)
{
    if (ib)
    {
        LARGE_INTEGER liRewind;
        liRewind.HighPart = 0xFFFFFFFF;
        liRewind.LowPart = -((LONG)ib);
        return m_pImpStream->Seek (liRewind, STREAM_SEEK_CUR, NULL);
    }
    return S_OK;
}

// End of File for XPSTREAM.CPP


XPSTREAM.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPSTREAM.H
//
//  Description
//      Interface declaraction file for CCachedStream.
//
//  Author
//      Irving De la Cruz
//
//  Note: This file is the C++ version of the buffered IStream wrapper
//        developed originally for the C-based MSPEER transport in
//        the MAPI SDK samples.
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _XPSTREAM_H
#define _XPSTREAM_H

#define STRICT
#include <WINDOWS.H>
#include <MAPIWIN.H>
#include <MAPISPI.H>
#include <MAPIUTIL.H>

#define XPSOF_BUFF_MAX      4096
#define XPSOF_READ          0x00000001
#define XPSOF_WRITE         0x00000002
#define XPSOF_READWRITE     (XPSOF_WRITE | XPSOF_READ)

class CCachedStream : public IStream
{
public:
///////////////////////////////////////////////////////////////////////////////
// IUnknown virtual member functions
//
    inline STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj)
                    { *ppvObj = NULL;
                      if (riid == IID_IStream || riid == IID_IUnknown)
                      {
                          *ppvObj = (LPVOID)this;
                          // Increase usage count of this object
                          AddRef();
                          return S_OK;
                      }
                      return E_NOINTERFACE;
                    };
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};
///////////////////////////////////////////////////////////////////////////////
// IStream virtual member functions
//
    STDMETHODIMP Read
                    (void *                     pv,
                     ULONG                      cb,
                     ULONG *                    pcbRead);
    STDMETHODIMP Write
                    (const void *               pv,
                     ULONG                      cb,
                     ULONG *                    pcbWritten);
    STDMETHODIMP Seek
                    (LARGE_INTEGER              dlibMove,
                     DWORD                      dwOrigin,
                     ULARGE_INTEGER *           plibNewPosition);
    STDMETHODIMP CopyTo
                    (IStream *                  pstm,
                     ULARGE_INTEGER cb,
                     ULARGE_INTEGER *           pcbRead,
                     ULARGE_INTEGER *           pcbWritten);
    STDMETHODIMP Commit
                    (DWORD                      grfCommitFlags);
    inline STDMETHODIMP SetSize
                    (ULARGE_INTEGER             libNewSize)
                     { return m_pImpStream->SetSize (libNewSize); };
    inline STDMETHODIMP Revert
                    ()
                     { return m_pImpStream->Revert(); };
    inline STDMETHODIMP LockRegion
                    (ULARGE_INTEGER             libOffset,
                     ULARGE_INTEGER             cb,
                     DWORD                      dwLockType)
                     { return m_pImpStream->LockRegion (libOffset, cb, dwLockType); };
    inline STDMETHODIMP UnlockRegion
                    (ULARGE_INTEGER             libOffset,
                     ULARGE_INTEGER             cb,
                     DWORD                      dwLockType)
                     { return m_pImpStream->UnlockRegion (libOffset, cb, dwLockType); };
    inline STDMETHODIMP Stat
                    (STATSTG *                  pstatstg,
                     DWORD                      grfStatFlag)
                     { return m_pImpStream->Stat (pstatstg, grfStatFlag); };
    inline STDMETHODIMP Clone
                    (IStream **                 ppstm)
                     { return m_pImpStream->Clone (ppstm); };

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this class
//
private:
    HRESULT WINAPI RewindStream
                    (ULONG                      ib);

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
public :
    CCachedStream   (LPSTREAM                   pImpStream,
                     DWORD                      ulFlags);
    ~CCachedStream  ();

///////////////////////////////////////////////////////////////////////////////
// Data members
public :
    ULONG               m_cRef;
    BOOL                m_fDirty;
    LPSTREAM            m_pImpStream;
    ULONG               m_ulFlags;
    LPVOID              m_pvCache;
    ULONG               m_libBuff;
    ULONG               m_cbBuffMac;
};

#endif // _XPSTREAM_H

// End of file for XPSTREAM.H


XPWDSR.H

///////////////////////////////////////////////////////////////////////////////
//
//  File Name
//      XPWDSR.H
//
//  Description
//      Interface declaraction file for CXPLogon, CXPProvider, CMAPIFolder,
//      and CMAPIStatus.
//      Property tag definitions.
//      Constant definitions.
//
//  Author
//      Irving De la Cruz
//
//  Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
#ifndef _XPWDSR_H
#define _XPWDSR_H

#define STRICT
#include <WINDOWS.H>
#include <WINDOWSX.H>
#include <MAPIWIN.H>
#include <MAPISPI.H>
#include <MAPIUTIL.H>
#include <MAPIVAL.H>
#include <TNEF.H>

#ifdef _DEBUG
#define ENABLE_DEBUG_OUTPUT     1
#endif // _DEBUG

// Header file for the trace and logging functions.
#include "TRACES.H"

// Common defs used by providers to communicate to the WINDS Sample Messaging Host
#include "COMWINDS.H"

// Config Page Dialog Control IDs Message String ID's
#include "RESOURCE.H"

// Cached stream for the TNEF object
#include "XPSTREAM.H"

// Definition for the helper classes CList and CMsgQueue
#include "XPLIST.H"

/// Names returned in the status object
#define TRANSPORT_DISPLAY_NAME_STRING       TEXT("PSS WINDS Remote Transport")
#define TRANSPORT_DLL_NAME_STRING           TEXT("XPWDSR.DLL")

// Internal provider version
#define TRANSPORT_VERSION_MAJOR             1
#define TRANSPORT_VERSION_MINOR             5

// Name of the stream where the message properties get encoded using TNEF encapsulation
#define TNEF_FILE_NAME                      TEXT("WINMAIL.DAT")

// Bitmask used to modify the PR_STATUS_CODE property in the status row of this
// transport, when the download or upload logic is active.
#define DOWNLOADING_MESSAGES (STATUS_INBOUND_FLUSH | STATUS_INBOUND_ACTIVE)
#define UPLOADING_MESSAGES (STATUS_OUTBOUND_FLUSH | STATUS_OUTBOUND_ACTIVE)

// Window class name used in the timer window
#define TIMER_WND_CLASS     TEXT("WINDS_XP_TIMER")

// This is copied from GENPROP.H in other PSS MAPI sample providers
typedef struct _PRIVATE_ENTRYID
{
    // MAPI-required fields
    BYTE        abFlags[4];     // 4 bytes          // MAPI Flags
    MAPIUID     uidGlobal;      // 16 bytes         // UID Unique for the backend (ie. at the database file level)  (This must be here)
    // Provider-defined fields
    BYTE        bVersion;       // 1 bytes          // Version of the entry ID schema
    BYTE        bObject;        // 1 bytes          // Object type
    BYTE        bPad[2];        // 2 bytes          // Pad to align the structure to 4-bytes boundaries
    DWORD       dwObjID;        // 4 bytes          // Object ID (container or object)
                                // 28 Bytes Total
} PRIVATE_ENTRYID;
#define     ENTRYID_VERSION     1       // Schema version
#define     CB_PRIVATE_EID      28      // Size of the structure above

// The order for this enumarations must match the order of the properties
// in the tag arrays. These arrays are used to access the
// LPSPropValue arrays returned from MAPI interfaces. The use of enumerations
// hides differences when the number of properties in the tag array increases
// or decreases. It also helps maintain error-free indexing in the use of
// the prop arrays
enum
{
    //SERVER_NAME           = 0,        // Array Indexes
    //MAILBOX_NAME          = 1,
    //MAILBOX_ID            = 2,
    //USER_NAME             = 3,
    //PASSWORD              = 4,
    //NET_CON               = 5,
    UPLOAD_TIME             = 6,
    HEADER_FILE             = 7,
    GET_HEADERS             = 8,
    NUM_LOGON_PROPERTIES    = 9         // Array size
};

// ID array of property tags for the trannsport
const static SizedSPropTagArray(NUM_LOGON_PROPERTIES, sptLogonProps) =
{
    NUM_LOGON_PROPERTIES,
    {
        PR_SMP_REMOTE_SERVER,           // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 0)
        PR_SMP_MAILBOX_NAME,            // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 1)
        PR_SMP_MAILBOX_ID,              // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 2)
        PR_SMP_USER_NAME,               // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 3)
        PR_SMP_MAILBOX_PASSWORD,        // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 4)
        PR_SMP_CONNECTION_TYPE,         // DON'T CHANGE THE ORDER OF THIS PROPERTY  (position 5)
        PR_SMP_UPLOAD_TIME,
        PR_SMP_HEADERS_FILENAME,
        PR_SMP_GET_HEADERS
    }
};

// Idle state of the transport
typedef enum _TRANSPORT_STATE
{
    WAITING,                // Waiting for the deferred submission time to arrive to send deferred messages
    READY,                  // Ready to accept re-submission of deferred messages
    SENDING,                // The transport is sending deferred messages
    HEADERS_AND_DOWNLOAD,   // Get the latest list of message headers and download messages from the server
    PENDING_RETURN_CODE,    // A remote operation has finished and the result code is pending to be returned to a remote viewer
    PROCESSING_TIMER_EVENT, // Download the message headers as a response to the scheduled action
} TRANSPORT_STATE;

typedef enum _REMOTE_ACTION
{
    REMOTE_ACTION_DOWNLOADING_MSGS,
    REMOTE_ACTION_PROCESSING_MSGS,
    REMOTE_ACTION_HEADER_REFRESH,
    REMOTE_ACTION_IDLE
} REMOTE_ACTION;

// Indexes of the identity property array
enum
{
    XPID_NAME,              // Array Indexes
    XPID_EID,
    XPID_SEARCH_KEY,
    NUM_IDENTITY_PROPS      // Array size
};

// Indexes of the properties in each row in the contents table of the remote mailbag folder.
enum
{
    HDR_EID,                // Array Indexes
    HDR_SNDR,
    HDR_REPR,
    HDR_DISP_TO,
    HDR_SUBJ,
    HDR_CLASS,
    HDR_FLAGS,
    HDR_SIZE,
    HDR_PRIOR,
    HDR_IMPORTANCE,
    HDR_SENS,
    HDR_TIME,
    HDR_STAT,
    HDR_DOWNLOAD_TIME,
    HDR_HASATTACH,
    HDR_OBJ_TYPE,
    HDR_INST_KEY,
    HDR_NORM_SUBJ,
    NUM_HDR_PROPS           // Array size
};
static const SizedSPropTagArray(NUM_HDR_PROPS, sptFldContents) =
{
    NUM_HDR_PROPS,
    {
        PR_ENTRYID,                 // Index column in the contents table
        PR_SENDER_NAME,
        PR_SENT_REPRESENTING_NAME,
        PR_DISPLAY_TO,
        PR_SUBJECT,
        PR_MESSAGE_CLASS,
        PR_MESSAGE_FLAGS,
        PR_MESSAGE_SIZE,
        PR_PRIORITY,
        PR_IMPORTANCE,
        PR_SENSITIVITY,
        PR_MESSAGE_DELIVERY_TIME,
        PR_MSG_STATUS,
        PR_MESSAGE_DOWNLOAD_TIME,
        PR_HASATTACH,
        PR_OBJECT_TYPE,
        PR_INSTANCE_KEY,
        PR_NORMALIZED_SUBJECT,
    }
};

// Available properties in the folder object
#define NUM_FOLDER_PROPS        12
static const SizedSPropTagArray (NUM_FOLDER_PROPS, sptFolderProps) =
{
    NUM_FOLDER_PROPS,
    {
        PR_ACCESS,
        PR_ACCESS_LEVEL,
        PR_CONTENT_COUNT,
        PR_ASSOC_CONTENT_COUNT,
        PR_FOLDER_TYPE,
        PR_OBJECT_TYPE,
        PR_SUBFOLDERS,
        PR_CREATION_VERSION,
        PR_CURRENT_VERSION,
        PR_CREATION_TIME,
        PR_DISPLAY_NAME,
        PR_DISPLAY_TYPE
    }
};

// Indexes of the properties we extracting of a message being
// submitted to generate the remote server message header.
enum
{
    MSG_DISP_TO,            // Array Indexes
    MSG_SUBJECT,
    MSG_CLASS,
    MSG_FLAGS,
    MSG_SIZE,
    MSG_PRIORITY,
    MSG_IMPORTANCE,
    MSG_SENSITIVITY,
    MSG_DR_REPORT,
    NUM_MSG_PROPS           // Array size
};
static const SizedSPropTagArray(NUM_MSG_PROPS, sptPropsForHeader) =
{
    NUM_MSG_PROPS,
    {
        PR_DISPLAY_TO,
        PR_SUBJECT,
        PR_MESSAGE_CLASS,
        PR_MESSAGE_FLAGS,
        PR_MESSAGE_SIZE,
        PR_PRIORITY,
        PR_IMPORTANCE,
        PR_SENSITIVITY,
        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED
    }
};

#define OUT_MSG_PROPS 2
static const SizedSPropTagArray(OUT_MSG_PROPS, sptOutMsgProps) =
{
    OUT_MSG_PROPS,
    {
        PR_SENDER_ENTRYID,
        PR_SENT_REPRESENTING_NAME
    }
};

// Used to generate one-off's for the sender of newly received messages
enum
{
    NEW_SENDER_NAME,
    NEW_SENDER_EMAIL,
    NEW_SENT_NAME,
    NEW_SENT_EMAIL,
    NEW_MSG_FLAGS,
    NUM_NEW_MSG_PROPS
};
static const SizedSPropTagArray(NUM_NEW_MSG_PROPS, sptNewMsgProps) =
{
    NUM_NEW_MSG_PROPS,
    {
        PR_SENDER_NAME,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_MESSAGE_FLAGS
    }
};

// Used to generate transport-computed properties on received mail messages
#define MSG_RECIP_PROPS     2
static const SizedSPropTagArray(MSG_RECIP_PROPS, sptMsgRecipProps) =
{
    MSG_RECIP_PROPS,
    {
        PR_EMAIL_ADDRESS,
        PR_RECIPIENT_TYPE
    }
};
// Indexes to order the recipient's table of messages being submitted
enum
{
    RECIP_ROWID,
    RECIP_NAME,
    RECIP_EMAIL_ADR,
    RECIP_TYPE,
    RECIP_RESPONSIBILITY,
    RECIP_DELIVER_TIME,
    RECIP_REPORT_TIME,
    RECIP_REPORT_TEXT,
    TABLE_RECIP_PROPS
};
const static SizedSPropTagArray (TABLE_RECIP_PROPS, sptRecipTable) =
{
    TABLE_RECIP_PROPS,
    {
        PR_ROWID,
        PR_DISPLAY_NAME,
        PR_EMAIL_ADDRESS,
        PR_RECIPIENT_TYPE,
        PR_RESPONSIBILITY,
        PR_DELIVER_TIME,
        PR_REPORT_TIME,
        PR_REPORT_TEXT
    }
};

// Available properties in the status object of this provider
#define NUM_STATUS_OBJECT_PROPS    13
const static SizedSPropTagArray (NUM_STATUS_OBJECT_PROPS, sptStatusObj) =
{
    NUM_STATUS_OBJECT_PROPS,
    {
        PR_OBJECT_TYPE,
        PR_STATUS_CODE,
        PR_PROVIDER_DISPLAY,
        PR_PROVIDER_DLL_NAME,
        PR_RESOURCE_METHODS,
        PR_RESOURCE_FLAGS,
        PR_RESOURCE_TYPE,
        PR_STATUS_STRING,
        PR_DISPLAY_NAME,
        PR_IDENTITY_DISPLAY,
        PR_IDENTITY_ENTRYID,
        PR_IDENTITY_SEARCH_KEY,
        PR_CURRENT_VERSION
    }
};

// Stand-alone helper functions (C style)
extern "C"
{
    HRESULT WINAPI DoLogonDlg
                    (PCFGDLG                    pCfgDialog);
    MSGSERVICEENTRY ServiceEntry;
    HRESULT WINAPI GetMAPIError
                    (LPMAPIERROR *              ppMAPIError,
                     ULONG                      ulFlags,
                     HRESULT                    hError,
                     HINSTANCE                  hInstance);
    BOOL WINAPI ParseTime
                    (LPTSTR                     pszTime,
                     WORD *                     pwHour,
                     WORD *                     pwMinute);
    void WINAPI GetTimeString
                    (LPTSTR                     pszTime,
                     FILETIME                   FileTime);
    extern LPALLOCATEBUFFER    gpfnAllocateBuffer;  // MAPIAllocateBuffer function
    extern LPALLOCATEMORE      gpfnAllocateMore;    // MAPIAllocateMore function
    extern LPFREEBUFFER        gpfnFreeBuffer;      // MAPIFreeBuffer function
}

// Forward class declarations
class CMAPIFolder;
class CList;

class CMAPIStatus : public IMAPIStatus
{
friend CXPLogon;
friend CList;
public:
///////////////////////////////////////////////////////////////////////////////
// Interface virtual member functions
//
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};

    MAPI_IMAPIPROP_METHODS(IMPL);
    MAPI_IMAPISTATUS_METHODS(IMPL);

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this transport
//

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CMAPIStatus     (CXPLogon *                 pLogon,
                     LPPROFSECT                 pProfSectObj);
    ~CMAPIStatus    ();

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG           m_cRef;
    CXPLogon *      m_pLogon;
    CMAPIFolder *   m_pHeaderFolder;
    LPPROFSECT      m_pProfSectObj;
};

class  CMAPIFolder : public IMAPIFolder
{
friend CXPLogon;
friend CMAPIStatus;
friend CList;
public:
///////////////////////////////////////////////////////////////////////////////
// Interface virtual member functions
//
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    STDMETHODIMP_(ULONG) Release();
    MAPI_IMAPIPROP_METHODS(IMPL);
    MAPI_IMAPICONTAINER_METHODS(IMPL);
    MAPI_IMAPIFOLDER_METHODS(IMPL);

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this class
//
    STDMETHODIMP FillContentsTable
                    (LPTSTR                     pszHeaderFilename);
    STDMETHODIMP CopyTableToFile
                    ();

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CMAPIFolder     (CMAPIStatus *              pStatusObj,
                     CXPLogon *                 pLogonObj);
    ~CMAPIFolder    ();

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG           m_cRef;
    CXPLogon *      m_pLogon;
    CMAPIStatus *   m_pStatusObj;
    LPTABLEDATA     m_pTableData;
    BOOL            m_fNoHeadersFile;
    FILETIME        m_ftLastUpdate;
};

class CXPLogon : public IXPLogon
{
public:
///////////////////////////////////////////////////////////////////////////////
// Interface virtual member functions
//
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};
    MAPI_IXPLOGON_METHODS(IMPL);

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this transport
//
public :
    STDMETHODIMP InitializeStatusRow
                    (ULONG                      ulFlags = 0);
    STDMETHODIMP SetIdentityProps
                    ();
    STDMETHODIMP DownloadMessageHeaders
                    ();
    STDMETHODIMP MakeSearchKey
                    (LPVOID                     pParentMemBlock,
                     LPTSTR                     pszAddress,
                     ULONG *                    pcbSearchKey,
                     LPBYTE *                   ppSearchKey);
    STDMETHODIMP GrowAddressList
                    (LPADRLIST *                ppAdrList,
                     ULONG                      ulResizeBy,
                     ULONG *                    pulOldAndNewCount);
    STDMETHODIMP SendMailMessage
                    (LPMESSAGE                  pMsgObj,
                     LPSRowSet                  pRecipRows);
    STDMETHODIMP SetIncomingProps
                    (LPMESSAGE                  pMsgObj,
                     PLIST_NODE                 pNode);
    BOOL WINAPI GetMsgTempFileName
                    (LPTSTR                     pszFileName);
    BOOL WINAPI IsValidAddress
                    (LPTSTR                     pszAddress,
                     LPTSTR *                   ppszServer,
                     LPTSTR *                   ppszMailbox);
    BOOL WINAPI LoadStatusString
                    (LPTSTR                     pString,
                     UINT                       uStringSize);
    void WINAPI InitializeTransportStatusFlags
                    (ULONG                      ulFlags);
    void WINAPI UpdateStatus
                    (BOOL                       fAddValidate = FALSE,
                     BOOL                       fValidateOkState = FALSE);
    void WINAPI UpdateProgress
                    (long                       lPercentComplete,
                     REMOTE_ACTION              raFlag);
    void WINAPI SetSessionFlags
                    (ULONG *                    pulFlags);
    void WINAPI CreateMsgHeaderTextLine
                    (LPSPropValue               pProps,
                     LPTSTR                     pszBuffer,
                     FILETIME &                 time);
    void WINAPI SetOutgoingProps
                    (LPMESSAGE                  pMsgObj,
                     FILETIME                   ft);
    void WINAPI CheckForUnfinishedDownloads
                    ();
    void WINAPI InitializeTimer
                    ();
    void WINAPI StopUploadTimer
                    ();
    void WINAPI StartUploadTimer
                    ();
    inline BOOL WINAPI SetUploadTime
                    (FILETIME                   ftUploads)
                    { SYSTEMTIME st;
                     FileTimeToSystemTime (&ftUploads, &st);
                     BOOL fChanged = FALSE;
                     if (m_stDelivTime.wHour != st.wHour || m_stDelivTime.wMinute != st.wMinute)
                     { fChanged = TRUE; }
                     m_stDelivTime = st;
                     return fChanged; }
    inline void WINAPI AddStatusBits
                    (DWORD dwNewBits) { m_ulTransportStatus |= dwNewBits; }
    inline void WINAPI RemoveStatusBits
                    (DWORD dwOldBits) { m_ulTransportStatus &= ~dwOldBits; }
    inline void WINAPI SetTransportState
                    (TRANSPORT_STATE NewState) { m_TransportState = NewState; }
    inline DWORD WINAPI GetTransportStatusCode
                    () { return m_ulTransportStatus; }
    inline LPTABLEDATA WINAPI GetRemoteFolderTableData
                    () { return m_pStatusObj->m_pHeaderFolder->m_pTableData; }
    inline TRANSPORT_STATE WINAPI GetTransportState
                    () { return m_TransportState; }
    inline HINSTANCE WINAPI GetInstance
                    () { return m_hInstance; }
    inline LPTSTR WINAPI GetLocalHeadersCache
                    () { return m_szHeaders; }
    inline LPTSTR WINAPI GetServerName
                    () { return m_szServer; }
    inline void WINAPI SetServerName
                    (LPTSTR                     szNewServer)
                    { lstrcpy (m_szServer, szNewServer); }
    inline LPTSTR WINAPI GetAccountName
                    () { return m_UserInfo.szMailboxName; }
    inline BOOL WINAPI PendingIncoming
                    () { return m_List.AreTherePendingDownloads(); }
    inline void WINAPI SetAccountProps
                    (LPTSTR                     szAccountName,
                     DWORD                      dwAccountID,
                     LPTSTR                     szUserName,
                     LPTSTR                     szPassword)
                    { lstrcpy (m_UserInfo.szMailboxName, szAccountName);
                      lstrcpy (m_UserInfo.szFullName, szUserName);
                      lstrcpy (m_UserInfo.szPassword, szPassword);
                      m_UserEID.dwObjID = dwAccountID;
                      LPSPropValue pOldProps = m_pIdentityProps;
                      if (S_OK == SetIdentityProps())
                      {
                          gpfnFreeBuffer (pOldProps);
                      }
                      wsprintf (m_szAddress, TEXT("%s\\%s"), m_szServer, m_UserInfo.szMailboxName);}
private:
    void WINAPI CheckSpoolerYield
                    (BOOL                       fReset = FALSE);
    void WINAPI EmptyInboundQueue
                    ();
    STDMETHODIMP ProcessHeaders
                    ();
    inline void WINAPI DecomposeAddress
                    (LPTSTR                     pszAddress,
                     LPTSTR *                   ppszServer,
                     LPTSTR *                   ppszMailbox)
                    {
                        // The 2 is to avoid the expected first two characters
                        strtok (&pszAddress[2], "\\");
                        *ppszServer = pszAddress;
                        *ppszMailbox = strtok (NULL, "\\");
                    }
    inline void WINAPI RecomposeAddress
                    (LPTSTR                     pszServer,
                     LPTSTR                     pszMailbox,
                     LPTSTR                     pszAddress)
                    {
                        // pszAddress must have previouly been decomposed with
                        // CXPLogon::DecomposeAddress
                        pszAddress[lstrlen (pszServer)] = '\\';
                    }

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
public :
    CXPLogon        (HINSTANCE                  hInstance,
                     LPMAPISUP                  pSupObj,
                     LPTSTR                     pszHeadersFile,
                     BOOL                       fGetHeaders,
                     PMAILBOX_INFO              pUserMailboxInfo,
                     DWORD                      dwMailboxID,
                     LPTSTR                     pszRemoteServer,
                     HANDLE                     hUIMutex);
    ~CXPLogon       ();

///////////////////////////////////////////////////////////////////////////////
// Data members
private :
    // Object implementation internal data
    ULONG               m_cRef;
    HINSTANCE           m_hInstance;
    CList               m_List;
    BOOL                m_fABWDSInstalled;
public :
    LPMAPISUP           m_pSupObj;
    HANDLE              m_hUIMutex;
    HRESULT             m_hRemoteActionErr;
    BOOL                m_fCancelPending;
    CMAPIStatus *       m_pStatusObj;

private :
    // Remote headers
    TCHAR               m_szHeaders[_MAX_PATH];
    BOOL                m_fGetHeaders;

    // Transport activity state
    ULONG               m_ulTransportStatus;
    TRANSPORT_STATE     m_TransportState;
    REMOTE_ACTION       m_raAction;

    // Server information
    TCHAR               m_szServer[MAX_STRING_SIZE+1];

    // User information and identity on the WINDS messaging server
    MAILBOX_INFO        m_UserInfo;
    PRIVATE_ENTRYID     m_UserEID;
    TCHAR               m_szAddress[64];
public :
    LPSPropValue        m_pIdentityProps;

private :
    // Deferred delivery and timer logic
    SYSTEMTIME          m_stDelivTime;
    HWND                m_hTimerWnd;
public :
    UINT                m_uTimerID;
};

class CXPProvider : public IXPProvider
{
friend CXPLogon;
friend CMAPIStatus;
friend CMAPIFolder;
public:
///////////////////////////////////////////////////////////////////////////////
// Interface virtual member functions
//
    STDMETHODIMP QueryInterface
                    (REFIID                     riid,
                     LPVOID *                   ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef
                    () { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release
                    () { ULONG ulCount = --m_cRef;
                         if (!ulCount) { delete this; }
                         return ulCount;};
    MAPI_IXPPROVIDER_METHODS(IMPL);

///////////////////////////////////////////////////////////////////////////////
// Other member functions specific to this class
//

///////////////////////////////////////////////////////////////////////////////
// Constructors and destructors
//
public :
    CXPProvider     (HINSTANCE                  hInst);
    ~CXPProvider    ();

///////////////////////////////////////////////////////////////////////////////
// Data members
//
private :
    ULONG               m_cRef;
    CRITICAL_SECTION    m_csTransport;
    HINSTANCE           m_hInstance;
};

#endif // _XPWDSR_H

// End of file for XPWDSR.H


README.TXT

MAPI Routing Client

The Route.Cli sample illustrates two important areas of MAPI:

1. How to use specially defined named properties, known as routing property
   sets, for storing addressing information when sending messages across
   different messaging systems.

2. How to use basic MAPI facilities, as an e-mail client might.

Different messaging systems use different address formats. So when a message
crosses a domain boundary, usually passing through a gateway, the addressing
information needs to be translated from the format used by the original domain
to the format used by the new domain. Almost all gateways do this for the
sender and ordinary message recipients. MAPI defines special mechanism for
preserving the addressing information for users who are not on the message
recipient list. Please see "Sending Messages Between Domains" in the MAPI
Online documentation for more information. This sample uses the mechanism to
route a message to a list of people one after another.

The routing client demonstrates the use of routing property sets by
implementing a simple linear route for a message with optional attached
documents. The routing list is stored in the routing property sets, and each
user who receives the message has an opportunity to edit the message and pass
it to the next person in the routing list. Each user may optionally edit the
remainder of the route as well.

The sample routing client also serves as a simple e-mail client. Features
include:

  - Viewing the contents table of any folder in any message store.
  - Sending or reading any message, using the form registered for it.
  - Forcing new mail to be downloaded to the default message store.
  - Deleting mail.

Performance

Recently the routing code of the sample has been revised with
performance considerations in mind. A few simple changes have
significantly improved response time.  If you looked at the source of
the sample before, you might find it very beneficial to compare the old
and new versions.  Here is the list of things that have been changed:

1. Instead of calling GetIDsFromNames and GetProps once for every
recipient on the routing list, now these functions are called only
once for all the recipients on the routing list.

2. Several calls to GetProps have been combined into one. (For example,
function HasAttachment which did a separate GetProps does not exist
any more. Instead the call has been combined with GetProps in function
RT_OnInitDialog).

3. Now the sample uses the MAPI_DEFERRED_ERRORS flag wherever possible.
(In calls such as OpenEntry, OpenProperty, CreateMessage, etc.) In
general, if a method accepts this flag, you should use it unless there
is a very good reason not to.

These simple tips can significantly boost performance of any MAPI
application.


BITMAP.H

/*
 -  B I T M A P . H
 *
 *  Purpose:
 *      Definitions for the Owner-Drawn Listbox bitmap stuff.
 *
 *  Copyright 1995, Microsoft Corporation. All Rights Reserved.
 */

/* Listbox string formatting defines */

#define chBOLD              TEXT('\b')
#define chUNDERLINE         TEXT('\v')
#define chTAB               TEXT('\t')
#define chBITMAP            TEXT('\001')

#define BMWIDTH             16
#define BMHEIGHT            16
#define NUMBMPS             4
#define RGBREPLACE          ((DWORD)0x00FF0000) // solid blue

/* Font style of font to use in listbox */

typedef struct
{
    int     lfHeight;
    int     lfWeight;
    BYTE    lfItalic;
    TCHAR   lfFaceName[LF_FACESIZE];
} FONTSTYLE;

/* Function Prototypes */

VOID    DrawItem(LPDRAWITEMSTRUCT pDI);
VOID    MeasureItem(HANDLE hwnd, LPMEASUREITEMSTRUCT mis);
VOID    SetRGBValues(void);
BOOL    InitBmps(HWND hwnd, int idLB);
VOID    DeInitBmps(void);
BOOL    LoadBitmapLB(void);
VOID    DeleteBitmapLB(void);
VOID    ConvertDateRec(LPSTR lpszDateRec, LPSTR lpszDateDisplay);


CLIENT.C

/*
 -  C L I E N T . C
 -
 *    Purpose:
 *      Sample routing mail client for the MAPI SDK.
 *      Exclusively uses the Extended MAPI interface.
 *
 *  Copyright 1986-1996, Microsoft Corporation. All Rights Reserved.
 *
 */

#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <windowsx.h>
#ifdef _WIN32
#include <objerror.h>
#include <objbase.h>
#endif
#ifdef WIN16
#include <compobj.h>
#endif
#include <mapiutil.h>
#include <mapidbg.h>
#include <mapix.h>
#include <mapiwin.h>
#include <pdkver.h>
#include <mapiform.h>
#include <ole2.h>
#include <wrap3d.h>

#define USES_IID_IMAPIStatus 1
#define USES_IID_IMessage  1
#include <mapiguid.h>

#include "client.h"
#include "bitmap.h"
#include "route.h"
#ifdef _WIN32
#include "chsfld.h"
#endif

#ifdef WIN16
#define GWL_USERDATA    DWL_USER
#endif

/* Application instance */
HANDLE hInst;

/* Static Data */

static ULONG cbeidFolderToView;
static LPENTRYID lpeidFolderToView = NULL;

LPADRBOOK pabAddrB = NULL;
LPMAPISESSION pses = NULL;
LPMDB pmdb = NULL;
LPMAPIFOLDER pfldOutBox = NULL;
LPSPropValue pvalSentMailEID = NULL;
HCURSOR hWaitCur;

LPVOID lpCtl3d = NULL;      /* 3D control context */

#ifdef _WIN32
/* Choose folder stuff */
HMODULE             g_hChsFldDll;
HRPICKFOLDER        g_lpfnHrPickFolder;
ULONG               cbCFDState = 0;
LPBYTE              pbCFDState = NULL;
#endif /*_WIN32 */

int PASCAL
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmd, int nCmdShow)
{
    MSG msg;

    if (!hPrevInst)
        if (!InitApplication (hInstance))
            return (FALSE);

    if (!InitInstance (hInstance, nCmdShow))
        return (FALSE);

    while (GetMessage (&msg, 0, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    DeinitApplication ();

    return (msg.wParam);
}

/*
 -  InitApplication
 -
 *  Purpose:
 *      Initialize the application.
 *
 *  Parameters:
 *      hInstance   - Instance handle
 *
 *  Returns:
 *      True/False
 *
 */

BOOL
InitApplication (HANDLE hInstance)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (hInstance, "NoMail");
    wc.hCursor = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName = "MailMenu";
    wc.lpszClassName = "RoutingSample";

    return (RegisterClass (&wc));
}

/*
 -  InitInstance
 -
 *  Purpose:
 *      Initialize this instance.
 *
 *  Parameters:
 *      hInstance   - Instance handle
 *      nCmdShow    - Do we show the window?
 *
 *  Returns:
 *      True/False
 *
 */

BOOL
InitInstance (HANDLE hInstance, int nCmdShow)
{
    HWND hWnd;
    BOOL fInit;
    BOOL f;

    hInst = hInstance;

    fInit = InitMAPI(0);

    if (!lpCtl3d)
    {
        lpCtl3d = CTL3D_Initialize(hInstance);
        CTL3D_AutoSubclass(lpCtl3d, hInstance, &f);
    }

    hWnd = CreateWindow ("RoutingSample", "Routing Sample", WS_OVERLAPPEDWINDOW,
        5, 5, 550, 75, 0, 0, hInst, NULL);

    if (!hWnd)
        return (FALSE);

    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);

    hWaitCur = LoadCursor(0, IDC_WAIT);

    if (fInit)
    {
        if (ClientLogon (hWnd))
            ToggleMenuState (hWnd, TRUE);
    }

    return (fInit);
}

BOOL
InitMAPI (HWND hWnd)
{
    HRESULT hr;

    hr = MAPIInitialize(NULL);
    if(hr)
    {
        MakeMessageBox(hWnd, GetScode(hr), IDS_MAPIINIF, NULL, MBS_ERROR);
        return FALSE;
    }
    return TRUE;
}

void
DeinitApplication ()
{
    DeinitMAPI ();

    CTL3D_Uninitialize(lpCtl3d);
    lpCtl3d = NULL;

#ifdef _WIN32
    if(g_hChsFldDll)
        FreeLibrary(g_hChsFldDll);

#endif /* _WIN32 */
}

void
DeinitMAPI ()
{
    MAPIUninitialize();

}

/*
 *  Log on to MAPI
 *
 *  Error messages are in subroutings.
 *
 *  Globals:
 *      pses        MAPI session handle
 *      pmdb        MAPI message store object
 *      pabAddrB    MAPI address book object
 *      pfldOutBox  Out folder
 *      pvalSentMailEID     EntryID of the "Sent mail" folder
 */

BOOL ClientLogon (HWND hWnd)
{
    HRESULT hr;
    /* We should not yet be logged on*/
    Assert(pses == NULL);
    Assert(pmdb == NULL);

        /* MAPILogon might yield control to Windows. So to prevent the user
    from clicking "logon" while we are in the process of loggin on we
    have to disable it*/

    SecureMenu(hWnd, TRUE);

    /* Create a MAPI session*/
    hr = MAPILogonEx((ULONG) hWnd, NULL, NULL,
            MAPI_EXTENDED | MAPI_EXPLICIT_PROFILE | MAPI_LOGON_UI |
            MAPI_NEW_SESSION, &pses);
    if(hr)
    {
        SecureMenu(hWnd, FALSE);
        pses = NULL;
        if (GetScode(hr) != MAPI_E_USER_CANCEL)
            MakeMessageBox (hWnd, GetScode(hr), IDS_LOGONFAIL, NULL, MBS_ERROR);
        return FALSE;
    }

    pmdb = OpenDefaultStore(hWnd);
    if (!pmdb) goto err;

    pabAddrB = OpenAddressBook(hWnd);
    if (!pabAddrB) goto err;

    if(!OpenOutFolder(hWnd, &pfldOutBox)) goto err;

    /* retrieve the EntryID of the sentmail folder and change the property tag
        so that it is ready to use on a message*/
    hr = HrGetOneProp((LPMAPIPROP)pmdb, PR_IPM_SENTMAIL_ENTRYID, &pvalSentMailEID);
    if(hr)
    {
        goto err;
    }
    pvalSentMailEID->ulPropTag = PR_SENTMAIL_ENTRYID;

    return TRUE;

err:

    ClientLogoff(hWnd);
    SecureMenu(hWnd, FALSE);

    return FALSE;
}

/*
 *  Releases  the global objects and logs off MAPI.
 *
 *  Globals:
 *      pses        Extended-MAPI session object
 *      pmdb        Extended-MAPI message store object
 *      pabAddrB        Address Book
 *      pfldOutBox  out folder
 *      pvalSetmailEID contains EID of the "Sent" folder
 */
VOID
ClientLogoff (HWND hWnd)
{

#ifdef _WIN32
    MAPIFreeBuffer(pbCFDState);
    pbCFDState = NULL;
    cbCFDState = 0;
#endif

    UlRelease(pfldOutBox);
    pfldOutBox = NULL;

    UlRelease(pmdb);
    pmdb = NULL;

    UlRelease(pabAddrB);
    pabAddrB = NULL;

    MAPIFreeBuffer(pvalSentMailEID);
    pvalSentMailEID = NULL;

    pses->lpVtbl->Logoff(pses, (ULONG)hWnd, MAPI_LOGOFF_UI, 0);
    UlRelease(pses);
    pses = NULL;

    SetWindowText(hWnd, "Routing Sample");
}

LPMDB
OpenDefaultStore(HWND hWnd)
{
    HRESULT hr;
    LPMDB lpmdb = NULL;
    LPMAPITABLE ptable = NULL;
    LPSRowSet prows = NULL;
    LPSPropValue pvalProp = NULL;
    static SizedSPropTagArray(2, columns) =
                { 2, { PR_DEFAULT_STORE, PR_ENTRYID} };
    SPropValue valDefStore;
    SPropertyRestriction restpropDefStore;
    SRestriction restDefStore;

    valDefStore.ulPropTag = PR_DEFAULT_STORE;
    valDefStore.dwAlignPad = 0;
    valDefStore.Value.b = TRUE;

    restpropDefStore.relop = RELOP_EQ;
    restpropDefStore.ulPropTag = PR_DEFAULT_STORE;
    restpropDefStore.lpProp = &valDefStore;

    restDefStore.rt = RES_PROPERTY;
    restDefStore.res.resProperty = restpropDefStore;

    hr = pses->lpVtbl->GetMsgStoresTable(pses, 0, &ptable);
    if (HR_FAILED(hr))
    {
        MakeMessageBox (hWnd, GetScode(hr), IDS_STORETBLFAIL, NULL, MBS_ERROR);
        goto ret;
    }

    hr = HrQueryAllRows(ptable, (LPSPropTagArray) &columns, &restDefStore, NULL, 0, &prows);
    if (HR_FAILED(hr))
    {
        MakeMessageBox (hWnd, GetScode(hr), IDS_QUERYROWFAIL, NULL, MBS_ERROR);
        goto ret;
    }

    if (prows == NULL || prows->cRows == 0
        || prows->aRow[0].lpProps[1].ulPropTag != PR_ENTRYID)
    {
        MakeMessageBox (hWnd, 0L, IDS_NODEFAULTSTORE, NULL, MBS_ERROR);
        goto ret;
    }

    Assert(prows->cRows == 1);

    hr = pses->lpVtbl->OpenMsgStore(pses, (ULONG)hWnd,
                        prows->aRow[0].lpProps[1].Value.bin.cb,
                        (LPENTRYID)prows->aRow[0].lpProps[1].Value.bin.lpb,
                        NULL, MDB_WRITE | MAPI_DEFERRED_ERRORS, &lpmdb);
    if (HR_FAILED(hr))
    {
        if (GetScode(hr) != MAPI_E_USER_CANCEL)
            MakeMessageBox (hWnd, GetScode(hr), IDS_OPENSTOREFAIL, NULL, MBS_ERROR);
        Assert(lpmdb == NULL);
        goto ret;
    }
    if(hr) /*if we have a warning, display it and succeed */
    {
        LPMAPIERROR perr = NULL;

        pses->lpVtbl->GetLastError(pses, hr, 0, &perr);
        MakeMessageBox(hWnd, GetScode(hr), IDS_OPENSTOREWARN, perr, MBS_ERROR);
        MAPIFreeBuffer(perr);
    }

    Assert(lpmdb != NULL);

    hr = HrGetOneProp((LPMAPIPROP)lpmdb, PR_DISPLAY_NAME, &pvalProp);
    if(!hr)
    {
        char buf[128];

        wsprintf(buf, "Routing Sample: %s", pvalProp->Value.lpszA);

        SetWindowText(hWnd, buf);
        MAPIFreeBuffer(pvalProp);
    }

ret:
    FreeProws(prows);
    UlRelease(ptable);

    return lpmdb;
}

#ifdef _WIN32
BOOL FGetFoldChooser(void)
{
    UINT uiErrMode;

    if(g_lpfnHrPickFolder)
        return TRUE;

    Assert(!g_hChsFldDll);

    uiErrMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);

    g_hChsFldDll = LoadLibrary(szChsFldDllName);

    SetErrorMode(uiErrMode);

    if(g_hChsFldDll)
    {
        if((g_lpfnHrPickFolder = (HRPICKFOLDER)GetProcAddress(g_hChsFldDll,
                                        szChsFldFnName)))
        {
            return TRUE;
        }

        DebugTrace("route.cli: GetProcAddress for %s failed", szChsFldFnName);

        FreeLibrary(g_hChsFldDll);
        g_hChsFldDll = NULL;
    }
    else
    {
        DebugTrace("smpfrm: failed to load choose folder dll\n");
    }

    return FALSE;
}
#endif /* _WIN32 */

/*
 -  MainWndProc
 -
 *   Purpose:
 *      Main Window Procedure.
 *      Handles the menu bar and standard window messages.
 *
 *   Parameters:
 *       hWnd
 *       message
 *       wParam
 *       lParam
 *
 *   Returns:
 *
 *
 */

LONG FAR PASCAL
MainWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    HANDLE_MSG(hWnd, WM_COMMAND, MAIN_OnCommand);

    case WM_ENDSESSION:
        DestroyWindow (hWnd);
        break;

    case WM_CLOSE:
    case WM_DESTROY:
        if (pses)
            ClientLogoff (hWnd);

        PostQuitMessage (0);
        break;

    default:
        return (DefWindowProc (hWnd, msg, wParam, lParam));
    }
    return FALSE;
}

LONG MAIN_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify)
{
    HRESULT hr;
#ifndef _WIN32
    LPDIALOGDATA pDialogData;
#endif
    LPMESSAGE pmsgOutgoing = NULL;
    ULONG ulMsgToken = 0;

    switch (id)
    {
    case IDM_NEWFORM:
        {
            LPMAPIFORMMGR pfmmgr = NULL;
            LPMAPIFORMINFO pfminfo = NULL;
            LPSPropValue pvalMsgClass = NULL;
            LPMESSAGE pmsgForm = NULL;

            hr = MAPIOpenFormMgr(pses, &pfmmgr);
            if(!hr)
            {
                hr = pfmmgr->lpVtbl->SelectForm(pfmmgr, (ULONG) hWnd, 0, NULL, NULL, &pfminfo);

                if(!hr)
                {

                    /*Get the form's msg class */
                    hr = HrGetOneProp((LPMAPIPROP)pfminfo, PR_MESSAGE_CLASS, &pvalMsgClass);
                    DebugTraceResult(HrGetOneProp, hr);

                    if(!hr)
                    {
                        /*create new message*/
                        if(CreateOutMessage(&pmsgForm))
                        {
                            hr = pses->lpVtbl->PrepareForm(pses, NULL, pmsgForm, (LPULONG) &ulMsgToken);
                            if(S_OK != GetScode(hr))
                            {
                                DebugTrace("Client: PrepareForm failed");
                                break;
                            }
                            UlRelease(pmsgForm);
                            pmsgForm = NULL;

                            hr = pses->lpVtbl->ShowForm(pses, (ULONG) hWnd, pmdb, pfldOutBox,
                                                        NULL, ulMsgToken, NULL, MAPI_NEW_MESSAGE,
                                                        0, MSGFLAG_UNSENT | MSGFLAG_READ, 0,
                                                        pvalMsgClass->Value.lpszA );
                            if(S_OK != GetScode(hr))
                            MakeMessageBox(hWnd, GetScode(hr), IDS_SHOWFORM, NULL, MBS_ERROR);
                        }
                        else
                            MakeMessageBox(hWnd, 1, IDS_CRTOUTMSG, NULL, MBS_ERROR);
                    }

                    MAPIFreeBuffer(pvalMsgClass);
                    UlRelease(pfminfo);
                }

                UlRelease(pfmmgr);
            }
        }

        break;

    case IDM_LOGON:
        if (!pses)
        {
            if (ClientLogon (hWnd))
                ToggleMenuState (hWnd, TRUE);
        }
        break;

    case IDM_LOGOFF:
        if (pses)
        {
            ClientLogoff (hWnd);
            ToggleMenuState (hWnd, FALSE);
        }
        break;

    case IDM_HIER:
        Assert(pses);
        //on win32 use the sample Choose Folder Dialog
#ifdef _WIN32
        {
            HRESULT hr;
            LPMAPIFOLDER pfld = NULL;
            LPMDB pmdbNew = NULL;

            if(!FGetFoldChooser())
                return TRUE;

            hr = (*g_lpfnHrPickFolder)(NULL, hWnd, pses, &pfld, &pmdbNew,
                                    &cbCFDState, &pbCFDState);
            if(HR_SUCCEEDED(hr))
            {
                LPSPropValue pval = NULL;

                UlRelease(pmdb);
                pmdb = pmdbNew;
                pmdbNew = NULL;

                hr = HrGetOneProp((LPMAPIPROP)pmdb, PR_DISPLAY_NAME, &pval);
                if(!hr)
                {
                    char buf[128];

                    wsprintf(buf, "Routing Sample: %s", pval->Value.lpszA);

                    SetWindowText(hWnd, buf);
                    MAPIFreeBuffer(pval);
                    pval = NULL;
                }

                hr = HrGetOneProp((LPMAPIPROP)pfld, PR_ENTRYID, &pval);
                if(!hr)
                {
                    cbeidFolderToView = pval->Value.bin.cb;
                    lpeidFolderToView = (LPENTRYID)pval->Value.bin.lpb;

                    DialogBox (hInst, "InBox", hWnd, InBoxDlgProc);

                    cbeidFolderToView = 0;
                    lpeidFolderToView = NULL;

                    MAPIFreeBuffer(pval);
                }

                UlRelease(pfld);
            }
        }

#else

        Assert(pmdb);
        if (pDialogData = CreateDialogData (iHierarchy))
            DialogBoxParam (hInst, "HierarchyTable", hWnd, CommonDlgProc, (LPARAM)pDialogData);
#endif /* _WIN32 */
        break;

#ifndef _WIN32
    case IDM_OPEN:
        Assert(pses);
        if (pDialogData = CreateDialogData (iStores))
            DialogBoxParam (hInst, "OpenStore", hWnd, CommonDlgProc, (LPARAM)pDialogData);
        break;
#endif /* _WIN32 */

    case IDM_ROUTE:
        Assert(pses);
        DialogBoxParam(hInst, "RouteNote", hWnd, RouteNoteDlgProc, (LPARAM) NULL );
        break;

    case IDM_READ:
        Assert(pses);
        Assert(pmdb);
        Assert(lpeidFolderToView == NULL);

        {
            /* Get the entry ID of the Inbox from the message store. */
            if ((hr = pmdb->lpVtbl->GetReceiveFolder(pmdb,
                    "IPM", 0,
                    &cbeidFolderToView, &lpeidFolderToView, NULL))
                        == hrSuccess)
            {
                DialogBox (hInst, "InBox", hWnd, InBoxDlgProc);
                MAPIFreeBuffer(lpeidFolderToView);
                lpeidFolderToView = NULL;
            }
            else
                MakeMessageBox (hWnd, GetScode(hr), IDS_GETRCVFAIL, NULL, MBS_ERROR);
        }
        break;

    case IDM_SEND:
        Assert(pses);
        {
            if(CreateOutMessage(&pmsgOutgoing))
            {
                hr = pses->lpVtbl->PrepareForm(pses, NULL, pmsgOutgoing, (LPULONG) &ulMsgToken);
                if(S_OK != GetScode(hr))
                {
                    DebugTrace("Client: PrepareForm failed");
                    break;
                }
                UlRelease(pmsgOutgoing);
                pmsgOutgoing = NULL;

                hr = pses->lpVtbl->ShowForm(pses, (ULONG) hWnd, pmdb, pfldOutBox, NULL, ulMsgToken,
                                            NULL, MAPI_NEW_MESSAGE, 0, MSGFLAG_UNSENT | MSGFLAG_READ, 0, "IPM.Note");
                if(S_OK != GetScode(hr))
                    MakeMessageBox(hWnd, GetScode(hr), IDS_SHOWFORM, NULL, MBS_ERROR);
            }
            else
                MakeMessageBox(hWnd, 1, IDS_CRTOUTMSG, NULL, MBS_ERROR);

        }
        break;

    case IDM_ABOUT:
        DialogBox (hInst, "AboutBox", hWnd, AboutDlgProc);
        break;

    case IDM_EXIT:
        if (pses)
            ClientLogoff (hWnd);

        PostQuitMessage (0);
        break;

        default:
            return TRUE;
    }

    return FALSE;
}

/*
 *  Displays an About dialog for the sample client.
 */

BOOL CALLBACK
AboutDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    char    rgchVersion[80];

    switch (msg)
    {
    case WM_INITDIALOG:
        wsprintf(rgchVersion, "Version %d.%d.%d (%s)", rmj, rmm, rup,
            szVerName && *szVerName ? szVerName : "BUDDY");
        SetDlgItemText(hDlg, IDC_VERSION, rgchVersion);
        return TRUE;

    case WM_COMMAND:
        if (wParam == IDOK || wParam == IDCANCEL)
        {
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

/*
 *  Handles the Inbox list view, its command buttons.
 *  The main window is a listbox presenting a summary line for
 *  each message in the Inbox. There are command buttons for
 *  refreshing the list, displaying a message, and deleting a
 *  message.
 *
 *  All operations are on single messages; multiple selection is
 *  not supported.
 *
 *  The EntryID of the folder to examine is passed in using
 *  the global lpeidFolderToView.
 */

BOOL CALLBACK
InBoxDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPMSGID lpMsgIdList = NULL;
    HCURSOR hOldCur;
    LPMESSAGE pmsgRead = NULL;
    ULONG ulObjType = 0;
    ULONG ulMsgToken = 0;
    LPINBOXDATA pIBData = NULL;

    switch (msg)
    {
    case WM_INITDIALOG:
        if(MAPIAllocateBuffer(sizeof(INBOXDATA), &pIBData))
        {
            EndDialog(hDlg, FALSE);
            return TRUE;
        }

        hOldCur = SetCursor(hWaitCur);

        ZeroMemory(pIBData, sizeof(INBOXDATA));

        InitBmps(hDlg, IDC_MSG);

        /* Populate List Box with all messages in InBox. */
        PopulateMessages(hDlg, pIBData);

        SetCursor(hOldCur);
        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        SetWindowLong(hDlg, GWL_USERDATA, (LONG)pIBData);
        return TRUE;
        break;

    case WM_SETFOCUS:
        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        break;

    case WM_MEASUREITEM:
        /* Sets the height of the owner-drawn List-Box */
        MeasureItem(hDlg, (MEASUREITEMSTRUCT *)lParam);
        break;

    case WM_DRAWITEM:
        DrawItem((DRAWITEMSTRUCT *)lParam);
        break;

    case WM_CHARTOITEM:     /* don't select item by character*/
        return  -2;

    /* Handled by IDC_DELETE */
    case WM_DELETEITEM:
        return TRUE;
    HANDLE_MSG(hDlg, WM_COMMAND, INBOX_OnCommand);
        break;
    }

    return FALSE;
}

void INBOX_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
{
    LPMSGID lpMsgNode = NULL;
    LPINBOXDATA pibData = NULL;
    HCURSOR hOldCur;
    UINT nIndex;
    RECT Rect;
    LPMESSAGE pmsgRead = NULL;
    ULONG ulObjType = 0;
    HRESULT hr;
    ULONG ulMsgToken = 0;
    ULONG cProps = 0;
    LPSPropValue pvalProps = NULL;
    LONG lStat = 0, lFlags = 0, lAccess = 0;
    LPSTR lpszMsgClass = "IPM.Note";

    enum { PRSTAT, PRFLAGS, PRACCESS, PRCLASS, PRDIM};      /* used to retrieve props*/
    SizedSPropTagArray(PRDIM, sptaSFProps) =                /*required for ShowForm call*/
    { PRDIM, {PR_MSG_STATUS, PR_MESSAGE_FLAGS, PR_ACCESS_LEVEL, PR_MESSAGE_CLASS} };

    switch (id)
    {

    case IDC_FLUSH:
        hr = DeliverNow(hDlg);
        if(hr)
            break;
         /*fall through if the flush was succesfull*/
    case IDC_NEW:

        pibData = (LPINBOXDATA)GetWindowLong(hDlg, GWL_USERDATA);
        if(!pibData)
        {
            DebugTrace("Client: userdata == 0 (inboxdlgproc)");
        }
        hOldCur = SetCursor(hWaitCur);

        /* Destroy the old message list. */
        FreeMsgList (pibData->lpMsgIdList);
        pibData->lpMsgIdList = NULL;

        /* Populate List Box with all messages in InBox. */
        PopulateMessages(hDlg, pibData);

        SetCursor(hOldCur);
        break;

    case IDC_MSG:
        if(codeNotify != LBN_DBLCLK)
        break;
        /* FALL THROUGH to read the double-clicked message */

    case IDC_READ:
        nIndex = (UINT)ListBox_GetCurSel(GetDlgItem(hDlg, IDC_MSG));
        if (nIndex == LB_ERR)
        break;

        lpMsgNode = (LPMSGID)ListBox_GetItemData(GetDlgItem(hDlg, IDC_MSG), nIndex);
        if (lpMsgNode)
        {
            hr = pmdb->lpVtbl->OpenEntry(pmdb, lpMsgNode->cbEID, lpMsgNode->lpEID,
                                NULL, MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
                                &ulObjType, (LPUNKNOWN FAR *) &pmsgRead);

            if(S_OK != GetScode(hr))
            {
                LPMAPIERROR perr = NULL;

                pmdb->lpVtbl->GetLastError(pmdb, hr, 0, &perr);
                MakeMessageBox(hDlg, GetScode(hr), IDS_READFAIL, perr, MBS_ERROR);
                MAPIFreeBuffer(perr);
                DebugTrace("Client: OpenEntry failed");
                break;
            }
            else
            {
                Assert(ulObjType == MAPI_MESSAGE);

                lpMsgNode->fUnRead = FALSE;

                /* get all the props in one call */
                hr = pmsgRead->lpVtbl->
                GetProps(pmsgRead, (LPSPropTagArray)&sptaSFProps, 0,
                                                    &cProps, &pvalProps);
                if(HR_SUCCEEDED(hr))
                {
                    if(pvalProps[PRSTAT].ulPropTag == PR_MSG_STATUS)
                        lStat = pvalProps[PRSTAT].Value.l;
                    if(pvalProps[PRFLAGS].ulPropTag == PR_MESSAGE_FLAGS)
                        lFlags = pvalProps[PRFLAGS].Value.l;
                    if(pvalProps[PRACCESS].ulPropTag == PR_ACCESS_LEVEL)
                        lAccess = pvalProps[PRACCESS].Value.l;
                    if(pvalProps[PRCLASS].ulPropTag == PR_MESSAGE_CLASS)
                        lpszMsgClass = pvalProps[PRCLASS].Value.lpszA;
                }

                else
                {
                    DebugTrace("Client: GetProps (for ShowForm) failed");
                    break;
                }

                if (!lstrcmpi(lpszMsgClass, lpszSmplRTMsgClass))
                {
                    DialogBoxParam(hInst, "RouteNote", hDlg, RouteNoteDlgProc, (LPARAM) pmsgRead);
                    /* RouteNoteDlgPropc will release pmsgRead*/
                }
                else
                {
                    hr = pses->lpVtbl->PrepareForm(pses, NULL, pmsgRead, (LPULONG) &ulMsgToken);
                    if(S_OK != GetScode(hr))
                    {
                        DebugTrace("Client: PrepareForm failed");
                        MAPIFreeBuffer(pvalProps);
                        break;
                    }
                    UlRelease(pmsgRead);
                    pmsgRead = NULL;
                    hr = pses->lpVtbl->ShowForm(pses, (ULONG) hDlg, pmdb, pfldOutBox, NULL, ulMsgToken,
                                            NULL, 0, lStat, lFlags, lAccess, lpszMsgClass);

                }
            }

        }

        MAPIFreeBuffer(pvalProps);
        pvalProps = NULL;
        /* Update the Messages List-Box with new icon */
        lpMsgNode = NULL;

        ListBox_GetItemRect(GetDlgItem(hDlg, IDC_MSG),nIndex, (LPARAM) &Rect);
        InvalidateRect(GetDlgItem(hDlg, IDC_MSG), &Rect, FALSE);

        break;

    case IDC_DELETE:
        {
        ENTRYLIST el;
        SBinary sb;

        nIndex = (UINT)ListBox_GetCurSel(GetDlgItem(hDlg, IDC_MSG));
        if (nIndex == LB_ERR)
            break;

        pibData = (LPINBOXDATA)GetWindowLong(hDlg, GWL_USERDATA);
        if(!pibData)
        {
            DebugTrace("Client: userdata == 0 (inboxdlgproc)");
        }

        lpMsgNode = (LPMSGID) ListBox_GetItemData(GetDlgItem(hDlg, IDC_MSG),
                                                nIndex);
        if (lpMsgNode)
        {
            sb.cb = lpMsgNode->cbEID;
            sb.lpb = (LPBYTE)lpMsgNode->lpEID;
            el.cValues = 1;
            el.lpbin = &sb;

            hr = pibData->pfld->lpVtbl->
                    DeleteMessages(pibData->pfld, &el, 0, NULL, 0);
            DeleteMsgNode (lpMsgNode, &pibData->lpMsgIdList);
        }

        ListBox_DeleteString(GetDlgItem(hDlg, IDC_MSG), nIndex);
        }
        break;

    case IDC_CLOSE:
    case IDCANCEL:
        pibData = (LPINBOXDATA)GetWindowLong(hDlg, GWL_USERDATA);
        if(!pibData)
        {
            DebugTrace("Client: userdata == 0 (inboxdlgproc)");

        }

        FreeMsgList (pibData->lpMsgIdList);
        pibData->lpMsgIdList = NULL;
        UlRelease(pibData->pfld);
        pibData->pfld = NULL;
        MAPIFreeBuffer(pibData);
        pibData = NULL;
        DeInitBmps();
        EndDialog (hDlg, TRUE);
        break;

    default:
        break;
    }
}

/*
 *  DeliverNow flushes outbound and inboud queues.
 *
 */
HRESULT DeliverNow(HWND hWnd)
{
    HRESULT hr;
    LPMAPISTATUS pstatSpooler = NULL;
    ULONG ulObjType = 0;
    LPMAPITABLE ptblStatus = NULL;
    SizedSPropTagArray(1, columns) =
                { 1, { PR_ENTRYID} };
    SPropValue valSpooler;
    SPropertyRestriction restpropSpooler;
    SRestriction restSpooler;
    LPSRowSet prows = NULL;

    /*Build property restriction (PR_RESOURCE_TYPE == MAPI_SPOOLER)*/
    valSpooler.ulPropTag = PR_RESOURCE_TYPE;
    valSpooler.dwAlignPad = 0;
    valSpooler.Value.l = MAPI_SPOOLER;

    restpropSpooler.relop = RELOP_EQ;
    restpropSpooler.ulPropTag = PR_RESOURCE_TYPE;
    restpropSpooler.lpProp = &valSpooler;

    restSpooler.rt = RES_PROPERTY;
    restSpooler.res.resProperty = restpropSpooler;

    /*open session status table*/
    hr = pses->lpVtbl->GetStatusTable(pses, 0, &ptblStatus);
    if(hr)
    {
        DebugTraceResult(GetStatusTable, hr);
        goto err;
    }

    /*find a row corresponding to the spooler*/
    hr = HrQueryAllRows(ptblStatus, (LPSPropTagArray) &columns, &restSpooler, NULL, 0, &prows);
    if (HR_FAILED(hr))
    {
        DebugTraceResult(HrQueryAllRows, hr);
        goto err;
    }

    Assert(prows && prows->cRows == 1); /*hope the spooler is always there */
    Assert(prows->aRow[0].lpProps[0].ulPropTag == PR_ENTRYID);

    /*open spooler as a status object*/
    hr = pses->lpVtbl->
            OpenEntry(pses, prows->aRow->lpProps->Value.bin.cb,
                        (LPENTRYID)prows->aRow->lpProps->Value.bin.lpb,
                        &IID_IMAPIStatus, MAPI_BEST_ACCESS, &ulObjType,
                        (LPUNKNOWN FAR *) &pstatSpooler);
    if(hr)
    {
        DebugTraceResult(OpenEntry, hr);
        goto err;
    }

    Assert(ulObjType == MAPI_STATUS);

    /*call FlushQueues()*/
    hr = pstatSpooler->lpVtbl->FlushQueues(pstatSpooler, (ULONG) hWnd, 0, NULL,
                                            FLUSH_DOWNLOAD | FLUSH_UPLOAD);
    if(hr)
    {
        DebugTraceResult(FlushQueues, hr);
        goto err;
    }

    /*release all used objects*/

err:
    UlRelease(ptblStatus);
    FreeProws(prows);
    UlRelease(pstatSpooler);

    return hr;
}

    /*
 -  MakeMessageBox
 -
 *  Purpose:
 *      Gets resource string and displays an error message box.
 *
 *  Parameters:
 *      hWnd            - Handle to parent window
 *      sc              - SCODE
 *      idString        - Resource ID of message in StringTable
 *      perr            - pointer to MAPIERROR from last GetLastError
 *      fStyle          - style for MessageBox
 *
 *  Returns:
 *      Void
 *
 */

void
MakeMessageBox (HWND hWnd, SCODE sc, UINT idString, LPMAPIERROR perr, UINT fStyle)
{
    char szMessage[512];
    char szbuf[256];

    if(!LoadString (hInst, idString, szMessage, 255))
        return;

    if(perr)
    {
        wsprintf(szbuf, "\n%s\n%s\nLowLevelError: 0x%08lx context: %ld ", (perr->lpszError ? perr->lpszError:""),
                        perr->lpszComponent ? perr->lpszComponent:"", perr->ulLowLevelError, perr->ulContext);
        lstrcat(szMessage, szbuf);
    }
    if (sc)
    {
        wsprintf (szbuf, "\nReturn Code: 0x%08lx", sc);
        lstrcat (szMessage, szbuf);
    }

    MessageBox (hWnd, szMessage, "Sample Routing Form", fStyle);
}

/*
 * Common Dialog Proc for store and folder listboxes
 *
 */
BOOL CALLBACK
CommonDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    MEASUREITEMSTRUCT *pmis;
    DRAWITEMSTRUCT *pdis;
    HCURSOR hOldCur;
    LPDIALOGDATA pdd = (LPDIALOGDATA) GetWindowLong(hDlg, DWL_USER);

    switch (msg)
    {
    case WM_INITDIALOG:
        /* Remember the address of our dialog data*/
        SetWindowLong(hDlg, DWL_USER, lParam);
        pdd = (LPDIALOGDATA)lParam;
        Assert(pdd->poarHead == NULL);

        /*/ Load up the rows of the dialog*/
        hOldCur = SetCursor(hWaitCur);
        PopulateStores (hDlg, &pdd->poarHead, pdd->iDlgType,
            pdd->cbEntryID, pdd->lpEntryID);
        SetCursor(hOldCur);

        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        return TRUE;

    case WM_DESTROY:
        /* Discard our dialog data*/
        FreeOarList(&pdd->poarHead);
        MAPIFreeBuffer(pdd);
        return TRUE;

    case WM_SETFOCUS:
        SetFocus (GetDlgItem (hDlg, IDC_MSG));
        break;

    case WM_MEASUREITEM:
        /* Sets the height of the owner-drawn List-Box */
        pmis = (MEASUREITEMSTRUCT *) lParam;
        pmis->itemHeight = 15;
        break;

    case WM_DRAWITEM:
        pdis = (DRAWITEMSTRUCT *) lParam;
        DrawOarItem (pdis, pdd->iDlgType);
        break;

    case WM_CHARTOITEM:     /* don't select item by character*/
        return -2;

    /* Handled by IDC_DELETE */
    case WM_DELETEITEM:
        return TRUE;

//  HANDLE_MSG(hDlg, WM_COMMAND, Common_OnCommand);
    case WM_COMMAND:
        return ((Common_OnCommand)((hDlg), (int)(wParam), (HWND)(LOWORD(lParam)), 0L));

    }   /* switch (msg) */

    return FALSE;
}

BOOL Common_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
{
    LPDIALOGDATA pdd = (LPDIALOGDATA) GetWindowLong(hDlg, DWL_USER);

    switch (id)
    {
    case IDC_MSG:
        if(codeNotify != LBN_DBLCLK)
            break;
            /* FALL THROUGH to read the double-clicked store */

    case IDC_DOWN:
    case IDC_READ:
        /*/ open a Hierarchy or Store*/
    {
            /*/ Open one of the items in the list*/
        LPMDB pmdbTemp;
        UINT nIndex;
        LPOAR poar;
        LPSPropValue pProp;
        LPSPropValue pvalProp = NULL;
        HRESULT hr;

        nIndex = (UINT)ListBox_GetCurSel(GetDlgItem(hDlg, IDC_MSG));

        if (nIndex == LB_ERR)
            return TRUE;

        poar = (LPOAR) ListBox_GetItemData(GetDlgItem(hDlg, IDC_MSG), nIndex);

        if (!poar)
            return TRUE;

                /* PropFindProp from mapiutil.h */
        pProp = PpropFindProp(poar->lpProps,  poar->cValues, PR_ENTRYID);

        Assert(pProp);
        /*/ Every row should have an EntryID */

        if (pdd->iDlgType == iStores)
        {
            hr = pses->lpVtbl->OpenMsgStore(pses,
                (ULONG)hDlg,
                pProp->Value.bin.cb,
                (LPENTRYID)pProp->Value.bin.lpb,
                NULL, MDB_WRITE, &pmdbTemp);

            if (HR_FAILED(hr))
            {
                if (GetScode(hr) != MAPI_E_USER_CANCEL)
                    MakeMessageBox (hDlg, GetScode(hr), IDS_OPENSTOREFAIL, NULL, MBS_ERROR);
                return TRUE;
                }
            if(hr) /*if we have a warning*/
            {
                LPMAPIERROR perr = NULL;

                pses->lpVtbl->GetLastError(pses, hr, 0, &perr);
                MakeMessageBox(hDlg, GetScode(hr), IDS_OPENSTOREWARN, perr, MBS_ERROR);
                MAPIFreeBuffer(perr);
            }

            Assert(hr == hrSuccess);    /* no warnings (for now?)*/

            UlRelease(pmdb);
            pmdb = pmdbTemp;

            /*Change the caption of the main window */
            hr = HrGetOneProp((LPMAPIPROP)pmdb, PR_DISPLAY_NAME, &pvalProp);
            if(!hr)
            {
                char buf[128];

                wsprintf(buf, "Routing Sample: %s", pvalProp->Value.lpszA);
                SetWindowText(GetParent(hDlg), buf);

                MAPIFreeBuffer(pvalProp);
                pvalProp = NULL;
            }

            EndDialog (hDlg, TRUE);
            return TRUE;
        }

        Assert (pdd->iDlgType == iHierarchy);

        if (id == IDC_DOWN)
        {
            LPDIALOGDATA pDialogData;
            if (pDialogData = CreateDialogData (iHierarchy))
            {
                pDialogData->cbEntryID = pProp->Value.bin.cb;
                pDialogData->lpEntryID = (LPENTRYID)pProp->Value.bin.lpb;
                DialogBoxParam (hInst, "HierarchyTable", hDlg, CommonDlgProc, (LPARAM)pDialogData);
            }

        }
        else    /* IDC_READ */
        {
            /* OPEN a contents table*/
            Assert(lpeidFolderToView == NULL);
            cbeidFolderToView = pProp->Value.bin.cb;
            lpeidFolderToView = (LPENTRYID)pProp->Value.bin.lpb;
            DialogBox (hInst, "InBox", hDlg, InBoxDlgProc);
            lpeidFolderToView = NULL;
        }
    }
    return TRUE;

    case IDC_CLOSE:
    case IDCANCEL:
        EndDialog (hDlg, TRUE);
        return TRUE;

    }

    return TRUE;
}

/*
 -  PopulateStores
 -
 *  Accumulate all the rows of a table onto the OAR list.
 *  idlgType indicates iStores, iHierarchy.
 *
 *  If iHierarchy, the EntryID of the folder is in cb/lpeid.
 */

VOID
PopulateStores ( HWND hDlg, LPOAR FAR * ppoarHead, int idlgType,
        ULONG cb, LPENTRYID lpeid)
{
    HRESULT hr;
    SCODE sc;
    LPMAPIFOLDER pfld = NULL;
    LPMAPITABLE ptable = NULL;
    LPSRowSet prows = NULL;
    LPSPropValue pvalProp = NULL;
    UINT idx;

    /* Get the list of available message stores or folderes from MAPI*/
    Assert(pses);

    switch (idlgType)
    {
    case iStores:
        if (hr = pses->lpVtbl->GetMsgStoresTable(pses, 0, &ptable))
        {
            MakeMessageBox (hDlg, GetScode(hr), IDS_STORETBLFAIL, NULL, MBS_ERROR);
            goto ret;
        }
        break;
    case iHierarchy:
        {
            ULONG ulObjType;
            if (hr = pmdb->lpVtbl->OpenEntry(pmdb, cb, lpeid, NULL,
                                MAPI_DEFERRED_ERRORS,
                            &ulObjType, (LPUNKNOWN FAR *) &pfld))
            {
                MakeMessageBox (hDlg, GetScode(hr), IDS_OPENFOLDERFAIL, NULL, MBS_ERROR);
                goto ret;
            }
            Assert(ulObjType == MAPI_FOLDER);
            if (hr = pfld->lpVtbl->GetHierarchyTable(pfld, MAPI_DEFERRED_ERRORS,
                                                        &ptable))
            {
                MakeMessageBox (hDlg, GetScode(hr), IDS_STORETBLFAIL, NULL, MBS_ERROR);
                goto ret;
            }
            hr = HrGetOneProp((LPMAPIPROP)pfld, PR_DISPLAY_NAME, &pvalProp);
            if(!hr)
            {
                if(*pvalProp->Value.lpszA)
                    SetWindowText(hDlg, pvalProp->Value.lpszA);
                MAPIFreeBuffer(pvalProp);
                pvalProp = NULL;
            }

        }
        break;
    default:
        Assert(0);
    }

    if(hr = HrQueryAllRows(ptable, NULL, NULL, NULL, 1000l, &prows))
    {
        MakeMessageBox (hDlg, GetScode(hr), IDS_QUERYROWFAIL, NULL, MBS_ERROR);
        goto ret;
    }
    for(idx = 0; idx < prows->cRows; ++idx)
    {
        LPOAR poar = NULL;
        sc = MAPIAllocateBuffer(sizeof(OAR), &poar);
        if (sc)
        {
            hr = ResultFromScode(sc);
            FreeProws(prows);   /* free ENTIRE row*/
            break;
        }
        /*/ Transfer the data of the row to our OAR structure.*/
        poar->cValues = prows->aRow[idx].cValues;
        poar->lpProps = prows->aRow[idx].lpProps;

        /* Put OAR at head of the list*/
        if (*ppoarHead)
            (*ppoarHead)->lpPrev = poar;
        poar->lpPrev = NULL;
        poar->lpNext = (*ppoarHead);
        *ppoarHead  = poar;

        ListBox_AddString(GetDlgItem(hDlg, IDC_MSG), (LONG) poar);
    }

    MAPIFreeBuffer(prows);  /* free OUTER buffer only*/

ret:
    UlRelease(ptable);
    UlRelease(pfld);
}

/*
 -  FreeOarList
 -
 *  Free the memory of the rows on the screen.
 *  Zero the pointer passed in.
 */
VOID
FreeOarList (LPOAR FAR *ppoarHead)
{
    LPOAR poar = *ppoarHead;

    while (poar)
    {
        LPOAR poarTemp = poar;

        poar = poarTemp->lpNext;
        MAPIFreeBuffer(poarTemp->lpProps);
        MAPIFreeBuffer(poarTemp);
    }

    *ppoarHead = NULL;
}

/*
 -  DrawOarItem
 -
 *  Purpose:
 *      Paint the client area of the owner-drawn listbox.
 *
 *  Parameters:
 *      pdis        - Pointer to a DRAWITEMSTRUCT
 *
 *  Returns:
 *      void
 *
 */

VOID
DrawOarItem (DRAWITEMSTRUCT FAR * pdis, int idlgType)
{
    HBRUSH hSolidBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    HBRUSH hOldBrush = SelectObject(pdis->hDC, hSolidBrush);

    if (ODA_DRAWENTIRE & pdis->itemAction)
    {
        LPOAR poar;
        LPSPropValue pProp;
        UINT c;

        /* Clear the item Rectangle */

        PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
            pdis->rcItem.right - pdis->rcItem.left,
            pdis->rcItem.bottom - pdis->rcItem.top, PATCOPY);

        /* Draw the item */

        poar = (LPOAR) pdis->itemData;

        for (pProp = poar->lpProps, c = (UINT)poar->cValues;
            c > 0;
            c-- , pProp++)
        {
            /* Identify the Default Store*/
            if (idlgType == iStores
                    && pProp->ulPropTag == PR_DEFAULT_STORE && pProp->Value.b)
                TextOut (pdis->hDC, pdis->rcItem.left + 10, pdis->rcItem.top+2,
                    TEXT(">"), 1);

            if (pProp->ulPropTag == PR_DISPLAY_NAME)
                TextOut (pdis->hDC, pdis->rcItem.left + 20, pdis->rcItem.top+2,
                    pProp->Value.LPSZ,
                    lstrlen (pProp->Value.LPSZ));

        }

        /* Invert item rectangle if item is selected */

        if (ODS_SELECTED & pdis->itemState)
            PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
                pdis->rcItem.right - pdis->rcItem.left,
                pdis->rcItem.bottom - pdis->rcItem.top, DSTINVERT);

        /* Draw a focus rectangle if item has focus */

        if (ODS_FOCUS & pdis->itemState)
            DrawFocusRect (pdis->hDC, &pdis->rcItem);
    }
    else
    {
        /* Invert the item if the selection state is changing */

        if (ODA_SELECT & pdis->itemAction)
            PatBlt (pdis->hDC, pdis->rcItem.left, pdis->rcItem.top,
                pdis->rcItem.right - pdis->rcItem.left,
                pdis->rcItem.bottom - pdis->rcItem.top, DSTINVERT);

        /* Draw a focus if the focus state is changing */

        if (ODA_FOCUS & pdis->itemAction)
            DrawFocusRect (pdis->hDC, &pdis->rcItem);
    }

    SelectObject(pdis->hDC, hOldBrush);
    DeleteObject(hSolidBrush);
}

/* Fill the lppMsgIdList with info about the messages in the specified folder. */
enum { E_EID=0, E_SUBJECT, E_SENDER_NAME, E_MSG_DEL_TIME, E_FLAGS, E_PRIORITY, E_CONVERS_KEY,
        E_SEARCH_KEY, E_CLASS, E_RECORD_KEY, E_PTINBOXDIM};
SizedSPropTagArray(E_PTINBOXDIM, ptInbox) =
{
    E_PTINBOXDIM,
    {
        PR_ENTRYID,
        PR_SUBJECT,
        PR_SENDER_NAME,
        PR_MESSAGE_DELIVERY_TIME,
        PR_MESSAGE_FLAGS,
        PR_PRIORITY,
        PR_CONVERSATION_KEY,
        PR_SEARCH_KEY,
        PR_MESSAGE_CLASS,
        PR_RECORD_KEY
    }
};

VOID
PopulateMessages( HWND hDlg, LPINBOXDATA pibData )
{
    LPMAPIFOLDER pfld = pibData->pfld;
    LPMAPITABLE ptable = NULL;
    LPSRowSet prows = NULL;
    HRESULT hr;
    ULONG ulType;
    SizedSSortOrderSet(1, sos) =
        { 1, 0, 0, { PR_MESSAGE_DELIVERY_TIME, TABLE_SORT_ASCEND } };
    UINT nIndex;
    LPMSGID lpMsgNode;
    LPSPropValue pvalProp = NULL;

    ListBox_ResetContent(GetDlgItem(hDlg, IDC_MSG));

    if(!pfld)
    {
        /* Open the right folder, and get the list of messages. */
        hr = pmdb->lpVtbl->OpenEntry(pmdb,cbeidFolderToView, lpeidFolderToView,
                                 NULL, MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS,
                                    &ulType, (LPUNKNOWN FAR *)&pfld);
        if(GetScode(hr) != S_OK)    goto ret;

        Assert(ulType == MAPI_FOLDER);
        pibData->pfld = pfld;
        hr = HrGetOneProp((LPMAPIPROP)pfld, PR_DISPLAY_NAME, &pvalProp);
        if(!hr)
        {
            if(*pvalProp->Value.lpszA)
                SetWindowText(hDlg, pvalProp->Value.lpszA);
            MAPIFreeBuffer(pvalProp);
            pvalProp = NULL;
        }
    }
    hr = pfld->lpVtbl->GetContentsTable(pfld, MAPI_DEFERRED_ERRORS, &ptable);
    if (hr)
        goto ret;

    if (hr = HrQueryAllRows(ptable, (LPSPropTagArray) &ptInbox, NULL,
        (LPSSortOrderSet) &sos, 0, &prows))
    {
        MakeMessageBox (hDlg, GetScode(hr), IDS_QUERYROWFAIL, NULL, MBS_ERROR);
        goto ret;
    }

    for (nIndex = 0; nIndex < prows->cRows; ++nIndex)
    {
        lpMsgNode = MakeMsgNode(prows->aRow + nIndex);

        if (lpMsgNode)
        {
            InsertMsgNode(lpMsgNode, &pibData->lpMsgIdList);

            ListBox_AddString(GetDlgItem(hDlg, IDC_MSG),(LONG) lpMsgNode);
        }
    }
    FreeProws(prows);

ret:
    UlRelease(ptable);
}

/*
 -  MakeMsgNode
 -
 *  Purpose:
 *      Allocate memory for a new MSGID node and initialize its
 *      data members to the values passed in.
 *      A separate allocation is used for each property which is pretty
 *      wastefull. This can be changed to a smarter allocation scheme.
 *
 *  Parameters:
 *
 *  Return:
 *      lpMsgNode       - Pointer to new node
 */

LPMSGID
MakeMsgNode (LPSRow prow)
{
    LPMSGID lpMsgNode = NULL;

    if (!prow)
        goto err;

    if (MAPIAllocateBuffer (sizeof (MSGID), (LPVOID far *) & lpMsgNode))
        goto err;

    ZeroMemory(lpMsgNode, sizeof (MSGID));

    if(prow->lpProps[E_FLAGS].ulPropTag == PR_MESSAGE_FLAGS)
    {
        lpMsgNode->fHasAttach = !!(prow->lpProps[E_FLAGS].Value.l & MSGFLAG_HASATTACH);
        lpMsgNode->fUnRead = !(prow->lpProps[E_FLAGS].Value.l & MSGFLAG_READ);
    }

    if(prow->lpProps[E_EID].ulPropTag == PR_ENTRYID)
    {
        if (MAPIAllocateMore(prow->lpProps[E_EID].Value.bin.cb, lpMsgNode,
                    (LPVOID FAR *)&lpMsgNode->lpEID))
            goto err;
        CopyMemory(lpMsgNode->lpEID, prow->lpProps[E_EID].Value.bin.lpb, prow->lpProps[E_EID].Value.bin.cb);
        lpMsgNode->cbEID = prow->lpProps[E_EID].Value.bin.cb;
    }

    if(prow->lpProps[E_SENDER_NAME].ulPropTag == PR_SENDER_NAME)
    {
        if (MAPIAllocateMore (lstrlen (prow->lpProps[E_SENDER_NAME].Value.LPSZ) + 1,
                    lpMsgNode, (LPVOID far *) & lpMsgNode->lpszFrom))
        goto err;
        lstrcpy (lpMsgNode->lpszFrom, prow->lpProps[E_SENDER_NAME].Value.LPSZ);
    }

    if(prow->lpProps[E_SUBJECT].ulPropTag == PR_SUBJECT)
    {
        if (MAPIAllocateMore (lstrlen (prow->lpProps[E_SUBJECT].Value.LPSZ) + 1, lpMsgNode,
                    (LPVOID far *) & lpMsgNode->lpszSubject))
            goto err;
        lstrcpy (lpMsgNode->lpszSubject, prow->lpProps[E_SUBJECT].Value.LPSZ);
    }

    if(prow->lpProps[E_MSG_DEL_TIME].ulPropTag == PR_MESSAGE_DELIVERY_TIME)
    {
        if (MAPIAllocateMore (32, lpMsgNode,(LPVOID far *) & lpMsgNode->lpszDateRec))
            goto err;
        FormatFILETIME (&prow->lpProps[E_MSG_DEL_TIME].Value.ft, lpMsgNode->lpszDateRec);
    }

    return lpMsgNode;

err:
    MAPIFreeBuffer (lpMsgNode);
    return NULL;
}

/*
 -  InsertMsgNode
 -
 *  Purpose:
 *      We insert the nodes
 *      at the beginning of the list.  This can be
 *      replaced with a routine that inserts sorted on
 *      different criteria, like DateReceived, From, or
 *      Subject.
 *
 *  Parameters:
 *      lpMsgNode       - Pointer to a MSGID node
 *      lppMsgHead      - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
InsertMsgNode (LPMSGID lpMsgNode, LPMSGID * lppMsgHead)
{
    if (*lppMsgHead)
    {
        lpMsgNode->lpNext = *lppMsgHead;
        (*lppMsgHead)->lpPrev = lpMsgNode;
    }
    else
        lpMsgNode->lpNext = NULL;

    /* The next 2 assignments are here in case the node came from somewhere */
    /* other than a call to MakeMsgNode () in which case we aren't sure */
    /* they're already NULL. */

    lpMsgNode->lpPrev = NULL;
    *lppMsgHead = lpMsgNode;
}

/*
 -  DeleteMsgNode
 -
 *  Purpose:
 *      Removes the node passed in from the list.  This
 *      may seem like a strange way to do this but it's
 *      not, because the Owner-Drawn List Box gives us
 *      direct access to elements in the list that makes
 *      it easier to do things this way.
 *
 *  Parameters:
 *      lpMsgNode       - Pointer to the MSGID node to delete
 *      lppMsgHead      - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
DeleteMsgNode (LPMSGID lpMsgNode, LPMSGID * lppMsgHead)
{
    if (!lpMsgNode)
        return;

    if (lpMsgNode->lpPrev)
    {
        /* Adjust Previous node to point to Next*/
        Assert(*lppMsgHead != lpMsgNode);
        lpMsgNode->lpPrev->lpNext = lpMsgNode->lpNext;
    }
    else
    {
        /*/ Adjust Head to point to Next*/
        Assert(*lppMsgHead == lpMsgNode);
        *lppMsgHead = lpMsgNode->lpNext;
    }

    /* Adjust next node to point to Previous*/

    if (lpMsgNode->lpNext)
        lpMsgNode->lpNext->lpPrev = lpMsgNode->lpPrev;

    MAPIFreeBuffer (lpMsgNode);
    return;
}

/*
 -  FindNode
 -
 *  Purpose:
 *      Returns a pointer to the node with EntryID equal to *pEntryID.
 *      Returns NULL if node doesn't exist.
 *
 *  Parameters:
 *      lpMsgHead       - Pointer to the head of the list
 *      pEntryID + cbEntryID    - Message ID to search for
 *
 *  Return:
 *      lpMsgNode       - Pointer to the node returned
 */

LPMSGID
FindNode (LPMSGID lpMsgHead, LPENTRYID pEntryID, ULONG cbEntryID)
{
    ULONG fl;
    HRESULT hr;

    Assert(pmdb);

    while (lpMsgHead)
    {
        hr = pmdb->lpVtbl->CompareEntryIDs(pmdb, cbEntryID, pEntryID, lpMsgHead->cbEID, lpMsgHead->lpEID,
                                            0, &fl);
        if(S_OK != GetScode(hr))
            return NULL;
        if(fl)
            break;

        lpMsgHead = lpMsgHead->lpNext;
    }

    return lpMsgHead;
}

/*
 -  FreeMsgList
 -
 *  Purpose:
 *      Walks down the MsgList and frees each node.
 *
 *  Parameters:
 *      lpMsgHead       - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
FreeMsgList (LPMSGID lpMsgHead)
{
    LPMSGID lpT;

    while (lpMsgHead)
    {
        lpT = lpMsgHead;
        lpMsgHead = lpMsgHead->lpNext;
        MAPIFreeBuffer (lpT);
    }
}

/*
 -  ToggleMenuState
 -
 *  Purpose:
 *      Enables/Disables menu items depending on the session state.
 *
 *  Parameters:
 *      hWnd            - handle to the window/dialog who called us
 *      fLoggedOn       - TRUE if logged on, FALSE if logged off
 *
 *  Return:
 *      Void.
 */

void ToggleMenuState(HWND hWnd, BOOL fLoggedOn)
{
    EnableMenuItem (GetMenu (hWnd), IDM_HIER,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_OPEN,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_LOGOFF,   !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_ROUTE,    !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_READ,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_SEND,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_NEWFORM,  !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON,    fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,       FALSE);
}

//
//  SecureMenu
//
//  Purpose:
//      Enables/Disables Logon and Exit menu items.
//      CMCLogon might yield control to Windows, so the user might be able to
//      access the window menu (for example click Logon) after we call
//      MAPILogon, but before it returns.
//
//  Parameters:
//      hWnd            - handle to the window/dialog who called us
//      fBeforeLogon    - TRUE when this function is called when we are about
//                      to call MAPILogon, FALSE if called after logon (failed)
//                      if Logon succeddes ToggleMenuState is called instead of
//                      this function.
//
//  Return:
//      Void.
//

void SecureMenu(HWND hWnd, BOOL fBeforeLogon)
{
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON, fBeforeLogon);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,  fBeforeLogon);
}

/*
 *  Formats a Win32 file time as a MAPI date/time string.
 *  NOTE: converts from GMT to local time.
 */
void FormatFILETIME(FILETIME *pft, LPSTR szTime)
{
    FILETIME        ft;
    SYSTEMTIME      systime;

    FileTimeToLocalFileTime(pft, &ft);
    FileTimeToSystemTime(&ft, &systime);
    wsprintf(szTime,
        "%04.4d/%02.2d/%02.2d %02.2d:%02.2d",
        systime.wYear, systime.wMonth, systime.wDay,
        systime.wHour, systime.wMinute);
}

/*
 *  Create a data block used in CommonDlgProc to remember
 *  the entire chain of rows in the scrolling list box.
 */
LPDIALOGDATA
CreateDialogData (int iDlgType)
{
    LPDIALOGDATA pDialogData;

    SCODE sc;

    sc = MAPIAllocateBuffer(sizeof(DIALOGDATA), &pDialogData);
    if (sc)
    {
        MakeMessageBox (0, MAPI_E_NOT_ENOUGH_MEMORY, IDS_OPERATION, NULL, MBS_ERROR);
        return NULL;
    }

    pDialogData->iDlgType = iDlgType;
    pDialogData->poarHead = NULL;
    pDialogData->cbEntryID = 0;
    pDialogData->lpEntryID = NULL;
    return pDialogData;
}


CLIENT.H

/*
 -  C L I E N T . H
 -
 *  Purpose:
 *      Header file for the sample mail client based on Extended MAPI.
 *
 *      Copyright 1995, Microsoft Corporation. All Rights Reserved.
 */

#ifndef __CLIENT_H__
#define __CLIENT_H__

/* Menu Item IDs */

#define IDM_LOGON       100
#define IDM_LOGOFF      101
#define IDM_EXIT        102
#define IDM_READ        103
#define IDM_SEND        104
#define IDM_ABOUT       105
#define IDM_OPEN        106
#define IDM_HIER        107

#define IDM_ROUTE       108

#define IDM_NEWFORM     109
/* Resource IDs */

#define ICON_NOMAIL     200
#define ICON_MAIL       201
#define IDB_ENVELOPE    300

/* Address Book Control IDs */

#define IDT_ADDRESS     101
#define IDC_ADDRESS     102
#define IDT_LIST        103
#define IDC_LIST        104
#define IDC_ADD         105
#define IDC_REMOVE      106

/* Compose Note Control IDs */

#define IDC_SEND        101
#define IDC_RESOLVE     102
#define IDC_ATTACH      103
#define IDC_OPTIONS     104
#define IDC_ADDRBOOK    105
#define IDT_TO          106
#define IDC_TO          107
#define IDT_CC          108
#define IDC_CC          109
#define IDT_SUBJECT     110
#define IDC_SUBJECT     111
#define IDC_NOTE        112
#define IDC_LINE1       -1
#define IDC_LINE2       -1
#define IDC_CATTACHMENT 113
#define IDT_CATTACHMENT -1

#define IDC_ADDROUTESLIP 115

//RouteNote control IDs
#define IDC_RTCC            101
#define IDT_RTCC            -1
#define IDT_RTSUBJECT           -1
#define IDC_RTSUBJECT           102
#define IDC_RTNOTE          103
#define IDC_RTROUTE         104
#define IDC_RTATTACH            105
#define IDC_RTEDITROUTESLIP 106
#define IDC_RTATTACHLIST        107
#define IDT_RTATTACHLIST        108
#define IDC_RTSAVEATTACH        109
#define IDC_RTDELATTACH         110

/* InBox Control IDs */

#define IDT_MSG         101
#define IDC_MSG         102
#define IDC_NEW         103
#define IDC_READ        104
#define IDC_DELETE      105
#define IDC_CLOSE       106
#define IDC_DOWN                107     // Hierarchy Table only
#define IDC_FLUSH       108

/* ReadNote Control IDs */

#define IDC_SAVECHANGES 101
#define IDC_SAVEATTACH  102
#define IDC_REPLY       103
#define IDC_REPLYALL    104
#define IDC_FORWARD     105
#define IDT_RFROM       106
#define IDT_RDATE       107
#define IDT_RTO         108
#define IDT_RCC         109
#define IDT_RSUBJECT    110
#define IDC_RFROM       111
#define IDC_RDATE       112
#define IDC_RTO         113
#define IDC_RCC         114
#define IDC_RSUBJECT    115
#define IDC_READNOTE    116
#define IDT_ATTACHMENT  117
#define IDC_ATTACHMENT  118

/* Options Control IDs */

#define IDC_RETURN      101

/* Details Control IDs */

#define IDT_NAME        100
#define IDC_NAME        101
#define IDT_TYPE        102
#define IDC_TYPE        103
#define IDT_ADDR        104
#define IDC_ADDR        105

/* About Box Control IDs */

#define IDC_VERSION             101

/* Route Box Control IDs*/

#define IDC_ROUTE               100
#define IDC_ADDRLISTOLD         101
#define IDC_ADDRLISTACTIVE      102
#define IDC_ADDADDR             103
#define IDC_REMOVEADDR          104
#define IDC_MOVEUP              105
#define IDC_MOVEDOWN            106
#define IDC_STATIC                      -1

#define LENGTHOLD        80
#define LENGTHACTIVE 80
#define LBLENGTH (LENGTHOLD + LENGTHACTIVE) /*sum of the lengths of the two listboxes in RouteNote DlgBox*/
#define DELTA 5  /*distance between the bottom of the first LB and the top of the second
                    DELATA is not used directly in the .rc file. it has to be calculated manualy*/
#define LBX 70  /* The x coordinate of the left edges of the listboxes*/
#define DELTAX 140 /*the width of the listboxes*/
#define OLDY   5        /*the y coordinate of the top of the first listbox*/
#define MINY   40 /*the min height of a listbox*/

/* String Table IDs */

#define MAPI_ERROR_MAX          30

#define IDS_LOGONFAIL           (MAPI_ERROR_MAX + 1)
#define IDS_ADDRBOOKFAIL        (MAPI_ERROR_MAX + 2)
#define IDS_RESOLVEFAIL         (MAPI_ERROR_MAX + 3)
#define IDS_UNRESOLVEDNAMES     (MAPI_ERROR_MAX + 4)
#define IDS_SENDERROR           (MAPI_ERROR_MAX + 5)
#define IDS_DETAILS_TOO_MANY    (MAPI_ERROR_MAX + 6)
#define IDS_DETAILSFAIL         (MAPI_ERROR_MAX + 7)
#define IDS_NORECIPS            (MAPI_ERROR_MAX + 8)
#define IDS_SAVEATTACHERROR     (MAPI_ERROR_MAX + 9)
#define IDS_READFAIL            (MAPI_ERROR_MAX + 10)
#define IDS_STORETBLFAIL        (MAPI_ERROR_MAX + 11)
#define IDS_QUERYROWFAIL        (MAPI_ERROR_MAX + 12)
#define IDS_OPENSTOREFAIL       (MAPI_ERROR_MAX + 13)
#define IDS_STATUSTBLFAIL       (MAPI_ERROR_MAX + 14)
#define IDS_OPENFOLDERFAIL      (MAPI_ERROR_MAX + 15)
#define IDS_GETRCVFAIL          (MAPI_ERROR_MAX + 16)
#define IDS_SETRCVFAIL          (MAPI_ERROR_MAX + 17)
#define IDS_SETPROPFAIL         (MAPI_ERROR_MAX + 18)
#define IDS_NOENTRYID           (MAPI_ERROR_MAX + 19)
#define IDS_OPERATION           (MAPI_ERROR_MAX + 20)
#define IDS_NODEFAULTSTORE      (MAPI_ERROR_MAX + 21)
#define IDS_SETCOLFAIL          (MAPI_ERROR_MAX + 22)
#define IDS_CRTOUTMSG                   (MAPI_ERROR_MAX + 23)
#define IDS_SHOWFORM                    (MAPI_ERROR_MAX + 24)
#define IDS_OPENAB                              (MAPI_ERROR_MAX + 25)
#define IDS_INIDIAG                             (MAPI_ERROR_MAX + 26)
#define IDS_UNXERR                              (MAPI_ERROR_MAX + 27)
#define IDS_OPENOUTF                    (MAPI_ERROR_MAX + 28)
#define IDS_MAPIINIF                    (MAPI_ERROR_MAX + 29)
#define IDS_OPENSTOREWARN               (MAPI_ERROR_MAX + 30)
#define IDS_OPENABWARN                  (MAPI_ERROR_MAX + 31)
#define IDS_LASTRECIP                   (MAPI_ERROR_MAX + 32)

#define IDS_IPM                 (MAPI_ERROR_MAX + 42)
#define IDS_IPM_COMMENT         (MAPI_ERROR_MAX + 43)
#define IDS_INBOX               (MAPI_ERROR_MAX + 44)
#define IDS_INBOX_COMMENT       (MAPI_ERROR_MAX + 45)
#define IDS_OUTBOX              (MAPI_ERROR_MAX + 46)
#define IDS_OUTBOX_COMMENT      (MAPI_ERROR_MAX + 47)
#define IDS_SENTMAIL            (MAPI_ERROR_MAX + 48)
#define IDS_SENTMAIL_COMMENT    (MAPI_ERROR_MAX + 49)
#define IDS_FILTER              (MAPI_ERROR_MAX + 50)
#define IDS_OPENOUTB                    (MAPI_ERROR_MAX + 51)

/* Manifest Constants */

#define MBS_ERROR           (MB_ICONSTOP | MB_OK)
#define MBS_INFO            (MB_ICONINFORMATION | MB_OK)
#define MBS_OOPS            (MB_ICONEXCLAMATION | MB_OK)

/* Structure Definitions */

typedef struct _msgid *LPMSGID;

typedef struct _msgid
{
    ULONG           cbEID;
    LPENTRYID       lpEID;
    BOOL        fHasAttach;
    BOOL        fUnRead;
    LPSTR       lpszFrom;
    LPSTR       lpszSubject;
    LPSTR       lpszDateRec;
    LPMSGID     lpPrev;
    LPMSGID     lpNext;
} MSGID;

typedef struct _inboxdata
{
    LPMSGID         lpMsgIdList;
    LPMAPIFOLDER pfld;
} INBOXDATA, FAR * LPINBOXDATA;

typedef struct _oar *LPOAR;

typedef struct _oar
{
    LPOAR                   lpNext;
    LPOAR                   lpPrev;
    ULONG                   cValues;
    LPSPropValue    lpProps;
} OAR;

/* Types of MAPI Tables shown by common dialog */
enum {
    iStores,
    iHierarchy
};

/* Data structure used by CommonDlgProc */
typedef struct {
    int             iDlgType;       // dialog type.  see "enum" above
    LPOAR           poarHead;       // chain of rows within the dialog
    ULONG           cbEntryID;
    LPENTRYID       lpEntryID;
} DIALOGDATA, FAR * LPDIALOGDATA;

/* Globals */
extern LPMAPISESSION pses;
extern LPADRBOOK pabAddrB;
extern LPMDB pmdb;
extern HANDLE hInst;
extern HCURSOR hWaitCur;
extern LPMAPIFOLDER pfldOutBox;
extern LPSPropValue pvalSentMailEID;
/* Function Prototypes */

int  PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, int);
void DeinitApplication(void);
BOOL ClientLogon(HWND);
VOID ClientLogoff(HWND);
BOOL InitMAPI (HWND);
void DeinitMAPI();
LPMDB OpenDefaultStore(HWND);
long FAR PASCAL MainWndProc(HWND, UINT, UINT, LPARAM);
BOOL CALLBACK CommonDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK InBoxDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK ComposeDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK ReadMailDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK OptionsDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK DetailsDlgProc(HWND, UINT, WPARAM, LPARAM);
void    MakeMessageBox(HWND, SCODE, UINT, LPMAPIERROR, UINT);
HRESULT HrCreateChildFolder (HWND, LPMAPIFOLDER, ULONG, ULONG, LPTSTR, ULONG, LPMAPIFOLDER FAR *);
VOID    PopulateStores ( HWND hDlg, LPOAR FAR * ppoarHead, int idlgType, ULONG cb, LPENTRYID lpeid);
VOID    FreeOarList (LPOAR FAR *ppoarHead);
VOID    DrawOarItem (DRAWITEMSTRUCT FAR * pdis, int idlgType);
VOID    PopulateMessages( HWND hDlg, LPINBOXDATA pibData );
LPMSGID MakeMsgNode(LPSRow);
LPMSGID FindNode (LPMSGID lpMsgHead, LPENTRYID pEntryID, ULONG cbEntryID);
void    InsertMsgNode(LPMSGID, LPMSGID *);
void    DeleteMsgNode(LPMSGID, LPMSGID *);
void    FreeMsgList(LPMSGID);
void    LogSendMail(ULONG);
void    ToggleMenuState(HWND, BOOL);
void    FormatMessageID(ULONG, LPBYTE, LPSTR);
void    FormatFILETIME(FILETIME *, LPSTR);
LPDIALOGDATA CreateDialogData(int iDlgType);
BOOL READ_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
BOOL READ_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam);
void INBOX_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
LONG MAIN_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);
BOOL Common_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
HRESULT DeliverNow(HWND hWnd);
void SecureMenu(HWND hWnd, BOOL fBeforeLogon);

#endif


BITMAP.C

/*
 -  B I T M A P . C
 -
 *  Purpose:
 *      Bitmap and Listbox support functions for InBox in sample mail client.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <windowsx.h>
#ifdef _WIN32
#include <objerror.h>
#include <objbase.h>
#endif
#ifdef WIN16
#include <compobj.h>
#endif
#include <mapiwin.h>
#include <mapidbg.h>
#include <mapi.h>
#include <mapix.h>
#include "bitmap.h"
#include "client.h"

// Fonts to use in dialogs

#ifdef _WIN32
#define SHELL_FONT "MS Shell Dlg"
#define SHELL_FONT_SIZE 8
#else
#define SHELL_FONT "MS Sans Serif"
#define SHELL_FONT_SIZE 8
#endif

/*
 *  globals
 */

DWORD   rgbWindowColor = 0xFF000000;    // variables for the current
DWORD   rgbHiliteColor = 0xFF000000;    // system color settings.
DWORD   rgbWindowText  = 0xFF000000;    // on a WM_SYSCOLORCHANGE
DWORD   rgbHiliteText  = 0xFF000000;    // we check to see if we need
DWORD   rgbGrayText    = 0xFF000000;    // to reload our bitmap.
DWORD   rgbDDWindow    = 0xFF000000;    //
DWORD   rgbDDHilite    = 0xFF000000;    // 0xFF000000 is an invalid RGB

// an array of integers containing the tab stops, in pixels. The tab
// stops must be sorted in ascending order; back tabs are not allowed.

int     rgTabs[] = { 2, 28, 135, 292 };
int     dxbmpLB, dybmpLB;   // dx and dy of listbox bmps

HDC     hdcMemory = 0;      // hdc to hold listbox bitmaps (for speed)
HBITMAP hbmpOrigMemBmp = 0; // original null bitmap in hdcMemory
HBITMAP hbmpLB = 0;         // cached listbox bitmaps
HFONT   hfontLB = 0;        // hfont of LB
HWND    hwndLB = 0;         // hwnd of LB

FONTSTYLE fontStyle = { SHELL_FONT_SIZE, FW_NORMAL, 0, TEXT(SHELL_FONT) };

extern HANDLE hInst;

/*
 -  DeInitBmps
 -
 *  Purpose:
 *      cleans up LB hfonts, hdc, and hbmps
 */

VOID DeInitBmps(VOID)
{
    DeleteBitmapLB();
    if(hdcMemory)
    {
        DeleteDC(hdcMemory);
        hdcMemory = 0;
    }

    if(hfontLB)
    {
        SetWindowFont(hwndLB, GetStockObject(SYSTEM_FONT), FALSE);
        DeleteObject(hfontLB);
        hfontLB = 0;
    }
}

/*
 -  SetLBFont
 -
 *  Purpose:
 *      creates a font from the global fontStyle
 *      sets global hfontLB to new font and WM_SETFONTs
 *      the hwndLB to the new font
 */

VOID SetLBFont(VOID)
{
    LOGFONT lf;

    lf.lfHeight = fontStyle.lfHeight;
    lf.lfWidth = 0;
    lf.lfEscapement = 0;
    lf.lfOrientation = 0;
    lf.lfWeight = fontStyle.lfWeight;
    lf.lfItalic = fontStyle.lfItalic;
    lf.lfUnderline = 0;
    lf.lfStrikeOut = 0;
    lf.lfCharSet = ANSI_CHARSET;
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf.lfQuality = DEFAULT_QUALITY;
    lf.lfPitchAndFamily = DEFAULT_PITCH | FF_SWISS;
    lstrcpy(lf.lfFaceName, fontStyle.lfFaceName);

    hfontLB = CreateFontIndirect(&lf);
    if(hfontLB)
        SetWindowFont(hwndLB, hfontLB, FALSE);
}

/*
 -  InitBmps
 -
 *  Purpose:
 *      inits listbox globals, creates listbox
 *
 *  Arguments:
 *      HWND    main hwnd of app (parent of LB)
 *
 *  Returns:
 *      TRUE - success; FALSE - failed
 */

BOOL InitBmps(HWND hwnd, int idLB)
{
    HDC     hdcScreen;
    HBITMAP hbmpTemp;

    hdcScreen = GetDC(0);
    if(!hdcScreen)
        goto CantInit;
    hdcMemory = CreateCompatibleDC(hdcScreen);
    if(!hdcMemory)
        goto ReleaseScreenDC;

    hbmpTemp = CreateCompatibleBitmap(hdcMemory, 1, 1);
    if(!hbmpTemp)
        goto ReleaseMemDC;
    hbmpOrigMemBmp = SelectObject(hdcMemory, hbmpTemp); // get hbmp of NULL
    if(!hbmpOrigMemBmp)                                 // bmp for hdcMemory
        goto ReleaseMemDC;                              // for when we delete
    SelectObject(hdcMemory, hbmpOrigMemBmp);            // it later in life
    DeleteObject(hbmpTemp);
    ReleaseDC(0, hdcScreen);

    SetRGBValues();     // set the global RGB values
    LoadBitmapLB();     // load the bmps into hdcMemory

    hwndLB = GetDlgItem(hwnd, idLB);

    SetLBFont();    // set the font of our listbox
    return TRUE;

/* Error recovery exits */
ReleaseMemDC:
    DeleteDC(hdcMemory);
    hdcMemory = 0;

ReleaseScreenDC:
    ReleaseDC(0, hdcScreen);

CantInit:
    return FALSE;
}

/*
 -  SetRGBValues
 -
 *  Purpose:
 *      To set various system colors in static variables.  Called at
 *      init time and when system colors change.
 */

VOID SetRGBValues(VOID)
{
    rgbWindowColor = GetSysColor(COLOR_WINDOW);
    rgbHiliteColor = GetSysColor(COLOR_HIGHLIGHT);
    rgbWindowText  = GetSysColor(COLOR_WINDOWTEXT);
    rgbHiliteText  = GetSysColor(COLOR_HIGHLIGHTTEXT);
    rgbGrayText    = GetSysColor(COLOR_GRAYTEXT);
}

/*
 -  MeasureItem
 -
 *  Purpose:
 *      called from msg WM_MEASUREITEM: returns max dy of listbox items
 *
 *  Arguments:
 *      HWND        hwnd of main window
 *      pmis        measureitemstruct from WM_MEASUREITEM call
 */

VOID MeasureItem(HANDLE hwnd, LPMEASUREITEMSTRUCT pmis)
{
    HDC        hDC = GetDC(hwnd);
    HANDLE     hFont = hfontLB;
    TEXTMETRIC TM;

    if(!hFont)
        hFont = GetStockObject(SYSTEM_FONT);
    hFont = SelectObject(hDC, hFont);
    GetTextMetrics(hDC, &TM);
    SelectObject(hDC, hFont);
    ReleaseDC(hwnd, hDC);

    // set the height to be max of (dyfont or dybitmap)
    pmis->itemHeight = max(dybmpLB, TM.tmHeight);
}

/*
 -  OutTextFormat
 -
 *  Purpose:
 *      to parse the string in the listbox and draw it accordingly:
 *      first char == chBOLD: line is bold
 *      first char == chUNDERLINE: line is underlined (can follow chBOLD)
 *      char == chTAB: go to next column in rgTabs
 *      '/001#': bitblt that numbered bitmap.
 *      otherwise, outtext the line
 *
 *  Arguments:
 *      pDI     from DrawItem from WM_DRAWITEM msg
 */

VOID OutTextFormat(LPDRAWITEMSTRUCT pDI)
{
    TCHAR   szDateRec[32];
    TCHAR   szItem[256];
    TCHAR   szTemp[4];
    TCHAR   szDots[4] = {"..."};
    TCHAR   *pch;
    INT     nT;
    INT     nTab = 0;           // current tab we is on
    INT     nBmp;               // index of envelope bitmap
    HFONT   hfDef = 0;
    HFONT   hfOld = 0;          // bold or underlined font
    TCHAR   *pchBuff = NULL;
    LPMSGID lpMsgId = (LPMSGID)pDI->itemData;

    pch = szItem;

    // Format a string from the info in lpMsgNode
    // First, calculate the index to the desired bitmap

    nBmp = ((!lpMsgId->fUnRead) * 2) + ((!!lpMsgId->fHasAttach) * 1 );

    // Convert our received date and build string

    ConvertDateRec (lpMsgId->lpszDateRec, szDateRec);

    // Limit our subject size

    szTemp[0] = '\0';

    if(lpMsgId->lpszSubject && (lstrlen(lpMsgId->lpszSubject) > 32))
    {
        memcpy(szTemp, &lpMsgId->lpszSubject[28], 4);
        memcpy(&lpMsgId->lpszSubject[28], szDots, 4);
    }

    wsprintf(szItem, "\001%d\t%s\t%s\t%s", nBmp,
            (lpMsgId->lpszFrom ? lpMsgId->lpszFrom : ""),
            (lpMsgId->lpszSubject ? lpMsgId->lpszSubject : ""),
            szDateRec);

    // erase background
    ExtTextOut(pDI->hDC, 0, 0, ETO_OPAQUE, &pDI->rcItem, NULL, 0, NULL);

    // underline or bold this line?  Only check first & second char
    if(*pch == chBOLD || *pch == chUNDERLINE)
    {
        LOGFONT     lf;

        hfOld = GetWindowFont(pDI->hwndItem);
        if(!hfOld)
            hfOld = GetStockObject(SYSTEM_FONT);
        GetObject(hfOld, sizeof(lf), &lf);

        if(*pch == chBOLD)
        {
            lf.lfWeight = FW_BOLD;
            pch++;
        }
        if(*pch == chUNDERLINE)
        {
            lf.lfUnderline = TRUE;
            pch++;
        }

        hfDef = CreateFontIndirect(&lf);
        if(hfDef)
            SelectObject(pDI->hDC, hfDef);
    }

    // selected or nonselected bmps?
    nT = (ODS_SELECTED & pDI->itemState) ? (BMWIDTH * NUMBMPS) : 0;

    // parse the string
    for(; *pch; pch++)
    {
        TCHAR   *pchT;
        RECT    rc;

        if(*pch == chBITMAP)     // do we have a bitmap?
        {
            ++pch;
            // draw the bitmap
            BitBlt(pDI->hDC, pDI->rcItem.left + rgTabs[nTab],
                pDI->rcItem.top, BMWIDTH, BMHEIGHT, hdcMemory,
                nT + (int)(*pch - TEXT('0')) * BMWIDTH, 0, SRCCOPY);
            continue;
        }

        if(*pch == chTAB)    // move to next tabstop?
        {
            nTab++;
            continue;
        }

        pchT = pch;     // find end of the column of text
        while(*pchT && (*pchT != chTAB))
            pchT++;

        // set rect to drawtext in
        SetRect(&rc, pDI->rcItem.left + rgTabs[nTab], pDI->rcItem.top,
            pDI->rcItem.right, pDI->rcItem.bottom);

        // draw the text
        ExtTextOut(pDI->hDC, rc.left, rc.top + 1, ETO_OPAQUE | ETO_CLIPPED,
            &rc, pch, pchT - pch, NULL);
        pch = pchT - 1; // move to end of this column
    }

    if(hfDef)   // delete underline or bold font if we created it
    {
        SelectObject(pDI->hDC, hfOld);
        DeleteObject(hfDef);
    }

    if(szTemp[0] != '\0')
    {
        memcpy(&lpMsgId->lpszSubject[28], szTemp, 4);
    }
}

/*
 -  DrawItem
 -
 *  Purpose:
 *      Handles WM_DRAWITEM for both drive and directory listboxes.
 *
 *  Parameters:
 *      pDI     LPDRAWITEMSTRUCT passed from the WM_DRAWITEM message.
 */

VOID DrawItem(LPDRAWITEMSTRUCT pDI)
{
    COLORREF    crText, crBack;

    if((int)pDI->itemID < 0)
        return;

    if((ODA_DRAWENTIRE | ODA_SELECT) & pDI->itemAction)
    {
        if(pDI->itemState & ODS_SELECTED)
        {
            // Select the appropriate text colors
            crText = SetTextColor(pDI->hDC, rgbHiliteText);
            crBack = SetBkColor(pDI->hDC, rgbHiliteColor);
        }

        // parse and spit out bmps and text
        OutTextFormat(pDI);

        // Restore original colors if we changed them above.
        if(pDI->itemState & ODS_SELECTED)
        {
            SetTextColor(pDI->hDC, crText);
            SetBkColor(pDI->hDC,   crBack);
        }
    }

    if((ODA_FOCUS & pDI->itemAction) || (ODS_FOCUS & pDI->itemState))
        DrawFocusRect(pDI->hDC, &pDI->rcItem);
}

/*
 -  ConvertDateRec
 -
 *  Purpose:
 *      To convert the lpszDateReceived field of a message to a
 *      more paletable display format; namely: mm/dd/yy hh:mmAM.
 *
 *  Parameters:
 *      lpszDateRec         - Original format
 *      lpszDateDisplay     - Display format
 */

VOID ConvertDateRec (LPSTR lpszDateRec, LPSTR lpszDateDisplay)
{
    char  szDateTmp[32];
    LPSTR lpszYear;
    LPSTR lpszMonth;
    LPSTR lpszDay;
    LPSTR lpszHour;
    LPSTR lpszMinute;
    int nHour;
    static char szFoo[2][3] =
    {"AM", "PM"};

    *lpszDateDisplay = 0;
    if (!lpszDateRec || !*lpszDateRec)
        return;

    lstrcpy(szDateTmp, lpszDateRec);

    lpszYear = strtok (szDateTmp, "/ :");
    lpszMonth = strtok (NULL, "/ :");
    lpszDay = strtok (NULL, "/ :");
    lpszHour = strtok (NULL, "/ :");
    lpszMinute = strtok (NULL, "/ :");

    if(lpszHour)
        nHour = atoi (lpszHour);
    else
        nHour = 0;

    if (nHour > 12)
        wsprintf (lpszHour, "%d", nHour - 12);

    wsprintf (lpszDateDisplay, "%s/%s/%s %s:%s%s", lpszMonth,
        (lpszDay ? lpszDay : ""),
        (lpszYear ? lpszYear : ""),
        (lpszHour ? lpszHour : ""),
        (lpszMinute ? lpszMinute : ""),
        szFoo[(nHour > 11 ? 1 : 0)]);
}

/*
 *  RgbInvertRgb
 *
 *  Purpose:
 *      To reverse the byte order of the RGB value (for file format
 *
 *  Arguments:
 *
 *  Returns:
 *      New color value (RGB to BGR)
 */

#define RgbInvertRgb(_rgbOld) \
    (DWORD)RGB(GetBValue(_rgbOld), GetGValue(_rgbOld), GetRValue(_rgbOld))

/*
 *  LoadAlterBitmap (mostly stolen from commdlg)
 *
 *  Purpose:
 *      Loads the IDB_ENVELOPE bitmap and gives all the pixels that are
 *      RGBREPLACE a new color.
 *
 *  Assumption:
 *      This function will work on one bitmap during it's lifetime.
 *      (Due to the fact that it finds RGBREPLACE once and then
 *      operates on that offset whenever called again because under NT,
 *      it appears that the bitmap is cached, so the second time you go
 *      looking for RGBREPLACE, it won't be found.) You could load the
 *      resource, copy it, then modify the copy as a workaround. But I
 *      chose the cheap way out as I will only ever modify one bmp.
 *
 *  Arguments:
 *      rgbInstead  rgb value to replace defined RGBREPLACE with
 *
 *  Returns:
 *      NULL - failed or hbmp of new modified bitmap
 */

HBITMAP LoadAlterBitmap(DWORD rgbInstead)
{
    HANDLE              hbmp = 0;
    LPBITMAPINFOHEADER  qbihInfo;
    HDC                 hdcScreen;
    HRSRC               hresLoad;
    HGLOBAL             hres;
    LPBYTE              qbBits;
    DWORD               rgbReplace = 0;
    DWORD               *rgdw = NULL;
    DWORD               *lpdw = NULL;
    ULONG               cb = 0;

    if (rgbInstead)
        rgbReplace = RGBREPLACE;

    // load our listbox bmps resource
    hresLoad = FindResource(hInst, MAKEINTRESOURCE(IDB_ENVELOPE), RT_BITMAP);
    if(hresLoad == 0)
        return 0;
    hres = LoadResource(hInst, hresLoad);
    if(hres == 0)
        return 0;

    rgbReplace = RgbInvertRgb(rgbReplace);
    rgbInstead = RgbInvertRgb(rgbInstead);
    qbihInfo = (LPBITMAPINFOHEADER)LockResource(hres);

    // Skip over the header structure
    qbBits = (LPBYTE)(qbihInfo + 1);

    // Skip the color table entries, if any
    qbBits += (1 << (qbihInfo->biBitCount)) * sizeof(RGBQUAD);

    // Copy the resource into writable memory so we can
    // munge the color table to set our background color
    cb = (ULONG)(qbBits - (LPBYTE)qbihInfo) + qbihInfo->biSizeImage;
    rgdw = (DWORD *)GlobalAllocPtr(GMEM_MOVEABLE, cb);

    CopyMemory((LPVOID)rgdw, (LPVOID)qbihInfo, cb);

    // find the color to replace in the color table
    for(lpdw = (DWORD *)((LPBYTE)rgdw + qbihInfo->biSize); ; lpdw++)
    {
        if(*lpdw == rgbReplace)
            break;
    }

    // replace that color value with our new one
    *lpdw = (DWORD)rgbInstead;

    // Create a color bitmap compatible with the display device
    hdcScreen = GetDC(0);
    if(hdcScreen != 0)
    {
        hbmp = CreateDIBitmap(hdcScreen, (LPBITMAPINFOHEADER)rgdw,
                (LONG)CBM_INIT, qbBits, (LPBITMAPINFO) rgdw, DIB_RGB_COLORS);
        ReleaseDC(0, hdcScreen);
    }

    UnlockResource(hres);
    FreeResource(hres);

    GlobalFreePtr(rgdw);

    return hbmp;
}

/*
 *  DeleteBitmapLB
 *
 *  Purpose:
 *      Get rid of hbmpLB, if it exists
 */

VOID DeleteBitmapLB(VOID)
{
    if(hbmpOrigMemBmp)
    {
        SelectObject(hdcMemory, hbmpOrigMemBmp);
        if(hbmpLB != 0)
        {
            DeleteObject(hbmpLB);
            hbmpLB = 0;
        }
    }
}

/*
 *  LoadBitmapLB (mostly stolen from commdlg)
 *
 *  Purpose:
 *      Creates the listbox bitmap. If an appropriate bitmap
 *      already exists, it just returns immediately.  Otherwise, it
 *      loads the bitmap and creates a larger bitmap with both regular
 *      and highlight colors.
 *
 *  Returns:
 *      TRUE - success; FALSE - failure
 */

BOOL LoadBitmapLB(VOID)
{
    BITMAP  bmp;
    HANDLE  hbmp, hbmpOrig;
    HDC     hdcTemp;
    BOOL    bWorked = FALSE;

    // check for existing bitmap and validity
    if( (hbmpLB != 0) &&
        (rgbWindowColor == rgbDDWindow) &&
        (rgbHiliteColor == rgbDDHilite))
    {
        if(SelectObject(hdcMemory, hbmpLB))
            return TRUE;
    }

    DeleteBitmapLB();

    rgbDDWindow = rgbWindowColor;
    rgbDDHilite = rgbHiliteColor;

    if(!(hdcTemp = CreateCompatibleDC(hdcMemory)))
        goto LoadExit;

    if(!(hbmp = LoadAlterBitmap(rgbWindowColor)))
        goto DeleteTempDC;

    GetObject(hbmp, sizeof(BITMAP), (LPBYTE) &bmp);
    dybmpLB = bmp.bmHeight;
    dxbmpLB = bmp.bmWidth;

    hbmpOrig = SelectObject(hdcTemp, hbmp);

    hbmpLB = CreateDiscardableBitmap(hdcTemp, dxbmpLB*2, dybmpLB);
    if(!hbmpLB)
        goto DeleteTempBmp;

    if(!SelectObject(hdcMemory, hbmpLB))
    {
        DeleteBitmapLB();
        goto DeleteTempBmp;
    }

    BitBlt(hdcMemory, 0, 0, dxbmpLB, dybmpLB,   // copy unhighlited bmps
           hdcTemp, 0, 0, SRCCOPY);             // into hdcMemory
    SelectObject(hdcTemp, hbmpOrig);

    DeleteObject(hbmp);

    if(!(hbmp = LoadAlterBitmap(rgbHiliteColor)))
        goto DeleteTempDC;

    hbmpOrig = SelectObject(hdcTemp, hbmp);
    BitBlt(hdcMemory, dxbmpLB, 0, dxbmpLB, dybmpLB, // copy highlited bmps
        hdcTemp, 0, 0, SRCCOPY);                    // into hdcMemory
    SelectObject(hdcTemp, hbmpOrig);

    bWorked = TRUE;

DeleteTempBmp:
    DeleteObject(hbmp);
DeleteTempDC:
    DeleteDC(hdcTemp);
LoadExit:
    return bWorked;
}


ROUTE.C

/*
 -  ROUTE.C
 *
 *
 *      Functions used to implement routing.
 *
 *      To create a new routing message call:
 *         DialogBoxParam(hInst, "RouteNote", hWnd, RouteNoteDlgProc, (LPARAM) NULL );
 *      To route a routed message to the next recipient call:
 *          DialogBoxParam(hInst, "RouteNote", hWnd, RouteNoteDlgProc, (LPARAM) pmsg );
 *              where pmsg is a pointer to the message you want to route.
 *      To find out if a message was routed by this program call:
 *          HasRougingSlip();
 *
 *
 *      To use this functions in your own program change the line (in route.h):
 *          #define lpszSmplRTMsgClass "IPM.Note.SampleRoutingForm"
 *      to reflect the name of your own class for routing messages.
 *
 *
 *  Copyright 1986-1996, Microsoft Corporation. All Rights Reserved.
 */

#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <windowsx.h>
#ifdef _WIN32
#include <objerror.h>
#include <objbase.h>
#endif
#ifdef WIN16
#include <compobj.h>
#include <commdlg.h>
#endif
#include <mapiutil.h>
#include <mapix.h>
#include <mapiwin.h>
#include <mapidbg.h>
#include <cindex.h>

#ifdef WIN16
#define GWL_USERDATA    DWL_USER
#endif

/*
 To use a GUID XXX in a program you have to initialize it in exactly one
obj file of your project.
Line "#define USES_XXX" tells the compiler that you are going to use GUID XXX
in this file.
Lines "#define INITGUID"  and "#include<initguid.h>"
tell the compiler to init GUIDs in this file.*/

/* the GUID we'll be using */
#define USES_PS_ROUTING_EMAIL_ADDRESSES 1
#define USES_PS_ROUTING_ADDRTYPE 1
#define USES_PS_ROUTING_DISPLAY_NAME 1
#define USES_PS_ROUTING_ENTRYID 1
#define USES_PS_ROUTING_SEARCH_KEY 1
#define USES_PS_PUBLIC_STRINGS  1
#define USES_IID_IMessage 1
#define USES_IID_IMAPIStatus 1
#define USES_IID_IMAPIForm 1
/*initialize the GUIDs in this file*/
#define INITGUID 1
#include <initguid.h>
#include <mapiguid.h>
#include <pdkver.h>

#include "client.h"
#include "bitmap.h"
#include "route.h"

/* Routing Data. Used in RouteNoteDlgProc that handles composing/sending/reading of
routing messages */
typedef struct _ROUTEDATA
{
    LPADRLIST palAddrListOld;
    LPADRLIST palAddrListActive;
    ULONG nCurrentRouteRecip;
    ULONG nTotalRouteRecip;
    LPMAPITABLE ptblAttach;
    BOOL bNewMessage;
    LPMESSAGE pmsgRouteMsg;
    ULONG tagCurrent;
    ULONG tagTotal;
    ULONG cbConvIdx;
    LPBYTE lpbConvIdx;
} ROUTEDATA, FAR * LPROUTEDATA;

BOOL MakeNewRouteMessage(LPMESSAGE pmsgRead, LPROUTEDATA FAR * ppRouteData);
BOOL SetRouteProps(LPROUTEDATA pRouteData);
BOOL DelRouteProps(LPROUTEDATA pRouteData);
BOOL GetRoutePropTagArray(ULONG cb, LPMESSAGE lpM, LPSPropTagArray FAR * lppspta);
BOOL GetRoutePropTagArrayFast(ULONG nTotalRecip, LPMESSAGE lpM, BOOL fCreate,
                                LPSPropTagArray FAR * lppspta);
BOOL GetRouteIndices(LPROUTEDATA pRouteData);
BOOL SetRouteIndices(LPROUTEDATA pRouteData);
BOOL CreateOutMessage(LPMESSAGE FAR * lpmSrcMsgI);
BOOL GetRouteAddrLists(LPROUTEDATA pRouteData);
UINT FirstRecipient(LPADRLIST lpAL);
BOOL SetMessageClass(LPMESSAGE lpM, LPSTR lpszClass);
BOOL HasAttachment(LPMESSAGE lpM);
BOOL PopulateAttachList(HWND hDlg, LPROUTEDATA pRouteData);
BOOL CreateNewAttachment(HWND hDlg);
void SaveAttachment( HWND hDlg, UINT indx);
void RT_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
BOOL RT_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam);
BOOL RBox_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam);
void RBox_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
void DeInitRouteData(LPROUTEDATA pRouteData);
BOOL CALLBACK RouteBoxDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
void InitRouteNameIDArray(ULONG cb, LPMAPINAMEID lpPropNames);
void PopulateRouteListBox(HWND hDlg);
void ConfigMoveButtons(HWND hDlg, HWND hLB);

typedef enum _ROUTEPROPSETS
{
    ROUTEPROPSET_EMAIL_ADDRESS = 0,
    ROUTEPROPSET_ADDRTYPE,
    ROUTEPROPSET_DISPLAY_NAME,
    ROUTEPROPSET_ENTRYID,
    ROUTEPROPSET_SEARCH_KEY,
    ROUTEPROPSETDIM     /* used for dimension*/
} ROUTEPROPSETS;

LPGUID  lpguidA[ROUTEPROPSETDIM]  =
{
    (LPGUID)&PS_ROUTING_EMAIL_ADDRESSES,
    (LPGUID)&PS_ROUTING_ADDRTYPE,
    (LPGUID)&PS_ROUTING_DISPLAY_NAME,
    (LPGUID)&PS_ROUTING_ENTRYID,
    (LPGUID)&PS_ROUTING_SEARCH_KEY
};

ULONG  ulRoutePropTypes [ROUTEPROPSETDIM] =
{
    PT_STRING8,
    PT_STRING8,
    PT_STRING8,
    PT_BINARY,
    PT_BINARY
};

/*  used for PrepareRecips call*/
SizedSPropTagArray(ROUTEPROPSETDIM, sptRouteProps) =
{
    ROUTEPROPSETDIM,
    {
        PR_EMAIL_ADDRESS,
        PR_ADDRTYPE,
        PR_DISPLAY_NAME,
        PR_ENTRYID,
        PR_SEARCH_KEY
    }
};

enum {EMSG_SUBJ = 0, EMSG_BODY, EMSG_MSGFLAGS, EMSG_CONVIDX, EMSGPROPDIM};
SizedSPropTagArray( EMSGPROPDIM, tagaMsgProps) =
{
    EMSGPROPDIM,
    {PR_SUBJECT, PR_BODY, PR_MESSAGE_FLAGS, PR_CONVERSATION_INDEX}
};
/*
//
// the number of entries could vary from
// EXCLUDED_PROPS_ON_REPLY to EXCLUDED_PROPS_ON_REPLY - 1 and vice versa,
// depending if the message is being reply or fowarded. If forwarded, the
// PR_MESSAGE_ATTACHMENTS property is included in the forwarded message
// otherwise it is excluded
*/
#define EXCLUDED_PROPS_ON_REPLY     29
SizedSPropTagArray (EXCLUDED_PROPS_ON_REPLY, sptExcludedProps) =
{
    EXCLUDED_PROPS_ON_REPLY,
    {
        PR_SENDER_NAME,
        PR_SENDER_ENTRYID,
        PR_SENDER_SEARCH_KEY,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENDER_ADDRTYPE,

        PR_RECEIVED_BY_NAME,
        PR_RECEIVED_BY_ENTRYID,
        PR_RECEIVED_BY_SEARCH_KEY,

        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_ENTRYID,
        PR_SENT_REPRESENTING_SEARCH_KEY,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_ADDRTYPE,

        PR_RCVD_REPRESENTING_NAME,
        PR_RCVD_REPRESENTING_ENTRYID,
        PR_RCVD_REPRESENTING_SEARCH_KEY,

        PR_MESSAGE_FLAGS,
        PR_MESSAGE_RECIPIENTS,

        PR_READ_RECEIPT_ENTRYID,
        PR_REPORT_ENTRYID,

        PR_REPLY_RECIPIENT_ENTRIES,
        PR_REPLY_RECIPIENT_NAMES,

        PR_PARENT_KEY,

        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,

        PR_READ_RECEIPT_REQUESTED,

        PR_CLIENT_SUBMIT_TIME,
        PR_MESSAGE_DELIVERY_TIME,
        PR_MESSAGE_DOWNLOAD_TIME,

//        PR_SUBJECT_PREFIX,
        PR_MESSAGE_ATTACHMENTS
    }
};

LPADRBOOK
OpenAddressBook(HWND hwnd)
{
    HRESULT hr;
    LPADRBOOK pabAddrBook = NULL;

    Assert(pses);
    hr = pses->lpVtbl->OpenAddressBook(pses, (ULONG) hwnd, NULL, 0, &pabAddrBook);
    if(HR_FAILED(hr))
    {
        MakeMessageBox(hwnd, GetScode(hr),IDS_OPENAB, NULL, MBS_ERROR);
        return NULL;
    }
    if(hr) /*if we have a warning*/
    {
        LPMAPIERROR perr = NULL;

        pses->lpVtbl->GetLastError(pses, hr, 0, &perr);
        MakeMessageBox(hwnd, GetScode(hr), IDS_OPENABWARN, perr, MBS_ERROR);
        MAPIFreeBuffer(perr);
    }

    return pabAddrBook;
}

/*
 *  Handles the RouteSlipbox
 *
 *  Extracts all necessary data from GWL_USERDATA window long of
 *  its parent which is RouteNote dialog.
 */
BOOL CALLBACK
RouteBoxDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch (msg)
    {
    case WM_INITDIALOG:
        PopulateRouteListBox(hDlg);
        return TRUE;

    HANDLE_MSG(hDlg, WM_COMMAND, RBox_OnCommand);
    }

    return FALSE;
}

void RBox_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
{
    ADRPARM AdrParm = { 0 };
    ULONG ulrectps = MAPI_TO;
    LPSTR  lpszTitles = "Route To ";
    UINT nIndex, nIndex1;
    LPSPropValue lpspv = NULL;
    LPROUTEDATA pRouteData = NULL;
    HRESULT hr;

    switch (id)
    {
    case IDC_ADDRLISTACTIVE:
    case IDC_ADDRLISTOLD:
            /*This is to disallow hitting "Remove" button when
            there is something selected in the old address list*/
        if(codeNotify == LBN_SETFOCUS)
        {
            if(id == IDC_ADDRLISTOLD)
            {
                EnableWindow(GetDlgItem(hDlg, IDC_REMOVEADDR), FALSE);
                EnableWindow(GetDlgItem(hDlg, IDC_MOVEUP), FALSE);
                EnableWindow(GetDlgItem(hDlg, IDC_MOVEDOWN), FALSE);
                ListBox_SetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), -1);

            }

            else /*must be IDC_ADDRLISTACTIVE*/
            {
                EnableWindow(GetDlgItem(hDlg, IDC_REMOVEADDR), TRUE);
                ListBox_SetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTOLD), -1);
            }
        }

        if(IDC_ADDRLISTACTIVE == id && LBN_SELCHANGE == codeNotify)
        {
            ConfigMoveButtons(hDlg, hwndCtl);
        }

        /* Details */
        if(codeNotify == LBN_DBLCLK)
        {
            LPSPropValue pvalDtls = NULL;
            ULONG cVals = 0;

            nIndex = (UINT)ListBox_GetCurSel(hwndCtl);
            if (nIndex == LB_ERR)
                break;
            nIndex1 = (UINT)ListBox_GetItemData(hwndCtl, nIndex);
            if(nIndex1 == LB_ERR)
            {
                DebugTrace("Client: error retrieving listbox item data");
                break;
            }

            pRouteData = (LPROUTEDATA) GetWindowLong(GetParent(hDlg), GWL_USERDATA);
            Assert(pRouteData);

            if(IDC_ADDRLISTACTIVE == id)
            {
                Assert(nIndex1 < pRouteData->palAddrListActive->cEntries);
                pvalDtls = pRouteData->palAddrListActive->aEntries[nIndex1].rgPropVals;
                cVals = pRouteData->palAddrListActive->aEntries[nIndex1].cValues;
            }
            else if(IDC_ADDRLISTOLD == id)
            {
                Assert(nIndex1 < pRouteData->palAddrListOld->cEntries);
                pvalDtls = pRouteData->palAddrListOld->aEntries[nIndex1].rgPropVals;
                cVals = pRouteData->palAddrListOld->aEntries[nIndex1].cValues;
            }
            else
                Assert(FALSE);

            Assert(pvalDtls);
            pvalDtls = PpropFindProp(pvalDtls, cVals, PR_ENTRYID);

            Assert(pvalDtls);
            if(pvalDtls)
            {
                hr = pabAddrB->lpVtbl->Details(pabAddrB, (LPULONG) &hDlg, NULL,
                                        NULL, pvalDtls->Value.bin.cb,
                                        (LPENTRYID)pvalDtls->Value.bin.lpb, NULL,
                                        NULL, NULL, DIALOG_MODAL);
            }
        }
        return;

    case IDC_MOVEDOWN:
    case IDC_MOVEUP:
        {
            int nInd, nIndNew;
            int nIndData, nIndDataNew;
#ifdef DEBUG
            int nTotal;
#endif
            char szStr[256];
            ADRENTRY ae = {0};
            HWND hLB = GetDlgItem(hDlg, IDC_ADDRLISTACTIVE);

            Assert(hLB);

            nInd = ListBox_GetCurSel(hLB);
            if (nInd == LB_ERR)
                break;
#ifdef DEBUG
            Assert(nInd != 0 || id != IDC_MOVEUP);

            nTotal = ListBox_GetCount(hLB);
            if(nTotal != LB_ERR)
                Assert(nInd != nTotal-1 || id != IDC_MOVEDOWN);
#endif

            if(IDC_MOVEDOWN == id)
                nIndNew = nInd + 1;
            else
                nIndNew = nInd - 1;

            nIndData = ListBox_GetItemData(hLB, nInd);
            if(nIndData == LB_ERR)
            {
                DebugTrace("Client: error retrieving listbox item data");
                break;
            }

            nIndDataNew = ListBox_GetItemData(hLB, nIndNew);
            if(nIndDataNew == LB_ERR)
            {
                DebugTrace("Client: error retrieving listbox item data");
                break;
            }

            pRouteData = (LPROUTEDATA) GetWindowLong(GetParent(hDlg), GWL_USERDATA);
            Assert(pRouteData);

            ae = pRouteData->palAddrListActive->aEntries[nIndData];
            pRouteData->palAddrListActive->aEntries[nIndData] =
            pRouteData->palAddrListActive->aEntries[nIndDataNew];
            pRouteData->palAddrListActive->aEntries[nIndDataNew] = ae;

            /*PopulateRouteListBox(hDlg);*/

            /*this works only because the two strings we swap are adjacent*/
            ListBox_GetText(hLB, nInd, szStr);
            ListBox_DeleteString(hLB, nInd);
            ListBox_InsertString(hLB, nIndNew, szStr);

            ListBox_SetItemData(hLB, nIndNew, nIndDataNew);
            ListBox_SetItemData(hLB, nInd, nIndData);

            ListBox_SetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), nIndNew);
            ConfigMoveButtons(hDlg, GetDlgItem(hDlg, IDC_ADDRLISTACTIVE));
            break;
        }

    case IDC_ADDADDR:
        Assert(pabAddrB != NULL);
        memset(&AdrParm, 0, sizeof(AdrParm));
        AdrParm.ulFlags = AB_SELECTONLY | DIALOG_MODAL;
        AdrParm.lpszCaption = "Routing Slip";
        AdrParm.lpszNewEntryTitle = "NEW ENTRY";
        AdrParm.cDestFields = 1;
        AdrParm.nDestFieldFocus = 0;
        AdrParm.lppszDestTitles = &lpszTitles;
        AdrParm.lpulDestComps =  &ulrectps ;

        pRouteData = (LPROUTEDATA) GetWindowLong(GetParent(hDlg), GWL_USERDATA);
        Assert(pRouteData);

        hr = pabAddrB->lpVtbl->Address(pabAddrB, (LPULONG) &hDlg, &AdrParm, &pRouteData->palAddrListActive);

        if(GetScode(hr)!= MAPI_E_USER_CANCEL)
        {
            PopulateRouteListBox(hDlg);
            SetWindowText(GetDlgItem(hDlg, IDCANCEL), "Close");
            SetFocus(GetDlgItem(hDlg, IDCANCEL));

            ListBox_SetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), 0);
            ConfigMoveButtons(hDlg, GetDlgItem(hDlg, IDC_ADDRLISTACTIVE));

        }
        return;

    case IDC_REMOVEADDR:
        nIndex = (UINT)ListBox_GetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE));
        if (nIndex == LB_ERR)
            break;

        /*items position in ADRLIST is stored with the item in the listbox*/
        nIndex1 = (UINT)ListBox_GetItemData(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), nIndex);
        if(nIndex1 == LB_ERR)
        {
            DebugTrace("Client: error retrieving listbox item data");
            break;
        }

        pRouteData = (LPROUTEDATA) GetWindowLong(GetParent(hDlg), GWL_USERDATA);
        Assert(pRouteData);

        /* Null out the removed item */
        MAPIFreeBuffer(pRouteData->palAddrListActive->aEntries[nIndex1].rgPropVals);
        pRouteData->palAddrListActive->aEntries[nIndex1].rgPropVals = NULL;
        pRouteData->palAddrListActive->aEntries[nIndex1].cValues = 0;

        ListBox_DeleteString(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), nIndex);

        if(nIndex > 0)
            --nIndex;

        ListBox_SetCurSel(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), nIndex);
        ConfigMoveButtons(hDlg, GetDlgItem(hDlg, IDC_ADDRLISTACTIVE));

        SetWindowText(GetDlgItem(hDlg, IDCANCEL), "Close");
        return;

    case IDCANCEL:

        EndDialog (hDlg, TRUE);
        return;

    default:
        break;
    }
}

void ConfigMoveButtons(HWND hDlg, HWND hLB)
{
    int nIndex;
    int nTotal;

    EnableWindow(GetDlgItem(hDlg, IDC_MOVEUP), FALSE);
    EnableWindow(GetDlgItem(hDlg, IDC_MOVEDOWN), FALSE);

    nIndex = (UINT)ListBox_GetCurSel(hLB);
    if (nIndex == LB_ERR)
        return;

    nTotal = ListBox_GetCount(hLB);
    if(nTotal == LB_ERR)
        return;

    if(nIndex)
        EnableWindow(GetDlgItem(hDlg, IDC_MOVEUP), TRUE);

    if(nIndex < nTotal-1)
        EnableWindow(GetDlgItem(hDlg, IDC_MOVEDOWN), TRUE);
}

/*
 *  Handles the Route Note dialog which is used for both composing
 *  and reading routing messages.
 *
 *  If RotueNoteDlg is called to route an existing msg, a pointer to
 *  the message is passed as a lParam to WM_INITDIALOG.
 *
 */

BOOL CALLBACK
RouteNoteDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    HANDLE_MSG(hDlg, WM_INITDIALOG, RT_OnInitDialog);

    HANDLE_MSG(hDlg, WM_COMMAND, RT_OnCommand);

    default:
        break;
    }
    return FALSE;
}

BOOL RT_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam)
{
    HRESULT hr;
    ULONG cVals = 0;
    LPSPropValue pvalProp = NULL;
    LPSTREAM lpstreamBody = NULL;
    STATSTG statstg = {0};
    LPSTR lpszNoteText = NULL;
    ULONG cb = 0;
    LPMESSAGE pmsgRead = (LPMESSAGE)lParam;
    LPROUTEDATA pRouteData = NULL;
    LPMESSAGE pmsgRoute = NULL;

    if(!MakeNewRouteMessage(pmsgRead, &pRouteData))
    {
        DebugTrace("Client: MakeNewRouteMessage failed (RT_OnInitDialog)");
        goto err;
    }

    if (pmsgRead)
    {
        hr = pmsgRead->lpVtbl->SetReadFlag(pmsgRead, MAPI_DEFERRED_ERRORS);
        /* RouteNote is being called to route  */
        /*  a message in the Inbox.  So, we'll initialize the   */
        /*  form with data from  pmsgRouteMsg */
        pmsgRoute = pRouteData->pmsgRouteMsg;
        Assert(pmsgRoute);

        hr = pmsgRoute->lpVtbl->GetProps(pmsgRoute, (LPSPropTagArray)&tagaMsgProps, 0,
                                                     &cVals, &pvalProp);
        Assert(cVals == EMSGPROPDIM);
        if(HR_SUCCEEDED(hr))
        {
            if(PROP_TYPE(pvalProp[EMSG_SUBJ].ulPropTag) == PT_ERROR)
            {
                DebugTrace("Client: Unable to retrieve subject");
                /*goto err;*/
            }
            else
            {
                SetDlgItemText(hDlg, IDC_RTSUBJECT, pvalProp[EMSG_SUBJ].Value.LPSZ);
                /* //$ */
            }

            if(PR_CONVERSATION_INDEX == pvalProp[EMSG_CONVIDX].ulPropTag)
            {
                LPSPropValue pval = &pvalProp[EMSG_CONVIDX];

                pRouteData->cbConvIdx = pval->Value.bin.cb;
                if(MAPIAllocateBuffer(pRouteData->cbConvIdx, &pRouteData->lpbConvIdx))
                {
                    DebugTrace("Client: MAPIAllocateBuffer failed\r\n");
                    pRouteData->lpbConvIdx = NULL;
                    pRouteData->cbConvIdx = 0;
                }
                else
                {
                    CopyMemory(pRouteData->lpbConvIdx, pval->Value.bin.lpb, pRouteData->cbConvIdx);
                }
            }
            else
            {
                pRouteData->lpbConvIdx = NULL;
                pRouteData->cbConvIdx = 0;
            }

            if(PROP_TYPE(pvalProp[EMSG_BODY].ulPropTag) == PT_ERROR)
            {
                if(GetScode(pvalProp[EMSG_BODY].Value.l) == MAPI_E_NOT_ENOUGH_MEMORY)
                {
                    hr = pmsgRoute->lpVtbl->OpenProperty(pmsgRoute, PR_BODY, &IID_IStream,
                                                STGM_READ, MAPI_DEFERRED_ERRORS,
                                                (LPUNKNOWN FAR *) &lpstreamBody);
                    if(S_OK != GetScode(hr))
                    {
                        DebugTraceResult(OpenProperty, hr);
                        goto err;
                    }
                    hr = lpstreamBody->lpVtbl->Stat(lpstreamBody, &statstg, STATFLAG_NONAME);
                    if(S_OK != GetScode(hr))
                    {
                        DebugTrace("IStream::Stat failed");
                        goto err;
                    }
                    Assert(statstg.cbSize.HighPart == 0);
                    if(MAPIAllocateBuffer(statstg.cbSize.LowPart + 1, (LPVOID FAR *) &lpszNoteText))
                    {
                        goto err;
                    }
                    hr = lpstreamBody->lpVtbl->Read(lpstreamBody, lpszNoteText,
                                                statstg.cbSize.LowPart, &cb);
                    if(S_OK != GetScode(hr))
                    {
                        DebugTrace("IStream::Read failed");
                        goto err;
                    }
                    lpszNoteText[statstg.cbSize.LowPart] = '\0';
                    SetDlgItemText(hDlg, IDC_RTNOTE, lpszNoteText);
                    MAPIFreeBuffer(lpszNoteText);
                    lpszNoteText = NULL;
                    lpstreamBody->lpVtbl->Release(lpstreamBody);
                    lpstreamBody = NULL;
                }
                else /* some other error (too bad) */
                {
                    DebugTrace("Client: error reading body");
                     /*//$goto err;*/
                }
            }
            else /* everything's fine */
            {
                SetDlgItemText(hDlg, IDC_RTNOTE, pvalProp[EMSG_BODY].Value.LPSZ);
            }
        }
        else  /* a real error*/
        {
            DebugTrace("Client: error reading body and subject");
            goto err;
        }

        if(pvalProp[EMSG_MSGFLAGS].ulPropTag == PR_MESSAGE_FLAGS)
        {
            if(pvalProp[EMSG_MSGFLAGS].Value.l & MSGFLAG_HASATTACH)
            {
                PopulateAttachList(hDlg, pRouteData);
            }
        }

        MAPIFreeBuffer(pvalProp);
        pvalProp = NULL;

        Assert(pRouteData->palAddrListOld || pRouteData->palAddrListActive);
    }
    else
    {
    /* if we are here it means we are creating a new message */
        pRouteData->lpbConvIdx = NULL;
        pRouteData->cbConvIdx = 0;
    }

    SetWindowLong(hDlg, GWL_USERDATA, (LONG) pRouteData);
    UlRelease(pmsgRead);
    return TRUE;

err:
    UlRelease(lpstreamBody);
    MAPIFreeBuffer(pvalProp);
    MAPIFreeBuffer(lpszNoteText);
    DeInitRouteData(pRouteData);
    UlRelease(pmsgRead);
    MakeMessageBox(hDlg, 0, IDS_INIDIAG, NULL, MBS_ERROR);
    EndDialog(hDlg, FALSE);
    return TRUE;
}

void RT_OnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
{
    LONG cb, cLines;
    ULONG nAttNum;
    HRESULT hr;
    LPSTR lpszSubject = NULL;
    LPSTR lpszNoteText = NULL;
    /* +1 for SentMailEID; +2 for conversation topic*/
    SPropValue spvProp[EMSGPROPDIM+2] = {0};
    LPSTREAM lpstreamBody = NULL;
    HCURSOR hcOld;
    UINT nFstRecip;
    LPROUTEDATA pRouteData = NULL;
    LPMESSAGE pmsgRoute = NULL;
    LPSPropProblemArray pProblems = NULL;
    ULONG cbNewConvIdx = 0;
    LPBYTE lpbNewConvIdx = NULL;

    switch (id)
        {
        case IDC_RTATTACH:
            if(!CreateNewAttachment(hDlg))
            {
                DebugTrace("CreateNewAttachment failed");
            }
            return;

        case IDC_RTDELATTACH:
            pRouteData = (LPROUTEDATA)GetWindowLong(hDlg, GWL_USERDATA);
            Assert(pRouteData);

            cb = ListBox_GetCurSel(GetDlgItem(hDlg, IDC_RTATTACHLIST));
            if(LB_ERR != cb)
            {
                nAttNum = ListBox_GetItemData(GetDlgItem(hDlg, IDC_RTATTACHLIST), cb);
                if(LB_ERR == nAttNum)
                {
                    DebugTrace("Client:GetItemData failed (routenote)");
                    break;
                }

                hr = pRouteData->pmsgRouteMsg->lpVtbl->DeleteAttach(pRouteData->pmsgRouteMsg, nAttNum, 0,
                                            NULL, 0);
                if(S_OK != GetScode(hr))
                {
                    DebugTrace("Client: DeleteAttach failed");
                    break;
                }
                ListBox_DeleteString(GetDlgItem(hDlg, IDC_RTATTACHLIST), cb);
            }

            break;

        case IDC_RTATTACHLIST:
            cb = ListBox_GetCurSel(hwndCtl);
                if(cb!=LB_ERR)
                {
                    EnableWindow(GetDlgItem(hDlg, IDC_RTSAVEATTACH), TRUE);
                    EnableWindow(GetDlgItem(hDlg, IDC_RTDELATTACH), TRUE);
                }
                else
                {
                    EnableWindow(GetDlgItem(hDlg, IDC_RTSAVEATTACH), FALSE);
                    EnableWindow(GetDlgItem(hDlg, IDC_RTDELATTACH), FALSE);
                }
            if(codeNotify != LBN_DBLCLK)
                break;
            /*fall through*/

        case IDC_RTSAVEATTACH:
            cb = ListBox_GetCurSel(GetDlgItem(hDlg, IDC_RTATTACHLIST));
            if(LB_ERR != cb)
            {
                SaveAttachment(hDlg, (UINT) cb);
            }
            break;

        case IDC_RTEDITROUTESLIP:
            DialogBox (hInst, "RouteSlipBox", hDlg, RouteBoxDlgProc);
            break;

        case IDC_RTROUTE:
            pRouteData = (LPROUTEDATA)GetWindowLong(hDlg, GWL_USERDATA);
            Assert(pRouteData);

            pmsgRoute = pRouteData->pmsgRouteMsg;
            Assert(pmsgRoute);
            if(!pRouteData->palAddrListActive && pRouteData->palAddrListOld)
            {
                MakeMessageBox(hDlg,0, IDS_LASTRECIP, NULL, MB_OK);
                break;
            }
            nFstRecip = FirstRecipient(pRouteData->palAddrListActive);
            if((!pRouteData->palAddrListActive && !pRouteData->palAddrListOld) || !nFstRecip)
            {
                MakeMessageBox(hDlg, 0, IDS_NORECIPS, NULL, MB_OK);
                break;
            }
            hcOld = SetCursor(hWaitCur);
            if (Edit_GetModify(GetDlgItem(hDlg, IDC_RTSUBJECT)))
            {
                cb = Edit_LineLength(GetDlgItem(hDlg, IDC_RTSUBJECT), 0);
                if (MAPIAllocateBuffer (cb + 1, (LPVOID far *) & lpszSubject))
                    goto err;

                GetDlgItemText (hDlg, IDC_RTSUBJECT, lpszSubject, (int)cb+1);
                spvProp[EMSG_SUBJ].ulPropTag = PR_SUBJECT;
                spvProp[EMSG_SUBJ].Value.LPSZ = lpszSubject;

            }
            else
            {
                spvProp[EMSG_SUBJ].ulPropTag = PR_NULL;
            }
            if (pRouteData->bNewMessage || Edit_GetModify(GetDlgItem(hDlg, IDC_RTNOTE)))
            {
                cLines = Edit_GetLineCount(GetDlgItem(hDlg, IDC_RTNOTE));

                if (cLines)
                {
                    /* Get the total number of bytes in the multi-line */

                    cb = Edit_LineIndex(GetDlgItem(hDlg, IDC_RTNOTE),(cLines - 1));
                    cb += Edit_LineLength(GetDlgItem(hDlg, IDC_RTNOTE), cb);

                    /* The next line is to account for CR-LF pairs per line. */

                    cb += cLines * 2;

                    if (MAPIAllocateBuffer (cb + 1, (LPVOID FAR *) & lpszNoteText))
                        goto err;

                    /* Get the Note Text from the edit */

                    GetDlgItemText (hDlg, IDC_RTNOTE, lpszNoteText, (int)cb);
                    spvProp[EMSG_BODY].ulPropTag = PR_BODY;
                    spvProp[EMSG_BODY].Value.LPSZ = lpszNoteText;
                }
            }
            else
            {
                spvProp[EMSG_BODY].ulPropTag = PR_NULL;
            }

            /*We want a copy of the message in the "Sent Mail" folder*/
            CopyMemory(&spvProp[EMSGPROPDIM], pvalSentMailEID, sizeof(SPropValue));

#ifdef DEBUG
            if(pRouteData->bNewMessage)
            {
                Assert(pRouteData->cbConvIdx == 0);
                Assert(pRouteData->lpbConvIdx == NULL);
            }
#endif
            if(!ScAddConversationIndex(pRouteData->cbConvIdx, pRouteData->lpbConvIdx,
                                        &cbNewConvIdx, &lpbNewConvIdx))
            {
                spvProp[EMSG_CONVIDX].ulPropTag = PR_CONVERSATION_INDEX;
                spvProp[EMSG_CONVIDX].Value.bin.cb = cbNewConvIdx;
                spvProp[EMSG_CONVIDX].Value.bin.lpb = lpbNewConvIdx;
            }
            else
            {
                spvProp[EMSG_CONVIDX].ulPropTag = PR_NULL;
            }

            if(pRouteData->bNewMessage)
            {
                spvProp[EMSGPROPDIM+1].ulPropTag = PR_CONVERSATION_TOPIC;
                if(PR_SUBJECT == spvProp[EMSG_SUBJ].ulPropTag)
                {
                    spvProp[EMSGPROPDIM+1].Value.LPSZ = spvProp[EMSG_SUBJ].Value.LPSZ;
                }
                else
                {
                    spvProp[EMSGPROPDIM+1].Value.LPSZ = "";
                }
            }
            else
            {
                spvProp[EMSGPROPDIM+1].ulPropTag = PR_NULL;
            }

            spvProp[EMSG_MSGFLAGS].ulPropTag = PR_NULL;

            hr = pmsgRoute->lpVtbl->SetProps(pmsgRoute, EMSGPROPDIM+2, spvProp, &pProblems);
            MAPIFreeBuffer(lpbNewConvIdx);
            lpbNewConvIdx = NULL;
            if(S_OK != GetScode(hr))
            {
                DebugTrace("Client: SetProps failed (RouteNote)");
                goto err;
            }

else
            {
                if(pProblems)
                {   /* use nAttNum as an index for a sec */
                    for(nAttNum = 0; nAttNum < pProblems->cProblem; ++nAttNum)
                    {
                        switch(pProblems->aProblem[nAttNum].ulPropTag)
                        {
                        case PR_SENTMAIL_ENTRYID:
                            DebugTrace("Client: Error setting PR_SENTMAIL_ENTRYID");
                            goto err;

                        case PR_SUBJECT:
                            DebugTrace("Client: Error settting PR_SUBJECT");
                            goto err;
                        case PR_BODY:
                            if(MAPI_E_NOT_ENOUGH_MEMORY != pProblems->aProblem[nAttNum].scode)
                            {
                                DebugTrace("Client: Error settting PR_BODY");
                                goto err;
                            }
                            else /* have to use IStream */
                            {
                                hr = pmsgRoute->lpVtbl->OpenProperty(pmsgRoute, PR_BODY, &IID_IStream,
                                                STGM_READWRITE, MAPI_DEFERRED_ERRORS,
                                                (LPUNKNOWN FAR *) &lpstreamBody);
                                if(S_OK != GetScode(hr))
                                {
                                    DebugTraceResult(OpenProperty, hr);
                                    goto err;
                                }
                                hr = lpstreamBody->lpVtbl->Write(lpstreamBody, lpszNoteText, lstrlen(lpszNoteText), NULL);
                                if(S_OK != GetScode(hr))
                                {
                                    DebugTrace("IStream::Write failed");
                                    goto err;
                                }
                            }
                            break;
                        }
                    }
                }
            }

            DelRouteProps(pRouteData);
            if(!SetRouteProps(pRouteData))
            {
                DebugTrace("Client: SetRouteProps failed (RouteNote)");
                goto err;
            }

            if(!SetMessageClass(pRouteData->pmsgRouteMsg,  lpszSmplRTMsgClass))
            {
                DebugTrace("Client: SetMessageClass failed");
                goto err;
            }

            cb = pRouteData->palAddrListActive->cEntries;
            /* nFstRecip is the index of the first non NULL entry in the
            palAddrListActive. So we pretend for a sec that palAddrListActive had
            only one recipient to set a "TO" recipient in the pmsgRouteMsg */
            pRouteData->palAddrListActive->cEntries = nFstRecip;
            hr = pRouteData->pmsgRouteMsg->lpVtbl->ModifyRecipients(pRouteData->pmsgRouteMsg,
                                        0, pRouteData->palAddrListActive);
            pRouteData->palAddrListActive->cEntries = cb;
            if(S_OK != GetScode(hr))
            {
                DebugTrace("Client: ModifyRecipients faild (routenote)");
                goto err;
            }

            hr = pRouteData->pmsgRouteMsg->lpVtbl->SubmitMessage(pRouteData->pmsgRouteMsg, 0);
            if(S_OK != GetScode(hr))
            {
                MakeMessageBox(hDlg, GetScode(hr), IDS_SENDERROR, NULL, MBS_ERROR);
                goto err;
            }
            /*fall through*/

err:
        case IDCANCEL:

            if(IDCANCEL == id)
            {
                pRouteData = (LPROUTEDATA)GetWindowLong(hDlg, GWL_USERDATA);
                Assert(pRouteData);
                if(pRouteData->palAddrListActive||
                    Edit_GetModify(GetDlgItem(hDlg, IDC_RTNOTE)) ||
                    Edit_GetModify(GetDlgItem(hDlg, IDC_RTSUBJECT)))
                    {
                        if(MessageBox(hDlg, "Are you sure you want to close this form?",
                        "?", MB_YESNO) == IDNO)  break;
                    }
            }
            else
            {
                SetCursor(hcOld);
            }

            MAPIFreeBuffer (lpszSubject);
            MAPIFreeBuffer (lpszNoteText);
            UlRelease(lpstreamBody);
            lpstreamBody = NULL;
            DeInitRouteData(pRouteData);
            pRouteData = NULL;

            EndDialog (hDlg, TRUE);
            break;

        default:
            break;
        }
}

/*
*   DeInitRouteData
*
*  releases pRouteData and everything that was in it
*/
void DeInitRouteData(LPROUTEDATA pRouteData)
{
    if(!pRouteData) return;

    FreePadrlist(pRouteData->palAddrListOld);
    FreePadrlist(pRouteData->palAddrListActive);
    UlRelease(pRouteData->pmsgRouteMsg);
    UlRelease(pRouteData->ptblAttach);
    MAPIFreeBuffer(pRouteData->lpbConvIdx);
    MAPIFreeBuffer(pRouteData);
}

/* Returns a 1-based index of the first recipent.
 returns 0 if all entries in the input address list are NULLs*/
UINT FirstRecipient(LPADRLIST lpAL)
{
    UINT idx;

    if(!lpAL || !lpAL->cEntries) return 0;

    for(idx = 0; idx < lpAL->cEntries; ++idx)
        if(lpAL->aEntries[idx].rgPropVals) return (idx+1);

    return 0;
}

void PopulateRouteListBox(HWND hDlg)
{
    LPADRENTRY lpadrent;
    LPSPropValue lpsprop;
    LPSTR pStr = NULL;
    LPROUTEDATA pRouteData = NULL;
    UINT iadrentry = 0, ilbitem = 0;

    UINT yOld = MINY, yActive = MINY;
    UINT cOld;
    UINT cActive;
    RECT rect;

    pRouteData = (LPROUTEDATA) GetWindowLong(GetParent(hDlg), GWL_USERDATA);

    Assert(pRouteData);

    if(!pRouteData->palAddrListOld && !pRouteData->palAddrListActive)
        return;

    /* some arithmetic to resize the listboxes.
       Make the listbox that has more entries taller*/
    if(pRouteData->palAddrListOld && pRouteData->palAddrListActive)
    {
        cOld = pRouteData->palAddrListOld->cEntries;
        cActive = pRouteData->palAddrListActive->cEntries;

        yOld = (UINT)LBLENGTH * cOld / (cOld + cActive);
        yActive = (UINT)LBLENGTH - yOld;
    }
    else if(pRouteData->palAddrListOld)
    {
        yActive = 0;
    }
    else
    {
        yOld = 0;
    }
    if(yOld < MINY)
    {
        yOld = MINY;
        yActive = LBLENGTH - MINY;
    }
    else if(yActive < MINY)
    {
        yActive = MINY;
        yOld = LBLENGTH - MINY;
    }

    rect.left = LBX;
    rect.top = OLDY;
    rect.right = DELTAX;
    rect.bottom = yOld;

    MapDialogRect(hDlg, &rect);

    SetWindowPos(GetDlgItem(hDlg, IDC_ADDRLISTOLD), 0,
                rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);

    rect.left = LBX;
    rect.top = OLDY + yOld + DELTA;
    rect.right = DELTAX;
    rect.bottom = yActive;

    MapDialogRect(hDlg, &rect);

    SetWindowPos(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), 0,
                rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);

    ListBox_ResetContent(GetDlgItem(hDlg, IDC_ADDRLISTOLD));
    ListBox_ResetContent(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE));

    if(pRouteData->palAddrListOld)
    {

    for(iadrentry = 0, lpadrent = pRouteData->palAddrListOld->aEntries;
         lpadrent < pRouteData->palAddrListOld->aEntries+pRouteData->palAddrListOld->cEntries;
         ++lpadrent, ++iadrentry)
    {
        if(!lpadrent->rgPropVals) continue;

        /* IAddrBook::Addres doesn't guarantee the order of the props.
            So we have to loop through all of them */
        for(lpsprop = lpadrent->rgPropVals;
            lpsprop < lpadrent->rgPropVals + lpadrent->cValues; ++lpsprop)
        {
            if(lpsprop->ulPropTag ==  PR_DISPLAY_NAME)
            {
                pStr =  lpsprop->Value.LPSZ;
                ilbitem = ListBox_AddString(GetDlgItem(hDlg, IDC_ADDRLISTOLD), pStr);
                if(LB_ERR != ilbitem)
                    ListBox_SetItemData(GetDlgItem(hDlg, IDC_ADDRLISTOLD), ilbitem, iadrentry);
                break;
            }
        }
    }
    }
    if(pRouteData->palAddrListActive)
    {
        for(iadrentry = 0, lpadrent = pRouteData->palAddrListActive->aEntries;
             lpadrent < pRouteData->palAddrListActive->aEntries+pRouteData->palAddrListActive->cEntries;
             ++lpadrent, ++iadrentry)
        {
            if(!lpadrent->rgPropVals) continue;

            /* IAddrBook::Addres doesn't guarantee the order of the props.
            So we have to loop through all of them */
            for(lpsprop = lpadrent->rgPropVals;
                lpsprop < lpadrent->rgPropVals + lpadrent->cValues; ++lpsprop)
            {
                if(lpsprop->ulPropTag ==  PR_DISPLAY_NAME)
                {
                    pStr =  lpsprop->Value.LPSZ;
                    ilbitem = ListBox_AddString(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), pStr);
                    if(LB_ERR != ilbitem)    /* save the position of the entry in the addrlist for future refs*/
                        ListBox_SetItemData(GetDlgItem(hDlg, IDC_ADDRLISTACTIVE), ilbitem, iadrentry);
                    break;
                }
            }
        }
    }
}

/* Get the index of the current recip and total number of recipients
 * from the routing slip of the message
 */
BOOL GetRouteIndices(LPROUTEDATA pRouteData)
{
    HRESULT hr;
    LPSPropTagArray lpsptProp = NULL;
    LPSPropValue lpspv = NULL ;
    ULONG cProps = 0;
    LPMESSAGE pmsg = NULL;
    MAPINAMEID nmidRTPR[2];
    MAPINAMEID *pnmidRTPR[2] = {&nmidRTPR[0], &nmidRTPR[1]};

    nmidRTPR[0].lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
    nmidRTPR[0].ulKind = MNID_STRING;
    nmidRTPR[0].Kind.lpwstrName =   L"CURRENT_RT_RECIP";
    nmidRTPR[1].lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
    nmidRTPR[1].ulKind = MNID_STRING;
    nmidRTPR[1].Kind.lpwstrName =   L"TOTAL_RT_RECIP";

    Assert(pRouteData);

    pmsg = pRouteData->pmsgRouteMsg;
    Assert(pmsg);
    hr = pmsg->lpVtbl->GetIDsFromNames(pmsg, 2, pnmidRTPR, 0, &lpsptProp);
    if(S_OK != GetScode(hr))
    {
        DebugTraceResult(GetIDsFromNames, hr);
        goto err;
    }

    lpsptProp->aulPropTag[0] = PROP_TAG(PT_LONG, PROP_ID(lpsptProp->aulPropTag[0]));
    lpsptProp->aulPropTag[1] = PROP_TAG(PT_LONG, PROP_ID(lpsptProp->aulPropTag[1]));

    /* save for later use in setrouteindices() */
    pRouteData->tagCurrent = lpsptProp->aulPropTag[0];
    pRouteData->tagTotal = lpsptProp->aulPropTag[1];

    hr = pmsg->lpVtbl->GetProps(pmsg, lpsptProp, 0, &cProps, &lpspv);
    if(S_OK != GetScode(hr))
    {
        DebugTraceResult(GetProps, hr);
        goto err;
    }

    Assert(lpspv);
    pRouteData->nCurrentRouteRecip = lpspv[0].Value.l;
    pRouteData->nTotalRouteRecip = lpspv[1].Value.l;

err:
    MAPIFreeBuffer(lpsptProp);
    MAPIFreeBuffer(lpspv);
    return !hr;
}

BOOL SetRouteIndices(LPROUTEDATA pRouteData)
{

    HRESULT hr;
    LPSPropTagArray lpsptProp = NULL;
    SPropValue spvRTProp[2];
    LPSPropProblemArray pProblems = NULL;

    MAPINAMEID nmidRTPR[2];
    MAPINAMEID *pnmidRTPR[2] = {&nmidRTPR[0], &nmidRTPR[1]};

    Assert(pRouteData);

    ZeroMemory(&spvRTProp, 2*sizeof(SPropValue));

    /* if this is a new message, get PropIDs */
    if(pRouteData->bNewMessage)
    {
        nmidRTPR[0].lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
        nmidRTPR[0].ulKind = MNID_STRING;
        nmidRTPR[0].Kind.lpwstrName =   L"CURRENT_RT_RECIP";
        nmidRTPR[1].lpguid = (LPGUID)&PS_PUBLIC_STRINGS;
        nmidRTPR[1].ulKind = MNID_STRING;
        nmidRTPR[1].Kind.lpwstrName =   L"TOTAL_RT_RECIP";

        hr = pRouteData->pmsgRouteMsg->lpVtbl->GetIDsFromNames(pRouteData->pmsgRouteMsg, 2, pnmidRTPR, MAPI_CREATE, &lpsptProp);
        if(S_OK != GetScode(hr))
        {
            DebugTraceResult(GetIDsFromNames, hr);
            goto err;
        }

        lpsptProp->aulPropTag[0] = PROP_TAG(PT_LONG, PROP_ID(lpsptProp->aulPropTag[0]));
        lpsptProp->aulPropTag[1] = PROP_TAG(PT_LONG, PROP_ID(lpsptProp->aulPropTag[1]));

        spvRTProp[0].ulPropTag = lpsptProp->aulPropTag[0];
        spvRTProp[1].ulPropTag = lpsptProp->aulPropTag[1];
    }
    else /* if we created this msg from one in the inbox, we already have the PropIDs*/
    {
        spvRTProp[0].ulPropTag = pRouteData->tagCurrent;
        spvRTProp[1].ulPropTag = pRouteData->tagTotal;

    }

    spvRTProp[0].Value.l = pRouteData->nCurrentRouteRecip;
    spvRTProp[1].Value.l = pRouteData->nTotalRouteRecip;

    hr = pRouteData->pmsgRouteMsg->lpVtbl->SetProps(pRouteData->pmsgRouteMsg, 2, spvRTProp, &pProblems);
    if(S_OK != GetScode(hr))
    {
        if(pProblems)
        {
            DebugTraceProblems("Client: ", pProblems);
            MAPIFreeBuffer(pProblems);
            pProblems = NULL;
        }
        DebugTraceResult(SetProps, hr);
        goto err;
    }

err:
    MAPIFreeBuffer(lpsptProp);

    return !hr;
}

void InitRouteNameIDArray(ULONG iRecip, LPMAPINAMEID  lpPropNames)
{
    ULONG idx;

    Assert (lpPropNames);
    for(idx = 0; idx < ROUTEPROPSETDIM; ++idx)
    {
        lpPropNames[idx].lpguid = (LPGUID)lpguidA[idx];
        lpPropNames[idx].ulKind = MNID_ID;
        lpPropNames[idx].Kind.lID = iRecip;
    }
}

BOOL GetRoutePropTagArray(ULONG iRecip, LPMESSAGE   lpM, LPSPropTagArray FAR * lppspta)
{
    MAPINAMEID nmidA[ROUTEPROPSETDIM];
    LPMAPINAMEID  lppnmidA[5];
    HRESULT hr;
    ULONG *pulptPropTag = NULL;
    UINT ulidx = 0;

    Assert(lpM);

    /*First create the 5 prop names and get IDs for them*/
    for(ulidx = 0; ulidx < ROUTEPROPSETDIM; ++ulidx)
        lppnmidA[ulidx] = &nmidA[ulidx];
    InitRouteNameIDArray(iRecip, nmidA);
    hr = lpM->lpVtbl->GetIDsFromNames(lpM, ROUTEPROPSETDIM, lppnmidA,
                                        MAPI_CREATE, lppspta);

    if(S_OK != GetScode(hr))
    {
        DebugTraceResult(GetIDsFromNames, hr);
        return FALSE;
    }
    Assert((*lppspta)->cValues == ROUTEPROPSETDIM);

    /*Now add right prop types */
    ulidx = 0;
    for(pulptPropTag = (*lppspta)->aulPropTag;
        pulptPropTag < (*lppspta)->aulPropTag + ROUTEPROPSETDIM; ++pulptPropTag, ++ulidx)
    {
     *pulptPropTag =  PROP_TAG(ulRoutePropTypes[ulidx], PROP_ID(*pulptPropTag));
    }

    return TRUE;
}

/* the output PropTag array contains nTotalRecip * ROUTEPROPSETDIM + 1
 * prop tags. The last one is PR_NULL.
 */
BOOL GetRoutePropTagArrayFast(ULONG nTotalRecip, LPMESSAGE lpM, BOOL fCreate,
                                LPSPropTagArray FAR * lppspta)
{
    LPMAPINAMEID pnmid = NULL;
    LPMAPINAMEID * ppnmid = NULL;
    HRESULT hr;
    ULONG *pulptPropTag = NULL;
    UINT ulidx = 0;
    /*//$ hack!!!  +1 is to save a spot for PR_RECIPIENT_TYPE */
    UINT nTotalProps = nTotalRecip * ROUTEPROPSETDIM + 1;

    Assert(lpM);

    if(hr = ResultFromScode(MAPIAllocateBuffer(nTotalProps * sizeof(MAPINAMEID),
                            (LPVOID FAR *)&pnmid)))
        return FALSE;

    ZeroMemory(pnmid, nTotalProps * sizeof(MAPINAMEID));

    if(hr = ResultFromScode(MAPIAllocateBuffer(nTotalProps * sizeof(LPMAPINAMEID),
                            (LPVOID FAR *)&ppnmid)))
        goto err;

    for(ulidx = 0; ulidx < nTotalProps; ++ulidx)
        ppnmid[ulidx] = &pnmid[ulidx];

    for(ulidx = 0; ulidx < nTotalRecip; ++ulidx )
        InitRouteNameIDArray(ulidx, &pnmid[ulidx * ROUTEPROPSETDIM]);

    //put someting reasonable in
    pnmid[nTotalProps-1].lpguid = (LPGUID)lpguidA[0];
    pnmid[nTotalProps-1].ulKind = MNID_ID;
    pnmid[nTotalProps-1].Kind.lID = 0;

    hr = lpM->lpVtbl->GetIDsFromNames(lpM, nTotalProps, ppnmid,
                        fCreate? MAPI_CREATE : 0, lppspta);
    if(hr) /* treat warnings as errors */
        goto err;

    Assert((*lppspta)->cValues == nTotalProps);

    ulidx = 0;
    for(pulptPropTag = (*lppspta)->aulPropTag;
        pulptPropTag < (*lppspta)->aulPropTag + nTotalProps - 1;
        ++pulptPropTag , ++ulidx)
    {
        *pulptPropTag = PROP_TAG(ulRoutePropTypes[ulidx % ROUTEPROPSETDIM],
                                PROP_ID(*pulptPropTag));
    }

    *pulptPropTag = PR_NULL;

err:
    MAPIFreeBuffer(pnmid);
    MAPIFreeBuffer(ppnmid);

    DebugTraceResult(ROUTECLI_GetRoutePropTagArray, hr);
    return (hr? FALSE : TRUE);
}

BOOL OpenOutFolder(HWND hWnd, LPMAPIFOLDER FAR * lppF)
{
    LPMAPIFOLDER lpfOutF = NULL;
    HRESULT hr;
    LPSPropValue lpspvFEID = NULL;
    ULONG  ulObjType = 0;

    Assert(pmdb);

    *lppF = NULL;
    hr = HrGetOneProp((LPMAPIPROP) pmdb, PR_IPM_OUTBOX_ENTRYID, &lpspvFEID);
    if(hr)
    {
        DebugTrace("Client: HrGetOneProp failed (OpneOutFolder)");
        goto err;
    }

    Assert(lpspvFEID->ulPropTag == PR_IPM_OUTBOX_ENTRYID);

    hr = pmdb->lpVtbl->OpenEntry(pmdb, lpspvFEID->Value.bin.cb,
                        (LPENTRYID)lpspvFEID->Value.bin.lpb, NULL,
                        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                        &ulObjType, (LPUNKNOWN FAR *) &lpfOutF);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: OpenEntry (OpenOutFolder)");
        goto err;
    }

    *lppF = lpfOutF;

err:
    MAPIFreeBuffer(lpspvFEID);
    if(hr)
        MakeMessageBox(hWnd, GetScode(hr), IDS_OPENOUTB, NULL, MBS_ERROR);

    return !hr;

}

BOOL CreateOutMessage(LPMESSAGE FAR * lpmM)
{

    LPMESSAGE lpmResM = NULL;
    HRESULT hr;

    Assert(pfldOutBox);

    hr = pfldOutBox->lpVtbl->CreateMessage(pfldOutBox, NULL, MAPI_DEFERRED_ERRORS,
                                            &lpmResM);

    if(S_OK != GetScode(hr)) return FALSE;

    *lpmM = lpmResM;

    return TRUE;
}

BOOL MakeNewRouteMessage(LPMESSAGE pmsgRead, LPROUTEDATA FAR * ppRouteData)
{
    HRESULT hr;
    LPROUTEDATA pRouteData = NULL;
    LPSPropProblemArray pProblems = NULL;

    if(MAPIAllocateBuffer(sizeof(ROUTEDATA), (LPVOID FAR *) &pRouteData))
    {
        return FALSE;
    }
    ZeroMemory(pRouteData, sizeof(ROUTEDATA));

    if(!CreateOutMessage(&pRouteData->pmsgRouteMsg))
    {
        goto err;
    }

    if(!pmsgRead)   /*if called  to create a new message*/
    {
        pRouteData->bNewMessage = TRUE;
        *ppRouteData = pRouteData;
        return TRUE;
    }

     pRouteData->bNewMessage = FALSE;

    /* include attachments*/
    sptExcludedProps.cValues = EXCLUDED_PROPS_ON_REPLY - 1;

    hr = pmsgRead->lpVtbl->CopyTo(pmsgRead, 0, NULL,
                                  (LPSPropTagArray)&sptExcludedProps,
                                  0, NULL, &IID_IMessage,
                                  pRouteData->pmsgRouteMsg, 0, &pProblems);

    if(HR_FAILED(hr))
    {
        if(pProblems)

        {
            DebugTraceProblems("Client: ", pProblems);
            MAPIFreeBuffer(pProblems);
            pProblems = NULL;
        }

        goto err;
    }

    if(!GetRouteIndices(pRouteData))
    {
        goto err;
    }

    if(!GetRouteAddrLists(pRouteData))
    {
        goto err;
    }

    *ppRouteData = pRouteData;

    return TRUE;

err:
    UlRelease(pRouteData->pmsgRouteMsg);
    MAPIFreeBuffer(pRouteData);
    *ppRouteData = NULL;
    return FALSE;
}

/*Assumes that members nCurrentRouteRecip and nTotalRouteRecip were succesfully
 *set. Initializes palAddrListActive and palAddrListOld.
 */

BOOL GetRouteAddrLists(LPROUTEDATA pRouteData)
{
    LPSPropTagArray lpspta = NULL;
//  SizedSPropTagArray((ROUTEPROPSETDIM + 1), sspta);
    LPADRLIST lpalOld = NULL;
    LPADRLIST lpalActive = NULL;
    ULONG cbal = 0;
    HRESULT hr;
    UINT ulidx, ulidx1;
    ULONG cActiveRecip ;
    LPMESSAGE pmsg;
    ULONG cvalAllProps = 0;
    LPSPropValue pvalAllProps = NULL;

    Assert(pRouteData);
    Assert(!pRouteData->palAddrListActive);
    Assert(!pRouteData->palAddrListOld);

    pmsg = pRouteData->pmsgRouteMsg;
    Assert(pmsg);

    if(!GetRoutePropTagArrayFast(pRouteData->nTotalRouteRecip,
                            pmsg,
                            FALSE,
                            &lpspta))
        goto err;

    /* hack!! the output PropTag array contains nTotalRecip * ROUTEPROPSETDIM + 1
     * prop tags. The last one is PR_NULL. We do this to reserve a spot for
     * PR_RECIPIENT_TYPE
     */

    hr = pmsg->lpVtbl->GetProps(pmsg, lpspta, 0, &cvalAllProps, &pvalAllProps);
    MAPIFreeBuffer(lpspta);
    lpspta=NULL;
    if(HR_FAILED(hr))
        goto err;

    Assert(cvalAllProps == pRouteData->nTotalRouteRecip * ROUTEPROPSETDIM + 1);

    //
    // In case somebody does not handle PR_NULL in GetProps
    //
    pvalAllProps[cvalAllProps - 1].ulPropTag = PR_NULL;

    cActiveRecip = pRouteData->nTotalRouteRecip - pRouteData->nCurrentRouteRecip;

    /*First do the recipients already routed to */
    cbal =  CbNewADRLIST(pRouteData->nCurrentRouteRecip);
    if (MAPIAllocateBuffer(cbal, &lpalOld))
        goto err;
    ZeroMemory(lpalOld, cbal);

    lpalOld->cEntries = pRouteData->nCurrentRouteRecip;

    for(ulidx = 0; ulidx < pRouteData->nCurrentRouteRecip; ++ulidx)
    {
        LPSPropValue pval = NULL;

        hr = ResultFromScode(ScDupPropset(ROUTEPROPSETDIM +1,
                                &pvalAllProps[ulidx * ROUTEPROPSETDIM],
                                MAPIAllocateBuffer,
                                &pval));

        if(hr)
            goto err;

        lpalOld->aEntries[ulidx].rgPropVals = pval;
        lpalOld->aEntries[ulidx].cValues = ROUTEPROPSETDIM + 1;

        for(ulidx1 = 0; ulidx1 < ROUTEPROPSETDIM; ++ulidx1)
        {
            pval[ulidx1].ulPropTag = sptRouteProps.aulPropTag[ulidx1];
            pval[ulidx1].dwAlignPad = 0;
        }
        /*Have to set RECIPIENT_TYPE in order for IAddressBook::Address to show the
          recipient in one of its listboxes*/
        pval[ROUTEPROPSETDIM].ulPropTag = PR_RECIPIENT_TYPE;
        pval[ROUTEPROPSETDIM].dwAlignPad = 0;
        pval[ROUTEPROPSETDIM].Value.l = MAPI_TO;

    }

    if(cActiveRecip == 0)
    {
        pRouteData->palAddrListOld = lpalOld;
        pRouteData->palAddrListActive = NULL;
        goto ret;
    }

    /*Now the same for palAddrListActive*/
    cbal =  CbNewADRLIST(cActiveRecip);
    if (MAPIAllocateBuffer(cbal, &lpalActive))
        goto err;
    memset(lpalActive, 0, cbal);
    lpalActive->cEntries = cActiveRecip;
    for(ulidx = 0; ulidx < cActiveRecip; ++ulidx)
    {
        LPSPropValue pval = NULL;

        hr = ResultFromScode(ScDupPropset(ROUTEPROPSETDIM +1,
                            &pvalAllProps[(ulidx + pRouteData->nCurrentRouteRecip) *
                                                ROUTEPROPSETDIM],
                                MAPIAllocateBuffer,
                                &pval));

        if(hr)
            goto err;

        lpalActive->aEntries[ulidx].rgPropVals = pval;
        lpalActive->aEntries[ulidx].cValues = ROUTEPROPSETDIM + 1;

        for(ulidx1 = 0; ulidx1 < ROUTEPROPSETDIM; ++ulidx1)
        {
            pval[ulidx1].ulPropTag = sptRouteProps.aulPropTag[ulidx1];
            pval[ulidx1].dwAlignPad = 0;
        }
        /*Have to set RECIPIENT_TYPE in order for IAddressBook::Address to show the
          recipient in one of its listboxes*/
        pval[ROUTEPROPSETDIM].ulPropTag = PR_RECIPIENT_TYPE;
        pval[ROUTEPROPSETDIM].dwAlignPad = 0;
        pval[ROUTEPROPSETDIM].Value.l = MAPI_TO;

    }

    pRouteData->palAddrListOld = lpalOld;
    pRouteData->palAddrListActive = lpalActive;

ret:
    MAPIFreeBuffer(pvalAllProps);
    return TRUE;
err:
    FreePadrlist(lpalOld);
    FreePadrlist(lpalActive);
    MAPIFreeBuffer(pvalAllProps);

    return FALSE;
}

/*deletes all the props corresponding to the entries in palAddrListActive*/
BOOL  DelRouteProps(LPROUTEDATA pRouteData)
{
    UINT ulidx;
    LPSPropTagArray lpsptaRouteTags = NULL;
    LPSPropProblemArray lpsppaProblems = NULL;
    HRESULT hr = hrSuccess;

    if(!pRouteData->palAddrListActive) return TRUE;

    Assert(pRouteData);
    Assert(pRouteData->pmsgRouteMsg);

    for(ulidx = pRouteData->nCurrentRouteRecip; ulidx < pRouteData->nTotalRouteRecip; ++ulidx)
    {
        if(!GetRoutePropTagArray(ulidx, pRouteData->pmsgRouteMsg, &lpsptaRouteTags))
        {
            /*
            //$ even if it gives an error still have to go through all of them*/
            continue;
        }
        hr = pRouteData->pmsgRouteMsg->lpVtbl->DeleteProps(pRouteData->pmsgRouteMsg, lpsptaRouteTags, &lpsppaProblems);
        /* even if it gives an error still have to go through all of them*/
        if(lpsppaProblems)
        {
            DebugTraceProblems("Client:", lpsppaProblems);
            MAPIFreeBuffer(lpsppaProblems);
            lpsppaProblems = NULL;
        }

        MAPIFreeBuffer(lpsptaRouteTags);

        lpsptaRouteTags = NULL;

    }

    return (S_OK == GetScode(hr));
}

BOOL SetRouteProps(LPROUTEDATA pRouteData)
{
    //SPropValue pspvRouteProps[ROUTEPROPSETDIM];
    LPSPropTagArray lpsptaRouteTags = NULL;
    ULONG cRouteRecip;
    LPADRENTRY lpae = NULL;
    LPSPropValue lpspv = NULL;
    UINT ulidx;
    LPSPropProblemArray lpsppaProblems = NULL;
    HRESULT hr;
    UINT cNewRecips = 0;
    LPSPropValue pvalAll = NULL;
    LPSPropValue pval = NULL;

    Assert(pRouteData);
    if(!pRouteData->palAddrListActive) return TRUE;

    cRouteRecip = pRouteData->nCurrentRouteRecip;

    if(!GetRoutePropTagArrayFast(
                cRouteRecip + pRouteData->palAddrListActive->cEntries,
                pRouteData->pmsgRouteMsg,
                TRUE,
                &lpsptaRouteTags))
    {
        hr = ResultFromScode(E_FAIL);
        goto err;
    }

    if(hr = MAPIAllocateBuffer(
                    pRouteData->palAddrListActive->cEntries * ROUTEPROPSETDIM *
                        sizeof(SPropValue), (LPVOID *) &pvalAll))
        goto err;

    ZeroMemory(pvalAll, pRouteData->palAddrListActive->cEntries *
                        ROUTEPROPSETDIM * sizeof(SPropValue));

    /* get required properties missing in AddrlistActive*/
    hr = pabAddrB->lpVtbl->PrepareRecips(pabAddrB, 0, (LPSPropTagArray)&sptRouteProps,
                                                        pRouteData->palAddrListActive);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: PrepareRecips failed");
        goto err;
    }

    for(lpae = pRouteData->palAddrListActive->aEntries,
            pval = pvalAll, cNewRecips = 0;
        lpae < pRouteData->palAddrListActive->aEntries +
                pRouteData->palAddrListActive->cEntries;
            ++lpae )
    {
        if(!lpae->rgPropVals)
        {
            continue;
        }

        ++cNewRecips;

        Assert(lpae->rgPropVals[ROUTEPROPSET_EMAIL_ADDRESS].ulPropTag == PR_EMAIL_ADDRESS);
        pval[ROUTEPROPSET_EMAIL_ADDRESS].Value.LPSZ =
            lpae->rgPropVals[ROUTEPROPSET_EMAIL_ADDRESS].Value.LPSZ;

        Assert(lpae->rgPropVals[ROUTEPROPSET_ADDRTYPE].ulPropTag == PR_ADDRTYPE);
        pval[ROUTEPROPSET_ADDRTYPE].Value.LPSZ =
            lpae->rgPropVals[ROUTEPROPSET_ADDRTYPE].Value.LPSZ;

        Assert(lpae->rgPropVals[ROUTEPROPSET_DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME);
        pval[ROUTEPROPSET_DISPLAY_NAME].Value.LPSZ =
            lpae->rgPropVals[ROUTEPROPSET_DISPLAY_NAME].Value.LPSZ;

        Assert(lpae->rgPropVals[ROUTEPROPSET_ENTRYID].ulPropTag == PR_ENTRYID);
        pval[ROUTEPROPSET_ENTRYID].Value =
            lpae->rgPropVals[ROUTEPROPSET_ENTRYID].Value;

        Assert(lpae->rgPropVals[ROUTEPROPSET_SEARCH_KEY].ulPropTag == PR_SEARCH_KEY);
        pval[ROUTEPROPSET_SEARCH_KEY].Value =
            lpae->rgPropVals[ROUTEPROPSET_SEARCH_KEY].Value;

        pval += ROUTEPROPSETDIM;
    }

    for(ulidx = cRouteRecip * ROUTEPROPSETDIM, pval = pvalAll;
         ulidx < (cRouteRecip + cNewRecips)*ROUTEPROPSETDIM;
         ++pval, ++ulidx)
    {
        pval->ulPropTag = lpsptaRouteTags->aulPropTag[ulidx];
    }

    hr = pRouteData->pmsgRouteMsg->lpVtbl->SetProps(
                                            pRouteData->pmsgRouteMsg,
                                            cNewRecips * ROUTEPROPSETDIM,
                                            pvalAll,
                                            &lpsppaProblems);

    if(S_OK != GetScode(hr))
    {

        DebugTraceResult(SetProps, hr);
        goto err;
    }

    if(lpsppaProblems)
    {
        DebugTraceProblems("Client: SetProps", lpsppaProblems);
        MAPIFreeBuffer(lpsppaProblems);
        lpsppaProblems = NULL;
    }

    MAPIFreeBuffer(lpsptaRouteTags);
    lpsptaRouteTags = NULL;

    pRouteData->nTotalRouteRecip = pRouteData->nCurrentRouteRecip + cNewRecips;

    ++(pRouteData->nCurrentRouteRecip);
    if(!SetRouteIndices(pRouteData))
    {
        hr = ResultFromScode(E_FAIL);
        goto err;
    }

err:
    MAPIFreeBuffer(lpsptaRouteTags);
    MAPIFreeBuffer(lpsppaProblems);
    MAPIFreeBuffer(pvalAll);
    return ( hr ? FALSE : TRUE);
}

BOOL SetMessageClass(LPMESSAGE lpM, LPSTR lpszClass)
{
    HRESULT hr;
    SPropValue spvProp;

    spvProp.ulPropTag = PR_MESSAGE_CLASS;
    spvProp.dwAlignPad = 0;
    spvProp.Value.LPSZ = lpszClass;

    Assert(lpM);

    hr = HrSetOneProp((LPMAPIPROP)lpM, &spvProp);
    if(hr)
    {
        DebugTrace("Client: HrSetOneProp failed (SetMessageClass)");
        return FALSE;
    }

    return TRUE;
}

/* this functions works correctly only for received (saved) msgs*/
/*BOOL HasAttachment(LPMESSAGE lpM)
{
    HRESULT hr;
    LPSPropValue lpspvProp = NULL;
    LONG lflags = 0;

    Assert(lpM);

    hr = HrGetOneProp((LPMAPIPROP) lpM, PR_MESSAGE_FLAGS, &lpspvProp);
    if(hr)
    {
        DebugTrace("Client: HrGetOneProp failed (hasattachment)");
        goto err;
    }

    lflags = lpspvProp->Value.l;

err:
    MAPIFreeBuffer(lpspvProp);

    return lflags & MSGFLAG_HASATTACH;
}*/

BOOL PopulateAttachList(HWND hDlg, LPROUTEDATA pRouteData)
{
    SizedSPropTagArray (1, sptAttachNum) =  {   1,  {PR_ATTACH_NUM}  };
    LPSRowSet lprsRows = NULL;
    LPATTACH lpAttach = NULL;
    HRESULT hr;
    UINT idx, lbidx;
    LPSPropValue lpProp = NULL;
    SPropValue spvPosProp = {0};
    spvPosProp.ulPropTag = PR_RENDERING_POSITION;
    spvPosProp.Value.l = -1;

    Assert(pRouteData);
    Assert(pRouteData->pmsgRouteMsg);

    hr = pRouteData->pmsgRouteMsg->lpVtbl->
            GetAttachmentTable(pRouteData->pmsgRouteMsg, 0,
                                &pRouteData->ptblAttach);
    if(S_OK != hr)
    {
        DebugTrace("Client: GetAttachmentTable failed (PopulateAttachList)");
        goto err;
    }

    hr = HrQueryAllRows(pRouteData->ptblAttach, (LPSPropTagArray)&sptAttachNum, NULL,
                                        NULL, 0, &lprsRows);
    if(S_OK != hr)
    {
        DebugTrace("Client: QueryAllRows failed (PopulateAttachList)");
        goto err;
    }

    if(!lprsRows || !(lprsRows->cRows)) goto err;

    for(idx = 0; idx < lprsRows->cRows; ++idx)
    {
        Assert(lprsRows->aRow[idx].cValues == 1);
        Assert(lprsRows->aRow[idx].lpProps->ulPropTag == PR_ATTACH_NUM);

        hr = pRouteData->pmsgRouteMsg->lpVtbl->
            OpenAttach(pRouteData->pmsgRouteMsg,
                            lprsRows->aRow[idx].lpProps->Value.l,
                        NULL, MAPI_MODIFY | MAPI_DEFERRED_ERRORS, &lpAttach);
        if(S_OK != hr)
        {
            DebugTrace("Client: openattach failed (PopulateAttachList)");
            goto err;
        }

        hr = HrGetOneProp((LPMAPIPROP)lpAttach, PR_DISPLAY_NAME, &lpProp);
        if(hr)
        {
            DebugTrace("Client: HrGetOneProp failed (PopulateAttachList)");
            goto err;
        }
        lbidx = ListBox_AddString(GetDlgItem(hDlg, IDC_RTATTACHLIST), lpProp->Value.LPSZ);
        if(LB_ERR != lbidx)
        {
            ListBox_SetItemData(GetDlgItem(hDlg, IDC_RTATTACHLIST), lbidx,
                                    lprsRows->aRow[idx].lpProps->Value.l);
        }

        MAPIFreeBuffer(lpProp);
        lpProp = NULL;

        /* in received msgs attachment positions are not -1 any more.
            Set them to -1 again*/
        hr = HrSetOneProp((LPMAPIPROP)lpAttach, &spvPosProp);
        if(!hr)
        {
            hr = lpAttach->lpVtbl->SaveChanges(lpAttach, 0);
        }
        lpAttach->lpVtbl->Release(lpAttach);
        lpAttach = NULL;
    }

err:

    FreeProws(lprsRows);
    lprsRows = NULL;

    UlRelease(lpAttach);

    MAPIFreeBuffer(lpProp);

    return (S_OK == GetScode(hr));
}

BOOL CreateNewAttachment(HWND hDlg)
{
    OPENFILENAME ofn;
    char szFileName[MAX_PATH] = "";
    char szFilter[256];
    static char szFileTitle[16] = "";
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    UINT idx, lbidx;
    HRESULT hr;
    LPROUTEDATA pRouteData = NULL;

    enum { REND_POS, PATH_NAME, ATT_METHOD, DISP_NAME, ATT_FILENAME, ATT_DIM};

    SizedSPropTagArray(ATT_DIM , sptAttachTags) =
    { ATT_DIM,
        {   PR_RENDERING_POSITION, PR_ATTACH_PATHNAME,
            PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_ATTACH_FILENAME }
    };
    SPropValue spvAttachProps[ATT_DIM];

    LPATTACH lpAttach = NULL;
    ULONG ulAttachNum = 0;

    pRouteData = (LPROUTEDATA)GetWindowLong(hDlg, GWL_USERDATA);
    Assert(pRouteData);

#ifndef WIN16
    if (!szDirName[0])
        GetCurrentDirectory (255, (LPSTR) szDirName);
    else
#endif
        lstrcpy (szFileName, szFileTitle);

    if(!LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter)))
    {
        DebugTrace("Client: failed to load a string");
        return FALSE;
    }

    for (idx = 0; szFilter[idx] != '\0'; idx++)
        if (szFilter[idx] == '|')
            szFilter[idx] = '\0';

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = 0;
    ofn.hInstance = 0;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = 256;
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = 16;
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Attach";
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    if (!GetOpenFileName (&ofn))
        return FALSE;

    /* Save the directory for the next time we call this */

    lstrcpy (szDirName, szFileName);
    if (lpszEndPath = SzFindLastCh(szDirName, '\\'))
        *(lpszEndPath) = '\0';

    hr = pRouteData->pmsgRouteMsg->lpVtbl->CreateAttach(pRouteData->pmsgRouteMsg,
                                 NULL, MAPI_DEFERRED_ERRORS, &ulAttachNum, &lpAttach);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: CreateAttach failed (CreateNewAttach)");
        return FALSE;
    }

    for(idx = 0; idx < ATT_DIM; ++idx)
    {
        spvAttachProps[idx].ulPropTag = sptAttachTags.aulPropTag[idx];
        spvAttachProps[idx].dwAlignPad = 0;
    }

    spvAttachProps[REND_POS].Value.l = -1;
    spvAttachProps[PATH_NAME].Value.LPSZ = szFileName;
    spvAttachProps[ATT_METHOD].Value.l = ATTACH_BY_REF_RESOLVE;
    /* ATTACH_BY_REF_RESOLVE means the spooler wiil reslove the attachment into ATTACH_BY_VALUE
        and place the attachment data in PR_ATTACH_DATA_BIN */
    spvAttachProps[DISP_NAME].Value.LPSZ = szFileTitle;
    spvAttachProps[ATT_FILENAME].Value.LPSZ = szFileTitle;

    hr = lpAttach->lpVtbl->SetProps(lpAttach, ATT_DIM, spvAttachProps, NULL);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: SetProp failed (createnewattachment)");
        goto err;
    }

    hr = lpAttach->lpVtbl->SaveChanges(lpAttach, MAPI_DEFERRED_ERRORS);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: SaveChanges failed (createnewattachment)");
        goto err;
    }

    lbidx = ListBox_AddString(GetDlgItem(hDlg, IDC_RTATTACHLIST), szFileTitle);
    if(LB_ERR != lbidx)
    {
        /*Store the attachment # with its name in the listbox*/
        ListBox_SetItemData(GetDlgItem(hDlg, IDC_RTATTACHLIST), lbidx,
                                    ulAttachNum);
    }

err:
    UlRelease(lpAttach);

    return (S_OK == GetScode(hr));
}

void SaveAttachment( HWND hDlg, UINT itemindx)
{
    OPENFILENAME ofn;
    char szFileName[256] = "";
    char szFilter[256];
    static char szFileTitle[16];
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    ULONG idx;
    HRESULT hr;
    LPSPropValue lpProp = NULL;
    LPATTACH lpAttach = NULL;
    LPSTREAM lpstrAtt=NULL, lpstrFile=NULL;
    STATSTG statstg = {0};
    ULONG ulAttachNum;
    LPROUTEDATA pRouteData = NULL;

    pRouteData = (LPROUTEDATA)GetWindowLong(hDlg, GWL_USERDATA);
    if(!pRouteData)
    {
        DebugTrace("Client: pRouteData == 0 (saveattachment)");
        goto err;
    }

    if (!szDirName[0])
        GetTempPath (sizeof(szDirName), szDirName);

    if(!LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter)))
    {
        DebugTrace("Client: failed to load a string");
        goto err;
    }

    for (idx = 0; szFilter[idx] != '\0'; idx++)
        if (szFilter[idx] == '|')
            szFilter[idx] = '\0';

    ulAttachNum = ListBox_GetItemData(GetDlgItem(hDlg, IDC_RTATTACHLIST), itemindx);
    if(LB_ERR == ulAttachNum)
    {
        DebugTrace("Client: can't get attach num (saveattachment)");
        return;
    }

    hr = pRouteData->pmsgRouteMsg->lpVtbl->
            OpenAttach(pRouteData->pmsgRouteMsg, ulAttachNum, NULL,
                        MAPI_BEST_ACCESS | MAPI_DEFERRED_ERRORS, &lpAttach);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: OpenAttach failed (saveattachment)");
        goto err;
    }

    hr = HrGetOneProp((LPMAPIPROP)lpAttach, PR_ATTACH_FILENAME, &lpProp);
    if(hr)
    {
        DebugTrace("Client: OpenAttach failed (saveattachment)");
        goto err;
    }

    lstrcpy (szFileName, lpProp->Value.LPSZ);
    MAPIFreeBuffer(lpProp);
    lpProp = NULL;

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = hDlg;
    ofn.hInstance = 0;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = sizeof(szFileName);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Save Attachment";
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;

    if (!GetSaveFileName (&ofn))
        goto err;

    /* Save the directory for the next time we call this */

    lstrcpy (szDirName, szFileName);
    if (lpszEndPath = SzFindLastCh(szDirName, '\\'))
        *(lpszEndPath) = '\0';

    hr = lpAttach->lpVtbl->OpenProperty (lpAttach, PR_ATTACH_DATA_BIN,
                                        (LPIID)&IID_IStream,
                                        0, MAPI_DEFERRED_ERRORS,
                                        (LPUNKNOWN *)&lpstrAtt);
    if(S_OK != GetScode(hr))
    {
        /*must be a new attachment*/
        hr = HrGetOneProp((LPMAPIPROP)lpAttach, PR_ATTACH_PATHNAME, &lpProp);
        if(hr)
        {
            DebugTrace("Client: PR_ATTACH_PATHNAME (saveattach)");
            goto err;
        }
        CopyFile(lpProp->Value.LPSZ, szFileName, TRUE);
        goto err;
    }

    hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
                            STGM_CREATE | STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
                            szFileName, NULL, &lpstrFile);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: OpenStreamOnFile failed (saveattachment)");
        goto err;
    }

    lpstrAtt->lpVtbl->Stat(lpstrAtt, &statstg, STATFLAG_NONAME);

    hr = lpstrAtt->lpVtbl->CopyTo(lpstrAtt, lpstrFile, statstg.cbSize, NULL, NULL);
    if(S_OK != GetScode(hr))
    {
        DebugTrace("Client: CopyTo failed (saveattachment)");
        goto err;
    }

err:
    UlRelease(lpstrAtt);
    UlRelease(lpstrFile);
    UlRelease(lpAttach);
    MAPIFreeBuffer(lpProp);
}


ROUTE.H

/*
 * R O U T E . H
 *
 * Routing fuctions called outside of route.c
 *
 *Copyright 1995, Microsoft Corporation. All Rights Reserved.
 */

/*put a name for your own message class here*/
#define lpszSmplRTMsgClass "IPM.Note.SampleRoutingForm"

LPADRBOOK  OpenAddressBook(HWND hwnd);
BOOL CALLBACK RouteNoteDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL HasRoutingSlip (LPMESSAGE lpMsg);
BOOL OpenOutFolder(HWND hWnd, LPMAPIFOLDER FAR * lppF);
BOOL CreateOutMessage(LPMESSAGE FAR * lpmSrcMsgI);


SENDRTF.CPP

////////////////////////////////////////////////////////////////////////////
//  SENDRTF.CPP
//
//  Command line mail program to send rtf files in a message body.
//
//  Copyright 1986-1996, Microsoft Corporation. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <mapiutil.h>
#include <mapix.h>
#include <mapidbg.h>
#include <cindex.h>

#ifdef DEBUG
#define TraceFnResult(f, hr)    \
{ (hr) ?   \
printf(#f " returns 0x%08lX %s\n", GetScode(hr), SzDecodeScode(GetScode(hr))) : 0;\
}
#else
#define TraceFnResult(f, hr)
#endif  //DEBUG

// Globals
LPSTR szProfile = NULL;     //profile name
LPSTR szPassword = NULL;    //profile password
LPSTR szSubject = NULL;     //message subject
LPSTR szRTFFile = NULL;     //rtf file name
LPSTR szToRecips = NULL;    //recipients

BOOL fMAPIInited = FALSE;

LPMAPISESSION pses = NULL;              //MAPI session
LPADRBOOK pabAddrB = NULL;              //Address Book
LPMDB pmdb = NULL;                      //default message store
LPMAPIFOLDER pfldOutBox = NULL;         // Out folder
LPSPropValue pvalSentMailEID = NULL;    //Entry ID of the "Sent Mail" folder

//Function Prototypes
BOOL fParseCommandLine(int argc, char * argv[]);
HRESULT InitMAPI(void);
void DeInitMAPI(void);
HRESULT HrOpenDefaultStore(LPMDB * ppmdb);
HRESULT HrOpenAddressBook(LPADRBOOK * ppAdrBook);
HRESULT HrOpenOutFolder(LPMAPIFOLDER FAR * lppF);
HRESULT HrCreateOutMessage(LPMESSAGE FAR * ppmM);
HRESULT HrInitMsg(LPMESSAGE pmsg);
HRESULT HrCreateAndSend(void);
HRESULT HrCreateAddrList(LPADRLIST * ppal);
HRESULT HrSetRTF(LPMESSAGE pmsg);

int main(int argc, char * argv[])
{
    HRESULT hr;

    if(!fParseCommandLine(argc, argv))
        return 1;

    hr = InitMAPI();
    if(HR_FAILED(hr))
        return 1;

    hr = HrCreateAndSend();

    DeInitMAPI();

    return 0;
}

//
//  Init MAPI. Open address book, default message store,  outbox
//
HRESULT InitMAPI(void)
{

    HRESULT hr;
    ULONG ulLogonFlag = 0;

    hr = MAPIInitialize(NULL);
    if(hr)
    {
        return hr;
    }

    fMAPIInited = TRUE;

    ulLogonFlag = szProfile ? 0 : MAPI_USE_DEFAULT;

    hr = MAPILogonEx(NULL, szProfile, szPassword,
                    MAPI_EXTENDED | MAPI_NEW_SESSION | ulLogonFlag,
                    &pses);
    if(hr)
    {
        goto err;
    }

    hr = HrOpenDefaultStore(&pmdb);
    if(HR_FAILED(hr))
        goto err;

    hr = HrOpenAddressBook(&pabAddrB);
    if(HR_FAILED(hr))
        goto err;

    hr = HrOpenOutFolder(&pfldOutBox);
    if(HR_FAILED(hr))
        goto err;

    /* retrieve the EntryID of the sentmail folder and change the property tag
        so that it is ready to use on a message*/
    hr = HrGetOneProp((LPMAPIPROP)pmdb, PR_IPM_SENTMAIL_ENTRYID, &pvalSentMailEID);
    if(hr)
    {
        goto err;
    }
    pvalSentMailEID->ulPropTag = PR_SENTMAIL_ENTRYID;

    return hrSuccess;

err:
    DeInitMAPI();

    DebugTraceResult(InitMAPI, hr);

    return hr;
}

//
//  Release MAPI interfaces and logoff
//
void DeInitMAPI(void)
{

    UlRelease(pfldOutBox);
    pfldOutBox = NULL;

    if(pmdb)
    {
        //get our message out of the outbox
        ULONG ulFlags = LOGOFF_PURGE;
        HRESULT hr;

        hr = pmdb->StoreLogoff(&ulFlags);

        TraceFnResult(StoreLogoff, hr);
#ifdef DEBUG
        printf("StoreLogoff output falgs: %lx\n", ulFlags);
#endif
        UlRelease(pmdb);
        pmdb = NULL;
    }

    UlRelease(pabAddrB);
    pabAddrB = NULL;

    MAPIFreeBuffer(pvalSentMailEID);
    pvalSentMailEID = NULL;

    if(pses)
    {
        pses->Logoff(0, 0, 0);
        UlRelease(pses);
        pses = NULL;
    }

    if(fMAPIInited)
    {
        MAPIUninitialize();
        fMAPIInited = FALSE;
    }

}

//
//  Open the default message store. (The one that has PR_DEFAULT_STORE set to
//  TRUE in the message store table.
//
HRESULT HrOpenDefaultStore(LPMDB * ppmdb)
{
    HRESULT hr;
    LPMDB lpmdb = NULL;
    LPMAPITABLE ptable = NULL;
    LPSRowSet prows = NULL;
    LPSPropValue pvalProp = NULL;
    static SizedSPropTagArray(2, columns) =
                { 2, { PR_DEFAULT_STORE, PR_ENTRYID} };
    SPropValue valDefStore;
    SRestriction restDefStore;

    valDefStore.ulPropTag = PR_DEFAULT_STORE;
    valDefStore.dwAlignPad = 0;
    valDefStore.Value.b = TRUE;

    restDefStore.rt = RES_PROPERTY;
    restDefStore.res.resProperty.relop = RELOP_EQ;
    restDefStore.res.resProperty.ulPropTag = PR_DEFAULT_STORE;
    restDefStore.res.resProperty.lpProp = &valDefStore;

    Assert(pses);

    hr = pses->GetMsgStoresTable(0, &ptable);
    if (HR_FAILED(hr))
    {
        TraceFnResult(GetMsgStoresTable, hr);
        goto ret;
    }

    hr = HrQueryAllRows(ptable, (LPSPropTagArray) &columns, &restDefStore, NULL, 0, &prows);
    if (HR_FAILED(hr))
    {
        TraceFnResult(HrQyeryAllRows, hr);
        goto ret;
    }

    if (prows == NULL || prows->cRows == 0
        || prows->aRow[0].lpProps[1].ulPropTag != PR_ENTRYID)
    {
        printf("No default store\n");
        goto ret;
    }

    Assert(prows->cRows == 1);

    hr = pses->OpenMsgStore(0,
                        prows->aRow[0].lpProps[1].Value.bin.cb,
                        (LPENTRYID)prows->aRow[0].lpProps[1].Value.bin.lpb,
                        NULL, MDB_WRITE | MAPI_DEFERRED_ERRORS, &lpmdb);
    if (HR_FAILED(hr))
    {
        if (GetScode(hr) != MAPI_E_USER_CANCEL)
            TraceFnResult(OpenMsgStore, hr);
        goto ret;
    }

#if 0
    if(hr) /*if we have a warning, display it and succeed */
    {
        LPMAPIERROR perr = NULL;

        pses->lpVtbl->GetLastError(pses, hr, 0, &perr);
        MakeMessageBox(hWnd, GetScode(hr), IDS_OPENSTOREWARN, perr, MBS_ERROR);
        MAPIFreeBuffer(perr);
    }

#endif

    Assert(lpmdb != NULL);

    *ppmdb = lpmdb;

ret:
    FreeProws(prows);
    UlRelease(ptable);

    DebugTraceResult(OpenDefaultStore, hr);
    return hr;
}

//
//  Open MAPI address book
//
HRESULT HrOpenAddressBook(LPADRBOOK * ppAddrBook)
{
    HRESULT hr;
    LPADRBOOK pabAddrBook = NULL;

    Assert(pses);

    hr = pses->OpenAddressBook(0, NULL, 0, &pabAddrBook);
    if(HR_FAILED(hr))
    {
        TraceFnResult(OpenAddressBook, hr);
        return hr;
    }
#if 0
    if(hr) /*if we have a warning*/
    {
        LPMAPIERROR perr = NULL;

        pses->lpVtbl->GetLastError(pses, hr, 0, &perr);
        MakeMessageBox(hwnd, GetScode(hr), IDS_OPENABWARN, perr, MBS_ERROR);
        MAPIFreeBuffer(perr);
    }
#endif

    *ppAddrBook = pabAddrBook;

    DebugTraceResult(HrOpenAddressBook, hr);
    return hrSuccess;
}

//
//  Open the outbox of the default store.
//  Assumes the default message store has been opened.
//
HRESULT HrOpenOutFolder(LPMAPIFOLDER FAR * lppF)
{
    LPMAPIFOLDER lpfOutF = NULL;
    HRESULT hr;
    LPSPropValue lpspvFEID = NULL;
    ULONG  ulObjType = 0;

    Assert(pmdb);

    *lppF = NULL;
    hr = HrGetOneProp((LPMAPIPROP) pmdb, PR_IPM_OUTBOX_ENTRYID, &lpspvFEID);
    if(hr)
    {
        TraceFnResult(HrGetOneProp, hr);
        goto err;
    }

    Assert(lpspvFEID->ulPropTag == PR_IPM_OUTBOX_ENTRYID);

    hr = pmdb->OpenEntry(lpspvFEID->Value.bin.cb,
                        (LPENTRYID)lpspvFEID->Value.bin.lpb, NULL,
                        MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                        &ulObjType, (LPUNKNOWN FAR *) &lpfOutF);
    if(HR_FAILED(hr))
    {
        TraceFnResult(OpenEntry, hr);
        goto err;
    }

    *lppF = lpfOutF;

err:
    MAPIFreeBuffer(lpspvFEID);

    DebugTraceResult(HrOpenOutFolder, hr);
    return hr;

}

//
//  Create an outbound message, put data in it and submit.
//
HRESULT HrCreateAndSend()
{
    HRESULT hr;
    LPMESSAGE pmsg = NULL;

    hr = HrCreateOutMessage(&pmsg);
    if(hr)
        return hr;

    hr = HrInitMsg(pmsg);
    if(HR_FAILED(hr))
        goto err;

    hr = pmsg->SubmitMessage(0);
    TraceFnResult(SubmitMessage, hr);

err:

    UlRelease(pmsg);

    DebugTraceResult(HrCreateAndSend, hr);
    return hr;

}

//
//  Create a message in the outbox
//
HRESULT HrCreateOutMessage(LPMESSAGE FAR * ppmM)
{

    LPMESSAGE lpmResM = NULL;
    HRESULT hr;

    Assert(pfldOutBox);

    hr = pfldOutBox->CreateMessage(NULL, MAPI_DEFERRED_ERRORS, &lpmResM);
    if(HR_FAILED(hr))
    {
        TraceFnResult(CreateMessage, hr);
        return hr;
    }

    *ppmM = lpmResM;

    DebugTraceResult(HrCreateOutMessage, hr);
    return hrSuccess;
}

//
//  Put the data from globals in the message
//
HRESULT HrInitMsg(LPMESSAGE pmsg)
{
    HRESULT hr;
    LPADRLIST pal = NULL;
    enum {iSubj, iSentMail, iConvTopic, iConvIdx, iMsgClass, cProps};
    // PR_SUBJECT, PR_SENTMAIL_ENTRYID,
    // PR_CONVERSATION_TOPIC, PR_COVERSATION_INDEX

    SPropValue props[cProps];
    ULONG cbConvIdx = 0;
    LPBYTE lpbConvIdx = NULL;

    //recipients
    hr = HrCreateAddrList(&pal);
    if(HR_FAILED(hr))
        goto err;

    Assert(pal);

    hr = pmsg->ModifyRecipients(0, pal);
    if(HR_FAILED(hr))
    {
        goto err;
    }

    //subject  and conversation topic
    if(szSubject)
    {
        props[iSubj].ulPropTag = PR_SUBJECT;
        props[iSubj].Value.LPSZ = szSubject;

        props[iConvTopic].ulPropTag = PR_CONVERSATION_TOPIC;
        props[iConvTopic].Value.LPSZ = szSubject;
    }
    else
    {
        props[iSubj].ulPropTag = PR_NULL;
        props[iConvTopic].ulPropTag = PR_NULL;
    }

    // conversaton index
    if(!ScAddConversationIndex(0, NULL, &cbConvIdx, &lpbConvIdx))
    {
        props[iConvIdx].ulPropTag = PR_CONVERSATION_INDEX;
        props[iConvIdx].Value.bin.lpb = lpbConvIdx;
        props[iConvIdx].Value.bin.cb = cbConvIdx;
    }
    else
    {
        props[iConvIdx].ulPropTag = PR_NULL;
    }

    // sent mail entry id (we want to leave a copy in the "Sent Items" folder)
    props[iSentMail] = *pvalSentMailEID;

    props[iMsgClass].ulPropTag = PR_MESSAGE_CLASS;
    props[iMsgClass].Value.lpszA = "IPM.Note";

    hr = pmsg->SetProps(cProps, (LPSPropValue)&props, NULL);
    if(HR_FAILED(hr))
    {
        TraceFnResult(SetProps, hr);
        goto err;
    }

    //body (PR_RTF_COMPRESSED)

    hr = HrSetRTF(pmsg);
    if(HR_FAILED(hr))
        goto err;

err:
    FreePadrlist(pal);
    MAPIFreeBuffer(lpbConvIdx);

    DebugTraceResult(HrInitMsg, hr);
    return hr;

}

//
//  Put the context of the input file in PR_RTF_COMPRESSED
//
HRESULT HrSetRTF(LPMESSAGE pmsg)
{
    if(!szRTFFile)
        return hrSuccess;

    Assert(pmsg);

    HRESULT hr;
    LPSTREAM lpstrRTFComp = NULL;
    LPSTREAM lpstrRTF = NULL;
    LPSTREAM lpstrRTFFile = NULL;
    STATSTG statstg;
    BOOL fUpdated;
    LPSPropValue pvalSupMask = NULL;

    hr = OpenStreamOnFile(MAPIAllocateBuffer, MAPIFreeBuffer,
                            STGM_READ | STGM_SHARE_DENY_WRITE,
                            szRTFFile,  NULL, &lpstrRTFFile);
    if(HR_FAILED(hr))
    {
        TraceFnResult(OpenStreamOnFile, hr);
        printf("Can't open file %s \n", szRTFFile);
        goto err;
    }

    hr = pmsg->OpenProperty (PR_RTF_COMPRESSED, &IID_IStream,
                        0, MAPI_CREATE | MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                            (LPUNKNOWN FAR *)&lpstrRTFComp);
    if(HR_FAILED(hr))
    {
        TraceFnResult(OpenProperty, hr);
        goto err;
    }

    hr = HrGetOneProp(pmsg, PR_STORE_SUPPORT_MASK, &pvalSupMask);
    if(HR_FAILED(hr))
    {
        TraceFnResult(HrGetOneProp, hr);
        goto err;
    }

    hr = WrapCompressedRTFStream(lpstrRTFComp,
                MAPI_MODIFY | (pvalSupMask->Value.l & STORE_UNCOMPRESSED_RTF),
                &lpstrRTF);
    if(HR_FAILED(hr))
    {
        TraceFnResult(WrapCompressedRTFStream, hr);
        goto err;
    }

    hr = lpstrRTFFile->Stat(&statstg, STATFLAG_NONAME);
    if(HR_FAILED(hr))
    {
        TraceFnResult(Stat, hr);
        goto err;
    }

    hr = lpstrRTFFile->CopyTo(lpstrRTF, statstg.cbSize, NULL, NULL);
    if(HR_FAILED(hr))
    {
        TraceFnResult(CopyTo, hr);
        goto err;
    }

    hr = lpstrRTF->Commit(STGC_OVERWRITE);
    if(HR_FAILED(hr))
    {
        TraceFnResult(Commit, hr);
        goto err;
    }

    if( !(pvalSupMask->Value.l & STORE_RTF_OK))
    {
#ifdef DEBUG
        printf("RTFSync will be called\n");
#endif //DEBUG

        hr = RTFSync(pmsg, RTF_SYNC_RTF_CHANGED, &fUpdated);
        if(HR_FAILED(hr))
        {
            TraceFnResult(RTFSync, hr);
            goto err;
        }
    }

err:

    UlRelease(lpstrRTF);
    UlRelease(lpstrRTFComp);
    UlRelease(lpstrRTFFile);

    MAPIFreeBuffer(pvalSupMask);

    DebugTraceResult(HrSetRTF, hr);

    return hr;
}

//
//  Create an adrlist with resolved recipients
//
HRESULT HrCreateAddrList(LPADRLIST * ppal)
{

    HRESULT hr;
    LPADRLIST pal = NULL;
    int ind;
    #define chDELIMITER ';'

    int nToRecips = 1;
    LPSTR strToken = szToRecips;
    while (strToken = strchr(strToken, chDELIMITER))
    {
        ++strToken;
        ++nToRecips;
    }

    int cb = CbNewADRLIST(nToRecips);

    hr = MAPIAllocateBuffer(cb, (LPVOID FAR *) &pal);
    if(hr)
    {
        TraceFnResult(MAPIAllocateBuffer, hr);
        goto err;
    }
    ZeroMemory(pal, cb);

    hr = MAPIAllocateBuffer(2 * sizeof(SPropValue),
                            (LPVOID FAR *)&pal->aEntries[0].rgPropVals);
    if(hr)
    {
        TraceFnResult(MAPIAllocateBuffer, hr);
        goto err;
    }

    pal->aEntries[0].rgPropVals[0].ulPropTag = PR_DISPLAY_NAME;
    pal->aEntries[0].rgPropVals[0].Value.LPSZ = szToRecips;
    pal->aEntries[0].rgPropVals[1].ulPropTag = PR_RECIPIENT_TYPE;
    pal->aEntries[0].rgPropVals[1].Value.l= MAPI_TO;
    pal->aEntries[0].cValues = 2;

    strToken = szToRecips;

    for(ind = 1; ind < nToRecips; ++ind)
    {
        LPADRENTRY pae = &pal->aEntries[ind];

        hr = MAPIAllocateBuffer(2 * sizeof(SPropValue),
                            (LPVOID FAR *)&pae->rgPropVals);
        if(hr)
        {
            TraceFnResult(MAPIAllocateBuffer, hr);
            goto err;
        }

        strToken = strchr(strToken, chDELIMITER);
        Assert(strToken);

        *strToken++ = '\0';

        pae->rgPropVals[0].ulPropTag = PR_DISPLAY_NAME;
        pae->rgPropVals[0].Value.LPSZ = strToken;
        pae->rgPropVals[1].ulPropTag = PR_RECIPIENT_TYPE;
        pae->rgPropVals[1].Value.l = MAPI_TO;
        pae->cValues = 2;
    }

    pal->cEntries = nToRecips;
    hr = pabAddrB->ResolveName(0, 0, NULL, pal);
    if(HR_FAILED(hr))
    {
        TraceFnResult(ResolveName, hr);
        goto err;
    }

    *ppal = pal;

    return hrSuccess;

err:

    FreePadrlist(pal);

    DebugTraceResult(HrCreateAddrList, hr);
    return hr;
}

//======================================================================
//  ProcessCommandLineArguments - show usage to the user
//======================================================================

BOOL fParseCommandLine(int argc,char * argv[])
{
    BOOL fResult;
    int iCount;

    fResult = FALSE;
    _try
    {
      if (argc < 2)                                // Must be at least 1 argument
      {
         return fResult;                           // Show Usage to user.
      }

      for (iCount = 1; iCount < argc; iCount++)
      {
        if ((*argv[iCount] == '-') || (*argv[iCount] == '/'))
        {
            switch (tolower(*(argv[iCount]+1)))            // Process switches
            {
            case 'u':
                szProfile = argv[++iCount];
                break;

            case 'p':
                szPassword = argv[++iCount];
                break;

            case 't':
                szToRecips = argv[++iCount];
                break;

            case 's':
                szSubject = argv[++iCount];
                break;

            case 'f':
                szRTFFile = argv[++iCount];
                break;

            case 'h':
            case '?':                   // User asked for help '-?'
            default:
                return fResult;
            } // Switch
        }   // If
      } // For Argument Count

      // Check for required command line arguments

      if (szToRecips)
      {
         fResult = TRUE;
      }
      else
      {
         printf("ERROR: Recipient(s) required.\n");

      }
      return fResult;

    }
    _finally
    {
        if (!fResult)
        {
            printf( "\nSend MAPI messages from Win32 command line.\n" );
            printf( "Usage: sendrtf [-u] [-p] -t [-s] [-f][-v][-?]\n" );
            printf( "Where...\n\n" );
            printf( "-u  profile name \n" );
            printf( "-p  profile password \n" );
            printf( "-t  recipient(s) (multiples must be separated by ';' and "
                    "enclosed in double\n");
            printf( "    quotes. recipients must not be ambiguous in default "
                    "address book.)\n" );
            //printf( "-c  specifies mail copy list (cc: list) \n" );
            printf( "-s  subject line \n" );
            printf( "-f  file (in rtf format) to be used as a message body\n" );
            //printf( "-v  generates verbose output\n" );
            printf( "-?  prints this message\n" );
         }
    } // Finally
} // End of Function


README.TXT

Sample Simple MAPI Client

The Simple.Cli sample application illustrates using Simple MAPI functions.

The sample is a very simple mail client. Using it is intuitive and
straightforward.

To run this application, you have to have MAPI run-time binaries installed on
your system. For installation instructions, see the readme.wri in the
mstools\mapi\ directory.

When writing simple MAPI applications, the addresses of all of the simple
MAPI functions has to be obtained explicitly (using GetProcAddress), as
opposed to linking to the import library for MAPI.DLL.

If sending/receiving mail is not the primary function of your application, you
can test presence of simple MAPI in a system without incurring high cost of
trying to load a DLL. You can test the value of the MAPI variable. It is 1 if
simple MAPI is installed. The variable is located in the [mail] section of
win.ini on 16-bit Windows and Windows NT version 3.51 and earlier and under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem in the
registry on Windows 95 and Windows NT 4.0 and later.

Known Problems
--------------

When you Reply All to a message that you sent to yourself, your name will
appear twice in the To well. This is caused by the fact that Simple MAPI
does not expose either search key or a way to compare entry IDs, so there is
no safe way to remove duplicates from the recipient list.


BITMAP.H

/*
 -  B I T M A P . H
 *
 *  Purpose:
 *      Definitions for the Owner-Drawn Listbox bitmap stuff.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

/* Listbox string formatting defines */

#define chBOLD              TEXT('\b')
#define chUNDERLINE         TEXT('\v')
#define chTAB               TEXT('\t')
#define chBITMAP            TEXT('\001')

#define BMWIDTH             16
#define BMHEIGHT            16
#define NUMBMPS             4
#define RGBREPLACE          ((DWORD)0x00FF0000) // solid blue

/* Font style of font to use in listbox */

typedef struct
{
    int     lfHeight;
    int     lfWeight;
    BYTE    lfItalic;
    TCHAR   lfFaceName[LF_FACESIZE];
} FONTSTYLE;

/* Function Prototypes */

VOID    DrawItem(LPDRAWITEMSTRUCT pDI);
VOID    MeasureItem(HANDLE hwnd, LPMEASUREITEMSTRUCT mis);
VOID    SetRGBValues(void);
BOOL    InitBmps(HWND hwnd, int idLB);
VOID    DeInitBmps(void);
BOOL    LoadBitmapLB(void);
VOID    DeleteBitmapLB(void);
VOID    ConvertDateRec(LPSTR lpszDateRec, LPSTR lpszDateDisplay);


CLIENT.C

/*
 -  C L I E N T . C
 -
 *  Purpose:
 *      Sample mail client for the MAPI 1.0 PDK.
 *              Exclusively uses the Simple MAPI interface.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <commdlg.h>
#include <mapiwin.h>
#include <mapidbg.h>
#include "client.h"
#include "bitmap.h"
#include "pvalloc.h"

HANDLE hInst;
HINSTANCE hlibMAPI = 0;

LPMAPILOGON lpfnMAPILogon = NULL;
LPMAPILOGOFF lpfnMAPILogoff = NULL;
LPMAPISENDMAIL lpfnMAPISendMail = NULL;
LPMAPISENDDOCUMENTS lpfnMAPISendDocuments = NULL;
LPMAPIFINDNEXT lpfnMAPIFindNext = NULL;
LPMAPIREADMAIL lpfnMAPIReadMail = NULL;
LPMAPISAVEMAIL lpfnMAPISaveMail = NULL;
LPMAPIDELETEMAIL lpfnMAPIDeleteMail = NULL;
LPMAPIFREEBUFFER lpfnMAPIFreeBuffer = NULL;
LPMAPIADDRESS lpfnMAPIAddress = NULL;
LPMAPIDETAILS lpfnMAPIDetails = NULL;
LPMAPIRESOLVENAME lpfnMAPIResolveName = NULL;

/* Static Data */

static BOOL fDialogIsActive = FALSE;
static DWORD cUsers = 0;
static ULONG flSendMsgFlags = 0;
static LHANDLE lhSession = 0L;
static HBITMAP hReadBmp = 0;
static HBITMAP hReadABmp = 0;
static HBITMAP hUnReadBmp = 0;
static HBITMAP hUnReadABmp = 0;
static HCURSOR hWaitCur;
static LPMSGID lpReadMsgNode;
static lpMapiMessage lpmsg = NULL;

#ifdef _WIN32
#define szMAPIDLL       "MAPI32.DLL"
#else
#define szMAPIDLL       "MAPI.DLL"
#endif

int WINAPI
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmd, int nCmdShow)
{
    MSG msg;

    if (!hPrevInst)
    if (!InitApplication (hInstance))
        return (FALSE);

    if (!InitInstance (hInstance, nCmdShow))
    return (FALSE);

    while (GetMessage (&msg, 0, 0, 0))
    {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
    }

    DeinitApplication ();

    return (msg.wParam);
}

/*
 -  InitApplication
 -
 *  Purpose:
 *      Initialize the application.
 *
 *  Parameters:
 *      hInstance   - Instance handle
 *
 *  Returns:
 *      True/False
 *
 */

BOOL
InitApplication (HANDLE hInstance)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (hInstance, "NoMail");
    wc.hCursor = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName = "MailMenu";
    wc.lpszClassName = "Client";

    return (RegisterClass (&wc));
}

/*
 -  InitInstance
 -
 *  Purpose:
 *      Initialize this instance.
 *
 *  Parameters:
 *      hInstance   - Instance handle
 *      nCmdShow    - Do we show the window?
 *
 *  Returns:
 *      True/False
 *
 */

BOOL
InitInstance (HANDLE hInstance, int nCmdShow)
{
    HWND hWnd;
    BOOL fInit;
    ULONG ulResult;

    hInst = hInstance;

    hWnd = CreateWindow ("Client", "MAPI Sample Mail Client",
        WS_OVERLAPPEDWINDOW, 5, 5, 300, 75, 0, 0, hInst, NULL);

    if (!hWnd)
    return (FALSE);

    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);

    hWaitCur = LoadCursor(0, IDC_WAIT);

    if (fInit = InitSimpleMAPI ())
    {

        /* MAPILogon might yield control to Windows. So to prevent the user
        from clicking "logon" while we are in the process of loggin on we
        have to disable it*/
        SecureMenu(hWnd, TRUE);

        if ((ulResult = MAPILogon ((ULONG) hWnd, NULL, NULL,
            MAPI_LOGON_UI | MAPI_NEW_SESSION,
            0, &lhSession)) == SUCCESS_SUCCESS)
        {
            ToggleMenuState (hWnd, TRUE);
        }
        else
        {
            SecureMenu(hWnd, FALSE);
            lhSession = 0;
            MakeMessageBox (hWnd, ulResult, IDS_LOGONFAIL, MBS_ERROR);
        }
    }

    return (fInit);
}

/*
 -  InitSimpleMAPI
 -
 *  Purpose:
 *      Loads the DLL containing the simple MAPI functions and sets
 *      up a pointer to each. Wrappers for the  function pointers
 *      are declared in SMAPI.H.
 *
 *  Returns:
 *      TRUE if sucessful, else FALSE
 *
 *  Side effects:
 *      Loads a DLL and sets up function pointers
 */
BOOL
InitSimpleMAPI (void)
{
    UINT fuError;

    /*
     *Check if MAPI is installed on the system
     */
    if(!fSMAPIInstalled())
        return FALSE;

    fuError = SetErrorMode(SEM_NOOPENFILEERRORBOX);
    hlibMAPI = LoadLibrary(szMAPIDLL);
    SetErrorMode(fuError);

#ifdef _WIN32
    if (!hlibMAPI)
#else
    if (hlibMAPI < 32)
#endif
    return (FALSE);

    if (!(lpfnMAPILogon = (LPMAPILOGON) GetProcAddress (hlibMAPI, "MAPILogon")))
    return (FALSE);
    if (!(lpfnMAPILogoff = (LPMAPILOGOFF) GetProcAddress (hlibMAPI, "MAPILogoff")))
    return (FALSE);
    if (!(lpfnMAPISendMail = (LPMAPISENDMAIL) GetProcAddress (hlibMAPI, "MAPISendMail")))
    return (FALSE);
    if (!(lpfnMAPISendDocuments = (LPMAPISENDDOCUMENTS) GetProcAddress (hlibMAPI, "MAPISendDocuments")))
    return (FALSE);
    if (!(lpfnMAPIFindNext = (LPMAPIFINDNEXT) GetProcAddress (hlibMAPI, "MAPIFindNext")))
    return (FALSE);
    if (!(lpfnMAPIReadMail = (LPMAPIREADMAIL) GetProcAddress (hlibMAPI, "MAPIReadMail")))
    return (FALSE);
    if (!(lpfnMAPISaveMail = (LPMAPISAVEMAIL) GetProcAddress (hlibMAPI, "MAPISaveMail")))
    return (FALSE);
    if (!(lpfnMAPIDeleteMail = (LPMAPIDELETEMAIL) GetProcAddress (hlibMAPI, "MAPIDeleteMail")))
    return (FALSE);
    if (!(lpfnMAPIFreeBuffer = (LPMAPIFREEBUFFER) GetProcAddress (hlibMAPI, "MAPIFreeBuffer")))
    return (FALSE);
    if (!(lpfnMAPIAddress = (LPMAPIADDRESS) GetProcAddress (hlibMAPI, "MAPIAddress")))
    return (FALSE);
    if (!(lpfnMAPIDetails = (LPMAPIDETAILS) GetProcAddress (hlibMAPI, "MAPIDetails")))
    return (FALSE);
    if (!(lpfnMAPIResolveName = (LPMAPIRESOLVENAME) GetProcAddress (hlibMAPI, "MAPIResolveName")))
    return (FALSE);

    return (TRUE);
}

/*
 -  fSMAPIInstalled
 -
 *  Purpose:
 *      Checks the appropriate win.ini/registry value to see if Simple MAPI is
 *      installed in the system.
 *
 *  Returns:
 *      TRUE if Simple MAPI is installed, else FALSE
 *
 */
BOOL
fSMAPIInstalled(void)
{
#ifdef _WIN32
    /* on win32, if it's NT 3.51 or lower the value to check is
        win.ini \ [Mail] \ MAPI, otherwise it's a registry value
        HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem\MAPI
    */

    OSVERSIONINFO osvinfo;
    LONG lr;
    HKEY hkWMS;

    #define MAPIVSize 8
    char szMAPIValue[MAPIVSize];
    DWORD dwType;
    DWORD cbMAPIValue = MAPIVSize;

    osvinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if(!GetVersionEx(&osvinfo))
        return FALSE;

    if( osvinfo.dwMajorVersion > 3 ||
        (osvinfo.dwMajorVersion == 3 && osvinfo.dwMinorVersion > 51))
    { //check the registry value
        lr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                        "SOFTWARE\\Microsoft\\Windows Messaging Subsystem",
                         0, KEY_READ, &hkWMS);
        if(ERROR_SUCCESS == lr)
        {
            lr = RegQueryValueEx(hkWMS, "MAPI", 0, &dwType, szMAPIValue, &cbMAPIValue);
            RegCloseKey(hkWMS);
            if(ERROR_SUCCESS == lr)
            {
                Assert(dwType == REG_SZ);
                if(lstrcmp(szMAPIValue, "1") == 0)
                    return TRUE;
            }
        }

        return FALSE;
    }

    /* fall through*/
#endif /*_WIN32*/

    /*check the win.ini value*/
    return GetProfileInt("Mail", "MAPI", 0);

}

void
DeinitApplication ()
{
    DeinitSimpleMAPI ();
}

void
DeinitSimpleMAPI ()
{
    if (hlibMAPI)
    {
    FreeLibrary (hlibMAPI);
    hlibMAPI = 0;
    }
}

/*
 -  MainWndProc
 -
 *  Purpose:
 *      Main Window Procedure for test program.
 *
 *  Parameters:
 *      hWnd
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *
 *
 */

LONG FAR PASCAL
MainWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    ULONG ulResult;

    switch (msg)
    {
    case WM_COMMAND:
    switch (LOWORD (wParam))
    {
    case IDM_LOGON:
        if (!lhSession)
        {
        /* MAPILogon might yield control to Windows. So to prevent the user
        from clicking "logon" while we are in the process of loggin on we
        have to disable it*/
        SecureMenu(hWnd, TRUE);

        if ((ulResult = MAPILogon ((ULONG) hWnd, NULL, NULL,
                MAPI_LOGON_UI | MAPI_NEW_SESSION,
                0, &lhSession)) == SUCCESS_SUCCESS)
        {
            ToggleMenuState (hWnd, TRUE);
        }
        else
        {
            SecureMenu(hWnd, FALSE);
            lhSession = 0;
            MakeMessageBox (hWnd, ulResult, IDS_LOGONFAIL, MBS_ERROR);
        }
        }
        break;

    case IDM_LOGOFF:
        if (lhSession)
        {
        MAPILogoff (lhSession, (ULONG) hWnd, 0, 0);
        ToggleMenuState (hWnd, FALSE);
        lhSession = 0;
        }
        break;

    case IDM_COMPOSE:
            fDialogIsActive = TRUE;
        DialogBox (hInst, "ComposeNote", hWnd, ComposeDlgProc);
            fDialogIsActive = FALSE;
        break;

    case IDM_READ:
            fDialogIsActive = TRUE;
        DialogBox (hInst, "InBox", hWnd, InBoxDlgProc);
            fDialogIsActive = FALSE;
        break;

    case IDM_SEND:
        if(lhSession)
            {
                MapiMessage msgSend;

                memset(&msgSend, 0, sizeof(MapiMessage));
                fDialogIsActive = TRUE;
                MAPISendMail(lhSession, (ULONG)hWnd, &msgSend, MAPI_DIALOG, 0L);
                fDialogIsActive = FALSE;
            }
        break;

    case IDM_ADDRBOOK:
        if (lhSession)
        {
                fDialogIsActive = TRUE;
        if ((ulResult = MAPIAddress (lhSession, (ULONG) hWnd,
                NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL)))
        {
            if (ulResult != MAPI_E_USER_ABORT)
            MakeMessageBox (hWnd, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);
        }
                fDialogIsActive = FALSE;
        }
        break;

    case IDM_DETAILS:
        if (lhSession)
            {
                fDialogIsActive = TRUE;
        DialogBox(hInst, "Details", hWnd, DetailsDlgProc);
                fDialogIsActive = FALSE;
            }
        break;

    case IDM_ABOUT:
            fDialogIsActive = TRUE;
        DialogBox (hInst, "AboutBox", hWnd, AboutDlgProc);
            fDialogIsActive = FALSE;
        break;

    case IDM_EXIT:
        if (lhSession)
        MAPILogoff (lhSession, (ULONG) hWnd, 0, 0);

        PostQuitMessage (0);
        break;

    default:
        return (DefWindowProc (hWnd, msg, wParam, lParam));
    }
    break;

    case WM_QUERYENDSESSION:
    {

        /*
         *      If we have a modal dialog open (all our dialogs are modal, so
         *      just see if we have a dialog open), veto the shutdown.
         */

        if (fDialogIsActive)
        {
            LPCSTR szTitle = "MAPI Sample Mail Client";
            char szText[256];

        LoadString (hInst, IDS_DIALOGACTIVE, szText, 255);

        #ifdef WIN16
            MessageBox((HWND)NULL, szText, szTitle, MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
        #else
            MessageBoxA(NULL, szText, szTitle, MB_OK | MB_ICONSTOP | MB_TASKMODAL | MB_SETFOREGROUND);
        #endif
        return FALSE;
        }

        else
        {
        return TRUE;
        }
    }

    case WM_ENDSESSION:

        if (wParam)
        {
        DestroyWindow (hWnd);
        }

    break;

    case WM_CLOSE:
    case WM_DESTROY:
    if (lhSession)
        MAPILogoff (lhSession, (ULONG) hWnd, 0, 0);

    PostQuitMessage (0);
    break;

    default:
    return (DefWindowProc (hWnd, msg, wParam, lParam));
    }
    return FALSE;
}

/*
 -  AboutDlgProc
 -
 *  Purpose:
 *      About box dialog procedure
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
AboutDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{

#include <pdkver.h>

    char    rgchVersion[80];

    switch (msg)
    {
    case WM_INITDIALOG:
        wsprintf(rgchVersion, "Version %d.%d.%d (%s)", rmj, rmm, rup,
            szVerName && *szVerName ? szVerName : "BUDDY");
        SetDlgItemText(hDlg, IDC_VERSION, rgchVersion);
    return TRUE;

    case WM_COMMAND:
    if (wParam == IDOK || wParam == IDCANCEL)
    {
        EndDialog (hDlg, TRUE);
        return TRUE;
    }
    break;
    }
    return FALSE;
}

/*
 -  OptionsDlgProc
 -
 *  Purpose:
 *      Message Options dialog procedure
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
OptionsDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    switch (msg)
    {
    case WM_INITDIALOG:
    CheckDlgButton (hDlg, IDC_RETURN,
        !!(flSendMsgFlags & MAPI_RECEIPT_REQUESTED));
    return TRUE;

    case WM_COMMAND:
    switch (LOWORD (wParam))
    {
    case IDOK:
        if (IsDlgButtonChecked (hDlg, IDC_RETURN))
        flSendMsgFlags |= MAPI_RECEIPT_REQUESTED;
        else
        flSendMsgFlags &= ~MAPI_RECEIPT_REQUESTED;

    case IDCANCEL:
        EndDialog (hDlg, TRUE);
        return TRUE;
    }
    break;
    }
    return FALSE;
}

/*
 -  DetailsDlgProc
 -
 *  Purpose:
 *      User Details dialog procedure
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
DetailsDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    LPSTR lpszType = NULL;
    LPSTR lpszAddr = NULL;
    LPSTR lpszName;
    ULONG cRecips;
    ULONG ulResult;
    lpMapiRecipDesc lpRecip = NULL;

    switch (msg)
    {
    case WM_INITDIALOG:
    while(!lpRecip)
    {
        if ((ulResult = MAPIAddress (lhSession, (ULONG) hDlg,
            "Select One User", 1, "User:", 0, NULL, 0, 0,
            &cRecips, &lpRecip)))
        {
        if (ulResult != MAPI_E_USER_ABORT)
            MakeMessageBox (hDlg, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);

        EndDialog (hDlg, TRUE);
        return TRUE;
        }

        if (cRecips == 0)
        {
        EndDialog (hDlg, TRUE);
        return TRUE;
        }

        if (cRecips > 1)
        {
        cRecips = 0;
        MAPIFreeBuffer (lpRecip);
        lpRecip = NULL;
        MakeMessageBox (hDlg, 0, IDS_DETAILS_TOO_MANY, MBS_OOPS);
        }
    }
    lpszName = lpRecip->lpszName;
    if(lpRecip->lpszAddress)
    {
        lpszType = strtok(lpRecip->lpszAddress, ":");
        lpszAddr = strtok(NULL, "\n");
    }

    SetDlgItemText(hDlg, IDC_NAME, lpszName);
    SetDlgItemText(hDlg, IDC_TYPE, (lpszType ? lpszType : "MSPEER"));
    SetDlgItemText(hDlg, IDC_ADDR, (lpszAddr ? lpszAddr : ""));

    MAPIFreeBuffer (lpRecip);
    return TRUE;

    case WM_COMMAND:
    if(LOWORD(wParam) == IDC_CLOSE || LOWORD(wParam) ==IDCANCEL)
    {
        EndDialog (hDlg, TRUE);
        return TRUE;
    }
    break;
    }
    return FALSE;
}

/*
 -  ComposeDlgProc
 -
 *  Purpose:
 *      Dialog procedure for the ComposeNote dialog.
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
ComposeDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    char szUnResNames[TO_EDIT_MAX];
    char szDisplayNames[TO_EDIT_MAX];
   /* char szAttach[FILE_ATTACH_MAX];*/
    BOOL fUnResTo, fUnResCc;
    LONG cb, cLines;
    ULONG ulResult;
    HCURSOR hOldCur;
    static LPSTR lpszSubject;
    static LPSTR lpszNoteText;
    static ULONG cRecips;
    static ULONG cNewRecips;
    static ULONG cAttach;
    static lpMapiRecipDesc lpRecips;
    static lpMapiRecipDesc lpNewRecips;
    static lpMapiFileDesc lpAttach;
    ULONG idx;

    switch (msg)
    {
    case WM_INITDIALOG:
    if (lpmsg)
    {
        /* ComposeNote is being called to either forward or reply */
        /* to a message in the Inbox.  So, we'll initialize the   */
        /* ComposeNote form with data from the global MapiMessage */

        lpszSubject = lpmsg->lpszSubject;
        lpszNoteText = lpmsg->lpszNoteText;
        cRecips = lpmsg->nRecipCount;
        cAttach = lpmsg->nFileCount;
        lpRecips = lpmsg->lpRecips;
        lpAttach = lpmsg->lpFiles;

        if (cRecips)
        {
        MakeDisplayNameStr (szDisplayNames, MAPI_TO,
            cRecips, lpRecips);
        if (*szDisplayNames)
            SetDlgItemText (hDlg, IDC_TO, szDisplayNames);

        MakeDisplayNameStr (szDisplayNames, MAPI_CC,
            cRecips, lpRecips);
        if (*szDisplayNames)
            SetDlgItemText (hDlg, IDC_CC, szDisplayNames);
        }
        SetDlgItemText (hDlg, IDC_SUBJECT, lpmsg->lpszSubject);
        SetDlgItemText (hDlg, IDC_NOTE, lpmsg->lpszNoteText);
        if (!cAttach)
        {
            EnableWindow (GetDlgItem (hDlg, IDC_CATTACHMENT), FALSE);
        EnableWindow (GetDlgItem (hDlg, IDT_CATTACHMENT), FALSE);
        }
        else
        {
            for(idx = 0; idx < cAttach; idx++)
            if (lpAttach[idx].lpszFileName)
                SendDlgItemMessage(hDlg, IDC_CATTACHMENT, LB_ADDSTRING, 0,
                (LPARAM)lpAttach[idx].lpszFileName);

           /*SendDlgItemMessage(hDlg, IDC_CATTACHMENT, LB_SETCURSEL, 0, 0L);*/
        }

        SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);
        SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);
        SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_SETMODIFY, FALSE, 0);
        SendDlgItemMessage (hDlg, IDC_NOTE, EM_SETMODIFY, FALSE, 0);
        if(cRecips)
        SetFocus (GetDlgItem (hDlg, IDC_NOTE));
        else
        SetFocus (GetDlgItem (hDlg, IDC_TO));
    }
    else
    {
        lpmsg = (lpMapiMessage)PvAlloc(sizeof(MapiMessage));

        if (!lpmsg)
        goto cleanup;

            memset (lpmsg, 0, sizeof (MapiMessage));

        lpszSubject = NULL;
        lpszNoteText = NULL;
        cRecips = 0;
        cAttach = 0;
        lpRecips = NULL;
        lpNewRecips = NULL;
        lpAttach = NULL;

        lpmsg->flFlags = flSendMsgFlags;
        SetFocus (GetDlgItem (hDlg, IDC_TO));
    }
    return FALSE;

    case WM_COMMAND:
    switch (LOWORD (wParam))
    {
    case IDC_ATTACH:
        if (GetNextFile (hDlg, (ULONG) -1, &cAttach, &lpAttach) == SUCCESS_SUCCESS)
        {
                /* if the first attachment */
                if (cAttach == 1)
                {
                    EnableWindow (GetDlgItem (hDlg, IDC_CATTACHMENT), TRUE);
            EnableWindow (GetDlgItem (hDlg, IDT_CATTACHMENT), TRUE);
                }

                if (lpAttach[cAttach - 1].lpszFileName)
            SendDlgItemMessage(hDlg, IDC_CATTACHMENT, LB_ADDSTRING, 0,
            (LPARAM)lpAttach[cAttach -1].lpszFileName);

             /* Now, send a little render message to the NoteText edit */

        /*wsprintf (szAttach, "<<File: %s>>",
            lpAttach[cAttach - 1].lpszFileName);

        SendDlgItemMessage (hDlg, IDC_NOTE, EM_REPLACESEL, 0,
            (LPARAM) ((LPSTR) szAttach));*/
        }
        break;

    case IDC_ADDRBOOK:
            SendMessage(hDlg, WM_COMMAND, MAKELONG(IDC_RESOLVE,0), 0);
        ulResult = MAPIAddress (lhSession, (ULONG) hDlg, NULL,
        2, NULL, cRecips, lpRecips, 0, 0,
        &cNewRecips, &lpNewRecips);
        if (ulResult)
        {
        if (ulResult != MAPI_E_USER_ABORT)
            MakeMessageBox (hDlg, ulResult, IDS_ADDRBOOKFAIL, MBS_ERROR);
        }
        else
        {
        if (cNewRecips)
        {
            PvFree(lpRecips);
            lpRecips = (lpMapiRecipDesc)PvAlloc(cNewRecips*sizeof(MapiRecipDesc));
            cRecips = cNewRecips;

                    while(cNewRecips--)
                        CopyRecipient(lpRecips, &lpRecips[cNewRecips],
                                &lpNewRecips[cNewRecips]);

            MAPIFreeBuffer(lpNewRecips);
            lpNewRecips = NULL;
            cNewRecips = 0;

            MakeDisplayNameStr (szDisplayNames, MAPI_TO,
            cRecips, lpRecips);
            if (*szDisplayNames)
            SetDlgItemText (hDlg, IDC_TO, szDisplayNames);

            MakeDisplayNameStr (szDisplayNames, MAPI_CC,
            cRecips, lpRecips);
            if (*szDisplayNames)
            SetDlgItemText (hDlg, IDC_CC, szDisplayNames);

            SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);
            SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);
        }
        }
        break;

    case IDC_OPTIONS:
        DialogBox (hInst, "Options", hDlg, OptionsDlgProc);
        break;

    case IDC_SEND:
    case IDC_RESOLVE:
        fUnResTo = FALSE;
        fUnResCc = FALSE;

        hOldCur = SetCursor(hWaitCur);

        /* Get the names from the To: field and resolve them first */

        /*if (SendDlgItemMessage (hDlg, IDC_TO, EM_GETMODIFY, 0, 0) && */
         if (cb = SendDlgItemMessage (hDlg, IDC_TO, WM_GETTEXT,
            (WPARAM)sizeof(szUnResNames), (LPARAM)szUnResNames))
        {
        if (!ResolveFriendlyNames (hDlg, szUnResNames, MAPI_TO,
            &cRecips, &lpRecips))
        {
            MakeDisplayNameStr (szDisplayNames, MAPI_TO,
            cRecips, lpRecips);
            if (*szDisplayNames)
            {
            if (*szUnResNames)
            {
                lstrcat (szDisplayNames, "; ");
                lstrcat (szDisplayNames, szUnResNames);
                fUnResTo = TRUE;
            }

            SetDlgItemText (hDlg, IDC_TO, szDisplayNames);
            }
            else
            {
            if (*szUnResNames)
            {
                SetDlgItemText (hDlg, IDC_TO, szUnResNames);
                fUnResTo = TRUE;
            }
            }
        }
        /*SendDlgItemMessage (hDlg, IDC_TO, EM_SETMODIFY, FALSE, 0);*/
        }

        /* Now, get the names from the Cc: field and resolve them */

        /*if (SendDlgItemMessage (hDlg, IDC_CC, EM_GETMODIFY, 0, 0) &&*/
        if (cb = SendDlgItemMessage (hDlg, IDC_CC, WM_GETTEXT,
            (WPARAM)sizeof(szUnResNames), (LPARAM)szUnResNames))
        {
        if (!ResolveFriendlyNames (hDlg, szUnResNames, MAPI_CC,
            &cRecips, &lpRecips))
        {
            MakeDisplayNameStr (szDisplayNames, MAPI_CC,
            cRecips, lpRecips);
            if (*szDisplayNames)
            {
            if (*szUnResNames)
            {
                lstrcat (szDisplayNames, "; ");
                lstrcat (szDisplayNames, szUnResNames);
                fUnResCc = TRUE;
            }

            SetDlgItemText (hDlg, IDC_CC, szDisplayNames);
            }
            else
            {
            if (*szUnResNames)
            {
                SetDlgItemText (hDlg, IDC_CC, szUnResNames);
                fUnResCc = TRUE;
            }
            }
        }
        /*SendDlgItemMessage (hDlg, IDC_CC, EM_SETMODIFY, FALSE, 0);*/
        }

        /* If we were just Resolving Names then we can leave now */

        if (LOWORD (wParam) == IDC_RESOLVE)
        {
        SetCursor(hOldCur);
        break;
        }

        if (cRecips == 0 || fUnResTo || fUnResCc)
        {
        if (!cRecips)
            MakeMessageBox (hDlg, 0, IDS_NORECIPS, MBS_OOPS);

        if (fUnResTo)
            SetFocus (GetDlgItem (hDlg, IDC_TO));
        else if (fUnResCc)
            SetFocus (GetDlgItem (hDlg, IDC_CC));
        else
            SetFocus (GetDlgItem (hDlg, IDC_TO));

        SetCursor(hOldCur);
        break;
        }

        /* Everything is OK so far, lets get the Subject */
        /* and the NoteText and try to send the message. */

        /* Get Subject from Edit */

        if (SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_GETMODIFY, 0, 0))
        {
        cb = SendDlgItemMessage (hDlg, IDC_SUBJECT, EM_LINELENGTH, 0, 0L);

        PvFree(lpszSubject);
        lpszSubject = (LPTSTR)PvAlloc(cb + 1);

        if (!lpszSubject)
            goto cleanup;

        GetDlgItemText (hDlg, IDC_SUBJECT, lpszSubject, (int)cb+1);
        }

        /* Get the NoteText from Edit */

        if (SendDlgItemMessage (hDlg, IDC_NOTE, EM_GETMODIFY, 0, 0))
        {
        cLines = SendDlgItemMessage (hDlg, IDC_NOTE,
            EM_GETLINECOUNT, 0, 0L);

        if (cLines)
        {
            /* Get the total number of bytes in the multi-line */

            cb = SendDlgItemMessage (hDlg, IDC_NOTE, EM_LINEINDEX,
            (UINT)cLines - 1, 0L);
            cb += SendDlgItemMessage (hDlg, IDC_NOTE, EM_LINELENGTH,
            (UINT)cb, 0L);

            /* The next line is to account for CR-LF pairs per line. */

            cb += cLines * 2;

                    PvFree(lpszNoteText);
            lpszNoteText = (LPTSTR)PvAlloc(cb + 1);

            if (!lpszNoteText)
            goto cleanup;

            /* Get the Note Text from the edit */

            GetDlgItemText (hDlg, IDC_NOTE, lpszNoteText, (int)cb);
        }
        else
        {
            /* Make an empty string for NoteText */

            lpszNoteText = (LPTSTR)PvAlloc(1);
            if (!lpszNoteText)
            goto cleanup;
            *lpszNoteText = '\0';
        }
        }

        lpmsg->lpszSubject = lpszSubject;
        lpmsg->lpszNoteText = lpszNoteText;
        lpmsg->nRecipCount = cRecips;
        lpmsg->lpRecips = lpRecips;
        lpmsg->nFileCount = cAttach;
        lpmsg->lpFiles = lpAttach;
        lpmsg->flFlags = flSendMsgFlags;

        ulResult = MAPISendMail (lhSession, (ULONG) hDlg, lpmsg, 0, 0);

        LogSendMail(ulResult);

        if (ulResult)
        {
        MakeMessageBox (hDlg, ulResult, IDS_SENDERROR, MBS_ERROR);
        SetCursor(hOldCur);
        break;
        }
cleanup:
        SetCursor(hOldCur);

    case IDCANCEL:
        PvFree(lpmsg->lpszMessageType);
        PvFree(lpmsg->lpszConversationID);
        PvFree(lpmsg);
        PvFree(lpRecips);
        PvFree(lpAttach);
        PvFree(lpszSubject);
        PvFree(lpszNoteText);
        lpmsg = NULL;

        EndDialog (hDlg, TRUE);
        return TRUE;
        break;

    default:
        break;
    }
    break;
    }
    return FALSE;
}

/*
 -  InBoxDlgProc
 -
 *  Purpose:
 *      Dialog procedure for the InBox dialog.
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
InBoxDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    char szMsgID[512];
    char szSeedMsgID[512];
    LPMSGID lpMsgNode;
    static LPMSGID lpMsgIdList = NULL;
    lpMapiMessage lpMessage;
    ULONG ulResult;
    DWORD nIndex;
    RECT Rect;
    HCURSOR hOldCur;

    switch (msg)
    {
    case WM_INITDIALOG:
    hOldCur = SetCursor(hWaitCur);

        InitBmps(hDlg, IDC_MSG);

    /* Populate List Box with all messages in InBox. */
    /* This is a painfully slow process for now.     */

    ulResult = MAPIFindNext (lhSession, (ULONG) hDlg, NULL, NULL,
        MAPI_GUARANTEE_FIFO | MAPI_LONG_MSGID, 0, szMsgID);

    while (ulResult == SUCCESS_SUCCESS)
    {
        ulResult = MAPIReadMail (lhSession, (ULONG) hDlg, szMsgID,
        MAPI_PEEK | MAPI_ENVELOPE_ONLY,
        0, &lpMessage);

        if (!ulResult)
        {
        lpMsgNode = MakeMsgNode (lpMessage, szMsgID);

        if (lpMsgNode)
        {
            InsertMsgNode (lpMsgNode, &lpMsgIdList);

            SendDlgItemMessage (hDlg, IDC_MSG, LB_ADDSTRING,
            0, (LONG) lpMsgNode);

}
        MAPIFreeBuffer (lpMessage);
        }

        lstrcpy (szSeedMsgID, szMsgID);
        ulResult = MAPIFindNext (lhSession, (ULONG) hDlg, NULL, szSeedMsgID,
        MAPI_GUARANTEE_FIFO | MAPI_LONG_MSGID, 0, szMsgID);
    }

    SetCursor(hOldCur);
    SetFocus (GetDlgItem (hDlg, IDC_MSG));
    return TRUE;
    break;

    case WM_SETFOCUS:
    SetFocus (GetDlgItem (hDlg, IDC_MSG));
    break;

    case WM_MEASUREITEM:
    /* Sets the height of the owner-drawn List-Box */
        MeasureItem(hDlg, (MEASUREITEMSTRUCT *)lParam);
    break;

    case WM_DRAWITEM:
    DrawItem((DRAWITEMSTRUCT *)lParam);
    break;

    case WM_DELETEITEM:
    /* This message is handled by the IDC_DELETE message */
    return TRUE;
    break;

    case WM_COMMAND:
    switch (LOWORD (wParam))
    {
    case IDC_NEW:
        hOldCur = SetCursor(hWaitCur);

        ulResult = MAPIFindNext (lhSession, (ULONG) hDlg, NULL, NULL,
        MAPI_UNREAD_ONLY | MAPI_LONG_MSGID, 0, szMsgID);

        while (ulResult == SUCCESS_SUCCESS)
        {
        if (!FindNode (lpMsgIdList, szMsgID))
        {
            ulResult = MAPIReadMail (lhSession, (ULONG) hDlg, szMsgID,
            MAPI_PEEK | MAPI_ENVELOPE_ONLY, 0, &lpMessage);

            if (!ulResult)
            {
            lpMsgNode = MakeMsgNode (lpMessage, szMsgID);
            InsertMsgNode (lpMsgNode, &lpMsgIdList);

            SendDlgItemMessage (hDlg, IDC_MSG, LB_ADDSTRING,
                0, (LONG) lpMsgNode);

            MAPIFreeBuffer (lpMessage);
            }
        }

        lstrcpy (szSeedMsgID, szMsgID);
        ulResult = MAPIFindNext (lhSession, (ULONG) hDlg, NULL, szSeedMsgID,
            MAPI_UNREAD_ONLY | MAPI_LONG_MSGID, 0, szMsgID);
        }
        SetCursor(hOldCur);
        break;

    case IDC_MSG:
        if(HIWORD(wParam) != LBN_DBLCLK)
        break;

    case IDC_READ:
        nIndex = SendDlgItemMessage (hDlg, IDC_MSG, LB_GETCURSEL, 0, 0);

        if (nIndex == LB_ERR)
        break;

        lpReadMsgNode = (LPMSGID) SendDlgItemMessage (hDlg, IDC_MSG,
        LB_GETITEMDATA, (UINT)nIndex, 0L);

        if (lpReadMsgNode)
        DialogBox (hInst, "ReadNote", hDlg, ReadMailDlgProc);

        /* Update the Messages List-Box with new icon */

        SendDlgItemMessage (hDlg, IDC_MSG, LB_GETITEMRECT, (UINT)nIndex, (LPARAM) &Rect);
        InvalidateRect(GetDlgItem(hDlg, IDC_MSG), &Rect, FALSE);
        break;

    case IDC_DELETE:
        nIndex = SendDlgItemMessage (hDlg, IDC_MSG, LB_GETCURSEL, 0, 0);

        if (nIndex == LB_ERR)
        break;

        lpMsgNode = (LPMSGID) SendDlgItemMessage (hDlg, IDC_MSG,
        LB_GETITEMDATA, (UINT)nIndex, 0);

        if (lpMsgNode)
        {
        MAPIDeleteMail (lhSession, (ULONG) hDlg, lpMsgNode->lpszMsgID, 0, 0);
        DeleteMsgNode (lpMsgNode, &lpMsgIdList);
        }

        SendDlgItemMessage (hDlg, IDC_MSG, LB_DELETESTRING, (UINT)nIndex, 0);
        break;

    case IDC_CLOSE:
    case IDCANCEL:
        FreeMsgList (lpMsgIdList);
        lpMsgIdList = NULL;

            DeInitBmps();

        EndDialog (hDlg, TRUE);
        return TRUE;
        break;

    default:
        break;
    }
    break;
    }

    return FALSE;
}

/*
 -  ReadMailDlgProc
 -
 *  Purpose:
 *      Dialog procedure for the ReadMail dilaog.
 *
 *  Parameters:
 *      hDlg
 *      message
 *      wParam
 *      lParam
 *
 *  Returns:
 *      True/False
 *
 */

BOOL FAR PASCAL
ReadMailDlgProc (HWND hDlg, UINT msg, UINT wParam, LONG lParam)
{
    ULONG ulResult;
    char szTo[TO_EDIT_MAX];
    char szCc[TO_EDIT_MAX];
    char szChangeMsg[512];
    ULONG idx;
    static lpMapiMessage lpReadMsg;

    switch (msg)
    {
    case WM_INITDIALOG:
    if (ulResult = MAPIReadMail (lhSession, (LONG) hDlg, lpReadMsgNode->lpszMsgID,
        0, 0, &lpReadMsg))
    {
        MakeMessageBox(hDlg, ulResult, IDS_READFAIL, MBS_ERROR);
        EndDialog (hDlg, TRUE);
        return TRUE;
    }

    lpReadMsgNode->fUnRead = FALSE;

    szTo[0] = '\0';
    szCc[0] = '\0';

    for (idx = 0; idx < lpReadMsg->nRecipCount; idx++)
    {
        if (lpReadMsg->lpRecips[idx].ulRecipClass == MAPI_TO)
        {
        lstrcat (szTo, lpReadMsg->lpRecips[idx].lpszName);
        lstrcat (szTo, "; ");
        }
        else if (lpReadMsg->lpRecips[idx].ulRecipClass == MAPI_CC)
        {
        lstrcat (szCc, lpReadMsg->lpRecips[idx].lpszName);
        lstrcat (szCc, "; ");
        }
        else
        {
        /* Must be Bcc, lets ignore it! */
        }
    }

    if(*szTo)
        szTo[lstrlen (szTo) - 2] = '\0';
    if(*szCc)
        szCc[lstrlen (szCc) - 2] = '\0';

    SetDlgItemText (hDlg, IDC_RFROM,
        (lpReadMsg->lpOriginator && lpReadMsg->lpOriginator->lpszName ?
                lpReadMsg->lpOriginator->lpszName : ""));
    SetDlgItemText (hDlg, IDC_RDATE,
        (lpReadMsg->lpszDateReceived ? lpReadMsg->lpszDateReceived : ""));
    SetDlgItemText (hDlg, IDC_RTO, szTo);
    SetDlgItemText (hDlg, IDC_RCC, szCc);
    SetDlgItemText (hDlg, IDC_RSUBJECT,
        (lpReadMsg->lpszSubject ? lpReadMsg->lpszSubject : ""));
    SetDlgItemText (hDlg, IDC_READNOTE,
        (lpReadMsg->lpszNoteText ? lpReadMsg->lpszNoteText : ""));

    if (!lpReadMsg->nFileCount)
    {
        EnableWindow (GetDlgItem (hDlg, IDC_SAVEATTACH), FALSE);
        EnableWindow (GetDlgItem (hDlg, IDC_ATTACHMENT), FALSE);
        EnableWindow (GetDlgItem (hDlg, IDT_ATTACHMENT), FALSE);
    }
    else
    {
        for(idx = 0; idx < lpReadMsg->nFileCount; idx++)
        if (lpReadMsg->lpFiles[idx].lpszFileName)
            SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_ADDSTRING, 0,
            (LPARAM)lpReadMsg->lpFiles[idx].lpszFileName);

        SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_SETCURSEL, 0, 0L);
    }

    SetFocus (GetDlgItem (hDlg, IDC_READNOTE));
    return FALSE;

    case WM_COMMAND:
    switch (LOWORD (wParam))
    {
    case IDC_SAVECHANGES:
        if (SendDlgItemMessage (hDlg, IDC_READNOTE, EM_GETMODIFY, 0, 0))
        ulResult = SaveMsgChanges (hDlg, lpReadMsg, lpReadMsgNode->lpszMsgID);
        SendDlgItemMessage (hDlg, IDC_READNOTE, EM_SETMODIFY, 0, 0);
        break;

    case IDC_ATTACHMENT:
        if(HIWORD(wParam) != LBN_DBLCLK)
        break;

    case IDC_SAVEATTACH:
        idx = SendDlgItemMessage(hDlg, IDC_ATTACHMENT, LB_GETCURSEL, 0, 0L);

        if(idx != LB_ERR)
        {
        SaveFileAttachments(hDlg, &lpReadMsg->lpFiles[idx]);
        SetFocus(GetDlgItem (hDlg, IDC_ATTACHMENT));
        return FALSE;

        }
        break;

    case IDC_REPLY:
    case IDC_REPLYALL:
    case IDC_FORWARD:
        MakeNewMessage (lpReadMsg, LOWORD (wParam));
        DialogBox (hInst, "ComposeNote", hDlg, ComposeDlgProc);
        break;

    case IDCANCEL:
        if (SendDlgItemMessage (hDlg, IDC_READNOTE, EM_GETMODIFY, 0, 0))
        {
        wsprintf (szChangeMsg, "Save changes to: '%s' in Inbox?",
            (lpReadMsg->lpszSubject ? lpReadMsg->lpszSubject : ""));

        if (MessageBox (hDlg, szChangeMsg, "Mail", MB_YESNO) == IDYES)
        {
            ulResult = SaveMsgChanges (hDlg, lpReadMsg, lpReadMsgNode->lpszMsgID);
        }
        }

        /* If there were file attachments, then delete the temps */

        for(idx = 0; idx < lpReadMsg->nFileCount; idx++)
        if (lpReadMsg->lpFiles[idx].lpszPathName)
            DeleteFile(lpReadMsg->lpFiles[idx].lpszPathName);

        MAPIFreeBuffer (lpReadMsg);
        lpReadMsg = NULL;
        EndDialog (hDlg, TRUE);
        return TRUE;
    }
    break;
    }
    return FALSE;
}

/*
 -  MakeMessageBox
 -
 *  Purpose:
 *      Gets resource string and displays an error message box.
 *
 *  Parameters:
 *      hWnd            - Handle to parent window
 *      idString        - Resource ID of message in StringTable
 *
 *  Returns:
 *      Void
 *
 */

void
MakeMessageBox (HWND hWnd, ULONG ulResult, UINT idString, UINT fStyle)
{
    char szMessage[256];
    char szMapiReturn[64];

    LoadString (hInst, idString, szMessage, 255);

    if (ulResult)
    {
    LoadString (hInst, (UINT)ulResult, szMapiReturn, 64);
    lstrcat (szMessage, "\nReturn Code: ");
    lstrcat (szMessage, szMapiReturn);
    }

    MessageBox (hWnd, szMessage, "Problem", fStyle);
}

/*
 -  ResolveFriendlyNames
 -
 *  Purpose:
 *      Helper function to convert a string of ';' delimited friendly
 *      names into an array of MapiRecipDescs.
 *
 *  Side Effects:
 *      The display string passed in is modified to contain the
 *      friendly names of the mail users as found in the sample
 *      address book.
 *
 *  Note:
 *      Duplicate names in the address book will result in undefined
 *      behavior.
 *
 *  Parameters:
 *      hWnd                - Handle to parent window
 *      lpszDisplayNames    - string of ';' delimited user names
 *      ulRecipClass        - either MAPI_TO, MAPI_CC, or MAPI_BCC
 *      lpcRecips           - Address of recipient count to be returned
 *      lppRecips           - Address of recipient array to be returned
 *
 *  Return:
 *      ulResult
 */

ULONG
ResolveFriendlyNames (HWND hWnd, LPSTR lpszDisplayNames, ULONG ulRecipClass,
    ULONG * lpcRecips, lpMapiRecipDesc * lppRecips)
{
    char szResolve[TO_EDIT_MAX];
    LPSTR lpszNameToken;
    ULONG cRecips = 0;
    ULONG cFails = 0;
    ULONG ulResult;
    lpMapiRecipDesc lpRecip;
    lpMapiRecipDesc lpRecipList;

    *szResolve = '\0';
    lpszNameToken = strtok (lpszDisplayNames, ";\n");

    while (lpszNameToken)
    {
    /* Strip leading blanks from name */

    while (*lpszNameToken == ' ')
        lpszNameToken++;

    /* Check if name has already been resolved */

    if (!FNameInList (lpszNameToken, *lpcRecips, *lppRecips))
    {
        lstrcat (szResolve, lpszNameToken);
        lstrcat (szResolve, "; ");
        cRecips++;
    }

    /* Get Next Token */

    lpszNameToken = strtok (NULL, ";\n");
    }

    *lpszDisplayNames = '\0';

    if (!szResolve[0])
    {
    ulResult = SUCCESS_SUCCESS;
    goto err;
    }

    szResolve[lstrlen (szResolve) - 2] = '\0';

    lpRecipList = (lpMapiRecipDesc)PvAlloc((cRecips + *lpcRecips) * sizeof (MapiRecipDesc));

    if (!lpRecipList)
    {
    ulResult = MAPI_E_INSUFFICIENT_MEMORY;
    goto err;
    }
    memset (lpRecipList, 0, (size_t)(cRecips+*lpcRecips)*sizeof(MapiRecipDesc));

    cRecips = 0;

    while (cRecips < *lpcRecips)
    {
    ulResult = CopyRecipient (lpRecipList, &lpRecipList[cRecips],
        *lppRecips + cRecips);

    if (ulResult)
    {
        PvFree(lpRecipList);
        goto err;
    }

    cRecips++;
    }

    PvFree(*lppRecips);

    lpszNameToken = strtok (szResolve, ";\n");

    while (lpszNameToken)
    {
    /* Strip leading blanks (again) */

    while (*lpszNameToken == ' ')
        lpszNameToken++;

    ulResult = MAPIResolveName (lhSession, (ULONG) hWnd, lpszNameToken,
        MAPI_DIALOG, 0, &lpRecip);

    if (ulResult == SUCCESS_SUCCESS)
    {
        lpRecip->ulRecipClass = ulRecipClass;
        ulResult = CopyRecipient (lpRecipList, &lpRecipList[cRecips], lpRecip);

        MAPIFreeBuffer (lpRecip);

        if (ulResult)
        goto cleanup;

        cRecips++;
    }
    else
    {
        lstrcat (lpszDisplayNames, lpszNameToken);
        lstrcat (lpszDisplayNames, "; ");
        cFails++;
    }
    lpszNameToken = strtok (NULL, ";\n");
    }

    /* if cFails > 0 then we have partial success */

    ulResult = SUCCESS_SUCCESS;

    if (cFails)
    MakeMessageBox (hWnd, 0, IDS_UNRESOLVEDNAMES, MBS_INFO);

cleanup:
    *lpcRecips = cRecips;
    *lppRecips = lpRecipList;
err:
    if (*lpszDisplayNames)
    lpszDisplayNames[lstrlen (lpszDisplayNames) - 2] = '\0';

    return ulResult;
}

/*
 -  CopyRecipient
 -
 *  Purpose:
 *      Called in support of ResolveFriendlyNames() to build an array
 *      of chained MapiRecipDescs.
 *
 *  Parameters:
 *      lpParent        - Parent memory that allocations get chained to
 *      lpDest          - Destination Recipient
 *      lpSrc           - Source Recipient
 *
 *  Return:
 *      ulResult
 */

ULONG
CopyRecipient (lpMapiRecipDesc lpParent,
    lpMapiRecipDesc lpDest,
    lpMapiRecipDesc lpSrc)
{
    lpDest->ulReserved = lpSrc->ulReserved;
    lpDest->ulRecipClass = lpSrc->ulRecipClass;
    lpDest->ulEIDSize = lpSrc->ulEIDSize;

    if (lpSrc->lpszName)
    {
    lpDest->lpszName = (LPTSTR)PvAllocMore(lstrlen(lpSrc->lpszName) + 1,
            (LPVOID)lpParent);

    if (!lpDest->lpszName)
        return MAPI_E_INSUFFICIENT_MEMORY;

    lstrcpy (lpDest->lpszName, lpSrc->lpszName);
    }
    else
    lpDest->lpszName = NULL;

    if (lpSrc->lpszAddress)
    {
    lpDest->lpszAddress = (LPTSTR)PvAllocMore(lstrlen (lpSrc->lpszAddress) + 1,
            (LPVOID)lpParent);

    if (!lpDest->lpszAddress)
        return MAPI_E_INSUFFICIENT_MEMORY;

    lstrcpy (lpDest->lpszAddress, lpSrc->lpszAddress);
    }
    else
    lpDest->lpszAddress = NULL;

    if (lpSrc->lpEntryID)
    {
    lpDest->lpEntryID = (LPBYTE)PvAllocMore(lpSrc->ulEIDSize,
            (LPVOID)lpParent);

    if (!lpDest->lpEntryID)
        return MAPI_E_INSUFFICIENT_MEMORY;

        if (lpSrc->ulEIDSize)
            memcpy (lpDest->lpEntryID, lpSrc->lpEntryID, (size_t)lpSrc->ulEIDSize);
    }
    else
    lpDest->lpEntryID = NULL;

    return SUCCESS_SUCCESS;

}

/*
 -  GetNextFile
 -
 *  Purpose:
 *      Called when user clicks 'Attach' button in Compose Note form.
 *      We will build a chained memory chunk for mmore than one file
 *      attachment so the memory can be freed with a single call to
 *      PvFree.
 *
 *  Parameters:
 *      hWnd            - Window handle of Compose Note dialog
 *      nPos            - Render position of attachment in Notetext.
 *      lpcAttach       - Pointer to the count of attachments.
 *      lppAttach       - Pointer to the MapiFileDesc array.
 *
 *  Return:
 *      ulResult.
 */

ULONG
GetNextFile (HWND hWnd, ULONG nPos, ULONG * lpcAttach,
    lpMapiFileDesc * lppAttach)
{
    lpMapiFileDesc lpAttach;
    lpMapiFileDesc lpAttachT;
    OPENFILENAME ofn;
    char szFileName[256] = "";
    char szFilter[256];
    static char szFileTitle[16];
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    ULONG idx;
    ULONG ulResult = SUCCESS_SUCCESS;

    if (!szDirName[0])
    GetSystemDirectory ((LPSTR) szDirName, 255);
    else
    lstrcpy (szFileName, szFileTitle);

    LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter));

    for (idx = 0; szFilter[idx] != '\0'; idx++)
    if (szFilter[idx] == '|')
        szFilter[idx] = '\0';

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = 0;
    ofn.hInstance = 0;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = 256;
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = 16;
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Attach";
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

    if (!GetOpenFileName (&ofn))
    return MAPI_USER_ABORT;

    /* Save the directory for the next time we call this */

    lstrcpy (szDirName, szFileName);
    if (lpszEndPath = strstr (szDirName, szFileTitle))
    *(--lpszEndPath) = '\0';

    lpAttach = (lpMapiFileDesc)PvAlloc(((*lpcAttach) + 1) * sizeof (MapiFileDesc));

    if(!lpAttach)
    goto err;

    memset (lpAttach, 0, (size_t)(*lpcAttach + 1) * sizeof (MapiFileDesc));

    lpAttachT = *lppAttach;

    for (idx = 0; idx < *lpcAttach; idx++)
    if(ulResult = CopyAttachment (lpAttach, &lpAttach[idx], &lpAttachT[idx]))
        goto err;

    lpAttach[idx].ulReserved = 0;
    lpAttach[idx].flFlags = 0;
    lpAttach[idx].nPosition = (ULONG)(-1);
    lpAttach[idx].lpFileType = NULL;

    lpAttach[idx].lpszPathName = (LPTSTR)PvAllocMore(lstrlen (szFileName) + 1,
        (LPVOID)lpAttach);

    if(!lpAttach[idx].lpszPathName)
    goto err;

    lpAttach[idx].lpszFileName = (LPTSTR)PvAllocMore(lstrlen (szFileTitle) + 1,
        (LPVOID)lpAttach);

    if(!lpAttach[idx].lpszFileName)
    goto err;

    lstrcpy (lpAttach[idx].lpszPathName, szFileName);
    lstrcpy (lpAttach[idx].lpszFileName, szFileTitle);

    PvFree(lpAttachT);

    *lppAttach = lpAttach;
    (*lpcAttach)++;

err:
    if(ulResult)
    PvFree(lpAttach);

    return ulResult;
}

/*
 -  CopyAttachment
 -
 *  Purpose:
 *      Called in support of GetNextFile() to re-build an array
 *      of chained MapiFileDescs.
 *
 *  Parameters:
 *      lpParent        - Parent memory that allocations get chained to
 *      lpDest          - Destination Recipient
 *      lpSrc           - Source Recipient
 *
 *  Return:
 *      Void.
 */

ULONG
CopyAttachment (lpMapiFileDesc lpParent,
    lpMapiFileDesc lpDest,
    lpMapiFileDesc lpSrc)
{
    lpDest->ulReserved = lpSrc->ulReserved;
    lpDest->flFlags = lpSrc->flFlags;
    lpDest->nPosition = lpSrc->nPosition;
    lpDest->lpFileType = lpSrc->lpFileType;

    if (lpSrc->lpszPathName)
    {
    lpDest->lpszPathName = (LPTSTR)PvAllocMore(lstrlen (lpSrc->lpszPathName) + 1,
            (LPVOID)lpParent);

    if (!lpDest->lpszPathName)
        return MAPI_E_INSUFFICIENT_MEMORY;

    lstrcpy (lpDest->lpszPathName, lpSrc->lpszPathName);
    }
    else
    lpDest->lpszPathName = NULL;

    if (lpSrc->lpszFileName)
    {
    lpDest->lpszFileName = (LPTSTR)PvAllocMore(lstrlen (lpSrc->lpszFileName) + 1,
            (LPVOID)lpParent);

    if (!lpDest->lpszFileName)
        return MAPI_E_INSUFFICIENT_MEMORY;

    lstrcpy (lpDest->lpszFileName, lpSrc->lpszFileName);
    }
    else
    lpDest->lpszFileName = NULL;

    return SUCCESS_SUCCESS;

}

/*
 -  FNameInList
 -
 *  Purpose:
 *      To find lpszName in an array of recipients.  Used to determine
 *      if user name has already been resolved.
 *
 *  Parameters:
 *      lpszName        - Friendly name to search for
 *      cRecips         - Count of recipients in lpRecips
 *      lpRecips        - Array of MapiRecipDescs
 *
 *  Return:
 *      TRUE/FALSE
 */

BOOL
FNameInList (LPSTR lpszName, ULONG cRecips, lpMapiRecipDesc lpRecips)
{
    /* Case sensitive compare of each friendly name in list.  */

    if (!cRecips || !lpRecips)
    return FALSE;

    while (cRecips--)
    if (!lstrcmp (lpszName, lpRecips[cRecips].lpszName))
        return TRUE;

    return FALSE;
}

/*
 -  MakeMsgNode
 -
 *  Purpose:
 *      Allocate memory for a new MSGID node and initialize its
 *      data members to the values passed in.
 *
 *  Parameters:
 *      lpMsg           - Pointer to a MapiMessage
 *      lpszMsgID       - Opaque message identifier
 *
 *  Return:
 *      lpMsgNode       - Pointer to new node
 */

LPMSGID
MakeMsgNode (lpMapiMessage lpMsg, LPSTR lpszMsgID)
{
    LPMSGID lpMsgNode = NULL;

    if (!lpMsg || !lpszMsgID)
    goto err;

    lpMsgNode = (LPMSGID)PvAlloc(sizeof (MSGID));

    if (!lpMsgNode)
    goto err;

    memset (lpMsgNode, 0, sizeof (MSGID));

    if (lpMsg->nFileCount)
    lpMsgNode->fHasAttach = TRUE;

    if (lpMsg->flFlags & MAPI_UNREAD)
    lpMsgNode->fUnRead = TRUE;

    lpMsgNode->lpszMsgID = (LPTSTR)PvAllocMore(lstrlen (lpszMsgID) + 1,
        (LPVOID)lpMsgNode);

    if (!lpMsgNode->lpszMsgID)
    goto err;

    lstrcpy (lpMsgNode->lpszMsgID, lpszMsgID);

    if (lpMsg->lpOriginator && lpMsg->lpOriginator->lpszName)
    {
    lpMsgNode->lpszFrom = (LPTSTR)PvAllocMore(lstrlen(lpMsg->lpOriginator->lpszName) + 1,
            (LPVOID)lpMsgNode);

    if (!lpMsgNode->lpszFrom)
        goto err;

    lstrcpy (lpMsgNode->lpszFrom, lpMsg->lpOriginator->lpszName);
    }

    if (lpMsg->lpszSubject)
    {
    lpMsgNode->lpszSubject = (LPTSTR)PvAllocMore(lstrlen (lpMsg->lpszSubject) + 1,
            (LPVOID)lpMsgNode);

    if (!lpMsgNode->lpszSubject)
        goto err;

    lstrcpy (lpMsgNode->lpszSubject, lpMsg->lpszSubject);
    }

    if (lpMsg->lpszDateReceived)
    {
    lpMsgNode->lpszDateRec = (LPTSTR)PvAllocMore(lstrlen (lpMsg->lpszDateReceived) + 1,
            (LPVOID)lpMsgNode);

    if (!lpMsgNode->lpszDateRec)
        goto err;

    lstrcpy (lpMsgNode->lpszDateRec, lpMsg->lpszDateReceived);
    }

    return lpMsgNode;

err:
    PvFree(lpMsgNode);
    return NULL;
}

/*
 -  InsertMsgNode
 -
 *  Purpose:
 *      Currently (for simplicity) we will insert the nodes
 *      at the beginning of the list.  This can later be
 *      replaced with a routine that can insert sorted on
 *      different criteria, like DateReceived, From, or
 *      Subject.  But for now...
 *
 *  Parameters:
 *      lpMsgNode       - Pointer to a MSGID node
 *      lppMsgHead      - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
InsertMsgNode (LPMSGID lpMsgNode, LPMSGID * lppMsgHead)
{
    if (*lppMsgHead)
    {
    lpMsgNode->lpNext = *lppMsgHead;
    (*lppMsgHead)->lpPrev = lpMsgNode;
    }
    else
    lpMsgNode->lpNext = NULL;

    /* The next 2 assignments are here in case the node came from somewhere */
    /* other than a call to MakeMsgNode () in which case we aren't sure */
    /* they're already NULL. */

    lpMsgNode->lpPrev = NULL;
    *lppMsgHead = lpMsgNode;
}

/*
 -  DeleteMsgNode
 -
 *  Purpose:
 *      Removes the node passed in from the list.  This
 *      may seem like a strange way to do this but it's
 *      not, because the Owner-Drawn List Box gives us
 *      direct access to elements in the list that makes
 *      it easier to do things this way.
 *
 *  Parameters:
 *      lpMsgNode       - Pointer to the MSGID node to delete
 *      lppMsgHead      - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
DeleteMsgNode (LPMSGID lpMsgNode, LPMSGID * lppMsgHead)
{
    if (!lpMsgNode)
    return;

    /* Check if we are the first node */

    if (lpMsgNode->lpPrev)
    lpMsgNode->lpPrev->lpNext = lpMsgNode->lpNext;

    /* Check if we are the last node */

    if (lpMsgNode->lpNext)
    lpMsgNode->lpNext->lpPrev = lpMsgNode->lpPrev;

    /* check if we are the only node */

    if(lpMsgNode == *lppMsgHead)
    *lppMsgHead = NULL;

    PvFree(lpMsgNode);
    return;
}

/*
 -  FindNode
 -
 *  Purpose:
 *      Returns a pointer to the node containing lpszMsgID.
 *      Returns NULL if node doesn't exist or lpszMsgID is NULL.
 *
 *  Parameters:
 *      lpMsgHead       - Pointer to the head of the list
 *      lpszMsgID       - Message ID to search for
 *
 *  Return:
 *      lpMsgNode       - Pointer to the node returned
 */

LPMSGID
FindNode (LPMSGID lpMsgHead, LPSTR lpszMsgID)
{
    if (!lpszMsgID)
    return NULL;

    while (lpMsgHead)
    {
    if (!lstrcmp (lpMsgHead->lpszMsgID, lpszMsgID))
        break;

    lpMsgHead = lpMsgHead->lpNext;
    }

    return lpMsgHead;
}

/*
 -  FreeMsgList
 -
 *  Purpose:
 *      Walks down the MsgList and frees each node.
 *
 *  Parameters:
 *      lpMsgHead       - Pointer to the head of the list
 *
 *  Return:
 *      Void.
 */

void
FreeMsgList (LPMSGID lpMsgHead)
{
    LPMSGID lpT;

    while (lpMsgHead)
    {
    lpT = lpMsgHead;
    lpMsgHead = lpMsgHead->lpNext;
    PvFree(lpT);
    }
}

/*
 -  MakeDisplayNameStr
 -
 *  Purpose:
 *      Finds all recipients of type ulRecipClass in lpRecips and adds
 *      their friendly name to the display string.
 *
 *  Parameters:
 *      lpszDisplay         - Destination string for names
 *      ulRecipClass        - Recipient types to search for
 *      cRecips             - Count of recipients in lpRecips
 *      lpRecips            - Pointer to array of MapiRecipDescs
 *
 *  Return:
 *      Void.
 */

void
MakeDisplayNameStr (LPSTR lpszDisplay, ULONG ulRecipClass,
    ULONG cRecips, lpMapiRecipDesc lpRecips)
{
    ULONG idx;

    *lpszDisplay = '\0';

    for (idx = 0; idx < cRecips; idx++)
    {
    if (lpRecips[idx].ulRecipClass == ulRecipClass)
    {
        lstrcat (lpszDisplay, lpRecips[idx].lpszName);
        lstrcat (lpszDisplay, "; ");
    }
    }

    if (*lpszDisplay)
    lpszDisplay[lstrlen (lpszDisplay) - 2] = '\0';
}

/*
 -  SaveMsgChanges
 -
 *  Purpose:
 *      If while reading a message the user changes the notetext at all
 *      then this function is called to save those changes in the Inbox.
 *
 *  Parameters:
 *      hWnd            - handle to the window/dialog who called us
 *      lpMsg           - pointer to the MAPI message to be saved
 *      lpszMsgID       - ID of the message to save
 *
 *  Return:
 *      ulResult        - Indicating success/failure
 */

ULONG
SaveMsgChanges (HWND hWnd, lpMapiMessage lpMsg, LPSTR lpszMsgID)
{
    LPSTR lpszT;
    LPSTR lpszNoteText = NULL;
    LONG cLines, cb;
    ULONG ulResult = MAPI_E_INSUFFICIENT_MEMORY;

    lpszT = lpMsg->lpszNoteText;

    cLines = SendDlgItemMessage (hWnd, IDC_READNOTE, EM_GETLINECOUNT, 0, 0L);
    cb = SendDlgItemMessage (hWnd, IDC_READNOTE, EM_LINEINDEX, (UINT)cLines - 1, 0L);
    cb += SendDlgItemMessage (hWnd, IDC_READNOTE, EM_LINELENGTH, (UINT)cb, 0L);
    cb += cLines * 2;

    lpszNoteText = (LPTSTR)PvAlloc(cb + 1);

    if (!lpszNoteText)
    goto err;

    SendDlgItemMessage (hWnd, IDC_READNOTE, WM_GETTEXT,
    (WPARAM) cb, (LPARAM) lpszNoteText);

    lpMsg->lpszNoteText = lpszNoteText;
    ulResult = MAPISaveMail (lhSession, (ULONG) hWnd, lpMsg, MAPI_LONG_MSGID,
        0, lpReadMsgNode->lpszMsgID);

    PvFree(lpszNoteText);

err:
    lpMsg->lpszNoteText = lpszT;
    return ulResult;
}

/*
 -  MakeNewMessage
 -
 *  Purpose:
 *      This function is used to construct a new message for the
 *      ComposeNote UI.  This gets called as a result of a Reply,
 *      ReplyAll, or a Forward action on a message being read.
 *      The destination for the new message is lpmsg, the global
 *      MapiMessage struct pointer used by ComposeNoteDlgProc.
 *      ComposeNoteDlgProc always frees the memory consumed by
 *      this object whether it allocated it or not.
 *
 *  Parameters:
 *      lpSrcMsg            - MapiMessage to be copied
 *      flType              - Specifies the action that caused this call
 *                            either: IDC_REPLY, IDC_REPLYALL, or IDC_FORWARD
 *
 *  Return:
 *      ulResult            - Indicates success/failure
 */

ULONG
MakeNewMessage (lpMapiMessage lpSrcMsg, UINT flType)
{
    ULONG idx;
    ULONG ulResult = SUCCESS_SUCCESS;

    if (!lpSrcMsg)
    return MAPI_E_FAILURE;

    lpmsg = (lpMapiMessage)PvAlloc(sizeof (MapiMessage));

    if (!lpmsg)
    goto err;

    memset (lpmsg, 0, sizeof (MapiMessage));

    lpmsg->flFlags = flSendMsgFlags;

    if (lpSrcMsg->lpszSubject)
    {
    lpmsg->lpszSubject = (LPTSTR)PvAlloc(lstrlen(lpSrcMsg->lpszSubject) + 5);

    if (!lpmsg->lpszSubject)
        goto err;

    if (flType == IDC_FORWARD)
        lstrcpy (lpmsg->lpszSubject, "FW: ");
    else
        lstrcpy (lpmsg->lpszSubject, "RE: ");

    lstrcat (lpmsg->lpszSubject, lpSrcMsg->lpszSubject);
    }

    if (lpSrcMsg->lpszNoteText)
    {
    lpmsg->lpszNoteText = (LPTSTR)PvAlloc(lstrlen(lpSrcMsg->lpszNoteText) + 32);

    if (!lpmsg->lpszNoteText)
        goto err;

    lstrcpy (lpmsg->lpszNoteText, "\r\n--------------------------\r\n");
    lstrcat (lpmsg->lpszNoteText, lpSrcMsg->lpszNoteText);
    }

    if (lpSrcMsg->lpszMessageType)
    {
    lpmsg->lpszMessageType = (LPTSTR)PvAlloc(lstrlen (lpSrcMsg->lpszMessageType) + 1);

    if (!lpmsg->lpszMessageType)
        goto err;

    lstrcpy (lpmsg->lpszMessageType, lpSrcMsg->lpszMessageType);
    }

    if (lpSrcMsg->lpszConversationID)
    {
    lpmsg->lpszConversationID = (LPTSTR)PvAlloc(lstrlen(lpSrcMsg->lpszConversationID) + 1);

    if (!lpmsg->lpszConversationID)
        goto err;

    lstrcpy (lpmsg->lpszConversationID, lpSrcMsg->lpszConversationID);
    }

    if (lpSrcMsg->nFileCount && flType == IDC_FORWARD )
    {
    lpmsg->nFileCount = lpSrcMsg->nFileCount;

    lpmsg->lpFiles = (lpMapiFileDesc)PvAlloc(lpmsg->nFileCount * sizeof (MapiFileDesc));

    if (!lpmsg->lpFiles)
        goto err;
        memset (lpmsg->lpFiles, 0, (size_t)lpmsg->nFileCount * sizeof (MapiFileDesc));

        for (idx = 0; idx < lpmsg->nFileCount; idx++)
    {
        CopyAttachment (lpmsg->lpFiles, &lpmsg->lpFiles[idx],
        &lpSrcMsg->lpFiles[idx]);

            if ((&lpmsg->lpFiles[idx])->nPosition != (ULONG) -1)
            {
                /*lpmsg->lpszNoteText[(&lpmsg->lpFiles[idx])->nPosition
                            + lstrlen("\r\n--------------------------\r\n")] = '+';*/
                (&lpmsg->lpFiles[idx])->nPosition = (ULONG) -1;

            }

        }
    }

    if (flType == IDC_REPLY || flType == IDC_REPLYALL)
    {
        ULONG idxSrc;

    if(lpSrcMsg->lpOriginator)
        lpmsg->nRecipCount = 1;

    if (flType == IDC_REPLYALL)
        lpmsg->nRecipCount += lpSrcMsg->nRecipCount;

        if(!lpmsg->nRecipCount)
            return ulResult;

    lpmsg->lpRecips = (lpMapiRecipDesc)PvAlloc(lpmsg->nRecipCount * sizeof (MapiRecipDesc));

    if (!lpmsg->lpRecips)
        goto err;

        memset (lpmsg->lpRecips, 0, (size_t)lpmsg->nRecipCount * sizeof (MapiRecipDesc));
        idx = 0;

        if(lpSrcMsg->lpOriginator)
        {
        lpSrcMsg->lpOriginator->ulRecipClass = MAPI_TO;
        CopyRecipient (lpmsg->lpRecips, lpmsg->lpRecips,
                lpSrcMsg->lpOriginator);
        lpSrcMsg->lpOriginator->ulRecipClass = MAPI_ORIG;
            idx = 1;
        }

    for (idxSrc = 0; idx < lpmsg->nRecipCount; idxSrc++, idx++)
        CopyRecipient (lpmsg->lpRecips, &lpmsg->lpRecips[idx],
        &lpSrcMsg->lpRecips[idxSrc]);
    }

    return ulResult;

err:
    if(lpmsg)
    {
        PvFree(lpmsg->lpszSubject);
    PvFree(lpmsg->lpszNoteText);
        PvFree(lpmsg->lpszMessageType);
        PvFree(lpmsg->lpszConversationID);
        PvFree(lpmsg->lpRecips);
        PvFree(lpmsg->lpFiles);
        PvFree(lpmsg);
        lpmsg = NULL;
    }
    return ulResult;
}

/*
 -  LogSendMail
 -
 *  Purpose:
 *      Used to track how many messages were sent with this client.
 *      This information is used strictly for gathering stats on
 *      how many messages were pumped through the spooler/transport.
 *
 *  Usage:
 *      Add the following to the win.ini file:
 *          [MAPI Client]
 *          LogFile=filepath
 *
 *      where: filepath can be a full UNC path or some local path & file
 *
 *  Parameters:
 *      ulResult        - Currently unused; should be used to count errors
 *
 *  Result:
 *      Void.
 */

void LogSendMail(ULONG ulResult)
{
    char szLogFile[128];
    char szCount[32];
    OFSTRUCT ofs;
    HFILE hf = HFILE_ERROR;
    int cSent = 1;

    if(!GetProfileString("MAPI Client", "LogFile", "mapicli.log",
        szLogFile, sizeof(szLogFile)))
    return;

    if((hf = OpenFile(szLogFile, &ofs, OF_READWRITE)) == HFILE_ERROR)
    {
    if((hf = OpenFile(szLogFile, &ofs, OF_CREATE|OF_READWRITE)) == HFILE_ERROR)
        return;
    }
    else
    {
    if(!_lread(hf, szCount, sizeof(szCount)))
    {
        _lclose(hf);
        return;
    }

    cSent = atoi(szCount) + 1;
    }

    wsprintf(szCount, "%d", cSent);

    _llseek(hf, 0, 0);

    _lwrite(hf, szCount, lstrlen(szCount));
    _lclose(hf);

    return;
}

/*
 -  SaveFileAttachments
 -
 *  Purpose:
 *      Displays a 'Save As' common dialog to allow the user to save
 *      file attachments contained in the current message.
 *
 *  Parameters:
 *      hWnd            - Window handle of calling WndProc
 *      cFiles          - Count of the files in the file array
 *      lpFiles         - Array of MapiFileDescs
 *
 *  Return:
 *      Void.
 */

void SaveFileAttachments(HWND hWnd, lpMapiFileDesc lpFile)
{
    OPENFILENAME ofn;
    char szFileName[256] = "";
    char szFilter[256];
    static char szFileTitle[16];
    static char szDirName[256] = "";
    LPSTR lpszEndPath;
    ULONG idx;

    if (!lpFile)
    return;

    if (!szDirName[0])
    GetTempPath (sizeof(szDirName), szDirName);

    LoadString(hInst, IDS_FILTER, szFilter, sizeof(szFilter));

    for (idx = 0; szFilter[idx] != '\0'; idx++)
    if (szFilter[idx] == '|')
        szFilter[idx] = '\0';

    lstrcpy (szFileName, lpFile->lpszFileName);

    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = hWnd;
    ofn.hInstance = 0;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = sizeof(szFileName);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = "Save Attachment";
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;

    if (!GetSaveFileName (&ofn))
    return;

    /* Save the directory for the next time we call this */

    lstrcpy (szDirName, szFileName);
    if (lpszEndPath = strstr (szDirName, szFileTitle))
    *(--lpszEndPath) = '\0';

    /* Use CopyFile to carry out the operation. */

    if(!CopyFile(lpFile->lpszPathName, szFileName, FALSE))
    MakeMessageBox (hWnd, 0, IDS_SAVEATTACHERROR, MBS_ERROR);
}

/*
 -  ToggleMenuState
 -
 *  Purpose:
 *      Enables/Disables menu items depending on the session state.
 *
 *  Parameters:
 *      hWnd            - handle to the window/dialog who called us
 *      fLoggedOn       - TRUE if logged on, FALSE if logged off
 *
 *  Return:
 *      Void.
 */

void ToggleMenuState(HWND hWnd, BOOL fLoggedOn)
{
    EnableMenuItem (GetMenu (hWnd), IDM_LOGOFF,   !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_COMPOSE,  !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_READ,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_SEND,     !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_ADDRBOOK, !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_DETAILS,  !fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON,    fLoggedOn);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,           FALSE);
}

//
//  SecureMenu
//
//  Purpose:
//      Enables/Disables Logon and Exit menu items.
//      CMCLogon might yield control to Windows, so the user might be able to
//      access the window menu (for example click Logon) after we call
//      MAPILogon, but before it returns.
//
//  Parameters:
//      hWnd            - handle to the window/dialog who called us
//      fBeforeLogon    - TRUE when this function is called when we are about
//                      to call MAPILogon, FALSE if called after logon (failed)
//                      if Logon succeddes ToggleMenuState is called instead of
//                      this function.
//
//  Return:
//      Void.
//

void SecureMenu(HWND hWnd, BOOL fBeforeLogon)
{
    EnableMenuItem (GetMenu (hWnd), IDM_LOGON, fBeforeLogon);
    EnableMenuItem (GetMenu (hWnd), IDM_EXIT,  fBeforeLogon);
}


CLIENT.H

/*
 -  C L I E N T . H
 -
 *  Purpose:
 *      Header file for the sample mail client based on Simple MAPI.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#include "smapi.h"

/* Menu Item IDs */

#define IDM_LOGON       100
#define IDM_LOGOFF      101
#define IDM_EXIT        102
#define IDM_COMPOSE     103
#define IDM_READ        104
#define IDM_SEND        105
#define IDM_ADDRBOOK    106
#define IDM_DETAILS     107
#define IDM_ABOUT       108

/* Resource IDs */

#define ICON_NOMAIL     200
#define ICON_MAIL       201
#define IDB_ENVELOPE    300

/* Address Book Control IDs */

#define IDT_ADDRESS     101
#define IDC_ADDRESS     102
#define IDT_LIST        103
#define IDC_LIST        104
#define IDC_ADD         105
#define IDC_REMOVE      106

/* Compose Note Control IDs */

#define IDC_SEND        101
#define IDC_RESOLVE     102
#define IDC_ATTACH      103
#define IDC_OPTIONS     104
#define IDC_ADDRBOOK    105
#define IDT_TO          106
#define IDC_TO          107
#define IDT_CC          108
#define IDC_CC          109
#define IDT_SUBJECT     110
#define IDC_SUBJECT     111
#define IDC_NOTE        112
#define IDC_CATTACHMENT 113
#define IDT_CATTACHMENT 114
#define IDC_LINE1       -1
#define IDC_LINE2       -1

/* InBox Control IDs */

#define IDT_MSG         101
#define IDC_MSG         102
#define IDC_NEW         103
#define IDC_READ        104
#define IDC_DELETE      105
#define IDC_CLOSE       106

/* ReadNote Control IDs */

#define IDC_SAVECHANGES 101
#define IDC_SAVEATTACH  102
#define IDC_REPLY       103
#define IDC_REPLYALL    104
#define IDC_FORWARD     105
#define IDT_RFROM       106
#define IDT_RDATE       107
#define IDT_RTO         108
#define IDT_RCC         109
#define IDT_RSUBJECT    110
#define IDC_RFROM       111
#define IDC_RDATE       112
#define IDC_RTO         113
#define IDC_RCC         114
#define IDC_RSUBJECT    115
#define IDC_READNOTE    116
#define IDT_ATTACHMENT  117
#define IDC_ATTACHMENT  118

/* Options Control IDs */

#define IDC_RETURN      101

/* Details Control IDs */

#define IDT_NAME        100
#define IDC_NAME        101
#define IDT_TYPE        102
#define IDC_TYPE        103
#define IDT_ADDR        104
#define IDC_ADDR        105

/* About Box Control IDs */

#define IDC_VERSION     101

/* String Table IDs */

#define MAPI_ERROR_MAX          30

#define IDS_LOGONFAIL           (MAPI_ERROR_MAX + 1)
#define IDS_ADDRBOOKFAIL        (MAPI_ERROR_MAX + 2)
#define IDS_RESOLVEFAIL         (MAPI_ERROR_MAX + 3)
#define IDS_UNRESOLVEDNAMES     (MAPI_ERROR_MAX + 4)
#define IDS_SENDERROR           (MAPI_ERROR_MAX + 5)
#define IDS_DETAILS_TOO_MANY    (MAPI_ERROR_MAX + 6)
#define IDS_DETAILSFAIL         (MAPI_ERROR_MAX + 7)
#define IDS_NORECIPS            (MAPI_ERROR_MAX + 8)
#define IDS_SAVEATTACHERROR     (MAPI_ERROR_MAX + 9)
#define IDS_READFAIL            (MAPI_ERROR_MAX + 10)
#define IDS_DIALOGACTIVE        (MAPI_ERROR_MAX + 11)

#define IDS_FILTER              (MAPI_ERROR_MAX + 50)

/* Manifest Constants */

#define ADDR_MAX            128
#define MAXUSERS            10
#define TO_EDIT_MAX         512
#define CC_EDIT_MAX         512
#define SUBJECT_EDIT_MAX    128
#define NOTE_LINE_MAX       1024
#define FILE_ATTACH_MAX     32

/* Message Box styles */

#define MBS_ERROR           (MB_ICONSTOP | MB_OK)
#define MBS_INFO            (MB_ICONINFORMATION | MB_OK)
#define MBS_OOPS            (MB_ICONEXCLAMATION | MB_OK)

/* Structure Definitions */

typedef struct _msgid *LPMSGID;

typedef struct _msgid
{
    LPSTR       lpszMsgID;
    BOOL        fHasAttach;
    BOOL        fUnRead;
    LPSTR       lpszFrom;
    LPSTR       lpszSubject;
    LPSTR       lpszDateRec;
    LPMSGID     lpPrev;
    LPMSGID     lpNext;
} MSGID;

/* Function Prototypes */

int  PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, int);
void DeinitApplication(void);
long FAR PASCAL MainWndProc(HWND, UINT, UINT, LPARAM);
BOOL FAR PASCAL AboutDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL ComposeDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL InBoxDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL ReadMailDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL OptionsDlgProc(HWND, UINT, UINT, LONG);
BOOL FAR PASCAL DetailsDlgProc(HWND, UINT, UINT, LONG);
void    MakeMessageBox(HWND, ULONG, UINT, UINT);
ULONG   ResolveFriendlyNames(HWND, LPSTR, ULONG, ULONG *, lpMapiRecipDesc *);
ULONG   CopyRecipient(lpMapiRecipDesc, lpMapiRecipDesc, lpMapiRecipDesc);
ULONG   GetNextFile(HWND, ULONG, ULONG *, lpMapiFileDesc *);
ULONG   CopyAttachment(lpMapiFileDesc, lpMapiFileDesc, lpMapiFileDesc);
BOOL    FNameInList(LPSTR, ULONG, lpMapiRecipDesc);
LPMSGID MakeMsgNode(lpMapiMessage, LPSTR);
LPMSGID FindNode(LPMSGID, LPSTR);
void    InsertMsgNode(LPMSGID, LPMSGID *);
void    DeleteMsgNode(LPMSGID, LPMSGID *);
void    FreeMsgList(LPMSGID);
void    MakeDisplayNameStr(LPSTR, ULONG, ULONG, lpMapiRecipDesc);
ULONG   SaveMsgChanges(HWND, lpMapiMessage, LPSTR);
ULONG   MakeNewMessage(lpMapiMessage, UINT);
void    LogSendMail(ULONG);
void    SaveFileAttachments(HWND, lpMapiFileDesc);
void    ToggleMenuState(HWND, BOOL);
BOOL    fSMAPIInstalled(void);
void SecureMenu(HWND hWnd, BOOL fBeforeLogon);


SMAPI.H

/*
 -  S M A P I . H
 -
 *  Purpose:
 *      Header file for Simple MAPI definitions.
 *
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
 */

#ifndef MAPI_H
#include <mapi.h>
#endif

extern HINSTANCE    hlibMAPI;

extern LPMAPILOGON lpfnMAPILogon;
extern LPMAPILOGOFF lpfnMAPILogoff;
extern LPMAPISENDMAIL lpfnMAPISendMail;
extern LPMAPISENDDOCUMENTS lpfnMAPISendDocuments;
extern LPMAPIFINDNEXT lpfnMAPIFindNext;
extern LPMAPIREADMAIL lpfnMAPIReadMail;
extern LPMAPISAVEMAIL lpfnMAPISaveMail;
extern LPMAPIDELETEMAIL lpfnMAPIDeleteMail;
extern LPMAPIFREEBUFFER lpfnMAPIFreeBuffer;
extern LPMAPIADDRESS lpfnMAPIAddress;
extern LPMAPIDETAILS lpfnMAPIDetails;
extern LPMAPIRESOLVENAME lpfnMAPIResolveName;

#undef MAPILogon
#undef MAPILogoff
#undef MAPISendMail
#undef MAPISendDocuments
#undef MAPIFindNext
#undef MAPIReadMail
#undef MAPISaveMail
#undef MAPIDeleteMail
#undef MAPIFreeBuffer
#undef MAPIAddress
#undef MAPIDetails
#undef MAPIResolveName
#define MAPILogon           (*lpfnMAPILogon)
#define MAPILogoff          (*lpfnMAPILogoff)
#define MAPISendMail        (*lpfnMAPISendMail)
#define MAPISendDocuments   (*lpfnMAPISendDocuments)
#define MAPIFindNext        (*lpfnMAPIFindNext)
#define MAPIReadMail        (*lpfnMAPIReadMail)
#define MAPISaveMail        (*lpfnMAPISaveMail)
#define MAPIDeleteMail      (*lpfnMAPIDeleteMail)
#define MAPIFreeBuffer      (*lpfnMAPIFreeBuffer)
#define MAPIAddress         (*lpfnMAPIAddress)
#define MAPIDetails         (*lpfnMAPIDetails)
#define MAPIResolveName     (*lpfnMAPIResolveName)

extern BOOL InitSimpleMAPI(void);
extern void DeinitSimpleMAPI(void);


README.TXT

MAPI Sample Simple Form

The Simple.Frm sample application illustrates writing a form in C++. The form
can be used by form-aware mail clients for sending/receiving messages of
IPM.Note.Sample.Simple message class instead of standard send/read forms.

Installation
------------

The form has to be properly registered to be used by mail clients. Run
pdkin32 -f smpfrm.cfg to register the form in the local form registry. Then
run regedit smpfrm.reg to register smpfrm.exe as an OLE server for the form in
the registry. The path to the form .EXE file has to be put in smpfrm.reg or
into the PATH.

Implementation
--------------

The forms class CBaseForm supports the IMAPIForm, IPersistMessage, and
IMAPIFormAdviseSink interfaces. Class CClassFactory implements IClassFactory.
The form also implements two helper classes: CLastError and CViewNotifier to
handle last error information and event notifications.

If you would like to use the sample as a base for your own form, you'll need
to change:

 - File names.
 - Form message class and class ID (in guid.h, smpfrm.reg, smpfrm.cfg).
 - The Verbs and Extension sections and all the human readable information
   in smpfrm.cfg.
 - The UI and supporting functions.
 - Implementation of all of the IPersistMessage methods, IMAPIForm::Doverb.

Supported Features
------------------

 - Send.
 - Reply, Forward.
 - Next, Previous.
 - Save, Delete.

Unsupported Features
--------------------

 - Attachments.
 - Reply All, Post.
 - Move, Copy.
 - Print.

For unsupported features, you have a choice of either disabling them or
delegating their execution to the form viewer. This is controlled by setting
the appropriate fields of the Value variable in the [Extensions.1] section of
the form .cfg file. Refer to MAPI Form Documentation for details.


FORMBASE.H

//
//  FORMBASE.H
//
//  Base form class definition
//
//  Copyright (c) 1986-1996, Microsoft Corporation.
//  All rights reserved.
//

#include        <chsfld.h>

//
//  Form states
//

enum { stateUninit, stateNormal, stateNoScribble, stateHandsOffFromNormal,
        stateHandsOffFromSave, stateDead };

//
//  Message properties held in memory
//

enum { irtTo, irtCc, irtSubject, irtTime, irtFrom, irtClass, irtBody,
        irtNormSubject, irtConvIdx, irtConvTopic, cpropMsg};
#define MESSAGE_TAGS { cpropMsg, \
    { PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SUBJECT, \
      PR_CLIENT_SUBMIT_TIME, PR_SENDER_NAME, PR_MESSAGE_CLASS, \
      PR_BODY, PR_NORMALIZED_SUBJECT, PR_CONVERSATION_INDEX, PR_CONVERSATION_TOPIC } }

//form type
enum { eformRead, eformSend};

//reply type  (this form does not implement reply all)
enum eREPLYTYPE {eREPLY, eFORWARD, eREPLY_ALL};

//message class
//has to be the same as MessageClass in smpfrm.cfg
#define FormClassName "IPM.Note.Sample.Simple"

//all the MAPI form interfaces we'll need
class CBaseForm : public IPersistMessage, public IMAPIForm,
                    public IMAPIFormAdviseSink
{
    CBaseForm *         m_pfrmNext;             // Linked list of forms

    ULONG               m_cRef;                 // Reference Count on object
    ULONG               m_state;                //uses state enum
    CLastError          m_lsterr;               // Last Error Implementation

    CClassFactory *     m_pClassFactory;        // The factory that created us
    CViewNotifier       m_viewnotify;

    LPMAPIVIEWCONTEXT   m_pviewctxOverride;
    LPMAPIVIEWCONTEXT   m_pviewctx;             // View context interface

    LPMESSAGE           m_pmsg;                 // our message
    LPMAPIMESSAGESITE   m_pmsgsite;             // our message site
    LPMAPISESSION       m_pses;                 // our MAPI session
    LPADRBOOK           m_pab;                  // our address book

    LPADRLIST           m_padrlist;             // Current recipient list
    LPSPropValue        m_pval;                 // Current message contents

    ULONG               m_ulMsgFlags;           // Message flags
    ULONG               m_ulMsgStatus;          // Message status flags
    ULONG               m_ulSiteStatus;         // Message Site status flags
    ULONG               m_ulViewStatus;         // View context status flags

    ULONG               m_cxMin;                // Minimium size of window
    ULONG               m_cyMin;                //

    int                 m_eFormType;            //read/write

    unsigned int        m_fSameAsLoaded:1;      // Copy of ::Save flag
    unsigned int        m_fRecipientsDirty:1;   // Is the recip list dirty?
    unsigned int        m_fDirty:1;
    unsigned int        m_fConvTopicSet:1;

    ULONG               m_cbConvIdx;
    LPBYTE              m_lpbConvIdx;

    HACCEL              m_HAccelTable;

    HMODULE             m_hChsFldDll;
    HRPICKFOLDER        m_lpfnHrPickFolder;
    ULONG               m_cbCFDState;
    LPBYTE              m_pbCFDState;

public:

    HWND                m_hwnd;
    HWND                m_hwndDialog;

public:
    CBaseForm(CClassFactory *);
    ~CBaseForm(void);

    MAPI_IUNKNOWN_METHODS(IMPL);
    MAPI_GETLASTERROR_METHOD(IMPL);
    MAPI_IPERSISTMESSAGE_METHODS(IMPL);
    MAPI_IMAPIFORM_METHODS(IMPL);
    MAPI_IMAPIFORMADVISESINK_METHODS(IMPL);

    //utility methods

    BOOL                TranslateMessage(MSG&);
    LPMESSAGE           Message(void) { return m_pmsg; }
    LPMAPIMESSAGESITE   MsgSite(void) { return m_pmsgsite; }
    LPMAPIVIEWCONTEXT   ViewCtx(void) { return (m_pviewctxOverride ? m_pviewctxOverride:m_pviewctx); }
    void                Address(int);
    void                ClearWindow(void);
    void                ConfigMenu(HMENU hMenu);
    HRESULT             HrQuerySave (DWORD ulSaveOptions);
    void                SetMinSize(int cx, int cy) { m_cxMin = cx; m_cyMin = cy; return; }
    CBaseForm *         GetNext(void) { return m_pfrmNext; }
    void                DoSubmit(void);
    void                DoSave(void);
    void                DoDelete(void);
    void                DoCopy(void);
    void                DoMove(void);
    void                DoNext(ULONG ulDir);
    void                DoReply(eREPLYTYPE);

private:

    void                DisplayMessage(void);
    HRESULT             HrGetMsgDataFromUI(HWND hDlg);
    HRESULT             HrGetMsgDataFromMsg(LPMESSAGE pmsg, ULONG ulMsgFlags);
    BOOL                IsAddressed(void);
    HRESULT             HrReply(eREPLYTYPE eType, HWND hwndParent, LPCRECT rcPos);
    void                DeInitObjects(void);
    HRESULT             HrOpenForm(HWND, LPCRECT, ULONG);
    HRESULT             HrSaveInto(LPMESSAGE);
    void                UpdateRecipientsDisplay(void);
    int                 ShowMessageBox(HWND, LPCTSTR, LPCTSTR, UINT);
    void                ShowError();
    inline void         ConfigWinMenu(void);
    HRESULT             HrSaveToString(LPSTR *);
    void                ClearDirty(void);
    BOOL                FGetFoldChooser(void);

};

//
//  Linked list of form objects
//

extern CBaseForm *      g_PfrmFirst;

//
//  Other globals
//

extern HINSTANCE        g_hinst;
extern LPVOID           g_lpCtl3d;

void FormatTime(FILETIME *pft, LPSTR szTime);
HRESULT GetMsgAdrlist (LPMESSAGE pmsg, LPADRLIST *  ppAdrList, CLastError *);
HRESULT HrStreamInMsgBody(LPMESSAGE pmsg, LPVOID pbase, LPSTR *  pszBody, CLastError * plsterr);
HRESULT HrStreamOutMsgBody(LPMESSAGE pmsg, LPSTR szBody, CLastError * plsterr);

///     CBaseForm::ConfigWinMenu()
//
inline void CBaseForm::ConfigWinMenu()
{
    if(NULL != m_hwnd)
    {
        HMENU hmenu = GetMenu(m_hwnd);
        if(NULL != hmenu)
            ConfigMenu(hmenu);
    }
}


FORMWND.CPP

/***********************************************************************
 *
 *  FORMWND.CPP
 *
 *
 *  Copyright 1986-1996 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#include        "precomp.h"

BOOL CALLBACK
AboutDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//WM_SIZE handler
void ON_Size(HWND hwnd, UINT state, int x, int y)
{
    CBaseForm * pform = (CBaseForm *) GetWindowLong(hwnd, 0);

    SetWindowPos(pform->m_hwndDialog, NULL, 0, 0, x, y,
              SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
}

////  A specialized version of the window proc which plays how messages
//      are going to be handled.
//

////

BOOL CALLBACK FormDlgProcSend(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    CBaseForm *         pfrm;
    RECT                rect;
    int                 wmId;
    int                 wmEvent;

    pfrm = (CBaseForm *) GetWindowLong(GetParent(hdlg), 0);

    switch ( msg ) {
    case WM_INITDIALOG:
        GetWindowRect(hdlg, &rect);
        pfrm->SetMinSize(rect.right - rect.left,
                         rect.bottom - rect.top +
                         GetSystemMetrics(SM_CYCAPTION) +
                         GetSystemMetrics(SM_CYMENU));
        break;

    case WM_SIZE:
        {
            HWND        hwnd;
            RECT        rect;
            int         cx;
            int         cy;

            //resize edit to match the dialog
            GetWindowRect(hdlg, &rect);
            cx = rect.right;
            cy = rect.bottom;

            HDWP hdwp = BeginDeferWindowPos(4);
            if(NULL == hdwp)
                break;

            hwnd = GetDlgItem(hdlg, ID_TO);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_CC);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_SUBJECT);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_BODY);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left, cy - rect.top,
                         SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER))
            {
                break;
            }

            EndDeferWindowPos(hdwp);
        }
        break;

    case WM_COMMAND:
        wmId = GET_WM_COMMAND_ID(wParam, lParam);
        wmEvent = GET_WM_COMMAND_CMD(wParam, lParam);

        switch(wmId) {
        case ID_TO_BUTTON:
        case ID_CC_BUTTON:
            switch (wmEvent) {
            case BN_CLICKED:
                pfrm->Address(wmId);
                break;

            default:
                return FALSE;
            }
            break;
        case ID_BODY:
        {
            HMENU hMenu = GetMenu(GetParent(hdlg));
            if(wmEvent == EN_KILLFOCUS)
            {
                EnableMenuItem(hMenu, IDC_EDIT_CUT, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_COPY, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_PASTE, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_UNDO, MF_BYCOMMAND|MF_GRAYED);
            }
            else if(wmEvent == EN_SETFOCUS)
            {
                EnableMenuItem(hMenu, IDC_EDIT_CUT, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_COPY, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_PASTE, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_UNDO, MF_BYCOMMAND);
            }
        }
        break;
        case ID_SUBJECT:
            if (wmEvent == EN_KILLFOCUS) {
                char    sz[250];

                if (Edit_GetText(GET_WM_COMMAND_HWND(wParam, lParam), sz, 200)) {
                    lstrcat(sz, " - ");
                    lstrcat(sz, g_szWindowCaption);
                    SetWindowText(GetParent(hdlg), sz);
                }
                else
                    SetWindowText(GetParent(hdlg), g_szWindowCaption);

                break;
            }
            return FALSE;

        default:
            return FALSE;
        }

    default:
        return FALSE;
    }

    return TRUE;
}

//// Window proc for frame window of both the read and send form.
//
//  Send frame window does not receive IDC_VIEW_ITEMABOVE, IDC_VIEW_ITEMBELOW
// commands.
// Read frame window does not receive IDC_MESSAGE_SUBMIT command.
LRESULT CALLBACK WndProcForm(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HMENU               hMenu;
    CBaseForm *         pform;
    int                 wmId;
    int                 wmEvent;

    pform = (CBaseForm *) GetWindowLong(hwnd, 0);

    switch( msg ) {
    case WM_COMMAND:
        wmId = GET_WM_COMMAND_ID(wParam, lParam);
        wmEvent = GET_WM_COMMAND_CMD(wParam, lParam);

        switch( wmId ) {
        case IDC_MESSAGE_CLOSE:
            pform->ShutdownForm(SAVEOPTS_PROMPTSAVE);
            break;

        case IDC_MESSAGE_SAVE:
            pform->DoSave();
            break;

        case IDC_VIEW_ITEMABOVE:
        case IDC_VIEW_ITEMBELOW:
            {
                ULONG ulDir = IDC_VIEW_ITEMABOVE == wmId ? VCDIR_PREV:VCDIR_NEXT;
                pform->DoNext(ulDir);

                break;
            }
        case IDC_MESSAGE_DELETE:
            pform->DoDelete();
            break;

        case IDC_MESSAGE_REPLY:
            pform->DoReply(eREPLY);
            break;

        case IDC_MESSAGE_FORWARD:
            pform->DoReply(eFORWARD);
            break;

        case IDC_MESSAGE_SUBMIT:
            pform->DoSubmit();
            break;

        case IDC_MESSAGE_COPY:
            pform->DoCopy();
            break;

        case IDC_MESSAGE_MOVE:
            pform->DoMove();
            break;

        //the edit menu commands are enabled only if the ID_BODY text
        //box has the focus
        case IDC_EDIT_COPY:
            SendMessage(GetDlgItem(pform->m_hwndDialog, ID_BODY), WM_COPY, 0,0);
            break;
        case IDC_EDIT_CUT:
            SendMessage(GetDlgItem(pform->m_hwndDialog, ID_BODY), WM_CUT, 0,0);
            break;
        case IDC_EDIT_PASTE:
            SendMessage(GetDlgItem(pform->m_hwndDialog, ID_BODY), WM_PASTE, 0,0);
            break;
        case IDC_EDIT_UNDO:
            SendMessage(GetDlgItem(pform->m_hwndDialog, ID_BODY), WM_UNDO, 0,0);
            break;

        case IDC_HELP_ABOUT:
            DialogBox (g_hinst, "AboutBox", hwnd, AboutDlgProc);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
        break;

        //
        //  Do all of the correct menu graying
        //
    case WM_INITMENU:
        hMenu = (HMENU) wParam;
        pform->ConfigMenu(hMenu);
   break;

        //
        //  Deal with the System Close message
        //
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_CLOSE)
        {
            pform->ShutdownForm(SAVEOPTS_PROMPTSAVE);
        }
        else
        {
            goto DoDefault;
        }
        break;

    case WM_SETFOCUS:
        SetFocus(pform->m_hwndDialog);
        break;

        //
        //  Let the object know that the window has been destroyed so
        //      that it will not talk to it any more
        //
    case WM_DESTROY:
        pform->ClearWindow();
        goto DoDefault;

    case WM_SIZE:
        HANDLE_WM_SIZE(hwnd, wParam, lParam, ON_Size);
        goto DoDefault;

    default:
DoDefault:
        return DefWindowProc (hwnd, msg, wParam, lParam);
    }

    return 0;
}

////

BOOL CALLBACK FormDlgProcRead(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    CBaseForm *         pfrm;
    RECT                rect;
    int                 wmId;
    int                 wmEvent;

    pfrm = (CBaseForm *) GetWindowLong(GetParent(hdlg), 0);

    switch ( msg ) {
    case WM_INITDIALOG:
        {
            Assert(pfrm);

            GetWindowRect(hdlg, &rect);
            pfrm->SetMinSize(rect.right - rect.left,
                            rect.bottom - rect.top +
                            GetSystemMetrics(SM_CYCAPTION) +
                            GetSystemMetrics(SM_CYMENU));
            break;
        }

    case WM_SIZE:
        {
            HWND        hwnd;
            RECT        rect;
            int         cx;
            int         cy;

            GetWindowRect(hdlg, &rect);
            cx = rect.right;
            cy = rect.bottom;

            HDWP hdwp = BeginDeferWindowPos(5);
            if(NULL == hdwp)
                break;

            hwnd = GetDlgItem(hdlg, ID_FROM);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_SENT);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_TO);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_SUBJECT);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left - 2,
                         rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOMOVE |
                         SWP_NOZORDER))
            {
                break;
            }

            hwnd = GetDlgItem(hdlg, ID_BODY);
            GetWindowRect(hwnd, &rect);
            if(NULL == DeferWindowPos(hdwp, hwnd, NULL, 0, 0, cx - rect.left, cy - rect.top,
                         SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER))
            {
                break;
            }

            EndDeferWindowPos(hdwp);
        }
        break;

    case WM_COMMAND:
        wmId = GET_WM_COMMAND_ID(wParam, lParam);
        wmEvent = GET_WM_COMMAND_CMD(wParam, lParam);

        switch(wmId) {
        case ID_BODY:
        {
            HMENU hMenu = GetMenu(GetParent(hdlg));
            if(wmEvent == EN_KILLFOCUS)
            {
                EnableMenuItem(hMenu, IDC_EDIT_CUT, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_COPY, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_PASTE, MF_BYCOMMAND|MF_GRAYED);
                EnableMenuItem(hMenu, IDC_EDIT_UNDO, MF_BYCOMMAND|MF_GRAYED);
            }
            else if(wmEvent == EN_SETFOCUS)
            {
                EnableMenuItem(hMenu, IDC_EDIT_CUT, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_COPY, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_PASTE, MF_BYCOMMAND);
                EnableMenuItem(hMenu, IDC_EDIT_UNDO, MF_BYCOMMAND);
            }
        }
        break;
        default:
            return FALSE;
        }

    default:
        return FALSE;
    }

    return TRUE;
}

BOOL CALLBACK
AboutDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    char    rgchVersion[80];

    switch (msg)
    {
    case WM_INITDIALOG:
        wsprintf(rgchVersion, "Version %d.%d.%d (%s)", rmj, rmm, rup,
            szVerName && *szVerName ? szVerName : "BUDDY");
        SetDlgItemText(hDlg, IDC_VERSION, rgchVersion);
        return TRUE;

    case WM_COMMAND:
        if (wParam == IDOK || wParam == IDCANCEL)
        {
            EndDialog (hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}


GUID.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  FILE:           GUID.CPP
//
//
//
//  Copyright (c) 1986-1996, Microsoft Corporation.
//  All rights reserved.
//
//--

#define STRICT
#include        <windows.h>
#include        <ole2.h>
#include        <initguid.h>

#define         USES_IID_IMAPIForm
#define         USES_IID_IMAPIFormAdviseSink
#define         USES_IID_IPersistMessage

#include        "guid.h"
#include        "mapiguid.h"


GUID.H

// {88FFF551-B8C5-11ce-8DE0-00AA0060D242}
//the same as CLSID in smpfrm.cfg
DEFINE_GUID(CLSID_IPM_NOTE_SAMPLE,
0x88fff551, 0xb8c5, 0x11ce, 0x8d, 0xe0, 0x00, 0xaa, 0x00, 0x60, 0xd2, 0x42);

//string representation of our class ID
#define  szGUID "{88FFF551-B8C5-11CE-8DE0-00AA0060D242}"


ICLASSF.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  FILE:           ICLASSF.CPP
//
//  DESCRIPTION:    Sample form's class factory
//
//
//  Copyright (c) 1994, Microsoft Corporation.
//  All rights reserved.
//
//--

#include "precomp.h"

CClassFactory           ClassFactory;

HWND  CClassFactory::ms_hWndMain;
ULONG CClassFactory::ms_cLocks= 0;
ULONG CClassFactory::ms_cObjs = 0;

///////////////////////////////////////////////////////////////////////////////
//
//  Constructor and destructor

CClassFactory::CClassFactory()
{
    m_cRef   = 1;
}

CClassFactory::~CClassFactory (void)
{
    ASSERT(m_cRef == 1);
}

///////////////////////////////////////////////////////////////////////////////
//
//  IUnknown methods

STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void ** ppvObj)
{

    if ((IID_IClassFactory == riid) || (IID_IUnknown == riid))
    {
        *ppvObj = (IClassFactory*) this;
        AddRef ();
        return NOERROR;
    }

    *ppvObj = 0;

    return ResultFromScode (E_NOINTERFACE);
}

STDMETHODIMP_ (ULONG)CClassFactory::AddRef()
{
    return ++ m_cRef;
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
    ULONG cRef = -- m_cRef;

    //We quit when cRef == 1 (as oppossed to == 0) because OLE
    //will not release its reference until after we call
    // CoRevokeClassObject(). So if cRef==1 we know that only
    //OLE has a reference to as, and it's time to go...
    if ((cRef == 1) && (ms_cLocks == 0) && (ms_cObjs == 0))
    {
        if (ms_hWndMain)
        {
            PostMessage (ms_hWndMain, WM_CLOSE, 0, 0L);
        }
    }

    return cRef;
}

///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid,
                                           LPVOID FAR* ppvObject)
{
    HRESULT     hr  = ResultFromScode (E_NOINTERFACE);
    CBaseForm * pbf = NULL;

    if (!ppvObject)
        return ResultFromScode (E_INVALIDARG);

    *ppvObject = 0;

    //
    //  We don't support aggregation.

    if (pUnkOuter != NULL)
        return  ResultFromScode (CLASS_E_NOAGGREGATION);

    pbf = new CBaseForm (this);

    if(NULL == pbf)
        return ResultFromScode(E_OUTOFMEMORY);

    hr = pbf -> QueryInterface (riid, ppvObject);
    if (HR_FAILED(hr))
        goto err;

    ms_cObjs ++;

    hr = hrSuccess;

err:
    if (pbf)
    {
        pbf -> Release ();
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
    if (fLock)
    {
        ++ ms_cLocks;
    }
    else
    {
        ASSERT (ms_cLocks > 0);
        -- ms_cLocks;

        if ((ms_cLocks == 0) && (ms_cObjs == 0))
        {
            if (ms_hWndMain)
            {
                PostMessage (ms_hWndMain, WM_CLOSE, 0, 0L);
            }
        }
    }
    return NOERROR;
}

void FAR PASCAL CClassFactory::ObjDestroyedCallback (void)
{
    ASSERT (ms_cObjs > 0);
    ms_cObjs --;

    if ((ms_cLocks == 0) && (ms_cObjs == 0))
    {
        if (ms_hWndMain)
        {
            PostMessage (ms_hWndMain, WM_CLOSE, 0, 0L);
        }
    }
}


MAIN.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  FILE:           MAIN.CPP
//
//  DESCRIPTION:    This file contains the code for the entry function and
//                      the main window for this form
//
//
//  Copyright (c) 1995, Microsoft Corporation.
//  All rights reserved.
//
//--

#include "precomp.h"
#include <wrap3d.h>

// Just to make it easier to determine appropriate code paths:
#if defined (_WIN32) // This had better be true, but just in case...
    #define IS_WIN32 TRUE
#else
    #define IS_WIN32 FALSE
#endif
#define IS_NT      IS_WIN32 && (BOOL)(GetVersion() < 0x80000000)
#define IS_WIN32S  IS_WIN32 && (BOOL)(!(IS_NT) && (LOBYTE(LOWORD(GetVersion()))<4))
#define IS_CHICAGO (BOOL)(!(IS_NT) && !(IS_WIN32S)) && IS_WIN32

///////////////////////// Global Variables /////////////////////////////////

extern CClassFactory   ClassFactory;
HINSTANCE       g_hinst;
char            SzAppName[] = "SimpFrm";  // The name of this application
char            g_szSendWinClass[] = "SampleFormSend";
char            g_szReadWinClass[] = "SampleFormRead";
char            g_szFormName[] = "Simple Sample Form";
char            g_szWindowCaption[] = "Sample Form";
CBaseForm *     g_PfrmFirst = NULL;
LPVOID          g_lpCtl3d = NULL;           //  CTL3D context

//// Internal Flags

ULONG           UlCoRegForm = 0;
BOOL            FMapiInitialized = FALSE;

//we have to create an atom containig string representation of
// the class id;  the atom has to have the same live time as the
//ClassFactory object
static ATOM     atmCFAlive;

//////////////////////////// Extern and forward declarations //////////////

LRESULT CALLBACK WndProcMain(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProcForm(HWND, UINT, WPARAM, LPARAM);

////    InitApplication
//

BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASSEX  wc;

    //  Fill in the window class structure with parameters that describe
    //  the main window.  This window will never be displayed and exists
    //  so that OLE can talk to us.

    wc.cbSize           = sizeof(wc);
    wc.style            = 0;
    wc.lpfnWndProc      = (WNDPROC) WndProcMain;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = hInstance;
    wc.hIcon            = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor          = ::LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH) (COLOR_WINDOW+1);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = SzAppName;
    wc.hIconSm          = LoadIcon(hInstance, "SmallIcon");

    //  Register the window class.
    if (!RegisterClassEx(&wc))
    {
        return FALSE;
    }

    //  Now setup and register the acutual window class used for the
    //  form.

    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName    = g_szSendWinClass;
    wc.lpfnWndProc      = (WNDPROC) WndProcForm;
    wc.cbWndExtra       = 4;
    wc.hIcon            = LoadIcon(hInstance, "IconL");
//    if (IS_CHICAGO) {
//        wc.lpszMenuName = "CHICAGO";
//    }
//    else {
        wc.lpszMenuName = MAKEINTRESOURCE(IDR_SEND_FORM);
//    }

    if (!RegisterClassEx(&wc))
    {
        return FALSE;
    }

    //  Now setup and register the acutual window class used for the
    //  form.

    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName    = g_szReadWinClass;
    wc.lpfnWndProc      = (WNDPROC) WndProcForm;
    wc.cbWndExtra       = 4;
    wc.hIcon            = LoadIcon(hInstance, "IconL");
//    if (IS_CHICAGO) {
//        wc.lpszMenuName = "CHICAGO";
//    }
//    else {
        wc.lpszMenuName = MAKEINTRESOURCE(IDR_READ_FORM);
//    }

    return RegisterClassEx(&wc);
}

////    InitInstance
//

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HRESULT             hr;
    ULONG               ulVer;
    HWND                hWnd;
    BOOL                f;

    ///////////////////////////////////////////////////////////////////////
    //
    //  Initialize OLE and MAPI

    ulVer = CoBuildVersion ();
#if 0
    if (HIWORD (ulVer) != rmm)
    {
        return FALSE;
    }
#endif // 0

    //
    //  Initialize MAPI -- this implies a successfull initialization of
    //  OLE on success
    //

    hr = MAPIInitialize (NULL);
    if (HR_FAILED(hr))
    {
        return FALSE;
    }
    FMapiInitialized = TRUE;

    //
    //  Give ourselves the 3D look
    //

    if (!g_lpCtl3d)
    {
        g_lpCtl3d = CTL3D_Initialize(hInstance);
        if (g_lpCtl3d)
            CTL3D_AutoSubclass(g_lpCtl3d, hInstance, &f);
    }

    //Create global atom with string representaton of our class id
    if(0 == FindAtom(szGUID))
        atmCFAlive = GlobalAddAtom(szGUID);

    //
    //  Register our class factory for the form
    //

    hr = CoRegisterClassObject(CLSID_IPM_NOTE_SAMPLE, &ClassFactory,
                               CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,
                               &UlCoRegForm);
    if (FAILED(hr))
    {
        return FALSE;
    }

    //
    //  Create a main window -- This window is used for dealing with
    //          messages coming from the top of the work and consitutes
    //          our main window.  We create this window of size zero and
    //          not visible since it is not the window the user
    //          interacts with

    hWnd = CreateWindow(SzAppName, NULL, WS_OVERLAPPEDWINDOW,
                        0, 0, 0, 0, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
        return FALSE;
    }

    ClassFactory.ms_hWndMain = hWnd;
    g_hinst = hInstance;

    return TRUE;
}

int ExitInstance ()
{
    //
    //  Revoke all classes factories here so we don't get any new
    //  creates

    if (UlCoRegForm != 0)
    {
        CoRevokeClassObject (UlCoRegForm);
        UlCoRegForm = 0;
    }

    //
    //  Shut down CTL3D

    if (g_lpCtl3d)
    {
        CTL3D_Uninitialize(g_lpCtl3d);
        g_lpCtl3d = NULL;
    }

    //
    //  Uninitialize MAPI

    if (FMapiInitialized)
    {
        MAPIUninitialize();
        FMapiInitialized = FALSE;
    }

    GlobalDeleteAtom(atmCFAlive);

    return 0;
}

////    WinMain
//

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR /*lpCmdLine*/, int nCmdShow)
{
    MSG         msg;

    if (!hPrevInstance)
    {
        // Perform instance initialization
        if (!InitApplication(hInstance))
        {
            return FALSE;
        }
    }

    // Perform application initialization
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    //  Main message loop
    while (GetMessage(&msg, NULL, 0, 0))
    {
        CBaseForm *     pfrm;

        //
        //  Find which top level window this message is for
        //

        for (pfrm = g_PfrmFirst; pfrm != NULL; pfrm = pfrm->GetNext())
        {
            if (pfrm->TranslateMessage(msg))
            {
                break;
            }
        }

        if (pfrm == NULL)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    ExitInstance();

    return msg.wParam;
}

////  A specialized version of the window proc which plays how messages
//      are going to be handled.
//

LRESULT CALLBACK WndProcMain(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)
   {
   case WM_DESTROY:
       PostQuitMessage(0);
       break;

   default:
       return DefWindowProc (hwnd, msg, wParam, lParam);
   }

   return 0;
}


PRECOMP.H

/***********************************************************************
 *
 *  PRECOMP.H
 *
 *
 *  Copyright 1995 Microsoft Corporation.  All Rights Reserved.
 *
 ***********************************************************************/

#define         STRICT  1

#include        <windows.h>
#include        <windowsx.h>
#include        <ole2.h>
#include        <mapix.h>
#include        <mapiform.h>
#include        <mapidbg.h>
#include        <mapiutil.h>
#include        <exchform.h>
#include        <pdkver.h>
#include        <strstrea.h>
#include        "resource.h"

#include        "viewntfr.h"
#include        "lasterr.h"

#include        "guid.h"

#define ASSERT(a)       Assert(a)

extern char g_szFormName[];
extern char g_szWindowCaption[];
extern char g_szSendWinClass[];
extern char g_szReadWinClass[];

//
// CClassFactory
//

class CClassFactory : public IClassFactory
{
public:
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID FAR * ppvObj);
    STDMETHOD_(ULONG,AddRef)  (THIS);
    STDMETHOD_(ULONG,Release) (THIS);
    STDMETHOD (CreateInstance) (LPUNKNOWN pUnkOuter, REFIID riid,
                                LPVOID FAR* ppvObject);
    STDMETHOD (LockServer) (BOOL fLock);

    CClassFactory(void);
    ~CClassFactory(void);

    void FAR PASCAL ObjDestroyedCallback ();

public:
    static HWND ms_hWndMain;    // Main window handle

protected:
    ULONG m_cRef;               // Count of references
    static ULONG ms_cObjs;      // Count of active objects
    static ULONG ms_cLocks;     // Count of active locks
};

#include        "formbase.h"


FORMBASE.CPP

//////////////////////////////////////////////////////////////////////////////
//
//  FILE:           FORMBASE.CPP
//
//
//
//  Copyright (c) 1986-1996, Microsoft Corporation.
//  All rights reserved.
//
//--

#include        "precomp.h"
#include        <cindex.h>

static BOOL     g_FModalUp = FALSE;
static BOOL     g_FMBoxUp = FALSE;
static HWND     g_hwndUp = NULL;

BOOL CALLBACK FormDlgProcSend(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK FormDlgProcRead(HWND, UINT, WPARAM, LPARAM);

SizedSPropTagArray(cpropMsg, tagaRead) = MESSAGE_TAGS;

//szRE_PREFIX and szFW_PREFIX have to have the same length
char szRE_PREFIX[] = "RE: ";
char szFW_PREFIX[] = "FW: ";

#define EXCLUDED_PROPS_ON_REPLY     32
SizedSPropTagArray (EXCLUDED_PROPS_ON_REPLY, sptExcludedProps) =
{
    EXCLUDED_PROPS_ON_REPLY,
    {
        PR_SENDER_NAME,
        PR_SENDER_ENTRYID,
        PR_SENDER_SEARCH_KEY,
        PR_SENDER_EMAIL_ADDRESS,
        PR_SENDER_ADDRTYPE,

        PR_RECEIVED_BY_NAME,
        PR_RECEIVED_BY_ENTRYID,
        PR_RECEIVED_BY_SEARCH_KEY,

        PR_SENT_REPRESENTING_NAME,
        PR_SENT_REPRESENTING_ENTRYID,
        PR_SENT_REPRESENTING_SEARCH_KEY,
        PR_SENT_REPRESENTING_EMAIL_ADDRESS,
        PR_SENT_REPRESENTING_ADDRTYPE,

        PR_RCVD_REPRESENTING_NAME,
        PR_RCVD_REPRESENTING_ENTRYID,
        PR_RCVD_REPRESENTING_SEARCH_KEY,

        PR_MESSAGE_FLAGS,
        PR_MESSAGE_RECIPIENTS,

        PR_READ_RECEIPT_ENTRYID,
        PR_REPORT_ENTRYID,

        PR_REPLY_RECIPIENT_ENTRIES,
        PR_REPLY_RECIPIENT_NAMES,

        PR_PARENT_KEY,

        PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,

        PR_READ_RECEIPT_REQUESTED,

        PR_CLIENT_SUBMIT_TIME,
        PR_MESSAGE_DELIVERY_TIME,
        PR_MESSAGE_DOWNLOAD_TIME,

        PR_BODY,
        PR_SUBJECT,
        PR_SUBJECT_PREFIX,
        PR_MESSAGE_ATTACHMENTS

    }
};

////    CBaseForm::CBaseForm
//

CBaseForm::CBaseForm(CClassFactory * pClassFactory)
                : m_lsterr(g_szFormName)
{
    m_pClassFactory = pClassFactory;
    m_pClassFactory->AddRef();

    m_ulViewStatus = 0;
    m_ulSiteStatus = 0;
    m_cRef = 1;

    m_pviewctxOverride = NULL;
    m_pviewctx = NULL;
    m_pmsgsite = NULL;
    m_pmsg = NULL;

    m_hwnd = NULL;
    m_hwndDialog = NULL;

    m_pses = NULL;
    m_pab = NULL;
    m_pval = NULL;

    m_padrlist = NULL;
    m_fRecipientsDirty = FALSE;

    m_fDirty = FALSE;

    m_fSameAsLoaded = FALSE;

    //
    //  Add self to the head of the linked list of forms
    //

    m_pfrmNext = g_PfrmFirst;
    g_PfrmFirst = this;

    m_cbConvIdx = 0;
    m_lpbConvIdx = NULL;
    m_fConvTopicSet = FALSE;

    m_hChsFldDll = NULL;
    m_lpfnHrPickFolder = NULL;
    m_cbCFDState = 0;
    m_pbCFDState = NULL;

    m_state = stateUninit;
}

////    CBaseForm::~CBaseForm
//
CBaseForm::~CBaseForm()
{
    CBaseForm *         pfrm;
    //
    //  Remove ourselves from the form link list
    //

    if (g_PfrmFirst == this)
    {
        g_PfrmFirst = m_pfrmNext;
    }
    else
    {
        for (pfrm = g_PfrmFirst; pfrm != NULL; pfrm = pfrm->GetNext())
        {
            if (pfrm->m_pfrmNext == this)
            {
                pfrm->m_pfrmNext = m_pfrmNext;
                break;
            }
        }
        Assert(pfrm != NULL);
    }

    MAPIFreeBuffer(m_pval);

    FreePadrlist(m_padrlist);

    MAPIFreeBuffer(m_lpbConvIdx);

    if(m_hChsFldDll)
        FreeLibrary(m_hChsFldDll);

    MAPIFreeBuffer(m_pbCFDState);

    //
    //  Let the class factory know we are gone
    //

    UlRelease(m_pClassFactory);

}

////    CBaseForm::DeInitObjects
//

void CBaseForm::DeInitObjects()
{
    if (m_hwnd != NULL)
    {
        DestroyWindow(m_hwnd);            // Destroy the frame window
    }

    Assert(m_pmsg == NULL);
    Assert(m_pmsgsite == NULL);
    Assert(m_pviewctx == NULL);
    Assert(m_pviewctxOverride == NULL);
    Assert(m_pab == NULL);
    Assert(m_pses == NULL);

    m_state = stateDead;
}

///////////////////////////////////////////////////////////////////////////////
//
//  IUnknown interface
//
///////////////////////////////////////////////////////////////////////////////

////    CBaseForm::QueryInterface
//
//
//  DESCRIPTION:    The form supports IMAPIform,
//                  IPersistMessage and IAMPIFormAdviseSink

STDMETHODIMP CBaseForm::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    if (riid == IID_IUnknown || riid == IID_IMAPIForm)
    {
        *ppvObj = (LPVOID) (IMAPIForm *) this;
    }
    else if (riid == IID_IPersistMessage)
    {
        *ppvObj = (LPVOID) (IPersistMessage *) this;
    }
    else if (riid == IID_IMAPIFormAdviseSink)
    {
        *ppvObj = (LPVOID) (IMAPIFormAdviseSink *) this;
    }
    else
    {
        *ppvObj = NULL;
        return  m_lsterr.HrSetLastError(ResultFromScode(E_NOINTERFACE));
    }

    AddRef();
    return hrSuccess;
}

////    CBaseForm::AddRef
//
//  Description:
//

STDMETHODIMP_(ULONG) CBaseForm::AddRef ()
{
    m_cRef += 1;
    return m_cRef;
}

////    CBaseForm::Release
//

STDMETHODIMP_(ULONG) CBaseForm::Release ()
{
    ULONG cRef = -- m_cRef;

    if (cRef == 0)
    {
        // Let the class factory now we are gone
        m_pClassFactory->ObjDestroyedCallback();
        delete this;
    }

    return cRef;
}

///////////////////////////////////////////////////////////////////////////////
//
//  IMAPIForm interface
//
///////////////////////////////////////////////////////////////////////////////

////    IMAPIForm::SetViewContext
//
//

STDMETHODIMP CBaseForm::SetViewContext(IN IMAPIViewContext * pvc)
{

    //
    //  If we currently have a view context, then release it
    //

    if (m_pviewctx != NULL)
    {
        m_pviewctx->SetAdviseSink(NULL);
        m_pviewctx->Release();
    }

    //
    // Accept the new view context.
    //

    m_pviewctx = pvc;

    //
    //  If the new view context is non-null, then save it away, setup
    //  the advise sink back to check for things and get the current set
    //  of status flags
    //

    m_ulViewStatus = 0;
    if (pvc != NULL)
    {
        m_pviewctx->AddRef ();
        m_pviewctx->SetAdviseSink (this);
        m_pviewctx->GetViewStatus(&m_ulViewStatus);
    }

    ConfigWinMenu();

    return hrSuccess;
}

////    IMAPIForm::GetViewContext
//

STDMETHODIMP CBaseForm::GetViewContext(OUT IMAPIViewContext * FAR * ppvc)
{
    Assert(ppvc);

    *ppvc = m_pviewctx;

    if(m_pviewctx != NULL)
    {
        m_pviewctx->AddRef();
        return hrSuccess;
    }
    else
        return ResultFromScode(S_FALSE);
}

////    IMAPIForm::ShutdownForm
//
//  Description:
//      This routine is called to shut down the form and if necessary
//      to cause save changes to the form.
//

STDMETHODIMP CBaseForm::ShutdownForm(DWORD dwSaveOptions)
{
  HRESULT             hr;

    //
    //  Check for valid state to make the call
    //

    switch( m_state )
    {
        default:
        case stateDead:
            m_viewnotify.OnShutdown ();
            return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));

        case stateUninit:
        case stateNormal:
        case stateNoScribble:
        case stateHandsOffFromSave:
        case stateHandsOffFromNormal:
            break;
    }

    hr = HrQuerySave(dwSaveOptions);
    if(HR_FAILED(hr))
        return hr;
    //
    //  Save us from ourselfs by add-refing the object
    //

    AddRef();

    //
    //  Release the view context
    //

    if (m_pviewctx != NULL)
    {
        m_pviewctx->SetAdviseSink(NULL);
        m_pviewctx->Release();
        m_pviewctx = NULL;
    }

    //
    //  We need to notify anyone who has an advise on us that we are
    //  shutting down.  We want to do this in such a manner as to
    //  protect ourselves since we are referencing the data structure
    //  internally.  Thus the AddRef/Release pair.
    //

    m_viewnotify.OnShutdown ();

    //
    //  Release message objects if we have them
    //

    if(g_FModalUp)
        m_pviewctxOverride = NULL;

    UlRelease(m_pmsg);
    m_pmsg = NULL;

    UlRelease(m_pmsgsite);
    m_pmsgsite = NULL;

    UlRelease(m_pab);
    m_pab = NULL;

    UlRelease(m_pses);
    m_pses = NULL;

    //
    //  Tell all objects to be closed and de-initialized, only IUnknown
    //          calls are legal after this.
    //

    DeInitObjects();

    //
    //  We are now all done -- release our internal reference
    //

    Release();

    return hrSuccess;
}

////    IMAPIForm::DoVerb
//

STDMETHODIMP CBaseForm::DoVerb(LONG iVerb, LPMAPIVIEWCONTEXT pviewctx,
                               ULONG hwndParent, LPCRECT lprcPosRect)
{
    HRESULT             hr;

    //
    //  If a view context was passed in, then we need to get the
    //  status bits from this view context.  Also we are going to save
    //  the current view context and use this view context for the
    //  duration of the verb execution.
    //

    if (pviewctx != NULL)
    {
        m_pviewctxOverride = pviewctx;
        pviewctx->GetViewStatus(&m_ulViewStatus);
    }

    //
    //   Execute the requested verb.  If we do not understand the verb
    //  or we do not support the verb then we return NO SUPPORT and let
    //  the viewer deal with this.
    //

    switch (iVerb)
    {

    case EXCHIVERB_OPEN:
        hr = HrOpenForm((HWND) hwndParent, lprcPosRect, m_ulViewStatus);
        break;

    case EXCHIVERB_REPLYTOSENDER:
        hr = HrReply(eREPLY, (HWND) hwndParent, lprcPosRect);
        if(HR_SUCCEEDED(hr))
        {
            m_pviewctxOverride = NULL;
            ShutdownForm(SAVEOPTS_NOSAVE);
        }
        break;

    case EXCHIVERB_FORWARD:
        hr = HrReply(eFORWARD, (HWND) hwndParent, lprcPosRect);
        if(HR_SUCCEEDED(hr))
        {
            m_pviewctxOverride = NULL;
            ShutdownForm(SAVEOPTS_NOSAVE);
        }
        break;

    case EXCHIVERB_PRINT:
    case EXCHIVERB_REPLYTOALL:
    case EXCHIVERB_SAVEAS:
    case EXCHIVERB_REPLYTOFOLDER:
        //the viewer should not call us here
        //(see Value in extensions section of smpfrm.cfg)
        Assert(FALSE);

    default:
        hr = m_lsterr.HrSetLastError(ResultFromScode(MAPI_E_NO_SUPPORT));
        break;
    }

    //
    //  If we moved to a different view context, then switch back to
    //  the one we started with.
    //

    m_pviewctxOverride = NULL;

    if(m_pviewctx != NULL)
    {
        m_ulViewStatus =0;
        m_pviewctx->GetViewStatus(&m_ulViewStatus);
        ConfigWinMenu();
    }

    return hr;
}

////    IMAPIForm::Advise
//

STDMETHODIMP CBaseForm::Advise (IN IMAPIViewAdviseSink * pViewAdvise,
                                OUT ULONG FAR * pulConnection)
{
    HRESULT     hr;

    hr = m_viewnotify.Advise (pViewAdvise, pulConnection);
    if (FAILED(hr))
    {
        hr = m_lsterr.HrSetLastError(hr);
    }
    return hr;
}

////    IMAPIForm::Unadvise
//

STDMETHODIMP CBaseForm::Unadvise(ULONG ulConnection)
{
    HRESULT     hr;

    hr = m_viewnotify.Unadvise(ulConnection);
    if (FAILED(hr))
    {
        hr = m_lsterr.HrSetLastError(hr);
    }

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
//  IPersistMessage interface
//
///////////////////////////////////////////////////////////////////////////////

////    IPersistMessage::GetClassID

STDMETHODIMP CBaseForm::GetClassID(LPCLSID lpClassID)
{
    *lpClassID = CLSID_IPM_NOTE_SAMPLE;
    return hrSuccess;
}

////  IPersistMessage::GetLastError
//
//  Description:  This routine is used to get back a string giving more
//              information about the last error in the form.
//

STDMETHODIMP CBaseForm::GetLastError(HRESULT hr, ULONG ulFlags,
                                     LPMAPIERROR FAR * lppMAPIError)
{
    return m_lsterr.HrGetLastError(hr, ulFlags, lppMAPIError);
}

////    IPersistMessage::IsDirty
//

STDMETHODIMP CBaseForm::IsDirty ()
{

    if(m_fDirty)
        return ResultFromScode(S_OK);

    if(NULL == m_hwndDialog)
    {
        m_fDirty = FALSE;
    }
    else if(m_eFormType == eformRead)
    {
        m_fDirty = (m_fRecipientsDirty ||
                    Edit_GetModify(GetDlgItem(m_hwndDialog, ID_BODY)));
    }
    else
    {
        m_fDirty = (m_fRecipientsDirty ||
                    Edit_GetModify(GetDlgItem(m_hwndDialog, ID_BODY)) ||
                    Edit_GetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT)) ||
                    Edit_GetModify(GetDlgItem(m_hwndDialog, ID_TO)) ||
                    Edit_GetModify(GetDlgItem(m_hwndDialog, ID_CC)));
    }

    return ResultFromScode ((m_fDirty ? S_OK : S_FALSE));
}

////  IPersistMessage::InitNew
//
//  Description: This function is called in the case of composing a new
//      message.  There is a small set of properties which are set by
//      the constructor of the message, however in general it can be
//      assumed the message is clean.
//

STDMETHODIMP CBaseForm::InitNew(LPMAPIMESSAGESITE pmsgsite, LPMESSAGE pmsg)
{
    //
    //  Ensure we are in a state where we can accept this call
    //

    switch(m_state)
    {
    case stateUninit:
    case stateHandsOffFromSave:
    case stateHandsOffFromNormal:
        break;

    default:
        return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
    }

    //
    //  If we currently have a message site, then release it as we
    //  will no longer be using it.
    //

    UlRelease(m_pmsgsite);
    m_pmsgsite = NULL;

    //
    //  Save away the pointers to the message and message site
    //

    m_pmsgsite = pmsgsite;
    pmsgsite->AddRef();

    m_ulSiteStatus = 0;
    m_pmsgsite->GetSiteStatus(&m_ulSiteStatus);

    m_pmsg = pmsg;
    pmsg->AddRef();

    //
    //  Make an assumption on the message flags and status
    //

    m_ulMsgStatus = 0;
    m_ulMsgFlags = MSGFLAG_UNSENT;

    if(m_hwnd)
        DisplayMessage();

    //
    //  We succeeded in doing the InitNew so move to the normal state
    //

    m_state = stateNormal;

    //
    //  Tell everybody who cares that we just loaded a new message
    //

    m_viewnotify.OnNewMessage();
    return hrSuccess;

}

//// IPersistMessage::Load
//
//  Description:  This routine is called as part of loading an existing
//      message into the form.
//

STDMETHODIMP CBaseForm::Load(LPMAPIMESSAGESITE pmsgsite, LPMESSAGE pmsg,
                             ULONG ulMsgStatus, ULONG ulMsgFlags)
{
    //
    //  Ensure we are in a state where we can accept this call
    //

    switch(m_state)
    {
    case stateUninit:
    case stateHandsOffFromSave:
    case stateHandsOffFromNormal:
        break;

    default:
        return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
    }

    //
    //  If we currently have a message site, then release it as we
    //  will no longer be using it.
    //

    UlRelease(m_pmsgsite);
    m_pmsgsite = NULL;

    UlRelease(m_pmsg);
    m_pmsg = NULL;

    HRESULT hr = HrGetMsgDataFromMsg(pmsg, ulMsgFlags);
    if(HR_FAILED(hr))
        goto err;

    //
    //  Save away the message and message site which are passed in.
    //

    m_pmsg = pmsg;
    pmsg->AddRef();

    m_pmsgsite = pmsgsite;
    pmsgsite->AddRef();

    //
    //  Get the site status flags for disabling buttons & menus
    //
    m_ulSiteStatus = 0;
    m_pmsgsite->GetSiteStatus(&m_ulSiteStatus);

    //
    //  Save away these properties
    //

    m_ulMsgStatus = ulMsgStatus;
    m_ulMsgFlags = ulMsgFlags;

    //
    //  Put us into the normal state
    //

    m_state = stateNormal;

    //
    //  if our form is up, display the message
    //
    if(m_hwnd)
        DisplayMessage();

    //
    //  Tell everybody who cares that we just loaded a new message
    //

    m_viewnotify.OnNewMessage();
    return hrSuccess;

err:
    return hr;
}

////    IPersistMessage::Save
//
//  Description:
//      This function will be called whenever a save operation of the
//      information into the form should be done.  We should only make
//      modifications to the message in this function.
//

STDMETHODIMP CBaseForm::Save(IN LPMESSAGE pmsg, IN ULONG fSameAsLoad)
{
    HRESULT             hr;

    //
    //  Check that we are in a state where we are willing to accept
    //  this call.  Must have a message.
    //

    switch( m_state )
    {
    default:
        Assert(FALSE);
    case stateDead:
    case stateUninit:
    case stateNoScribble:
    case stateHandsOffFromSave:
    case stateHandsOffFromNormal:
        return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));

    case stateNormal:
        break;
    }

    if (fSameAsLoad)
    {
        //
        //  Its the same message interface as was loaded into us.  We can
        //      assume that the pmsg passed in is either NULL or an interface
        //      on the same object as the message we already have loaded
        //

        hr = HrSaveInto(m_pmsg);
    }
    else
    {
        //
        //  We need to copy everything into the new message as we are going
        //      to clone ourselves into it.
        //

        hr = m_pmsg->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, pmsg, 0, NULL);
        if (FAILED(hr))
        {
            m_lsterr.HrSetLastError(hr, m_pmsg);
            return hr;
        }

        //
        //  Now make all of the incremental changes
        //

        hr = HrSaveInto(pmsg);
    }

    if (FAILED(hr))
    {
        return hr;
    }

    m_state = stateNoScribble;
    m_fSameAsLoaded = fSameAsLoad;

    return S_OK;
}

////    IPersistMessage::SaveCompleted
//
//

STDMETHODIMP CBaseForm::SaveCompleted(IN LPMESSAGE pmsg)
{

    switch( m_state )
    {
    case stateHandsOffFromNormal:
    case stateHandsOffFromSave:
    case stateNoScribble:
        break;

    default:
        return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
    }

    if((stateHandsOffFromNormal == m_state ||
        stateHandsOffFromSave == m_state)  && NULL == pmsg)
    {
        DebugTrace("smpfrm: SaveCompleted called in handsOff state with pmsg==NULL\r\n");
        return  m_lsterr.HrSetLastError(ResultFromScode(E_INVALIDARG));
    }

    ULONG ulOldState = m_state;
    m_state = stateNormal;

    //state == NoScribble , pmsg == NULL
    if(NULL == pmsg)
    {
        if(m_fSameAsLoaded)
        {
            ClearDirty();
            m_viewnotify.OnSaved();
        }

        return hrSuccess;
    }

    //state == handsOffFromNormal, pmsg != NULL
    if(stateHandsOffFromNormal == ulOldState)
    {
        UlRelease(m_pmsg);

        m_pmsg = pmsg;
        pmsg->AddRef();

        return hrSuccess;
    }

    //state == handsOffFromSave || NoScribble, pmsg != NULL
    if(stateNoScribble == ulOldState)
    {
        UlRelease(m_pmsg);
        m_pmsg = pmsg;
        pmsg->AddRef();
    }

    m_viewnotify.OnSaved();
    ClearDirty();

   return hrSuccess;
}

////  IPersistMessage::HandsOffMessage
//
//  Description: store, folder and message objects has to be released
//              in this method.
//
//

STDMETHODIMP CBaseForm::HandsOffMessage ()
{

    switch( m_state )
    {
    case stateNormal:
    case stateNoScribble:
        break;

    default:
        return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
    }

    if(stateNormal == m_state)
        m_state = stateHandsOffFromNormal;
    else
        m_state = stateHandsOffFromSave;

    //
    //  We must have a message
    //

    Assert(m_pmsg != NULL);
    m_pmsg->Release();
    m_pmsg = NULL;

    return hrSuccess;
}

///////////////////////////////////////////////////////////////////////////////
//
//  IMAPIFormAdviseSink interfaces
//
///////////////////////////////////////////////////////////////////////////////

////    IMAPIFormAdviseSink::OnChange
//
//  Description: called to notify about changes in viewctx status

STDMETHODIMP CBaseForm::OnChange(ULONG ulflag)
{
    if(m_pviewctxOverride == NULL)
    {
        m_ulViewStatus = ulflag;
        ConfigWinMenu();
    }

    return hrSuccess;
}

////    CBaseForm::OnActivateNext
//
//  Description:  We only say that we will handle the next message if
//              it is the exact same message class as the current message.
//              If the next message has the same "unsentness" will reuse the
//              current object, otherwise ask our ClassFactory for a new one.
//

STDMETHODIMP CBaseForm::OnActivateNext(LPCSTR lpszMessageClass, ULONG ulMessageStatus,
                                        ULONG ulMessageFlags,
                                       LPPERSISTMESSAGE FAR * ppPersistMessage)
{
    HRESULT hr;

    *ppPersistMessage = NULL;

    Assert(m_pval);

    if(PR_MESSAGE_CLASS == m_pval[irtClass].ulPropTag)
    {
        //the message class comparison has to be case insensitive
        if((lstrcmpi(m_pval[irtClass].Value.LPSZ, lpszMessageClass) != 0) &&
            lstrcmpi(FormClassName, lpszMessageClass) != 0)
        {
            return ResultFromScode(S_FALSE);
        }
    }
    else
    {
        if(lstrcmpi(FormClassName, lpszMessageClass) != 0)
        {
            return ResultFromScode(S_FALSE);
        }
    }

    if((m_ulMsgFlags & MSGFLAG_UNSENT) == (ulMessageFlags & MSGFLAG_UNSENT))
        //tell the viewer to reuse our object
        return ResultFromScode(S_OK);

    //Get a new object from our class factory
    hr = m_pClassFactory->CreateInstance(NULL, IID_IPersistMessage, (LPVOID FAR *)ppPersistMessage);
    if(hr)
        return ResultFromScode (S_FALSE);
    else
        return ResultFromScode(S_OK);
}

///////////////////////////////////////////////////////////////////////////////
//
//  Non-IMAPIinterface functions
//
///////////////////////////////////////////////////////////////////////////////

///     CBaseForm::HrGetMsgDataFromMsg
//
//      fills in m_pval and m_padrlist (for unsent msgs only)
//      with the info from pmsg
HRESULT CBaseForm::HrGetMsgDataFromMsg(LPMESSAGE pmsg, ULONG ulMsgFlags)
{
    Assert(pmsg);

    FreePadrlist(m_padrlist);
    m_padrlist = NULL;

    ULONG   cValues = 0;
    MAPIFreeBuffer(m_pval);
    m_pval = NULL;

    MAPIFreeBuffer(m_lpbConvIdx);
    m_lpbConvIdx = NULL;

    HRESULT hr = pmsg->GetProps((LPSPropTagArray) &tagaRead, 0,
                                    &cValues, &m_pval);
    if (HR_FAILED(hr))
    {
        m_lsterr.HrSetLastError(hr, pmsg);
        goto err;
    }

    if(PROP_TYPE(m_pval[irtBody].ulPropTag) == PT_ERROR  &&
       GetScode(m_pval[irtBody].Value.l) == MAPI_E_NOT_ENOUGH_MEMORY)
    {
        hr = HrStreamInMsgBody(pmsg, m_pval, &m_pval[irtBody].Value.LPSZ, &m_lsterr);
        if(hr)
        {
            goto err;
        }
        else
        {
            m_pval[irtBody].ulPropTag = PR_BODY;
        }
    }

    Assert(cValues ==  cpropMsg);

    if(PR_CONVERSATION_INDEX == m_pval[irtConvIdx].ulPropTag)
    {
        LPSPropValue pval = &m_pval[irtConvIdx];

        m_cbConvIdx = pval->Value.bin.cb;
        if(MAPIAllocateBuffer(m_cbConvIdx, (LPVOID *)&m_lpbConvIdx))
        {
            m_lpbConvIdx = NULL;
            m_cbConvIdx = 0;
        }
        else
        {
            CopyMemory(m_lpbConvIdx, pval->Value.bin.lpb, m_cbConvIdx);
        }
    }
    else
    {
        m_lpbConvIdx = NULL;
        m_cbConvIdx = 0;
    }

    m_fConvTopicSet = (PR_CONVERSATION_TOPIC == m_pval[irtConvTopic].ulPropTag);

    if(ulMsgFlags & MSGFLAG_UNSENT)
    {
        hr = GetMsgAdrlist (pmsg, &m_padrlist, &m_lsterr);
        if(HR_FAILED(hr))
        {
            goto err;
        }
    }

    return hrSuccess;

err:
    MAPIFreeBuffer(m_pval);
    m_pval = NULL;

    FreePadrlist(m_padrlist);
    m_padrlist = NULL;

    return hr;

}

///         CBaseForm::ClearDirty
//
//      Clears dirty state
void CBaseForm::ClearDirty(void)
{
    m_fDirty = FALSE;
    m_fRecipientsDirty = FALSE;

    if(m_eFormType == eformSend)
    {
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_TO), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_CC), FALSE);
    }
    else
    {
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
    }
}

////    CBaseForm::Address
//
//  Description:
//      This function is used to address the form.
//      The parameter determines which button in the address
//      dialog has the focus.
//

void CBaseForm::Address(int id)
{
    Assert( ID_TO_BUTTON == id || ID_CC_BUTTON == id);

    HRESULT hr;

    if (m_pses == NULL)
    {
        hr = m_pmsgsite->GetSession(&m_pses);
        if(hr)
        {
            m_lsterr.HrSetLastError(hr, m_pmsgsite);
            ShowError();
            return;
        }
    }

    Assert(m_pses != NULL);

    if (m_pab == NULL)
    {
        hr = m_pses->OpenAddressBook((ULONG) m_hwnd, NULL, 0, &m_pab);
        if(hr)
        {
            m_lsterr.HrSetLastError(hr, m_pses);
            ShowError();
        }
        if(HR_FAILED(hr)) //if it's a real error (not a warning)
            return;
    }

    Assert(m_pab != NULL);

    ADRPARM adrparm = { 0, NULL, AB_RESOLVE | DIALOG_MODAL, NULL, 0L,
                        NULL, NULL, NULL, NULL, "Address Book", NULL,
                        "Send Note To", 2, (id == ID_TO_BUTTON ? 0:1),
                        NULL, NULL, NULL, NULL };
    ULONG   ulHwndAddr = (ULONG) m_hwnd;

    hr = m_pab->Address(&ulHwndAddr, &adrparm, &m_padrlist);
    if (!hr)
    {
        m_fRecipientsDirty = TRUE;
        UpdateRecipientsDisplay();
    }
    else if(GetScode(hr) != MAPI_E_USER_CANCEL)
    {
        m_lsterr.HrSetLastError(hr, m_pab);
        ShowError();
    }

}

////    CBaseForm::ClearWindow
//
//  Description:
//      This routine is called when the window for this form object is
//      destroyed.  We clear out pointer to the window and the windows
//      pointer to us.
//

void CBaseForm::ClearWindow()
{
    Assert(m_hwnd != NULL);

    //
    //  Clear the back pointer to us and remove the reference count for it.
    //

    SetWindowLong(m_hwnd, 0, 0);
    Release();

    //
    //  Clear out pointer to the window since it is now dead
    //

    m_hwnd = NULL;
    m_hwndDialog = NULL;
}

////  CBaseForm::HrOpenForm
//
//  Description:  This is the internal routine which is called from the
//      open/display verb.  It will cause UI to appear if there is none
//      and force the window to the foreground if there is already UI.
//

HRESULT CBaseForm::HrOpenForm(HWND hwndParent, LPCRECT lprcPosRect,

ULONG ulViewFlags)
{

    if (lprcPosRect == NULL)
        return m_lsterr.HrSetLastError(ResultFromScode(E_INVALIDARG));

    //
    //  If any modal forms are visible then do not do anything
    //

    Assert( g_FModalUp && g_hwndUp || !g_FModalUp && !g_hwndUp);
    if (g_FMBoxUp || (g_FModalUp && hwndParent != g_hwndUp))
        return m_lsterr.HrSetLastError(
                               ResultFromScode(OLEOBJ_S_CANNOT_DOVERB_NOW));

    if (!(ulViewFlags & VCSTATUS_MODAL))
    {
        //  If we are not modal then don't do anything relative to the parent
        hwndParent = NULL;
    }

    //
    //  Check to see if we have a window up
    //

    if (m_hwnd != 0)
    {
        MoveWindow(m_hwnd, lprcPosRect->left, lprcPosRect->top,
                   lprcPosRect->right - lprcPosRect->left,
                   lprcPosRect->bottom - lprcPosRect->top,
                   TRUE);
        SetForegroundWindow(m_hwnd);

        return S_OK;
    }

    m_hwnd = CreateWindow((m_ulMsgFlags & MSGFLAG_UNSENT) ?
                          g_szSendWinClass : g_szReadWinClass,
                          g_szWindowCaption, WS_OVERLAPPEDWINDOW,
                          0, 0, 10, 10, hwndParent, NULL, g_hinst, NULL);

    if (m_hwnd == NULL)
    {
        return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
    }

    //
    //  Put the pointer to this object into the window
    //

    SetWindowLong(m_hwnd, 0, (long) (void FAR *) this);
    //we just created another pointer to ourself, so addref
    AddRef();

    //
    //  Create the dialog as a child of this window
    //

    if (m_ulMsgFlags & MSGFLAG_UNSENT)
    {
        m_eFormType = eformSend;
        m_hwndDialog = CreateDialog(g_hinst, MAKEINTRESOURCE(IDR_SEND_FORM),
                                    m_hwnd, &FormDlgProcSend);
        m_HAccelTable = LoadAccelerators(g_hinst, MAKEINTRESOURCE(IDR_SEND_FORM));
    }
    else
    {
        m_eFormType = eformRead;
        m_hwndDialog = CreateDialog(g_hinst, MAKEINTRESOURCE(IDR_READ_FORM),
                                    m_hwnd, &FormDlgProcRead);
        m_HAccelTable = LoadAccelerators(g_hinst, MAKEINTRESOURCE(IDR_READ_FORM));
    }

    DisplayMessage();

    //
    //  Position the window where it is suppose to be
    //

    MoveWindow(m_hwnd, lprcPosRect->left, lprcPosRect->top,
        lprcPosRect->right - lprcPosRect->left,
        lprcPosRect->bottom - lprcPosRect->top,
        TRUE);

    ShowWindow(m_hwnd, SW_SHOW);

    SetForegroundWindow(m_hwnd);

    //
    //  If we are modal, then we loop until the form is closed
    //

    if (ulViewFlags & VCSTATUS_MODAL)
    {
        MSG         msg;

        BOOL fOldModalUp = g_FModalUp;
        HWND hwndOldUp = g_hwndUp;

        g_FModalUp = TRUE;
        g_hwndUp = m_hwnd;

        while ((m_hwnd != NULL) && (GetMessage(&msg, m_hwnd, 0, 0)))
        {
            //first call our method and see if this message makes sense to us.
            //if not, let WIN API care about it.
            if (!TranslateMessage(msg))
            {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
        }

        g_FModalUp = fOldModalUp;
        g_hwndUp = hwndOldUp;
    }

    return hrSuccess;
}

////    CBaseForm::HrSaveInto
//
//  Description:
//    This routine gives one central location which save all modified
//      properties into a message.
//

HRESULT CBaseForm::HrSaveInto(LPMESSAGE pmsg)
{
    ULONG               cval = 0;
    HRESULT             hr;

    //
    //  First, write out the recipient table
    //

    if(m_padrlist && m_fRecipientsDirty)
    {
        hr = pmsg->ModifyRecipients(0, m_padrlist);
        if (FAILED(hr))
        {
            m_lsterr.HrSetLastError(hr, pmsg);
            return hr;
        }
    }
    //
    //  Next set up and save the rest of the info
    //

    HrGetMsgDataFromUI(m_hwndDialog);

    //PR_DISPLAY_TO AND PR_DISPLAY_CC can't be set
    ULONG ulToTag = m_pval[irtTo].ulPropTag;
    m_pval[irtTo].ulPropTag = PR_NULL;

    ULONG ulCcTag = m_pval[irtCc].ulPropTag;
    m_pval[irtCc].ulPropTag = PR_NULL;

    ULONG ulNormSubjectTag = m_pval[irtNormSubject].ulPropTag;
    m_pval[irtNormSubject].ulPropTag = PR_NULL;

    m_pval[irtClass].ulPropTag = PR_MESSAGE_CLASS;
    m_pval[irtClass].Value.lpszA = FormClassName;

    /*
     *If the message didn't have PR_CONVERSATION_TOPIC when we loaded it, we'll
     *  set it every time we save the message. Otherwise we don't touch it
     */
    if(!m_fConvTopicSet)
    {
        m_pval[irtConvTopic].ulPropTag = PR_CONVERSATION_TOPIC;
        if(PR_SUBJECT == m_pval[irtSubject].ulPropTag)
        {
            m_pval[irtConvTopic].Value.LPSZ = m_pval[irtSubject].Value.LPSZ;
        }
        else
        {
            m_pval[irtConvTopic].Value.LPSZ = "";
        }
    }
    else
    {
        m_pval[irtConvTopic].ulPropTag = PR_NULL;
    }

    /*
     * if the message doesn't have a PR_CONVERSATION_INDEX, create and set it
     *
     */
    if(m_cbConvIdx == 0)
    {
        if(!ScAddConversationIndex(0, NULL, &m_cbConvIdx,   &m_lpbConvIdx))
        {
            m_pval[irtConvIdx].ulPropTag = PR_CONVERSATION_INDEX;
            m_pval[irtConvIdx].Value.bin.lpb = m_lpbConvIdx;
            m_pval[irtConvIdx].Value.bin.cb = m_cbConvIdx;
        }
        else
        {
            m_pval[irtConvIdx].ulPropTag = PR_NULL;
        }
    }
    else
    {
        m_pval[irtConvIdx].ulPropTag = PR_NULL;
    }

    LPSPropProblemArray pProblems = NULL;

    hr = pmsg->SetProps(cpropMsg, m_pval, &pProblems);
    if (hr)
    {
        m_lsterr.HrSetLastError(hr, pmsg);
        goto err;
    }

    if(pProblems)
    {
        for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
        {
            if(PR_BODY == pProblems->aProblem[ind].ulPropTag &&
               MAPI_E_NOT_ENOUGH_MEMORY == pProblems->aProblem[ind].scode)
            {
                hr = HrStreamOutMsgBody(pmsg, m_pval[irtBody].Value.LPSZ, &m_lsterr);
                if(hr)
                {
                    MAPIFreeBuffer(pProblems);
                    pProblems = NULL;
                    goto err;
                }
                break;
            }
        }

        MAPIFreeBuffer(pProblems);
        pProblems = NULL;
    }

err:
    m_pval[irtTo].ulPropTag = ulToTag;
    m_pval[irtCc].ulPropTag = ulCcTag;
    m_pval[irtNormSubject].ulPropTag = ulNormSubjectTag;

    return hr;
}

BOOL CBaseForm::TranslateMessage(MSG& msg)
{
    //
    //  We translate accelerators before the dialog message so that we
    //  can get our accelerators to override the dialog's.
    //

    if(msg.hwnd == m_hwnd || IsChild(m_hwnd, msg.hwnd))
    {
        if (::TranslateAccelerator(m_hwnd, m_HAccelTable, &msg))
        {
            return TRUE;
        }
    }

    if ((m_hwndDialog != NULL) && ::IsDialogMessage(m_hwndDialog, &msg))
    {
        return TRUE;
    }

    return FALSE;
}

///  CBaseForm::UpdateRecipientsDisplay
//  go through m_padrlist and display TO and CC recipients in the
//  edit boxes.
// Called only for send form. The Read form can use PR_DISPLAY_*
void CBaseForm::UpdateRecipientsDisplay(void)
{
    Assert(m_hwndDialog);

    Assert(m_eFormType == eformSend);

    HWND hwTo = GetDlgItem(m_hwndDialog, ID_TO);
    HWND hwCC = GetDlgItem(m_hwndDialog, ID_CC);

    Edit_SetText(hwTo, "");
    Edit_SetText(hwCC, "");

    if(m_padrlist == NULL || m_padrlist->cEntries == 0) return;

    #define ADRTEXTLEN  512
    char szTo [ADRTEXTLEN] = "";
    char szCC [ADRTEXTLEN] = "";

    BOOL fToFull = FALSE;
    BOOL fCCFull = FALSE;

    LPADRENTRY pae;
    for(pae = m_padrlist->aEntries;
        pae < m_padrlist->aEntries + m_padrlist->cEntries;
        ++pae)
    {
        if( NULL == pae->rgPropVals) continue;

        LPSPropValue lpsprop;
        LPSTR szDisplay = NULL;
        LPSTR szName = NULL;
        BOOL * pfFull = NULL;
        for(lpsprop = pae->rgPropVals;
            lpsprop < pae->rgPropVals + pae->cValues;
            ++lpsprop)
        {
            if(lpsprop->ulPropTag == PR_RECIPIENT_TYPE)
            {
                if(lpsprop->Value.l == MAPI_TO)
                {
                    szDisplay = szTo;
                    pfFull = &fToFull;
                }
                else if(lpsprop->Value.l == MAPI_CC)
                {
                    szDisplay = szCC;
                    pfFull = &fCCFull;
                }
            }
            else if(lpsprop->ulPropTag == PR_DISPLAY_NAME)
            {
                szName = lpsprop->Value.LPSZ;
            }

            if(NULL != szName && NULL != szDisplay)
            {
                Assert(pfFull);

                if(*pfFull) break;

                if(lstrlen(szDisplay) + lstrlen(szName) + 7 < ADRTEXTLEN)
                {
                    lstrcat(szDisplay, szName);
                    lstrcat(szDisplay, "; ");
                }
                else
                {
                    lstrcat(szDisplay, "...");
                    *pfFull = TRUE;
                }

                break;
            }
        }
    }

    //get rid of the "; " after the last recipient
    if(lstrlen(szTo) > 0  &&  !fToFull)
        szTo[lstrlen(szTo)-2] = 0;
    if(lstrlen(szCC) > 0  &&  !fCCFull)
        szCC[lstrlen(szCC)-2] = 0;

    Edit_SetText(hwTo, szTo);
    Edit_SetText(hwCC, szCC);

}

///  CBaseForm::DisplayMessage
//
//  display the info from m_pval in the dialog
void CBaseForm::DisplayMessage(void)
{
    char    sz[256];

    Assert(m_hwnd);
    Assert(m_hwndDialog);

    if(NULL == m_pval) return;

    if (m_pval[irtSubject].ulPropTag == PR_SUBJECT)
    {
        SetDlgItemText(m_hwndDialog, ID_SUBJECT, m_pval[irtSubject].Value.LPSZ);
        lstrcpyn(sz, m_pval[irtSubject].Value.LPSZ, 200);
        sz[200] = 0;
        lstrcat(sz, " - ");
        lstrcat(sz, g_szWindowCaption);
        SetWindowText(m_hwnd, sz);
    }
    else
    {
        SetWindowText(m_hwnd, g_szWindowCaption);
    }

    if (m_pval[irtBody].ulPropTag == PR_BODY)
        SetDlgItemText(m_hwndDialog, ID_BODY, m_pval[irtBody].Value.LPSZ);

    if(m_eFormType == eformRead)
    {
        if (m_pval[irtFrom].ulPropTag == PR_SENDER_NAME)
        SetDlgItemText(m_hwndDialog, ID_FROM, m_pval[irtFrom].Value.LPSZ);

        if (m_pval[irtTime].ulPropTag == PR_CLIENT_SUBMIT_TIME) {
            FormatTime(&m_pval[irtTime].Value.ft, sz);
            SetDlgItemText(m_hwndDialog, ID_SENT, sz);
        }

        if (m_pval[irtTo].ulPropTag == PR_DISPLAY_TO)
            SetDlgItemText(m_hwndDialog, ID_TO, m_pval[irtTo].Value.LPSZ);

        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
    }
    else if (m_eFormType == eformSend)
    {
        UpdateRecipientsDisplay();

        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_TO), FALSE);
        Edit_SetModify(GetDlgItem(m_hwndDialog, ID_CC), FALSE);
    }
    else
    {
        Assert(FALSE);
    }

}

/// CBaseForm::HrGetMsgDataFromUI
//
// save the  message info from the dialog into the m_pval
HRESULT CBaseForm::HrGetMsgDataFromUI(HWND hDlg)
{

    LONG cb = 0;
    HRESULT hr = hrSuccess;

    //have to call IsDirty() to make sure  m_fDirty is current
    if(!m_fDirty)
        IsDirty();

    if(m_eFormType == eformRead)
    {
        Assert(m_pval);

        if(!m_fDirty)
            return hrSuccess;

        //everything is read-only except for the body
        cb = GetWindowTextLength(GetDlgItem(hDlg, ID_BODY));
        if(m_pval[irtBody].ulPropTag == PR_BODY &&
         cb <= lstrlen(m_pval[irtBody].Value.LPSZ))
        {
            GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
        }
        else
        {
            if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtBody].Value.LPSZ))
            {
                return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            }

            GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
        }

        m_pval[irtBody].ulPropTag = PR_BODY;
    }
    else if (m_eFormType == eformSend)
    {
        if(!m_fDirty && m_pval != NULL)
            return hrSuccess;

        if(NULL != m_pval)
        {
            MAPIFreeBuffer(m_pval);
            m_pval = NULL;
        }
        if(MAPIAllocateBuffer(sizeof(SPropValue)* cpropMsg, (LPVOID FAR *) &m_pval))
        {
            return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
        }

        ZeroMemory(m_pval, sizeof(SPropValue)* cpropMsg);

        m_pval[irtTime].ulPropTag = PR_NULL;
        m_pval[irtFrom].ulPropTag = PR_NULL;

        cb = GetWindowTextLength(GetDlgItem(hDlg, ID_SUBJECT)) + 1;
        /*if(cb > 0)
        {*/
            if(MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtSubject].Value.LPSZ))
            {
                return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            }

            cb = GetWindowText(GetDlgItem(hDlg, ID_SUBJECT), m_pval[irtSubject].Value.LPSZ, cb+1);
            m_pval[irtSubject].ulPropTag = PR_SUBJECT;
        /*}
        else
        { //no subject

            m_pval[irtSubject].ulPropTag = PR_NULL;
        } */

        cb = GetWindowTextLength(GetDlgItem(hDlg, ID_BODY)) +1 ;
        /*if(cb > 0)
        { */
            if(MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtBody].Value.LPSZ))
            {
                return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            }

            cb = GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
            m_pval[irtBody].ulPropTag = PR_BODY;
        /*}
        else
        { //no body

            m_pval[irtBody].ulPropTag = PR_NULL;
        } */

        cb = GetWindowTextLength(GetDlgItem(hDlg, ID_TO));
        if(cb > 0)
        {
            if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtTo].Value.LPSZ))
            {
                return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            }

            GetWindowText(GetDlgItem(hDlg, ID_TO), m_pval[irtTo].Value.LPSZ, cb+1);
            m_pval[irtTo].ulPropTag = PR_DISPLAY_TO;
        }
        else
        { //no to

            m_pval[irtTo].ulPropTag = PR_NULL;
        }

        cb = GetWindowTextLength(GetDlgItem(hDlg, ID_CC));
        if(cb > 0)
        {
            if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtCc].Value.LPSZ))
            {
                return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            }

            GetWindowText(GetDlgItem(hDlg, ID_CC), m_pval[irtCc].Value.LPSZ, cb+1);
            m_pval[irtTo].ulPropTag = PR_DISPLAY_CC;
        }
        else
        { //no cc

            m_pval[irtCc].ulPropTag = PR_NULL;
        }
    }
    else
    {
        Assert(FALSE);
    }
    return hr;
}

/// CBaseForm::IsAddressed
//
//Does m_padrlist contain a recipient?
BOOL CBaseForm::IsAddressed(void)
{
    Assert(m_eFormType == eformSend) ;

    if(NULL == m_padrlist || m_padrlist->cEntries == 0)
        return FALSE;

    for(LPADRENTRY pae = m_padrlist->aEntries;
        pae < m_padrlist->aEntries + m_padrlist->cEntries; ++pae)
    {
        if(pae->rgPropVals)
            return TRUE;
    }

    return FALSE;
}

///     CBaseForm::ConfigMenu
//Enable/disable menu commands based on the values of m_ulSiteStatus
// and m_ulViewStatus
void CBaseForm::ConfigMenu(HMENU hMenu)
{
    if(m_eFormType == eformRead)
    {
        EnableMenuItem(hMenu, IDC_MESSAGE_SAVE,
            MF_BYCOMMAND|((m_ulSiteStatus & VCSTATUS_SAVE)? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_DELETE,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_DELETE) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_COPY,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_COPY) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_MOVE,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_MOVE) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_VIEW_ITEMABOVE,
            MF_BYCOMMAND|(m_ulViewStatus & VCSTATUS_PREV ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_VIEW_ITEMBELOW,
            MF_BYCOMMAND|(m_ulViewStatus & VCSTATUS_NEXT ? MF_ENABLED:MF_GRAYED));
    }

    else
    {
        EnableMenuItem(hMenu, IDC_MESSAGE_SUBMIT,
            MF_BYCOMMAND|((m_ulSiteStatus &VCSTATUS_SUBMIT) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_SAVE,
            MF_BYCOMMAND|((m_ulSiteStatus & VCSTATUS_SAVE)? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_DELETE,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_DELETE) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_COPY,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_COPY) ? MF_ENABLED:MF_GRAYED));
        EnableMenuItem(hMenu, IDC_MESSAGE_MOVE,
            MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
                            (m_ulSiteStatus & VCSTATUS_MOVE) ? MF_ENABLED:MF_GRAYED));

    }
}

///CBaseForm::HrReply
//
//
HRESULT CBaseForm::HrReply(eREPLYTYPE eReplyType, HWND hwndParent, LPCRECT prect)
{

    //reply all is not implemented
    Assert(eREPLY == eReplyType || eFORWARD == eReplyType);

    HRESULT hr;
    LONG cb;

    char * szBody = NULL;
    char * szSubject = NULL;
    SPropValue val[3] = {0};

    enum { eName, eAddrType, eEID, eRecipType, eDim};
    SizedSPropTagArray(eDim, sptSender) =
        {eDim, {PR_SENDER_NAME, PR_SENDER_ADDRTYPE,
                PR_SENDER_ENTRYID, PR_NULL}};

    LPSPropProblemArray pProblems = NULL;
    LPMAPIFORM          pfrmReply = NULL;
    LPPERSISTMESSAGE    ppermsg = NULL;
    LPMAPIMESSAGESITE   pmsgsite = NULL;
    LPMAPIVIEWCONTEXT   pviewctx = NULL;
    LPMESSAGE           pmsg = NULL;
    ULONG cbNewConvIdx = 0;
    LPBYTE lpbNewConvIdx = NULL;

    Assert(m_pmsg);

    hr = m_pClassFactory->CreateInstance(NULL, IID_IMAPIForm, (LPVOID FAR *) &pfrmReply);
    if(hr)
    {
        m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
        goto err;
    }

    hr = pfrmReply->QueryInterface(IID_IPersistMessage, (LPVOID *) &ppermsg);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, pfrmReply);
        goto err;
    }

    hr = m_pmsgsite->NewMessage(FALSE, NULL, ppermsg, &pmsg, &pmsgsite, &pviewctx);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);
        goto err;
    }

    hr = m_pmsg->CopyTo(0, NULL, (LPSPropTagArray)&sptExcludedProps,
                        0, NULL, &IID_IMessage, pmsg, 0, &pProblems);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, m_pmsg);
        goto err;
    }

    if(pProblems)
    {
        DebugTraceProblems("SmplForm: CopyTo returned ...", pProblems);
        //  if any of the errors is other than MAPI_E_COMPUTED, fail
        for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
        {
            if(MAPI_E_COMPUTED != pProblems->aProblem[ind].scode)
            {
                hr = m_lsterr.HrSetLastError(
                            ResultFromScode(pProblems->aProblem[ind].scode));
                MAPIFreeBuffer(pProblems);
                pProblems = NULL;
                goto err;

            }
        }
        MAPIFreeBuffer(pProblems);
        pProblems = NULL;
    }

    if(m_pval && m_pval[irtNormSubject].ulPropTag == PR_NORMALIZED_SUBJECT)
        cb = lstrlen(m_pval[irtNormSubject].Value.LPSZ);
    else
        cb = 0;

    if(hr = MAPIAllocateBuffer(cb+lstrlen(szRE_PREFIX)+1, (LPVOID FAR *) &szSubject))
    {
        m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
        goto err;
    }

    *szSubject = '\0';

    if(eREPLY == eReplyType)
    {
        lstrcat(szSubject, szRE_PREFIX);
    }
    else
    {
        lstrcat(szSubject, szFW_PREFIX);
    }

    if(cb > 0)
    {
        lstrcat(szSubject, m_pval[irtNormSubject].Value.LPSZ);
    }

    val[1].Value.lpszA = szSubject;
    val[1].ulPropTag = PR_SUBJECT;

    HrSaveToString(&szBody);
    if(szBody)
    {
        val[0].Value.LPSZ = szBody;
        val[0].ulPropTag = PR_BODY;
    }
    else
        val[0].ulPropTag = PR_NULL;

    /*
     * Create a conversation index for the reply msg based on that of ours
     *
     */
    if(!ScAddConversationIndex(m_cbConvIdx, m_lpbConvIdx,
                                &cbNewConvIdx, &lpbNewConvIdx))
    {
        val[2].ulPropTag = PR_CONVERSATION_INDEX;
        val[2].Value.bin.cb = cbNewConvIdx;
        val[2].Value.bin.lpb = lpbNewConvIdx;
    }
    else
    {
        val[2].ulPropTag = PR_NULL;
    }

    hr = pmsg->SetProps(3, val, &pProblems);
    MAPIFreeBuffer(lpbNewConvIdx);
    lpbNewConvIdx = NULL;

    MAPIFreeBuffer(szSubject);
    szSubject = NULL;

    if(!hr)
    {
        if(pProblems)
        {
            for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
            {
                if(PR_BODY == pProblems->aProblem[ind].ulPropTag &&
                   MAPI_E_NOT_ENOUGH_MEMORY == pProblems->aProblem[ind].scode)
                {
                    hr = HrStreamOutMsgBody(pmsg, szBody, &m_lsterr);
                    if(hr)
                    {
                        MAPIFreeBuffer(pProblems);
                        pProblems = NULL;
                        goto err;
                    }
                    break;
                }
            }
            MAPIFreeBuffer(pProblems);
            pProblems = NULL;
        }
    }
    else
    {
        m_lsterr.HrSetLastError(hr, m_pmsg);
        goto err;
    }

    // if it's a reply, set the addressee
    if(eREPLY == eReplyType)
    {
        LPADRLIST pal = NULL;
        ULONG cVal = 0;
        LPSPropValue pval = NULL;

        hr = MAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID FAR *)&pal);
        if(hr)
        {
            m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            goto err;
        }
        hr = m_pmsg->GetProps((LPSPropTagArray) &sptSender, 0, &cVal, &pval);
        if(hr) //treat warnings as an error, 'cause the props we ask for are required
        {
            m_lsterr.HrSetLastError(hr, m_pmsg);
            MAPIFreeBuffer(pal);
            goto err;
        }

        Assert(cVal == eDim);

        pval[eRecipType].ulPropTag = PR_RECIPIENT_TYPE;
        pval[eRecipType].Value.l = MAPI_TO;

        Assert(pval[eName].ulPropTag == PR_SENDER_NAME);
        pval[eName].ulPropTag = PR_DISPLAY_NAME;

        Assert(pval[eAddrType].ulPropTag == PR_SENDER_ADDRTYPE);
        pval[eAddrType].ulPropTag = PR_ADDRTYPE;

        Assert(pval[eEID].ulPropTag == PR_SENDER_ENTRYID);
        pval[eEID].ulPropTag = PR_ENTRYID;

        pal->aEntries[0].rgPropVals = pval;

        pal->cEntries = 1;
        pal->aEntries[0].cValues = eDim;

        hr = pmsg->ModifyRecipients(0, pal);
        FreePadrlist(pal); //this will also free pval
        pal = NULL;
        pval = NULL;
        if(hr)
        {
            m_lsterr.HrSetLastError(hr, pmsg);
            goto err;
        }
    }

    hr = ppermsg->Load(pmsgsite, pmsg, 0, MSGFLAG_UNSENT );
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, ppermsg);
        goto err;
    }

    hr = pfrmReply->DoVerb(EXCHIVERB_OPEN, pviewctx, (ULONG)hwndParent, prect);
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, pfrmReply);
        pfrmReply->ShutdownForm(SAVEOPTS_NOSAVE);
        goto err;
    }

err:

    UlRelease(pfrmReply);
    UlRelease(ppermsg);
    UlRelease(pmsgsite);
    UlRelease(pviewctx);
    UlRelease(pmsg);
    delete [] szBody;

    return hr;
}

///     CBaseForm::HrSaveToString
//
//  The returned string has to be freed using delete
HRESULT CBaseForm::HrSaveToString(LPSTR * pszMessage)
{
    Assert(m_pval);
    Assert(pszMessage);

    ostrstream  ostrBody;
    char *szMsg = NULL;

    ostrBody << "\r\n-----------------------------";
    ostrBody << "\r\nFrom:\t";
    if(m_pval[irtFrom].ulPropTag == PR_SENDER_NAME)
    {
        ostrBody << m_pval[irtFrom].Value.LPSZ;
    }
    else
    {
        ostrBody << "Unknown";
    }

    if(m_pval[irtTime].ulPropTag == PR_CLIENT_SUBMIT_TIME)
    {
        char sz[30];
        FormatTime(&m_pval[irtTime].Value.ft, sz);
        ostrBody << "\r\nSent:\t";
        ostrBody << sz;
    }

    if(m_pval[irtTo].ulPropTag == PR_DISPLAY_TO)
    {
        ostrBody << "\r\nTo:\t";
        ostrBody <<  m_pval[irtTo].Value.LPSZ;
    }

    if(m_pval[irtSubject].ulPropTag == PR_SUBJECT)
    {
        ostrBody << "\r\nSubject:\t";
        ostrBody << m_pval[irtSubject].Value.LPSZ;
    }

    ostrBody << "\r\n";
    if(m_pval && m_pval[irtBody].ulPropTag == PR_BODY)
        ostrBody << m_pval[irtBody].Value.LPSZ;

    ostrBody << ends;

    szMsg = ostrBody.str();

    if(szMsg != NULL)
    {
        *pszMessage = szMsg;
        return hrSuccess;
    }
    else
    {
        *pszMessage = NULL;
        return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
    }
}

///     CBaseForm::HrQuerySave
//
//
HRESULT CBaseForm::HrQuerySave (DWORD ulSaveOptions)
{
    HRESULT             hr = hrSuccess;
    UINT                ui;

    //
    // Check to see if we are marked as dirty.  If we are clean then
    //  we can just return without doing any work
    //

    if(GetScode(IsDirty()) != S_OK)
        return hrSuccess;

    switch( ulSaveOptions )
    {
    default:
        Assert(FALSE);

    case SAVEOPTS_SAVEIFDIRTY:
        break;

    case SAVEOPTS_NOSAVE:
        return hrSuccess;

    case SAVEOPTS_PROMPTSAVE:
        ui = ShowMessageBox (m_hwnd, "Would you like to save changes?",
                           g_szFormName, MB_ICONEXCLAMATION | MB_YESNOCANCEL);
        switch (ui)
        {
        case IDYES:
            break;

        default:
            Assert(FALSE);
        case IDNO:
            return hrSuccess;

        case IDCANCEL:
            return MAPI_E_USER_CANCEL;

        }
    }

    hr = m_pmsgsite->SaveMessage();
    if(hr)
        m_lsterr.HrSetLastError(hr, m_pmsgsite);

    return hr;
}

///  CBaseForm::DoDelete
//
//  called only from our UI
void CBaseForm::DoDelete(void)
{
    HRESULT hr;
    RECT rect;

    GetWindowRect(m_hwnd, &rect);

    hr = m_pmsgsite->DeleteMessage(m_pviewctx, &rect);
    if(HR_FAILED(hr))
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);
        ShowError();
    }
    if(NULL == m_pmsg)
    {
        ShutdownForm(SAVEOPTS_NOSAVE);
    }
}

///  CBaseForm::DoSave
//
//  called only from our UI
void CBaseForm::DoSave(void)
{
    HRESULT hr;

    hr = m_pmsgsite->SaveMessage();
    if (FAILED(hr))
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);

ShowError();
    }
}

///  CBaseForm::DoSubmit
//
//  called only from our UI
void CBaseForm::DoSubmit(void)
{
    HRESULT hr;

    if(!IsAddressed())
    {
        ShowMessageBox(m_hwndDialog, "No recipients", g_szFormName, MB_OK);
        return;
    }

    hr = m_pmsgsite->SubmitMessage(0);
    if (FAILED(hr))
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);
        ShowError();
    }
    else
    {
        m_viewnotify.OnSubmitted();
    }

    if (m_pmsg == NULL)
        ShutdownForm(SAVEOPTS_NOSAVE);
}

///  CBaseForm::DoNext
//
//  called only from our UI
void CBaseForm::DoNext(ULONG ulDir)
{
    Assert(VCDIR_NEXT == ulDir || VCDIR_PREV == ulDir);

    HRESULT hr;

    hr = HrQuerySave(SAVEOPTS_PROMPTSAVE);
    if(hr)
    {
        if(hr != MAPI_E_USER_CANCEL)
            ShowError();
        return;
    }

    RECT rect;
    GetWindowRect(m_hwnd, &rect);

    hr = ViewCtx()->ActivateNext(ulDir, &rect);
    if(NULL == m_pmsg)
    {
        ShutdownForm(SAVEOPTS_NOSAVE);
    }
}

///  CBaseForm::DoReply
//
//  called only from our UI
void CBaseForm::DoReply(eREPLYTYPE eType)
{
    HRESULT hr;

    hr = HrQuerySave(SAVEOPTS_PROMPTSAVE);
    if(hr)
    {
        if(hr != MAPI_E_USER_CANCEL)
            ShowError();
        return;
    }

    RECT rect;
    GetWindowRect(m_hwnd, &rect);

    int iOffset = GetSystemMetrics(SM_CYCAPTION);
    OffsetRect(&rect, iOffset, iOffset);

    hr = HrReply(eType, m_hwnd, &rect);
    if(!hr)
    {
        ShutdownForm(SAVEOPTS_NOSAVE);
    }
    else
    {
        ShowError();
    }
}

///  CBaseForm::DoCopy
//
//  called only from our UI
void CBaseForm::DoCopy(void)
{
    HRESULT         hr;
    LPMAPIFOLDER    pfld = NULL;
    LPMDB           pmdb = NULL;

    if(!FGetFoldChooser())
    {
        ShowMessageBox(m_hwnd, "Can't copy", g_szFormName, MB_OK | MB_ICONSTOP);
        return;
    }

    Assert(m_lpfnHrPickFolder && m_hChsFldDll);

    if (m_pses == NULL)
    {
        hr = m_pmsgsite->GetSession(&m_pses);
        if(hr)
        {
            m_lsterr.HrSetLastError(hr, m_pmsgsite);
            ShowError();
            return;
        }
    }

    BOOL fOldModalUp = g_FModalUp;

    g_FModalUp = TRUE;

    hr = (*m_lpfnHrPickFolder)(g_hinst, m_hwnd, m_pses, &pfld, &pmdb,
                                &m_cbCFDState, &m_pbCFDState);

    g_FModalUp = fOldModalUp;

    if(hr)
    {
        if(GetScode(hr) != MAPI_E_USER_CANCEL)
            ShowMessageBox(m_hwnd, "Can't copy", g_szFormName, MB_OK | MB_ICONSTOP);

        return;
    }

    Assert(m_pmsgsite);
    Assert(pfld);
    Assert(pmdb);

    hr = m_pmsgsite->CopyMessage(pfld);
    pfld->Release();
    pmdb->Release();
    if(hr)
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);
        ShowError();
        return;
    }

}

///  CBaseForm::DoMove
//
//  called only from our UI
void CBaseForm::DoMove(void)
{
    HRESULT         hr;
    LPMAPIFOLDER    pfld = NULL;
    LPMDB           pmdb = NULL;

    if(!FGetFoldChooser())
    {
        ShowMessageBox(m_hwnd, "Can't move", g_szFormName, MB_OK | MB_ICONSTOP);
        return;
    }

    Assert(m_lpfnHrPickFolder && m_hChsFldDll);

    if (m_pses == NULL)
    {
        hr = m_pmsgsite->GetSession(&m_pses);
        if(hr)
        {
            m_lsterr.HrSetLastError(hr, m_pmsgsite);
            ShowError();
            return;
        }
    }

    BOOL fOldModalUp = g_FModalUp;

    g_FModalUp = TRUE;

    hr = (*m_lpfnHrPickFolder)(g_hinst, m_hwnd, m_pses, &pfld, &pmdb,
                                //&m_cbCFDState, &m_pbCFDState);
                                NULL, NULL);

    g_FModalUp = fOldModalUp;

    if(hr)
    {
        if(GetScode(hr) != MAPI_E_USER_CANCEL)
            ShowMessageBox(m_hwnd, "Can't move", g_szFormName, MB_OK | MB_ICONSTOP);

        return;
    }

    Assert(m_pmsgsite);
    Assert(pfld);
    Assert(pmdb);

    RECT rect;
    GetWindowRect(m_hwnd, &rect);

    hr = m_pmsgsite->MoveMessage(pfld, ViewCtx(), &rect);
    pfld->Release();
    pmdb->Release();
    if(HR_FAILED(hr))
    {
        m_lsterr.HrSetLastError(hr, m_pmsgsite);
        ShowError();
        return;
    }

    if(NULL == m_pmsg)
    {
        ShutdownForm(SAVEOPTS_NOSAVE);
    }

}

//wraper for MessageBox()
int CBaseForm::ShowMessageBox(HWND hwnd, LPCTSTR lpszText, LPCTSTR lpszTitle, UINT uiStyle)
{
    int iret;
    BOOL fOldModalUp = g_FMBoxUp;

    g_FMBoxUp = TRUE;

    iret = MessageBox(hwnd, lpszText, lpszTitle, uiStyle);

    g_FMBoxUp = fOldModalUp;

    return iret;
}

//wraper for m_lsterr.ShowError()
void CBaseForm::ShowError(void)
{
    int iret;
    BOOL fOldModalUp = g_FMBoxUp;

    g_FMBoxUp = TRUE;

    iret = m_lsterr.ShowError(m_hwnd);

    g_FMBoxUp = fOldModalUp;

}

BOOL CBaseForm::FGetFoldChooser(void)
{
    if(m_lpfnHrPickFolder)
        return TRUE;

    Assert(!m_hChsFldDll);

    UINT uiErrMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);

    m_hChsFldDll = LoadLibrary(szChsFldDllName);

    SetErrorMode(uiErrMode);

    if(m_hChsFldDll)
    {
        if((m_lpfnHrPickFolder = (HRPICKFOLDER)GetProcAddress(m_hChsFldDll,
                                        szChsFldFnName)))
        {
            return TRUE;
        }

        DebugTrace("smpfrm: GetProcAddress for %s failed", szChsFldFnName);

        FreeLibrary(m_hChsFldDll);
        m_hChsFldDll = NULL;
    }
    else
    {
        DebugTrace("smpfrm: failed to load choose folder dll\n");
    }

    return FALSE;
}

//
//if the body is to large for GetProps, have to use IStream
//
HRESULT HrStreamInMsgBody(LPMESSAGE pmsg, LPVOID pbase,
                            LPSTR *  pszBody, CLastError * plasterror)
{
    Assert(pmsg);
    Assert(pszBody);
    Assert(plasterror);

    HRESULT hr;
    LPSTREAM lpstreamBody = NULL;
    STATSTG statstg;
    LPSTR szRet = NULL;
    ULONG cb = 0;

    Assert(pszBody);
    *pszBody = NULL;

    hr = pmsg->OpenProperty(PR_BODY, &IID_IStream,
                            STGM_READ, 0, (LPUNKNOWN FAR *) &lpstreamBody);
    if(S_OK != GetScode(hr))
    {
        plasterror->HrSetLastError(hr, pmsg);
        goto err;
    }

    hr = lpstreamBody->Stat(&statstg, STATFLAG_NONAME);
    if(S_OK != GetScode(hr))
    {
        plasterror->HrSetLastError(hr, lpstreamBody);
        goto err;
    }
    Assert(statstg.cbSize.HighPart == 0);

    //if p base is not null, link the new buffer to it
    if(pbase)
    {
        if(MAPIAllocateMore(statstg.cbSize.LowPart + 1, pbase, (LPVOID FAR *) &szRet))
        {
            plasterror->HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            goto err;
        }
    }
    else
    {
        if(MAPIAllocateBuffer(statstg.cbSize.LowPart + 1, (LPVOID FAR *) &szRet))
        {
            plasterror->HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
            goto err;
        }
    }

    hr = lpstreamBody->Read(szRet, statstg.cbSize.LowPart, &cb);
    if(S_OK != GetScode(hr))
    {
        plasterror->HrSetLastError(hr);
        goto err;
    }
    szRet[statstg.cbSize.LowPart] = '\0';

err:
    UlRelease(lpstreamBody);
    lpstreamBody = NULL;
    if(hr)
    {
        if(!pbase)
            MAPIFreeBuffer(szRet);
        szRet = NULL;

    }

    *pszBody = szRet;

    return hr;
}
//
//if the body is to large for SetProps, have to use IStream
//
HRESULT HrStreamOutMsgBody(LPMESSAGE pmsg, LPSTR szBody, CLastError * plasterror)
{
    Assert(pmsg);
    Assert(szBody);
    Assert(plasterror);

    HRESULT hr;
    LPSTREAM lpstreamBody = NULL;
    ULONG cb = 0;

    Assert(szBody);

    hr = pmsg->OpenProperty(PR_BODY, &IID_IStream,
                            STGM_READWRITE, 0, (LPUNKNOWN FAR *) &lpstreamBody);
    if(S_OK != GetScode(hr))
    {
        plasterror->HrSetLastError(hr, pmsg);
        goto err;
    }

    hr = lpstreamBody->Write(szBody, lstrlen(szBody)+1, NULL);
    if(hr)
        plasterror->HrSetLastError(hr);

err:
    UlRelease(lpstreamBody);
    lpstreamBody = NULL;

    return hr;
}

/*
 *  Formats a Win32 file time as a MAPI date/time string.
 *  NOTE: converts from GMT to local time.
 */
void FormatTime(FILETIME *pft, LPSTR szTime)
{
    FILETIME        ft;
    SYSTEMTIME      systime;

    FileTimeToLocalFileTime(pft, &ft);
    FileTimeToSystemTime(&ft, &systime);
    wsprintf(szTime,
        "%04.4d/%02.2d/%02.2d %02.2d:%02.2d",
        systime.wYear, systime.wMonth, systime.wDay,
        systime.wHour, systime.wMinute);
}

///     GetMsgAdrlist
//    retrieves recipients adrlist of a message
HRESULT GetMsgAdrlist (LPMESSAGE pmsg, LPADRLIST *  ppAdrList, CLastError * plasterror)
{
    *ppAdrList = NULL;

    LPMAPITABLE pTable = NULL;
    HRESULT hr;

    hr = pmsg->GetRecipientTable (0, &pTable);
    if(!hr)
    {
        hr = HrQueryAllRows(pTable, NULL, NULL, NULL, 0, (LPSRowSet *)ppAdrList);
        if(hr)
        {
            plasterror->HrSetLastError(hr, pTable);
        }
    }
    else
    {
        plasterror->HrSetLastError(hr, pmsg);
    }

    pTable->Release();

    return hr;
}


RESOURCE.H

/*
 *  RESOURCE.H
 *
 *  Resource file constants
 *
 *  Copyright (c) 1986-1996, Microsoft Corporation.
 *  All rights reserved.
 */

#define IDR_SEND_FORM                           2
#define IDR_READ_FORM                           3

#define IDS_COMPONENT                           500

#define IDS_E_OUTOFMEMORY                       100
#define IDS_INVALID_ARGUMENT                    101
#define IDS_INVALID_OBJECT                      102
#define IDS_INTERFACE_NOT_SUPPORTED             103
#define IDS_ACCESS_DENIED                       104
#define IDS_NOT_SUPPORTED                       105
#define IDS_INVALID_CHARWIDTH                   106
#define IDS_NOT_FOUND                           107
#define IDS_CALL_FAILED                         108
#define IDS_USER_CANCEL                         109
#define IDS_ERRORS_RETURNED                     110
#define IDS_UNKNOWN_FLAGS                       111
#define IDS_UNEXPECTED                          112
#define IDS_CANTNOW                             113

#define IDC_MESSAGE_SAVE                        1000
#define IDC_MESSAGE_SAVE_AS                     1001
#define IDC_MESSAGE_MOVE                        1002
#define IDC_MESSAGE_COPY                        1003
#define IDC_MESSAGE_PRINT                       1004
#define IDC_MESSAGE_DELETE                      1005
#define IDC_MESSAGE_CLOSE                       1006
#define IDC_MESSAGE_SUBMIT                      1007
#define IDC_MESSAGE_REPLY                       1008
#define IDC_MESSAGE_REPLY_ALL                   1009
#define IDC_MESSAGE_FORWARD                     1010

#define IDC_EDIT_UNDO                           1100
#define IDC_EDIT_CUT                            1101
#define IDC_EDIT_COPY                           1102
#define IDC_EDIT_PASTE                          1103

#define IDC_VIEW_ITEMABOVE                      1200
#define IDC_VIEW_ITEMBELOW                      1201

#define IDC_HELP_ABOUT                          1300

#define IDC_VERSION                             1301

#define ID_TO_BUTTON                            2000
#define ID_CC_BUTTON                            2002
#define ID_CC                                   2003
#define ID_SUBJECT_LABEL                        2004
#define ID_FROM_LABEL                           2006
#define ID_FROM                                 2007
#define ID_SENT_LABEL                           2008
#define ID_SENT                                 2009
#define ID_TO_LABEL                             2010
#define ID_TO                                   2011
#define ID_BODY                                 2012
#define ID_SUBJECT                              2013

#define ICON_L                                  2100


SMPFRM.CFG

[Description]
MessageClass=IPM.Note.Sample.Simple
CLSID={88FFF551-B8C5-11ce-8DE0-00AA0060D242}
DisplayName=Simple Sample Message
Category=Standard
Subcategory=Form
Comment=This is the simple sample form provided as part of the MAPI SDK.
LargeIcon=smpfrml.ico
SmallIcon=smpfrms.ico
Version=1.0
Locale=enu
Hidden=0
Owner=Microsoft
Contact=Your Administrator

[Platforms]
Platform1=Win16
Platform2=NTx86
Platform3=Win95
;Platform4=MIPS
;Platform5=ALPHA
;Platform6=PPC

[Platform.Win16]
CPU=ix86
OSVersion=Win3.1
file1=smpfrm.exe
registry1=LocalServer = smpfrm.exe

[Platform.NTx86]
CPU=ix86
OSVersion=WinNT3.5
file1=smpfrm32.exe
registry1=LocalServer32 = smpfrm32.exe

[Platform.Win95]
CPU=ix86
OSVersion=Win95
LinkTo=NTx86

[Platform.MIPS]
CPU=MIPS
OSVersion=WinNT3.5
LinkTo=NTx86

[Platform.ALPHA]
CPU=AXP
OSVersion=WinNT3.5
LinkTo=NTx86

[Platform.PPC]
CPU=PPC
OSVersion=WinNT3.5
LinkTo=NTx86

[Properties]

[Verbs]
Verb1=1

[Verb.1]
DisplayName=&Open
Code=0
Flags=0
Attribs=2

[Extensions]
Extensions1=1

[Extension.1]
Type=30
NmidPropset={00020D0C-0000-0000-C000-000000000046}
NmidInteger=1
Value=1112122220000000


VIEWNTFR.CPP

/* copyright (c) 1995 Microsoft Corporation -- All rights reserved */

#include "precomp.h"

#pragma warning(disable:4514) // Unreferened inline function has been removed

CViewNotifier::CViewNotifier (void)
{
    Initialize();
}

CViewNotifier::~CViewNotifier ()
{
}

BOOL CViewNotifier::Initialize (void)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i)
    {
        m_aryAdviseSink [i] = NULL;
    }

    return TRUE;
}

HRESULT CViewNotifier::Advise(LPMAPIVIEWADVISESINK pAdvise,
                              ULONG * pulConnection)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i)
    {
        if (NULL == m_aryAdviseSink [i] )
        {
            m_aryAdviseSink [i] = pAdvise;
            m_aryAdviseSink [i] ->AddRef();
            *pulConnection = ++i;
            // connection == index + 1
            return S_OK;
        }
    }

    return ResultFromScode(E_OUTOFMEMORY);
}

HRESULT CViewNotifier::Unadvise (ULONG ulConnection)
{
    if (--ulConnection < MAXSINKS)
    {   // connection == index + 1
        m_aryAdviseSink [ulConnection] ->Release();
        m_aryAdviseSink [ulConnection] = NULL;
        return S_OK;
    }
    return ResultFromScode(E_INVALIDARG);
}

void CViewNotifier::OnShutdown (void)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i)
    {
        if (NULL != m_aryAdviseSink [i] )
        {
            m_aryAdviseSink [i] -> OnShutdown ();
        }
    }
}

void CViewNotifier::OnNewMessage (void)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i)
    {
        if (NULL != m_aryAdviseSink [i] )
        {
            m_aryAdviseSink [i] ->OnNewMessage();
        }
    }
}

HRESULT CViewNotifier::OnPrint(ULONG ulPageNumber, HRESULT hrStatus)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i)
    {
        if (NULL != m_aryAdviseSink [i] )
        {
            if(MAPI_E_USER_CANCEL == GetScode(m_aryAdviseSink [i] ->OnPrint(ulPageNumber, hrStatus)))
                return MAPI_E_USER_CANCEL;
        }
    }

    return hrSuccess;
}

void CViewNotifier::OnSubmitted (void)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i) {
        if (NULL != m_aryAdviseSink [i] ) {
            m_aryAdviseSink [i] ->OnSubmitted();
        }
    }
}

void CViewNotifier::OnSaved (void)
{
    ULONG   i;

    for (i = 0; i < MAXSINKS; ++i) {
        if (NULL != m_aryAdviseSink [i] ) {
            m_aryAdviseSink [i] ->OnSaved();
        }
    }
}


VIEWNTFR.H

/*
 *  VIEWNTFR.H
 *
 *  Helper class for supporting event notifications
 *
 *  Copyright (c) 1995, Microsoft Corporation.
 *  All rights reserved.
 */

#ifndef _VIEWNTFR_H_
#define _VIEWNTFR_H_

#define MAXSINKS    32

class CViewNotifier
{
private:
    LPMAPIVIEWADVISESINK    m_aryAdviseSink [MAXSINKS];

public:

    CViewNotifier  (void);
    ~CViewNotifier ();

    BOOL        Initialize (void);

    HRESULT     Advise (LPMAPIVIEWADVISESINK pAdvise, ULONG * pulConnection);
    HRESULT     Unadvise (ULONG ulConnection);

    void        OnShutdown (void);
    void        OnNewMessage (void);
    HRESULT        OnPrint (ULONG ulPageNumber, HRESULT hrStatus);
    void        OnSubmitted (void);
    void        OnSaved (void);
};

#endif //_VIEWNTFR_H_


README.TXT

MAPI

The MAPI sample is a demonstration of the Messaging Applications Programming
Interface (MAPI).


MAPI.H

/*
 *  m a p i . h
 *
 *  Messaging Applications Programming Interface.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All rights reserved.
 *
 *  Purpose:
 *    This file defines the structures and constants used by
 *    that subset of the messaging applications programming
 *    interface which will be supported under Windows by
 *    Microsoft Mail for PC Networks vesion 3.0.
 */

/*
 *  Types.
 */

#ifdef __cplusplus
extern "C" {
#endif

typedef unsigned long       ULONG;
typedef unsigned long FAR * LPULONG;
typedef unsigned long       FLAGS;
typedef unsigned longLHANDLE, FAR *LPLHANDLE;
#define lhSessionNull   ((LHANDLE)0)

#define MAPI_MESSAGEID_LENGTH   64

typedef struct
  {
    ULONG ulReserved;    // Reserved for future use (must be 0)
ULONG flFlags; // Flags
    ULONG nPosition;     // character in text to be replaced by attachment
    LPSTR lpszPathName;  // Full path name of attachment file
    LPSTR lpszFileName;  // Original file name (optional)
    LPVOID  UNALIGNED lpFileType;   // Attachment file type (optional)
  } UNALIGNED MapiFileDesc, UNALIGNED FAR * lpMapiFileDesc;

#defineMAPI_OLE0x00000001
#defineMAPI_OLE_STATIC0x00000002

typedef struct
  {
    ULONG ulReserved;           // Reserved for future use
    ULONG ulRecipClass;         // Recipient class
                                // MAPI_TO, MAPI_CC, MAPI_BCC, MAPI_ORIG
    LPSTR lpszName;             // Recipient name
    LPSTR lpszAddress;          // Recipient address (optional)
    ULONG ulEIDSize;        // Count in bytes of size of pEntryID
    LPVOID lpEntryID;           // System-specific recipient reference
  } UNALIGNED MapiRecipDesc, UNALIGNED FAR * lpMapiRecipDesc;

#define MAPI_ORIG   0           // Recipient is message originator
#define MAPI_TO     1           // Recipient is a primary recipient
#define MAPI_CC     2           // Recipient is a copy recipient
#define MAPI_BCC    3           // Recipient is blind copy recipient

typedef struct
  {
    ULONG ulReserved;                   // Reserved for future use (M.B. 0)
    LPSTR lpszSubject;                  // Message Subject
    LPSTR lpszNoteText;                 // Message Text
    LPSTR lpszMessageType;              // Message Class
    LPSTR lpszDateReceived;             // in YYYY/MM/DD HH:MM format
LPSTR lpszConversationID;// conversation thread ID
    FLAGS flFlags;                      // unread,return receipt
    lpMapiRecipDesc lpOriginator;       // Originator descriptor
    ULONG nRecipCount;                  // Number of recipients
    lpMapiRecipDesc lpRecips;           // Recipient descriptors
    ULONG nFileCount;                   // # of file attachments
    lpMapiFileDesc lpFiles;             // Attachment descriptors
  } UNALIGNED MapiMessage, UNALIGNED FAR * lpMapiMessage;

#define MAPI_UNREAD             0x00000001
#define MAPI_RECEIPT_REQUESTED  0x00000002
#define MAPI_SENT               0x00000004

/*
 *  Entry points.
 */

#define MAPI_LOGON_UI                   0x00000001  // Display logon UI
#define MAPI_NEW_SESSION                0x00000002  // Do not use default.
#define MAPI_DIALOG                     0x00000008  // Display a send note UI
#define MAPI_UNREAD_ONLY                0x00000020  // Only unread messages
#define MAPI_ENVELOPE_ONLY              0x00000040  // Only header information
#define MAPI_PEEK                       0x00000080  // Do not mark as read.
#define MAPI_GUARANTEE_FIFO0x00000100// use date order
#defineMAPI_BODY_AS_FILE0x00000200
#define MAPI_AB_NOMODIFY0x00000400// Don't allow mods of AB entries
#defineMAPI_SUPPRESS_ATTACH0x00000800// header + body, no files
#defineMAPI_FORCE_DOWNLOAD0x00001000// force download of new mail during MAPILogon

ULONG FAR PASCAL MAPILogon(ULONG ulUIParam, LPSTR lpszName, LPSTR lpszPassword,
                           FLAGS flFlags, ULONG ulReserved,
                           LPLHANDLE lplhSession);

ULONG FAR PASCAL MAPILogoff(LHANDLE lhSession, ULONG ulUIParam, FLAGS flFlags,
                            ULONG ulReserved);

ULONG FAR PASCAL MAPISendMail(LHANDLE lhSession, ULONG ulUIParam,
                              lpMapiMessage lpMessage, FLAGS flFlags,
                              ULONG ulReserved);

ULONG FAR PASCAL MAPISendDocuments(ULONG ulUIParam, LPSTR lpszDelimChar,
                                   LPSTR lpszFilePaths, LPSTR lpszFileNames,
                                   ULONG ulReserved);

ULONG FAR PASCAL MAPIFindNext(LHANDLE lhSession, ULONG ulUIParam,
                              LPSTR lpszMessageType, LPSTR lpszSeedMessageID,
                              FLAGS flFlags, ULONG ulReserved,
                              LPSTR lpszMessageID);

ULONG FAR PASCAL MAPIReadMail(LHANDLE lhSession, ULONG ulUIParam,
                              LPSTR lpszMessageID, FLAGS flFlags,
                              ULONG ulReserved, lpMapiMessage FAR *lppMessageOut);

ULONG FAR PASCAL MAPISaveMail(LHANDLE lhSession, ULONG ulUIParam,
                              lpMapiMessage pMessage, FLAGS flFlags,
                              ULONG ulReserved, LPSTR lpszMessageID);

ULONG FAR PASCAL MAPIDeleteMail(LHANDLE lhSession, ULONG ulUIParam,
                                LPSTR lpszMessageID, FLAGS flFlags,
                                ULONG ulReserved);

ULONG FAR PASCAL MAPIFreeBuffer( LPVOID pv );

ULONG FAR PASCAL MAPIAddress(LHANDLE lhSession, ULONG ulUIParam,
LPSTR plszCaption, ULONG nEditFields,
LPSTR lpszLabels, ULONG nRecips,
lpMapiRecipDesc lpRecips, FLAGS flFlags, ULONG ulReserved,
LPULONG lpnNewRecips, lpMapiRecipDesc FAR *lppNewRecips);

ULONG FAR PASCAL MAPIDetails(LHANDLE lhSession, ULONG ulUIParam,
lpMapiRecipDesc lpRecip, FLAGS flFlags, ULONG ulReserved);

ULONG FAR PASCAL MAPIResolveName(LHANDLE lhSession, ULONG ulUIParam,
LPSTR lpszName, FLAGS flFlags,
ULONG ulReserved, lpMapiRecipDesc FAR *lppRecip);

#define SUCCESS_SUCCESS                     0
#define MAPI_USER_ABORT                     1
#define MAPI_E_FAILURE                      2
#define MAPI_E_LOGIN_FAILURE                3
#define MAPI_E_DISK_FULL                    4
#define MAPI_E_INSUFFICIENT_MEMORY          5
#define MAPI_E_ACCESS_DENIED6
#define MAPI_E_TOO_MANY_SESSIONS            8
#define MAPI_E_TOO_MANY_FILES               9
#define MAPI_E_TOO_MANY_RECIPIENTS          10
#define MAPI_E_ATTACHMENT_NOT_FOUND         11
#define MAPI_E_ATTACHMENT_OPEN_FAILURE      12
#define MAPI_E_ATTACHMENT_WRITE_FAILURE     13
#define MAPI_E_UNKNOWN_RECIPIENT            14
#define MAPI_E_BAD_RECIPTYPE                15
#define MAPI_E_NO_MESSAGES                  16
#define MAPI_E_INVALID_MESSAGE              17
#define MAPI_E_TEXT_TOO_LARGE               18
#defineMAPI_E_INVALID_SESSION19
#defineMAPI_E_TYPE_NOT_SUPPORTED20
#defineMAPI_E_AMBIGUOUS_RECIPIENT21
#define MAPI_E_MESSAGE_IN_USE22
#define MAPI_E_NETWORK_FAILURE23
#defineMAPI_E_INVALID_EDITFIELDS24
#defineMAPI_E_INVALID_RECIPS25
#defineMAPI_E_NOT_SUPPORTED26

#ifdef __cplusplus
}
#endif


MAPIAPP.C

/* QuickCase:W KNB Version 1.00 */
#include "MAPIAPP.h"
#include <string.h>
#include <stdio.h>
#include "mapi.h"
#define MAIN 1
#include "mapinit.h"

HANDLE hLibrary;
LHANDLE hMAPISession;

MapiMessage mmMapiMessage;

ULONG nMessageSize = 0x000004000;
lpMapiMessage FAR *lppMessage;
lpMapiMessage lpMessage;
char szSeedMessageID[512];
char szMessageID[512];
char szSubject[512];
char szNoteText[4096];

LPSTR lpszSeedMessageID = &szSeedMessageID[0];
LPSTR lpszMessageID = &szMessageID[0];

LPSTR pszSubject = &szSubject[0];
LPSTR pszNoteText = &szNoteText[0];
LPSTR pszDateReceived = "1991/03/23 12:00";
MapiRecipDesc rdOriginator = { 0L,MAPI_ORIG,(LPSTR)"ONE",NULL};

int iFindFirst= TRUE;

void InitMessage(lpMapiMessage pmmMessage)
{
  pmmMessage->ulReserved = 0L;
  pmmMessage->lpszSubject =pszSubject;
  pmmMessage->lpszNoteText =pszNoteText;
  pmmMessage->lpszMessageType = NULL;
  pmmMessage->lpszDateReceived = pszDateReceived;
  pmmMessage->flFlags = MAPI_UNREAD;
  pmmMessage->lpOriginator = &rdOriginator;
  pmmMessage->nRecipCount = 0L;
  pmmMessage->lpRecips = NULL;
  pmmMessage->nFileCount = 0L;
  pmmMessage->lpFiles = NULL;
}

/****************************************************************************

    FUNCTION: OutOfMemory(void)

    PURPOSE:  Displays warning message

****************************************************************************/
void OutOfMemory(void)
{
    MessageBox(
        GetFocus(),
        GetStringRes (IDS_ERR_NO_MEMORY),
        NULL,
        MB_ICONHAND | MB_SYSTEMMODAL);
    return;
}

void mycpystr(char * npszStrg, LPSTR lpszStrg)
{
  if (lpszStrg != NULL)
    strcpy(npszStrg, lpszStrg);
  else
     *npszStrg = '\0';
}

/************************************************************************/
/*                                                                      */
/* Windows 3.0 Main Program Body                                        */
/*                                                                      */
/* The following routine is the Windows Main Program.  The Main Program */
/* is executed when a program is selected from the Windows Control      */
/* Panel or File Manager.  The WinMain routine registers and creates    */
/* the program's main window and initializes global objects.  The       */
/* WinMain routine also includes the applications message dispatch      */
/* loop.  Every window message destined for the main window or any      */
/* subordinate windows is obtained, possibly translated, and            */
/* dispatched to a window or dialog processing function. The dispatch   */
/* loop is exited when a WM_QUIT message is obtained.  Before exiting   */
/* the WinMain routine should destroy any objects created and free      */
/* memory and other resources.                                          */
/*                                                                      */
/************************************************************************/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
 /***********************************************************************/
 /* HANDLE hInstance;       handle for this instance                    */
 /* HANDLE hPrevInstance;   handle for possible previous instances      */
 /* LPSTR  lpszCmdLine;     long pointer to exec command line           */
 /* int    nCmdShow;        Show code for main window display           */
 /***********************************************************************/

 MSG        msg;           /* MSG structure to store your messages        */
 int        nRc;           /* return value from Register Classes          */
 long       nWndunits;     /* window units for size and location          */
 int        nWndx;         /* the x axis multiplier                       */
 int        nWndy;         /* the y axis multiplier                       */
 int        nX;            /* the resulting starting point (x, y)         */
 int        nY;
 int        nWidth;        /* the resulting width and height for this     */
 int        nHeight;       /* window                                      */

 strcpy(szAppName, "MAPIAPP");
 hInst = hInstance;
 if(!hPrevInstance)
   {
    /* register window classes if first instance of application         */
    if ((nRc = nCwRegisterClasses()) == -1)
      {
       /* registering one of the windows failed                         */
       LoadString(hInst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString));
       MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
       return nRc;
      }
   }

 /* Create a device independant size and location                       */
 nWndunits = GetDialogBaseUnits();
 nWndx = LOWORD(nWndunits);
 nWndy = HIWORD(nWndunits);
 nX = ((60 * nWndx) / 4);
 nY = ((60 * nWndy) / 8);
 nWidth = ((197 * nWndx) / 4);
 nHeight = ((138 * nWndy) / 8);

 /* create application's Main window                                    */
 hWndMain = CreateWindow(
                szAppName,               /* Window class name           */
GetStringRes (IDS_APP_TITLE),  /* Window's title        */
                WS_CAPTION      |        /* Title and Min/Max           */
                WS_SYSMENU      |        /* Add system menu box         */
                WS_MINIMIZEBOX  |        /* Add minimize box            */
                WS_MAXIMIZEBOX  |        /* Add maximize box            */
                WS_BORDER       |        /* thin frame                  */
                WS_CLIPCHILDREN |        /* don't draw in child windows areas */
                WS_OVERLAPPED,
                nX, nY,                  /* X, Y                        */
                nWidth, nHeight,         /* Width, Height of window     */
                NULL,                    /* Parent window's handle      */
                NULL,                    /* Default to Class Menu       */
                hInst,                   /* Instance of window          */
                NULL);                   /* Create struct for WM_CREATE */

 if (InitMAPI() != 0)
   return ERR_LOAD_LIB;

  InitMessage(&mmMapiMessage);

 if(hWndMain == NULL)
   {
    LoadString(hInst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
    MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
    return IDS_ERR_CREATE_WINDOW;
   }

 EnableMenuItem(GetMenu(hWndMain), IDM_A_LOGOFF, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_S_MAIL, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_S_DOCUMENT, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDFIRST, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDNEXT, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_M_READ, MF_DISABLED | MF_GRAYED);
 EnableMenuItem(GetMenu(hWndMain), IDM_M_DELETE, MF_DISABLED | MF_GRAYED);

 ShowWindow(hWndMain, nCmdShow);            /* display main window      */

 while(GetMessage(&msg, NULL, 0, 0))        /* Until WM_QUIT message    */
   {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
   }

 DeInitMAPI();
 /* Do clean up before exiting from the application                     */
 CwUnRegisterClasses();
 return msg.wParam;
} /*  End of WinMain                                                    */
/************************************************************************/
/*                                                                      */
/* Main Window Procedure                                                */
/*                                                                      */
/* This procedure provides service routines for the Windows events      */
/* (messages) that Windows sends to the window, as well as the user     */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding       */
/* keyboard accelerators.                                               */
/*                                                                      */
/* The SWITCH statement shown below distributes the window messages to  */
/* the respective message service routines, which are set apart by the  */
/* CASE statements. The window procedures must provide an appropriate   */
/* service routine for its end user initiated messages, as well as the  */
/* general Windows messages (ie. WM_CLOSE message). If a message is     */
/* sent to this procedure for which there is no programmed CASE clause  */
/* (i.e., no service routine), the message is defaulted to the          */
/* DefWindowProc function, where it is handled by Windows               */
/*                                                                      */
/* For the end-user initiated messages, this procedure is concerned     */
/* principally with the WM_COMMAND message. The menu control ID (or the */
/* corresponding accelerator ID) is communicated to this procedure in   */
/* the first message parameter (wParam). The window procedure provides  */
/* a major CASE statement for the WM_COMMAND message and a subordinate  */
/* SWITCH statement to provide CASE clauses for the message service     */
/* routines for the various menu item's, identified by their ID values. */
/*                                                                      */
/* The message service routines for the individual menu items are the   */
/* main work points in the program. These service routines contain the  */
/* units of work performed when the end user select one of the menu     */
/* controls. The required application response to a menu control is     */
/* programmed in its associated CASE clause. The service routines may   */
/* contain subroutine calls to separately compiled and libraried        */
/* routines, in-line calls to subroutines to be embodied in this source */
/* code module, or program statements entered directly in the CASE      */
/* clauses. Program control is switched to the appropriate service      */
/* routine when Windows recognizes the end user event and sends a WM_COMMAND */
/* message to the window procedure. The service routine provides the    */
/* appropriate application-specific response to the end user initiated  */
/* event, then breaks to return control to the WinMain() routine which  */
/* continues to service the message queue of the window(s).             */
/*                                                                      */
/************************************************************************/

LONG FAR PASCAL WndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
 HMENU      hMenu=0;            /* handle for the menu                 */
 HBITMAP    hBitmap=0;          /* handle for bitmaps                  */
 HDC        hDC;                /* handle for the display device       */
 PAINTSTRUCT ps;                /* holds PAINT information             */
 int        nRc=0;              /* return code                         */
 FLAGS flFlag = 0L;
 ULONG ulResult;
  char szBuf[32*1024]; // Waste of stack space, but this is only a demo.
  char szTmp[32*1024]; // Waste of stack space, but this is only a demo.

 switch (Message) {
   case WM_COMMAND:
     /* The Windows messages for action bar and pulldown menu items */
     /* are processed here.                                         */
     /* The WM_COMMAND message contains the message ID in its first */
     /* parameter (wParam). This routine is programmed to SWITCH on */
     /* the #define values generated by CASE:W for the menu items   */
     /* in the application's header (*.H) file. The ID values have  */
     /* the format, IDM_itemname. The service routines for the      */
     /* various menu items follow the CASE statements up to the     */
     /* generated BREAK statements.                                 */
     switch (LOWORD(wParam)) {
       case IDM_A_LOGON:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "Log&In" here.                      */
         flFlag =MAPI_NEW_SESSION | MAPI_LOGON_UI;
         ulResult = (*lpfnMAPILogon)(hWnd, NULL, NULL, flFlag, 0L, (LPLHANDLE)&hMAPISession);
         if (ulResult == 0L) {
           EnableMenuItem(GetMenu(hWndMain), IDM_A_LOGOFF, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_S_MAIL, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_S_DOCUMENT, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDFIRST, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDNEXT, MF_ENABLED);
         }
       break;

       case IDM_A_LOGOFF:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "Log&Off" here.                     */
         ulResult = (*lpfnMAPILogoff)(hMAPISession, hWnd, 0L, 0L);
         if (ulResult == 0L) {
            EnableMenuItem(GetMenu(hWndMain), IDM_A_LOGOFF, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_S_MAIL, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_S_DOCUMENT, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDFIRST, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDNEXT, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_M_READ, MF_DISABLED | MF_GRAYED);
             EnableMenuItem(GetMenu(hWndMain), IDM_M_DELETE, MF_DISABLED | MF_GRAYED);
         }
       break;

       case IDM_S_MAIL:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "&Mail" here.                       */
          flFlag =MAPI_DIALOG;
         ulResult = (*lpfnMAPISendMail)(hMAPISession, hWnd, &mmMapiMessage, flFlag, 0L);
       break;

       case IDM_S_DOCUMENT:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "&Document" here.                   */
         ulResult = (*lpfnMAPISendDocuments)(hWnd, ";", "C:\\CONFIG.SYS", "CONFIG.SYS", 0L);
       break;

       case IDM_M_FINDFIRST:
           iFindFirst=TRUE;
       case IDM_M_FINDNEXT:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "&Find Next" here.                  */
         if (iFindFirst) {
           *lpszSeedMessageID = '\0';
           iFindFirst = FALSE;
         }
         else {
           strcpy(lpszSeedMessageID,lpszMessageID);
         }
         ulResult = (*lpfnMAPIFindNext)(hMAPISession, hWnd, NULL, lpszSeedMessageID, flFlag, 0L, lpszMessageID);
         if (ulResult == 0L) {
           EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDNEXT, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_READ, MF_ENABLED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_DELETE, MF_ENABLED);
         }
         else {
           EnableMenuItem(GetMenu(hWndMain), IDM_M_FINDNEXT, MF_DISABLED | MF_GRAYED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_READ, MF_DISABLED | MF_GRAYED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_DELETE, MF_DISABLED | MF_GRAYED);
         }
       break;

       case IDM_M_READ:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "&Read" here.                       */

           lppMessage= (lpMapiMessage FAR *) &lpMessage;
           ulResult = (*lpfnMAPIReadMail)(hMAPISession, hWnd, lpszMessageID, flFlag, 0L, lppMessage);
           if (ulResult == 0L) {
             mycpystr(szTmp,lpMessage->lpOriginator->lpszName);
 sprintf(szBuf, GetStringRes(IDS_FROM), szTmp);
             mycpystr(szTmp,lpMessage->lpRecips[0].lpszName);
             sprintf(szBuf, GetStringRes(IDS_TO), szBuf,szTmp);
             mycpystr(szTmp,lpMessage->lpszDateReceived);
             sprintf(szBuf, GetStringRes(IDS_DATE), szBuf,szTmp);
             mycpystr(szTmp,lpMessage->lpszSubject);
 sprintf(szBuf, GetStringRes(IDS_SUBJECT), szBuf,szTmp);
 mycpystr(szTmp,lpMessage->lpszNoteText);
 sprintf(szBuf,"%s%s",szBuf,szTmp);

 MessageBox(hWnd,szBuf, GetStringRes(IDS_MESSAGE), MB_OK);
             ulResult = lpfnMAPIFreeBuffer((LPVOID)lpMessage);
           }
       break;

       case IDM_M_DELETE:
            /* Place User Code to respond to the                   */
            /* Menu Item Named "&Delete" here.                  */

         ulResult = (*lpfnMAPIDeleteMail)(hMAPISession, hWnd, lpszMessageID, flFlag, 0l);
         if (ulResult == 0L) {
           EnableMenuItem(GetMenu(hWndMain), IDM_M_READ, MF_DISABLED | MF_GRAYED);
           EnableMenuItem(GetMenu(hWndMain), IDM_M_DELETE, MF_DISABLED | MF_GRAYED);
         }
       break;

       default:
         return DefWindowProc(hWnd, Message, wParam, lParam);
     }
     if (ulResult != 0L) {       /** oops **/

       sprintf(szBuf, GetStringRes(IDS_RETURNED_ERROR), ulResult);
       MessageBox(hWnd,szBuf, GetStringRes(IDS_FAILED),MB_OK);
     }
     else {
       sprintf(szBuf, GetStringRes(IDS_GOOD_JOB));
       MessageBox(hWnd,szBuf, GetStringRes(IDS_SUCCESS), MB_OK);
     }
   break;        /* End of WM_COMMAND                             */

   case WM_CREATE:
     /* The WM_CREATE message is sent once to a window when the     */
     /* window is created.  The window procedure for the new window */
     /* receives this message after the window is created, but      */
     /* before the window becomes visible.                          */
     /*                                                             */
     /* Parameters:                                                 */
     /*                                                             */
     /*    lParam  -  Points to a CREATESTRUCT structure with       */
     /*               the following form:                           */
     /*                                                             */
     /*    typedef struct                                           */
     /*              {                                              */
     /*               LPSTR     lpCreateParams;                     */
     /*               HANDLE    hInst;                              */
     /*               HANDLE    hMenu;                              */
     /*               HWND      hwndParent;                         */
     /*               int       cy;                                 */
     /*               int       cx;                                 */
     /*               int       y;                                  */
     /*               int       x;                                  */
     /*               LONG      style;                              */
     /*               LPSTR     lpszName;                           */
     /*               LPSTR     lpszClass;                          */
     /*               DWORD     dwExStyle;                          */
     /*              }  CREATESTRUCT;                               */

   break;       /*  End of WM_CREATE                              */

   case WM_MOVE:     /*  code for moving the window                    */
   break;

   case WM_SIZE:     /*  code for sizing client area                   */
        /* wParam contains a code indicating the requested sizing      */
        /* lParam contains the new height and width of the client area */
   break;       /* End of WM_SIZE                                 */

   case WM_PAINT:    /* code for the window's client area              */
     /* Obtain a handle to the device context                       */
     /* BeginPaint will sends WM_ERASEBKGND if appropriate          */
     memset(&ps, 0x00, sizeof(PAINTSTRUCT));
     hDC = BeginPaint(hWnd, &ps);

     /* Included in case the background is not a pure color         */
     SetBkMode(hDC, TRANSPARENT);

     /* Application should draw on the client window using          */
     /* the GDI graphics and text functions.  'ps' the PAINTSTRUCT  */
     /* returned by BeginPaint contains a rectangle to the          */
     /* area that must be repainted.                                */

     /* Inform Windows painting is complete                         */
     EndPaint(hWnd, &ps);
   break;       /*  End of WM_PAINT                               */

   case WM_CLOSE:  /* close the window                                 */
     /* Destroy child windows, modeless dialogs, then, this window  */
     DestroyWindow(hWnd);
     if (hWnd == hWndMain)
       PostQuitMessage(0);  /* Quit the application                 */
   break;

   default:
        /* For any message for which you don't specifically provide a  */
        /* service routine, you should return the message to Windows   */
        /* for default message processing.                             */
     return DefWindowProc(hWnd, Message, wParam, lParam);
 }
 return 0L;
}     /* End of WndProc                                         */

/************************************************************************/
/*                                                                      */
/* nCwRegisterClasses Function                                          */
/*                                                                      */
/* The following function registers all the classes of all the windows  */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0.                             */
/*                                                                      */
/************************************************************************/

int nCwRegisterClasses(void)
{
 WNDCLASS   wndclass;    /* struct to define a window class             */
 memset(&wndclass, 0x00, sizeof(WNDCLASS));

 /* load WNDCLASS with window's characteristics                         */
 wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;
 wndclass.lpfnWndProc = WndProc;
 /* Extra storage for Class and Window objects                          */
 wndclass.cbClsExtra = 0;
 wndclass.cbWndExtra = 0;
 wndclass.hInstance = hInst;
 wndclass.hIcon = LoadIcon(hInst, "MAPIAPP");
 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
 /* Create brush for erasing background                                 */
 wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wndclass.lpszMenuName = szAppName;   /* Menu Name is App Name */
 wndclass.lpszClassName = szAppName; /* Class Name is App Name */
 if(!RegisterClass(&wndclass))
   return -1;

 return(0);
} /* End of nCwRegisterClasses                                          */

/************************************************************************/
/*  CwUnRegisterClasses Function                                        */
/*                                                                      */
/*  Deletes any refrences to windows resources created for this         */
/*  application, frees memory, deletes instance, handles and does       */
/*  clean up prior to exiting the window                                */
/*                                                                      */
/************************************************************************/

void CwUnRegisterClasses(void)
{
 WNDCLASS   wndclass;    /* struct to define a window class             */
 memset(&wndclass, 0x00, sizeof(WNDCLASS));

 UnregisterClass(szAppName, hInst);
}    /* End of CwUnRegisterClasses                                      */

//---------------------------------------------------------------------------
//
// FUNCTION:    GetStringRes (int id INPUT ONLY)
//
// COMMENTS:    Load the resource string with the ID given, and return a
//              pointer to it.  Notice that the buffer is common memory so
//              the string must be used before this call is made a second time.
//
//---------------------------------------------------------------------------

LPTSTR GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


MAPIAPP.H

/* QuickCase:W KNB Version 1.00 */
#include <windows.h>
#include <string.h>
#define IDM_ACCESS                1000
#define IDM_A_LOGON                    1050
#define IDM_A_LOGOFF                   1100
#define IDM_SEND                  2000
#define IDM_S_MAIL                     2050
#define IDM_S_DOCUMENT                 2100
#define IDM_MANIPULATE            3000
#define IDM_M_FINDFIRST           3040
#define IDM_M_FINDNEXT                 3050
#define IDM_M_READ                     3100
#define IDM_M_DELETE                   3150

#define IDS_ERR_REGISTER_CLASS   1
#define IDS_ERR_CREATE_WINDOW    2
#define IDS_FROM                 3
#define IDS_TO                   4
#define IDS_DATE                 5
#define IDS_SUBJECT              6
#define IDS_MESSAGE              7
#define IDS_RETURNED_ERROR       8
#define IDS_FAILED               9
#define IDS_GOOD_JOB            10
#define IDS_SUCCESS             11
#define IDS_ERR_NO_MEMORY       12
#define IDS_APP_TITLE           13

char szString[128];   /* variable to load resource strings         */

char szAppName[20];   /* class name for the window               */
HINSTANCE hInst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, UINT, WPARAM, LPARAM);
int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);
LPTSTR GetStringRes (int id);


MAPINIT.C

#include <windows.h>
#include "mapi.h"
#include "mapinit.h"

extern HANDLE hLibrary;

int FAR PASCAL InitMAPI()
{

  if ((hLibrary = LoadLibrary(MAPIDLL)) < (HANDLE)32)
    return(ERR_LOAD_LIB);

  if ((lpfnMAPILogon = (PFNMAPILOGON)GetProcAddress(hLibrary,SZ_MAPILOGON)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPILogoff= (PFNMAPILOGOFF)GetProcAddress(hLibrary,SZ_MAPILOGOFF)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPISendMail= (PFNMAPISENDMAIL)GetProcAddress(hLibrary,SZ_MAPISENDMAIL)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPISendDocuments= (PFNMAPISENDDOCUMENTS)GetProcAddress(hLibrary,SZ_MAPISENDDOC)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIFindNext= (PFNMAPIFINDNEXT)GetProcAddress(hLibrary,SZ_MAPIFINDNEXT)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIReadMail= (PFNMAPIREADMAIL)GetProcAddress(hLibrary,SZ_MAPIREADMAIL)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPISaveMail= (PFNMAPISAVEMAIL)GetProcAddress(hLibrary,SZ_MAPISAVEMAIL)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIDeleteMail= (PFNMAPIDELETEMAIL)GetProcAddress(hLibrary,SZ_MAPIDELMAIL)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIFreeBuffer= (PFNMAPIFREEBUFFER)GetProcAddress(hLibrary,SZ_MAPIFREEBUFFER)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIAddress= (PFNMAPIADDRESS)GetProcAddress(hLibrary,SZ_MAPIADDRESS)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIDetails= (PFNMAPIDETAILS)GetProcAddress(hLibrary,SZ_MAPIDETAILS)) == NULL)
    return(ERR_LOAD_FUNC);

  if ((lpfnMAPIResolveName= (PFNMAPIRESOLVENAME)GetProcAddress(hLibrary,SZ_MAPIRESOLVENAME)) == NULL)
    return(ERR_LOAD_FUNC);

  return(0);
}

int FAR PASCAL DeInitMAPI()
{

  lpfnMAPILogon = NULL;
  lpfnMAPILogoff= NULL;
  lpfnMAPISendMail= NULL;
  lpfnMAPISendDocuments= NULL;
  lpfnMAPIFindNext= NULL;
  lpfnMAPIReadMail= NULL;
  lpfnMAPISaveMail= NULL;
  lpfnMAPIDeleteMail= NULL;
  lpfnMAPIFreeBuffer = NULL;
  lpfnMAPIAddress= NULL;
  lpfnMAPIDetails = NULL;
  lpfnMAPIResolveName;

  FreeLibrary(hLibrary);

  return(0);
}


MAPINIT.H

#define SZ_MAPILOGON "MAPILogon"
#define SZ_MAPILOGOFF "MAPILogoff"
#define SZ_MAPISENDMAIL "MAPISendMail"
#define SZ_MAPISENDDOC "MAPISendDocuments"
#define SZ_MAPIFINDNEXT "MAPIFindNext"
#define SZ_MAPIREADMAIL "MAPIReadMail"
#define SZ_MAPISAVEMAIL "MAPISaveMail"
#define SZ_MAPIDELMAIL "MAPIDeleteMail"
#define SZ_MAPIFREEBUFFER "MAPIFreeBuffer"
#define SZ_MAPIADDRESS "MAPIAddress"
#define SZ_MAPIDETAILS "MAPIDetails"
#define SZ_MAPIRESOLVENAME "MAPIResolveName"
#define MAPIDLL "MAPI32.DLL"

#define ERR_LOAD_LIB  0x02
#define ERR_LOAD_FUNC 0x04

typedef ULONG (FAR PASCAL *PFNMAPILOGON)(HWND, LPSTR, LPSTR, FLAGS, ULONG, LPLHANDLE);

typedef ULONG (FAR PASCAL *PFNMAPILOGOFF)(LHANDLE, HWND, FLAGS,ULONG);

typedef ULONG (FAR PASCAL *PFNMAPISENDMAIL)(LHANDLE, HWND, lpMapiMessage, FLAGS,
                                     ULONG);

typedef ULONG (FAR PASCAL *PFNMAPISENDDOCUMENTS)(HWND, LPSTR, LPSTR, LPSTR, ULONG);

typedef ULONG (FAR PASCAL *PFNMAPIFINDNEXT)(LHANDLE, HWND, LPSTR, LPSTR, FLAGS,
                                     ULONG, LPSTR);

typedef ULONG (FAR PASCAL *PFNMAPIREADMAIL)(LHANDLE, HWND, LPSTR, FLAGS, ULONG,
                                     lpMapiMessage FAR *);

typedef ULONG (FAR PASCAL *PFNMAPISAVEMAIL)(LHANDLE, HWND, lpMapiMessage, FLAGS,
                                     ULONG, LPSTR);

typedef ULONG (FAR PASCAL *PFNMAPIDELETEMAIL)(LHANDLE, HWND, LPSTR, FLAGS, ULONG);

typedef ULONG (FAR PASCAL *PFNMAPIFREEBUFFER)(LPVOID);

typedef ULONG (FAR PASCAL *PFNMAPIADDRESS)(LHANDLE, HWND, LPSTR, ULONG, LPSTR,
                ULONG, lpMapiRecipDesc, FLAGS, ULONG,
                                    LPULONG, lpMapiRecipDesc FAR *);

typedef ULONG (FAR PASCAL *PFNMAPIDETAILS)(LHANDLE, HWND,lpMapiRecipDesc, FLAGS,
                                    ULONG);

typedef ULONG (FAR PASCAL *PFNMAPIRESOLVENAME)(LHANDLE, HWND, LPSTR, FLAGS,
                ULONG, lpMapiRecipDesc FAR *);
#ifdef MAIN

PFNMAPILOGON lpfnMAPILogon;
PFNMAPILOGOFF lpfnMAPILogoff;
PFNMAPISENDMAIL lpfnMAPISendMail;
PFNMAPISENDDOCUMENTS lpfnMAPISendDocuments;
PFNMAPIFINDNEXT lpfnMAPIFindNext;
PFNMAPIREADMAIL lpfnMAPIReadMail;
PFNMAPISAVEMAIL lpfnMAPISaveMail;
PFNMAPIDELETEMAIL lpfnMAPIDeleteMail;
PFNMAPIFREEBUFFER lpfnMAPIFreeBuffer;
PFNMAPIADDRESS lpfnMAPIAddress;
PFNMAPIDETAILS lpfnMAPIDetails;
PFNMAPIRESOLVENAME lpfnMAPIResolveName;

#else

extern PFNMAPILOGON lpfnMAPILogon;
extern PFNMAPILOGOFF lpfnMAPILogoff;
extern PFNMAPISENDMAIL lpfnMAPISendMail;
extern PFNMAPISENDDOCUMENTS lpfnMAPISendDocuments;
extern PFNMAPIFINDNEXT lpfnMAPIFindNext;
extern PFNMAPIREADMAIL lpfnMAPIReadMail;
extern PFNMAPISAVEMAIL lpfnMAPISaveMail;
extern PFNMAPIDELETEMAIL lpfnMAPIDeleteMail;
extern PFNMAPIFREEBUFFER lpfnMAPIFreeBuffer;
extern PFNMAPIADDRESS lpfnMAPIAddress;
extern PFNMAPIDETAILS lpfnMAPIDetails;
extern PFNMAPIRESOLVENAME lpfnMAPIResolveName;

#endif

int FAR PASCAL InitMAPI(void);
int FAR PASCAL DeInitMAPI(void);


ADMNDEMO.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   ADMNDEMO.C
//|      This module contains the main interface modules for SATOOL, including
//|         the MDI client support
//*---------------------------------------------------------------------------------
#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include "admndemo.h"
#include "sql.h"
#include "sqlext.h"
#include "ini.h"
#include "dialogs.h"
#include "odbcinst.h"
#include "errcheck.h"
#include "strings.h"
VSZFile;

//*---------------------------------------------------------------------------------
//|   Defines
//*---------------------------------------------------------------------------------
#define HLP_ADMNDEMO 50

typedef struct tagCMDLINE {
   BOOL        fOptions;                     // TRUE if there are options
   BOOL        fExit;                        // TRUE if we should exit when done
   char        szDriverConnect[100];         // String for SQLDriverConnect
   char        szExecuteFile[255];           // File to execute
   BOOL        fTrace;                       // TRUE to trace
   char        szTraceFile[255];             // File to log ODBC calls
   char        szTerm[10];                   // Stmt terminator for file
   int         cbStmt;                       // Maximum statement length
   } CMDLINE;

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
HWND              hwndFrame, hwndClient, hwndOutput;
HINSTANCE         hInst;
HENV              henv=NULL;
HMENU             hMenu=NULL, hSubMenu=NULL;
HICON             hIcon=NULL, hResultsIcon=NULL, hConnectIcon=NULL;
HFONT             hSmallEditFont;
RETCODE           retcode;
int               iHelpCnt=0;                // Tracks the
CHILDINFO FAR *   CreateCI=NULL;
char              OutStr[MAXBUFF];
char              szDirName[_MAX_PATH];
char              szDftFileFilter[MAXBUFF];
CMDLINE           cl;

// Following is an array of connections and a global to track the current
lpCHILDINFO       ConnectStructs[MAXCONNECTS];
lpCHILDINFO       lpActiveConn=NULL;
HWND              hwndCurMDIChild=NULL;

extern UWORD            uMenuIDs[NUM_MENU_FLAGS];
extern lpRESULTSINFO    lpActiveResults;

dCSEG(char) szFrameClass[]                   = "SAMDI:Frame";
dCSEG(char) szChildClass[]                   = "SACLASS:1";
dCSEG(char) szResultsClass[]                 = "SACLASS:2";
dCSEG(char) szWindowTitle[]                  = "ODBC Admin Demo";
dCSEG(char) szHELPFILE[]                     = "..\\doc\\ODBCSMPL.HLP";

dCSEG(char) szOPENFILE[]                     = "Open File";
dCSEG(char) szSAVEFILE[]                     = "Save File";
dCSEG(char) szUID[]                          =  "UID=";
dCSEG(char) szDSN[]                          =  "DSN=";
dCSEG(char) szNewLine[]                      =  "\r\n";
dCSEG(char) szComma[]                        =  ", ";

dCSEG(char) szUserAt[]                       =  "%s@%s";
dCSEG(char) szUserAtFile[]                   =  "%s@%s   File: %s";

//*---------------------------------------------------------------------------------
//|   Local functions
//*---------------------------------------------------------------------------------
BOOL WINAPI Init(HANDLE hInstance,   HANDLE hPrevInstance,
          LPSTR  lpszCmdLine, int    CmdShow);
BOOL WINAPI SetUp(LPSTR szCmdLine, CMDLINE FAR * cl);
void WINAPI CleanUp(void);
long EXTFUN WndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
long EXTFUN PASCAL ChildWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL EXTFUN PASCAL AboutWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);

BOOL WINAPI ParseCommandLine(LPSTR szCmdLine, CMDLINE FAR * cl);
BOOL WINAPI ParseOptionsValues(LPSTR instr, LPSTR szTerm, int cbTerm, int FAR * lpMax);
VOID WINAPI DoConnect(HWND hwnd, DWORD dwData);
BOOL WINAPI Connect(CHILDINFO FAR * ci, HWND hwnd, LPSTR szAutoConnect);
VOID WINAPI DoDisconnect(lpCHILDINFO lpci);
BOOL WINAPI Disconnect(CHILDINFO FAR * ci);
void WINAPI DisplayAbout(HWND hwnd, HINSTANCE hInst);
int  WINAPI DoMain(HANDLE hInstance);
HWND INTFUN GetEditWindow(CHILDINFO FAR * ci);

// File functions
VOID WINAPI DoFileOpen(lpCHILDINFO lpci);
VOID WINAPI DoFileSave(lpCHILDINFO lpci);
VOID WINAPI DoFileSaveAs(lpCHILDINFO lpci);

//*------------------------------------------------------------------------
//| WinMain:
//|     Parameters:
//|         hInstance     - Handle to current Data Segment
//|         hPrevInstance - Handle to previous Data Segment (NULL if none)
//|         lpszCmdLine   - Long pointer to command line info
//|         nCmdShow      - Integer value specifying how to start app.,
//|                            (Iconic [7] or Normal [1,5])
//*------------------------------------------------------------------------
int PASCAL WinMain (HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR  lpszCmdLine,
                    int    nCmdShow)
{
   int         nReturn;

   if (Init(hInstance, hPrevInstance,lpszCmdLine,nCmdShow)) {
      if(!SetUp(lpszCmdLine, &cl))        // Quit on errors
         return 0;
      nReturn = DoMain(hInstance);
      CleanUp();
   }
   return nReturn;
}

//*---------------------------------------------------------------------------------
//| Init:
//|   This function will take care of initilization for the application.
//| Parms:
//|   in       hInstance            Data segment for this instance
//|   in       hPrevInstance        Data segment for previous instance
//|   in       lpszCmdLine          Command line parameters
//|   in       nCmdShow             Desired display method
//| Returns:
//|   TRUE     if function was successful
//|   FALSE    if there was an error
//*---------------------------------------------------------------------------------
BOOL WINAPI Init(HANDLE hInstance,   HANDLE hPrevInstance,
          LPSTR  lpszCmdLine, int    nCmdShow)
{
   WNDCLASS wc;

   // Make sure the .ini file is there before creating anything
   ResolveIniFile();

   hInst = hInstance;

   if(!hPrevInstance) {
      // Register the Frame Window first
      wc.style = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc = (WNDPROC)WndProc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = 0;
      wc.hInstance = hInstance;
      hIcon = wc.hIcon = LoadIcon(hInstance, "BASEICON");
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = szFrameClass;
      if(!RegisterClass(&wc)) {
         PostError((LPSTR)szRegisterClassFailed);
         return FALSE;
      }

      // Register the child window class
      wc.style = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc = (WNDPROC)ChildWndProc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = sizeof(lpCHILDINFO FAR *);
      wc.hInstance = hInstance;
      hConnectIcon = wc.hIcon = LoadIcon(hInstance, "CONNECTICON");
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = szChildClass;
      if(!RegisterClass(&wc)) {
         PostError((LPSTR)szRegisterClassFailed);
         return FALSE;
      }

      // Register the results output window class
      wc.style = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc = (WNDPROC)ResultsWndProc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = sizeof(lpRESULTSINFO);
      wc.hInstance = hInstance;
      hResultsIcon = wc.hIcon = LoadIcon(hInstance, "RSLTICON");
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = szResultsClass;
      if(!RegisterClass(&wc)) {
         PostError((LPSTR)szRegisterClassFailed);
         return FALSE;
      }
   }

   hMenu = LoadMenu(hInstance, "BASEMENU");
   hSubMenu = GetSubMenu(hMenu, IDM_WINDOW_POS);

   hwndFrame = CreateWindow(szFrameClass,
                            szWindowTitle,
                            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            NULL,
                            hMenu,
                            hInstance,
                            NULL);

   hwndClient = GetWindow(hwndFrame, GW_CHILD);
   ShowWindow(hwndFrame,
              (GetPrivateProfileInt(szSCREEN, szMAX, 0, szLABINI) == 1)
              ? SW_SHOWMAXIMIZED : nCmdShow);
   UpdateWindow(hwndFrame);

   return (BOOL)(hwndFrame!=NULL);
}

//*------------------------------------------------------------------------
//| DoMain:
//|     This is the main loop for the application:
//*------------------------------------------------------------------------
int  WINAPI DoMain(HANDLE hInstance)
{
   MSG msg;
   HANDLE hAccel;

   SendMessage(hwndFrame, USER_INITAPP, 0, 0L);
   hAccel = LoadAccelerators(hInstance,"BASEACCELS");
   while(GetMessage(&msg, NULL, 0, 0)) {
      if(!TranslateAccelerator(hwndFrame,hAccel,&msg) &&
         !TranslateMDISysAccel(hwndClient, &msg)) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }

   return msg.wParam;
}

//*---------------------------------------------------------------------------------
//| DoCloseDown:
//|   Clean up all of the global windows, handles, etc...   After this function
//|   is called, the application may be killed off.
//| Parms:
//|   hwnd              The window handle being destoryed, send only on main
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID WINAPI DoCloseDown(HWND hwnd)
{
   char     szTempBuf[10];

   // Close all connection windows
   SendMessage(hwnd, WM_COMMAND, IDM_CLOSEALL, 0L);

   // Kill off the help file if we opened it.
   if(iHelpCnt)
      WinHelp(hwnd, szHELPFILE, HELP_QUIT, 0L);

   // Save our max/min state
   wsprintf(szTempBuf, "%d", IsZoomed(hwnd));
   WritePrivateProfileString(szSCREEN, szMAX, szTempBuf, szLABINI);

   // Destroy GDI objects
   DeleteObject(hSmallEditFont);

   DestroyWindow(hwnd);
}

//*------------------------------------------------------------------------
//| WndProc:
//|     Handle requests for action:
//*------------------------------------------------------------------------
long EXTFUN WndProc(HWND      hwnd,
                        unsigned msg,
                        WPARAM     wParam,
                        LPARAM     lParam)
{
   switch(msg) {
     case WM_CREATE:
      {
         LOGFONT                 lf;
         CLIENTCREATESTRUCT         clientcreate;

         memset(&lf, 0, sizeof(LOGFONT));
         lf.lfHeight = -10;
         lf.lfWeight = FW_THIN;
         lf.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
         lstrcpy(lf.lfFaceName, szMSSansSerif);
         hSmallEditFont = CreateFontIndirect(&lf);

         clientcreate.hWindowMenu = hSubMenu;
         clientcreate.idFirstChild = IDM_FIRSTCHILD;
         hwndClient = CreateWindow("MDICLIENT", NULL,
                                   WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS,
                                   0, 0, 0, 0, hwnd,
                                   (HMENU)(1), hInst,
                                   (LPSTR)&clientcreate);
         if(!hwndClient)
            return -1;
      }
      return 0;

      // This user message is sent to handle any first steps once the
      // application is up and ready for input.  We'll cause a connection
      // to be done.
     case USER_INITAPP:
      SendMessage(hwnd, WM_COMMAND, IDM_CONNECT,
                  (cl.fOptions) ? (LPARAM)(LPSTR)cl.szDriverConnect : 0L);
      if(lpActiveConn &&
         *cl.szExecuteFile)
         ExecuteFile(lpActiveConn, hwnd, cl.szExecuteFile, cl.szTerm, cl.cbStmt);
      if(cl.fExit)
         SendMessage(hwnd, WM_COMMAND, IDM_EXIT, 0L);
      return 0;

      //
      // Hande the WM_INITMENUPOPUP message so that when the user selects
      //    a menu, we enable/disable each item based on our current state.
      //
     case WM_INITMENUPOPUP:
      if(!(BOOL)HIWORD(lParam))        // Not the system menu
         ResetMenu(hMenu, (int)LOWORD(lParam));
      break;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
         //-------------------------------------------------------------------
         // The F6 key is a virtual key which toggles the cursor
         // location from the input/output window.
         //-------------------------------------------------------------------
        case IDM_MOVE_WINDOW:
         {
            if(lpActiveConn) {
               DWORD    dwActive=SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0L);
#ifndef WIN32
               HWND     Active = LOWORD(dwActive);
#else
               HWND     Active = (HWND)(dwActive);
#endif

               if(Active != lpActiveConn->hwnd) {
                  SendMessage(hwndClient, WM_MDIACTIVATE,
                              (WPARAM)(HWND)lpActiveConn->hwnd, 0L);
                  return 0;
               }
               if(GetFocus() == lpActiveConn->hwndIn)
                  SetFocus(lpActiveConn->hwndOut);
               else
                  SetFocus(lpActiveConn->hwndIn);
            }
         }
         return 0;

         //-------------------------------------------------------------------
         // Connect Menu
         //-------------------------------------------------------------------
        case IDM_CONNECT:
         DoConnect(hwnd, lParam);
         return 0;

        case IDM_DISCONNECT:
         DoDisconnect(lpActiveConn);
         return 0;

        case IDM_EXECUTE_FILE:
         ExecuteFile(lpActiveConn, hwnd, NULL, NULL, 0);
         return 0;

        case IDM_OPEN:
         DoFileOpen(lpActiveConn);
         return 0;

        case IDM_CLOSE:
         {
            // zzz:  Check for save if modified
            szWrite(lpActiveConn->hwndOut,
                    GetidsString(idsFileClosed, OutStr, sizeof(OutStr)),
                    (LPSTR)lpActiveConn->szFile);

            wsprintf((LPSTR)lpActiveConn->szClientTitle, szUserAt,
                     (LPSTR)lpActiveConn->CurrentUser, (LPSTR)lpActiveConn->CurrentServer);
            lpActiveConn->hFile = 0;
            ReleaseMemory(lpActiveConn->szFileBuff);
            SetWindowText(lpActiveConn->hwnd, lpActiveConn->szClientTitle);
         }
         return 0;

        case IDM_SAVE:
         DoFileSave(lpActiveConn);
         return 0;

        case IDM_SAVEAS:
         DoFileSaveAs(lpActiveConn);
         return 0;

        case IDM_EXIT:
         SendMessage(hwnd, WM_CLOSE, 0, 0L);
         return 0;

         //-------------------------------------------------------------------
         // Edit Menu
         //-------------------------------------------------------------------
        case IDM_UNDO:
        case IDM_CUT:
        case IDM_COPY:
        case IDM_PASTE:
         {
            HWND        fhwnd=GetEditWindow(lpActiveConn);

            if(fhwnd)
               SendMessage(fhwnd,
                           (wParam == IDM_CUT) ? WM_CUT :
                           (wParam == IDM_COPY) ? WM_COPY :
                           (wParam == IDM_PASTE) ? WM_PASTE : WM_UNDO,
                           0, 0L);
         }
         return 0;

        case IDM_SELECT_ALL:
         {
            HWND              fhwnd;

            if(!(fhwnd = GetEditWindow(lpActiveConn)))
               break;
            SetFocus(fhwnd);
            SendMessage(fhwnd, EM_SETSEL, 0, MAKELPARAM(0, -1));
         }
         return 0;

         //-------------------------------------------------------------------
         // Command Menu
         //-------------------------------------------------------------------
        case IDM_EXECUTE_NOW:
         ExecuteCmds(lpActiveConn, NULL);
         return 0;

        case IDM_COMMIT:
        case IDM_ROLLBACK:
         DoCommitRollback(lpActiveConn, wParam);
         return 0;

         //-------------------------------------------------------------------
         // Pipe Menu
         //-------------------------------------------------------------------
        case IDM_PIPE_EDIT:
         EditPipe(lpActiveConn);
         return 0;

        case IDM_PIPE_DO:
         HandlePipe(lpActiveConn, lpActiveResults);
         return 0;

         //-------------------------------------------------------------------
         // Info Menu
         //-------------------------------------------------------------------
        case IDM_MANAGE_DSNS:
         {
            HWND     fHwnd=GetFocus();

            SQLManageDataSources(hwnd);
            if(fHwnd)
               SetFocus(fHwnd);
         }
         return 0;

        case IDM_DATA_TYPES:
         DisplayODBCDataTypes(lpActiveConn);
         return 0;

        case IDM_FUNCTIONS:
         DisplayODBCFunctions(lpActiveConn);
         return 0;

        case IDM_DATA_SOURCES:
         DisplayODBCDataSources(lpActiveConn);
         return 0;

        case IDM_ODBC_INFO:
         DisplayGetInfo(lpActiveConn);
         return 0;

         //-------------------------------------------------------------------
         // Lists Menu
         //-------------------------------------------------------------------
        case IDM_TABLES:
        case IDM_COLUMNS:
        case IDM_STATISTICS:
        case IDM_PRIMARY_KEYS:
        case IDM_FOREIGN_KEYS:
        case IDM_TABLE_PRIVS:
        case IDM_COLUMN_PRIVS:
        case IDM_SPECIAL_COLUMNS:
        case IDM_PROCEDURES:
        case IDM_PROC_COLUMNS:
         DoList(lpActiveConn, wParam);
         return 0;

         //-------------------------------------------------------------------
         // Window Menu
         //-------------------------------------------------------------------
        case IDM_FONT:
         PromptForFontName(hwnd);
         return 0;

        case IDM_CASCADE:
         SendMessage(hwndClient, WM_MDICASCADE, 0, 0L);
         return 0;

        case IDM_TILE:
         SendMessage(hwndClient, WM_MDITILE, 0, 0L);
         return 0;

        case IDM_ARRANGE:
         SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0L);
         return 0;

        case IDM_CLOSEALL:
         {
            int         idex;
            int         iNum=NumItems(ConnectStructs);

            for(idex=0;  idex<iNum;  idex++)
               if(ConnectStructs[idex] != NULL)
                  DoDisconnect(ConnectStructs[idex]);
         }
         return 0;

         //-------------------------------------------------------------------
         // Help Menu
         //-------------------------------------------------------------------
        case IDM_HELP:
         iHelpCnt += WinHelp(hwnd, szHELPFILE, HELP_INDEX, 0L);
         return 0;

        case IDM_ABOUT:
         DisplayAbout(hwnd, hInst);
         return 0;
      }
      break;

case WM_CLOSE:
      DoCloseDown(hwnd);
      return 0;

     case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

     default:
      break;
   }

   return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}

//*------------------------------------------------------------------------
//| ChildWndProc:
//|     Handle all messages which are received for the MDI child
//|         windows.
//*------------------------------------------------------------------------
long EXTFUN ChildWndProc(HWND    hwnd,
                        unsigned msg,
                        WPARAM     wParam,
                        LPARAM     lParam)
{
   switch(msg) {
      //
      // When a successful connection is made, then we are called to
      //    create a window to handle it.  The CHILDINFO structure has
      //    already been allocated.
      //
     case WM_CREATE:
      {
         CREATESTRUCT FAR *      cs;
         MDICREATESTRUCT FAR *   mdi;
         lpCHILDINFO             lpci;

         cs = (CREATESTRUCT FAR *)lParam;
         mdi = (MDICREATESTRUCT FAR *)cs->lpCreateParams;
         lpci = (lpCHILDINFO)mdi->lParam;

         SETCIPOINTER(hwnd, lpci);

         lpci->hwnd = hwnd;
         lpci->hInst = hInst;
         hwndClient = GetParent(hwnd);
         hwndFrame = GetParent(hwndClient);
         if(!(lpci->hwndIn = CreateWindow("edit", NULL,
                                          WS_CHILD | WS_VISIBLE | WS_VSCROLL |
                                          WS_BORDER | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL,
                                          0, 0, 0, 0,
                                          hwnd, (HMENU)(3), hInst, NULL)))
            return -1;
         if(!(lpci->hwndOut = CreateWindow("edit", NULL,
                                           WS_CHILD | WS_VISIBLE | WS_VSCROLL |
                                           WS_BORDER | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL,
                                           0, 0, 0, 0,
                                           hwnd, (HMENU)(4), hInst, NULL)))
            return -1;

         SendMessage(lpci->hwndIn, WM_SETFONT,
                     (WPARAM)hSmallEditFont, (LPARAM)MAKELONG((WORD)TRUE, 0));
         SendMessage(lpci->hwndOut, WM_SETFONT,
                     (WPARAM)hSmallEditFont, (LPARAM)MAKELONG((WORD)TRUE, 0));

         szWrite(lpci->hwndOut,
                 GetidsString(idsSuccessConnect, OutStr, sizeof(OutStr)),
                 (LPSTR)lpci->CurrentUser, (LPSTR)lpci->CurrentServer);
      }
      return 0;

      //
      // NOTE:  We must break to return DefMDIWndProc in order for the menu to
      //    be redrawn when we maximize the child window.
      //
     case WM_SIZE:
      {
         lpCHILDINFO       lpci;
         int               di, dot;

         lpci = GETCIPOINTER(hwnd);

         lpci->dx = LOWORD(lParam);
         lpci->dy = HIWORD(lParam);
         di = lpci->dy * 1 / 2;
         dot = lpci->dy - di;
         MoveWindow(lpci->hwndIn, 0, 0, lpci->dx, di, TRUE);
         MoveWindow(lpci->hwndOut, 0, di, lpci->dx, dot, TRUE);
      }
      break;

      //
      // Handle the WM_INITMENUPOPUP message so that when the user selects
      //    a menu, we enable/disable each item based on our current state.
      //
     case WM_INITMENUPOPUP:
      if(!(BOOL)HIWORD(lParam))        // Not the system menu
         ResetMenu((HMENU)wParam, (int)LOWORD(lParam));
      break;

      //
      // All messages are handled in the main wnd proc, so pass them back
      //
     case WM_COMMAND:
      {
         UINT        id=GET_WM_COMMAND_ID(wParam, lParam);

         if(id >= IDM_CONNECT &&
            id <= IDM_MOVE_WINDOW)
            SendMessage(hwndFrame, WM_COMMAND, wParam, lParam);
      }
      break;

      //
      // The WM_MDIACTIVATE message is received first by the child window
      //    which is losing focus, then by the window which is receiving
      //    focus.
      //
     case WM_MDIACTIVATE:
      {
#ifndef WIN32
         if(wParam) {
            lpActiveConn = GETCIPOINTER((HWND)LOWORD(lParam));
         }
#else
         if((HWND)lParam == hwnd) {
            lpActiveConn = GETCIPOINTER((HWND)lParam);
         }
#endif
         else
            lpActiveConn = NULL;

         if(lpActiveConn) {
            hwndCurMDIChild = lpActiveConn->hwnd;
            SetFocus(lpActiveConn->hwndIn);
         }
      }
      return 0;

      //
      // Check for a system close command.  This is the equivalent of a
      //    Disconnect, so process it as such.
      //
     case WM_SYSCOMMAND:
      {
         if(wParam == SC_CLOSE) {
            SendMessage(hwnd, WM_COMMAND, IDM_DISCONNECT, 0L);
            return 0;
         }
      }
      break;

     default:
      break;
   }

   return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

//*---------------------------------------------------------------------------------
//| SetUp:
//|   This function handles one-time setup operations for the app.  This includes
//|   allocating an environment handle for ODBC, and processing the command
//|   line.
//| Parms:
//|   szCmdLine         Command line arguments
//|   cl                Command line strucutre for return
//| Returns:
//|   FALSE if there was an error
//*---------------------------------------------------------------------------------
BOOL WINAPI SetUp(LPSTR szCmdLine, CMDLINE FAR * cl)
{
   LPSTR str;

   // Load default resource strings
   GetidsString(idsDftFileFilter, szDftFileFilter, sizeof(szDftFileFilter));
   str = (LPSTR)szDftFileFilter;
   while((str = strchr(str, '|')))
      *str++ = '\0';

   if(SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HENV, &henv)) {
      szMessageBox(GetActiveWindow(),
                   MB_ICONEXCLAMATION,
                   (LPSTR)szErrTitle,
                   GetidsString(idsSQLAllocHandleFailed, OutStr, sizeof(OutStr)));
      return FALSE;
   }

   if(SQL_SUCCESS != SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                                   (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER)) {
      DisplayErrors(NULL, GetidsString(idsSQLSetEnvAttr, OutStr, sizeof(OutStr)),
                    SQL_HANDLE_ENV, henv);
      return FALSE;
   }

   if(!ParseCommandLine(szCmdLine, cl))
      return FALSE;

   if(cl->fTrace) {
      if(SQL_SUCCESS !=
         (retcode = SQLSetConnectAttr(NULL, SQL_ATTR_TRACEFILE,
                                      (LPSTR)cl->szTraceFile, SQL_NTS)))
         DisplayErrors(NULL, GetidsString(idsSQLSetConnectAttr, OutStr, sizeof(OutStr)),
                       SQL_HANDLE_ENV, henv);

      if(SQL_SUCCESS !=
         (retcode = SQLSetConnectAttr(NULL, SQL_ATTR_TRACE,
                                      (SQLPOINTER) SQL_TRUE,
                                      SQL_IS_INTEGER)))
         DisplayErrors(NULL, GetidsString(idsSQLSetConnectAttr, OutStr, sizeof(OutStr)),
                       SQL_HANDLE_ENV, henv);
   }

   return TRUE;
}

//*------------------------------------------------------------------------
//| CleanUp:
//|     Any last-minute application cleanup activities are done here:
//*------------------------------------------------------------------------
void WINAPI CleanUp(void)
{
   retcode = SQLFreeHandle(SQL_HANDLE_ENV, henv);

#ifdef DEBUGGING
   {
      retcode = SQLSetConnectAttr(NULL, SQL_ATTR_TRACE, SQL_FALSE, SQL_IS_INTEGER);
      WinAssert(retcode == SQL_SUCCESS, (LPSTR)"SetConnectAttr: SQL_ATTR_TRACE");
   }
#endif
}

//*---------------------------------------------------------------------------------
//| ParseCommandLine:
//|   This function will break down the command line and look for common elements.
//| Parms:
//|   instr             The input string to look at
//|   cl                Command line strucutre to store info
//| Returns:
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL WINAPI ParseCommandLine(LPSTR instr, CMDLINE FAR * cl)
{
   LPSTR    str=instr;
   LPSTR    endstr=str;

   if(!str)
      return TRUE;

   // Assume some defaults
   memset(cl, 0, sizeof(CMDLINE));
   cl->cbStmt = 1000;
   lstrcpy(cl->szTerm, (LPSTR)"\r\n");
   cl->fExit = FALSE;

   // Look for each / value.  Spaces are allowed.  Any other character
   // at this level is a syntax error
   while(endstr && *str)
      switch(*str) {
         // We have encountered a command line switch.  See which it is
        case '/':
        case '-':
         ++str;
         switch(*str) {
            // /D[] for driver connection string
           case 'D':
           case 'd':
            ++str;
            if(*str != '[' ||
               !(endstr = strchr(str, ']')))
               goto tokenerr;
            *endstr = '\0';
            if(lstrlen(cl->szDriverConnect) > sizeof(cl->szDriverConnect))
               goto toobig;
            lstrcpy(cl->szDriverConnect, str + 1);
            str = endstr + 1;
            cl->fOptions = TRUE;
            break;

            // /F for file name to execute
           case 'F':
           case 'f':
            if((endstr = strchr(++str, ' ')))
               *endstr = '\0';
            if(lstrlen(cl->szExecuteFile) > sizeof(cl->szExecuteFile))
               goto toobig;
            lstrcpy(cl->szExecuteFile, str);
            str = endstr + 1;
            break;

            // /O for options
           case 'O':
           case 'o':
            if((endstr = strchr(++str, ' ')))
               *endstr = '\0';
            if(!ParseOptionsValues(str, cl->szTerm, sizeof(cl->szTerm), &cl->cbStmt))
               goto tokenerr;
            str = endstr + 1;
            break;

            // /T for a trace file name
           case 'T':
           case 't':
            if((endstr = strchr(++str, ' ')))
               *endstr = '\0';
            if(lstrlen(cl->szTraceFile) > sizeof(cl->szTraceFile))
               goto toobig;
            lstrcpy(cl->szTraceFile, str);
            str = endstr + 1;
            cl->fTrace = TRUE;
            break;

            // /X for exit option
           case 'X':
           case 'x':
            ++str;
            if(str &&
               (*str == 'Y' || *str == 'y'))
               cl->fExit = TRUE;
            else if(*str == 'N' || *str == 'n')
               cl->fExit = FALSE;
            else
               goto tokenerr;
            ++str;
            break;

            // Not a valid argument, error
           default:
            goto tokenerr;
         }
         break;   // End of '/' or '-'

        case ' ':
         ++str;
         break;

        default:
         goto tokenerr;
      }
   return TRUE;

  tokenerr:
   szMessageBox(GetActiveWindow(),
                MB_ICONEXCLAMATION,
                (LPSTR)szErrTitle,
                GetidsString(idsInvalidCmdLine, OutStr, sizeof(OutStr)));
   return FALSE;

  toobig:
   szMessageBox(GetActiveWindow(),
                MB_ICONEXCLAMATION,
                (LPSTR)szErrTitle,
                GetidsString(idsCmdLineTooBig, OutStr, sizeof(OutStr)));
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| ParseOptionsValues:
//|   This function breaks down the /O switch by returning the invidual elements.
//| Parms:
//|   instr             The string to parse
//|   szTerm            Terminator value
//|   cbTerm            Max for terminator value
//|   lpMax             Maximum size for statement execution
//| Returns:
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL WINAPI ParseOptionsValues(LPSTR instr, LPSTR szTerm, int cbTerm, int FAR * lpMax)
{
   LPSTR str=instr;
   LPSTR endstr=instr;
   char  cApost = '\'';

   // Now look for user specific values
   while(endstr && *str)
      switch(*str) {
         // Found the terminator
        case 'T':
        case 't':
         // Could be a Carriage return or a string
         ++str;
         if(*str == cApost) {
            if(!(endstr = strchr(++str, cApost)))
               return FALSE;
            *endstr = '\0';
            if(lstrlen(str) > cbTerm)
               return FALSE;
            lstrcpy(szTerm, str);
            str = endstr + 1;
         }
         else if(strncmp(str, "CR", 2) != 0)
            return FALSE;
         else {
            lstrcpy(szTerm, (LPSTR)"\r\n");
            str += 2;
         }
         break;

         // Found the Maximum length value
        case 'M':
        case 'm':
         ++str;
         if((endstr = strchr(str, ':')))
            *endstr = '\0';
         *lpMax = lpatoi(str);
         if(*lpMax < MINSTMTSIZE ||
            *lpMax > MAXSTMTSIZE)
            return FALSE;
         str = endstr + 1;
         break;

         // Ignore delimiter
        case ':':
         ++str;
         break;

         // This is an error
        default:
         return FALSE;
         break;
      }
   return TRUE;
}

//*---------------------------------------------------------------------------------
//| DoConnect:
//|   Handle the structures involved in creating a connection window.  Invoke the
//|   Connect function to do the actual ODBC work.
//| Parms:
//|   hwnd              The parent window handle for the new child
//|   dwData            Indicates a start code or string.
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID WINAPI DoConnect(HWND hwnd, DWORD dwData)
{
   lpCHILDINFO       citmp;
   MDICREATESTRUCT   mdicreate;
   int               idex;

   for(idex=0;  idex<MAXCONNECTS;  idex++)
      if(!ConnectStructs[idex])
         break;

   // See if we have exceeded the max connects
   if(idex >= MAXCONNECTS) {
      szMessageBox(hwnd,
                   MB_ICONINFORMATION | MB_OK,
                   (LPSTR)szErrTitle,
                   GetidsString(idsTooManyConnects, OutStr, sizeof(OutStr)),
                   MAXCONNECTS);
      return;
   }

   citmp = (lpCHILDINFO)GetMemory(sizeof(CHILDINFO));
   if(!citmp)
      return;

   memset(citmp, 0, sizeof(CHILDINFO));

   if(Connect(citmp, hwnd,
              (HIWORD(dwData) == 1) ? NULL : (LPSTR)dwData)) {  // Pass NULL if accelerator used
      // We are connected, save the handle
      ConnectStructs[idex] = citmp;

      // Now fill out the rest
      citmp->hwndClient = hwndClient;
      citmp->henv = henv;                    // Keep local copy of global

      wsprintf((LPSTR)citmp->szClientTitle, szUserAt, (LPSTR)citmp->CurrentUser,
               (LPSTR)citmp->CurrentServer);
      mdicreate.szClass = szChildClass;
      mdicreate.szTitle = citmp->szClientTitle;
      mdicreate.hOwner  = hInst;
      mdicreate.x       = CW_USEDEFAULT;
      mdicreate.y       = CW_USEDEFAULT;
      mdicreate.cx      = CW_USEDEFAULT;
      mdicreate.cy      = CW_USEDEFAULT;
      mdicreate.style   = (hwndCurMDIChild) ? ((IsZoomed(hwndCurMDIChild)) ? WS_MAXIMIZE : 0) : 0;
      mdicreate.lParam  = (LPARAM)citmp;
      SendMessage(hwndClient, WM_MDICREATE, 0,
                  (LPARAM)(LPMDICREATESTRUCT)&mdicreate);
   }
   else {
      // No connect made, so release all memory
      retcode = SQLFreeHandle(SQL_HANDLE_DBC, citmp->hdbc);
      ReleaseMemory(citmp);
   }
}

//*---------------------------------------------------------------------------------
//| Connect:
//|   This function attempts to make a connection.  If the user has specified the
//|   /D command line option, then we will simply use the connection string.
//| Parms:
//|   ci                Connection window information
//|   hwnd              Window handle for any prompting
//|   szAutoConnect     NULL if prompt, else pointer to szConnStrIn
//| Returns:
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL WINAPI Connect(CHILDINFO FAR * ci, HWND hwnd, LPSTR szAutoConnect)
{
   SWORD       cb;
   RETCODE     rc;
   UWORD       fExists;
   int         idex;

   if(SQL_SUCCESS != (retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &ci->hdbc))) {
      szMessageBox(GetActiveWindow(),
                   MB_ICONEXCLAMATION | MB_OK,
                   (LPSTR)szErrTitle,
                   GetidsString(idsSQLAllocHandleFailed, OutStr, sizeof(OutStr)));
      return FALSE;
   }

   // Do the connection
   if(!szAutoConnect)
      retcode = SQLDriverConnect(ci->hdbc, hwnd, NULL, 0, ci->szConnStrOut,
                                 MAXBUFF, &cb, SQL_DRIVER_COMPLETE);
   else
      retcode = SQLDriverConnect(ci->hdbc, hwnd, szAutoConnect, SQL_NTS,
                                 ci->szConnStrOut, MAXBUFF, &cb, SQL_DRIVER_NOPROMPT);
   if(retcode == SQL_NO_DATA)
      return FALSE;
   else if(retcode == SQL_ERROR) {
      PrintErrors(ci, SQL_HANDLE_DBC);
      return FALSE;
   }

   if(RC_SUCCESSFUL(retcode)) {
      SWORD len;

      retcode = SQLGetInfo(ci->hdbc, SQL_USER_NAME, ci->CurrentUser,
                           sizeof(ci->CurrentUser), &len);
      if(retcode == SQL_ERROR || len == 0)   // Assume driver not capable
         *ci->CurrentUser = '\0';

      retcode = SQLGetInfo(ci->hdbc, SQL_DATA_SOURCE_NAME, ci->CurrentServer,
                           sizeof(ci->CurrentServer), NULL);
   }

   if(SQL_SUCCESS != (retcode = SQLAllocHandle(SQL_HANDLE_STMT, ci->hdbc, &ci->hstmt))) {
      szMessageBox(GetActiveWindow(),
                   MB_ICONEXCLAMATION,
                   (LPSTR)szErrTitle,
                   GetidsString(idsSQLAllocHandleFailed, OutStr, sizeof(OutStr)));
      return FALSE;
   }

   //
   // Find out what functions are supported and store them in a bitmask
   //
   memset(&ci->fFunctions, 0, sizeof(ci->fFunctions));
   for(idex=0;  idex<NUM_MENU_FLAGS;  idex++) {
      rc = SQLGetFunctions(ci->hdbc,
                           uMenuIDs[idex],
                           &fExists);
      if(RC_NOTSUCCESSFUL(rc)) {
         fExists = FALSE;
         PrintErrors(ci, SQL_HANDLE_DBC);
      }

      if(fExists)
         BitSet(ci->fFunctions, idex);
   }

   return TRUE;
}

int WINAPI GetConnDex(lpCHILDINFO lpci)
{
   int idex;

   for(idex=0;  idex<MAXCONNECTS;  idex++)
      if(lpci == ConnectStructs[idex])
         return idex;

   return -1;
}

//*---------------------------------------------------------------------------------
//| DoDisconnect:
//|   Look for the handle passed in and then disconnect it.  The Disconnect function
//|   is used to do the ODBC portion.
//| Parms:
//|   lpci              Connect window to free
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID WINAPI DoDisconnect(lpCHILDINFO lpci)
{
   int         idex;

   idex = GetConnDex(lpci);
   if(idex == -1)
      return;

   if(Disconnect(lpci)) {
      FreeConnectWindowResults(lpci);
      SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)(HWND)lpci->hwnd, 0L);
      ConnectStructs[idex] = NULL;
   }
}

//*------------------------------------------------------------------------
//| Disconnect:
//|     Make a connection to a server.
//*------------------------------------------------------------------------
BOOL WINAPI Disconnect(CHILDINFO FAR * ci)
{
   if(SQL_SUCCESS != (retcode = SQLFreeHandle(SQL_HANDLE_STMT, ci->hstmt)))
      DisplayErrors(ci->hwnd, GetidsString(idsSQLFreeHandle, OutStr, sizeof(OutStr)),
                    SQL_HANDLE_STMT, ci->hstmt);
   if(SQL_SUCCESS != (retcode = SQLDisconnect(ci->hdbc)))
      DisplayErrors(ci->hwnd, GetidsString(idsSQLDisconnect, OutStr, sizeof(OutStr)),
                    SQL_HANDLE_DBC, ci->hdbc);
   if(SQL_SUCCESS != (retcode = SQLFreeHandle(SQL_HANDLE_DBC, ci->hdbc)))
      DisplayErrors(ci->hwnd, GetidsString(idsSQLFreeHandle, OutStr, sizeof(OutStr)),
                    SQL_HANDLE_DBC, ci->hstmt);

   ci->hstmt = ci->hdbc = NULL;
   szWrite(ci->hwndOut,
           GetidsString(idsDisconnectSuccess, OutStr, sizeof(OutStr)));

   return TRUE;
}

//*------------------------------------------------------------------------
//| DisplayAbout:
//|     Display the about box for this app.
//*------------------------------------------------------------------------
void WINAPI DisplayAbout(HWND hwnd, HINSTANCE hInst)
{
   HWND     fHwnd=GetFocus();

   if(-1 == DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, (DLGPROC)AboutWndProc,
                           (LPARAM)(HINSTANCE FAR * )&hInst))
      MessageBox(hwnd, "Could not open dialog box.",
                 "About", MB_ICONEXCLAMATION);

   if(fHwnd)
      SetFocus(fHwnd);
}

//*------------------------------------------------------------------------
//| AboutWndProc:
//|     Handle the About messages
//*------------------------------------------------------------------------
BOOL EXTFUN AboutWndProc(HWND    hDlg,
                              unsigned msg,
                              WPARAM     wParam,
                              LPARAM     lParam)
{
   int x=10,y=30;
   static HINSTANCE FAR *  lpHINST;

   switch(msg) {
     case WM_INITDIALOG:
      lpHINST = (HINSTANCE FAR *)lParam;
      CenterDialog(hDlg);
      return TRUE;

     case WM_COMMAND:
      SendMessage(hDlg, WM_CLOSE, 0, 0L);
      return TRUE;

     case WM_CLOSE:
      EndDialog(hDlg, FALSE);
      return TRUE;

     default:
      return FALSE;
   }
}

//*------------------------------------------------------------------------
//| Busy:
//|     Display the hour glass cursor when we are working
//|         flag = TRUE means we are busy to so display it
//|         flag = FALSE means go back to old cursor
//*------------------------------------------------------------------------
void Busy(int flag)
{
   static HCURSOR hCursor;                // Save old cursor handle

   ShowCursor(FALSE);
   if(flag)
      hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
   else
      SetCursor(hCursor);
   ShowCursor(TRUE);
}

//*---------------------------------------------------------------------------------
//| iLoadString:
//|   This function will retrieve a string from our resource fork and place it
//|      in the buffer provided.  If the string is not found, an empty string
//|      is returned.
//| Parms:
//|   in       id                   String number to retrieve
//|   in       str                  Output string
//|   in       len                  Lenth of possible output
//| Returns:
//|   Pointer to buffer str
//*---------------------------------------------------------------------------------
LPSTR iLoadString(int id, LPSTR str, int len)
{
   if(!str ||
      len <= 0)
      return str;

   if(!LoadString(hInst, id, str, len))
      *str = '\0';
   return str;
}

//*---------------------------------------------------------------------------------
//| GetEditWindow:
//|   This function will get the current focus window and verify that it is
//|   an edit window which is valid for one of the Edit menu items.
//| Parms:
//|   lpci                 Connection window information
//| Returns:
//|   Edit window handle if valid, NULL if not valid
//*---------------------------------------------------------------------------------
HWND INTFUN GetEditWindow(lpCHILDINFO lpci)
{
   HWND hwnd;
   hwnd = GetFocus();

   if(hwnd && lpci) {
      if(hwnd == lpci->hwndIn ||
         hwnd == lpci->hwndOut)
         return hwnd;
   }

   return NULL;
}

//*------------------------------------------------------------------------
//| CenterDialog:
//|      Center the dialog over the parent window.  It is possible that
//|         their is not a parent window, since the Translate DLL was
//|         never meant to display data.  For this reason, we may just
//|         center over the entire screen.
//*------------------------------------------------------------------------
void WINAPI  CenterDialog(HWND hdlg)
{
   RECT  rcParent;                         // Parent window client rect
   RECT  rcDlg;                            // Dialog window rect
   int   nLeft, nTop;                      // Top-left coordinates
   int   cWidth, cHeight;                  // Width and height
   HWND  hwnd;

   // Get frame window client rect in screen coordinates
   if((hwnd = GetParent(hdlg)) == NULL) {
      rcParent.top = rcParent.left = 0;
      rcParent.right = GetSystemMetrics(SM_CXFULLSCREEN);
      rcParent.bottom = GetSystemMetrics(SM_CYFULLSCREEN);
   }
   else
      GetWindowRect(hwnd, &rcParent);

   // Determine the top-left point for the dialog to be centered
   GetWindowRect(hdlg, &rcDlg);
   cWidth  = rcDlg.right  - rcDlg.left;
   cHeight = rcDlg.bottom - rcDlg.top;
   nLeft   = rcParent.left +
      (((rcParent.right  - rcParent.left) - cWidth ) / 2);
   nTop    = rcParent.top  +
      (((rcParent.bottom - rcParent.top ) - cHeight) / 2);
   if (nLeft < 0) nLeft = 0;
   if (nTop  < 0) nTop  = 0;

   // Place the dialog
   MoveWindow(hdlg, nLeft, nTop, cWidth, cHeight, TRUE);
   return;
}

//*---------------------------------------------------------------------------------
//| DoFileOpen:
//|   Use the commond dialog functions to open a file.
//| Parms:
//|   lpci                    Connection window information
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID WINAPI DoFileOpen(lpCHILDINFO lpci)
{
   OPENFILENAME      ofn;
   HWND              fHwnd=GetFocus();

   // If user has a file open already, verify that we should continue
   if(lpci->hFile) {
      if(IDYES == szMessageBox(lpci->hwnd,
                               MB_YESNO,
                               (LPSTR)szOPENFILE,
                               GetidsString(idsCloseFile, OutStr, sizeof(OutStr)),
                               (LPSTR)lpci->szFile))
         SendMessage(lpci->hwnd, WM_COMMAND, IDM_CLOSE, 0L);
      else
         return;
   }

   // Now ask to open a file
   lpci->szFileBuff = (LPSTR)GetMemory(MAXFILEBUFF);
   if(!lpci->szFileBuff)
      return;

   if(!*szDirName)
      GetWindowsDirectory(szDirName, sizeof(szDirName));

   // Fill out the struct
   memset(&ofn, 0, sizeof(OPENFILENAME));
   lpci->szFile[0] = '\0';
   ofn.lStructSize =          sizeof(OPENFILENAME);
   ofn.hwndOwner =            lpci->hwnd;
   ofn.lpstrFilter =          szDftFileFilter;
   ofn.nFilterIndex =         1;
   ofn.lpstrFile =            lpci->szFile;
   ofn.nMaxFile =             sizeof(lpci->szFile);
   ofn.lpstrInitialDir =      szDirName;
   ofn.Flags =                OFN_HIDEREADONLY |
      OFN_PATHMUSTEXIST |
         OFN_FILEMUSTEXIST;

   // Try to open the file
   if(GetOpenFileName(&ofn)) {
      if(fHwnd)
         SetFocus(fHwnd);

      if(HFILE_ERROR == (lpci->hFile = _lopen(ofn.lpstrFile, OF_READ))) {
         szMessageBox(lpci->hwnd,
                      MB_ICONEXCLAMATION,
                      (LPSTR)szOPENFILE,
                      GetidsString(idsOpenFileFailed, OutStr, sizeof(OutStr)),
                      (LPSTR)ofn.lpstrFile);
         return;
      }

      lpci->cbFileSize = _lread(lpci->hFile,
                                (void FAR *)lpci->szFileBuff, MAXFILEBUFF);
      _lclose(lpci->hFile);
      szWrite(lpci->hwndIn, (LPSTR)lpci->szFileBuff);
      SendMessage(lpci->hwndIn, EM_SETMODIFY, FALSE, 0L);
      wsprintf((LPSTR)lpci->szClientTitle, szUserAtFile,
               (LPSTR)lpci->CurrentUser, (LPSTR)lpci->CurrentServer,
               (LPSTR)ofn.lpstrFile);
      SetWindowText(lpci->hwnd, lpci->szClientTitle);
      GetNewDirectory(szDirName, ofn.lpstrFile);
   }

   if(fHwnd)
      SetFocus(fHwnd);
}

//*---------------------------------------------------------------------------------
//| DoSave:
//|   Save the current file.
//| Parms:
//|   lpci                    Connection window information
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID WINAPI DoFileSave(lpCHILDINFO lpci)
{
   OFSTRUCT       ofs;

   ofs.cBytes = sizeof(OFSTRUCT);

   lpci->cbFileSize = GetWindowText(lpci->hwndIn,
                                    (LPSTR)lpci->szFileBuff, MAXFILEBUFF);
   if(HFILE_ERROR == (lpci->hFile =
                      OpenFile(lpci->szFile, &ofs, OF_CREATE))) {
      szMessageBox(lpci->hwnd,
                   MB_ICONEXCLAMATION,
                   (LPSTR)szSAVEFILE,
                   GetidsString(idsSaveFileFailed, OutStr, sizeof(OutStr)),
                   (LPSTR)lpci->szFile);
      return;
   }

   _lwrite(lpci->hFile, (void FAR *)lpci->szFileBuff, lstrlen(lpci->szFileBuff));
   _lclose(lpci->hFile);
}

//*---------------------------------------------------------------------------------
//| DoFileSaveAs:
//|   Use the commond dialog functions to save a file.
//| Parms:
//|   lpci                    Connection window information
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
VOID INTFUN DoFileSaveAs(lpCHILDINFO lpci)
{
   OPENFILENAME         ofn;
   HWND                 fHwnd=GetFocus();
   char                 szSaveFile[_MAX_PATH];

   // Fill out struct
   szSaveFile[0] = '\0';
   memset(&ofn, 0, sizeof(OPENFILENAME));
   ofn.lStructSize =          sizeof(OPENFILENAME);
   ofn.hwndOwner =            lpci->hwnd;
   ofn.lpstrFilter =          szDftFileFilter;
   ofn.nFilterIndex =         1;
   ofn.lpstrFile =            szSaveFile;
   ofn.nMaxFile =             sizeof(lpci->szFile);
   ofn.lpstrInitialDir =      szDirName;
   ofn.Flags =                OFN_HIDEREADONLY |
      OFN_PATHMUSTEXIST |
         OFN_OVERWRITEPROMPT;

   // See if user selects a file
   if(GetSaveFileName(&ofn)) {
      lstrcpy(lpci->szFile, szSaveFile);
      wsprintf((LPSTR)lpci->szClientTitle, szUserAtFile,
               (LPSTR)lpci->CurrentUser, (LPSTR)lpci->CurrentServer,
               (LPSTR)szSaveFile);
      SetWindowText(lpci->hwnd, lpci->szClientTitle);
      DoFileSave(lpci);
   }

   if(fHwnd)
      SetFocus(fHwnd);
}


ADMNDEMO.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//*---------------------------------------------------------------------------------
#ifndef satool_DEFS
#define satool_DEFS

#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include <commdlg.h>
#include "child.h"
#include "menu.h"
#include "standard.h"
#include "errcheck.h"
#include "results.h"
#include "execute.h"
#include "info.h"
#include "ctl3d.h"

//------------------------------------------------------------------------
//  Defines
//------------------------------------------------------------------------
typedef  void FAR * lpVOID;
typedef BOOL (FAR PASCAL *FPROC)(void FAR * parm);

#define  TABLES            1
#define  PROCS             2

#define  IDD_ABOUTBOX      2000

#define USER_INITAPP       (WM_USER + 100)
#define USER_RESETHANDLE   (WM_USER + 101)

#define  IDB_ABOUTOK       200
#define  IDBIT_PICT        201

#define  CONNECTION_WINDOW    1
#define  RESULTS_WINDOW       2

#define  DISCONNECTED      0
#define  CONNECTED         1

#define VSZFile static char vszFile[]=__FILE__;

#define GetText(h,s)                                                    \
   {                                                                    \
   SendMessage(h, WM_GETTEXT,                                           \
               (WORD)SendMessage(h, WM_GETTEXTLENGTH, 0, 0L) + 1,       \
               (LPARAM)(LPSTR)s);                                       \
   }
#define IsRadioButtonOn(hwnd) SendMessage(hwnd, BM_GETCHECK, 0, 0L)

extern dCSEG(char) szOPENFILE[];

//------------------------------------------------------------------------
//  Declare function prototypes
//------------------------------------------------------------------------
void Busy(int flag);
LPSTR iLoadString(int id, LPSTR str, int len);
void FAR PASCAL CenterDialog(HWND hdlg);
HWND INTFUN GetEditWindow(CHILDINFO FAR * ci);

#endif


CHILD.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   CHILD.H
//|      This files sole purpose in life is to define the CHILDINFO type
//|         which is the main data structure for a connection window.
//*---------------------------------------------------------------------------------
#ifndef child_DEFS
#define child_DEFS

#include <Windows.h>
#include <stdlib.h>
#include "commdlg.h"
#include "standard.h"
#include "menu.h"
#include "results.h"
#include "sql.h"
#include "sqlext.h"

#define MAXCONNECTS        15

//*------------------------------------------------------------------------
//|  The following structure is used to keep track of the vital pieces of
//|      information for each child MDI window.  The actual structure is
//|      stored with the window and can be extracted with only the window
//|      handle.
//*------------------------------------------------------------------------
typedef struct tagCHILDINFO {
   HINSTANCE      hInst;                        // Handle to our data segment
   HWND           hwndClient;                   // Handle to client window
   HWND           hwnd;                         // Handle to the child window
   HWND           hwndIn;                       // Input window
   HWND           hwndOut;                      // Output window
   HENV           henv;                         // Environment handle for application
   HDBC           hdbc;                         // Connection handle of this window
   HSTMT          hstmt;                        // Statement handle for this window

   // Display information
   int            dx;                           // Width of client area
   int            dy;                           // Height of client area

   char           szConnStrOut[DFTBUFFSIZE];    // Save returned connect string
   char           CurrentUser[DFTBUFFSIZE];     // Name of user logged on this window
   char           CurrentServer[DFTBUFFSIZE];   // Server connected to in this window
   int            listtype;                     // What are we listing
   char           szClientTitle[MAXBUFF];       // Title for the window
   UDWORD         fFunctions[MINREQUIREDINTS(NUM_MENU_FLAGS)];
                                                // bitmask to track supported functions
   HFILE          hFile;                        // Pointer to file
   char           szFile[_MAX_PATH];            // Name of the file
   int            cbFileSize;                   // How big is the file
   LPSTR          szFileBuff;                   // Memory for the file
   int            cbResultCount;                // Incremental counter of results windows
   int            cbMaxRS;                      // Maximum results windows
   int            cbResults;                    // Current count of results sets
   LPVOID         lprihead;                     // Head node
   LPVOID         lpritail;                     // Tail node
   } CHILDINFO, FAR * lpCHILDINFO;

// Since this is an MDI app, we will store the connection window information with
// the window handle and must then get this value for each message processed.
// This little macro simply sets the value of ci based on the window
#define GETCIPOINTER(hwnd) (lpCHILDINFO)(GetWindowLong(hwnd, 0))
#define SETCIPOINTER(hwnd, x) SetWindowLong(hwnd, 0, (LONG)(x))

#endif


DIALOGS.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   DIALOGS.C
//|      This module contains the dialog procs which are used to display
//|         catalog function arguements and display the results.  Note that
//|         the best way to determine the size of the arguements would be
//|         to use SQLGetInfo, however, due to ease of coding, the values
//|         have been hard-coded to constants.
//*---------------------------------------------------------------------------------
#include <windows.h>
#include "admndemo.h"
#include "dialogs.h"
#include "sql.h"
#include "sqlext.h"
#include "execute.h"
#include "info.h"
#include "results.h"
#include "strings.h"

VSZFile;
#define CHKNULL(str)  (*str ? str : NULL)

//*---------------------------------------------------------------------------------
//|   Local function prototypes
//*---------------------------------------------------------------------------------
BOOL ShowCatalogResults(CHILDINFO FAR * ci, LPSTR szTitle);
BOOL EXTFUN CatalogWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern char OutStr[MAXBUFF];

//
// The following set of defines and host vars are used to create a common
// dialog handler for each of the catalog function dialogs.  This makes
// the code size smaller while still handling each catalog functions
// parameters.  It also allows for sharing of values from one function
// to another.
//
#define CAT_LAST         256
#define CAT_QUALIFIER      1
#define CAT_TABLE          2
#define CAT_OWNER          4
#define CAT_TYPE           8
#define CAT_COLUMN        16
#define CAT_QUALIFIER2    32
#define CAT_OWNER2        64
#define CAT_TABLE2       128
#define CAT_PROCS        256
char szQualifierVal[MAX_QUALIFIER];
char szTableVal[MAX_NAME];
char szOwnerVal[MAX_OWNER];
char szTypeVal[MAX_NAME];
char szColumnVal[MAX_NAME];
char szQualifierVal2[MAX_QUALIFIER];
char szOwnerVal2[MAX_OWNER];
char szTableVal2[MAX_NAME];
char szProcsVal[MAX_NAME];

struct {
   int         mMask;                        // Bit mask value
   int         id;                           // Edit control id
   LPSTR       szStr;                        // Buffer location
   int         cbMax;                        // Maximum size
   } CatalogVals[] = {
// mMask                id                      szStr                   cbMax
// -------------------  ---------------------   ----------------------- ------------------------
   CAT_QUALIFIER,       IDE_QUALIFIER,          (LPSTR)szQualifierVal,  MAX_QUALIFIER,
   CAT_TABLE,           IDE_TABLE,              (LPSTR)szTableVal,      MAX_NAME,
   CAT_OWNER,           IDE_OWNER,              (LPSTR)szOwnerVal,      MAX_OWNER,
   CAT_TYPE,            IDE_TYPE,               (LPSTR)szTypeVal,       MAX_NAME,
   CAT_COLUMN,          IDE_COLUMN,             (LPSTR)szColumnVal,     MAX_NAME,
   CAT_QUALIFIER2,      IDE_QUALIFIER2,         (LPSTR)szQualifierVal2, MAX_QUALIFIER,
   CAT_OWNER2,          IDE_OWNER2,             (LPSTR)szOwnerVal2,     MAX_OWNER,
   CAT_TABLE2,          IDE_TABLE2,             (LPSTR)szTableVal2,     MAX_NAME,
   CAT_PROCS,           IDE_PROCEDURE,          (LPSTR)szProcsVal,      MAX_NAME,
   };

dCSEG(char) szTablesTitle[]               =  "Tables";
dCSEG(char) szColumnsTitle[]              =  "Columns";
dCSEG(char) szStatistics[]                =  "Statistics";
dCSEG(char) szPrimaryKeys[]               =  "Primary Keys";
dCSEG(char) szForeignKeys[]               =  "Foreign Keys";
dCSEG(char) szTablePrivs[]                =  "Table Privileges";
dCSEG(char) szColumnPrivs[]               =  "Column Privileges";
dCSEG(char) szSpecialCols[]               =  "Special Columns";
dCSEG(char) szProcsTitle[]                =  "Procedures";
dCSEG(char) szProcCols[]                  =  "Procedure Columns";
dCSEG(char) szCOLON[]                     =  ":";

struct {
   int               idMenu;                    // Menu identifier
   int               iDialogName;               // ID of dialog
// DLGWNDPROC        dlgProc;                   // Dialog procedure
   LPSTR             szDlgTitle;                // Title of the dialog
   UINT              mask;                      // Mask for this dialog
   } Dialogs[] = {
// idMenu                  iDialogName                szDlgTitle              mask
// ---------------------   ------------------         ----------------------- -------------------------------------------------
   IDM_TABLES,             IDD_SQLTABLES,             (LPSTR)szTablesTitle,   CAT_QUALIFIER | CAT_OWNER | CAT_TABLE | CAT_TYPE,
   IDM_COLUMNS,            IDD_SQLCOLUMNS,            (LPSTR)szColumnsTitle,  CAT_QUALIFIER | CAT_OWNER | CAT_TABLE | CAT_COLUMN,
   IDM_STATISTICS,         IDD_SQLSTATISTICS,         (LPSTR)szStatistics,    CAT_QUALIFIER | CAT_OWNER | CAT_TABLE,
   IDM_PRIMARY_KEYS,       IDD_SQLPRIMARYKEYS,        (LPSTR)szPrimaryKeys,   CAT_QUALIFIER | CAT_OWNER | CAT_TABLE,
   IDM_FOREIGN_KEYS,       IDD_SQLFOREIGNKEYS,        (LPSTR)szForeignKeys,   CAT_QUALIFIER | CAT_OWNER | CAT_TABLE | CAT_QUALIFIER2 | CAT_OWNER2 | CAT_TABLE2,
   IDM_TABLE_PRIVS,        IDD_SQLTABLEPRIVILEGES,    (LPSTR)szTablePrivs,    CAT_QUALIFIER | CAT_OWNER | CAT_TABLE,
   IDM_COLUMN_PRIVS,       IDD_SQLCOLUMNPRIVILEGES,   (LPSTR)szColumnPrivs,   CAT_QUALIFIER | CAT_OWNER | CAT_TABLE | CAT_COLUMN,
   IDM_SPECIAL_COLUMNS,    IDD_SQLSPECIALCOLUMNS,     (LPSTR)szSpecialCols,   CAT_QUALIFIER | CAT_OWNER | CAT_TABLE,
   IDM_PROCEDURES,         IDD_SQLPROCEDURES,         (LPSTR)szProcsTitle,    CAT_QUALIFIER | CAT_OWNER | CAT_PROCS,
   IDM_PROC_COLUMNS,       IDD_SQLPROCEDURECOLUMNS,   (LPSTR)szProcCols,      CAT_QUALIFIER | CAT_OWNER | CAT_PROCS | CAT_COLUMN,
   };

//
// This structure is used to control the catalog fucntions
//
typedef struct tagCATALOGINFO {
   HINSTANCE         hInst;               // Instance handle for this app
   CHILDINFO FAR *   ci;                  // Child info
   int               id;                  // Dialog id to indicate function
   LPSTR             szDlgTitle;          // Title for the dialog
   UINT              mask;                // Dialog control mask
// For SQLStatistics
   UWORD             fUnique;
   UWORD             fAccuracy;
// For SQLSpecialColumns
   UWORD             fColType;
   UWORD             fScope;
   UWORD             fNullable;
   } CATALOGINFO;

//
// A prototype for radio button controls
//
typedef struct tagIDXINFO {
   int               idxVal;
   int               fType;
   } IDXINFO;

//
// For SQLStatistics, fUnqiue parameter
//
struct {
   int               idxVal;                    // Dialog item number
   int               fType;                     // SQL constant equivalent
   } StatsfUnique[] = {
// idxVal                        fType
// --------------------------    ---------------------------------
   IDX_INDEX_UNIQUE,             SQL_INDEX_UNIQUE,
   IDX_INDEX_ALL,                SQL_INDEX_ALL,
   };

//
// For SQLStatistics, fAccuracy parameter
//
struct {
   int               idxVal;                    // Dialog item number
   int               fType;                     // SQL constant equivalent
   } StatsfAccuracy[] = {
// idxVal                        fType
// --------------------------    ---------------------------------
   IDX_ENSURE,                   SQL_ENSURE,
   IDX_QUICK,                    SQL_QUICK,
   };

//
// For SQLSpecialColumns, fColType parameter
//
struct {
   int               idxVal;                    // Dialog item number
   int               fType;                     // SQL constant equivalent
   } SpecColsfColType[] = {
// idxVal                        fType
// --------------------------    ---------------------------------
   IDX_BEST_ROWID,               SQL_BEST_ROWID,
   IDX_ROWVER,                   SQL_ROWVER,
   };

//
// For SQLSpecialColumns, fNullable parameter
//
struct {
   int               idxVal;                    // Dialog item number
   int               fType;                     // SQL constant equivalent
   } SpecColsfNullable[] = {
// idxVal                        fType
// --------------------------    ---------------------------------
   IDX_NO_NULLS,                 SQL_NO_NULLS,
   IDX_NULLABLE,                 SQL_NULLABLE,
   };

//
// For SQLSpecialColumns, fScope parameter
//
struct {
   int               idxVal;                    // Dialog item number
   int               fType;                     // SQL constant equivalent
   } SpecColsfScope[] = {
// idxVal                        fType
// --------------------------    ---------------------------------
   IDX_SCOPE_CURROW,             SQL_SCOPE_CURROW,
   IDX_SCOPE_TRANSACTION,        SQL_SCOPE_TRANSACTION,
   IDX_SCOPE_SESSION,            SQL_SCOPE_SESSION,
   };

//*---------------------------------------------------------------------------------
//| GetRadioValue:
//|   This function will examine a group of radio buttons based on the control
//|   structure and see which is checked.  When found, that value is returned.
//| Parms:
//|   hDlg           Window handle of dialog with control
//|   stct           Radio control structure
//|   cbNum          How many items in control structure
//| Returns:
//|   The value for the item checked
//*---------------------------------------------------------------------------------
int GetRadioValue(HWND hDlg, IDXINFO FAR * stct, int cbNum)
{
   int   dex;
   for(dex=0;  dex<cbNum;  dex++)
      if(IsDlgButtonChecked(hDlg, stct[dex].idxVal))
         return(stct[dex].fType);
   return 0;
}

//*---------------------------------------------------------------------------------
//| ShowCatalogResults:
//|   This function will display the results set returned by a catalog
//|   function.
//| Parms:
//|   in       ci                   CHILDINFO information
//|   in       szTitle              Title for the results set
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
BOOL ShowCatalogResults(CHILDINFO FAR * ci, LPSTR szTitle)
{
   RESULTSSET FAR *  rs;
   int               cbCols;

   if(!(cbCols = GetNumResultsCols(ci->hstmt)))
      return TRUE;
   if(cbCols == 0) {
      szWrite(ci->hwndOut, GetidsString(idsNoDataFound, OutStr, sizeof(OutStr)));
      return TRUE;
   }
   rs = GetConnectWindowResultsNode(ci);
   if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst,
                        cbCols, szTitle))
      return TRUE;
   SetMetaDataFromSql(ci->hwndOut, ci->hstmt, rs, cbCols);
   if(!CreateResultsWindow(ci, rs))
      return TRUE;
   FetchAllRecordsToResults(ci->hwndOut, ci->hstmt, rs, cbCols, TRUE);
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);

   return FALSE;
}

//*---------------------------------------------------------------------------------
//| DoList:
//|   This function will display a selection dialog for the catalog functions
//|   then process the request.
//| Parms:
//|   in       ci                   CHILDINFO information
//|   in       idMenuSelection      Menu value
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoList(CHILDINFO FAR * ci, int idMenuSelection)
{
   static BOOL             fInit;            // Have we called before
   CATALOGINFO             cati;
   CATALOGINFO FAR *       lpcati=&cati;
   int                     dex;
   HWND                    fHwnd=GetFocus();

   // Clean up all buffers on first catalog function
   if(!fInit) {
      for(dex=0;  dex<NumItems(CatalogVals);  dex++)
         memset(CatalogVals[dex].szStr, 0, CatalogVals[dex].cbMax);
      fInit = TRUE;
   }

   // Find the dialog
   for(dex=0;  dex<NumItems(Dialogs) &&
       Dialogs[dex].idMenu != idMenuSelection;  dex++);

   // Collect information for the dialog proc
   cati.hInst = ci->hInst;
   cati.id = idMenuSelection;
   cati.ci = ci;
   cati.szDlgTitle = Dialogs[dex].szDlgTitle;
   cati.mask = Dialogs[dex].mask;

   // Display the dialog
   if(-1 == DialogBoxParam(cati.hInst,
                           MAKEINTRESOURCE(Dialogs[dex].iDialogName),
                           ci->hwnd,
                           (DLGPROC) CatalogWndProc, (LPARAM)(CATALOGINFO FAR *)lpcati))
      MessageBox(NULL, "Could not open dialog box.",
                 "DoList", MB_ICONEXCLAMATION);

   if(fHwnd)
      SetFocus(fHwnd);
}

//*---------------------------------------------------------------------------------
//| DoCatalogFunction:
//|   The caller should have prepared all the information we need, so we will
//|   simply invoke the function they want to run.
//|
//| Parms:
//|   cati     CATALOGINFO
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void DoCatalogFunction(CATALOGINFO FAR * cati)
{
   char                 szTitle[MAXBUFF];
   RETCODE              retcode;
   CHILDINFO FAR *      ci=cati->ci;

   SQLFreeStmt(ci->hstmt, SQL_CLOSE);

   switch(cati->id) {
     case IDM_TABLES:
      if (!*szTableVal &&
          ((!*szOwnerVal && strcmp(szQualifierVal, "%") == 0)||
           (!*szQualifierVal && strcmp(szOwnerVal, "%") == 0)))
         retcode = SQLTables(ci->hstmt,
                             szQualifierVal, SQL_NTS,
                             szOwnerVal, SQL_NTS,
                             szTableVal, SQL_NTS,
                             szTypeVal, SQL_NTS);
      else
         retcode = SQLTables(ci->hstmt,
                             CHKNULL(szQualifierVal), SQL_NTS,
                             CHKNULL(szOwnerVal), SQL_NTS,
                             CHKNULL(szTableVal), SQL_NTS,
                             CHKNULL(szTypeVal), SQL_NTS);
      break;

     case IDM_COLUMNS:
      retcode = SQLColumns(ci->hstmt,
                           CHKNULL(szQualifierVal), SQL_NTS,
                           CHKNULL(szOwnerVal), SQL_NTS,
                           CHKNULL(szTableVal), SQL_NTS,
                           CHKNULL(szColumnVal), SQL_NTS);
      break;

     case IDM_STATISTICS:
      retcode = SQLStatistics(ci->hstmt,
                              CHKNULL(szQualifierVal), SQL_NTS,
                              CHKNULL(szOwnerVal), SQL_NTS,
                              CHKNULL(szTableVal), SQL_NTS,
                              cati->fUnique,
                              cati->fAccuracy);
      break;

     case IDM_PRIMARY_KEYS:
      retcode = SQLPrimaryKeys(ci->hstmt,
                               CHKNULL(szQualifierVal), SQL_NTS,
                               CHKNULL(szOwnerVal), SQL_NTS,
                               CHKNULL(szTableVal), SQL_NTS);
      break;

     case IDM_FOREIGN_KEYS:
      retcode = SQLForeignKeys(ci->hstmt,
                               CHKNULL(szQualifierVal), SQL_NTS,
                               CHKNULL(szOwnerVal), SQL_NTS,
                               CHKNULL(szTableVal), SQL_NTS,
                               CHKNULL(szQualifierVal2), SQL_NTS,
                               CHKNULL(szOwnerVal2), SQL_NTS,
                               CHKNULL(szTableVal2), SQL_NTS);
      break;

     case IDM_TABLE_PRIVS:
      retcode = SQLTablePrivileges(ci->hstmt,
                                   CHKNULL(szQualifierVal), SQL_NTS,
                                   CHKNULL(szOwnerVal), SQL_NTS,
                                   CHKNULL(szTableVal), SQL_NTS);
      break;

     case IDM_COLUMN_PRIVS:
      retcode = SQLColumnPrivileges(ci->hstmt,
                                    CHKNULL(szQualifierVal), SQL_NTS,
                                    CHKNULL(szOwnerVal), SQL_NTS,
                                    CHKNULL(szTableVal), SQL_NTS,
                                    CHKNULL(szColumnVal), SQL_NTS);
      break;

     case IDM_SPECIAL_COLUMNS:
      retcode = SQLSpecialColumns(ci->hstmt,
                                  cati->fColType,
                                  CHKNULL(szQualifierVal), SQL_NTS,
                                  CHKNULL(szOwnerVal), SQL_NTS,
                                  CHKNULL(szTableVal), SQL_NTS,
                                  cati->fScope,
                                  cati->fNullable);
      break;

     case IDM_PROCEDURES:
      retcode = SQLProcedures(ci->hstmt,
                              CHKNULL(szQualifierVal), SQL_NTS,
                              CHKNULL(szOwnerVal), SQL_NTS,
                              CHKNULL(szProcsVal), SQL_NTS);
      break;

     case IDM_PROC_COLUMNS:
      retcode = SQLProcedureColumns(ci->hstmt,
                                    CHKNULL(szQualifierVal), SQL_NTS,
                                    CHKNULL(szOwnerVal), SQL_NTS,
                                    CHKNULL(szProcsVal), SQL_NTS,
                                    CHKNULL(szColumnVal), SQL_NTS);
      break;

     default:
      break;
   }

   if(retcode != SQL_SUCCESS) {
      PrintErrors(ci, SQL_HANDLE_STMT);
      return;
   }

   lstrcpy((LPSTR)szTitle, (LPSTR)ci->szClientTitle);
   lstrcat((LPSTR)szTitle, (LPSTR)szCOLON);
   lstrcat((LPSTR)szTitle, (LPSTR)cati->szDlgTitle);
   if(ShowCatalogResults(ci, (LPSTR)szTitle))
      return;
}

//*------------------------------------------------------------------------
//| CatalogWndProc:
//|   This windows procedure handles all of the catalog function messages.
//|   Many things such as setting/getting text values are table driven.
//|   This reduces overall code size significantly.
//| Parms:
//|   in       Standard window parms
//| Returns:
//|   Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN CatalogWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   static CATALOGINFO FAR *   cati;
   int                        dex;

   switch(msg) {
     case WM_INITDIALOG:
      cati = (CATALOGINFO FAR *)lParam;
      CenterDialog(hDlg);

      // Set all default values for edit controls
      for(dex=0;  dex<NumItems(CatalogVals);  dex++)
         if(cati->mask & CatalogVals[dex].mMask) {
            SetWindowText(GetDlgItem(hDlg, CatalogVals[dex].id),
                          CatalogVals[dex].szStr);
            SendMessage(GetDlgItem(hDlg, CatalogVals[dex].id), EM_LIMITTEXT,
                        CatalogVals[dex].cbMax, 0L);
         }
      // Special case for dialogs with options
      if(cati->id == IDM_STATISTICS) {
         CheckRadioButton(hDlg, IDX_INDEX_UNIQUE, IDX_INDEX_ALL, IDX_INDEX_UNIQUE);
         CheckRadioButton(hDlg, IDX_ENSURE, IDX_QUICK, IDX_ENSURE);
      }
      else if(cati->id == IDM_SPECIAL_COLUMNS) {
         CheckRadioButton(hDlg, IDX_BEST_ROWID, IDX_ROWVER, IDX_BEST_ROWID);
         CheckRadioButton(hDlg, IDX_NO_NULLS, IDX_NULLABLE, IDX_NO_NULLS);
         CheckRadioButton(hDlg, IDX_SCOPE_CURROW, IDX_SCOPE_SESSION, IDX_SCOPE_CURROW);
      }

      return TRUE;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         {
            Busy(TRUE);
            for(dex=0;  dex<NumItems(CatalogVals);  dex++)
               if(cati->mask & CatalogVals[dex].mMask)
                  GetText(GetDlgItem(hDlg, CatalogVals[dex].id),
                          CatalogVals[dex].szStr);
            // Special case for dialogs with options
            if(cati->id == IDM_STATISTICS) {
               cati->fUnique = GetRadioValue(hDlg, (IDXINFO FAR *)StatsfUnique, NumItems(StatsfUnique));
               cati->fAccuracy = GetRadioValue(hDlg, (IDXINFO FAR *)StatsfAccuracy, NumItems(StatsfAccuracy));
            }
            else if(cati->id == IDM_SPECIAL_COLUMNS) {
               cati->fColType = GetRadioValue(hDlg, (IDXINFO FAR *)SpecColsfColType,
                                              NumItems(SpecColsfColType));
               cati->fScope = GetRadioValue(hDlg, (IDXINFO FAR *)SpecColsfScope,
                                            NumItems(SpecColsfScope));
               cati->fNullable = GetRadioValue(hDlg, (IDXINFO FAR *)SpecColsfNullable,
                                               NumItems(SpecColsfNullable));
            }
            DoCatalogFunction(cati);

            Busy(FALSE);
            EndDialog(hDlg, IDOK);
         }
         return TRUE;

        case IDCANCEL:
         SendMessage(hDlg, WM_CLOSE, 0, 0L);
         EndDialog(hDlg, IDCANCEL);
         return TRUE;
      }
      return TRUE;

     default:
      return FALSE;
   }
   return FALSE;
}


DIALOGS.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   DIALOGS.H
//|      This file contains all of the constant definitions for the
//|      dialogs used for the lists.
//*---------------------------------------------------------------------------------
#ifndef dialogs_DEFS
#define dialogs_DEFS

#include <windows.h>
#include "standard.h"
#include "child.h"

//*---------------------------------------------------------------------------------
//|   Macros and Defines
//*---------------------------------------------------------------------------------
#define  IDD_SQLTABLES              3000
#define  IDD_SQLCOLUMNS             3001
#define  IDD_SQLSPECIALCOLUMNS      3002
#define  IDD_SQLSTATISTICS          3003
#define  IDD_SQLPRIMARYKEYS         3004
#define  IDD_SQLFOREIGNKEYS         3005
#define  IDD_SQLTABLEPRIVILEGES     3006
#define  IDD_SQLCOLUMNPRIVILEGES    3007
#define  IDD_SQLPROCEDURES          3008
#define  IDD_SQLPROCEDURECOLUMNS    3009

#define  IDT_TABLE                  301
#define  IDT_TYPE                   303
#define  IDT_COLUMN                 304

#define  IDE_TABLE                  331
#define  IDE_TYPE                   333
#define  IDE_COLUMN                 334

#define  IDX_BEST_ROWID             340
#define  IDX_ROWVER                 341
#define  IDX_NO_NULLS               342
#define  IDX_NULLABLE               343
#define  IDX_SCOPE_CURROW           344
#define  IDX_SCOPE_TRANSACTION      345
#define  IDX_SCOPE_SESSION          346

#define  IDX_INDEX_UNIQUE           360
#define  IDX_INDEX_ALL              361
#define  IDX_ENSURE                 362
#define  IDX_QUICK                  363

#define  IDT_QUALIFIER2             365
#define  IDT_OWNER2                 366
#define  IDT_TABLE2                 367
#define  IDE_QUALIFIER2             368
#define  IDE_OWNER2                 369
#define  IDE_TABLE2                 370

#define  IDT_PROCEDURE              375
#define  IDE_PROCEDURE              376

typedef BOOL (FAR PASCAL *DLGWNDPROC) (HWND hDlg, unsigned msg, WORD wParam, LONG lParam);

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
void DoList(CHILDINFO FAR * ci, int idMenuSelection);

#endif


ERRCHECK.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//*---------------------------------------------------------------------------------
#include "errcheck.h"
#include "standard.h"
#include "strings.h"

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
char        szErrOut[100];

dCSEG(char) szErrTitle[]                  =  "Error!";
dCSEG(char) szError[]                     =  "Error: %s,  File: %s, Line: %d";
dCSEG(char) szOutOfMemory[]               =  "Memory levels are very low.  Please exit other applications and try your request again.";
dCSEG(char) szInvalidParms[]              =  "Invalid parameters";
dCSEG(char) szRegisterClassFailed[]       =  "Register class failed";

//*------------------------------------------------------------------------
//| GetSQLState:
//|     Parameters:
//|         handletype     - Type of handle (env, conn, stmt, descr)
//|         handle         - Handle used in last ODBC call
//|         psMsgNum       - Pointer to variable storing message number
//|         szState        - Return sqlstate
//|         szNative       - Native return code (driver specific)
//|         szMessage      - Return message
//*------------------------------------------------------------------------
LPSTR GetSQLState(SWORD handletype, SQLHANDLE handle, SWORD *psMsgNum,
                  LPSTR szState, SDWORD FAR * pfNative, LPSTR szMessage)
{
   RETCODE  rc;
   SWORD    cb;

   rc = SQLGetDiagRec(handletype, handle, (*psMsgNum)++,
                      szState, pfNative,
                      szMessage, RTN_MSG_SIZE, &cb);
   if(rc == SQL_NO_DATA || rc == SQL_ERROR)
      return NULL;
   else
      return szState;
}

//*------------------------------------------------------------------------
//| DoPostError:
//|   This function will post an error message to standard output, whereever
//|      that should be.
//| Parms:
//|   in       szErr                Error message
//|   in       szFile               File name
//|   in       cbLine               Line number
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoPostError(LPSTR szErr, LPSTR szFile, int cbLine)
{
   wsprintf(szErrOut, szError, (LPSTR)szErr, szFile, cbLine);
   MessageBox(NULL, szErrOut, szErrTitle, MB_OK);
}

//*------------------------------------------------------------------------
//| PrintErrors:
//|     Print out all relevant errors.
//|         ci          -  Pointer to client information
//|         handletype  -  Which handle has relevant error information
//*------------------------------------------------------------------------
void PrintErrors(CHILDINFO FAR * ci, SWORD handletype)
{
   SQLHANDLE handle;

   switch (handletype) {
     case SQL_HANDLE_ENV:
      handle = (SQLHANDLE) ci->henv;
      break;

     case SQL_HANDLE_DBC:
      handle = (SQLHANDLE) ci->hdbc;
      break;

     case SQL_HANDLE_STMT:
      handle = (SQLHANDLE) ci->hstmt;
      break;
   }

   DisplayErrors(ci->hwndOut, (LPSTR)szErrTitle, SQL_HANDLE_ENV, ci->henv);
   if(!ci->hwndOut)
      PrintErrorsHwnd(ci->hwndOut, handletype, handle);
}

//*------------------------------------------------------------------------
//| PrintErrorsHwnd:
//|   Does the actual work.  Needed as separate function for those
//|   function which are not woking directly with a ci struct.
//| Parms:
//|   hwnd        Output window
//|   henv        Environment handle
//|   hdbc        Connection handle
//|   hstmt       Statement handle
//*------------------------------------------------------------------------
void PrintErrorsHwnd(HWND hwnd, SWORD handletype, SQLHANDLE handle)
{
   SWORD    sMsgNum = 0;
   char     szState[7]="";
   char     szMessage[RTN_MSG_SIZE];
   SDWORD   pfNative=0;

   while(GetSQLState(handletype, handle, &sMsgNum,
                     szState, &pfNative, szMessage) != NULL)
      szWrite(hwnd,
              GetidsString(idsErrorString, szErrOut, sizeof(szErrOut)),
              (LPSTR)szState,
              (LPSTR)szMessage);
}

//*------------------------------------------------------------------------
//| DisplayErrors:
//|   This will take all of the errors from the ODBC handles and display
//|   them using message box.  This is usually done when there is no
//|   output window to write them to.
//| Parms:
//|   hwnd        Window handle to own the message box
//|   title       The title for the message box
//|   henv        Environment handle to look on
//|   hdbc        Connection handle to look at
//|   hstmt       Statement handle to look at
//| Returns:
//|   Nothing
//*------------------------------------------------------------------------
void DisplayErrors(HWND hwnd, LPSTR title, SWORD handletype, SQLHANDLE handle)
{
   SWORD    sMsgNum = 0;
   char     szState[7]="";
   char     szMessage[RTN_MSG_SIZE];
   SDWORD   pfNative=0;

   while(GetSQLState(handletype, handle, &sMsgNum,
                     szState, &pfNative, szMessage) != NULL)
{
    if(  szMessageBox((hwnd) ? hwnd : GetActiveWindow(),
                   MB_ICONEXCLAMATION,
                   title,
                   GetidsString(idsMsgErrorString, szErrOut, sizeof(szErrOut)),
                   (LPSTR)szState,
                   (LPSTR)szMessage) )
 break;
}
}


ERRCHECK.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|
//|    Title: ERRCHECK.H
//|
//|      This include file contains the definitions for the ERRCHECK.C module.
//|         This module contains common error checking functions.
//|
//*---------------------------------------------------------------------------------
#ifndef errcheck_DEFS
#define errcheck_DEFS

#include <windows.h>
#include <string.h>
#include "standard.h"
#include "sql.h"
#include "sqlext.h"
#include "child.h"

//------------------------------------------------------------------------
//  Defines
//------------------------------------------------------------------------
#define  RTN_MSG_SIZE      SQL_MAX_MESSAGE_LENGTH - 1

#define PostError(str)                                            \
      DoPostError(str, __FILE__, __LINE__)

//------------------------------------------------------------------------
//  Declare global variables
//------------------------------------------------------------------------
extern dCSEG(char) szErrTitle[];
extern dCSEG(char) szError[];
extern dCSEG(char) szOutOfMemory[];
extern dCSEG(char) szInvalidParms[];
extern dCSEG(char) szRegisterClassFailed[];

//------------------------------------------------------------------------
//  Declare function prototypes
//------------------------------------------------------------------------
LPSTR GetSQLState(SWORD handletype, SQLHANDLE handle, SWORD *psMsgNum,
                  LPSTR szState, SDWORD FAR * pfNative, LPSTR szMessage);
void DoPostError(LPSTR szErr, LPSTR szFile, int cbLine);
void PrintErrors(CHILDINFO FAR * ci, SWORD handletype);
void PrintErrorsHwnd(HWND hwnd, SWORD handletype, SQLHANDLE handle);
void DisplayErrors(HWND hwnd, LPSTR title, SWORD handletype, SQLHANDLE handle);

#endif


EXECUTE.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   EXECUTE.C
//|      This file contains the actual code to execute SQL Statements and
//|         display them.  This file is dependent on the SA Tool data structures
//|         and the independent module RESULTS.
//*---------------------------------------------------------------------------------
#include "admndemo.h"
#include "execute.h"
#include "strings.h"

VSZFile;

#define MAXRECORDS 1000

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
lpRESULTSINFO           lpActiveResults=NULL;         // Tracks the active results window
extern lpCHILDINFO      lpActiveConn;
extern HWND             hwndCurMDIChild;
extern HINSTANCE        hInst;
extern HWND             hwndFrame;

extern char OutStr[MAXBUFF];
extern char szDirName[_MAX_PATH];
extern char szDftFileFilter[MAXBUFF];

dCSEG(char) szNullString[]                =  "<null>";
dCSEG(char) szDash[]                      =  " - ";
dCSEG(char) szResults[]                   =  "Results ";
dCSEG(char) szErrorVal[]                  =  "#Error";
dCSEG(char) sz1000[]                      =  "1000";
dCSEG(char) szDlgTitle[]                  =  "Execute File";

typedef struct tagEXECUTEFILE {
   BOOL        fExecute;                  // TRUE if ok to proceede
   HINSTANCE   hInst;                     // Instance handle
   LPSTR       szFile;                    // File name to execute
   char        szCharacter[4];            // Character to stop on
   int         cbMaxLength;               // Maximum buffer size
   } EXECUTEFILE;

//*---------------------------------------------------------------------------------
//|   Local function prototypes
//*---------------------------------------------------------------------------------
BOOL INTFUN FindTerminator(LPSTR str, LPSTR term, LPSTR * nxtstr);
BOOL INTFUN NotInQuote(LPSTR str, LPSTR tar);
BOOL INTFUN ValidSQLStmt(LPSTR str);
BOOL EXTFUN ExecuteFileWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);
void INTFUN InternalDestroyResultsWindow(CHILDINFO FAR * ci, RESULTSSET FAR * rs);
void INTFUN DestroyResultsWindow(CHILDINFO FAR * ci, lpRESULTSINFO lpri);

//*---------------------------------------------------------------------------------
//| ExecuteFile:
//|   This function will open a file and execute each SQL statement in it.
//| Parms:
//|   ci                   CHILDINFO information
//|   hwnd                 Owner window for prompting
//|   szExeFile            If not NULL, then the name of a file to execute, in
//|                           which case the following *are* used
//|   szTerm               Terminator for statement
//|   cbStmt               Max statement size
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void ExecuteFile(CHILDINFO FAR * ci, HWND hwnd, LPSTR szExeFile,
      LPSTR szTerm, int cbStmt)
{
   char              szFile[MAXBUFF];
   int               cbRead, cbNextRead;
   HFILE             hf;
   LPSTR             szBuff;
   EXECUTEFILE       ef;
   HWND              fHwnd=GetFocus();

   //
   // Display dialog to get execute parameters
   //
   memset(szFile, 0, MAXBUFF);
   ef.hInst = ci->hInst;
   ef.szFile = szFile;

   // If the caller doesn't supply a file, then ask them for it
   if(!szExeFile) {
      if(-1 == DialogBoxParam(ci->hInst,
                              MAKEINTRESOURCE(IDD_EXECUTE_FILE),
                              hwnd,
                              (DLGPROC) ExecuteFileWndProc, (LPARAM)(EXECUTEFILE FAR *)&ef))
         MessageBox(NULL, "Could not open dialog box.",
                    "Execute File", MB_ICONEXCLAMATION);

      if(fHwnd)
         SetFocus(fHwnd);

      if(!ef.fExecute)
         return;
   }
   // If they do, then grab the options
   else {
      lstrcpy(ef.szFile, szExeFile);
      lstrcpy(ef.szCharacter, szTerm);
      ef.cbMaxLength = cbStmt;
   }

   // Now execute the file
   szBuff = (LPSTR)GetMemory(ef.cbMaxLength);
   if(!szBuff)
      return;

   if((hf = _lopen(ef.szFile, OF_READ)) != HFILE_ERROR) {
      LPSTR nxtstr, nxtread;
      szWrite(ci->hwndOut,
              GetidsString(idsExecutingFile, OutStr, MAXBUFF),
              (LPSTR)ef.szFile);
      nxtread = szBuff;
      cbNextRead = ef.cbMaxLength;
      while((cbRead = _lread(hf, nxtread, cbNextRead)) ||
            *szBuff) {
         if(FindTerminator(szBuff, ef.szCharacter, &nxtstr)) {
            if(ValidSQLStmt(szBuff))
               ExecuteCmds(ci, szBuff);
         }
         else {
            szWrite(ci->hwndOut, GetidsString(idsTerminatorNotFound, OutStr, MAXBUFF));
            goto exit01;
         }
         cbNextRead = ef.cbMaxLength;
         if(nxtstr) {
            lstrcpy(szBuff, nxtstr);
            cbNextRead -= lstrlen(szBuff);
            nxtread = szBuff + lstrlen(szBuff) + 1;
         }
      }
   }
   else {      // Couldn't open file
      szMessageBox(GetActiveWindow(),
                   MB_ICONEXCLAMATION,
                   (LPSTR) szOPENFILE,
                   GetidsString(idsOpenFileFailed, szBuff, ef.cbMaxLength),
                   (LPSTR)ef.szFile);
   }

  exit01:
   _lclose(hf);

   ReleaseMemory(szBuff);
}

//*------------------------------------------------------------------------
//| FindTerminator:
//|   Looks for a statement terminator, clears out carriage returns,
//|   and finds the next statement.
//| Parms:
//|   str      Starting location to look
//|   term     The terminator string
//|   nxtstr   The next string if there is one, NULL otherwise
//| Returns:
//|   TRUE if a valid statement was found, FALSE on error
//*------------------------------------------------------------------------
BOOL INTFUN FindTerminator(LPSTR str, LPSTR term, LPSTR * nxtstr)
{
   LPSTR    cstr=str;
   int      len;
   LPSTR    next=*nxtstr;

   next = str;
   while(next) {
      if(!(next = strstr(cstr, term))) {
         *nxtstr = NULL;
         return FALSE;
      }
      if(NotInQuote(cstr, next)) {
         len = lstrlen(term);
         while(len--)
            *next++ = '\0';
         *nxtstr = next;
         return TRUE;
      }
   }

   return FALSE;
}

//*------------------------------------------------------------------------
//| NotInQuote:
//|   Given a starting position and a target, this function determines
//|   if the target is within a quoted string.  If so, then both
//|   pointers are advaned one character after the closing quote.
//| Parms:
//|   str      Starting location to look
//|   tar      Target location
//| Returns:
//|   TRUE if the value is not in a quote, FALSE if it is
//*------------------------------------------------------------------------
BOOL INTFUN NotInQuote(LPSTR str, LPSTR tar)
{
   static char    apost = '\'';
   LPSTR          instr;
   LPSTR          tstr=str;

   while((instr = strchr(tstr, apost))) {
      if(instr < tar) {
         instr = strchr(instr+1, apost);
         if(instr > tar) {
            str = tar = instr + 1;
            return FALSE;        // Target in quoted string
         }
         else
            tstr = instr + 1;
      }
      else
         return TRUE;         // Target not between quotes
   }

   return TRUE;
}

//*------------------------------------------------------------------------
//| ValidSQLStmt:
//|   Takes a buffer and makes sure it is valid.  Essentially it removes
//|   all non-embedded carriage returns and looks for a statement which
//|   has nothing but blanks.
//| Parms:
//|   str      The string to parse
//| Returns:
//|   TRUE if the statement is valid, FALSE otherwise
//*------------------------------------------------------------------------
BOOL INTFUN ValidSQLStmt(LPSTR str)
{
   LPSTR tmpstr=str;

   RemoveCrLf(str);
   while(*tmpstr)
      if(*tmpstr++ != ' ')
         return TRUE;
   return FALSE;
}

//*------------------------------------------------------------------------
//| ExecuteFileWndProc:
//|   This window procedure is for executing a file.
//| Parms:
//|   in       Standard window parms
//| Returns:
//|   Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN ExecuteFileWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   static EXECUTEFILE FAR *      ef;

   switch(msg) {
     case WM_INITDIALOG:
      {
         ef = (EXECUTEFILE FAR *)lParam;
         ef->fExecute = FALSE;                           // Assume we close
         CenterDialog(hDlg);
         if(!*szDirName)
            GetWindowsDirectory(szDirName, MAXBUFF);
         SendMessage(GetDlgItem(hDlg, IDE_CHARACTER), EM_LIMITTEXT, 3, 0L);
         SendMessage(GetDlgItem(hDlg, IDE_MAXLENGTH), EM_LIMITTEXT, 4, 0L);
         CheckRadioButton(hDlg, IDR_CARRIAGE,
                          IDR_CHARACTER, IDR_CARRIAGE);
         EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), FALSE);
         SetWindowText(GetDlgItem(hDlg, IDT_FILE), szDirName);
         SetWindowText(GetDlgItem(hDlg, IDE_MAXLENGTH), (LPSTR)sz1000);
      }
      return TRUE;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
         // Handle radio buttons
        case IDR_CARRIAGE:
         EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), FALSE);
         return TRUE;

        case IDR_CHARACTER:
         EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), TRUE);
         SetFocus(GetDlgItem(hDlg, IDE_CHARACTER));
         return TRUE;

         // Get file to execute, use last directory name
        case IDB_FILE:
         {
            OPENFILENAME   lpofn;
            char           szFileTitle[MAXBUFF];

            _fmemset(&lpofn, 0, sizeof(OPENFILENAME));
            lpofn.hInstance = ef->hInst;
            lpofn.lStructSize = sizeof(OPENFILENAME);
            lpofn.hwndOwner = hDlg;
            lpofn.lpstrFilter = (LPSTR)szDftFileFilter;
            lpofn.nFilterIndex = 1;
            lpofn.lpstrFile = ef->szFile;
            lpofn.nMaxFile = MAXBUFF;
            lpofn.lpstrFileTitle = szFileTitle;
            lpofn.nMaxFileTitle = sizeof(szFileTitle);
            lpofn.lpstrInitialDir = szDirName;
            lpofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
            if(GetOpenFileName(&lpofn)) {
               lstrcpy(ef->szFile, lpofn.lpstrFile);
               SetWindowText(GetDlgItem(hDlg, IDT_FILE), ef->szFile);
               GetNewDirectory(szDirName, lpofn.lpstrFile);
            }
         }
         return TRUE;

         // User has clicked OK
        case IDOK:
         {
            char  szNum[5];
            GetText(GetDlgItem(hDlg, IDE_MAXLENGTH), szNum);
            ef->cbMaxLength = atoi(szNum);
            if(ef->cbMaxLength < MINSTMTSIZE) {
               MessageBox(hDlg, GetidsString(idsNumTooSmall, OutStr, MAXBUFF),
                          szDlgTitle, MB_OK);
               SetFocus(GetDlgItem(hDlg, IDE_MAXLENGTH));
               return TRUE;
            }
            GetText(GetDlgItem(hDlg, IDT_FILE), ef->szFile);
            if(IsRadioButtonOn(GetDlgItem(hDlg, IDR_CARRIAGE)))
               lstrcpy((LPSTR)ef->szCharacter, (LPSTR)"\r\n");
            else
               GetText(GetDlgItem(hDlg, IDE_CHARACTER), (LPSTR)ef->szCharacter);
            if(*ef->szCharacter == ' ' ||
               !*ef->szCharacter) {
               MessageBox(GetActiveWindow(),
                          GetidsString(idsInvalidTerminator, OutStr, MAXBUFF),
                          szErrTitle, MB_OK);
               SetFocus(GetDlgItem(hDlg, IDE_CHARACTER));
               return TRUE;
            }
            ef->fExecute = TRUE;
            EndDialog(hDlg, IDOK);
         }
         return TRUE;

        case IDCANCEL:
         ef->fExecute = FALSE;
         EndDialog(hDlg, IDCANCEL);
         return TRUE;
      }
      return TRUE;

     default:
      return FALSE;
   }
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| DoCommitRollback:
//|   This function will use SQLTransact to do either a commit or a rollback
//|   on the current HDBC.
//| Parms:
//|   in       ci                   CHILDINFO information
//|   in       type                 Which action identified by menu item
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoCommitRollback(CHILDINFO FAR * ci, int type)
{
   RETCODE retcode;

   //-------------------------------------------------------------------------------
   // This tool has only one HSTMT per HDBC, and therefore we need only specify
   // the HDBC we want to COMMIT.  Note that some drivers may allow the user to
   // Execute a COMMIT or ROLLBACK and thereby would not even require a call to
   // this function.
   //-------------------------------------------------------------------------------
   switch(type) {
     case IDM_COMMIT:
      retcode = SQLEndTran(SQL_HANDLE_DBC, ci->hdbc, SQL_COMMIT);
      break;

     case IDM_ROLLBACK:
      retcode = SQLEndTran(SQL_HANDLE_DBC, ci->hdbc, SQL_ROLLBACK);
      break;
   }

   if(retcode != SQL_SUCCESS)
      PrintErrors(ci, SQL_HANDLE_DBC);
   else
      szWrite(ci->hwndOut, GetidsString(idsStmtProcessed, OutStr, MAXBUFF));
}

//*---------------------------------------------------------------------------------
//| ExecuteCmds:
//|   This function will take a command from the given input window and execute
//|      it.  The order of execution is to use the selcted text, and if none
//|      is selected, to use the entire text from the input window.
//| Parms:
//|   ci             CHILDINFO information
//|   stmt           A null-terminated statement to execute, NULL
//|                                    if the value should be taken from input window.
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void ExecuteCmds(CHILDINFO FAR * ci, LPSTR stmt)
{
   RETCODE              retcode;
   LPSTR                sqlstmt, tmpstr;
   DWORD                size;
   DWORD                len;

   // The user may pass in their own SQL statement to be executed.  In
   // this case simply do so.
   Busy(TRUE);
   if(stmt)
      sqlstmt = stmt;
   // If stmt is NULL, however, we must get the statement from the input
   // window of the current connection window.
   else {
      len = SendMessage(ci->hwndIn, WM_GETTEXTLENGTH, 0, 0L);
      sqlstmt = (LPSTR)GetMemory(len+1);
      if(!sqlstmt) {
         Busy(FALSE);
         return;
      }
      SendMessage(ci->hwndIn, WM_GETTEXT, (WPARAM)len + 1, (LPARAM)sqlstmt);
      size = SendMessage(ci->hwndIn, EM_GETSEL, 0, 0L);
      if(HIWORD(size) - LOWORD(size) > 0) {        // A selection has been made
         len = HIWORD(size) - LOWORD(size);
         memmove(sqlstmt, &sqlstmt[LOWORD(size)],
                 HIWORD(size) - LOWORD(size));
         tmpstr = sqlstmt + len;
         *tmpstr = '\0';
      }
      RemoveCrLf(sqlstmt);
   }

   // Execute the statement
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);
   retcode = SQLExecDirect(ci->hstmt, sqlstmt, SQL_NTS);
   if(retcode != SQL_SUCCESS) {
      PrintErrors(ci, SQL_HANDLE_STMT);
      Busy(FALSE);
      goto exit00;
   }

   // Check for results
   CheckForResults(ci);

  exit00:
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);
   if(!stmt)
      ReleaseMemory(sqlstmt);
   Busy(FALSE);
}

//*---------------------------------------------------------------------------------
//| CheckForResults:
//|   Call this function after successful execution of an SQL statement.  This
//|   function will attempt to fetch the results from the statement, if they
//|   exist.  It is not an error if they do not.
//| Parms:
//|   ci             CHILDINFO information
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void CheckForResults(CHILDINFO FAR * ci)
{
   RESULTSSET FAR *     rs;
   SWORD                cbCols;
   SDWORD               cbRowCount;
   char                 szStr[MAXBUFF];
   char                 tmpbuff[30];
   RETCODE              retcode;

   //
   // At this point we have executed the statement successfully.  If there is
   // a results set, fetch it to a results window.  Otherwise simply tell the
   // user how many rows were affected, if possible.
   //
   retcode = SQLFetch(ci->hstmt);
   if(retcode == SQL_ERROR) {
      cbRowCount = PrintAffectedRows(ci->hstmt, ci->hwndOut);
      SQLFreeStmt(ci->hstmt, SQL_CLOSE);
      return;
   }
   else if (retcode == SQL_NO_DATA) {
      szWrite(ci->hwndOut, GetidsString(idsNoDataFound, szStr, sizeof(szStr)));
      return;
   }

   //
   // If we made it this far, then we have a results set to work with.  The
   //    following loop will go through each results set (if there are more
   //    than one) and place their contents in a results window.
   //
   while(RC_SUCCESSFUL(retcode)) {
      lstrcpy((LPSTR)szStr, (LPSTR)ci->szClientTitle);
      lstrcat((LPSTR)szStr, (LPSTR)szDash);
      lstrcat((LPSTR)szStr, (LPSTR)szResults);
      wsprintf(tmpbuff, "%u", ++ci->cbResultCount);
      lstrcat((LPSTR)szStr, (LPSTR)tmpbuff);

      if(!(cbCols = GetNumResultsCols(ci->hstmt)))
         return;

      rs = GetConnectWindowResultsNode(ci);
      if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, cbCols, (LPSTR)szStr))
         return;

      //
      // Set the meta data
      //
      SetMetaDataFromSql(ci->hwndOut, ci->hstmt, rs, cbCols);

      //
      // Now create the MDI child window which will hold the results.
      //
      if(!CreateResultsWindow(ci, rs))
         return;

      //
      // Loop through each data source and add it to the results set.
      //
      cbRowCount = FetchAllRecordsToResults(ci->hwndOut, ci->hstmt, rs, cbCols, FALSE);
      szWrite(ci->hwndOut,
              GetidsString(idsAffectedRows, szStr, sizeof(szStr)),
              cbRowCount);

      //
      // Now see if there are any more results to fetch.
      //
      retcode = SQLMoreResults(ci->hstmt);
      if(RC_SUCCESSFUL(retcode))
         retcode = SQLFetch(ci->hstmt);
   }     // End of loop through results sets

   SQLFreeStmt(ci->hstmt, SQL_CLOSE);

   return;
}

//*---------------------------------------------------------------------------------
//| FreeConnectWindowResults:
//|   This function will free all present results sets (if there are any) and then
//|      free up the memory they occupied.
//| Parms:
//|   in       ci                   Pointer to connection window
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void FreeConnectWindowResults(lpCHILDINFO lpci)
{
   while(lpci->lprihead)
      DestroyResultsWindow(lpci,
                           (lpRESULTSINFO)lpci->lprihead);

   return;
}

//*---------------------------------------------------------------------------------
//| GetConnectWindowResultsNode:
//|   This function will return a results set pointer which describes only
//|   the graphical portion of a results set.
//| Parms:
//|   lpci                    Pointer to connection window
//| Returns:
//|   Pointer to Results set
//*---------------------------------------------------------------------------------
lpRESULTSSET GetConnectWindowResultsNode(lpCHILDINFO lpci)
{
   lpRESULTSSET lprs;

   lprs = (lpRESULTSSET)GetMemory(sizeof(RESULTSSET));
   return lprs;
}

//*---------------------------------------------------------------------------------
//| AddResultsInfoNode:
//|   Associate a child MDI window structure with a results set.
//| Parms:
//|   lpci                    Pointer to connection window
//|   lprs                    The results set to put into a window
//| Returns:
//|   Pointer to Results set
//*---------------------------------------------------------------------------------
lpRESULTSINFO AddResultsInfoNode(lpCHILDINFO lpci, lpRESULTSSET lprs)
{
   lpRESULTSINFO     lpri;
   lpRESULTSINFO     head=(lpRESULTSINFO)lpci->lprihead;
   lpRESULTSINFO     tail=(lpRESULTSINFO)lpci->lpritail;

   lpri = (lpRESULTSINFO)GetMemory(sizeof(RESULTSINFO));
   if(!lpri)
      return NULL;

   if(!head) {
      lpci->lprihead = lpri;
      lpri->next =
         lpri->prev = NULL;
   }
   else {
      tail->next = lpri;
      lpri->prev = tail;
      lpri->next = NULL;
   }
   lpci->lpritail = lpri;
   ++lpci->cbResults;

   return lpri;
}

//*---------------------------------------------------------------------------------
//| CreateResultsWindow:
//|   This function will create an MDI client window of type Results which can
//|      be used to display a results set.  Call this function with a CHILDINFO
//|      structure which is the owner.  A spot will be found in the array of
//|      results a child can hold.  FIFO is used if there are no empty array
//|      locations.
//| Parms:
//|   cs                   Pointer to connection window
//|   rs                   Pointer to the results set to use for creation
//| Returns:
//|   TRUE on success, FALSE on failure
//*---------------------------------------------------------------------------------
BOOL INTFUN CreateResultsWindow(CHILDINFO FAR * ci, lpRESULTSSET rs)
{
   MDICREATESTRUCT            mdicreate;
   lpRESULTSINFO              lpri;

   //
   // Allocate memory for the RESULTSINFO which holds all the control
   //    structures needed for creating a window.
   //
   lpri = AddResultsInfoNode(ci, rs);
   if(!lpri)
      return FALSE;
   lpri->ci = ci;
   lpri->rs = rs;

   //
   //  User must have a valid pointer to a results set which was create via
   //    CreateResultsSet.  This function will simply create a results set window
   //    for the user based on this value.
   //
   mdicreate.szClass = szResultsClass;
   mdicreate.szTitle = (LPSTR)rs->szTitle;
   mdicreate.hOwner  = rs->hInst;
   mdicreate.x       = CW_USEDEFAULT;
   mdicreate.y       = CW_USEDEFAULT;
   mdicreate.cx      = CW_USEDEFAULT;
   mdicreate.cy      = CW_USEDEFAULT;
   mdicreate.style   = (hwndCurMDIChild) ? ((IsZoomed(hwndCurMDIChild)) ? WS_MAXIMIZE : 0) : 0;
   mdicreate.lParam  = (LPARAM)lpri;
   if(SendMessage(rs->hwndClient, WM_MDICREATE, 0,
                  (LONG)(LPMDICREATESTRUCT)&mdicreate))
      return TRUE;
   else
      return FALSE;
}

//*---------------------------------------------------------------------------------
//| DestroyResultsWindow:
//|   This function will free all memory for a specified results set.  It must
//|      remove the results set from the array of results sets and collapse the
//|      array to maintain FIFO order.
//| Parms:
//|   lpci                 Pointer to Child info
//|   lpri                 The results to drop
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void INTFUN DestroyResultsWindow(lpCHILDINFO lpci, lpRESULTSINFO lpri)
{
   //
   // First destroy the contents of the results set, then the memory for
   //    the results set itself.
   //
   SendMessage(lpri->rs->hwndList, LB_RESETCONTENT, 0, 0L);
   SendMessage(lpci->hwndClient, WM_MDIDESTROY,
               (WPARAM)(HWND)lpri->rs->hwndResults, 0L);
   FreeResultsSet(lpri->rs);

   if(lpci->lprihead == lpri)
      lpci->lprihead = lpri->next;
   if(lpci->lpritail == lpri)
      lpci->lpritail = lpri->prev;
   if(lpri->next)
      lpri->next->prev = lpri->prev;
   if(lpri->prev)
      lpri->prev->next = lpri->next;

   --lpci->cbResults;
}

//*---------------------------------------------------------------------------------
//| ResultsWndProc:
//|   This function will handle all messages which are received by a results
//|      window.
//| Parms:
//|   in       hwnd                 Window to work with
//|   in       msg                  Message we need to handle
//|   in       wParam               First param
//|   in       lParam               Second param
//| Returns:
//|   Depends on message.
//*---------------------------------------------------------------------------------
long EXTFUN ResultsWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg) {
      //
      // WM_CREATE is received when the new window is created.  We need to create
      //    the listbox for the results set when this message is received.
      //
     case WM_CREATE:
      //
      // First get information on the font being used for the display.  Declare
      //    all variables which are only needed once on stack for this message only.
      //
      {
         CREATESTRUCT FAR *      cs;
         MDICREATESTRUCT FAR *   mdi;
         lpRESULTSINFO           rwi;
         lpRESULTSSET            rs;

         cs = (CREATESTRUCT FAR *)lParam;
         mdi = (MDICREATESTRUCT FAR *)cs->lpCreateParams;
         lpActiveResults = rwi = (lpRESULTSINFO)mdi->lParam;
         rs = rwi->rs;

         SETRWPOINTER(hwnd, lpActiveResults);
         CreateResultsFont(rs, hwnd, NULL);

         //
         // Now get the window handle values and then create the components of the
         //    results window.  These include the title which has the column names,
         //    and the actual owner drawn list box which has the results set in it.
         //
         rs->hwndResults = hwnd;
         rs->hwndClient = GetParent(hwnd);
         if(!(rs->hwndList = CreateWindow("listbox", NULL,
                                          WS_CHILD | WS_VISIBLE | WS_VSCROLL |
                                          LBS_MULTIPLESEL | LBS_OWNERDRAWFIXED |
                                          LBS_NOTIFY | LBS_EXTENDEDSEL | LBS_NOINTEGRALHEIGHT,
                                          0, 0, 0, 0,
                                          hwnd, (HMENU)(2), hInst, NULL)))
            return -1;
         if(!(rs->hwndHScroll = CreateWindow("scrollbar", NULL,
                                             WS_CHILD | WS_VISIBLE,
                                             0, 0, 0, 0,
                                             hwnd, (HMENU)(3), hInst, NULL)))
            return -1;

         //
         // Get scroll bar stats and set scroll range
         //
         SetScrollRange(rs->hwndHScroll, SB_CTL, 0, rs->cbColumns - 1, TRUE);
      }
      return 0;

      //
      // When WM_SIZE is received, we need to move the child windows including the
      //    title information and the listbox to fit the new area.  If all of the
      //    columns will fit in one display, hide our horizontal scroll bar.
      // NOTE:  We must break to return DefMDIWndProc in order for the menu to
      //    be redrawn when we maximize the child window.
      //
     case WM_SIZE:
      {
         lpRESULTSINFO        rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;
         int                  cScroll, dex;

         rwi->dx = LOWORD(lParam);
         rwi->dy = HIWORD(lParam);
         rwi->xRightCol = FindRightCol(rs, rwi->xLeftCol, rwi->dx);
         for(cScroll=0, dex=rwi->xLeftCol;  dex<=rwi->xRightCol;  dex++)
            cScroll += rs->md[dex].cColWidth;
         rwi->fScrollPresent = FALSE;
         if(cScroll > rwi->dx) {
            rwi->fScrollPresent = TRUE;
            MoveWindow(rs->hwndHScroll, 0, rwi->dy - GetSystemMetrics(SM_CYHSCROLL),
                       rwi->dx - GetSystemMetrics(SM_CXVSCROLL), GetSystemMetrics(SM_CYHSCROLL), TRUE);
         }
         MoveWindow(rs->hwndList, 0, rs->cTitleHeight, rwi->dx,
                    (rwi->fScrollPresent) ? rwi->dy - rs->cTitleHeight - GetSystemMetrics(SM_CYHSCROLL) : rwi->dy - rs->cTitleHeight, TRUE);
         ShowWindow(rs->hwndHScroll, (rwi->fScrollPresent) ? SW_SHOW : SW_HIDE);
         rwi->tRect.left = rwi->tRect.top = 0;
         rwi->tRect.bottom = rs->cTitleHeight;
         rwi->tRect.right = min(rwi->dx, cScroll);
      }
      break;

      //
      // Hande the WM_INITMENUPOPUP message so that when the user selects
      //    a menu, we enable/disable each item based on our current state.
      //
     case WM_INITMENUPOPUP:
      if(!(BOOL)HIWORD(lParam))        // Not the system menu
         ResetMenu((HMENU)wParam, (int)LOWORD(lParam));
      break;

      //
      // A WM_HSCROLL message is received when the user does something with our

//    scroll bar.  We must handle horizontal scroll since a listbox does
      //    does inherintly support it.
      //
     case WM_HSCROLL:
      {
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;

         HandleHScroll(wParam, rs, hwnd, rs->hwndHScroll, &rwi->xLeftCol, &rwi->xRightCol,
                       rs->hwndList, rs->cbColumns, rwi->dx, &rwi->tRect);
      }
      return 0;

      // In order to enable the keyboard to run the scroll bars (for those users
      //    without a mouse), we must look for the cursor keys etc... and cause
      //    them to do scrolling.
     case WM_KEYDOWN:
      {
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;

         HandleVirtualHScroll(wParam, rs->hwndList, rs->hwndResults);
      }
      break;                              // Let it pass through to the app

      //
      // WM_MEASUREITEM is received when the listbox is first being drawn.  We tell
      //    Windows what each row is going to look like.
      //
     case WM_MEASUREITEM:
      {
         MEASUREITEMSTRUCT FAR * ms;
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;

         if((int)wParam != 2)                   // Not our list box
            return FALSE;
         ms = (MEASUREITEMSTRUCT FAR *)lParam;
         ms->itemHeight = rs->cTitleHeight;
      }
      return TRUE;

      //
      // WM_DRAWITEM is received whenever we must draw a record in the results set
      //    list box.  We will recieve a pointer to the ROWDATA structure.
      //
     case WM_DRAWITEM:
      {
         DRAWITEMSTRUCT FAR * dwitem;
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;

         dwitem = (DRAWITEMSTRUCT FAR *)lParam;
         DrawRowData(rs, dwitem, rwi->xLeftCol, rwi->xRightCol);
      }
      return TRUE;

      //
      // The WM_DELETEITEM message is received whenever a row is to be
      //    deleted from the listbox.  We will take the opportunity to
      //    free the storage for the row.
      //
     case WM_DELETEITEM:
      {
         DELETEITEMSTRUCT FAR *  dlt;
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET         rs=rwi->rs;
         ROWDATA FAR *        rd;

         dlt = (DELETEITEMSTRUCT FAR *)lParam;
         rd = (ROWDATA FAR *)dlt->itemData;
         FreeRowData(rs, rd);
      }
      return 0;

      //
      // WM_PAINT means it's time for us to paint our columns titles which are
      //    simply drawn in the client area for speed.
      //
     case WM_PAINT:
      {
         HDC            hdc;
         PAINTSTRUCT    ps;
         lpRESULTSINFO  rwi = GETRWPOINTER(hwnd);
         lpRESULTSSET   rs=rwi->rs;

         hdc = BeginPaint(hwnd, &ps);
         if(hdc) {
            DrawColumnTitles(hdc, rs, &rwi->tRect, rwi->xLeftCol, rwi->xRightCol);
            if(rwi->fScrollPresent) {
               RECT           rct;
               rct.left = rwi->dx - GetSystemMetrics(SM_CXVSCROLL);
               rct.top = rwi->dy - GetSystemMetrics(SM_CYHSCROLL);
               rct.right = rwi->dx;
               rct.bottom = rwi->dy;
               MoveTo(hdc, rct.left, rct.top);
               LineTo(hdc, rct.right, rct.top);
               ++rct.top;
               FillRect(hdc, &rct, GetStockObject(LTGRAY_BRUSH));
            }
            EndPaint(hwnd, &ps);
         }
      }
      break;

      //
      // All messages are handled in the main wnd proc, so pass them back
      //
     case WM_COMMAND:
      {
         UINT        id=GET_WM_COMMAND_ID(wParam, lParam);

         if(id >= IDM_CONNECT &&
            id <= IDM_MOVE_WINDOW)
            SendMessage(hwndFrame, WM_COMMAND, wParam, lParam);
      }
      break;

      //
      // The WM_MDIACTIVATE message is received first by the child window
      //    which is losing focus, then by the window which is receiving
      //    focus. If we're changing windows, get the RESULTSSET.
      //
     case WM_MDIACTIVATE:
      {
#ifndef WIN32
         if(wParam) {
            lpActiveResults = GETRWPOINTER((HWND)LOWORD(lParam));
         }
#else
         if((HWND)lParam == hwnd) {
            lpActiveResults = GETRWPOINTER((HWND)lParam);
         }
#endif
         else
            lpActiveResults = NULL;

         if(lpActiveResults) {
            RECT     rect;

            hwndCurMDIChild = lpActiveResults->rs->hwndResults;
            lpActiveConn = lpActiveResults->ci;
            GetClientRect(hwndCurMDIChild, &rect);
            InvalidateRect(hwndCurMDIChild, &rect, TRUE);
         }
      }
      return 0;

      //
      // If the user selects close from the system menu for the window, we need
      //    to be able to backtrack and delete our results set.  Since our
      //    list of results set is not kept by us, we will pass in our rs
      //    pointer and let InternalDestroyResultsWindow find the correct
      //    node to destroy.
      //
     case WM_SYSCOMMAND:
      {
         lpRESULTSINFO  rwi = lpActiveResults;

         if(wParam == SC_CLOSE)
            DestroyResultsWindow(lpActiveConn, rwi);
      }
      break;

     default:
      break;
   }

   //
   // If we haven't already processed the message, do default behavior.
   //
   return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

//*------------------------------------------------------------------------
//| SetMetaDataFromSql:
//|   This function will walk through the columns of a results set and
//|      set the meta data accordingly.  If the caller so chooses, they
//|      may update the default values after calling this function.
//| Parms:
//|   in       hwndOut              Where to write output
//|   in       hstmt                Statement handle with results set
//|   in       rs                   Pointer to results set
//|   in       cbCols               Number of results cols
//| Returns:
//|   TRUE if successful, FALSE otherwise
//*------------------------------------------------------------------------
BOOL SetMetaDataFromSql(HWND hwndOut, HSTMT hstmt, RESULTSSET FAR * rs, int cbCols)
{
   int         dex;
   char        szColumnName[MAXBUFF];
   SWORD       fSqlType;
   UDWORD      precision;
   SWORD       scale;
   SWORD       fNullable;
   RETCODE     retcode;

   for(dex=0;  dex<cbCols;  dex++) {
      retcode = SQLDescribeCol(hstmt,
                               (UWORD)(dex+1), (LPSTR)szColumnName,
                               sizeof(szColumnName),
                               NULL,
                               &fSqlType, &precision, &scale, &fNullable);
      if(!*szColumnName)         // Some drivers don't return names for computed columns
         wsprintf(szColumnName, GetidsString(idsExpression, OutStr, MAXBUFF), dex);
      if(retcode != SQL_SUCCESS)
         PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
      else
         SetMetaDataColumn(rs, dex, (LPSTR)szColumnName,
                           GetTypeName(SQL_TYPE, fSqlType), fSqlType,
                           (UDWORD)precision, (SWORD)scale,
                           (int)(min(MAXBYTES, precision)), TA_LEFT);
   }
   return TRUE;
}

//*------------------------------------------------------------------------
//| FetchAllRecordsToResults:
//|   This function will fetch each row, convert it to char, then add it
//|      to the results set we're querying.
//| Parms:
//|   in       hwnd                 Where to write output
//|   in       hstmt                Statement handle with results set
//|   in       rs                   Pointer to results set
//|   in       cbCols               Number of columns
//|   in       fFetch               TRUE if need to fetch first record
//| Returns:
//|   Number of records which were fetched.
//*------------------------------------------------------------------------
SDWORD FetchAllRecordsToResults(HWND hwndOut, HSTMT hstmt,
            RESULTSSET FAR * rs, int cbCols, BOOL fFetch)
{
#define MAXRSLTSIZE 65535
   int                  dex;
   ROWDATA FAR *        rd;
   LPSTR                inbuff, outbuff;
   SDWORD               cNull;
   RETCODE              retcode;
   SDWORD               cnt=0;
   COLORREF             rgbDft=GetDefaultRGB();

   //
   // First get some memory to work with
   //
   inbuff = (LPSTR)GetMemory(MAXRSLTSIZE);
   outbuff = (LPSTR)GetMemory(MAXRSLTSIZE);
   if(!inbuff ||
      !outbuff)
      return FALSE;

   //
   // Now fetch each row, do a getdata on each column, convert it to char, then
   //    add it to the results set.  The caller has the option of fetching the
   //    first row.  This is required, as the caller may have needed to find
   //    out if there was a results set.
   //
   if(fFetch)
      retcode = SQLFetch(hstmt);
   else
      retcode = SQL_SUCCESS;
   while(retcode != SQL_NO_DATA) {
      // Increment count and enforce max records
      if(cnt > MAXRECORDS) {
         szMessageBox(hwndOut,
                      MB_ICONEXCLAMATION + MB_OK,
                      "Limit",
                      GetidsString(idsMaxRecords, OutStr, MAXBUFF),
                      MAXRECORDS);
         SQLFreeStmt(hstmt, SQL_CLOSE);
         goto exit00;
      }
      ++cnt;

      if(retcode != SQL_SUCCESS)
         PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
      else {
         //
         // Loop through each column and retrieve it's value
         //
         rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
         for(dex=0;  dex<cbCols;  dex++) {
            retcode = SQLGetData(hstmt, (UWORD)(dex+1),
                                 SQL_C_DEFAULT,
                                 (PTR)inbuff,
                                 rs->md[dex].precision + 1,
                                 &cNull);
            if(retcode != SQL_SUCCESS) {
               PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
               SetColumnData(dex, rd, (LPSTR)szErrorVal);
            }
            else {
               if(cNull != SQL_NULL_DATA)
                  ConvertSqlTypeToChar(rs, dex, inbuff, outbuff, cNull);
               SetColumnData(dex, rd,
                             (cNull == SQL_NULL_DATA) ? (LPSTR)szNullString : outbuff);
            }
         }     // End of loop through columns
         if(AddRowData(rs, rd) == LB_ERRSPACE)
            goto exit00;
      }

      retcode = SQLFetch(hstmt);
   }           // End of fetch loop

  exit00:
   ReleaseMemory(inbuff);
   ReleaseMemory(outbuff);

   return cnt;
}

//*------------------------------------------------------------------------
//| PrintAffectedRows:
//|   This function will print out the "Affected rows" message based on
//|   row count.
//| Parms:
//|   in       hstmt                Statement handle to use
//|   in       hwnd                 Where to write output
//| Returns:
//|   Number of records which were affected
//*------------------------------------------------------------------------
SDWORD PrintAffectedRows(HSTMT hstmt, HWND hwnd)
{
   SDWORD   cbRowCount;
   RETCODE  retcode;

   //
   // Use SQLRowCount to see how many rows were affected.  It is possible
   //    that the driver does not know, in which case -1 will be returned.
   //    in this case, we assume success.
   //
   retcode = SQLRowCount(hstmt, &cbRowCount);
   if(cbRowCount > 0)
      szWrite(hwnd,
              GetidsString(idsAffectedRows, OutStr, MAXBUFF),
              cbRowCount);
   else
      szWrite(hwnd, GetidsString(idsStmtProcessed, OutStr, MAXBUFF));

   return cbRowCount;
}


EXECUTE.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   EXECUTE.H
//|      This file contains the prototypes and defines for executing SQL
//|         statements and displaying their results.
//*---------------------------------------------------------------------------------
#ifndef execute_DEFS
#define execute_DEFS

#include <windows.h>
#include "menu.h"
#include "admndemo.h"
#include "standard.h"
#include "errcheck.h"
#include "results.h"
#include "ctl3d.h"
#include "ini.h"
#include "sql.h"
#include "sqlext.h"

//*---------------------------------------------------------------------------------
//|   Macros and Defines
//*---------------------------------------------------------------------------------
#define MINSTMTSIZE                           100
#define MAXSTMTSIZE                          9999
#define IDD_EXECUTE_FILE                     3100
#define NO_STOP                              -999
#define IDT_FILE                              500
#define IDB_FILE                              501
#define IDR_CARRIAGE                          502
#define IDR_CHARACTER                         503
#define IDE_CHARACTER                         504
#define IDR_NOSTOP                            505
#define IDR_SQLERROR                          506
#define IDE_MAXLENGTH                         507

#define EXECUTE_DIRECT_SQL_STMT                 1
#define PREPARE_SQL_STMT                        2
#define EXECUTE_SQL_STMT                        3

typedef struct tagRESULTSINFO {
   RESULTSSET FAR *     rs;               // Results set
   CHILDINFO FAR *      ci;               // Child info
   int                  xLeftCol;         // Left column index
   int                  xRightCol;        // Right column index
   int                  dx;               // Client screen width
   int                  dy;               // Client screen height
   RECT                 tRect;            // Client rectangle
   BOOL                 fScrollPresent;   // TRUE if scroll bar is visible
   struct tagRESULTSINFO FAR * next;
   struct tagRESULTSINFO FAR * prev;
   } RESULTSINFO, FAR * lpRESULTSINFO;

#define GETRWPOINTER(hwnd) (lpRESULTSINFO)(GetWindowLong(hwnd, 0))
#define SETRWPOINTER(hwnd, x) SetWindowLong(hwnd, 0, (LONG)(x))

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern HMENU hMenu;

extern dCSEG(char) szResultsClass[];
extern dCSEG(char) szDash[];

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
void ExecuteFile(CHILDINFO FAR * ci, HWND hwnd, LPSTR szExeFile,
      LPSTR szTerm, int cbStmt);
void DoCommitRollback(CHILDINFO FAR * ci, int type);
void ExecuteCmds(CHILDINFO FAR * ci, LPSTR stmt);
void CheckForResults(CHILDINFO FAR * ci);
void FreeConnectWindowResults(CHILDINFO FAR * ci);
lpRESULTSSET GetConnectWindowResultsNode(lpCHILDINFO lpci);
BOOL INTFUN CreateResultsWindow(CHILDINFO FAR * ci, lpRESULTSSET rs);
long FAR PASCAL ResultsWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL SetMetaDataFromSql(HWND hwndOut, HSTMT hstmt, RESULTSSET FAR * rs, int cbCols);
SDWORD FetchAllRecordsToResults(HWND hwndOut, HSTMT hstmt,
            RESULTSSET FAR * rs, int cbCols, BOOL fFetch);
SDWORD PrintAffectedRows(HSTMT hstmt, HWND hwnd);

#endif


INFO.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   INFO.C
//|      This module contains the functions which handle the Info menu items.
//|         This module relies on RESULTS and EXECUTE to
//|      This file contains the actual code to execute SQL Statements and
//|         display them.  This file is dependent on the SA Tool data structures
//|         and the independent module RESULTS.
//|
//|      NOTE:  Due to the timing of this sample, only the 1.0 GetInfo constants
//|         are shown.  To see all GetInfo constants for a 2.0 driver, use the
//|         ODBC Test Tool which comes with this SDK.
//*---------------------------------------------------------------------------------
#include "info.h"
#include "ini.h"
#include "sql.h"
#include "sqlext.h"
#include "stdlib.h"
#include "strings.h"
VSZFile;

//*---------------------------------------------------------------------------------
//|   Defines and macros
//*---------------------------------------------------------------------------------
#define MAXNAME            35
#define MAXPARMS           18
#define MAXSQL             300

#define szCOMMA            ","
#define szBLANK            " "

typedef struct tagNEWPIPE {
   HWND        hwnd;
   HINSTANCE   hInst;
   char        szName[MAXNAME];
   BOOL        fSuccess;
} NEWPIPE;

//*---------------------------------------------------------------------------------
//|   Local Function Prototypes
//*---------------------------------------------------------------------------------
BOOL EXTFUN EditPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);
void NewPipe(NEWPIPE FAR * np);
BOOL EXTFUN NewPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL EXTFUN DoPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);
void DoPipe(RESULTSSET FAR * rs, CHILDINFO FAR * ci, LPSTR szPipeName,
         int FAR xPipes[], int cbCnt);
void DoPipeByValue(RESULTSSET FAR * rs, CHILDINFO FAR * ci, LPSTR sqlpsql,
      LPSTR szparms, int FAR xPipes[], int cbCnt, LPSTR szPipeName);
void PrepareParmList(LPSTR str);
SDWORD RefreshList(HWND hDlg, RESULTSSET FAR * rs, CHILDINFO FAR * ci, int state,
         LPSTR szQual, LPSTR szOwner, LPSTR szName, int cbCols);
SDWORD RefreshPipeList(HWND hDlg);

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern dCSEG(char) szMSSansSerif[];
extern dCSEG(char) szPIPES[];
extern dCSEG(char) szFONT[];
extern char        OutStr[MAXBUFF];
extern dCSEG(char) szVALUE[];

dCSEG(char) szISO92[]            =  "ISO 92";
dCSEG(char) szXOpen[]            =  "X/Open";
dCSEG(char) szODBC[]             =  "ODBC";
dCSEG(char) szDepr[]             =  "Deprecated";

dCSEG(char) szCore[]             =  "Core";
dCSEG(char) szLvl1[]             =  "Level 1";
dCSEG(char) szLvl2[]             =  "Level 2";

dCSEG(char) szYes[]              =  "Yes";
dCSEG(char) szNo[]               =  "No";

dCSEG(char) szODBCFunctions[]    =  "Functions";
dCSEG(char) szODBCDataSources[]  =  "Data Sources";
dCSEG(char) szODBCDataTypes[]    =  "Data Types";
dCSEG(char) szGetInfoTitle[]     =  "Get Info";
dCSEG(char) szQualifier[]        =  "Qualifier";
dCSEG(char) szOwner[]            =  "Owner";
dCSEG(char) szName[]             =  "Name";
dCSEG(char) szType[]             =  "Type";
dCSEG(char) szSQL[]              =  "Sql";
dCSEG(char) szPARMS[]            =  "Parms";
dCSEG(char) szPARMOPT[]          =  "ParmOpt";
dCSEG(char) szDELETEOPT[]        =  "Delete";
dCSEG(char) szBothTypes[]        =  "'%s','%s'";
dCSEG(char) szOneType[]          =  "'%s'";
dCSEG(char) szBlank[]            =  " ";
dCSEG(char) szTABLETYPE[]        =  "TABLE";
dCSEG(char) szVIEWTYPE[]         =  "VIEW";
dCSEG(char) szVALUE[]            =  "value";
dCSEG(char) szADDRESS[]          =  "address";
dCSEG(char) szDeletePipe[]       =  "Delete pipe %s?";
dCSEG(char) szEditPipe[]         =  "Edit Pipe";
dCSEG(char) szDuplicatePipe[]    =  "Pipe already exists";
dCSEG(char) szInstalled[]        =  "Installed";
dCSEG(char) szDROPPROCSEMI[]     =  "Drop Procedure (with semi-colon)";

static char szErrorMsgTitle[]    =  "Error";

struct {
   UWORD          fFunction;                 // Identifier for SQLGetFunctions
   LPSTR          szLevel;                   // Conformance Level
   int            idFunction;                // String table identifier for function name
   } ODBCFunctions[] = {

// fFunction                        szLevel           idFunction
// -------------------------------  ---------------   --------------------------------

// ---- ISO 92 Conformance -----------------------------------
   {SQL_API_SQLALLOCHANDLE,          (LPSTR)szISO92,    idsSQLAllocHandle},
   {SQL_API_SQLBINDCOL,              (LPSTR)szISO92,    idsSQLBindCol},
   {SQL_API_SQLCANCEL,               (LPSTR)szISO92,    idsSQLCancel},
   {SQL_API_SQLCLOSECURSOR,          (LPSTR)szISO92,    idsSQLCloseCursor},
   {SQL_API_SQLCOLATTRIBUTE,         (LPSTR)szISO92,    idsSQLColAttribute},
   {SQL_API_SQLCONNECT,              (LPSTR)szISO92,    idsSQLConnect},
   {SQL_API_SQLCOPYDESC,             (LPSTR)szISO92,    idsSQLCopyDesc},
   {SQL_API_SQLDATASOURCES,          (LPSTR)szISO92,    idsSQLDataSources},
   {SQL_API_SQLDESCRIBECOL,          (LPSTR)szISO92,    idsSQLDescribeCol},
   {SQL_API_SQLDISCONNECT,           (LPSTR)szISO92,    idsSQLDisconnect},
   {SQL_API_SQLENDTRAN,              (LPSTR)szISO92,    idsSQLEndTran},
   {SQL_API_SQLEXECDIRECT,           (LPSTR)szISO92,    idsSQLExecDirect},
   {SQL_API_SQLEXECUTE,              (LPSTR)szISO92,    idsSQLExecute},
   {SQL_API_SQLFETCH,                (LPSTR)szISO92,    idsSQLFetch},
   {SQL_API_SQLFETCHSCROLL,          (LPSTR)szISO92,    idsSQLFetchScroll},
   {SQL_API_SQLFREEHANDLE,           (LPSTR)szISO92,    idsSQLFreeHandle},
   {SQL_API_SQLFREESTMT,             (LPSTR)szISO92,    idsSQLFreeStmt},
   {SQL_API_SQLGETCONNECTATTR,       (LPSTR)szISO92,    idsSQLGetConnectAttr},
   {SQL_API_SQLGETCURSORNAME,        (LPSTR)szISO92,    idsSQLGetCursorName},
   {SQL_API_SQLGETDATA,              (LPSTR)szISO92,    idsSQLGetData},
   {SQL_API_SQLGETDESCFIELD,         (LPSTR)szISO92,    idsSQLGetDescField},
   {SQL_API_SQLGETDESCREC,           (LPSTR)szISO92,    idsSQLGetDescRec},
   {SQL_API_SQLGETDIAGFIELD,         (LPSTR)szISO92,    idsSQLGetDiagField},
   {SQL_API_SQLGETDIAGREC,           (LPSTR)szISO92,    idsSQLGetDiagRec},
   {SQL_API_SQLGETFUNCTIONS,         (LPSTR)szISO92,    idsSQLGetFunctions},
   {SQL_API_SQLGETINFO,              (LPSTR)szISO92,    idsSQLGetInfo},
   {SQL_API_SQLGETSTMTATTR,          (LPSTR)szISO92,    idsSQLGetStmtAttr},
   {SQL_API_SQLGETTYPEINFO,          (LPSTR)szISO92,    idsSQLGetTypeInfo},
   {SQL_API_SQLGETSTMTATTR,          (LPSTR)szISO92,    idsSQLGetStmtAttr},
   {SQL_API_SQLNUMRESULTCOLS,        (LPSTR)szISO92,    idsSQLNumResultCols},
   {SQL_API_SQLPREPARE,              (LPSTR)szISO92,    idsSQLPrepare},
   {SQL_API_SQLPUTDATA,              (LPSTR)szISO92,    idsSQLPutData},
   {SQL_API_SQLROWCOUNT,             (LPSTR)szISO92,    idsSQLRowCount},
   {SQL_API_SQLSETCONNECTATTR,       (LPSTR)szISO92,    idsSQLGetConnectAttr},
   {SQL_API_SQLSETCURSORNAME,        (LPSTR)szISO92,    idsSQLSetCursorName},
   {SQL_API_SQLSETDESCFIELD,         (LPSTR)szISO92,    idsSQLSetDescField},
   {SQL_API_SQLSETDESCREC,           (LPSTR)szISO92,    idsSQLSetDescRec},
   {SQL_API_SQLSETENVATTR,           (LPSTR)szISO92,    idsSQLSetEnvAttr},
   {SQL_API_SQLSETSTMTATTR,          (LPSTR)szISO92,    idsSQLSetStmtAttr},

   //---- X/Open Conformance -----------------------------------
   {SQL_API_SQLCOLUMNS,              (LPSTR)szXOpen,   idsSQLColumns},
   {SQL_API_SQLGETENVATTR,           (LPSTR)szXOpen,   idsSQLGetEnvAttr},
   {SQL_API_SQLSPECIALCOLUMNS,       (LPSTR)szXOpen,   idsSQLSpecialColumns},
   {SQL_API_SQLSTATISTICS,           (LPSTR)szXOpen,   idsSQLStatistics},
   {SQL_API_SQLTABLES,               (LPSTR)szXOpen,   idsSQLTables},

   //---- ODBC Conformance -------------------------------------
   {SQL_API_SQLBINDPARAMETER,        (LPSTR)szODBC,    idsSQLBindParameter},
   {SQL_API_SQLBROWSECONNECT,        (LPSTR)szODBC,    idsSQLBrowseConnect},
   {SQL_API_SQLBULKOPERATIONS,       (LPSTR)szODBC,    idsSQLBulkOperations},
   {SQL_API_SQLCOLUMNPRIVILEGES,     (LPSTR)szODBC,    idsSQLColumnPrivileges},
   {SQL_API_SQLDESCRIBEPARAM,        (LPSTR)szODBC,    idsSQLDescribeParam},
   {SQL_API_SQLDRIVERCONNECT,        (LPSTR)szODBC,    idsSQLDriverConnect},
   {SQL_API_SQLFOREIGNKEYS,          (LPSTR)szODBC,    idsSQLForeignKeys},
   {SQL_API_SQLMORERESULTS,          (LPSTR)szODBC,    idsSQLMoreResults},
   {SQL_API_SQLNATIVESQL,            (LPSTR)szODBC,    idsSQLNativeSQL},
   {SQL_API_SQLNUMPARAMS,            (LPSTR)szODBC,    idsSQLNumParams},
   {SQL_API_SQLPRIMARYKEYS,          (LPSTR)szODBC,    idsSQLPrimaryKeys},
   {SQL_API_SQLPROCEDURECOLUMNS,     (LPSTR)szODBC,    idsSQLProcedureColumns},
   {SQL_API_SQLPROCEDURES,           (LPSTR)szODBC,    idsSQLProcedures},
   {SQL_API_SQLSETPOS,               (LPSTR)szODBC,    idsSQLSetPos},
   {SQL_API_SQLTABLEPRIVILEGES,      (LPSTR)szODBC,    idsSQLTablePrivileges},

   //---- Deprecated ----------------------------------------
   {SQL_API_SQLALLOCCONNECT,         (LPSTR)szDepr,    idsSQLAllocConnect},
   {SQL_API_SQLALLOCENV,             (LPSTR)szDepr,    idsSQLAllocEnv},
   {SQL_API_SQLALLOCSTMT,            (LPSTR)szDepr,    idsSQLAllocStmt},
   {SQL_API_SQLBINDPARAM,            (LPSTR)szDepr,    idsSQLBindParam},
   {SQL_API_SQLCOLATTRIBUTES,        (LPSTR)szDepr,    idsSQLColAttributes},
   {SQL_API_SQLERROR,                (LPSTR)szDepr,    idsSQLError},
   {SQL_API_SQLEXTENDEDFETCH,        (LPSTR)szDepr,    idsSQLExtendedFetch},
   {SQL_API_SQLFREECONNECT,          (LPSTR)szDepr,    idsSQLFreeConnect},
   {SQL_API_SQLFREEENV,              (LPSTR)szDepr,    idsSQLFreeEnv},
   {SQL_API_SQLGETCONNECTOPTION,     (LPSTR)szDepr,    idsSQLGetConnectOption},
   {SQL_API_SQLGETSTMTOPTION,        (LPSTR)szDepr,    idsSQLGetStmtOption},
   {SQL_API_SQLPARAMOPTIONS,         (LPSTR)szDepr,    idsSQLParamOptions},
   {SQL_API_SQLSETCONNECTOPTION,     (LPSTR)szDepr,    idsSQLSetConnectOption},
   {SQL_API_SQLSETPARAM,             (LPSTR)szDepr,    idsSQLSetParam},
   {SQL_API_SQLSETSCROLLOPTIONS,     (LPSTR)szDepr,    idsSQLSetScrollOptions},
   {SQL_API_SQLSETSTMTOPTION,        (LPSTR)szDepr,    idsSQLSetStmtOption},
   {SQL_API_SQLTRANSACT,             (LPSTR)szDepr,    idsSQLTransact},
   };

//
// Generic prototype for bitmap and value lists
//
// This structure is used for to locate both bitmap and enumeration values.
// Values may appear in the list in any order.  If a value is aliased, the
// second value in an enumeration list will not be found; however, a bitmap
// entry would show both aliases as being set.
//
typedef struct tagINFOSTRUCT {
   UDWORD         fVal;                      // Which constant
   LPSTR          szStr;                     // Print String
   } INFOSTRUCT;
typedef INFOSTRUCT FAR * lpINFOSTRUCT;

//
// This macro is used to build rows of the INFOSTRUCT arrays
// guaranteeing that the "print" string is the same as the
// INFO manifest constant.
//
#define INFO_VALUE_ROW(tag) {tag, #tag}

//
// Structure for SQL_ALTER_DOMAIN
//
static INFOSTRUCT GetInfoAlterDomain[] = {
INFO_VALUE_ROW( SQL_AD_ADD_DOMAIN_CONSTRAINT               ),
INFO_VALUE_ROW( SQL_AD_DROP_DOMAIN_CONSTRAINT              ),
INFO_VALUE_ROW( SQL_AD_ADD_DOMAIN_DEFAULT                  ),
INFO_VALUE_ROW( SQL_AD_DROP_DOMAIN_DEFAULT                 ),
INFO_VALUE_ROW( SQL_AD_ADD_CONSTRAINT_INITIALLY_DEFERRED   ),
INFO_VALUE_ROW( SQL_AD_ADD_CONSTRAINT_INITIALLY_IMMEDIATE  ),
INFO_VALUE_ROW( SQL_AD_ADD_CONSTRAINT_DEFERRABLE           ),
INFO_VALUE_ROW( SQL_AD_ADD_CONSTRAINT_NON_DEFERRABLE       ),
};

//
// Structure for SQL_ALTER_TABLE
//
static INFOSTRUCT GetInfoAlterTable[] = {
INFO_VALUE_ROW( SQL_AT_ADD_COLUMN_SINGLE                   ),
INFO_VALUE_ROW( SQL_AT_ADD_CONSTRAINT                      ),
INFO_VALUE_ROW( SQL_AT_ADD_COLUMN_DEFAULT                  ),
INFO_VALUE_ROW( SQL_AT_ADD_COLUMN_COLLATION                ),
INFO_VALUE_ROW( SQL_AT_SET_COLUMN_DEFAULT                  ),
INFO_VALUE_ROW( SQL_AT_DROP_COLUMN_DEFAULT                 ),
INFO_VALUE_ROW( SQL_AT_DROP_COLUMN_CASCADE                 ),
INFO_VALUE_ROW( SQL_AT_DROP_COLUMN_RESTRICT                ),
INFO_VALUE_ROW( SQL_AT_ADD_TABLE_CONSTRAINT                ),
INFO_VALUE_ROW( SQL_AT_DROP_TABLE_CONSTRAINT_CASCADE       ),
INFO_VALUE_ROW( SQL_AT_DROP_TABLE_CONSTRAINT_RESTRICT      ),
INFO_VALUE_ROW( SQL_AT_CONSTRAINT_NAME_DEFINITION          ),
INFO_VALUE_ROW( SQL_AT_CONSTRAINT_INITIALLY_DEFERRED       ),
INFO_VALUE_ROW( SQL_AT_CONSTRAINT_INITIALLY_IMMEDIATE      ),
INFO_VALUE_ROW( SQL_AT_CONSTRAINT_DEFERRABLE               ),
INFO_VALUE_ROW( SQL_AT_CONSTRAINT_NON_DEFERRABLE           ),
};

//
// Structure for SQL_ANSI_ASYNC_MODE
//
static INFOSTRUCT GetInfoAsyncMode[] = {
INFO_VALUE_ROW( SQL_AM_NONE                                ),
INFO_VALUE_ROW( SQL_AM_CONNECTION                          ),
INFO_VALUE_ROW( SQL_AM_STATEMENT                           ),
};

//
// Structure for SQL_BATCH_ROW_COUNT
//
static INFOSTRUCT GetInfoBatchRowCount[] = {
INFO_VALUE_ROW( SQL_BRC_ROLLED_UP                          ),
INFO_VALUE_ROW( SQL_BRC_PROCEDURES                         ),
INFO_VALUE_ROW( SQL_BRC_EXPLICIT                           ),
};

//
// Structure for SQL_BATCH_SUPPORT
//
static INFOSTRUCT GetInfoBatchSupport[] = {
INFO_VALUE_ROW( SQL_BS_SELECT_EXPLICIT                     ),
INFO_VALUE_ROW( SQL_BS_ROW_COUNT_EXPLICIT                  ),
INFO_VALUE_ROW( SQL_BS_SELECT_PROC                         ),
INFO_VALUE_ROW( SQL_BS_ROW_COUNT_PROC                      ),
};

//
// Structure for SQL_BOOKMARK_PERSISTENCE
//
static INFOSTRUCT GetInfoBookmarkPersist[] = {
INFO_VALUE_ROW( SQL_BP_CLOSE                               ),
INFO_VALUE_ROW( SQL_BP_DELETE                              ),
INFO_VALUE_ROW( SQL_BP_DROP                                ),
INFO_VALUE_ROW( SQL_BP_TRANSACTION                         ),
INFO_VALUE_ROW( SQL_BP_UPDATE                              ),
INFO_VALUE_ROW( SQL_BP_OTHER_HSTMT                         ),
};

//
// Structure for SQL_CATALOG_LOCATION
//
static INFOSTRUCT GetInfoCatLocation[] = {
INFO_VALUE_ROW( SQL_CL_START                               ),
INFO_VALUE_ROW( SQL_CL_END                                 ),
};

//
// Structure for SQL_CATALOG_USAGE
//
static INFOSTRUCT GetInfoCatUsage[] = {
INFO_VALUE_ROW( SQL_CU_DML_STATEMENTS                      ),
INFO_VALUE_ROW( SQL_CU_PROCEDURE_INVOCATION                ),
INFO_VALUE_ROW( SQL_CU_TABLE_DEFINITION                    ),
INFO_VALUE_ROW( SQL_CU_INDEX_DEFINITION                    ),
INFO_VALUE_ROW( SQL_CU_PRIVILEGE_DEFINITION                ),
};

//
// Structure for SQL_CONCAT_NULL_BEHAVIOR
//
static INFOSTRUCT GetInfoConcat[] = {
INFO_VALUE_ROW( SQL_CB_NULL                                ),
INFO_VALUE_ROW( SQL_CB_NON_NULL                            ),
};

//
// Structure for SQL_CONVERT_xxxx
//
static INFOSTRUCT GetInfoConvertTypes[] = {
INFO_VALUE_ROW( SQL_CVT_BIGINT                             ),
INFO_VALUE_ROW( SQL_CVT_BINARY                             ),
INFO_VALUE_ROW( SQL_CVT_BIT                                ),
INFO_VALUE_ROW( SQL_CVT_CHAR                               ),
INFO_VALUE_ROW( SQL_CVT_DATE                               ),
INFO_VALUE_ROW( SQL_CVT_DECIMAL                            ),
INFO_VALUE_ROW( SQL_CVT_DOUBLE                             ),
INFO_VALUE_ROW( SQL_CVT_FLOAT                              ),
INFO_VALUE_ROW( SQL_CVT_INTEGER                            ),
INFO_VALUE_ROW( SQL_CVT_INTERVAL_YEAR_MONTH                ),
INFO_VALUE_ROW( SQL_CVT_INTERVAL_DAY_TIME                  ),
INFO_VALUE_ROW( SQL_CVT_LONGVARBINARY                      ),
INFO_VALUE_ROW( SQL_CVT_LONGVARCHAR                        ),
INFO_VALUE_ROW( SQL_CVT_NUMERIC                            ),
INFO_VALUE_ROW( SQL_CVT_REAL                               ),
INFO_VALUE_ROW( SQL_CVT_SMALLINT                           ),
INFO_VALUE_ROW( SQL_CVT_TIME                               ),
INFO_VALUE_ROW( SQL_CVT_TIMESTAMP                          ),
INFO_VALUE_ROW( SQL_CVT_TINYINT                            ),
INFO_VALUE_ROW( SQL_CVT_VARBINARY                          ),
INFO_VALUE_ROW( SQL_CVT_VARCHAR                            ),
INFO_VALUE_ROW( SQL_CVT_WCHAR                              ),
INFO_VALUE_ROW( SQL_CVT_WLONGVARCHAR                       ),
INFO_VALUE_ROW( SQL_CVT_WVARCHAR                           ),
};

//
// Structure for SQL_CONVERT_FUNCTIONS
//
static INFOSTRUCT GetInfoConvertFunctions[] = {
INFO_VALUE_ROW( SQL_FN_CVT_CAST                            ),
INFO_VALUE_ROW( SQL_FN_CVT_CONVERT                         ),
};

//
// Structure for SQL_CORRELATION_NAME
//
static INFOSTRUCT GetInfoCorrelationName[] = {
INFO_VALUE_ROW( SQL_CN_NONE                                ),
INFO_VALUE_ROW( SQL_CN_DIFFERENT                           ),
INFO_VALUE_ROW( SQL_CN_ANY                                 ),
};

//
// Structure for SQL_CREATE_ASSERTION
//
static INFOSTRUCT GetInfoCreateAssertion[] = {
INFO_VALUE_ROW( SQL_CA_CREATE_ASSERTION                    ),
INFO_VALUE_ROW( SQL_CA_CONSTRAINT_INITIALLY_DEFERRED       ),
INFO_VALUE_ROW( SQL_CA_CONSTRAINT_INITIALLY_IMMEDIATE      ),
INFO_VALUE_ROW( SQL_CA_CONSTRAINT_DEFERRABLE               ),
INFO_VALUE_ROW( SQL_CA_CONSTRAINT_NON_DEFERRABLE           ),
};

//
// Structure for SQL_CREATE_CHARACTER_SET
//
static INFOSTRUCT GetInfoCreateCharset[] = {
INFO_VALUE_ROW( SQL_CCS_CREATE_CHARACTER_SET               ),
INFO_VALUE_ROW( SQL_CCS_COLLATE_CLAUSE                     ),
INFO_VALUE_ROW( SQL_CCS_LIMITED_COLLATION                  ),
};

//
// Structure for SQL_CREATE_COLLATION
//
static INFOSTRUCT GetInfoCreateCollation[] = {
INFO_VALUE_ROW( SQL_CCOL_CREATE_COLLATION                  ),
};

//
// Structure for SQL_CREATE_DOMAIN
//
static INFOSTRUCT GetInfoCreateDomain[] = {
INFO_VALUE_ROW( SQL_CDO_CREATE_DOMAIN                      ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT                         ),
INFO_VALUE_ROW( SQL_CDO_DEFAULT                            ),
INFO_VALUE_ROW( SQL_CDO_COLLATION                          ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT_NAME_DEFINITION         ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT_INITIALLY_DEFERRED      ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT_INITIALLY_IMMEDIATE     ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT_DEFERRABLE              ),
INFO_VALUE_ROW( SQL_CDO_CONSTRAINT_NON_DEFERRABLE          ),
};

//
// Structure for SQL_CREATE_SCHEMA
//
static INFOSTRUCT GetInfoCreateSchema[] = {
INFO_VALUE_ROW( SQL_CS_CREATE_SCHEMA                       ),
INFO_VALUE_ROW( SQL_CS_AUTHORIZATION                       ),
INFO_VALUE_ROW( SQL_CS_DEFAULT_CHARACTER_SET               ),
};

//
// Structure for SQL_CREATE_TABLE
//
static INFOSTRUCT GetInfoCreateTable[] = {
INFO_VALUE_ROW( SQL_CT_COMMIT_PRESERVE                     ),
INFO_VALUE_ROW( SQL_CT_COMMIT_DELETE                       ),
INFO_VALUE_ROW( SQL_CT_GLOBAL_TEMPORARY                    ),
INFO_VALUE_ROW( SQL_CT_LOCAL_TEMPORARY                     ),
INFO_VALUE_ROW( SQL_CT_CONSTRAINT_INITIALLY_DEFERRED       ),
INFO_VALUE_ROW( SQL_CT_CONSTRAINT_INITIALLY_IMMEDIATE      ),
INFO_VALUE_ROW( SQL_CT_CONSTRAINT_DEFERRABLE               ),
INFO_VALUE_ROW( SQL_CT_CONSTRAINT_NON_DEFERRABLE           ),
};

//
// Structure for SQL_CREATE_TRANSLATION
//
static INFOSTRUCT GetInfoCreateXlation[] = {
INFO_VALUE_ROW( SQL_CTR_CREATE_TRANSLATION                 ),
};

//
// Structure for SQL_CREATE_VIEW
//
static INFOSTRUCT GetInfoCreateView[] = {
INFO_VALUE_ROW( SQL_CV_CREATE_VIEW                         ),
INFO_VALUE_ROW( SQL_CV_CHECK_OPTION                        ),
INFO_VALUE_ROW( SQL_CV_CASCADED                            ),
INFO_VALUE_ROW( SQL_CV_LOCAL                               ),
};

//
// Structure for SQL_CURSOR_COMMIT_BEHAVIOR,
//               SQL_CURSOR_ROLLBACK_BEHAVIOR
//
static INFOSTRUCT GetInfoCommit[] = {
INFO_VALUE_ROW( SQL_CB_DELETE                              ),
INFO_VALUE_ROW( SQL_CB_CLOSE                               ),
INFO_VALUE_ROW( SQL_CB_PRESERVE                            ),
};

//
// Structure for SQL_CURSOR_SENSITIVITY
//
static INFOSTRUCT GetInfoCursorSensitiv[] = {
INFO_VALUE_ROW( SQL_INSENSITIVE                            ),
INFO_VALUE_ROW( SQL_UNSPECIFIED                            ),
INFO_VALUE_ROW( SQL_SENSITIVE                              ),
};

//
// Structure for SQL_DEFAULT_TXN_ISOLATION
//
static INFOSTRUCT GetInfoTXNIsolation[] = {
INFO_VALUE_ROW( SQL_TXN_READ_UNCOMMITTED                   ),
INFO_VALUE_ROW( SQL_TXN_READ_COMMITTED                     ),
INFO_VALUE_ROW( SQL_TXN_REPEATABLE_READ                    ),
INFO_VALUE_ROW( SQL_TXN_SERIALIZABLE                       ),
};

//
// Structure for SQL_*_CURSOR_ATTRIBUTES1
//
static INFOSTRUCT GetInfoCurAttrs1[] = {
INFO_VALUE_ROW( SQL_CA1_NEXT                               ),
INFO_VALUE_ROW( SQL_CA1_ABSOLUTE                           ),
INFO_VALUE_ROW( SQL_CA1_RELATIVE                           ),
INFO_VALUE_ROW( SQL_CA1_BOOKMARK                           ),
INFO_VALUE_ROW( SQL_CA1_LOCK_NO_CHANGE                     ),
INFO_VALUE_ROW( SQL_CA1_LOCK_UNLOCK                        ),
INFO_VALUE_ROW( SQL_CA1_POS_POSITION                       ),
INFO_VALUE_ROW( SQL_CA1_POS_UPDATE                         ),
INFO_VALUE_ROW( SQL_CA1_POS_DELETE                         ),
INFO_VALUE_ROW( SQL_CA1_POS_REFRESH                        ),
INFO_VALUE_ROW( SQL_CA1_POSITIONED_UPDATE                  ),
INFO_VALUE_ROW( SQL_CA1_POSITIONED_DELETE                  ),
INFO_VALUE_ROW( SQL_CA1_SELECT_FOR_UPDATE                  ),
INFO_VALUE_ROW( SQL_CA1_BULK_ADD                           ),
INFO_VALUE_ROW( SQL_CA1_BULK_UPDATE_BY_BOOKMARK            ),
INFO_VALUE_ROW( SQL_CA1_BULK_DELETE_BY_BOOKMARK            ),
INFO_VALUE_ROW( SQL_CA1_BULK_FETCH_BY_BOOKMARK             ),
};

//
// Structure for SQL_*_CURSOR_ATTRIBUTES2
//
static INFOSTRUCT GetInfoCurAttrs2[] = {
INFO_VALUE_ROW( SQL_CA2_READ_ONLY_CONCURRENCY              ),
INFO_VALUE_ROW( SQL_CA2_LOCK_CONCURRENCY                   ),
INFO_VALUE_ROW( SQL_CA2_OPT_ROWVER_CONCURRENCY             ),
INFO_VALUE_ROW( SQL_CA2_OPT_VALUES_CONCURRENCY             ),
INFO_VALUE_ROW( SQL_CA2_SENSITIVITY_ADDITIONS              ),
INFO_VALUE_ROW( SQL_CA2_SENSITIVITY_DELETIONS              ),
INFO_VALUE_ROW( SQL_CA2_SENSITIVITY_UPDATES                ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_SELECT                    ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_INSERT                    ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_UPDATE                    ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_DELETE                    ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_CATALOG                   ),
INFO_VALUE_ROW( SQL_CA2_MAX_ROWS_AFFECTS_ALL               ),
INFO_VALUE_ROW( SQL_CA2_CRC_EXACT                          ),
INFO_VALUE_ROW( SQL_CA2_CRC_APPROXIMATE                    ),
INFO_VALUE_ROW( SQL_CA2_SIMULATE_NON_UNIQUE                ),
INFO_VALUE_ROW( SQL_CA2_SIMULATE_TRY_UNIQUE                ),
INFO_VALUE_ROW( SQL_CA2_SIMULATE_UNIQUE                    ),
};

//
// Structure for SQL_DATETIME_LITERALS
//
static INFOSTRUCT GetInfoSQLDateLit[] = {
INFO_VALUE_ROW( SQL_DL_SQL92_DATE                          ),
INFO_VALUE_ROW( SQL_DL_SQL92_TIME                          ),
INFO_VALUE_ROW( SQL_DL_SQL92_TIMESTAMP                     ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_YEAR                 ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_MONTH                ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_DAY                  ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_HOUR                 ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_MINUTE               ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_SECOND               ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_YEAR_TO_MONTH        ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_DAY_TO_HOUR          ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_DAY_TO_MINUTE        ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_DAY_TO_SECOND        ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_HOUR_TO_MINUTE       ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_HOUR_TO_SECOND       ),
INFO_VALUE_ROW( SQL_DL_SQL92_INTERVAL_MINUTE_TO_SECOND     ),
};

//
// Structure for SQL_DROP_ASSERTION
//
static INFOSTRUCT GetInfoDropAssertion[] = {
INFO_VALUE_ROW( SQL_DA_DROP_ASSERTION                      ),
};

//
// Structure for SQL_DROP_CHARACTER_SET
//
static INFOSTRUCT GetInfoDropCharset[] = {
INFO_VALUE_ROW( SQL_DCS_DROP_CHARACTER_SET                 ),
};

//
// Structure for SQL_DROP_COLLATION
//
static INFOSTRUCT GetInfoDropCollation[] = {
INFO_VALUE_ROW( SQL_DC_DROP_COLLATION                      ),
};

//
// Structure for SQL_DROP_DOMAIN
//
static INFOSTRUCT GetInfoDropDomain[] = {
INFO_VALUE_ROW( SQL_DD_DROP_DOMAIN                         ),
INFO_VALUE_ROW( SQL_DD_CASCADE                             ),
INFO_VALUE_ROW( SQL_DD_RESTRICT                            ),
};

//
// Structure for SQL_DROP_SCHEMA
//
static INFOSTRUCT GetInfoDropSchema[] = {
INFO_VALUE_ROW( SQL_DS_DROP_SCHEMA                         ),
INFO_VALUE_ROW( SQL_DS_CASCADE                             ),
INFO_VALUE_ROW( SQL_DS_RESTRICT                            ),
};

//
// Structure for SQL_DROP_TABLE
//
static INFOSTRUCT GetInfoDropTable[] = {
INFO_VALUE_ROW( SQL_DT_DROP_TABLE                          ),
INFO_VALUE_ROW( SQL_DT_CASCADE                             ),
INFO_VALUE_ROW( SQL_DT_RESTRICT                            ),
};

//
// Structure for SQL_DROP_TRANSLATION
//
static INFOSTRUCT GetInfoDropXlation[] = {
INFO_VALUE_ROW( SQL_DTR_DROP_TRANSLATION                   ),
};

//
// Structure for SQL_DROP_VIEW
//
static INFOSTRUCT GetInfoDropView[] = {
INFO_VALUE_ROW( SQL_DV_DROP_VIEW                           ),
INFO_VALUE_ROW( SQL_DV_CASCADE                             ),
INFO_VALUE_ROW( SQL_DV_RESTRICT                            ),
};

//
// Structure for SQL_FILE_USAGE
//
static INFOSTRUCT GetInfoFileUsage[] = {
INFO_VALUE_ROW( SQL_FILE_NOT_SUPPORTED                     ),
INFO_VALUE_ROW( SQL_FILE_TABLE                             ),
INFO_VALUE_ROW( SQL_FILE_CATALOG                           ),
};

//
// Structure for SQL_GETDATA_EXTENSIONS
//
static INFOSTRUCT GetInfoGetdataExt[] = {
INFO_VALUE_ROW( SQL_GD_ANY_COLUMN                          ),
INFO_VALUE_ROW( SQL_GD_ANY_ORDER                           ),
INFO_VALUE_ROW( SQL_GD_BLOCK                               ),
INFO_VALUE_ROW( SQL_GD_BOUND                               ),
};

//
// Structure for SQL_GROUP_BY
//
static INFOSTRUCT GetInfoGroupBy[] = {
INFO_VALUE_ROW( SQL_GB_NOT_SUPPORTED                       ),
INFO_VALUE_ROW( SQL_GB_GROUP_BY_EQUALS_SELECT              ),
INFO_VALUE_ROW( SQL_GB_GROUP_BY_CONTAINS_SELECT            ),
INFO_VALUE_ROW( SQL_GB_NO_RELATION                         ),
INFO_VALUE_ROW( SQL_GB_COLLATE                             ),
};

//
// Structure for SQL_INDEX_KEYWORDS
//
static INFOSTRUCT GetInfoIndexKeywords[] = {
INFO_VALUE_ROW( SQL_IK_NONE                                ),
INFO_VALUE_ROW( SQL_IK_ASC                                 ),
INFO_VALUE_ROW( SQL_IK_DESC                                ),
INFO_VALUE_ROW( SQL_IK_ALL                                 ),
};

//
// Structure for SQL_INFO_SCHEMA_VIEWS
//
static INFOSTRUCT GetInfoInfoSchemaViews[] = {
INFO_VALUE_ROW( SQL_ISV_ASSERTIONS                         ),
INFO_VALUE_ROW( SQL_ISV_CHARACTER_SETS                     ),
INFO_VALUE_ROW( SQL_ISV_CHECK_CONSTRAINTS                  ),
INFO_VALUE_ROW( SQL_ISV_COLLATIONS                         ),
INFO_VALUE_ROW( SQL_ISV_COLUMN_DOMAIN_USAGE                ),
INFO_VALUE_ROW( SQL_ISV_COLUMN_PRIVILEGES                  ),
INFO_VALUE_ROW( SQL_ISV_COLUMNS                            ),
INFO_VALUE_ROW( SQL_ISV_CONSTRAINT_COLUMN_USAGE            ),
INFO_VALUE_ROW( SQL_ISV_CONSTRAINT_TABLE_USAGE             ),
INFO_VALUE_ROW( SQL_ISV_ASSERTIONS                         ),
INFO_VALUE_ROW( SQL_ISV_DOMAIN_CONSTRAINTS                 ),
INFO_VALUE_ROW( SQL_ISV_DOMAINS                            ),
INFO_VALUE_ROW( SQL_ISV_KEY_COLUMN_USAGE                   ),
INFO_VALUE_ROW( SQL_ISV_REFERENTIAL_CONSTRAINTS            ),
INFO_VALUE_ROW( SQL_ISV_SQL_LANGUAGES                      ),
INFO_VALUE_ROW( SQL_ISV_TABLE_CONSTRAINTS                  ),
INFO_VALUE_ROW( SQL_ISV_TABLE_PRIVILEGES                   ),
INFO_VALUE_ROW( SQL_ISV_TABLES                             ),
INFO_VALUE_ROW( SQL_ISV_TRANSLATIONS                       ),

INFO_VALUE_ROW( SQL_ISV_USAGE_PRIVILEGES                   ),
INFO_VALUE_ROW( SQL_ISV_VIEW_COLUMN_USAGE                  ),
INFO_VALUE_ROW( SQL_ISV_VIEW_TABLE_USAGE                   ),
INFO_VALUE_ROW( SQL_ISV_VIEWS                              ),
};

//
// Structure for SQL_NON_NULLABLE_COLUMNS
//
static INFOSTRUCT GetInfoNonNullCols[] = {
INFO_VALUE_ROW( SQL_NNC_NULL                               ),
INFO_VALUE_ROW( SQL_NNC_NON_NULL                           ),
};

//
// Structure for SQL_NULL_COLLATION
//
static INFOSTRUCT GetInfoNullCollation[] = {
INFO_VALUE_ROW( SQL_NC_END                                 ),
INFO_VALUE_ROW( SQL_NC_HIGH                                ),
INFO_VALUE_ROW( SQL_NC_LOW                                 ),
INFO_VALUE_ROW( SQL_NC_START                               ),
};

//
// Structure for SQL_PARAM_ARRAY_ROW_COUNTS
//
static INFOSTRUCT GetInfoParamRowCounts[] = {
INFO_VALUE_ROW( SQL_PARC_BATCH                             ),
INFO_VALUE_ROW( SQL_PARC_NO_BATCH                          ),
};

//
// Structure for SQL_PARAM_ARRAY_SELECTS
//
static INFOSTRUCT GetInfoParamSelects[] = {
INFO_VALUE_ROW( SQL_PAS_BATCH                              ),
INFO_VALUE_ROW( SQL_PAS_NO_BATCH                           ),
INFO_VALUE_ROW( SQL_PAS_NO_SELECT                          ),
};

//
// Structure for SQL_SCHEMA_USAGE
//
static INFOSTRUCT GetInfoSchemaUsage[] = {
INFO_VALUE_ROW( SQL_SU_DML_STATEMENTS                      ),
INFO_VALUE_ROW( SQL_SU_PROCEDURE_INVOCATION                ),
INFO_VALUE_ROW( SQL_SU_TABLE_DEFINITION                    ),
INFO_VALUE_ROW( SQL_SU_INDEX_DEFINITION                    ),
INFO_VALUE_ROW( SQL_SU_PRIVILEGE_DEFINITION                ),
};

//
// Structure for SQL_SCROLL_OPTIONS
//
static INFOSTRUCT GetInfoScrollOptions[] = {
INFO_VALUE_ROW( SQL_SO_FORWARD_ONLY                        ),
INFO_VALUE_ROW( SQL_SO_KEYSET_DRIVEN                       ),
INFO_VALUE_ROW( SQL_SO_DYNAMIC                             ),
INFO_VALUE_ROW( SQL_SO_MIXED                               ),
};

//
// Structure for SQL_SQL_CONFORMANCE
//
static INFOSTRUCT GetInfoSQLConform[] = {
INFO_VALUE_ROW( SQL_SC_SQL92_ENTRY                         ),
INFO_VALUE_ROW( SQL_SC_FIPS127_2_TRANSITIONAL              ),
INFO_VALUE_ROW( SQL_SC_SQL92_FULL                          ),
INFO_VALUE_ROW( SQL_SC_SQL92_INTERMEDIATE                  ),
};

//
// Structure for SQL_SQL92_DATETIME_FUNCTIONS
//
static INFOSTRUCT GetInfoS92DatetimeFns[] = {
INFO_VALUE_ROW( SQL_SDF_CURRENT_DATE                       ),
INFO_VALUE_ROW( SQL_SDF_CURRENT_TIME                       ),
INFO_VALUE_ROW( SQL_SDF_CURRENT_TIMESTAMP                  ),
};

//
// Structure for SQL_SQL92_FOREIGN_KEY_DELETE_RULE
//
static INFOSTRUCT GetInfoS92FKeyDelRule[] = {
INFO_VALUE_ROW( SQL_SFKD_CASCADE                           ),
INFO_VALUE_ROW( SQL_SFKD_NO_ACTION                         ),
INFO_VALUE_ROW( SQL_SFKD_SET_DEFAULT                       ),
INFO_VALUE_ROW( SQL_SFKD_SET_NULL                          ),
};

//
// Structure for SQL_SQL92_FOREIGN_KEY_UPDATE_RULE
//
static INFOSTRUCT GetInfoS92FKeyUpdRule[] = {
INFO_VALUE_ROW( SQL_SFKU_CASCADE                           ),
INFO_VALUE_ROW( SQL_SFKU_NO_ACTION                         ),
INFO_VALUE_ROW( SQL_SFKU_SET_DEFAULT                       ),
INFO_VALUE_ROW( SQL_SFKU_SET_NULL                          ),
};

//
// Structure for SQL_SQL92_GRANT
//
static INFOSTRUCT GetInfoS92Grant[] = {
INFO_VALUE_ROW( SQL_SG_USAGE_ON_DOMAIN                     ),
INFO_VALUE_ROW( SQL_SG_USAGE_ON_CHARACTER_SET              ),
INFO_VALUE_ROW( SQL_SG_USAGE_ON_COLLATION                  ),
INFO_VALUE_ROW( SQL_SG_USAGE_ON_TRANSLATION                ),
INFO_VALUE_ROW( SQL_SG_WITH_GRANT_OPTION                   ),
};

//
// Structure for SQL_NUMERIC_FUNCTIONS
//
static INFOSTRUCT GetInfoNumeric[] = {
INFO_VALUE_ROW( SQL_FN_NUM_ABS                             ),
INFO_VALUE_ROW( SQL_FN_NUM_ACOS                            ),
INFO_VALUE_ROW( SQL_FN_NUM_ASIN                            ),
INFO_VALUE_ROW( SQL_FN_NUM_ATAN                            ),
INFO_VALUE_ROW( SQL_FN_NUM_ATAN2                           ),
INFO_VALUE_ROW( SQL_FN_NUM_CEILING                         ),
INFO_VALUE_ROW( SQL_FN_NUM_COS                             ),
INFO_VALUE_ROW( SQL_FN_NUM_COT                             ),
INFO_VALUE_ROW( SQL_FN_NUM_EXP                             ),
INFO_VALUE_ROW( SQL_FN_NUM_FLOOR                           ),
INFO_VALUE_ROW( SQL_FN_NUM_LOG                             ),
INFO_VALUE_ROW( SQL_FN_NUM_MOD                             ),
INFO_VALUE_ROW( SQL_FN_NUM_RAND                            ),
INFO_VALUE_ROW( SQL_FN_NUM_PI                              ),
INFO_VALUE_ROW( SQL_FN_NUM_SIGN                            ),
INFO_VALUE_ROW( SQL_FN_NUM_SIN                             ),
INFO_VALUE_ROW( SQL_FN_NUM_SQRT                            ),
INFO_VALUE_ROW( SQL_FN_NUM_TAN                             ),
};

//
// Structure for SQL_SQL92_NUMERIC_VALUE_FUNCTIONS
//
static INFOSTRUCT GetInfoS92NumValFns[] = {
INFO_VALUE_ROW( SQL_SNVF_BIT_LENGTH                        ),
INFO_VALUE_ROW( SQL_SNVF_CHAR_LENGTH                       ),
INFO_VALUE_ROW( SQL_SNVF_CHARACTER_LENGTH                  ),
INFO_VALUE_ROW( SQL_SNVF_EXTRACT                           ),
INFO_VALUE_ROW( SQL_SNVF_OCTET_LENGTH                      ),
INFO_VALUE_ROW( SQL_SNVF_POSITION                          ),
};

//
// Structure for SQL_SQL92_PREDICATES
//
static INFOSTRUCT GetInfoS92Predicates[] = {
INFO_VALUE_ROW( SQL_SP_EXISTS                              ),
INFO_VALUE_ROW( SQL_SP_ISNOTNULL                           ),
INFO_VALUE_ROW( SQL_SP_ISNULL                              ),
INFO_VALUE_ROW( SQL_SP_MATCH_FULL                          ),
INFO_VALUE_ROW( SQL_SP_MATCH_PARTIAL                       ),
INFO_VALUE_ROW( SQL_SP_MATCH_UNIQUE_FULL                   ),
INFO_VALUE_ROW( SQL_SP_MATCH_UNIQUE_PARTIAL                ),
INFO_VALUE_ROW( SQL_SP_OVERLAPS                            ),
INFO_VALUE_ROW( SQL_SP_UNIQUE                              ),
INFO_VALUE_ROW( SQL_SP_LIKE                                ),
INFO_VALUE_ROW( SQL_SP_IN                                  ),
INFO_VALUE_ROW( SQL_SP_BETWEEN                             ),
INFO_VALUE_ROW( SQL_SP_COMPARISON                          ),
INFO_VALUE_ROW( SQL_SP_QUANTIFIED_COMPARISON               ),
};

//
// Structure for SQL_SQL92_RELATIONAL_JOIN_OPERATORS
//
static INFOSTRUCT GetInfoS92RelJoinOps[] = {
INFO_VALUE_ROW( SQL_SRJO_CORRESPONDING_CLAUSE              ),
INFO_VALUE_ROW( SQL_SRJO_CROSS_JOIN                        ),
INFO_VALUE_ROW( SQL_SRJO_EXCEPT_JOIN                       ),
INFO_VALUE_ROW( SQL_SRJO_FULL_OUTER_JOIN                   ),
INFO_VALUE_ROW( SQL_SRJO_INNER_JOIN                        ),
INFO_VALUE_ROW( SQL_SRJO_INTERSECT_JOIN                    ),
INFO_VALUE_ROW( SQL_SRJO_LEFT_OUTER_JOIN                   ),
INFO_VALUE_ROW( SQL_SRJO_NATURAL_JOIN                      ),
INFO_VALUE_ROW( SQL_SRJO_RIGHT_OUTER_JOIN                  ),
INFO_VALUE_ROW( SQL_SRJO_UNION_JOIN                        ),
};

//
// Structure for SQL_SQL92_REVOKE
//
static INFOSTRUCT GetInfoS92Revoke[] = {
INFO_VALUE_ROW( SQL_SR_USAGE_ON_DOMAIN                     ),
INFO_VALUE_ROW( SQL_SR_USAGE_ON_CHARACTER_SET              ),
INFO_VALUE_ROW( SQL_SR_USAGE_ON_COLLATION                  ),
INFO_VALUE_ROW( SQL_SR_USAGE_ON_TRANSLATION                ),
INFO_VALUE_ROW( SQL_SR_GRANT_OPTION_FOR                    ),
};

//
// Structure for SQL_SQL92_ROW_VALUE_CONSTRUCTOR
//
static INFOSTRUCT GetInfoS92RowValConstr[] = {
INFO_VALUE_ROW( SQL_SRVC_VALUE_EXPRESSION                  ),
INFO_VALUE_ROW( SQL_SRVC_NULL                              ),
INFO_VALUE_ROW( SQL_SRVC_DEFAULT                           ),
INFO_VALUE_ROW( SQL_SRVC_ROW_SUBQUERY                      ),
};

//
// Structure for SQL_SQL92_STRING_FUNCTIONS
//
static INFOSTRUCT GetInfoS92StringFns[] = {
INFO_VALUE_ROW( SQL_SSF_CONVERT                            ),
INFO_VALUE_ROW( SQL_SSF_LOWER                              ),
INFO_VALUE_ROW( SQL_SSF_UPPER                              ),
INFO_VALUE_ROW( SQL_SSF_SUBSTRING                          ),
INFO_VALUE_ROW( SQL_SSF_TRANSLATE                          ),
INFO_VALUE_ROW( SQL_SSF_TRIM_BOTH                          ),
INFO_VALUE_ROW( SQL_SSF_TRIM_LEADING                       ),
INFO_VALUE_ROW( SQL_SSF_TRIM_TRAILING                      ),
};

//
// Structure for SQL_SQL92_VALUE_EXPRESSIONS
//
static INFOSTRUCT GetInfoS92ValueExprs[] = {
INFO_VALUE_ROW( SQL_SVE_CASE                               ),
INFO_VALUE_ROW( SQL_SVE_CAST                               ),
INFO_VALUE_ROW( SQL_SVE_COALESCE                           ),
INFO_VALUE_ROW( SQL_SVE_NULLIF                             ),
};

//
// Structure for SQL_SQL92_STANDARD_CLI_CONFORMANCE
//
static INFOSTRUCT GetInfoS92StdCLIConf[] = {
INFO_VALUE_ROW( SQL_SCC_XOPEN_CLI_VERSION1                 ),
INFO_VALUE_ROW( SQL_SCC_ISO92_CLI                          ),
};

//
// Structure for SQL_SUBQUERUIES
//
static INFOSTRUCT GetInfoSubqueries[] = {
INFO_VALUE_ROW( SQL_SQ_CORRELATED_SUBQUERIES               ),
INFO_VALUE_ROW( SQL_SQ_COMPARISON                          ),
INFO_VALUE_ROW( SQL_SQ_EXISTS                              ),
INFO_VALUE_ROW( SQL_SQ_IN                                  ),
INFO_VALUE_ROW( SQL_SQ_QUANTIFIED                          ),
};

//
// Structure for SQL_STRING_FUNCTIONS
//
static INFOSTRUCT GetInfoStrings[] = {
INFO_VALUE_ROW( SQL_FN_STR_ASCII                                         ),
INFO_VALUE_ROW( SQL_FN_STR_CHAR                            ),
INFO_VALUE_ROW( SQL_FN_STR_CONCAT                          ),
INFO_VALUE_ROW( SQL_FN_STR_INSERT                          ),
INFO_VALUE_ROW( SQL_FN_STR_LCASE                           ),
INFO_VALUE_ROW( SQL_FN_STR_LEFT                            ),
INFO_VALUE_ROW( SQL_FN_STR_LENGTH                          ),
INFO_VALUE_ROW( SQL_FN_STR_LOCATE                          ),
INFO_VALUE_ROW( SQL_FN_STR_LTRIM                           ),
INFO_VALUE_ROW( SQL_FN_STR_REPEAT                          ),
INFO_VALUE_ROW( SQL_FN_STR_REPLACE                         ),
INFO_VALUE_ROW( SQL_FN_STR_RIGHT                           ),
INFO_VALUE_ROW( SQL_FN_STR_RTRIM                           ),
INFO_VALUE_ROW( SQL_FN_STR_SUBSTRING                       ),
INFO_VALUE_ROW( SQL_FN_STR_UCASE                           ),
};

//
// Structure for SQL_SYSTEM_FUNCTIONS
//
static INFOSTRUCT GetInfoSystem[] = {
INFO_VALUE_ROW( SQL_FN_SYS_DBNAME                          ),
INFO_VALUE_ROW( SQL_FN_SYS_IFNULL                          ),
INFO_VALUE_ROW( SQL_FN_SYS_USERNAME                        ),
};

//
// Structure for SQL_TIMEDATE_FUNCTIONS
//
static INFOSTRUCT GetInfoTimeDate[] = {
INFO_VALUE_ROW( SQL_FN_TD_CURDATE                          ),
INFO_VALUE_ROW( SQL_FN_TD_CURRENT_DATE                     ),
INFO_VALUE_ROW( SQL_FN_TD_CURRENT_TIME                     ),
INFO_VALUE_ROW( SQL_FN_TD_CURRENT_TIMESTAMP                ),
INFO_VALUE_ROW( SQL_FN_TD_CURTIME                          ),
INFO_VALUE_ROW( SQL_FN_TD_DAYOFMONTH                       ),
INFO_VALUE_ROW( SQL_FN_TD_DAYOFWEEK                        ),
INFO_VALUE_ROW( SQL_FN_TD_DAYOFYEAR                        ),
INFO_VALUE_ROW( SQL_FN_TD_EXTRACT                          ),
INFO_VALUE_ROW( SQL_FN_TD_HOUR                             ),
INFO_VALUE_ROW( SQL_FN_TD_MINUTE                           ),
INFO_VALUE_ROW( SQL_FN_TD_MONTH                            ),
INFO_VALUE_ROW( SQL_FN_TD_NOW                              ),
INFO_VALUE_ROW( SQL_FN_TD_QUARTER                          ),
INFO_VALUE_ROW( SQL_FN_TD_SECOND                           ),
INFO_VALUE_ROW( SQL_FN_TD_WEEK                             ),
INFO_VALUE_ROW( SQL_FN_TD_YEAR                             ),
};

//
// Structure for SQL_TIMEDATE_ADD_INTERVALS,
//               SQL_TIMEDATE_DIFF_INTERVALS,
//
static INFOSTRUCT GetInfoTimeDateIvls[] = {
INFO_VALUE_ROW( SQL_FN_TSI_FRAC_SECOND                     ),
INFO_VALUE_ROW( SQL_FN_TSI_SECOND                          ),
INFO_VALUE_ROW( SQL_FN_TSI_MINUTE                          ),
INFO_VALUE_ROW( SQL_FN_TSI_HOUR                            ),
INFO_VALUE_ROW( SQL_FN_TSI_DAY                             ),
INFO_VALUE_ROW( SQL_FN_TSI_WEEK                            ),
INFO_VALUE_ROW( SQL_FN_TSI_MONTH                           ),
INFO_VALUE_ROW( SQL_FN_TSI_QUARTER                         ),
INFO_VALUE_ROW( SQL_FN_TSI_YEAR                            ),
};

//
// Structure for SQL_ODBC_API_CONFORMANCE
//
static INFOSTRUCT GetInfoAPIConform[] = {
INFO_VALUE_ROW( SQL_OIC_CORE                               ),
INFO_VALUE_ROW( SQL_OIC_LEVEL1                             ),
INFO_VALUE_ROW( SQL_OIC_LEVEL2                             ),
};

//
// Structure for SQL_ODBC_SQL_CONFORMANCE
//
static INFOSTRUCT GetInfoODBCSQL[] = {
INFO_VALUE_ROW( SQL_OSC_MINIMUM                            ),
INFO_VALUE_ROW( SQL_OSC_CORE                               ),
INFO_VALUE_ROW( SQL_OSC_EXTENDED                           ),
};

//
// Structure for SQL_IDENTIFIER_CASE
//
static INFOSTRUCT GetInfoIDCase[] = {
INFO_VALUE_ROW( SQL_IC_UPPER                               ),
INFO_VALUE_ROW( SQL_IC_LOWER                               ),
INFO_VALUE_ROW( SQL_IC_SENSITIVE                           ),
INFO_VALUE_ROW( SQL_IC_MIXED                               ),
};

//
// Structure for SQL_TXN_CAPABLE
//
static INFOSTRUCT GetInfoTxnCapable[] = {
INFO_VALUE_ROW( SQL_TC_NONE                                ),
INFO_VALUE_ROW( SQL_TC_DML                                 ),
INFO_VALUE_ROW( SQL_TC_DDL_COMMIT                          ),
INFO_VALUE_ROW( SQL_TC_DDL_IGNORE                          ),
INFO_VALUE_ROW( SQL_TC_ALL                                 ),
};

//
// Structure for SQL_TXN_ISOLATION_OPTIONS
//
static INFOSTRUCT GetInfoTxnIsoOptions[] = {
INFO_VALUE_ROW( SQL_TXN_READ_UNCOMMITTED                   ),
INFO_VALUE_ROW( SQL_TXN_READ_COMMITTED                     ),
INFO_VALUE_ROW( SQL_TXN_REPEATABLE_READ                    ),
INFO_VALUE_ROW( SQL_TXN_SERIALIZABLE                       ),
};

//
// Structure for SQL_UNION
//
static INFOSTRUCT GetInfoUnion[] = {
INFO_VALUE_ROW( SQL_U_UNION                                ),
INFO_VALUE_ROW( SQL_U_UNION_ALL                            ),
};

//
// The following structure is used to retrieve information about the driver.  There
//    are 5 types of GetInfo structures:
//          INT16       16-bit value
//          INT32       32-bit value
//          STRVAL      String value
//          DEXVAL      Indexed item (eg: 0-x)
//          BITVAL      Bit-mask value
//
char           szGetInfo[MAXBUFF];
UWORD          cb16;
UDWORD         cb32;
#define        INT16             1
#define        INT32             2
#define        STRVAL            3
#define        DEXVAL            4
#define        BITVAL            5

#define INFOROW(name, string, type, addr, varsize, ptr, size) {name, string, type, addr, varsize, ptr, size}

#define INT16_ITEM(name)        INFOROW(name, #name, INT16,  &cb16,      sizeof(cb16), NULL, 0)
#define INT32_ITEM(name)        INFOROW(name, #name, INT32,  &cb32,      sizeof(cb32), NULL, 0)
#define STRNG_ITEM(name)        INFOROW(name, #name, STRVAL, szGetInfo,  sizeof(szGetInfo), NULL, 0)
#define BIT32_ITEM(name, list)  INFOROW(name, #name, BITVAL, &cb32,      sizeof(cb32), list, NumItems(list))
#define DEX32_ITEM(name, list)  INFOROW(name, #name, DEXVAL, &cb32,      sizeof(cb32), list, NumItems(list))

struct {

   UWORD          fInfoType;                 // What we're looking for
   LPSTR          szInfoName;                // Print name of the GetInfo item
   int            fOutType;                  // string, 16-bit or 32-bit
   PTR            rgbInfoValue;              // Output buffer
   SWORD          cbInfoMax;                 // Size of output buffer
   void FAR *     ptr;                       // Generic constant structure
   int            cbNum;                     // Count of items in ptr

} GetInfo[] = {

   // Out Type  fInfoType                          List of Values
   // --------  -------------------------          ---------------------------
   STRNG_ITEM(  SQL_ACCESSIBLE_PROCEDURES                                      ),
   STRNG_ITEM(  SQL_ACCESSIBLE_TABLES                                          ),
   INT16_ITEM(  SQL_ACTIVE_ENVIRONMENTS                                        ),
   BIT32_ITEM(  SQL_ALTER_DOMAIN,                  GetInfoAlterDomain          ),
   BIT32_ITEM(  SQL_ALTER_TABLE,                   GetInfoAlterTable           ),
   DEX32_ITEM(  SQL_ASYNC_MODE,                    GetInfoAsyncMode            ),

   BIT32_ITEM(  SQL_BATCH_ROW_COUNT,               GetInfoBatchRowCount        ),
   BIT32_ITEM(  SQL_BATCH_SUPPORT,                 GetInfoBatchSupport         ),
   BIT32_ITEM(  SQL_BOOKMARK_PERSISTENCE,          GetInfoBookmarkPersist      ),

   DEX32_ITEM(  SQL_CATALOG_LOCATION,              GetInfoCatLocation          ),
   STRNG_ITEM(  SQL_CATALOG_NAME                                               ),
   STRNG_ITEM(  SQL_CATALOG_NAME_SEPARATOR                                     ),
   STRNG_ITEM(  SQL_CATALOG_TERM                                               ),
   BIT32_ITEM(  SQL_CATALOG_USAGE,                 GetInfoCatUsage             ),
   STRNG_ITEM(  SQL_COLLATION_SEQ                                              ),
   STRNG_ITEM(  SQL_COLUMN_ALIAS                                               ),
   DEX32_ITEM(  SQL_CONCAT_NULL_BEHAVIOR,          GetInfoConcat               ),
   BIT32_ITEM(  SQL_CONVERT_BIGINT,                GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_BINARY,                GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_BIT,                   GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_CHAR,                  GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_DATE,                  GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_DECIMAL,               GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_DOUBLE,                GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_FLOAT,                 GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_FUNCTIONS,             GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_INTEGER,               GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_LONGVARBINARY,         GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_LONGVARCHAR,           GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_NUMERIC,               GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_REAL,                  GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_SMALLINT,              GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_TIME,                  GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_TIMESTAMP,             GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_TINYINT,               GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_VARBINARY,             GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_VARCHAR,               GetInfoConvertTypes         ),
   BIT32_ITEM(  SQL_CONVERT_FUNCTIONS,             GetInfoConvertFunctions     ),
   BIT32_ITEM(  SQL_CORRELATION_NAME,              GetInfoCorrelationName      ),
   BIT32_ITEM(  SQL_CREATE_ASSERTION,              GetInfoCreateAssertion      ),
   BIT32_ITEM(  SQL_CREATE_CHARACTER_SET,          GetInfoCreateCharset        ),
   BIT32_ITEM(  SQL_CREATE_COLLATION,              GetInfoCreateCollation      ),
   BIT32_ITEM(  SQL_CREATE_DOMAIN,                 GetInfoCreateDomain         ),
   BIT32_ITEM(  SQL_CREATE_SCHEMA,                 GetInfoCreateSchema         ),
   BIT32_ITEM(  SQL_CREATE_TABLE,                  GetInfoCreateTable          ),
   BIT32_ITEM(  SQL_CREATE_TRANSLATION,            GetInfoCreateXlation        ),
   BIT32_ITEM(  SQL_CREATE_VIEW,                   GetInfoCreateView           ),
   DEX32_ITEM(  SQL_CURSOR_COMMIT_BEHAVIOR,        GetInfoCommit               ),
   DEX32_ITEM(  SQL_CURSOR_ROLLBACK_BEHAVIOR,      GetInfoCommit               ),
   DEX32_ITEM(  SQL_CURSOR_SENSITIVITY,            GetInfoCursorSensitiv       ),

   STRNG_ITEM(  SQL_DATA_SOURCE_NAME                                           ),
   STRNG_ITEM(  SQL_DATA_SOURCE_READ_ONLY                                      ),
   STRNG_ITEM(  SQL_DATABASE_NAME                                              ),
   BIT32_ITEM(  SQL_DATETIME_LITERALS,             GetInfoSQLDateLit           ),
   STRNG_ITEM(  SQL_DBMS_NAME                                                  ),
   STRNG_ITEM(  SQL_DBMS_VER                                                   ),
   BIT32_ITEM(  SQL_DEFAULT_TXN_ISOLATION,         GetInfoTXNIsolation         ),
   STRNG_ITEM(  SQL_DESCRIBE_PARAMETER                                         ),
   INT32_ITEM(  SQL_DRIVER_HDBC                                                ),
   INT32_ITEM(  SQL_DRIVER_HENV                                                ),
   INT32_ITEM(  SQL_DRIVER_HLIB                                                ),
   INT32_ITEM(  SQL_DRIVER_HSTMT                                               ),
   STRNG_ITEM(  SQL_DRIVER_NAME                                                ),
   STRNG_ITEM(  SQL_DRIVER_ODBC_VER                                            ),
   STRNG_ITEM(  SQL_DRIVER_VER                                                 ),
   BIT32_ITEM(  SQL_DROP_ASSERTION,                GetInfoDropAssertion        ),
   BIT32_ITEM(  SQL_DROP_CHARACTER_SET,            GetInfoDropCharset          ),
   BIT32_ITEM(  SQL_DROP_COLLATION,                GetInfoDropCollation        ),
   BIT32_ITEM(  SQL_DROP_DOMAIN,                   GetInfoDropDomain           ),
   BIT32_ITEM(  SQL_DROP_SCHEMA,                   GetInfoDropSchema           ),
   BIT32_ITEM(  SQL_DROP_TABLE,                    GetInfoDropTable            ),
   BIT32_ITEM(  SQL_DROP_TRANSLATION,              GetInfoDropXlation          ),
   BIT32_ITEM(  SQL_DROP_VIEW,                     GetInfoDropView             ),
   BIT32_ITEM(  SQL_DYNAMIC_CURSOR_ATTRIBUTES1,    GetInfoCurAttrs1            ),
   BIT32_ITEM(  SQL_DYNAMIC_CURSOR_ATTRIBUTES2,    GetInfoCurAttrs2            ),

   STRNG_ITEM(  SQL_EXPRESSIONS_IN_ORDERBY                                     ),

   DEX32_ITEM(  SQL_FILE_USAGE,                    GetInfoFileUsage            ),
   BIT32_ITEM(  SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1, GetInfoCurAttrs1          ),
   BIT32_ITEM(  SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2, GetInfoCurAttrs2          ),

   BIT32_ITEM(  SQL_GETDATA_EXTENSIONS,            GetInfoGetdataExt           ),
   BIT32_ITEM(  SQL_GROUP_BY,                      GetInfoGroupBy              ),

   DEX32_ITEM(  SQL_IDENTIFIER_CASE,               GetInfoIDCase               ),
   INT16_ITEM(  SQL_IDENTIFIER_QUOTE_CHAR                                      ),
   BIT32_ITEM(  SQL_INDEX_KEYWORDS,                GetInfoIndexKeywords        ),
   BIT32_ITEM(  SQL_INFO_SCHEMA_VIEWS,             GetInfoInfoSchemaViews      ),
   STRNG_ITEM(  SQL_INTEGRITY                                                  ),

   BIT32_ITEM(  SQL_KEYSET_CURSOR_ATTRIBUTES1,     GetInfoCurAttrs1            ),
   BIT32_ITEM(  SQL_KEYSET_CURSOR_ATTRIBUTES2,     GetInfoCurAttrs2            ),
   STRNG_ITEM(  SQL_KEYWORDS                                                   ),

   STRNG_ITEM(  SQL_LIKE_ESCAPE_CLAUSE                                         ),

   INT32_ITEM(  SQL_MAX_ASYNC_CONCURRENT_STATEMENTS                            ),
   INT32_ITEM(  SQL_MAX_BINARY_LITERAL_LEN                                     ),
   INT16_ITEM(  SQL_MAX_CATALOG_NAME_LEN                                       ),
   INT32_ITEM(  SQL_MAX_CHAR_LITERAL_LEN                                       ),
   INT16_ITEM(  SQL_MAX_COLUMN_NAME_LEN                                        ),
   INT16_ITEM(  SQL_MAX_COLUMNS_IN_GROUP_BY                                    ),
   INT16_ITEM(  SQL_MAX_COLUMNS_IN_INDEX                                       ),
   INT16_ITEM(  SQL_MAX_COLUMNS_IN_ORDER_BY                                    ),
   INT16_ITEM(  SQL_MAX_COLUMNS_IN_SELECT                                      ),
   INT16_ITEM(  SQL_MAX_COLUMNS_IN_TABLE                                       ),
   INT16_ITEM(  SQL_MAX_CONCURRENT_ACTIVITIES                                  ),
   INT16_ITEM(  SQL_MAX_CURSOR_NAME_LEN                                        ),
   INT16_ITEM(  SQL_MAX_DRIVER_CONNECTIONS                                     ),
   INT16_ITEM(  SQL_MAX_IDENTIFIER_LEN                                         ),
   INT32_ITEM(  SQL_MAX_INDEX_SIZE                                             ),
   INT16_ITEM(  SQL_MAX_PROCEDURE_NAME_LEN                                     ),
   INT32_ITEM(  SQL_MAX_ROW_SIZE                                               ),
   STRNG_ITEM(  SQL_MAX_ROW_SIZE_INCLUDES_LONG                                 ),
   INT16_ITEM(  SQL_MAX_SCHEMA_NAME_LEN                                        ),
   INT16_ITEM(  SQL_MAX_STATEMENT_LEN                                          ),
   INT16_ITEM(  SQL_MAX_TABLE_NAME_LEN                                         ),
   INT16_ITEM(  SQL_MAX_TABLES_IN_SELECT                                       ),
   INT16_ITEM(  SQL_MAX_USER_NAME_LEN                                          ),
   STRNG_ITEM(  SQL_MULTIPLE_ACTIVE_TXN                                        ),
   STRNG_ITEM(  SQL_MULT_RESULT_SETS                                           ),

   STRNG_ITEM(  SQL_NEED_LONG_DATA_LEN                                         ),
   DEX32_ITEM(  SQL_NON_NULLABLE_COLUMNS,          GetInfoNonNullCols          ),
   DEX32_ITEM(  SQL_NULL_COLLATION,                GetInfoNullCollation        ),
   BIT32_ITEM(  SQL_NUMERIC_FUNCTIONS,             GetInfoNumeric              ),

   DEX32_ITEM(  SQL_ODBC_INTERFACE_CONFORMANCE,    GetInfoAPIConform           ),
   DEX32_ITEM(  SQL_ODBC_SQL_CONFORMANCE,          GetInfoODBCSQL              ),
   STRNG_ITEM(  SQL_ODBC_VER                                                   ),
   STRNG_ITEM(  SQL_ORDER_BY_COLUMNS_IN_SELECT                                 ),
   STRNG_ITEM(  SQL_OUTER_JOINS                                                ),

   DEX32_ITEM(  SQL_PARAM_ARRAY_ROW_COUNTS,        GetInfoParamRowCounts       ),
   DEX32_ITEM(  SQL_PARAM_ARRAY_SELECTS,           GetInfoParamSelects         ),
   STRNG_ITEM(  SQL_PROCEDURES                                                 ),
   STRNG_ITEM(  SQL_PROCEDURE_TERM                                             ),

   DEX32_ITEM(  SQL_QUOTED_IDENTIFIER_CASE,        GetInfoIDCase               ),

   STRNG_ITEM(  SQL_ROW_UPDATES                                                ),

   STRNG_ITEM(  SQL_SCHEMA_TERM                                                ),
   BIT32_ITEM(  SQL_SCHEMA_USAGE,                  GetInfoSchemaUsage          ),
   BIT32_ITEM(  SQL_SCROLL_OPTIONS,                GetInfoScrollOptions        ),
   STRNG_ITEM(  SQL_SEARCH_PATTERN_ESCAPE                                      ),
   STRNG_ITEM(  SQL_SERVER_NAME                                                ),
   STRNG_ITEM(  SQL_SPECIAL_CHARACTERS                                         ),
   BIT32_ITEM(  SQL_SQL_CONFORMANCE,               GetInfoSQLConform           ),
   BIT32_ITEM(  SQL_SQL92_DATETIME_FUNCTIONS,      GetInfoS92DatetimeFns       ),
   BIT32_ITEM(  SQL_SQL92_FOREIGN_KEY_DELETE_RULE, GetInfoS92FKeyDelRule       ),
   BIT32_ITEM(  SQL_SQL92_FOREIGN_KEY_UPDATE_RULE, GetInfoS92FKeyUpdRule       ),
   BIT32_ITEM(  SQL_SQL92_GRANT,                   GetInfoS92Grant             ),
   BIT32_ITEM(  SQL_SQL92_NUMERIC_VALUE_FUNCTIONS, GetInfoS92NumValFns         ),
   BIT32_ITEM(  SQL_SQL92_PREDICATES,              GetInfoS92Predicates        ),
   BIT32_ITEM(  SQL_SQL92_RELATIONAL_JOIN_OPERATORS, GetInfoS92RelJoinOps      ),
   BIT32_ITEM(  SQL_SQL92_REVOKE,                  GetInfoS92Revoke            ),
   BIT32_ITEM(  SQL_SQL92_ROW_VALUE_CONSTRUCTOR,   GetInfoS92RowValConstr      ),
   BIT32_ITEM(  SQL_SQL92_STRING_FUNCTIONS,        GetInfoS92StringFns         ),
   BIT32_ITEM(  SQL_SQL92_VALUE_EXPRESSIONS,       GetInfoS92ValueExprs        ),
   BIT32_ITEM(  SQL_STANDARD_CLI_CONFORMANCE,      GetInfoS92StdCLIConf        ),
   BIT32_ITEM(  SQL_STATIC_CURSOR_ATTRIBUTES1,     GetInfoCurAttrs1            ),
   BIT32_ITEM(  SQL_STATIC_CURSOR_ATTRIBUTES2,     GetInfoCurAttrs2            ),
   BIT32_ITEM(  SQL_STRING_FUNCTIONS,              GetInfoStrings              ),
   BIT32_ITEM(  SQL_SUBQUERIES,                    GetInfoSubqueries           ),
   BIT32_ITEM(  SQL_SYSTEM_FUNCTIONS,              GetInfoSystem               ),

   STRNG_ITEM(  SQL_TABLE_TERM                                                 ),
   BIT32_ITEM(  SQL_TIMEDATE_ADD_INTERVALS,        GetInfoTimeDateIvls         ),
   BIT32_ITEM(  SQL_TIMEDATE_DIFF_INTERVALS,       GetInfoTimeDateIvls         ),
   BIT32_ITEM(  SQL_TIMEDATE_FUNCTIONS,            GetInfoTimeDate             ),
   DEX32_ITEM(  SQL_TXN_CAPABLE,                   GetInfoTxnCapable           ),
   BIT32_ITEM(  SQL_TXN_ISOLATION_OPTION,          GetInfoTxnIsoOptions        ),

   BIT32_ITEM(  SQL_UNION,                         GetInfoUnion                ),
   STRNG_ITEM(  SQL_USER_NAME                                                  ),

   STRNG_ITEM(  SQL_XOPEN_CLI_YEAR                                             ),
};

//*---------------------------------------------------------------------------------
//| GetBitVal:
//|   Call this function to retrieve the string values for all items which meet
//|   the bitwise and condition.
//| Parms:
//|   in       rs                   Pointer to the results set
//|   in       szInfoName           The info value being retrieved
//|   in       is                   Structure with resource ids and values
//|   in       maxdex               Number of items in struct
//|   in       mask                 The value to compare against
//|   in       szOut                Output buffer for retrieval
//|   in       cbOut                Size of output buffer
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
BOOL GetBitVal
   (RESULTSSET FAR * rs,
    LPSTR szInfoName,
    lpINFOSTRUCT is,
    int maxdex,
    UDWORD mask,
    LPSTR szVal,
    int cbVal)
{
   int            tdex;
   ROWDATA FAR *  rd;
   COLORREF       rgbDft=GetDefaultRGB();

   if (mask != 0) {
      for(tdex=0;  tdex<maxdex;  tdex++)
         if(is[tdex].fVal & mask) {
            rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
            SetColumnData(0, rd, szInfoName);
            SetColumnData(1, rd, is[tdex].szStr);
            if(AddRowData(rs, rd) == LB_ERRSPACE)
               return FALSE;
         }
   }
   else {
      rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
      SetColumnData(0, rd, szInfoName);
      SetColumnData(1, rd, "0");
      if(AddRowData(rs, rd) == LB_ERRSPACE)
         return FALSE;
   }

   return TRUE;
}

//*---------------------------------------------------------------------------------
//| GetIndexVal:
//|   Call this function to retrieve the string value for a constant.
//| Parms:
//|   in       rs                   Pointer to the results set
//|   in       szInfoName           The info value being retrieved
//|   in       is                   Structure with resource ids and values
//|   in       maxdex               Number of items in struct
//|   in       dex                  String index value
//|   in       szOut                Output buffer for retrieval
//|   in       cbOut                Size of output buffer
//| Returns:
//|   FALSE if there is an error
//| No error is posted if matching value is not found.
//*---------------------------------------------------------------------------------
BOOL GetIndexVal
   (RESULTSSET FAR * rs,
    LPSTR szInfoName,
    lpINFOSTRUCT is,
    int maxdex,
    int dex,
    LPSTR szOut,
    int cbOut)
{
   int tdex;
   ROWDATA FAR *  rd;
   COLORREF       rgbDft=GetDefaultRGB();

   for (tdex = 0; tdex<maxdex; tdex++)
      if (is[tdex].fVal == (UDWORD) dex) {
         rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
         SetColumnData(0, rd, szInfoName);
         SetColumnData(1, rd, is[tdex].szStr);
         if(AddRowData(rs, rd) == LB_ERRSPACE)
            return FALSE;
      }

   return TRUE;
}

//*---------------------------------------------------------------------------------
//| DisplayGetInfo:
//|   This function goes through all of the SQLGetInfo constants defined in the
//|   ODBC reference guide and displays them in a results set.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   TRUE if successful,
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL DisplayGetInfo(CHILDINFO FAR * ci)
{
   char                 szTitle[MAXBUFF];
   char                 szVal[MAXBUFF];
   int                  dex;
   lpINFOSTRUCT         lpINFS;
   RESULTSSET FAR *     rs;
   ROWDATA FAR *        rd;
   RETCODE              retcode;
   COLORREF             rgbDft=GetDefaultRGB();

   //
   // Create a hard coded results set with 2 columns
   //
   lstrcpy((LPSTR)szTitle, (LPSTR)ci->szClientTitle);
   lstrcat((LPSTR)szTitle, (LPSTR)szDash);
   lstrcat((LPSTR)szTitle, (LPSTR)szGetInfoTitle);
   rs = GetConnectWindowResultsNode(ci);
   if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, 2, (LPSTR)szTitle))
      return FALSE;

   //
   // Set the meta data
   //
   SetMetaDataColumn(rs, 0, (LPSTR)"fInfoType",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 40, 0,
                     45, TA_LEFT);
   SetMetaDataColumn(rs, 1, (LPSTR)"Value",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 70, 0,
                     70, TA_LEFT);

   //
   // Now create the MDI child window which will hold the results.
   //
   if(!CreateResultsWindow(ci, rs))
      goto exit00;

   //
   // Loop through the control structure and check each fInfoType.  Certain
   //    types require extra processing.
   //
   Busy(TRUE);
   for(dex=0;  dex<NumItems(GetInfo);  dex++) {
      if(GetInfo[dex].fInfoType == SQL_DRIVER_HSTMT)           // Input arg also
         *(HSTMT FAR *)GetInfo[dex].rgbInfoValue = ci->hstmt;

      memset(GetInfo[dex].rgbInfoValue, 0, GetInfo[dex].cbInfoMax);

      retcode = SQLGetInfo(ci->hdbc,
                           GetInfo[dex].fInfoType,
                           GetInfo[dex].rgbInfoValue,
                           GetInfo[dex].cbInfoMax,
                           NULL);
      if(retcode != SQL_SUCCESS)
         PrintErrors(ci, SQL_HANDLE_DBC);

      switch(GetInfo[dex].fInfoType) {
        case SQL_DRIVER_HENV:
        case SQL_DRIVER_HDBC:
        case SQL_DRIVER_HSTMT:
         rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
         SetColumnData(0, rd, GetInfo[dex].szInfoName);
         wsprintf(szVal, "%04X:%04X", HIWORD(cb32), LOWORD(cb32));
         SetColumnData(1, rd, (LPSTR)szVal);
         if(AddRowData(rs, rd) == LB_ERRSPACE)
            goto exit00;
         break;

         //
         // The default action is taken when we only need to display the
         //    value as is.  We can use the structure to figure out what
         //    format it is in.
         //
        default:
         rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
         SetColumnData(0, rd, GetInfo[dex].szInfoName);
         switch(GetInfo[dex].fOutType) {
           case INT16:
            wsprintf(szVal, "%d", cb16);
            SetColumnData(1, rd, (LPSTR)szVal);
            if(AddRowData(rs, rd) == LB_ERRSPACE)
               goto exit00;
            break;

           case INT32:
            wsprintf(szVal, "%ld", cb32);
            SetColumnData(1, rd, (LPSTR)szVal);
            if(AddRowData(rs, rd) == LB_ERRSPACE)
               goto exit00;
            break;

           case DEXVAL:
            lpINFS = (lpINFOSTRUCT)GetInfo[dex].ptr;
            if(!GetIndexVal(rs, GetInfo[dex].szInfoName,
                            lpINFS, GetInfo[dex].cbNum,
                            cb32,
                            (LPSTR)szVal, sizeof(szVal)))
               goto exit00;
            break;

           case BITVAL:
            lpINFS = (lpINFOSTRUCT)GetInfo[dex].ptr;
            if(!GetBitVal(rs, GetInfo[dex].szInfoName,
                          lpINFS, GetInfo[dex].cbNum,
                          cb32,
                          (LPSTR)szVal, sizeof(szVal)))
               goto exit00;
            break;

           default:
            szGetInfo[69] = '\0'; // truncate long string...
            SetColumnData(1, rd, (LPSTR)GetInfo[dex].rgbInfoValue);
            if(AddRowData(rs, rd) == LB_ERRSPACE)
               goto exit00;
            break;
         }
      }

   }

   Busy(FALSE);
   return TRUE;

  exit00:
   Busy(FALSE);
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| DisplayODBCFunctions:
//|   This function will enumerate all of the ODBC functions in a results set
//|      and indicate which ones are supported.  The results set is attatched
//|      as a valid results window on the current child.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   TRUE if successful,
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL DisplayODBCFunctions(CHILDINFO FAR * ci)
{
   UWORD                fSupport;
   char                 szFuncName[35];
   char                 szTitle[MAXBUFF];
   int                  dex;
   RESULTSSET FAR *     rs;
   ROWDATA FAR *        rd;
   RETCODE              retcode;
   COLORREF             rgbDft=GetDefaultRGB();
   //
   // Create a hard coded results set with 3 columns
   //
   lstrcpy((LPSTR)szTitle, (LPSTR)ci->szClientTitle);
   lstrcat((LPSTR)szTitle, (LPSTR)szDash);
   lstrcat((LPSTR)szTitle, (LPSTR)szODBCFunctions);
   rs = GetConnectWindowResultsNode(ci);
   if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, 3, (LPSTR)szTitle))
      return FALSE;

   //
   // Set the meta data
   //
   SetMetaDataColumn(rs, 0, (LPSTR)"Function",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 20, 0,
                     25, TA_LEFT);
   SetMetaDataColumn(rs, 1, (LPSTR)"Conformance",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 10, 0,
                     10, TA_LEFT);
   SetMetaDataColumn(rs, 2, (LPSTR)"Supported",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 10, 0,
                     10, TA_LEFT);

   //
   // Now create the MDI child window which will hold the results.
   //
   if(!CreateResultsWindow(ci, rs))
      goto exit00;

   //
   // Loop through the control structure and check each function.  For each row
   //    add a record with the function name, conformance level, and Yes if
   //    supported, No if not.
   //
   for(dex=0;  dex<NumItems(ODBCFunctions);  dex++) {
      retcode = SQLGetFunctions(ci->hdbc,
                                ODBCFunctions[dex].fFunction, &fSupport);
      if(retcode != SQL_SUCCESS)
         PrintErrors(ci, SQL_HANDLE_DBC);
      iLoadString(ODBCFunctions[dex].idFunction, (LPSTR)szFuncName, sizeof(szFuncName));
      rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
      SetColumnData(0, rd, szFuncName);
      SetColumnData(1, rd, ODBCFunctions[dex].szLevel);
      SetColumnData(2, rd, (fSupport) ? (LPSTR)szYes : (LPSTR)szNo);
      AddRowData(rs, rd);
   }

   return TRUE;

  exit00:
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| DisplayODBCDataSources:
//|   This function will enumerate all of the ODBC Data sources.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   TRUE if successful,
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL DisplayODBCDataSources(CHILDINFO FAR * ci)
{
   HENV                 henv;
   RESULTSSET FAR *     rs;
   ROWDATA FAR *        rd;
   RETCODE              retcode;
   char                 szDSN[SQL_MAX_DSN_LENGTH + 1];
   char                 szDesc[MAXBUFF];
   char                 szTitle[MAXBUFF];
   COLORREF             rgbDft=GetDefaultRGB();

   //
   // Create a hard coded results set with 2 columns
   //
   lstrcpy((LPSTR)szTitle, (LPSTR)ci->szClientTitle);
   lstrcat((LPSTR)szTitle, (LPSTR)szDash);
   lstrcat((LPSTR)szTitle, (LPSTR)szODBCDataSources);
   rs = GetConnectWindowResultsNode(ci);
   if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, 2, (LPSTR)szTitle))
      return FALSE;

   //
   // Set the meta data
   //
   SetMetaDataColumn(rs, 0, (LPSTR)"Driver",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 15, 0,
                     15, TA_LEFT);
   SetMetaDataColumn(rs, 1, (LPSTR)"Description",
                     GetTypeName(SQL_TYPE, SQL_CHAR), SQL_CHAR, 35, 0,
                     35, TA_LEFT);

   //
   // Now create the MDI child window which will hold the results.
   //
   if(!CreateResultsWindow(ci, rs))
      goto exit00;

   //
   // Loop through each data source and add it to the results set.
   //
   if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HENV, &henv) )
{
 PrintErrors(ci, SQL_HANDLE_ENV);
 goto exit00;
}
if (SQL_SUCCESS != SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER))
{
 PrintErrors(ci, SQL_HANDLE_ENV);
 goto exit00;
}

   retcode = SQLDataSources(henv, SQL_FETCH_FIRST, szDSN, sizeof(szDSN),
                            NULL, szDesc, sizeof(szDesc), NULL);
   while(retcode != SQL_NO_DATA) {
      if(retcode != SQL_SUCCESS )
{
         PrintErrors(ci, SQL_HANDLE_ENV);
if (retcode != SQL_SUCCESS_WITH_INFO)
goto exit00;
}
      rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
      SetColumnData(0, rd, szDSN);
      SetColumnData(1, rd, szDesc);
      AddRowData(rs, rd);
      retcode = SQLDataSources(henv, SQL_FETCH_NEXT, szDSN, sizeof(szDSN),
                               NULL, szDesc, sizeof(szDesc), NULL);
   }
   SQLFreeHandle(SQL_HANDLE_ENV, henv);

   return TRUE;

  exit00:

   return FALSE;
}

//*---------------------------------------------------------------------------------
//| DisplayODBCDataTypes:
//|   This function will enumerate data type information.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   TRUE if successful,
//|   FALSE on error
//*---------------------------------------------------------------------------------
BOOL DisplayODBCDataTypes(CHILDINFO FAR * ci)
{
   RESULTSSET FAR *     rs;
   RETCODE              retcode;
   SWORD                cbCols;
   char                 szTitle[MAXBUFF];

   //
   // We'll use SQLGetTypeInfo for this query.  Since this function can return more
   //    than the standard types, we must first execute the query and then create
   //    the results set.
   //
   lstrcpy((LPSTR)szTitle, (LPSTR)ci->szClientTitle);
   lstrcat((LPSTR)szTitle, (LPSTR)szDash);
   lstrcat((LPSTR)szTitle, (LPSTR)szODBCDataTypes);
   retcode = SQLGetTypeInfo(ci->hstmt, SQL_ALL_TYPES);
   if(retcode != SQL_SUCCESS) {
      PrintErrors(ci, SQL_HANDLE_STMT);
      return FALSE;
   }

   if(!(cbCols = GetNumResultsCols(ci->hstmt)))
      return FALSE;

   rs = GetConnectWindowResultsNode(ci);
   if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, cbCols, (LPSTR)szTitle))
      return FALSE;

   //
   // Set the meta data
   //
   SetMetaDataFromSql(ci->hwndOut, ci->hstmt, rs, cbCols);

   //
   // Now create the MDI child window which will hold the results.
   //
   if(!CreateResultsWindow(ci, rs))
      goto exit00;

   //
   // Loop through each data source and add it to the results set.
   //
   FetchAllRecordsToResults(ci->hwndOut, ci->hstmt, rs, cbCols, TRUE);
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);

   return TRUE;

  exit00:
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| EditPipe:
//|   This function allows the user to create a new pipe.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   TRUE if successful,
//|   FALSE on error
//*---------------------------------------------------------------------------------
void EditPipe(CHILDINFO FAR * ci)
{
   HWND     fHwnd=GetFocus();

   if(-1 == DialogBoxParam(ci->hInst,
                           MAKEINTRESOURCE(IDD_EDIT_PIPE),
                           ci->hwnd,
                           (DLGPROC) EditPipeWndProc, (LPARAM)ci))
      MessageBox(NULL, "Could not open dialog box.",
                 "Pipe", MB_ICONEXCLAMATION);

   if(fHwnd)
      SetFocus(fHwnd);
}

//*------------------------------------------------------------------------
//| IsValidParms:
//|   Verify that the parameters specified are the correct comma
//|   separated format.
//| Parms:
//|   hwnd              Window handle for errors
//|   szParms           The null terminated list of parms
//| Returns:
//|   TRUE if they are valid, FALSE on error
//*------------------------------------------------------------------------
BOOL WINAPI IsValidParms(HWND hwnd, LPSTR szParms)
{
   LPSTR       str=szParms, nstr;
   char        sztmp[MAXSQL];
   int         iNum, iCnt=0;

   lstrcpy(sztmp, szParms);
   nstr = str = strtok(sztmp, szCOMMA);
   while(str) {
      ++iCnt;
      if(!(strlen(str) == 1 && *str == '0')) {
         iNum = atoi(str);
         while(*str) {
            if(*str < '0' ||
               *str > '9')
               goto invalidint;
            ++str;
         }

         // It was not 0, so if atoi returned 0 it was invalid
         if(!iNum)
            goto invalidint;
      }

      nstr = str = strtok(NULL, szCOMMA);
   }

   if(iCnt <= MAXPARMS)
      return TRUE;
   else {
      szMessageBox(hwnd,
                   MB_ICONEXCLAMATION | MB_OK,
                   (LPSTR)szErrorMsgTitle,
                   iLoadString(idsTooManyParms, OutStr, MAXBUFF),
                   iCnt,
                   MAXPARMS);
      return FALSE;
   }

  invalidint:
   szMessageBox(hwnd,
                MB_ICONEXCLAMATION | MB_OK,
                (LPSTR)szErrorMsgTitle,
                iLoadString(idsInvalidInt, OutStr, MAXBUFF),
                nstr);

   return FALSE;
}

//*------------------------------------------------------------------------
//| EditPipeWndProc:
//|   Message handler for creating a new pipe.
//| Parms:
//|   in       Standard window parms
//| Returns:
//|   Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN EditPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   static CHILDINFO FAR *     ci;
   static                     cbNames;
   static char                szName[MAXNAME];

   switch(msg) {
     case WM_INITDIALOG:
      {
         ci = (CHILDINFO FAR *)lParam;
         CenterDialog(hDlg);

         SendMessage(GetDlgItem(hDlg, IDC_NAME), CB_LIMITTEXT, MAXPARMS, 0L);
         SendMessage(GetDlgItem(hDlg, IDE_SQL), EM_LIMITTEXT, MAXSQL, 0L);
         SendMessage(GetDlgItem(hDlg, IDE_PARMS), EM_LIMITTEXT, (MAXNAME*2), 0L);
         CheckRadioButton(hDlg, IDR_VALUE, IDR_ADDRESS, IDR_VALUE);
         SendMessage(hDlg, USER_RESETLIST, 0, 0L);
         SendMessage(hDlg, USER_SETSTATES, 0, 0L);
      }
      return TRUE;

      // This user message is sent when the list needs to be refreshed
     case USER_RESETLIST:
      {
         LPSTR       str, addstr;

         addstr = str = (LPSTR)GetMemory(1000);
         if(!addstr)
            return TRUE;
         cbNames = 0;
         if(str) {
            SendMessage(GetDlgItem(hDlg, IDC_NAME), CB_RESETCONTENT, 0, 0L);
            if(GetPrivateProfileString((LPSTR)szPIPES, NULL, NULL,
                                       str, 1000, szLABINI))
               while(*addstr) {
                  ++cbNames;
                  SendMessage(GetDlgItem(hDlg, IDC_NAME),
                              CB_ADDSTRING, 0,
                              (LPARAM)(LPSTR)addstr);
                  addstr = addstr + lstrlen(addstr) + 1;
               }
         }
         ReleaseMemory(str);
         if(cbNames)
            SendMessage(GetDlgItem(hDlg, IDC_NAME), CB_SETCURSEL, 0, 0L);
         SendMessage(hDlg, USER_SETDEFAULTS, 0, 0L);
         SendMessage(hDlg, USER_SETSTATES, 0, 0L);
      }
      return TRUE;

      // This user defined message will set the state of controls
     case USER_SETSTATES:
      EnableWindow(GetDlgItem(hDlg, IDB_DELETE), cbNames);
      EnableWindow(GetDlgItem(hDlg, IDE_SQL), cbNames);
      EnableWindow(GetDlgItem(hDlg, IDR_VALUE), cbNames);
      EnableWindow(GetDlgItem(hDlg, IDR_ADDRESS), cbNames);
      EnableWindow(GetDlgItem(hDlg, IDE_PARMS), cbNames);
      EnableWindow(GetDlgItem(hDlg, IDOK), cbNames);
      return TRUE;

      // This user defined message is for setting default values
     case USER_SETDEFAULTS:
      {
         char  szParmType[10];
         char  szSql[MAXSQL];
         char  szParms[MAXBUFF];

         HWND  hName = GetDlgItem(hDlg, IDC_NAME);

         if(cbNames == 0) {               // No current driver
            SetDlgItemText(hDlg, IDE_SQL, "");
            SetDlgItemText(hDlg, IDE_PARMS, "");
            return TRUE;
         }
         SendMessage(hName, CB_GETLBTEXT,
                     (WPARAM)SendMessage(hName, CB_GETCURSEL, 0, 0L),
                     (LPARAM)(LPSTR)szName);
         if(GetPrivateProfileString(szName, szSQL, NULL, szSql, sizeof(szSql), szLABINI))
            SetDlgItemText(hDlg, IDE_SQL, szSql);
         else
            SetDlgItemText(hDlg, IDE_SQL, "");
         if(GetPrivateProfileString(szName, szPARMS, NULL, szParms, sizeof(szParms), szLABINI))
            SetDlgItemText(hDlg, IDE_PARMS, szParms);
         else
            SetDlgItemText(hDlg, IDE_PARMS, "");
         if(GetPrivateProfileString(szName, szPARMOPT, NULL, szParmType, sizeof(szParmType), szLABINI))
            if(lstrcmpi(szVALUE, szParmType) == 0)
               CheckRadioButton(hDlg, IDR_VALUE, IDR_ADDRESS, IDR_VALUE);
            else
               CheckRadioButton(hDlg, IDR_VALUE, IDR_ADDRESS, IDR_ADDRESS);
         CheckDlgButton(hDlg, IDX_DELETE, GetPrivateProfileInt(szName, szDELETEOPT, 0, szLABINI));
      }
      return TRUE;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDB_NEW:
         {
            NEWPIPE  np;

            np.hwnd = hDlg;
            np.hInst = ci->hInst;
            NewPipe(&np);
            if(np.fSuccess) {
               lstrcpy(szName, np.szName);
               SendMessage(GetDlgItem(hDlg, IDC_NAME), CB_SETCURSEL,
                           (WPARAM)SendMessage(GetDlgItem(hDlg, IDC_NAME), CB_ADDSTRING,
                                               0, (LPARAM)(LPSTR)szName), 0L);
               if(cbNames)
                  SendMessage(hDlg, USER_SETDEFAULTS, 0, 0L);
               else
                  SendMessage(hDlg, USER_RESETLIST, 0, 0L);
            }
         }
         return TRUE;

        case IDB_DELETE:
         GetText(GetDlgItem(hDlg, IDC_NAME), szName);
         wsprintf(OutStr, szDeletePipe, (LPSTR)szName);
         if(IDOK == MessageBox(hDlg, OutStr, szEditPipe, MB_OKCANCEL)) {
            WritePrivateProfileString(szName, NULL, NULL, szLABINI);
            WritePrivateProfileString(szPIPES, szName, NULL, szLABINI);
            SendMessage(hDlg, USER_RESETLIST, 0, 0L);
         }
         return TRUE;

         //
         // Read in the info from the dialog, validate the parms, write to file
         //
        case IDOK:
         {
            char  szSql[MAXSQL];
            char  szParms[MAXBUFF];

            GetDlgItemText(hDlg, IDC_NAME, (LPSTR)szName, sizeof(szName));

            GetDlgItemText(hDlg, IDE_PARMS, szParms, sizeof(szParms));
            if(IsValidParms(hDlg, szParms)) {
               WritePrivateProfileString(szName, szPARMS, szParms, szLABINI);

               GetDlgItemText(hDlg, IDE_SQL, szSql, sizeof(szSql));
               WritePrivateProfileString(szName, szSQL, szSql, szLABINI);

               if(IsDlgButtonChecked(hDlg, IDR_VALUE))
                  WritePrivateProfileString(szName, szPARMOPT, szVALUE, szLABINI);
               else
                  WritePrivateProfileString(szName, szPARMOPT, szADDRESS, szLABINI);

               WritePrivateProfileString(szName, szDELETEOPT,
                                         (IsDlgButtonChecked(hDlg, IDX_DELETE)) ? (LPSTR)"1" : (LPSTR)"0",
                                         szLABINI);
            }
         }
         return TRUE;

        case IDCANCEL:
         EndDialog(hDlg, IDCANCEL);;
         return TRUE;
      }
      // Now check for control notification messages
      switch(HIWORD(lParam)) {
        case CBN_SELENDOK:
        case CBN_KILLFOCUS:
         SendMessage(hDlg, USER_SETDEFAULTS, TRUE, 0L);
         return TRUE;

        default:
         break;
      }
      break;

     default:
      return FALSE;
   }
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| NewPipe:
//|   This function allows the user to create a new pipe.
//| Parms:
//|   in       ci                   CHILDINFO information
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void NewPipe(NEWPIPE FAR * np)
{
   HWND     fHwnd=GetFocus();

   if(-1 == DialogBoxParam(np->hInst,
                           MAKEINTRESOURCE(IDD_NEW_PIPE),
                           np->hwnd,
                           (DLGPROC) NewPipeWndProc, (LPARAM)np))
      MessageBox(NULL, "Could not open dialog box.",
                 "Pipe", MB_ICONEXCLAMATION);

   if(fHwnd)
      SetFocus(fHwnd);
}

//*------------------------------------------------------------------------
//| NewPipeWndProc:
//|   Message handler for creating a new pipe.
//| Parms:
//|   in       Standard window parms
//| Returns:
//|   Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN NewPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   char                       szName[MAXNAME];
   static NEWPIPE FAR *       np;

   switch(msg) {
     case WM_INITDIALOG:
      {
         np = (NEWPIPE FAR *)lParam;
         CenterDialog(hDlg);
         SendMessage(GetDlgItem(hDlg, IDE_NAME), EM_LIMITTEXT, MAXNAME, 0L);
      }
      return TRUE;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         {
            char szTmp[MAXNAME];

            //
            // Don't allow names with [,], or = in them, nor any
            // reserved section names
            //
            GetText(GetDlgItem(hDlg, IDE_NAME), (LPSTR)szName);
            if(!ValidName((LPSTR)szName) ||
               !*szName ||
               !lstrcmpi((LPSTR)szName, szSCREEN) ||
               !lstrcmpi((LPSTR)szName, szFONT) ||
               !lstrcmpi((LPSTR)szName, szCONNECTOPTIONS) ||
               !lstrcmpi((LPSTR)szName, szPIPES)) {

               szMessageBox(hDlg,
                            MB_ICONEXCLAMATION | MB_OK,
                            (LPSTR)szErrorMsgTitle,
                            iLoadString(idsInvalidName, OutStr, MAXBUFF),
                            (LPSTR)szName);
               return TRUE;
            }
            if(GetPrivateProfileString(szPIPES, szName, NULL,
                                       szTmp, sizeof(szTmp), szLABINI))
               MessageBox(hDlg, szDuplicatePipe, szEditPipe, MB_OK);
            else {
               lstrcpy(np->szName, szName);
               np->fSuccess = TRUE;
               WritePrivateProfileString(szPIPES, szName, szInstalled, szLABINI);
               EndDialog(hDlg, IDOK);
            }
         }
         return TRUE;

        case IDCANCEL:
         np->fSuccess = FALSE;
         EndDialog(hDlg, IDCANCEL);
         return TRUE;
      }
      break;

     default:
      return FALSE;
   }
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| HandlePipe:
//|   This function will use the active results set and run use pipes against it.
//| Parms:
//|   lpci              Connection window information
//|   lpri              Ative results set
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void INTFUN HandlePipe(lpCHILDINFO lpci, lpRESULTSINFO lpri)
{
   HWND                 fHwnd=GetFocus();

   if(-1 == DialogBoxParam(lpci->hInst,
                           MAKEINTRESOURCE(IDD_DO_PIPE),
                           lpci->hwnd,
                           (DLGPROC) DoPipeWndProc, (LPARAM)(lpRESULTSINFO)lpri))
      MessageBox(NULL, "Could not open dialog box.",
                 "HandlePipe", MB_ICONEXCLAMATION);

   if(fHwnd)
      SetFocus(fHwnd);
}

//*------------------------------------------------------------------------
//| DoPipeWndProc:

//|   Handle dialog messages for IDD_DO_PIPE.
//| Parms:
//|   in       Standard window parms
//| Returns:
//|   Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN DoPipeWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
   static CHILDINFO FAR *     ci;
   static RESULTSSET FAR *    rs;
   static SDWORD              cbPipes;

   switch(msg) {
     case WM_INITDIALOG:
      {
         lpRESULTSINFO rwi;
         rwi = (lpRESULTSINFO)lParam;
         ci = rwi->ci;
         rs = rwi->rs;
         CenterDialog(hDlg);
         cbPipes = RefreshPipeList(GetDlgItem(hDlg, IDL_PIPES));
         EnableWindow(GetDlgItem(hDlg, IDL_PIPES), (int)(cbPipes));
         EnableWindow(GetDlgItem(hDlg, IDOK), (int)(cbPipes));
      }
      return TRUE;

     case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam)) {
         // User has clicked OK.  Retrieve an array of the selected indexes
         // and run the current pipe against each.  Finally see if this
         // pipe wants to delete items.
        case IDOK:
         {
            int         cbCnt;
            int FAR *   xSel;
            int         dex;
            char        szPipeName[MAXBUFF];

            Busy(TRUE);
            cbCnt = (int)SendMessage(rs->hwndList, LB_GETSELCOUNT, 0, 0L);
            xSel = (int FAR *)GetMemory(sizeof(int) * cbCnt);
            if(!xSel) {
               Busy(FALSE);
               return TRUE;
            }
            SendMessage(rs->hwndList, LB_GETSELITEMS, cbCnt, (LPARAM)(int FAR *)xSel);
            SendMessage(GetDlgItem(hDlg, IDL_PIPES), LB_GETTEXT,
                        (WPARAM)SendMessage(GetDlgItem(hDlg, IDL_PIPES), LB_GETCURSEL, 0, 0L),
                        (LPARAM)(LPSTR)szPipeName);
            DoPipe(rs, ci, (LPSTR)szPipeName, xSel, cbCnt);
            if(GetPrivateProfileInt(szPipeName, szDELETEOPT, 0, szLABINI))
               for(dex=cbCnt-1;  dex>=0;  dex--)
                  SendMessage(rs->hwndList, LB_DELETESTRING, xSel[dex], 0L);
            Busy(FALSE);
            ReleaseMemory(xSel);
         }
         return TRUE;

        case IDCANCEL:
         EndDialog(hDlg, IDCANCEL);
         return TRUE;
      }
      return TRUE;

     default:
      return FALSE;
   }
   return FALSE;
}

//*---------------------------------------------------------------------------------
//| RefreshPipeList:
//|   This function will reset the list of pipes based on the values returned
//|      from GetPipeName.  Having this extra level of abstraction allows us
//|      to change the location of the pipes without affecting this code.
//| Parms:
//|   in       hwnd                 Window handle to list box to fill
//| Returns:
//|   Number of items selected
//*---------------------------------------------------------------------------------
SDWORD RefreshPipeList(HWND hDlg)
{
#define MAX_PIPE_SIZE 4000
   LPSTR    szPipes, str;
   SDWORD count=0;

   szPipes = (LPSTR)GetMemory(MAX_PIPE_SIZE);
   if(!szPipes)
      return 0;

   SendMessage(hDlg, LB_RESETCONTENT, 0, 0L);
   GetPipeNames((LPSTR)szPipes, MAX_PIPE_SIZE);
   str = szPipes;
   while(*str) {
      SendMessage(hDlg, LB_ADDSTRING, 0, (LPARAM)(LPSTR)str);
      str += lstrlen(str) + 1;
      ++count;
   }
   if(count)
      SendMessage(hDlg, LB_SETCURSEL, 0, 0L);

   ReleaseMemory(szPipes);
   return count;
}

//*---------------------------------------------------------------------------------
//| DoPipe:
//|   This function will implement a pipe against the object which is passed in.
//| Parms:
//|   in       rs                   Pointer to results set describing data
//|   in       ci                   Connection window information
//|   in       szPipeName           Name of pipe to use
//|   in       xPipes               Array of items to pipe
//|   in       cbCnt                Number of items
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoPipe(RESULTSSET FAR * rs, CHILDINFO FAR * ci, LPSTR szPipeName,
            int FAR xPipes[], int cbCnt)
{
   SDWORD            cbDataAtExec=SQL_DATA_AT_EXEC;
   int               dex;
   int               cParm;
   char              szpsql[200];
   char              szparms[35];
   char              parmopt[10];
   UWORD             cParmCnt=0;
   LPSTR             str=szparms;
   LPSTR             numstr=szparms;
   ROWDATA FAR *     rd;
   RETCODE           retcode;

   //
   // Make sure we can retrieve the pipe from the .ini file.  Also get the parameter
   //    values if they are available.
   //
   if(!GetPrivateProfileString(szPipeName, szSQL, NULL, szpsql, sizeof(szpsql), szLABINI)) {
      szWrite(ci->hwndOut,
              GetidsString(idsPipeNotFound, OutStr, MAXBUFF),
              (LPSTR)szPipeName);
      return;
   }
   GetPrivateProfileString(szPipeName, szPARMS, NULL, szparms, sizeof(szparms), szLABINI);
   GetPrivateProfileString(szPipeName, szPARMOPT, NULL, parmopt, sizeof(parmopt), szLABINI);

   //
   // If there are parameters to set, set each one based on user desription
   //
   if(str && *str)
      PrepareParmList(str);
   SQLFreeStmt(ci->hstmt, SQL_CLOSE);

   //
   // What type of parameter passing to do?  value means substitue text and execute,
   //    address means use parameter data.  The following will handle the former, the
   //    next more complicated routine will pass parameters.
   //
   if(lstrcmpi(parmopt, szVALUE) == 0) {
      DoPipeByValue(rs, ci, szpsql, str, xPipes, cbCnt, (LPSTR)szPipeName);
      return;
   }

   //
   // Now prepare the user's statement, return on error
   //
   retcode = SQLPrepare(ci->hstmt, szpsql, SQL_NTS);
   if(retcode != SQL_SUCCESS) {
      PrintErrors(ci, SQL_HANDLE_STMT);
      return;
   }

   //
   // For each parameter, make sure it's in our range, then see which mode we want,
   //    address (param data) or value (textual substitution).
   //
   while(*str) {
      ++cParmCnt;
      cParm = lpatoi(str);
      if(cParm > rs->cbColumns)
         szWrite(ci->hwndOut,
                 GetidsString(idsInvalidParamValue, OutStr, MAXBUFF),
                 cParm, (LPSTR)szPipeName);
      else {
         retcode = SQLBindParameter(ci->hstmt,
                                    cParmCnt, SQL_PARAM_INPUT,
                                    SQL_C_CHAR, SQL_CHAR,
                                    rs->md[cParm-1].precision,
                                    rs->md[cParm-1].scale,
                                    (PTR FAR *)(cParm - 1),
                                    rs->md[cParm-1].precision,
                                    &cbDataAtExec);
         if(retcode != SQL_SUCCESS)
            PrintErrors(ci, SQL_HANDLE_STMT);
      }
      str += lstrlen(str) + 1;
   }

   //
   // For each row selected, retrieve the row data structure associated with it,
   //    then do an execute.  When prompted for SQL_NEED_DATA, substitute the
   //    correct parameter address.
   //
   for(dex=0;  dex<cbCnt;  dex++) {
      int      cNeedParm;
      rd = (ROWDATA FAR *)SendMessage(rs->hwndList, LB_GETITEMDATA, (WPARAM)xPipes[dex], 0L);
      retcode = SQLExecute(ci->hstmt);
      switch(retcode) {
         //
         // User had parameter data which we are being prompted for.  Since we
         //    did the SQLSetParam using the index number, we simply use that
         //    value to index into our column data and give the driver what
         //    it requires.
         //
        case SQL_NEED_DATA:
         retcode = SQLParamData(ci->hstmt, (PTR FAR *)&cNeedParm);
         while(retcode == SQL_NEED_DATA) {
            retcode = SQLPutData(ci->hstmt, rd->cd[cNeedParm].szCols, SQL_NTS);
            retcode = SQLParamData(ci->hstmt, (PTR FAR *)&cNeedParm);
         }
         break;

        case SQL_SUCCESS:
         CheckForResults(ci);
         break;

        default:
         PrintErrors(ci, SQL_HANDLE_STMT);
         break;
      }
   }

   SQLFreeStmt(ci->hstmt, SQL_CLOSE);
   return;
}

//*---------------------------------------------------------------------------------
//| PrepareParmList:
//|   The user will enter a list of numbers separated by columns which will
//|   designate which parms go for what marker.  We will turn this list into
//|   a double-null terminated list which can be used later for retrieval.
//| Parms:
//|   in       str                  Pointer to string to work on
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void PrepareParmList(LPSTR str)
{
   LPSTR tmpstr=str;
   LPSTR lststr=tmpstr;

   //
   // Convert parm list into a double-null terminated list
   //
   while(tmpstr) {
      if((tmpstr = _fstrchr(str, ','))) {
         lststr = tmpstr + 1;
         *tmpstr++ = '\0';
      }
      else {
         lststr += lstrlen(lststr) + 1;
         *lststr = '\0';
      }
   }
}

//*---------------------------------------------------------------------------------
//| DoPipeByValue:
//|   This function will process all of the selcted values by creating a
//|   statement which has all parameters embedded in it.
//|
//|   Note:    There are some servers which use a semi-colon for the name of
//|            a stored procedure, but which cannot handle doing a drop of the
//|            object with this name.  If the pipe name is the reserved name
//|            of "Drop Procedure (with semi-colon)" then we will strip off the
//|            name since this can't really be done any other way.
//| Parms:
//|   in       rs                   Results set pointer
//|   in       ci                   Child information
//|   in       szUserSQL               Statement with parameter markers
//|   in       szParms              Parameter list, double null terminated
//|   in       xPipes               Array of indexes to use for param data
//|   in       cbCnt                Number of records to process
//|   in       szPipeName           Pipe names
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoPipeByValue(RESULTSSET FAR * rs, CHILDINFO FAR * ci, LPSTR szUserSQL,
                   LPSTR szParms, int FAR xPipes[], int cbCnt, LPSTR szPipeName)
{
   char              szUserSQLCopy[300];
   char              sqlstmt[300];
   LPSTR             szParmStrOut;
   LPSTR             szParmStrIn;
   LPSTR             szParmStrLast;
   LPSTR             str=szParms;
   int               dex;
   int               cParm;
   ROWDATA FAR *     rd;
   BOOL              fSemiProc=FALSE;

   // Handle special case of a procedure name with a semi-colon
   if(lstrcmp(szPipeName, szDROPPROCSEMI) == 0)
      fSemiProc = TRUE;

   //
   // For each record selected, create a statement which can be executed by finding
   //    parameter markers and replacing them at run time.
   //
   for(dex=0;  dex<cbCnt;  dex++) {
      _fmemset(sqlstmt, 0, sizeof(sqlstmt));
      _fmemset(szUserSQLCopy, 0, sizeof(szUserSQLCopy));
      lstrcpy(szUserSQLCopy, szUserSQL);
      szParmStrOut = sqlstmt;
      szParmStrIn = szUserSQLCopy;
      szParmStrLast = szParmStrIn;
      str = szParms;
      rd = (ROWDATA FAR *)SendMessage(rs->hwndList, LB_GETITEMDATA, (WPARAM)xPipes[dex], 0L);
      while(*str) {
         cParm = lpatoi(str);
         if(cParm > rs->cbColumns)
            szWrite(ci->hwndOut,
                    GetidsString(idsInvalidParamValue, OutStr, MAXBUFF),
                    cParm, (LPSTR)szPipeName);
         else if(szParmStrIn && *szParmStrIn) {
            if((szParmStrIn = _fstrchr(szParmStrIn, '?'))) {
               *szParmStrIn++ = '\0';
               lstrcpy(szParmStrOut, szParmStrLast);
               _fstrcat(szParmStrOut, rd->cd[cParm-1].szCols);
               // Remove semi-colon for special case of drop procedure
               if(fSemiProc) {
                  LPSTR    str = _fstrchr(szParmStrOut, ';');
                  if(str)
                     *str = '\0';
               }
               szParmStrLast = szParmStrIn;
            }
            else
               lstrcpy(szParmStrOut, szParmStrLast);              // end of list
         }
         str += lstrlen(str) + 1;
      }
      if(*szParmStrLast)
         _fstrcat(szParmStrOut, szParmStrLast);
      ExecuteCmds(ci, sqlstmt);
   }

   return;
}


INFO.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   INFO.H
//|      This file contains the prototypes and defines for executing SQL
//|         statements and displaying their results.
//*---------------------------------------------------------------------------------
#ifndef info_DEFS
#define info_DEFS

#include <windows.h>
#include "standard.h"
#include "errcheck.h"
#include "ctl3d.h"
#include "results.h"
#include "child.h"
#include "execute.h"
#include "stdlib.h"

//*---------------------------------------------------------------------------------
//|   Macros and Defines
//*---------------------------------------------------------------------------------
#define  MAX_QUALIFIER     45
#define  MAX_OWNER         40
#define  MAX_NAME          40
#define  MAX_TYPE          40
#define  MAX_SPEC_VAL      128

#define LIST_ALL        0
#define LIST_TABLES     1
#define LIST_VIEWS      2
#define  USER_SETCHECKSTATE         WM_USER + 1
#define  USER_SETBUTTONSTATE        WM_USER + 2
#define  USER_RESETLIST             WM_USER + 3
#define  USER_SETSTATES             WM_USER + 4
#define  USER_SETDEFAULTS           WM_USER + 5

#define  IDE_QUALIFIER     151
#define  IDE_OWNER         153
#define  IDE_NAME          155
#define  IDL_LIST          157
#define  IDX_ALL           158
#define  IDX_TABLES        159
#define  IDX_VIEWS         160
#define  IDL_PIPES         162
#define  LIST_WIDTH        184

#define SQL_FN_IDS_BASE 100

#define idsSQLAllocConnect      SQL_FN_IDS_BASE+1
#define idsSQLAllocEnv          SQL_FN_IDS_BASE+2
#define idsSQLAllocHandle       SQL_FN_IDS_BASE+3
#define idsSQLAllocStmt         SQL_FN_IDS_BASE+4
#define idsSQLBindCol           SQL_FN_IDS_BASE+5
#define idsSQLBindParam         SQL_FN_IDS_BASE+6
#define idsSQLBindParameter     SQL_FN_IDS_BASE+7
#define idsSQLBrowseConnect     SQL_FN_IDS_BASE+8
#define idsSQLBulkOperations    SQL_FN_IDS_BASE+9
#define idsSQLCancel            SQL_FN_IDS_BASE+10
#define idsSQLCloseCursor       SQL_FN_IDS_BASE+11
#define idsSQLColAttribute      SQL_FN_IDS_BASE+12
#define idsSQLColAttributes     SQL_FN_IDS_BASE+13
#define idsSQLColumnPrivileges  SQL_FN_IDS_BASE+14
#define idsSQLColumns           SQL_FN_IDS_BASE+15
#define idsSQLConnect           SQL_FN_IDS_BASE+16
#define idsSQLCopyDesc          SQL_FN_IDS_BASE+17
#define idsSQLDataSources       SQL_FN_IDS_BASE+18
#define idsSQLDescribeCol       SQL_FN_IDS_BASE+19
#define idsSQLDescribeParam     SQL_FN_IDS_BASE+20
#define idsSQLDisconnect        SQL_FN_IDS_BASE+21
#define idsSQLDriverConnect     SQL_FN_IDS_BASE+22
#define idsSQLEndTran           SQL_FN_IDS_BASE+23
#define idsSQLError             SQL_FN_IDS_BASE+24
#define idsSQLExecDirect        SQL_FN_IDS_BASE+25
#define idsSQLExecute           SQL_FN_IDS_BASE+26
#define idsSQLExtendedFetch     SQL_FN_IDS_BASE+27
#define idsSQLFetch             SQL_FN_IDS_BASE+28
#define idsSQLFetchScroll       SQL_FN_IDS_BASE+29
#define idsSQLForeignKeys       SQL_FN_IDS_BASE+30
#define idsSQLFreeConnect       SQL_FN_IDS_BASE+31
#define idsSQLFreeEnv           SQL_FN_IDS_BASE+32
#define idsSQLFreeHandle        SQL_FN_IDS_BASE+33
#define idsSQLFreeStmt          SQL_FN_IDS_BASE+34
#define idsSQLGetConnectAttr    SQL_FN_IDS_BASE+35
#define idsSQLGetConnectOption  SQL_FN_IDS_BASE+36
#define idsSQLGetCursorName     SQL_FN_IDS_BASE+37
#define idsSQLGetData           SQL_FN_IDS_BASE+38
#define idsSQLGetDescField      SQL_FN_IDS_BASE+39
#define idsSQLGetDescRec        SQL_FN_IDS_BASE+40
#define idsSQLGetDiagField      SQL_FN_IDS_BASE+41
#define idsSQLGetDiagRec        SQL_FN_IDS_BASE+42
#define idsSQLGetEnvAttr        SQL_FN_IDS_BASE+43
#define idsSQLGetFunctions      SQL_FN_IDS_BASE+44
#define idsSQLGetInfo           SQL_FN_IDS_BASE+45
#define idsSQLGetStmtAttr       SQL_FN_IDS_BASE+46
#define idsSQLGetStmtOption     SQL_FN_IDS_BASE+47
#define idsSQLGetTypeInfo       SQL_FN_IDS_BASE+48
#define idsSQLMoreResults       SQL_FN_IDS_BASE+49
#define idsSQLNativeSQL         SQL_FN_IDS_BASE+50
#define idsSQLNumParams         SQL_FN_IDS_BASE+51
#define idsSQLNumResultCols     SQL_FN_IDS_BASE+52
#define idsSQLParamOptions      SQL_FN_IDS_BASE+53
#define idsSQLPrepare           SQL_FN_IDS_BASE+54
#define idsSQLPrimaryKeys       SQL_FN_IDS_BASE+55
#define idsSQLProcedureColumns  SQL_FN_IDS_BASE+56
#define idsSQLProcedures        SQL_FN_IDS_BASE+57
#define idsSQLPutData           SQL_FN_IDS_BASE+58
#define idsSQLRowCount          SQL_FN_IDS_BASE+59
#define idsSQLSetConnectAttr    SQL_FN_IDS_BASE+60
#define idsSQLSetConnectOption  SQL_FN_IDS_BASE+61
#define idsSQLSetCursorName     SQL_FN_IDS_BASE+62
#define idsSQLSetDescField      SQL_FN_IDS_BASE+63
#define idsSQLSetDescRec        SQL_FN_IDS_BASE+64
#define idsSQLSetEnvAttr        SQL_FN_IDS_BASE+65
#define idsSQLSetParam          SQL_FN_IDS_BASE+66
#define idsSQLSetPos            SQL_FN_IDS_BASE+67
#define idsSQLSetScrollOptions  SQL_FN_IDS_BASE+68
#define idsSQLSetStmtAttr       SQL_FN_IDS_BASE+69
#define idsSQLSetStmtOption     SQL_FN_IDS_BASE+70
#define idsSQLSpecialColumns    SQL_FN_IDS_BASE+71
#define idsSQLStatistics        SQL_FN_IDS_BASE+72
#define idsSQLTablePrivileges   SQL_FN_IDS_BASE+73
#define idsSQLTables            SQL_FN_IDS_BASE+74
#define idsSQLTransact          SQL_FN_IDS_BASE+75

#define IDD_EDIT_PIPE                             700
#define IDC_NAME                                  701
#define IDB_NEW                                   702
#define IDB_DELETE                                703
#define IDE_SQL                                   704
#define IDR_VALUE                                 705
#define IDR_ADDRESS                               706
#define IDE_PARMS                                 707
#define IDX_DELETE                                708

#define IDD_NEW_PIPE                              730
#define IDD_DO_PIPE                               750

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern dCSEG(char) szResultsClass[];
extern dCSEG(char) szODBCFunctions[];

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
void EditPipe(CHILDINFO FAR * ci);
BOOL DisplayGetInfo(CHILDINFO FAR * ci);
BOOL DisplayODBCFunctions(CHILDINFO FAR * ci);
BOOL DisplayODBCDataSources(CHILDINFO FAR * ci);
BOOL DisplayODBCDataTypes(CHILDINFO FAR * ci);
void INTFUN HandlePipe(lpCHILDINFO lpci, lpRESULTSINFO lpri);

#endif


INI.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   INI.C
//|      This module contains the functions which work with SATOOL.INI.
//*---------------------------------------------------------------------------------
#include "ini.h"
#include "results.h"
#include <commdlg.h>

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
dCSEG(char) szLABINI[]                       = "ADMNDEMO.INI";
dCSEG(char) szSCREEN[]                       = "screen";
dCSEG(char) szMAX[]                          = "Maximized";
dCSEG(char) szCONNECTOPTIONS[]               = "Connect Options";
dCSEG(char) szMAXRESULTS[]                   = "Max Results Windows";
dCSEG(char) szFONT[]                         = "Font";
dCSEG(char) szFONTSIZE[]                     = "FontSize";
dCSEG(char) szFONTWEIGHT[]                   = "FontWeight";
dCSEG(char) szFONTNAME[]                     = "FontName";
dCSEG(char) szFONTRGB[]                      = "FontColor";
dCSEG(char) szSTRIKEOUT[]                    = "FontStrikeOut";
dCSEG(char) szUNDERLINE[]                    = "FontUnderline";
dCSEG(char) szFONTSTYLE[]                    = "FontStyle";
dCSEG(char) szPIPES[]                        = "Pipes";
dCSEG(char) szRegular[]                      = "Regular";
dCSEG(char) szArial[]                        = "Arial";
dCSEG(char) szMSSansSerif[]                  = "MS Sans Serif";
dCSEG(char) szINSTALL[]                      = "Installed";
dCSEG(char) szDROPPROC[]                     = "Drop Procedure (with semi-colon)";
dCSEG(char) szDROPTABLE[]                    = "Drop Table";
dCSEG(char) szSQLSECTION[]                   = "Sql";
dCSEG(char) szPARMSSECTION[]                 = "Parms";
dCSEG(char) szPARMOPTSECTION[]               = "ParmOpt";
dCSEG(char) szDELETESECTION[]                = "Delete";
dCSEG(char) szSQLDROPSTMT[]                  = "drop procedure ?";
dCSEG(char) szSQLDROPTABLESTMT[]             = "drop table ?";
dCSEG(char) szPARMVALUE[]                    = "value";
dCSEG(char) szPARM1[]                        = "1";
dCSEG(char) szPARM3[]                        = "3";

//*---------------------------------------------------------------------------------
//|   Local Functions
//*---------------------------------------------------------------------------------
LPSTR GetFontStyle(LPSTR szStyle, int cbStyle);
void SetDefaultFont(LOGFONT FAR * lf);
void SetDefaultRGB(COLORREF rgb);
void SetFontStyle(LPSTR szFontStyle);

//*---------------------------------------------------------------------------------
//| ResolveIniFile:
//|   Look for ADMNDEMO.INI, if not found, fill out the defaults.
//| Parms:
//|   Nothing
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
VOID INTFUN ResolveIniFile()
{
   // Max status is written after every exit, so if this constant cannot
   // be found, there is no file to find.
   if(GetPrivateProfileInt(szSCREEN, szMAX, -1, szLABINI) == -1) {
      // No file, create one
      WritePrivateProfileString(szPIPES, szDROPPROC, szINSTALL, szLABINI);
      WritePrivateProfileString(szDROPPROC, szSQLSECTION, szSQLDROPSTMT, szLABINI);
      WritePrivateProfileString(szDROPPROC, szPARMSSECTION, szPARM3, szLABINI);
      WritePrivateProfileString(szDROPPROC, szPARMOPTSECTION, szPARMVALUE, szLABINI);
      WritePrivateProfileString(szDROPPROC, szDELETESECTION, szPARM1, szLABINI);

      WritePrivateProfileString(szPIPES, szDROPTABLE, szINSTALL, szLABINI);
      WritePrivateProfileString(szDROPTABLE, szSQLSECTION, szSQLDROPTABLESTMT, szLABINI);
      WritePrivateProfileString(szDROPTABLE, szPARMSSECTION, szPARM3, szLABINI);
      WritePrivateProfileString(szDROPTABLE, szPARMOPTSECTION, szPARMVALUE, szLABINI);
      WritePrivateProfileString(szDROPTABLE, szDELETESECTION, szPARM1, szLABINI);
   }
}

//*---------------------------------------------------------------------------------
//| GetDefaultFont:
//|   This function will retrieve all of the font information that is stored in
//|   the .INI file and places it in the LOGFONT structure passed in.
//|   NOTE:  User should clear the LOGFONT structure to 0 (eg: fmemset(&lf, 0,sizeof())
//|     before calling this function.  The font name buffer must point to a user's
//|     allocated buffer.
//| Parms:
//|   in       lf             LOGFONT structure
//| Returns:
//|   Pointer to the LOGFONT structure passed in or NULL on error.
//*---------------------------------------------------------------------------------
LOGFONT FAR * GetDefaultFont(LOGFONT FAR * lf)
{
   lf->lfHeight = GetPrivateProfileInt(szFONT, szFONTSIZE, -12, szLABINI);
   lf->lfWeight = GetPrivateProfileInt(szFONT, szFONTWEIGHT, FW_NORMAL, szLABINI);
   lf->lfStrikeOut = GetPrivateProfileInt(szFONT, szSTRIKEOUT, 0, szLABINI);
   lf->lfUnderline = GetPrivateProfileInt(szFONT, szUNDERLINE, 0, szLABINI);
   lf->lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
   if(!GetPrivateProfileString(szFONT, szFONTNAME, NULL,
                               lf->lfFaceName, LF_FACESIZE, szLABINI))
      lstrcpy(lf->lfFaceName, (LPSTR)szArial);

   return lf;
}

//*---------------------------------------------------------------------------------
//| SetDefaultFont:
//|   This function uses the values in the LOGFONT structure passed in to write the
//|   default values to the .INI file.
//| Parms:
//|   in       lf             LOGFONT structure
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void SetDefaultFont(LOGFONT FAR * lf)
{
   char  szTmp[20];
   char  szpd[] = "%d";

   wsprintf(szTmp, szpd, lf->lfHeight);
   WritePrivateProfileString(szFONT, szFONTSIZE, szTmp, szLABINI);

   wsprintf(szTmp, szpd, lf->lfWeight);
   WritePrivateProfileString(szFONT, szFONTWEIGHT, szTmp, szLABINI);

   wsprintf(szTmp, szpd, lf->lfStrikeOut);
   WritePrivateProfileString(szFONT, szSTRIKEOUT, szTmp, szLABINI);

   wsprintf(szTmp, szpd, lf->lfUnderline);
   WritePrivateProfileString(szFONT, szUNDERLINE, szTmp, szLABINI);

   WritePrivateProfileString(szFONT, szFONTNAME, lf->lfFaceName, szLABINI);
}

//*---------------------------------------------------------------------------------
//| GetDefaultRGB:
//|   This function will retrieve the default rgbvalue from the ini file.
//| Parms:
//|   Nothing.
//| Returns:
//|   A COLORREF value that can be used for color.
//*---------------------------------------------------------------------------------
COLORREF GetDefaultRGB(void)
{
   COLORREF dft;
   LPSTR    lpsz;
   char     szBuff[20];

   if(!GetPrivateProfileString(szSCREEN, szFONTRGB, NULL,
                               szBuff, sizeof(szBuff), szLABINI))
      dft = RGB(0,0,0);          // If not specified, use Black
   else
      dft = strtoul(szBuff, &lpsz, 10);

   return dft;
}

//*---------------------------------------------------------------------------------
//| SetDefaultRGB:
//|   This function will set the default RGB value which will be used for
//|   color.
//| Parms:
//|   in       rgb                  The rgb value to use
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void SetDefaultRGB(COLORREF rgb)
{
   char     szBuff[20];

   wsprintf(szBuff, "%lu", rgb);
   WritePrivateProfileString(szSCREEN, szFONTRGB,
                             szBuff, szLABINI);
}

//*---------------------------------------------------------------------------------
//| GetFontStyle:
//|   This function will copy the default font style to the user's buffer.
//| Parms:
//|   in       szStyle                 Pointer to output for font sytle
//|   in       cbStyle                 Maximum size of buffer
//| Returns:
//|   Style to use, NULL otherwise
//*---------------------------------------------------------------------------------
LPSTR GetFontStyle(LPSTR szStyle, int cbStyle)
{
   if(!GetPrivateProfileString(szSCREEN, szFONTSTYLE, NULL,
                               szStyle, cbStyle, szLABINI))
      lstrcpy(szStyle, (LPSTR)szRegular);
   return szStyle;
}

//*---------------------------------------------------------------------------------
//| SetFontStyle:
//|   This function will set the default font style.
//| Parms:
//|   in       szFontStyle          The new font style
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void SetFontStyle(LPSTR szFontStyle)
{
   WritePrivateProfileString(szSCREEN, szFONTSTYLE,
                             szFontStyle, szLABINI);
}

//*---------------------------------------------------------------------------------
//| GetPipeNames:
//|   This function will get as many pipes as will fit into the user's
//|   buffer.  The list is simply retrieve via GetPrivateProfileString
//|   and therefore each pipe name will be null terminated with 2 nulls
//|   at the end of the entire list.
//| Parms:
//|   in       szPipes                 Pointer to string to hold names
//|   in       size                    Maximum size of pipe string
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void GetPipeNames(LPSTR szPipes, int size)
{
   if(!GetPrivateProfileString(szPIPES, NULL, NULL, szPipes, size,
                               szLABINI))
      *szPipes = '\0';
}

//*---------------------------------------------------------------------------------
//| PromptForFontName:
//|   This function will prompt the user for a font style and name.
//| Parms:
//|   in       szPipes                 Pointer to string to hold names
//|   in       size                    Maximum size of pipe string
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void PromptForFontName(HWND hwndOwner)
{
   CHOOSEFONT     cf;
   LOGFONT        lf;
   HWND           fHwnd=GetFocus();
   char           lpszStyle[LF_FACESIZE];

   memset(&lf, 0, sizeof(LOGFONT));
   memset(&cf, 0, sizeof(CHOOSEFONT));
   cf.lpLogFont = (LOGFONT FAR *)&lf;
   cf.lpszStyle = (LPSTR)lpszStyle;
   GetDefaultFont(&lf);

   cf.lStructSize = sizeof(CHOOSEFONT);
   cf.hwndOwner = hwndOwner;
   cf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT;
   cf.rgbColors = GetDefaultRGB();
   cf.nFontType = SCREEN_FONTTYPE;
   GetFontStyle(cf.lpszStyle, LF_FACESIZE);

   if(ChooseFont(&cf)) {
      SetDefaultFont(&lf);
      SetDefaultRGB(cf.rgbColors);
      SetFontStyle(cf.lpszStyle);
   }

   if(fHwnd)
      SetFocus(fHwnd);
}


INI.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   INI.H
//|      This file contains the prototypes and defines for executing SQL
//|         statements and displaying their results.
//*---------------------------------------------------------------------------------
#ifndef ini_DEFS
#define ini_DEFS

#include <windows.h>
#include "standard.h"
#include "errcheck.h"

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern dCSEG(char) szLABINI[];
extern dCSEG(char) szSCREEN[];
extern dCSEG(char) szMAX[];
extern dCSEG(char) szCONNECTOPTIONS[];
extern dCSEG(char) szMAXRESULTS[];
extern dCSEG(char) szFONTSIZE[];
extern dCSEG(char) szMSSansSerif[];

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
VOID INTFUN ResolveIniFile();
LOGFONT FAR * GetDefaultFont(LOGFONT FAR * lf);
COLORREF GetDefaultRGB(void);
void GetPipeNames(LPSTR szPipes, int size);
void PromptForFontName(HWND hwndOwner);

#endif


MENU.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   MENU.C
//|      This module will take care of enabling and disabling menus.
//*---------------------------------------------------------------------------------
#include <windows.h>
#include "menu.h"
#include "standard.h"
#include "child.h"
#include "admndemo.h"

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
extern lpCHILDINFO      lpActiveConn;
extern lpRESULTSINFO    lpActiveResults;
extern HWND             hwndCurMDIChild;

extern dCSEG(char)   szChildClass[];

UWORD uMenuIDs[] = {
   SQL_API_SQLTABLES,
   SQL_API_SQLCOLUMNS,
   SQL_API_SQLSTATISTICS,
   SQL_API_SQLPRIMARYKEYS,
   SQL_API_SQLFOREIGNKEYS,
   SQL_API_SQLTABLEPRIVILEGES,
   SQL_API_SQLCOLUMNPRIVILEGES,
   SQL_API_SQLSPECIALCOLUMNS,
   SQL_API_SQLPROCEDURES,
   SQL_API_SQLPROCEDURECOLUMNS,
   SQL_API_SQLTRANSACT,
   };

//*---------------------------------------------------------------------------------
//| ResetMenu:
//|   When user drops the menu, determine what items are to be enabled/disabled.
//| Parms:
//|   hMenu                Handle to the menu being displayed
//|   iMenu                Index to the menu
//|   iCalledFrom          Where were we called from?
//|                           1     From main WndProc
//|                           2     From connection window
//|                           3     Test output window
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
VOID INTFUN ResetMenu(HMENU hMenu, int iMenu)
{
   switch(GETMENUNUM(iMenu)) {
     case IDM_FILE_MENU:
      {
         UINT        uMnuFlag;
         HWND        hwnd = GetEditWindow(lpActiveConn);

         uMnuFlag = MNU_HAVE_CONNECTS;

         // Is there an active connection
         EnableMenuItem(hMenu, IDM_DISCONNECT, uMnuFlag);
         EnableMenuItem(hMenu, IDM_EXECUTE_FILE, uMnuFlag);
         EnableMenuItem(hMenu, IDM_OPEN,
                        (uMnuFlag == MF_ENABLED && !lpActiveResults) ?
                        MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_CLOSE,
                        (lpActiveConn && lpActiveConn->hFile && !lpActiveResults) ?
                        MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_SAVE,
                        (lpActiveConn && lpActiveConn->hFile && !lpActiveResults) ?
                        MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_SAVEAS,
                        (lpActiveConn &&
                         SendMessage(lpActiveConn->hwndIn, WM_GETTEXTLENGTH, 0, 0L) &&
                         !lpActiveResults) ?
                        MF_ENABLED : MF_GRAYED);
      }
      break;

     case IDM_EDIT_MENU:
      {
         HWND     hwnd = GetEditWindow(lpActiveConn);
         DWORD    sel=0;

         if(hwnd)
            sel = SendMessage(hwnd, EM_GETSEL, 0, 0L);

         EnableMenuItem(hMenu, IDM_UNDO,
                        (lpActiveConn && hwnd &&
                         SendMessage(hwnd, EM_CANUNDO, 0, 0L)) ? MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_CUT,
                        (lpActiveConn && hwnd &&
                         HIWORD(sel) != LOWORD(sel)) ? MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_COPY,
                        (lpActiveConn && hwnd &&
                         HIWORD(sel) != LOWORD(sel)) ? MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_PASTE,
                        (lpActiveConn && hwnd &&
                         IsClipboardFormatAvailable(CF_TEXT)) ? MF_ENABLED : MF_GRAYED);
         EnableMenuItem(hMenu, IDM_SELECT_ALL,
                        (lpActiveConn && hwnd &&
                         SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L)) ? MF_ENABLED : MF_GRAYED);
      }
      break;

     case IDM_EXECUTE_MENU:
      {
         HWND     hwnd = GetEditWindow(lpActiveConn);
         DWORD    sel=0;
         UINT     utmp;

         if(hwnd)
            sel = SendMessage(hwnd, EM_GETSEL, 0, 0L);

         EnableMenuItem(hMenu, IDM_EXECUTE_NOW,
                        (lpActiveConn && hwnd &&
                         hwnd == lpActiveConn->hwndIn &&
                         SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L)) ? MF_ENABLED : MF_GRAYED);

         // Need to support commit and rollback to call them
         utmp = (lpActiveConn && BitGet(lpActiveConn->fFunctions, MENU_SQLTRANSACT)) ?
            MF_ENABLED : MF_GRAYED;
         EnableMenuItem(hMenu, IDM_COMMIT, utmp);
         EnableMenuItem(hMenu, IDM_ROLLBACK, utmp);
      }
      break;

     case IDM_PIPE_MENU:
      {
         // Just look for active connection
         UINT utmp = MNU_HAVE_CONNECTS;

         // Just look for active connections and results
         EnableMenuItem(hMenu, IDM_PIPE_EDIT, utmp);
         EnableMenuItem(hMenu, IDM_PIPE_DO,
                        (lpActiveResults) ? MF_ENABLED : MF_GRAYED);
      }
      break;

     case IDM_INFO_MENU:
      {
         // Just look for active connection
         UINT utmp = MNU_HAVE_CONNECTS;

         EnableMenuItem(hMenu, IDM_DATA_SOURCES, utmp);
         EnableMenuItem(hMenu, IDM_DATA_TYPES, utmp);
         EnableMenuItem(hMenu, IDM_FUNCTIONS, utmp);
         EnableMenuItem(hMenu, IDM_ODBC_INFO, utmp);
      }
      break;

     case IDM_LIST_MENU:
      {
         int   idex;

         for(idex=0;  idex<NUM_MENU_FLAGS;  idex++)
            EnableMenuItem(hMenu, (IDM_TABLES + idex),
                           (lpActiveConn &&
                            BitGet(lpActiveConn->fFunctions, idex)) ?
                           MF_ENABLED : MF_GRAYED);
      }
      break;

     case IDM_WINDOW_MENU:
      {
         // Just look for active connection
         UINT utmp = MNU_HAVE_CONNECTS;

         EnableMenuItem(hMenu, IDM_CASCADE, utmp);
         EnableMenuItem(hMenu, IDM_TILE, utmp);
         EnableMenuItem(hMenu, IDM_ARRANGE, utmp);
         EnableMenuItem(hMenu, IDM_CLOSEALL, utmp);
      }
      break;
   }
}


MENU.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   MENU.H
//|      This file contains our menu ids.
//*---------------------------------------------------------------------------------
#ifndef menu_DEFS
#define menu_DEFS

#include <Windows.h>

//
// Following are indexes into the menu table
//
enum {
   MENU_SQLTABLES,
   MENU_SQLCOLUMNS,
   MENU_SQLSTATISTICS,
   MENU_SQLPRIMARYKEYS,
   MENU_SQLFOREIGNKEYS,
   MENU_SQLTABLEPRIVILEGES,
   MENU_SQLCOLUMNPRIVILEGES,
   MENU_SQLSPECIALCOLUMNS,
   MENU_SQLPROCEDURES,
   MENU_SQLPROCEDURECOLUMNS,
   MENU_SQLTRANSACT
   };

#define NUM_MENU_FLAGS     MENU_SQLTRANSACT

#define  IDM_WINDOW_POS      6
#define  IDM_FIRSTCHILD       200

#define  IDM_FILE_MENU        0
#define  IDM_CONNECT          100
#define  IDM_DISCONNECT       101
#define  IDM_EXECUTE_FILE     102
#define  IDM_OPEN             103
#define  IDM_CLOSE            104
#define  IDM_SAVE             105
#define  IDM_SAVEAS           106
#define  IDM_EXIT             107

#define  IDM_EDIT_MENU        1
#define  IDM_UNDO             108
#define  IDM_CUT              109
#define  IDM_COPY             110
#define  IDM_PASTE            111
#define  IDM_SELECT_ALL       112

#define  IDM_EXECUTE_MENU     2
#define  IDM_EXECUTE_NOW      113
#define  IDM_COMMIT           115
#define  IDM_ROLLBACK         116

#define  IDM_PIPE_MENU        3
#define  IDM_PIPE_EDIT        120
#define  IDM_PIPE_DO          121

#define  IDM_INFO_MENU        4
#define  IDM_MANAGE_DSNS      125
#define  IDM_DATA_SOURCES     126
#define  IDM_DATA_TYPES       127
#define  IDM_FUNCTIONS        128
#define  IDM_ODBC_INFO        129

#define  IDM_LIST_MENU        5
#define  IDM_TABLES           131
#define  IDM_COLUMNS          132
#define  IDM_STATISTICS       133
#define  IDM_PRIMARY_KEYS     134
#define  IDM_FOREIGN_KEYS     135
#define  IDM_TABLE_PRIVS      136
#define  IDM_COLUMN_PRIVS     137
#define  IDM_SPECIAL_COLUMNS  138
#define  IDM_PROCEDURES       139
#define  IDM_PROC_COLUMNS     140

#define  IDM_WINDOW_MENU      6
#define  IDM_FONT             150
#define  IDM_CASCADE          151
#define  IDM_TILE             152
#define  IDM_ARRANGE          153
#define  IDM_CLOSEALL         154

#define  IDM_ABOUT_MENU       7
#define  IDM_HELP             160
#define  IDM_ABOUT            161

#define  IDM_MOVE_WINDOW      165         // F6 key

#define MNU_HAVE_CONNECTS ((lpActiveConn) ? MF_ENABLED : MF_GRAYED)

// Normally menu numbers start at 0 and go up, which is how each offset is shown
//    in this file.  If a child MDI window is zoomed, however, then the child
//    system menu becomes menu number 0 and others are offset.  The following
//    macro will look at the current active window and return the correct
//    0 based menu number.
#define GETMENUNUM(iMenu) (iMenu - ((hwndCurMDIChild) ? (int)IsZoomed(hwndCurMDIChild) : 0))

#define CBITS (sizeof(UDWORD) * 8)
#define BitSet(lpa, pos)   \
   (lpa[((pos) / CBITS)] |= 1 << ((pos) - (CBITS * ((pos) / CBITS))))
#define BitClear(lpa, pos) \
   (lpa[((pos) / CBITS)] &= ~(1 << ((pos) - (CBITS * ((pos) / CBITS)))))
#define BitGet(lpa, pos)   \
   (lpa[((pos) / CBITS)] & (1 << ((pos) - (CBITS * ((pos) / CBITS)))))
#define BitFlip(lpa,pos)   \
   (BitGet(lpa,pos)) ? BitClear(lpa,pos) : BitSet(lpa,pos)
#define MINREQUIREDINTS(items) (UDWORD)((items / CBITS) + ((items % CBITS) ? 1 : 0))

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
VOID WINAPI ResetMenu(HMENU hMenu, int iMenu);

#endif


RESULTS.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   RESULTS.C
//|      This module contains functions which allow you to view results via an
//|      owner-drawn list box.  Having the list box functionality made it easy
//|      to implement Pipes, but has some serious draw-backs as far as record
//|      limits, storage methods, etc...  If you are looking for a great way to
//|      paint data in a grid, check out the C++ or Cursor Demo samples that came
//|      with this SDK.
//*---------------------------------------------------------------------------------
#include <stdio.h>
#include "errcheck.h"
#include "standard.h"
#include "results.h"
#include "math.h"
#include "ini.h"

//------------------------------------------------------------------------
//  Defines
//------------------------------------------------------------------------
VSZFile;

//------------------------------------------------------------------------
//  Globals
//------------------------------------------------------------------------
char szErrMsg[100];
dCSEG(char) szONE[]                       =  "1";
dCSEG(char) szZERO[]                      =  "0";
dCSEG(char) szdate[]                      =  "%02u/%02u/%02u";
dCSEG(char) sztime[]                      =  "%02u:%02u:%02u";
dCSEG(char) sztimestmp[]                  =  "%02u/%02u/%02u %02u:%02u:%02u.%lu";
dCSEG(char) szTypeNotFound[]              =  "Not found";
dCSEG(char) szMaxRowsFetched[]            =  "Maximum rows fetched.  Total rows: %lu";

struct {
   SWORD type;                      // Data type value
   LPSTR sztype;                    // String equivalent
   } SqlTypes[] = {
// type                 sztype
// -------------------  -----------------------------
   SQL_BIGINT,          "SQL_BIGINT=-5",
   SQL_BINARY,          "SQL_BINARY=-2",
   SQL_BIT,             "SQL_BIT=-7",
   SQL_CHAR,            "SQL_CHAR=1",
   SQL_DATE,            "SQL_DATE=9",
   SQL_DECIMAL,         "SQL_DECIMAL=3",
   SQL_DOUBLE,          "SQL_DOUBLE=8",
   SQL_FLOAT,           "SQL_FLOAT=6",
   SQL_INTEGER,         "SQL_INTEGER=4",
   SQL_LONGVARBINARY,   "SQL_LONGVARBINARY=-4",
   SQL_LONGVARCHAR,     "SQL_LONGVARCHAR=-1",
   SQL_NUMERIC,         "SQL_NUMERIC=2",
   SQL_REAL,            "SQL_REAL=7",
   SQL_SMALLINT,        "SQL_SMALLINT=5",
   SQL_TIME,            "SQL_TIME=10",
   SQL_TIMESTAMP,       "SQL_TIMESTAMP=11",
   SQL_TINYINT,         "SQL_TINYINT=-6",
   SQL_VARBINARY,       "SQL_VARBINARY=-3",
   SQL_VARCHAR,         "SQL_VARCHAR=12",
   };

struct {
   SWORD type;                      // Data type value
   LPSTR sztype;                    // String equivalent
   } CTypes[] = {
// type                 sztype
// -------------------  -----------------------------
   SQL_C_BINARY,        "SQL_C_BINARY=-2",
   SQL_C_BIT,           "SQL_C_BIT=-7",
   SQL_C_CHAR,          "SQL_C_CHAR=1",
   SQL_C_DATE,          "SQL_C_DATE=9",
   SQL_C_DOUBLE,        "SQL_C_DOUBLE=8",
   SQL_C_FLOAT,         "SQL_C_FLOAT=7",
   SQL_C_LONG,          "SQL_C_LONG=4",
   SQL_C_SHORT,         "SQL_C_SHORT=5",
   SQL_C_TIME,          "SQL_C_TIME=10",
   SQL_C_TIMESTAMP,     "SQL_C_TIMESTAMP=11",
   SQL_C_TINYINT,       "SQL_C_TINYINT=-6",
   };

typedef struct tagDATATYPE{
   SWORD type;                      // Data type value
   LPSTR sztype;                    // String equivalent
   } DATATYPE;

//------------------------------------------------------------------------
//  Local function prototypes
//------------------------------------------------------------------------
void CheckDisplayMode(LPSTR strin, SDWORD cbin, LPSTR strout);
BOOL INTFUN             DrawRow(DRAWITEMSTRUCT FAR * dw,
                           RESULTSSET FAR * rs, int xLeftCol, int xRightCol, BOOL fSelect);

//*------------------------------------------------------------------------
//| CreateResultsSet:
//|   This is the first function that should be called to create a
//|      results set.  When there is no more use for the results set,
//|      call DeleteResultsSet to delete it.
//|
//|   Parameters:
//|      in    rs                Pointer to a new results set
//|      in    hwndClient        Client window
//|      in    hInst             Instance handle of caller
//|      in    count             How many columns in the results set
//|      in    szTitle           Title for the window
//|
//|   Returns:
//|      TRUE if there were no errors,
//|      FALSE otherwise
//*------------------------------------------------------------------------
BOOL EXTFUN CreateResultsSet(RESULTSSET FAR * rs, HWND hwndClient, HINSTANCE hInst,
            int count, LPSTR szTitle)
{
   if(!rs ||
      count <=0)
      return FALSE;

   memset(rs, 0, sizeof(RESULTSSET));
   rs->cbColumns = count;
   rs->hInst = hInst;
   rs->hwndClient = hwndClient;
   if(*szTitle)
      lstrcpy(rs->szTitle, szTitle);

   rs->md = (METADATA FAR *)GetMemory(sizeof(METADATA) * count);
   if(!rs->md)
      return FALSE;

   return TRUE;
}

//*------------------------------------------------------------------------
//| SetMetaDataColumn:
//|   This function must be called for each column in the results set.  The
//|      information placed for each row can be obtained using ODBC functions
//|      such as SQLDescribeCol and SQLColAttribute.
//|   Parameters:
//|      in    rs                Pointer to a results set
//|      in    iCol              Column number
//|      in    szCol             Pointer to column name
//|      in    szTypeName        Data type name
//|      in    fSqlType          ODBC data type number
//|      in    precision         Precision
//|      in    scale             Scale
//|      in    cbDisplay         Display size
//|      in    fAlign            Alignment
//|   Returns:
//|      Nothing
//*------------------------------------------------------------------------
BOOL EXTFUN SetMetaDataColumn(RESULTSSET FAR * rs, int iCol, LPSTR szCol,
      LPSTR szTypeName, SDWORD fSqlType, UDWORD precision, SWORD scale,
      int cbDisplay, UINT fAlign)
{
   if(!rs ||
      iCol < 0 ||
      !szCol ||
      !*szCol ||
      !szTypeName ||
      !*szTypeName) {
      PostError((LPSTR)szInvalidParms);
      return FALSE;
   }

   rs->md[iCol].szColumnName = (LPSTR)GetMemory(lstrlen(szCol)+1);
   if(!rs->md[iCol].szColumnName)
      return FALSE;
   precision = min(precision, MAXBYTES);
   lstrcpy(rs->md[iCol].szColumnName, szCol);
   lstrcpy(rs->md[iCol].szTypeName, szTypeName);
   rs->md[iCol].fSqlType = fSqlType;
   rs->md[iCol].precision = precision;
   rs->md[iCol].scale = scale;
   rs->md[iCol].cbDisplaySize = cbDisplay;
   rs->md[iCol].fAlign = fAlign;
   rs->md[iCol].cbOffset = (iCol > 0) ? (UINT)(precision + rs->md[iCol-1].cbOffset) : (UINT)(precision);
   ++rs->md[iCol].cbOffset;               // Room for terminators

   return TRUE;
}

//*------------------------------------------------------------------------
//| AllocateRowData:
//|   Call this function for each row in the results set to allocate
//|      memory and insert a row into the results set.
//|   Parameters:
//|      in    rs                Pointer to results set
//|      in    rd                Pointer to a row data structure
//|      in    cColor            Text color
//|      in    cBkg              Background color
//|   Returns:
//|      Pointer to a ROWDATA structure
//*------------------------------------------------------------------------
ROWDATA FAR * AllocateRowData(RESULTSSET FAR * rs, COLORREF cColor, COLORREF cBkg)
{
   ROWDATA FAR *  rd;
   int            dex;

   if(!rs) {
      PostError((LPSTR)szInvalidParms);
      return FALSE;
   }
   rd = (ROWDATA FAR *)GetMemory(sizeof(ROWDATA));
   if(!rd)
      return NULL;
   rd->textColor = cColor;
   rd->bkgrnd = cBkg;
   rd->cd = (COLUMNDATA FAR *)GetMemory((sizeof(COLUMNDATA) * rs->cbColumns));
   if(!rd->cd)
      return NULL;
   rd->data = (LPSTR)GetMemory(rs->md[rs->cbColumns-1].cbOffset + 1);
   if(!rd->data)
      return NULL;
   rd->cd[0].szCols = rd->data;
   for(dex=1;  dex<rs->cbColumns;  dex++)
      rd->cd[dex].szCols = rd->data + rs->md[dex-1].cbOffset;

   return rd;
}

//*------------------------------------------------------------------------
//| SetColumnData:
//|   Call this function for a particular column in a ROWDATA structure.
//|      If memory has been allocated for the column, it will be freed
//|      and reallocated for the new string.
//|   Parameters:
//|      in    icol              Which column?
//|      in    rd                Pointer to a row data structure
//|      in    str               Pointer to the new buffer
//|   Returns:
//|      TRUE if successful
//|      FALSE on error
//*------------------------------------------------------------------------
BOOL EXTFUN SetColumnData(int icol, ROWDATA FAR * rd, LPSTR str)
{
   if(!str ||
      !*str)
      return FALSE;

   lstrcpy(rd->cd[icol].szCols, str);
   return TRUE;
}

//*------------------------------------------------------------------------
//| FreeRowData:
//|   Pass a pointer to the ROWDATA structure to free.  Obviously since
//|      you are asked for a RESULTSSET pointer, you should call this
//|      function before freeing the results set data.
//| Parms:
//|   in       rs                   Pointer to results set
//|   in       rd                   Pointer to row data
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void EXTFUN FreeRowData(RESULTSSET FAR * rs, ROWDATA FAR * rd)
{
   ReleaseMemory(rd->data);
   ReleaseMemory((LPVOID)rd->cd);
   ReleaseMemory((LPVOID)rd);
}

//*------------------------------------------------------------------------
//| FreeResultsSet:
//|   Call this function to free all of the memory for a results set.
//| Parms:
//|   in       rs                   Pointer to results set data to free
//| Returns:
//|   TRUE     If successful
//|   FALSE    if there was an error
//*------------------------------------------------------------------------
void EXTFUN FreeResultsSet(RESULTSSET FAR * rs)
{
   int   dex;

   DeleteObject(rs->hFont);

   for(dex=0;  dex<rs->cbColumns;  dex++)
      ReleaseMemory(rs->md[dex].szColumnName);
   ReleaseMemory(rs->md);
   ReleaseMemory(rs);
   return;
}

//*------------------------------------------------------------------------
//| CreateResultsFont:
//|   This function is called to create a font for the results set.  The
//|   default font is used if the lf parameter is NULL.  Alternatively
//|   the user can pass in a complete LOGFONT structure to use for the
//|   font.
//| Parms:
//|   in       rs                   Pointer to results set to store info
//|   in       hwnd                 Window handle to verify font
//|   in       lf                   LOGFONT structure to use, NULL for dft
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void CreateResultsFont(RESULTSSET FAR * rs, HWND hwnd, LOGFONT FAR * lf)
{
   HDC                     hdc;
   LOGFONT                 logfont;
   HFONT                   hf;
   TEXTMETRIC              tm;
   SIZE                    sz;
   int                     tmp, dex, cbExtra;

   if(!lf) {
      memset(&logfont, 0, sizeof(LOGFONT));
      GetDefaultFont(&logfont);
   }
   else
      memmove(&logfont, lf, sizeof(LOGFONT));

   rs->hFont = CreateFontIndirect(&logfont);
   hdc = GetDC(hwnd);
   hf = SelectObject(hdc, rs->hFont);
   GetTextMetrics(hdc, &tm);
   rs->cx = tm.tmAveCharWidth;

   rs->cy = tm.tmHeight + tm.tmExternalLeading;
   cbExtra = GetSystemMetrics(SM_CYBORDER);
   rs->cTitleHeight = rs->cy + (7 * cbExtra);
   rs->yTitleLoc = (rs->cTitleHeight / 2) + rs->cy;
   for(dex=0, tmp=0;  dex<rs->cbColumns;  dex++) {
      GetTextExtentPoint(hdc, rs->md[dex].szColumnName,
                         lstrlen(rs->md[dex].szColumnName), &sz);
      rs->md[dex].cColWidth = (rs->md[dex].cbDisplaySize * rs->cx) + (7 * cbExtra);
      rs->md[dex].cColWidth = max((UINT)(sz.cx * 1.5),
                                  rs->md[dex].cColWidth);
      rs->md[dex].xCol = tmp;
      tmp += rs->md[dex].cColWidth;
   }
   rs->cRowWidth = tmp;

   SelectObject(hdc,hf);
   ReleaseDC(hwnd, hdc);
}

//*------------------------------------------------------------------------
//| FindRightCol:
//|   This function will take the left column and a results set descriptor
//|      and return the right column index based on what will fit in the
//|      window.
//| Parms:
//|   in       rs                   Pointer to results set to store info
//|   in       xLeftCol             Current left column index
//|   in       cWidth               Available width
//| Returns:
//|   Index to be used for right column
//*------------------------------------------------------------------------
int FindRightCol(RESULTSSET FAR * rs, int xLeftCol, int cWidth)
{
   int xRightCol;
   int cSpace;

   xRightCol = xLeftCol;
   cSpace = cWidth - rs->md[xLeftCol].cColWidth;
   while(cSpace>0 &&
         xRightCol < rs->cbColumns-1) {
      ++xRightCol;
      cSpace -= rs->md[xRightCol].cColWidth;
   }
   return xRightCol;
}

//*------------------------------------------------------------------------
//| DrawRowData:
//|   This function will do the actual drawing on the screen based on the
//|      control structures passed in.
//| Parms:
//|   in       rs                   Pointer to results set to store info
//|   in       dwitem               Draw structure
//|   in       xLeftCol             Current left column index
//|   in       xRightCol            Right column index
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void DrawRowData(RESULTSSET FAR * rs, DRAWITEMSTRUCT FAR * dwitem,
               int xLeftCol, int xRightCol)
{
   switch(dwitem->itemAction) {
     case ODA_DRAWENTIRE:
     case ODA_SELECT:
      DrawRow(dwitem, rs, xLeftCol, xRightCol,
              (dwitem->itemState == ODS_SELECTED));
      return;
   }
}

//*------------------------------------------------------------------------
//| DrawColumnTitles:
//|   This function is called when we need to paint the column titles for a
//|      results set.  We will simply write them out.
//| Parms:
//|   in       hdc                  Handle to our device contex
//|   in       rs                   Our results set to draw
//|   in       crect                Client rectangle to paint in
//|   in       xLeftCol             Left column
//|   in       xRightCol            Right column
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void INTFUN DrawColumnTitles(HDC hdc, RESULTSSET FAR * rs,
            RECT FAR * crect, int xLeftCol, int xRightCol)
{
   int               dex, offset, cright=0;
   RECT              rect;
   HFONT             hf;

   hf = SelectObject(hdc, rs->hFont);
   SetTextColor(hdc, RDATA_BLACK);
   offset = 0 - rs->md[xLeftCol].xCol;
   for (dex=xLeftCol; dex<=xRightCol; dex++)
      cright += rs->md[dex].cColWidth;
   Rectangle(hdc, crect->left, crect->top, min(cright, crect->right), crect->bottom+1);
   SetBkColor(hdc, RDATA_GRAY);

   rect.top = crect->top +1;
   rect.bottom = crect->bottom;
   for(dex=xLeftCol;  dex<=xRightCol;  dex++) {
      rect.left = rs->md[dex].xCol + offset;
      rect.right = rect.left + rs->md[dex].cColWidth;
      MoveTo(hdc, rect.right, rect.top);
      LineTo(hdc, rect.right, rect.bottom);
      ++rect.left;
#ifdef TITLE_DEBUG
      {
         char tmpbuff[50];
         wsprintf(tmpbuff, "Column: %d, left=%d, top=%d, right=%d, bottom=%d",
                  dex,
                  rect.left, rect.top,
                  rect.right, rect.bottom);
         DrawFocusRect(hdc, &rect);
         MessageBox(NULL, (LPSTR)tmpbuff, "Debug", MB_OK);
         DrawFocusRect(hdc, &rect);
      }
#endif
      ExtTextOut(hdc, rs->md[dex].xCol + 3 + offset, rect.top + 4,
                 ETO_CLIPPED | ETO_OPAQUE,
                 &rect,
                 rs->md[dex].szColumnName,
                 lstrlen(rs->md[dex].szColumnName),
                 NULL);
   }
   SelectObject(hdc,hf);               // change font back
}

//*------------------------------------------------------------------------
//| DrawRow:
//|   Call this function for each row which must be painted.
//| Parms:
//|   in       dw                   Draw structure
//|   in       rs                   Our results set to draw
//|   in       xLeftCol             Index to left-most column displayed
//|   in       xRightCol            Index to right-most column displayed
//|   in       fSelect              Is the item supposed to be selected?
//| Returns:
//|   TRUE if successful,
//|   FALSE otherwise
//*------------------------------------------------------------------------
//#define RECT_DEBUG
BOOL INTFUN DrawRow(DRAWITEMSTRUCT FAR * dw,
                     RESULTSSET FAR * rs,
                     int xLeftCol, int xRightCol,
                     BOOL fSelect)
{
   ROWDATA FAR *     rd=(ROWDATA FAR *)dw->itemData;
   int               dex;
   int               offset;
   int               cright=0;
   RECT              rect;
   HFONT             hf;

   //
   // First set the font and text colors according to the user's request, then draw
   //    a line at the bottom of the row for a separator.  Note that the rcItem
   //    rectangle passed to us in the DRAWITEMSTRUCT is for the
   //
   hf = SelectObject(dw->hDC, rs->hFont);
   dw->rcItem.right = min(rs->cRowWidth, dw->rcItem.right);
   for (dex=xLeftCol; dex<=xRightCol; dex++)
      cright += rs->md[dex].cColWidth;
   // Draw top of box
   MoveTo(dw->hDC, dw->rcItem.left, dw->rcItem.top);
   LineTo(dw->hDC, min(cright, dw->rcItem.right), dw->rcItem.top);

   // Draw bottom also, to take care of last line
   MoveTo(dw->hDC, dw->rcItem.left, dw->rcItem.bottom);
   LineTo(dw->hDC, min(cright, dw->rcItem.right), dw->rcItem.bottom);

#ifdef RECT_DEBUG
   {
      char tmpbuff[50];
      wsprintf(tmpbuff, "dw->rcItem, left=%d, top=%d, right=%d, bottom=%d",
               dw->rcItem.left, dw->rcItem.top,
               dw->rcItem.right, dw->rcItem.bottom);
      DrawFocusRect(dw->hDC, &dw->rcItem);
      MessageBox(NULL, (LPSTR)tmpbuff, "Debug", MB_OK);
      DrawFocusRect(dw->hDC, &dw->rcItem);
   }
#endif

   //
   // Now loop through each column in the row and draw it's contents by creating
   //    a logical rectangle for each column, then filling in that rectangle with
   //    the value to be displayed.
   //
   rect.top = dw->rcItem.top+1;
   rect.bottom = dw->rcItem.bottom;
   SetBkMode(dw->hDC, TRANSPARENT);
   if(fSelect) {
      SetBkColor(dw->hDC, GetSysColor(COLOR_HIGHLIGHT));
      SetTextColor(dw->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
   }
   else {
      SetBkColor(dw->hDC, rd->bkgrnd);
      SetTextColor(dw->hDC, rd->textColor);
   }
   offset = 0 - rs->md[xLeftCol].xCol;
   for (dex=xLeftCol; dex<=xRightCol; dex++) {
      rect.left = offset + rs->md[dex].xCol;
      rect.right = rect.left + rs->md[dex].cColWidth;
      MoveTo(dw->hDC, rect.right, rect.top);
      LineTo(dw->hDC, rect.right, rect.bottom);

#ifdef RECT_DEBUG
      {
         char tmpbuff[50];
         wsprintf(tmpbuff, "Column: %d, left=%d, top=%d, right=%d, bottom=%d",
                  dex,
                  rect.left, rect.top,
                  rect.right, rect.bottom);
         DrawFocusRect(dw->hDC, &rect);
         MessageBox(NULL, (LPSTR)tmpbuff, "Debug", MB_OK);
         DrawFocusRect(dw->hDC, &rect);
      }
#endif
      SetTextAlign(dw->hDC, rs->md[dex].fAlign);
      if(dex != xLeftCol)
         ++rect.left;
      ExtTextOut(dw->hDC, rs->md[dex].xCol + 3 + offset, rect.top + 4,
                 ETO_CLIPPED | ETO_OPAQUE,
                 &rect,
                 rd->cd[dex].szCols,
                 lstrlen(rd->cd[dex].szCols),
                 NULL);
   }
   SelectObject(dw->hDC,hf);              // change font back

   return TRUE;
}

//*------------------------------------------------------------------------
//| HandleHScroll:
//|   This function adds a new item to our results set.
//| Parms:
//|   in       wParam               Scroll option
//|   in       rs                   Results set pointer
//|   in       hwnd                 Window handle for column title
//|   in       hwndHScroll          Scroll bar window handle
//|   in       xLeftCol             Left column index
//|   in       xRightCol            Right column index
//|   in       hwndList             Listbox window handle
//|   in       cbColumns            Number of columns
//|   in       cbClient             Width of screen available to draw in
//|   in       tRect                Bounding rectangle for client window
//| Returns:
//|   Index to string if successful, LB_ERRSPACE otherwise
//*------------------------------------------------------------------------
void HandleHScroll(WPARAM wParam, RESULTSSET FAR * rs,
         HWND hwnd, HWND hwndHScroll, int FAR * xLeftCol, int FAR * xRightCol,
         HWND hwndList, int cbColumns, int cbClient, RECT FAR * tRect)
{
   int      cHScrollPos;
   int      fhScroll=FALSE;

   cHScrollPos = GetScrollPos(hwndHScroll, SB_CTL);
   switch(wParam) {
     case SB_LINEUP:          // Shift right one column
      if(!*xLeftCol)
         fhScroll = FALSE;
      else {
         --cHScrollPos;
         fhScroll = TRUE;
         --*xLeftCol;
      }
      break;

     case SB_LINEDOWN:           // Shift left one column
      if(*xLeftCol+1 == cbColumns)
         fhScroll = FALSE;             // No change required
      else {
         ++cHScrollPos;
         fhScroll = TRUE;
         ++*xLeftCol;
      }
      break;

     case SB_PAGEUP:          // Shift right one screen
      if(!*xLeftCol)
         fhScroll = FALSE;
      else {
         --cHScrollPos;
         fhScroll = TRUE;
         --*xLeftCol;
      }
      break;

     case SB_PAGEDOWN:           // Shift left one screen
      if(*xLeftCol+1 == cbColumns)
         fhScroll = FALSE;             // No change required
      else {
         if(*xLeftCol < *xRightCol) {
            cHScrollPos += *xRightCol - *xLeftCol;
            *xLeftCol = *xRightCol;
            fhScroll = TRUE;
         }
         else {
            ++cHScrollPos;
            ++*xLeftCol;
            fhScroll = TRUE;
         }
      }
      break;

     case SB_THUMBPOSITION:      // Specific location
      break;
   }

   //
   // If movement is required, we will have adjusted the scroll position
   //    and columns already.  Calculate what columns will fit on our current
   //    display to find the rightmost column.  Next invalidate the areas
   //    requiring painting and set the new scroll position.  This will cause
   //    each row to be redrawn starting with the new rwi->xLeftCol.
   //
   if(fhScroll) {                      // Movement is required
      RECT     rect;
      *xRightCol = FindRightCol(rs, *xLeftCol, cbClient);
      GetClientRect(hwndList, &rect);
      InvalidateRect(hwndList, &rect, TRUE);
      SetScrollPos(hwndHScroll, SB_CTL, cHScrollPos, TRUE);
      InvalidateRect(hwnd, tRect, TRUE);
   }
}

//*------------------------------------------------------------------------
//| HandleVirtualHScroll:
//|   This function should be called in response to the WM_KEYDOWN
//|   message. It will look for a virtual key to see if the user
//|   is trying to do scrolling.  If so, we will force the scroll
//|   to happen.
//| Parms:
//|   in       wParam               Value of wParam for WM_KEYDOWN
//|   in       hwndList             Handle of list box
//|   in       hwndOwner            Owner window of the horizontal scrollbar
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void HandleVirtualHScroll(WPARAM wParam, HWND hwndList, HWND hwndOwner)
{
   switch(wParam) {
     case VK_HOME:
      SendMessage(hwndList, WM_VSCROLL, SB_TOP, 0L);
      return;

     case VK_END:
      SendMessage(hwndList, WM_VSCROLL, SB_BOTTOM, 0L);
      return;

     case VK_PRIOR:
      SendMessage(hwndList, WM_VSCROLL, SB_PAGEUP, 0L);
      return;

     case VK_NEXT:
      SendMessage(hwndList, WM_VSCROLL, SB_PAGEDOWN, 0L);
      return;

     case VK_UP:
      SendMessage(hwndList, WM_VSCROLL, SB_LINEUP, 0L);
      return;

     case VK_DOWN:
      SendMessage(hwndList, WM_VSCROLL, SB_LINEDOWN, 0L);
      return;

     case VK_LEFT:
      SendMessage(hwndOwner, WM_HSCROLL, SB_LINEUP, 0L);
      return;

     case VK_RIGHT:
      SendMessage(hwndOwner, WM_HSCROLL, SB_LINEDOWN, 0L);
      return;
   }
}

//*------------------------------------------------------------------------
//| AddRowData:
//|   This function adds a new item to our results set.
//| Parms:
//|   in       rs                   Pointer to results set
//|   in       rd                   Pointer to row data to add
//| Returns:
//|   Index to string if successful, LB_ERRSPACE otherwise
//*------------------------------------------------------------------------
int AddRowData(RESULTSSET FAR * rs, ROWDATA FAR * rd)
{
   int         rtn;
   DWORD       cbCnt;
   rtn = (int)SendMessage(rs->hwndList, LB_ADDSTRING, 0, (LPARAM)(ROWDATA FAR *)rd);
   if(rtn == LB_ERRSPACE) {
      cbCnt = SendMessage(rs->hwndList, LB_GETCOUNT, 0, 0L);
      wsprintf(szErrMsg, szMaxRowsFetched, cbCnt);
      MessageBox(rs->hwndClient, szErrMsg, szErrTitle, MB_OK);
   }
   return rtn;
}

//*------------------------------------------------------------------------
//| GetNumResultsCols:
//|   Given an hstmt which has an executed statement on it, find the number
//|      of results columns in it.
//| Parms:
//|   in       hstmt                Statement handle with results set
//| Returns:
//|   Number of columns
//*------------------------------------------------------------------------
SWORD GetNumResultsCols(HSTMT hstmt)
{
   SWORD cbCols;
   RETCODE retcode;

   retcode = SQLNumResultCols(hstmt, &cbCols);
   if(RC_NOTSUCCESSFUL(retcode))
      return -1;
   else
      return cbCols;
}

//*------------------------------------------------------------------------
//| GetTypeName:
//|   This function will return the null-terminated character name of
//|   the type passed in.
//| Parms:
//|   in       type                 SQL_TYPE or C_TYPE
//|   in       fType                The fCType or fSqlType
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
LPSTR GetTypeName(int type, int fType)
{
   int               dex, stopdex;
   DATATYPE FAR *    dt;

   if(type == SQL_TYPE) {
      stopdex = NumItems(SqlTypes);
      dt = (DATATYPE FAR *)&SqlTypes;
   }
   else {
      stopdex = NumItems(CTypes);
      dt = (DATATYPE FAR *)&CTypes;
   }
   for(dex=0;  dex<stopdex;  dex++)
      if(dt[dex].type == fType)
         return dt[dex].sztype;
   return (LPSTR)szTypeNotFound;
}

//*------------------------------------------------------------------------
//| ConvertSqlTypeToChar:
//|   This function will convert the value passed in to it's character equivalent.
//| Parms:
//|   in       rs                   Pointer to results set
//|   in       col                  Which column is it?
//|   in       inbuff               Input buffer
//|   in       outbuff              Output buffer
//|   in       rtnd                 Returned bytes from SQLGetData
//| Returns:
//|   Nothing.
//*------------------------------------------------------------------------
void ConvertSqlTypeToChar(RESULTSSET FAR * rs, int col, LPSTR inbuff,
         LPSTR outbuff, SDWORD rtnd)
{
   LPSTR                   tmpstr;
   SWORD FAR *             tmpsword;
   SDWORD FAR *            tmpsdword;
   SFLOAT FAR *            tmpsfloat;
   SDOUBLE FAR *           tmpsdouble;
   DATE_STRUCT FAR *       tmpdate;
   TIME_STRUCT FAR *       tmptime;
   TIMESTAMP_STRUCT FAR *  tmptimestmp;

   *outbuff = '\0';
   switch(rs->md[col].fSqlType) {
      //
      // Look for any non-displayable characters and change them to periods
      //
     case SQL_CHAR:
     case SQL_VARCHAR:
     case SQL_LONGVARCHAR:
      CheckDisplayMode((LPSTR)inbuff, rtnd, outbuff);
      tmpstr = outbuff + rtnd;
      *tmpstr = '\0';
      break;

     case SQL_BINARY:
     case SQL_VARBINARY:
     case SQL_LONGVARBINARY:
      lstrcpy(outbuff, "0x");

BinToChar(outbuff+2, (LPSTR)inbuff, rtnd);
      break;

     case SQL_TINYINT:
     case SQL_SMALLINT:
      tmpsword = (SWORD FAR *)inbuff;
      wsprintf(outbuff, "%d", *tmpsword);
      break;

     case SQL_INTEGER:
     case SQL_BIGINT:
      tmpsdword = (SDWORD FAR *)inbuff;
      wsprintf(outbuff, "%ld", *tmpsdword);
      break;

     case SQL_FLOAT:
     case SQL_DOUBLE:
      tmpsdouble = (SDOUBLE FAR *)inbuff;
      sprintf(outbuff, "%Fg", *tmpsdouble);
      break;

     case SQL_REAL:
      tmpsfloat = (SFLOAT FAR *)inbuff;
      sprintf(outbuff, "%Fg", *tmpsfloat);
      break;

     case SQL_BIT:
      tmpsword = (SWORD FAR *)inbuff;
      lstrcpy(outbuff, (*tmpsword) ? (LPSTR)szONE : (LPSTR)szZERO);
      break;

     case SQL_DECIMAL:
     case SQL_NUMERIC:
      lstrcpy(outbuff, inbuff);
      break;

     case SQL_DATE:
      tmpdate = (DATE_STRUCT FAR *)inbuff;
      wsprintf(outbuff, szdate, tmpdate->month, tmpdate->day, tmpdate->year);
      break;

     case SQL_TIME:
      tmptime= (TIME_STRUCT FAR *)inbuff;
      wsprintf(outbuff, sztime, tmptime->hour, tmptime->minute, tmptime->second);
      break;

     case SQL_TIMESTAMP:
      tmptimestmp = (TIMESTAMP_STRUCT FAR *)inbuff;
      wsprintf(outbuff, sztimestmp, tmptimestmp->year, tmptimestmp->month,
               tmptimestmp->day, tmptimestmp->hour, tmptimestmp->minute,
               tmptimestmp->second, tmptimestmp->fraction);
      break;
   }

   return;
}

//*------------------------------------------------------------------------
//| CheckDisplayMode:
//|   This function looks through a string for the count specified, then
//|      changes any x"00" to a period so it can be displayed.
//| Parms:
//|   strin       - String coming in
//|   cbin        - Byte count of incoming string
//|   strout      - Output string
//*------------------------------------------------------------------------
void CheckDisplayMode(LPSTR strin, SDWORD cbin, LPSTR strout)
{
   SDWORD      dex,max=cbin;
   LPSTR    str=strout;

   if(cbin < 0)
      max = lstrlen(strin);
   memcpy(strout, strin, (size_t)max);
   for(dex=0; dex<cbin; dex++, str++)
      if(!*str)
         *str = '.';
}

//*------------------------------------------------------------------------
//| BinToChar:
//|   Takes a string and converts to its hexidecimal equivalent
//*------------------------------------------------------------------------
void BinToChar(LPSTR outstr, LPSTR instr, SDWORD count)
{
   UCHAR uletter;
   LPSTR istr=instr;
   LPSTR ostr=outstr;

   while(count--) {
      uletter = (*instr & 0xF0) >> 4;              // High nibble
      if(uletter <= 9)
         *ostr++ = uletter + '0';
      else
         *ostr++ = 'A' + (uletter - 10);
      uletter = *instr++ & 0x0F;
      if(uletter <= 9)
         *ostr++ = uletter + '0';
      else
         *ostr++ = 'A' + (uletter - 10);
   }
   *ostr = '\0';
}


RESULTS.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   RESULTS.H
//|      This file contains the prototypes and defines for anyone using RESULTS.H.
//*---------------------------------------------------------------------------------
#ifndef results_DEFS
#define results_DEFS

#include <windows.h>
#include "sql.h"
#include "sqlext.h"

//------------------------------------------------------------------------
//  Defines
//------------------------------------------------------------------------
#define SQL_TYPE                 1
#define C_TYPE                   2
#define TYPENAME                 35

#define MAXBYTES                 255

#define RDATA_WHITE              RGB(255,255,255)
#define RDATA_GRAY               RGB(190, 190, 190)
#define RDATA_BLACK              RGB(0, 0, 0)
#define RDATA_RED                RGB(255,0,0)
#define RDATA_GREEN              RGB(0,255,0)
#define RDATA_BLUE               RGB(0,0,255)
#define RDATA_LTBLUE             RGB(0,255,255)
#define RDATA_DEFAULT_TEXT             GetSysColor(COLOR_WINDOWTEXT)
#define RDATA_DEFAULT_BKGRND           GetSysColor(COLOR_WINDOW)
#define RDATA_DEFAULT_SELECT_TEXT      GetSysColor(COLOR_HIGHLIGHTTEXT)
#define RDATA_DEFAULT_SELECT           GetSysColor(COLOR_HIGHLIGHT)

//------------------------------------------------------------------------
//  Declare global variables
//------------------------------------------------------------------------

//*--------------------------------------------------------------------
//|  METADATA:
//|   This structure contains the meta data about each column in the
//|      results set.  This information can be easily retrieved using
//|      SQLColAttribute or SQLDescribeCol.
//*--------------------------------------------------------------------
typedef struct tagMETADATA {
   LPSTR       szColumnName;           // Name of the column
   char        szTypeName[TYPENAME];   // ODBC type name
   SDWORD      fSqlType;               // Numeric SQL type
   UDWORD      precision;              // Precision of the data type
   SWORD       scale;                  // Scale of the data type
   int         cbDisplaySize;          // How many bytes to display
   UINT        fAlign;                 // What alignment to take
   int         xCol;                   // x offset from 0 for column
   UINT        cColWidth;              // Width of column in pixels
   UINT        cbOffset;               // Byte offset in row storage
   } METADATA;

//*--------------------------------------------------------------------
//|  This structure is created when the user creates a results set.
//|   It must be used for all major function calls.
//*--------------------------------------------------------------------
typedef struct tagRESULTSSET {
   char        szTitle[MAXBUFF];       // Title for the window
   int         cbColumns;              // How many columns in results?
   HINSTANCE   hInst;                  // Instance handle for this dll
   HWND        hwndResults;            // Window handle for the MDI client window
   HWND        hwndClient;             // Window handle of the client window
   HWND        hwndTitle;              // Column names
   HWND        hwndList;               // Window handle for list box
   HWND        hwndHScroll;            // Window handle for horizontal scroll bar
   HWND        hwndVScroll;            // Window handle for vertical scroll bar
   HFONT       hFont;                  // Logical font for window
   int         cx;                     // Width of the average character
   int         cy;                     // Height of the average character
   int         cTitleHeight;           // Height of the title bar
   int         yTitleLoc;              // What y value to use when drawing title text
   int         cRowWidth;              // Overall width if completely shown
   METADATA    FAR * md;               // Structure with meta data
   } RESULTSSET, FAR * lpRESULTSSET;

//------------------------------------------------------------------------
//  Following structure is used to keep track of data for each column
//------------------------------------------------------------------------
typedef struct tagCOLUMNDATA {
   LPSTR    szCols;                             // Column data
   } COLUMNDATA, FAR * lpCOLUMNDATA;

//------------------------------------------------------------------------
//  Following structure should be allocated for each row
//------------------------------------------------------------------------
typedef struct tagRowData {
   COLUMNDATA FAR *     cd;                     // Pointer to column information
   LPSTR                data;                   // Buffer for all column data
   COLORREF             textColor;              // Color of text
   COLORREF             bkgrnd;                 // Color of background
   } ROWDATA, FAR * lpROWDATA;

//------------------------------------------------------------------------
//  Declare function prototypes
//------------------------------------------------------------------------
BOOL EXTFUN CreateResultsSet(RESULTSSET FAR * rs, HWND hwndClient, HINSTANCE hInst,
                           int count, LPSTR szTitle);
BOOL EXTFUN SetMetaDataColumn(RESULTSSET FAR * rs, int iCol, LPSTR szCol,
                           LPSTR szTypeName, SDWORD fSqlType, UDWORD precision, SWORD scale,
                           int cbDisplay, UINT fAlign);
ROWDATA FAR * AllocateRowData(RESULTSSET FAR * rs, COLORREF cColor, COLORREF cBkg);
BOOL EXTFUN SetColumnData(int icol, ROWDATA FAR * rd, LPSTR str);
void EXTFUN FreeRowData(RESULTSSET FAR * rs, ROWDATA FAR * rd);
void EXTFUN FreeResultsSet(RESULTSSET FAR * rs);
int  FindRightCol(RESULTSSET FAR * rs, int xLeftCol, int cWidth);
void CreateResultsFont(RESULTSSET FAR * rs, HWND hwnd, LOGFONT FAR * lf);
void DrawRowData(RESULTSSET FAR * rs, DRAWITEMSTRUCT FAR * dwitem,
                           int xLeftCol, int xRightCol);
void INTFUN DrawColumnTitles(HDC hdc, RESULTSSET FAR * rs,
                           RECT FAR * crect, int xLeftCol, int xRightCol);
void  HandleHScroll(WPARAM wParam, RESULTSSET FAR * rs,
                           HWND hwnd, HWND hwndHScroll, int FAR * xLeftCol, int FAR * xRightCol,
                           HWND hwndList, int cbColumns, int cbClient, RECT FAR * tRect);
void HandleVirtualHScroll(WPARAM wParam, HWND hwndList, HWND hwndOwner);
int   AddRowData(RESULTSSET FAR * rs, ROWDATA FAR * rd);
SWORD GetNumResultsCols(HSTMT hstmt);
LPSTR GetTypeName(int type, int fType);
void  ConvertSqlTypeToChar(RESULTSSET FAR * rs, int col, LPSTR inbuff,
                           LPSTR outbuff, SDWORD rtnd);
void  BinToChar(LPSTR outstr, LPSTR instr, SDWORD count);

#endif


STANDARD.C

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   STANDARD.C
//|      This module contains standard functions which can be used by many
//|      different tools.
//|
//|      The DumpDebugInfo function when enabled will cause all memory requests
//|      and frees to be written to a comma separated file.  This file can then
//|      be queries via the sample Text ODBC Driver to find memory problems.
//*---------------------------------------------------------------------------------
#include "standard.h"
#include <windowsx.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "errcheck.h"
#include "strings.h"

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
char OutStr[MAXBUFF];
dCSEG(char) szui[]                        =  "%u";
dCSEG(char) szEmpty[]                     =  "\0";
dCSEG(char) szcrlf[]                      =  "\r\n";

#ifdef _DEBUG
#include "time.h"
#define szASSERTERROR (LPSTR)"Assert: %s, %s, %d"
#define szDEBUGFILE (LPSTR)"C:\\TMP\\MEM"
char szDbgOut[200];
BOOL fNew=TRUE;

void WinAssertReal(int exp, LPSTR msg, LPSTR file, int line)
{
   if(!exp) {
      wsprintf(szDbgOut, szASSERTERROR, msg, file, line);
      MessageBox(GetActiveWindow(), szDbgOut, szErrTitle, MB_OK);
   }
}

void DumpDebugInfo(LPVOID tmp, LPSTR szFile, int cbLine, int stat,
         DWORD rqSize, DWORD aSize)
{
   static   OFSTRUCT    ofs;
   static   HFILE       hf;
   static   time_t      thetime;
   static   struct tm * tmVal;

   if(fNew) {
      fNew = FALSE;
      ofs.cBytes = sizeof(OFSTRUCT);
      hf = OpenFile(szDEBUGFILE, &ofs, OF_DELETE);
      _lclose(hf);
   }

   // Creates file MEM which is a comma separated text file.
   // Issue the select statement to find memory ptrs
   // which were allocated but never freed (status=1), and
   // pointers which were freed but never allocated (status=-1).
   // Of course "No data found" is the desired response.
   //    create table mem
   //        (address char(9),
   //         logtime char(8),
   //         status integer,
   //         desired integer,
   //         actual integer,
   //         line integer,
   //         file char(45))
   //    select address, sum(status) from mem
   //      group by address having sum(status) <> 0
   time(&thetime);
   tmVal = localtime(&thetime);
   wsprintf(szDbgOut, (LPSTR)"%04X:%04X,%02u:%02u:%02u,%d,%lu,%lu,%u,%s\r\n",
            HIWORD(tmp), LOWORD(tmp), tmVal->tm_hour, tmVal->tm_min, tmVal->tm_sec,
            stat, rqSize, aSize, cbLine, (LPSTR)szFile);
   if((hf = _lopen(szDEBUGFILE, WRITE)) == -1)  // File not found
      hf = OpenFile(szDEBUGFILE, &ofs, OF_CREATE);
   _llseek(hf, 0L, 2);                    // Try to go to end of file
   _lwrite(hf, szDbgOut, lstrlen(szDbgOut));  // Write wherever we are
   _lclose(hf);
}

void FAR * DebugGetMemory(DWORD size, LPSTR szFile, int cbLine)
{
   LPVOID   ptr;
   DWORD    aSize;

   ptr = DoGetMemory(size);
   if(!ptr)
      return ptr;
   aSize = GlobalSize(GlobalPtrHandle(ptr));
   DumpDebugInfo(ptr, szFile, cbLine, 1, size, aSize);

   return ptr;
}

void DebugReleaseMemory(LPVOID ptr, LPSTR szFile, int cbLine)
{
   GlobalFreePtr(ptr);
   DumpDebugInfo(ptr, szFile, cbLine, -1, 0, 0);
}
#endif      // Debug memory routines

//*---------------------------------------------------------------------------------
//| DoGetMemory:
//|   This function allocates the specified amount of memory.
//| Parms:
//|   in       size                 How much memory
//| Returns:
//|   Long pointer to void
//*---------------------------------------------------------------------------------
void FAR * DoGetMemory(DWORD size)
{
   LPVOID   tmp;

   tmp = GlobalAllocPtr(GMEM_FIXED | GMEM_ZEROINIT, size);
   if(!tmp)
      szMessageBox(GetActiveWindow(),
                   MB_ICONEXCLAMATION,
                   (LPSTR)szErrTitle,
                   GetidsString(idsOutOfMemory, OutStr, MAXBUFF));
   return tmp;
}

//*---------------------------------------------------------------------------------
//| DoReleaseMemory:
//|   Free up the memory we have requested
//| Parms:
//|   ptr         The pointer to free
//| Returns:
//|   Nothing.
//*---------------------------------------------------------------------------------
void DoReleaseMemory(LPVOID ptr)
{
   GlobalFreePtr(ptr);
}

//*---------------------------------------------------------------------------------
//| RemoveCrLf:
//|   This will remove all carriage return/line feeds from the input buffer.
//| Parms:
//|   in       instr                Null terminated string
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void RemoveCrLf(LPSTR instr)
{
   LPSTR str=instr;

   if(!str ||
      !*str)
      return;
   while((str = _fstrstr(str, (LPSTR)szcrlf))) {
      *str++ = ' ';
      *str++ = ' ';
   }
}

//*---------------------------------------------------------------------------------
//| GetNewDirectory:
//|   This function will take a complete file name (must have path included)
//|   and return only the path portion with no trailing '\'
//| Parms:
//|   outstr            Output path name with no file
//|   instr             Input complete file name
//| Returns:
//|   Nothing
//*---------------------------------------------------------------------------------
void GetNewDirectory(LPSTR outstr, LPSTR instr)
{
   LPSTR    str=outstr;
   LPSTR    lstr=outstr;

   lstrcpy(str, instr);
   while((str = _fstrchr(lstr+1, '\\')))
      lstr = str++;
   *++lstr = '\0';
}

//*---------------------------------------------------------------------------------
//| ValidName:
//|   This function parses a string to look for invalid characters which would
//|   preclude it from being written as a section or entry in an .ini file.
//| Parms:
//|   instr             Input complete file name
//| Returns:
//|   TRUE if it is valid, FALSE on error
//*---------------------------------------------------------------------------------
BOOL ValidName(LPSTR instr)
{
   LPSTR str=instr;
   if(!str)
      return TRUE;
   while(*str)
      switch(*str) {
        case '[':
        case ']':
        case '=':
         return FALSE;

        default:
         ++str;
      }
   return TRUE;
}

//*---------------------------------------------------------------------------------
//| lpatoi:
//|   atoi only works for NEAR host vars, which makes it useless in a large
//|   application.  This function tricks atoi by copy the long string to a
//|   local variable and then doing the conversion.  This is a major cluge,
//|   but a necessary one.
//| Parms:
//|   instr             Input number
//| Returns:
//|   The integer value of instr
//*---------------------------------------------------------------------------------
int lpatoi(LPSTR instr)
{
   char szStr[35];
   lstrcpy((LPSTR)szStr, instr);
   return atoi(szStr);
}

//*------------------------------------------------------------------------
//|  GetidsString:
//|      Will retrieve a string from our resource fork given the id.
//|  Parms:
//|      ids            The id of the string
//|      szOut          Output buffer for string
//|      cbSize         How big is the buffer
//|  Returns:
//|      Pointer to szOut
//*------------------------------------------------------------------------
LPSTR EXTFUN GetidsString(UINT ids, LPSTR szOut, UINT cbSize)
{
   extern HINSTANCE hInst;

   if(!szOut)
      return NULL;
   if(!LoadString(hInst, ids, szOut, cbSize))
      lstrcpy(szOut, "Not found");
   return szOut;
}

//*------------------------------------------------------------------------
//|  szWrite:
//|      Allows you to format an output string which is then added
//|      to the specified edit window.
//|  Parms:
//|      hwnd           Edit window for output
//|      szFmt          Format string
//|      (varying)      Arguements for format string
//|  Returns:
//|      Nothing
//*------------------------------------------------------------------------
VOID FAR CDECL szWrite(HWND hwnd, LPSTR szFmt, ...)
{
#define MAXEDITBUFF 30000
   static      char  szBuffer[MAXBUFF];
   UCHAR *     pszBuffer;
   UCHAR       bufFmt[MAXBUFF];
   va_list           marker;
   UINT              rtn=0;
   UINT              len=0;

   pszBuffer = &szBuffer[0];
   lstrcpy(bufFmt, szFmt);

   // Use format and arguements as input
   va_start(marker, szFmt);
   if (_vsnprintf(pszBuffer, MAXBUFF, bufFmt, marker) < 0) {
      wsprintf(pszBuffer,"Buffer overflow reporting '%*.*s'", 50,50,(LPSTR)szBuffer);
      return;
   }
   va_end(marker);

   // Now we have the string to add to the end of our output.  Verify that the
   // new string will not be too large and set selection accordingly.
   len = (UINT)SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L);
   if(len + lstrlen(pszBuffer) > MAXEDITBUFF) {    //  Need to truncate
      SendMessage(hwnd, EM_SETSEL, 0, MAKELPARAM(0,len + 10));
      SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)((LPSTR)"...\r\n"));
      SendMessage(hwnd, EM_SETSEL, 0, MAKELPARAM(len,len));
   }
   SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)((LPSTR)pszBuffer));
}

//*------------------------------------------------------------------------
//|  szMessageBox:
//|      Works like sprintf only the output goes to a message box.
//|  Parms:
//|      hwnd           Owner window, NULL uses GetActiveWindow
//|      style          Flags for MessageBox
//|      szTitle        Title for message box
//|      szFmt          Format string
//|      (varying)      Arguements for format string
//|  Returns:
//|      Id from MessageBox
//*------------------------------------------------------------------------
int FAR CDECL szMessageBox(HWND hwnd, UINT style, LPSTR szTitle, LPSTR szFmt, ...)
{
   char        szBuffer[MAXBUFF];
   char *      pszBuffer;
   UCHAR       bufFmt[MAXBUFF];
   va_list     marker;

   pszBuffer = &szBuffer[0];
   lstrcpy(bufFmt, szFmt);

   // Use format and arguements as input
   va_start(marker, szFmt);
   if (_vsnprintf(pszBuffer, MAXBUFF, bufFmt, marker) < 0)
      wsprintf(pszBuffer,"Buffer overflow reporting '%*.*s'", 50,50,szBuffer);
   va_end(marker);

   return(MessageBox((hwnd) ? hwnd : GetActiveWindow(),
                     pszBuffer,
                     szTitle,
                     style) == IDOK);
}


STANDARD.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   STANDARD.H
//|      This module contains standard includes which are required by many modules.
//|         This include file is not specific for any tool.
//|   Notes:
//|      1) If using this file for a DLL, #define STANDARD_DLL, otherwise it is
//|            assumed that it is being used for a Windows application.
//*---------------------------------------------------------------------------------
#ifndef STANDARD_DEFS
#define STANDARD_DEFS

#include <windows.h>
#include "sql.h"
#include "sqlext.h"

//----------------------------------------------------------------------------------
//    Defines and macros
//----------------------------------------------------------------------------------
#define TEMP_SIZE    50
#define MAX_BUFF     1000
#define  MAXBUFF           256
#define  MAXFILEBUFF       32000
#define  DFTBUFFSIZE       30
#define  LARGEBUFF         65535

//
// When calling ODBC functions, you should allow for the case where
// SQL_SUCCESS_WITH_INFO was returned.  Unless you want to see the informational
// SQL state, you should treat these as the same.  The following macros are
// handy to do just that.
//
#define RC_SUCCESSFUL(rc)  (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))

//
// Win 3.x specific items
//
#ifndef WIN32

#define INTFUN FAR PASCAL
#define EXTFUN FAR PASCAL __export
#define dCSEG(type) const type _based(_segname("_CODE"))

//
// 32-bit stuff
//
#else
#define MoveTo(hdc, x, y)        MoveToEx(hdc, x, y, NULL)
#define dCSEG(type) const type
#define INTFUN WINAPI
#define EXTFUN WINAPI

#define WRITE     OF_WRITE
#define READ      OF_READ
#endif

#ifdef WIN32
#else
#endif
#define VSZFile static char vszFile[]=__FILE__;

#ifdef _DEBUG
#define DEBUGMSG(msg) OutputDebugString((LPSTR)msg)
#else
#define DEBUGMSG(msg)
#endif

#ifdef _DEBUG
void FAR * DebugGetMemory(DWORD size, LPSTR szFile, int cbLine);
void DebugReleaseMemory(LPVOID ptr, LPSTR szFile, int cbLine);
void WinAssertReal(int exp, LPSTR msg, LPSTR file, int line);

#define WinAssert(exp, msg)                                             \
         WinAssertReal(exp, msg, vszFile, __LINE__);
#define GetMemory(size) DebugGetMemory(size, vszFile, __LINE__)
#define ReleaseMemory(size) DebugReleaseMemory(size, vszFile, __LINE__)
#define DEBUGADDR(title, hwnd, ptr)                                                                      \
   {                                                                                                     \
   static char szTmp[100];                                                                               \
   wsprintf(szTmp, "%s, hwnd: %04X, ptr: %04X:%04X", (LPSTR)title, hwnd, HIWORD(ptr), LOWORD(ptr));      \
   DEBUGMSG(szTmp);                                                                                      \
   }
#else
#define  WinAssert(exp, msg)
#define GetMemory(size) DoGetMemory(size)
#define ReleaseMemory(size) DoReleaseMemory(size)
#define DEBUGADDR(p1,p2,p3)
#endif

#define NumItems(s) (sizeof (s) / sizeof(s[0]))

#define ABS(val) ((val < 0) ? (val * -1) : (val))

//*---------------------------------------------------------------------------------
//|   Global variables
//*---------------------------------------------------------------------------------
dCSEG(char) szui[];
dCSEG(char) szEmpty[];

//*---------------------------------------------------------------------------------
//|   Function prototypes
//*---------------------------------------------------------------------------------
void FAR * DoGetMemory(DWORD size);
void DoReleaseMemory(LPVOID ptr);
void RemoveCrLf(LPSTR instr);
void GetNewDirectory(LPSTR outstr, LPSTR instr);
BOOL ValidName(LPSTR instr);
int lpatoi(LPSTR instr);

LPSTR EXTFUN GetidsString(UINT ids, LPSTR szOut, UINT cbSize);
VOID FAR CDECL szWrite(HWND hwnd, LPSTR szFmt, ...);
int FAR CDECL szMessageBox(HWND hwnd, UINT style, LPSTR szTitle, LPSTR szFmt, ...);

#endif


STRINGS.H

//*---------------------------------------------------------------------------------
//|  ODBC System Administrator
//|
//|  This code is furnished on an as-is basis as part of the ODBC SDK and is
//|  intended for example purposes only.
//|
//|   Title:   STRINGS.H
//|      This file contains the defines for the string table error and
//|      information messages.  Load these strings using the GetidsString
//|      function.
//*---------------------------------------------------------------------------------
#ifndef strings_DEFS
#define strings_DEFS

//*---------------------------------------------------------------------------------
//|   Macros and Defines
//*---------------------------------------------------------------------------------
#define idsFileClosed                        8000
#define idsSuccessConnect                    8001
#define idsDisconnectSuccess                 8002
#define idsErrorString                       8003
#define idsMsgErrorString                    8004
#define idsCloseFile                         8005
#define idsOpenFileFailed                    8006
#define idsSaveFileFailed                    8007
#define idsSQLAllocHandleFailed              8008
#define idsMaxRecords                        8009
#define idsStmtProcessed                     8010
#define idsAffectedRows                      8011
#define idsNoDataFound                       8012
#define idsNumTooSmall                       8013
#define idsTerminatorNotFound                8014
#define idsExecutingFile                     8015
#define idsInvalidTerminator                 8016
#define idsPipeNotFound                      8017
#define idsInvalidParamValue                 8018
#define idsOutOfMemory                       8019
#define idsInvalidCmdLine                    8020
#define idsCmdLineTooBig                     8021
#define idsDftFileFilter                     8022
#define idsExpression                        8023
#define idsInvalidInt                        8024
#define idsTooManyParms                      8025
#define idsInvalidName                       8026
#define idsTooManyConnects                   8027

#endif


CONVDSN.C

/*
** CONVDSN.C - This is the ODBC sample code for
** creating File DSN pointers to machine DSNs.
**
**This code is furnished on an as-is basis as part of the ODBC SDK and is
**intended for example purposes only.
**
*/

#include <windows.h>
#include <stdio.h>
#include "sql.h"
#include "sqlext.h"
#include "odbcinst.h"

// prototypes

void ExpandFileName(LPSTR szFileDSNName, LPCSTR szDSNName);
void MakeLegalName(LPSTR szLegalDSNName, LPCSTR szDSNName);

// main routine:  Iterate through the user and system DSNs, creating a pointer
//                to each.

int _stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpszCmdLine, int nCmdShow)
{
HENV    henv;
RETCODE retcode;
CHAR    szDSNName[SQL_MAX_DSN_LENGTH + 1];
CHAR    szLegalDSNName[SQL_MAX_DSN_LENGTH + 1];
CHAR    szFileDSNName[MAX_PATH + 1];

if(SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv)))
{
// set the ODBC behavior version.
(void) SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);

// Enumerate the user data sources.
retcode = SQLDataSources(henv, SQL_FETCH_FIRST_SYSTEM, szDSNName,
sizeof(szDSNName), NULL, NULL, 0, NULL);

while(SQL_SUCCEEDED(retcode))
{
// Make a version of the name acceptable for use as a filename.
MakeLegalName(szLegalDSNName, szDSNName);

// Build the full filename.
ExpandFileName(szFileDSNName, szLegalDSNName);

// Write the DSN= keyword into the File DSN.
SQLWriteFileDSN(szFileDSNName, "ODBC", "DSN", szDSNName);

// Do the next one, if it exists.
retcode = SQLDataSources(henv, SQL_FETCH_NEXT, szDSNName,
sizeof(szDSNName), NULL, NULL, 0, NULL);
}

// Enumerate the system data sources.
retcode = SQLDataSources(henv, SQL_FETCH_FIRST_USER, szDSNName,
sizeof(szDSNName), NULL, NULL, 0, NULL);

while(SQL_SUCCEEDED(retcode))
{
// Make a version of the name acceptable for use as a filename.
MakeLegalName(szLegalDSNName, szDSNName);

// Build the full filename.
ExpandFileName(szFileDSNName, szLegalDSNName);

// Write the DSN= keyword into the File DSN.
SQLWriteFileDSN(szFileDSNName, "ODBC", "DSN", szDSNName);

// Do the next one, if it exists.
retcode = SQLDataSources(henv, SQL_FETCH_NEXT, szDSNName,
sizeof(szDSNName), NULL, NULL, 0, NULL);
}

SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

return 0;
}

// ExpandFileName:  Take the pieces of the filename, and form a complete
//                  filename.

void ExpandFileName(LPSTR szFileDSNName, LPCSTR szDSNName)
{
strcpy(szFileDSNName, szDSNName);

// Hint that we're not sharable
strcat(szFileDSNName, " (not sharable)");
}

// MakeLegalName:  Make a version of the DSN name suitable for use as a
//                 filename.

void MakeLegalName(LPSTR szLegalDSNName, LPCSTR szDSNName)
{
CHAR   szIllegalChars[] = "\\/:*?\"<>|";
LPSTR  pchIllegalChar;
LPCSTR pchDSNSource     = szDSNName;
LPSTR  pchDSNTarget     = szLegalDSNName;
BOOL   fIllegal;

while(*pchDSNSource)
{
pchIllegalChar = szIllegalChars;

fIllegal = FALSE;

// if the character is double byte, copy it and move ahead
if(IsDBCSLeadByte(*pchDSNSource))
{
*(pchDSNTarget++) = *(pchDSNSource++);
*(pchDSNTarget++) = *(pchDSNSource++);
}
else
{
// single byte
if(!isalpha(*pchDSNSource) && !isdigit(*pchDSNSource))
{
while(*pchIllegalChar)
{
if(*pchDSNSource == *(pchIllegalChar++))
{
fIllegal = TRUE;
break;
}
}
}

if(fIllegal)
{
pchDSNSource++;
}
else
{
*(pchDSNTarget++) = *(pchDSNSource++);
}
}
}
*pchDSNTarget = '\0';
}


CHILD.C

/*--------------------------------------------------------------------------
  Child.C --- Cursors child window procedure

  Description:
   This sample is spread across four files, each named for the role
   the contained functions play.  Each file header contains a brief
   description of its purpose and the routines it contains.

   CHILD.C contains those routines which maintain a child window.
   These include most interfaces with ODBC including all data
   retrieval and display.  These functions are:

     AllocChild      - Allocate and prepare child window memory
     AllocClipRgn    - Allocate clip region for painting
     Cancel          - Cancel SQL request
     CancelSQL       - Cancel asynchronous SQL request
     ChildProc       - Process child window messages
     CvtSqlToCType   - Return the default ODBC C type for a SQL type
     DeleteRow       - Build and issue a positioned delete
     DoChildMenu     - Process a menu request
     DoSQL           - Issue SQL statement and prepare all required
                       variables necessary for displaying the data
     Fetch           - Retrieve one row set
     FreeStmt        - Issue ODBC SQLFreeStmt (and adjust child memory)
     GetCurrentValue - Retrieve (in character format) column value
                       from current row (used by DIALOGS.C)
     GetData         - Issue a SQLGetData request
     GetTableName    - Extract table name from SQL
     IsUpdateable    - Check whether a column can be updated
     OnDataRow       - Determine if point is over displayed row of data
     PaintChild      - Paint child window
     ParamValid      - Validate the max column width of lpChild
     SetCurrentValue - Set a column value in the current row
                       (used by DIALOGS.C)
     SetPos          - Set current position in row set
     SetScroll       - Set scroll bar states and ranges
     SizeScroll      - Size and position scroll bars
     UpdateRow       - Build and issue a positioned update request

   This code is furnished on an as-is basis as part of the ODBC SDK and is
   intended for example purposes only.

--------------------------------------------------------------------------*/

/* Includes --------------------------------------------------------------*/
#include "headers.h"

#pragma warning(disable:4001)
#include   "resource.h"
#include   "crsrdemo.h"

// Constants ---------------------------------------------------------------
#define fDISABLED (MF_BYCOMMAND | MF_DISABLED | MF_GRAYED)
#define fENABLED  (MF_BYCOMMAND | MF_ENABLED)
#define NULLIFEMPTY(x) (*x?x:NULL)

const char szDELETE[]       = "DELETE FROM ";
const char szFROM[]         = "FROM";
const char szUPDATE[]       = "UPDATE ";
const char szWHERE[]        = " WHERE CURRENT OF ";
const char szNoDataTitle[]  = "No data to display";
const char szNoData[]       = "The query didn't return any data";
const char szSET[]          = " SET ";
const char szDataAffected[] = "%ld rows were affected";
const char szRowAffected[]  = "%ld row was affected";

const int   cMAXCOLS   = 15;

#define Async(x)     lpChild->fCanceled = FALSE;               \
                     while ((rc = (x)) == SQL_STILL_EXECUTING) \
                     CancelSQL(lpChild);

#define STMTError(x) ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmt, (x))

// Prototypes --------------------------------------------------------------
LPCHILD INTFUNC AllocChild(HWND);
void    INTFUNC AllocClipRgn(LPCHILD);
void    INTFUNC Cancel(LPCHILD);
void    INTFUNC CancelSQL(LPCHILD);
SWORD   INTFUNC CvtSqlToCType(SWORD);
void    INTFUNC DeleteRow(LPCHILD);
BOOL    INTFUNC DoChildMenu(LPCHILD, WPARAM, LPARAM);
void    INTFUNC DoSQL(LPCHILD);
void    INTFUNC Fetch(LPCHILD);
void    INTFUNC FreeStmt(UWORD, LPCHILD);
void    INTFUNC GetData(LPCHILD);
void    INTFUNC GetTableName(LPSTR, LPCSTR);
int     INTFUNC OnDataRow(LPCHILD, LPARAM);
void    INTFUNC PaintChild(LPCHILD, HDC, BOOL, BOOL, BOOL);
BOOL    INTFUNC  ParamValid(LPCHILD);
void    INTFUNC SetPos(LPCHILD, UWORD);
void    INTFUNC SetScroll(LPCHILD);
void    INTFUNC SizeScroll(LPCHILD);
void    INTFUNC UpdateRow(LPCHILD);
#ifdef THREAD
void    INTFUNC DeleteRowThread(LPCHILD);
void    INTFUNC DoSQLThread(LPCHILD);
void    INTFUNC FetchThread(LPCHILD);
void    INTFUNC GetDataThread(LPCHILD);
void    INTFUNC UpdateRowThread(LPCHILD);
#endif

/* AllocChild --------------------------------------------------------------
   Description: Allocate and initialize child variables
   --------------------------------------------------------------------------*/
LPCHILD INTFUNC AllocChild(HWND hwnd)
{
   SQLHSTMT hstmt;
   LPCHILD  lpChild;
   char  sz[cbSTRLEN];

   // Allocate ODBC SQLHSTMT and set cursor name
   if (DBCError(hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &hstmt)))
      return NULL;

#ifdef THREAD
   wsprintf(sz, szTITLEFMT, (LPSTR)g_szDSN, g_cCursor, GetCurrentThreadId());
#else
   wsprintf(sz, szTITLEFMT, (LPSTR)g_szDSN, g_cCursor);
#endif

   SetWindowText(hwnd, sz);

   wsprintf(sz, szCURSORNAME, g_cCursor);
   if (ODBCError(hwnd, SQL_HANDLE_STMT, hstmt,
                 SQLSetCursorName(hstmt, (UCHAR FAR *)sz, SQL_NTS)))
      return NULL;

   // Allocate child window structure and initialize
   lpChild = (LPCHILD)AllocPtr(sizeof(CHILD));

   lpChild->hwnd             = hwnd;
   lpChild->fInSetScroll     = FALSE;
   lpChild->fIsMinimized     = FALSE;
   lpChild->fHaveMouse       = FALSE;
   lpChild->iMouseRow        = -1;
   lpChild->fNoConcurrency   = FALSE;
   lpChild->fNoCursorType    = FALSE;

   lpChild->ccols            = 0;
   lpChild->crowwin          = 0;
   lpChild->ccolwin          = 0;
   lpChild->fVScroll         =
      lpChild->fHScroll         = FALSE;
   lpChild->lpsz             = AllocPtr(cbBUFSIZE);

   lpChild->hrgn             = NULL;

   lpChild->hstmt            = hstmt;
   lpChild->hstmtTmp         = SQL_NULL_HSTMT;

   lpChild->fBindByRow       = IDC_RADIO_BINDROW;
   lpChild->fConcurrency     = SQL_CONCUR_VALUES;
   lpChild->crowKeyset       = SQL_CURSOR_STATIC;
   lpChild->crowRowset       = 10;
   lpChild->fAsync           = FALSE;
   lpChild->irowPos          = 0;
   lpChild->irow             = 0;
   lpChild->cBind            = 0;
   lpChild->fBindAll         = TRUE;
   lpChild->ccolRetrieved    = 0;

   lpChild->arow             = 1;
   lpChild->rrow             = 10;
   lpChild->ccol             = 0;
   lpChild->lpnTabs          = NULL;
   lpChild->lpcol            = NULL;
   lpChild->lpfStatus        = NULL;
   lpChild->fResultSetExists = FALSE;
   lpChild->fDataFetched     = FALSE;
   lpChild->rglpv            = NULL;
   lpChild->crowMaxBind      = DEF_MAXBIND;

   lpChild->lpb              = NULL;
   lpChild->sql              = AllocPtr(cbMAXSQL);
   lpChild->cbrow            = 0;
   lpChild->dwGuiFlags       = GUIF_ALWAYSFETCH;
#ifdef THREAD
   InitializeCriticalSection (&lpChild->ThreadCreation);
#endif

   // Create scroll bars
   lpChild->hwndVScroll = CreateWindow(szSCROLLCLASS, NULL,
                                       WS_CHILD | SBS_VERT,
                                       0, 0, 0, 0,
                                       hwnd, (HMENU)1, g_hinst, NULL);

   lpChild->hwndHScroll = CreateWindow(szSCROLLCLASS, NULL,
                                       WS_CHILD | SBS_HORZ,
                                       0, 0, 0, 0,
                                       hwnd, (HMENU)2, g_hinst, NULL);

   // Load default SQL string
   LoadString(g_hinst, IDS_SQL, sz, cbSTRLEN);
   wsprintf(lpChild->sql, sz, g_szTable);

   return lpChild;
}

/* AllocClipRgn ------------------------------------------------------------
   Description: Allocate child window clip region
   --------------------------------------------------------------------------*/
void INTFUNC AllocClipRgn(LPCHILD lpChild)
{
   RECT  rc;

   // Determine client window size less space for scroll bars
   GetClientRect(lpChild->hwnd, &rc);

   if (lpChild->hrgn) DeleteObject(lpChild->hrgn);

   if (lpChild->fVScroll)
      rc.right -= g_cxVScroll - 1;
   rc.bottom -= g_cyHScroll - 1;

   // Allocate clip region
   lpChild->hrgn = CreateRectRgn(rc.left,
                                 rc.top,
                                 rc.right,
                                 rc.bottom);
   return;
}

/* CancelSQL ---------------------------------------------------------------
   Description: Display message while an async request is executing and
                give the user a chance to cancel the request (if it has
                not already been canceled)
                --------------------------------------------------------------------------*/
void INTFUNC CancelSQL(LPCHILD lpChild)
{
   char  sz[cbSTRLEN];
   int   rc;

   // Display message
   LoadString(g_hinst, IDS_STILLEXEC, sz, sizeof(sz));
   rc = MessageBox(lpChild->hwnd,
                   sz, g_szTITLE,
                   MB_ICONINFORMATION |
                   (lpChild->fCanceled
                    ? MB_OK
                    : MB_OKCANCEL | MB_DEFBUTTON1));

   // If the user requested, cancel the current request
   if (rc == IDCANCEL)
      lpChild->fCanceled = SUCCESS(SQLCancel(lpChild->hstmt));

   return;
}

/* ChildProc ---------------------------------------------------------------
   Description: Child window procedure
   --------------------------------------------------------------------------*/
LRESULT EXPFUNC ChildProc(HWND    hwnd,
                          UINT   msg,
                          WPARAM wparam,
                          LPARAM lparam)
{
   LPCHILD  lpChild;

   // Get access to child variables and set current window handle
   lpChild = (LPCHILD)GetWindowLong(hwnd, 0);

   switch (msg) {

      // Allocate child variables and save pointer
     case WM_CREATE:
      lpChild = AllocChild(hwnd);

      SetWindowLong(hwnd, 0, (LONG)lpChild);

      if (!lpChild)
         return -1;
      break;

      // Paint child window (active or inactive)
     case WM_PAINT: {
        PAINTSTRUCT ps;
        BOOL     fActive;

        fActive = (hwnd ==
                   FORWARD_WM_MDIGETACTIVE(g_hwndClient, SendMessage));

        BeginPaint(hwnd, &ps);
        PaintChild(lpChild, ps.hdc, TRUE, FALSE, fActive);
        EndPaint(hwnd, &ps);
        break;
     }

      // Trap mouse if over a rowset row
     case WM_LBUTTONDOWN:
      lpChild->iMouseRow = OnDataRow(lpChild, lparam);
      if (lpChild->iMouseRow >= 0) {
         lpChild->fHaveMouse = TRUE;
         SetCapture(hwnd);
      }
      break;

      // Make row current row (if mouse is still on the row)
     case WM_LBUTTONUP:
      if (!lpChild->fHaveMouse)
         break;

      ReleaseCapture();
      lpChild->fHaveMouse = FALSE;

      if (lpChild->fDataFetched                         &&
          lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
          lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY &&
          lpChild->iMouseRow    == OnDataRow(lpChild, lparam)) {
         RECT  rc;
         int   y;

         GetClientRect(hwnd, &rc);

         y = (int)HIWORD(lparam) - rc.top - g_cy;

         SetPos(lpChild,
                (UWORD)(GetScrollPos(lpChild->hwndVScroll, SB_CTL) + (y / g_cy) + 1));
      }
      break;

      // Convert keyboard requests to scroll and change window requests
     case WM_KEYDOWN:
      if (wparam == VK_TAB) {
         FORWARD_WM_MDINEXT(g_hwndClient, hwnd,
                            (GetKeyState(VK_BACK) & 0x1000 ? TRUE :FALSE),
                            SendMessage);
         break;
      }

      else if (wparam == VK_DOWN || wparam == VK_UP) {

         msg    = WM_VSCROLL;
         GET_WM_VSCROLL_CODE(wparam, lparam) =
            (wparam == VK_DOWN
             ? SB_LINEDOWN
             : SB_LINEUP);
      }
      else if (wparam == VK_LEFT || wparam == VK_RIGHT) {
         if (!lpChild->fHScroll)
            break;

         msg    = WM_HSCROLL;
         GET_WM_HSCROLL_CODE(wparam, lparam) =
            (wparam == VK_RIGHT
             ? SB_LINEDOWN
             : SB_LINEUP);
      }
      else
         break;

      // Scroll window
     case WM_HSCROLL:
     case WM_VSCROLL: {
        HWND  hwndCtl;
        int   cInc;
        int   iPos;
        int   cPage;
        int   nPos;
        int   iOrig;
        int   nMin, nMax;

        if (!lpChild->fDataFetched)
           break;

        // Determine scroll direction and distance
        hwndCtl = (msg == WM_HSCROLL
                   ? lpChild->hwndHScroll
                   : lpChild->hwndVScroll);
        cInc    = (msg == WM_HSCROLL ? 1 : 1);
        cPage   = (msg == WM_HSCROLL
                   ? lpChild->ccolwin
                   : lpChild->crowwin - 1);
        nPos    = GET_WM_HSCROLL_POS(wparam, lparam);
        iPos    =
           iOrig   = GetScrollPos(hwndCtl, SB_CTL);

        GetScrollRange(hwndCtl, SB_CTL, &nMin, &nMax);
        switch (GET_WM_HSCROLL_CODE(wparam, lparam)) {
          case SB_BOTTOM:        iPos = nMax;  break;
          case SB_LINEDOWN:      iPos+= cInc;  break;
          case SB_LINEUP:        iPos-= cInc;  break;
          case SB_PAGEDOWN:      iPos+= cPage; break;
          case SB_PAGEUP:        iPos-= cPage; break;
          case SB_TOP:           iPos = nMin;  break;
          case SB_THUMBPOSITION: iPos = nPos;  break;
        }

        // For updateable cursors, vertical scroll requests move the
        // current row scroll the window only as needed to keep the
        // current row visible
        if (msg == WM_VSCROLL &&
            lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY &&
            lpChild->fConcurrency != SQL_CONCUR_READ_ONLY) {
           int   delta;

           if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEDOWN)
              delta = cInc;
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEUP)
              delta = -cInc;
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_PAGEDOWN) {
              if (iPos <= nMax)
                 delta = cPage;
              else
                 delta = lpChild->crowRowset - lpChild->irowPos;
           }
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_PAGEUP) {
              if (iPos >= nMin)
                 delta = -cPage;
              else
                 delta = 1 - lpChild->irowPos;
           }
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_BOTTOM)
              delta = lpChild->crowRowset - lpChild->irowPos;
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_TOP)
              delta = 1 - lpChild->irowPos;
           else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_THUMBPOSITION) {
              if (lpChild->irowPos > (UWORD)iPos &&
                  lpChild->irowPos < (UWORD)(iPos+lpChild->crowwin))
                 delta = 0;
              else if (iPos == nMin)
                 delta = 1 - lpChild->irowPos;
              else if (iPos == nMax)
                 delta = lpChild->crowRowset - lpChild->irowPos;
              else if (iPos <= iOrig)
                 delta = iPos + lpChild->crowwin - 1 - lpChild->irowPos;
              else
                 delta = iPos - lpChild->irowPos + 1;
           }
           else
              break;

           SetPos(lpChild, (UWORD)(lpChild->irowPos + delta));

           if ((GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEDOWN ||
                GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEUP) &&
               lpChild->irowPos > (UWORD)iOrig                &&
               lpChild->irowPos < (UWORD)(iOrig+lpChild->crowwin))
              break;
        }

        // Pin scroll requests within scroll boundaries
        if (iPos < nMin)
           iPos = nMin;
        else if (iPos > nMax)
           iPos = nMax;

        // Scroll the window if movement has occurred
        if (iPos != iOrig) {
           HDC      hdc;
           BOOL  fTitle;

           hdc = GetDC(hwnd);

           SetScrollPos(hwndCtl, SB_CTL, iPos, TRUE);

           fTitle = (msg == WM_HSCROLL);

           PaintChild(lpChild, hdc, fTitle, FALSE, TRUE);

           ReleaseDC(hwnd, hdc);
        }
        break;
     }

      // Activate the child window
     case WM_MDIACTIVATE: {
        HDC   hdc;

        AdjustMenus();

        hdc = GetDC(hwnd);

        PaintChild(lpChild, hdc, TRUE, TRUE,
                   GET_WM_MDIACTIVATE_FACTIVATE(lpChild->hwnd, wparam, lparam));

        ReleaseDC(lpChild->hwnd, hdc);
        break;
     }

      // Free all child memory
     case WM_DESTROY:
      if (lpChild) {
         FreeStmt(SQL_DROP, lpChild);

         FreePtr(lpChild->lpsz);
         FreePtr(lpChild->sql);
#ifdef THREAD
         DeleteCriticalSection (&lpChild->ThreadCreation);
#endif
         FreePtr(lpChild);

         SetWindowLong(hwnd, 0, 0L);
      }
      break;

      // Close the window
     case WM_CLOSE:
      g_cChild--;

      if( !g_cChild )
#ifdef WIN32
         SendMessage(g_hwndClient, WM_MDISETMENU,
                     (WPARAM)g_hmenuFrame,
                     (LPARAM)g_hmenuFrameWindow);
#else
      FORWARD_WM_MDISETMENU(g_hwndClient, 0, g_hmenuFrame,
                            g_hmenuFrameWindow, SendMessage);
#endif

      // Destroy child window
      FORWARD_WM_MDIDESTROY(g_hwndClient, hwnd, SendMessage);
      AdjustMenus();
      break;

      // Pass all other messages (eventually) to the MDI window procedure
     default:

      // Reset scroll bars (if needed) when the window is resized
      if (msg == WM_SIZE) {

         if (wparam == SIZE_MINIMIZED)
            lpChild->fIsMinimized = TRUE;

         else {
            if (lpChild->fIsMinimized)
               lpChild->fIsMinimized = FALSE;

            SizeScroll(lpChild);

            if (lpChild->fDataFetched) {
               int   row;

               SetScroll(lpChild);

               row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);

               if (lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
                   lpChild->irowPos >= (UWORD)(row+lpChild->crowwin))
                  if (lpChild->crowwin > 1)
                     SetScrollPos(lpChild->hwndVScroll,
                                  SB_CTL,
                                  lpChild->irowPos-lpChild->crowwin+1,
                                  TRUE);
                  else
                     SetScrollPos(lpChild->hwndVScroll,
                                  SB_CTL,
                                  lpChild->irowPos-lpChild->crowwin,
                                  TRUE);
            }

            AllocClipRgn(lpChild);
         }

         InvalidateRect(hwnd, NULL, TRUE);
      }

      // Handle child window menu requests
      else if (msg == WM_COMMAND)
         DoChildMenu(lpChild, wparam, lparam);

      // Pass message on to the MDI window procedure
      return DefMDIChildProc(hwnd, msg, wparam, lparam);
   }

   return (LRESULT)NULL;
}

/* CvtSqlToCType -----------------------------------------------------------
   Description: Determine the default ODBC C type for a given SQL type
   --------------------------------------------------------------------------*/
SWORD INTFUNC CvtSqlToCType(SWORD fSqlType)
{
   switch (fSqlType) {
     case SQL_CHAR:
     case SQL_VARCHAR:
     case SQL_LONGVARCHAR:
     case SQL_DECIMAL:
     case SQL_NUMERIC:
     case SQL_BIGINT:           return SQL_C_CHAR;

     case SQL_BIT:              return SQL_C_BIT;
     case SQL_TINYINT:
     case SQL_SMALLINT:         return SQL_C_SHORT;
     case SQL_INTEGER:          return SQL_C_LONG;
     case SQL_REAL:             return SQL_C_FLOAT;

     case SQL_FLOAT:
     case SQL_DOUBLE:           return SQL_C_DOUBLE;

     case SQL_BINARY:
     case SQL_VARBINARY:
     case SQL_LONGVARBINARY:    return SQL_C_BINARY;

     case SQL_TYPE_DATE:        return SQL_C_TYPE_DATE;
     case SQL_TYPE_TIME:        return SQL_C_TYPE_TIME;
     case SQL_TYPE_TIMESTAMP:   return SQL_C_TYPE_TIMESTAMP;

     case SQL_DATE:             return SQL_C_DATE;
     case SQL_TIME:             return SQL_C_TIME;
     case SQL_TIMESTAMP:        return SQL_C_TIMESTAMP;
   }

   return SQL_C_CHAR;
}

/* DeleteRow ---------------------------------------------------------------
   Description: Delete the current (positioned) row
   --------------------------------------------------------------------------*/
void INTFUNC DeleteRow(LPCHILD lpChild)
{
   HCURSOR  hcur;
   LPSTR    lpsz;
   LPSTR    lpszT;
   SWORD    cb;

   // Ensure the delete request is valid
   if (!lpChild->fDataFetched) {
      DoMessage(lpChild->hwnd, IDS_NODATAFETCHED);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_NOROW) {
      DoMessage(lpChild->hwnd, IDS_NOROWDELETE);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_ERROR) {
      DoMessage(lpChild->hwnd, IDS_ERRORROWDELETE);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_DELETED) {
      DoMessage(lpChild->hwnd, IDS_DELROWDELETE);
      return;
   }

   if (lpChild->fConcurrency == SQL_CONCUR_READ_ONLY) {
      DoMessage(lpChild->hwnd, IDS_NOUPDATE);
      return;
   }

   lpsz  = AllocPtr(2 * cbMAXSQL);
   lpszT = lpsz + cbMAXSQL;

   // Verify the request and allocate a new (temporary) SQLHSTMT for the delete
   LoadString(g_hinst, IDS_DELETEROW, lpsz, cbMAXSQL);
   if (IDYES == MessageBox(lpChild->hwnd, lpsz,
                           g_szTITLE, MB_ICONQUESTION | MB_YESNO) &&
       !DBCError(lpChild->hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &lpChild->hstmtTmp))) {

      // Build DELETE <table> WHERE CURRENT OF <cursor> statement
      lstrcpy(lpsz, szDELETE);

      GetTableName(lpszT, lpChild->sql);
      lstrcat(lpsz, lpszT);

      lstrcat(lpsz, szWHERE);

      lpszT = lpsz + lstrlen(lpsz);

      hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

      if (!STMTError(SQLGetCursorName(lpChild->hstmt, (UCHAR FAR *)lpszT,
                                      cbMAXSQL, &cb))) {

         // Issue the request via SQLPrepare/SQLExecute
         if (!ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
                        SQLPrepare(lpChild->hstmtTmp, (UCHAR FAR *)lpsz, SQL_NTS)) &&
             !ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
                        SQLExecute(lpChild->hstmtTmp)) ) {
            UWORD irowPos;

            irowPos = lpChild->irowPos;

            // Completely refresh local rowset buffer
            lpChild->rrow = 0;
            lpChild->FetchOP = SQL_FETCH_RELATIVE;
            Fetch(lpChild);

            // Reset current position (fetching sets it to the first row)
            SetPos(lpChild, irowPos);

            // Repaint window
            InvalidateRect(lpChild->hwnd, NULL, FALSE);
         }
      }

      DBCError(lpChild->hwnd, SQLFreeHandle(SQL_HANDLE_STMT,lpChild->hstmtTmp));
      lpChild->hstmtTmp = SQL_NULL_HSTMT;

      SetCursor(hcur);

   }

   FreePtr(lpsz);

   return;
}

/* DoChildMenu -------------------------------------------------------------
   Description: Respond to a request from the child window menu
   --------------------------------------------------------------------------*/
BOOL INTFUNC DoChildMenu(LPCHILD lpChild, WPARAM  wParam, LPARAM  lParam)
{
   UNREF_PARAM (lParam);
   switch (GET_WM_COMMAND_ID(wParam, lparam)) {

     case IDM_STMT_SEND:
      if (IDOK == DoDialog(lpChild->hwnd, IDD_STATEMENT, StmtDlgProc))
#ifdef THREAD
         DoSQLThread(lpChild);
#else
      DoSQL(lpChild);
#endif
      break;

     case IDM_STMT_TABLE:
      if (IDOK == DoDialog(lpChild->hwnd, IDD_TABLE_INFO, SQLTablesDlgProc))
#ifdef THREAD
         DoSQLThread(lpChild);
#else
      DoSQL(lpChild);
#endif
      break;

     case IDM_STMT_TYPE:
      lpChild->dwOperation = OPER_TYPES;
#ifdef THREAD
      DoSQLThread(lpChild);
#else
      DoSQL(lpChild);
#endif
      break;

     case IDM_STMT_OPTIONS:     // general
      {
         CHILD    ChildOld;

         // save old values (only works because no pointers
         // get modified in options)

         memcpy(&ChildOld, lpChild, sizeof(ChildOld));
         // no modification on those option values yet
         lpChild->fBind =
            lpChild->fMaxBind =
               lpChild->fRowset = FALSE;

         if (IDOK == DoDialog(lpChild->hwnd, IDD_OPTION_DIALOG,
                              OptionsDlgProc) && ParamValid(lpChild)) {
            if (lpChild->fDataFetched) {
               FreeStmt(SQL_CLOSE,  lpChild);
               FreeStmt(SQL_UNBIND, lpChild);
            }
         }
         else {
            // restore previous state
            memcpy(lpChild, &ChildOld, sizeof(ChildOld));
         }
         break;
      }

     case IDM_STMT_CANCEL:      // general
      Cancel(lpChild);
      break;

     case IDM_FETCH_FIRST:
      lpChild->FetchOP = SQL_FETCH_FIRST;
#ifdef THREAD
      FetchThread(lpChild);
#else
      Fetch(lpChild);
#endif
      break;

     case IDM_FETCH_PRIOR:
      lpChild->FetchOP = SQL_FETCH_PRIOR;
#ifdef THREAD
      FetchThread(lpChild);
#else
      Fetch(lpChild);
#endif
      break;

     case IDM_FETCH_NEXT:
      lpChild->FetchOP = SQL_FETCH_NEXT;
#ifdef THREAD
      FetchThread(lpChild);
#else
      Fetch(lpChild);
#endif
      break;

     case IDM_FETCH_LAST:
      lpChild->FetchOP = SQL_FETCH_LAST;
#ifdef THREAD
      FetchThread(lpChild);
#else
      Fetch(lpChild);
#endif
      break;

     case IDM_FETCH_ABSOLUTE:
      if (IDOK == DoDialog(lpChild->hwnd, IDD_ABSOLUTE, AbsDlgProc)) {
         lpChild->FetchOP = SQL_FETCH_ABSOLUTE;
#ifdef THREAD
         FetchThread(lpChild);
#else
         Fetch(lpChild);
#endif
      }
      break;

     case IDM_FETCH_RELATIVE:
      if (IDOK == DoDialog(lpChild->hwnd, IDD_RELATIVE, RelDlgProc)) {
         lpChild->FetchOP = SQL_FETCH_RELATIVE;
#ifdef THREAD
         FetchThread(lpChild);
#else
         Fetch(lpChild);
#endif
      }
      break;

     case IDM_FETCH_GET:
#ifdef THREAD
      GetDataThread(lpChild);
#else
      GetData(lpChild);
#endif
      break;

     case IDM_FETCH_DELETEROW:
#ifdef THREAD
      DeleteRowThread(lpChild);
#else
      DeleteRow(lpChild);
#endif
      break;

     case IDM_FETCH_UPDATEROW:
#ifdef THREAD
      UpdateRowThread(lpChild);
#else
      UpdateRow(lpChild);
#endif
      break;
     default:
      return FALSE;
   }

   AdjustMenus();
   return TRUE;
}

#ifdef THREAD
void INTFUNC DoSQLThread(LPCHILD lpChild)
{
   DWORD dwThreadId;

   EnterCriticalSection (&lpChild->ThreadCreation);
   lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)DoSQL,
                                    (LPVOID)lpChild, 0, &dwThreadId);
   LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC FetchThread(LPCHILD lpChild)
{
   DWORD dwThreadId;

   EnterCriticalSection (&lpChild->ThreadCreation);
   lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)Fetch,
                                    (LPVOID)lpChild, 0, &dwThreadId);
   LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC GetDataThread(LPCHILD lpChild)
{
   DWORD dwThreadId;

   EnterCriticalSection (&lpChild->ThreadCreation);
   lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)GetData,
                                    (LPVOID)lpChild, 0, &dwThreadId);
   LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC UpdateRowThread(LPCHILD lpChild)
{
   DWORD dwThreadId;

   EnterCriticalSection (&lpChild->ThreadCreation);
   lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)UpdateRow,
                                    (LPVOID)lpChild, 0, &dwThreadId);
   LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC DeleteRowThread(LPCHILD lpChild)
{
   DWORD dwThreadId;

   EnterCriticalSection (&lpChild->ThreadCreation);
   lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)DeleteRow,
                                    (LPVOID)lpChild, 0, &dwThreadId);
   LeaveCriticalSection (&lpChild->ThreadCreation);
}
#endif

/* DoSQL -------------------------------------------------------------------
   Description: Issue a SQL statement and prepare for fetching data
   --------------------------------------------------------------------------*/

void INTFUNC DoSQL(LPCHILD lpChild)
{
   SQLRETURN rc;

   // Prepare the statement

   if (PrepareStmt(lpChild) == SQL_ERROR)
      return;

   switch(lpChild->dwOperation) {
     case  OPER_SELECT:
      // Issue the request via SQLExecDirect
      Async(SQLExecDirect(lpChild->hstmt,
                          (UCHAR FAR *)lpChild->sql,
                          lstrlen(lpChild->sql)));
      break;

     case  OPER_TYPES:
      // Issue a GetTypeInfo request
      Async(SQLGetTypeInfo(lpChild->hstmt, SQL_ALL_TYPES));
      break;

     case  IDC_TABLE_RAD_TABLE:
      if (!*lpChild->szTable &&
          ((!*lpChild->szUser && strcmp(lpChild->szQualifier, "%") == 0)||
           (!*lpChild->szQualifier && strcmp(lpChild->szUser, "%") == 0))) {  // Special qualifier enumeration
         Async(SQLTables(lpChild->hstmt,
                         lpChild->szQualifier, SQL_NTS,
                         lpChild->szUser, SQL_NTS,
                         lpChild->szTable, SQL_NTS,
                         lpChild->szType,  SQL_NTS));
      }
      else
         {  // Normal SQLTables call
            Async(SQLTables(lpChild->hstmt,
                            NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
                            NULLIFEMPTY(lpChild->szUser), SQL_NTS,
                            NULLIFEMPTY(lpChild->szTable),  SQL_NTS,
                            NULLIFEMPTY(lpChild->szType),  SQL_NTS));
         }
      break;

     case  IDC_TABLE_RAD_PRIV:
      Async(SQLTablePrivileges(lpChild->hstmt,
                               NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
                               NULLIFEMPTY(lpChild->szUser), SQL_NTS,
                               NULLIFEMPTY(lpChild->szTable),  SQL_NTS));

      break;

     case  IDC_TABLE_RAD_STATISTICS:
      Async(SQLStatistics(lpChild->hstmt,
                          NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
                          NULLIFEMPTY(lpChild->szUser), SQL_NTS,
                          NULLIFEMPTY(lpChild->szTable),  SQL_NTS,
                          SQL_INDEX_ALL,      // XXX
                          SQL_QUICK));     // XXX
      break;

     case  IDC_TABLE_RAD_PROC:
      Async(SQLProcedures(lpChild->hstmt,
                          NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
                          NULLIFEMPTY(lpChild->szUser), SQL_NTS,
                          NULLIFEMPTY(lpChild->szTable),  SQL_NTS));

      break;

     case  IDC_TABLE_RAD_COLUMN:
      Async(SQLColumns(lpChild->hstmt,
                       NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
                       NULLIFEMPTY(lpChild->szUser), SQL_NTS,
                       NULLIFEMPTY(lpChild->szTable),  SQL_NTS,
                       NULLIFEMPTY(lpChild->szColName), SQL_NTS));

   }

   if (STMTError(rc))
      return;

   if (ProcessResults(lpChild)) {

      if (lpChild->dwGuiFlags & GUIF_ALWAYSFETCH) {
         lpChild->FetchOP = SQL_FETCH_NEXT;
         Fetch(lpChild);
      }
   }

   AdjustMenus();
   return;

}

/* ProcessResults---------------------------------------------------------------
   Description: Process the results from a query or statement
   --------------------------------------------------------------------------*/
BOOL INTFUNC ProcessResults(LPCHILD lpChild)
{
   LPINT     lptab;
   LPCOL     lpcol;
   LPBYTE    lpb;
   SDWORD    cbMsg;
   SDWORD    cbNull;
   int       nLastTab;
   SWORD     i;
   SQLRETURN rc;

   // Retrieve number of columns in the result set
   Async(SQLNumResultCols(lpChild->hstmt, &i));
   if (STMTError(rc))
      return FALSE;
   lpChild->ccol = ((UWORD)i < lpChild->cBind || lpChild->fBindAll
                    ? i
                    : lpChild->cBind);

   // If a result set exists, continue; otherwise, return immediately
   if (lpChild->ccol)
      lpChild->fResultSetExists = TRUE;
   else
      return FALSE;

   // Allocate painting related storage and row status array

   lpChild->rglpv     = (char *)AllocPtr(sizeof(char *) * lpChild->ccol);
   lpChild->lpnTabs   = (LPINT)AllocPtr(sizeof(int) * (lpChild->ccol+1));
   lpChild->lpcol     = (LPCOL)AllocPtr(sizeof(COL) * lpChild->ccol);
   lpChild->lpfStatus = (LPUWORD)AllocPtr((DWORD) sizeof(UWORD)
                                          * lpChild->crowRowset);

   for (i=1, lpcol=lpChild->lpcol; i <= lpChild->ccol; i++, lpcol++) {
      lpcol->lpb  = NULL;
      lpcol->lpcb = NULL;
   }

   cbMsg  = lstrlen(g_szRowDeleted);
   cbNull = lstrlen(g_szNull);
   cbMsg  = max(cbMsg, lstrlen(g_szNoRow));
   cbMsg  = max(cbMsg, cbNull);

   // Initialize row width (in bytes) and total number of characters per line
   lpChild->cbrow = 0;
   lpChild->ccols = 0;

   lpcol = lpChild->lpcol;
   lptab = lpChild->lpnTabs;

   nLastTab =
      *lptab++ = cxBORDER;

   // For each bound column
   //   a) Get column attributes (e.g., name, size, data type)
   //   b) Add column to physical and display row widths
   //   c) Determine tab location
   for (i=1; i <= lpChild->ccol; i++, lpcol++, lptab++) {

      // Get column name
      Async(SQLColAttribute(lpChild->hstmt, i,
                            SQL_DESC_NAME,
                            lpcol->szName, sizeof(lpcol->szName), NULL,
                            NULL));
      if (STMTError(rc)) {
         FreeStmt(SQL_DROP, lpChild);
         return FALSE;
      }

      // Get actual column length (number of physical data bytes)
// if the sample were to be used with a 3.0 driver only SQLColAttribute
// with fattribute of SQL_DESC_OCTECT_LENGTH could be called instead.
 Async(SQLColAttributes(lpChild->hstmt, i,
                            SQL_COLUMN_LENGTH,
                            NULL, 0, NULL,
                            &lpcol->cb));

      if (STMTError(rc)) {
         FreeStmt(SQL_DROP, lpChild);
         return FALSE;
      }

      // Get display width
      Async(SQLColAttribute(lpChild->hstmt, i,
                            SQL_DESC_DISPLAY_SIZE,
                            NULL, 0, NULL,
                            &lpcol->cbc));
      if (STMTError(rc)) {
         FreeStmt(SQL_DROP, lpChild);
         return FALSE;
      }

      // if the display size is too big, force to the the maximum
      if (lpcol->cbc > lpChild->crowMaxBind)
         lpcol->cbc = lpChild->crowMaxBind;

      // Ensure display width is wide enough for:
      //   a) Column name
      //   b) Null string
      //   c) Row status (for the first column only)
      if (lpcol->cbc < cbMsg)
         lpcol->cbc = cbMsg;

      if (lstrlen(lpcol->szName) > lpcol->cbc)
         lpcol->cbc = lstrlen(lpcol->szName);

      lpcol->cbc++;

      // Get column SQL type
      Async(SQLColAttribute(lpChild->hstmt, i,
                            SQL_DESC_CONCISE_TYPE,
                            NULL, 0, NULL,
                            (SDWORD FAR*) &lpcol->fSqlType));
      if (STMTError(rc)) {
         FreeStmt(SQL_DROP, lpChild);
         return FALSE;
      }

      // Determine target C type
      lpcol->fCType = CvtSqlToCType(lpcol->fSqlType);

      // For hard to handle C types, let the driver convert to character
      if (lpcol->fCType == SQL_C_BIT    ||
          lpcol->fCType == SQL_C_BINARY ||
          lpcol->fCType == SQL_C_DATE   ||
          lpcol->fCType == SQL_C_TIME   ||
          lpcol->fCType == SQL_C_TIMESTAMP ||
          lpcol->fCType == SQL_C_TYPE_DATE   ||
          lpcol->fCType == SQL_C_TYPE_TIME   ||
          lpcol->fCType == SQL_C_TYPE_TIMESTAMP) {
         lpcol->fCType = SQL_C_CHAR;
         lpcol->cb = lpcol->cbc;
      }

      // Determine next column tab (based on column width plus border)
      nLastTab =
         *lptab    = nLastTab + ((int)lpcol->cbc * g_cx) + (2 * cxBORDER);

      // Set maximum column length for character data
      if( lpcol->cb > lpChild->crowMaxBind &&
         lpcol->fCType == SQL_C_CHAR )
         lpcol->cb = lpChild->crowMaxBind;

      if( lpcol->fCType == SQL_C_CHAR )
         lpcol->cb++;

      // Increment total phsyical row width and display width
      lpChild->cbrow += lpcol->cb;
      lpChild->ccols += (int)lpcol->cbc;

      cbMsg = cbNull;
   }

   // Include a count field for each bound column in physical row width
   lpChild->cbrow += lpChild->ccol * sizeof(SDWORD);

   // Add intra-column border amounts to total character width
   lpChild->ccols += (lpChild->ccol * (2 * cxBORDER)) / g_cx;

   // For each column, include an element in the format string
   lpb  = (LPBYTE)lpChild->szFmt;
   for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
      *(lpb++) = '\t';
      *(lpb++) = '%';
      *(lpb++) = 's';
   }
   *lpb = 0;

   // If row-wise binding, allocate a buffer; switch to column-wise
   // if the entire row-set cannot fit into 64K minus cursor library
   // headers
   lpb = NULL;
   if (ROW_BINDING(lpChild)) {
      if ((lpChild->cbrow * lpChild->crowRowset) > 65500L) {
         DoMessage(lpChild->hwnd, IDS_BIGROWSET);
         lpChild->fBindByRow = IDC_RADIO_BINDCOL ;
      }
      else {
         lpb          =
            lpChild->lpb = (LPBYTE)AllocPtr(lpChild->cbrow *
                                            lpChild->crowRowset);
      }
   }

   // Set binding type
   if (STMTError(SQLSetStmtAttr(lpChild->hstmt,
                                SQL_ATTR_ROW_BIND_TYPE,
                                (SQLPOINTER) (ROW_BINDING(lpChild)
                                              ? lpChild->cbrow
                                              : SQL_BIND_BY_COLUMN),
                                SQL_IS_INTEGER)))
      return FALSE;

   // Finally, for each bound column, bind the data value
   for (i=1, lpcol=lpChild->lpcol; i <= lpChild->ccol; i++, lpcol++) {
      if (!ROW_BINDING(lpChild)) {
         lpcol->lpb  = (LPBYTE)AllocPtr(lpcol->cb * lpChild->crowRowset);
         lpcol->lpcb = (LPSDWORD)AllocPtr((DWORD) sizeof(SDWORD) *
                                          lpChild->crowRowset);
      }
      else {
         lpcol->lpb  = (LPBYTE)lpb;
         lpcol->lpcb = (LPSDWORD)(lpb + lpcol->cb);
         lpb += lpcol->cb + sizeof(SDWORD);
      }

      if (STMTError(SQLBindCol(lpChild->hstmt, i, (SWORD)lpcol->fCType,
                               (PTR)(lpcol->lpb),
                               lpcol->cb,
                               lpcol->lpcb)))
         return FALSE;
   }

   return TRUE;
}

/* PrepareStmt -------------------------------------------------------------
   Description: Prepare a statement for future processing

   Returns: SQL_ERROR if an error occurs
   --------------------------------------------------------------------------*/
SQLRETURN INTFUNC PrepareStmt(LPCHILD lpChild)
{

   // Close the statement and drop bindings
   FreeStmt(SQL_CLOSE,  lpChild);
   FreeStmt(SQL_UNBIND, lpChild);

   // Set scroll options
   if (!(SUCCESS(SQLSetStmtAttr(lpChild->hstmt,
                                SQL_ATTR_CURSOR_TYPE,
                                (SQLPOINTER) lpChild->crowKeyset,
                                SQL_IS_INTEGER))))
      lpChild->fNoCursorType = TRUE;

   if (!(SUCCESS(SQLSetStmtAttr(lpChild->hstmt,
                                SQL_ATTR_CONCURRENCY,
                                (SQLPOINTER) lpChild->fConcurrency,
                                SQL_IS_INTEGER))))
      lpChild->fNoConcurrency = TRUE;

   if (STMTError(SQLSetStmtAttr(lpChild->hstmt,
                                SQL_ATTR_ROW_ARRAY_SIZE,
                                (SQLPOINTER) lpChild->crowRowset,
                                SQL_IS_INTEGER)))
      return SQL_ERROR;

   // Set async mode (if supported by the driver)
   if (g_fAsyncSupported &&
       STMTError(SQLSetStmtAttr(lpChild->hstmt,
                                SQL_ATTR_ASYNC_ENABLE,
                                (SQLPOINTER) (lpChild->fAsync ? 1 : 0),
                                SQL_IS_INTEGER)))
      return SQL_ERROR;

   return FALSE;

}

/* Cancel -------------------------------------------------------------------
   Description: Issue cancel request
   --------------------------------------------------------------------------*/
void INTFUNC Cancel(LPCHILD lpChild)
{
   SQLRETURN rc;

   // Call SQLCancel
   if (lpChild->hstmtTmp != SQL_NULL_HSTMT) {
      rc = SQLCancel(lpChild->hstmtTmp);  // Cancel temp hstmt
      STMTError(rc);
   }
   else {
      rc = SQLCancel(lpChild->hstmt);
      STMTError(rc);
      FreeStmt(SQL_CLOSE,  lpChild);   // Cleanup statement
   }

   return;
}

/* Fetch -------------------------------------------------------------------
   Description: Issue fetch request
   --------------------------------------------------------------------------*/
void INTFUNC Fetch(LPCHILD lpChild)
{
   SQLRETURN rc;
   UDWORD    crow;
   HCURSOR   hcur;
   SDWORD    sdwIrowLast = lpChild->irow;
   SDWORD    sdwRowsAffected;
   SDWORD    irow;
   UWORD     fFetchType;

   static UWORD   fFetchTypeLast = SQL_FETCH_NEXT;

   fFetchType = lpChild->FetchOP;
   if (fFetchType == SQL_FETCH_ABSOLUTE)
      irow = lpChild->arow;
   else if (fFetchType == SQL_FETCH_RELATIVE)
      irow = lpChild->rrow;
   else
      irow = 0;
   if (!(lpChild->fResultSetExists)) {
      STMTError(SQLRowCount(lpChild->hstmt, &sdwRowsAffected));

      if (sdwRowsAffected > -1) {
         UCHAR szBuffer[200];
         wsprintf(szBuffer,
                  (sdwRowsAffected == 1) ? szRowAffected:szDataAffected,
                  sdwRowsAffected);

         MessageBox(lpChild->hwnd,
                    szBuffer,
                    szNoDataTitle,
                    MB_ICONINFORMATION);
      }
      else
         {
            MessageBox( lpChild->hwnd,
                       szNoDataTitle,
                       szNoData,
                       MB_ICONINFORMATION);
         }
      return;
   }

   hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

   SQLSetStmtAttr(lpChild->hstmt, SQL_ATTR_ROWS_FETCHED_PTR,
                  &crow, SQL_IS_POINTER);
   SQLSetStmtAttr(lpChild->hstmt, SQL_ATTR_ROW_STATUS_PTR,
                  lpChild->lpfStatus, SQL_IS_POINTER);

   // Call SQLFetchScroll
   Async(SQLFetchScroll(lpChild->hstmt, fFetchType, irow));

   if ((rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) &&
       lpChild->irowPos > crow) {
      SetPos(lpChild, (UWORD) crow);
   }

   if (!STMTError(rc) && rc != SQL_NO_DATA) {

      // Reset columns retrieved count
      lpChild->ccolRetrieved = (UWORD)lpChild->ccol;

      // If this is the first fetch, initialize child variables
      if (!lpChild->fDataFetched) {

         lpChild->fDataFetched = TRUE;

         lpChild->irow    = -((SDWORD)lpChild->crowRowset);
         lpChild->irowPos = 1;

         SetScrollPos(lpChild->hwndVScroll, SB_CTL, 0, FALSE);
         SetScrollPos(lpChild->hwndHScroll, SB_CTL, 0, FALSE);

         SetScroll(lpChild);
         AllocClipRgn(lpChild);
      }

      // Otherwise, maintain current row position in the row-set
      else if (lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
               lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY)
         SetPos(lpChild, lpChild->irowPos);

      // if (fFetchType == SQL_FETCH_RESUME)
      //    fFetchType = fFetchTypeLast;

      // Adjust absolute row number by fetch amount
      switch (fFetchType) {
        case SQL_FETCH_FIRST:
         lpChild->irow = 1;
         break;

        case SQL_FETCH_PRIOR:
         lpChild->irow -= crow;
         if (lpChild->irow < 1)
            lpChild->irow = 1;
         break;

        case SQL_FETCH_NEXT:
         lpChild->irow += lpChild->crowCurrent;  // Previous RS
         break;

        case SQL_FETCH_LAST: {
           SDWORD iRowT;   // scratch variable

           if (STMTError(SQLGetStmtAttr(lpChild->hstmt,
                                        SQL_ATTR_ROW_NUMBER,
                                        &iRowT,
                                        SQL_IS_INTEGER,
                                        NULL))) {
              // the cursor library does not support SQL_ROW_NUMBER,
              // so use the following calculation. The  result might
              // be incorrect
              STMTError(SQLRowCount(lpChild->hstmt, &iRowT));
              lpChild->irow += (iRowT - crow);
              if (lpChild->irow < 1) lpChild->irow = 1;

           }
           else {    // the cursor library supports SQL_ROW_NUMBER
              lpChild->irow = iRowT;
           }
        }
         break;

        case SQL_FETCH_RELATIVE:
         lpChild->irow += irow;
         if (lpChild->irow < 1)
            lpChild->irow = 1;
         break;

        case SQL_FETCH_ABSOLUTE:
         if (irow > 0)
            lpChild->irow = irow;
         else {
            STMTError(SQLRowCount(lpChild->hstmt, &lpChild->irow));
            lpChild->irow = lpChild->irow + irow + 1;
            if (lpChild->irow < 1) lpChild->irow = 1;
         }
         break;
      }

      // Repaint window
      InvalidateRect(lpChild->hwnd, NULL, FALSE);

      lpChild->crowCurrent = crow;
   }

   fFetchTypeLast = fFetchType;

   AdjustMenus();
   SetCursor(hcur);
   return;
}

/* FreeStmt ----------------------------------------------------------------
   Description: Free SQLHSTMT and reset associated variables of a child window
                NOTE: Only SQL_CLOSE, SQL_DROP, and SQL_UNBIND are valid
                --------------------------------------------------------------------------*/
void INTFUNC FreeStmt(UWORD fOption, LPCHILD lpChild)
{
   SWORD i;
   LPCOL lpcol;

   if (!lpChild->hstmt)
      return;

   // Issue the real SQLFreeStmt call
   if (fOption == SQL_DROP) {
      if (STMTError(SQLFreeHandle(SQL_HANDLE_STMT, lpChild->hstmt)))
         return;
   }
   else {
      if (STMTError(SQLFreeStmt(lpChild->hstmt, fOption)))
         return;
   }

   // Drop data buffers for SQL_DROP and SQL_UNBIND requests
   if (fOption == SQL_DROP || fOption == SQL_UNBIND) {
      if (!ROW_BINDING(lpChild)) {
         for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
            FreePtr(lpcol->lpb);  lpcol->lpb  = NULL;
            FreePtr(lpcol->lpcb); lpcol->lpcb = NULL;
         }
      }
      else if (lpChild->lpb) {
         FreePtr(lpChild->lpb);
         lpChild->lpb = NULL;
      }
   }

   // Only drop memory for SQL_UNBIND requests
   if (fOption == SQL_UNBIND)
      return;

   // Clear SQLHSTMT handle for SQL_DROP
   if (fOption == SQL_DROP)
      lpChild->hstmt = SQL_NULL_HSTMT;

   // Always reset and free result set related variables
   lpChild->fResultSetExists = FALSE;
   lpChild->ccol         = 0;
   lpChild->fDataFetched = FALSE;
   lpChild->cbrow        = 0;
   lpChild->ccols        = 0;

   FreePtr(lpChild->rglpv);     lpChild->rglpv     = NULL;
   FreePtr(lpChild->lpnTabs);   lpChild->lpnTabs   = NULL;
   FreePtr(lpChild->lpcol);     lpChild->lpcol     = NULL;
   FreePtr(lpChild->lpfStatus); lpChild->lpfStatus = NULL;

   if (lpChild->hrgn) {
      DeleteObject(lpChild->hrgn);
      lpChild->hrgn = NULL;
   }

   if (lpChild->hwnd) {
      lpChild->fVScroll = FALSE;
      lpChild->fHScroll = FALSE;

      ShowWindow(lpChild->hwndVScroll, SW_HIDE);
      ShowWindow(lpChild->hwndHScroll, SW_HIDE);
      InvalidateRect(lpChild->hwnd, NULL, FALSE);
   }
   return;
}

/* GetCurrentValue ---------------------------------------------------------
   Description: Convert to character and return column data from current
                row
--------------------------------------------------------------------------*/
#pragma optimize("ceglntw", off)
void INTFUNC GetCurrentValue(LPSTR lpsz, LPCOL lpcol, LPCHILD lpChild)
{
   LPBYTE      lpb;
   SDWORD FAR  *lpcb;
   UWORD       irowPos;

   irowPos = lpChild->irowPos - 1;

   // Get data and count field pointers based on binding type
   if (ROW_BINDING(lpChild)) {
      lpb  = lpcol->lpb + (irowPos * lpChild->cbrow);
      lpcb = (LPSDWORD)(lpb + lpcol->cb);
   }
   else {
      lpb  = lpcol->lpb + (irowPos * lpcol->cb);
      lpcb = lpcol->lpcb + irowPos;
   }

   // Convert column data to character using the supplied buffer
   if (*lpcb == SQL_NULL_DATA) {
      lstrcpy(lpsz, g_szNull);
      return;
   }

   switch (lpcol->fSqlType) {

     case SQL_CHAR:
     case SQL_VARCHAR:
      lstrcpy(lpsz, (LPSTR)lpb);
      break;

     case SQL_INTEGER:
     case SQL_SMALLINT:
     case SQL_TINYINT: {
        long  l;

        l = (lpcol->fSqlType == SQL_INTEGER
             ? *((DWORD FAR *)lpb)
             : lpcol->fSqlType == SQL_SMALLINT
             ? *((WORD FAR *)lpb)
             : *((UCHAR FAR *)lpb));

        _ltoa(l, lpsz, 10);
        break;
     }

     case SQL_REAL:
     case SQL_FLOAT:
     case SQL_DOUBLE: {
        double   d;

        d = (lpcol->fSqlType == SQL_REAL
             ? *((float FAR *)lpb)
             : *((double FAR *)lpb));

        _gcvt(d, 15, lpsz);
        break;
     }

     default:
      *lpsz = '\0';
      break;
   }

   return;
}
#pragma optimize("ceglntw", on)

/* GetData -----------------------------------------------------------------
   Description: Retrieve the next unbound column and display the data
   --------------------------------------------------------------------------*/
void INTFUNC GetData(LPCHILD lpChild)
{
   HCURSOR   hcur;
   BIGCOL    bcol;
   UWORD     icol;
   SQLRETURN rc;

   // Prevent Cancel from closing statement
   lpChild->hstmtTmp = lpChild->hstmt;

   // Determine next unbound column index
   icol = lpChild->ccolRetrieved + 1;

   hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

   // Get column name
   Async(SQLColAttribute(lpChild->hstmt, icol,
                         SQL_DESC_NAME,
                         bcol.szName, sizeof(bcol.szName), NULL,
                         NULL));

   SetCursor(hcur);

   if (STMTError(rc)) {
      lpChild->hstmtTmp = SQL_NULL_HSTMT;
      return;
   }

   bcol.lpsz = lpChild->lpsz;

   hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

   // Get the data converting it to character
   Async(SQLGetData(lpChild->hstmt, icol, SQL_C_CHAR,
                    bcol.lpsz, cbBUFSIZE-1, &bcol.cb));

   SetCursor(hcur);

   lpChild->hstmtTmp = SQL_NULL_HSTMT;

   if (STMTError(rc) || rc == SQL_NO_DATA)
      return;

   // Display the retrieved data
   DialogBoxParam(g_hinst,
                  MAKEINTRESOURCE(IDD_DATADLG),
                  lpChild->hwnd,
                  DataDlgProc,
                  (LPARAM)((LPSTR)&bcol));

   lpChild->ccolRetrieved = icol;
   return;
}

/* GetTableName ------------------------------------------------------------
   Description: Extract table name from a SELECT statement
   --------------------------------------------------------------------------*/
void INTFUNC GetTableName(LPSTR lpszTable, LPCSTR szSql)
{
   LPCSTR   lpsz;
   int      cp;
   int      cb;

   cb = lstrlen(szFROM);

   for (lpsz=szSql, cp=0; *lpsz; ) {

      while (*lpsz && ISWHITE(*lpsz)) lpsz++;

      if (!cp && !_fstrnicmp(lpsz, szFROM, cb) && ISWHITE(*(lpsz+cb)))
         break;

      if (ISLPAREN(*lpsz))
         cp++;
      else if (ISRPAREN(*lpsz))
         cp--;

      while (*lpsz && !ISWHITE(*lpsz)) lpsz++;
   }

   while (*lpsz && !ISWHITE(*lpsz)) lpsz++;
   while (*lpsz && ISWHITE(*lpsz))  lpsz++;

   if (*lpsz == *g_szQuoteChar) {
      *lpszTable++ = *lpsz++; // Copy beginning quote
      while (*lpsz && *lpsz != *g_szQuoteChar) *lpszTable++ = *lpsz++;
      *lpszTable++ = *lpsz++; // Copy ending quote
   }
   else  // Not a quoted identifier
      while (*lpsz && !ISCOMMA(*lpsz) && !ISWHITE(*lpsz)) *lpszTable++ = *lpsz++;

   *lpszTable = '\0';

   return;
}

/* IsUpdateable ------------------------------------------------------------
   Description: Return TRUE if this app supports updating the particular
                SQL data type (due to limited conversion support)
                --------------------------------------------------------------------------*/
BOOL INTFUNC IsUpdateable(SDWORD fSqlType)
{
   switch (fSqlType) {
     case SQL_CHAR:
     case SQL_VARCHAR:
     case SQL_SMALLINT:
     case SQL_INTEGER:
     case SQL_REAL:
     case SQL_FLOAT:
     case SQL_DOUBLE:
     case SQL_TINYINT:
      return TRUE;

     default:
      return FALSE;
   }
}

/* OnDataRow ---------------------------------------------------------------
   Description: Return 0 or greater if the mouse coordinates in lparam are
                over a valid data row, Otherwise return -1
                --------------------------------------------------------------------------*/
int INTFUNC OnDataRow(LPCHILD lpChild, LPARAM lparam)
{
   RECT  rc;
   int   row;

   if (!(lpChild->hrgn))
      return -1;

   GetRgnBox(lpChild->hrgn, &rc);

   row = (int)HIWORD(lparam) - g_cy;

   if (row < 0)
      return FALSE;

   row /= g_cy;

   if (row >= 0                        &&
       (UWORD)row < lpChild->crowRowset )
      return row;
   else
      return -1;
}

/* PaintChild --------------------------------------------------------------
   Description: Paint child window
--------------------------------------------------------------------------*/
void INTFUNC PaintChild(LPCHILD  lpChild,
                        HDC      hdc,
                        BOOL  fTitle,
                        BOOL  fRefresh,
                        BOOL  fActive)
{
   RECT  rc;

   GetClientRect(lpChild->hwnd, &rc);

   // If no data exists, just erase the window
   if (!lpChild->fDataFetched)
      FillRect(hdc, &rc, g_hbrWin);

   // Otherwise paint the data in a simple scrollable grid
   else {
      HRGN     hrgn;
      HFONT    hfontOld;
      HBRUSH   hbrOld;
      UWORD    ir;
      int      ic;
      int      icFirst, icLast;
      LPCOL    lpcol;
      UWORD    row;
      int      col;
      int      cx, cy;
      LPINT    lpnTab;
      LPSTR    lpszValues;
      LPSTR    lpsz;
      LPUWORD  lpfStatus;
      UWORD    irowLast;
      UWORD    irowPos;
      COLORREF clrfTxt;
      COLORREF clrfBkg;
      char     szFmt[cbSTRLEN];

      // Prepare the device context
      SetBkColor(hdc, GetSysColor(COLOR_WINDOW));

      clrfTxt = GetTextColor(hdc);
      clrfBkg = GetBkColor(hdc);

      // Determine first (row,col) and corresponding (x,y) offset
      row = (UWORD)GetScrollPos(lpChild->hwndVScroll, SB_CTL);
      col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

      cx = col * g_cx;
      cy = row * g_cy;

      // Determine last row to be painted
      irowLast = (UWORD)row + ((UWORD)lpChild->crowwin < lpChild->crowRowset
                               ? lpChild->crowwin
                               : lpChild->crowRowset);
      if (irowLast > lpChild->crowRowset)
         irowLast--;

      // Get current row number as a zero based index
      irowPos  = lpChild->irowPos - 1;

      // Determine which columns will be painted
      lpnTab = lpChild->lpnTabs;
      for (icFirst=0;
           icFirst < (lpChild->ccol-1) && (cx + cxBORDER) > *(lpnTab+1);
           icFirst++, lpnTab++);
      for (icLast=icFirst+1;
           icLast < lpChild->ccol &&
           (cx + cxBORDER + (lpChild->ccolwin * g_cx)) > *lpnTab;
           icLast++, lpnTab++);

      // Offset the device context to appropriate position in the rowset
      SetWindowOrgEx(hdc, cx, cy, NULL);

      // Set the clip region (to keep from erasing scroll bars, etc.)

      if (!(lpChild->hrgn))
         return;

      SelectClipRgn(hdc, lpChild->hrgn);

      // Allocate working buffer to converted data values
      lpszValues = AllocPtr((DWORD) lpChild->ccol * cbSTRLEN);

      // If requested, paint column titles
      if (fTitle) {

         // Prepare device context
         SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
         hfontOld = SelectObject(hdc, g_hfontName);

         // Determine bounding rectangle in logical coordinates
         rc.top    = cy;
         rc.bottom = rc.top + g_cy;
         rc.left   = *(lpChild->lpnTabs + icFirst) - cxBORDER;
         rc.right  = *(lpChild->lpnTabs + icLast)  - cxBORDER;

         // Fill rectangle with appropriate color
         FillRect(hdc, &rc, g_hbrBtn);

         // Paint white bar across the top
         PatBlt(hdc, 0, rc.top, rc.right, 1, WHITENESS);

         // For each visible (or partially visible) column, paint
         // separating lines and column name
         lpcol  = lpChild->lpcol   + icFirst;
         lpnTab = lpChild->lpnTabs + icFirst + 1;

         for (ic=icFirst; ic < icLast; ic++, lpcol++, lpnTab++) {

            PatBlt(hdc, rc.left, rc.top, 1, g_cy, WHITENESS);

            rc.right = *lpnTab - cxBORDER + 1;

            DrawText(hdc, lpcol->szName, lstrlen(lpcol->szName),
                     &rc, DT_CENTER | DT_SINGLELINE | DT_VCENTER);

            rc.left = rc.right;

            PatBlt(hdc, rc.left-1, rc.top, 1, g_cy, BLACKNESS);
         }

         // Paint black line across the bottom
         PatBlt(hdc, 1, rc.bottom-1, rc.right-1, 1, BLACKNESS);

         // Reset device context
         SelectObject(hdc, hfontOld);
         SetBkColor(hdc, clrfBkg);
      }

      // Determine bounding rectangle for first row to be painted
      rc.top    = cy + g_cy;
      rc.bottom = rc.top + g_cy - 1;
      rc.left   = *(lpChild->lpnTabs + icFirst) - cxBORDER;
      rc.right  = *(lpChild->lpnTabs + icLast)  - cxBORDER;

      // Make format string from the whole format string created earlier
#ifdef WIN32
      _fstrncpy(szFmt, lpChild->szFmt+(icFirst*3), ((icLast-icFirst)*3)+1);
#else
      lstrcpyn(szFmt, lpChild->szFmt+(icFirst*3), ((icLast-icFirst)*3)+1);
#endif
      szFmt[((icLast-icFirst)*3)+1] = '\0';

      // Offset into the row status array
      lpfStatus = lpChild->lpfStatus + row;

      // Prepare device context
      hfontOld = SelectObject(hdc, g_hfontData);
      hbrOld   = SelectObject(hdc, g_hbrBtn);

      // Paint each row
      for (ir=row; ir < irowLast; ir++) {

         // Erase row if requested (i.e., !fRefresh) or if just
         // painting the row is insufficient to remove old data
         if (!fRefresh                   ||
             (ir == irowPos && !fActive) ||
             *lpfStatus == SQL_ROW_NOROW ||
             *lpfStatus == SQL_ROW_ERROR ||
             *lpfStatus == SQL_ROW_DELETED)
            FillRect(hdc, &rc, g_hbrWin);

         // For an empty row, write the empty row string
         if (*lpfStatus == SQL_ROW_NOROW) {
            if (icFirst)
               *lpChild->lpsz = '\0';
            else
               lstrcpy(lpChild->lpsz, g_szNoRow);
         }

         // For a deleted row, write the deleted row string
         else if (*lpfStatus == SQL_ROW_DELETED) {
            if (icFirst)
               *lpChild->lpsz = '\0';
            else
               lstrcpy(lpChild->lpsz, g_szRowDeleted);
         }

         // For an error row, write the error row string
         else if (*lpfStatus == SQL_ROW_ERROR) {
            if (icFirst)
               *lpChild->lpsz = '\0';
            else
               lstrcpy(lpChild->lpsz, g_szRowError);
         }

         // For all other rows, build a string of data values
         else {
            LPSTR FAR   *lplpsz;
            LPBYTE      lpb;
            LPSDWORD lpcb;

            // Paint updated rows in RED
            if (*lpfStatus == SQL_ROW_UPDATED)
               SetTextColor(hdc, RGB(255,0,0));

            lplpsz = (LPSTR FAR *)lpChild->rglpv;

            lpsz   = lpszValues;

            lpcol  = lpChild->lpcol   + icFirst;
            lpnTab = lpChild->lpnTabs + icFirst;

            // Convert each column to character data
            for (ic=icFirst; ic < icLast; ic++, lpcol++) {

               lpb  = lpcol->lpb  + (ir * (ROW_BINDING(lpChild)
                                           ? lpChild->cbrow
                                           : lpcol->cb));
               lpcb = (ROW_BINDING(lpChild)
                       ? (LPSDWORD)(lpb + lpcol->cb)
                       : lpcol->lpcb + ir);

               if (*lpcb == SQL_NULL_DATA)
                  *lplpsz++ = g_szNull;

               else if (lpcol->fCType == SQL_C_CHAR)
                  *lplpsz++ = (LPSTR)lpb;

               else {

                  if (lpcol->fCType == SQL_C_FLOAT ||
                      lpcol->fCType == SQL_C_DOUBLE ) {
                     double   d;

                     d = (lpcol->fCType == SQL_C_FLOAT
                          ? *((float FAR *)lpb)
                          : *((double FAR *)lpb));

                     _gcvt(d, 15, lpsz);
                  }

                  else {
                     long  l;

                     l = ( lpcol->fCType == SQL_C_SHORT
                          ? *((short FAR *)lpb)
                          : lpcol->fCType == SQL_C_LONG
                          ? *((long FAR *)lpb)
                          : *((signed char FAR *)lpb));

                     _ltoa(l, lpsz, 10);
                  }

                  *lplpsz++ = lpsz;
                  lpsz += cbSTRLEN;
               }
            }

            // Combine all columns into one string (with tab markers)
Print(lpChild->lpsz, szFmt, lpChild->rglpv );

         }

         // Paint the row
         TabbedTextOut(hdc,
                       rc.left, rc.top,
                       lpChild->lpsz, lstrlen(lpChild->lpsz),
                       icLast - icFirst, lpnTab, 0);

         // Paint bottom separator
         PatBlt(hdc, 0, rc.bottom, rc.right, 1, PATCOPY);

         // Paint inter-column separators
         lpnTab = lpChild->lpnTabs + icFirst + 1;
         for (ic=icFirst+1; ic < (icLast+1); ic++, lpnTab++)
            PatBlt(hdc, *lpnTab-cxBORDER, rc.top, 1, g_cy, PATCOPY);

         // Reset text color (if it was changed)
         if (*lpfStatus == SQL_ROW_UPDATED)
            SetTextColor(hdc, clrfTxt);

         // Hilite the current row
         if (ir == irowPos                                 &&
             lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
             fActive                                        ) {
            RECT  rcTemp, rcInvert;

            GetClipBox(hdc, &rcTemp);

            IntersectRect(&rcInvert, &rc, &rcTemp);

            GetClientRect(lpChild->hwnd, &rcTemp);

            if( rcInvert.top == rc.top )
               rcInvert.top++;

            if( rcInvert.bottom == rc.bottom )
               rcInvert.bottom--;

            if( rcInvert.left == rcTemp.left )
               rcInvert.left++;

            if( rcInvert.right == rcTemp.right ||
               rcInvert.right == rc.right )
               rcInvert.right--;

            InvertRect(hdc, &rcInvert);
         }

         // Advance row rectangle and status array offset
         rc.top    += g_cy;
         rc.bottom += g_cy;
         lpfStatus++;
      }

      // Erase any partial row which may be displayed after the last
      // row of the row-set has been painted
      if (ir == lpChild->crowRowset)
         FillRect(hdc, &rc, g_hbrWin);

      // Erase any partial column which may be displayed after the last
      // column has been painted
      if (icLast == lpChild->ccol) {
         GetClipBox(hdc, &rc);
         rc.left = *(lpChild->lpnTabs + icLast) - cxBORDER + 1;
         FillRect(hdc, &rc, g_hbrWin);
      }

      FreePtr(lpszValues);

      // Reset clip region to paint areas outside data grid
      GetClientRect(lpChild->hwnd, &rc);

      hrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);

      SelectClipRgn(hdc, hrgn);

      OffsetRect(&rc, cx, cy);

      // Paint bottom record display bar and/or the corner box
      // between the scroll bars
      rc.top = rc.bottom - g_cyHScroll + 1;

      PatBlt(hdc, rc.left, rc.top, rc.right, 1, BLACKNESS);

      SelectObject(hdc, g_hbrScroll);

      rc.top++;

      cx = (2 * cxBORDER) + g_cxRecord + g_cxRecnum + 2;

      if (lpChild->fHScroll) {
         if (lpChild->fVScroll)
            PatBlt(hdc,
                   rc.right - g_cxVScroll + 2, rc.top,
                   g_cxVScroll, g_cyHScroll,
                   PATCOPY);
      }
      else
         PatBlt(hdc,
                rc.left + cx, rc.top,
                rc.right - rc.left, g_cyHScroll,
                PATCOPY);

      rc.right = rc.left + cx;

      PatBlt(hdc, rc.right, rc.top, 1, rc.bottom, BLACKNESS);

      rc.right--;

      // Paint current row number
      if (fRefresh)
         FillRect(hdc, &rc, g_hbrWin);

      rc.left += cxBORDER;

      TextOut(hdc, rc.left, rc.top, szRECORD, lstrlen(szRECORD));

      rc.left += g_cxRecord + 2;

      FillRect(hdc, &rc, g_hbrWin);

      wsprintf(lpChild->lpsz, szRECNUM, lpChild->irow-1 + lpChild->irowPos);

      TextOut(hdc, rc.left, rc.top, lpChild->lpsz, lstrlen(lpChild->lpsz));

      DeleteObject(hrgn);

      // Reset device context
      SelectObject(hdc, hbrOld);

      SelectObject(hdc, hfontOld);
   }

   return;
}

/* SetCurrentValue ---------------------------------------------------------
   Description: Set a column value from the user buffer
--------------------------------------------------------------------------*/
#pragma optimize("ceglntw", off)
BOOL INTFUNC SetCurrentValue(LPSTR lpsz, LPCOL lpcol, LPCHILD lpChild)
{
   LPBYTE   lpb;
   LPSDWORD lpcb;
   BOOL     fNew;
   UWORD    irowPos;

   irowPos = lpChild->irowPos - 1;

   // Get data and count field pointers based on binding type
   if (ROW_BINDING(lpChild)) {
      lpb  = lpcol->lpb + (irowPos * lpChild->cbrow);
      lpcb = (LPSDWORD)(lpb + lpcol->cb);
   }
   else {
      lpb  = lpcol->lpb + (irowPos * lpcol->cb);
      lpcb = lpcol->lpcb + irowPos;
   }

   // If the data is NULL, just set the count field to SQL_NULL_DATA
   if (!lstrcmpi(lpsz, g_szNull)) {
      if (*lpcb != SQL_NULL_DATA) {
         *lpcb = SQL_NULL_DATA;
         fNew  = TRUE;
      }
   }

   // Otherwise, convert the character data back to the appropriate type
   else switch (lpcol->fSqlType) {

     case SQL_CHAR:
     case SQL_VARCHAR:
      if (lstrcmp(lpsz, (LPSTR)lpb)) {
         lstrcpy((LPSTR)lpb, lpsz);
         *lpcb = lstrlen(lpsz);
         fNew  = TRUE;
      }
      break;

     case SQL_INTEGER:
     case SQL_SMALLINT:
     case SQL_TINYINT: {
        long  lNew, lCur;
        char  *EndPtr;

        lNew = strtol(lpsz, &EndPtr, 10);
        for (; *EndPtr && ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
        if (*EndPtr)  { // check to see if there exists non-numeric chars
           UCHAR szBuffer[128];

           LoadString(g_hinst, IDS_BADNUMERIC, szBuffer, sizeof(szBuffer));
           MessageBox(lpChild->hwnd,
                      szBuffer, NULL, MB_ICONSTOP);
           fNew = FALSE;
           break;
        }

        lCur = (lpcol->fSqlType == SQL_INTEGER
                ? *((DWORD FAR *)lpb)
                : lpcol->fSqlType == SQL_SMALLINT
                ? *((WORD FAR *)lpb)
                : *((UCHAR FAR *)lpb));

        if (lNew != lCur) {

           switch (lpcol->fSqlType) {
             case SQL_INTEGER:
              *((DWORD FAR *)lpb) = lNew;
              *lpcb = sizeof(DWORD);
              break;

             case SQL_SMALLINT:
              *((WORD FAR *)lpb) = (WORD)lNew;
              *lpcb = sizeof(WORD);
              break;

             case SQL_TINYINT:
              *((UCHAR FAR *)lpb) = (UCHAR)lNew;
              *lpcb = sizeof(UCHAR);
              break;
           }

           fNew = TRUE;
        }
        break;
     }

     case SQL_REAL:
     case SQL_FLOAT:
     case SQL_DOUBLE: {
        double   dNew, dCur;
        char  *EndPtr;

        dNew = strtod(lpsz, &EndPtr);
        for (; *EndPtr && ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
        if (*EndPtr)  { // check to see if there exists non-numeric chars
           UCHAR szBuffer[128];

           LoadString(g_hinst, IDS_BADNUMERIC, szBuffer, sizeof(szBuffer));
           MessageBox(lpChild->hwnd,
                      szBuffer, NULL, MB_ICONSTOP);
           fNew = FALSE;
           break;
        }

        dCur = (lpcol->fSqlType == SQL_REAL
                ? *((float FAR *)lpb)
                : *((double FAR *)lpb));

        if (dNew != dCur) {

           switch (lpcol->fSqlType) {
             case SQL_REAL:
              *((float FAR *)lpb) = (float)dNew;
              *lpcb = sizeof(float);
              break;

             case SQL_FLOAT:
             case SQL_DOUBLE:
              *((double FAR *)lpb) = dNew;
              *lpcb = sizeof(double);
              break;
           }

           fNew = TRUE;
        }
        break;
     }
   }

   return fNew;
}
#pragma optimize("ceglntw", on)

/* SetPos ------------------------------------------------------------------
   Description: Set current row, de-hilite last current row and hilite
                new current row
                --------------------------------------------------------------------------*/
void INTFUNC SetPos(LPCHILD lpChild, UWORD irowPos)
{
   HDC      hdc;
   HFONT    hfont;
   RECT     rc, rcClip, rcInvert;
   int      row, col;
   int      cx, cy;

   if (!(lpChild->hrgn))
      return ;

   if (irowPos < 1)
      irowPos = 1;

   else if (irowPos > lpChild->crowRowset)
      irowPos = lpChild->crowRowset;

   // Call SQLSetPos to set current row in the row-set
   if (STMTError(SQLSetPos(lpChild->hstmt, irowPos, SQL_POSITION,
                           SQL_LOCK_NO_CHANGE)))
      return;

   // Reset number of columns retrieved
   lpChild->ccolRetrieved = (UWORD)lpChild->ccol;

   // Obtain a device context for painting
   hdc = GetDC(lpChild->hwnd);

   row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);
   col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

   cx  = col * g_cx;
   cy  = row * g_cy;

   SetWindowOrgEx(hdc, cx, cy, NULL);

   GetClientRect(lpChild->hwnd, &rc);
   GetRgnBox(lpChild->hrgn, &rcClip);
   OffsetRect(&rcClip, cx, cy);

   rcClip.top += g_cy;

   // Offset to last current row
   rc.top    = ((lpChild->irowPos - 1) * g_cy) + g_cy;
   rc.bottom = rc.top + g_cy - 1;
   rc.left   = 0;
   rc.right  = *(lpChild->lpnTabs + lpChild->ccol) - cxBORDER;

   // De-hilite last current row
   IntersectRect(&rcInvert, &rc, &rcClip);
   InflateRect(&rcInvert, -1, -1);
   InvertRect(hdc, &rcInvert);

   // Save new current row
   lpChild->irowPos = irowPos;

   // Offset to new current row
   rc.top    = ((lpChild->irowPos - 1) * g_cy) + g_cy;
   rc.bottom = rc.top + g_cy - 1;

   // Hilite current row (if visible)
   if (lpChild->irowPos > (UWORD)row) {

      IntersectRect(&rcInvert, &rc, &rcClip);
      InflateRect(&rcInvert, -1, -1);
      InvertRect(hdc, &rcInvert);
   }

   // Update record number
   GetClientRect(lpChild->hwnd, &rc);
   OffsetRect(&rc, cx, cy);

   rc.left += cxBORDER + g_cxRecord + 2;
   rc.top   = rc.bottom - g_cyHScroll + 2;
   rc.right = rc.left + g_cxRecnum;

   FillRect(hdc, &rc, g_hbrWin);

   wsprintf(lpChild->lpsz, szRECNUM, lpChild->irow-1 + lpChild->irowPos);

   hfont = SelectObject(hdc, g_hfontData);

   TextOut(hdc, rc.left, rc.top, lpChild->lpsz, lstrlen(lpChild->lpsz));

   SelectObject(hdc, hfont);

   // Release device context
   ReleaseDC(lpChild->hwnd, hdc);

   return;
}

/* SetScroll ---------------------------------------------------------------
   Description: Determine if scroll bars are required and their ranges
   --------------------------------------------------------------------------*/
void INTFUNC SetScroll(LPCHILD lpChild)
{
   RECT  rc;
   int   cx, cy;
   int   row, col;

   // Use the fInSetScroll flag to prevent recursion due WM_SIZE messages
   if (lpChild->fInSetScroll)
      return;

   lpChild->fInSetScroll = TRUE;

   // Save current scroll positions
   row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);
   col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

   // Get window dimensions
   GetClientRect(lpChild->hwnd, &rc);
   cx = rc.right - rc.left;
   cy = rc.bottom - rc.top - g_cy - g_cyHScroll;

   // Assume no scrolling is required
   lpChild->fHScroll =
      lpChild->fVScroll = FALSE;

   // Include a vertical scroll bar if all rows do not fit
   lpChild->crowwin = cy / g_cy;
   if ((UWORD)lpChild->crowwin < lpChild->crowRowset) {
      lpChild->fVScroll = TRUE;
      cx -= g_cxVScroll;
   }

   // Include a horizontal scroll bar if all columns do not fit
   lpChild->ccolwin = cx / g_cx;
   if (lpChild->ccolwin < lpChild->ccols)
      lpChild->fHScroll = TRUE;

   // Reset scroll positions if no scrolling is necessary
   if (!lpChild->fVScroll) row = 0;
   if (!lpChild->fHScroll) col = 0;

   // Set scroll ranges, positions, and scroll bar visibility
   SetScrollRange(lpChild->hwndVScroll, SB_CTL, 0, lpChild->crowRowset - lpChild->crowwin, TRUE);
   SetScrollRange(lpChild->hwndHScroll, SB_CTL, 0, lpChild->ccols - lpChild->ccolwin,      TRUE);

   SetScrollPos(lpChild->hwndVScroll, SB_CTL, row, TRUE);
   SetScrollPos(lpChild->hwndHScroll, SB_CTL, col, TRUE);

   ShowWindow(lpChild->hwndVScroll, (lpChild->fVScroll ? SW_SHOW : SW_HIDE));
   ShowWindow(lpChild->hwndHScroll, (lpChild->fHScroll ? SW_SHOW : SW_HIDE));

   // Add one extra to window depth so no white space is left between
   // the last full row and bottom record display bar
   lpChild->crowwin++;

   // Size and position scroll bars
   SizeScroll(lpChild);

   lpChild->fInSetScroll = FALSE;

   return;
}

/* SizeScroll --------------------------------------------------------------
   Description: Size and position scroll bars
   --------------------------------------------------------------------------*/
void INTFUNC SizeScroll(LPCHILD lpChild)
{
   RECT  rc;
   int   cxRecord;

   GetClientRect(lpChild->hwnd, &rc);

   // Place vertical scroll bar
   MoveWindow(lpChild->hwndVScroll,
              rc.right - g_cxVScroll + 1,
              rc.top,
              g_cxVScroll,
              rc.bottom - g_cyHScroll + 2,
              TRUE);

   // Place horizontal scroll bar
   cxRecord = (2 * cxBORDER) + g_cxRecord + g_cxRecnum + 2;

   MoveWindow(lpChild->hwndHScroll,
              rc.left + cxRecord,
              rc.bottom - g_cyHScroll + 1,
              (lpChild->fVScroll
               ? rc.right - g_cxVScroll + 2 - cxRecord
               : rc.right + 2 - cxRecord),
              g_cyHScroll,
              TRUE);

   return;
}

/* UpdateRow ---------------------------------------------------------------
   Description: Update current (positioned) row
   --------------------------------------------------------------------------*/
void INTFUNC UpdateRow(LPCHILD lpChild)
{
   HCURSOR  hcur;
   LPSTR    lpsz;
   LPSTR    lpszT;
   SWORD    cb;
   LPCOL    lpcol;
   int      cCtls;
   UWORD    irowPos;
   int      i;

   // Ensure the update request is valid
   if (!lpChild->fDataFetched) {
      DoMessage(lpChild->hwnd, IDS_NODATAFETCHED);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_NOROW) {
      DoMessage(lpChild->hwnd, IDS_NOROWUPDATE);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_ERROR) {
      DoMessage(lpChild->hwnd, IDS_ERRORROWUPDATE);
      return;
   }

   if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_DELETED) {
      DoMessage(lpChild->hwnd, IDS_DELROWUPDATE);
      return;
   }

   if (lpChild->fConcurrency == SQL_CONCUR_READ_ONLY) {
      DoMessage(lpChild->hwnd, IDS_NOUPDATE);
      return;
   }

   // Count number of updateable columns
   for (cCtls=0, i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++)
      if (IsUpdateable(lpcol->fSqlType))
         cCtls++;

   // Ensure a valid number of updateable columns exist
   if (cCtls > cMAXCOLS) {
      DoMessage(lpChild->hwnd, IDS_TOOMANYCOLS);
      return;
   }

   if (!cCtls) {
      DoMessage(lpChild->hwnd, IDS_CANNOTUPDATE);
      return;
   }

   hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

   lpsz  = AllocPtr(2 * cbMAXSQL);
   lpszT = lpsz + cbMAXSQL;

   // Build and display update dialog to get new values, then issue update
   // (use a temporary SQLHSTMT for the update request)
   if (IDOK == DoDialog(lpChild->hwnd, IDD_UPDATEROW, UpdateDlgProc) &&
       !DBCError(lpChild->hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &lpChild->hstmtTmp))) {

      // Build UPDATE <table> WHERE CURRENT OF <cursor name> statement
      lstrcpy(lpsz, szUPDATE);

      GetTableName(lpszT, lpChild->sql);
      lstrcat(lpsz, lpszT);

      // Build the Set Clause
      lstrcat(lpsz, szSET);
      for (lpcol = lpChild->lpcol, i = 0; i < lpChild->ccol; i++, lpcol++) {
         if (IsUpdateable(lpcol->fSqlType)) {
            LPSDWORD lpcb;
            LPBYTE   lpb;

            if (i == 0) wsprintf(lpszT, "%s=?", lpcol->szName);
            else wsprintf(lpszT, ",%s=?", lpcol->szName);
            lstrcat(lpsz, lpszT);

            irowPos = lpChild->irowPos - 1;

            // Get data and count field pointers based on binding type
            if (ROW_BINDING(lpChild)) {
               lpb  = lpcol->lpb + (irowPos * lpChild->cbrow);
               lpcb = (LPSDWORD)(lpb + lpcol->cb);
            }
            else {
               lpb  = lpcol->lpb + (irowPos * lpcol->cb);
               lpcb = lpcol->lpcb + irowPos;
            }

            // set parameter
            ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
                      SQLSetParam(lpChild->hstmtTmp, (UWORD) (i+1), lpcol->fCType,
                                  lpcol->fSqlType, (UDWORD) lpcol->cb, (SWORD) 0,
                                  lpb, lpcb));
         }
      }

      lstrcat(lpsz, szWHERE);

      lpszT = lpsz + lstrlen(lpsz);

      if (!STMTError(SQLGetCursorName(lpChild->hstmt, (UCHAR FAR *)lpszT,
                                      cbMAXSQL, &cb))) {

         // Issue update request via SQLExecDirect
         ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
                   SQLExecDirect(lpChild->hstmtTmp, (UCHAR FAR *)lpsz, SQL_NTS));
      }

      DBCError(lpChild->hwnd, SQLFreeHandle(SQL_HANDLE_STMT,lpChild->hstmtTmp));
      lpChild->hstmtTmp = SQL_NULL_HSTMT;

   }

   // Refresh entire row-set buffer (saving current row position)
   irowPos = lpChild->irowPos;

   lpChild->FetchOP = SQL_FETCH_RELATIVE;
   lpChild->rrow = 0;
   Fetch(lpChild);

   SetPos(lpChild, irowPos);

   // Repaint the row-set
   InvalidateRect(lpChild->hwnd, NULL, FALSE);

   FreePtr(lpsz);

   SetCursor(hcur);

   return;
}

/*--------------------------------------------------------------------
Input:  lpChild
Output: TRUE if the option parameters are valid, FALSE otherwise
--------------------------------------------------------------------*/
BOOL INTFUNC ParamValid(LPCHILD lpChild)
{
   char szBuffer[128]; // error message
   char *EndMaxBind, *EndRowset, *EndBind;

   // the maximum column width
   wsprintf((LPSTR) szBuffer,
            "Maximum column width must be at least 1 and at most %d",
            MAX_MAXBIND);

   if (lpChild->fMaxBind) { // it's been changed
      lpChild->crowMaxBind =
         (SDWORD) strtol((char*) lpChild->szMaxBind, &EndMaxBind, 10);
      for (; *EndMaxBind && ISWHITE(*EndMaxBind);
           EndMaxBind = AnsiNext(EndMaxBind));
   }

   if (lpChild->fBind) {  // cBind has been changed
      lpChild->cBind =  (UWORD) strtol((char*) lpChild->szBind, &EndBind, 10);
      for (; *EndBind && ISWHITE(*EndBind); EndBind = AnsiNext(EndBind));
   }

   if (lpChild->fRowset) { // rowset has been changed
      lpChild->crowRowset =
         (UWORD) strtol((char*) lpChild->szRowset, &EndRowset, 10);
      for (; *EndRowset && ISWHITE(*EndRowset);
           EndRowset = AnsiNext(EndRowset));
   }

   while (lpChild->fMaxBind && *EndMaxBind  ||
          lpChild->crowMaxBind < 1 ||
          lpChild->crowMaxBind > MAX_MAXBIND ||
          lpChild->fRowset && *EndRowset ||
          lpChild->crowRowset < 1 ||
          lpChild->crowRowset > 4096 ||
          lpChild->fBind && *EndBind) {

      // the maximum column width
      if (lpChild->crowMaxBind < 1 || lpChild->crowMaxBind > MAX_MAXBIND
          || lpChild->fMaxBind && *EndMaxBind)
         MessageBox(lpChild->hwnd,  szBuffer, NULL, MB_ICONSTOP);

      // the rowset size
      if (lpChild->crowRowset < 1 || lpChild->crowRowset > 4096
          || lpChild->fRowset && *EndRowset) {
         char szBuffer[128];

         LoadString(g_hinst, IDS_BADROWSET, szBuffer, sizeof(szBuffer));
         MessageBox(lpChild->hwnd, szBuffer, NULL, MB_ICONSTOP);
      }

      // number of bound columns
      if (lpChild->fBind && *EndBind)
         MessageBox(lpChild->hwnd,
                    "Invalid number of bound columns", NULL, MB_ICONSTOP);

      if (IDOK != DoDialog(lpChild->hwnd, IDD_OPTION_DIALOG, OptionsDlgProc))
         return FALSE;

      if (lpChild->fMaxBind) { // it's been changed
         lpChild->crowMaxBind =
            (SDWORD) strtol((char*) lpChild->szMaxBind, &EndMaxBind, 10);
         for (; *EndMaxBind && ISWHITE(*EndMaxBind);
              EndMaxBind = AnsiNext(EndMaxBind));
      }

      if (lpChild->fBind) {  // cBind has been changed
         lpChild->cBind =
            (UWORD) strtol((char*) lpChild->szBind, &EndBind, 10);

         for (; *EndBind && ISWHITE(*EndBind); EndBind = AnsiNext(EndBind));
      }

      if (lpChild->fRowset) { // rowset has been changed
         lpChild->crowRowset =
            (UWORD) strtol((char*) lpChild->szRowset, &EndRowset, 10);
         for (; *EndRowset && ISWHITE(*EndRowset);
              EndRowset = AnsiNext(EndRowset));
      }
   }

   return TRUE;

}


CRSRDEMO.C

/*--------------------------------------------------------------------------
  Crsrdemo.C --- Cursors main file

  Description:
    This sample is spread across four files, each named for the role
    the contained functions play.  Each file header contains a brief
    description of its purpose and the routines it contains.

    CRSRDEMO.C contains the standard functions used in a Windows program
    (such as, WinMain) plus two functions shared between all the files.
    These functions are:

      DoMessage       - Issue a message
      EndInstance     - Clean up an instance of this program
      InitApplication - Prepare the first instance of this program
      InitInstance    - Prepare an instance of this program
      ODBCError       - Retrieve and display an ODBC error
      WinMain         - Main Windows entry point

    This code is furnished on an as-is basis as part of the ODBC SDK and is
    intended for example purposes only.

--------------------------------------------------------------------------*/

/* Includes --------------------------------------------------------------*/
#include "headers.h"

#pragma warning(disable:4001)
#define  INCL_GLOBAL
#include    "resource.h"
#include "crsrdemo.h"

// Prototypes --------------------------------------------------------------
void INTFUNC EndInstance(void);
BOOL INTFUNC InitApplication(void);
BOOL INTFUNC InitInstance(int);
int  INTFUNC WinMain(HINSTANCE, HINSTANCE, LPSTR, int);

/* DoMessage ---------------------------------------------------------------
   Description: Issue a message
   --------------------------------------------------------------------------*/
void INTFUNC DoMessage(HWND hwnd, UINT id)
{
   char  sz[cbSTRLEN];

   LoadString(g_hinst, id, sz, sizeof(sz));
   MessageBox(hwnd, sz, g_szTITLE, MB_ICONINFORMATION | MB_OK);
   return;
}

/* EndInstance -------------------------------------------------------------
   Description: Free instance related data
   --------------------------------------------------------------------------*/
void INTFUNC EndInstance(void)
{
   if (g_hfontName)  DeleteObject(g_hfontName);
   if (g_hfontData)  DeleteObject(g_hfontData);

   if (g_hbrWin)     DeleteObject(g_hbrWin);
   if (g_hbrBtn)     DeleteObject(g_hbrBtn);
   if (g_hbrScroll)  DeleteObject(g_hbrScroll);

   return;
}

/* InitApplication ---------------------------------------------------------
   Description: Prepare application by registering window classes
   --------------------------------------------------------------------------*/
BOOL INTFUNC InitApplication(void)
{
   WNDCLASS  wc;

   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = FrameProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = g_hinst;
   wc.hIcon         = LoadIcon(g_hinst, MAKEINTRESOURCE(IDR_MAIN));
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = szFRAMECLASS;

   if (!RegisterClass(&wc))
      return FALSE;

   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = ChildProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = sizeof(LPCHILD);
   wc.hInstance     = g_hinst;
   wc.hIcon         = LoadIcon(g_hinst, MAKEINTRESOURCE(IDR_CHILD));
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = szCHILDCLASS;

   if (!RegisterClass(&wc))
      return FALSE;

   return TRUE;
}

/* InitInstance ------------------------------------------------------------
   Description: Prepare instance by initializing global variables and
                creating main frame window
                --------------------------------------------------------------------------*/
BOOL INTFUNC InitInstance(int nCmdShow)
{
   RECT     rc;
   HDC         hdc;
   HFONT    hfont;
   TEXTMETRIC  tm;
   char     sz[cbSTRLEN];
   SIZE        size;

   // Initialize global variables
   g_hwnd       =
      g_hwndClient = NULL;

   g_haccel     = NULL;

   g_hmenuInit        =
      g_hmenuInitWindow  =
         g_hmenuFrame       =
            g_hmenuFrameWindow =
               g_hmenuChild       =
                  g_hmenuChildWindow = NULL;

   g_hfontName =
      g_hfontData = NULL;

   g_hbrWin    =
      g_hbrBtn    =
         g_hbrScroll = NULL;

   g_henv = SQL_NULL_HENV;
   g_hdbc = SQL_NULL_HDBC;

   g_haccel = LoadAccelerators(g_hinst, MAKEINTRESOURCE(IDR_MAIN));

   g_hmenuInit  = LoadMenu(g_hinst, MAKEINTRESOURCE(IDR_INIT));
   g_hmenuFrame = LoadMenu(g_hinst, MAKEINTRESOURCE(IDR_MAIN));
   g_hmenuChild = LoadMenu(g_hinst, MAKEINTRESOURCE(IDR_CHILD));

   g_hmenuInitWindow  = GetSubMenu(g_hmenuInit,  IDM_WINDOWINIT);
   g_hmenuFrameWindow = GetSubMenu(g_hmenuFrame, IDM_WINDOWFRAME);
   g_hmenuChildWindow = GetSubMenu(g_hmenuChild, IDM_WINDOWCHILD);

   g_cxVScroll = GetSystemMetrics(SM_CXVSCROLL);
   g_cyHScroll = GetSystemMetrics(SM_CYHSCROLL);

   LoadString(g_hinst, IDS_TABLE,      g_szTable,      sizeof(g_szTable));
   LoadString(g_hinst, IDR_MAIN,       g_szTITLE,      sizeof(g_szTITLE));
   LoadString(g_hinst, IDS_NOROW,      g_szNoRow,      sizeof(g_szNoRow));
   LoadString(g_hinst, IDS_ROWERROR,   g_szRowError,  sizeof(g_szRowError));
   LoadString(g_hinst, IDS_NULL,       g_szNull,       sizeof(g_szNull));
   LoadString(g_hinst, IDS_ROWDELETED, g_szRowDeleted, sizeof(g_szRowDeleted));
   LoadString(g_hinst, IDS_UNKNOWN,    g_szUnknown,    sizeof(g_szUnknown));

   // Create main window in upper 3/4 of desktop
   GetWindowRect(GetDesktopWindow(), &rc);

   g_hwnd = CreateWindow(szFRAMECLASS,
                         g_szTITLE,
                         WS_OVERLAPPEDWINDOW,
                         rc.left,
                         rc.top,
                         rc.right - rc.left,
                         ((rc.bottom - rc.top) / 4) * 3,
                         HWND_DESKTOP,
                         g_hmenuInit,
                         g_hinst,
                         NULL);
   if (!g_hwnd)
      return FALSE;

   // Create fonts used in painting child windows
   hdc = GetDC(g_hwnd);

   g_hfontName = CreateFont((GetDeviceCaps(hdc, LOGPIXELSY) * cPOINTS) / 72,
                            0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, 0, 0,
                            szFONT);

   hfont = SelectObject(hdc, g_hfontName);

   GetTextMetrics(hdc, &tm);
   g_cx = tm.tmMaxCharWidth;
   g_cy = tm.tmHeight + tm.tmInternalLeading;

   g_hfontData = CreateFont((GetDeviceCaps(hdc, LOGPIXELSY) * cPOINTS) / 72,
                            0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0,
                            szFONT);

   SelectObject(hdc, g_hfontData);

   // Determine font size characteristics
   GetTextMetrics(hdc, &tm);
   g_cx = max(g_cx, tm.tmMaxCharWidth);
   g_cy = max(g_cy, tm.tmHeight + tm.tmInternalLeading);

   GetTextExtentPoint(hdc, szRECORD, lstrlen(szRECORD), &size);
   g_cxRecord = size.cx;

   wsprintf(sz, szRECNUM, 999999);

   GetTextExtentPoint(hdc, sz, lstrlen(sz), &size);
   g_cxRecnum = size.cx;

   SelectObject(hdc, hfont);

   ReleaseDC(g_hwnd, hdc);

   // Allocate brushes
   g_hbrWin    = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
   g_hbrBtn    = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
   g_hbrScroll = CreateSolidBrush(GetSysColor(COLOR_SCROLLBAR));

   // Allocate ODBC environment and connection handles; register as a 3.0 app
   if (ENVError(g_hwnd, SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HENV,&g_henv)))
      return FALSE;
   if (ENVError(g_hwnd, SQLSetEnvAttr(g_henv, SQL_ATTR_ODBC_VERSION,
                                      (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER)))
      return FALSE;
   if (ENVError(g_hwnd, SQLAllocHandle(SQL_HANDLE_DBC,g_henv, &g_hdbc)))
      return FALSE;

   // Always use the cursor library
   if (DBCError(g_hwnd, SQLSetConnectAttr(   g_hdbc,
                                          SQL_ATTR_ODBC_CURSORS,
                                          (void *)SQL_CUR_USE_ODBC,0))) {
      return FALSE;
   }

   // Complete variable initialization
   g_cbName          = 0;
   g_fConnected      = FALSE;
   g_fAsyncSupported = FALSE;
   g_szDSN[0]        = '\0';
   g_cChild          = 0;
   g_cCursor         = 0;

   g_mrows      = 1000;

   g_hwndClient = GetWindow(g_hwnd, GW_CHILD);

   // Set initial menu state
   AdjustMenus();

   // Show frame window
   ShowWindow(g_hwnd, nCmdShow);
   UpdateWindow(g_hwnd);
   return TRUE;
}

/* ODBCError ---------------------------------------------------------------
   Description: Fetch and display an ODBC error message
                NOTE: SQL_NO_DATA and SQL_STILL_EXECUTING are
                      not considered errors
                      --------------------------------------------------------------------------*/
BOOL INTFUNC ODBCError(HWND hwnd, SWORD fHandleType, SQLHANDLE handle, SQLRETURN rc)
{
   if (rc == SQL_SUCCESS)
      return FALSE;

   if (rc == SQL_NO_DATA) {
      DoMessage(hwnd, IDS_NODATAFOUND);
      return FALSE;
   }

   if (rc == SQL_STILL_EXECUTING) {
      DoMessage(hwnd, IDS_STILLEXEC);
      return FALSE;
   }

   {
      SDWORD   fNative;
      SWORD cbError;
      LPSTR lpszFmt;
      LPSTR lpszSQLState;
      LPSTR lpszError;
      LPSTR lpsz;
      SWORD sMsgNum = 1;

      // Allocate storage
      lpsz = AllocPtr(1024 + cbSTRLEN + 6 + SQL_MAX_MESSAGE_LENGTH);

      lpszFmt      = lpsz + 1024;
      lpszSQLState = lpszFmt + cbSTRLEN;
      lpszError    = lpszSQLState + 6;
      LoadString(g_hinst, IDS_MSGFMT, lpszFmt, cbSTRLEN);

      // sometimes handle comes in as NULL
      if (handle) {
         // Retrieve and display errors until there are no more
         while (SQLGetDiagRec(fHandleType, handle, sMsgNum++,
                              (UCHAR FAR *)lpszSQLState,
                              &fNative,
                              (UCHAR FAR *)lpszError,
                              SQL_MAX_MESSAGE_LENGTH-1,
                              &cbError) != SQL_NO_DATA) {
            if (lstrcmpi(lpszSQLState, szDATATRUNC)) {
               wsprintf(lpsz, lpszFmt, lpszSQLState, fNative, lpszError);

               MessageBox(hwnd, lpsz, g_szTITLE,
                          strncmp(lpszSQLState, "01", 2)
                          ? MB_ICONSTOP | MB_OK
                          : MB_ICONINFORMATION | MB_OK);
            }
         }
      }
      else {
         MessageBox(hwnd, "Invalid handle", g_szTITLE,
                    MB_ICONSTOP | MB_OK);
      }

      // Free storage
      FreePtr(lpsz);
   }

   return (!SUCCESS(rc));
}

/* WinMain -----------------------------------------------------------------
   Description: Standard WinMain function
   --------------------------------------------------------------------------*/
int INTFUNC WinMain(HINSTANCE   hinstCur,
                    HINSTANCE   hinstPrev,
                    LPSTR       lpszCmdLine,
                    int         nCmdShow)
{
   MSG msg;

   UNREF_PARAM(lpszCmdLine);
   g_hinst = hinstCur;

   if (!hinstPrev)
      if (!InitApplication())
         return (FALSE);

   if (!InitInstance(nCmdShow)) {
      EndInstance();
      return (FALSE);
   }

   while (GetMessage(&msg, (HWND)NULL, (UINT)NULL, (UINT)NULL))
      if (!TranslateMDISysAccel(g_hwndClient, &msg) &&
          !TranslateAccelerator(g_hwnd, g_haccel, &msg) &&
          (!(g_hwndChildDialog) || (!IsDialogMessage(g_hwndChildDialog, &msg)))) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }

   EndInstance();

   return msg.wParam;
}


CRSRDEMO.H

/*--------------------------------------------------------------------------
  Cursors.h -- General constants, types, prototypes and variables

  This code is furnished on an as-is basis as part of the ODBC SDK and is
  intended for example purposes only.

--------------------------------------------------------------------------*/

#ifndef __CURSORS_H
#define __CURSORS_H

#ifdef_M_ALPHA
#define  Print(x,y,z) wsprintf(x,y,z)
#else
#define Print(x,y,z)  wvsprintf(x,y,z)
#endif

#ifdef INCL_GLOBAL
#define GLOBAL
#define INITHAND(name, type)    type name = NULL
#ifdef WIN32
#define CONSTSTR(name, str) const char name[]=str
#else
#define CONSTSTR(name, str) const char FAR * name=str
#endif
#else
#define GLOBAL  extern
#define INITHAND(name, type)    extern type name
#ifdef WIN32
#define CONSTSTR(name, str) extern const char name[]
#else
#define CONSTSTR(name, str) extern const char FAR * name
#endif
#endif

// Constants ---------------------------------------------------------------
#ifdef WIN32
#define EXPFUNC __stdcall
#define INTFUNC __stdcall
#else
#define EXPFUNC __export __far __pascal
#define INTFUNC __pascal
#endif

#define INLINE __inline

// Miscellaneous
#define cbMAXSQL    512                    // Maximum SQL string length
#define cbSTRLEN    256                    // Maximum string length
#define cbINTLEN    16                     // maximum length of an decimal
                                           // integer in text format
#define cbBUFSIZE   4096                   // Working buffer size
#define cPOINTS 10                         // Font point size
#define cxBORDER    6                      // Painting border
#define cyBORDER    2                      // Painting border
#define MAX_MAXBIND 200                    // maximum length of any column
#define DEF_MAXBIND 50                     // default maximum length

// Miscellaneous strings
CONSTSTR(szMDICLIENT,   "MDICLIENT");
CONSTSTR(szCHILDCLASS,  "CursorsChild");
CONSTSTR(szFRAMECLASS,  "CursorsFrame");
CONSTSTR(szSTATICCLASS, "STATIC");
CONSTSTR(szEDITCLASS,   "EDIT");
CONSTSTR(szSCROLLCLASS, "SCROLLBAR");
CONSTSTR(szDSNKEY,      "DSN=");
CONSTSTR(szDATATRUNC,   "01004");
CONSTSTR(szTITLEFMT,    "%s (CURSOR%04d)");
CONSTSTR(szCURSORNAME,  "CURSOR%04d");
CONSTSTR(szFONT,        "MS Sans Serif");
CONSTSTR(szRECORD,      "Record:");
CONSTSTR(szRECNUM,      "%6d");
CONSTSTR(szHELPFILE,    "..\\doc\\odbcsmpl.hlp");  // Samples help file
CONSTSTR(szKeyword,"cursor demo features");

#define HLP_CRSRDEMO 40     // magic from help file

// Macros
#define AllocPtr(x) GlobalAllocPtr(GHND, (x))
#define FreePtr(x)      if ((x)) GlobalFreePtr((x))
#define DBCError(hwnd,x)    ODBCError(hwnd,SQL_HANDLE_DBC, g_hdbc, (x))
#define ENVError(hwnd,x)    ODBCError(hwnd,SQL_HANDLE_ENV, g_henv,  (x))

#define SUCCESS(x)  (((x)==SQL_SUCCESS) || ((x)==SQL_SUCCESS_WITH_INFO))

#define ISBLANK(x)      ((x) == ' ')
#define ISCOMMA(x)      ((x) == ',')
#define ISNUM(x)        (((x) >= '0') && ((x) <= '9'))
#define ISLPAREN(x)     ((x) == '(')
#define ISRPAREN(x)     ((x) == ')')
#define ISPERIOD(x)     ((x) == '.')
#define ISRETURN(x)     (((x) == '\n') || ((x) == '\r'))
#define ISTAB(x)        ((x) == '\t')
#define ISWHITE(x)      (ISBLANK(x) || ISTAB(x) || ISRETURN(x))

#define ROW_BINDING(x)  (x->fBindByRow ==   IDC_RADIO_BINDROW)
#define IS_ALLWFETCH(x) (x->dwGuiFlags & GUIF_ALWAYSFETCH)
#define UNREF_PARAM(x)  (x)

#define MAXNAME     100

// Types -------------------------------------------------------------------
typedef UWORD  FAR  *LPUWORD;
typedef SDWORD FAR  *LPSDWORD;

typedef struct tagBIGCOL {                 // Big column structure
    char        szName[cbSTRLEN];          //   Column name
    LPSTR       lpsz;                      //   Pointer to column data
    SDWORD      cb;                        //   Length of data
} BIGCOL, FAR *LPBIGCOL;

typedef struct tagCOL {                    // Column structure
    char        szName[cbSTRLEN];          //   Column name
    SDWORD      cb;                        //   Column width (transfer width)
    SDWORD      cbc;                       //   Column width (display  width)
    SWORD       fCType;                    //   C type
    SWORD       fSqlType;                  //   ODBC Sql type
    LPSDWORD    lpcb;                      //   Pointer to returned width
    LPBYTE      lpb;                       //   Pointer to returned data
} COL, FAR *LPCOL;

typedef struct tagCHILD {                  // Child window variables
    HWND        hwnd;                      //   Current window handle
    BOOL        fInSetScroll;              //   In SetScroll function flag
    BOOL        fIsMinimized;              //   Window minimized flag
    BOOL        fHaveMouse;                //   Mouse captured flag
    int         iMouseRow;                 //   Mouse down row

    int         ccols;                     //   Number of characters per line
    int         crowwin;                   //   Rows per window
    int         ccolwin;                   //   Character columns per window
    HWND        hwndVScroll;               //   Vertical scroll bar
    HWND        hwndHScroll;               //   Horizontal scroll bar
    BOOL        fVScroll;                  //   Vertical scroll active flag
    BOOL        fHScroll;                  //   Horizontal scroll active flag

    LPINT       lpnTabs;                   //   Array of tab values
    char        szFmt[cbSTRLEN];           //   Format string
    char *     rglpv;                     //   wvsprintf data array

    HRGN        hrgn;                      //   Clip region
    LPSTR       lpsz;                      //   Working buffer

    SQLHSTMT    hstmt;                     //   ODBC statement handle
    SQLHSTMT    hstmtTmp;                  //   Temp ODBC statement handle
    LPSTR       sql;                       //   SQL statement

    SWORD       ccol;                      //   Number of result columns
    LPCOL       lpcol;                     //   Column information array
    BOOL        fResultSetExists;          //   Result set exists flag
    BOOL        fDataFetched;              //   Data has been fetched flag
    LPUWORD     lpfStatus;                 //   Row status array

    BOOL        fBindByRow;                //   Row-wise binding flag
    LPBYTE      lpb;                       //   Row-wise binding buffer
    SDWORD      cbrow;                     //   Row-wise binding step quantity

    UWORD       fConcurrency;              //   Scroll concurrency
    SDWORD      crowKeyset;                //   Keyset option
    UWORD       crowRowset;                //   Rowset option
    BOOL        fRowset;                   //   crowRowset has been modified
    char        szRowset[cbINTLEN];        //   text format buffer of inputed
                                           //   crowRowset

    BOOL        fAsync;                    //   Async enabled flag
    BOOL        fCanceled;                 //   Request canceled flag
    UWORD       irowPos;                   //   Positioned row (via SQLSetPos)
    SDWORD      irow;                      //   First row in row set (absolute)
    UWORD       cBind;                     //   Number of bound columns
    BOOL        fBind;                     //   cBind's been modified
    char        szBind[cbINTLEN];          //   a buffer to store the
                                           // text format of inputed cBind
    BOOL        fBindAll;                  //   Bind all columns flag
    UWORD       ccolRetrieved;             //   Number of retrieved columns

    SDWORD      arow;                      //   Fetch absolute value
    SDWORD      rrow;                      //   Fetch relative value
    UCHAR       szQualifier[MAXNAME];      //   qualifier name
    UCHAR       szTable[MAXNAME];          //   table name
    UCHAR       szUser[MAXNAME];           //   user name
    UCHAR       szType[MAXNAME];           //   table type (for SQLtables)
    UCHAR       szColName[MAXNAME];        //   column name (for SQLColumns)
    UWORD       dwOperation;               //   operation to perform  (mistype)
    DWORD       dwGuiFlags;                //   flags and state to the gui
    SDWORD      crowMaxBind;               //   max length to bind col to
    BOOL        fMaxBind;                  //   crowMaxBind has been modified
    char        szMaxBind[cbINTLEN];       //   the text format buffer of the
                                           //   inputed crowMaxBind
    DWORD       crowCurrent;               //   current actual size of rowset
    BOOL        fNoConcurrency;            //   driver doesn't support concur
    BOOL        fNoCursorType;             //   no cursor type
    UWORD       dwRadioButton;             //   What radio button in info dlg?
    UWORD       FetchOP;
#ifdef THREAD
    CRITICAL_SECTION ThreadCreation;
    HANDLE hThread;
#endif
} CHILD, FAR *LPCHILD;

#define     OPER_SELECT     1
#define     OPER_TABLES     2
#define     OPER_PRIVS      3
#define     OPER_STATS      4
#define     OPER_PROCS      5
#define     OPER_COLUMN     6
#define     OPER_TYPES      7

#define     GUIF_TABLES_RADIO   0x00001     // 'tables' radio button set
#define     GUIF_ALWAYSFETCH    0x00002     // always fetch first

#define SetDlgText(hdlg,id,text)  Edit_SetText(GetDlgItem(hdlg,id), text)

// dialog pair -- dialog and id

typedef struct tagDialogPair{
    LPSTR   szDlgPairTitle;
    int     iDlgPairDlgId;
} DIALOG_PAIR;

// Prototypes --------------------------------------------------------------
// General routines
void INTFUNC AdjustMenus(void);
int  INTFUNC DoDialog(HWND, int, DLGPROC);
void INTFUNC DoMessage(HWND, UINT);
void INTFUNC GetCurrentValue(LPSTR, LPCOL, LPCHILD);
BOOL INTFUNC IsUpdateable(SDWORD);
void INTFUNC MakeTable(void);
BOOL INTFUNC ODBCError(HWND, SWORD, SQLHANDLE, SQLRETURN);
BOOL INTFUNC SetCurrentValue(LPSTR, LPCOL, LPCHILD);
SQLRETURN INTFUNC PrepareStmt(LPCHILD lpchild);
BOOL INTFUNC ProcessResults(LPCHILD lpchild);

// Window/Dialog procedures
BOOL CALLBACK InitControlCallback( HWND , LPARAM);

INLINE  BOOL    SetOrGetCheck( HWND,  int   , BOOL);
INLINE  VOID    SetOrGetEditArray( HWND, UCHAR FAR *, int);

VOID    INTFUNC  InitializeDialogControls( HWND hDlg, LPCHILD   lpChild);
VOID    INTFUNC  ControlValue ( LPCHILD, HWND, HWND , int   , int );
BOOL    INTFUNC  InitializeListBox( HWND, int, const DIALOG_PAIR FAR * , int, int);
BOOL    INTFUNC  SetHiddenFields( HWND  hDlg, int       iAct);
BOOL    INTFUNC  AlignToControl( HWND   hWnd, HWND  hDlg, int       iCtlId);
BOOL    CALLBACK OptionsDlgProc(HWND hdlg,UINT msg,WPARAM wparam,LPARAM lparam);
VOID    INTFUNC  ClassOnCommand(HWND hWnd, int iId, HWND hWndCtl, UINT uNotify);
BOOL    CALLBACK ChildOptDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL    CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK AbsDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK DataDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK FindDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK MakeTableDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK OptDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK RelDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK StmtDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK TableDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK UpdateDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK SQLTablesDlgProc(HWND,UINT,WPARAM,LPARAM);

LRESULT CALLBACK ChildProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK FrameProc(HWND, UINT, WPARAM, LPARAM);

// Variables ---------------------------------------------------------------
GLOBAL  HWND        g_hwnd;                // Main window handle
GLOBAL  HWND        g_hwndClient;          // MDI client window handle
GLOBAL  HWND        g_hwndChildDialog;     // child dialog if active
GLOBAL  HINSTANCE   g_hinst;               // Instance handle
GLOBAL  HACCEL      g_haccel;              // Accelerators

GLOBAL  HMENU       g_hmenuInit;           // Pre-connection menu
GLOBAL  HMENU       g_hmenuInitWindow;     //   Windows menu (for MDI)
GLOBAL  HMENU       g_hmenuFrame;          // Connection made menu
GLOBAL  HMENU       g_hmenuFrameWindow;    //   Windows menu (for MDI)
GLOBAL  HMENU       g_hmenuChild;          // Child window menu
GLOBAL  HMENU       g_hmenuChildWindow;    //   Windows menu (for MDI)

GLOBAL  int         g_cxVScroll;           // Width  of vertical scroll bar
GLOBAL  int         g_cyHScroll;           // Height of horizontal scroll bar

GLOBAL  int         g_cx;                  // Width  of a character
GLOBAL  int         g_cy;                  // Height of a character

GLOBAL  int         g_cxRecord;            // Width of record count area
GLOBAL  int         g_cxRecnum;            // Width of record number

GLOBAL  HFONT       g_hfontName;           // Column name font
GLOBAL  HFONT       g_hfontData;           // Data font

GLOBAL  HBRUSH      g_hbrWin;              // Window background brush
GLOBAL  HBRUSH      g_hbrBtn;              // Button face brush
GLOBAL  HBRUSH      g_hbrScroll;           // Scroll bar brush

GLOBAL  SQLHENV     g_henv;                // ODBC environment handle
GLOBAL  SQLHDBC     g_hdbc;                // ODBC connection handle

GLOBAL  SWORD       g_cbName;              // Maximum column name width
GLOBAL  char        g_szQuoteChar[2];      // Identifier quote char
GLOBAL  BOOL        g_fConnected;          // Connected flag
GLOBAL  BOOL        g_fAsyncSupported;     // Driver supports async flag
GLOBAL  char        g_szDSN[SQL_MAX_DSN_LENGTH+1];  // Data source name
GLOBAL  int         g_cChild;              // Number of child windows
GLOBAL  int         g_cCursor;             // Current cursor number

GLOBAL  char        g_szTable[cbSTRLEN];   // Make table name
GLOBAL  SDWORD      g_mrows;               // Make table row count

GLOBAL  char        g_szTITLE[cbSTRLEN];   // App title
GLOBAL  char        g_szNoRow[cbSTRLEN];   // No row string
GLOBAL  char        g_szRowError[cbSTRLEN];// No row string
GLOBAL  char        g_szNull[cbSTRLEN];    // Null column string
GLOBAL  char        g_szRowDeleted[cbSTRLEN]; // Row deleted string
GLOBAL  char        g_szUnknown[cbSTRLEN]; // Unknown string

// Constants ---------------------------------------------------------------
#define WMU_INIT            (WM_USER+1)
#define WMU_DROPTABLE       (WM_USER+2)
#define WMU_INSERTREC       (WM_USER+4)
#define WMU_CLEANUP         (WM_USER+5)
#define WMU_CANCEL          (WM_USER+6)
#define WMU_NEWOPTION       (WM_USER+7)         // new options child menu
#define WMU_SETSUBTEXT      (WM_USER+8)         // set subtext (options group)

#define ACT_INIT            1                   // initialize control
#define ACT_TRIGGER         2                   // control has been set

#endif // __CURSORS_H


DIALOGS.C

/*--------------------------------------------------------------------------
  Dialogs.c -- Cursors dialogs

  Description:
      This sample is spread across four files, each named for the role
      the contained functions play.  Each file header contains a brief
      description of its purpose and the routines it contains.

      DIALOGS.C contains those routines used to display and manage
      dialogs.  Those functions are:

         AddEditControls - Dynamically create edit controls for UPDATE
                        dialog
         AddDlgProc      - Manage add row dialog
         CenterDialog    - Center a dialog over its parent window
         DoDialog        - Display a dialog
         MyCreateDialog - Create a modeless dialog
         GetEditControls - Retrieve values from dynamically created
                        edit controls
         IsMsgWaiting    - Check for waiting messages

         AboutDlgProc    - Manage about box
         AbsDlgProc      - Manage absolute row number dialog
         DataDlgProc     - Manage large data display dialog
         FindDlgProc    - Manager dialog to get text string to find in result set
         RelDlgProc      - Manage relative row number dialog
         StmtDlgProc     - Manage SQL statement dialog
         UpdateDlgProc   - Manage update row dialog
         SqlTablesDlgProc- handle SQLTables-type request
         ClassOnCommand - handle a command message

  This code is furnished on an as-is basis as part of the ODBC SDK and is
  intended for example purposes only.

--------------------------------------------------------------------------*/

// Includes ----------------------------------------------------------------
#include "headers.h"

#include    "resource.h"
#include "crsrdemo.h"

const char szCREATE[] = "CREATE TABLE %s (id int NOT NULL, name char(31) NOT NULL, C3 int)";
const char szDROP[]   = "DROP TABLE %s";
const char szINSERT[] = "INSERT INTO %s VALUES (?, '-FakeTable-FakeTable-FakeTable-', NULL)";

const int   xFIRST = 10;
const int   cxSEP  = 6;
const int   cySEP  = 3;
const int   cxNAME = 35;
const int   cyNAME = 8;
const int   cxEDIT = 180;
const int   cyEDIT = 10;

const DWORD dwDLGSTYLE    = DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU;
const DWORD dwSTATICSTYLE = WS_CHILD | WS_VISIBLE | SS_RIGHT;
const DWORD dwEDITSTYLE   = WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT;

#define STMTError(x) ODBCError(SQL_NULL_HENV, SQL_NULL_HDBC, lpmtbl->hstmt, (x))

#define  USERDATA GWL_USERDATA

// Types -------------------------------------------------------------------
typedef struct tagMTBL {                  // Make table structure
   SQLHSTMT hstmt;                        //   HSTMT in use
   SDWORD   i;                            //   Rows inserted
} MTBL, FAR *LPMTBL;

// Prototypes --------------------------------------------------------------
void INTFUNC AddEditControls(HWND, LPCHILD);
void INTFUNC CenterDialog(HWND);
BOOL INTFUNC GetEditControls(HWND, LPCHILD);
BOOL INTFUNC IsMsgWaiting(HWND);
BOOL INTFUNC DlgProcFilter(HWND, UINT, WPARAM, LPARAM);

/* AddEditControls ---------------------------------------------------------
   Description: Add one edit control for each updateable bound column to
                the dialog box
                --------------------------------------------------------------------------*/
void INTFUNC AddEditControls(HWND hDlg, LPCHILD lpChild)
{
#define DLGX(x)   (((x) * LOWORD(dwBaseUnits)) / 4)
#define DLGY(y)   (((y) * HIWORD(dwBaseUnits)) / 8)

   HWND  hWnd;
   HFONT hfont;
   RECT  rc;
   LPCOL lpcol;
   char  sz[cbMAXSQL];
   char  szFmt[cbSTRLEN];
   DWORD dwBaseUnits;
   UINT  idName, idEdit;
   int      xName, yName;
   int      cxName, cyName;
   int      xEdit, yEdit;
   int      cxEdit, cyEdit;
   int      i;
   HDC   hdc;
   SIZE  size;

   // Determine basic characteristics and start position in dialog
   hfont = (HFONT)SendDlgItemMessage(hDlg, IDC_STATIC1,
                                     WM_GETFONT, 0, 0L);

   dwBaseUnits = GetDialogBaseUnits();

   cxName = DLGX(cxNAME);
   cyName = DLGY(cyNAME);
   cxEdit = DLGX(cxEDIT);
   cyEdit = DLGY(cyEDIT);

   LoadString(g_hinst, IDS_COLNAME, szFmt, sizeof(szFmt));

   // Calculate the size of the largest name label

   hdc = GetDC(NULL);

   if( hdc ) {
      for( i = 0, lpcol = lpChild->lpcol; i < lpChild->ccol; i++, lpcol++ )
         if( IsUpdateable(lpcol->fSqlType) ) {
            wsprintf(sz, szFmt, lpcol->szName);
            GetTextExtentPoint(hdc, sz, lstrlen(sz), &size);
            if( size.cx > cxName )
               cxName = size.cx;
         }
      ReleaseDC(NULL, hdc);
   }

   GetWindowRect(GetDlgItem(hDlg, IDOK), &rc);

   xName = DLGX(xFIRST);
   yName = (4 * DLGY(cySEP)) + (DLGY(cySEP) / 2) + (2 * (rc.bottom - rc.top));

   xEdit = xName + cxName + DLGX(cxSEP);

   idName = stc1;
   idEdit = edt1;

   // For each bound, updateable column, create and add an edit control
   for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
      if (IsUpdateable(lpcol->fSqlType)) {

         // Create control label
         wsprintf(sz, szFmt, lpcol->szName);

         hWnd = CreateWindow(szSTATICCLASS, sz, dwSTATICSTYLE,
                             xName, yName, cxName, cyName, hDlg,
                             (HMENU)idName, g_hinst, NULL);

         FORWARD_WM_SETFONT(hWnd, hfont, 0, SendMessage);

         // Create (and intialize) edit control
         yEdit = yName - ((cyEDIT - cyNAME) / 2);

         GetCurrentValue(sz, lpcol, lpChild);

         hWnd = CreateWindow(szEDITCLASS, sz, dwEDITSTYLE,
                             xEdit, yEdit, cxEdit, cyEdit, hDlg,
                             (HMENU)idEdit, g_hinst, NULL);

         FORWARD_WM_SETFONT(hWnd, hfont, 0, SendMessage);

         // Limit number of characters user can type to column display size
         Edit_LimitText(hWnd, lpcol->cbc-1);

         yName += cyEdit + DLGY(cySEP);

         idName++;
         idEdit++;
      }
   }

   // Grow dialog so that all controls are visible
   GetClientRect(hDlg, &rc);

   rc.top    = 0;
   rc.bottom = yName + DLGY(cySEP);

   rc.left   = 0;
   rc.right  = (2 * DLGX(xFIRST)) + DLGX(cxNAME) + DLGX(cxEDIT) + DLGX(cxSEP);

   AdjustWindowRect(&rc, dwDLGSTYLE, FALSE);
   MoveWindow(hDlg, 0, 0, rc.right - rc.left, rc.bottom - rc.top, TRUE);

   // Place OK and Cancel buttons appropriately
   GetClientRect(hDlg, &rc);
   {  RECT  rcButton;
      int      x, y;

      GetWindowRect(GetDlgItem(hDlg, IDOK), &rcButton);

      x = rc.right - DLGX(cxSEP) - (rcButton.right - rcButton.left);
      y = DLGY(cySEP);

      MoveWindow(GetDlgItem(hDlg, IDOK),
                 x, y,
                 rcButton.right - rcButton.left,
                 rcButton.bottom - rcButton.top,
                 TRUE);

      y += rcButton.bottom - rcButton.top + (DLGY(cySEP) / 2);

      GetWindowRect(GetDlgItem(hDlg, IDCANCEL), &rcButton);
      MoveWindow(GetDlgItem(hDlg, IDCANCEL),
                 x, y,
                 rcButton.right - rcButton.left,
                 rcButton.bottom - rcButton.top,
                 TRUE);
   }

   return;
}

/* CenterDialog ------------------------------------------------------------
   Description: Center dialog over its owning parent window
                If the entire dialog does not fit on the desktop,
                ensure upper left corner is always visible
                --------------------------------------------------------------------------*/
void INTFUNC CenterDialog(HWND hDlg)
{
   RECT  rcDlg, rcScr, rcParent;
   int      cx, cy;

   GetWindowRect(hDlg, &rcDlg);
   cx = rcDlg.right  - rcDlg.left;
   cy = rcDlg.bottom - rcDlg.top;

   GetWindowRect(GetParent(hDlg), &rcParent);
   rcDlg.top    = rcParent.top +
      (((rcParent.bottom - rcParent.top) - cy) >> 1);
   rcDlg.left   = rcParent.left +
      (((rcParent.right - rcParent.left) - cx) >> 1);
   rcDlg.bottom = rcDlg.top  + cy;
   rcDlg.right  = rcDlg.left + cx;

   GetWindowRect(GetDesktopWindow(), &rcScr);
   if (rcDlg.bottom > rcScr.bottom) {
      rcDlg.bottom = rcScr.bottom;
      rcDlg.top    = rcDlg.bottom - cy;
   }
   if (rcDlg.right  > rcScr.right) {
      rcDlg.right = rcScr.right;
      rcDlg.left  = rcDlg.right - cx;
   }

   if (rcDlg.left < 0) rcDlg.left = 0;
   if (rcDlg.top  < 0) rcDlg.top  = 0;

   MoveWindow(hDlg, rcDlg.left, rcDlg.top, cx, cy, FALSE);
   return;
}

/* DoDialog ----------------------------------------------------------------
   Description: Launch a dialog passing child window variables
   --------------------------------------------------------------------------*/
int INTFUNC DoDialog(HWND    hWndParent,
                     int     nDlgResNum,
                     DLGPROC lpDlgProc)
{
   HWND  hWnd;
   LPCHILD  lpChild;
   int      nRC;

   hWnd    = FORWARD_WM_MDIGETACTIVE(g_hwndClient, SendMessage);
   lpChild = (hWnd
              ? (LPCHILD)GetWindowLong(hWnd, 0)
              : NULL);

   nRC = DialogBoxParam(g_hinst,
                        MAKEINTRESOURCE(nDlgResNum),
                        hWndParent,
                        lpDlgProc,
                        (LONG)lpChild);
   return nRC;
}

/* MyCreateDialog ------------------------------------------------------------
   Description: Launch a  modeless dialog passing child window variables
--------------------------------------------------------------------------*/

HWND INTFUNC MyCreateDialog(HWND    hWndParent,
                            int     nDlgResNum,
                            DLGPROC lpDlgProc)
{
   HWND  hWnd, hWndRet;
   LPCHILD  lpChild;

   hWnd    = FORWARD_WM_MDIGETACTIVE(g_hwndClient, SendMessage);
   lpChild = (hWnd
              ? (LPCHILD)GetWindowLong(hWnd, 0)
              : NULL);

   hWndRet = CreateDialogParam(g_hinst,
                               MAKEINTRESOURCE(nDlgResNum),
                               hWndParent,
                               lpDlgProc,
                               (LPARAM)lpChild);
   return hWndRet;
}

/* GetEditControls ---------------------------------------------------------
   Description: Get values from edit controls and move to row-set buffers
   --------------------------------------------------------------------------*/
BOOL INTFUNC GetEditControls(HWND hDlg, LPCHILD lpChild)
{
   LPCOL lpcol;
   char  sz[cbMAXSQL];
   UINT  idEdit;
   BOOL  fChangeMade;
   int      i;

   fChangeMade = FALSE;
   idEdit      = edt1;

   // For each bound, updateable column, retrieve the value
   for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
      if (IsUpdateable(lpcol->fSqlType)) {

         // Get the control value
         SendDlgItemMessage(hDlg, idEdit, WM_GETTEXT,
                            (WPARAM)sizeof(sz), (LPARAM)((LPSTR)sz));

         // Move into row-set buffer
         if (SetCurrentValue(sz, lpcol, lpChild) && !fChangeMade)
            fChangeMade = TRUE;

         idEdit++;
      }
   }

   return fChangeMade;
}

/* IsMsgWaiting ------------------------------------------------------------
   Description: Return TRUE if the Cancel button has been pressed
   --------------------------------------------------------------------------*/
BOOL INTFUNC IsMsgWaiting(HWND hWnd)
{
   MSG   msg;

   UNREF_PARAM (hWnd);

   // Check all waiting messages
   while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

      // Process message
      TranslateMessage(&msg);
      DispatchMessage(&msg);

      // Return TRUE if the Cancel button was pressed
      if ((msg.message == WM_COMMAND
           && GET_WM_COMMAND_ID(msg.wParam, msg.lParam) == IDCANCEL)
          || msg.message == WMU_CANCEL)
         return TRUE;
   }
   return FALSE;
}

/* OptionsDlgProc ----------------------------------------------------------
   Description: handle options dialog
----------------------------------------------------------------------------*/

BOOL EXPFUNC OptionsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{

   switch (msg) {

     case WM_INITDIALOG:
      CenterDialog(hDlg);

      SetWindowLong(hDlg, USERDATA, (LONG)lParam);
      InitializeDialogControls(hDlg, (LPCHILD) lParam);

      return TRUE;

     case WM_COMMAND:
      (void) HANDLE_WM_COMMAND(hDlg,wParam,lParam,ClassOnCommand);
      break;

      // New option dialog selected

     case WMU_NEWOPTION:
      {
         LPCHILD  lpChild = (LPCHILD) GetWindowLong(hDlg, USERDATA);

         if (g_hwndChildDialog)
            DestroyWindow(g_hwndChildDialog);

         g_hwndChildDialog = MyCreateDialog(hDlg,
                                            (int) lParam,
                                            ChildOptDlgProc);

         SetFocus(GetDlgItem(hDlg, IDC_OPTIONLIST));

         break;
      }

      // Set title of option area

     case WMU_SETSUBTEXT:
      {
         char  szBuffer[1000];

         ListBox_GetText(GetDlgItem(hDlg, wParam),
                         ListBox_GetCurSel(GetDlgItem(hDlg,wParam)),
                         szBuffer);

         Static_SetText( GetDlgItem(hDlg, IDC_OPTIONAREA_TITLE),
                        szBuffer);
         break;

      }

   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* ChildOptDlgProc --------------------------------------------------------
   Description: handle generic dialog
----------------------------------------------------------------------------*/

BOOL EXPFUNC ChildOptDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{

   switch (msg) {

     case WM_INITDIALOG:
      SetWindowLong(hDlg, USERDATA, (LONG)lParam);
      InitializeDialogControls(hDlg, (LPCHILD) lParam);

      // Align the window to a hidden static in the parent

      (void) AlignToControl(hDlg, GetParent(hDlg), IDC_OPTION_WINPOS);

      return TRUE;

     case WM_COMMAND:
      return (BOOL)HANDLE_WM_COMMAND(hDlg,wParam,lParam,ClassOnCommand);

   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* AboutDlgProc ------------------------------------------------------------
   Description: Display about box
   --------------------------------------------------------------------------*/
BOOL EXPFUNC AboutDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   UNREF_PARAM (lParam);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);
      return TRUE;

     case WM_COMMAND:
      if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK
          || GET_WM_COMMAND_ID(wParam, lParam) == IDCANCEL) {
         EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* AbsDlgProc --------------------------------------------------------------
   Description: Get absolute row number to fetch
   --------------------------------------------------------------------------*/
BOOL EXPFUNC AbsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;
   char  sz[11];
   char  *EndPtr;
   SDWORD   arow;

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);

      lpChild = (LPCHILD)lParam;

      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);
      wsprintf(sz, "%ld", lpChild->arow);
      SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), sz);
      SendDlgItemMessage(hDlg, IDC_EDIT1,
                         EM_LIMITTEXT, sizeof(sz)-1, 0L);
      return TRUE;

     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         GetWindowText(GetDlgItem(hDlg, IDC_EDIT1), sz, sizeof(sz));
         arow = strtol((char*) sz, &EndPtr, 10);
         for (; *EndPtr &&
              ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
         // the number inputed is within '0' to '9' or + or -
         if (*EndPtr != '\0') {
            MessageBox(hDlg,
                       "Invalid absolute row number",
                       NULL,
                       MB_ICONSTOP);
            return TRUE;
         }
         lpChild->arow = arow;

        case IDCANCEL:
         EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* DataDlgProc -------------------------------------------------------------
   Description: Display large data value (retrieved via SQLGetData)
   --------------------------------------------------------------------------*/
BOOL EXPFUNC DataDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg) {
     case WM_INITDIALOG: {
        LPBIGCOL  lpbc;
        char      sz[cbSTRLEN];
        char      szNum[cbSTRLEN];
        char      szFmt[cbSTRLEN];

        CenterDialog(hDlg);

        lpbc = (LPBIGCOL)lParam;

        if (lpbc->cb == SQL_NULL_DATA) {
           lstrcpy(lpbc->lpsz, g_szNull);
           lpbc->cb = 0;
           _ltoa(0, szNum, 10);
        }
        else if (lpbc->cb == SQL_NO_TOTAL)
           lstrcpy(szNum, g_szUnknown);
        else
           _ltoa(lpbc->cb, szNum, 10);

        LoadString(g_hinst, IDS_DATADLG, szFmt, sizeof(szFmt));
        wsprintf(sz, szFmt, lpbc->szName, szNum);
        SetWindowText(GetDlgItem(hDlg, IDC_TEXT1), sz);

        SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), lpbc->lpsz);
        return TRUE;
     }

     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
        case IDCANCEL:
         EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* RelDlgProc --------------------------------------------------------------
   Description: Get relative row offset to fetch
   --------------------------------------------------------------------------*/
BOOL EXPFUNC RelDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;
   char  sz[7];

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);

      lpChild = (LPCHILD)lParam;
      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);

      wsprintf(sz, "%ld", lpChild->rrow);
      SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), sz);
      SendDlgItemMessage(hDlg, IDC_EDIT1,
                         EM_LIMITTEXT, sizeof(sz)-1, 0L);
      return TRUE;

     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK: {
           SDWORD rrow;
           char   *EndPtr;

           GetWindowText(GetDlgItem(hDlg, IDC_EDIT1), sz, sizeof(sz));
           rrow = strtol((char*) sz, &EndPtr, 10);
           for (; *EndPtr &&
                ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
           // the number inputed is within '0' to '9' or + or -
           if (*EndPtr != '\0' || rrow < -100000 || rrow > 100000) {
              MessageBox(hDlg,
                         "Step amount must be between -100,000 and 100,000",
                         NULL,
                         MB_ICONSTOP);
              return TRUE;
           }
           lpChild->rrow = rrow;
        }

        case IDCANCEL:
         EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* StmtDlgProc -------------------------------------------------------------
   Description: Get SQL statement to execute
   --------------------------------------------------------------------------*/
BOOL EXPFUNC StmtDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);

      lpChild = (LPCHILD)lParam;
      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);

      SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), lpChild->sql);
      SendDlgItemMessage(hDlg, IDC_EDIT1, EM_LIMITTEXT, cbMAXSQL-1, 0L);
      return TRUE;

     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         GetWindowText(GetDlgItem(hDlg, IDC_EDIT1),
                       lpChild->sql, cbMAXSQL);

         lpChild->dwOperation = OPER_SELECT;

        case IDCANCEL:
         EndDialog(hDlg, wParam);
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* UpdateDlgProc -----------------------------------------------------------
   Description: Get new values with which to update the current row
   --------------------------------------------------------------------------*/
BOOL EXPFUNC UpdateDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {

      // Build dialog dynamically, adding appropriate edit controls
     case WM_INITDIALOG:
      lpChild = (LPCHILD)lParam;

      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);
      AddEditControls(hDlg, lpChild);
      SendDlgItemMessage(hDlg,edt1,EM_SETSEL,GET_EM_SETSEL_MPS(0, -1));

      CenterDialog(hDlg);
      SetFocus(GetDlgItem(hDlg, edt1));
      return FALSE;

      // Close dialog updating row-set buffer (if OK) with new values
     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         if (!GetEditControls(hDlg, lpChild))
            GET_WM_COMMAND_ID(wParam, lParam) = IDCANCEL;

        case IDCANCEL:
         EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/* FindDlgProc -------------------------------------------------------------
   Description: Get text string to find in result set
   --------------------------------------------------------------------------*/
BOOL EXPFUNC FindDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);

      lpChild = (LPCHILD)lParam;
      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);

      SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), lpChild->sql);
      SendDlgItemMessage(hDlg, IDC_EDIT1, EM_LIMITTEXT, cbMAXSQL-1, 0L);
      return TRUE;

     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
         GetWindowText(GetDlgItem(hDlg, IDC_EDIT1),
                       lpChild->sql, cbMAXSQL);

        case IDCANCEL:
         EndDialog(hDlg, wParam);
         return TRUE;
      }
      break;
   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);
}

/*---------------------------------------------------------------------------
** SQLTablesDlgProc -- get information for SQLTables and similar functions
----------------------------------------------------------------------------*/
BOOL EXPFUNC SQLTablesDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   LPCHILD  lpChild;

   lpChild = (LPCHILD)GetWindowLong(hDlg, USERDATA);

   switch (msg) {
     case WM_INITDIALOG:
      CenterDialog(hDlg);

      lpChild = (LPCHILD)lParam;
      SetWindowLong(hDlg, USERDATA, (LONG)lpChild);

      // Set the default button
      if (!(lpChild->dwGuiFlags & GUIF_TABLES_RADIO)) {
         lpChild->dwOperation = IDC_TABLE_RAD_TABLE;
         lpChild->dwRadioButton = IDC_TABLE_RAD_TABLE;
      }

      // Set the dialog up

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,lpChild->dwRadioButton),
                   lpChild->dwRadioButton,
                   ACT_INIT);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,lpChild->dwRadioButton),
                   lpChild->dwRadioButton,
                   ACT_TRIGGER);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,IDC_TABLEINFO_QUALIFIER),
                   IDC_TABLEINFO_QUALIFIER,
                   ACT_INIT);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,IDC_TABLEINFO_NAME),
                   IDC_TABLEINFO_NAME,
                   ACT_INIT);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,IDC_TABLEINFO_OWNER),
                   IDC_TABLEINFO_OWNER,
                   ACT_INIT);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,IDC_TABLEINFO_TYPE),
                   IDC_TABLEINFO_TYPE,
                   ACT_INIT);

      ControlValue(  lpChild,
                   hDlg,
                   GetDlgItem(hDlg,IDC_TABLEINFO_COLNAME),
                   IDC_TABLEINFO_COLNAME,
                   ACT_INIT);
      return TRUE;

     case WM_COMMAND:
      return (BOOL)HANDLE_WM_COMMAND(hDlg, wParam, lParam, ClassOnCommand);
      break;

   }

   return DlgProcFilter(hDlg, msg, wParam, lParam);

}

/*---ClassCommand---------------------------------------------------------
   Filters commands for dialogs.   Forwards those messages that we think
   are interesting to a handler that processes the events.
---------------------------------------------------------------------------*/

VOID INTFUNC ClassOnCommand(HWND hWnd, int iId, HWND hWndCtl, UINT uNotify)
{
   LPCHILD  lpChild;

   lpChild = (LPCHILD)GetWindowLong(hWnd,USERDATA);

   if (!(lpChild))
      return;

   switch (uNotify) {

     case   BN_CLICKED:       // button control selected
     case   EN_UPDATE:        // edit box has been updated
     case   LBN_SELCHANGE:       // listbox has gotten a message
      ControlValue(lpChild, hWnd, hWndCtl, iId, ACT_TRIGGER);
      break;

   }
}

/*
 ** ControlValue -- Initialize a control's value from the child's state, or
 **               set the child's state from the control.   All control-
 **               specific logic should go here.
 **
 ** Parameters:
 **      lpChild  -- child state
 **      hDlg  --  id of the active dialog
 **      hCtl  --  control's window
 **      iId      --  control's id
 **      iAction  -- Action to take -- ACT_INIT or ACT_TRIGGER
 **
 ** No return value
 */

VOID INTFUNC ControlValue (LPCHILD     lpChild,
                           HWND     hDlg,
                           HWND     hCtl,
                           int         iId,
                           int         iAction)
{
   int      iLbSelection;
   DWORD dwListData;
   BOOL  fTemp;
   UCHAR szBuffer[MAXNAME];

   const DIALOG_PAIR dpOptList[] = {
      {"Binding",          IDD_BIND_OPTIONS},
      {"General options",     IDD_GENERAL_OPTIONS},
      {"Scrolling Options",   IDD_SCROLLING_OPTIONS},
      {"Concurrency Options", IDD_CONCURRENCY_OPTIONS}};

   switch (iId) {

     case   IDOK:                   // generic OK
     case   IDCANCEL:                  // generic CANCEL
      if (iAction == ACT_TRIGGER) {
         if (g_hwndChildDialog) {
            g_hwndChildDialog = NULL;
         }
         EndDialog(hDlg, iId);
      }
      break;

     case IDC_RADIO_BINDROW:
     case IDC_RADIO_BINDCOL:
      if (SetOrGetCheck(hCtl, iAction, (iId == lpChild->fBindByRow))) {
         lpChild->fBindByRow = iId;
      }
      break;

     case IDC_RADIO_READONLY:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->fConcurrency == SQL_CONCUR_READ_ONLY))) {
         lpChild->fConcurrency = SQL_CONCUR_READ_ONLY;
      }
      break;
     case IDC_RADIO_LOCKING:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->fConcurrency == SQL_CONCUR_LOCK))) {
         lpChild->fConcurrency = SQL_CONCUR_LOCK;
      }
      break;
     case IDC_RADIO_OPTIMISTIC:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->fConcurrency == SQL_CONCUR_ROWVER))) {
         lpChild->fConcurrency = SQL_CONCUR_ROWVER;
      }
      break;
     case IDC_RADIO_OPTIMVALUE:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->fConcurrency == SQL_CONCUR_VALUES))) {
         lpChild->fConcurrency = SQL_CONCUR_VALUES;
      }
      break;

     case IDC_RADIO_FORWARD:
      if (SetOrGetCheck(hCtl, iAction,

(lpChild->crowKeyset == SQL_CURSOR_FORWARD_ONLY))) {
         lpChild->crowKeyset = SQL_CURSOR_FORWARD_ONLY;
      }
      break;
     case IDC_RADIO_SNAPSHOT:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->crowKeyset == SQL_CURSOR_STATIC))) {
         lpChild->crowKeyset = SQL_CURSOR_STATIC;
      }
      break;
     case IDC_RADIO_KEYSET:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->crowKeyset == SQL_CURSOR_KEYSET_DRIVEN))) {
         lpChild->crowKeyset = SQL_CURSOR_KEYSET_DRIVEN;
      }
      break;
     case IDC_RADIO_DYNAMIC:
      if (SetOrGetCheck(hCtl, iAction,
                        (lpChild->crowKeyset == SQL_CURSOR_DYNAMIC))) {
         lpChild->crowKeyset = SQL_CURSOR_DYNAMIC;
      }
      break;

     case IDC_CHECK_BINDALL:
      fTemp = SetOrGetCheck(hCtl,iAction, (lpChild->fBindAll));
      lpChild->fBindAll = fTemp;
      Edit_Enable(GetDlgItem(hDlg,IDC_EDIT_BIND), !(fTemp));
      Static_Enable(GetDlgItem(hDlg, IDC_STATIC_NBIND), !(fTemp));
      break;

     case IDC_EDIT_BIND:
      if (lpChild->fBind)
         wsprintf((LPSTR)szBuffer,"%s", lpChild->szBind);
      else  wsprintf((LPSTR)szBuffer,"%u", lpChild->cBind);
      SetOrGetEditArray(hCtl, szBuffer, iAction);
      if (iAction == ACT_TRIGGER) {
         lpChild->fBind = TRUE;
         strncpy(lpChild->szBind, szBuffer, cbINTLEN);
         lpChild->szBind[cbINTLEN-1] = '\0';
      }
      break;

     case IDC_EDIT_ROWSETSIZE:
      if (lpChild->fRowset)
         wsprintf((LPSTR)szBuffer,"%s", lpChild->szRowset);
      else wsprintf((LPSTR)szBuffer,"%u", lpChild->crowRowset);
      SetOrGetEditArray(hCtl, szBuffer, iAction);
      if (iAction == ACT_TRIGGER) {
         lpChild->fRowset = TRUE;
         strncpy(lpChild->szRowset, szBuffer, cbINTLEN);
         lpChild->szRowset[cbINTLEN-1] = '\0';
      }
      break;

     case IDC_MAXCOL:
      if (lpChild->fMaxBind)
         wsprintf((LPSTR)szBuffer, "%s", lpChild->szMaxBind);
      else wsprintf((LPSTR)szBuffer, "%ld", lpChild->crowMaxBind);
      SetOrGetEditArray(hCtl, szBuffer, iAction);
      if (iAction == ACT_TRIGGER) {
         lpChild->fMaxBind = TRUE;
         strncpy(lpChild->szMaxBind, szBuffer, cbINTLEN);
         lpChild->szMaxBind[cbINTLEN-1] = '\0';
      }
      break;

     case IDC_TABLE_RAD_STATISTICS:       // TABLEINFO->STATSTICS
     case IDC_TABLE_RAD_COLUMN:           // TABLEINFO->COLUMNS
     case IDC_TABLE_RAD_PRIV:          // TABLEINFO->PRIVILEGES
     case IDC_TABLE_RAD_PROC:          // TABLEINFO->PROCEDURES
     case IDC_TABLE_RAD_TABLE:            // TABLEINFO->TABLES

      // Initialize the button, or set iAction to its value
      // Hide any fields not related to the button, show fields related

      if( SetOrGetCheck(hCtl, iAction,
                        (iId == (int)lpChild->dwRadioButton)) )
         lpChild->dwOperation = (UWORD) iId;

      if (iAction == ACT_TRIGGER) {
         lpChild->dwGuiFlags |= GUIF_TABLES_RADIO;
         lpChild->dwRadioButton = iId;
         (void) SetHiddenFields(hDlg, iId);
      }
      break;

     case IDC_CHECK_FETCH:
      if (SetOrGetCheck(hCtl, iAction, (int)IS_ALLWFETCH(lpChild)))
         lpChild->dwGuiFlags |= GUIF_ALWAYSFETCH;
      else
         lpChild->dwGuiFlags &= ~GUIF_ALWAYSFETCH;

      break;

     case IDC_CHECK_ASYNC:
      lpChild->fAsync = SetOrGetCheck(hCtl,iAction,lpChild->fAsync);
      break;

     case IDC_TABLEINFO_QUALIFIER:        // TABLEINFO->QUALIFIER
      SetOrGetEditArray(hCtl, lpChild->szQualifier, iAction);
      break;

     case IDC_TABLEINFO_NAME:          // TABLEINFO->NAME
      SetOrGetEditArray(hCtl, lpChild->szTable, iAction);
      break;

     case IDC_TABLEINFO_OWNER:            // TABLEINFO->OWNER
      SetOrGetEditArray(hCtl, lpChild->szUser, iAction);
      break;

     case IDC_TABLEINFO_TYPE:          // TABLEINFO->TYPE
      SetOrGetEditArray(hCtl, lpChild->szType, iAction);
      break;

     case IDC_TABLEINFO_COLNAME:          // TABLEINFO->COLUMN NAME
      SetOrGetEditArray(hCtl, lpChild->szColName, iAction);
      break;

     case IDC_OPTIONLIST:              // OPTIONS->options
      {

         if (iAction == ACT_INIT) {
            InitializeListBox(hDlg,
                              IDC_OPTIONLIST,
                              &dpOptList[0],
                              sizeof(dpOptList) / sizeof(DIALOG_PAIR),
                              IDD_GENERAL_OPTIONS);
         }

         // Get the title and data for the currently-selected list box

         iLbSelection = ListBox_GetCurSel(hCtl);
         dwListData   = ListBox_GetItemData(hCtl, iLbSelection);

         SendMessage(hDlg,WMU_NEWOPTION, iLbSelection,(LPARAM)dwListData);
         SendMessage(hDlg,WMU_SETSUBTEXT,iId, 0);

         break;
      }

   }
}

/*
** SetOrGetCheck      -- set a value based upon a button action, or set a
**                 check button if the action is equal to a value
**                 Also used for radios
**
** Parameters:
**    hCtl     -- control handle
**    iAction     -- ACT_INIT or ACT_TRIGGER
**    bEqual      -- Initialization -- should value be set?
**
** Returns:
**    TRUE if checkbox is now set
*/
INLINE BOOL SetOrGetCheck(
                          HWND   hCtl,
                          int    iAction,
                          BOOL   bEqual)
{
   if (iAction == ACT_INIT) {
      Button_SetCheck(hCtl, bEqual);
      return bEqual;
   }

   if (iAction == ACT_TRIGGER) {
      Button_SetCheck(hCtl, Button_GetCheck(hCtl));
      return Button_GetCheck(hCtl);
   }

   return FALSE;
}

/*
** SetOrGetEditArray -- set the value of an array from an edit control, or
**                set the edit control from the array  (inline)
**
** Parameters:
**    hCtl  -- control
**    lpStr -- string
**    iAction  -- ACT_INIT or ACT_TRIGGER
**
** Notes:   Assumes lpStr is MAXNAME bytes long
*/
INLINE VOID SetOrGetEditArray(
                              HWND  hCtl,
                              UCHAR FAR *lpStr,
                              int      iAction)
{
   if (iAction == ACT_INIT)
      Edit_SetText(hCtl, (LPSTR)lpStr);
   else
      if (iAction == ACT_TRIGGER)
         Edit_GetText(hCtl, (LPSTR)lpStr, MAXNAME - 1);
}

/*
** SetHiddenFields -- show or hide hidden fields in a dialog box, depending
**               upon the action taken.
**
** Parameters:
**    hDlg  -- dialog we are dealing with
**    iAct  -- action
**
*/
BOOL    INTFUNC SetHiddenFields(
                                HWND   hWnd,
                                int    iAct)
{
   HWND  hTTag, hCol;
   HWND  hType, hCTag;

   // Get handles for all of the windows we want to deal with

   hTTag = GetDlgItem(hWnd, IDC_TYPETAG);
   hCol  = GetDlgItem(hWnd, IDC_COLTAG);

   hType = GetDlgItem(hWnd, IDC_TABLEINFO_TYPE);
   hCTag = GetDlgItem(hWnd, IDC_TABLEINFO_COLNAME);

   if (hType) {
      Edit_Enable(hType, (iAct == IDC_TABLE_RAD_TABLE)  ? 1 : 0);
      ShowWindow(hType,  (iAct == IDC_TABLE_RAD_TABLE)  ? SW_SHOW : SW_HIDE);
      ShowWindow(hTTag,  (iAct == IDC_TABLE_RAD_TABLE)  ? SW_SHOW : SW_HIDE);

      Edit_Enable(hCTag, (iAct == IDC_TABLE_RAD_COLUMN) ? 1 : 0);
      ShowWindow(hCol,   (iAct == IDC_TABLE_RAD_COLUMN) ? SW_SHOW : SW_HIDE);
      ShowWindow(hCTag,  (iAct == IDC_TABLE_RAD_COLUMN) ? SW_SHOW : SW_HIDE);

      return TRUE;
   }

   return FALSE;
}

/*
 **   InitializeListBox -- initialize a listbox from a DIALOG_PAIR
 **                     structure.
 **
 **  Parameters:
 **      hWnd     --  window the list box lives in
 **      lbId     --  resource id of the list box
 **      dpOptList   --  pointer to option list structure
 **      iEntries --  number of entries in the list box
 **      iDefId      --  default to select
 **
 **
 **  Returns:  FALSE
 **
 */

BOOL INTFUNC InitializeListBox(
   HWND                    hWnd,
   int                     lbId,
   const DIALOG_PAIR FAR   *dpOptList  ,
   int                     iEntries,
   int                     iDefId)
{

   int            iDlg, iIndex;
   HWND        hOptionBox;
   LPSTR       szDefaultTitle;
   hOptionBox  =  GetDlgItem(hWnd, lbId);

   for (iDlg = 0; iDlg < iEntries; iDlg++) {
      iIndex = ListBox_AddString(hOptionBox,
                                 dpOptList[iDlg].szDlgPairTitle);

      ListBox_SetItemData(hOptionBox,
                          iIndex,
                          dpOptList[iDlg].iDlgPairDlgId);

      if (iDefId == dpOptList[iDlg].iDlgPairDlgId) {
         szDefaultTitle = dpOptList[iDlg].szDlgPairTitle;
      }

   }

   ListBox_SetCurSel(hOptionBox,
                     ListBox_FindStringExact(hOptionBox, 0, szDefaultTitle));

   return FALSE;
}

/*
** InitializeDialogControls   -- Initialize all of the controls in a dialog
**                      from the lpchild structure.   Callback function.
**
** Parameters:
**    hDlg     -- dialog handle
**    lpChild     -- state structure
**
*/

VOID INTFUNC InitializeDialogControls(
                                      HWND   hDlg,
                                      LPCHILD   lpChild)
{
   FARPROC     ControlInstance = MakeProcInstance((FARPROC)InitControlCallback,
                                                  g_hinst);

   EnumChildWindows(hDlg, (WNDENUMPROC)ControlInstance, (LPARAM)lpChild);
   FreeProcInstance(ControlInstance);

}

/*
** InitControlCallback  -- callback function for initializing controls
**
** Parameters:
**    hwndChild   -- child window handle
**    lParam      -- lparam (lpChild from EnumChildProc
*/

BOOL CALLBACK InitControlCallback(
                                  HWND hwndChild,
                                  LPARAM  lParam)
{
   int      iControl= GetDlgCtrlID(hwndChild);

   if (iControl) {
      ControlValue((LPCHILD) lParam,
                   GetParent(hwndChild),
                   hwndChild,
                   iControl,
                   ACT_INIT);

   }
   return TRUE;
}

BOOL INTFUNC DlgProcFilter(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{

   switch (msg) {
     case WM_SETTEXT:
     case WM_NCPAINT:
     case WM_NCACTIVATE:
      SetWindowLong(hDlg, DWL_MSGRESULT,0L);
      return TRUE;
   }
   return FALSE;
}

/*
** AlignToControl -- align a window to a control in a dialog
**
** Parameters:
**    hWnd  -- window to align
**    hParent -- parent dialog
**    iCtlId   -- control Id
**
** Returns: BOOL that moveWindow returns
*/

BOOL INTFUNC AlignToControl(
                            HWND hWnd,
                            HWND hParent,
                            int     iCtlId)
{
   HWND  hwndDrawArea;
   RECT  rcDrawArea, rcChildWnd;
   POINT ptPoint;

   hwndDrawArea = GetDlgItem(hParent, iCtlId);
   GetWindowRect(hwndDrawArea, &rcDrawArea);
   GetWindowRect(hWnd, &rcChildWnd);

   ptPoint.x = rcDrawArea.left;
   ptPoint.y = rcDrawArea.top;

   ScreenToClient(hWnd, &ptPoint);

   return (MoveWindow(  hWnd,
                      ptPoint.x,
                      ptPoint.y,
                      rcChildWnd.right- rcChildWnd.left,
                      rcChildWnd.bottom - rcChildWnd.top,
                      FALSE));
}


FRAME.C

/*--------------------------------------------------------------------------
  Frame.C --- Cursors main window procedure

  Description:
        This sample is spread across four files, each named for the role
        the contained functions play.  Each file header contains a brief
        description of its purpose and the routines it contains.

        FRAME.C contains those functions used to manage the main frame
        window.  The frame window, created by code in MAIN.C, owns the
        MDI client window, the menu bar, and other major parts of the
        interface.  The functions is contains are:

            AdjustMenus    - Enable/disable menu items given current state
            CloseAll       - Close all child windows
            CloseAllEnum   - Window enumeration callback used by CloseAll
            CloseChild     - Close the currently active child window
            CreateChild    - Create a new child window
            DoConnect      - Connect to a host
            DoDisconnect   - Disconnect from a host
            DoFrameMenu    - Process a menu request
            FrameProc      - Process frame window messages

  This code is furnished on an as-is basis as part of the ODBC SDK and is
  intended for example purposes only.

--------------------------------------------------------------------------*/

/* Includes --------------------------------------------------------------*/
#include    "headers.h"

#include   "resource.h"
#include    "crsrdemo.h"

// Constants ---------------------------------------------------------------
#define fDISABLED   (MF_BYCOMMAND | MF_DISABLED | MF_GRAYED)
#define fENABLED    (MF_BYCOMMAND | MF_ENABLED)

// Prototypes --------------------------------------------------------------
void INTFUNC CloseAll(HWND);
void INTFUNC CloseChild(HWND);
HWND INTFUNC CreateChild(HWND);
BOOL INTFUNC DoConnect(HWND);
void INTFUNC DoDisconnect(void);
BOOL INTFUNC DoFrameMenu(HWND, WPARAM, LPARAM);

BOOL CALLBACK CloseAllEnum(HWND, LPARAM);

/* AdjustMenus -------------------------------------------------------------
    Description: Enable/disable appropriate menu items
    --------------------------------------------------------------------------*/
void INTFUNC AdjustMenus(void)
{
   LRESULT lresult;
   BOOL    fMaximized;
   HWND    hwnd;
   HMENU   hmenu;
   UINT    idMenu;
   UINT    fOption;

   lresult = SendMessage(g_hwndClient, WM_MDIGETACTIVE, 0, 0L);

#ifdef WIN32
   hwnd       = (HWND)lresult;
   fMaximized = GetWindowLong(hwnd, GWL_STYLE) & WS_MAXIMIZE;
#else
   hwnd       = (HWND)(LOWORD(lresult));
   fMaximized = HIWORD(lresult);
#endif

   if( hwnd ) {
      LPCHILD lpchild;

      lpchild = (LPCHILD)GetWindowLong(hwnd, 0);

      idMenu = IDM_FETCH + (fMaximized ? 1 : 0);

      if( lpchild->fResultSetExists ) {
         EnableMenuItem(GetMenu(g_hwnd), idMenu, MF_BYPOSITION |
                        MF_ENABLED);
         fOption = fENABLED;
      }
      else {
         EnableMenuItem(GetMenu(g_hwnd), idMenu, MF_BYPOSITION |
                        MF_DISABLED   |
                        MF_GRAYED);
         fOption = fDISABLED;
      }

      hmenu = GetSubMenu(GetMenu(g_hwnd), idMenu);
      EnableMenuItem(hmenu, IDM_FETCH_FIRST, fOption);
      EnableMenuItem(hmenu, IDM_FETCH_PRIOR,  fOption);
      EnableMenuItem(hmenu, IDM_FETCH_NEXT,   fOption);
      EnableMenuItem(hmenu, IDM_FETCH_LAST,   fOption);
      EnableMenuItem(hmenu, IDM_FETCH_ABSOLUTE, fOption);
      EnableMenuItem(hmenu, IDM_FETCH_RELATIVE, fOption);
#if 0
      EnableMenuItem(hmenu, IDM_FETCH_RESUME, fOption);
#endif  //  0
      if( lpchild->dwOperation != OPER_SELECT ) {
         EnableMenuItem(hmenu, (UINT)IDM_FETCH_DELETEROW, fDISABLED);
         EnableMenuItem(hmenu, (UINT)IDM_FETCH_UPDATEROW, fDISABLED);
      }
      else {
         EnableMenuItem(hmenu, (UINT)IDM_FETCH_DELETEROW, fOption);
         EnableMenuItem(hmenu, (UINT)IDM_FETCH_UPDATEROW, fOption);
      }

      idMenu = IDM_FETCHCHILD + (fMaximized ? 1 : 0);
      if( g_fConnected ) {
         EnableMenuItem(GetMenu(g_hwnd), idMenu, MF_BYPOSITION |
                        MF_ENABLED);
         fOption = fENABLED;
      }
      else {
         EnableMenuItem(GetMenu(g_hwnd), idMenu, MF_BYPOSITION  |
                        MF_DISABLED    |
                        MF_GRAYED);
         fOption = fDISABLED;
      }
   }
   else {
      UINT    wCount;

      hmenu = GetMenu(g_hwnd);
      wCount = GetMenuItemCount(hmenu);
      for( idMenu = 0; idMenu < wCount; idMenu++ )
         EnableMenuItem(hmenu, idMenu, MF_BYPOSITION | MF_ENABLED);
   }

   DrawMenuBar(g_hwnd);
   return;
}

/* CloseAll ----------------------------------------------------------------
    Description: Close all child windows
    --------------------------------------------------------------------------*/
void INTFUNC CloseAll(HWND hwnd)
{
   UNREF_PARAM(hwnd);
   if (!g_fConnected)
      return;

   EnumChildWindows(g_hwndClient, CloseAllEnum, 0L);
   return;
}

/* CloseAllEnum ------------------------------------------------------------
    Description: EnumChildWindows procedure, it calls CloseChild for each
                 child window passed
                 --------------------------------------------------------------------------*/
BOOL CALLBACK CloseAllEnum(HWND hwnd, LPARAM lparam)
{
   UNREF_PARAM(lparam);
   if (!GetWindow(hwnd, GW_OWNER))
      CloseChild(hwnd);
   return TRUE;
}

/* CloseChild --------------------------------------------------------------
    Description: Close a child window
    --------------------------------------------------------------------------*/
void INTFUNC CloseChild(HWND hwnd)
{
   // Destroy child window
   FORWARD_WM_MDIDESTROY(g_hwndClient, hwnd, SendMessage);

   // Decrement child count
   g_cChild--;

   // Set appropriate menu bar
   if (!g_cChild)
#ifdef WIN32
      SendMessage(g_hwndClient,WM_MDISETMENU,(WPARAM)g_hmenuFrame,(LPARAM)g_hmenuFrameWindow);
#else
   FORWARD_WM_MDISETMENU(g_hwndClient,
                         0, g_hmenuFrame, g_hmenuFrameWindow, SendMessage);
#endif
   return;
}

/* CreateChild -------------------------------------------------------------
    Description: Create a child window
    --------------------------------------------------------------------------*/
HWND INTFUNC CreateChild(HWND hwnd)
{
   HWND            hwndChild;
   MDICREATESTRUCT mdi;
   int             x, y, cx, cy;

   // Have child fill client area if it is the only child window
   if (!g_cChild) {
      RECT    rc;

      GetClientRect(hwnd, &rc);

      x  = rc.left;
      y  = rc.top;
      cx = rc.right - rc.left;
      cy = rc.bottom - rc.top;
   }

   // Otherwise, accept default placement
   else {
      x  =
         y  =
            cx =
               cy = CW_USEDEFAULT;
   }

   // Increment child count and cursor number
   g_cChild++;
   g_cCursor++;

   // Create child via MDI interface
   mdi.szClass = szCHILDCLASS;
   mdi.szTitle = g_szDSN;
   mdi.hOwner  = g_hinst;
   mdi.x       = x;
   mdi.y       = y;
   mdi.cx      = cx;
   mdi.cy      = cy;
   mdi.style   = 0;
   mdi.lParam  = (LPARAM)NULL;

   hwndChild = FORWARD_WM_MDICREATE(g_hwndClient, ((LPSTR)&mdi), SendMessage);

   if (hwndChild) {
      if (g_cChild == 1)
#ifdef WIN32
         SendMessage(g_hwndClient,WM_MDISETMENU,(WPARAM)g_hmenuChild,(LPARAM)g_hmenuChildWindow);
#else
      FORWARD_WM_MDISETMENU(g_hwndClient,
                            0, g_hmenuChild, g_hmenuChildWindow, SendMessage);
#endif
   }
   else
      g_cChild--;

   return hwndChild;
}

/* DoConnect ---------------------------------------------------------------
    Description: Connect to a data source
    --------------------------------------------------------------------------*/
BOOL INTFUNC DoConnect(HWND hwnd)
{
   HCURSOR   hcur;
   char      sz[cbMAXSQL];
   SWORD     cb;
   SQLRETURN rc;

   // If already connected, close all children and disconnect
   if (g_fConnected) {
      LoadString(g_hinst, IDS_CONWARN, sz, sizeof(sz));
      rc = MessageBox(hwnd,
                      sz, g_szTITLE,
                      MB_ICONQUESTION | MB_OKCANCEL | MB_DEFBUTTON2);
      if (rc == IDCANCEL)
         return FALSE;
      CloseAll(hwnd);
      DoDisconnect();
   }

   hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

   // Call SQLDriverConnect
   rc = SQLDriverConnect(g_hdbc, g_hwnd, NULL, 0,
                         (UCHAR FAR *)sz, sizeof(sz), &cb, SQL_DRIVER_COMPLETE);

   // If successfully connected, get data source attributes
   if (SUCCESS(rc)) {
      LPSTR    lpszS, lpszD;
      SQLHSTMT hstmt;

      // Mark as connected
      g_fConnected = TRUE;

      // Extract and save data source name
      lpszS = _fstrstr(sz, szDSNKEY);

      if (lpszS) {
         lpszS = _fstrstr(sz, szDSNKEY) + lstrlen(szDSNKEY);
         lpszD = g_szDSN;
         while (*lpszS && *lpszS != ';') *lpszD++ = *lpszS++;
         *lpszD = '\0';
      }
      else
         LoadString(g_hinst, IDS_NODSN, g_szDSN, sizeof(g_szDSN));

      // Change to the appropriate menu bar
#ifdef WIN32
      SendMessage(g_hwndClient,WM_MDISETMENU,(WPARAM)g_hmenuFrame,(LPARAM)g_hmenuFrameWindow);
#else
      FORWARD_WM_MDISETMENU(g_hwndClient,
                            0, g_hmenuFrame, g_hmenuFrameWindow, SendMessage);
#endif

      // Get maximum column name length
      if (DBCError(hwnd, SQLGetInfo(g_hdbc, SQL_MAX_COLUMN_NAME_LEN,
                                    &g_cbName, sizeof(g_cbName), NULL)))
         g_cbName = 32;

      // Get identifier quote character
      if (DBCError(hwnd, SQLGetInfo(g_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
                                    g_szQuoteChar, sizeof(g_szQuoteChar), NULL)))
         *g_szQuoteChar = ' ';

      // Determine if async support is available
      rc = SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &hstmt);
      if (!SUCCESS(rc))
         g_fAsyncSupported = FALSE;

      else {
         g_fAsyncSupported = SUCCESS(SQLSetStmtAttr(hstmt,
                                                    SQL_ATTR_ASYNC_ENABLE,
                                                    (SQLPOINTER) 1,
                                                    SQL_IS_INTEGER));
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
      }
   }
   else if (rc != SQL_NO_DATA)
      DBCError(hwnd, rc);

   SetCursor(hcur);
   return SUCCESS(rc);
}

/* DoDisconnect ------------------------------------------------------------
    Description: Drop ODBC connection
    --------------------------------------------------------------------------*/
void INTFUNC DoDisconnect(void)
{
   // Change to the appropriate menu bar
#ifdef WIN32
   SendMessage(g_hwndClient,WM_MDISETMENU,(WPARAM)g_hmenuInit,(LPARAM)g_hmenuInitWindow);
#else
   FORWARD_WM_MDISETMENU(g_hwndClient,
                         0, g_hmenuInit, g_hmenuInitWindow, SendMessage);
#endif

   // If not fully connected, return immediately
   if (!g_fConnected)
      return;

   // Disconnect with ODBC
   SQLDisconnect(g_hdbc);

   // Reset connection related variables
   g_cbName     = 0;
   g_fConnected = FALSE;
   g_szDSN[0]   = '\0';
   g_cChild     = 0;
   g_cCursor    = 0;
   return;
}

/* DoFrameMenu -------------------------------------------------------------
    Description: Respond to a request from the frame window menu
    --------------------------------------------------------------------------*/
BOOL INTFUNC DoFrameMenu(HWND  hwnd, WPARAM  wparam, LPARAM  lparam)
{
   HWND    hwndChild;

   hwndChild = FORWARD_WM_MDIGETACTIVE(g_hwndClient, SendMessage);

   switch (GET_WM_COMMAND_ID(wparam, lparam)) {

     case IDM_STMT_ADDDSN:
      SQLCreateDataSource(hwnd, NULL);
      break;

     case IDM_STMT_DISCONNECT:
      if (g_cChild)
         CloseAll(hwnd);
      DoDisconnect();
      break;

     case IDM_STMT_CONNECT:
      if (!DoConnect(hwnd))
         break;

     case IDM_STMT_NEW:
      hwndChild = CreateChild(hwnd);
      break;

     case IDM_STMT_CLOSE:
      CloseChild(hwndChild);
      break;

     case IDM_STMT_CLOSEALL:
      CloseAll(hwnd);
      break;

     case IDM_STMT_EXIT:
      PostMessage(hwnd, WM_CLOSE, 0, 0L);
      break;

     case IDM_WINDOW_ARRANGE:
      FORWARD_WM_MDIICONARRANGE(g_hwndClient, SendMessage);
      break;

     case IDM_WINDOW_CASCADE:
      FORWARD_WM_MDICASCADE(g_hwndClient, 0, SendMessage);
      break;

     case IDM_WINDOW_TILEH:
     case IDM_WINDOW_TILEV: {
        WPARAM  fTile;

        fTile = (GET_WM_COMMAND_ID(wparam, lparam) == IDM_WINDOW_TILEH
                 ? MDITILE_HORIZONTAL
                 : MDITILE_VERTICAL);
        FORWARD_WM_MDITILE(g_hwndClient, fTile, SendMessage);
        break;
     }

     case IDM_HELP_ABOUT:
      DoDialog(g_hwnd, IDD_ABOUTBOX, AboutDlgProc);
      break;

     case IDM_HELP_HELP:
      WinHelp(g_hwnd, szHELPFILE, HELP_KEY ,(DWORD)(LPTSTR)szKeyword );
break;

     default:
      // Pass unrecognized request to the current child
      SendMessage(hwndChild, WM_COMMAND, wparam, lparam);
      return FALSE;
   }

   // Adjust menu state
   AdjustMenus();
   return TRUE;
}

/* FrameProc ---------------------------------------------------------------
    Description: Frame window procedure
    --------------------------------------------------------------------------*/
LRESULT EXPFUNC FrameProc(HWND    hwnd,
                          UINT   msg,
                          WPARAM wparam,
                          LPARAM lparam)
{
   switch (msg) {

      // Create MDI client window
     case WM_CREATE: {
        CLIENTCREATESTRUCT  client;

        client.hWindowMenu  = g_hmenuInitWindow;
        client.idFirstChild = IDM_FIRSTCHILD;

        g_hwndClient = CreateWindow(szMDICLIENT, NULL,
                                    WS_CHILD        |
                                    WS_CLIPCHILDREN |
                                    WS_VISIBLE,
                                    0, 0, 0, 0, hwnd, (HMENU)1, g_hinst,
                                    &client);
        break;
     }

      // Refresh brushes when colors change
     case WM_SYSCOLORCHANGE:
      if (g_hbrWin)    DeleteObject(g_hbrWin);
      if (g_hbrBtn)    DeleteObject(g_hbrBtn);
      if (g_hbrScroll) DeleteObject(g_hbrScroll);

      g_hbrWin    = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
      g_hbrBtn    = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
      g_hbrScroll = CreateSolidBrush(GetSysColor(COLOR_SCROLLBAR));
      break;

      // Close all children, drop connection, and close frame window
     case WM_CLOSE:
      CloseAll(hwnd);
      DoDisconnect();
      DestroyWindow(hwnd);
      break;

      // Destroy menus not attached to the window and drop ODBC handles
     case WM_DESTROY: {
        HMENU   hmenu;

        hmenu = GetMenu(hwnd);

        if (g_hmenuInit  != hmenu) DestroyMenu(g_hmenuInit);
        if (g_hmenuFrame != hmenu) DestroyMenu(g_hmenuFrame);
        if (g_hmenuChild != hmenu) DestroyMenu(g_hmenuChild);

        if (g_hdbc)
           SQLFreeHandle(SQL_HANDLE_DBC,g_hdbc);

        if (g_henv)
           SQLFreeHandle(SQL_HANDLE_ENV,g_henv);

        g_hwnd = NULL;

        WinHelp(hwnd, szHELPFILE, HELP_QUIT, 0L);
        PostQuitMessage(0);
        break;
     }

      // Pass menu commands to handler routine
     case WM_COMMAND:
      if (DoFrameMenu(hwnd, wparam, lparam))
         break;

      // All other requests go to default MDI frame procedure
     default:
      return DefFrameProc(hwnd, g_hwndClient, msg, wparam, lparam);
   }

   return (LRESULT)NULL;
}


HEADERS.C

/*--------------------------------------------------------------------------
  Headers.c -- Phraseology headers file

  This code is furnished on an as-is basis as part of the ODBC SDK and is
  intended for example purposes only.

--------------------------------------------------------------------------*/

// Includes ----------------------------------------------------------------
#include "headers.h"


HEADERS.H

/*--------------------------------------------------------------------------
   Headers.h -- Phraseology precompiled headers

--------------------------------------------------------------------------*/

// Includes ----------------------------------------------------------------
#define  STRICT
#include <windows.h>
#include <windowsx.h>
#include <dlgs.h>

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "sql.h"
#include "sqlext.h"
#include "odbcinst.h"

#pragma  hdrstop


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by CRSRDEMO.RC
//
#define IDM_STMT                        0
#define IDR_INIT                        1
#define IDM_FETCH                       1
#define IDR_MAIN                        2
#define IDM_FETCHCHILD                  2
#define IDM_WINDOWINIT                  2
#define IDM_WINDOWFRAME                 2
#define IDR_CHILD                       3
#define IDM_WINDOWCHILD                 2
#define IDS_SQL                         16
#define IDS_MSGFMT                      17
#define IDS_CONNECTED                   18
#define IDS_NOROWDELETE                 19
#define IDS_NOROW                       20
#define IDS_NODATAFOUND                 21
#define IDS_STILLEXEC                   22
#define IDS_BADROWSET                   23
#define IDS_BIGROWSET                   24
#define IDS_NULL                        26
#define IDS_ROWDELETED                  27
#define IDS_DELROWDELETE                28
#define IDS_NODATAFETCHED               29
#define IDS_NOROWUPDATE                 30
#define IDS_DELROWUPDATE                31
#define IDS_TABLE                       32
#define IDS_DROPTABLE                   33
#define IDS_INSERTREC                   35
#define IDS_CLEANUP                     36
#define IDS_DATADLG                     37
#define IDS_UNKNOWN                     38
#define IDS_CANCEL                      39
#define IDS_CANNOTUPDATE                40
#define IDS_DELETEROW                   41
#define IDS_NOUPDATE                    42
#define IDS_COLNAME                     43
#define IDS_TOOMANYCOLS                 44
#define IDS_CONWARN                     45
#define IDS_ROWERROR                    46
#define IDS_ERRORROWDELETE              47
#define IDS_ERRORROWUPDATE              48
#define IDD_ABOUTBOX                    100
#define IDC_EDIT1                       101
#define IDD_TABLE_INFO                  101
#define IDC_CHECK1                      102
#define IDD_BIND_OPTIONS                102
#define IDC_RADIO1                      103
#define IDD_OPTION_DIALOG               103
#define IDC_RADIO_MAYBESIMCUR           103
#define IDC_RADIO2                      104
#define IDD_CURSOR_LIB                  104
#define IDC_RADIO_NOSIMCUR              104
#define IDC_RADIO3                      105
#define IDC_RADIO_SIMCUR                105
#define IDC_RADIO4                      106
#define IDC_RADIO5                      107
#define IDC_RADIO6                      108
#define IDC_RADIO7                      109
#define IDC_RADIO8                      110
#define IDD_STATEMENT                   111
#define IDD_ABSOLUTE                    112
#define IDD_RELATIVE                    113
#define IDC_EDIT2                       113
#define IDD_OPTIONS                     114
#define IDD_MAKETABLE1                  115
#define IDC_CHECK2                      115
#define IDD_MAKETABLE2                  116
#define IDC_TEXT1                       116
#define IDC_STATIC1                     117
#define IDD_UPDATEROW                   117
#define IDD_DATADLG                     118
#define IDD_FIND                        119
#define IDC_CHECK3                      120
#define IDD_CONCURRENCY_OPTIONS         120
#define IDC_RADIO_BINDROW               121
#define IDC_RADIO_BINDCOL               122
#define IDD_GENERAL_OPTIONS             122
#define IDC_CHECK_BINDALL               123
#define IDC_EDIT_BIND                   124
#define IDC_RADIO_READONLY              125
#define IDC_RADIO_OPTIMISTIC            126
#define IDC_RADIO_OPTIMVALUE            127
#define IDC_RADIO_LOCKING               128
#define IDC_CHECK_ASYNC                 129
#define IDC_CHECK_FETCH                 130
#define IDC_RADIO_FORWARD               131
#define IDC_RADIO_SNAPSHOT              132
#define IDC_RADIO_KEYSET                133
#define IDC_EDIT_ROWSETSIZE             134
#define IDM_FETCH_FIRST                 200
#define IDM_FETCH_PRIOR                 201
#define IDM_FETCH_NEXT                  202
#define IDM_FETCH_LAST                  203
#define IDM_FETCH_ABSOLUTE              204
#define IDM_FETCH_RELATIVE              205
#define IDM_FETCH_RESUME                206
#define IDM_FETCH_DELETEROW             207
#define IDM_FETCH_UPDATEROW             208
#define IDM_FETCH_GET                   209
#define IDM_STMT_OPTIONS                300
#define IDM_STMT_EXIT                   301
#define IDM_STMT_NEW                    302
#define IDM_STMT_SEND                   303
#define IDM_STMT_CLOSE                  304
#define IDM_STMT_CLOSEALL               305
#define IDM_STMT_CONNECT                306
#define IDM_STMT_ADDDSN                 307
#define IDM_STMT_DISCONNECT             308
#define IDM_STMT_TABLE                  309
#define IDM_STMT_COLUMNS                310
#define IDM_STMT_DRIVER                 311
#define IDM_STMT_CANCEL                 312
#define IDM_WINDOW_CASCADE              400
#define IDM_WINDOW_TILEH                401
#define IDM_WINDOW_TILEV                402
#define IDM_WINDOW_ARRANGE              403
#define IDM_HELP_ABOUT                  404
#define IDM_TEST_MAKETABLE              500
#define IDM_SEEK_EQ                     600
#define IDM_SEEK_LT                     601
#define IDM_SEEK_GT                     602
#define IDM_SEEK_LE                     603
#define IDM_SEEK_GE                     604
#define IDM_FIND_FIRST                  605
#define IDM_FIND_PRIOR                  606
#define IDM_FIND_NEXT                   607
#define IDM_FIND_LAST                   608
#define IDM_FIRSTCHILD                  1000
#define IDC_TABLEINFO_QUALIFIER         1002
#define IDC_TABLEINFO_OWNER             1003
#define IDC_TABLEINFO_NAME              1004
#define IDC_TABLEINFO_TYPE              1005
#define IDC_TYPETAG                     1006
#define IDC_TABLE_RAD_TABLE             1007
#define IDC_TABLE_RAD_PROC              1008
#define IDC_TABLE_RAD_STATISTICS        1009
#define IDC_TABLE_RAD_PRIV              1010
#define IDC_COLTAG                      1011
#define IDC_TABLEINFO_COLNAME           1012
#define IDC_ALWAYSFETCH                 1013
#define IDC_TABLE_RAD_COLUMN            1014
#define IDD_SCROLLING_OPTIONS           1015
#define IDC_OPTIONLIST                  1016
#define IDC_OPTION_WINPOS               1017
#define IDC_OPTIONAREA_TITLE            1018
#define IDC_STATIC_NBIND                1020
#define IDS_NODSN                       1021
#define IDC_MAXCOL                      1022
#define IDM_STMT_TYPE                   1023
#define IDM_HELP_HELP                   1024
#define IDC_RADIO_DYNAMIC               1025
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        1026
#define _APS_NEXT_COMMAND_VALUE         1100
#define _APS_NEXT_CONTROL_VALUE         1023
#define _APS_NEXT_SYMED_VALUE           1200
#endif
#endif

#define IDS_BADNUMERIC          1201


AUTOTEST.H

//*---------------------------------------------------------------------------------
//|  Title:AUTOTEST.H
//|
//|This file contains constants and prototypes required to compile an
//|Auto Test DLL.
//*---------------------------------------------------------------------------------
#ifndef AUTOTEST_DEFS
#define AUTOTEST_DEFS

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include "sql.h"
#include "sqlext.h"

#ifdef __cplusplus
extern "C" {
#endif/* __cplusplus */

extern HINSTANCEhLoadedInst;

//----------------------------------------------------------------------------------
//Defines and Macros
//----------------------------------------------------------------------------------
#define TEST_ABORTED(-1)

#define AUTO_MAX_TEST_NAME35
#define AUTO_MAX_TESTCASE_NAME35
#define AUTO_MAX_TESTDESC_NAME75

#define MAXFLUSH    300
#define MAX_USER_INFO  50
#define MAX_KEYWORD_LEN   149

#ifdef WIN32
#define EXTFUNCDECL_stdcall
#define EXTFUN_stdcall
#else
#define EXTFUNCDECL
#define EXTFUN__export FAR PASCAL
#endif

#define InitTest(lps)\
{ lps->cErrors=0; }
#define AbortTest(lps)\
{ lps->cErrors=TEST_ABORTED; }

#define     AllocateMemory(cb)\
                (GlobalLock(GlobalAlloc((GMEM_FIXED | GMEM_ZEROINIT), (cb))))
#ifdef WIN32
#define     ReleaseMemory(lp)\
 (GlobalUnlock((HGLOBAL)GlobalHandle(lp)), (BOOL)GlobalFree((HGLOBAL)GlobalHandle(lp)))
#else
#define     ReleaseMemory(lp)\
                (GlobalUnlock(((HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(lp))))), \
                (BOOL)GlobalFree(((HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(lp))))))
#endif

#define NumItems(s) (sizeof(s) / sizeof(s[0]))

// Following will access bit number pos in a bit array and return
//TRUE if it is set, FALSE if it is not
#define CQBITS (sizeof(UINT) * 8)
#define getqbit(lpa, pos)\
(lpa[((pos) / CQBITS)] & (1 << ((pos) - (CQBITS * ((pos) / CQBITS)))))
#define GETBIT(p1,p2) getqbit(p1,(p2)-1)

//
// This structure contains the information found in the .INI file for a
//data source.  The filled out structure is in turn passed to AutoTestFunc
//to drive the individual tests.
//
typedef struct tagSERVERINFO {
HWND hwnd;// Output edit window
TCHAR   szLogFile[_MAX_PATH];// Output log file
HENV henv;// .EXE's henv
HDBC hdbc;// .EXE's hdbc
HSTMThstmt;// .EXE's hstmt

// The following items are gathered from the .INI file and may be defined
//via the "Manage Test Sources" menu item from ODBC Test
TCHAR szSource[SQL_MAX_DSN_LENGTH+1];
TCHAR szValidServer0[SQL_MAX_DSN_LENGTH+1];
TCHAR szValidLogin0[MAX_USER_INFO+1];
TCHAR szValidPassword0[MAX_USER_INFO+1];
TCHARszKeywords[MAX_KEYWORD_LEN+1];

// Following are used for run-time
UINT FAR * rglMask;  // Run test mask
int  failed;// Track failures on a test case basis
int  cErrors;// Count of errors
BOOL fDebug;// TRUE if debugging is to be enabled
BOOL fScreen;// TRUE if test output goes to screen
BOOL fLog;// TRUE if test output goes to log
BOOL fIsolate;// TRUE to isolate output
UDWORDvCursorLib;// Value for SQL_ODBC_CURSOR on SQLSetConnectOption
HINSTANCEhLoadedInst;// Instance handle of loaded test

// Following are used for buffering output to edit window
TCHARszBuff[MAXFLUSH];// Hold temporary results
UINTcBuff;// Number of TCHARs in szBuff
} SERVERINFO;
typedef SERVERINFO FAR * lpSERVERINFO;

//----------------------------------------------------------------------------------
// Function prototypes
//----------------------------------------------------------------------------------
BOOL EXTFUNCDECL FAR szLogPrintf(lpSERVERINFO lps, BOOL fForce, LPTSTR szFmt, ...);
int EXTFUNCDECL FAR szMessageBox(HWND hwnd, UINT style, LPTSTR szTitle, LPTSTR szFmt, ...);
LPTSTR EXTFUN GetRCString(HINSTANCE hInst, LPTSTR buf, int cbbuf, UINT ids);

#ifdef __cplusplus
}
#endif

#endif


CUSTOM.C

//*---------------------------------------------------------------------------------
//| Custom Auto Test DLL
//|
//| Title:CUSTOM.C
//|
//| Purpose:
//|This sample Auto Test DLL shows how an ODBC auto test may be written and
//|subsequently run via the ODBC Test application.  To use this DLL:
//|1) Compile the source code via the BUILD.EXE program
//|CUSTOM.CThis shource file with test code
//|CUSTOM.HInclude files with defines, macros, and prototypes
//|CUSTOM.RCResource file for string tables
//|(Note that .H and .RC files are optional in for an Auto Test)
//|2) Start the ODBC Test application
//|3) If you have not done so, choose Tools, Manage Test Sources and define
//|a test source to run against
//|4) Choose Tools, Manage Tests and add the CUSTOM.DLL created in step 1
//|to the list of installed test DLLs
//|5) Choose Tools, Run Tests and select "Custom Auto Test" and your Test Source
//|from step #3
//|For more details, please see the SDK documentation.
//*---------------------------------------------------------------------------------
#include "autotest.h"
#include "custom.h"

//----------------------------------------------------------------------------------
//Defines and macros
//----------------------------------------------------------------------------------
typedef SWORD (FAR PASCAL *TESTCASEFUNC)(HENV FAR *, HDBC FAR *, HSTMT FAR *, lpSERVERINFO);

#define CHECKTEST(lps, exprc, actrc, func) \
{                                                  \
if(!CheckTest(lps, exprc, actrc, (LPSTR)func))  \
return TEST_ABORTED;                         \
}
#define CHECKERRS(sErr)\
if(!sErr) szLogPrintf(lpSrvr, FALSE, "\t\tPassed\r\n\r\n"); \
else szLogPrintf(lpSrvr, FALSE, "\t\t%d errors\r\n\r\n", sErr);

//----------------------------------------------------------------------------------
//Local function prototypes
//----------------------------------------------------------------------------------
SWORD FAR PASCAL DoHelloWorld(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr);
SWORD FAR PASCAL DoDisplayInfoDesc(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr);
SWORD FAR PASCAL DoSimpleConnect(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr);
BOOL FAR PASCAL CheckTest(lpSERVERINFO lps, RETCODE exprc, RETCODE actrc,
LPSTRszFuncName);

//
// This structure is declared to describe the test cases and descriptions
//that this auto test supports.  Note that the strings are stored in
//the resource fork, but could have been hard coded.
//
struct {
UINTuiName;// Test case name
UINTuiDesc;// Test case description
TESTCASEFUNClpTestFunc;// Pointer to function to implement test
} TestCases[] = {
// szNameszDesclpTestFunc
// -------------------------------------------------------------------
idsHelloWorld,idsHelloWorldDesc,DoHelloWorld,
idsDisplayInfo,idsDisplayInfoDesc,DoDisplayInfoDesc,
idsSimpleConnect,idsSimpleConnectDesc,DoSimpleConnect,
};

//**************************************************************************
//***************************  External Interfaces  ************************
//*  These functions are called by ODBC Test to gather run-time information
//**************************************************************************

//*---------------------------------------------------------------------------------
//| AutoTestName:
//|This function is called to give the name of the auto test (which cannot
//|exceed AUTO_MAX_TEST_NAME) as well as the number of test cases which
//|are implemented in this test DLL.
//| Parms:
//|szNameThe name to be displayed
//|pcbTestCasesPointer to count of test cases
//| Returns:
//|TRUE if successful (pcbTestCases set), FALSE for error
//*---------------------------------------------------------------------------------
BOOL EXTFUN AutoTestName(LPSTR szName, UINT FAR * pcbTestCases)
{
GetRCString(hLoadedInst, szName, AUTO_MAX_TEST_NAME, idsTestName);
*pcbTestCases = NumItems(TestCases);

   return TRUE;
}

//*---------------------------------------------------------------------------------
//| AutoTestDesc:
//|This function is called by ODBC Test when a description of a specific
//|test case is required.  The returned name must be no larger than
//|AUTO_MAX_TESTCASE_NAME including the NULL terminator.  The returned
//|description must be no larger than AUTO_MAX_TESTDESC_NAME including the
//|NULL.  iTest will be 1-based index of the test required.
//|
//|Note that iTest will start at 1 and will go to the number of
//|test cases as specified by the AutoTestName function.
//|
//| Parms:
//|iTest1-based index of test case required
//|szNameThe name of the test case
//|szDescA description of the test case
//| Returns:
//|TRUE if successful, FALSE for error
//*---------------------------------------------------------------------------------
BOOL EXTFUN AutoTestDesc(UWORD iTest, LPSTR szName, LPSTR szDesc)
{
// Extra protection should AutoTestName return invalid pcbTestCases
if(iTest > NumItems(TestCases))
return FALSE;

// Use GetRCString to retrieve resource string directly into return
//values
GetRCString(hLoadedInst, szName,
AUTO_MAX_TESTCASE_NAME, TestCases[(iTest-1)].uiName);
GetRCString(hLoadedInst, szDesc,
AUTO_MAX_TESTDESC_NAME, TestCases[(iTest-1)].uiDesc);

   return TRUE;
}

//*---------------------------------------------------------------------------------
//| AutoTestFunc:
//|This function is called to execute a test case selected by the user for
//|execution.  The lpSrvr structure contains the information required for
//|connected (as defined in the chosen Test Source), as well as other
//|usefull information.  See the AUTOTEST.H file for the structure
//|declaration.
//|
//|Use the GETBIT macro to determine which test should be executed.
//|
//| Parms:
//|lpSrvrInformation required for running the test
//| Returns:
//|Nothing
//*---------------------------------------------------------------------------------
void EXTFUN AutoTestFunc(lpSERVERINFO lpSrvr)
{
HENV henv=NULL;
HDBC hdbc=NULL;
HSTMT hstmt0=NULL;
intiDex;
SWORDcErrCnt;// Count errors
charszName[AUTO_MAX_TESTCASE_NAME+6];// Room for NULL and \r\n

// Sets the error count to 0
InitTest(lpSrvr);

// Loop through the count of test cases looking for set bits via GETBIT.
//When a bit is set, that test is to be run.  We've stored the
//function address which will implement the test, so simply call it.
for(iDex=1;  iDex<=NumItems(TestCases);  iDex++)
if(GETBIT(lpSrvr->rglMask, iDex)) {
// Print out title of test
GetRCString(hLoadedInst, szName,
AUTO_MAX_TESTCASE_NAME, TestCases[(iDex-1)].uiName);
szLogPrintf(lpSrvr, FALSE, "%s:\r\n", (LPSTR)szName);

// Call the test case and add errors
cErrCnt =
(*TestCases[(iDex-1)].lpTestFunc)(&henv, &hdbc, &hstmt0, lpSrvr);
if(cErrCnt != TEST_ABORTED)
lpSrvr->cErrors += cErrCnt;
else
goto abort;
}
return;

// When a test must abort, the test case should call the AbortTest
//macro which sets lpSrvr->cErrors to TEST_ABORTED.
abort:
return;
}

//**************************************************************************
//*****************************  Test Cases  *******************************
//*  The following functions implement the tests
//**************************************************************************

//*---------------------------------------------------------------------------------
//| DoHelloWord:
//|This is a simple test which uses the szLogPrintf and szMessageBox
//|functions defined in GATORTST.DLL.
//|
//|Note that this test also simulates an error by returning a count
//|of 1.  This value is then totaled by ODBC Test and displayed as
//|part of the grand total.
//|
//| Returns:
//|Number of Errors or TEST_ABORTED
//*---------------------------------------------------------------------------------
SWORD FAR PASCAL DoHelloWorld(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr)
{
SWORDsErr=1;// Pretend there was 1 error

// The szMessageBox function will display a formatted message via the
//Windows MessageBox function.  This function should not be used
//for standard testing since a good test will run unattended.
szMessageBox(lpSrvr->hwnd,
MB_ICONINFORMATION | MB_OK,
"Hello World",
"This is a sample message.");

// The szLogPrintf function is preferred for output operations.  It will
//format your string using wvsprintf (which has a limit of 2000 characters)
//and log the result both to the output window and to a file per
//user instructions.
szLogPrintf(lpSrvr, FALSE, "\tHello World!!\r\n");

// check for errors
CHECKERRS(sErr);

return sErr;
}

//*---------------------------------------------------------------------------------
//| DoDisplayInfoDesc:
//|This test case will use the szLogPrintf function to dump the contents
//|of the lpSrvr structure.
//|
//| Returns:
//|Number of Errors or TEST_ABORTED
//*---------------------------------------------------------------------------------
SWORD FAR PASCAL DoDisplayInfoDesc(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr)
{
SWORDsErr=0;

#ifndef WIN32
#define szAddress "%04X:%04X\r\n"
#else
#define szAddress "%08X\r\n"
#endif

// The hwnd parameter is the window of style "edit" which is used for output.
//The szLogFile parameter is used for file logging of output.
szLogPrintf(lpSrvr, FALSE,
"\thwnd:\t\t\t\t\t\t\t%04X\r\n", lpSrvr->hwnd);
szLogPrintf(lpSrvr, FALSE,
"\tszLogFile:\t\t\t\t%s\r\n", (LPSTR)lpSrvr->szLogFile);

// Print out address information.  Note that szAddress is conditionaly compiled
//to handle 16 and 32-bit.  It will be concatenated to the format string
//by the compiler to create a file platform correct string.
szLogPrintf(lpSrvr, FALSE,
"\thenv:\t\t\t\t\t\t\t" szAddress,
#ifndef WIN32
HIWORD(lpSrvr->henv), LOWORD(lpSrvr->henv));
#else
lpSrvr->henv);
#endif
szLogPrintf(lpSrvr, FALSE,
"\thdbc:\t\t\t\t\t\t\t" szAddress,
#ifndef WIN32
HIWORD(lpSrvr->hdbc), LOWORD(lpSrvr->hdbc));
#else
lpSrvr->hdbc);
#endif
szLogPrintf(lpSrvr, FALSE,
"\thstmt:\t\t\t\t\t\t\t" szAddress,
#ifndef WIN32
HIWORD(lpSrvr->hstmt), LOWORD(lpSrvr->hstmt));
#else
lpSrvr->hstmt);
#endif

// The following are defined via the Tools, Manage Test Sources dialog in
//the ODBC Test application.
szLogPrintf(lpSrvr, FALSE,
"\tszSource:\t\t\t\t\t%s\r\n", (LPSTR)lpSrvr->szSource);
szLogPrintf(lpSrvr, FALSE,
"\tszValidServer0:\t\t\t\t\t\t%s\r\n", (LPSTR)lpSrvr->szValidServer0);
szLogPrintf(lpSrvr, FALSE,
"\tszValidLogin0:\t\t\t\t\t\t%s\r\n", (LPSTR)lpSrvr->szValidLogin0);
szLogPrintf(lpSrvr, FALSE,
"\tszValidPassword0:\t\t\t%s\r\n", (LPSTR)lpSrvr->szValidPassword0);
szLogPrintf(lpSrvr, FALSE,
"\tszKeywords:\t\t\t%s\r\n", (LPSTR)lpSrvr->szKeywords);

// The following elements describe the run-time environment
szLogPrintf(lpSrvr, FALSE,
"\tcErrors:\t\t\t\t\t\t%d\r\n", (LPSTR)lpSrvr->cErrors);
szLogPrintf(lpSrvr, FALSE,
"\tfDebug:\t\t\t\t\t\t%d\r\n", (LPSTR)lpSrvr->fDebug);
szLogPrintf(lpSrvr, FALSE,
"\tfScreen:\t\t\t\t\t\t%d\r\n", (LPSTR)lpSrvr->fScreen);
szLogPrintf(lpSrvr, FALSE,
"\tfLog:\t\t\t\t\t\t\t\t%d\r\n", (LPSTR)lpSrvr->fLog);
szLogPrintf(lpSrvr, FALSE,
"\tfIsolate:\t\t\t\t\t\t%d\r\n", (LPSTR)lpSrvr->fIsolate);
szLogPrintf(lpSrvr, FALSE,
"\tvCursorLib:\t\t\t\t%lu\r\n", (LPSTR)lpSrvr->vCursorLib);
szLogPrintf(lpSrvr, FALSE,
"\thLoadedInst:\t\t\t%04X\r\n", (LPSTR)lpSrvr->hLoadedInst);

// check for errors
CHECKERRS(sErr);

return sErr;
}

//*---------------------------------------------------------------------------------
//| DoSimpleConnect:
//|This test case will use the information in SERVERINFO to make a connection
//|to the chosen test source.
//|
//| Returns:
//|Number of Errors or TEST_ABORTED
//*---------------------------------------------------------------------------------
SWORD FAR PASCAL DoSimpleConnect(HENV FAR * phenv, HDBC FAR * phdbc,
HSTMT FAR * phstmt, lpSERVERINFO lpSrvr)
{
RETCODErc;
SWORDsErr=0;

// This test will assume that the ODBC handles passed in
//are NULL.  One could have this function do a connection
//and pass the handles to other test functions.
rc = SQLAllocEnv(phenv);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLAllocEnv");

rc = SQLAllocConnect(*phenv, phdbc);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLAllocConnect");

rc = SQLConnect(*phdbc, lpSrvr->szValidServer0, SQL_NTS,
lpSrvr->szValidLogin0, SQL_NTS,
lpSrvr->szValidPassword0, SQL_NTS);
CHECKTEST(lpSrvr,
(RETCODE)((rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
? rc : SQL_SUCCESS),
rc, "SQLConnect");

rc = SQLAllocStmt(*phdbc, phstmt);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLAllocStmt");

rc = SQLFreeStmt(*phstmt, SQL_DROP);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLFreeStmt");

rc = SQLDisconnect(*phdbc);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLDisconnect");

rc = SQLFreeConnect(*phdbc);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLFreeConnect");

rc = SQLFreeEnv(*phenv);
CHECKTEST(lpSrvr, SQL_SUCCESS, rc, "SQLFreeEnv");

// check for errors
CHECKERRS(sErr);

return sErr;
}

//**************************************************************************
//*************************  Utility Functions  ****************************
//*  This section contains internal utility functions
//**************************************************************************

//*---------------------------------------------------------------------------------
//| CheckTest:
//|This function will do a simple comparison of return codes and issue
//|erros on failure.  Use the CHECKTEST macro to invoke.
//|
//| Returns:
//|TRUE if the codes match, FALSE on error
//*---------------------------------------------------------------------------------
BOOL FAR PASCAL CheckTest(lpSERVERINFO lps, RETCODE exprc, RETCODE actrc,
LPSTRszFuncName)
{
if(exprc != actrc) {
szLogPrintf(lps, FALSE, "\t%s failed:\r\n", (LPSTR)szFuncName);
szLogPrintf(lps, FALSE, "\t\tExpected: %d\r\n", exprc);
szLogPrintf(lps, FALSE, "\t\tActual:   %d\r\n", actrc);
return FALSE;
}
else
return TRUE;
}


CUSTOM.H

//*---------------------------------------------------------------------------------
//| Custom Auto Test DLL
//|
//| Title:CUSTOM.H
//|
//| Purpose:
//|This file contains the defines required for the resource strings in CUSTOM.RC.
//|This file is optional for an auto test DLL.
//|
//|Note that user resource strings should start at 30001.
//*---------------------------------------------------------------------------------
#ifndef _CUSTOM_DEFS
#define _CUSTOM_DEFS

#define idsTestName30001
#define idsHelloWorld30002
#define idsHelloWorldDesc30003
#define idsDisplayInfo30004
#define idsDisplayInfoDesc30005
#define idsSimpleConnect30006
#define idsSimpleConnectDesc30007

#endif


DLLSTUB.C

//*------------------------------------------------------------------------
//|File:DLLSTUB.C
//|
//|Purpose:This is a generic module which contains the entry points
//|required to compile a DLL.
//*------------------------------------------------------------------------
#include <windows.h>
#include "autotest.h"

HINSTANCEhLoadedInst;

//-------------------------------------------------------------------------
//For WIN32 programs, there is only one entry point which takes care of
//everything.  For 16 bit apps, however, there are two entry points,
//LibMain which is called upon initialization, and WEP which is the
//windows exit procedure.
//-------------------------------------------------------------------------

#ifdef WIN32
#include <winnt.h>

int EXTFUN TstLibMain(HANDLE hInst, DWORD fdwReason, LPVOID lpReserved)
{
BOOL WINAPI _CRT_INIT(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpReserved);

switch(fdwReason) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
hLoadedInst = hInst;
if(!hLoadedInst || !(_CRT_INIT(hInst, fdwReason, lpReserved)))
return FALSE;
break;

case DLL_PROCESS_DETACH:
case DLL_THREAD_DETACH:
if(!(_CRT_INIT(hInst, fdwReason, lpReserved)))
return FALSE;
break;
}

return TRUE;
}

#else

//*---------------------------------------------------------------------------------
//| LibMain:
//|This entry point will simply take care of our data segment.
//| Parms:
//|inhInstCurrent hInstance (data segment)
//|inwDataSeg
//|incbHeapSize
//|inszCmdLineCommand line arguments
//| Returns:
//|TRUE if successful, FALSE on a failure
//*---------------------------------------------------------------------------------
int EXTFUN LibMain(HINSTANCE hInst, WORD wDataSeg, WORD cbHeapSize, LPSTR szCmdLine)
{
if(cbHeapSize != 0)
UnlockData(0);

hLoadedInst = hInst;

return TRUE;
}

//*---------------------------------------------------------------------------------
//| WEP:
//|This is the windows exit procedure.  We will be passed a flag which
//|tells us if we are being unloaded by a normal Unload call or if
//|the system is shutting down.
//| Parms:
//|intfParmWhat type of unload?
//| Returns:
//|TRUE always
//*---------------------------------------------------------------------------------
int EXTFUN WEP(int fParm)
{
switch (fParm) {
case WEP_SYSTEM_EXIT:
case WEP_FREE_DLL:
default:
return TRUE;
}
}

#endif


QUIKTEST.C

//---------------------------------------------------------------------------------------
//
// PROGRAM: quiktest.c
//        This code is furnished on an as-is basis as part of the ODBC SDK and is
//        intended for example purposes only.
//
//  PURPOSE: This is a Quick Test of the basic functionality of an ODBC driver.
//
//  FUNCTIONS:
//     QuickTest() - performs the quick test focusing on basic functionalities.
//
//--------------------------------------------------------------------------------------------
#pragma warning (disable : 4703)

#define QUIKTEST

#include "autotest.h"
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <wchar.h>
#include <tchar.h>
#include <time.h>
// #include <crtdbg.h>
#include "sql.h"
#include "sqlext.h"
#include "quiktest.h"

//--------------------------------------------------------------------------------------
//  static
//--------------------------------------------------------------------------------------
static TCHAR  vszFile[]= TEXT(__FILE__);

static LPTSTR sz07009= TEXT("07009");
static LPTSTR szIM001= TEXT("IM001");
static LPTSTR szHYC00= TEXT("HYC00");
static LPTSTR szHY009= TEXT("HY009");
static LPTSTR szHY091= TEXT("HY091");
static LPTSTR szHY092= TEXT("HY092");
static LPTSTR szISO= TEXT("ISO 9075");
static LPTSTR szODBC30= TEXT("ODBC 3.0");
static LPTSTR szNotSupported= TEXT("did not return Not supported message");
static LPTSTR szSQLSETCONNECTATTR= TEXT("SQLSetConnectAttr");
static LPTSTR szSQLGETCONNECTATTR= TEXT("SQLGetConnecTAttr");
static LPTSTR szSQLGETINFO= TEXT("SQLGetInfo");
static LPTSTR szSQLGETTYPEINFO= TEXT("SQLGetTypeInfo");
static LPTSTR szSQLALLOCHANDLE= TEXT("SQLAllocHandle");
static LPTSTR szSQLALLOCSTMT= TEXT("SQLAllocStmt");
static LPTSTR szSQLALLOCCONNECT= TEXT("SQLAllocConnect");
static LPTSTR szSQLALLOCENV= TEXT("SQLAllocEnv");
static LPTSTR szSQLSETSTMTATTR= TEXT("SQLSetStmtAttr");
static LPTSTR szSQLGETSTMTATTR= TEXT("SQLGetStmtAttr");
static LPTSTR szSQLSETSTMTOPTION= TEXT("SQLSetStmtOption");
static LPTSTR szSQLGETSTMTOPTION= TEXT("SQLGetStmtOption");
static LPTSTR szSQLSETCONNECTOPTION= TEXT("SQLSetConnectOption");
static LPTSTR szSQLGETCONNECTOPTION= TEXT("SQLGetConnectOption");
static LPTSTR szSQLFETCH= TEXT("SQLFetch");
static LPTSTR szSQLBINDCOL= TEXT("SQLBindCol");
static LPTSTR szSQLFOREIGNKEYS= TEXT("SQLForeignkeys");
static LPTSTR szSQLFREEHANDLE= TEXT("SQLFreeHandle");
static LPTSTR szSQLFREECONNECT= TEXT("SQLFreeConnect");
static LPTSTR szSQLFREEENV= TEXT("SQLFreeEnv");
static LPTSTR szSQLGETDESCREC= TEXT("SQLGetDescRec");
static LPTSTR szSQLSETDESCREC= TEXT("SQLSetDescRec");
static LPTSTR szSQLGETDESCFIELD= TEXT("SQLGetDescField");
static LPTSTR szSQLSETDESCFIELD= TEXT("SQLSetDescField");
static LPTSTR szSQLCOPYDESC= TEXT("SQLCopyDesc");
static LPTSTR szSQLSETENVATTR= TEXT("SQLSetEnvAttr");
static LPTSTR szSQLGETENVATTR= TEXT("SQLGetEnvAttr");
static LPTSTR szSQLFREESTMT= TEXT("SQLFreeStmt");
static LPTSTR szSQLENDTRAN= TEXT("SQLEndTran");
static LPTSTR szSQLGETCURSORNAME= TEXT("SQLGetCursorName");
static LPTSTR szSQLEXECDIRECT= TEXT("SQLExecDirect");
static LPTSTR szSQLPUTDATA= TEXT("SQLPutData");
static LPTSTR szSQLPARAMDATA= TEXT("SQLParamData");
static LPTSTR szSQLROWCOUNT= TEXT("SQLRowCount");
static LPTSTR szSQLSETPARAM= TEXT("SQLSetParam");
static LPTSTR szSQLBINDPARAMETER= TEXT("SQLBindParameter");
static LPTSTR szSQLBINDPARAM= TEXT("SQLBindParam");
static LPTSTR szSQLPREPARE= TEXT("SQLPrepare");
static LPTSTR szSQLEXECUTE= TEXT("SQLExecute");
static LPTSTR szSQLCOLATTRIBUTES= TEXT("SQLColAttributes");
static LPTSTR szSQLGETDATA= TEXT("SQLGetData");
static LPTSTR szSQLCOLUMNS= TEXT("SQLColumns");
static LPTSTR szSQLGETFUNCTIONS= TEXT("SQLGetFunctions");
static LPTSTR szSQLDRIVERCONNECT= TEXT("SQLDriverConnect");
static LPTSTR szSQLDISCONNECT= TEXT("SQLDisconnect");
static LPTSTR szSQLCONNECT= TEXT("SQLConnect");
static LPTSTR szSQLCANCEL= TEXT("SQLCancel");
static LPTSTR szSQLDESCRIBECOL= TEXT("SQLDescribeCol");
static LPTSTR szSQLTABLES= TEXT("SQLTables");
static LPTSTR szSQLSPECIALCOLUMNS= TEXT("SQLSpecialColumns");
static LPTSTR szSQLDESCRIBEPARAM= TEXT("SQLDescribeParam");
static LPTSTR szSQLNUMPARAMS= TEXT("SQLNumParams");
static LPTSTR szSQLPARAMOPTIONS= TEXT("SQLParamOptions");
static LPTSTR szSQLPRIMARYKEYS= TEXT("SQLPrimaryKeys");
static LPTSTR szSQLEXTENDEDFETCH= TEXT("SQLExtendedFetch");
static LPTSTR szSELECTSTAR= TEXT("select * from %s");
static LPTSTR szQUICKREBIND= TEXT("Quick Rebind");
static LPTSTR szSQLBROWSECONNECT= TEXT("SQLBrowseConnect");
static LPTSTR szBLANKLINE= TEXT(" ");
static LPTSTR szTAB= TEXT("\t");
static LPTSTR szSQLNUMRESULTCOLS= TEXT("SQLNumResultCols");
static LPTSTR szSQLSETSCROLLOPTIONS= TEXT("SQLSetScrollOptions");
static LPTSTR szSQLFETCHSCROLL= TEXT("SQLFetchScroll");
static LPTSTR szSQLSTATISTICS= TEXT("SQLStatistics");
static LPTSTR szSQLCLOSECURSOR= TEXT("SQLCloseCursor");
static LPTSTR szSQLGETDIAGREC= TEXT("SQLGetDiagRec");
static LPTSTR szSQLGETDIAGFIELD= TEXT("SQLGetDiagField");
static LPTSTR szRETDATA= TEXT("returned data");
static LPTSTR szINCORRECTOUTLEN= TEXT("incorrect outlen");
static LPTSTR szSQLNATIVESQL= TEXT("SQLNativeSql");
static LPTSTR szSQLNATIVESQLW= TEXT("SQLNativeSqlW");
static LPTSTR szSQLCONNECTW= TEXT("SQLConnectW");
static LPTSTR szSQLTABLESA= TEXT("SQLTablesA");
static LPTSTR szSQLTABLESW= TEXT("SQLTablesW");
static LPTSTR szSQLCOLUMNSA= TEXT("SQLColumnsA");
static LPTSTR szSQLCOLUMNSW= TEXT("SQLColumnsW");
static LPTSTR szSQLEXECDIRECTA= TEXT("SQLExecDirectA");
static LPTSTR szSQLEXECDIRECTW= TEXT("SQLExecDirectW");

LPTSTR iTestNames[]={
TEXT("Test Connection Options"),
TEXT("Test Statement Options"),
TEXT("Test SQLGetCursorName"),
TEXT("Test Data"),
TEXT("Test SQLNumResultCols"),
TEXT("Test Meta-Data"),
TEXT("Test Searched Query"),
TEXT("Test Large Query"),
TEXT("Test SQLTables"),
TEXT("Test SQLStatistics"),
TEXT("Test SQLSpecialColumns"),
TEXT("Test 'Like' Query"),
TEXT("Test SQLForeignKeys"),
TEXT("Test SQLBrowseConnect"),
TEXT("Test SQLDataSources"),
TEXT("Test SQLDrivers"),
TEXT("Test SQLMoreResults"),
TEXT("Test SQLNativeSQL"),
TEXT("Test SQLDescribeParam"),
TEXT("Test SQLNumParams"),
TEXT("Test SQLParamOptions"),
TEXT("Test SQLPrimaryKeys"),
TEXT("Test SQLProcedures"),
TEXT("Test SQLTablePrivileges"),
TEXT("Test SQLColumnPrivileges"),
TEXT("Test SQLSetScrollOptions"),
TEXT("Test SQLExtendedFetch"),
TEXT("Test SQL_OJ_CAPABILITIES"),
TEXT("Test SQLSetConnectAttr"),
TEXT("Test SQLSetStmtAttr"),
TEXT("Test Threading"),
TEXT("Test GetDescField"),
TEXT("Test SetDescField"),
TEXT("Test GetDescRec"),
TEXT("Test SetDescRec"),
TEXT("Test CopyDesc"),
TEXT("Test Descriptor Defaults"),
TEXT("Test Usage of Descriptor"),
TEXT("Test Environment Attributes"),
TEXT("Test SQLEndTran"),
TEXT("Test SQLBindParam"),
TEXT("Test Quick Rebind"),
TEXT("Test SQLFetchScroll"),
TEXT("Test GetDiagRec"),
TEXT("Test GetDiagField"),
TEXT("Test MixedAnsiUnicode")
};

typedef struct tagHANDLES
{
SWORDfHandleType;
SQLHANDLEhHandle;

} HANDLES, *lpHANDLES;

SUPPORTOPTINFO OptionList[] = {SQL_SO_FORWARD_ONLY, SQL_SCROLL_FORWARD_ONLY,
SQL_SO_KEYSET_DRIVEN, SQL_SCROLL_KEYSET_DRIVEN,
SQL_SO_DYNAMIC, SQL_SCROLL_DYNAMIC};

SUPPORTCONCURINFO ConcurList[] = {SQL_SCCO_READ_ONLY, SQL_CONCUR_READ_ONLY,
SQL_SCCO_LOCK, SQL_CONCUR_LOCK,
SQL_SCCO_OPT_TIMESTAMP, SQL_CONCUR_TIMESTAMP,
SQL_SCCO_OPT_VALUES, SQL_CONCUR_VALUES};

/*--------------------------------------------------------------------------------------*/
/* globals */
/*--------------------------------------------------------------------------------------*/
lpSERVERINFOlpSI = NULL;
UWORDfBindParameter=FALSE,
fDiagRecSupported=FALSE,
fDiagFieldSupported=FALSE,
uDriverODBCVer=0;
UWORDguwRowCount=0;// Count of rows in the automaketable

/* these are globals so that the error functions can access them without
needing to have them passed to every error check */
static HENV            henv=SQL_NULL_HENV;
static HDBC            hdbc=SQL_NULL_HDBC;
static HSTMT           hstmt=SQL_NULL_HSTMT;
static SQLHDESC        hdesc=SQL_NULL_HDESC;

//globals for test descriptors
TIMESTAMP_STRUCT tsval= {1955, 12, 31, 23, 59, 59, 999};
SQLINTEGER cbtsval=sizeof(TIMESTAMP_STRUCT);
SQLINTEGER cbValueMax, cbValue, swBindOffset=128;
SQLUINTEGER RowsProcessed;
SQLSMALLINT DescBuf[MAX_DESC_BUF];
SQLUSMALLINT StatusArray[STATUS_ARRAY_SIZE];
SQLTCHAR buf[MAX_STRING_SIZE];
SQLTCHAR szParamName[MAX_STRING_SIZE]=TEXT("Parameter Name");

struct tagDescType {
SQLUSMALLINTuwDescType;
SQLUSMALLINTuwTypeMask;
TCHAR szDescName[4];
} DescTypes[] = {
SQL_ATTR_APP_ROW_DESC,DESC_ARD,TEXT("ARD"),
SQL_ATTR_APP_PARAM_DESC,DESC_APD,TEXT("APD"),
SQL_ATTR_IMP_ROW_DESC,DESC_IRD,TEXT("IRD"),
SQL_ATTR_IMP_PARAM_DESC,DESC_IPD,TEXT("IPD")
};

struct tagDescInfo {
SQLSMALLINT uwDescField;
LPTSTR szDescFieldName;
BOOL fHeader;
SQLSMALLINT fGettable;// Can be retrieved for given descriptor types
SQLSMALLINT fSettable;// Can be set for given descriptor types
SQLSMALLINT fDefault;// Has a default value for the descriptor types
SQLINTEGER NewValue;
SQLINTEGER DefaultVal;
SQLSMALLINT cbValue;
SQLINTEGER size;
} rgDescInfo[] ={
// Test expects all header fields to be listed first.  The TYPE field must be the first record field listed.
// size should be set to 0 for string fields, since we don't know up front what to set it to
// Field IDField NameHeaderGettableSettablefDefaultNew ValueDefault ValueData SizeData Type

//Header fields
SQL_DESC_COUNT,TEXT("SQL_DESC_COUNT"),TRUE,DESC_ALL,DESC_MOST,DESC_MOST,1,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_ALLOC_TYPE,TEXT("SQL_DESC_ALLOC_TYPE"),TRUE,DESC_ALL,DESC_NONE,DESC_ALL,0,SQL_DESC_ALLOC_AUTO,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_ARRAY_SIZE,TEXT("SQL_DESC_ARRAY_SIZE"),TRUE,DESC_AD,DESC_AD,DESC_AD,STATUS_ARRAY_SIZE,1,sizeof(SQLUINTEGER),SQL_IS_UINTEGER,
SQL_DESC_ARRAY_STATUS_PTR,TEXT("SQL_DESC_ARRAY_STATUS_PTR"),TRUE,DESC_ALL,DESC_ALL,DESC_ALL,(SQLINTEGER)(StatusArray),(SQLINTEGER)NULL,sizeof(SQLPOINTER),SQL_IS_POINTER,
SQL_DESC_BIND_OFFSET_PTR,TEXT("SQL_DESC_BIND_OFFSET_PTR"),TRUE,DESC_AD,DESC_AD,DESC_AD,(SQLINTEGER)(&swBindOffset),(SQLINTEGER)NULL,sizeof(SQLPOINTER),SQL_IS_POINTER,
SQL_DESC_BIND_TYPE,TEXT("SQL_DESC_BIND_TYPE"),TRUE,DESC_AD,DESC_AD,DESC_AD,BIND_SIZE,0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
//SQL_DESC_OUT_OF_LINE_HEAP,TEXT("SQL_DESC_OUT_OF_LINE_HEAP"),TRUE,DESC_AD,DESC_AD,(SQLINTEGER)(StatusArray),sizeof(SQLINTEGER),SQL_IS_POINTER,
//SQL_DESC_OUT_OF_LINE_HEAP_OCTET_LENGTH,TEXT("SQL_DESC_OUT_OF_LINE_HEAP_OCTET_LENGTH"),TRUE, DESC_AD,DESC_AD,sizeof(StatusArray),sizeof(SQLINTEGER),SQL_IS_NOT_POINTER,
SQL_DESC_ROWS_PROCESSED_PTR,TEXT("SQL_DESC_ROWS_PROCESSED_PTR"),TRUE,DESC_ID,DESC_ID,DESC_ID,(SQLINTEGER)(&RowsProcessed),(SQLINTEGER)NULL,sizeof(SQLPOINTER),SQL_IS_POINTER,

// Record fields
SQL_DESC_TYPE,TEXT("SQL_DESC_TYPE"), FALSE,DESC_ALL,DESC_MOST,DESC_AD,SQL_DATETIME,SQL_C_DEFAULT,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_DATETIME_INTERVAL_CODE,TEXT("SQL_DESC_DATETIME_INTERVAL_CODE"),FALSE,DESC_ALL, DESC_MOST,DESC_NONE,SQL_CODE_TIMESTAMP,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_CONCISE_TYPE,TEXT("SQL_DESC_CONCISE_TYPE"),FALSE,DESC_ALL,DESC_MOST,DESC_AD,SQL_TYPE_TIMESTAMP,SQL_C_DEFAULT,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_AUTO_UNIQUE_VALUE,TEXT("SQL_DESC_AUTO_UNIQUE_VALUE"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,0,0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
//SQL_DESC_BIND_OUT_OF_LINE,TEXT("SQL_DESC_BIND_OUT_OF_LINE"),FALSE,DESC_AD,DESC_AD,SQL_TRUE,sizeof(SQLSMALLINT),SQL_IS_NOT_POINTER,
SQL_DESC_BASE_COLUMN_NAME,TEXT("SQL_DESC_BASE_COLUMN_NAME"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_CASE_SENSITIVE,TEXT("SQL_DESC_CASE_SENSITIVE"),FALSE,DESC_ID,DESC_NONE,DESC_NONE,0,0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
SQL_DESC_CATALOG_NAME,TEXT("SQL_DESC_CATALOG_NAME"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_DATETIME_INTERVAL_PRECISION, TEXT("SQL_DESC_DATETIME_INTERVAL_PRECISION"),FALSE, DESC_ALL,DESC_MOST,DESC_NONE,3,0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
SQL_DESC_DISPLAY_SIZE,TEXT("SQL_DESC_DISPLAY_SIZE"),FALSE,DESC_IRD,DESC_NONE, DESC_NONE,0,0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
SQL_DESC_FIXED_PREC_SCALE,TEXT("SQL_DESC_FIXED_PREC_SCALE"),FALSE,DESC_ID,DESC_NONE,DESC_NONE,0,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_LABEL,TEXT("SQL_DESC_LABEL"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_LENGTH,TEXT("SQL_DESC_LENGTH"),FALSE,DESC_ALL,DESC_MOST,DESC_NONE,23,0,sizeof(SQLUINTEGER),SQL_IS_UINTEGER,
SQL_DESC_LITERAL_PREFIX,TEXT("SQL_DESC_LITERAL_PREFIX"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_LITERAL_SUFFIX,TEXT("SQL_DESC_LITERAL_SUFFIX"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_LOCAL_TYPE_NAME,TEXT("SQL_DESC_LOCAL_TYPE_NAME"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_NAME,TEXT("SQL_DESC_NAME"),FALSE,DESC_ID,DESC_IPD,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_NULLABLE,TEXT("SQL_DESC_NULLABLE"),FALSE,DESC_ID,DESC_NONE,DESC_NONE,0,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_OCTET_LENGTH,TEXT("SQL_DESC_OCTET_LENGTH"),FALSE,DESC_ALL,DESC_MOST,DESC_NONE,sizeof(tsval),0,sizeof(SQLINTEGER),SQL_IS_INTEGER,
SQL_DESC_OCTET_LENGTH_PTR,TEXT("SQL_DESC_OCTET_LENGTH_PTR"),FALSE,DESC_AD,DESC_AD,DESC_AD,(SQLINTEGER)(&cbtsval),(SQLINTEGER)NULL,sizeof(SQLINTEGER),SQL_IS_POINTER,
SQL_DESC_PARAMETER_TYPE,TEXT("SQL_DESC_PARAMETER_TYPE"),FALSE,DESC_IPD,DESC_IPD,DESC_IPD,SQL_PARAM_OUTPUT,SQL_PARAM_INPUT,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_PRECISION,TEXT("SQL_DESC_PRECISION"),FALSE,DESC_ALL,DESC_MOST,DESC_NONE,23,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_SCALE,TEXT("SQL_DESC_SCALE"),FALSE,DESC_ALL,DESC_MOST,DESC_NONE,3,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_SCHEMA_NAME,TEXT("SQL_DESC_SCHEMA_NAME"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_SEARCHABLE,TEXT("SQL_DESC_SEARCHABLE"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,0,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_TABLE_NAME,TEXT("SQL_DESC_TABLE_NAME"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_TYPE_NAME,TEXT("SQL_DESC_TYPE_NAME"),FALSE,DESC_ID,DESC_NONE,DESC_NONE,(SQLINTEGER)szParamName,0,sizeof(SQLCHAR),0,
SQL_DESC_UNSIGNED,TEXT("SQL_DESC_UNSIGNED"),FALSE,DESC_ID,DESC_NONE,DESC_NONE,0,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_UPDATABLE,TEXT("SQL_DESC_UPDATABLE"),FALSE,DESC_IRD,DESC_NONE,DESC_NONE,0,0,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_INDICATOR_PTR,TEXT("SQL_DESC_INDICATOR_PTR"),FALSE,DESC_AD,DESC_AD,DESC_AD,SQL_NULL_DATA,(SQLINTEGER)NULL,sizeof(SQLPOINTER),SQL_IS_POINTER,
SQL_DESC_UNNAMED,TEXT("SQL_DESC_UNNAMED"),FALSE,DESC_ID,DESC_IPD,DESC_NONE,SQL_UNNAMED,SQL_NAMED,sizeof(SQLSMALLINT),SQL_IS_SMALLINT,
SQL_DESC_DATA_PTR,TEXT("SQL_DESC_DATA_PTR"),FALSE,DESC_AD,DESC_MOST,DESC_AD,(SQLINTEGER)&tsval,(SQLINTEGER)NULL,sizeof(SQLPOINTER),SQL_IS_POINTER
 };

struct tagDIAGINFO {
SQLSMALLINT uwDescField;
LPTSTR szDescFieldName;
SWORD irecRecNumber;
} rgDiagInfo[] ={
//SQL_DIAG_CURSOR_ROW_COUNT,TEXT("SQL_DIAG_CURSOR_ROW_COUNT"),0,
//SQL_DIAG_DYNAMIC_FUNCTION,TEXT("SQL_DIAG_DYNAMIC_FUNCTION"),0,
//SQL_DIAG_DYNAMIC_FUNCTION_CODE,TEXT("SQL_DIAG_DYNAMIC_FUNCTION_CODE"),0,
SQL_DIAG_NUMBER,TEXT("SQL_DIAG_NUMBER"),0,
SQL_DIAG_RETURNCODE,TEXT("SQL_DIAG_RETURNCODE"),0,
//SQL_DIAG_ROW_NUMBER,TEXT("SQL_DIAG_ROW_NUMBER"),0,
//SQL_DIAG_COLUMN_NUMBER,TEXT("SQL_DIAG_COLUMN_NUMBER"),1,
//SQL_DIAG_ROW_COUNT,TEXT("SQL_DIAG_ROW_COUNT"),0,
SQL_DIAG_SQLSTATE,TEXT("SQL_DIAG_SQLSTATE"),1,
SQL_DIAG_NATIVE,TEXT("SQL_DIAG_NATIVE"),1,
SQL_DIAG_MESSAGE_TEXT,TEXT("SQL_DIAG_MESSAGE_TEXT"),1,
SQL_DIAG_CLASS_ORIGIN,TEXT("SQL_DIAG_CLASS_ORIGIN"),1,
SQL_DIAG_SUBCLASS_ORIGIN,TEXT("SQL_DIAG_SUBCLASS_ORIGIN"),1,
//SQL_DIAG_CONNECTION_NAME,TEXT("SQL_DIAG_CONNECTION_NAME"),1,
SQL_DIAG_SERVER_NAME,TEXT("SQL_DIAG_SERVER_NAME"),1,
};

BOOLg_f3XDriver=FALSE;

//the insert statement bufferto hold the insert statement
TCHAR szInsertStmt[MAX_STRING_SIZE];

//-----------------------------------------------------------------------
//      Function:               Supported
//-----------------------------------------------------------------------

_inline UWORD  PASCAL Supported(UWORD uwSQLFunc)
{
UWORDfLevel2=FALSE;
RETCODErc=SQL_SUCCESS;

rc = SQLGetFunctions(hdbc, uwSQLFunc,&fLevel2);
RETCHECK(SQL_SUCCESS, rc,szSQLGETFUNCTIONS);

return(fLevel2);

} //Supported()

/*------------------------------------------------------------------------------
 *Function:CheckDataResults - To check data returned by Fetch calls
 *Checks data at (row, col) in result set against (row, col) in table
 * ----------------------------------------------------------------------------*/
BOOL CheckDataResults(SWORD row, SWORD iTableCol, FIELDINFO*  pField, DSTRUCT* pDataStr,
 SDWORD cbValue, TCHAR* rgbValue)
{
if((pField ->nullable)&& (cbValue<1))
{
if(cbValue == SQL_NULL_DATA && pDataStr[iTableCol].cb == SQL_NULL_DATA)
return TRUE;
return FALSE;
}

return CmpODBCtoCHAR(pDataStr, rgbValue, pField->wSQLType, iTableCol);
}

/*------------------------------------------------------------------------------
/Function:BinToHex
/Purpose:Render a buffer contents as a hexidecimal string.
/*----------------------------------------------------------------------------*/

void PASCAL BinToHex(PTR rgbValue, SWORD cbValue, LPTSTR szHexString)
{
UWORD ib;
LPTSTR pch;
TCHAR rgchDigits[] = TEXT("0123456789ABCDEF");

lstrcpy(szHexString, TEXT("0x"));
if(cbValue > (MAX_STRING_SIZE - 3)/2 || cbValue < 0)
cbValue = (MAX_STRING_SIZE - 3)/2;

pch = &szHexString[lstrlen(szHexString)];
for(ib=0; ib < cbValue; ib++)
{
*pch++ = rgchDigits[((LPBYTE)rgbValue)[ib] >> 4];
*pch++ = rgchDigits[((LPBYTE)rgbValue)[ib] & 0xF];
}
*pch = TEXT('\0');

} /* BinToHex() */

/*-----------------------------------------------------------------------*/
/*      Function:               SearchForError                           */
/*-----------------------------------------------------------------------*/

_inline int PASCAL SearchForError(HENV henv, HDBC hdbc,HSTMT hstmt,LPTSTR szFindState)
{
TCHAR buf[MAX_STRING_SIZE];
RETCODE rc;
TCHAR szState[6];
int found = FALSE;

for(rc = SQLError(henv, hdbc, hstmt, szState, NULL, buf, MAX_STRING_SIZE, NULL)
; !found && (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
; rc = SQLError(henv, NULL, NULL, szState, NULL, buf, MAX_STRING_SIZE, NULL))
{
found = lstrcmp(szState, szFindState) == 0;
}

return(found);

} /* SearchForError() */

/*-----------------------------------------------------------------------*/
/*      Function:               szWrite                                  */
/*-----------------------------------------------------------------------*/

_inline VOID WINAPI szWrite(LPTSTR szStr, BOOL fNewLine)
{
szLogPrintf(lpSI, FALSE, szStr);
if(fNewLine)
szLogPrintf(lpSI, FALSE, TEXT("\r\n"));
}

/*-----------------------------------------------------------------------*/
/*      Function:               GetErrors                         */
/*-----------------------------------------------------------------------*/

VOID PASCAL GetErrors(HENV henv,HDBC hdbc,HSTMT hstmt)
{
TCHAR buf[MAX_ERROR_SIZE];
TCHAR largebuf[COMBINED_SIZE];
TCHAR szState[100];
RETCODE rc=SQL_SUCCESS;

for(rc = SQLError(henv, hdbc, hstmt, szState, NULL, buf, MAX_ERROR_SIZE, NULL)
; RC_SUCCESSFUL(rc)
; rc = SQLError(henv, NULL, NULL, szState, NULL, buf, MAX_ERROR_SIZE, NULL))
{
wsprintf(largebuf,TEXT("\t\t\tState: %s"), szState);
szWrite(largebuf, TRUE);
wsprintf(largebuf,TEXT("\t\t\tError: %s"), buf);
szWrite(largebuf, TRUE);
}

} /* GetErrors() */

/*-----------------------------------------------------------------------*/
/*      Function:               GetDiagRecs                         */
/*-----------------------------------------------------------------------*/

VOID PASCAL GetDiagRecs(SWORD fHandleType,SQLHANDLE hHandle)
{
TCHARszMessageText[XLARGEBUFF]=TEXT(""),
szBuff[XLARGEBUFF]=TEXT("");
TCHARszSQLState[100]=TEXT("");
RETCODErc=SQL_SUCCESS;
SWORDirecRecNumber=0;
SDWORDfNativeError=0;
SWORDcbBufferLength=sizeof(szMessageText),
cbTextLength=0;
SDWORDsdNumRecs=0;

if (fDiagFieldSupported)
{
/* Get number of diag, records */
rc = SQLGetDiagField(fHandleType, hHandle,0,SQL_DIAG_NUMBER,
&sdNumRecs,sizeof(sdNumRecs),NULL);
}

for(irecRecNumber=1;(irecRecNumber <= sdNumRecs) && RC_SUCCESSFUL(rc);irecRecNumber++)
{
rc = SQLGetDiagRec(fHandleType, hHandle, irecRecNumber,szSQLState,&fNativeError,
szMessageText,cbBufferLength,&cbTextLength);

if (RC_SUCCESSFUL(rc))
{
wsprintf(szBuff,TEXT("\t\t\tState: %s"), szSQLState);
szWrite(szBuff, TRUE);
wsprintf(szBuff,TEXT("\t\t\tError: %s"), szMessageText);
szWrite(szBuff, TRUE);
}

}

} /* GetErrors() */

/*-----------------------------------------------------------------------*/
/*      Function:               DisplayAllErrors                         */
/*-----------------------------------------------------------------------*/

void  PASCAL DisplayAllErrors()
{
if (!fDiagRecSupported)
{
GetErrors(henv,NULL,NULL);
GetErrors(NULL,hdbc,NULL);
GetErrors(NULL,NULL,hstmt);
}
else
{
GetDiagRecs(SQL_HANDLE_ENV,henv);
GetDiagRecs(SQL_HANDLE_DBC,hdbc);
GetDiagRecs(SQL_HANDLE_STMT,hstmt);
GetDiagRecs(SQL_HANDLE_DESC,hdesc);
}

} /* DisplayAllErrors() */

/*-----------------------------------------------------------------------*/
/*      Function:               RetcodeToTCHAR                            */
/*-----------------------------------------------------------------------*/

LPTSTR  PASCAL RetcodeToTCHAR(RETCODE retcode, LPTSTR buf)
{
switch (retcode)
{
case SQL_SUCCESS:
lstrcpy (buf,TEXT("SQL_SUCCESS"));
break;
case SQL_ERROR:
lstrcpy (buf,TEXT("SQL_ERROR"));
break;
case SQL_SUCCESS_WITH_INFO:
lstrcpy (buf,TEXT("SQL_SUCCESS_WITH_INFO"));
break;
case SQL_NO_DATA_FOUND:
lstrcpy (buf,TEXT("SQL_NO_DATA_FOUND"));
break;
case SQL_NEED_DATA:
lstrcpy (buf,TEXT("SQL_NEED_DATA"));
break;
case SQL_INVALID_HANDLE:
lstrcpy (buf,TEXT("SQL_INVALID_HANDLE"));
break;
case SQL_STILL_EXECUTING:
lstrcpy (buf,TEXT("SQL_STILL_EXECUTING"));
break;
default:
lstrcpy(buf,TEXT("UNKNOWN rc"));
}

return buf;
}

//-----------------------------------------------------------------------
//      Function:               ReturnCheck
//-----------------------------------------------------------------------

int  PASCAL ReturnCheck(RETCODE retExpected, RETCODE retReceived, LPTSTR szFunction,
LPTSTR szFile, int iLine,BOOL fDiag)
{
TCHAR buf[MAX_STRING_SIZE];

if(retExpected == retReceived)
return TRUE;

szWrite(TEXT(""), TRUE);

szWrite(TEXT("\t\t\t"), FALSE);
szWrite(szFunction, TRUE);

szWrite(TEXT("\t\t\tExpected: "), FALSE);
szWrite(RetcodeToTCHAR(retExpected, buf), TRUE);

szWrite(TEXT("\t\t\tReceived: "), FALSE);
szWrite(RetcodeToTCHAR(retReceived, buf), TRUE);

if (!fDiag)
DisplayAllErrors();

wsprintf(buf,TEXT("\t\t\t%s: %d"), szFile, iLine);
szWrite(buf, TRUE);

szWrite(TEXT("\t\t\t --------  "), TRUE);

lpSI->failed++;

return FALSE;
}

//-----------------------------------------------------------------------
//      Function:               ErrsHandle
//-----------------------------------------------------------------------

int  PASCAL ErrsHandle(SWORD fHandleType,SQLHANDLE hHandle, RETCODE retExpected,
RETCODE retReceived, LPTSTR szFunction, LPTSTR szFile, int iLine,BOOL fDiag)
{
TCHAR buf[MAX_STRING_SIZE];

if(retExpected == retReceived)
return TRUE;

szWrite(TEXT(""), TRUE);

szWrite(TEXT("\t\t\t"), FALSE);
szWrite(szFunction, TRUE);

szWrite(TEXT("\t\t\tExpected: "), FALSE);
szWrite(RetcodeToTCHAR(retExpected, buf), TRUE);

szWrite(TEXT("\t\t\tReceived: "), FALSE);
szWrite(RetcodeToTCHAR(retReceived, buf), TRUE);

if (!fDiag)
GetDiagRecs(fHandleType,hHandle);

wsprintf(buf,TEXT("\t\t\t%s: %d"), szFile, iLine);
szWrite(buf, TRUE);

szWrite(TEXT("\t\t\t --------  "), TRUE);

lpSI->failed++;

return FALSE;
}

/*------------------------------------------------------------------------------
/Function:FindError
/Purpose:Find the expected error message. Display a failure if not found.
/*----------------------------------------------------------------------------*/

BOOL PASCAL FindError(SWORD fHandleType, LPTSTR szFindState)
{
SQLHANDLE handle = SQL_NULL_HANDLE;
SWORD irec;
TCHAR szErrorMsg[SQL_MAX_MESSAGE_LENGTH];
RETCODE rc;
TCHAR szState[6];

switch(fHandleType)
{
case SQL_HANDLE_ENV:
handle = henv;
break;

case SQL_HANDLE_DBC:
handle = hdbc;
break;

case SQL_HANDLE_STMT:
handle = hstmt;
break;

case SQL_HANDLE_DESC:
if(!Supported(SQL_API_SQLGETDIAGREC))
return TRUE;

handle = hdesc;
break;

default:
DISPLAYERROR(TEXT("FindError"), TEXT("invalid fHandleType"));
return FALSE;
}

for(irec = 1
; rc = SQLGetDiagRec(fHandleType, handle, irec, szState, NULL, szErrorMsg, SQL_MAX_MESSAGE_LENGTH, NULL)
, RC_SUCCESSFUL(rc)
; irec++)
{
if(lstrcmp(szState, szFindState) == 0)
return TRUE;
}
return FALSE;

} /* FindError() */

/*------------------------------------------------------------------------------
 *Function:CompareWithExpected
 *Checks data in rgbValue against expected value given row, col, and rgFields.
 *Posts an error if mismatch
 * ----------------------------------------------------------------------------*/
void CompareWithExpected(PTR rgbValue, SDWORD * pcbValue, UWORD row, UWORD icol, FIELDINFO  *rgFields)
{
LPTSTR   pch=NULL;
TCHAR szExpBuf[MAX_STRING_SIZE]=TEXT("");
TCHAR * stopstr;

pch = qtMakeData(row-1, icol-1, &rgFields[icol-1], szExpBuf);

// Field is not READ ONLY, compare the value we inserted
if(pch)
{
 // Where row and column match and field is nullable, a NULL exists
if (icol-1 == row-1 && rgFields[icol-1].nullable)
{
// Post an error if not SQL_NULL_DATA
if (*pcbValue != SQL_NULL_DATA)
DISPLAYERROR(TEXT("Simulate BindCol"),TEXT("Didn't return SQL_NULL_DATA"));
}
else
{
BOOL bCompare;
TCHAR * pstr=szExpBuf;// Pointer to start of expected string

switch(rgFields[icol-1].wSQLType)
{
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
// Compare the data values as long // tcsncmp
bCompare=(_ttol(rgbValue) == _ttol(pstr));
break;
case SQL_NUMERIC:
case SQL_DECIMAL:
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
// Compare the data values as doubles strtod
bCompare=(_tcstod(rgbValue, &stopstr) == _tcstod(pstr, &stopstr));
break;
case SQL_DATE:
case SQL_TIME:
case SQL_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
pstr=szExpBuf+lstrlen(TEXT("{ts '"));
// Replace second ' with '\0''
_tcstok(pstr, TEXT("'"));
if (lstrlen(rgbValue) > lstrlen(TEXT("1994-10-10 10:00:00")))
lstrcat(pstr, TEXT(".000"));
bCompare=(!_tcsncmp(rgbValue, pstr, rgFields[icol-1].precision));

break;
default:
bCompare=(!_tcsncmp(rgbValue, pstr, lstrlen(pstr)));
}

// Compare the data values as char
if (!bCompare)
{
DISPLAYERROR(TEXT("Simulate BindCol"),TEXT("Returned data didn't match"));
szWrite(TEXT("\t\t\tExpected: "), FALSE);
szWrite(pstr, TRUE);
szWrite(TEXT("\t\t\tReceived: "), FALSE);
szWrite(rgbValue, TRUE);
}
}
}
} //CompareWithExpected

//-----------------------------------------------------------------------
//      Function:               ResetHstmt
//-----------------------------------------------------------------------
void ResetHstmt(SQLHSTMT * phstmt)
{
SQLRETURN rc;

// Free and reallocate stmt handle to clean up
rc=SQLFreeHandle(SQL_HANDLE_STMT, *phstmt);
ERRSHANDLE(SQL_HANDLE_STMT, *phstmt, SQL_SUCCESS, rc, szSQLFREESTMT);
rc=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, phstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCHANDLE);
}

/*-----------------------------------------------------------------------*/
/*      Function:               GetSomeData                              */
/*-----------------------------------------------------------------------*/

VOID PASCAL GetSomeData(HSTMT lhstmt, LPTSTR szTableName,BOOL fBindCol,
FIELDINFO  rgField)
{
RETCODErc=SQL_SUCCESS;
SDWORDcbValue=0;
TCHARszQuery[XLARGEBUFF]=TEXT(""),
szBuff[XLARGEBUFF]=TEXT("");
TCHARszErrMsg[MAX_STRING_SIZE]=TEXT(""),
szDataItem[XLARGEBUFF]=TEXT("");
LPTSTR   pch=NULL;

pch = qtMakeData(0, 0,&rgField, szDataItem);

wsprintf(szQuery,szSELECTSTAR,szTableName);
rc=SQLExecDirect(lhstmt,szQuery,SQL_NTS);
//RETCHECK(SQL_SUCCESS,rc,szSQLEXECDIRECT);

if (fBindCol)
{
rc = SQLBindCol(lhstmt, 1, SQL_C_TCHAR, szBuff, sizeof(szBuff),&cbValue);
//RETCHECK(SQL_SUCCESS, rc, szSQLBINDCOL);
}

rc = SQLFetch(lhstmt);
//RETCHECK(SQL_SUCCESS, rc,szSQLFETCH);

if (!fBindCol)
{
rc = SQLGetData(lhstmt,1, SQL_C_TCHAR,szBuff, sizeof(szBuff), NULL);
//RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
}

lstrcmp(szDataItem,szBuff);

} /* GetSomeData() */

/*-----------------------------------------------------------------------*/
/*      Function:               SelectFromTable                          */
/*-----------------------------------------------------------------------*/

_inline RETCODE PASCAL SelectFromTable(QTSTRUCT *lpqt)
{
RETCODErc=SQL_SUCCESS;
TCHARszQuery[LARGEBUFF];

wsprintf(szQuery,TEXT("select %s from %s"),
//wsprintf(szQuery,TEXT("select %s from %s order by %s"),
lpqt->szColNames, lpqt->szTableName/*, lpqt->szColNames*/);

rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

return(rc);

} /* SelectFromTable() */

/*-----------------------------------------------------------------------*/
/*      Function:               SelectFromTableFetch                     */
/*-----------------------------------------------------------------------*/

_inline RETCODE PASCAL SelectFromTableFetch(QTSTRUCT *lpqt, TCHAR* szColName)
{
RETCODErc=SQL_SUCCESS;
TCHARszQuery[LARGEBUFF];

wsprintf(szQuery,TEXT("select %s from %s order by %s"),
lpqt->szColNames, lpqt->szTableName, szColName);

rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
if((SQL_SUCCESS!=rc) && (SQL_SUCCESS_WITH_INFO!=rc)){
RETCHECK(SQL_SUCCESS_WITH_INFO, rc,szSQLEXECDIRECT);
return SQL_NO_DATA;
}

return SQL_SUCCESS;
} /* SelectFromTableFetch() */

//-----------------------------------------------------------------------
//      Function:               AllocHstmt
//-----------------------------------------------------------------------

_inline BOOL PASCAL AllocHstmt()
{
BOOLfFailed=FALSE;
RETCODErc=SQL_SUCCESS;

if (Supported(SQL_API_SQLALLOCHANDLE))
{
rc = SQLAllocHandle(SQL_HANDLE_STMT,hdbc, &hstmt);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLALLOCHANDLE))
fFailed=TRUE;
}
else
{
rc = SQLAllocStmt(hdbc, &hstmt);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLALLOCSTMT))
fFailed=TRUE;
}

return(fFailed);

} //AllocHstmt()

//-----------------------------------------------------------------------
//      Function:               AllocHdbc
//-----------------------------------------------------------------------

_inline BOOL PASCAL AllocHdbc(HDBC *phdbc)
{
BOOLfFailed=FALSE;
RETCODErc=SQL_SUCCESS;

if (Supported(SQL_API_SQLALLOCHANDLE))
{
rc = SQLAllocHandle(SQL_HANDLE_DBC,henv, phdbc);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLALLOCHANDLE))
fFailed=TRUE;
}
else
{
rc = SQLAllocConnect(henv, phdbc);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLALLOCCONNECT))
fFailed=TRUE;
}

return(fFailed);

} //AllocHdbc()

//-----------------------------------------------------------------------
//      Function:AllocHenv
//-----------------------------------------------------------------------

_inline BOOL PASCAL PASCAL AllocHenv()
{
BOOLfFailed=FALSE;
RETCODErc=SQL_SUCCESS;

rc = SQLAllocEnv(&henv);
if (!RETCHECK(SQL_SUCCESS, rc,szSQLALLOCENV))
fFailed=TRUE;

return(fFailed);

} //AllocHenv()

//-----------------------------------------------------------------------
//      Function:               SetStatementAttributes
//-----------------------------------------------------------------------

_inline BOOL PASCAL SetStatementAttributes(UWORD fAttr, PTR rgbValue)
{
RETCODErc=SQL_SUCCESS;
TCHARszAPI[MEDBUFF];
BOOLfFailed=FALSE;

if (Supported(SQL_API_SQLSETSTMTATTR))
{
rc = SQLSetStmtAttr(hstmt,(SDWORD)fAttr,rgbValue,sizeof(UDWORD));
lstrcpy(szAPI,szSQLSETSTMTATTR);
}
else
{
rc = SQLSetStmtOption(hstmt,fAttr,(UDWORD)rgbValue);
lstrcpy(szAPI,szSQLSETSTMTOPTION);
}

if (rc != SQL_SUCCESS)
{
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS, rc,szAPI);

fFailed=TRUE;
}

return(fFailed);

} //SetStatementAttributes()

//-----------------------------------------------------------------------
//      Function:               SetConnectionAttributes
//-----------------------------------------------------------------------

_inline BOOL PASCAL SetConnectionAttributes(HDBC lhdbc,UWORD fAttr, UDWORD rgbValue)
{
RETCODErc=SQL_SUCCESS;
TCHARszAPI[MEDBUFF];
BOOLfFailed=FALSE;

if (Supported(SQL_API_SQLSETCONNECTATTR))
{
rc = SQLSetConnectAttr(lhdbc,(SDWORD)fAttr,(PTR)rgbValue,sizeof(UDWORD));
lstrcpy(szAPI,szSQLSETCONNECTATTR);
}
else
{
rc = SQLSetConnectOption(lhdbc,fAttr,rgbValue);
lstrcpy(szAPI,szSQLSETCONNECTOPTION);
}

if (rc != SQL_SUCCESS)
{
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS, rc,szAPI);

fFailed=TRUE;
}

return(fFailed);

} //SetConnectionAttributes()

//-----------------------------------------------------------------------
//      Function:               FreeStmt
//-----------------------------------------------------------------------

_inline BOOL PASCAL FreeStmt(UWORD fOption)
{
RETCODErc=SQL_SUCCESS;

if (Supported(SQL_API_SQLFREEHANDLE) && (fOption == SQL_DROP))
{
rc = SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);
}
else
{
rc = SQLFreeStmt(hstmt, fOption);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);
}

return(RC_NOTSUCCESSFUL(rc));

} //FreeStmt()

//-----------------------------------------------------------------------
//      Function:               DropHdbc
//-----------------------------------------------------------------------

VOID PASCAL DropHdbc(HDBC hdbc)
{
RETCODE rc=SQL_SUCCESS;

if (Supported(SQL_API_SQLFREEHANDLE))
{
rc = SQLFreeHandle(SQL_HANDLE_DBC,hdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);
}
else
{
rc = SQLFreeConnect(hdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLFREECONNECT);
}

} //DropHdbc()

//-----------------------------------------------------------------------
//      Function:               DropHenv
//-----------------------------------------------------------------------

_inline VOID PASCAL DropHenv()
{
RETCODE rc=SQL_SUCCESS;

if (Supported(SQL_API_SQLFREEHANDLE))
{
rc = SQLFreeHandle(SQL_HANDLE_ENV,henv);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);
}
else
{
rc = SQLFreeEnv(henv);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEENV);
}

} //DropHenv()

//-----------------------------------------------------------------------
//      Function:               ClearErrorQueue
//-----------------------------------------------------------------------

VOID PASCAL ClearErrorQueue()
{
TCHAR szErrMsg[MAX_STRING_SIZE];
TCHAR szState[6];

/* Eat up the SQL_SUCCESS_WITH_INFO error record */
while (RC_SUCCESSFUL(SQLError(NULL, hdbc,NULL, szState, NULL,
szErrMsg, sizeof(szErrMsg), NULL)))
;
}

//-----------------------------------------------------------------------
//      Function:               Is3XDriver
//-----------------------------------------------------------------------

BOOL PASCAL Is3XDriver(HDBC hdbc,QTSTRUCT *lpqt)
{
RETCODErc=SQL_SUCCESS;
LPTSTRpch=NULL;
BOOLf3XDriver=FALSE;

rc = SQLGetInfo(hdbc, SQL_DRIVER_ODBC_VER, lpqt->buf, MAX_STRING_SIZE, NULL);
if(RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO))
{
pch = _tcstok(lpqt->buf,TEXT("."));

if (!_tcsnccmp(pch,TEXT("02"), 2))
{
pch = _tcstok(NULL,TEXT("."));

if(!_tcsnccmp(pch,TEXT("00"), 2))
DISPLAYERROR(szSQLGETINFO,TEXT("Driver returned ODBC version 2.00 which is no longer supported.  Please upgrade to 2.01"));

}
else if (!_tcsnccmp(pch,TEXT("03"), 2))
f3XDriver=TRUE;
}

uDriverODBCVer=_ttoi(lpqt->buf);

return(f3XDriver);

} //Is3XDriver()

//-----------------------------------------------------------------------
//      Function:               Connect
//-----------------------------------------------------------------------

BOOL  PASCAL Connect(lpSERVERINFO lpSI,QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
SDWORD sdwAppVer;

/* The signal for test to use it's
own allocated handles, is if the a server name is passed in.  If it
is not, the test should use the hdbc passed in */

/* ****************** Allocate Env ************************* */
if (AllocHenv())
goto ConnectErrorRet;

/* ****************** Set Application Version ************************* */
rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,
SQL_IS_UINTEGER);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLSETENVATTR))
goto ConnectErrorRet;

/* ****************** Allocate Connection ************************* */

rc = SQLAllocConnect(henv, &hdbc);
if(!RETCHECK(SQL_SUCCESS, rc, szSQLALLOCCONNECT))
goto ConnectErrorRet;

/* ****************** Set Connection Attributes ************************* */
rc = SQLSetConnectOption(hdbc,SQL_ODBC_CURSORS,lpSI->vCursorLib);

if(!FindError(SQL_HANDLE_DBC,szHYC00))
if (!RETCHECK(SQL_SUCCESS, rc,szSQLSETCONNECTOPTION))
goto ConnectErrorRet;

/* create a DriverConnect string */
wsprintf(lpqt->sz, TEXT("dsn=%s;uid=%s;pwd=%s;"),
lpSI->szValidServer0,
lpSI->szValidLogin0,
lpSI->szValidPassword0);

/* since SQL_DRIVER_COMPLETE is used, it is possible that the
driver will bring up a dialog asking for more information. */
rc = SQLDriverConnect(hdbc, lpSI->hwnd, lpqt->sz,
SQL_NTS, lpqt->buf, MAX_STRING_SIZE, NULL, SQL_DRIVER_COMPLETE);

if (RC_NOTSUCCESSFUL(rc))
{
if (!RETCHECK(SQL_SUCCESS, rc,szSQLDRIVERCONNECT))
goto ConnectErrorRet;
}
else
ClearErrorQueue();

rc = SQLDisconnect(hdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLDISCONNECT);

rc = SQLConnect(hdbc, lpSI->szValidServer0, SQL_NTS,
lpSI->szValidLogin0, SQL_NTS,
lpSI->szValidPassword0, SQL_NTS);

if (RC_SUCCESSFUL(rc))
ClearErrorQueue();
else if (!RETCHECK(SQL_SUCCESS, rc,szSQLCONNECT))
goto ConnectErrorRet;

// Set the global 3.0 driver flag
g_f3XDriver=Is3XDriver(hdbc,lpqt);

// We're now connected, check application version and adjust expected error
// states appropriately.
rc = SQLGetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, &sdwAppVer, SQL_IS_UINTEGER, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETENVATTR);

if (RC_SUCCESSFUL(rc) && (SQL_OV_ODBC2 == sdwAppVer))
{
lstrcpy(szHY009, TEXT("S1009"));
lstrcpy(szHYC00, TEXT("S1C00"));
}

return(FALSE);

ConnectErrorRet:

return(TRUE);

} //Connect()

//-----------------------------------------------------------------------
//      Function:               IgnoreType
//-----------------------------------------------------------------------

BOOL  PASCAL IgnoreType(LPTSTR szType,SWORD wSQLType)
{
BOOL fIgnore=FALSE;

/* If a line below is compiled in, that type will not be used in creating
 the table.  (useful for when one specific type is causing problems in
 a particular test, this way the test can be run for all other types)
*/

if (!lstrcmp(szType,TEXT("LONG RAW")) ||
 !lstrcmp(szType,TEXT("LONG VARTCHAR")) ||
  !lstrcmp(szType,TEXT("LONG VARTCHAR FOR BIT DATA")))
{
 fIgnore=TRUE;
}

//if (wSQLType != SQL_CHAR)
//fIgnore=TRUE;

return(fIgnore);

} //IgnoreType()

//-----------------------------------------------------------------------
//      Function:               CheckConformanceLevel
//-----------------------------------------------------------------------

UWORD  PASCAL CheckConformanceLevel()
{
RETCODE rc=SQL_SUCCESS;
UWORDfIndex=0;
SWORDcb=0;

rc = SQLGetInfo(hdbc, SQL_ODBC_SQL_CONFORMANCE, &fIndex,
sizeof (fIndex), &cb);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

if(fIndex != 0 && fIndex != 1 && fIndex != 2) {
/* one of these two values should always be returned */
DISPLAYERROR(szSQLGETINFO,TEXT("SQL_ODBC_API_CONFORMANCE - invalid value"));
}

return(fIndex);

} //CheckConformanceLevel()

/*-----------------------------------------------------------------------*/
/*      Function:               TestConnectionOptions */
/*-----------------------------------------------------------------------*/

void  PASCAL TestConnectionOptions()
{
RETCODErc=SQL_SUCCESS;
SDWORDsdw,
cbValue=0;
BOOLfConnectOption=TRUE;
TCHARszAPI[MEDBUFF];
HDBClhdbc=NULL;

rc=SQLAllocConnect(henv,&lhdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCCONNECT);

SetConnectionAttributes(lhdbc,SQL_ACCESS_MODE,SQL_MODE_READ_WRITE);

SetConnectionAttributes(lhdbc,SQL_AUTOCOMMIT,TRUE);

if (SetConnectionAttributes(lhdbc,SQL_LOGIN_TIMEOUT,TEST_CONNECT_OPTION))
fConnectOption = FALSE;

if (Supported(SQL_API_SQLGETCONNECTATTR))
{
rc = SQLGetConnectAttr(lhdbc, SQL_LOGIN_TIMEOUT, (PTR)&sdw,0,&cbValue);
lstrcpy(szAPI,szSQLGETCONNECTATTR);
}
else
{
rc = SQLGetConnectOption(lhdbc, SQL_LOGIN_TIMEOUT, &sdw);
lstrcpy(szAPI,szSQLSETCONNECTOPTION);
}

if(rc != SQL_SUCCESS)
{
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS, rc,szAPI);
}
else
{
if(sdw != TEST_CONNECT_OPTION && fConnectOption)
DISPLAYERROR(szAPI,TEXT("SQL_LOGIN_TIMEOUT returned incorrect value"));
}

rc=SQLFreeConnect(lhdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLFREECONNECT);

} //TestConnectionOptions()

/*-----------------------------------------------------------------------*/
/*      Function:            CanAllocHdesc        */
/*-----------------------------------------------------------------------*/

BOOL PASCAL CanAllocHdesc()
{
BOOL fSupported=FALSE;

/* Allocate an hdesc: */
if(Supported(SQL_API_SQLALLOCHANDLE))
fSupported = RC_SUCCESSFUL(SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc));

return(fSupported);

}

/*-----------------------------------------------------------------------*/
/*      Function:            SetErrorCondition    */
/*-----------------------------------------------------------------------*/

VOID PASCAL SetErrorCondition(SWORD fHandleType)
{
RETCODE rc=SQL_SUCCESS;

/* The following are used to produce "(S1)HY009" SQL State */
switch (fHandleType)
{
case SQL_HANDLE_ENV:
rc = SQLAllocConnect(henv, NULL);
break;
case SQL_HANDLE_DBC:
rc = SQLAllocStmt(hdbc, NULL);
break;
case SQL_HANDLE_STMT:
rc = SQLExecDirect(hstmt, NULL, SQL_NTS);// Produces a DM error
break;
case SQL_HANDLE_DESC:
rc = SQLGetDescField(hdesc, 0, SQL_DESC_AUTO_UNIQUE_VALUE, NULL, 0, NULL);
break;
}

} //SetErrorCondition()

VOID PASCAL CheckData(SWORD fHandleType,UWORD uwDescField, PTR rgbValue,LPTSTR szDataSourceName)
{
TCHARszBuff[MEDBUFF]=TEXT("");
LPTSTRpszExpState=szHY009;

if (fHandleType == SQL_HANDLE_DESC)
pszExpState=szHY091;

switch(uwDescField)
{
case SQL_DIAG_RETURNCODE:
{
RETCODE rcRec=*(RETCODE *)rgbValue;

if (rcRec!= SQL_ERROR)
{
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_RETURN_CODE\tReceived: %d\tExpected: %d"),rcRec,-1);
}
}
break;

case SQL_DIAG_SQLSTATE:
{
if (lstrcmp((LPTSTR)rgbValue,pszExpState))
{
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_SQLSTATE\tReceived: %s\tExpected: %s"),(LPTSTR)rgbValue,pszExpState);
}
}
break;
case SQL_DIAG_CLASS_ORIGIN:
{
if (lstrcmp((LPTSTR)rgbValue,szISO))
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_CLASS_ORIGIN\tReceived: %s\tExpected: %s"),(LPTSTR)rgbValue,szISO);
}
break;
case SQL_DIAG_SUBCLASS_ORIGIN:
{
if (lstrcmp((LPTSTR)rgbValue,szISO))
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_CLASS_ORIGIN\tReceived: %s\tExpected: %s"),(LPTSTR)rgbValue,szISO);
}
break;
case SQL_DIAG_NUMBER:
{
SDWORD cRecs=*(SDWORD *)rgbValue;
if (cRecs != 1)
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_NUMBER\tReceived: %d\tExpected: %d"),cRecs,1);
}
break;
case SQL_DIAG_SERVER_NAME:
{
switch(fHandleType)
{
case SQL_HANDLE_DBC:
case SQL_HANDLE_STMT:
if (lstrcmp((LPTSTR)rgbValue,szDataSourceName))
wsprintf(szBuff,TEXT("\t\t\tSQL_DIAG_SERVER_NAME\tReceived: %s\tExpected: %s"),(LPTSTR)rgbValue,szDataSourceName);
break;
}
}
break;
default:
break;

}

if (*szBuff)
DISPLAYERROR(szSQLGETDIAGREC,szBuff);

} /* CheckData() */

/*------------------------------------------------------------------------------
/Function:TestDiagRec
/Purpose:For each type of handle, generate an error. If SQLGetDiagField
/is not supported, verify the appropriate DM error is returned.
/Otherwise, verify SQLGetDiagField returns the expected error
/information. If SQLGetDiagRec is not supported, verify the
/appropriate DM error is returned. Otherwise, verify
/SQLGetDiagField returns the expected error information. If
/SQLGetDiagRec is supported, compare its output with that of
/SQLGetDiagField.
/*----------------------------------------------------------------------------*/

void PASCAL TestDiagRec()
{
SQLRETURNrcFunction,
rc=SQL_SUCCESS;
SWORDcbErrorMsg;
SDWORDfNativeError;
UWORDiHandle=0,
iField=0,
cHandles;

SDWORD*pfNativeError = NULL;
LPTSTRpchMessageText = NULL;

TCHARszDataSourceName[MAX_STRING_SIZE] = TEXT("");
TCHARszState[SQL_SQLSTATE_SIZE+1];
TCHARszErrorMsg[SQL_MAX_MESSAGE_LENGTH];
TCHARszAPI[MEDBUFF]=TEXT("");
TCHARszBuff[MEDBUFF]=TEXT("");

LPTSTRpszExpState=szHY009;
RETCODErcExp=SQL_SUCCESS;

HANDLES rgHandles[] =
{
SQL_HANDLE_ENV, henv,
SQL_HANDLE_DBC, hdbc,
SQL_HANDLE_STMT, hstmt,
SQL_HANDLE_DESC, hdesc,
};

cHandles=sizeof(rgHandles)/sizeof(HANDLES);

/* Call SQLGetInfo to get values to compare: */
rcFunction = SQLGetInfo(hdbc, SQL_DATA_SOURCE_NAME, szDataSourceName, MAX_STRING_SIZE, NULL);
RETCHECK(SQL_SUCCESS, rcFunction, szSQLGETINFO);

/* Allocate an hdesc: */
if(Supported(SQL_API_SQLALLOCHANDLE) && g_f3XDriver)
{
rcFunction = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);
RETCHECK(SQL_SUCCESS, rcFunction, szSQLALLOCHANDLE);

// Update the array entry for the hdesc
rgHandles[3].hHandle=hdesc;

}
else
cHandles--;

for (iHandle=0; iHandle < cHandles; iHandle++)
{

SetErrorCondition(rgHandles[iHandle].fHandleType);

rc = SQLGetDiagRec(rgHandles[iHandle].fHandleType, rgHandles[iHandle].hHandle,
1,szState,&fNativeError, szErrorMsg,
SQL_MAX_MESSAGE_LENGTH, &cbErrorMsg);

if (rgHandles[iHandle].fHandleType == SQL_HANDLE_DESC)
pszExpState=szHY091;

rcExp=SQL_SUCCESS;

if(fDiagRecSupported)
{

DIAGRETCHECK(rcExp, rc, szSQLGETDIAGREC);
if (RC_SUCCESSFUL(rc) && lstrcmp(szState,pszExpState))
{
wsprintf(szBuff,TEXT("\t\t\tReceived: %s\tExpected: %s"),szState,pszExpState);
DISPLAYERROR(szSQLGETDIAGREC,szBuff);
}
}
else
{
if(!FindError(rgHandles[iHandle].fHandleType, szIM001))
DISPLAYERROR(szSQLGETDIAGREC, szNotSupported);
}

}

/* Free the hdesc: */
if (hdesc)
{
rcFunction = SQLFreeHandle(SQL_HANDLE_DESC, hdesc);
RETCHECK(rcExp, rcFunction, szSQLFREEHANDLE);
hdesc = SQL_NULL_HDESC;
}

//ExitTestDiagRec

FreeStmt(SQL_CLOSE);

} /* TestDiagRec() */

/*------------------------------------------------------------------------------
/Function:TestDiagField
/Purpose:For each type of handle, generate an error. If SQLGetDiagField
/is not supported, verify the appropriate DM error is returned.
/Otherwise, verify SQLGetDiagField returns the expected error
/information. If SQLGetDiagRec is not supported, verify the
/appropriate DM error is returned. Otherwise, verify
/SQLGetDiagField returns the expected error information. If
/SQLGetDiagRec is supported, compare its output with that of
/SQLGetDiagField.
/*----------------------------------------------------------------------------*/

void PASCAL TestDiagField()
{
SQLRETURNrcFunction,
rc=SQL_SUCCESS;
SWORDcbValue=0;
UWORDiHandle=0,
iField=0,
cHandles;

TCHARszDataSourceName[MAX_STRING_SIZE] = TEXT("");
TCHARrgbValue[SQL_MAX_MESSAGE_LENGTH];
TCHARszAPI[MEDBUFF]=TEXT("");
TCHARszBuff[MEDBUFF]=TEXT("");
RETCODErcExp=SQL_SUCCESS;

HANDLES rgHandles[] =
{
SQL_HANDLE_ENV, henv,
SQL_HANDLE_DBC, hdbc,
SQL_HANDLE_STMT, hstmt,
SQL_HANDLE_DESC, hdesc,
};

cHandles=sizeof(rgHandles)/sizeof(HANDLES);

/* Call SQLGetInfo to get values to compare: */
rcFunction = SQLGetInfo(hdbc, SQL_DATA_SOURCE_NAME, szDataSourceName, MAX_STRING_SIZE, NULL);
RETCHECK(SQL_SUCCESS, rcFunction, szSQLGETINFO);

hdesc = SQL_NULL_HDESC;

/* Allocate an hdesc: */
if(Supported(SQL_API_SQLALLOCHANDLE) && g_f3XDriver)
{
rcFunction = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);
RETCHECK(SQL_SUCCESS, rcFunction, szSQLALLOCHANDLE);

// Update the array entry for the hdesc
rgHandles[3].hHandle=hdesc;

}
else
cHandles--;

for (iHandle=0; iHandle < cHandles; iHandle++)
{

SetErrorCondition(rgHandles[iHandle].fHandleType);

if(fDiagFieldSupported)
{
for (iField=0;iField < sizeof(rgDiagInfo)/sizeof(struct tagDIAGINFO);iField++)
{
rc = SQLGetDiagField(rgHandles[iHandle].fHandleType, rgHandles[iHandle].hHandle,
rgDiagInfo[iField].irecRecNumber, rgDiagInfo[iField].uwDescField,
rgbValue, SQL_MAX_MESSAGE_LENGTH, &cbValue);

DIAGRETCHECK(rcExp, rc, szSQLGETDIAGFIELD);

if (RC_SUCCESSFUL(rc))
CheckData(rgHandles[iHandle].fHandleType,rgDiagInfo[iField].uwDescField,
rgbValue,szDataSourceName);
}

}
else
{
rc = SQLGetDiagField(SQL_HANDLE_ENV, henv, 0, SQL_DIAG_RETURNCODE, rgbValue,SQL_MAX_MESSAGE_LENGTH, &cbValue);

if(!FindError(rgHandles[iHandle].fHandleType, szIM001))
DISPLAYERROR(szAPI, szNotSupported);

}

}

/* Free the hdesc: */
if (hdesc)
{
rcFunction = SQLFreeHandle(SQL_HANDLE_DESC, hdesc);
RETCHECK(rcExp, rcFunction, szSQLFREEHANDLE);
hdesc = SQL_NULL_HDESC;
}

//ExitTestDiagField

FreeStmt(SQL_CLOSE);

} /* TestDiagField() */

//------------------------------------------------------------------------------
//Function:CompareAnsiToUnicode
//Compares an ANSI string to a Unicode string
//------------------------------------------------------------------------------
BOOL CompareAnsiToUnicode(SQLCHAR * aszBuf, SQLINTEGER cbA, SQLWCHAR * wszBuf,
SQLINTEGER cbW, SQLTCHAR * szAPI)
{
SQLCHAR aszConvBuf[MAX_STRING_SIZE];
SQLINTEGER cbT;
BOOL bCompare=TRUE;

// Check ANSI count of bytes returned
if ((cbA != SQL_NULL_DATA) && (SQLINTEGER)strlen(aszBuf) != cbA)
{
wsprintf(buf, TEXT("ANSI count of bytes returned incorrect.")\
TEXT(" Expected: %d, received: %d"),
strlen(aszBuf), cbA);
DISPLAYERROR(szAPI, buf);
bCompare=FALSE;
}

// Compare ANSI count of bytes with Unicode count
cbT = (cbA > 0) ? (SQLINTEGER)(cbA * sizeof(SQLWCHAR)) : cbA;
if (cbW != cbT)
{
wsprintf(buf, TEXT("Unicode count of bytes returned incorrect.")\
TEXT(" Expected: %d, received: %d"), cbT, cbW);
DISPLAYERROR(szAPI, buf);
bCompare=FALSE;
}

// Compare strings if not NULL DATA
if (cbA != SQL_NULL_DATA)
{
// Compare ANSI and Unicode strings returned
(void)(WideCharToMultiByte(CP_ACP, 0, wszBuf, wcslen(wszBuf), aszConvBuf,
sizeof(aszConvBuf), NULL, NULL));
aszConvBuf[wcslen(wszBuf)]=TEXT('\0');

if (strcmp(aszBuf, aszConvBuf))
{
wsprintf(buf, TEXT("Unicode string did not match ANSI string.\r\n")\
TEXT("\t\t\tExpected: '%hs', Received: '%hs'"), aszBuf, aszConvBuf);
DISPLAYERROR(szAPI, buf);
bCompare=FALSE;
}
}

return bCompare;
}

//------------------------------------------------------------------------------
//Function:CompareAnsiUnicodeResults
//Compares an ANSI result set to a Unicode result set
//------------------------------------------------------------------------------
void CompareAnsiUnicodeResults(SQLHSTMT hstmtA, SQLHSTMT hstmtW, SQLTCHAR * szAPI)
{
SQLCHAR aszBuf[MAX_STRING_SIZE];
SQLWCHAR wszBuf[MAX_STRING_SIZE];
SQLINTEGER cbANSI, cbUnicode;
SQLUSMALLINTuwCol;
SQLSMALLINTswColCountA, swColCountW;
SQLRETURN rc, rcA, rcW;

// Check width of result sets
rc=SQLNumResultCols(hstmtA, &swColCountA);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtA, SQL_SUCCESS, rc, szSQLNUMRESULTCOLS);
rc=SQLNumResultCols(hstmtW, &swColCountW);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLNUMRESULTCOLS);
if (swColCountA != swColCountW)
DISPLAYERROR(szAPI,TEXT("ANSI and Unicode result sets different widths."));

uwCol=1;
do {
rc=SQLBindCol(hstmtA, uwCol, SQL_C_CHAR, aszBuf, sizeof(aszBuf), &cbANSI);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtA, SQL_SUCCESS, rc, szSQLBINDCOL);
rc=SQLBindCol(hstmtW, uwCol, SQL_C_WCHAR, wszBuf, sizeof(wszBuf), &cbUnicode);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLBINDCOL);

// Copy something into the buffers so we can tell if they were modified
strcpy(aszBuf, "Untouched");
wcscpy(wszBuf, L"Untouched");

// Fetch into the buffers
rcA=SQLFetch(hstmtA);
rcW=SQLFetch(hstmtW);

if (RC_SUCCESSFUL(rcA) && RC_SUCCESSFUL(rcW))
{
if (!CompareAnsiToUnicode(aszBuf, cbANSI, wszBuf, cbUnicode, szSQLFETCH))
{
rcW=SQL_NO_DATA;// Abort the loop w/o generating failure
rcA=SQL_NO_DATA;
}
}

// Unbind the column bound above on each statement
rc=SQLFreeStmt(hstmtA, SQL_UNBIND);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtA, SQL_SUCCESS, rc, szSQLFREESTMT);
rc=SQLFreeStmt(hstmtW, SQL_UNBIND);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLFREESTMT);

// Don't exceed the total number of columns in result set
uwCol++;
if (uwCol > (SQLUSMALLINT)swColCountA)
uwCol=1;
}
while (RC_SUCCESSFUL(rcA) && RC_SUCCESSFUL(rcW));

// Make sure the result sets were the same size
if (rcW != SQL_NO_DATA)
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_NO_DATA, rc, szSQLFETCH);

// Make sure the result sets were the same size
if (rcA != rcW)
DISPLAYERROR(szAPI,TEXT("ANSI and Unicode result sets not the same length."));

rc=SQLFreeStmt(hstmtA, SQL_CLOSE);

ERRSHANDLE(SQL_HANDLE_STMT, hstmtA, SQL_SUCCESS, rc, szSQLFREESTMT);
rc=SQLFreeStmt(hstmtW, SQL_CLOSE);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLFREESTMT);

}

/*------------------------------------------------------------------------------
/Function:TestMixedAnsiUnicode
/Purpose:Call several of the Unicode functions mixed with ANSI functions
/to verify functions succeed in the mixed case.
/*----------------------------------------------------------------------------*/
void PASCAL TestMixedAnsiUnicode(QTSTRUCT * lpqt)
{
HDBChdbcANSI, hdbcUnicode;
HSTMT hstmtANSIA, hstmtANSIW, hstmtUA, hstmtUW, hstmtW;
SQLINTEGER cbANSI, cbUnicode;
SQLWCHAR wszServer[MAX_STRING_SIZE], wszLogin[MAX_STRING_SIZE],
wszPassword[MAX_STRING_SIZE], wszQuery[MAX_STRING_SIZE],
wszBuf[MAX_STRING_SIZE], wszTable[MAX_STRING_SIZE];
SQLCHARaszServer[MAX_STRING_SIZE], aszLogin[MAX_STRING_SIZE],
aszPassword[MAX_STRING_SIZE], aszQuery[MAX_STRING_SIZE],
aszBuf[MAX_STRING_SIZE], aszTable[MAX_STRING_SIZE];
SQLUSMALLINT fStmts;
SQLRETURN rc, rcA=SQL_SUCCESS, rcW=SQL_SUCCESS;

// We've already set the environment attribute to 3.0 by this point

#ifdef UNICODE
// Copy the unicode strings
lstrcpy(wszServer, lpSI->szValidServer0);
lstrcpy(wszLogin, lpSI->szValidLogin0);
lstrcpy(wszPassword, lpSI->szValidPassword0);
lstrcpy(wszTable, lpqt->szTableName);
wsprintf(wszQuery,szSELECTSTAR,lpqt->szTableName);

// Convert and copy to ANSI strings
/*
int WideCharToMultiByte(
 UINT  CodePage,// code page
 DWORD  dwFlags,// performance and mapping flags
 LPCWSTR  lpWideCharStr,// address of wide-character string
 int  cchWideChar,// number of characters in string
 LPSTR  lpMultiByteStr,// address of buffer for new string
 int  cchMultiByte,// size of buffer
 LPCSTR  lpDefaultChar,// address of default for unmappable characters
 LPBOOL  lpUsedDefaultChar // address of flag set when default char. used
);
*/
// Server
(void)(WideCharToMultiByte(CP_ACP,
  0,
  lpSI->szValidServer0,
  lstrlen(lpSI->szValidServer0),
  aszServer,
  sizeof(aszServer),
  NULL,
  NULL));
aszServer[lstrlen(lpSI->szValidServer0)]=TEXT('\0');

// UID
(void)(WideCharToMultiByte(CP_ACP,
  0,
  lpSI->szValidLogin0,
  lstrlen(lpSI->szValidLogin0),
  aszLogin,
  sizeof(aszLogin),
  NULL,
  NULL));
aszLogin[lstrlen(lpSI->szValidLogin0)]=TEXT('\0');

// PWD
(void)(WideCharToMultiByte(CP_ACP,
  0,
  lpSI->szValidPassword0,
  lstrlen(lpSI->szValidPassword0),
  aszPassword,
  sizeof(aszPassword),
  NULL,
  NULL));
aszPassword[lstrlen(lpSI->szValidPassword0)]=TEXT('\0');

// Select stmt
(void)(WideCharToMultiByte(CP_ACP,
  0,
  wszQuery,
  lstrlen(wszQuery),
  aszQuery,
  sizeof(aszQuery),
  NULL,
  NULL));
aszQuery[lstrlen(wszQuery)]=TEXT('\0');

// Table name
(void)(WideCharToMultiByte(CP_ACP,
  0,
  wszTable,
  lstrlen(wszTable),
  aszQuery,
  sizeof(aszTable),
  NULL,
  NULL));
aszTable[lstrlen(wszTable)]=TEXT('\0');

#else
// Copy the ANSI strings
lstrcpy(aszServer, lpSI->szValidServer0);
lstrcpy(aszLogin, lpSI->szValidLogin0);
lstrcpy(aszPassword, lpSI->szValidPassword0);
lstrcpy(aszTable, lpqt->szTableName);
wsprintf(aszQuery,szSELECTSTAR,lpqt->szTableName);

// Convert and copy to Unicode strings
/*
int MultiByteToWideChar(
 UINT  CodePage,// code page
 DWORD  dwFlags,// character-type options
 LPCSTR  lpMultiByteStr,// address of string to map
 int  cchMultiByte,// number of characters in string
 LPWSTR  lpWideCharStr,// address of wide-character buffer
 int  cchWideChar // size of buffer
);
*/

// Server
(void) MultiByteToWideChar(CP_ACP,
0,
lpSI->szValidServer0,
lstrlen(lpSI->szValidServer0),
wszServer,
sizeof(wszServer)/sizeof(WCHAR)
);
wszServer[lstrlen(lpSI->szValidServer0)]=TEXT('\0');

// UID
(void) MultiByteToWideChar(CP_ACP,
0,
lpSI->szValidLogin0,
lstrlen(lpSI->szValidLogin0),
wszLogin,
sizeof(wszLogin)/sizeof(WCHAR)
);
wszLogin[lstrlen(lpSI->szValidLogin0)]=TEXT('\0');

// PWD
(void) MultiByteToWideChar(CP_ACP,
0,
lpSI->szValidPassword0,
lstrlen(lpSI->szValidPassword0),
wszPassword,
sizeof(wszPassword)/sizeof(WCHAR)
);
wszPassword[lstrlen(lpSI->szValidPassword0)]=TEXT('\0');

// Select stmt
(void) MultiByteToWideChar(CP_ACP,
0,
aszQuery,
lstrlen(aszQuery),
wszQuery,
sizeof(wszQuery)/sizeof(WCHAR)
);
wszQuery[lstrlen(aszQuery)]=TEXT('\0');

// Table name
(void) MultiByteToWideChar(CP_ACP,
0,
aszTable,
lstrlen(aszTable),
wszTable,
sizeof(wszTable)/sizeof(WCHAR)
);
wszTable[lstrlen(aszTable)]=TEXT('\0');

#endif

// Allocate connection for ANSI
rc = SQLAllocConnect(henv, &hdbcANSI);
RETCHECK(SQL_SUCCESS, rc, szSQLALLOCCONNECT);

// Allocate connection for Unicode
rc = SQLAllocConnect(henv, &hdbcUnicode);
RETCHECK(SQL_SUCCESS, rc, szSQLALLOCCONNECT);

// If Unicode is defined, then strings need to be converted to ANSI
// before calling SQLConnectA
rc = SQLConnectA(hdbcANSI, (LPSTR)aszServer, SQL_NTS,
(LPSTR)aszLogin, SQL_NTS,
(LPSTR)aszPassword, SQL_NTS);
if (!RC_SUCCESSFUL(rc))
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLCONNECT);

rc = SQLConnectW(hdbcUnicode, (LPWSTR)wszServer, SQL_NTS,
(LPWSTR)wszLogin, SQL_NTS,
(LPWSTR)wszPassword, SQL_NTS);
if (!RC_SUCCESSFUL(rc))
ERRSHANDLE(SQL_HANDLE_DBC, hdbcUnicode, SQL_SUCCESS, rc,szSQLCONNECTW);

// Test mixed SQLNativeSQL callson the ANSI connection
rc=SQLNativeSqlA(hdbcANSI, aszQuery, strlen(aszQuery), aszBuf, sizeof(aszBuf), &cbANSI);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLNATIVESQL);
rc=SQLNativeSqlW(hdbcANSI,wszQuery, wcslen(wszQuery)*sizeof(WCHAR), wszBuf, sizeof(wszBuf),
&cbUnicode);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLNATIVESQL);

CompareAnsiToUnicode(aszBuf, cbANSI, wszBuf, cbUnicode, szSQLNATIVESQLW);

// Allocate stmt handles for ANSI connection
rc = SQLAllocStmt(hdbcANSI, &hstmtANSIA);// Handle for ANSI calls
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLALLOCSTMT);
rc = SQLAllocStmt(hdbcANSI, &hstmtANSIW);// Handle for Unicode calls
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLALLOCSTMT);

// Allocate stmt handles for Unicode connection
rc = SQLAllocStmt(hdbcUnicode, &hstmtUA); // Handle for ANSI calls
ERRSHANDLE(SQL_HANDLE_DBC, hdbcUnicode, SQL_SUCCESS, rc,szSQLALLOCSTMT);
rc = SQLAllocStmt(hdbcUnicode, &hstmtUW); // Handle for Unicode calls
ERRSHANDLE(SQL_HANDLE_DBC, hdbcUnicode, SQL_SUCCESS, rc,szSQLALLOCSTMT);

// Call SQLTablesA&W on two different hdbcs
strcpy(aszBuf,"%");
wcscpy(wszBuf,L"%");
rc = SQLTablesA(hstmtANSIA, NULL, 0, NULL, 0, aszBuf, SQL_NTS, NULL, 0);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtANSIA, SQL_SUCCESS, rc, szSQLTABLESA);
rc = SQLTablesW(hstmtUW, NULL, 0, NULL, 0, wszBuf, SQL_NTS, NULL, 0);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtUW, SQL_SUCCESS, rc, szSQLTABLESW);

// Compare the two result sets
CompareAnsiUnicodeResults(hstmtANSIA, hstmtUW, szSQLTABLESW);

// See if the driver supports multiple active stmts
rc = SQLGetInfo(hdbcANSI, SQL_ACTIVE_STATEMENTS, &fStmts, 0, NULL);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc, szSQLGETINFO);

// Create a search string for ANSI
aszBuf[0]=aszTable[0];
aszBuf[1]=TEXT('\0');
strcat(aszBuf,"%");

// Create a search string for Unicode
wszBuf[0]=wszTable[0];
wszBuf[1]=TEXT('\0');
wcscat(wszBuf,L"%");

// If the allowed active statements is > 1, then use the same connection
// otherwise use the other connection
if (fStmts > 1)
hstmtW=hstmtUW;
else
hstmtW=hstmtANSIW;

// Call SQLColumnsA&W on two different hstmts (same connection if allowed)
// Call for only the given table to limit compare errors at this time
rc = SQLColumnsA(hstmtUA, NULL, 0, NULL, 0, aszTable, SQL_NTS, NULL, 0);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtANSIA, SQL_SUCCESS, rc, szSQLCOLUMNSA);

// Call for only the given table to limit compare errors at this time
rc = SQLColumnsW(hstmtW, NULL, 0, NULL, 0, wszTable, SQL_NTS, NULL, 0);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLCOLUMNSW);

CompareAnsiUnicodeResults(hstmtUA, hstmtW, szSQLCOLUMNSW);

rc = SQLExecDirectA(hstmtUA, aszQuery, SQL_NTS);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtUA, SQL_SUCCESS, rc, szSQLEXECDIRECTA);

// Call for only the given table to limit compare errors at this time
rc = SQLExecDirectW(hstmtW, wszQuery, SQL_NTS);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtW, SQL_SUCCESS, rc, szSQLEXECDIRECTW);

CompareAnsiUnicodeResults(hstmtUA, hstmtW, szSQLEXECDIRECTW);

// Disconnect and free ANSI handles
rc=SQLFreeStmt(hstmtANSIA, SQL_DROP);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtANSIA, SQL_SUCCESS, rc,szSQLFREESTMT);
rc=SQLFreeStmt(hstmtANSIW, SQL_DROP);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtANSIW, SQL_SUCCESS, rc,szSQLFREESTMT);
rc=SQLDisconnect(hdbcANSI);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLCONNECT);
rc=SQLFreeConnect(hdbcANSI);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcANSI, SQL_SUCCESS, rc,szSQLCONNECT);
// Disconnect and free Unicode handles
rc=SQLFreeStmt(hstmtUA, SQL_DROP);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtUA, SQL_SUCCESS, rc,szSQLFREESTMT);
rc=SQLFreeStmt(hstmtUW, SQL_DROP);
ERRSHANDLE(SQL_HANDLE_STMT, hstmtUW, SQL_SUCCESS, rc,szSQLFREESTMT);
rc=SQLDisconnect(hdbcUnicode);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcUnicode, SQL_SUCCESS, rc,szSQLCONNECT);
rc=SQLFreeConnect(hdbcUnicode);
ERRSHANDLE(SQL_HANDLE_DBC, hdbcUnicode, SQL_SUCCESS, rc,szSQLCONNECT);

} /* TestMixedAnsiUnicode() */

//-----------------------------------------------------------------------
//      Function:               TestStmtOptions
//-----------------------------------------------------------------------

void  PASCAL TestStmtOptions()
{
UDWORDdwLen,
udw;
BOOLfConnectOption;
RETCODErc=SQL_SUCCESS;
TCHARszAPI[MEDBUFF];
SDWORDcbValue=0;

SetStatementAttributes(SQL_MAX_LENGTH,(PTR)&udw);

if (SetStatementAttributes(SQL_MAX_LENGTH, (PTR)TEST_STMT_OPTION))
fConnectOption = TRUE;

if (Supported(SQL_API_SQLGETSTMTATTR))
{
rc = SQLGetStmtAttr(hstmt, SQL_MAX_LENGTH, (PTR)&dwLen,sizeof(dwLen),&cbValue);
lstrcpy(szAPI,szSQLGETSTMTATTR);
}
else
{
rc = SQLGetStmtOption(hstmt, SQL_MAX_LENGTH, &dwLen);
lstrcpy(szAPI,szSQLGETSTMTOPTION);
}

if(rc != SQL_SUCCESS)
{
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTOPTION);
}
else
{
if(dwLen != TEST_STMT_OPTION && rc == SQL_SUCCESS && fConnectOption)
DISPLAYERROR(szSQLGETSTMTOPTION,TEXT("incorrect SQL_MAX_LENGTH value returned"));
}

} //TestStmtOptions()

//-----------------------------------------------------------------------
//      Function:               SetTableName
//-----------------------------------------------------------------------

void  PASCAL SetTableName(QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
UWORDcTableName;
TCHARszTime[MEDBUFF]=TEXT(""),
szTmpBuff[MEDBUFF],
*pTime=NULL;

_tstrtime(szTmpBuff);

/* Remove colons */
pTime=_tcstok(szTmpBuff, TEXT(":"));
while (pTime)
{
lstrcat(szTime,pTime);
pTime=_tcstok(NULL, TEXT(":"));
}

rc = SQLGetInfo(hdbc, SQL_MAX_TABLE_NAME_LEN, &cTableName,
sizeof (int), NULL);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

rc = SQLGetInfo(hdbc, SQL_QUALIFIER_NAME_SEPARATOR, lpqt->buf,
MAX_STRING_SIZE, NULL);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

if(!lstrcmp(TEXT("\\"), lpqt->buf))
cTableName -= 4;

_sntprintf(lpqt->szTableName, min(cTableName, MAX_TABLE_NAME-1),
TEXT("q%s"), szTime);

} //SetTableName()

//-----------------------------------------------------------------------
//      Function:               AllowedType
//
//If swNumTypes > 0, limits SQL types to those in array
//-----------------------------------------------------------------------
BOOL AllowedType(SWORD fType)
{
SWORD i, swNumTypes=0;
BOOL bAllowed=TRUE;
SWORD rgAllowedTypes[MAX_TYPES_SUPPORTED]={
SQL_CHAR,
SQL_BINARY
//SQL_BIT
/*
SQL_TINYINT
SQL_NUMERIC,
SQL_INTEGER,
SQL_SMALLINT,
SQL_FLOAT,
SQL_REAL,
SQL_TYPE_TIMESTAMP,
SQL_LONGVARBINARY,
SQL_LONGVARCHAR
*/
};

if (swNumTypes > 0)
{
bAllowed=FALSE;

for (i=0; i < swNumTypes; i++)
{
if (fType == rgAllowedTypes[i])
bAllowed=TRUE;
}
}

return bAllowed;
}

//-----------------------------------------------------------------------
//      Function:               GetTypeInfo
//-----------------------------------------------------------------------

UWORD  PASCAL GetTypeInfo(FIELDINFO  *rgFields,QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
UWORD       cFieldName;
SDWORD      cMaxTypeSize, cMaxRowSize;
UWORD       i,
cTypes=0;
SDWORD      sdw;
LPTSTR       pch=NULL;
BOOLfAutoInc=0;

/* Get the type information to use in a create statement for a table */

rc = SQLGetInfo(hdbc, SQL_MAX_COLUMN_NAME_LEN, &cFieldName,
sizeof cFieldName, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

rc = SQLGetInfo(hdbc, SQL_MAX_ROW_SIZE, &cMaxRowSize,
sizeof cMaxRowSize, NULL);
/* don't check for SQL_SUCCES here, it's a 2.0 function.  If it's successful
That's great, if not then no maximum is assumed. */

cMaxTypeSize = cMaxRowSize / MAX_TYPES_SUPPORTED;

/* TO DO: add checks in here for dos file types GetInfo SQL_QUALIFIER_NAME_SEPARATER */

if(cFieldName > MAX_FIELD - 1)
cFieldName = MAX_FIELD - 1;

if(cFieldName < PREFIX_SIZE)
{
DISPLAYERROR(szSQLGETINFO,TEXT("MAX_COLUMN_NAME_LEN is too small for autotest to run"));
goto TypeErrorRet;
}

rc = SQLGetTypeInfo(hstmt, SQL_ALL_TYPES);
RETCHECK(SQL_SUCCESS, rc,szSQLGETTYPEINFO);

for(i = 0; i < MAX_TYPES_SUPPORTED; i++)
{

rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS && rc !=SQL_SUCCESS_WITH_INFO)
break;

*rgFields[i].szType = *rgFields[i].szLength = *rgFields[i].szParams = TEXT('\0');

/* get type name */
rc = SQLGetData(hstmt, 1, SQL_C_TCHAR,
rgFields[i].szType, MAX_TYPE_NAME, &sdw);

if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

wsprintf(rgFields[i].szFieldName,TEXT("c%02u"), i+COLNAME_START);
/* copy first portion of type name for easy reference */
_tcsnccat(rgFields[i].szFieldName, rgFields[i].szType, TYPE_PORTION);

/* change spaces in field name to '_' */
while((pch = (LPTSTR)_tcschr(rgFields[i].szFieldName, TEXT(' '))) != NULL)
*pch = TEXT('_');

/* get the SQLType */
rc = SQLGetData(hstmt, 2, SQL_C_DEFAULT,
(SWORD  *)&rgFields[i].wSQLType, IGNORED,
&sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* Max length */
rc = SQLGetData(hstmt, 3, SQL_C_TCHAR,
rgFields[i].szLength, MAX_FIELD, &sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* limit the row size for those drivers that don't support a long
enough row length for all the fields to be at the max length */
switch(rgFields[i].wSQLType)
{
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_VARBINARY:
case SQL_BINARY:
case SQL_LONGVARCHAR:
case SQL_LONGVARBINARY:

if(cMaxTypeSize)
{
if(_ttol (rgFields[i].szLength) > cMaxTypeSize)
_ltot(cMaxTypeSize, rgFields[i].szLength, 10);
}
}

/* prefix */
rc = SQLGetData(hstmt, 4, SQL_C_TCHAR,
rgFields[i].szPrefix, MAX_PREFIX, &sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* suffix */
rc = SQLGetData(hstmt, 5, SQL_C_TCHAR,
rgFields[i].szSuffix, MAX_SUFFIX, &sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* create params */
rc = SQLGetData(hstmt, 6, SQL_C_TCHAR,
rgFields[i].szParams, MAX_PARAM_SIZE, &sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* nullable */
rc = SQLGetData(hstmt, 7, SQL_C_SHORT, &rgFields[i].nullable, IGNORED,
&sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* searchable */
rc = SQLGetData(hstmt, 9, SQL_C_SHORT, &rgFields[i].fSearchable, IGNORED,
&sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

/* autoincrement */
rc = SQLGetData(hstmt, 12, SQL_C_SHORT, &rgFields[i].autoinc, IGNORED,
&sdw);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA))
goto TypeErrorRet;

// Since some data types will have NULL for autoincrement, we need to set to FALSE
if (SQL_NULL_DATA == sdw)
rgFields[i].autoinc=FALSE;

if (IgnoreType(rgFields[i].szType,rgFields[i].wSQLType))
{
i--;
continue;
}

/* The following code only allows the first autoincrement column to
be placed in the table.  Many DBMS's do not allow more than one per table. */
if(rgFields[i].autoinc == TRUE)
{
if(fAutoInc)
{
i--;
continue;
}
else
fAutoInc = TRUE;
}

// Limit data types to those desired (use for debugging)
if (!AllowedType(rgFields[i].wSQLType))
{
i--;
continue;
}
}

cTypes = i;

/* if the type name contains spaces, replace them with _ because
most servers don't allow spaces in field names */

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

SetTableName(lpqt);

return(cTypes);

TypeErrorRet:

return(0);

} //GetTypeInfo()

//-----------------------------------------------------------------------
//      Function:               BuildCreateStmt
//-----------------------------------------------------------------------

BOOL  PASCAL BuildCreateStmt(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes)
{
RETCODErc=SQL_SUCCESS;
TCHAR        szNum[MAX_NUM_BUFFER];   /* used with _ttoi and itoa conversions */
SWORD       wNum;
UWORD       i;
SDWORD      sdw;
LPTSTR       pch=NULL;

/* the column names will be ctypname (where typename is the
type name returned by the source in SQLGetTypeInfo) */

*lpqt->sz = TEXT('\0');
lstrcpy(lpqt->sz,TEXT("Create Table "));
lstrcat(lpqt->sz, lpqt->szTableName);
lstrcat(lpqt->sz,TEXT(" ("));
for(i = 0; i < cTypes; i++) {
TCHAR szParamType[50];
TCHAR szNotNull[10];
*lpqt->szParam = TEXT('\0');

/* if SQLGetTypeInfo returned params they need to be used in
the create statement */

if(rgFields[i].szParams == NULL || lstrlen(rgFields[i].szParams) == 0)
*lpqt->szParam = TEXT('\0');
else if(_tcschr(rgFields[i].szParams, TEXT(',')) == NULL)
wsprintf(lpqt->szParam,TEXT("(%s)"), rgFields[i].szLength);
else
{
lstrcpy(szNum, rgFields[i].szLength);
wsprintf(lpqt->szParam,TEXT("(%s, %d)"), rgFields[i].szLength,
_ttoi(szNum) - SCALEDIFF);
}

lstrcpy(szParamType, rgFields[i].szType);
if(pch = _tcschr((LPTSTR)szParamType, TEXT('(')))
{
*pch = TEXT('\0');
lstrcat(szParamType, lpqt->szParam);
lstrcat(szParamType, (LPTSTR)_tcschr(rgFields[i].szType, TEXT(')')) + 1);
}
else
{
lstrcat(szParamType, lpqt->szParam);
}

/* If the field is not nullable, we need to create the table that way */
rc = SQLGetInfo(hdbc, SQL_NON_NULLABLE_COLUMNS, &wNum,
sizeof (SWORD), NULL);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

if((rgFields[i].nullable == SQL_NO_NULLS) && (wNum != SQL_NNC_NULL))
lstrcpy(szNotNull,TEXT("NOT NULL"));
else
szNotNull[0] = TEXT('\0');

wsprintf(&lpqt->sz[lstrlen(lpqt->sz)],TEXT(" %s %s %s ,"),
rgFields[i].szFieldName, (LPTSTR)szParamType, (LPTSTR)szNotNull);
}

/* remove the last comma and space */
lpqt->sz[lstrlen(lpqt->sz) - 2] = TEXT('\0');
lstrcat(lpqt->sz,TEXT(")"));

rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
if(!RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT))
goto BuildErrorRet;

/* now that table was created, call SQLColumns.  Both as
a test and to get necessary information to insert data */

rc = SQLColumns(hstmt, NULL, SQL_NTS, NULL, SQL_NTS,
lpqt->szTableName, SQL_NTS, NULL, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLCOLUMNS);

for(i = 0; i < cTypes; i++) {
rc = SQLFetch(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLFETCH);

/* precision */
rc = SQLGetData(hstmt, 7, SQL_C_LONG, &rgFields[i].precision, IGNORED,
&sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);

/* length */
if((rgFields[i].precision == 0) || (sdw == SQL_NO_TOTAL)){
rc = SQLGetData(hstmt, 8, SQL_C_LONG, &rgFields[i].precision, IGNORED,
&sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
if(sdw == SQL_NO_TOTAL)/* precision & length were undetermined */
rgFields[i].precision = 1000;/* so set it to an arbitary value */
}

/* numeric scale */
rc = SQLGetData(hstmt, 9, SQL_C_SHORT, &rgFields[i].scale, IGNORED,
&sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
if(sdw == SQL_NO_TOTAL)
rgFields[i].scale = 0;
}

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

/* look for columns the test should not try to update by running
a select * query and calling ColAttributes on the result fields */
/* select * does not return the fields in a defined order, so field order
must be specified */

lstrcpy(lpqt->sz,TEXT("select "));
for(i = 0; i < cTypes; i ++) {
if(i)
lstrcat(lpqt->sz,TEXT(","));
lstrcat(lpqt->sz, rgFields[i].szFieldName);
}

lstrcat(lpqt->sz,TEXT(" from "));
lstrcat(lpqt->sz, lpqt->szTableName);

rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

for(i = 1; i <= cTypes; i++) {

rc = SQLColAttributes(hstmt, i, SQL_COLUMN_UPDATABLE, NULL,
0, NULL, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLCOLATTRIBUTES);

rgFields[i - 1].fAutoUpdate = sdw == SQL_ATTR_READONLY;

rc = SQLColAttributes(hstmt, i, SQL_COLUMN_UNSIGNED, NULL,
0, NULL, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLCOLATTRIBUTES);

rgFields[i - 1].fUnsigned = sdw;
}

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

return(FALSE);

BuildErrorRet:

return(TRUE);

} //BuildCreateStmt()

//-----------------------------------------------------------------------
//      Function:               BuildInsertStmt
//-----------------------------------------------------------------------

BOOL  PASCAL BuildInsertStmt(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd,SDWORD  *cColsSelected,BOOL fBindParameter)
{
RETCODE  rc;
UWORD    w;
UWORD    i;
PTRptr;
UWORD    ind;
SDWORD   sdw;
LPTSTR    pch=NULL;
BOOLfColNames=TRUE,
fLongData=FALSE;
SDWORD  *sdwArray = (SDWORD  *)AllocateMemory(sizeof(SDWORD) * MAX_TYPES_SUPPORTED);

lstrcpy(lpqt->szColNames,TEXT("\0"));
lstrcpy(lpqt->szValues,TEXT("\0"));

for(i = 0; i < cTypes; i++) {

for(ind = 0, w = 0; ind < cTypes; ind++) {

pch = qtMakeData(i, ind,&rgFields[ind], lpqt->szDataItem);

if(!pch) /* current type is READ_ONLY) */
continue;

/* for every nullable type, that field will be set to */
/* null when the row number corresponds with the field */
/* number */

if(*pch)
{
lstrcpy(lpd[w].data, pch);
lpd[w].cb = SQL_NTS;
}
else
{
lstrcpy(lpd[w].data,TEXT(""));
lpd[w].cb = SQL_NULL_DATA;
}

if(fColNames)
{
/* the first time throught, the insert */
/* statement itself is created         */
lstrcat(lpqt->szColNames, rgFields[ind].szFieldName);
lstrcat(lpqt->szColNames,TEXT(", "));
lstrcat(lpqt->szValues,TEXT(" ?, "));

/* and parameters are set */
/* set all the parameters using pointers to buffers in*/
/* the param struct. */
if(!fBindParameter)
{
rc = SQLSetParam(hstmt, (UWORD)(w+1),
SQL_C_TCHAR, rgFields[ind].wSQLType,
rgFields[ind].precision, rgFields[ind].scale,
&lpd[w].data,
&lpd[w].cb);
RETCHECK(SQL_SUCCESS, rc,szSQLSETPARAM);
}
else
{
rc = SQLBindParameter(hstmt, (UWORD)(w+1),
SQL_PARAM_INPUT,
SQL_C_TCHAR, rgFields[ind].wSQLType,
rgFields[ind].precision, rgFields[ind].scale,
&lpd[w].data, MAX_STRING_SIZE,
&lpd[w].cb);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDPARAMETER);
}
}
w++;
cNumResSetCols=w;
}

if(fColNames)
{
/* the first time through, the insert*/
/* statement needs to be SQLPrepare'd*/
/* remove the "," at the end of the string*/
lpqt->szColNames[lstrlen(lpqt->szColNames) - 2] = TEXT('\0');
lpqt->szValues[lstrlen(lpqt->szValues) - 2] = TEXT('\0');

wsprintf(lpqt->sz,TEXT("insert into %s (%s) VALUES(%s)"),
lpqt->szTableName, lpqt->szColNames, lpqt->szValues);

//copy the insert statement, with the column names.
//_ASSERTE(lstrlen(lpqt->sz) < MAX_STRING_SIZE);
lstrcpy(szInsertStmt,lpqt->sz);

//prepare the statement
rc = SQLPrepare(hstmt, lpqt->sz, SQL_NTS);
if(!RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE))
goto InsertErrorRet;

fColNames = FALSE; /* no more first times through */
}

rc = SQLExecute(hstmt); /* insert a row */

if(!RETCHECK(SQL_SUCCESS, rc,szSQLEXECUTE))
{
if (SQL_SUCCESS_WITH_INFO != rc)// BANDAID, normally would fail on S.W.I
goto InsertErrorRet;
}

rc = SQLRowCount(hstmt, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLROWCOUNT);

if(sdw != 1) /* the execute inserted 1 row */
DISPLAYERROR(szSQLROWCOUNT,TEXT("Insert single row"));

/* FreeStmt SQL_CLOSE */
rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);
}

/* for the last row use SQLParamData and SQLPutData */

/* First check to see if the SQL_LEN_DATA_AT_EXEC macro is supported */
rc = SQLGetInfo(hdbc, SQL_NEED_LONG_DATA_LEN, lpqt->buf, (SWORD)sizeof(lpqt->buf), NULL);
if(rc == SQL_SUCCESS)
if(lpqt->buf[0] == TEXT('Y'))
fLongData = TRUE;

for(ind = 0, w = 0; ind < cTypes; ind++)
{

/* set all the params */

if(rgFields[ind].fAutoUpdate)
continue;

if(fLongData && ((rgFields[ind].wSQLType == SQL_LONGVARBINARY) ||
(rgFields[ind].wSQLType == SQL_LONGVARCHAR))){
/* Make sure we don't exceed the max negative SDWORD value */
if(rgFields[ind].precision > (MAXSDWORD + SQL_LEN_DATA_AT_EXEC_OFFSET))
sdwArray[ind] = MINSDWORD - SQL_LEN_DATA_AT_EXEC_OFFSET;
else
sdwArray[ind] = SQL_LEN_DATA_AT_EXEC(rgFields[ind].precision);
}
else
sdwArray[ind] = SQL_DATA_AT_EXEC;

w++;
if(!fBindParameter){
rc = SQLSetParam(hstmt, w,
SQL_C_TCHAR, rgFields[ind].wSQLType,
rgFields[ind].precision, rgFields[ind].scale,
(LPTSTR)(UDWORD)ind, &sdwArray[ind]);
RETCHECK(SQL_SUCCESS, rc,szSQLSETPARAM);
}
else{
rc = SQLBindParameter(hstmt, w, SQL_PARAM_INPUT,
SQL_C_TCHAR, rgFields[ind].wSQLType,
rgFields[ind].precision, rgFields[ind].scale,
(LPTSTR)(((UDWORD)ind)+BIND_PARM_OFFSET), 0, &sdwArray[ind]);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDPARAMETER);
}
}

*cColsSelected = w;

rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
if (!RETCHECK(SQL_NEED_DATA, rc,szSQLEXECDIRECT))
goto InsertErrorRet;

for(ind = 0; ind <= cTypes; ind++)
{
rc = SQLParamData(hstmt, &ptr);
if(rc != SQL_NEED_DATA)
break;

pch = qtMakeData(cTypes, ind, &rgFields[(unsigned long)ptr-BIND_PARM_OFFSET],
lpqt->szDataItem);

if(*pch) {
rc = SQLPutData(hstmt, pch, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPUTDATA);
} else {
rc = SQLPutData(hstmt, (LPTSTR)IGNORED, SQL_NULL_DATA);
RETCHECK(SQL_SUCCESS, rc,szSQLPUTDATA);
}
}

RETCHECK(SQL_SUCCESS, rc,szSQLPARAMDATA);

rc = SQLRowCount(hstmt, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLROWCOUNT);

if(sdw != 1)
DISPLAYERROR(szSQLROWCOUNT,TEXT("Insert single row"));

rc = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);

RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

ReleaseMemory(sdwArray);

return(FALSE);

InsertErrorRet:

ReleaseMemory(sdwArray);

return(TRUE);

} //BuildInsertStmt()

//-----------------------------------------------------------------------
//      Function:               FullDisconnect
//-----------------------------------------------------------------------

void  PASCAL FullDisconnect()
{
RETCODE rc=SQL_SUCCESS;

/* SQLCancel has the same functionality as SQLFreeStmt w/ SQL_CLOSE*/
/*in a non-asynchronous environment */

rc = SQLCancel(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLCANCEL);

FreeStmt(SQL_DROP);

/* if the connection was made in the*/
/*test, it should be disconnected*/
/* in the test, otherwise it should be left connected */

rc = SQLDisconnect(hdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLDISCONNECT);

rc = SQLFreeConnect(hdbc);
RETCHECK(SQL_SUCCESS, rc,szSQLFREECONNECT);

rc = SQLFreeEnv(henv);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEENV);

} //FullDisconnect()

//-----------------------------------------------------------------------
//      Function:               DropTable
//-----------------------------------------------------------------------

void  PASCAL DropTable(LPTSTR szTableName, BOOL fSilent)
{
TCHARszBuff[100];
RETCODErc=SQL_ERROR;

lstrcpy(szBuff,TEXT("drop table "));
lstrcat(szBuff, szTableName);

rc = SQLExecDirect(hstmt, szBuff, SQL_NTS);

if (!fSilent)
{
if(!RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT))
szWrite(TEXT("Unable to drop table"), TRUE);
}

} //DropTable()

//-----------------------------------------------------------------------
//    Function:CleanUp
//Purpose:To drop the table,disconnect from data source and
//release memory.
//-----------------------------------------------------------------------

void  PASCAL CleanUp(DSTRUCT  *lpd,QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
LPTSTR szTableName,BOOL fSilent,LPTSTR szValidServer)
{

DropTable(szTableName, fSilent);

if(szValidServer)
FullDisconnect();

ReleaseMemory(lpd);
ReleaseMemory(lpqt);
ReleaseMemory(rgFields);

} //CleanUp()

//-----------------------------------------------------------------------
//      Function:               BindAllColumns
//-----------------------------------------------------------------------

BOOL  PASCAL BindAllColumns(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd)
{
//-----------------------------------------------------------------------
//      Function:               BindAllColumns
UWORDi,
w;
RETCODErc=SQL_SUCCESS;

for(i = 0, w = 0; i < cTypes; i++)
{

if(!qtMakeData(1, i,&rgFields[i],lpqt->szDataItem))
continue;

w++;

switch(rgFields[i].wSQLType)
{
case SQL_INTEGER:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].sdword, IGNORED, &lpd[i].cb);
break;
case SQL_SMALLINT:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].sword, IGNORED, &lpd[i].cb);
break;
case SQL_FLOAT:
case SQL_DOUBLE:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].sdouble, IGNORED, &lpd[i].cb);
break;
case SQL_REAL:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].sfloat, IGNORED, &lpd[i].cb);
break;
case SQL_DATE:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].date, IGNORED, &lpd[i].cb);
break;
case SQL_TIME:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].time, IGNORED, &lpd[i].cb);
break;
case SQL_TIMESTAMP:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpd[i].timestamp, IGNORED, &lpd[i].cb);
break;
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_NUMERIC:
case SQL_DECIMAL:
default:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
lpd[i].data, MAX_STRING_SIZE, &lpd[i].cb);
break;
}

if(!RETCHECK(SQL_SUCCESS, rc, szSQLBINDCOL))
return(FALSE);
}

return(TRUE);

} //BindAllColumns()

/*-----------------------------------------------------------------------
 *      Function:               BindFetchColumns
 *  Used for Checking data in Result sets from fetch calls.
 *  Binds Binary data types as SQL_C_CHAR
 *----------------------------------------------------------------------- */

BOOL  PASCAL BindFetchColumns(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpdFetch)
{
UWORDi,
w;
RETCODErc=SQL_SUCCESS;

for(i = 0, w = 0; i < cTypes; i++)
{

if(!qtMakeData(0, i,&rgFields[i],lpqt->szDataItem))
continue;

w++;

switch(rgFields[i].wSQLType)
{
case SQL_BIGINT:
case SQL_INTEGER:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].sdword, IGNORED, &lpdFetch[i].cb);
break;
case SQL_TINYINT:
case SQL_SMALLINT:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].sword, IGNORED, &lpdFetch[i].cb);
break;
case SQL_FLOAT:
case SQL_DOUBLE:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].sdouble, IGNORED, &lpdFetch[i].cb);
break;
case SQL_REAL:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].sfloat, IGNORED, &lpdFetch[i].cb);
break;
case SQL_DATE:
case SQL_TYPE_DATE:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].date, IGNORED, &lpdFetch[i].cb);
break;
case SQL_TIME:
case SQL_TYPE_TIME:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].time, IGNORED, &lpdFetch[i].cb);
break;
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
&lpdFetch[i].timestamp, IGNORED, &lpdFetch[i].cb);
break;
case SQL_BIT:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
rc = SQLBindCol(hstmt, w, SQL_C_TCHAR,
lpdFetch[i].data, MAX_STRING_SIZE, &lpdFetch[i].cb);
break;
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_NUMERIC:
case SQL_DECIMAL:
default:
rc = SQLBindCol(hstmt, w, SQL_C_DEFAULT,
lpdFetch[i].data, MAX_STRING_SIZE, &lpdFetch[i].cb);
break;
}

if(!RETCHECK(SQL_SUCCESS, rc, szSQLBINDCOL))
return(FALSE);
}

return(TRUE);

} /*BindFetchColumns() */

//-----------------------------------------------------------------------
//      Function:               TestMetaData
//-----------------------------------------------------------------------

void  PASCAL TestMetaData(lpSERVERINFO lpSI, QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes)
{
SWORD    wNum;
UWORD    w;
UWORD    i;
UDWORD   dwLen;
RETCODErc=SQL_SUCCESS;
SWORDwType;
SWORDwNull;

SelectFromTable(lpqt);

for(i = 0, w = 0; i < cTypes; i++)
{
dwLen = 0;
wNum = 0;
wNull = 0;

/* information returned by SQLDescribeCol should match info */
/*used to create table */

if(!qtMakeData(1, i,&rgFields[i],lpqt->szDataItem))
continue;

w++;
rc = SQLDescribeCol(hstmt, w, lpqt->buf,
MAX_STRING_SIZE, NULL, &wType, &dwLen, &wNum, &wNull);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBECOL);

/* verify that column name returned is column name created */

if(0 != lstrcmpi(rgFields[i].szFieldName, lpqt->buf))
DISPLAYERROR(szSQLDESCRIBECOL,TEXT("incorrect column name"));

if(wType != rgFields[i].wSQLType)
{
// For a 2.x driver, SQLGetTypeInfo result will be SQL_DATE, etc,
// but 3.0 DM will convert SQLDescribeCol values to TYPE_DATE, ETC.
if ((uDriverODBCVer < 3) &&
((SQL_TYPE_DATE == wType && SQL_DATE == rgFields[i].wSQLType) ||
(SQL_TYPE_TIME == wType && SQL_TIME == rgFields[i].wSQLType) ||
(SQL_TYPE_TIMESTAMP == wType && SQL_TIMESTAMP == rgFields[i].wSQLType)))
NULL;
else
DISPLAYERROR(szSQLDESCRIBECOL,TEXT("incorrect SQLType"));
}

if(dwLen != (UDWORD)rgFields[i].precision)
DISPLAYERROR(szSQLDESCRIBECOL,TEXT("incorrect precision"));

if(wNum != rgFields[i].scale)
DISPLAYERROR(szSQLDESCRIBECOL,TEXT("incorrect scale"));

if(wNull != rgFields[i].nullable && wNull != SQL_NULLABLE_UNKNOWN &&
rgFields[i].nullable != SQL_NULLABLE_UNKNOWN)
{
DISPLAYERROR(szSQLDESCRIBECOL,TEXT("incorrect nullable"));
}
}

FreeStmt(SQL_CLOSE);

} //TestMetaData()

//-----------------------------------------------------------------------
//      Function:               RetrieveData
//-----------------------------------------------------------------------

void PASCAL RetrieveData(QTSTRUCT *lpqt,FIELDINFO *rgFields,
UWORD cTypes,DSTRUCT *lpd)
{
RETCODErc=SQL_SUCCESS;
UWORDind,
i;
intnSame;
LPTSTRpch=NULL;
TCHARszNum[MAX_NUM_BUFFER];   /* used with _ttoi and itoa conversions */
TCHAR*stopstring=NULL;
doubleNum=0,
Num2=0;

for(ind = 0; ;ind++)
{

/* Get the data back */
rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
break;

for(i = 0; i < cTypes; i++)
{
/* make sure it's original data placed */
/* in that field/row location */
pch = qtMakeData(ind, i,&rgFields[i], lpqt->szDataItem);

if(!pch)
continue;

if(!*pch) {
if(lpd[i].cb != SQL_NULL_DATA)
DISPLAYERROR(szRETDATA, TEXT("should have been NULL"));
continue;
}
switch(rgFields[i].wSQLType)
{
/* check the outlen and data*/
/* returned for each type. */
case SQL_INTEGER:
if(lpd[i].cb != sizeof(SDWORD))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

lstrcpy(szNum, pch);
nSame = _ttol(szNum) == lpd[i].sdword;
break;
case SQL_SMALLINT:
if(lpd[i].cb != sizeof(SWORD))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

lstrcpy(szNum, pch);
nSame = _ttoi(szNum) == lpd[i].sword;
break;

case SQL_FLOAT:
case SQL_DOUBLE:
if(lpd[i].cb != sizeof(SDOUBLE))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

lstrcpy(szNum, pch);
Num=_tcstod(szNum, &stopstring);
nSame = Num - lpd[i].sdouble < 0.001 && Num - lpd[i].sdouble > -0.001;

break;

case SQL_REAL:
if(lpd[i].cb != sizeof(SFLOAT))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

lstrcpy(szNum, pch);
Num=_tcstod(szNum, &stopstring);

nSame = Num - lpd[i].sfloat < 0.001 && Num - lpd[i].sfloat > -0.001;
break;

case SQL_TINYINT:
if(lpd[i].cb != sizeof(TCHAR))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

lstrcpy(szNum, pch);
nSame = (TCHAR)_ttoi(szNum) == (TCHAR)lpd[i].data[0];
break;

case SQL_DECIMAL:
case SQL_NUMERIC:
if(lpd[i].cb > MAX_STRING_SIZE)
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

if(lpd[i].cb > lstrlen(lpd[i].data))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

{
TCHAR szNum2[MAX_NUM_BUFFER];

lstrcpy(szNum, pch);
lstrcpy(szNum2, pch);
Num=_tcstod(szNum, &stopstring);
Num2=_tcstod(szNum2, &stopstring);

nSame = Num - Num2 < 0.001 &&Num - Num2 > -0.001;
}

break;

case SQL_VARCHAR:
case SQL_LONGVARCHAR:
if(lpd[i].cb > MAX_STRING_SIZE)
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

if(lpd[i].cb > lstrlen(lpd[i].data))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

nSame = !!_tcsstr(lpd[i].data, pch);

break;

case SQL_VARBINARY:
case SQL_LONGVARBINARY:
if(lpd[i].cb > MAX_STRING_SIZE)
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

if(lpd[i].cb  * 2 != lstrlen(pch))
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

nSame = TRUE; /* not checking returned data for binary */

break;

case SQL_CHAR:
if(lpd[i].cb > MAX_STRING_SIZE && rc == SQL_SUCCESS)
DISPLAYERROR(szRETDATA,TEXT("incorrect return code for outlen"));

if(lpd[i].cb != rgFields[i].precision && rc == SQL_SUCCESS)
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);

nSame = !_tcsnccmp(lpd[i].data, pch, lstrlen(pch));

break;
case SQL_BINARY:
default:
if(lpd[i].cb > MAX_STRING_SIZE && rc == SQL_SUCCESS)
DISPLAYERROR(szRETDATA,szINCORRECTOUTLEN);
nSame = TRUE; /* not checking returned data for binary */
}

if(!nSame)
DISPLAYERROR(szRETDATA, TEXT("invalid data"));
}
}

RETCHECK(SQL_NO_DATA_FOUND, rc, szSQLFETCH);

if(ind != cTypes + 1)
DISPLAYERROR(szSQLFETCH, TEXT("Incorrect number of result rows"));

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLFreeStmt(hstmt, SQL_UNBIND);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

} // RetrieveData()

//-----------------------------------------------------------------------
//      Function:               TestData
//-----------------------------------------------------------------------

void  PASCAL TestData(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
UWORD cTypes,DSTRUCT  *lpd)
{

/* bind all fields to a variable of the correct type for retreiving data */
if (!BindAllColumns(lpqt,rgFields,cTypes,lpd))
return;

SelectFromTable(lpqt);

RetrieveData(lpqt,rgFields,cTypes,lpd);

} // TestData()

//-----------------------------------------------------------------------
//      Function:               CreateParamQuery
//-----------------------------------------------------------------------

void  PASCAL CreateParamQuery(QTSTRUCT *lpqt, FIELDINFO *rgFields,UWORD cTypes)
{
RETCODErc=SQL_SUCCESS;
UWORDi;
LPTSTRpch=NULL;
TCHARszQuery[MAX_QUERY_SIZE];
SDWORDsdw = SQL_DATA_AT_EXEC;
UWORDw,
ind;

wsprintf(szQuery,TEXT("select %s from %s where "), lpqt->szColNames,lpqt->szTableName);

for(i = 0, w = 0,ind=0; i < cTypes; i++)
{
pch = qtMakeData(ind, i,&rgFields[i], lpqt->szDataItem);

if ((rgFields[i].fSearchable == SQL_SEARCHABLE ||
rgFields[i].fSearchable == SQL_ALL_EXCEPT_LIKE) &&
pch && *pch)
{
w++;

switch(rgFields[i].wSQLType)
{
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
wsprintf(&szQuery[lstrlen(szQuery)],TEXT("%s < ? + 1 and "),
rgFields[i].szFieldName);
break;
default:
wsprintf(&szQuery[lstrlen(szQuery)],TEXT("%s = ? and "),
rgFields[i].szFieldName);
}

if(!fBindParameter)
{
rc = SQLSetParam(hstmt, w,
SQL_C_TCHAR, rgFields[i].wSQLType,
rgFields[i].precision, rgFields[i].scale,
(LPTSTR)(UDWORD)i, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLSETPARAM);
}
else
{
rc = SQLBindParameter(hstmt, w, SQL_PARAM_INPUT,
SQL_C_TCHAR, rgFields[i].wSQLType,
rgFields[i].precision, rgFields[i].scale,
(LPTSTR)((UDWORD)i+BIND_PARM_OFFSET), 0, &sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDPARAMETER);
}

ind++;
}
}

if (_tcsstr(szQuery,TEXT("and")))
{
/* remove the final "and " */
szQuery[lstrlen(szQuery) - 5] = TEXT('\0');
}

lstrcpy(lpqt->szParamQuery, szQuery);

}

//-----------------------------------------------------------------------
//      Function:               TestSearchedQuery
//-----------------------------------------------------------------------

void  PASCAL TestSearchedQuery(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
UWORD cTypes,DSTRUCT  *lpd,BOOL fBindParameter)
{
RETCODErc=SQL_SUCCESS;
UWORDi;
LPTSTRpch=NULL;
SDWORDsdw;
UWORDw;
UDWORDudw;
UWORDind;

wsprintf(lpqt->sz, TEXT("select %s from %s where "), lpqt->szColNames,
lpqt->szTableName);

    sdw = SQL_DATA_AT_EXEC;

for(i = 0, w = 0,ind=0; i < cTypes; i++)
{
pch = qtMakeData(ind, i,&rgFields[i], lpqt->szDataItem);

if ((rgFields[i].fSearchable == SQL_SEARCHABLE ||
rgFields[i].fSearchable == SQL_ALL_EXCEPT_LIKE) &&
pch && *pch) {
w++;
switch(rgFields[i].wSQLType) {
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
wsprintf(&lpqt->sz[lstrlen(lpqt->sz)], TEXT("%s < ? + 1 and "),
rgFields[i].szFieldName);
break;
default:
wsprintf(&lpqt->sz[lstrlen(lpqt->sz)], TEXT("%s = ? and "),
rgFields[i].szFieldName);
}

if(!fBindParameter){
rc = SQLSetParam(hstmt, w,
SQL_C_TCHAR, rgFields[i].wSQLType,
rgFields[i].precision, rgFields[i].scale,
(LPTSTR)(UDWORD)i, &sdw);
RETCHECK(SQL_SUCCESS, rc, szSQLSETPARAM);
}
else{
rc = SQLBindParameter(hstmt, w, SQL_PARAM_INPUT,
SQL_C_TCHAR, rgFields[i].wSQLType,
rgFields[i].precision, rgFields[i].scale,
(LPTSTR)((UDWORD)i+BIND_PARM_OFFSET), 0, &sdw);
RETCHECK(SQL_SUCCESS, rc, szSQLBINDPARAMETER);
}

ind++;
}
}
/* remove the final "and " */
lpqt->sz[lstrlen(lpqt->sz) - 5] = TEXT('\0');
lstrcpy(lpqt->szParamQuery, lpqt->sz);

rc = SQLPrepare(hstmt, lpqt->sz, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc, szSQLPREPARE);

rc = SQLExecute(hstmt);
RETCHECK(SQL_NEED_DATA, rc, szSQLEXECUTE);

udw = cTypes;

for(i = 0; ; i++)
{
rc = SQLParamData(hstmt, (PTR *)&udw);
if(rc != SQL_NEED_DATA)
break;

if(udw-BIND_PARM_OFFSET < cTypes)
pch = qtMakeData(cTypes, i,&rgFields[udw-BIND_PARM_OFFSET], lpqt->szDataItem);
else
DISPLAYERROR(szSQLPARAMDATA, TEXT("invalid rgbValue"));

if(*pch)
{
udw = lstrlen(pch);
rc = SQLPutData(hstmt, pch, (SDWORD)lstrlen(pch));
RETCHECK(SQL_SUCCESS, rc, szSQLPUTDATA);
}
else
{
rc = SQLPutData(hstmt, (LPTSTR)IGNORED, SQL_NULL_DATA);
RETCHECK(SQL_SUCCESS, rc, szSQLPUTDATA);
}
}

RETCHECK(SQL_SUCCESS, rc, szSQLPARAMDATA);

for(i = 0;; i++)
{
rc = SQLFetch(hstmt);

if(rc != SQL_SUCCESS)
break;
}

RETCHECK(SQL_NO_DATA_FOUND, rc, szSQLFETCH);

/* should have gotten 1 row back */

if(i != 1)
DISPLAYERROR(TEXT("Param/PutData"), TEXT("incorrect number of rows returned"));

FreeStmt(SQL_CLOSE);

} //TestSearchedQuery()

//-----------------------------------------------------------------------
//      Function:               TestLargeQuery
//-----------------------------------------------------------------------

void  PASCAL TestLargeQuery(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
UWORD cTypes,DSTRUCT  *lpd)
{
RETCODErc=SQL_SUCCESS;
UWORDind=0,
i;
LPTSTRpch=NULL;
TCHARszQuery[2048];

/* > 1K query */
wsprintf(szQuery,TEXT("select %s from %s where "), lpqt->szColNames,
lpqt->szTableName);

for(i = 0; i < cTypes; i++)
{
if (rgFields[i].fSearchable == SQL_SEARCHABLE ||
rgFields[i].fSearchable == SQL_ALL_EXCEPT_LIKE)
break;
}

pch = qtMakeData(cTypes, 1, &rgFields[i], lpqt->szDataItem);
while(lstrlen(szQuery) < 1024L)
{
int li=lstrlen(szQuery);
switch(rgFields[i].wSQLType)
{
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
wsprintf(&szQuery[lstrlen(szQuery)],TEXT("%s < %s + 1 and "),
rgFields[i].szFieldName, pch);
break;
default:
wsprintf(&szQuery[lstrlen(szQuery)],TEXT("%s = %s%s%s and "),
rgFields[i].szFieldName, rgFields[i].szPrefix, pch, rgFields[i].szSuffix);
break;
}
}

/* remove the final "and " */
szQuery[lstrlen(szQuery) - 5] = TEXT('\0');

rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

if (RC_SUCCESSFUL(rc))
{
for(i = 0;; i++)
{
rc = SQLFetch(hstmt);

if(rc != SQL_SUCCESS)
break;
}

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

/* should have gotten at least 1 row back */

if(i < 1)
DISPLAYERROR(TEXT("> 1K query"),TEXT("incorrect number of rows returned"));

}

/* SQLFreeStmt with SQL_CLOSE to re-use the hstmt */

FreeStmt(SQL_CLOSE);

} //TestLargeQuery()

//-----------------------------------------------------------------------
//      Function:               TestSQLTables
//-----------------------------------------------------------------------

void  PASCAL TestSQLTables(QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
BOOLfFoundTable=FALSE;
UWORDi;

/* this call may return many tables, as  */
/* long as the one created earlier shows */
/* up it will pass. */

rc = SQLTables(hstmt, NULL, 0, NULL, 0,TEXT("q%"),SQL_NTS,TEXT("'TABLE'"), SQL_NTS);

RETCHECK(SQL_SUCCESS, rc,szSQLTABLES);

for(i = 0;; i++)
{
rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS)
break;

/* column 3 is tablename */
rc = SQLGetData(hstmt, 3, SQL_C_TCHAR, lpqt->sz, MAX_STRING_SIZE, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA); /* should not overflow
and return SQL_SUCCESS_WITH_INFO because the buffer is larger than the
table name */
fFoundTable += 0 == lstrcmpi(lpqt->sz, lpqt->szTableName);
}

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

if(1 != fFoundTable) {
DISPLAYERROR(szSQLTABLES,TEXT("table not found"));
}

FreeStmt(SQL_CLOSE);

} //TestSQLTables()

//-----------------------------------------------------------------------
//      Function:               TestSQLSpecialCols
//-----------------------------------------------------------------------

void  PASCAL TestSQLSpecialCols(QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
UWORDi;

rc = SQLSpecialColumns(hstmt, SQL_BEST_ROWID, NULL, 0, NULL, 0,
lpqt->szTableName, (SWORD)(lstrlen(lpqt->szTableName)), SQL_SCOPE_TRANSACTION,
SQL_NULLABLE);
RETCHECK(SQL_SUCCESS, rc,szSQLSPECIALCOLUMNS);

for(i = 0;; i++)
{
rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS)
break;
}

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);

} //TestSQLSpecialCols()

//-----------------------------------------------------------------------
//      Function:               VerifyIndex
//-----------------------------------------------------------------------

void  PASCAL VerifyIndex(QTSTRUCT  *lpqt,UWORD fIndex)
{
RETCODErc=SQL_SUCCESS;
BOOLfFoundTable=FALSE;
UWORDi;
SDWORDsdw;

rc = SQLStatistics(hstmt, NULL, 0, NULL, 0, lpqt->szTableName,
SQL_NTS, SQL_INDEX_ALL, SQL_ENSURE);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLStatistics"));

fFoundTable = 0;
for(i = 0;; i++)
{
rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS)
break;

rc = SQLGetData(hstmt, 3, SQL_C_TCHAR, lpqt->sz, MAX_STRING_SIZE,
&sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
if (lstrcmpi(lpqt->sz, lpqt->szTableName) == 0)
{
rc = SQLGetData(hstmt, 6, SQL_C_TCHAR, lpqt->sz, MAX_STRING_SIZE,
&sdw);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
fFoundTable += 0 == lstrcmpi(lpqt->sz, lpqt->szDataItem);
}
}

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

if(1 != fFoundTable && fIndex)
DISPLAYERROR(TEXT("SQLStatistics"),TEXT("index not returned"));

/* one row represents original table, the */
/* other represents the index */
if(i > 2 || i < 1)
DISPLAYERROR(TEXT("SQLStatistics"),TEXT("too many rows"));

FreeStmt(SQL_CLOSE);

} //VerifyIndex()

//-----------------------------------------------------------------------
//      Function:               TestSQLStatistics
//-----------------------------------------------------------------------

BOOL  PASCAL TestSQLStatistics(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
UWORD cTypes,DSTRUCT  *lpd,UWORD fIndex)
{
RETCODErc=SQL_SUCCESS;
UWORDi;

lstrcpy(lpqt->szDataItem, lpqt->szTableName);
lpqt->szDataItem[0] = TEXT('i');
for(i = 1; i < cTypes; i++)
if(rgFields[i].wSQLType == SQL_INTEGER ||
rgFields[i].wSQLType == SQL_SMALLINT)
break;
if(i == cTypes)
i = 0;

lstrcpy(lpqt->buf, rgFields[i].szFieldName);
wsprintf(lpqt->sz,TEXT("create unique index %s on %s (%s)"),
lpqt->szDataItem, lpqt->szTableName, lpqt->buf);

rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
if(fIndex < 1)
/* if this is minimal grammar, don't count on indexes being available */
fIndex = rc == SQL_SUCCESS;
else
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

VerifyIndex(lpqt,fIndex);

return(fIndex);

} //TestSQLStatistics()

//-----------------------------------------------------------------------
//      Function:               TestLikeQuery
//-----------------------------------------------------------------------

void  PASCAL TestLikeQuery(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,
UWORD cTypes,DSTRUCT  *lpd)
{
RETCODErc=SQL_SUCCESS;
UWORDi;
SDWORDcbValue=0;

for(i = 0; i < cTypes; i++)
{
if(rgFields[i].fSearchable == SQL_LIKE_ONLY ||
rgFields[i].fSearchable == SQL_SEARCHABLE)
{
lstrcpy(lpqt->buf, rgFields[i].szFieldName);
break;
}
}

if(i < cTypes)
{

/* execute a query using like.  This query should return all records */
/* this query should return all rows in the table */

wsprintf(lpqt->sz,TEXT("select * from %s where %s not like 'a'"), lpqt->szTableName,
lpqt->buf, lpqt->buf);
rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

for(i = 0;; i++)
{
rc = SQLFetch(hstmt);
if(rc != SQL_SUCCESS)
break;

rc = SQLGetData(hstmt, 1, SQL_C_TCHAR, lpqt->sz, MAX_STRING_SIZE,
&cbValue);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);
}

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

if(i != cTypes)
DISPLAYERROR(TEXT("'LIKE' query"),TEXT("incorrect number of result rows"));

}

FreeStmt(SQL_CLOSE);

} //TestLikeQuery()

//-----------------------------------------------------------------------
//      Function:               TestLikeQuery
//-----------------------------------------------------------------------

void  PASCAL TestOJCap(QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
UDWORDudw;

rc = SQLGetInfo(hdbc, (UWORD)SQL_OJ_CAPABILITIES, &udw, (SWORD)(sizeof(udw)), NULL);
if(RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO))
{
udw &= ~(SQL_OJ_LEFT | SQL_OJ_RIGHT | SQL_OJ_FULL | SQL_OJ_NESTED|
SQL_OJ_NOT_ORDERED | SQL_OJ_INNER | SQL_OJ_ALL_COMPARISON_OPS);
if(udw)
{
wsprintf(lpqt->buf,TEXT("Undefined flags return from SQLGetInfo(...SQL_OJ_CAPABILITIES...) = %lX"), udw);
DISPLAYERROR(szSQLGETINFO, lpqt->buf);
}
}

rc = SQLGetInfo(hdbc, SQL_OUTER_JOINS, lpqt->buf, MAX_STRING_SIZE, NULL);
if(RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO)){
*lpqt->buf = toupper(*lpqt->buf);
if((!_tcsnccmp(lpqt->buf,TEXT("Y"), 1)) && (!_tcsnccmp(lpqt->buf,TEXT("N"), 1)))
DISPLAYERROR(szSQLGETINFO,TEXT("Driver returned an invalid value for SQLGetInfo(...SQL_OUTER_JOINS...).  The only valid values are \"Y\" and \"N\"."));
}

} //TestOJCap()

/*-----------------------------------------------------------------------
 *      Function:               TestExtendedFetch
 *-----------------------------------------------------------------------*/

void  PASCAL TestExtendedFetch(QTSTRUCT  *lpqt, FIELDINFO  *rgFields, UWORD cTypes)
{
RETCODErc=SQL_SUCCESS;
WORDfLevel2 = FALSE;
DWORDdwLen=0;
SWORDfSqlType;
SDWORDcbValue=0;
UWORDiTableCol,row,iCol,uwRowSetSize;
TCHARszQuery[MAX_STRING_SIZE], szColName[SMALLBUFF];
TCHAR*rgbValue;
DSTRUCT*rgData;
DSTRUCT*pDataBuf;
BOOLfCol0Bound=TRUE;

uwRowSetSize=5; /* Cinco seems like a good rowset size */

/* Only col which might not be bound but might not be AutoUpdateable is col 0 */
if(!qtMakeData(0, 0, &rgFields[0], szQuery))
fCol0Bound = FALSE;

SelectFromTable(lpqt);

rc = SQLBindCol(hstmt, 1, SQL_C_BINARY, lpqt->sz, MAX_BIND_ARRAY_ELEMENT,
&cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDCOL);

if (!Supported(SQL_API_SQLEXTENDEDFETCH))
{
rc = SQLExtendedFetch(hstmt, SQL_FETCH_FIRST, IGNORED, NULL, NULL);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLEXTENDEDFETCH, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLEXTENDEDFETCH);

FreeStmt(SQL_CLOSE);
return;
}

rc = SQLGetStmtOption(hstmt,SQL_CURSOR_TYPE,&dwLen);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTOPTION);

if(dwLen != SQL_CURSOR_FORWARD_ONLY)
{
rc = SQLExtendedFetch(hstmt, SQL_FETCH_FIRST, IGNORED, NULL, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLEXTENDEDFETCH);
}
else
{
rc = SQLExtendedFetch(hstmt, SQL_FETCH_FIRST, IGNORED, NULL, NULL);
RETCHECK(SQL_ERROR, rc,szSQLEXTENDEDFETCH);
}

rc = SQLFreeStmt(hstmt, SQL_CLOSE);

RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLFreeStmt(hstmt, SQL_UNBIND);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

/* Allocate an area for ExtendedFetch to take place */
if (!(rgData = AllocateMemory(sizeof(DSTRUCT)*cTypes*uwRowSetSize))){
DISPLAYERROR(szSQLEXTENDEDFETCH,TEXT("Insufficient Memory Available"));
return;
}

/* Allocate an area for test data */
if (!(rgbValue = AllocateMemory(MAX_STRING_SIZE))){
DISPLAYERROR(szSQLEXTENDEDFETCH,TEXT("Insufficient Memory Available"));
ReleaseMemory(rgData);
return;
}

/* Get column for "order by" clause: */
wsprintf(szQuery,TEXT("select %s from %s"),lpqt->szColNames, lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
if((SQL_SUCCESS!=rc) && (SQL_SUCCESS_WITH_INFO!=rc)){
RETCHECK(SQL_SUCCESS_WITH_INFO, rc, szSQLEXECDIRECT);
goto abortxfetch;
}
iCol=0;
do{
iCol++;
rc=SQLDescribeCol(hstmt, iCol, szColName, (SMALLBUFF*sizeof(TCHAR)),
NULL, &fSqlType, NULL, NULL, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBECOL);
} while(fSqlType!=SQL_CHAR && iCol<=cTypes);

FreeStmt(SQL_CLOSE);
if(iCol>cTypes){
DISPLAYERROR(szSQLEXTENDEDFETCH, TEXT("\t\t\tSelect-order by  Failed"));
goto abortxfetch;
}

/* Set rowset size and set to row-wise binding:  */
rc = SQLSetStmtOption(hstmt, SQL_ROWSET_SIZE, (UDWORD)uwRowSetSize);
if (SQL_SUCCESS != rc){
DISPLAYERROR(TEXT("ExtendedFetch...SQLSetStmtOption(Rowset_Size)"),
TEXT("SQLSetStmtOption Failed"));
GetErrors(henv, hdbc,hstmt);
}
rc = SQLSetStmtOption(hstmt, SQL_BIND_TYPE, (sizeof(DSTRUCT)*cTypes));
if (SQL_SUCCESS != rc){
DISPLAYERROR(TEXT("ExtendedFetch...SQLSetStmtOption(Bind_Type)"),
TEXT("SQLSetStmtOption Failed"));
GetErrors(henv, hdbc,hstmt);
}

if(SQL_SUCCESS != SelectFromTableFetch(lpqt, szColName)){
DISPLAYERROR(szSQLEXTENDEDFETCH, TEXT("\t\t\tSelect-order by  Failed"));
goto abortxfetch;
}
if (!BindFetchColumns(lpqt, rgFields, cTypes, rgData)){
DISPLAYERROR(szSQLEXTENDEDFETCH, szSQLBINDCOL);
goto abortxfetch;
}
/* Fetch into allocated area rowset to be tested */
rc = SQLExtendedFetch(hstmt, SQL_FETCH_NEXT, 1, NULL, NULL);
if (SQL_SUCCESS != rc){
RETCHECK(SQL_SUCCESS, rc,szSQLEXTENDEDFETCH);
DISPLAYERROR(szSQLEXTENDEDFETCH, TEXT("Fetch Failed"));
goto abortxfetch;
}

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLFreeStmt(hstmt, SQL_UNBIND);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

/* Set rowset size to "1" to test each row of rowset independently:  */
rc = SQLSetStmtOption(hstmt, SQL_ROWSET_SIZE, (UDWORD)(1));
if (SQL_SUCCESS != rc){
DISPLAYERROR(TEXT("ExtendedFetch...SQLSetStmtOption(Rowset_Size)"),
TEXT("SQLSetStmtOption Failed"));
GetErrors(henv, hdbc,hstmt);
}

 /* Check data in all rows of rowset */
if(SQL_SUCCESS != SelectFromTableFetch(lpqt, szColName)){
DISPLAYERROR(szSQLEXTENDEDFETCH, TEXT("\t\t\tSelect-order by  Failed"));
goto abortxfetch;
}

// Need to change this loop if number of rows in table < uwRowSetSize
for (row=0; row<uwRowSetSize; row++)
{
SWORD iResultSetCol=0;
if (SQL_SUCCESS != (rc = SQLFetch(hstmt))){
RETCHECK(SQL_SUCCESS, rc, szSQLFETCH);
DISPLAYERROR(szSQLEXTENDEDFETCH, TEXT("Fetch Failed"));
goto abortxfetch;
}

pDataBuf=rgData+row*cTypes;
for (iTableCol=0; iTableCol<cTypes; iTableCol++)
{
/* Ignore columns which aren't bound as they aren't in the result set */
if((!iTableCol && !fCol0Bound) || rgFields[iTableCol].fAutoUpdate)
continue;

iResultSetCol++;
rc = SQLGetData(hstmt, iResultSetCol, SQL_C_TCHAR, rgbValue, MAX_STRING_SIZE,
 &cbValue);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);

if(!CheckDataResults(row, iTableCol, &rgFields[iTableCol], pDataBuf,cbValue,rgbValue))
{
wsprintf(lpqt->buf, TEXT("Data Check Failed, Row %d, Table Col %d, DataType %d (%s)"),
row, iTableCol, rgFields[iTableCol].wSQLType, rgFields[iTableCol].szFieldName) ;
szWrite(lpqt->buf,FALSE);
rgbValue[MAX_STRING_SIZE-1]=TEXT('\0');
wsprintf(lpqt->buf, TEXT("Returned Data: (%s)"), rgbValue);
DISPLAYERROR(TEXT("ExtendedFetch"),lpqt->buf);
// goto abortxfetch;
}
}
}

abortxfetch:
FreeStmt(SQL_CLOSE);
FreeStmt(SQL_UNBIND);
ReleaseMemory(rgData);
ReleaseMemory(rgbValue);
} /* TestExtendedFetch() */

/*------------------------------------------------------------------------------
 *      Function:       TestQuickRebind Feature
 *      Purpose:        To test this specific feature, not the function(s) using it.
 *      Synopsis:
 *      An area large enough for 12 rows of QRDSTRUCTs will be created. There will
 * be two rows of the result set per Rowset. Rowset 1 should be duplicated
 * in rows 0,1 and 3,4, while Rowset 2 should be duplicated in rows 6,7 and 9,10.
 * Thus, rows 0 and 3 will contain the first row of the result set, rows 1 and
 * 4 will contain the second row of the result set, etc.  Rows 2, 5, 8, and
 * 11 should have their preset condition of all zeroes remain unchanged.
 * ----------------------------------------------------------------------------*/

void  PASCAL TestQuickRebind(QTSTRUCT * lpqt, FIELDINFO  *rgFields, UWORD cTypes,
  DSTRUCT  *lpd)
{
RETCODErc;
UWORDrow,iCol,iRowset,uwRowsetSize,iRowsetRow,iTableCol;
UDWORDudwRowSize;
SWORDfSqlType;
SDWORDsdwOffset;
DSTRUCT*rgData, *pZeroRow;
DSTRUCT*pCurrentRowData;
TCHARszQuery[MAX_STRING_SIZE], szColName[SMALLBUFF];
SDWORDcbValue;
TCHAR*rgbValue=NULL;
BOOLfCol0Bound=TRUE;

udwRowSize = sizeof(DSTRUCT)*cTypes;
/* cNumResSetCols is set in BuildInsertStmt() */
uwRowsetSize = 5;/* uwRowsetSize rows per Rowset */

/* Only col which might not be bound but might not be AutoUpdateable is col 0 */
if(!qtMakeData(0, 0, &rgFields[0], szQuery))
fCol0Bound = FALSE;

/* Allocate an area for QuickRebind to take place */
if (!(rgData = AllocateMemory(NUM_QUIKREBIND_ROWSETS*(uwRowsetSize+1)*udwRowSize) )){
DISPLAYERROR(szQUICKREBIND,TEXT("\t\t\tInsufficient Memory Available"));
return;
}

/* Allocate an area for a row of zeroes to place between each Rowset. */
if (!(pZeroRow = AllocateMemory(udwRowSize) )){
DISPLAYERROR(szQUICKREBIND,TEXT("\t\t\tInsufficient Memory Available"));
goto abortQuikRebind;
}

/* Allocate an area for test data */
if (!(rgbValue = AllocateMemory(MAX_STRING_SIZE))){
DISPLAYERROR(szSQLEXTENDEDFETCH,TEXT("Insufficient Memory Available"));
goto abortQuikRebind;
}

ResetHstmt(&hstmt);

/* Get column for "order by" clause: */
wsprintf(szQuery,TEXT("select %s from %s"),lpqt->szColNames, lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
if((SQL_SUCCESS!=rc) && (SQL_SUCCESS_WITH_INFO!=rc)){
RETCHECK(SQL_SUCCESS_WITH_INFO, rc, szSQLEXECDIRECT);
goto abortQuikRebind;
}
iCol=0;
do{
iCol++;
rc=SQLDescribeCol(hstmt, iCol, szColName, (SMALLBUFF*sizeof(TCHAR)),
NULL, &fSqlType, NULL, NULL, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBECOL);
} while(fSqlType!=SQL_CHAR && iCol<=cTypes);

FreeStmt(SQL_CLOSE);
if(iCol>=cTypes){
DISPLAYERROR(szQUICKREBIND, TEXT("\t\t\tSelect-order by  Failed"));
goto abortQuikRebind;
}

/* Set to row-wise binding and set Rowset size to uwRowsetSize:  */
rc = SQLSetStmtAttr(hstmt,SQL_ATTR_ROW_BIND_TYPE,(PTR)udwRowSize,SQL_IS_INTEGER);
if (SQL_SUCCESS != rc){
_tcscpy(lpqt->buf,TEXT("\t\t\tQuick Rebind FAILED...SQLSetDescField(Bind_Type)"));
szWrite(lpqt->buf, TRUE);
goto abortQuikRebind;
}
rc = SQLSetStmtAttr(hstmt,SQL_ATTR_ROW_ARRAY_SIZE,(PTR)uwRowsetSize,SQL_IS_INTEGER);
if (SQL_SUCCESS != rc){
DISPLAYERROR(szQUICKREBIND,TEXT("\t\t\tSetStmtAttr(Rowset Size)"));
goto abortQuikRebind;
}

/* Define area storing Quikrebind offset value: */
rc = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, &sdwOffset, SQL_IS_POINTER);
if (SQL_SUCCESS != rc){
if(FindError(SQL_HANDLE_STMT,szHYC00)){
_tcscpy(lpqt->buf,TEXT("\t\t\tQuickRebind Not Supported"));
szWrite(lpqt->buf, TRUE);
goto abortQuikRebind;
}
_tcscpy(lpqt->buf,TEXT("\t\t\tQuick Rebind FAILED...QuickRebind Not Functional"));
szWrite(lpqt->buf, TRUE);
goto abortQuikRebind;
}

if(SQL_SUCCESS != SelectFromTableFetch(lpqt, szColName)){
DISPLAYERROR(szQUICKREBIND, TEXT("\t\t\tSelect-order by  Failed"));
goto abortQuikRebind;
}

if (!BindFetchColumns(lpqt, rgFields, cTypes, rgData)){
DISPLAYERROR(szQUICKREBIND,szSQLBINDCOL);
goto abortQuikRebind;
}

/* Test quick rebind feature with two pairs of Rowsets, all *
 * rowsets separated by a row of zeroes:                    */
for (iRowset=0; iRowset<=1; iRowset++)
{
if(iRowset)
if(SQL_SUCCESS != SelectFromTableFetch(lpqt, szColName)){
DISPLAYERROR(szQUICKREBIND, TEXT("\t\t\tSelect-order by  Failed"));
goto abortQuikRebind;
}

/* Set offset value (to 0 or 1  * uwRowsetSize * udwRowSize) for 1st rowset */
sdwOffset = iRowset*((1+uwRowsetSize)*udwRowSize);

/* Fetch rowset into desired area */
rc = SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 1);
if (SQL_SUCCESS != rc){
ERRSHANDLE(SQL_HANDLE_STMT, hstmt, SQL_SUCCESS, rc, szSQLFETCHSCROLL);
DISPLAYERROR(szQUICKREBIND,TEXT("Fetch Failed"));
goto abortQuikRebind;
}

/* Set offset value (to 2 or 3 * uwRowsetSize * udwRowSize) for 2nd rowset */
sdwOffset = (iRowset+2)*(1+uwRowsetSize)*udwRowSize;

rc = SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 1);
if (SQL_SUCCESS != rc){
ERRSHANDLE(SQL_HANDLE_STMT, hstmt, SQL_SUCCESS, rc, szSQLFETCHSCROLL);
DISPLAYERROR(szQUICKREBIND,TEXT("Fetch Failed"));
goto abortQuikRebind;
}
FreeStmt(SQL_CLOSE);
}

rc = SQLFreeStmt(hstmt, SQL_UNBIND);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

rc = SQLSetStmtAttr(hstmt,SQL_ATTR_ROW_ARRAY_SIZE,(PTR)1,SQL_IS_INTEGER);
if (SQL_SUCCESS != rc){
DISPLAYERROR(szQUICKREBIND,TEXT("\t\t\tSetStmtAttr(Rowset Size)"));
goto abortQuikRebind;
}

/* Check stored values: */
for (iRowset=0; iRowset<=1; iRowset++)
{
if(SQL_SUCCESS != SelectFromTableFetch(lpqt, szColName)){
DISPLAYERROR(szQUICKREBIND, TEXT("\t\t\tSelect-order by  Failed"));
goto abortQuikRebind;
}

/* First Rowset starts either in Allocated Row 0 or 3 */
iRowsetRow= iRowset*(1+uwRowsetSize);
//for (row=0; row<NUM_QUIKREBIND_ROWSETS  ;row++,iRowsetRow++)
for (row=0; row<(uwRowsetSize*2)  ;row++,iRowsetRow++)
{
UWORD iResultSetCol=0;
if(row==uwRowsetSize)
iRowsetRow+= 2+uwRowsetSize; /* Jump to 2nd Rowset */

/* Don't check data in "zero" row, *
 * the spacer row between rowsets:*/
if(iRowsetRow%(1+uwRowsetSize) == uwRowsetSize)
continue;

/* Fetch next rowset */
rc = SQLFetch(hstmt);
if (SQL_SUCCESS != rc){
ERRSHANDLE(SQL_HANDLE_STMT, hstmt, SQL_SUCCESS, rc, szSQLFETCH);
DISPLAYERROR(szQUICKREBIND,TEXT("Fetch Failed"));
goto abortQuikRebind;
}

pCurrentRowData = rgData+iRowsetRow*cTypes;
for(iTableCol=0; iTableCol<cTypes; iTableCol++)
{
/* Ignore columns which aren't bound as they aren't in the result set */
if((!iTableCol && !fCol0Bound) || rgFields[iTableCol].fAutoUpdate)
continue;

iResultSetCol++;
rc = SQLGetData(hstmt, iResultSetCol, SQL_C_TCHAR, rgbValue, MAX_STRING_SIZE,
&cbValue);
if(rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);

if(!CheckDataResults(row, iTableCol, &rgFields[iTableCol], pCurrentRowData,cbValue,rgbValue))
{
wsprintf(lpqt->buf, TEXT("Data Check Failed, Row %d, Table Col %d, DataType %d (%s)"),
row, iTableCol, rgFields[iTableCol].wSQLType, rgFields[iTableCol].szFieldName) ;
szWrite(lpqt->buf,FALSE);
rgbValue[MAX_STRING_SIZE-1]=TEXT('\0');
wsprintf(lpqt->buf, TEXT("Returned Data: (%s)"), rgbValue);
DISPLAYERROR(szQUICKREBIND,lpqt->buf);
goto abortQuikRebind;
}
}/* end for(iTableCol) */
} /* end for(row)  */
FreeStmt(SQL_CLOSE);
} /* end for (iRowset)  */

/* Check Unused memory areas:    */
for (iRowsetRow=uwRowsetSize; iRowsetRow<NUM_QUIKREBIND_ROWSETS*(uwRowsetSize+1);  )
{
pCurrentRowData = rgData+iRowsetRow*cTypes;
if(memcmp(pCurrentRowData, pZeroRow, udwRowSize))
DISPLAYERROR(szQUICKREBIND,
TEXT("Unused memory areas altered after Quick Rebind."));
iRowsetRow+=(uwRowsetSize+1);
}

abortQuikRebind:
ResetHstmt(&hstmt);
if (rgbValue)
ReleaseMemory(rgbValue);
if (pZeroRow)
ReleaseMemory(pZeroRow);
ReleaseMemory(rgData);

} /* TestQuickRebind() */

//-----------------------------------------------------------------------
//      Function:               TestSQLForeignKeys
//-----------------------------------------------------------------------

void  PASCAL TestSQLForeignKeys(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

if (!Supported(SQL_API_SQLFOREIGNKEYS))
{
rc = SQLForeignKeys(hstmt, NULL, 0, NULL, 0, lpqt->szTableName,
SQL_NTS, NULL, 0, NULL, 0, NULL, 0);

if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLFOREIGNKEYS, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLFOREIGNKEYS);
}
else
{
rc = SQLForeignKeys(hstmt, NULL, 0, NULL, 0, lpqt->szTableName,
SQL_NTS, NULL, 0, NULL, 0, NULL, 0);

RETCHECK(SQL_SUCCESS, rc,szSQLFOREIGNKEYS);

while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);

}

} //TestSQLForeignKeys()

//-----------------------------------------------------------------------
//      Function:               TestSQLBrowseConnect
//-----------------------------------------------------------------------

void  PASCAL TestSQLBrowseConnect(lpSERVERINFO lpSI)
{
RETCODErc=SQL_SUCCESS;
UWORDcMaxConnections=0;
TCHARszBCString[40];
TCHARszDSN[40];
HDBChdbcb=NULL;

AllocHdbc(&hdbcb);

rc = SQLGetInfo(hdbc, SQL_ACTIVE_CONNECTIONS, &cMaxConnections, sizeof(int), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

if(cMaxConnections != 1)
{
lstrcpy(szBCString,TEXT("DSN="));
lstrcat(szBCString, lpSI->szValidServer0);

if(lpSI->szValidServer0[0] == 0)
{
rc = SQLGetInfo(hdbc, SQL_DATA_SOURCE_NAME, &szDSN, 40, NULL);
lstrcat(szBCString, szDSN);
}

if(!Supported(SQL_API_SQLBROWSECONNECT))
{
HDBC thdbc=NULL;

rc = SQLBrowseConnect(hdbcb, szBCString, SQL_NTS, NULL, 0, NULL);
thdbc = hdbc;
hdbc = hdbcb;
if(!FindError(SQL_HANDLE_DBC,szIM001))
DISPLAYERROR(szSQLBROWSECONNECT, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLBROWSECONNECT);
hdbc = thdbc;

rc = SQLFreeConnect(hdbcb);
RETCHECK(SQL_SUCCESS, rc,szSQLFREECONNECT);

}
else
{
rc = SQLBrowseConnect(hdbcb, szBCString, SQL_NTS, NULL, 0, NULL);
RETCHECK(SQL_NEED_DATA, rc,szSQLBROWSECONNECT);

rc = SQLDisconnect(hdbcb);
RETCHECK(SQL_SUCCESS, rc,szSQLDISCONNECT);
rc = SQLFreeConnect(hdbcb);
RETCHECK(SQL_SUCCESS, rc,szSQLFREECONNECT);
}
}

} //TestSQLBrowseConnect()

//-----------------------------------------------------------------------
//      Function:               TestSQLDataSources
//-----------------------------------------------------------------------

void  PASCAL TestSQLDataSources()
{
RETCODE rc=SQL_SUCCESS;

if (!Supported( SQL_API_SQLDATASOURCES))
{
rc = SQLDataSources(henv, SQL_FETCH_FIRST, NULL, 0, NULL, NULL, 0, NULL);

if(!FindError(SQL_HANDLE_ENV,szIM001))
DISPLAYERROR(TEXT("SQLDataSources"), szNotSupported);

RETCHECK(SQL_ERROR, rc,TEXT("SQLDataSources"));
}
else
{
rc = SQLDataSources(henv, SQL_FETCH_FIRST, NULL, 0, NULL, NULL, 0, NULL);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLDataSources"));
}

} //TestSQLDataSources()

//-----------------------------------------------------------------------
//      Function:               TestSQLDataSources
//-----------------------------------------------------------------------

void  PASCAL TestSQLDrivers()
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLDRIVERS))
{
rc = SQLDrivers(henv, SQL_FETCH_FIRST, NULL, 0, NULL, NULL, 0, NULL);
if(!FindError(SQL_HANDLE_ENV,szIM001))
DISPLAYERROR(TEXT("SQLDrivers"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLDataSources"));
}
else
{
rc = SQLDrivers(henv, SQL_FETCH_FIRST, NULL, 0, NULL, NULL, 0, NULL);
if(rc != SQL_SUCCESS)
RETCHECK(SQL_SUCCESS_WITH_INFO, rc,TEXT("SQLDrivers"));
}

} //TestSQLDrivers()

//-----------------------------------------------------------------------
//      Function:               TestSQLMoreResults
//-----------------------------------------------------------------------

void  PASCAL TestSQLMoreResults(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLMORERESULTS))
{
SelectFromTable(lpqt);

rc = SQLMoreResults(hstmt);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(TEXT("SQLMoreResults"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLMoreResults"));
}
else
{
SelectFromTable(lpqt);

rc = SQLMoreResults(hstmt);
RETCHECK(SQL_NO_DATA_FOUND, rc,TEXT("SQLMoreResults"));
}

FreeStmt(SQL_CLOSE);

} //TestSQLMoreResults()

//-----------------------------------------------------------------------
//      Function:               TestSQLMoreResults
//-----------------------------------------------------------------------

void  PASCAL TestSQLNativeSQL(QTSTRUCT  *lpqt)
{
RETCODErc=SQL_SUCCESS;
TCHARszQuery[XLARGEBUFF];

wsprintf(szQuery,TEXT("select * from %s were 0 > 1"),lpqt->szTableName);

if(!Supported( SQL_API_SQLNATIVESQL))
{
rc = SQLNativeSql(hdbc, szQuery, SQL_NTS, NULL, 0, NULL);
if(!FindError(SQL_HANDLE_DBC,szIM001))
DISPLAYERROR(TEXT("SQLNativeSql"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLNativeSql"));
}
else
{
rc = SQLNativeSql(hdbc, szQuery, SQL_NTS, NULL, 0, NULL);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLNativeSql"));
}

FreeStmt(SQL_CLOSE);

} //TestSQLNativeSQL()

//-----------------------------------------------------------------------
//      Function:               TestSQLDescribeParam
//-----------------------------------------------------------------------

void  PASCAL TestSQLDescribeParam(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

rc = SQLPrepare(hstmt, lpqt->szParamQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);

if(!Supported( SQL_API_SQLDESCRIBEPARAM))
{
rc = SQLPrepare(hstmt, lpqt->szParamQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);
rc = SQLDescribeParam(hstmt, 1, NULL, NULL, NULL, NULL);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLDESCRIBEPARAM, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLDESCRIBEPARAM);
}
else
{
rc = SQLPrepare(hstmt, lpqt->szParamQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);
rc = SQLDescribeParam(hstmt, 1, NULL, NULL, NULL, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBEPARAM);
}

FreeStmt(SQL_CLOSE);

} //TestSQLDescribeParam()

//-----------------------------------------------------------------------
//      Function:               TestSQLNumParams
//-----------------------------------------------------------------------

void  PASCAL TestSQLNumParams(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

rc = SQLPrepare(hstmt, lpqt->szParamQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);

if(!Supported( SQL_API_SQLNUMPARAMS))
{
rc = SQLNumParams(hstmt, NULL);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLNUMPARAMS, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLNUMPARAMS);
}
else
{
rc = SQLNumParams(hstmt, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLNUMPARAMS);
}

FreeStmt(SQL_CLOSE);

} //TestSQLNumParams()

//-----------------------------------------------------------------------
//      Function:               TestSQLParamOptions
//-----------------------------------------------------------------------

void  PASCAL TestSQLParamOptions(QTSTRUCT *lpqt)
{
RETCODE rc=SQL_SUCCESS;

rc = SQLPrepare(hstmt, lpqt->szParamQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);

if(!Supported( SQL_API_SQLPARAMOPTIONS))
{
rc = SQLParamOptions(hstmt, 1, NULL);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLPARAMOPTIONS, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLPARAMOPTIONS);
}
else
{
rc = SQLParamOptions(hstmt, 1, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLPARAMOPTIONS);
}

FreeStmt(SQL_CLOSE);

} //TestSQLParamOptions()

//-----------------------------------------------------------------------
//      Function:               TestSQLPrimaryKeys
//-----------------------------------------------------------------------

void  PASCAL TestSQLPrimaryKeys(QTSTRUCT *lpqt)
{
RETCODE rc=SQL_SUCCESS;

if (!Supported( SQL_API_SQLPRIMARYKEYS))
{
rc = SQLPrimaryKeys(hstmt, NULL, 0, NULL, 0, lpqt->szTableName, SQL_NTS);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLPRIMARYKEYS, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLPRIMARYKEYS);
}
else
{
rc = SQLPrimaryKeys(hstmt, NULL, 0, NULL, 0, lpqt->szTableName, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPRIMARYKEYS);
while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);

}

} //TestSQLPrimaryKeys()

//-----------------------------------------------------------------------
//      Function:               TestSQLProcedureColumns
//-----------------------------------------------------------------------

void  PASCAL TestSQLProcedureColumns(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLPROCEDURECOLUMNS))
{
rc = SQLProcedureColumns(hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(TEXT("SQLProcedureColumns"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLProcedureColumns"));
}
else
{
rc = SQLProcedureColumns(hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLProcedureColumns"));
while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);
}

} //TestSQLProcedureColumns()

//-----------------------------------------------------------------------
//      Function:               TestSQLProcedures
//-----------------------------------------------------------------------

void  PASCAL TestSQLProcedures()
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLPROCEDURES))
{
rc = SQLProcedures(hstmt, NULL, 0, NULL, 0, NULL, 0);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(TEXT("SQLProcedures"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLProcedures"));
}
else
{
rc = SQLProcedures(hstmt, NULL, 0, NULL, 0, NULL, 0);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLProcedures"));
while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);
RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);
}

} //TestSQLProcedures()

//-----------------------------------------------------------------------
//      Function:               TestSQLTablePrivileges
//-----------------------------------------------------------------------

void  PASCAL TestSQLTablePrivileges()
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLTABLEPRIVILEGES))
{
rc = SQLTablePrivileges(hstmt, NULL, 0, NULL, 0, NULL, 0);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(TEXT("SQLTablePrivileges"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLTablePrivileges"));
}
else
{
rc = SQLTablePrivileges(hstmt, NULL, 0, NULL, 0, NULL, 0);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLTablePrivileges"));
while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);
}

} //TestSQLTablePrivileges()

//-----------------------------------------------------------------------
//      Function:               TestSQLColumnPrivileges
//-----------------------------------------------------------------------

void  PASCAL TestSQLColumnPrivileges(QTSTRUCT  *lpqt)
{
RETCODE rc=SQL_SUCCESS;

if(!Supported( SQL_API_SQLCOLUMNPRIVILEGES))
{
rc = SQLColumnPrivileges(hstmt, NULL, 0, NULL, 0, lpqt->szTableName, SQL_NTS,
NULL, 0);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(TEXT("SQLColummPrivileges"), szNotSupported);
RETCHECK(SQL_ERROR, rc,TEXT("SQLColumnPrivileges"));
}
else
{
rc = SQLColumnPrivileges(hstmt, NULL, 0, NULL, 0, lpqt->szTableName, SQL_NTS,
NULL, 0);
RETCHECK(SQL_SUCCESS, rc,TEXT("SQLColumnPrivileges"));
while(rc == SQL_SUCCESS)
rc = SQLFetch(hstmt);

RETCHECK(SQL_NO_DATA_FOUND, rc,szSQLFETCH);

FreeStmt(SQL_CLOSE);
}

} // TestSQLColumnPrivileges()

//-----------------------------------------------------------------------
//      Function:               TestSQLSetScrollOptions
//-----------------------------------------------------------------------

void  PASCAL TestSQLSetScrollOptions()
{
RETCODErc=SQL_SUCCESS;
UWORDi,
j;
UDWORDfSupportedOpt;
UDWORDfSupportedCon;
TCHARszState[100]=TEXT(""),
szErrMsg[XLARGEBUFF]=TEXT("");

FreeStmt(SQL_CLOSE);

if(!Supported( SQL_API_SQLSETSCROLLOPTIONS))
{
rc = SQLSetScrollOptions(hstmt, SQL_CONCUR_READ_ONLY, SQL_SCROLL_FORWARD_ONLY,
1);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLSETSCROLLOPTIONS, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLSETSCROLLOPTIONS);
}
else
{
rc = SQLGetInfo(hdbc, SQL_SCROLL_CONCURRENCY, &fSupportedCon, 4, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);
rc = SQLGetInfo(hdbc, SQL_SCROLL_OPTIONS, &fSupportedOpt, 4, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

for(i = 0; i < sizeof(OptionList) / sizeof(SUPPORTOPTINFO); i ++)
{
for(j = 0; j < sizeof(ConcurList) / sizeof(SUPPORTCONCURINFO); j++)
{
if(fSupportedOpt & OptionList[i].Support && fSupportedCon & ConcurList[j].Support)
{
if(!((ConcurList[j].Option == SQL_CONCUR_VALUES) && ((lpSI->vCursorLib ==
SQL_CUR_USE_IF_NEEDED) || (lpSI->vCursorLib == SQL_CUR_USE_ODBC))))
{
rc = SQLSetScrollOptions(hstmt, ConcurList[j].Option,OptionList[i].Option, 1);
RETCHECK(SQL_SUCCESS, rc,szSQLSETSCROLLOPTIONS);
}

}
}
}
}

FreeStmt(SQL_CLOSE);

} //TestSQLSetScrollOptions()

//-----------------------------------------------------------------------------
//Function:  TestConnectAttr
//-----------------------------------------------------------------------------

VOID PASCAL TestConnectAttr(HDBC hdbc,SDWORD fAttribute, UDWORD rgbValue,LPTSTR lpAttr,BOOL fReadOnlyAttr)
{
RETCODErc=SQL_SUCCESS;
TCHARszBuff[LARGEBUFF]=TEXT("");
UDWORDpvParam=0;
RETCODErcExp=fReadOnlyAttr ? SQL_ERROR : SQL_SUCCESS;

rc = SQLSetConnectAttr(hdbc,fAttribute,(PTR)rgbValue,sizeof(rgbValue));

if(!FindError(SQL_HANDLE_DBC,szHYC00))
{

RETCHECK(rcExp,rc,szSQLSETCONNECTATTR);

rc = SQLGetConnectAttr(hdbc,fAttribute,&pvParam,sizeof(pvParam),NULL);

if(!FindError(SQL_HANDLE_DBC,szHYC00))
{
RETCHECK(SQL_SUCCESS,rc,szSQLGETCONNECTATTR);

if(!fReadOnlyAttr && (pvParam != rgbValue))
{
wsprintf(szBuff,TEXT("%s returned incorrect value"),lpAttr);
DISPLAYERROR(szSQLGETCONNECTATTR,szBuff);
}
}
else
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
}
else
RETCHECK(SQL_ERROR,rc,szSQLSETCONNECTATTR);

} //TestConnectAttr()

//-----------------------------------------------------------------------------
//Function:  TestSQLSetConnectAttr
//     This function tests both SQLSetConnectAttr and SQLGetConnectAttr
//-----------------------------------------------------------------------------

void  PASCAL TestSQLSetConnectAttr()
{
RETCODE rc = SQL_SUCCESS;
UDWORDpvParam;

UWORDrgbValue;
BOOLfSupportSetConnectAttr = TRUE;
BOOLfSupportGetConnectAttr = TRUE;

//make sure these APIs are supported before testing
if(!Supported(SQL_API_SQLSETCONNECTATTR))
{
rgbValue = SQL_MODE_READ_WRITE;
fSupportSetConnectAttr = FALSE;
rc = SQLSetConnectAttr(hdbc,SQL_ACCESS_MODE,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_DBC,szIM001))
DISPLAYERROR(szSQLSETCONNECTATTR,TEXT("did not return Not supported message"));
RETCHECK(SQL_ERROR,rc,szSQLSETCONNECTATTR);
}

if(!Supported(SQL_API_SQLGETCONNECTATTR))
{
fSupportGetConnectAttr = FALSE;
rc = SQLGetConnectAttr(hdbc,SQL_ACCESS_MODE,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_DBC,szIM001))
DISPLAYERROR(szSQLGETCONNECTATTR,TEXT("did not return Not supported message"));
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
}

if(fSupportSetConnectAttr && (!fSupportGetConnectAttr))
{
//test SQL_ACCESS_MODE
rgbValue = SQL_MODE_READ_WRITE;
rc = SQLSetConnectAttr(hdbc,SQL_ACCESS_MODE,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLSETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLSETCONNECTATTR);
//test SQL_AUTOCOMMIT
rgbValue = SQL_AUTOCOMMIT_ON;
rc = SQLSetConnectAttr(hdbc,SQL_AUTOCOMMIT,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLSETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLSETCONNECTATTR);
//test SQL_LOGIN_TIMEOUT
//the choice of 15 seconds for timeout is arbitrary
rgbValue = 15;
rc = SQLSetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLSETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLSETCONNECTATTR);
}

if((!fSupportSetConnectAttr) && fSupportGetConnectAttr)
{
//test SQL_ACCESS_MODE
rc = SQLGetConnectAttr(hdbc,SQL_ACCESS_MODE,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
//test SQL_AUTOCOMMIT
rc = SQLGetConnectAttr(hdbc,SQL_AUTOCOMMIT,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
//test SQL_LOGIN_TIMEOUT
rc = SQLGetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
//SQL_ATTR_AUTO_IPD is a read-only attribute
rc = SQLGetConnectAttr(hdbc,SQL_ATTR_AUTO_IPD,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_DBC,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETCONNECTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETCONNECTATTR);
}

if(fSupportSetConnectAttr && fSupportGetConnectAttr)
{

//test SQL_ACCESS_MODE
TestConnectAttr(hdbc,SQL_ACCESS_MODE,SQL_MODE_READ_WRITE,TEXT("SQL_ACCESS_MODE"),FALSE);

//test SQL_AUTOCOMMIT
TestConnectAttr(hdbc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_ON,TEXT("SQL_AUTOCOMMIT"),FALSE);

//test SQL_LOGIN_TIMEOUT
//the choice of 15 seconds for timeout is arbitrary
TestConnectAttr(hdbc,SQL_LOGIN_TIMEOUT,15,TEXT("SQL_LOGIN_TIMEOUT"),FALSE);

//SQL_ATTR_AUTO_IPD is a read-only attribute
TestConnectAttr(hdbc,SQL_ATTR_AUTO_IPD,15,TEXT("SQL_ATTR_AUTO_IPD"),TRUE);
}

} //TestSQLSetConnectAttr()

//-----------------------------------------------------------------------------
//Function:  TestSQLSetStmtAttr
//     This function tests both SQLSetStmtAttr and SQLGetStmtAttr
//-----------------------------------------------------------------------------

void  PASCAL TestSQLSetStmtAttr()
{
RETCODE rc = SQL_SUCCESS, rc2= SQL_SUCCESS;
UDWORDpvParam;
UWORDrgbValue;
BOOLfSupportSetStmtAttr = TRUE;
BOOLfSupportGetStmtAttr = TRUE;

//make sure these APIs are supported before testing
if(!Supported(SQL_API_SQLSETSTMTATTR))
{
//the choice of 300 for SQL_MAX_LENGTH is arbitrary
rgbValue = 300;
fSupportSetStmtAttr = FALSE;
rc = SQLSetStmtAttr(hstmt,SQL_MAX_LENGTH,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLSETSTMTATTR,TEXT("did not return Not supported message"));
RETCHECK(SQL_ERROR,rc,szSQLSETSTMTATTR);
}

if(!Supported(SQL_API_SQLGETSTMTATTR))
{
fSupportGetStmtAttr = FALSE;
rc = SQLGetStmtAttr(hstmt,SQL_MAX_LENGTH,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLGETSTMTATTR,TEXT("did not return Not supported message"));
RETCHECK(SQL_ERROR,rc,szSQLGETSTMTATTR);
}

if(fSupportSetStmtAttr && (!fSupportGetStmtAttr))
{
//test SQL_MAX_LENGTH
rgbValue = 300;
rc = SQLSetStmtAttr(hstmt,SQL_MAX_LENGTH,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLSETSTMTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLSETSTMTATTR);
//test SQL_ATTR_METADATA_ID -- new in 3.0
rgbValue = SQL_FALSE;
rc = SQLSetStmtAttr(hstmt,SQL_ATTR_METADATA_ID,&rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLSETSTMTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLSETSTMTATTR);
}

if((!fSupportSetStmtAttr) && fSupportGetStmtAttr)
{
//test SQL_MAX_LENGTH
rc = SQLGetStmtAttr(hstmt,SQL_MAX_LENGTH,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETSTMTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETSTMTATTR);
//test SQL_ATTR_METADATA_ID -- new in 3.0
rc = SQLGetStmtAttr(hstmt,SQL_ATTR_METADATA_ID,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_STMT,szHYC00))
RETCHECK(SQL_SUCCESS,rc,szSQLGETSTMTATTR);
else
RETCHECK(SQL_ERROR,rc,szSQLGETSTMTATTR);
}

if(fSupportSetStmtAttr && fSupportGetStmtAttr)
{

//test SQL_MAX_LENGTH
rgbValue = 300;
rc = SQLSetStmtAttr(hstmt,SQL_MAX_LENGTH,(PTR)rgbValue,sizeof(rgbValue));
if(!FindError(SQL_HANDLE_STMT,szHYC00))
{
RETCHECK(SQL_SUCCESS,rc,szSQLSETSTMTATTR);
rc = SQLGetStmtAttr(hstmt,SQL_MAX_LENGTH,&pvParam,sizeof(pvParam),NULL);
if(!FindError(SQL_HANDLE_STMT,szHYC00))
{
RETCHECK(SQL_SUCCESS,rc,szSQLGETSTMTATTR);
if(pvParam != 300)
DISPLAYERROR(szSQLGETSTMTATTR,TEXT("SQL_MAX_LENGTH returned incorrect value"));
}
else
RETCHECK(SQL_ERROR,rc,szSQLGETSTMTATTR);
}
else
RETCHECK(SQL_ERROR,rc,szSQLSETSTMTATTR);

//test SQL_ATTR_METADATA_ID -- new in 3.0
rgbValue = SQL_FALSE;
rc = SQLSetStmtAttr(hstmt,SQL_ATTR_METADATA_ID,(PTR)rgbValue,sizeof(rgbValue));
if (!FindError(SQL_HANDLE_STMT,szHYC00) && !FindError(SQL_HANDLE_STMT,szHY092))
RETCHECK(SQL_SUCCESS,rc,szSQLSETSTMTATTR);

rc2 = SQLGetStmtAttr(hstmt,SQL_ATTR_METADATA_ID,&pvParam,sizeof(pvParam),NULL);
if (!FindError(SQL_HANDLE_STMT,szHYC00) && !FindError(SQL_HANDLE_STMT,szHY092))
RETCHECK(SQL_SUCCESS,rc,szSQLGETSTMTATTR);

if (RC_SUCCESSFUL(rc) && RC_SUCCESSFUL(rc2) && pvParam != SQL_FALSE)
DISPLAYERROR(szSQLGETSTMTATTR,TEXT("SQL_ATTR_METADATA_ID returned incorrect value"));
}

} //TestSQLSetStmtAttr()

//--------------------------------------------------------------------------------
// FUNCTION:TestOneThread
// PURPOSE:This function allocates the statement handle, performs simple
//queries and deallocates the statement handle.  This function is called
//by testthreading.
//--------------------------------------------------------------------------------
static int TestOneThread(THREAD_STRUCT *ptArg)
{
HENVhenv0 = ptArg->henv;
HDBChdbc0 = ptArg->hdbc;
HSTMThstmt0 = NULL;
//RETCODErc=SQL_SUCCESS;

/* allocate new statment handle */
if (RC_NOTSUCCESSFUL(SQLAllocHandle(SQL_HANDLE_STMT,hdbc0, &hstmt0)))
return(FALSE);

/* perform some simple catalog functions */
if (RC_NOTSUCCESSFUL(SQLStatistics(hstmt0,NULL,SQL_NTS,NULL,SQL_NTS,NULL,SQL_NTS,SQL_INDEX_ALL,SQL_QUICK)))
return(FALSE);

if (RC_NOTSUCCESSFUL(SQLCloseCursor(hstmt0)))
return(FALSE);

/* Get some data */
GetSomeData(hstmt0, ptArg->szTableName, TRUE,ptArg->rgFieldInfo);

if (RC_NOTSUCCESSFUL(SQLGetTypeInfo(hstmt0,SQL_CHAR)))
return(FALSE);

// Cleanup
if (RC_NOTSUCCESSFUL(SQLFreeHandle(SQL_HANDLE_STMT,hstmt0)))
return(FALSE);

return(TRUE);

} //end of TestOneThread

//--------------------------------------------------------------------------------
// FUNCTION:ThreadLoop
// PURPOSE:This is the main loop for the threads, and it's called by
//CreateThread() API.
//--------------------------------------------------------------------------------
DWORD WINAPI ThreadLoop(void *pArg)
{

THREAD_STRUCT *ptArg = (THREAD_STRUCT *)pArg;
int i;
RETCODErc;

for(i=0; i< NUM_THREAD; i++)
TestOneThread(ptArg);

// free connection handle for each thread
rc=SQLDisconnect(ptArg->hdbc);
    RETCHECK(SQL_SUCCESS,rc,szSQLDISCONNECT);
rc=SQLFreeHandle(SQL_HANDLE_DBC,ptArg->hdbc);
RETCHECK(SQL_SUCCESS,rc,szSQLFREEHANDLE);

   return(0);
} //end of ThreadLoop

//-------------------------------------------------------------------------------
// FUNCTION:TestThreading
// PURPOSE:This function tests the multi-threading capabilities of the
//driver by creating multiple threads that connect to the same data
// source.  Once connected, each thread performs simple queries to
//test that the driver handles these queries successfully.
//This function is to be called inside AutoTestFunc() in QuikTest.
//--------------------------------------------------------------------------------
void TestThreading(lpSERVERINFO lpSI,QTSTRUCT  *lpqt,FIELDINFO *rgFieldInfo)
{
RETCODE rc=SQL_SUCCESS;
   THREAD_STRUCT *pArg;
   HANDLE hThread[NUM_THREAD];
DWORD dwThreadId;
UWORD i;
HENVhenv1;
HDBChdbc1;

// allocate memory for the threads
pArg = (THREAD_STRUCT *)AllocateMemory((NUM_THREAD) * sizeof(THREAD_STRUCT));

if(!pArg)
{
szWrite(TEXT("\t\t\tMultiThreading Test interrupted due to error in memory allocation"), TRUE);
return;
}

// only one environment handle will be needed for all threads
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv1);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCHANDLE);

// Set environment attribute, or we can't allocate connection
rc = SQLSetEnvAttr(henv1, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,
SQL_IS_UINTEGER);
RETCHECK(SQL_SUCCESS, rc,szSQLSETENVATTR);

for (i=0; i < NUM_THREAD; i++ )
{
// allocate connection handle for each thread
rc=SQLAllocHandle(SQL_HANDLE_DBC,henv1, &hdbc1);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCHANDLE);

// create string to be used in connecting
wsprintf(lpqt->sz,TEXT("dsn=%s;uid=%s;pwd=%s;"),
lpSI->szValidServer0,
lpSI->szValidLogin0,
lpSI->szValidPassword0);

SQLDriverConnect(hdbc1, lpSI->hwnd, lpqt->sz,
SQL_NTS, lpqt->buf, MAX_STRING_SIZE, NULL, SQL_DRIVER_COMPLETE);

// initialize thread structure
pArg[i].henv = henv1;
pArg[i].hdbc = hdbc1;
lstrcpy(pArg[i].szTableName, lpqt->szTableName);
pArg[i].rgFieldInfo=rgFieldInfo[0];

// start thread
hThread[i]= CreateThread(NULL,
0,
ThreadLoop,
&pArg[i],
0,
&dwThreadId);

if(hThread[i]==0)
{
DISPLAYERROR(TEXT("TestThread failed:"),TEXT("cannot create thread"));
break;
}
}

// wait for thread completion
WaitForMultipleObjects(NUM_THREAD,hThread,TRUE,INFINITE);

for(i=0; i<NUM_THREAD; i++ )
    CloseHandle(hThread[i]);

SQLFreeHandle(SQL_HANDLE_ENV,henv1);
if (pArg)
ReleaseMemory(pArg);
}//end of TestThreading()

//-----------------------------------------------------------------------
//      Function:               CreateDescRecord
//-----------------------------------------------------------------------
SWORD CreateDescRecord(SQLHDESC hdesc, SQLHSTMT hstmt, UWORD uwDescType)
{
UDWORD cbColDef;
SWORD swColCount;
SQLRETURN rc;
SWORD fType, fSQLType;
SWORD ibScale;
TCHAR rgBuf[100];

switch(uwDescType) {
case SQL_ATTR_APP_ROW_DESC:
rc = SQLSetDescField(hdesc, 1, SQL_DESC_TYPE, (SQLPOINTER)SQL_C_DEFAULT,
SQL_IS_SMALLINT);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD);

if (SQL_SUCCEEDED(rc))
return 1;

rc=SQLBindCol(hstmt, 1, SQL_C_TCHAR, rgBuf, sizeof(rgBuf), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDCOL);
if (SQL_SUCCEEDED(rc))
return 1;
break;
case SQL_ATTR_IMP_PARAM_DESC:
case SQL_ATTR_APP_PARAM_DESC:
// Select statement has already been done by this time
rc=SQLDescribeCol(hstmt, 1, NULL, 0, NULL, &fSQLType, &cbColDef,
&ibScale, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBECOL);
if (!SQL_SUCCEEDED(rc))
return 0;

if (SQL_ATTR_APP_PARAM_DESC == uwDescType)
fType=SQL_C_TCHAR;
else
fType=fSQLType;

rc = SQLSetDescField(hdesc, 1, SQL_DESC_TYPE, (SQLPOINTER)fType,
SQL_IS_SMALLINT);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD);

if (SQL_SUCCEEDED(rc))
return 1;

rc=SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TCHAR,
fSQLType, cbColDef, ibScale, rgBuf, sizeof(rgBuf), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLDESCRIBECOL);
if (SQL_SUCCEEDED(rc))
return 1;
break;
case SQL_ATTR_IMP_ROW_DESC:
// Quiktest has already done the select for us
// Need to find out how many cols in result set
rc=SQLNumResultCols(hstmt, &swColCount);
if (SQL_SUCCEEDED(rc))
return(swColCount);
break;
}
return 0;
}//CreateDescRecord

//-----------------------------------------------------------------------
//      Function:               CheckDescOp
//-----------------------------------------------------------------------
void CheckDescOp(SQLHDESC hdesc, SQLRETURN rc, SQLUSMALLINT uwDescIndex,
SQLSMALLINT swDescRecCount, SQLSMALLINT swDescRec, SQLUSMALLINT iDescField,
SQLINTEGER swUpdateMode)
{
TCHAR * pszAPI, szOp[10];
SQLSMALLINT fValidOp;// Descriptor types operation is valid for

if (DESC_UPDATE_MODE_READ == swUpdateMode)
{
pszAPI=szSQLGETDESCFIELD;
lstrcpy(szOp, TEXT("read"));
fValidOp=rgDescInfo[iDescField].fGettable;
}
else
{
pszAPI=szSQLSETDESCFIELD;
lstrcpy(szOp, TEXT("write"));
fValidOp=rgDescInfo[iDescField].fSettable;
}

// If we're reading beyond the last record, expect SQL_NO_DATA.
if (swDescRec > swDescRecCount && DESC_UPDATE_MODE_READ == swUpdateMode)
{
wsprintf(buf,TEXT("%s: Able to %s descriptor field %s in the %s beyond last record."),
pszAPI, szOp, rgDescInfo[iDescField].szDescFieldName, DescTypes[uwDescIndex].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_NO_DATA, rc, buf);
}
else
{
if (fValidOp & DescTypes[uwDescIndex].uwTypeMask)
{
wsprintf(buf,TEXT("%s: Unable to %s descriptor field %s in the %s"),
pszAPI, szOp, rgDescInfo[iDescField].szDescFieldName, DescTypes[uwDescIndex].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, buf);
}
else
{
wsprintf(buf,TEXT("%s: Able to %s descriptor field %s in the %s"),
pszAPI, szOp, rgDescInfo[iDescField].szDescFieldName, DescTypes[uwDescIndex].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_ERROR, rc, buf);
}
}
}

//-----------------------------------------------------------------------
//
// Function: CompareDescField
//
// Compares two fields from the given descriptor, or to rgDescInfo if hdesc2
// is NULL.  uwDescField contains the entry in rgDescInfo for the desired field
//-----------------------------------------------------------------------

void CompareDescField(SQLHDESC hdesc1, SQLHDESC hdesc2, SQLUSMALLINT uwDescIndex,
SQLSMALLINT swDescRec, SQLSMALLINT swDescRecCount, SQLUSMALLINT uwDescField,
TCHAR * pszAPI)
{
SQLRETURN rc;
 SQLTCHAR DescBuf2[MAX_DESC_BUF];
SQLINTEGER cbValue2;

// Make sure we've got a valid descriptor handle for hdesc1
// (hdesc2 is allowed to be NULL)
if (SQL_NULL_HDESC == hdesc1) {
wsprintf(buf,TEXT("\t\tCompareDescriptor: hdesc1 handle was null!"));
DISPLAYERROR(pszAPI, buf);
return;
}

// Read the value from hdesc1
rc = SQLGetDescField(hdesc1, swDescRec, rgDescInfo[uwDescField].uwDescField,
&DescBuf, sizeof(DescBuf), &cbValue);

CheckDescOp(hdesc1, rc, uwDescIndex, swDescRecCount, swDescRec, uwDescField, DESC_UPDATE_MODE_READ);

cbValue2=rgDescInfo[uwDescField].cbValue;

// Get the value to compare to
if (SQL_NULL_HDESC == hdesc2) {

// Compare to rgDescInfo if it's settable
if (rgDescInfo[uwDescField].fSettable & DescTypes[uwDescIndex].uwTypeMask) {

// Copy the set value into DescBuf2.  For char fields size is 0
// (unknown), and the new value is actually the pointer to the start
// of the string.
SQLPOINTER pNewvalue = &(rgDescInfo[uwDescField].NewValue);

if (!rgDescInfo[uwDescField].size)
{
pNewvalue = (SQLPOINTER)rgDescInfo[uwDescField].NewValue;
cbValue2= (lstrlen(pNewvalue)+1) * sizeof(TCHAR);
}

memcpy(&DescBuf2, pNewvalue, cbValue2);
}
}
else {

// Compare to hdesc2
rc = SQLGetDescField(hdesc2, swDescRec, rgDescInfo[uwDescField].uwDescField,
&DescBuf2, sizeof(DescBuf2), &cbValue);

// Since hdesc2 is an unassociated hdesc, treat it as an ARD
// (uwDescIndex=0)
CheckDescOp(hdesc2, rc, 0, swDescRecCount, swDescRec, uwDescField, DESC_UPDATE_MODE_READ);
}

// Perform the compare if settable in source desc and settable in
// target desc (application descriptors) when using hdesc2
if (rgDescInfo[uwDescField].fSettable & DescTypes[uwDescIndex].uwTypeMask)
{
if (!hdesc2 || (rgDescInfo[uwDescField].fSettable & DESC_ARD))
{
// IPD field SQL_DESC_TYPE is settable for consitency check, but set
// value can't be retrieved, so skip compare in this case
if (SQL_ATTR_IMP_PARAM_DESC != DescTypes[uwDescIndex].uwDescType ||
SQL_DESC_DATA_PTR != rgDescInfo[uwDescField].uwDescField)
{
if (memcmp(DescBuf, DescBuf2, cbValue2)) {
// Compare error, print message
wsprintf(buf,TEXT("Compare error in %s field %s"),
DescTypes[uwDescIndex].szDescName,rgDescInfo[uwDescField].szDescFieldName);
DISPLAYERROR(pszAPI, buf);
}
}
}
}
}

//-----------------------------------------------------------------------
//
// Function:  CheckDescriptor
//
// Checks all fields in all records of given descriptor.  If swUpdateMode is
// DESC_UPDATE_MODE_READ then all fields are read, otherwise all updatable
// fields are written.
//-----------------------------------------------------------------------
void CheckDescriptor(SQLHDESC hdesc, SQLINTEGER swUpdateMode, SQLHSTMT hstmt, UWORD uwDescIndex)
{
 SQLINTEGER cbValue2;
SQLSMALLINT swDescRec=0, swDescRecCount, swAdditionalRecs=0;
SQLUSMALLINT iDescField, iStartField=0;
SQLRETURN rc;

// Get the descriptor record count.
rc = SQLGetDescField(hdesc, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount), &cbValue);

if (SQL_SUCCESS == rc) {

// In read mode, try to read one past last record
if (swUpdateMode == DESC_UPDATE_MODE_READ)
swAdditionalRecs=1;

// Check all records, including header
for (swDescRec=0; swDescRec <= swDescRecCount+swAdditionalRecs; swDescRec++) {

// Check all fields
for (iDescField=iStartField; iDescField < sizeof(rgDescInfo)/sizeof(rgDescInfo[0]); iDescField++) {

// If we've gone past the header fields, then break out of loop to check record fields
if (0 == swDescRec && !rgDescInfo[iDescField].fHeader) {
iStartField=iDescField;
break;
}

if (DESC_UPDATE_MODE_READ == swUpdateMode)
{
// Read from the field
rc = SQLGetDescField(hdesc, swDescRec, rgDescInfo[iDescField].uwDescField,
&DescBuf, sizeof(DescBuf), &cbValue);

CheckDescOp(hdesc, rc, uwDescIndex, swDescRecCount, swDescRec, iDescField, swUpdateMode);

if (RC_SUCCESSFUL(rc))
{
// Compare cbValue with expected
cbValue2=rgDescInfo[iDescField].cbValue; // For most fields

if (!rgDescInfo[iDescField].size)
cbValue2 = lstrlen((LPTSTR)DescBuf);  // For string fields

// Make sure cbValue and cbValue2 agree
if (cbValue != cbValue2)
{
wsprintf(buf,TEXT("Compare error in %s field %s, expected")\
TEXT(" StringLength %d, received %d"), DescTypes[uwDescIndex].szDescName,
rgDescInfo[iDescField].szDescFieldName, cbValue2, cbValue);
DISPLAYERROR(szSQLGETDESCFIELD, buf);
}
}
}
else
{
// Write to the field.  For char fields size is 0 (unknown),
// so compute the length of the string
rc = SQLSetDescField(hdesc, swDescRec, rgDescInfo[iDescField].uwDescField,
(SQLPOINTER)rgDescInfo[iDescField].NewValue,
(rgDescInfo[iDescField].size) ? rgDescInfo[iDescField].size :
lstrlen((LPTSTR)rgDescInfo[iDescField].NewValue)*sizeof(TCHAR));

CheckDescOp(hdesc, rc, uwDescIndex, swDescRecCount, swDescRec, iDescField, swUpdateMode);

if (RC_SUCCESSFUL(rc))
CompareDescField(hdesc, NULL, uwDescIndex, swDescRec, swDescRecCount,iDescField, szSQLSETDESCFIELD);
}
}
}
}
else {
// Unable to read record count from descriptor header, can't proceed
wsprintf(buf,TEXT("\t\tUnable to read record count from descriptor header."));
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, buf);
}
}

//-----------------------------------------------------------------------
//
// Function:  CompareDescriptor
//
// Compares two descriptors, or if hdesc2 is NULL compares hdesc1 to rgDescInfo
// updatable columns
//-----------------------------------------------------------------------

void CompareDescriptor(SQLHDESC hdesc1, SQLHDESC hdesc2, UWORD uwDescIndex, TCHAR * pszAPI)
{
 SQLSMALLINT swDescRec, swDescRecCount;
SQLUSMALLINT iDescField, iStartField=0;
SQLRETURN rc;

// Make sure we've got a valid descriptor handle for hdesc1
// (hdesc2 is allowed to be NULL)
if (SQL_NULL_HDESC == hdesc1) {
wsprintf(buf,TEXT("\t\thCompareDescriptor: hdesc1 handle was null!"));
DISPLAYERROR(szSQLGETDESCFIELD, buf);
return;
}

// Get the descriptor record count.
rc = SQLGetDescField(hdesc1, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount), &cbValue);

if (SQL_SUCCESS == rc) {

// Check all records, including header
for (swDescRec=0; swDescRec <= swDescRecCount; swDescRec++) {

// Check all fields
for (iDescField=iStartField; iDescField < sizeof(rgDescInfo)/sizeof(rgDescInfo[0]); iDescField++) {

// If we've gone past the header fields, then break out of loop to check record fields
if (0 == swDescRec && !rgDescInfo[iDescField].fHeader) {
iStartField=iDescField;
break;
}

CompareDescField(hdesc1, hdesc2, uwDescIndex, swDescRec, swDescRecCount, iDescField, pszAPI);
}
}
}
else {
// Unable to read record count from descriptor header, can't proceed
wsprintf(buf,TEXT("\t\tUnable to read record count from descriptor header."));
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, buf);
}
}

void CheckDescRecord(SQLHDESC hdesc, SQLSMALLINT swDescRec, SQLSMALLINT fType,
           SQLSMALLINT fSubType, SQLINTEGER cbLength, SQLSMALLINT ibPrecision, SQLSMALLINT ibScale,
           SQLPOINTER pData, SQLINTEGER *pcbValue, SQLINTEGER *pIndicator, SQLUSMALLINT iDescType)
{
SQLINTEGER Length, * pIndicator2;
PTR pDescPtr;
SQLRETURN rc;
SQLSMALLINT fType2, fSubType2, cbName, Precision, Scale, Nullable;
TCHARszName[MAX_DESC_BUF]=TEXT("");

// Write the record
rc = SQLSetDescRec(hdesc, swDescRec, fType, fSubType, cbLength, ibPrecision,
ibScale, pData, pcbValue, pIndicator);

// Can't set IRD, so it should error
if (SQL_ATTR_IMP_ROW_DESC == DescTypes[iDescType].uwDescType)
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_ERROR, rc, szSQLSETDESCREC);
else
{
wsprintf(buf,TEXT("%s: Unable to write record %d in %s"),
szSQLSETDESCREC, swDescRec, DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, buf);
}

if (RC_SUCCESSFUL(rc))
{
// Read it again
rc = SQLGetDescRec(hdesc, swDescRec, szName, sizeof(szName), &cbName, &fType2,
&fSubType2, &Length, &Precision, &Scale, &Nullable);

wsprintf(buf,TEXT("%s: Unable to read record %d in %s"),
szSQLGETDESCREC, swDescRec, DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, buf);

if (RC_SUCCESSFUL(rc))
{
// Compare values
if (fType2 != fType)
DISPLAYERROR(szSQLSETDESCREC,TEXT("fType not set correctly"));

if (Precision != ibPrecision)
DISPLAYERROR(szSQLSETDESCREC,TEXT("ibPrecision not set correctly"));

switch(fType)
{
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_C_DOUBLE:
case SQL_C_FLOAT:
// Scale is only defined for NUMERIC or DECIMAL types
if (Scale != ibScale)
DISPLAYERROR(szSQLSETDESCREC,TEXT("ibScale not set correctly"));
break;
case SQL_DATETIME:
case SQL_INTEGER:
// Subtype is only valid for DATETIME or INTERVAL types
if (fSubType2 != fSubType)
DISPLAYERROR(szSQLSETDESCREC,TEXT("fSubType not set correctly"));
break;
}

// For application descriptors, compare the Length and get the
// data ptr value to check for accuracy
switch(DescTypes[iDescType].uwDescType)
{
case SQL_ATTR_APP_ROW_DESC:
case SQL_ATTR_APP_PARAM_DESC:

if (Length != cbLength)
DISPLAYERROR(szSQLSETDESCREC,TEXT("cbLength not set correctly"));

rc = SQLGetDescField(hdesc, swDescRec, SQL_DESC_DATA_PTR,
&pDescPtr, sizeof(PTR), &cbValue);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, szSQLGETDESCFIELD);

if (pDescPtr != pData)
DISPLAYERROR(szSQLSETDESCREC,TEXT("rgbValue not set correctly"));

// Get the octet length ptr value and check for accuracy
rc = SQLGetDescField(hdesc, swDescRec, SQL_DESC_OCTET_LENGTH_PTR,
&pDescPtr, sizeof(PTR), &cbValue);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, szSQLGETDESCFIELD);

if (pDescPtr != pcbValue)
DISPLAYERROR(szSQLSETDESCREC,TEXT("cbValue not set correctly"));

rc = SQLGetDescField(hdesc, swDescRec, SQL_DESC_INDICATOR_PTR,
&pIndicator2, sizeof(PTR), &cbValue);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, szSQLGETDESCFIELD);

if (pIndicator2 != pIndicator)
DISPLAYERROR(szSQLSETDESCREC,TEXT("Indicator not set correctly"));

break;
case SQL_ATTR_IMP_ROW_DESC:
case SQL_ATTR_IMP_PARAM_DESC:
break;
default:
DISPLAYERROR(szSQLSETDESCREC,TEXT("Unknown descriptor type."));
}
}
}

}

SQLRETURN DescBindParameter(SQLHSTMT hstmt, SQLUSMALLINT ipar,
    SQLSMALLINT fParamType, SQLSMALLINT fCType, SQLSMALLINT fSqlType,
    SQLUINTEGER cbColDef, SQLSMALLINT ibScale, SQLPOINTER rgbValue,
    SQLINTEGER cbValueMax, SQLINTEGER  *pcbValue)
{
SQLHDESC hdescAPD, hdescIPD;
SQLRETURN rc;

rc = SQLGetStmtAttr(hstmt, SQL_ATTR_APP_PARAM_DESC, &hdescAPD, sizeof(hdescAPD), NULL);
if (!RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR))
return rc;

rc = SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hdescIPD, sizeof(hdescIPD), NULL);
if (!RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR))
return rc;

// Parameter type
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_PARAMETER_TYPE, (SQLPOINTER)fParamType,
SQL_IS_SMALLINT);// Set ipar and fParamType for IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

// C type
rc = SQLSetDescField(hdescAPD, ipar, SQL_DESC_CONCISE_TYPE, (SQLPOINTER)fCType,
SQL_IS_SMALLINT);// Set ipar and fCType of APD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescAPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

// Sql type
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_CONCISE_TYPE, (SQLPOINTER)fSqlType,
SQL_IS_SMALLINT);// Set fSqlType of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

// Column Size (Length or Precision)
if (fSqlType == SQL_CHAR ||
fSqlType == SQL_VARCHAR ||
fSqlType == SQL_LONGVARCHAR ||
fSqlType == SQL_BINARY ||
fSqlType == SQL_VARBINARY ||
fSqlType == SQL_LONGVARBINARY ||
fSqlType == SQL_TYPE_DATE ||
fSqlType == SQL_TYPE_TIME ||
fSqlType == SQL_TYPE_TIMESTAMP ||
(fSqlType >= SQL_INTERVAL_YEAR &&
 fSqlType <= SQL_INTERVAL_MINUTE_TO_SECOND))
{
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_LENGTH, (SQLPOINTER)cbColDef,
SQL_IS_UINTEGER);// Set cbColDef (Length) of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;
}
else if (fSqlType == SQL_DECIMAL ||

fSqlType == SQL_NUMERIC ||
 fSqlType == SQL_FLOAT ||
 fSqlType == SQL_REAL ||
 fSqlType == SQL_DOUBLE)
{
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_PRECISION, (SQLPOINTER)cbColDef,
SQL_IS_SMALLINT);// Set cbColDef (Precision) of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;
}

// Scale
if (fSqlType == SQL_TYPE_DATE ||
fSqlType == SQL_TYPE_TIME ||
fSqlType == SQL_TYPE_TIMESTAMP ||
(fSqlType >= SQL_INTERVAL_YEAR &&
 fSqlType <= SQL_INTERVAL_MINUTE_TO_SECOND))
{
switch(fSqlType)
{
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
case SQL_INTERVAL_SECOND:
case SQL_INTERVAL_DAY_TO_SECOND:
case SQL_INTERVAL_HOUR_TO_SECOND:
case SQL_INTERVAL_MINUTE_TO_SECOND:
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_PRECISION, (SQLPOINTER)ibScale,
SQL_IS_SMALLINT);// Set scale of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;
break;
default:
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_PRECISION, (SQLPOINTER)0,
SQL_IS_SMALLINT);// Set scale of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;
}
}
else
{
rc = SQLSetDescField(hdescIPD, ipar, SQL_DESC_SCALE, (SQLPOINTER)ibScale,
SQL_IS_SMALLINT);// Set scale of IPD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescIPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;
}

// cbValueMax
rc = SQLSetDescField(hdescAPD, ipar, SQL_DESC_OCTET_LENGTH, (SQLPOINTER)cbValueMax,
SQL_IS_INTEGER);// Set cbValueMax of APD
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescAPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

// pcbValue
rc = SQLSetDescField(hdescAPD, ipar, SQL_DESC_OCTET_LENGTH_PTR, (SQLPOINTER)pcbValue,
SQL_IS_POINTER);// Set pcbValue/octet length
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescAPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

rc = SQLSetDescField(hdescAPD, ipar, SQL_DESC_INDICATOR_PTR, (SQLPOINTER)pcbValue,
SQL_IS_POINTER);// Set pcbValue/indicator
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdescAPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

// Data ptr
rc = SQLSetDescField(hdescAPD, ipar, SQL_DESC_DATA_PTR, (SQLPOINTER)rgbValue,
SQL_IS_POINTER);// Set rgbValue
ERRSHANDLE(SQL_HANDLE_DESC, hdescAPD, SQL_SUCCESS, rc,szSQLSETDESCFIELD);

return rc;

}

SQLRETURN DescBindCol(SQLHSTMT hstmt, SQLSMALLINT icol,
           SQLSMALLINT fCType, SQLPOINTER rgbValue,
           SQLINTEGER cbValueMax, SQLINTEGER *pcbValue)
{
SQLHDESC hdesc;
SQLRETURN rc;

rc = SQLGetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, &hdesc, sizeof(hdesc), NULL);
if (!RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR))
return rc;

rc = SQLSetDescField(hdesc, icol, SQL_DESC_CONCISE_TYPE, (SQLPOINTER)fCType,
SQL_IS_SMALLINT);// Set icol and fCType
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

rc = SQLSetDescField(hdesc, icol, SQL_DESC_OCTET_LENGTH, (SQLPOINTER)cbValueMax,
SQL_IS_INTEGER);// Set cbValueMax
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

rc = SQLSetDescField(hdesc, icol, SQL_DESC_OCTET_LENGTH_PTR, (SQLPOINTER)pcbValue,
SQL_IS_POINTER);// Set pcbValue/octet length
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

rc = SQLSetDescField(hdesc, icol, SQL_DESC_INDICATOR_PTR, (SQLPOINTER)pcbValue,
SQL_IS_POINTER);// Set pcbValue/indicator
if (!ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD))
return rc;

rc = SQLSetDescField(hdesc, icol, SQL_DESC_DATA_PTR, (SQLPOINTER)rgbValue,
SQL_IS_POINTER);// Set rgbValue
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc,szSQLSETDESCFIELD);

return rc;
}

//-----------------------------------------------------------------------
//      Function:               TestGetDescField
//-----------------------------------------------------------------------

void  PASCAL TestGetDescField(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
SQLUSMALLINT iDescType;

SQLHDESC hdesc1=NULL, hdesc2=NULL;
PTR rgbValue=NULL;
SQLSMALLINT swDescRecCount, swExpRecCount;
SQLINTEGER cbValue;
TCHARszName[MAX_DESC_BUF]=TEXT("");
TCHARszQuery[XLARGEBUFF]=TEXT("");

if (!Supported(SQL_API_SQLGETDESCFIELD))
{
// Allocate a default descriptor
rc = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);

if (RC_SUCCESSFUL(rc))
{
 rc = SQLGetDescField(hdesc, 0, SQL_DESC_COUNT, &DescBuf, sizeof(DescBuf),
&cbValue);

if(!FindError(SQL_HANDLE_DESC,szIM001))
DISPLAYERROR(szSQLGETDESCFIELD, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLGETDESCFIELD);
}
}
else
{
// Make sure all parameters and columns are unbound
ResetHstmt(&hstmt);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{

// Get the handle for the automatically allocated descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc1, sizeof(hdesc1), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

// Create a descriptor record so we have something to read besides header
swExpRecCount=CreateDescRecord(hdesc1, hstmt, DescTypes[iDescType].uwDescType);

// Get the count of descriptor records (iRecord ignored for header fields)
rc = SQLGetDescField(hdesc1, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount), &cbValue);

if (SQL_SUCCEEDED(rc))
{
// Check count value for expected
if (swExpRecCount != swDescRecCount)
{
wsprintf(buf,TEXT("Expected record count %d, found %d"),
swExpRecCount, swDescRecCount);
DISPLAYERROR(szSQLGETDESCFIELD, buf);
}

CheckDescriptor(hdesc1, DESC_UPDATE_MODE_READ, hstmt, iDescType);
}
else
{
// Unable to read record count from descriptor header, can't proceed
wsprintf(buf,TEXT("Unable to read record count from descriptor header for descriptor type %d"),
DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, buf);
}
}

rc=SQLCloseCursor(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLCLOSECURSOR);
}

} // TestGetDescField()

//-----------------------------------------------------------------------
//      Function:               TestSetDescField
//-----------------------------------------------------------------------

void  PASCAL TestSetDescField(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
SQLUSMALLINT iDescType;

SQLHDESC hdesc1=NULL, hdesc2=NULL;
PTR rgbValue=NULL;
SQLSMALLINT swDescRecCount, swExpRecCount;
SQLINTEGER cbValue;
TCHARszName[MAX_DESC_BUF]=TEXT("");
TCHARszQuery[XLARGEBUFF]=TEXT("");

if (!Supported(SQL_API_SQLSETDESCFIELD))
{
// Allocate a default descriptor
rc = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);

if (RC_SUCCESSFUL(rc))
{
rc = SQLSetDescField(hdesc, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount));

if(!FindError(SQL_HANDLE_DESC,szIM001))
DISPLAYERROR(szSQLSETDESCFIELD, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLSETDESCFIELD);
}
}
else
{
// Make sure all parameters and columns are unbound
ResetHstmt(&hstmt);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{

// Get the handle for the automatically allocated descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc1, sizeof(hdesc1), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

// Create a descriptor record so we have something to read besides header
swExpRecCount=CreateDescRecord(hdesc1, hstmt, DescTypes[iDescType].uwDescType);

// Update all descriptor records to new values
CheckDescriptor(hdesc1, DESC_UPDATE_MODE_WRITE, hstmt, iDescType);

// Compare the descriptor with the update values
// CompareDescriptor(hdesc1, NULL, iDescType, szSQLSETDESCFIELD);
}

ResetHstmt(&hstmt);
}

} //TestSetDescField()

//-----------------------------------------------------------------------
//      Function:               TestGetDescRec
//-----------------------------------------------------------------------

void  PASCAL TestGetDescRec(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
SQLUSMALLINT iDescType;

SQLHDESC hdesc1=NULL, hdesc2=NULL;
PTR rgbValue=NULL;
SQLSMALLINT swDescRec, swDescRecCount, iRecord, cbNameMax, cbName, fType, fSubType,
Precision, Scale, Nullable, swExpRecCount;
SQLINTEGER Length, cbValue;
TCHARszName[MAX_DESC_BUF]=TEXT("");
TCHARszQuery[XLARGEBUFF]=TEXT("");

if (!Supported(SQL_API_SQLGETDESCREC))
{
// Allocate a default descriptor
rc = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);

if (RC_SUCCESSFUL(rc))
{
rc = SQLGetDescRec(hdesc, iRecord, szName, cbNameMax, &cbName, &fType,
&fSubType, &Length, &Precision, &Scale, &Nullable);

if(!FindError(SQL_HANDLE_DESC,szIM001))
DISPLAYERROR(szSQLGETDESCREC, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLGETDESCREC);
}
}
else
{
// Make sure all parameters and columns are unbound
ResetHstmt(&hstmt);

// Turn on bookmarks so we can retrieve from record 0 of ARD
rc = SQLSetStmtAttr(hstmt, SQL_ATTR_USE_BOOKMARKS, (SQLPOINTER)SQL_UB_ON, SQL_IS_INTEGER);
RETCHECK(SQL_SUCCESS, rc,szSQLSETSTMTATTR);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{

// Get the handle for the automatically allocated descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc1, sizeof(hdesc1), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

// Create a descriptor record so we have something to read besides header
swExpRecCount=CreateDescRecord(hdesc1, hstmt, DescTypes[iDescType].uwDescType);

// Get the count of descriptor records
// Count for automatically allocated descriptors is written in
// SetDescField test.
rc = SQLGetDescField(hdesc1, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount), &cbValue);

if (SQL_SUCCESS == rc)
{

// For all descriptor records
for (swDescRec=0; swDescRec <= swDescRecCount+1; swDescRec++)
{
// Read the record
rc = SQLGetDescRec(hdesc1, swDescRec, szName, sizeof(szName), &cbName, &fType,
&fSubType, &Length, &Precision, &Scale, &Nullable);

if (swDescRec <= swDescRecCount)
{
// Retrieving bookmark on IPD is an error
if (SQL_ATTR_IMP_PARAM_DESC == DescTypes[iDescType].uwDescType && 0 == swDescRec)
{
wsprintf(buf,TEXT("%s: Able to read record %d from %s"),
szSQLGETDESCREC, swDescRec, DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_ERROR, rc, buf);
}
else
{
wsprintf(buf,TEXT("%s: Unable to read record %d from %s"),
szSQLGETDESCREC, swDescRec, DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, buf);
}
}
else
{
wsprintf(buf,TEXT("%s: Did not receive SQL_NO_DATA reading record %d from %s"),
szSQLGETDESCREC, swDescRec, DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_NO_DATA, rc, buf);
}
}
}
else
{
// Unable to read record count from descriptor header, can't proceed
wsprintf(buf,TEXT("\t\tUnable to read record count from descriptor header for %s"),
DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, buf);
}
}

rc=SQLCloseCursor(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLCLOSECURSOR);

}

} //TestGetDescRec()

//-----------------------------------------------------------------------
//      Function:               TestSetDescRec
//-----------------------------------------------------------------------

void  PASCAL TestSetDescRec(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
SQLINTEGER cbLength, cbValue, Indicator;
SQLUSMALLINT iDescType;

SQLHDESC hdesc1=NULL, hdesc2=NULL;
PTR rgbValue=NULL;
SQLSMALLINT swDescRec, swDescRecCount, swStartRec=1, iRecord, fType, fSubType,
ibPrecision, ibScale;
SQLSMALLINT swExpRecCount;
TCHARszQuery[XLARGEBUFF]=TEXT("");

if (!Supported(SQL_API_SQLSETDESCREC))
{
// Allocate a default descriptor
rc = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);

if (RC_SUCCESSFUL(rc))
{
rc = SQLSetDescRec(hdesc, iRecord, fType, fSubType, cbLength,
ibPrecision, ibScale, &DescBuf, &cbValue, &Indicator);

if(!FindError(SQL_HANDLE_DESC,szIM001))
DISPLAYERROR(szSQLSETDESCREC, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLSETDESCREC);
}
}
else
{
// Make sure all parameters and columns are unbound
ResetHstmt(&hstmt);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// If bookmarks are supported, then we want to try to set record 0
rc = SQLSetStmtAttr(hstmt, SQL_ATTR_USE_BOOKMARKS,
(PTR)SQL_UB_VARIABLE, SQL_IS_INTEGER);
if (RC_SUCCESSFUL(rc))
swStartRec=0;

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{

// Get the handle for the automatically allocated descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc1, sizeof(hdesc1), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

// Create a descriptor record so we have something to read besides header
swExpRecCount=CreateDescRecord(hdesc1, hstmt, DescTypes[iDescType].uwDescType);

// Get the count of descriptor records
rc = SQLGetDescField(hdesc1, 0, SQL_DESC_COUNT, &swDescRecCount,
sizeof(swDescRecCount), &cbValue);

if (SQL_SUCCESS == rc)
{

// For all descriptor records
for (swDescRec=swStartRec; swDescRec <= swDescRecCount; swDescRec++)
{
// Test with a DATETIME record so we can check the subtype field.
SQLINTEGER Indicator=0;
fType=SQL_DATETIME;
fSubType=SQL_CODE_TIMESTAMP;
cbLength=sizeof(TIMESTAMP_STRUCT);
ibPrecision=5; // For DATETIME, SQL_CODE_DATE data
// precision defaults to 0, but it implies it can be set otherwise.
ibScale=0;// Only used for DECIMAL or NUMERIC types, otherwise undefined

CheckDescRecord(hdesc1, swDescRec, fType, fSubType, cbLength, ibPrecision,
ibScale, &tsval, &cbValue, &Indicator, iDescType);

// Test with a numeric record to test scale
Indicator=0;
fType=SQL_NUMERIC;
fSubType=SQL_CODE_TIMESTAMP;
cbLength=sizeof(TIMESTAMP_STRUCT);
ibPrecision=5; // For DATETIME, SQL_CODE_DATE data
// precision defaults to 0, but it implies it can be set otherwise.
ibScale=0;// Only used for DECIMAL or NUMERIC types, otherwise undefined

CheckDescRecord(hdesc1, swDescRec, fType, fSubType, cbLength, ibPrecision,
ibScale, &tsval, &cbValue, &Indicator, iDescType);
}
}
else
{
// Unable to read record count from descriptor header,
// can't proceed
wsprintf(buf,TEXT("\t\tUnable to read record count from descriptor header for  %s"),
DescTypes[iDescType].szDescName);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, buf);
}
}
ResetHstmt(&hstmt);
}

} //TestSetDescRec()

//-----------------------------------------------------------------------
//      Function:               TestCopyDesc
//-----------------------------------------------------------------------

void  PASCAL TestCopyDesc(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
SQLUSMALLINT iDescType;
SQLSMALLINT swExpRecCount;

SQLHDESC hdesc1=NULL, hdesc2=NULL;
PTR rgbValue=NULL;
TCHARszName[MAX_DESC_BUF]=TEXT("");
TCHARszQuery[XLARGEBUFF]=TEXT("");

// Allocate a default descriptor
rc = SQLAllocHandle(SQL_HANDLE_DESC, hdbc, &hdesc);

if (RC_SUCCESSFUL(rc))
{

if (!Supported(SQL_API_SQLCOPYDESC))
{
rc = SQLCopyDesc(hdesc, hdesc1);

if(!FindError(SQL_HANDLE_DESC,szIM001))
DISPLAYERROR(szSQLCOPYDESC, szNotSupported);
RETCHECK(SQL_ERROR, rc,szSQLCOPYDESC);
}
else
{
ResetHstmt(&hstmt);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{
// Get the handle for the automatically allocated descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc1, sizeof(hdesc1), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

// Create a descriptor record so we have something to read besides header
swExpRecCount=CreateDescRecord(hdesc1, hstmt, DescTypes[iDescType].uwDescType);

// Update all descriptor records to new values
CheckDescriptor(hdesc1, DESC_UPDATE_MODE_WRITE, hstmt, iDescType);

// Copy the descriptor to the newly allocated descriptor
rc = SQLCopyDesc(hdesc1, hdesc);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc1, SQL_SUCCESS, rc, szSQLCOPYDESC);
ERRSHANDLE(SQL_HANDLE_DESC, hdesc, SQL_SUCCESS, rc, szSQLCOPYDESC);

// Compare each field in the copied descriptor to the original
if (RC_SUCCESSFUL(rc))
CompareDescriptor(hdesc1, hdesc, iDescType, szSQLCOPYDESC);
}

ResetHstmt(&hstmt);
}

// Free descriptor handle
if (hdesc)
{
rc = SQLFreeHandle(SQL_HANDLE_DESC, hdesc);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);
}
}

} //TestCopyDesc()

//-----------------------------------------------------------------------
//      Function:               TestDescDefaults
//-----------------------------------------------------------------------
void  PASCAL TestDescDefaults(QTSTRUCT * lpqt)
{
SQLRETURN rc=SQL_SUCCESS;
 SQLINTEGER cbValue;
SQLSMALLINT swDescRec;
SQLUSMALLINT iDescField, iStartField=0, iDescType;

if (Supported(SQL_API_SQLGETDESCFIELD))
{

// Reallocate hstmt to check defaults against
ResetHstmt(&hstmt);

// For each descriptor type
for (iDescType=0; iDescType < sizeof(DescTypes)/sizeof(DescTypes[0]); iDescType++)
{
// Get the handle for the descriptor
rc = SQLGetStmtAttr(hstmt, DescTypes[iDescType].uwDescType, &hdesc, sizeof(hdesc), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);

swDescRec=0;

// For each descriptor field, read the value
for (iDescField=iStartField; iDescField < sizeof(rgDescInfo)/sizeof(rgDescInfo[0]); iDescField++) {

// If we've gone past the header fields, then create a record and check record fields
if (0 == swDescRec && !rgDescInfo[iDescField].fHeader) {
iStartField=iDescField;
// Make sure we're not on the IRD, 'cause you can't modify an IRD
if (SQL_ATTR_IMP_ROW_DESC != DescTypes[iDescType].uwDescType)
{
// Create a record with all default values
rc = SQLSetDescField(hdesc, 0, SQL_DESC_COUNT, (SQLPOINTER)1,
SQL_IS_SMALLINT);
RETCHECK(SQL_SUCCESS, rc,szSQLSETDESCFIELD);

swDescRec++;
}
continue;
}

// If the field has a default value
if (rgDescInfo[iDescField].fDefault & DescTypes[iDescType].uwTypeMask)
{
// Read from the field
rc = SQLGetDescField(hdesc, swDescRec, rgDescInfo[iDescField].uwDescField,
&DescBuf, sizeof(DescBuf), &cbValue);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDESCFIELD);

if (RC_SUCCESSFUL(rc))
{
// Perform the compare
if (memcmp(DescBuf, &rgDescInfo[iDescField].DefaultVal, cbValue))
{
// Compare error, print message
wsprintf(buf,TEXT("Error in %s field %s default value"),
DescTypes[iDescType].szDescName,rgDescInfo[iDescField].szDescFieldName);
DISPLAYERROR(szSQLGETDESCFIELD, buf);
}
}
}
}
}
}
} // TestDescDefaults

//-----------------------------------------------------------------------
//      Function:               TestUseDesc
//-----------------------------------------------------------------------
void  PASCAL TestUseDesc(QTSTRUCT * lpqt, FIELDINFO  *rgFields)
{
LPTSTR   pch=NULL;
SDWORD crow=0;
TCHARszQuery[XLARGEBUFF]=TEXT("");
SQLRETURN rc;
UWORDrow, icol, paramno;
SWORDccol;
TCHAR * rgbMemBuf=NULL, * rgbValue=NULL;
SDWORD * pcbValueBuf=NULL, * pcbValue=NULL;

if (Supported(SQL_API_SQLSETDESCFIELD))
{

// Test descriptor bindcol

// Reallocate hstmt to set to defaults
ResetHstmt(&hstmt);

// Perform a select stmt
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

rc=SQLNumResultCols(hstmt, &ccol);
RETCHECK(SQL_SUCCESS, rc, szSQLNUMRESULTCOLS);

rc=SQLFreeStmt(hstmt, SQL_CLOSE); // Close the cursor so we can insert
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

rgbMemBuf=AllocateMemory(ccol*MAX_STRING_SIZE*sizeof(TCHAR));

if (!rgbMemBuf)
{
DISPLAYERROR(TEXT("Simulate BindCol"),TEXT("Out of memory!"));
goto ExitUseDesc;
}

pcbValueBuf=(SDWORD *)AllocateMemory(ccol*sizeof(SDWORD));

if (!pcbValueBuf)
{
DISPLAYERROR(TEXT("Simulate BindCol"),TEXT("Out of memory!"));
goto ExitUseDesc;
}

// Bind a parameter for each column in the result set using descriptors
paramno=0;
for (icol=1, rgbValue=rgbMemBuf, pcbValue=pcbValueBuf;
icol <= ccol;
icol++, rgbValue+=MAX_STRING_SIZE*sizeof(TCHAR), pcbValue++)
{
pch = qtMakeData(guwRowCount, icol-1, &rgFields[icol-1], rgbValue);

// Field is not READ ONLY
if(pch)
{
 // Where row and column match and field is nullable, make NULL
if (icol == guwRowCount && rgFields[icol-1].nullable)
*pcbValue=SQL_NULL_DATA;
else
*pcbValue=SQL_NTS;

paramno++;
//rc=SQLBindParameter(hstmt, paramno, SQL_PARAM_INPUT, SQL_C_CHAR,
rc=DescBindParameter(hstmt, paramno, SQL_PARAM_INPUT, SQL_C_CHAR,
rgFields[icol-1].wSQLType, rgFields[icol-1].precision,
rgFields[icol-1].scale, rgbValue, 0, pcbValue);
RETCHECK(SQL_SUCCESS, rc,TEXT("DescBindParameter"));
}
}

// Insert the new row.  Note that the inserted row will be checked
// for accuracy below.
rc = SQLExecDirect(hstmt, szInsertStmt, SQL_NTS);
ERRSHANDLE(SQL_HANDLE_STMT, hstmt, SQL_SUCCESS, rc, szSQLEXECDIRECT);

if (RC_SUCCESSFUL(rc))
guwRowCount++;

// Perform the select stmt again
wsprintf(szQuery,szSELECTSTAR,lpqt->szTableName);
rc = SQLExecDirect(hstmt, szQuery, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

// Bind all the columns to char
for (icol=1, rgbValue=rgbMemBuf, pcbValue=pcbValueBuf;
icol <= ccol;
icol++, rgbValue+=MAX_STRING_SIZE*sizeof(TCHAR), pcbValue++)
{
// Bind to next column
rc = DescBindCol(hstmt, icol, SQL_C_CHAR, rgbValue, MAX_STRING_SIZE*sizeof(TCHAR), pcbValue);
//rc = SQLBindCol(hstmt, icol, SQL_C_CHAR, rgbValue, MAX_STRING_SIZE*sizeof(TCHAR), pcbValue);
RETCHECK(SQL_SUCCESS, rc, TEXT("DescBindCol"));
}

// Fetch all the rows and check the bound value with MakeAData for
// each column
for (row=1; rc == SQL_SUCCESS; row++)
{
rc=SQLFetch(hstmt); // Will return SQL_NO_DATA after last row

if (SQL_SUCCESS == rc)
{

// Compare all the columns
for (icol=1, rgbValue=rgbMemBuf, pcbValue=pcbValueBuf;
icol <= ccol;
icol++, rgbValue+=MAX_STRING_SIZE*sizeof(TCHAR), pcbValue++)
{

CompareWithExpected(rgbValue, pcbValue, row, icol, rgFields);
}
}
else
ERRSHANDLE(SQL_HANDLE_STMT, hstmt, SQL_NO_DATA, rc, szSQLFETCH);

}

}

ExitUseDesc:
rc=SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc,szSQLFREESTMT);

if (rgbMemBuf)
ReleaseMemory(rgbMemBuf);
if (pcbValueBuf)
ReleaseMemory(pcbValueBuf);

} // TestUseDesc

//-----------------------------------------------------------------------
//      Function:               TestEnvAttr
//
// Note:Only one environment attribute (SQL_ATTR_OUTPUT_NTS) exists at this
// time.  The default value is SQL_TRUE.
//
//For the Environment attributes we assume support if DM version >= 3.00
//-----------------------------------------------------------------------

void  PASCAL TestEnvAttr()
{
SQLINTEGERsdwEnvAttr;
SQLRETURNrc;
HENVtshenv;
BOOLfSetSupported = Supported(SQL_API_SQLSETENVATTR),
fGetSupported = Supported(SQL_API_SQLGETENVATTR);

//allocate an enviroment handle
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &tshenv);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCHANDLE);

//-------------------------------
//-------- SQLSetEnvAttr --------
//-------------------------------
// Set to SQL_FALSE
rc = SQLSetEnvAttr(tshenv, SQL_ATTR_ODBC_VERSION, (PTR)SQL_OV_ODBC3,
SQL_IS_UINTEGER);

if(!fSetSupported)
{
if (!FindError(SQL_HANDLE_ENV,szIM001))
DISPLAYERROR(szSQLSETENVATTR, szNotSupported);

RETCHECK(SQL_ERROR, rc,szSQLSETENVATTR);
}
else
RETCHECK(SQL_SUCCESS, rc,szSQLSETENVATTR);

if (RC_SUCCESSFUL(rc))
{
// Read it again
rc = SQLGetEnvAttr(tshenv, SQL_ATTR_ODBC_VERSION, &sdwEnvAttr,
sizeof(sdwEnvAttr), NULL);

if(!fGetSupported)
{
if (!FindError(SQL_HANDLE_ENV,szIM001))
DISPLAYERROR(szSQLGETENVATTR, szNotSupported);

RETCHECK(SQL_ERROR, rc,szSQLGETENVATTR);
}
else
RETCHECK(SQL_SUCCESS, rc,szSQLGETENVATTR);

if (RC_SUCCESSFUL(rc) && (SQL_OV_ODBC3 != sdwEnvAttr))
{
// New value wrong
DISPLAYERROR(szSQLSETENVATTR,TEXT("Unable to set environment attribute SQL_ATTR_ODBC_VERSION to SQL_OV_ODBC3."));
}
}

rc = SQLFreeHandle(SQL_HANDLE_ENV,tshenv);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);

//-------------------------------
//-------- SQLGetEnvAttr --------
//-------------------------------
rc = SQLAllocEnv(&tshenv);
RETCHECK(SQL_SUCCESS, rc,szSQLALLOCENV);

rc = SQLGetEnvAttr(tshenv, SQL_ATTR_ODBC_VERSION, &sdwEnvAttr,
sizeof(sdwEnvAttr), NULL);

if(!fGetSupported)
{
if (!FindError(SQL_HANDLE_ENV,szIM001))
DISPLAYERROR(szSQLGETENVATTR, szNotSupported);

RETCHECK(SQL_ERROR, rc,szSQLGETENVATTR);
}
else
RETCHECK(SQL_SUCCESS, rc,szSQLGETENVATTR);

if (RC_SUCCESSFUL(rc) && (SQL_OV_ODBC2 != sdwEnvAttr))
{
// Default value wrong
DISPLAYERROR(szSQLGETENVATTR,TEXT("Value was not SQL_OV_ODBC3!"));
}

rc = SQLFreeHandle(SQL_HANDLE_ENV,tshenv);
RETCHECK(SQL_SUCCESS, rc,szSQLFREEHANDLE);

} //TestEnvAttr()

//-----------------------------------------------------------------------
//    Function: GetRowCnt
//
//Get the row count of the table lpqt->szTalbeName
//-----------------------------------------------------------------------
SWORD  PASCAL GetRowCnt(QTSTRUCT  *lpqt)
{
 SWORD sRowCnt;
RETCODErc;

// free the statment handle for a fresh start
FreeStmt(SQL_DROP);

AllocHstmt();

//get row count for the table
wsprintf(lpqt->sz,TEXT("select count(*) from %s"), lpqt->szTableName);

rc = SQLExecDirect(hstmt, lpqt->sz, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECDIRECT);

rc = SQLFetch(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLFETCH);

rc = SQLGetData(hstmt,1, SQL_C_SSHORT, &sRowCnt, sizeof(sRowCnt), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETDATA);

FreeStmt(SQL_CLOSE);

return sRowCnt;
}//GetRowCount

//-----------------------------------------------------------------------
//    Function: InsertOneRow
//
//Insert one row into the table.
//
//lpqt:temp place to hold data genrated
//rgFields:hold info about each data type in the dbs
//cTypes;the count of data types
//lpd:buffer used for SQLBindParam
//cSeed;the seed used to generate data
//fFreeHsmt:if the hstmt should be freed
//
//Return TRUE: the insertion was successful
// FALSE: otherwise
//-----------------------------------------------------------------------

BOOL  PASCAL InsertOneRow(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd,UWORD cSeed,BOOL fFreeHstmt)
{
RETCODE  rc;
UWORD    w;
UWORD    ind;
LPTSTR    pch=NULL;
SQLHDESChdesc;
   SQLSMALLINT      fParamType = SQL_PARAM_INPUT;
TCHARszDataItem[150];

//prepare the insert statement
rc = SQLPrepare(hstmt, szInsertStmt, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);

//going through each data type, call SQLBindParam
for(ind = 0, w = 0; ind < cTypes; ind++)
{

//make data according to data type description in rgFields[], cSeed is the seed
pch = qtMakeData(cSeed,ind,&rgFields[ind], szDataItem);

//if the data type is READ_ONLY, skip
if(!pch)
continue;

//populate the NULL data
if(*pch)
{
lstrcpy(lpd[w].data, pch);
lpd[w].cb = SQL_NTS;
}
else
{
lstrcpy(lpd[w].data,TEXT(""));
lpd[w].cb = SQL_NULL_DATA;
}

if (g_f3XDriver && Supported(SQL_API_SQLSETDESCFIELD))
//if (0)//DEBUG
{
//bind the data buffer in lpd[] to each column of the table
rc = SQLBindParam(hstmt, (SQLSMALLINT)(w+1),SQL_C_TCHAR, rgFields[ind].wSQLType,
rgFields[ind].precision, rgFields[ind].scale,
&lpd[w].data, &lpd[w].cb);
RETCHECK(SQL_SUCCESS, rc,szSQLBINDPARAM);

if(rc!= SQL_SUCCESS)
return FALSE;

//get the driver parameter descriptor handle
rc = SQLGetStmtAttr(hstmt,SQL_ATTR_IMP_PARAM_DESC, &hdesc, sizeof(hdesc),NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETSTMTATTR);
if(rc!= SQL_SUCCESS)
return FALSE;

//set the parameter type field in the descriptor
rc = SQLSetDescField(hdesc, (SQLSMALLINT)(w+1), SQL_DESC_PARAMETER_TYPE,
(PTR)fParamType, sizeof(fParamType));
RETCHECK(SQL_SUCCESS, rc,szSQLSETDESCFIELD);

}
else
{
rc = SQLBindParameter(hstmt, (UWORD)(w+1),SQL_PARAM_INPUT,SQL_C_TCHAR,
rgFields[ind].wSQLType,rgFields[ind].precision,
rgFields[ind].scale,&lpd[w].data, 100, &lpd[w].cb);

RETCHECK(SQL_SUCCESS, rc,szSQLBINDPARAMETER);

if(rc!= SQL_SUCCESS)
return FALSE;
}

//go on to next column
w++;
}

// insert a row
rc = SQLExecute(hstmt);
RETCHECK(SQL_SUCCESS, rc,szSQLEXECUTE);
if(rc!= SQL_SUCCESS)
return FALSE;

//free the hstmt if required
if(fFreeHstmt)
{
if (FreeStmt(SQL_CLOSE))
return FALSE;

}

return TRUE;

}//InsertOneRow

//-----------------------------------------------------------------------
//    Function: ExecEndTran
//
//Execute EndTran statement, return the row count of the table
//-----------------------------------------------------------------------
SWORD  PASCAL ExecEndTran(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd,SQLSMALLINT fHandleType, SQLSMALLINT fMode,
UWORD cSeed)
{
  RETCODE rc;

//does not free the hstmt
if(!InsertOneRow(lpqt,rgFields,cTypes,lpd,cSeed, FALSE))
return (INSERT_FAIL);

switch(fHandleType)
{
case SQL_HANDLE_ENV:
rc= SQLEndTran(fHandleType,henv,fMode);
break;
case SQL_HANDLE_DBC:
rc= SQLEndTran(fHandleType,hdbc,fMode);
break;
default:
DISPLAYERROR(TEXT("ExecEndTran"),TEXT("Did not recieve correct handle type"));
break;
}
RETCHECK(SQL_SUCCESS, rc,szSQLENDTRAN);

if (SQL_COMMIT == fMode && SQL_SUCCESS == rc)
guwRowCount++;// We inserted a row, increment global row count

//free the hstmt handle
FreeStmt(SQL_CLOSE);

return(GetRowCnt(lpqt));
} //ExecEndTran

//-----------------------------------------------------------------------
//    Function: TestEndTran
//
//Test the SQLEndTran for SDK 3.0
//-----------------------------------------------------------------------
void  PASCAL TestEndTran(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd)
{
SWORDi;
RETCODE rc;
SWORDsTXN;
SWORDsRowCnt_Pre;
SWORDsRowCnt_Aft;
SQLSMALLINT fHandleType[]={SQL_HANDLE_ENV, SQL_HANDLE_DBC};
SDWORDcbValue=0;

//check if transaction is supported
rc = SQLGetInfo(hdbc, SQL_TXN_CAPABLE, &sTXN, sizeof(sTXN), NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

//check both henv and hdbc handle
for(i=0; i<=1; i++)
{

//get the row count of the table
sRowCnt_Pre = GetRowCnt(lpqt);

//check if SQLEndTran is supported
if(Supported(SQL_API_SQLENDTRAN))
{
if(sTXN != SQL_TC_NONE)
{
//set the autocommit to off
SetConnectionAttributes(hdbc,SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);

//insert one row, and call SQLEndTran to roll back the transction
sRowCnt_Aft = ExecEndTran(lpqt,rgFields,cTypes,lpd,fHandleType[i],SQL_ROLLBACK,(UWORD)(guwRowCount));

//the row count should be the same
if(sRowCnt_Aft != sRowCnt_Pre)
DISPLAYERROR(szSQLENDTRAN,TEXT("Did not Roll Back the insert transaction"));
}

//insert one row, and call SQLEndTran to commit the transction
sRowCnt_Aft = ExecEndTran(lpqt,rgFields,cTypes,lpd,fHandleType[i],SQL_COMMIT,(UWORD)(guwRowCount));

//the row count should be increased by 1
if(sRowCnt_Aft != (sRowCnt_Pre+1) )
DISPLAYERROR(szSQLENDTRAN,TEXT("Did not commit the insert transaction"));

if( sTXN != SQL_TC_NONE)
{
//set the autocommit to off
SetConnectionAttributes(hdbc,SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
}

}//end of the situation where SQLEndTran is supported
else
{

switch(fHandleType[i])
{
case SQL_HANDLE_ENV:
rc= SQLEndTran(fHandleType[i],henv,SQL_COMMIT);
break;
case SQL_HANDLE_DBC:
rc= SQLEndTran(fHandleType[i],hdbc,SQL_ROLLBACK);
break;
}

//check error msg
if(!FindError(fHandleType[i],szIM001))
DISPLAYERROR(szSQLENDTRAN,TEXT("Did not return not Supported Message"));

RETCHECK(SQL_ERROR, rc,szSQLENDTRAN);

//free the hstmt handle
FreeStmt(SQL_CLOSE);

}//end of error msg checking

}//end of the loop to check both henv and hdbc

// Commit the transaction started in GetRowCnt inside ExecEndTran
rc= SQLEndTran(SQL_HANDLE_ENV,henv,SQL_COMMIT);
RETCHECK(SQL_SUCCESS, rc,szSQLENDTRAN);

SetConnectionAttributes(hdbc,SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON);

//free the hstmt handle
FreeStmt(SQL_CLOSE);

} // TestEndTran()

//-----------------------------------------------------------------------
//    Function: TestBindParam
//
//Test the SQLBindParam for SDK 3.0
//-----------------------------------------------------------------------
void  PASCAL TestBindParam(QTSTRUCT  *lpqt,FIELDINFO  *rgFields,UWORD cTypes,
DSTRUCT  *lpd)
{
 RETCODE rc;
SQLINTEGERcbValue = SQL_NTS;
TCHARszDataItem[150];

if(Supported(SQL_API_SQLBINDPARAM))
{
//test SQLBindParam by inserting one row, do not free the hstmt handle
if (InsertOneRow(lpqt,rgFields,cTypes,lpd,(UWORD)(guwRowCount),FALSE))
guwRowCount++; // We've inserted a row
}
else
{
 //prepare the insert statement
rc = SQLPrepare(hstmt, szInsertStmt, SQL_NTS);
RETCHECK(SQL_SUCCESS, rc,szSQLPREPARE);

//make data according to data type description in rgFields[0], cSeed is the seed
qtMakeData(cTypes+1,0,&rgFields[0],szDataItem);

//try to bind lpqt->szDataItem to the first column of the table
rc = SQLBindParam(hstmt,1,SQL_C_TCHAR, rgFields[0].wSQLType, rgFields[0].precision,
rgFields[0].scale,lpqt->szDataItem, &cbValue);

//check error msg
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLBINDPARAM,TEXT("Did not return Not Supported Message"));

RETCHECK(SQL_ERROR, rc,szSQLBINDPARAM);
}

//free the hstmt handle
FreeStmt(SQL_CLOSE);

} // TestBindParam()

//-----------------------------------------------------------------------
//    Function: TestFetchScroll
//
//Test the SQLFetchScroll for SDK 3.0
//-----------------------------------------------------------------------

void PASCAL TestFetchScroll(QTSTRUCT *lpqt)
{
RETCODErc=SQL_SUCCESS;
TCHARszSave[MAX_QUERY_SIZE];
DWORDdwLen=0;
SDWORDcbValue=0;

/* check if SQLGetFunctions says your API is supported */
if(!Supported( SQL_API_SQLFETCHSCROLL))
{
/* Call your 3.0 API here and verify that the correct state is returned */
rc = SQLFetchScroll(hstmt,SQL_FETCH_FIRST, 0);
if(!FindError(SQL_HANDLE_STMT,szIM001))
DISPLAYERROR(szSQLFETCHSCROLL, szNotSupported);
RETCHECK(SQL_ERROR, rc, szSQLFETCHSCROLL);
}
else
{
ResetHstmt(&hstmt);

/* Valid Testing of SQLFetchScroll */
SelectFromTable(lpqt);

rc = SQLBindCol(hstmt, 1, SQL_C_TCHAR, lpqt->sz, MAX_BIND_ARRAY_ELEMENT,
&cbValue);
RETCHECK(SQL_SUCCESS, rc, szSQLBINDCOL);

rc = SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 3);
RETCHECK(SQL_SUCCESS, rc, szSQLFETCHSCROLL);
lstrcpy(szSave,lpqt->sz);

rc = SQLFetchScroll(hstmt, SQL_FETCH_PRIOR, 1);
RETCHECK(SQL_ERROR, rc, szSQLFETCHSCROLL);

rc = SQLFetchScroll(hstmt, SQL_FETCH_FIRST, 0);
RETCHECK(SQL_ERROR, rc, szSQLFETCHSCROLL);

rc = SQLFetchScroll(hstmt, SQL_FETCH_LAST, 0);
RETCHECK(SQL_ERROR, rc, szSQLFETCHSCROLL);

rc = SQLFreeStmt(hstmt, SQL_CLOSE);
RETCHECK(SQL_SUCCESS, rc, szSQLFREESTMT);

}

//free the hstmt handle
FreeStmt(SQL_CLOSE);

} // TestFetchScroll()

/*-----------------------------------------------------------------------*/
/*      Function:               qtDisplayError                           */
/*-----------------------------------------------------------------------*/

void  PASCAL qtDisplayError(LPTSTR szFunction, LPTSTR buf, LPTSTR szFile, int iLine)
{
TCHAR szTmp[MAX_STRING_SIZE];

szWrite(TEXT(""), TRUE);
szWrite(TEXT("\t\t\t"), FALSE);
szWrite(szFunction, FALSE);
szWrite(TEXT("  FAILED"), TRUE);
szWrite(TEXT("\t\t\t"), FALSE);
szWrite(buf, TRUE);

wsprintf(szTmp,TEXT("\t\t\t%s: %d"), szFile, iLine);
szWrite(szTmp, TRUE);

szWrite(TEXT("\t\t\t  --------  "), TRUE);

lpSI->failed++;

return;
}

/*-----------------------------------------------------------------------*/
/*      Function:               qtMakeData                               */
/*-----------------------------------------------------------------------*/

LPTSTR  PASCAL qtMakeData(int row, int col,FIELDINFO  *rgField, LPTSTR buf)
{
if(rgField->fAutoUpdate)
return NULL;

if(rgField->nullable && (row==col))
{
lstrcpy(buf,TEXT(""));
return buf;
}

switch(rgField->wSQLType) {
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
if(rgField->precision < 4)
wsprintf(buf,TEXT("%d"), 1);
else
wsprintf(buf,TEXT("%d%d"), row, row);
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
if(row == 2 && !rgField->fUnsigned) /* make the second row negative for variety */
wsprintf(buf,TEXT("-%d.%d"), row, row);
else
wsprintf(buf,TEXT("%d.%d"), row, row);
break;

case SQL_BIT:
if(row > 2)
wsprintf(buf,TEXT("%d"), 1);
else
wsprintf(buf,TEXT("%d"), 0);

break;

case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_TINYINT:
case SQL_BIGINT:
if(row == 2 && !rgField->fUnsigned) /* make the second row negative for variety */
wsprintf(buf,TEXT("-%d"), row);
else
wsprintf(buf,TEXT("%d"), row);
break;

case SQL_TIME:
case SQL_TYPE_TIME:
wsprintf(buf,TEXT("{t '01:%02d:%02d'}"), row % 60, row % 60);
break;
case SQL_DATE:
case SQL_TYPE_DATE:
wsprintf(buf,TEXT("{d '1994-%02d-%02d'}"), (row % 12) + 1, (row % 30) + 1);
break;
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
wsprintf(buf,TEXT("{ts '1994-%02d-%02d 01:%02d:%02d'}"), (row % 12) + 1, (row % 30) + 1, row % 60, row % 60);
break;
}
return buf;
}

/*-----------------------------------------------------------------------*/
/*      Function:               TestNumResCols                           */
/*-----------------------------------------------------------------------*/

VOID PASCAL TestNumResCols(SDWORD cColsSelected,QTSTRUCT *lpqt)
{
RETCODErc=SQL_SUCCESS;
SWORDwNum;

SelectFromTable(lpqt);

rc = SQLNumResultCols(hstmt, &wNum);
RETCHECK(SQL_SUCCESS, rc,szSQLNUMRESULTCOLS);

if(wNum != cColsSelected)
DISPLAYERROR(szSQLNUMRESULTCOLS,TEXT("incorrect value returned"));

FreeStmt(SQL_CLOSE);

} /* TestNumResCols() */

/*-----------------------------------------------------------------------*/
/*      Function:               TestNumResCols                           */
/*-----------------------------------------------------------------------*/

VOID PASCAL TestCursorName(QTSTRUCT *lpqt)
{
RETCODErc=SQL_SUCCESS;
TCHARszCursorName[MEDBUFF];
SWORDcb=0;

SelectFromTable(lpqt);

/* the cursor name should be created by the driver since one was*/
/* not specified*/

rc = SQLGetCursorName(hstmt, szCursorName, MAX_STRING_SIZE, &cb);
RETCHECK(SQL_SUCCESS, rc,szSQLGETCURSORNAME);

if(cb > MAX_STRING_SIZE)
{
DISPLAYERROR(szSQLGETCURSORNAME,TEXT("invalid cb"));
cb = MAX_STRING_SIZE;
}

/* don't check the name itself, just make sure that*/
/*something was returned */
if(!*szCursorName)
DISPLAYERROR(szSQLGETCURSORNAME,TEXT("no name returned"));

FreeStmt(SQL_CLOSE);

} /* TestCursorName() */

//-----------------------------------------------------------------------
//      Function:               GetDMVersion
//-----------------------------------------------------------------------

UWORD PASCAL GetDMVersion()
{
RETCODErc=SQL_SUCCESS;
TCHARszNum[MAX_NUM_BUFFER];

rc = SQLGetInfo(hdbc, SQL_ODBC_VER, &szNum, MAX_NUM_BUFFER, NULL);
RETCHECK(SQL_SUCCESS, rc,szSQLGETINFO);

return((UWORD)_ttoi(_tcstok(szNum,TEXT("."))));

} /* GetDMVersion() */

//-----------------------------------------------------------------------
//      Function:               DisplayTestName
//-----------------------------------------------------------------------

VOID PASCAL DisplayTestName(UWORD i)
{
szWrite(szBLANKLINE,TRUE);
szWrite(szTAB,FALSE);
szWrite(iTestNames[i-1], TRUE);

} /* DisplayTestName() */

/***************************  External Interfaces  *************************/
/*  These functions are called by Gator to envoke the tests.               */
/***************************************************************************/

BOOL CALLBACK AutoTestName(LPTSTR szName, UINT  * cbTestCases)
{
static TCHAR szTestName[] = TEXT("Quick Test");

lstrcpy(szName, szTestName);

*cbTestCases = sizeof(iTestNames)/sizeof(LPTSTR);

return TRUE;
}

//-----------------------------------------------------------------------
//      Function:               AutoTestDesc
//      Purpose:                        Provides gator with a list of tests which can be run.
//-----------------------------------------------------------------------
BOOL CALLBACK AutoTestDesc(UWORD iTest, LPTSTR szName, LPTSTR szDesc)
{

UWORD len=0;

if (iTest > (sizeof(iTestNames)/sizeof(LPTSTR)))
return FALSE;

if (szName)
     lstrcpy(szName,iTestNames[iTest-1]);

if (szDesc)
        lstrcpy(szDesc,TEXT(" "));

return TRUE;

}

//-----------------------------------------------------------------------
//      Function:               AutoTestFunc
//-----------------------------------------------------------------------

void CALLBACK AutoTestFunc(lpSERVERINFO pTestSource)
{

RETCODErc;
UWORDcTypes;
UDWORDdwLen;
UWORDfIndex;
SDWORDcColsSelected;
FIELDINFO*rgFields = (FIELDINFO  *)AllocateMemory(sizeof(FIELDINFO) * MAX_TYPES_SUPPORTED);
QTSTRUCT*lpqt = (QTSTRUCT  *)AllocateMemory(sizeof (QTSTRUCT));
DSTRUCT*lpd = (DSTRUCT  *)AllocateMemory(sizeof (DSTRUCT) * MAX_TYPES_SUPPORTED);
UWORDuDMVer;
SDWORDcbValue=0;
TCHARszAPI[MEDBUFF];
UWORDi;
TCHARszState[100]=TEXT(""),
szErrMsg[XLARGEBUFF]=TEXT("");

lpSI = pTestSource;
lpSI->failed = 0;
henv = NULL;
hdbc = NULL;
hstmt = NULL;
hdesc = NULL;

if(lpSI->szValidServer0)
{
if (Connect(lpSI,lpqt))
goto ErrorRet;
}
else
{
henv = lpSI->henv;
hdbc = lpSI->hdbc;

rc = SQLSetConnectOption(hdbc,SQL_ODBC_CURSORS,dwLen);
lstrcpy(szAPI,szSQLSETCONNECTOPTION);

if(RETCHECK(SQL_SUCCESS, rc,szAPI))
lpSI->vCursorLib = dwLen;
}

 uDMVer=GetDMVersion();

fIndex=CheckConformanceLevel();

fBindParameter=Supported(SQL_API_SQLBINDPARAMETER);
fDiagRecSupported=Supported(SQL_API_SQLGETDIAGREC);
fDiagFieldSupported=Supported(SQL_API_SQLGETDIAGFIELD);

AllocHstmt();

if (!(cTypes=GetTypeInfo(rgFields,lpqt)))
goto ErrorRet;

guwRowCount=cTypes+1;// Extra row inserted with DAE.

// cTypes=20;  //DEBUG

/* build create statement */
if (BuildCreateStmt(lpqt,rgFields,cTypes))
goto ErrorRet;

/* put together the insert statement, and set the parameters   */
/* parameters are only set the first time through, after which */
/* the contents of the pointers is changed                     */
if (BuildInsertStmt(lpqt, rgFields,cTypes,lpd,&cColsSelected,fBindParameter))
goto ErrorRet;

CreateParamQuery(lpqt,rgFields,cTypes);

for(i=1;i <= (sizeof(iTestNames)/sizeof(LPTSTR));i++)
{
lpqt->sz[0] = TEXT('\0');

if(GETBIT(pTestSource->rglMask,i))
{
/* Display current Test */
DisplayTestName(i);

switch(i)
{
case 1:
TestConnectionOptions();
break;
case 2:
TestStmtOptions();
break;
case 3:
TestCursorName(lpqt);
break;
case 4:
TestData(lpqt,rgFields,cTypes,lpd);
break;
case 5:
TestNumResCols(cColsSelected,lpqt);
break;
case 6:
TestMetaData(lpSI,lpqt,rgFields,cTypes);
break;
case 7:
TestSearchedQuery(lpqt,rgFields,cTypes,lpd,fBindParameter);
break;
case 8:
TestLargeQuery(lpqt,rgFields,cTypes,lpd);
break;
case 9:
TestSQLTables(lpqt);
break;
case 10:
TestSQLStatistics(lpqt,rgFields,cTypes,lpd,fIndex);
break;
case 11:
TestSQLSpecialCols(lpqt);
break;
case 12:
TestLikeQuery(lpqt,rgFields,cTypes,lpd);
break;
case 13:
TestSQLForeignKeys(lpqt);
break;
case 14:
TestSQLBrowseConnect(lpSI);
break;
case 15:
TestSQLDataSources();
break;
case 16:
TestSQLDrivers();
break;
 case 17:
TestSQLMoreResults(lpqt);
break;
 case 18:
TestSQLNativeSQL(lpqt);
break;
case 19:
TestSQLDescribeParam(lpqt);
break;
case 20:
TestSQLNumParams(lpqt);
break;
case 21:
TestSQLParamOptions(lpqt);
break;
case 22:
TestSQLPrimaryKeys(lpqt);
break;
case 23:
TestSQLProcedures();
break;
case 24:
TestSQLTablePrivileges();
break;
case 25:
TestSQLColumnPrivileges(lpqt);
break;
case 26:
TestSQLSetScrollOptions();
break;
case 27:
TestExtendedFetch(lpqt,rgFields,cTypes);
break;
case 28:
TestOJCap(lpqt);
break;
case 29:
TestSQLSetConnectAttr();
break;
case 30:
TestSQLSetStmtAttr();
break;
case 31:
TestThreading(lpSI,lpqt,rgFields);
break;
case 32:
TestGetDescField(lpqt);
break;
case 33:
TestSetDescField(lpqt);
break;
case 34:
TestGetDescRec(lpqt);
break;
case 35:
TestSetDescRec(lpqt);
break;
case 36:
TestCopyDesc(lpqt);
break;
case 37:
TestDescDefaults(lpqt);
break;
case 38:
TestUseDesc(lpqt, rgFields);
break;
case 39:
TestEnvAttr();
break;
case 40:
TestEndTran(lpqt,rgFields,cTypes,lpd);
break;
case 41:
TestBindParam(lpqt,rgFields,cTypes,lpd);
break;
case 42:
TestQuickRebind(lpqt,rgFields,cTypes,lpd);
break;
case 43:
TestFetchScroll(lpqt);
break;
case 44:
TestDiagRec();
break;
case 45:
TestDiagField();
break;
case 46:
TestMixedAnsiUnicode(lpqt);
break;

} /* switch(i) */

} /* if(GETBIT(pTestSource->rglMask,i)) */

}

//the end of test cases for ODBC SDK 3.0

/* finished testing, clean up */
CleanUp(lpd,lpqt,rgFields,lpqt->szTableName,FALSE,lpSI->szValidServer0);

szWrite(szBLANKLINE,TRUE);

lpSI->cErrors = lpSI->failed;

return;

ErrorRet:
/* a failure in an ODBC function that prevents completion of the*/
/* test - for example, connect to the server */

szWrite(TEXT("\t\t *** Unrecoverable Quick Test FAILURE ***"), TRUE);

/* finished testing, clean up */
CleanUp(lpd,lpqt,rgFields,lpqt->szTableName,FALSE,lpSI->szValidServer0);

lpSI->cErrors = ABORT;

return;
}


QUIKTEST.H

//****************************************************************************
//|   Title:   quiktest.h
//|
//|   (c) Copyright 1991, 1992, 1993, 1994, 1995
//|       by Microsoft Corp.
//|
//******************************************************************************
#ifndef quiktest_defs
#define quiktest_defs

/*-----------------------------------------------------------------------------------*/
/*constants defines */
/*-----------------------------------------------------------------------------------*/
#define MAX_QUERY_SIZE2048
#define MAX_BIND_ELEMENTS100
#define MAX_BIND_ARRAY_ELEMENTMAX_QUERY_SIZE / MAX_BIND_ELEMENTS
#define MAX_STRING_SIZE350  // Increased from 255 due to overwrite
#define MAX_PARAM_SIZE129
#define MAX_DATA_SIZE45
#define MAX_COLUMN_LIST_SIZE200
#define MAX_COLUMN_NAME_SIZE100
#define MAX_INSERT_VALUES_SIZE200
#define MAX_TYPES_SUPPORTED25
#define MAX_TYPE_NAME129
#define MAX_FIELD40
#define MAX_PREFIX129
#define MAX_SUFFIX129
#define MAX_TABLE_NAME60
#define MAX_NUM_BUFFER60
#define MAX_ERROR_SIZE200
#define MAX_ROW_ITEM_LIMIT4

#define PREFIX_SIZE3
#define IGNORED999
#define TEST_CONNECT_OPTION15
#define TEST_STMT_OPTION300
#define SCALEDIFF4
#define ABORT-1
#define TYPE_PORTION4
#define MAXUDWORD((UDWORD)(-1))
#define MINSDWORD(-(SDWORD)((MAXUDWORD / 2) + 1))
#define MAXSDWORD((SDWORD)(MAXUDWORD / 2))

#define SMALLBUFF100
#define MEDBUFF200
#define LARGEBUFF300
#define XLARGEBUFF1024
#define STATE_SIZE6
#define COMBINED_SIZEMAX_ERROR_SIZE + 30

// Descriptor type defines
#define DESC_NONE0// No descriptor type
#define DESC_APD1// APD only
#define DESC_IPD2// IPD only
#define DESC_ARD4// ARD only
#define DESC_IRD8// IRD only
#define DESC_PDDESC_APD|DESC_IPD//Parameter descriptors
#define DESC_RDDESC_ARD|DESC_IRD// Row descriptors
#define DESC_IDDESC_IPD|DESC_IRD// Implementation descriptors
#define DESC_ADDESC_APD|DESC_ARD// Application descriptors
#define DESC_MOSTDESC_AD|DESC_IPD// This is the most common settable combination
#define DESC_ALLDESC_APD|DESC_IPD|DESC_ARD|DESC_IRD// All descriptor types

#define STATUS_ARRAY_SIZE5// Array size to use for row and param arrays
#define BIND_SIZE12// Bind type size value

/* NUM_THREAD is the number of threads that will be created for multi-thread test */
#define NUM_THREAD5
#define NUM_QUIKREBIND_ROWSETS6

/* BIND_PARM_OFFSET specifies the parameter base for DAE bindings. Either 0 */
/* or 1 based.  */
#define BIND_PARM_OFFSET0

// COLNAME_START specifies the beginning number used in colnames, i.e. c00bit
// vs. c01bit.
#define COLNAME_START1

/*constants for Descriptor tests */
#define MAX_DESC_BUF200
#define DESC_UPDATE_MODE_READ0
#define DESC_UPDATE_MODE_WRITE1
#define REC_COUNT_UNK-1// Unkown record count
// #define DESC_LOCATE_FAIL-1

/* constants for TestBindParam */
#define INSERT_FAIL  -1

#define UNTOUCHED 0xCA

/*------------------------------------------------------------------------------------*/
/*macros                                                                             */
/*------------------------------------------------------------------------------------*/

#define RC_SUCCESSFUL(rc)((rc) == SQL_SUCCESS || \
    (rc) == SQL_SUCCESS_WITH_INFO)

#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))

#define RETCHECK(exp, rec, buf) ReturnCheck(exp, rec, buf, TEXT(__FILE__), __LINE__,FALSE)

#define ERRSHANDLE(type, handle, exp, rec, buf) ErrsHandle(type, handle, exp, rec, buf, TEXT(__FILE__), __LINE__,FALSE)

#define DIAGRETCHECK(exp, rec, buf) ReturnCheck(exp, rec, buf, TEXT(__FILE__), __LINE__,TRUE)

#define DISPLAYERROR(func, string) qtDisplayError(func, string, TEXT(__FILE__), __LINE__)

/*--------------------------------------------------------------------------------------*/
/*Structure definitions    */
/*--------------------------------------------------------------------------------------*/

/* Number of Cols in result set of a query: */
UWORD cNumResSetCols;

/* used to store information from GetTypeInfo for table creation */
typedef struct FieldInfo {
int iField;
TCHAR szType[MAX_TYPE_NAME];
TCHAR szFieldName[MAX_FIELD];
SWORD  wSQLType;
TCHAR szParams[MAX_PARAM_SIZE];
TCHAR szLength[MAX_FIELD];
TCHAR szPrefix[MAX_PREFIX];
TCHAR szSuffix[MAX_SUFFIX];
SDWORD precision;
SWORD scale;
SWORD nullable;
UDWORD length;
int   fAutoUpdate;
SWORD fSearchable;
SDWORD fUnsigned;
SWORD autoinc;
} FIELDINFO;

/* large buffers allocated as a global used for queries and other*/
/* returned information                                          */
typedef struct  tagQtStruct {
TCHARbuf[MAX_STRING_SIZE];
TCHARsz[MAX_QUERY_SIZE];
TCHARszParamQuery[MAX_QUERY_SIZE];
TCHARszParam[MAX_PARAM_SIZE];
TCHARszDataItem[MAX_DATA_SIZE];
TCHARszTableName[MAX_TABLE_NAME];
TCHARszColNames[MAX_COLUMN_LIST_SIZE];
TCHARszColName[MAX_COLUMN_NAME_SIZE];
TCHARszValues[MAX_INSERT_VALUES_SIZE];
}       QTSTRUCT;

/* the storage used for data retreived using bind/fetch sequence  */
/* and data for BindParameter                                     */
/* only cb and one of the types will be used for each entry       */
typedef struct tagDataStruct {
SDWORD cb;
TCHAR data[MAX_STRING_SIZE];
SDWORD sdword;
UDWORD udword;
SWORD sword;
UWORD uword;
TCHARsChar;
SQLTCHAR uChar;
SDOUBLE sdouble;
SFLOAT sfloat;
TIME_STRUCT time;
DATE_STRUCT date;
TIMESTAMP_STRUCT timestamp;
} DSTRUCT;

/* These declarations used to reside locally, but the structures didn't get initialized correctly */
typedef struct SupportOptList {
UDWORD Support;
SDWORD Option;
} SUPPORTOPTINFO;

typedef struct SupportConcurList {
UDWORD Support;
UWORD Option;
} SUPPORTCONCURINFO;

/* Each thread will contain the following information */
typedef struct {
HENVhenv;
HDBChdbc;
TCHARszTableName[MAX_TABLE_NAME];
FIELDINFO rgFieldInfo;

} THREAD_STRUCT;

static union
{
BYTE rgb[max(max(max(sizeof (HENV),sizeof (HDBC)),
max(sizeof (HSTMT), sizeof (SQLHDESC))),sizeof (UDWORD))];
SWORD sw;
UWORD uw;
SDWORD sdw;
UDWORD udw;
HENV henv;
HDBC hdbc;
HSTMT hstmt;
SQLHDESC hdesc;
} Untouched = {UNTOUCHED, UNTOUCHED, UNTOUCHED, UNTOUCHED};

int  PASCAL ReturnCheck(RETCODE retExpected, RETCODE retReceived, LPTSTR szFunction,
LPTSTR szFile, int iLine,BOOL fDiag);
void PASCAL qtDisplayError(LPTSTR szFunction, LPTSTR buf, LPTSTR szFile, int iLine);
LPTSTR PASCAL qtMakeData(int row, int col,FIELDINFO * rgField, LPTSTR buf);

BOOL PASCAL CmpODBCtoCHAR(DSTRUCT * rgTestValue, TCHAR * szTrueValue,
SWORD fSqlType, SWORD iResSetCol);

void* PASCAL CvtCHARtoODBC(void * odbcValue, TCHAR * buf, SWORD fSqlType)
{
if(!buf)
return NULL;

switch (fSqlType) {
case SQL_DATE:
case SQL_TYPE_DATE:
{
TCHAR szDATEDELIM[] = TEXT("d{}'- ");
TCHAR szTempBuff[30];
LPTSTR   tmpstr=szTempBuff;
_tcscpy((LPTSTR) szTempBuff, buf);

((DATE_STRUCT *)odbcValue)->year = _ttoi(_tcstok(tmpstr, szDATEDELIM));
((DATE_STRUCT *)odbcValue)->month = _ttoi(_tcstok(NULL, szDATEDELIM));
((DATE_STRUCT *)odbcValue)->day = _ttoi(_tcstok(NULL, szDATEDELIM));
break;
}

case SQL_TIME:
case SQL_TYPE_TIME:
{
TCHAR szTIMEDELIM[] = TEXT("t{}': ");
TCHAR szTempBuff[30];
LPTSTR   tmpstr=szTempBuff;
_tcscpy((LPTSTR) szTempBuff, buf);

((TIME_STRUCT *)odbcValue)->hour = _ttoi(_tcstok(tmpstr, szTIMEDELIM));
((TIME_STRUCT *)odbcValue)->minute = _ttoi(_tcstok(NULL, szTIMEDELIM));
((TIME_STRUCT *)odbcValue)->second = _ttoi(_tcstok(NULL, szTIMEDELIM));

break;
}

case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
{
TCHAR szDATETIMEDELIM[] = TEXT("ts'{}-:. ");
TCHAR szTempBuff[30];
LPTSTR   tmpstr=szTempBuff;
LPTSTRendstr, frstr;
_tcscpy((LPTSTR) szTempBuff, buf);

/* First the date */
((TIMESTAMP_STRUCT *)odbcValue)->year = _ttoi(_tcstok(tmpstr, szDATETIMEDELIM));
((TIMESTAMP_STRUCT *)odbcValue)->month = _ttoi(_tcstok(NULL, szDATETIMEDELIM));
((TIMESTAMP_STRUCT *)odbcValue)->day = _ttoi(_tcstok(NULL, szDATETIMEDELIM));
/* Then the complete time */
((TIMESTAMP_STRUCT *)odbcValue)->hour = _ttoi(_tcstok(NULL, szDATETIMEDELIM));
((TIMESTAMP_STRUCT *)odbcValue)->minute = _ttoi(_tcstok(NULL, szDATETIMEDELIM));
((TIMESTAMP_STRUCT *)odbcValue)->second = _ttoi(_tcstok(NULL, szDATETIMEDELIM));
if(frstr = _tcstok(NULL, szDATETIMEDELIM))
((TIMESTAMP_STRUCT *)odbcValue)->fraction = (_tcstoul(frstr, &endstr, 10) * 1000000);
else
((TIMESTAMP_STRUCT *)odbcValue)->fraction = 0;

break;
}

}
return odbcValue;
}

/* Compares the value stored at location (row,col) in the array of
 * DSTRUCT's with the value created by qtMakeData*/
BOOL PASCAL CmpODBCtoCHAR(DSTRUCT * rgTestValue, TCHAR * szTrueValue,
SWORD fSqlType, SWORD iCol)
{
BOOL fTestPass=TRUE;
SWORD test1,test2;
TCHAR*pStop=NULL;

switch (fSqlType) {
case SQL_BIGINT:
case SQL_INTEGER:
if(_ttoi(szTrueValue) == (int)rgTestValue[iCol].sdword)
return TRUE;
return FALSE;

case SQL_DECIMAL:
case SQL_NUMERIC:
return  (_tcstod(szTrueValue,&pStop) ==
(float)_tcstod(rgTestValue[iCol].data,&pStop));
case SQL_TINYINT:
case SQL_SMALLINT:
if(_ttoi(szTrueValue) == (int)rgTestValue[iCol].sword)
return TRUE;
return FALSE;

case SQL_FLOAT:
case SQL_DOUBLE:
return  (_tcstod(szTrueValue,&pStop) == (float)rgTestValue[iCol].sdouble);

case SQL_REAL:
/* Need to account for round-off errors in _tcstod conversions: */
test1 = (SWORD)(1000*_tcstod(szTrueValue,&pStop));
test1 = (SWORD)((test1>0) ? (test1+5)/10 : (test1-5)/10);
test2 = (SWORD)(1000*rgTestValue[iCol].sfloat);
test2 = (SWORD)((test2>0) ? (test2+5)/10 : (test2-5)/10);
return  (test1 == test2);

case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
{
TIMESTAMP_STRUCT tss;
CvtCHARtoODBC(&tss, szTrueValue, SQL_TYPE_TIMESTAMP);

fTestPass= ((rgTestValue[iCol].timestamp.month == tss.month)
&& (rgTestValue[iCol].timestamp.day == tss.day)
&& (rgTestValue[iCol].timestamp.year == tss.year)
&& (rgTestValue[iCol].timestamp.hour == tss.hour)
&& (rgTestValue[iCol].timestamp.minute == tss.minute)
&& (rgTestValue[iCol].timestamp.fraction ==
tss.fraction));
/* Seconds value is zero for small timestamp, so to keep it from always failing... */
if(0 != rgTestValue[iCol].timestamp.second)
fTestPass= (fTestPass && rgTestValue[iCol].timestamp.second == tss.second);
return fTestPass;
}

case SQL_TIME:
case SQL_TYPE_TIME:
{
TIME_STRUCT tss;

CvtCHARtoODBC(&tss, szTrueValue, SQL_TYPE_TIME);
fTestPass=  ((rgTestValue[iCol].time.hour == tss.hour)
&& (rgTestValue[iCol].time.minute == tss.minute));
/* Seconds value is zero for small timestamp, so to keep it from always failing... */
if(0 != rgTestValue[iCol].time.second)
fTestPass= (fTestPass && rgTestValue[iCol].time.second == tss.second);
return fTestPass;
}

case SQL_DATE:
case SQL_TYPE_DATE:
{
DATE_STRUCT tss;
CvtCHARtoODBC(&tss, szTrueValue, SQL_TYPE_DATE);

return ((rgTestValue[iCol].date.month == tss.month)
&& (rgTestValue[iCol].date.day == tss.day)
&& (rgTestValue[iCol].date.year == tss.year));
}

 case SQL_BIT:
 case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
default:
return !(_tcsncmp(szTrueValue, rgTestValue[iCol].data,
_tcslen(szTrueValue)));

}
}

#endif


MAIN.C

/*
    PROGRAM: QURYDEMO
    ========

    PURPOSE:
    ========
    demonstrates a simple MDI application that allows a user to
    simultaneously connect to multiple hetrogeneous databases
    and perform SQL queries to get results.

    FUNCTIONS:
    ==========
    WinMain() - main routine
    MainWndProc() - processes Main Application Window messages
    MDIChildProc() - processes MDI child window messages
    ToolbarProc() - processes tool bar messages
    StatusbarProc() - processes Status bar messages
    ConnectDlgProc() - processes Connection Dialog box messages
    DisconnectDlgProc() - processes Disconnect Dialog box messages
    AboutDlgProc() - processes messages for About dialog box
    MDIChildDlgProc() - processes messages for dummy child dialog box
            in MDI child window
    DrawBitmap() - draws bitmaps for toolbuttons

    COMMENTS:
    =========
    Created by Microsoft Corporation.
*/

#include <stdio.h>
#include <string.h>
#include <time.h>

#include <windows.h>
#ifdef WIN32
#include <windowsx.h>
#else
#include "w16macro.h"
#endif
#include "qurydemo.h"

CONSTSTR(szKeyWord, "query demo, features");

/*
    FUNCTION: WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    COMMENTS: Application Entry Routine.
          Register Classes. Create Main Window and MDI Child Window.
          Process Main Message Loop.
*/

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   MSG     msg;            //msg structure
   WNDCLASS    wc;         //class structure
   HICON       hMainIcon;      //App Icon
   HICON       hMDIChildIcon;      //MDI Child Icon
   HWND        hWndMDIChild;       //temp MDI Child Window Handle
   char        szBuffer[MAXBUFLEN+1];  //temp string buffer to check class name
   BOOL        bDialogMessage = FALSE; //temp boolean to check dilogbox msgs
   HACCEL      hAccel;         //accelerator table handle

   hAppInstance = hInstance;

   // check if application is already running, if
   // so make it active and bring it in focus

   if (hWndFrame = FindWindow(ODBCFRAMECLASS, NULL)) {
      hWndFrame = GetLastActivePopup(hWndFrame);
      if (IsIconic(hWndFrame))
         OpenIcon(hWndFrame);
      else
         BringWindowToTop(hWndFrame);
      ACTIVATEWINDOW(hWndFrame);
      return (FALSE);
   }

   // initialize ODBC Driver Manager Interface

   if (!InitSQLEnvironment()) {
      MessageBox(hWndFrame, INITERROR, EXECERROR, MB_OK|MB_ICONHAND);
      return (FALSE);
   }

   // register window classes for the application - Main Window Class

   wc.style = 0;
   wc.lpfnWndProc = MainWndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = hAppInstance;
   wc.hIcon = hMainIcon = LoadIcon(hAppInstance, APPICON);
   wc.hCursor = LoadCursor((HINSTANCE)NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
   wc.lpszMenuName =  QURYDEMOMENU;
   wc.lpszClassName = ODBCFRAMECLASS;

   if (!RegisterClass(&wc)) {
      MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
      return (FALSE);
   }

   // register Toolbar Class

   wc.hIcon = (HICON)NULL;
   wc.lpszMenuName =  NULL;
   wc.lpfnWndProc = ToolbarProc;
   wc.lpszClassName = ODBCTOOLCLASS;

   if (!RegisterClass(&wc)) {
      MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
      return (FALSE);
   }

   // register Statusbar Class

   wc.lpfnWndProc = StatusbarProc;
   wc.lpszClassName = ODBCSTATUSCLASS;

   if (!RegisterClass(&wc)) {
      MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
      return (FALSE);
   }

   // register MDI Child Window Class

   wc.hIcon = hMDIChildIcon = LoadIcon(hAppInstance, QUERYWINDOWICON);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wc.cbWndExtra = CBWNDEXTRA;
   wc.lpszClassName = ODBCMDICLASS;
   wc.lpfnWndProc = MDIChildProc;

   if (!RegisterClass(&wc)) {
      MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
      return (FALSE);
   }

   // create Main window and the MDI Client window

   if (!(hWndFrame = CreateWindow(ODBCFRAMECLASS, APPTITLE, WS_OVERLAPPEDWINDOW,
                                  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                                  (HWND)NULL, (HMENU)NULL, hAppInstance, NULL))) {
      MessageBox(hWndFrame, CREATEMAINERR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
      return (FALSE);
   }

   ShowWindow(hWndFrame, nCmdShow);
   UpdateWindow(hWndFrame);

   // load accelerators

   hAccel = LoadAccelerators(hAppInstance, APPACCELERATOR);

   // acquire and dispatch messages until a WM_QUIT message is received

   while (GetMessage(&msg, (HWND)NULL, 0, 0)) {

      // check for App accelerators

      if (TranslateAccelerator(hWndFrame, hAccel, &msg))
         continue;

      // check for MDI accelerators

      if (TranslateMDISysAccel(hWndMDIClient, &msg))
         continue;

      // each MDI Child has a modeless dialog in its client area
      // to provide tab controls. Check for Modeless Dialog msgs.

      for (hWndMDIChild = GetWindow(hWndMDIClient, GW_CHILD); hWndMDIChild; hWndMDIChild = GetWindow(hWndMDIChild, GW_HWNDNEXT)) {
         GetClassName(hWndMDIChild, szBuffer, MAXBUFLEN);
         if (strcmp(szBuffer, ODBCMDICLASS))
            continue;
         if (IsDialogMessage((HWND)GetWindowLong(hWndMDIChild, GWLAPP_HDLG), &msg)) {
            bDialogMessage = TRUE;
            break;
         }
      }

      if (bDialogMessage) {
         bDialogMessage = FALSE;
         continue;
      }

      // if the message does not need special processing, dispatch it

      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   // free memory used by ODBC Driver Manager interface

   FreeSQLEnvironment();

   // free Icon resources

   if (hMainIcon)
      DestroyIcon(hMainIcon);

   if (hMDIChildIcon)
      DestroyIcon(hMDIChildIcon);

   return (msg.wParam);
}

/*
    FUNCTION: MainWndProc(HWND hWnd, UINT   message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Windows Callback procedure to handle Window messages.
              Menu Commands and System Command messages are handled by
              this main window.
*/

long CALLBACK MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      static int  iToolY;         //remember Toolbar height for resize
      static int  iStatusY;       //remember Statusbar height for resize

     case WM_CREATE:
      {
         CLIENTCREATESTRUCT  ccs;        //MDIclient window structure
         HDC         hDC;        //Device Context handle
         SIZE            sizeBar;    //Size of a text bar
         RECT            rectCombo;      //Size of combo box

         // Create child windows
         //1. combobox to display connections - DSN, SQLHDBC
         //2. combobox to display SQLHSTMT on current SQLHDBC
         //3. Toolbar to put toolbuttons
         //4. Statusbat to display current action, date and time
         //5. MDI Client Window to process MDI children

         hWndCrsrList = CreateWindow(COMBOBOXCLASS, NULL,
                                     WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
                                     WS_VSCROLL | CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL,
                                     5, 2, 180, 150, hWnd,
                                     (HMENU)IDW_CRSRLIST, hAppInstance, NULL);

         hWndStmtList = CreateWindow(COMBOBOXCLASS, NULL,
                                     WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
                                     WS_VSCROLL | CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL,
                                     190, 2, 150, 150, hWnd,
                                     (HMENU)IDW_STMTLIST, hAppInstance, NULL);

         hWndToolbar = CreateWindow(ODBCTOOLCLASS, NULL,
                                    WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
                                    0, 0, 0, 0, hWnd, (HMENU)IDW_TOOLBAR, hAppInstance, NULL);

         hWndStatusbar = CreateWindow(ODBCSTATUSCLASS, NULL,
                                      WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
                                      0, 0, 0, 0, hWnd, (HMENU)IDW_STATUSBAR, hAppInstance, NULL);

         ccs.hWindowMenu = GetSubMenu(GetMenu(hWnd), WINDOWMENUPLACE);
         ccs.idFirstChild = IDM_MDICHILD;

         hWndMDIClient = CreateWindow(MDICLIENTCLASS, NULL,
                                      WS_CHILD | WS_CLIPSIBLINGS |
                                      WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
                                      0, 0, 0, 0, hWnd,
                                      (HMENU)IDW_MDICLIENT, hAppInstance, (LPSTR)&ccs);

         // check to see if any of the above window creation failed

         if (!hWndCrsrList || !hWndStmtList || !hWndToolbar || !hWndStatusbar || !hWndMDIClient) {
            MessageBox(hWnd, CREATEMAINERR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
            PostMessage(hWnd, WM_CLOSE, 0, 0);
            break;
         }
         // calculate proper text height for tool and status bars

         GetWindowRect(hWndStmtList, &rectCombo);
         iToolY = rectCombo.bottom - rectCombo.top + TOOLBARMARGINY;

         if (hDC = GetDC(hWndStatusbar)) {
            GetTextExtentPoint(hDC, ALPHABETS, strlen(ALPHABETS), &sizeBar);
            ReleaseDC(hWndStatusbar, hDC);
            iStatusY = sizeBar.cy + STATUSBARMARGINY;
         }
         else {
            iStatusY = 0;
            DestroyWindow(hWndStatusbar);
         }

         break;
      }

     case WM_GETMINMAXINFO:

      // limit minimum size of the main window

      ((MINMAXINFO FAR*)lParam)->ptMinTrackSize.x =
         max(MINWIDTH, rectStatusText.right-rectStatusText.left+iTimex+iDatex+14);
      ((MINMAXINFO FAR*)lParam)->ptMinTrackSize.y = MINHEIGHT;
      break;

     case WM_SIZE: // resize children
      {
         WORD    wWidth = LOWORD(lParam);    //width of rectangle
         WORD    wHeight = HIWORD(lParam);   //height of rectangle

         MoveWindow(hWndToolbar, 0, 0, wWidth, iToolY, TRUE);
         MoveWindow(hWndStatusbar, 0, wHeight-iStatusY, wWidth, iStatusY, TRUE);
         InvalidateRect(hWndStatusbar, NULL, TRUE);
         MoveWindow(hWndMDIClient, 0, iToolY, wWidth, wHeight-iStatusY-iToolY, TRUE);
         break;
      }

     case WM_SYSCOLORCHANGE: // inform 3D controls of color change

      //      Ctl3dColorChange();
      break;

     case WM_SYSCOMMAND: // close comboboxes if dropped down

      SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
      SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
      return (DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam));

     case WM_INITMENUPOPUP: // initialize popup menus
      {
         int iMenuId;    //Menu ID being processed
         int nItems;         //# of menu items
         int nPos;           //Menu Position

         //ignore the msg if it is for a system menu

         if (HIWORD(lParam))
            break;

         // Go through the menu items for current popup menu
         // and enable/disable menu item, if required

         nItems = GetMenuItemCount((HMENU)wParam);
         for (nPos = 0; nPos < nItems; nPos++)
            switch (iMenuId = GetMenuItemID((HMENU)wParam, nPos)) {
              case IDM_DISCONNECT:
              case IDM_NEW:

               EnableMenuItem((HMENU)wParam, iMenuId, MF_BYCOMMAND|
                              ((SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) > 0)
                               ? MF_ENABLED
                               : MF_GRAYED));
               break;

              case IDM_QUERY:
              case IDM_TILE:
              case IDM_CASCADE:
              case IDM_ICONS:
              case IDM_CLOSEALL:

               EnableMenuItem((HMENU)wParam, iMenuId, MF_BYCOMMAND|
                              (GetWindow(hWndMDIClient, GW_CHILD)
                               ? MF_ENABLED
                               : MF_GRAYED));
               break;

              default:

               break;
            }
         break;
      }

     case WM_MENUSELECT: // update status bar to reflect menu selection
      {
         int iMenuFlag;      //Check menu type
         HMENU   hMenu;              //Menu Handle
         char    szMenuName[MAXBUFLEN+1];//Menu Name

         // store Menuitem ID as a state value for text display

         wStatusText = GET_WM_MENUSELECT_CMD(wParam, lParam);

         // process popup menus ie non menuitem selections

         iMenuFlag = GET_WM_MENUSELECT_FLAGS(wParam, lParam);

         // if the selected menu is a system popup menu

         if (wStatusText && (iMenuFlag & MF_SYSMENU) && (iMenuFlag & MF_POPUP))
            wStatusText = IDM_POPUPAPPSYS;

         // else if the selected menu is a popup menu check menu names
         // OR check if it is a control popup menu of maximized MDI Child window

         else if (wStatusText && (iMenuFlag & MF_POPUP)) {
            hMenu = (HMENU)wStatusText;
            GetMenuString(hMenu, 0, szMenuName, MAXBUFLEN, MF_BYPOSITION);
            if (!strcmp(szMenuName, MENUITEMCONNECT))
               wStatusText = IDM_POPUPLOGIN;
            else if (!strcmp(szMenuName, MENUITEMQUERY))
               wStatusText = IDM_POPUPQUERY;
            else if (!strcmp(szMenuName, MENUITEMTILE))
               wStatusText = IDM_POPUPWINDOW;
            else if (!strcmp(szMenuName, MENUITEMAPPHELP))
               wStatusText = IDM_POPUPHELP;
            else if (GetMenuString(hMenu, SC_NEXTWINDOW, szMenuName, MAXBUFLEN, MF_BYCOMMAND)>0)
               wStatusText = IDM_POPUPMDISYS;
            else
               wStatusText = 0;
         }

         // invalidate status bar for repaint

         InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
         break;
      }

     case WM_COMMAND: // process menu commands

      switch (GET_WM_COMMAND_ID(wParam, lParam)) {

        case IDM_CONNECT: // bring up connect dialog & do connect processing

         DialogBox(hAppInstance, CONNECTDIALOG, hWnd,ConnectDlgProc);
         break;

        case IDM_DRIVERCONNECT: // let the driver do the dialogs

         DriverConnectDatabase(hWnd);
         break;

        case IDM_DISCONNECT: // bringup disconnect dlg and do disconnects

         DialogBox(hAppInstance, DISCONNECTDIALOG, hWnd,DisconnectDlgProc);
         break;

        case IDM_QUERY: // process execute query request

         ExecuteQuery();
         break;

        case IDM_EXIT: // process exit request

         SendMessage(hWndFrame, WM_CLOSE, 0, 0);
         break;

        case IDM_NEW: // create a new query window on current connect

         NewQueryWindow();
         break;

        case IDM_TILE: // let MDI Client tile the MDI children

         SendMessage(hWndMDIClient, WM_MDITILE, 0, 0);
         break;

        case IDM_CASCADE: // let MDI Client cascade MDI children

         SendMessage(hWndMDIClient, WM_MDICASCADE, 0, 0);
         break;

        case IDM_ICONS: // let MDI Client arrange iconic MDI children

         SendMessage(hWndMDIClient, WM_MDIICONARRANGE, 0, 0);
         break;

        case IDM_CLOSEALL: // Close all open windows and free hstmts
         {
            HWND    hWndTemp;   //temp window handle

            // hide MDI Client Windows to avoid repaints

            ShowWindow(hWndMDIClient,SW_HIDE);
            while (hWndTemp = GetWindow(hWndMDIClient, GW_CHILD))
               SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndTemp, 0);
            ShowWindow(hWndMDIClient, SW_SHOW);
            break;
         }

        case IDM_APPHELP: // bring up Samples Help file

         WinHelp(hWnd, SAMPLESHELPFILE, HELP_KEY,(DWORD)(LPTSTR)szKeyWord);
break;

        case IDM_ABOUT: // bringup About dialog

         DialogBox(hAppInstance, ABOUTDIALOG, hWnd, AboutDlgProc);
         break;

        case IDW_CRSRLIST: // change current cursor ?

         if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
            ChangeCurrentCursor(GET_WM_COMMAND_HWND(wParam, lParam));
         else if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_KILLFOCUS)
            PostMessage(GET_WM_COMMAND_HWND(wParam, lParam), CB_SHOWDROPDOWN,
                        (WPARAM)FALSE, 0);
         else
            DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
         break;

        case IDW_STMTLIST: // change current hstmt?

         if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
            ChangeCurrentQuery(GET_WM_COMMAND_HWND(wParam, lParam));
         else if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_KILLFOCUS)
            PostMessage(GET_WM_COMMAND_HWND(wParam, lParam), CB_SHOWDROPDOWN,
                        (WPARAM)FALSE, 0);
         else
            DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
         break;

        default:

         DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
         break;
      }
      break;

     case WM_CLOSE: //close all MDI windows, hdbcs & hstmts, else fail

      return (CloseDatabases()
              ? DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam)
              : FALSE);

     case WM_DESTROY:

      // Close Help File if open

      WinHelp(hWnd, SAMPLESHELPFILE, HELP_QUIT, 0);
      PostQuitMessage(0);
      break;

     default:

      return (DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam));
   }
   return (0);
}

/*
    FUNCTION: MDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Window Procedure for MDI Child windows
*/

long CALLBACK MDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      HWND    hWndTemp;   //temporary window handle
      DLGPROC lpDlgProc;  //procedure address for modeless dialog

     case WM_CREATE:

      // create child windows
      // 1. Modeless Dialog box in the background to process tabs
      // 2. Static Text to display prompt
      // 3. Edit Control to type SQL text
      // 4. List Box to display results
      // store dialog handle for future reference
      // set focus to edit control

      // if create failed due to low system resources

      if (!hWnd)
         break;

      hWndActiveChild = hWnd;
      hWndTemp = CreateDialog(hAppInstance, MDICHILDDIALOG, hWndActiveChild, MDIChildDlgProc);

      // check to see if the dialog was created?, if not destroy this window

      if (!hWndTemp) {
         return (-1);
      }

      SetWindowLong(hWnd, GWLAPP_HDLG, (LONG)hWndTemp);
      SetFocus(GetDlgItem(hWndTemp, IDTEXT_SQL));
      break;

     case WM_SIZE: {
        WORD    wWidth;         //New Width of MDI Child
        WORD    wHeight;        //New Height of MDI Child
        HDC     hDC;            //Device Context
        char    szBuffer[MAXBUFLEN+1];  //Static Control Text
        int nStrLen;        //Buffer Length
        SIZE    size;           //Screen size for text display

        // call default procedure first, to let MDI position the child & then move its children

        DefMDIChildProc(hWnd, message, wParam, lParam);

        // move child windows with proper screen size for text display

        wWidth = LOWORD(lParam);
        wHeight = HIWORD(lParam);
        hWndTemp = (HWND)GetWindowLong(hWnd, GWLAPP_HDLG);

        nStrLen = GetWindowText(GetDlgItem(hWndTemp, IDTEXT_PRMPT), szBuffer, MAXBUFLEN);
        hDC = GetDC(hWnd);
        GetTextExtentPoint(hDC, szBuffer, nStrLen, &size);
        ReleaseDC(hWnd, hDC);

        MoveWindow(hWndTemp, 0, 0, wWidth, wHeight, TRUE);
        MoveWindow(GetDlgItem(hWndTemp, IDTEXT_PRMPT), 0, 0, size.cx+2, size.cy+2, TRUE);
        MoveWindow(GetDlgItem(hWndTemp, IDTEXT_SQL), size.cx+3, 0, wWidth - (size.cx+2), size.cy+2, TRUE);
        MoveWindow(GetDlgItem(hWndTemp, IDLIST_RSLT), 0, size.cy+3, wWidth, wHeight - (size.cy+2), TRUE);
        break;
     }

     case WM_MENUSELECT: // update status bar to reflect menu selection
      {
         int iMenuFlag;      //Check menu type

         // store the Menu Item Id as a state value for text display

         wStatusText = GET_WM_MENUSELECT_CMD(wParam, lParam);

         // if none of the menuitems was selected, check if
         // the control popup menu is selected.

         iMenuFlag = GET_WM_MENUSELECT_FLAGS(wParam, lParam);

         // if the selected menu is a system popup menu

         if (wStatusText && (iMenuFlag & MF_SYSMENU) && (iMenuFlag & MF_POPUP))
            wStatusText = IDM_POPUPMDISYS;

         // invalidate status bar for repaint

         InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
         break;
      }

     case WM_MDIACTIVATE:

      // check if the display of comboboxes require a change

      if (GET_WM_MDIACTIVATE_FACTIVATE(hWnd, wParam, lParam) &&
          (hWndActiveChild) && (hWndActiveChild != hWnd)) {
         hWndActiveChild = hWnd;
         DisplayNewCrsrAndStmt();
      }
      break;

     case WM_MOUSEACTIVATE:

      // current window has changed, update comboboxes.

      hWndActiveChild = hWnd;
      DisplayNewCrsrAndStmt();
      break;

     case WM_SETFOCUS:

      // pass on the focus to the edit box for user to type in SQL

      SetFocus(GetDlgItem((HWND)GetWindowLong(hWnd, GWLAPP_HDLG), IDTEXT_SQL));
      break;

     case WM_DESTROY:

      // check if the window was being destroyed while creation failed

      if (!hWnd)
         break;

      // close the window and free instance thunk for modeless dialog

      CloseQueryWindow(hWnd);
      lpDlgProc = (DLGPROC)GetWindowLong((HWND)GetWindowLong(hWnd, GWLAPP_HDLG), DWL_DLGPROC);
      DestroyWindow((HWND)GetWindowLong(hWnd, GWLAPP_HDLG));
      FreeProcInstance(lpDlgProc);
      if (hWnd == hWndActiveChild)
         hWndActiveChild = (HWND)NULL;
      break;

     default:

      return (DefMDIChildProc(hWnd, message, wParam, lParam));
   }
   return (0);
}

/*
    FUNCTION: ToolbarProc(HWND hWnd, UINT   message, WPARAM wParam, LPARAM lParam)
    COMMENTS: callback window procedure for toolbar window.
          Handle pain and mouse messages to paint the toolbar and
          provide default button behaviour for toolbar buttons.
*/

long CALLBACK ToolbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      static HBITMAP  hbmpNewQuery;           //btn1 bitmap handle
      static HBITMAP  hbmpRunQuery;           //btn2 bitmap handle
      static RECT stNewQuery;         //btn1 rectangle
      static RECT stRunQuery;                     //btn2 rectangle
      POINT       stMousePosition;        //current mouse pos
      BOOL        bButtonPosition;        //mouse pos flag
      static BOOL bNewQueryBtnDown = FALSE;   //was btn1 down before?
      static BOOL bRunQueryBtnDown = FALSE;   //was btn2 down before?
      static int  nLastButtonDown = 0;        //Which btn was down before?

     case WM_CREATE:

      // load bitmaps for buttons
      // initialize static rectangles for button positions on toolbar
      // initialize state variable for status text display

      hbmpNewQuery = LoadBitmap(hAppInstance, BMP_NEWQUERY);
      hbmpRunQuery = LoadBitmap(hAppInstance, BMP_RUNQUERY);

      stNewQuery.left   = BTTNX;
      stNewQuery.right  = BTTNX+BTTNWIDTH+1;
      stNewQuery.top    = BTTNY;
      stNewQuery.bottom = BTTNY+BTTNHEIGHT+1;

      stRunQuery.left   = BTTNX+BTTNWIDTH+BTTNMARGIN;
      stRunQuery.right  = BTTNX+BTTNWIDTH+BTTNMARGIN+BTTNWIDTH+1;
      stRunQuery.top    = BTTNY;
      stRunQuery.bottom = BTTNY+BTTNHEIGHT+1;

      wStatusText = 0;
      break;

     case WM_DESTROY:

      // delete bitmap handles

      if (hbmpNewQuery)
         DeleteObject(hbmpNewQuery);
      if (hbmpRunQuery)
         DeleteObject(hbmpRunQuery);
      break;

     case WM_LBUTTONDOWN:

      // Check if the mouse key lies on any one of the buttons
      // if so, set state variable to reflect that button and
      // invalidate proper regions on tool & status bars for update.
      // set capture on mouse movements till the mouse key is
      // released.

      stMousePosition.x = LOWORD(lParam);
      stMousePosition.y = HIWORD(lParam);

      if (PtInRect(&stNewQuery, stMousePosition)) {
         bNewQueryBtnDown = TRUE;
         wStatusText = nLastButtonDown = IDM_NEW;
         SetCapture(hWnd);
         InvalidateRect(hWnd, &stNewQuery, TRUE);
         InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
      }
      else if (PtInRect(&stRunQuery, stMousePosition)) {
         bRunQueryBtnDown = TRUE;
         wStatusText = nLastButtonDown = IDM_QUERY;
         SetCapture(hWnd);
         InvalidateRect(hWnd, &stRunQuery, TRUE);
         InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
      }
      break;

     case WM_LBUTTONUP:

      // check if the mouse movements from key down movements
      // were captured, if so process the key release state.
      // if the key was released in the same button where it
      // was pressed, it is equivalent to a button click.

      if (hWnd != GetCapture())
         break;

      stMousePosition.x = LOWORD(lParam);
      stMousePosition.y = HIWORD(lParam);

      if (bNewQueryBtnDown && PtInRect(&stNewQuery, stMousePosition)) {
         bNewQueryBtnDown = FALSE;
         nLastButtonDown = 0;
         InvalidateRect(hWnd, &stNewQuery, TRUE);
         PostMessage(hWndFrame, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_NEW, 0, 0));
         SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
         SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
      }
      else if (bRunQueryBtnDown && PtInRect(&stRunQuery, stMousePosition)) {
         bRunQueryBtnDown = FALSE;
         nLastButtonDown = 0;
         InvalidateRect(hWnd, &stRunQuery, TRUE);
         PostMessage(hWndFrame, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_QUERY, 0, 0));
         SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
         SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
      }

      ReleaseCapture();
      wStatusText = 0;
      InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
      break;

     case WM_MOUSEMOVE:

      // process mouse movement only if the mouse key was pressed
      // down and its movements were being captured. If the mouse
      // moves outside of the currently depressed button, it needs
      // to be drawn again with normal state.

      if (hWnd != GetCapture())
         break;

      stMousePosition.x = LOWORD(lParam);
      stMousePosition.y = HIWORD(lParam);

      if (nLastButtonDown == IDM_NEW) {
         bButtonPosition = PtInRect(&stNewQuery, stMousePosition);
         if (bNewQueryBtnDown != bButtonPosition) {
            bNewQueryBtnDown = bButtonPosition;
            InvalidateRect(hWnd, &stNewQuery, TRUE);
         }
      }
      else if (nLastButtonDown == IDM_QUERY) {
         bButtonPosition = PtInRect(&stRunQuery, stMousePosition);
         if (bRunQueryBtnDown != bButtonPosition) {
            bRunQueryBtnDown = bButtonPosition;
            InvalidateRect(hWnd, &stRunQuery, TRUE);
         }
      }
      break;

     case WM_PAINT:
      {
         PAINTSTRUCT ps;     //paint structure
         RECT        rect;       //rectangle for tool bar
         HDC     hDC;        //device context handle
         int     iWidth;     //tool bar width
         int     iHeight;    //tool bar height
         HPEN        hLtGrayPen; //buttonface color pen
         HPEN        hGrayPen;   //buttonshadow color pen
         int     btnx;       //button x coordinate position

         if (!(hDC = BeginPaint(hWnd, &ps)))
            break;

         GetClientRect(hWnd, &rect);
         iWidth = rect.right;
         iHeight = rect.bottom;

         hLtGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNFACE));
         hGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW));

         // draw background and border

         SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
         SelectObject(hDC, hLtGrayPen);
         Rectangle(hDC, 0, 0, iWidth, iHeight);

         SelectObject(hDC, GetStockObject(BLACK_PEN));
         MoveToEx(hDC, 0, iHeight-1, NULL);
         LineTo(hDC, iWidth, iHeight-1);

         SelectObject(hDC, GetStockObject(WHITE_PEN));
         MoveToEx(hDC, 0, 0, NULL);
         LineTo(hDC, iWidth, 0);

         // draw tool bar buttons (new query, run query)

// check state variables to draw proper button state

         btnx = BTTNX;
         SelectObject(hDC, GetStockObject(BLACK_PEN));
         DRAWBTTNRECT(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
         DrawBitmap(hDC, (bNewQueryBtnDown?btnx+3:btnx+2), (bNewQueryBtnDown?BTTNY+3:BTTNY+2), hbmpNewQuery);
         SelectObject(hDC, (bNewQueryBtnDown ? hGrayPen : GetStockObject(WHITE_PEN)));
         DRAWBTTNLIFT1(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
         SelectObject(hDC, (bNewQueryBtnDown ? hLtGrayPen : hGrayPen));
         DRAWBTTNLIFT2(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);

         btnx += BTTNWIDTH+BTTNMARGIN;
         SelectObject(hDC, GetStockObject(BLACK_PEN));
         DRAWBTTNRECT(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
         DrawBitmap(hDC, (bRunQueryBtnDown?btnx+3:btnx+2), (bRunQueryBtnDown?BTTNY+3:BTTNY+2), hbmpRunQuery);
         SelectObject(hDC, (bRunQueryBtnDown ? hGrayPen : GetStockObject(WHITE_PEN)));
         DRAWBTTNLIFT1(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
         SelectObject(hDC, (bRunQueryBtnDown ? hLtGrayPen : hGrayPen));
         DRAWBTTNLIFT2(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);

         SelectObject(hDC, GetStockObject(WHITE_PEN));
         EndPaint(hWnd, &ps);

         // delete create objects

         if (hLtGrayPen)
            DeleteObject(hLtGrayPen);
         if (hGrayPen)
            DeleteObject(hGrayPen);
         break;
      }

     default:

      return (DefWindowProc(hWnd, message, wParam, lParam));
   }
   return (0);
}

/*
    FUNCTION: StatusbarProc(HWND hWnd, UINT   message, WPARAM wParam, LPARAM lParam)
    COMMENTS: callback window procedure for status bar.
          process paint messages and timer messages to update current
          state, date and time
*/

long CALLBACK StatusbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      static RECT DateTimeRect; // remember for frequent updates

     case WM_CREATE:
      {
         HDC hDC;        // device context
         SIZE    sizeText;       // size of status text box
         SIZE    sizeTime;       // size of time display box
         SIZE    sizeDate;       // size of date display box

         // start a timer for periodic updates to date and time display
         // find out width of status text, date and time display boxes

         SetTimer(hWnd,  (UINT)IDT_STATUSTIMER, (UINT)TIMERDELAY, NULL);

         iTimex = iDatex = 0;
         rectStatusText.left = 2;
         rectStatusText.top = 3;

         if (hDC = GetDC(hWnd)) {
            GetTextExtentPoint(hDC, STATUSNEW, strlen(STATUSNEW), &sizeText);
            GetTextExtentPoint(hDC, TIMETEXT, strlen(TIMETEXT), &sizeTime);
            GetTextExtentPoint(hDC, DATETEXT, strlen(DATETEXT), &sizeDate);
            ReleaseDC(hWnd, hDC);
            rectStatusText.right = sizeText.cx + rectStatusText.left;
            iTimex = sizeTime.cx;
            iDatex = sizeDate.cx;
         }
         break;
      }

     case WM_TIMER:

      // invalidate only the date&time area for update

      InvalidateRect(hWnd, &DateTimeRect, TRUE);
      break;

     case WM_PAINT:
      {
         HDC     hDC;            //device context
         PAINTSTRUCT ps;         //paint structure
         RECT        rect;                   //status bar rect
         int     iWidth;                 //status bar width
         int     iHeight;                //status bar height
         HPEN        hLtGrayPen;             //btnface color pen
         HPEN        hGrayPen;               //btnshadow color pen
         char        szText[MAXBUFLEN];      //text buffer for display
         time_t      tCurrentTime;           //current date&time
         struct tm   stTime;                 //date&time structure

         if (!(hDC = BeginPaint(hWnd, &ps)))
            break;

         GetClientRect(hWnd, &rect);
         iWidth = rect.right;
         iHeight = rect.bottom;
         rectStatusText.bottom = iHeight-2;

         hLtGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNFACE));
         hGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW));

         // paint background and border

         SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
         SelectObject(hDC, hLtGrayPen);
         Rectangle(hDC, 0, 0, iWidth, iHeight);

         SelectObject(hDC, GetStockObject(BLACK_PEN));
         MoveToEx(hDC, 0, 0, NULL);
         LineTo(hDC, iWidth, 0);
         SelectObject(hDC, GetStockObject(WHITE_PEN));
         MoveToEx(hDC, 0, 1, NULL);
         LineTo(hDC, iWidth, 1);

         // draw text boxes for status, time and date display

         SelectObject(hDC, hGrayPen);
         MoveToEx(hDC, rectStatusText.left, rectStatusText.bottom, NULL);
         LineTo(hDC, rectStatusText.left, rectStatusText.top);
         LineTo(hDC, rectStatusText.right, rectStatusText.top);
         SelectObject(hDC, GetStockObject(WHITE_PEN));
         LineTo(hDC, rectStatusText.right, rectStatusText.bottom);
         LineTo(hDC, rectStatusText.left, rectStatusText.bottom);

         SelectObject(hDC, hGrayPen);
         MoveToEx(hDC, iWidth-2, 3, NULL);
         LineTo(hDC, iWidth-iDatex-2, 3);
         LineTo(hDC, iWidth-iDatex-2, iHeight-2);
         SelectObject(hDC, GetStockObject(WHITE_PEN));
         LineTo(hDC, iWidth-2, iHeight-2);
         LineTo(hDC, iWidth-2, 3);

         SelectObject(hDC, hGrayPen);
         MoveToEx(hDC, iWidth-iDatex-6, 3, NULL);
         LineTo(hDC, iWidth-iTimex-iDatex-6, 3);
         LineTo(hDC, iWidth-iTimex-iDatex-6, iHeight-2);
         SelectObject(hDC, GetStockObject(WHITE_PEN));
         LineTo(hDC, iWidth-iDatex-6, iHeight-2);
         LineTo(hDC, iWidth-iDatex-6, 3);

         // draw status text in the display box based on current
         // value of wStatusText global flag

         SetBkMode(hDC, TRANSPARENT);
         SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));

         switch (wStatusText) {
           case IDM_POPUPAPPSYS:

            strcpy(szText, STATUSPOPUPAPPSYS);
            break;

           case IDM_POPUPMDISYS:

            strcpy(szText, STATUSPOPUPMDISYS);
            break;

           case SC_RESTORE:

            strcpy(szText, STATUSRESTORE);
            break;

           case SC_MOVE:

            strcpy(szText, STATUSMOVE);
            break;

           case SC_SIZE:

            strcpy(szText, STATUSSIZE);
            break;

           case SC_MINIMIZE:

            strcpy(szText, STATUSMINIMIZE);
            break;

           case SC_MAXIMIZE:

            strcpy(szText, STATUSMAXIMIZE);
            break;

           case SC_CLOSE:

            strcpy(szText, STATUSCLOSE);
            break;

           case SC_NEXTWINDOW:

            strcpy(szText, STATUSNEXTWINDOW);
            break;

           case SC_PREVWINDOW:

            strcpy(szText, STATUSPREVWINDOW);
            break;

           case SC_TASKLIST:

            strcpy(szText, STATUSTASKLIST);
            break;

           case IDM_POPUPLOGIN:

            strcpy(szText, STATUSPOPUPLOGIN);
            break;

           case IDM_CONNECT:

            strcpy(szText, STATUSCONNECT);
            break;

           case IDM_DRIVERCONNECT:

            strcpy(szText, STATUSDRIVERCONNECT);
            break;

           case IDM_DISCONNECT:

            strcpy(szText, STATUSDISCONNECT);
            break;

           case IDM_EXIT:

            strcpy(szText, STATUSEXIT);
            break;

           case IDM_POPUPQUERY:

            strcpy(szText, STATUSPOPUPQUERY);
            break;

           case IDM_QUERY:

            strcpy(szText, STATUSQUERY);
            break;

           case IDM_NEW:

            strcpy(szText, STATUSNEW);
            break;

           case IDM_POPUPWINDOW:

            strcpy(szText, STATUSPOPUPWINDOW);
            break;

           case IDM_TILE:

            strcpy(szText, STATUSTILE);
            break;

           case IDM_CASCADE:

            strcpy(szText, STATUSCASCADE);
            break;

           case IDM_ICONS:

            strcpy(szText, STATUSICONS);
            break;

           case IDM_CLOSEALL:

            strcpy(szText, STATUSCLOSEALL);
            break;

           case IDM_POPUPHELP:

            strcpy(szText, STATUSPOPUPHELP);
            break;

           case IDM_APPHELP:

            strcpy(szText, STATUSAPPHELP);
            break;

           case IDM_ABOUT:

            strcpy(szText, STATUSABOUT);
            break;

           default:

            if (wStatusText >= IDM_MDICHILD)
               sprintf(szText, STATUSMDICHILD, wStatusText-IDM_MDICHILD+1);
            else
               strcpy(szText, STATUSDEFAULT);
            break;
         }

         DrawText(hDC, szText, strlen(szText), &rectStatusText, DT_LEFT);

         // get current date and time and display time in time box

         time(&tCurrentTime);
         stTime = *localtime(&tCurrentTime);
         strftime(szText, MAXBUFLEN, TIMEFORMAT, &stTime);
         rect.top = rectStatusText.top;
         rect.bottom = rectStatusText.bottom;
         rect.left  = iWidth-iTimex-iDatex-6;
         rect.right = iWidth-iDatex-6;
         DrawText(hDC, szText, strlen(szText), &rect, DT_LEFT);

         // display date in date box

         strftime(szText, MAXBUFLEN, DATEFORMAT, &stTime);
         rect.left  = iWidth-iDatex-2;
         rect.right = iWidth-2;
         DrawText(hDC, szText, strlen(szText), &rect, DT_LEFT);

         // remember the date&time rectangle to minimize painting

         DateTimeRect.left   = iWidth-iTimex-iDatex-6;
         DateTimeRect.right  = iWidth-2;
         DateTimeRect.top    = rect.top;
         DateTimeRect.bottom = rect.bottom;

         EndPaint(hWnd, &ps);

         // delete created objects

         if (hLtGrayPen)
            DeleteObject(hLtGrayPen);
         if (hGrayPen)
            DeleteObject(hGrayPen);
         break;
      }

     default:

      return (DefWindowProc(hWnd, message, wParam, lParam));
      break;
   }
   return (0);
}

/*
    FUNCTION: ConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Callback dialog box procedure for connect menu command
          displays a list of available data sources, asks for user
          name and password to pass default connection parameters
          for a data source connection
*/

BOOL CALLBACK ConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      HCURSOR hOldCursor; // Default Cursor Shape

     case WM_INITDIALOG:

      // display list of available data sources

      hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));
      DisplayDatabases(GetDlgItem(hWnd, IDCOMBO_DATASOURCE));
      SetCursor(hOldCursor);
      break;

     case WM_COMMAND:

      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK: // make a connection using the supplied values

         hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));

         // check if a DSN was provided for connection

         if (SendDlgItemMessage(hWnd, IDCOMBO_DATASOURCE, WM_GETTEXTLENGTH, 0, 0))
            EndDialog(hWnd, ConnectDatabase(hWnd));
         else
            MessageBox(hWnd, NODSNERR, MOREINFO, MB_OK|MB_ICONHAND);

         SetCursor(hOldCursor);
         break;

        case IDCANCEL:

         EndDialog(hWnd, FALSE);
         break;

        default:

         return (FALSE);
         break;
      }
      break;

     default:

      return (FALSE);
      break;
   }
   return (TRUE);
}

/*
    FUNCTION: DisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Callback dialog box procedure for disconnect dialog.
          provides a list of available SQLHDBCs and a list of SQLHSTMTs
          for currently selected SQLHDBC. Allows closure of all SQLHDBCs
          and SQLHSTMTs one by one or in groups.
*/

BOOL CALLBACK DisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message) {
      static HWND hListhdbc;  //listbox that displays hdbc(s)
      static HWND hListstmt;      //listbox that displays hstmt(s)
      static HWND hPushOk;        //pushbutton to free hdbc
      static HWND hPushClose;     //pushbutton to free hstmt
      static HWND hPushCancel;    //pushbutton to close dialog

     case WM_INITDIALOG:

      // store handles for future reference

      hListhdbc = GetDlgItem(hWnd, IDLIST_HDBC);
      hListstmt = GetDlgItem(hWnd, IDLIST_STMT);
      hPushOk = GetDlgItem(hWnd, IDDISCONNECT);
      hPushCancel = GetDlgItem(hWnd, IDCANCEL);
      hPushClose = GetDlgItem(hWnd, IDCLOSE_ACTVTY);

      // display connected database handles and statements

      DisplayConnections(hListhdbc);
      DisplayQueries(hListstmt, hListhdbc, 0);

      // enable or disable pushbuttons & listboxes to match available hdbc & hstmt

      if (SendMessage(hListhdbc, LB_GETCOUNT, 0, 0)>0) {
         EnableWindow(hPushOk, TRUE);
         if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0) {
            EnableWindow(hPushClose, TRUE);
            SetFocus(hPushClose);
            SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
         }
         else {
            EnableWindow(hListstmt, FALSE);
            EnableWindow(hPushClose, FALSE);
            SetFocus(hPushOk);
            SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
         }
      }
      else {
         EnableWindow(hListhdbc, FALSE);
         EnableWindow(hListstmt, FALSE);
         EnableWindow(hPushOk, FALSE);
         SetFocus(hPushCancel);
         SendMessage(hPushCancel, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
      }

      // return FALSE to prevent default focus.

      return (FALSE);

     case WM_COMMAND:

      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDDISCONNECT:

         // Free current hdbc, display available hdbc(s)

         FreeConnect(hListhdbc);
         SendMessage(hListstmt, LB_RESETCONTENT, 0, 0);
         DisplayConnections(hListhdbc);

         // update displayed hstmt(s) for current hdbc
         // enable or disable pushbuttons to match available
         // hdbc(s) and hstmt(s) for closure

         if (SendMessage(hListhdbc, LB_GETCOUNT, 0, 0) > 0) {
            SendMessage(hListhdbc, LB_SETCURSEL, 0, 0);
            EnableWindow(hListstmt, TRUE);
            DisplayQueries(hListstmt, hListhdbc, 0);
            if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0) {
               EnableWindow(hPushClose, TRUE);
               SetFocus(hPushClose);
               SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
            }
            else {
               EnableWindow(hListstmt, FALSE);
               EnableWindow(hPushClose, FALSE);
               SetFocus(hPushOk);
               SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
            }
         }
         else {
            EnableWindow(hListhdbc, FALSE);
            EnableWindow(hPushOk, FALSE);
            EnableWindow(hPushClose, FALSE);
            SetFocus(hPushCancel);
            SendMessage(hPushCancel, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
         }
         break;

        case IDCANCEL:

         // close dialog

         EndDialog(hWnd, FALSE);
         break;

        case IDCLOSE_ACTVTY:
         {
            int nIndex; // counter to search for selected hstmt(s)

            // go through all displayed hstmt(s) and free all highlighted ones

            for (nIndex = (int)SendMessage(hListstmt, LB_GETCOUNT, 0, 0)-1;
                 nIndex >= 0; nIndex--)
               if (SendMessage(hListstmt, LB_GETSEL, nIndex, 0))
                  FreeQuery(hListstmt, hListhdbc, nIndex);

            // reset both hdbc(s) and hstmt(s) display

            nIndex = (int)SendMessage(hListhdbc, LB_GETCURSEL, 0, 0);
            DisplayConnections(hListhdbc);
            SendMessage(hListhdbc, LB_SETCURSEL, nIndex, 0);
            DisplayQueries(hListstmt, hListhdbc, nIndex);

            // enable or disable pushbuttons to match available
            // hdbc(s) and hstmt(s) for closure

            if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0) {
               EnableWindow(hPushClose, TRUE);
               SetFocus(hPushClose);
               SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
            }
            else {
               EnableWindow(hListstmt, FALSE);
               EnableWindow(hPushClose, FALSE);
               SetFocus(hPushOk);
               SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
            }
            break;
         }

        case IDLIST_HDBC:

         // If the current selection in hdbc(s) has changed
         // update the list of hstmt(s) to match the new hdbc

         if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_SELCHANGE)
            DisplayQueries(hListstmt, hListhdbc,
                           (UINT)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 0, 0));

         // Enable or disable hstmt listbox and close pushbutton accordingly

         EnableWindow(hListstmt, (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0));
         EnableWindow(hPushClose, (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0));
         break;

        default:

         return (FALSE);
      }
      break;

     default:

      return (FALSE);
   }

   return (TRUE);
}

/*
    FUNCTION: AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Callback dialog box procedure for About dialog box
          displays the about information and closes upon selection of
          ok button
*/

BOOL CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   if (message == WM_COMMAND) {
      EndDialog(hWnd, TRUE);
      return (TRUE);
   }
   else
      return (FALSE);
}

/*
    FUNCTION: MDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    COMMENTS: Callback dialog box procedure for modeless child dialog box
          in each MDI Child Window. This dialog box simply processes
          the tab messages (by default) to allow switching from edit
          control (SQL Text) to list box control (Query results).
*/

BOOL CALLBACK MDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   return (FALSE);
}

/*
    FUNCTION: DrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap
    COMMENTS: Draws a bitmap on given Device context with given bitmap
          handle at given location
*/

VOID FAR PASCAL DrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap)
{
   HDC hMemDC;     // Device Context in Memory
   POINT   stPoint;        // point structure for conversion from device to logical units
   BITMAP  stBitmap;
   HGDIOBJ hObject;

   // create a compatible device context in memory and select the bitmap
   // in to it.

   if (!(hMemDC = CreateCompatibleDC(hDC))) return;

   hObject = SelectObject(hMemDC, hBitmap);
   SetMapMode(hMemDC, GetMapMode(hDC));

   // Get bitmap size and convert it to logical units from device units.

   GetObject(hBitmap, sizeof(BITMAP), &stBitmap);
   stPoint.x = stBitmap.bmWidth;
   stPoint.y = stBitmap.bmHeight;
   DPtoLP(hDC, &stPoint, 1);

   // bit block transfer the bitmap from memory device context to given
   // device context at specified location

   BitBlt(hDC, iLeft, iTop, stPoint.x, stPoint.y, hMemDC, 0, 0, SRCCOPY);

   // select original object in the memory device context and destroy it

   SelectObject(hMemDC, hObject);
   DeleteDC(hMemDC);
}

/********************************************* END OF FILE **************************************************/


QUERY.C

/*
    PROGRAM: QURYDEMO
    ========

    PURPOSE:
    ========
    demonstrates a simple MDI (Multiple Document Interface)application
    that allows a user to simultaneously connect to multiple
    hetrogeneous databases and perform SQL queries to get results.

    FUNCTIONS:
    ==========
    InitSQLEnvironment() - Initialize ODBC library
    DisplayDatabases() - Display available Data Sources
    ConnectDatabase() - Connect to a specific data source
    DriverConnectDatabase() - Connect through driver specific dialogs
    DisplayConnections() - Display List of made connections
    DisplayQueries() - Display list of Query Handles (hstmts)
    NewQueryWindow() - Open a new hstmt and update displays
    ChangeCurrentCursor() - Change current cursor display
    ChangeCurrentQuery() - Change current query display
    DisplayNewCrsrAndStmt() - update cusor & query displays
    FreeConnect() - free a connection
    FreeQuery() - free a query handle
    CloseQueryWindow() - close a query window
    ExecuteQuery() - execute a user specified query
    CloseDatabases() - check if all connections are closed
    FreeSQLEnvironment() - free ODBC library
    DisplayError() - displays SQL Error and Warnings

    COMMENTS:
    =========
    Created by Microsoft Corporation.

    The application uses MDI Child Window Titles to store values of
    connected SQLHDBCs and SQLHSTMTs. These values are also stored in the
    comboboxes that are displayed on the toolbar.

*/

#include <stdio.h>
#include <string.h>

#include <windows.h>

#include "sql.h"
#include "sqlext.h"
#include "qurydemo.h"

/*
    FUNCTION: InitSQLEnvironment()
    COMMENTS: Allocate an environment handle for ODBC function calls.
*/

BOOL FAR PASCAL InitSQLEnvironment()
{
   // reset child window count
   nChildCount = 0;

   // Allocate an Environment Handle
   if (SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&henv) != SQL_SUCCESS)
      return FALSE;

   return (SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                         (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER) == SQL_SUCCESS);
}

/*
    FUNCTION: DisplayDatabases(HWND hWnd)
    COMMENTS: Display a list of available data sources. Use
          SQLDataSources function call.
*/

void FAR PASCAL DisplayDatabases(HWND hWnd)
{
   SWORD     nDirection;                             //fetch direction
   SWORD     nDataSourceNameLength;                  //DSN str length
   SWORD     nSourceDescriptionLength;               //Driver Description str length
   char      szDataSourceName[MAXBUFLEN+1];          //DSN string
   char      szSourceDescription[MAXBUFLEN+1];       //Driver Description string
   SQLRETURN nResult;                                //Return Code

   //Call SQLDataSources, first with SQL_FETCH_FIRST and from then on
   //with SQL_FETCH_NEXT until it returns SQL_NO_DATA. Add
   //all returned DSNs in the provided combo box for display.
   SendMessage(hWnd, CB_RESETCONTENT, 0, 0);
   for (nDirection = SQL_FETCH_FIRST;
        (nResult = SQLDataSources(henv, nDirection, szDataSourceName, MAXBUFLEN,
                                  &nDataSourceNameLength, szSourceDescription, MAXBUFLEN, &nSourceDescriptionLength)) !=
        SQL_NO_DATA &&
        nResult != SQL_ERROR;
        nDirection = SQL_FETCH_NEXT
        )
      SendMessage(hWnd, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szDataSourceName);
   SendMessage(hWnd, CB_SETCURSEL, 0, 0);
}

/*
    FUNCTION: ConnectDatabase(HWND hWnd)
    COMMENTS: Connect to a DSN.
*/

BOOL FAR PASCAL ConnectDatabase(HWND hWnd)
{
   char      szBuffer[MAXBUFLEN+1];  // buffer to display successful connection on hdbc combo-box
   char      szDBName[MAXBUFLEN+1];  // DSN sting
   char      szUserName[MAXBUFLEN+1];// User name
   char      szPassword[MAXBUFLEN+1];// Password
   SQLHDBC   hdbc;                   // hdbc
   SQLRETURN nResult;                // Result code

   // check if enough windows are already open, refuse connection

   if (nChildCount >= MAXCHILDWNDS) {
      MessageBox(hWndFrame, MAXCHILDEXCEEDED, MAXCHLDERR, MB_OK | MB_ICONHAND);
      return (FALSE);
   }

   // retrieve DSN, UID and PWD values from the connect dialog box

   GetDlgItemText(hWnd, IDCOMBO_DATASOURCE, szDBName, MAXBUFLEN);
   GetDlgItemText(hWnd, IDTEXT_USERNAME, szUserName, MAXBUFLEN);
   GetDlgItemText(hWnd, IDTEXT_PASSWORD, szPassword, MAXBUFLEN);

   // Allocate a new SQLHDBC. If successful then call SQLConnect using the
   // allocated hdbc and supplied connection information.
   if ((nResult = SQLAllocHandle(SQL_HANDLE_DBC,henv, (SQLHDBC FAR *)&hdbc)) != SQL_SUCCESS) {
      DisplayError(nResult, hWnd, SQL_HANDLE_ENV, henv);
      return (FALSE);
   }

   nResult = SQLConnect(hdbc,(LPSTR)szDBName,SQL_NTS,(LPSTR)szUserName,SQL_NTS,(LPSTR)szPassword,SQL_NTS);

   // if failed to connect, free the allocated hdbc before return
   if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
      DisplayError(nResult, hWnd, SQL_HANDLE_DBC, hdbc);
      SQLFreeHandle(SQL_HANDLE_DBC,hdbc);
      return (FALSE);
   }

   // display any connection information if driver returns SQL_SUCCESS_WITH_INFO
   if (nResult == SQL_SUCCESS_WITH_INFO)
      DisplayError(nResult, hWnd, SQL_HANDLE_DBC, hdbc);

   // update the hdbc(s) combo-box and create a new hstmt and its
   // associated window.
   wsprintf(szBuffer, DSN_HDBC_FORMAT, (LPSTR)szDBName, hdbc);
   nResult = (UINT)SendMessage(hWndCrsrList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndCrsrList, CB_SETCURSEL, (WPARAM)nResult, 0);
   SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   ChangeCurrentCursor(hWndCrsrList);
   NewQueryWindow();
   return (TRUE);
}

/*
    FUNCTION: DriverConnectDatabase(HWND hWnd)
    COMMENTS: Connect to a DSN using SQLDriverConnect function call. It
          allows driver manager and drivers to interact with the user
          directly to prompt for connection information.
*/

BOOL FAR PASCAL DriverConnectDatabase(HWND hWnd)
{
   char      szBuffer[MAXBUFLEN+1];  //display successful connection info on hdbc(s) combo-box
   char      szDBName[MAXBUFLEN+1];  //DSN string
   SWORD     swStrLen;               //String length
   SQLHDBC   hdbc;                   //hdbc
   SQLRETURN nResult;                //result code

   // Allocate a new Connection Handle.
   if ((nResult = SQLAllocHandle(SQL_HANDLE_DBC,henv, (SQLHDBC FAR *)&hdbc)) != SQL_SUCCESS) {
      DisplayError(nResult, hWnd, SQL_HANDLE_ENV, henv);
      return (FALSE);
   }

   // Call SQLDriverConnect to make a connection request. Provide SQL_DRIVER_COMPLETE_REQUIRED
   // flag sothat driver manager and driver can ask for connection information
   // If failed to connect, free the allocated hdbc in previous step.
   nResult = SQLDriverConnect(hdbc, hWnd, "", 0, szBuffer,  MAXBUFLEN, &swStrLen, SQL_DRIVER_COMPLETE_REQUIRED);

   if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
      SQLFreeHandle(SQL_HANDLE_DBC,hdbc);
      return (FALSE);
   }

   // If driver returns additional information with SQL_SUCCESS_WITH_INFO
   // flag, display the information
   if (nResult == SQL_SUCCESS_WITH_INFO)
      DisplayError(nResult, hWnd, SQL_HANDLE_DBC, hdbc);

   // Call SQLGetInfo to find out the DSN name for the above connection.
   // Note, application should not depend upon the DSN name being present
   // in the returned connection string.
   strcpy(szDBName, DEFDBNAME); // initialize the string value

   SQLGetInfo(hdbc, SQL_DATA_SOURCE_NAME, (PTR)szDBName, MAXBUFLEN+1, &swStrLen);
if( !*szDBName)
strcpy(szDBName,"nameless");

   // Display the new connection information in the hdbc(s) combo-box.
   // Create a new hstmt and its associated window.
   wsprintf(szBuffer, DSN_HDBC_FORMAT, (LPSTR)szDBName, hdbc);
   nResult = (UINT)SendMessage(hWndCrsrList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndCrsrList, CB_SETCURSEL, (WPARAM)nResult, 0);
   SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   ChangeCurrentCursor(hWndCrsrList);
   NewQueryWindow();
   return (TRUE);
}

/*
    FUNCTION: DisplayConnections(HWND hWndhdbc)
    COMMENTS: Display list of available hdbc(s) in the given list box.
*/

void FAR PASCAL DisplayConnections(HWND hWndhdbc)
{
   int  nConnects;      // # of connections
   int  nIndex;                 // counter
   char szBuffer[MAXBUFLEN+1];  // string buffer

   // read the information from the hdbc(s) combo-box in the tool bar
   // and feed it in the given list box.

   SendMessage(hWndhdbc, LB_RESETCONTENT, 0, 0);
   nConnects = (int) SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0);
   for (nIndex = 0; nIndex < nConnects; nIndex++) {
      SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      SendMessage(hWndhdbc, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   }
   SendMessage(hWndhdbc, LB_SETCURSEL, 0, 0);
}

/*
    FUNCTION: DisplayQueries(HWND hWndhstmt int nCrsrIndex)
    COMMENTS: Display list of hstmt(s) for the currently selected hdbc
          in the given listbox.
*/

void FAR PASCAL DisplayQueries(HWND hWndhstmt, HWND hWndhdbc, int nCrsrIndex)
{
   char  szBuffer[MAXBUFLEN+1];  // MDI child window title
   HWND  hWndChild;              // MDI child window handle
   SQLHDBC  hdbc1;               // hdbc in the window title
   SQLHDBC  hdbc2;               // selected hdbc in the list box
   SQLHSTMT hstmt;               // hstmt in the window title

   // reset the hstmt list box in the disconnect dialog box

   SendMessage(hWndhstmt, LB_RESETCONTENT, 0, 0);

   // go through all available MDI child windows and check if the
   // hdbc in the title matches the one selected in the list box.
   // if they match, use the hstmt in the window title to create
   // a new entry in the hstmt(s) list box.

   for (hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT)) {
      // Class name check is necessary as some of MDI child
      // windows may be iconized by the user and MDIClient
      // in such cases create additional windows (such as
      // icon titles).

      GetClassName(hWndChild, szBuffer, MAXBUFLEN);
      if (strcmp(szBuffer, ODBCMDICLASS))
         continue;

      GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
      sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc1, &hstmt);

      SendMessage(hWndhdbc, LB_GETTEXT, (WPARAM)nCrsrIndex, (LPARAM)(LPSTR)szBuffer);
      sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc2);

      if (hdbc1 != hdbc2)
         continue;

      wsprintf(szBuffer, ((hWndChild == hWndActiveChild) ?  CURQUERY_STRING:    QUERY_STRING), hstmt);
      SendMessage(hWndhstmt, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   }
   SendMessage(hWndhstmt, LB_SETSEL, TRUE, 0);
}

/*
    FUNCTION: NewQueryWindow()
    COMMENTS: Create a new hstmt on current hdbc and its associated window
*/

void FAR PASCAL NewQueryWindow()
{
   int              nCurrenthdbc;           // current selection in the hdbc combobox
   char             szBuffer[MAXBUFLEN+1];  // string in the hdbc combobox on the toolbar
   char             szDBName[MAXBUFLEN+1];  // DSN string
   SQLHDBC          hdbc;                   // hdbc value in the combobox
   SQLHSTMT         hstmt;                  // hstmt
   MDICREATESTRUCT  mcs;                    // MDI child window create structure
   SQLRETURN        nResult;                // Result code

   szDBName[0]='\0';
szBuffer[0]= '\0';

// check if there is hdbc selected in the combobox

if ((nCurrenthdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0)) == CB_ERR) {
      MessageBox(hWndFrame, MAKECONNECT, NOHDBCERROR, MB_OK | MB_ICONHAND);
      return;
   }

   // check if the number of windows exceeds MAXCHILDWNDS

   if (nChildCount >= MAXCHILDWNDS) {
      MessageBox(hWndFrame, MAXCHILDEXCEEDED, MAXCHLDERR, MB_OK | MB_ICONHAND);
      return;
   }

   // Scan DSN string and hdbc value from the combo-box
   SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nCurrenthdbc, (LPARAM)(LPSTR)szBuffer);
   sscanf(szBuffer, SCANDSNHDBC_FORMAT, szDBName, &hdbc);

   // Allocate a new Statement Handle on the scanned hdbc
   if ((nResult = SQLAllocHandle(SQL_HANDLE_STMT,hdbc, &hstmt)) != SQL_SUCCESS) {
      DisplayError(nResult, hWndFrame, SQL_HANDLE_DBC, hdbc);
      return;
   }

   // create a new MDI client window. maximized, if the previous is so.
   mcs.szClass = ODBCMDICLASS;
   mcs.szTitle = UNTITLED;
   mcs.hOwner  = hAppInstance;
   mcs.style   = hWndActiveChild && IsZoomed(hWndActiveChild) ? WS_MAXIMIZE : 0;
   mcs.x = mcs.cx = mcs.y = mcs.cy = CW_USEDEFAULT;
   hWndActiveChild = (HWND)(UINT)SendMessage(hWndMDIClient, WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mcs);

   // check if it was created, if it wasn't free up resource and flag warning
   if (!hWndActiveChild) {
      MessageBox(hWndFrame, CREATECHILDERR, EXECERROR, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
      SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
      return;
   }

   // display the DSN string, SQLHDBC and SQLHSTMT in the title of newly
   // created window. Increment the child window counter
   wsprintf(szBuffer, QUERY_STRING, hstmt);
   SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   wsprintf(szBuffer, DSN_HDBC_HSTMT_FORMAT, (LPSTR)szDBName, hdbc, hstmt);
   SetWindowText(hWndActiveChild, szBuffer);
   nChildCount++;

   // update the hstmt(s) combo-box in the tool bar.
   ChangeCurrentQuery(hWndStmtList);
}

/*
    FUNCTION: ChangeCurrentCursor(HWND hWndCrsrList)
    COMMENTS: Change the displayed hdbc in the hdbc(s) combobox
          Also activate the appropriate MDI child window that
          has the same hdbc as the new hdbc in the combobox.
*/

void FAR PASCAL ChangeCurrentCursor(HWND hWndCrsrList)
{
   int      nNewhdbc;               //new hdbc position
   int      nConnects;              //# of connections
   int      nIndex;                 //counter
   char     szBuffer[MAXBUFLEN+1];  //string buffer
   BOOL     bChangedFocus;          //Activate a different MDI child?
   HWND     hWndChild;              //MDI Child window
   SQLHDBC  hdbc1;                  //hdbc in the window title
   SQLHDBC  hdbc2;                  //hdbc in the combobox
   SQLHSTMT hstmt;                  //hstmt in the window title

   // check to see if the current selection in the combo-box
   // differs from the previous selection, if it is the same then
   // simply return. Check is made by searching a marked string
   // in the hdbc(s) combobox.
   nNewhdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);
   nConnects = (int)SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0);

   for(nIndex = 0; nIndex < nConnects; nIndex++) {
      SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      if (strstr(szBuffer, CUR_MARK))
         break;
   }

   if (nIndex == nNewhdbc)
      return;

   // if there was a current marked hdbc in the combobox, remove the
   // mark from the string and replace it in the combobox.
   if (nIndex != nConnects) {
      SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      szBuffer[strlen(szBuffer)-2] = '\0';
      SendMessage(hWndCrsrList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
      SendMessage(hWndCrsrList, CB_DELETESTRING, nIndex+1, 0);
   }

   // Create a new marked string with currently selected hdbc string in
   // the combobox and replace it with the original.
   SendMessage(hWndCrsrList, CB_GETLBTEXT, nNewhdbc, (LPARAM)(LPSTR)szBuffer);
   strcat(szBuffer, CUR_MARK);
   SendMessage(hWndCrsrList, CB_INSERTSTRING, nNewhdbc, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndCrsrList, CB_DELETESTRING, nNewhdbc+1, 0);
   SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

   // reset the hstmt(s) combobox. Search through the MDI child windows
   // and collect all hstmt(s) from window titles that have the same
   // hdbc value as the newly selected hdbc in the hdbc(s) combo-box above.
   SendMessage(hWndStmtList, CB_RESETCONTENT, 0, 0);
   for (bChangedFocus=FALSE, hWndChild=GetWindow(hWndMDIClient, GW_CHILD);  hWndChild; hWndChild = GetWindow(
                                                                                hWndChild, GW_HWNDNEXT)) {
      // check class name to skip iconized titles or other
      // such non MDI Child windows
      GetClassName(hWndChild, szBuffer, MAXBUFLEN);
      if (strcmp(szBuffer, ODBCMDICLASS))
         continue;

      GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
      sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc1, &hstmt);

      SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nNewhdbc, (LPARAM)(LPSTR)szBuffer);
      sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc2);

      if (hdbc1 != hdbc2)
         continue;

      if (!bChangedFocus) {
         // If the first match is found, change the active window
         // and update the hstmt(s) combobox with a new entry that
         // has hstmt marked with current marker.
         bChangedFocus = TRUE;
         hWndActiveChild = hWndChild;
         SendMessage(hWndMDIClient, WM_MDIACTIVATE, (WPARAM)hWndChild, 0);
         wsprintf(szBuffer, CURQUERY_STRING, hstmt);
         SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
         SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
      }
      else {
         // simply add the hstmt in the hstmt(s) combobox.
         wsprintf(szBuffer, QUERY_STRING, hstmt);
         SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
      }
   }
}

/*
    FUNCTION: ChangeCurrentQuery(HWND hWndStmtList)
    COMMENTS: Change the current selection in the hstmt(s) combobox.
          Update the current marker in the combobox and activate
          proper MDI Child window.
*/

void FAR PASCAL ChangeCurrentQuery(HWND hWndStmtList)
{
   int      nNewhstmt;              //new selection in combobox
   int      nStmts;                 //# of hstmts
   int      nIndex;                 //counter
   char     szBuffer[MAXBUFLEN+1];  //string buffer to update displays
   HWND     hWndChild;              //MDI child window handle
   SQLHDBC  hdbc1 = NULL;                  //hdbc in hdbc(s) combobox
   SQLHDBC  hdbc2 = NULL;                  //hdbc in child window title
   SQLHSTMT hstmt1 = NULL;                 //newly selected hstmt
   SQLHSTMT hstmt2 = NULL;                 //hstmt in child window title

   // find the index of new selection and total number of hstmt(s)
   nNewhstmt = (int)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0);
   nStmts = (int)SendMessage(hWndStmtList, CB_GETCOUNT, 0, 0);

   // check if the current selection is same as previous one, if
   // so simply return. Check for marker to determine previous selection
   for(nIndex = 0; nIndex < nStmts; nIndex++) {
      SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      if (strstr(szBuffer, CUR_MARK))
         break;
   }

   if (nIndex == nNewhstmt)
      return;

   // if a previous selection was found, remove current marker
   // and update it in the hstmt(s) combobox.
   if (nIndex != nStmts) {
      SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      szBuffer[strlen(szBuffer)-2] = '\0';
      SendMessage(hWndStmtList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
      SendMessage(hWndStmtList, CB_DELETESTRING, nIndex+1, 0);
   }

   // mark the current selection and update it in the hstmt(s) combobox
   SendMessage(hWndStmtList, CB_GETLBTEXT, nNewhstmt, (LPARAM)(LPSTR)szBuffer);
   strcat(szBuffer, CUR_MARK);
   SendMessage(hWndStmtList, CB_INSERTSTRING, nNewhstmt, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndStmtList, CB_DELETESTRING, nNewhstmt+1, 0);
   SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

   // scan hstmt value and hdbc value from current selection in
   // hdbc(s) and hstmt(s) comboboxes.
   sscanf(szBuffer, QUERY_STRING, &hstmt1);

   SendMessage(hWndCrsrList, CB_GETLBTEXT, (UINT)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0),
               (LPARAM)(LPSTR)szBuffer);
   sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc1);

   // go through list of MDI Child windows and match the hstmt and hdbc
   // values. If a match if found (must be), activate the window
   for (hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT)) {
      // ignore non MDI child windows
      GetClassName(hWndChild, szBuffer, MAXBUFLEN);
      if (strcmp(szBuffer, ODBCMDICLASS))
         continue;

      GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
      sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc2, &hstmt2);

      if (hdbc1 == hdbc2 && hstmt1 == hstmt2) {
         hWndActiveChild = hWndChild;
         SendMessage(hWndMDIClient, WM_MDIACTIVATE, (WPARAM)hWndChild, 0);
         break;
      }
   }
}

/*
    FUNCTION: DisplayNewCrsrAndStmt()
    COMMENTS: If user changes currently active MDI Child Window, both
          hstmt(s) and hdbc(s) comboboxes need to be updated to
          reflect currently selected MDI child. match the hstmt and
          hdbc value in the title of MDI child to those on display
          in hstmt(s) and hdbc(s) comboboxes respectively.
*/

void FAR PASCAL DisplayNewCrsrAndStmt()
{
   int      nConnects;              // # of hdbc(s)
   int      nStmts;                 // # of hstmt(s)
   int      nOldhdbc;               // prev selection in hdbc combobox
   int      nOldhstmt;              // prev selection in hstmt combobox
   int      nIndex;                 // counter
   char     szBuffer[MAXBUFLEN+1];  // display buffer
   HWND     hWndChild;              // MDI Child window
   SQLHDBC  hdbc1;                  // hdbc in window title
   SQLHDBC  hdbc2;                  // hdbc in hdbc(s) combobox
   SQLHSTMT hstmt1;                 // hstmt in window title
   SQLHSTMT hstmt2;                 // hstmt in hstmt(s) combobox

   // scan hdbc and hstmt values from newly selected window
   GetWindowText(hWndActiveChild, szBuffer, MAXBUFLEN);
   sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc1, &hstmt1);

   // search through list of hdbc(s) in hdbc combobox and find
   // matching hdbc. remove marker from prev selection and add
   // marker to the new selection. update combobox accordingly.
   nConnects = (int)SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0);
   nOldhdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);

   for(hdbc2 = (SQLHDBC)(nIndex = 0); hdbc1 != hdbc2; nIndex++) {
      SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
      sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc2);
   }

   if (--nIndex != nOldhdbc) // change in hdbc combobox required.
      {
         SendMessage(hWndCrsrList, CB_GETLBTEXT, nOldhdbc, (LPARAM)(LPSTR)szBuffer);
         szBuffer[strlen(szBuffer)-2] = '\0';
         SendMessage(hWndCrsrList, CB_INSERTSTRING, nOldhdbc, (LPARAM)(LPSTR)szBuffer);
         SendMessage(hWndCrsrList, CB_DELETESTRING, nOldhdbc+1, 0);

         SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
         strcat(szBuffer, CUR_MARK);
         SendMessage(hWndCrsrList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
         SendMessage(hWndCrsrList, CB_DELETESTRING, nIndex+1, 0);
         SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

         // reset the hstmt(s) combobox, search through the list
         // of MDI child windows and find all hstmt(s) associated to
         // new hdbc. Build the new list of hstmt(s) for the hstmt
         // combobox. Mark the one hstmt that matches the currently
         // activated MDI child window.
         SendMessage(hWndStmtList, CB_RESETCONTENT, 0, 0);
         for (hWndChild=GetWindow(hWndMDIClient,GW_CHILD);hWndChild;hWndChild=GetWindow(hWndChild,GW_HWNDNEXT)) {
            GetClassName(hWndChild, szBuffer, MAXBUFLEN);
            if (strcmp(szBuffer, ODBCMDICLASS))
               continue;

            GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
            sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc2, &hstmt2);

            if (hdbc1 != hdbc2)
               continue;

            if (hWndActiveChild == hWndChild) {
               wsprintf(szBuffer, CURQUERY_STRING, hstmt2);
               SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
               SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
            }
            else {
               wsprintf(szBuffer, QUERY_STRING, hstmt2);
               SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
            }
         }
      }
   else // no change in hdbc combobox required
      {
         // go through the list of hstmt(s) in hstmt combobox.
         // find the one that matches the currently activated MDI
         // child window.
         nOldhstmt = (int)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0);
         nStmts = (int)SendMessage(hWndStmtList, CB_GETCOUNT, 0, 0);

         for(hstmt2 = (SQLHDBC)(nIndex = 0); hstmt1 != hstmt2; nIndex++) {
            SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
            sscanf(szBuffer, QUERY_STRING, &hstmt2);
         }

         if (--nIndex != nOldhstmt) // new index in hstmt differs from previous selection
            {
               // remove the marker from previous selection. Add
               // it to the new string and update the combobox display
               SendMessage(hWndStmtList, CB_GETLBTEXT, nOldhstmt, (LPARAM)(LPSTR)szBuffer);
               szBuffer[strlen(szBuffer)-2] = '\0';
               SendMessage(hWndStmtList, CB_INSERTSTRING, nOldhstmt, (LPARAM)(LPSTR)szBuffer);
               SendMessage(hWndStmtList, CB_DELETESTRING, nOldhstmt+1, 0);

               SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
               strcat(szBuffer, CUR_MARK);
               SendMessage(hWndStmtList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
               SendMessage(hWndStmtList, CB_DELETESTRING, nIndex+1, 0);
               SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
            }
      }
}

/*
    FUNCTION: FreeConnect(HWND hWndhdbc)
    COMMENTS: Disconnect and Free the currently selected hdbc in
          the hdbc listbox in disconnect dialog. Call SQLDisconnect
          and SQLFreeConnect to free the hdbc. Close all MDI
          child windows associated with this hdbc. That will
          automatically free associated hstmt(s).
*/

void FAR PASCAL FreeConnect(HWND hWndhdbc)
{
   int     nIndex;                 // current selection in the listbox of disconnect dlg
   int     nCurrent;               // current selection in hdbc(s) combobox
   char    szBuffer[MAXBUFLEN+1];  // display buffer
   char    szSelect[MAXBUFLEN+1];  // original selection in hdbc(s) combobox
   char    szDBName[MAXBUFLEN+1];  // DSN string
   HWND    hWndChild;              // MDI Child window
   SQLHDBC hdbc1 = NULL;                  // hdbc in the list box of disconnect dlg
   SQLHDBC hdbc2 = NULL;                  // hdbc in the MDI child window title

   szDBName[0]='\0';
// check current selection in the list box of disconnect dialog. scan
   // hdbc value from the current selection.
   if ((nIndex = (int)SendMessage(hWndhdbc, LB_GETCURSEL, 0, 0)) == LB_ERR)
      return;

   SendMessage(hWndhdbc, LB_GETTEXT, (WPARAM)nIndex, (LPARAM)(LPSTR)szBuffer);
   sscanf(szBuffer, SCANDSNHDBC_FORMAT, szDBName, &hdbc1);

   // go through the list of MDI child windows and find matching hdbc(s)
   // close all children who have the same hdbc value. Closing them
   // automatically frees associated hstmts. see CloseQueryWindow.
   for(hWndChild = GetWindow(hWndMDIClient, GW_CHILD); hWndChild; ) {
      // store next window handle before destroying the currentone
      HWND hWndTemp = GetWindow(hWndChild, GW_HWNDNEXT);

      // ignore non MDI child windows
      GetClassName(hWndChild, szBuffer, MAXBUFLEN);
      if (!strcmp(szBuffer, ODBCMDICLASS)) {
         GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
         sscanf(szBuffer, SCANHDBC_TITLEFORMAT, &hdbc2);

         if (hdbc1 == hdbc2) {
            // destroy the window and restart search
            SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
            hWndTemp = GetWindow(hWndMDIClient, GW_CHILD);
         }
      }
      hWndChild = hWndTemp;
   }

   // call SQLDisconnect and SQLFreeConnect to disconnect and free hdbc
   SQLDisconnect(hdbc1);
   SQLFreeHandle(SQL_HANDLE_DBC,hdbc1);

   // update the hdbc(s) combobox display by removing the deleted hdbc
   // from the list and reselecting the previous selection
   wsprintf(szBuffer, DSN_HDBC_FORMAT, (LPSTR)szDBName, hdbc1);
   nCurrent = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);
   SendMessage(hWndCrsrList, CB_GETLBTEXT, nCurrent, (LPARAM)(LPSTR)szSelect);
   nIndex = (int)SendMessage(hWndCrsrList, CB_FINDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
   SendMessage(hWndCrsrList, CB_DELETESTRING, (WPARAM)nIndex, 0);

SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szSelect);

   // if there is no query window open and the current selected SQLHDBC
   // was deleted, make sure to make the next available SQLHDBC as current
   if (SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) &&
       !GetWindow(hWndMDIClient, GW_CHILD) && (nCurrent == nIndex)) {
      if ((nCurrent = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0))!=CB_ERR)
         return;
      SendMessage(hWndCrsrList, CB_GETLBTEXT, 0, (LPARAM)(LPSTR)szSelect);
      strcat(szSelect, CUR_MARK);
      SendMessage(hWndCrsrList, CB_INSERTSTRING, 0, (LPARAM)(LPSTR)szSelect);
      SendMessage(hWndCrsrList, CB_DELETESTRING, 1, 0);
      SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szSelect);
   }
}

/*
    FUNCTION: FreeQuery(HWND hWndhstmt, HWND hWndhdbc, int nIndex)
    COMMENTS: Free a query window based upon current selection in
          hstmt list box in the disconnect dialog.
*/

void FAR PASCAL FreeQuery(HWND hWndhstmt, HWND hWndhdbc, int nIndex)
{
   char     szBuffer[MAXBUFLEN+1];  // display buffer
   HWND     hWndChild;      // MDI child window
   SQLHDBC  hdbc1;          // hdbc in the hdbc listbox of disconnect dlg
   SQLHDBC  hdbc2;          // hdbc in the MDI child window title
   SQLHSTMT hstmt1;         // hstmt in the hstmt listbox of disconnect dlg
   SQLHSTMT hstmt2;         // hstmt in the MDI child window title

   // scan the hstmt and hdbc values from the current selections in
   // respective listboxes of disconnect dialog box.
   SendMessage(hWndhstmt, LB_GETTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
   sscanf(szBuffer, QUERY_STRING, &hstmt1);
   SendMessage(hWndhdbc, LB_GETTEXT, (UINT)SendMessage(hWndhdbc, LB_GETCURSEL,  0, 0), (LPARAM)(LPSTR)szBuffer);
   sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc1);

   // go through the list of MDI child windows and find matching window
   // that has same values for hdbc and hstmt. Destroy the matching window.
   // that will call CloseQueryWindow and thus free up associated hstmt.
   for(hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT)) {
      // ignore non MDI child windows
      GetClassName(hWndChild, szBuffer, MAXBUFLEN);
      if (strcmp(szBuffer, ODBCMDICLASS))
         continue;
      GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
      sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc2, &hstmt2);
      if (hdbc1 == hdbc2 && hstmt1 == hstmt2)
         break;
   }
   SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
}

/*
    FUNCTION: CloseQueryWindow(HWND hWnd)
    COMMENTS: Close a Query window. Call SQLFreeHandle to free
          associated hstmt.
*/

void FAR PASCAL CloseQueryWindow(HWND hWnd)
{
   char szBuffer[MAXBUFLEN+1];  // display buffer
   char szSelect[MAXBUFLEN+1];  // current selection in hstmt(s) combobox
   SQLHSTMT hstmt;              // hstmt value in window title

   // scan the hstmt value from the window title
   GetWindowText(hWnd, szBuffer, MAXBUFLEN);
   sscanf(szBuffer, SCANHSTMT_TITLEFORMAT, &hstmt);

   // Drop the current Statement Handle
   SQLFreeHandle(SQL_HANDLE_STMT,hstmt);

   // find the matching hstmt in the hstmt(s) combobox and remove it
   // from the list. Closure of a MDI child window will cause MDIClient
   // to automatically activate a different child window if available.
   // that will automatically refresh the hstmt and hdbc displays. See
   // DisplayNewCrsrAndStmt function.
   wsprintf(szBuffer, QUERY_STRING, hstmt);
   SendMessage(hWndStmtList, CB_GETLBTEXT, (WPARAM)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0), (LPARAM)(LPSTR)szSelect);
   SendMessage(hWndStmtList, CB_DELETESTRING, (WPARAM)SendMessage(hWndStmtList, CB_FINDSTRING, 0,
                                                                  (LPARAM)(LPSTR)szBuffer), 0);
   SendMessage(hWndStmtList, CB_SELECTSTRING, (WPARAM)-1, (LPARAM)(LPSTR)szSelect);

   // decrement the child window counter.
   nChildCount--;
}

/*
    FUNCTION: ExecuteQuery()
    COMMENTS: Execute the user typed SQL Statements in the currently
          active MDI child window. If successful, then prepare
          the list of results and display it in the child listbox.
          Display errors in the ODBC function(s) failed.
*/

void FAR PASCAL ExecuteQuery()
{
   char      szBuffer[MAXBUFLEN+1];      // display buffer
   HWND      hList;                      // result listbox handle
   SQLHDBC   hdbc;                       // hdbc
   SQLHSTMT  hstmt;                      // hstmt
   SWORD     nCols = 0;                  // # of result columns
   SWORD     nCount;                     // index
   SWORD     swColType;                  // column data type
   SWORD     swColScale;                 // column scale
   SWORD     swColNull;                  // nullable column ?
   SWORD     swColLength = MAXDATALEN;   // column data length
   DWORD     dwText;                     // tab stop for listbox
   UDWORD    udwColDef;                  // precision on the column
   SQLRETURN nReturn;                    // return code
   HCURSOR   hOldCursor;                 // default cursor handle
   int       nRows;                      // # of result rows

   // check if there is an active window available
   if (!hWndActiveChild) {
      MessageBox(hWndFrame, ((SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) <=0) ?
                             MAKECONNECT : OPENWINDOW ), NOHSTMTERROR, MB_OK | MB_ICONHAND);
      return;
   }

   // change cursor shape to hour glass
   hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));

   // initialize column data and column data length arrays
   for (nCount = 0; nCount < MAX_COL; nCount++) {
      dwDataLen[nCount] = 0;
      rgbData[nCount][0] = '\0';
   }

   // scan hdbc, hstmt values
   GetWindowText(hWndActiveChild, szBuffer, MAXBUFLEN);
   sscanf(szBuffer, SCANHDBCHSTMT_FORMAT, &hdbc, &hstmt);

   // get the user typed SQL
   GetWindowText(GetDlgItem((HWND)GetWindowLong(hWndActiveChild, GWLAPP_HDLG), IDTEXT_SQL), szBuffer, MAXBUFLEN);

   // execute SQL and process errors if any
   // call SQLExecDirect to execute the SQL statement
   nReturn = SQLExecDirect(hstmt, szBuffer, SQL_NTS);
   if (nReturn != SQL_SUCCESS) {
      DisplayError(nReturn, hWndActiveChild, SQL_HANDLE_STMT, hstmt);

      // if the return value was just information, carry on
      if (nReturn != SQL_SUCCESS_WITH_INFO) {
         // Close the open result set.
         SQLCloseCursor(hstmt);
         SetCursor(hOldCursor);
         return;
      }
   }

   // call SQLNumResultCols to calculate the number of columns in
   // the result set, if more than the MAX_COL (the array boundary)
   // limit the number to MAX_COL and display truncation warning.
   // if it is 0, the statement probably was a non-SELECT simply return
   nReturn = SQLNumResultCols(hstmt, &nCols);
   if (nCols >= MAX_COL) {
      nCols = MAX_COL;
      wsprintf(szDispBuffer, COLTRUNC_WARNG, MAX_COL);
      MessageBox(hWndFrame, szDispBuffer, TRUNCERR, MB_OK | MB_ICONINFORMATION);
   }
   else if (nCols == 0) {
      // Close the open result set.
      SQLCloseCursor(hstmt);
      SetCursor(hOldCursor);
      return;
   }

   // call SQLBindCol to bind column data array and column data length
   // array to the result set
   for(nCount=0; nCount<nCols; nCount++)
      SQLBindCol(hstmt, (UWORD)(nCount+1), SQL_C_CHAR, rgbData[nCount], MAXDATALEN, &dwDataLen[nCount]);

   // reset the display in the list box. Set tabstops to display
   // multiple columns in the list box separated by tabs.
   hList = GetDlgItem((HWND)GetWindowLong(hWndActiveChild, GWLAPP_HDLG), IDLIST_RSLT);

   SendMessage(hList, LB_RESETCONTENT, 0, 0);
   SendMessage(hList, WM_SETREDRAW, FALSE, 0);
   dwText = LISTTABSTOP;
   SendMessage(hList, LB_SETTABSTOPS, (WPARAM)1, (LPARAM)(LPINT)&dwText);

   // call SQLDescribeCol to get description of each column in the
   // result set. Store the column name in the display buffer and
   // make it the first entry in the results list box of the MDI
   // child window.
   for(nCount=1, szDispBuffer[0]='\0'; nCount <= nCols; nCount++) {
      SQLDescribeCol(hstmt, nCount, szBuffer, MAXDATALEN, &swColLength, &swColType, &udwColDef,
                     &swColScale, &swColNull);
      strcat(szDispBuffer, szBuffer);
      dwText = strlen(szDispBuffer);
      szDispBuffer[dwText++] = '\t';
      szDispBuffer[dwText] = '\0';
   }
   if (*szDispBuffer)
      szDispBuffer[strlen(szDispBuffer)-1]='\0';
   SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szDispBuffer);

   // call SQLFetch to fetch each row of the result set. Extract data
   // value and store it in the display buffer, separated by tabs.
   // continue until SQLFetch fails.
   for(nRows = 0; (nReturn = SQLFetch(hstmt))==SQL_SUCCESS || nReturn==SQL_SUCCESS_WITH_INFO;) {
      if (nReturn != SQL_SUCCESS)
         DisplayError(nReturn, hWndActiveChild, SQL_HANDLE_STMT, hstmt);
      for(nCount=0, szDispBuffer[0]='\0'; nCount<nCols; nCount++) {
         // check if the column is a null value?
         strcat(szDispBuffer, (dwDataLen[nCount]==SQL_NULL_DATA)?NULLDATASTRING:rgbData[nCount]);
         dwText = strlen(szDispBuffer);
         szDispBuffer[dwText++] = '\t';
         szDispBuffer[dwText] = '\0';
      }
      if (*szDispBuffer)
         szDispBuffer[strlen(szDispBuffer)-1]='\0';
      else
         break;
      SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szDispBuffer);
      if (++nRows == MAX_ROW) {
         wsprintf(szDispBuffer, ROWTRUNC_WARNG, MAX_ROW);
         MessageBox(hWndFrame, szDispBuffer, TRUNCERR, MB_OK | MB_ICONINFORMATION);
         break;
      }
   }

   // set the horizontal scroll extent in the list box and ask for repaint.
   SendMessage(hList, LB_SETHORIZONTALEXTENT, (WPARAM)(nCols*LISTHORZSCROLL+LISTHORZEXT), 0);
   SendMessage(hList, WM_SETREDRAW, TRUE, 0);

   // if there was any error returned by SQLFetch, display it
   if (nReturn == SQL_ERROR)
      DisplayError(nReturn, hWndActiveChild, SQL_HANDLE_STMT, hstmt);

   // Close the open result set.
   // Unbind all bound columns.
   SQLCloseCursor(hstmt);
   SQLFreeStmt(hstmt, SQL_UNBIND);

   // restore the cursor to default value
   SetCursor(hOldCursor);
}

/*
    FUNCTION: CloseDatabases()
    COMMENTS: Go through all open connections (hdbcs) and hstmt(s)
          and close them one by one.
*/

BOOL FAR PASCAL CloseDatabases()
{
   int     nIndex;                 // index
   int     nCount;                 // # of connected hdbc(s)
   char    szBuffer[MAXBUFLEN+1];  // display string buffer
   HWND    hWndChild;              // MDI Child Window
   SQLHDBC hdbc1;                  // hdbc value in the hdbc(s) combobox
   SQLHDBC hdbc2;                  // hdbc value in the MDI Child window title

   // get count of connected hdbc(s) from the hdbc(s) combobox on the toolbar
   if (!(nCount = (int)SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0)))
      return (TRUE);

   // go through all available MDI child windows and for each hdbc,
   // find the matching MDI child window and ask it for closure, thereby
   // freeing the associated hstmt (see CloseQueryWindow). Once all
   // associated hstmt(s) are freed, free the hdbc
   for (nIndex = 0; nIndex < nCount; nIndex++) {
      // scan current indexed hdbc from hdbc(s) combobox
      SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nIndex, (LPARAM)(LPSTR)szBuffer);
      sscanf(szBuffer, SCANHDBC_FORMAT, &hdbc1);

      // search through the list of MDI Child Windows
      for(hWndChild = GetWindow(hWndMDIClient, GW_CHILD); hWndChild; ) {
         // store the next child, before destroying the current
         HWND hWndTemp = GetWindow(hWndChild, GW_HWNDNEXT);

         // ignore non MDI child windows
         GetClassName(hWndChild, szBuffer, MAXBUFLEN);
         if (!strcmp(szBuffer, ODBCMDICLASS)) {
            GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
            sscanf(szBuffer, SCANHDBC_TITLEFORMAT, &hdbc2);
            if (hdbc1 == hdbc2) {
               // destroy the window and restart search
               SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
               hWndTemp = GetWindow(hWndMDIClient, GW_CHILD);
            }
         }
         hWndChild = hWndTemp;
      }

      // call SQLDisconnect and SQLFreeConnect to disconnect and
      // free the curren hdbc resource
      SQLDisconnect(hdbc1);
      SQLFreeHandle(SQL_HANDLE_DBC,hdbc1);
   }

   // reset the hdbc(s) combobox display and display all connections
   // closed message. return success to let application exit.
   SendMessage(hWndCrsrList, CB_RESETCONTENT, 0, 0);
   MessageBox(hWndFrame, CLOSEALLHDBC, LOGOUTINFO,  MB_OK | MB_ICONINFORMATION);
   return (TRUE);
}

/*
    FUNCTION: FreeSQLEnvironment()
    COMMENTS: Free the ODBC environment.
*/

BOOL FAR PASCAL FreeSQLEnvironment()
{
   // Free up all ODBC related resources
   return (SQLFreeHandle(SQL_HANDLE_ENV,henv) == SQL_SUCCESS);
}

/*
    FUNCTION: DisplayError(SQLRETURN nResult, HWND hWnd, SWORD fHandleType, SQLHANDLE handle)
    COMMENTS: Format and display errors or warnings.
*/

void FAR PASCAL DisplayError(SQLRETURN nResult, HWND hWnd, SWORD fHandleType, SQLHANDLE handle)
{
   UCHAR    szErrState[SQL_SQLSTATE_SIZE+1];    // SQL Error State string
   UCHAR    szErrText[SQL_MAX_MESSAGE_LENGTH+1];    // SQL Error Text string
   char szBuffer[SQL_SQLSTATE_SIZE+SQL_MAX_MESSAGE_LENGTH+MAXBUFLEN+1];
   // formatted Error text Buffer
   SWORD    wErrMsgLen;             // Error message length
   UDWORD   dwErrCode;              // Native Error code
   int  iSize;                      // Display Error Text size
   SQLRETURN  nErrResult;             // Return Code from SQLGetDiagRec
   SWORD sMsgNum = 1;
   SWORD fFirstRun = TRUE;

   szBuffer[0] = '\0';

   do
      {
         // continue to bring messageboxes till all errors are displayed.
         // more than one message box may be reqd. as err text has fixed
         // string size.

         // initialize display buffer with the string in error text buffer
         strcpy(szDispBuffer, szBuffer);

         // call SQLGetDiagRec function with proper ODBC handles, repeatedly until
         // function returns SQL_NO_DATA. Concatenate all error strings
         // in the display buffer and display all results.
         while ((nErrResult = SQLGetDiagRec(fHandleType, handle, sMsgNum++,
                                            szErrState, &dwErrCode, szErrText,
                                            SQL_MAX_MESSAGE_LENGTH-1, &wErrMsgLen))
                != SQL_NO_DATA) {

if(nErrResult == SQL_ERROR || nErrResult == SQL_INVALID_HANDLE)
break;

            wsprintf(szBuffer, SQLERR_FORMAT, (LPSTR)szErrState, dwErrCode, (LPSTR)szErrText);
            iSize = strlen(szDispBuffer);
            if (iSize && (iSize+strlen(szBuffer)+1) >= MAXDISPLAYSIZE)
               break;
            if (iSize)
               strcat(szDispBuffer, "\n");
            strcat(szDispBuffer, szBuffer);
         }

         // display proper ERROR or WARNING message with proper title

         if (nResult == SQL_SUCCESS_WITH_INFO)
            MessageBox(hWnd, szDispBuffer, (fFirstRun? SQLWRNMSGTITLE : SQLWRNCNTDTITLE),
                       MB_OK | MB_ICONINFORMATION);
         else
            MessageBox(hWnd, szDispBuffer, (fFirstRun? SQLERRMSGTITLE : SQLERRCNTDTITLE),
                       MB_OK | MB_ICONEXCLAMATION);

         if (fFirstRun)
            fFirstRun = FALSE;
      }
   while (!(nErrResult == SQL_NO_DATA || nErrResult == SQL_ERROR || nErrResult == SQL_INVALID_HANDLE));
}
/********************************************* END OF FILE **************************************************/


QURYDEMO.H

/*
    Header File for QURYDEMO application
    Created by Microsoft Corporation.
*/
#ifdef WIN32
#define CONSTSTR(name, str) const char name[]=str
#else
#define CONSTSTR(name, str) const char FAR * name=str
#endif

#ifdef RC_INVOKED
#define ID(id) id
#else
#define ID(id) MAKEINTRESOURCE(id)
#endif

/* Resource Identifiers */

#define BMP_NEWQUERY        ID(10)  // new window button bitmap
#define BMP_RUNQUERY        ID(20)  // exec query button bitmap
#define APPICON             ID(30)  // application icon
#define QUERYWINDOWICON     ID(40)  // MDI Child window icon
#define QURYDEMOMENU        ID(50)  // Application menu
#define CONNECTDIALOG       ID(60)  // connect modal dialog box
#define DISCONNECTDIALOG    ID(70)  // disconnect modal dialog box
#define ABOUTDIALOG         ID(80)  // about modal dialog box
#define MDICHILDDIALOG      ID(90)  // MDI Child modeless dialog box
#define APPACCELERATOR      ID(100) // Accelerator table for the app

/* Menu Identifiers */

#define IDM_POPUPAPPSYS     10  // Placeholder, used by status bar
#define IDM_POPUPMDISYS     20  // Placeholder, used by status bar
#define IDM_POPUPLOGIN      100 // Placeholder, used by status bar
#define IDM_CONNECT         110
#define IDM_DRIVERCONNECT   120
#define IDM_DISCONNECT      130
#define IDM_EXIT            140
#define IDM_POPUPQUERY      150 // Placeholder, used by status bar
#define IDM_QUERY           160
#define IDM_NEW             170
#define IDM_POPUPWINDOW     180     // Placeholder, used by status bar
#define IDM_TILE            190
#define IDM_CASCADE         200
#define IDM_ICONS           210
#define IDM_CLOSEALL        220
#define IDM_POPUPHELP       230 // Placeholder, used by status bar
#define IDM_APPHELP         240
#define IDM_ABOUT           250

#define IDM_MDICHILD        260

/* child window ids */

#define IDW_TOOLBAR         10
#define IDW_STATUSBAR       20
#define IDW_MDICLIENT       30

#define IDW_CRSRLIST        10  // toolbar comboboxes
#define IDW_STMTLIST        20

#define IDCOMBO_DATASOURCE  10  // connect dialog box controls
#define IDTEXT_USERNAME     20
#define IDTEXT_PASSWORD     30

#define IDLIST_HDBC         10  // disconnect dialog box controls
#define IDLIST_STMT         20
#define IDCLOSE_ACTVTY      30
#define IDDISCONNECT        40
#define IDICON_WARN         50

#define IDTEXT_PRMPT        10  // MDI child's modeless dialog controls
#define IDTEXT_SQL          20
#define IDLIST_RSLT         30

/* MDI Child Window Offsets */

#define CBWNDEXTRA          4   // storage in MDI Child Window
#define GWLAPP_HDLG         0   // child dialog handle

/* Help File */

#define SAMPLESHELPFILE     "..\\doc\\odbcsmpl.hlp"
#define HLP_QURYDEMO        30

/* Other Constants */

#define MAXCHILDWNDS        10          //maximum child windows allowed
#define MAXBUFLEN           256         //display buffer size
#define MAX_COL             15          //maximum column in result set
#define MAX_ROW             100         //maximum number of rows
#define MAXDATALEN          25          //maximum data length per column
#define MAXDISPLAYSIZE      MAX_COL*(MAXDATALEN+1)
#define MINWIDTH            415         //minimum width of app window
#define MINHEIGHT           350         //minimum height of app window
#define LISTTABSTOP         100         //tabstop in the display listbox
#define LISTHORZSCROLL      (LISTTABSTOP*2)     //horizontal scroll for the listbox
#define LISTHORZEXT         0           //horizontal extent inthe list box
#define WINDOWMENUPLACE     2           //Window menu place for adding MDI child windows
#define IDT_STATUSTIMER     1           //timer id
#define TIMERDELAY          60000           //timer duration
#define BTTNX               345         //first toolbar button location
#define BTTNY               2           //button y position
#define BTTNWIDTH           25                      //button width
#define BTTNHEIGHT          23                      //button height
#define BTTNMARGIN          5           //distance between toolbar buttons
#define TOOLBARMARGINY      5           //fixed margin w/o combobox height
#define STATUSBARMARGINY    8           //fixed margin w/o textbox height

/* String Constants */

#define POPUPLOGIN          "&Login"
#define MENUITEMCONNECT     "&Connect... \aCtrl+C"
#define MENUITEMDRIVERCONNECT   "D&river Connect... \aCtrl+R"
#define MENUITEMDISCONNECT  "&Disconnect... \aCtrl+D"
#define MENUITEMEXIT        "E&xit"
#define POPUPQUERY          "&Query"
#define MENUITEMQUERY       "&Execute SQL \aCtrl+E"
#define MENUITEMNEW         "&New Query \aCtrl+N"
#define POPUPWINDOW         "&Window"
#define MENUITEMTILE        "&Tile"
#define MENUITEMCASCADE     "&Cascade"
#define MENUITEMICONS       "Arrange &Icons"
#define MENUITEMCLOSEALL    "Close &All"
#define POPUPHELP           "&Help"
#define MENUITEMAPPHELP     "&Help on Qurydemo"
#define MENUITEMABOUT       "&About Qurydemo..."

#ifdef WIN32
#define ODBCFRAMECLASS      "QDEMO32MAIN"
#else
#define ODBCFRAMECLASS      "QDEMOMAIN"
#endif

#define ODBCTOOLCLASS       "QDEMOTool"
#define ODBCSTATUSCLASS     "QDEMOStatus"
#define ODBCMDICLASS        "QDEMOMDI"
#define COMBOBOXCLASS       "COMBOBOX"
#define MDICLIENTCLASS      "MDICLIENT"
#define UNTITLED            "Untitled"
#define APPTITLE            "ODBC Sample Program"
#define EXECERROR           "Execution Error"
#define INITERROR           "Failed to initialize ODBC interface"
#define CLASSERROR          "Failed to register application class"
#define CREATEMAINERR       "Failed to create application main window"
#define CREATECHILDERR      "Failed to create a new window"
#define ALPHABETS           "abcdefghijklmnopqrstuvwxyz"
#define TIMETEXT            "MM:MM MM"
#define DATETEXT            "MMM-MM"
#define TIMEFORMAT          " %I:%M %p"
#define DATEFORMAT          " %b-%d"
#define NODSNERR            "Please provide a Data Source"
#define MOREINFO            "Incomplete Information"
#define STATUSPOPUPAPPSYS   " Control menu for the application "
#define STATUSPOPUPMDISYS   " Control menu for the window "
#define STATUSRESTORE       " Restore window to normal "
#define STATUSMOVE          " Move window "
#define STATUSSIZE          " Size window "
#define STATUSMINIMIZE      " Minimize window "
#define STATUSMAXIMIZE      " Maximize window "
#define STATUSCLOSE         " Close window "
#define STATUSNEXTWINDOW    " Switch to next window "
#define STATUSPREVWINDOW    " Switch to previous window "
#define STATUSTASKLIST      " Bring up task list "
#define STATUSPOPUPLOGIN    " Login and logout commands "
#define STATUSCONNECT       " Connect with default parameters... "
#define STATUSDRIVERCONNECT " Connect with driver dialogs... "
#define STATUSDISCONNECT    " Close open connections... "
#define STATUSEXIT          " Exit application "
#define STATUSPOPUPQUERY    " Query commands "
#define STATUSQUERY         " Execute SQL statement... "
#define STATUSNEW           " New window on current connection... "
#define STATUSPOPUPWINDOW   " Window management commands "
#define STATUSTILE          " Tile all normal child windows "
#define STATUSCASCADE       " Cascade all normal child windows "
#define STATUSICONS         " Arrange iconic child windows "
#define STATUSCLOSEALL      " Close all child windows "
#define STATUSMDICHILD      " Activate the window marked as %d"
#define STATUSPOPUPHELP     " Help commands "
#define STATUSAPPHELP       " Display help on application "
#define STATUSABOUT         " About application "
#define STATUSDEFAULT       " Ready "

#define DEFDBNAME               "?"
#define DSN_HDBC_FORMAT         "%s!%lX"
#define DSN_HDBC_HSTMT_FORMAT   "DSN=%s!HDBC=%lX!HSTMT=%lX"
#define SCANDSNHDBC_FORMAT      "%[^!]!%lX"
#define SCANHDBC_FORMAT         "%*[^!]!%lX"
#define SCANHDBC_TITLEFORMAT    "DSN=%*[^!]!HDBC=%lX"
#define SCANHDBCHSTMT_FORMAT    "DSN=%*[^!]!HDBC=%lX!HSTMT=%lX"
#define SCANHSTMT_TITLEFORMAT   "DSN=%*[^!]!HDBC=%*lX!HSTMT=%lX"
#define SCAN_HDBC_FORMAT"%*c%lX"
#define SCAN_HDBC_TITLEFORMAT"DSN=%*cHDBC=%lX"

#define CUR_MARK            "<-"
#define CURQUERY_STRING     "Query!%lX<-"
#define QUERY_STRING        "Query!%lX"
#define MAKECONNECT         "Please connect to a data source using Login menu."
#define OPENWINDOW          "Please open a window from Query menu."
#define NOHDBCERROR         "No Connection Available"
#define NOHSTMTERROR        "No Query Window Available"
#define MAXCHILDEXCEEDED    "Please close a child window and try again."
#define MAXCHLDERR          "Error - Too many Child Windows"
#define COLTRUNC_WARNG      "Number of columns in display truncated to %u"
#define ROWTRUNC_WARNG      "Number of rows in display truncated to %u"
#define TRUNCERR            "Data Truncation"
#define CLOSEALLHDBC        "All open connections have been closed."
#define LOGOUTINFO          "Log Out Information"
#define SQLERR_FORMAT       "SQL Error State:%s, Native Error Code: %lX, ODBC Error: %s"
#define SQLERRMSGTITLE      "SQL_ERROR results"
#define SQLERRCNTDTITLE     "SQL_ERROR results continued"
#define SQLWRNMSGTITLE      "SQL_SUCCESS_WITH_INFO results"
#define SQLWRNCNTDTITLE     "SQL_SUCCESS_WITH_INFO results continued"
#define NULLDATASTRING      "SQL_NULL_DATA"

/* Globals */

HWND        hWndFrame;      // Main Frame Window handle
HWND        hWndToolbar;    // Toolbar window handle
HWND        hWndCrsrList;   // hdbc(s) combobox on the tool bar
HWND        hWndStmtList;   // hstmt(s) combobox on the tool bar
HWND        hWndStatusbar;  // status bar handle
HWND        hWndMDIClient;  // MDI Client window handle
HWND        hWndActiveChild;// Current active MDI Child window
HINSTANCE   hAppInstance;   // Application instance
WORD        wStatusText;    // Status text state
int         iTimex;         // Time box (on the status bar) width
int         iDatex;         // Date box (on the status bar) width
RECT        rectStatusText; // Text box on the status bar

#ifdef __SQL    // internal to query.c

int         nChildCount;            // Number of child windows currently open
SQLHENV     henv;                   // Environment Handle
char        szDispBuffer[MAXDISPLAYSIZE+1]; // Display Buffer
UCHAR       rgbData[MAX_COL][MAXDATALEN];   // Results Data Array
SDWORD      dwDataLen[MAX_COL];     // Results Data Length Array

#endif

/* Macros and Function Prototypes */

#ifdef WIN32
#define     ACTIVATEWINDOW(h)   SetForegroundWindow(h)
#else
#define     ACTIVATEWINDOW(h)   SetFocus(h)
#endif

#define DRAWBTTNRECT(h,x,y,cx,cy)   MoveToEx(h,x+1,y,NULL);\
                    LineTo(h,x+cx,y);\
                    MoveToEx(h,x+cx,y+1,NULL);\
                    LineTo(h,x+cx,y+cy);\
                    MoveToEx(h,x+cx-1,y+cy,NULL);\
                    LineTo(h,x,y+cy);\
                    MoveToEx(h,x,y+cy-1,NULL);\
                    LineTo(h,x,y);

#define DRAWBTTNLIFT1(h,x,y,cx,cy)  MoveToEx(h,x+1,y+cy-1,NULL);\
                    LineTo(h,x+1,y+1);\
                    LineTo(h,x+cx-1,y+1);

#define DRAWBTTNLIFT2(h,x,y,cx,cy)  MoveToEx(h,x+1,y+cy-1,NULL);\
                    LineTo(h,x+cx-1,y+cy-1);\
                    LineTo(h,x+cx-1,y);\
                    MoveToEx(h,x+2,y+cy-2,NULL);\
                    LineTo(h,x+cx-2,y+cy-2);\
                    LineTo(h,x+cx-2,y+1);

int PASCAL      WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
long    CALLBACK    MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long    CALLBACK    ToolbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long    CALLBACK    StatusbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long    CALLBACK    MDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL    CALLBACK    ConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL    CALLBACK    DisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL    CALLBACK    AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL    CALLBACK    MDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
VOID    FAR PASCAL  DrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap);

BOOL    FAR PASCAL  InitSQLEnvironment();
void    FAR PASCAL  DisplayDatabases(HWND hWnd);
BOOL    FAR PASCAL  ConnectDatabase(HWND hWnd);
BOOL    FAR PASCAL  DriverConnectDatabase(HWND hWnd);
void    FAR PASCAL  DisplayConnections(HWND hWndhdbc);
void    FAR PASCAL  DisplayQueries(HWND hWndhstmt, HWND hWndhdbc, int nCrsrIndex);
void    FAR PASCAL  NewQueryWindow();
void    FAR PASCAL  ChangeCurrentQuery(HWND hWndStmtList);
void    FAR PASCAL  ChangeCurrentCursor(HWND hWndCrsrList);
void    FAR PASCAL  DisplayNewCrsrAndStmt();
void    FAR PASCAL  FreeConnect(HWND hWndhdbc);
void    FAR PASCAL  FreeQuery(HWND hWndhstmt, HWND hWndhdbc, int nIndex);
void    FAR PASCAL  CloseQueryWindow(HWND hWnd);
BOOL    FAR PASCAL  FreeSQLEnvironment();
void    FAR PASCAL  ExecuteQuery();
BOOL    FAR PASCAL  CloseDatabases();

#ifdef __SQL    // internal to query.c

void    FAR PASCAL  DisplayError(SQLRETURN nResult, HWND hWnd, SWORD fHandleType, SQLHANDLE handle);

#endif
/******************************* End of File ********************************/


DOCLIENT.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB ISAPI Sample
// Copyight(c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DOclient.CPP | OLE DB DO client, it connects to the provider,
//                 runs SQL SELECT statement, prints data onto a webpage
//
// @rev 1 | 06-11-96 | Created
//

//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <afxole.h>

#define DBINITCONSTANTS

#include <iostream.h>
#include <assert.h>
#include <oledb.h>
#include <msdasql.h>
#include <oledberr.h>
#include "DOclient.h"   //Data Objects Client header file

#define NUMELEM(p1) (sizeof(p1) / sizeof(p1[0]))

//-----------------------------------------------------------------------------
// Global Variables
//-----------------------------------------------------------------------------

IMalloc* g_pIMalloc = NULL;                      //Memory allocator

//-----------------------------------------------------------------------------------
// ConnectDB
//
// @func HRESULT| ConnectDB  | Instantiates Kagera, Initialize OLE DB, Create Session
//
// @rdesc HRESULT
//      @flag E_FAIL  | Failed
//      @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT ConnectDB
(
IDBCreateCommand **ppIDBCreateCommand_out,//@param OUT | Pointer to Create Command
LPCSTR pstrDNS,                                 //@param IN  | Login Inforamtion
LPCSTR pstrUserName,                            //@param IN  | Login Inforamtion
LPCSTR pstrPassword,                            //@param IN  | Login Inforamtion
CHttpServerContext* pCtxt//@param IN  | HTML page context
)
{
ULONGcVar;     //Utility Variables
HRESULThr = ResultFromScode(S_OK);

IDBProperties * pIDBProps    = NULL; // IDBInitialize Object
IDBInitialize * pIDBInit    = NULL; // IDBInitialize Object
IDBCreateSession *pIDBCreateSession= NULL;// IDBCreateSession
IDBCreateCommand *pIDBCreateCommand   = NULL;// IDBCreateCommand

DBPROPSETrgPropertySet[1];// Array of property sets
DBPROPrgProperties[3];// Array of property values

 // Strings for OLE DB DO (CHAR & WCHAR)
*ppIDBCreateCommand_out=NULL;

// Set up the Property Buffer
rgPropertySet[0].guidPropertySet=DBPROPSET_DBINIT;
rgPropertySet[0].rgProperties=rgProperties;
rgPropertySet[0].cProperties=NUMELEM(rgProperties);

if( FAILED(hr = SetInitializationData(pstrDNS, pstrUserName, pstrPassword,
rgPropertySet, pCtxt)) )
goto error;

// Initialize OLE
hr = CoInitialize( NULL );
if (FAILED(hr))
{
*pCtxt << "<p>Ole Failed</p>";
goto error;
}

    // Retrieve the task memory allocator
    hr = CoGetMalloc( MEMCTX_TASK, &g_pIMalloc );
    if (FAILED(hr))
{
*pCtxt << "<p>CoGetMalloc Failed</p>";
goto error;
}

    GUID ProviderClsid  = CLSID_MSDASQL;
// Get IDBProperties Pointer
hr = CoCreateInstance( ProviderClsid, NULL,
CLSCTX_INPROC_SERVER, IID_IDBProperties, (LPVOID*)&pIDBProps );
if (FAILED(hr))
{
*pCtxt << "<p>CoCreateInstance Failed</p>";
        *pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

// Set DSO init properties.
hr = pIDBProps->SetProperties(NUMELEM(rgPropertySet), rgPropertySet);
if (FAILED(hr))
{
*pCtxt << "<p>Set properties.</p>";
        *pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

//Get IDBInitialize pointer.
hr = pIDBProps->QueryInterface (IID_IDBInitialize,(LPVOID*)&pIDBInit);
if (FAILED(hr))
{
*pCtxt << "<p>QI for IDBInit failed.</p>";
        *pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

//Initialize DSO.
hr = pIDBInit->Initialize();
if (FAILED(hr))
{
PrintErrorInfo (pIDBInit,IID_IDBInitialize,pCtxt);
*pCtxt<<"<p>Initialize Failed</p>";
*pCtxt<<"<h3>Check your login parameters</h3>";
goto error;
}

// from the DataSource Object get the Session Object
hr = pIDBInit->QueryInterface(IID_IDBCreateSession,
 (void**)&pIDBCreateSession );
    if (FAILED(hr))
{
PrintErrorInfo (pIDBInit,IID_IUnknown,pCtxt);
*pCtxt<<"<p>QI for Session Object failed </p>";
goto error;
}

// From the Session object, get a Command Object
hr = pIDBCreateSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**) ppIDBCreateCommand_out);
if (FAILED(hr))
{
PrintErrorInfo (pIDBCreateSession,IID_IDBCreateCommand,pCtxt);
*pCtxt<<"<p>Create Session failed !</p>";
goto error;
}

error :
//Release resources that are not essential anymore.
for (cVar=0; cVar<rgPropertySet[0].cProperties; cVar++)
VariantClear(&rgPropertySet[0].rgProperties[cVar].vValue);

if( pIDBCreateSession )
pIDBCreateSession->Release();
if( pIDBProps )
pIDBProps->Release();
if( pIDBInit )
pIDBInit->Release();

return hr;
}  //ConnectDB

//-----------------------------------------------------------------------------------
// SetAndExecute
//
// @func HRESULT| SetAndExecute | Sets the query, executes and sets up the ground
//                for data retrieval
//
// @rdesc HRESULT
//      @flag E_FAIL  | Failed
//      @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------

HRESULT SetAndExecute
(
WCHAR *wszSQLCommand,
IDBCreateCommand *pIDBCreateCommand,
IRowset**ppIRowset_out,
DBCOLUMNINFO **ppColumnInfo_out,
WCHAR **ppStringBuffer_out,
ULONG *pcCol,
DBBINDING **pprgBind,
ULONG *pcBind,
HACCESSOR *phAccessor,
ULONG *pcbMaxRowSize,
CHttpServerContext*pCtxt
)
{
HRESULThr = S_OK;

//Column Info Variables
ULONGcCol;
DBCOLUMNINFO *pColumnInfo;
WCHAR*pStringBuffer;                    //It is needed for printing column names
ULONG               cBind;
HACCESSOR           hAccessor= NULL;
IAccessor*pIAccessor= NULL;
IColumnsInfo*pIColumnsInfo= NULL;
ICommandText *      pICommandText       = NULL;   //
IRowset *           pIRowset            = NULL;   //
ULONG               cbMaxRowSize;

 hr = pIDBCreateCommand->CreateCommand(NULL,IID_ICommandText,(IUnknown**)&pICommandText);
if (FAILED(hr))
    {
PrintErrorInfo (pIDBCreateCommand,IID_ICommand,pCtxt);   //?????
*pCtxt << "<h3>ERROR : Create Command Text Failed</h3>";
goto error;
}
else
{
hr = pICommandText->SetCommandText (DBGUID_DBSQL,wszSQLCommand);
if (FAILED (hr))
{
PrintErrorInfo (pIDBCreateCommand,IID_ICommandText,pCtxt);
*pCtxt <<"<h3> ERROR : Could not set command </h3>";
goto error;
}
}

//Execute Query
hr = pICommandText->Execute (
NULL,
IID_IRowset,
NULL,
NULL,
(IUnknown**)&pIRowset);

if (FAILED(hr))
{
PrintErrorInfo (pICommandText,IID_ICommandText,pCtxt);
*pCtxt<<"<h3>ERROR : Execution failed, Check your SQL statement.</h3>";
goto error;
}

//Get column Info
hr = pIRowset->QueryInterface(IID_IColumnsInfo,(void **) &pIColumnsInfo);
if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

hr = pIColumnsInfo->GetColumnInfo(&cCol,&pColumnInfo,&pStringBuffer);
if (FAILED(hr))
{
PrintErrorInfo (pIColumnsInfo,IID_IRowset,pCtxt);
goto error;
}

//Allocate array of Bindings
(*pprgBind) = new DBBINDING [cCol];
if (*pprgBind==NULL)
{
*pCtxt << "<p>Could not allocate required number of columns.\n<p>";
*pCtxt << "<p>Out of memory.\n<p>";
goto error;
}

if (cCol==0)
{
*pCtxt << "<p>No Data (no columns found). \n<p>";
goto error;
}

hr = SetupBindings(cCol,pColumnInfo,*pprgBind,&cBind,&cbMaxRowSize);
if (FAILED(hr))
{
*pCtxt<<"<h3>ERROR : Could not setup bindings</h3>";
goto error;
}

  // Get an accessor for our bindings from the rowset, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
cBind,
*pprgBind,
0,
&hAccessor,
NULL);

if (FAILED(hr))
{
PrintErrorInfo (pIAccessor,IID_IAccessor,pCtxt);
goto error;
}

//Assign out parameters
error :
if( SUCCEEDED(hr) )
{
*ppIRowset_out = pIRowset;
pIRowset = NULL;
*ppColumnInfo_out = pColumnInfo;
pColumnInfo = NULL;
*ppStringBuffer_out = pStringBuffer;
pStringBuffer = NULL;
*pcCol = cCol;
*pcBind = cBind;
*phAccessor = hAccessor;
*pcbMaxRowSize  = cbMaxRowSize;
}
else
{
*ppIRowset_out = NULL;
*ppColumnInfo_out = NULL;
*ppStringBuffer_out = NULL;
*pcCol = 0;
*pcBind = 0;
*phAccessor = NULL;
*pcbMaxRowSize  = 0;
}

if( pStringBuffer )
g_pIMalloc->Free(pStringBuffer);
if( pIRowset )
pIRowset->Release();
if( pIColumnsInfo )
pIColumnsInfo->Release();
if( pIAccessor )
    pIAccessor->Release();
if( pICommandText )
pICommandText->Release();
if( pIDBCreateCommand )
pIDBCreateCommand->Release();

return hr;
} //Set And Execute

//-----------------------------------------------------------------------------------
// GetAndWrite
//
// @func HRESULT| GetAndWrite | Gets data from the rowset.
//
// @rdesc HRESULT
//      @flag E_FAIL  | Failed
//      @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT GetAndWrite
(
IRowset*pIRowset,                   //@param IN | Pointer to set of rows
ULONG   cMaxRowSize,//@param IN | MaxRowSize
    HACCESSOR   hAccessor,//@param IN | Accessor Handle
    DBBINDING*rgBind,  //@param IN | Array of bindings
    ULONGcBind,  //@param IN | Number of bindings
    DBCOLUMNINFO*pColumnInfo,//@param IN | Points to an array of DBCOLUMNIFO structures
    ULONGcCol,//@param IN | Number of columns
CHttpServerContext* pCtxt//@param IN | Output place
    )

{
ULONG cRowsObtained;   // Number of rows obtained
ULONGiRow;   // Row Count
BYTE*pRowData = NULL;   // Memory for Data
HROW rghRows[NUMROWS_CHUNK];    // Row Handles
HROW*pRows = &rghRows[0];   // Pointer to the Row Handles
HRESULT hr=S_OK;   // HRESULT

ULONG   ulCount;                   //Counter
    CHAR    szBuffer[MAXBUFLEN+1];

// Asserts
assert(pIRowset != NULL);
    assert(rgBind != NULL);
    assert(pColumnInfo  != NULL);

// Create a buffer for row data, big enough to hold the biggest row
pRowData = (BYTE *) malloc( cMaxRowSize );
if (!pRowData)
goto error;

    //Initialize table output
*pCtxt << "<CENTER>\n";
    *pCtxt << "<table>\n";
    *pCtxt << "<TABLE BORDER>\n";
*pCtxt << "<caption> <h3> The data you requested: <h3> </caption>\n";

    //Print Column Names
*pCtxt << "<TR>";
    for (ulCount=0;ulCount<cCol;ulCount++)
    {
WideCharToMultiByte(CP_ACP, 0, pColumnInfo[ulCount].pwszName, -1, szBuffer, MAXBUFLEN+1, NULL, NULL);
*pCtxt << "<TH><B>"<<szBuffer<<"</B></TH>";
    }
    *pCtxt << "</TR>";

    // Process all the rows, NUMROWS_CHUNK rows at a time
while (1)
{
hr = pIRowset->GetNextRows(
0,// cbChapter
0,// cRowsToSkip
NUMROWS_CHUNK,// cRowsDesired
&cRowsObtained,// cRowsObtained
&pRows );// filled in w/ row handles

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

// All done, no more rows left to get
if ( cRowsObtained == 0 )
break;

// Loop over rows obtained, getting data for each
for ( iRow=0; iRow < cRowsObtained; iRow++ )
{
hr = pIRowset->GetData(
rghRows[iRow],
hAccessor,
pRowData );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

// Print to web page
*pCtxt <<"<tr>\n";
            DumpRow( rgBind, cBind,pRowData,pCtxt);
            *pCtxt <<"</tr>\n";
} //for

// Release row handles
hr = pIRowset->ReleaseRows( cRowsObtained, rghRows, NULL, NULL, NULL );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

}// end while
*pCtxt << "</table>";   //End of table (no more data)

error:
if( pRowData )
free( pRowData );

return hr;
} //GetAndWrite

//-----------------------------------------------------------------------------------
// FreeEnvironment
//
// @func void| FreeEnvironment | Deallocates resources
//
// @rdesc NONE
//-----------------------------------------------------------------------------------
void FreeEnvironment
(
IRowset*pIRowset,                   //@param IN | Pointer to set of rows
HACCESSOR   hAccessor,//@param IN | Accessor Handle
DBCOLUMNINFO*pColumnInfo, //@param IN | DBCOLUMNINFO array pointer
WCHAR * pStringBuffer                    //@param IN | String Buffer contining the info about columns
)
{
HRESULT hr;
IAccessor*pIAccessor = NULL;// Pointer to an Accessor

// Tell the rowset object it can release the accessor, via IAccessor
if  (pIRowset)
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
else
goto error;

if (FAILED(hr))
goto error;

hr = pIAccessor->ReleaseAccessor( hAccessor,NULL );
if (FAILED(hr))
goto error;

error :
if( pIAccessor )
    pIAccessor->Release();
if( pIRowset )
pIRowset->Release();

if (pStringBuffer!=NULL)
{
g_pIMalloc->Free(pStringBuffer);
pStringBuffer = NULL;
}

if (pColumnInfo!=NULL)
{
g_pIMalloc->Free(pColumnInfo);
pColumnInfo = NULL;
}

if (g_pIMalloc)
g_pIMalloc->Release();

SetErrorInfo(0, NULL);

CoUninitialize();

} //Free Environment

//-----------------------------------------------------------------------------------
//Functions that are not called directly by MainFunction but are used by
//functions described above.
//-----------------------------------------------------------------------------------

HRESULT SetInitializationData
(
LPCSTR pstrDNS,//@param IN  | DNS Name
LPCSTR pstrUserName,//@param IN  | User Name
LPCSTR pstrPassword,//@param IN  | Passwd
DBPROPSET rgPropertySet[],//@param OUT | Array of propery sets (only one)
CHttpServerContext* pCtxt//@param IN  | Web page context
)
{
HRESULT hr = ResultFromScode(S_OK);

    CHARszDBName[MAXBUFLEN+1];// DSN
CHARszUserName[MAXBUFLEN+1];    // User Name
CHARszPassword[MAXBUFLEN+1];        // Password

WCHARwszDBName[MAXBUFLEN+1];// DSN String (WCHAR)
WCHARwszUserName[MAXBUFLEN+1];// User Name  (WCHAR)
WCHARwszPassword[MAXBUFLEN+1];// Password   (WCHAR)

strcpy (szDBName,pstrDNS);
strcpy (szUserName,pstrUserName);
strcpy (szPassword,pstrPassword);

//Reset the out parameter(s)
//*****************************************************************
// Identify Property Set
// Initialize property values
for(ULONG ul=0; ul<rgPropertySet[0].cProperties; ul++)
{
VariantInit(&rgPropertySet[0].rgProperties[ul].vValue);

// Set Common structure values
rgPropertySet[0].rgProperties[ul].dwOptions=DBPROPOPTIONS_REQUIRED;
rgPropertySet[0].rgProperties[ul].colid=DB_NULLID;
rgPropertySet[0].rgProperties[ul].vValue.vt=VT_BSTR;
}

// Fill in Data Source
rgPropertySet[0].rgProperties[0].dwPropertyID=DBPROP_INIT_DATASOURCE;
if (0==MultiByteToWideChar(CP_ACP, 0, szDBName, -1, wszDBName, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[0].vValue.bstrVal=SysAllocString(wszDBName);
if (rgPropertySet[0].rgProperties[0].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #0 (ConnectDB)\n";
 hr = E_OUTOFMEMORY;
 goto error;
}

// Fill in User Name
rgPropertySet[0].rgProperties[1].dwPropertyID=DBPROP_AUTH_USERID;
if (0==MultiByteToWideChar(CP_ACP, 0, szUserName, -1, wszUserName, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[1].vValue.bstrVal=SysAllocString(wszUserName);
if (rgPropertySet[0].rgProperties[1].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #1 (ConnectDB)\n";
 hr = E_OUTOFMEMORY;
 goto error;
}

// Fill in Password
rgPropertySet[0].rgProperties[2].dwPropertyID=DBPROP_AUTH_PASSWORD;
if (0==MultiByteToWideChar(CP_ACP, 0, szPassword, -1, wszPassword, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[2].vValue.bstrVal=SysAllocString(wszPassword);
if (rgPropertySet[0].rgProperties[2].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #2(ConnectDB)\n";
 hr = E_OUTOFMEMORY;
 goto error;
}

error :
return hr;
} //SetInitializationData

//-----------------------------------------------------------------------------------
// PrintErrorInfo
//
// @func void| PrintErrorInfo | Uses error collection data to issue an error.
//
// @rdesc HRESULT
//-----------------------------------------------------------------------------------
void PrintErrorInfo
      (
   IUnknown * pBadObject,                   //@param IN | Pointer to the object where error happened
   REFIID IID_BadIntereface,                  //@param IN | Interface that caused the error
   CHttpServerContext* pCtxt//@param IN | Output place
  )
{
HRESULT             hr = S_OK;
IErrorInfo *        pErrorInfo= NULL;
IErrorInfo *    pErrorInfoRec= NULL;
IErrorRecords *     pErrorRecords= NULL;
ISupportErrorInfo * pSupportErrorInfo= NULL;
ULONG               i,ulNumErrorRecs;
ERRORINFO           ErrorInfo;
BSTRbstrDescriptionOfError = NULL;
BSTRbstrSourceOfError = NULL;

CHAR                 szBuffer [MAXBUFLEN+1];  //Error Info Buffer
DWORD                MYLOCALEID;

MYLOCALEID = GetSystemDefaultLCID();

hr = pBadObject->QueryInterface (IID_ISupportErrorInfo,
            (LPVOID FAR*)&pSupportErrorInfo);

if (SUCCEEDED(hr))
{
hr = pSupportErrorInfo->InterfaceSupportsErrorInfo(IID_BadIntereface);
if( hr == S_OK )
{
//Get Error Object. Return if no object Exists
GetErrorInfo (0,&pErrorInfo);
if( !pErrorInfo )
goto error;

//Get the IErrorRecord interface and get the count of error recs.
pErrorInfo->QueryInterface (IID_IErrorRecords,(LPVOID FAR*)&pErrorRecords);
pErrorRecords->GetRecordCount (&ulNumErrorRecs);

//Go through and print messages
for (i=0;i<ulNumErrorRecs;i++)
{
pErrorRecords->GetBasicErrorInfo(i,&ErrorInfo);
pErrorRecords->GetErrorInfo (i,MYLOCALEID,&pErrorInfoRec);

hr = pErrorInfoRec->GetDescription (&bstrDescriptionOfError);
if (FAILED(hr))
{
*pCtxt << "pErrorInfoRec->GetDescription FAILED!!! \n";
goto error;
}

hr = pErrorInfoRec->GetSource (&bstrSourceOfError);
if (FAILED(hr))
{
*pCtxt << "pErrorInfoRec->GetSource FAILED!!! \n";
goto error;
}

if (0==WideCharToMultiByte(CP_ACP, 0, bstrDescriptionOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL))
{
*pCtxt << "WideChar -> Multi Conversion #1 failed. ERROR in PrintErrorInfo. \n";
goto error;
}

*pCtxt << "<p> Error Description : </p>";
*pCtxt <<"<p>" << szBuffer<< "</p>\n";
if (0==WideCharToMultiByte(CP_ACP, 0, bstrSourceOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL))
{
*pCtxt << "WideChar -> Multi Conversion  #2 failed. ERROR in PrintErrorInfo. \n";
goto error;
}

  *pCtxt << "<p> Error Source : </p>";
*pCtxt <<"<p>" << szBuffer<< "</p>\n";

if( bstrDescriptionOfError )
{
SysFreeString (bstrDescriptionOfError);
bstrDescriptionOfError = NULL;
}
if( bstrSourceOfError )
{
SysFreeString (bstrSourceOfError);
bstrSourceOfError = NULL;
}

pErrorInfoRec->Release();
pErrorInfoRec = NULL;
}

pErrorInfo->Release();
pErrorInfo = NULL;

pErrorRecords->Release();
pErrorRecords = NULL;
} //S_OK
    } //if (SUCCEEDED)

error :
if( bstrDescriptionOfError )
SysFreeString (bstrDescriptionOfError);
if( bstrSourceOfError )
SysFreeString (bstrSourceOfError);
if( pErrorInfo )
pErrorInfo->Release();
if( pErrorRecords )
pErrorRecords->Release();
if( pErrorInfoRec )
pErrorInfoRec->Release();
if( pSupportErrorInfo )
pSupportErrorInfo->Release();
} //PrintErrorInfo

//-----------------------------------------------------------------------------------
// SetupBindings
//
// @func HRESULT| SetupBindings  | Recycle
//
// @rdesc HRESULT
//      @flag E_FAIL  | Failed
//      @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*pcBind_out,
ULONG*pcMaxRowSize_out
)
{
ULONG dwOffset;// Length of a Row
ULONG iCol;// Column Count
ULONG iBind;// Binding Index

// Asserts
    assert(pColumnInfo!= NULL);
    assert(rgBind_out != NULL);
    assert(pcBind_out != NULL);
    assert(pcMaxRowSize_out != NULL);

   // Create bindings.
// Bind everything as a string just to keep things simple.
dwOffset = 0;
iBind=0;
for (iCol=0; iCol < cCol; iCol++)
{
// Binding Structure
rgBind_out[iBind].dwPart    = DBPART_VALUE | DBPART_LENGTH |
  DBPART_STATUS;
rgBind_out[iBind].eParamIO  = DBPARAMIO_OUTPUT;
rgBind_out[iBind].iOrdinal  = pColumnInfo[iCol].iOrdinal;
rgBind_out[iBind].wType     = DBTYPE_STR;
rgBind_out[iBind].pTypeInfo = NULL;
rgBind_out[iBind].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBind_out[iBind].obValue   = dwOffset + offsetof(COLUMNDATA,bData);
rgBind_out[iBind].obLength  = dwOffset + offsetof(COLUMNDATA,dwLength);
rgBind_out[iBind].obStatus  = dwOffset + offsetof(COLUMNDATA,wStatus);
rgBind_out[iBind].cbMaxLen  = pColumnInfo[iCol].wType == DBTYPE_STR ?
  pColumnInfo[iCol].ulColumnSize + 1 : DEFAULT_CBMAXLENGTH;

// LONG DATA hack
if(rgBind_out[iBind].cbMaxLen > MAX_ROW_SIZE)
rgBind_out[iBind].cbMaxLen = MAX_ROW_SIZE;

dwOffset += rgBind_out[iBind].cbMaxLen + offsetof( COLUMNDATA, bData );
dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
iBind++;
}

// Return Values
*pcBind_out   = iBind;
*pcMaxRowSize_out = dwOffset;

return S_OK;
}

//-----------------------------------------------------------------------------------
// DumpRow
//
// @func void | DumpRow | Puts data onto the webpage
//
//-----------------------------------------------------------------------------------

void DumpRow
(
DBBINDING* rgBind,                    //@param  IN | Column bindings
    ULONGcBind,                     //@param  IN | Count of bindings
    BYTE* pData,                     //@param  IN | Data Pointer
CHttpServerContext* pCtxt              //@param  IN | Web Server context
)
{
ULONG iBind;// Binding Count
    COLUMNDATA*pColumn;// Data Structure
    CHARszDispBuffer[MAXBUFLEN+1];//Display Buffer

// Print each column we're bound to.
szDispBuffer[0] = '\0';
for (iBind=0; iBind < cBind; iBind++)
{
pColumn = (COLUMNDATA *) (pData + rgBind[iBind].obLength);

// Check Status for NULL / OK / CANTCONVERT.
switch (pColumn->wStatus)
{
case DBSTATUS_S_ISNULL:
strcpy(szDispBuffer, (LPSTR)"<b>NULL</b>\n");
break;
case DBSTATUS_S_OK:
            pColumn->bData[MAXBUFLEN] = '\0';         //Long data problem solved??!
            if (NULL==lstrcpy(szDispBuffer, (LPSTR)pColumn->bData))
*pCtxt << "lstrcpy ERROR. ";
break;
case DBSTATUS_E_CANTCONVERTVALUE:
strcpy(szDispBuffer,(LPSTR)"<b>can't convert</b>\n");
break;
default:
strcpy(szDispBuffer,(LPSTR)"<b>unknown status</b>\n");
break;
}

*pCtxt<<"<td>"<<szDispBuffer<<"</td>\n";
}

}//DumpRow


DOCLIENT.H

//--------------------------------------------------------------------
// Microsoft OLE DB ISAPI Sample
// Copyight(c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DOCLIENT.H | Headers for DOCLIENT.CPP
//
// @rev 1 | 06-11-96 | Created
//

#include <afxisapi.h>
//Constants
#define MAXBUFLEN256//display buffer size
#define MAXDATALEN25//maximum data length per column
#define NUMROWS_CHUNK20                    // Number of Rows to Grab at a Time
#define DEFAULT_CBMAXLENGTH40                    // cbMaxLength for binding
#define COLUMN_ALIGNVAL8
#define MAX_ROW_SIZE1000
#define NUMBER_OF_PROPERTY_SETS1

#define ROUND_UP( Size, Amount ) (((DWORD)(Size) +  ((Amount) - 1)) & ~((Amount) - 1))
//Data Structures ****************************************************************
struct COLUMNDATA
{
DWORDdwLength;// length of data (not space allocated)
DBSTATUSwStatus;// status of column
SHORT       wReserved;  // padding
BYTEbData[1];// data here and beyond
};

//Exposed Functions ****************************************************************
HRESULT GetAndWrite
(
IRowset*pIRowset,
ULONG   cMaxRowSize,
    HACCESSOR   hAccessor,
    DBBINDING*rgBind,
    ULONGcBind,
    DBCOLUMNINFO*pColumnInfo,
    ULONGcCol,
CHttpServerContext* pCtxt
);

HRESULT ConnectDB
(
IDBCreateCommand **ppIDBCreateCommand_out,    //@param |
LPCSTR pstrDNS,                                //@param | Login Inforamtion
LPCSTR pstrUserName,                           //@param | Login Inforamtion
LPCSTR pstrPassword,                           //@param | Login Inforamtion
CHttpServerContext* pCtxt   //@param|
);

HRESULT SetAndExecute
(
WCHAR * wszSQLCommand,                   //@param  IN  | SQL command
IDBCreateCommand * pIDBCreateCommand,    //@param  IN  |
IRowset**  ppIRowset_out,                //@param  OUT | Pointer to set of rows
DBCOLUMNINFO ** ppColumnInfo_out,        //@param  OUT | array of DBCOLUMNINFO structures
WCHAR ** ppStringBuffer_out,
ULONG * pcCol,                           //@param  OUT | Number of columns
DBBINDING ** rgBind,                     //@param
ULONG * pcBind,                          //@param
HACCESSOR * phAccessor,                  //@param
ULONG * pcbMaxRowSize,                   //@param
CHttpServerContext* pCtxt     //@param  IN | Server Context
);

 void FreeEnvironment
(
IRowset*pIRowset,                //@param IN | Pointer to set of rows
HACCESSOR   hAccessor, //@param IN | Accessor Handle
DBCOLUMNINFO*pColumnInfo,  //@param IN | DBCOLUMNINFO array pointer
WCHAR * pStringBuffer                    //@param IN | String Buffer contining the info about columns
);

//Internal functions ************************************************************
void PrintErrorInfo
(
IUnknown * pBadObject,                  //@param IN | Pointer to the object where error happened
REFIID IID_BadIntereface,          //@param IN | Interface that caused the error
CHttpServerContext* pCtxt//@param IN | Output place
);

HRESULT SetInitializationData
(
LPCSTR pstrDNS,//@param IN  | DNS Name
LPCSTR pstrUserName,//@param IN  | User Name
LPCSTR pstrPassword,//@param IN  | Passwd
DBPROPSET rgPropertySet[],//@param OUT | Array of propery sets (only one)
CHttpServerContext* pCtxt//@param IN  | Web page context
);

HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*pcBind_out,
ULONG*pcMaxRowSize_out
);

HRESULT CleanupRowset
(
IRowset*pIRowset,
HACCESSOR hAccessor
);

void DumpRow
(
    DBBINDING* rgBind,
    ULONGcBind,
    BYTE* pData,
CHttpServerContext* pCtxt
);


ISAPIQRY.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB ISAPI Sample
// Copyight(c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ISAPIQRY.CPP | Internet Server Extention. Created by VC++ 4.1
//             ISAPI extention wizard. This class is used by the MFC file
//             ISAPI.cpp. Standard IS calls are mapped to this class.
//
// @rev 1 | 06-11-96 | Created
//

//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
//#include <afx.h>
#include <afxwin.h>
#include <afxisapi.h>
#include "resource.h"
#include "ISAPIQRY.h"

//Headers needed to run OLE DB DO
//Note : #define DBINITCONSTANTS is not present
#include <afxole.h>
#include <iostream.h>
#include <assert.h>
#include <oledb.h>
#include <oledberr.h>
#include "DOclient.h"   //Data Objects Client Header File

//-----------------------------------------------------------------------------
// Externals
//-----------------------------------------------------------------------------

extern  g_pIMalloc;                             //Global memory allocator

TCHAR szTitle[] = _T("OLE DB - Internet Demo (Results)");

// command-parsing map
BEGIN_PARSE_MAP(CIsapiqryExtension, CHttpServer)
    ON_PARSE_COMMAND(MainFunction, CIsapiqryExtension, ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS ("DSN=~ UserName=~ Password=~ SQL=~")
DEFAULT_PARSE_COMMAND(MainFunction, CIsapiqryExtension)
END_PARSE_MAP(CIsapiqryExtension)

CIsapiqryExtension theExtension;

//-----------------------------------------------------------------------------
// CIsapiqryExtension implementation

CIsapiqryExtension::CIsapiqryExtension()
{

}

CIsapiqryExtension::~CIsapiqryExtension()
{
}

LPCTSTR CIsapiqryExtension::GetTitle (void) const
{
   return szTitle;
}

BOOL CIsapiqryExtension::GetExtensionVersion(HSE_VERSION_INFO* pVer)
{
// Call default implementation for initialization
CHttpServer::GetExtensionVersion(pVer);

// Load description string
TCHAR sz[HSE_MAX_EXT_DLL_NAME_LEN+1];
ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
IDS_SERVER, sz, HSE_MAX_EXT_DLL_NAME_LEN));
_tcscpy(pVer->lpszExtensionDesc, sz);
return TRUE;
}

//-----------------------------------------------------------------------------------
// CFirstExtension::MainFunction
//
// @mfunc void| MainFunction | All the code functions are called from here.
//                1. Initialize  DataObjects
//                2. Set the SQL command
//                3. etc.
//
// @rdesc NONE
//
//-----------------------------------------------------------------------------------
void CIsapiqryExtension::MainFunction
      (
   CHttpServerContext* pCtxt,     //@param IN | Place where output is directed
   LPCSTR pstrDSN,                //@param IN | DSN connection name
   LPCSTR pstrUserName,           //@param IN | User name
   LPCSTR pstrPassword,           //@param IN | Passwd
   LPCSTR pstrSQLCommand          //@param IN | SQL Command to executed
  )
{

//************************************ BEGIN OLE DB DO Variables ***********************************************
    WCHAR  wszSQLCommand[MAXBUFLEN+1];
    ULONG cCol;

   //Bindings and accessor
    DBBINDING *         prgBind=NULL;
    ULONG               cBind;
    HACCESSOR           hAccessor=NULL;
    ULONG               cbMaxRowSize;

  //Column Info
    DBCOLUMNINFO *      pColumnInfo=NULL;
WCHAR *             pStringBuffer=NULL;

    IRowset*            pIRowset            = NULL;
    IDBCreateCommand *pIDBCreateCommand   = NULL;// IDBCreateCommand

//***************************** END OLE DB DO Variables **********************************

StartContent(pCtxt);
//********************************** OLE DB Calls ****************************************
WriteTitle(pCtxt);

    if( FAILED(ConnectDB(&pIDBCreateCommand, pstrDSN, pstrUserName, pstrPassword, pCtxt)) )
goto error;

    if (0==MultiByteToWideChar (CP_ACP,0,pstrSQLCommand,-1,wszSQLCommand,MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar Failed!!!\n";
goto error;
};

if( FAILED(SetAndExecute(wszSQLCommand, pIDBCreateCommand, &pIRowset, &pColumnInfo, &pStringBuffer,
&cCol, &prgBind, &cBind, &hAccessor, &cbMaxRowSize, pCtxt)) )
goto error;

    if( FAILED(GetAndWrite(pIRowset, cbMaxRowSize, hAccessor, prgBind, cBind,
pColumnInfo, cCol, pCtxt)) )
goto error;

error :
FreeEnvironment(pIRowset, hAccessor, pColumnInfo, pStringBuffer);
delete [] prgBind;
//********************************** END OLE DB Calls ************************************
EndContent(pCtxt);

}//MainFunction


ISAPIQRY.H

//--------------------------------------------------------------------
// Microsoft OLE DB ISAPI Sample
// Copyight(c) 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ISAPIQRY.H | header file
//
// @rev 1 | 06-11-96 | Created

class CIsapiqryExtension : public CHttpServer
{
public:
CIsapiqryExtension();
~CIsapiqryExtension();

BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer);
    LPCTSTR GetTitle(void) const;

    void MainFunction
      (
   CHttpServerContext* pCtxt,     //@param IN | Place where output is directed
   LPCSTR pstrDSN,                //@param IN | DSN connection name
   LPCSTR pstrUserName,           //@param IN | User name
   LPCSTR pstrPassword,           //@param IN | Passwd
   LPCSTR pstrSQLCommand          //@param IN | SQL Command to executed
  );

    DECLARE_PARSE_MAP()
};


ISAPIVER.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB Version File
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module version include file
//
//

// Constants -----------------------------------------------------------------
#ifndef _AXVER_H_
#define _AXVER_H_

#define VER_FILEVERSION  01,01,24,17
#define VER_FILEVERSION_STR  " 1.01.2417\0"
#define VER_PRODUCTVERSION  01,01,24,17
#define VER_PRODUCTVERSION_STR  " 1.01.2417\0"

#define VER_FILEFLAGSMASK(VS_FF_DEBUG | VS_FF_PRERELEASE)
#ifdef DEBUG
#define VER_FILEFLAGS(VS_FF_DEBUG)
#else
#define VER_FILEFLAGS(0)
#endif

#define VER_FILEOSVOS_NT_WINDOWS32

#define VER_COMPANYNAME_STR"Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR"Microsoft OLE DB\0"
#define VER_LEGALCOPYRIGHT_STR"Copyright \251 Microsoft Corporation 1994-1996\0"

// Resource DLL Version Resource ID
#define IDS_RESDLL_VER1

#endif
//--------------------


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by isapiqry.rc
//

#define IDS_SERVER        102

#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_CONTROL_VALUE         103
#define _APS_NEXT_SYMED_VALUE           103
#define _APS_NEXT_COMMAND_VALUE         32768


MAIN.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB QURYDEMO Sample
// Copyright 1994 - 1998 Microsoft Corporation.
//
// @doc
//
// @module MAIN.CPP
//
//-----------------------------------------------------------------------------------
/*
PROGRAM: QURYDEMO
========

PURPOSE:
========
demonstrates a simple MDI application that allows a user to
simultaneously connect to multiple hetrogeneous databases
and perform SQL queries to get results.

FUNCTIONS:
==========
WinMain() - main routine
MainWndProc() - processes Main Application Window messages
MDIChildProc() - processes MDI child window messages
ToolbarProc() - processes tool bar messages
StatusbarProc() - processes Status bar messages
ConnectDlgProc() - processes Connection Dialog box messages
DisconnectDlgProc() - processes Disconnect Dialog box messages
AboutDlgProc() - processes messages for About dialog box
MDIChildDlgProc() - process messages for dummy child dialog box in MDI window
DrawBitmap() - draws bitmaps for toolbuttons

COMMENTS:
=========
Created by Microsoft Corporation.
*/

#include <stdio.h>
#include <string.h>
#include <time.h>

#include <windows.h>
#include <windowsx.h>

#ifdef CTL3D
#include "ctl3d.h"
#endif

#include "oledb.h"
#include "oledberr.h"

#include "qurydemo.h"

// Globals
HWNDhWndFrame;      // Main Frame Window handle
HWNDhWndCrsrList;   // hdbc(s) combobox on the tool bar
HWNDhWndStmtList;   // hstmt(s) combobox on the tool bar
HWNDhWndMDIClient;  // MDI Client window handle
HWNDhWndActiveChild;// Current active MDI Child window
HINSTANCEhAppInstance;   // Application instance

intiTimex;         // Time box (on the status bar) width
intiDatex;         // Date box (on the status bar) width
HWNDhWndToolbar;    // Toolbar window handle
HWNDhWndStatusbar;  // status bar handle
WORDwStatusText;    // Status text state
RECTrectStatusText; // Text box on the status bar

/*
FUNCTION: WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
COMMENTS: Application Entry Routine.
  Register Classes. Create Main Window and MDI Child Window.
  Process Main Message Loop.
*/
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSGmsg;//msg structure
WNDCLASSwc;//class structure
    HICONhMainIcon;//App Icon
    HICONhMDIChildIcon;//MDI Child Icon
HWNDhWndMDIChild;//temp MDI Child Window Handle
charszBuffer[MAXBUFLEN+1];//temp string buffer to check class name
BOOLbDialogMessage = FALSE;//temp boolean to check dilogbox msgs
HACCELhAccel;//accelerator table handle

hAppInstance = hInstance;

// Check if application is already running, if
// so make it active and bring it in focus
if (hWndFrame = FindWindow(OLEDBFRAMECLASS, NULL))
{
hWndFrame = GetLastActivePopup(hWndFrame);
if (IsIconic(hWndFrame))
OpenIcon(hWndFrame);
else
BringWindowToTop(hWndFrame);
ACTIVATEWINDOW(hWndFrame);
return (FALSE);
}

// Initialize OLEDB Interface
if (!InitEnvironment())
{
MessageBox(hWndFrame, INITERROR, EXECERROR, MB_OK|MB_ICONHAND);
return (FALSE);
}

// Register window classes for the application - Main Window Class
wc.style= 0;
wc.lpfnWndProc= MainWndProc;
wc.cbClsExtra= 0;
wc.cbWndExtra= 0;
wc.hInstance= hAppInstance;
wc.hIcon= hMainIcon = LoadIcon(hAppInstance, APPICON);
wc.hCursor= LoadCursor((HINSTANCE)NULL, IDC_ARROW);
wc.hbrBackground= (HBRUSH)(COLOR_APPWORKSPACE+1);
wc.lpszMenuName= QURYDEMOMENU;
wc.lpszClassName= OLEDBFRAMECLASS;

if (!RegisterClass(&wc))
{
MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
return (FALSE);
}

// register Toolbar Class
wc.hIcon= (HICON)NULL;
wc.lpszMenuName=  NULL;
wc.lpfnWndProc= ToolbarProc;
wc.lpszClassName= OLEDBTOOLCLASS;

if (!RegisterClass(&wc))
{
MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
return (FALSE);
}

// register Statusbar Class
wc.lpfnWndProc= StatusbarProc;
wc.lpszClassName= OLEDBSTATUSCLASS;

if (!RegisterClass(&wc))
{
MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
return (FALSE);
}

// register MDI Child Window Class
wc.hIcon = hMDIChildIcon = LoadIcon(hAppInstance, QUERYWINDOWICON);
wc.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);
wc.cbWndExtra= CBWNDEXTRA;
wc.lpszClassName= OLEDBMDICLASS;
wc.lpfnWndProc= MDIChildProc;

if (!RegisterClass(&wc))
{
MessageBox(hWndFrame, CLASSERROR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
return (FALSE);
}

// create Main window and the MDI Client window
if (!(hWndFrame = CreateWindow(OLEDBFRAMECLASS, APPTITLE, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND)NULL, (HMENU)NULL, hAppInstance, NULL)))
{
MessageBox(hWndFrame, CREATEMAINERR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
return (FALSE);
}

ShowWindow(hWndFrame, nCmdShow);
UpdateWindow(hWndFrame);

#ifdef CTL3D
// register 3D controls
Ctl3dRegister(hAppInstance);
Ctl3dAutoSubclass(hAppInstance);
#endif

// load accelerators
hAccel = LoadAccelerators(hAppInstance, APPACCELERATOR);

// acquire and dispatch messages until a WM_QUIT message is received
while (GetMessage(&msg, (HWND)NULL, 0, 0))
{

// check for App accelerators
if (TranslateAccelerator(hWndFrame, hAccel, &msg))
continue;

// check for MDI accelerators
if (TranslateMDISysAccel(hWndMDIClient, &msg))
continue;

        // each MDI Child has a modeless dialog in its client area
        // to provide tab controls. Check for Modeless Dialog msgs.
for (hWndMDIChild = GetWindow(hWndMDIClient, GW_CHILD); hWndMDIChild; hWndMDIChild = GetWindow(hWndMDIChild, GW_HWNDNEXT))
{
GetClassName(hWndMDIChild, szBuffer, MAXBUFLEN);
if (strcmp(szBuffer, OLEDBMDICLASS))
continue;
if (IsDialogMessage((HWND)GetWindowLong(hWndMDIChild, GWLAPP_HDLG), &msg))
{
bDialogMessage = TRUE;
break;
}
}

if (bDialogMessage)
{
bDialogMessage = FALSE;
continue;
        }

        // if the message does not need special processing, dispatch it
TranslateMessage(&msg);
DispatchMessage(&msg);
}

#ifdef CTL3D
// unregister 3D controls
Ctl3dUnregister(hAppInstance);
#endif

// Free memory used by OLEDB interface
FreeEnvironment();

// free Icon resources
if (hMainIcon)
DestroyIcon(hMainIcon);

if (hMDIChildIcon)
DestroyIcon(hMDIChildIcon);

return (msg.wParam);
}

/*
FUNCTION: MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Windows Callback procedure to handle Window messages.
          Menu Commands and System Command messages are handled by
          this main window.
*/
long CALLBACK MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
static intiToolY;         //remember Toolbar height for resize
static intiStatusY;       //remember Statusbar height for resize

case WM_CREATE:
        {
CLIENTCREATESTRUCTccs;//MDIclient window structure
HDChDC;//Device Context handle
SIZEsizeBar;//Size of a text bar
RECTrectCombo;  //Size of combo box

// Create child windows
                //1. combobox to display connections - DSN, SESSION
                //2. combobox to display HSTMT on current SESSION
                //3. Toolbar to put toolbuttons
                //4. Statusbat to display current action, date and time
                //5. MDI Client Window to process MDI children

hWndCrsrList = CreateWindow(COMBOBOXCLASS, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_VSCROLL | CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL, 5, 2, 180, 150, hWnd,
(HMENU)IDW_CRSRLIST, hAppInstance, NULL);

hWndStmtList = CreateWindow(COMBOBOXCLASS, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_VSCROLL | CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL, 190, 2, 150, 150, hWnd,
(HMENU)IDW_COMMANDLIST, hAppInstance, NULL);

hWndToolbar = CreateWindow(OLEDBTOOLCLASS, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
0, 0, 0, 0, hWnd, (HMENU)IDW_TOOLBAR, hAppInstance, NULL);

hWndStatusbar = CreateWindow(OLEDBSTATUSCLASS, NULL, WS_CHILD | WS_VISIBLE |
WS_CLIPSIBLINGS, 0, 0, 0, 0, hWnd, (HMENU)IDW_STATUSBAR, hAppInstance, NULL);

ccs.hWindowMenu = GetSubMenu(GetMenu(hWnd), WINDOWMENUPLACE);
ccs.idFirstChild = IDM_MDICHILD;

hWndMDIClient = CreateWindow(MDICLIENTCLASS, NULL, WS_CHILD | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE, 0, 0, 0, 0, hWnd,
(HMENU)IDW_MDICLIENT, hAppInstance, (LPSTR)&ccs);

// check to see if any of the above window creation failed
if (!hWndCrsrList || !hWndStmtList || !hWndToolbar || !hWndStatusbar || !hWndMDIClient)
{
MessageBox(hWnd, CREATEMAINERR, EXECERROR, MB_OK|MB_ICONHAND|MB_TASKMODAL);
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}

// calculate proper text height for tool and status bars
GetWindowRect(hWndStmtList, &rectCombo);
iToolY = rectCombo.bottom - rectCombo.top + TOOLBARMARGINY;

if (hDC = GetDC(hWndStatusbar))
{
GetTextExtentPoint(hDC, ALPHABETS, strlen(ALPHABETS), &sizeBar);
ReleaseDC(hWndStatusbar, hDC);
iStatusY = sizeBar.cy + STATUSBARMARGINY;
}
else
{
iStatusY = 0;
DestroyWindow(hWndStatusbar);
}

break;
}

// Limit minimum size of the main window
case WM_GETMINMAXINFO:

((MINMAXINFO FAR*)lParam)->ptMinTrackSize.x =
max(MINWIDTH, rectStatusText.right-rectStatusText.left+iTimex+iDatex+14);
((MINMAXINFO FAR*)lParam)->ptMinTrackSize.y = MINHEIGHT;
break;

// Resize children
case WM_SIZE:
        {
WORDwWidth = LOWORD(lParam);//width of rectangle
WORDwHeight = HIWORD(lParam);//height of rectangle

MoveWindow(hWndToolbar, 0, 0, wWidth, iToolY, TRUE);
MoveWindow(hWndStatusbar, 0, wHeight-iStatusY, wWidth, iStatusY, TRUE);
InvalidateRect(hWndStatusbar, NULL, TRUE);
MoveWindow(hWndMDIClient, 0, iToolY, wWidth, wHeight-iStatusY-iToolY, TRUE);
break;
        }

#ifdef CTL3D
// Inform 3D controls of color change
case WM_SYSCOLORCHANGE:

Ctl3dColorChange();
break;
#endif
// Close comboboxes if dropped down
case WM_SYSCOMMAND:

SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
return (DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam));

// Initialize popup menus
case WM_INITMENUPOPUP:
        {
        intiMenuId;//Menu ID being processed
intnItems;         //# of menu items
intnPos;           //Menu Position

        // Ignore the msg if it is for a system menu
if (HIWORD(lParam))
break;

// Go through the menu items for current popup menu
// and enable/disable menu item, if required
nItems = GetMenuItemCount((HMENU)wParam);
for (nPos = 0; nPos < nItems; nPos++)
switch (iMenuId = GetMenuItemID((HMENU)wParam, nPos))
{
case IDM_DISCONNECT:
case IDM_NEW:

EnableMenuItem( (HMENU)wParam, iMenuId, MF_BYCOMMAND|
((SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) > 0)?
MF_ENABLED:MF_GRAYED) );
break;

case IDM_QUERY:
case IDM_TILE:
case IDM_CASCADE:
case IDM_ICONS:
case IDM_CLOSEALL:

case IDM_CATALOGS:
case IDM_COLUMNS:
case IDM_PROCEDURE_PARAMETERS:
case IDM_PROCEDURES:
case IDM_SCHEMATA:
case IDM_TABLES:
case IDM_TYPES:

EnableMenuItem( (HMENU)wParam, iMenuId, MF_BYCOMMAND|
(GetWindow(hWndMDIClient, GW_CHILD)?
MF_ENABLED:MF_GRAYED) );
break;

default:
break;
}
break;
}

// Update status bar to reflect menu selection
case WM_MENUSELECT:
{
            intiMenuFlag; //Check menu type
            HMENUhMenu;           //Menu Handle
            charszMenuName[MAXBUFLEN+1]; //Menu Name

            // store Menuitem ID as a state value for text display
wStatusText = GET_WM_MENUSELECT_CMD(wParam, lParam);

// process popup menus ie non menuitem selections
iMenuFlag = GET_WM_MENUSELECT_FLAGS(wParam, lParam);

// if the selected menu is a system popup menu
        // else if the selected menu is a popup menu check menu names
        // OR check if it is a control popup menu of maximized MDI Child window
        if (wStatusText && (iMenuFlag & MF_SYSMENU) && (iMenuFlag & MF_POPUP))
wStatusText = IDM_POPUPAPPSYS;
else if (wStatusText && (iMenuFlag & MF_POPUP))
{
hMenu = (HMENU)wStatusText;
GetMenuString(hMenu, 0, szMenuName, MAXBUFLEN, MF_BYPOSITION);
if (!strcmp(szMenuName, MENUITEMCONNECT))
wStatusText = IDM_POPUPLOGIN;
else if (!strcmp(szMenuName, MENUITEMQUERY))
wStatusText = IDM_POPUPQUERY;
else if (!strcmp(szMenuName, MENUITEMTILE))
wStatusText = IDM_POPUPSCHEMA;
else if (!strcmp(szMenuName, MENUITEMTYPES))
wStatusText = IDM_POPUPWINDOW;
else if (!strcmp(szMenuName, MENUITEMAPPHELP))
wStatusText = IDM_POPUPHELP;
else if (GetMenuString(hMenu, SC_NEXTWINDOW, szMenuName, MAXBUFLEN, MF_BYCOMMAND)>0)
wStatusText = IDM_POPUPMDISYS;
            else
                wStatusText = 0;
        }

// invalidate status bar for repaint
InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
break;
}

// Process menu commands
case WM_COMMAND:

switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDM_INITIALIZE: // bring up connect dialog & do connect processing
DialogBox(hAppInstance, INITDIALOG, hWnd,ConnectDlgProc);
break;

case IDM_DISCONNECT: // bringup disconnect dlg and do disconnects

DialogBox(hAppInstance, DISCONNECTDIALOG, hWnd,DisconnectDlgProc);
break;

case IDM_QUERY: // process execute query request

ExecuteQuery();
break;

case IDM_CATALOGS: // Execute DBSCHEMA_CATALOGS request

GetSchemaRowset(DBSCHEMA_CATALOGS);
break;

case IDM_COLUMNS: // Execute DBSCHEMA_COLUMNS request

GetSchemaRowset(DBSCHEMA_COLUMNS);
break;

case IDM_PROCEDURE_PARAMETERS: // Execute DBSCHEMA_PROCEDURE_PARAMETERS request

GetSchemaRowset(DBSCHEMA_PROCEDURE_PARAMETERS);
break;

case IDM_PROCEDURES: // Execute DBSCHEMA_PROCEDURES request

GetSchemaRowset(DBSCHEMA_PROCEDURES);
break;

case IDM_SCHEMATA: // Execute DBSCHEMA_SCHEMATA request

GetSchemaRowset(DBSCHEMA_SCHEMATA);
break;

case IDM_TABLES: // Execute DBSCHEMA_TABLES request

GetSchemaRowset(DBSCHEMA_TABLES);
break;

case IDM_TYPES: // Execute DBSCHEMA_TYPES request

GetSchemaRowset(DBSCHEMA_PROVIDER_TYPES);
break;

case IDM_EXIT: // process exit request

SendMessage(hWndFrame, WM_CLOSE, 0, 0);
break;

case IDM_NEW: // create a new query window on current connect

NewICommandWindow();
break;

case IDM_TILE: // let MDI Client tile the MDI children

SendMessage(hWndMDIClient, WM_MDITILE, 0, 0);
break;

case IDM_CASCADE: // let MDI Client cascade MDI children

SendMessage(hWndMDIClient, WM_MDICASCADE, 0, 0);
break;

case IDM_ICONS: // let MDI Client arrange iconic MDI children

SendMessage(hWndMDIClient, WM_MDIICONARRANGE, 0, 0);
break;

case IDM_CLOSEALL: // Close all open windows and free hstmts
            {
HWNDhWndTemp;//temp window handle

// hide MDI Client Windows to avoid repaints
ShowWindow(hWndMDIClient,SW_HIDE);
while (hWndTemp = GetWindow(hWndMDIClient, GW_CHILD))
SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndTemp, 0);
ShowWindow(hWndMDIClient, SW_SHOW);
break;
}

case IDM_ABOUT: // bringup About dialog

DialogBox(hAppInstance, ABOUTDIALOG, hWnd, AboutDlgProc);
break;

case IDW_CRSRLIST: // change current cursor ?

if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
ChangeCurrentCursor(GET_WM_COMMAND_HWND(wParam, lParam));
else if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_KILLFOCUS)
PostMessage(GET_WM_COMMAND_HWND(wParam, lParam), CB_SHOWDROPDOWN,
(WPARAM)FALSE, 0);
else
DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
break;

case IDW_COMMANDLIST: // change current Command ?

if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
ChangeCurrentICommand(GET_WM_COMMAND_HWND(wParam, lParam));
else if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_KILLFOCUS)
PostMessage(GET_WM_COMMAND_HWND(wParam, lParam), CB_SHOWDROPDOWN,
(WPARAM)FALSE, 0);
else
DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
break;

default:

DefFrameProc(hWnd, hWndMDIClient, WM_COMMAND, wParam, lParam);
break;
}
break;

case WM_CLOSE: //close all MDI windows, hdbcs & hstmts, else fail

return (CloseIDBCreateCommand() ? DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam) : FALSE);

case WM_DESTROY:

PostQuitMessage(0);
break;

default:
return (DefFrameProc(hWnd, hWndMDIClient, message, wParam, lParam));
}
return (0);
}

/*
FUNCTION: MDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Window Procedure for MDI Child windows
*/
long CALLBACK MDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
HWNDhWndTemp;//temporary window handle
        DLGPROClpDlgProc;//procedure address for modeless dialog

case WM_CREATE:

// create child windows
// 1. Modeless Dialog box in the background to process tabs
// 2. Static Text to display prompt
// 3. Edit Control to type SQL text
// 4. List Box to display results
// store dialog handle for future reference
// set focus to edit control

// if create failed due to low system resources
if (!hWnd)
break;

hWndActiveChild = hWnd;
hWndTemp = CreateDialog(hAppInstance, MDICHILDDIALOG, hWndActiveChild, MDIChildDlgProc);

// check to see if the dialog was created?, if not destroy this window
if (!hWndTemp)
return (-1);

SetWindowLong(hWnd, GWLAPP_HDLG, (LONG)hWndTemp);
SetFocus(GetDlgItem(hWndTemp, IDTEXT_SQL));
break;

case WM_SIZE:
                {
WORDwWidth;//New Width of MDI Child
WORDwHeight;//New Height of MDI Child
HDChDC;//Device Context
charszBuffer[MAXBUFLEN+1];//Static Control Text
intnStrLen;//Buffer Length
SIZEsize;//Screen size for text display

// call default procedure first, to let MDI position the child & then move its children
DefMDIChildProc(hWnd, message, wParam, lParam);

    // move child windows with proper screen size for text display
wWidth = LOWORD(lParam);
wHeight = HIWORD(lParam);
hWndTemp = (HWND)GetWindowLong(hWnd, GWLAPP_HDLG);

nStrLen = GetWindowText(GetDlgItem(hWndTemp, IDTEXT_PRMPT), szBuffer, MAXBUFLEN);
hDC = GetDC(hWnd);
GetTextExtentPoint(hDC, szBuffer, nStrLen, &size);
ReleaseDC(hWnd, hDC);

        MoveWindow(hWndTemp, 0, 0, wWidth, wHeight, TRUE);
MoveWindow(GetDlgItem(hWndTemp, IDTEXT_PRMPT), 0, 0, size.cx+2, size.cy+2, TRUE);
MoveWindow(GetDlgItem(hWndTemp, IDTEXT_SQL), size.cx+3, 0, wWidth - (size.cx+2), size.cy+2, TRUE);
MoveWindow(GetDlgItem(hWndTemp, IDLIST_RSLT), 0, size.cy+3, wWidth, wHeight - (size.cy+2), TRUE);
break;
                }

case WM_MENUSELECT: // update status bar to reflect menu selection
                {
                intiMenuFlag;//Check menu type

// store the Menu Item Id as a state value for text display
wStatusText = GET_WM_MENUSELECT_CMD(wParam, lParam);

// if none of the menuitems was selected, check if
// the control popup menu is selected.
iMenuFlag = GET_WM_MENUSELECT_FLAGS(wParam, lParam);

// if the selected menu is a system popup menu
if (wStatusText && (iMenuFlag & MF_SYSMENU) && (iMenuFlag & MF_POPUP))
wStatusText = IDM_POPUPMDISYS;

// invalidate status bar for repaint
InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
break;
}

case WM_MDIACTIVATE:

// check if the display of comboboxes require a change
if (GET_WM_MDIACTIVATE_FACTIVATE(hWnd, wParam, lParam) &&
(hWndActiveChild) && (hWndActiveChild != hWnd))
{
hWndActiveChild = hWnd;
DisplayNewCrsrAndICommand();
}
break;

case WM_MOUSEACTIVATE:

        // current window has changed, update comboboxes.
hWndActiveChild = hWnd;
DisplayNewCrsrAndICommand();
break;

case WM_SETFOCUS:

// pass on the focus to the edit box for user to type in SQL
SetFocus(GetDlgItem((HWND)GetWindowLong(hWnd, GWLAPP_HDLG), IDTEXT_SQL));
break;

case WM_DESTROY:

        // check if the window was being destroyed while creation failed
        if (!hWnd)
break;

// close the window and free instance thunk for modeless dialog
CloseICommandWindow(hWnd);
lpDlgProc = (DLGPROC)GetWindowLong((HWND)GetWindowLong(hWnd, GWLAPP_HDLG), DWL_DLGPROC);
DestroyWindow((HWND)GetWindowLong(hWnd, GWLAPP_HDLG));
FreeProcInstance(lpDlgProc);

if (hWnd == hWndActiveChild)
hWndActiveChild = (HWND)NULL;
break;

default:
return (DefMDIChildProc(hWnd, message, wParam, lParam));
}
return (0);
}

/*
FUNCTION: ToolbarProc(HWND hWnd, UINT   message, WPARAM wParam, LPARAM lParam)
COMMENTS: callback window procedure for toolbar window.
  Handle pain and mouse messages to paint the toolbar and
  provide default button behaviour for toolbar buttons.
*/
long CALLBACK ToolbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
static HBITMAPhbmpNewQuery;//btn1 bitmap handle
static HBITMAPhbmpRunQuery;//btn2 bitmap handle
static RECTstNewQuery;//btn1 rectangle
static RECTstRunQuery;                 //btn2 rectangle
POINTstMousePosition;//current mouse pos
BOOLbButtonPosition;//mouse pos flag
static BOOLbNewQueryBtnDown;//was btn1 down before?
static BOOLbRunQueryBtnDown;//was btn2 down before?
static intnLastButtonDown;//Which btn was down before?

bNewQueryBtnDown = FALSE;
bRunQueryBtnDown = FALSE;
nLastButtonDown  = 0;

case WM_CREATE:

// load bitmaps for buttons
// initialize static rectangles for button positions on toolbar
// initialize state variable for status text display
hbmpNewQuery = LoadBitmap(hAppInstance, BMP_NEWQUERY);
hbmpRunQuery = LoadBitmap(hAppInstance, BMP_RUNQUERY);

stNewQuery.left   = BTTNX;
stNewQuery.right  = BTTNX+BTTNWIDTH+1;
stNewQuery.top    = BTTNY;
stNewQuery.bottom = BTTNY+BTTNHEIGHT+1;

        stRunQuery.left   = BTTNX+BTTNWIDTH+BTTNMARGIN;
        stRunQuery.right  = BTTNX+BTTNWIDTH+BTTNMARGIN+BTTNWIDTH+1;
        stRunQuery.top    = BTTNY;
        stRunQuery.bottom = BTTNY+BTTNHEIGHT+1;

wStatusText = 0;
break;

case WM_DESTROY:

// delete bitmap handles
if (hbmpNewQuery)
DeleteObject(hbmpNewQuery);
if (hbmpRunQuery)
DeleteObject(hbmpRunQuery);
break;

case WM_LBUTTONDOWN:

// Check if the mouse key lies on any one of the buttons
// if so, set state variable to reflect that button and
// invalidate proper regions on tool & status bars for update.
// set capture on mouse movements till the mouse key is
// released.
stMousePosition.x = LOWORD(lParam);
stMousePosition.y = HIWORD(lParam);

if (PtInRect(&stNewQuery, stMousePosition))
{
bNewQueryBtnDown = TRUE;
wStatusText = nLastButtonDown = IDM_NEW;
SetCapture(hWnd);
InvalidateRect(hWnd, &stNewQuery, TRUE);
InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
}
else if (PtInRect(&stRunQuery, stMousePosition))
{
bRunQueryBtnDown = TRUE;
wStatusText = nLastButtonDown = IDM_QUERY;
SetCapture(hWnd);
InvalidateRect(hWnd, &stRunQuery, TRUE);
InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
}
break;

case WM_LBUTTONUP:

// check if the mouse movements from key down movements
// were captured, if so process the key release state.
// if the key was released in the same button where it
// was pressed, it is equivalent to a button click.
if (hWnd != GetCapture())
break;

stMousePosition.x = LOWORD(lParam);
stMousePosition.y = HIWORD(lParam);

if (bNewQueryBtnDown && PtInRect(&stNewQuery, stMousePosition))
{
bNewQueryBtnDown = FALSE;
nLastButtonDown = 0;
InvalidateRect(hWnd, &stNewQuery, TRUE);
PostMessage(hWndFrame, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_NEW, 0, 0));
SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
}
else if (bRunQueryBtnDown && PtInRect(&stRunQuery, stMousePosition))
{
bRunQueryBtnDown = FALSE;
nLastButtonDown = 0;
InvalidateRect(hWnd, &stRunQuery, TRUE);
PostMessage(hWndFrame, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_QUERY, 0, 0));
SendMessage(hWndCrsrList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
SendMessage(hWndStmtList, CB_SHOWDROPDOWN, (WPARAM)FALSE, 0);
                }

ReleaseCapture();
wStatusText = 0;
InvalidateRect(hWndStatusbar, &rectStatusText, TRUE);
break;

case WM_MOUSEMOVE:

        // process mouse movement only if the mouse key was pressed
        // down and its movements were being captured. If the mouse
        // moves outside of the currently depressed button, it needs
        // to be drawn again with normal state.
if (hWnd != GetCapture())
break;

stMousePosition.x = LOWORD(lParam);
stMousePosition.y = HIWORD(lParam);

if (nLastButtonDown == IDM_NEW)
{
bButtonPosition = PtInRect(&stNewQuery, stMousePosition);
if (bNewQueryBtnDown != bButtonPosition)
{
bNewQueryBtnDown = bButtonPosition;
InvalidateRect(hWnd, &stNewQuery, TRUE);
}
}
else if (nLastButtonDown == IDM_QUERY)
{
bButtonPosition = PtInRect(&stRunQuery, stMousePosition);
if (bRunQueryBtnDown != bButtonPosition)
{
bRunQueryBtnDown = bButtonPosition;
InvalidateRect(hWnd, &stRunQuery, TRUE);
}
}
break;

case WM_PAINT:
                {
PAINTSTRUCTps;//paint structure
RECTrect;//rectangle for tool bar
HDChDC;//device context handle
intiWidth;//tool bar width
intiHeight;//tool bar height
HPENhLtGrayPen;//buttonface color pen
HPENhGrayPen;//buttonshadow color pen
intbtnx;//button x coordinate position

if (!(hDC = BeginPaint(hWnd, &ps)))
break;

GetClientRect(hWnd, &rect);
iWidth = rect.right;
iHeight = rect.bottom;

hLtGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNFACE));
hGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW));

        // draw background and border
SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
SelectObject(hDC, hLtGrayPen);
Rectangle(hDC, 0, 0, iWidth, iHeight);

SelectObject(hDC, GetStockObject(BLACK_PEN));
MoveToEx(hDC, 0, iHeight-1, NULL);
LineTo(hDC, iWidth, iHeight-1);

SelectObject(hDC, GetStockObject(WHITE_PEN));
MoveToEx(hDC, 0, 0, NULL);
LineTo(hDC, iWidth, 0);

// draw tool bar buttons (new query, run query)
        // check state variables to draw proper button state
btnx = BTTNX;
SelectObject(hDC, GetStockObject(BLACK_PEN));
DRAWBTTNRECT(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
DrawBitmap(hDC, (bNewQueryBtnDown?btnx+3:btnx+2), (bNewQueryBtnDown?BTTNY+3:BTTNY+2), hbmpNewQuery);
SelectObject(hDC, (bNewQueryBtnDown ? hGrayPen : GetStockObject(WHITE_PEN)));
DRAWBTTNLIFT1(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
SelectObject(hDC, (bNewQueryBtnDown ? hLtGrayPen : hGrayPen));
DRAWBTTNLIFT2(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);

btnx += BTTNWIDTH+BTTNMARGIN;
SelectObject(hDC, GetStockObject(BLACK_PEN));
DRAWBTTNRECT(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
DrawBitmap(hDC, (bRunQueryBtnDown?btnx+3:btnx+2), (bRunQueryBtnDown?BTTNY+3:BTTNY+2), hbmpRunQuery);
SelectObject(hDC, (bRunQueryBtnDown ? hGrayPen : GetStockObject(WHITE_PEN)));

DRAWBTTNLIFT1(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);
SelectObject(hDC, (bRunQueryBtnDown ? hLtGrayPen : hGrayPen));
DRAWBTTNLIFT2(hDC, btnx, BTTNY, BTTNWIDTH, BTTNHEIGHT);

SelectObject(hDC, GetStockObject(WHITE_PEN));
EndPaint(hWnd, &ps);

// delete create objects
if (hLtGrayPen)
DeleteObject(hLtGrayPen);
if (hGrayPen)
DeleteObject(hGrayPen);
break;
                }

default:

return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (0);
}

/*
FUNCTION: StatusbarProc(HWND hWnd, UINT   message, WPARAM wParam, LPARAM lParam)
COMMENTS: callback window procedure for status bar.
  process paint messages and timer messages to update current
  state, date and time
*/
long CALLBACK StatusbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
static RECT DateTimeRect; // remember for frequent updates

case WM_CREATE:
                {
HDChDC;// device context
SIZEsizeText;       // size of status text box
SIZEsizeTime;       // size of time display box
SIZEsizeDate;       // size of date display box

        // start a timer for periodic updates to date and time display
        // find out width of status text, date and time display boxes
SetTimer(hWnd,  (UINT)IDT_STATUSTIMER, (UINT)TIMERDELAY, NULL);

iTimex = iDatex = 0;
rectStatusText.left = 2;
rectStatusText.top = 3;

if (hDC = GetDC(hWnd))
{
GetTextExtentPoint(hDC, STATUSNEW, strlen(STATUSNEW), &sizeText);
GetTextExtentPoint(hDC, TIMETEXT, strlen(TIMETEXT), &sizeTime);
GetTextExtentPoint(hDC, DATETEXT, strlen(DATETEXT), &sizeDate);
ReleaseDC(hWnd, hDC);
rectStatusText.right = sizeText.cx + rectStatusText.left;
iTimex = sizeTime.cx;
iDatex = sizeDate.cx;
}
break;
                }

case WM_TIMER:

// invalidate only the date&time area for update
 InvalidateRect(hWnd, &DateTimeRect, TRUE);
break;

case WM_PAINT:
                {
HDChDC;//device context
PAINTSTRUCTps;//paint structure
RECTrect;               //status bar rect
intiWidth;             //status bar width
intiHeight;            //status bar height
HPENhLtGrayPen;         //btnface color pen
HPENhGrayPen;           //btnshadow color pen
charszText[MAXBUFLEN];  //text buffer for display
time_ttCurrentTime;       //current date&time
struct tmstTime;             //date&time structure

if (!(hDC = BeginPaint(hWnd, &ps)))
break;

GetClientRect(hWnd, &rect);
iWidth = rect.right;
iHeight = rect.bottom;
rectStatusText.bottom = iHeight-2;

hLtGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNFACE));
hGrayPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW));

// paint background and border
SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
SelectObject(hDC, hLtGrayPen);
Rectangle(hDC, 0, 0, iWidth, iHeight);

SelectObject(hDC, GetStockObject(BLACK_PEN));
MoveToEx(hDC, 0, 0, NULL);
LineTo(hDC, iWidth, 0);
SelectObject(hDC, GetStockObject(WHITE_PEN));
MoveToEx(hDC, 0, 1, NULL);
LineTo(hDC, iWidth, 1);

        // draw text boxes for status, time and date display
        SelectObject(hDC, hGrayPen);
        MoveToEx(hDC, rectStatusText.left, rectStatusText.bottom, NULL);
        LineTo(hDC, rectStatusText.left, rectStatusText.top);
        LineTo(hDC, rectStatusText.right, rectStatusText.top);
        SelectObject(hDC, GetStockObject(WHITE_PEN));
        LineTo(hDC, rectStatusText.right, rectStatusText.bottom);
        LineTo(hDC, rectStatusText.left, rectStatusText.bottom);

        SelectObject(hDC, hGrayPen);
        MoveToEx(hDC, iWidth-2, 3, NULL);
        LineTo(hDC, iWidth-iDatex-2, 3);
        LineTo(hDC, iWidth-iDatex-2, iHeight-2);
        SelectObject(hDC, GetStockObject(WHITE_PEN));
        LineTo(hDC, iWidth-2, iHeight-2);
        LineTo(hDC, iWidth-2, 3);

        SelectObject(hDC, hGrayPen);
        MoveToEx(hDC, iWidth-iDatex-6, 3, NULL);
        LineTo(hDC, iWidth-iTimex-iDatex-6, 3);
        LineTo(hDC, iWidth-iTimex-iDatex-6, iHeight-2);
        SelectObject(hDC, GetStockObject(WHITE_PEN));
        LineTo(hDC, iWidth-iDatex-6, iHeight-2);
        LineTo(hDC, iWidth-iDatex-6, 3);

// draw status text in the display box based on current
// value of wStatusText global flag
SetBkMode(hDC, TRANSPARENT);
SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));

switch (wStatusText)
{
case IDM_POPUPAPPSYS:

strcpy(szText, STATUSPOPUPAPPSYS);
break;

case IDM_POPUPMDISYS:

strcpy(szText, STATUSPOPUPMDISYS);
break;

case SC_RESTORE:

strcpy(szText, STATUSRESTORE);
break;

case SC_MOVE:

strcpy(szText, STATUSMOVE);
break;

case SC_SIZE:

strcpy(szText, STATUSSIZE);
break;

case SC_MINIMIZE:

strcpy(szText, STATUSMINIMIZE);
break;

case SC_MAXIMIZE:

strcpy(szText, STATUSMAXIMIZE);
break;

case SC_CLOSE:

strcpy(szText, STATUSCLOSE);
break;

case SC_NEXTWINDOW:

strcpy(szText, STATUSNEXTWINDOW);
break;

case SC_PREVWINDOW:

strcpy(szText, STATUSPREVWINDOW);
break;

case SC_TASKLIST:

strcpy(szText, STATUSTASKLIST);
break;

case IDM_POPUPLOGIN:

strcpy(szText, STATUSPOPUPLOGIN);
break;

case IDM_INITIALIZE:

strcpy(szText, STATUSCONNECT);
break;

case IDM_DISCONNECT:

strcpy(szText, STATUSDISCONNECT);
break;

case IDM_EXIT:

strcpy(szText, STATUSEXIT);
break;

case IDM_POPUPQUERY:

strcpy(szText, STATUSPOPUPQUERY);
break;

        case IDM_QUERY:

        strcpy(szText, STATUSQUERY);
        break;

case IDM_NEW:

        strcpy(szText, STATUSNEW);
break;

case IDM_POPUPSCHEMA:

strcpy(szText, STATUSPOPUPSCHEMA);
break;

case IDM_POPUPWINDOW:

strcpy(szText, STATUSPOPUPWINDOW);
break;

case IDM_TILE:

strcpy(szText, STATUSTILE);
break;

case IDM_CASCADE:

strcpy(szText, STATUSCASCADE);
break;

case IDM_ICONS:

strcpy(szText, STATUSICONS);
break;

case IDM_CLOSEALL:

strcpy(szText, STATUSCLOSEALL);
break;

case IDM_POPUPHELP:

strcpy(szText, STATUSPOPUPHELP);
break;

case IDM_APPHELP:

strcpy(szText, STATUSAPPHELP);
break;

case IDM_ABOUT:

strcpy(szText, STATUSABOUT);
break;

case IDM_CATALOGS:

strcpy(szText, STATUSCATALOGS);
break;

case IDM_COLUMNS:

strcpy(szText, STATUSCOLUMNS);
break;

case IDM_FOREIGN_KEYS:

strcpy(szText, STATUSFOREIGN_KEYS);
break;

case IDM_PRIMARY_KEYS:

strcpy(szText, STATUSPRIMARY_KEYS);
break;

case IDM_PROCEDURE_PARAMETERS:

strcpy(szText, STATUSPROCEDURE_PARAMETERS);
break;

case IDM_PROCEDURES:

strcpy(szText, STATUSPROCEDURES);
break;

case IDM_SCHEMATA:

strcpy(szText, STATUSSCHEMATA);
break;

case IDM_TYPES:

strcpy(szText, STATUSTYPES);
break;

case IDM_TABLES:

strcpy(szText, STATUSTABLES);
break;

default:

            if (wStatusText >= IDM_MDICHILD)
            sprintf(szText, STATUSMDICHILD, wStatusText-IDM_MDICHILD+1);
            else
        strcpy(szText, STATUSDEFAULT);
break;
}

DrawText(hDC, szText, strlen(szText), &rectStatusText, DT_LEFT);

    // get current date and time and display time in time box
time(&tCurrentTime);
stTime = *localtime(&tCurrentTime);
strftime(szText, MAXBUFLEN, TIMEFORMAT, &stTime);
rect.top = rectStatusText.top;
rect.bottom = rectStatusText.bottom;
rect.left  = iWidth-iTimex-iDatex-6;
rect.right = iWidth-iDatex-6;
DrawText(hDC, szText, strlen(szText), &rect, DT_LEFT);

// display date in date box
strftime(szText, MAXBUFLEN, DATEFORMAT, &stTime);
rect.left  = iWidth-iDatex-2;
rect.right = iWidth-2;
DrawText(hDC, szText, strlen(szText), &rect, DT_LEFT);

// remember the date&time rectangle to minimize painting
DateTimeRect.left   = iWidth-iTimex-iDatex-6;
DateTimeRect.right  = iWidth-2;
DateTimeRect.top    = rect.top;
DateTimeRect.bottom = rect.bottom;

EndPaint(hWnd, &ps);

// delete created objects
if (hLtGrayPen)
DeleteObject(hLtGrayPen);
if (hGrayPen)
DeleteObject(hGrayPen);
break;
                }

default:

return (DefWindowProc(hWnd, message, wParam, lParam));
break;
}
return (0);
}

/*
FUNCTION: ConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Callback dialog box procedure for connect menu command
  displays a list of available data sources, asks for user
  name and password to pass default connection parameters
  for a data source connection
*/
BOOL CALLBACK ConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
BOOL fResult; // ENDDIALOG Return Code

switch (message)
{
HCURSORhOldCursor;// Default Cursor Shape

case WM_INITDIALOG:

        // display list of available providers
hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));
DisplayProviders(GetDlgItem(hWnd, IDCOMBO_PROVIDER));
//DisplayDataSource(hWnd);
SetCursor(hOldCursor);
break;

case WM_COMMAND:

switch (GET_WM_COMMAND_ID(wParam, lParam))
{
// Make a connection using the supplied values
case IDOK:
hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));
            // Check if a DSO was provided for connection
if( ConnectDatabase(hWnd) )
EndDialog(hWnd, fResult);
SetCursor(hOldCursor);
break;

case IDCANCEL:
EndDialog(hWnd, FALSE);
break;

default:

return (FALSE);
break;
}
break;

default:

return (FALSE);
break;
}
return (TRUE);
}

/*
FUNCTION: DisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Callback dialog box procedure for disconnect dialog.
  provides a list of available SESSIONs and a list of HSTMTs
  for currently selected SESSION. Allows closure of all SESSIONs
  and HSTMTs one by one or in groups.
*/
BOOL CALLBACK DisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
static HWNDhListhdbc;//listbox that displays hdbc(s)
static HWNDhListstmt;      //listbox that displays hstmt(s)
static HWNDhPushOk;        //pushbutton to free hdbc
static HWNDhPushClose;     //pushbutton to free hstmt
static HWNDhPushCancel;    //pushbutton to close dialog

case WM_INITDIALOG:

// store handles for future reference
hListhdbc = GetDlgItem(hWnd, IDLIST_SESSION);
hListstmt = GetDlgItem(hWnd, IDLIST_COMMAND);
hPushOk = GetDlgItem(hWnd, IDDISCONNECT);
hPushCancel = GetDlgItem(hWnd, IDCANCEL);
hPushClose = GetDlgItem(hWnd, IDCLOSE_ACTVTY);

// display connected database handles and statements
DisplayConnections(hListhdbc);
DisplayICommands(hListstmt, hListhdbc, 0);

// enable or disable pushbuttons & listboxes to match available hdbc & hstmt
if (SendMessage(hListhdbc, LB_GETCOUNT, 0, 0)>0)
{
EnableWindow(hPushOk, TRUE);
if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0)
{
EnableWindow(hPushClose, TRUE);
SetFocus(hPushClose);
SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
else
{
EnableWindow(hListstmt, FALSE);
EnableWindow(hPushClose, FALSE);
SetFocus(hPushOk);
SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
}
else
{
EnableWindow(hListhdbc, FALSE);
EnableWindow(hListstmt, FALSE);
EnableWindow(hPushOk, FALSE);
SetFocus(hPushCancel);
SendMessage(hPushCancel, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}

// return FALSE to prevent default focus.
return (FALSE);

case WM_COMMAND:

switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDDISCONNECT:

            // Free current hdbc, display available hdbc(s)
FreeConnect(hListhdbc);
SendMessage(hListstmt, LB_RESETCONTENT, 0, 0);
DisplayConnections(hListhdbc);

            // update displayed hstmt(s) for current hdbc
            // enable or disable pushbuttons to match available
            // hdbc(s) and hstmt(s) for closure
if (SendMessage(hListhdbc, LB_GETCOUNT, 0, 0) > 0)
{
SendMessage(hListhdbc, LB_SETCURSEL, 0, 0);
EnableWindow(hListstmt, TRUE);
DisplayICommands(hListstmt, hListhdbc, 0);
if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0)
{
EnableWindow(hPushClose, TRUE);
SetFocus(hPushClose);
SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
else
{
EnableWindow(hListstmt, FALSE);
EnableWindow(hPushClose, FALSE);
SetFocus(hPushOk);
SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
}
else
{
EnableWindow(hListhdbc, FALSE);
EnableWindow(hPushOk, FALSE);
EnableWindow(hPushClose, FALSE);
SetFocus(hPushCancel);
SendMessage(hPushCancel, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
break;

case IDCANCEL:

// close dialog
EndDialog(hWnd, FALSE);
break;

case IDCLOSE_ACTVTY:
                        {
intnIndex;// counter to search for selected hstmt(s)

// go through all displayed hstmt(s) and free all highlighted ones
for (nIndex = (int)SendMessage(hListstmt, LB_GETCOUNT, 0, 0)-1;
nIndex >= 0; nIndex--)
if (SendMessage(hListstmt, LB_GETSEL, nIndex, 0))
FreeICommand(hListstmt, hListhdbc, nIndex);

// reset both hdbc(s) and hstmt(s) display
nIndex = (int)SendMessage(hListhdbc, LB_GETCURSEL, 0, 0);
DisplayConnections(hListhdbc);
SendMessage(hListhdbc, LB_SETCURSEL, nIndex, 0);
DisplayICommands(hListstmt, hListhdbc, nIndex);

// enable or disable pushbuttons to match available
// hdbc(s) and hstmt(s) for closure
if (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0)
{
EnableWindow(hPushClose, TRUE);
SetFocus(hPushClose);
SendMessage(hPushClose, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
else
{
EnableWindow(hListstmt, FALSE);
EnableWindow(hPushClose, FALSE);
SetFocus(hPushOk);
SendMessage(hPushOk, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
}
break;
                        }

case IDLIST_SESSION:
// If the current selection in hdbc(s) has changed
// update the list of hstmt(s) to match the new hdbc
if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_SELCHANGE)
DisplayICommands(hListstmt, hListhdbc,
(UINT)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 0, 0));

            // Enable or disable hstmt listbox and close pushbutton accordingly
EnableWindow(hListstmt, (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0));
EnableWindow(hPushClose, (SendMessage(hListstmt, LB_GETCOUNT, 0, 0)>0));
break;

default:

return (FALSE);
}
break;

default:

return (FALSE);
}

return (TRUE);
}

/*
FUNCTION: AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Callback dialog box procedure for About dialog box
  displays the about information and closes upon selection of
  ok button
*/
BOOL CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_COMMAND)
{
EndDialog(hWnd, TRUE);
return (TRUE);
}
else
return (FALSE);
}

/*
FUNCTION: MDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
COMMENTS: Callback dialog box procedure for modeless child dialog box
  in each MDI Child Window. This dialog box simply processes
  the tab messages (by default) to allow switching from edit
  control (SQL Text) to list box control (Query results).
*/
BOOL CALLBACK MDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return (FALSE);
}

/*
FUNCTION: DrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap
COMMENTS: Draws a bitmap on given Device context with given bitmap
  handle at given location
*/

VOID FAR PASCAL DrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap)
{
HDChMemDC;// Device Context in Memory
POINTstPoint;    // point structure for conversion from device to logical units
BITMAPstBitmap;
    HGDIOBJ hObject;

    // create a compatible device context in memory and select the bitmap
    // in to it.
if (!(hMemDC = CreateCompatibleDC(hDC))) return;

hObject = SelectObject(hMemDC, hBitmap);
SetMapMode(hMemDC, GetMapMode(hDC));

// Get bitmap size and convert it to logical units from device units.
GetObject(hBitmap, sizeof(BITMAP), &stBitmap);
stPoint.x = stBitmap.bmWidth;
stPoint.y = stBitmap.bmHeight;
DPtoLP(hDC, &stPoint, 1);

// bit block transfer the bitmap from memory device context to given
// device context at specified location
BitBlt(hDC, iLeft, iTop, stPoint.x, stPoint.y, hMemDC, 0, 0, SRCCOPY);

    // select original object in the memory device context and destroy it
SelectObject(hMemDC, hObject);
DeleteDC(hMemDC);
}

/********************************************* END OF FILE **************************************************/


QUERY.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB QURYDEMO Sample
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module QURYDEMO.CPP
//
//-----------------------------------------------------------------------------------
/*
PROGRAM: QURYDEMO
========

PURPOSE:
========
demonstrates a simple MDI (Multiple Document Interface)application
that allows a user to simultaneously connect to multiple
hetrogeneous databases and perform SQL queries to get results.

FUNCTIONS:
==========
InitEnvironment() - Initialize OLE
DisplayProviders() - Display available Providers
ConnectDatabase() - Connect to a specific data source
DisplayConnections() - Display List of made connections
DisplayICommands() - Display list of ICommands
NewICommandWindow() - Open a new ICommand and update displays
ChangeCurrentCursor() - Change current cursor display
ChangeCurrentICommand() - Change current ICommand display
DisplayNewCrsrAndICommand() - update cusor & ICommand displays
FreeConnect() - free a IDBCreateCommand
FreeICommand() - free a ICommand
CloseICommandWindow() - close a ICommand window
ExecuteQuery() - execute a user specified query
CloseIDBCreateCommand() - check if all connections are closed
FreeEnvironment() - free OLE
ExecuteCommand() - Execute the ICommand and return an IRowset
GetDataFromRowset() - Get the Data from the IRowset
SetupBindings() - Set the binding structure
CreateAccessor() - Create Accessor to store the Data
GetData() - Get the Data out of the rowset

COMMENTS:
=========
Created by Microsoft Corporation.

The application uses MDI Child Window Titles to store values of
PROVIDER's and Command Objects. These values are also stored in the
comboboxes that are displayed on the toolbar.

*/
#define DBINITCONSTANTS
#define INITGUID

#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stddef.h>// offsetof
#include <assert.h>// assert

#include "oledb.h"
#include "oledberr.h"
#include "msdaguid.h"

#include "qurydemo.h"

// Globals
extern HWNDhWndFrame; // Main Frame Window handle
extern HWNDhWndCrsrList;// hdbc(s) combobox on the tool bar
extern HWNDhWndStmtList;// hstmt(s) combobox on the tool bar
extern HWNDhWndMDIClient;// MDI Client window handle
extern HWNDhWndActiveChild;// Current active MDI Child window
extern HINSTANCEhAppInstance;// Application instance

IMalloc* g_pIMalloc = NULL;

intnChildCount;// Number of child windows currently open
charszDispBuffer[MAXDISPLAYSIZE+1];// Display Buffer
unsigned charrgbData[MAX_COL][MAXDATALEN];// Results Data Array
long dwDataLen[MAX_COL];// Results Data Length Array

charszErrorDescription [MAXDISPLAYSIZE+1];
charszErrorSource      [MAXDISPLAYSIZE+1];

//Variables and constants used for provider enumeration
//#define   MAX_VALUE_NAME          300
//#define   MAX_PROVIDER_NUM        10
#define NUMELEM(p1) (sizeof(p1) / sizeof(p1[0]))
#define COLUMN_ALIGNVAL 8
#define ROUND_UP( Size, Amount ) (((DWORD)(Size) +  ((Amount) - 1)) & ~((Amount) - 1))

const ULONGDEF_SOURCES_CBMAXLEN= 64;
const ULONG MAX_NUM_PROVIDERS= 16;

IParseDisplayName*g_pIParse = NULL;
ULONGg_cProvNames;
CHARg_rgszProvName[MAX_NUM_PROVIDERS][DEF_SOURCES_CBMAXLEN];
WCHARg_rgwszParseName[MAX_NUM_PROVIDERS][DEF_SOURCES_CBMAXLEN];

/*
FUNCTION: InitEnvironment()
COMMENTS: Allocate an environment handle for OLE function calls.
*/
FAR PASCAL InitEnvironment()
{
HRESULThr;

// Initialize OLE
hr = CoInitialize( NULL );

if (FAILED(hr))
{
DumpErrorHResult( hr, (LPSTR)"CoInitialize FAILED!!" );
return FALSE;
}

    // Retrieve the task memory allocator
    hr = CoGetMalloc( MEMCTX_TASK, &g_pIMalloc );

if (FAILED(hr))
{
DumpErrorHResult( hr, (LPSTR)"CoGetMalloc FAILED!!" );
return FALSE;
}

// reset child window count
nChildCount = 0;

// Return TRUE
return TRUE;
}

/*
FUNCTION: DisplayProviders(HWND hWnd)
COMMENTS: Display a list of available Providers.
*/
void FAR PASCAL DisplayProviders(HWND hWnd)
{
    ULONGiProv;
HRESULThr;

// Initialize count of provider names
g_cProvNames = 0;

//Go to registry and get me the provider names
hr = EnumerateProviders();
if (FAILED (hr))
{
  MessageBox(hWndFrame,
             "There was an error retrieve providers from the OLE DB Enumerator",
             "Enumeration Error",
 MB_OK | MB_ICONERROR);
  return;
}

//All returned Providerss in the provided combo box for display.
SendMessage(hWnd, CB_RESETCONTENT, 0, 0);
for (iProv=0; iProv<g_cProvNames; iProv++)
   SendMessage(hWnd, CB_ADDSTRING, 0, (LPARAM)(LPSTR)g_rgszProvName[iProv]);

SendMessage(hWnd, CB_SETCURSEL, 0, 0);
}

/*
FUNCTION: EnumerateProviders
COMMENTS: Display a list of available providers
*/
HRESULT EnumerateProviders
(
)
{
HRESULThr;
ULONGul, cRows = 0;
ISourcesRowset*pISrcRowset = NULL;
IRowset*pIRowset = NULL;
IAccessor*pIAccessor = NULL;
BYTE*pData = NULL;
DWORDdwOffset;
HACCESSORhAccessor = NULL;
DBBINDINGrgBind[3];
HROWrghRows[MAX_NUM_PROVIDERS];
HROW*pRows = &rghRows[0];

enum enumSOURCES_COLUMNS {
eid_SOURCES_NAME = 1,
eid_SOURCES_PARSENAME,
eid_SOURCES_DESCRIPTION,
eid_SOURCES_TYPE,
eid_SOURCES_ISPARENT,
eid_SOURCES_CLSID,
};

static struct tagSOURCES
{
ULONGiOrdinal;
DBTYPEwType;
ULONGcbMaxLen;
} s_rgSources[] = {
eid_SOURCES_NAME,DBTYPE_STR,DEF_SOURCES_CBMAXLEN,
eid_SOURCES_PARSENAME,DBTYPE_WSTR,DEF_SOURCES_CBMAXLEN * sizeof(WCHAR),
eid_SOURCES_TYPE,DBTYPE_UI4,sizeof(ULONG),
};

memset(rghRows, 0, sizeof(rghRows));

// Initialize the OLE DB Enumerator
if( FAILED(hr = CoCreateInstance(CLSID_OLEDB_ENUMERATOR, NULL,
CLSCTX_INPROC_SERVER, IID_ISourcesRowset, (LPVOID*)&pISrcRowset)) )
{
DumpErrorHResult( hr, (LPSTR)"CoCreateInstance FAILED!!" );
goto EXIT;
}

// Retrieve the Rowset
if( FAILED(hr = pISrcRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL,
(IUnknown**)&pIRowset)) )
{
DumpErrorHResult( hr, (LPSTR)"GetSourcesRowset FAILED!!" );
goto EXIT;
}

memset(rgBind, 0, sizeof(rgBind));

if( FAILED(hr = pIRowset->QueryInterface(IID_IAccessor, (LPVOID*)&pIAccessor)) )
{
DumpErrorHResult( hr, (LPSTR)"QI of IID_IAccessor from pIRowset failed" );
goto EXIT;
}

dwOffset = 0;
for(ul=0; ul< NUMELEM(s_rgSources); ul++)
{
rgBind[ul].dwPart= DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
rgBind[ul].eParamIO= DBPARAMIO_NOTPARAM;
rgBind[ul].iOrdinal= s_rgSources[ul].iOrdinal;
rgBind[ul].wType= s_rgSources[ul].wType;
rgBind[ul].obValue= dwOffset + offsetof(COLUMNDATA,bData);
rgBind[ul].obLength= dwOffset + offsetof(COLUMNDATA,dwLength);
rgBind[ul].obStatus= dwOffset + offsetof(COLUMNDATA,wStatus);
rgBind[ul].cbMaxLen= s_rgSources[ul].cbMaxLen;
rgBind[ul].dwMemOwner= DBMEMOWNER_CLIENTOWNED;
dwOffset += rgBind[ul].cbMaxLen + offsetof( COLUMNDATA, bData );
dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
}

if( FAILED(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, NUMELEM(s_rgSources),
rgBind, dwOffset, &hAccessor, NULL)) )
{
DumpErrorHResult( hr, (LPSTR)"Accessor Creation failed!!" );
goto EXIT;
}

// Retrieve the providers
if( SUCCEEDED(hr = pIRowset->GetNextRows(NULL, 0, MAX_NUM_PROVIDERS, &cRows, &pRows)) )
{
// Allocate block of memory to retrieve the row data into.
pData = new BYTE[dwOffset];
if( pData == NULL )
{
DumpErrorHResult( E_OUTOFMEMORY, (LPSTR)"Unable to allocate memory for buffer");
goto EXIT;
}

// Loop over the rows of data, collecting providers and discarding
// enumerators..
for(ul=0; (ul<cRows) && (ul<MAX_NUM_PROVIDERS); ul++)
{
memset(pData, 0, dwOffset);

if( SUCCEEDED(hr = pIRowset->GetData(rghRows[ul], hAccessor, pData)) )
{
if( *((ULONG*)(pData + rgBind[2].obValue)) == DBSOURCETYPE_DATASOURCE )
{
// Store Provider Name
strcpy(g_rgszProvName[g_cProvNames], (CHAR*)(pData + rgBind[0].obValue));

// Store Parse Name
wcscpy(g_rgwszParseName[g_cProvNames], (WCHAR*)(pData + rgBind[1].obValue));

g_cProvNames++;
}
}
}
}
else
{
DumpErrorHResult( hr, (LPSTR)"GetNextRows failed to retrieve Providers" );
goto EXIT;
}

// Retrieve the IID_IParseDisplayName interface before returning
if( FAILED(hr = pISrcRowset->QueryInterface(IID_IParseDisplayName, (LPVOID*)&g_pIParse)) )
{
DumpErrorHResult( hr, (LPSTR)"QI for IParseDisplayName failed");
goto EXIT;
}

EXIT:
if( pData )
delete[] pData;

if( pIAccessor )
{
if( hAccessor )
{
if( FAILED(hr = pIAccessor->ReleaseAccessor(hAccessor, NULL)) )
DumpErrorHResult( hr, (LPSTR)"Release Accessor failed!!" );
}
pIAccessor->Release();
}

if( pIRowset )
{
if( cRows )
{
if( FAILED(hr = pIRowset->ReleaseRows(cRows, rghRows, NULL, NULL, NULL)) )
DumpErrorHResult( hr, (LPSTR)"Release of Row handles failed");
}
pIRowset->Release();
}

if( pISrcRowset )
pISrcRowset->Release();
return hr;
}

//**********************************************************************
//
// ConnectDatabase
//
// Purpose:
//
//     Initializes the PROVIDER and creates a IDBCreateCommand Object.
//
// Parameters:
//
// HWND hWnd                - handle to the window
//
// Return Value:
//     TRUE         - Success
//     FALSE        - Failure
//
// Function Calls:
//     Function                    Location
//
//     IDBInitialize::Release      provider's Command object
//
//
// Comments:
//
//
//**********************************************************************
BOOL FAR PASCAL ConnectDatabase(HWND hWnd)
{
// Global IDBInitialize Options
IMoniker*pIMoniker= NULL;// Moniker
IDBInitialize * pIDBInit= NULL;// IDBInitialize Object
IDBCreateCommand *pIDBCreateCommand= NULL;// IDBCreateCommand
IDBCreateSession *pIDBCreateSession= NULL; // IDBCreateSession
IDBProperties*pIDBProperties= NULL; // IDBProperties
IOpenRowset *pIOpenRowset= NULL;// IOpenRowset
IUnknown *pIUnknown= NULL;// IUnknown
HRESULThr;// HRESULT
DBPROPSETrgPropertySet[1];// Array of property sets
DBPROPrgProperties[3];// Array of property values
BOOLbReturn = FALSE;// Return Value
ULONGiProp, iPDex;
ULONGchEaten;

// Strings for MSDASQL (CHAR & WCHAR)
CHARszProvName[MAXBUFLEN+1];// Provider String
CHARszDBName[MAXBUFLEN+1];// DSN String
WCHARwszBuff[MAXBUFLEN+1];// WCHAR buffer
CHARszUserName[MAXBUFLEN+1];// User Name
CHARszPassword[MAXBUFLEN+1];// Password
CHARszBuffer[MAXBUFLEN+1];// String Buffer
LRESULT nResult;// Return Code
BOOLfCommandWindow=FALSE;// Flag to indicate Command support

// Initialize Property Buffers
for(iProp=0; iProp<NUMELEM(rgProperties); iProp++)
VariantInit(&(rgProperties[iProp].vValue));

// check if enough windows are already open, refuse connection
if (nChildCount >= MAXCHILDWNDS)
{
MessageBox(hWndFrame, MAXCHILDEXCEEDED, MAXCHLDERR, MB_OK | MB_ICONHAND);
return (FALSE);
}

// Retrieve Provider values from the connect dialog box
GetDlgItemText(hWnd, IDCOMBO_PROVIDER, szProvName, MAXBUFLEN);
GetDlgItemText(hWnd, IDCOMBO_NAME,    szDBName,   MAXBUFLEN);
GetDlgItemText(hWnd, IDTEXT_USERID,    szUserName, MAXBUFLEN);
GetDlgItemText(hWnd, IDTEXT_PASSWORD,  szPassword, MAXBUFLEN);

    //Determine the ParseDisplayName
for(iPDex=0; iPDex<g_cProvNames; iPDex++)
    {
if( strcmp((const char *)szProvName, (const char *) g_rgszProvName[iPDex])==0 )
break;
}

if( iPDex >= g_cProvNames )
{
DumpErrorHResult( E_FAIL, (LPSTR)"Unknown Provider, Please Select Valid Provider" );
goto error;
}

if( FAILED(hr = g_pIParse->ParseDisplayName(NULL, g_rgwszParseName[iPDex], &chEaten, &pIMoniker)) )
{
DumpErrorHResult( hr, (LPSTR)"ParseDisplayName failed");
goto error;
}

if( FAILED(hr = BindMoniker(pIMoniker, 0, IID_IDBInitialize, (LPVOID*)&pIDBInit)) )
{
DumpErrorHResult( hr, (LPSTR)"BindMoniker failed");
goto error;
}

iProp = 0;
// If DataSource name specified, then create property node
if( *szDBName != '\0' )
{
// Fill in Data Source
rgProperties[iProp].dwPropertyID=DBPROP_INIT_DATASOURCE;
rgProperties[iProp].dwOptions=DBPROPOPTIONS_REQUIRED;
rgProperties[iProp].colid=DB_NULLID;
MultiByteToWideChar(CP_ACP, 0, szDBName, -1, wszBuff, MAXBUFLEN+1);
V_VT(&(rgProperties[iProp].vValue))=VT_BSTR;
V_BSTR(&(rgProperties[iProp].vValue))=SysAllocString(wszBuff);
iProp++;
}

// If User Name specified, then create property node
if( *szUserName != '\0' )
{
rgProperties[iProp].dwPropertyID=DBPROP_AUTH_USERID;
rgProperties[iProp].dwOptions=DBPROPOPTIONS_REQUIRED;
rgProperties[iProp].colid=DB_NULLID;
MultiByteToWideChar(CP_ACP, 0, szUserName, -1, wszBuff, MAXBUFLEN+1);
V_VT(&(rgProperties[iProp].vValue))=VT_BSTR;
V_BSTR(&(rgProperties[iProp].vValue))=SysAllocString(wszBuff);
iProp++;
}

// If Password specified, then create property node
if( *szPassword != '\0' )
{
rgProperties[iProp].dwPropertyID=DBPROP_AUTH_PASSWORD;
rgProperties[iProp].dwOptions=DBPROPOPTIONS_REQUIRED;
rgProperties[iProp].colid=DB_NULLID;
MultiByteToWideChar(CP_ACP, 0, szPassword, -1, wszBuff, MAXBUFLEN+1);
V_VT(&(rgProperties[iProp].vValue))=VT_BSTR;
V_BSTR(&(rgProperties[iProp].vValue))=SysAllocString(wszBuff);
iProp++;
}

if( iProp )
{
// Identify Property Set
rgPropertySet[0].rgProperties=rgProperties;
rgPropertySet[0].cProperties=iProp;
rgPropertySet[0].guidPropertySet=DBPROPSET_DBINIT;

// from the DataSource Object get the Session Object
hr = pIDBInit->QueryInterface(
IID_IDBProperties,
(void**)&pIDBProperties );
if (FAILED(hr))
{
GetDetailedErrorInfo (pIDBInit,IID_IDBInitialize,szErrorSource,szErrorDescription);
DumpError ( hr, (LPSTR)"QI for IDBProperties FAILED!!",szErrorSource,szErrorDescription);
goto error;
}

// Set Connection Properties
hr = pIDBProperties->SetProperties(1, rgPropertySet);
if (FAILED(hr))
{
GetDetailedErrorInfo (pIDBProperties,IID_IDBProperties,szErrorSource,szErrorDescription);
DumpError ( hr, (LPSTR)"IDBProperties->SetProperties FAILED!!",szErrorSource,szErrorDescription);
goto error;
}
}

 // Initialize the PROVIDER
hr = pIDBInit->Initialize();
if (FAILED(hr))
{
        GetDetailedErrorInfo (pIDBInit,IID_IDBInitialize,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"IDBInit->Initialize FAILED!!",szErrorSource,szErrorDescription);
goto error;
}

// from the DataSource Object get the Session Object
hr = pIDBInit->QueryInterface(
IID_IDBCreateSession,
(void**)&pIDBCreateSession );
if (FAILED(hr))
{
GetDetailedErrorInfo (pIDBInit,IID_IDBInitialize,szErrorSource,szErrorDescription);
DumpError ( hr, (LPSTR)"QI for IDBCreateSession FAILED!!",szErrorSource,szErrorDescription);
goto error;
}

// from the Session object, attempt to get an IUnknown
hr = pIDBCreateSession->CreateSession(NULL, IID_IUnknown, (IUnknown**)&pIUnknown);

if (FAILED(hr))
{
GetDetailedErrorInfo (pIDBCreateSession,IID_IDBCreateSession,szErrorSource,szErrorDescription);
DumpError ( hr, (LPSTR)"IDBCreateSession->CreateSession FAILED!!",szErrorSource,szErrorDescription);
goto error;
}

// from the Session object, attempt to get the IDBCreateCommand interface
hr = pIUnknown->QueryInterface(
IID_IDBCreateCommand,
(void**)&pIDBCreateCommand);
if (FAILED(hr))
{
// if no Command object support, attempt to get the IOpenRowset interface
hr = pIUnknown->QueryInterface(
IID_IOpenRowset,
(void**)&pIOpenRowset);
if (FAILED(hr))
{
        GetDetailedErrorInfo (pIUnknown,IID_IUnknown,szErrorSource,szErrorDescription);
    DumpError ( hr, (LPSTR)"QI for IOpenRowset FAILED!!",szErrorSource,szErrorDescription);
goto error;
}
}
else
{
fCommandWindow = TRUE;
}

// update the combo-box with IUnknown from Session object
wsprintf( szBuffer, PROVIDER_SESSION_FORMAT, (LPSTR)szProvName, pIUnknown );

nResult = (UINT)SendMessage(hWndCrsrList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_SETCURSEL, (WPARAM)nResult, 0);
SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
ChangeCurrentCursor(hWndCrsrList);

if (fCommandWindow)
{
// Create a Command Object and its associated window.
NewICommandWindow();
}
else
{
NewIOpenRowsetWindow();
}

bReturn = TRUE;

error:
if( pIMoniker )
pIMoniker->Release();

// Initialize Property Buffers
for(iProp=0; iProp<NUMELEM(rgProperties); iProp++)
VariantClear(&(rgProperties[iProp].vValue));

// If we are successully connected, and the IParse pointer
// exists release it.
if( bReturn && g_pIParse )
{
g_pIParse->Release();
g_pIParse = NULL;
}

// Release IUnknown (for Session) on error only.
// The pointer value has been copied to the window.
if( (!bReturn) && (pIUnknown) )
pIUnknown->Release();

if( (pIDBCreateCommand) )
pIDBCreateCommand->Release();

if( (pIDBProperties) )
pIDBProperties->Release();

if( (pIOpenRowset) )
pIOpenRowset->Release();

if( pIDBCreateSession )
pIDBCreateSession->Release();

if(pIDBInit)
pIDBInit->Release();

return (bReturn);

}

//**********************************************************************
//
// DisplayConnections
//
// Purpose:
//
//     Display list of available hdbc(s) in the given list box.
//
// Parameters:
//
// HWND hWndhdbc               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL DisplayConnections(HWND hWndhdbc)
{
ULONG nConnects;// # of Connections
ULONG count;// Count
charszBuffer[MAXBUFLEN+1];// String Buffer

// Read the information from the  combo-box
// on the tool bar and feed it in the given list box.
SendMessage(hWndhdbc, LB_RESETCONTENT, 0, 0);
nConnects = SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0);

for (count = 0; count < nConnects; count++)
{
SendMessage(hWndCrsrList, CB_GETLBTEXT, count, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndhdbc, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}

SendMessage(hWndhdbc, LB_SETCURSEL, 0, 0);
}

//**********************************************************************
//
// DisplayICommands
//
// Purpose:
//
//     Initializes the PROVIDER and creates a ICommand Object.
//     Display list of ICommand Objects for the currently selected PROVIDER.
//
// Parameters:
//
//  HWND hWndhdbc - handle to the PROVIDER
// HWND hWndhstmt                - handle to the ICommand
//int nCrsrIndex - Count
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL DisplayICommands(HWND hWndhstmt, HWND hWndhdbc, int nCrsrIndex)
{
charszBuffer[MAXBUFLEN+1];// MDI child window title
HWNDhWndChild;// MDI child window handle
IDBCreateCommand * pIDBCreate1;// IDBCreateCommand Object #1
IDBCreateCommand * pIDBCreate2;// IDBCreateCommand Object #2
ICommand *pICommand;// ICommand Object

// Reset the Command list box in the disconnect dialog box
SendMessage(hWndhstmt, LB_RESETCONTENT, 0, 0);

// Go through all available MDI child windows and check if the
// PROVIDER in the title matches the one selected in the list box.
// If they match, use the Command in the window title to create
// a new entry in the Command list box.
for (hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT))
{
// Class name check is necessary as some of MDI child
// windows may be iconized by the user and MDIClient
// in such cases create additional windows (such as icon titles).
GetClassName(hWndChild, szBuffer, MAXBUFLEN);

if (strcmp(szBuffer, OLEDBMDICLASS))
continue;

GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate1, &pICommand);

SendMessage(hWndhdbc, LB_GETTEXT, (WPARAM)nCrsrIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate2);

if (pIDBCreate1 != pIDBCreate2)
continue;

wsprintf(szBuffer, ((hWndChild == hWndActiveChild) ?  CURQUERY_STRING:QUERY_STRING), pICommand);
SendMessage(hWndhstmt, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
SendMessage(hWndhstmt, LB_SETSEL, TRUE, 0);
}

//**********************************************************************
//
// NewICommandWindow
//
// Purpose:
//
//     Create a new ICommand Object on the current IDBCreateCommand.
//
// Parameters:
//
// Return Value:
//
// Function Calls:
//     Function                         Location
//
//   IDBCreateCommand::CreateCommandprovider's Command object
//     ICommand::Release                provider's Command object
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL NewICommandWindow()
{
int nCurrenthdbc;// Current PROVIDER
charszBuffer[MAXBUFLEN+1];// String in PROVIDER ComboBox on Toolbar
charszProvName[MAXBUFLEN+1];// DSN String
MDICREATESTRUCTmcs;// MDI Child Window Create Struc

IDBCreateCommand * pIDBCreateCommand= NULL;// IDBCreateCommand Object
IOpenRowset * pIOpenRowset= NULL;// IOpenRowset Object
ICommand *pICommand= NULL;// ICommand Object
ICommand *pIUnknown= NULL;// IUnknown for session Object
HRESULThr;// HRESULT
BOOLbReturn= FALSE;// Return Value

// check if there is PROVIDER selected in the Combo-Box
if ((nCurrenthdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0)) == CB_ERR)
{
MessageBox(hWndFrame, MAKECONNECT, NOSESSIONERROR, MB_OK | MB_ICONHAND);
return;
}

// check if the number of windows exceeds MAXCHILDWNDS
if (nChildCount >= MAXCHILDWNDS)
{
MessageBox(hWndFrame, MAXCHILDEXCEEDED, MAXCHLDERR, MB_OK | MB_ICONHAND);
return;
}

// Scan PROVIDER string and IDBCreateCommand value from the combo-box
SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nCurrenthdbc, (LPARAM)(LPSTR)szBuffer);

sscanf(szBuffer, SCANPROVIDERSESSION_FORMAT, szProvName, &pIUnknown);

// from the Session object, attempt to get the IDBCreateCommand interface
hr = pIUnknown->QueryInterface(
IID_IDBCreateCommand,
(void**)&pIDBCreateCommand);
if (FAILED(hr))
{
// if no Command object support, attempt to get the IOpenRowset interface
hr = pIUnknown->QueryInterface(
IID_IOpenRowset,
(void**)&pIOpenRowset);
if (FAILED(hr))
{
    GetDetailedErrorInfo (pIUnknown,IID_IUnknown,szErrorSource,szErrorDescription);
            DumpError ( hr, (LPSTR)"QI for IOpenRowset FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IOpenRowset FAILED!!" );
goto error;
}
}
else
{
// Create a command object
hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommand, (IUnknown**)&pICommand);

if (FAILED(hr))
{
       GetDetailedErrorInfo (pIDBCreateCommand,IID_ICommand,szErrorSource,szErrorDescription);
           DumpError ( hr, (LPSTR)"pIDBCreateCommand->CreateCommand FAILED!!",szErrorSource,szErrorDescription);
   //DumpErrorHResult( hr, (LPSTR)"pIDBCreateCommand->CreateCommand FAILED!!" );
   goto error;
}
}

// create a new MDI client window. maximized, if the previous is so.
mcs.szClass = OLEDBMDICLASS;
mcs.szTitle = UNTITLED;
mcs.hOwner  = hAppInstance;
mcs.style   = hWndActiveChild && IsZoomed(hWndActiveChild) ? WS_MAXIMIZE : 0;
mcs.x = mcs.cx = mcs.y = mcs.cy = CW_USEDEFAULT;
hWndActiveChild = (HWND)(UINT)SendMessage(hWndMDIClient, WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mcs);

    // check if it was created, if it wasn't free up resource and flag warning
    if (!hWndActiveChild)
    {
MessageBox(hWndFrame, CREATECHILDERR, EXECERROR, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
goto error;
    }

// Display the Provider string, PROVIDER and IDBCreateCommand/IOpenRowset in the title
// of newly created window. Increment the child window counter
 wsprintf(szBuffer, QUERY_STRING, pICommand);

SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

 wsprintf(szBuffer, PROVIDER_SESSION_COMMAND_FORMAT, (LPSTR)szProvName, pIUnknown, pICommand);

SetWindowText(hWndActiveChild, szBuffer);
nChildCount++;

// Update the Command Combo-Box on the tool bar.
ChangeCurrentICommand(hWndStmtList);
bReturn = TRUE;

error:
// Note that we obtained pIUnknown (Session ptr) from the window.
// So we leave it alone.

// Release ICommand on error only.
// The pointer value has been copied to the window.
if (pICommand && !bReturn)
pICommand->Release();

if (pIDBCreateCommand)
pIDBCreateCommand->Release();

if (pIOpenRowset)
pIOpenRowset->Release();
}

//**********************************************************************
//
// ChangeCurrentCursor
//
// Purpose:
//
//     Change the displayed PROVIDER in the PROVIDER(s) combobox.
//     Also activate the appropriate MDI child window that
//     has the same PROVIDER as the new PROVIDER in the combobox.
//
// Parameters:
//
// HWND hWndCrsrList               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL ChangeCurrentCursor(HWND hWndCrsrList)
{
ULONGnNewhdbc;// New PROVIDER position
ULONGnConnects;// # of connections
ULONGnCount;// Counter
charszBuffer[MAXBUFLEN+1];// String Buffer
BOOLbChangedFocus;// Activate different MDI child
HWNDhWndChild;// MDI Child window
IDBCreateCommand* pIDBCreate1;// IDBCreateCommand #1
IDBCreateCommand* pIDBCreate2;// IDBCreateCommand #2
ICommand*pICommand;// ICommand Object

// check to see if the current selection in the combo-box
// differs from the previous selection, if it is the same then
// simply return. Check is made by searching a marked string
// in the PROVIDER combobox.
nNewhdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);
nConnects = (int)SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0);

for(nCount = 0; nCount < nConnects; nCount++)
{
SendMessage(hWndCrsrList, CB_GETLBTEXT, nCount, (LPARAM)(LPSTR)szBuffer);
if (strstr(szBuffer, CUR_MARK))
break;
}

if (nCount == nNewhdbc)
return;

// if there was a current marked hdbc in the combobox, remove the
// mark from the string and replace it in the combobox.
if (nCount != nConnects)
{
SendMessage(hWndCrsrList, CB_GETLBTEXT, nCount, (LPARAM)(LPSTR)szBuffer);
szBuffer[strlen(szBuffer)-2] = '\0';
SendMessage(hWndCrsrList, CB_INSERTSTRING, nCount, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_DELETESTRING, nCount+1, 0);
}

// Create a new marked string with currently selected hdbc string in
// the combobox and replace it with the original.
SendMessage(hWndCrsrList, CB_GETLBTEXT, nNewhdbc, (LPARAM)(LPSTR)szBuffer);
strcat(szBuffer, CUR_MARK);
SendMessage(hWndCrsrList, CB_INSERTSTRING, nNewhdbc, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_DELETESTRING, nNewhdbc+1, 0);
SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

// Reset the ICommand combobox. Search through the MDI child windows
// and collect all ICommands from window titles that have the same
// PROVIDER value as the newly selected PROVIDER in the PROVIDER(s) combo-box above.
SendMessage(hWndStmtList, CB_RESETCONTENT, 0, 0);
for (bChangedFocus=FALSE, hWndChild=GetWindow(hWndMDIClient, GW_CHILD);hWndChild; hWndChild = GetWindow(

hWndChild, GW_HWNDNEXT))
{
// Check class name to skip iconized titles or other
// such non MDI Child windows
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if (strcmp(szBuffer, OLEDBMDICLASS))
continue;

GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate1, &pICommand);

SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nNewhdbc, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate2);

if (pIDBCreate1 != pIDBCreate2)
continue;

if (!bChangedFocus)
{
            // If the first match is found, change the active window
        // and update the ICommand(s) combobox with a new entry that
        // has ICommand marked with current marker.
bChangedFocus = TRUE;
hWndActiveChild = hWndChild;
SendMessage(hWndMDIClient, WM_MDIACTIVATE, (WPARAM)hWndChild, 0);
wsprintf(szBuffer, CURQUERY_STRING, pICommand);
SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
else
{
// simply add the ICommand in the ICommand(s) combobox.
wsprintf(szBuffer, QUERY_STRING, pICommand);
SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
}
}

//**********************************************************************
//
// ChangeCurrentICommand
//
// Purpose:
//
//     Change the current selection in the ICommand(s) combobox.
//     Update the current marker in the combobox and activate
//     proper MDI Child window.
//
// Parameters:
//
// HWND hWndStmtList               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL ChangeCurrentICommand(HWND hWndStmtList)
{
ULONGnNewICommand;// New Selection in Combo-Box
ULONG nICommands;// # of ICommands
ULONG nCount;// Counter
charszBuffer[MAXBUFLEN+1];// String Buffer
HWNDhWndChild;// MDI Child Window Handle
IDBCreateCommand* pIDBCreate1;// IDBCreateCommand Object #1
IDBCreateCommand* pIDBCreate2;// IDBCreateCommand Object #2
ICommand*pICommand1;// ICommand Object #1
ICommand*pICommand2;// ICommand Object #2

// Find the index of new selection and total number of ICommand(s)
nNewICommand = (int)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0);
nICommands  = (int)SendMessage(hWndStmtList, CB_GETCOUNT,  0, 0);

// Check if the current selection is same as previous one, if
// so simply return. Check for marker to determine previous selection
for(nCount = 0; nCount < nICommands; nCount++)
{
SendMessage(hWndStmtList, CB_GETLBTEXT, nCount, (LPARAM)(LPSTR)szBuffer);
if (strstr(szBuffer, CUR_MARK))
break;
}

if (nCount == nNewICommand)
return;

// If a previous selection was found, remove current marker
// and update it in the ICommand(s) combobox.
if (nCount != nICommands)
{
SendMessage(hWndStmtList, CB_GETLBTEXT, nCount, (LPARAM)(LPSTR)szBuffer);
szBuffer[strlen(szBuffer)-2] = '\0';
SendMessage(hWndStmtList, CB_INSERTSTRING, nCount, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_DELETESTRING, nCount+1, 0);
}

// Mark the current selection and update it in the ICommand(s) combobox
SendMessage(hWndStmtList, CB_GETLBTEXT, nNewICommand, (LPARAM)(LPSTR)szBuffer);
strcat(szBuffer, CUR_MARK);
SendMessage(hWndStmtList, CB_INSERTSTRING, nNewICommand, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_DELETESTRING, nNewICommand+1, 0);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

// Scan ICommand value and DSN value from current selection in
// PROVIDER(s) and ICommand(s) comboboxes.
sscanf(szBuffer, QUERY_STRING, &pICommand1);

SendMessage( hWndCrsrList, CB_GETLBTEXT,
(UINT)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0),
(LPARAM)(LPSTR)szBuffer);

sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate1);

// Go through list of MDI Child windows and match the ICommand and PROVIDER
// values. If a match if found (must be), activate the window
for (hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT))
{
// Ignore non MDI child windows
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if (strcmp(szBuffer, OLEDBMDICLASS))
continue;

GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate2, &pICommand2);

if (pIDBCreate1 == pIDBCreate2 && pICommand1 == pICommand2)
{
hWndActiveChild = hWndChild;
SendMessage(hWndMDIClient, WM_MDIACTIVATE, (WPARAM)hWndChild, 0);
break;
}
}
}

//**********************************************************************
//
// DisplayNewCrsrAndICommand
//
// Purpose:
//
//     Change the current selection in the ICommand(s) combobox.
//     Update the current marker in the combobox and activate
//     proper MDI Child window.
//
// Parameters:
//
// HWND hWndStmtList               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL DisplayNewCrsrAndICommand()
{
ULONG nConnects;// # of PROVIDER(s)
ULONG nICommands;// # of ICommand(s)
ULONG nOldPROVIDER;// Prev selected PROVIDER in combobox
ULONGnOldICommand;// Prev selected ICommand in combobox
ULONGnIndex;// Counter
charszBuffer[MAXBUFLEN+1];// String Buffer
HWNDhWndChild;// MDI Child Window
IDBCreateCommand* pIDBCreate1;// IDBCreateCommand Object #1
IDBCreateCommand* pIDBCreate2;// IDBCreateCommand Object #2
ICommand*pICommand1;// ICommand Object #1
ICommand*pICommand2;// ICommand Object #2

// Scan PROVIDER and ICommand values from newly selected window
GetWindowText(hWndActiveChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate1, &pICommand1);

// Search through list of PROVIDER(s) in PROVIDER combobox and find
// matching PROVIDER. remove marker from prev selection and add
// marker to the new selection. Update combobox accordingly.
nConnects = (int)SendMessage(hWndCrsrList, CB_GETCOUNT,  0, 0);
nOldPROVIDER   = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);

for(pIDBCreate2 = (IDBCreateCommand*)(nIndex = 0); pIDBCreate1 != pIDBCreate2; nIndex++)
{
SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate2);
}

// Change in PROVIDER combobox required.
if (--nIndex != nOldPROVIDER)
{
SendMessage(hWndCrsrList, CB_GETLBTEXT, nOldPROVIDER, (LPARAM)(LPSTR)szBuffer);
szBuffer[strlen(szBuffer)-2] = '\0';
SendMessage(hWndCrsrList, CB_INSERTSTRING, nOldPROVIDER, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_DELETESTRING, nOldPROVIDER+1, 0);

SendMessage(hWndCrsrList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
strcat(szBuffer, CUR_MARK);
SendMessage(hWndCrsrList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_DELETESTRING, nIndex+1, 0);
SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

// Reset the ICommand(s) combobox, search through the list
// of MDI child windows and find all hstmt(s) associated to
// new PROVIDER. Build the new list of ICommand(s) for the ICommand
// combobox. Mark the one ICommand that matches the currently
// activated MDI child window.
SendMessage(hWndStmtList, CB_RESETCONTENT, 0, 0);
for (hWndChild=GetWindow(hWndMDIClient,GW_CHILD);hWndChild;hWndChild=GetWindow(hWndChild,GW_HWNDNEXT))
{
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if (strcmp(szBuffer, OLEDBMDICLASS))
continue;

GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate2, &pICommand2);

if (pIDBCreate1 != pIDBCreate2)
continue;

if (hWndActiveChild == hWndChild)
{
wsprintf(szBuffer, CURQUERY_STRING, pICommand2);
SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
else
{
wsprintf(szBuffer, QUERY_STRING, pICommand2);
SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
}
}
// No change in PROVIDER combobox required
else
{
// Go through the list of ICommand(s) in ICommand combobox.
// Find the one that matches the currently activated MDI child window.
nOldICommand = (int)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0);
nICommands = (int)SendMessage(hWndStmtList, CB_GETCOUNT, 0, 0);

for(pICommand2 = (ICommand*)(nIndex = 0); pICommand1 != pICommand2; nIndex++)
{
SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, QUERY_STRING, &pICommand2);
}

// New index in ICommand differs from previous selection
if (--nIndex != nOldICommand)
{
// Remove the marker from previous selection.
// Add it to the new string and update the combobox display
SendMessage(hWndStmtList, CB_GETLBTEXT, nOldICommand, (LPARAM)(LPSTR)szBuffer);
szBuffer[strlen(szBuffer)-2] = '\0';
SendMessage(hWndStmtList, CB_INSERTSTRING, nOldICommand, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_DELETESTRING, nOldICommand+1, 0);

SendMessage(hWndStmtList, CB_GETLBTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
strcat(szBuffer, CUR_MARK);
SendMessage(hWndStmtList, CB_INSERTSTRING, nIndex, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_DELETESTRING, nIndex+1, 0);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);
}
}
}

//**********************************************************************
//
// FreeConnect
//
// Purpose:
//
//     Disconnect and Free the currently selected PROVIDER in
//     the PROVIDER listbox in disconnect dialog. Call RELEASE
//     to free the PROVIDER. Close all MDI
//     child windows associated with this PROVIDER. That will
//     automatically free associated ICommand(s).
//
// Parameters:
//
// HWND hWndhdbc               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//     IDBCreateCommand::Release   provider's Command object
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL FreeConnect(HWND hWndhdbc)
{
int nIndex;// Current Selection in the listbox of disconnect dlg
intnCurrent;// Current Selected PROVIDER(s) combobox
charszBuffer[MAXBUFLEN+1];// String Buffer
charszSelect[MAXBUFLEN+1];// Original Selected PROVIDER(s) combobox
charszProvName[MAXBUFLEN+1];// DSN string
HWNDhWndChild;// MDI Child window
IDBCreateCommand * pIDBCreate1;// IDBCreateCmmand Object #1
IDBCreateCommand * pIDBCreate2;// IDBCreateCmmand Object #2
IErrorInfo *pIErrorInfo;// IErrorInfo Object

// Check current selection in the list box of disconnect dialog.
// Scan PROVIDER value from the current selection.
if ((nIndex = (int)SendMessage(hWndhdbc, LB_GETCURSEL, 0, 0)) == LB_ERR)
return;

SendMessage(hWndhdbc, LB_GETTEXT, (WPARAM)nIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANPROVIDERSESSION_FORMAT, szProvName, &pIDBCreate1);

// Go through the list of MDI child windows and find matching PROVIDER(s)
// close all children who have the same PROVIDER value. Closing them
// automatically frees associated ICommands. See CloseICommandWindow.
for(hWndChild = GetWindow(hWndMDIClient, GW_CHILD); hWndChild; )
{
// Store next window handle before destroying the currentone
HWND hWndTemp = GetWindow(hWndChild, GW_HWNDNEXT);

// Ignore non MDI child windows
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if (!strcmp(szBuffer, OLEDBMDICLASS))
{
GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSION_TITLEFORMAT, &pIDBCreate2);
if (pIDBCreate1 == pIDBCreate2)
{
// Destroy the window and restart search
SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
hWndTemp = GetWindow(hWndMDIClient, GW_CHILD);
}
}
hWndChild = hWndTemp;
}

// Release the Command Object & return
if( pIDBCreate1 )
pIDBCreate1->Release();

// Release any remaining error objects.  This is idempotent.
// Otherwise the provider might have an outstanding global-object count.
GetErrorInfo( 0, &pIErrorInfo );
if (pIErrorInfo)
pIErrorInfo->Release();

// Unload any DLL's no longer being used
CoFreeUnusedLibraries();

// Update the PROVIDER(s) combobox display by removing the deleted PROVIDER
// from the list and reselecting the previous selection
wsprintf(szBuffer, PROVIDER_SESSION_FORMAT, (LPSTR)szProvName, pIDBCreate1);
nCurrent = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0);
SendMessage(hWndCrsrList, CB_GETLBTEXT, nCurrent, (LPARAM)(LPSTR)szSelect);
nIndex = (int)SendMessage(hWndCrsrList, CB_FINDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndCrsrList, CB_DELETESTRING, (WPARAM)nIndex, 0);
SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szSelect);

// If there is no query window open and the current selected PROVIDER
// was deleted, make sure to make the next available PROVIDER as current
if (SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) &&
!GetWindow(hWndMDIClient, GW_CHILD) && (nCurrent == nIndex))
{
if ((nCurrent = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0))!=CB_ERR)
return;
SendMessage(hWndCrsrList, CB_GETLBTEXT, 0, (LPARAM)(LPSTR)szSelect);
strcat(szSelect, CUR_MARK);
SendMessage(hWndCrsrList, CB_INSERTSTRING, 0, (LPARAM)(LPSTR)szSelect);
SendMessage(hWndCrsrList, CB_DELETESTRING, 1, 0);
SendMessage(hWndCrsrList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szSelect);
}
}

//**********************************************************************
//
// FreeICommand
//
// Purpose:
//
//     Free a ICommand window based upon current selection in
//     ICommand list box in the disconnect dialog.
//
// Parameters:
//
// HWND hWndhstmt               - handle to the window
// HWND hWndhdbc                - handle to the window
//  int             nIndex- Index
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL FreeICommand(HWND hWndhstmt, HWND hWndhdbc, int nIndex)
{
charszBuffer[MAXBUFLEN+1];// display buffer
HWNDhWndChild;// MDI child window
IDBCreateCommand* pIDBCreate1;// IDBCreateCommand Object #1
IDBCreateCommand* pIDBCreate2;// IDBCreateCommand Object #2
ICommand*pICommand1;// ICommand Object #1
ICommand*pICommand2;// ICommand Object #2

// Scan the ICommand and PROVIDER values from the current selections in
// respective listboxes of disconnect dialog box.
SendMessage(hWndhstmt, LB_GETTEXT, nIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, QUERY_STRING, &pICommand1);
SendMessage(hWndhdbc, LB_GETTEXT, (UINT)SendMessage(hWndhdbc, LB_GETCURSEL,0, 0), (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate1);

    // Go through the list of MDI child windows and find matching window
    // that has same values for PROVIDER and ICommand. Destroy the matching window.
    // That will call CloseICommandWindow and free up associated ICommand(s).
for(hWndChild=GetWindow(hWndMDIClient, GW_CHILD); hWndChild; hWndChild=GetWindow(hWndChild, GW_HWNDNEXT))
{
// Ignore non MDI child windows
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if( strcmp(szBuffer, OLEDBMDICLASS) )
continue;

GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate2, &pICommand2);
if (pIDBCreate1 == pIDBCreate2 && pICommand1 == pICommand2)
break;
}
SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
}

//**********************************************************************
//
// CloseICommandWindow
//
// Purpose:
//
//     Close a ICommand window.
//     Call ICommand->Release() to free associated ICommand(s).
//
// Parameters:
//
// HWND hWnd               - handle to the window
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//     ICommand::Release           provider's Command object
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL CloseICommandWindow(HWND hWnd)
{
charszBuffer[MAXBUFLEN+1];// String buffer
charszSelect[MAXBUFLEN+1];// Current Selected ICommand(s)
ICommand*pICommand;// ICommand Object

// Scan the ICommand value from the window title
GetWindowText(hWnd, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANCOMMAND_TITLEFORMAT, &pICommand);

// Release the Command Object & return
if( pICommand )
pICommand->Release();

// Find the matching ICommand in the ICommand(s) combobox and remove it
// from the list. Closure of a MDI child window will cause MDIClient
// to automatically activate a different child window if available.
// That will automatically refresh the ICommand and PROVIDER displays.
// See DisplayNewCrsrAndICommand function.
wsprintf( szBuffer, QUERY_STRING, pICommand );

SendMessage( hWndStmtList, CB_GETLBTEXT,
(WPARAM)SendMessage(hWndStmtList, CB_GETCURSEL, 0, 0),
(LPARAM)(LPSTR)szSelect );

SendMessage( hWndStmtList, CB_DELETESTRING,
(WPARAM)SendMessage(hWndStmtList, CB_FINDSTRING, 0,
(LPARAM)(LPSTR)szBuffer), 0 );

SendMessage( hWndStmtList, CB_SELECTSTRING,
(WPARAM)-1,
(LPARAM)(LPSTR)szSelect );

// Decrement the child window counter.
nChildCount--;
}

//**********************************************************************
//
// ExecuteQuery
//
// Purpose:
//
//     Execute the user typed SQL Statements in the currently
//     active MDI child window. If successful, then prepare
//     the list of results and display it in the child listbox.
//     Display errors in the OLE DB function(s) failed.
//
// Parameters:
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//     ExecuteCommand              query.cpp
//     GetDataFromRowset           query.cpp
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL ExecuteQuery()
{
CHARszBuffer[MAXBUFLEN+1];// String Buffer
WCHARwszBuffer[MAXBUFLEN+1];// String Buffer (WCHAR)
signed short intswColLength = MAXDATALEN;// Column Data Length
HCURSORhOldCursor;// Default Cursor Handle
IUnknown*pIUnknown = NULL;// Session Object
ICommand*pICommand = NULL;// ICommand Object
IOpenRowset*pIOpenRowset= NULL;// IOpenRowset Object
IRowset*pIRowset = NULL;// IRowset Object
HRESULThr;// HRESULT

// Check if there is an active window available
if (!hWndActiveChild)
{
MessageBox(hWndFrame, ((SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) <=0) ?
MAKECONNECT : OPENWINDOW ), NOCOMMANDERROR, MB_OK | MB_ICONHAND);
return;
}

// Change cursor shape to hour glass
hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));

// Scan PROVIDER, session and ICommand values
GetWindowText(hWndActiveChild, (char*)szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIUnknown, &pICommand);

// Get the user typed SQL
GetWindowText(GetDlgItem((HWND)GetWindowLong(hWndActiveChild, GWLAPP_HDLG), IDTEXT_SQL), szBuffer, MAXBUFLEN);

// Convert ANSI String to WCHARString
MultiByteToWideChar(CP_ACP, 0, szBuffer, -1, wszBuffer, MAXBUFLEN+1);

// Execute the Command Object
hr = ExecuteCommand( pIUnknown, pICommand, wszBuffer, &pIRowset );

if (FAILED(hr))
return;

// Empty IRowset
if( hr == ResultFromScode(S_FALSE) )
{
DumpErrorHResult( ResultFromScode(S_OK), (LPSTR)"ExecuteCommand returned No Rowset!!" );
return;
}

// Get Data from the IRowset Object
hr = GetDataFromRowset( pIRowset );

if (FAILED(hr))
{
//DumpErrorHResult( hr, (LPSTR)"GetDataFromRowset returned error!!" );
return;
}
}

//**********************************************************************
//
// CloseIDBCreateCommand
//
// Purpose:
//
//     Go through all open PROVIDER's (IDBCreateCommand(s) and ICommand(s))
//     and close them one by one.
//
// Parameters:
//
// Return Value:
//
// Function Calls:
//     Function                    Location
//
//     IDBCreateCommand::Release   provider's Command object
//
// Comments:
//
//
//**********************************************************************
BOOL FAR PASCAL CloseIDBCreateCommand()
{
ULONG nIndex;// Index
ULONGnCount;// # of PROVIDER(s)
charszBuffer[MAXBUFLEN+1];// String Buffer
HWNDhWndChild;// MDI Child Window
IDBCreateCommand* pIDBCreate1;// IDBCreateCommand Object #1
IDBCreateCommand* pIDBCreate2;// IDBCreateCommand Object #2

// Get count of connected PROVIDER(s) from the PROVIDER(s) combobox on the toolbar
if (!(nCount = (int)SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0)))
return (TRUE);

// Go through all available MDI child windows and for each PROVIDER,
// find the matching MDI child window and ask it for closure, thereby
// freeing the associated ICommand (see CloseICommandWindow). Once all
// associated ICommand(s) are freed, free the PROVIDER
for (nIndex = 0; nIndex < nCount; nIndex++)
{
// Scan current indexed PROVIDER from PROVIDER(s) combobox
SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nIndex, (LPARAM)(LPSTR)szBuffer);
sscanf(szBuffer, SCANSESSION_FORMAT, &pIDBCreate1);

// Search through the list of MDI Child Windows
for(hWndChild = GetWindow(hWndMDIClient, GW_CHILD); hWndChild; )
{
// Store the next child, before destroying the current
HWND hWndTemp = GetWindow(hWndChild, GW_HWNDNEXT);

// Ignore non MDI child windows
GetClassName(hWndChild, szBuffer, MAXBUFLEN);
if (!strcmp(szBuffer, OLEDBMDICLASS))
{
GetWindowText(hWndChild, szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSION_TITLEFORMAT, &pIDBCreate2);
if (pIDBCreate1 == pIDBCreate2)
{
// Destroy the window and restart search
SendMessage(hWndMDIClient, WM_MDIDESTROY, (WPARAM)hWndChild, 0);
hWndTemp = GetWindow(hWndMDIClient, GW_CHILD);
}
}
hWndChild = hWndTemp;
}

// Call IDBCreateCommand->Release()
// to free the current IDBCreateCommand resource
if( pIDBCreate1 )
pIDBCreate1->Release();
}

// Reset the PROVIDER(s) combobox display and display all connections
// closed message. Return success to let application exit.
SendMessage(hWndCrsrList, CB_RESETCONTENT, 0, 0);
MessageBox(hWndFrame, CLOSEALLSESSION, LOGOUTINFO,MB_OK | MB_ICONINFORMATION);
return (TRUE);
}

/*
FUNCTION: FreeEnvironment()
COMMENTS: Free the OLE environment.
*/
HRESULT FreeEnvironment()
{
HRESULThr;// HRESULT
IErrorInfo*pIErrorInfo;//IErrorInfo

    // Release the task memory allocator
if( g_pIMalloc )
g_pIMalloc->Release();

// Release any remaining error objects.  This is idempotent.
// Otherwise the provider might have an outstanding global-object count.
GetErrorInfo( 0, &pIErrorInfo );
if (pIErrorInfo)
pIErrorInfo->Release();

// Free the error queues
SetErrorInfo(0,NULL);

// Uninitialize OLE
CoUninitialize();

return hr;
}

//**********************************************************************
//
// ExecuteCommand
//
// Purpose:
//
//    Executes the query command that was previously set on the Command object,
//    and returns a first interface pointer on the resulting Rowset object.
//
// Parameters:
//   IOpenRowset*pIOpenRowset- interface pointer to data provider's
//  IOpenRowset
//   ICommand*pICommand       - interface pointer on data provider's
//              Command object
//   LPWSTRwszBuffer- SQL-style query string to be sent
//              to the Command object or table/file name to
//  be sent to IOpenRowset
//   IRowset**ppIRowset_out   - out pointer through which to return
//              interface pointer on provider's
//          Rowset object
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
// Function Calls:
//     Function                    Location
//
//     ICommand::Execute           provider's Command object
//     IMalloc::Free               OLE task memory allocator
//
//
//     assert                      c runtime
//
//
//
// Comments:
//
//     The interface pointer returned through ppIRowset_out has been AddRef'ed,
//     it must be Release'd later by the caller.
//
//**********************************************************************
HRESULT ExecuteCommand
(
IUnknown*pIUnknown,// Session object
ICommand*pICommand,// Command object
LPWSTR wszBuffer,// SQL String (WCHAR) or Table Name
IRowset**ppIRowset_out
)
{
ICommandText*pICommandText = NULL;// ICommandText Object
IOpenRowset*pIOpenRowset = NULL;
IRowset*pIRowset = NULL;// Pointer to an Array of IRowset pointers
DBIDTableID;// Table ID
HRESULThr = S_OK;// HRESULT

// Asserts
assert(ppIRowset_out != NULL);
    assert(wszBuffer != NULL);

memset(&TableID, 0, sizeof(DBID));

// If no command object use IOpenRowset
if (NULL == pICommand)
{
// if no Command object support, attempt to get the IOpenRowset interface
hr = pIUnknown->QueryInterface(
IID_IOpenRowset,
(void**)&pIOpenRowset);
if (FAILED(hr))
{
        GetDetailedErrorInfo (pIUnknown,IID_IUnknown,szErrorSource,szErrorDescription);
            DumpError ( hr, (LPSTR)"QI for IOpenRowset FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IOpenRowset FAILED!!" );
goto error;
}
// Pass in table/file name
TableID.eKind = DBKIND_NAME;

TableID.uName.pwszName = wszBuffer;

// From IOpenRowset, get a rowset object
hr = pIOpenRowset->OpenRowset(
NULL,// pUnkOuter
&TableID,// pTableID
NULL,// pIndexID
IID_IRowset,// refiid
0,// cProperties
NULL,// rgProperties
 (IUnknown**)&pIRowset);  // IRowset pointer

if (FAILED(hr))
{
     GetDetailedErrorInfo (pIOpenRowset,IID_IOpenRowset,szErrorSource,szErrorDescription);
            DumpError ( hr, (LPSTR)"IOpenRowset->OpenRowset FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IOpenRowset->OpenRowset FAILED!!" );
goto error;
}
}
else
{
// QueryInterface for ICommandText::SetCommandText
hr = pICommand->QueryInterface(IID_ICommandText,(LPVOID*)&pICommandText);

if( !FAILED(hr) )
{
// Tell the command object to copy this command text
// The command object will then use this query when we call ICommand::Execute
hr = pICommandText->SetCommandText(DBGUID_DBSQL, wszBuffer );

if (FAILED(hr))
{
        GetDetailedErrorInfo (pICommandText,IID_ICommandText,szErrorSource,szErrorDescription);
                DumpError ( hr, (LPSTR)"ICommandText->SetCommandText FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"ICommandText->SetCommandText FAILED!!" );
goto error;
}
}

if( pICommandText )
pICommandText->Release();

// From the command object, get a rowset object by executing command
hr = pICommand->Execute(
NULL,// pUnkOuter
IID_IRowset,// refiid
NULL,// disp parms
NULL,// rows affected
 (IUnknown**)&pIRowset);  // IRowset pointer

if (FAILED(hr))
{
GetDetailedErrorInfo (pICommand,IID_ICommand,szErrorSource,szErrorDescription);
            DumpError ( hr, (LPSTR)"ICommand->Execute FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"ICommand->Execute FAILED!!" );
goto error;
}
}

if(pIRowset)
{
*ppIRowset_out = pIRowset;
return NOERROR;
}
// NO Rowset Returning Statement
else
    return ResultFromScode(S_FALSE);

error:
*ppIRowset_out = NULL;

return hr;
}

//**********************************************************************

//
// GetDataFromRowset
//
// Purpose:
//
//     Pulls the data from a Rowset object.
//
// Parameters:
//
// IRowset*pIRowset    -  interface pointer on data provider's
//                             Rowset object
//
// Return Value:
//
//     S_OK        - Success
//     E_*         - Failure
//
// Function Calls:
//     Function                    Location
//
//   IRowset::QueryInterface   Clients Rowset pointer
//     SetupBindings               query.cpp
//     CreateAccessor              query.cpp
//     GetData                     query.cpp
//     CleanupRowset               query.cpp
//
//     IMalloc::Free               OLE task memory allocator
//
//     assert                      c runtime
//
// Comments:
//
//     At a high level, a consumer pulls the data from a Rowset object by:
//
//     1. getting metadata for the Rowset's columns
//     2. using that metadata, along with the consumer's own knowledge of
//        how it wants to recieve the data, to create bindings. Bindings
//        represent how the actual data in the Rowset's columns is
//        actually transferred to the consumer's buffer.
//     3. pass the bindings to the Rowset, and get in return an accessor
//        handle that represents that particular set of bindings
//     4. get the actual data
//     5. clean up the rowset (at a minumum, release the accessor)
//
//     GetDataFromRowset performs these steps by calling GetColumnsInfo,
//     SetupBindings, CreateAccessor, GetData, and CleanupRowset
//
//**********************************************************************
HRESULT GetDataFromRowset
(
IRowset*pIRowset
)
{
charszBuffer[MAXBUFLEN+1];// String Buffer
HWNDhList;// Result Listbox Handle
DWORD dwText;// Tab Stop for Listbox
unsigned short intnCount;// Index
signed short intswColLength = MAXDATALEN;// Column Data Length
intcch;// Count of characters
HCURSORhOldCursor;// Default Cursor Handle
HRESULThr = S_OK;// HRESULTS
    ULONGcCol;// # of Columns in Rowset
    DBCOLUMNINFO*pColumnInfo;// Array of DBCOLUMNINFO
    WCHAR*pStringsBuffer;// Storage for strings

// BINDINGS
ULONG cbMaxRowSize;// buffer size for 1 row's data
ULONG cBind;
DBBINDINGrgBind[MAX_COL];

// ACCESSORS
HACCESSORhAccessor= NULL;

// Asserts
assert(pIRowset   != NULL);
assert(g_pIMalloc != NULL);

// Get the Columns Info
GetColumnsInfo( pIRowset, &cCol, &pColumnInfo, &pStringsBuffer);

// Call GetColumnsInfo to calculate the number of columns in
// the result set, if more than the MAX_COL (the array boundary)
// limit the number to MAX_COL and display truncation warning.
// if it is 0, the statement probably was a non-SELECT simply return
if (cCol >= MAX_COL)
{
cCol = MAX_COL;
wsprintf(szDispBuffer, COLTRUNC_WARNG, MAX_COL);
MessageBox(hWndFrame, szDispBuffer, TRUNCERR, MB_OK | MB_ICONINFORMATION);
}
else if (cCol == 0)
{
SetCursor(hOldCursor);
goto error;
}

// Reset the display in the list box. Set tabstops to display
// multiple columns in the list box separated by tabs.
hList = GetDlgItem((HWND)GetWindowLong(hWndActiveChild, GWLAPP_HDLG), IDLIST_RSLT);

SendMessage(hList, LB_RESETCONTENT, 0, 0);
SendMessage(hList, WM_SETREDRAW, FALSE, 0);
dwText = LISTTABSTOP;
SendMessage(hList, LB_SETTABSTOPS, (WPARAM)1, (LPARAM)(LPINT)&dwText);

// Display a description of each column in the result set.
// Store the column name in the display buffer and make it
// the first entry in the results list box of the MDI child window.
for(nCount=0, szDispBuffer[0]='\0'; nCount < cCol; nCount++)
{
if (pColumnInfo[nCount].pwszName)
{
cch = WideCharToMultiByte(CP_ACP, 0, pColumnInfo[nCount].pwszName, -1, szBuffer, MAXBUFLEN+1, NULL, NULL);

// Truncate if needed.
// We should use GetTextExtents to determine width, but that's too complicated for now.
szBuffer[MAXCOLNAMELEN] = '\0';

// Might be empty string for column name.
if (cch)
strcat(szDispBuffer, szBuffer);
else
strcat(szDispBuffer, "<EMPTY STRING>");
}
else
{
// Missing the column name; it is unknown.
strcat(szDispBuffer, "<NULL STRING>");
}

dwText = strlen(szDispBuffer);
szDispBuffer[dwText++] = '\t';
szDispBuffer[dwText] = '\0';
}

// NULL Terminate the Display Buffer
if (*szDispBuffer)
szDispBuffer[strlen(szDispBuffer)-1]='\0';

// ADD the Column Info to the Screen
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPSTR)szDispBuffer);

// SetUp Bindings
hr = SetupBindings( cCol, pColumnInfo, rgBind, &cBind, &cbMaxRowSize );

if (FAILED(hr))
{
//DumpErrorHResult( hr, (LPSTR)"Setting up Bindings returned error!!" );
goto error;
}

// Create the Accessor
hr = CreateAccessor( pIRowset, rgBind, cBind, &hAccessor );

if (FAILED(hr))
{
//DumpErrorHResult( hr, (LPSTR)"Creating the Accessor returned error!!" );
goto error;
}

// GetData
hr = GetData( pIRowset, cbMaxRowSize, hAccessor, rgBind, cBind, pColumnInfo, cCol, hList, dwText, LB_ADDSTRING);

if (FAILED(hr))
{
//DumpErrorHResult( hr, (LPSTR)"Getting the Data returned error!!" );
goto error;
}

// restore the cursor to default value
SetCursor(hOldCursor);

error:
// Release the IColumnsInfo Object
if( pColumnInfo )
g_pIMalloc->Free( pColumnInfo );
if( pStringsBuffer )
g_pIMalloc->Free( pStringsBuffer );

// Clean-up the Rowset
CleanupRowset( pIRowset, hAccessor );

    return hr;
}

//**********************************************************************
//
// GetColumnsInfo
//
// Purpose:
//
//     Get the Columns metadata back for the provider to setup the bundings.
//
// Parameters:
//
//IRowset*pIRowset- IRowset pointer to QI off of
// ULONG cCol                - number of columns in metadata
// DBCOLUMNINFO*pColumnInfo         - pointer to column metadata
// WCHAR*pStringsBuffer      - pointer through which to return
//                                        an array of string data, one
//                                        structure per column bound
//
// Return Value:
//     S_OK         - Success
//     E_*          - Failure
//
// Function Calls:
//     Function                    Location
//
//     assert                      c runtime
//     IRowset::QueryInterface     provider's Rowset object
//     IColumnsInfo::GetColumnInfo provider's ColumnInfo object
//     IColumnsInfo::Release       provider's ColumnInfo object
//
//
// Comments:
//
//
//**********************************************************************
HRESULT GetColumnsInfo
(
IRowset*pIRowset,
ULONG* pcCol,
DBCOLUMNINFO**ppColumnInfo,
WCHAR**ppStringsBuffer
)
{

HRESULThr=NOERROR;// HRESULTS
IColumnsInfo* pIColumnsInfo = NULL;// IColumnsInfo Object

// QI for IColumnsInfo Object
hr = pIRowset->QueryInterface( IID_IColumnsInfo, (void **) &pIColumnsInfo );

if (FAILED(hr))
{
GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"QI for IColumnsInfo FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IColumnsInfo FAILED!!" );
goto error;
}

// Get column information from the command object via IColumnsInfo::GetColumnsInfo
hr = pIColumnsInfo->GetColumnInfo( pcCol, ppColumnInfo, ppStringsBuffer );

if (FAILED(hr))
{
GetDetailedErrorInfo (pIColumnsInfo,IID_IColumnsInfo,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"QI for IColumnsInfo FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IColumnsInfo->GetColumnsInfo FAILED!!" );
goto error;
}

error:
// Release the IColumnsInfo Object
if( pIColumnsInfo )
pIColumnsInfo->Release();
return hr;

}

//**********************************************************************
//
// SetupBindings
//
// Purpose:
//
//     Creates bindings that map the data in the rowset's columns to
//     slots in the consumer's data buffer.
//
// Parameters:
//
// ULONG cCol                - number of columns in rowset to bind
// DBCOLUMNINFO*pColumnInfo         - pointer to column metadata
// DBBINDING*rgBind_out          - out pointer through which to return
//                                        an array of binding structures, one
//                                        structure per column bound
// ULONG*pcBind_out          - out pointer through which to return
//                                        the number of columns bound (number
//                                        of valid elements in rgBind_out)
// ULONG*pcMaxRowSize_out    - out pointer through which to return
//                                        the buffer size necessary to hold
//                                        the largest row data
//
// Return Value:
//     S_OK         - Success
//     E_*          - Failure
//
// Function Calls:
//     Function                    Location
//
//     assert                      c runtime
//
//
// Comments:
//
//
//**********************************************************************
HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*pcBind_out,
ULONG*pcMaxRowSize_out
)
{
#define DEFAULT_CBMAXLENGTH 40// cbMaxLength for binding
ULONG dwOffset;// Length of a Row
ULONG iCol;// Column Count
ULONG iBind;// Binding Index

// Asserts
    assert(pColumnInfo!= NULL);
    assert(rgBind_out != NULL);
    assert(pcBind_out != NULL);
    assert(pcMaxRowSize_out != NULL);

   // Create bindings.
// Bind everything as a string just to keep things simple.
dwOffset = 0;
iBind=0;
for (iCol=0; iCol < cCol; iCol++)
{
// Binding Structure
rgBind_out[iBind].dwPart= DBPART_VALUE | DBPART_LENGTH |
  DBPART_STATUS;
rgBind_out[iBind].eParamIO  = DBPARAMIO_NOTPARAM;
rgBind_out[iBind].iOrdinal= pColumnInfo[iCol].iOrdinal;
rgBind_out[iBind].wType     = DBTYPE_STR;
rgBind_out[iBind].pTypeInfo = NULL;
rgBind_out[iBind].obValue   = dwOffset + offsetof(COLUMNDATA,bData);
rgBind_out[iBind].obLength  = dwOffset + offsetof(COLUMNDATA,dwLength);
rgBind_out[iBind].obStatus  = dwOffset + offsetof(COLUMNDATA,wStatus);
rgBind_out[iBind].cbMaxLen  = pColumnInfo[iCol].wType == DBTYPE_STR ?
  pColumnInfo[iCol].ulColumnSize + 1 : DEFAULT_CBMAXLENGTH;
rgBind_out[iBind].pObject= NULL;
rgBind_out[iBind].pBindExt= NULL;
rgBind_out[iBind].dwFlags= 0;
rgBind_out[iBind].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBind_out[iBind].bPrecision = 0;
rgBind_out[iBind].bScale= 0;

// LONG DATA hack
if(rgBind_out[iBind].cbMaxLen > 1000)
rgBind_out[iBind].cbMaxLen = 1000;

dwOffset += rgBind_out[iBind].cbMaxLen + offsetof( COLUMNDATA, bData );
dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
iBind++;
}

// Return Values
*pcBind_out   = iBind;
*pcMaxRowSize_out = dwOffset;

return S_OK;
}

//**********************************************************************
//
// CreateAccessor
//
// Purpose:
//
//     Passes a set of bindings to the data provider and recieves in return
//     an accessor handle that represents those bindings.
//
// Parameters:
// IRowset*pIRowset        - interface pointer on data provider's Rowset
//                                object
// DBBINDING*rgBind          - array of binding structures
// ULONGcBind           - number of binding structures in rgBind
// HACCESSOR*phAccessor_out  - out pointer through which to return an
//                                accessor handle that represents all the bindings
//                                in rgBind
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
// Function Calls:
//     Function                    Location
//
//     IRowset::QueryInterface     provider's Rowset object
//     IAccessor::CreateAccessor   provider's Rowset object
//     IAccessor::Release          provider's Rowset object
//
//     assert                      c runtime
//
// Comments:
//
//
//**********************************************************************
HRESULT CreateAccessor
(
IRowset*pIRowset,
DBBINDING*rgBind,
ULONGcBind,
HACCESSOR*phAccessor_out
)
{
IAccessor*pIAccessor = NULL;
HACCESSOR   hAccessor;
HRESULT hr;

// Asserts
assert(pIRowset   != NULL);
    assert(rgBind   != NULL);
    assert(phAccessor_out != NULL);

  // Get an accessor for our bindings from the rowset, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );

if (FAILED(hr))
{
GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"QI for IAccessor FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IAccessor FAILED!!" );
goto error;
}

hr = pIAccessor->CreateAccessor(
DBACCESSOR_ROWDATA,
cBind,
rgBind,
0,
&hAccessor,
NULL );

if (FAILED(hr))
{
GetDetailedErrorInfo (pIAccessor,IID_IAccessor,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"IAccessor->CreateAccessor FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IAccessor->CreateAccessor FAILED!!" );
goto error;
}

// Release the IAccessor Object
pIAccessor->Release();
pIAccessor = NULL;

// Return Value
*phAccessor_out = hAccessor;

return S_OK;

error:
if (pIAccessor)
    pIAccessor->Release();
*phAccessor_out = NULL;

return hr;
}

//**********************************************************************
//
// GetData
//
// Purpose:
//
//     Reads the data from a rowset.
//
// Parameters:
//
//    IRowset* pIRowset           - interface pointer on data provider's
//                                    Rowset object
//    ULONG    cMaxRowSize        - size of buffer needed to hold the data
//                                    for the largest row
//     HACCESSOR hAccessor          - accessor handle representing the set
//                                    of desired bindings
//     DBBINDING*rgBind  - needed only for pretty printing
//     ULONGcBind  - for pretty printing
//     DBCOLUMNINFO*pColumnInfo - for pretty printing
//     ULONGcCol- for pretty printing
//
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
// Function Calls:
//     Function                    Location
//
//     IRowset::GetNextRows        provider's Rowset object
//     IRowset::GetData            provider's Rowset object
//     IRowset::ReleaseRows        provider's Rowset object
//
//
//     malloc, free                c runtime
//     assert                      c runtime
//
//
//
// Comments:
//
//     GetData reads all the rows in the rowset, sequentially.
//
//
//**********************************************************************
HRESULT GetData
(
IRowset*pIRowset,
ULONG   cMaxRowSize,
    HACCESSOR   hAccessor,
    DBBINDING*rgBind,
    ULONGcBind,
    DBCOLUMNINFO*pColumnInfo,
    ULONGcCol,
HWNDhList,
DWORDdwText,
DWORDaddString
)
{
#define NUMROWS_CHUNK       20// Number of Rows to Grab at a Time

ULONG cRowsObtained;// Number of rows obtained
ULONGiRow;// Row Count
BYTE*pRowData = NULL;// Memory for Data
HROW rghRows[NUMROWS_CHUNK];// Row Handles
HROW*pRows = &rghRows[0];// Pointer to the Row Handles
ULONGcMaxColWidth;  // Needed for Output
HRESULT hr;// HRESULT

// Asserts
assert(pIRowset != NULL);
    assert(rgBind != NULL);
    assert(pColumnInfo  != NULL);

// Create a buffer for row data, big enough to hold the biggest row
pRowData = (BYTE *) malloc( cMaxRowSize );
if (!pRowData)
        goto error;

    // Process all the rows, NUMROWS_CHUNK rows at a time
while (1)
{
hr = pIRowset->GetNextRows(
0,// cbChapter
0,// cRowsToSkip
NUMROWS_CHUNK,// cRowsDesired
&cRowsObtained,// cRowsObtained
&pRows );// filled in w/ row handles

if (FAILED(hr))
{
    GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
            DumpError ( hr, (LPSTR)"IRowset->GetNextRows FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IRowset->GetNextRows FAILED!!" );
goto error;
}

// All done, no more rows left to get
if ( cRowsObtained == 0 )
break;

// Loop over rows obtained, getting data for each
for ( iRow=0; iRow < cRowsObtained; iRow++ )
{
hr = pIRowset->GetData(
rghRows[iRow],
hAccessor,
pRowData );

if (FAILED(hr))
{
                GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
                DumpError ( hr, (LPSTR)"IRowset->GetData FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IRowset->GetData FAILED!!" );
goto error;
}

// Print to the Screen
            DumpRow( rgBind, cBind, cMaxColWidth, pRowData, hList, dwText, addString );

// See if you are over the limit of rows
if ((iRow + 1) == MAX_ROW)
{
wsprintf(szDispBuffer, ROWTRUNC_WARNG, MAX_ROW);
MessageBox(hWndFrame, szDispBuffer, TRUNCERR, MB_OK | MB_ICONINFORMATION);
break;
}

}

// Release row handles
hr = pIRowset->ReleaseRows( cRowsObtained, rghRows, NULL, NULL, NULL );

if (FAILED(hr))
{
           GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
           DumpError ( hr, (LPSTR)"IRowset->ReleaseRows FAILED!!",szErrorSource,szErrorDescription);
       //DumpErrorHResult( hr, (LPSTR)"IRowset->ReleaseRows FAILED!!" );
   goto error;
}

}// end while

// Set the horizontal scroll extent in the list box and ask for repaint.
SendMessage(hList, LB_SETHORIZONTALEXTENT, (WPARAM)(cBind*LISTHORZSCROLL+LISTHORZEXT), 0);
SendMessage(hList, WM_SETREDRAW, TRUE, 0);

// free row data buffer
free( pRowData );
return S_OK;

error:
// Set the horizontal scroll extent in the list box and ask for repaint.
SendMessage(hList, LB_SETHORIZONTALEXTENT, (WPARAM)(cBind*LISTHORZSCROLL+LISTHORZEXT), 0);
SendMessage(hList, WM_SETREDRAW, TRUE, 0);

if( pRowData )
free( pRowData );

    return hr;
}

//**********************************************************************
//
// GetSchemaRowset
//
// Purpose:
//
//     Pulls the schema info out of the provider.
//
// Parameters:
//
// GUID  rguidSchema    -  SchemaRowset IID
//
//
// Return Value:
//
// Function Calls:
//     Function                      Location
//
//   IDBCreateCommand::QueryInterface   QI
//   IDBSchemaRowset->GetRowset  Clients Rowset pointer
//     GetDataFromRowset                 query.cpp
//
//     IMalloc::Free                 OLE task memory allocator
//
//     assert                        c runtime
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL GetSchemaRowset(GUID rguidSchema)
{
charszBuffer[MAXBUFLEN+1];// String Buffer
HCURSORhOldCursor;// Default Cursor Handle
IDBCreateCommand* pIDBCreate  = NULL;// IDBCreateCommand Object
IDBSchemaRowset*pIDBSchemaRowset = NULL;// SchemaRowset Object
ICommand*pICommand = NULL;// Command Object
IRowset*pIRowset = NULL;// Rowset Object
HRESULThr;// HRESULT

// Check if there is an active window available
if( !hWndActiveChild )
{
MessageBox( hWndFrame,
((SendMessage(hWndCrsrList, CB_GETCOUNT, 0, 0) <=0) ?
MAKECONNECT : OPENWINDOW ),
NOCOMMANDERROR, MB_OK | MB_ICONHAND);
return;
}

// Change cursor shape to hour glass
hOldCursor = SetCursor(LoadCursor((HINSTANCE)NULL, IDC_WAIT));

// Scan hdbc, hstmt values
GetWindowText(hWndActiveChild, (char*)szBuffer, MAXBUFLEN);
sscanf(szBuffer, SCANSESSIONCOMMAND_FORMAT, &pIDBCreate, &pICommand);

// QueryInterface for IDBSession::IDBSchemaRowset
hr = pIDBCreate->QueryInterface( IID_IDBSchemaRowset,
(LPVOID*)&pIDBSchemaRowset );

if (FAILED(hr))
{
        GetDetailedErrorInfo (pIDBCreate,IID_IDBCreateCommand,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"QI for IDBScemaRowset FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IDBScemaRowset FAILED!!" );
goto error;
}

// Get a SchemaRowset back
hr = pIDBSchemaRowset->GetRowset(
NULL,// punkOuter
rguidSchema,// schema IID
0L,// # of restrictions
NULL,// array of restrictions
IID_IRowset,// rowset interface
0L,// # of properties
NULL,// properties
(IUnknown**)&pIRowset);// rowset pointer

if (FAILED(hr))
{
        GetDetailedErrorInfo (pIDBSchemaRowset,IID_IDBSchemaRowset,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"IDBSchemaRowset->GetRowset FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IDBSchemaRowset->GetRowset FAILED!!" );
goto error;
}

// Get Data from the IRowset Object
hr = GetDataFromRowset( pIRowset );

if (FAILED(hr))
{
//DumpErrorHResult( hr, (LPSTR)"GetDataFromRowset returned error!!" );
goto error;
}

// Release Object
pIDBSchemaRowset->Release();
    pIDBSchemaRowset = NULL;

return;

error:
if( pIDBSchemaRowset )
pIDBSchemaRowset->Release();

return;
}

//**********************************************************************
//
// CleanupRowset
//
// Purpose:
//
//     Allows the rowset to perform any necessary cleanup.
//
// Parameters:
//
// IRowset*pIRowset    - interface pointer on data provider's Rowset
//                            object
// HACCESSOR hAccessor   - accessor handle to release
//
// Return Value:
//
//     S_OK        - Success
//     E_*         - Failure
//
// Function Calls:
//     Function                    Location
//
//     IRowset::QueryInterface     provider's Rowset object
//     IAccessor::ReleaseAccessor  provider's Rowset object
//     IAccessor::Release          provider's Rowset object
//
//     assert                      c runtime
//
//
// Comments:
//
//     In this sample, the only cleanup that the rowset needs to do is
//     release the accessor handle.
//
//**********************************************************************
HRESULT CleanupRowset
(
IRowset*pIRowset,
HACCESSOR hAccessor
)
{
IAccessor*pIAccessor = NULL;// Pointer to an Accessor
HRESULThr;// HRESULT

// Assert
    assert(pIRowset != NULL);

// Tell the rowset object it can release the accessor, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );

if (FAILED(hr))
{
        GetDetailedErrorInfo (pIRowset,IID_IRowset,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"QI for IAccessor FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"QI for IAccessor FAILED!!" );
goto error;
}

hr = pIAccessor->ReleaseAccessor( hAccessor, NULL );
if (FAILED(hr))
{
        GetDetailedErrorInfo (pIAccessor,IID_IAccessor,szErrorSource,szErrorDescription);
        DumpError ( hr, (LPSTR)"IAccessor->ReleaseAccessor FAILED!!",szErrorSource,szErrorDescription);
//DumpErrorHResult( hr, (LPSTR)"IAccessor->ReleaseAccessor FAILED!!" );
goto error;
}

pIAccessor->Release();
pIAccessor = NULL;

pIRowset->Release();
pIRowset = NULL;

return S_OK;

error:
if( pIAccessor )
    pIAccessor->Release();
if( pIRowset )
pIRowset->Release();

    return hr;
}

//**********************************************************************
//
// DumpRow
//
// Purpose:
//
//     Display the row to the screen.
//
// Parameters:
//
//
// Return Value:
//
//
// Function Calls:
//     Function                    Location
//
//     assert                      c runtime
//
//
// Comments:
//
//
//**********************************************************************
void DumpRow
(
    DBBINDING* rgBind,
    ULONGcBind,
    ULONGcMaxColWidth,
    BYTE* pData,
HWNDhList,
DWORDdwText,
DWORDaddString
    )
{
ULONG iBind;// Binding Count
    COLUMNDATA*pColumn;// Data Structure

char    szTempString[MAXDISPLAYSIZE+1];// Temporary Display Buffer, used to check data length

// Asserts
assert(rgBind);
assert( offsetof(COLUMNDATA, wStatus) == 0);

// Print each column we're bound to.
for (iBind=0, szDispBuffer[0]='\0'; iBind < cBind; iBind++)
{
// Columns are bound differently; not so easy.
// Print out to at least DEFAULT_CBMAXLENGTH width (pretty),
// Limit to first dwLength characters.
pColumn = (COLUMNDATA *) (pData + rgBind[iBind].obStatus);

// Check Status for NULL / OK / CANTCONVERT.
switch (pColumn->wStatus)
{
case DBSTATUS_S_ISNULL:
strcat(szDispBuffer, (LPSTR)"<NULL>");
break;
case DBSTATUS_S_OK:
// Truncate if needed.
         // We should use GetTextExtents to determine width.
strcpy (szTempString,(LPSTR)pColumn->bData);
if (strlen (szTempString)>MAXCOLNDATALENGTH)
{
  szTempString[MAXCOLNDATALENGTH] = '\0';
      strcat (szTempString,"...");
}
strcat (szDispBuffer,szTempString);
        break;
case DBSTATUS_E_CANTCONVERTVALUE:
strcat(szDispBuffer, "<can't convert value>");
break;
default:
strcat(szDispBuffer, "<unknown status>");
break;
}

dwText = strlen(szDispBuffer);
szDispBuffer[dwText++] = '\t';
szDispBuffer[dwText] = '\0';
}
// Take the last \t off the end
szDispBuffer[--dwText] = '\0';
SendMessage(hList, addString, 0, (LPARAM)(LPSTR)szDispBuffer);

}

//**********************************************************************
//
// GetDetailedErrorInfo
//
// Purpose:
//
//    Reads error from the error collection and passes it to DumpError
//
// Parameters:
//
// IUnknown   *  pBadObject          - Object that caused an error
//  GUID     IID_BadInterface         - Interface that contains method that caused the error
//  char * szErrorDescription_out     - Out | Error Description
//char * szErrorSource_out          - Out | Error Source
//
// Return Value:
//
// Comments:
// If any sort of failure is encountered the return values will be NULL
//   strings. Therefore no extended error info will be printed.
//**********************************************************************

HRESULT GetDetailedErrorInfo
      (
   IUnknown * pBadObject,
   GUID IID_BadIntereface,
   char * szErrorSource_out,
   char * szErrorDescription_out
  )
{

   IErrorInfo *         pErrorInfo;
   IErrorInfo *        pErrorInfoRec;
   IErrorRecords *      pErrorRecords;
   ISupportErrorInfo *  pSupportErrorInfo;
   HRESULT              hr;
   ULONG                i,ulNumErrorRecs;

   char                 szBuffer [MAXBUFLEN+1];  //Error Info Buffer
   DWORD                MYLOCALEID=0x0409;       //American English

   strcpy (szErrorDescription_out,"\0");
   strcpy (szErrorSource_out,"\0");

   hr = pBadObject->QueryInterface (IID_ISupportErrorInfo,
            (LPVOID FAR*)&pSupportErrorInfo);

   if (SUCCEEDED(hr))
   {
   hr = pSupportErrorInfo->InterfaceSupportsErrorInfo(IID_BadIntereface);
   if (hr ==S_OK)
   {
   //Get Error Object. Return if no object Exists
   hr = GetErrorInfo (0,&pErrorInfo);
           if (FAILED(hr))
   goto Error;

   //Get the IErrorRecord interface and get the count of error recs.
   hr = pErrorInfo->QueryInterface (IID_IErrorRecords,(LPVOID FAR*)&pErrorRecords);
   if (FAILED(hr))
   goto Error;

   hr = pErrorRecords->GetRecordCount (&ulNumErrorRecs);
   if (FAILED(hr))
   goto Error;

   //Go through and print messages

for (i=0;i<ulNumErrorRecs;i++)
   {
   BSTR bstrDescriptionOfError = NULL;
               BSTR bstrSourceOfError = NULL;
   WCHAR wszSourceOfError = NULL;
   WCHAR wszDescriptionOfError = NULL;

   hr = pErrorRecords->GetErrorInfo (i,MYLOCALEID,&pErrorInfoRec);
   if (FAILED (hr))
  goto Error;
   hr = pErrorInfoRec->GetDescription (&bstrDescriptionOfError);
   if (FAILED (hr))
  goto Error;
               hr = pErrorInfoRec->GetSource (&bstrSourceOfError);
   if (FAILED (hr))
  goto Error;

   if (bstrDescriptionOfError!=NULL )
   {
                    strcpy (szErrorDescription_out,"Error Description : ");
                WideCharToMultiByte(CP_ACP, 0, bstrDescriptionOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL);
          strcat (szErrorDescription_out,szBuffer);
        strcat (szErrorDescription_out,"\n");
        SysFreeString (bstrDescriptionOfError);
   }

   if (bstrSourceOfError!=NULL )
   {
                    strcpy (szErrorSource_out,"Error Source: ");
WideCharToMultiByte(CP_ACP, 0, bstrSourceOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL);
        strcat (szErrorSource_out,szBuffer);
        strcat (szErrorSource_out,"\n");
SysFreeString (bstrSourceOfError);
   }

              if (pErrorInfoRec)
   pErrorInfoRec->Release();
   }

          if (pErrorInfo)
  pErrorInfo->Release();
          if (pErrorRecords)
  pErrorRecords->Release();
   } //S_OK
   } //if (SUCCEEDED)

 Error :

 if (pSupportErrorInfo)
 pSupportErrorInfo->Release();
 return hr;
} //PrintErrorInfo

//**********************************************************************
//
// DumpError
//
// Purpose:
//
//     Dump the OLE DB HRESULT in a Message Box with a text Title Bar.
//
// Parameters:
//
// HRESULThr    - OLE DB HRESULT
//
// LPSTR lpStrBuffer - Title Bar text
//  LPSTR
//
// Return Value:
//
//
// Comments:
//
//
//**********************************************************************
void DumpError( HRESULT hr, LPSTR lpStrBuffer, LPSTR lpSource, LPSTR lpDescription)
{
CHAR* lpMessageBuffer;

lpMessageBuffer = new CHAR[512];
if( lpMessageBuffer )
{
sprintf((LPSTR)lpMessageBuffer, "HResult of 0x%0x (%ld) returned\n",
(long)hr,(long)hr);

if (lpSource!=NULL)
   strcat ((LPSTR)lpMessageBuffer,lpSource);
if (lpDescription!=NULL)
   strcat ((LPSTR)lpMessageBuffer,lpDescription);

   MessageBox( NULL, (LPCTSTR)lpMessageBuffer, lpStrBuffer, MB_OK);

   delete[] lpMessageBuffer;
}

} //DumpError

//**********************************************************************
//
// DumpErrorHResult
//
// Purpose:
//
//     Dump the OLE DB HRESULT in a Message Box with a text Title Bar.
//
// Parameters:
//
// HRESULThr    - OLE DB HRESULT
//
// LPSTR lpStrBuffer - Title Bar text
//
//
// Return Value:
//
//
// Function Calls:
//     Function                    Location
//
//     assert                      c runtime
//
//
// Comments:
//
//
//**********************************************************************
void DumpErrorHResult( HRESULT hr, LPSTR lpStrBuffer )
{
LPVOIDlpMessageBuffer;

if(0 != FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&lpMessageBuffer,
0,
NULL))
{
MessageBox( NULL, (LPCTSTR)lpMessageBuffer, lpStrBuffer, MB_OK);
LocalFree(lpMessageBuffer);
}
else
{
lpMessageBuffer = LocalAlloc(LPTR, 256);

if( lpMessageBuffer )
{
sprintf((LPSTR)lpMessageBuffer, "HResult of %ld returned", (long)hr);
MessageBox( NULL, (LPCTSTR)lpMessageBuffer, lpStrBuffer, MB_OK);
LocalFree(lpMessageBuffer);
}
}
}

//**********************************************************************
//
// NewIOpenRowsetWindow
//
// Purpose:
//
//     Create a new ICommand Object on the current IDBCreateCommand.
//
// Parameters:
//
// Return Value:
//
// Function Calls:
//     Function                         Location
//
//   IDBCreateCommand::CreateCommandprovider's Command object
//     ICommand::Release                provider's Command object
//
//
// Comments:
//
//
//**********************************************************************
void FAR PASCAL NewIOpenRowsetWindow()
{
int nCurrenthdbc;// Current PROVIDER
charszBuffer[MAXBUFLEN+1];// String in PROVIDER ComboBox on Toolbar
charszProvName[MAXBUFLEN+1];// DSN String
MDICREATESTRUCTmcs;                    // MDI Child Window Create Struc

IOpenRowset * pIOpenRowset= NULL;// IOpenRowset Object

// check if there is PROVIDER selected in the Combo-Box
if ((nCurrenthdbc = (int)SendMessage(hWndCrsrList, CB_GETCURSEL, 0, 0)) == CB_ERR)
{
MessageBox(hWndFrame, MAKECONNECT, NOSESSIONERROR, MB_OK | MB_ICONHAND);
return;
}

// check if the number of windows exceeds MAXCHILDWNDS
if (nChildCount >= MAXCHILDWNDS)
{
MessageBox(hWndFrame, MAXCHILDEXCEEDED, MAXCHLDERR, MB_OK | MB_ICONHAND);
return;
}

// Scan PROVIDER string and IDBCreateCommand value from the combo-box
SendMessage(hWndCrsrList, CB_GETLBTEXT, (WPARAM)nCurrenthdbc, (LPARAM)(LPSTR)szBuffer);

 sscanf(szBuffer, SCANPROVIDERSESSION_FORMAT, szProvName, &pIOpenRowset);

// create a new MDI client window. maximized, if the previous is so.
mcs.szClass = OLEDBMDICLASS;
mcs.szTitle = UNTITLED;
mcs.hOwner  = hAppInstance;
mcs.style   = hWndActiveChild && IsZoomed(hWndActiveChild) ? WS_MAXIMIZE : 0;
mcs.x = mcs.cx = mcs.y = mcs.cy = CW_USEDEFAULT;
hWndActiveChild = (HWND)(UINT)SendMessage(hWndMDIClient, WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mcs);

    // check if it was created, if it wasn't free up resource and flag warning
    if (!hWndActiveChild)
    {
MessageBox(hWndFrame, CREATECHILDERR, EXECERROR, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
return;
    }

// Display the Provider string, PROVIDER and IOpenRowset in the title
// of newly created window. Increment the child window counter
wsprintf(szBuffer, QUERY_STRING, pIOpenRowset);

SendMessage(hWndStmtList, CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuffer);
SendMessage(hWndStmtList, CB_SELECTSTRING, 0, (LPARAM)(LPSTR)szBuffer);

wsprintf(szBuffer, PROVIDER_SESSION_COMMAND_FORMAT, (LPSTR)szProvName, pIOpenRowset, NULL);

SetWindowText(hWndActiveChild, szBuffer);
nChildCount++;

// Update the Command Combo-Box on the tool bar.
ChangeCurrentICommand(hWndStmtList);
}


QURYDEMO.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB Main Include File
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module main include file
//
//-----------------------------------------------------------------------------

#ifdef RC_INVOKED
#define ID(id) id
#else
#define ID(id) MAKEINTRESOURCE(id)
#endif

/* Resource Identifiers */
#define BMP_NEWQUERYID(10)// new window button bitmap
#define BMP_RUNQUERYID(20)// exec query button bitmap
#defineAPPICONID(30)// application icon
#define QUERYWINDOWICONID(40)// MDI Child window icon
#define QURYDEMOMENUID(50)// Application menu
#define INITDIALOGID(60)// connect modal dialog box
#define DISCONNECTDIALOGID(70)// disconnect modal dialog box
#define ABOUTDIALOGID(80)// about modal dialog box
#define MDICHILDDIALOGID(90)// MDI Child modeless dialog box
#define APPACCELERATORID(100)// Accelerator table for the app

/* Menu Identifiers */
#define IDM_POPUPAPPSYS10// Placeholder, used by status bar
#define IDM_POPUPMDISYS20// Placeholder, used by status bar
#define IDM_POPUPLOGIN100// Placeholder, used by status bar
#define IDM_INITIALIZE110
#define IDM_DISCONNECT120
#define IDM_EXIT130
#define IDM_POPUPQUERY140// Placeholder, used by status bar
#define IDM_QUERY150
#define IDM_NEW160
#define IDM_POPUPSCHEMA170     // Placeholder, used by status bar
#define IDM_TYPES           180
#define IDM_TABLES          190
#define IDM_COLUMNS         200
#define IDM_POPUPWINDOW210     // Placeholder, used by status bar
#define IDM_TILE220
#define IDM_CASCADE230
#define IDM_ICONS240
#define IDM_CLOSEALL250
#define IDM_POPUPHELP260// Placeholder, used by status bar
#define IDM_APPHELP270
#define IDM_ABOUT280
#define IDM_MDICHILD290

#define IDM_CATALOGS        300
#define IDM_FOREIGN_KEYS    310
#define IDM_PRIMARY_KEYS    320
#define IDM_PROCEDURE_PARAMETERS  330
#define IDM_PROCEDURES      340
#define IDM_SCHEMATA        350
#define IDM_INDEXES         360  //added

/* child window ids */
#define IDW_TOOLBAR10
#define IDW_STATUSBAR20
#define IDW_MDICLIENT30

#define IDW_CRSRLIST10// toolbar comboboxes
#define IDW_COMMANDLIST20

#define IDCOMBO_PROVIDER10// connect dialog box controls
#define IDCOMBO_NAME20
#define IDTEXT_USERID30
#define IDTEXT_PASSWORD40
#define IDENUM              50

#define IDLIST_SESSION10// disconnect dialog box controls
#define IDLIST_COMMAND20
#define IDCLOSE_ACTVTY30
#define IDDISCONNECT40
#define IDICON_WARN50

#define IDTEXT_PRMPT10// MDI child's modeless dialog controls
#define IDTEXT_SQL20
#define IDLIST_RSLT30

/* MDI Child Window Offsets */
#define CBWNDEXTRA4// storage in MDI Child Window
#define GWLAPP_HDLG0// child dialog handle

/* Other Constants */
#define MAXCHILDWNDS    10//maximum child windows allowed
#define MAXBUFLEN256//display buffer size
#define MAX_COL35//maximum column in result set
#define MAX_ROW100//maximum number of rows
#define MAXDATALEN25//maximum data length per column
#define MAXCOLNAMELEN20
#define MAXCOLNDATALENGTH   23
#define MAXDISPLAYSIZEMAX_COL*(MAXDATALEN+1)
#defineMINWIDTH415//minimum width of app window
#defineMINHEIGHT350//minimum height of app window
#define LISTTABSTOP100//tabstop in the display listbox
#define LISTHORZSCROLL(LISTTABSTOP*2)//horizontal scroll for the listbox
#define LISTHORZEXT0//horizontal extent inthe list box
#define WINDOWMENUPLACE2//Window menu place for adding MDI child windows
#define IDT_STATUSTIMER1//timer id
#define TIMERDELAY60000//timer duration
#define BTTNX345//first toolbar button location
#define BTTNY2//button y position
#defineBTTNWIDTH25                      //button width
#define BTTNHEIGHT23                      //button height
#define BTTNMARGIN5//distance between toolbar buttons
#define TOOLBARMARGINY5//fixed margin w/o combobox height
#define STATUSBARMARGINY8//fixed margin w/o textbox height

/* String Constants */
#define POPUPLOGIN"&Login"
#define MENUITEMCONNECT"&Connect... \aCtrl+C"
#define MENUITEMDISCONNECT"&Unitialize... \aCtrl+U"
#defineMENUITEMEXIT"E&xit"
#define POPUPQUERY"&Command"
#define MENUITEMQUERY"&Execute Command Text \aCtrl+E"
#define MENUITEMNEW"&New Command \aCtrl+N"
#define POPUPSCHEMA"&Schema \aCtrl+S"
#define MENUITEMTYPES"&PROVIDER_TYPES \aCtrl+P"
#defineMENUITEMTABLES"T&ABLES \aCtrl+A"
#define MENUITEMCOLUMNS"&COLUMNS \aCtrl+C"
#define POPUPWINDOW"&Window"
#define MENUITEMTILE"&Tile"
#defineMENUITEMCASCADE"&Cascade"
#define MENUITEMICONS"Arrange &Icons"
#defineMENUITEMCLOSEALL"Close &All"
#define POPUPHELP"&Help"
#define MENUITEMAPPHELP"&Help on Qurydemo \aF1"
#defineMENUITEMABOUT"&About Qurydemo..."

#define OLEDBFRAMECLASS"QDEMO32MAIN"

#define OLEDBTOOLCLASS"QDEMOTool"
#define OLEDBSTATUSCLASS"QDEMOStatus"
#define OLEDBMDICLASS"QDEMOMDI"
#define COMBOBOXCLASS"COMBOBOX"
#define MDICLIENTCLASS"MDICLIENT"
#define UNTITLED"Untitled"
#define APPTITLE"Microsoft OLE DB Sample Query Program"
#defineEXECERROR"Execution Error"
#define INITERROR"Failed to initialize Session interface"
#define CLASSERROR"Failed to register application class"
#define CREATEMAINERR"Failed to create application main window"
#define CREATECHILDERR"Failed to create a new Command"
#define ALPHABETS"abcdefghijklmnopqrstuvwxyz"
#define TIMETEXT"MM:MM MM"
#define DATETEXT"MMM-MM"
#define TIMEFORMAT" %I:%M %p"
#define DATEFORMAT" %b-%d"
#define NOPROVIDERERR"Please provide a Data Source"
#define MOREINFO"Incomplete Information"

#define STATUSPOPUPAPPSYS   " Control menu for the application "
#define STATUSPOPUPMDISYS" Control menu for the window "
#define STATUSRESTORE       " Restore window to normal "
#define STATUSMOVE          " Move window "
#define STATUSSIZE          " Size window "
#define STATUSMINIMIZE      " Minimize window "
#define STATUSMAXIMIZE      " Maximize window "
#define STATUSCLOSE         " Close window "
#define STATUSNEXTWINDOW    " Switch to next window "
#define STATUSPREVWINDOW    " Switch to previous window "
#define STATUSTASKLIST      " Bring up task list "
#define STATUSPOPUPLOGIN" Login and logout commands "
#define STATUSCONNECT" Connect with default parameters... "
#define STATUSDISCONNECT" Release open sessions... "
#define STATUSEXIT" Exit application "
#define STATUSPOPUPQUERY" Commands "
#define STATUSQUERY" Execute Command text... "
#define STATUSNEW" New Command on current Session... "
#define STATUSPOPUPSCHEMA" IDBSchema Commands "

#define STATUSCATALOGS" DBSCHEMA_CATALOGS Rowset "
#define STATUSCOLUMNS" DBSCHEMA_COLUMNS Rowset "
#define STATUSFOREIGN_KEYS" DBSCHEMA_FOREIGN_KEYS Rowset "
#define STATUSPRIMARY_KEYS" DBSCHEMA_PRIMARY_KEYS Rowset "
#define STATUSPROCEDURE_PARAMETERS" DBSCHEMA_PROCEDURE_PARAMETERS Rowset "
#define STATUSPROCEDURES" DBSCHEMA_PROCEDURES Rowset "
#define STATUSSCHEMATA" DBSCHEMA_SCHEMATA Rowset "
#define STATUSTYPES" DBSCHEMA_PROVIDER_TYPES Rowset "
#define STATUSTABLES" DBSCHEMA_TABLES Rowset "

#define STATUSPOPUPWINDOW" Window management commands "
#define STATUSTILE" Tile all normal child windows "
#define STATUSCASCADE" Cascade all normal child windows "
#define STATUSICONS" Arrange iconic child windows "
#define STATUSCLOSEALL" Close all child windows "
#define STATUSMDICHILD      " Activate the window marked as %d"
#define STATUSPOPUPHELP" Help commands "
#define STATUSAPPHELP" Display help on application "
#define STATUSABOUT" About application "
#define STATUSDEFAULT" Ready "

#define DEFDBNAME"?"
#define PROVIDER_SESSION_FORMAT"%s!%lX"
#define PROVIDER_SESSION_COMMAND_FORMAT"Provider:=%s!Session=%lX!Command=%lX"
#define SCANPROVIDERSESSION_FORMAT"%[^!]!%lX"
#define SCANSESSION_FORMAT"%*[^!]!%lX"
#define SCANSESSION_TITLEFORMAT"Provider:=%*[^!]!Session=%lX"
#define SCANSESSIONCOMMAND_FORMAT"Provider:=%*[^!]!Session=%lX!Command=%lX"
#define SCANCOMMAND_TITLEFORMAT"Provider:=%*[^!]!Session=%*lX!Command=%lX"
#define CUR_MARK"<-"
#define CURQUERY_STRING"Command!%lX<-"
#define QUERY_STRING"Command!%lX"
#define MAKECONNECT"Please connect to a data source using Login menu."
#defineOPENWINDOW"Please open a window from Query menu."
#define NOSESSIONERROR"No Session Available"
#define NOCOMMANDERROR"No Command Window Available"
#define MAXCHILDEXCEEDED"Please close a child window and try again."
#define MAXCHLDERR"Error - Too many Child Windows"
#define COLTRUNC_WARNG"Number of columns in display truncated to %u"
#define ROWTRUNC_WARNG"Number of rows in display truncated to %u"
#define TRUNCERR"Data Truncation"
#define CLOSEALLSESSION"All open sessions have been released."
#define LOGOUTINFO"Log Out Information"
#define SQLERR_FORMAT"SQL Error State:%s, Native Error Code: %lX, OLEDB Error: %s"
#define SQLERRMSGTITLE"SQL_ERROR results"
#define SQLERRCNTDTITLE"SQL_ERROR results continued"
#define SQLWRNMSGTITLE"SQL_SUCCESS_WITH_INFO results"
#define SQLWRNCNTDTITLE"SQL_SUCCESS_WITH_INFO results continued"
#define NULLDATASTRING"SQL_NULL_DATA"

/* Globals */
//HWNDhWndFrame;      // Main Frame Window handle
//HWNDhWndCrsrList;   // hdbc(s) combobox on the tool bar
//HWNDhWndStmtList;   // hstmt(s) combobox on the tool bar
//HWNDhWndMDIClient;  // MDI Client window handle
//HWNDhWndActiveChild;// Current active MDI Child window
//HINSTANCEhAppInstance;   // Application instance

/* Macros and Function Prototypes */
#ifdef WIN32
#defineACTIVATEWINDOW(h)SetForegroundWindow(h)
#else
#defineACTIVATEWINDOW(h)SetFocus(h)
#endif

#define DRAWBTTNRECT(h,x,y,cx,cy)MoveToEx(h,x+1,y,NULL);\
LineTo(h,x+cx,y);\
MoveToEx(h,x+cx,y+1,NULL);\
LineTo(h,x+cx,y+cy);\
MoveToEx(h,x+cx-1,y+cy,NULL);\
LineTo(h,x,y+cy);\
MoveToEx(h,x,y+cy-1,NULL);\
LineTo(h,x,y);

#define DRAWBTTNLIFT1(h,x,y,cx,cy)MoveToEx(h,x+1,y+cy-1,NULL);\
LineTo(h,x+1,y+1);\
LineTo(h,x+cx-1,y+1);

#defineDRAWBTTNLIFT2(h,x,y,cx,cy)MoveToEx(h,x+1,y+cy-1,NULL);\
LineTo(h,x+cx-1,y+cy-1);\
LineTo(h,x+cx-1,y);\
MoveToEx(h,x+2,y+cy-2,NULL);\
LineTo(h,x+cx-2,y+cy-2);\
LineTo(h,x+cx-2,y+1);
// MAIN.CPP
intPASCALWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
longCALLBACKMainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
longCALLBACKToolbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
longCALLBACKStatusbarProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
longCALLBACKMDIChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOLCALLBACKConnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOLCALLBACKDisconnectDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOLCALLBACKAboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOLCALLBACKMDIChildDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
VOIDFAR PASCALDrawBitmap(HDC hDC, int iLeft, int iTop, HBITMAP hBitmap);

// QURYDEMO.CPP
BOOLFAR PASCALInitEnvironment();
voidFAR PASCALDisplayProviders(HWND hWnd);
BOOLFAR PASCAL  ConnectDatabase(HWND hWnd);
voidFAR PASCALDisplayConnections(HWND hWndhdbc);
voidFAR PASCALDisplayICommands(HWND hWndhstmt, HWND hWndhdbc, int nCrsrIndex);
voidFAR PASCALNewICommandWindow();
voidFAR PASCALNewIOpenRowsetWindow();
voidFAR PASCALChangeCurrentICommand(HWND hWndStmtList);
voidFAR PASCALChangeCurrentCursor(HWND hWndCrsrList);
voidFAR PASCALDisplayNewCrsrAndICommand();
voidFAR PASCALFreeConnect(HWND hWndhdbc);
voidFAR PASCALFreeICommand(HWND hWndhstmt, HWND hWndhdbc, int nIndex);
voidFAR PASCALCloseICommandWindow(HWND hWnd);
voidFAR PASCALExecuteQuery();
BOOLFAR PASCALCloseIDBCreateCommand();
HRESULT DisplayDataSource(HWND hWnd);
HRESULTFreeEnvironment();
voidFAR PASCALGetSchemaRowset(GUID rguidSchema);

HRESULT EnumerateProviders ();

HRESULT ExecuteCommand
(
IUnknown*pIUnknown,
ICommand*pICommand,
LPWSTRwszBuffer,
IRowset**ppIRowset_out
);

HRESULT GetDataFromRowset
(
IRowset*pIRowset
);

HRESULT GetColumnsInfo
(
IRowset*pIRowset,
ULONG* pcCol,
DBCOLUMNINFO**ppColumnInfo,
WCHAR**ppStringsBuffer
);

HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*cBind_out,
    ULONG*          pcMaxRowSize_out
);

HRESULT CreateAccessor
(
IRowset*pIRowset,
DBBINDING*rgBind,
ULONGcBind,
HACCESSOR*phAccessor_out
);

HRESULT GetData
(
IRowset*pIRowset,
ULONG   cMaxRowSize,
    HACCESSORhAccessor,
    DBBINDING*    rgBind,// needed for pretty printing
    ULONG    cBind,    // for pretty printing
    DBCOLUMNINFO*pColumnInfo, // for pretty printing
    ULONGcCol,// for pretty printing
HWNDhList,
DWORDdwText,
DWORDaddString
);

HRESULT CleanupRowset
(
IRowset*pIRowset,
HACCESSOR hAccessor
);

void DumpRow
(
    DBBINDING* rgBind,
    ULONGcBind,
    ULONGcMaxColWidth,
    BYTE* pData,
HWNDhList,
DWORDdwText,
DWORDaddString
    );

HRESULT GetDetailedErrorInfo
      (
   IUnknown * pBadObject,
   GUID IID_BadIntereface,
       char * szErrorDescription_out,
   char * szErrorSource_out
  );

void DumpError
     ( HRESULT hr,
   LPSTR lpStrBuffer,
   LPSTR lpSource,
   LPSTR lpDescription
  );

void DumpErrorHResult
(
HRESULT hr,
LPSTR lpStrBuffer
);

// How to lay out each column in memory.
// Note that bData should be DWORD aligned, since it may contain a DWORD.
struct COLUMNDATA
{
DBSTATUSwStatus;// status of column
DWORDdwLength;// length of data (not space allocated)
BYTEbData[1];// data here and beyond
};

/******************************* End of File ********************************/


QURYVER.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB Version File
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module version include file
//
//

// Constants -----------------------------------------------------------------
#ifndef _AXVER_H_
#define _AXVER_H_

#define VER_FILEVERSION  01,01,24,17
#define VER_FILEVERSION_STR  " 1.01.2417\0"
#define VER_PRODUCTVERSION  01,01,24,17
#define VER_PRODUCTVERSION_STR  " 1.01.2417\0"

#define VER_FILEFLAGSMASK(VS_FF_DEBUG | VS_FF_PRERELEASE)
#ifdef DEBUG
#define VER_FILEFLAGS(VS_FF_DEBUG)
#else
#define VER_FILEFLAGS(0)
#endif

#define VER_FILEOSVOS_NT_WINDOWS32

#define VER_COMPANYNAME_STR"Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR"Microsoft OLE DB\0"
#define VER_LEGALCOPYRIGHT_STR"Copyright \251 Microsoft Corporation 1994-1996\0"

// Resource DLL Version Resource ID
#define IDS_RESDLL_VER1

#endif
//--------------------


RESOURCE.H

//{{NO_DEPENDENCIES}}
//Microsoft OLE DB QUERY DEMO Sample
//(C) Copyright 1994 - 1996 By Microsoft Corporation.
//
// Microsoft Developer Studio generated include file.
// Used by qurydemo.rc
//
#define ENUMDIALOG                      102

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40006
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


DUMP.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Consumer
// (C) Copyright 1995 - 1996 Microsoft Corporation. All Rights Reserved.
//
// File name: DUMP.CPP
//
//      Dump\output routines for the SAMPCLNT sample OLE DB consumer.
//
//      See README.TXT for more information on the SAMPCLNT sample.
//
// Functions:
//
//      See SAMPCLNT.H for function prototypes
//

#include "sampclnt.h"

void DumpErrorMsg
(
    const char* format,
    ...
)
{
va_list argptr;

assert(format != NULL);

// log this message to stderr and to our log file
va_start( argptr, format );
tvfprintf( stderr, format, argptr);
    tvfprintf( g_fpLogFile, format, argptr);
va_end( argptr );
}

void DumpStatusMsg
(
    const char* format,
    ...
)
{
va_list argptr;

assert(format != NULL);

// log this message to stdout and to our log file
va_start( argptr, format );
tvfprintf( stdout, format, argptr );
    tvfprintf( g_fpLogFile, format, argptr );
va_end( argptr );
}

HRESULT DumpErrorHResult
(
HRESULT      hr_return,
const char  *format,// can be NULL
...
)
{
char     buff[100];
int      cBytesWritten;
va_list  argptr;

//
// Dump an error message.
// Print the text of the HRESULT,
// Return the HRESULT we were passed.

    // these result codes were generated from the oledberr.h
static Note ResultCodes[] = {
// oledberr.h error codes
NOTE(DB_E_BADACCESSORHANDLE),
NOTE(DB_E_BADACCESSORHANDLE),
NOTE(DB_E_ROWLIMITEXCEEDED),
NOTE(DB_E_READONLYACCESSOR),
NOTE(DB_E_SCHEMAVIOLATION),
NOTE(DB_E_BADROWHANDLE),
NOTE(DB_E_OBJECTOPEN),
NOTE(DB_E_BADBINDINFO),
NOTE(DB_SEC_E_PERMISSIONDENIED),
NOTE(DB_E_NOTAREFERENCECOLUMN),
NOTE(DB_E_NOCOMMAND),
NOTE(DB_E_BADBOOKMARK),
NOTE(DB_E_BADLOCKMODE),
NOTE(DB_E_PARAMNOTOPTIONAL),
NOTE(DB_E_BADRATIO),
NOTE(DB_E_ERRORSINCOMMAND),
NOTE(DB_E_CANNOTFREE),
NOTE(DB_E_BADSTARTPOSITION),
NOTE(DB_E_NOTREENTRANT),
NOTE(DB_E_NOAGGREGATION),
NOTE(DB_E_DELETEDROW),
NOTE(DB_E_CANTFETCHBACKWARDS),
NOTE(DB_E_ROWSNOTRELEASED),
NOTE(DB_E_BADSTORAGEFLAG),
NOTE(DB_E_BADSTATUSVALUE),
NOTE(DB_E_CANTSCROLLBACKWARDS),
NOTE(DB_E_INTEGRITYVIOLATION),
NOTE(DB_E_ABORTLIMITREACHED),
NOTE(DB_E_ROWSETINCOMMAND),
NOTE(DB_E_DUPLICATEINDEXID),
NOTE(DB_E_NOINDEX),
NOTE(DB_E_INDEXINUSE),
NOTE(DB_E_NOTABLE),
NOTE(DB_E_CONCURRENCYVIOLATION),
NOTE(DB_E_BADCOPY),
NOTE(DB_E_BADPRECISION),
NOTE(DB_E_BADSCALE),
NOTE(DB_E_BADID),
NOTE(DB_E_BADTYPE),
NOTE(DB_E_DUPLICATECOLUMNID),
NOTE(DB_E_DUPLICATETABLEID),
NOTE(DB_E_TABLEINUSE),
NOTE(DB_E_NOLOCALE),
NOTE(DB_E_BADRECORDNUM),
NOTE(DB_E_BOOKMARKSKIPPED),
NOTE(DB_E_BADPROPERTYVALUE),
NOTE(DB_E_INVALID),
NOTE(DB_E_BADACCESSORFLAGS),
NOTE(DB_E_BADSTORAGEFLAGS),
NOTE(DB_E_BYREFACCESSORNOTSUPPORTED),
NOTE(DB_E_NULLACCESSORNOTSUPPORTED),
NOTE(DB_E_NOTPREPARED),
NOTE(DB_E_BADACCESSORTYPE),
NOTE(DB_E_WRITEONLYACCESSOR),
NOTE(DB_SEC_E_AUTH_FAILED),
NOTE(DB_E_CANCELED),
NOTE(DB_E_BADSOURCEHANDLE),
NOTE(DB_S_ROWLIMITEXCEEDED),
NOTE(DB_S_COLUMNTYPEMISMATCH),
NOTE(DB_S_TYPEINFOOVERRIDDEN),
NOTE(DB_S_BOOKMARKSKIPPED),
NOTE(DB_S_ENDOFROWSET),
NOTE(DB_S_BUFFERFULL),
NOTE(DB_S_CANTRELEASE),
NOTE(DB_S_DIALECTIGNORED),
NOTE(DB_S_UNWANTEDPHASE),
NOTE(DB_S_COLUMNSCHANGED),
NOTE(DB_S_ERRORSRETURNED),
NOTE(DB_S_BADROWHANDLE),
NOTE(DB_S_DELETEDROW),
NOTE(DB_S_STOPLIMITREACHED),
NOTE(DB_S_LOCKUPGRADED),
NOTE(DB_S_PROPERTIESCHANGED),
NOTE(DB_S_ERRORSOCCURRED),
NOTE(DB_S_PARAMUNAVAILABLE),
NOTE(DB_S_MULTIPLECHANGES),

// winerr.h
NOTE(E_UNEXPECTED),
NOTE(E_NOTIMPL),
NOTE(E_OUTOFMEMORY),
NOTE(E_INVALIDARG),
NOTE(E_NOINTERFACE),
NOTE(E_POINTER),
NOTE(E_HANDLE),
NOTE(E_ABORT),
NOTE(E_FAIL),
NOTE(E_ACCESSDENIED),
NOTE(S_OK),
NOTE(S_FALSE),
NOTE(E_UNEXPECTED),
NOTE(E_NOTIMPL),
NOTE(E_OUTOFMEMORY),
NOTE(E_INVALIDARG),
NOTE(E_NOINTERFACE),
NOTE(E_POINTER),
NOTE(E_HANDLE),
NOTE(E_ABORT),
NOTE(E_FAIL),
NOTE(E_ACCESSDENIED),
// BindMoniker Errors
NOTE(MK_E_NOOBJECT),
NOTE(MK_E_EXCEEDEDDEADLINE),
NOTE(MK_E_CONNECTMANUALLY),
NOTE(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED),
NOTE(STG_E_ACCESSDENIED),
NOTE(MK_E_SYNTAX),
NOTE(MK_E_CANTOPENFILE),
};

// Format the message.
// Print name of hresult code.

if (format)
{
va_start( argptr, format );
cBytesWritten = _vsnprintf( buff, sizeof(buff), format, argptr );
va_end( argptr );
}
else
strcpy( buff, "" );

// log to stderr and also to our log file
tfprintf( stderr, "%.*s: Returned %.30s\n",
sizeof(buff), buff,
GetNoteString( ResultCodes, NUMELEM(ResultCodes), GetScode(hr_return)) );

   tfprintf( g_fpLogFile, "%.*s: Returned %.30s\n",
sizeof(buff), buff,
GetNoteString( ResultCodes, NUMELEM(ResultCodes), GetScode(hr_return)) );

return ResultFromScode( hr_return );
}

void DumpColumnsInfo
(
    DBCOLUMNINFO* pColInfo,
    ULONG      cCol
    )
{
ULONG j;

assert(pColInfo != NULL);

tfprintf( g_fpLogFile, "\nColumn Information:\n\n");

for (j=0; j < cCol; j++)
{
WriteColumnInfo( g_fpLogFile, &pColInfo[j] );
}
}

void WriteColumnInfo
(
FILE*fp,
DBCOLUMNINFO*p
)
{
DBID     *pCol;
    DBKIND      eKind;
wchar_t wszGuidBuff[MAX_GUID_STRING];
    wchar_t     wszNameBuff[MAX_GUID_STRING];

static char *szDbcolkind[] = { "Guid+Name", "Guid+PropID", "Name",
    "Guid+Name", "Guid+PropID", "PropID", "Guid" };

assert(p != NULL);

// For DBTYPEENUM.  Doesn't need to be in order.
// Below we mask off the high bits.
static Note typenotes[] =
    {
NOTE(DBTYPE_EMPTY),
NOTE(DBTYPE_NULL),
NOTE(DBTYPE_I2),
NOTE(DBTYPE_I4),
NOTE(DBTYPE_R4),
NOTE(DBTYPE_R8),
NOTE(DBTYPE_CY),
NOTE(DBTYPE_DATE),
NOTE(DBTYPE_BSTR),
NOTE(DBTYPE_IDISPATCH),
NOTE(DBTYPE_ERROR),
NOTE(DBTYPE_BOOL),
NOTE(DBTYPE_VARIANT),
NOTE(DBTYPE_IUNKNOWN),
NOTE(DBTYPE_DECIMAL),
NOTE(DBTYPE_UI1),
NOTE(DBTYPE_ARRAY),
NOTE(DBTYPE_BYREF),
NOTE(DBTYPE_I1),
NOTE(DBTYPE_UI2),
NOTE(DBTYPE_UI4),
NOTE(DBTYPE_I8),
NOTE(DBTYPE_UI8),
NOTE(DBTYPE_GUID),
NOTE(DBTYPE_VECTOR),
NOTE(DBTYPE_RESERVED),
NOTE(DBTYPE_BYTES),
NOTE(DBTYPE_STR),
NOTE(DBTYPE_WSTR),
NOTE(DBTYPE_NUMERIC),
NOTE(DBTYPE_UDT),
NOTE(DBTYPE_DBDATE),
NOTE(DBTYPE_DBTIME),
NOTE(DBTYPE_DBTIMESTAMP),
    };

static Note flagnotes[] =
    {
NOTE(DBCOLUMNFLAGS_ISBOOKMARK),
NOTE(DBCOLUMNFLAGS_MAYDEFER),
NOTE(DBCOLUMNFLAGS_WRITE),
NOTE(DBCOLUMNFLAGS_WRITEUNKNOWN),
NOTE(DBCOLUMNFLAGS_ISFIXEDLENGTH),
NOTE(DBCOLUMNFLAGS_ISNULLABLE),
NOTE(DBCOLUMNFLAGS_MAYBENULL),
NOTE(DBCOLUMNFLAGS_ISLONG),
NOTE(DBCOLUMNFLAGS_ISROWID),
NOTE(DBCOLUMNFLAGS_ISROWVER),
NOTE(DBCOLUMNFLAGS_CACHEDEFERRED),
};

pCol = & p->columnid;
    eKind = pCol->eKind;

    // stringize GUID for pretty printing
    switch (eKind)
        {
        case DBKIND_GUID_NAME:
        case DBKIND_GUID_PROPID:
        case DBKIND_GUID:
        StringFromGUID2( pCol->uGuid.guid, wszGuidBuff, sizeof(wszGuidBuff) );
            break;
        case DBKIND_PGUID_NAME:
        case DBKIND_PGUID_PROPID:
        StringFromGUID2( *(pCol->uGuid.pguid), wszGuidBuff, sizeof(wszGuidBuff) );
            break;
        default:
            wcscpy( wszGuidBuff, L"<none>" );
            break;
        }

    // stringize name or propID for pretty printing
    switch (eKind)
        {
        case DBKIND_GUID_NAME:
        case DBKIND_NAME:
        case DBKIND_PGUID_NAME:
            swprintf( wszNameBuff, L"[name=%.50S]", pCol->uName.pwszName ? pCol->uName.pwszName : L"(unknown)" );
            break;
        case DBKIND_GUID_PROPID:
        case DBKIND_PGUID_PROPID:
        case DBKIND_PROPID:
            swprintf( wszNameBuff, L"[propid=%lu]", pCol->uName.ulPropid );
            break;
        default:
            wcscpy( wszNameBuff, L"" );
            break;
        }

    // pretty print column info
    tfprintf( fp, "ColumnId [kind=%.40s] [guid=%.40S] %.60S\n",
        szDbcolkind[eKind], wszGuidBuff, wszNameBuff );

// Now move on to other stuff...
// Name in DBCOLUMNINFO different than name in DBCOLUMNID (maybe).
tfprintf(fp, "  Name          = '%.50S'\n", p->pwszName );
tfprintf(fp, "  iOrdinal      = %d\n", p->iOrdinal);
tfprintf(fp, "  wType         = %.100s\n",
GetNoteString( typenotes, NUMELEM(typenotes),
    p->wType & (~DBTYPE_BYREF) & (~DBTYPE_ARRAY) & (~DBTYPE_VECTOR) ) );
if (p->wType & DBTYPE_BYREF)
tfprintf(fp, "      (BYREF)\n");
if (p->wType & DBTYPE_ARRAY)
tfprintf(fp, "      (ARRAY)\n");
if (p->wType & DBTYPE_VECTOR)
tfprintf(fp, "      (VECTOR)\n");
tfprintf(fp, "  ulColumnSize  = %ld\n", p->ulColumnSize );
tfprintf(fp, "  bPrecision    = %b\n",  p->bPrecision );
tfprintf(fp, "  bScale        = %b\n",  p->bScale );
tfprintf(fp, "  dwFlags       = %s\n\n",
GetNoteStringBitvals( flagnotes, NUMELEM(flagnotes), p->dwFlags ) );

}

char* GetNoteString
    (
Note * rgNote,
int    cNote,
DWORD  dwValue
)
{
int j;

assert(rgNote != NULL);

// Scan a table of value/string,
// return ptr to string found.

for (j=0; j < cNote; j++) {
if (rgNote[j].dwFlag == dwValue)
return rgNote[j].szText;
}
return "<unknown>";
}

char*GetNoteStringBitvals
(
Note* rgNote,
int     cNote,
DWORD   dwValue
)
{
static char buff[400];
int j;

assert(rgNote != NULL);

// Make a string that combines all the bits ORed together.

strcpy(buff, "");
for (j=0; j < cNote; j++) {
if (rgNote[j].dwFlag & dwValue) {
if (buff[0])
strcat( buff, " | " );
strcat( buff, rgNote[j].szText );
}
}
assert(strlen(buff) < sizeof(buff));
return buff;
}

ULONG CalcPrettyPrintMaxColWidth
    (
    DBBINDING*rgBind,
    ULONG       cBind
    )
{
ULONGcMaxWidth;
    ULONG   cTotalWidth;
    ULONGiBind;

assert(rgBind != NULL);

cMaxWidth = DEFAULT_CBMAXLENGTH;
while (1)
{
cTotalWidth = 0;

for (iBind=0; iBind < cBind; iBind++)
cTotalWidth += min( cMaxWidth, rgBind[iBind].cbMaxLen ) + 1;

if (cTotalWidth < PRETTYPRINT_MAXTOTALWIDTH || cMaxWidth < PRETTYPRINT_MINCOLWIDTH)
break;

cMaxWidth--;
}

return cMaxWidth;
}

void DumpColumnHeadings
(
DBBINDING*rgBind,
ULONGcBind,
DBCOLUMNINFO* pColInfo,
ULONGcCol,
    ULONGcMaxColWidth
)
{
ULONG iBind;

assert(rgBind != NULL);
    assert(pColInfo != NULL);

for (iBind=0; iBind < cBind; iBind++)
tfprintf( g_fpLogFile, "%-*.*S ",
min( cMaxColWidth, rgBind[iBind].cbMaxLen ),
min( cMaxColWidth, rgBind[iBind].cbMaxLen ),
LookupColumnName( pColInfo, cCol, rgBind[iBind].iOrdinal ) );
tfprintf( g_fpLogFile, "\n" );
for (iBind=0; iBind < cBind; iBind++)
tfprintf( g_fpLogFile, "%-*.*s ",
min( cMaxColWidth, rgBind[iBind].cbMaxLen ),
min( cMaxColWidth, rgBind[iBind].cbMaxLen ),
"------------------------------" );
tfprintf( g_fpLogFile, "\n" );
}

WCHAR* LookupColumnName
(
DBCOLUMNINFO*rgColInfo,
ULONG cCol,
ULONG iCol
)
{
ULONG j;

assert(rgColInfo != NULL);

// A really slow way to get the column name, given the ordinal.
// The problem is that result-set ordinals do not necessarily match
// the index into the ColumnInfo array.
// (May have bookmark, which is always column 0.)

for (j=0; j < cCol; j++)
if (rgColInfo[j].iOrdinal == iCol)
return rgColInfo[j].pwszName;

return L"Error";
}

void DumpRow
(
    DBBINDING* rgBind,
    ULONGcBind,
    ULONGcMaxColWidth,
    BYTE* pData
    )
{
ULONG iBind;
    COLUMNDATA*pColumn;

assert(rgBind);
assert( offsetof(COLUMNDATA, dwLength) == 0);

// Print each column we're bound to.
for (iBind=0; iBind < cBind; iBind++)
{
// Columns are bound differently; not so easy.
// Print out to at least DEFAULT_CBMAXLENGTH width (pretty),
// Limit to first dwLength characters.

pColumn = (COLUMNDATA *) (pData + rgBind[iBind].obLength);
PrintColumn( pColumn, rgBind, iBind, cMaxColWidth );
}
tfprintf( g_fpLogFile, "\n" );
}

void PrintColumn
(
COLUMNDATA    *pColumn,
DBBINDING     *rgBind,
ULONG          iBind,
ULONG          cMaxColWidth
)
{
void*p;
ULONG   ulPrintWidth;
ULONG   ulPrintPrecision;
DWORD   dwStatus;
DWORD   dwLength;
BOOL    fDidVariant;
BOOL    fIsUnicode;
char*sFormat;
HRESULT hr;

    assert(pColumn != NULL);
    assert(rgBind != NULL);

// Pretty print a column.
// May have different type of binding.

fDidVariant = FALSE;
fIsUnicode  = FALSE;
dwStatus = pColumn->dwStatus;
dwLength = pColumn->dwLength;

if (dwStatus == DBSTATUS_S_ISNULL)
{
p = "<null>";
dwLength = strlen( (char *) p);
}
    else if (dwStatus == DBBINDSTATUS_UNSUPPORTEDCONVERSION)
        {
        p = "<unsupportedconversion>";
dwLength = strlen( (char *) p);
        }
else
{
switch (rgBind[iBind].wType)
{
case DBTYPE_STR:
// We have a string in our buffer, so use it.
p = (void *) &pColumn->bData;
break;
case DBTYPE_VARIANT:
// We have a variant in our buffer, so convert to string.
p = (void *) &pColumn->bData;
hr = VariantChangeTypeEx(
(VARIANT *) p,// Destination (convert in place)
(VARIANT *) p,// Source
LOCALE_SYSTEM_DEFAULT,// LCID
0,// dwFlags
VT_BSTR );
if (FAILED(hr))
            {
DumpErrorHResult( hr, "VariantChangeTypeEx, field %d", iBind );
                return;
                }
p = (wchar_t *) (((VARIANT *)p)->bstrVal) ;
dwLength = ((DWORD *)p)[-1] / sizeof(wchar_t);
fDidVariant = TRUE;
fIsUnicode  = TRUE;
break;
default:
p = "??? unknown type ???";
break;
}
}

// Print the column.
// If it has been truncated or rounded, print a '#' in
// the far right-hand column.
ulPrintWidth     = min( cMaxColWidth, rgBind[iBind].cbMaxLen );
ulPrintPrecision = min( cMaxColWidth, dwLength );
if (dwStatus == DBSTATUS_S_TRUNCATED ||  cMaxColWidth < dwLength)
{
ulPrintWidth--;
ulPrintPrecision--;
}

sFormat = fIsUnicode ? "%-*.*S" : "%-*.*s";

tfprintf( g_fpLogFile, sFormat, ulPrintWidth, ulPrintPrecision, p );

if (dwStatus == DBSTATUS_S_TRUNCATED ||  cMaxColWidth < dwLength)
tfprintf( g_fpLogFile, "#" );
tfprintf( g_fpLogFile, " " );

// Free memory used by the variant.
if (fDidVariant)
VariantClear( (VARIANT *) &pColumn->bData );

return;
}

void tfprintf
(
FILE*fp,
const char* format,
...
)
{
int cBytesWritten;
char buff[400];
va_list argptr;

assert(format != NULL);

// Dump a formatted string.
    // _vsnprintf prevents overflowing our buffer.
va_start( argptr, format );
cBytesWritten = _vsnprintf( buff, sizeof(buff), format, argptr );
va_end( argptr );
buff[sizeof(buff)-1] = '\0';

// Can't use fprintf, because string could contain '%'.
if (fp)
fputs( buff, fp );
}

void tvfprintf
(
FILE*fp,
const char* format,
va_listargptr
)
{
int cBytesWritten;
char buff[400];

assert(format != NULL);

// Dump a formatted string.
    // _vsnprintf prevents overflowing our buffer.
cBytesWritten = _vsnprintf( buff, sizeof(buff), format, argptr );
buff[sizeof(buff)-1] = '\0';

// Can't use fprintf, because string could contain '%'.
if (fp)
fputs( buff, fp );
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft OLE DB Sample Consumer
//(C) Copyright 1994 - 1996 By Microsoft Corporation.
//
// Microsoft Visual C++ generated include file.
// Used by SAMPCLNT.RC
//

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40006
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SAMPCLNT.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Consumer
// (C) Copyright 1995 - 1996 Microsoft Corporation. All Rights Reserved.
//
// File name: SAMPCLNT.CPP
//
//      Implementation file for a simple OLE DB consumer.
//      Dump\output routines for SAMPCLNT are in DUMP.CPP
//      SAMPCLNT.H is the header file.
//
//      See OLE DB SDK Guide for information on building and running
//this sample, as well as notes concerning the implementation of
//a simple OLE DB consumer.
//
// Functions:
//
//      See SAMPCLNT.H for function prototypes
//
//
// SampClnt is structured to match the steps required for simple OLE DB data access:
//
// DoTests
// GetSampprovDataSource
//      GetDBSessionFromDataSource
//      GetRowsetFromDBSession
// GetDataFromRowset
// GetColumnsInfo
// SetupBindings
// CreateAccessor
// GetData
// CleanupRowset
//

#define INITGUID
#define DBINITCONSTANTS
#include "sampclnt.h"

IMalloc* g_pIMalloc = NULL;
FILE*    g_fpLogFile = NULL;    // our log file

//**********************************************************************
//
// main
//
// Purpose:
//
//     Entry point for this program.
//
// Parameters:
//
//     none
//
// Return Value:
//
//     none
//
// Comments:
//
//     Main does some initializing, then calls DoTests, then cleans up.
//     All of the interesting action in this program happens in DoTests.
//
//**********************************************************************

void main()
{
DWORD   dwVersion;
HRESULT hr;
time_tttime;
    BOOL fOleInitialized = FALSE;
charch;

  g_fpLogFile = fopen( "sampclnt.out", "at");
    if (!g_fpLogFile)
    {
    DumpErrorMsg( "Warning: cannot open log file sampclnt.out\n" );
        }

  time(&ttime);

    DumpStatusMsg( "\n-------------------------\n\n");
    DumpStatusMsg( "running sampclnt.exe\n%s\n\n", ctime(&ttime) );

dwVersion = OleBuildVersion();
    if (HIWORD(dwVersion) != rmm)
{
DumpErrorMsg( "Error: OLE version mismatch. Build version %ld, current version %ld\n",
rmm, HIWORD(dwVersion) );
        goto error;
}

hr = OleInitialize( NULL );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorMsg("Error: OleInitialize failed\n");
        goto error;
}
fOleInitialized = TRUE;

    hr = CoGetMalloc( MEMCTX_TASK, &g_pIMalloc );
 if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorMsg("Error: CoGetMalloc failed\n");
        goto error;
        }

    hr = DoTests();
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "DoTests");
        goto error;
        }

    g_pIMalloc->Release();
OleUninitialize();

if (g_fpLogFile)
    fclose(g_fpLogFile);

/* Pause before we quit, in case user ran from an icon, so they can see our messages. */
printf("\n\n>>> Output has gone into 'sampclnt.out'.");
printf("\n>>> You may wish to use a wide-column editor to view this file.\n\n");
printf("<press any key to continue>");
ch = _getch();
return;

error:
    if (g_pIMalloc)
    g_pIMalloc->Release();
if (fOleInitialized)
    OleUninitialize();
if (g_fpLogFile)
    fclose(g_fpLogFile);

/* Pause before we quit, in case user ran from an icon, so they can see our messages. */
printf("\n\n>>> Output has gone into 'sampclnt.out'.");
printf("\n>>> You may wish to use a wide-column editor to view this file.\n\n");
printf("<press any key to continue>");
ch = _getch();

return;
}

//**********************************************************************
//
//  DoTests
//
//  Purpose:
//
//     Hooks up to the SAMPPROV OLE DB provider application, asks the provider
//     for all data in the CSV file CUSTOMER.CSV, and logs the resulting data
//     to sampclnt.out
//
//  Parameters:
//
//  none
//
//  Return Value:
//
//  S_OK- Success
//      E_*- Failure
//
//
//  Comments:
//
//     At a high level, an OLE DB data consumer obtains data by
//
//     1. Getting hooked up to a data provider's Data Source object,
//        and initializing that object
//     2. Getting a DBSession object from the Data Source object
//     3. Getting the data from the Rowset object.
//
//     DoTests follows these steps by making calls to GetSampprovDataSource,
//     GetDBSessionDataSource, and GetDataFromRowset
//
//**********************************************************************

HRESULT DoTests
(
)
{
IDBInitialize*    pIDBInitialize = NULL;
    IOpenRowset*        pIOpenRowset    = NULL;
    IRowset*    pIRowset= NULL;
LPWSTR    pwszTableName   = L"customer.csv";
HRESULT    hr;

hr = GetSampprovDataSource( &pIDBInitialize );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "GetSampprovDataSource" );
        goto error;
        }

    hr = GetDBSessionFromDataSource( pIDBInitialize, &pIOpenRowset );
    if (FAILED(hr))
        {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "GetDBSessionFromDataSource" );
        goto error;
        }

    pIDBInitialize->Release();
    pIDBInitialize = NULL;

    hr = GetRowsetFromDBSession( pIOpenRowset, pwszTableName, &pIRowset );
    if (FAILED(hr))
        {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "GetRowsetFromDBCreateSession" );
        goto error;
        }

    pIOpenRowset->Release();
    pIOpenRowset = NULL;

hr = GetDataFromRowset( pIRowset );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "GetDataFromRowset" );
        goto error;
        }
    pIRowset->Release();
    pIRowset = NULL;
CoFreeUnusedLibraries();

DumpStatusMsg( "\nDone! ");
printf("\n\nFor more data from this run, see the log file sampclnt.out\n" );
return ResultFromScode( S_OK );

error:
if (pIRowset)
pIRowset->Release();
    if (pIOpenRowset)
        pIOpenRowset->Release();
    if (pIDBInitialize)
    pIDBInitialize->Release();

return ResultFromScode( hr );
}

//**********************************************************************
//
// GetSampprovDataSource
//
// Purpose:
//
//     Calls OLE to find and load the SAMPPROV data provider.
//     Returns an IDBInitialize interface pointer on SAMPPROV's
//     Data Source object.
//
// Parameters:
//
//   IDBInitialize** ppIDBInitialize_out  - out pointer through which to return
//         IDBInitialize pointer on data
//            provider's Data Source object
//
// Return Value:
//
// S_OK- Success
//  E_*    - Failure
//
//
// Comments:
//
//     The call to CoCreateInstance is hard-coded with SAMPPROV's CLSID.
//     The pointer returned through ppIDBInitialize_out has been AddRef'ed,
//     it must be Release'd later by the caller.
//
//**********************************************************************

HRESULT GetSampprovDataSource
(
IDBInitialize**ppIDBInitialize_out
)
{
IDBInitialize*pIDBInit = NULL;
IDBProperties*pIDBProperties = NULL;
DBPROPSETdbPropSet[1];
DBPROPdbProp[1];

HRESULThr;

DumpStatusMsg( "Connecting to the SampProv sample data provider...\n" );

assert(ppIDBInitialize_out != NULL);

VariantInit(&(dbProp[0].vValue));

// Create an instance of the SampProv sample data provider
hr = CoCreateInstance( CLSID_SampProv, NULL, CLSCTX_INPROC_SERVER,
IID_IDBInitialize, (void **)&pIDBInit );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "CoCreateInstance" );
        goto error;
        }

// Initialize this provider with the path to the customer.csv file
dbPropSet[0].rgProperties= &dbProp[0];
dbPropSet[0].cProperties= 1;
dbPropSet[0].guidPropertySet= DBPROPSET_DBINIT;

dbProp[0].dwPropertyID= DBPROP_INIT_DATASOURCE;
dbProp[0].dwOptions= DBPROPOPTIONS_REQUIRED;
dbProp[0].colid= DB_NULLID;
V_VT(&(dbProp[0].vValue))= VT_BSTR;
V_BSTR(&(dbProp[0].vValue))= SysAllocString( L"." );
    if ( NULL == V_BSTR(&(dbProp[0].vValue)) )
        {
        DUMP_ERROR_LINENUMBER();
        DumpErrorMsg( "SysAllocString failed\n" );
        goto error;
        }

    hr = pIDBInit->QueryInterface( IID_IDBProperties, (void**)&pIDBProperties);
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IDBInitialize::QI for IDBProperties");
        goto error;
        }

hr = pIDBProperties->SetProperties( 1, &dbPropSet[0]);
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IDBProperties::SetProperties" );
        goto error;
        }

hr = pIDBInit->Initialize();
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IDBInitialize::Initialize" );
        goto error;
        }

*ppIDBInitialize_out = pIDBInit;

hr = ResultFromScode( S_OK );

error:
    VariantClear( &(dbProp[0].vValue) );

if( pIDBProperties )
pIDBProperties->Release();

if( FAILED(hr) )
{
if (pIDBInit)
    pIDBInit->Release();
*ppIDBInitialize_out = NULL;
}

return hr;
}

//  **********************************************************************
//
// GetDBSessionFromDataSource
//
// Purpose:
//      Calls the provider's Data Source object to get an IOpenRowset interface
//      pointer on a DBSession object.
//
// Parameters:
//      pIDBInitialize      - pointer to Data Source object
//      ppIOpenRowset_out   - out pointer through which to return
//                            IOpenRowset pointer on DBSession object
//
// Return Value:
//
// S_OK- Success
//  E_*    - Failure
//
//
// Comments:
//
//      The interface pointer returned through ppIOpenRowset_out has been
//      AddRef'ed, the caller must Release it later.
//
//**********************************************************************

HRESULT GetDBSessionFromDataSource
    (
    IDBInitialize*      pIDBInitialize,      // [in]
    IOpenRowset**       ppIOpenRowset_out    // [out]
    )
{
    IDBCreateSession*   pIDBCreateSession;
    IOpenRowset*        pIOpenRowset;
    HRESULT             hr;

    DumpStatusMsg( "Getting a DBSession object from the data source object...\n" );

    assert(pIDBInitialize != NULL);
    assert(ppIOpenRowset_out  != NULL );

    hr = pIDBInitialize->QueryInterface( IID_IDBCreateSession, (void**)&pIDBCreateSession);
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IDBInitialize::QI for IDBCreateSession");
        goto error;
        }

    hr = pIDBCreateSession->CreateSession( NULL, IID_IOpenRowset, (IUnknown**)&pIOpenRowset );
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IDBCreateSession::CreateSession");
        goto error;
        }
     pIDBCreateSession->Release();
     pIDBCreateSession = NULL;

     // all went well
     *ppIOpenRowset_out = pIOpenRowset;
     return ResultFromScode( S_OK );

error:
    if (pIDBCreateSession)
        pIDBCreateSession->Release();

    *ppIOpenRowset_out = NULL;
    return ResultFromScode( hr );
}

//**********************************************************************
//
// GetRowsetFromDBSession
//
// Purpose:
//      Calls the provider's DBSession object to get an IRowset interface
//      pointer on a Rowset object.
//
// Parameters:
//      pIOpenRowset        - interface pointer on DBSession object
//      pwszTableName       - name of "table" (in this case text file)
//      ppIRowset_out       - out pointer through which to return
//                            IRowset pointer on Rowset object
//
// Return Value:
//
// S_OK- Success
//  E_*    - Failure
//
// Comments:
//
//      The interface pointer returned through ppIRowset_out has been
//      AddRef'ed, the caller must Release it later.
//
///**********************************************************************

HRESULT GetRowsetFromDBSession
    (
    IOpenRowset*   pIOpenRowset,    // [in]
    LPWSTR         pwszTableName,   // [in]
    IRowset**      ppIRowset_out    // [out]
    )
{
    DBID            dbcolid;
    IRowset*        pIRowset = NULL;
    HRESULT         hr;

    DumpStatusMsg( "Getting a rowset object from the DBSession object...\n" );

    assert(pIOpenRowset != NULL);
    assert(ppIRowset_out  != NULL );

    // tell the provider which table to open
    dbcolid.eKind           = DBKIND_NAME;
    dbcolid.uName.pwszName  = pwszTableName;

    hr = pIOpenRowset->OpenRowset
                        (
                        NULL,                   // pUnkOuter - we are not aggregating
                        &dbcolid,               // pTableID -  the table we want
NULL,// pIndexID - the index we want
                        IID_IRowset,            // riid - interface we want on the rowset object
                        0,                      // cProperties - we are niave about props for now
                        NULL,                   // prgProperties[]
                        (IUnknown**)&pIRowset   // ppRowset
                        );
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IOpenRowset::OpenRowset" );
        goto error;
        }

    // all went well
    *ppIRowset_out = pIRowset;
    return ResultFromScode( S_OK );

error:
    if (pIRowset)
        pIRowset->Release();
    *ppIRowset_out = NULL;

    return ResultFromScode( hr );
}

//**********************************************************************
//
// GetDataFromRowset
//
// Purpose:
//
//     Pulls the data from a Rowset object.
//
// Parameters:
//
// IRowset*pIRowset    -  interface pointer on data provider's
//                             Rowset object
//
// Return Value:
//
//     S_OK        - Success
//     E_*         - Failure
//
// Comments:
//
//     At a high level, a consumer pulls the data from a Rowset object by:
//
//     1. getting metadata for the Rowset's columns
//     2. using that metadata, along with the consumer's own knowledge of
//        how it wants to recieve the data, to create bindings. Bindings
//        represent how the actual data in the Rowset's columns is
//        actually transferred to the consumer's buffer.
//     3. pass the bindings to the Rowset, and get in return an accessor
//        handle that represents that particulr set of bindings
//     4. get the actual data
//     5. clean up the rowset (at a minumum, release the accessor)
//
//     GetDataFromRowset performs these steps by calling GetColumnsInfo,
//     SetupBindings, CreateAccessor, GetData, and CleanupRowset
//
//**********************************************************************

HRESULT GetDataFromRowset
(
IRowset*pIRowset
)
{
ULONG cCol;
ULONG cbMaxRowSize;// buffer size for 1 row's data
ULONG cBind;
DBBINDINGrgBind[MAX_BINDINGS];
HACCESSORhAccessor= NULL;
DBCOLUMNINFO*pColumnInfo = NULL;
WCHAR*pStringsBuffer  = NULL;
HRESULT hr;

DumpStatusMsg( "Reading all the data in the rowset...\n" );

assert(pIRowset != NULL);
assert(g_pIMalloc != NULL);

hr = GetColumnsInfo( pIRowset, &cCol, &pColumnInfo, &pStringsBuffer );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr,  "GetColumnsInfo");
        goto error;
        }

hr = SetupBindings( cCol, pColumnInfo, rgBind, &cBind, &cbMaxRowSize );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr,  "SetupBindings");
        goto error;
        }

hr = CreateAccessor( pIRowset, rgBind, cBind, &hAccessor );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr,  "CreateAccessor" );
        goto error;
        }

hr = GetData( pIRowset, cbMaxRowSize, hAccessor, rgBind, cBind, pColumnInfo, cCol );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr,  "GetData" );
        goto error;
        }

g_pIMalloc->Free( pColumnInfo );
    pColumnInfo = NULL;
g_pIMalloc->Free( pStringsBuffer );
    pStringsBuffer = NULL;

hr = CleanupRowset( pIRowset, hAccessor );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr,  "CleanupRowset" );
        goto error;
        }

return ResultFromScode( S_OK );

error:
if (pColumnInfo)
g_pIMalloc->Free( pColumnInfo );
if (pStringsBuffer)
g_pIMalloc->Free( pStringsBuffer );

    return ResultFromScode( hr );
}

//**********************************************************************
//
// GetColumnsInfo
//
// Purpose:
//
//     Obtains information (metadata) about the columns in the rowset - the types
//     of the data and so on.
//
//
// Parameters:
// IRowset*pIRowset            - interface pointer on data provider's
//                                        Rowset object
// ULONG*pcCol_out           - out pointer through which to return
//                                        number of columns in the rowset
// DBCOLUMNINFO**ppColumnInfo_out    - out pointer through which to return
//                                        pointer to structure containing
//                                        metadata for the columns in the rowset
// WCHAR**ppStringsBuffer_out - out pointer through which to return
//                                        pointer to table of strings. see comments.
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
//
// Comments:
//
//     ppColumnInfo_out and ppStringsBuffer_out are used to return pointers
//     to two buffers. These buffers are allocated by the data provider
//     (when GetColumnsInfo calls IColumnsInfo::GetColumnInfo). The data
//     provider uses IMalloc to allocate the buffers; therefore, the caller
//     of this routine must at a later point use IMalloc::Free to free
//     both of these buffers. The StringsBuffer contains strings pointed
//     to by pointers in the ColumnInfo buffer, therefore the StringsBuffer
//     should be freed *after* the ColumnInfo pointer.
//
//     GetColumnsInfo calls DumpColumnsInfo to dump the column metadata to
//     the log file.
//
//**********************************************************************

HRESULT GetColumnsInfo
(
IRowset*pIRowset,
ULONG*pcCol_out,
DBCOLUMNINFO**ppColumnInfo_out,
WCHAR**ppStringsBuffer_out
)
{
IColumnsInfo* pIColumnsInfo = NULL;
    ULONGcCol;
    DBCOLUMNINFO*pColumnInfo;
    WCHAR*pStringsBuffer;
HRESULT hr;

assert(pIRowset != NULL);
    assert(pcCol_out != NULL);
    assert(ppColumnInfo_out != NULL);
    assert(ppStringsBuffer_out != NULL);

// get column information from the command object via IColumnsInfo
hr = pIRowset->QueryInterface( IID_IColumnsInfo, (void **) &pIColumnsInfo );
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "IRowset::QI for IID_IColumnsInfo" );
        goto error;
        }
hr = pIColumnsInfo->GetColumnInfo(
&cCol,
&pColumnInfo,
&pStringsBuffer );
    if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "pIColumnsInfo->GetColumnInfo" );
        goto error;
        }
pIColumnsInfo->Release();
pIColumnsInfo = NULL;

DumpColumnsInfo( pColumnInfo, cCol );

    // fill out-params
    *pcCol_out = cCol;
    *ppColumnInfo_out = pColumnInfo;
    *ppStringsBuffer_out = pStringsBuffer;

return ResultFromScode( S_OK );

error:
if (pIColumnsInfo)
    pIColumnsInfo->Release();

*pcCol_out = 0;
    *ppColumnInfo_out = NULL;
    *ppStringsBuffer_out = NULL;

return ResultFromScode( hr );
}

//**********************************************************************
//
// SetupBindings
//
// Purpose:
//
//     Creates bindings that map the data in the rowset's columns to
//     slots in the consumer's data buffer.
//
// Parameters:
//
// ULONG cCol                - number of columns in rowset to bind
// DBCOLUMNINFO*pColumnInfo         - pointer to column metadata
// DBBINDING*rgBind_out          - out pointer through which to return
//                                        an array of binding structures, one
//                                        structure per column bound
// ULONG*pcBind_out          - out pointer through which to return
//                                        the number of columns bound (number
//                                        of valid elements in rgBind_out)
// ULONG*pcMaxRowSize_out    - out pointer through which to return
//                                        the buffer size necessary to hold
//                                        the largest row data
//
// Return Value:
//     S_OK         - Success
//     E_*          - Failure
//
//
// Comments:
//
//
//**********************************************************************

HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*pcBind_out,
ULONG*pcMaxRowSize_out
)
{
ULONG dwOffset;
ULONG iCol;
ULONG iBind;

    assert(pColumnInfo != NULL);
    assert(rgBind_out != NULL);
    assert(pcBind_out != NULL);
    assert(pcMaxRowSize_out != NULL);

// Create bindings.
// Bind everything as a string just to keep things simple.
dwOffset = 0;
iBind=0;
for (iCol=0; iCol < cCol; iCol++)
{
// Skip columns of type _VECTOR. Probably binary data.
if (pColumnInfo[iCol].wType & DBTYPE_VECTOR)
continue;

rgBind_out[iBind].dwPart= DBPART_VALUE | DBPART_LENGTH |
  DBPART_STATUS;
        rgBind_out[iBind].eParamIO  = DBPARAMIO_NOTPARAM;
rgBind_out[iBind].iOrdinal  = pColumnInfo[iCol].iOrdinal;
rgBind_out[iBind].wType= DBTYPE_STR;
rgBind_out[iBind].pTypeInfo = NULL;
rgBind_out[iBind].obValue   = dwOffset + offsetof(COLUMNDATA,bData);
 rgBind_out[iBind].obLength  = dwOffset + offsetof(COLUMNDATA,dwLength);
rgBind_out[iBind].obStatus  = dwOffset + offsetof(COLUMNDATA,dwStatus);
rgBind_out[iBind].cbMaxLen  = pColumnInfo[iCol].wType == DBTYPE_STR ?
pColumnInfo[iCol].ulColumnSize + sizeof(char) : DEFAULT_CBMAXLENGTH;
rgBind_out[iBind].pObject= NULL;
dwOffset += rgBind_out[iBind].cbMaxLen + offsetof( COLUMNDATA, bData );
dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
iBind++;
}

*pcBind_out   = iBind;
*pcMaxRowSize_out = dwOffset;

return ResultFromScode( S_OK );
}

//**********************************************************************
//
// CreateAccessor
//
// Purpose:
//
//     Passes a set of bindings to the data provider and recieves in return
//     an accessor handle that represents those bindings.
//
// Parameters:
// IRowset*pIRowset        - interface pointer on data provider's Rowset
//                                object
// DBBINDING*rgBind          - array of binding structures
// ULONGcBind           - number of binding structures in rgBind
// HACCESSOR*phAccessor_out  - out pointer through which to return an
//                                accessor handle that represents all the bindings
//                                in rgBind
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
//
// Comments:
//
//
//**********************************************************************

HRESULT CreateAccessor
(
IRowset*pIRowset,
DBBINDING*rgBind,
ULONGcBind,
HACCESSOR*phAccessor_out
)
{
IAccessor*pIAccessor = NULL;
HACCESSOR   hAccessor;
HRESULT hr;

assert(pIRowset != NULL);
    assert(rgBind != NULL);
    assert(phAccessor_out != NULL);

  // Get an accessor for our bindings from the rowset, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "pIRowset->QI for IID_IAccessor" );
        goto error;
        }
hr = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, cBind, rgBind, 0,
                                 &hAccessor, NULL );
if (FAILED(hr))
    {
        DUMP_ERROR_LINENUMBER();
        DumpErrorHResult( hr, "pIAccessor->CreateAccessor" );
        goto error;
        }
pIAccessor->Release();
pIAccessor = NULL;

*phAccessor_out = hAccessor;

return ResultFromScode( S_OK );

error:
if (pIAccessor)
    pIAccessor->Release();
*phAccessor_out = NULL;

return ResultFromScode( hr );
}

//**********************************************************************
//
// GetData
//
// Purpose:
//
//     Reads the data from a rowset.
//
// Parameters:
//
//    IRowset* pIRowset           - interface pointer on data provider's
//                                    Rowset object
//    ULONG    cMaxRowSize        - size of buffer needed to hold the data
//                                    for the largest row
//     HACCESSOR hAccessor          - accessor handle representing the set
//                                    of desired bindings
//     DBBINDING*rgBind  - needed only for pretty printing
//     ULONGcBind  - for pretty printing
//     DBCOLUMNINFO*pColumnInfo - for pretty printing
//     ULONGcCol- for pretty printing
//
//
// Return Value:
//     S_OK        - Success
//     E_*         - Failure
//
//
//
// Comments:
//
//     GetData reads all the rows in the rowset, sequentially.
//
//     GetData calls CalcPrettyPrintMaxColWidth, DumpColumnHeadings, and
//     DumpRow to dump the row data to a log file.
//
//
//**********************************************************************

HRESULT GetData
(
IRowset*pIRowset,
ULONG   cMaxRowSize,
    HACCESSOR   hAccessor,
    DBBINDING*rgBind,
    ULONGcBind,
    DBCOLUMNINFO*pColumnInfo,
    ULONGcCol
)
{
ULONG cRowsObtained;
ULONGiRow;
BYTE*pRowData = NULL;
HROW rghRows[NUMROWS_CHUNK];
HROW*pRows = &rghRows[0];
ULONGcMaxColWidth;  // needed for pretty printing
HRESULT hr;

assert(pIRowset != NULL);
    assert(rgBind != NULL);
    assert(pColumnInfo != NULL);

// create a buffer for row data, big enough to hold the biggest row
pRowData = (BYTE *) malloc( cMaxRowSize );
if (!pRowData)
{
        DUMP_ERROR_LINENUMBER();
        DumpErrorMsg("GetData: malloc failed\n");
        goto error;
}

// pretty print
    cMaxColWidth = CalcPrettyPrintMaxColWidth( rgBind, cBind );

    // pretty print
DumpColumnHeadings( rgBind, cBind, pColumnInfo, cCol, cMaxColWidth );

    // process all the rows, NUMROWS_CHUNK rows at a time
while (1)
{
hr = pIRowset->GetNextRows(
NULL,// hChapter
0,// cRowsToSkip
NUMROWS_CHUNK,// cRowsDesired
&cRowsObtained,             // pcRowsObtained
&pRows );// filled in w/ row handles
if (FAILED(hr))
{
            DUMP_ERROR_LINENUMBER();
            DumpErrorHResult( hr, "pIRowset->GetNextRows" );
            goto error;
}

if ( cRowsObtained == 0 )// all done, no more rows left to get
break;

// loop over rows obtained, getting data for each
for ( iRow=0; iRow < cRowsObtained; iRow++ )
{
hr = pIRowset->GetData(
rghRows[iRow],
hAccessor,
pRowData );
             if (FAILED(hr))
             {
                DUMP_ERROR_LINENUMBER();
                DumpErrorHResult( hr, "pIRowset->GetData" );
                goto error;
             }

// pretty print
            DumpRow( rgBind, cBind, cMaxColWidth, pRowData );

}
// release row handles
hr = pIRowset->ReleaseRows( cRowsObtained, rghRows, NULL, NULL, NULL );
        if (FAILED(hr))
             {
                DUMP_ERROR_LINENUMBER();
                DumpErrorHResult( hr, "pIRowset->ReleaseRows" );
                goto error;
             }
}// end while

// free row data buffer
free( pRowData );
return ResultFromScode( S_OK );

error:
if (pRowData)
free( pRowData );

    return ResultFromScode( hr );
}

//**********************************************************************
//
// CleanupRowset
//
// Purpose:
//
//     Allows the rowset to perform any necessary cleanup.
//
// Parameters:
//
// IRowset*pIRowset    - interface pointer on data provider's Rowset
//                            object
// HACCESSOR hAccessor   - accessor handle to release
//
// Return Value:
//
//     S_OK        - Success
//     E_*         - Failure
//
//
// Comments:
//
//     In this sample, the only cleanup that the rowset needs to do is
//     release the accessor handle.
//
//**********************************************************************

HRESULT CleanupRowset
(
IRowset*pIRowset,
HACCESSOR hAccessor
)
{
IAccessor*pIAccessor = NULL;
HRESULThr;

    assert(pIRowset != NULL);

// tell the rowset object it can release the accessor, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
if (FAILED(hr))
{
        DUMP_ERROR_LINENUMBER();
DumpErrorHResult( hr, "pIRowset->QI for IID_IAccessor" );
goto error;
}
hr = pIAccessor->ReleaseAccessor( hAccessor, NULL );
if (FAILED(hr))
{
        DUMP_ERROR_LINENUMBER();
DumpErrorHResult( hr, "pIAccessor->ReleaseAccessor" );
goto error;
}
pIAccessor->Release();
pIAccessor = NULL;

return ResultFromScode( S_OK );

error:
if (pIAccessor)
    pIAccessor->Release();

    return ResultFromScode( hr );
}


SAMPCLNT.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Consumer
// (C) Copyright 1995 - 1996 Microsoft Corporation. All Rights Reserved.
//
// File name: SAMPCLNT.H
//
//      Declaration file for a simple OLE DB consumer.
//
//      See OLE DB SDK Guide for information on building and running
//this sample, as well as notes concerning the implementation of
//a simple OLE DB consumer.
//

#define WIN32_LEAN_AND_MEAN// avoid the world
#define INC_OLE2// tell windows.h to always include ole2.h

#include <windows.h>//
#include <ole2ver.h>// OLE2.0 build version
#include <cguid.h>// GUID_NULL
#include <stdio.h>// vsnprintf, etc.
#include <stddef.h>// offsetof
#include <stdarg.h>// va_arg
#include <time.h>// time
#include <assert.h>// assert
#include <conio.h>// _getch()

//OLE DB headers
#include <oledb.h>
#include <oledberr.h>

//-----------------------------------
//constants
//------------------------------------

// Alignment for placement of each column within memory.
// Rule of thumb is "natural" boundary, i.e. 4-byte member should be
// aligned on address that is multiple of 4.
// Worst case is double or __int64 (8 bytes).
#define COLUMN_ALIGNVAL 8

#define MAX_GUID_STRING     42// size of a GUID, in characters
#define MAX_NAME_STRING     60  // size of DBCOLOD name or propid string
#define MAX_BINDINGS       100// size of binding array
#define NUMROWS_CHUNK       20// number of rows to grab at a time
#define DEFAULT_CBMAXLENGTH 40// cbMaxLength for binding

// for pretty printing
#define PRETTYPRINT_MAXTOTALWIDTH200     // max entire width of printed row
#define PRETTYPRINT_MINCOLWIDTH     6        // min width of printed column

//-----------------------------------
//macros
//------------------------------------

// Rounding amount is always a power of two.
#define ROUND_UP(   Size, Amount ) (((DWORD)(Size) +  ((Amount) - 1)) & ~((Amount) - 1))

#ifndef  NUMELEM
# define NUMELEM(p) (sizeof(p)/sizeof(*p))
#endif

// usage: DUMPLINE();
#define DUMP_ERROR_LINENUMBER() DumpErrorMsg("Error at file: %s  line: %u  \n", __FILE__, __LINE__)

//-----------------------------------
//type and structure definitions
//------------------------------------

// How to lay out each column in memory.
// Issue? we depend on the dwLength field being first in memory (see assert)
// is there another way to handle this?
struct COLUMNDATA
{
DWORDdwLength;// length of data (not space allocated)
DWORDdwStatus;// status of column
BYTEbData[1];// data here and beyond
};

// Lists of value/string pairs.
typedef struct {
DWORD dwFlag;
char *szText;
} Note;

#define NOTE(s) { (DWORD) s, #s }

//-----------------------------------
//global variables and functions that are private to the file
//------------------------------------

DEFINE_GUID(CLSID_SampProv, 0xE8CCCB79L,0x7C36,0x101B,0xAC,0x3A,0x00,0xAA,0x00,0x44,0x77,0x3D);
DEFINE_GUID(DBINIT_OPT_SAMPPROV_PATH, 0xe9fbaf50, 0xd402, 0x11ce, 0xbe, 0xdc, 0x0, 0xaa, 0x0, 0xa1, 0x4d, 0x7d);

extern IMalloc*g_pIMalloc;
extern FILE*    g_fpLogFile;

// function prototypes, sampclnt.cpp

void main();

HRESULT DoTests();

HRESULT GetSampprovDataSource
(
IDBInitialize**ppIDBInitialize_out
);

HRESULT GetDBSessionFromDataSource
    (
    IDBInitialize*      pIDBInitialize,
    IOpenRowset**       ppIOpenRowset_out
    );

HRESULT GetRowsetFromDBSession
    (
    IOpenRowset*   pIOpenRowset,
    LPWSTR         pwszTableName,
    IRowset**      ppIRowset_out
    );

HRESULT GetDataFromRowset
(
IRowset*pIRowset
);

HRESULT GetColumnsInfo
(
IRowset*pIRowset,
ULONG*pcCol_out,
DBCOLUMNINFO**ppColumnInfo_out,
WCHAR**ppStringsBuffer_out
);

HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*cBind_out,
    ULONG*          pcMaxRowSize_out
);

HRESULT CreateAccessor
(
IRowset*pIRowset,
DBBINDING*rgBind,
ULONGcBind,
HACCESSOR*phAccessor_out
);

HRESULT GetData
(
IRowset*pIRowset,
ULONG   cMaxRowSize,
    HACCESSORhAccessor,
    DBBINDING*    rgBind,// needed for pretty printing
    ULONG    cBind,    // for pretty printing
    DBCOLUMNINFO*pColumnInfo, // for pretty printing
    ULONGcCol// for pretty printing
);

HRESULT CleanupRowset
(
IRowset*pIRowset,
HACCESSOR hAccessor
);

// function prototypes, dump.cpp

void DumpErrorMsg
(
    const char* format,
    ...
);

void DumpStatusMsg
(
    const char* format,
    ...
);

HRESULT DumpErrorHResult
(
HRESULT      hr_return,
const char  *format,
...
);

void DumpColumnsInfo
(
    DBCOLUMNINFO* pColInfo,
    ULONG      cCol
    );

void WriteColumnInfo
(
FILE*fp,
DBCOLUMNINFO*p
);

char* GetNoteString
    (
Note * rgNote,
int    cNote,
DWORD  dwValue
);

char* GetNoteStringBitvals
(
Note* rgNote,
int     cNote,
DWORD   dwValue
);

ULONG CalcPrettyPrintMaxColWidth
    (
    DBBINDING*rgBind,
    ULONG       cBind
    );

void DumpColumnHeadings
(
DBBINDING*rgBind,
ULONGcBind,
DBCOLUMNINFO* pColInfo,
ULONGcCol,
    ULONGcMaxColWidth
);

WCHAR* LookupColumnName
(
DBCOLUMNINFO*rgColInfo,
ULONG cCol,
ULONG iCol
);

void DumpRow
(
    DBBINDING* rgBind,
    ULONGcBind,
    ULONGcMaxColWidth,
    BYTE* pData
    );

void PrintColumn
(
COLUMNDATA    *pColumn,
DBBINDING     *rgBind,
ULONG          iBind,
ULONG          cMaxColWidth
);

void tfprintf
(
FILE*fp,
const char* format,
...
);

void tvfprintf
(
FILE*fp,
const char* format,
va_listargptr
);


SAMPVER.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB Version File
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module version include file
//
//

// Constants -----------------------------------------------------------------
#ifndef _AXVER_H_
#define _AXVER_H_

#define VER_FILEVERSION  01,01,24,17
#define VER_FILEVERSION_STR  " 1.01.2417\0"
#define VER_PRODUCTVERSION  01,01,24,17
#define VER_PRODUCTVERSION_STR  " 1.01.2417\0"

#define VER_FILEFLAGSMASK(VS_FF_DEBUG | VS_FF_PRERELEASE)
#ifdef DEBUG
#define VER_FILEFLAGS(VS_FF_DEBUG)
#else
#define VER_FILEFLAGS(0)
#endif

#define VER_FILEOSVOS_NT_WINDOWS32

#define VER_COMPANYNAME_STR"Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR"Microsoft OLE DB\0"
#define VER_LEGALCOPYRIGHT_STR"Copyright \251 Microsoft Corporation 1994-1996\0"

// Resource DLL Version Resource ID
#define IDS_RESDLL_VER1

#endif
//--------------------


ACCESSOR.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ACCESSOR.CPP | CImpIAccessor object implementation
//
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// IAccessor specific methods

// CImpIAccessor::AddRefAccessor -----------------------------------------
//
// @mfunc Adds a reference count to an existing accessor
//
// @rdesc HRESULT
//      @flag S_OK                      | Method Succeeded
//      @flag E_FAIL                    | Provider specific Error
//
STDMETHODIMP CImpIAccessor::AddRefAccessor
    (
HACCESSORhAccessor,//@parm IN | Accessor Handle
ULONG*pcRefCounts//@parm OUT | Reference Count
    )
{
    // Retrieve our accessor structure from the client's hAccessor,
    // free it, then mark accessor ptr as unused.
    // We do not re-use accessor handles.  This way, we hope
    // to catch more client errors.  (Also, ExtBuffer doesn't
    // maintain a free list, so it doesn't know how to.)

    PACCESSOR   pAccessor;
    HRESULT     hr;

    if( pcRefCounts )
*pcRefCounts = 0;

hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
    if (FAILED( hr ) || pAccessor == NULL)
        return ResultFromScode( DB_E_BADACCESSORHANDLE );

InterlockedIncrement(&(pAccessor->cRef));

if( pcRefCounts )
*pcRefCounts = (ULONG)(pAccessor->cRef);

return ResultFromScode(S_OK);
}

// CImpIAccessor::CreateAccessor -----------------------------------------
//
// @mfunc Creates a set of bindings that can be used to send data
// to or retrieve data from the data cache.
// NOTE:  Currently does not support returning rgStatus[].
//
// @rdesc HRESULT
//      @flag S_OK                      | Method Succeeded
//      @flag E_FAIL                    | Provider specific Error
//      @flag E_INVALIDARG              | pHAccessor was NULL, dwAccessorFlags was
//                                        invalid, or cBindings was not 0 and
//                                        rgBindings was NULL
//      @flag E_OUTOFMEMORY             | Out of Memory
//      @flag DB_E_ERRORSOCCURRED| dwBindPart in an rgBindings element was invalid, OR
//    | Column number specified was out of range, OR
//| Requested coercion is not supported.
//      @flag OTHER                     | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIAccessor::CreateAccessor
    (
    DBACCESSORFLAGS dwAccessorFlags,
    ULONG           cBindings,      //@parm IN | Number of Bindings
    const DBBINDING rgBindings[],   //@parm IN | Array of DBBINDINGS
    ULONG           cbRowSize,      //@parm IN | Number of bytes in consumer's buffer
    HACCESSOR*      phAccessor,     //@parm OUT | Accessor Handle
DBBINDSTATUSrgStatus[]//@parm OUT| Binding status
    )
{
    PACCESSOR   pAccessor;
    ULONG       hAccessor;
    ULONG       ibind;
    ULONG       icol;
    HRESULT     hr;

    // Check Parameters
    if( (cBindings && !rgBindings) ||
(phAccessor == NULL) )
        return ResultFromScode( E_INVALIDARG );

    // init out params
    *phAccessor = (HACCESSOR) 0;

    // Check for the binding types we don't accept..
    if ((dwAccessorFlags & DBACCESSOR_PASSBYREF)        ||
        (dwAccessorFlags & DBACCESSOR_PARAMETERDATA))
        return ResultFromScode( E_INVALIDARG );

    // .. then check for the binding type that is required
    if (!(dwAccessorFlags & DBACCESSOR_ROWDATA))
        return ResultFromScode( E_INVALIDARG );

    // Check on the bindings the user gave us.
    for (ibind =0, hr =NOERROR; ibind < cBindings; ibind++)
    {
        icol = rgBindings[ibind].iOrdinal;

        // make sure column number is in range
        if (!(0 < icol && icol <= m_pObj->m_cCols))
        {
            TRACE( "CreateAccessor failure: binding %d, bad column number %d\n", ibind, icol );
            hr = ResultFromScode( DB_E_ERRORSOCCURRED );
            break;
        }

        // At least one of these valid parts has to be set. In SetData I assume it is the case.
        if ((rgBindings[ibind].dwPart & DBPART_VALUE)  == 0
         && (rgBindings[ibind].dwPart & DBPART_LENGTH) == 0
         && (rgBindings[ibind].dwPart & DBPART_STATUS) == 0)
         {
            TRACE( "CreateAccessor failure: binding %d, column %d, _VALUE, _LENGTH, _STATUS not specified", ibind, icol );
            hr = ResultFromScode( DB_E_ERRORSOCCURRED );

if ( rgStatus )
{
//  Set Bind status to DBBINDSTATUS_BADBINDINFO
}
            break;
        }

        // Make sure we can do the coercion that is requested
        if( NOERROR != g_pIDataConvert->CanConvert(rgBindings[ibind].wType, m_pObj->m_rgdbcolinfo[icol].wType) ||
NOERROR != g_pIDataConvert->CanConvert(m_pObj->m_rgdbcolinfo[icol].wType, rgBindings[ibind].wType) )
        {
            TRACE( "CreateAccessor failure: binding %d, column %d, cannot coerce types\n", ibind, icol );
            hr = ResultFromScode( DB_E_ERRORSOCCURRED );
if ( rgStatus )
{
//  Set Bind status to DBBINDSTATUS_UNSUPPORTEDCONVERSION
}
        break;
        }
    }

    // Any errors amongst those checks?
    if (hr != NOERROR)
    {
        return hr;
    }

    // Make a copy of the client's binding array, and the type of binding.
    // Note: accessors with no bindings (cBindings=0) are legal.
    pAccessor = (ACCESSOR *) new BYTE[sizeof( ACCESSOR ) + (cBindings - 1) *sizeof( DBBINDING )];
    if (pAccessor == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    // We store a ptr to the newly created variable-sized ACCESSOR.
    // We have an array of ptrs (to ACCESSOR's).
    // The handle is the index into the array of ptrs.
    // The InsertIntoExtBuffer function appends to the end of the array.
    assert( m_pObj->m_pextbufferAccessor );
    hr = m_pObj->m_pextbufferAccessor->InsertIntoExtBuffer( &pAccessor, hAccessor );
    if (FAILED( hr ))
        {
        delete [] pAccessor;
        return ResultFromScode( E_OUTOFMEMORY );
        }
    assert( hAccessor );

    // Copy the client's bindings into the ACCESSOR.
    pAccessor->dwAccessorFlags= dwAccessorFlags;
    pAccessor->cBindings= cBindings;
    pAccessor->cRef= 1;// Establish Reference count.
memcpy( &(pAccessor->rgBindings[0]), &rgBindings[0], cBindings*sizeof( DBBINDING ));

    // fill out-param and return
    *phAccessor = (HACCESSOR) hAccessor;
    return ResultFromScode( S_OK );
}

// CImpIAccessor::GetBindings --------------------------------------------------
//
// @mfunc Returns the bindings in an accessor
//
// @rdesc HRESULT
//      @flag S_OK                      | Method Succeeded
//      @flag E_INVALIDARG              | pdwAccessorFlags/pcBinding/prgBinding were NULL
//      @flag E_OUTOFMEMORY             | Out of Memory
//      @flag DB_E_BADACCESSORHANDLE    | Invalid Accessor given
//
STDMETHODIMP CImpIAccessor::GetBindings
    (
    HACCESSOR        hAccessor,         //@parm IN | Accessor Handle
    DBACCESSORFLAGS* pdwAccessorFlags,  //@parm OUT | Binding Type flag
    ULONG*           pcBindings,        //@parm OUT | Number of Bindings returned
    DBBINDING**      prgBindings        //@parm OUT | Bindings
    )
{
    // Retrieve our accessor structure from the client's hAccessor,
    // make a copy of the bindings for the user, then done.
    PACCESSOR   pAccessor;
    ULONG       cBindingSize;
    HRESULT     hr;

    // check parameters
    if (!pdwAccessorFlags || !pcBindings || !prgBindings)
        return ResultFromScode( E_INVALIDARG );

    // init out-params
    *pdwAccessorFlags = 0;
    *pcBindings       = 0;
    *prgBindings      = NULL;

    // Validate Accessor Handle
    hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
    if (FAILED( hr ) || pAccessor == NULL)
        return ResultFromScode( DB_E_BADACCESSORHANDLE );

    // Allocate and return Array of bindings
    cBindingSize = pAccessor->cBindings * sizeof( DBBINDING );
    *prgBindings = (DBBINDING *) g_pIMalloc->Alloc( cBindingSize );
    if (*prgBindings)
        {
        *pdwAccessorFlags = pAccessor->dwAccessorFlags;
        *pcBindings = pAccessor->cBindings;
        memcpy( *prgBindings, pAccessor->rgBindings, cBindingSize );
        }
    else
        {
        return ResultFromScode( E_OUTOFMEMORY );
        }

    // all went well..
    return ResultFromScode( S_OK );
}

// CImpIAccessor::ReleaseAccessor ---------------------------------------
//
// @mfunc Releases an Accessor
//
// @rdesc HRESULT
//      @flag S_OK                      | Method Succeeded
//      @flag DB_E_BADACCESSORHANDLE    | hAccessor was invalid
//
STDMETHODIMP CImpIAccessor::ReleaseAccessor
    (
    HACCESSORhAccessor,      //@parm IN | Accessor handle to release
ULONG*pcRefCounts//@parm OUT | Reference Count
    )
{
    // Retrieve our accessor structure from the client's hAccessor,
    // free it, then mark accessor ptr as unused.
    // We do not re-use accessor handles.  This way, we hope
    // to catch more client errors.  (Also, ExtBuffer doesn't
    // maintain a free list, so it doesn't know how to.)

    PACCESSOR   pAccessor;
    HRESULT     hr;

if( pcRefCounts )
*pcRefCounts = 0;

    hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
    if (FAILED( hr ) || pAccessor == NULL)
        return ResultFromScode( DB_E_BADACCESSORHANDLE );

    // Free the actual structure.
InterlockedDecrement(&(pAccessor->cRef));
assert( pAccessor->cRef >= 0 );
if( pAccessor->cRef <= 0 )
{
delete [] pAccessor;
if( pcRefCounts )
*pcRefCounts = 0;
}
else
{
if( pcRefCounts )
*pcRefCounts = (ULONG)(pAccessor->cRef);
}

    // Store a null in our array-of-ptrs,
    // so we know next time that it is invalid.
    // (operator[] returns a ptr to the space where the ptr is stored.)
    *(PACCESSOR*) ((*m_pObj->m_pextbufferAccessor)[ (ULONG) hAccessor]) = NULL;

    return ResultFromScode( S_OK );
}


ASSERTS.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ASSERTS.CPP | Simple Assertion Routines
//
//
#include "headers.h"
#include <time.h>
#include "asserts.h"

#ifdef DEBUG    // only compile for debug!

//--------------------------------------------------------------------
// @func Variable argument formatter and Dump routine for messages
//
// @rdesc NONE
//
void OLEDB_Trace
    (
    const char *format,     //@parm IN | Format String
    ...                     //@parm IN | Variable Arg List
    )
{
    char buff[300 ];
    int cBytesWritten;
    va_list argptr;

    // If this overflows, it will wipe out the return stack.
    // However, we have a wonderful version that ensures no overwrite.
    // Nice because we can't do anything about errors here.

    va_start( argptr, format );
    cBytesWritten = _vsnprintf( buff, sizeof( buff ), format, argptr );
    va_end( argptr );

    // assert would report overflow first, recursively, but don't bother.
    // Would be OK, since this assert could be proven
    // not to overflow temp buffer in assert.
    //assert( cBytesWritten < sizeof(buff) );

    OutputDebugString( buff );
}

//--------------------------------------------------------------------
// @func This an internal assertion routine that dumps more information
// than the normal assertion routines..
//
// @rdesc NONE
//
void OLEDB_Assert
    (
    LPSTR expression,   //@parm IN | Expression to assert on
    LPSTR filename,     //@parm IN | Filname where assertion occurred
    long linenum        //@parm IN | Line number where assertion occurred
    )
{
    char          szbuff[350 ];
    int           cBytesWritten;
    volatile int  fAbort;

    // If this overflows, it will wipe out the return stack.
    // However, we have a wonderful version that ensures no overwrite.
    // Good thing, because we can't do much about overflows here anyway.
    // (However, use of "%.nns" works well.)

    cBytesWritten = _snprintf( szbuff, sizeof( szbuff ),
        "Assertion error!\n  File '%.50s', line '%ld'\n  Expression '%.200s'\n",
        filename, linenum, expression );

    TRACE( szbuff );

    // We're a DLL (therefore Windows), so may not have an output stream we can write to.
    ::MessageBox(
        NULL,   // HWND, which we don't have
        szbuff, // Text
        "Assertion Error",  // Title
        MB_SYSTEMMODAL | MB_ICONHAND | MB_OK );

    // Break and let the user get a crack at it.
    // You can set fAbort=0 to continue merrily along.
    fAbort = 1;
    if (fAbort)
        {
        abort();    // Raises SIGABRT
        }
}
#endif


ASSERTS.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ASSERTS.H | Assertion Routines
//
//
#ifndef _ASSERTS_H_
#define _ASSERTS_H_

//-----------------------------------------------------------------------------
// Global function prototypes -- helper stuff
//-----------------------------------------------------------------------------

// The assert and trace macros below calls these.
void OLEDB_Assert(LPSTR expression, LPSTR filename, long linenum);

void OLEDB_Trace(const char* format, ...);

//-----------------------------------------------------------------------------
// Debugging macros
//-----------------------------------------------------------------------------

// Ensure "DEBUG" is set if "_DEBUG" is set.
#ifdef _DEBUG
# ifndef DEBUG
#  define DEBUG 1
# endif
#endif

// Ensure no previous versions of our macros.
#ifdef  assert
# undef assert
#endif
#ifdef  Assert
# undef Assert
#endif
#ifdef  ASSERT
# undef ASSERT
#endif
#ifdef  TRACE
# undef TRACE
#endif

#ifdef DEBUG
# define assert(x) { if ( ! (x) ) OLEDB_Assert( #x, __FILE__, __LINE__ ); }
# define Assert(x) assert(x)
# define ASSERT(x) assert(x)
# define VERIFY(x) assert(x)
# define TRACE  OLEDB_Trace
# define DEBUGCODE(p) p
#else// DEBUG
# define assert(x)  ((void)0)
# define Assert(x)  ((void)0)
# define ASSERT(x)  ((void)0)
# define VERIFY(x)  ((void)(x))
# define TRACE  OLEDB_Trace
  inline void OLEDB_Trace( const char *format, ... ) { /* do nothing */ }
# define DEBUGCODE(p)
#endif// DEBUG

#endif


BITARRAY.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module BITARRAY.CPP | This contains an implementation of a bit array
// class currently used by the Internal Buffer to mark released or
// unreleased rows.
//
//
#include "headers.h"
#include "bitarray.h"

//--------------------------------------------------------------------
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CBitArray::CBitArray
    (
    void
    )
{
    m_rgbBit       = NULL;
    m_cPageMax     = 0;
    m_cPageCurrent = 0;
    m_cslotCurrent = 0;
}

//--------------------------------------------------------------------
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CBitArray:: ~CBitArray
    (
    void
    )
{
    if (m_rgbBit)
        {
        if (m_cPageCurrent)
            VirtualFree((VOID *) m_rgbBit, m_cPageCurrent *m_cbPage, MEM_DECOMMIT );
        VirtualFree((VOID *) m_rgbBit, 0, MEM_RELEASE );
        }
}

//--------------------------------------------------------------------
// @mfunc Allocate and Initialize the array of bits
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK          | Initialization succeeded
//      @flag  E_OUTOFMEMORY | Not enough memory to allocate bit array
//
STDMETHODIMP CBitArray::FInit
    (
    ULONG cslotMax,     //@parm IN  | Maximum number of slot
    ULONG cbPage        //@parm IN  | Count of bytes per page
    )
{
    ULONG cPage;
    ULONG ib;

    cPage = (cslotMax / 8 + 1) / cbPage + 1;
    m_rgbBit = (BYTE *) VirtualAlloc( NULL, cbPage *cPage, MEM_RESERVE, PAGE_READWRITE );

    if (m_rgbBit == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    m_cPageMax = cPage;

    m_cbPage = cbPage;

    for (ib =0; ib < 8; ib++)
        m_rgbBitMask[ib] = (1 << ib);
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Set a range of bit slots
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK          | Initialization succeeded
//      @flag  E_OUTOFMEMORY | Not enough memory to allocate bit array
//
STDMETHODIMP CBitArray::SetSlots
    (
    ULONG islotFirst,   //@parm IN | First slot in range to set
    ULONG islotLast             //@parm IN | Last slot in range to set
    )
{
    ULONG islot;

    if (islotLast >=    m_cslotCurrent)
        {
        ULONG cPageAdd;

        cPageAdd = ((islotLast - m_cslotCurrent + 1) / 8 + 1) / m_cbPage + 1;

        if ((cPageAdd + m_cPageCurrent) > m_cPageMax
            || VirtualAlloc( m_rgbBit + m_cPageCurrent*m_cbPage, cPageAdd *m_cbPage, MEM_COMMIT, PAGE_READWRITE ) == NULL)
            return ResultFromScode( E_OUTOFMEMORY );

        memset( m_rgbBit + m_cPageCurrent*m_cbPage, 0x00, cPageAdd *m_cbPage );
        m_cPageCurrent += cPageAdd;
        m_cslotCurrent += cPageAdd *m_cbPage *8;
        }

    // Only do this top section
    // if we have at least 2 byte's worth of bits to set.
    // Although no real speedup until we have 3 byte's worth.
    // Note really ought to be ((ilast-ifirst+1) >= 2*8).
    // (Note could use CHAR_BIT, num bits in a char.)
    // Also optimized end cases, so nothing is done
    // if the start or end is byte aligned.
    // Need this copied into ResetSlots.

    //if((islotLast -islotFirst) > 2*sizeof(BYTE))
    if (islotLast - islotFirst > 2 * 8)
        {
        ULONG ibFirst, ibLast;
        int iFixFirst, iFixLast;

        ibFirst = islotFirst / 8;
        ibLast  = islotLast / 8;
        iFixFirst = (islotFirst % 8 != 0);  // set to 1 if first byte not totally set
        iFixLast  = (islotLast % 8 != 7);   // set to 1 if last  byte not totally set

        if (iFixFirst)
            for (islot = islotFirst; (islot / 8) == ibFirst; islot++)
                m_rgbBit[islot / 8] |= m_rgbBitMask[islot % 8];

        memset( &m_rgbBit[ibFirst + iFixFirst], 0xff, ibLast - ibFirst + 1 - iFixFirst - iFixLast );

        if (iFixLast)
            for (islot = islotLast; (islot / 8) == ibLast; islot--)
                m_rgbBit[islot / 8] |= m_rgbBitMask[islot % 8];
        }
    else
        {
        for (islot = islotFirst; islot <= islotLast; islot++)
            m_rgbBit[islot / 8] |= m_rgbBitMask[islot % 8];
        }

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Clear all bit slots
//
// @rdesc NONE
//
VOID CBitArray::ResetAllSlots
    (
    void
    )
{
    memset( m_rgbBit, 0x00, m_cPageCurrent*m_cbPage );
}

//--------------------------------------------------------------------
// @mfunc Reset a range of slots
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK | Reset Succeeded
//
STDMETHODIMP CBitArray::ResetSlots
    (
    ULONG islotFirst,   //@parm IN | First slot in range to reset
    ULONG islotLast     //@parm IN | Last slot in range to reset
    )
{
    ULONG ibFirst, ibLast, islot;

    if (islotFirst < m_cslotCurrent)
        {
        if (islotLast >=    m_cslotCurrent)
            islotLast = m_cslotCurrent - 1;

        if ((islotLast - islotFirst) > 2*8)
            {
            ibFirst = islotFirst / 8;
            ibLast  = islotLast / 8;
            for (islot = islotFirst; (islot / 8) == ibFirst; islot++)
                m_rgbBit[islot / 8] &= ~m_rgbBitMask[islot % 8];
            memset( &m_rgbBit[ibFirst + 1], 0x00, ibLast - ibFirst - 1 );
            for (islot = islotLast; (islot / 8) == ibLast; islot--)
                m_rgbBit[islot / 8] &= ~m_rgbBitMask[islot % 8];
            }
        else
            {
            for (islot = islotFirst; islot <= islotLast; islot++)
                m_rgbBit[islot / 8] &= ~m_rgbBitMask[islot % 8];
            }
        }

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Determines if any bits are set
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK     | Array is Empty
//      @flag  S_FALSE  | Array contains set bits
//
STDMETHODIMP CBitArray::ArrayEmpty
    (
    void
    )
{
    if (m_cPageCurrent)
        {
        ULONG idw, cdw, *rgdw;

        cdw = m_cPageCurrent * (m_cbPage / sizeof( ULONG ));
        rgdw = (ULONG *) m_rgbBit;
        for (idw =0; idw < cdw; idw++)
            if (rgdw[idw])
                return ResultFromScode( S_FALSE );
        }
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Determine if a particular bit slot is set
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK          | Slot is set
//      @flag  E_OUTOFMEMORY | Slot is not set
//
STDMETHODIMP CBitArray::IsSlotSet
    (
    ULONG islot                 //@parm IN | Bit slot to check
    )
{
    if (islot >= m_cslotCurrent || (m_rgbBit[islot / 8] & m_rgbBitMask[islot % 8]) == 0x00)
        return ResultFromScode( S_FALSE );  // not set
    else
        return ResultFromScode( S_OK ); // is set
}

//--------------------------------------------------------------------
// @mfunc Find the first set slot within the bit array given a starting
// position
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK          | Initialization succeeded
//      @flag  E_OUTOFMEMORY | Not enough memory to allocate bit array
//
STDMETHODIMP CBitArray::FindSet
    (
    ULONG islotStart,   //@parm IN | Starting slot to search from
    ULONG islotLimit,   //@parm IN | Number of slots to check
    ULONG *pislot       //@parm OUT | Index of first set slot
    )
{
    ULONG ibStart, ibLimit, idwStart, idwLimit, ibEnd, ib, islot, islotEnd, idw, *pdw;

    if (islotStart > islotLimit)
        {
        ibStart  = islotStart / 8;
        ibLimit  = islotLimit / 8;
        if ((ibStart - ibLimit) > 1)
            {
            islotEnd = ibStart*8;
            for (islot = islotStart; islot >= islotEnd; islot--)
                if (m_rgbBit[islot / 8] & m_rgbBitMask[islot % 8])
                    {
                    *pislot = islot;
                    return ResultFromScode( S_OK );
                    }
            idwStart = islotStart / 32;
            idwLimit = islotLimit / 32;
            if (idwStart - idwLimit > 1)
                {
                ibEnd = idwStart*4;
                for (ib = ibStart - 1; ib >= ibEnd; ib--)
                    if (m_rgbBit[ib])
                        {
                        islot = ib*8 + 7;
                        goto Found1;
                        }
                for (pdw = (ULONG *) & m_rgbBit[ (idwStart - 1) *4], idw = idwStart - 1; idw > idwLimit; idw--, pdw--)
                    if (*pdw)
                        {
                        islot = idw*32 + 31;
                        goto Found1;
                        }
                ib = (idwLimit*4 + 3);
                }
            else
                ib = ibStart - 1;
            for (; ib > ibLimit; ib--)
                if (m_rgbBit[ib])
                    {
                    islot = ib*8 + 7;
                    goto Found1;
                    }
            islot = (ibLimit*8 + 7);
            }
        else
            islot = islotStart;

        Found1:
        for (; islot >= islotLimit; islot--)
            if (m_rgbBit[islot / 8] & m_rgbBitMask[islot % 8])
                {
                *pislot = islot;
                return ResultFromScode( S_OK );
                }
        return ResultFromScode( S_FALSE );  // not found
        }
    else
        return ResultFromScode( E_FAIL );
}


BITARRAY.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module BITARRAY.H | Class Definitions for Bitarray Class
//
//
#ifndef _BITARRAY_H_
#define _BITARRAY_H_

// Forward Declaration
class FAR CBitArray;
typedef CBitArray FAR *LPBITARRAY;

//--------------------------------------------------------------------
// @class Allocates and manages a bit array through various methods
// defined in the class
//
// @hungarian bits or pbits
//
class FAR CBitArray
{
private://@access private
//@cmember Count of Slots
ULONG    m_cslotCurrent;
//@cmember Maximum number of pages
ULONG    m_cPageMax;
//@cmember Current number of pages
ULONG    m_cPageCurrent;
//@cmember Number of bytes per page
ULONG    m_cbPage;
//@cmember Mask buffer
BYTE     m_rgbBitMask[8];
//@cmember Bit Array
BYTE     *m_rgbBit;

public://@access public
//@cmember Class constructor
CBitArray( void );
//@cmember Class destructor
~CBitArray( void );
//@cmember Initialization method
STDMETHODIMP FInit(ULONG cslotMax, ULONG cbPage);
//@cmember Set a range of slots
STDMETHODIMP SetSlots(ULONG islotFirst, ULONG islotLast);
//@cmember Reset a range of slots
STDMETHODIMP ResetSlots(ULONG islotFirst, ULONG islotLast);
//@cmember Reset all slots
VOID ResetAllSlots(void);
//@cmember Check if any bits are set
STDMETHODIMP ArrayEmpty(void);
//@cmember Check the status of a particular bit
STDMETHODIMP IsSlotSet(ULONG islot);
//@cmember Find the first set bit in a range of bits
STDMETHODIMP FindSet(ULONG islotStart, ULONG islotLimit, ULONG* pislot);

};

#endif


CLASSFAC.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module CLASSFAC.CPP | The module contains the DLL Entry and Exit
// points, plus the OLE ClassFactory class.
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"
#include "classfac.h"

// Static vars ---------------------------------------------------------------

static const char * s_strDllName = "SampProv";  // used with GetModuleHandle
static const struct
{
    char * strRegKey;
    char * strValue;
} s_rgRegInfo[] =
{
    { "SampProv",  "Microsoft OLE DB Sample Provider" },
    { "SampProv\\Clsid", "{E8CCCB79-7C36-101B-AC3A-00AA0044773D}" },
    { "CLSID\\{E8CCCB79-7C36-101B-AC3A-00AA0044773D}", "SampProv" },
    { "CLSID\\{E8CCCB79-7C36-101B-AC3A-00AA0044773D}\\ProgID", "SampProv" },
    { "CLSID\\{E8CCCB79-7C36-101B-AC3A-00AA0044773D}\\VersionIndependentProgID", "SampProv" },
    { "CLSID\\{E8CCCB79-7C36-101B-AC3A-00AA0044773D}\\InprocServer32", "%s" },
{ "CLSID\\{E8CCCB79-7C36-101B-AC3A-00AA0044773D}\\OLE DB Provider", "Microsoft OLE DB Sample Provider"},
};

// Code ----------------------------------------------------------------------

// DllMain -------------------------------------------------------------------
//
// @func DLL Entry point where Instance and Thread attach/detach notifications
// takes place.  OLE is initialized and the IMalloc Interface pointer is obtained.
//
// @rdesc Boolean Flag
//      @flag TRUE | Successful initialization
//      @flag FALSE | Failure to intialize
//
BOOL WINAPI DllMain
    (
    HINSTANCE   hInstDLL,   //@parm IN | Application Instance Handle
    DWORD       fdwReason,  //@parm IN | Indicated Process or Thread activity
    LPVOID      lpvReserved //@parm IN | Reserved...
    )
{
    BOOL        fRetVal = FALSE;
    HRESULT     hr;
    SYSTEM_INFO SystemInformation;

    switch (fdwReason)
        {
    case DLL_PROCESS_ATTACH:

        // Assume successfully initialized
        fRetVal = TRUE;

        // Do one-time initialization when first process attaches
        if (!g_cAttachedProcesses)
            {
            g_hInstance = hInstDLL;

            // Get the OLE task memory allocator; we'll use it to allocate
            // all memory that we return to the client
            hr = CoGetMalloc( MEMCTX_TASK, &g_pIMalloc );
            if (!g_pIMalloc || !SUCCEEDED( hr ))
                {
                fRetVal = FALSE;
                break;
                }

            // Get the system page size
            if (!g_dwPageSize)
                {
                GetSystemInfo( &SystemInformation );    // can't fail
                g_dwPageSize = SystemInformation.dwPageSize;
                }
            }

        // Do per-process initialization here...

        // Remember that another process successfully attached
        g_cAttachedProcesses++;
        break;

    case DLL_PROCESS_DETACH:
        // Clean up when the last process is going away
        if (g_cAttachedProcesses == 1)
            {
            // Release the memory allocator object.
            if (g_pIMalloc)
                g_pIMalloc->Release();
            g_pIMalloc = NULL;

            }

        // Do per-process clean up here...

        // Remember that a process has detached
        g_cAttachedProcesses--;
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;
        }

    return fRetVal;
}

// DllGetClassObject ---------------------------------------------------------
//
// @func This function is exposed to OLE so that the classfactory can
// be obtained.
//
// @rdesc HRESULT indicating status of routine
//      @flag S_OK                      | The object was retrieved successfully.
//      @flag CLASS_E_CLASSNOTAVAILABLE | DLL does not support class.
//      @flag E_OUTOFMEMORY             | Out of memory.
//      @flag E_INVALIDARG              | One or more arguments are invalid.
//      @flag E_UNEXPECTED              | An unexpected error occurred.
//      @flag OTHER | Other HRESULTs returned by called functions
//
HRESULT CALLBACK DllGetClassObject
    (
    REFCLSID    rclsid, //@parm IN | CLSID of the object class to be loaded
    REFIID      riid,   //@parm IN | Interface on object to be instantiated
    LPVOID *    ppvObj  //@parm OUT | Pointer to interface that was instantiated
    )
{
    CClassFactory * pClassFactory;
    HRESULT         hr;

    // Check for valid ppvObj pointer
    if (!ppvObj)
        return ResultFromScode( E_INVALIDARG );

    // In case we fail, we need to zero output arguments
    *ppvObj = NULL;

    // We only service CLSID_SampProv
    if (rclsid != CLSID_SampProv)
        return ResultFromScode( CLASS_E_CLASSNOTAVAILABLE );

    // We only support the IUnknown and IClassFactory interfaces
    if (riid != IID_IUnknown &&
        riid != IID_IClassFactory)
        return ResultFromScode( E_NOINTERFACE );

    // Create our ClassFactory object
    pClassFactory = new CClassFactory();
    if (pClassFactory == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    // Get the desired interface on this object
    hr = pClassFactory->QueryInterface( riid, ppvObj );
    if (!SUCCEEDED( hr ))
        delete pClassFactory;

    return hr;
}

// DllCanUnloadNow -----------------------------------------------------------
//
// @func Indicates whether the DLL is no longer in use and
// can be unloaded.
//
// @rdesc HRESULT indicating status of routine
//      @flag S_OK | DLL can be unloaded now.
//      @flag S_FALSE | DLL cannot be unloaded now.
//
STDAPI DllCanUnloadNow( void )
{
    if (!g_cObj && !g_cLock)
        return ResultFromScode( S_OK );
    else
        return ResultFromScode( S_FALSE );
}

//-----------------------------------------------------------------------------
// CClassFactory
//-----------------------------------------------------------------------------

// CClassFactory -------------------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CClassFactory::CClassFactory
    (
    void
    )
{
    m_cRef = 0;

    // Increment global object count
    OBJECT_CONSTRUCTED();
}

// ~CClassFactory ------------------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CClassFactory:: ~CClassFactory( void )
{
    // Decrement global object count
    OBJECT_DESTRUCTED();
}

// QueryInterface ------------------------------------------------------------
//
// @mfunc Returns a pointer to a specified interface. Callers use
// QueryInterface to determine which interfaces the called object
// supports.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK          | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG  | One or more arguments are invalid.
//
STDMETHODIMP CClassFactory::QueryInterface
    (
    REFIID      riid,   //@parm IN | Interface ID of the interface being queried for.
    LPVOID *    ppvObj  //@parm OUT | Pointer to interface that was instantiated
    )
{
    // Check for valid ppvObj pointer
    if (!ppvObj)
        return ResultFromScode( E_INVALIDARG );

    // In case we fail, we need to zero output arguments
    *ppvObj = NULL;

    // Do we support this interface?
    if (riid == IID_IUnknown ||
        riid == IID_IClassFactory)
        *ppvObj = (LPVOID) this;

    // If we're going to return an interface, AddRef it first
    if (*ppvObj)
        {
        ((LPUNKNOWN) *ppvObj)->AddRef();
        return ResultFromScode( S_OK );
        }
    else
        return ResultFromScode( E_NOINTERFACE );
}

// AddRef --------------------------------------------------------------------
//
// @mfunc Increments a persistence count for the object
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CClassFactory::AddRef( void )
{
    return ++m_cRef;
}

// Release -------------------------------------------------------------------
//
// @mfunc Decrements a persistence count for the object and if
// persistence count is 0, the object destroys itself.
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CClassFactory::Release( void )
{
    if (!--m_cRef)
        {
        delete this;
        return 0;
        }

    return m_cRef;
}

// CreateInstance ------------------------------------------------------------
//
// @mfunc Creates an uninitialized instance of an object class.
// Initialization is subsequently performed using another
// interface-specific method
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK          | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG  | One or more arguments are invalid.
//      @flag E_OUTOFMEMORY | Memory could not be allocated
//      @flag OTHER         | Other HRESULTs returned by called functions
//
STDMETHODIMP CClassFactory::CreateInstance
    (
    LPUNKNOWN   pUnkOuter,  //@parm IN | Points to the controlling IUnknown interface
    REFIID      riid,       //@parm IN | Interface ID of the interface being queried for.
    LPVOID *    ppvObj      //@parm OUT | Pointer to interface that was instantiated
    )
{
    PCDATASOURCE    pObj;
    HRESULT         hr;

    // Check for valid ppvObj pointer
    if (ppvObj == NULL)
        return ResultFromScode( E_INVALIDARG );

    // In case we fail, we need to zero output arguments
    *ppvObj = NULL;

    // If we're given a controlling IUnknown, it must ask for IUnknown.
    // Otherwise, the caller will end up getting a pointer to their pUnkOuter
    // instead of to the new object we create and will have no way of getting
    // back to this new object, so they won't be able to free it.  Bad!
    if (pUnkOuter && riid != IID_IUnknown)
        return ResultFromScode( E_NOINTERFACE );

    // Prepare for the possibility that there might be an error
    hr = ResultFromScode( E_OUTOFMEMORY );

    // Create a CDataSource object
    if (!(pObj = new CDataSource( pUnkOuter )))
        return hr;

    // Initialize it
    if (pObj->FInit())
        hr = pObj->QueryInterface( riid, ppvObj );

    if (FAILED( hr ))
        delete pObj;

    return hr;
}

// LockServer ----------------------------------------------------------------
//
// @mfunc Controls whether an object application is kept in memory.
// Keeping the application alive in memory allows instances of this
// class to be created more quickly.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK | Interface is supported and ppvObject is set.
//
STDMETHODIMP CClassFactory::LockServer
    (
    BOOL fLock                  //@parm IN | TRUE or FALSE to lock or unlock
    )
{
    if (fLock)
        InterlockedIncrement( &g_cLock );
    else
        InterlockedDecrement( &g_cLock );

    return NOERROR;
}

//---------------------------------------------------------------------------
// @func    Removes keys to the registry.
//
// @rdesc Returns NOERROR
//
// @comm
// Special Notes:   This allows us to avoid using a .reg file.
//
// Note that a more robust method would be to trace existing
// CLSID and ProgID, and trace each from the other.  This would handle the
// case where either changed.  Then should probably enumerate all keys under
// the ProgID and CLSID, then delete them.
//
// Also note the problem with our exposing a different CLSID for the debug
// and ndebug versions, yet the ProgID remains the same.  Should we have
// different ProgID's also?
//
//---------------------------------------------------------------------------
STDAPI DllUnregisterServer
    (
    void
    )
{
    int     i;
    int     iNumErrors = 0;
LONGstat;

    // Delete all table entries.  Loop in reverse order, since they
    // are entered in a basic-to-complex order.
    // We cannot delete a key that has subkeys.
    // Ignore errors.
    for (i=NUMELEM( s_rgRegInfo ) - 1; i >= 0; i--)
        {
stat = RegDeleteKey( HKEY_CLASSES_ROOT, s_rgRegInfo[i].strRegKey );
        if ((stat != ERROR_SUCCESS) &&
        (stat != ERROR_FILE_NOT_FOUND) )
            iNumErrors++;
        }

    return ResultFromScode( iNumErrors ? E_FAIL : S_OK );
}

//---------------------------------------------------------------------------
// @func    Adds necessary keys to the registry.
//
// @rdesc Returns one of the following
// @flag NOERROR    | Registration succeeded
// @flag E_FAIL     | Something didn't work
//
// @comm
// Special Notes:   This allows us to avoid using a .reg file.
// Here is what was in the SampProv.REG file of yore.
// Note that now we have two CLSID's, one for DEBUG.  Then each one
// can point to a different .DLL.
//
//---------------------------------------------------------------------------
STDAPI DllRegisterServer
    (
    void
    )
{
    int         i;
    HKEY        hk;
    HMODULE     hModule;
    DWORD       dwDisposition;
    LONG        stat;
    char        strOutBuff[300 ], strFileName[MAX_PATH + 1 ];

    // Get the full path name for this DLL.
    if (NULL == (hModule = GetModuleHandle( s_strDllName )))
        return ResultFromScode( E_FAIL );
    if (0 == GetModuleFileName( hModule, strFileName, sizeof( strFileName ) / sizeof( char )))
        return ResultFromScode( E_FAIL );

    // Make a clean start
    DllUnregisterServer();

    // Loop through s_rgRegInfo, and put everything in it.
    // Every entry is based on HKEY_CLASSES_ROOT.
    for (i=0; i < NUMELEM( s_rgRegInfo ); i++)
        {
        // Fill in any "%s" arguments with the name of this DLL.
        wsprintf( strOutBuff, s_rgRegInfo[i].strValue, strFileName );

        // Create the Key.  If it exists, we open it.
        // Thus we can still change the value below.
        stat = RegCreateKeyEx(
                HKEY_CLASSES_ROOT,
                s_rgRegInfo[i].strRegKey,
                0,  // dwReserved
                NULL,   // lpszClass
                REG_OPTION_NON_VOLATILE,
                KEY_ALL_ACCESS, // security access mask
                NULL,   // lpSecurityAttributes
                &hk,    // phkResult
                &dwDisposition );
        if (stat != ERROR_SUCCESS)
            return ResultFromScode( E_FAIL );

        stat = RegSetValueEx(
                hk, // created above
                NULL,   // lpszValueName
                0,  // dwReserved
                REG_SZ, // fdwType
                (BYTE *) strOutBuff,    // value
                strlen( strOutBuff ) + sizeof( char )); // cbData, including null terminator
        RegCloseKey( hk );
        if (stat != ERROR_SUCCESS)
            return ResultFromScode( E_FAIL );
        }

    return ResultFromScode( S_OK );
}


CLASSFAC.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module CLASSFAC.H | Class Definitions for CClassFactory and
// DLL Entry Points
//
//
#ifndef _CLASSFAC_H_
#define _CLASSFAC_H_

// Classes -------------------------------------------------------------------

//----------------------------------------------------------------------------
// @class CClassFactory
//
class CClassFactory : public IClassFactory//@base public | IClassFactory
{
protected: //@access protected
//@cmember Reference count
ULONGm_cRef;

public: // @access public
//@cmember Constructor
CClassFactory(void);
//@cmember Destructor
~CClassFactory(void);

//IUnknown members
//@cmember Request an Interface
STDMETHODIMPQueryInterface(REFIID, LPVOID *);
//@cmember Increments the Reference count
STDMETHODIMP_(ULONG)AddRef(void);
//@cmember Decrements the Reference count
STDMETHODIMP_(ULONG)Release(void);

//IClassFactory members
//@cmember Instantiates an uninitialized instance of an object
STDMETHODIMPCreateInstance(LPUNKNOWN, REFIID, LPVOID *);
    //@cmember Lock Object so that it can not be unloaded
STDMETHODIMPLockServer(BOOL);
};

#endif


COLINFO.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module COLINFO.CPP | IColumnsInfo interface implementation
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

//  IColumnsInfo specific methods

// CImpIColumnsInfo::GetColumnInfo -------------------------------------------
//
// @mfunc Returns the column metadata needed by most consumers.
//
// @rdesc HRESULT
//      @flag S_OK              | The method succeeded
//      @flag E_OUTOFMEMORY     | Out of memory
//      @flag E_INVALIDARG      | pcColumns or prginfo or ppStringsbuffer was NULL
//
STDMETHODIMP CImpIColumnsInfo::GetColumnInfo
    (
    ULONG*          pcColumns,      //@parm OUT | Number of columns in rowset
    DBCOLUMNINFO**  prgInfo,        //@parm OUT | Array of DBCOLUMNINFO Structures
    WCHAR**         ppStringsBuffer //@parm OUT | Storage for all string values
    )
{
    ULONG           icol,
        icolStart;
    DBCOLUMNINFO*   rgdbcolinfo;
    WCHAR*          pstrBuffer;

    // Usual argument checking, prescribed by the spec.
    if (pcColumns == NULL || prgInfo == NULL || ppStringsBuffer == NULL)
        return ResultFromScode( E_INVALIDARG );

    // Fill in with something, in case of error later.
    *pcColumns   = 0;
    *prgInfo     = NULL;
    *ppStringsBuffer = NULL;

    icolStart   = 1;
    rgdbcolinfo = (DBCOLUMNINFO *) g_pIMalloc->Alloc( m_pObj->m_cCols*sizeof( DBCOLUMNINFO ));
    if (rgdbcolinfo == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    memcpy( rgdbcolinfo, &(m_pObj->m_rgdbcolinfo[icolStart]), m_pObj->m_cCols*sizeof( DBCOLUMNINFO ));

    // Copy the heap for column names.
    if (m_pObj->m_cbHeapUsed)
        {
        ptrdiff_t dp;

        pstrBuffer = (WCHAR *) g_pIMalloc->Alloc( m_pObj->m_cbHeapUsed );
        if (pstrBuffer == NULL)
            {
            g_pIMalloc->Free( rgdbcolinfo );
            return ResultFromScode( E_OUTOFMEMORY );
            }
        memcpy( pstrBuffer, m_pObj->m_pbHeap, m_pObj->m_cbHeapUsed );
        dp = (LONG) pstrBuffer - (LONG) (m_pObj->m_pbHeap);
        dp >>= 1;

        /* Loop through columns and adjust pointers to column names. */
        for (icol =0; icol < m_pObj->m_cCols; icol++)
            {
            if (rgdbcolinfo[icol].pwszName)
                rgdbcolinfo[icol].pwszName += dp;
            }
        }

    *prgInfo     = rgdbcolinfo;
    *ppStringsBuffer = pstrBuffer;
    *pcColumns   = m_pObj->m_cCols;

    return ResultFromScode( S_OK );
}

// CImpIColumnsInfo::MapColumnIDs --------------------------------------------
//
// @mfunc Returns an array of ordinals of the columns in a rowset that are
// identified by the specified column IDs.
//
// @rdesc HRESULT
//      @flag S_OK                      | The method succeeded
//      @flag E_INVALIDARG              | cColumnIDs was not 0 and rgColumnIDs was NULL,
//                                        rgColumns was NULL
//      @flag DB_E_COLUMNUNAVAILABLE    | An element of rgColumnIDs was invalid
//
STDMETHODIMP CImpIColumnsInfo::MapColumnIDs
    (
    ULONG           cColumnIDs,     //@parm IN | Number of Column IDs to map
    const DBIDrgColumnIDs[],  //@parm IN | Column IDs to map
    ULONG           rgColumns[]     //@parm OUT | Ordinal values
    )
{
    BOOL    fError = FALSE;
    ULONG   i;

    // Check arguments
    if ((cColumnIDs != 0) && (NULL == rgColumnIDs))
        return ResultFromScode( E_INVALIDARG );

    if (NULL == rgColumns)
        return ResultFromScode( E_INVALIDARG );

    if (0 == cColumnIDs)
        {
        return ResultFromScode( S_OK );
        }

    // Walk the Column ID structs and determine
    // the ordinal value
    for (i=0; i < cColumnIDs; i++)
        {
        if ((rgColumnIDs[i].uName.ulPropid < 1)               ||
            (rgColumnIDs[i].uName.ulPropid > m_pObj->m_cCols) ||
            (rgColumnIDs[i].eKind != DBKIND_GUID_PROPID)      ||
            (rgColumnIDs[i].uGuid.guid != GUID_NULL))
            {
            rgColumns[i] = DB_INVALIDCOLUMN;
            fError = TRUE;
            }
        else
            rgColumns[i] = rgColumnIDs[i].uName.ulPropid;
        }

    if (TRUE == fError)
        return ResultFromScode( DB_S_ERRORSOCCURRED );

    return ResultFromScode( S_OK );
}


CRTSESS.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module CRTSESS.CPP | IDBCreateSession interface implementation
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// CImpIDBCreateSession::CreateSession ------------------------------------------------
//
// @mfunc Creates a new DB Session object from the DSO, and returns the
// requested interface on the newly created object.
//
// @rdesc HRESULT
//      @flag S_OK                  | The method succeeded.
//      @flag E_INVALIDARG          | ppDBSession was NULL
//      @flag DB_E_NOAGGREGATION    | pUnkOuter was not NULL (this object does not support
//                                    being aggregated)
//      @flag E_FAIL                | Provider-specific error. This provider can only create
//                                    one DBSession
//      @flag E_OUTOFMEMORY         | Out of memory
//      @flag E_NOINTERFACE         | Could not obtain requested interface on DBSession object
//
STDMETHODIMP CImpIDBCreateSession::CreateSession
    (
    IUnknown*   pUnkOuter,  //@parm IN | Controlling IUnknown if being aggregated
    REFIID      riid,       //@parm IN | The ID of the interface
    IUnknown**  ppDBSession //@parm OUT | A pointer to memory in which to return the interface pointer
    )
{
    CDBSession* pDBSession = NULL;
    HRESULT hr;

    // check in-params and NULL out-params in case of error
    if (ppDBSession)
        *ppDBSession = NULL;
    else
        return ResultFromScode( E_INVALIDARG );

    if (pUnkOuter)
        return ResultFromScode( DB_E_NOAGGREGATION );

    assert( m_pObj );

    // this Data Source object can only create 1 DBSession...
    if (m_pObj->m_fDBSessionCreated)
        return ResultFromScode( E_FAIL );

    // open a DBSession object
    pDBSession = new CDBSession( NULL );
    if (!pDBSession)
        return ResultFromScode( E_OUTOFMEMORY );

    // initialize the object
    if (!pDBSession->FInit( m_pObj->m_szPath, m_pObj ))
        {
        delete pDBSession;
        return ResultFromScode( E_FAIL );
        }

    // get requested interface pointer on DBSession
    hr = pDBSession->QueryInterface( riid, (void **) ppDBSession );
    if (FAILED( hr ))
        {
        delete pDBSession;
        return ResultFromScode( hr );
        }

    // all went well
    m_pObj->m_fDBSessionCreated = TRUE;
    return ResultFromScode( S_OK );
}


DATASRC.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DATASRC.CPP | CDataSource object implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// CDataSource::CDataSource --------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CDataSource::CDataSource
    (
    LPUNKNOWN pUnkOuter         //@parm IN | Outer Unkown Pointer
    )
{

    //  Initialize simple member vars
    m_cRef              = 0L;
    m_pUnkOuter         = pUnkOuter;
    *m_szPath           = '\0';
    m_fDSOInitialized   = FALSE;
    m_fDBSessionCreated = FALSE;
    m_pUtilProp         = NULL;

    //  Initially, NULL all contained interfaces
    m_pIDBInitialize    = NULL;
    m_pIDBProperties= NULL;
    m_pIDBInfo= NULL;
m_pIDBCreateSession = NULL;

    // Increment global object count.
    OBJECT_CONSTRUCTED();

    return;
}

// CDataSource::~CDataSource -------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CDataSource:: ~CDataSource
    (
    void
    )
{
ULONGulRefCount;

// Decrement the ref count on the data conversion object
if( g_pIDataConvert )
{
ulRefCount = g_pIDataConvert->Release();

// Is it gone for good?
if( !ulRefCount )
g_pIDataConvert = NULL;
}

    // Free properties management object
    delete m_pUtilProp;

    //  Free contained interfaces
    delete m_pIDBInitialize;
    delete m_pIDBProperties;
    delete m_pIDBInfo;
delete m_pIDBCreateSession;

    // Decrement global object count.
    OBJECT_DESTRUCTED();

    return;
}

// CDataSource::FInit --------------------------------------------------------
//
// @mfunc Initialize the command Object
//
// @rdesc Did the Initialization Succeed
//      @flag  TRUE | Initialization succeeded
//      @flag  FALSE | Initialization failed
//
BOOL CDataSource::FInit
    (
    void
    )
{
HRESULThr;
    LPUNKNOWN   pIUnknown = (LPUNKNOWN) this;

    if (m_pUnkOuter)
        pIUnknown = m_pUnkOuter;

// Instantiate the data conversion service object
if( !g_pIDataConvert )
{
hr = CoCreateInstance(CLSID_OLEDB_CONVERSIONLIBRARY,
  NULL,
  CLSCTX_INPROC_SERVER,
  IID_IDataConvert,
  (void **)&g_pIDataConvert);
if( FAILED(hr) )
return FALSE;
}
else
// Already instantiated, increment reference count
g_pIDataConvert->AddRef();

    // Allocate properties management object
    m_pUtilProp = new CUtilProp();

    //  Allocate contained interface objects
    m_pIDBInitialize    = new CImpIDBInitialize( this, pIUnknown );
    m_pIDBProperties= new CImpIDBProperties( this, pIUnknown );
m_pIDBInfo= new CImpIDBInfo( this, pIUnknown );
    m_pIDBCreateSession = new CImpIDBCreateSession( this, pIUnknown );

    return (BOOL) (m_pIDBInitialize &&
   m_pIDBInfo &&
                   m_pIDBProperties&&
                   m_pIDBCreateSession);
}

// CDataSource::QueryInterface -----------------------------------------------
//
// @mfunc Returns a pointer to a specified interface. Callers use
// QueryInterface to determine which interfaces the called object
// supports.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK          | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG  | One or more arguments are invalid.
//
STDMETHODIMP CDataSource::QueryInterface
    (
    REFIID riid,    //@parm IN | Interface ID of the interface being queried for.
    LPVOID * ppv    //@parm OUT | Pointer to interface that was instantiated
    )
{
    // Is the pointer bad?
    if (ppv == NULL)
        return ResultFromScode( E_INVALIDARG );

    //  Place NULL in *ppv in case of failure
    *ppv = NULL;

    //  This is the non-delegating IUnknown implementation
    if (riid == IID_IUnknown)
        *ppv = (LPVOID) this;
    else if (riid == IID_IDBInitialize)
        *ppv = (LPVOID) m_pIDBInitialize;
else if (riid == IID_IDBInfo)
*ppv = (LPVOID) m_pIDBInfo;
else if (riid == IID_IDBProperties)
        *ppv = (LPVOID) m_pIDBProperties;
    else if (riid == IID_IDBCreateSession)
        *ppv = (LPVOID) m_pIDBCreateSession;

    //  If we're going to return an interface, AddRef it first
    if (*ppv)
        {
        ((LPUNKNOWN) *ppv)->AddRef();
        return ResultFromScode( S_OK );
        }
    else
        return ResultFromScode( E_NOINTERFACE );
}

// CDataSource::AddRef -------------------------------------------------------
//
// @mfunc Increments a persistence count for the object
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CDataSource::AddRef
     (
     void
     )
{
    return ++m_cRef;
}

// CDataSource::Release ------------------------------------------------------
//
// @mfunc Decrements a persistence count for the object and if
// persistence count is 0, the object destroys itself.
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CDataSource::Release
     (
     void
     )
{
    if (!--m_cRef)
        {
        delete this;
        return 0;
        }

    return m_cRef;
}


DATASRC.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DATASRC.H | CDataSource base object and contained interface
// definitions
//
//
#ifndef _DATASRC_H_
#define _DATASRC_H_

// Forward declarations ------------------------------------------------------

class   CImpIDBInitialize;
class   CImpIDBProperties;
classCImpIDBInfo;
class   CImpIDBCreateSession;
classCDataSource;

typedef CImpIDBInitialize*      PIMPIDBINITIALIZE;
typedef CImpIDBProperties*      PIMPIDBProperties;
typedef CImpIDBInfo*PIMPIDBINFO;
typedef CImpIDBCreateSession*   PIMPIDBCREATESESSION;
typedef CDataSource*PCDATASOURCE;

// For common dialog browsing directories
typedef struct tagBROWSEINFO
{
OPENFILENAME*lpofn;             // Pointer to OPENFILENAME
UINTuFileOkString;      // FILEOKSTRING message
LPCSTRszPrompt;// Prompt text, NULL if not required
} BROWSEINFO,  * lpBROWSEINFO;

// Classes -------------------------------------------------------------------

//----------------------------------------------------------------------------
// @class CDataSource | Containing class for all interfaces on the Datasource
// CoType Object
//
class CDataSource : public IUnknown//@base public | IUnknown
{
//Contained interfaces are friends
friend class CImpIDBInitialize;
    friend class CImpIDBInfo;
friend class CImpIDBProperties;
    friend class CImpIDBCreateSession;
    friend class CDBSession;

protected: //@access protected
//@cmember Reference count
ULONGm_cRef;
//@cmember Path Name
charm_szPath[MAX_PATH];
        //@member flag == TRUE if this Data Source object is an an initialized state
        BOOL                        m_fDSOInitialized;
        //@member flag == TRUE if DBSession object has been created
        BOOL                        m_fDBSessionCreated;
        //@member Utility object to manage properties
        PCUTILPROP                  m_pUtilProp;
//@cmember Controlling IUnknown
LPUNKNOWNm_pUnkOuter;
//@cmember Contained IDBInitialize
PIMPIDBINITIALIZEm_pIDBInitialize;
        //@cmember Contained IDBProperties
        PIMPIDBProperties           m_pIDBProperties;
//@cmember Contained IDBInfo
PIMPIDBINFOm_pIDBInfo;
        //@member contained IDBCreateSession
        PIMPIDBCREATESESSION        m_pIDBCreateSession;

public: //@access public
//@cmember Constructor
 CDataSource(LPUNKNOWN);
//@cmember Destructor
~CDataSource(void);

//@cmember Intitialization Routine
BOOL FInit(void);

//Object's base IUnknown
//@cmember Request an Interface
STDMETHODIMPQueryInterface(REFIID, LPVOID *);
//@cmember Increments the Reference count
STDMETHODIMP_(ULONG)AddRef(void);
//@cmember Decrements the Reference count
STDMETHODIMP_(ULONG)Release(void);
//@cmember Set the DBSessionCreated flag to FALSE
inline VOIDRemoveSession(void)
{ m_fDBSessionCreated = FALSE;};
};

typedef CDataSource *PCDATASOURCE;

//----------------------------------------------------------------------------
// @class CImpIDBInitialize | Contained IDBInitialize class
//
class CImpIDBInitialize : public IDBInitialize//@base public | IDBInitialize
{
private:  //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDataSource)

public:  //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDataSource, CImpIDBInitialize);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IDBInitialize members
//@cmember Initialize Method
    STDMETHODIMPInitialize(void);
//@cmember Uninitialize Method
        STDMETHODIMP            Uninitialize(void);
};

//----------------------------------------------------------------------------------------
// @class CImpIDBCreateSession   | contained IDBCreateSession class

class CImpIDBCreateSession : public IDBCreateSession    //@base public | IDBCreateSession
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDataSource)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDataSource, CImpIDBCreateSession);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

        // IDBCreateSession method
        //@cmember CreateSession method
        STDMETHODIMP    CreateSession( IUnknown*, REFIID, IUnknown** );
};

//----------------------------------------------------------------------------
// @class CImpIDBProperties | Contained IDBProperties class
//
class CImpIDBProperties : public IDBProperties//@base public | IDBProperties
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDataSource)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDataSource, CImpIDBProperties);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IDBProperties member functions

        //@cmember GetProperties method
        STDMETHODIMP GetProperties
        (
ULONGcPropertySets,
      const DBPROPIDSETrgPropertySets[],
        ULONG*              pcProperties,
 DBPROPSET**prgProperties
        );

        //@cmember GetPropertyInfo method
        STDMETHODIMP    GetPropertyInfo
                        (
ULONGcPropertySets,
const DBPROPIDSETrgPropertySets[],
ULONG*pcPropertyInfoSets,
DBPROPINFOSET**prgPropertyInfoSets,
WCHAR**ppDescBuffer
                        );

        //@cmember SetProperties method
        STDMETHODIMPSetProperties
 (
ULONGcProperties,
 DBPROPSETrgProperties[]
);
};

//----------------------------------------------------------------------------
// @class CImpIDBInfo | Contained IDBInfo class
//
class CImpIDBInfo : public IDBInfo//@base public | IDBInfo
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDataSource)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDataSource, CImpIDBInfo);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IDBProperties member functions

        //@cmember GetKeywords method
        STDMETHODIMP    GetKeywords
                        (
                        LPWSTR*ppwsKeywords
                        );

        //@cmember GetLiteralInfo method
        STDMETHODIMP    GetLiteralInfo
                        (
                        ULONG           cLiterals,
const DBLITERAL rgLiterals[ ],
ULONG*          pcLiteralInfo,
DBLITERALINFO** prgLiteralInfo,
WCHAR**         ppCharBuffer
                        );
};

// Support function for the Browse dialog
// in IDBInitialize::Initialize
void WINAPI CenterDialog(HWND hDlg);
UINT WINAPI CommDlgHook(HWND hDlg, UINTmsg, WPARAMwParam, LPARAM lParam);
BOOL WINAPI BrowseDirs(HWND hDlg, HINSTANCE hInst, LPCSTR lpszPrompt, LPSTR lpszDrive);

#endif


DBINIT.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DBINIT.CPP | IDBInitialize interface implementation
//
//

// Includes ------------------------------------------------------------------
#include "headers.h"

// IDBInitialize specific interface methods

// CImpIDBInitialize::Initialize ---------------------------------------------
//
// @mfunc Initializes the DataSource object.. For this provider it requires
// that a valid path is given to where the file will be located.
//
// @rdesc HRESULT
//      @flag S_OK          | Path exists
//      @flag E_FAIL        | Invalid path
//      @flag E_INVALIDARG  | Invalid Parameters passed in
//
STDMETHODIMP CImpIDBInitialize::Initialize
    (
)
{
HRESULThr;
DBPROPIDSETrgPropertyIDSets[1];
ULONGcPropertySets;
DBPROPSET*prgPropertySets;
DBPROPIDrgPropId[1];

    char szNewVal[MAX_PATH ];

    assert( m_pObj );

    if (m_pObj->m_fDSOInitialized)
        return ResultFromScode( DB_E_ALREADYINITIALIZED );

rgPropId[0]= DBPROP_INIT_DATASOURCE;

rgPropertyIDSets[0].guidPropertySet= DBPROPSET_DBINIT;
rgPropertyIDSets[0].rgPropertyIDs= rgPropId;
rgPropertyIDSets[0].cPropertyIDs= 1;

    // Get the value of the DBPROP_INIT_DATASOURCE property
    hr = m_pObj->m_pUtilProp->GetProperties(
1,
rgPropertyIDSets,
&cPropertySets,
&prgPropertySets );

// On failure treat it as if we were opening with prompt..
    if( SUCCEEDED(hr) )
lstrcpy(szNewVal, (char *)V_BSTR(&prgPropertySets[0].rgProperties->vValue));
else
*szNewVal = '\0';

    // if caller didn't supply a directory path, ask the user
    if (*szNewVal == '\0')
    {
    if (!BrowseDirs( GetDesktopWindow(), g_hInstance, NULL, szNewVal ))
        {
            return ResultFromScode( E_FAIL );
    }
        else
        {
        lstrcpyn( m_pObj->m_szPath, szNewVal, MAX_PATH );
m_pObj->m_fDSOInitialized = TRUE;
return ResultFromScode( S_OK );
        }
    }
    else    // caller did supply a directory path
    {
    // Check if the directory is a valid directory
if (SetCurrentDirectory( szNewVal ))
        {
lstrcpyn( m_pObj->m_szPath, szNewVal, MAX_PATH );
m_pObj->m_fDSOInitialized = TRUE;
return ResultFromScode( S_OK );
        }
    else
        {
return ResultFromScode( E_FAIL );
        }
    }
}

// CImpIDBInitialize::Uninitialize ---------------------------------------------
//
// @mfunc Returns the Data Source Object to an uninitialized state
//
// @rdesc HRESULT
//      @flag S_OK            | The method succeeded
//      @flag DB_E_OBJECTOPEN | A DBSession object was already created
STDMETHODIMP CImpIDBInitialize::Uninitialize
    (
    void
    )
{
    assert( m_pObj );

    if (!m_pObj->m_fDSOInitialized)
        {
        // data source object is not initialized; do nothing
        return ResultFromScode( S_OK );
        }
    else
        {
        if (!m_pObj->m_fDBSessionCreated)
            {
            // DSO initialized, but no DBSession has been created.
            // So, reset DSO to uninitialized state
            m_pObj->m_fDSOInitialized = FALSE;
            return ResultFromScode( S_OK );
            }
        else
            {
            // DBSession has already been created; trying to uninit
            // the DSO now is an error
            return ResultFromScode( DB_E_OBJECTOPEN );
            }
        }
}

//////////////////////////////////////////////////////////////////////////////
//   Helper Functions   Helper Functions    Helper Functions
//////////////////////////////////////////////////////////////////////////////

// BrowseDirs ----------------------------------------------------------------
//
// @func Common dialog look-alike which allows you to select a direcotry.
//
// @rdesc BOOLEAN value indicates success or failure
//      @flag TRUE | Directory was obtained
//      @flag FALSE | No Directory was obtained
//
BOOL WINAPI BrowseDirs
    (
    HWND        hDlg,       //@parm IN | Window Handle
    HINSTANCE   hInst,      //@parm IN | Instance of this DLL
    LPCSTR      lpszPrompt, //@parm IN | Prompt text for dialog, NULL for default
    LPSTR       lpszDrive   //@parm OUT | Drive Name
    )
{
    OPENFILENAME    ofn;
    char            szFile[_MAX_PATH ];
    BROWSEINFO      bi;

    lstrcpy( szFile, "DUMMY.TXT" );

    memset( &bi, 0, sizeof( BROWSEINFO ));
    bi.szPrompt = lpszPrompt;

    memset( &ofn, 0, sizeof( ofn ));
    ofn.lStructSize       = sizeof( OPENFILENAME );
    ofn.hwndOwner         = hDlg;
    ofn.hInstance         = hInst;
    ofn.lpstrFile         = (LPSTR) szFile;
    ofn.nMaxFile          = sizeof( szFile );
    ofn.lpstrInitialDir   = lpszDrive;
    ofn.Flags             = OFN_ENABLEHOOK       |
                            OFN_ENABLETEMPLATE   |
                            OFN_HIDEREADONLY     |
                            OFN_NOTESTFILECREATE |
                            OFN_PATHMUSTEXIST    |
                            OFN_SHAREAWARE;
    ofn.lpfnHook          = CommDlgHook;
    ofn.lpTemplateName    = MAKEINTRESOURCE( IDD_BROWSE );
    ofn.lCustData         = (LPARAM) (lpBROWSEINFO) & bi;
    if (GetOpenFileName( &ofn ))
        {
        lstrcpyn( lpszDrive, szFile, ofn.nFileOffset );
        *(lpszDrive + ofn.nFileOffset - 1) = '\0';
        return TRUE;
        }

    return FALSE;
}

// CenterDialog --------------------------------------------------------------
//
// @func Center the dialog over the parent window.
//
// @rdesc NONE
//
void WINAPI CenterDialog
    (
    HWND hDlg                   //@parm IN | Window to Center
    )
{
    RECT  rcParent;         // Parent window client rect
    RECT  rcDlg;            // Dialog window rect
    int   nLeft, nTop;      // Top-left coordinates
    int   cWidth, cHeight;  // Width and height
    HWND  hwnd;

    // Get frame window client rect in screen coordinates
    if ((hwnd = GetParent( hDlg )) == NULL)
        {
        rcParent.top = rcParent.left = 0;
        rcParent.right = GetSystemMetrics( SM_CXFULLSCREEN );
        rcParent.bottom = GetSystemMetrics( SM_CYFULLSCREEN );
        }
    else
        GetWindowRect( hwnd, &rcParent );

    // Determine the top-left point for the dialog to be centered
    GetWindowRect( hDlg, &rcDlg );
    cWidth  = rcDlg.right - rcDlg.left;
    cHeight = rcDlg.bottom - rcDlg.top;
    nLeft   = rcParent.left +
        (((rcParent.right - rcParent.left) - cWidth) / 2);
    nTop    = rcParent.top +
        (((rcParent.bottom - rcParent.top) - cHeight) / 2);
    if (nLeft < 0)
        nLeft = 0;
    if (nTop < 0)
        nTop  = 0;

    // Place the dialog
    MoveWindow( hDlg, nLeft, nTop, cWidth, cHeight, TRUE );
    return;
}

// CommDlgHook ---------------------------------------------------------------
//
// @func Hook function for browsing a directory.  Need for custom info.
//
// @rdesc UINT value indicating hook procs status
//
UINT CALLBACK CommDlgHook
    (
    HWND    hDlg,   //@parm IN | Window Handle
    UINT    msg,    //@parm IN | Window Message
    WPARAM  wParam, //@parm IN | Standard Value
    LPARAM  lParam  //@parm IN | Standard Value
    )
{
    lpBROWSEINFO    lpbi;

    lpbi = (lpBROWSEINFO) GetWindowLong( hDlg, GWL_USERDATA );

    switch (msg)
        {
    case WM_INITDIALOG:
        {
            OPENFILENAME FAR *  lpofn;

            lpofn = (OPENFILENAME FAR *) lParam;
            lpbi = (lpBROWSEINFO) lpofn->lCustData;
            SetWindowLong( hDlg, GWL_USERDATA, (LONG) lpbi );

            CenterDialog( hDlg );

            lpbi->uFileOkString = RegisterWindowMessage( FILEOKSTRING );
            lpbi->lpofn         = (LPOPENFILENAME) lParam;
            if (lpbi->szPrompt)
                SetWindowText( GetDlgItem( hDlg, IDT_PROMPT ), (LPSTR) lpbi->szPrompt );
        }
        return TRUE;

    case WM_CLOSE:
        if (lpbi)
            SetWindowLong( hDlg, GWL_USERDATA, (LONG) 0L );
        break;

    default:
        break;
        }

    return FALSE;
}


DBPROP.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DBPROP.CPP | IDBProperties and IDBInfo interface implementations
//
//

// Includes ------------------------------------------------------------------
#include "headers.h"

// Code ----------------------------------------------------------------------

//  IDBInfo specific interface methods
// CImpIDBInfo::GetKeywords  --------------------------------------------
//
// @mfunc Returns information about keywords used in text commands
//
// @rdesc HRESULT
//      @flag E_INVALIDARG  | ppwszKeywords was NULL
//      @flag E_NOTIMPL| this method is not implemented
//
STDMETHODIMP    CImpIDBInfo::GetKeywords
    (
    LPWSTR*ppwszKeywords
    )
{
    // check params
    if (NULL == ppwszKeywords)
return ResultFromScode( E_INVALIDARG );

    // we don't implement this method, fail gracefully
    *ppwszKeywords= NULL;

    return ResultFromScode( E_NOTIMPL );
}

// CImpIDBInfo::GetLiteralInfo  -----------------------------------------
//
// @mfunc Returns information about literals used in text command
//
// @rdesc HRESULT
//      @flag E_INVALIDARG  | cLiterals not equal to 9 and rgLiterals was NULL or
//| pcLiteralInfo, prgLiteralInfo, or ppCharBuffer was NULL
//      @flag E_NOTIMPL| this method is not implemented
//
STDMETHODIMP    CImpIDBInfo::GetLiteralInfo
    (
    ULONG           cLiterals,      //@parm IN | Number of literals being asked about
const DBLITERAL rgLiterals[],   //@parm IN | Array of literals about which to return
                        //           information
ULONG*          pcLiteralInfo,  //@parm OUT | Number of literals for which info is
                            //            returned
DBLITERALINFO** prgLiteralInfo, //@parm OUT | Array of info structures
WCHAR**         ppCharBuffer    //@parm OUT | Buffer for characters
    )
{
    // check params
    if ((cLiterals != 0) && (rgLiterals == NULL))
        return ResultFromScode( E_INVALIDARG );

    if (!pcLiteralInfo || !prgLiteralInfo || !ppCharBuffer)
        return ResultFromScode( E_INVALIDARG );

    // we don't implement this method, fail gracefully
    *pcLiteralInfo  = 0;
    *prgLiteralInfo = NULL;
    *ppCharBuffer   = NULL;

    return ResultFromScode( E_NOTIMPL );
}

//  IDBProperties specific interface methods

// CImpIDBProperties::GetPropertyInfo  -----------------------------------------
//
// @mfunc Returns information about rowset and data source properties supported
// by the provider
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcPropertyInfo or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP    CImpIDBProperties::GetPropertyInfo
    (
    ULONGcPropertySets,//@parm IN  | Number of properties being asked about
    const DBPROPIDSETrgPropertySets[],//@parm IN  | Array of cPropertySets properties about
                                //            which to return information
    ULONG*pcPropertyInfoSets,//@parm OUT | Number of properties for which information
                                    //            is being returned
    DBPROPINFOSET**prgPropertyInfoSets,//@parm OUT | Buffer containing default values returned
WCHAR**ppDescBuffer//@parm OUT| Buffer containing property descriptions
    )
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->GetPropertyInfo(
cPropertySets,
rgPropertySets,
pcPropertyInfoSets,
prgPropertyInfoSets,
ppDescBuffer);
}

// IDBProperties::GetProperties ----------------------------------------------------
//
// @mfunc Returns current settings of all properties in the DBPROPFLAGS_DATASOURCE
//property group
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcProperties or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CImpIDBProperties::GetProperties
    (
    ULONGcPropertySets,//@parm IN | count of restiction guids
const DBPROPIDSETrgPropertySets[],//@parm IN | restriction guids
ULONG*              pcProperties,//@parm OUT | count of properties returned
DBPROPSET**prgProperties//@parm OUT | property information returned
    )
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->GetProperties(
cPropertySets,
rgPropertySets,
pcProperties,
prgProperties );
}

// CImpIDBProperties::SetProperties  --------------------------------------------
//
// @mfunc Set properties in the DBPROPFLAGS_DATASOURCE property group
//
// @rdesc HRESULT
//      @flag E_INVALIDARG  | cProperties was not equal to 0 and rgProperties was NULL
//      @flag E_NOTIMPL| this method is not implemented
//
STDMETHODIMP    CImpIDBProperties::SetProperties
    (
    ULONGcProperties,
DBPROPSETrgProperties[]
)
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->SetProperties(
cProperties,
rgProperties);
}


DBSESS.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DBSESS.CPP | CDBSession object implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// CDBSession::CDBSession --------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CDBSession::CDBSession
    (
    LPUNKNOWN pUnkOuter         //@parm IN | Outer Unkown Pointer
    )
{
    //  Initialize simple member vars
    m_cRef              = 0L;
    m_pUnkOuter         = pUnkOuter;
    *m_szPath           = '\0';
    m_fRowsetCreated    = FALSE;

    // Initially, NULL all contained interfaces
    m_pIGetDataSource= NULL;
    m_pIOpenRowset= NULL;

// Pointer to parent object
m_pCDataSource= NULL;

    // Increment global object count.
    OBJECT_CONSTRUCTED();

    return;
}

// CDBSession::~CDBSession -------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CDBSession:: ~CDBSession
    (
    void
    )
{
    // Free contained interfaces
    delete m_pIGetDataSource;
    delete m_pIOpenRowset;

m_pCDataSource->Release();
m_pCDataSource->RemoveSession();

    // Decrement global object count.
    OBJECT_DESTRUCTED();

    return;
}

// CDBSession::FInit --------------------------------------------------------
//
// @mfunc Initialize the command Object
//
// @rdesc Did the Initialization Succeed
//      @flag  TRUE | Initialization succeeded
//      @flag  FALSE | Initialization failed
//
BOOL CDBSession::FInit
    (
    char*szPath,
CDataSource*pCDataSource
    )
{
    LPUNKNOWN   pIUnknown = (LPUNKNOWN) this;

    if (m_pUnkOuter)
        pIUnknown = m_pUnkOuter;

    // just make a copy for ourselves.
    if (!lstrcpyn( m_szPath, szPath, MAX_PATH ))
        return FALSE;

// Establish parent object pointer
assert( pCDataSource );
m_pCDataSource= pCDataSource;

m_pCDataSource->AddRef();

    // Allocate contained interface objects
    m_pIGetDataSource= new CImpIGetDataSource( this, pIUnknown );
    m_pIOpenRowset= new CImpIOpenRowset( this, pIUnknown );
    m_pISessionProperties= new CImpISessionProperties( this, pIUnknown );

    return (BOOL) (m_pIGetDataSource && m_pIOpenRowset);
}

// CDBSession::QueryInterface -----------------------------------------------
//
// @mfunc Returns a pointer to a specified interface. Callers use
// QueryInterface to determine which interfaces the called object
// supports.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK          | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG  | One or more arguments are invalid.
//
STDMETHODIMP CDBSession::QueryInterface
    (
    REFIID riid,        //@parm IN | Interface ID of the interface being queried for.
    LPVOID * ppv        //@parm OUT | Pointer to interface that was instantiated
    )
{
    // Is the pointer bad?
    if (ppv == NULL)
        return ResultFromScode( E_INVALIDARG );

    //  Place NULL in *ppv in case of failure
    *ppv = NULL;

    //  This is the non-delegating IUnknown implementation
    if (riid == IID_IUnknown)
        *ppv = (LPVOID) this;
else if (!m_pCDataSource->m_fDSOInitialized)
return ResultFromScode(E_UNEXPECTED);
    else if (riid == IID_IGetDataSource)
        *ppv = (LPVOID) m_pIGetDataSource;
    else if (riid == IID_IOpenRowset)
        *ppv = (LPVOID) m_pIOpenRowset;
    else if (riid == IID_ISessionProperties)
        *ppv = (LPVOID) m_pISessionProperties;

    //  If we're going to return an interface, AddRef it first
    if (*ppv)
        {
        ((LPUNKNOWN) *ppv)->AddRef();
        return ResultFromScode( S_OK );
        }
    else
        return ResultFromScode( E_NOINTERFACE );
}

// CDBSession::AddRef -------------------------------------------------------
//
// @mfunc Increments a persistence count for the object
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CDBSession::AddRef
     (
     void
     )
{
    return ++m_cRef;
}

// CDBSession::Release ------------------------------------------------------
//
// @mfunc Decrements a persistence count for the object and if
// persistence count is 0, the object destroys itself.
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CDBSession::Release
     (
     void
     )
{
    if (!--m_cRef)
        {
        delete this;
        return 0;
        }

    return m_cRef;
}

//-----------------------------------------------------------------------------
// CImpIGetDataSource::GetDataSource
//
// @mfunc Retrieve an interface pointer on the session object
//
// @rdesc
//@flag S_OK | Session Object Interface returned
//@flag E_INVALIDARG | ppDataSource was NULL
//@flag E_NOINTERFACE | IID not supported
//
STDMETHODIMP CImpIGetDataSource::GetDataSource
(
REFIIDriid,// @parm IN  | IID desired
IUnknown**ppDataSource// @parm OUT | ptr to interface
)
{
// Check Function Arguments
if( ppDataSource == NULL )
return ResultFromScode(E_INVALIDARG);

assert( m_pObj->m_pCDataSource );
return ((m_pObj->m_pCDataSource)->QueryInterface(riid, (LPVOID*)ppDataSource));
}

// ISessionProperties::GetProperties ----------------------------------------------------
//
// @mfunc Returns current settings of all properties in the DBPROPFLAGS_SESSION property
//group
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcProperties or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CImpISessionProperties::GetProperties
    (
    ULONGcPropertySets,//@parm IN | count of restiction guids
const DBPROPIDSETrgPropertySets[],//@parm IN | restriction guids
ULONG*              pcProperties,//@parm OUT | count of properties returned
DBPROPSET**prgProperties//@parm OUT | property information returned
    )
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->GetProperties(
cPropertySets,
rgPropertySets,
pcProperties,
prgProperties );
}

// CImpISessionProperties::SetProperties  --------------------------------------------
//
// @mfunc Set properties in the DBPROPFLAGS_SESSION property group
//
// @rdesc HRESULT
//      @flag E_INVALIDARG  | cProperties was not equal to 0 and rgProperties was NULL
//      @flag E_NOTIMPL| this method is not implemented
//
STDMETHODIMP    CImpISessionProperties::SetProperties
    (
    ULONGcProperties,
DBPROPSETrgProperties[]
)
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->SetProperties(
cProperties,
rgProperties);
}


DBSESS.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module DBSESS.H | CDBSession base object and contained interface
// definitions
//
//
#ifndef _DBSESS_H_
#define _DBSESS_H_

// Forward declarations ------------------------------------------------------

class   CImpIGetDataSource;
class   CImpIOpenRowset;
classCImpISessionProperties;
typedef CImpIGetDataSource*PIMPIGETDATASOURCE;
typedef CImpIOpenRowset*PIMPIOPENROWSET;
typedef CImpISessionProperties*PIMPISESSIONPROPERTIES;

// Classes -------------------------------------------------------------------

//----------------------------------------------------------------------------
// @class CDBSession | Containing class for all interfaces on the DBSession
// Object
//
class CDBSession : public IUnknown//@base public | IUnknown
{
    // contained interfaces are friends
    friend class CImpIGetDataSource;
    friend class CImpIOpenRowset;
    friend class CImpIDataSource;
    friend class CImpISessionProperties;

protected: //@access protected
//@cmember Reference count
ULONGm_cRef;
//@cmember Controlling IUnknown
LPUNKNOWNm_pUnkOuter;
//@cmember Path Name
charm_szPath[MAX_PATH];
        //@member flag is TRUE if Rowset has been created
        BOOL                        m_fRowsetCreated;
        //@member Utility object to manage properties
        PCUTILPROP                  m_pUtilProp;

        //@member contained IOpenRowset
        PIMPIOPENROWSET             m_pIOpenRowset;

//@member contained IGetDataSource
PIMPIGETDATASOURCEm_pIGetDataSource;

//@member contained ISessionProperties
PIMPISESSIONPROPERTIESm_pISessionProperties;

public: //@access public
//@cmember Constructor
 CDBSession(LPUNKNOWN);
//@cmember Destructor
~CDBSession(void);

//@mcember parent data source object
PCDATASOURCEm_pCDataSource;

//@cmember Intitialization Routine
BOOL FInit( char* szPath, CDataSource*pCDataSource );

//Object's base IUnknown
//@cmember Request an Interface
STDMETHODIMPQueryInterface(REFIID, LPVOID *);
//@cmember Increments the Reference count
STDMETHODIMP_(ULONG)AddRef(void);
//@cmember Decrements the Reference count
STDMETHODIMP_(ULONG)Release(void);

inline void RowsetDestroyed() { m_fRowsetCreated = FALSE; };
};

typedef CDBSession *PCDBSESSION;

//----------------------------------------------------------------------------------------
// @class CImpIGetDataSource   | contained IGetDataSource class

class CImpIGetDataSource : public IGetDataSource      //@base public | IGetDataSource
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDBSession)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDBSession, CImpIGetDataSource);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

        // IGetDataSource method
        //@cmember GetDataSource
STDMETHODIMP  GetDataSource( REFIID, IUnknown** );
};

//----------------------------------------------------------------------------------------
// @class CImpIOpenRowset   | contained IOpenRowset class

class CImpIOpenRowset : public IOpenRowset      //@base public | IOpenRowset
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDBSession)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDBSession, CImpIOpenRowset);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

        // IOpenRowset method
        //@cmember OpenRowset
STDMETHODIMP  OpenRowset( IUnknown*, DBID*, DBID*, REFIID, ULONG, DBPROPSET[], IUnknown** );
};

//----------------------------------------------------------------------------
// @class CImpISessionProperties | Contained ISessionProperties class
//
class CImpISessionProperties : public ISessionProperties//@base public | ISessionProperties
{
private:        //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CDBSession)

public:         //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CDBSession, CImpISessionProperties);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IDBProperties member functions

        //@cmember GetProperties method
        STDMETHODIMP GetProperties
        (
ULONGcPropertySets,
      const DBPROPIDSETrgPropertySets[],
        ULONG*              pcProperties,
 DBPROPSET**prgProperties
        );

        //@cmember SetProperties method
        STDMETHODIMPSetProperties
 (
ULONGcProperties,
 DBPROPSETrgProperties[]
);
};

#endif


EXTBUFF.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module EXTBUFF.CPP | This class provides an array-based access to
// a big block of memory. You pass in the fixed size of each element
// to the constructor. Get (read)  an element with GetItemOfExtBuffer.
// Set (write) an element with InsertIntoExtBuffer.
// You must also pass in the system page size (found with GetSystemInfo).
// 'hItem' is the index into the array, beginning with 1.
// The array always contains m_cItem elements.
//
// GetItemOfExtBuffer returns element hItem, where hItem=1 is the first element.
// (Copies block to caller's memory.)
//
// InsertIntoExtBuffer always appends elements to the tail of the array,
// and returns the index of the newly appended element.
// (So adding first element will return 1.)
//
//
#include "headers.h"
#include "extbuff.h"

//--------------------------------------------------------------------
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CExtBuffer::CExtBuffer
    (
    void
    )
{
    m_cItem   = 0;
    m_cbAlloc = 0;
    m_rgItem  = NULL;
}

//--------------------------------------------------------------------
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CExtBuffer:: ~CExtBuffer
    (
    void
    )
{
    if (m_cbAlloc)
        VirtualFree((VOID *) m_rgItem, m_cbAlloc, MEM_DECOMMIT );

    if (m_rgItem)
        VirtualFree((VOID *) m_rgItem, 0, MEM_RELEASE );
}

//--------------------------------------------------------------------
// @mfunc Allocate and Initialize Buffer
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK | Initialization succeeded
//      @flag  E_OUTOFMEMORY | Not enough memory to allocate buffer
//
STDMETHODIMP CExtBuffer::FInit
    (
    ULONG cItemMax,     //@parm IN | Maximum number of items ever
    ULONG cbItem,       //@parm IN | Size of each item, in bytes
    ULONG cbPage        //@parm IN | Size of system page size (from SysInfo)
    )
{
    BYTE  *pb;

    m_cbReserved = ((cbItem *cItemMax) / cbPage + 1) *cbPage;
    m_rgItem = (BYTE *) VirtualAlloc( NULL, m_cbReserved, MEM_RESERVE, PAGE_READWRITE );

    if (m_rgItem == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    m_cbItem  = cbItem;
    m_dbAlloc = (cbItem / cbPage + 1) *cbPage;
    pb = (BYTE *) VirtualAlloc( m_rgItem, m_dbAlloc, MEM_COMMIT, PAGE_READWRITE );
    if (pb == NULL)
        {
        VirtualFree((VOID *) m_rgItem, 0, MEM_RELEASE );
        m_rgItem = NULL;
        return ResultFromScode( E_OUTOFMEMORY );
        }

    m_cbAlloc = m_dbAlloc;
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Retrieves a pointer to the value at given index
//
// @rdesc If index is within the range of 1 to m_cItems then a valid
// pointer is returned, else NULL is returned.
//
void* CExtBuffer::operator[]
    (
    ULONG hItem          //@parm IN | Index of element in buffer
    )
{
    // Return ptr to element [n], where n = 1...m_cItem.
    // Returns NULL if 'n' is out of range.
    //
    // You must use InsertIntoExtBuffer to add new elements.
    // Thereafter you can use this operator to retrieve the address of the item.
    // (You can't delete an element, but you can overwrite its space.)

    if (1 <= hItem && hItem <= m_cItem)
        return m_rgItem + (hItem - 1) *m_cbItem;
    else
        return NULL;
}

//--------------------------------------------------------------------
// @mfunc Add Data to the fixed buffers and return the index it was
// added at.
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK | Data copied successfully
//      @flag  E_OUTOFMEMORY | Not enough memory to allocate buffer
//
STDMETHODIMP CExtBuffer::InsertIntoExtBuffer
    (
    VOID* pvItem,       //@parm IN | Pointer to buffer to copy
    ULONG &hItem        //@parm OUT | Index of where data was placed
    )
{
    ULONG cbOffset;

    cbOffset = m_cItem*m_cbItem;
    if ((cbOffset + m_cbItem) > m_cbAlloc)
        {
        BYTE *pb;

        if ((m_cbAlloc + m_dbAlloc) > m_cbReserved)
            pb = NULL;
        else
            pb = (BYTE *) VirtualAlloc( m_rgItem + m_cbAlloc,
                                        m_dbAlloc,
                                        MEM_COMMIT,
                                        PAGE_READWRITE );
        if (pb == NULL)
            {
            return ResultFromScode( E_OUTOFMEMORY );
            }
        m_cbAlloc += m_dbAlloc;
        }

    memcpy((m_rgItem + cbOffset), (BYTE *) pvItem, m_cbItem );
    m_cItem++;
    hItem = m_cItem;

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Obtain a pointer to the data at a given index into the buffer
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK | pvItem contains a pointer to the data requested
//      @flag  E_INVALIDARG | Invalid Index passed in
//
STDMETHODIMP CExtBuffer::GetItemOfExtBuffer
    (
    ULONG hItem,        //@parm IN | Index of item to get
    VOID* pvItem        //@parm OUT | Pointer to block at index
    )
{
    if ((hItem > m_cItem) ||
(hItem == 0) )
        return ResultFromScode( E_INVALIDARG );

    memcpy((BYTE *) pvItem, (m_rgItem + (hItem - 1) *m_cbItem), m_cbItem );

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Get the extents of the currently allocated buffers
//
// @rdesc HRESULT indicating routines status
//      @flag  S_OK | Extents were obtained successfuly
//
STDMETHODIMP CExtBuffer::GetFirstLastItemH
    (
    ULONG &hItemFirst,      //@parm OUT | First item allocated
    ULONG &hItemLast        //@parm OUT | Last item allocated
    )
{
    hItemFirst = 1;
    hItemLast  = m_cItem;
    return ResultFromScode( S_OK );
}


EXTBUFF.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module EXTBUFF.H | Class Definitions for CExtBuffer Class
//
//
#ifndef _EXTBUFF_H_
#define _EXTBUFF_H_

// Forward Declaration
class FAR CExtBuffer;
typedef CExtBuffer FAR *LPEXTBUFFER;

//--------------------------------------------------------------------
// @class Allocates and manages fixed sized block memory routines
//
// @hungarian ext or pext
//
class FAR CExtBuffer
{
private: //@access private
//@cmember Current count of items
ULONG    m_cbItem;
//@cmember Item size, in bytes
ULONG    m_cItem;
//@cmember Reserved byte count
ULONG    m_cbReserved;
//@cmember Allocated byte count
ULONG    m_cbAlloc;
//@cmember increment value
ULONG    m_dbAlloc;
//@cmember Ptr to beginning of buffer
BYTE     *m_rgItem;

private:
// Not implemented; private so dcl prevents generation.
CExtBuffer( const CExtBuffer & p);
CExtBuffer& operator=(const CExtBuffer & p);

public://@access public
//@cmember Construcutor
CExtBuffer ( void );
//@cmember Destructor
~CExtBuffer ( void );
//@cmember Calculated data pointer from index value
void * operator[] (ULONG nIndex);
//@cmember Initialize the fixed size buffer
STDMETHODIMP FInit (ULONG cItemMax, ULONG cbItem, ULONG cbPage);
//@cmember Add new items to the buffer
STDMETHODIMP InsertIntoExtBuffer (VOID* pvItem, ULONG &hItem);
//cmember Retrieve items from buffer
STDMETHODIMP    GetItemOfExtBuffer (ULONG hItem,VOID* pvItem);
//@cmember Get usage extent indexes
STDMETHODIMP    GetFirstLastItemH (ULONG &hItemFirst, ULONG &hItemLast);
};

#endif


FILEIDX.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module FILEIDX.CPP | This module contains the index array code
// for a Comma Seperated Value (CSV) Simple Provider.
//
//
#include <assert.h>
#include <stdlib.h>
#include "fileidx.h"

static const ARRAY_INIT_SIZE = 1000;
static const DELETED_ROW = 1;

//--------------------------------------------------------------------
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CFileIdx::CFileIdx
    (
    void
    )
{
    m_rgDex = NULL;
    m_ulDexCnt = 0;
}

//--------------------------------------------------------------------
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CFileIdx:: ~CFileIdx()
{
    if (m_rgDex)
        free( m_rgDex );
}

//--------------------------------------------------------------------
// @mfunc Initialization routine
//
// @rdesc BOOLEAN value
//      @flag TRUE | Succeeded
//      @flag FALSE | Failed to Initialize
//
BOOL CFileIdx::fInit
    (
    void
    )
{
    //Allocate Offset Array
    m_rgDex = (LPFILEDEX) calloc( ARRAY_INIT_SIZE, sizeof( FILEDEX ));
    if (NULL == m_rgDex)
        return FALSE;

    m_ulDexCnt = ARRAY_INIT_SIZE;
    return TRUE;
}

//--------------------------------------------------------------------
// @mfunc ReAllocation of Current Index Array.  This can be called to
// increase the array of index structures.
//
// @rdesc BOOLEAN value
//      @flag TRUE | Succeeded
//      @flag FALSE | Failed to Initialize
//
BOOL CFileIdx::ReAlloc
    (
    ULONG ulRows                //@parm IN | Number of rows to increase array for.
    )
{
    VOID* pDex;

    // Change the array size
    pDex = realloc( m_rgDex, ((m_ulDexCnt + ulRows) * sizeof( FILEDEX )));
    if (NULL == pDex)
        return FALSE;

m_ulDexCnt += ulRows;
    m_rgDex = (FILEDEX*) pDex;
    return TRUE;
}

//--------------------------------------------------------------------
// @mfunc Set the offset into the file in bytes for a particular row.
// If the Index is beyond current allocation, reallocation is attempted.
//
// @rdesc BOOLEAN value
//      @flag TRUE | Succeeded
//      @flag FALSE | Failed to Initialize
//
BOOL CFileIdx::SetIndex
    (
    ULONG ulDex,        //@parm IN | Row Index value
    ULONG ulOffset      //@parm IN | Offset of Row in the File
    )
{
    // Check index and realloc if beyond our current range
    if (m_ulDexCnt <= ulDex)
        if (FALSE == ReAlloc( ARRAY_INIT_SIZE ))
            return FALSE;

    m_rgDex[ulDex].ulOffset = ulOffset;
    m_rgDex[ulDex].bStatus = FALSE;

    return TRUE;
}

//--------------------------------------------------------------------
// @mfunc Mark the row as deleted.
//
// @rdesc BOOLEAN value
//      @flag TRUE | Succeeded
//      @flag FALSE | Failed to Initialize
//
BOOL CFileIdx::DeleteRow
    (
    ULONG ulDex                 //@parm IN | Row Index value
    )
{
    // Index should alway be valid
    assert((0 <= ulDex) && (m_ulDexCnt > ulDex));

    // Mark Row
    m_rgDex[ulDex].bStatus = DELETED_ROW;

    return TRUE;
}

//--------------------------------------------------------------------
// @mfunc Has the row been deleted or not
//
// @rdesc BOOLEAN value
//      @flag TRUE | Row Already Deleted
//      @flag FALSE | Row has not been Deleted
//
BOOL CFileIdx::IsDeleted
    (
    ULONG ulDex                 //@parm IN | Row Index value
    )
{
    // Index should alway be valid
    assert((0 <= ulDex) && (m_ulDexCnt > ulDex));

    // Check deletion status
    if (DELETED_ROW != m_rgDex[ulDex].bStatus)
        return FALSE;

    return TRUE;
}

//--------------------------------------------------------------------
// @mfunc Return the File Offset that the row starts
//
// @rdesc Offset from Beginning of File
//
ULONG CFileIdx::GetRowOffset
    (
    ULONG ulDex                 //@parm IN | Row Index value
    )
{
    // Index should alway be valid
    assert((0 <= ulDex) && (m_ulDexCnt > ulDex));

    return m_rgDex[ulDex].ulOffset;
}


FILEIDX.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module FILEIDX.H | Class Definitions for CFileIdx Class
//
//
#ifndef _FILEIDX_H_
#define _FILEIDX_H_
#include <windows.h>

//--------------------------------------------------------------------
// @struct FILEDEX | Simple structure used to maintain file offset and
// deletion status for the rows.
//
typedef struct {
ULONGulOffset;//@field Offset into file
BYTEbStatus;//@field Deletion Status
} FILEDEX, FAR * LPFILEDEX;

//--------------------------------------------------------------------
// @class CFileIdx | Manages the array of offsets into a file.  This
// maintains whether the row has been deleted and where the row begins.
//
// @hungarian
//
class CFileIdx
{
private: //@access private
//@cmember Array of Offset and Status flags
LPFILEDEXm_rgDex;
//@cmember Current number of rows allocated
ULONGm_ulDexCnt;
//@cmember Reallocation Routine
BOOL ReAlloc(ULONG ulRows);

public: //@access public
//@cmember Class Constructor
CFileIdx(void);
//@cmember Class Destructor
~CFileIdx(void);
//@cmember Initialization Routine
BOOL fInit(void);
//@cmember Sets file offset of a particular index
BOOL SetIndex(ULONG ulDex,ULONG ulOffset);
//@cmember Sets the delete flag for a particular row
BOOL DeleteRow(ULONG ulDex);
//@cmember Has the Row been deleted
BOOL IsDeleted(ULONG ulDex);
//@cmember Returns the offset in the file for a particular row
ULONG GetRowOffset(ULONG ulDex);
};

#endif


FILEIO.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module FILEIO.CPP | This module contains the File Manipulation code
// for a Comma Seperated Value (CSV) Simple Provider.
//
//
#include "headers.h"
#include "fileio.h"

static const ARRAY_INIT_SIZE = 1000;

// Data Types supported
static const TYPE_CHAR = 1;
static const TYPE_SLONG = 3;

// Data Type Parse strings and lengths
static const char CHAR_STRING[] = "CHAR";
static const CHAR_STRING_SIZE = 4;
static const char SLONG_STRING[] = "SLONG";
static const SLONG_STRING_SIZE = 5;

//--------------------------------------------------------------------
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CFileIO::CFileIO()
{
    m_pColNames        = NULL;
    m_pvInput          = NULL;
    m_ulDataTypeOffset = 0;
    m_dwColumns        = 0;
    m_cRows            = 0;
}

//--------------------------------------------------------------------
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CFileIO:: ~CFileIO()
{
    // Close file
    if (is_open())
        close();

    // Delete buffers
    delete[] m_pColNames;
    delete[] m_pvInput;
}

//--------------------------------------------------------------------
// @mfunc Initialization routine, opens file specified and creates
// buffers
//
// @rdesc HRESULTs
//      @flag S_OK | Succeeded
//      @flag E_FAIL | Failed to Initialize
//
HRESULT CFileIO::fInit
    (
    LPSTR ptstrFileName         //@parm IN | File Name to Open
    )
{
    // Allocate Stream Buffer
    m_pvInput = new char[MAX_INPUT_BUFFER ];
    if (NULL == m_pvInput)
        return ResultFromScode( E_FAIL );

    // Open the File
//@DEVNOTE: csv file must not be read only, else E_FAIL will be returned.
    open( ptstrFileName, ios::in | ios::out | ios::nocreate, filebuf::sh_none );
    if (!is_open())
        return ResultFromScode( E_FAIL );

    // Obtain the Column Names, Data Types, and Indexes
    // for each of the rows
    if (FAILED( GenerateFileInfo()))
        return ResultFromScode( E_FAIL );

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Retrieve the Name associated with a particular column.  If
// names have not been read from the file yet, retrieve those names.
// If a name does not exist for a column, fabricate one.
//
// @rdesc HRESULT
//      @flag S_OK | Succeeded
//      @flag E_FAIL | Invalid Column Number
//
HRESULT CFileIO::GetColumnName
    (
    DWORD   dwCol,          //@parm IN | Column Number
    LPSTR*  pptstrName      //@parm OUT | Pointer to Column Name
    )
{
    // Column number greater than MAX
    if (dwCol > MAX_COLUMNS)
        {
        TRACE( "dwCol > MAX_COLUMNS" );
        return ResultFromScode( E_FAIL );
        }

    // If Column Names have not been retrieved,
    // then retrieve them into the internal array
    if (!m_pColNames)
        {
        // Save Current Position and move to beginning of file
        seekg( 0L );
        clear();

        // Retrieve the column names record
        getline( m_pvInput, MAX_INPUT_BUFFER );
        if (good() && 0 < gcount())
        {
m_pColNames = new char[gcount() ];
memcpy( m_pColNames, m_pvInput, gcount());
        }
else
{
//Invalid Table, first line does not contain
//column metadata.
return ResultFromScode(E_FAIL);
}

ParseColumnNames( m_pColNames );

        return ResultFromScode( S_FALSE );
        }

    ASSERT( pptstrName );

    // If the column number is in range then return
    // the pointer
    if ((0 == dwCol) || (m_dwColumns < dwCol))
        return ResultFromScode( E_FAIL );
    else
        {
        *pptstrName = m_rgpColNames[dwCol];
        return ResultFromScode( S_OK );
        }
}

//--------------------------------------------------------------------
// @mfunc Tokenize the column names
//
// @rdesc HRESULT
//      @flag S_OK | Parsing yielded no Error
//
HRESULT CFileIO::ParseColumnNames
    (
    LPTSTR ptstrInput
    )
{
    LPTSTR  pvInput = ptstrInput;

    ASSERT( pvInput );

    // Set first column pointer
    if ('\0' != *pvInput)
        {
        m_rgpColNames[++m_dwColumns] = pvInput;
        }

    // Null Terminate each column
    while ('\0' != *pvInput)
        {
        // Check for Comma
        if (0 == strncmp( ",", pvInput, sizeof( char )))
            {
            memcpy( pvInput, "", sizeof( char ));

            if (0 != strncmp( "", (LPTSTR) ((ULONG) pvInput + 1), sizeof( char )))
                {
                m_rgpColNames[++m_dwColumns] = (LPTSTR) ((ULONG) pvInput + 1);
                }
            }

        pvInput++;
        }

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Retrieves the columns data characteristics
//
// @rdesc HRESULT
//      @flag S_OK | Succeeded
//      @flag E_FAIL | Invalid Column Number
//
HRESULT CFileIO::GetDataTypes
    (
    DWORD dwCol,        //@parm IN | Column number
    SWORD* pswType,     //@parm OUT | Data Type
    UDWORD* pudwColDef, //@parm OUT | Precision of the column
    BOOL* pfSigned      //@parm OUT | Is the columns signed
    )
{
    HRESULT hr;

    // Column number greater than MAX
    if (dwCol > MAX_COLUMNS)
        {
        TRACE( "dwCol > MAX_COLUMNS" );
        return ResultFromScode( E_FAIL );
        }

    // If Data Types have not been retrieved,
    // then retrieve them into the internal array
    if (0 == m_ulDataTypeOffset)
        {
        seekg( 0L );
        clear();

        // To retrieve the Column Data Types, we need
        // to skip the first row and get to the 2nd row
        getline( m_pvInput, MAX_INPUT_BUFFER );
        m_ulDataTypeOffset = tellg();
        getline( m_pvInput, MAX_INPUT_BUFFER );

        // Check Stream status
        if (bad() || 0 == gcount())
            return ResultFromScode( E_FAIL );

        // Parse the datatypes from the stream.
        hr = ParseDataTypes();
        if (FAILED( hr ))
            return hr;

        return ResultFromScode( S_FALSE );
        }

    assert( pswType || pudwColDef || pfSigned );

    // If the column number is in range then return
    // the pointer
    if ((0 == dwCol) || (m_dwColumns < dwCol))
        return ResultFromScode( E_FAIL );
    else
        {
        *pswType = m_rgswColType[dwCol];
        *pudwColDef = m_rgudwColSize[dwCol];
        *pfSigned = m_rgfSigned[dwCol];
        return ResultFromScode( S_OK );
        }
}

//--------------------------------------------------------------------
// @mfunc Tokenize the DataTypes and Lengths
// Valid Data Types are CHAR(n), INTEGER, and LONG
//
// CHAR, SLONG
//
// @rdesc HRESULT
//      @flag S_OK | Parsing yielded no Error
//
HRESULT CFileIO::ParseDataTypes()
{
    ULONG   ulColumn = 0;
    LPSTR   pVal, pOpen;

    assert( m_pvInput );

    pVal = strtok( m_pvInput, ",\0" );
    if (NULL == pVal)
        return ResultFromScode( E_FAIL );

    while (NULL != pVal)
        {
        ulColumn++;

        if (0 == _strnicmp( pVal, CHAR_STRING, CHAR_STRING_SIZE ))
            {
            m_rgswColType[ulColumn] = TYPE_CHAR;
            pOpen = strstr( pVal, "(" );
            m_rgudwColSize[ulColumn] = atol( ++pOpen ) + 1;
            m_rgfSigned[ulColumn] = FALSE;
            }
        else if (0 == _strnicmp( pVal, SLONG_STRING, SLONG_STRING_SIZE ))
            {
            m_rgswColType[ulColumn] = TYPE_SLONG;
            m_rgudwColSize[ulColumn] = 4;
            m_rgfSigned[ulColumn] = TRUE;
            }
        else
            return ResultFromScode( E_FAIL );

        pVal = strtok( NULL, ",\0" );
        }

    // should have exactly the same number of types as we have columns
    if (ulColumn != m_dwColumns)
        return ResultFromScode( E_FAIL );

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Obtain the offsets into the file that each row exists at.
// Ignore any deleted rows while reading the file.
//
// @rdesc HRESULT
//      @flag S_OK | Got the offsets, Column Names and Data Types
//      @flag E_FAIL | Could not obtain all the necessary info
//
HRESULT CFileIO::GenerateFileInfo()
{
    ULONG ulDex = 0;
    ULONG ulSavePos;

    // Generate Column Info, if NULL is returned, a problem
    // was encountered while reading the Column Names.
    if (S_FALSE != GetColumnName( 0, NULL ))
        return ResultFromScode( E_FAIL );

    // Generate DataType Mapping, if FALSE is returned, a problem
    // was encountered while reading the DataTypes
    if (S_FALSE != GetDataTypes( 0, NULL, NULL, NULL ))
        return ResultFromScode( E_FAIL );

    // Create and Initialize the Index Array
    if (FALSE == m_FileIdx.fInit())
        return ResultFromScode( E_FAIL );

    // Obtain the starting offset for each row
    seekg( m_ulDataTypeOffset );
    ulSavePos = tellg();
    getline( m_pvInput, MAX_INPUT_BUFFER );
    while (good() && !eof())
        {
        //Ignore Deleted Lines
        if ('@' != *m_pvInput && '\0' != *m_pvInput)
            m_FileIdx.SetIndex( ulDex++, ulSavePos );
        ulSavePos = tellg();
        getline( m_pvInput, MAX_INPUT_BUFFER );
        }

    // Store the number of rows
    m_cRows = ulDex - 1;

#ifdef _DEBUG
    for (ULONG i=0; i < ulDex; i++)
        {
        // printf("%d - %u\r\n", i, m_FileIdx.GetRowOffset(i));
        }
#endif

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Check if the row has already been deleted
//
// @rdesc HRESULT
//      @flag S_OK | Row already deleted
//      @flag S_FALSE | Row not deleted
//
HRESULT CFileIO::IsDeleted
    (
    ULONG ulRow                 //@parm IN | Row to Check
    )
{
    // Already deleted
    if (TRUE == m_FileIdx.IsDeleted( ulRow ))
        return ResultFromScode( S_OK );
    else
        return ResultFromScode( S_FALSE );
}

//--------------------------------------------------------------------
// @mfunc Fill the row with '@' characters, the deletion pattern..
// And set the Deletion status flag in the index class.
//
// @rdesc HRESULT
//      @flag S_OK | Deleted Row
//      @flag E_FAIL | Row Number was invalid or problem deleting.
//
HRESULT CFileIO::DeleteRow
    (
    ULONG ulRow                 //@parm IN | Row to Delete
    )
{
    assert( is_open());
    assert( m_pvInput );

    // Check the Row Number
    if ((ulRow < 1) || (ulRow > m_cRows))
        return ResultFromScode( E_FAIL );

    // If already deleted, just ignore.
    if (TRUE == m_FileIdx.IsDeleted( ulRow ))
        return ResultFromScode( S_OK );

    // Set the File Pointer
    seekg( m_FileIdx.GetRowOffset( ulRow ));
    clear();

    // Delete the row in the file and mark the status
    // as deleted in the index Array
    getline( m_pvInput, MAX_INPUT_BUFFER );
    if (good())
        {
        // Set the number bytes in the stream minus
        // the null terminator to this pattern
        memset( m_pvInput, '@', gcount() - 1 );
        seekp( m_FileIdx.GetRowOffset( ulRow ));
        clear();
        write( m_pvInput, gcount() - 1 );
        if (bad())
            return ResultFromScode( E_FAIL );
        else
            flush();
        }
    else
        return ResultFromScode( E_FAIL );

    m_FileIdx.DeleteRow( ulRow );

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Establish the Binding Information for the given file
//
// @rdesc HRESULT
//      @flag S_OK | Binding set
//      @flag E_FAIL | Problem setting the binding
//
HRESULT CFileIO::SetColumnBind
    (
    DWORD dwCol,            //@parm IN | Column Number
    PCOLUMNDATA pColumn,    //@parm IN | Pointer to the Data Area
    SDWORD sdwMaxLen        //@parm IN | Maximum size for the value
    )
{
    assert( is_open());
    assert( m_rgpColumnData );
    assert( m_rgsdwMaxLen );

    // If the column number is in range then return
    // the pointer
    if ((0 == dwCol) || (m_dwColumns < dwCol))
        return ResultFromScode( E_FAIL );

    // Expect valid pointer
    assert( pColumn );

    m_rgpColumnData[dwCol] = pColumn;
    m_rgsdwMaxLen[dwCol] = sdwMaxLen;

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Fetch the row data from the stream to the internal data
// buffers
//
// @rdesc HRESULT
//      @flag S_OK    | Row Retrieve successfully
//      @flag S_FALSE | End of Result Set
//      @flag E_FAIL  | Row could not be retrieved
//
HRESULT CFileIO::Fetch
    (
    ULONG   ulRow           //@parm IN | Row to retrieve
    )
{
    assert( is_open());
    assert( m_rgpColumnData );
    assert( m_rgsdwMaxLen );

    // Check the Row Number
    if ((ulRow < 1))
        return ResultFromScode( E_FAIL );

    //Check end of Result Set
    if (ulRow > m_cRows)
        return ResultFromScode( S_FALSE );

    // Set the File Pointer to the row.
    seekg( m_FileIdx.GetRowOffset( ulRow ));
    clear();

    // Retrieve the column names record
    getline( m_pvInput, MAX_INPUT_BUFFER );
    if (good() && 0 < gcount())
        {
        return ParseRowValues();
        }

    return ResultFromScode( E_FAIL );
}

//--------------------------------------------------------------------
// @mfunc Tokenize the Data values and put them into the correct
// binding areas
//
// @rdesc HRESULT
//      @flag S_OK | Parsing yielded no Error
//      @flag E_FAIL | Data value could not be parsed or stored
//
HRESULT CFileIO::ParseRowValues
    (
    void
    )
{
    DWORD   dwColumns = 0;
    DWORD   cQuotes = 0;
    LPTSTR  pvCopy,
        pvInput,
        pLastQuote;

    pLastQuote = NULL;
    pvCopy = NULL;
    pvInput = m_pvInput;

    assert( pvInput );
    assert( m_dwColumns > 0 );

    while ('\0' != *pvInput)
        {

        // Check for Quotes
        if (0 == strncmp( "\"", pvInput, sizeof( char )))
            {
            pLastQuote = pvInput;
            cQuotes++;
            goto TermCheck;
            }

        // Check for Comma
        // NOTE: THIS won't handle """
        if (0 == strncmp( ",", pvInput, sizeof( char )) &&
             0 == cQuotes % 2)
            {
            if (pLastQuote)
                memcpy( pLastQuote, "", sizeof( char ));
            else
                memcpy( pvInput, "", sizeof( char ));

            // Increment Columns processed
            dwColumns++;

            //          TRACE(pvCopy ? pvCopy : "<NULL>");
            if (FAILED( FillBinding( dwColumns, pvCopy )))
                return ResultFromScode( E_FAIL );

            pLastQuote = NULL;
            pvCopy = NULL;
            cQuotes = 0;
            goto TermCheck;
            }

        //Valid First character for next column
        if (NULL == pvCopy)
            pvCopy = pvInput;

        TermCheck:
        // Check for Final Null Terminator
        if (0 == strncmp( "", (LPTSTR) ((ULONG) pvInput + 1), sizeof( char )))
            {
            //If we are to the null terminator and have unbalanced "'s
            //then we fail
            if (0 != cQuotes % 2)
                return ResultFromScode( E_FAIL );

            if (pLastQuote)
                memcpy( pLastQuote, "", sizeof( char ));

            // Increment Columns processed
            dwColumns++;

            //          TRACE(pvCopy ? pvCopy : "<NULL>");
            if (FAILED( FillBinding( dwColumns, pvCopy )))
                return ResultFromScode( E_FAIL );
            }

        pvInput++;
        }

    // Check that we returned the correct number of columns
    if (dwColumns < m_dwColumns)
        return ResultFromScode( E_FAIL );

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Based on the given bindings and column data, put the data
// in the correct area, update the status and length fields
//
// @rdesc HRESULT
//      @flag S_OK | Data copied to the specified location
//
HRESULT CFileIO::FillBinding
    (
    DWORD dwColumn,     //@parm IN | Column that value is for
    LPTSTR pvCopy       //@parm IN | Pointer to data value to transfer
    )
{
    assert( m_rgswColType );
    assert( m_rgpColumnData );
    assert( m_rgsdwMaxLen );

    // Null Value
    if (!pvCopy)
        {
        m_rgpColumnData[dwColumn]->dwStatus = DBSTATUS_S_ISNULL;
        return ResultFromScode( S_OK );
        }

    switch (m_rgswColType[dwColumn])
        {
    case TYPE_CHAR:
        lstrcpyn((LPTSTR) m_rgpColumnData[dwColumn]->bData, pvCopy, m_rgsdwMaxLen[dwColumn] );
        m_rgpColumnData[dwColumn]->dwLength = lstrlen( pvCopy ) + sizeof( char );
        m_rgpColumnData[dwColumn]->dwStatus = DBSTATUS_S_OK;
        break;

    case TYPE_SLONG:
        *(ULONG*) m_rgpColumnData[dwColumn]->bData = atol( pvCopy );
        m_rgpColumnData[dwColumn]->dwLength = 4;
        m_rgpColumnData[dwColumn]->dwStatus = DBSTATUS_S_OK;
        break;

    default:
        assert( !"Unknown Data Type" );
        break;
        }

    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// @mfunc Given a pointer to the new data and the row the the data is
// for, write the data to the file.
//
// @rdesc HRESULT
//      @flag S_OK | Record and Indexes updated
//      @flag E_FAIL | Problems updating record
//
HRESULT CFileIO::UpdateRow
    (
    ULONG       ulRow,      //@parm IN | Row to update
    ULONG*      ulOffset,   //@parm IN | Array of offsets for the columns
    BYTE*       pbProvRow   //@parm IN | Data to update row with.
    )
{
    LPSTR       pvInput = m_pvInput;
    PCOLUMNDATA pColData;
    DWORD       i;
    int         nCnt;
    LONG        lPos;

    // Check the Row Number
    if ((ulRow < 0) || (ulRow > m_cRows))
        return ResultFromScode( E_FAIL );

    // Updated Rows are added to the end of the file, the row number will
    // remain the same until the rowset is closed, because the old
    // offset is deleted and the new is put in it's place.
    for (i = 1; i <= m_dwColumns; i++)
        {
        nCnt = 0;

        pColData = (PCOLUMNDATA) (pbProvRow + ulOffset[i]);

        // Handle NULL Data
        if (pColData->dwStatus != DBSTATUS_S_ISNULL)
            {
            switch (m_rgswColType[i])
                {
            case TYPE_CHAR:
                nCnt = wsprintf( pvInput, "\"%s\"",
                    (LPTSTR) pColData->bData );
                break;

            case TYPE_SLONG:
                nCnt = wsprintf( pvInput, "%d",
                    (signed long) *pColData->bData );
                break;

            default:
                assert( !"Unknown Data Type" );
                break;

                }

            }

        //Calculate the next append area
        pvInput = (LPTSTR) ((ULONG) pvInput + (nCnt * sizeof( char )));

        if (i == m_dwColumns)
            lstrcat( pvInput, "\n" );
        else
            {
            lstrcat( pvInput, "," );
            pvInput = (LPTSTR) ((ULONG) pvInput + sizeof( char ));
            }
        }

    // Write Stream to File
    seekg( 0, ios::end );
    lPos = tellg();
    clear();
    write( m_pvInput, lstrlen( m_pvInput ));
    if (bad())
        return ResultFromScode( E_FAIL );
    else
        flush();

    // Delete old Row and fix up Row offset value
    if (SUCCEEDED( DeleteRow( ulRow )))
        m_FileIdx.SetIndex( ulRow, lPos );
    else
        return ResultFromScode( E_FAIL );

    return ResultFromScode( S_OK );
}


FILEIO.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module FILEIO.H | Class Definitions for CFileIO Class
//
//
#ifndef _FILEIO_H_
#define _FILEIO_H_

#include <fstream.h>
#include "fileidx.h"
#include "hashtbl.h"

#define MAX_INPUT_BUFFER 2048
#define MAX_COLUMNS129// 128 columns supported, but 1 extra
// is needed since 1 based.

//--------------------------------------------------------------------
// @class CFileIO | Opens and manipulates a given CSV file.  Allows
// deletions, reads, and updates.
//
// @hungarian
//
class CFileIO : public fstream//@base public | fstream
{
private: //@access private
//@cmember Count of rows in the table
ULONGm_cRows;
//@cmember Count of columns in the table
DWORDm_dwColumns;
//@cmember Pointer to Column Names buffer
LPTSTRm_pColNames;
//@cmember Array of pointers into the Column Names buffer
LPTSTRm_rgpColNames[MAX_COLUMNS];
//@cmember Array of Column Data Types
SWORDm_rgswColType[MAX_COLUMNS];
//@cmember Array of Precision values for columns
UDWORDm_rgudwColSize[MAX_COLUMNS];
//@cmember True if columns contains signed values
BOOLm_rgfSigned[MAX_COLUMNS];
//@cmember Array of pointers to Data
PCOLUMNDATA m_rgpColumnData[MAX_COLUMNS];
//@cmember Array of Lengths for the data
SDWORDm_rgsdwMaxLen[MAX_COLUMNS];
//@cmember Input buffer of size MAX_INPUT_BUFFER
LPTSTRm_pvInput;
//@cmember Offset into file for DataTypes
ULONGm_ulDataTypeOffset;
//@cmember Index Class declaration
CFileIdxm_FileIdx;

private: //@access private
//@cmember Break a stream into column names
HRESULT ParseColumnNames(LPTSTR ptstrInput);
//@cmember Break a stream into MetaData information
HRESULT ParseDataTypes();
//@cmember Control procedure to Read and parse intitial
//information from the file
HRESULT GenerateFileInfo();
//@cmember Fill the COLUMNDATA structure
HRESULT CFileIO::FillBinding(DWORD dwColumn, LPTSTR pvCopy);

public: //@access public
//@cmember Constructor
CFileIO(void);
//@cmember Destructor
~CFileIO(void);
//@cmember Initialization routine
HRESULT fInit(LPTSTR pstrFileName);
//@cmember Return the number of columns in the file
inline DWORD GetColumnCnt() { return m_dwColumns; };
//@cmember Return the number of rows in the file
inline DWORD GetRowCnt() { return m_cRows; };
//@cmember Retrieve pointers to the columns names
HRESULT GetColumnName(DWORD dwCol, LPTSTR* pptstrName = NULL);
//@cmember Retrieve the metadata for a particular column
HRESULT GetDataTypes(DWORD dwCol, SWORD* pswType, UDWORD* pudwColDef, BOOL* pfSigned);
//@cmember Set the Binding Areas.
HRESULT SetColumnBind(DWORD dwcol, PCOLUMNDATA pColumn, SDWORD sdwMaxLen);
//@cmember Extract the data from a stream
HRESULT ParseRowValues();
//@cmember Fetch A single rows data values
HRESULT Fetch(ULONG ulRow);
//@cmember Update the current rows values
HRESULT UpdateRow(ULONG ulRow, ULONG* ulOffset, BYTE* pbProvRow);
//@cmember Remove the specified row from the file
HRESULT DeleteRow(ULONG ulRow);
//@cmember Determines if the row has already been deleted
HRESULT IsDeleted(ULONG ulRow);
};

typedef CFileIO FAR * PCFILEIO;

#endif


GLOBALS.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module GLOBALS.CPP | Global Initialization Object..
//
//

// Includes ------------------------------------------------------------------

//  Don't include everything from windows.h, but always bring in OLE 2 support
#define WIN32_LEAN_AND_MEAN
#define INC_OLE2

//  Make sure constants get initialized
#define INITGUID
#define DBINITCONSTANTS

// Basic Windows and OLE everything
#include <windows.h>

//  OLE DB headers
#include "oledb.h"
#include "oledberr.h"

//Data conversion library header
#include "msdadc.h"

//  Sample Provider-specific general headers
#include "sampprov.h"

//  SampProv's GUIDs
#include "guids.h"


GUIDS.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module GUIDS.H | Internal GUIDS
//
//
#ifndef _GUIDS_H_
#define _GUIDS_H_

//SampProv's CLSID
// @msg CLSID_SampProv | {E8CCCB79-7C36-101B-AC3A-00AA0044773D}
// Provider Class Id
DEFINE_GUID(CLSID_SampProv, 0xE8CCCB79L,0x7C36,0x101B,0xAC,0x3A,0x00,0xAA,0x00,0x44,0x77,0x3D);

//
// @msg DBINIT_OPT_SAMPPROV_PATH | {E9FBAF50-D402-11ce-BEDC-00AA00A14D7D}
// option GUID for IDBInitialize::Initialize. Sampprov-specific GUID indicating path to CSV database file.
DEFINE_GUID(DBINIT_OPT_SAMPPROV_PATH, 0xe9fbaf50, 0xd402, 0x11ce, 0xbe, 0xdc, 0x0, 0xaa, 0x0, 0xa1, 0x4d, 0x7d);

#endif


HASHTBL.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @module hashtbl.cpp | Hashing routines for row manipulation.
//
//
#include "headers.h"
#include "hashtbl.h"

//--------------------------------------------------------------------
// GetNextSlots
//
// @func Allocates a contiguous block of the required number of slots.
//
// @rdesc Returns one of the following values:
//      @flag S_OK          | slot allocate succeeded
// @flag E_OUTOFMEMORY | slot allocation failed because of memory allocation
//  problem
///
HRESULT GetNextSlots
    (
    PLSTSLOT plstslot,  //@parm IN | slot list
    ULONG cslot,        //@parm IN | needed block size (in slots)
    ULONG* pislot       //@parm IN | handle of the first slot in the returned block
    )
{
    ULONG   islot, dslot;
    PSLOT   pslot, pslotTmp;
    ULONG   cbCommit;
    HRESULT hr;

    if (plstslot->islotRov)
        plstslot->islotRov = ((PSLOT) & plstslot->rgslot[plstslot->islotRov *plstslot->cbSlot])->islotNext;
    else
        plstslot->islotRov = plstslot->islotFirst;

    islot = plstslot->islotRov;
    while (islot)
        {
        if (((PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot])->cslot >= cslot)
            break;
        islot = ((PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot])->islotNext;
        }
    if (islot == 0)
        {
        islot = plstslot->islotFirst;
        while (islot != plstslot->islotRov)
            {
            if (((PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot])->cslot >= cslot)
                break;
            islot = ((PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot])->islotNext;
            }
        if (islot == plstslot->islotRov)
            islot = 0;
        }

    if (islot == 0)
        {
        cbCommit = ((cslot *plstslot->cbSlot) / plstslot->cbPage + 1) *plstslot->cbPage;
        if ((plstslot->cbCommitCurrent + cbCommit) > plstslot->cbCommitMax
            || VirtualAlloc((VOID *) ((BYTE *) plstslot + plstslot->cbCommitCurrent),
                                    cbCommit,
                                    MEM_COMMIT,
                                    PAGE_READWRITE ) == NULL)
            return ResultFromScode( E_OUTOFMEMORY );

        islot = (ULONG) ((plstslot->cbCommitCurrent + plstslot->cbExtra) / plstslot->cbSlot);
        dslot = ((cbCommit + plstslot->cbslotLeftOver) / plstslot->cbSlot);
        if ((plstslot->pbitsSlot)->IsSlotSet( islot - 1 ) != NOERROR)
            {
            if ((plstslot->pbitsSlot)->FindSet( islot - 1, plstslot->islotMin, &islot ) == NOERROR)
                islot++;
            else
                islot = plstslot->islotMin;
            pslot = (PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot];
            pslot->cslot += dslot;
            DecoupleSlot( plstslot, islot, pslot );
            }
        else
            {
            pslot = (PSLOT) ((BYTE *) plstslot + plstslot->cbCommitCurrent - plstslot->cbslotLeftOver);
            pslot->cslot = dslot;
            }

        pslot->islotNext = plstslot->islotFirst;
        pslot->islotPrev = 0;

        plstslot->islotMax += dslot;
        plstslot->islotFirst       = islot;
        plstslot->cbslotLeftOver   = (cbCommit + plstslot->cbslotLeftOver) % plstslot->cbSlot;
        plstslot->cbCommitCurrent += cbCommit;

        if (pslot->islotNext)
            ((PSLOT) & plstslot->rgslot[pslot->islotNext *plstslot->cbSlot])->islotPrev = islot;
        islot = plstslot->islotFirst;
        }

    pslot = (PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot];
    DecoupleSlot( plstslot, islot, pslot );
    if (pslot->cslot > cslot)
        {
        pslotTmp = (PSLOT) & plstslot->rgslot[ (islot + cslot) *plstslot->cbSlot];
        pslotTmp->cslot = pslot->cslot - cslot;
        AddSlotToList( plstslot, islot + cslot, pslotTmp );
        }

    if (FAILED( hr = (plstslot->pbitsSlot)->SetSlots( islot, islot + cslot - 1 )))
        return hr;

    if (pislot)
        *pislot = islot;
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// DecoupleSlot
//
// @func Decouples a slot from the list of free slots
//
// @rdesc NONE
//
VOID DecoupleSlot
    (
    PLSTSLOT plstslot,  //@parm IN | slot list
    ULONG islot,        //@parm IN | slot handle to decouple
    PSLOT pslot         //@parm IN | pointer to the slot header
    )
{
    if (pslot->islotNext)
        ((PSLOT) & plstslot->rgslot[pslot->islotNext *plstslot->cbSlot])->islotPrev = pslot->islotPrev;
    if (pslot->islotPrev)
        ((PSLOT) & plstslot->rgslot[pslot->islotPrev *plstslot->cbSlot])->islotNext = pslot->islotNext;
    else
        plstslot->islotFirst = pslot->islotNext;
    if (islot == plstslot->islotRov)
        plstslot->islotRov = pslot->islotNext;
}

//--------------------------------------------------------------------
// AddSlotToList
//
// @func Adds a slot to the list of free slots
//
// @rdesc NONE
//
VOID AddSlotToList
    (
    PLSTSLOT plstslot,  //@parm IN | slot list
    ULONG islot,        //@parm IN | slot handle
    PSLOT pslot         //@parm IN | pointer to the slot header
    )
{
    pslot->islotPrev = 0;
    pslot->islotNext = plstslot->islotFirst;
    plstslot->islotFirst = islot;
    if (pslot->islotNext)
        ((PSLOT) & plstslot->rgslot[pslot->islotNext *plstslot->cbSlot])->islotPrev = islot;
}

//--------------------------------------------------------------------
// ReleaseSlots
//
// @func Releases a contiguous block of slots.
//
// @rdesc Returns one of the following values:
//      @flag   S_OK | method succeeded
//
HRESULT ReleaseSlots
    (
    PLSTSLOT plstslot,  //@parm IN | slot list
    ULONG    islot,     //@parm IN | handle of first slot to release
    ULONG    cslot      //@parm IN | count of slots to release
    )
{
    PSLOT pslot, pslotTmp;

    (plstslot->pbitsSlot)->ResetSlots( islot, islot + cslot - 1 );
    pslot = (PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot];
    pslot->cslot = cslot;

    if (islot > plstslot->islotMin && (plstslot->pbitsSlot)->IsSlotSet( islot - 1 ) != NOERROR)
        {
        if ((plstslot->pbitsSlot)->FindSet( islot - 1, plstslot->islotMin, &islot ) == NOERROR)
            islot++;
        else
            islot = plstslot->islotMin;
        pslot = (PSLOT) & plstslot->rgslot[islot *plstslot->cbSlot];
        pslot->cslot += cslot;
        DecoupleSlot( plstslot, islot, pslot );
        }

    if ((islot + cslot) <= plstslot->islotMax && (plstslot->pbitsSlot)->IsSlotSet( islot + cslot ) != NOERROR)
        {
        pslotTmp = (PSLOT) & plstslot->rgslot[ (islot + cslot) *plstslot->cbSlot];
        pslot->cslot += pslotTmp->cslot;
        DecoupleSlot( plstslot, (islot + cslot), pslotTmp );
        }

    AddSlotToList( plstslot, islot, pslot );
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// InitializeSlotList
//
// @func Initializes the Slot List object
//
// @rdesc Did the initialization succeed
//      @flag S_OK          | method succeeded
//      @flag E_OUTOFMEMORY | failed, out of memory
//
//
HRESULT InitializeSlotList
    (
    ULONG cslotMax,         //@parm IN | max number of slots
    ULONG cbSlot,           //@parm IN | slot size (row buffer size)
    ULONG cbPage,           //@parm IN | page size
    LPBITARRAY pbits,       //@parm IN |
    PLSTSLOT* pplstslot,    //@parm OUT | pointer to slot list
    BYTE** prgslot          //@parm OUT |
    )
{
    ULONG    cbReserve;
    BYTE     *pbAlloc;
    ULONG    cbCommitFirst;
    PLSTSLOT plstslot;
    ULONG    cslot, islotFirst;
    PSLOT    pslot;

    if (cbPage == 0)
        {
        SYSTEM_INFO sysinfo;

        GetSystemInfo( &sysinfo );
        cbPage = sysinfo.dwPageSize;
        }

    cbReserve = ((cslotMax *cbSlot + sizeof( LSTSLOT )) / cbPage + 1) *cbPage;

    pbAlloc = (BYTE *) VirtualAlloc( NULL, cbReserve, MEM_RESERVE, PAGE_READWRITE );
    if (pbAlloc == NULL)
        return ResultFromScode( E_OUTOFMEMORY );

    cbCommitFirst = (sizeof( LSTSLOT ) / cbPage + 1) * cbPage;
    plstslot = (PLSTSLOT) VirtualAlloc( pbAlloc, cbCommitFirst, MEM_COMMIT, PAGE_READWRITE );
    if (plstslot == NULL)
        {
        VirtualFree((VOID *) pbAlloc, 0, MEM_RELEASE );
        return ResultFromScode( E_OUTOFMEMORY );
        }

    plstslot->cbSlot          = cbSlot;
    plstslot->cbPage          = cbPage;
    plstslot->cbCommitCurrent = cbCommitFirst;
    plstslot->cbCommitMax     = cbReserve;
    plstslot->pbitsSlot       = pbits;

    if (cbSlot <= 2*sizeof( LSTSLOT ))
        {
        islotFirst        = sizeof( LSTSLOT ) / cbSlot + ((sizeof( LSTSLOT ) % cbSlot) ? 1 : 0);
        plstslot->cbExtra = 0;
        cslot = (ULONG) ((cbCommitFirst / cbSlot) - islotFirst);
        plstslot->cbslotLeftOver = cbCommitFirst - cbSlot * (cslot + islotFirst);
        }
    else
        {
        islotFirst        = 1;
        plstslot->cbExtra = cbSlot - sizeof( LSTSLOT );
        cslot = (cbCommitFirst - sizeof( LSTSLOT )) / cbSlot;
        plstslot->cbslotLeftOver = cbCommitFirst - sizeof( LSTSLOT ) - cslot*cbSlot;
        }
    plstslot->rgslot = ((BYTE *) plstslot - plstslot->cbExtra);
    if (cslot)
        {
        plstslot->islotFirst = islotFirst;
        pslot = (PSLOT) & plstslot->rgslot[islotFirst *plstslot->cbSlot];
        pslot->cslot     = cslot;
        pslot->islotNext = 0;
        pslot->islotPrev = 0;
        }
    else
        plstslot->islotFirst = 0;
    plstslot->islotMin   = islotFirst;
    plstslot->islotMax   = islotFirst + cslot - 1;
    plstslot->islotRov = plstslot->islotFirst;

    *pplstslot = plstslot;
    *prgslot   = plstslot->rgslot;
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
//  ResetSlotList
//
// @func Restore slot list to newly-initiated state
//
// @rdesc
//  @flag S_OK  | method succeeded
//
HRESULT ResetSlotList
    (
    PLSTSLOT plstslot           //@parm IN | slot list
    )
{
    ULONG   cslot;
    PSLOT   pslot;

    cslot = (plstslot->islotMax >= plstslot->islotMin) ? (plstslot->islotMax - plstslot->islotMin + 1) : 0;
    if (cslot)
        {
        plstslot->islotFirst = plstslot->islotMin;
        pslot = (PSLOT) & plstslot->rgslot[plstslot->islotFirst *plstslot->cbSlot];
        pslot->cslot     = cslot;
        pslot->islotNext = 0;
        pslot->islotPrev = 0;
        }
    else
        plstslot->islotFirst = 0;

    plstslot->islotRov = plstslot->islotFirst;
    return ResultFromScode( S_OK );
}

//--------------------------------------------------------------------
// ReleaseSlotList
//
// @func Free slot list's memory
//
// @rdesc
//  @flag S_OK  | method succeeded
//
HRESULT ReleaseSlotList
    (
    PLSTSLOT plstslot           //@parm IN | slot list
    )
{
    if (plstslot == NULL)
        return NOERROR;

    if (plstslot->cbCommitCurrent)
        VirtualFree((VOID *) plstslot, plstslot->cbCommitCurrent, MEM_DECOMMIT );

    VirtualFree((VOID *) plstslot, 0, MEM_RELEASE );
    return ResultFromScode( S_OK );
}


HASHTBL.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @module hashtbl.h | Class Definitions for CHashTbl Class and
// miscellaneous bookmark functions
//
//
#ifndef _HASHTBL_H_
#define _HASHTBL_H_
#include "bitarray.h"

#define SIZE_64K   65535// Actually 64*1024-1

// This defines the data as stored within the row buffer.
// Each row has columns laid out sequentially.
// Use 'offsetof' when doing pointer addition.
// Note that it is important to align these.
// Suggest ensuring quadword alignment for double and __int64.

typedef struct _COLUMNDATA {
SDWORDdwLength;// length of data (not space allocated)
DWORDdwStatus;// status of column
BYTEbData[1];// data here and beyond
} COLUMNDATA, *PCOLUMNDATA;

// This is the layout of a row.
// Note the unique arrangement of the hash chain pointers
// inside the row itself.
// Note also that the structure for columns is defined, and
// each row contains an array of columns.
// Bookmarks are named separately from ColumnData, for clarity
// of usage.  The layout directly matches COLUMNDATA, however.
// There are asserts which enforce this.
typedef struct tagRowBuff
{
ULONG       ulRefCount;// reference count of outstanding handles
ULONG       irowbuffNext;// next row in bookmark hash chain
USHORT      wBmkHash;// hash value (redundant)
VOID       *pbBmk;// ptr  to bookmark
ULONG       cbBmk;// (dwLength) bookmark size, in bytes
ULONG       dwBmkStatus;// (dwStatus) bookmark status
ULONG       dwBmk;// (bData)    bookmark value, , maybe row count
COLUMNDATA  cdData[1];// Column data here and beyond (Bookmark should be here)
} ROWBUFF, *PROWBUFF;

typedef struct tagSLOT
{
ULONG islotNext;
ULONG islotPrev;
ULONG cslot;
} SLOT, *PSLOT;

typedef struct tagLSTSLOT
{
ULONG       islotFirst;
ULONG       islotRov;
ULONG       islotMin;
ULONG       islotMax;
BYTE        *rgslot;
LPBITARRAYpbitsSlot;// bit array to mark active rows
ULONG       cbExtra;
ULONG       cbslotLeftOver;
ULONG       cbSlot;
ULONG       cbPage;
ULONG       cbCommitCurrent;
ULONG       cbCommitMax;
} LSTSLOT, *PLSTSLOT;

HRESULT GetNextSlots(PLSTSLOT plstslot,ULONG cslot, ULONG* pislot);
VOID DecoupleSlot(PLSTSLOT plstslot, ULONG islot, PSLOT pslot);
VOID AddSlotToList(PLSTSLOT plstslot, ULONG islot, PSLOT pslot);
HRESULT ReleaseSlots(PLSTSLOT plstslot,ULONG islot, ULONG cslot);
HRESULT InitializeSlotList(ULONG cslotMax, ULONG cbSlot, ULONG cbPage, LPBITARRAY pbits, PLSTSLOT* pplstslot, BYTE** prgslot);
HRESULT ResetSlotList(PLSTSLOT plstslot);
HRESULT ReleaseSlotList(PLSTSLOT plstslot);

#endif


HEADERS.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module HEADERS.CPP | precompiled headers module
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"
#pragma hdrstop


HEADERS.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module HEADERS.H | Precompiled headers
//
#ifndef _HEADERS_H_
#define _HEADERS_H_

//Don't include everything from windows.h, but always bring in OLE 2 support
#define WIN32_LEAN_AND_MEAN
#define INC_OLE2

// Basic Windows and OLE everything
#include <windows.h>
#include <limits.h>
#include <stdio.h>// vsnprintf, etc.
#include <stddef.h>// offsetof, etc.
#include <wchar.h>// swprintf
#include <commdlg.h>// GetOpenFileName

//OLE DB headers
#include "oledb.h"
#include "oledberr.h"

//Data conversion library header
#include "msdadc.h"

//Sample Provider -specific general headers
#include "sampprov.h"
#include "asserts.h"
#include "utilprop.h"

#include "rc.h"

// GUIDs
#include "guids.h"

//CDataSource object and contained interface objects
#include "datasrc.h"

// CDBSession object and contained interface objects
#include "dbsess.h"

//CRowset object and contained interface objects
#include "rowset.h"

#endif


IROWSET.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module IROWSET.CPP | IRowset interface implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

//  IRowset specific interface methods

// CImpIRowset::GetData --------------------------------------------------
//
// @mfunc Retrieves data from the rowset's cache
//
// @rdesc HRESULT
//      @flag S_OK                   | Method Succeeded
//      @flag DB_S_ERRORSOCCURED     | Could not coerce a column value
//      @flag DB_E_BADACCESSORHANDLE | Invalid Accessor given
//      @flag DB_E_BADROWHANDLE      | Invalid row handle given
//      @flag E_INVALIDARG           | pData was NULL
//      @flag OTHER                  | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowset::GetData
    (
    HROW        hRow,       //@parm IN | Row Handle
    HACCESSOR   hAccessor,  //@parm IN | Accessor to use
    void       *pData       //@parm OUT | Pointer to buffer where data should go.
    )
{
    PACCESSOR   pAccessor;
    ULONG       icol, ibind;
    ROWBUFF     *pRowBuff;
    COLUMNDATA  *pColumnData;
    DBBINDING   *pBinding;
    ULONG       cBindings;
    ULONG       ulErrorCount;
    DBTYPE      dwSrcType;
    DBTYPE      dwDstType;
    void        *pSrc;
    void        *pDst;
    ULONG       ulSrcLength;
    ULONG       *pulDstLength;
    ULONG       ulDstMaxLength;
    DWORD       dwSrcStatus;
    DWORD       *pdwDstStatus;
    DWORD       dwPart;
    HRESULT     hr;

    // Coerce data for row 'hRow', according to hAccessor.
    // Put in location 'pData'.  Offsets and types are in hAccessor's bindings.
    //
    // Return S_OK if all lossless conversions,
    // return DB_S_ERRORSOCCURED if lossy conversion (truncation, rounding, etc.)
    // Return E_FAIL, etc., if horrible errors.

    // GetItemOfExtBuffer is basically operator[].
    // It takes an index (or handle) (referenced from 1...n),
    // and a ptr for where to write the data.
    //
    // It holds ptrs to a variable-length ACCESSOR struct.
    // So we get the ACCESSOR ptr for the client's accessor handle.

    assert( m_pObj->m_pextbufferAccessor );
    hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
    if (FAILED( hr ) || pAccessor == NULL)
        return ResultFromScode( DB_E_BADACCESSORHANDLE );

    assert( pAccessor );
    cBindings = pAccessor->cBindings;
    pBinding  = pAccessor->rgBindings;

    // IsSlotSet returns S_OK    if row is marked.
    //                   S_FALSE if row is not marked.
    // The "mark" means that there is data present in the row.
    // Rows are [1...n], slot marks are [0...n-1].
    if (m_pObj->m_prowbitsIBuffer->IsSlotSet((ULONG) hRow ) != S_OK)
        return ResultFromScode( DB_E_BADROWHANDLE );

    // Ensure a place to put data.
    if (pData == NULL)
        return ResultFromScode( E_INVALIDARG );

    pRowBuff = m_pObj->GetRowBuff((ULONG) hRow );

    // Internal error for a 0 reference count on this row,
    // since we depend on the slot-set stuff.
    assert( pRowBuff->ulRefCount );

    ulErrorCount = 0;
    for (ibind = 0; ibind < cBindings; ibind++)
        {
        icol = pBinding[ibind].iOrdinal;
        pColumnData = (COLUMNDATA *) ((BYTE *) pRowBuff + m_pObj->m_rgdwDataOffsets[icol]);

        dwSrcType      = m_pObj->m_rgdbcolinfo[icol].wType;
        pSrc           = &(pColumnData->bData);
        ulSrcLength    = pColumnData->dwLength;
        dwSrcStatus    = pColumnData->dwStatus;
        ulDstMaxLength = pBinding[ibind].cbMaxLen;
        dwDstType      = pBinding[ibind].wType;
        dwPart         = pBinding[ibind].dwPart;

        pDst           = dwPart & DBPART_VALUE ? ((BYTE*) pData + pBinding[ibind].obValue) : NULL;
        pulDstLength   = dwPart & DBPART_LENGTH ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) : NULL;
        pdwDstStatus   = dwPart & DBPART_STATUS ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus) : NULL;

        hr = g_pIDataConvert->DataConvert(
                dwSrcType,
                dwDstType,
                ulSrcLength,
                pulDstLength,
                pSrc,
                pDst,
                ulDstMaxLength,
                dwSrcStatus,
                pdwDstStatus,
                0,// bPrecision for conversion to DBNUMERIC
0,// bScale for conversion to DBNUMERIC
DBDATACONVERT_DEFAULT);
        if (FAILED( hr ))
            return hr;  // fatal error
        if (hr != ResultFromScode( S_OK ))
            ulErrorCount++; // can't coerce
        }

    // We report any lossy conversions with a special status.
    // Note that DB_S_ERRORSOCCURED is a success, rather than failure.
    return ResultFromScode( ulErrorCount ? DB_S_ERRORSOCCURRED : S_OK );
}

// CImpIRowset::GetNextRows --------------------------------------------------
//
// @mfunc Fetches rows in a sequential style, remembering the previous position
//
// @rdesc HRESULT
//      @flag S_OK                      | Method Succeeded
//      @flag DB_S_ENDOFROWSET          | Reached end of rowset
//      @flag DB_E_CANTFETCHBACKWARDS   | cRows was negative and we can't fetch backwards
//      @flag DB_E_ROWSNOTRELEASED      | Must release all HROWs before calling GetNextRows
//      @flag E_FAIL                    | Provider-specific error
//      @flag E_INVALIDARG              | pcRowsObtained or prghRows was NULL
//      @flag E_OUTOFMEMORY             | Out of Memory
//      @flag OTHER                     | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowset::GetNextRows
    (
    HCHAPTER   hReserved,       //@parm IN | Reserved for future use. Ingored.
    LONG       cRowsToSkip,     //@parm IN | Rows to skip before reading
    LONG       cRows,           //@parm IN | Number of rows to fetch
    ULONG      *pcRowsObtained, //@parm OUT | Number of rows obtained
    HROW       **prghRows       //@parm OUT | Array of Hrows obtained
    )
{
    ULONG    cRowsTmp;
    BOOL     fExtraRow;
    ULONG    cSlotAlloc =0;
    ULONG    di;
    LONG     irow, ih;
    ULONG    irowFirst, irowLast;
    PROWBUFF prowbuff;
    HRESULT  hr;
    BYTE     *pbRowFirst;

    // Check validity of arguments.
    if (pcRowsObtained == NULL || prghRows == NULL)
        return ResultFromScode( E_INVALIDARG );

    // init out-params
    *pcRowsObtained = 0;

    // No-op case always succeeds.
    if (cRows == 0 && cRowsToSkip == 0)
        return ResultFromScode( S_OK );

    // This implementation doesn't support scrolling backward.
    if (cRows < 0 || cRowsToSkip < 0)
        return ResultFromScode( DB_E_CANTFETCHBACKWARDS );

    // Are there any unreleased rows?
    if ((m_pObj->m_prowbitsIBuffer)->ArrayEmpty() != S_OK)
        return ResultFromScode( DB_E_ROWSNOTRELEASED );
    assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty

    // Is the cursor fully materialized (end-of-cursor condition)?
    if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR)
        return ResultFromScode( DB_S_ENDOFROWSET );

    assert( m_pObj->m_rgbRowData );
    if (FAILED( m_pObj->Rebind((BYTE *) m_pObj->GetRowBuff( m_pObj->m_irowMin ))))
        return ResultFromScode( E_FAIL );

    // Sometimes we need an extra row to fetch data and copy it to appropriate place.
    fExtraRow = (cRows > 1);
    cRowsTmp = cRows + (fExtraRow ? 1 : 0);

    if (FAILED( hr = GetNextSlots( m_pObj->m_pIBuffer, cRowsTmp, &irowFirst )))
        return hr;
    cSlotAlloc = cRowsTmp;

    //
    // Fetch Data
    //
    if (cRowsToSkip)
        {
        // Calculate the new position
        m_pObj->m_irowFilePos += cRowsToSkip;

        // Check if skip causes END_OF_ROWSET
        if (m_pObj->m_irowFilePos > m_pObj->m_pFileio->GetRowCnt() ||
            m_pObj->m_irowFilePos <= 0)
            {
            m_pObj->m_dwStatus |= STAT_ENDOFCURSOR;
            return ResultFromScode( DB_S_ENDOFROWSET );
            }
        }

    pbRowFirst = (BYTE *) m_pObj->GetRowBuff( irowFirst );
    for (irow =1; irow <= cRows; irow++)
        {
        // Get the Data from the File into the row buffer
        if (S_FALSE == (hr =  m_pObj->m_pFileio->Fetch( m_pObj->m_irowFilePos + irow )))
            {
            m_pObj->m_dwStatus |= STAT_ENDOFCURSOR;
            break;
            }
        else
            {
            if (FAILED( hr ))
                return ResultFromScode( E_FAIL );
            }

        // Got a row, so copy it from bound row to the destination.
        // Very first row is Fetch buffer, we give out rows [2...cRows+1].
        if (fExtraRow)
            memcpy( pbRowFirst + (m_pObj->m_cbRowSize * irow), pbRowFirst, m_pObj->m_cbRowSize );
        }

    cRowsTmp = irow - 1; //Irow will be +1 because of For Loop
    m_pObj->m_irowLastFilePos = m_pObj->m_irowFilePos;
    m_pObj->m_irowFilePos += cRowsTmp;

    //
    // Through fetching many rows of data
    //
    // Allocate row handles for client.
    // Note that we need to use IMalloc for this.
    //
    // Should only malloc cRowsTmp, instead of cRows.
    //
    // Modified to use IMalloc.
    // Should malloc cRows, since client will assume it's that big.
    //

    *pcRowsObtained = cRowsTmp;
    if (*prghRows == NULL)
        {
        *prghRows = (HROW *) g_pIMalloc->Alloc( cRows*sizeof( HROW ));
        }

    if (NULL == *prghRows)
        return ResultFromScode( E_OUTOFMEMORY );

    //
    // Fill in the status information: Length, IsNull
    // May be able to wait until first call to GetData,
    // but have to do it sometime.
    //
    // Suggest keeping an array of structs of accessor info.
    // One element is whether accessor requires any status info or length info.
    // Then we could skip this whole section.
    //
    // Added check for cRowsTmp to MarkRows call.
    // Don't want to call if cRowsTmp==0.
    // (Range passed to MarkRows is inclusive, so can't specify marking 0 rows.)
    //
    // Note that SetSlots is a CBitArray member function -- not an IBuffer function.
    //
    // Bits are [0...n-1], row handles are [1...n].
    //
    // Cleanup. Row handles, bits, indices are the same [m....(m+n)], where m is some # >0,
    //
    // Added row-wise reference counts and cursor-wise reference counts.
    //

    // Set row handles, fix data length field and compute data status field.//
    di = fExtraRow ? 1 : 0;
    m_pObj->m_cRows   = cRowsTmp;
    irowLast = irowFirst + di + cRowsTmp - 1;

    // Cleanup extra slots where no hRow actually was put..
    //  ** Because of less rows than asked for
    //  ** Because of temporary for for data transfer.
    if (fExtraRow)
        if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst, 1 )))
            return hr;
    if (cSlotAlloc > (cRowsTmp + di))
        if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst + cRowsTmp + di, (cSlotAlloc - cRowsTmp - di))))
            return hr;

    for (irow = (LONG) (irowFirst + di), ih =0; irow <= (LONG) irowLast; irow++, ih++)
        {
        // Increment the rows-read count,
        // then store it as the bookmark in the very first DWORD of the row.

        prowbuff = m_pObj->GetRowBuff( irow );

        // Insert the bookmark and its row number (from 1...n) into a hash table.
        // This allows us to quickly determine the presence of a row in mem, given the bookmark.
        // The bookmark is contained in the row buffer, at the very beginning.
        // Bookmark is the row number within the entire result set [1...num_rows_read].

        // This was a new Bookmark, not in memory,
        // so return to user (in *prghRows) the hRow we stored.
        prowbuff->ulRefCount++;
        prowbuff->pbBmk = (BYTE*) m_pObj->m_irowLastFilePos + ih + 1;
        m_pObj->m_ulRowRefCount++;

        (*prghRows)[ih] = (HROW) (irow);
        }

    if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR)
        return ResultFromScode( DB_S_ENDOFROWSET );
    else
        return ResultFromScode( S_OK );
}

// CImpIRowset::ReleaseRows ---------------------------------------
//
// @mfunc Releases row handles
//
// @rdesc HRESULT
//      @flag S_OK              | Method Succeeded
//      @flag E_INVALIDARG      | Invalid parameters were specified
//      @flag DB_E_BADROWHANDLE | Row handle was invalid
//
STDMETHODIMP CImpIRowset::ReleaseRows
    (
    ULONGcRow,           //@parm IN | Number of rows to release
    const HROWrghRow[],       //@parm IN | Array of handles of rows to be released
DBROWOPTIONSrgRowOptions[],//@parm IN | Additional Options
    ULONG*pcRowReleased, //@parm OUT | Count of rows actually released
    ULONGrgRefCount[]    //@parm OUT | Array of refcnts for the rows
    )
{
    ULONG   ihRow;
    BOOL    fEncounteredError;
    ROWBUFF *pRowBuff;
    ULONG   cRowReleased = 0;

    // check params
    if (cRow && !rghRow)
        return ResultFromScode( E_INVALIDARG );

    // init out param
    if (pcRowReleased)
        *pcRowReleased = 0;

    fEncounteredError = FALSE;
    ihRow = 0;

    while (ihRow < cRow)
        {
        if ((m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRow[ihRow] ) == S_OK)
            {
            // Found valid row, so decrement reference counts.
            // (Internal error for refcount to be 0 here, since slot set.)
            pRowBuff = m_pObj->GetRowBuff((ULONG) rghRow[ihRow] );
            assert( pRowBuff->ulRefCount != 0 );
            assert( m_pObj->m_ulRowRefCount != 0 );
            --pRowBuff->ulRefCount;
            --m_pObj->m_ulRowRefCount;

            if (rgRefCount)
                rgRefCount[ihRow] = pRowBuff->ulRefCount;

            if (pRowBuff->ulRefCount == 0)
                {
                ReleaseSlots( m_pObj->m_pIBuffer, (ULONG) rghRow[ihRow], 1 );
                cRowReleased++;
                }
            ihRow++;
            }
        else
            {
            // It is an error for client to try to release a row
            // for which "IsSetSlot" is false.  Client gave us an invalid handle.
            // Ignore it (we can't release it...) and report error when done.
            fEncounteredError = TRUE;
            if (rgRefCount)
                rgRefCount[ihRow] = 0;
            // stop looping if we hit an error
            break;
            }
        }

    if (pcRowReleased)
        *pcRowReleased = cRowReleased;

    if (fEncounteredError)
        return ResultFromScode( DB_E_BADROWHANDLE );
    else
        return ResultFromScode( S_OK );
}

// CImpIRowset::ResartPosition ---------------------------------------
//
// @mfunc Repositions the next fetch position to the start of the rowset
//
// - all rows must be released before calling this method
// - it is not expensive to Restart us, because we are from a single table
//
//
// @rdesc HRESULT
//      @flag S_OK                  | Method Succeeded
//      @flag DB_E_ROWSNOTRELEASED  | All HROWs must be released before calling
//
STDMETHODIMP CImpIRowset::RestartPosition
    (
    HCHAPTER    hReserved        //@parm IN | Reserved for future use.  Ignored.
    )
{
    // make sure all rows have been released
    if (S_OK != (m_pObj->m_prowbitsIBuffer)->ArrayEmpty())
        return DB_E_ROWSNOTRELEASED;
    assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty

    // set "next fetch" position to the start of the rowset
    m_pObj->m_irowFilePos = 0;

    // clear "end of cursor" flag
    m_pObj->m_dwStatus &= ~STAT_ENDOFCURSOR;

    return ResultFromScode( S_OK );
}

// CImpIRowset::AddRefRows --------------------------------------------------
//
// @mfunc Adds a reference count to an existing row handle
//
// @rdesc HResult
//      @flag S_OK              | Method Succeeded
//      @flag E_INVALIDARG      | rghRows was NULL and cRows was not 0
//      @flag DB_E_BADROWHANDLE | An element of rghRows was invalid

STDMETHODIMP  CImpIRowset::AddRefRows
    (
    ULONG           cRows,          // @parm IN     | Number of rows to refcount
    const HROW      rghRows[],      // @parm IN     | Array of row handles to refcount
    ULONG*          pcRefCounted,   // @parm OUT    | Number successfully refcounted
    ULONG           rgRefCounts[]   // @parm OUT    | Array of refcounts
    )
{
    ULONG   ihRow = 0;
    ROWBUFF *pRowBuff;
    ULONG   cAddRef;

    // init out param
    if (pcRefCounted)
        *pcRefCounted = 0;

    // check params
    if (cRows && !rghRows)
        return ResultFromScode( E_INVALIDARG );

    cAddRef = 0;

    // for each of the HROWs the caller provided...
    for (ihRow = 0; ihRow < cRows; ihRow++)
        {
        // check the row handle
        if (S_OK != (m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRows[ihRow] ))
            return ResultFromScode( DB_E_BADROWHANDLE );

        // bump refcount
        pRowBuff = m_pObj->GetRowBuff((ULONG) rghRows[ihRow] );
        assert( pRowBuff->ulRefCount != 0 );
        assert( m_pObj->m_ulRowRefCount != 0 );
        ++pRowBuff->ulRefCount;
        ++m_pObj->m_ulRowRefCount;

        // bump total refcounted
        cAddRef++;

        // stuff new refcount into caller's array
        if (rgRefCounts)
            rgRefCounts[ihRow] = pRowBuff->ulRefCount;
        }

    // fill out-param
    if (pcRefCounted)
        (*pcRefCounted) = cAddRef;

    return ResultFromScode( S_OK );
}


OPNROWST.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module OPNROWST.CPP | IOpenRowset interface implementation
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// CImpIOpenRowset::OpenRowset ------------------------------------------------
//
// @mfunc Opens and returns a rowset that includes all rows from a single base table
//
// @rdesc HRESULT
//      @flag S_OK                  | The method succeeded
//      @flag E_INVALIDARG          | pTableID was NULL
//      @flag E_FAIL                | Provider-specific error
//      @flag DB_E_NOTABLE          | Specified table does not exist in current Data
//                                  | Data Source object
//      @flag E_OUTOFMEMORY         | Out of memory
//      @flag E_NOINTERFACE         | The requested interface was not available

STDMETHODIMP CImpIOpenRowset::OpenRowset
    (
    IUnknown*                   pUnkOuter,          //@parm IN    | Controlling unknown, if any
    DBID*                       pTableID,           //@parm IN    | table to open
DBID*pIndexID,//@parm IN  | DBID of the index
    REFIID                      riid,               //@parm IN    | interface to return
    ULONG                       cProperties,        //@parm IN    | count of properties
    DBPROPSETrgProperties[],     //@parm INOUT | array of property values
    IUnknown**                  ppRowset            //@parm OUT   | where to return interface
    )
{
    int  cCharsCopied;
    char szFileName[MAX_PATH ];
    char szFile[_MAX_PATH ] = "";

    CFileIO*    pFileio = NULL;
    CRowset*    pRowset = NULL;
    HRESULT     hr;

    // NULL out-params in case of error
    if( ppRowset )
    *ppRowset = NULL;

    // validate in-params
    if (pUnkOuter)
        return ResultFromScode( DB_E_NOAGGREGATION );

    if (!pTableID && !pIndexID)
        return ResultFromScode( E_INVALIDARG );

    if (pTableID->eKind != DBKIND_NAME || !pTableID->uName.pwszName)
        return ResultFromScode( E_INVALIDARG );

    if ((cProperties != 0) && (!rgProperties))
        return ResultFromScode( E_INVALIDARG );

    assert( m_pObj );

    // this DBSession object can only create 1 Rowset object
    if (m_pObj->m_fRowsetCreated)
        return ResultFromScode( E_FAIL );

    // get file name
    cCharsCopied = WideCharToMultiByte( CP_ACP, 0, pTableID->uName.pwszName, -1,
                        szFileName, sizeof( szFileName ), NULL, NULL );
    if (!cCharsCopied)
        return ResultFromScode( E_FAIL );

    // Concatenate the path and filename
    lstrcat( szFile, m_pObj->m_szPath );
    lstrcat( szFile, "\\" );
    lstrcat( szFile, szFileName );

    // open and initialize a file object
    pFileio = new CFileIO();
    if (!pFileio)
        return ResultFromScode( E_OUTOFMEMORY );

    hr = pFileio->fInit( szFile );
    if (FAILED( hr ))
        {
        delete pFileio;
        return ResultFromScode( E_FAIL );
        }

    // open and initialize a rowset\cursor object
    pRowset = new CRowset( NULL );
    if (!pRowset)
        {
        delete pFileio;
        return ResultFromScode( E_OUTOFMEMORY );
        }
    // Initialize the rowset\cursor.
    // For now, since don't yet support "setable" properties, so no properties to pass.
    // The rowset will always create all of its interfaces.
    // This is all-or-nothing.
    if (!pRowset->FInit( pFileio ))
        {
        delete pFileio;
        delete pRowset;
        return ResultFromScode( DB_E_NOTABLE );
        }
//At this point we have handed off the pFileio pointer to the sample
//provider so null it out.
pFileio = NULL;

    // get requested interface pointer on rowset\cursor
    hr = pRowset->QueryInterface( riid, (void **) ppRowset );
    if (FAILED( hr ))
    {
    delete pRowset;
    delete pFileio;

// After a call to QI we still want to return S_OK,
// if the ppRowset was NULL because Execution was to happen
// we just don't want to return a result set.
if( ppRowset == NULL )
return ResultFromScode(S_OK);
else
        return ResultFromScode( E_NOINTERFACE );
    }

    // all went well
    m_pObj->m_fRowsetCreated = TRUE;
//Assign creator pointer. Used for IRosetInfo::GetSpecificetion
    pRowset->m_pCreator = m_pObj;
pRowset->m_pCreator->AddRef();
    return ResultFromScode( S_OK );
}


RC.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module RC.H | Resource IDs
//
#ifndef _RC_H_
#define _RC_H_

// Common Dialog Ids
#define IDT_PROMPT 2999
#define IDD_BROWSE 3000
#define IDD_GETLOG 3001
#define IDC_APPEND 3002

#endif


ROWCHNG.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ROWCHNG.CPP | IRowsetChange interface implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

//  IRowsetChange specific methods

// CImpIRowsetChange::SetData ------------------------------------------------
//
// @mfunc Sets new data values into fields of a row.
//
// @rdesc HRESULT
//      @flag S_OK                   | The method succeeded
//      @flag E_OUTOFMEMORY          | Out of memory
//      @flag DB_E_BADACCESSORHANDLE | Bad accessor handle
//      @flag DB_E_READONLYACCESSOR  | Tried to write through a read-only accessor
//      @flag DB_E_BADROWHANDLE      | Invalid row handle
//      @flag E_INVALIDARG           | pData was NULL
//      @flag E_FAIL                 | Provider-specific error
//      @flag OTHER                  | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowsetChange::SetData
    (
    HROW        hRow,       //@parm IN | Handle of the row in which to set the data
    HACCESSOR   hAccessor,  //@parm IN | Handle to the accessor to use
    void*pData       //@parm IN | Pointer to the data
    )
{
    PACCESSOR       paccessor;
    ULONG           icol, ibind;
    BYTE*           pbProvRow;
    HRESULT         hr;
    ULONG           cBindings;
    DBBINDING*      pBinding;
    ULONG           dwErrorCount;
    DBTYPE          dwSrcType;
    DBTYPE          dwDstType;
    void*           pSrc;
    void*           pDst;
    ULONG           dwSrcLength;
    ULONG*          pdwDstLength;
    ULONG           dwDstMaxLength;
    DWORD           dwSrcStatus;
    DWORD*          pdwDstStatus;
    DWORD           dwPart;
    PCOLUMNDATA     pColumnData;
    BYTE            b;

    BYTE*           rgbRowDataSave = NULL;

    rgbRowDataSave = (BYTE *) malloc( m_pObj->m_cbRowSize );
    if (NULL == rgbRowDataSave)
        return ResultFromScode( E_OUTOFMEMORY );

    if (   m_pObj->m_pextbufferAccessor == NULL
        || FAILED( m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &paccessor))
        || paccessor == NULL)
        {
        return ResultFromScode( DB_E_BADACCESSORHANDLE );
        }

    assert( paccessor );

    cBindings = paccessor->cBindings;
    pBinding  = paccessor->rgBindings;

    // Is row handle right?
    if ((m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) hRow ) != S_OK)
        return ResultFromScode( DB_E_BADROWHANDLE );

    // Ensure a source of data.
    if (pData == NULL)
        return ResultFromScode( E_INVALIDARG );

    pbProvRow = (BYTE *) (m_pObj->GetRowBuff((ULONG) hRow ));

    // Save the row.
    memcpy( rgbRowDataSave, pbProvRow, m_pObj->m_cbRowSize );

    // Apply accessor to data.
    for (ibind = 0, dwErrorCount = 0; ibind < cBindings; ibind++)
        {
        icol = pBinding[ibind].iOrdinal;
        pColumnData = (COLUMNDATA *) (pbProvRow + m_pObj->m_rgdwDataOffsets[icol]);

        dwDstType      = m_pObj->m_rgdbcolinfo[icol].wType;
        pDst           = &(pColumnData->bData);
        pdwDstLength   = (ULONG *) & (pColumnData->dwLength);
        pdwDstStatus   = &(pColumnData->dwStatus);
        dwDstMaxLength = m_pObj->m_rgdbcolinfo[icol].ulColumnSize;

        dwPart         = pBinding[ibind].dwPart;
        dwSrcType      = pBinding[ibind].wType;

        if ((dwPart & DBPART_VALUE) == 0)
            {
            if (((dwPart & DBPART_STATUS)
                && (*(ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus) & DBSTATUS_S_ISNULL))
               || ((dwPart & DBPART_LENGTH) && *(ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) == 0))
                {
                pSrc = &b;
                b = 0x00;
                }
            else
                return ResultFromScode( E_FAIL );
            }
        else
            {
            pSrc = (void *) ((BYTE*) pData + pBinding[ibind].obValue);
            }

        dwSrcLength = (dwPart & DBPART_LENGTH) ? *(ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) : 0;

        dwSrcStatus = (dwPart & DBPART_STATUS) ? *(ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus)
            : DBSTATUS_S_OK;

        hr = g_pIDataConvert->DataConvert(
dwSrcType,
                dwDstType,
                dwSrcLength,
                pdwDstLength,
                pSrc,
                pDst,
                dwDstMaxLength,
                dwSrcStatus,
                pdwDstStatus,
0,// bPrecision for conversion to DBNUMERIC
0,// bScale for conversion to DBNUMERIC
DBDATACONVERT_SETDATABEHAVIOR);
        if (FAILED( hr ))
            return hr;  // fatal error
        if (hr != S_OK)
            dwErrorCount++; // rounding or truncation or can't coerce
        }

    // Carry out the update.
    if (FAILED( m_pObj->m_pFileio->UpdateRow((ULONG) ((PROWBUFF) pbProvRow)->pbBmk, m_pObj->m_rgdwDataOffsets, pbProvRow )))
        {
        // Restore the row to its previous state */
        memcpy( pbProvRow, rgbRowDataSave, m_pObj->m_cbRowSize );
        return ResultFromScode( E_FAIL );
        }

    free( rgbRowDataSave );

    // We report any lossy conversions with a special status.
    // Note that DB_S_ERRORSOCCURED is a success, rather than failure.
    return ResultFromScode( dwErrorCount ? DB_S_ERRORSOCCURRED : S_OK );
}

// CImpIRowsetChange::DeleteRows ---------------------------------------
//
// @mfunc Deletes rows from the provider.  If Errors on individual rows
// occur, the DBERRORINFO array is updated to reflect the error and S_FALSE
// is returned instead of S_OK.
//
// @rdesc HRESULT indicating the status of the method
//      @Flag S_OK                  | All row handles deleted
//      @Flag DB_S_ERRORSOCCURRED   | Some, but not all, row handles deleted
//      @Flag E_INVALIDARG          | Arguments did not match spec.
//      @Flag E_OUTOFMEMORY         | Could not allocated error array
//
STDMETHODIMP CImpIRowsetChange::DeleteRows
    (
HCHAPTERhReserved,//@parm IN| Reserved for future use
ULONG           cRows,//@parm IN| Number of rows to delete
const HROW      rghRows[],//@parm IN| Array of handles to delete
DBROWSTATUSrgRowStatus[]//@parm OUT | Error information
    )
{
    ULONG           ihRow        = 0L;
    ULONG           cErrors      = 0L;
    ULONG           cRowReleased = 0L;
    BYTE*           pbProvRow;

    // If No Row handle, just return.
    if (0 == cRows)
        return ResultFromScode( S_OK );

    // Check for Invalid Arguments
    if ((1 >= cRows) && (NULL == rghRows))
        return ResultFromScode( E_INVALIDARG );

    // Process row handles
    while (ihRow < cRows)
    {
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_S_OK;

        // Is row handle valid
        if (S_OK != (m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRows[ihRow] ))
        {
            // Log Error
if (rgRowStatus)
rgRowStatus[ihRow]= DBROWSTATUS_E_INVALID;

            cErrors++;
            ihRow++;
            continue;
}

        // Get RowBuffer to look at which row this applies to
        pbProvRow = (BYTE *) (m_pObj->GetRowBuff((ULONG) rghRows[ihRow] ));

        // Has row already been deleted
        //  S_OK means deleted
        if (S_OK == m_pObj->m_pFileio->IsDeleted((ULONG) ((PROWBUFF) pbProvRow)->pbBmk ))
        {
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_E_DELETED;
            cErrors++;
            ihRow++;
            continue;
        }

        // Delete the Row,
        if (S_OK != m_pObj->m_pFileio->DeleteRow((ULONG) ((PROWBUFF) pbProvRow)->pbBmk ))
        {
// Some better decision as to what rowstatus to set could be done here..
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_E_PERMISSIONDENIED;
            cErrors++;
            ihRow++;
            continue;
        }
} //while

// If everything went OK except errors in rows use DB_S_ERRORSOCCURRED.
return cErrors ? (cErrors < cRows) ?
ResultFromScode(DB_S_ERRORSOCCURRED) :
 ResultFromScode(DB_E_ERRORSOCCURRED) :
 ResultFromScode(S_OK);
}

// CImpIRowsetChange::InsetRow  --------------------------------------------
//
// @mfunc Insert row into provider
//
// @rdesc HRESULT
//      @flag E_NOTSUPPORTED| this method is not implemented
//
STDMETHODIMP    CImpIRowsetChange::InsertRow
    (
    HCHAPTERhReserved,
HACCESSORhAccessor,
void*pData,
HROW*phRow
)
{
    return ResultFromScode( DB_E_NOTSUPPORTED );
}


ROWINFO.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ROWINFO.CPP | IRowsetInfo interface implementation
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Code ----------------------------------------------------------------------

// IRowsetInfo specific methods

// CImpIRowsetInfo::GetReferencedRowset ----------------------------------------------------
//
// @mfunc Returns an interface pointer to the rowset to which the bookmark
// applies
//
// @rdesc HRESULT
//      @flag DB_E_NOTAREFERENCECOLUMN| This rowset does not support bookmarks
//
STDMETHODIMP CImpIRowsetInfo::GetReferencedRowset
    (
    ULONGiOrdinal,//@parm IN| Bookmark Column
    REFIIDriid,//@parm IN| ID of the interface pointer to return
IUnknown **ppReferencedRowset//@parm OUT | IRowset Interface Pointer
    )
{
    // Since we don't support bookmarks, this will alway return
    // an error
    return ResultFromScode( DB_E_NOTAREFERENCECOLUMN );
}

// CImpIRowsetInfo::GetProperties ----------------------------------------------------
//
// @mfunc Returns current settings of all properties supported by the rowset
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcProperties or prgProperties was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CImpIRowsetInfo::GetProperties
    (
    const ULONG         cPropertySets,//@parm IN | # of property sets
    const DBPROPIDSETrgPropertySets[],//@parm IN | Array of DBPROPIDSET
    ULONG*              pcProperties,//@parm OUT | count of properties returned
    DBPROPSET**prgProperties//@parm OUT | property information returned
    )
{
    assert( m_pObj );
    assert( m_pObj->m_pUtilProp );

    // just pass this call on to the utility object that manages our properties
    return m_pObj->m_pUtilProp->GetProperties( cPropertySets, rgPropertySets,
                pcProperties, prgProperties );
}

// CImpIRowsetInfo::GetSpecification ---------------------------------------
//
// @mfunc Returns the interface pointer of the object that created the rowset
//
// @rdesc HRESULT
//      @flag S_OK          | Method Succeeded
//      @flag E_INVALIDARG  | Invalid parameters were specified
//
STDMETHODIMP CImpIRowsetInfo::GetSpecification
    (
    REFIID   riid,              //@parm IN | Interface ID of the interface being queried for.
    IUnknown **ppSpecification  //@parm OUT | Pointer to interface that instantiated this object
    )
{
HRESULT hr=S_OK;

    if (NULL == ppSpecification)
        return ResultFromScode( E_INVALIDARG );

    // we do not have an interface pointer on the object that created this rowset, yet.
    *ppSpecification = NULL;

if (m_pObj->m_pCreator)
   hr = m_pObj->m_pCreator->QueryInterface (riid,(void**)ppSpecification);

return hr;
}


ROWSET.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ROWSET.CPP | CRowset object implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

static const MAX_BITS = 1008;

static const TYPE_CHAR = 1;
static const TYPE_SLONG = 3;

// Code ----------------------------------------------------------------------

// CRowset::CRowset ----------------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CRowset::CRowset
    (
    LPUNKNOWN pUnkOuter         //@parm IN | Outer Unkown Pointer
    )
{

    //  Initialize simple member vars
    m_cRef          = 0L;
    m_pUnkOuter     = pUnkOuter;

    // Intialize buffered row count + pointers to allocated memory
    m_pFileio           = NULL;
    m_cRows             = 0;
    m_irowFilePos       = 0;
    m_irowLastFilePos   = 0;
    m_cbRowSize         = 0;
    m_cbHeapUsed        = 0;
    m_irowMin           = 0;
    m_ulRowRefCount     = 0;
    m_rgdbcolinfo       = NULL;
    m_pbHeap            = NULL;
    m_rgdwDataOffsets   = NULL;
    m_rgfIsSigned       = NULL;
    m_rgcbLen           = NULL;
    m_pextbufferAccessor= NULL;
    m_pIBuffer          = NULL;
    m_prowbitsIBuffer   = NULL;
    m_pLastBindBase     = NULL;
    m_rgbRowData        = NULL;
    m_dwStatus          = 0;
    m_pUtilProp         = NULL;
m_pCreator= NULL;

    //  Initially, NULL all contained interfaces
    m_pIColumnsInfo                 = NULL;
    m_pIRowset                      = NULL;
    m_pIRowsetChange                = NULL;
    m_pIAccessor                    = NULL;
    m_pIRowsetInfo                  = NULL;

    // Increment global object count.
    OBJECT_CONSTRUCTED();

    return;
}

// CRowset::~CRowset ---------------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CRowset:: ~CRowset
    (
    void
    )
{
    // Release buffer for column names (pointed to by m_rgdbcolinfo).
    if (NULL != m_pbHeap)
        VirtualFree((VOID *) m_pbHeap, 0, MEM_RELEASE );

    // Free pointers.
    // (Note delete is safe for NULL ptr.)
    delete [] m_rgdbcolinfo;
    delete [] m_rgdwDataOffsets;
    delete [] m_rgfIsSigned;
    delete [] m_rgcbLen;
    delete    m_prowbitsIBuffer;
    delete    m_pUtilProp;

    if (NULL != m_pIBuffer)
        ReleaseSlotList( m_pIBuffer );

    // Free accessors.
    // Each accessor is allocated via new/delete.
    // We store an array of ptrs to each accessor (m_pextbufferAccessor).
    if (NULL != m_pextbufferAccessor)
        {
        ULONG       hAccessor, hAccessorLast;
        PACCESSOR   pAccessor;

        m_pextbufferAccessor->GetFirstLastItemH( hAccessor, hAccessorLast );
        for (; hAccessor <= hAccessorLast; hAccessor++)
            {
            m_pextbufferAccessor->GetItemOfExtBuffer( hAccessor, &pAccessor );
            delete [] pAccessor;
            }
        delete m_pextbufferAccessor;
        }

    //  Free contained interfaces
    delete m_pIColumnsInfo;
    delete m_pIRowset;
    delete m_pIRowsetChange;
    delete m_pIAccessor;
    delete m_pIRowsetInfo;

    // free CFileio object
    if (m_pFileio)
        delete m_pFileio;

// Decrement the DBSession Count.  GetSpecification is not possible anymore
if( m_pCreator )
{
// Mark the session as not having an open rowset anymore
m_pCreator->RowsetDestroyed();
m_pCreator->Release();
}

    // Decrement global object count.
    OBJECT_DESTRUCTED();

    return;
}

// CRowset::FInit ------------------------------------------------------------
//
// @mfunc Initialize the rowset Object
//
// @rdesc Did the Initialization Succeed
//      @flag  TRUE | Initialization succeeded
//      @flag  FALSE | Initialization failed
//
BOOL CRowset::FInit
    (
    CFileIO*    pCFileio        //@parm IN | pointer to Fileio object
    )
{
    LPUNKNOWN   pIUnknown = (LPUNKNOWN) this;

    if (m_pUnkOuter)
        pIUnknown = m_pUnkOuter;

    m_pFileio = pCFileio;

    // Find # of columns in the result set.
    m_cCols = m_pFileio->GetColumnCnt();
    if (m_cCols <= 0)
        return FALSE;

    //---------------------
    // Allocate info arrays
    //---------------------

    // Heap for column names.
    // Commit it all, then de-commit and release once we know size.
    m_pbHeap = (BYTE *) VirtualAlloc( NULL,
                                      MAX_HEAP_SIZE,
                                      MEM_RESERVE | MEM_COMMIT,
                                      PAGE_READWRITE );
    if (NULL == m_pbHeap)
        return FALSE;

    // Add an extra index, so we can be 1 based
    m_rgdbcolinfo     = new DBCOLUMNINFO[m_cCols + 1];
    m_rgdwDataOffsets = new ULONG[m_cCols + 1];
    m_rgfIsSigned     = new BOOL[m_cCols + 1];
    m_rgcbLen         = new SDWORD[m_cCols + 1];

    if (   m_rgdbcolinfo == NULL
        || m_rgdwDataOffsets == NULL
        || m_rgfIsSigned == NULL
        || m_rgcbLen == NULL)
        return FALSE;

    if (FAILED( GatherColumnInfo()))
        return FALSE;

    if (FAILED( CreateHelperFunctions()))
        return FALSE;

    //--------------------
    // Perform binding
    //--------------------
    // Bind result set columns to the first row of the internal buffer.
    // For each column bind it's data as well as length. Leave space for
    // derived status info.
    // Note that we could defer binding, but this way we can check for
    // bad errors before we begin.
    // We may need to bind again if going back and forth
    // with GetNextRows.
    assert( m_rgbRowData );
    if (FAILED( Rebind((BYTE *) GetRowBuff( m_irowMin ))))
        return FALSE;

    // allocate utility object that manages our properties
    m_pUtilProp = new CUtilProp();

    // Allocate contained interface objects
    // Note that our approach is simple - we always create *all* of the Rowset interfaces
    // If our properties were read\write (i.e., could be set), we would need to
    // consult properties to known which interfaces to create.
    // Also, none of our interfaces conflict. If any did conflict, then we could
    // not just blindly create them all.
    m_pIColumnsInfo             = new CImpIColumnsInfo( this, pIUnknown );
    m_pIRowset                  = new CImpIRowset( this, pIUnknown );
    m_pIRowsetChange            = new CImpIRowsetChange( this, pIUnknown );
    m_pIAccessor                = new CImpIAccessor( this, pIUnknown );
    m_pIRowsetInfo              = new CImpIRowsetInfo( this, pIUnknown );

    // if all interfaces were created, return success
    return (BOOL) (m_pIColumnsInfo &&
                   m_pIRowset &&
                   m_pIRowsetChange &&
                   m_pIAccessor &&
                   m_pIRowsetInfo);
}

// CRowset::QueryInterface ---------------------------------------------------
//
// @mfunc Returns a pointer to a specified interface. Callers use
// QueryInterface to determine which interfaces the called object
// supports.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG | One or more arguments are invalid.
//
STDMETHODIMP CRowset::QueryInterface
    (
    REFIID riid,
    LPVOID * ppv
    )
{
    if (NULL == ppv)
        return ResultFromScode( E_INVALIDARG );

    //  Place NULL in *ppv in case of failure
    *ppv = NULL;

    //  This is the non-delegating IUnknown implementation
    if (riid == IID_IUnknown)
        *ppv = (LPVOID) this;
    else if (riid == IID_IRowset)
        *ppv = (LPVOID) m_pIRowset;
    else if (riid == IID_IColumnsInfo)
        *ppv = (LPVOID) m_pIColumnsInfo;
    else if (riid == IID_IRowsetChange)
        *ppv = (LPVOID) m_pIRowsetChange;
    else if (riid == IID_IAccessor)
        *ppv = (LPVOID) m_pIAccessor;
    else if (riid == IID_IRowsetInfo)
        *ppv = (LPVOID) m_pIRowsetInfo;

    //  If we're going to return an interface, AddRef it first
    if (*ppv)
        {
        ((LPUNKNOWN) *ppv)->AddRef();
        return ResultFromScode( S_OK );
        }
    else
        return ResultFromScode( E_NOINTERFACE );
}

// CRowset::AddRef -----------------------------------------------------------
//
// @mfunc Increments a persistence count for the object
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CRowset::AddRef
     (
     void
     )
{
    return ++m_cRef;
}

// CRowset::Release ----------------------------------------------------------
//
// @mfunc Decrements a persistence count for the object and if
// persistence count is 0, the object destroys itself.
//
// @rdesc Current reference count
//
STDMETHODIMP_( ULONG ) CRowset::Release
     (
     void
     )
{
    if (!--m_cRef)
        {
        delete this;
        return 0;
        }

    return m_cRef;
}

// CRowset::CreateHelperFunctions --------------------------------------------
//
// @mfunc Creates Helper classes that are needed to manage the Rowset Object
//
// @rdesc HRESULT
//      @flag S_OK | Helper classes created
//      @flag E_FAIL | Helper classes were not created
//
HRESULT CRowset::CreateHelperFunctions
    (
    void
    )
{
    //----------------------
    // Create helper objects
    //----------------------

    // Bit array to track presence/absense of rows.
    m_prowbitsIBuffer = new CBitArray;
    if (m_prowbitsIBuffer == NULL || FAILED( m_prowbitsIBuffer->FInit( MAX_BITS, g_dwPageSize )))
        return ResultFromScode( E_FAIL );

    // List of free slots.
    // This manages the allocation of sets of contiguous rows.
    if (FAILED( InitializeSlotList( MAX_TOTAL_ROWBUFF_SIZE / m_cbRowSize,
                         m_cbRowSize, g_dwPageSize, m_prowbitsIBuffer,
                         &m_pIBuffer, &m_rgbRowData )))
        return ResultFromScode( E_FAIL );

    // Create the ExtBuffer array.
    // This is an array of pointers to malloc'd accessors.
    m_pextbufferAccessor = (LPEXTBUFFER) new CExtBuffer;
    if (m_pextbufferAccessor == NULL || FAILED( m_pextbufferAccessor->FInit( 1, sizeof( PACCESSOR ), g_dwPageSize )))
        return ResultFromScode( E_FAIL );

    // Locate some free slots.
    // Should be at very beginning.
    // This tells us which row we will bind to: m_irowMin.
    if (FAILED( GetNextSlots( m_pIBuffer, 1, &m_irowMin )))
        return ResultFromScode( E_FAIL );

    ReleaseSlots( m_pIBuffer, m_irowMin, 1 );

    return ResultFromScode( S_OK );
}

// CRowset::GatherColumnInfo -------------------------------------------------
//
// @mfunc Creates DBCOLINFO structures for each column in the result set.
//
// @rdesc HRESULT
//      @flag S_OK | Column Info Obtained
//      @flag E_FAIL | Problems getting Column Info
//
HRESULT CRowset::GatherColumnInfo
    (
    void
    )
{
    HRESULT         hr;
    LPWSTR          lpwstr;
    int             cchFree, cchWide;
    DBCOLUMNINFO*   pcolinfo;
    ULONG           dwOffset;
    UWORD           icol;
    SWORD           cbName;
    SWORD           swCSVType;
    UDWORD          cbColDef;
    DWORD           dwdbtype;

    //----------------------------------
    // Gather column info
    //----------------------------------

    lpwstr  = (LPWSTR) m_pbHeap;
    cchFree = MAX_HEAP_SIZE / 2;

    pcolinfo = &m_rgdbcolinfo[1];
    dwOffset = offsetof( ROWBUFF, cdData );

    for (icol=1; icol <= m_cCols; icol++, pcolinfo++)
        {
        LPTSTR ptstrName;

        // Get Column Names and Lengths
        hr = m_pFileio->GetColumnName( icol, &ptstrName );
        if (FAILED( hr ))
            return ResultFromScode( E_FAIL );

        // Store the Column Name in the Heap
        if (cchFree)
            {
            cbName = lstrlen( ptstrName );
            if (cbName)
                cchWide = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, ptstrName, (int) cbName, lpwstr, cchFree );
            else
                cchWide = 0;
            pcolinfo->pwszName = lpwstr;
            lpwstr += cchWide;

            cchFree -= cchWide;
            if (cchFree)
                {
                *lpwstr++ = 0;
                cchFree--;
                }
            else
                *(lpwstr - 1) = 0;
            }
        else
            pcolinfo->pwszName = NULL;

        // Get DataTypes and Precision
        hr = m_pFileio->GetDataTypes( icol,
                        &swCSVType, // CSV data type
                        &cbColDef,  // Precision
                        &m_rgfIsSigned[icol]    // Signed or Unsigned
                        );
        if (FAILED( hr ))
            return ResultFromScode( E_FAIL );

        // We use ordinal numbers for our columns
        pcolinfo->columnid.eKind          = DBKIND_GUID_PROPID;
        pcolinfo->columnid.uGuid.guid     = GUID_NULL;
        pcolinfo->columnid.uName.ulPropid = icol;

        // Determine the OLE DB type, for binding.
        hr = GetInternalTypeFromCSVType( swCSVType,              // in
                                         m_rgfIsSigned[icol],    // in
                                         &dwdbtype );            // out, DBTYPE to show client
        if (FAILED( hr ))
            return ResultFromScode( E_FAIL );

        // Check for overflow of size.
        m_rgcbLen[icol] = cbColDef;
        m_rgcbLen[icol] = MIN( m_rgcbLen[icol], MAX_BIND_LEN );

        pcolinfo->iOrdinal= icol;
        pcolinfo->wType= (DBTYPE) dwdbtype;
        pcolinfo->pTypeInfo     = NULL;
        pcolinfo->ulColumnSize  = m_rgcbLen[icol];
        pcolinfo->bPrecision    = (BYTE)cbColDef;
        pcolinfo->bScale        = 0;
        pcolinfo->dwFlags       = 0;

        // Is it a fixed length datatype
        if(pcolinfo->wType != DBTYPE_STR)
        pcolinfo->dwFlags |= DBCOLUMNFLAGS_ISFIXEDLENGTH;

        // We do support nulls
        pcolinfo->dwFlags |= DBCOLUMNFLAGS_ISNULLABLE;

        //We should always be able to write to the column
        pcolinfo->dwFlags |= DBCOLUMNFLAGS_WRITE;

        // Set the offset from the start of the row,
        // for this column, then advance past.
        dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
        m_rgdwDataOffsets[icol] = dwOffset;
        dwOffset += offsetof( COLUMNDATA, bData ) + m_rgcbLen[icol];
        }

    m_cbRowSize = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
    m_cbHeapUsed = MAX_HEAP_SIZE - 2*cchFree;

    // Decommit unused memory in our column-name buffer.
    // We know it will never grow beyond what it is now.
    // Decommit all pages past where we currently are.
    BYTE  *pDiscardPage;
    ULONG  ulSize;
    pDiscardPage = (BYTE *) ROUND_UP( lpwstr, g_dwPageSize );
    ulSize       = MAX_HEAP_SIZE - (pDiscardPage - m_pbHeap);
    if (ulSize > 0)
        VirtualFree( pDiscardPage, ulSize, MEM_DECOMMIT );
    assert( '\0' == (*lpwstr = '\0'));  // We shouldn't generate a mem fault.

    return ResultFromScode( S_OK );

}

// CRowset::Rebind --------------------------------------------
//
// @mfunc Establishes data offsets and type for the file
// routines to place the data
//
// @rdesc HRESULT
//      @flag S_OK | Bindings set fine
//      @flag E_FAIL | Bindings could not be set
//
HRESULT CRowset::Rebind
    (
    BYTE *pBase                 //@parm IN | Base pointer for Data Area
    )
{
    UWORD       icol;
    COLUMNDATA  *pColumn;

    // Bind result set columns.
    // Use established types and sizes and offsets.
    // Bind to internal row buffer, area beginning with 'pRowBuff'.
    //
    // For each column, bind it's data as well as length.
    // Offsets point to start of COLUMNDATA structure.

    assert( pBase );
    assert( m_rgdwDataOffsets );
    assert( m_rgcbLen );

    // Optimize by not doing it over again.
    if (pBase != m_pLastBindBase)
        {
        m_pLastBindBase = 0;

        for (icol=1; icol <= m_cCols; icol++)
            {
            pColumn = (COLUMNDATA *) (pBase + m_rgdwDataOffsets[icol]);
            if (FAILED( m_pFileio->SetColumnBind( icol, pColumn, m_rgcbLen[icol] )))
                return ResultFromScode( E_FAIL );
            }

        // Remember in case we bind to same place again.
        m_pLastBindBase = pBase;
        }

    return ResultFromScode( S_OK );
}

// CRowset::GetRowBuff--------------------------------------------
//
// @mfunc Shorthand way to get the address of a row buffer.
// Later we may extend this so that it can span several
// non-contiguous blocks of memory.
//
// @rdesc Pointer to the buffer.
//
ROWBUFF* CRowset::GetRowBuff
    (
    ULONG iRow                  //@parm IN | Row to get address of.
    )
{
    // This assumes iRow is valid...
    // How can we calculate maximum row?
    // Should we return NULL if it's out of range?
    assert( m_rgbRowData );
    assert( m_cbRowSize );
    assert( iRow > 0 );
    return (ROWBUFF *) (m_rgbRowData + m_cbRowSize*iRow);
}

//////////////////////////////////////////////////////////////////////////////
//      Helper functions    Helper functions    Helper functions
//////////////////////////////////////////////////////////////////////////////

// GetInternalTypeFromCSVType ------------------------------------------------
//
// @func This function returns the default OLE DB representation
// for a data type
//
HRESULT GetInternalTypeFromCSVType
    (
    SWORD  swDataType,  //@parm IN | Data Type
    BOOL   fIsSigned,   //@parm IN | Signed or Unsigned
    DWORD  *pdwdbType   //@parm OUT | OLE DB type for DBColumnInfo
    )
{
    static struct {
        SWORD   swCSVType;
        BOOL    fIsSigned;          // 1=signed, 0=unsigned
        BOOL    fSignedDistinction; // 1=signed matters
        DWORD   dwdbType;
    } TypeTable[] =
        {
            {TYPE_CHAR,         0, 0, DBTYPE_STR },
            {TYPE_SLONG,        1, 1, DBTYPE_I4  },
        };

    for (int j=0; j < NUMELEM( TypeTable ); j++)
        {
        if (swDataType == TypeTable[j].swCSVType        // type match
        && (!TypeTable[j].fSignedDistinction            // care about sign?
            || fIsSigned == TypeTable[j].fIsSigned))    // sign match
            {
            assert( pdwdbType );
            *pdwdbType     = TypeTable[j].dwdbType;
            return ResultFromScode( S_OK );
            }
        }

    // Should never get here, since we supposedly have
    // a table of all possible CSV types.
    assert( !"Unmatched CSV Type." );
    return ResultFromScode( E_FAIL );
}


ROWSET.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module ROWSET.H | CRowset base object and contained interface
// definitions
//
//
#ifndef _ROWSET_H_
#define _ROWSET_H_

#include "fileio.h"
#include "bitarray.h"
#include "extbuff.h"
#include "hashtbl.h"

#include "dbsess.h"

// Forward declarations ------------------------------------------------------

class CImpIRowset;
class CImpIRowsetChange;
class CImpIColumnsInfo;
class CImpIAccessor;
class CImpIRowsetInfo;

typedef CImpIRowset*PIMPIROWSET;
typedef CImpIRowsetChange*PIMPIROWSETCHANGE;
typedef CImpIColumnsInfo*PIMPICOLUMNSINFO;
typedef CImpIAccessor *PIMPIACCESSOR;
typedef CImpIRowsetInfo*PIMPIROWSETINFO;

// General Helper Function
HRESULT GetInternalTypeFromCSVType(SWORD swDataType, BOOL fIsSigned, DWORD* pdwdbType);

// Classes -------------------------------------------------------------------

//----------------------------------------------------------------------------
// @class CRowset | Rowset object. Containing class for all interfaces on the Rowset
// Object
//
class CRowset : public IUnknown//@base public | IUnknown
{
//Contained interfaces are friends
friend class CImpIColumnsInfo;
friend class CImpIRowset;
friend class CImpIRowsetChange;
friend class CImpIAccessor;
friend class CImpIRowsetInfo;

private: //@access private
//@cmember Builds DBCOLINFO structures
HRESULT GatherColumnInfo(void);
//@cmember Creates Helper Classes
HRESULT CreateHelperFunctions(void);
//@cmember Returns the Buffer Pointer for the specified row
ROWBUFF* GetRowBuff(ULONG iRow);
//@cmember Establishes the data area bindings
HRESULT Rebind(BYTE* pBase);

protected: //@access protected
//@cmember File Manipulation Class
CFileIO*m_pFileio;
//@cmember Count of Columns in Result Set
ULONGm_cCols;
//@cmember Pointer to Info Array Heap (heap of column name strings)
BYTE*m_pbHeap;
//@cmember ColumnInfo array
DBCOLUMNINFO*m_rgdbcolinfo;
//@cmember how many bytes of the column names heap is in use
ULONG           m_cbHeapUsed;
//@cmember column offsets in SampProv's buffer
ULONG*m_rgdwDataOffsets;
//@cmember if type is signed
BOOL*m_rgfIsSigned;
//@cmember length of buffer slots for column data
SDWORD*m_rgcbLen;
//@cmember array of accessor ptrs
LPEXTBUFFER     m_pextbufferAccessor;
//@cmember internal buffer structure
PLSTSLOT        m_pIBuffer;
//@cmember bit array to mark active rows
LPBITARRAYm_prowbitsIBuffer;
//@cmember size of row in the buffer
ULONG           m_cbRowSize;
//@cmember points to the first buffered row
BYTE*m_rgbRowData;
//@cmember index of the first available rowbuffer
ULONG           m_irowMin;
//@cmember current # of rows in the buffer
ULONG           m_cRows;
//@cmember position in the resultset
ULONG           m_irowFilePos;
//@cmember Start of the rowset
ULONGm_irowLastFilePos;
//@cmember status word for the entire cursor
UDWORD          m_dwStatus;
//@cmember remember last binding location
BYTE*m_pLastBindBase;
//@cmember RefCount of all outstanding row handles
ULONG           m_ulRowRefCount;
        //@member Utility object to manage properties
        PCUTILPROP                      m_pUtilProp;

// Interface and OLE Variables

//@cmember Reference count
ULONGm_cRef;
//@cmember Controlling IUnknown
LPUNKNOWNm_pUnkOuter;
//@cmember Contained IColumnsInfo
PIMPICOLUMNSINFOm_pIColumnsInfo;
//@cmember Contained IRowset
PIMPIROWSETm_pIRowset;
//@cmember Contained IRowsetChange
PIMPIROWSETCHANGEm_pIRowsetChange;
//@cmember Contained IAccessor
PIMPIACCESSORm_pIAccessor;
//@cmember Contained IRowsetInfo
PIMPIROWSETINFOm_pIRowsetInfo;

public: //@access public
//@cmember Constructor
 CRowset(LPUNKNOWN);
//@cmember Destructor
~CRowset(void);

//@cmember Intitialization Routine
BOOL FInit(CFileIO*);

//Object's base IUnknown
//@cmember Request an Interface
STDMETHODIMPQueryInterface(REFIID, LPVOID *);
//@cmember Increments the Reference count
STDMETHODIMP_(ULONG)AddRef(void);
//@cmember Decrements the Reference count
STDMETHODIMP_(ULONG)Release(void);

//Back pointer to a creator object. Used in IRowssetInfo::GetSpecification
PCDBSESSION                     m_pCreator;
};

typedef CRowset *PCROWSET;

//----------------------------------------------------------------------------
// @class CImpIRowset | Contained IRowset class
//
class CImpIRowset : public IRowset//@base public | IRowset
{
private: //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CRowset)

public: //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CRowset, CImpIRowset);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IRowset members
//@cmember GetData Method
STDMETHODIMPGetData(HROW, HACCESSOR, void*);
//@cmember GetNextRows Method
STDMETHODIMPGetNextRows(HCHAPTER, LONG, LONG, ULONG*, HROW**);
        //cmember ReleaseRows method
STDMETHODIMPReleaseRows(ULONG, const HROW[], DBROWOPTIONS[], ULONG[], DBROWSTATUS[]);
        //@cmember RestartPosition method
STDMETHODIMPRestartPosition(HCHAPTER);
        //@cmember AddRefRows method
        STDMETHODIMP            AddRefRows(ULONG, const HROW[], ULONG[], DBROWSTATUS[]);

};

//----------------------------------------------------------------------------
// @class CImpIRowsetChange | Contained IRowsetChange class
//
class CImpIRowsetChange : public IRowsetChange//@base public | IRowsetChange
{
private: //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CRowset)

public: //@access public

 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CRowset, CImpIRowsetChange);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IRowsetChange members
//@cmember SetData Method
    STDMETHODIMPSetData(HROW, HACCESSOR, void*);
    STDMETHODIMPDeleteRows(HCHAPTER, ULONG, const HROW[], DBROWSTATUS[]);
STDMETHODIMP    InsertRow
    (
    HCHAPTERhReserved,
HACCESSORhAccessor,
void*pData,
HROW*phRow
);
};

//----------------------------------------------------------------------------
// @class CImpIColumnsInfo | Contained IColumnsInfo class
//
class CImpIColumnsInfo : public IColumnsInfo //@base public | IColumnsInfo
{
private: //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CRowset)

public: //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CRowset, CImpIColumnsInfo);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//IColumnsInfo members
//@cmember GetColumnInfo method
    STDMETHODIMPGetColumnInfo(ULONG*, DBCOLUMNINFO**, WCHAR**);
//@cmember MapColumnIDs
STDMETHODIMPMapColumnIDs(ULONG, const DBID[], ULONG[]);
};

//----------------------------------------------------------------------------
// @class CImpIAccessor | Contained IAccessor class
//
class CImpIAccessor : public IAccessor //@base public | IAccessor
{
private: //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CRowset)

public: //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CRowset, CImpIAccessor);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//@cmember Increment Reference count on accessor
STDMETHODIMPAddRefAccessor(HACCESSOR hAccessor, ULONG* pcRefCounts);
//@cmember CreateAccessor Method
    STDMETHODIMPCreateAccessor(DBACCESSORFLAGS, ULONG, const DBBINDING[], ULONG, HACCESSOR*, DBBINDSTATUS[]);
//@cmember GetBindings Method
STDMETHODIMPGetBindings(HACCESSOR, DBACCESSORFLAGS*, ULONG*, DBBINDING**);
//@cmember ReleaseAccessor Method
STDMETHODIMPReleaseAccessor(HACCESSOR, ULONG*);

};

//----------------------------------------------------------------------------
// @class CImpIRowsetInfo | Contained IRowsetInfo class
//
class CImpIRowsetInfo : public IRowsetInfo //@base public | IAccessor
{
private: //@access private
DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CRowset)

public: //@access public
 DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CRowset, CImpIRowsetInfo);
DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE

//@cmember GetReferencedRowset
STDMETHODIMPGetReferencedRowset
(
ULONGiOrdinal,
REFIIDrrid,
IUnknown**ppReferencedRowset
);

//@cmember GetProperties
STDMETHODIMPGetProperties
    (
    const ULONGcPropertySets,
    const DBPROPIDSETrgPropertySets[],
    ULONG*              pcProperties,
    DBPROPSET**prgProperties
    );

//@cmember GetSpecification Method
STDMETHODIMPGetSpecification(REFIID, IUnknown**);
};

#endif


SAMPPROV.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module SAMPPROV.H | Main include file
//
//
#ifndef _SAMPPROV_H_
#define _SAMPPROV_H_

// Defines -------------------------------------------------------------------
#ifdef DBINITCONSTANTS
#define GLOBAL_(type, name, val) type name = val
#else
#define GLOBAL_(type, name, val)extern type name
#endif

#define OBJECT_CONSTRUCTED()InterlockedIncrement(&g_cObj);
#define OBJECT_DESTRUCTED()InterlockedDecrement(&g_cObj);

#ifndef  MAX
# define MIN(a,b)  ( (a) < (b) ? (a) : (b) )
# define MAX(a,b)  ( (a) > (b) ? (a) : (b) )
#endif

#ifndef NUMELEM
# define NUMELEM(x) (sizeof(x)/sizeof(*x))
#endif

#define MAX_HEAP_SIZE          128000
#define MAX_TOTAL_ROWBUFF_SIZE (10*1024*1024)// Max for all row buffers.
#define MAX_IBUFFER_SIZE       2000000
#define MAX_BIND_LEN      (MAX_IBUFFER_SIZE/10)

#define STAT_ENDOFCURSOR            0x00000100// for forward-only means fully materialized

//-----------------------------------------------------------------------------
// Memory alignment
//-----------------------------------------------------------------------------

//++
// Useful rounding macros.
// Rounding amount is always a power of two.
//--
#define ROUND_DOWN( Size, Amount )  ((DWORD)(Size) & ~((Amount) - 1))
#define ROUND_UP(   Size, Amount ) (((DWORD)(Size) +  ((Amount) - 1)) & ~((Amount) - 1))

//++
// These macros are for aligment of ColumnData within the internal row buffer.
// COLUMN_ALIGN takes a ptr where you think data ought to go,
// and rounds up to the next appropriate address boundary.
//
// Rule of thumb is "natural" boundary, i.e. 4-byte member should be
// aligned on address that is multiple of 4.
//
// Most everything should be aligned to 32-bit boundary.
// But doubles should be aligned to 64-bit boundary, so let's play it safe.
// Also have __int64.
//--

// make these symbols a little easier to type\read
#if   defined(_M_IX86)
#     define X86
#elif defined(_M_MRX000)
#     define MIPS
#elif defined(_M_ALPHA)
#     define ALPHA
#elif defined(_M_PPC)
#     define PPC
#else
#     error: Unknown machine type: Not one of {X86,MIPS,ALPHA,PPC}.
#endif

#ifdef MIPS
# define COLUMN_ALIGNVAL 8
#elif  defined(ALPHA)
# define COLUMN_ALIGNVAL 8
#else
# define COLUMN_ALIGNVAL 8// venerable 80x86
#endif

// Typedefs ------------------------------------------------------------------
typedef VOID**LPLPVOID;

typedef long int            SDWORD;
typedef short int           SWORD;
typedef unsigned long int   UDWORD;
typedef unsigned short int  UWORD;
typedef signed long SLONG;
typedef unsigned longULONG;
typedef unsigned shortUSHORT;

// Accessor Structure
typedef struct tagACCESSOR
{
    DBACCESSORFLAGS dwAccessorFlags;
LONGcRef;
ULONGcBindings;
DBBINDINGrgBindings[1];
} ACCESSOR, *PACCESSOR;

//-----------------------------------------------------------------------------
// Macros for interface classes -- IUnknown methods and constructor\destructor
// Use these in the class definition for an interface.
// The code is defined in the header (definition) part because
// it is easier to do so here, and easier to use.
//
//
// @func void | DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA |
// Macro to define default IUnknown member data.
//
// @parm BaseClass | BaseClass | . | Class that is the OLE object.
//
// @ex Example usage|
//
//class CImpISomething : public ISomething {
//private:
//DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(CSomethingBase)
//public:
//DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(CSomethingBase, CImpISomething)
//DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE
//... implementation-specific methods ...
//};
//
#define DEFINE_DEFAULT_IUNKNOWN_MEMBER_DATA(BaseClass)\
DEBUGCODE(ULONG m_cRef);\
BaseClass*m_pObj;\
LPUNKNOWNm_pUnkOuter;

// @func void | DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR |
// Macro to define default IUnknown Ctor/Dtor.
//
// @parm BaseClass | BaseClass      | . | Class that is the OLE object.
//
// @parm Any class | InterfaceClass | . | OLE interface class that this is part of.

#define DEFINE_DEFAULT_IUNKNOWN_CTOR_DTOR(BaseClass, InterfaceClass)\
InterfaceClass( BaseClass *pObj, IUnknown *pUnkOuter )\
{\
DEBUGCODE(m_cRef = 0L);\
m_pObj= pObj;\
m_pUnkOuter= pUnkOuter;\
}\
~InterfaceClass()\
{\
}

// @func void | DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE |
// Macro to define default IUnknown AddRef/Release.

#define DEFINE_DEFAULT_IUNKNOWN_ADDREF_RELEASE\
STDMETHODIMP_(ULONG)AddRef(void)\
{\
DEBUGCODE( ++m_cRef );\
return m_pUnkOuter->AddRef();\
}\
STDMETHODIMP_(ULONG)Release(void)\
{\
ASSERT( m_cRef > 0 );\
DEBUGCODE( --m_cRef );\
return m_pUnkOuter->Release();\
}\
STDMETHODIMPQueryInterface(REFIID riid, LPVOID *ppv)\
{\
return m_pUnkOuter->QueryInterface(riid, ppv);\
}

// Globals -------------------------------------------------------------------

GLOBAL_(LONG, g_cObj, 0L);// # of outstanding objects
GLOBAL_(LONG, g_cLock, 0L);// # of explicit locks set
GLOBAL_(DWORD, g_cAttachedProcesses, 0L);// # of attached processes
GLOBAL_(DWORD, g_dwPageSize, 0L);// System page size
GLOBAL_(IMalloc *, g_pIMalloc, NULL);// OLE2 task memory allocator
GLOBAL_(HINSTANCE, g_hInstance, 0L);// Instance Handle
GLOBAL_(IDataConvert *, g_pIDataConvert, NULL);// IDataConvert pointer

#endif


UTILPROP.CPP

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module UTILPROP.CPP | Properties utility object implementation
//

//
// Notes - there are two main methods in this module:
//     - CUtilProps::GetPropertyInfo, a helper function for IDBInfo::GetPropertyInfo
//     - CUtilProps::GetProperties, a helper function for IRowsetInfo::GetProperties
//
// Our property implementation is simplified considerably by the fact that we
// only support reading\getting the properties, we do not support
// writing\setting them. This makes sense because we are a simple provider,
// and our rowset object always creates all the interfaces it exposes. In
// other words, there are really no properties the consumer could set.
//
// The implementation is very simple - we keep a global table of the
// properties we support in s_rgprop. We search this table sequentially.
//
// Note that a full-featured provider would probably need to use a more
// sophisticated implementation. We keep the entire GUID for each property in
// our struct, which would become a waste of space if we had a lot more
// properties. Similarly, with large numbers of properties some sort of
// hashing would be better than the sequential search used here.
//

// Includes ------------------------------------------------------------------

#include "headers.h"

// Struct containing the properties we know about. The GUID and string fields are
// initialized in the constructor, because C++ makes it awkward to do so at declaration
// time. So, if you change this table, be sure to make parallel changes in CUtilProp::CUtilProp.
PROPSTRUCT s_rgprop[] =
   {
        /* 0 */ {DBPROP_IAccessor,FLAGS_ROWSETRO,     VT_BOOL, TRUE,   0, NULL},
        /* 1 */ {DBPROP_IColumnsInfo,FLAGS_ROWSETRO,     VT_BOOL, TRUE,   0, NULL},
        /* 2 */ {DBPROP_IRowset,FLAGS_ROWSETRW,     VT_BOOL, TRUE,   0, NULL},
        /* 3 */ {DBPROP_IRowsetChange,FLAGS_ROWSETRW,     VT_BOOL, TRUE,   0, NULL},
        /* 4 */ {DBPROP_IRowsetInfo,FLAGS_ROWSETRO,     VT_BOOL, TRUE,   0, NULL},
        /* 5 */ {DBPROP_DBMSNAME,FLAGS_DATASOURCE,VT_BSTR, FALSE,  0, NULL},
        /* 6 */ {DBPROP_DBMSVER,FLAGS_DATASOURCE,VT_BSTR, FALSE,  0, NULL},
        /* 7 */ {DBPROP_INIT_DATASOURCE,FLAGS_DBINITRW,VT_BSTR, FALSE,  0, NULL}
    };

PROPSTRUCT s_rgDBInitProp[] =
{
        /* 0 */ {DBPROP_INIT_DATASOURCE,FLAGS_DBINITRW,VT_BSTR, FALSE,  0, NULL},
};

PROPSTRUCT s_rgDataSourceInfoProp[] =
   {
        /* 0 */ {DBPROP_DBMSNAME,FLAGS_DATASOURCE,VT_BSTR, FALSE,  0, NULL},
        /* 1 */ {DBPROP_DBMSVER,FLAGS_DATASOURCE,VT_BSTR, FALSE,  0, NULL}
    };

PROPSTRUCT s_rgRowsetProp[] =
   {
        /* 0 */ {DBPROP_IAccessor,FLAGS_ROWSETRO,     VT_BOOL, TRUE,   0, NULL},
        /* 1 */ {DBPROP_IColumnsInfo,FLAGS_ROWSETRW,     VT_BOOL, TRUE,   0, NULL},
        /* 2 */ {DBPROP_IRowset,FLAGS_ROWSETRW,     VT_BOOL, TRUE,   0, NULL},
        /* 3 */ {DBPROP_IRowsetChange,FLAGS_ROWSETRW,     VT_BOOL, TRUE,   0, NULL},
        /* 4 */ {DBPROP_IRowsetInfo,FLAGS_ROWSETRO,     VT_BOOL, TRUE,   0, NULL},
    };

// Number of supported properties per property set
#defineNUMBER_OF_SUPPORTED_PROPERTY_SETS3

#define NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES1
#defineNUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES2
#defineNUMBER_OF_SUPPORTED_ROWSET_PROPERTIES5

#defineNUMBER_OF_SUPPORTED_PROPERTIES\
(NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES +\
NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES +\
NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES)

// Code ----------------------------------------------------------------------

// CUtilProp::CUtilProp ----------------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CUtilProp::CUtilProp
    (
    void
    )
{
    // initialize the members of our global table that we couldn't initialize
    // in the declaration
s_rgprop[0].pwstrDescBuffer,L"IAccessor";
s_rgprop[1].pwstrDescBuffer,L"IColumnsInfo";
s_rgprop[2].pwstrDescBuffer,L"IRowset";
s_rgprop[3].pwstrDescBuffer,L"IRowsetChange";
s_rgprop[4].pwstrDescBuffer,L"IRowsetInfo";

    s_rgprop[5].pwstrVal = L"Sampprov";
s_rgprop[5].pwstrDescBuffer,L"DBMS Name";

    s_rgprop[6].pwstrVal = L"00.99.0000";
s_rgprop[6].pwstrDescBuffer,L"DBMS Version";

s_rgprop[7].pwstrVal = L"";
s_rgprop[7].pwstrDescBuffer,L"Data Source";

s_rgRowsetProp[0].pwstrDescBuffer,L"IAccessor";
s_rgRowsetProp[1].pwstrDescBuffer,L"IColumnsInfo";
s_rgRowsetProp[2].pwstrDescBuffer,L"IRowset";
s_rgRowsetProp[3].pwstrDescBuffer,L"IRowsetChange";
s_rgRowsetProp[4].pwstrDescBuffer,L"IRowsetInfo";

    s_rgDataSourceInfoProp[0].pwstrVal = L"Sampprov";
s_rgDataSourceInfoProp[0].pwstrDescBuffer,L"DBMS Name";

    s_rgDataSourceInfoProp[1].pwstrVal = L"00.99.0000";
s_rgDataSourceInfoProp[1].pwstrDescBuffer,L"DBMS Version";

s_rgDBInitProp[0].pwstrVal,L"";
s_rgDBInitProp[0].pwstrDescBuffer,L"Data Source";

    return;
}

// CUtilProp::~CUtilProp ---------------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CUtilProp:: ~CUtilProp
    (
    void
    )
{
if ( s_rgDBInitProp[0].pwstrVal )
g_pIMalloc->Free( s_rgDBInitProp[0].pwstrVal );

    return;
}

// CUtilProp::GetPropIndex ----------------------------------------------------
//
// @mfunc Returns index of the given property in our global table of properties
//
// @rdesc BOOL
//      @flag TRUE      | found match, copied it to pulIndex out-param
//      @flag FALSE     | no match. In this case, pulIndex has no meaning
//
BOOL CUtilProp::GetPropIndex
    (
DBPROPIDdwPropertyID,   //@parm IN  | PROPID of desired property
    ULONG*pulIndex//@parm OUT | index of desired property if return was TRUE
    )
{
    ULONG cNumberOfProperties;
    assert( pulIndex );

    for (cNumberOfProperties = 0;
cNumberOfProperties < NUMBER_OF_SUPPORTED_PROPERTIES;
cNumberOfProperties++)
    {
        if (dwPropertyID == s_rgprop[cNumberOfProperties].dwPropertyID )
        {
            // found a match
            *pulIndex = cNumberOfProperties;
            return TRUE;
         }
    }
    // found no matches
    return FALSE;
}

// CUtilProp::LoadDBPROPINFO  ----------------------------------------------------
//
// @mfunc Helper for GetPropertyInfo. Loads field of DBPROPINFO structure.
//
// @rdesc BOOL
//      @flag TRUE          | Method succeeded
//      @flag FALSE         | Method failed (couldn't allocate memory)
//
BOOL CUtilProp::LoadDBPROPINFO
    (
    PROPSTRUCT*pPropStruct,
    DBPROPINFO*pPropInfo
    )
{
    // asserts
    assert( pPropStruct );
    assert( pPropInfo );

    // init the variant
    VariantInit( &pPropInfo->vValues );

    // set the easy fields..
    pPropInfo->dwPropertyID= pPropStruct->dwPropertyID;
    pPropInfo->dwFlags= pPropStruct->dwFlags;
    pPropInfo->vtType= pPropStruct->vtType;

    switch (pPropStruct->vtType)
    {
    case VT_BOOL:
V_VT( &pPropInfo->vValues ) = VT_BOOL;
V_BOOL( &pPropInfo->vValues ) = pPropStruct->boolVal;
break;

case VT_I4:
V_VT( &pPropInfo->vValues ) = VT_I4;
V_I4( &pPropInfo->vValues ) = pPropStruct->longVal;
break;

case VT_BSTR:
V_VT( &pPropInfo->vValues ) = VT_BSTR;

V_BSTR( &pPropInfo->vValues ) = SysAllocString( pPropStruct->pwstrVal );

if (NULL == V_BSTR( &pPropInfo->vValues ))
            {
VariantClear( &pPropInfo->vValues );
return FALSE;
            }
break;

default:
assert( !"LoadDBPROPINFO unknown variant type!\n\r" );
break;
       }
    // all went well
    return TRUE;
}

// CUtilProp::LoadDBPROP  ----------------------------------------------------
//
// @mfunc Helper for GetProperties. Loads field of DBPROP structure.
//
// @rdesc BOOL
//      @flag TRUE          | Method succeeded
//      @flag FALSE         | Method failed (couldn't allocate memory)
//
BOOL CUtilProp::LoadDBPROP
    (
    PROPSTRUCT*pPropStruct,
    DBPROP*pPropSupport
    )
{
    // asserts
    assert( pPropStruct );
    assert( pPropSupport );

    // init the variant
    VariantInit( &pPropSupport->vValue );

    // set the easy fields..
    pPropSupport->dwPropertyID  = pPropStruct->dwPropertyID;
    pPropSupport->colid= DB_NULLID;

    // set pPropSupport->vValue based on Variant type
    switch (pPropStruct->vtType)
    {
    case VT_BOOL:
V_VT( &pPropSupport->vValue ) = VT_BOOL;
V_BOOL( &pPropSupport->vValue ) = pPropStruct->boolVal;
break;

    case VT_I4:
    V_VT( &pPropSupport->vValue ) = VT_I4;
V_I4( &pPropSupport->vValue ) = pPropStruct->longVal;
break;

    case VT_BSTR:
    V_VT( &pPropSupport->vValue ) = VT_BSTR;

V_BSTR( &pPropSupport->vValue ) = SysAllocString( pPropStruct->pwstrVal );

        if (NULL == V_BSTR( &pPropSupport->vValue ))
            {
VariantClear( &pPropSupport->vValue );
return FALSE;
            }
break;

default:
assert( !"LoadDBPROP unknown variant type!\n\r" );
break;
    }
    // all went well
    return TRUE;
}

// CUtilProp::GetPropertyInfo  -----------------------------------------
//
// @mfuncReturns information about rowset and data source properties
//supported by the provider
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcPropertyIDSets or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//

STDMETHODIMP CUtilProp::GetPropertyInfo
    (
    ULONGcPropertyIDSets,//@parm IN | # properties
    const DBPROPIDSETrgPropertyIDSets[],//@parm IN | Array of property sets
ULONG*pcPropertyInfoSets,//@parm OUT | # DBPROPSET structures
DBPROPINFOSET**prgPropertyInfoSets,//@parm OUT | DBPROPSET structures property
//| information returned
WCHAR**ppDescBuffer//@parm OUT| Property descriptions
    )
{
    ULONGcNumberOfPropertySets, cNumberOfProperties;
ULONGcCount;
    ULONGcProps = 0;
    DBPROPINFO*pPropInfo;
    DBPROPINFOSET*pPropInfoSet;
    BOOLfRet;

    // asserts
    assert( s_rgprop );

    // check params
    if (!pcPropertyInfoSets || !prgPropertyInfoSets)
        return ResultFromScode( E_INVALIDARG );

    if ((cPropertyIDSets != 0) && !rgPropertyIDSets)
        return ResultFromScode( E_INVALIDARG );

    // if no restriction array, count our properties
    if (cPropertyIDSets == 0)
    {
        cProps = NUMBER_OF_SUPPORTED_PROPERTY_SETS;
    }
    // restriction array specified, so get the count
    else
    {
        if (cPropertyIDSets > NUMBER_OF_SUPPORTED_PROPERTY_SETS)
return ( E_INVALIDARG );
else
cProps = cPropertyIDSets;
    }

    // init out params
    *pcPropertyInfoSets= 0;
    *prgPropertyInfoSets= NULL;

    // use task memory allocater to alloc a DBPROPINFOSET struct
    pPropInfoSet = (DBPROPINFOSET*) g_pIMalloc->Alloc
(cProps *
sizeof( DBPROPINFOSET ));
    if (!pPropInfoSet)
        return ResultFromScode( E_OUTOFMEMORY );

    memset( pPropInfoSet, 0, (cProps * sizeof( DBPROPINFOSET )));

// For each supported Property Set
for (cNumberOfPropertySets=0; cNumberOfPropertySets < cProps; cNumberOfPropertySets++)
{

// If no restrictions return all properties from the three supported property sets
if (cPropertyIDSets == 0)
{
if (cNumberOfPropertySets == 0)
pPropInfoSet[cNumberOfPropertySets].guidPropertySet = DBPROPSET_DBINIT;
else if (cNumberOfPropertySets == 1)
pPropInfoSet[cNumberOfPropertySets].guidPropertySet = DBPROPSET_DATASOURCEINFOALL;
else if (cNumberOfPropertySets == 2)
pPropInfoSet[cNumberOfPropertySets].guidPropertySet = DBPROPSET_ROWSET;
}
else
{
pPropInfoSet[cNumberOfPropertySets].guidPropertySet =
rgPropertyIDSets[cNumberOfPropertySets].guidPropertySet;
}

// Check supported property sets
if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DBINIT))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
}
else if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DATASOURCEINFOALL))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
}
else if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_ROWSET))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
else
{
cNumberOfProperties = 0;
}

if (0 != cNumberOfProperties)
{
    // use task memory allocater to alloc array of DBPROPINFO structs
pPropInfo = (DBPROPINFO*) g_pIMalloc->Alloc
( cNumberOfProperties *
sizeof( DBPROPINFO ));

if (!pPropInfo)
return ResultFromScode( E_OUTOFMEMORY );

memset( pPropInfo, 0,
(cNumberOfProperties * sizeof( DBPROPINFO )));
}

    // for each prop in our table..
for (cCount=0; cCount < cNumberOfProperties; cCount++)
{

// init the Variant right up front
// that way we can VariantClear with no worried (if we need to)
VariantInit( &pPropInfo[cCount].vValues );

// Check supported property sets
if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DBINIT))
{
// load up their DBPROPINFO from our table
fRet = LoadDBPROPINFO( &s_rgDBInitProp[cCount], &pPropInfo[cCount] );
}
else if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DATASOURCEINFOALL))
{
// load up their DBPROPINFO from our table
fRet = LoadDBPROPINFO( &s_rgDataSourceInfoProp[cCount], &pPropInfo[cCount] );
}
else if (IsEqualGUID(pPropInfoSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_ROWSET))
{
// load up their DBPROPINFO from our table
fRet = LoadDBPROPINFO( &s_rgRowsetProp[cCount], &pPropInfo[cCount] );
}

if (!fRet)
{
ULONG ulFor;

// something went wrong
// clear all variants used so far..
for (ulFor = 0; ulFor < cCount; ulFor++)
{
VariantClear( &pPropInfo[ulFor].vValues );
}
// .. delete the pPropInfo array, return failure
g_pIMalloc->Free( pPropInfo );
return ResultFromScode( E_OUTOFMEMORY );
}
pPropInfoSet[cNumberOfPropertySets].rgPropertyInfos[cCount] =
pPropInfo[cCount];
}// for each property
}// for each property set

// set count of properties and property information
    *pcPropertyInfoSets= cProps;
    *prgPropertyInfoSets= pPropInfoSet;

    return ResultFromScode( S_OK );
}

// CUtilProp::GetProperties ----------------------------------------------------
//
// @mfunc Returns current settings of all properties supported by the DSO/rowset
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcProperties or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CUtilProp::GetProperties
    (
ULONGcPropertyIDSets,//@parm IN | # of restiction property IDs
const DBPROPIDSETrgPropertyIDSets[],//@parm IN | restriction guids
ULONG*              pcPropertySets,//@parm OUT | count of properties returned
DBPROPSET**prgPropertySets//@parm OUT | property information returned
    )
{
    ULONGcNumberOfPropertySets, cNumberOfProperties;
ULONGcCount;
    ULONGcProps = 0;
    DBPROP*pProp;
    DBPROPSET*pPropSet;
    BOOLfRet;

    // asserts
    assert( s_rgprop );

    // check params
    if (!pcPropertySets || !prgPropertySets)
        return ResultFromScode( E_INVALIDARG );

    if ((cPropertyIDSets != 0) && !rgPropertyIDSets)
        return ResultFromScode( E_INVALIDARG );

    // if no restriction array, count our properties
    if (cPropertyIDSets == 0)
    {
        cProps = NUMBER_OF_SUPPORTED_PROPERTY_SETS;
    }
    // restriction array specified, so get the count
    else
    {
        if (cPropertyIDSets > NUMBER_OF_SUPPORTED_PROPERTY_SETS)
return ( E_INVALIDARG );
else
cProps = cPropertyIDSets;
    }

    // init out params
    *pcPropertySets= 0;
    *prgPropertySets= NULL;

    // use task memory allocater to alloc a DBPROPINFOSET struct
    pPropSet = (DBPROPSET*) g_pIMalloc->Alloc
(cProps *
sizeof( DBPROPSET ));
    if (!pPropSet)
        return ResultFromScode( E_OUTOFMEMORY );

    memset( pPropSet, 0, (cProps * sizeof( DBPROPSET )));

// For each supported Property Set
for (cNumberOfPropertySets=0; cNumberOfPropertySets < cProps; cNumberOfPropertySets++)
{

// If no restrictions return all properties from the two supported property sets
if (cPropertyIDSets == 0)
{
if (cNumberOfPropertySets == 0)
pPropSet[cNumberOfPropertySets].guidPropertySet = DBPROPSET_DATASOURCEINFOALL;
else if (cNumberOfPropertySets == 1)
pPropSet[cNumberOfPropertySets].guidPropertySet = DBPROPSET_ROWSET;
}
else
{
pPropSet[cNumberOfPropertySets].guidPropertySet =
rgPropertyIDSets[cNumberOfPropertySets].guidPropertySet;
}

// Check supported property sets
if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DBINIT))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
}
else if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DATASOURCEINFOALL))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
}
else if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_ROWSET))
{
cNumberOfProperties = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
else
{
cNumberOfProperties = 0;
}

if (0 != cNumberOfProperties)
{
    // use task memory allocater to alloc array of DBPROP structs
pProp = (DBPROP*) g_pIMalloc->Alloc
( cNumberOfProperties *
sizeof( DBPROP ));

if (!pProp)
{
g_pIMalloc->Free( pPropSet );
return ResultFromScode( E_OUTOFMEMORY );
}

memset( pProp, 0,
(cNumberOfProperties * sizeof( DBPROP )));
}

    // for each prop in our table..
for (cCount=0; cCount < cNumberOfProperties; cCount++)
{

// init the Variant right up front
// that way we can VariantClear with no worried (if we need to)
VariantInit( &pProp[cCount].vValue );

// Check supported property sets
if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DBINIT))
{
// load up their DBPROP from our table
fRet = LoadDBPROP( &s_rgDBInitProp[cCount], &pProp[cCount] );
}
else if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_DATASOURCEINFOALL))
{
// load up their DBPROP from our table
fRet = LoadDBPROP( &s_rgDataSourceInfoProp[cCount], &pProp[cCount] );
}
else if (IsEqualGUID(pPropSet[cNumberOfPropertySets].guidPropertySet,
DBPROPSET_ROWSET))
{
// load up their DBPROP from our table
fRet = LoadDBPROP( &s_rgRowsetProp[cCount], &pProp[cCount] );
}

if (!fRet)
{
ULONG ulFor;

// something went wrong
// clear all variants used so far..
for (ulFor = 0; ulFor < cCount; ulFor++)
{
VariantClear( &pProp[ulFor].vValue );
}
// .. delete the pProp array, return failure
g_pIMalloc->Free( pProp );
g_pIMalloc->Free( pPropSet );
return ResultFromScode( E_OUTOFMEMORY );
}
}// for each property
pPropSet[cNumberOfPropertySets].rgProperties = pProp;
}// for each property set

// set count of properties and property information
    *pcPropertySets= cProps;
    *prgPropertySets= pPropSet;

    return ResultFromScode( S_OK );
}

// CUtilProp::SetProperties ----------------------------------------------------
//
// @mfunc Set current settings of properties supported by the DSO/rowset
//
// @rdesc HRESULT
//      @flag S_OK          | The method succeeded
//      @flag E_INVALIDARG  | pcProperties or prgPropertyInfo was NULL
//      @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CUtilProp::SetProperties
    (
ULONGcPropertyIDSets,//@parm IN# of DBPROPSET
DBPROPSETrgPropertyIDSets[]//@parm INOUTArray of property sets
)
{
// Quick Exit with 0 PropertyIDSets
if ( cPropertyIDSets == 0 )
return ResultFromScode( S_OK );

    // Check params
    if ((0 != cPropertyIDSets) && (NULL == rgPropertyIDSets))
        return ResultFromScode( E_INVALIDARG );

    if (1 != cPropertyIDSets)
        return ResultFromScode( E_FAIL );

    if (!IsEqualGUID(rgPropertyIDSets[0].guidPropertySet,DBPROPSET_DBINIT))
        return ResultFromScode( E_FAIL );

if (rgPropertyIDSets[0].cProperties == 0)
return ResultFromScode( S_OK );

// If rg.cProperties is not 0 and rg.rgProperties is NULL, this is
// an error.
if (rgPropertyIDSets[0].rgProperties == NULL)
return ResultFromScode(E_INVALIDARG);

switch(rgPropertyIDSets[0].rgProperties->dwPropertyID)
{
case DBPROP_INIT_DATASOURCE:

    // get the directory path string and convert it to ANSI
s_rgDBInitProp[0].pwstrVal = (WCHAR *) g_pIMalloc->Alloc(MAX_PATH);

WideCharToMultiByte( CP_ACP, 0, V_BSTR( &rgPropertyIDSets[0].rgProperties->vValue ), -1,
                 (char *)s_rgDBInitProp[0].pwstrVal, MAX_PATH, NULL, NULL );

return ResultFromScode( S_OK );
}
return ResultFromScode( E_FAIL );
}


UTILPROP.H

//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved.
//
// @doc
//
// @module UTILPROP.H | CUtilProp object definitions
//
//
#ifndef _UTILPROP_H_
#define _UTILPROP_H_

//-----------  structs and #defines -------------------------------------------

// @struct PROPSTRUCT  | simple table used to store property information. Used in
// our read-only implementation of IDBProperties::GetPropertyInfo and IRowsetInfo::GetProperties
typedef struct _tagPROPSTRUCT
    {
    DBPROPIDdwPropertyID;
DBPROPFLAGS dwFlags;
    VARTYPE     vtType;
    BOOL        boolVal;
    SLONG       longVal;
    PWSTR       pwstrVal;
    PWSTRpwstrDescBuffer;
    } PROPSTRUCT;

// flags for IDBProperties::GetPropertyInfo
#define FLAGS_ROWSETRO(DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ)
#define FLAGS_ROWSETRW(DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define FLAGS_DATASOURCE    (DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ)
#define FLAGS_DBINITRW(DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)

// Classes -------------------------------------------------------------------

//----------------------------------------------------------------------------
// @class CUtilProp | Containing class for all interfaces on the UtilProp
// Object
//
class CUtilProp
{
protected: //@access protected
        //@cmember Gets index of entry for a given property in global property table
        BOOL GetPropIndex
        (
       DBPROPIDdwPropertyID,
 ULONG*pulIndex
        );

        //@cmember Loads fields of DBPROPINFO struct. Helper for GetPropertyInfo
        BOOL LoadDBPROPINFO
            (
PROPSTRUCT*pPropStruct,
DBPROPINFO*pPropInfo
            );

        //@cmember Loads fields of DBPROP struct. Helper for GetProperties
        BOOL LoadDBPROP
            (
PROPSTRUCT*pPropStruct,
DBPROP*pPropSupport
            );

public: //@access public
//@cmember Constructor
 CUtilProp(void);
//@cmember Destructor
~CUtilProp(void);

STDMETHODIMP GetProperties
    (
    ULONGcPropertySets,
    const DBPROPIDSETrgPropertySets[],
    ULONG*              pcProperties,
    DBPROPSET**prgProperties
    );

STDMETHODIMP GetPropertyInfo
    (
    ULONGcPropertySets,
    const DBPROPIDSETrgPropertySets[],
ULONG*pcPropertyInfoSets,
DBPROPINFOSET**prgPropertyInfoSets,
WCHAR**ppDescBuffer
    );

STDMETHODIMP CUtilProp::SetProperties
(
ULONGcProperties,
DBPROPSETrgProperties[]
);

};

typedef CUtilProp *PCUTILPROP;

#endif


COMMON.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module COMMON.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////
// Include
//
/////////////////////////////////////////////////////////////////////////////
#include "winmain.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"

/////////////////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////////////////

//Displays values like VALUE as   VALUE , L"VALUE"
#define VALUE_WCHAR(value) value, L#value

typedef struct _TYPEMAP
{
DBTYPEwType;// The sql type value
WCHAR*pwszTypeName;// Name for display
} TYPEMAP;

TYPEMAP rgDBTypes[] =
{
VALUE_WCHAR(DBTYPE_I1),
VALUE_WCHAR(DBTYPE_I2),
VALUE_WCHAR(DBTYPE_I4),
VALUE_WCHAR(DBTYPE_I8),
VALUE_WCHAR(DBTYPE_UI1),
VALUE_WCHAR(DBTYPE_UI2),
VALUE_WCHAR(DBTYPE_UI4),
VALUE_WCHAR(DBTYPE_UI8),
VALUE_WCHAR(DBTYPE_R4),
VALUE_WCHAR(DBTYPE_R8),
VALUE_WCHAR(DBTYPE_CY),
VALUE_WCHAR(DBTYPE_DECIMAL),
VALUE_WCHAR(DBTYPE_NUMERIC),
VALUE_WCHAR(DBTYPE_BOOL),
VALUE_WCHAR(DBTYPE_ERROR),
VALUE_WCHAR(DBTYPE_UDT),
VALUE_WCHAR(DBTYPE_VARIANT),
VALUE_WCHAR(DBTYPE_IDISPATCH),
VALUE_WCHAR(DBTYPE_IUNKNOWN),
VALUE_WCHAR(DBTYPE_GUID),
VALUE_WCHAR(DBTYPE_DATE),
VALUE_WCHAR(DBTYPE_DBDATE),
VALUE_WCHAR(DBTYPE_DBTIME),
VALUE_WCHAR(DBTYPE_DBTIMESTAMP),
VALUE_WCHAR(DBTYPE_BSTR),
VALUE_WCHAR(DBTYPE_STR),
VALUE_WCHAR(DBTYPE_WSTR),
VALUE_WCHAR(DBTYPE_BYTES),
};

/////////////////////////////////////////////////////////////////////////////
// HRESULT ConvertToMBCS
//
/////////////////////////////////////////////////////////////////////////////
HRESULT ConvertToMBCS(WCHAR* pwsz, CHAR* psz, ULONG cbStrLen)
{
ASSERT(pwsz && psz);

//Convert the string to MBCS
INT iResult = WideCharToMultiByte(CP_ACP, 0, pwsz, -1, psz, cbStrLen, NULL, NULL);
return iResult ? S_OK : E_FAIL;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT ConvertToWCHAR
//
/////////////////////////////////////////////////////////////////////////////
HRESULT ConvertToWCHAR(CHAR* psz, WCHAR* pwsz, ULONG cbStrLen)
{
ASSERT(psz && pwsz);

//Convert the string to MBCS
INT iResult = MultiByteToWideChar(CP_ACP, 0, psz, -1, pwsz, cbStrLen);
return iResult ? S_OK : E_FAIL;
}

/////////////////////////////////////////////////////////////////////////////
// WCHAR* wcsDuplicate
//
/////////////////////////////////////////////////////////////////////////////
WCHAR* wcsDuplicate(WCHAR* pwsz)
{
ASSERT(pwsz);
ULONG cLen= wcslen(pwsz);

//Allocate space for the string
WCHAR* pwszBuffer = NULL;
SAFE_ALLOC(pwszBuffer, WCHAR, cLen+1);

//Now copy the string
wcscpy(pwszBuffer, pwsz);

CLEANUP:
return pwszBuffer;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL GetQuotedID
//
/////////////////////////////////////////////////////////////////////////////
BOOL GetQuotedID(
WCHAR* pwszOutBuff,// User's output buffer
WCHAR* pwszInBuff,// User's input buffer
WCHAR* pwszQuote,// QUOTE Character
WCHAR* pwszSeperator    // SEPERATOR Character
)
{
ASSERT(pwszOutBuff && pwszInBuff);

WCHAR pwsz[MAX_NAME_LEN*2];
WCHAR* pwszItr = pwsz;

ULONG cQuoteLen = pwszQuote ? wcslen(pwszQuote) : 0;
ULONG cSepLen= pwszSeperator ? wcslen(pwszSeperator) : 0;

//Put on front delimeter
if(pwszQuote)
wcscpy(pwszItr, pwszQuote);
pwszItr += cQuoteLen;

//Copy from Source to Temp
while(*pwszInBuff!=EOL)
{
//If we have a seperator char, we need to quote both pieces
if(pwszSeperator && wcsncmp(pwszInBuff, pwszSeperator, cSepLen)==0)
{
// "dbo.authors -> "dbo"."authors
wcscat(pwszItr, pwszQuote);//Ending Quote
wcscat(pwszItr, pwszSeperator);//Seperator
wcscat(pwszItr, pwszQuote);//Beginning Quote
pwszItr += cQuoteLen + cSepLen + cQuoteLen;
}
else
{
*pwszItr = *pwszInBuff;//Actual Character
pwszItr++;
}

pwszInBuff++;
*pwszItr = EOL;
}

//Put on the Tail delimeter
if(pwszQuote)
wcscat(pwszItr, pwszQuote);

//Give back to the user
wcscpy(pwszOutBuff, pwsz);
return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL MakeAlphaNumeric
//
/////////////////////////////////////////////////////////////////////////////
BOOL MakeAlphaNumeric(
WCHAR* pwszBuffer,// buffer to translate
WCHAR  wchReplace// New character for unallowed
)
{
ASSERT(pwszBuffer && wchReplace);

while(pwszBuffer[0])
{
// If the character is not a normal alpha numeric value
// replace it with an acceptable one
if(!iswalnum(pwszBuffer[0]))
pwszBuffer[0] = wchReplace;

pwszBuffer++;
}

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG GetCreateParams
//
/////////////////////////////////////////////////////////////////////////////
ULONG GetCreateParams(WCHAR* pwszCreateParam)
{
ASSERT(pwszCreateParam);

ULONGulType = 0;

if(wcsstr(pwszCreateParam, L"precision") || wcsstr(pwszCreateParam, L"PRECISION"))
ulType |= CP_PRECISION;
if(wcsstr(pwszCreateParam, L"scale") || wcsstr(pwszCreateParam, L"SCALE"))
ulType |= CP_SCALE;
if(wcsstr(pwszCreateParam, L"length") || wcsstr(pwszCreateParam, L"LENGTH"))
ulType |= CP_LENGTH;
if(wcsstr(pwszCreateParam, L"max length") || wcsstr(pwszCreateParam, L"MAX LENGTH"))
ulType |= CP_LENGTH;

return ulType;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL IsVariableType
//
/////////////////////////////////////////////////////////////////////////////
BOOL IsVariableType(DBTYPE wType)
{
//According to OLEDB Spec Appendix A (Variable-Length Data Types)
switch(wType)
{
case DBTYPE_STR:
case DBTYPE_WSTR:
case DBTYPE_BYTES:
return TRUE;
}
return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL IsFixedType
//
/////////////////////////////////////////////////////////////////////////////
BOOL IsFixedType(DBTYPE wType)
{
return !IsVariableType(wType);
}

/////////////////////////////////////////////////////////////////////////////
// BOOL IsNumericType
//
/////////////////////////////////////////////////////////////////////////////
BOOL IsNumericType(DBTYPE wType)
{
//According to OLEDB Spec Appendix A (Variable-Length Data Types)
switch(wType)
{
case DBTYPE_I1:
case DBTYPE_I2:
case DBTYPE_I4:
case DBTYPE_I8:
case DBTYPE_UI1:
case DBTYPE_UI2:
case DBTYPE_UI4:
case DBTYPE_UI8:
case DBTYPE_R4:
case DBTYPE_R8:
case DBTYPE_CY:
case DBTYPE_DECIMAL:
case DBTYPE_NUMERIC:
return TRUE;
}
return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// WCHAR* GetDBTypeName
//
/////////////////////////////////////////////////////////////////////////////
WCHAR* GetDBTypeName(DBTYPE wType)
{
// Do a table look-up on the type
for(ULONG i=0; i<NUMELE(rgDBTypes); i++)
{
if(wType == rgDBTypes[i].wType)
return rgDBTypes[i].pwszTypeName;
}

ASSERT(!"Type not in rgSqlType list");
return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL GetPromotedType
//
/////////////////////////////////////////////////////////////////////////////
BOOL GetPromotedType(DBTYPE* pwType)
{
ASSERT(pwType);

switch(*pwType)
{

// Integer family
case DBTYPE_BOOL:
*pwType = DBTYPE_I1;
break;

case DBTYPE_I1:
case DBTYPE_UI1:
*pwType = DBTYPE_I2;
break;

case DBTYPE_I2:
case DBTYPE_UI2:
*pwType = DBTYPE_I4;
break;

case DBTYPE_I4:
case DBTYPE_UI4:
case DBTYPE_CY:
*pwType = DBTYPE_I8;
break;

case DBTYPE_I8:
case DBTYPE_UI8:
*pwType = DBTYPE_NUMERIC;
break;

// Floating-point type family.  FLOAT and DOUBLE actually
// have the same precision, so do a mutual promotion (that
// is, allow FLOAT to become DOUBLE and DOUBLE to become
// FLOAT) before going to CHAR.
//
case DBTYPE_R4:
*pwType = DBTYPE_R8;
break;

// Fixed-point exact numerics. Ordering of the two types
// is unimportant--for our purposes they have exactly the
// same semantics.
//
case DBTYPE_NUMERIC:
*pwType = DBTYPE_DECIMAL;
break;

case DBTYPE_DECIMAL:
*pwType = DBTYPE_R8;
break;

// Binary types
case DBTYPE_BYTES:
*pwType = DBTYPE_STR;
break;

// Date/Time family
case DBTYPE_DATE:
case DBTYPE_DBTIME:
case DBTYPE_DBDATE:
*pwType = DBTYPE_DBTIMESTAMP;
break;

case DBTYPE_R8:
case DBTYPE_DBTIMESTAMP:
*pwType = DBTYPE_BYTES;
break;

//we don't support any other types
default:
ASSERT(0);
return FALSE;
}

return TRUE;
}

///////////////////////////////////////////////////////////////
// BOOL FreeBindings
//
///////////////////////////////////////////////////////////////
BOOL FreeBindings(ULONG cBindings, DBBINDING* rgBindings)
{
//Need to walk the array and free any pObject structures
for(ULONG i=0; i<cBindings; i++)
SAFE_FREE(rgBindings[i].pObject);

//Now we can free the outer struct
SAFE_FREE(rgBindings);
return TRUE;
}

///////////////////////////////////////////////////////////////
// Static Strings Messages
//
///////////////////////////////////////////////////////////////

extern WCHAR wsz_ERROR[]= L"OLEDB TableCopy - Error";
extern WCHAR wsz_SUCCESS[]= L"OLEDB TableCopy - Success";
extern WCHAR wsz_WARNING[]= L"OLEDB TableCopy - Warning";
extern WCHAR wsz_CANCEL[]= L"OLEDB TableCopy - Cancel";
extern WCHAR wsz_ERRORINFO[]= L"OLEDB TableCopy - IErrorInfo";

//Copying Status
extern WCHAR wsz_COPYING[] = L"Copying records";
extern WCHAR wsz_COPIED_RECORDS[]= L"%lu records copied";
extern WCHAR wsz_COPY_SUCCESS[]= L"Copy succeeded, %lu records copied!";
extern WCHAR wsz_COPY_FAILURE[]= L"Copy failed!";
extern WCHAR wsz_CANCEL_OP[]= L"Do you want to cancel?";

//Tables
extern WCHAR wsz_CREATE_TABLE[]= L"CREATE TABLE ";
extern WCHAR wsz_DROP_TABLE_[]= L"DROP TABLE %s ";
extern WCHAR wsz_ASK_DROP_TABLE_[] = L"Table %s already exists.  Would you like to drop it?";
extern WCHAR wsz_SAME_TABLE_NAME[] = L"Target table name must be different on the same DataSource";

//Indexes
extern WCHAR wsz_CREATE_INDEX_[]= L"CREATE%sINDEX ";
extern WCHAR wsz_UNIQUE_INDEX[]= L" UNIQUE ";
extern WCHAR wsz_INDEX_DESC[] = L" DESC ";
extern WCHAR wsz_INDEX_FAILED_[]= L"INDEX %s failed to be created.  Would you like to Continue?";

//Columns
extern WCHAR wsz_TYPE_FORMAT_[]= L"%-20s \t%-20s \t[%s]";
extern WCHAR wsz_COLUMN_FORMAT_[]= L"%-20s \t[%s]";
extern WCHAR wsz_NO_TYPE_MATCH_[]= L"Target Data type not found for %s type";
extern WCHAR wsz_NO_TYPE_FOUND_[]= L"Source Data type not found for %s type";

extern WCHAR wsz_CONNECT_STRING_[]= L"DSN=%s, UID=%s, PWD=%s, (%s %s)";
extern WCHAR wsz_NOT_CONNECTED[]= L"Press Connect to establish a connection";
extern WCHAR wsz_FROM_TABLE_DSN_[]= L"From: %s in %s";
extern WCHAR wsz_TO_TABLE_DSN_[]= L"To: %s in %s";

extern WCHAR wsz_PROVIDER_STRING_[]= L"%s %s %s";
extern WCHAR wsz_NO_PROVIDER[]= L"";

extern WCHAR wsz_INVALID_VALUE_[]= L"Invalid Value '%s' specified.  Please specify a value >= %lu and <= %lu.";
extern WCHAR wsz_READONLY_DATASOURCE_[]= L"Datasource %s is read only and not valid as a target.";

//Query
extern WCHAR wsz_SHOW_SQL_TITLE[]= L"OLEDB TableCopy - ICommand::Execute Query";
extern WCHAR wsz_SHOW_SQL_[]= L"DSN = %s\n\nSQL = %s";
extern WCHAR wsz_SELECT[]= L" SELECT ";
extern WCHAR wsz_FROM[]= L" FROM ";
extern WCHAR wsz_BOGUS_WHERE[]= L" WHERE 0 = 1";
extern WCHAR wsz_INSERT_INTO[]= L"INSERT INTO ";
extern WCHAR wsz_VALUES_CLAUSE[]= L" ) VALUES ( ";
extern WCHAR wsz_PARAM[]= L"?";
extern WCHAR wsz_PRIMARY_KEY[]= L" PRIMARY KEY";

//General String Values
extern WCHAR wsz_COMMA[]= L", ";
extern WCHAR wsz_LPAREN[]= L" ( ";
extern WCHAR wsz_RPAREN[]= L" ) ";
extern WCHAR wsz_SPACE[]= L" ";


COMMON.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module COMMON.H
//
//-----------------------------------------------------------------------------

#ifndef _COMMON_H_
#define _COMMON_H_

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include "winmain.h"

#include "oledb.h"
#include "oledberr.h"

#include "error.h"
#include "tablecopy.h"

///////////////////////////////////////////////////////////////
// Defines
//
///////////////////////////////////////////////////////////////
#define NUMELE(rgEle) (sizeof(rgEle) / sizeof(rgEle[0]))

//FindLeaks
#ifdef _DEBUG
#define DISP_FILE_LINETRACE("File '%s', Line '%lu'\n", __FILE__, __LINE__)
#else
#define DISP_FILE_LINE
#endif //_DEBUG

//IMalloc Wrappers
#define SAFE_ALLOC(pv, type, cb){ pv = (type*)CoTaskMemAlloc((cb)*sizeof(type)); DISP_FILE_LINE; CHECK_MEMORY(pv); }
#define SAFE_REALLOC(pv, type, cb){ pv = (type*)CoTaskMemRealloc(pv, (cb)*sizeof(type)); DISP_FILE_LINE; CHECK_MEMORY(pv); }
#define SAFE_SYSALLOC(pv, bstr){ pv = SysAllocString(bstr); DISP_FILE_LINE; CHECK_MEMORY(pv); }

#define SAFE_FREE(pv){ CoTaskMemFree(pv); pv = NULL; }
#define SAFE_SYSFREE(bstr){ SysFreeString(bstr); bstr = NULL;}

//IUnknown->Release Wrapper
#define SAFE_RELEASE(pv)if(pv) { (pv)->Release(); pv = NULL; }

//Test macros
#define TEST(exp){ if(FAILED(exp)) { ASSERT(!#exp); } }
#define TESTC(exp){ if(FAILED(exp)) { goto CLEANUP;  } }

#define XTEST(exp){ if(FAILED(exp)) { DisplayErrorRecords(NULL); } }

#define XTESTC(exp){ if(FAILED(exp)) { DisplayErrorRecords(NULL); goto CLEANUP; } }
#define CHECK_ERRORS(exp, riid, pz){ if(FAILED(exp))  { DisplayAllErrors(NULL, exp, riid, pz); goto CLEANUP; } }

///////////////////////////////////////////////////////////////////
// Accessor / Binding
//
///////////////////////////////////////////////////////////////////
#define ADJUST_SIZE(Size, MaxSize)  ((Size) = min((Size), (MaxSize)))

//STATUS helpers, for locating obStatus offsets in the bindings
#define STATUS_IS_BOUND(Binding)    ( Binding.dwPart & DBPART_STATUS )
#define BINDING_STATUS(Binding, pv) (*(ULONG*)((BYTE*)pv + Binding.obStatus))

//LENGTH helpers, for locating obLength offsets in the bindings
#define LENGTH_IS_BOUND(Binding)    ( Binding.dwPart & DBPART_LENGTH )
#define BINDING_LENGTH(Binding, pv) (*(ULONG*)((BYTE*)pv + Binding.obLength))

//VALUE helpers, for locating obValue offsets in the bindings
#define VALUE_IS_BOUND(Binding)     ( Binding.dwPart & DBPART_VALUE )
#define BINDING_VALUE(Binding, pv)  (*(ULONG*)((BYTE*)pv + Binding.obValue ))

//ROUNDUP on all platforms pointers must be aligned properly
#define ROUNDUP_AMOUNT8
#define ROUNDUP_(size,amount)(((ULONG)(size)+((amount)-1))&~((amount)-1))
#define ROUNDUP(size)ROUNDUP_(size, ROUNDUP_AMOUNT)

FreeBindings(ULONG cBindings, DBBINDING* rgBindings);

////////////////////////////////////////////////////////////////////////////
// DBTYPE functions
//
////////////////////////////////////////////////////////////////////////////
BOOL IsFixedType(DBTYPE wType);
BOOL IsVariableType(DBTYPE wType);
BOOL IsNumericType(DBTYPE wType);

WCHAR* GetDBTypeName(DBTYPE wType);
WCHAR* GetSQLTypeName(DBTYPE wType);

BOOL GetPromotedType(DBTYPE* pwType);
BOOL MakeAlphaNumeric(WCHAR* pwszBuffer,WCHAR  wchReplace);

////////////////////////////////////////////////////////////////////////////
// OLEDB General Helper functions
//
////////////////////////////////////////////////////////////////////////////
ULONG GetCreateParams(WCHAR* pwszCreateParam);
BOOL GetQuotedID(WCHAR* pwszOutBuff, WCHAR* pwszInBuff, WCHAR* pwszQuote, WCHAR* pwszSeperator);

HRESULT ConvertToMBCS(WCHAR* pwsz, CHAR* psz, ULONG cbStrLen);
HRESULT ConvertToWCHAR(CHAR* psz, WCHAR* pwsz, ULONG cbStrLen);
WCHAR* wcsDuplicate(WCHAR* pwsz);

///////////////////////////////////////////////////////////////
// Static Strings Messages
//
///////////////////////////////////////////////////////////////

//General Statss
extern WCHAR wsz_ERROR[];
extern WCHAR wsz_SUCCESS[];
extern WCHAR wsz_WARNING[];
extern WCHAR wsz_CANCEL[];
extern WCHAR wsz_ERRORINFO[];

//Copying Status
extern WCHAR wsz_COPYING[];
extern WCHAR wsz_COPIED_RECORDS[];
extern WCHAR wsz_COPY_SUCCESS[];
extern WCHAR wsz_COPY_FAILURE[];
extern WCHAR wsz_CANCEL_OP[];

//Tables
extern WCHAR wsz_CREATE_TABLE[];
extern WCHAR wsz_DROP_TABLE_[];
extern WCHAR wsz_ASK_DROP_TABLE_[];
extern WCHAR wsz_SAME_TABLE_NAME[];

//Indexes
extern WCHAR wsz_CREATE_INDEX_[];
extern WCHAR wsz_UNIQUE_INDEX[];
extern WCHAR wsz_INDEX_DESC[];
extern WCHAR wsz_INDEX_FAILED_[];

//Columns
extern WCHAR wsz_TYPE_FORMAT_[];
extern WCHAR wsz_COLUMN_FORMAT_[];
extern WCHAR wsz_NO_TYPE_MATCH_[];
extern WCHAR wsz_NO_TYPE_FOUND_[];

extern WCHAR wsz_CONNECT_STRING_[];
extern WCHAR wsz_NOT_CONNECTED[];
extern WCHAR wsz_FROM_TABLE_DSN_[];
extern WCHAR wsz_TO_TABLE_DSN_[];

extern WCHAR wsz_PROVIDER_STRING_[];
extern WCHAR wsz_NO_PROVIDER[];

extern WCHAR wsz_INVALID_VALUE_[];
extern WCHAR wsz_READONLY_DATASOURCE_[];

//Query
extern WCHAR wsz_SHOW_SQL_TITLE[];
extern WCHAR wsz_SHOW_SQL_[];
extern WCHAR wsz_SELECT[];
extern WCHAR wsz_FROM[];
extern WCHAR wsz_BOGUS_WHERE[];
extern WCHAR wsz_INSERT_INTO[];
extern WCHAR wsz_VALUES_CLAUSE[];
extern WCHAR wsz_PARAM[];
extern WCHAR wsz_PRIMARY_KEY[];

//General String Values
extern WCHAR wsz_COMMA[];
extern WCHAR wsz_LPAREN[];
extern WCHAR wsz_RPAREN[];
extern WCHAR wsz_SPACE[];

#define EOLL'\0'
#define SPACEL' '
#define UNDERSCOREL'_'

#endif //_COMMON_H_


DATASOURCE.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module DATASOURCE.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "WinMain.h"
#include "Common.h"
#include "DataSource.h"
#include "msdasql.h"//CLSID_MSDASQL provider guid

/////////////////////////////////////////////////////////////////
// CDataSource::CDataSource
//
/////////////////////////////////////////////////////////////////
CDataSource::CDataSource()
{
m_pIDBInitialize= NULL;
m_pIDBCreateCommand= NULL;
m_pICommandText= NULL;

m_pwszCatalog= NULL;
m_pwszDataSource= NULL;
m_pwszDBMS= NULL;
m_pwszDBMSVer= NULL;
m_pwszUserName= NULL;
m_pwszPassword= NULL;

m_pwszProvider= NULL;
m_pwszProviderVer= NULL;
m_pwszProviderOLEDBVer= NULL;

m_fReadOnly= FALSE;
m_fPrimaryKeysSupported = FALSE;
m_fMultipleParamSets    = FALSE;

m_fConnected = FALSE;
}

/////////////////////////////////////////////////////////////////
// CDataSource::~CDataSource
//
/////////////////////////////////////////////////////////////////
CDataSource::~CDataSource()
{
Disconnect();
}

/////////////////////////////////////////////////////////////////
// BOOL CDataSource::IsConnected
//
/////////////////////////////////////////////////////////////////
BOOL CDataSource::IsConnected()
{
return m_fConnected;
}

/////////////////////////////////////////////////////////////////
// BOOL CDataSource::IsSimilar
//
/////////////////////////////////////////////////////////////////
BOOL CDataSource::IsSimilar(CDataSource* pCDataSource)
{
ASSERT(pCDataSource);

//Must be Connected to compare
if(!IsConnected() || !pCDataSource->IsConnected())
return FALSE;

ASSERT(m_pwszDBMS && pCDataSource->m_pwszDBMS);
ASSERT(m_pwszDBMSVer && pCDataSource->m_pwszDBMSVer);

//Similiar: Must be the same DBMS and Version
if(wcscmp(m_pwszDBMS, pCDataSource->m_pwszDBMS)==0 &&
wcscmp(m_pwszDBMSVer, pCDataSource->m_pwszDBMSVer)==0)
return TRUE;

return FALSE;
}

/////////////////////////////////////////////////////////////////
// BOOL CDataSource::IsEqual
//
/////////////////////////////////////////////////////////////////
BOOL CDataSource::IsEqual(CDataSource* pCDataSource)
{
ASSERT(pCDataSource);

//Must be Connected to compare
if(!IsConnected() || !pCDataSource->IsConnected())
return FALSE;

ASSERT(m_pwszCatalog && pCDataSource->m_pwszCatalog);
ASSERT(m_pwszDBMS && pCDataSource->m_pwszDBMS);
ASSERT(m_pwszDBMSVer && pCDataSource->m_pwszDBMSVer);

//Equal:
//Must have same Catalog, DBMS, Version
//Note, Don't have to have the same DataSource Name (an access
//database can have multiple names for the same file)
if(wcscmp(m_pwszCatalog, pCDataSource->m_pwszCatalog)==0 &&
wcscmp(m_pwszDBMS, pCDataSource->m_pwszDBMS)==0 &&
wcscmp(m_pwszDBMSVer, pCDataSource->m_pwszDBMSVer)==0)
return TRUE;

return FALSE;
}

/////////////////////////////////////////////////////////////////
// BOOL CDataSource::Disconnect
//
/////////////////////////////////////////////////////////////////
BOOL CDataSource::Disconnect()
{
m_fConnected = FALSE;

SAFE_FREE(m_pwszCatalog);
SAFE_FREE(m_pwszDataSource);
SAFE_FREE(m_pwszDBMS);
SAFE_FREE(m_pwszDBMSVer);
SAFE_FREE(m_pwszUserName);
SAFE_FREE(m_pwszPassword);

SAFE_FREE(m_pwszProvider);
SAFE_FREE(m_pwszProviderVer);
SAFE_FREE(m_pwszProviderOLEDBVer);

SAFE_RELEASE(m_pIDBInitialize);
SAFE_RELEASE(m_pIDBCreateCommand);
SAFE_RELEASE(m_pICommandText);

return TRUE;
}

/////////////////////////////////////////////////////////////////
// BOOL CDataSource::Connect
//
/////////////////////////////////////////////////////////////////
BOOL CDataSource::Connect(HWND hDlg)
{
ASSERT(hDlg);

HRESULThr;
ULONG               cPropSets = 0;
DBPROPSET*rgPropSets = NULL;
IDBProperties*pIDBProperties = NULL;
IDBCreateSession*   pIDBCreateSession = NULL;

//If there are current connections, close them
Disconnect();

//Get the IDBInitalize interface
hr = CoCreateInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (void**)&m_pIDBInitialize);
CHECK_ERRORS(hr, IID_IDBInitialize, L"CoCreateInstance Failed for CLSID_MSDASQL!");

//Create the Init Properties structs
//Set DBPROP_INIT_HWND / DBPROP_INIT_PROMPT
TESTC(hr = SetupInitProps(hDlg, &cPropSets, &rgPropSets));
XTESTC(hr = m_pIDBInitialize->QueryInterface(IID_IDBProperties, (void **)&pIDBProperties));

//Set the DataSource Properties
XTESTC(hr = pIDBProperties->SetProperties(cPropSets, rgPropSets));

//Initailize
//The user might have canceled the connect, don't display extended error info
TESTC(hr = m_pIDBInitialize->Initialize());

//Get the Session Object
XTESTC(hr = m_pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void **)&pIDBCreateSession));

//Create the SessionObject
XTESTC(hr = pIDBCreateSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**)&m_pIDBCreateCommand));

//Get the CommandText object
XTESTC(hr = m_pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&m_pICommandText));

//Now get the connection properties
TESTC(hr = GetConnectionProps());

//We are now successfully connected
m_fConnected = TRUE;

CLEANUP:
FreeProperties(cPropSets, rgPropSets);

//Release Interfaces
SAFE_RELEASE(pIDBProperties);
SAFE_RELEASE(pIDBCreateSession);
return (hr==S_OK);
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT CDataSource::SetupInitProps
//
// Set up the initialization properties to make a connection to DSO
/////////////////////////////////////////////////////////////////////////////
HRESULT CDataSource::SetupInitProps(HWND hWnd, ULONG* pcPropSets, DBPROPSET** prgPropSets)
{
ASSERT(pcPropSets && prgPropSets);
HRESULT hr = S_OK;

//Allocate outer PropSet struct
ULONG cPropSets = 1;
DBPROPSET* rgPropSets = NULL;
SAFE_ALLOC(rgPropSets, DBPROPSET, cPropSets);

//Allocate inner properties
rgPropSets[0].guidPropertySet = DBPROPSET_DBINIT;
rgPropSets[0].cProperties  = 2;
SAFE_ALLOC(rgPropSets[0].rgProperties, DBPROP, rgPropSets[0].cProperties);

// window handle to display dialog boxes to get the required input
rgPropSets[0].rgProperties[0].dwPropertyID= DBPROP_INIT_HWND;
rgPropSets[0].rgProperties[0].dwOptions= DBPROPOPTIONS_REQUIRED;
rgPropSets[0].rgProperties[0].dwStatus= DBPROPSTATUS_OK;
rgPropSets[0].rgProperties[0].colid= DB_NULLID;
rgPropSets[0].rgProperties[0].vValue.vt = VT_I4;
rgPropSets[0].rgProperties[0].vValue.lVal = (LONG)hWnd;

// prompt level
rgPropSets[0].rgProperties[1].dwPropertyID= DBPROP_INIT_PROMPT;
rgPropSets[0].rgProperties[1].dwOptions= DBPROPOPTIONS_REQUIRED;
rgPropSets[0].rgProperties[1].dwStatus= DBPROPSTATUS_OK;
rgPropSets[0].rgProperties[1].colid= DB_NULLID;
 rgPropSets[0].rgProperties[1].vValue.vt = VT_I2;
rgPropSets[0].rgProperties[1].vValue.iVal = DBPROMPT_COMPLETE;

//Assign pointers
*pcPropSets = cPropSets;
*prgPropSets = rgPropSets;

CLEANUP:
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT CDataSource::GetConnectionProps
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CDataSource::GetConnectionProps()
{
HRESULT hr;
DWORD dwSqlSupport = 0;

//Should have disconnected first
ASSERT(m_pwszDataSource == NULL);
ASSERT(m_pwszDBMS == NULL);
ASSERT(m_pwszDBMSVer == NULL);
ASSERT(m_pwszUserName == NULL);
ASSERT(m_pwszPassword == NULL);

//DBPROP_CURRENTCATALOG
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_CURRENTCATALOG,
DBPROPSET_DATASOURCE, &m_pwszCatalog));

//DBPROP_DATASOURCENAME
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_DATASOURCENAME,
DBPROPSET_DATASOURCEINFO, &m_pwszDataSource));

//DBPROP_DBMSNAME
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_DBMSNAME,
DBPROPSET_DATASOURCEINFO, &m_pwszDBMS));

//DBPROP_DBMSVER
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_DBMSVER,
DBPROPSET_DATASOURCEINFO, &m_pwszDBMSVer));

//DBPROP_USERNAME
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_USERNAME,
DBPROPSET_DATASOURCEINFO, &m_pwszUserName));

//DBPROP_DATASOURCEREADONLY
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_DATASOURCEREADONLY,
DBPROPSET_DATASOURCEINFO, &m_fReadOnly));

//DBPROP_PROVIDERNAME
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_PROVIDERNAME,
DBPROPSET_DATASOURCEINFO, &m_pwszProvider));

//DBPROP_PROVIDEROLEDBVER
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_PROVIDEROLEDBVER,
DBPROPSET_DATASOURCEINFO, &m_pwszProviderVer));

//DBPROP_PROVIDERVER
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_PROVIDERVER,
DBPROPSET_DATASOURCEINFO, &m_pwszProviderOLEDBVer));

//DBPROP_MULTIPLEPARAMSETS
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_MULTIPLEPARAMSETS,
DBPROPSET_DATASOURCEINFO, &m_fMultipleParamSets));

//DBPROP_SQLSUPPORT
TESTC(hr = GetProperty(m_pIDBInitialize, DBPROP_SQLSUPPORT,
DBPROPSET_DATASOURCEINFO, &dwSqlSupport));

//Are PrimaryKeys supported
m_fPrimaryKeysSupported = dwSqlSupport & DBPROPVAL_SQL_ANSI89_IEF;

CLEANUP:
return hr;
}


DATASOURCE.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module DATASOURCE.H
//
//-----------------------------------------------------------------------------
#ifndef _DATASOURCE_H_
#define _DATASOURCE_H_

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include "Property.h"

/////////////////////////////////////////////////////////////////
// CDataSource class
//
/////////////////////////////////////////////////////////////////
class CDataSource
{
public:
//Constructors
CDataSource();
virtual ~CDataSource();

//Members
virtual BOOL IsConnected();
virtual BOOL IsEqual(CDataSource* pCDataSource);
virtual BOOL IsSimilar(CDataSource* pCDataSource);

virtual BOOL Connect(HWND hDlg);
virtual BOOL Disconnect();

virtual HRESULT GetConnectionProps();
virtual HRESULT SetupInitProps(HWND hWnd, ULONG* pcPropSets, DBPROPSET** prgPropSets);

//Data
IDBInitialize*m_pIDBInitialize;//DataSource interface
IDBCreateCommand*m_pIDBCreateCommand;//Session interface
ICommandText*m_pICommandText;//Command interface

//DataSource info
WCHAR*m_pwszCatalog;
WCHAR*m_pwszDataSource;
WCHAR*m_pwszDBMS;
WCHAR*m_pwszDBMSVer;
WCHAR*m_pwszUserName;
WCHAR*m_pwszPassword;

//Provider info
WCHAR*m_pwszProvider;
WCHAR*m_pwszProviderVer;
WCHAR*m_pwszProviderOLEDBVer;

BOOLm_fReadOnly;
BOOLm_fPrimaryKeysSupported;
BOOLm_fMultipleParamSets;

BOOLm_fConnected;
};

#endif//_DATASOURCE_H_


ERROR.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright 1996 - 1998 Microsoft Corporation.
//
// @doc
//
// @module ERROR.CPP
//
//-----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////
// Includes
//
////////////////////////////////////////////////////////////////////////

#define OLEDBVER 0x0200

#include "WinMain.h"
#include "Error.h"

#include <olectl.h>  // IConnectionPoints interface

////////////////////////////////////////////////////////////////////////
// Defines
//
////////////////////////////////////////////////////////////////////////

//Displays values like VALUE as   VALUE , L"VALUE"
#define VALUE_WCHAR(value) value, L#value

typedef struct _INTERFACEMAP
{
const IID*pIID;// The sql type value
WCHAR*pwszInterface;// Name for display
} INTERFACEMAP;

INTERFACEMAP rgInterfaceMap[] =
{
//IUnknown
& VALUE_WCHAR(IID_IUnknown),

//TEnumerator
& VALUE_WCHAR(IID_IParseDisplayName),
& VALUE_WCHAR(IID_ISourcesRowset),

//TDataSource
& VALUE_WCHAR(IID_IDBInitialize),
& VALUE_WCHAR(IID_IDBProperties),
& VALUE_WCHAR(IID_IDBCreateSession),
& VALUE_WCHAR(IID_IDBInfo),
& VALUE_WCHAR(IID_IPersist),
& VALUE_WCHAR(IID_IDBDataSourceAdmin),
& VALUE_WCHAR(IID_IPersistFile),
& VALUE_WCHAR(IID_ISupportErrorInfo),

//TSession
& VALUE_WCHAR(IID_IGetDataSource),
& VALUE_WCHAR(IID_IOpenRowset),
& VALUE_WCHAR(IID_ISessionProperties),
& VALUE_WCHAR(IID_IDBCreateCommand),
& VALUE_WCHAR(IID_IDBSchemaRowset),
& VALUE_WCHAR(IID_IIndexDefinition),
& VALUE_WCHAR(IID_ITableDefinition),
& VALUE_WCHAR(IID_ITransactionJoin),
& VALUE_WCHAR(IID_ITransactionLocal),
& VALUE_WCHAR(IID_ITransactionObject),

//TCommand
& VALUE_WCHAR(IID_IAccessor),
& VALUE_WCHAR(IID_IColumnsInfo),
& VALUE_WCHAR(IID_ICommand),
& VALUE_WCHAR(IID_ICommandProperties),
& VALUE_WCHAR(IID_ICommandText),
& VALUE_WCHAR(IID_IConvertType),
& VALUE_WCHAR(IID_IColumnsRowset),
& VALUE_WCHAR(IID_ICommandPrepare),
& VALUE_WCHAR(IID_ICommandWithParameters),

//TRowset
& VALUE_WCHAR(IID_IRowset),
& VALUE_WCHAR(IID_IRowsetInfo),
& VALUE_WCHAR(IID_IColumnsRowset),
& VALUE_WCHAR(IID_IConnectionPointContainer),
& VALUE_WCHAR(IID_IRowsetChange),
& VALUE_WCHAR(IID_IRowsetIdentity),
& VALUE_WCHAR(IID_IRowsetLocate),
& VALUE_WCHAR(IID_IRowsetResynch),
& VALUE_WCHAR(IID_IRowsetScroll),
& VALUE_WCHAR(IID_IRowsetUpdate),

//TIndex
& VALUE_WCHAR(IID_IRowsetIndex),

//TError
& VALUE_WCHAR(IID_IErrorInfo),
& VALUE_WCHAR(IID_IErrorRecords),
& VALUE_WCHAR(IID_ISQLErrorInfo),
};

////////////////////////////////////////////////////////////////////////
// WCHAR* GetInterfaceName
//
////////////////////////////////////////////////////////////////////////
WCHAR* GetInterfaceName(REFIID riid)
{
for(ULONG i=0; i<NUMELE(rgInterfaceMap); i++)
{
if(riid == *(rgInterfaceMap[i].pIID))
return rgInterfaceMap[i].pwszInterface;
}

//Otherwise just return IUnknown
return rgInterfaceMap[0].pwszInterface;
}

////////////////////////////////////////////////////////////////////////
// ERRORMAP
//
////////////////////////////////////////////////////////////////////////
typedef struct _ERRORMAP
{
HRESULThr;// HRESULT
WCHAR*pwszError;// Name
} ERRORMAP;

ERRORMAP rgErrorMap[] =
{
 VALUE_WCHAR(NULL),

 //System Errors
 VALUE_WCHAR(E_FAIL),
 VALUE_WCHAR(E_INVALIDARG),
 VALUE_WCHAR(E_OUTOFMEMORY),
 VALUE_WCHAR(E_NOINTERFACE),
 VALUE_WCHAR(REGDB_E_CLASSNOTREG),
 VALUE_WCHAR(CLASS_E_NOAGGREGATION),

 //OLE DB Errors
     VALUE_WCHAR(DB_E_BADACCESSORHANDLE),
     VALUE_WCHAR(DB_E_ROWLIMITEXCEEDED),
     VALUE_WCHAR(DB_E_READONLYACCESSOR),
     VALUE_WCHAR(DB_E_SCHEMAVIOLATION),
     VALUE_WCHAR(DB_E_BADROWHANDLE),
     VALUE_WCHAR(DB_E_OBJECTOPEN),
     VALUE_WCHAR(DB_E_CANTCONVERTVALUE),
     VALUE_WCHAR(DB_E_BADBINDINFO),
     VALUE_WCHAR(DB_SEC_E_PERMISSIONDENIED),
     VALUE_WCHAR(DB_E_NOTAREFERENCECOLUMN),
     VALUE_WCHAR(DB_E_NOCOMMAND),
     VALUE_WCHAR(DB_E_BADBOOKMARK),
     VALUE_WCHAR(DB_E_BADLOCKMODE),
     VALUE_WCHAR(DB_E_PARAMNOTOPTIONAL),
     VALUE_WCHAR(DB_E_BADCOLUMNID),
     VALUE_WCHAR(DB_E_BADRATIO),
     VALUE_WCHAR(DB_E_ERRORSINCOMMAND),
     VALUE_WCHAR(DB_E_CANTCANCEL),
     VALUE_WCHAR(DB_E_DIALECTNOTSUPPORTED),
     VALUE_WCHAR(DB_E_DUPLICATEDATASOURCE),
     VALUE_WCHAR(DB_E_CANNOTRESTART),
     VALUE_WCHAR(DB_E_NOTFOUND),
     VALUE_WCHAR(DB_E_CANNOTFREE),
     VALUE_WCHAR(DB_E_NEWLYINSERTED),
     VALUE_WCHAR(DB_E_UNSUPPORTEDCONVERSION),
     VALUE_WCHAR(DB_E_BADSTARTPOSITION),
     VALUE_WCHAR(DB_E_NOTREENTRANT),
     VALUE_WCHAR(DB_E_ERRORSOCCURRED),
     VALUE_WCHAR(DB_E_NOAGGREGATION),
     VALUE_WCHAR(DB_E_DELETEDROW),
     VALUE_WCHAR(DB_E_CANTFETCHBACKWARDS),
     VALUE_WCHAR(DB_E_ROWSNOTRELEASED),
     VALUE_WCHAR(DB_E_BADSTORAGEFLAG),
     VALUE_WCHAR(DB_E_BADSTATUSVALUE),
     VALUE_WCHAR(DB_E_CANTSCROLLBACKWARDS),
     VALUE_WCHAR(DB_E_MULTIPLESTATEMENTS),
     VALUE_WCHAR(DB_E_INTEGRITYVIOLATION),
     VALUE_WCHAR(DB_E_ABORTLIMITREACHED),
     VALUE_WCHAR(DB_E_ROWSETINCOMMAND),
     VALUE_WCHAR(DB_E_DUPLICATEINDEXID),
     VALUE_WCHAR(DB_E_NOINDEX),
     VALUE_WCHAR(DB_E_INDEXINUSE),
     VALUE_WCHAR(DB_E_NOTABLE),
     VALUE_WCHAR(DB_E_CONCURRENCYVIOLATION),
     VALUE_WCHAR(DB_E_BADCOPY),
     VALUE_WCHAR(DB_E_BADPRECISION),
     VALUE_WCHAR(DB_E_BADSCALE),
     VALUE_WCHAR(DB_E_BADID),
     VALUE_WCHAR(DB_E_BADTYPE),
     VALUE_WCHAR(DB_E_DUPLICATECOLUMNID),
     VALUE_WCHAR(DB_E_DUPLICATETABLEID),
     VALUE_WCHAR(DB_E_TABLEINUSE),
     VALUE_WCHAR(DB_E_NOLOCALE),
     VALUE_WCHAR(DB_E_BADRECORDNUM),
     VALUE_WCHAR(DB_E_BOOKMARKSKIPPED),
     VALUE_WCHAR(DB_E_BADPROPERTYVALUE),
     VALUE_WCHAR(DB_E_INVALID),
     VALUE_WCHAR(DB_E_BADACCESSORFLAGS),
     VALUE_WCHAR(DB_E_BADSTORAGEFLAGS),
     VALUE_WCHAR(DB_E_BYREFACCESSORNOTSUPPORTED),
     VALUE_WCHAR(DB_E_NULLACCESSORNOTSUPPORTED),
     VALUE_WCHAR(DB_E_NOTPREPARED),
     VALUE_WCHAR(DB_E_BADACCESSORTYPE),
     VALUE_WCHAR(DB_E_WRITEONLYACCESSOR),
     VALUE_WCHAR(DB_SEC_E_AUTH_FAILED),
     VALUE_WCHAR(DB_E_CANCELED),
     VALUE_WCHAR(DB_E_BADSOURCEHANDLE),
     VALUE_WCHAR(DB_E_PARAMUNAVAILABLE),
     VALUE_WCHAR(DB_E_ALREADYINITIALIZED),
     VALUE_WCHAR(DB_E_NOTSUPPORTED),
     VALUE_WCHAR(DB_E_MAXPENDCHANGESEXCEEDED),
     VALUE_WCHAR(DB_E_BADORDINAL),
     VALUE_WCHAR(DB_E_PENDINGCHANGES),
     VALUE_WCHAR(DB_E_DATAOVERFLOW),
     VALUE_WCHAR(DB_E_BADHRESULT),
     VALUE_WCHAR(DB_E_BADLOOKUPID),
     VALUE_WCHAR(DB_E_BADDYNAMICERRORID),
     VALUE_WCHAR(DB_E_PENDINGINSERT),
     VALUE_WCHAR(DB_E_BADCONVERTFLAG),
     VALUE_WCHAR(DB_S_ROWLIMITEXCEEDED),
     VALUE_WCHAR(DB_S_COLUMNTYPEMISMATCH),
     VALUE_WCHAR(DB_S_TYPEINFOOVERRIDDEN),
     VALUE_WCHAR(DB_S_BOOKMARKSKIPPED),
     VALUE_WCHAR(DB_S_ENDOFROWSET),
     VALUE_WCHAR(DB_S_COMMANDREEXECUTED),
     VALUE_WCHAR(DB_S_BUFFERFULL),
     VALUE_WCHAR(DB_S_NORESULT),
     VALUE_WCHAR(DB_S_CANTRELEASE),
     VALUE_WCHAR(DB_S_DIALECTIGNORED),
     VALUE_WCHAR(DB_S_UNWANTEDPHASE),
     VALUE_WCHAR(DB_S_UNWANTEDREASON),
     VALUE_WCHAR(DB_S_COLUMNSCHANGED),
     VALUE_WCHAR(DB_S_ERRORSRETURNED),
     VALUE_WCHAR(DB_S_BADROWHANDLE),
     VALUE_WCHAR(DB_S_DELETEDROW),
     VALUE_WCHAR(DB_S_STOPLIMITREACHED),
     VALUE_WCHAR(DB_S_LOCKUPGRADED),
     VALUE_WCHAR(DB_S_PROPERTIESCHANGED),
     VALUE_WCHAR(DB_S_ERRORSOCCURRED),
     VALUE_WCHAR(DB_S_PARAMUNAVAILABLE),
     VALUE_WCHAR(DB_S_MULTIPLECHANGES),
};

////////////////////////////////////////////////////////////////////////
// WCHAR* GetErrorName
//
////////////////////////////////////////////////////////////////////////
WCHAR* GetErrorName(HRESULT hr)
{
for(ULONG i=0; i<NUMELE(rgErrorMap); i++)
{
if(hr == rgErrorMap[i].hr)
return rgErrorMap[i].pwszError;
}

//Otherwise just return Unknown
return rgErrorMap[0].pwszError;
}

////////////////////////////////////////////////////////////////////////
// HRESULT GetErrorRecords
//
// Get the error message generated by an OLE DB object
/////////////////////////////////////////////////////////////////////////////
HRESULT GetErrorRecords(ULONG* pcRecords, IErrorRecords** ppIErrorRecords)
{
ASSERT(pcRecords && ppIErrorRecords);
HRESULT hr;

//NULL output params
*pcRecords = 0;
*ppIErrorRecords = NULL;

ISupportErrorInfo* pISupportErrorInfo = NULL;
IErrorInfo* pIErrorInfo = NULL;

//See if this interface supports ErrorInfo
//If not there is no reason to display any error
if((hr = GetErrorInfo(0, &pIErrorInfo))==S_OK && pIErrorInfo)
{
TESTC(hr = pIErrorInfo->QueryInterface(IID_IErrorRecords, (void**)ppIErrorRecords));
TESTC(hr = (*ppIErrorRecords)->GetRecordCount(pcRecords));
}

CLEANUP:
SAFE_RELEASE(pISupportErrorInfo);
SAFE_RELEASE(pIErrorInfo);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT GetErrorInfo
//
// Get the error message generated by an OLE DB object
/////////////////////////////////////////////////////////////////////////////
HRESULT GetErrorInfo(BSTR* pBstr)
{
ASSERT(pBstr);
HRESULT hr;

ULONG cRecords = 0;
IErrorRecords* pIErrorRecords = NULL;

//Try to display Extened ErrorInfo
if((hr = GetErrorRecords(&cRecords, &pIErrorRecords))==S_OK)
{
//GetErrorInfo, just for the first error, (ie: 0)
TESTC(hr = GetErrorInfo(0, pIErrorRecords, pBstr));
}

CLEANUP:
SAFE_RELEASE(pIErrorRecords);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT GetErrorInfo
//
// Get the error message generated by an OLE DB object
/////////////////////////////////////////////////////////////////////////////
HRESULT GetErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords, BSTR* pBstr)
{
ASSERT(pBstr);
HRESULT hr = S_OK;

IErrorInfo* pIErrorInfo = NULL;
LCID lcid = GetSystemDefaultLCID();

//Get the Error Records
if(pIErrorRecords)
{
//If there is ErrorInfo, GetSQLInfo for the desired record
TESTC(hr = pIErrorRecords->GetErrorInfo(iRecord,lcid,&pIErrorInfo));

//If there was a CustomErrorObject
if(pIErrorInfo)
TESTC(hr = pIErrorInfo->GetDescription(pBstr));
}

CLEANUP:
SAFE_RELEASE(pIErrorInfo);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT GetSqlErrorInfo
//
// Get the error message generated by an OLE DB object
/////////////////////////////////////////////////////////////////////////////
HRESULT GetSqlErrorInfo(BSTR* pBstr)
{
ASSERT(pBstr);
HRESULT hr;

ULONG cRecords = 0;
IErrorRecords* pIErrorRecords = NULL;

//Get the Error Records
if((hr = GetErrorRecords(&cRecords, &pIErrorRecords))==S_OK)
{
//Deletegate to SqlErrorInfo
//Just get the first record
TESTC(hr = GetSqlErrorInfo(0, pIErrorRecords, pBstr));
}

CLEANUP:
SAFE_RELEASE(pIErrorRecords);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT GetSqlErrorInfo
//
// Get the error message generated by an OLE DB object
/////////////////////////////////////////////////////////////////////////////
HRESULT GetSqlErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords, BSTR* pBstr)
{
ASSERT(pBstr);
HRESULT hr = S_OK;

ISQLErrorInfo* pISQLErrorInfo = NULL;
LONG lNativeError = 0;

//Get the Error Records
if(pIErrorRecords)
{
//If there is ErrorInfo, GetSQLInfo for the desired record
TESTC(hr = pIErrorRecords->GetCustomErrorObject(iRecord, IID_ISQLErrorInfo, (IUnknown**)&pISQLErrorInfo));

//If there was a CustomErrorObject
if(pISQLErrorInfo)
hr = pISQLErrorInfo->GetSQLInfo(pBstr, &lNativeError);
}

CLEANUP:
SAFE_RELEASE(pISQLErrorInfo);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT DisplayAllErrors
//
/////////////////////////////////////////////////////////////////////////////
HRESULT DisplayAllErrors(HWND hWnd, HRESULT Actualhr, REFIID riid, WCHAR* pwsz)
{
HRESULT hr = S_OK;

ULONG cRecords = 0;
IErrorRecords* pIErrorRecords = NULL;

//Try to display Extened ErrorInfo
if((hr = GetErrorRecords(&cRecords, &pIErrorRecords))==S_OK)
{
hr = DisplayErrorRecords(hWnd, cRecords, pIErrorRecords);
}
//If not available, display MSG Box with info
else
{
//display the Error
wMessageBox(hWnd, MB_ICONEXCLAMATION | MB_OK, wsz_ERRORINFO,
L"Interface: %s\nResult: %x = %s\n\n%s\n",
GetInterfaceName(riid), Actualhr, GetErrorName(Actualhr), pwsz ? pwsz : L"");
}

SAFE_RELEASE(pIErrorRecords);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT DisplayErrorRecords
//
/////////////////////////////////////////////////////////////////////////////
HRESULT DisplayErrorRecords(HWND hWnd)
{
HRESULT hr;

ULONG cRecords = 0;
IErrorRecords* pIErrorRecords = NULL;

//Get the Error Records
if((hr = GetErrorRecords(&cRecords, &pIErrorRecords))==S_OK)
hr = DisplayErrorRecords(hWnd, cRecords, pIErrorRecords);

SAFE_RELEASE(pIErrorRecords);
return hr;
}

////////////////////////////////////////////////////////////////////////
// HRESULT DisplayErrorRecords
//
/////////////////////////////////////////////////////////////////////////////
HRESULT DisplayErrorRecords(HWND hWnd, ULONG cRecords, IErrorRecords* pIErrorRecords)
{
HRESULT hr = S_OK;

IErrorInfo* pIErrorInfo = NULL;
BSTR bstrErrorInfo = NULL;
BSTR bstrSQLInfo = NULL;

LCID lcid = GetSystemDefaultLCID();

//Get the Error Records
if(cRecords && pIErrorRecords)
{
WCHAR wszError[MAX_QUERY_LEN*2];
LONG lNativeError = 0;
ERRORINFO ErrorInfo;

//Loop through the records
for(ULONG i=0; i<cRecords; i++)
{
//GetErrorInfo
TESTC(hr = pIErrorRecords->GetErrorInfo(i,lcid,&pIErrorInfo));

//Get the Description
TESTC(hr = pIErrorInfo->GetDescription(&bstrErrorInfo));

//Get the Basic ErrorInfo
TESTC(hr = pIErrorRecords->GetBasicErrorInfo(i,&ErrorInfo));

//Get the SQL Info
TESTC(hr = GetSqlErrorInfo(i, pIErrorRecords, &bstrSQLInfo));

//Display the Error
if(bstrSQLInfo)
swprintf(wszError,  L"Interface: %s\nResult: %x = %s\n\nIErrorInfo: [%s] %s\n", GetInterfaceName(ErrorInfo.iid), ErrorInfo.hrError, GetErrorName(ErrorInfo.hrError), bstrSQLInfo, bstrErrorInfo);
else
swprintf(wszError,  L"Interface: %s\nResult: %x = %s\n\nIErrorInfo: %s\n", GetInterfaceName(ErrorInfo.iid), ErrorInfo.hrError, GetErrorName(ErrorInfo.hrError), bstrErrorInfo);

wMessageBox(hWnd, MB_ICONEXCLAMATION | MB_OK, wsz_ERRORINFO, wszError);

SAFE_RELEASE(pIErrorInfo);
SAFE_SYSFREE(bstrErrorInfo);
SAFE_SYSFREE(bstrSQLInfo);
}
}

CLEANUP:
SAFE_RELEASE(pIErrorInfo);
SAFE_SYSFREE(bstrErrorInfo);
SAFE_SYSFREE(bstrSQLInfo);
return hr;
}


ERROR.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module ERROR.H
//
//-----------------------------------------------------------------------------
#ifndef _ERROR_H_
#define _ERROR_H_

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include "common.h"

////////////////////////////////////////////////////////////////////////////
// Extened Error Info
//
////////////////////////////////////////////////////////////////////////////
WCHAR* GetErrorName(HRESULT hr);

//Get IErrorInfo
HRESULT GetErrorInfo(BSTR* pBstr);
HRESULT GetErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords, BSTR* pBstr);

//Get ISQLErrorInfo
HRESULT GetSqlErrorInfo(BSTR* pBstr);
HRESULT GetSqlErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords, BSTR* pBstr);

//Get IErrorRecords
HRESULT GetErrorRecords(ULONG* pcRecords, IErrorRecords** ppIErrorRecords);
HRESULT DisplayErrorRecords(HWND hWnd);
HRESULT DisplayErrorRecords(HWND hWnd, ULONG cRecords, IErrorRecords* pIErrorRecords);

HRESULT DisplayAllErrors(HWND hWnd, HRESULT hr, REFIID riid, WCHAR* pwsz);

#endif//_ERROR_H_


LIST.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module LIST.H
//
//-----------------------------------------------------------------------------
#ifndef _LIST_H_
#define _LIST_H_

/////////////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////////////
#include "winmain.h"

/////////////////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////////////////
#define POSITION CNode<TYPE>*

/////////////////////////////////////////////////////////////////////////////
// CNode
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> class CNode
{
public:
// constructors
CNode(TYPE val, CNode* pPrevNode, CNode* pNextNode);

// members
TYPE     m_data;       // element data
CNode*   m_pNextNode;  // next CNode
CNode*   m_pPrevNode;  // prev CNode
};

/////////////////////////////////////////////////////////////////////////////
// CNode::CNode
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> CNode<TYPE>::CNode(TYPE data, CNode<TYPE>* pPrevNode, CNode<TYPE>* pNextNode)
{
  //Constructor
  m_data = data;
  m_pPrevNode = pPrevNode;
  m_pNextNode = pNextNode;
}

/////////////////////////////////////////////////////////////////////////////
// CList
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> class CList
{
  public:

//constructors
CList();
virtual ~CList();

//members

//list modifying operations
virtual POSITIONAddHead(TYPE element);// Add to Head
virtual POSITIONAddTail(TYPE element);// Add to Tail

virtual POSITIONInsertBefore(POSITION position, TYPE newdata);// Add before position
virtual POSITIONInsertAfter(POSITION position, TYPE newdata);// Add after m_pItr

virtual TYPERemoveHead();// Remove from Head
virtual TYPERemoveTail();// Remove from Tail
virtual voidRemoveAt(POSITION position);// RemoveAt position
virtual voidRemoveAll();// Remove all elements

//Seeking operations
virtual POSITIONFind(TYPE element);        // Find element

//Peek operations
virtual TYPEGetHead();// Head element
virtual TYPEGetTail();// Tail element
virtual TYPEGetNext(POSITION position);// Next element
virtual TYPEGetPrev(POSITION position);// Prev element

//informational operations
virtual BOOLIsEmpty();// IsEmpty
virtual ULONGGetCount();// m_ulLengthgth of CList

//data
CNode<TYPE>*  m_pHeadNode;// Head of CList
CNode<TYPE>*  m_pTailNode;// Tail of CList

ULONGm_ulLength;// m_ulLengthgth of CList
};

/////////////////////////////////////////////////////////////////////////////
// CList::CList
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> CList<TYPE>::CList()
{
//constructor
m_pHeadNode = NULL;
m_pTailNode = NULL;
m_ulLength = 0;
}

/////////////////////////////////////////////////////////////////////////////
// CList::~CList
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> CList<TYPE>::~CList()
{
//Remove all elements
RemoveAll();
}

/////////////////////////////////////////////////////////////////////////////
// CList::AddHead
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> POSITION CList<TYPE>::AddHead(TYPE element)
{
//Add to the Head of the CList, (stack)
CNode<TYPE>* pHeadNode = new CNode<TYPE>(element, NULL, m_pHeadNode);
ASSERT(pHeadNode);

//If there was a list hook the head->prev to the new head
if(m_pHeadNode)
  m_pHeadNode->m_pPrevNode = pHeadNode;

//If there isn't a tail element, hook it to the head
if(!m_pTailNode)
  m_pTailNode = pHeadNode;

m_pHeadNode = pHeadNode;
m_ulLength++;
return m_pHeadNode;
}

/////////////////////////////////////////////////////////////////////////////
// CList::AddTail
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> POSITION CList<TYPE>::AddTail(TYPE element)
{
//Add to the m_pTailNode of the CList
CNode<TYPE>* pTailNode = new CNode<TYPE>(element, m_pTailNode, 0);
ASSERT(pTailNode);

//if previously empty
if(!m_pHeadNode)
m_pHeadNode = pTailNode;
else
m_pTailNode->m_pNextNode = pTailNode;

m_pTailNode = pTailNode;
m_ulLength++;
return m_pTailNode;
}

/////////////////////////////////////////////////////////////////////////////
// CList::GetHead
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline TYPE CList<TYPE>::GetHead()
{
//return Head element value
ASSERT(m_pHeadNode);
return m_pHeadNode->m_data;
}

/////////////////////////////////////////////////////////////////////////////
// CList::AddTail
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline TYPE CList<TYPE>::GetTail()
{
// return Tail element value
ASSERT(m_pTailNode);
return m_pTailNode->m_data;
}

/////////////////////////////////////////////////////////////////////////////
// CList::GetNext
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline TYPE CList<TYPE>::GetNext(POSITION position)
{
ASSERT(position);

// return the next element
return position->m_pNextNode->m_data;
}

/////////////////////////////////////////////////////////////////////////////
// CList::GetPrev
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline TYPE CList<TYPE>::GetPrev(POSITION position)
{
ASSERT(position);

// return the prev element
return position->m_pPrevNode->m_data;
}

/////////////////////////////////////////////////////////////////////////////
// CList::Find
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> POSITION CList<TYPE>::Find(TYPE element)
{
//return pointer to found element
for(CNode<TYPE>* p = m_pHeadNode; p; p = p->m_pNextNode)
  if(p->m_data == element)
return p;   // return position to found CNode

return NULL;  // return NULL if not found
}

/////////////////////////////////////////////////////////////////////////////
// CList::IsEmpty
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline BOOL CList<TYPE>::IsEmpty()
{
// returns TRUE if Empty
return m_ulLength == 0;
}

/////////////////////////////////////////////////////////////////////////////
// CList::RemoveHead
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> TYPE CList<TYPE>::RemoveHead()
{
//Remove and return from the Head of the List
ASSERT(m_pHeadNode);

CNode<TYPE>* pHeadNode = m_pHeadNode;// pointer to the Removed node
TYPE element = GetHead();//make a copy, before deleteing

m_pHeadNode = pHeadNode->m_pNextNode;// reroute Head to exclude the first element
if(m_pHeadNode)
m_pHeadNode->m_pPrevNode = NULL;
else
m_pTailNode = NULL;

m_ulLength--;
delete pHeadNode;// delete head
return element;
}

/////////////////////////////////////////////////////////////////////////////
// CList::RemoveTail
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> TYPE CList<TYPE>::RemoveTail()
{
//Remove and return from the m_pTailNode of the CList
ASSERT(m_pTailNode);

CNode<TYPE>* pTailNode = m_pTailNode->m_pPrevNode;
TYPE element = GetTail();  //make a copy before deleteing

m_pTailNode = pTailNode;
if(m_pTailNode)
m_pTailNode->m_pNextNode = NULL;
else
m_pHeadNode = NULL;

m_ulLength--;
delete m_pTailNode;
return element;
}

/////////////////////////////////////////////////////////////////////////////
// CList::RemoveAt
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> void CList<TYPE>::RemoveAt(POSITION position)
{
//Remove CList[position]
ASSERT(position);

CNode<TYPE>* pNode = position;

// If removing the head
if (pNode == m_pHeadNode)
m_pHeadNode = pNode->m_pNextNode;
else
pNode->m_pPrevNode->m_pNextNode = pNode->m_pNextNode;

//If removing the tail
if (pNode == m_pTailNode)
m_pTailNode = pNode->m_pPrevNode;
else
pNode->m_pNextNode->m_pPrevNode = pNode->m_pPrevNode;

m_ulLength--;
delete pNode;
}

/////////////////////////////////////////////////////////////////////////////
// CList::RemoveAll
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> void CList<TYPE>::RemoveAll()
{
// Remove all items from the CList
for (CNode<TYPE>* p = m_pHeadNode; p; p = p->m_pNextNode)
delete p;

m_pHeadNode   = NULL;
m_pTailNode   = NULL;
m_ulLength    = 0;
}

/////////////////////////////////////////////////////////////////////////////
// CList::GetCount
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> inline ULONG CList<TYPE>::GetCount()
{
// return the Length
return m_ulLength;
}

/////////////////////////////////////////////////////////////////////////////
// CList::InsertBefore
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> POSITION CList<TYPE>::InsertBefore(POSITION position, TYPE element)
{
//insert before the position
if(position == m_pHeadNode)    // Add before Head
  return AddHead(element);

//otherwise a little more difficult
CNode<TYPE>* pNode = new CNode<TYPE>(element, position->m_pPrevNode, position);

//Create the new node
pNode->m_pNextNode = new CNode<TYPE>(element, position->m_pPrevNode, position->m_pNextNode);

//Hook up before after nodes to it
position->m_pPrevNode->m_pNextNode = pNode;
position->m_pPrevNode = pNode;

m_ulLength++;
return pNode;
}

/////////////////////////////////////////////////////////////////////////////
// CList::InsertAfter
//
/////////////////////////////////////////////////////////////////////////////
template <class TYPE> POSITION CList<TYPE>::InsertAfter(POSITION position, TYPE element)
{
//insert after the position
if(position == m_pTailNode)     // Add after the m_pTailNode
  return AddTail(element);

//other wise a little more difficult
CNode<TYPE>* pNode = new CNode<TYPE>(element, position, position->m_pNextNode);

//Hook up before after nodes to it
position->m_pNextNode->m_pPrevNode = pNode;
position->m_pNextNode = pNode;

m_ulLength++;
return pNode;
}

#endif //_LIST_H_


PROGRESS.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module PROGRESS.CPP
//
//-----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////
// Includes
//
////////////////////////////////////////////////////////////////////
#include "progress.h"
#include "common.h"

////////////////////////////////////////////////////////////////////
// CProgress::CProgress
//
////////////////////////////////////////////////////////////////////
CProgress::CProgress(HWND hWnd, HINSTANCE hInst)
: CDialog(hWnd, hInst)
{
}

////////////////////////////////////////////////////////////////////
// CProgress::~CProgress
//
////////////////////////////////////////////////////////////////////
CProgress::~CProgress()
{
StopProgress();
}

////////////////////////////////////////////////////////////////////
// BOOL WINAPI CProgress::DlgProc
//
////////////////////////////////////////////////////////////////////
BOOL WINAPI CProgress::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
//Save the this pointer, since this is a static method
CProgress* pThis = (CProgress*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;

CenterDialog(hWnd);
return TRUE;
}
break;

case WM_COMMAND:
{
// All buttons are handled the same way
//Restore instance pointer, since this is a static function
CProgress* pThis = (CProgress*)GetWindowLong(hWnd, GWL_USERDATA);

switch(GET_WM_COMMAND_ID(wParam, lParam))
{
                case IDCANCEL:
pThis->m_fCancel = TRUE;
                return TRUE;
}
return FALSE;
}
break;

default:
return FALSE;
}
}

////////////////////////////////////////////////////////////////////
// ULONG CProgress::Display
//
////////////////////////////////////////////////////////////////////
ULONG CProgress::Display()
{
m_fCancel = FALSE;

//Create a ModeLess Dialog Box
m_hWnd = CreateDialogParam(m_hInst, MAKEINTRESOURCE(IDD_PROGRESS), NULL, DlgProc, (LPARAM)this);
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::StopProgress
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::StopProgress()
{
if(m_hWnd)
{
DestroyWindow(m_hWnd);
m_hWnd = NULL;
}
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::SetWorkingItem
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::SetWorkingItem(WCHAR* pwszWorking)
{
ASSERT(pwszWorking);

wSetDlgItemText(m_hWnd, IDT_STATUS, pwszWorking);
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::SetProgressText
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::SetProgressText(WCHAR* pwszProgress)
{
ASSERT(pwszProgress);

wSetDlgItemText(m_hWnd, IDT_STAT_INFO, pwszProgress);
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::Cancel
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::Cancel()
{
MSGmsg;

//Must have an existing window to cancel
if(m_hWnd==NULL)
return FALSE;

while(PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
{
if(!IsDialogMessage(m_hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return m_fCancel;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::ShowCancel
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::ShowCancel(BOOL fShow)
{
ShowWindow(GetDlgItem(m_hWnd, IDCANCEL), (fShow) ? SW_SHOW: SW_HIDE );
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::RestartProgress
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::RestartProgress()
{
m_fCancel = FALSE;
return TRUE;
}

////////////////////////////////////////////////////////////////////
// BOOL CProgress::UpdateProgress
//
////////////////////////////////////////////////////////////////////
BOOL CProgress::UpdateProgress(WCHAR* pwszItem, WCHAR* pwszText)
{
ASSERT(pwszItem && pwszText);

//Update the progress text
SetProgressText(pwszText);

// Now check for a cancel from the user, by checking
// the messages from the user
if(Cancel())
{
SetWorkingItem(pwszItem);
if(IDYES == wMessageBox(NULL, MB_ICONEXCLAMATION | MB_YESNO, wsz_CANCEL, wsz_CANCEL_OP))
{
//Indicate the users wishes to stop
return FALSE;
}
else
{
SetWorkingItem(pwszItem);
RestartProgress();
}
}

//Indicate the user wishes to continue
return TRUE;
}


PROGRESS.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module PROGRESS.H
//
//-----------------------------------------------------------------------------

#ifndef _PROGRESS_H_
#define _PROGRESS_H_

//////////////////////////////////////////////////////////////////////
// Includes
//
//////////////////////////////////////////////////////////////////////
#include "wizard.h"

//////////////////////////////////////////////////////////////////////
// CProgress
//
//////////////////////////////////////////////////////////////////////
class CProgress : public CDialog
{
public:

//Constructors
CProgress(HWND hWnd, HINSTANCE hInst);
virtual ~CProgress();

//members
ULONG   Display();
static  BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

virtual BOOL StopProgress();

virtual BOOL SetWorkingItem(WCHAR* pwszWorking);
virtual BOOL SetProgressText(WCHAR* pwszProgress);

virtual BOOL Cancel();
virtual BOOL ShowCancel(BOOL fShow);
virtual BOOL RestartProgress();

virtual BOOL UpdateProgress(WCHAR* pwszItem, WCHAR* pwszText);

//data
BOOLm_fCancel;
};

#endif //_PROGRESS_H_


PROPERTY.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module PROPERTY.CPP
//
//-----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////
// Includes
//
////////////////////////////////////////////////////////////////////////
#include "WinMain.h"
#include "Property.h"

/////////////////////////////////////////////////////////////////////////////
// HRESULT GetProperty
//
// Get the property information from the data source or object.
// propid specifies the property and propset specifies the property set to which
// propid belongs. The datatype of the property is required for the correct
// coercion of the data
/////////////////////////////////////////////////////////////////////////////
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,
   DBPROP** ppProperty)
{
ASSERT(pIUnknown && ppProperty);
HRESULT hr;

IDBProperties* pIDBProperties = NULL;
ICommandProperties* pICommandProperties = NULL;
IRowsetInfo* pIRowsetInfo = NULL;

ULONGcPropSets = 0;
DBPROPSET*rgPropSets = NULL;

const ULONGcPropertyIDSets = 1;
DBPROPIDSETrgPropertyIDSets[cPropertyIDSets];

//SetUp input DBPROPIDSET struct (all static)
rgPropertyIDSets[0].cPropertyIDs = cPropertyIDSets;
rgPropertyIDSets[0].rgPropertyIDs = &PropertyID;
rgPropertyIDSets[0].guidPropertySet = guidPropertySet;

//Need to figure out which Interface was passed in
//IDBInitialize, or ICommand, or IRowset, all three allow GetProperties
if(SUCCEEDED(hr = pIUnknown->QueryInterface(IID_IDBProperties,(void **)&pIDBProperties)))
{
//GetProperties
XTESTC(hr = pIDBProperties->GetProperties(cPropertyIDSets, rgPropertyIDSets, &cPropSets, &rgPropSets));
}
else if(SUCCEEDED(hr = pIUnknown->QueryInterface(IID_ICommandProperties,(void **)&pICommandProperties)))
{
//GetProperties
XTESTC(hr = pICommandProperties->GetProperties(cPropertyIDSets, rgPropertyIDSets, &cPropSets, &rgPropSets));
}
else
{
XTESTC(hr = pIUnknown->QueryInterface(IID_IRowsetInfo, (void **)&pIRowsetInfo));

//GetProperties
XTESTC(hr = pIRowsetInfo->GetProperties(cPropertyIDSets, rgPropertyIDSets, &cPropSets, &rgPropSets));
}

//Verify results
ASSERT(cPropSets==1 && rgPropSets!=NULL);
ASSERT(rgPropSets[0].cProperties==1 && rgPropSets[0].rgProperties!=NULL);
ASSERT(rgPropSets[0].rgProperties[0].dwPropertyID == PropertyID);

//Return the property to the user
*ppProperty = rgPropSets[0].rgProperties;

CLEANUP:
//Just Free the outer Struct,
//since we return the inner Property for the user to free
SAFE_FREE(rgPropSets);

SAFE_RELEASE(pIDBProperties);
SAFE_RELEASE(pICommandProperties);
SAFE_RELEASE(pIRowsetInfo);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT GetProperty
//
/////////////////////////////////////////////////////////////////////////////
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,WCHAR** ppwszValue)
{
ASSERT(pIUnknown && ppwszValue);
HRESULT hr;

DBPROP* pProperty = NULL;

TESTC(hr = GetProperty(pIUnknown, PropertyID, guidPropertySet, &pProperty));

//Copy the value
ASSERT(pProperty->vValue.vt == VT_BSTR);
*ppwszValue = wcsDuplicate(V_BSTR(&pProperty->vValue));

CLEANUP:
FreeProperties(1, pProperty);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT GetProperty
//
/////////////////////////////////////////////////////////////////////////////
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,WCHAR* pwszValue)
{
ASSERT(pIUnknown && pwszValue);
HRESULT hr;

DBPROP* pProperty = NULL;

TESTC(hr = GetProperty(pIUnknown, PropertyID, guidPropertySet, &pProperty));

//Copy the value
ASSERT(pProperty->vValue.vt == VT_BSTR);
wcscpy(pwszValue, V_BSTR(&pProperty->vValue));

CLEANUP:
FreeProperties(1, pProperty);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT GetProperty
//
/////////////////////////////////////////////////////////////////////////////
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,ULONG* pcValue)
{
ASSERT(pIUnknown && pcValue);
HRESULT hr;

DBPROP* pProperty = NULL;

TESTC(hr = GetProperty(pIUnknown, PropertyID, guidPropertySet, &pProperty));

//Copy the value
ASSERT(pProperty->vValue.vt == VT_I4);
*pcValue = V_I4(&pProperty->vValue);

CLEANUP:
FreeProperties(1, pProperty);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT GetProperty
//
/////////////////////////////////////////////////////////////////////////////
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,BOOL* pbValue)
{
ASSERT(pIUnknown && pbValue);
HRESULT hr;

DBPROP* pProperty = NULL;

TESTC(hr = GetProperty(pIUnknown, PropertyID, guidPropertySet, &pProperty));

//Copy the value
ASSERT(pProperty->vValue.vt == VT_BOOL);
*pbValue = V_BOOL(&pProperty->vValue) == VARIANT_TRUE;

CLEANUP:
FreeProperties(1, pProperty);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT FreeVariants
//
/////////////////////////////////////////////////////////////////////////////
HRESULT FreeVariants(ULONG cVariants, VARIANT* rgVariants)
{
HRESULT hr = S_OK;

//Free the inner variants first
for(ULONG i=0; i<cVariants; i++)
TEST(hr = VariantClear(&rgVariants[i]));

return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT InitVariants
//
/////////////////////////////////////////////////////////////////////////////
HRESULT InitVariants(ULONG cVariants, VARIANT* rgVariants)
{
HRESULT hr = S_OK;

//Free all variants
for(ULONG i=0; i<cVariants; i++)
VariantInit(&rgVariants[i]);

return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT FreeProperties
//
/////////////////////////////////////////////////////////////////////////////
HRESULT FreeProperties(ULONG cProperties, DBPROP* rgProperties)
{
HRESULT hr = S_OK;

//Free the inner variants first
for(ULONG i=0; i<cProperties; i++)
TEST(hr = VariantClear(&rgProperties[i].vValue));

//Now free the set
SAFE_FREE(rgProperties);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT FreeProperties
//
/////////////////////////////////////////////////////////////////////////////
HRESULT FreeProperties(ULONG cPropSets, DBPROPSET* rgPropSets)
{
HRESULT hr = S_OK;

//Loop over all the property sets
for(ULONG i=0; i<cPropSets; i++)
TEST(hr = FreeProperties(rgPropSets[i].cProperties, rgPropSets[i].rgProperties));

//Now free the outer set
SAFE_FREE(rgPropSets);
return hr;
}


PROPERTY.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module PROPERTY.H
//
//-----------------------------------------------------------------------------
#ifndef _PROPERTY_H_
#define _PROPERTY_H_

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include "common.h"

////////////////////////////////////////////////////////////////////////////
// Properties
//
////////////////////////////////////////////////////////////////////////////

//Get Property
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,WCHAR** ppwszValue);
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet, DBPROP** ppProperty);
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet, WCHAR* pwszValue);
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet, ULONG* pcValue);
HRESULT GetProperty(IUnknown* pIUnknown, DBPROPID PropertyID, GUID guidPropertySet,BOOL* pbValue);

//Free Property
HRESULT FreeProperties(ULONG cPropSets, DBPROPSET* rgPropSets);
HRESULT FreeProperties(ULONG cProperties, DBPROP* rgProperties);

//Variants
HRESULT InitVariants(ULONG cVariants, VARIANT* rgVariants);
HRESULT FreeVariants(ULONG cVariants, VARIANT* rgVariants);

#endif//_PROPERTY_H_


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by TableCopy.rc
//
#define IDUNSELECTALL                   3
#define IDB_UNSELECTALL                 3
#define IDSELECTALL                     4
#define IDB_SELECTALL                   4
#define IDD_FROM_INFO                   101
#define ID_TBLCPY_MENU                  102
#define IDD_TO_INFO                     102
#define IDD_OPTIONS                     103
#define IDD_INDEX_INFO                  105
#define IDI_TABLE_COPY                  105
#define IDD_SHOW_TYPES                  106
#define IDD_DATA_TYPES                  107
#define IDD_PROGRESS                    108
#define IDD_DIALOG1                     109
#define IDD_DIALOG2                     110
#define IDB_FROM_CONNECT                1000
#define IDB_SHOW_TYPES                  1002
#define IDX_TABLES                      1003
#define IDB_PREV                        1004
#define IDX_VIEWS                       1005
#define IDX_SYSTEM_TABLES               1006
#define IDX_SYNONYMS                    1007
#define IDL_TABLES                      1008
#define IDL_COLUMNS                     1009
#define IDT_CONNECT                     1010
#define IDT_FROM_INFO                   1011
#define IDT_PROVIDER                    1011
#define IDE_TO_TABLE                    1012
#define IDB_FINISH                      1013
#define IDX_COPY_SCHEMA                 1014
#define IDX_COPY_TABLE                  1014
#define IDX_COPY_DATA                   1015
#define IDX_COPY_INDEXES                1016
#define IDT_TO_PROVIDER                 1016
#define IDE_MAX_ROWS                    1017
#define IDT_MAX_ROWS                    1018
#define IDE_BLOB_SIZE                   1018
#define IDT_SHOW_GROUP                  1019
#define IDE_PARAM_SETS                  1019
#define IDT_TABLE_LIST                  1020
#define IDT_COLUMN_LIST                 1021
#define IDT_INDEXES                     1022
#define IDL_INDEXES                     1023
#define IDT_TO_TITLE                    1024
#define IDB_TO_CONNECT                  1025
#define IDT_SHOW_TABLES                 1026
#define IDT_SHOW_VIEWS                  1027
#define IDL_FROM_DATA_TYPES             1028
#define IDT_SHOW_SYNONYMS               1029
#define IDL_TO_DATA_TYPES               1030
#define IDT_STATUS                      1031
#define IDT_SHOW_SYSTEM_TABLES          1032
#define IDT_STAT_INFO                   1033
#define IDX_SHOW_SQL                    1034
#define IDR_ALL_ROWS                    1035
#define IDR_ROW_COUNT                   1036
#define IDR_MAX_ROWS                    1036
#define IDS_FROM_DSN                    1037
#define IDR_BLOB_SIZE                   1037
#define IDT_FROM_DSN                    1037
#define IDS_TO_DSN                      1038
#define IDR_ISEQ_STREAM                 1038
#define IDT_TO_DSN                      1038
#define IDR_PARAM_SETS                  1039
#define IDR_INSERT_ROW                  1040
#define IDC_SHOW_TYPES                  1043
#define IDC_TABLES                      1044
#define IDC_VIEWS                       1045
#define IDC_SYSTEMTABLES                1046
#define IDC_SYSTABLES                   1046
#define IDC_SYNONYMS                    1047
#define IDM_FILE_COPYTABLE              40001
#define IDM_FILE_EXIT                   40002
#define IDM_HELP_ABOUT                  40003
#define IDM_HELP_HELP                   40004
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        125
#define _APS_NEXT_COMMAND_VALUE         40005
#define _APS_NEXT_CONTROL_VALUE         1078
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SPY.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module SPY.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////////////
#include "spy.h"
#include "common.h"

/////////////////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////////////////

// HEADER + BUFFER + FOOTER
// HEADER = HEADSIGNITURE + BUFFERSIZE + BUFFERID
// FOOTER = TAILSIGNITURE

//All the header info must be ULONGs,
//so that the user buffer falls on a word boundary
//The tail must be a byte, since if it was a ULONG it would
//also require a word boundary, but the users buffer could
//be an odd number of bytes, so instead of rounding up, just use BYTE

const ULONG HEADSIZE= sizeof(ULONG);  //HEADSIGNITURE
const ULONG LENGTHSIZE= sizeof(ULONG);  //BUFFERSIZE
const ULONG IDSIZE= sizeof(ULONG);  //BUFFERIF
const ULONG TAILSIZE= sizeof(BYTE);  //TAILSIGNITURE

const ULONG HEADERSIZE = ROUNDUP(HEADSIZE + LENGTHSIZE + IDSIZE);
const ULONG FOOTERSIZE = TAILSIZE;

const BYTE  HEADSIGN = '{';
const BYTE  TAILSIGN = '}';

const BYTE  ALLOCSIGN = '$';
const BYTE  FREESIGN  = 'Z';

#define HEAD_OFFSET(pHeader)((BYTE*)pHeader)
#define TAIL_OFFSET(pHeader)(USERS_OFFSET(pHeader)+BUFFER_LENGTH(pHeader))

#define USERS_OFFSET(pHeader)(HEAD_OFFSET(pHeader) + HEADERSIZE)
#define HEADER_OFFSET(pUserBuffer) ((BYTE*)(pUserBuffer) - HEADERSIZE)

#define LENGTH_OFFSET(pHeader)(HEAD_OFFSET(pHeader) + HEADSIZE)
#define BUFFER_LENGTH(pHeader)(*(ULONG*)LENGTH_OFFSET(pHeader))

#define ID_OFFSET(pHeader)(LENGTH_OFFSET(pHeader) + LENGTHSIZE)
#define BUFFER_ID(pHeader)(*(ULONG*)ID_OFFSET(pHeader))

#define HEAD_SIGNITURE(pHeader)(*(ULONG*)HEAD_OFFSET(pHeader))
#define TAIL_SIGNITURE(pHeader)(*(BYTE*)TAIL_OFFSET(pHeader))

/////////////////////////////////////////////////////////////////////////////
// CMallocSpy::CMallocSpy()
//
/////////////////////////////////////////////////////////////////////////////
CMallocSpy::CMallocSpy()
{
    m_cRef = 0;
m_cbRequest = 0;

//AddRef, so delete is required
AddRef();
}

/////////////////////////////////////////////////////////////////////////////
// CMallocSpy::~CMallocSpy()
//
/////////////////////////////////////////////////////////////////////////////
CMallocSpy::~CMallocSpy()
{
//Remove all the elements of the list
CAllocList.RemoveAll();
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CMallocSpy::Add
//
/////////////////////////////////////////////////////////////////////////////
BOOL CMallocSpy::Add(void* pv)
{
ASSERT(pv);

//Add this element to the list
CAllocList.AddTail(pv);
return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CMallocSpy::Remove
//
/////////////////////////////////////////////////////////////////////////////
BOOL CMallocSpy::Remove(void* pv)
{
ASSERT(pv);

//Remove this element from the list
CAllocList.RemoveAt(CAllocList.Find(pv));
return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CMallocSpy::DumpLeaks
//
/////////////////////////////////////////////////////////////////////////////
BOOL CMallocSpy::DumpLeaks()
{
ULONG ulTotalLeaked = 0;

//Display Leaks to the Output Window
while(!CAllocList.IsEmpty())
{
//Obtain the pointer to the leaked memory
void* pUsersBuffer = CAllocList.RemoveHead();
ASSERT(pUsersBuffer);

void* pHeader = HEADER_OFFSET(pUsersBuffer);
ASSERT(pHeader);

//Make sure that the head/tail signitures are intact
if(HEAD_SIGNITURE(pHeader) != HEADSIGN)
TRACE(L"-- IMallocSpy HeadSigniture Corrupted! - 0x%08x, ID=%08lu, %lu bytes\n", pUsersBuffer, BUFFER_ID(pHeader), BUFFER_LENGTH(pHeader));

if(TAIL_SIGNITURE(pHeader) != TAILSIGN)
TRACE(L"-- IMallocSpy TailSigniture Corrupted! - 0x%08x, ID=%08lu, %lu bytes\n", pUsersBuffer, BUFFER_ID(pHeader), BUFFER_LENGTH(pHeader));

ULONG ulSize = BUFFER_LENGTH(pHeader);
ULONG ulID = BUFFER_ID(pHeader);

TRACE(L"-- IMallocSpy LEAK! - 0x%08x, ID=%08lu, %lu bytes\n", pUsersBuffer, ulID, ulSize);
ulTotalLeaked += ulSize;
}

if(ulTotalLeaked)
TRACE(L"-- IMallocSpy Total LEAKED! - %lu bytes\n", ulTotalLeaked);

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT CMallocSpy::QueryInterface
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CMallocSpy::QueryInterface(REFIID riid, void** ppIUnknown)
{
if(!ppIUnknown)
return E_INVALIDARG;

*ppIUnknown = NULL;

//IID_IUnknown
    if(riid == IID_IUnknown)
*ppIUnknown = this;
    //IDD_IMallocSpy
else if(riid == IID_IMallocSpy)
         *ppIUnknown =  this;

if(*ppIUnknown)
    {
        ((IUnknown*)*ppIUnknown)->AddRef();
        return S_OK;
}

return E_NOINTERFACE;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CMallocSpy::AddRef
//
/////////////////////////////////////////////////////////////////////////////
ULONG CMallocSpy::AddRef()
{
    return ++m_cRef;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CMallocSpy::Release
//
/////////////////////////////////////////////////////////////////////////////
ULONG CMallocSpy::Release()
{
    if(--m_cRef)
    return m_cRef;

TRACE(L"Releasing IMallocSpy\n");

    delete this;
    return 0;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CMallocSpy::PreAlloc
//
/////////////////////////////////////////////////////////////////////////////
ULONG CMallocSpy::PreAlloc(ULONG cbRequest)
{
//cbRequest is the orginal number of bytes requested by the user
//Store the users requested size
    m_cbRequest = cbRequest;

//Return the total size requested, plus extra for header/footer
return (m_cbRequest + HEADERSIZE + FOOTERSIZE);
}

/////////////////////////////////////////////////////////////////////////////
// void* CMallocSpy::PostAlloc
//
/////////////////////////////////////////////////////////////////////////////
void* CMallocSpy::PostAlloc(void* pHeader)
{
//pActual is the pointer to the head of the buffer, including the header
//Add the users pointer to the list
Add(USERS_OFFSET(pHeader));

//Place the HeadSigniture in the HEADER
HEAD_SIGNITURE(pHeader) = HEADSIGN;

//Place the Size in the HEADER
BUFFER_LENGTH(pHeader) = m_cbRequest;

//Place the ID in the HEADER
static ULONG ulID = 0;
BUFFER_ID(pHeader) = ++ulID;

//Set the UsersBuffer to a known char
    memset(USERS_OFFSET(pHeader), ALLOCSIGN, m_cbRequest);

//Place the TailSigniture in the HEADER
TAIL_SIGNITURE(pHeader) = TAILSIGN;

#ifdef FINDLEAKS
TRACE(L"-- IMallocSpy Alloc - 0x%08x, ID=%08lu, %lu bytes\n", USERS_OFFSET(pHeader), ulID, m_cbRequest);
#endif // FINDLEAKS

// Return the actual users buffer
    return USERS_OFFSET(pHeader);
}

/////////////////////////////////////////////////////////////////////////////
// void* CMallocSpy::PreFree
//
/////////////////////////////////////////////////////////////////////////////
void* CMallocSpy::PreFree(void* pUsersBuffer, BOOL fSpyed)
{
//pUsersBuffer is the users pointer to thier buffer, not the header

// Check for NULL
    if(pUsersBuffer == NULL)
        return NULL;

    //If this memory was alloced under IMallocSpy, need to remove it
    if(fSpyed)
{
//Remove this pointer form the list
Remove(pUsersBuffer);

void* pHeader = HEADER_OFFSET(pUsersBuffer);

//Make sure that the head/tail signitures are intact
if(HEAD_SIGNITURE(pHeader) != HEADSIGN)
TRACE(L"-- IMallocSpy HeadSigniture Corrupted! - 0x%08x, ID=%08lu, %lu bytes\n", pUsersBuffer, BUFFER_ID(pHeader), BUFFER_LENGTH(pHeader));

if(TAIL_SIGNITURE(pHeader) != TAILSIGN)
TRACE(L"-- IMallocSpy TailSigniture Corrupted! - 0x%08x, ID=%08lu, %lu bytes\n", pUsersBuffer, BUFFER_ID(pHeader), BUFFER_LENGTH(pHeader));

//Set the UsersBuffer to a known char
memset(pUsersBuffer, FREESIGN, BUFFER_LENGTH(pHeader));

//Need to return the actual header pointer to
//free the entire buffer including the heading
return pHeader;
}

//else
return pUsersBuffer;
}

/////////////////////////////////////////////////////////////////////////////
// void CMallocSpy::PostFree
//
/////////////////////////////////////////////////////////////////////////////
void CMallocSpy::PostFree(BOOL fSpyed)
{
    // Note the free or whatever
    return;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CMallocSpy::PreRealloc
//
/////////////////////////////////////////////////////////////////////////////
ULONG CMallocSpy::PreRealloc(void* pUsersBuffer, ULONG cbRequest,
                             void** ppNewRequest, BOOL fSpyed)
{
ASSERT(pUsersBuffer && ppNewRequest);

//If this was alloced under IMallocSpy we need to adjust
//the size stored in the header
    if(fSpyed)
    {
        //Find the start
*ppNewRequest = HEADER_OFFSET(pUsersBuffer);

//Store the new desired size
m_cbRequest = cbRequest;

//Return the total size, including extra
return (m_cbRequest + HEADERSIZE + FOOTERSIZE);
    }

//else
*ppNewRequest = pUsersBuffer;
    return cbRequest;
}

/////////////////////////////////////////////////////////////////////////////
// void* CMallocSpy::PostRealloc
//
/////////////////////////////////////////////////////////////////////////////
void* CMallocSpy::PostRealloc(void* pHeader, BOOL fSpyed)
{
    //If this buffer was alloced under IMallocSpy
    if(fSpyed)
    {
//HeadSigniture should still be intact
if(HEAD_SIGNITURE(pHeader) != HEADSIGN)
TRACE(L"-- IMallocSpy HeadSigniture Corrupted! - 0x%08x, ID=%08lu, %lu bytes\n", USERS_OFFSET(pHeader), BUFFER_ID(pHeader), BUFFER_LENGTH(pHeader));

//ID should still be intact

//Place the new Size in the HEADER
BUFFER_LENGTH(pHeader) = m_cbRequest;

        //Need to place the tail signiture again,
//since it will be over written by the realloc
TAIL_SIGNITURE(pHeader) = TAILSIGN;

//Return the actual "user" buffer
return USERS_OFFSET(pHeader);
    }

//else
    return pHeader;
}

/////////////////////////////////////////////////////////////////////////////
// void* CMallocSpy::PreGetSize
//
/////////////////////////////////////////////////////////////////////////////
void* CMallocSpy::PreGetSize(void* pUsersBuffer, BOOL fSpyed)
{
    if (fSpyed)
        return HEADER_OFFSET(pUsersBuffer);

    return pUsersBuffer;
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CMallocSpy::PostGetSize
//
/////////////////////////////////////////////////////////////////////////////
ULONG CMallocSpy::PostGetSize(ULONG cbActual, BOOL fSpyed)
{
    if (fSpyed)
        return cbActual - HEADERSIZE - FOOTERSIZE;

    return cbActual;
}

/////////////////////////////////////////////////////////////////////////////
// void* CMallocSpy::PreDidAlloc
//
/////////////////////////////////////////////////////////////////////////////
void* CMallocSpy::PreDidAlloc(void* pUsersBuffer, BOOL fSpyed)
{
    if (fSpyed)
        return HEADER_OFFSET(pUsersBuffer);

return pUsersBuffer;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CMallocSpy::PostDidAlloc
//
/////////////////////////////////////////////////////////////////////////////
BOOL CMallocSpy::PostDidAlloc(void* pRequest, BOOL fSpyed, BOOL fActual)
{
    return fActual;
}

/////////////////////////////////////////////////////////////////////////////
// void CMallocSpy::PreHeapMinimize
//
/////////////////////////////////////////////////////////////////////////////
void CMallocSpy::PreHeapMinimize()
{
    // We don't do anything here
    return;
}

/////////////////////////////////////////////////////////////////////////////
// void CMallocSpy::PostHeapMinimize
//
/////////////////////////////////////////////////////////////////////////////
void CMallocSpy::PostHeapMinimize()
{
    // We don't do anything here
    return;
}

/////////////////////////////////////////////////////////////////////////////
// Resgistration
//
/////////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
void MallocSpyRegister(CMallocSpy** ppCMallocSpy)
{
ASSERT(ppCMallocSpy);

//Allocate Interface
*ppCMallocSpy = new CMallocSpy(); //Constructor AddRef's

//Regisiter Interface
CoRegisterMallocSpy(*ppCMallocSpy); // Does an AddRef on Object
}

void MallocSpyUnRegister(CMallocSpy* pCMallocSpy)
{
ASSERT(pCMallocSpy);
CoRevokeMallocSpy(); //Does a Release on Object
}

void MallocSpyDump(CMallocSpy* pCMallocSpy)
{
ASSERT(pCMallocSpy);
pCMallocSpy->DumpLeaks();
}

#else
void MallocSpyRegister(CMallocSpy** ppCMallocSpy) {return; };
void MallocSpyUnRegister(CMallocSpy* pCMallocSpy) {return; };
void MallocSpyDump(CMallocSpy* pCMallocSpy)  { return; };
#endif //DEBUG


SPY.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module SPY.H
//
//-----------------------------------------------------------------------------

#ifndef _SPY_H_
#define _SPY_H_

/////////////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////////////
#include "objidl.h" //IMallocSpy
#include "List.h"   //CList

/////////////////////////////////////////////////////////////////////////////
// CMallocSpy
//
/////////////////////////////////////////////////////////////////////////////
class CMallocSpy : public IMallocSpy
{
public:
    CMallocSpy(void);
virtual ~CMallocSpy(void);

//Interface
virtual BOOL Add(void* pv);
virtual BOOL Remove(void* pv);
virtual BOOL DumpLeaks();

    // IUnknown methods
    virtual STDMETHODIMP QueryInterface(REFIID riid, void** ppIUnknown);
    virtual STDMETHODIMP_(ULONG) AddRef();
    virtual STDMETHODIMP_(ULONG) Release();

// IMallocSpy methods

    //Alloc
virtual STDMETHODIMP_(ULONG) PreAlloc(ULONG cbRequest);
    virtual STDMETHODIMP_(void*) PostAlloc(void *pActual);

    //Free
virtual STDMETHODIMP_(void*) PreFree(void *pRequest, BOOL fSpyed);
    virtual STDMETHODIMP_(void ) PostFree(BOOL fSpyed);

    //Realloc
virtual STDMETHODIMP_(ULONG) PreRealloc(void *pRequest, ULONG cbRequest, void **ppNewRequest, BOOL fSpyed);
    virtual STDMETHODIMP_(void*) PostRealloc(void *pActual, BOOL fSpyed);

    //GetSize
virtual STDMETHODIMP_(void*) PreGetSize(void *pRequest, BOOL fSpyed);
    virtual STDMETHODIMP_(ULONG) PostGetSize(ULONG cbActual, BOOL fSpyed);

    //DidAlloc
virtual STDMETHODIMP_(void*) PreDidAlloc(void *pRequest, BOOL fSpyed);
    virtual STDMETHODIMP_(BOOL)  PostDidAlloc(void *pRequest, BOOL fSpyed, BOOL fActual);

    //HeapMinimize
virtual STDMETHODIMP_(void ) PreHeapMinimize();
    virtual STDMETHODIMP_(void ) PostHeapMinimize();

private:
    ULONG    m_cRef;//Reference count
    ULONG    m_cbRequest;//Bytes requested

CList<void*> CAllocList;//List to keep track of leaks
};

/////////////////////////////////////////////////////////////////////////////
// Registration
//
/////////////////////////////////////////////////////////////////////////////
void MallocSpyRegister(CMallocSpy** ppCMallocSpy);
void MallocSpyUnRegister(CMallocSpy* pCMallocSpy);
void MallocSpyDump(CMallocSpy* pCMallocSpy);

#endif // _SPY_H_


STEP1.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module STEP1.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "wizard.h"
#include "common.h"
#include "table.h"

/////////////////////////////////////////////////////////////////////
// CS1Dialog::CS1Dialog
//
/////////////////////////////////////////////////////////////////////
CS1Dialog::CS1Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pCTableCopy)
: CDialog(hWnd, hInst)
{
ASSERT(pCTableCopy);
m_pCTableCopy = pCTableCopy;
}

/////////////////////////////////////////////////////////////////////
// CS1Dialog::~CS1Dialog
//
/////////////////////////////////////////////////////////////////////
CS1Dialog::~CS1Dialog()
{
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CS1Dialog::Display
//
/////////////////////////////////////////////////////////////////////////////
ULONG CS1Dialog::Display()
{
return DialogBoxParam(m_hInst, MAKEINTRESOURCE(IDD_FROM_INFO), NULL, DlgProc, (LPARAM)this);
}

/////////////////////////////////////////////////////////////////////
// CS1Dialog::DlgProc
//
/////////////////////////////////////////////////////////////////////
BOOL WINAPI CS1Dialog::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
//Store the "this" pointer, since this is a static method
CS1Dialog* pThis = (CS1Dialog*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;
CTable* pCFromTable = pThis->m_pCTableCopy->m_pCFromTable;

//Init all controls to the default values
pThis->InitControls();

// If there is a source to look at, Display the table list
if(pCFromTable->IsConnected())
pThis->ResetTableList(GetDlgItem(hWnd, IDL_TABLES), GetDlgItem(hWnd, IDL_COLUMNS));

pThis->RefreshControls();
CenterDialog(hWnd);
return HANDLED_MSG;
}

case WM_COMMAND:
{
//Obtain the "this" pointer
CS1Dialog* pThis = (CS1Dialog*)GetWindowLong(hWnd, GWL_USERDATA);

CTable* pCFromTable = pThis->m_pCTableCopy->m_pCFromTable;
CTable* pCToTable = pThis->m_pCTableCopy->m_pCToTable;

// First check for regular command ids
switch(GET_WM_COMMAND_ID(wParam, lParam))
{
case IDB_FROM_CONNECT:
{
//Try to connect to the DataSource
if(pCFromTable->Connect(hWnd))
pThis->ResetTableList(GetDlgItem(hWnd, IDL_TABLES), GetDlgItem(hWnd, IDL_COLUMNS));

pThis->RefreshControls();
return HANDLED_MSG;
}

case IDC_TABLES:
case IDC_VIEWS:
case IDC_SYSTABLES:
case IDC_SYNONYMS:
{
//Reset the available table list, with the new restrictions
pThis->ResetTableList(GetDlgItem(hWnd, IDL_TABLES), GetDlgItem(hWnd, IDL_COLUMNS));

//refresh any controls
pThis->RefreshControls();
return HANDLED_MSG;
}

                case IDOK:
pThis->GetTableColInfo();

case IDCANCEL:
                EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
return HANDLED_MSG;
}

// Now look for notification messages
switch (GET_WM_COMMAND_CMD(wParam, lParam))
case LBN_SELCHANGE:
{
// A table change requires a new list of columns
if(IDL_TABLES == GET_WM_COMMAND_ID(wParam, lParam))
{
WCHAR wszName[MAX_NAME_LEN];

//Obtain the TableName includes (username.tablename)
WPARAM nRow = (WPARAM)SendDlgItemMessage(hWnd, IDL_TABLES, LB_GETCURSEL, 0, 0L);
wSendMessage(GetDlgItem(hWnd, IDL_TABLES), LB_GETTEXT, (WPARAM)nRow, wszName);

//Only Reset if selected a different TableName
if(wcscmp(wszName, pCFromTable->m_wszQualTableName)!=0)
{
//Extract just the tablename
ULONG ulTableOffset = SendDlgItemMessage(hWnd, IDL_TABLES, LB_GETITEMDATA, nRow, 0L);

//Qualified Name
wcscpy(pCFromTable->m_wszQualTableName, wszName);
wcscpy(pCFromTable->m_wszTableName, wszName + ulTableOffset);

//Schema Name
wcsncpy(pCFromTable->m_wszSchemaName, wszName, ulTableOffset);
pCFromTable->m_wszSchemaName[ulTableOffset-1] = L'\0';

//Free the current columns list, since the new
//table will have diffent columns
pCFromTable->m_cColumns = 0;
SAFE_FREE(pCFromTable->m_rgColDesc);

//Reset the column list, since we have a new table
pThis->ResetColInfo(GetDlgItem(hWnd, IDL_COLUMNS));

//Reset the index list, since we have a new table
pCFromTable->m_cIndexes = 0;
SAFE_FREE(pCFromTable->m_rgIndexInfo);

//Reset the "To" table, since we have a new table
pCToTable->m_wszTableName[0] = L'\0';
}
}

pThis->RefreshControls();
return HANDLED_MSG;
}
}
}

return UNHANDLED_MSG;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::InitControls
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::InitControls()
{
//Setup defaults
CheckDlgButton(m_hWnd, IDC_TABLES,m_pCTableCopy->m_dwShowTypes & SHOW_TABLES? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(m_hWnd, IDC_VIEWS,m_pCTableCopy->m_dwShowTypes & SHOW_VIEWS? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(m_hWnd, IDC_SYSTABLES,m_pCTableCopy->m_dwShowTypes & SHOW_SYSTABLES? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(m_hWnd, IDC_SYNONYMS,m_pCTableCopy->m_dwShowTypes & SHOW_SYNONYMS? BST_CHECKED : BST_UNCHECKED);

//Set Horizontal ScrollBar dimensions
SendDlgItemMessage(m_hWnd, IDL_TABLES, LB_SETHORIZONTALEXTENT, (WPARAM)120*4, (LPARAM)0);
SendDlgItemMessage(m_hWnd, IDL_COLUMNS, LB_SETHORIZONTALEXTENT, (WPARAM)120*4, (LPARAM)0);

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::RefreshControls
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::RefreshControls()
{
// Must have a connection to edit other controls
BOOL fConnected = m_pCTableCopy->m_pCFromTable->IsConnected();
CDataSource* pCDataSource = m_pCTableCopy->m_pCFromTable->m_pCDataSource;

//Enable dialog items, only if connected
EnableWindow(GetDlgItem(m_hWnd, IDC_SHOW_TYPES),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDC_TABLES),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDC_VIEWS),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDC_SYSTABLES),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDC_SYNONYMS),fConnected);

EnableWindow(GetDlgItem(m_hWnd, IDT_TABLE_LIST),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDT_COLUMN_LIST),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDL_TABLES),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDL_COLUMNS),fConnected);

//Save CheckBoxes status
m_pCTableCopy->m_dwShowTypes = SHOW_NONE;
if(IsDlgButtonChecked(m_hWnd, IDC_TABLES) == BST_CHECKED)
m_pCTableCopy->m_dwShowTypes |= SHOW_TABLES;
if(IsDlgButtonChecked(m_hWnd, IDC_VIEWS) == BST_CHECKED)
m_pCTableCopy->m_dwShowTypes |= SHOW_VIEWS;
if(IsDlgButtonChecked(m_hWnd, IDC_SYSTABLES) == BST_CHECKED)
m_pCTableCopy->m_dwShowTypes |= SHOW_SYSTABLES;
if(IsDlgButtonChecked(m_hWnd, IDC_SYNONYMS) == BST_CHECKED)
m_pCTableCopy->m_dwShowTypes |= SHOW_SYNONYMS;

//If there is at a table selected and at least 1 columns select
BOOL fColumnsSelected = (fConnected &&
SendDlgItemMessage(m_hWnd, IDL_COLUMNS, LB_GETSELCOUNT, 0, 0L));

//Enable OK/Next if there is at least 1 columns selected
EnableWindow(GetDlgItem(m_hWnd, IDOK),fColumnsSelected);

// Show user the connection string
if(fConnected)
{
wSetDlgItemText(m_hWnd, IDT_CONNECT, wsz_CONNECT_STRING_,
pCDataSource->m_pwszDataSource,
pCDataSource->m_pwszUserName,
pCDataSource->m_pwszPassword,
pCDataSource->m_pwszDBMS,
pCDataSource->m_pwszDBMSVer);
}
else
{
wSetDlgItemText(m_hWnd, IDT_CONNECT, wsz_NOT_CONNECTED);
wSetDlgItemText(m_hWnd, IDT_PROVIDER, wsz_NO_PROVIDER);
}

if(pCDataSource->m_pwszProvider)
{
wSetDlgItemText(m_hWnd, IDT_PROVIDER, wsz_PROVIDER_STRING_,
pCDataSource->m_pwszProvider,
pCDataSource->m_pwszProviderVer,
pCDataSource->m_pwszProviderOLEDBVer);
}
else
{
wSetDlgItemText(m_hWnd, IDT_PROVIDER, wsz_NO_PROVIDER);
}

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::GetTableColInfo
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::GetTableColInfo()
{
CTable* pCFromTable = m_pCTableCopy->m_pCFromTable;

// Initialize working variables
HWND hWndCol = GetDlgItem(m_hWnd, IDL_COLUMNS);

// Get the list of selected items
LONG cColumns = SendDlgItemMessage(m_hWnd, IDL_COLUMNS, LB_GETSELCOUNT, 0, 0L);
LONG*rgColumns = NULL;
SAFE_ALLOC(rgColumns, LONG, cColumns);
SendMessage(hWndCol, LB_GETSELITEMS, (WPARAM)cColumns, (LPARAM)rgColumns);

//Allocate space for the ColNames
SAFE_FREE(pCFromTable->m_rgColDesc);
SAFE_ALLOC(pCFromTable->m_rgColDesc, COLDESC, cColumns);
memset(pCFromTable->m_rgColDesc, 0, cColumns*sizeof(COLDESC));
pCFromTable->m_cColumns = cColumns;

if(LB_ERR != SendMessage(hWndCol, LB_GETSELITEMS, (WPARAM)cColumns, (LPARAM)rgColumns))
{
for(LONG i=0; i<cColumns; i++)
{
//Obtain the entire ColumnName with TypeInfo
wSendMessage(hWndCol, LB_GETTEXT, (WPARAM)rgColumns[i], pCFromTable->m_rgColDesc[i].wszColName);

//Obtain the ending point for just the table name
LONG lColumnOffset = SendMessage(hWndCol, LB_GETITEMDATA, (WPARAM)rgColumns[i], (LPARAM)0L);
ASSERT(lColumnOffset>=0 && lColumnOffset<MAX_NAME_LEN);

//End the ColName with an EOL, don't include the typeinfo
pCFromTable->m_rgColDesc[i].wszColName[lColumnOffset] = EOL;
}
}

CLEANUP:
SAFE_FREE(rgColumns);
return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::ResetTableList
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::ResetTableList(HWND hWndTable, HWND hWndCol)
{
HRESULT hr;

DWORDdwTableLoc;
LONGiRow;

WCHARwszTypes[MAX_NAME_LEN];
WCHARwszBuffer[MAX_NAME_LEN*2];
SCHEMATABLESSchemaTables;

IDBSchemaRowset* pIDBSchemaRowset = NULL;
IRowset* pIRowset = NULL;
IAccessor* pIAccessor = NULL;

//get the data
HROW*rghRows = NULL;
ULONGi,cRowsObtained = 0;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;
Busy();

//Use the passed in Session interface
CTable*pCFromTable = m_pCTableCopy->m_pCFromTable;
IDBCreateCommand* pIDBCreateCommand = pCFromTable->m_pCDataSource->m_pIDBCreateCommand;

// Figure out types to enumrate based on options
SendMessage(hWndTable, LB_RESETCONTENT, (WPARAM)0, (LPARAM)0L);
GetTypes(wszTypes);

//set up the restrictions
const ULONG cRestrictions = 4;
VARIANTrgRestrictions[cRestrictions];

//Init the restrictions
InitVariants(cRestrictions, rgRestrictions);

// Bind the user and table name for the list
const ULONG cBindings = 2;
const DBBINDING rgBindings[cBindings] = {
2,
offsetof(SCHEMATABLES, wszUserName),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
MAX_NAME_LEN,// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_WSTR, // data type indicator
0,// precision
0, // scale

3, // ordinal
offsetof(SCHEMATABLES, wszTableName),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,// input param
MAX_NAME_LEN,// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_WSTR, // data type indicator
0,// precision
0, // scale

};

//Setup the restrictions
rgRestrictions[0].vt = VT_BSTR;
SAFE_SYSALLOC(rgRestrictions[0].bstrVal, pCFromTable->m_pCDataSource->m_pwszCatalog);
rgRestrictions[1].vt = VT_EMPTY;
rgRestrictions[2].vt = VT_EMPTY;
rgRestrictions[3].vt = VT_BSTR;
SAFE_SYSALLOC(rgRestrictions[3].bstrVal, wszTypes);

//get IDBSchemaRowset interface
XTESTC(hr = pIDBCreateCommand->QueryInterface(IID_IDBSchemaRowset,(void**)&pIDBSchemaRowset));

//GetRowset
//DBSCHEMA_TABLES is required a SCHEMA
XTESTC(hr = pIDBSchemaRowset->GetRowset(NULL, DBSCHEMA_TABLES, cRestrictions, rgRestrictions,
IID_IRowset, 0, NULL,(IUnknown **)&pIRowset));

XTESTC(hr = pIRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor));

//Create the Accessor
XTESTC(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, &hAccessor, NULL));

//Grab all the rows
while(TRUE)
{
hr = pIRowset->GetNextRows(NULL, 0, MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

//Loop over the rows retrived
for (i=0; i<cRowsObtained; i++)
{
//Reset the SchemaTables
memset(&SchemaTables, 0, sizeof(SCHEMATABLES));

//Get the Data
XTESTC(hr = pIRowset->GetData(rghRows[i], hAccessor, (void *)&SchemaTables));

// If there is a user, format buffer in a familiar way
if(SchemaTables.wszUserName[0])
{
//QualififedTableName = "dbo.authors"
swprintf(wszBuffer, L"%s%s%s", SchemaTables.wszUserName, pCFromTable->m_wszIDSeperator, SchemaTables.wszTableName);
iRow = wSendMessage(hWndTable, LB_ADDSTRING, (WPARAM)0, wszBuffer);
dwTableLoc = wcslen(SchemaTables.wszUserName) + wcslen(pCFromTable->m_wszIDSeperator);
}
else
{
//QualififedTableName = "authors"
iRow = wSendMessage(hWndTable, LB_ADDSTRING, (WPARAM)0, SchemaTables.wszTableName);
dwTableLoc = 0;
}

// Store the offset of the table name with the item, so that we know
// where to start translating later.
SendMessage(hWndTable, LB_SETITEMDATA, (WPARAM)iRow, (LPARAM)dwTableLoc);
}

//Release the rows obtained
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

// If there was a previous selection, select it again on Back
    if(pCFromTable->m_wszQualTableName[0])
{
    LONGlFoundTable;

lFoundTable = wSendMessage(hWndTable, LB_FINDSTRINGEXACT,
    (WPARAM)0, pCFromTable->m_wszQualTableName);

    // If there was a selection, select it now
    if(lFoundTable != LB_ERR)
{
SendMessage(hWndTable, LB_SETCURSEL, (WPARAM)lFoundTable, (LPARAM)0L);
ResetColInfo(hWndCol);
}
else
{
   //else we need to clear the columns list
   SendMessage(hWndCol, LB_RESETCONTENT, (WPARAM)0, (LPARAM)0L);
}
    }

Busy();

CLEANUP:
//Free Restrictions
FreeVariants(cRestrictions, rgRestrictions);

if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor,NULL));

SAFE_RELEASE(pIDBSchemaRowset);
SAFE_RELEASE(pIRowset);
SAFE_RELEASE(pIAccessor);

SAFE_FREE(rghRows);
return hr==S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::ResetColInfo
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::ResetColInfo(HWND hWndCol)
{
HRESULT hr;

WCHARwszBuffer[MAX_NAME_LEN];
SCHEMACOLUMNSSchemaColumns;

IDBSchemaRowset* pIDBSchemaRowset = NULL;
IRowset* pIRowset = NULL;
IAccessor* pIAccessor = NULL;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;

HROW*rghRows = NULL;
ULONGi,cRowsObtained = 0;
Busy();

//Use the passed in Session interface
//Use the passed in Session interface
CTable*pCFromTable = m_pCTableCopy->m_pCFromTable;
IDBCreateCommand* pIDBCreateCommand = pCFromTable->m_pCDataSource->m_pIDBCreateCommand;

// Get a list of columns based on the selected table
SendMessage(hWndCol, LB_RESETCONTENT, (WPARAM)0, (LPARAM)0L);

//set up the restrictions
const ULONG cRestrictions = 4;
VARIANTrgRestrictions[cRestrictions];

InitVariants(cRestrictions, rgRestrictions);

// Bind the user and table name for the list
const ULONG cBindings = 9;
const DBBINDING rgBindings[cBindings] =
{
4,
offsetof(SCHEMACOLUMNS, wszColName),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
MAX_NAME_LEN,// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_WSTR, // data type indicator
0,// precision
0, // scale

5,
offsetof(SCHEMACOLUMNS, guidCol),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(GUID),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_GUID, // data type indicator
0,// precision
0, // scale

7,
offsetof(SCHEMACOLUMNS, iOrdinal),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(ULONG),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI4, // data type indicator
0,// precision
0, // scale

10,
offsetof(SCHEMACOLUMNS, dwFlags),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(DBCOLUMNFLAGS),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI4, // data type indicator
0,// precision
0, // scale

12,
offsetof(SCHEMACOLUMNS, wType), // offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(USHORT),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI2, // data type indicator
0,// precision
0, // scale

13,
offsetof(SCHEMACOLUMNS, guidType), // offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(GUID),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_GUID, // data type indicator
0,// precision
0, // scale

14,
offsetof(SCHEMACOLUMNS, ulMaxLength),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(ULONG),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI4, // data type indicator
0,// precision
0, // scale

16,
offsetof(SCHEMACOLUMNS, wPrecision),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(USHORT),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI2, // data type indicator
0,// precision
0, // scale

17,
offsetof(SCHEMACOLUMNS, wScale),// offset of value in consumers buffer
0,// offset of length in consumers buffer
0,// offset of status in consumers buffer
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies value parts
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(USHORT),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI2, // data type indicator
0,// precision
0, // scale
};

//set up the restrictions
rgRestrictions[0].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[0].bstrVal, pCFromTable->m_pCDataSource->m_pwszCatalog);
rgRestrictions[1].vt = DBTYPE_EMPTY;
rgRestrictions[2].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[2].bstrVal, pCFromTable->m_wszTableName);
rgRestrictions[3].vt = DBTYPE_EMPTY;

//get IDBSchemaRowset interface
XTESTC(hr = pIDBCreateCommand->QueryInterface(IID_IDBSchemaRowset,(void**)&pIDBSchemaRowset));

//GetRowset
//DBSCHEMA_COLUMNS is required a SCHEMA
XTESTC(hr = pIDBSchemaRowset->GetRowset(NULL, DBSCHEMA_COLUMNS, cRestrictions, rgRestrictions,
IID_IRowset, 0, NULL, (IUnknown **)&pIRowset));

XTESTC(hr = pIRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor));

//Create the Accessor
XTESTC(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, &hAccessor, NULL));

//Grab all the rows
while(TRUE)
{
hr = pIRowset->GetNextRows(NULL, 0, MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

//Loop over the rows retrived
for(ULONG i=0; i<cRowsObtained; i++)
{
ULONG iColumn = 0;

//Reset the SchemaColumn struct
memset(&SchemaColumns, 0, sizeof(SCHEMACOLUMNS));

//Get the Data into the SchemaStruct
XTESTC(hr = pIRowset->GetData(rghRows[i], hAccessor, (void*)&SchemaColumns));

//Format the string
//ColumnName Type- For Non-Numeric Columns - Fixed Columns
//ColumnName Type(ulMaxLength)- For Non-Numeric Columns - Vairable Columns
//ColumnName Type(Precision, Scale)- For Numeric Columns
//ColumnName Type(Precision)- For Numeric Columns - No Scale
if(IsVariableType(SchemaColumns.wType))
{
swprintf(wszBuffer, L"%-20s \t[%s](%lu)", SchemaColumns.wszColName, GetDBTypeName(SchemaColumns.wType), SchemaColumns.ulMaxLength);
}
else if(SchemaColumns.wType==DBTYPE_NUMERIC || SchemaColumns.wType==DBTYPE_DECIMAL)
{
if(SchemaColumns.wScale)
swprintf(wszBuffer, L"%-20s \t[%s](%lu,%lu)", SchemaColumns.wszColName, GetDBTypeName(SchemaColumns.wType), SchemaColumns.wPrecision, SchemaColumns.wScale);
else
swprintf(wszBuffer, L"%-20s \t[%s](%lu)", SchemaColumns.wszColName, GetDBTypeName(SchemaColumns.wType), SchemaColumns.wPrecision);
}
else
{
swprintf(wszBuffer, wsz_COLUMN_FORMAT_, SchemaColumns.wszColName, GetDBTypeName(SchemaColumns.wType));
}

//Send the Formated String to the Window
iColumn = wSendMessage(hWndCol, LB_ADDSTRING, (WPARAM)0, wszBuffer);

//Now send where the ColName ends to the window
SendMessage(hWndCol, LB_SETITEMDATA, iColumn, (LPARAM)wcslen(SchemaColumns.wszColName));
}

//Release all rows
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

// If there is an existing columns list (only on Back or error), then
// the user already has a list so use it.
for(i=0; i<pCFromTable->m_cColumns; i++)
{
ASSERT(pCFromTable->m_rgColDesc);

//Find the Column Name in the Window List
swprintf(wszBuffer, L"%s ", pCFromTable->m_rgColDesc[i].wszColName);
LONG lFoundColumn = wSendMessage(hWndCol, LB_FINDSTRING, (WPARAM)0, wszBuffer);

//Select the Column Name if found in the list
if(lFoundColumn != LB_ERR)
SendMessage(hWndCol, LB_SETSEL, (WPARAM)TRUE, (LPARAM)lFoundColumn);
}

//Otherwise, just select all as default
if(pCFromTable->m_cColumns == 0)
SendMessage(hWndCol, LB_SETSEL, (WPARAM)TRUE, (LPARAM)-1L);

Busy();

CLEANUP:
//Free Restrictions
FreeVariants(cRestrictions, rgRestrictions);

if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor,NULL));

SAFE_RELEASE(pIDBSchemaRowset);
SAFE_RELEASE(pIRowset);
SAFE_RELEASE(pIAccessor);

SAFE_FREE(rghRows);
return hr==S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS1Dialog::GetTypes
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS1Dialog::GetTypes(WCHAR* pwszTypes)
{
ASSERT(pwszTypes);
pwszTypes[0] = EOL;

//IDC_TABLES
if(IsDlgButtonChecked(m_hWnd, IDC_TABLES) == BST_CHECKED)
wcscat(pwszTypes, L"'TABLE',");

//IDC_VIEWS
if(IsDlgButtonChecked(m_hWnd, IDC_VIEWS) == BST_CHECKED)
wcscat(pwszTypes, L"'VIEW',");

//IDC_SYSTABLES
if(IsDlgButtonChecked(m_hWnd, IDC_SYSTABLES) == BST_CHECKED)
wcscat(pwszTypes, L"'SYSTEM TABLE',");

//IDC_SYNONYMS
if(IsDlgButtonChecked(m_hWnd, IDC_SYNONYMS) == BST_CHECKED)
wcscat(pwszTypes, L"'SYNONYM',");

//remove the trailing comma
if(pwszTypes[0] != EOL)
pwszTypes[wcslen(pwszTypes)-1] = EOL;

return TRUE;
}


STEP2.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module STEP2.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "wizard.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"

/////////////////////////////////////////////////////////////////////
// CS2Dialog::CS2Dialog
//
/////////////////////////////////////////////////////////////////////
CS2Dialog::CS2Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pCTableCopy)
: CDialog(hWnd, hInst)
{
ASSERT(pCTableCopy);
m_pCTableCopy = pCTableCopy;
}

/////////////////////////////////////////////////////////////////////
// CS2Dialog::~CS2Dialog
//
/////////////////////////////////////////////////////////////////////
CS2Dialog::~CS2Dialog()
{
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CS2Dialog::Display
//
/////////////////////////////////////////////////////////////////////////////
ULONG CS2Dialog::Display()
{
return DialogBoxParam(m_hInst, MAKEINTRESOURCE(IDD_INDEX_INFO), NULL, DlgProc, (LPARAM)this);
}

/////////////////////////////////////////////////////////////////////
// CS2Dialog::DlgProc
//
/////////////////////////////////////////////////////////////////////
BOOL WINAPI CS2Dialog::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
//Store the "this" pointer, since this is a static method
CS2Dialog* pThis = (CS2Dialog*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;

//Set Horizontal ScrollBar dimensions
SendDlgItemMessage(hWnd, IDL_INDEXES, LB_SETHORIZONTALEXTENT, (WPARAM)200, (LPARAM)0);

pThis->ResetIndexList(GetDlgItem(hWnd, IDL_INDEXES));
CenterDialog(hWnd);
return HANDLED_MSG;
}

case WM_COMMAND:
{
//Obtain the "this" pointer
CS2Dialog* pThis = (CS2Dialog*)GetWindowLong(hWnd, GWL_USERDATA);

// All buttons are handled the same way
switch(GET_WM_COMMAND_ID(wParam, lParam))
{
                case IDB_PREV:
                case IDOK:

case IDCANCEL:
//Record all selected indexes
pThis->RecordSelectedIndexes(GetDlgItem(hWnd, IDL_INDEXES));

//Get which indexes are really PrimaryKeys,
pThis->GetPrimaryKeys();

EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;
}
}
}

return UNHANDLED_MSG;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS2Dialog::ResetIndexList
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS2Dialog::ResetIndexList(HWND hWnd)
{
HRESULT hr;
IDBSchemaRowset* pIDBSchemaRowset = NULL;

HROW* rghRows = NULL;
ULONG cRowsObtained = 0;
IRowset* pIRowset = NULL;

IAccessor* pIAccessor = NULL;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;

ULONG cIndexes = 0;
INDEXINFO* pIndexInfo = NULL;

Busy();

//set up the restrictions
const ULONG cRestrictions = 5;
VARIANT rgRestrictions[cRestrictions];
InitVariants(cRestrictions, rgRestrictions);

//Use the passed in Session interface
CTable* pCFromTable = m_pCTableCopy->m_pCFromTable;
IDBCreateCommand* pIDBCreateCommand = pCFromTable->m_pCDataSource->m_pIDBCreateCommand;

// Now make the call
SendMessage(hWnd, LB_RESETCONTENT, (WPARAM)0, (LPARAM)0L);

// Bind the user and table name for the list
const ULONG cBindings = 6;
const DBBINDING rgBindings[cBindings] =
{
6,
offsetof(INDEXINFO, wszIndexName),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
MAX_NAME_LEN,
0,
DBTYPE_WSTR,
0,
0,

8,
offsetof(INDEXINFO, fUnique),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(BOOL),
0,
DBTYPE_BOOL,
0,
0,

17,
offsetof(INDEXINFO, iOrdinal),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(ULONG),
0,
DBTYPE_UI4,
0,
0,

18,
offsetof(INDEXINFO, wszColName),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
MAX_NAME_LEN,
0,
DBTYPE_WSTR,
0,
0,

19,
offsetof(INDEXINFO, guidCol),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(GUID),
0,
DBTYPE_GUID,
0,
0,

21,
offsetof(INDEXINFO, dwCollation),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(DWORD),
0,
DBTYPE_I4,
0,
0,
};

//set up the restrictions
rgRestrictions[0].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[0].bstrVal, pCFromTable->m_pCDataSource->m_pwszCatalog);
rgRestrictions[1].vt = DBTYPE_EMPTY;
rgRestrictions[2].vt = DBTYPE_EMPTY;
rgRestrictions[3].vt = DBTYPE_EMPTY;
rgRestrictions[4].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[4].bstrVal, pCFromTable->m_wszTableName);

//Get IDBSchemaRowset interface
XTESTC(hr = pIDBCreateCommand->QueryInterface(IID_IDBSchemaRowset,(void **)&pIDBSchemaRowset));

//GetRowset
//DBSCHEMA_INDEXES may not be supported by the driver.
//If an error occurs, just don't display IndexInfo
TESTC(hr = pIDBSchemaRowset->GetRowset(NULL, DBSCHEMA_INDEXES, cRestrictions, rgRestrictions,
IID_IRowset, 0, NULL, (IUnknown **)&pIRowset));

XTESTC(hr = pIRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor));

//Create Accessor for IndexInfo
XTESTC(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, &hAccessor, NULL));

while(TRUE)
{
hr = pIRowset->GetNextRows(NULL, 0, MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

for (ULONG i=0; i<cRowsObtained; i++)
{
ULONG iSel = 0;
SAFE_FREE(pIndexInfo);
SAFE_ALLOC(pIndexInfo, INDEXINFO, 1);

//Reset the IndexInfo
memset(pIndexInfo, 0, sizeof(INDEXINFO));

//Get the Data
XTESTC(hr = pIRowset->GetData(rghRows[i], hAccessor, (void*)pIndexInfo));

//If this is an index the ordinal will be 1 based
if(pIndexInfo->iOrdinal==0)
continue;

// New indexes are added to the list box
// Note: We pass an alloced pointer to the window to store
// We need to free all the pointers when the window closes
iSel = wSendMessage(hWnd, LB_ADDSTRING, (WPARAM)0, pIndexInfo->wszIndexName);
SendMessage(hWnd, LB_SETITEMDATA, (WPARAM)iSel, (LPARAM)pIndexInfo);

//Now that we have an actual index...
//Set pIndexInfo to NULL since its now stored in the Window ITEMDATA
//And we don't want the CLEANUP veriosn to delete it again
pIndexInfo = NULL;
cIndexes++;
}

//Release all the rows
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

// If there was a previous selection, select it again on Back
    if(pCFromTable->m_rgIndexInfo)
{
    LONGlFoundIndex;

for(ULONG i=0; i<pCFromTable->m_cIndexes; i++)
{
lFoundIndex = wSendMessage(hWnd, LB_FINDSTRINGEXACT,
    (WPARAM)0, pCFromTable->m_rgIndexInfo[i].wszIndexName);

    // If there was a selection, select it now
    if(lFoundIndex != LB_ERR)
SendMessage(hWnd, LB_SETSEL, (WPARAM)TRUE, (LPARAM)lFoundIndex);
}
    }
//Otherwise select all as default
else
SendMessage(hWnd, LB_SETSEL, (WPARAM)TRUE, (LPARAM)-1L);

//Only enable the ListBox/Title if there are indexes
EnableWindow(GetDlgItem(m_hWnd, IDT_INDEXES),cIndexes);
EnableWindow(GetDlgItem(m_hWnd, IDL_INDEXES),cIndexes);

Busy();

CLEANUP:
//Free Resriticions
FreeVariants(cRestrictions, rgRestrictions);

if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor,NULL));

SAFE_RELEASE(pIDBSchemaRowset);
SAFE_RELEASE(pIRowset);
SAFE_RELEASE(pIAccessor);
SAFE_FREE(pIndexInfo);
SAFE_FREE(rghRows);
return hr==S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS2Dialog::GetPrimaryKeys
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS2Dialog::GetPrimaryKeys()
{
HRESULT hr;
IDBSchemaRowset* pIDBSchemaRowset = NULL;

HROW* rghRows = NULL;
ULONG cRowsObtained = 0;
IRowset* pIRowset = NULL;

IAccessor* pIAccessor = NULL;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;

PRIMARYKEY PrimaryKey;
Busy();

//set up the restrictions
const ULONG cRestrictions = 3;
VARIANT rgRestrictions[cRestrictions];
InitVariants(cRestrictions, rgRestrictions);

//Use the passed in Session interface
CTable* pCFromTable = m_pCTableCopy->m_pCFromTable;
IDBCreateCommand* pIDBCreateCommand = pCFromTable->m_pCDataSource->m_pIDBCreateCommand;

// Bind the user and table name for the list
const ULONG cBindings = 3;
const DBBINDING rgBindings[cBindings] =
{
4,
offsetof(PRIMARYKEY, wszColName),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
MAX_NAME_LEN,
0,
DBTYPE_WSTR,
0,
0,

5,
offsetof(PRIMARYKEY, guidCol),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(GUID),
0,
DBTYPE_GUID,
0,
0,

7,
offsetof(PRIMARYKEY, iOrdinal),
0,
0,
NULL,
NULL,
NULL,
DBPART_VALUE,
DBMEMOWNER_CLIENTOWNED,
DBPARAMIO_NOTPARAM,
sizeof(ULONG),
0,
DBTYPE_UI4,
0,
0,
};

//set up the restrictions
rgRestrictions[0].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[0].bstrVal, pCFromTable->m_pCDataSource->m_pwszCatalog);
rgRestrictions[1].vt = DBTYPE_EMPTY;
rgRestrictions[2].vt = DBTYPE_BSTR;
SAFE_SYSALLOC(rgRestrictions[2].bstrVal, pCFromTable->m_wszTableName);

//Get IDBSchemaRowset interface
XTESTC(hr = pIDBCreateCommand->QueryInterface(IID_IDBSchemaRowset,(void **)&pIDBSchemaRowset));

//GetRowset
//DBSCHEMA_PRIMARY_KEYS may not be supported by the driver.
//If an error occurs, just don't display PrimaryKey info
TESTC(hr = pIDBSchemaRowset->GetRowset(NULL, DBSCHEMA_PRIMARY_KEYS, cRestrictions, rgRestrictions,
IID_IRowset, 0, NULL, (IUnknown **)&pIRowset));

XTESTC(hr = pIRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor));

//Create Accessor for IndexInfo
XTESTC(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, &hAccessor, NULL));

while(TRUE)
{
hr = pIRowset->GetNextRows(NULL, 0, MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

for (ULONG i=0; i<cRowsObtained; i++)
{
//Reset the PrimaryKey
memset(&PrimaryKey, 0, sizeof(PRIMARYKEY));

//Get the Data
XTESTC(hr = pIRowset->GetData(rghRows[i], hAccessor, (void*)&PrimaryKey));

//Need to find the corresponding column in IndexInfo and
//mark it as a primary key column
for(ULONG iIndex=0; iIndex<pCFromTable->m_cIndexes; iIndex++)
if(wcscmp(PrimaryKey.wszColName, pCFromTable->m_rgIndexInfo[iIndex].wszColName)==0)
pCFromTable->m_rgIndexInfo[iIndex].fIsPrimaryKey = TRUE;
}

//Release all the rows
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

Busy();

CLEANUP:
//Free Resriticions
FreeVariants(cRestrictions, rgRestrictions);

if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor,NULL));

SAFE_RELEASE(pIDBSchemaRowset);
SAFE_RELEASE(pIRowset);
SAFE_RELEASE(pIAccessor);
SAFE_FREE(rghRows);
return hr==S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS2Dialog::RecordSelectedIndexes
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS2Dialog::RecordSelectedIndexes(HWND hWnd)
{
CTable* pCTable = m_pCTableCopy->m_pCFromTable;
LONG i,iSel;

//When we setup the IndexList, we stored the pointer of the index
//in the window.  So now we need free all the indexes stored, and
//save all the indexes selected by the user.

//Get the Total Number of indexes in the window
LONG cIndexes = SendMessage(hWnd, LB_GETCOUNT, (WPARAM)0, (LPARAM)0l);
//Get the Number of selected indexes
LONG cSelIndexes = SendMessage(hWnd, LB_GETSELCOUNT, (WPARAM)0, (LPARAM)0l);

//Get the selected index ordinals
LONG* rgSelIndexes = NULL;
SAFE_ALLOC(rgSelIndexes, LONG, cSelIndexes);
SendMessage(hWnd, LB_GETSELITEMS, (WPARAM)cSelIndexes, (LPARAM)rgSelIndexes);

//Alloc Table struct for Indexes
pCTable->m_cIndexes = cSelIndexes;
SAFE_FREE(pCTable->m_rgIndexInfo);
SAFE_ALLOC(pCTable->m_rgIndexInfo, INDEXINFO, cSelIndexes);

//Loop over all the indexes
for(i=0, iSel=0; i<cIndexes; i++)
{
//Get the index pointer for this ordinal from the window
INDEXINFO* pIndexInfo = (INDEXINFO*)SendMessage(hWnd, LB_GETITEMDATA, (WPARAM)i, (LPARAM)0);
ASSERT(pIndexInfo);

//Save this index, if it is a selected one
if(iSel<cSelIndexes && i==rgSelIndexes[iSel])
{
memcpy(&pCTable->m_rgIndexInfo[iSel], pIndexInfo, sizeof(INDEXINFO));
iSel++;
}

//delete the indexinfo
SAFE_FREE(pIndexInfo);
}

CLEANUP:
SAFE_FREE(rgSelIndexes);
return TRUE;
}


STEP3.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module STEP3.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "wizard.h"
#include "common.h"
#include "Tablecopy.h"
#include "Table.h"

/////////////////////////////////////////////////////////////////////
// CS3Dialog::CS3Dialog
//
/////////////////////////////////////////////////////////////////////
CS3Dialog::CS3Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pCTableCopy)
: CDialog(hWnd, hInst)
{
ASSERT(pCTableCopy);
m_pCTableCopy = pCTableCopy;
}

/////////////////////////////////////////////////////////////////////
// CS3Dialog::~CS3Dialog
//
/////////////////////////////////////////////////////////////////////
CS3Dialog::~CS3Dialog()
{
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CS3Dialog::Display
//
/////////////////////////////////////////////////////////////////////////////
ULONG CS3Dialog::Display()
{
return DialogBoxParam(m_hInst, MAKEINTRESOURCE(IDD_TO_INFO), NULL, DlgProc, (LPARAM)this);
}

/////////////////////////////////////////////////////////////////////
// CS3Dialog::DlgProc
//
/////////////////////////////////////////////////////////////////////
BOOL WINAPI CS3Dialog::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
//Store the "this" pointer, since this is a static method
CS3Dialog* pThis = (CS3Dialog*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;

//Limit the length of User Entered CTableName
SendDlgItemMessage(hWnd, IDE_TO_TABLE, EM_LIMITTEXT, (WPARAM)MAX_NAME_LEN-1, 0L);

pThis->RefreshControls();
CenterDialog(hWnd);
return HANDLED_MSG;
}

case WM_COMMAND:
{
//Obtain the "this" pointer
CS3Dialog* pThis = (CS3Dialog*)GetWindowLong(hWnd, GWL_USERDATA);

CTable* pCFromTable = pThis->m_pCTableCopy->m_pCFromTable;
CTable* pCToTable = pThis->m_pCTableCopy->m_pCToTable;

// All buttons are handled the same way
switch(GET_WM_COMMAND_ID(wParam, lParam))
{
case IDB_TO_CONNECT:
{
if(pThis->Connect())
{
pThis->RefreshControls();
SetFocus(GetDlgItem(hWnd, IDE_TO_TABLE));
}
return HANDLED_MSG;
}

                case IDOK:
{
//Get the TableName
wSendMessage(GetDlgItem(hWnd, IDE_TO_TABLE), WM_GETTEXT, MAX_NAME_LEN-1, pCToTable->m_wszTableName);

//If the TableNames are the same and the DataSource is the
//same then the copy is worthless (a no-op)
if(wcscmp(pCToTable->m_wszTableName, pCFromTable->m_wszTableName)==0 &&
pCToTable->m_pCDataSource->IsEqual(pCFromTable->m_pCDataSource))
{
//Need to enter a different table name from the source
wMessageBox(hWnd, MB_ICONEXCLAMATION | MB_OK,
wsz_ERROR, wsz_SAME_TABLE_NAME);
SetFocus(GetDlgItem(hWnd, IDE_TO_TABLE));
return HANDLED_MSG;
}

wcscpy(pCToTable->m_wszQualTableName,pCToTable->m_wszTableName);
                EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;
}

                case IDB_PREV:
//Get the TableName
wSendMessage(GetDlgItem(hWnd, IDE_TO_TABLE), WM_GETTEXT, MAX_NAME_LEN-1, pCToTable->m_wszTableName);
wcscpy(pCToTable->m_wszQualTableName,pCToTable->m_wszTableName);
                EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;

case IDCANCEL:
                EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;
}

// Now look for notification messages
switch (GET_WM_COMMAND_CMD(wParam, lParam))
{
case EN_CHANGE:
{
pThis->EnableTable();
return HANDLED_MSG;
}
}
}
}

return UNHANDLED_MSG;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS3Dialog::RefreshControls
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS3Dialog::RefreshControls()
{
BOOLfConnected;

CTable* pCToTable = m_pCTableCopy->m_pCToTable;
CTable* pCFromTable = m_pCTableCopy->m_pCFromTable;

// Must have a connection to edit other controls
fConnected = pCToTable->IsConnected();

//Enable certain dialog controls, only if connected
EnableWindow(GetDlgItem(m_hWnd, IDT_TO_TITLE),fConnected);
EnableWindow(GetDlgItem(m_hWnd, IDE_TO_TABLE),fConnected);

// Show user the connection string, and enable Next that requires connection
if(fConnected)
{
wSetDlgItemText(m_hWnd, IDT_CONNECT, wsz_CONNECT_STRING_,
pCToTable->m_pCDataSource->m_pwszDataSource,
pCToTable->m_pCDataSource->m_pwszUserName,
pCToTable->m_pCDataSource->m_pwszPassword,
pCToTable->m_pCDataSource->m_pwszDBMS,
pCToTable->m_pCDataSource->m_pwszDBMSVer);
}
else
{
wSetDlgItemText(m_hWnd, IDT_CONNECT, wsz_NOT_CONNECTED);
}

if(pCFromTable->m_pCDataSource->m_pwszProvider)
{
wSetDlgItemText(m_hWnd, IDT_TO_PROVIDER, wsz_PROVIDER_STRING_,
pCFromTable->m_pCDataSource->m_pwszProvider,
pCFromTable->m_pCDataSource->m_pwszProviderVer,
pCFromTable->m_pCDataSource->m_pwszProviderOLEDBVer);
}
else
{
wSetDlgItemText(m_hWnd, IDT_TO_PROVIDER, wsz_NO_PROVIDER);
}

// Set the from info
    wSetDlgItemText(m_hWnd, IDT_FROM_INFO, L"%s, %s",
pCFromTable->m_pCDataSource->m_pwszDataSource,
pCFromTable->m_wszQualTableName);

// If there is already a CTable from previous selections, just use that one
wSetDlgItemText(m_hWnd, IDE_TO_TABLE, pCToTable->m_wszTableName[0] ? pCToTable->m_wszTableName :pCFromTable->m_wszTableName);

// Determine if there is enough information to move on
return EnableTable();
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS3Dialog::Connect
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS3Dialog::Connect()
{
BOOL fConnected = FALSE;

CDataSource* pCToDataSource = m_pCTableCopy->m_pCToTable->m_pCDataSource;
CDataSource* pCFromDataSource = m_pCTableCopy->m_pCFromTable->m_pCDataSource;

//Connect to the DataSource
HRESULT hr = m_pCTableCopy->m_pCToTable->Connect(m_hWnd);

//If Connected
if(SUCCEEDED(hr))
{
fConnected = TRUE;

// Verify we can use this data source
if(pCToDataSource->m_fReadOnly)
{
wMessageBox(m_hWnd, MB_ICONEXCLAMATION | MB_OK, wsz_ERROR,
wsz_READONLY_DATASOURCE_, pCToDataSource->m_pwszDataSource);

fConnected =  FALSE;
}

//See if this is a similar DSN than the Source
//If DSN's are not similar, then we need to translate
m_pCTableCopy->m_fTranslate = !pCToDataSource->IsSimilar(pCFromDataSource);
}

return fConnected;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS3Dialog::EnableTable
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS3Dialog::EnableTable()
{
BOOL fConnected = m_pCTableCopy->m_pCToTable->IsConnected();

EnableWindow(GetDlgItem(m_hWnd, IDOK),
fConnected && SendDlgItemMessage(m_hWnd, IDE_TO_TABLE, WM_GETTEXTLENGTH, 0, 0L));

return TRUE;
}


STEP4.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module STEP4.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "wizard.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"

/////////////////////////////////////////////////////////////////////
// CS4Dialog::CS4Dialog
//
/////////////////////////////////////////////////////////////////////
CS4Dialog::CS4Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pCTableCopy)
: CDialog(hWnd, hInst)
{
ASSERT(pCTableCopy);
m_pCTableCopy = pCTableCopy;
}

/////////////////////////////////////////////////////////////////////
// CS4Dialog::~CS4Dialog
//
/////////////////////////////////////////////////////////////////////
CS4Dialog::~CS4Dialog()
{
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CS4Dialog::Display
//
/////////////////////////////////////////////////////////////////////////////
ULONG CS4Dialog::Display()
{
return DialogBoxParam(m_hInst, MAKEINTRESOURCE(IDD_OPTIONS), NULL, DlgProc, (LPARAM)this);
}

/////////////////////////////////////////////////////////////////////
// CS4Dialog::DlgProc
//
/////////////////////////////////////////////////////////////////////
BOOL WINAPI CS4Dialog::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
//Store the "this" pointer, since this is a static method
CS4Dialog* pThis = (CS4Dialog*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;

pThis->InitControls();
CenterDialog(hWnd);
return HANDLED_MSG;
}

case WM_COMMAND:
{
//Obtain the "this" pointer
CS4Dialog* pThis = (CS4Dialog*)GetWindowLong(hWnd, GWL_USERDATA);

// All buttons are handled the same way
switch(GET_WM_COMMAND_ID(wParam, lParam))
{
case IDX_COPY_TABLE:
case IDX_COPY_INDEXES:
case IDX_SHOW_SQL:

case IDR_ALL_ROWS:
case IDR_MAX_ROWS:
case IDR_PARAM_SETS:
case IDR_INSERT_ROW:
case IDR_BLOB_SIZE:
case IDR_ISEQ_STREAM:
pThis->RefreshControls();
return HANDLED_MSG;

case IDOK:
case IDB_PREV:
if(!pThis->RefreshControls())
return HANDLED_MSG;

case IDCANCEL:
                EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;
}
}
}

return UNHANDLED_MSG;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS4Dialog::InitControls
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS4Dialog::InitControls()
{
//IDX_COPY_TABLE (default-checked)
CheckDlgButton(m_hWnd, IDX_COPY_TABLE, m_pCTableCopy->m_fCopyTables);

//IDX_COPY_INDEXES (default-checked)
CheckDlgButton(m_hWnd, IDX_COPY_INDEXES, m_pCTableCopy->m_fCopyIndexes);

//IDX_SHOW_SQL (checked)
CheckDlgButton(m_hWnd, IDX_SHOW_SQL, m_pCTableCopy->m_fShowQuery);

//IDR_ALL_ROWS (default)
//IDR_MAX_ROWS
CheckRadioButton(m_hWnd, IDR_ALL_ROWS, IDR_MAX_ROWS, m_pCTableCopy->m_dwRowOpt);
wSetDlgItemText(m_hWnd, IDE_MAX_ROWS, L"%lu", m_pCTableCopy->m_ulMaxRows);

//IDR_PARAM_SETS (default)
//IDR_INSERT_ROW
//If multiple param sets are not supported by this driver, select only 1 set
if(!m_pCTableCopy->m_pCToTable->m_pCDataSource->m_fMultipleParamSets)
m_pCTableCopy->m_ulParamSets = 1;
CheckRadioButton(m_hWnd, IDR_PARAM_SETS, IDR_INSERT_ROW, m_pCTableCopy->m_dwParamOpt);
wSetDlgItemText(m_hWnd, IDE_PARAM_SETS, L"%lu", m_pCTableCopy->m_ulParamSets);

//IDR_BLOB_SIZE(default)
//IDR_ISEQ_STREAM
CheckRadioButton(m_hWnd, IDR_BLOB_SIZE, IDR_ISEQ_STREAM, m_pCTableCopy->m_dwBlobOpt);
wSetDlgItemText(m_hWnd, IDE_BLOB_SIZE, L"%lu", m_pCTableCopy->m_ulBlobSize);

//ISEQ_STREAM is currently disabled, until limitations are fixed
EnableWindow(GetDlgItem(m_hWnd, IDR_ISEQ_STREAM), FALSE);

//Limit the TextLength of the Edit Controls to 10 chars
SendDlgItemMessage(m_hWnd, IDE_MAX_ROWS, EM_LIMITTEXT, (WPARAM)10, 0L);
SendDlgItemMessage(m_hWnd, IDE_PARAM_SETS, EM_LIMITTEXT, (WPARAM)10, 0L);
SendDlgItemMessage(m_hWnd, IDE_BLOB_SIZE, EM_LIMITTEXT, (WPARAM)10, 0L);

// Refresh the controls
RefreshControls();
return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CS4Dialog::RefreshControls
//
/////////////////////////////////////////////////////////////////////////////
BOOL CS4Dialog::RefreshControls()
{
// IDE_MAX_ROWS Get the Copy options first so we can check the row count if given.
if(IsDlgButtonChecked(m_hWnd, IDR_MAX_ROWS))
{
m_pCTableCopy->m_dwRowOpt = IDR_MAX_ROWS;
EnableWindow(GetDlgItem(m_hWnd, IDE_MAX_ROWS), TRUE);
}
else
{
m_pCTableCopy->m_dwRowOpt = IDR_ALL_ROWS;
EnableWindow(GetDlgItem(m_hWnd, IDE_MAX_ROWS), FALSE);
}

//Verify IDE_MAX_ROWS has legal value
if(!GetEditBoxValue(GetDlgItem(m_hWnd, IDE_MAX_ROWS), 1, LONG_MAX, &m_pCTableCopy->m_ulMaxRows))
return FALSE;

//ISEQ_STREAM is currently disabled, until limitations are fixed
EnableWindow(GetDlgItem(m_hWnd, IDR_ISEQ_STREAM), FALSE);

//IDE_BLOB_SIZE Get the Insert ParamSets options
if(IsDlgButtonChecked(m_hWnd, IDR_BLOB_SIZE))
{
m_pCTableCopy->m_dwBlobOpt = IDR_BLOB_SIZE;
EnableWindow(GetDlgItem(m_hWnd, IDE_BLOB_SIZE), TRUE);
}
else
{
m_pCTableCopy->m_dwBlobOpt = IDR_ISEQ_STREAM;
EnableWindow(GetDlgItem(m_hWnd, IDE_BLOB_SIZE), FALSE);
}

//Verify IDE_BLOB_SIZE has legal value
if(!GetEditBoxValue(GetDlgItem(m_hWnd, IDE_BLOB_SIZE), 1, MAX_COL_SIZE, &m_pCTableCopy->m_ulBlobSize))
return FALSE;

//IDE_PARAM_SETS Get the Insert ParamSets options
if(IsDlgButtonChecked(m_hWnd, IDR_PARAM_SETS))
{
m_pCTableCopy->m_dwParamOpt = IDR_PARAM_SETS;
EnableWindow(GetDlgItem(m_hWnd, IDE_PARAM_SETS), m_pCTableCopy->m_pCToTable->m_pCDataSource->m_fMultipleParamSets);
}
else
{
m_pCTableCopy->m_dwParamOpt = IDR_INSERT_ROW;
EnableWindow(GetDlgItem(m_hWnd, IDE_PARAM_SETS), FALSE);
}

//Verify IDE_PARAM_SETS has legal value
if(!GetEditBoxValue(GetDlgItem(m_hWnd, IDE_PARAM_SETS), 1, LONG_MAX, &m_pCTableCopy->m_ulParamSets))
return FALSE;

// Get Create options
m_pCTableCopy->m_fCopyTables = IsDlgButtonChecked(m_hWnd, IDX_COPY_TABLE);
m_pCTableCopy->m_fCopyIndexes = IsDlgButtonChecked(m_hWnd, IDX_COPY_INDEXES);

// Set other Options
m_pCTableCopy->m_fShowQuery = IsDlgButtonChecked(m_hWnd, IDX_SHOW_SQL);
return TRUE;
}

/////////////////////////////////////////////////////////////////////
// CTypesDialog::CTypesDialog
//
/////////////////////////////////////////////////////////////////////
CTypesDialog::CTypesDialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pCTableCopy)
: CDialog(hWnd, hInst)
{
ASSERT(pCTableCopy);
m_pCTableCopy = pCTableCopy;
}

/////////////////////////////////////////////////////////////////////
// CTypesDialog::~CTypesDialog
//
/////////////////////////////////////////////////////////////////////
CTypesDialog::~CTypesDialog()
{
}

/////////////////////////////////////////////////////////////////////////////
// ULONG CTypesDialog::Display
//
/////////////////////////////////////////////////////////////////////////////
ULONG CTypesDialog::Display()
{
return DialogBoxParam(m_hInst, MAKEINTRESOURCE(IDD_DATA_TYPES), NULL, DlgProc, (LPARAM)this);
}

/////////////////////////////////////////////////////////////////////
// CTypesDialog::DlgProc
//
/////////////////////////////////////////////////////////////////////
BOOL WINAPI CTypesDialog::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)
{
case WM_INITDIALOG:
{
//Store the "this" pointer, since this is a static method
CTypesDialog* pThis = (CTypesDialog*)lParam;
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

//On INIT we know we have a valid hWnd to store
pThis->m_hWnd = hWnd;

//Tell TableCopy to Map the Types from the Source->Target
//If MapTypes fails, end the app, (ie: same as hitting CANCEL)
if(FAILED(pThis->m_pCTableCopy->MapTypes()))
EndDialog(hWnd, IDCANCEL);

//Set Horizontal ScrollBar dimensions
SendDlgItemMessage(hWnd, IDL_FROM_DATA_TYPES, LB_SETHORIZONTALEXTENT, (WPARAM)120*4, (LPARAM)0);
SendDlgItemMessage(hWnd, IDL_TO_DATA_TYPES, LB_SETHORIZONTALEXTENT, (WPARAM)120*4, (LPARAM)0);

// Now Display the lists of Mappings
pThis->RefreshLists();
CenterDialog(hWnd);
return HANDLED_MSG;
}

case WM_COMMAND:
{
// All buttons are handled the same way

//Obtain the "this" pointer
CTypesDialog* pThis = (CTypesDialog*)GetWindowLong(hWnd, GWL_USERDATA);

switch(GET_WM_COMMAND_ID(wParam, lParam))
{
                case IDOK:
//Now copy the Tables, End the Dialog first, so the user
//can't still acess it while the copiing is going on...
EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
pThis->m_pCTableCopy->CopyTables();
                return HANDLED_MSG;

case IDCANCEL:
case IDB_PREV:
EndDialog(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
                return HANDLED_MSG;

                case IDL_FROM_DATA_TYPES:
                switch(GET_WM_COMMAND_CMD(wParam, lParam))
{
case LBN_SELCHANGE:
SyncSibling(GetDlgItem(hWnd,IDL_TO_DATA_TYPES), GetDlgItem(hWnd,IDL_FROM_DATA_TYPES));
                return HANDLED_MSG;
}

                case IDL_TO_DATA_TYPES:
                switch(GET_WM_COMMAND_CMD(wParam, lParam))
{
case LBN_SELCHANGE:
SyncSibling(GetDlgItem(hWnd,IDL_FROM_DATA_TYPES), GetDlgItem(hWnd,IDL_TO_DATA_TYPES));
                return HANDLED_MSG;
}
}
}
}

return UNHANDLED_MSG;
}

/////////////////////////////////////////////////////////////////////////////
// BOOL CTypesDialog::RefreshLists
//
/////////////////////////////////////////////////////////////////////////////
BOOL CTypesDialog::RefreshLists()
{
HWNDhWndFrom;// From data types
HWNDhWndTo;// To data types

WCHARwszType[MAX_NAME_LEN];// Buffer for type value
WCHARwszBuffer[MAX_NAME_LEN];// Buffer for type value

CTable* pCFromTable = m_pCTableCopy->m_pCFromTable;
CTable* pCToTable = m_pCTableCopy->m_pCToTable;

//Set the TABLENAME / DSN for Source
wSetDlgItemText(m_hWnd, IDT_FROM_DSN, wsz_FROM_TABLE_DSN_, pCFromTable->m_wszQualTableName, pCFromTable->m_pCDataSource->m_pwszDataSource);

//Set the TABLENAME / DSN for Target
wSetDlgItemText(m_hWnd, IDT_TO_DSN, wsz_TO_TABLE_DSN_, pCToTable->m_wszQualTableName, pCToTable->m_pCDataSource->m_pwszDataSource);

// Get window handles to speed up the population
hWndFrom = GetDlgItem(m_hWnd, IDL_FROM_DATA_TYPES);
hWndTo = GetDlgItem(m_hWnd, IDL_TO_DATA_TYPES);

// Now populate each list
for(ULONG i=0;  i<pCFromTable->m_cColumns; i++)
{
// Format both values and add them to the list
wcscpy(wszType, pCFromTable->m_rgColDesc[i].wszTypeName);
ULONG ulParamFlag = pCFromTable->m_rgColDesc[i].ulCreateParams;

// Add the Precision/Scale info
if(ulParamFlag & CP_LENGTH)
{
swprintf(wszBuffer, L"(%lu)", COLINFO_SIZE(pCFromTable->m_rgColDesc[i]));
wcscat(wszType, wszBuffer);
}
else if(ulParamFlag & CP_PRECISION && ulParamFlag & CP_SCALE)
{
swprintf(wszBuffer, L"(%lu,%lu)", COLINFO_SIZE(pCFromTable->m_rgColDesc[i]), pCFromTable->m_rgColDesc[i].bScale);
wcscat(wszType, wszBuffer);
}

// Send the Entire String to the Window
swprintf(wszBuffer, wsz_TYPE_FORMAT_,
pCFromTable->m_rgColDesc[i].wszColName,wszType,
GetDBTypeName(pCFromTable->m_rgColDesc[i].wType));
wSendMessage(hWndFrom, LB_ADDSTRING, (WPARAM)0, wszBuffer);

// Format the To type info
wcscpy(wszType, pCToTable->m_rgColDesc[i].wszTypeName);
ulParamFlag = pCToTable->m_rgColDesc[i].ulCreateParams;

// Add the Precision/Scale info
if(ulParamFlag & CP_LENGTH)
{
swprintf(wszBuffer, L"(%lu)", COLINFO_SIZE(pCToTable->m_rgColDesc[i]));
wcscat(wszType, wszBuffer);
}
else if(ulParamFlag & CP_PRECISION && ulParamFlag & CP_SCALE)
{
swprintf(wszBuffer, L"(%lu,%lu)", COLINFO_SIZE(pCToTable->m_rgColDesc[i]), pCToTable->m_rgColDesc[i].bScale);
wcscat(wszType, wszBuffer);
}

// Send the Entire String to the Window
swprintf(wszBuffer, wsz_TYPE_FORMAT_,
pCToTable->m_rgColDesc[i].wszColName, wszType,
GetDBTypeName(pCToTable->m_rgColDesc[i].wType));
wSendMessage(hWndTo, LB_ADDSTRING, (WPARAM)0, wszBuffer);
}
return TRUE;
}


TABLE.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module TABLE.H
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "winmain.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"
#include "wizard.h"
#include "progress.h"

//////////////////////////////////////////////////////////////////////////////
// Defines / Macros
//
//////////////////////////////////////////////////////////////////////////////
#define NO_MATCH0x0000
#define MATCH_EXACT0x0001
#define MATCH_TYPE0x0002
#define MATCH_SIZE0x0004
#define MATCH_DEFAULT0x0008

/////////////////////////////////////////////////////////////////
// CTable::CTable
//
/////////////////////////////////////////////////////////////////
CTable::CTable(CWizard* pCWizard)
{
ASSERT(pCWizard);

m_pCDataSource   = new CDataSource;

m_wszIDQuote[0]= EOL;
m_wszIDSeperator[0]= EOL;

m_wszSchemaName[0]= EOL;
m_wszTableName[0]= EOL;
m_wszQualTableName[0]= EOL;

//IndexInfo
m_cIndexes= 0;// Count of indexes
m_rgIndexInfo= NULL;// Index information

//ColumnInfo
m_cColumns= 0;// Count of columns
m_rgColDesc= NULL;// Column information
m_cBlobs= 0;// 0 Blob Columns

m_pCWizard= pCWizard;// Back pointer to Windowing class
}

/////////////////////////////////////////////////////////////////
// CTable::~CTable
//
/////////////////////////////////////////////////////////////////
CTable::~CTable()
{
delete m_pCDataSource;

SAFE_FREE(m_rgIndexInfo);
SAFE_FREE(m_rgColDesc);
}

/////////////////////////////////////////////////////////////////
// BOOL CTable::Connect
//
/////////////////////////////////////////////////////////////////
BOOL CTable::Connect(HWND hWnd)
{
ASSERT(m_pCDataSource);
if(m_pCDataSource->Connect(hWnd))
{
//Get LiteralInfo for this table
GetLiteralInfo();
}

return m_pCDataSource->IsConnected();
}

/////////////////////////////////////////////////////////////////
// BOOL CTable::IsConnected
//
/////////////////////////////////////////////////////////////////
BOOL CTable::IsConnected()
{
ASSERT(m_pCDataSource);
return m_pCDataSource->IsConnected();
}

/////////////////////////////////////////////////////////////////
// BOOL CTable::TranslateNames
//
/////////////////////////////////////////////////////////////////
BOOL CTable::TranslateNames()
{
//Converts all DSN dependents names to allowable chars
MakeAlphaNumeric(m_wszTableName, UNDERSCORE);

for(ULONG i=0; i<m_cColumns; i++)
{
ASSERT(m_rgColDesc);
MakeAlphaNumeric(m_rgColDesc[i].wszColName, UNDERSCORE);
}

for(i=0; i<m_cIndexes; i++)
{
ASSERT(m_rgIndexInfo);
MakeAlphaNumeric(m_rgIndexInfo[i].wszIndexName, UNDERSCORE);
MakeAlphaNumeric(m_rgIndexInfo[i].wszColName, UNDERSCORE);
}

return TRUE;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::GetLiteralInfo
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::GetLiteralInfo()
{
ASSERT(m_pCDataSource);
ASSERT(m_pCDataSource->m_pIDBInitialize);

HRESULT hr;

const ULONG cLiterals = 2;
DBLITERAL rgLiterals[cLiterals] = {DBLITERAL_QUOTE, DBLITERAL_CATALOG_SEPARATOR};

IDBInfo* pIDBInfo = NULL;

ULONGcLiteralInfo = 0;
DBLITERALINFO* rgLiteralInfo = NULL;
WCHAR* pwszCharBuffer = NULL;

//Obtain IDBInfo interface
XTESTC(hr = m_pCDataSource->m_pIDBInitialize->QueryInterface(IID_IDBInfo, (void **)&pIDBInfo));

//GetLiteralInfo
XTESTC(hr = pIDBInfo->GetLiteralInfo(cLiterals, rgLiterals, &cLiteralInfo,&rgLiteralInfo, &pwszCharBuffer));

//DBLITERAL_QUOTE
if(rgLiteralInfo[0].fSupported)
wcscpy(m_wszIDQuote, rgLiteralInfo[0].pwszLiteralValue);

//DBLITERAL_CATALOG_SEPARATOR
if(rgLiteralInfo[1].fSupported)
wcscpy(m_wszIDSeperator, rgLiteralInfo[1].pwszLiteralValue);

CLEANUP:
SAFE_RELEASE(pIDBInfo);
SAFE_FREE(rgLiteralInfo);
SAFE_FREE(pwszCharBuffer);
return hr;
}

/////////////////////////////////////////////////////////////////
// BOOL CTable::GetQuotedID
//
/////////////////////////////////////////////////////////////////
BOOL CTable::GetQuotedID(WCHAR* pwszOutBuff, WCHAR* pwszInBuff)
{
return ::GetQuotedID(pwszOutBuff, pwszInBuff, m_wszIDQuote, m_wszIDSeperator);
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::GetColInfo
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::GetColInfo()
{
ASSERT(m_pCDataSource);
ASSERT(m_pCDataSource->m_pICommandText);
HRESULT hr;

WCHAR     wszSqlStmt[MAX_QUERY_LEN]; // from SELECT statement
IColumnsInfo* pIColumnsInfo = NULL;

ULONG cColumns = 0;
DBCOLUMNINFO* rgColInfo = NULL;
WCHAR*rgStringBuffer = NULL;

IRowset* pIRowset = NULL;
LONG cRowsAffected = 0;

ULONG i;
//Create the SELECT statment, BOGUS WHERE Clause
CreateSQLStmt(ESQL_SELECT_BOGUS, wszSqlStmt, FALSE);

//Set the command text
XTESTC(hr = m_pCDataSource->m_pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));

// Execute the query and retrieve the results
XTESTC(hr = m_pCDataSource->m_pICommandText->Execute(NULL, IID_IRowset, NULL, &cRowsAffected, (IUnknown **)&pIRowset));

//Now finally GetColInfo
XTESTC(hr = pIRowset->QueryInterface(IID_IColumnsInfo, (void **)&pIColumnsInfo));
XTESTC(hr = pIColumnsInfo->GetColumnInfo(&cColumns, &rgColInfo, &rgStringBuffer));

//Loop through the ColInfo and Copy to our ColDesc
m_cColumns = 0;
m_cBlobs = 0;

for(i=0; i<cColumns; i++)
{
//ignore Bookmark columns
if(rgColInfo[i].iOrdinal == 0)
continue;

//Don't need to alloc the rgColDesc struct, since that should have already
//been done in the selection phase of Step1, and the ColName
//Should have already been copied as well, but make sure
ASSERT(m_rgColDesc);
ASSERT(wcscmp(m_rgColDesc[m_cColumns].wszColName, rgColInfo[i].pwszName)==0);

//Now copy the rest of the info
//DBCOLUMNINFO
m_rgColDesc[m_cColumns].iOrdinal= rgColInfo[i].iOrdinal;
m_rgColDesc[m_cColumns].ulColumnSize= rgColInfo[i].ulColumnSize;
m_rgColDesc[m_cColumns].wType= rgColInfo[i].wType;
m_rgColDesc[m_cColumns].dwFlags= rgColInfo[i].dwFlags;
m_rgColDesc[m_cColumns].bPrecision= rgColInfo[i].bPrecision;
m_rgColDesc[m_cColumns].bScale= rgColInfo[i].bScale;

//BLOB Columns
if(rgColInfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG)
m_cBlobs++;

m_cColumns++;
}

CLEANUP:
SAFE_RELEASE(pIRowset);
SAFE_RELEASE(pIColumnsInfo);

SAFE_FREE(rgColInfo);
SAFE_FREE(rgStringBuffer);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::GetTypeInfo
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::GetTypeInfo()
{
ASSERT(m_pCDataSource);
HRESULT hr;

IRowset*pIRowset = NULL;
IAccessor*pIAccessor = NULL;
HACCESSORhAccessor = DB_NULL_HACCESSOR;

ULONGi;
ULONGcRowsObtained = 0;
HROW*rghRows = NULL;

//Current SchemaInfo for each type, until we find the correct match
TYPEINFO TypeInfo;
TYPEINFO* rgTypeInfo = NULL;
ULONG* rgMatch = NULL;

//Arrays to store best TypeInfo
SAFE_ALLOC(rgTypeInfo, TYPEINFO, m_cColumns);
SAFE_ALLOC(rgMatch, ULONG, m_cColumns);
memset(rgMatch, NO_MATCH, m_cColumns*sizeof(ULONG));

//Get ProviderTypes rowset IDBSchemaRowset
TESTC(hr = GetTypeInfoRowset(&pIAccessor, &hAccessor, &pIRowset));

//Loop over all the Schema TypeInfo rowset
//And match up with ColInfo
while(TRUE)
{
hr = pIRowset->GetNextRows(NULL,0,MAX_BLOCK_SIZE,&cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

//Loop over the BLOCK of rows obtained
for (i=0; i<cRowsObtained; i++)
{
//Reset all the TypeInfo fields
memset(&TypeInfo, 0, sizeof(TYPEINFO));

//Put the data for one type into the TypeInfo Struct
XTESTC(hr = pIRowset->GetData(rghRows[i],hAccessor, (void*)&TypeInfo));

//Loop over all the columns and see if they match this type
for(ULONG iCol=0; iCol<m_cColumns; iCol++)
{
ASSERT(m_rgColDesc);

//Only try matching if this is the correct type and
//the column doesn't already have a perfect match
if(TypeInfo.wType != m_rgColDesc[iCol].wType || (rgMatch[iCol] & MATCH_EXACT))
continue;

//A Nullable type cannot be mapped to a non-Nullable type
if(m_rgColDesc[iCol].fIsNullable && !TypeInfo.fIsNullable)
continue;

//If never matched before, we at least know they match by type
if(!rgMatch[iCol])
{
rgMatch[iCol] |= MATCH_TYPE;
rgTypeInfo[iCol] = TypeInfo;
}

// Exact type/size matches take precedence
 if(COLINFO_SIZE(m_rgColDesc[iCol]) == TypeInfo.ulColumnSize)
{
rgMatch[iCol] |= MATCH_EXACT;
rgTypeInfo[iCol] = TypeInfo;
}

// Otherwise try best fit size
if(COLINFO_SIZE(m_rgColDesc[iCol]) < TypeInfo.ulColumnSize &&
TypeInfo.ulColumnSize < rgTypeInfo[iCol].ulColumnSize)
{
rgMatch[iCol] |= MATCH_SIZE;
rgTypeInfo[iCol] = TypeInfo;
}
}
}

//Release this group of rows
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained,rghRows,NULL,NULL,NULL));
SAFE_FREE(rghRows);
}

//Now that we have the TypeInfo matched, fill in our ColDesc struct
for(i=0; i<m_cColumns; i++)
{
ASSERT(m_rgColDesc);
if(rgMatch[i])
{
//TYPEINFO
wcscpy(m_rgColDesc[i].wszTypeName, rgTypeInfo[i].wszTypeName);
m_rgColDesc[i].ulCreateParams= GetCreateParams(rgTypeInfo[i].wszCreateParams);
m_rgColDesc[i].fIsNullable= rgTypeInfo[i].fIsNullable == VARIANT_TRUE;
m_rgColDesc[i].guidType= rgTypeInfo[i].guidType;
}
else
{
wMessageBox(NULL, MB_ICONEXCLAMATION | MB_OK, wsz_ERROR,
wsz_NO_TYPE_FOUND_, GetDBTypeName(m_rgColDesc[i].wType));
}
}

CLEANUP:
if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor,NULL));

SAFE_RELEASE(pIAccessor);
SAFE_RELEASE(pIRowset);

SAFE_FREE(rgMatch);
SAFE_FREE(rgTypeInfo);

SAFE_FREE(rghRows);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::MapTableInfo
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::MapTableInfo(CTable* pCSourceTable)
{
ASSERT(pCSourceTable);
ASSERT(m_pCDataSource);
HRESULT hr;

IAccessor* pIAccessor = NULL;
HACCESSOR  hAccessor = DB_NULL_HACCESSOR;

IRowset* pIRowset = NULL;

ULONGcRowsObtained = 0;
HROW*rghRows = NULL;

//Match bitmask, indicating what type of match was found
BOOL fMatchedAll = FALSE;

TYPEINFO TypeInfo;
TYPEINFO* rgTypeInfo = NULL;
ULONG* rgMatch = NULL;

//ColumnInfo
m_cColumns= pCSourceTable->m_cColumns;
SAFE_FREE(m_rgColDesc);
SAFE_ALLOC(m_rgColDesc, COLDESC, m_cColumns);
memcpy(m_rgColDesc,pCSourceTable->m_rgColDesc, m_cColumns * sizeof(COLDESC));

//IndexInfo
m_cIndexes= pCSourceTable->m_cIndexes;
SAFE_FREE(m_rgIndexInfo);
SAFE_ALLOC(m_rgIndexInfo, INDEXINFO, m_cIndexes);
memcpy(m_rgIndexInfo, pCSourceTable->m_rgIndexInfo, m_cIndexes * sizeof(INDEXINFO));

//Arrays to store best TypeInfo
SAFE_ALLOC(rgTypeInfo, TYPEINFO, m_cColumns);
SAFE_ALLOC(rgMatch, ULONG, m_cColumns);
memset(rgMatch, NO_MATCH, m_cColumns*sizeof(ULONG));

//Get ProviderTypes rowset IDBSchemaRowset
TESTC(hr = GetTypeInfoRowset(&pIAccessor, &hAccessor, &pIRowset));

//Loop until all types are matched.
//We may not find a match, which promotes the type to the next higher
//type, which will require another cycle to match that type...
while(!fMatchedAll)
{
//Get data for each row in rowset
XTESTC(hr = pIRowset->RestartPosition(NULL));

//Loops over the entire SchemaRowset
while(TRUE)
{
hr = pIRowset->GetNextRows(NULL, 0, MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained == 0)
break;

//Loop over the BLOCK of rows obtained
for (ULONG i=0; i<cRowsObtained; i++)
{
ASSERT(m_rgColDesc);

//Reset all the TypeInfo fields
memset(&TypeInfo, 0, sizeof(TYPEINFO));

//Put the data for one type into the TypeInfo Struct
XTESTC(hr = pIRowset->GetData(rghRows[i], hAccessor, (void *)&TypeInfo));

//Loop over the columns and get TypeInfo
for(ULONG iCol=0; iCol<m_cColumns; iCol++)
{
//Only try matching if this is the correct type and
//the column doesn't already have a perfect match
if(TypeInfo.wType != m_rgColDesc[iCol].wType || (rgMatch[iCol] & MATCH_EXACT))
continue;

//A Nullable type cannot be mapped to a non-Nullable type
if(m_rgColDesc[iCol].fIsNullable && !TypeInfo.fIsNullable)
continue;

//If never matched before, we at least know they match by type
if(!rgMatch[iCol])
{
rgMatch[iCol] |= MATCH_TYPE;
rgTypeInfo[iCol] = TypeInfo;
}

// Exact type/size matches take precedence
if(COLINFO_SIZE(m_rgColDesc[iCol]) == TypeInfo.ulColumnSize)
{
rgMatch[iCol] |= MATCH_EXACT;
rgTypeInfo[iCol] = TypeInfo;
}

// Otherwise try best fit size
if(COLINFO_SIZE(m_rgColDesc[iCol]) < TypeInfo.ulColumnSize &&
TypeInfo.ulColumnSize < rgTypeInfo[iCol].ulColumnSize)
{
rgMatch[iCol] |= MATCH_SIZE;
rgTypeInfo[iCol] = TypeInfo;
}
}
}

//Release this group of rows
XTESTC(hr = pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

// See if every type has a match
fMatchedAll = TRUE;
for(ULONG i=0; i<m_cColumns; i++)
{
ASSERT(m_rgColDesc);

// If not we will have to promote a type and try again
if(rgMatch[i])
{
//If found a match fill in the TypeInfo fileds of our ColDesc
wcscpy(m_rgColDesc[i].wszTypeName, rgTypeInfo[i].wszTypeName);
m_rgColDesc[i].ulCreateParams= GetCreateParams(rgTypeInfo[i].wszCreateParams);
m_rgColDesc[i].fIsNullable= rgTypeInfo[i].fIsNullable == VARIANT_TRUE;
}
else
{
fMatchedAll = FALSE;

//Try to promote it to the next largest type
if(!GetPromotedType(&m_rgColDesc[i].wType))
{
//If unable to promote, we are out of luck
wMessageBox(NULL, MB_ICONEXCLAMATION | MB_OK, wsz_ERROR,
wsz_NO_TYPE_MATCH_, GetDBTypeName(m_rgColDesc[i].wType));
goto CLEANUP;
}
}
}
}

CLEANUP:
if(hAccessor && pIAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor, NULL));

SAFE_RELEASE(pIAccessor);
SAFE_RELEASE(pIRowset);

SAFE_FREE(rgMatch);
SAFE_FREE(rgTypeInfo);

SAFE_FREE(rghRows);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::CreateTable
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::CreateTable()
{
ASSERT(m_pCDataSource);
ASSERT(m_pCDataSource->m_pICommandText);
HRESULT hr;

BSTRbstrSqlState = NULL;
WCHARwszSqlStmt[MAX_QUERY_LEN];// Create table statement

ULONG cRecords = 0;
IErrorRecords* pIErrorRecords = NULL;
ICommandText* pICommandText = NULL;

Busy();

// Setup the initialize CREATE TABLE '<CTableName>'
CreateSQLStmt(ESQL_CREATE_TABLE, wszSqlStmt);

// Set the Command Text
pICommandText = m_pCDataSource->m_pICommandText;
XTESTC(hr = pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));

// Execute the command
hr = pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL);

// If this didn't work, then we need to display messages, and if the
// error was a duplicate table, offer to drop.
if(FAILED(hr))
{
//Get the Error Records, need to save them, since every call
//cleans the previous error objects
TESTC(GetErrorRecords(&cRecords, &pIErrorRecords));

//If Error was due to an existing table, just ask to drop it
if(GetSqlErrorInfo(0, pIErrorRecords, &bstrSqlState)==S_OK &&
bstrSqlState && wcscmp(bstrSqlState, L"S0001")==0)
{
WCHAR wszBuffer[MAX_QUERY_LEN];

//If the user doesn't wants to drop it, exit
if(IDNO == wMessageBox(NULL, MB_ICONQUESTION | MB_YESNO, wsz_ERROR,
wsz_ASK_DROP_TABLE_, m_wszQualTableName))
goto CLEANUP;

//Otherwise drop that table and continue
CreateSQLStmt(ESQL_DROP_TABLE, wszBuffer);

//Drop the existing Table
XTESTC(hr = pICommandText->SetCommandText(DBGUID_DBSQL, wszBuffer));
XTESTC(hr = pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL));

//Now reset the CreateTable text to the SqlStmt and Execute
XTESTC(hr = pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));
XTESTC(hr = pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL));
}
else
{
//Otherwsie unknown error, just display it to the user
DisplayErrorRecords(NULL, cRecords, pIErrorRecords);
}
}

CLEANUP:
Busy();
SAFE_SYSFREE(bstrSqlState);
SAFE_RELEASE(pIErrorRecords);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::CopyIndexes
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::CopyIndexes(CTable* pCTable)
{
ASSERT(pCTable);
HRESULT hr;

WCHARwszBuffer[MAX_NAME_LEN];
WCHARwszSqlStmt[MAX_QUERY_LEN];
ULONG i;

//Copy Index Info from Source table
m_cIndexes= pCTable->m_cIndexes;

//Array to indicate which index/columns we have used
ULONG* rgIndexUsed = NULL;
SAFE_ALLOC(rgIndexUsed, ULONG, m_cIndexes);
memset(rgIndexUsed, 0, m_cIndexes * sizeof(ULONG));

SAFE_FREE(m_rgIndexInfo);
SAFE_ALLOC(m_rgIndexInfo, INDEXINFO, m_cIndexes);
memcpy(m_rgIndexInfo, pCTable->m_rgIndexInfo, m_cIndexes * sizeof(INDEXINFO));

// Loop around each index that is valid.  See if any are to be created
for(i=0; i<m_cIndexes; i++)
{
//If this index has already been created, skip
//might have been used in another index creation
if(rgIndexUsed[i])
continue;

//If this index is used as a primary key, skip
//PrimaryKeys are taken care of differently
if(m_rgIndexInfo[i].fIsPrimaryKey)
continue;

//"CREATE <UNIQUE> INDEX "
swprintf(wszSqlStmt, wsz_CREATE_INDEX_, (m_rgIndexInfo[i].fUnique) ? wsz_UNIQUE_INDEX : wsz_SPACE);

//Add IndexName
GetQuotedID(wszBuffer, m_rgIndexInfo[i].wszIndexName);
wcscat(wszSqlStmt, wszBuffer);

//Add CTableName
wcscat(wszSqlStmt, L" ON ");
GetQuotedID(wszBuffer, m_wszQualTableName);
wcscat(wszSqlStmt, wszBuffer);

wcscat(wszSqlStmt, wsz_LPAREN);

// Now loop through the indexes and find all columns that
// belong to this index
for(ULONG iCol=i; iCol<m_cIndexes; iCol++)
{
//If not the same index skip
if(wcscmp(m_rgIndexInfo[i].wszIndexName, m_rgIndexInfo[iCol].wszIndexName)!=0)
continue;

//mark this Index as used
rgIndexUsed[iCol] = TRUE;

// Add the column to the list
GetQuotedID(wszBuffer, m_rgIndexInfo[iCol].wszColName);
wcscat(wszSqlStmt, wszBuffer);

// Indicate Asending or Decending
if(m_rgIndexInfo[iCol].dwCollation == DB_COLLATION_DESC)
wcscat(wszSqlStmt, wsz_INDEX_DESC);

//Add trailing "," between col names
wcscat(wszSqlStmt, wsz_COMMA);
}

//Replace last trailing "," with a ")"
wcscpy(&wszSqlStmt[wcslen(wszSqlStmt)-wcslen(wsz_COMMA)], wsz_RPAREN);

// If user wants to see the statement, show it to them
if(m_pCWizard->m_pCTableCopy->m_fShowQuery)
wMessageBox(NULL, MB_OK | MB_ICONINFORMATION, wsz_SHOW_SQL_TITLE,
wsz_SHOW_SQL_, m_pCDataSource->m_pwszDataSource, wszSqlStmt);

//Set the command text
XTESTC(hr = m_pCDataSource->m_pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));

//Execute the command
//Don't exit yet, the user might want to continue even though this index failed
XTEST(hr = m_pCDataSource->m_pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL));

//If INDEX Failed
if(FAILED(hr))
{
//Index Failed, Do you want to Continue?
if(IDNO == wMessageBox(NULL, MB_ICONINFORMATION | MB_YESNO, wsz_ERROR,
wsz_INDEX_FAILED_, m_rgIndexInfo[i].wszIndexName))
goto CLEANUP;
}

//Since the User didn't exit, continue as normal
hr = S_OK;
}

CLEANUP:
Busy();
SAFE_FREE(rgIndexUsed);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::CreateSQLStmt
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::CreateSQLStmt(ESQL_STMT eSqlStmt, WCHAR* pwszSqlStmt, BOOL fShowSql)
{
ASSERT(pwszSqlStmt);
HRESULT hr = S_OK;
WCHAR     wszBuffer[MAX_NAME_LEN*2]; // Buffer

switch(eSqlStmt)
{
//SELECT <ColumnList> FROM <QualifiedTableName>
case ESQL_SELECT:
{
//Create the SELECT statment
wcscpy(pwszSqlStmt, wsz_SELECT);

// Loop through each column
for(ULONG i=0; i<m_cColumns; i++)
{
// Add the column to the list
GetQuotedID(wszBuffer, m_rgColDesc[i].wszColName);
wcscat(pwszSqlStmt, wszBuffer);

if(i<m_cColumns-1)
wcscat(pwszSqlStmt, wsz_COMMA);
}

// Add the Table Name
wcscat(pwszSqlStmt, wsz_FROM);
GetQuotedID(wszBuffer, m_wszQualTableName);
wcscat(pwszSqlStmt, wszBuffer);
}
break;

case ESQL_SELECT_BOGUS:
{
//Create the column list first in standard order
wcscpy(pwszSqlStmt, wsz_SELECT);

// Loop through each column
for(ULONG i=0; i<m_cColumns; i++)
{
GetQuotedID(wszBuffer, m_rgColDesc[i].wszColName);
wcscat(pwszSqlStmt, wszBuffer);

if(i<m_cColumns-1)
wcscat(pwszSqlStmt, wsz_COMMA);
}

// Add the table name
wcscat(pwszSqlStmt, wsz_FROM);
GetQuotedID(wszBuffer, m_wszQualTableName);
wcscat(pwszSqlStmt, wszBuffer);

// Finally, add a bogus WHERE clause. Because we want this
// statement to get only metadata, we'll try to tell the
// server not to actually get rows.
wcscat(pwszSqlStmt, wsz_BOGUS_WHERE);
}
break;

case ESQL_INSERT:
{
wcscpy(pwszSqlStmt, wsz_INSERT_INTO);

//Add the Table Name
GetQuotedID(wszBuffer, m_wszQualTableName);
wcscat(pwszSqlStmt, wszBuffer);
wcscat(pwszSqlStmt, wsz_LPAREN);

// Add the column list
for (ULONG i=0; i<m_cColumns; i++)
{
//Only Bind updatable columns
if(m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITE || m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITEUNKNOWN)
{
// Quote each column name if required
GetQuotedID(wszBuffer, m_rgColDesc[i].wszColName);
wcscat(pwszSqlStmt, wszBuffer);

// Now add ", "
if(i<m_cColumns-1)
wcscat(pwszSqlStmt, wsz_COMMA);
}
}

// And finally the values list, which will just be parameter markers
wcscat(pwszSqlStmt, wsz_VALUES_CLAUSE);

// Loop through each column
for(i=0; i<m_cColumns; i++)
{
//Only Bind those columns that are updatable
if(m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITE || m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITEUNKNOWN)
{
wcscat(pwszSqlStmt, wsz_PARAM);
if(i<m_cColumns-1)
wcscat(pwszSqlStmt, wsz_COMMA);
}
}

// Finish off the string
wcscat(pwszSqlStmt, wsz_RPAREN);
}
break;

case ESQL_CREATE_TABLE:
{

// Setup the initialize CREATE TABLE '<CTableName>'
wcscpy(pwszSqlStmt, wsz_CREATE_TABLE);
GetQuotedID(wszBuffer, m_wszQualTableName);
wcscat(pwszSqlStmt, wszBuffer);

// Setup (<ColList>)
wcscat(pwszSqlStmt, wsz_LPAREN);

// Loop through each column and format the column name, type, and precision
for(ULONG i=0; i<m_cColumns; i++)
{
// Add the ColumnName
GetQuotedID(wszBuffer, m_rgColDesc[i].wszColName);
wcscat(pwszSqlStmt, wszBuffer);

// Add the ColumnType
wcscat(pwszSqlStmt, wsz_SPACE);
wcscat(pwszSqlStmt, m_rgColDesc[i].wszTypeName);

// If required, add the precision and scale information
if(m_rgColDesc[i].ulCreateParams & CP_LENGTH)
{
swprintf(wszBuffer, L"(%lu)", COLINFO_SIZE(m_rgColDesc[i]));
wcscat(pwszSqlStmt, wszBuffer);
}
else if(m_rgColDesc[i].ulCreateParams & CP_PRECISION &&
m_rgColDesc[i].ulCreateParams & CP_SCALE)
{
swprintf(wszBuffer, L"(%lu,%lu)", COLINFO_SIZE(m_rgColDesc[i]), m_rgColDesc[i].bScale);
wcscat(pwszSqlStmt, wszBuffer);
}

//Add PRIMARY KEY if Supported and a PrimaryKey Column
if(m_pCDataSource->m_fPrimaryKeysSupported)
{
BOOL fPrimaryKey = FALSE;

//Loop through the index info and see if this Column
//is supposed to be a primary key of the table
for(ULONG iIndex=0; iIndex<m_cIndexes; iIndex++)
if(m_rgIndexInfo[iIndex].fIsPrimaryKey && wcscmp(m_rgIndexInfo[iIndex].wszColName, m_rgColDesc[i].wszColName)==0)
fPrimaryKey = TRUE;

//If PrimaryKey, add PRIMARY KEY info
if(fPrimaryKey)
wcscat(pwszSqlStmt, wsz_PRIMARY_KEY);
}

if(i<m_cColumns-1)
wcscat(pwszSqlStmt, wsz_COMMA);
}

//Add trailing ")"
wcscat(pwszSqlStmt, wsz_RPAREN);
}
break;

case ESQL_DROP_TABLE:
{
swprintf(pwszSqlStmt, wsz_DROP_TABLE_, m_wszQualTableName);
}
break;

default:
ASSERT(!"Unhandled Case!");
break;
};

// If user wants to see the statement, show it to them
if(m_pCWizard->m_pCTableCopy->m_fShowQuery && fShowSql)
wMessageBox(NULL, MB_OK | MB_ICONINFORMATION, wsz_SHOW_SQL_TITLE,
wsz_SHOW_SQL_, m_pCDataSource->m_pwszDataSource, pwszSqlStmt);

return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::GetRowset
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::GetRowset(REFIID riid, IUnknown** ppIRowset, HACCESSOR* phAccessor, ULONG* pRowSize, ULONG ulBlobSize)
{
ASSERT(ppIRowset);
ASSERT(m_pCDataSource);
ASSERT(m_pCDataSource->m_pICommandText);

HRESULT hr;

WCHAR*pwszCol = NULL;// String manipulation
    WCHARwszSqlStmt[MAX_QUERY_LEN];// Format the select statement

ULONGi;// Column index

ULONG ulOffset = 0;
ULONG cBindings = 0;
DBBINDING* rgBindings = NULL;

const ULONG cPropSets = 1;
DBPROPSET rgPropSets[cPropSets];
const ULONG cProperties = 3;
DBPROP rgProperties[cProperties];

rgPropSets[0].cProperties = cProperties;
rgPropSets[0].rgProperties = rgProperties;
rgPropSets[0].guidPropertySet = DBPROPSET_ROWSET;

    Busy();

ICommandProperties* pICommandProperties = NULL;
IAccessor* pIAccessor = NULL;

//Alloc the space to hold the Bindings
SAFE_ALLOC(rgBindings, DBBINDING, m_cColumns);

//Create the SELECT statment
CreateSQLStmt(ESQL_SELECT, wszSqlStmt);

// Now execute the select statement
LONGcRowsAffected;

rgPropSets[0].cProperties = 0;
if(m_cBlobs)
{
//Kagera's Implementation requires IID_RowsetLocate for BLOB Support
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwPropertyID= DBPROP_IRowsetLocate;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwOptions= DBPROPOPTIONS_REQUIRED;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwStatus= DBPROPSTATUS_OK;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].colid= DB_NULLID;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].vValue.vt= VT_BOOL;
V_BOOL(&rgPropSets[0].rgProperties[rgPropSets[0].cProperties].vValue) = VARIANT_TRUE;
rgPropSets[0].cProperties++;
}

if(riid == IID_IRowsetChange)
{
//Set UPDATABILITY for IID_IRowsetChange
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwPropertyID= DBPROP_UPDATABILITY;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwOptions= DBPROPOPTIONS_REQUIRED;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].dwStatus= DBPROPSTATUS_OK;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].colid= DB_NULLID;
rgPropSets[0].rgProperties[rgPropSets[0].cProperties].vValue.vt= VT_I4;
V_I4(&rgPropSets[0].rgProperties[rgPropSets[0].cProperties].vValue) = DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT;
rgPropSets[0].cProperties++;
}

//Set any properties

XTESTC(hr = m_pCDataSource->m_pICommandText->QueryInterface(IID_ICommandProperties, (void**)&pICommandProperties));
XTESTC(hr = pICommandProperties->SetProperties(cPropSets, rgPropSets));

//Set the command text
XTESTC(hr = m_pCDataSource->m_pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));

//Execute the Command
XTESTC(hr = m_pCDataSource->m_pICommandText->Execute(NULL, riid, NULL, &cRowsAffected, (IUnknown**)ppIRowset));

// Prepare the bindings and create the accessor for each column
XTESTC(hr = (*ppIRowset)->QueryInterface(IID_IAccessor, (void **)&pIAccessor));

ulOffset = 0;
cBindings = 0;
for(i=0; i<m_cColumns; i++)
{
//SetUp the Bindings
rgBindings[cBindings].iOrdinal= i+1;
rgBindings[cBindings].obStatus= ulOffset;
rgBindings[cBindings].obLength= ulOffset + sizeof(DBSTATUS);
rgBindings[cBindings].obValue= ulOffset + sizeof(DBSTATUS) + sizeof(ULONG);

rgBindings[cBindings].pTypeInfo = NULL;
rgBindings[cBindings].pBindExt  = NULL;

rgBindings[cBindings].dwPart= DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
rgBindings[cBindings].dwMemOwner= DBMEMOWNER_CLIENTOWNED;
rgBindings[cBindings].eParamIO= DBPARAMIO_NOTPARAM;

rgBindings[cBindings].dwFlags= 0;
rgBindings[cBindings].bPrecision= m_rgColDesc[i].bPrecision;
rgBindings[cBindings].bScale= m_rgColDesc[i].bScale;

rgBindings[cBindings].pObject= NULL;
rgBindings[cBindings].wType= m_rgColDesc[i].wType;
rgBindings[cBindings].cbMaxLen= m_rgColDesc[i].ulColumnSize;

//Adjust ISLONG Columns if not bound as ISeqStream
if(m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_ISLONG)
ADJUST_SIZE(rgBindings[cBindings].cbMaxLen, ulBlobSize);

ulOffset = ROUNDUP(rgBindings[cBindings].obValue + rgBindings[cBindings].cbMaxLen);
cBindings++;
}

//Size for pData
if(pRowSize)
*pRowSize = ulOffset;

//Create the accessor for the entire row
if(phAccessor)
XTESTC(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, phAccessor, NULL));

CLEANUP:
Busy();
FreeBindings(cBindings, rgBindings);
SAFE_RELEASE(pICommandProperties);
SAFE_RELEASE(pIAccessor);
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTable::CopyData
//
/////////////////////////////////////////////////////////////////
HRESULT CTable::CopyData(CTable* pCTable, ULONG* pcRows, ULONG ulParamSets, ULONG ulBlobSize)
{
ASSERT(pCTable && pcRows);
ASSERT(m_pCDataSource);
ASSERT(m_pCDataSource->m_pICommandText);
HRESULT hr;

WCHARwszSqlStmt[MAX_QUERY_LEN];// Format the select statement
WCHARwszBuffer[MAX_NAME_LEN];

ULONGi,iCol;// Column index
    ULONGulOffset = 0;
ULONGcBindings = 0;
DBBINDING*rgBindings = NULL;

IAccessor* pIAccessor = NULL;
HACCESSOR  hAccessor = DB_NULL_HACCESSOR;

ULONG cRowSize = 0;

IRowsetChange* pIRowsetChange = NULL;

ULONG cRowsObtained = 0;
HROW* rghRows = NULL;

DBPARAMS DBParams;

void* pData = NULL;
ULONG cRows = 0;
ULONG cMaxRows = *pcRows;

    Busy();

HACCESSOR hFromAccessor = DB_NULL_HACCESSOR;
IRowset* pIFromRowset = NULL;

//Get the Rowset from the FromCTable
TESTC(hr = pCTable->GetRowset(IID_IRowset, (IUnknown**)&pIFromRowset, &hFromAccessor, &cRowSize, ulBlobSize));

//Obtain the Accessor
XTESTC(hr = m_pCDataSource->m_pICommandText->QueryInterface(IID_IAccessor, (void **)&pIAccessor));
SAFE_ALLOC(rgBindings, DBBINDING, m_cColumns);

cBindings = 0;
for(i=0; i<m_cColumns; i++)
{
rgBindings[cBindings].iOrdinal= ulParamSets ? cBindings+1 : i+1;
rgBindings[cBindings].obStatus  = ulOffset;
rgBindings[cBindings].obLength  = ulOffset + sizeof(DBSTATUS);
rgBindings[cBindings].obValue   = ulOffset + sizeof(ULONG) + sizeof(DBSTATUS);

rgBindings[cBindings].pTypeInfo = NULL;
rgBindings[cBindings].pBindExt  = NULL;

rgBindings[cBindings].dwPart= DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
rgBindings[cBindings].dwMemOwner= DBMEMOWNER_CLIENTOWNED;
rgBindings[cBindings].eParamIO= ulParamSets ? DBPARAMIO_INPUT : DBPARAMIO_NOTPARAM;
rgBindings[cBindings].dwFlags= 0;

rgBindings[cBindings].bPrecision= pCTable->m_rgColDesc[i].bPrecision;
rgBindings[cBindings].bScale= pCTable->m_rgColDesc[i].bScale;

rgBindings[cBindings].pObject= NULL;
rgBindings[cBindings].wType= m_rgColDesc[i].wType;
rgBindings[cBindings].cbMaxLen= pCTable->m_rgColDesc[i].ulColumnSize;

//Adjust ISLONG Columns if not bound as ISeqStream
if(m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_ISLONG)
ADJUST_SIZE(rgBindings[cBindings].cbMaxLen, ulBlobSize);

ulOffset = ROUNDUP(rgBindings[cBindings].obValue + rgBindings[cBindings].cbMaxLen);

//Only Bind Updatable columns
//Note, we are not using the Source info here, since in the process
//of adjusting columns to a different DSN, the columns may have become writeable
if(m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITE || m_rgColDesc[i].dwFlags & DBCOLUMNFLAGS_WRITEUNKNOWN)
cBindings++;
}

if (cBindings)
XTESTC(hr = pIAccessor->CreateAccessor(ulParamSets ? DBACCESSOR_PARAMETERDATA : DBACCESSOR_ROWDATA, cBindings,rgBindings, ulOffset, &hAccessor, NULL));

//If using Parameters to INSERT the Data
if(ulParamSets)
{
// Now create the INSERT INTO statment
CreateSQLStmt(ESQL_INSERT, wszSqlStmt, ulParamSets);

//Set the command text
XTESTC(hr = m_pCDataSource->m_pICommandText->SetCommandText(DBGUID_DBSQL, wszSqlStmt));
}
//Oterwise were using InsertRow
else
{
//Create the TargetRowset
TESTC(hr = GetRowset(IID_IRowsetChange, (IUnknown**)&pIRowsetChange, NULL, NULL, ulBlobSize));
}

// Display the progress dialog
ASSERT(m_pCWizard && m_pCWizard->m_pCProgress);
m_pCWizard->m_pCProgress->Display();
m_pCWizard->m_pCProgress->SetWorkingItem(wsz_COPYING);

//Alloc room for pData
SAFE_ALLOC(pData, BYTE, max(ulParamSets, MAX_BLOCK_SIZE) * cRowSize);

//Setup DBPARAMS Struct
DBParams.cParamSets = 1;//Numer of Parameter sets
DBParams.hAccessor= hAccessor;//Target Param Accessor
DBParams.pData= pData;//Source Data

while(cRows < cMaxRows)
{
hr = pIFromRowset->GetNextRows(NULL, 0, (ulParamSets > 1) ? ulParamSets : MAX_BLOCK_SIZE, &cRowsObtained, &rghRows);
if(FAILED(hr) || cRowsObtained==0)
break;

//Determine the number of rows that are actually needed to retrieve
//The user might have specfified the number of rows to retrieve which could
//be smaller than the number in the block size
ULONG cRowsNeeded = min(cRowsObtained, cMaxRows-cRows);

//Use Parameters to INSERT the data, use MultipleParamSets
if(ulParamSets > 1)
{
//GetData
for (i=0; i<cRowsNeeded; i++)
XTESTC(hr = pIFromRowset->GetData(rghRows[i], hFromAccessor, (BYTE*)pData + (i*cRowSize)));

//Adjust the paramerer sets
DBParams.cParamSets = cRowsNeeded;

//Execute the INSERT (multiple param sets)
XTESTC(hr = m_pCDataSource->m_pICommandText->Execute(NULL, IID_NULL, &DBParams, NULL, NULL));

// Update insert progress
swprintf(wszBuffer, wsz_COPIED_RECORDS, (cRows += cRowsNeeded));
if(!m_pCWizard->m_pCProgress->UpdateProgress(wsz_COPYING, wszBuffer))
goto CLEANUP;
}
//Use Paramseters to INSERT the data, but only 1 ParamSet (not multiple)
else if(ulParamSets == 1)
{
for (i=0; i<cRowsNeeded; i++)
{
//GetData
XTESTC(hr = pIFromRowset->GetData(rghRows[i], hFromAccessor, pData));

//Execute the INSERT
XTESTC(hr = m_pCDataSource->m_pICommandText->Execute(NULL, IID_NULL, &DBParams, NULL, NULL));

// Update insert progress
swprintf(wszBuffer, wsz_COPIED_RECORDS, cRows++);
if(!m_pCWizard->m_pCProgress->UpdateProgress(wsz_COPYING, wszBuffer))
goto CLEANUP;
}
}
//Use IRowsetChange::InsertRow to INSERT the Data
else
{
for (i=0; i<cRowsNeeded; i++)
{
//GetData from the Source
XTESTC(hr = pIFromRowset->GetData(rghRows[i], hFromAccessor, pData));

//Adjust the Status fields to be DBSTATUS_S_OK | DBSTATUS_S_ISNULL
//GetData mihgt have reported DB_S_TRUNCATED or other minor problems
for(iCol = 0; iCol < cBindings; iCol++)
if(STATUS_IS_BOUND(rgBindings[iCol]) && BINDING_STATUS(rgBindings[iCol], pData) != DBSTATUS_S_ISNULL)
BINDING_STATUS(rgBindings[iCol],pData) = DBSTATUS_S_OK;

//InsertRow to the Target
XTESTC(hr = pIRowsetChange->InsertRow(NULL, hAccessor, pData, NULL));

// Update insert progress
swprintf(wszBuffer, wsz_COPIED_RECORDS, cRows++);
if(!m_pCWizard->m_pCProgress->UpdateProgress(wsz_COPYING, wszBuffer))
goto CLEANUP;
}
}

//Release the group of rows
XTESTC(hr = pIFromRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL));
SAFE_FREE(rghRows);
}

CLEANUP:
//Stop the propgress
m_pCWizard->m_pCProgress->StopProgress();

Busy();
*pcRows = cRows;

//Release Accessors
if(m_pCDataSource->m_pICommandText && hAccessor)
XTEST(pIAccessor->ReleaseAccessor(hAccessor, NULL));
SAFE_RELEASE(pIAccessor);

//Release Accessors
if(pIFromRowset && hFromAccessor)
{
XTEST(pIFromRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor));
XTEST(pIAccessor->ReleaseAccessor(hFromAccessor, NULL));
}
SAFE_RELEASE(pIAccessor);
SAFE_RELEASE(pIFromRowset);
SAFE_RELEASE(pIRowsetChange);

FreeBindings(cBindings, rgBindings);
SAFE_FREE(rghRows);
SAFE_FREE(pData);
return hr;
}

/////////////////////////////////////////////////////////////////////////////
// HRESULT CTable::GetTypeInfoRowset
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CTable::GetTypeInfoRowset(IAccessor** ppIAccessor, HACCESSOR* phAccessor, IRowset** ppIRowset)
{
ASSERT(ppIAccessor && phAccessor && ppIRowset);
ASSERT(m_pCDataSource && m_pCDataSource->m_pIDBCreateCommand);
HRESULT hr;

IDBSchemaRowset* pIDBSchemaRowset = NULL;

//Bind all the columns from types rowset:
const ULONG cBindings = 6;
const DBBINDING rgBindings[cBindings] =
{
1,
offsetof(TYPEINFO, wszTypeName),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
MAX_NAME_LEN,// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_WSTR, // data type indicator
0,// precision
0, // scale

2,
offsetof(TYPEINFO, wType),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(USHORT),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI2, // data type indicator
0,// precision
0, // scale

3,
offsetof(TYPEINFO, ulColumnSize),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(ULONG),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_UI4, // data type indicator
0,// precision
0, // scale

6,
offsetof(TYPEINFO, wszCreateParams),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
MAX_NAME_LEN,// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_WSTR, // data type indicator
0,// precision
0, // scale

7,
offsetof(TYPEINFO, fIsNullable),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(VARIANT_BOOL),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_BOOL, // data type indicator
0,// precision
0, // scale

16,
offsetof(TYPEINFO, guidType),// offset of value in consumers buffer
0,// offset of length
0,// offset of status
NULL,// reserved
NULL,// for ole object
 NULL,// reserved
DBPART_VALUE,// specifies Value is bound only
DBMEMOWNER_CLIENTOWNED,// memory is client owned
DBPARAMIO_NOTPARAM,//
sizeof(GUID),// size in bytes of the value part in the consumers buffer
0, // reserved
DBTYPE_GUID, // data type indicator
0,// precision
0, // scale
};

//Return if this interface is not supported
XTESTC(hr = m_pCDataSource->m_pIDBCreateCommand->QueryInterface(IID_IDBSchemaRowset, (void **)&pIDBSchemaRowset));

//GetRowset
//DBSCHEMA_PROVIDER_TYPES is required a SCHEMA
XTESTC(hr = pIDBSchemaRowset->GetRowset(NULL, DBSCHEMA_PROVIDER_TYPES, 0, NULL, IID_IRowset,0, NULL, (IUnknown**)ppIRowset));

//Create the the Accessor
XTESTC(hr = (*ppIRowset)->QueryInterface(IID_IAccessor, (void **)ppIAccessor));
XTESTC(hr = (*ppIAccessor)->CreateAccessor(DBACCESSOR_ROWDATA, cBindings, rgBindings, 0, phAccessor, NULL));

CLEANUP:
SAFE_RELEASE(pIDBSchemaRowset);
return hr;
}


TABLE.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module TABLE.H
//
//-----------------------------------------------------------------------------
#ifndef _TABLE_H_
#define _TABLE_H_

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "DataSource.h"
#include "TableCopy.h"

/////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////
enum ESQL_STMT
{
ESQL_SELECT,
ESQL_SELECT_BOGUS,
ESQL_INSERT,
ESQL_CREATE_TABLE,
ESQL_DROP_TABLE
};

/////////////////////////////////////////////////////////////////
// Structs for Column/Index info
//
/////////////////////////////////////////////////////////////////
struct TYPEINFO
{
WCHARwszTypeName[MAX_NAME_LEN];
USHORTwType;
ULONGulColumnSize;
WCHARwszCreateParams[MAX_NAME_LEN];
VARIANT_BOOLfIsNullable;
GUIDguidType;
};

struct INDEXINFO
{
WCHARwszIndexName[MAX_NAME_LEN];
WCHARwszColName[MAX_NAME_LEN];
DWORDdwCollation;
ULONGiOrdinal;
BOOLfUnique;
BOOLfIsPrimaryKey;
GUIDguidCol;
};

struct PRIMARYKEY
{
WCHARwszColName[MAX_NAME_LEN];
ULONGiOrdinal;
GUIDguidCol;
};

struct SCHEMATABLES
{
WCHARwszUserName[MAX_NAME_LEN];
WCHARwszTableName[MAX_NAME_LEN];
};

struct SCHEMACOLUMNS
{
WCHARwszColName[MAX_NAME_LEN];
ULONGiOrdinal;
GUIDguidCol;
DBCOLUMNFLAGSdwFlags;
USHORTwType;
ULONGulMaxLength;
USHORTwPrecision;
USHORTwScale;
GUIDguidType;
};

struct COLDESC
{
//DBCOLUMNINFO
WCHARwszColName[MAX_NAME_LEN];
ULONGiOrdinal;
DBCOLUMNFLAGSdwFlags;
ULONGulColumnSize;
DBTYPEwType;
BYTEbPrecision;
BYTEbScale;

//SCHEMACOLUMNS
GUIDguidCol;

//TYPEINFO
WCHARwszTypeName[MAX_NAME_LEN];
ULONGulCreateParams;
BOOLfIsNullable;
GUIDguidType;
};

/////////////////////////////////////////////////////////////////
// CTable
//
/////////////////////////////////////////////////////////////////
class CTable
{
public:
//Constructors
CTable(CWizard* pCWizard);
virtual ~CTable();

//Members
virtual BOOLConnect(HWND hWnd);
virtual BOOLIsConnected();

virtual BOOLTranslateNames();
virtual BOOLGetQuotedID(WCHAR* pwszOutBuff, WCHAR* pwszInBuff);

virtual HRESULT GetColInfo();
virtual HRESULT GetTypeInfo();
virtual HRESULT GetLiteralInfo();

virtual HRESULT CreateSQLStmt(ESQL_STMT eSqlStmt, WCHAR* pwszSqlStmt, BOOL fShowSql = TRUE);

virtual HRESULT MapTableInfo(CTable* pCSourceTable);
virtual HRESULT CreateTable();

virtual HRESULT GetRowset(REFIID riid, IUnknown** ppIRowset, HACCESSOR* phAccessor, ULONG* pcRowSize, ULONG ulBlobSize);
virtual HRESULT CopyData(CTable* pCSourceTable, ULONG* pcRows, ULONG ulParamSets, ULONG ulBlobSize);
virtual HRESULT CopyIndexes(CTable* pCSourceTable);

virtual HRESULT GetTypeInfoRowset(IAccessor** ppIAccessor, HACCESSOR* phAccessor, IRowset** ppIRowset);

//Data
WCHAR m_wszIDQuote[MAX_NAME_LEN];
WCHAR m_wszIDSeperator[MAX_NAME_LEN];

WCHARm_wszSchemaName[MAX_NAME_LEN];
WCHARm_wszTableName[MAX_NAME_LEN];
WCHARm_wszQualTableName[MAX_NAME_LEN];

//Index info
ULONGm_cIndexes;// Count of indexes
INDEXINFO*m_rgIndexInfo;// Index information

//ColumnInfo
ULONGm_cColumns;// Count of columns
COLDESC* m_rgColDesc;// Column Information
ULONG       m_cBlobs;// Blob columns

//DataSource
CDataSource*m_pCDataSource;
CWizard*m_pCWizard;
};

#endif//_TABLE_H_


TABLECOPY.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module TABLECOPY.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#define DBINITCONSTANTS  //Initilaizes OLEDB Guids / Constants

#include "winmain.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"
#include "wizard.h"

#include "msdasql.h"//CLSID_MSDASQL provider guid

/////////////////////////////////////////////////////////////////
// CTableCopy::CTableCopy
//
/////////////////////////////////////////////////////////////////
CTableCopy::CTableCopy(CWizard* pCWizard)
{
ASSERT(pCWizard);

m_pCFromTable= new CTable(pCWizard);
m_pCToTable= new CTable(pCWizard);

m_dwShowTypes= SHOW_TABLES;  //Default to just user Tables in the DSN

//Options
m_fShowQuery= FALSE;

//Copy Options
m_dwRowOpt= IDR_ALL_ROWS;
m_ulMaxRows= MAX_BLOCK_SIZE;

m_dwParamOpt= IDR_PARAM_SETS;
m_ulParamSets= 10;

m_dwBlobOpt= IDR_BLOB_SIZE;
m_ulBlobSize= MAX_COL_SIZE;

//Create Options
m_fCopyTables= TRUE;
m_fCopyIndexes= TRUE;

m_fTranslate= TRUE;
m_pCWizard= pCWizard;  //Back pointer to windowing class
}

/////////////////////////////////////////////////////////////////
// CTableCopy::~CTableCopy
//
/////////////////////////////////////////////////////////////////
CTableCopy::~CTableCopy()
{
delete m_pCFromTable;
delete m_pCToTable;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTableCopy::MapTypes
//
/////////////////////////////////////////////////////////////////
HRESULT CTableCopy::MapTypes()
{
HRESULT hr = S_OK;

// Get descriptions of each column in the Source Table
TESTC(hr = m_pCFromTable->GetColInfo());

//Now get the TypeInfo for all columns
TESTC(hr = m_pCFromTable->GetTypeInfo());

//Now map all the Types correctly from the Source to the Target
TESTC(hr = m_pCToTable->MapTableInfo(m_pCFromTable));

CLEANUP:
return hr;
}

/////////////////////////////////////////////////////////////////
// HRESULT CTableCopy::CreateTable
//
/////////////////////////////////////////////////////////////////
HRESULT CTableCopy::CreateTable()
{
HRESULT hr = S_OK;

// Create the Table (if desired)
if(m_fCopyTables)
{
TESTC(hr = m_pCToTable->CreateTable());
TESTC(hr = m_pCToTable->GetColInfo());
}

CLEANUP:
return hr;
}

///////////////////////////////////////////////////////////////////////
// HRESULT CTableCopy::CreateIndexes
//
///////////////////////////////////////////////////////////////////////
HRESULT CTableCopy::CreateIndexes()
{
HRESULT hr = S_OK;

// Create the indexes (if desired)
if(m_fCopyIndexes)
TESTC(hr = m_pCToTable->CopyIndexes(m_pCFromTable));

CLEANUP:
return hr;
}

///////////////////////////////////////////////////////////////////////
// HRESULT CTableCopy::CopyData
//
///////////////////////////////////////////////////////////////////////
HRESULT CTableCopy::CopyData(ULONG* pcRows)
{
ASSERT(pcRows);
HRESULT hr = S_OK;

//If MaxRows is specified, then copy upto number specified, otherwise
//indicate ULONG_MAX to mean all rows, (upto ULONG_MAX)
*pcRows= (m_dwRowOpt == IDR_MAX_ROWS) ? m_ulMaxRows : ULONG_MAX;

//If ParamSets are specified then use the ParamSet size, otherwise
//indicate 0 to mean no param sets
ULONG ulParamSets= (m_dwParamOpt == IDR_PARAM_SETS) ? m_ulParamSets : 0;

//If BlobSize is specified then use the Specified size, otherwise
//indicate ULONG_MAX to use ISequentialStream
ULONG ulBlobSize= (m_dwBlobOpt == IDR_BLOB_SIZE) ? m_ulBlobSize : ULONG_MAX;

//Copy the Data from the Source to the Target table
TESTC(hr = m_pCToTable->CopyData(m_pCFromTable, pcRows, ulParamSets, ulBlobSize));

CLEANUP:
return hr;
}

///////////////////////////////////////////////////////////////////////
// HRESULT CTableCopy::CopyTables
//
///////////////////////////////////////////////////////////////////////
HRESULT CTableCopy::CopyTables()
{
HRESULT hr;
ULONG cRows = 0;

//MapTypes should have already been called
ASSERT(m_pCFromTable->m_rgColDesc);
ASSERT(m_pCToTable->m_rgColDesc);

//Create the new Table
TESTC(hr = CreateTable());

//Create the Indexes
TESTC(hr = CreateIndexes());

//Now Copy the Data
TESTC(hr = CopyData(&cRows));

CLEANUP:
if(SUCCEEDED(hr))
wMessageBox(NULL, MB_ICONINFORMATION | MB_OK, wsz_SUCCESS, wsz_COPY_SUCCESS, cRows);
else
wMessageBox(NULL, MB_ICONEXCLAMATION | MB_OK, wsz_ERROR, wsz_COPY_FAILURE);

return hr;
}


TABLECOPY.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module TABLECOPY.H
//
//-----------------------------------------------------------------------------

#ifndef _TABLECOPY_H_
#define _TABLECOPY_H_

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include "oledb.h"

///////////////////////////////////////////////////////////////
// Forward Declarations
//
///////////////////////////////////////////////////////////////
class CTable;
class CWizard;

///////////////////////////////////////////////////////////////
// Defines
//
///////////////////////////////////////////////////////////////
#define MAX_QUERY_LEN4096
#define MAX_NAME_LEN256

#define MAX_COL_SIZE   50000
#define MAX_BLOCK_SIZE  20

// Create param bitmasks describe the parameter required on create table
#define CP_PRECISION0x0001
#define CP_SCALE0x0002
#define CP_LENGTH0x0004

// This macro will determine if the type is numeric, and
// if numeric -> bPrecision, if not numeric -> dwColumnSize
#define COLINFO_SIZE(ColInfo) (IsNumericType(ColInfo.wType) ? ColInfo.bPrecision : ColInfo.ulColumnSize)

// Enum or which Tables to show in the DSN
enum SHOW_TYPES
{
SHOW_NONE= 0x00000000,
SHOW_TABLES= 0x00000001,
SHOW_SYSTABLES= 0x00000002,
SHOW_VIEWS= 0x00000004,
SHOW_SYNONYMS= 0x00000008
};

/////////////////////////////////////////////////////////////////
// CTableCopy
//
/////////////////////////////////////////////////////////////////
class CTableCopy
{
public:
//Constructors
CTableCopy(CWizard* pCWizard);
virtual ~CTableCopy();

//Members
virtual HRESULT CopyTables();

virtual HRESULT MapTypes();
virtual HRESULT CreateTable();
virtual HRESULT CreateIndexes();
virtual HRESULT CopyData(ULONG* pcRows);

//Data
CTable*m_pCFromTable;//Source Table
CTable*m_pCToTable;//Target Table

//Options
DWORDm_dwRowOpt;// Row Options
ULONGm_ulMaxRows;// Maximum rows or ALL_ROWS

DWORDm_dwParamOpt;// Param Options
ULONGm_ulParamSets;// Number of Parameters Sets

DWORDm_dwBlobOpt;        // Blob Options
ULONGm_ulBlobSize;       // Maximum Size for BLOB Columns

DWORDm_dwShowTypes;// Which Tables in the DSN to show

BOOLm_fShowQuery;// TRUE to display SQL statements
BOOLm_fCopyTables;// TRUE to create the table definition
BOOLm_fCopyIndexes;// TRUE to create indexes on new table

BOOLm_fTranslate;// TRUE to translate object names
CWizard*    m_pCWizard;
};

#endif//_TABLECOPY_H_


VERSION.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB Version File
// Copyright (C) 1994 - 1996 By Microsoft Corporation.
//
// @doc
//
// @module version include file
//
//

// Constants -----------------------------------------------------------------
#ifndef _AXVER_H_
#define _AXVER_H_

#define VER_FILEVERSION  01,01,24,17
#define VER_FILEVERSION_STR  " 1.01.2417\0"
#define VER_PRODUCTVERSION  01,01,24,17
#define VER_PRODUCTVERSION_STR  " 1.01.2417\0"

#define VER_FILEFLAGSMASK(VS_FF_DEBUG | VS_FF_PRERELEASE)
#ifdef DEBUG
#define VER_FILEFLAGS(VS_FF_DEBUG)
#else
#define VER_FILEFLAGS(0)
#endif

#define VER_FILEOSVOS_NT_WINDOWS32

#define VER_COMPANYNAME_STR"Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR"Microsoft OLE DB\0"
#define VER_LEGALCOPYRIGHT_STR"Copyright \251 Microsoft Corporation 1994-1996\0"

// Resource DLL Version Resource ID
#define IDS_RESDLL_VER1

#endif
//--------------------


WINMAIN.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module WINMAIN.CPP
//
//-----------------------------------------------------------------------------

///////////////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////////////
#include "winmain.h"
#include "wizard.h"
#include "common.h"
#include "tablecopy.h"
#include "table.h"
#include "spy.h"

//////////////////////////////////////////////////////////////////
// int WINAPI WinMain
//
// Main application entry point
//////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    CHAR*  pszCmdLine,
                    INT    nCmdShow)
{
//Register IMallocSpy
CMallocSpy* pCMallocSpy = NULL;
MallocSpyRegister(&pCMallocSpy);

CWizard* pCWizard = new CWizard(NULL, hInstance);
HRESULT hr;

XTESTC(hr = CoInitialize(NULL));

//Main Execution
pCWizard->Run();

CLEANUP:
delete pCWizard;

MallocSpyUnRegister(pCMallocSpy);
CoUninitialize();

//Dump all the leaks
MallocSpyDump(pCMallocSpy);
delete pCMallocSpy;

return (hr==S_OK);
}

//////////////////////////////////////////////////////////////////
// void SyncSibling
//
//////////////////////////////////////////////////////////////////
void SyncSibling(HWND hToWnd, HWND hFromWnd)
{
ASSERT(hToWnd && hFromWnd);

//Make both windows synched,
//Get the current selection from the Source
LONG lSel = SendMessage(hFromWnd, LB_GETCURSEL, (WPARAM)0, (LPARAM)0L);

//Tell the Target to select the same selection
if(lSel != LB_ERR)
SendMessage(hToWnd, LB_SETCURSEL, (WPARAM)lSel, (LPARAM)0L);
}

//////////////////////////////////////////////////////////////////
// void InternalAssert
//
//////////////////////////////////////////////////////////////////
void InternalAssert(
char*pszExp,// The expression causing assert
char*pszFile,// The file name
UINTiLine// Line number of assert
)
{
CHARszMsg[MAX_QUERY_LEN];
_snprintf(szMsg, MAX_NAME_LEN, "Assertion Error!\n File '%s', Line '%lu'\n"
"Expression '%s'\n\n"
"Do you wish to Continue?  (Press 'OK' to ignore the assertion."
"  Press 'Cancel to debug.)",pszFile, iLine, pszExp);

//Popup a MessageBox
LONG dwSelection = MessageBoxA(NULL, szMsg,"Assertion Error",
MB_APPLMODAL | MB_ICONSTOP | MB_OKCANCEL | MB_DEFBUTTON1 );

switch (dwSelection)
{
case IDOK:
break;
case IDCANCEL:
DebugBreak();
break;
default:
ASSERT(!L"Unhandled Choice");
}
}

//////////////////////////////////////////////////////////////////
// void InternalTrace
//
//////////////////////////////////////////////////////////////////
void InternalTrace(WCHAR*pwszFmt, ...)
{
va_listmarker;
WCHARwszBuffer[MAX_NAME_LEN];
CHARszBuffer[MAX_NAME_LEN];

// Use format and arguements as input
//This version will not overwrite the stack, since it only copies
//upto the max size of the array
va_start(marker, pwszFmt);
_vsnwprintf(wszBuffer, MAX_NAME_LEN, pwszFmt, marker);
va_end(marker);

//Make sure there is a NULL Terminator, vsnwprintf will not copy
//the terminator if length==MAX_NAME_LEN
wszBuffer[MAX_NAME_LEN-1] = EOL;

//Convert to MBCS
ConvertToMBCS(wszBuffer, szBuffer, MAX_NAME_LEN);

//Output to the DebugWindow
OutputDebugString(szBuffer);
}

//////////////////////////////////////////////////////////////////
// void InternalTrace
//
//////////////////////////////////////////////////////////////////
void InternalTrace(CHAR*pszFmt, ...)
{
va_listmarker;
CHARszBuffer[MAX_NAME_LEN];

// Use format and arguements as input
//This version will not overwrite the stack, since it only copies
//upto the max size of the array
va_start(marker, pszFmt);
_vsnprintf(szBuffer, MAX_NAME_LEN, pszFmt, marker);
va_end(marker);

//Make sure there is a NULL Terminator, vsnwprintf will not copy
//the terminator if length==MAX_NAME_LEN
szBuffer[MAX_NAME_LEN-1] = '\0';

OutputDebugStringA(szBuffer);
}

//////////////////////////////////////////////////////////////////
// void Busy
//
//////////////////////////////////////////////////////////////////
void Busy(void)
{
static HCURSORhCursor;

ShowCursor(FALSE);

if(hCursor == NULL)
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
else
{
SetCursor(hCursor);
hCursor = NULL;
}

ShowCursor(TRUE);
}

//////////////////////////////////////////////////////////////////
// void OutOfMemory
//
//////////////////////////////////////////////////////////////////
void OutOfMemory(HWND hWnd)
{
//Unicode version is supported on Win95/WinNT
MessageBoxW(hWnd, L"Out of memory", L"Error", MB_OK);
}

//////////////////////////////////////////////////////////////////
// BOOL CenterDialog
//
//////////////////////////////////////////////////////////////////
BOOL CenterDialog(HWND hdlg)
{
RECT  rcParent;                         // Parent window client rect
RECT  rcDlg;                            // Dialog window rect
int   nLeft, nTop;                      // Top-left coordinates
int   cWidth, cHeight;                  // Width and height
HWNDhwnd;

// Get frame window client rect in screen coordinates
hwnd = GetParent(hdlg);
if(hwnd == NULL || hwnd == GetDesktopWindow())
{
rcParent.top = rcParent.left = 0;
rcParent.right = GetSystemMetrics(SM_CXFULLSCREEN);
rcParent.bottom = GetSystemMetrics(SM_CYFULLSCREEN);
}
else
GetWindowRect(hwnd, &rcParent);

// Determine the top-left point for the dialog to be centered
GetWindowRect(hdlg, &rcDlg);
cWidth  = rcDlg.right  - rcDlg.left;
cHeight = rcDlg.bottom - rcDlg.top;
nLeft   = rcParent.left +
            (((rcParent.right  - rcParent.left) - cWidth ) / 2);
nTop    = rcParent.top  +
            (((rcParent.bottom - rcParent.top ) - cHeight) / 2);
if (nLeft < 0) nLeft = 0;
if (nTop  < 0) nTop  = 0;

// Place the dialog
return MoveWindow(hdlg, nLeft, nTop, cWidth, cHeight, TRUE);
}

//////////////////////////////////////////////////////////////////
// ULONG wMessageBox
//
//////////////////////////////////////////////////////////////////
INT wMessageBox(
HWND hwnd,// Parent window for message display
UINT uiStyle,// Style of message box
WCHAR* pwszTitle,// Title for message
WCHAR* pwszFmt,// Format string
...// Substitution parameters
)
{
va_listmarker;
WCHARwszBuffer[MAX_QUERY_LEN];

// Use format and arguements as input
//This version will not overwrite the stack, since it only copies
//upto the max size of the array
va_start(marker, pwszFmt);
_vsnwprintf(wszBuffer, MAX_QUERY_LEN, pwszFmt, marker);
va_end(marker);

//Make sure there is a NULL Terminator, vsnwprintf will not copy
//the terminator if length==MAX_QUERY_LEN
wszBuffer[MAX_QUERY_LEN-1] = EOL;

//Unicode version is supported on both Win95 / WinNT do need to convert
return MessageBoxW(hwnd, wszBuffer, pwszTitle, uiStyle);
}

//////////////////////////////////////////////////////////////////
// void wSetDlgItemText
//
//////////////////////////////////////////////////////////////////
void wSetDlgItemText(HWND hWnd, INT DlgItem, WCHAR* pwszFmt, ...)
{
va_listmarker;
WCHARwszBuffer[MAX_NAME_LEN];
CHARszBuffer[MAX_NAME_LEN];

// Use format and arguements as input
//This version will not overwrite the stack, since it only copies
//upto the max size of the array
va_start(marker, pwszFmt);
_vsnwprintf(wszBuffer, MAX_NAME_LEN, pwszFmt, marker);
va_end(marker);

//Make sure there is a NULL Terminator, vsnwprintf will not copy
//the terminator if length==MAX_NAME_LEN
wszBuffer[MAX_NAME_LEN-1] = EOL;

//convert to MBCS
ConvertToMBCS(wszBuffer, szBuffer, MAX_NAME_LEN);

SetDlgItemTextA(hWnd, DlgItem, szBuffer);
}

//////////////////////////////////////////////////////////////////
// UINT wGetDlgItemText
//
//////////////////////////////////////////////////////////////////
UINT wGetDlgItemText(HWND hWnd, INT DlgItem, WCHAR* pwsz, INT nMaxSize)
{
ASSERT(pwsz);
CHAR szBuffer[MAX_NAME_LEN];

UINT iReturn = GetDlgItemTextA(hWnd, DlgItem, szBuffer, MAX_NAME_LEN);

//convert to WCHAR
ConvertToWCHAR(szBuffer, pwsz, MAX_NAME_LEN);
return iReturn;
}

//////////////////////////////////////////////////////////////////
// LRESULT wSendMessage
//
//////////////////////////////////////////////////////////////////
LRESULT wSendMessage(HWND hWnd, UINT Msg, WPARAM wParam, WCHAR* pwszName)
{
ASSERT(pwszName);
CHAR szBuffer[MAX_NAME_LEN];

//Convert to ANSI before sending, since we don't know if this was a GET/SET message
ConvertToMBCS(pwszName, szBuffer, MAX_NAME_LEN);

//Send the message with an ANSI Buffer
LRESULT lResult = SendMessage(hWnd, Msg, (WPARAM)wParam, (LPARAM)szBuffer);

//Now convert the result into the users WCHAR buffer
ConvertToWCHAR(szBuffer, pwszName, MAX_NAME_LEN);
return lResult;
}

//////////////////////////////////////////////////////////////////
// BOOL GetEditBoxValue
//
//////////////////////////////////////////////////////////////////
BOOL GetEditBoxValue(HWND hEditWnd, ULONG ulMin, ULONG ulMax, ULONG* pulCount)
{
ASSERT(hEditWnd);
ASSERT(pulCount);

ULONGulCount = 0;
WCHARwszBuffer[MAX_NAME_LEN];
WCHAR*  pwszEnd = NULL;

//Get the EditText
wSendMessage(hEditWnd, WM_GETTEXT, MAX_NAME_LEN, wszBuffer);

//Convert to ULONG
ulCount = wcstoul(wszBuffer, &pwszEnd, 10);
if(!wszBuffer[0] || ulCount<ulMin || ulCount>ulMax || pwszEnd==NULL || pwszEnd[0]!=EOL)
{
wMessageBox(hEditWnd, MB_ICONEXCLAMATION | MB_OK, wsz_ERROR,
wsz_INVALID_VALUE_, wszBuffer, ulMin, ulMax);
SetFocus(hEditWnd);
return FALSE;
}

*pulCount = ulCount;
return TRUE;
}


WINMAIN.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright 1996 - 1998 Microsoft Corporation.
//
// @doc
//
// @module WINMAIN.H
//
//-----------------------------------------------------------------------------

#ifndef _WINMAIN_H_
#define _WINMAIN_H_

///////////////////////////////////////////////////////////////
// Defines
//
///////////////////////////////////////////////////////////////

//We want type checking on Window Handles

#ifndef STRICT
#define STRICT
#endif

///////////////////////////////////////////////////////////////
// Includes
//
///////////////////////////////////////////////////////////////
#include <windows.h>
#include <windowsx.h>

#include <stddef.h>
#include <stdio.h>
#include <limits.h>
#include <wchar.h>

#include "resource.h"

////////////////////////////////////////////////////////////////////////////
// Windows Defines
//
////////////////////////////////////////////////////////////////////////////
#define CHECK_MEMORY(pv)if(!pv) { OutOfMemory(NULL); goto CLEANUP; }

//Dialog Box procedures want to know if you handled the MSG
//or not.  If you do, thenit just returns, if not then it calls
//the default windialog procedure to try and handle it
const BOOL HANDLED_MSG = TRUE;
const BOOL UNHANDLED_MSG = FALSE;

////////////////////////////////////////////////////////////////////////////
// Windows functions
//
////////////////////////////////////////////////////////////////////////////
void Busy();
void OutOfMemory(HWND hwnd);

INT wMessageBox(HWND hDlg, UINT uiStyle, WCHAR* pwszTitle, WCHAR* pwszFmt, ...);
LRESULT wSendMessage(HWND hWnd, UINT Msg, WPARAM wParam, WCHAR* pwszName);

void wSetDlgItemText(HWND hWnd, INT DlgItem, WCHAR* pwszFmt, ...);
UINT wGetDlgItemText(HWND hWnd, INT DlgItem, WCHAR* pwsz, INT nMaxSize);

BOOL CenterDialog(HWND hDlg);
void SyncSibling(HWND hwndLstChg,HWND hwndLstSrc);

BOOL GetEditBoxValue(HWND hEditWnd, ULONG ulMin, ULONG ulMax, ULONG* pulCount);

/////////////////////////////////////////////////////////////////////
// Memory debugging code
//
/////////////////////////////////////////////////////////////////////
void InternalAssert(char* pszExp, char* pszFile, UINT iLine);
void InternalTrace(CHAR* pszExp, ...);
void InternalTrace(WCHAR* pwszExp, ...);

#undef ASSERT
#undef TRACE

#ifdef _DEBUG
#define ASSERT(exp)if(!(exp)) InternalAssert(#exp, __FILE__, __LINE__)
#define TRACEInternalTrace
#else  //_DEBUG
#define ASSERT(exp)
#define TRACEif(0) InternalTrace
#endif //_DEBUG

#endif //_WINMAIN_H_


WIZARD.CPP

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module WIZARD.CPP
//
//-----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "common.h"
#include "tablecopy.h"
#include "wizard.h"
#include "progress.h"

/////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////
enum WIZ_STEP
{
WIZ_STEP1= 0,
WIZ_STEP2= 1,
WIZ_STEP3= 2,
WIZ_STEP4= 3,
WIZ_TYPES= 4,
END_WIZ= 5
};

/////////////////////////////////////////////////////////////////////
// CDialog::CDialog
//
/////////////////////////////////////////////////////////////////////
CDialog::CDialog(HWND hWnd, HINSTANCE hInst)
{
ASSERT(hInst);

m_hWnd= hWnd;
m_hInst = hInst;
}

/////////////////////////////////////////////////////////////////////
// CDialog::~CDialog
//
/////////////////////////////////////////////////////////////////////
CDialog::~CDialog()
{
}

////////////////////////////////////////////////////////////////
// CWizard::CWizard
//
/////////////////////////////////////////////////////////////////
CWizard::CWizard(HWND hWnd, HINSTANCE hInst)
: CDialog(hWnd, hInst)
{
m_pCTableCopy = new CTableCopy(this);
m_pCProgress = new CProgress(hWnd, hInst);
}

////////////////////////////////////////////////////////////////
// CWizard::~CWizard
//
/////////////////////////////////////////////////////////////////
CWizard::~CWizard()
{
delete m_pCTableCopy;
delete m_pCProgress;
}

////////////////////////////////////////////////////////////////
// ULONG CWizard::FindNextStep
//
/////////////////////////////////////////////////////////////////
ULONG CWizard::FindNextStep(ULONG iStep, ULONG iButton)
{
ASSERT(iStep>=WIZ_STEP1 && iStep<END_WIZ);

//iStep - (the current dialog were in), along with
//iButton - (button pressed which closed the current dialog)

//From these 2 input, it calculates the next dialog to display
switch(iButton)
{
case IDOK:
//if(iStep+1 == END_WIZ)
//return WIZ_STEP1;
return iStep + 1;

case IDCANCEL:
return END_WIZ;

case IDB_PREV:
return iStep - 1;

default:
ASSERT(!"Unhandled Case");
return END_WIZ;
};
}

////////////////////////////////////////////////////////////////
// ULONG CWizard::Run
//
/////////////////////////////////////////////////////////////////
ULONG CWizard::Run()
{
// Call each step. The return value will be either the next
// step to run or END_WIZARD when we are all done.

ULONG iStep = WIZ_STEP1;
while(iStep != END_WIZ)
{
switch(iStep)
{
case WIZ_STEP1:
{
CS1Dialog CStep1(m_hWnd, m_hInst, m_pCTableCopy);
iStep = FindNextStep(iStep, CStep1.Display());
break;
}

case WIZ_STEP2:
{
CS2Dialog CStep2(m_hWnd, m_hInst, m_pCTableCopy);
iStep = FindNextStep(iStep, CStep2.Display());
break;
}

case WIZ_STEP3:
{
CS3Dialog CStep3(m_hWnd, m_hInst, m_pCTableCopy);
iStep = FindNextStep(iStep, CStep3.Display());
break;
}

case WIZ_STEP4:
{
CS4Dialog CStep4(m_hWnd, m_hInst, m_pCTableCopy);
iStep = FindNextStep(iStep, CStep4.Display());
break;
}

case WIZ_TYPES:
{
CTypesDialog CTypes(m_hWnd, m_hInst, m_pCTableCopy);
iStep = FindNextStep(iStep, CTypes.Display());
break;
}

default:
{
ASSERT(!"Unhandled Case!");
break;
}
}
 }
return iStep;
}


WIZARD.H

//-----------------------------------------------------------------------------
// Microsoft OLE DB TABLECOPY Sample
// Copyright (C) 1996 By Microsoft Corporation.
//
// @doc
//
// @module WIZARD.H
//
//-----------------------------------------------------------------------------

#ifndef _WIZARD_H_
#define _WIZARD_H_

/////////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////////
#include "winmain.h"

/////////////////////////////////////////////////////////////////
// Forward Declarations
//
/////////////////////////////////////////////////////////////////
class CTableCopy;
class CProgress;

/////////////////////////////////////////////////////////////////////
// CDialog
//
/////////////////////////////////////////////////////////////////////
class CDialog
{
public:
//constructors
CDialog(HWND hWnd, HINSTANCE hInst);
virtual ~CDialog();

//Data
HWNDm_hWnd;
HINSTANCEm_hInst;
};

/////////////////////////////////////////////////////////////////////
// CS1Dialog
//
/////////////////////////////////////////////////////////////////////
class CS1Dialog : public CDialog
{
public:
//constructors
CS1Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pTableCopy);
virtual ~CS1Dialog();

//abstract members
ULONG Display();
static BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//members
virtual BOOL InitControls();
virtual BOOL RefreshControls();

virtual BOOL ResetTableList(HWND hWndTable, HWND hWndCol);
virtual BOOL ResetColInfo(HWND hWndCol);
virtual BOOL GetTableColInfo();

virtual BOOL GetTypes(WCHAR* pwszTypes);

//Data
CTableCopy* m_pCTableCopy;
};

/////////////////////////////////////////////////////////////////////
// CS2Dialog
//
/////////////////////////////////////////////////////////////////////
class CS2Dialog : public CDialog
{
public:
//constructors
CS2Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pTableCopy);
virtual ~CS2Dialog();

//abstract members
ULONG Display();
static BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//members
virtual BOOL GetPrimaryKeys();

virtual BOOL ResetIndexList(HWND hWnd);
virtual BOOL RecordSelectedIndexes(HWND hWnd);

//Data
CTableCopy* m_pCTableCopy;
};

/////////////////////////////////////////////////////////////////////
// CS3Dialog
//
/////////////////////////////////////////////////////////////////////
class CS3Dialog : public CDialog
{
public:
//constructors
CS3Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pTableCopy);
virtual ~CS3Dialog();

//abstract members
ULONG Display();
static BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//members
virtual BOOL Connect();
virtual BOOL RefreshControls();
virtual BOOL EnableTable();

//Data
CTableCopy* m_pCTableCopy;
};

/////////////////////////////////////////////////////////////////////
// CS4Dialog
//
/////////////////////////////////////////////////////////////////////
class CS4Dialog : public CDialog
{
public:
//constructors
CS4Dialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pTableCopy);
virtual ~CS4Dialog();

//abstract members
ULONG Display();
static BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//members
virtual BOOL InitControls();
virtual BOOL RefreshControls();

//Data
CTableCopy* m_pCTableCopy;
};

/////////////////////////////////////////////////////////////////////
// CTypesDialog
//
/////////////////////////////////////////////////////////////////////
class CTypesDialog : public CDialog
{
public:
//constructors
CTypesDialog(HWND hWnd, HINSTANCE hInst, CTableCopy* pTableCopy);
virtual ~CTypesDialog();

//abstract members
ULONG Display();
static BOOL WINAPI DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//members
virtual BOOL RefreshLists();

//Data
CTableCopy* m_pCTableCopy;
};

/////////////////////////////////////////////////////////////////////
// CWizard
//
/////////////////////////////////////////////////////////////////////
class CWizard : public CDialog
{
public:
//Constructors
CWizard(HWND hWnd, HINSTANCE hInst);
virtual ~CWizard();

//Members
virtual ULONG Run();
virtual ULONG FindNextStep(ULONG iStep, ULONG iButton);

//Data
CTableCopy*m_pCTableCopy;
CProgress*  m_pCProgress;
};

#endif//_WIZARD_H_


README.TXT

****************************************************************
         MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
              DB-LIBRARY FOR C SAMPLE PROGRAMS
****************************************************************
This file describes the DB-Library for C sample programs included
with Microsoft(R) SQL Server(TM) Workstation System 6.5.

For additional information about SQL Server 6.5, DB-Library for C,
and ODBC, see the "What's New in SQL Server 6.5," "Programming
DB-Library for C," and "Programming ODBC for SQL Server" sections
of SQL Server Books Online, which contains the most up-to-date
information available.

****************************************************************
DB-LIBRARY FOR C SAMPLE APPLICATIONS

The DB-Library for C sample program files are stored as projects
in their own directories. The following DB-Library sample projects
are targeted to specific client operating systems: Microsoft(R)
Windows NT(R) and Microsoft Windows(R) 95 for Intel(R), MIPS(R),
Alpha AXP(R), and PowerPC(TM) platforms; Microsoft Windows and
MS-DOS(R) for Intel. Each of these samples has an associated make
file for building with the NMAKE utility:

****************************************************************
DB-LIBRARY FOR C SAMPLE 32-BIT APPLICATIONS

Windows NT for Intel, Alpha AXP, MIPS, and PowerPC platforms

Project directories are located in the following path:

     C:\MSSQL\SAMPLES\DBLIB\C

     EXAMPLE1    Sending queries in a command batch
     EXAMPLE2    Working with data files
     EXAMPLE3    Binding aggregate and compute results
     EXAMPLE4    Using row buffering
     EXAMPLE5    Converting data with dbconvert
     EXAMPLE6    Querying and updating the database
     EXAMPLE7    Using browse-mode functions
     EXAMPLE8    Using remote stored procedures and return
 parameters
     NMPSDMP     Windows NT-based console named pipe dump device
 sample
     SQLCURS     Using cursor functions
     SQLEXAMP    Command-line interpreter
     SQLTESTC    Windows NT-based console sample
     SQLTESTN    Windows NT-based GUI sample
     TEXTCOPY    Windows NT-based console text and image sample,
 also uses GETOPT.*
     TWOPHASE    Two-phase commit example

     DATAFILE    Data file used by the EXAMPLE2 and EXAMPLE 6
 programs.

Each project on the Intel platform includes Visual C++ 4.0
workspace files (.MDP). Double-click the workspace file to load
the workspace and make file into the Developer Studio environment.

Each project also contains cross-platform native Visual C++ 4.0 make
files (.MAK). For each 32-bit project the .MAK file contains Intel,
Alpha, MIPS, and PowerPC build information. When the project is
loaded into Developer Studio, pick the appropriate target, for
example "Example1 - Win32 (ALPHA) Debug", and build it.

You can also build from the command line. BUILDALL.BAT, a file that
will build all of the 32-bit PTK samples, is in the samples directory.
Use the following syntax:

BUILDALL ({intel | alpha | mips | powerpc} {debug | release} {force})

To use these samples with Visual C++ 4.0, you need to ensure that
your Library and Include directory settings point to your own
directories. To do this using Developer Studio, from the Tools menu,
choose Options, then click the Directories tab. From the "Show
directories for" box, choose Include files and Library files, and
ensure that these directories (as appropriate) are included:

Include files:Library files:
C:\MSSQL\DBLIB\INCLUDEC:\MSSQL\DBLIB\LIB
C:\MSSQL\MSDTC\INCLUDEC:\MSSQL\MSDTC\LIB
C:\MSSQL\ODBC\INCLUDEC:\MSSQL\ODS\LIB
C:\MSSQL\ODS\INCLUDE
C:\MSSQL\SQLOLE\INCLUDE

****************************************************************
DB-LIBRARY FOR C SAMPLE 16-BIT APPLICATIONS

16-bit Windows project directories are located in the following path:

C:\MSSQL\SAMPLES\DBLIB\C\WIN16

    SQLTEST3      Windows-based sample
    SQLCURSW      Windows-based cursor sample

    SQLTEST.ICO   Icon used by both Windows-based samples

The MS-DOS project directory is located in the following path:

C:\MSSQL\SAMPLES\DBLIB\C\MSDOS

    SQLTESTR      MS-DOS-based sample

Visual C++ 1.5x batch and make files are included to build all of
these cross-platform samples on a given platform. Use "?" to display
options:

    BUILDR.BAT    Builds the examples for MS-DOS
    BUILDR.MAK
    BUILDBOR.BAT  Builds the examples for MS-DOS using Borland C
    BUILDBOR.MAK


EXAMPLE1.C

/*example1.c */
/*
**This example illustrates how to send two queries to
**SQL Server in a command batch.It binds each set
**of results and prints the rows.
**
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

#define DATELEN26
#define TYPELEN2

/* Forward declarations of the error handler and message handler.
*/
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main(argc, argv)
intargc;
char*argv[];
{
DBPROCESS*dbproc;/* Our connection with SQL Server. */
LOGINREC*login;/* Our login information. */

/* These are the variables used to store the returning data. */

DBCHARcrdate[DATELEN+1];
DBINTid;
DBCHARname[MAXNAME+1];/* MAXNAME is defined in
* "sqldb.h" as the maximum
* length for names of database
* objects, such as tables,
* columns, and procedures.
*/
DBCHARtype[TYPELEN+1];
RETCODEresult_code;

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/*
** Get a LOGINREC structure and fill it with the necessary
** login information.
*/

login = dblogin();
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example1");
DBSETLVERSION(login, DBVER60);

/*
** Get a DBPROCESS structure for communicating with SQL Server.
*/

dbproc = dbopen(login, "my_server");

/*
** We are going to retrieve some information from a table
** named "sysobjects", regarding names of system tables and
** stored procedures.
** We will submit two queries.The first finds all the rows
** that describe system tables.The second finds all the rows
** that describe stored procedures.The program will only look
** at the first 10 rows that describe stored procedures.
*/

/* First, put the commands into the command buffer. */

dbcmd(dbproc, "select name, type, id, crdate from sysobjects");
dbcmd(dbproc, " where type = 'S' ");
dbcmd(dbproc, "select name, type, id, crdate from sysobjects");
dbcmd(dbproc, " where type = 'P' ");

/*
** SQL Server processes the command batch in the following
** order:
**
** 1)It checks for syntax errors (i.e., "use database pubs"
**is syntactically incorrect; it should be "use pubs").
** 2)The second check is a semantic check (i.e., "select * from
**titels" is incorrect because the spelling should be
**"titles".)
** 3) The third check occurs in the actual execution phase. This
**check involves issues like permissions or memory problems.
**
** In the execution phase, dbsqlexec() and dbresults() can return
** the value "SUCCEED", which means there are more commands in the
** batch to process and that command was successful. A value
** of "FAIL" means that the query failed but there may be more
** commands in the batch to process. A value of "NO_MORE_RESULTS"
** means that there are no more commands in the batch to process.
** Therefore, the programmer must check the return values after
** dbsqlexec() and dbresults(), as illustrated below.
**
*/

/* Send the commands to SQL Server and start execution. */
dbsqlexec(dbproc);

/* Process each command until there are no more. */

while ((result_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (result_code == SUCCEED)
{
/* Bind program variables. */

dbbind(dbproc, 1, NTBSTRINGBIND, (DBINT) 0, name);
dbbind(dbproc, 2, NTBSTRINGBIND, (DBINT) 0, type);
dbbind(dbproc, 3, INTBIND, (DBINT) 0, (BYTE *) &id);
dbbind(dbproc, 4, NTBSTRINGBIND, (DBINT) 0, crdate);

/* Print appropriate header for the type of
* data coming back.
*/

printf("\n %s Objects: \n\n",
DBCURCMD(dbproc) == 1 ? "System Table": "Procedure");

/* Now print the rows. */

while (dbnextrow(dbproc) != NO_MORE_ROWS)
{
/*
** If this is the 2nd command and
** 10th row, flush the rest of the
** rows for that command.
*/

if ((DBCURCMD(dbproc) == 2)
&& (DBCURROW(dbproc) > 10))
    continue;

printf
("%s %s %ld %s\n", name, type, id, crdate);
}
}
}

/* Close our connection and exit the program. */

dbexit();
return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE2.C

/*example2.c */
/*
** This example opens a data file, inserts data from the file
** into a newly created table containing several of the
** SQL Server datatypes, and binds and prints the results.
** You must have a file named datafile and CREATE DATABASE
** permission in your login database.  Also you must have enough room
** in your default device to create a new database (minimum 2Mb).
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

#define BUFLEN2048
#define HEXLEN510
#define PLEN25
#define SALESLEN 11

/* Forward declarations of the error handler and message handler functions.
*/
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main(argc,argv)
intargc;
char*argv[];
{

LOGINREC*login;
DBPROCESS*dbproc;
RETCODEreturn_code;

DBTINYINTage;
DBSMALLINTuserid;
DBINTroyalty;
DBCHARname[PLEN+1];
DBBINARYtitle_id[PLEN+1];
DBBITus_citizen;
DBFLT8account;
DBCHARtitle[PLEN+1];/* string*/
DBCHARmanager[PLEN+1];/* ntbstring */
DBCHARid_buffer[HEXLEN+1];
DBCHAR   sales_buffer[SALESLEN+1];
DBDECIMAL sales;

static charcmdbuf[BUFLEN];
FILE*infile;

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/

dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Allocate and initialize the LOGINREC structure to be used
* to open a connection to SQL Server.
*/

login = dblogin();
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example2");
DBSETLVERSION(login, DBVER60);

dbproc = dbopen(login, "my_server");
printf("Creating the 'test' database.\n");
dbcmd(dbproc,"create database test ");
dbsqlexec(dbproc);
dbresults(dbproc);
dbuse(dbproc,"test");
printf("Creating the 'alltypes' table.\n");

/* Create a table that contains several SQL Server datatypes. */
dbcmd(dbproc,"create table alltypes ");
dbcmd(dbproc,"(age tinyint,");
dbcmd(dbproc,"userid smallint,");
dbcmd(dbproc,"royalty int,");
dbcmd(dbproc,"name char(25),");
dbcmd(dbproc,"title_id varbinary(20),");
dbcmd(dbproc,"us_citizen bit,");
dbcmd(dbproc,"account float,");
dbcmd(dbproc,"title varchar(20),");
dbcmd(dbproc,"manager char(25),");
dbcmd(dbproc,"sales decimal(10,2))");
dbsqlexec(dbproc);
dbresults(dbproc);

/* Insert rows of data into the newly created table "alltypes".
* We will read in the contents of the file and form an
* INSERT statement.
*/

if ((infile=fopen("datafile","r")) == NULL)
{
printf("Unable to open file 'datafile'.\n");
return(STDEXIT);
}

printf("Inserting rows into the 'alltypes' table.\n");

while ((fgets(cmdbuf,BUFLEN,infile)) != NULL)
{
dbfcmd(dbproc,"insert into alltypes \n");
dbfcmd(dbproc,"values(%s) \n",cmdbuf);
}
dbsqlexec(dbproc);

/* Process the results of each of the INSERT statements. */
while ((return_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (return_code == FAIL)
printf("One of the insert statements failed.\n");
}
printf("Selecting rows from the 'alltypes' table:\n");
dbcmd(dbproc,"select * from alltypes");
dbsqlexec(dbproc);

/* Initialize the DBDECIMAL structure so binding and conversions will use defaults */
sales.precision = 0;
sales.scale = 0;

while ((return_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (return_code == SUCCEED)
{
dbbind(dbproc,1,TINYBIND, (DBINT) 0,(BYTE *) &age);
dbbind(dbproc,2,SMALLBIND, (DBINT) 0,(BYTE *) &userid);
dbbind(dbproc,3,INTBIND, (DBINT) 0,(BYTE *) &royalty);
dbbind(dbproc,4,CHARBIND, (DBINT) 0,name);
dbbind(dbproc,5,BINARYBIND, (DBINT) 0,title_id);
dbbind(dbproc,6,BITBIND, (DBINT) 0,(BYTE *) &us_citizen);
dbbind(dbproc,7,FLT8BIND, (DBINT) 0,(BYTE *) &account);
dbbind(dbproc,8,STRINGBIND, (DBINT) 0,title);
dbbind(dbproc,9,NTBSTRINGBIND, (DBINT) 0,manager);
dbbind(dbproc,10,DECIMALBIND, (DBINT) 0, (BYTE *)&sales);

/*
** Initialize null terminator in "name" array,
** since CHARBIND does not add one.
*/
name[PLEN] = '\0';

while (dbnextrow(dbproc) != NO_MORE_ROWS)
{
dbconvert (dbproc, SQLBINARY, title_id, dbdatlen(dbproc, 5), SQLCHAR, id_buffer, (DBINT)-1);
dbconvert (dbproc, SQLDECIMAL, (BYTE *)&sales, sizeof(DBDECIMAL), SQLCHAR, sales_buffer, (DBINT)-1);

printf
("%d%d%ld%s0x%s\n",
age, userid, royalty, name, id_buffer);
printf
("%d%8.2f%s%s %s\n",
us_citizen, account, title, manager, sales_buffer);

}
}
}
dbexit();
return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE3.C

/*example3.c */
/*
** This example selects some information from the "pubs" database.
** It illustrates binding of both aggregate and compute results.
**
** Note that this example only work if the "pubs" database exists
** on your SQL Server.
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

#define PLEN 4
#define DATEPRINT 26
#define MONEYPRINT 12

/* Forward declarations of the error handler and message handler.
*/
interr_handler();
intmsg_handler();

main(argc,argv)
intargc;
char*argv[];
{
LOGINREC*login;
DBPROCESS*dbproc;

/* Declare the datatypes for the columns in the table "titles". */
DBINTpcount;
DBINTsales;
DBINTsalesavg;
DBINTsumsale;
DBCHARdate[DATEPRINT+1];
DBCHARprice[MONEYPRINT+1];
DBCHARpriceavg[MONEYPRINT+1];
DBCHARpubid[PLEN+1];
RETCODEresult_code; /* to hold the results of dbresults(). */
STATUSrow_code;/* to hold the results of dbnextrow(). */

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Set up the login information. */

login = dblogin();
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example3");
DBSETLVERSION(login, DBVER60);

dbproc = dbopen(login, "my_server");

/* Send a "use database" command. */
dbuse(dbproc,"pubs");

/* Put the SQL statement into the command buffer. */
dbcmd(dbproc, "select pub_id, pubdate, price, avg(price), ytd_sales,");
dbcmd(dbproc, " avg(ytd_sales), sum(ytd_sales) from titles");
        dbcmd(dbproc, " group by pub_id, pubdate, price, ytd_sales");
dbcmd(dbproc, " order by pub_id");
dbcmd(dbproc, " compute count(pub_id) by pub_id");

/* Send the command buffer to SQL Server for execution. */
dbsqlexec(dbproc);

/*
** Using the aggregates "sum" and "avg" with the COMPUTE clause
** necessitates special handling when binding the results. Since each
** aggregate creates a new column, this is accounted for in the bind.
** Notice that avg(price) is the fourth column in the select list
** and is also specified as the fourth column in the dbbind() function.
**
** The COMPUTE clause creates a compute row, which requires a
** special bind function called dbaltbind().
*/

while ((result_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (result_code == SUCCEED)
{
dbbind(dbproc,1,NTBSTRINGBIND, (DBINT) 0,pubid);
dbbind(dbproc,2,NTBSTRINGBIND, (DBINT) 0,date);
dbbind(dbproc,3,NTBSTRINGBIND, (DBINT) 0,price);
dbbind(dbproc,4,NTBSTRINGBIND, (DBINT) 0,priceavg);
dbbind(dbproc,5,INTBIND, (DBINT) 0, (BYTE *) &sales);
dbbind(dbproc,6,INTBIND, (DBINT) 0, (BYTE *) &salesavg);
dbbind(dbproc,7,INTBIND, (DBINT) 0, (BYTE *) &sumsale);

/* dbaltbind() binds compute columns. */
dbaltbind(dbproc,1,1,INTBIND,(DBINT) 0,(BYTE *)&pcount);

printf("\nAccounts:\n");
printf("---------\n\n");
printf
("%-5s%-19s%-6s%-10s%-5s%-10s%-10s\n\n",
"pubid", "date", "price", "avg(price)",
"sales", "avg(sales)", "sum(sales)");

/*
** Print out each result row, using different statements
** depending on whether the row is a regular row or a
** compute row.
*/

while ((row_code = dbnextrow(dbproc)) != NO_MORE_ROWS)
{
if (row_code == REG_ROW)
{
printf
("%5s%19s%6s%10s%5ld%10ld%10ld\n",
pubid, date, price, priceavg, sales,
salesavg, sumsale);
}
else
printf("title count:%ld\n\n",pcount);
}
}
}

dbexit();
return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE4.C

/*example4.c */
/*
** This example accesses the data within each row without using dbbind()
** and illustrates the use of row buffering.
**
** It runs a query, saves all of the returned rows (up to a maximum
** of 100) using DB-LIBRARY row buffering, and allows the user to
** examine data rows at random.
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sqlfront.h>
#include <sqldb.h>

DBPROCESS*dbproc;/* Our connection with SQL Server. */
LOGINREC*login;/* Our login information. */

#define TYPELEN 2
#define DATELEN 25

/* Forward declarations of the error handler and message handler.
*/
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main(argc, argv)
intargc;
char*argv[];
{
/* Here are the variables which will be used to store the
* data being examined.
*/
DBCHARname[MAXNAME+1];/* MAXNAME is defined in
* "sqldb.h" as the maximum
* length for names of database
* objects, such as tables,
* columns, and procedures.
*/
DBCHARtype[TYPELEN+1];
DBINTid;

DBCHARdatebuf[64];
DBINTlen;
charnumstring[32];
intquitflag = 0;
RETCODErow_code;
DBINTrownum;

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/*
** Get a LOGINREC structure and fill it with the necessary
** login information.
*/

login = dblogin();
DBSETLUSER (login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example4");
DBSETLVERSION(login, DBVER60);

dbproc = dbopen(login, "my_server");
dbcmd(dbproc, "select name, type, id, crdate from sysobjects");
dbcmd(dbproc, " where type = 'S'");

/*
        ** Set row buffering to 100 rows, via dbsetopt().
** Note that this parameter must be passed as an ASCII string.
*/

dbsetopt(dbproc, DBBUFFER, "100");
dbsqlexec(dbproc);
if (dbresults(dbproc) == SUCCEED)
{
/* Read all of the rows into DB-LIBRARY's buffer */
while ((row_code = dbnextrow(dbproc)) != NO_MORE_ROWS)
{
/* If DB-LIBRARY's row buffer is full, throw
* away the oldest row to allow the newest
* row to be read in.
*/
if (row_code == BUF_FULL)
dbclrbuf(dbproc, (DBINT) 1);
}

/* Print out the column headers. */

puts("NAME                 TYPE DATE                   ID");
puts("-------------------- ---- ---------------------- ----");

/* Let the user view any row in the table. */

printf("Type the number of the row you want to see.\n");
printf("The first row is number 1.\n");
printf("Asking for row 0 will terminate the program.\n");

while (quitflag == 0)
{
printf("Row number: ");
gets(numstring);
rownum = atoi(numstring);

if (rownum == 0)
quitflag = 1;
else
{
/* Print out the requested row. */
if (dbgetrow(dbproc, rownum) == NO_MORE_ROWS)
printf
("That row is not in the table.\n");
else
{
/* Copy variable-length character data
* (colname).
*/

strncpy
(name, (DBCHAR *)dbdata(dbproc, 1),
(len = dbdatlen(dbproc, 1)));

/* String needs terminating null. */

name[len] = '\0';

/* Copy fixed-length character data. */

strncpy
(type, (DBCHAR *)dbdata(dbproc, 2),
(len = dbdatlen(dbproc, 2)));
type[len] = '\0';

/* Copy integer data. */
id = *((DBINT *)dbdata(dbproc, 3));

/* Convert datetime data to a printable
* string.
*/

dbconvert(dbproc, SQLDATETIME, (dbdata(dbproc, 4)),
(DBINT)-1, SQLCHAR, datebuf, (DBINT)-1);

printf("%-20s %-4s %-22s %-4ld \n",
name, type, datebuf, id);
}
}
}
}

dbexit();
return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE5.C

/*example5.c */
/*
** This example illustrates dbconvert.It converts a
** number of constants to strings, a number of strings
** to numerical or binary quantities, and a number of
** numerical quantities to other numerical types.
**
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

#define ARRAY_LEN20

/* Forward declarations of the error handler and message handler.
*/
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main(argc, argv)
intargc;
char*argv[];
{
/* These variables hold the results of data conversions. */
static DBBINARYmy_binary_array[ARRAY_LEN]
= {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
DBFLT8my_flt8;
DBINTmy_int4;
DBMONEYmy_money;
DBCHARmy_string[ARRAY_LEN];

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Convert numerical and binary constants to strings. */

dbconvert
((DBPROCESS *)NULL, SQLBINARY, my_binary_array, (DBINT)8,
            SQLCHAR, my_string,(DBINT)-1);
printf("Binary constant 0x123456789abcdef0 converted to string");
printf("\"%s\".\n\n", my_string);

my_flt8 = 55.555;
dbconvert
((DBPROCESS *)NULL, SQLFLT8, (BYTE *) &my_flt8,
            (DBINT)-1, SQLCHAR, my_string, (DBINT)-1);
printf
("Floating-pt constant 55.555 converted to string \"%s\".\n\n",
my_string);

/* Convert string constants to numerical and binary quantities. */

dbconvert
((DBPROCESS *)NULL, SQLCHAR, "123", (DBINT)-1, SQLINT4, (BYTE *)
&my_int4, (DBINT)-1);
printf
("String constant \"123\" converted to 4-byte integer %ld.\n\n",
my_int4);

dbconvert
((DBPROCESS *)NULL,
SQLCHAR, "0xfedc", (DBINT)-1, SQLBINARY, my_binary_array,
            (DBINT)ARRAY_LEN);
printf("String constant \"0xfedc\" converted to binary sequence ");
printf("%hx.\n\n", *((DBSMALLINT *)my_binary_array));

dbconvert
((DBPROCESS *)NULL, SQLCHAR, "123.456", (DBINT)-1, SQLFLT8,
   (BYTE *) &my_flt8, (DBINT)-1);
printf("String constant \"123.456\" converted to ");
printf("floating-pt number %f.\n\n", my_flt8);

/* Convert numerical types to other numerical types. */

my_flt8 = 98.76;
dbconvert
   ((DBPROCESS *)NULL, SQLFLT8, (BYTE *) &my_flt8, (DBINT)-1, SQLMONEY,
(BYTE *) &my_money, (DBINT)-1);
dbconvert
   ((DBPROCESS *)NULL, SQLMONEY, (BYTE *) &my_money, (DBINT)-1, SQLCHAR,
       my_string, (DBINT)-1);
printf
("floating-pt number %f converted to money value %s.\n\n",
my_flt8, my_string);

dbconvert
   ((DBPROCESS *)NULL, SQLMONEY, (BYTE *) &my_money, (DBINT)-1, SQLFLT8,
       (BYTE *) &my_flt8, (DBINT)-1);
printf
("money value %s converted to floating-pt value %f.\n\n",
my_string, my_flt8);

return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE6.C

/*example6.c */
/*
** This example illustrates opening a data file, inserting data
** from the file into a newly created table containing several
** SQL Server datatypes, and updating the table using browse mode
** techniques.
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

#define BUFLEN 2048

/* Forward declarations of the error-handling and message-handing
functions. */
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main()
{
LOGINREC*login;
DBPROCESS*q_dbproc;/* This DBPROCESS is used to
* query the database.
*/
DBPROCESS*u_dbproc;/* This DBPROCESS is used to
* simultaneously update the database.
*/
const char  *qualptr;/* This points to the WHERE clause
* appropriate for updating q_dbproc's
* current data row.
*/
RETCODEreturn_code;
DBTINYINTage;
static charcmdbuf[BUFLEN];
FILE*infile;

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Allocate and initialize the LOGINREC structure to be used
* to open a connection to SQL Server.
*/

login = dblogin();

DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example6");
DBSETLVERSION(login, DBVER60);

q_dbproc = dbopen(login, "my_server");
u_dbproc = dbopen(login, "my_server");

printf("Creating the 'alltypes' table.\n");

/* Create a table that contains several SQL Server datatypes. */
dbcmd(q_dbproc,"if exists(select name from sysobjects where name='alltypes') drop table alltypes ");

dbcmd(q_dbproc,"create table alltypes ");
dbcmd(q_dbproc,"(age tinyint,");
dbcmd(q_dbproc,"userid smallint,");
dbcmd(q_dbproc,"royalty int,");
dbcmd(q_dbproc,"name char(25),");
dbcmd(q_dbproc,"title_id varbinary(20),");
dbcmd(q_dbproc,"us_citizen bit,");
dbcmd(q_dbproc,"account float,");
dbcmd(q_dbproc,"title varchar(20),");
dbcmd(q_dbproc,"manager char(25),");
dbcmd(q_dbproc,"sales decimal(10,2),");
dbcmd(q_dbproc,"timestamp)");

dbcmd(q_dbproc, "create unique index index1 on alltypes(userid)");

dbsqlexec(q_dbproc);
while (dbresults(q_dbproc) != NO_MORE_RESULTS)
continue;

/* Insert rows of data into the newly created table "alltypes".
* We will read in the contents of the file and form an
* INSERT statement.
*/

if ((infile=fopen("datafile","r")) == NULL)
{
printf("Unable to open file 'datafile'.\n");
return(STDEXIT);
}

printf("Inserting rows into the 'alltypes' table...\n");

while ((fgets(cmdbuf,BUFLEN,infile)) != NULL)
{
dbfcmd(q_dbproc,"insert into alltypes \n");
dbfcmd(q_dbproc,"values(%s, null) \n",cmdbuf);
}

dbsqlexec(q_dbproc);

/* Process the results of each of the INSERT statements. */

while ((return_code = dbresults(q_dbproc)) != NO_MORE_RESULTS)
{
if (return_code == FAIL)
printf("One of the insert statements failed.\n");
}

/* Using DB-LIBRARY's browse mode facilities, we'll increment
* the age of every person in the table.
*/

printf("Updating rows in the 'alltypes' table...\n");

dbcmd(q_dbproc,"select * from alltypes for browse");
dbsqlexec(q_dbproc);

while ((return_code = dbresults(q_dbproc)) != NO_MORE_RESULTS)
{
if (return_code == SUCCEED)
{
while (dbnextrow(q_dbproc) != NO_MORE_ROWS)
{
age = *((DBTINYINT *)(dbdata(q_dbproc, 1)));
qualptr = dbqual(q_dbproc, -1, "alltypes");
dbcmd(u_dbproc, "update alltypes");
dbfcmd
(u_dbproc, " set age = %d %s", age+1, qualptr);
dbsqlexec(u_dbproc);
dbresults(u_dbproc);
dbfreequal(qualptr);
}
}
}

/* Now, we'll look at the updated contents of the table to
* verify that the ages were properly incremented.
*/
printf("Selecting rows from the 'alltypes' table:\n");
dbcmd(q_dbproc, "select * from alltypes");
dbsqlexec(q_dbproc);
dbresults(q_dbproc);
dbprrow(q_dbproc);

printf("Dropping 'alltypes' table:\n");
dbcmd(q_dbproc, "drop table alltypes");
dbsqlexec(q_dbproc);
dbresults(q_dbproc);

dbexit();
return(STDEXIT);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE7.C

/*example7.c */
/*
** This example illustrates the use of browse mode functions to
** determine the source of result columns from ad hoc queries.
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

voidexamine_results();
BOOL send_command();

/* Forward declarations of the error-handling and message-handling
functions. */
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

main()
{
LOGINREC*login;
DBPROCESS*dbproc;

intcommand_count = 0;
RETCODEretcode;

        dbinit();/* initialize dblib */

/* Install the user-supplied error-handling and message-handling
* functions. They are defined at the bottom of this source file.
*/
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Allocate and initialize the LOGINREC structure to be used
* to open a connection to SQL Server.
*/

login = dblogin();
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example7");
DBSETLVERSION(login, DBVER60);

dbproc = dbopen(login, "my_server");

/* Allow the user to type in a series of queries. This program
* is terminated by the word "quit" appearing at the
* beginning of the line.
*/
while (1)
{
// Send a user-generated query to SQL Server.
// Exit if user typed in quit.

if (!send_command(dbproc))
break;

/* Now, examine the results of any queries the user has
* typed in.
*/

command_count = 1;
while ((retcode = dbresults(dbproc)) != NO_MORE_RESULTS)
{
command_count++ ;
if (retcode == FAIL)
printf("Command %d failed.\n", command_count);
else
{
if (!(DBCMDROW(dbproc)))
printf
("Command %d returned no rows.\n",
command_count);
else
{
/* This is a command that can return
* rows. Let's take a closer look at it.
*/
printf("Command %d:\n", command_count);
examine_results(dbproc);

/* Throw away all data rows. */
dbcanquery(dbproc);
}
}
}
}

return(STDEXIT);
}

void examine_results(dbproc)
DBPROCESS*dbproc;
{
intcolcount;
intcolnum;
charfullsource[128];
const char *sourcecolname;
inttabcount;
const char *tabname;
inttabnum;

/* Determine which tables were used to generate the query results.*/

tabcount = dbtabcount(dbproc);
printf
("The following tables were used to generate these query results:\n");

for (tabnum = 1; tabnum <= tabcount; tabnum++)
{
if ((tabname = dbtabname(dbproc, tabnum)) != NULL)
printf
("\t%s (%s)\n", tabname,
(dbtabbrowse(dbproc, tabnum)
? "browsable" : "not browsable"));
}

/* Determine which tables were used to generate each result column.*/

colcount = dbnumcols(dbproc);
printf("Here are the columns of the target list and their sources:\n");

printf
("\t%-20s%-30s%s\n\n",
"Result column:", "Source:", "Browsable?");
for (colnum = 1; colnum <= colcount; colnum++)
{
tabname = dbtabsource(dbproc, colnum, NULL);
sourcecolname = dbcolsource(dbproc, colnum);

if (tabname == NULL)
strcpy(fullsource, "(result of expression)");
else
sprintf(fullsource, "%s.%s", tabname, sourcecolname);

printf
("\t%-20s%-30s%s\n",
dbcolname(dbproc, colnum),
fullsource,
(dbcolbrowse(dbproc, colnum) ? "yes" : "no"));
}
}

BOOL send_command(dbproc)
DBPROCESS*dbproc;
{

static charcmdbuf[2048];

/* Allow the user to type in an ad hoc query. This query
* is terminated by the word "go" appearing at the
* beginning of the line.
*
* If the user types the word "quit" at the beginning of a line,
* we'll quit the program.
*/
printf("Enter SQL query (or 'quit'):\n");

while (1)
{
printf("> ");
gets(cmdbuf);

if (strcmp(cmdbuf, "go") == 0)
{
dbsqlexec(dbproc);
break;
}
else if (strcmp(cmdbuf, "quit") == 0)
{
return FALSE;
}
else
{
/* Keep reading SQL commands. */
dbcmd(dbproc, cmdbuf);
}
}

return TRUE;
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
    printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
printf
("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


EXAMPLE8.C

/* ** The example uses the following stored procedure,
** named "rpctest", which it assumes is located in the
** user's default database. Before running this example,
** you must create "rpctest" in your default database.
**
**     create procedure rpctest
**         (@param1 int out,
**          @param2 int out,
**          @param3 int out,
**          @param4 int)
**     as
**     begin
**         select "rpctest is running."
**         select @param1 = 11
**         select @param2 = 22
**         select @param3 = 33
**         select @param1
**
**         return 123
**     end
**
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sqlfront.h>
#include <sqldb.h>
#define FMTSTR    "%-8.8s %-8.8s %-8.8s %-8.8s\n"
#define FMTSTR1    "%-8.8s %-8.8s %8.8ld %8.8ld\n"

/* Forward declarations of the error handler and message handler routines. */
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*, char*, char*, DBUSMALLINT);

void main()
{
LOGINREC         *login;
DBPROCESS        *dbproc;

int              i;
int              numrets;
DBINT            param1 = 1;
DBINT            param2 = 2;
DBINT            param3 = 3;
DBINT            param4 = 4;
RETCODE          return_code;

  /* Initialize private DB Library structures. */
dbinit();

/* Install the user-supplied error-handling and message-handling
 * routines. They are defined at the bottom of this source file.
 */

dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Allocate and initialize the LOGINREC structure to be used
 * to open a connection to SQL Server.
 */

login = dblogin( );
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "rpcexample");
DBSETLVERSION(login, DBVER60);

dbproc = dbopen(login, (char *)"my_server");

/* Make the rpc. */
if (dbrpcinit(dbproc, "rpctest", (DBSMALLINT)0) == FAIL)
{
printf("dbrpcinit failed.\n");
dbexit();
exit(ERREXIT);
}
if (dbrpcparam(dbproc, "@param1", (BYTE)DBRPCRETURN, SQLINT4,
-1, -1, (BYTE *)&param1)
   == FAIL)
{
printf("dbrpcparam failed.\n");
dbexit();
exit(ERREXIT);
}

if (dbrpcparam(dbproc, "@param2", (BYTE)DBRPCRETURN, SQLINT4,
-1, -1, (BYTE *)&param2)
   == FAIL)
{
printf("dbrpcparam failed.\n");
dbexit();
exit(ERREXIT);
}

if (dbrpcparam(dbproc, "@param3", (BYTE)DBRPCRETURN, SQLINT4,
-1, -1, (BYTE *)&param3)
   == FAIL)
{
printf("dbrpcparam failed.\n");
dbexit();
exit(ERREXIT);
}

if (dbrpcparam(dbproc, "@param4", (BYTE)NULL, SQLINT4,
-1, -1, (BYTE *)&param4)
   == FAIL)
{
printf("dbrpcparam failed.\n");
dbexit();
exit(ERREXIT);
}

if (dbrpcsend(dbproc) == FAIL)
{
printf("dbrpcsend failed.\n");
dbexit();
exit(ERREXIT);
}

if (dbsqlok(dbproc) == FAIL)
{
printf("dbsqlok failed.\n");
dbexit();
exit(ERREXIT);
}
while ((return_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (return_code == FAIL)
{
printf("dbresults failed.\n");
dbexit();
exit(ERREXIT);
}

/* Print any rows that may have been returned. */
dbprrow(dbproc);

/* Examine any return parameters that may have arrived. */

numrets = dbnumrets(dbproc);
printf("%d return values received.\n\n", numrets);

if (numrets != 0)
{
printf
 (FMTSTR, "Name", "Type", "Length", "Value");
printf
 (FMTSTR,
 "------------", "------------",
 "------------", "------------");

   for (i = 1; i <= numrets; i++)
    {
   printf
   (FMTSTR1, dbretname(dbproc, i),
   dbprtype(dbrettype(dbproc, i)), dbretlen(dbproc, i),
   *((DBINT *)(dbretdata(dbproc, i))));
   }

}
if (dbhasretstat(dbproc))
printf("Return status = %ld\n", dbretstatus(dbproc));
else
printf("No return status for this command.\n");
}

dbexit();
}
int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS       *dbproc;
int             severity;
int             dberr;
int             oserr;
char            *dberrstr;
char            *oserrstr;
{
if (dberrstr != NULL) printf("DB-Library error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);
if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);
else
{

return(INT_CANCEL);
}
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext,
srvname, procname, line)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
char*srvname;
char*procname;
DBUSMALLINTline;

{
printf ("Msg %ld, Level %d, State %d\n",
msgno, severity, msgstate);

if (srvname !=  NULL)
printf ("Server '%s', ", srvname);
if (procname !=  NULL)
printf ("Procedure '%s', ", procname);
if (line !=  0)
printf ("Line %d", line);

printf("\n\t%s\n", msgtext);

return(0);
}


NMPSDMP.C

// NMPSDMP.C
//
// DBLIB program for using named pipe dump devices for backup and restore data
// from SQL Server 95.This program is an example and shows the usage of named pipe
// dump devices on the client side.
//
//
// The program needs the following arguments (it asks from the user interactively) :
//Operation: DUMP/LOAD
//Servername: Name of the server to dump from/load into
//Databasename: Name of the database to be dumped/loaded
//Stripenum: How many pipes are to be used for dump/load
//Dumpname: Prefix of the files to write/read the dumped/load data (DMP postfix)

#define DBNTWIN32
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <process.h>
#include <sqlfront.h>
#include <sqldb.h>

// definitions
#define MAX_DEVICES32// maximum number of stripe devices
#define PIPE_WAIT_TIME500// wait time in milliseconds
#defineDBPAGESIZE2048// size of physical page dumped

// status values for dump/load threads
#define STAT_SUCCESS0x0// default status
#define STAT_ERROR0x1  // error in the current thread
#define STAT_ENDTHREAD0x2// the current thread has terminated

// opcode values
#define DUMP_MODE0x1// dump mode of operation
#define LOAD_MODE0x2// load mode of operation

// this structure is maintained on a global basis among all threads that
// are doing reads/writes from pipe
typedef struct ginfo
{
char servername[30];// server name
char dbname[30];// database name
char dumpname[80];// dump file prefix
charpassword[30];// dblogin password of SA
longstatus;// global status flag for error recovery
shortopcode;// operation code
shortstripenum;// number of pipe devices to be used
} GINFO;

// this structure contains pipe specific info to be run by different threads
// that read/write data during the stripe dump/load
typedef struct pipeinfo
{
charpipename[80];// Physical name of pipe used for client
charspipename[80];// Physical name of pipe sent to SQL Server
chardumpname[80];// Physical name of the dump file
shortslot;// which device slot
GINFO*curinfo;// pointer to global info buffer
longstatus;// thread local status
} PIPEINFO;

// Macros
# define NEWARG(i)   (*(argv[i]) == '-' || *(argv[i]) == '/')
# define BUFALIGN(a)    ( (BYTE *) ( ((long)a + 64) & 0xffffffc0) )

// function prototypes
void parse_parms(int argc, char *argv[], GINFO *curinfo);
void argvalue(int i, int argc, char* argv[], char* out);
int set_up_pipes(GINFO *curinfo, PIPEINFO **pipes);
void close_up_pipes(GINFO *curinfo, PIPEINFO **pipes);
void dump_proc(PIPEINFO*curpipe);
void load_proc(PIPEINFO*curpipe);
INT msg_handler(PDBPROCESS,DBINT,INT,INT,LPCSTR,LPCSTR,LPCSTR,DBUSMALLINT);
INT err_handler(PDBPROCESS,INT,INT,INT,LPCSTR,LPCSTR);

// start of the progam
main(int argc, char *argv[])
{

RETCODEretcode;// DBLIB return code
LOGINREC *loginrec = NULL;// DBLIB LOGINREC pointer
DBPROCESS *dbproc = NULL;// DBLIB DBPROC pointer
GINFOinfo;// to store global info & status
PIPEINFO*pipes[MAX_DEVICES];// array of PIPEINFO pointers
charcmd[1024];// SQL command buffer

   // scratch variables
  charscratch[80];
inti;

  // Initialize
for (i=0; i<MAX_DEVICES; i++)
pipes[i] = NULL;

info.servername[0] = 0;
info.dbname[0] = 0;
info.dumpname[0] = 0;
info.password[0] = 0;
info.stripenum = 1;
info.opcode = DUMP_MODE;
info.status = STAT_SUCCESS;

// read the user input
parse_parms(argc, argv, &info);

// Install the DBLIB handlers
dberrhandle(err_handler);
dbmsghandle(msg_handler);

printf("Logging on to server...\n");

loginrec = dblogin();
DBSETLUSER(loginrec, "sa");
DBSETLPWD(loginrec,"");

if ((dbproc = dbopen(loginrec, info.servername)) == (DBPROCESS *)NULL)
{
  printf("dbopen() failed");
  info.status |= STAT_ERROR;
  goto clean_up;
};

// Set up the thread(s) for reading/writing the pipe device
if (!set_up_pipes(&info, pipes))
{
info.status |= STAT_ERROR;
goto clean_up;
}

// prepare the command now
if (info.opcode == DUMP_MODE)
strcpy(cmd, "dump database ");
else
strcpy(cmd, "load database ");
strcat(cmd, info.dbname);
if (info.opcode == DUMP_MODE)
strcat(cmd, " to ");
else
strcat(cmd, " from ");
for (i=0; i < info.stripenum; i++)
{
sprintf(scratch, "pipe = '%s'", &pipes[i]->spipename);
strcat(cmd, scratch);
if ((i+1) < info.stripenum)
strcat(cmd, ", ");
}
printf("Command : <%s>\n", cmd);

// this thread will be blocked till the dump/load command is not
// completed; for changing the behavior to non blocking, use
// dbsqlsend(), dbdataready() and dbsqlok() instead of dbsqlexec()
retcode = dbcmd(dbproc, cmd);
    retcode = dbsqlexec(dbproc);

   if (retcode != SUCCEED)
{
printf("dbsqlexec failed\n");
info.status |= STAT_ERROR;
goto clean_up;
}

   // discard results ; we do not need it
   while (dbresults(dbproc) == SUCCEED)
    while (dbnextrow(dbproc) != NO_MORE_ROWS)
;

// We are done with all the work ; clean up the resources and exit
// strictly speaking, cleanup is not necessary for this since we
// will exit the program (but for implementing this as a procedures
// would involve cleanup)
clean_up:

if (dbproc)
   dbclose(dbproc);
if (loginrec)
dbfreelogin(loginrec);

close_up_pipes(&info, pipes);
exit(1);
    return(0);
}

// procedure to get all the parameters
void  parse_parms(int argc, char *argv[], GINFO *curinfo)
{
int     i;
char    argval[30];

for (i = 0 ; i < argc; i++)
{
if (NEWARG(i))
{
switch(*(argv[i] + 1))
{
case 'S' : // Servername
argvalue(i,argc,argv,curinfo->servername);
break;

case 'D' : // Databasename
argvalue(i,argc,argv,curinfo->dbname);
break;

case 'P' : // SA password
argvalue(i,argc,argv,curinfo->password);
break;

case 'N' : // Stripe Number
argvalue(i,argc,argv,argval);
if ((curinfo->stripenum = (short)atoi(argval)) < 1)
{
printf("Invalid value %d for stripe device. Setting to 1\n");
curinfo->stripenum = 1;
}
else if (curinfo->stripenum > MAX_DEVICES)
{
printf("Invalid value %d for stripe device. Setting to %d\n",
MAX_DEVICES);
curinfo->stripenum = MAX_DEVICES;
}
break;

case 'F' : // Dump file prefix
argvalue(i,argc,argv,curinfo->dumpname);
break;

case 'O' : // Mode of operation
argvalue(i,argc,argv,argval);
if ( (!strcmp(argval, "DUMP")) || (!strcmp(argval, "dump")) )
curinfo->opcode = DUMP_MODE;
else if ( (!strcmp(argval, "LOAD")) || (!strcmp(argval, "load")) )
curinfo->opcode = LOAD_MODE;
else
{
printf("Invalid mode %s specified\n", argval);
exit(1);
}
break;

case '?' : // Help message
printf("NMPSDMP -Sservername -Ppassword -O{dump | load} -Ddatabasename -Nstripenumber -Fdumpprefix\n");
exit(1);
break;

default:
printf("Invalidparameter option : %s\n", *(argv[i] + 1) );
printf("NMPSDMP -Sservername -Ppassword -O{dump | load} -Ddatabasename -Nstripenumber -Fdumpprefix\n");
exit(1);
}
}
}
return;
}

// argument reading routine for parse_parms()
void argvalue(
    int   i,                           // parameter number
    int   argc,                        // #args
    char* argv[],                      // arglist
    char* out                          // receives parm 'i'
){
if( *(argv[i]+2) )
strcpy(out, argv[i]+2);
else
{
if( i+1<argc && !( NEWARG(i+1) ) )
            strcpy(out, argv[i+1]);
}
}

// procedure to setup all the threads for reading pipes
//
// return : 1 if success
//0 otherwise
int set_up_pipes(GINFO *curinfo, PIPEINFO **pipes)
{

inti;
PIPEINFO*curpipe;
charpipebuf[30];

// for every device
for (i=0; i < curinfo->stripenum; i++)
{
// allocate for the current pipe
if ((curpipe = malloc(sizeof(PIPEINFO))) == NULL)
return 0;
curpipe->slot = i;
curpipe->curinfo = curinfo;
curpipe->status = STAT_SUCCESS;

// prepare the pipe name
strcpy(curpipe->pipename, "\\\\");
strcpy(curpipe->spipename, "\\\\");
if (curinfo->servername[0])
strcat(curpipe->pipename, curinfo->servername);
else
strcat(curpipe->pipename, ".");
strcat(curpipe->spipename, ".");
strcat(curpipe->pipename, "\\pipe\\");
strcat(curpipe->spipename, "\\pipe\\");
sprintf(pipebuf, "sqldmpld%d", i);
strcat(curpipe->pipename, pipebuf);
strcat(curpipe->spipename, pipebuf);

// prepare the dump file name
sprintf(curpipe->dumpname, "%s%d.dmp", curinfo->dumpname, i);

// now start the thread for this device
pipes[i] = curpipe;
if (curinfo->opcode == DUMP_MODE)
{
if (!(_beginthread(dump_proc, 0, (LPVOID)curpipe)))
{
printf("beginthread failed for slot %d\n", i);
return 0;
}
}
else
{
if (!(_beginthread(load_proc, 0, (LPVOID)curpipe)))
{
printf("beginthread failed for slot %d\n", i);
return 0;
}
}
}

// started all the threads
return 1;
}

// procedure to close all the threads for reading/writing pipes
//
// return : nothing
//
void close_up_pipes(GINFO *curinfo, PIPEINFO **pipes)
{
inti;
PIPEINFO*curpipe;

// check if we need to clean up
if (!curinfo->stripenum)
return;

// for every open device
for (i=0; i < curinfo->stripenum; i++)
{
if (!(curpipe = pipes[i]))
continue;

// wait for the thread to close itself
while (!(curpipe->status & STAT_ENDTHREAD))
SleepEx(10, TRUE);

// free the allocated memory
free(curpipe);
}
}

// DUMP_PROC
// This procedure does the opening and reading of dumped data sent
//by the SQL Server. Each thread invoked with this routine services
//one named pipe (instance) connection; for simplicity we are creating
//separate named pipes for each stripe number.
//
//For dump process, the thread opens the pipe created by SQL Server
// and connects to it. Then keeps reading data and writing it
//out to the dumpfile till the SQL Server is done.

void dump_proc(PIPEINFO*curpipe)
{
GINFO*curinfo = curpipe->curinfo;
HANDLEhpipe = NULL;
HANDLEhfile = NULL;
BYTE*buf_start = NULL;
BYTE*buf_ptr;
DWORDbufsize, cbread, cbwritten, mode;
DWORDerrcode = 0;
DWORDoffl, offh;

// Open the pipe
while (1)
{

// check the global flag
if (curinfo->status & STAT_ERROR)
goto dump_proc_cleanup;

hpipe = CreateFile(curpipe->pipename,
GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);

// break if handle is valid
if (hpipe != INVALID_HANDLE_VALUE)
break;

// check the pipe state
errcode = GetLastError();
if (errcode == ERROR_FILE_NOT_FOUND)
{
// SQL Server has not yet created the pipe; wait
SleepEx(PIPE_WAIT_TIME, TRUE);
continue;
}
else if (errcode != ERROR_PIPE_BUSY)
{
printf("Error in %s : %d\n", "CreateFile", errcode);
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}

// Wait for sometime if pipe is busy
if (!WaitNamedPipe(curpipe->pipename, PIPE_WAIT_TIME))
{
printf("Error in %s : %d\n", "WaitNamedPipe", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}
}

printf("dump thread : %d : connected to server\n", curpipe->slot);

// get the buffer size
if ((!GetNamedPipeInfo(hpipe, NULL, NULL, &bufsize, NULL)) || (bufsize == 0))
{
printf("Error in %s : %d\n", "GetNamedPipeInfo", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}

// Align the buffer size to database pagesize if necessary
if (bufsize % DBPAGESIZE)
bufsize = ((bufsize / DBPAGESIZE) - 1) * DBPAGESIZE;
//printf("dump thread : %d : buffer size %d \n", curpipe->slot, bufsize);

// allocate the buffer for reading incoming data and align it
if ((buf_start = malloc(bufsize + 64)) == NULL)
{
printf("malloc failed\n");
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}
buf_ptr = BUFALIGN(buf_start);

// Set the pipe mode to message read mode
mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(hpipe, &mode, NULL, NULL))
{
printf("Error in %s : %d\n", "SetNamedPipeHandleState", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}

// open the dump file ; if it exists then overwrite it
// otherwise open a new file
hfile = CreateFile(curpipe->dumpname,
            GENERIC_READ | GENERIC_WRITE, 0, NULL,
            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hfile == INVALID_HANDLE_VALUE)
{
printf("Error in %s : %d\n", "CreateFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}

// set the file pointer to start of file
offh = 0;
if ((offl = SetFilePointer(hfile, 0, &offh, FILE_BEGIN)) == -1)
{
printf("Error in %s : %d\n", "SetFilePointer", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}

// now we are all set to read from the pipe; keep reading till the pipe is
// closed.
while (1)
{
// check the global flag
if (curinfo->status & STAT_ERROR)
goto dump_proc_cleanup;

if (!ReadFile(hpipe, buf_ptr, bufsize, &cbread, NULL))
{
// Are we done reading
if ((errcode = GetLastError()) == ERROR_BROKEN_PIPE)
break;
if (errcode != ERROR_MORE_DATA)
{
printf("Error in %s : %d\n", "ReadFile", errcode);
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}
}
//printf("dump thread : %d : read %d bytes\n", curpipe->slot, cbread);
if (!WriteFile(hfile, buf_ptr, cbread, &cbwritten, NULL))
{
printf("Error in %s : %d\n", "WriteFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto dump_proc_cleanup;
}
}

dump_proc_cleanup:

// release the resources
if (buf_start)
free(buf_start);
if (hfile)
{
FlushFileBuffers(hfile);
CloseHandle(hfile);
}
if (hpipe)
CloseHandle(hpipe);

// set the status flags
curpipe->status |= STAT_ENDTHREAD;
if (curpipe->status & STAT_ERROR)
curinfo->status |= STAT_ERROR;

return;
}

// LOAD_PROC
// This procedure does the opening and writing of dumped data sent
//to the SQL Server. Each thread invoked with this routine services
//one named pipe (instance) connection; we are create
//separate named pipes for each stripe number.
//
//For load process, the thread opens the pipe created by SQL Server
// and connects to it. Then keeps reading data from the dumpfile till end
//  and writes to the pipe; Finally closes the pipe when done.

void load_proc(PIPEINFO*curpipe)
{
GINFO*curinfo = curpipe->curinfo;
HANDLEhpipe = NULL;
HANDLEhfile = NULL;
BYTE*buf_start = NULL;
BYTE*buf_ptr;
DWORDbufsize, cbread, cbwritten, mode;
DWORDerrcode = 0;
DWORDoffl, offh;

// Open the pipe
while (1)
{

// check the global flag
if (curinfo->status & STAT_ERROR)
goto load_proc_cleanup;

hpipe = CreateFile(curpipe->pipename,
GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);

// break if handle is valid
if (hpipe != INVALID_HANDLE_VALUE)
break;

// check the pipe state
errcode = GetLastError();
if (errcode == ERROR_FILE_NOT_FOUND)
{
// SQL Server has not yet created the pipe; wait
SleepEx(PIPE_WAIT_TIME, TRUE);
continue;
}
else if (errcode != ERROR_PIPE_BUSY)
{
printf("Error in %s : %d\n", "CreateFile", errcode);
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

// Wait for sometime if pipe is busy
if (!WaitNamedPipe(curpipe->pipename, PIPE_WAIT_TIME))
{
printf("Error in %s : %d\n", "WaitNamedPipe", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}
}

printf("load thread : %d : connected to server\n", curpipe->slot);

// get the buffer size
if ((!GetNamedPipeInfo(hpipe, NULL, NULL, &bufsize, NULL)) || (bufsize == 0))
{
printf("Error in %s : %d\n", "GetNamedPipeInfo", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

// Align the buffer size to database pagesize if necessary
if (bufsize % DBPAGESIZE)
bufsize = ((bufsize / DBPAGESIZE) - 1) * DBPAGESIZE;
//printf("load thread : %d : buffer size %d \n", curpipe->slot, bufsize);

// allocate the buffer for reading incoming data and align it
if ((buf_start = malloc(bufsize + 64)) == NULL)
{
printf("malloc failed\n");
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}
buf_ptr = BUFALIGN(buf_start);

// Set the pipe mode to message read mode
mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(hpipe, &mode, NULL, NULL))
{
printf("Error in %s : %d\n", "SetNamedPipeHandleState", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

// open the dump file ; it should exist
hfile = CreateFile(curpipe->dumpname,
            GENERIC_READ | GENERIC_WRITE, 0, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hfile == INVALID_HANDLE_VALUE)
{
printf("Error in %s : %d\n", "CreateFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

// set the file pointer to start of file
offh = 0;
if ((offl = SetFilePointer(hfile, 0, &offh, FILE_BEGIN)) == -1)
{
printf("Error in %s : %d\n", "SetFilePointer", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

//
// Read the first page and write it to the pipe
//
if ((!ReadFile(hfile, buf_ptr, DBPAGESIZE, &cbread, NULL)) ||
(cbread != DBPAGESIZE))
{
printf("Error in %s : %d\n", "ReadFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}
if (!WriteFile(hpipe, buf_ptr, DBPAGESIZE, &cbwritten, NULL))
{
printf("Error in %s : %d\n", "WriteFile", errcode);
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}
//printf("load thread : %d : wrote %d bytes\n", curpipe->slot, cbwritten);

// now we are all set to read from the dumpfile; keep reading till we reach
// the end of dumpfile
while (1)
{
// check the global flag
if (curinfo->status & STAT_ERROR)
goto load_proc_cleanup;

// read from the dump file
if (!ReadFile(hfile, buf_ptr, bufsize, &cbread, NULL))
{
// check if we are done reading
if ((errcode = GetLastError()) == ERROR_HANDLE_EOF)
break;

printf("Error in %s : %d\n", "ReadFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}

// Done reading the dump file
if (cbread == 0)
break;

// write to the pipe
if (!WriteFile(hpipe, buf_ptr, cbread, &cbwritten, NULL))
{
printf("Error in %s : %d\n", "WriteFile", GetLastError());
curpipe->status |= STAT_ERROR;
goto load_proc_cleanup;
}
//printf("load thread : %d : wrote %d bytes\n", curpipe->slot, cbwritten);
}

load_proc_cleanup:

// release the resources
if (buf_start)
free(buf_start);
if (hfile)
CloseHandle(hfile);
if (hpipe)
{
FlushFileBuffers(hpipe);
CloseHandle(hpipe);
}

// set the status flags
curpipe->status |= STAT_ENDTHREAD;
if (curpipe->status & STAT_ERROR)
curinfo->status |= STAT_ERROR;

return;
}

INT msg_handler(dbproc,msgno,msgstate, severity, msgtext,server,proc,line)
PDBPROCESS dbproc;
DBINT msgno;
INT msgstate;
INT severity;
LPCSTRmsgtext;
LPCSTRserver;
LPCSTRproc;
DBUSMALLINT line;
{
   if (severity)
      printf("\nDB-LIB message: %s \n", msgtext);
   return (0);
};

INT err_handler(dbproc,severity, herrno, oserr, dberrstr, oserrstr)
PDBPROCESS dbproc;
INT    severity;
INT herrno;
INT oserr;
LPCSTR dberrstr;
LPCSTR oserrstr;
{
if (severity)
      printf("\nDB-LIBRARY error: %s \n", dberrstr);
    return (INT_CANCEL);
};


SQLCURS.C

/*************************************************************************

PROGRAM: SQLCURS - SQL DB-Library cursors sample program
 Copyright (c), 1995 by Microsoft Corp.

*************************************************************************/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <sqlfront.h>
#include <sqldb.h>/* DBLIB header files (should always be included    */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>

#ifndef NULL
#define    NULL    0
#endif

#define INVALID_VALUE (short)-100

/* Forward declarations of the error handler and message handler. */
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

void ProcessResults(void);

#define NROWS    5/* Number of rows to be scrolled at a time   */

/* Select statement to be used in the cursor case */

char stmt[] = " select au_lname, au_fname, city, state from authors where contract = 1 ";

/* Status array, and results set */

DBINT    pstat[NROWS];
char    au_lname[NROWS][41];
char    au_fname[NROWS][21];
char    au_city[NROWS][21];
char    au_state[NROWS][3];
DBINT    au_citlen[NROWS];
DBINT    au_statlen[NROWS];
DBINT    au_fnamlen[NROWS];
char    *stats[NROWS];
char    values[250];
short     ScrollOpt;
short     ConcurOpt;
int       ch;

main ()
{
DBPROCESS *dbproc;/* allocate a DB-LIB process structure*/
LOGINREC  *login;/* allocate a DB-LIB login structure*/
DBCURSOR  *hcursor; /* allocate a DB-LIB cursor structure*/

char       Servername[21] = "";
RETCODE    result_code;
int     fetch, optype;
int     nrow;
char    sfetch[3], soptype[3];
char    srow[3], stab[31];

/* Install the user-supplied error-handlingand message-handling
 * routines. They are defined at the bottomof thissource file.
 */

dberrhandle((DBERRHANDLE_PROC)err_handler);
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);

/* Get server's computer name */
printf ("\nEnter Name of SQL Server: ");
gets (Servername);

ScrollOpt = INVALID_VALUE;
do
{
printf ("\nEnter Scroll Option: F)orward  K)eyset  D)ynamic  I)nsensitive: ");
ch = tolower(_getch());
switch (ch)
{
case 'f': ScrollOpt = CUR_FORWARD; break;
case 'k': ScrollOpt = CUR_KEYSET;  break;
case 'd': ScrollOpt = CUR_DYNAMIC; break;
case 'i': ScrollOpt = CUR_INSENSITIVE; break;
default:  printf("Invalid character entered."); break;
}
} while (ScrollOpt == INVALID_VALUE);

ConcurOpt = INVALID_VALUE;
do
{
printf ("\nEnter Concurrency Option: R)ead Only   L)ock CC   O)opt CC   V)al Opt CC: ");
ch = tolower(_getch());
switch (ch)
{
case 'r': ConcurOpt = CUR_READONLY; break;
case 'l': ConcurOpt = CUR_LOCKCC;   break;
case 'o': ConcurOpt = CUR_OPTCC;    break;
case 'v': ConcurOpt = CUR_OPTCCVAL; break;
default:  printf("Invalid character entered."); break;
}
} while (ConcurOpt == INVALID_VALUE);
printf("\n");
login = dblogin();    /* get login recordfrom DB-LIB */
DBSETLUSER (login, (char far *)"sa");     /* set the username*/
DBSETLAPP (login, (char far *)"curtest"); /* set the application name */
DBSETLPWD (login, (char far *)"");      /* set the SQL Server password */
DBSETLVERSION(login, DBVER60);

/* Now attempt to create and initialize a DBPROCESSstructure */
if((dbproc = dbopen (login, Servername)) == NULL)
{
printf ("dbopen failed\n");
return (1); /* exit program */
}

dbuse (dbproc, "pubs");  /* use the "pubs" database */

/* Open the cursor */
hcursor = dbcursoropen(dbproc, stmt, ScrollOpt, ConcurOpt, NROWS, pstat);
if (hcursor == (DBCURSOR *)NULL)
{
printf("\ndbcursoropen failed\n");
return(1);
}

/* Bind variables */
result_code = dbcursorbind(hcursor, 1, NTBSTRINGBIND, 41, NULL, (char *)au_lname);
if (result_code == FAIL)
{
printf("\ndbcursorbind failed, column 1\n");
return(1);
}
result_code = dbcursorbind(hcursor, 2, NTBSTRINGBIND, 21, au_fnamlen, (char *)au_fname);
if (result_code == FAIL)
{
printf("\ndbcursorbind failed, column 2\n");
return(1);
}
result_code = dbcursorbind(hcursor, 3, NTBSTRINGBIND, 21, au_citlen, (char *)au_city);
if (result_code == FAIL)
{
printf("\ndbcursorbind failed, column 3\n");
return(1);
}

result_code = dbcursorbind(hcursor, 4, NOBIND, 0, au_statlen, (char *)stats);
if (result_code == FAIL)
{
printf("\ndbcursorbind failed, column 4\n");
return(1);
}

/* Begin transaction. Will exit without committing the transaction so that
** none of the data modifications will actually be committed */
if ((dbcmd(dbproc, "begin transaction") == FAIL) ||
(dbsqlexec(dbproc) == FAIL) || (dbresults(dbproc) == FAIL))
{
printf("\n BEGIN TRAN failed");
return(1);
}

/* Now fetch and print the rows */
while (TRUE)
{
printf("\n0)Leave  1)First  2)Next  3)Previous  4)Random  5)Relative  6)Last  7)By Value");
printf("\nEnter fetch value: ");
fetch = atoi(gets(sfetch));
if (fetch != 0)
{
if ((fetch == FETCH_RANDOM) || (fetch == FETCH_RELATIVE))
{
printf("\n Enter row number: ");
nrow = atoi(gets(srow));
}
else
nrow = 0;

if (dbcursorfetchex(hcursor, fetch, nrow, NROWS, 0) == FAIL)
printf("\ndbcursorfetchex() failed\n");

ProcessResults();
continue;/* Fetch again */
}

/* Do updates */
while (TRUE)
{
printf("\n0)Leave  1)Update  2)Delete  3)Insert  4)Refresh  5)Lock  6)Cursor Fetch");
printf("\nEnter operation: ");
optype = atoi(gets(soptype));

if ((optype == 0) || (optype == 6))/* Exit condition */
break;

if (optype > 6)
continue;

printf("\nEnter buffer number: ");
nrow = atoi(gets(srow));
printf("\nEnter table: ");
gets(stab);
printf("\nEnter values: ");
gets(values);
if (values == (char *)NULL)
values[0] = '\0';
if ((result_code=dbcursor(hcursor, optype, nrow, stab, values)) == FAIL)
printf("\n dbcursor failed");
else
if (optype == CRS_REFRESH)
ProcessResults();
else
printf("\ndbcursor() succeeded");

} /* Exit dbcursor() loop */

if (optype == 0)
break;   /* Close cursor */
}

dbcursorclose(hcursor);

dbexit();
}

void ProcessResults()
{
int i, len;

printf(" Row       First Name                 Last Name                City       State\n");
printf("----- -------------------- ------------------------------ --------------- -----\n");
for (i = 0 ; i < NROWS ; i++)
{/* Process results */
if(pstat[i] & FTC_SUCCEED)
{
len = 0;
if (au_statlen[i] != 0) /* This is a NOBIND type */
{
len = min(2, (SHORT)au_statlen[i]);
memcpy(au_state[i], stats[i], len);
}
au_state[i][len] = '\0';
  printf("%5d %-20s %-30s %-15s %-2s\n",i+1,au_fname[i],au_lname[i],au_city[i],au_state[i]);
}
else
if (pstat[i] &  FTC_MISSING)
printf("\n Row no. %d is missing", i+1);

/*

The following code could be used if dbcursorfetch()
was used instead of dbcursorfetchex().

if (pstat[i] & FTC_ENDOFRESULTS)
{
printf("\n End of results");
break;
}
if (pstat[i] & FTC_ENDOFKEYSET)
{
printf("\nEnd of keyset");
break;
}
*/

}
return;
}

int err_handler(dbproc,severity, dberr, oserr,dberrstr, oserrstr)
DBPROCESS *dbproc;
int severity;
int dberr;
int oserr;
char    *dberrstr;
char    *oserrstr;
{
printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) ||(DBDEAD(dbproc)))
return(INT_EXIT);
else
{
return(INT_CANCEL);
}
}

int msg_handler(dbproc,msgno, msgstate, severity, msgtext)
DBPROCESS *dbproc;
DBINT msgno;
int msgstate;
int severity;
char    *msgtext;
{
printf("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno,msgstate, severity, msgtext);
return(0);
}


SQLEXAMP.C

/* SQLEXAMP.C - Copyright (c) 1990 - 1995 by Craig Henry.  This program may
** be freely distributed and copied, without charge.  However, any attempt to
** charge for this program will be considered a copyright infringement and
** will be prosecuted to the fullest extent.
**
** This program provides a simple example of logging onto a SQL Server,
** sending down commands, retrieving metadata, and result rows.  Formatting
** and printing those results on the console.
**
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include "stdio.h"// include standard header
#include "sqlfront.h"// include dblib macro/manifest defines
#include "sqldb.h"// include dblib datatype, prottypes, etc
#include "string.h"// include for string functions
#include "malloc.h"// include for malloc and free

// prototypes for internal functions
extern int DetermineRowSize(DBPROCESS *,int);
extern RETCODE PrintHeaders(DBPROCESS *);
extern RETCODE PrintRow(DBPROCESS *);

/*
** DetermineRowSize(DBPROCESS *,int)
**
** This function returns either the size of all columns in the row, converted
** to character data (SQLCHAR) with one space between each column, or
** if col is non-zero, the length of the input column converted to string.
** It is used to build the header strings, and each row of data, and is
** called to allocate the memory needed for each row, and determine how
** much of that space is to be used for each column
*/
int DetermineRowSize(dbproc,col)
DBPROCESS *dbproc;// The SQL Server connection structure
int col;// column size to get, 0 for all
{
    int x,cols;// counters
    int length=0;// total length of column(row).
    int namelength;// length of name of column
    int prlength;// printable length
    char *name;// pointer to column name
    if(!col)        // get number of columns
       cols = dbnumcols(dbproc);
    // count from 1 to numcols if col is 0, else x will = col only
    for(x=((col) ? col : 1);x<=((col) ? col : cols);x++)
    {
        switch(dbcoltype(dbproc,x))// get column type, determine SQLCHAR
        {// converted length
case SQLNUMERIC:
case SQLDECIMAL:
{
DBCOL Col;
dbcolinfo(dbproc,CI_REGULAR,x,0,&Col);
prlength = Col.Precision + 2;
}
break;

    case SQLBIT:// The PR... values are found in the
        prlength = PRBIT;// SQLDB.H header file.
        break;
    case SQLINT1:
           prlength = PRINT1;
        break;
    case SQLINT2:
          prlength = PRINT2;
        break;
    case SQLINT4:
        prlength = PRINT4;
        break;
    case SQLFLT8:
        prlength = PRFLT8;
        break;
    case SQLDATETIME:
        prlength = PRDATETIME;
        break;
    case SQLMONEY:
        prlength = PRMONEY;
        break;
            case SQLVARBINARY :// VARBINARY IMAGE, and BINARY
            case SQLBINARY:// convert to 2 times length
            case SQLIMAGE:
                prlength = dbcollen(dbproc,x)*2;
                break;
    default :
        prlength = dbcollen(dbproc,x); // other types are maximum of
        break; // actual column length
        }
        name = (char *)dbcolname(dbproc,x); // names may be longer than
        namelength =  (name) ? strlen(name) : 0; // column so use name len if
        if(prlength<namelength) // longer of two.
           length+=namelength+1; // add one for space between
        else // columns
           length+=prlength+1;
    }
    return length;// return the length of the
    // field
}
/*
** RETCODE PrintHeaders(DBPROCESS *)
**
** This function builds the string that contains the names of each column,
** and a string containing '=' as a separator line.  It does this by finding
** the print size of each column, allocating a buffer to hold all column names
** plus one space between each column name, then copying that name into the
** appropriate location into the buffer.  Finally the two lines are
** printed.
*/
RETCODE PrintHeaders(dbproc)
DBPROCESS *dbproc;// The SQL Server connection structure pointer
{
    int x,cols,size;// counters
    char *header;// pointer for separator buffer
    char *colnames;// pointer for column name buffer
    char *colname;// scratch pointers
    char *ptr,*hptr;
    size = DetermineRowSize(dbproc,0);// get size of buffers
    ptr = colnames = malloc(size+1); // get name buffer
    hptr = header = malloc(size+1); // get separator buf
    memset (header,' ',size);// set buffers to all spaces
    memset (colnames,' ',size);
    cols = dbnumcols(dbproc);// get number of columns
    for(x=1;x<=cols;x++)// loop on all columns
    {
        size = DetermineRowSize(dbproc,x); // get size of this column
        colname = (char *)dbcolname(dbproc,x);// get column name
        strncpy(ptr,colname,strlen(colname));// copy name
        memset(hptr,'=',size-1);// set ='s in separator line
        hptr+=size;// move to next position
        ptr+=size;// move to next position
    }
    *ptr = '\0';// null term both strings
    *hptr = '\0';
    printf("%s\n",colnames);// print both strings
    printf("%s\n",header);
    free(colnames);// free both buffers
    free(header);
    return SUCCEED;// done
}
/*
** RETCODE PrintRow(DBPROCESS *)
**
** This function prints out one row.  dbnextrow() must be called to fetch the
** row to print.  This routine could be used to print the current row as
** many times as wanted, as the current row data is always available until
** dbnextrow() is called to fetch the next row.  This routine works like
** PrintHeaders above, but each column's data is obtained instead of a row
** name, and converted to a string.  It is then set into the buffer.
*/
RETCODE PrintRow(dbproc)
DBPROCESS *dbproc;// SQL Server connection structure
{
    int x,cols,size,datasize,colwidth,coltype;// counters
    char *datavals;// data buffer pointer
    char *data;// column data pointer
    char *ptr;// scratch pointer
 colwidth = DetermineRowSize(dbproc,0);
    ptr = datavals = malloc(colwidth+1); // get buffer
    cols = dbnumcols(dbproc);// get number of columns
    for(x=1;x<=cols;x++)// do all columns
    {
   coltype = dbcoltype(dbproc,x);
        size = DetermineRowSize(dbproc,x); // determine size of this column
        memset(ptr,' ',size);// set it to spaces
        data = (char *)dbdata(dbproc,x);// get pointer to column's data
        if(data == (BYTE *)NULL)// if NULL, use "NULL"
        {
            strncpy(ptr,"NULL",4);// set NULL into buffer
            ptr += size;// point past this column in output buf
        }
        else// else have data, so convert to char
        {
            datasize = dbconvert(dbproc,coltype,data,dbdatlen(dbproc,x),
                SQLCHAR,ptr,(DBINT)size-1);
if (datasize < size && (coltype == SQLNUMERIC || coltype == SQLDECIMAL || coltype == SQLINT1 ||
    coltype == SQLINT2 || coltype == SQLINT4 || coltype == SQLFLT8 || coltype == SQLFLT4))
{
memmove(ptr+size-1-datasize,ptr,datasize);
memset(ptr,' ',size-1-datasize);
}
            ptr+=size;
        }
    }
    *ptr = '\0';// null term string
    printf("%s\n",datavals);// print row
    free(datavals);// free buffer
    return SUCCEED;// done
}

/*
**
** The below main is a mini isql interpreter and as such is only
** used for demonstration purposes.  Command line args include the Server
** name as arg 1, User ID as arg 2, assumes the password is null.
** This routine requests opens the connection after obtaining the login record
** and filling it with the necessary info.  Once the connection is established
** it accpets command input, set's it into the dbproc.  On "go" it executes
** the command against the server, processes each results set and then returns
** to accepting command input.  If "quit" or "exit" is input the program
** is terminated.  This interpreter will not process COMPUTE statements,
** and will not work with commands that return text/image data.
*/
int main(argc, argv)
int argc;
char *argv[];
{
    LOGINREC *login;// login rec pointer
    DBPROCESS *dbproc;// SQL Server connection structure pointer

    char cmd[150];// command buffer
    char server[30];// server name buffer
    int x=1;// command line counter
    STATUS retc;// return code
    const char * sqlversion;// pointer for version string
    int err_handler(DBPROCESS*, int, int, int, char*, char*);
    int msg_handler(DBPROCESS*, DBINT, int, int, char*);

    *server = '\0';// null start these two buffers
    *cmd = '\0';

    if(argc == 1)// if no server name, request it
    {
printf("Enter Server Name: ");
gets(server);
    }
    else// else it was input as first arg
       strcpy(server,argv[1]);
    if(argc < 2)// if no login id, request it
    {
       printf("Enter User Name: ");
       gets(cmd);
    }
    else// otherwise it was input as second arg.
       strcpy(cmd,argv[2]);

    // check to see if communications layer was loaded (DOS ONLY)
    if((sqlversion = dbinit()) == (BYTE *)NULL)
    {
       // DOS TSR (DBNMPIPE.EXE) is not loaded, don't bother going any farther
       printf("Error in DB-Library initialization, exiting\n");
       return 1;
    }
    else
       printf("DB-Library version: %s\n",sqlversion); // print dblib version

    dbsettime(30);// set timeouts to 30 seconds

    // set error/msg handlers for this program
    dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
    dberrhandle((DBERRHANDLE_PROC)err_handler);

    login = dblogin();// get a login rec

    DBSETLUSER(login,cmd);// set login id
    DBSETLHOST(login,"SQL EXAMPLE");// set host name for sp_who
    DBSETLVERSION(login, DBVER60);

    // open connection to requested server.  Pass null server name for local
    // connection, if name not entered.
    if((dbproc = dbopen(login,(*server) ? server : (char *)NULL)) == (DBPROCESS *)NULL)
    {
        // no one answered, so couldn't connect or error occurred
printf("Login failed\n");
return 1;
    }
    else
    {
        // loop on command input until quit or exit appears in first 4 bytes.
while((strnicmp(cmd,"quit",4) != 0) && (strnicmp(cmd,"exit",4)!=0))
{
   printf("%d> ",x++);// print command prompt
   gets(cmd);// get command
   if(strnicmp(cmd,"go",2) == 0)// is it go
   {
      if(dbsqlexec(dbproc) == FAIL)// execute command
              {
                 // problem occurred, just try another command
                 printf("Error in executing command batch!\n");
                 x = 1;
                 continue;
              }
              // command executed correctly, get results information
      while((retc = dbresults(dbproc)) != NO_MORE_RESULTS)
      {
 if (retc == FAIL)// if error get out of loop
     break;
 // headers and data could be printed here with only two
                 // function calls, dbprhead(dbproc), and dbprrow(dbproc),
                 // which would output the headers, and all the data to
                 // standard output.  However, that isn't very informative
                 // toward understanding how this data is obtained and
                 // processed, so I do it the hard way, one column at a time.

                 PrintHeaders(dbproc);// print header data

                 // loop on each row, until all read
                 while((retc= dbnextrow(dbproc))!=NO_MORE_ROWS)
                 {
                    if(retc == FAIL)// if fail, then clear
                    {// connection completely, just
                       dbcancel(dbproc);// in case.
                       break;
                    }
                    else
                        PrintRow(dbproc);// else print the current row
                 }
 if(DBCOUNT(dbproc) == 1L)// print the row count
      printf("(1 row effected)\n");
  else
      printf("(%ld rows effected)\n",DBCOUNT(dbproc));

      } // end while(dbresults())

              x = 1;// reset command line counter
   }
   else
   {
      strcat(cmd," ");// go not detected, so put space
      dbcmd(dbproc,cmd);// between each command and set in
   }// dbproc.

        } // end while()

        dbclose(dbproc);// quit/exit input, close connection

        // print adios and exit.
        printf("SQL Server Connection to %s closed, bye bye.\n",server);
return 0;
     }
}
/*
** msg_handler(char *buffer, long len);
**
** This routine is a local isql message handler call back function that
** is invoked whenever the SQL Server is sending a message back to
** the program.
**
*/
int msg_handler(dbproc, Msg, State, Severity, Message)
DBPROCESS *dbproc;// SQL Server connection structure
DBINT Msg;// SQL Server message number
int State;// State of the message
int Severity;// Severity of the message
char *Message;// The message itself (read only)
{
    printf("Message No.: %ld, Msg. State: %d, Msg. Severity: %d\n",
Msg,State,Severity);
    if(Message != NULL)
       printf("%s\n",Message);
    return (0);
}

/*
** err_handler(char *buffer, long len);
**
** This routine is a local error handler called by dblib if an internal
** error occurs, also to notify when a server message has been sent, which is
** obtained through the above message handler.
**
*/
int err_handler(dbproc, Severity,dberr, oserr, errstr, oserrstr)
DBPROCESS *dbproc;// SQL Server connection structure
int Severity;// Severity of Dblib error
int dberr;// dblib error, all dblib errors start at 10000
int oserr;// OS error from, C runtime
char *errstr;// dblib error string
char *oserrstr;// OS error string (if any)
{

    printf("DB-LIBRARY Error - Severity: %d, Error No: %d, OS Error No: %d\n",
       Severity, dberr, oserr);
    if(errstr != NULL)
       printf("%s\n",errstr);
    if(oserrstr != NULL)
       printf("%s\n",oserrstr);
    return INT_CANCEL;
}

/*****************************************************************************/
/*======================== E N D - O F - F I L E ============================*/
/*****************************************************************************/


SQLTESTC.C

/*************************************************************************

SQLTESTC - SQL Data Server sample program for console-based Windows NT.

Copyright (c), 1995 by Microsoft Corp.

*************************************************************************/

#defineDBNTWIN32// must identify operating system environment
#include "windows.h"

#include <sqlfront.h>
#include <sqldb.h>   // DB-LIB header file (should always be included)
#include <stdio.h>

main ()
{
PDBPROCESS dbproc;  // allocatea DB-LIB process structure
PLOGINREC  login;   // allocatea DB-LIB login structure

// Variables used to store the returning data
char       au_lname[41];
char       au_fname[20];
char       id[12];
char       phone[13];
char       address[41];
char       city[21];
char       state[3];
char       zip[6];
char       getname[41];
char       Servername[25];
RETCODE    result_code;

// Forward declarations of the error handler and message handler.
    int err_handler(PDBPROCESS, int, int, int, char*, char*);
    int msg_handler(PDBPROCESS, DBINT, int, int, char*);

if (dbinit() == (char *)NULL)
{
printf("Communications layer not loaded\n");
return(1);
}

// Install the user-supplied error-handlingand message-handling
// routines. They are defined at the bottomof thissource file.

dberrhandle((DBERRHANDLE_PROC)err_handler);
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);

// Get server's computer name
Servername[0] = '\0';
printf ("\nEnter Name of SQL Server: ");
gets (Servername);

login = dblogin();                     // get login record from DB-LIB
DBSETLUSER (login, (char *)"sa");      // set the username
DBSETLAPP (login, (char *)"sqltestp"); // set the application name
DBSETLPWD (login, (char *)"");         // set the SQL Server password
DBSETLVERSION(login,DBVER60);

// Now attempt to create and initialize a DBPROCESS structure
if ((dbproc= dbopen (login, Servername)) == NULL)
{
printf ("dbopen failed\n");
return (1); // exit program
}

dbuse (dbproc, "pubs"); // use the "pubs" database

while (TRUE)
{
printf ("\nEnter author's last name to retrieve (return to exit): ");
gets (getname);

if (getname[0] == '\0') // if only a return was entered
break;

// construct command buffer to be sent to the SQL server
dbcmd (dbproc, (char *)"select au_id, au_lname, au_fname, phone,");
dbcmd (dbproc, (char *)" address, city, state, zip");
dbcmd (dbproc, (char *)" from authors");
dbcmd (dbproc,(char *)" where au_lname = '");
dbcmd (dbproc, (char *)getname);
dbcmd (dbproc, (char *)"'");

dbsqlexec (dbproc); // send command buffer to SQL server

// now check the results from the SQL server
while ((result_code = dbresults(dbproc)) != NO_MORE_RESULTS)
{
if (result_code == SUCCEED)
{
dbbind (dbproc,1, NTBSTRINGBIND, (DBINT) 0, (char *)id);
dbbind (dbproc,2, NTBSTRINGBIND, (DBINT) 0, (char *)au_lname);
dbbind (dbproc,3, NTBSTRINGBIND, (DBINT) 0, (char *)au_fname);
dbbind (dbproc,4, NTBSTRINGBIND, (DBINT) 0, (char *)phone);
dbbind (dbproc,5, NTBSTRINGBIND, (DBINT) 0, (char *)address);
dbbind (dbproc,6, NTBSTRINGBIND, (DBINT) 0, (char *)city);
dbbind (dbproc,7, NTBSTRINGBIND, (DBINT) 0, (char *)state);
dbbind (dbproc,8, NTBSTRINGBIND, (DBINT) 0, (char *)zip);

// now process the rows
while (dbnextrow(dbproc) != NO_MORE_ROWS)
{
printf ("Author ID:  %s\n",id);
printf ("Last Name:  %s\n",au_lname);
printf ("First Name: %s\n",au_fname);
printf ("Address:    %s\n",address);
printf ("City:       %s\n",city);
printf ("State:      %s\n",state);
printf ("Zip Code:   %s\n",zip);
printf ("Telephone:  %s\n",phone);
printf ("\n");
}
}
else
{
printf ("Results Failed\n");
break;
}
}
} // while (TRUE)

// Close the connection and exit
dbexit();
}

int err_handler(PDBPROCESS dbproc, int severity, int dberr, int oserr, char * dberrstr, char * oserrstr)
{
if (dberrstr != NULL)
printf("DB-LIBRARY error:\n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf("Operating-system error:\n\t%s\n", oserrstr);

if ((dbproc == NULL) ||(DBDEAD(dbproc)))
return(INT_EXIT);
else
return(INT_CANCEL);
}

int msg_handler(PDBPROCESS dbproc, DBINT msgno, int msgstate, int severity, char * msgtext)
{
printf("SQL Server message %ld, state %d, severity %d:\n\t%s\n",
msgno, msgstate, severity, msgtext);
return(0);
}


SQLTESTN.C

/****************************************************************************

PROGRAM: SqlTestn.c
Copyright 1995 - 1998 Microsoft Corp.

PURPOSE: SqlTest sample Windows applications

FUNCTIONS:

WinMain() - calls initialization function, processes message loop
SqlTestInit() - initializes window data and registers window
SqlTestWndProc() - processes messages
AboutSQL() - processes messages for "About" dialog box
SelectSQL() - processes input of author name
ConnectSQL() - processes input of server name and connects to server

COMMENTS:

WIN32 can have several copies of your application running at the
same time.  The variable hInst keeps track of which instance this
application is so that processing will be to the correct window.

You only need to initialize the application once.  After it is
initialized, all other copies of the application will use the same
window class, and do not need to be separately initialized.

****************************************************************************/

#include "windows.h"      /* required for all NT Windows applications*/
#include "stdio.h"
#define DBNTWIN32         /* needed to define environment     */
#include "sqlfront.h"     /* standard dblib include file    */
#include "sqldb.h"        /* standard dblib include file    */
#include "sqltestn.h"     /* specific to this program    */

PDBPROCESS dbproc = (PDBPROCESS)NULL;
              /* dbprocess pointer for dblib connection*/
HINSTANCE hInst;    /* current instance    */
HWND ghWnd;      /* global window handle for handlers    */
HWND errhWnd;    /* global window handle for current error*/

/****************************************************************************

FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)

PURPOSE: calls initialization function, processes message loop

COMMENTS:

This will initialize the window class if it is the first time this
application is run.  It then creates the window, and processes the
message loop until a PostQuitMessage is received.  It exits the
application by returning the value passed by the PostQuitMessage.

****************************************************************************/

int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
HINSTANCE hInstance;     /* current instance     */
HINSTANCE hPrevInstance;     /* previous instance     */
LPSTR lpCmdLine;     /* command line     */
int nCmdShow;     /* show-window type (open/icon) */
{
HWND hWnd;     /* window handle     */
MSG msg;     /* message     */

if (!hPrevInstance)/* Has application been initialized? */
if (!SqlTestInit(hInstance))
return (0);/* Exits if unable to initialize     */

hInst = hInstance;/* Saves the current instance     */

hWnd = CreateWindow("SQL Test",               /* window class     */
"SQL Server Sample Windows NT Application", /* window name     */
WS_OVERLAPPEDWINDOW|WS_VISIBLE,            /* window style     */
CW_USEDEFAULT,                             /* x position     */
CW_USEDEFAULT,                             /* y position     */
CW_USEDEFAULT,                             /* width     */
CW_USEDEFAULT,                             /* height     */
NULL,                                      /* parent handle     */
NULL,                                      /* menu or child ID     */
hInstance,                                 /* instance     */
NULL);                                     /* additional info     */

if (!hWnd)  /* Was the window created? */
return (0);

ghWnd = hWnd;  /* set global handle     */
errhWnd = hWnd;

ShowWindow(hWnd, SW_SHOW); /* Shows the window        */
UpdateWindow(hWnd);        /* Sends WM_PAINT message  */

while (GetMessage(&msg, /* message structure */
NULL,      /* handle of window receiving the message */
0,             /* lowest message to examine */
0))            /* highest message to examine*/
{
TranslateMessage(&msg);   /* Translates virtual key codes     */
DispatchMessage(&msg);   /* Dispatches message to window     */
}
return (msg.wParam);   /* Returns the value from PostQuitMessage */
}

/****************************************************************************

FUNCTION: SqlTestInit(HANDLE)

PURPOSE: Initializes window data and registers window class

COMMENTS:

Sets up a structure to register the window class.  Structure includes
such information as what function will process messages, what cursor
and icon to use, etc.

****************************************************************************/

BOOL SqlTestInit(hInstance)
HANDLE hInstance;       /* current instance     */
{
HANDLE hMemory;       /* handle to allocated memory */
PWNDCLASS pWndClass;       /* structure pointer     */
BOOL bSuccess;       /* RegisterClass() result     */

hMemory = LocalAlloc(LPTR, sizeof(WNDCLASS));
pWndClass = (PWNDCLASS)hMemory;

pWndClass->style = 0; /*CS_HREDRAW | CS_VREDRAW; */
pWndClass->lpfnWndProc = (WNDPROC)SqlTestWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = LoadIcon(hInstance, "SQLITEST");
pWndClass->hCursor = LoadCursor(NULL, IDC_ARROW);
pWndClass->hbrBackground = GetStockObject(WHITE_BRUSH);
pWndClass->lpszMenuName = (LPSTR)"SQLTest";
pWndClass->lpszClassName = (LPSTR)"SQL Test";

bSuccess = RegisterClass(pWndClass);

LocalFree(hMemory);    /* Returns it to NT */
return (bSuccess); /* Returns result of registering the window */
}

/****************************************************************************

FUNCTION: SqlTestWndProc(HWND, unsigned, WORD, LONG)

PURPOSE:  Processes messages

MESSAGES:

WM_SYSCOMMAND - system menu (About dialog box)
WM_CREATE     - create window
WM_DESTROY    - destroy window
WM_COMMAND    - application menus (Connect and Select dialog boxes

COMMENTS:

To process the ID_ABOUTSQL message. Call Dialog
box which will create the box according to the information in your
SqlTest.rc file and turn control over to the About() function.When
it returns, free the intance address.
This same action will take place for the two menu items Connect and
Select.

****************************************************************************/

LONG APIENTRY SqlTestWndProc(hWnd, message, wParam, lParam)
HWND hWnd;  /* window handle     */
UINT message;          /* type of message     */
DWORD wParam;  /* additional information     */
LONG lParam;  /* additional information     */
{
  /* functions      */
HMENU hMenu;  /* handle to the System menu     */

switch (message)
{
case WM_SYSCOMMAND:/* message: command from system menu */
if (wParam == ID_ABOUTSQL)
{
DialogBox(hInst, /* current instance     */
(LPTSTR)"ABOUTSQL", /* resource to use     */
hWnd, /* parent handle     */
(DLGPROC) AboutSQL); /* About() instance address */

break;
}
else    /* Lets NT process it     */
return (DefWindowProc(hWnd, message, wParam, lParam));

case WM_CREATE:    /* message: window being created */

/* Get the handle of the System menu */
hMenu = GetSystemMenu(hWnd, FALSE);

/* Add a separator to the menu */
AppendMenu(hMenu,      /* menu handle     */
MF_SEPARATOR,      /* menu item to change */
0,      /* new menu item     */
0);      /* menu identifier     */

/* Add new menu item to the System menu */
      AppendMenu(hMenu,      /* menu handle     */
MF_STRING,      /* menu item to change */
ID_ABOUTSQL,      /* menu identifier     */
"A&bout SQL Test...");      /* new menu item     */

dbinit();                                 /* initialize dblib    */

/* Now make the message and error    */
/* Install the handler into dblib */
dberrhandle((DBERRHANDLE_PROC)dbwinErrorHandler);
dbmsghandle((DBMSGHANDLE_PROC)dbwinMessageHandler);
break;

case WM_COMMAND :/* menu selections generate */

/* the WM_COMMAND message   */
switch(wParam)/* menu in WORD parameter   */
{
case IDM_CONNECT :/* connect to server    */
DialogBox(hInst,/* current instance     */
"CONNECT", /* resource to use     */
hWnd,/* parent handle     */
(DLGPROC) ConnectSQL); /* ConnectSQL() instance address */

break;

case IDM_SELECT :/* select an author    */

DialogBox(hInst, /* current instance     */
"SELECT", /* resource to use     */
hWnd, /* parent handle     */
(DLGPROC) SelectSQL); /* About() instance address */

break;
}
break;

case WM_DBRESULTS :/* a select has been issued */
SqlTestProcessResults(hWnd);/* process results    */
break;

case WM_DESTROY:  /* message: window being destroyed */
dbexit();  /* free any active dbprocesses     */
PostQuitMessage(0);
break;

default:  /* Passes it on if unproccessed    */
return (DefWindowProc(hWnd, message, wParam, lParam));
}

return (0L);
}

/****************************************************************************

FUNCTION: AboutSQL(HWND, unsigned, WORD, LONG)

PURPOSE:  Processes messages for "AboutSQL" dialog box

MESSAGES:

WM_INITDIALOG - initialize dialog box
WM_COMMAND    - Input received

COMMENTS:

No initialization is needed for this particular dialog box, but TRUE
must be returned to NT.

Wait for user to click on "Ok" button, then close the dialog box.

****************************************************************************/

LONG APIENTRY AboutSQL(hDlg, message, wParam, lParam)
HWND hDlg;
UINT message;
DWORD wParam;
LONG lParam;
{
switch (message)
{
case WM_INITDIALOG:   /* message: initialize dialog box */
return (TRUE);

case WM_COMMAND:      /* message: received a command */
if (wParam == IDOK)
{      /* "OK" box selected?     */
EndDialog(hDlg, 0);      /* Exits the dialog box     */
return (TRUE);
}
break;
}
return (FALSE);      /* Didn't process a message    */
}
/****************************************************************************

FUNCTION: SelectSQL(HWND, unsigned, WORD, LONG)

PURPOSE:  Processes messages for "SelectSQL" dialog box

MESSAGES:

WM_INITDIALOG - initialize dialog box
WM_COMMAND    - Input received

COMMENTS:

No initialization is needed for this particular dialog box, but TRUE
must be returned to NT.

Let user input into edit control the name of an author (the select
IS case sensitive).  When user presses OK, format the select statement
then send it to the server and execute it via dbsqlexec(). If the
dbsqlexec() SUCCEED's post a WM_DBRESULTS message so the results
may be retrieved and processed.

Wait for user to click on "Ok" button, then close the dialog box.

****************************************************************************/

LONG APIENTRY SelectSQL(hDlg, message, wParam, lParam)
HWND hDlg;
UINT message;
DWORD wParam;
LONG lParam;
{
char szSelectAuthor[41];  /* string for authors name    */
char szServerMess[45];  /* string for server response    */
char szAName[40];  /* format string for author    */
switch (message)
{
case WM_INITDIALOG:   /* message: initialize dialog box */
SendDlgItemMessage(hDlg,       /* limit input to 40 characters   */
AUTHORNAME,EM_LIMITTEXT,40,0L);
return (TRUE);

case WM_COMMAND:      /* message: received a command */
errhWnd = hDlg;
switch(wParam)
{
case IDOK :      /* "OK" box selected?     */
*szSelectAuthor = '\0';   /* Null author     */
GetDlgItemText(hDlg,AUTHORNAME, (LPSTR)szSelectAuthor, MAX_ANAME);
if(dbproc == (PDBPROCESS)NULL) /* if not a valid process*/
{
/* No server to query*/
MessageBox(hDlg,
"No SQL Server Connected to Query",
"SQL Test",MB_ICONHAND | MB_OK);
}
else
if(*szSelectAuthor != '\0') /* if a name exists */
{
DBLOCKLIB();/* lock down the library */
/* format the select statement */
dbcmd(dbproc,
(LPSTR)"select au_id, au_lname,"
"au_fname, phone, address, city, state, zip");
dbcmd(dbproc, (LPSTR)" from authors");
dbcmd(dbproc, (LPSTR)" where au_lname = ");
sprintf(szAName,"'%s'",szSelectAuthor);
dbcmd(dbproc,(LPSTR)szAName);
if (dbsqlexec(dbproc) == FAIL)
{
sprintf(szServerMess,/* error, not in db */
"%s not found in database pubs",
szSelectAuthor);
MessageBox(hDlg,
(LPSTR)szServerMess,(LPSTR)"SQL Test",
MB_ICONHAND | MB_OK);
}
else/* query SUCCEEDed so */
{/* post message to process results*/
PostMessage(GetParent(hDlg),WM_DBRESULTS,0,0L);
}
DBUNLOCKLIB();/* unlock library*/
}
EndDialog(hDlg, 0);      /* Exits the dialog box     */
return (TRUE);
break;

case IDCANCEL :
EndDialog(hDlg, 0);      /* cancelled select */
return(TRUE);
break;
}
break;
}

return (FALSE); /* Didn't process a message    */
}
/****************************************************************************

FUNCTION: ConnectSQL(HWND, unsigned, WORD, LONG)

PURPOSE:  Processes messages for "Connect" dialog box

MESSAGES:

WM_INITDIALOG - initialize dialog box
WM_COMMAND    - Input received

COMMENTS:

No initialization is needed for this particular dialog box, but TRUE
must be returned to NT.

Wait for user to click on "Ok" button, then close the dialog box.

****************************************************************************/

LONG APIENTRY ConnectSQL(hDlg, message, wParam, lParam)
HWND hDlg;
UINT message;
DWORD wParam;
LONG lParam;
{
char szSQLServer[31];
static PLOGINREC LoginRec;

*szSQLServer = '\0';
switch (message)
{
case WM_INITDIALOG:   /* message: initialize dialog box*/
SendDlgItemMessage(hDlg,       /* limit input to 30 characters  */
SQL_SERVER,EM_LIMITTEXT,30,0L);
return (TRUE);

case WM_COMMAND:      /* message: received a command*/
errhWnd = hDlg;
switch(wParam)
{
case IDOK :      /* "OK" box selected?    */
GetDlgItemText(hDlg,SQL_SERVER,
(LPSTR)szSQLServer,
MAX_SERVERNAME); /* get Server name */
if(*szSQLServer != '\0') /* was something input    */
{
DBLOCKLIB();/* lock down library    */
if (dbproc != (PDBPROCESS)NULL) /* if an active     */
/* process close it */
dbclose(dbproc);
if ((LoginRec = dblogin()) != (PLOGINREC)NULL) /* get loginrec */
{
DBSETLUSER(LoginRec,(char far *)"sa"); /* set user  */
DBSETLVERSION(LoginRec,DBVER60);

/* now open the connection to server */
if((dbproc = dbopen(LoginRec,(LPSTR)szSQLServer))
== (PDBPROCESS)NULL)
{
/* if NULL couldn't connect*/
dbfreelogin(LoginRec);
}
else /* got connect so use the pubs database */
{
dbuse(dbproc,(LPSTR)"pubs");
dbfreelogin(LoginRec);
}
}
else /* memory allocation problem */
MessageBox(hDlg, "Could not allocate Login Record","System Error", MB_ICONHAND | MB_OK);
DBUNLOCKLIB(); /* done unlock library*/
}
EndDialog(hDlg, 0);      /* Exits the dialog box     */
return (TRUE);
break;

case IDCANCEL :
EndDialog(hDlg, 0);
return(TRUE);
break;

}
break;
}

return (FALSE); /* Didn't process a message */
}

/****************************************************************************

FUNCTION: CheckForScroll(HWND, int, int, int)

PURPOSE:  Check if next output line will be out of client area

PARAMETERS:
hWnd - Handle to the window.
CurrentPosition - Current y coordinate for the line of
text just written to the client area.
Spacing - The height of the line (including the space
separating lines) of the text just written.
Length - The length of the line just written in device units.

RETURN:Returns the Y coordinate for the next line of text.

COMMENTS:

Will determine if the next line of text will be out of the client
area.  If so will scroll the window for the next line.  Also validates
the current line of text so that a WM_PAINT will not clear it.

****************************************************************************/
int CheckForScroll(hWnd,CurrentPosition,Spacing, Length)
HWND hWnd;
int CurrentPosition;
int Spacing;
int Length;
{
RECT rect;/* RECT structure for validation */
rect.top = CurrentPosition; /* top of last line of text */
rect.bottom = CurrentPosition+Spacing+1; /* bottom of last line */
rect.left = 1;/* left most column of line */
rect.right = Length+1;/* right most column of line */
ValidateRect(hWnd,(LPRECT)&rect);   /* validate line so that it is   */

/* not blanked on next paint */

GetClientRect(hWnd,(LPRECT)&rect);/* get rect for current client   */
if(CurrentPosition + (Spacing*2) > rect.bottom) /* will line fit */
{
/* if not scroll window and  */
/* update client window */
ScrollWindow(hWnd,0,-(Spacing+1),NULL,NULL);
UpdateWindow(hWnd);
return(CurrentPosition);
}

return(CurrentPosition+Spacing);
}

/****************************************************************************

FUNCTION: SQLTestProcessResults(HWND)

PURPOSE:  If a valid dbprocess is present process all results from pending
select statement, output each field to client area.  Whenever
a new line is written to client area it is checked to see if
the client area needs to be scrolled.

PARAMETERS: hWnd - Handle to the window.

RETURN:Returns the Y coordinate for the next line of text.

COMMENTS:
This function will bind the fields in the select statement
to local variables, format an output string then
write that string to the client area via TextOut.
It is called by the main message processing loop
SQLTestWndProc via the message WM_DBRESULTS.

****************************************************************************/
BOOL SqlTestProcessResults(hWnd)
HWND hWnd;
{
HDC hDC;/* display context */
TEXTMETRIC tm;/* text metric structure */
char szId[12];/* Author ID for binding */
char szLastName[41];/* Author last name for binding */
char szFirstName[21];/* Author first name for binding */
char szPhone[13];/* Author phone for binding */
char szAddress[41];/* Author address for binding */
char szCity[21];/* Author city for binding */
char szState[3];/* Author state for binding */
char szZip[6];/* Author zipcode for binding */
char szOutputString[81];/* general output string */
RETCODE result_code;/* results code from dbresults */
int Y;/* Y coordinate for text output  */
int Spacing;/* Spacing between lines */
errhWnd = hWnd;

hDC = GetDC(hWnd);/* get display context */
GetTextMetrics(hDC, (LPTEXTMETRIC)&tm); /* get font info */
Spacing = tm.tmExternalLeading + tm.tmHeight; /* set up spacing */
Y = 1;/* start at line 1 */

if(dbproc == (PDBPROCESS)NULL)/* if process null, no results */
{
ReleaseDC(hWnd,hDC);/* free resources and return */
return(TRUE);
}

SendMessage(hWnd,WM_ERASEBKGND,(DWORD)hDC,0L); /* always erase background */
UpdateWindow(hWnd);/* force painting of window */
DBLOCKLIB();/* lock down library */

/* get all results from the query*/
while(((result_code = dbresults(dbproc)) != NO_MORE_RESULTS) && result_code != FAIL)
{
if(result_code == SUCCEED)/* if results ready */
{
/* Bind all data of interest */
dbbind(dbproc,1,NTBSTRINGBIND, 12L, (LPSTR)szId);
dbbind(dbproc,2,NTBSTRINGBIND, 41L, (LPSTR)szLastName);
dbbind(dbproc,3,NTBSTRINGBIND, 21L, (LPSTR)szFirstName);
dbbind(dbproc,4,NTBSTRINGBIND, 13L, (LPSTR)szPhone);
dbbind(dbproc,5,NTBSTRINGBIND, 41L, (LPSTR)szAddress);
dbbind(dbproc,6,NTBSTRINGBIND, 21L, (LPSTR)szCity);
dbbind(dbproc,7,NTBSTRINGBIND, 3L, (LPSTR)szState);
dbbind(dbproc,8,NTBSTRINGBIND, 6L, (LPSTR)szZip);
while(dbnextrow(dbproc) != NO_MORE_ROWS) /* get all rows */
{
/* here we format each field and write it to client */
/* area checking to see if the client area needs to */
/* be scrolled after each line is written    */
sprintf(szOutputString,"Author ID: %s",szId);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"Last Name: %s",szLastName);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"Address:   %s",szAddress);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"City:      %s",szCity);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"State:     %s",szState);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"ZipCode:   %s",szZip);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

sprintf(szOutputString,"Telephone: %s",szPhone);
TextOut(hDC,1,Y,szOutputString,strlen(szOutputString));
Y = CheckForScroll(hWnd,Y,Spacing,strlen(szOutputString) * tm.tmMaxCharWidth);

Y = CheckForScroll(hWnd,Y,Spacing,0); /* add extra line     */
/* after each results */
}
}
}

DBUNLOCKLIB();/* unlock library   */
ReleaseDC(hWnd,hDC);/* free resource   */
return(TRUE);
}

/****************************************************************************

FUNCTION: dbwinMessageHandler(PDBPROCESS, DBINT, DBSMALLINT, DBSMALLINT,
LPSTR)

PURPOSE:  When the Data Server returns a message to dblib this function
will be called to process that message.  This function is
installed into dblib via MakeProcInstance.  It must be declared
as a FAR cdecl function, not as a FAR PASCAL function, unlike
other call back routines, as dblib conducts all of it's calls
in the cdecl fashion.  You must return 0 to dblib.

RETURN: Return 0

COMMENTS:

****************************************************************************/

int API dbwinMessageHandler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS        *dbproc;
DBINT            msgno;
INT              msgstate;
INT              severity;
CHAR             *msgtext;
{
MessageBox(errhWnd,msgtext,(LPSTR)"SQL DataServer Message",MB_OK);
return(0);
}

/****************************************************************************

FUNCTION: dbwinErrorHandler(PDBPROCESS, int, int, int, LPSTR, LPSTR)

PURPOSE: When dblib returns an error message to the application this
function will be called to process that error.  This function is
installed into dblib via MakeProcInstance.  It must be declared
as a FAR cdecl function, not as a FAR PASCAL function, unlike
other call back routines, as dblib conducts all of it's calls
in the cdecl fashion.  You must return either INT_CANCEL,
INT_CONTINUE, or INT_EXIT to dblib.

RETURN: Return continuation code.

COMMENTS:

****************************************************************************/

int API dbwinErrorHandler(dbproc, severity, errno, oserr, dberrstr, oserrstr)
PDBPROCESS dbproc;
int severity;
int errno;
int oserr;
LPSTR dberrstr;
LPSTR oserrstr;
{
MessageBox(errhWnd,dberrstr,(LPSTR)"DB-LIBRARY error",MB_ICONHAND | MB_OK);

if (oserr != DBNOERR)/* os error*/
MessageBox(errhWnd,oserrstr,(LPSTR)"Operating-System error",MB_ICONHAND | MB_OK);

return(INT_CANCEL);/* cancel command */
}


SQLTESTN.H

/***************************************************************************
FILE: SQLTESTW.H
      Copyright (C) 1995, Microsoft Corp.

PURPOSE: Defines values internal to the dblib test windows app.

COMMENTS: This file contains all the constant declarations for
the sqltestc windows application.

***************************************************************************/

#define ID_ABOUTSQL 100/* ID for ABOUTSQL box     */
#define AUTHORNAME201/* ID for authorname edit  */
/* control   */
#define SQL_SERVER202/* ID for server edit      */
/* control   */
#define MAX_ANAME30/* max length for author   */
#define MAX_SERVERNAME31/* max length for server   */
#defineIDM_CONNECT400/* menu id for connect   */
#defineIDM_SELECT401/* menu id for select   */
#define WM_DBRESULTSWM_USER/* results proc message   */

/* declare all functions for application here   */

int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL SqlTestInit(HANDLE);
BOOL SqlTestProcessResults(HWND);
LONG APIENTRY SqlTestWndProc(HWND, UINT, DWORD, LONG);
LONG APIENTRY AboutSQL(HWND, UINT, DWORD, LONG);
LONG APIENTRY SelectSQL(HWND, UINT, DWORD, LONG);
LONG APIENTRY ConnectSQL(HWND, UINT, DWORD, LONG);
int API dbwinErrorHandler(DBPROCESS *, int, int, int, CHAR *, CHAR *);
int API dbwinMessageHandler(DBPROCESS *, DBINT, INT, INT, CHAR *);


GETOPT.C

///////////////////////////////////////////////////////////////////////////////
//
//  FILE: getopt.c
//
//      GetOption function
//
//  FUNCTIONS:
//
//      GetOption() - Get next command line option and parameter
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include "getopt.h"

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: GetOption()
//
//      Get next command line option and parameter
//
//  PARAMETERS:
//
//      argc - count of command line arguments
//      argv - array of command line argument strings
//      pszValidOpts - string of valid, case-sensitive option characters,
//                     a colon ':' following a given character means that
//                     option can take a parameter
//      ppszParam - pointer to a pointer to a string for output
//
//  RETURNS:
//
//      If valid option is found, the character value of that option
//          is returned, and *ppszParam points to the parameter if given,
//          or is NULL if no param
//      If standalone parameter (with no option) is found, 1 is returned,
//          and *ppszParam points to the standalone parameter
//      If option is found, but it is not in the list of valid options,
//          -1 is returned, and *ppszParam points to the invalid argument
//      When end of argument list is reached, 0 is returned, and
//          *ppszParam is NULL
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

int GetOption (
    int argc,
    char** argv,
    char* pszValidOpts,
    char** ppszParam)
{
    static int iArg = 1;
    char chOpt;
    char* psz = NULL;
    char* pszParam = NULL;

    if (iArg < argc)
    {
        psz = &(argv[iArg][0]);
        if (*psz == '-' || *psz == '/')
        {
            // we have an option specifier
            chOpt = argv[iArg][1];
            if (isalnum(chOpt) || ispunct(chOpt))
            {
                // we have an option character
                psz = strchr(pszValidOpts, chOpt);
                if (psz != NULL)
                {
                    // option is valid, we want to return chOpt
                    if (psz[1] == ':')
                    {
                        // option can have a parameter
                        psz = &(argv[iArg][2]);
                        if (*psz == '\0')
                        {
                            // must look at next argv for param
                            if (iArg+1 < argc)
                            {
                                psz = &(argv[iArg+1][0]);
                                if (*psz == '-' || *psz == '/')
                                {
                                    // next argv is a new option, so param
                                    // not given for current option
                                }
                                else
                                {
                                    // next argv is the param
                                    iArg++;
                                    pszParam = psz;
                                }
                            }
                            else
                            {
                                // reached end of args looking for param
                            }

                        }
                        else
                        {
                            // param is attached to option
                            pszParam = psz;
                        }
                    }
                    else
                    {
                        // option is alone, has no parameter
                    }
                }
                else
                {
                    // option specified is not in list of valid options
                    chOpt = -1;
                    pszParam = &(argv[iArg][0]);
                }
            }
            else
            {
                // though option specifier was given, option character
                // is not alpha or was was not specified
                chOpt = -1;
                pszParam = &(argv[iArg][0]);
            }
        }
        else
        {
            // standalone arg given with no option specifier
            chOpt = 1;
            pszParam = &(argv[iArg][0]);
        }
    }
    else
    {
        // end of argument list
        chOpt = 0;
    }

    iArg++;
    *ppszParam = pszParam;
    return (chOpt);
}


GETOPT.H

///////////////////////////////////////////////////////////////////////////////
//
//  FILE: getopt.h
//
//      Header for the GetOption function
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

// function prototypes
int GetOption (int argc, char** argv, char* pszValidOpts, char** ppszParam);


TEXTCOPY.CPP

///////////////////////////////////////////////////////////////////////////////
//
//  FILE: textcopy.cpp
//
//      Text and image copy application
//
//  FUNCTIONS:
//
//      main() - Main application
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

#include <afx.h>                // MFC
#include <iostream.h>           // iostream
#include <stdlib.h>             // C run-time

#if defined (_DEBUG)
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define DBNTWIN32               // Win32 DB-Library for Windows NT
#include <sqlfront.h>           // DB-Library
#include <sqldb.h>              // DB-Library

extern "C"
{
    #include "getopt.h"         // GetOption
}

#include "textcopy.h"           // specific to this program

// GLOBAL VARIABLES
BOOL bDebug = FALSE;            // debug info
BYTE* aBuf;

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: main()
//
//      Main application
//
//  PARAMETERS:
//
//      argc - count of command line arguments
//      argv - array of command line argument strings
//      envp - array of environment strings
//
//  RETURNS: 0
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

int main (
    int argc,
    char** argv,
    char** envp)
{
    char chOpt;                 // gotten option character
    char* pszParam;             // gotten parameter
    int nChunkSize = 4096;          // chunk size
    CString strChunkSize = "4096";  // chunk size
    CString strColumnType;

    CString strServer,          // SQL Server
        strLogin,               // login
        strPassword,            // password
        strDatabase,            // database
        strTable,               // table
        strColumn,              // column
        strWhere,               // where clause
        strFile;                // file
    BOOL bServer = FALSE,       // flags for tracking options
        bLogin = FALSE,
        bPassword = FALSE,
        bDatabase = FALSE,
        bTable = FALSE,
        bColumn = FALSE,
        bWhere = FALSE,
        bFile = FALSE,
        bIn = FALSE,
        bOut = FALSE;

    // banner
    cout << "TEXTCOPY Version 1.0" << endl;

    // initialize DB-Library, get DB-Library version number
    CString strDblibVersion;
    strDblibVersion = dbinit();
    if (strDblibVersion.IsEmpty())
    {
        cout << err << "Could not initialize DB-Library" << endl;
        return (1);
    }
    cout << strDblibVersion << endl;

    // get command-line options and parameters
    while (TRUE)
    {
        // all the following command-line options are valid, they
        // are neither case sensitive nor white-space sensitive, and
        // both '/' and '-' are valid switch characters
        //
        //  OPTION  PARAMETER           DESCRIPTION
        //
        //  /S      [sqlserver]         SQL Server
        //  /U      [login]             Login
        //  /P      [password]          Password
        //  /D      [database]          Database
        //  /T      table               Table
        //  /C      column              Column
        //  /W      "where clause"      Where clause in double quotes
        //  /F      file                File
        //  /I                          Into SQL Server from file
        //  /O                          Out of SQL Server into file
        //  /K      chunksize           Chunk size in bytes
        //  /Z                          Debug information
        //  /?                          Usage information
        //
        // for example, all of the following are valid ways to specify
        // a connection to a SQL Server named 'gizmo'
        //
        //  /Sgizmo     /sgizmo     -Sgizmo     -sgizmo
        //  /S gizmo    /s gizmo    -S gizmo    -s gizmo

        chOpt = GetOption(argc, argv, "s:S:u:U:p:P:d:D:t:T:c:C:w:W:f:F:k:K:iIoOzZ?", &pszParam);
        if (chOpt > 1)
        {
            // chOpt is valid argument
            switch (chOpt)
            {
            case 's':   // SQL Server
            case 'S':
                bServer = TRUE;
                strServer = pszParam;
                break;
            case 'u':   // login
            case 'U':
                bLogin = TRUE;
                strLogin = pszParam;
                break;
            case 'p':   // password
            case 'P':
                bPassword = TRUE;
                strPassword = pszParam;
                break;
            case 'd':   // database
            case 'D':
                bDatabase = TRUE;
                strDatabase = pszParam;
                break;
            case 't':   // table
            case 'T':
                bTable = TRUE;
                strTable = pszParam;
                break;
            case 'c':   // column
            case 'C':
                bColumn = TRUE;
                strColumn = pszParam;
                break;
            case 'w':   // where clause
            case 'W':
                bWhere = TRUE;
                strWhere = pszParam;
                break;
            case 'f':   // file
            case 'F':
                bFile = TRUE;
                strFile = pszParam;
                break;
            case 'i':   // direction: into SQL Server from file
            case 'I':
                bIn = TRUE;
                break;
            case 'o':   // direction: out of SQL Server into file
            case 'O':
                bOut = TRUE;
                break;
            case 'k':   // chunk size in bytes
            case 'K':
                if (pszParam != NULL)
                {
                    nChunkSize = atoi (pszParam);
                    _itoa (nChunkSize, strChunkSize.GetBuffer(20), 10);
                    strChunkSize.ReleaseBuffer();
                    if (strChunkSize != pszParam)
                    {
                        cout << err << "Converted chunk size '" << pszParam << "' to " << nChunkSize << endl;
                        return (0);
                    }
                }
                break;
            case 'z':   // debug
            case 'Z':
                bDebug = TRUE;
                break;
            case '?':   // usage info
                DisplayUsage();
                return(0);
                break;
            }
        }
        if (chOpt == 0)
        {
            // end of argument list
            break;
        }
        if ((chOpt == 1) || (chOpt == -1))
        {
            // standalone param or error
            cout << err << "Argument '" << pszParam << "' not recognized" << endl;
            break;
        }
    }

    if ((chOpt == 1) || (chOpt == -1))
    {
        // exit on error
        return (1);
    }

    // prompt the user for any unspecified options
    if (!bServer)
    {
        cout << "Type the SQL Server to connect to: ";
        cinstr (strServer);
    }

    if (!bLogin)
    {
        cout << "Type your login: ";
        cinstr (strLogin);
    }

    if (!bPassword)
    {
        cout << "Type your password: ";
        cinstr (strPassword);
    }

    if (!bDatabase)
    {
        cout << "Type the database: ";
        cinstr (strDatabase);
    }

    if (!bTable)
    {
        cout << "Type the table: ";
        cinstr (strTable);
    }

    if (!bColumn)
    {
        cout << "Type the text or image column: ";
        cinstr (strColumn);
    }

    if (!bWhere)
    {
        cout << "Type the where clause: ";
        cinstr (strWhere);
    }

    if (!bFile)
    {
        cout << "Type the file: ";
        cinstr (strFile);
    }

    if (!bIn && !bOut)
    {
        while (TRUE)
        {
            CString strDirection;
            cout << "Type the direction ('I' for in, 'O' for out): ";
            cinstr (strDirection);
            if (strDirection.CompareNoCase("i") == 0)
            {
                bIn = TRUE;
                break;
            }
            else
            {
                if (strDirection.CompareNoCase("o") == 0)
                {
                    bOut = TRUE;
                    break;
                }
                else
                {
                    cout << err << "The value '" << strDirection << "' is invalid." << endl;
                }
            }
        }
    }

    // parameter validation
    if (strTable.IsEmpty())
    {
        cout << err << "You did not specify a table." << endl;
        return (1);
    }

    if (strColumn.IsEmpty())
    {
        cout << err << "You did not specify a column." << endl;
        return (1);
    }

    if (strWhere.IsEmpty())
    {
        cout << err << "You did not specify a where clause." << endl;
        return (1);
    }

    /*
    CString strLowerWhere = strWhere;
    strLowerWhere.MakeLower();
    if (strLowerWhere.Find ("where ") == -1)
    {
        cout << err << "Your where clause '" << strWhere << "' did not contain the keyword 'where'." << endl;
        return (1);
    }
    */

    if (strFile.IsEmpty())
    {
        cout << err << "You did not specify a file." << endl;
        return (1);
    }

    if (bIn && bOut)
    {
        cout << err << "You cannot specify both 'in' and 'out' directions." << endl;
        return (1);
    }

    if (nChunkSize < 1024)
    {
        cout << err << "Your specified chunk size of " << nChunkSize << " bytes is too small." << endl;
        return (1);
    }

    if (bDebug)
    {
        cout << dbg << "Final parameters:" << endl;
        cout << dbg << "  Server: " << strServer << endl;
        cout << dbg << "  Login: " << strLogin << endl;
        cout << dbg << "  Password: " << strPassword << endl;
        cout << dbg << "  Database: " << strDatabase << endl;
        cout << dbg << "  Table: " << strTable << endl;
        cout << dbg << "  Column: " << strColumn << endl;
        cout << dbg << "  Where clause: " << strWhere << endl;
        cout << dbg << "  File: " << strFile << endl;
        cout << dbg << "  Direction: ";
        if (bIn)
        {
            cout << "Into SQL Server from file." << endl;
        }
        if (bOut)
        {
            cout << "Out of SQL Server into file." << endl;
        }
        cout << dbg << "  Chunk size: " << nChunkSize << " bytes" << endl;
    }

    // install error and message handlers
    dberrhandle (ErrorHandler);
    dbmsghandle (MessageHandler);

    // set DB-Library options
    dbsettime(30);
    dbsetlogintime(10);

    // get login record
    PLOGINREC pLoginRec;
    pLoginRec = dblogin();
    if (pLoginRec == NULL)
    {
        cout << err << "Could not allocate a login record" << endl;
        return (1);
    }

    // fill the login record
    DBSETLUSER (pLoginRec, strLogin);       // set the login
    DBSETLPWD (pLoginRec, strPassword);     // set the password
    DBSETLAPP (pLoginRec, "textcopy");      // set the app name
    DBSETLHOST (pLoginRec, "textcopy");     // set the host name

    // attempt to connect to SQL Server
    PDBPROCESS pDbproc;
    pDbproc = dbopen (pLoginRec, strServer);
    dbfreelogin (pLoginRec);
    if (pDbproc == NULL)
    {
        cout << err << "Could not connect to SQL Server '" << strServer << "'" << endl;
        return (1);
    }

    // re-used DB-Library return code
    RETCODE r;

    // set textlimit and textsize options for this connection
    if (bOut)
    {
        dbsetopt (pDbproc, DBTEXTLIMIT, strChunkSize);
        dbsetopt (pDbproc, DBTEXTSIZE, "2147483647");
    }

    if (bIn)
    {
        dbsetopt (pDbproc, DBTEXTSIZE, "1024");
    }

    // must send and execute batch to set options
    r = dbsqlexec (pDbproc);
    if (r == FAIL)
    {
        cout << err << "Query execution failed." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // get empty result set(s) from setting options
    while (TRUE)
    {
        r = dbresults (pDbproc);
        if (r == FAIL)
        {
            cout << err << "Query results failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }
        if (r == NO_MORE_RESULTS)
            break; // while loop
    }

    // use specified database
    if (!strDatabase.IsEmpty())
    {
        r = dbuse (pDbproc, strDatabase);
        if (r == FAIL)
        {
            cout << err << "Could not use database '" << strDatabase << "'" << endl;
            Cleanup(pDbproc);
            return (1);
        }
    }

    // build query
    CString strQuery;

    strQuery = "select " + strColumn + " from " + strTable + " " + strWhere;
    D(cout << "Query: " << strQuery << endl);

    r = dbcmd (pDbproc, strQuery);

    // send and execute query
    r = dbsqlexec (pDbproc);
    if (r == FAIL)
    {
        cout << err << "Query execution failed." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // get first result set
    r = dbresults (pDbproc);
    if (r != SUCCEED)
    {
        cout << err << "Query results failed." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // verify that only a single column was selected
    if (dbnumcols (pDbproc) != 1)
    {
        cout << err << "More than one column specified." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // verify that the single column selected is either text or image
    int nColumnType;
    nColumnType = dbcoltype (pDbproc, 1);
    if ((nColumnType != SQLTEXT) && (nColumnType != SQLIMAGE))
    {
        cout << err << "Specified column is not a text or image column." << endl;
        Cleanup (pDbproc);
        return (1);
    }
    else
    {
        if (nColumnType == SQLTEXT)
        {
            strColumnType = "text";
        }
        if (nColumnType == SQLIMAGE)
        {
            strColumnType = "image";
        }
    }

    // buffer for data transfer between DB-Library and file
    aBuf = new BYTE[nChunkSize];
    if (aBuf == 0)
    {
        cout << err << "Unable to allocate transfer buffer of '" << nChunkSize << "' bytes." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // if the data is coming out of SQL Server and into a file, use dbreadtext
    // (instead of dbnextrow) to read the text or image data from the row
    // in chunks
    if (bOut)
    {
    try
    {
        // set up file
        CFile file (strFile, CFile::modeCreate | CFile::modeWrite);
        D(cout << "File '" << strFile << "' opened for write" << endl);

        DBINT lWriteBytes;
        while (TRUE)
        {
            // read chunk of text or image data from SQL Server into aBuf
            lWriteBytes = dbreadtext (pDbproc, aBuf, nChunkSize);
            switch (lWriteBytes)
            {
            case 0:
                // end of text or image row
                //D(cout << "End of row" << endl);
                break;
            case -1:
                // dbreadtext failed
                cout << err << "Text or image data retrieval failed." << endl;
                Cleanup (pDbproc);
                return (1);
                break;
            case NO_MORE_ROWS:
                //D(cout << "No more rows" << endl);
                break;
            default:
                // dbreadtext has placed lBytes of text or image data
                // into aBuf, now write that chunk to the file
                file.Write (aBuf, lWriteBytes);
                D(cout << "Wrote " << lWriteBytes << " bytes to file" << endl);
                break;
            }
            if ((lWriteBytes == -1) || (lWriteBytes == NO_MORE_ROWS))
                break; // while loop
        }
        file.Close();
        D(cout << "File closed" << endl);
    }
    catch (CFileException* e)
    {
        //if (e->m_cause == CFileException::fileNotFound)
        cout << err << "Problem with file '" << strFile << "'." << endl;
        e->Delete();
        Cleanup (pDbproc);
        return (1);
    }

    }

    DBBINARY aTextPointer[DBTXPLEN], aTextTimestamp[DBTXTSLEN];
    DBBINARY *pTextPointer, *pTextTimestamp;

    // if the data is going into SQL Server from a file, copy the text
    // pointer and text timestamp values into private buffers
    if (bIn)
    {
        // get the single row
        STATUS s;
        s = dbnextrow (pDbproc);
        if (s != REG_ROW)
        {
            cout << err << "Row retrieval failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }

        // get the pointers to the text pointer and text timestamp values
        pTextPointer = dbtxptr (pDbproc, 1);
        pTextTimestamp = dbtxtimestamp (pDbproc, 1);
        if ((pTextPointer == NULL) || (pTextTimestamp == NULL))
        {
            cout << err << "Text or image pointer and timestamp retrieval failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }

        // copy the actual text pointer and text timestamp values into
        // private buffers
        memcpy (aTextPointer, pTextPointer, DBTXPLEN);
        memcpy (aTextTimestamp, pTextTimestamp, DBTXTSLEN);

        // we should only have received one row, so call dbnextrow
        // to get NO_MORE_ROWS
        s = dbnextrow (pDbproc);
        switch (s)
        {
        case NO_MORE_ROWS:
            // this is what we expect
            break;
        case REG_ROW:
            cout << err << "Where clause returned more than one row." << endl;
            Cleanup (pDbproc);
            return (1);
            break;
        default:
            cout << err << "Row retrieval failed." << endl;
            Cleanup (pDbproc);
            return (1);
            break;
        }
    }

    // get NO_MORE_RESULTS to clear out all results
    r = dbresults (pDbproc);
    if (r != NO_MORE_RESULTS)
    {
        cout << err << "Query results failed." << endl;
        Cleanup (pDbproc);
        return (1);
    }

    // if the data is going into SQL Server from a file, use dbwritetext
    // (with the private copies of the text or image pointer and timestamp
    // values) and dbmoretext to write the text or image data to
    // SQL Server in chunks
    if (bIn)
    {
    try
    {
        // set up file
        CFile file (strFile, CFile::modeRead);
        D(cout << "File '" << strFile << "' opened for read" << endl);
        D(cout << "File is " << file.GetLength() << " bytes long" << endl);

        // call dbwritetext will NULL text value to indicate that the text
        // values will be sent in chunks using dbmoretext
        r = dbwritetext (pDbproc, strTable + "." + strColumn,
            aTextPointer, DBTXPLEN, aTextTimestamp,
            TRUE, file.GetLength(), NULL);
        if (r == FAIL)
        {
            cout << err << "Text or image write failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }

        // call dbsqlok and dbresults to prepare for calling dbmoretext
        r = dbsqlok (pDbproc);
        if (r == FAIL)
        {
            cout << err << "Text or image write failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }
        r = dbresults (pDbproc);
        if (r == FAIL)
        {
            cout << err << "Query results failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }

        // read each chunk from the file and write it to SQL Server
        UINT nReadBytes = nChunkSize;
        while (nReadBytes == UINT(nChunkSize))
        {
            // read chunk from file into aBuf
            nReadBytes = file.Read (aBuf, nChunkSize);
            D(cout << "Read "<< nReadBytes << " bytes from file" << endl);

            // write chunk of text or image data from aBuf to SQL Server
            r = dbmoretext (pDbproc, nReadBytes, aBuf);
            if (r == FAIL)
            {
                cout << err << "Text or image write failed." << endl;
                Cleanup (pDbproc);
                return (1);
            }
        }

        file.Close();
        D(cout << "File closed" << endl);

        // call dbsqlok and dbresults to signal end of calls to
        // dbmoretext and completion of text or image write
        r = dbsqlok (pDbproc);
        if (r == FAIL)
        {
            cout << err << "Text or image write failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }
        r = dbresults (pDbproc);
        if (r == FAIL)
        {
            cout << err << "Query results failed." << endl;
            Cleanup (pDbproc);
            return (1);
        }
    }
    catch (CFileException* e)
    {
        //if (e->m_cause == CFileException::fileNotFound)
        cout << err << "Problem with file '" << strFile << "'." << endl;
        e->Delete();
        Cleanup (pDbproc);
        return (1);
    }

    }

    // display success message
    if (bIn)
    {
        cout << "Data copied into SQL Server " << strColumnType << " column from file '" << strFile << "'." << endl;
    }
    if (bOut)
    {
        cout << "Data copied out of SQL Server " << strColumnType << " column into file '" << strFile << "'." << endl;
    }

    // prepare DB-Library for exit
    Cleanup (pDbproc);

    return (0);
}

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: Cleanup()
//
//      Prepare DB-Library for exit
//
//  PARAMETERS:
//
//      pDbproc - pointer to DBPROCESS connection
//
//  RETURNS: 0
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

int Cleanup (PDBPROCESS pDbproc)
{
    delete [] aBuf;
    dbcancel (pDbproc);
    dbclose (pDbproc);
    return (0);
}

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: ErrorHandler()
//
//      Called when a DB-Library error occurs
//
//  PARAMETERS:
//
//      pDbproc - pointer to DBPROCESS connection
//      nDblibSeverity - DB-Library error severity
//      nDblibErrorNo - DB-Library error number
//      nOSErrorNo - operating system error number
//      lpstrDblibError - DB-Library error string
//      lpstrOSError - operating system error string
//
//  RETURNS: One of three DB-Library return continuation codes:
//
//      INT_CANCEL
//      INT_CONTINUE
//      INT_EXIT
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

extern "C" int ErrorHandler (
    PDBPROCESS pDbproc,
    INT nSeverity,
    INT nDBLibError,
    INT nOSError,
    LPCSTR pszDBLibError,
    LPCSTR pszOSError)
{
    // display DB-Library error information
    cout << "DB-Library Error " << nDBLibError << ": " << pszDBLibError << endl;

    if ((pDbproc == NULL) || (DBDEAD(pDbproc)))
    {
        return(INT_EXIT);
    }
    else
    {
        if (nOSError != DBNOERR)
        {
            // this DB-Library error was caused by an operating system
            // error, so display OS error information
            cout << "Operating System Error " << nOSError << ": " << pszOSError << endl;
        }

        return(INT_CANCEL);
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: MessageHandler()
//
//      Called when a SQL Server message is received
//
//  PARAMETERS:
//
//      pDbproc - pointer to DBPROCESS connection
//      lMessage - SQL Server message number
//      nState - SQL Server message state
//      nSeverity - SQL Server message severity
//      pszMessage - SQL Server message string
//      pszServer - the SQL Server that sent this message
//      pszProcedure - the stored procedure (if any) where the message
//                     occurred
//      usLine - the line number (if any) of the batch or stored procedure
//             where the message occurred
//
//  RETURNS: 0
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

extern "C" int MessageHandler (
    PDBPROCESS pDbproc,
    DBINT lMessage,
    INT nState,
    INT nSeverity,
    LPCSTR pszMessage,
    LPCSTR pszServer,
    LPCSTR pszProcedure,
    DBUSMALLINT usLine)
{
    // only display certain SQL Serve messages when debug info enabled
    switch (lMessage)
    {
    case 5701:
        if (!bDebug)
        {
            return (0);
        }
        break;
    }

    // display SQL Server message information
    cout << "SQL Server";

    if (pszServer != NULL)
    {
        if (*pszServer != '\0')
        {
            cout << " '" << pszServer << "'";
        }
    }

    cout << " Message " << lMessage << ": " << pszMessage;

    if (usLine != 0)
    {
        cout << " (Concerning line " << usLine;
        if (pszProcedure != NULL)
        {
            cout << " of stored procedure '" << pszProcedure << "'";
        }
        cout << ")";
    }

    cout << endl;

    return(0);
}

///////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: DisplayUsage()
//
//      Display usage information
//
//  PARAMETERS:
//
//  RETURNS: 0
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

int DisplayUsage (void)
{
    cout << endl;
    cout << "Copies a single text or image value into or out of SQL Server. The value" << endl;
    cout << "is a specified text or image 'column' of a single row (specified by the" << endl;
    cout << "\"where clause\") of the specified 'table'." << endl;
    cout << endl;
    cout << "If the direction is IN (/I) then the data from the specified 'file' is" << endl;
    cout << "copied into SQL Server, replacing the existing text or image value. If the" << endl;
    cout << "direction is OUT (/O) then the text or image value is copied from" << endl;
    cout << "SQL Server into the specified 'file', replacing any existing file." << endl;
    cout << endl;
    cout << "TEXTCOPY [/S [sqlserver]] [/U [login]] [/P [password]]" << endl;
    cout << "  [/D [database]] [/T table] [/C column] [/W\"where clause\"]" << endl;
    cout << "  [/F file] [{/I | /O}] [/K chunksize] [/Z] [/?]" << endl;
    cout << endl;
    cout << "  /S sqlserver       The SQL Server to connect to. If 'sqlserver' is not" << endl;
    cout << "                     specified, the local SQL Server is used." << endl;
    cout << "  /U login           The login to connect with. If 'login' is not specified," << endl;
    cout << "                     a trusted connection will be used." << endl;
    cout << "  /P password        The password for 'login'. If 'password' is not" << endl;
    cout << "                     specified, a NULL password will be used." << endl;
    cout << "  /D database        The database that contains the table with the text or" << endl;
    cout << "                     image data. If 'database' is not specified, the default" << endl;
    cout << "                     database of 'login' is used." << endl;
    cout << "  /T table           The table that contains the text or image value." << endl;
    cout << "  /C column          The text or image column of 'table'." << endl;
    cout << "  /W \"where clause\"  A complete where clause (including the WHERE keyword)" << endl;
    cout << "                     that specifies a single row of 'table'." << endl;
    cout << "  /F file            The file name." << endl;
    cout << "  /I                 Copy text or image value into SQL Server from 'file'." << endl;
    cout << "  /O                 Copy text or image value out of SQL Server into 'file'." << endl;
    cout << "  /K chunksize       Size of the data transfer buffer in bytes. Minimum" << endl;
    cout << "                     value is 1024 bytes, default value is 4096 bytes." << endl;
    cout << "  /Z                 Display debug information while running." << endl;
    cout << "  /?                 Display this usage information and exit." << endl;
    cout << endl;
    cout << "You will be prompted for any required options you did not specify." << endl;

    return (0);
}


TEXTCOPY.H

///////////////////////////////////////////////////////////////////////////////
//
//  FILE: textcopy.h
//
//      Header for text and image copy application
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////////

// function prototypes
int Cleanup (PDBPROCESS);
extern "C" int ErrorHandler (PDBPROCESS, INT, INT, INT, LPCSTR, LPCSTR);
extern "C" int MessageHandler (PDBPROCESS, DBINT, INT, INT, LPCSTR, LPCSTR, LPCSTR, DBUSMALLINT);
int DisplayUsage (void);

#define D(a) if (bDebug) { cout << dbg; (a); }

const char* const err = "ERROR: ";
const char* const dbg = "  debug: ";

inline void cinstr (CString& s, int n) { cin.getline (s.GetBuffer (n), n); s.ReleaseBuffer(); };
inline void cinstr (CString& s) { cin.getline (s.GetBuffer (100), 100); s.ReleaseBuffer(); };


TWOPHASE.C

/*Demo of Two-Phase Commit Service
**
**This example uses the two-phase commit service
**to perform a simultaneous update on two servers.
**In this example, one of the servers participating
**in the distributed transaction also functions as
**the commit service.
**
**In this particular example, the same update is
**performed on both servers. You can, however, use
**the commit server to perform completely different
**updates on each server.
**
*/

#if defined(DBNTWIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <sqlfront.h>
#include <sqldb.h>

charcmdbuf[256];
charxact_string[128];

/* Forward declarations of the error handler and message handler.
*/
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);
void abortall (DBPROCESS * dbproc_server1, DBPROCESS * dbproc_server2, DBPROCESS * dbproc_commit, DBINT commid);

void main(argv,argc)
int argc;
char *argv[];
{

DBPROCESS*dbproc_server1;
DBPROCESS*dbproc_server2;
DBPROCESS*dbproc_commit;
LOGINREC*login;
DBINTcommid;

RETCODEret_server1;
RETCODEret_server2;

// set error/msg handlers for this program
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

/* Open connections with the servers and the commit service. */
printf("Demo of Two Phase Commit\n");
login = dblogin();
DBSETLUSER(login, "user");
DBSETLPWD(login, "my_passwd");
DBSETLAPP(login, "example");
DBSETLVERSION(login, DBVER60);

dbproc_server1 = dbopen (login, "my_server1");
dbproc_server2 = dbopen (login, "my_server2");
dbproc_commit = open_commit (login, "my_commitsrv");

if (dbproc_server1 == NULL ||
dbproc_server2 == NULL ||
dbproc_commit== NULL)
{
printf (" Connections failed!\n");
exit (ERREXIT);
}

/* Use the "pubs" database. */
dbuse(dbproc_server1, "pubs");
dbuse(dbproc_server2, "pubs");

/* Start the distributed transaction on the commit service. */
commid = start_xact(dbproc_commit, "demo", "test", 2);

/* Build the transaction name. */
build_xact_string ("test", "my_commitsrv", commid, xact_string);

/* Build the first command buffer. */
sprintf(cmdbuf, "BEGIN TRANSACTION %s", xact_string);

/* Begin the transactions on the different servers. */
dbcmd(dbproc_server1, cmdbuf);
dbsqlexec(dbproc_server1);
dbcmd(dbproc_server2, cmdbuf);
dbsqlexec(dbproc_server2);

dbcancel(dbproc_server1);
dbcancel(dbproc_server2);

/* Do various updates. */
sprintf(cmdbuf, " update titles set price = $1.50 where");
strcat(cmdbuf, " title_id = 'BU1032'");
dbcmd(dbproc_server1, cmdbuf);
ret_server1 = dbsqlexec(dbproc_server1);
dbcmd(dbproc_server2, cmdbuf);
ret_server2 =dbsqlexec(dbproc_server2);
if (ret_server1 == FAIL || ret_server2 == FAIL)
{
/* Some part of the transaction failed. */
printf(" Transaction aborted -- dbsqlexec failed\n");
abortall(dbproc_server1, dbproc_server2, dbproc_commit, commid);
}

dbcancel(dbproc_server1);
dbcancel(dbproc_server2);

/* Find out if all servers can commit the transaction. */
sprintf(cmdbuf, "PREPARE TRANSACTION");
dbcmd(dbproc_server1, cmdbuf);
dbcmd(dbproc_server2, cmdbuf);
ret_server1 = dbsqlexec(dbproc_server1);
ret_server2 = dbsqlexec(dbproc_server2);
if (ret_server1 == FAIL || ret_server2 == FAIL)
{
/* One or both of the servers failed to prepare. */
printf(" Transaction aborted -- PREPARE failed\n");
abortall(dbproc_server1, dbproc_server2, dbproc_commit, commid);
}

dbcancel(dbproc_server1);
dbcancel(dbproc_server2);

/* Commit the transaction. */
if (commit_xact(dbproc_commit, commid) == FAIL)
{
/* The commit server failed to record the commit. */
printf( " Transaction aborted -- commit_xact failed\n");
abortall(dbproc_server1, dbproc_server2, dbproc_commit, commid);
exit(ERREXIT);
}

/* The transaction has successfully committed.Inform the servers.
*/
sprintf(cmdbuf, "COMMIT TRANSACTION");
dbcmd(dbproc_server1, cmdbuf);
if (dbsqlexec(dbproc_server1) != FAIL)
remove_xact(dbproc_commit, commid, 1);
dbcmd(dbproc_server2, cmdbuf);
if (dbsqlexec(dbproc_server2) != FAIL)
remove_xact(dbproc_commit, commid, 1);

/* Close the connection to the commit server. */
close_commit(dbproc_commit);

printf( "We made it!\n");
dbexit();
exit(STDEXIT);
}

/* Function to abort the distributed transaction. */

void abortall( dbproc_server1, dbproc_server2, dbproc_commit, commid )
DBPROCESS*dbproc_server1;
DBPROCESS*dbproc_server2;
DBPROCESS*dbproc_commit;
DBINTcommid;
{
/* Some part of the transaction failed. */

/* Inform the commit server of the failure. */
abort_xact(dbproc_commit, commid);

/* Roll back the transactions on the different servers. */
sprintf(cmdbuf, "ROLLBACK TRANSACTION");
dbcmd(dbproc_server1, cmdbuf);
if (dbsqlexec(dbproc_server1) != FAIL)
remove_xact(dbproc_commit, commid, 1);
dbcmd(dbproc_server2, cmdbuf);
if (dbsqlexec(dbproc_server2) != FAIL)
remove_xact(dbproc_commit, commid, 1);

dbexit();
exit(ERREXIT);
}
/* Message and error handling functions. */
int msg_handler(dbproc,msgno,msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{
/*Msg 5701 is just a USE DATABASE message, so skip it.*/
if (msgno == 5701)
return (0);
/*Print any severity 0 message as is, without extra stuff.*/
if (severity == 0)
{
printf ("%s\n",msgtext);
return (0);
}

printf("SQL Server message %ld, severity %d:\n\t%s\n",
msgno, severity, msgtext);

if (severity >>= 16)
{
printf("Program Terminated! Fatal SQL Server error.\n");
exit(ERREXIT);
}
return (0);
}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{
if ((dbproc == NULL) || (DBDEAD(dbproc)))
return (INT_EXIT);
else
{
printf ("DB-LIBRARY error: \n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf ("Operating system error:\n\t%s\n", oserrstr);
}
return (INT_CANCEL);
}


README.TXT

****************************************************************
           MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
  MICROSOFT DISTRIBUTED TRANSACTION COORDINATOR SAMPLE PROGRAMS
****************************************************************
This file describes the Microsoft Distributed Transaction
Coordinator (MS DTC) sample programs included with Microsoft(R)
SQL Server(TM) Workstation System 6.5.

For additional information about SQL Server 6.5, MS DTC, and ODBC,
see the "What's New in SQL Server 6.5," "Guide to Microsoft
Distributed Management Coordinator," and "Programming ODBC for SQL
Server" sections of SQL Server Books Online, which contains the most
up-to-date information available.

****************************************************************
MICROSOFT DISTRIBUTED TRANSACTION COORDINATOR SAMPLE APPLICATIONS

The following 32-bit C++ sample programs are located in the
C:\MSSQL\SAMPLES\MSDTC directories by default:

DBLIB
Sample C++ program using 32-bit Visual C++ and the DB-Library
interface to communicate with both MS DTC and SQL Server. Includes
the following files:
    DBLIB.MAK
    DBLIB.CPP
    DBLIB.MDP

ODBC
Sample C++ program using 32-bit Visual C++ and the ODBC interface to
communicate with both MS DTC and SQL Server. Includes the following
files:
    ODBC.MAK
    ODBC.CPP
    ODBC.MDP

Each project on the Intel(R) platform includes Visual C++ 4.0
workspace files (.MDP). Double-click the workspace file to load
the workspace and make file into the Developer Studio environment.

Each project also contains cross-platform native Visual C++ 4.0 make
files (.MAK). For each 32-bit project the .MAK file contains Intel,
Alpha AXP(R), MIPS(R), and PowerPC(TM) build information. When the
project is loaded into Developer Studio, pick the appropriate target,
for example "Example1 - Win32 (ALPHA) Debug", and build it.

You can also build from the command line. BUILDALL.BAT, a file that
will build all of the 32-bit PTK samples, is in the samples directory.
Use the following syntax:

BUILDALL ({intel | alpha | mips | powerpc} {debug | release} {force})

To use these samples with Visual C++ 4.0, you need to ensure that
your Library and Include directory settings point to your own
directories. To do this using Developer Studio, from the Tools menu,
choose Options, then click the Directories tab. From the "Show
directories for" box, choose Include files and Library files, and
ensure that these directories (as appropriate) are included:

Include files:Library files:
C:\MSSQL\DBLIB\INCLUDEC:\MSSQL\DBLIB\LIB
C:\MSSQL\MSDTC\INCLUDEC:\MSSQL\MSDTC\LIB
C:\MSSQL\ODBC\INCLUDEC:\MSSQL\ODS\LIB
C:\MSSQL\ODS\INCLUDE
C:\MSSQL\SQLOLE\INCLUDE

The following Transact-SQL sample scripts are located in the
C:\MSSQL\SAMPLES\MSDTC directories by default:

DBLIB
Sample Transact-SQL scripts that demonstrate communicating with both
MS DTC and SQL Server. Includes the following files:
    CHGADDR.SQL
    UPDADR.SQL


DBLIB.CPP

/*MS DTC Sample
**
**This example uses MS DTC (distributed transaction
**coordinator) to perform simultaneous updates on two
**SQL servers.  The transaction in this example is
**client initiated. The client also initiates the
**commit operation.
**
**  This sample uses the DBLib interface to communicate
**  with both MS DTC and SQL server.
**
**In this particular example, the pre-installed SQL server
**table "Authors" is used.
**
**  Build Instructions:
**  This sample must link with the following files:  ntwdblib.lib, xolehlp.lib
**
**  Usage Instructions:
**  Please run this program with the '-h' flag.
**
*/

//------------------------------------------------------------------------------
// Include standard header files
//------------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <ctype.h>

//------------------------------------------------------------------------------
// Include MS DTC specific header files.
//------------------------------------------------------------------------------
#define INITGUID

#include "txdtc.h"
#include "xolehlp.h"

//------------------------------------------------------------------------------
// Include DBLib specific header files
//------------------------------------------------------------------------------
#ifndef DBNTWIN32
#define DBNTWIN32

#include <SQL.h>
#include <SQLEXT.h>
#include <sqlfront.h>
#include <sqldb.h>

#endif /* DBNTWIN32 */

//------------------------------------------------------------------------------
// Define constants
//------------------------------------------------------------------------------
#define STR_LEN40

#define TABLE_NAME"Authors"

//------------------------------------------------------------------------------
// Define datatypes
//------------------------------------------------------------------------------
typedef struct DBCONN
{
TCHARpszSrv[STR_LEN];// data source name, configured through control panel
TCHARpszUser [STR_LEN];// Login user name
TCHARpszPasswd[STR_LEN];// Login user password
TCHARpszTableName[STR_LEN];// not used - should remove.

}DBCONN;

//------------------------------------------------------------------------------
// Define Globals
//------------------------------------------------------------------------------
static DBCONNgSrv1, gSrv2;// global DB connection struct for server 1 and 2

static TCHAR gAuthorID[STR_LEN];// use 11 chars only - per Authors table.
static TCHAR gNewAddress[STR_LEN+1];// max. address length in the Author's table.

//-------------------------------------------------------------------------------
//  DBLib specific global vars...
//-------------------------------------------------------------------------------
DBPROCESS*dbproc_server1;
DBPROCESS*dbproc_server2;
LOGINREC*login;

//-------------------------------------------------------------------------------
// Forward declaration of routines used.
//-------------------------------------------------------------------------------

void InitGlobals(int argc, char **argv);
void LogonToDB(DBPROCESS **dbp, DBCONN *ptr);
void Enlist(DBCONN *ptr,DBPROCESS *dbp, ITransaction *pTransaction);
void ExecuteStatement(DBCONN *ptr, DBPROCESS *dbp, LPTSTR pszBuf);

//-------------------------------------------------------------------------------
// DBLib error and message handlers
//-------------------------------------------------------------------------------
int err_handler(DBPROCESS*, int, int, int, char*, char*);
int msg_handler(DBPROCESS*, DBINT, int, int, char*);

//-------------------------------------------------------------------------------
// main()
//-------------------------------------------------------------------------------

void main(int argc, char **argv)
{

ITransactionDispenser*pTransactionDispenser;
ITransaction*pTransaction;
HRESULThr = S_OK ;
TCHARSqlStatement[STR_LEN*2];

// Initialize globals & validate command line arguments
InitGlobals(argc,argv);

// set error/msg handlers for this program
dbmsghandle((DBMSGHANDLE_PROC)msg_handler);
dberrhandle((DBERRHANDLE_PROC)err_handler);

// initialize LOGINREC structure
login = dblogin();

// Obtain the ITransactionDispenser Interface pointer
// by calling DtcGetTransactionManager()
hr = DtcGetTransactionManager( NULL, // LPTSTRpszHost,
NULL, // LPTSTRpszTmName,
IID_ITransactionDispenser,// /* in */ REFIIDrid,
    0,// /* in */ DWORDdwReserved1,
    0, // /* in */ WORDwcbReserved2,
    0,// /* in */ voidFAR * pvReserved2,
(void **)&pTransactionDispenser // /*out */ void**ppvObject
) ;
if (FAILED (hr))
    {
printf("DtcGetTransactionManager failed: %x\n", hr);
   exit (1);
    }

// Establish connection to database on server#1
LogonToDB(&dbproc_server1,&gSrv1);

// Establish connection to database on server#2
LogonToDB(&dbproc_server2,&gSrv2);

// Loop performing distributed transactions
for (INT i = 0; i < 5; i++)
{

// Initiate an MS DTC transaction
hr = pTransactionDispenser->BeginTransaction(
NULL,///* [in]  */ IUnknown __RPC_FAR *punkOuter,
ISOLATIONLEVEL_ISOLATED,///* [in]  */ ISOLEVEL isoLevel,
ISOFLAG_RETAIN_DONTCARE,// /* [in]  */ ULONG isoFlags,
NULL,///* [in]  */ITransactionOptions *pOptions
&pTransaction///* [out] */ ITransaction **ppTransaction
) ;

if (FAILED (hr))
{
printf("BeginTransaction failed: %x\n",hr);
exit(1);
}

// Enlist each of the data sources on the transaction
Enlist(&gSrv1,dbproc_server1,pTransaction);
Enlist(&gSrv2,dbproc_server2,pTransaction);

// Generate the SQL statement to execute on each of the databases
sprintf(SqlStatement,
"update authors set address = '%s_%d' where au_id = '%s'",
 gNewAddress,i,gAuthorID);

// Perform updates on both of the DBs participating in the transaction
ExecuteStatement(&gSrv1,dbproc_server1,SqlStatement);
ExecuteStatement(&gSrv2,dbproc_server2,SqlStatement);

// Commit the transaction
hr = pTransaction->Commit(0,0,0);
if (FAILED(hr))
{
printf("pTransaction->Commit() failed: %x\n",hr);
exit(1);
}

// At end of each transaction, pTransaction-Release() must be called.
hr = pTransaction->Release();
if (FAILED(hr))
{
printf("pTransaction->Release() failed: %x\n",hr);
exit(1);
}

printf("Successfully committed Transaction #%d\n",i);

} // for

// release the transaction dispenser
pTransactionDispenser->Release();

// release DBLib resources and exit
dbexit();
}

//-------------------------------------------------------------------------------

void InitGlobals(INT argc, char **argv)
{

TCHAR Usage[] = "\n           [-S1  server_1] \n"\
"           [-U1  user_name_1]\n"\
"           [-P1  passwd_1]\n"\
"           [-S2  server_2]\n"\
"           [-U2  user_name_2]\n"\
"           [-P2  passwd_2]\n"\
        "           [-ID  au_id]\n"\
"           [-Ad  new_address\n"\
"           [-h   Usage]\n";

// Init Table name
lstrcpy(gSrv1.pszTableName,TABLE_NAME);
lstrcpy(gSrv2.pszTableName,TABLE_NAME);

// Null login info, set defaults as appropriate

lstrcpy(gSrv1.pszSrv,"");
lstrcpy(gSrv1.pszUser,"sa");
lstrcpy(gSrv1.pszPasswd,"");
lstrcpy(gSrv2.pszSrv,"");
lstrcpy(gSrv2.pszUser,"sa");
lstrcpy(gSrv2.pszPasswd,"");

lstrcpy(gAuthorID,"172-32-1176");// default au_id value from authors table in pubs db.

// scan command line arguments for user input.
for (INT i = 1; i < argc; i++)
{
if (*argv[i] == '-' || *argv[i] == '/')
{
switch (argv[i][1])
{
// get the server or DSN name
case 's':
case 'S':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszSrv,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszSrv,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
 };
 break;

// get user name
case 'u':
case 'U':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszUser,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszUser,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
 };
 break;

// get password
case 'p':
case 'P':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszPasswd,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszPasswd,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
  };
 break;

// get au_id, overriding default value.
case 'i':
case 'I':
lstrcpy(gAuthorID,argv[++i]);
break;
// get new address to associate with the au_id
case 'a':
case 'A':
lstrcpy(gNewAddress,argv[++i]);
break;
case '?':
case 'h':
case 'H':
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
break;

default:
printf("Invalid Input: %s\n",argv[i]);
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
}
}
else
{
printf("Illegal command line argument #%d, %s\n",i,argv[i]);
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
}
}

printf("-----------------------------\n");

printf("MS DTC/DBLib Sample Configuration parameters\n");
printf( "server_1:      %s\n"\
"user_name_1:   %s\n"\
"passwd_1:      %s\n"\
"server_2:      %s\n"\
"user_name_2:   %s\n"\
"passwd_2:      %s\n",

gSrv1.pszSrv,gSrv1.pszUser,gSrv1.pszPasswd,
gSrv2.pszSrv,gSrv2.pszUser,gSrv2.pszPasswd);

printf("-----------------------------\n");

}

//-------------------------------------------------------------------------------

void LogonToDB(DBPROCESS **dbp, DBCONN *ptr)
{

DBSETLUSER(login, ptr->pszUser);
DBSETLPWD(login, ptr->pszPasswd);
DBSETLAPP(login, "example");

*dbp = dbopen (login, ptr->pszSrv);
if (*dbp == NULL)
{
printf ("\nLogin to server: %s failed, exiting!\n",ptr->pszSrv);
exit (ERREXIT);
}

/* Use the "pubs" database. */
dbuse(*dbp, "pubs");
}

//-------------------------------------------------------------------------------

void Enlist(DBCONN *ptr, DBPROCESS *dbp, ITransaction *pTransaction)
{
RETCODE rc = 0;

rc = dbenlisttrans (dbp, pTransaction);

if (FAIL == rc)
{
printf("\ndbenlisttrans() failed: %x\n",rc);
exit(1);
}

}

// ---------------------------------------------------------------------------

void ExecuteStatement(DBCONN *ptr, DBPROCESS *dbp, char *pszBuf)
{
RETCODE rc = 0;

dbcmd(dbp,pszBuf);

rc = dbsqlexec(dbp);
if (FAIL == rc)
{
// one or both of the servers failed to prepare
printf("dbsqlexec() failed\n");
exit(1);
}

rc = dbresults(dbp);
if (rc != SUCCEED)
{
printf("dbresults() failed: %x\n",rc);
exit(1);
}
}

// ---------------------------------------------------------------------------

/* Message and error handling functions. */

int msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext)
{
/*Msg 5701 is just a USE DATABASE message, so skip it.*/
if (msgno == 5701)
return (0);

/*Print any severity 0 message as is, without extra stuff.*/
if (severity == 0)
{
printf ("%s\n",msgtext);
return (0);
}

printf("SQL Server message %ld, severity %d:\n\t%s\n",
msgno, severity, msgtext);

if (severity >>= 16)
{
printf("Program Terminated! Fatal SQL Server error.\n");
exit(ERREXIT);
}
return (0);
}

// ---------------------------------------------------------------------------

int err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)

{
if ((dbproc == NULL) || (DBDEAD(dbproc)))
return (INT_EXIT);
else
{
printf ("DB-LIBRARY error: \n\t%s\n", dberrstr);

if (oserr != DBNOERR)
printf ("Operating system error:\n\t%s\n", oserrstr);
}
return (INT_CANCEL);
}

// ---------------------------------------------------------------------------


ODBC.CPP

/*MS DTC Sample
**
**This example uses MS DTC (distributed transaction
**coordinator) to perform simultaneous updates on two
**SQL servers.  The transaction in this example is
**client initiated. The client also initiates the
**commit operation.
**
**  As this sample uses the ODBC interface, you'll need
**  to configure ODBC data source names for 2 SQL servers
**  that you can use to run this sample.  Each DSN; data
**  source name,  must point to the pubs database in
**  a separate SQL server. The authors table in the pubs
**  database is used in this sample.
**
**  Build Instructions:
**  This sample must link with the following files:  xolehlp.lib, odbc32.lib, odbccp32.lib
**
**  Usage Instructions:
**  Please run this program with the '-h' flag.
**
*/

//------------------------------------------------------------------------------
// Include standard header files
//------------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <ctype.h>

//------------------------------------------------------------------------------
// Include MS DTC specific header files.
//------------------------------------------------------------------------------
#define INITGUID

#include "txdtc.h"
#include "xolehlp.h"

//------------------------------------------------------------------------------
// Include ODBC specific header file.
//------------------------------------------------------------------------------
#ifndef DBNTWIN32
#define DBNTWIN32

#include <SQL.h>
#include <SQLEXT.h>
#include "odbcss.h"
#include <ODBCINST.h>

#endif /* DBNTWIN32 */

//------------------------------------------------------------------------------
// Define constants
//------------------------------------------------------------------------------
#define STR_LEN40

//------------------------------------------------------------------------------
// Define datatypes
//------------------------------------------------------------------------------
typedef struct DBCONN
{
TCHARpszSrv[STR_LEN];// data source name, configured through control panel
TCHARpszUser [STR_LEN];// Login user name
TCHARpszPasswd[STR_LEN];// Login user password
HDBChdbc;// handle to an ODBC database connection
HSTMThstmt;// an ODBC statement handle, for use with SQLExecDirect

}DBCONN;

//------------------------------------------------------------------------------
// Define Globals
//------------------------------------------------------------------------------
static DBCONNgSrv1, gSrv2;// global DB connection struct for server 1 and 2

static TCHARgAuthorID[STR_LEN];// use 11 chars only - per Authors table.
static TCHARgNewAddress[STR_LEN+1];// max. address length in the Author's table.

//-------------------------------------------------------------------------------
//  ODBC specific global vars...
//-------------------------------------------------------------------------------
HENVgHenv = SQL_NULL_HENV;

//-------------------------------------------------------------------------------
// Forward declaration of routines used.
//-------------------------------------------------------------------------------

void InitGlobals(int argc, char **argv);
void LogonToDB(DBCONN *ptr);
void Enlist(DBCONN *ptr, ITransaction *pTransaction);
void ExecuteStatement(DBCONN *ptr, LPTSTR pszBuf);
BOOL ProcessRC(LPTSTR pszFuncName, DBCONN *ptr, RETCODE rc);
void DoSQLError(DBCONN *ptr);
void FreeODBCHandles(DBCONN *ptr);

//-------------------------------------------------------------------------------
// main()
//-------------------------------------------------------------------------------

void main(int argc, char **argv)
{

ITransactionDispenser*pTransactionDispenser;
ITransaction*pTransaction;
HRESULThr = S_OK ;
BOOLtf = 0 ;
TCHARSqlStatement[STR_LEN*2];

// Initialize globals & validate command line arguments
InitGlobals(argc,argv);

// Obtain the ITransactionDispenser Interface pointer
// by calling DtcGetTransactionManager()
hr = DtcGetTransactionManager(
NULL,// LPTSTR pszHost,
NULL,// LPTSTR pszTmName,
IID_ITransactionDispenser,// /* in  */ REFIID rid,
0,// /* in  */ DWORDdwReserved1,
0, // /* in  */ WORDwcbReserved2,
NULL,// /* in  */ void FAR * pvReserved2,
(void **)&pTransactionDispenser // /* out */ void** ppvObject
);

if (FAILED (hr))
{
printf("DtcGetTransactionManager failed: %x\n", hr);
   exit (1);
}

// Initialize the ODBC environment handle.
if (SQL_NULL_HENV == gHenv)
{
ProcessRC("SQLAllocEnv",0,SQLAllocEnv(&gHenv));
}

// Establish connection to database on server#1
LogonToDB(&gSrv1);

// Establish connection to database on server#2
LogonToDB(&gSrv2);

// Loop performing distributed transactions
for (INT i = 0; i < 5; i++)
{

// Initiate an MS DTC transaction
hr = pTransactionDispenser->BeginTransaction(
NULL,///* [in]  */ IUnknown __RPC_FAR *punkOuter,
ISOLATIONLEVEL_ISOLATED,///* [in]  */ ISOLEVEL isoLevel,
ISOFLAG_RETAIN_DONTCARE,// /* [in]  */ ULONG isoFlags,
NULL,///* [in]  */ ITransactionOptions *pOptions
&pTransaction///* [out] */ ITransaction **ppTransaction
) ;

if (FAILED (hr))
{
printf("BeginTransaction failed: %x\n",hr);
exit(1);
}

// Enlist each of the data sources in the transaction
Enlist(&gSrv1,pTransaction);
Enlist(&gSrv2,pTransaction);

// Generate the SQL statement to execute on each of the databases
sprintf(SqlStatement,
"update authors set address = '%s_%d' where au_id = '%s'",
 gNewAddress,i,gAuthorID);

// Perform updates on both of the DBs participating in the transaction
ExecuteStatement(&gSrv1,SqlStatement);
ExecuteStatement(&gSrv2,SqlStatement);

// Commit the transaction
hr = pTransaction->Commit(0,0,0);
if (FAILED(hr))
{
printf("pTransaction->Commit() failed: %x\n",hr);
exit(1);
}

// At end of each transaction, pTransaction-Release() must be called.
hr = pTransaction->Release();
if (FAILED(hr))
{
printf("pTransaction->Release() failed: %x\n",hr);
exit(1);
}

printf("Successfully committed Transaction #%d\n",i);

} // for

// release the transaction dispenser
pTransactionDispenser->Release();

// Free ODBC handles
FreeODBCHandles(&gSrv1);
FreeODBCHandles(&gSrv2);

// Free the global ODBC environment handle.
SQLFreeEnv(gHenv);
}

//-------------------------------------------------------------------------------

void InitGlobals(INT argc, char **argv)
{

TCHAR Usage[] = "\n           [-S1  server_1] \n"\
"           [-U1  user_name_1]\n"\
"           [-P1  passwd_1]\n"\
"           [-S2  server_2]\n"\
"           [-U2  user_name_2]\n"\
"           [-P2  passwd_2]\n"\
        "           [-ID  au_id]\n"\
"           [-Ad  new_address\n"\
"           [-h   Usage]\n";

// Init ODBC handles to be invalid
    gSrv1.hdbc = SQL_NULL_HDBC;
gSrv1.hstmt= SQL_NULL_HSTMT;

    gSrv2.hdbc = SQL_NULL_HDBC;
gSrv2.hstmt= SQL_NULL_HSTMT;

// Null login info, set defaults as appropriate

lstrcpy(gSrv1.pszSrv,"");
lstrcpy(gSrv1.pszUser,"sa");
lstrcpy(gSrv1.pszPasswd,"");
lstrcpy(gSrv2.pszSrv,"");
lstrcpy(gSrv2.pszUser,"sa");
lstrcpy(gSrv2.pszPasswd,"");

lstrcpy(gAuthorID,"172-32-1176");// default au_id value from authors table in pubs db.

// scan command line arguments for user input.
for (INT i = 1; i < argc; i++)
{
if (*argv[i] == '-' || *argv[i] == '/')
{
switch (argv[i][1])
{
// get the server or DSN name
case 's':
case 'S':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszSrv,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszSrv,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
 };
 break;

// get user name
case 'u':
case 'U':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszUser,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszUser,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
 };
 break;

// get password
case 'p':
case 'P':
 switch (argv[i][2])
 {

 case '1':
 lstrcpy(gSrv1.pszPasswd,argv[++i]);
 break;

 case '2':
 lstrcpy(gSrv2.pszPasswd,argv[++i]);
 break;

 default:
 printf("Invalid Input %s\n",argv[i]);
 printf("\nUsage: %s %s",argv[0],Usage);
 exit(1);
  };
 break;

// get au_id, overriding default value.
case 'i':
case 'I':
lstrcpy(gAuthorID,argv[++i]);
break;
// get new address to associate with the au_id
case 'a':
case 'A':
lstrcpy(gNewAddress,argv[++i]);
break;
case '?':
case 'h':
case 'H':
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
break;

default:
printf("Invalid Input: %s\n",argv[i]);
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
}
}
else
{
printf("Illegal command line argument #%d, %s\n",i,argv[i]);
printf("\nUsage: %s %s",argv[0],Usage);
exit(1);
}
}

printf("-----------------------------\n");

printf("MS DTC/ODBC Sample Configuration parameters\n");
printf( "server_1:      %s\n"\
"user_name_1:   %s\n"\
"passwd_1:      %s\n"\
"server_2:      %s\n"\
"user_name_2:   %s\n"\
"passwd_2:      %s\n",

gSrv1.pszSrv,gSrv1.pszUser,gSrv1.pszPasswd,
gSrv2.pszSrv,gSrv2.pszUser,gSrv2.pszPasswd);

printf("-----------------------------\n");

}

//-------------------------------------------------------------------------------

void LogonToDB(DBCONN *ptr)
{
RETCODE rc = 0;

rc = SQLAllocConnect(gHenv, &(ptr->hdbc) );

if (ProcessRC("SQLAllocConnect",ptr,rc))
{
rc = SQLConnect(ptr->hdbc,
(UCHAR *)(ptr->pszSrv),
SQL_NTS,
(UCHAR *)(ptr->pszUser),
SQL_NTS,
(UCHAR *)(ptr->pszPasswd),
SQL_NTS
);

ProcessRC("SQLConnect",ptr,rc);
}
}

//-------------------------------------------------------------------------------

void Enlist(DBCONN *ptr, ITransaction *pTransaction)
{
RETCODE rc = 0;

// Enlist database in the transaction

    rc = SQLSetConnectOption (ptr->hdbc, SQL_COPT_SS_ENLIST_IN_DTC, (UDWORD)pTransaction);

ProcessRC("SQLSetConnectOption",ptr,rc);
}

// ---------------------------------------------------------------------------

void ExecuteStatement(DBCONN *ptr, LPTSTR pszBuf)
{
RETCODE rc = 0;

// Allocate a statement handle for use with SQLExecDirect
rc = SQLAllocStmt(ptr->hdbc,&(ptr->hstmt));

ProcessRC("SQLAllocStmt",ptr,rc);

// Execute the passed string as a SQL statement
    rc = SQLExecDirect(ptr->hstmt,(UCHAR *)pszBuf,SQL_NTS);

ProcessRC("SQLExecDirect",ptr,rc);

// Free the statement handle
rc = SQLFreeStmt(ptr->hstmt, SQL_DROP);
ptr->hstmt = SQL_NULL_HSTMT;

ProcessRC("SQLFreeStmt",ptr,rc);

}

// ---------------------------------------------------------------------------

void FreeODBCHandles(DBCONN *ptr)
{
SQLDisconnect(ptr->hdbc);

SQLFreeConnect(ptr->hdbc);

ptr->hdbc   = SQL_NULL_HDBC;
ptr->hstmt  = SQL_NULL_HSTMT;
}

// ---------------------------------------------------------------------------

BOOL ProcessRC(LPTSTR pszFuncName, DBCONN *ptr,RETCODE rc)
{

switch (rc)
{

case SQL_SUCCESS:
return TRUE;
break;

case SQL_SUCCESS_WITH_INFO:
DoSQLError(ptr);
return TRUE;
break;

case SQL_ERROR:
printf("%s Failed - see more info\n",pszFuncName);
DoSQLError(ptr);
exit(-1);
return FALSE;
break;

case SQL_INVALID_HANDLE:
printf("%s Failed - SQL_INVALID_HANDLE\n",pszFuncName);
exit(-1);
return FALSE;
break;

case SQL_NO_DATA_FOUND:
printf("%s Failed - SQL_NO_DATA_FOUND\n",pszFuncName);
return FALSE;
break;

case SQL_STILL_EXECUTING:
printf("%s Failed - SQL_STILL_EXECUTING\n",pszFuncName);
return FALSE;
break;

case SQL_NEED_DATA:
printf("%s Failed - SQL_NEED_DATA\n",pszFuncName);
return FALSE;
break;

default:
printf("%s Failed - unexpected error, rc = %x\n",pszFuncName,rc);
DoSQLError(ptr);
exit(-1);
return FALSE;
break;
}

}

// ---------------------------------------------------------------------------

void DoSQLError(DBCONN *ptr)
{

const INTMSG_BUF_SIZE = 300;
UCHAR  szSqlState[MSG_BUF_SIZE];
UCHARszErrorMsg[MSG_BUF_SIZE];

SQLINTEGERfNativeError= 0;
SWORDcbErrorMsg= MSG_BUF_SIZE;
RETCODErc;

rc = SQLError(gHenv,
  ptr ? ptr->hdbc : 0,
  ptr ? ptr->hstmt : 0,
  szSqlState,
  &fNativeError,
  szErrorMsg,
  MSG_BUF_SIZE,
  &cbErrorMsg
  );

if (rc != SQL_NO_DATA_FOUND || rc != SQL_ERROR)
{
if (fNativeError != 0x1645)// ignore change database to master context message
{
printf("SQLError info:\n");
printf("SqlState: %s, fNativeError: %x\n",szSqlState,fNativeError);
printf("Error Message: %s\n",szErrorMsg);
}
}
else
{
printf("SQLError() failed: %x, NO_DATA_FOUND OR SQL_ERROR\n",rc);
}

}
// ---------------------------------------------------------------------------


README.TXT

****************************************************************
          MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
              OPEN DATA SERVICES SAMPLE PROGRAMS
****************************************************************
This file describes the Open Data Services sample programs
included with Microsoft(R) SQL Server(TM) Workstation System 6.5.

For additional information about SQL Server 6.5, Open Data Services,
and ODBC, see the "What's New in SQL Server 6.5," "Programming
Open Data Services," and "Programming ODBC for SQL Server" sections
of SQL Server Books Online, which contains the most up-to-date
information available.

****************************************************************
OPEN DATA SERVICES SAMPLE APPLICATIONS

The following 32-bit C sample programs are located in the
C:\MSSQL\SAMPLES\ODS directory by default:

GATEWAY
Sample C gateway application that connects to SQL Server as an
alternate data source and demonstrates a "pass through" application.
Includes the following files:
    GATECBS.C
    GATEWAY.C
    GATEWAY.MAK
    GATEWAY.MDP

ODSODBC
Builds an Open Data Services gateway version of the SQL Server ODBC
driver that can be customized for the capabilities of a particular
Open Data Services gateway application. Documented in Appendix E of
"Programming Open Data Services." To build a 16-bit version of the
SQL Server ODBC driver, refer to
C:\MSSQL\SAMPLES\ODS\ODSODBC\README.TXT. Includes the following files:
    BUILDW16.BAT
    LIBMAIN.C
    LOCAL.RC
    MAKEFILE.W16
    ODBCSS.H
    ODBCVER.H
    ODXRC.H
    ODSSAMP.MAK
    ODSSAMP.MDP
    README.TXT
    SAMP.RC

PROCSRV
Demonstrates how to implement a procedure server using Open Data
Services to handle specific pre-defined requests. Includes the
following files:
    PROCSRV.C
    PROCSRV.MAK
    PROCSRV.MDP
    SALES.DBF
    SERVICE.C

XP
Sample extended procedure DLL built with Open Data Services. The
functions within the DLL can be invoked by using the extended
stored procedures support in SQL Server.  To register the functions
and allow all users to use them, run the Transact-SQL script
INSTXP.SQL. Includes the following files:
    INSTXP.SQL
    XP.C
    XP.DEF
    XP.MAK
    XP.MDP

XP_DBLIB
Sample extended stored procedure using 32-bit Visual C++ and the
DB-Library interface to open a new connection to the SQL Server
that called the extended stored procedure. Returns a result set and
passes the results to the client. Uses a bound session for the
loopback connection.
    XP_DBLIB.C
    XP_DBLIB.DEF
    XP_DBLIB.MAK
    XP_DBLIB.MDP
    XP_DBLIB.SQL

XP_ODBC
Sample extended stored procedure using 32-bit Visual C++ and the
ODBC interface to open a new connection to the SQL Server that
called the extended stored procedure. Returns a result set and
passes the results to the client. Uses a bound session for the
loopback connection. Note: To connect to the server using integrated
security, the local server data source name (registered using ODBC
in the Control Panel) must be used. Includes the following
files:
    XP_ODBC.C
    XP_ODBC.DEF
    XP_ODBC.MAK
    XP_ODBC.MDP
    XP_ODBC.SQL

Each project on the Intel(R) platform includes Visual C++ 4.0
workspace files (.MDP). Double-click the workspace file to load
the workspace and make file into the Developer Studio environment.

Each project also contains cross-platform native Visual C++ 4.0 make
files (.MAK). For each 32-bit project the .MAK file contains Intel,
Alpha AXP(R), MIPS(R), and PowerPC(TM) build information. When the
project is loaded into Developer Studio, pick the appropriate target,
for example "Example1 - Win32 (ALPHA) Debug", and build it.

You can also build from the command line. BUILDALL.BAT, a file that
will build all of the 32-bit PTK samples, is in the samples
directory. Use the following syntax:

BUILDALL ({intel | alpha | mips | powerpc} {debug | release} {force})

To use these samples with Visual C++ 4.0, you need to ensure that
your Library and Include directory settings point to your own
directories. To do this using Developer Studio, from the Tools menu,
choose Options, then click the Directories tab. From the "Show
directories for" box, choose Include files and Library files, and
ensure that these directories (as appropriate) are included:

Include files:Library files:
C:\MSSQL\DBLIB\INCLUDEC:\MSSQL\DBLIB\LIB
C:\MSSQL\MSDTC\INCLUDEC:\MSSQL\MSDTC\LIB
C:\MSSQL\ODBC\INCLUDEC:\MSSQL\ODS\LIB
C:\MSSQL\ODS\INCLUDE
C:\MSSQL\SQLOLE\INCLUDE


GATECBS.C

// This file defines all the Open Data Services event handler functions for the
// GATEWAY application.
//

#include    <stdlib.h>
#include    <stdio.h>
#include    <string.h>
#include    <windows.h>
#include    <sqlfront.h>
#include    <sqldb.h>
#include    <srv.h>

// Define some user message codes.
//
#define SRV_MAXERROR    20000
#define RPC_UNKNOWN     SRV_MAXERROR + 1
#define COMPUTE_ROW     SRV_MAXERROR + 2
#define REMOTE_MSG      SRV_MAXERROR + 3
#define SEND_FAILED     SRV_MAXERROR + 4
#define BULK_CMD        "insert bulk"

// This error value must be sent back to the client in the event
// of a failure to login to the target database.  It is used in the
// init_remote() function below.
//
#define REMOTE_FAIL 4002

// Declare the structure to use for our private data area.
// It will be passed to the event handlers in the SRV_PROC structure.
// It contains information to make and use a connection to the remote
// DBMS -- in this case a SQL Server.
//
// A gateway to a non-SQL Server DBMS would contain a different structure.
//
#define MAXPARAMS   255
typedef struct remote_dbms {
    LOGINREC *login;    // The SQL Server login structure
    DBPROCESS *dbproc;  // The connection to the remote SQL Server
    BOOL bulk_mode;         // Flag indicating "bulk insert" mode
    short retparams[MAXPARAMS]; // tag return parameters
} REMOTE_DBMS;

// Declare the structure to be used for packing column metadata into the user datatype
// field.  This is a new feature of SQL Server 6.0.  The additional column metadata
// is available via dbcolinfo() in DB-Lib.

typedef struct _typeinfo
{
    USHORT  typeid;// used to hold the real user datatype
    USHORT  nullable:1, // TRUE/FALSE
    case_sensitive:1,// TRUE, FALSE
    updateable:2,// TRUE, FALSE, UNKNOWN
    identity:1,// TRUE/FALSE
    spare1:1,// spare fields
    spare2:2,
    spare3:8;
} TYPEINFO;

// The remote server name of this gateway
//
DBCHAR *remote_server = NULL;
SRV_PROC *Newsrvproc;       // Latest SRV_PROC allocated.

// Mutex used for init_remote()
HANDLE init_remote_mutex = 0;

int dbcolntype(DBPROCESS *dbproc, int column);

int attn_handler(SRV_PROC *srvproc);

int chk_err(SRV_SERVER *server, SRV_PROC *srvproc, int srverror,
            BYTE severity, BYTE state, int oserrnum, DBCHAR *errtext,
            int errtextlen, DBCHAR *oserrtext, int oserrtextlen);

int init_remote(SRV_PROC *srvproc);

int init_server(SRV_SERVER *server);

int rpc_execute(SRV_PROC *srvproc);

int exit_remote(SRV_PROC *srvproc);

int lang_execute(SRV_PROC *srvproc);

int handle_results(DBPROCESS *rmtproc, SRV_PROC *srvproc);

int remotemsgs(DBPROCESS *dbproc, DBINT msgno, INT msgstate,
               INT severity, LPCSTR msgtext, LPCSTR srvname, LPCSTR procname,
DBUSMALLINT line);

int remoteerr(DBPROCESS *dbproc, INT severity, INT dberr, INT oserr,
              LPCSTR dberrstr, LPCSTR oserrstr);

void set_remote_server_name(char *name);

//
// This section defines the "call-back" functions supplied to the gateway
// application.
//
#pragma check_stack( off )  // turn off stack checking

//
// SET_REMOTE_SERVER_NAME
//
//     This function sets the name of the remote server.
//
// Parameters:
//     name - Pointer to name of server.
//
// Returns:
//     none
//
void set_remote_server_name(name)
char *name;
{
    remote_server = name;
}

//
// INIT_SERVER
//     Initialize the server on a SRV_START event.
//     Event handlers for the server are installed.
//
// Parameters:
//     server - Pointer to SRV_SERVER structure
//
// Returns:
//     SRV_CONTINUE
//
int init_server(server)
SRV_SERVER *server;
{
    char log_buffer[256];

    // When we get a connection request from a client, we want to
    // call "init_remote()" to make a connection to the remote
    // server.
    //
    srv_handle(server, (DBINT)SRV_CONNECT, init_remote);

    // When the client issues a language request, call
    // "lang_execute()" to send the SQL statement to the remote DBMS.
    //
    srv_handle(server, (DBINT)SRV_LANGUAGE, lang_execute);

    // When the client issues an RSP, call "rpc_execute()"
    // to send the RSP to the remote DBMS (the SQL Server).
    //
    srv_handle(server, (DBINT)SRV_RPC, rpc_execute);

    // When a disconnect request is issued, call "exit_remote()"
    // to close the connection to the remote DBMS.
    //
    srv_handle(server, (DBINT)SRV_DISCONNECT, exit_remote);

    // Install the handler that will be called when the
    // gateway receives an attention from one of its
    // clients.
    //
    srv_handle(server, (DBINT)SRV_ATTENTION, attn_handler);

 // Initialize dblib and configure for 100 connections
 //
 dbinit();
 dbsetmaxprocs(100);

    // Now we'll install the message and error handlers for any
    // messages from the remote DBMS.
    //
    dberrhandle(remoteerr);
    dbmsghandle(remotemsgs);

 // Create mutex to serialize init_remote
 //
 init_remote_mutex = CreateMutex(NULL, FALSE, NULL);

    //  Log Server information to log file
    //
    sprintf(log_buffer, "Client connections allowed = %s",
            srv_sfield(server, SRV_CONNECTIONS, (int *)NULL));

    srv_log(server, FALSE, log_buffer, SRV_NULLTERM);
    printf("%s\n", log_buffer);

    return(SRV_CONTINUE);
}

// INIT_REMOTE
//    Event handler for a SRV_CONNECT event.
//    A connection is made to the remote DBMS.
//
//    In this example program, the connection is to a SQL Server.
//    If using a non-SQL Server remote DBMS, the connection code
//    would be different, but would probably still occur here.
//
// Parameters:
//      srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//     SRV_CONTINUE
//
// Side Effects:
//     If the connection to the remote dbms cannot be made, then issue
//     a SRV_DISCONNECT request.
//
//
int init_remote(srvproc)
SRV_PROC *srvproc;
{
 char *string;
 int len;
    REMOTE_DBMS *remote;    // the private data area to keep
                                // track of the remote DBMS
                                // connection.
BOOLbImpersonated;

    // Set Newsrvproc.  This is used if we get an error on
    // the open from DBLIB.  Since there isn't a dbproc,
    // it is clear which srvproc to send the msg back on when the
    // DBLIB error-handler gets called.
    //
    // First lock out any other threads trying to connect using this same
    // function.
    //
    WaitForSingleObject(init_remote_mutex, INFINITE);

    Newsrvproc = srvproc;

 if (SRV_TDSVERSION(srvproc) != SRV_TDS_6_0)
 {
// Send a message to the client that they must be 4.2 or 6.0 clients.
// 4.2 client could be supported by
// converting all decimal/numeric values to float before sending to
// client, and omiting the column metadata packed into srv_setutype in
// the handle_results() function.
//
srv_sendmsg(srvproc,
SRV_MSG_ERROR,
(DBINT)REMOTE_FAIL,
(DBTINYINT)0,
(DBTINYINT)0,
NULL,
0,
0,
"Gateway sample application only supports version 6.0 clients.",
SRV_NULLTERM);

// Now allow other threads to enter this function.
//
   ReleaseMutex(init_remote_mutex);
return(SRV_DISCONNECT);
    }

    // Allocate a REMOTE_DBMS information structure.
    //
    remote = (REMOTE_DBMS *)srv_alloc((DBINT)sizeof(*remote));

    // Try to open a connection to the remote DBMS.
    //
    if (remote == NULL) {
        // Send a message to the client that
        // the remote connection failed.
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_FAIL, (DBTINYINT)0,
                    (DBTINYINT)0, NULL, 0, 0,
                    "Login to remote DBMS failed (srv_alloc).", SRV_NULLTERM);
 ReleaseMutex(init_remote_mutex);
        return(SRV_DISCONNECT);
    }

    // Set "bulk insert" mode flag to false.
    //
    remote->bulk_mode = FALSE;

    // Allocate the LOGINREC structure used to make connections to the
    // remote server. Open the connection in the SRV_CONNECT handler.
    //
    remote->login = dblogin();

    if (remote->login == NULL) {
        // Send a message to the client that the
        // remote connection failed.
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_FAIL, (DBTINYINT)0,
                    (DBTINYINT)0, NULL, 0, 0,
                    "Login to remote DBMS failed (dblogin).", SRV_NULLTERM);

        // Deallocate the remote structure and set the user data
        // pointer in "srvproc" to null so that the disconnect
        // handler won't try to disconnect from the remote dbms.
        //
        srv_free(remote);
 ReleaseMutex(init_remote_mutex);
        return(SRV_DISCONNECT);
    }
    remote->dbproc = (DBPROCESS *)NULL;

    // Set the user name, password, and application name for the remote DBMS.
    //
    DBSETLUSER(remote->login, srv_pfield(srvproc, SRV_USER, (int *)NULL));
    DBSETLPWD(remote->login, srv_pfield(srvproc, SRV_PWD, (int *)NULL));
    DBSETLAPP(remote->login, srv_pfield(srvproc, SRV_APPLNAME, (int *)NULL));
    DBSETLNATLANG(remote->login, srv_pfield(srvproc, SRV_NATLANG, (int *)NULL));

// following will let us pass decimal data back to client
DBSETLVERSION(remote->login, DBVER60);

//if client requested a trusted connection, gateway must do so too
if (srv_pfield(srvproc, SRV_LSECURE, (int *)NULL) == "TRUE")
DBSETLSECURE(remote->login);

// Even if client hasn't explicitly requested a trusted connection, he may be
// using integrated security if the server is set to mixed or integrated mode.
// To handle this case, always tryy to impersonate the client.

bImpersonated = srv_impersonate_client(srvproc);

    // See if client has set Bulk Copy flag
    //
    if (strcmp(srv_pfield(srvproc, SRV_BCPFLAG, (int *)NULL), "TRUE") == 0)
        BCP_SETL(remote->login, TRUE);
    else
        BCP_SETL(remote->login, FALSE);

    // If no server name was specified as a parameter to the main program,
    // then assume that the name is coming from the client.
    //
    if (remote_server == NULL || remote_server[0] == '\0') {
        remote_server = srv_pfield(srvproc, SRV_HOST, (int *)NULL);
    }

    // Try to open a connection to the remote DBMS.
    //
    if ((remote->dbproc = dbopen(remote->login,
                                 remote_server)) == (DBPROCESS *)NULL) {

// Send a message to the client that
// the remote connection failed.
//
srv_sendmsg(srvproc,
SRV_MSG_ERROR,
(DBINT)REMOTE_FAIL,
(DBTINYINT)0,
(DBTINYINT)0,
NULL,
0,
0,
"Login to remote DBMS failed (dbopen).",
SRV_NULLTERM);

        // Deallocate the remote structure and set the user data
        // pointer in "srvproc" to null so that the disconnect
        // handler won't try to disconnect from the remote DBMS.
        //
        srv_free(remote);
        srv_setuserdata(srvproc, (BYTE *)NULL);
 ReleaseMutex(init_remote_mutex);
if( bImpersonated )
        srv_revert_to_self(srvproc);
        return(SRV_DISCONNECT);
    } else {
        // Connection to the remote DBMS successful.  Save
        // remote data structure in the "srvproc" so it will be
        // available to the other handlers. We'll also map the remote
        // DBMS connection to our "srvproc".
        //
        srv_setuserdata(srvproc, (VOID *)remote);
        dbsetuserdata(remote->dbproc, (VOID *)srvproc);

    }

 ReleaseMutex(init_remote_mutex);

// if currently running in user context, don't need to any more
if( bImpersonated )
       srv_revert_to_self(srvproc);

    // Display connect info on console
    //
 string = srv_pfield(srvproc, SRV_CPID, &len);
    string[len] = '\0';
 printf("\nClient process ID: %s\n", string);

    string = srv_pfield(srvproc, SRV_USER, &len);
    string[len] = '\0';
    printf("User name: %s\n", string);

    string = srv_pfield(srvproc, SRV_APPLNAME, &len);
    string[len] = '\0';
    if (len > 0)
       printf("Application program name: %s\n", string);

    string = srv_pfield(srvproc, SRV_RMTSERVER, &len);
    string[len] = '\0';
    if (len > 0)
       printf("Remote Server: %s\n", string);

    return(SRV_CONTINUE);
}

//
// LANG_EXECUTE
//     Execute a client language request on the remote dbms.
//     Any results from the remote dbms are passed back to the client.
//
//  Parameters:
//     srvproc -  process handle to the current client connection.
//
// Returns:
//     SRV_CONTINUE
//
int lang_execute(srvproc)
SRV_PROC *srvproc;
{
    REMOTE_DBMS *rmt_dbms;  // the remote database pointer
    DBPROCESS *rmtproc; // our DBPROCESS pointer
    DBCHAR *query;      // pointer to language buffer
    int query_len;          // length of query
    int status;             // status of dblib calls

    // Get the remote dbms pointer we saved in the srvproc via
    // srv_setuserdata.
    //
    rmt_dbms = (REMOTE_DBMS *)srv_getuserdata(srvproc);

    // Get the pointer to the remote DBPROCESS
    //
    rmtproc = rmt_dbms->dbproc;

    // Get the pointer to the client language request command buffer.
    //
    query = srv_langptr(srvproc);

    // See if the previous command was a "bulk insert" command
    //
    if (rmt_dbms->bulk_mode) {

        // Get length of the SQL command.
        //
        query_len = srv_langlen(srvproc);

        // If length of data is zero, then send a zero length buffer
        // to the destination SQL Server.  This is required in order to
        // signal the SQL Server that no more data follows.
        //
        if (query_len == -1L)
            query_len = 0L;

        // Place buffer into target SQL server's buffer.  Use
        // dbfcmd() to pass all binary data from the language buffer on to
        // the SQL Server.
        //
        status = dbfcmd(rmtproc, query, query_len);

        rmt_dbms->bulk_mode = FALSE;
    } else {
        // Let's check for "insert bulk" request
        //
        if (srv_langlen(srvproc) > (sizeof(BULK_CMD) - 1))
            if (strnicmp(query, BULK_CMD, (sizeof(BULK_CMD) - 1)) == 0)
                rmt_dbms->bulk_mode = TRUE;

            // Place buffer into target SQL server's buffer.
            //
            status = dbcmd(rmtproc,
            query);
    }

    // If previous DBLIB call successful, send command buffer to SQL Server.
    //
    if (status == SUCCEED) {    // if previous DBLIB call successful
        status = dbsqlexec(rmtproc);
    }
    if (!SRV_GOT_ATTENTION(srvproc) && (status == SUCCEED)) {
        //
        // Get the remote dbms results and pass them back to
        // client.
        //
        handle_results(rmtproc, srvproc);
    } else {
        //
        // If an attention event was issued or the dbsqlexec failed,
        // acknowledge with senddone.
        //
        if (DBDEAD(rmtproc)) {
            printf("thread shutting down");
            srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_FAIL,
                        (DBTINYINT)23, (DBTINYINT)17, NULL, 0, 0, "Remote Server connection no longer active: \
                         thread shutting down"
                        , SRV_NULLTERM);
            srv_senddone(srvproc, SRV_DONE_FINAL | SRV_DONE_ERROR,
                         (DBUSMALLINT)0, (DBINT)0);
            srv_event(srvproc, SRV_DISCONNECT, NULL);
        } else {
            srv_senddone(srvproc, SRV_DONE_FINAL, (DBUSMALLINT)0, (DBINT)0);
        }
    }
    return(SRV_CONTINUE);
}

//
// RPC_EXECUTE
//     Execute a client RPC.
//
//     Note that in our example, the remote DBMS is an SQL Server.
//     If our remote DBMS was a foreign DBMS, the code to send
//     the query to the remote DBMS and receive remote results
//     would be different.  In this example, we'll just send
//     the rpc along to the SQL Server.
//
// Parameters:
//    srvproc - The Open Server process handle to use to send results to the
//               client.
//
// Returns:
//     SRV_CONTINUE
//
int rpc_execute(srvproc)
SRV_PROC *srvproc;
{
    DBPROCESS *rmtproc; // Our DBPROCESS pointer
    int i;                  // Index variable
    short params;
    short retparam;
    short *paramarray;
DBCHAR complete_rpc_name[4 * (MAXNAME + 1)]; //database.owner.name;#
DBCHAR *rpc_db;
DBCHAR *rpc_owner;
DBCHAR *rpc_name;
int rpc_number;
char rpc_number_char[17];
DBCHAR *rpc_paramname;
BYTE rpc_paramstatus;
int rpc_paramtype;
DBINT rpc_parammaxlen;
DBINT rpc_paramlen;
void *rpc_paramdata;
DBNUMERIC  numeric;//structure to hold numeric params

    rmtproc = ((REMOTE_DBMS *)srv_getuserdata(srvproc))->dbproc;
    paramarray = ((REMOTE_DBMS *)srv_getuserdata(srvproc))->retparams;

// initialize numeric structure
memset (&numeric, 0, sizeof(DBNUMERIC));

   // Construct full RPC name and initialize the RPC to the remote DBMS.
   //
memset (complete_rpc_name, 0, sizeof(complete_rpc_name));
rpc_db = srv_rpcdb(srvproc, (int *)NULL);
rpc_owner = srv_rpcowner(srvproc, (int *)NULL);
rpc_name = srv_rpcname(srvproc, (int *)NULL);

if (rpc_db != NULL) {
strcat(complete_rpc_name,rpc_db);
strcat(complete_rpc_name,".");
};
if (rpc_owner != NULL) {
strcat(complete_rpc_name,rpc_owner);
strcat(complete_rpc_name,".");
};
strcat(complete_rpc_name,rpc_name);

rpc_number = srv_rpcnumber(srvproc);
if (rpc_number > 0) {
    strcat(complete_rpc_name,";");
itoa(rpc_number,rpc_number_char,10);
strcat(complete_rpc_name, rpc_number_char);
};

dbrpcinit(rmtproc, complete_rpc_name, (DBUSMALLINT)srv_rpcoptions(srvproc));

    // Set up any RPC parameters.
    //
    params = srv_rpcparams(srvproc);
    retparam = 1;

    for (i = 1; i <= params; i++) {
rpc_paramname = srv_paramname(srvproc, i, (int *)NULL);
    if (!strlen(rpc_paramname))
 rpc_paramname = NULL;
       rpc_paramstatus = (BYTE)srv_paramstatus(srvproc, i);
       rpc_paramtype = srv_paramtype(srvproc, i);
       rpc_parammaxlen = srv_parammaxlen(srvproc, i);
rpc_paramlen = srv_paramlen(srvproc, i);
       rpc_paramdata = srv_paramdata(srvproc, i);

//  DB-Lib requires maxlen of -1 for fixed-length datatypes, but ODS doesn't
// always return this.   So set it.
    switch (rpc_paramtype)
    {
case SQLDECIMAL:
case SQLNUMERIC:
case SQLBIT:
case SQLINT1:
case SQLINT2:
case SQLINT4:
case SQLFLT4:
case SQLDATETIM4:
case SQLMONEY4:
case SQLMONEY:
case SQLDATETIME:
case SQLFLT8:
    rpc_parammaxlen = -1;
    break ;
default:
    break ;
}

// Special handling for decimal types.  DB-Lib treats them as fixed length datatypes
// that are the full size of a DBNUMERIC.  ODS uses variable-length numeric data
// and only gives the exact bytes needed to represent the number

    switch (rpc_paramtype)
    {
case SQLDECIMAL:
case SQLNUMERIC:
memset (&numeric, 0, sizeof(DBNUMERIC));
memmove(&numeric, rpc_paramdata, rpc_paramlen);
rpc_paramdata = &numeric;
// ODS says null numeric values have a length of 2, dblib expects them to be 0
if (rpc_paramlen == 2)
rpc_paramlen = 0;
break;
default:
break;
}

dbrpcparam(rmtproc,rpc_paramname,rpc_paramstatus,rpc_paramtype,
 rpc_parammaxlen,rpc_paramlen, rpc_paramdata);

        // The current rpc may have three parameters, but only the
        // first and third are return parameters.  This means that
        // dbnumrets() returns two parameters, not three.  The first
        // call to dbretdata() referes to the rpc's first parameter,
        // and the second call to dbretdata() refers to the rpc's
        // third parameter.  To handle this, we map each return
        // parameter to its original parameter so we can later
        // reset the return value of the correct return parameters
        // in "handle_results()".
        //
        if ((BYTE)srv_paramstatus(srvproc, i) & SRV_PARAMRETURN) {
            paramarray[retparam] = i;
            retparam++;
        }
    }

    // Send the RPC to the remote DBMS.
    //
    dbrpcsend(rmtproc);
    dbsqlok(rmtproc);

    // Now get the remote DBMS results and pass them back to
    // Open Server client.
    //
    handle_results(rmtproc, srvproc);
    return(SRV_CONTINUE);
}

//
// HANDLE_RESULTS
//     Once a command has been sent to the remote DBMS by the
//     SRV_LANGUAGE  handler, this routine processes the results
//     and passes them back to the client.
//
// Parameters:
//     rmtproc - The DBPROCESS handle to the remote DBMS.
//     srvproc - The process handle to use to send results to the client.
//
// Returns:
//     SUCCEED or FAIL.
//
int handle_results(rmtproc, srvproc)
DBPROCESS *rmtproc;
SRV_PROC *srvproc;
{
    short cols;             // data columns returned
    int i;                  // index variable
    BOOL results_sent;      // number of result sets sent
    BOOL gotcompute;        // COMPUTE row indicator
    short *paramarray;
    RETCODE returnvalue;    // value returned from dblib call
DBCOLdbcol;// column metadata structure
TYPEINFOtypeinfo;// used to send metadata in usertype
int dbcolsize;// size of DBCOL struct
BOOL bRowsAffected;// was command one that affects or returns rows
DBINT nRowsAffected;// result of DBCOUNT
DBNUMERIC numeric;// structure to hold precision and scale for numeric cols
short collen = 0;// holder for col data length
DBINTretlen;// for sending output params
DBINTrpc_paramtype;// for sending output params

    results_sent = FALSE;
    gotcompute = FALSE;
bRowsAffected = FALSE;
nRowsAffected = 0;

    paramarray = ((REMOTE_DBMS *)srv_getuserdata(srvproc))->retparams;

// initialize the DBCOL structure
dbcolsize = sizeof(DBCOL);
memset(&dbcol, 0, dbcolsize);
dbcol.SizeOfStruct = dbcolsize;

// initialize numeric structure
memset (&numeric, 0, sizeof(DBNUMERIC));

    // Process the results from the remote DBMS.
    // Since a command may consist of multiple commands or a single
    // command that has multiple sets of results, we'll loop through
    // each results set.
    //
    while (TRUE) {

        returnvalue = dbresults(rmtproc);
        if (returnvalue == NO_MORE_RESULTS) {
            break;
        }

        // Check to see if the client has sent an attention event.  If
        // so, simply discard data from remote server
        //
        if (SRV_GOT_ATTENTION(srvproc)) {
    dbcancel(rmtproc);// See attn_handler comments
        continue;
        }

        //
        // If this is the second time through the loop,
        // send a completion message to the client
        // for the previous results sent.
        //
        if (results_sent == TRUE) {

            // If there are some COMPUTE rows, send a message
            // to the client that Data Services Library doesn't yet handle them.
            //
            if (gotcompute == TRUE) {
                gotcompute = FALSE;
                srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)COMPUTE_ROW,
                            (DBTINYINT)0, (DBTINYINT)0, NULL, 0, 0,
                          "Data Services library can't handle COMPUTE rows.",
                            SRV_NULLTERM);
            }

            // If the previous batch was one that may
            // have affected rows, set the DONE status
            // accordingly.
            //
            if (bRowsAffected) {
                srv_senddone(srvproc, SRV_DONE_MORE | SRV_DONE_COUNT,
                             (DBUSMALLINT)0, nRowsAffected);
            } else
                srv_senddone(srvproc, SRV_DONE_MORE, (DBUSMALLINT)0,
                            nRowsAffected);

        }

        // How many data columns are in the row?
        // Non-"select" statements will have 0 columns.
        //
        cols = dbnumcols(rmtproc);

        // Build the row description for the client return.
        //
        for (i = 1; i <= cols; i++) {

            //  Call "srv_describe()" for each column in the row.
//  dbcolntype is used to preserve the nullability information
//that is lost by dbcoltype.
            //
            srv_describe(srvproc, i, (DBCHAR *)dbcolname(rmtproc, i), SRV_NULLTERM,
                         (DBINT)dbcolntype(rmtproc, i), dbcollen(rmtproc, i),
                         (DBINT)dbcolntype(rmtproc, i), dbcollen(rmtproc, i),
                         (BYTE *)NULL);

// Now pack additional col metadata into the usertype
// Note this code below does "SQL 6.0" style packing used by DB-Lib,
// and the ODS gateway ODBC driver if the resources DLL is set to use it
// "ODBC-style" metadata encoding uses a different structure, not shown here.
// See ODS Programmer's Reference for more details.
// get column metadata
dbcolinfo(rmtproc, CI_REGULAR, i, 0, &dbcol);

// reset to 0
*(DBINT*)&typeinfo = 0;

/* Set user type */
if (dbcol.UserType < 0)
typeinfo.typeid = 0;
else
typeinfo.typeid = (USHORT) dbcol.UserType;

if (dbcol.Identity == TRUE)
   typeinfo.identity = TRUE;

if (dbcol.Null == TRUE)
   typeinfo.nullable = TRUE;
else if (dbcol.Null == DBUNKNOWN)
   typeinfo.nullable = DBUNKNOWN;

if (dbcol.CaseSensitive == TRUE)
   typeinfo.case_sensitive = TRUE;
else if (dbcol.CaseSensitive == DBUNKNOWN)
   typeinfo.case_sensitive = DBUNKNOWN;

if (dbcol.Updatable == TRUE)
typeinfo.updateable = TRUE;
else if (dbcol.Updatable == DBUNKNOWN)
typeinfo.updateable = DBUNKNOWN;

srv_setutype(srvproc, i, *(DBINT*)&typeinfo);

// If column is a decimal or numeric, need to setup a valid precision and
// scale.  Normally this would be provided by dbdata() prior to srv_sendrow().
// But if there are no rows in the result set, srv_sendrow() will not
// be called, and ODS will send the header when srv_senddone() is called.
if ((dbcol.Type == SQLNUMERIC) || (dbcol.Type == SQLDECIMAL))
{
numeric.precision = dbcol.Precision;
numeric.scale = dbcol.Scale;
                srv_setcoldata(srvproc, i, &numeric);
}
        }

        // Send each row from the remote DBMS to the client.
        //
        while (TRUE) {

            returnvalue = dbnextrow(rmtproc);
            if (returnvalue == NO_MORE_ROWS) {
                break;
            }

            // If it's not a regular row, it's a COMPUTE row.
            // This SQL extension is particular to Sybase
            // TRANSACT-SQL and is not yet supported.
            //
            if (DBROWTYPE(rmtproc) != REG_ROW) {
                gotcompute = TRUE;
                continue;
            } else
                gotcompute = FALSE;

            // The row description is built.  Move the
            // rows from the remote server to the client.
            //
            for (i = 1; i <= cols; i++) {
                srv_setcollen(srvproc, i, (collen = (short)dbdatlen(rmtproc, i)));
// special handling for the srv_setcoldata pointer if the value is null numeric
// dbdata does not point to a valid numeric structure in this case
// so point instead to the numeric structure set up at srv_describe time
 if (  ((dbcoltype(rmtproc, i) == SQLDECIMAL) || (dbcoltype(rmtproc, i) == SQLNUMERIC))
 && (collen == 0))
{
srv_setcoldata(srvproc, i, &numeric);
}
else
                srv_setcoldata(srvproc, i, dbdata(rmtproc, i));
            }

            // Check to see if the client has issued an attention event.
            // If so, discard data from the remote server.
            //
            if (SRV_GOT_ATTENTION(srvproc)) {
    dbcancel(rmtproc);// See attn_handler comments
                continue;
            }

// Now put the row in the ODS output buffer
srv_sendrow(srvproc);
        }
        // Check to see if any parameter were returned from
        // the remote DBMS.  If so, pass them through to the
        // client.
        for (i = 1; i <= dbnumrets(rmtproc); i++) {
            //
            // If the return parameters are a result of
            // an rpc, we used srv_paramset() to set the return
            // value.  If the return parameters are not the
            // result of an rpc, we use srv_returnval().
            //
retlen = dbretlen(rmtproc, i);
rpc_paramtype = dbrettype(rmtproc, i);

// special handling for decimal types.  need to add 2 bytes for precision & scale
// from what is returned by dbretlen

if ((rpc_paramtype == SQLNUMERIC)|| (rpc_paramtype == SQLDECIMAL))
retlen += 2;

            if (srv_rpcname(srvproc, NULL) != NULL) {
                //
                // The current rpc may have three parameters, but
                // only the first and third are return parameters.
                // This means that dbnumrets() returns two parameters,
                // not three.  The first call to dbretdata() refers to
                // the rpc's first parameter, and the second call to
                // dbretdata() refers to the rpc's third parameter.
                // To handle this, we map each return parameter to
                // its original parameter so we can later reset the
                // return value of the correct return parameters in
                // handle_results().
                //
                srv_paramset(srvproc, paramarray[i], dbretdata(rmtproc, i), retlen);

            } else {

                srv_returnval(srvproc, (DBCHAR *)dbretname(rmtproc, i), SRV_NULLTERM,
                              SRV_PARAMRETURN, rpc_paramtype,
                              retlen, retlen,dbretdata(rmtproc, i));

            }
        }
        // Check to see if we got a return status code from the
        // remote DBMS.  Pass it through to the client.
        //
        if (dbhasretstat(rmtproc))
           srv_sendstatus(srvproc, dbretstatus(rmtproc));

        // If the command was one where count is meaningful
        // send the srv_senddone message accordingly.
        //
bRowsAffected = dbiscount(rmtproc);
nRowsAffected = DBCOUNT(rmtproc);

        // Set flag so that we will send a completion
        // message for the current set of results.
        //
        results_sent = TRUE;
    }

    // If there are some COMPUTE rows, send a message
    // to the client that Open Services Library doesn't handle them yet.
    //
    if (gotcompute == TRUE) {
        gotcompute = FALSE;
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)COMPUTE_ROW, (DBTINYINT)0,
                    (DBTINYINT)0, NULL, 0, 0,
                    "Data Services Library can't handle COMPUTE rows.",
                    SRV_NULLTERM);
    }

    // Send the final done packet for the execution of the command batch.
    //
    // If the previous batch was one that may
    // have affected rows, set the DONE status
    // accordingly.
    //
if (bRowsAffected) {
    srv_senddone(srvproc, SRV_DONE_FINAL | SRV_DONE_COUNT,
                    (DBUSMALLINT)0, nRowsAffected);
   } else
       srv_senddone(srvproc, SRV_DONE_FINAL, (DBUSMALLINT)0,
                nRowsAffected);

    return(SUCCEED);
}

// EXIT_REMOTE
//     Handler for SRV_DISCONNECT events.
//     Closes remote DBMS connection if appropriate.
//
//    The code to disconnect from the remote DBMS is SQL Server specific. If
//    using a non-SQL Server database, the disconnect from the  remote database
//    would be different but would probably still occur here.
//
// Parameters:
//     srvproc - the handle to the client connection
//
// Returns:
//     SRV_CONTINUE
//
int exit_remote(srvproc)
SRV_PROC *srvproc;
{
 char *string;
 int len;
    REMOTE_DBMS *remote;    // pointer to target connect structure

    remote = (REMOTE_DBMS *)srv_getuserdata(srvproc);

    // Is there a REMOTE_DBMS structure to clean-up?
    //
    if (remote != (REMOTE_DBMS *)NULL) {

        // Is there a live dbproc?
        //
        if (remote->dbproc != (DBPROCESS *)NULL) {
            dbclose(remote->dbproc);
        }
        dbfreelogin(remote->login);
        srv_free(remote);
    }

 // Display info on console
 //
 string = srv_pfield(srvproc, SRV_CPID, &len);
    string[len] = '\0';
   printf("\nClient connection closed, process ID: %s\n", string);

    return(SRV_CONTINUE);
}

// CHK_ERR
//     Print out errors.
//
// Parameters:
//     server        - pointer to gateway server structure.
//     srvproc      - pointer to client connection structure
//     errornum     - error number.
//     severity     - error severity.
//     state         - error state.
//     oserrnum     - operating system error number, if any.
//     errtext      - the text of the error message.
//     errtextlen   - length of the errtext message
//     oserrtext    - the text of the operating system error message.
//     oserrtextlen - length of the errtext message
//
// Returns:
//     SRV_CONTINUE, SRV_CANCEL, or SRV_EXIT
//

RETCODE chk_err(server, srvproc, errornum, severity, state, oserrnum, errtext,
                errtextlen, oserrtext, oserrtextlen)
SRV_SERVER *server;
SRV_PROC *srvproc;
int errornum;
BYTE severity;
BYTE state;
int oserrnum;
DBCHAR *errtext;
int errtextlen;
DBCHAR *oserrtext;
int oserrtextlen;
{
    char log_buffer[256];
    char error[256];
    char oserror[256];

    memcpy(error, errtext, errtextlen);
    error[errtextlen] = '\0';
    memcpy(oserror, oserrtext, oserrtextlen);
    oserror[oserrtextlen] = '\0';

// Strip out resource information. Get the actual error number.
errornum = (errornum & 0x0000FFFF);

    // Operating system error?
    //
    if (oserrnum != SRV_ENO_OS_ERR) {
        sprintf(log_buffer, "SERVER OS ERROR: %d: %s.", oserrnum, oserror);

        if (server)
srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
else// If application not initialized log to screen
    printf ("%s\n", log_buffer);
    }

    // Is this a fatal error for the server?
    //
    if (severity >= SRV_FATAL_SERVER) {
        sprintf(log_buffer,
  "SERVER: FATAL SERVER ERROR: errornum = %d, severity = %d, state = %d: %s.",
 errornum, severity, state, error);
        if (server)
srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
else// If application not initialized log to screen
    printf ("%s\n", log_buffer);
        return(SRV_EXIT);
    } else {
        //
        // Did the "srvproc" get a fatal error?
        //
        if (severity >= SRV_FATAL_PROCESS) {
            sprintf(log_buffer,
  "SERVER: FATAL CONNECT ERROR: errornum = %d, severity = %d, state = %d: %s.",
                     errornum, severity, state, error);
        if (server)
srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
else// If application not initialized log to screen
    printf ("%s\n", log_buffer);
            return(SRV_CANCEL);
        }
    }

    // A non-fatal error or an information message received.
    // We'll pass it through to the client.
    //
    if (srvproc != (SRV_PROC *)NULL && (server != NULL))
        if (severity < 10) {    // if informational message
            srv_sendmsg(srvproc, SRV_MSG_INFO, (DBINT)errornum, severity, 0,
                        NULL, 0, 0, error, SRV_NULLTERM);
        } else {            // must be an error message
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)errornum, severity, 0,
                    NULL, 0, 0, error, SRV_NULLTERM);
    } else {
        sprintf(log_buffer, "ODS ERROR: errornum = %d, severity = %d: %s",
                errornum, severity, error);
        if (server)
srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
else// If application not initialized log to screen
    printf ("%s\n", log_buffer);
    }
    return(SRV_CONTINUE);
}

// REMOTEMSGS
//     DBLIB calls this routine when any messages are received
//     from the remote DBMS. It gets the remote message information and
//     sends it back to the client as a message.
//
// Parameters:
//     dbproc    - The handler to the remote DBMS process that sent the message.
//     msgno     - The message number.
//     msgstate  - The message state.
//     severity  - The message severity.
//     msgtext   - The text of the message.
//     srvname   - The server name
//     procname  - The stored procedure name (if there was one)
//     line      - The line number of the procedure
//
//     The following three parameters are available in TDS4.0 and above:
//
//     srvname  - The name of the server that sent the message.
//     procname - The procedure name, if any, of the remote DBMS command.
//     line  - The remote DBMS command buffer line to which the msg applies.
//
// Returns:
//     0
//

int remotemsgs(dbproc, msgno, msgstate, severity, msgtext, srvname, procname, line)
DBPROCESS *dbproc;
DBINT msgno;
INT msgstate;
INT severity;
LPCSTR msgtext;
LPCSTR srvname;
LPCSTR procname;
DBUSMALLINT line;
{
    SRV_PROC *srvproc;

    // If a remote DBMS error was received during the remote
    // open, the dbproc is NULL and a message is sent back on the
    // most recent srvproc.
    //
    if (dbproc == (DBPROCESS *)NULL) {
        srvproc = Newsrvproc;
    } else {
        if ((srvproc = (SRV_PROC *)dbgetuserdata(dbproc)) == NULL) {
            //
            // An error was received after the dbproc was assigned, but
            // before we were able to associate our srvproc.
            //
            srvproc = Newsrvproc;
        }
    }
    if (severity < 10) {    // if informational message

        srv_sendmsg(srvproc, SRV_MSG_INFO, msgno, (DBTINYINT)severity,
                    (DBTINYINT)msgstate, NULL, 0, 0, (DBCHAR *)msgtext, SRV_NULLTERM);
    return(0);
    }

// Trap login fail message
if (msgno == REMOTE_FAIL){
        // Send a message to the client that
        // the remote connection failed.
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)msgno, (DBTINYINT)severity,
                    (DBTINYINT)msgstate, NULL, 0, 0,
                    "Login to remote DBMS failed (dbopen).", SRV_NULLTERM);
}

// must be an error message
    srv_sendmsg(srvproc, SRV_MSG_ERROR, msgno, (DBTINYINT)severity,
    (DBTINYINT)msgstate, NULL, 0, 0, (DBCHAR *)msgtext, SRV_NULLTERM);

    return(0);
}

// REMOTEERR
//     This is the handler for error messages from the remote DBMS, in this
//     case DBLIB.  It gets called whenever a DBLIB error occurs.  It takes
//     the error message and passes it back to the client.
//
// Parameters:
//     dbproc - The process handle for the remote DBMS.
//     severity - The severity of the error.
//     dberr - The DBLIB error number.
//     oserr - The operating system error, if any.
//     dberrstr - The text of the DBLIB error.
//     oserrstr - The text of operating system error, if any.
//
// Returns:
//     INT_EXIT to exit the program.
//     INT_CANCEL to cause a FAIL return from the DBLIB routine that got
//     the error.
//
int remoteerr(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS *dbproc;
INT severity;
INT dberr;
INT oserr;
LPCSTR dberrstr;
LPCSTR oserrstr;
{
    SRV_PROC *srvproc = (SRV_PROC *)NULL;

    // If the DBLIB process is dead or we get a DBLIB error SQLESMSG
// ("General SQL Server Error:...") then simply ignore it.  The error
    // message has already been sent to the client.
    //
    if (dberr == SQLESMSG) {
        return(INT_CANCEL);
    }

    //
    // A remote DBMS error may have been issued during the remote
    // open. In this case, the dbproc will be NULL and a message
    // will be sent on the most recent srvproc.
    //
    if (dbproc == (DBPROCESS *)NULL) {
        srvproc = Newsrvproc;
    } else {
        if ((srvproc = (SRV_PROC *)dbgetuserdata(dbproc)) == NULL) {
            // An error was issued after the dbproc was assigned but before
            // we were able to associate our srvproc.
            //
            srvproc = Newsrvproc;
        }
    }

// Trap connection failure error
if (dberr == SQLECONN){
        // Send a message to the client that
        // the remote connection failed.
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_FAIL, (DBTINYINT)severity,
                    (DBTINYINT)0, NULL, 0, 0,
                    "Unable to establish connection to remote DBMS (dbopen).", SRV_NULLTERM);

        return(INT_CANCEL);
}

    //
    // Send error message to client.
    //
    srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_MSG,
                (DBTINYINT)severity, (DBTINYINT)0, NULL, 0, 0,
 (DBCHAR *)dberrstr, SRV_NULLTERM);

    if (oserr != DBNOERR) {
        srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_MSG,
                    (DBTINYINT)severity, (DBTINYINT)0, NULL, 0, 0,
  (DBCHAR *)oserrstr, SRV_NULLTERM);
    }
    return(INT_CANCEL);
}

// ATTN_HANDLER
//     This is an event handler that will be called when the gateway receives
//     an attention from a client.
//
// Parameters:
//     srvproc - Pointer to the client connection structure
//               receiving the attention.
//
// Returns:
//     SRV_CONTINUE
//
int attn_handler(srvproc)
SRV_PROC *srvproc;
{
// Open Data Services NT receives client attention events asynchronously.
  // Being handle_results may be calling dbresults or dbnextrow, we can not
// process the attention with the dbcancel call here. Instead dbcancel
// will be called after the attention has been detected using
// SRV_GOT_ATTENTION.

    return(SRV_CONTINUE);
}

#pragma check_stack()   // set stack checking to its default setting


GATEWAY.C

// This program is an example of an Open Data Services application. It accepts
// requests from clients and then passes those requests on to an SQL server.
// The results from the SQL server are then sent back to the client.

#include    <stdlib.h>
#include    <stdio.h>
#include    <string.h>
#include    <windows.h>
#include    <sqlfront.h>
#include    <sqldb.h>
#include    <srv.h>

// Globals
//
DBCHAR *SrvrName = "";  // Default remote server name
DBCHAR *RegistryName = "Gateway";   // Default registry name

#define EXIT_OK 0
#define EXIT_ERROR 1

// function prototypes
//
void getargs(int argc, char **argv);

void main(int argc, char **argv);

int SRVAPI init_server(SRV_SERVER *server);

void SRVAPI set_remote_server_name(char *name);

int chk_err(SRV_SERVER *server, SRV_PROC *srvproc, int srverror,
            BYTE severity, BYTE state, int oserrnum, DBCHAR *errtext,
            int errtextlen, DBCHAR *oserrtext, int oserrtextlen);

void main(argc, argv)
int argc;
char *argv[];
{
    SRV_CONFIG *config; // The configuration structure
    SRV_SERVER *server; // The service process
    int exitcode = EXIT_ERROR;

    // Read any command line arguments.
    //
    getargs(argc, argv);

    // Send the name retrieved to the gateway's DLL module
    //
    set_remote_server_name(SrvrName);

    // Allocate a configuration structure that is used to initialize
    // the Open Data Services application
    //
    config = srv_config_alloc();

    // Allow 20 connections at a time.
    //
    srv_config(config, (DBINT)SRV_CONNECTIONS, "100", SRV_NULLTERM);

    // Set the log file.
    //
    srv_config(config, (DBINT)SRV_LOGFILE, "gateway.log", SRV_NULLTERM);

    // The gateway will not convert data source strings in order to allow
    // SQL Server and DB-libraray to coordinate on the codepage as usual.
    //
    srv_config(config, (DBINT)SRV_ANSI_CODEPAGE, "TRUE", SRV_NULLTERM);

    // Install the Open Data Services error handler.
    //
    srv_errhandle(chk_err);

    // Initialize the gateway and save the server handle
    // so it can be used in later functions.
    //
    server = srv_init(config, RegistryName, SRV_NULLTERM);
    if (server == NULL) {
        printf("Unable to initialize Gateway.\n");
        ExitProcess(exitcode);
    }

    // When starting the gateway, initialize the remote server structure.
    // This is done in the init_server() function.
    // All the other event handlers are also defined in the init_server()
    // function.
    //
    srv_handle(server, (DBINT)SRV_START, init_server);

    // Now everything's ready to go with our gateway, so we
    // start it and keep it going until we get a stop request.
    //
    srv_log(server, FALSE, " ", SRV_NULLTERM);  // insert blank line
    srv_log(server, TRUE, "Gateway Starting", SRV_NULLTERM);

    printf("\nGateway Starting\n");

    srv_run(server);

}

// GETARGS
//    Read the command line arguments.
//
// Parameters:
//     argc - int (from "main" entry)
//     argv - pointer to array of char pointers (from "main" entry)
//
// Returns:
//     VOID
//
void getargs(int argc, char *argv[])
{

    int i;
    char *ptr;
    int exitcode = EXIT_ERROR;

    for (i = 1; i < argc; ++i) {
        if (argv[i][0] != '-' && argv[i][0] != '/') {
            printf(
            "Usage: gateway -S<remote server name> [-R<registry key name>]\n");

            ExitProcess(exitcode);
        }
        ptr = argv[i];
        switch (ptr[1]) {

        case 'S':
            if (strlen(ptr + 2) > 0) {
                SrvrName = ptr + 2;
            }
            break;

        case 'R':
            if (strlen(ptr + 2) > 0) {
                RegistryName = ptr + 2;
            }
            break;

        default:
            printf(
            "Usage: gateway -S<remote server name> [-R<registry key name>]\n");

            ExitProcess(exitcode);
        }
    }
}


README.TXT

****************************************************************
          MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
            OPEN DATA SERVICES ODBC SAMPLE PROGRAM
****************************************************************
This file describes the Open Data Services ODBC sample program
included with Microsoft(R) SQL Server Workstation System 6.5. The
information in this file supplements the information in your
Microsoft SQL Server Workstation documentation.

****************************************************************
OPEN DATA SERVICES ODBC SAMPLE APPLICATION

The ODSODBC sample gateway driver is described in Appendix E of
"Programming Open Data Services." This sample is located by
default in the C:\MSSQL65\ODS\SAMPLES\ODSODBC directory.

To build the Win32(R) version of this driver, refer to the
instructions in C:\MSSQL\SAMPLES\ODS\README.TXT.

For Intel platforms only, to build the Win16 version of the sample
resource DLL, you need the following:

* Microsoft Visual C++ version 1.50 or later
* Microsoft Windows 3.1 Software Development Kit
* The ODBC Software Development Kit version 2.1 or later

Your LIB and INCLUDE environment variables must point to these
tools. Then from the BUILD directory type the following:

    buildw16

The sample resources DLL included in this directory supports the
Open Data Services GATEWAY sample application as a pass-through
gateway to SQL Server 6.5.


LIBMAIN.C

/*
** LIBMAIN.C - This is the module containing the initialization code for the
** ODS resource DLL.
**
** (C) Copyright 1992-1994 By Microsoft Corp.
*/

#define WINVER 0x030A                // prevent Windows 3.1 feature usage
#include "windows.h"

/* DLLinitialization functions */

#ifdef _WIN32

INT __stdcall LibMain(HANDLE hInst,
  DWORD ul_reason_being_called,
  LPVOID lpReserved)
{
    switch (ul_reason_being_called)
{
case DLL_PROCESS_ATTACH:
// Initilize global variable here !!!
break;
case DLL_THREAD_ATTACH:
break;
    case DLL_PROCESS_DETACH:
break;
    case DLL_THREAD_DETACH:
break;
default:
break;
} /* switch */

    return TRUE;

    UNREFERENCED_PARAMETER(hInst);
    UNREFERENCED_PARAMETER(lpReserved);
} /* LibMain */

#else//_WIN32

int FAR PASCAL libmain(
HANDLE hinst,
short cbHeapSize,
LPSTR lpszCmdLine)
{

if (cbHeapSize != 0)/* if DLL data segment is moveable */
UnlockData(0);

return 1;/* successful initialization */

}

#endif//_WIN32


ODBCSS.H

/*
** ODBCSS.H - This is the application include file for the
** SQL Server driver specific defines.
**
** (C) Copyright 1993-1996 By Microsoft Corp.
**
*/

//SQLSetConnectOption/SQLSetStmtOption driver specific defines.
//Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server driver usage.

//Connection Options
#define SQL_COPT_SS_BASE1200
#define SQL_REMOTE_PWD(SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only
#define SQL_USE_PROCEDURE_FOR_PREPARE(SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare
#define SQL_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login
#define SQL_PRESERVE_CURSORS(SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact
#define SQL_COPT_SS_USER_DATA(SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata
#define SQL_COPT_SS_ANSI_OEM(SQL_COPT_SS_BASE+6) // dbsetopt/DBANSItoOEM/DBOEMtoANSI
#define SQL_COPT_SS_ENLIST_IN_DTC(SQL_COPT_SS_BASE+7) // Enlist in a Viper transaction
#define SQL_COPT_SS_ENLIST_IN_XA(SQL_COPT_SS_BASE+8) // Enlist in a XA transaction
#define SQL_COPT_SS_CONNECTION_DEAD(SQL_COPT_SS_BASE+9) // dbdead SQLGetConnectOption only
#define SQL_COPT_SS_FALLBACK_CONNECT(SQL_COPT_SS_BASE+10) // Enables FallBack connections
#define SQL_COPT_SS_PERF_DATA(SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data
#define SQL_COPT_SS_PERF_DATA_LOG(SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data
#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds.
#define SQL_COPT_SS_PERF_QUERY_LOG(SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies.
#define SQL_COPT_SS_PERF_QUERY(SQL_COPT_SS_BASE+15) // Used to start and stop query logging.
#define SQL_COPT_SS_PERF_DATA_LOG_NOW(SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk.

#define SQL_COPT_SS_MAX_USEDSQL_COPT_SS_PERF_DATA_LOG_NOW

//Statement Options
#define SQL_SOPT_SS_BASE1225
#define SQL_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging
#define SQL_SOPT_SS_CURRENT_COMMAND(SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only
#define SQL_SOPT_SS_MAX_USEDSQL_SOPT_SS_CURRENT_COMMAND

//Defines for use with SQL_USE_PROCEDURE_FOR_PREPARE
#define SQL_UP_OFF0L//Procedures won't be used for prepare
#define SQL_UP_ON1L//Procedures will be used for prepare
#define SQL_UP_ON_DROP2L//Temp procedures will be explicitly dropped
#define SQL_UP_DEFAULTSQL_UP_ON

//Defines for use with SQL_INTEGRATED_SECURITY - only useable before connecting
#define SQL_IS_OFF0L//Integrated security isn't used
#define SQL_IS_ON1L//Integrated security is used
#define SQL_IS_DEFAULTSQL_IS_OFF

//Defines for use with SQL_PRESERVE_CURSORS
#define SQL_PC_OFF0L//Cursors are closed on SQLTransact
#define SQL_PC_ON1L//Cursors remain open on SQLTransact
#define SQL_PC_DEFAULTSQL_PC_OFF

//Defines for use with SQL_COPT_SS_USER_DATA
#define SQL_UD_NOTSET0L//No user data pointer set

//Defines for use with SSQL_COPT_SS_ANSI_OEMQL_ANSI_OEM
#define SQL_AO_OFF0L//ANSI/OEM translation is not performed
#define SQL_AO_ON1L//ANSI/OEM translation is performed
#define SQL_AO_DEFAULTSQL_AO_OFF//Default unless DSN OEM/ANSI checkbox is checked

//Defines for use with SQL_COPT_SS_CONNECTION_DEAD
#define SQL_CD_FALSE0L//Connection is open/available
#define SQL_CD_TRUE1L//Connection is closed/dead

//Defines for use with SQL_COPT_SS_FALLBACK_CONNECT
#define SQL_FB_OFF0L//FallBack connections are disabled
#define SQL_FB_ON1L//FallBack connections are enabled
#define SQL_FB_DEFAULTSQL_FB_OFF

//Defines for use with SQL_ENLIST_IN_DTC
#define SQL_DTC_DONE0L//Delimits end of Viper transaction

//Defines for use with SQL_TEXTPTR_LOGGING
#define SQL_TL_OFF0L//No logging on text pointer ops
#define SQL_TL_ON1L//Logging occurs on text pointer ops
#define SQL_TL_DEFAULTSQL_TL_ON

//SQLColAttributes driver specific defines.
//SQLSet/GetDescField driver specific defines.
//Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server driver usage.

#define SQL_CA_SS_BASE1200
#define SQL_CA_SS_COLUMN_SSTYPE(SQL_CA_SS_BASE+0)//dbcoltype/dbaltcoltype
#define SQL_CA_SS_COLUMN_UTYPE(SQL_CA_SS_BASE+1)//dbcolutype/dbaltcolutype
#define SQL_CA_SS_NUM_ORDERS(SQL_CA_SS_BASE+2)//dbnumorders
#define SQL_CA_SS_COLUMN_ORDER(SQL_CA_SS_BASE+3)//dbcolorder
#define SQL_CA_SS_COLUMN_VARYLEN(SQL_CA_SS_BASE+4)//dbvarylen
#define SQL_CA_SS_NUM_COMPUTES(SQL_CA_SS_BASE+5)//dbnumcompute
#define SQL_CA_SS_COMPUTE_ID(SQL_CA_SS_BASE+6)//dbnextrow status return
#define SQL_CA_SS_COMPUTE_BYLIST(SQL_CA_SS_BASE+7)//dbbylist
#define SQL_CA_SS_COLUMN_ID(SQL_CA_SS_BASE+8)//dbaltcolid
#define SQL_CA_SS_COLUMN_OP(SQL_CA_SS_BASE+9)//dbaltcolop
#define SQL_CA_SS_MAX_USEDSQL_CA_SS_COLUMN_OP

// SQL Server Data Type Tokens. Returned by SQLColAttributes/SQL_CA_SS_COLUMN_SSTYPE.
#define SQLTEXT 0x23
#define SQLVARBINARY0x25
#define SQLINTN 0x26
#define SQLVARCHAR0x27
#define SQLBINARY0x2d
#define SQLIMAGE0x22
#define SQLCHARACTER0x2f
#define SQLINT1 0x30
#define SQLBIT0x32
#define SQLINT2 0x34
#define SQLINT4 0x38
#define SQLMONEY0x3c
#define SQLDATETIME 0x3d
#define SQLFLT8 0x3e
#define SQLFLTN 0x6d
#define SQLMONEYN0x6e
#define SQLDATETIMN 0x6f
#define SQLFLT4 0x3b
#define SQLMONEY40x7a
#define SQLDATETIM4 0x3a
#define SQLDECIMAL0x37
#define SQLDECIMALN0x6a
#define SQLNUMERIC0x3f
#define SQLNUMERICN 0x6c

//User Data Type definitions. Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE.
#define SQLudtTEXT19
#define SQLudtVARBINARY 4
#define SQLudtINTN13
#define SQLudtVARCHAR2
#define SQLudtBINARY3
#define SQLudtIMAGE 20
#define SQLudtCHAR1
#define SQLudtINT15
#define SQLudtBIT16
#define SQLudtINT26
#define SQLudtINT47
#define SQLudtMONEY 11
#define SQLudtDATETIME12
#define SQLudtFLT88
#define SQLudtFLTN14
#define SQLudtMONEYN17
#define SQLudtDATETIMN15
#define SQLudtSYSNAME18
#define SQLudtTIMESTAMP 80
#define SQLudtFLT423
#define SQLudtMONEY421
#define SQLudtDATETIM422
#define SQLudtDECML24
#define SQLudtDECMLN26
#define SQLudtNUM10
#define SQLudtNUMN25
#define MIN_USER_DATATYPE100

// aggregate operator types (returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP)
#define SQLAOPCNT0x4b
#define SQLAOPSUM0x4d
#define SQLAOPAVG0x4f
#define SQLAOPMIN0x51
#define SQLAOPMAX0x52
#define SQLAOPANY0x53
#define SQLAOPNOOP0x56

//SQLGetInfo driver specific defines.
//Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server driver usage.

#define SQL_INFO_SS_FIRST1200
#define SQL_INFO_SS_NETLIB_NAME (SQL_INFO_SS_FIRST+0) //  dbprocinfo
#define SQL_INFO_SS_MAX_USEDSQL_INFO_SS_NETLIB_NAME

//Internal server datatypes - used when binding to SQL_C_BINARY
#ifndef MAXNUMERICLEN// Resolve ODS/DBLib conflicts
typedef struct dbmoney
{// Internal representation of MONEY data type
LONG  mnyhigh;// Money value *10,000 (High 32 bits/signed)
ULONG mnylow;// Money value *10,000 (Low 32 bits/unsigned)
} DBMONEY;

typedef struct dbdatetime
{// Internal representation of DATETIME data type
LONG  dtdays;// No of days since Jan-1-1900 (maybe negative)
ULONG dttime;// No. of 300 hundredths of a second since midnight
} DBDATETIME;

typedef struct dbdatetime4
{// Internal representation of SMALLDATETIME data type
USHORT numdays; // No of days since Jan-1-1900
USHORT nummins; // No. of minutes since midnight
} DBDATETIM4;

typedef LONG DBMONEY4;// Internal representation of SMALLMONEY data type
// Money value *10,000

#define MAXNUMERICLEN 16

#define DBNUM_PREC_TYPE BYTE
#define DBNUM_SCALE_TYPE BYTE
#define DBNUM_VAL_TYPE BYTE

typedef struct dbnumeric
{// Internal representation of NUMERIC data type
DBNUM_PREC_TYPE   precision;// Precision
DBNUM_SCALE_TYPE  scale;// Scale
BYTE  sign; // Sign (1 if positive, 0 if negative)
DBNUM_VAL_TYPE  val[MAXNUMERICLEN];// Value
} DBNUMERIC;

typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type
#endif //MAXNUMERICLEN

/*****************************************************************
 This struct is a global used for
 gathering statistal data on the driver.
 Access to this struct is controled via the
 pStatCrit;
******************************************************************/

typedef struct sqlperf
{
// Application Profile Statistics
DWORD TimerResolution;
DWORD SQLidu;
DWORD SQLiduRows;
DWORD SQLSelects;
DWORD SQLSelectRows;
DWORD Transactions;
DWORD SQLPrepares;
DWORD ExecDirects;
DWORD SQLExecutes;
DWORD CursorOpens;
DWORD CursorSize;
DWORD CursorUsed;
LDOUBLE PercentCursorUsed;
LDOUBLE AvgFetchTime;
LDOUBLE AvgCursorSize;
LDOUBLE AvgCursorUsed;
DWORD SQLFetchTime;
DWORD SQLFetchCount;
DWORD CurrentStmtCount;
DWORD MaxOpenStmt;
DWORD SumOpenStmt;

// Connection Statistics
DWORD CurrentConnectionCount;
DWORD MaxConnectionsOpened;
DWORD SumConnectionsOpened;
DWORD SumConnectiontime;
LDOUBLE AvgTimeOpened;

// Network Statistics
DWORD ServerRndTrips;
DWORD BuffersSent;
DWORD BuffersRec;
DWORD BytesSent;
DWORD BytesRec;

// Time Statistics;
DWORD msExecutionTime;
DWORD msNetWorkServerTime;

} SQLPERF;

// The following are option for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY
#define SQL_PERF_START1// Starts the driver sampling performance data.
#define SQL_PERF_STOP2// Stops the counters from sampling performance data.

//The following options have been deprecated

#define SQL_FAST_CONNECT(SQL_COPT_SS_BASE+0)
//Defines for use with SQL_FAST_CONNECT - only useable before connecting
#define SQL_FC_OFF0L//Fast connect is off
#define SQL_FC_ON1L//Fast connect is on
#define SQL_FC_DEFAULTSQL_FC_OFF

/*

The following data struct is used as the vParam with
SQL_COPT_SS_ENLIST_IN_XA to enlist in a XA stansaction
when calling SQLSetStmtAttribute();

*/
typedef struct SQLXaTranTAG
{
    void FAR *transManager;
    void FAR *xaTransID;
    ULONGxaTransIDLength;
    DWORD   dwErrorInfo;
} SQLXaTran;

//End of odbcss.h


ODBCVER.H

/*--------------------------------------------------------------------------
  ODBCVer.h -- ODBC Version info
--------------------------------------------------------------------------*/

// Includes ----------------------------------------------------------------
#ifndef WIN32
#include  <ver.h>                         // Version defines
#endif

// Constants ---------------------------------------------------------------
#define VER_FILEVERSION 02,65,02,01
#define VER_FILEVERSION_STR " 2.65.0201\0"
#define VER_PRODUCTVERSION 02,65,02,01
#define VER_PRODUCTVERSION_STR " 2.65.0201\0"

#define VER_FILEFLAGSMASK (VS_FF_DEBUG | VS_FF_PRERELEASE)
#ifdef DEBUG
#define VER_FILEFLAGS (VS_FF_DEBUG)
#else
#define VER_FILEFLAGS (0)
#endif

#ifdef WIN32
#defineVER_FILEOSVOS_NT_WINDOWS32
#else
#define VER_FILEOS  VOS_DOS_WINDOWS16
#endif

#define VER_COMPANYNAME_STR     "Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR "Microsoft SQL Server\0"
#define VER_LEGALCOPYRIGHT_STR"Copyright \251 Microsoft Corporation 1990-1996\0"


ODSRC.H

/*
** ODSRC.H - This is the resource include for the ODS driver resource DLLs.
**
** (C) Copyright 1992-1995 By Microsoft Corp.
**
**These definitions are common between the ODS driver and the ODS resource
**DLL and cannot be changed without both being re-built.
*/

#define IDS_DATATYPE_OFFSET1000
#define IDS_INITIALIZATION_OFFSET1050
#define IDS_INFO_OFFSET 1100
#if (ODBCVER >= 0x0300)
#define SQL_OJ_CAPABILITIES_OLD 65003
#define SQL_OJ_CAPABILITIES_NEW SQL_OJ_CAPABILITIES
#else
#define SQL_OJ_CAPABILITIES_OLD SQL_OJ_CAPABILITIES
#define SQL_OJ_CAPABILITIES_NEW 115
#endif
#define IDS_AUTOCOMMIT_OFFSET350
#define IDS_ACCESS_MODE_OFFSET352
#define IDS_AUX_SETROWCOUNT 359
#define IDS_BEGINTRAN361
#define IDS_AUX_SETTEXTSIZE 362
#define IDS_AUX_USE 389
#define IDS_AUX_RPC_TEXT411
#define IDS_TRANSACT_OFFSET 413
#define IDS_AUX_FORMAT_TS424
#define IDS_LANGUAGE_QUERY459
#define IDS_DATABASE_QUERY461
#define IDS_AUX_LANGUAGE462
#define IDS_DEF_LANGUAGE_QUERY464
#define IDS_DEF_DATABASE_QUERY466
#define IDS_ISOLATION_OFFSET477
#define IDS_AUX_RPC_SEP484

#define IDS_AUX_KEYWORDS540
//Range from IDS_AUX_KEYWORDS to IDS_AUX_KEYWORDS+8 is reserved
//#define IDS_AUX_KEYWORDS+8548

//RCDATA defines
/* CAUTION: The following seven defines must be in sequence. */
#define IDB_AUX_ECODE_PARSE_DATE1
#define IDB_AUX_ECODE_PARSE_TIME2
#define IDB_AUX_ECODE_PARSE_TS3
//#define IDB_AUX_ECODE_PARSE_FUNC4
#define IDB_AUX_ECODE_PARSE_OJ5
//#define IDB_AUX_ECODE_PARSE_CALL6
//#define IDB_AUX_ECODE_PARSE_ESC7
/* END OF ORDER*/

//Binary String IDs from IDB_CONVERT to IDB_IFNULL are reserved
#define IDB_CONVERT101
#define IDB_CONCAT102
#define IDB_DIFFERENCE103
#define IDB_INSERT104
#define IDB_LEFT105
#define IDB_LTRIM106
#define IDB_LENGTH107
#define IDB_LOCATE108
#define IDB_LCASE109
#define IDB_REPEAT110
#define IDB_REPLACE111
#define IDB_RIGHT112
#define IDB_RTRIM113
#define IDB_SOUNDEX114
#define IDB_SPACE115
#define IDB_SUBSTRING116
#define IDB_UCASE117
#define IDB_ASCII118
#define IDB_CHAR119
#define IDB_ABS120
#define IDB_ACOS121
#define IDB_ASIN122
#define IDB_ATAN123
#define IDB_ATAN2124
#define IDB_CEILING125
#define IDB_COS126
#define IDB_COT127
#define IDB_DEGREES128
#define IDB_EXP129
#define IDB_FLOOR130
#define IDB_LOG131
#define IDB_LOG10132
#define IDB_MOD133
#define IDB_PI134
#define IDB_POWER135
#define IDB_RADIANS136
#define IDB_RAND137
#define IDB_ROUND138
#define IDB_SIGN139
#define IDB_SIN140
#define IDB_SQRT141
#define IDB_TAN142
#define IDB_TRUNCATE143
#define IDB_NOW144
#define IDB_TIMESTAMPADD145
#define IDB_TIMESTAMPDIFF146
#define IDB_CURDATE147
#define IDB_DAYOFMONTH148
#define IDB_DAYOFWEEK149
#define IDB_DAYOFYEAR150
#define IDB_DAYNAME151
#define IDB_MONTH152
#define IDB_MONTHNAME153
#define IDB_QUARTER154
#define IDB_WEEK155
#define IDB_YEAR156
#define IDB_CURTIME157
#define IDB_HOUR158
#define IDB_MINUTE159
#define IDB_SECOND160
#define IDB_USER161
#define IDB_DATABASE162
#define IDB_IFNULL163

#define IDS_FEATURE_FMTONLY_SUPPORT150
#define IDS_FEATURE_NOEXEC_SUPPORT151
#define IDS_FEATURE_HOLDLOCK_SUPPORT152
#define IDS_FEATURE_EXEC_THRU_PROC153
#define IDS_FEATURE_STRIP_SEPARATORS154
#define IDS_FEATURE_NOTNULL_DEFAULT 155
#define IDS_FEATURE_RPC_SUPPORT156
#define IDS_FEATURE_TEXTPTR_SUPPORT157
#define IDS_FEATURE_TSQL_OJ_SUPPORT 158
#define IDS_FEATURE_KEEP_PARM_MARKERS159
#define IDS_FEATURE_KEEP_AS_ALIAS160
#define IDS_FEATURE_KEEP_ASC_INDEX161
#define IDS_FEATURE_SP_CURSOR_SUPPORT162
#define IDS_FEATURE_DECNUM_SUPPORT163
#define IDS_FEATURE_UDT_ODS_FORMAT164

#defineIDS_HOSTDBMS180
#define IDS_GTWYNAME181
#define IDS_SPROC_VERSION182
#define IDS_HELP_FILENAME183

/* CAUTION: The following three defines must be in sequence. */
#define IDS_AUX_ECODE_TEMPLATE_DATE 401
#define IDS_AUX_ECODE_TEMPLATE_TIME 402
#define IDS_AUX_ECODE_TEMPLATE_TS403
/* END OF ORDER*/

/* CAUTION: The following three defines must be in sequence. */
#define IDS_AUX_ECODE_FORMAT_DATE456
#define IDS_AUX_ECODE_FORMAT_TIME457
#define IDS_AUX_ECODE_FORMAT_TS 458
/* END OF ORDER*/

//String IDs from IDS_LOCAL_OFFSET to
//IDS_LOCAL_OFFSET+999 are reserved for localized strings.
//All strings to be localized must be in this range
#define IDS_LOCAL_OFFSET40000U

//All localized strings must maintain the same string id forever.
//Never re-use a localized string ID or you will screw up Expresso.

#define IDS_LOGIN_DIALOG_CAPTION40001U
#define IDS_LOCAL_TABLE_TERM40002U
#define IDS_LOCAL_OWNER_TERM40003U
#define IDS_LOCAL_QUALIFIER_TERM40004U
#define IDS_LOCAL_PROCEDURE_TERM40005U

//Localized string unique to ODS Gateway driver
#define IDS_LOGIN_DIALOG_USERTERM40700U
#define IDS_LOGIN_DIALOG_PASSWORDTERM40701U
#define IDS_LOGIN_DIALOG_DATABASETERM40702U
#define IDS_LOGIN_DIALOG_LANGUAGETERM40703U


PROCSRV.C

//
// This program is an example of an Open Data Services application. It accepts
// requests from clients to execute stored procedures either as language
// events or as remote stored procedure calls.
// It may also be invoked using the Service Control Manager.
//

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <ctype.h>
#include <process.h>
#include <srv.h>

#define VERSION "6.00.01"

// Globals
//
BOOL         SrvCtrMan;
HANDLE       hServerDone;
SRV_SERVER * gblServer = NULL;
DBCHAR     * szRegistryName = "PROCSRV";  // Default registry name

SERVICE_STATUS_HANDLE   NTBServiceHandle;
CRITICAL_SECTION        SCMCrtSec;
SERVICE_STATUS          NTBServiceStatus;

// Syscharsets query from DBLIB 4.2 NT clients
//
#define SERVER_INFO_QUERY   "exec sp_server_info 18"

// Define some user message codes.
//
#define     SRV_MAXERROR           20000
#define     SP_UNKNOWN             SRV_MAXERROR + 1
#define     INVALID_SP_SYNTAX      SRV_MAXERROR + 2
#define     BAD_SP_PARAMETER       SRV_MAXERROR + 3
#define     BROADCAST              SRV_MAXERROR + 4
#define     EXEC                   SRV_MAXERROR + 5

// Miscellaneous defines used by sp-handling routines.
//
#define EXEC_CMD            "exec"
#define MAXNAME             31
#define MAXLEN              80
#define MAXPARAMS           4
#define VALUE               0x0000
#define REFERENCE           0x0001
#define CMDSTR              7
#define BUF_SIZE            2048
#define XBASE_HDR_SIZE      32
#define XBASE_MAX_COLUMNS   128

// Standard error macro for reporting API errors
//
#define SETERROR( api, retstring )                          \
    sprintf(retstring,"%s: Error %d from %s on line %d\n",  \
            __FILE__, GetLastError(), api, __LINE__);

// Event handlers for SP requests.
//
SRVRETCODE proclist();         // List SP associated with Server
SRVRETCODE sp_exec();          // Execute the specified command string
SRVRETCODE diskfree();         // Return the space on a given drive
SRVRETCODE disklist();         // List available drives and their space
SRVRETCODE scan_xbase();       // Open and read an xBase file

// Stored Procedure parameter information structure.
//
typedef struct sp_params {
    DBCHAR name[MAXNAME];           // Parameter name
    int    type;                    // Parameter data type
    DBINT  length;                  // Parameter type length
    int    status;                  // Parameter return status
    DBCHAR defaultvalue[MAXLEN *10];// Optional default value
} SP_PARAMS;

// Stored Procedure information structure.
//
typedef struct sp_info {
    DBCHAR      name[MAXNAME];          // Procedure name
    DBCHAR      usage[MAXLEN];          // Usage string
    int         numparams;              // Number of parameters
    SP_PARAMS   params[MAXPARAMS];      // Parameter array
    SRVRETCODE   (*handler)(VOID *, ...);  // Pointer to function with variable arguments
} SP_INFO;

// Array of Stored Procedure handlers.
//
SP_INFO Sps[] =
{
    "proclist",
    "usage: proclist",
    0,
       {
        0
       },
    proclist,

    "sp_exec",              // Procedure name
    "usage: sp_exec <[@command =] command string>", // Procedure usage
    1,                      // Number of parameters
   {    // Parameter definitions
        "command",          // Parameter name
        SRVCHAR,            // Parameter type
        MAXLEN,             // Parameter length (0 if fixed)
        VALUE,              // Pass by value
        "dir *.*",          // Default parameter value
    },
    sp_exec,                // Procedure function pointer

    "disklist",
    "usage: disklist ",
    0,
   {
        0
    },
    disklist,

    "diskfree",
    "usage: diskfree <[@drive =] drive letter> [,] <[@space =] free space>",
    2,
   {
        "drive",
        SRVCHAR,
        1,
        VALUE,              // pass by value
        "c",

        "space",
        SRVINT4,
        8,
        REFERENCE,          // pass by reference
        "0",
    },
    diskfree,

    "scan_xbase",               // rpc name
    "usage: scan_xbase <[@file_name =] xbase file name>",   // rpc usage
    1,                          // number of parameters
   {   // parameter definitions
        "file_name",            // parameter name
        SRVCHAR,                // parameter type
        MAXLEN,                 // parameter length (0 if fixed)
        VALUE,                  // pass by value
        "\\sql\\opends\\samples\\procsrv\\build\\sales.dbf", // default parameter value
    },
    scan_xbase,                 // rpc function pointer

};

#define Rpcnumber sizeof(Sps) / sizeof(SP_INFO)

// Other function prototypes
//
void main( int argc, char **argv );
void WINAPI ProcSrvMain( DWORD argc, char *argv[] );
void initsignal( SRV_SERVER *, char * );
void ctrlc_hndl( ULONG );
void WINAPI NTBServiceCtrlHandler( DWORD );
void completesignal( SRV_SERVER * );
void NTBShutdown( LPVOID );
char * get_last_error_str();

SRVRETCODE chk_err( SRV_SERVER * server,
                 SRV_PROC   * srvproc,
                 int          srverror,
                 BYTE         severity,
                 BYTE         state,
                 int          oserrnum,
                 DBCHAR     * errtext,
                 int          errtextlen,
                 DBCHAR     * oserrtext,
                 int          oserrtextlen );

SRVRETCODE init_remote(SRV_PROC * srvproc);

SRVRETCODE init_server(SRV_SERVER * server);

SRVRETCODE sp_execute(SRV_PROC * srvproc);

SRVRETCODE exit_remote(SRV_PROC *srvproc);

SRVRETCODE lang_execute(SRV_PROC *srvproc);

// Miscellaneous prototypes for sp language events
//
SRVRETCODE lang_parser(SRV_PROC *srvproc, SP_INFO **sp,
                    DBCHAR paramvalues[MAXPARAMS][MAXLEN]);

DBCHAR *scan_next(DBCHAR *string, DBCHAR *word);

// =================================== main ==================================
//
void main( int argc, char *argv[] )
{
    SERVICE_TABLE_ENTRY DispatchTable[] =
    {
        "ProcSrv", ProcSrvMain,
        NULL,      NULL
    };

    // Assume ProcSrv.exe was started from the Service Control Manager or
    // SQL Service Manager.
    //
    SrvCtrMan = TRUE;

    // Check for command line arguments.  The following command line arguments
    // are supported:
    //
    //    -c  Procsrv was started from the command line.  Avoids the timeout
    //        delay when an attempt is made to start procsrv as a service.
    //
    //    -r<registry key name>
    //        Look in Registry under the <registry key name> for the ListenOn
    //        values.  Also assumes procsrv.exe was started from command line.
    //
    //    -?  Displays the command usage information.
    //
    if( argc > 2 )
        goto Usage;

    if( argc == 2 )
    {
        if( !strcmp(argv[1], "-?") )
            goto Usage;

        if( !stricmp(argv[1], "-c") )
            SrvCtrMan = FALSE;
        else if( strlen(argv[1]) < 3 )
            goto Usage;
        else if( !strnicmp(argv[1], "-r", 2) )
        {
            szRegistryName = argv[1] + 2;
            SrvCtrMan = FALSE;
        }
        else
            goto Usage;
    }

    if( SrvCtrMan )
    {
        // Now we will attempt to start the ProcSrv.exe as a service.  The attempt
        // will time out if this process was started from the command line.
        // StartServiceCtrlDispatcher does not return until after ProcSrv has stopped.
        // The ProcSrvMain is called from Service Control Manager within
        // the context of the same process.
        //
        if( StartServiceCtrlDispatcher(DispatchTable) )
        {
            _flushall();
            return;
        }
    }

    // The service was started from the command line or the attempt to
    // start the service failed.  We can assume that this process
    // was started from the command line.
    //
    SrvCtrMan = FALSE;
    ProcSrvMain( argc, argv );
    return;

Usage:
    printf( "Usage: procsrv [-c] | [-r<registry key name>] | [-?]\n"
            "    -c  Procsrv was started from the command line\n"
            "    -r  Look in Registry under <registry key name> for ListenOn values\n"
            "    -?  Displays this help message\n" );
}

// ================================== ProcSrvMain ============================
//
void WINAPI ProcSrvMain( DWORD argc, char *argv[] )
{
    SRV_CONFIG * config;        // The configuration structure
    DWORD        dwPathLength;
    char         szPath[1024];
    char         szLogBuffer[1024];

    if( !SrvCtrMan )
    {
        printf( "\nProcedure Server, Copyright 1994, Microsoft\n" );
        printf( "              version: %s\n\n", VERSION );
    }

    // Allocate a configuration structure that is used to initialize
    // the Open Data Services application
    //
    config = srv_config_alloc();

    // Allow 20 connections at a time.
    //
    srv_config(config, (DBINT)SRV_CONNECTIONS, "20", SRV_NULLTERM);

    // Set the log file.
    //
    // Get the path of this process.  We'll use it to constuct the path of the
    // log file.
    //
    szPath[0] = '\0';
    dwPathLength = GetModuleFileName( GetModuleHandle(NULL),
                                      szPath,
                                      sizeof(szPath) );

    // Stip off process name (i.e. "ProcSrv.exe")
    //
    while( dwPathLength > 1 )
    {
        --dwPathLength;
        if( szPath[dwPathLength] == '\\' || szPath[dwPathLength] == ':' )
        {
            dwPathLength++;
            szPath[dwPathLength] = '\0';    // Null terminate after back slash
            break;
        }
    }

    // Append "<registryname>.log" to path
    //
    strcat( szPath, szRegistryName );
    strcat( szPath, ".log" );

    srv_config(config, (DBINT)SRV_LOGFILE, szPath, SRV_NULLTERM);

    // All data source strings will be converted from ANSI to the OEM codepage
    // in order to make this application behave like SQL Server.
    //
    srv_config(config, (DBINT)SRV_ANSI_CODEPAGE, "FALSE", SRV_NULLTERM);

    // Install the error handler.
    //
    srv_errhandle(chk_err);

    //  Initialize Procedure Server and save the server handle
    //  so it can be used in later functions.
    //
    gblServer = srv_init(config, szRegistryName, SRV_NULLTERM);

    if( gblServer == NULL )
    {
        printf( "\nUnable to initialize Procedure Server.  "
                "Check Event Log.\n" );
        goto Exit;
    }

    // Create an event flag that will tell us when ProcSrv is completely
    // shut down (srv_run() has returned)
    //
    hServerDone = CreateEvent( NULL, TRUE, FALSE, NULL );
    if( hServerDone == NULL )
    {
        sprintf( szLogBuffer,
                 "Procedure Server Service Manager Failer: %s "
                 "(ProcSrvMain(), line = %d), message = %s",
                 szRegistryName,
                 __LINE__,
                 get_last_error_str() );
        srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );
        printf( "\n%s\n", szLogBuffer );
        goto Exit;
    }

    // When starting Procedure Server, initialize the remote server structure.
    // This is done in the init_server() function.
    // All the other event handlers are also defined in the init_server()
    // function.
    //
    srv_handle( gblServer, (DBINT)SRV_START, init_server );

    sprintf( szLogBuffer,
             "Procedure Server Starting, name = %s",
             szRegistryName );

    //  Now everything's ready to go with Procedure Server, so we
    //  start it and keep it going until we get a stop request.
    //
    srv_log( gblServer, FALSE, " ", SRV_NULLTERM );    // insert blank line
    srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );

    // initsignal() notifies the Service Control Manager that the
    // service has been started and sets up the signal handlers.
    //
    initsignal( gblServer, argv[0] );

    // completesignal() notifies the Service Control Manager that the
    // service has completed its startup process.
    //
    completesignal( gblServer );

    // srv_run() does not return until either a FAILure occurs or a SRV_EXIT
    // event has been issued.
    //
    if( srv_run(gblServer) == FAIL )
    {
        printf( "\nProcedure Server Failer, Check logs.\n" );
        goto Exit;
    }

    // Set flag indicating all processing completed
    //
    SetEvent( hServerDone );
    return;

Exit:
    // initsignal() notifies the Service Control Manager that the
    // service has been started and sets up the signal handlers.
    // This must be done even though we have an "error exit" condition.
    //
    initsignal( gblServer, argv[0] );

    // completesignal() notifies the Service Control Manager that the
    // service has completed its startup process.
    //
    completesignal( gblServer );

    // NTBShutdown() sets the SQL Service Manager to "Stop" and terminates
    // the service.
    //
    SetThreadPriority( (HANDLE)_beginthread(NTBShutdown, 0, NULL),
                       THREAD_PRIORITY_HIGHEST );

    // Set flag indicating all processing completed
    //
    SetEvent( hServerDone );
}

// This section defines all the Open Data Services event handler functions for
// the Procedure Server application. The procedures implemented are:
//
//
//  PROCLIST        Returns all the supported procedures and their usuage.
//
//  SP_EXEC         Executes a command string and returns output as a rows
//                  of text.
//
//  DISKFREE        Returns the amount the amount of available space for a given
//                  drive.
//
//  DISKLIST        Returns a row for each defined drive containing its name
//                  and the amount of disk space available.
//
// SCAN_XBASE       Reads an xBase file and sends it to the client as if it
//                  were a SQL Server query result set (the equivalent of a
//                  'SELECT * FROM tablename' SQL statement).
//
//
// ================================== init_server ============================
//
// INIT_SERVER
//    Initialize the server on a SRV_START event.
//    Event handlers for the server are installed.
//
// Parameters:
//    server - Pointer to SRV_SERVER structure
//
// Returns:
//    SRV_CONTINUE
//
SRVRETCODE init_server( SRV_SERVER *server )
{
    char log_buffer[256];

    //  When we get a connection request from a client, we want to
    //  call "init_remote()" to make a connection to the remote
    //  server.
    //
    srv_handle(server, (DBINT)SRV_CONNECT, init_remote);

    // When the client issues a language request, call
    // "lang_execute()" to send the SQL statement to the remote DBMS.
    //
    srv_handle(server, (DBINT)SRV_LANGUAGE, lang_execute);

    // When the client issues an RSP, call "sp_execute()"
    // to send the RSP to the remote DBMS (the SQL Server).
    //
    srv_handle(server, (DBINT)SRV_RPC, sp_execute);

    // When a disconnect request is issued, call "exit_remote()"
    // to close the connection to the remote DBMS.
    //
    srv_handle(server, (DBINT)SRV_DISCONNECT, exit_remote);

    // Log Server information to log file
    //
    sprintf(log_buffer, "Client connections allowed = %s",
            srv_sfield(server, SRV_CONNECTIONS, (int *)NULL));

    srv_log(server, FALSE, log_buffer, SRV_NULLTERM);
    printf("%s\n", log_buffer);

    return SRV_CONTINUE;
}

// ================================== init_remote ============================
//
// INIT_REMOTE
//    Event handler for a SRV_CONNECT event.
//    A connection is made to the procedure server.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    SRV_CONTINUE
//
// Side Effects:
//    If the connection to the remote dbms cannot be made, then issue
//    a SRV_DISCONNECT request.
//
//
SRVRETCODE init_remote( SRV_PROC *srvproc )
{
    char *string;
    int   len;

    // Set server name
    //
    srvproc->serverlen = (BYTE)strlen(szRegistryName);
    srvproc->servername = srv_alloc((DBINT)srvproc->serverlen);
    strcpy(srvproc->servername, szRegistryName);

    // Display info on console
    //
    string = srv_pfield(srvproc, SRV_CPID, &len);
    string[len] = '\0';
    printf("\nClient process ID: %s\n", string);

    string = srv_pfield(srvproc,    SRV_USER, &len);
    string[len] = '\0';
    printf("User name: %s\n", string);

    string = srv_pfield(srvproc, SRV_APPLNAME, &len);
    string[len] = '\0';
    if (len > 0)
        printf("Application program name: %s\n", string);

    string = srv_pfield(srvproc, SRV_RMTSERVER, &len);
    string[len] = '\0';
    if (len > 0)
        printf("Remote Server: %s\n", string);

    return SRV_CONTINUE;
}

// ================================ lang_execute =============================
//
// LANG_EXECUTE
//    Execute a client language request on the procedure server.
//
// Parameters:
//    srvproc - process handle to the current client connection.
//
// Returns:
//    SRV_CONTINUE
//
SRVRETCODE lang_execute( SRV_PROC *srvproc )
{
    int      i;
    DBCHAR   paramvalues[MAXPARAMS][MAXLEN];
    BYTE     convertvalues[MAXPARAMS][MAXLEN];
    SP_INFO *sp = NULL;

    // Initialize parameter storage
    //
    for (i = 0; i < MAXPARAMS; i++) {
        memset(paramvalues[i], 0, MAXLEN);
        memset(convertvalues[i], 0, MAXLEN);
    }
    if (lang_parser(srvproc, &sp, paramvalues) == SUCCEED) {
        for (i = 0; i < sp->numparams; i++) {
            if (sp->params[i].status == REFERENCE) {
                srv_sendstatus(srvproc, 1);
                srv_sendmsg(srvproc, SRV_MSG_ERROR, INVALID_SP_SYNTAX,
                            SRV_INFO, (DBTINYINT)0, NULL, 0, 0,
                                    "Procedure contains a return parameter.\
                            Unable to execute as a language event.",
                             SRV_NULLTERM);
                srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0,
                             0);
                return SRV_CONTINUE;

            }
            if (strlen(paramvalues[i]) == 0 &&
                strlen(sp->params[i].defaultvalue) == 0) {
                srv_sendstatus(srvproc, 1);
                srv_sendmsg(srvproc, SRV_MSG_ERROR, INVALID_SP_SYNTAX,
                            SRV_INFO, (DBTINYINT)0, NULL, 0, 0, sp->usage,
                            SRV_NULLTERM);
                srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0,
                             0);
                return SRV_CONTINUE;
            }
            if (strlen(paramvalues[i]) == 0 &&
                strlen(sp->params[i].defaultvalue) != 0)
                strcpy(paramvalues[i], sp->params[i].defaultvalue);

            // convert parameters from character string to parmeter type
            //
            srv_convert(srvproc, SRVCHAR, paramvalues[i], -1,
                        sp->params[i].type, &convertvalues[i],
                        sp->params[i].length);
        }

        // Execute the procedure
        //
        (sp->handler)((VOID *)srvproc, &convertvalues[0], &convertvalues[1],
                      &convertvalues[2], &convertvalues[3]);
    }
    return SRV_CONTINUE;
}

// ================================= lang_parser =============================
//
// LANG_PARSER
//    A procedure server specific language event parser.
//
// Parameters:
//    srvproc - process handle to the current client connection.
//    sp - Pointer to the stored procedure structure
//    paramvalues - An array of the values of the parameters.
//
// Returns:
//    SUCCEED
//
SRVRETCODE lang_parser( SRV_PROC *srvproc,
                     SP_INFO **sp,
                     DBCHAR    paramvalues[MAXPARAMS][MAXLEN] )
{
    DBCHAR *query;      // pointer to language buffer
    int     i;
    int     numparams;
    DBCHAR  msg[MAXLEN *5];
    DBINT   msgnum;
    DBCHAR  spname[MAXLEN];
    BOOL    paramsyntax = FALSE;
    DBCHAR  paramname[MAXLEN];
    DBCHAR  equalstring[2];
    DBCHAR *paramvalue = NULL;

    query = srv_langptr(srvproc);

    // Ignore the syscharsets query from DBLIB 4.2 NT clients
    //
    if (!strncmp(query, SERVER_INFO_QUERY, strlen(SERVER_INFO_QUERY))){
        srv_senddone(srvproc, SRV_DONE_FINAL, 0, 0);
        return FAIL;
    }

    query = scan_next(query, spname);
    if (strlen(spname) == 0) {
        srv_senddone(srvproc, SRV_DONE_FINAL, 0, 0);
        return FAIL;
    }
    if (strnicmp(spname, EXEC_CMD, (sizeof(EXEC_CMD) - 1)) == 0) {
        // stored procedure name
        //
        query = scan_next(query, spname);
        if (strlen(spname) == 0)
            goto syntax_error;
    }

    // Check for existence
    //
    for (i = 0; i < Rpcnumber; i++)
        if (strcmp(Sps[i].name, spname) == 0) {
            *sp = &Sps[i];
            break;
        }
    if (*sp == NULL) {
        sprintf(msg, "Procedure \'%s \' not found.", spname);
        msgnum = SP_UNKNOWN;
        goto error;
    }

    // Parameters
    //
    numparams = 0;
    while (*query != '\0') {
        if (++numparams > (*sp)->numparams) {
            sprintf(msg, (*sp)->usage);
            msgnum = INVALID_SP_SYNTAX;
            goto error;
        }
        if (!paramsyntax && *query == '@')
            paramsyntax = TRUE; // parameter name mode

        if (!paramsyntax)
            if (paramvalue == NULL)
                paramvalue = paramvalues[0];
            else
                paramvalue += MAXLEN;

        if (paramsyntax) {
            if (*query != '@') {
                sprintf( msg,
                         "Once the form '@name = value' has been used, "
                         "all subsequent parameters must be passed in "
                         "the form '@name = value'." );
                msgnum = INVALID_SP_SYNTAX;
                goto error;
            } else
                query++;

            query = scan_next(query,
            paramname);
            if (strlen(paramname) == 0)
                goto syntax_error;

            // Get parameter index
            //
            paramvalue = NULL;
            for (i = 0; i < (*sp)->numparams; i++)
                if (strcmp((*sp)->params[i].name, paramname) == 0) {
                    paramvalue = paramvalues[i];
                    break;
                }
            if (paramvalue == NULL) {
                sprintf( msg,
                         "Procedure '%s' does not recognize parameter name: %s",
                         spname, paramname );
                msgnum = BAD_SP_PARAMETER;
                goto error;
            }

            // Already assigned value
            //
            if (strlen(paramvalue) > 0)
                goto syntax_error;

            // Check for '='
            //
            query = scan_next(query,
            equalstring);
            if (*equalstring != '=')
                goto syntax_error;

        }
        query = scan_next(query,
        paramvalue);
        if (strlen(paramvalue) == 0)
            goto syntax_error;

        if (*query == ',') {
            query++;
            while (*query == ' ' || *query == '\t')
                query++;
        }
    }
    return SUCCEED;

syntax_error:
    sprintf(msg, "Incorrect syntax found near '%s'.", query);
    msgnum = INVALID_SP_SYNTAX;

error:
    srv_sendstatus(srvproc, 1);
    srv_sendmsg(srvproc, SRV_MSG_ERROR, msgnum, SRV_INFO, (DBTINYINT)0, NULL,
                0, 0, msg, SRV_NULLTERM);
    srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
    return FAIL;
}

// ================================= scan_next ===============================
//
// SCAN_NEXT
//    Reads the next token in a string, ignoring whitespace.
//
// Parameters:
//    string - The language event string
//    word - The next token in the string
//
// Returns:
//    The string incremented passed the token.
//
DBCHAR *scan_next( DBCHAR *string, DBCHAR *word )
{
    DBCHAR *p;

    word[0] = '\0';
    if (*string == '\"' || *string == '\'') {
        // check for unclosed quote
        //
        p = strchr(string + 1,
        *string);
        if (p == NULL)
            return string;
        strncpy(word, string + 1, p - (string + 1));
        word[p - (string + 1)] = '\0';
        string += 2;
    } else {
        // clear proceeding white space
        //
        while (*string == ' ' || *string == '\t' || *string == '\n' ||
               *string == '\r')
            string++;
        sscanf(string, "%s", word);

        // ignore comments
        //
        while (strncmp(word, "/*", 2) == 0) {
            string = strstr(string,
            "*/");
            if (string != NULL) {
                string += 2;
                word[0] = '\0';
                while (*string == ' ' || *string == '\t' || *string == '\n' ||
                       *string == '\r')
                    string++;
                sscanf(string, "%s", word);
            } else
                return string;
        }
    }
    if (strlen(word) > 0)
        string += strlen(word);

    // clear trailing white space
    //
    while (*string == ' ' || *string == '\t' || *string == '\n' ||
           *string == '\r')
        string++;

    return string;
}

// ================================== sp_execute =============================
//
// SP_EXECUTE
//    Execute a client stored procedure.
//
//    Scans the list of defined stored procedures, checks the parameters and
//    executes the procedure. If results are returned it is the responsiblity
//    of the underlying proedure.
//
// Parameters:
//    srvproc - The process handle to use to send results to the client.
//
// Returns:
//    SRV_CONTINUE
//
SRVRETCODE sp_execute( SRV_PROC  *srvproc )
{
    int      i;
    int      x;
    int      y;
    int      len;
    int      numparams;
    SP_INFO *sp = NULL;
    DBCHAR   msg[MAXLEN];
    DBINT    msgnum;
    DBCHAR   paramvalues[MAXPARAMS][MAXLEN];
    BOOL     paramnamemode = FALSE;
    DBCHAR  *paramname;
    DBINT    paramtype;
    DBCHAR  *value;
    DBINT    type;
    DBINT    status;

    // Initialize parameter storage
    //
    for( i = 0; i < MAXPARAMS; i++ )
        memset( paramvalues[i], 0, MAXLEN );

    for( i = 0; i < Rpcnumber; i++ )
    {
        // Create name generated by calling server
        //
        if( strcmp(Sps[i].name, srv_rpcname(srvproc, (int *)NULL)) == 0 )
        {
            sp = &Sps[i];
            break;
        }
    }

    if( sp == NULL )
    {
        sprintf( msg,
                 "Procedure \'%s \' not found.",
                 srv_rpcname(srvproc, (int *)NULL) );

        msgnum = SP_UNKNOWN;
        goto error;
    }

    numparams = srv_rpcparams( srvproc );

    if( srv_paramname(srvproc, 1, &len) && len > 0 )
        paramnamemode = TRUE;

    for( y = 1; y <= numparams; y++ )
    {
        // Find parameter number
        //
        if( paramnamemode )
        {
            paramname = srv_paramname( srvproc,
                                       y,
                                       &len );

            if( strlen(paramname) == 0 )
                goto parameter_error;

            if( *paramname == '@' )
                paramname++;
            else
                goto parameter_error;

            value = NULL
            ;
            for( x = 0; x < sp->numparams; x++ )
            {
                if( strcmp(sp->params[x].name, paramname) == 0 )
                {
                    value  = paramvalues[x];
                    type   = sp->params[x].type;
                    status = sp->params[x].status;
                    break;
                }
            }

            if( value == NULL )
                goto parameter_error;
        }
        else  // if( paramnamemode )
        {
            value  = paramvalues[y - 1];
            type   = sp->params[y - 1].type;
            status = sp->params[y - 1].status;
        }

        // Check parameters for correct type
        //
        paramtype = srv_paramtype( srvproc, y );

        switch( paramtype )

{
        case SRVVARCHAR:    // Type sent by Servers instead of SRVCHAR
            paramtype = SRVCHAR;
            break;
        case SRVINTN:       // Type sent by Servers instead of SRVINT
            paramtype = SRVINT4;
            break;
        default:
            break;
        }

        if( type != paramtype )
        {
            if( paramnamemode )
                sprintf( msg, "Parameter \'%s \' is incorrect type.",
                         paramname );
            else
                sprintf( msg, "Parameter \'%d \' is incorrect type.", y );

            msgnum = BAD_SP_PARAMETER;
            goto error;
        }

        // Check parameters for correct status
        //
        if( (DBINT)srv_paramstatus(srvproc, y) != status )
        {
            if( paramnamemode )
                sprintf( msg, "Parameter \'%s \' has incorrect status.",
                         paramname);
            else
                sprintf( msg, "Parameter \'%d \' had incorrect status.", y );

            msgnum = BAD_SP_PARAMETER;
            goto error;
        }

        // Move SP parameters to local variables
        //
        srv_bmove( srv_paramdata(srvproc, y), value, srv_paramlen(srvproc, y) );
        value[srv_paramlen(srvproc, y)] = '\0';

    }

    // If unspecified, use default value
    //
    for( i = 0; i < sp->numparams; i++ )
    {
        if( strlen(paramvalues[i]) == 0
        &&  strlen(sp->params[i].defaultvalue) == 0 )
        {
            strcpy( msg, sp->usage );
            msgnum = INVALID_SP_SYNTAX;
            goto error;
        }

        if( strlen(paramvalues[i]) == 0
        &&  strlen(sp->params[i].defaultvalue) != 0 )
            strcpy(paramvalues[i], sp->params[i].defaultvalue);
    }

    // Execute procedure
    //
    (*sp->handler)( (VOID *)srvproc,
                    paramvalues[0],
                    paramvalues[1],
                    paramvalues[2],
                    paramvalues[3] );

    return SRV_CONTINUE;

parameter_error:
    sprintf( msg, "Procedure '%s' does not recognize parameter name: %s",
             sp->name, paramname );
    msgnum = BAD_SP_PARAMETER;

error:
    srv_sendstatus( srvproc, 1 );
    srv_sendmsg( srvproc, SRV_MSG_ERROR, msgnum, SRV_INFO, (DBTINYINT)0, NULL,
                 0, 0, msg, SRV_NULLTERM );
    srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0 );
    return SRV_CONTINUE;
}

// ================================= exit_remote =============================
//
// EXIT_REMOTE
//    Handler for SRV_DISCONNECT events.
//
//    The code to disconnect from the procedure server.
//
// Parameters:
//    srvproc - the handle to the client connection
//
// Returns:
//    SRV_DISCONNECT
///
SRVRETCODE exit_remote( SRV_PROC  *srvproc )
{
    char *string;
    int len;

    // Display info on console
    //
    string = srv_pfield(srvproc, SRV_CPID, &len);
    string[len] = '\0';
    printf("\nClient connection closed, process ID: %s\n", string);

    return SRV_CONTINUE;
}

// ================================== chk_err =================================================
//
// CHK_ERR
//    Print out errors.
//
// Parameters:
//    server        - pointer to procedure server server structure.
//    srvproc      - pointer to client connection structure
//    errornum     - error number.
//    severity     - error severity.
//    state        - error state.
//    oserrnum     - operating system error number, if any.
//    errtext      - the text of the error message.
//    errtextlen   - length of the errtext message
//    oserrtext    - the text of the operating system error message.
//    oserrtextlen - length of the errtext message
//
// Returns:
//    SRV_CONTINUE, SRV_CANCEL, or SRV_EXIT_PROGRAM
//

SRVRETCODE chk_err( SRV_SERVER *server,
                 SRV_PROC   *srvproc,
                 int         errornum,
                 BYTE        severity,
                 BYTE        state,
                 int         oserrnum,
                 DBCHAR     *errtext,
                 int         errtextlen,
                 DBCHAR     *oserrtext,
                 int         oserrtextlen )
{
    char log_buffer[256];
    char error[256];
    char oserror[256];

    memcpy(error, errtext, errtextlen);
    error[errtextlen] = '\0';
    memcpy(oserror, oserrtext, oserrtextlen);
    oserror[oserrtextlen] = '\0';

    // Strip out resource information. Get the actual error number.
    errornum = (errornum & 0x0000FFFF);

    // Operating system error?
    //
    if (oserrnum != SRV_ENO_OS_ERR) {
        sprintf(log_buffer, "SERVER OS ERROR: %d: %s.", oserrnum, oserror);
        if (server)
            srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
        else    // If application not initialized log to screen
            printf ("%s\n", log_buffer);
    }

    // Is this a fatal error for the server?
    //
    if (severity >= SRV_FATAL_SERVER) {
        sprintf(log_buffer,
                    "SERVER: FATAL SERVER ERROR: errornum = %d, "
                    "severity = %d, state = %d: %s.",
                errornum, severity, state, error);

        if (server)
            srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
        else    // If application not initialized log to screen
            printf ("%s\n", log_buffer);
        return SRV_EXIT;
    } else {
        //
        // Did the "srvproc" get a fatal error?
        //
        if (severity >= SRV_FATAL_PROCESS) {
            sprintf(log_buffer,
                          "SERVER: FATAL CONNECT ERROR: errornum = %d, "
                          "severity = %d, state = %d: %s.",
                     errornum, severity, state, error);

            if (server)
                srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
            else    // If application not initialized log to screen
                printf ("%s\n", log_buffer);

            return SRV_CANCEL;
        }
    }

    // A non-fatal error or an information message received.
    // We'll pass it through to the client.
    //
    if (srvproc != (SRV_PROC *)NULL && (server != NULL))
        if (severity < 10) {    // if informational message
            srv_sendmsg(srvproc, SRV_MSG_INFO, (DBINT)errornum, severity, 0,
                        NULL, 0, 0, error, SRV_NULLTERM);
        } else {            // must be an error message
               srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)errornum, severity, 0,
                           NULL, 0, 0, error, SRV_NULLTERM);
    } else {
        sprintf(log_buffer, "ODS ERROR: errornum = %d, severity = %d: %s",
                errornum, severity, error);
        if (server)
            srv_log(server, TRUE, log_buffer, SRV_NULLTERM);
        else    // If application not initialized log to screen
            printf ("%s\n", log_buffer);
    }
    return SRV_CONTINUE;
}

// The following are the supported store procedure functions
//
// ================================== proclist ===============================
//
// PROCLIST
//    Returns the usage for all defined stored procedures
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    SUCCEED
//
// Side Effects:
//    Returns a result set to client
//
SRVRETCODE proclist( SRV_PROC *srvproc )
{
    DBCHAR colname1[MAXNAME];
    DBCHAR colname2[MAXNAME];
    int i;

    sprintf(colname1, "spname");
    srv_describe(srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, MAXNAME,
                 SRVCHAR, 0, NULL);

    sprintf(colname2, "spusage");
    srv_describe(srvproc, 2, colname2, SRV_NULLTERM, SRVCHAR, MAXLEN, SRVCHAR,
                 0, NULL);

    // Return each SP handler as a row
    //
    for (i = 0; i < Rpcnumber; i++) {
        srv_setcoldata(srvproc, 1, Sps[i].name);
        srv_setcollen(srvproc, 1, strlen(Sps[i].name));

        srv_setcoldata(srvproc, 2, Sps[i].usage);
        srv_setcollen(srvproc, 2, strlen(Sps[i].usage));

        srv_sendrow(srvproc);
    }
    srv_senddone(srvproc, (SRV_DONE_COUNT | SRV_DONE_FINAL), 0, i);

    return SUCCEED;
}

// ================================== sp_exec ================================
//
// SP_EXEC
//      Execute a given command string and returns any output as rows of
//      text.
//
// Parameters:
//      srvproc - the handle to the client connection that got the SRV_CONNECT.
//      command - the command string to execute
//
// Returns:
//      SUCCEED or FAIL
//
// Side Effects:
//      Returns messages and/or a result set to client
//
SRVRETCODE sp_exec( SRV_PROC *srvproc, DBCHAR *command )
{

    DBCHAR bReadBuffer[MAXLEN];
    DBCHAR bErrorMsg[80];
    int    cbReadBuffer;

    DBINT   cnt;
    DBINT   rows = 0;
    DBCHAR *paramvalue;
    DBINT   paramlength;
    DBINT   cmdlength;
    BOOL    fSuccess;

    STARTUPINFO         si;
    PROCESS_INFORMATION pi;

    SECURITY_ATTRIBUTES saPipe;
    HANDLE              hReadPipe;
    HANDLE              hWritePipe;

    // Allocation local storage for command string.
    //
    paramlength = strlen( command );
    cmdlength   = paramlength + CMDSTR + 1;
    paramvalue  = (DBCHAR *)malloc( cmdlength );

    if( !paramvalue )
    {
        SETERROR( "Malloc", bErrorMsg );
        srv_sendstatus( srvproc, 1 );
        srv_sendmsg( srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                     NULL, 0, 0, bErrorMsg, SRV_NULLTERM );
        srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0 );
        return FAIL;
    }

    // Cancatenate "cmd /c " to command string so child process will
    // execute the given command and exit.  Move command string to
    // local variable.
    //
    memset( paramvalue, 0, cmdlength );
    srv_bmove( "cmd /c ", paramvalue, CMDSTR );
    srv_bmove( command, &paramvalue[CMDSTR], paramlength );

    // Create child process to execute the command string.  Use an
    // anonymous pipe to read the output from the command and send
    // any results to the client.

    // In order for the child process to be able to write
    // to the anonymous pipe, the handle must be marked as
    // inheritable by child processes by setting the
    // SECURITY_ATTRIBUTES.bInheritHandle flag to TRUE.
    //
    saPipe.nLength              = sizeof( SECURITY_ATTRIBUTES );
    saPipe.lpSecurityDescriptor = NULL;
    saPipe.bInheritHandle       = TRUE;

    fSuccess = CreatePipe( &hReadPipe,      // read handle
                           &hWritePipe,     // write handle
                           &saPipe,         // security descriptor
                           0 );             // use default pipe buffer size
    if( !fSuccess )
    {
        SETERROR( "CreatePipe", bErrorMsg );
        srv_sendstatus( srvproc, 1 );
        srv_sendmsg( srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                     NULL, 0, 0, bErrorMsg, SRV_NULLTERM );
        srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0 );
        free( paramvalue );
        return FAIL;
    }

    // Now we must set standard out and standard error to the
    // write end of the pipe.  Once standard out and standard
    // error are set to the pipe handle, we must close the pipe
    // handle so that when the child process dies, the write end
    // of the pipe will close, setting an EOF condition on the pipe.
    //
    memset( &si, 0, sizeof(si) );

    si.cb          = sizeof(si);
    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdOutput  = hWritePipe;
    si.hStdError   = hWritePipe;

    // Set the fInheritHandles parameter to TRUE so that open
    // file handles will be inheritied. We can close the child
    // process and thread handles as we won't be needing them.
    // The child process will not die until these handles are
    // closed.
    //
    fSuccess = CreateProcess( NULL,         // filename
                              paramvalue,   // command line for child
                              NULL,         // process security descriptor
                              NULL,         // thread security descriptor
                              TRUE,         // inherit handles?
                              0,            // creation flags
                              NULL,         // inherited environment address
                              NULL,         // startup dir; NULL = start in current
                              &si,          // pointer to startup info (input)
                              &pi );        // pointer to process info (output)
    if( !fSuccess )
    {
        SETERROR( "CreateProcess", bErrorMsg );
        srv_sendstatus( srvproc, 1 );
        srv_sendmsg( srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                     NULL, 0, 0, bErrorMsg, SRV_NULLTERM );
        srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0 );
        free( paramvalue );
        return FAIL;
    }

    CloseHandle( pi.hThread );
    CloseHandle( pi.hProcess );

    // We need to close our instance of the inherited pipe write
    // handle now that it's been inherited so that it will actually
    // close when the child process ends. This will put an EOF
    // condition on the pipe which we can then detect.
    //
    fSuccess = CloseHandle( hWritePipe );

    if( !fSuccess )
    {
        SETERROR( "CloseHandle", bErrorMsg );

        srv_sendstatus( srvproc, 1 );
        srv_sendmsg( srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                     NULL, 0, 0, bErrorMsg, SRV_NULLTERM );
        srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0 );

        free( paramvalue );
        return FAIL;
    }

    // Now read from the pipe until EOF condition reached.
    //
    do
    {
        cnt = 0;

        while( fSuccess = ReadFile( hReadPipe,          // read handle
                                    &bReadBuffer[cnt],  // buffer for incoming data
                                    1,                  // number of bytes to read
                                    &cbReadBuffer,      // number of bytes actually read
                                    NULL))
        {
            if( !fSuccess )
            {
                if( GetLastError() == ERROR_BROKEN_PIPE )
                    break;  // child has died
                else
                {
                    SETERROR( "CloseHandle", bErrorMsg );

                    srv_sendstatus( srvproc, 1 );
                    srv_sendmsg( srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO,
                                 (DBTINYINT)0, NULL, 0, 0, bErrorMsg,
                                 SRV_NULLTERM );
                    srv_senddone( srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL),
                                  0, 0 );

                    free( paramvalue );
                    return FAIL;
                }
            }

            if (bReadBuffer[cnt] == '\n')
                break;
            else
                cnt++;
        }

        if( fSuccess && cbReadBuffer )
        {
            if( rows == 0 )
            {
                // Describe result row: it will be one column of size
                // sizeof(buf)  We do this in the retrieval loop to
                // ensure that the row description will occur only if
                // there are rows to be outputted.
                //
                srv_describe( srvproc,
                              1,
                              &paramvalue[CMDSTR],
                              SRV_NULLTERM,
                              SRVCHAR,
                              sizeof(bReadBuffer),
                              SRVCHAR,
                              sizeof(bReadBuffer),
                              bReadBuffer );
            }

            // Make sure we have at least one data
            //
            if( !cnt )
            {
                bReadBuffer[0] = ' ';
                cnt = 1;
            }

            // Remove carriage return if it exists
            //
            if( bReadBuffer[cnt-1] == 0x0D )
                cnt--;

            // Send result rows back to client.
            //
            srv_setcollen( srvproc, 1, cnt );
            srv_sendrow( srvproc );
            rows++;
        }
    } while( fSuccess && cbReadBuffer );

    // close the trace file, pipe handles
    //
    CloseHandle( hReadPipe );

    if( rows == 0 )
    {
        srv_sendstatus( srvproc, 0 );
        srv_sendmsg( srvproc, SRV_MSG_INFO, EXEC, SRV_INFO, (DBTINYINT)0, NULL,
                     0, 0, "Command executed successfully", SRV_NULLTERM );
    }

    srv_senddone( srvproc, (SRV_DONE_COUNT | SRV_DONE_FINAL), 0, rows );
    free( paramvalue );
    return SUCCEED;
}

// =================================== disklist ==============================
//
// DISKLIST
//     Returns a row for each defined drive containing its name and the
//     amount of disk space available.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    SUCCEED
//
// Side Effects:
//     Returns a result set to client
//
SRVRETCODE disklist( SRV_PROC  *srvproc )
{
    DBCHAR colname1[MAXNAME];
    DBCHAR colname2[MAXNAME];
    DBCHAR drivename;
    DBCHAR rootname[16];
    int    drivenum;
    unsigned    secPerCluster;
    unsigned    bytesPerSector;
    unsigned    freeClusters;
    unsigned    totalClusters;
    int    drivenums;
    int    space_remaining;
    int    i = 0;

    sprintf( colname1, "drive" );
    srv_describe( srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, 1, SRVCHAR, 1,
                  (BYTE *)&drivename );

    sprintf( colname2, "Kbytes free" );
    srv_describe( srvproc, 2, colname2, SRV_NULLTERM, SRVINT4, 4, SRVINT4, 4,
                  (BYTE *)&space_remaining );

    drivenums = GetLogicalDrives();

    drivenums >>= 2;        // Ignore drives A and B
    for( drivename = 'C', drivenum = 3; drivename <= 'Z';
         drivename++, drivenum++ )
    {
        if( drivenums & 1 )
        {
            i++;

            sprintf( rootname, "%c:\\", drivename );
            GetDiskFreeSpace( rootname, &secPerCluster, &bytesPerSector,
                              &freeClusters, &totalClusters );

            space_remaining = secPerCluster * bytesPerSector * (freeClusters/1000) ;

            srv_sendrow( srvproc );
        }

        drivenums >>= 1;
    }

    srv_senddone( srvproc, (SRV_DONE_COUNT | SRV_DONE_FINAL), 0, i );
    return SUCCEED;
}

// ================================== diskfree ===============================
//
// DISKFREE
//    Returns the amount of space available on a given drive. The value
//     is placed into the defined return parameter of the stored procedure.
//
//     NOTE: This routine can not be called via a language event.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//     drive - the drive letter to check
//
// Returns:
//    SUCCEED
//
// Side Effects:
//    Returns messages and/or a result set to client. Returns a value in the
//     defined return parameter.
//
SRVRETCODE diskfree( SRV_PROC *srvproc, DBCHAR *drive )
{
    DBCHAR colname1[MAXNAME];
    int    drivenum;
    DBCHAR rootname[16];
    int    drivenums;
    int    secPerCluster;
    int    bytesPerSector;
    int    freeClusters;
    int    totalClusters;
    int    space_remaining = -1;
    int    i = 0;

    drive = strupr( drive );

    sprintf( colname1, "drive" );
    srv_describe( srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, 1, SRVCHAR, 1,
                  (BYTE *)drive );
    srv_sendrow( srvproc );
    srv_senddone( srvproc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1 );

    drivenums = GetLogicalDrives();

    drivenum = drive[0] - 'A' + 1;

    drivenums >>= drivenum - 1; //Ignore drives A and B
    if( drivenums & 0x01 )
    {
        sprintf( rootname, "%c:\\", drive[0] );
        GetDiskFreeSpace( rootname, &secPerCluster, &bytesPerSector,
                          &freeClusters, &totalClusters );

        space_remaining = secPerCluster * freeClusters * bytesPerSector;

    }

    // Process return parameter
    //
    if( srv_paramstatus(srvproc, 2) & 0x0001 )
        srv_paramset( srvproc, 2, (BYTE *)&space_remaining, 4 );

    srv_senddone( srvproc, SRV_DONE_FINAL, 0, 0 );
    return SUCCEED;
}

// ================================== scan_xbase =============================
//
// SCAN_XBASE
//    Reads an xBase file and sends it to the client as if it were a SQL
//    Server query result set (the equivalent of a 'SELECT * FROM
//    tablename' SQL statement).
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//    szFileName - dbase file path name
//
// Returns:
//    SUCCEED or FAIL
//
// Side Effects:
//    Returns messages and/or a result set to client
//
SRVRETCODE scan_xbase( SRV_PROC *srvproc, char *filename )
{
    FILE  *xbasefile;
    size_t count;
    char   buffer[BUF_SIZE];
    short  numrecords;
    short  headerlength;
    short  recordlength;
    short  lengthlist[XBASE_MAX_COLUMNS];
    int    i;
    short  j;
    short  position;
    short  numcolumns;

    // now read the database header info
    //
    if ((xbasefile = fopen(filename, "r")) == NULL) {
        srv_sendstatus(srvproc, 1);
        srv_sendmsg(srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
        return FAIL;
    }
    count = fread(buffer,
    XBASE_HDR_SIZE,
    1,
    xbasefile);

    if (count == 0) {
        srv_sendstatus(srvproc, 1);
        srv_sendmsg(srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
        fclose(xbasefile);
        return FAIL;
    }
    numrecords   = *((short *)&buffer[4]);
    headerlength = *((short *)&buffer[8]);
    recordlength = *((short *)&buffer[10]);
    numcolumns   = (headerlength - 32 - 1) / 32;

    // now get the column header information
    //
    for (j = 0; j < numcolumns; j++) {
        count = fread(buffer,   XBASE_HDR_SIZE, 1, xbasefile);
        if (count == 0) {
            srv_sendstatus(srvproc, 1);
            srv_sendmsg(srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                        NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
            srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
            fclose(xbasefile);
            return FAIL;
        }

        // we need to NULL terminate the column name (if it is a
        // full 11 characters int)
        //
        buffer[11] = '\0';

        // now find our the column length for this data buffer
        //
        lengthlist[j] = (short)buffer[16];

        // now 'describe' this column
        //
        srv_describe( srvproc, j + 1,   // column number
                      buffer,           // pointer to column name
                      SRV_NULLTERM,     // column name is NULL terminated
                      SRVCHAR,          // datatype is char (xBase numbers are ASCII)
                      lengthlist[j],    // column length
                      SRVCHAR,          // destination datatype is also char
                      lengthlist[j],    // destination column length
                      NULL);            // pointer to where the data will be

    }

    // now read the one byte 'column header seperator'
    //
    count = fread(buffer, 1, 1,  xbasefile);
    if (count == 0) {
        srv_sendstatus(srvproc, 1);
        srv_sendmsg(srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
        fclose(xbasefile);
        return FAIL;
    }
    for (i = 0; i < numrecords; i++) {
        count = fread(buffer, recordlength, 1, xbasefile);
        if (count == 0 && !feof(xbasefile)) {
            srv_sendstatus(srvproc, 1);
            srv_sendmsg(srvproc, SRV_MSG_ERROR, EXEC, SRV_INFO, (DBTINYINT)0,
                        NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
            srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_FINAL), 0, 0);
            fclose(xbasefile);
            return FAIL;
        }

        // check to see if this is a deleted row
        //
        if (buffer[0] == '*')
            break;

        // Now set the length and data pointers for each column
        //
        for (j = 0, position = 1; j < numcolumns; j++) {
            srv_setcollen(srvproc, j + 1, lengthlist[j]);
            srv_setcoldata(srvproc, j + 1, &buffer[position]);
            position += lengthlist[j];
        }

        // send the row to the client.
        //
        srv_sendrow(srvproc);
    }
    srv_senddone(srvproc, SRV_DONE_COUNT | SRV_DONE_FINAL, 0, i);
    fclose(xbasefile);
    return SUCCEED;
}

// The following section defines the Service Control Manager support functions.
//
// ================================== initsignal =============================
//
//  initsignal -- Install signal handlers for NTB Server.
//
void initsignal( SRV_SERVER * server, char * szServiceName )
{
    char szLogBuffer[1024];

    if( SrvCtrMan ) // if started from Service Control Manager
    {
        // Use RegisterServiceCtrlHandler() to communicate with
        // the Service Control Manager.
        //
        NTBServiceHandle = RegisterServiceCtrlHandler( "ProcSrv",
                                                       NTBServiceCtrlHandler );
        InitializeCriticalSection( &SCMCrtSec );

        // Now send a START_PENDING message
        //
        NTBServiceStatus.dwServiceType  = SERVICE_WIN32_OWN_PROCESS;
        NTBServiceStatus.dwCurrentState = SERVICE_START_PENDING;

        NTBServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
                                            | SERVICE_ACCEPT_PAUSE_CONTINUE
                                            | SERVICE_ACCEPT_SHUTDOWN;

        NTBServiceStatus.dwWin32ExitCode           = NO_ERROR;
        NTBServiceStatus.dwServiceSpecificExitCode = 0;
        NTBServiceStatus.dwCheckPoint              = 1;
        NTBServiceStatus.dwWaitHint                = 20000L;   // 20 seconds

        if( !SetServiceStatus(NTBServiceHandle, &NTBServiceStatus) )
        {
            sprintf( szLogBuffer,
                     "Procedure Server Service Manager Failer: %s "
                     "(initsignal(), line = %d), message = %s",
                     szServiceName,
                     __LINE__,
                     get_last_error_str() );
            srv_log( server, TRUE, szLogBuffer, SRV_NULLTERM );

            // Flush all file buffers
            //
            _flushall();
            ExitProcess( 1 );
        }

        // We need to increment checkpoint field in the above structure
        // regularly in order to notify Service Control Manager that
        // we aren't hung.
        //
        ++(NTBServiceStatus.dwCheckPoint);

    } // if( SrvCtrMan )

    // Don't display a message box for hard errors, return the error back
    // to the application instead.
    //
    SetErrorMode( SEM_FAILCRITICALERRORS );

    // Install Ctrl-C handler
    //
    if( !SrvCtrMan )
    {
        if( SetConsoleCtrlHandler((PHANDLER_ROUTINE)ctrlc_hndl, TRUE)
        != TRUE )
        {
            sprintf( szLogBuffer,
                     "Procedure Server Service Manager Failer: %s "
                     "(SetConsoleCtrlHandler(), line = %d), message = %s",
                     szServiceName,
                     __LINE__,
                     get_last_error_str() );
            srv_log( server, TRUE, szLogBuffer, SRV_NULLTERM );
        }
    }
    return;
}

// ================================== ctrlc_hndl =============================
//
//  ctrlc_hndl(ulong) -- Handles Ctrl-C and Ctrl-Break events received
//  by NTB Server.
//
void ctrlc_hndl( ULONG CtrlTyp )
{
    char c;
    char szLogBuffer[1024];

    switch( CtrlTyp )
    {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:

        printf( "Terminate Procedure Server? (y/n): ");

        do
        {
            c = getch();

} while( c != 'y' && c != 'Y' && c != 'n' && c != 'N' );

        printf( "%c\n", c );

        if( c == 'y' || c == 'Y' )
        {
            sprintf( szLogBuffer,
                     "Procedure Server terminated by Ctrl-C or Ctrl-Break, name = %s",
                     szRegistryName );

            if( gblServer ) {
                srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );
                srv_setevent( gblServer, SRV_EXIT );
            }
            WaitForSingleObject(hServerDone, INFINITE );

            _flushall();
            ExitProcess( 0 );
        }

        break;

    default:
        break;
    }
    return;
}

// ============================== NTBServiceCtrlHandler ======================
//
//  NTBServiceCtrlHandler(DWORD) -- Responds to START, STOP, etc..
//  requests of Service Control Manager.
//
void WINAPI NTBServiceCtrlHandler( DWORD dwCtrl )
{
    char szLogBuffer[1024];

    switch( dwCtrl )
    {
    case SERVICE_CONTROL_SHUTDOWN:
        //
        // NT is shutting down.
        //
        // Fall through

    case SERVICE_CONTROL_STOP:

        EnterCriticalSection( &SCMCrtSec );

        NTBServiceStatus.dwCurrentState            = SERVICE_STOP_PENDING;
        NTBServiceStatus.dwWin32ExitCode           = NO_ERROR;
        NTBServiceStatus.dwServiceSpecificExitCode = 0;
        NTBServiceStatus.dwCheckPoint              = 1;
        NTBServiceStatus.dwWaitHint                = 60000L;   // 60 seconds

        SetServiceStatus( NTBServiceHandle, &NTBServiceStatus );
        LeaveCriticalSection( &SCMCrtSec );

        // NTBShutdown() sets the SQL Service Manager to "Stop" and terminates
        // the service.
        //
        SetThreadPriority( (HANDLE)_beginthread(NTBShutdown, 0, NULL),
                           THREAD_PRIORITY_HIGHEST );
        break;

    case SERVICE_CONTROL_INTERROGATE:

        // Serialize with increment signal thread
        //
        EnterCriticalSection( &SCMCrtSec );

        SetServiceStatus( NTBServiceHandle, &NTBServiceStatus );
        LeaveCriticalSection( &SCMCrtSec );
        break;

    case SERVICE_CONTROL_PAUSE:

        // Serialize with increment signal thread
        //
        EnterCriticalSection( &SCMCrtSec );

        srv_setevent( gblServer, SRV_SLEEP );

        NTBServiceStatus.dwCurrentState            = SERVICE_PAUSED;
        NTBServiceStatus.dwWin32ExitCode           = NO_ERROR;
        NTBServiceStatus.dwServiceSpecificExitCode = 0;
        NTBServiceStatus.dwCheckPoint              = 1;
        NTBServiceStatus.dwWaitHint                = 60000L;   // 60 seconds

        SetServiceStatus( NTBServiceHandle, &NTBServiceStatus );
        LeaveCriticalSection( &SCMCrtSec );

        sprintf( szLogBuffer,
                 "Procedure Server PAUSED, name = %s",
                 szRegistryName );

        if( gblServer )
            srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );
        break;

    case SERVICE_CONTROL_CONTINUE:

        EnterCriticalSection( &SCMCrtSec );

        srv_setevent( gblServer, SRV_RESTART );

        NTBServiceStatus.dwCurrentState            = SERVICE_RUNNING;
        NTBServiceStatus.dwWin32ExitCode           = NO_ERROR;
        NTBServiceStatus.dwServiceSpecificExitCode = 0;
        NTBServiceStatus.dwCheckPoint              = 1;
        NTBServiceStatus.dwWaitHint                = 60000L;   // 60 seconds

        SetServiceStatus(NTBServiceHandle, &NTBServiceStatus);
        LeaveCriticalSection( &SCMCrtSec );

        sprintf( szLogBuffer,
                 "Procedure Server CONTINUED, name = %s",
                 szRegistryName );

        if( gblServer )
            srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );
        break;

    default:
        // Values 128-255 can be user app defined
        //
        ;
    } // switch( dwCtrl )

    return;
}

// ================================= completesignal ==========================
//
// completesignal() -- Notifies Service Control Manager that NTB Server
//  has started.
//
void completesignal( SRV_SERVER * server )
{
    char szLogBuffer[1024];

    if( !SrvCtrMan )
        return;

    EnterCriticalSection( &SCMCrtSec );

    NTBServiceStatus.dwCurrentState = SERVICE_RUNNING;
    NTBServiceStatus.dwCheckPoint   = 0;
    NTBServiceStatus.dwWaitHint     = 0;

    if( !SetServiceStatus(NTBServiceHandle, &NTBServiceStatus) )
    {
        sprintf( szLogBuffer,
                 "Procedure Server Service Manager Failer: %s, (completesignal(), line = %d), message = %s",
                     szRegistryName,
                     __LINE__,
                 get_last_error_str() );

        srv_log( server, TRUE, szLogBuffer, SRV_NULLTERM );
    }

    LeaveCriticalSection( &SCMCrtSec );
    return;
}

// ==================================== NTBShutdown ==========================
//
//  NTBShutdown() -- This routine notifies ODS to terminate.  After ODS has terminate,
//                   the Service Control Manager is notified that everything has shut down.
//
void NTBShutdown( LPVOID notused )
{
    char szLogBuffer[1024];

    // Flush all file buffers
    //
    _flushall();

    // Tell ODS to terminate...
    //
    srv_setevent( gblServer, SRV_EXIT );

    do
    {
        // Start the checkpoint incrementer
        //
        ++(NTBServiceStatus.dwCheckPoint);

    } while( WaitForSingleObject(hServerDone, 1000) == WAIT_TIMEOUT );

    sprintf( szLogBuffer,
             "Procedure Server STOPPED, name = %s",
             szRegistryName );

    if( gblServer )
        srv_log( gblServer, TRUE, szLogBuffer, SRV_NULLTERM );

    _flushall();

    EnterCriticalSection( &SCMCrtSec );

    NTBServiceStatus.dwCurrentState            = SERVICE_STOPPED;
    NTBServiceStatus.dwWin32ExitCode           = NO_ERROR;
    NTBServiceStatus.dwServiceSpecificExitCode = 0;
    NTBServiceStatus.dwCheckPoint              = 0;
    NTBServiceStatus.dwWaitHint                = 0;

    SetServiceStatus( NTBServiceHandle, &NTBServiceStatus );
    LeaveCriticalSection( &SCMCrtSec );

    // Flush all file buffers
    //
    _flushall();

    ExitProcess( 0 );
}

// ============================== get_last_error_str =========================
//
// This function returns the Operating System message text and length
// associated with the error value sent to it.
//
//      Inputs:
//              iOSerror = value of message to be returned
//
//      Outputs:
//              pointer to message string (NULL if message not found)
//
char * get_last_error_str()
{
    static char * szBuffer = NULL;
    DWORD dwLastError = GetLastError();

    if( szBuffer )
        LocalFree( szBuffer );

    szBuffer = NULL;

    //  Attempt retrieving the message from system resource table
    //
    FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM    |
                   FORMAT_MESSAGE_ALLOCATE_BUFFER,
                   NULL,
                   dwLastError,
                   (DWORD)GetSystemDefaultLangID(),
                   (LPSTR)&szBuffer,
                   255,            // maximum message length allowed
                   (LPVOID)NULL );
    return szBuffer;
}


SERVICE.C

//
// This program installs the PROCSRV.exe into the Service Control Manager.  It also
// adds a service entry to the SQL Service Manager.
// The service name "ProcSrv" will be added to the registry under the tree
// "\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SQLServer\"
//
// Copyright 1994, Microsoft.
//

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PROCSRV             "ProcSrv"
#define PROCSRV_KEY_PATH    "SOFTWARE\\Microsoft\\MSSQLServer\\ProcSrv\\"
#define SQL_SM_KEY          "SOFTWARE\\Microsoft\\MSSQLServer\\SQL Service Manager\\"

#define NAMED_PIPE_DLL      "SSNMPN60"
#define NAMED_PIPE_ADDRESS  "\\\\.\\pipe\\sql\\query"
#define TCP_IP_DLL          "SSMSSO60"
#define TCP_ADDRESS         "1433"

void AddToRegistry();
void RemoveFromRegistry();
BOOL DuplicateEntry();
void RefreshStoplight();

void main( int argc, char ** argv )
{
    if( argc != 2 )
        goto Usage;

    if( !stricmp("add", argv[1]) )
                AddToRegistry();
        else if( !stricmp("remove", argv[1]) )
                RemoveFromRegistry();
    else
        goto Usage;

    RefreshStoplight();
    return;

Usage:
        printf( "Usage: service {add|remove}\n"
                    "  where \"add\" adds ProcSrv and \"remove\" removes it\n" );
}

// **************************************************************************
//
//  FUNCTION: AddToRegistry()
//
//  PURPOSE:  Add ProcSrv to Registry and Service Control Manager
//
// **************************************************************************

void AddToRegistry()
{
    int    i;
    HKEY   hKey;
    HKEY   hKeySM;
    DWORD  dwDisposition;
    DWORD  dwSize = 0;
    DWORD  dwSizeSM;
    DWORD  dwType;
    DWORD  dwPathLength;
    char   szPath[1024];
    char   szSMvalues[1024];
    char   szSaveListenOn[1024];
    char * szSaveListenOnPos = szSaveListenOn;

    SC_HANDLE hSCM;     // Handle to opened Service Control Manager
    SC_LOCK   LockSCM;  // Lock of Service Control Manager
    SC_HANDLE hSrvc;    // Handle to service

    // Check for existance of "ProcSrv" in registry
    //
    if( DuplicateEntry() )
    {
        printf( "Duplicate entry 'ProcSrv' found in Registry, remove 'ProcSrv' from Registry\n" );
        return;
    }

    // ======== Update SQL Service Manager info ===========
    //
    if( RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                       SQL_SM_KEY,
                       0,
                       NULL,
                       REG_OPTION_NON_VOLATILE,
                       KEY_ALL_ACCESS,
                       NULL,
                       &hKeySM,
                       &dwDisposition)
    != ERROR_SUCCESS )
        goto AddExit2;

    dwSizeSM = sizeof( szSMvalues );

    // Get list of SQL Service Manager entries
    //
    if( RegQueryValueEx(hKeySM,
                        "Services",
                        NULL,
                        &dwType,
                        szSMvalues,
                        &dwSizeSM)
    != ERROR_SUCCESS )
    {
        dwSizeSM = 1;
        szSMvalues[0] = '\0';
    }

    if( (dwSizeSM+strlen(PROCSRV)+1) < sizeof(szSMvalues) )
    {
        // Append new value to list of SQL Service Manager entries.
        //
        memcpy( (szSMvalues+dwSizeSM-1),
                PROCSRV,
                (strlen(PROCSRV)+1) );
        dwSizeSM += strlen(PROCSRV);
        szSMvalues[dwSizeSM] = '\0';
        dwSizeSM++;
    }

    // Note that we delay write of new values to Stoplight Registry entry.
    // First well will try to update the Service Control Manager.

    // ==== Write entry to Service Control Manager ====
    //
    hSCM = OpenSCManager( NULL,
                          "ServicesActive",
                          SC_MANAGER_ALL_ACCESS );
    if( hSCM == NULL )
        goto AddExit4;

    i = 0;
    while( TRUE )
    {
        LockSCM = LockServiceDatabase( hSCM );

        if( LockSCM != NULL )
            break;

        if( i++ >= 5 )
            goto AddExit3;  // Can't lock database

        Sleep( 1000 );  // sleep for a second
    }

    // Get the path of this process.  We'll use it to constuct the path of the
    // ProcSrv.exe
    //
    szPath[0] = '\0';
    dwPathLength = GetModuleFileName( GetModuleHandle(NULL), szPath, sizeof(szPath) );

    // Stip off process name (i.e. "service.exe")
    //
    while( dwPathLength > 1 )
    {
        --dwPathLength;
        if( szPath[dwPathLength] == '\\' || szPath[dwPathLength] == ':' )
        {
                dwPathLength++;
                szPath[dwPathLength] = '\0';    // Null terminate after the back slash
                break;
        }
    }

    // Append "procsrv.exe" to path
    //
    strcat( szPath, PROCSRV );
    strcat( szPath, ".exe" );

    // Let's create the service entry
    //
    hSrvc = CreateService( hSCM,
                           PROCSRV,  // Service name to start
                           PROCSRV,  // Display name
                           SERVICE_ALL_ACCESS,
                           SERVICE_WIN32_OWN_PROCESS,
                           SERVICE_DEMAND_START,
                           SERVICE_ERROR_NORMAL,
                           szPath,
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           NULL );
    if( hSrvc == NULL )
        goto AddExit3;

    CloseServiceHandle( hSrvc );
    UnlockServiceDatabase( LockSCM );
    CloseServiceHandle( hSCM );

    // Now we'll update Registry with new ProcSrv entry
    //
    if( RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                       PROCSRV_KEY_PATH,
                       0,
                       NULL,
                       REG_OPTION_NON_VOLATILE,
                       KEY_ALL_ACCESS,
                       NULL,
                       &hKey,
                       &dwDisposition)
    != ERROR_SUCCESS )
        goto AddExit2;

    // ==== Update ListenOn ====
    //
    strcpy( szSaveListenOnPos, NAMED_PIPE_DLL );
    strcat( szSaveListenOnPos, "," );
    strcat( szSaveListenOnPos, NAMED_PIPE_ADDRESS );

    dwSize += (strlen(szSaveListenOnPos) + 1);
    szSaveListenOnPos = szSaveListenOn + dwSize;

    strcpy( szSaveListenOnPos, TCP_IP_DLL );
    strcat( szSaveListenOnPos, "," );
    strcat( szSaveListenOnPos, TCP_ADDRESS );

    dwSize += (strlen(szSaveListenOnPos) + 1);
    szSaveListenOnPos = szSaveListenOn + dwSize;

    szSaveListenOn[dwSize] = '\0';
    dwSize++;

    if( RegSetValueEx(hKey,
                      "ListenOn",
                      0,
                      REG_MULTI_SZ,
                      szSaveListenOn,
                      dwSize)
    != ERROR_SUCCESS )
        goto AddExit1;

    // ==== Complete the update of the SQL Service Manager info ============
    // We do it here to avoid a duplicate entry if other registry updates
    // fail.
    //
    if( RegSetValueEx(hKeySM,
                      "Services",
                      0,
                      REG_MULTI_SZ,
                      szSMvalues,
                      dwSizeSM)
    != ERROR_SUCCESS )
    {
        RegCloseKey( hKeySM );
        goto AddExit1;
    }

    RegFlushKey( hKeySM );
    RegCloseKey( hKeySM );

    return;

AddExit1:
    RegCloseKey( hKey );

AddExit2:
    printf( "Unable to Update Registry\n" );
    return;

AddExit3:
    UnlockServiceDatabase( LockSCM );
    CloseServiceHandle( hSCM );

AddExit4:
    printf( "Unable to Update Service Control Manager\n" );
    return;
}

// **************************************************************************
//
//  FUNCTION: RemoveFromRegistry()
//
//  PURPOSE:  Remove ProcSrv From Registry and Service Control Manager
//
// **************************************************************************

void RemoveFromRegistry()
{
    int    i;
    HKEY   hKey;
    DWORD  dwSizeSM;
    DWORD  dwType;
    char   szSMvalues[1024];
    char * szSMvaluesPos;

    SC_HANDLE hSCM;     // Handle to opened Service Control Manager
    SC_LOCK   LockSCM;  // Lock of Service Control Manager
    SC_HANDLE hSrvc;    // Handle to service

    // Check for non-existance of "ProcSrv" in registry
    //
    if( !DuplicateEntry() )
    {
        printf( "'ProcSrv' not found in Registry\n" );
        return;
    }

    // =============== Remove 'ProcSrv' key from Registry =============
    //
    RegDeleteKey( HKEY_LOCAL_MACHINE, PROCSRV_KEY_PATH );

    // =============== Update SQL Service Manager info ================
    //
    if( RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                     SQL_SM_KEY,
                     0,
                     KEY_ALL_ACCESS,
                     &hKey)
    != ERROR_SUCCESS )
        goto RemoveExit2;

    dwSizeSM = sizeof( szSMvalues );

    // Get SQL Service Manager entries
    //
    if( RegQueryValueEx(hKey,
                        "Services",
                        NULL,
                        &dwType,
                        szSMvalues,
                        &dwSizeSM)
    != ERROR_SUCCESS )
        goto RemoveExit1;       // No SQL Service Manager entries.

    szSMvaluesPos = szSMvalues;

    while( szSMvaluesPos < (szSMvalues + dwSizeSM) )
    {
        if( !stricmp(szSMvaluesPos, PROCSRV) )
        {
                // Remove 'ProcSrv' from SQL Service Manager entries.
                //
                memcpy( szSMvaluesPos,
                        szSMvaluesPos + sizeof(PROCSRV),
                        ((szSMvalues + dwSizeSM) - (szSMvaluesPos + sizeof(PROCSRV))) );

            dwSizeSM -= sizeof( PROCSRV );
                break;
        }

        // 'ProcSrv' entry not found, position to next entry.
        //
        szSMvaluesPos += (strlen( szSMvaluesPos ) + 1);
    }

    // Write back out new list of SQL Service Manager entries.
    //
    if( RegSetValueEx(hKey,
                      "Services",
                      0,
                      REG_MULTI_SZ,
                      szSMvalues,
                      dwSizeSM)
    != ERROR_SUCCESS )
    {
        RegCloseKey( hKey );
        goto RemoveExit1;
    }

    RegFlushKey( hKey );
    RegCloseKey( hKey );

    // ==== Remove entry from Service Control Manager ====
    //
    hSCM = OpenSCManager( NULL,
                          "ServicesActive",
                          SC_MANAGER_ALL_ACCESS );
    if( hSCM == NULL )
        goto RemoveExit4;

    i = 0;
    while( TRUE )
    {
        LockSCM = LockServiceDatabase( hSCM );

        if( LockSCM != NULL )
            break;

        if( i++ >= 5 )
            goto RemoveExit3;   // Can't lock database

        Sleep( 1000 );  // sleep for a second
    }

    // Let's remove the service entry
    //
    hSrvc = OpenService( hSCM,
                         PROCSRV,       // Service name
                         SERVICE_ALL_ACCESS );
    if( hSrvc == NULL )
        goto RemoveExit3;

    DeleteService( hSrvc );

    CloseServiceHandle( hSrvc );
    UnlockServiceDatabase( LockSCM );
    CloseServiceHandle( hSCM );

    return;

RemoveExit1:
    RegCloseKey( hKey );

RemoveExit2:
    printf( "Unable to Update Registry\n" );
    return;

RemoveExit3:
    UnlockServiceDatabase( LockSCM );
    CloseServiceHandle( hSCM );

RemoveExit4:
    printf( "Unable to Update Service Control Manager\n" );
    return;
}

// **************************************************************************
//
//  FUNCTION: DuplicateEntry
//
//  PURPOSE:  Determines if a duplicate "ProcSrv" exist in
//            the registry.
//
//  RETURNS:
//          TRUE if name exist, else
//          FALSE if name not found.
//
//
// **************************************************************************

BOOL DuplicateEntry()
{
    HKEY     hKey;

    if( RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                     PROCSRV_KEY_PATH,
                     0,
                     KEY_ENUMERATE_SUB_KEYS,
                     &hKey)
    != ERROR_SUCCESS )
        return FALSE;

    RegCloseKey( hKey );
    return TRUE;
}

// **************************************************************************
//
//  FUNCTION: RefreshStoplight()
//
//  PURPOSE:  This routine causes the SQL Service Manager to refresh its
//            list.
//
// **************************************************************************

void RefreshStoplight()
{
    HANDLE hEvent;

    hEvent = OpenEvent( EVENT_MODIFY_STATE,
                        TRUE,
                        "MICROSOFTSQLServiceControlManagerRefresh" );
    if( hEvent )
    {
        SetEvent( hEvent );
        CloseHandle( hEvent );
    }
}


XP.C

// This is an example of an extended procedure DLL built with Open Data
// Services. The functions within the DLL can be invoked by using the extended
//stored procedures support in SQL Server.  To register the functions
// and allow all users to use them run the ISQL script INSTXP.SQL.
//
// For further information on Open Data Services refer to the Microsoft Open
// Data Services Programmer's Reference.
//
//  The extended procedures implemented in this DLL are:
//
//  XP_PROCLISTReturns all this DLL's extended stored procedures and
//their usuage.
//
// XP_DISKLISTReturns a row for each defined drive containing its name
//and the amount of disk space available.
//
//  XP_DISKFREE   Returns the amount of space available on a given drive.
//             The value is placed into the defined return parameter of
//             the stored procedure.
//
//  XP_SCAN_XBASEReads an xBase file and sends it to the client as if it
//were a SQL Server query result set (the equivalent of a
//'SELECT * FROM tablename' SQL statement).
//
//  XP_ECHOUsed to demo the handling of output parameters in
//extended procedures.
//
//  XP_TRACEUsed to show the installation and removal of pre-handlers
//for language and rpc events. The pre-handlers installed
//just print langauge and rpc events to the console in
//readable form.  Start SQL Server at the command line
//to see the output.
//

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <windows.h>
#include <srv.h>
#include <time.h>

// Miscellaneous defines
//
#define  XP_NOERROR0
#define  XP_ERROR1

#define  MAXNAME       31// Maximum extended procedure name length
#define  MAXLEN        80// Maximum string length
#define  COMMAND_PARAM1// Command Parameter
#define  OUTPUT_PARAM2// Command Parameter

// Extended procedure error codes
//
#define  SRV_MAXERROR           20000
#define  XP_PROCLIST_ERROR      SRV_MAXERROR + 1
#define  CMDSHELL_ERROR         SRV_MAXERROR + 2
#define  DISKLIST_ERROR         SRV_MAXERROR + 3
#define  SCAN_XBASE_ERROR       SRV_MAXERROR + 4
#define  TRACE_ERROR      SRV_MAXERROR + 6

// Standard error macro for reporting API errors
//
#define SETERROR( api, retstring)               \
    sprintf(retstring,"%s: Error %d from %s on line %d\n",  \
        __FILE__, GetLastError(), api, __LINE__);

// SCAN_XBASE defines
//
#define XBASE_HDR_SIZE  32
#define XBASE_MAX_COLUMNS 128
#define XBASE_BUF_SIZE    2048

// function prototypes for xp_trace callbacks
SRVRETCODE trace_lang(SRV_PROC *);
SRVRETCODE trace_rpc(SRV_PROC *);

// Stored Procedure information structure.
//
typedef struct xp_info {
    DBCHAR name[MAXNAME];   // Extended procedure name
    DBCHAR usage[MAXLEN];   // Usage string
} XP_INFO;

// Array of Extended Stored Procedures supported by this DLL.
//
XP_INFO Xps[] =
{
    "xp_proclist",// Procedure name
    "usage: xp_proclist", // Procedure usage string

    "xp_disklist",
    "usage: xp_disklist",

    "xp_diskfree",
    "usage: xp_diskfree <[@drive =] drive letter> [,] <[@space =] free space>",

    "xp_scan_xbase",
    "usage: xp_scan_xbase <xbase file name>",

    "xp_echo",
    "usage: xp_echo @p1=<input> , @p2=<output-param> output",

    "xp_trace",
    "usage: xp_trace <[@state=] 'on'|'off'> [, [@users=] NULL | 'all']",
};
#define Xpcnumber sizeof(Xps) / sizeof(XP_INFO)

//
// PROCLIST
//    Returns the usage for all defined stored procedures
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//    Returns a result set to client
//
SRVRETCODE xp_proclist(srvproc)
SRV_PROC *srvproc;
{
    int paramnum;
    DBCHAR colname1[MAXNAME];
    DBCHAR colname2[MAXNAME];
    int i;

    // Get number of parameters
    //
    paramnum = srv_rpcparams(srvproc);

    // Check number of parameters
    //
    if (paramnum != -1) {
        // Send error message and return
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, XP_PROCLIST_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error executing extended stored procedure: Invalid Parameter",
                    SRV_NULLTERM);
   // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
   // result set of an Extended Stored Procedure.
   //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

    sprintf(colname1, "spname");
    srv_describe(srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, MAXNAME,
                 SRVCHAR, 0, NULL);

    sprintf(colname2, "spusage");
    srv_describe(srvproc, 2, colname2, SRV_NULLTERM, SRVCHAR, MAXLEN, SRVCHAR,
                 0, NULL);

    // Return each XP handler as a row
    //
    for (i = 0; i < Xpcnumber; i++) {
        srv_setcoldata(srvproc, 1, Xps[i].name);
        srv_setcollen(srvproc, 1, strlen(Xps[i].name));

        srv_setcoldata(srvproc, 2, Xps[i].usage);
        srv_setcollen(srvproc, 2, strlen(Xps[i].usage));

        srv_sendrow(srvproc);
    }
 // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
 // result set of an Extended Stored Procedure.
 //
    srv_senddone(srvproc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, i);

    return(XP_NOERROR);
}

//
// XP_ECHO
//Used to demo the handling of output parameters in extended procedures.
//The first parameter should be the input parameter and the second
//parameter should be the output.  The input parameter is placed in the
//output parameter and returned.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
//

SRVRETCODE xp_echo(srvproc)
SRV_PROC *srvproc;
{
    int paramnum;

    // Check number of parameters
    //
    if ((paramnum = srv_rpcparams(srvproc)) != 2) {
        // Send error message and return
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, CMDSHELL_ERROR, SRV_INFO, (DBTINYINT)0,
    NULL, 0, 0, "Error executing extended stored procedure: Invalid # of Parameters",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

    // Set output parameter to input parameter.
    //

    srv_paramset(srvproc, 2, srv_paramdata(srvproc,1), srv_paramlen(srvproc,1));

    srv_senddone(srvproc, SRV_DONE_MORE, 0, 0);

    return(XP_NOERROR);

}

//
// XP_DISKLIST
//     Returns a row for each defined drive containing its name and the
//     amount of disk space available.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//     Returns a result set to client
//
SRVRETCODE xp_disklist(srvproc)
SRV_PROC *srvproc;
{
    int paramnum;
    DBCHAR colname1[MAXNAME];
    DBCHAR colname2[MAXNAME];
    DBCHAR drivename;
    DBCHAR rootname[16];
    int drivenum;
    int secPerCluster;
    int bytesPerSector;
    int freeClusters;
    int totalClusters;
    int drivenums;
    int space_remaining;
    int i = 0;

    // Get number of parameters
    //
    paramnum = srv_rpcparams(srvproc);

    // Check number of parameters
    //
    if (paramnum != -1) {
        // Send error message and return
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, DISKLIST_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error executing extended stored procedure: Invalid Parameter",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }
    sprintf(colname1, "drive");
    srv_describe(srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, 1, SRVCHAR, 1,
                 (BYTE *)&drivename);

    sprintf(colname2, "bytes free");
    srv_describe(srvproc, 2, colname2, SRV_NULLTERM, SRVINT4, 4, SRVINT4, 4,
                 (BYTE *)&space_remaining);

    drivenums = GetLogicalDrives();

    drivenums >>= 2;        //Ignore drives A and B
    for (drivename = 'C', drivenum = 3; drivename <= 'Z';
         drivename++, drivenum++) {

        if (drivenums & 1) {
            i++;
            sprintf(rootname, "%c:\\", drivename);
            GetDiskFreeSpace(rootname, &secPerCluster, &bytesPerSector,
                             &freeClusters, &totalClusters);
            space_remaining = secPerCluster * freeClusters * bytesPerSector;

            srv_sendrow(srvproc);
        }
        drivenums >>= 1;
    }
    // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
    // result set of an Extended Stored Procedure.
    //
    srv_senddone(srvproc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, i);
    return(XP_NOERROR);
}

//
// XP_DISKFREE
//    Returns the amount of space available on a given drive. The value
//     is placed into the defined return parameter of the stored procedure.
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//    Returns messages and/or a result set to client. Returns a value in the
//     defined return parameter.
//
SRVRETCODE xp_diskfree(srvproc)
SRV_PROC *srvproc;
{
    DBCHAR *drive;
    DBCHAR colname1[MAXNAME];
    DBCHAR colname2[MAXNAME];
    DBINT paramlength;
    DBCHAR rootname[16];
    DBCHAR bErrorMsg[MAXLEN];

    int drivenum;
    int drivenums;
    int secPerCluster;
    int bytesPerSector;
    int freeClusters;
    int totalClusters;
    int space_remaining = -1;

    // Check number of parameters
    //
    if (srv_rpcparams(srvproc) > 0) {

// Allocation local storage for drive name.
//
paramlength = srv_paramlen(srvproc, 1);
drive = (DBCHAR *)malloc(paramlength);
if (!drive) {

    SETERROR("Malloc", bErrorMsg);
    srv_sendmsg(srvproc, SRV_MSG_ERROR, CMDSHELL_ERROR, SRV_INFO, (DBTINYINT)0,
       NULL, 0, 0, bErrorMsg, SRV_NULLTERM);
    // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
    // result set of an Extended Stored Procedure.
    //
    srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
    return(XP_ERROR);
}

// Fetch drive letter.
srv_bmove(srv_paramdata(srvproc, 1), drive, paramlength);
drive = strupr(drive);

    } else

// Default drive is C.
drive = "C";

    drivenums = GetLogicalDrives();
    drivenum = drive[0] - 'A' + 1;

    drivenums >>= drivenum - 1; //Ignore drives A and B
    if (drivenums & 0x01) {

        sprintf(rootname, "%c:\\", drive[0]);
        GetDiskFreeSpace(rootname, &secPerCluster, &bytesPerSector,
                         &freeClusters, &totalClusters);

        space_remaining = secPerCluster * freeClusters * bytesPerSector;

    }

    // Process return values
    //
    if (srv_paramstatus(srvproc, 2) < 0 ) {
// Setup column heading
sprintf(colname1, "drive");
sprintf(colname2, "space");
srv_describe(srvproc, 1, colname1, SRV_NULLTERM, SRVCHAR, 1, SRVCHAR, 1,
     (BYTE *)drive);
srv_describe(srvproc, 2, colname2, SRV_NULLTERM,
     SRVINT4, sizeof(space_remaining), SRVINT4,
     sizeof(space_remaining), &space_remaining);

srv_sendrow(srvproc);
srv_senddone(srvproc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1);

   }
    else {
srv_paramset(srvproc, 2, (BYTE *)&space_remaining, 4);
srv_senddone(srvproc, SRV_DONE_MORE, 0, 0);
    }

    return(XP_NOERROR);
}

//
// XP_SCAN_XBASE
//    Reads an xBase file and sends it to the client as if it were a SQL
//    Server query result set (the equivalent of a 'SELECT * FROM
//    tablename' SQL statement).
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//    Returns messages and/or a result set to client
//
SRVRETCODE xp_scan_xbase(srvproc)
SRV_PROC *srvproc;
{
    int paramnum;
    DBINT paramtype;
    DBCHAR *filename;
    FILE *xbasefile;
    size_t count;
    char buffer[XBASE_BUF_SIZE];
    short numrecords;
    short headerlength;
    short recordlength;
    short lengthlist[XBASE_MAX_COLUMNS];
    int i;
    short j;
    short position;
    short numcolumns;

    // Get number of parameters
    //
    paramnum = srv_rpcparams(srvproc);

    // Check number of parameters
    //
    if (paramnum != 1) {
        // Send error message and return
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error executing extended stored procedure: Invalid Parameter",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

    // Check parameters for correct type
    //
    paramtype = srv_paramtype(srvproc, paramnum);
    if (paramtype != SRVVARCHAR) {
        // Send error message and return
        //
        srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0,
                    "Error executing extended stored procedure: Invalid Parameter Type",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }
    filename = srv_paramdata(srvproc, 1);

    // now read the database header info
    //
    if ((xbasefile = fopen(filename, "r")) == NULL) {
        srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }
    count = fread(buffer, XBASE_HDR_SIZE, 1, xbasefile);

    if (count == 0) {
        srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
        fclose(xbasefile);
return(XP_ERROR);
    }
    numrecords = *((short *)&buffer[4]);
    headerlength = *((short *)&buffer[8]);
    recordlength = *((short *)&buffer[10]);
    numcolumns = (headerlength - 32 - 1) / 32;

    // now get the column header information
    //
    for (j = 0; j < numcolumns; j++) {
        count = fread(buffer,
        XBASE_HDR_SIZE,
        1,
        xbasefile);
        if (count == 0) {
            srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                        NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
         // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
         // result set of an Extended Stored Procedure.
         //
            srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
            fclose(xbasefile);
    return(XP_ERROR);
        }

        // we need to NULL terminate the column name (if it is a
        // full 11 characters int)
        //
        buffer[11] = '\0';

        // now find our the column length for this data buffer
        //
        lengthlist[j] = (short)buffer[16];

        // now 'describe' this column
        //
        srv_describe(srvproc, j + 1,// column number
                buffer,     // pointer to column name
                SRV_NULLTERM,// column name is NULL terminated
                SRVCHAR,    // datatype is char (xBase numbers are ASCII
                lengthlist[j],// column length
                SRVCHAR,    // destination datatype is also char
                lengthlist[j],// destination column length
                NULL);      // pointer to where the data will be

    }
    // now read the one byte 'column header seperator'
    //
    count = fread(buffer, 1, 1, xbasefile);
    if (count == 0) {
        srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
     //
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
        fclose(xbasefile);
return(XP_ERROR);
    }
    for (i = 0; i < numrecords; i++) {
        count = fread(buffer, recordlength, 1, xbasefile);
        if (count == 0 && !feof(xbasefile)) {
            srv_sendmsg(srvproc, SRV_MSG_ERROR, SCAN_XBASE_ERROR, SRV_INFO, (DBTINYINT)0,
                        NULL, 0, 0, "Error reading xBase file", SRV_NULLTERM);
         // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
         // result set of an Extended Stored Procedure.
         //
            srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
            fclose(xbasefile);
    return(XP_ERROR);
        }

        // check to see if this is a deleted row
        //
        if (buffer[0] == '*')
            break;

        // Now set the length and data pointers for each column
        for (j = 0, position = 1; j < numcolumns; j++) {
            srv_setcollen(srvproc, j + 1, lengthlist[j]);
            srv_setcoldata(srvproc, j + 1, &buffer[position]);
            position += lengthlist[j];
        }

        // send the row to the client.
        //
        srv_sendrow(srvproc);
    }
 // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
 // result set of an Extended Stored Procedure.
 //
    srv_senddone(srvproc, SRV_DONE_COUNT | SRV_DONE_MORE, 0, i);
    fclose(xbasefile);
    return(XP_NOERROR);
}

//
// XP_TRACE
//Used to demo the proc-specific pre-handlers in extended procedures.
//Parameter 1 is @state, "on" or "off" to install or remove.
//Parameter 2 is @users, NULL to install pre-handlers on this
//srvproc only , or 'all' to install server-wide pre-handlers
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//

SRVRETCODE xp_trace(srvproc)
SRV_PROC *srvproc;
{
    int      numparams;
    DBCHAR * paramname;
    int      i;
    int      position;
    int      len;
    DBINT    paramtype;
    DBINT    paramlength;
char state[4];
char users[4];
char timestr[9];
char *errmsg;
SRV_SERVER *server;
intretcode;

state[0] = '\0';
users[0] = '\0';

    // Check parameters
// Support both named and positional parameters
//
numparams = srv_rpcparams( srvproc );

    if ((numparams < 1) || (numparams > 2)) {
        errmsg = "Error: Invalid number of Parameters";
goto ErrorExit;
    }

    for( i = 1; i <= numparams; i++ )
    {
        paramname = srv_paramname( srvproc, i, &len );

        if( len )
        {
            if( !stricmp(paramname, "@state") )
                position = 1;
            else if( !stricmp(paramname, "@users") )
                position = 2;
            else {
                srv_sendmsg( srvproc,SRV_MSG_ERROR,TRACE_ERROR,
                             16,(DBTINYINT)1,NULL,0,0,"Invalid Parameter",SRV_NULLTERM );
                return FAIL;
            }
        }
        else
            position = i;

        paramtype   = srv_paramtype( srvproc, i );
        if ( (paramlength = srv_paramlen( srvproc, i )) > 3 )
        {
errmsg = "Error:  Parameter value too long";
goto ErrorExit;
}

        switch( position )
        {
        case 1: // @state
            if( paramtype != SRVVARCHAR && paramtype != SRVNULL )
            {
        errmsg = "Error:  Invalid @state parameter type, expecting varchar";
goto ErrorExit;
            }
srv_bmove(srv_paramdata(srvproc, i), state, paramlength);
state[paramlength] = '\0';

        break;

        case 2: // @users
            if( paramtype != SRVVARCHAR && paramtype != SRVNULL )
            {
        errmsg = "Error:  Invalid @users parameter type, expecting varchar";
goto ErrorExit;
            }

srv_bmove(srv_paramdata(srvproc, i), users, paramlength);
users[paramlength] = '\0';

            if ( (stricmp(users,"all") != 0) && paramlength )
{
        errmsg = "Error:  Invalid @users parameter, must be 'all' or NULL";
goto ErrorExit;
}
break;
        } // switch( position )

    } // for( i = 1; i <= numparams; i++ )

// check for required 1st parameter
if ( (stricmp(state,"on") != 0) && (stricmp(state,"off") != 0) )
{
errmsg = "Error:  Required @state parameter must be 'on' or 'off'";
goto ErrorExit;
}

if (!stricmp(state,"on") && !strlen(users)) {
// turn on trace for just this srvproc
srv_pre_handle(NULL,srvproc,SRV_LANGUAGE,trace_lang,FALSE);
retcode = srv_pre_handle(NULL,srvproc,SRV_RPC,trace_rpc,FALSE);
switch (retcode) {
case FAIL:
errmsg = "Error installing pre-handlers";
goto ErrorExit;
case SRV_DUPLICATE_HANDLER:
errmsg = "Pre-handler already installed";
goto ErrorExit;
default:
printf("%s  starting trace for KPID %s\n", _strtime(timestr),
srv_pfield(srvproc, SRV_SPID, NULL));
}
}
else if (!stricmp(state,"off") && !strlen(users)) {
// set remove flag to true
srv_pre_handle(NULL,srvproc,SRV_LANGUAGE,trace_lang,TRUE);
retcode = srv_pre_handle(NULL,srvproc,SRV_RPC,trace_rpc,TRUE);
switch (retcode) {
case FAIL:
errmsg = "Pre-handler not found";
goto ErrorExit;
default:
printf("%s  stopping trace for KPID %s\n", _strtime(timestr),
srv_pfield(srvproc, SRV_SPID, NULL));
}
}
else if (!stricmp(state,"on") && !stricmp(users, "all")) {
// turn on trace for all connections
server = SRV_GETSERVER(srvproc);
srv_pre_handle(server,NULL,SRV_LANGUAGE,trace_lang,FALSE);
retcode = srv_pre_handle(server,NULL,SRV_RPC,trace_rpc,FALSE);
switch (retcode) {
case FAIL:
errmsg = "Error installing pre-handlers";
goto ErrorExit;
case SRV_DUPLICATE_HANDLER:
errmsg = "Pre-handler already installed";
goto ErrorExit;
default:
printf("%s  starting trace for all connections \n", _strtime(timestr));
}
}
else if (!stricmp(state,"off") && !stricmp(users, "all")) {
// set remove flag to true
server = SRV_GETSERVER(srvproc);
srv_pre_handle(server,NULL,SRV_LANGUAGE,trace_lang,TRUE);
retcode = srv_pre_handle(server,NULL,SRV_RPC,trace_rpc,TRUE);
switch (retcode) {
case FAIL:
errmsg = "Pre-handler not found";
goto ErrorExit;
default:
printf("%s  stopping trace for all connections \n", _strtime(timestr));
}
}
else {
        errmsg = "Error in xp_trace logic";
goto ErrorExit;
}

    return(XP_NOERROR);

ErrorExit:
// Send error message and return
    //
    srv_sendmsg(srvproc, SRV_MSG_ERROR, TRACE_ERROR, SRV_INFO, (DBTINYINT)0,
    NULL, 0, 0, errmsg, SRV_NULLTERM);

    srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);

}
//****************************************************************************
// trace_lang
//      This is the pre-language handler routine to print language requests to
//stdio
//
// Parameters:
//    srvproc - a pointer to SRV_PROC stucture
//
// Returns:
//    SRV_CONTINUE
//
// Side Effects:
//
//****************************************************************************
SRVRETCODE trace_lang(srvproc)
SRV_PROC *srvproc;
{
    char timestr[9];
DBCHAR *query;
int query_len;

query= srv_langptr(srvproc);
query_len = srv_langlen(srvproc);

   //print the request
    printf("%s  KPID %s lang:  %s\n", _strtime(timestr),
    srv_pfield(srvproc, SRV_SPID, (int *)NULL),query);
if (query[query_len-1] != '\n')
printf("\n");

    return(SRV_CONTINUE);
}

//****************************************************************************
// trace_rpc
//      This is the pre-rpc handler routine to print language requests to
//stdio
//
// Parameters:
//    srvproc - a pointer to SRV_PROC stucture
//
// Returns:
//    SRV_CONTINUE
//
// Side Effects:
//
//****************************************************************************
SRVRETCODE trace_rpc(srvproc)
SRV_PROC *srvproc;
{
    int i;                  // Index variable
    short params;
    DBCHAR complete_rpc_name[4 * (MAXNAME + 1)]; //database.owner.name;#
DBCHAR *rpc_db;
DBCHAR *rpc_owner;
DBCHAR *rpc_name;
int rpc_number;
char rpc_number_char[17];
DBCHAR *rpc_paramname;
DBCHAR * rpc_paramstatus;
int rpc_paramtype;
DBINT rpc_parammaxlen;
DBINT rpc_paramlen;
void *rpc_paramdata;
char rpc_paramstr[256];
char timestr[9];
DBCHAR * rpc_typename;
DBCHAR sztypestr[20];
DBNUMERIC numeric;

    // Construct full RPC name
    //
memset (complete_rpc_name, 0, sizeof(complete_rpc_name));
rpc_db = srv_rpcdb(srvproc, (int *)NULL);
rpc_owner = srv_rpcowner(srvproc, (int *)NULL);
rpc_name = srv_rpcname(srvproc, (int *)NULL);
rpc_number = srv_rpcnumber(srvproc);

if (rpc_db != NULL) {
strcat(complete_rpc_name,rpc_db);
strcat(complete_rpc_name,".");
};
if (rpc_owner != NULL) {
strcat(complete_rpc_name,rpc_owner);
strcat(complete_rpc_name,".");
};
strcat(complete_rpc_name,rpc_name);
if (rpc_number > 0) {
    strcat(complete_rpc_name,";");
_itoa(rpc_number,rpc_number_char,10);
strcat(complete_rpc_name, rpc_number_char);
};

//start printing the request

    printf("%s  KPID %s rpc:  %s", _strtime(timestr),
    srv_pfield(srvproc, SRV_SPID, (int *)NULL),complete_rpc_name);

    // Set up any RPC parameters.
    //
    params = srv_rpcparams(srvproc);

    for (i = 1; i <= params; i++) {

rpc_paramname = srv_paramname(srvproc, i, (int *)NULL);
if (strlen(rpc_paramname) == 0) rpc_paramname = NULL;

        if ((BYTE)srv_paramstatus(srvproc, i) == SRV_PARAMRETURN)
        rpc_paramstatus = "OUT";
else
rpc_paramstatus = " ";

        rpc_paramtype = srv_paramtype(srvproc, i);
        rpc_parammaxlen = srv_parammaxlen(srvproc, i);
rpc_paramlen = srv_paramlen(srvproc, i);
       rpc_paramdata = srv_paramdata(srvproc, i);

switch (rpc_paramtype) {
case SRVVARCHAR:
sprintf(sztypestr, "%s(%d)","varchar",rpc_parammaxlen);
break;
case SRVCHAR:
sprintf(sztypestr, "%s(%d)","char",rpc_parammaxlen);
break;
case SRVINTN:
switch (rpc_paramlen) {
case 1:
rpc_typename = "tinyint";
break;
case 2:
rpc_typename = "smallint";
break;
default:
rpc_typename = "int";
break;
}
sprintf(sztypestr, "%s null",rpc_typename);
break;
case SRVINT1:
sprintf(sztypestr, "tinyint");
break;
case SRVINT2:
sprintf(sztypestr, "smallint");
break;
case SRVINT4:
sprintf(sztypestr, "int");
break;

case SRVMONEYN:
switch (rpc_paramlen) {
case 4:
rpc_typename = "smallmoney";
break;
default:
rpc_typename = "money";
break;
}
sprintf(sztypestr, "%s null",rpc_typename);
break;
case SRVMONEY4:
sprintf(sztypestr, "smallmoney");
break;
case SRVMONEY:
sprintf(sztypestr, "money");
break;

case SRVDATETIMN:
switch (rpc_paramlen) {
case 4:
rpc_typename = "smalldatetime";

break;
default:
rpc_typename = "datetime";
break;
}
sprintf(sztypestr, "%s null",rpc_typename);
break;
case SRVDATETIM4:
sprintf(sztypestr, "smalldatetime");
break;
case SRVDATETIME:
sprintf(sztypestr, "datetime");
break;

case SRVFLTN:
switch (rpc_paramlen) {
case 4:
rpc_typename = "real";
break;
default:
rpc_typename = "float";
break;
}
sprintf(sztypestr, "%s null",rpc_typename);
break;
case SRVFLT4:
sprintf(sztypestr, "real");
break;
case SRVFLT8:
sprintf(sztypestr, "float");
break;

case SRVNUMERIC:
case SRVNUMERICN:
case SRVDECIMAL:
case SRVDECIMALN:
memset (&numeric, 0, sizeof(DBNUMERIC));
memmove (&numeric, rpc_paramdata, rpc_paramlen);
rpc_paramdata = &numeric;
switch (rpc_paramtype) {
case SRVNUMERIC:
case SRVNUMERICN:
rpc_typename = "numeric";
break;
case SRVDECIMAL:
case SRVDECIMALN:
rpc_typename = "decimal";
break;
}
sprintf(sztypestr, "%s(%d,%d)",rpc_typename, numeric.precision, numeric.scale);
if (rpc_paramlen == 2)
rpc_paramlen = 0;
break;
case SRVTEXT:
sprintf(sztypestr, "text");
break;
case SRVIMAGE:
sprintf(sztypestr, "image");
break;

case SRVVARBINARY:
sprintf(sztypestr, "%s(%d)","varbinary",rpc_parammaxlen);
break;
case SRVBINARY:
sprintf(sztypestr, "%s(%d)","binary",rpc_parammaxlen);
break;
case SRVBIT:
sprintf(sztypestr, "bit");
break;

case SRVNULL:
sprintf(sztypestr, "srvnull");
break;

default:
sprintf(sztypestr, "unknown type");
break;

} // end of switch (rpc_paramtype)

        if (rpc_paramlen > 0) {
if (rpc_paramtype != SRVVARBINARY)
srv_convert(srvproc,rpc_paramtype,rpc_paramdata,
rpc_paramlen,SRVCHAR,rpc_paramstr,-1);
else
strcpy(rpc_paramstr,"<conversion not supported>");
}
else
strcpy(rpc_paramstr,"<null>");

printf("\r\n   P%3d: %-10s %-15s %4s %s",i,rpc_paramname, sztypestr, rpc_paramstatus, rpc_paramstr);

    }
printf("\n\n");

    return(SRV_CONTINUE);
}


XP_DBLIB.C

//This is an example of an extended procedure DLL built with Open Data
//Services. The functions within the DLL can be invoked by using the extended
//stored procedures support in SQL Server.  To register the functions
//and allow all users to use them run the ISQL script XP_DBLIB.SQL.
//
//For further information on Open Data Services refer to the Microsoft Open
//Data Services Programmer's Reference.
//
//  The extended procedures implemented in this DLL is:
//
//  XP_GETTABLEUsed to show the creation of a new connection to SQL Server
//using DBLIB that is bound to the initial client connection;
//retrieves all rows from the table <table_name>, which is passed
//as an input parameter
//

#defineDBNTWIN32

#include    <stdlib.h>
#include    <stdio.h>
#include    <string.h>
#include<ctype.h>
#include<time.h>
#include    <windows.h>

#include    <sqlfront.h>
#include    <sqldb.h>

#include    <srv.h>

// Miscellaneous defines
//
#define  XP_NOERROR0
#define  XP_ERROR1

#define  MAXLEN        255// Maximum string length
#define  MAXNAME        31// Maximum name length

// Extended procedure error codes
#define  SRV_MAXERROR           20000
#define GETTABLE_ERRORSRV_MAXERROR + 1
#define REMOTE_FAIL4002
#define REMOTE_MSGSRV_MAXERROR + 3

// Stored Procedure information structure.
typedef struct xp_info {
    DBCHAR name[MAXNAME];   // Extended procedure name
    DBCHAR usage[MAXLEN];   // Usage string
} XP_INFO;

int err_handler();
int msg_handler();

// Array of Extended Stored Procedures supported by this DLL.
XP_INFO Xps[] =
{

    "xp_gettable",
    "usage: xp_gettable <table_name>",
};

//
// XP_GETTABLE
//    Returns the result of the SQL statement
//select * from <table_name>
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//    Returns messages and/or a result set to client.
//

RETCODE xp_gettable(srvproc)
SRV_PROC *srvproc;
{

DBPROCESS*dbproc;// DBPROCESS handle for the new connection
LOGINREC*loginrec;

    intparamnum;
intbImpersonated;
DBCHARtable_name[MAXLEN];
DBINTparamtype;

charbindtoken[MAXLEN];
charSqlStmt[MAXLEN];

shortcols;// Data columns returned
    inti;// Index variable
    DBINTrows;// Number of rows sent
    RETCODErc;// Value returned from DB-Library calls

    // Get number of parameters
    paramnum = srv_rpcparams(srvproc);

    // Check number of parameters
    if (paramnum != 1) {
        // Send error message and return
        srv_sendmsg(srvproc, SRV_MSG_ERROR, GETTABLE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error executing extended stored procedure: Invalid Parameter",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

    // Check parameters for correct type
    paramtype = srv_paramtype(srvproc, paramnum);
    if (paramtype != SRVVARCHAR) {
        // Send error message and return
        srv_sendmsg(srvproc, SRV_MSG_ERROR, GETTABLE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0,
                    "Error executing extended stored procedure: Invalid Parameter Type",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

// Terminate parameter string with NULL
sprintf(table_name, srv_paramdata(srvproc, 1));
table_name[srv_paramlen(srvproc, 1)] = '\0';

loginrec = dblogin();

dbprocerrhandle(loginrec, (DBERRHANDLE_PROC)err_handler);
dbprocmsghandle(loginrec, (DBMSGHANDLE_PROC)msg_handler);

// Check for integrated security
if (strcmp(srv_pfield(srvproc, SRV_LSECURE, (int *)NULL), "TRUE") == 0) {
// Client has accessed using some form of integrated security
// Impersonate client and set DBSETLSECURE flag

    bImpersonated = srv_impersonate_client( srvproc );
DBSETLSECURE(loginrec);
}
else {
    // Client used standard login
    // Set the user name, password, and application name for the remote

    DBSETLUSER( loginrec, srv_pfield(srvproc, SRV_USER, (int *)NULL) );
    DBSETLPWD( loginrec, srv_pfield(srvproc, SRV_PWD, (int *)NULL) );
}

DBSETLAPP (loginrec, "xp_gettable");

    // Try to open a connection to the local DBMS.
    // Since the servername parameter is set to NULL, the connection will be
    // opened to the local DBMS

//dbproc = dbopen(loginrec, "server_name");
dbproc = dbopen(loginrec, NULL);

dbsetuserdata(dbproc, (VOID *)srvproc);

//get the client session token
srv_getbindtoken (srvproc, bindtoken);

// put the SQL statement in the command buffer
srv_bzero(SqlStmt, MAXLEN);
sprintf (SqlStmt, "exec sp_bindsession \'%s\' ", bindtoken);

dbcmd (dbproc, SqlStmt);
dbsqlexec(dbproc);

while ((rc = dbresults (dbproc)) == NO_MORE_RESULTS);

// put the SQL statement in the command buffer
sprintf (SqlStmt, "select * from %s ", table_name);
rc=dbcmd (dbproc, SqlStmt);

//send the command buffer to SQL Server for execution
rc=dbsqlexec(dbproc);

// Process the results
while ((rc = dbresults (dbproc)) != NO_MORE_RESULTS)
{

        // How many data columns are in the row?
cols = dbnumcols (dbproc);

        // Build the row description for the client return.
for (i = 1; i <= cols; i++) {

// Call srv_describe for each column that will be sent back via ODS
//to the caller of the extended procedure
srv_describe (srvproc, i, (DBCHAR *)dbcolname (dbproc, i), SRV_NULLTERM,
                   (DBINT)dbcoltype (dbproc, i), dbcollen (dbproc, i),
                   (DBINT)dbcoltype (dbproc, i), dbcollen (dbproc, i),
                   (BYTE *)NULL);

}
rows = 0;  // Initialize a counter for the row number.
while (dbnextrow (dbproc) != NO_MORE_ROWS){

// For each data field in the current row, fill the structure that will
//    be sent back to the caller of the extended procedure
for (i = 1; i <= cols; i++){
srv_setcollen (srvproc, i, (short)dbdatlen (dbproc, i));
        srv_setcoldata (srvproc, i, dbdata(dbproc, i));
}

// Send the data row back to SQL Server via ODS
if (srv_sendrow (srvproc) == SUCCEED)
rows++;                        // Go to the next row.
}
}

// Send the final done packet for the execution of the command
// batch. The previous batch was one that might have returned
// rows, so set the DONE status accordingly.
if (rows > 0)
srv_senddone (srvproc, SRV_DONE_COUNT | SRV_DONE_MORE,
              (DBUSMALLINT) 0, rows);
else
srv_senddone (srvproc, SRV_DONE_MORE, (DBUSMALLINT) 0, (DBINT) 0);

    // Close the connection to SQL Server.
    dbclose( dbproc );
    dbfreelogin( loginrec );

    // Revert back to SQL Server's user account
    //
    if( bImpersonated )
        srv_revert_to_self(srvproc);

return(XP_NOERROR);

}

int err_handler(dbproc, severity, dberr, oserr, dberrstr, oserrstr)
DBPROCESS*dbproc;
intseverity;
intdberr;
intoserr;
char*dberrstr;
char*oserrstr;
{

SRV_PROC *srvproc = (SRV_PROC *)NULL;

srvproc = (SRV_PROC *)dbgetuserdata(dbproc);

srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)REMOTE_MSG,
(DBTINYINT)severity, (DBTINYINT)0, NULL, 0, 0, dberrstr,
SRV_NULLTERM);

if ((dbproc == NULL) || (DBDEAD(dbproc)))
return(INT_EXIT);

return(INT_CANCEL);
}

int msg_handler(dbproc, msgno, msgstate, severity, msgtext)
DBPROCESS*dbproc;
DBINTmsgno;
intmsgstate;
intseverity;
char*msgtext;
{

SRV_PROC *srvproc = (SRV_PROC *)NULL;

srvproc = (SRV_PROC *)dbgetuserdata(dbproc);

if (severity < 10) {    // if informational message

srv_sendmsg(srvproc, SRV_MSG_INFO, msgno, (DBTINYINT)severity,
               (DBTINYINT)msgstate, NULL, 0, 0, (DBCHAR *)msgtext, SRV_NULLTERM);
return(0);
}

    // Trap login fail message
    if (msgno == REMOTE_FAIL){
// Send a message to the client that
// the remote connection failed.
//
srv_sendmsg(srvproc, SRV_MSG_ERROR, (DBINT)msgno, (DBTINYINT)severity,
                 (DBTINYINT)msgstate, NULL, 0, 0,
                 "Login to remote DBMS failed (dbopen).", SRV_NULLTERM);
    }

     // must be an error message
srv_sendmsg(srvproc, SRV_MSG_ERROR, msgno, (DBTINYINT)severity,
     (DBTINYINT)msgstate, NULL, 0, 0, (DBCHAR *)msgtext, SRV_NULLTERM);

return(0);

}


XP_ODBC.C

// This is an example of an extended procedure DLL built with Open Data
// Services. The functions within the DLL can be invoked by using the extended
//stored procedures support in SQL Server.  To register the functions
// and allow all users to use them run the ISQL script XP_ODBC.SQL.
//
// For further information on Open Data Services refer to the Microsoft Open
// Data Services Programmer's Reference.
//
//  The extended procedures implemented in this DLL is:
//
//  XP_GETTABLEUsed to show the creation of a new connection to SQL Server
//using ODBC that is bound to the initial client connection
//

#defineDBNTWIN32

#include    <stdlib.h>
#include    <stdio.h>
#include    <string.h>
#include<ctype.h>
#include<time.h>
#include    <windows.h>
#include    <sql.h>
#include    <sqlext.h>
#include    <srv.h>
#include    <odbcss.h>

// Miscellaneous defines
//
#define XP_NOERROR0
#define XP_ERROR1

#define MAXNAME31// Maximum extended procedure name length
#define MAXLEN80// Maximum string length
#define COMMAND_PARAM1// Command Parameter
#define OUTPUT_PARAM2// Command Parameter

#defineSQLCHAR 0x2f//used by srv_describe

// Extended procedure error codes
//
#define SRV_MAXERROR20000
#define GETTABLE_ERRORSRV_MAXERROR + 1
#defineCOMPUTE_ROWSRV_MAXERROR + 8
#defineREMOTE_MSGSRV_MAXERROR + 9
#defineSEND_FAILEDSRV_MAXERROR + 10

#define REMOTE_FAIL4002

// #define Xpcnumber sizeof(Xps) / sizeof(XP_INFO)

#define NAME_LEN30
#define MAXCOLS30

// Stored Procedure information structure.
//
typedef struct xp_info {
    DBCHAR name[MAXNAME];   // Extended procedure name
    DBCHAR usage[MAXLEN];   // Usage string
} XP_INFO;

typedef BYTE * dataptr ;

// Array of Extended Stored Procedures supported by this DLL.
//
XP_INFO Xps[] =
{
    "xp_gettable",
//    "usage: xp_gettable <@pubs_table_name>",
    "usage: xp_gettable",
};

void handle_odbc_err(char *,RETCODE ,DBINT ,HENV ,HDBC ,HSTMT ,SRV_PROC *);

//
// XP_GETTABLE
//    Returns the result of the SQL statement
//select * from <table_name>
//
// Parameters:
//    srvproc - the handle to the client connection that got the SRV_CONNECT.
//
// Returns:
//    XP_NOERROR
//    XP_ERROR
//
// Side Effects:
//    Returns messages and/or a result set to client.
//

RETCODE xp_gettable(srvproc)
SRV_PROC *srvproc;
{

HENVhenv= SQL_NULL_HENV;
HDBC hdbc= SQL_NULL_HDBC;
HSTMThstmt= SQL_NULL_HSTMT;
SWORDColNameLen[MAXCOLS];
SDWORDcbName= SQL_NTS;
UDWORDFAR ColLen[MAXCOLS];
UDWORDFAR ColLenT[MAXCOLS];
UCHARColName[NAME_LEN];
RETCODErc;
SDWORDnresultcols;// number of result columns
dataptrdata[MAXCOLS];
DBINTrows=0L;// number of rows sent
charbindtoken[256];
    UWORDi;// Index variable
UCHAR FAR *DSN= "local";// for integrated security to work you need to
//   specify a local server in the ODBC setting
//     in the Control Panel in Windows
charSqlStmt[500];
intbImpersonated;

PTRrgbDesc;// pointer to storage for descriptor info
SWORDcbDescMax = 2550;// max length of rgbDesc buffer
SWORD FARpcbDesc;// number of bytes returned in rgbDesc
SDWORD FARpfdesc;// pointer to storage info for numeric descriptor types
SDWORD FARSqlType[MAXCOLS];// storage info for SQL data types

UCHAR FAR uid[50];
UCHAR FAR pwd[50];

intparamnum;
DBCHARtable_name[500];
DBINTparamtype;

    // Get number of parameters
    paramnum = srv_rpcparams(srvproc);

    // Check number of parameters
    if (paramnum != 1) {
        // Send error message and return
        srv_sendmsg(srvproc, SRV_MSG_ERROR, GETTABLE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0, "Error executing extended stored procedure: Invalid Parameter",
                    SRV_NULLTERM);
     // A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
     // result set of an Extended Stored Procedure.
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

    // Check parameters for correct type
    paramtype = srv_paramtype(srvproc, paramnum);
    if (paramtype != SRVVARCHAR) {

// Send error message and return
        srv_sendmsg(srvproc, SRV_MSG_ERROR, GETTABLE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0,
                    "Error executing extended stored procedure: Invalid Parameter Type",
                    SRV_NULLTERM);

// A SRV_DONE_MORE instead of a SRV_DONE_FINAL must complete the
// result set of an Extended Stored Procedure.
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
    }

// Terminate parameter string with NULL
    sprintf(table_name, srv_paramdata(srvproc, 1));
table_name[srv_paramlen(srvproc, 1)] = '\0';

// Allocate an ODBC environment handle
rc = SQLAllocEnv(&henv);              // Environment handle
if (rc != SQL_SUCCESS) {
handle_odbc_err("SQLConnect",
rc,
(DBINT)REMOTE_FAIL,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);
    }

// Allocate an ODBC connection handle
rc = SQLAllocConnect(henv, &hdbc); // Connection handle
if (rc != SQL_SUCCESS) {
handle_odbc_err("SQLConnect",
rc,
(DBINT)REMOTE_FAIL,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);

}

// Check for integrated security
if (strcmp(srv_pfield(srvproc, SRV_LSECURE, (int *)NULL), "TRUE") == 0) {
// Client has accessed using some form of integrated security
// Impersonate client and set SQL_INTEGRATED_SECURITY option

    bImpersonated = srv_impersonate_client(srvproc);

rc=SQLSetConnectOption(hdbc, SQL_INTEGRATED_SECURITY, SQL_IS_ON);

// Connect to DSN using integrated security
rc = SQLConnect(hdbc, DSN, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS);

}
else {
    // Client used standard login
    // Set the user name, password, and application name for the remote
sprintf(uid, srv_pfield(srvproc, SRV_USER, (int*)NULL));
sprintf(pwd, srv_pfield(srvproc, SRV_PWD, (int*)NULL));
rc = SQLConnect(hdbc, DSN, SQL_NTS, uid, SQL_NTS, pwd, SQL_NTS);

}
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))
{
handle_odbc_err("SQLConnect",
rc,
(DBINT)REMOTE_FAIL,
henv,
hdbc,
SQL_NULL_HSTMT,
srvproc);
return(XP_ERROR);

}

// Process data after successful connection
rc = SQLAllocStmt(hdbc, &hstmt); // Statement handle
if (rc != SQL_SUCCESS) {
handle_odbc_err("SQLConnect",
rc,
(DBINT)REMOTE_FAIL,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);
    }

//get the client session token
rc = srv_getbindtoken(srvproc, bindtoken);
if (rc == FAIL){
srv_sendmsg(srvproc, SRV_MSG_ERROR, GETTABLE_ERROR, SRV_INFO, (DBTINYINT)0,
                    NULL, 0, 0,
                    "Error with srv_getbindtoken",
                    SRV_NULLTERM);
        srv_senddone(srvproc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
return(XP_ERROR);
}

// put the SQL statement in the command buffer
sprintf (SqlStmt, "exec sp_bindsession \'%s\' ", bindtoken);

rc=SQLExecDirect(hstmt, SqlStmt, SQL_NTS);
if (!((rc == SQL_SUCCESS) ||(rc == SQL_SUCCESS_WITH_INFO)))
{
// if query failed, notify user & acknowledge with senddone.
handle_odbc_err("SQLExecDirect",
rc,
(DBINT)SEND_FAILED,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);
}

// SELECT the result set and bind its columns to local storage
sprintf (SqlStmt, "SELECT * FROM  %s ", table_name);
rc=SQLExecDirect(hstmt, SqlStmt, SQL_NTS);
if (rc != SQL_SUCCESS) {
handle_odbc_err("SQLExecDirect",
rc,
(DBINT)SEND_FAILED,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);
    }

// if returnvalue was successful, must have results rows
SQLColAttributes(hstmt,
0,
SQL_COLUMN_COUNT,
rgbDesc,
cbDescMax,// max length of rgbDesc buffer
&pcbDesc,// number of bytes returned in rgbDesc
&nresultcols);

// Build the column description for this results set.
for (i = 1; i <= nresultcols; i++)
{
// get the column description
SQLColAttributes(hstmt,
i,
SQL_CA_SS_COLUMN_SSTYPE,
rgbDesc,
cbDescMax,// max length of rgbDesc buffer
&pcbDesc,// number of bytes returned in rgbDesc
&SqlType[i]);

SQLColAttributes(hstmt,
i,
SQL_COLUMN_NAME,
&ColName[i],// returned column name
NAME_LEN,// max length of rgbDesc buffer
&ColNameLen[i],// number of bytes returned in rgbDesc
 &pfdesc);

SQLColAttributes(hstmt,
i,
SQL_COLUMN_LENGTH,
rgbDesc,
cbDescMax,// max length of rgbDesc buffer
&pcbDesc,// number of bytes returned in rgbDesc
&ColLen[i]);// returned column length

// Over-write the column length returned by ODBC with the correct value
//    to be used by ODS

switch( SqlType[i] ){

case SQLMONEY://money
case SQLDATETIME://datetime
ColLen[i] = 8;
break;

case SQLMONEYN://moneyn
if (21 == ColLen[i])
ColLen[i] = 8;
else
ColLen[i] = 4;
break;

case SQLDATETIMN://datetimn
if (23 == ColLen[i])
ColLen[i] = 8;
else
ColLen[i] = 4;
break;

case SQLNUMERIC://numeric
case SQLNUMERICN://numericn
case SQLDECIMAL://decimal
case SQLDECIMALN://decimaln
ColLen[i] = sizeof(DBNUMERIC);
break;

case SQLMONEY4://smallmoney
case SQLDATETIM4://smalldatetime
ColLen[i] = 4;
break;

}

// allocate memory to hold each filed of data in a row
data[i] = (dataptr) malloc(ColLen[i]);
srv_bzero (data[i], ColLen[i] );

// Bind column
SQLBindCol(hstmt,
i,
SQL_C_BINARY,// no data conversion
data[i], // will hold 'fetched' data
ColLen[i], // max bytes to store in 'data'
&ColLenT[i]);// actual bytes stored by 'fetch'

// Prepare structure that will be sent via ODS back to the caller of the
//extended procedure
srv_describe(srvproc,
i,
&ColName[i],
SRV_NULLTERM,
SqlType[i],// destdata type
(DBINT) ColLen[i],// dest data length
SqlType[i], // source data type
(DBINT) ColLen[i],// source data length
(BYTE *)NULL);

}

// initialize the row counter
 rows = 0;

// get each row of data from ODBC until there are no more rows
while((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
{
if (!((SQL_SUCCESS == rc) ||(SQL_SUCCESS_WITH_INFO == rc)))
{
handle_odbc_err("SQLExecDirect",
rc,
(DBINT)SEND_FAILED,
henv,
hdbc,
hstmt,
srvproc);
return(XP_ERROR);
}

// For each data field in the current row, fill the structure that will
//    be sent back to the caller of the extended procedure
 for (i = 1; i <= nresultcols; i++)
{
if (SQL_NULL_DATA == ColLenT[i])
ColLenT[i] = 0;
srv_setcollen(srvproc, i, (int)ColLenT[i]);
srv_setcoldata(srvproc, i, data[i]);
}

// Send the data row back to SQL Server via ODS
if (srv_sendrow(srvproc) == SUCCEED)
rows++;
}

// free the data buffers
for (i=1; i<=nresultcols; i++)
(void)free(data[i]);

// free handles
SQLFreeStmt(hstmt, SQL_DROP);
SQLDisconnect(hdbc);
SQLFreeConnect(hdbc);
SQLFreeEnv(henv);

if (rows > 0)
srv_senddone(srvproc, SRV_DONE_MORE | SRV_DONE_COUNT, (DBUSMALLINT)0, rows);
else
srv_senddone(srvproc, SRV_DONE_MORE, (DBUSMALLINT)0, (DBINT)0);
return(XP_NOERROR);

// Revert back to SQL Server's user account
    //
    if( bImpersonated )
        srv_revert_to_self(srvproc);

}

// HANDLE_ODBC_ERR
//This routine is called to send messages to clients when an ODBC function
//returns what could be considered an error (e.g., SQL_ERROR,
//SQL_INVALID_HANDLE).
//
// Parameters:
//rc- The return code of the function that did not SUCCESS.
//msgnum- The ODS user message code.
//connection- The ODBC connection handle.
//statement- The ODBC statement handle.
//srvproc- Contains additional client information.
//
// Returns:
//none
//

void handle_odbc_err(char *szODBCApi,
RETCODE rc,
DBINT msgnum,
HENV henv,
HDBC hdbc,
HSTMT hstmt,
SRV_PROC *srvproc)
{
UCHAR szSQLState[6], szErrorMsg[SQL_MAX_MESSAGE_LENGTH];
SDWORD NativeError;
SWORD cbErrorMsg;
RETCODE retcode;

// if rc is SQL_SUCCESS, return without doing anything
if (rc == SQL_SUCCESS)
return;

retcode = SQLError(henv,
hdbc,
hstmt,
szSQLState,
&NativeError,
szErrorMsg,
SQL_MAX_MESSAGE_LENGTH-1,
&cbErrorMsg);

// if rc is informational, sned message, else send error to client
if (rc == SQL_SUCCESS_WITH_INFO)
{
srv_sendmsg(srvproc,
SRV_MSG_INFO,
msgnum,
(DBTINYINT)0,
(DBTINYINT)0,
NULL,
0,
0,
szErrorMsg,
SRV_NULLTERM);

printf("ODBC message(%s) = %d:%s\n", szODBCApi,rc, szErrorMsg);
}
else
{
srv_sendmsg(srvproc,
SRV_MSG_ERROR,
msgnum,
(DBTINYINT)11,
(DBTINYINT)0,
NULL,
0,
0,
szErrorMsg,
SRV_NULLTERM);

printf("ODBC error(%s) = %d:%s\n", szODBCApi,rc, szErrorMsg);
}
}


README.TXT

****************************************************************
          MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
                OLE AUTOMATION SAMPLE PROGRAMS
****************************************************************
This file describes the OLE automation sample programs included
with Microsoft(R) SQL Server(TM) Workstation System 6.5.

For additional information about SQL Server 6.5, DB-Library, and
ODBC, see the "What's New in SQL Server 6.5," "Programming
DB-Library for Visual Basic," and "Programming ODBC for SQL
Server" sections of SQL Server Books Online, which containsthe
most up-to-date information available.

****************************************************************
OLE AUTOMATION SAMPLE APPLICATIONS

The sample applications require the Microsoft(R) Visual Basic(R) 4.0
Enterprise Edition. The following Visual Basic sample programs and
Transact-SQL scripts are located in the C:\MSSQL\SAMPLES\OLEAUTO
directory by default:

GETNPV
Demonstrates the use of an OLE Automation object to call an NPV
function in Visual Basic from a trigger. Includes the following
files:
    CGETNPV.CLS
    GETNPV.VBP
    NPVMAIN.BAS
    NPVTEST.SQL
    NPVTRIG.SQL
    TESTERR.SQL

To run this application
1.  Create the test table and trigger by running the NPVTEST.SQL
    script.
2.  Open the Visual Basic project, GETNPV.VBP. From the File Menu
    choose Make EXE file or Make OLE DLL file to compile GETNPV.VBP.
3.  Run the script, NPVTEST.SQL to insert sample data into the table.
    The trigger then fires and calls the Visual Basic NPV function.
4.  Run the script, TESTERR.SQL, to show an example of the VB
    function raising a custom error message that can be retrieved by
    calling sp_OAGetErrorInfo.

LOOPBACK.DAO
Demonstrates connecting back to a SQL Server via an OLE Automation
object. The OLE Automation server retrieves a result set from a SQL
Server table and returns it to the stored procedure that called it.
In the Visual Basic project, if you make an OLE DLL file, the OA
server that is created will run in the same process space as SQL
Server. If you make an EXE file, the OA server will run as a separate
process. Includes the following files:
    CSQLDAO.CLS
    DAOMAIN.BAS
    LOOPDAO.SQL
    SQLDAO.VBP

LOOPBACK.RDO
Demonstrates connecting back to a SQL Server via an OLE Automation
object. The OLE Automation server retrieves a result set from a SQL
Server table and returns it to the stored procedure that called it.
In the Visual Basic project, if you make an OLE DLL file, the OA
server that is created will run in the same process space as SQL
Server. Ifyou make an EXE file, the OA server will run as a separate
process. Includes the following files:
    CSQLRDO.CLS
    LOOPRDO.SQL
    RDOMAIN.BAS
    SQLRDO.VBP

TRAVERSE
Transact-SQL script that demonstrates how to use traversal syntax
for object hierarchies. Uses the SQL-DMO administration objects.
Includes the following file:
    TRAVERSE.SQL


NPVMAIN.BAS

Attribute VB_Name = "modMain"
Option Explicit

Public Sub Main()

End Sub


DAOMAIN.BAS

Attribute VB_Name = "modMain"
Option Explicit

Sub main()

End Sub


RDOMAIN.BAS

Attribute VB_Name = "modMain"
Option Explicit

Public Sub Main()
    'Test
End Sub

Public Sub Test()
    Dim obj As New CSqlServerConnect

    Debug.Print ""
    Debug.Print "CSqlServerConnect::GetAuthors"
    DumpArray (obj.DaoGetAuthors)
    Debug.Print ""
    Debug.Print "CSqlServerConnect::RdoGetAuthors"
    DumpArray (obj.RdoGetAuthors)
    Debug.Print ""
    Debug.Print "CSqlServerConnect::OleGetAuthors"
    DumpArray (obj.OleGetAuthors)

    Stop
    End
End Sub

Sub DumpArray(vArray As Variant)
    Dim i As Integer

    Debug.Print "au_fname"; Tab(20); "au_lname"; Tab(40); "state"
    For i = LBound(vArray) To UBound(vArray)
        Debug.Print vArray(i, 1); Tab(20); _
                  ; vArray(i, 2); Tab(40); _
                  ; vArray(i, 3)
    Next i

    Debug.Print "(" + CStr(i) + " row(s).)"

End Sub


README.TXT

****************************************************************
         MICROSOFT SQL SERVER WORKSTATION SYSTEM 6.5
 SQL DISTRIBUTED MANAGEMENT OBJECTS (SQL-DMO) SAMPLE PROGRAMS
****************************************************************
This file describes the SQL-DMO sample programs included with
Microsoft(R) SQL Server(TM) 6.5 and Microsoft SQL Server
Workstation System 6.5.

For additional information about SQL Server 6.5, SQL Distributed
Management Objects, and ODBC, see the "What's New in SQL Server 6.5,"
"Programming SQL Distributed Management Objects," and "Programming
ODBC for SQL Server" sections of SQL Server Books Online, which
contains the most up-to-date information available.

****************************************************************
32-BIT VISUAL BASIC SAMPLES

The following 32-bit Microsoft(R) Visual Basic(R)-based sample
programs are located in the C:\MSSQL\SAMPLES\SQLDMO\VB directory
by default:

EXPLORE
Sample SQL-DMO object browser using 32-bit Visual Basic. Includes
the following files:
    EXPLORE.MAK
    EXPLORE.FRM
    EXPLORE.BAS

GENERIC
Sample SQL-DMO generic program using 32-bit Visual Basic. Includes
the following files:
    GENERIC.MAK
    GENERIC.FRM

****************************************************************
32-BIT VISUAL C++ SAMPLES

The following 32-bit C++ sample program is located in the
C:\MSSQL\SAMPLES\SQLDMO\C\CPP directory by default:

SOCPP
Sample C++ program using 32-bit Visual C++. Includes the following
files:
    SOCPP.MAK
    SOCPP.CPP
    SOCPP.MDP

The following 32-bit C sample program is located in the
C:\MSSQL\SAMPLES\SQLDMO\C\CPP directory by default:

SOC
Sample C program using 32-bit Visual C++. Includes the following
files:
    SOC.MAK
    SOC.C
    SOC.MDP

Each project on the Intel(R) platform includes Visual C++ 4.0
workspace files (.MDP). Double-click on the workspace file to load
the workspace and make file into the Developer Studio environment.

Each project also contains cross-platform native Visual C++ 4.0 make
files (.MAK). For each 32-bit project the .MAK file contains Intel,
Alpha AXP(R), MIPS(R), and PowerPC(TM) build information. When the
project is loaded into Developer Studio, pick the appropriate target,
for example "Example1 - Win32 (ALPHA) Debug", and build it.

You can also build from the command line. BUILDALL.BAT, a file that
will build all of the 32-bit PTK samples, is in the samples directory.
Use the following syntax:

BUILDALL ({intel | alpha | mips | powerpc} {debug | release} {force})

To use these samples with Visual C++ 4.0, you need to ensure that
your Library and Include directory settings point to your own
directories. To do this using Developer Studio, from the Tools menu,
choose Options, then click the Directories tab. From the "Show
directories for" box, choose Include files and Library files, and
ensure that these directories (as appropriate) are included:

Include files:Library files:
C:\MSSQL\DBLIB\INCLUDEC:\MSSQL\DBLIB\LIB
C:\MSSQL\MSDTC\INCLUDEC:\MSSQL\MSDTC\LIB
C:\MSSQL\ODBD\INCLUDEC:\MSSQL\ODS\LIB
C:\MSSQL\ODS\INCLUDE
C:\MSSQL\SQLOLE\INCLUDE


SOC.C

#include <stdio.h>

#define INITGUID
#define INC_OLE2
#include <windows.h>

#include <sqloleid.h>
#include <sqlole.h>

// This sample demonstrates how to program SQLOLE in C.  Differences from C++ are noted.
int main (void)
{
// Variables must be defined at beginning of block, rather than at point of initialization.
// (If this is done later, the compiler error message is very unhelpful in this case).
LPSQLOLESERVER pSQL = NULL;
HRESULT hr;
if FAILED(hr = CoInitialize (NULL))
{
printf("CoInitialize Failed\n");
return (0);
}
printf("Hello\n");

// Must use '&' operator on IID's in C; C++ uses references for these.
if FAILED(hr = CoCreateInstance(&CLSID_SQLOLEServer, NULL, CLSCTX_INPROC_SERVER,
&IID_ISQLOLEServer, (LPVOID*)&pSQL))
{
printf("CoCreateInstance Failed\n");
return (0);
}

// The returned pointer from SQLOLE is interpreted as a pointer to an array of function pointers,
// which are the C equivalent of C++'s "virtual function table", or vtbl.  Hence in this code,
// pSQL is a pointer to a pointer to a function table, and the additional indirection of including
// the actual vtbl pointer (->lpVtbl) is necessary.  Also, the pSQL pointer must be explicitly passed
// to the called function in C; in C++, this is done implicitly as the "this" pointer.
pSQL->lpVtbl->SetLoginTimeout(pSQL, 10);

if FAILED(hr = pSQL->lpVtbl->Connect(pSQL, "tedhar2","sa",""))
{
HRESULT h = HRESULT_CODE(hr);
printf("Connect failed\n");
}
else
{
SQLOLE_BSTR name;
pSQL->lpVtbl->GetName(pSQL, &name);
printf("%s\n", name);
SQLOLEFreeString(name);
}

pSQL->lpVtbl->Release(pSQL);
CoUninitialize ();
printf("Goodbye\n");
return (0);
}


SOCPP.CPP

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <initguid.h>
#include <tchar.h>
#include <sqloleid.h>
#include <sqlole.h>

int main (void)
{
HRESULT hr;
if FAILED(hr = CoInitialize (NULL))
{
_tprintf(TEXT("CoInitialize Failed\n"));
return (0);
}

LPSQLOLESERVER pSQLServer = NULL;
if FAILED(hr = CoCreateInstance(CLSID_SQLOLEServer, NULL, CLSCTX_INPROC_SERVER,
IID_ISQLOLEServer, (LPVOID*)&pSQLServer))
{
_tprintf(TEXT("CoCreateInstance Failed\n"));
return (0);
}

pSQLServer->SetLoginTimeout(10);

if FAILED(hr = pSQLServer->Connect(TEXT(""),TEXT("sa"),TEXT("")))
{
_tprintf(TEXT("Connect failed\n"));
LPSUPPORTERRORINFO pSEI = NULL;
if SUCCEEDED(pSQLServer->QueryInterface(IID_ISupportErrorInfo, (LPVOID*)&pSEI))
{
if SUCCEEDED(pSEI->InterfaceSupportsErrorInfo(IID_ISQLOLEServer))
{
LPERRORINFO pEI = NULL;
GetErrorInfo (0, &pEI);
SQLOLE_BSTR strDescription, strSource;
pEI->GetDescription (&strDescription);
pEI->GetSource (&strSource);
_tprintf(TEXT("%s returned error %d: %s\n"),
strSource,
SCODE_CODE(hr),
strDescription);
pEI->Release ();
}
}
else
{
_tprintf(TEXT("Error %d\n"), HRESULT_CODE(hr));
}
pSEI->Release ();
}
else
{
SQLOLE_BSTR str;
pSQLServer->GetVersionString(&str);
_tprintf(TEXT("%s\n"),str);
LPSQLOLEAPPLICATION pApp = NULL;
pSQLServer->GetApplication(&pApp);
pApp->GetName(&str);
_tprintf(TEXT("%s\n"),str);
SysFreeString((BSTR)str);

LONG ver;
pSQLServer->GetVersionMajor(&ver);
_tprintf(TEXT("%d\n"),ver);

}

pSQLServer->Release ();

CoUninitialize ();
return (0);
}


EXPLORE.BAS

Attribute VB_Name = "basExplore"
Option Explicit

' Global constants
Public oSQLServer As SQLOLE.SQLServer
Public oCurrentOne As Object
Public oCurrentTwo As Object
Public oCurrentThree As Object
Public oCurrentFour As Object

Public NL As String
Sub GetCollection(oObject As Object, strCollection As String, oCollection() As Object)
    Select Case strCollection

    ' SQLServer
    Case "Databases"
        Set oCollection(0) = oObject.Databases
    Case "Devices"
        Set oCollection(0) = oObject.Devices
    Case "Languages"
        Set oCollection(0) = oObject.Languages
    Case "Logins"
        Set oCollection(0) = oObject.Logins
    Case "RemoteServers"
        Set oCollection(0) = oObject.RemoteServers
    Case "Alerts"
        Set oCollection(0) = oObject.Alerts
    Case "Operators"
        Set oCollection(0) = oObject.Operators

    ' Database
    Case "Defaults"
        Set oCollection(0) = oObject.Defaults
    Case "Groups"
        Set oCollection(0) = oObject.Groups
    Case "Rules"
        Set oCollection(0) = oObject.Rules
    Case "StoredProcedures"
        Set oCollection(0) = oObject.StoredProcedures
    Case "SystemDataTypes"
        Set oCollection(0) = oObject.SystemDatatypes
    Case "Tables"
        Set oCollection(0) = oObject.Tables
    Case "UserDefinedDataTypes"
        Set oCollection(0) = oObject.UserDefinedDatatypes
    Case "Users"
        Set oCollection(0) = oObject.Users
    Case "Views"
        Set oCollection(0) = oObject.Views
    Case "Publications"
        Set oCollection(0) = oObject.Publications
    Case "Articles"
        Set oCollection(0) = oObject.Articles
    Case "Subscriptions"
        Set oCollection(0) = oObject.Subscriptions

    ' RemoteServer
    Case "RemoteLogins"
        Set oCollection(0) = oObject.RemoteLogins

    ' Table
    Case "Columns"
        Set oCollection(0) = oObject.Columns
    Case "Indexes"
        Set oCollection(0) = oObject.Indexes
    Case "Triggers"
        Set oCollection(0) = oObject.Triggers
    Case "Keys"
        Set oCollection(0) = oObject.Keys
    Case "Checks"
        Set oCollection(0) = oObject.Checks

    End Select

End Sub


EXPLORE.FRM

VERSION 4.00
Begin VB.Form frmMain
   Caption         =   "SQL-DMO Explorer"
   ClientHeight    =   6705
   ClientLeft      =   180
   ClientTop       =   390
   ClientWidth     =   9240
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   7110
   Left            =   120
   LinkTopic       =   "Form1"
   ScaleHeight     =   6705
   ScaleWidth      =   9240
   Top             =   45
   Width           =   9360
   Begin VB.TextBox txtProperties
      Height          =   2535
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   13
      Top             =   4080
      Width           =   9015
   End
   Begin VB.CommandButton cmdExit
      Caption         =   "E&xit"
      Height          =   375
      Left            =   8640
      TabIndex        =   4
      Top             =   120
      Width           =   495
   End
   Begin VB.ComboBox cboFour
      Height          =   315
      Left            =   6960
      Sorted          =   -1  'True
      Style           =   2  'Dropdown List
      TabIndex        =   11
      Top             =   600
      Width           =   2175
   End
   Begin VB.ListBox lstFour
      Height          =   2985
      Left            =   6960
      Sorted          =   -1  'True
      TabIndex        =   12
      Top             =   960
      Width           =   2175
   End
   Begin VB.CommandButton cmdConnect
      Caption         =   "&Connect"
      Height          =   375
      Left            =   7560
      TabIndex        =   3
      Top             =   120
      Width           =   975
   End
   Begin VB.TextBox txtPassword
      Height          =   285
      Left            =   5880
      PasswordChar    =   "*"
      TabIndex        =   2
      Top             =   120
      Width           =   1455
   End
   Begin VB.TextBox txtLogin
      Height          =   285
      Left            =   3360
      TabIndex        =   1
      Top             =   120
      Width           =   1455
   End
   Begin VB.TextBox txtServer
      Height          =   285
      Left            =   1200
      TabIndex        =   0
      Top             =   120
      Width           =   1455
   End
   Begin VB.ListBox lstThree
      Height          =   2985
      Left            =   4680
      Sorted          =   -1  'True
      TabIndex        =   10
      Top             =   960
      Width           =   2175
   End
   Begin VB.ListBox lstTwo
      Height          =   2985
      Left            =   2400
      Sorted          =   -1  'True
      TabIndex        =   8
      Top             =   960
      Width           =   2175
   End
   Begin VB.ListBox lstOne
      Height          =   2985
      Left            =   120
      Sorted          =   -1  'True
      TabIndex        =   6
      Top             =   960
      Width           =   2175
   End
   Begin VB.ComboBox cboThree
      Height          =   315
      Left            =   4680
      Sorted          =   -1  'True
      Style           =   2  'Dropdown List
      TabIndex        =   9
      Top             =   600
      Width           =   2175
   End
   Begin VB.ComboBox cboTwo
      Height          =   315
      Left            =   2400
      Sorted          =   -1  'True
      Style           =   2  'Dropdown List
      TabIndex        =   7
      Top             =   600
      Width           =   2175
   End
   Begin VB.ComboBox cboOne
      Height          =   315
      Left            =   120
      Sorted          =   -1  'True
      Style           =   2  'Dropdown List
      TabIndex        =   5
      Top             =   600
      Width           =   2175
   End
   Begin VB.Label lblPassword
      Caption         =   "Password:"
      Height          =   255
      Left            =   4920
      TabIndex        =   16
      Top             =   120
      Width           =   855
   End
   Begin VB.Label lblLogin
      Caption         =   "Login:"
      Height          =   255
      Left            =   2760
      TabIndex        =   15
      Top             =   120
      Width           =   615
   End
   Begin VB.Label lblServer
      Caption         =   "SQL Server:"
      Height          =   255
      Left            =   120
      TabIndex        =   14
      Top             =   120
      Width           =   1095
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Private Sub FillProperties(oObject As Object, txtText As Object)
    On Error Resume Next
    Dim oProperty As Object

    frmMain.MousePointer = 11
    With txtText
        Select Case oObject.TypeOf
        Case SQLOLEObj_Subscription
            .Text = "Properties for " & oObject.ServerName & NL
        Case Else
            .Text = "Properties for " & oObject.Name & NL
        End Select
        For Each oProperty In oObject.Properties
            .Text = .Text & oProperty.Name & ": " & oProperty.Value & NL
        Next
    End With
    frmMain.MousePointer = 0

End Sub

Private Sub cboFour_Click()

    If oCurrentThree Is Nothing Then Exit Sub

    FillFour

End Sub

Private Sub cboOne_Click()

    cboTwo.Clear
    lstTwo.Clear
    cboThree.Clear
    lstThree.Clear
    cboFour.Clear
    lstFour.Clear

    Set oCurrentOne = Nothing

    Select Case cboOne.Text
    Case "Databases"
        cboTwo.AddItem "Defaults"
        cboTwo.AddItem "Groups"
        cboTwo.AddItem "Publications"
        cboTwo.AddItem "Rules"
        cboTwo.AddItem "StoredProcedures"
        cboTwo.AddItem "SystemDataTypes"
        cboTwo.AddItem "Tables"
        cboTwo.AddItem "UserDefinedDataTypes"
        cboTwo.AddItem "Users"
        cboTwo.AddItem "Views"
    Case "RemoteServers"
        cboTwo.AddItem "RemoteLogins"
    End Select

    FillOne

End Sub

Private Sub cboThree_Click()

    cboFour.Clear
    lstFour.Clear

    Set oCurrentThree = Nothing

    Select Case cboThree.Text
    Case "Articles"
        cboFour.AddItem "Subscriptions"
    End Select

    If oCurrentTwo Is Nothing Then Exit Sub

    FillThree

End Sub

Private Sub cboTwo_Click()

    cboThree.Clear
    lstThree.Clear
    cboFour.Clear
    lstFour.Clear

    Set oCurrentTwo = Nothing

    Select Case cboTwo.Text
    Case "Tables"
        cboThree.AddItem "Checks"
        cboThree.AddItem "Columns"
        cboThree.AddItem "Indexes"
        cboThree.AddItem "Keys"
        cboThree.AddItem "Triggers"
    Case "Publications"
        cboThree.AddItem "Articles"
    End Select

    If oCurrentOne Is Nothing Then Exit Sub

    FillTwo

End Sub

Private Sub FillOne()
    On Error Resume Next

    lstOne.Clear
    If cboOne.ListIndex = -1 Then Exit Sub

    ReDim oCollection(0) As Object
    GetCollection oSQLServer, (cboOne.Text), oCollection()

    Dim i As Integer
    For i = 1 To oCollection(0).Count
        lstOne.AddItem oCollection(0)(i).Name
    Next i

End Sub

Private Sub FillThree()
    On Error Resume Next

    lstThree.Clear
    If cboThree.ListIndex = -1 Then Exit Sub

    ReDim oCollection(0) As Object
    GetCollection oCurrentTwo, (cboThree.Text), oCollection()

    Dim i As Integer
    For i = 1 To oCollection(0).Count
        lstThree.AddItem oCollection(0)(i).Name
    Next i

End Sub

Private Sub FillFour()
    On Error Resume Next

    lstFour.Clear
    If cboFour.ListIndex = -1 Then Exit Sub

    ReDim oCollection(0) As Object
    GetCollection oCurrentThree, (cboFour.Text), oCollection()

    Dim i As Integer
    For i = 1 To oCollection(0).Count
    Select Case oCollection(0)(i).TypeOf
    Case SQLOLEObj_Subscription
        lstFour.AddItem oCollection(0)(i).ServerName
    Case Else
        lstFour.AddItem oCollection(0)(i).Name
    End Select
    Next i

End Sub

Private Sub FillTwo()
    On Error Resume Next

    lstTwo.Clear
    If cboTwo.ListIndex = -1 Then Exit Sub

    ReDim oCollection(0) As Object
    GetCollection oCurrentOne, (cboTwo.Text), oCollection()

    Dim i As Integer
    For i = 1 To oCollection(0).Count
        lstTwo.AddItem oCollection(0)(i).Name
    Next i

End Sub

Private Sub cmdConnect_Click()
    On Error Resume Next

    frmMain.MousePointer = 11
    oSQLServer.DisConnect
    oSQLServer.Connect txtServer.Text, txtLogin.Text, txtPassword.Text
    With txtProperties
        If Err.Number = 0 Then
            .Text = "Properties for SQL Server " & oSQLServer.TrueName & NL
            FillProperties oSQLServer, txtProperties
        Else
            .Text = Err.Source & " Error " & Err.Number - vbObjectError & ":" & NL
            .Text = .Text & "    " & Err.Description
        End If
    End With

    frmMain.MousePointer = 0

    lstOne.Clear
    lstTwo.Clear
    lstThree.Clear
    lstFour.Clear

End Sub

Private Sub cmdExit_Click()
    Unload frmMain
End Sub

Private Sub Form_Load()
    On Error Resume Next
    NL = Chr$(13) & Chr$(10)

    Set oSQLServer = New SQLOLE.SQLServer
    oSQLServer.LoginTimeout = 10

    With cboOne
        .Clear
        .AddItem "Alerts"
        .AddItem "Databases"
        .AddItem "Devices"
        .AddItem "Languages"
        .AddItem "Logins"
        .AddItem "Operators"
        .AddItem "RemoteServers"
    End With

End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error Resume Next

    oSQLServer.DisConnect
    oSQLServer.Close

End Sub

Private Sub lstFour_Click()
    On Error Resume Next

    Select Case cboFour.Text
    Case "Subscriptions"
        Set oCurrentFour = oCurrentThree.Subscriptions(lstFour.Text)
    End Select

    FillProperties oCurrentFour, txtProperties

End Sub

Private Sub lstOne_Click()
    On Error Resume Next

    Select Case cboOne.Text
    Case "Databases"
        Set oCurrentOne = oSQLServer.Databases(lstOne.Text)
    Case "Devices"
        Set oCurrentOne = oSQLServer.Devices(lstOne.Text)
    Case "Languages"
        Set oCurrentOne = oSQLServer.Languages(lstOne.Text)
    Case "Logins"
        Set oCurrentOne = oSQLServer.Logins(lstOne.Text)
    Case "RemoteServers"
        Set oCurrentOne = oSQLServer.RemoteServers(lstOne.Text)
    Case "Alerts"
        Set oCurrentOne = oSQLServer.Alerts(lstOne.Text)
    Case "Operators"
        Set oCurrentOne = oSQLServer.Operators(lstOne.Text)
    End Select

    lstTwo.Clear
    lstThree.Clear
    lstFour.Clear

    FillTwo

    FillProperties oCurrentOne, txtProperties

End Sub

Private Sub lstThree_Click()
    On Error Resume Next

    Select Case cboThree.Text
    Case "Columns"
        Set oCurrentThree = oCurrentTwo.Columns(lstThree.Text)
    Case "Indexes"
        Set oCurrentThree = oCurrentTwo.Indexes(lstThree.Text)
    Case "Triggers"
        Set oCurrentThree = oCurrentTwo.Triggers(lstThree.Text)
    Case "Keys"
        Set oCurrentThree = oCurrentTwo.Keys(lstThree.Text)
    Case "Checks"
        Set oCurrentThree = oCurrentTwo.Checks(lstThree.Text)
    Case "Articles"
        Set oCurrentThree = oCurrentTwo.Articles(lstThree.Text)
    End Select

    lstFour.Clear

    FillFour

    FillProperties oCurrentThree, txtProperties

End Sub

Private Sub lstTwo_Click()
    On Error Resume Next

    Select Case cboTwo.Text
    Case "Defaults"
        Set oCurrentTwo = oCurrentOne.Defaults(lstTwo.Text)
    Case "Groups"
        Set oCurrentTwo = oCurrentOne.Groups(lstTwo.Text)
    Case "Rules"
        Set oCurrentTwo = oCurrentOne.Rules(lstTwo.Text)
    Case "StoredProcedures"
        Set oCurrentTwo = oCurrentOne.StoredProcedures(lstTwo.Text)
    Case "SystemDataTypes"
        Set oCurrentTwo = oCurrentOne.SystemDatatypes(lstTwo.Text)
    Case "Tables"
        Set oCurrentTwo = oCurrentOne.Tables(lstTwo.Text)
    Case "UserDefinedDataTypes"
        Set oCurrentTwo = oCurrentOne.UserDefinedDatatypes(lstTwo.Text)
    Case "Users"
        Set oCurrentTwo = oCurrentOne.Users(lstTwo.Text)
    Case "Views"
        Set oCurrentTwo = oCurrentOne.Views(lstTwo.Text)
    Case "RemoteLogins"
        Set oCurrentTwo = oCurrentOne.RemoteLogins(lstTwo.Text)
    Case "Publications"
        Set oCurrentTwo = oCurrentOne.Publications(lstTwo.Text)
    End Select

    lstThree.Clear
    lstFour.Clear

    FillThree

    FillProperties oCurrentTwo, txtProperties

End Sub


GENERIC.FRM

VERSION 4.00
Begin VB.Form frmMain
   Caption         =   "SQL-DMO Generic"
   ClientHeight    =   6015
   ClientLeft      =   510
   ClientTop       =   675
   ClientWidth     =   6735
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   6420
   Left            =   450
   LinkTopic       =   "Form1"
   ScaleHeight     =   6015
   ScaleWidth      =   6735
   Top             =   330
   Width           =   6855
   Begin VB.TextBox txtOut
      Height          =   4695
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   5
      Top             =   1200
      Width           =   6495
   End
   Begin VB.CommandButton cmdExit
      Caption         =   "E&xit"
      Height          =   375
      Left            =   5640
      TabIndex        =   4
      Top             =   120
      Width           =   975
   End
   Begin VB.CommandButton cmdConnect
      Caption         =   "&Connect"
      Height          =   375
      Left            =   4560
      TabIndex        =   3
      Top             =   120
      Width           =   975
   End
   Begin VB.TextBox txtPassword
      Height          =   285
      Left            =   1320
      PasswordChar    =   "*"
      TabIndex        =   2
      Top             =   840
      Width           =   2775
   End
   Begin VB.TextBox txtLogin
      Height          =   285
      Left            =   1320
      TabIndex        =   1
      Top             =   480
      Width           =   2775
   End
   Begin VB.TextBox txtServer
      Height          =   285
      Left            =   1320
      TabIndex        =   0
      Top             =   120
      Width           =   2775
   End
   Begin VB.Label lblPassword
      Caption         =   "Password:"
      Height          =   255
      Left            =   120
      TabIndex        =   8
      Top             =   840
      Width           =   855
   End
   Begin VB.Label lblLogin
      Caption         =   "Login:"
      Height          =   255
      Left            =   120
      TabIndex        =   7
      Top             =   480
      Width           =   615
   End
   Begin VB.Label lblServer
      Caption         =   "SQL Server:"
      Height          =   255
      Left            =   120
      TabIndex        =   6
      Top             =   120
      Width           =   1095
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

' Global variables
Dim oSQLServer As SQLOLE.SQLServer
Dim NL As String

Private Sub cmdConnect_Click()
    On Error Resume Next

    frmMain.MousePointer = 11
    oSQLServer.DisConnect
    oSQLServer.Connect txtServer.Text, txtLogin.Text, txtPassword.Text
    With txtOut
        If Err.Number = 0 Then
            .Text = "Connected to SQL Server " & oSQLServer.TrueName & NL
            Dim oProperty As Object
            .Text = .Text & "Properties for " & oSQLServer.Name & NL
            For Each oProperty In oSQLServer.Properties
                .Text = .Text & oProperty.Name & ": " & oProperty.Value & NL
            Next
        Else
            .Text = Err.Source & " Error " & Err.Number - vbObjectError & ":" & NL
            .Text = .Text & "    " & Err.Description
        End If
    End With
    frmMain.MousePointer = 0

End Sub

Private Sub cmdExit_Click()
    Unload frmMain
End Sub

Private Sub Form_Load()
    On Error Resume Next
    NL = Chr$(13) & Chr$(10)

    Set oSQLServer = New SQLOLE.SQLServer
    oSQLServer.LoginTimeout = 10

End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error Resume Next

    oSQLServer.DisConnect
    oSQLServer.Close

End Sub


README.TXT

****************************************************************
                MICROSOFT SQL WORKSTATION 6.0
          DB-LIBRARY FOR VISUAL BASIC SAMPLE PROGRAMS
****************************************************************
This file tells about the DB-Library for Visual Basic sample
programs included with Microsoft(R) SQL Workstation 6.0. The
information in this file supplements the information in your
Microsoft SQL Workstation documentation.

****************************************************************
DB-LIBRARY FOR VISUAL BASIC SAMPLE APPLICATIONS

DB-Library for Visual Basic provides a set of sample applications
that demonstrate the use of many DB-Library for Visual Basic functions
and routines.

You can use the code in these sample applications as models or
templates for your own applications. By cutting and pasting code from
specific application files, by using global procedures from the sample
common code files, and by including the declarations for those
procedures from the sample include files, you can quickly create the
foundation for your own applications.

The QUERY sample application demonstrates the following SQL Server
operations. Each sample:
* Logs in and opens a connection to SQL Server from the client.
* Allows a user to choose which database to use.
* Allows the user to send a query to SQL Server.
* Accepts results from SQL Server and returns the results to the user.
* Closes the SQL Server connection.
* Exits the application.
* Handles any errors or messages generated by these operations.

The rest of this file describes the contents of all of the Project
files used by the QUERY sample application. This information is
organized by the names of the subdirectories in which each Project
file resides.

Once you are familiar with the QUERY sample applications and their
use of the common code directories, refer to the other sample
applications provided for examples of implementing additional
SQL Server features.

COMMON CODE

The files in the subdirectory DBLIB\VSAMPLES\COMMON contain general
code that can be used by all DB-Library for Visual Basic applications.
The QUERY sample application include all of the COMMON files with
their Project files and invoke the procedures that these files contain.
The following sections describe the files contained in the COMMON
subdirectory.

The VBSQLGEN.BAS File
The VBSQLGEN.BAS module defines a set of procedures that perform tasks
common to most DB-Library for Visual Basic applications. Such tasks
include:
* Opening and closing a SQL Server connection
* Processing SQL queries
* Executing SQL commands
* Displaying error messages

The LOGIN.FRM File
The LOGIN.FRM file calls a subroutine in the VBSQLGEN.BAS module that
uses DB-Library for Visual Basic functions to open a SQL Server
connection and log in users. The code for logging in users is used in
all of the sample applications except the browse-mode example.

The CHNGDB.FRM File
The CHNGDB.FRM file uses the DB-Library for Visual Basic function
SqlUse% to change the current database to a database name supplied
by the user. The code for changing databases is used in all of the
sample applications.

THE WINCODE DIRECTORY

The files in the DBLIB\VSAMPLES\WINCODE subdirectory contain code
that is available for use by all applications. To use the common code,
you can add the files to a Visual Basic Project or you can copy the
code from these files into an existing file. The following sections
describe the files in the WINCODE subdirectory.

INIEXITW.BAS
INIEXITW.BAS contains DB-Library initialization and exit code.

RESULTSW.BAS
RESULTSW.BAS contains procedures for handling compute rows and stored
procedure output parameters. These procedures are called by the
Process_Sql_query procedure defined in VBSQLGEN.BAS.

THE VBQUERY DIRECTORY

The directory DBLIB\VSAMPLES\QUERY include the project make file for
the sample application and the sample application's primary, or
startup, form, VBQUERY.FRM.

VBQUERY.BAS and VBSQL.BAS
VBQUERY.BAS and VBSQL.BAS contain the global definitions for this
application. They include the relevant variable definitions from
GENGLOB.BAS. These are required. The VBSQL.BAS file contains the
global definitions for VBSQL functions and constants.

ADDITIONAL SAMPLE APPLICATIONS

This section provides information about some of the other
DB-Library for Visual Basic sample applications provided in your
package.

The TEXT Sample Application
The TEXT sample application enables users to select text files from
a database, and view and edit the text in the text file.

The TEXT.FRM file of the TEXT project contains the code of the main
form of the application. It includes subroutines for selecting,
retrieving, and editing text files. For example, the LoadText
subroutine contains DB-Library for Visual Basic functions that read
text fields from database tables; the InsertText subroutine contains
DB-Library for Visual Basic functions that insert text into database
text tables.

The IMAGE Sample Application
The IMAGE sample application enables users to select image files from
a database, view images, and add images to a database. It looks and
works much like the preceding TEXT sample application.

The IMAGE.FRM file of the IMAGE project contains the code of the
main form of the application. It includes subroutines for selecting,
retrieving, and adding image files. For example, the LoadImage
subroutine contains DB-Library functions that read images from
databases; the InsertImage subroutine contains DB-Library functions
that insert images into databases.

The BROWSE Sample Application
The BROWSE sample application demonstrates the use of DB-Library
for Visual Basic browse-mode functions. The BROWSE project uses the
common code files VBSQLGEN.BAS and ABOUT.FRM. The code from the
GENGLOB.BAS file is included in the BROWSE.BAS global file.

The BLOGIN.FRM file includes the code from the common code file
LOGIN.FRM. However, because the browse mode requires two SQL Server
connections, the code to open a connection is repeated. The
OK_BUTTON_Click subroutine of the BLOGIN.FRM file illustrates the
correct way to open SQL Server connections for browsing.

The BROWSE.FRM file of the BROWSE project contains the code of
the main form of the application. It includes subroutines for
selecting, retrieving, and updating rows of information. For example,
the Select_Button_Click subroutine sends a command to query
SQL Server and processes the results of the query; the
Update_Button_Click subroutine sends a command to update or change
information in results rows; the ProcessResultRow subroutine
processes the data in a results row. This subroutine is used in
the Select_Button_Click subroutine mentioned previously.

The CURSORS Sample Application
The CURSORS sample application illustrates the use of cursors in
DB-Library for Visual Basic applications. The CURSORS project uses
the common code files VBSQLGEN.BAS, and LOGIN.FRM. The code from
the GENGLOB.BAS file is included in the CURSORS.BAS global file.
The CURSORS.FRM file of the CURSORS project contains the code of
the main form of the application. It includes subroutines for
opening and closing cursors, fetching rows of data, updating rows,
and displaying rows.

The RPC Sample Application
The RPC sample application shows the use of SqlRpcSend% and related
calls. This sample invokes stored procedures directly (not through
a language command) and retrieves return status and output
parameters. Direct invocation of stored procedures is especially
useful with Open Data Services applications. The sample also shows
the use of SqlServerEnum% for listing available servers.

The RPC sample application contains most of the stored procedure
calls in the RPC.FRM module. The RPCLOGIN.FRM file shows the use of
SqlServerEnum%. RPC return status and output parameter handling are
covered in RESULTSW.BAS, called by the Process_Sql_query function
in VBSQLGEN.BAS.

The PUBS Sample Application
The PUBS sample application is a full-featured application showing
how many VBSQL functions can be combined into a business application.
This sample shows a data entry and query form for the authors in
the pubs database. It illustrates the use of a list box for
maintaining a key set of records, browse mode for optimistic
concurrency, and building SQL statements based on data entered.
It also shows techniques for using Visual Basic features such as
MDI child windows and grid controls within an application.


ABOUT.FRM

VERSION 2.00
Begin Form About_Form
   BackColor       =   &H00FFFF00&
   BorderStyle     =   3  'Fixed Double
   Caption         =   "About"
   ControlBox      =   0   'False
   ForeColor       =   &H00C0C0C0&
   Height          =   2625
   Left            =   2565
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2220
   ScaleWidth      =   3150
   Top             =   1245
   Width           =   3270
   Begin CommandButton Command1
      Caption         =   "OK"
      Height          =   375
      Left            =   960
      TabIndex        =   3
      Top             =   1440
      Width           =   1215
   End
   Begin Label Label1
      BackColor       =   &H00FFFF00&
      Caption         =   "VBSQL Sample Application"
      Height          =   255
      Left            =   384
      TabIndex        =   0
      Top             =   816
      Width           =   2624
   End
   Begin Label Label3
      Alignment       =   2  'Center
      BackColor       =   &H00FF0000&
      Caption         =   "Microsoft Corporation"
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   9.75
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00FFFFFF&
      Height          =   255
      Left            =   384
      TabIndex        =   2
      Top             =   272
      Width           =   2528
   End
End

Sub Command1_Click ()

    Unload About_Form

End Sub


ADBABOUT.FRM

VERSION 2.00
Begin Form fAbout
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   3  'Fixed Double
   Caption         =   "About ADBPubs 1"
   ForeColor       =   &H00C0C0C0&
   Height          =   2475
   Left            =   1335
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2070
   ScaleWidth      =   6330
   Top             =   1800
   Width           =   6450
   Begin SSPanel panOm
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   2  'Raised
      BevelOuter      =   1  'Inset
      BorderWidth     =   2
      Font3D          =   1  'Raised w/light shading
      ForeColor       =   &H00000000&
      Height          =   1935
      Left            =   60
      TabIndex        =   2
      Top             =   45
      Width           =   6225
      Begin SSPanel Panel3D1
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelWidth      =   0
         BorderWidth     =   0
         Caption         =   "for Microsoft Corporation"
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   255
         Left            =   900
         TabIndex        =   14
         Top             =   630
         Width           =   3945
      End
      Begin PictureBox picFlag
         BackColor       =   &H00C0C0C0&
         BorderStyle     =   0  'None
         Height          =   555
         Left            =   5310
         ScaleHeight     =   555
         ScaleWidth      =   555
         TabIndex        =   7
         Top             =   240
         Width           =   555
      End
      Begin PictureBox Picture1
         BackColor       =   &H00FFFFFF&
         Height          =   525
         Left            =   180
         Picture         =   ADBABOUT.FRX:0000
         ScaleHeight     =   495
         ScaleWidth      =   495
         TabIndex        =   6
         Top             =   240
         Width           =   525
      End
      Begin SSPanel Panel3D2
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelWidth      =   0
         BorderWidth     =   0
         Caption         =   "Made by ADB-Arkitektur AB, Sweden 1993"
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   255
         Left            =   900
         TabIndex        =   5
         Top             =   345
         Width           =   3945
      End
      Begin SSPanel Panel3D3
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelWidth      =   0
         BorderWidth     =   0
         Caption         =   "ADBPubs 1"
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   255
         Left            =   900
         TabIndex        =   4
         Top             =   105
         Width           =   3945
      End
      Begin SSCommand cmdOk
         Caption         =   "&Ok"
         Font3D          =   3  'Inset w/light shading
         ForeColor       =   &H00000000&
         Height          =   375
         Left            =   5040
         TabIndex        =   3
         Top             =   1425
         Width           =   1020
      End
      Begin Label Label2
         Alignment       =   2  'Center
         BackColor       =   &H00000000&
         ForeColor       =   &H00000000&
         Height          =   45
         Left            =   855
         TabIndex        =   13
         Top             =   975
         Width           =   3945
      End
      Begin Label lblCoProcessorInfo
         BackColor       =   &H00C0C0C0&
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Left            =   3135
         TabIndex        =   12
         Top             =   1590
         Width           =   1695
      End
      Begin Label lblModeInfo
         BackColor       =   &H00C0C0C0&
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Left            =   855
         TabIndex        =   11
         Top             =   1125
         Width           =   2280
      End
      Begin Label lblMemoryInfo
         BackColor       =   &H00C0C0C0&
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Left            =   3135
         TabIndex        =   10
         Top             =   1380
         Width           =   1725
      End
      Begin Label Label3
         AutoSize        =   -1  'True
         BackColor       =   &H00C0C0C0&
         Caption         =   "Math Co-Processor:"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Left            =   870
         TabIndex        =   9
         Top             =   1590
         Width           =   1395
      End
      Begin Label Label1
         AutoSize        =   -1  'True
         BackColor       =   &H00C0C0C0&
         Caption         =   "Memory:"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Left            =   870
         TabIndex        =   8
         Top             =   1350
         Width           =   600
      End
   End
   Begin SSCommand picFlagOrg
      Font3D          =   1  'Raised w/light shading
      ForeColor       =   &H00000000&
      Height          =   600
      Index           =   1
      Left            =   150
      Picture         =   ADBABOUT.FRX:0302
      TabIndex        =   1
      Top             =   2160
      Visible         =   0   'False
      Width           =   675
   End
   Begin SSCommand picFlagOrg
      Font3D          =   1  'Raised w/light shading
      ForeColor       =   &H00000000&
      Height          =   600
      Index           =   2
      Left            =   825
      Picture         =   ADBABOUT.FRX:0604
      TabIndex        =   0
      Top             =   2160
      Visible         =   0   'False
      Width           =   675
   End
   Begin Timer Timer1
      Interval        =   50
      Left            =   1545
      Top             =   2265
   End
End
Dim FlagNmbr As Integer

Sub cmdOk_Click ()

    Timer1.Enabled = False
    Unload Me

End Sub

Sub Form_Load ()

    Me.Height = 2470
    Me.Width = 6480
    ' Center form
    Left = screen.Width / 2 - Width / 2
    Top = screen.Height / 2 - Height / 2

    ' Retrieve current Windows system and memory configuration
    WinFlags = GetWinFlags()

    ' Display mode information
    If WinFlags And WF_ENHANCED Then
        lblModeInfo = "386 Enhanced Mode"
    Else
        lblModeInfo = "Standard Mode"
    End If

    ' Display math co-processor information
    If WinFlags And WF_80x87 Then
        lblCoProcessorInfo = "Present"
    Else
        lblCoProcessorInfo = "Not Present"
    End If

    ' Scan global heap to get memory information
    temp = GetFreeSpace(0)
    If Sgn(temp) = -1 Then
        FreeSpace = CLng(temp + 1&) Xor &HFFFFFFFF
    Else
        FreeSpace = temp
    End If

    ' Divide by 1024 to display info in KB
    FreeSpace = FreeSpace / 1024
    lblMemoryInfo = Format(FreeSpace, "#,#") + " KB Free"

    FlagNmbr = 1
    Timer1.Interval = 150
    Timer1.Enabled = True

End Sub

Sub Timer1_Timer ()

    picFlag.Picture = picFlagOrg(FlagNmbr).Picture
    If FlagNmbr = 1 Then
        FlagNmbr = 2
    Else
        FlagNmbr = 1
    End If

End Sub


ADBGLOB.BAS

' ===========================================================================
' Global variables specifically for this application:
Global Server$, LoginID$, Password$, DataBase$, Worksta$, AppName$, SQLUser$
Global ShowSql As Integer
Global NEWLINE$

' Global constants for the variable MODE which is used uniquelly in every form
    Global Const SQLREST = 0
    Global Const SQLNEW = 1
    Global Const SQLSHOW = 2

' Memory management functions for determining system information
' displayed in the About Dialog

' Returns the current system configurations flags
Declare Function GetWinFlags Lib "kernel" () As Long

' Returns the number of free bytes in the global heap
Declare Function GetFreeSpace Lib "kernel" (ByVal flag%) As Long

' System configuration flags
Global Const WF_CPU286 = &H2&
Global Const WF_CPU386 = &H4&
Global Const WF_CPU486 = &H8&
Global Const WF_STANDARD = &H10&
Global Const WF_ENHANCED = &H20&
Global Const WF_80x87 = &H400&

' ===========================================================================

Declare Function WinHelp Lib "User" (ByVal hWnd As Integer, ByVal lpHelpFile As String, ByVal wCommand As Integer, dwData As Any) As Integer


ADBLOGON.FRM

VERSION 2.00
Begin Form fLogin
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   3  'Fixed Double
   Caption         =   "Logon to SQL Server"
   ControlBox      =   0   'False
   ForeColor       =   &H00C0C0C0&
   Height          =   2850
   Left            =   2100
   LinkMode        =   1  'Source
   LinkTopic       =   "Form2"
   MaxButton       =   0   'False
   MDIChild        =   -1  'True
   MinButton       =   0   'False
   ScaleHeight     =   2445
   ScaleWidth      =   3480
   Top             =   1875
   Width           =   3600
   Begin SSPanel Panel3D4
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      Font3D          =   0  'None
      ForeColor       =   &H00000000&
      Height          =   330
      Left            =   1110
      TabIndex        =   12
      Top             =   1410
      Width           =   2145
      Begin TextBox txtDatabase
         BackColor       =   &H00FFFFFF&
         ForeColor       =   &H00000000&
         Height          =   300
         Left            =   15
         TabIndex        =   13
         Top             =   15
         Width           =   2115
      End
   End
   Begin SSPanel Panel3D3
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      Font3D          =   0  'None
      ForeColor       =   &H00000000&
      Height          =   330
      Left            =   1110
      TabIndex        =   10
      Top             =   975
      Width           =   2145
      Begin TextBox txtPassword
         BackColor       =   &H00FFFFFF&
         ForeColor       =   &H00000000&
         Height          =   300
         Left            =   15
         PasswordChar    =   "*"
         TabIndex        =   11
         Top             =   15
         Width           =   2115
      End
   End
   Begin SSPanel Panel3D2
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      Font3D          =   0  'None
      ForeColor       =   &H00000000&
      Height          =   330
      Left            =   1110
      TabIndex        =   8
      Top             =   555
      Width           =   2145
      Begin TextBox txtLoginId
         BackColor       =   &H00FFFFFF&
         Height          =   300
         Left            =   15
         TabIndex        =   9
         Top             =   15
         Width           =   2115
      End
   End
   Begin SSPanel Panel3D1
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      Font3D          =   0  'None
      ForeColor       =   &H00000000&
      Height          =   330
      Left            =   1110
      TabIndex        =   6
      Top             =   135
      Width           =   2145
      Begin ComboBox cboServer
         BackColor       =   &H00FFFFFF&
         Height          =   300
         Left            =   15
         TabIndex        =   7
         Text            =   "Combo1"
         Top             =   15
         Width           =   2115
      End
   End
   Begin CommandButton cmdLogin
      Caption         =   "&Login"
      Default         =   -1  'True
      Height          =   390
      Left            =   225
      TabIndex        =   0
      Top             =   1920
      Width           =   1335
   End
   Begin CommandButton cmdCancel
      Caption         =   "&Cancel"
      Height          =   390
      Left            =   1875
      TabIndex        =   2
      Top             =   1920
      Width           =   1335
   End
   Begin Label lblLogin
      AutoSize        =   -1  'True
      BackColor       =   &H00C0C0C0&
      Caption         =   "Database"
      Height          =   195
      Index           =   3
      Left            =   165
      TabIndex        =   5
      Top             =   1515
      Width           =   825
   End
   Begin Label lblLogin
      AutoSize        =   -1  'True
      BackColor       =   &H00C0C0C0&
      Caption         =   "Password"
      Height          =   195
      Index           =   2
      Left            =   165
      TabIndex        =   4
      Top             =   1080
      Width           =   825
   End
   Begin Label lblLogin
      AutoSize        =   -1  'True
      BackColor       =   &H00C0C0C0&
      Caption         =   "Login Id"
      Height          =   195
      Index           =   0
      Left            =   165
      TabIndex        =   3
      Top             =   660
      Width           =   705
   End
   Begin Label lblLogin
      AutoSize        =   -1  'True
      BackColor       =   &H00C0C0C0&
      Caption         =   "Server"
      Height          =   195
      Index           =   1
      Left            =   165
      TabIndex        =   1
      Top             =   255
      Width           =   570
   End
End

Sub cmdCancel_Click ()

    Unload Me

End Sub

Sub cmdLogin_Click ()
Dim Channel%
On Error GoTo LoginError

    screen.MousePointer = 11
    Server$ = cboServer.Text
    LoginId$ = txtLoginId.Text
    Password$ = txtPassword.Text
    Database$ = txtDatabase.Text
    WorkSta$ = ""
    Res% = SQLSetLogintime(30)
    Res% = SQLSettime(30)

    Channel% = Logon%(Server$, LoginId$, Password$, WorkSta$, AppName$)

    If (Channel% = FAIL%) Then
        Beep
        txtPassword.SetFocus
    Else
        Results% = SqlUse(Channel%, Database$)
        If (Results% = FAIL%) Then
            Beep
            MsgBox "Could not access database " + Database$
            GoTo LoginError
        Else
            MDIMA.altAuthors.Enabled = True
            MDIMA.altLogout.Enabled = True
            MDIMA.cmdLogout.Enabled = True

            ' Get info on who is logged in for panel display:
            Res% = SQLComm(Channel%, "select User_Name()")
            If Res% = SUCCEED% Then
                Res% = SQLNextRow(Channel%)
                If REGROW Then
                    SQLUser$ = SQLData(Channel%, 1)
                Else
                    SQLUser$ = ""
                End If
            Else
                SQLUser$ = ""
            End If
            Sqlclose (Channel%)

            'Save login info in local log file for next time:
            Open "c:\adbpubs.log" For Output As #1
            Print #1, Server$
            Print #1, Database$
            Print #1, LoginId$
            Close #1
            screen.MousePointer = 0
            MDIMA.cmdUnloadActForms.Value = True
        End If
    End If

LoginExit:
    SetLoginInfo Server$, LoginId$, Database$, SQLUser$
    screen.MousePointer = 0
    Exit Sub

LoginError:
    Resume Next

End Sub

Sub Form_Load ()
Dim Srvr$, Db$, Usr$

On Error GoTo FormLoadError

    cboServer.Text = ""
    txtLoginId.Text = ""
    txtPassword.Text = ""
    txtDatabase.Text = ""
    Me.Width = 3600
    Me.Height = 2850
    Me.Top = screen.Height / 2 - Me.Height / 2 - 710
    Me.Left = screen.Width / 2 - Me.Width / 2
    Open "c:\adbpubs.log" For Input As #1
    Input #1, Srvr$
    Input #1, Db$
    Input #1, Usr$
    Close #1
    cboServer.Text = Srvr$
    txtDatabase.Text = Db$
    txtLoginId.Text = Usr$
    Me.Show
    If Len(RTrim$(cboServer.Text)) Then txtPassword.SetFocus

FormLoadExit:
    Exit Sub

FormLoadError:
    Resume FormLoadExit

End Sub

Sub SetLoginInfo (pServer$, pLoginId$, pDatabase$, pUser$)
Dim lInfo$

    lInfo$ = " Server: " + pServer$
    lInfo$ = lInfo$ + "    Login: " + pLoginId$
    lInfo$ = lInfo$ + "    Database: " + pDatabase$
    lInfo$ = lInfo$ + "    User: " + pUser$
    MDIMA.panInfo.Caption = lInfo$

End Sub

Sub txtDatabase_GotFocus ()

    txtDatabase.SelStart = 0
    txtDatabase.SelLength = Len(txtDatabase.Text)

End Sub

Sub txtLoginId_GotFocus ()

    txtLoginId.SelStart = 0
    txtLoginId.SelLength = Len(txtLoginId.Text)

End Sub

Sub txtPassword_GotFocus ()

    txtPassword.SelStart = 0
    txtPassword.SelLength = Len(txtPassword.Text)

End Sub


ADBSQL.BAS

Function Logon% (pServer$, pLoginId$, pPassword$, pWorkSta$, pAppName$)
'
' Open connection to SQL Server. Returns channel number
' to use in further communication with SQL Server through
' this channel.
'
' This function can be called one or several times from
' each form.

    Logon% = SqlOpenConnection(pServer$, pLoginId$, pPassword$, pWorkSta$, pAppName$)

End Function

Function SQLComm% (pChannel%, pCmd$)
'
' Generalized function to send SQL statements to SQL
' Server. If global variable ShowSql is set to True,
' the SQL statement is shown in a messagebox before
' it is sent to SQL Server

Dim Res%

    If ShowSql Then
        MsgBox pCmd$
    End If
    Do
        Res% = SqlCancel(pChannel%)
        If Res% <> SUCCEED% Then
            Exit Do
        End If
        Res% = SQLCmd(pChannel%, pCmd$)
        If Res% <> SUCCEED% Then
            Exit Do
        End If
        Res% = SqlExec(pChannel%)
        If Res% <> SUCCEED% Then
            Exit Do
        End If
        Res% = SqlResults%(pChannel%)
        If Res% <> SUCCEED% Then
            Exit Do
        End If
    Loop Until True
    SQLComm% = Res%

End Function


AUTHORS.BAS

Option Explicit

Function AlterAuthors (pChannel As Integer)
'
' This routine alters the Authors table with
' a TIMESTAMP column. Then it sends a 'dummy' update
' statement so that every row in the table
' gets a value in the TIMESTAMP column.

Dim lCmd$, lRetValue As Integer, lInfoText As String, Res%

    lRetValue = False
    lInfoText = mdiMA.panInfo.Caption
    mdiMA.panInfo.Caption = " Altering the authors table with a TIMESTAMP column."
    lCmd$ = "alter table authors add timestamp timestamp NULL "
    Res% = SQLComm%(pChannel, lCmd$)
    If Res% = SUCCEED% Then
        mdiMA.panInfo.Caption = " Every row in the authors table is now being updated."
        lCmd$ = "update authors set au_fname = au_fname"
        Res% = SQLComm%(pChannel, lCmd$)
        If Res% = SUCCEED% Then lRetValue = True
    End If
    mdiMA.panInfo.Caption = lInfoText
    AlterAuthors = lRetValue

End Function

Function AskifAlter ()
'
' This routine asks if the user wants to
' alter the Authors table with a TIMESTAMP
' column.

Dim lMsg$, Res%, lRetValue As Integer

    lMsg$ = "This sample application needs a column with "
    lMsg$ = lMsg$ + " the name TIMESTAMP and the datatype "
    lMsg$ = lMsg$ + " TIMESTAMP in the authors table." + NEWLINE$
    lMsg$ = lMsg$ + NEWLINE$ + "The reason is that this application "
    lMsg$ = lMsg$ + "uses Browse Mode and Optimistic Concurrency "
    lMsg$ = lMsg$ + "Control." + NEWLINE$ + NEWLINE$
    lMsg$ = lMsg$ + "There is no TIMESTAMP column in your authors "
    lMsg$ = lMsg$ + "table at this moment!" + NEWLINE$ + NEWLINE$
    lMsg$ = lMsg$ + "If you click on the OK button the application "
    lMsg$ = lMsg$ + "will create the column for you. If you click "
    lMsg$ = lMsg$ + "on the cancel button you won't be able to run "
    lMsg$ = lMsg$ + "this sample until the column is there."
    Beep
    Res% = MsgBox(lMsg$, 17, "Alter table authors")
    If Res% = IDOK Then
        lRetValue = True
    Else
        lRetValue = False
    End If
    AskifAlter = lRetValue

End Function

Function CheckifTimestamp (pChannel As Integer)
'
' This routine checks if there is a TIMESTAMP column
' for the table Authors.

Dim lCmd$, lRetValue As Integer, Res%

    lRetValue = False
    lCmd$ = "select count(*) from sysobjects so, syscolumns "
    lCmd$ = lCmd$ + "sc where so.id = sc.id "
    lCmd$ = lCmd$ + "and so.name = 'authors' "
    lCmd$ = lCmd$ + "and sc.name = 'timestamp' "
    lCmd$ = lCmd$ + "and sc.type = 37"
    Res% = SQLComm%(pChannel, lCmd$)
    If Res% = SUCCEED% Then
        Res% = SQLNextRow(pChannel)
        If Res% = REGROW Then
            If Val(SQLData(pChannel, 1)) > 0 Then
                lRetValue = True
            End If
        End If
    End If
    CheckifTimestamp = lRetValue

End Function

Function EmptyQualString ()
'
' This routine lets the user know that some condition
' for using Concurrency Control with Browse Mode is
' not fullfilled.

Dim lRetValue As String

    lRetValue = "The qual string is empty, which is not acceptable."
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "There are three mandatory preconditions for using "
    lRetValue = lRetValue + "Concurrency Control with Browse Mode."
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "1 - You must have a unique index."
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "2 - You must have a column named TIMESTAMP with the "
    lRetValue = lRetValue + " datatype TIMESTAMP in the specific table."
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "3 - You must add 'FOR BROWSE' to your SQL select statement "
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "Check to see if the unique index on the authors table "
    lRetValue = lRetValue + "has been dropped. That might be the problem."
    lRetValue = lRetValue + NEWLINE$ + NEWLINE$
    lRetValue = lRetValue + "This operation will not go through to SQL Server."
    EmptyQualString = lRetValue

End Function


AUTHORS.FRM

VERSION 2.00
Begin Form fAuthors
   BackColor       =   &H00C0C0C0&
   Caption         =   "Authors"
   ForeColor       =   &H00C0C0C0&
   Height          =   5655
   Icon            =   AUTHORS.FRX:0000
   Left            =   270
   LinkTopic       =   "Form1"
   MDIChild        =   -1  'True
   ScaleHeight     =   4965
   ScaleWidth      =   9135
   Top             =   1695
   Width           =   9255
   Begin SSFrame frmTitles
      Caption         =   "Titles"
      FontBold        =   0   'False
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   8.25
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   2535
      Left            =   75
      TabIndex        =   41
      Top             =   2325
      Visible         =   0   'False
      Width           =   8970
      Begin SSPanel panGrid
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   2055
         Left            =   120
         TabIndex        =   42
         Top             =   330
         Width           =   8730
         Begin Grid grdTitles
            Cols            =   9
            FixedCols       =   0
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   2025
            Left            =   15
            Rows            =   10
            TabIndex        =   43
            Top             =   15
            Width           =   8700
         End
      End
   End
   Begin SSRibbon cmdDown
      AutoSize        =   0  'None
      BackColor       =   &H00C0C0C0&
      BevelWidth      =   1
      Height          =   420
      Left            =   8670
      PictureUp       =   AUTHORS.FRX:0302
      Top             =   75
      Width           =   420
   End
   Begin SSPanel Panel3D9
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   75
      TabIndex        =   38
      Top             =   1530
      Width           =   2625
      Begin SSCommand cmdMoreRows
         BevelWidth      =   1
         Caption         =   "&More rows"
         Enabled         =   0   'False
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         ForeColor       =   &H00000000&
         Height          =   345
         Left            =   15
         TabIndex        =   39
         Top             =   15
         Width           =   2595
      End
   End
   Begin SSPanel Panel3D1
      Alignment       =   8  'Center - BOTTOM
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      ForeColor       =   &H00000000&
      Height          =   2190
      Left            =   2775
      TabIndex        =   29
      Top             =   75
      Width           =   5850
      Begin SSPanel panAuthors
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Index           =   1
         Left            =   105
         TabIndex        =   37
         Top             =   315
         Width           =   1140
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   1
            Left            =   15
            TabIndex        =   1
            Top             =   15
            Width           =   1110
         End
      End
      Begin SSPanel Panel3D8
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   3450
         TabIndex        =   36
         Top             =   1020
         Width           =   1695
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   6
            Left            =   15
            TabIndex        =   6
            Top             =   15
            Width           =   1665
         End
      End
      Begin SSPanel Panel3D2
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   5190
         TabIndex        =   35
         Top             =   1020
         Width           =   570
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   7
            Left            =   15
            TabIndex        =   7
            Top             =   15
            Width           =   540
         End
      End
      Begin SSPanel Panel3D3
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   2235
         TabIndex        =   34
         Top             =   1020
         Width           =   1170
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   5
            Left            =   15
            TabIndex        =   5
            Top             =   15
            Width           =   1140
         End
      End
      Begin SSPanel Panel3D4
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   90
         TabIndex        =   33
         Top             =   1020
         Width           =   2100
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   4
            Left            =   15
            TabIndex        =   4
            Top             =   15
            Width           =   2070
         End
      End
      Begin SSPanel Panel3D5
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   3180
         TabIndex        =   32
         Top             =   315
         Width           =   2580
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   3
            Left            =   15
            TabIndex        =   3
            Top             =   15
            Width           =   2550
         End
      End
      Begin SSPanel Panel3D6
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   1290
         TabIndex        =   31
         Top             =   315
         Width           =   1845
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   2
            Left            =   15
            TabIndex        =   2
            Top             =   15
            Width           =   1815
         End
      End
      Begin SSPanel Panel3D7
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         ForeColor       =   &H00000000&
         Height          =   315
         Left            =   105
         TabIndex        =   30
         Top             =   1695
         Width           =   1635
         Begin TextBox txtAuthors
            FontBold        =   0   'False
            FontItalic      =   0   'False
            FontName        =   "MS Sans Serif"
            FontSize        =   8.25
            FontStrikethru  =   0   'False
            FontUnderline   =   0   'False
            Height          =   285
            Index           =   8
            Left            =   15
            TabIndex        =   8
            Top             =   15
            Width           =   1605
         End
      End
      Begin SSCheck chkContract
         Caption         =   "Contract"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         ForeColor       =   &H00000000&
         Height          =   240
         Left            =   2235
         TabIndex        =   9
         Top             =   1725
         Width           =   930
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "Id"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   1
         Left            =   120
         TabIndex        =   10
         Top             =   120
         Width           =   150
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "First"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   2
         Left            =   1320
         TabIndex        =   11
         Top             =   120
         Width           =   285
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "Last"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   3
         Left            =   3195
         TabIndex        =   12
         Top             =   120
         Width           =   300
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "Address"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   4
         Left            =   120
         TabIndex        =   13
         Top             =   825
         Width           =   570
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "Zip Code"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   5
         Left            =   2295
         TabIndex        =   14
         Top             =   825
         Width           =   645
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "City"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   6
         Left            =   3480
         TabIndex        =   15
         Top             =   825
         Width           =   270
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "State"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   7
         Left            =   5205
         TabIndex        =   16
         Top             =   825
         Width           =   375
      End
      Begin Label lblAuthors
         BackColor       =   &H00C0C0C0&
         Caption         =   "Phone"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   195
         Index           =   8
         Left            =   120
         TabIndex        =   17
         Top             =   1500
         Width           =   465
      End
   End
   Begin SSPanel panItemsRemoved
      Alignment       =   8  'Center - BOTTOM
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      FontBold        =   0   'False
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   8.25
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   300
      Left            =   75
      TabIndex        =   28
      Top             =   75
      Width           =   2625
   End
   Begin SSPanel panItemsRead
      Alignment       =   8  'Center - BOTTOM
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      FontBold        =   0   'False
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   8.25
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   300
      Left            =   75
      TabIndex        =   27
      Top             =   1965
      Width           =   2625
   End
   Begin SSPanel panAuthors
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BevelInner      =   1  'Inset
      BevelOuter      =   0  'None
      BorderWidth     =   0
      ForeColor       =   &H00000000&
      Height          =   1035
      Index           =   0
      Left            =   75
      TabIndex        =   26
      Top             =   450
      Width           =   2625
      Begin ListBox lstPrim
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         Height          =   1005
         Left            =   15
         TabIndex        =   0
         Top             =   15
         Width           =   2595
      End
   End
   Begin Frame Frame1
      BackColor       =   &H00C0C0C0&
      Caption         =   "Invisibles"
      FontBold        =   0   'False
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   8.25
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      Height          =   825
      Left            =   9300
      TabIndex        =   18
      Top             =   555
      Visible         =   0   'False
      Width           =   5700
      Begin SSCommand cmdSubGridDetail
         Caption         =   "SubGridDetail"
         Font3D          =   1  'Raised w/light shading
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         ForeColor       =   &H00000000&
         Height          =   495
         Left            =   3570
         TabIndex        =   40
         Top             =   240
         Visible         =   0   'False
         Width           =   1215
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   7
         Left            =   2850
         Picture         =   AUTHORS.FRX:049C
         TabIndex        =   25
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   6
         Left            =   2325
         Picture         =   AUTHORS.FRX:0636
         TabIndex        =   24
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   5
         Left            =   1935
         Picture         =   AUTHORS.FRX:07A0
         TabIndex        =   23
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   4
         Left            =   1410
         Picture         =   AUTHORS.FRX:093A
         TabIndex        =   22
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   3
         Left            =   885
         Picture         =   AUTHORS.FRX:0AA4
         TabIndex        =   21
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Enabled         =   0   'False
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   2
         Left            =   495
         Picture         =   AUTHORS.FRX:0C0E
         TabIndex        =   20
         Top             =   300
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Index           =   1
         Left            =   105
         Picture         =   AUTHORS.FRX:0D78
         TabIndex        =   19
         Top             =   300
         Width           =   345
      End
   End
   Begin Menu menFile
      Caption         =   "&File"
      Begin Menu altClose
         Caption         =   "&Close Form"
      End
      Begin Menu altDummy1
         Caption         =   "-"
      End
      Begin Menu altExit
         Caption         =   "&Exit"
      End
   End
   Begin Menu menEdit
      Caption         =   "&Edit"
      Begin Menu altNew
         Caption         =   "&New"
         Shortcut        =   ^N
      End
      Begin Menu altSave
         Caption         =   "&Save"
         Shortcut        =   ^S
      End
      Begin Menu altDelete
         Caption         =   "&Delete"
      End
      Begin Menu altClear
         Caption         =   "Clear &Form"
         Shortcut        =   ^C
      End
      Begin Menu altRefresh
         Caption         =   "&Refresh"
         Shortcut        =   ^R
      End
      Begin Menu altQuery
         Caption         =   "&Query"
         Shortcut        =   ^Q
      End
      Begin Menu altCancel
         Caption         =   "&Cancel"
      End
      Begin Menu altDummy2
         Caption         =   "-"
      End
      Begin Menu altCopy
         Caption         =   "Copy &to new"
      End
      Begin Menu altRelease
         Caption         =   "&Release"
      End
   End
   Begin Menu menForms
      Caption         =   "Fo&rms"
      Begin Menu altAuthors
         Caption         =   "&Authors"
         Shortcut        =   ^A
      End
   End
   Begin Menu menWindow
      Caption         =   "&Window"
      WindowList      =   -1  'True
      Begin Menu altCascade
         Caption         =   "&Cascade"
      End
      Begin Menu altTile
         Caption         =   "&Tile"
      End
      Begin Menu altArrangeIcons
         Caption         =   "&Arrange Icons"
      End
      Begin Menu altDummy4
         Caption         =   "-"
      End
      Begin Menu altNormalSize
         Caption         =   "&Normal Size"
      End
   End
   Begin Menu menSettings
      Caption         =   "&Options"
      Begin Menu altShowSQL
         Caption         =   "&Show SQL"
      End
   End
   Begin Menu menHelp
      Caption         =   "&Help"
      Begin Menu altContents
         Caption         =   "&Contents"
      End
      Begin Menu altDummy3
         Caption         =   "-"
      End
      Begin Menu altAbout
         Caption         =   "&About..."
      End
   End
End
'
' The AUTHORS form
' =================
' Used for maintenance of information on authors
' including selecting, inserting, updating and deleting
' authors
'
' Constant definitions:
' --------------------
' The nominal Width and Height of the form are saved
' as constants to allow the form to be properly opened
' as a MDI Child and to regain its normal size when
' that option is chosen in the menu system (Window -
' Normal Size):
Const DEFWIDTH% = 9255
Const DEFMINHEIGHT% = 3060 - 280
Const DEFMAXHEIGHT% = 5650 - 280

' The value of the constant CHUNKSIZE% determins the number of
' rows that will be read from the result set
' per chunk and added to the listbox.
' The value of the constant LSTPRIMMAX% is the max number of
' rows that will be held in the listbox lstPrim. When the
' number has been exeeded the application will start to remove
' the top row for each new row added to the listbox.
'
' NOTE! Both of these constants are indeed changeble to match
' your specific needs and thoughts.
Const CHUNKSIZE% = 100
Const LSTPRIMMAX% = 500

' The list box shows authors names. To help locate an
' author whose name is selected in the list box his or
' her AU_ID is also stored in the list box invisible
' to the user. The LISTWIDTH constant is used to determine
' exactly where in the list box text string the AU_ID is
' stored. It begins on the position of LISTWIDTH + 1 (71):
Const LISTWIDTH% = 70

' Declare variables for a select channel and an update
' channel to SQL Server, known all over but not outside
' the form:
Dim SelChannel As Integer
Dim UpdChannel As Integer

' Declare a variable to store the last used Where clause.
' This allows a refresh operation, sending the same SQL
' select clause once again to the server using the refresh
' tool (shower) in the tool bar:
Dim LastWhereClause As String

' Declare a MODE variable to store the different modes
' of the form. Mode could take up either of the following
' values: SQLNEW, SQLSHOW or SQLREST. Those values are
' declared as symbolic constants in the module ADBGLOB.BAS.
' SQLNEW is the mode when a new author is being entered.
' SQLSHOW is the mode when an existing author is shown
' in the form for possible modification or deletion.
' SQLREST is the mode when the form is resting, i.e. when
' a selection is being made or when the form is waiting for
' examples to use for QBE.

Dim Mode As Integer

' The variable QualString save the qualification string
' returned from the call to SQLQUAL(). It containts references
' to the unique key and to the timestamp column.
' This is essential for Browse Mode.
Dim QualString As String

' The variable FormMaxOpen is TRUE if the subgrid is showned.
' The user brings it up by clicking on the push button for
' titles. To close the subgrid just click on the push button
' again and FormMaxOpen will be set to FALSE.

Dim FormMaxOpen As Integer

Sub altAbout_Click ()
'
' Show the about box
'

    fAbout.Show MODAL

End Sub

Sub altArrangeIcons_Click ()
'
' Arrange the icons of the MDI application
'

    MDIMA.Arrange ARRANGE_ICONS

End Sub

Sub altAuthors_click ()
'
' Show another instance of the authors form
' Try opening several instances showing different
' sets of authors in each separate form, for instance
' Utah authors in one form and Michigan authors in
' another. Tile the forms for better overview.
'

' Declare a new instance of the Authors form:
Dim newAuthors As New fAuthors

    ' Open the new instance:
    screen.MousePointer = 11
    newAuthors.Show

screen.MousePointer = 0

End Sub

Sub altCancel_Click ()
'
' Cancels the ongoing operation; for instance modification
' of data about an author. If there is any operation
' to cancel, the cancel push button (with the X, like
' in Excel) will be enabled and then automatically pushed.
' If it is not enable there will be a beep:

    If MainButton(7).Enabled Then
        MainButton(7).Value = True
    Else
        Beep
    End If

End Sub

Sub altCascade_Click ()
'
' Cascade the icons of the MDI application
'

    MDIMA.Arrange CASCADE

End Sub

Sub altClear_Click ()
'
' Clears the content of the form by automatically
' pressing the Clear button in the main tool bar:

    If MainButton(4).Enabled Then
        MainButton(4).Value = True
    Else
        Beep
    End If

End Sub

Sub altClose_Click ()
'
' Closes the form by unloading it, thus securing
' that any unload procedure will be carried out:

    Unload Me

End Sub

Sub altContents_Click ()
Dim x%, lHelpPath As String
'
' Activate the help system
'

    lHelpPath = App.Path + "\pubs1.hlp"
    x% = WinHelp(MDIMA.hWnd, lHelpPath, HELP_INDEX, 0)

End Sub

Sub altCopy_Click ()
'
' Sets the mode to SQLNEW, thus allowing the record
' in the form to be modified and then inserted, then
' sets focus to the first text field in the form. This
' function can save some typing:

    Mode = SQLNEW
    txtAuthors(1).SetFocus

End Sub

Sub altDelete_Click ()
'
' Starts a delete procedure by pressing the delete
' button in the main tool bar. If it is enabled, the
' process controlled by the button will be started,
' otherwise there will be a beep, telling that there
' is nothing to delete at present:

    If MainButton(3).Enabled Then
        MainButton(3).Value = True
    Else
        Beep
    End If

End Sub

Sub altExit_Click ()
'
' Exit the entire application by unloading the MDI
' form. The result will be that any opened MDI child
' window will be unloaded, their respective unload
' procedures automatically carried out:

    Unload MDIMA

End Sub

Sub altNew_Click ()
'
' Start the NEW procedure to enter and insert information
' about a new author. If the corresponding button in the
' main tool bar is enabled, it is pushed, otherwise a new
' author could not be entered at this state of the form,
' and a beep is heard:

    If MainButton(1).Enabled Then
        MainButton(1).Value = True
    Else
        Beep
    End If

End Sub

Sub altNormalSize_Click ()
'
' Reinstate the normal size of the form, if changed
'

    If Me.WindowState <> 0 Then Me.WindowState = 0  ' normal size
    Me.Width = DEFWIDTH%
    If FormMaxOpen Then
        Me.Height = DEFMAXHEIGHT%
    Else
        Me.Height = DEFMINHEIGHT%
    End If
    CenterForm

End Sub

Sub altQuery_Click ()
'
' Send a query to SQL Server by pushing the corresponding
' push button in the tool bar, if it is enabled. If not,
' there is no query to send and a beep is heard:

    If MainButton(6).Enabled Then
        MainButton(6).Value = True
    Else
        Beep
    End If

End Sub

Sub altRefresh_Click ()
'
' Send the same query as was last sent to SQL Server,
' thereby refreshing the result set to include any
' changes made by other users since the data was last
' selected:
' Pushes the corresponding button in the main tool bar
' if it is enabled. If not, a beep is heard, signalling
' that refresh is an inappropriate operation at this time:

    If MainButton(5).Enabled Then
        MainButton(5).Value = True
    Else
        Beep
    End If

End Sub

Sub altRelease_Click ()
'
' Releases the record in the form from its connection
' with the database, allowing the user to make changes
' and then use the result as an example for a query.
'
' Sets the MODE variable to SQLREST, indicating that the
' form is "resting" (will not allow saving the info)

    Mode = SQLREST

End Sub

Sub altSave_Click ()
'
' Saves the content of the form to SQL Server by issuing
' an UPDATE or INSERT statement. Uses the corresponding
' push button in the main tool bar if it is enabled.
' Otherwise a beep is heard, indicating that there is
' nothing to save right now:

    If MainButton(2).Enabled Then
        MainButton(2).Value = True
    Else
        Beep
    End If

End Sub

Sub altShowSQL_Click ()
'
' Controls wether SQL statements are shown in a message
' box or not. When this menu option is checked, all
' SQL statements are shown before sent to the server:

    If altShowSQL.Checked Then
        altShowSQL.Checked = False
        ShowSQL = False
    Else
        altShowSQL.Checked = True
        ShowSQL = True
    End If

End Sub

Sub AltTile_Click ()
'
' Tile the icons of the MDI application
'

    MDIMA.Arrange TILE_HORIZONTAL

End Sub

Sub CenterForm ()

    Me.Top = screen.Height / 2 - Me.Height / 2 - 710

End Sub

Sub chkContract_Click (Value As Integer)
'
' If the form is not resting (MODE <> SQLREST), the
' SAVE and CANCEL buttons are enabled, indicating that
' some information that should be saved has been entered:

    If Mode <> SQLREST Then
        MainButton(2).Enabled = True        ' save button
        MainButton(7).Enabled = True        ' cancel button
    End If

End Sub

Sub ClearFields ()
'
' Clear the contents of any field in the form
'

Dim x As Integer, lMODE As Integer

    lMODE = Mode        ' Save actual Mode value
    Mode = SQLREST      ' Set Mode to resting
    For x = 1 To 8
        txtAuthors(x).Text = ""
    Next
    chkContract.Value = False
    Mode = lMODE        ' Reset initial mode

End Sub

Sub ClearForm ()
Dim lMODE As Integer

    lMODE = Mode        ' Save actual mode
    Mode = SQLREST      ' Set mode to resting
    lstPrim.Clear       ' Clear primary list box
    ClearFields         ' Clear all fields
    If FormMaxOpen Then ClearGrid grdTitles ' clear the grid
    panItemsRead.Caption = ""
    panItemsRemoved.Caption = ""
    Mode = lMODE        ' Reset initial mode
    SetMainButtons Mode ' Enable/Disable buttons in toolbar
    txtAuthors(1).SetFocus

End Sub

Sub ClearGrid (pGrid As Control)
'
' Clear the grid
'

Dim x As Integer, y As Integer

    For x = 1 To pGrid.Rows - 1
        pGrid.Row = x
        For y = 0 To pGrid.Cols - 1
            pGrid.Col = y
            pGrid.Text = ""
        Next
    Next

End Sub

Sub cmdDown_Click (Value As Integer)
'
' Shows the sub grid with titles for an author
'

    If cmdDown.Value = False Then
        SubGridClose
        Exit Sub
    End If
    If Me.WindowState <> 2 Then
        Me.Height = DEFMAXHEIGHT%
        CenterForm
    End If
    FormMaxOpen = True
    frmTitles.Visible = True
    If Len(RTrim$(txtAuthors(1).Text)) > 0 Then
        cmdSubGridDetail.Value = True
    Else
        HighlightGridRow grdTitles, 1
    End If

End Sub

Sub cmdMoreRows_Click ()
'
' This button is enabled whenever the result set
' contains rows that has not yet been read and
' transferred to the lstPrim listbox. When the
' user presses this button another chunk of
' CHUNKSIZE% rows are read. When all the rows of
' the result set has been read (NO_MORE_ROWS) this
' button is disabled.

    SQLGetRows

End Sub

Sub cmdSubGridDetail_Click ()
'
' Sending a SQL statement and filling the sub grid
' with titles for a specific author
'

Dim lCmd$, lId$, res%

    lId$ = txtAuthors(1).Text
    lCmd$ = "select t.title_id, title, type, price, "
    lCmd$ = lCmd$ + "advance, royalty, ytd_sales, "
    lCmd$ = lCmd$ + "notes, pubdate "
    lCmd$ = lCmd$ + "from titles t, titleauthor ta "
    lCmd$ = lCmd$ + "where t.title_id = ta.title_id "
    lCmd$ = lCmd$ + "and ta.au_id = " + Chr$(34) + lId$ + Chr$(34)
    ClearGrid grdTitles
    res% = SQLComm(UpdChannel%, lCmd$)
    If res% = SUCCEED Then SQLFillGrid grdTitles, UpdChannel%
    HighlightGridRow grdTitles, 1

End Sub

Function EvaluateForm ()
'
' Evaluate each field in the form and create a where clause
' for the Select statement
'

Dim lRetStr$, x As Integer

    ' Initialize return string.
    ' (Not neccessary but neater.)
    lRetStr$ = ""
    For x = 1 To 8      ' Loop through each text box in form
        If Len(RTrim$(txtAuthors(x).Text)) > 0 Then
            If Len(Trim$(lRetStr$)) Then lRetStr$ = lRetStr$ + " and "
            Select Case x
                ' Build condition for each field:
                Case 1
                    lRetStr$ = lRetStr$ + "au_id like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 2
                    lRetStr$ = lRetStr$ + "au_fname like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 3
                    lRetStr$ = lRetStr$ + "au_lname like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 4
                    lRetStr$ = lRetStr$ + "address like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 5
                    lRetStr$ = lRetStr$ + "zip like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 6
                    lRetStr$ = lRetStr$ + "city like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 7
                    lRetStr$ = lRetStr$ + "state like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
                Case 8
                    lRetStr$ = lRetStr$ + "phone like " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34)
            End Select
        End If
    Next
    ' Check check box content, build upon the where clause:
    If chkContract.Value = True Then        ' checked
        If Len(Trim$(lRetStr$)) Then lRetStr$ = lRetStr$ + " and "
        lRetStr$ = lRetStr$ + "contract = 1"
    End If
    LastWhereClause = lRetStr$      ' Save as last used clause
    EvaluateForm = lRetStr$         ' Return where clause

End Function

Sub Form_Activate ()

    If ShowSQL Then
        altShowSQL.Checked = True
    Else
        altShowSQL.Checked = False
    End If

End Sub

Sub Form_Load ()
'
' Set up the form at load time
'

Dim res%, lMsg$

    ' Set The Width and Height properties of the form
    ' to their predefined values. Setting the Top and
    ' Left properties to center the form within the
    ' MDI form:
    Me.Width = DEFWIDTH%
    Me.Height = DEFMINHEIGHT%
    CenterForm
    Me.Left = screen.Width / 2 - Me.Width / 2

    ' Opening two connections to SQL Server, one for selecting
    ' sets of authors, another for updating purposes.
    ' Putting the pubs database (Database$) in use:
    SelChannel = Logon%(Server$, LoginId$, Password$, WorkSta$, AppName$)
    UpdChannel = Logon%(Server$, LoginId$, Password$, WorkSta$, AppName$)
    res% = SqlUse(SelChannel, Database$)
    res% = SqlUse(UpdChannel, Database$)

    ' Check the status of the TIMESTAMP column in the Authors table
    res% = CheckifTimestamp(SelChannel)
    If res% = False Then
        res% = AskifAlter()
        If res% Then
            res% = AlterAuthors(SelChannel)
            Beep
            If res% Then
                lMsg$ = "The Authors table has been successfully altered with a TIMESTAMP column."
                MsgBox lMsg$, 64
            Else
                lMsg$ = "There was some problem altering and updating the Authors table." + NEWLINE$ + NEWLINE$
                lMsg$ = lMsg$ + "The sample application will terminate."
                MsgBox lMsg$, 48
                Unload MDIMA
            End If
        Else
            Beep
            MsgBox "Sorry, the sample application will terminate.", 48
            Unload MDIMA            ' this will terminate the application
        End If
    End If

    ' Specify the sub grid for titles
    SpecifyGrid

End Sub

Sub Form_Unload (Cancel As Integer)

    SqlClose SelChannel
    SqlClose UpdChannel

End Sub

Sub grdTitles_Click ()

    HighlightGridRow grdTitles, (grdTitles.Row)

End Sub

Sub HandleEmptyQual ()
Dim lMsg$

    Beep
    lMsg$ = EmptyQualString()
    MsgBox lMsg$, 16, "Empty qual string"
    RefreshFormFields

End Sub

Sub HighlightGridRow (pGrid As Control, pRowNmbr As Integer)
'
' Highlights the selected row in the grid
'

    If pRowNmbr = 0 Then pRowNmbr = 1       ' to avoide VB error
    pGrid.Row = pRowNmbr
    pGrid.SelStartRow = pRowNmbr
    pGrid.SelEndRow = pRowNmbr
    pGrid.Col = pGrid.Cols - 1
    pGrid.SelStartCol = 0
    pGrid.SelEndCol = pGrid.Cols - 1

End Sub

Sub lstPrim_Click ()
'
' If there are any items in the primary list box when
' a click event occurs, then SQLGetDetailRow is called
' to get all information on the selected author and put
' it in the form fields. MODE is set to SQLREST in order
' to prevent some change actions on the different fields.
' After the author is shown, MODE is set to SQLSHOW,
' indicating that the form is active (an author in it)

    If lstPrim.ListIndex <> -1 Then
        Mode = SQLREST
        SQLGetDetailRow
        Mode = SQLSHOW
    End If

End Sub

Sub MainButton_Click (index As Integer)
Dim x As Integer
'
' MainButton is a control array of push buttons.
' They may be clicked by the user directly or by
' menu choices where the users select a menu item
' and the menu item pushes a button by setting its
' Value property True.

    Select Case index
        Case 1              ' new
            ' Click button 4 to clear the fields:
            MainButton(4).Value = True
            ' Enable the cancel button:
            MainButton(7).Enabled = True
            ' Set MODE to SQLNEW to allow saving:
            Mode = SQLNEW
        Case 2              ' save
            ' Call the SAVE routine, who knows how
            ' to do inserts and updates:
            SQLSave
        Case 3              ' delete
            ' Call the SQLDelete routine:
            SQLDelete
        Case 4              ' clear form
            ClearForm
        Case 5              ' refresh
            ' Send the same Select statement as last was sent:
            SQLQuery "REFRESH"
        Case 6              ' query
            ' Send a Select statement based on examples given
            ' by filling in parts of the form:
            SQLQuery ""
        Case 7              ' cancel
            ' Reset to same as before any changes were made:
            If Mode = SQLSHOW Then
                Mode = SQLREST
                x = lstPrim.ListIndex
                lstPrim.ListIndex = -1
                lstPrim.ListIndex = x
                Mode = SQLSHOW
            Else
                ClearForm
                Mode = SQLREST
            End If
    End Select

End Sub

Sub RefreshFormFields ()
Dim x As Integer

    x = lstPrim.ListIndex
    lstPrim.ListIndex = -1
    lstPrim.ListIndex = x

End Sub

Function RowToListbox ()
'
' Building a string to be added to the listbox:

Dim lBoxString As String

    ' Last and First Name:
    lBoxString = RTrim$(SQLData(SelChannel, 1)) + ", " + SQLData(SelChannel, 2)
    ' Id:
    lBoxString = Left$(lBoxString + Space$(LISTWIDTH%), LISTWIDTH%) + SQLData(SelChannel, 3)
    RowToListbox = lBoxString

End Function

Sub SetMainButtons (pMode As Integer)
'
' Enable/Disable buttons in tool bar based on
' actual Mode:

    MainButton(1).Enabled = True
    MainButton(2).Enabled = False
    MainButton(4).Enabled = True
    MainButton(7).Enabled = False
    If pMode = SQLSHOW Then
        MainButton(3).Enabled = True
    Else
        MainButton(3).Enabled = False
    End If

End Sub

Sub SetNmbrofRowsPanels (pNmbrRows, pRemoved)
'
' Sets the right information for how many
' rows that have been read from the result
' set, and how many rows that have been
' removed from the listbox:

    If Len(panItemsRead.Caption) > 0 Then
        pNmbrRows = pNmbrRows + Val(panItemsRead.Caption)
    End If
    If Len(panItemsRemoved.Caption) > 0 Then
        pRemoved = Val(panItemsRemoved.Caption) + pRemoved
    End If
    panItemsRead.Caption = Str$(pNmbrRows) + " rows read"
    If pRemoved > 0 Then
        panItemsRemoved.Caption = Str$(pRemoved) + " rows removed"
    Else
        panItemsRemoved.Caption = ""
    End If

End Sub

Sub SpecifyGrid ()
'
' Specify the sub grid for titles
'

    grdTitles.Rows = 10
    grdTitles.Cols = 9
    grdTitles.Row = 0
    grdTitles.Col = 0
    grdTitles.Text = "Title id"
    grdTitles.ColWidth(0) = 750
    grdTitles.Col = 1
    grdTitles.Text = "Title"
    grdTitles.ColWidth(1) = 3500
    grdTitles.Col = 2
    grdTitles.Text = "Type"
    grdTitles.ColWidth(2) = 855
    grdTitles.Col = 3
    grdTitles.Text = "Price"
    grdTitles.ColWidth(3) = 645
    grdTitles.Col = 4
    grdTitles.Text = "Advance"
    grdTitles.ColWidth(4) = 960
    grdTitles.Col = 5
    grdTitles.Text = "Royalty"
    grdTitles.ColWidth(5) = 645
    grdTitles.Col = 6
    grdTitles.Text = "YTD sales"
    grdTitles.ColWidth(6) = 840
    grdTitles.Col = 7
    grdTitles.Text = "Notes"
    grdTitles.ColWidth(7) = 4050
    grdTitles.Col = 8
    grdTitles.Text = "Pub. date"
    grdTitles.ColWidth(8) = 1050

End Sub

Sub SQLDelete ()
'
' Create an appropriate delete statement, based on the actual
' record shown in the form. Ask the user to confirm the delete.
' If all is well: Send the delete statement:

Dim lCmd$, x As Integer, res%

    If Len(RTrim$(txtAuthors(1).Text)) = 0 Or Mode <> SQLSHOW Then
        Beep
        Exit Sub            ' Nothing to delete
    End If
    If Len(RTrim$(txtAuthors(1).Text)) > 0 Then
        lCmd$ = "Delete authors " + QualString
        ' Check if the variable QualString is blank - if so we can
        ' not allow any deletions
        If Len(RTrim$(QualString)) = 0 Then
            HandleEmptyQual
            screen.MousePointer = 0
            Exit Sub
        End If

        If MsgBox("Do you really want to delete this row?", 36, "Delete") = 6 Then
            ' Use the SQLComm function to perform the delete:
            screen.MousePointer = 11
            res% = SQLComm%(UpdChannel, lCmd$)
            If res% = SUCCEED% Then
                ' Remove record from form and set up form for continuation:
                Mode = SQLREST
                x = lstPrim.ListIndex
                ClearFields
                If x <> -1 Then lstPrim.RemoveItem x
                ClearGrid grdTitles
            End If
            screen.MousePointer = 0
        End If
    End If

End Sub

Sub SQLFillGrid (pGrid As Control, pChannel%)
'
' Filling then grid with data
'

Dim x As Integer

    pGrid.Row = 0: pGrid.Rows = 10
    Do
        res% = SQLNextRow(pChannel%)
        Select Case res%
        Case REGROW
            If pGrid.Row >= pGrid.Rows - 1 Then pGrid.Rows = pGrid.Rows + 1
            pGrid.Row = pGrid.Row + 1
            For x = 0 To pGrid.Cols - 1
                pGrid.Col = x
                pGrid.Text = SQLData(pChannel%, x + 1)
            Next
        Case NOMOREROWS
            pGrid.Col = 0
            pGrid.Row = 1
            Exit Do
        Case Else
            Exit Do        ' error message by ordinary VBSQL-error routine
        End Select
    Loop Until 1 = 0
' ------------------------------------------------

End Sub

Sub SQLGetDetailRow ()
'
' The user has selected an item in the primary list box.
' This routine creates and sends a SQL statement to
' retrieve all information on the selected author:

Dim lAu_Id$, lCmd$, res%, x As Integer

    screen.MousePointer = 11
    ' The AU_ID field is positioned beyond LISTWIDTH% in the list box item:
    lAu_Id$ = Mid$(lstPrim.List(lstPrim.ListIndex), LISTWIDTH% + 1)

    lCmd$ = "select au_id, au_fname, au_lname, address, zip, city, "
    lCmd$ = lCmd$ + "state, phone, contract "
    lCmd$ = lCmd$ + "from authors "
    lCmd$ = lCmd$ + "where au_id = " + Chr$(34) + lAu_Id$ + Chr$(34)
    lCmd$ = lCmd$ + " FOR BROWSE"

    ' Send the select statement and check the result:
    res% = SQLComm(UpdChannel, lCmd$)
    If res% = SUCCEED% Then
        res% = SQLNextRow(UpdChannel)
        QualString = sqlqual(UpdChannel, 1, "")
        If res% = REGROW Then
            ' Show each separate field:
            For x = 1 To 8
                txtAuthors(x).Text = SQLData(UpdChannel, x)
            Next
            chkContract.Value = SQLData(UpdChannel, 9)
        End If
    End If
    ' Check to see if the sub grid is showned - if so we will
    ' refresh it with data from the selected author
    If FormMaxOpen Then cmdSubGridDetail.Value = True
    screen.MousePointer = 0

End Sub

Sub SQLGetRows ()
'
' This routine loops through the result set and
' add rows to the listbox:

Dim lNmbrRows As Integer, lItems%, res%
Dim lNoMoreRowsToRead  As Integer, lRemoved As Integer

    lNoMoreRowsToRead = False
    For lNmbrRows = 1 To CHUNKSIZE%
        res% = SQLNextRow(SelChannel)
        If res% = REGROW Then
            lstPrim.AddItem RowToListbox()
        ElseIf res% = NOMOREROWS Then
            lNoMoreRowsToRead = True
            Exit For
        Else
            Exit For            ' error message by ordinary VBSQL-error routine
        End If
        If lstPrim.ListCount > LSTPRIMMAX% Then
            lstPrim.RemoveItem 0
            lRemoved = lRemoved + 1
        End If
    Next
    lNmbrRows = lNmbrRows - 1

    If lNoMoreRowsToRead Then          'no more rows to read from the result set
        cmdMoreRows.Enabled = False
    Else
        cmdMoreRows.Enabled = True
    End If

    SetNmbrofRowsPanels lNmbrRows, lRemoved

End Sub

Sub SQLInsert ()
'
' Create an Insert statement and send it to SQL Server:
'

Dim lCmd$, x As Integer, res%, lKey$

    screen.MousePointer = 11
    lCmd$ = "Insert authors ("
    lCmd$ = lCmd$ + "au_id, au_fname, au_lname, address, "
    lCmd$ = lCmd$ + "zip, city, state, phone, contract) "
    lCmd$ = lCmd$ + "values ("
    For x = 1 To 8      ' Evaluate the content of each field
        lCmd$ = lCmd$ + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
    Next
    lCmd$ = lCmd$ + Abs(chkContract.Value) + ")"

    ' Send to SQL Server and check result; Set up form after success:
    res% = SQLComm%(SelChannel, lCmd$)
    If res% = SUCCEED% Then
        Mode = SQLREST
        lKey$ = txtAuthors(1).Text
        MainButton(4).Value = True      ' Clear form
        txtAuthors(1).Text = lKey$      ' Set up query for inserted Author
        MainButton(6).Value = True      ' Send query
        Mode = SQLSHOW
    Else
        Mode = SQLREST
    End If
    lstPrim.SetFocus
    screen.MousePointer = 0

End Sub

Sub SQLQuery (pMode As String)
'
' Evaluate the form content for a QBE Select statement
' Send it to SQL Server. Read the result set and use it
' to populate the primary list box:

Dim lCmd$, lWhereClause$, res%

    screen.MousePointer = 11
    Mode = SQLREST
    panItemsRead.Caption = ""
    panItemsRemoved.Caption = ""
    If pMode = "REFRESH" Then
        lWhereClause$ = LastWhereClause$
    Else
        lWhereClause$ = EvaluateForm()
    End If
    lstPrim.Clear
    lCmd$ = "select au_lname, au_fname, au_id "
    lCmd$ = lCmd$ + "from authors "
    If Len(RTrim$(lWhereClause$)) Then
        lCmd$ = lCmd$ + "where " + lWhereClause$ + " "
    End If
    lCmd$ = lCmd$ + "order by au_lname, au_fname"

    res% = SQLComm(SelChannel, lCmd$)

    If res% = SUCCEED% Then
        SQLGetRows
    End If
    If lstPrim.ListCount > 0 Then
        lstPrim.ListIndex = 0           ' Autoclick on first in list box
        Mode = SQLSHOW
        SetMainButtons Mode
    Else
        Mode = SQLREST
    End If
    screen.MousePointer = 0

End Sub

Sub SQLSave ()
'
' Choose wether to create an insert or update statement based
' on the actual Mode:

    If Mode = SQLNEW Then
        SQLInsert
    Else
        SQLUpdate
    End If
    ' NOTE: Mode will be changed by SQLInsert/SQLUpdate
    SetMainButtons Mode

End Sub

Sub SQLUpdate ()
'
' Create an Update statement and send it to SQL Server:

Dim lCmd$, lRow$, x As Integer, res%

    screen.MousePointer = 11
    If Len(RTrim$(txtAuthors(1).Text)) > 0 Then
        lCmd$ = "Update authors set "
        For x = 2 To 8      ' Evaluate the content of each non key field
            Select Case x
                Case 2
                    lCmd$ = lCmd$ + "au_fname = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 3
                    lCmd$ = lCmd$ + "au_lname = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 4
                    lCmd$ = lCmd$ + "address = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 5
                    lCmd$ = lCmd$ + "zip = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 6
                    lCmd$ = lCmd$ + "city = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 7
                    lCmd$ = lCmd$ + "state = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
                Case 8
                    lCmd$ = lCmd$ + "phone = " + Chr$(34) + RTrim$(txtAuthors(x).Text) + Chr$(34) + ", "
            End Select
        Next
        lCmd$ = lCmd$ + "contract = " + Abs(chkContract.Value)
        lCmd$ = lCmd$ + " " + QualString
        ' Control to see if the variable QualString is blank - if so we can not allow any update
        If Len(RTrim$(QualString)) = 0 Then
            HandleEmptyQual
            screen.MousePointer = 0
            Exit Sub
        End If
        ' Send to SQL Server and check result; Set up form after success:
        res% = SQLComm%(UpdChannel, lCmd$)
        If res% = SUCCEED% Then
            Mode = SQLREST
            lRow$ = Left$(RTrim$(txtAuthors(3).Text) + ", " + txtAuthors(2).Text + Space$(LISTWIDTH%), LISTWIDTH%) + txtAuthors(1).Text
            ' Modify list box item to reflect possible changes from update:
            If lstPrim.ListIndex <> -1 Then     ' to prevent a VB error
                x = lstPrim.ListIndex
                lstPrim.RemoveItem x
                lstPrim.AddItem lRow$, x
                lstPrim.ListIndex = x
                Mode = SQLSHOW
            End If
        Else
            RefreshFormFields
        End If
    Else
        Beep
        MsgBox "The update operation was not successful.", 16
    End If
    screen.MousePointer = 0

End Sub

Sub SubGridClose ()
'
' Clears the grid and makes it invisible
'

    If Me.WindowState <> 2 Then
        Me.Height = DEFMINHEIGHT%
        CenterForm
    End If
    FormMaxOpen = False
    ClearGrid grdTitles
    frmTitles.Visible = False

End Sub

Sub txtAuthors_Change (index As Integer)
'
' If changes are made to any text field included in the
' control array of text boxes, and if MODE is anything
' else than SQLREST, then enable the save button and
' the cancel button. This makes it possible to save or
' cancel any modifications made:

    If Mode <> SQLREST Then
        MainButton(2).Enabled = True        ' save button
        MainButton(7).Enabled = True        ' cancel button
    End If

End Sub

Sub txtAuthors_GotFocus (index As Integer)

    ' Modification of key field prevented
    If Mode = SQLSHOW And index = 1 Then txtAuthors(index + 1).SetFocus
    ' Highlight existing entry
    txtAuthors(index).SelStart = 0
    txtAuthors(index).SelLength = Len(RTrim$(txtAuthors(index).Text))

End Sub


BLOGIN.FRM

VERSION 2.00
Begin Form login
   BorderStyle     =   3  'Fixed Double
   Caption         =   "SQL Server Login"
   Height          =   2685
   Icon            =   0
   Left            =   1035
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2280
   ScaleWidth      =   6690
   Top             =   1020
   Width           =   6810
   Begin TextBox password_field
      Height          =   375
      Left            =   1320
      TabIndex        =   5
      Text            =   "Text1"
      Top             =   1440
      Width           =   3495
   End
   Begin CommandButton CANCEL_BUTTON
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   495
      Left            =   5160
      TabIndex        =   7
      Top             =   840
      Width           =   1335
   End
   Begin TextBox login_id_field
      Height          =   375
      Left            =   1320
      TabIndex        =   3
      Text            =   "Text1"
      Top             =   840
      Width           =   3495
   End
   Begin CommandButton OK_BUTTON
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   495
      Left            =   5160
      TabIndex        =   6
      Top             =   240
      Width           =   1335
   End
   Begin TextBox Server_name_field
      Height          =   375
      Left            =   1320
      TabIndex        =   1
      Text            =   "Text1"
      Top             =   240
      Width           =   3495
   End
   Begin Label Label3
      Caption         =   "&Password:"
      Height          =   255
      Left            =   120
      TabIndex        =   4
      Top             =   1440
      Width           =   975
   End
   Begin Label Label2
      Caption         =   "&Login ID:"
      Height          =   255
      Left            =   120
      TabIndex        =   2
      Top             =   840
      Width           =   975
   End
   Begin Label Label1
      Caption         =   "&Server:"
      Height          =   255
      Left            =   120
      TabIndex        =   0
      Top             =   240
      Width           =   975
   End
End
'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub CANCEL_BUTTON_Click ()

    ExitApplication

End Sub

Sub Form_Load ()
    Server_name_field.Text = DefServer$
    Login_id_field.Text = DefLogin$
    PASSWORD_FIELD.Text = ""
    password$ = ""
End Sub

Sub OK_BUTTON_Click ()

Rem
Rem Get the server name, login Id, & password from the form
Rem

   Servername$ = Server_name_field.Text
   LoginID$ = Login_id_field.Text

Rem Note-- allow null servername becuase you might be running on the same
Rem machine as SQL Server.  Allow null userid because server might be running
Rem integrated security.

Rem Open select connection for browse mode:
Rem

    If LoginToServer() = FAIL Then
        PASSWORD_FIELD.Text = ""
        password$ = ""
        Exit Sub
    End If

Rem
Rem Now Open update connection for browse mode:
Rem Check to see if the connection is live, if so, then close it
Rem Set the max time to login to 30 seconds
Rem Open the new connection
Rem Change the caption of the application to reflect the server name and the database
Rem Set the max time we will wait for a SQL Server response
Rem

    If SqlUpdConn <> 0 Then SqlClose (SqlUpdConn)
    Status% = SqlSetLoginTime%(LoginTimeout)
    SqlUpdConn = SqlOpenConnection(Servername$, LoginID$, password$, ProgramName$, ProgramName$)
    If SqlUpdConn <> 0 Then
        DatabaseName$ = SqlName(SqlUpdConn)
        ChangePrimaryWindowCaption
        Result5 = SqlSetTime%(QueryTimeout)
    Else
        DatabaseName$ = ""
        Servername$ = ""
    End If

    Unload Login
End Sub

Sub password_field_KeyPress (keyascii As Integer)

Rem
Rem This will keep the key from being seen
Rem

'   if key pressed is a letter or a number, add it to password and show *

If (keyascii >= 48 And keyascii <= 57) Or (keyascii >= 65 And keyascii <= 90) Or (keyascii >= 97 And keyascii <= 122) Then
        password$ = password$ + Chr$(keyascii)
        keyascii = Asc("*")

ElseIf (keyascii = 8) Then   'if backspace
        If Len(password$) > 0 Then
        password$ = Left$(password$, Len(password$) - 1)
        End If
Else                        'if anything else, throw out
        keyascii = 0
        Beep
End If

End Sub


BROWSE.BAS

Rem Globals

Rem These handle the SQL Server connection

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global SqlConn%
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global DefServer$
Global DefLogin$
Global Const LoginTimeout% = 30
Global Const QueryTimeout% = 60

Rem Additional needed for updates in browse mode
Global SqlUpdConn%


BROWSE.FRM

VERSION 2.00
Begin Form PrimaryWindow
   BackColor       =   &H00FFFF00&
   Caption         =   "Browse Mode Example"
   Height          =   5190
   Icon            =   BROWSE.FRX:0000
   Left            =   1515
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   4665
   ScaleWidth      =   6105
   Top             =   930
   Width           =   6225
   Begin VBSQL VBSQL1
      Caption         =   "SQL Msg/Err"
      Height          =   255
      Left            =   3840
      Top             =   4440
      Visible         =   0   'False
      Width           =   1935
   End
   Begin TextBox Contract_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   19
      Top             =   4200
      Width           =   1935
   End
   Begin CommandButton Command3
      Caption         =   "Exit"
      Height          =   495
      Left            =   1200
      TabIndex        =   1
      Top             =   3960
      Width           =   855
   End
   Begin CommandButton Command1
      Caption         =   "About"
      Height          =   495
      Left            =   240
      TabIndex        =   13
      Top             =   3960
      Width           =   855
   End
   Begin TextBox Zip_Code_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   18
      Top             =   3720
      Width           =   1935
   End
   Begin TextBox State_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   17
      Top             =   3240
      Width           =   1935
   End
   Begin CommandButton Cancel_Query_Button
      Caption         =   "Cancel Query"
      Height          =   615
      Left            =   240
      TabIndex        =   8
      Top             =   3000
      Width           =   1815
   End
   Begin TextBox City_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   20
      Top             =   2760
      Width           =   1935
   End
   Begin TextBox Address_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   16
      Top             =   2280
      Width           =   1935
   End
   Begin CommandButton Update_Button
      Caption         =   "Update Row"
      Height          =   615
      Left            =   240
      TabIndex        =   11
      Top             =   2040
      Width           =   1815
   End
   Begin TextBox Phone_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   15
      Top             =   1800
      Width           =   1935
   End
   Begin TextBox First_Name_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   14
      Top             =   1320
      Width           =   1935
   End
   Begin CommandButton Next_Button
      Caption         =   "Next Row"
      Height          =   615
      Left            =   240
      TabIndex        =   12
      Top             =   1200
      Width           =   1815
   End
   Begin TextBox Last_Name_Edit
      Height          =   285
      Left            =   3840
      TabIndex        =   22
      Top             =   840
      Width           =   1935
   End
   Begin CommandButton Select_Button
      Caption         =   "Select for Browse"
      Height          =   615
      Left            =   240
      TabIndex        =   0
      Top             =   360
      Width           =   1815
   End
   Begin Label Label9
      BackColor       =   &H00FFFF00&
      Caption         =   "Contract:"
      Height          =   255
      Left            =   2640
      TabIndex        =   10
      Top             =   4200
      Width           =   825
   End
   Begin Label Label6
      BackColor       =   &H00FFFF00&
      Caption         =   "Zip Code:"
      Height          =   255
      Left            =   2640
      TabIndex        =   6
      Top             =   3720
      Width           =   975
   End
   Begin Label Label5
      BackColor       =   &H00FFFF00&
      Caption         =   "State:"
      Height          =   255
      Left            =   2640
      TabIndex        =   5
      Top             =   3240
      Width           =   555
   End
   Begin Label Label4
      BackColor       =   &H00FFFF00&
      Caption         =   "City:"
      Height          =   285
      Left            =   2640
      TabIndex        =   4
      Top             =   2760
      Width           =   420
   End
   Begin Label Label8
      BackColor       =   &H00FFFF00&
      Caption         =   "Address:"
      Height          =   255
      Left            =   2640
      TabIndex        =   9
      Top             =   2280
      Width           =   780
   End
   Begin Label Label3
      BackColor       =   &H00FFFF00&
      Caption         =   "Phone:"
      Height          =   255
      Left            =   2640
      TabIndex        =   3
      Top             =   1800
      Width           =   630
   End
   Begin Label Label7
      BackColor       =   &H00FFFF00&
      Caption         =   "First Name:"
      Height          =   255
      Left            =   2655
      TabIndex        =   23
      Top             =   1365
      Width           =   1095
   End
   Begin Label Label2
      BackColor       =   &H00FFFF00&
      Caption         =   "Last Name:"
      Height          =   255
      Left            =   2670
      TabIndex        =   7
      Top             =   870
      Width           =   1095
   End
   Begin Label Author_ID_Label
      BackColor       =   &H00FFFF00&
      Height          =   255
      Left            =   3840
      TabIndex        =   21
      Top             =   405
      Width           =   1935
   End
   Begin Label Label1
      BackColor       =   &H00FFFF00&
      Caption         =   "Author ID:"
      Height          =   195
      Left            =   2640
      TabIndex        =   2
      Top             =   360
      Width           =   915
   End
End
Dim Qual$

Dim UpdateValueArray(8) As String
Dim TableNameArray(8) As String

Sub Address_Edit_Change ()

    If Address_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(3) = Address_Edit.Text
    End If

End Sub

Sub Cancel_Query_Button_Click ()

    Rem Cancel Query
    PrimaryWindow.MousePointer = 11
    Results% = SQLCanQuery(SqlConn%)
    ClearAuthorInformation
    Select_Button.Enabled = True
    Cancel_Query_Button.Enabled = False
    Update_Button.Enabled = False
    Next_Button.Enabled = False
    PrimaryWindow.MousePointer = 0

End Sub

Sub CheckForTimestampColumn ()

    Rem Change context to pubs sample database
    Results% = SqlUse(SqlConn%, "pubs")
    Results% = SqlUse(SqlUpdConn%, "pubs")
    If Results% = FAIL Then
        Beep
        Msg$ = "The SQL Server you have logged" + Chr$(13) + Chr$(10)
        Msg$ = Msg$ + "into (" + Servername$ + ") does not contain the" + Chr$(13) + Chr$(10)
        Msg$ = Msg$ + "Pubs sample database."
        MsgBox Msg$
        ExitApplication
    Else
        Rem Check to see if sample authors table has a timestamp column
        Cmd$ = "If 'timestamp' in (select c.name from syscolumns c, "
        Cmd$ = Cmd$ + "sysobjects o where c.id = o.id and o.name = 'authors') "
        Cmd$ = Cmd$ + "select -1 else select 0 "
        Results% = ExecuteSQLCommand(Cmd$)
        Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
            Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
                Timestamp_Found% = Val(Sqldata(SqlConn%, 1))
            Loop
        Loop
        If Not Timestamp_Found% Then
            Rem Add timestamp column
            Cmd$ = "alter table authors add timestamp timestamp null "
            Results% = ExecuteSQLCommand(Cmd$)
            Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
                Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
                Loop
            Loop
            Rem Now initialize the timestamp values
            Cmd$ = "update authors set au_id = au_id"
            Results% = ExecuteSQLCommand(Cmd$)
            Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
                Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
                Loop
            Loop
        End If
    End If
End Sub

Sub City_Edit_Change ()

    If City_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(4) = City_Edit.Text
    End If

End Sub

Sub ClearAuthorInformation ()

    Author_ID_Label.Caption = ""
    First_Name_Edit.Text = ""
    Last_Name_Edit.Text = ""
    Phone_Edit.Text = ""
    Address_Edit.Text = ""
    City_Edit.Text = ""
    State_Edit.Text = ""
    Zip_Code_Edit.Text = ""
    Contract_Edit.Text = ""

End Sub

Sub Command1_Click ()

    About_Form.Show 1

End Sub

Sub Command3_Click ()

    ExitApplication

End Sub

Sub Contract_Edit_Change ()

    If Contract_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(7) = Contract_Edit.Text
    End If

End Sub

Sub First_Name_Edit_Change ()

    If First_Name_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(1) = First_Name_Edit.Text
    End If

End Sub

Sub Form_Load ()

    LoadTableNameArray
    PrimaryWindowTitle$ = "Browse Mode Example"
    InitializeApplication
    Login.Show 1
    CheckForTimestampColumn
    DatabaseName = SqlName(SqlConn%)
    ChangePrimaryWindowCaption
    Cancel_Query_Button.Enabled = False
    Next_Button.Enabled = False
    Update_Button.Enabled = False

End Sub

Sub Form_Unload (Cancel As Integer)

    ExitApplication

End Sub

Sub InitializeUpdateArray ()
    Dim count As Integer

    For count = 0 To 7
        UpdateValueArray(count) = "not changed"
    Next

End Sub

Sub Last_Name_Edit_Change ()

    If Last_Name_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(0) = Last_Name_Edit.Text
    End If

End Sub

Sub LoadTableNameArray ()

    TableNameArray(0) = "au_lname"
    TableNameArray(1) = "au_fname"
    TableNameArray(2) = "phone"
    TableNameArray(3) = "address"
    TableNameArray(4) = "city"
    TableNameArray(5) = "state"
    TableNameArray(6) = "zip"
    TableNameArray(7) = "contract"

End Sub

Sub Next_Button_Click ()

    PrimaryWindow.MousePointer = 11
    If SqlNextRow%(SqlConn%) <> NOMOREROWS Then
        ProcessResultRow
    Else
        Next_Button.Enabled = False
        Cancel_Query_Button.Enabled = False
        Select_Button.Enabled = True
        ClearAuthorInformation
        Beep
        MsgBox "No more rows in table."
    End If
    InitializeUpdateArray
    Update_Button.Enabled = False
    PrimaryWindow.MousePointer = 0

End Sub

Sub Phone_Edit_Change ()

    If Phone_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(2) = Phone_Edit.Text
    End If

End Sub

Sub ProcessResultRow ()

    Author_ID_Label.Caption = Sqldata(SqlConn%, 1)
    Last_Name_Edit.Text = Sqldata(SqlConn%, 2)
    First_Name_Edit.Text = Sqldata(SqlConn%, 3)
    Phone_Edit.Text = Sqldata(SqlConn%, 4)
    Address_Edit.Text = Sqldata(SqlConn%, 5)
    City_Edit.Text = Sqldata(SqlConn%, 6)
    State_Edit.Text = Sqldata(SqlConn%, 7)
    Zip_Code_Edit.Text = Sqldata(SqlConn%, 8)
    Contract_Edit.Text = Sqldata(SqlConn%, 9)
    Rem Get Qual String
    Qual$ = SqlQual$(SqlConn%, -1, "authors")

End Sub

Sub Select_Button_Click ()

    Rem Send Query to SQL Server
    PrimaryWindow.MousePointer = 11
    Results% = ExecuteSQLCommand("Select * from authors for browse")
    Results% = SqlResults%(SqlConn%)
    If SqlNextRow%(SqlConn%) <> NOMOREROWS Then
        ProcessResultRow
    End If
    Cancel_Query_Button.Enabled = True
    Next_Button.Enabled = True
    Update_Button.Enabled = False
    Select_Button.Enabled = False
    InitializeUpdateArray
    PrimaryWindow.MousePointer = 0

End Sub

Sub State_Edit_Change ()

    If State_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(5) = State_Edit.Text
    End If

End Sub

Sub Update_Button_Click ()
    Dim count As Integer

    PrimaryWindow.MousePointer = 11

    Cmd$ = "update authors set "
    For count = 0 To 7
        If UpdateValueArray(count) <> "not changed" Then
            Cmd$ = Cmd$ + TableNameArray(count) + " = '"
            Cmd$ = Cmd$ + UpdateValueArray(count) + "',"
        End If
    Next
    Rem Replace final comma with space before adding qual string
    Cmd$ = Left$(Cmd$, Len(Cmd$) - 1) + " "
    Rem Now add qual string
    Cmd$ = Cmd$ + Qual$
    Results% = SqlCmd(SqlUpdConn%, Cmd$)
    Results% = SqlExec(SqlUpdConn%)
    Do While (SqlResults%(SqlUpdConn%) <> NOMORERESULTS)
        Do While (SqlNextRow(SqlUpdConn%) <> NOMOREROWS)
        Loop
    Loop
    Update_Button.Enabled = False
    PrimaryWindow.MousePointer = 0

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)

End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$
End Sub

Sub Zip_Code_Edit_Change ()

    If Zip_Code_Edit.Text <> "not changed" Then
        Update_Button.Enabled = True
        UpdateValueArray(6) = Zip_Code_Edit.Text
    End If

End Sub


CHNGDB.FRM

VERSION 2.00
Begin Form CHNGDB
   BackColor       =   &H00FFFFFF&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Change Database"
   ForeColor       =   &H00000000&
   Height          =   2160
   Left            =   2895
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   1755
   ScaleWidth      =   5640
   Top             =   3150
   Width           =   5760
   Begin CommandButton CANCEL_BUTTON
      BackColor       =   &H00C0C0C0&
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   480
      Left            =   3240
      TabIndex        =   3
      Top             =   1200
      Width           =   1320
   End
   Begin CommandButton OK_BUTTON
      BackColor       =   &H00C0C0C0&
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   480
      Left            =   1080
      TabIndex        =   2
      Top             =   1200
      Width           =   1320
   End
   Begin ComboBox DBNAME_COMBO
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   300
      Left            =   2040
      TabIndex        =   1
      Text            =   "Combo1"
      Top             =   360
      Width           =   3240
   End
   Begin Label Label1
      BackColor       =   &H00FFFFFF&
      Caption         =   "Database Name:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   240
      TabIndex        =   0
      Top             =   480
      Width           =   1560
   End
End
'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub CANCEL_BUTTON_Click ()
Unload Chngdb
End Sub

Sub Form_Load ()
Result% = GetDatabases(DBNAME_COMBO)
If Result% = FAIL% Then Unload Chngdb
End Sub

Sub OK_BUTTON_Click ()

newname$ = DBNAME_COMBO.Text
If SqlUse(SqlConn, newname$) = SUCCEED% Then
DatabaseName$ = DBNAME_COMBO.Text
ChangePrimaryWindowCaption
SqlStatus = SUCCEED
Else
SqlStatus = FAIL
End If
Unload Chngdb
End Sub


CURSORS.BAS

Rem Global Definitions

Rem These handle the SQL Server connection

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global SqlConn%
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global DefServer$
Global DefLogin$
Global Const LoginTimout% = 30
Global Const QueryTimeout% = 60
Rem Globals for cursor operations

Global CursorHandle%
Global RowStatus&()
Global OpenTran%
Global CursorColName$(255)
Global CursorColLen&(255)
Global NumRowsInCursor%
Global NumRowsInKeyset&
Global ScrollOpt%
Global NumCols%
Global CursorSliderValue%           'To tell if we are scrolling up or down
Global OutputData(20) As String     'Storage for up to 21 rows


CURSORS.FRM

VERSION 2.00
Begin Form PrimaryWindow
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Cursor Scroll Bar Example"
   ForeColor       =   &H00000000&
   Height          =   7215
   Icon            =   CURSORS.FRX:0000
   Left            =   990
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   6285
   ScaleWidth      =   7725
   Top             =   -30
   Width           =   7845
   Begin ListBox Cursor_Buffer
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "Courier"
      FontSize        =   9.75
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      Height          =   2955
      Left            =   120
      TabIndex        =   18
      Top             =   3240
      Width           =   7215
   End
   Begin VScrollBar VScroll1
      Height          =   3255
      LargeChange     =   15
      Left            =   7320
      Max             =   500
      SmallChange     =   15
      TabIndex        =   20
      Top             =   3000
      Width           =   255
   End
   Begin VBSQL VBSQL1
      Caption         =   "SQL Error/Message"
      Height          =   255
      Left            =   5160
      Top             =   2760
      Visible         =   0   'False
      Width           =   2175
   End
   Begin Frame Frame2
      BackColor       =   &H00C0C0C0&
      Caption         =   "Cursor Information"
      Height          =   1575
      Left            =   120
      TabIndex        =   5
      Top             =   1080
      Width           =   7455
      Begin CommandButton Update_Button
         Caption         =   "&Update"
         Height          =   375
         Left            =   6360
         TabIndex        =   16
         Top             =   1080
         Width           =   975
      End
      Begin ComboBox CC_List
         Height          =   300
         Left            =   2160
         Style           =   2  'Dropdown List
         TabIndex        =   13
         Top             =   1080
         Width           =   3255
      End
      Begin CommandButton Close_Button
         Caption         =   "C&lose"
         Height          =   375
         Left            =   6360
         TabIndex        =   15
         Top             =   720
         Width           =   975
      End
      Begin TextBox IntN_value
         Height          =   285
         Left            =   4800
         TabIndex        =   11
         Text            =   "Text1"
         Top             =   720
         Width           =   495
      End
      Begin ComboBox Sensitivity_List
         Height          =   300
         Left            =   2160
         Style           =   2  'Dropdown List
         TabIndex        =   9
         Top             =   720
         Width           =   2175
      End
      Begin CommandButton Open_Button
         Caption         =   "&Open "
         Height          =   375
         Left            =   6360
         TabIndex        =   14
         Top             =   360
         Width           =   975
      End
      Begin TextBox Fetch_size
         Height          =   285
         Left            =   2160
         TabIndex        =   7
         Text            =   "1"
         Top             =   360
         Width           =   375
      End
      Begin Label Label3
         BackColor       =   &H00C0C0C0&
         Caption         =   "Concurrenc&y Control:"
         Height          =   210
         Left            =   120
         TabIndex        =   12
         Top             =   1080
         Width           =   1845
      End
      Begin Label Label8
         BackColor       =   &H00C0C0C0&
         Caption         =   "&N:"
         Height          =   255
         Left            =   4440
         TabIndex        =   10
         Top             =   720
         Width           =   255
      End
      Begin Label Label2
         BackColor       =   &H00C0C0C0&
         Caption         =   "Cursor &Sensitivity:"
         Height          =   225
         Left            =   120
         TabIndex        =   8
         Top             =   720
         Width           =   1605
      End
      Begin Label Label6
         BackColor       =   &H00C0C0C0&
         Caption         =   "(Max = 15)"
         Height          =   255
         Left            =   2640
         TabIndex        =   19
         Top             =   360
         Width           =   975
      End
      Begin Label Label5
         BackColor       =   &H00C0C0C0&
         Caption         =   "Rows per &Fetch:"
         Height          =   240
         Left            =   120
         TabIndex        =   6
         Top             =   360
         Width           =   1815
      End
   End
   Begin Frame Frame1
      BackColor       =   &H00C0C0C0&
      Caption         =   "Object"
      Height          =   855
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   7455
      Begin ComboBox Table_List
         Height          =   300
         Left            =   4800
         TabIndex        =   4
         Text            =   "Table_List"
         Top             =   360
         Width           =   2295
      End
      Begin ComboBox Database_List
         Height          =   300
         Left            =   1320
         TabIndex        =   2
         Text            =   "Database_List"
         Top             =   360
         Width           =   2295
      End
      Begin Label Label4
         BackColor       =   &H00C0C0C0&
         Caption         =   "&Table:"
         Height          =   225
         Left            =   3840
         TabIndex        =   3
         Top             =   360
         Width           =   735
      End
      Begin Label Label1
         BackColor       =   &H00C0C0C0&
         Caption         =   "&Database:"
         Height          =   210
         Left            =   120
         TabIndex        =   1
         Top             =   360
         Width           =   1005
      End
   End
   Begin Label COLUMN_LABEL
      BorderStyle     =   1  'Fixed Single
      Caption         =   "Label8"
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "Courier"
      FontSize        =   9.75
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      Height          =   255
      Left            =   120
      TabIndex        =   21
      Top             =   3000
      Width           =   7215
   End
   Begin Label Label7
      BackColor       =   &H00C0C0C0&
      Caption         =   "&Results:"
      Height          =   255
      Left            =   120
      TabIndex        =   17
      Top             =   2760
      Width           =   855
   End
   Begin Menu Menu_File
      Caption         =   "&Connect"
      Begin Menu Logon_Selection
         Caption         =   "&Logon"
      End
      Begin Menu Log_Off_Selection
         Caption         =   "Log &Off"
      End
      Begin Menu Exit_Selection
         Caption         =   "&Exit"
      End
   End
   Begin Menu Menu_Help
      Caption         =   "&Help"
      Begin Menu About_Selection
         Caption         =   "&About..."
      End
   End
End

Sub About_Selection_Click ()
    About_Form.Show 1
End Sub

Sub BeginTran ()
    Results% = ExecuteSQLCommand("begin tran")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
    Do While SqlNextRow(SqlConn%) <> NOMOREROWS
    Loop
    Loop
    OpenTran% = True
End Sub

Sub CC_List_Click ()
    If Sensitivity_List.ListIndex = -1 Or Table_List.ListIndex = -1 Then
Open_Button.Enabled = False
    Else
Open_Button.Enabled = True
    End If

End Sub

Sub ClearCursorBuffer ()

Do While Cursor_Buffer.ListCount
    Cursor_Buffer.RemoveItem 0
Loop

End Sub

Sub ClearDatabaseList ()

Rem Clear all databases out of list box

    Do While Database_List.ListCount
Database_List.RemoveItem 0
    Loop
    Database_List.Text = ""
End Sub

 Sub ClearOutputBuffer ()

 For i% = 0 To 19
    OutputData(i%) = ""
 Next i%

End Sub

Sub ClearTableList ()

    Rem Clear all tables out of list box
    Do While Table_List.ListCount
Table_List.RemoveItem 0
    Loop
    Table_List.Text = ""

End Sub

Sub Close_Button_Click ()
    If OpenTran% Then
CommitTran
    End If

    SqlCursorClose CursorHandle%

    Open_Button.Enabled = True
    Close_Button.Enabled = False
    Update_Button.Enabled = False
    Fetch_Size.Enabled = True
    Sensitivity_List.Enabled = True
    CC_List.Enabled = True
    IntN_value.Enabled = True

    COLUMN_LABEL.Caption = ""
    ClearCursorBuffer

End Sub

Sub CommitTran ()
    Results% = ExecuteSQLCommand("commit tran")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
    Do While SqlNextRow(SqlConn%) <> NOMOREROWS
    Loop
    Loop
OpenTran% = False
End Sub

Sub Database_List_Click ()

Rem
Rem This routine will use the selected database, get all the user
Rem tables in the database, and change the primary window title
Rem to reflect the database change.
Rem

    DatabaseSelection$ = Database_List.Text
    Results% = SqlUse(SqlConn%, DatabaseSelection$)
    If Results% = SUCCEED Then
PrimaryWindow.MousePointer = 11
RetrieveTableNames
PrimaryWindow.MousePointer = 0
DatabaseName = SqlName$(SqlConn%)
ChangePrimaryWindowCaption
If Table_List.ListIndex = -1 Or Sensitivity_List.ListIndex = -1 Or CC_List.ListIndex = -1 Then
    Open_Button.Enabled = False
Else
    Open_Button.Enabled = True
End If
    End If

End Sub

Sub Exit_Selection_Click ()

    ExitApplication
    End

End Sub

Sub Fetch_First ()

    ClearCursorBuffer
    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHFIRST, 0)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor.", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_Last ()
    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHLAST, 0)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor.", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_Next ()

Rem
Rem This routine will get this next batch of rows
Rem

    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHNEXT, 0)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor. You are more than likely at the end of the result set", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_Previous ()

Rem
Rem This routine will get the previous batch of rows
Rem

    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHPREV, 0)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor.", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_Random (Rownum As Integer)

Rem
Rem This routine will get a row number to start fetching from
Rem

    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHRANDOM, Rownum%)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor.", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_Relative (ScrollValue As Integer)

Rem
Rem This routine will get a batch relative to the selected row
Rem First Calculate where to start the fetch
Rem

    MaxRows% = Vscroll1.Max
    n = (ScrollValue * MaxRows%) / 100
    Rownum% = n - CursorSliderValue%

Rem
Rem Fetch the rows
Rem

    If Not OpenTran% Then
BeginTran
    Else
CommitTran
BeginTran
    End If

    Results% = SqlCursorFetch%(CursorHandle%, FETCHRELATIVE, Rownum%)
    If Results% = SUCCEED And Int(RowStatus&(0)) <> FTCENDOFRESULTS% Then
ProcessCursorRows
Update_Button.Enabled = True
FillCursorBuffer -1        'display the data
    Else
MsgBox "Problem fetching cursor.", 0, "Cursor Example"
    End If
End Sub

Sub Fetch_size_LostFocus ()

Rem
Rem Make sure the fetch size fits in the results list box to avoid scrolling
Rem

    Num% = Val(Fetch_Size.Text) * Val(IntN_value.Text)

    If Val(Fetch_Size.Text) < 1 Or Val(Fetch_Size.Text) > 15 Then
MsgBox "Value must be between 1 and 15", 0, "Cursor Example"
Fetch_Size.SetFocus
    End If

End Sub

Sub FillCursorBuffer (x As Integer)

Rem
Rem If x% >= 0, then we are doing a horizontal scroll
Rem Clear the list box
Rem Fill the list box with the shifted data
Rem

    If x% >= 0 Then

    ClearCursorBuffer
    startshift% = x%

    rowlen& = Len(OutputData(0))
    i% = 0
    While OutputData(i%) <> ""
If i% <= 2 Then
If x% = 0 Then startshift% = 1
Cursor_Buffer.AddItem Mid$(OutputData(i%), startshift%, rowlen&)
Else
If x% = 0 Then startshift% = 2
Cursor_Buffer.AddItem Mid$(OutputData(i%), startshift% - 1, rowlen&)
End If
i% = i% + 1
    Wend

Rem
Rem First fill the buffer with the headings
Rem Then fill in the data
Rem

    Else
i% = 0
While OutputData(i%) <> ""
    Cursor_Buffer.AddItem OutputData(i%)
    i% = i% + 1
Wend

    End If
End Sub

Sub Form_Load ()

Rem
Rem Initialize the application
Rem

    PrimaryWindowTitle = "Cursor Example"
    ChangePrimaryWindowCaption

    InitializeApplication
    MsgBox DBLIB_VERSION$, 0, "Cursor Example"

Rem
Rem Set the initial state of each of the controls in the form
Rem

    Sensitivity_List.AddItem "Static"
    Sensitivity_List.AddItem "Keyset driven"
    Sensitivity_List.AddItem "Dynamic"
    Sensitivity_List.AddItem "Int N"

    CC_List.AddItem "Read only"
    CC_List.AddItem "Locking control"
    CC_List.AddItem "Optimistic concurrency"
    CC_List.AddItem "Optimistic concurrency by values"

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True

    Open_Button.Enabled = False
    Close_Button.Enabled = False
    Update_Button.Enabled = False

    OpenTran% = False
    COLUMN_LABEL.Caption = ""
    ClearCursorBuffer
    ClearDatabaseList
    ClearTableList
    IntN_value.Text = "0"
End Sub

Sub Log_Off_Selection_Click ()

Rem
Rem Set the state of the controls
Rem Logoff
Rem

    ClearDatabaseList
    ClearTableList
    Open_Button.Enabled = False
    Close_Button.Enabled = False
    Update_Button.Enabled = False

    Logoff

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True

End Sub

Sub Logon_Selection_Click ()

Rem
Rem Logon to the server
Rem Get the databases in the server and display them in the list box
Rem Change the primary window caption to reflect current status
Rem

    Login.Show 1
    PrimaryWindow.MousePointer = 11
    If CheckServerConnection() = 1 Then
Results% = GetDatabases(Database_List)
ChangePrimaryWindowCaption
Logon_Selection.Enabled = False
Log_Off_Selection.Enabled = True
    End If
    PrimaryWindow.MousePointer = 0

End Sub

Sub Open_Button_Click ()

Rem
Rem Get the fetch size and keyset size
Rem Redimension the rowstatus array
Rem

    NumRowsInCursor% = Val(Fetch_Size.Text)
    IntN% = Val(IntN_value.Text)
    ReDim RowStatus&(NumRowsInCursor%)

Rem
Rem Set the scroll option and concurrency control values
Rem

    Select Case Sensitivity_List.Text
    Case "Static"
ScrollOpt% = CURFORWARD%
    Case "Keyset driven"
    ScrollOpt% = CURKEYSET%
    Case "Dynamic"
ScrollOpt% = CURDYNAMIC%
    Case "Int N"
If IntN% <= 0 Then
    MsgBox "You must Supply N", 0, "Cursor Example"
    Exit Sub
Else
    ScrollOpt% = IntN%
End If
    End Select

    Select Case CC_List.Text
    Case "Read only"
Concuropt% = CURREADONLY%
    Case "Locking control"
Concuropt% = CURLOCKCC%
    Case "Optimistic concurrency"
Concuropt% = CUROPTCC%
    Case "Optimistic concurrency by values"
Concuropt% = CUROPTCCVAL%
    End Select

Rem
Rem Setup the select statement and open the cursor
Rem

    Statement$ = "select * from " + Table_List.Text

    CursorHandle% = SqlCursorOpen(SqlConn%, Statement$, ScrollOpt%, Concuropt%, NumRowsInCursor%, RowStatus&(0))

    If CursorHandle% <> FAIL Then
Open_Button.Enabled = False
Close_Button.Enabled = True

Rem
Rem Get Column names and lengths
Rem Output the column header
Rem

Results% = SqlCursorInfo%(CursorHandle%, NumCols%, NumRowsInKeyset&)
For ColCount% = 1 To NumCols%
    Results% = SqlCursorColInfo%(CursorHandle%, ColCount%, colname$, Coltype%, ColLen&, UserType%)
    CursorColName$(ColCount%) = colname$
    CursorColLen&(ColCount%) = ColLen&
Next ColCount%
ClearOutputBuffer
ProcessCursorHeader
IntN_value.Enabled = False
Sensitivity_List.Enabled = False
CC_List.Enabled = False

Rem
Rem Set the vertical scroll bar to <page up> and <page down> the size of the fetch
Rem Determine the max value of VScroll1
Rem     If we are dealing with a mixed scroll, then set to number (ScrollOpt% * IntN%) * an arbitrary number
Rem         NOTE: You choose an arbitrary number because you want to be able to go outside of keyset.
Rem     If Dealing with keyset or static scroll and we have enough room to store the whole keyset,
Rem         then set max to full keyset size.
Rem     Otherwise, we're dealing with Dynamic scroll or we can't fit the who keyset.
Rem         Make a guess as to how big the keyset is by multiplying the "asked for"
Rem         fetch size by some arbitrary number (we chose 10).
Rem Fetch the first batch of rows
Rem

Vscroll1.SmallChange = NumRowsInCursor%
Vscroll1.LargeChange = Vscroll1.SmallChange
Vscroll1.Value = 0
Vscroll1.Refresh        'Refresh the vertical scrollbar
CursorSliderValue = 0

If IntN% <> 0 Then
    Vscroll1.Max = (ScrollOpt% * IntN%) * 10
Else
    If NumRowsInKeyset& > 0 Then
Vscroll1.Max = NumRowsInKeyset&
    Else
Vscroll1.Max = NumRowsInCursor% * 10       'Note: the number 10 is an arbitrary number
    End If
End If
Fetch_First
    Else
MsgBox "Cursor failed to open.", 0, "Cursor Example"
    End If

End Sub

Function PrepareString (String_In As String) As String

    String_Out$ = ""

    For i% = 1 To Len(String_In)
    If Mid$(String_In, i%, 1) = Chr$(39) Then
String_Out$ = String_Out$ + Chr$(39) + Chr$(39)
    Else
String_Out$ = String_Out$ + Mid$(String_In, i%, 1)
    End If
    Next

    PrepareString = String_Out$

End Function

Sub ProcessCursorHeader ()

Rem
Rem This routine gets and formats the cursor header
Rem

    TABKEY$ = "    "
    NEWLINE$ = Chr$(13) + Chr$(10)
    colline$ = ""

    For ColCount% = 1 To NumCols%
    tmplen% = CursorColLen&(ColCount%)
    If tmplen% > 256 Then tmplen% = 256
    colname$ = CursorColName$(ColCount%)
    actuallen& = Len(colname$)
    If actuallen& < tmplen% Then
If ColCount% <> NumCols% Then
    colline$ = colline$ + colname$ + Space$((tmplen% - actuallen&) + 1) + TABKEY$
Else
    colline$ = colline$ + colname$ + Space$((tmplen% - actuallen&) + 1)
End If
    Else
If ColCount% <> NumCols% Then
    colline$ = colline$ + colname$ + TABKEY$
Else
    colline$ = colline$ + colname$
End If

    End If
    Next ColCount%

Rem
Rem Set the COLUMN_LABEL label
Rem

    COLUMN_LABEL.Caption = colline$
End Sub

Sub ProcessCursorRows ()

Rem
Rem This routine gets each of the cursor rows and displays them
Rem

    TABKEY$ = Chr$(9)
    colline$ = ""
    ClearCursorBuffer
    ClearOutputBuffer

Rem    HScroll1.Refresh     'Refresh the horizontal scroll bar

Rem
Rem Set the end of results and keyset values
Rem

    ENDRESULTS% = FTCENDOFRESULTS% + 1
    ENDKEYSET% = FTCENDOFRESULTS% + FTCENDOFKEYSET% + 1

Rem
Rem Get the cursor data based on the row and column
Rem

    For RowCount% = 1 To NumRowsInCursor%
For ColCount% = 1 To NumCols%
    ColValue$ = SqlCursorData(CursorHandle%, RowCount%, ColCount%)
    actuallen& = Len(ColValue$)
    tmplen% = CursorColLen(ColCount%)
    If tmplen% > 256 Then tmplen% = 256
    If actuallen& < tmplen% Then
If ColCount% <> NumCols% Then
    DataStr$ = DataStr$ + ColValue$ + Space$((tmplen% - actuallen&) + 1) + TABKEY$
Else
    DataStr$ = DataStr$ + ColValue$ + Space$((tmplen% - actuallen&) + 1)
End If
    Else
If ColCount% <> NumCols% Then
    DataStr$ = DataStr$ + ColValue$ + TABKEY$
Else
    DataStr$ = DataStr$ + ColValue$
End If
    End If
    ColValue$ = ""
Next ColCount%
OutputData(RowCount% - 1) = DataStr$
DataStr$ = ""
testval% = Int(RowStatus&(RowCount% - 1))
If testval% = ENDRESULTS% Or testval% = ENDKEYSET% Or testval% = FTCENDOFRESULTS% Then
    Exit For
End If
    Next RowCount%

End Sub

Sub RetrieveTableNames ()

Rem
Rem Retrieve table names from SQL Server into list box
Rem

    ClearTableList
    Results% = ExecuteSQLCommand("Select name from sysobjects where type = 'U'")
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Table_List.AddItem SqlData(SqlConn%, 1)
Loop
    Loop
    Table_List.Text = Table_List.List(0)
End Sub

Sub Sensitivity_List_Click ()

Rem
Rem Make sure the required data is supplied before enabling the open button
Rem

    If Table_List.ListIndex = -1 Or CC_List.ListIndex = -1 Then
Open_Button.Enabled = False
    Else
Open_Button.Enabled = True
    End If

End Sub

Sub Table_List_Click ()

Rem
Rem Make sure the required data is supplied before enabling the open button
Rem

    If Sensitivity_List.ListIndex = -1 Or CC_List.ListIndex = -1 Then
Open_Button.Enabled = False
    Else
Open_Button.Enabled = True
    End If

End Sub

Sub Update_Button_Click ()

Rem
Rem Get the selected row number
Rem

    Rownum% = Cursor_Buffer.ListIndex + 1

    If Rownum% = 0 Then
Beep
    MsgBox "Select a row to update.", 0, "Cursor Example"
Exit Sub
    End If

Rem
Rem Get column # to update and new value
Rem

    ColNum$ = InputBox$("Number of column to update:", "Update")
    If ColNum$ = "" Then Exit Sub

    Value$ = InputBox$("Update column " + ColNum$ + " with value: ", "Update")
    If Value$ = "" Then Exit Sub

    Table$ = Table_List.Text

Rem
Rem Determine name of column to update
Rem Create the update string
Rem

    Results% = SqlCursorColInfo(CursorHandle%, Val(ColNum$), colname$, Coltype%, ColLen&, UserType%)

    UpdateValue$ = "set " + colname$ + "="
    If Coltype% = SQLCHAR Or Coltype% = SQLVARCHAR Then
       UpdateValue$ = UpdateValue$ + "'" + Value$ + "'"
    Else
UpdateValue$ = UpdateValue$ + Value$
    End If

Rem
Rem Perform update
Rem

    Results% = SqlCursor(CursorHandle%, CRSUPDATE, Rownum%, Table$, UpdateValue$)

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)
End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$
End Sub

Sub VScroll1_Change ()

If Vscroll1.Value = 0 And CursorSliderValue% = 0 Then Exit Sub

Rem
Rem Define <Page Up> and <Page Down> values
Rem Get the current scroll value
Rem

PAGEUP = CursorSliderValue% - Vscroll1.SmallChange
PAGEDOWN = CursorSliderValue% + Vscroll1.SmallChange

x = Vscroll1.Value

Rem
Rem Based on the scroll value, call the appropriate routine
Rem If "Mixed" scroll and scrolling within the keyset, then just do a random
Rem

If x > CursorSliderValue% And x < PAGEDOWN Then
    If x > Vscroll1.SmallChange Then
Fetch_Random (x)
    Else
Fetch_Relative (x)
    End If
Else
    If x < CursorSliderValue% And x > PAGEUP Then
If x > Vscroll1.SmallChange Then
    Fetch_Random (x)
Else
    Fetch_Relative (x)
End If
    Else

Rem
Rem User is either doing a <Page Down>, <Page Up>, or Dynamic scrolling
Rem

Select Case x
    Case Is = PAGEDOWN
Fetch_Next
    Case Is = PAGEUP
Fetch_Previous
    Case Is > PAGEDOWN
If ScrollOpt% = CURKEYSET% Then
    Fetch_Random Int(x)
Else
    Fetch_Relative Int(x)
End If
    Case Is < PAGEUP
If ScrollOpt% = CURKEYSET% Then
    Fetch_Random Int(x)
Else
    Fetch_Relative Int(x)
End If
End Select
    End If
End If

Rem
Rem Save the current scroll value for next time.
Rem

CursorSliderValue = x

End Sub


GENGLOB.BAS

_ _


IMAGE.BAS

Rem Global Definitions

Rem temporary file for bitmap storage

Global Temp_Image_File As String

Rem These handle the SQL Server connection

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global SqlConn%
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global DefServer$
Global DefLogin$
Global Const LoginTimeout% = 30
Global Const QueryTimeout% = 60


IMAGE.FRM

VERSION 2.00
Begin Form PrimaryWindow
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Image"
   ControlBox      =   0   'False
   ForeColor       =   &H00000000&
   Height          =   7035
   Icon            =   IMAGE.FRX:0000
   Left            =   615
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   6345
   ScaleWidth      =   6990
   Top             =   75
   Width           =   7110
   Begin PictureBox Image_Control
      Height          =   3495
      Left            =   120
      ScaleHeight     =   3465
      ScaleWidth      =   6705
      TabIndex        =   12
      Top             =   2760
      Width           =   6735
   End
   Begin VBSQL VBSQL1
      Caption         =   "SQL Err/Msg"
      Height          =   272
      Left            =   4800
      Top             =   2505
      Visible         =   0   'False
      Width           =   2055
   End
   Begin ListBox Titles_List
      Height          =   810
      Left            =   135
      TabIndex        =   8
      Top             =   1440
      Width           =   2055
   End
   Begin DirListBox Dir_Tree_Combo
      Height          =   1275
      Left            =   2520
      TabIndex        =   4
      Top             =   1170
      Width           =   2055
   End
   Begin FileListBox Image_File_List
      Height          =   1590
      Left            =   4800
      Pattern         =   "*.bmp"
      TabIndex        =   5
      Top             =   855
      Width           =   2055
   End
   Begin DriveListBox Drive_Combo
      Height          =   357
      Left            =   2528
      TabIndex        =   3
      Top             =   833
      Width           =   2048
   End
   Begin ListBox Database_List
      Height          =   420
      Left            =   135
      TabIndex        =   10
      Top             =   360
      Width           =   2055
   End
   Begin TextBox Title_Edit
      Height          =   323
      Left            =   3855
      TabIndex        =   1
      Top             =   75
      Width           =   3000
   End
   Begin Label Label5
      BackColor       =   &H00C0C0C0&
      Caption         =   "Image in database:"
      Height          =   225
      Left            =   120
      TabIndex        =   11
      Top             =   2520
      Width           =   2055
   End
   Begin Label Label4
      BackColor       =   &H00C0C0C0&
      Caption         =   "Titles in database:"
      Height          =   225
      Left            =   120
      TabIndex        =   9
      Top             =   1200
      Width           =   2055
   End
   Begin Label Image_File_Label
      BackColor       =   &H00C0C0C0&
      Caption         =   "(none)"
      Height          =   255
      Left            =   3000
      TabIndex        =   7
      Top             =   500
      Width           =   3840
   End
   Begin Label Label3
      BackColor       =   &H00C0C0C0&
      Caption         =   "File:"
      Height          =   255
      Left            =   2520
      TabIndex        =   6
      Top             =   500
      Width           =   375
   End
   Begin Label Label2
      BackColor       =   &H00C0C0C0&
      Caption         =   "Image file title:"
      ForeColor       =   &H00000000&
      Height          =   225
      Left            =   2520
      TabIndex        =   2
      Top             =   120
      Width           =   1335
   End
   Begin Label Label1
      BackColor       =   &H00C0C0C0&
      Caption         =   "Databases:"
      Height          =   210
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   2085
   End
   Begin Menu Menu_File
      Caption         =   "&File"
      Begin Menu Logon_Selection
         Caption         =   "&Logon"
      End
      Begin Menu Log_Off_Selection
         Caption         =   "Log &Off"
      End
      Begin Menu Exit_Selection
         Caption         =   "&Exit"
      End
   End
   Begin Menu Options_Menu
      Caption         =   "&Options"
      Begin Menu View_Selection
         Caption         =   "&View Image"
      End
      Begin Menu Insert_Selection
         Caption         =   "&Insert Image"
      End
      Begin Menu Delete_Selection
         Caption         =   "&Delete Image"
      End
   End
   Begin Menu About_Menu
      Caption         =   "&About"
   End
End

Sub About_Menu_Click ()

    About_Form.Show 1

End Sub

Function CheckForImageTable () As Integer

Rem
Rem Check to see if sample table exits
Rem If it's not there, then see if they want to create it
Rem

    Results% = ExecuteSQLCommand("Select count(*) from sysobjects where name = 'image_table'")
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Table_Found$ = Sqldata(SqlConn%, 1)
Loop
    Loop

    If Val(Table_Found$) = 0 Then
Results% = MsgBox("Text table not found in " + Database$ + " database." + Chr$(13) + Chr$(10) + "Do you wish to create the table?", 52)
If Results% = 7 Then
    CheckForImageTable = FAIL
    Exit Function
Else
    Cmd$ = "create table image_table (title varchar(30) not null, image_col image null)"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Results% = SqlExec(SqlConn%)
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
Loop
    Loop
    CheckForImageTable = SUCCEED
End If
    Else
CheckForImageTable = SUCCEED
    End If

End Function

Sub ClearImageTitles ()

    Rem Clear all text titles out of list box
    Do While Titles_List.ListCount
Titles_List.RemoveItem 0
    Loop

End Sub

Sub Database_List_Click ()

Rem
Rem This procedure gets the chosen database.
Rem Checks to see if the image table is there
Rem If it's not, then clear the images field, and disable other fields
Rem If it is, then get the list of images, display the list and enable other fields.
Rem

    DatabaseSelection$ = Database_List.Text
    Results% = SqlUse(SqlConn%, DatabaseSelection$)
    Results% = CheckForImageTable()
    If Results% = SUCCEED Then
PrimaryWindow.MousePointer = 11
RetrieveImageTitles
View_Selection.Enabled = True
Insert_Selection.Enabled = True
Delete_Selection.Enabled = True
PrimaryWindow.MousePointer = 0
    Else
ClearImageTitles
View_Selection.Enabled = False
Insert_Selection.Enabled = False
Delete_Selection.Enabled = False
    End If

Rem
Rem Change the primary window title to show the database name
Rem Clear the image control
Rem

    DatabaseName$ = SQLName(SqlConn%)
    ChangePrimaryWindowCaption
    Image_Control.Picture = LoadPicture()

End Sub

Sub Delete_Selection_Click ()

    Image_Title$ = Titles_List.Text

    If Image_Title$ = "" Then
Beep
MsgBox "You must first select a title."
    Else
Response% = MsgBox("Delete " + Image_Title$ + "?", 49)
If Response% = 1 Then
    PrimaryWindow.MousePointer = 11
    Results% = ExecuteSQLCommand("Delete from image_table where title = '" + Image_Title$ + "'")
    Results% = SqlResults%(SqlConn%)
    Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Loop
    Image_Control.Picture = LoadPicture()
    RetrieveImageTitles
    PrimaryWindow.MousePointer = 0
Else
    MsgBox "Delete aborted."
End If
    End If

End Sub

Sub Dir_Tree_Combo_Change ()

    Image_File_Label.Caption = "(none)"
    Image_File_List.Path = Dir_Tree_Combo.Path

End Sub

Sub Drive_Combo_Change ()

    Image_File_Label.Caption = "(none)"
    Dir_Tree_Combo.Path = Drive_Combo.Drive

End Sub

Sub Exit_Selection_Click ()

    ExitApplication
    End

End Sub

Sub Form_Load ()

   Temp_Image_File = "c:\image.tmp"

    PrimaryWindowTitle = "Image Example"
    ChangePrimaryWindowCaption

    InitializeApplication
    MsgBox DBLIB_VERSION$

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True
    View_Selection.Enabled = False
    Insert_Selection.Enabled = False
    Delete_Selection.Enabled = False

End Sub

Sub Image_File_List_Click ()

    If Right$(Dir_Tree_Combo.Path, 1) = "\" Then
Image_File_Label.Caption = Dir_Tree_Combo.Path + Image_File_List.FileName
    Else
Image_File_Label.Caption = Dir_Tree_Combo.Path + "\" + Image_File_List.FileName
    End If

End Sub

Sub Insert_Selection_Click ()

Rem
Rem This routine will insert the image from the file into the database
Rem

    Image_File$ = Image_File_Label.Caption
    Title$ = Title_Edit.Text
    Title$ = PrepareString(Title$)

    If Image_File$ = "(none)" Then
Beep
MsgBox "Please select a bitmap to insert."
    ElseIf Title$ = "" Then
Beep
MsgBox "Please enter a title for the bitmap you wish to insert."
    Else
PrimaryWindow.MousePointer = 11
InsertImage Title$, Image_File$
Title_Edit.Text = ""
Image_File_List.ListIndex = -1
Image_File_Label.Caption = ""
RetrieveImageTitles
PrimaryWindow.MousePointer = 0
    End If

End Sub

Sub InsertImage (Title As String, Bitmap_File As String)

Rem
Rem This routine inserts an image into the SQL Server
Rem Insert new row with title and image token data
Rem

    Results% = ExecuteSQLCommand("Insert into image_table values ('" + Title + "',0x80)")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS%
Do While SqlNextRow(SqlConn%) <> NOMOREROWS%
Loop
    Loop

Rem Get identifier for image column in current row

    Results% = ExecuteSQLCommand("select image_col from image_table where title = '" + Title + "'")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS%
Do While SqlNextRow(SqlConn%) <> NOMOREROWS%
    SqlPointer$ = SqlTxPtr(SqlConn%, 1)
    SqlTimestamp$ = SqlTxTimeStamp(SqlConn%, 1)
Loop
    Loop

Rem Open bitmap file to load into SQL Server table

    Open Bitmap_File For Binary As #1
    FileLength& = LOF(1)

Rem Begin inserting bitmap into image column in DatePartLimit& size chunks

    Table$ = "image_table.image_col"
    DataPartLimit& = 4096
    DataPartSize& = 0
    If SqlWriteText(SqlConn%, Table$, SqlPointer$, SQLTXPLEN%, SqlTimestamp$, 1, FileLength&, "") <> FAIL% Then
If SqlOk(SqlConn%) <> FAIL% Then
    Results% = SqlResults(SqlConn%)
    Done% = False
    Do While Not Done%
DataPart$ = Input$(DataPartLimit&, 1)
DataPartSize& = DataPartSize& + Len(DataPart$)
Results% = SqlMoreText%(SqlConn%, Len(DataPart$), DataPart$)
If DataPartSize& = FileLength& Then
    Done% = True
End If
    Loop
    If SqlOk(SqlConn%) <> FAIL% Then
If SqlResults(SqlConn%) <> FAIL% Then
    MsgBox "Image inserted."
End If
    End If
End If
    End If

Close 1

End Sub

Sub LoadImage (Title As String)

Rem
Rem This routine reads an image from the SQL Server
Rem Get length of bitmap image in image column
Rem

    Results% = ExecuteSQLCommand("select datalength(image_col) from image_table where title = '" + Title + "'")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
Do While SqlNextRow(SqlConn%) <> NOMOREROWS
    ImageLen& = Val(Sqldata(SqlConn%, 1))
Loop
    Loop

    Offset& = 0

Rem Set LoadSizeLimit to 8K

    LoadSizeLimit& = 4096
    If LoadSizeLimit& > ImageLen& Then
LoadSizeLimit& = ImageLen&
    End If

    LoadSize& = LoadSizeLimit&

Rem Set size of image returned to LoadSizeLimit&

    Results% = ExecuteSQLCommand("set textsize " + Str$(LoadSizeLimit&))
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
Do While SqlNextRow(SqlConn%) <> NOMOREROWS
Loop
    Loop

Rem Begin reading image column in LoadSizeLimit& size chunks

    Cmd$ = "Declare @val varbinary(30)"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Cmd$ = "Select @val = textptr(image_col) from image_table where title = '" + Title + "'"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Table$ = "image_table.image_col"
    Done% = False
    Do While Not Done%
Cmd$ = "READTEXT " + Table$ + " @val " + Str$(Offset&) + " " + Str$(LoadSize&)
Results% = SqlCmd(SqlConn%, Cmd$)
If Offset& + LoadSize& = ImageLen& Then
    Done% = True
Else
    Offset& = Offset& + LoadSizeLimit&
    If Offset& + LoadSizeLimit& > ImageLen& Then
LoadSize& = ImageLen& - Offset&
    End If
End If
    Loop

Rem Retrieve image data in result rows and write to temporary bitmap file

    If SqlExec(SqlConn%) <> FAIL% Then
Open Temp_Image_File For Binary As #1
filepos& = 1
Do While SqlResults(SqlConn%) <> NOMORERESULTS
    Do While SqlNextRow(SqlConn%) <> NOMOREROWS
in$ = Sqldata(SqlConn%, 1)
Put 1, filepos&, in$
filepos& = filepos& + Len(in$)
    Loop
Loop
Close 1
    End If

End Sub

Sub Log_Off_Selection_Click ()

    Logoff

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True
    View_Selection.Enabled = False
    Insert_Selection.Enabled = False
    Delete_Selection.Enabled = False

End Sub

Sub Logon_Selection_Click ()

    Login.Show 1
    PrimaryWindow.MousePointer = 11
    If CheckServerConnection() = 1 Then
Results% = GetDatabases(Database_List)
ChangePrimaryWindowCaption
Logon_Selection.Enabled = False
Log_Off_Selection.Enabled = True
    End If
    PrimaryWindow.MousePointer = 0

End Sub

Function PrepareString (String_In As String) As String

    String_Out$ = ""

    For I% = 1 To Len(String_In)
If Mid$(String_In, I%, 1) = Chr$(39) Then
    String_Out$ = String_Out$ + Chr$(39) + Chr$(39)
Else
    String_Out$ = String_Out$ + Mid$(String_In, I%, 1)
End If
    Next

    PrepareString = String_Out$

End Function

Sub RetrieveImageTitles ()

    ClearImageTitles
    Rem Retrieve text titles from SQL Server into list box
    Results% = ExecuteSQLCommand("Select title from image_table")
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Titles_List.AddItem Sqldata(SqlConn%, 1)
Loop
    Loop

End Sub

Sub Title_Edit_KeyPress (KeyAscii As Integer)

    If Len(Title_Edit.Text) = 30 Then
If KeyAscii <> 8 Then
    KeyAscii = 0
End If
    End If

End Sub

Sub Titles_List_Click ()

Rem
Rem Clear the picture
Rem

    Image_Control.Picture = LoadPicture()

End Sub

Sub Titles_List_DblClick ()

Rem
Rem Get the image image selected from the server
Rem Display the image
Rem

    Image_Title$ = Titles_List.Text
    PrimaryWindow.MousePointer = 11
    LoadImage Image_Title$
    Image_Control.Picture = LoadPicture(Temp_Image_File)
    PrimaryWindow.MousePointer = 0

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)

End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)

    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$

End Sub

Sub View_Selection_Click ()

    Image_Title$ = Titles_List.Text
    If Image_Title$ = "" Then
Beep
MsgBox "You must first select a title."
    Else
PrimaryWindow.MousePointer = 11
LoadImage Image_Title$
Image_Control.Picture = LoadPicture(Temp_Image_File)
PrimaryWindow.MousePointer = 0
    End If

End Sub


INIEXITW.BAS

Sub ExitApplication ()

Rem
Rem Exit SQL, then exit the application.
Rem

   SqlExit
   SqlWinExit
   End

End Sub

Sub InitializeApplication ()

Rem
Rem Initialize the connection to SQL Server
Rem
    SqlConn = 0
    DBLIB_VERSION$ = SqlInit$()
End Sub


LOGIN.FRM

VERSION 2.00
Begin Form login
   BackColor       =   &H00FFFFFF&
   BorderStyle     =   3  'Fixed Double
   Caption         =   "SQL Server Login"
   ForeColor       =   &H00000000&
   Height          =   2640
   Left            =   975
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2235
   ScaleWidth      =   6720
   Top             =   2055
   Width           =   6840
   Begin TextBox password_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      TabIndex        =   5
      Text            =   "Text1"
      Top             =   1440
      Width           =   3480
   End
   Begin CommandButton CANCEL_BUTTON
      BackColor       =   &H00C0C0C0&
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   480
      Left            =   5160
      TabIndex        =   7
      Top             =   960
      Width           =   1320
   End
   Begin TextBox login_id_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      TabIndex        =   3
      Text            =   "Text1"
      Top             =   840
      Width           =   3480
   End
   Begin CommandButton OK_BUTTON
      BackColor       =   &H00C0C0C0&
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   480
      Left            =   5160
      TabIndex        =   6
      Top             =   240
      Width           =   1320
   End
   Begin TextBox Server_name_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      TabIndex        =   1
      Text            =   "Text1"
      Top             =   240
      Width           =   3480
   End
   Begin Label Label3
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Password:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   4
      Top             =   1560
      Width           =   960
   End
   Begin Label Label2
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Login ID:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   2
      Top             =   960
      Width           =   960
   End
   Begin Label Label1
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Server:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   0
      Top             =   360
      Width           =   960
   End
End
'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub CANCEL_BUTTON_Click ()
 Unload Login
End Sub

Sub Form_Load ()
Server_name_field.Text = DefServer$
Login_id_field.Text = DefLogin$
Password_field.Text = ""
password$ = ""

End Sub

Sub OK_BUTTON_Click ()

Rem
Rem Get the server name, login Id, & password from the form
Rem

   Servername$ = Server_name_field.Text
   LoginID$ = Login_id_field.Text

Rem Note: Allow a null server name because you may be running on the
Rem same machine as SQL Server.  Allow a null login id because
Rem SQL Server may be running integrated security.

Rem
Rem Connect to the server
Rem

If LoginToServer() = SUCCEED Then
    Unload Login
Else
    Password_field.Text = ""
    password$ = ""
End If

End Sub

Sub password_field_KeyPress (keyascii As Integer)

Rem
Rem This will keep the key from being seen
Rem

'   if key pressed is a letter or a number, add it to password and show *

If (keyascii >= 48 And keyascii <= 57) Or (keyascii >= 65 And keyascii <= 90) Or (keyascii >= 97 And keyascii <= 122) Then
password$ = password$ + Chr$(keyascii)
keyascii = Asc("*")

ElseIf (keyascii = 8) Then   'if backspace
If Len(password$) > 0 Then
password$ = Left$(password$, Len(password$) - 1)
End If
Else                        'if anything else, throw out
keyascii = 0
Beep
End If

End Sub


MDIMA.FRM

VERSION 2.00
Begin MDIForm MDIMa
   Caption         =   "Demo ADBPubs"
   Height          =   4755
   Icon            =   MDIMA.FRX:0000
   Left            =   75
   LinkTopic       =   "MDIForm1"
   Top             =   555
   Width           =   7740
   WindowState     =   2  'Maximized
   Begin SSPanel panStatusbar
      Align           =   2  'Align Bottom
      Alignment       =   2  'Left Justify - BOTTOM
      BackColor       =   &H00C0C0C0&
      BevelInner      =   2  'Raised
      BevelOuter      =   0  'None
      BorderWidth     =   0
      FontBold        =   0   'False
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   8.25
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   420
      Left            =   0
      Outline         =   -1  'True
      TabIndex        =   8
      Top             =   3645
      Width           =   7620
      Begin SSPanel panInfo
         Alignment       =   2  'Left Justify - BOTTOM
         AutoSize        =   3  'AutoSize Child To Panel
         BackColor       =   &H00C0C0C0&
         BevelInner      =   1  'Inset
         BevelOuter      =   0  'None
         BorderWidth     =   0
         Caption         =   " Server: [Not logged in]"
         FontBold        =   0   'False
         FontItalic      =   0   'False
         FontName        =   "MS Sans Serif"
         FontSize        =   8.25
         FontStrikethru  =   0   'False
         FontUnderline   =   0   'False
         ForeColor       =   &H00000000&
         Height          =   300
         Left            =   120
         TabIndex        =   11
         Top             =   60
         Width           =   12930
      End
      Begin VBSQL VBSQL1
         Caption         =   "VBSQL1"
         Height          =   210
         Left            =   12555
         Top             =   105
         Visible         =   0   'False
         Width           =   525
      End
   End
   Begin SSPanel panToolbar
      Align           =   1  'Align Top
      AutoSize        =   3  'AutoSize Child To Panel
      BackColor       =   &H00C0C0C0&
      BorderWidth     =   4
      ForeColor       =   &H00000000&
      Height          =   435
      Left            =   0
      Outline         =   -1  'True
      TabIndex        =   0
      Top             =   0
      Width           =   7620
      Begin CommandButton cmdUnloadActForms
         Caption         =   "Unload Forms"
         Height          =   270
         Left            =   9570
         TabIndex        =   12
         Top             =   75
         Visible         =   0   'False
         Width           =   1455
      End
      Begin SSCommand cmdLogin
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Left            =   8610
         Picture         =   MDIMA.FRX:0302
         TabIndex        =   10
         TabStop         =   0   'False
         Top             =   45
         Width           =   345
      End
      Begin SSCommand cmdLogout
         BevelWidth      =   1
         Enabled         =   0   'False
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   345
         Left            =   9000
         Picture         =   MDIMA.FRX:0604
         TabIndex        =   9
         TabStop         =   0   'False
         Top             =   45
         Width           =   345
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   1
         Left            =   75
         Picture         =   MDIMA.FRX:0906
         TabIndex        =   7
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   2
         Left            =   465
         Picture         =   MDIMA.FRX:0A70
         TabIndex        =   6
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   3
         Left            =   855
         Picture         =   MDIMA.FRX:0BDA
         TabIndex        =   5
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   4
         Left            =   1380
         Picture         =   MDIMA.FRX:0D44
         TabIndex        =   4
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   5
         Left            =   1905
         Picture         =   MDIMA.FRX:0EAE
         TabIndex        =   3
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   6
         Left            =   2295
         Picture         =   MDIMA.FRX:1048
         TabIndex        =   2
         Top             =   45
         Width           =   340
      End
      Begin SSCommand MainButton
         BevelWidth      =   1
         Font3D          =   1  'Raised w/light shading
         ForeColor       =   &H00000000&
         Height          =   340
         Index           =   7
         Left            =   2820
         Picture         =   MDIMA.FRX:11B2
         TabIndex        =   1
         Top             =   45
         Width           =   340
      End
   End
   Begin Menu MenFile
      Caption         =   "&File"
      Begin Menu altLogin
         Caption         =   "&Login"
         Shortcut        =   ^L
      End
      Begin Menu altLogout
         Caption         =   "Log&out"
         Enabled         =   0   'False
      End
      Begin Menu altDummy
         Caption         =   "-"
      End
      Begin Menu AltExit
         Caption         =   "&Exit"
      End
   End
   Begin Menu MenForms
      Caption         =   "F&orms"
      WindowList      =   -1  'True
      Begin Menu altAuthors
         Caption         =   "&Authors"
         Enabled         =   0   'False
         Shortcut        =   ^A
      End
   End
   Begin Menu menHelp
      Caption         =   "&Help"
      Begin Menu altContents
         Caption         =   "&Contents"
      End
      Begin Menu altDummy2
         Caption         =   "-"
      End
      Begin Menu altAbout
         Caption         =   "&About..."
      End
   End
End

Sub altAbout_Click ()
'
' Show the About box
'

    fAbout.Show MODAL

End Sub

Sub altAuthors_click ()
'
' Open the Authors form
' The menu in the Authors form allows opening
' of several simultaneous instances of the Authors form
'

    screen.MousePointer = 11
    fAuthors.Show
    screen.MousePointer = 0

End Sub

Sub altContents_Click ()
Dim X%, lHelpPath As String
'
' Activate the help system
'

    lHelpPath = App.Path + "\pubs1.hlp"
    X% = WinHelp(MDIMA.hWnd, lHelpPath, HELP_INDEX, 0)

End Sub

Sub altExit_Click ()
'
' Exit the application
'
    Unload Me       ' Unload the MDI form and so exit the app.
    ' Better then end, since unload will call
    ' the MDIForm_Unload procedure

End Sub

Sub altLogin_Click ()
'
' Call the log in procedure
'

    ' Auto press the cmdLogin push button if it is enabled:
    If cmdLogin.Enabled Then
        cmdLogin.Value = True
    Else
        Beep
    End If

End Sub

Sub altLogout_Click ()
'
' Call the log out procedure
'

    ' Auto press the cmdLogout push button if it is enabled:
    If cmdLogout.Enabled Then
        cmdLogout.Value = True
    Else
        Beep
    End If

End Sub

Sub cmdLogin_Click ()
'
' Show the login form
'

    fLogin.Show

End Sub

Sub cmdLogout_Click ()
'
' Log out from SQL Server and do neccessary maintaneance
'

    ' Set caption of panel to show "not logged in":
    panInfo.Caption = " Server: [Not logged in]"

    SqlExit             ' close all connections to SQL Server

    ' Press invisible push button to unload all active forms:
    cmdUnloadActForms.Value = True

    ' Disable objects inappropriate when not logged in:
    altAuthors.Enabled = False
    altLogout.Enabled = False
    cmdLogout.Enabled = False

End Sub

Sub cmdUnloadActForms_Click ()
'
' Unload all forms.
' Normally called when logging out.
' Thus no qualified info will be shown on the screen
' after the user with permissions has logged out
'

On Error GoTo cmdActFormError

    ' In a loop: unload all forms:
    Do
        ' Unload the active form; make another active:
        Unload ActiveForm
    Loop

cmdActFormExit:
    Exit Sub

cmdActFormError:
    ' Presume no more active forms; go to exit:
    Resume cmdActFormExit:

End Sub

Sub MainButton_Click (index As Integer)
'
' The main buttons are organized as a button array,
' where each button has a corresponding button in
' each (or most) forms. When the users click on a
' button in the MDI form tool bar the corresponding
' button in the active form will be automatically
' clicked if it is enabled. Each form will have its
' own specialized procedure connected to each separate
' button. If you will, study the MainButton procedure
' in the authors form.
'
' This technique could be claimed to offer some sort
' of polymorphism to the app.
'
On Error GoTo MainButtonError

    If ActiveForm.MainButton(index).Enabled Then
        ActiveForm.MainButton(index).Value = True
    Else
        Beep
    End If

MainButtonExit:
    Exit Sub

MainButtonError:

    Beep
    Resume MainButtonExit

End Sub

Sub MDIForm_Load ()
'
' Set up the MDI form for processing.
' Call the log in procedure
'

    ' Set global variable AppName$ for log in purposes
    AppName$ = "Demo Authors 1"              'Global variable

    ' Initialize the global variable NEWLINE$
    NEWLINE$ = Chr$(13) + Chr$(10)

    ' When global variable ShowSQL is set to false
    ' SQL statements will not be shown. Set to true
    ' by means of the Option menu.
    ShowSQL = False

    ' Setting the width of the Info Panel
    SetPanelWidth

    'Initialize VBSQL before log in:
    msg$ = SQLInit$()
    If msg$ = "" Then
        Beep
        MsgBox "Couldn't initialize VBSQL. Sorry. Must terminate.", 16
        End
    End If

    fLogin.Show         ' Open login form

End Sub

Sub MDIForm_Resize ()

    SetPanelWidth

End Sub

Sub MDIForm_Unload (Cancel As Integer)
'
' Clean up any connection with SQL Server and close
' the help system, then exit the application (End).
'

Dim X%

    SqlExit
    SqlWinExit
    X% = WinHelp(MDIMA.hWnd, "pubs1.hlp", HELP_QUIT, 0)
    End

End Sub

Sub SetPanelWidth ()
Dim lWidth As Integer

    lWidth = MDIMA.Width - 350
    If lWidth >= 5500 Then panInfo.Width = MDIMA.Width - 350

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
'
' Minimal error management.
' Show the severity of the error, the error number
' and the error message in a message box.

Dim lMsg$

    lMsg$ = "Severity: " & Severity & Chr$(13) + Chr$(13)
    lMsg$ = lMsg$ + Str$(ErrorNum) + " " + ErrorStr + Chr$(13) + Chr$(13)
    MsgBox lMsg$, 16, "SQL-Error"

End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
'
' Minimal SQL Server message management.
' Filter out very trivial messages (5701 and 5703)
' Show the others with message number and message
' string in a message box.
'
    If Message <> 5701 And Message <> 5703 Then
        MsgBox "MSG: " + Str$(Message) + ": " + MsgStr, 0, "SQL Message"
    End If

End Sub


RESULTSW.BAS

Sub Process_altrows (Result As Integer, OutputData() As String, outputrowcnt As Integer, colpositions() As Integer)

Rem Alternate rows (from COMPUTE clause) only available in VBWin
Rem Get the number of columns in the compute row.
Rem Determine what type of aggregate each column uses
Rem Line up the column in the correct position
Rem Output the row.

OutputData(outputrowcnt%) = " "
outputrowcnt% = outputrowcnt% + 1
AltStr1$ = Space$(256)
AltStr2$ = Space$(256)
AltStr3$ = Space$(256)

numalts% = SqlNumAlts%(SqlConn, Result%)
For x% = 1 To numalts%
    altcol% = SqlAltColId%(SqlConn, Result%, x%)
    alttype$ = SQLPRTYPE(SqlAltOp(SqlConn, Result%, x%))
    altvalue$ = SqlAData$(SqlConn, Result%, x%)

    Mid$(AltStr1$, colpositions(altcol%), Len(alttype$)) = alttype$
    Mid$(AltStr2$, colpositions(altcol%), Len(alttype$)) = String$(Len(alttype$), "-")
    Mid$(AltStr3$, colpositions(altcol%), Len(altvalue$)) = altvalue$

Next x%

OutputData(outputrowcnt%) = AltStr1$
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = AltStr2$
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = AltStr3$
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = " "
outputrowcnt% = outputrowcnt% + 1

End Sub

Sub Process_rpc_returns (OutputData() As String, outputrowcnt As Integer)

Rem See if there are any return parameters from a stored procedure (available in VBWin only).
Rem Then See if there is a return status

    numrets% = SQLNumRets(SqlConn)
    If numrets% <> 0 Then
OutputData(outputrowcnt%) = " "
outputrowcnt% = outputrowcnt% + 1
For i% = 1 To numrets%
    DataStr$ = "NAME: " + SQLRetName(SqlConn, i%)
    DataStr$ = DataStr$ + "  TYPE: " + SQLPRTYPE(SQLRetType(SqlConn, i%))
    DataStr$ = DataStr$ + "  LENGTH: " + Str$(SQLRetLen(SqlConn, i%))
    DataStr$ = DataStr$ + "  VALUE: " + SQLRetData(SqlConn, i%)
    OutputData(outputrowcnt%) = DataStr$
    outputrowcnt% = outputrowcnt% + 1
Next i%
    End If

    If (SQLHasRetStat(SqlConn) = SUCCEED%) Then
OutputData(outputrowcnt%) = " "
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = "RETURN STATUS: " + Str$(SQLRetStatus(SqlConn))
outputrowcnt% = outputrowcnt% + 1
    End If

End Sub


RPC.BAS

Rem These handle the SQL Server connection for this application

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global SqlConn%
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global Const LoginTimeout% = 10
Global Const QueryTimeout% = 60


RPC.FRM

VERSION 2.00
Begin Form PrimaryWindow
   Caption         =   "Remote Stored Procedure"
   Height          =   6765
   Icon            =   0
   Left            =   150
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   ScaleHeight     =   6075
   ScaleWidth      =   9060
   Top             =   390
   Width           =   9180
   Begin VBSQL VBSQL1
      Caption         =   "VBSQL1"
      Height          =   255
      Left            =   5280
      Top             =   120
      Visible         =   0   'False
      Width           =   855
   End
   Begin TextBox RESULT_FIELD
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "Courier"
      FontSize        =   9.75
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      Height          =   2430
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   3
      Text            =   "Text1"
      Top             =   3600
      Width           =   8460
   End
   Begin Frame Frame2
      Caption         =   "Procedure Sampler"
      Height          =   1575
      Left            =   120
      TabIndex        =   11
      Top             =   1680
      Width           =   8895
      Begin TextBox PARAMVALUE_FIELD
         Height          =   285
         Left            =   1800
         TabIndex        =   15
         Text            =   "Text1"
         Top             =   1080
         Width           =   6975
      End
      Begin TextBox PARAMNAME_FIELD
         Height          =   285
         Left            =   1800
         TabIndex        =   16
         Text            =   "Text2"
         Top             =   720
         Width           =   6975
      End
      Begin ComboBox PROCNAME_COMBO
         Height          =   300
         Left            =   1800
         TabIndex        =   17
         Top             =   360
         Width           =   2895
      End
      Begin Label Label7
         Caption         =   "Parameter &Values:"
         Height          =   255
         Left            =   120
         TabIndex        =   14
         Top             =   1080
         Width           =   1815
      End
      Begin Label Label6
         Caption         =   "Para&meter Names:"
         Height          =   255
         Left            =   120
         TabIndex        =   13
         Top             =   720
         Width           =   1815
      End
      Begin Label Label3
         Caption         =   "&Procedure Name:"
         Height          =   255
         Left            =   120
         TabIndex        =   12
         Top             =   360
         Width           =   1575
      End
   End
   Begin CommandButton SEND_QUERY_BUTTON
      Caption         =   "E&xecute Proc"
      Enabled         =   0   'False
      Height          =   375
      Left            =   6360
      TabIndex        =   1
      Top             =   720
      Width           =   1575
   End
   Begin CommandButton MAKE_PROC_BUTTON
      Caption         =   "&Make Test Proc"
      Enabled         =   0   'False
      Height          =   375
      Left            =   6360
      TabIndex        =   0
      Top             =   240
      Width           =   1575
   End
   Begin Frame Frame1
      Caption         =   "Procedure qualifiers (optional)"
      Height          =   1455
      Left            =   120
      TabIndex        =   4
      Top             =   120
      Width           =   5055
      Begin TextBox OWNER_FIELD
         Height          =   285
         Left            =   2040
         TabIndex        =   10
         Text            =   "Text3"
         Top             =   1080
         Width           =   2535
      End
      Begin TextBox DBNAME_FIELD
         Height          =   285
         Left            =   2040
         TabIndex        =   8
         Text            =   "Text2"
         Top             =   720
         Width           =   2535
      End
      Begin ComboBox SERVER_COMBO
         Height          =   300
         Left            =   2040
         TabIndex        =   6
         Top             =   360
         Width           =   2535
      End
      Begin Label Label5
         Caption         =   "&Owner:"
         Height          =   255
         Left            =   120
         TabIndex        =   9
         Top             =   1080
         Width           =   735
      End
      Begin Label Label4
         Caption         =   "&Database Name:"
         Height          =   255
         Left            =   120
         TabIndex        =   7
         Top             =   720
         Width           =   1455
      End
      Begin Label Label1
         Caption         =   "Remote &Server:"
         Height          =   255
         Left            =   120
         TabIndex        =   5
         Top             =   360
         Width           =   1455
      End
   End
   Begin Label Label2
      Caption         =   "&Results:"
      Height          =   255
      Left            =   120
      TabIndex        =   2
      Top             =   3360
      Width           =   735
   End
   Begin Menu CONNECT_MENU
      Caption         =   "&Connection"
      Begin Menu LOGIN_MENU
         Caption         =   "&Login"
      End
      Begin Menu DATABASE_MENU
         Caption         =   "Change &Database"
      End
      Begin Menu EXIT_MENU
         Caption         =   "E&xit"
      End
   End
End

Sub ClearFields ()
    RESULT_FIELD.Text = ""
    DBNAME_FIELD.Text = ""
    OWNER_FIELD.Text = ""
    PARAMNAME_FIELD.Text = ""
    PARAMVALUE_FIELD.Text = ""
End Sub

Sub DATABASE_MENU_Click ()
    If SqlConn% = 0 Then
        MsgBox "Must login first"
        Exit Sub
    End If
    CHNGDB.Show 1

End Sub

Sub EXIT_MENU_Click ()

    ExitApplication

End Sub

Sub Form_Load ()

Rem
Rem Initialize the connection to SQL Server
Rem
    InitializeApplication
    MsgBox DBLIB_VERSION$
    PrimaryWindowTitle = "VBSQL RPC demonstration"

Rem
Rem Call routine to clear the fields
Rem Fill in the procedure names combobox
Rem

    ClearFields
    PROCNAME_COMBO.AddItem "sp_help"
    PROCNAME_COMBO.AddItem "sp_who"
    PROCNAME_COMBO.AddItem "echo_test"
End Sub

Function GetServers (Server_Control As Control) As Integer

Rem
Rem This routine gets the name of all the remote servers
Rem Fill each element in the combobox or list box which is passed into this procedure
Rem execute the command.  Get each server name and fill the combobox.
Rem

    If ExecuteSQLCommand("Select srvname from master..sysservers where srvid != 0") = FAIL% Then
        GetServers = FAIL
        Exit Function
    Else
        If SqlResults(SqlConn%) = FAIL% Then Exit Function
        While SqlNextRow(SqlConn%) <> NOMOREROWS%
            Server_Control.AddItem SqlData(SqlConn%, 1)
        Wend
    End If

    GetServers = SUCCEED
End Function

Sub LOGIN_MENU_Click ()

    Login.Show 1

    If SqlConn% <> 0 Then
        SEND_QUERY_BUTTON.Enabled = True
        MAKE_PROC_BUTTON.Enabled = True
        Results% = GetServers(SERVER_COMBO)
    End If
End Sub

Sub MAKE_PROC_BUTTON_Click ()
Static OutputData(0) As String

cmd$ = "CREATE PROCEDURE echo_test(@inparm1 varchar(20), @inparm2 int, "
cmd$ = cmd$ + "@outparm varchar(20) = NULL OUTPUT) AS "
cmd$ = cmd$ + "select @outparm = @inparm1 "
cmd$ = cmd$ + "return @inparm2"

Ret% = ExecuteSQLCommand(cmd$)
If Ret% = SUCCEED% Then
    MsgBox "Procedure echo_test created successfully"
    ' get rid of an result rows
    numrows& = Process_SQL_query("", OutputData())
End If

End Sub

Sub Parse_params (ParamIn As String, Delimiter As String, ParamOut() As String) '

Rem
Rem This routine takes the comma delimited Parameter names/values
Rem

Start% = 1
For i% = 0 To UBound(ParamOut)
    If done% = False Then
        'look for delimiter
        endpos% = InStr(Start%, ParamIn$, Delimiter$)
        If endpos% = 0 Then
            'we're at the last parameter
            ParamOut(i%) = Mid$(ParamIn$, Start%, 255)
            done% = True
        Else
            ParamOut(i%) = Mid$(ParamIn$, Start%, (endpos% - Start%))
            Start% = endpos% + 1
        End If
    Else
        'clear out rest of array
        ParamOut(i%) = ""
    End If
Next i%

End Sub

Sub PROCNAME_COMBO_Click ()

Select Case PROCNAME_COMBO.Text
    Case Is = "sp_help"
        PARAMNAME_FIELD.Text = "@objname VARCHAR(30)"
        PARAMVALUE_FIELD.Text = "sysobjects"
    Case Is = "echo_test"
        PARAMNAME_FIELD.Text = "@inparm1 VARCHAR(20),@inparm2 INTEGER,@outparm VARCHAR(20) OUT"
        PARAMVALUE_FIELD.Text = "hello,7,notused"
    Case Else
        PARAMNAME_FIELD.Text = ""
        PARAMVALUE_FIELD.Text = ""
End Select

End Sub

Sub SEND_QUERY_BUTTON_Click ()

Static OutputData(500) As String
CRLF$ = Chr$(13) + Chr$(10)

Rem
Rem Assemble the procedure name
Rem Get the parameter names
Rem Get the parameter values
Rem Initialize the stored proc.
Rem Fill the parameters
Rem

Static Param_Values(10) As String
Static Param_Declares(10) As String
Static Param_Types(2) As String
Static Param_Datatype(1) As String

'  Note:  If this is a remote server-to-server procedure call (SERVER_NAME is
'       not blank), and your remote password is different from your local
'       password, you will need to set the remote password field with SqlRPwSet%
'       prior to logging on.

procname$ = SERVER_COMBO.Text + "." + DBNAME_FIELD.Text + "." + OWNER_FIELD.Text + "." + PROCNAME_COMBO.Text
Results% = SqlRpcInit(SqlConn%, procname$, 0)

If PARAMVALUE_FIELD.Text <> "" Then
    In$ = PARAMVALUE_FIELD.Text
    Parse_params In$, ",", Param_Values()
    In$ = PARAMNAME_FIELD.Text
    Parse_params In$, ",", Param_Declares()
    For i% = 0 To 10
        'check to see if we are at the end of the parameters provided
        If Param_Values(i%) = "" Then Exit For

        'for each parameter, find out its type
        'first split type declaration into name, type(len), output
        In$ = Param_Declares(i%)
        Parse_params In$, " ", Param_Types()

        'param name is first part
        paramname$ = Param_Types(0)

        'If this is an output var, set the flag
        If Left$(Param_Types(2), 3) = "OUT" Then
            status% = SQLRPCRETURN%
        Else
            status% = 0
        End If

        'now take type(len) and split into type, len)
        In$ = Param_Types(1)
        Parse_params In$, "(", Param_Datatype()

        Select Case Param_Datatype(0)
            Case "VARCHAR"
                typecode% = SQLVARCHAR%
                If status% = SQLRPCRETURN% Then
                    maxlen& = Val(Left$(Param_Datatype(1), Len(Param_Datatype(1)) - 1))
                Else
                    maxlen& = -1
                End If
                datalen& = Len(Param_Values(i%))

            Case "INTEGER"
                typecode% = SQLINT4%
                maxlen& = -1
                datalen& = -1

            Case Else
                MsgBox "Sample does not handle " + Param_Datatype(0) + " paramters"
                Exit Sub
        End Select

        Results% = SqlRpcParam(SqlConn%, paramname$, status%, typecode%, maxlen&, datalen&, Param_Values(i%))
    Next i%
End If

Results% = SqlRpcSend(SqlConn%)

Rem
Rem Clear the result array, and result controls first
Rem

i% = 0
For i% = 0 To 499
    OutputData(i%) = ""
Next

Rem
Rem Fill the result array with the data
Rem Fill the results field
Rem

RESULT_FIELD.Text = ""
numrows& = Process_SQL_query(cmd$, OutputData())

For i% = 0 To numrows& - 1
    DataLine$ = DataLine$ + OutputData(i%) + CRLF$
Next i%

RESULT_FIELD.Text = DataLine$

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)

End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$

End Sub


RPCLOGIN.FRM

VERSION 2.00
Begin Form login
   BackColor       =   &H00FFFFFF&
   BorderStyle     =   3  'Fixed Double
   Caption         =   "SQL Server Login"
   ForeColor       =   &H00000000&
   Height          =   2640
   Left            =   975
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2235
   ScaleWidth      =   6720
   Top             =   2055
   Width           =   6840
   Begin CommandButton LIST_BUTTON
      Caption         =   "List Servers..."
      Height          =   495
      Left            =   5160
      TabIndex        =   8
      Top             =   840
      Width           =   1335
   End
   Begin TextBox password_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      PasswordChar    =   "*"
      TabIndex        =   5
      Text            =   "Text1"
      Top             =   1440
      Width           =   3480
   End
   Begin CommandButton CANCEL_BUTTON
      BackColor       =   &H00C0C0C0&
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   480
      Left            =   5160
      TabIndex        =   7
      Top             =   1440
      Width           =   1320
   End
   Begin TextBox login_id_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      TabIndex        =   3
      Text            =   "Text1"
      Top             =   840
      Width           =   3480
   End
   Begin CommandButton OK_BUTTON
      BackColor       =   &H00C0C0C0&
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   480
      Left            =   5160
      TabIndex        =   6
      Top             =   240
      Width           =   1320
   End
   Begin TextBox Server_name_field
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   1320
      TabIndex        =   1
      Text            =   "Text1"
      Top             =   240
      Width           =   3480
   End
   Begin Label Label3
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Password:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   4
      Top             =   1560
      Width           =   960
   End
   Begin Label Label2
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Login ID:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   2
      Top             =   960
      Width           =   960
   End
   Begin Label Label1
      BackColor       =   &H00FFFFFF&
      Caption         =   "&Server:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   0
      Top             =   360
      Width           =   960
   End
End
'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub CANCEL_BUTTON_Click ()
 Unload Login
End Sub

Sub Form_Load ()
Server_name_field.Text = DefServer$
Login_id_field.Text = DefLogin$
Password_field.Text = ""
End Sub

Sub LIST_BUTTON_Click ()
NL$ = Chr$(13) + Chr$(10)
nullchar$ = Chr$(0)

'  First look for locally defined servers in Win.ini
serverlist$ = Space(1000)
retcode% = SqlServerEnum(LOCSEARCH%, serverlist$, numservers%)
If retcode% <> ENUMSUCCESS% Then
    msg$ = "SQLServerEnum for LOCSEARCH failed, return code " + Str$(retcode%) + NL$
Else
    'parse out the names
    msg$ = "Locally defined servers: " + NL$
    work$ = serverlist$
    scount = 0
    While Len(work$) > 0
nullcharpos% = InStr(work$, nullchar$)
If nullcharpos% <> 0 Then
      srvname$ = Left$(work$, nullcharpos% - 1)
    If Len(srvname$) <> 0 Then
scount = scount + 1
msg$ = msg$ + "Server" + Str$(scount) + ": '" + srvname$ + "'" + NL$
work$ = Right$(work$, Len(work$) - nullcharpos%)
    Else
work$ = ""
    End If
End If

    Wend
    msg$ = msg$ + NL$
End If

'Now look for servers announced on the network
serverlist$ = Space(1000)
retcode% = SqlServerEnum(NETSEARCH%, serverlist$, numservers%)
If retcode% <> ENUMSUCCESS% Then
    msg$ = msg$ + "SQLServerEnum for NETSEARCH failed, return code " + Str$(retcode%) + NL$
Else
    'parse out the names
    msg$ = msg$ + "Network defined servers: " + NL$
    work$ = serverlist$
    scount = 0
    While Len(work$) > 0
nullcharpos% = InStr(work$, nullchar$)
If nullcharpos% <> 0 Then
      srvname$ = Left$(work$, nullcharpos% - 1)
    If Len(srvname$) <> 0 Then
scount = scount + 1
msg$ = msg$ + "Server" + Str$(scount) + ": '" + srvname$ + "'" + NL$
work$ = Right$(work$, Len(work$) - nullcharpos%)
    Else
work$ = ""
    End If
End If

    Wend
    msg$ = msg$ + NL$
End If

MsgBox msg$, 0, "Search for Servers"

End Sub

Sub OK_BUTTON_Click ()

Rem
Rem Get the server name, login Id, & password from the form
Rem

   Servername$ = Server_name_field.Text
   LoginID$ = Login_id_field.Text
   Password$ = Password_field.Text

If Servername$ = "" Then
MsgBox "Must supply a server name"
Exit Sub
End If
If LoginID$ = "" Then
MsgBox "Must supply a login id"
Exit Sub
End If

Rem
Rem Connect to the server
Rem

If LoginToServer() = SUCCEED Then Unload Login

End Sub


TEXT.BAS

Rem Global Definitions

Rem These handle the SQL Server connection

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global SqlConn%
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global DefServer$
Global DefLogin$
Global Const LoginTimeout% = 30
Global Const QueryTimeout% = 60


TEXT.FRM

VERSION 2.00
Begin Form PrimaryWindow
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Text"
   ControlBox      =   0   'False
   ForeColor       =   &H00000000&
   Height          =   6585
   Icon            =   TEXT.FRX:0000
   Left            =   630
   LinkMode        =   1  'Source
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   5895
   ScaleWidth      =   7815
   Top             =   75
   Width           =   7935
   Begin TextBox Text_Edit
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   2775
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   0
      Top             =   2760
      Width           =   7575
   End
   Begin VBSQL VBSQL1
      Caption         =   "SQL Err/Msg"
      Height          =   272
      Left            =   5580
      Top             =   2430
      Visible         =   0   'False
      Width           =   2055
   End
   Begin ListBox Titles_List
      Height          =   810
      Left            =   135
      TabIndex        =   9
      Top             =   1410
      Width           =   2415
   End
   Begin DirListBox Dir_Tree_Combo
      Height          =   1275
      Left            =   3360
      TabIndex        =   5
      Top             =   1155
      Width           =   2055
   End
   Begin FileListBox Text_File_List
      Height          =   1590
      Left            =   5580
      Pattern         =   "*.txt"
      TabIndex        =   6
      Top             =   840
      Width           =   2055
   End
   Begin DriveListBox Drive_Combo
      Height          =   357
      Left            =   3360
      TabIndex        =   4
      Top             =   833
      Width           =   2048
   End
   Begin ListBox Database_List
      Height          =   420
      Left            =   135
      TabIndex        =   11
      Top             =   360
      Width           =   2415
   End
   Begin TextBox Title_Edit
      Height          =   323
      Left            =   4605
      TabIndex        =   2
      Top             =   75
      Width           =   3075
   End
   Begin Label Label5
      BackColor       =   &H00C0C0C0&
      Caption         =   "Text in database:"
      Height          =   225
      Left            =   120
      TabIndex        =   12
      Top             =   2520
      Width           =   2055
   End
   Begin Label Label4
      BackColor       =   &H00C0C0C0&
      Caption         =   "Titles in database:"
      Height          =   225
      Left            =   120
      TabIndex        =   10
      Top             =   1185
      Width           =   2055
   End
   Begin Label Text_File_Label
      BackColor       =   &H00C0C0C0&
      Caption         =   "(none)"
      Height          =   255
      Left            =   3855
      TabIndex        =   8
      Top             =   480
      Width           =   3870
   End
   Begin Label Label3
      BackColor       =   &H00C0C0C0&
      Caption         =   "File:"
      Height          =   255
      Left            =   3360
      TabIndex        =   7
      Top             =   480
      Width           =   375
   End
   Begin Label Label2
      BackColor       =   &H00C0C0C0&
      Caption         =   "Text file title:"
      ForeColor       =   &H00000000&
      Height          =   255
      Left            =   3360
      TabIndex        =   3
      Top             =   120
      Width           =   1245
   End
   Begin Label Label1
      BackColor       =   &H00C0C0C0&
      Caption         =   "Databases:"
      Height          =   210
      Left            =   120
      TabIndex        =   1
      Top             =   120
      Width           =   1005
   End
   Begin Menu Menu_File
      Caption         =   "&File"
      Begin Menu Logon_Selection
         Caption         =   "&Logon"
      End
      Begin Menu Log_Off_Selection
         Caption         =   "Log &Off"
      End
      Begin Menu Exit_Selection
         Caption         =   "&Exit"
      End
   End
   Begin Menu Options_Menu
      Caption         =   "&Options"
      Begin Menu View_Selection
         Caption         =   "&View Text"
      End
      Begin Menu Insert_Selection
         Caption         =   "&Insert Text"
      End
      Begin Menu Delete_Selection
         Caption         =   "&Delete Text"
      End
   End
   Begin Menu About_Menu
      Caption         =   "&About"
   End
End

Sub About_Menu_Click ()

    About_Form.Show 1

End Sub

Function CheckForTextTable () As Integer

    Rem Check to see if sample table exits
    Cmd$ = "Select count(*) from sysobjects where name = 'text_table'"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Results% = SqlExec(SqlConn%)
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Table_Found$ = Sqldata(SqlConn%, 1)
Loop
    Loop

    If Val(Table_Found$) = 0 Then
Results% = MsgBox("Text table not found in " + Database$ + " database." + Chr$(13) + Chr$(10) + "Do you wish to create the table?", 52)
If Results% = 7 Then
    CheckForTextTable = FAIL
    Exit Function
Else
    Rem If sample table does not exist, create it
    Cmd$ = "create table text_table (title varchar(30) not null, text_col text null)"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Results% = SqlExec(SqlConn%)
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
Loop
    Loop
    CheckForTextTable = SUCCEED
End If
    Else
CheckForTextTable = SUCCEED
    End If

End Function

Sub ClearTextTitles ()

Rem Clear all text titles out of list box
Do While Titles_List.ListCount
    Titles_List.RemoveItem 0
Loop

End Sub

Sub Database_List_Click ()

Rem
Rem Get the database user wants to open
Rem If it doesn't have the text table in it, see if user wants to create one
Rem If it does exist, get all the text titles
Rem

    DatabaseSelection$ = Database_list.Text
    Results% = SqlUse(SqlConn%, DatabaseSelection$)
    Results% = CheckForTextTable()
    If Results% = SUCCEED Then
PrimaryWindow.MousePointer = 11
RetrieveTextTitles
View_Selection.Enabled = True
Insert_Selection.Enabled = True
Delete_Selection.Enabled = True
PrimaryWindow.MousePointer = 0
    Else
ClearTextTitles
View_Selection.Enabled = False
Insert_Selection.Enabled = False
Delete_Selection.Enabled = False
    End If

Rem
Rem Display the current database in the title window
Rem clear the text field
Rem

    DatabaseName$ = SqlName(SqlConn%)
    ChangePrimaryWindowCaption
    Text_Edit.Text = ""

End Sub

Sub Delete_Selection_Click ()

    Text_Title$ = Titles_List.Text

    If Text_Title$ = "" Then
Beep
MsgBox "You must first select a title."
    Else
Response% = MsgBox("Delete " + Text_Title$ + "?", 49)
If Response% = 1 Then
    PrimaryWindow.MousePointer = 11
    Results% = ExecuteSQLCommand("Delete from text_table where title = '" + Text_Title$ + "'")
    Results% = SqlResults%(SqlConn%)
    Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Loop
    PrimaryWindow.Text_Edit.Text = ""
    RetrieveTextTitles
    PrimaryWindow.MousePointer = 0
Else
    MsgBox "Delete aborted."
End If
    End If

End Sub

Sub Dir_Tree_Combo_Change ()

    Text_File_Label.Caption = "(none)"
    Text_File_List.Path = Dir_Tree_Combo.Path

End Sub

Sub Drive_Combo_Change ()

    Text_File_Label.Caption = "(none)"
    Dir_Tree_Combo.Path = Drive_Combo.Drive

End Sub

Sub Exit_Selection_Click ()

    ExitApplication
    End

End Sub

Sub Form_Load ()

    PrimaryWindowTitle = "Text Example"
    ChangePrimaryWindowCaption

    InitializeApplication
    MsgBox DBLIB_VERSION$

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True
    View_Selection.Enabled = False
    Insert_Selection.Enabled = False
    Delete_Selection.Enabled = False

End Sub

Sub Insert_Selection_Click ()

    Text_File$ = Text_File_Label.Caption
    Title$ = Title_Edit.Text
    Title$ = PrepareString(Title$)

    If Text_File$ = "(none)" Then
Beep
MsgBox "Please select a text file to insert."
    ElseIf Title$ = "" Then
Beep
MsgBox "Please enter a title for the text file you wish to insert."
    Else
PrimaryWindow.MousePointer = 11
InsertText Title$, Text_File$
Title_Edit.Text = ""
Text_File_List.ListIndex = -1
Text_File_Label.Caption = ""
RetrieveTextTitles
PrimaryWindow.MousePointer = 0
    End If

End Sub

Sub InsertText (Title As String, Text_File As String)

Rem
Rem This routine will insert the text data into the table
Rem Insert new row with title and token text value
Rem

    Results% = ExecuteSQLCommand("Insert into text_table values ('" + Title + "','none')")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS%
Do While SqlNextRow(SqlConn%) <> NOMOREROWS%
Loop
    Loop

Rem Get identifier for text column in current row

    Results% = ExecuteSQLCommand("select text_col from text_table where title = '" + Title + "'")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS%
Do While SqlNextRow(SqlConn%) <> NOMOREROWS%
    SqlPointer$ = SqlTxPtr(SqlConn%, 1)
    SqlTimestamp$ = SqlTxTimeStamp(SqlConn%, 1)
Loop
    Loop

Rem Open text file to load into SQL Server table

    DataPartLimit& = 8192
    Done% = False

    Open Text_File For Input As #1

    FileLength& = LOF(1)
    If FileLength& > 65536 Then
MsgBox "This application cannot display text files greater than 64K."
    Else
FileData$ = ""
Do While Not EOF(1)
    Input #1, DataPart$
    If Right$(DataPart$, 2) <> Chr$(13) + Chr$(10) Then
If Right$(DataPart$, 1) = Chr$(13) Then
    FileData$ = FileData$ + Left$(DataPart$, Len(DataPart$) - 1) + Chr$(13) + Chr$(10)
ElseIf Right$(DataPart$, 1) = Chr$(10) Then
    FileData$ = FileData$ + Left$(DataPart$, Len(DataPart$) - 1) + Chr$(13) + Chr$(10)
Else
    FileData$ = FileData$ + DataPart$ + Chr$(13) + Chr$(10)
End If
    Else
FileData$ = FileData$ + DataPart$
    End If
Loop

Rem Begin inserting text into text column in DatePartLimit& size chunks

Table$ = "text_table.text_col"
DataPos& = 1
DataLen& = Len(FileData$)

If SqlWriteText(SqlConn%, Table$, SqlPointer$, SQLTXPLEN%, SqlTimestamp$, 1, DataLen&, "") <> FAIL% Then
    If SqlOk(SqlConn%) <> FAIL% Then
Results% = SqlResults(SqlConn%)
Do While Not Done%
    If DataPos& + DataPartLimit& - 1 < DataLen& Then
DataPart$ = Mid$(FileData$, DataPos&, DataPartLimit&)
DataPos& = DataPos& + Len(DataPart$)
    Else
DataPart$ = Mid$(FileData$, DataPos&, DataLen& - DataPos& + 1)
Done% = True
    End If
Results% = SqlMoreText%(SqlConn%, Len(DataPart$), DataPart$)
Loop
If SqlOk(SqlConn%) <> FAIL% Then
    If SqlResults(SqlConn%) <> FAIL% Then
    MsgBox "Text inserted."
    End If
End If
    End If
End If
    End If
Close 1

End Sub

Sub LoadText (Title As String)

Rem
Rem This routine will read the text field from the table
Rem Clear Text_Edit box on primary window
Rem

    Text_Edit.Text = ""

Rem Get length of text in text column

    Results% = ExecuteSQLCommand("select datalength(text_col) from text_table where title = '" + Title + "'")
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
Do While SqlNextRow(SqlConn%) <> NOMOREROWS
    TextLen& = Val(Sqldata(SqlConn%, 1))
Loop
    Loop

    Offset& = 0

Rem Set size limit on chunks of text data

    LoadSizeLimit& = 8192
    If LoadSizeLimit& > TextLen& Then
LoadSizeLimit& = TextLen&
    End If

    LoadSize& = LoadSizeLimit&

Rem Set size of text returned to LoadSizeLimit&

    Results% = ExecuteSQLCommand("set textsize " + Str$(LoadSizeLimit&))
    Do While SqlResults(SqlConn%) <> NOMORERESULTS
Do While SqlNextRow(SqlConn%) <> NOMOREROWS
Loop
    Loop

Rem Begin reading text column in LoadSizeLimit& size chunks

    Cmd$ = "Declare @val varbinary(30)"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Cmd$ = "Select @val = textptr(text_col) from text_table where title = '" + Title + "'"
    Results% = SqlCmd(SqlConn%, Cmd$)
    Done% = False
Do While Not Done%
    Cmd$ = "READTEXT text_table.text_col @val " + Str$(Offset&) + " " + Str$(LoadSize&)
    Results% = SqlCmd(SqlConn%, Cmd$)
    If Offset& + LoadSize& = TextLen& Then
Done% = True
    Else
Offset& = Offset& + LoadSizeLimit&
If Offset& + LoadSizeLimit& > TextLen& Then
    LoadSize& = TextLen& - Offset&
End If
    End If
Loop

Rem Retrieve text data in result rows and place in text_edit box

    FileData$ = ""
    If SqlExec(SqlConn%) <> FAIL% Then
Do While SqlResults(SqlConn%) <> NOMORERESULTS
    Do While SqlNextRow(SqlConn%) <> NOMOREROWS
FileData$ = FileData$ + Sqldata(SqlConn%, 1)
    Loop
Loop
    End If

Rem Load text data into edit box on primary window
    Text_Edit.Text = FileData$

End Sub

Sub Log_Off_Selection_Click ()

    Logoff

    Logon_Selection.Enabled = True
    Log_Off_Selection.Enabled = False
    Exit_Selection.Enabled = True
    View_Selection.Enabled = False
    Insert_Selection.Enabled = False
    Delete_Selection.Enabled = False

End Sub

Sub Logon_Selection_Click ()

    Login.Show 1
    PrimaryWindow.MousePointer = 11
    If CheckServerConnection() = 1 Then
Results% = GetDatabases(Database_list)
ChangePrimaryWindowCaption
Logon_Selection.Enabled = False
Log_Off_Selection.Enabled = True
    End If
    PrimaryWindow.MousePointer = 0

End Sub

Function PrepareString (String_In As String) As String

Rem
Rem This routine will double up the single quotation mark to
Rem avoid syntax errors
Rem

    String_Out$ = ""

    For I% = 1 To Len(String_In)
If Mid$(String_In, I%, 1) = Chr$(39) Then
    String_Out$ = String_Out$ + Chr$(39) + Chr$(39)
Else
    String_Out$ = String_Out$ + Mid$(String_In, I%, 1)
End If
    Next

    PrepareString = String_Out$

End Function

Sub RetrieveTextTitles ()

Rem
Rem This routine will get all the text titles from the table
Rem Put them in the list box as they are retrieved.
Rem

    ClearTextTitles
    Results% = ExecuteSQLCommand("Select title from text_table")
    Do While (SqlResults%(SqlConn%) <> NOMORERESULTS)
Do While (SqlNextRow(SqlConn%) <> NOMOREROWS)
    Titles_List.AddItem Sqldata(SqlConn%, 1)
Loop
    Loop

End Sub

Sub Text_Edit_KeyPress (KeyAscii As Integer)

    If KeyAscii <> 0 Then
KeyAscii = 0
    End If

End Sub

Sub Text_File_List_Click ()

    If Right$(Dir_Tree_Combo.Path, 1) = "\" Then
Text_File_Label.Caption = Dir_Tree_Combo.Path + Text_File_List.FileName
    Else
Text_File_Label.Caption = Dir_Tree_Combo.Path + "\" + Text_File_List.FileName
    End If

End Sub

Sub Title_Edit_KeyPress (KeyAscii As Integer)

    If Len(Title_Edit.Text) = 30 Then
If KeyAscii <> 8 Then
    KeyAscii = 0
End If
    End If

End Sub

Sub Titles_List_Click ()

    Text_Edit.Text = ""

End Sub

Sub Titles_List_DblClick ()

    Text_Title$ = Titles_List.Text
    PrimaryWindow.MousePointer = 11
    LoadText Text_Title$
    PrimaryWindow.MousePointer = 0

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)
End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)

    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$

End Sub

Sub View_Selection_Click ()

    Text_Title$ = Titles_List.Text
    If Text_Title$ = "" Then
Beep
MsgBox "You must first select a title."
    Else
PrimaryWindow.MousePointer = 11
LoadText Text_Title$
PrimaryWindow.MousePointer = 0
    End If

End Sub


VBCONST.BAS

''''''''''''''''''''''''''''
' Visual Basic global constant file. This file can be loaded
' into a code module.
'
' Some constants are commented out because they have
' duplicates (e.g., NONE appears several places).
'
' If you are updating a Visual Basic 1.0 program to run in
' Visual Basic 2.0, you should replace your global constants
' with the constants in this file.  Note that True and False
' are now built into Visual Basic so are no longer defined in
' this file.
'
''''''''''''''''''''''''''''

' General

' Clipboard formats
Global Const CF_LINK = &HBF00
Global Const CF_TEXT = 1
Global Const CF_BITMAP = 2
Global Const CF_METAFILE = 3
Global Const CF_DIB = 8
Global Const CF_PALETTE = 9

' DragOver
Global Const ENTER = 0
Global Const LEAVE = 1
Global Const OVER = 2

' Drag (controls)
Global Const CANCEL = 0
Global Const BEGIN_DRAG = 1
Global Const END_DRAG = 2

' Show parameters
Global Const MODAL = 1
Global Const MODELESS = 0

' Arrange Method
' for MDI Forms
Global Const CASCADE = 0
Global Const TILE_HORIZONTAL = 1
Global Const TILE_VERTICAL = 2
Global Const ARRANGE_ICONS = 3

'ZOrder Method
Global Const BRINGTOFRONT = 0
Global Const SENDTOBACK = 1

' Key Codes
Global Const KEY_LBUTTON = &H1
Global Const KEY_RBUTTON = &H2
Global Const KEY_CANCEL = &H3
Global Const KEY_MBUTTON = &H4    ' NOT contiguous with L & RBUTTON
Global Const KEY_BACK = &H8
Global Const KEY_TAB = &H9
Global Const KEY_CLEAR = &HC
Global Const KEY_RETURN = &HD
Global Const KEY_SHIFT = &H10
Global Const KEY_CONTROL = &H11
Global Const KEY_MENU = &H12
Global Const KEY_PAUSE = &H13
Global Const KEY_CAPITAL = &H14
Global Const KEY_ESCAPE = &H1B
Global Const KEY_SPACE = &H20
Global Const KEY_PRIOR = &H21
Global Const KEY_NEXT = &H22
Global Const KEY_END = &H23
Global Const KEY_HOME = &H24
Global Const KEY_LEFT = &H25
Global Const KEY_UP = &H26
Global Const KEY_RIGHT = &H27
Global Const KEY_DOWN = &H28
Global Const KEY_SELECT = &H29
Global Const KEY_PRINT = &H2A
Global Const KEY_EXECUTE = &H2B
Global Const KEY_SNAPSHOT = &H2C
Global Const KEY_INSERT = &H2D
Global Const KEY_DELETE = &H2E
Global Const KEY_HELP = &H2F

' KEY_A thru KEY_Z are the same as their ASCII equivalents: 'A' thru 'Z'
' KEY_0 thru KEY_9 are the same as their ASCII equivalents: '0' thru '9'

Global Const KEY_NUMPAD0 = &H60
Global Const KEY_NUMPAD1 = &H61
Global Const KEY_NUMPAD2 = &H62
Global Const KEY_NUMPAD3 = &H63
Global Const KEY_NUMPAD4 = &H64
Global Const KEY_NUMPAD5 = &H65
Global Const KEY_NUMPAD6 = &H66
Global Const KEY_NUMPAD7 = &H67
Global Const KEY_NUMPAD8 = &H68
Global Const KEY_NUMPAD9 = &H69
Global Const KEY_MULTIPLY = &H6A
Global Const KEY_ADD = &H6B
Global Const KEY_SEPARATOR = &H6C
Global Const KEY_SUBTRACT = &H6D
Global Const KEY_DECIMAL = &H6E
Global Const KEY_DIVIDE = &H6F
Global Const KEY_F1 = &H70
Global Const KEY_F2 = &H71
Global Const KEY_F3 = &H72
Global Const KEY_F4 = &H73
Global Const KEY_F5 = &H74
Global Const KEY_F6 = &H75
Global Const KEY_F7 = &H76
Global Const KEY_F8 = &H77
Global Const KEY_F9 = &H78
Global Const KEY_F10 = &H79
Global Const KEY_F11 = &H7A
Global Const KEY_F12 = &H7B
Global Const KEY_F13 = &H7C
Global Const KEY_F14 = &H7D
Global Const KEY_F15 = &H7E
Global Const KEY_F16 = &H7F

Global Const KEY_NUMLOCK = &H90

' Variant VarType tags

Global Const V_EMPTY = 0
Global Const V_NULL = 1
Global Const V_INTEGER = 2
Global Const V_LONG = 3
Global Const V_SINGLE = 4
Global Const V_DOUBLE = 5
Global Const V_CURRENCY = 6
Global Const V_DATE = 7
Global Const V_STRING = 8

' Event Parameters

' ErrNum (LinkError)
Global Const WRONG_FORMAT = 1
Global Const DDE_SOURCE_CLOSED = 6
Global Const TOO_MANY_LINKS = 7
Global Const DATA_TRANSFER_FAILED = 8

' QueryUnload
Global Const FORM_CONTROLMENU = 0
Global Const FORM_CODE = 1
Global Const APP_WINDOWS = 2
Global Const APP_TASKMANAGER = 3
Global Const FORM_MDIFORM = 4

' Properties

' Colors
Global Const BLACK = &H0&
Global Const RED = &HFF&
Global Const GREEN = &HFF00&
Global Const YELLOW = &HFFFF&
Global Const BLUE = &HFF0000
Global Const MAGENTA = &HFF00FF
Global Const CYAN = &HFFFF00
Global Const WHITE = &HFFFFFF

' System Colors
Global Const SCROLL_BARS = &H80000000           ' Scroll-bars gray area.
Global Const DESKTOP = &H80000001               ' Desktop.
Global Const ACTIVE_TITLE_BAR = &H80000002      ' Active window caption.
Global Const INACTIVE_TITLE_BAR = &H80000003    ' Inactive window caption.
Global Const MENU_BAR = &H80000004              ' Menu background.
Global Const WINDOW_BACKGROUND = &H80000005     ' Window background.
Global Const WINDOW_FRAME = &H80000006          ' Window frame.
Global Const MENU_TEXT = &H80000007             ' Text in menus.
Global Const WINDOW_TEXT = &H80000008           ' Text in windows.
Global Const TITLE_BAR_TEXT = &H80000009        ' Text in caption, size box, scroll-bar arrow box..
Global Const ACTIVE_BORDER = &H8000000A         ' Active window border.
Global Const INACTIVE_BORDER = &H8000000B       ' Inactive window border.
Global Const APPLICATION_WORKSPACE = &H8000000C ' Background color of multiple document interface (MDI) applications.
Global Const HIGHLIGHT = &H8000000D             ' Items selected item in a control.
Global Const HIGHLIGHT_TEXT = &H8000000E        ' Text of item selected in a control.
Global Const BUTTON_FACE = &H8000000F           ' Face shading on command buttons.
Global Const BUTTON_SHADOW = &H80000010         ' Edge shading on command buttons.
Global Const GRAY_TEXT = &H80000011             ' Grayed (disabled) text.  This color is set to 0 if the current display driver does not support a solid gray color.
Global Const BUTTON_TEXT = &H80000012           ' Text on push buttons.

' Enumerated Types

' Align (picture box)
Global Const NONE = 0
Global Const ALIGN_TOP = 1
Global Const ALIGN_BOTTOM = 2

' Alignment
Global Const LEFT_JUSTIFY = 0  ' 0 - Left Justify
Global Const RIGHT_JUSTIFY = 1 ' 1 - Right Justify
Global Const CENTER = 2        ' 2 - Center

' BorderStyle (form)
'Global Const NONE = 0          ' 0 - None
Global Const FIXED_SINGLE = 1   ' 1 - Fixed Single
Global Const SIZABLE = 2        ' 2 - Sizable (Forms only)
Global Const FIXED_DOUBLE = 3   ' 3 - Fixed Double (Forms only)

' BorderStyle (Shape and Line)
'Global Const TRANSPARENT = 0    '0 - Transparent
'Global Const SOLID = 1          '1 - Solid
'Global Const DASH = 2         ' 2 - Dash
'Global Const DOT = 3          ' 3 - Dot
'Global Const DASH_DOT = 4     ' 4 - Dash-Dot
'Global Const DASH_DOT_DOT = 5 ' 5 - Dash-Dot-Dot
'Global Const INSIDE_SOLID = 6 ' 6 - Inside Solid

' MousePointer
Global Const DEFAULT = 0        ' 0 - Default
Global Const ARROW = 1          ' 1 - Arrow
Global Const CROSSHAIR = 2      ' 2 - Cross
Global Const IBEAM = 3          ' 3 - I-Beam
Global Const ICON_POINTER = 4   ' 4 - Icon
Global Const SIZE_POINTER = 5   ' 5 - Size
Global Const SIZE_NE_SW = 6     ' 6 - Size NE SW
Global Const SIZE_N_S = 7       ' 7 - Size N S
Global Const SIZE_NW_SE = 8     ' 8 - Size NW SE
Global Const SIZE_W_E = 9       ' 9 - Size W E
Global Const UP_ARROW = 10      ' 10 - Up Arrow
Global Const HOURGLASS = 11     ' 11 - Hourglass
Global Const NO_DROP = 12       ' 12 - No drop

' DragMode
Global Const MANUAL = 0    ' 0 - Manual
Global Const AUTOMATIC = 1 ' 1 - Automatic

' DrawMode
Global Const BLACKNESS = 1      ' 1 - Blackness
Global Const NOT_MERGE_PEN = 2  ' 2 - Not Merge Pen
Global Const MASK_NOT_PEN = 3   ' 3 - Mask Not Pen
Global Const NOT_COPY_PEN = 4   ' 4 - Not Copy Pen
Global Const MASK_PEN_NOT = 5   ' 5 - Mask Pen Not
Global Const INVERT = 6         ' 6 - Invert
Global Const XOR_PEN = 7        ' 7 - Xor Pen
Global Const NOT_MASK_PEN = 8   ' 8 - Not Mask Pen
Global Const MASK_PEN = 9       ' 9 - Mask Pen
Global Const NOT_XOR_PEN = 10   ' 10 - Not Xor Pen
Global Const NOP = 11           ' 11 - Nop
Global Const MERGE_NOT_PEN = 12 ' 12 - Merge Not Pen
Global Const COPY_PEN = 13      ' 13 - Copy Pen
Global Const MERGE_PEN_NOT = 14 ' 14 - Merge Pen Not
Global Const MERGE_PEN = 15     ' 15 - Merge Pen
Global Const WHITENESS = 16     ' 16 - Whiteness

' DrawStyle
Global Const SOLID = 0        ' 0 - Solid
Global Const DASH = 1         ' 1 - Dash
Global Const DOT = 2          ' 2 - Dot
Global Const DASH_DOT = 3     ' 3 - Dash-Dot
Global Const DASH_DOT_DOT = 4 ' 4 - Dash-Dot-Dot
Global Const INVISIBLE = 5    ' 5 - Invisible
Global Const INSIDE_SOLID = 6 ' 6 - Inside Solid

' FillStyle
' Global Const SOLID = 0           ' 0 - Solid
Global Const TRANSPARENT = 1       ' 1 - Transparent
Global Const HORIZONTAL_LINE = 2   ' 2 - Horizontal Line
Global Const VERTICAL_LINE = 3     ' 3 - Vertical Line
Global Const UPWARD_DIAGONAL = 4   ' 4 - Upward Diagonal
Global Const DOWNWARD_DIAGONAL = 5 ' 5 - Downward Diagonal
Global Const CROSS = 6             ' 6 - Cross
Global Const DIAGONAL_CROSS = 7    ' 7 - Diagonal Cross

' LinkMode (forms and controls)
' Global Const NONE = 0         ' 0 - None
Global Const LINK_SOURCE = 1    ' 1 - Source (forms only)
Global Const LINK_AUTOMATIC = 1 ' 1 - Automatic (controls only)
Global Const LINK_MANUAL = 2    ' 2 - Manual (controls only)
Global Const LINK_NOTIFY = 3    ' 3 - Notify (controls only)

' LinkMode (kept for VB1.0 compatibility, use new constants instead)
Global Const HOT = 1    ' 1 - Hot (controls only)
'Global Const SERVER = 1 ' 1 - Server (forms only)
Global Const COLD = 2   ' 2 - Cold (controls only)

' ScaleMode
Global Const USER = 0        ' 0 - User
Global Const TWIPS = 1       ' 1 - Twip
Global Const POINTS = 2      ' 2 - Point
Global Const PIXELS = 3      ' 3 - Pixel
Global Const CHARACTERS = 4  ' 4 - Character
Global Const INCHES = 5      ' 5 - Inch
Global Const MILLIMETERS = 6 ' 6 - Millimeter
Global Const CENTIMETERS = 7 ' 7 - Centimeter

' ScrollBar
' Global Const NONE     = 0 ' 0 - None
Global Const HORIZONTAL = 1 ' 1 - Horizontal
Global Const VERTICAL = 2   ' 2 - Vertical
Global Const BOTH = 3       ' 3 - Both

' Shape
Global Const SHAPE_RECTANGLE = 0
Global Const SHAPE_SQUARE = 1
Global Const SHAPE_OVAL = 2
Global Const SHAPE_CIRCLE = 3
Global Const SHAPE_ROUNDED_RECTANGLE = 4
Global Const SHAPE_ROUNDED_SQUARE = 5

' WindowState
Global Const NORMAL = 0    ' 0 - Normal
Global Const MINIMIZED = 1 ' 1 - Minimized
Global Const MAXIMIZED = 2 ' 2 - Maximized

' Check Value
Global Const UNCHECKED = 0 ' 0 - Unchecked
Global Const CHECKED = 1   ' 1 - Checked
Global Const GRAYED = 2    ' 2 - Grayed

' Shift parameter masks
Global Const SHIFT_MASK = 1
Global Const CTRL_MASK = 2
Global Const ALT_MASK = 4

' Button parameter masks
Global Const LEFT_BUTTON = 1
Global Const RIGHT_BUTTON = 2
Global Const MIDDLE_BUTTON = 4

' Function Parameters
' MsgBox parameters
Global Const MB_OK = 0                 ' OK button only
Global Const MB_OKCANCEL = 1           ' OK and Cancel buttons
Global Const MB_ABORTRETRYIGNORE = 2   ' Abort, Retry, and Ignore buttons
Global Const MB_YESNOCANCEL = 3        ' Yes, No, and Cancel buttons
Global Const MB_YESNO = 4              ' Yes and No buttons
Global Const MB_RETRYCANCEL = 5        ' Retry and Cancel buttons

Global Const MB_ICONSTOP = 16          ' Critical message
Global Const MB_ICONQUESTION = 32      ' Warning query
Global Const MB_ICONEXCLAMATION = 48   ' Warning message
Global Const MB_ICONINFORMATION = 64   ' Information message

Global Const MB_APPLMODAL = 0          ' Application Modal Message Box
Global Const MB_DEFBUTTON1 = 0         ' First button is default
Global Const MB_DEFBUTTON2 = 256       ' Second button is default
Global Const MB_DEFBUTTON3 = 512       ' Third button is default
Global Const MB_SYSTEMMODAL = 4096      'System Modal

' MsgBox return values
Global Const IDOK = 1                  ' OK button pressed
Global Const IDCANCEL = 2              ' Cancel button pressed
Global Const IDABORT = 3               ' Abort button pressed
Global Const IDRETRY = 4               ' Retry button pressed
Global Const IDIGNORE = 5              ' Ignore button pressed
Global Const IDYES = 6                 ' Yes button pressed
Global Const IDNO = 7                  ' No button pressed

' SetAttr, Dir, GetAttr functions
Global Const ATTR_NORMAL = 0
Global Const ATTR_READONLY = 1
Global Const ATTR_HIDDEN = 2
Global Const ATTR_SYSTEM = 4
Global Const ATTR_VOLUME = 8
Global Const ATTR_DIRECTORY = 16
Global Const ATTR_ARCHIVE = 32

'Grid
'ColAlignment,FixedAlignment Properties
Global Const GRID_ALIGNLEFT = 0
Global Const GRID_ALIGNRIGHT = 1
Global Const GRID_ALIGNCENTER = 2

'Fillstyle Property
Global Const GRID_SINGLE = 0
Global Const GRID_REPEAT = 1

'OLE Client Control
'Action
Global Const OLE_CREATE_NEW = 0
Global Const OLE_CREATE_FROM_FILE = 1
Global Const OLE_COPY = 4
Global Const OLE_PASTE = 5
Global Const OLE_UPDATE = 6
Global Const OLE_ACTIVATE = 7
Global Const OLE_EXECUTE = 8
Global Const OLE_CLOSE = 9
Global Const OLE_DELETE = 10
Global Const OLE_SAVE_TO_FILE = 11
Global Const OLE_READ_FROM_FILE = 12
Global Const OLE_CONVERT_TO_TYPE = 13

'ServerType
Global Const OLE_LINKED = 0
Global Const OLE_EMBEDDED = 1
Global Const OLE_STATIC = 2

'UpdateOptions
Global Const OLE_AUTOMATIC = 0
Global Const OLE_FROZEN = 1
Global Const OLE_MANUAL = 2

'Update Event Constants
Global Const OLE_CHANGED = 0
Global Const OLE_SAVED = 1
Global Const OLE_CLOSED = 2
Global Const OLE_RELEASE = 3

'---------------------------------------------------------
'      Table of Contents for Visual Basic Professional
'
'       1.  3-D Controls
'           (Frame/Panel/Option/Check/Command/Group Push)
'       2.  Animated Button
'       3.  Common Dialog Section
'       4.  Gauge Control
'       5.  Graph Control Section
'       6.  Key Status Control
'       7.  Spin Button
'       8.  MCI Control (Multimedia)
'       9.  Masked Edit Control
'       10. Comm Control
'       11. ODBC Constants
'---------------------------------------------------------

'-------------------------------------------------------------------
'3D Controls
'-------------------------------------------------------------------
'Alignment (Check Box)
Global Const SSCB_TEXT_RIGHT = 0         '0 - Text to the right
Global Const SSCB_TEXT_LEFT = 1          '1 - Text to the left

'Alignment (Option Button)
Global Const SSOB_TEXT_RIGHT = 0         '0 - Text to the right
Global Const SSOB_TEXT_LEFT = 1          '1 - Text to the left

'Alignment (Frame)
Global Const SSFR_LEFT_JUSTIFY = 0       '0 - Left justify text
Global Const SSFR_RIGHT_JUSTIFY = 1      '1 - Right justify text
Global Const SSFR_CENTER = 2             '2 - Center text

'Alignment (Panel)
Global Const SSPN_LEFT_TOP = 0           '0 - Text to left and top
Global Const SSPN_LEFT_MIDDLE = 1        '1 - Text to left and middle
Global Const SSPN_LEFT_BOTTOM = 2        '2 - Text to left and bottom
Global Const SSPN_RIGHT_TOP = 3          '3 - Text to right and top
Global Const SSPN_RIGHT_MIDDLE = 4       '4 - Text to right and middle
Global Const SSPN_RIGHT_BOTTOM = 5       '5 - Text to right and bottom
Global Const SSPN_CENTER_TOP = 6         '6 - Text to center and top
Global Const SSPN_CENTER_MIDDLE = 7      '7 - Text to center and middle
Global Const SSPN_CENTER_BOTTOM = 8      '8 - Text to center and bottom

'Autosize (Command Button)
Global Const SS_AUTOSIZE_NONE = 0        '0 - No Autosizing
Global Const SSPB_AUTOSIZE_PICTOBUT = 1  '0 - Autosize Picture to Button
Global Const SSPB_AUTOSIZE_BUTTOPIC = 2  '0 - Autosize Button to Picture

'Autosize (Ribbon Button)
'Global Const SS_AUTOSIZE_NONE      = 0  '0 - No Autosizing
Global Const SSRI_AUTOSIZE_PICTOBUT = 1  '0 - Autosize Picture to Button
Global Const SSRI_AUTOSIZE_BUTTOPIC = 2  '0 - Autosize Button to Picture

'Autosize (Panel)
'Global Const SS_AUTOSIZE_NONE    = 0    '0 - No Autosizing
Global Const SSPN_AUTOSIZE_WIDTH = 1     '1 - Autosize Panel width to Caption
Global Const SSPN_AUTOSIZE_HEIGHT = 2    '2 - Autosize Panel height to Caption
Global Const SSPN_AUTOSIZE_CHILD = 3     '3 - Autosize Child to Panel

'BevelInner (Panel)
Global Const SS_BEVELINNER_NONE = 0      '0 - No Inner Bevel
Global Const SS_BEVELINNER_INSET = 1     '1 - Inset Inner Bevel
Global Const SS_BEVELINNER_RAISED = 2    '2 - Raised Inner Bevel

'BevelOuter (Panel)
Global Const SS_BEVELOUTER_NONE = 0      '0 - No Outer Bevel
Global Const SS_BEVELOUTER_INSET = 1     '1 - Inset Outer Bevel
Global Const SS_BEVELOUTER_RAISED = 2    '2 - Raised Outer Bevel

'FloodType (Panel)
Global Const SS_FLOODTYPE_NONE = 0       '0 - No flood
Global Const SS_FLOODTYPE_L_TO_R = 1     '1 - Left to light
Global Const SS_FLOODTYPE_R_TO_L = 2     '2 - Right to left
Global Const SS_FLOODTYPE_T_TO_B = 3     '3 - Top to bottom
Global Const SS_FLOODTYPE_B_TO_T = 4     '4 - Bottom to top
Global Const SS_FLOODTYPE_CIRCLE = 5     '5 - Widening circle

'Font3D (Panel, Command Button, Option Button, Check Box, Frame)
Global Const SS_FONT3D_NONE = 0          '0 - No 3-D text
Global Const SS_FONT3D_RAISED_LIGHT = 1  '1 - Raised with light shading
Global Const SS_FONT3D_RAISED_HEAVY = 2  '2 - Raised with heavy shading
Global Const SS_FONT3D_INSET_LIGHT = 3   '3 - Inset with light shading
Global Const SS_FONT3D_INSET_HEAVY = 4   '4 - Inset with heavy shading

'PictureDnChange (Ribbon Button)
Global Const SS_PICDN_NOCHANGE = 0       '0 - Use 'Up'bitmap with no change
Global Const SS_PICDN_DITHER = 1         '1 - Dither 'Up'bitmap
Global Const SS_PICDN_INVERT = 2         '2 - Invert 'Up'bitmap

'ShadowColor (Panel, Frame)
Global Const SS_SHADOW_DARKGREY = 0      '0 - Dark grey shadow
Global Const SS_SHADOW_BLACK = 1         '1 - Black shadow

'ShadowStyle (Frame)
Global Const SS_SHADOW_INSET = 0         '0 - Shadow inset
Global Const SS_SHADOW_RAISED = 1        '1 - Shadow raised

'---------------------------------------
'Animated Button
'---------------------------------------
'Cycle property
Global Const ANI_ANIMATED = 0
Global Const ANI_MULTISTATE = 1
Global Const ANI_TWO_STATE = 2

'Click Filter property
Global Const ANI_ANYWHERE = 0
Global Const ANI_IMAGE_AND_TEXT = 1
Global Const ANI_IMAGE = 2
Global Const ANI_TEXT = 3

'PicDrawMode Property
Global Const ANI_XPOS_YPOS = 0
Global Const ANI_AUTOSIZE = 1
Global Const ANI_STRETCH = 2

'SpecialOp Property
Global Const ANI_CLICK = 1

'TextPosition Property
Global Const ANI_CENTER = 0
Global Const ANI_LEFT = 1
Global Const ANI_RIGHT = 2
Global Const ANI_BOTTON = 3
Global Const ANI_TOP = 4

'-----------------------------------
'Common Dialog Control
'-----------------------------------
'Action Property
Global Const DLG_FILE_OPEN = 1
Global Const DLG_FILE_SAVE = 2
Global Const DLG_COLOR = 3
Global Const DLG_FONT = 4
Global Const DLG_PRINT = 5
Global Const DLG_HELP = 6

'File Open/Save Dialog Flags
Global Const OFN_READONLY = &H1&
Global Const OFN_OVERWRITEPROMPT = &H2&
Global Const OFN_HIDEREADONLY = &H4&
Global Const OFN_NOCHANGEDIR = &H8&
Global Const OFN_SHOWHELP = &H10&
Global Const OFN_NOVALIDATE = &H100&
Global Const OFN_ALLOWMULTISELECT = &H200&
Global Const OFN_EXTENTIONDIFFERENT = &H400&
Global Const OFN_PATHMUSTEXIST = &H800&
Global Const OFN_FILEMUSTEXIST = &H1000&
Global Const OFN_CREATEPROMPT = &H2000&
Global Const OFN_SHAREAWARE = &H4000&
Global Const OFN_NOREADONLYRETURN = &H8000&

'Color Dialog Flags
Global Const CC_RGBINIT = &H1&
Global Const CC_FULLOPEN = &H2&
Global Const CC_PREVENTFULLOPEN = &H4&
Global Const CC_SHOWHELP = &H8&

'Fonts Dialog Flags
Global Const CF_SCREENFONTS = &H1&
Global Const CF_PRINTERFONTS = &H2&
Global Const CF_BOTH = &H3&
Global Const CF_SHOWHELP = &H4&
Global Const CF_INITTOLOGFONTSTRUCT = &H40&
Global Const CF_USESTYLE = &H80&
Global Const CF_EFFECTS = &H100&
Global Const CF_APPLY = &H200&
Global Const CF_ANSIONLY = &H400&
Global Const CF_NOVECTORFONTS = &H800&
Global Const CF_NOSIMULATIONS = &H1000&
Global Const CF_LIMITSIZE = &H2000&
Global Const CF_FIXEDPITCHONLY = &H4000&
Global Const CF_WYSIWYG = &H8000&                        'must also have CF_SCREENFONTS & CF_PRINTERFONTS
Global Const CF_FORCEFONTEXIST = &H10000
Global Const CF_SCALABLEONLY = &H20000
Global Const CF_TTONLY = &H40000
Global Const CF_NOFACESEL = &H80000
Global Const CF_NOSTYLESEL = &H100000
Global Const CF_NOSIZESEL = &H200000

'Printer Dialog Flags
Global Const PD_ALLPAGES = &H0&
Global Const PD_SELECTION = &H1&
Global Const PD_PAGENUMS = &H2&
Global Const PD_NOSELECTION = &H4&
Global Const PD_NOPAGENUMS = &H8&
Global Const PD_COLLATE = &H10&
Global Const PD_PRINTTOFILE = &H20&
Global Const PD_PRINTSETUP = &H40&
Global Const PD_NOWARNING = &H80&
Global Const PD_RETURNDC = &H100&
Global Const PD_RETURNIC = &H200&
Global Const PD_RETURNDEFAULT = &H400&
Global Const PD_SHOWHELP = &H800&
Global Const PD_USEDEVMODECOPIES = &H40000
Global Const PD_DISABLEPRINTTOFILE = &H80000
Global Const PD_HIDEPRINTTOFILE = &H100000

'Help Constants
Global Const HELP_CONTEXT = &H1           'Display topic in ulTopic
Global Const HELP_QUIT = &H2              'Terminate help
Global Const HELP_INDEX = &H3             'Display index
Global Const HELP_CONTENTS = &H3
Global Const HELP_HELPONHELP = &H4        'Display help on using help
Global Const HELP_SETINDEX = &H5          'Set the current Index for multi index help
Global Const HELP_SETCONTENTS = &H5
Global Const HELP_CONTEXTPOPUP = &H8
Global Const HELP_FORCEFILE = &H9
Global Const HELP_KEY = &H101             'Display topic for keyword in offabData
Global Const HELP_COMMAND = &H102
Global Const HELP_PARTIALKEY = &H105      'call the search engine in winhelp

'Error Constants
Global Const CDERR_DIALOGFAILURE = &HFFFF

Global Const CDERR_GENERALCODES = &H0
Global Const CDERR_STRUCTSIZE = &H1
Global Const CDERR_INITIALIZATION = &H2
Global Const CDERR_NOTEMPLATE = &H3
Global Const CDERR_NOHINSTANCE = &H4
Global Const CDERR_LOADSTRFAILURE = &H5
Global Const CDERR_FINDRESFAILURE = &H6
Global Const CDERR_LOADRESFAILURE = &H7
Global Const CDERR_LOCKRESFAILURE = &H8
Global Const CDERR_MEMALLOCFAILURE = &H9
Global Const CDERR_MEMLOCKFAILURE = &HA
Global Const CDERR_NOHOOK = &HB

'Added for CMDLG.VBX
Global Const CDERR_CANCEL = &HC
Global Const CDERR_NODLL = &HD
Global Const CDERR_ERRPROC = &HE
Global Const CDERR_ALLOC = &HF
Global Const CDERR_HELP = &H10

Global Const PDERR_PRINTERCODES = &H1000
Global Const PDERR_SETUPFAILURE = &H1001
Global Const PDERR_PARSEFAILURE = &H1002
Global Const PDERR_RETDEFFAILURE = &H1003
Global Const PDERR_LOADDRVFAILURE = &H1004
Global Const PDERR_GETDEVMODEFAIL = &H1005
Global Const PDERR_INITFAILURE = &H1006
Global Const PDERR_NODEVICES = &H1007
Global Const PDERR_NODEFAULTPRN = &H1008
Global Const PDERR_DNDMMISMATCH = &H1009
Global Const PDERR_CREATEICFAILURE = &H100A
Global Const PDERR_PRINTERNOTFOUND = &H100B

Global Const CFERR_CHOOSEFONTCODES = &H2000
Global Const CFERR_NOFONTS = &H2001

Global Const FNERR_FILENAMECODES = &H3000
Global Const FNERR_SUBCLASSFAILURE = &H3001
Global Const FNERR_INVALIDFILENAME = &H3002
Global Const FNERR_BUFFERTOOSMALL = &H3003

Global Const FRERR_FINDREPLACECODES = &H4000
Global Const CCERR_CHOOSECOLORCODES = &H5000

'---------------------------------------
'GAUGE
'---------------------------------------
'Style Property
Global Const GAUGE_HORIZ = 0
Global Const GAUGE_VERT = 1
Global Const GAUGE_SEMI = 2
Global Const GAUGE_FULL = 3

'----------------------------------------
'Graph Control
'----------------------------------------
'General
Global Const G_NONE = 0
Global Const G_DEFAULT = 0

Global Const G_OFF = 0
Global Const G_ON = 1

Global Const G_MONO = 0
Global Const G_COLOR = 1

'Graph Types
Global Const G_PIE2D = 1
Global Const G_PIE3D = 2
Global Const G_BAR2D = 3
Global Const G_BAR3D = 4
Global Const G_GANTT = 5
Global Const G_LINE = 6
Global Const G_LOGLIN = 7
Global Const G_AREA = 8
Global Const G_SCATTER = 9
Global Const G_POLAR = 10
Global Const G_HLC = 11

'Colors
Global Const G_BLACK = 0
Global Const G_BLUE = 1
Global Const G_GREEN = 2
Global Const G_CYAN = 3
Global Const G_RED = 4
Global Const G_MAGENTA = 5
Global Const G_BROWN = 6
Global Const G_LIGHT_GRAY = 7
Global Const G_DARK_GRAY = 8
Global Const G_LIGHT_BLUE = 9
Global Const G_LIGHT_GREEN = 10
Global Const G_LIGHT_CYAN = 11
Global Const G_LIGHT_RED = 12
Global Const G_LIGHT_MAGENTA = 13
Global Const G_YELLOW = 14
Global Const G_WHITE = 15
Global Const G_AUTOBW = 16

'Patterns
Global Const G_SOLID = 0
Global Const G_HOLLOW = 1
Global Const G_HATCH1 = 2
Global Const G_HATCH2 = 3
Global Const G_HATCH3 = 4
Global Const G_HATCH4 = 5
Global Const G_HATCH5 = 6
Global Const G_HATCH6 = 7
Global Const G_BITMAP1 = 16
Global Const G_BITMAP2 = 17
Global Const G_BITMAP3 = 18
Global Const G_BITMAP4 = 19
Global Const G_BITMAP5 = 20
Global Const G_BITMAP6 = 21
Global Const G_BITMAP7 = 22
Global Const G_BITMAP8 = 23
Global Const G_BITMAP9 = 24
Global Const G_BITMAP10 = 25
Global Const G_BITMAP11 = 26
Global Const G_BITMAP12 = 27
Global Const G_BITMAP13 = 28
Global Const G_BITMAP14 = 29
Global Const G_BITMAP15 = 30
Global Const G_BITMAP16 = 31

'Symbols
Global Const G_CROSS_PLUS = 0
Global Const G_CROSS_TIMES = 1
Global Const G_TRIANGLE_UP = 2
Global Const G_SOLID_TRIANGLE_UP = 3
Global Const G_TRIANGLE_DOWN = 4
Global Const G_SOLID_TRIANGLE_DOWN = 5
Global Const G_SQUARE = 6
Global Const G_SOLID_SQUARE = 7
Global Const G_DIAMOND = 8
Global Const G_SOLID_DIAMOND = 9

'Line Styles
'Global Const G_SOLID = 0
Global Const G_DASH = 1
Global Const G_DOT = 2
Global Const G_DASHDOT = 3
Global Const G_DASHDOTDOT = 4

'Grids
Global Const G_HORIZONTAL = 1
Global Const G_VERTICAL = 2

'Statistics
Global Const G_MEAN = 1
Global Const G_MIN_MAX = 2
Global Const G_STD_DEV = 4
Global Const G_BEST_FIT = 8

'Data Arrays
Global Const G_GRAPH_DATA = 1
Global Const G_COLOR_DATA = 2
Global Const G_EXTRA_DATA = 3
Global Const G_LABEL_TEXT = 4
Global Const G_LEGEND_TEXT = 5
Global Const G_PATTERN_DATA = 6
Global Const G_SYMBOL_DATA = 7
Global Const G_XPOS_DATA = 8
Global Const G_ALL_DATA = 9

'Draw Mode
Global Const G_NO_ACTION = 0
Global Const G_CLEAR = 1
Global Const G_DRAW = 2
Global Const G_BLIT = 3
Global Const G_COPY = 4
Global Const G_PRINT = 5
Global Const G_WRITE = 6

'Print Options
Global Const G_BORDER = 2

'Pie Chart Options             '
Global Const G_NO_LINES = 1
Global Const G_COLORED = 2
Global Const G_PERCENTS = 4

'Bar Chart Options             '
'Global Const G_HORIZONTAL = 1
Global Const G_STACKED = 2
Global Const G_PERCENTAGE = 4
Global Const G_Z_CLUSTERED = 6

'Gantt Chart Options           '
Global Const G_SPACED_BARS = 1

'Line/Polar Chart Options      '
Global Const G_SYMBOLS = 1
Global Const G_STICKS = 2
Global Const G_LINES = 4

'Area Chart Options            '
Global Const G_ABSOLUTE = 1
Global Const G_PERCENT = 2

'HLC Chart Options             '
Global Const G_NO_CLOSE = 1
Global Const G_NO_HIGH_LOW = 2

'---------------------------------------
'Key Status Control
'---------------------------------------
'Style
Global Const KEYSTAT_CAPSLOCK = 0
Global Const KEYSTAT_NUMLOCK = 1
Global Const KEYSTAT_INSERT = 2
Global Const KEYSTAT_SCROLLLOCK = 3

'---------------------------------------
'MCI Control (Multimedia)
'---------------------------------------
'Mode Property
Global Const MCI_MODE_NOT_OPEN = 11
Global Const MCI_MODE_STOP = 12
Global Const MCI_MODE_PLAY = 13
Global Const MCI_MODE_RECORD = 14
Global Const MCI_MODE_SEEK = 15
Global Const MCI_MODE_PAUSE = 16
Global Const MCI_MODE_READY = 17

'NotifyValue Property
Global Const MCI_NOTIFY_SUCCESSFUL = 1
Global Const MCI_NOTIFY_SUPERSEDED = 2
Global Const MCI_ABORTED = 4
Global Const MCI_FAILURE = 8

'Orientation Property
Global Const MCI_ORIENT_HORZ = 0
Global Const MCI_ORIENT_VERT = 1

'RecordMode Porperty
Global Const MCI_RECORD_INSERT = 0
Global Const MCI_RECORD_OVERWRITE = 1

'TimeFormat Property
Global Const MCI_FORMAT_MILLISECONDS = 0
Global Const MCI_FORMAT_HMS = 1
Global Const MCI_FORMAT_FRAMES = 3
Global Const MCI_FORMAT_SMPTE_24 = 4
Global Const MCI_FORMAT_SMPTE_25 = 5
Global Const MCI_FORMAT_SMPTE_30 = 6
Global Const MCI_FORMAT_SMPTE_30DROP = 7
Global Const MCI_FORMAT_BYTES = 8
Global Const MCI_FORMAT_SAMPLES = 9
Global Const MCI_FORMAT_TMSF = 10

'---------------------------------------
'Spin Button
'---------------------------------------
'SpinOrientation
Global Const SPIN_VERTICAL = 0
Global Const SPIN_HORIZONTAL = 1

'---------------------------------------
'Masked Edit Control
'---------------------------------------
'ClipMode
Global Const ME_INCLIT = 0
Global Const ME_EXCLIT = 1

'---------------------------------------
'Comm Control
'---------------------------------------

'Handshaking
Global Const MSCOMM_HANDSHAKE_NONE = 0
Global Const MSCOMM_HANDSHAKE_XONXOFF = 1
Global Const MSCOMM_HANDSHAKE_RTS = 2
Global Const MSCOMM_HANDSHAKE_RTSXONXOFF = 3

'Event constants
Global Const MSCOMM_EV_SEND = 1
Global Const MSCOMM_EV_RECEIVE = 2
Global Const MSCOMM_EV_CTS = 3
Global Const MSCOMM_EV_DSR = 4
Global Const MSCOMM_EV_CD = 5
Global Const MSCOMM_EV_RING = 6
Global Const MSCOMM_EV_EOF = 7

'Error code constants
Global Const MSCOMM_ER_BREAK = 1001
Global Const MSCOMM_ER_CTSTO = 1002
Global Const MSCOMM_ER_DSRTO = 1003
Global Const MSCOMM_ER_FRAME = 1004
Global Const MSCOMM_ER_OVERRUN = 1006
Global Const MSCOMM_ER_CDTO = 1007
Global Const MSCOMM_ER_RXOVER = 1008
Global Const MSCOMM_ER_RXPARITY = 1009
Global Const MSCOMM_ER_TXFULL = 1010

'-------------------------------------------------
' VBMAPIM CONTROL CONSTANTS (MAPI SESSION CONTROL)
'-------------------------------------------------
'Action
Global Const SESSION_SIGNON = 1
Global Const SESSION_SIGNOFF = 2

'-------------------------------------------------
' VBMAPIM CONTROL CONSTANTS (MAPI MESSAGE CONTROL)
'-------------------------------------------------
'Action
Global Const MESSAGE_FETCH = 1             ' Load all messages from message store
Global Const MESSAGE_SENDDLG = 2           ' Send mail bring up default mapi dialog
Global Const MESSAGE_SEND = 3              ' Send mail without default mapi dialog
Global Const MESSAGE_SAVEMSG = 4           ' Save message in the compose buffer
Global Const MESSAGE_COPY = 5              ' Copy current message to compose buffer
Global Const MESSAGE_COMPOSE = 6           ' Initialize compose buffer (previous
   ' data is lost
Global Const MESSAGE_REPLY = 7             ' Fill Compose buffer as REPLY
Global Const MESSAGE_REPLYALL = 8          ' Fill Compose buffer as REPLY ALL
Global Const MESSAGE_FORWARD = 9           ' Fill Compose buffer as FORWARD
Global Const MESSAGE_DELETE = 10           ' Delete current message
Global Const MESSAGE_SHOWADBOOK = 11       ' Show Address book
Global Const MESSAGE_SHOWDETAILS = 12      ' Show details of the current recipient
Global Const MESSAGE_RESOLVENAME = 13      ' Resolve the display name of the recipient
Global Const RECIPIENT_DELETE = 14            ' Fill Compose buffer as FORWARD
Global Const ATTACHMENT_DELETE = 15          ' Delete current message

'-------------------------------------------------
'  ERROR CONSTANT DECLARATIONS (MAPI CONTROLS)
'-------------------------------------------------
Global Const SUCCESS_SUCCESS = 32000
Global Const MAPI_USER_ABORT = 32001
Global Const MAPI_E_FAILURE = 32002
Global Const MAPI_E_LOGIN_FAILURE = 32003
Global Const MAPI_E_DISK_FULL = 32004
Global Const MAPI_E_INSUFFICIENT_MEMORY = 32005
Global Const MAPI_E_ACCESS_DENIED = 32006
Global Const MAPI_E_TOO_MANY_SESSIONS = 32008
Global Const MAPI_E_TOO_MANY_FILES = 32009
Global Const MAPI_E_TOO_MANY_RECIPIENTS = 32010
Global Const MAPI_E_ATTACHMENT_NOT_FOUND = 32011
Global Const MAPI_E_ATTACHMENT_OPEN_FAILURE = 32012
Global Const MAPI_E_ATTACHMENT_WRITE_FAILURE = 32013
Global Const MAPI_E_UNKNOWN_RECIPIENT = 32014
Global Const MAPI_E_BAD_RECIPTYPE = 32015
Global Const MAPI_E_NO_MESSAGES = 32016
Global Const MAPI_E_INVALID_MESSAGE = 32017
Global Const MAPI_E_TEXT_TOO_LARGE = 32018
Global Const MAPI_E_INVALID_SESSION = 32019
Global Const MAPI_E_TYPE_NOT_SUPPORTED = 32020
Global Const MAPI_E_AMBIGUOUS_RECIPIENT = 32021
Global Const MAPI_E_MESSAGE_IN_USE = 32022
Global Const MAPI_E_NETWORK_FAILURE = 32023
Global Const MAPI_E_INVALID_EDITFIELDS = 32024
Global Const MAPI_E_INVALID_RECIPS = 32025
Global Const MAPI_E_NOT_SUPPORTED = 32026

Global Const CONTROL_E_SESSION_EXISTS = 32050
Global Const CONTROL_E_INVALID_BUFFER = 32051
Global Const CONTROL_E_INVALID_READ_BUFFER_ACTION = 32052
Global Const CONTROL_E_NO_SESSION = 32053
Global Const CONTROL_E_INVALID_RECIPIENT = 32054
Global Const CONTROL_E_INVALID_COMPOSE_BUFFER_ACTION = 32055
Global Const CONTROL_E_FAILURE = 32056
Global Const CONTROL_E_NO_RECIPIENTS = 32057
Global Const CONTROL_E_NO_ATTACHMENTS = 32058

'-------------------------------------------------
'  MISCELLANEOUS GLOBAL CONSTANT DECLARATIONS (MAPI CONTROLS)
'-------------------------------------------------
Global Const RECIPTYPE_ORIG = 0
Global Const RECIPTYPE_TO = 1
Global Const RECIPTYPE_CC = 2
Global Const RECIPTYPE_BCC = 3

Global Const ATTACHTYPE_DATA = 0
Global Const ATTACHTYPE_EOLE = 1
Global Const ATTACHTYPE_SOLE = 2

'-------------------------------------------------
'  ODBC
'-------------------------------------------------
'field type constants
Global Const FT_TRUEFALSE = 1
Global Const FT_BYTE = 2
Global Const FT_INTEGER = 3
Global Const FT_LONG = 4
Global Const FT_CURRENCY = 5
Global Const FT_SINGLE = 6
Global Const FT_DOUBLE = 7
Global Const FT_DATETIME = 8
Global Const FT_STRING = 10
Global Const FT_MEMO = 12


VBQUERY.BAS

Rem These handle the SQL Server connection for this application

' Declare SqlConn as Integer in VBWin, Long in VBDOS
Global SqlConn As Integer

Global DBLIB_VERSION$
Global PrimaryWindowTitle$
Global ServerName$
Global LoginID$
Global Password$
Global DatabaseName$
Global SQLStatus%
Global Const LoginTimeout% = 30
Global Const QueryTimeout% = 60


VBQUERY.FRM

VERSION 2.00
Begin Form PrimaryWindow
   BackColor       =   &H00808000&
   Caption         =   "VBSQL Sample Query Tool - [No Server]"
   ForeColor       =   &H00000000&
   Height          =   6120
   Left            =   240
   ScaleHeight     =   5430
   ScaleWidth      =   8760
   Top             =   660
   Width           =   8880
   Begin VBSQL VBSQL1
      Caption         =   "Errors"
      Height          =   375
      Left            =   2640
      Top             =   0
      Visible         =   0   'False
      Width           =   1815
   End
   Begin CommandButton SEND_QUERY_BUTTON
      BackColor       =   &H00C0C0C0&
      Caption         =   "&Send Query"
      Enabled         =   0   'False
      Height          =   480
      Left            =   7080
      TabIndex        =   5
      Top             =   960
      Width           =   1440
   End
   Begin CommandButton NEW_QUERY_BUTTON
      BackColor       =   &H00C0C0C0&
      Caption         =   "&New Query"
      Enabled         =   0   'False
      Height          =   480
      Left            =   7080
      TabIndex        =   4
      Top             =   240
      Width           =   1440
   End
   Begin TextBox RESULT_FIELD
      BackColor       =   &H00FFFFFF&
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "Courier"
      FontSize        =   9.75
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   3120
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   3
      Top             =   1920
      Width           =   8520
   End
   Begin TextBox QUERY_FIELD
      BackColor       =   &H00FFFFFF&
      ForeColor       =   &H00000000&
      Height          =   960
      Left            =   120
      MultiLine       =   -1  'True
      ScrollBars      =   3  'Both
      TabIndex        =   1
      Top             =   480
      Width           =   6480
   End
   Begin Label Label2
      BackColor       =   &H00808000&
      Caption         =   "&Results:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   2
      Top             =   1680
      Width           =   720
   End
   Begin Label Label1
      BackColor       =   &H00808000&
      Caption         =   "&Query:"
      ForeColor       =   &H00000000&
      Height          =   240
      Left            =   120
      TabIndex        =   0
      Top             =   240
      Width           =   720
   End
   Begin Menu CONNECT_MENU
      Caption         =   "&Connection"
      Begin Menu LOGIN_MENU
         Caption         =   "&Login"
      End
      Begin Menu DATABASE_MENU
         Caption         =   "Change &Database"
      End
      Begin Menu EXIT_MENU
         Caption         =   "E&xit"
      End
   End
   Begin Menu HELP_MENU
      Caption         =   "&Help"
      Begin Menu ABOUT_MENU
         Caption         =   "&About..."
      End
   End
End
'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub ABOUT_MENU_Click ()
    About_Form.Show 1
End Sub

Sub ClearFields ()
QUERY_FIELD.Text = ""
RESULT_FIELD.Text = ""
End Sub

Sub DATABASE_MENU_Click ()
If SqlConn = 0 Then
MsgBox "Must login first"
Exit Sub
End If
CHNGDB.Show 1
End Sub

Sub EXIT_MENU_Click ()

ExitApplication
End Sub

Sub Form_Load ()

Rem
Rem Initialize the connection to SQL Server
Rem
InitializeApplication
MsgBox DBLIB_VERSION$
PrimaryWindowTitle = "VBSQL Sample Query Tool"
End Sub

Sub LOGIN_MENU_Click ()

Login.Show 1
NEW_QUERY_BUTTON.Enabled = -1
SEND_QUERY_BUTTON.Enabled = -1

End Sub

Sub NEW_QUERY_BUTTON_Click ()
ClearFields
QUERY_FIELD.SetFocus
End Sub

Sub SEND_QUERY_BUTTON_Click ()

On Error GoTo OutofRoom

Static OutputData() As String
ReDim Preserve OutputData(500) As String

Rem
Rem Get the query.  Make sure there is one
Rem

CRLF$ = Chr$(13) + Chr$(10)

cmd$ = QUERY_FIELD.Text

If cmd$ = "" Then
MsgBox "There is no query in the Query field"
Exit Sub
End If

Rem
Rem Clear the result array, and result controls first
Rem

i% = 0
For i% = 0 To 499
OutputData(i%) = ""
Next

Rem
Rem Fill the result array with the data
Rem Fill the results field
Rem

RESULT_FIELD.Text = ""
numrows& = Process_SQL_query(cmd$, OutputData())

'fill temporary string first, to avoid flashing with every row

For i% = 0 To numrows& - 1
DataLine$ = DataLine$ + OutputData(i%) + CRLF$

Next i%

RESULT_FIELD.Text = DataLine$
QUERY_FIELD.SetFocus

Exit Sub

OutofRoom:
'can run out of space filling strings.  If so, try just 20 rows
DataLine$ = ""
If numrows& > 20 Then
numrows& = 20

For i% = 0 To numrows& - 1
DataLine$ = DataLine$ + OutputData(i%) + CRLF$
Next i%

RESULT_FIELD.Text = DataLine$
End If
MsgBox "Out of room, output truncated"
QUERY_FIELD.SetFocus
Exit Sub

End Sub

Sub VBSQL1_Error (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, ErrorStr As String, RetCode As Integer)
' Call the required VBSQL error-handling function
' OSErr and OSErrStr not used in VBSQL for Windows, but DOS interprets
' anything other than -1 as an OS error
    OsErr% = -1

    RetCode% = UserSqlErrorHandler%(SqlConn, Severity%, ErrorNum%, OsErr%, ErrorStr$, OsErrStr$)

End Sub

Sub VBSQL1_Message (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
    UserSqlMsgHandler SqlConn, Message&, State%, Severity%, MsgStr$
End Sub


VBSQLGEN.BAS

'$INCLUDE: 'VBQUERY.BI'
'$INCLUDE: 'VBDSQL.BI'

Sub ChangePrimaryWindowCaption ()
PrimaryWindow.Caption = PrimaryWindowTitle + " - " + Servername$ + "/" + DatabaseName$
End Sub

Function CheckServerConnection () As Integer
If SqlConn <> 0 Then
    CheckServerConnection = 1
Else
    CheckServerConnection = 0
End If
End Function

Sub DoubleQuotes (InString As String)

Rem
Rem This will replace all double quotes with ""
Rem and all single quotes with ''
Rem

DOUBLEQUOTE$ = Chr$(34) + Chr$(34)
SINGLEQUOTE$ = Chr$(39) + Chr$(39)
Static mychar As String * 1

Rem
Rem Get the length of the string coming in
Rem Set the length of TmpString to length of string coming in + 100 new chars
Rem

y% = Len(InString)
TmpString$ = Space$(y% + 100)
i% = 1

For x% = 1 To y%

mychar$ = Mid$(InString, x%, 1)
If mychar$ = Chr$(34) Then
Mid$(TmpString$, i%, 2) = DOUBLEQUOTE$
i% = i% + 1
Else
If mychar$ = Chr$(39) Then
Mid$(TmpString$, i%, 2) = SINGLEQUOTE$
i% = i% + 1
Else
mychar$ = Mid$(InString, x%, 1)
Mid$(TmpString$, i%) = mychar$
End If
End If
i% = i% + 1
Next x%

InString$ = RTrim$(TmpString$)

End Sub

Function ExecuteSQLCommand (cmd As String) As Integer

Rem
Rem This routine executes a command(s) and returns whether the
Rem execute succeeded or failed.
Rem

SQLStatus% = SUCCEED
ExecuteSQLCommand = SUCCEED
If SqlCmd(SqlConn, cmd$) = FAIL% Then
SQLStatus% = FAIL
ExecuteSQLCommand = FAIL
End If
If SqlExec(SqlConn) = FAIL% Then
SQLStatus% = FAIL
ExecuteSQLCommand = FAIL
End If
End Function

Sub FixTextLineFeeds (InString As String)

Rem
Rem This will replace all LF characters in the InString with CRLF
Rem

CRLF$ = Chr$(13) + Chr$(10)
LF$ = Chr$(10)

Rem
Rem Get the length of the string coming in
Rem Set the length of TmpString to length of string coming in + 100 new chars
Rem

y% = Len(InString)
TmpString$ = Space$(y% + 100)
i% = 1

For x% = 1 To y%

mychar$ = Mid$(InString, x%, 1)
If mychar$ = LF$ Then
Mid$(TmpString$, i%, 1) = CRLF$
i% = i% + 1
Else
mychar$ = Mid$(InString, x%, 1)
Mid$(TmpString$, i%) = mychar$
End If
i% = i% + 1
Next x%

InString$ = RTrim$(TmpString$)
End Sub

Function GetDatabases (Database_Control As Control) As Integer

Rem
Rem This routine gets the name of all the databases on the SQL Server.
Rem Fill each element in the combobox or list box which is passed into this procedure
Rem execute the command.  Get each database name and fill the combobox.
Rem

If ExecuteSQLCommand("Select name from master..sysdatabases") = FAIL% Then
GetDatabases = FAIL
Exit Function
Else
If SqlResults(SqlConn) = FAIL% Then Exit Function
While SqlNextRow(SqlConn) <> NOMOREROWS%
Database_Control.AddItem SqlData(SqlConn, 1)
Wend
End If

Rem If this is a combobox we are filling, then display the first database in the list to start with

If TypeOf Database_Control Is ComboBox Then
Database_Control.Text = Database_Control.List(0)
End If

GetDatabases = SUCCEED
End Function

Function LoginToServer () As Integer

LoginToServer = SUCCEED

Rem
Rem Check to see if the connection is live, if so, then close it
Rem Set the max time to login to 30 seconds
Rem Open the new connection
Rem Change the caption of the application to reflect the server name and the database
Rem Set the max time we will wait for a SQL Server response
Rem

If SqlConn <> 0 Then SqlClose (SqlConn)
Status% = SqlSetLoginTime%(LoginTimeout%)
SqlConn = SqlOpenConnection(Servername$, LoginID$, password$, ProgramName$, ProgramName$)
If SqlConn <> 0 Then
DatabaseName$ = SqlName(SqlConn)
ChangePrimaryWindowCaption
Result% = SqlSetTime%(QueryTimeout%)
Else
DatabaseName$ = ""
Servername$ = ""
LoginToServer = FAIL
End If

End Function

Sub Logoff ()
If SqlConn <> 0 Then
SqlClose (SqlConn)
Servername$ = "[No server]"
DatabaseName$ = "[no database]"
ChangePrimaryWindowCaption
End If
End Sub

Function MakeRuleList (Rawtext As String) As String

Rem
Rem This functions takes a rule of type "IN" from sp_helptext and makes it a
Rem comma delimited list for easy use in list boxes
Rem

start% = InStr(1, Rawtext$, "'")
MakeRuleList = Mid$(Rawtext$, start%, Len(Rawtext$) - 2)
End Function

Sub ParseRule (Rulename() As String)

Rem
Rem This routine takes the comma delimeted rules,which came from the
Rem MakeRuleList procedure, removes the quotes and stores the values
Rem in an array.  This is good for use in combo and list boxes.
Rem

in$ = Rulename$(0)

start% = 1
For i% = 0 To 100
endpos% = InStr(start% + 1, in$, "'")
Rulename(i%) = Mid$(in$, start% + 1, (endpos% - start%) - 1)
start% = InStr(endpos% + 1, in$, "'")
If start% = 0 Then Exit For
Next i%

End Sub

Function Process_SQL_query (cmd As String, OutputData() As String) As Long

Rem
Rem This routine will process query rows and output the total number
Rem of rows which reflects the number of items in the output array.
Rem
Rem Define array for column lengths, column positions, and column types
Rem Define structures for getting a compute column's information and getting
Rem a regular column's information
Rem

Rem Declare a local error handler for string overflows
On Error GoTo CancelQuery

Static ColValue$
Static collengths() As Long
ReDim Preserve collengths(255) As Long
Static colpositions() As Integer
ReDim Preserve colpositions(255) As Integer
Static Coltypes() As Integer
ReDim Preserve Coltypes(50) As Integer

Process_SQL_query = 0

Rem
Rem Define the new line character and the tab key
Rem Get the command from the QUERY_FIELD.
Rem Fill the command buffer. If fail, then exit the subroutine.
Rem Execute the command
Rem

NL$ = Chr$(13) + Chr$(10)
COLSEP$ = " "

If cmd$ <> "" Then
If ExecuteSQLCommand(cmd$) = FAIL% Then Exit Function
End If

outputrowcnt% = 0

Rem
Rem Get each set of results
Rem Get the number of compute columns, order by columns, and select columns
Rem Get the exact position of each column (for lining up compute columns)
Rem

Do Until ResultProcess% = NOMORERESULTS%
    ResultProcess% = SqlResults(SqlConn)
    If ResultProcess% = NOMORERESULTS% Or ResultProcess% = FAIL Then Exit Do

    numcol% = SqlNumCols%(SqlConn)
    If numcol% > 0 Then
numorder% = SqlNumOrders%(SqlConn)
colline$ = ""
coluline$ = ""

Rem
Rem Get the column name and length for each column
Rem Format and output the column headings (max 256 chars wide).
Rem

For x% = 1 To numcol%
colname$ = SqlColName(SqlConn, x%)
Coltypes(x%) = SqlColType(SqlConn, x%)
collengths(x%) = SqlColLen(SqlConn, x%)

' templen holds length of column data.  truncate text and image
tmplen% = collengths(x%)
If tmplen% > 255 Then tmplen% = 255

actuallen& = Len(colname$)

If x% = 1 Then
   colpositions(x%) = 1
Else
colpositions(x%) = Len(colline$) + Len(COLSEP$)
End If

If actuallen& < tmplen% Then
colline$ = colline$ + colname$ + Space$((tmplen% - actuallen&) + 1) + COLSEP$
coluline$ = coluline$ + String$(Len(colname$), "_") + Space$((tmplen% - actuallen&) + 1) + COLSEP$
Else
colline$ = colline$ + colname$ + COLSEP$
coluline$ = coluline$ + String$(Len(colname$), "_") + COLSEP$
End If

Next x%

OutputData(outputrowcnt%) = colline$
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = coluline$
outputrowcnt% = outputrowcnt% + 1
OutputData(outputrowcnt%) = " "
outputrowcnt% = outputrowcnt% + 1

    End If   'end of numcol% > 0 test

Rem
Rem Get each row of data, and process according to type of row
Rem Output each row into the list box
Rem

    RowProcess% = 99
    Do Until RowProcess% = NOMOREROWS%
DataStr$ = ""
Result% = SqlNextRow(SqlConn)
If Result% = NOMOREROWS% Or Result% = FAIL Then Exit Do

Rem
Rem Process a COMPUTE Row  (Available in VB Win only).
Rem In DOS, this function pops up a message box saying COMPUTE rows are not supported.
Rem

If Result% <> REGROW Then
Process_altrows Result%, OutputData(), outputrowcnt%, colpositions()
Else

Rem
Rem Process a regular row.
Rem Get the column value and length.
Rem If it is a Text column, then change the LF to CRLF if they exist
Rem Align columns even with the column headings.
Rem
For x% = 1 To numcol%
ColValue$ = SqlData(SqlConn, x%)
actuallen& = Len(ColValue$)
If actuallen& > 255 Then
ColValue$ = Left$(ColValue$, 255)
actuallen& = 255
End If

If Coltypes(x%) = SQLTEXT% Then
FixTextLineFeeds ColValue$
End If

If x% <> numcol% Then
DataStr$ = DataStr$ + ColValue$ + Space$(colpositions(x% + 1) - colpositions(x%) - actuallen&)
Else
DataStr$ = DataStr$ + ColValue$
End If

ColValue$ = ""
Next x%

OutputData(outputrowcnt%) = DataStr$
End If
outputrowcnt% = outputrowcnt% + 1

    Loop        'End of row loop

Rem
Rem Output the number of rows affected by the query (if applicable)
Rem Output the sort order (if applicable)
Rem

    rowcnt& = SqlCount(SqlConn)
    If SqlIsCount(SqlConn) Then
DataStr$ = "(" + Str$(rowcnt&) + " rows affected)"
OutputData(outputrowcnt%) = " "
OutputData(outputrowcnt% + 1) = DataStr$
outputrowcnt% = outputrowcnt% + 2
    End If

    If numorder% > 0 Then
OutputData(outputrowcnt%) = " "
DataStr$ = "Sort Order: "
For y% = 1 To numorder%
ordercol$ = SqlColName(SqlConn, SqlOrderCol(SqlConn, y%))
DataStr$ = DataStr$ + " " + ordercol$
Next y%
OutputData(outputrowcnt% + 1) = DataStr$
outputrowcnt% = outputrowcnt% + 2
    End If

Loop        'End of result loop

Rem
Rem Check for return parameters and return status from stored procedures at the end
Rem of every result set.  Available in VBWin only.
Rem

Process_rpc_returns OutputData(), outputrowcnt%
Process_SQL_query = outputrowcnt%

Exit Function

CancelQuery:
Result% = SqlCancel%(SqlConn)
Msg$ = "Error number " + Str$(Err) + ":  " + Error$ + NL$
Msg$ = Msg$ + "Query Cancelled" + NL$
MsgBox Msg$, MB_ICONEXCLAMATION, "Visual Basic Error"
Exit Function

End Function

Function UserSqlErrorHandler% (SqlConn As Integer, Severity As Integer, ErrorNum As Integer, OsErr As Integer, ErrorStr As String, OsErrStr As String)
'UserSqlErrorHander% - This function is REQUIRED for all VBDSQL applications.  It
'is called by the VB-DOS interface code for DB-LIBRARY whenever a
'DB-LIBRARY error occurs.  In VB-Win, it can be called from the error event handler.

'This function can do anything EXCEPT call another
'DB-LIBRARY function (with the exception of SqlDead%, which you can
'call to determine if the connection is still intact).
'
'You can return 1 of 3 values:
' INTEXIT     - exit the program
' INTCANCEL   - cancel the operation
' INTCONTINUE - continue the operation (can only continue on timeout read
'                    errors, which usually occur if a table that is locked
'                    is updated or read)
'

Rem
Rem Only display message if it's not a notification that there's a server error
Rem

If ErrorNum% <> SQLESMSG% Then
MsgBox ("DBLibrary Error: " + Str$(ErrorNum%) + " " + ErrorStr$)
End If

'If an operating-system error occurred, print the error string.
 If OsErr% <> -1 Then
MsgBox ("Operating-System Error: " + OsErrStr$)
 End If

'Exit if the error is fatal.
If Severity% = EXFATAL Then
UserSqlErrorHandler% = INTEXIT
Else
UserSqlErrorHandler% = INTCANCEL
End If

End Function

Sub UserSqlMsgHandler (SqlConn As Integer, Message As Long, State As Integer, Severity As Integer, MsgStr As String)
'UserSqlMsgHandler - This procedure is REQUIRED for VBDSQL applicaitons.
'In VB-DOS, it is called by BASIC DB-LIBRARY whenever a connected server needs to
'issue a message to the client.  You can call it from the message handler event
'in VB-Win

NL$ = Chr$(13) + Chr$(10)

Rem
Rem Only display the message if it's not a general msg or a change language message
Rem

If Message& <> 5701 And Message& <> 5703 Then
Msg$ = "SQL Server Error: " + Str$(Message&) + " " + MsgStr$ + NL$
Msg$ = Msg$ + "State=" + Str$(State%) + ", Severity=" + Str$(Severity)

MsgBox Msg$
End If

End Sub

Function WarningMessage (MsgStr As String) As Integer

Rem
Rem This routine displays a warning message with a YES and NO button
Rem and returns the result.
Rem

Const MB_YESNO = 4
Const MB_ICONEXLAMATION = 48
Const IDYES = 6
Const IDNO = 7
Const DEFBUTTON2 = 256

DgDef% = MB_YESNO + MB_ICONEXCLAMATION + MB_DEFBUTTON2
Response% = MsgBox(MsgStr$, DgDef%, "System Warning")
If Response% = IDNO Then
WarningMessage = 0
Else
WarningMessage = 1
End If
End Function